pronin/webauthn-emulator

PHP 库,用于模拟 WebAuthn 验证器,如 YubiKeys、Touch ID、Face ID、Windows Hello 等

1.1.0 2023-12-28 16:14 UTC

This package is auto-updated.

Last update: 2024-10-03 14:24:27 UTC


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 对象中的附加数据。如果省略,则仅包含 typeoriginchallenge

返回一个类似于 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 对象中的附加数据。如果省略,则仅包含 typeoriginchallenge

返回一个类似于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