pronin / webauthn-emulator
PHP 库,用于模拟 WebAuthn 验证器,如 YubiKeys、Touch ID、Face ID、Windows Hello 等
Requires
- php: ^8.0
- ext-openssl: *
- spomky-labs/cbor-php: ^3.0
Requires (Dev)
- guzzlehttp/guzzle: ^7.4
README
一个简单的 PHP WebAuthn (FIDO2) 客户端库
webauthn-emulator
是一个 PHP 库,它模拟了 WebAuthn 兼容的验证器,如 YubiKeys、Touch ID、Face ID、Windows Hello 等。它本质上模拟了浏览器中 Web Authentication API 的行为,使得开发者可以将 WebAuthn 客户端端认证集成到他们的应用程序中。
目录
功能
- 为 WebAuthn 注册(声明)和认证(断言)请求生成响应。
- 与任何密钥存储兼容。
- 支持多个用户和凭据。
安装
安装 webauthn-emulator
的推荐方法是使用 Composer
composer require pronin/webauthn-emulator
快速开始
通过 Composer 安装 webauthn-emulator
后,您可以快速创建一个 Authenticator
类的实例,并使用它来处理 WebAuthn 注册和认证。
初始化验证器
<?php use WebauthnEmulator\Authenticator; use WebauthnEmulator\CredentialRepository\FileRepository; // Instantiate the file repository to store credentials $storage = new FileRepository('path/to/credential/storage.txt'); // Create the authenticator instance $authenticator = new Authenticator($storage);
生成注册挑战的响应
// Sample registration challenge. See examples/webauthnio_reg.php for a complete example. $registrationChallenge = "rp" => [ "name" => "webauthn.io", "id" => "webauthn.io" ], "user" => [ "id" => "dGVzdDMxNA", "name" => "test777", "displayName" => "test777" ], "challenge" => "7BYLAiLMeNm3103ZBmBIHxEI-5-O_5uWtkaNWC4oTzR47KtFLfs7oy0i0qCJ3A-ENpvsNMbdWbkHGvcFZyhBZQ", "pubKeyCredParams" => [ [ "type" => "public-key", "alg" => -7 ] ], "timeout" => 60000, "excludeCredentials" => [], "authenticatorSelection" => [ "residentKey" => "preferred", "requireResidentKey" => false, "userVerification" => "preferred" ], "attestation" => "none", "extensions" => [ "credProps" => true ] ]; // Generate a response to the registration challenge $attestation = $authenticator->getAttestation($registrationChallenge);
生成认证挑战的响应
// Sample authentication challenge. See examples/webauthnio_login.php for a complete example. $authChallenge = [ "challenge" => "RzckEwPCCFGmO-lkYs_z15YCKAsEcoW49X2DSuuCzL2b6iXjozuap5iVnWzenmfhbsTs0-mqKOwkvhbk8uDbRw", "timeout" => 60000, "rpId" => "webauthn.io", "allowCredentials" => [ [ "id" => "2h0MoJD7Slojb_SecLOCfKyMDnC-mEDnFeYLTAefaz4", "type" => "public-key", "transports" => [] ], [ "id" => "ySHAlkz_D3-MTo2GZwXNRhDVdDLR23oQaSI3cGz-7Hc", "type" => "public-key", "transports" => [] ] ], "userVerification" => "preferred" ]; // Generate a response to the authentication challenge $assertion = $authenticator->getAssertion( $authChallenge['rpId'], $authChallenge['allowCredentials'], $authChallenge['challenge'] );
使用
webauthn-emulator
库提供了一个简单直观的接口来模拟 WebAuthn 验证器。以下是您将用于与库交互的主要方法,以及它们参数的详细说明。
初始化
要开始使用模拟器,请使用实现 RepositoryInterface
的凭据仓库实例化 Authenticator
类。库包含一个用于测试的 FileRepository
,您可以将其替换为用于不同存储解决方案的自定义仓库。
use WebauthnEmulator\Authenticator; use WebauthnEmulator\CredentialRepository\FileRepository; // Instantiate the file repository to store credentials $storage = new FileRepository('path/to/credential/storage.txt'); // Create the authenticator instance $authenticator = new Authenticator($storage);
注册(声明)
getAttestation
创建一个新密钥对,并生成响应以应对 WebAuthn 注册挑战,模拟了将新的验证器注册到 WebAuthn 依赖方的过程。其行为类似于在浏览器中依次调用 navigator.credentials.create() 和 navigator.credentials.get()。有关详细信息,请参阅 Web Authentication API 文档。
getAttestation
接受以下参数
registerOptions
(数组,必需):包含来自依赖方的注册挑战数据,包括依赖方的信息、用户数据、挑战和其他注册选项。origin
(字符串,可选):依赖方网站的来源。如果省略,则默认为从rpId
构造的来源。extra
(数组,可选):包含在clientDataJSON
对象中的附加数据。如果省略,则仅包含type
、origin
和challenge
。
返回一个类似于 PublicKeyCredential 的数组。
$registrationChallenge = [ // ... (challenge data provided by the relying party) ]; // Generate a response to the registration challenge $attestation = $authenticator->getAttestation( registerOptions: $registrationChallenge, origin: 'https://service.example.com', // optional extra: ['crossOrigin' => false] // optional ); echo(json_encode($attestation, JSON_PRETTY_PRINT)); /* Output: { "id": "HB_Pkygg...LQK3WkA", "rawId": "HB\/Pkygg...LQK3WkA=", "response": { "clientDataJSON": "eyJ0e...pbyJ9", "attestationObject": "o2Nmb...y4kw=" }, "type": "public-key" } */
认证(断言)
getAssertion
方法生成对WebAuthn身份验证挑战的响应,模拟使用先前注册的密钥登录的过程。其行为类似于调用浏览器的navigator.credentials.get()。
getAssertion
方法接受以下参数
rpId
(字符串,必需):依赖方标识符,通常是依赖方网站的域名。credentialIds
(字符串|数组|null,可选):单个凭据ID、凭据描述符数组或null。它标识哪些凭据可以用于身份验证。如果为null或省略,则可以使用rpId
的任何可用凭据。challenge
(字符串,必需):依赖方发送的base64或base64url编码的挑战,以防止重放攻击。origin
(字符串,可选):依赖方网站的来源。如果省略,则默认为从rpId
构造的来源。extra
(数组,可选):包含在clientDataJSON
对象中的附加数据。如果省略,则仅包含type
、origin
和challenge
。
返回一个类似于PublicKeyCredential的数组,它是浏览器中navigator.credentials.get()的输出。
$authChallenge = [ // ... (challenge data provided by the relying party) ]; // Generate a response to the authentication challenge $assertion = $authenticator->getAssertion( rpId: $authChallenge['rpId'], credentialIds: $authChallenge['allowCredentials'], challenge: $authChallenge['challenge'] origin: 'https://service.example.com', // optional extra: ['crossOrigin' => false] // optional ); echo(json_encode($assertion, JSON_PRETTY_PRINT)); /* Output: { "id": "HB_Pkygg...LQK3WkA", "rawId": "HB\/Pkygg...LQK3WkA=", "response": { "authenticatorData": "E4Mf1...AAA==", "clientDataJSON": "eyJ0e...pbyJ9", "signature": "MEYCI...lzwEj", "userHandle": "ZmRmM...mZDk2" }, "type": "public-key" } */
Base64url 与 Base64 编码
WebAuthn服务器通常使用base64url编码来表示URL安全格式的二进制数据。这种编码类似于标准的base64,但使用不同的字符进行填充以及表示索引表中的第62和63个值。具体来说,base64url编码将+
替换为-
,将/
替换为_
,并省略填充字符=
。这使得它适用于在URL和文件名中使用,而无需进行额外编码。
不同的WebAuthn服务器实现在使用base64url编码方面存在差异。一些使用base64url编码的字符串作为'id'或'challenge'字段,而另一些则使用标准base64编码或两者的混合。这种不一致性给开发者带来了负担,需要确定特定服务器使用的编码方式,并相应地进行数据转换。
webauthn-emulator
库提供了两个实用方法来处理这些编码变化
-
base64Normal2Url
:将标准base64编码的字符串或数组转换为base64url编码。此方法在需要向期望base64url编码字符串的服务器发送数据时很有用。 -
base64Url2Normal
:将base64url编码的字符串或数组转换回带有填充的标准base64编码。此方法在从使用base64url编码的服务器接收数据并在将其馈送到模拟器之前很有帮助。
这些方法可以递归地应用于数组,使得轻松编码或解码数组中的所有元素。
Base64url 编码/解码方法的用法
在与WebAuthn服务器交互时,您可能需要编码或解码'id'、'challenge'或其他二进制数据字段。以下是使用提供的方法的方式
使用单个字符串
use WebauthnEmulator\Authenticator; // Example of recoding a standard base64 string to base64url echo Authenticator::base64Normal2Url('wib1OPW9EkDeiwUoyTgJ1+PpFG4dljeXodqRX15DG+gBAAAABQ=='); // Output: wib1OPW9EkDeiwUoyTgJ1-PpFG4dljeXodqRX15DG-gBAAAABQ // Example of recoding a base64url string to standard base64 echo Authenticator::base64Url2Normal('wib1OPW9EkDeiwUoyTgJ1-PpFG4dljeXodqRX15DG-gBAAAABQ'); // Output: wib1OPW9EkDeiwUoyTgJ1+PpFG4dljeXodqRX15DG+gBAAAABQ==
使用数组
use WebauthnEmulator\Authenticator; $input = [ "id" => "HB_PkyggPmHCHbcYyQCfLXTakdmq3WGCcOBjLQK3WkA", // already base64url-encoded "rawId" => "HB/PkyggPmHCHbcYyQCfLXTakdmq3WGCcOBjLQK3WkA=", // standard base64 ]; // Example of recoding an array of standard base64 strings to base64url $base64urlArray = Authenticator::base64Normal2Url($input); /* Result: [ "id" => "HB_PkyggPmHCHbcYyQCfLXTakdmq3WGCcOBjLQK3WkA", // left as is "rawId" => "HB_PkyggPmHCHbcYyQCfLXTakdmq3WGCcOBjLQK3WkA", // recoded to base64url ] */ // Example of recoding an array of base64url to standard base64 $base64Array = Authenticator::base64Url2Normal($input); /* Result: [ "id" => "HB/PkyggPmHCHbcYyQCfLXTakdmq3WGCcOBjLQK3WkA=", // recoded to standard base64 "rawId" => "HB/PkyggPmHCHbcYyQCfLXTakdmq3WGCcOBjLQK3WkA=", // left as is ] */
使用这些方法,您可以确保从WebAuthn服务器发送和接收的数据正确编码,无论服务器的具体实现细节如何。
更多示例
有关如何使用webauthn-emulator
模拟WebAuthn注册和身份验证过程的更详细示例,请参阅存储库中的示例目录。
- webauthnio_reg.php:在webauthn.io演示服务器上注册。
- webauthnio_login.php:在webauthn.io演示服务器上登录。
- yubico_reg.php:在Yubico演示服务器上注册(非标准查询结构)。
- yubico_login.php:在Yubico演示服务器上登录(非标准查询结构)。
- lubuch_reg.php:使用 Lubu.ch 示例服务器进行注册(非标准 base64url/binary 编码)。
- lubuch_login.php:使用 Lubu.ch 示例服务器进行登录(非标准 base64url/binary 编码)。
- quadoio_reg.php:使用 Quado 示例服务器进行注册(自定义来源和附加数据)。
- quadoio_login.php:使用 Quado 示例服务器进行登录(自定义来源和附加数据)。
每个服务器都有其独特之处,因此示例演示了如何处理不同场景,例如非标准 base64url 编码、自定义来源和附加数据。
存储凭据
webauthn-emulator
依赖于凭证存储库来存储和管理凭证。默认情况下,提供了一个使用基于文件的存储库(FileRepository
)的示例实现。但是,您可以根据 RepositoryInterface
实现自己的存储库来在其他地方存储凭证,例如数据库。
以下是使用提供的 FileRepository
的示例
use WebauthnEmulator\CredentialRepository\FileRepository; // Path to the JSON file that will store the credentials $storagePath = 'path/to/credential/storage.txt'; // Create a new FileRepository instance $storage = new FileRepository($storagePath);
要创建自定义存储库,实现 RepositoryInterface
use WebauthnEmulator\CredentialRepository\RepositoryInterface; use WebauthnEmulator\CredentialInterface; class CustomRepository implements RepositoryInterface { // Implement the required methods public function save(CredentialInterface $credential): static { // Logic to save the credential } public function get(string $rpId): array { // Logic to retrieve credentials by rpId } public function getById(string $rpId, string $id): CredentialInterface { // Logic to retrieve a credential by rpId and id } }
在初始化 Authenticator
时,将 FileRepository
替换为您的自定义存储库
use WebauthnEmulator\Authenticator; // Assuming $customStorage is an instance of your custom repository $authenticator = new Authenticator($customStorage);
这种灵活性允许您根据应用程序的需求,将 webauthn-emulator
集成到各种存储后端,例如数据库或云存储解决方案。
限制
webauthn-emulator
设计用于支持 WebAuthn 注册和认证所需的核心功能。但是,有一些限制需要注意
- 模拟器目前仅支持 'none' 认证格式。其他格式如 'packed'、'tpm'、'android-safetynet' 等,均不受支持。
- 库限制为使用 ES256(算法:-7)签名算法进行公钥凭证的签名。其他算法如 RS256 或 EdDSA 目前不受支持。如果您需要支持其他算法,请提交问题或拉取请求。
贡献和报告问题
欢迎贡献力量!如果您想贡献或有发现错误,请提交拉取请求或在该项目的 GitHub 存储库上报告问题。在贡献时,请确保您的代码遵循项目的现有样式以保持一致性。
对于功能请求或错误报告,请检查 GitHub Issues,查看是否已报告。如果没有,请创建一个带有详细描述的新问题。
许可
webauthn-emulator
是开源软件,采用 MIT 许可证。有关更多详细信息,请参阅存储库中的 LICENSE 文件。
致谢
特别感谢 Rayaz Sultanov(codeproger)共同创建 webauthn-emulator
。