code-lts / u2f-php-server
FIDO U2F 注册和身份验证的服务端处理类
Requires
- php: ^7.2 || ^8.0
- ext-openssl: *
Requires (Dev)
- phpunit/phpunit: ^8 || ^9 || ^10
- wdes/coding-standard: ^3.3
README
为 PHP 提供的 FIDO U2F 注册和身份验证的服务端处理。
保护您的在线账户和做您能做的事情来保护您的数据非常重要,并且随着黑客变得更加复杂,这一点变得越来越重要。FIDO 的 U2F 允许您添加一种简单且不引人注目的第二种因素认证方法,允许您的服务和/或应用程序的用户将硬件密钥链接到他们的账户。
关于此库分叉的故事
一切始于 2020 年 10 月的这个 问题。我迅速努力构建测试并提交了一个 拉取请求。但由于库的作者消失了,所以没有任何内容被合并。
这个分叉有什么变化?
对于第一个发布版本:没有任何变化,只是编写了测试。并且完成了对 PHP 8.1 的支持。未来的版本可能会有所变化。
U2F php 库系列
基础库
https://github.com/code-lts/U2F-php-server
内容
安装
composer require code-lts/u2f-php-server
需求
在开始使用此之前,您需要了解一些 重要事项
- OpenSSL 您需要至少 OpenSSL 1.0.0 或更高版本。
- 客户端处理 您需要能够与某种设备进行通信。
- HTTPS URL 这非常重要,没有 HTTPS,U2F 将无法工作。
- 数据存储 您需要某种类型的数据存储来存储所有已注册的 U2F 用户(尽管如果您有一个具有用户认证的系统,我假设您已经处理了这一点)。
OpenSSL
此存储库需要 OpenSSL 1.0.0 或更高版本。有关安装 OpenSSL 的更多详细信息,请参阅 php 手册。
还可以查看 兼容性代码,以检查您是否安装了正确的 OpenSSL 版本,并且不确定如何检查其他方式。
客户端(与 USB 设备通信的魔法 JavaScript 部分)
我的假设是,如果您正在寻找将 U2F 身份验证添加到 php 系统,那么您可能也在寻找一些客户端处理。您有一个启用了 U2F 的 USB 设备,并且希望该 USB 设备与浏览器以及运行 php 的服务器进行通信。
- 谷歌已经解决了这个问题: u2f-api.js
- Mastahyeti 创建了一个专门用于谷歌JavaScript客户端API的仓库: https://github.com/mastahyeti/u2f-api
HTTPS 和 SSL
为了使U2F工作,您的网站/服务必须使用HTTPS URL。没有HTTPS URL,您的代码将无法工作,所以为您的localhost和您的生产环境都获取一个。 https://letsencrypt.openssl.ac.cn/ 基本上加密一切。
术语
HID : 人机界面设备,例如USB设备 像这些设备
推荐的数据存储结构
您不需要完全遵循此结构,但您需要将注册数据与用户关联。您还需要存储密钥句柄、公钥和证书,计数器不是100%必要的,但它会使您的应用程序更安全。
待办事项:描述
流程
注册流程
- 用户导航到应用程序中的第二个因素认证页面。
... 待办事项:添加注册过程流程的其余部分 ...
身份验证流程
- 用户像平常一样导航到其登录页面,提交用户名和密码。
- 服务器收到POST请求认证数据,发生正常的用户名 + 密码验证。
- 在认证成功后,应用程序检查是否需要第二个因素认证。我们假设需要,否则用户将在这个阶段登录。
- 应用程序从应用程序数据存储中获取用户的注册签名:
$registrations
。 - 应用程序获取其ID,通常是应用程序可访问的域:
$appId
- 应用程序调用
U2F::makeAuthentication($registrations, $appId)
,该方法返回一个SignRequest
对象数组:$authenticationRequest
。 - 应用程序将数组JSON编码并传递给视图
- 当浏览器加载页面时,JavaScript将触发
u2f.sign(authenticationRequest, function(data){ // 回调逻辑 })
函数 - 视图将使用JavaScript / 浏览器轮询主机机器的端口以查找FIDO U2F设备
- 一旦找到HID,JavaScript / 浏览器将发送带有数据的签名请求。
- HID将提示用户授权签名请求
- 在成功后,HID返回认证数据
- JavaScript接收HID返回的数据并将其传递给服务器
- 应用程序接收返回的数据并将其传递给
U2F::authenticate($authenticationRequest, $registrations, $authenticationResponse)
方法 - 如果方法返回一个注册项且不抛出异常,则认证完成。
- 设置用户的会话,通知用户成功,并重定向。
示例代码
有关此存储库的完整工作代码示例,请参阅专门的示例存储库
您也可以使用以下方式安装它
$ git clone https://github.com/code-lts/U2F-php-server-examples.git
$ cd u2f-php-server-examples
$ composer install
兼容性代码
您只需在每个安装中调用此方法一次,并且仅在类给您意外错误的情况下进行调试时才需要。此方法调用将检查您的OpenSSL版本并确保它至少为1.0.0。
<?php require __DIR__ . '/vendor/autoload.php'; use CodeLts\U2F\U2FServer\U2FServer as U2F; var_dump(U2F::checkOpenSSLVersion());
注册代码
注册步骤1
开始注册过程
我们假设用户已成功认证并希望注册。
<?php require __DIR__ . '/vendor/autoload.php'; use CodeLts\U2F\U2FServer\U2FServer as U2F; session_start(); // This can be anything, but usually easier if you choose your applications domain and top level domain. $appId = 'yourdomain.tld'; // Call the makeRegistration method, passing in the app ID $registrationData = U2F::makeRegistration($appId); // Store the request for later $_SESSION['registrationRequest'] = $registrationData['request']; // Extract the request and signatures, JSON encode them so we can give the data to our javaScript magic $jsRequest = json_encode($registrationData['request']); $jsSignatures = json_encode($registrationData['signatures']); // now pass the data to a fictitious view. echo View::make('template/location/u2f-registration.html', [ 'jsRequest' => $jsRequest, 'jsSignatures' => $jsSignatures, ]);
注册步骤2
客户端,与USB通信
U2F密钥令牌的非AJAX客户端注册。当然,您可以在应用程序中使用AJAX,但使用AJAX和回调演示线性过程更容易。
<html> <head> <title>U2F Key Registration</title> </head> <body> <h1>U2F Registration</h1> <h2>Please enter your FIDO U2F device into your computer's USB port. Then confirm registration on the device.</h2> <div style="display:none;"> <form id="u2f_submission" method="post" action="auth/u2f-registration/confirm"> <input id="u2f_registration_response" name="registration_response" value="" /> </form> </div> <script type="javascript" src="https://raw.githubusercontent.com/google/u2f-ref-code/master/u2f-gae-demo/war/js/u2f-api.js"></script> <script> setTimeout(function() { // A magic JS function that talks to the USB device. This function will keep polling for the USB device until it finds one. u2f.register([<?php echo $jsRequest ?>], <?php echo $jsSignatures ?>], function(data) { // Handle returning error data if(data.errorCode && errorCode != 0) { alert('registration failed with error: ' + data.errorCode); // Or handle the error however you'd like. return; } // On success process the data from USB device to send to the server var registration_response = JSON.stringify(data); // Get the form items so we can send data back to the server var form = document.getElementById('u2f_submission'); var response = document.getElementById('u2f_registration_response'); // Fill and submit form. response.value = JSON.stringify(registration_response); form.submit(); }); }, 1000); </script> </body> </html>
注册步骤3
验证和密钥存储
这是注册的最后阶段。将注册响应数据与原始请求数据进行验证。
<?php require('vendor/autoload.php'); use CodeLts\U2F\U2FServer\U2FServer as U2F; session_start(); // Fictitious function representing getting the authenticated user object $user = getAuthenticatedUser(); try { // Validate the registration response against the registration request. // The output are the credentials you need to store for U2F authentication. $validatedRegistration = U2F::register( $_SESSION['registrationRequest'], json_decode($_POST['u2f_registration_response']) ); // Fictitious function representing the storing of the validated U2F registration data against the authenticated user. $user->storeRegistration($validatedRegistration); // Then let your user know what happened $userMessage = 'Success'; } catch( Exception $e ) { $userMessage = 'We had an error: '. $e->getMessage(); } //Fictitious view. echo View::make('template/location/u2f-registration-result.html', [ 'userMessage' => $userMessage, ]);
身份验证代码
认证步骤1
开始认证过程
我们假设用户已成功认证,并且之前已注册使用FIDO U2F。
<?php require('vendor/autoload.php'); use CodeLts\U2F\U2FServer\U2FServer as U2F; session_start(); // Fictitious function representing getting the authenticated user object $user = getAuthenticatedUser(); // Fictitious function, get U2F registrations associated with the user $registrations = $user->U2FRegistrations(); // This can be anything, but usually easier if you choose your applications domain and top level domain. $appId = 'yourdomain.tld'; // Call the U2F makeAuthentication method, passing in the user's registration(s) and the app ID $authenticationRequest = U2F::makeAuthentication($registrations, $appId); // Store the request for later $_SESSION['authenticationRequest'] = $authenticationRequest; // now pass the data to a fictitious view. echo View::make('template/location/u2f-authentication.html', [ 'authenticationRequest' => $authenticationRequest, ]);
认证步骤 2
客户端,与USB通信
U2F密钥令牌的非AJAX客户端端认证。当然,您可以在应用程序中使用AJAX,但不用AJAX和回调函数来展示线性过程会更简单。
<html> <head> <title>U2F Key Authentication</title> </head> <body> <h1>U2F Authentication</h1> <h2>Please enter your FIDO U2F device into your computer's USB port. Then confirm authentication on the device.</h2> <div style="display:none;"> <form id="u2f_submission" method="post" action="auth/u2f-authentication/confirm"> <input id="u2f_authentication_response" name="authentication_response" value="" /> </form> </div> <script type="javascript" src="https://raw.githubusercontent.com/google/u2f-ref-code/master/u2f-gae-demo/war/js/u2f-api.js"></script> <script> setTimeout(function() { // Magic JavaScript talking to your HID u2f.sign(<?php echo $authenticationRequest; ?>, function(data) { // Handle returning error data if(data.errorCode && errorCode != 0) { alert('Authentication failed with error: ' + data.errorCode); // Or handle the error however you'd like. return; } // On success process the data from USB device to send to the server var authentication_response = JSON.stringify(data); // Get the form items so we can send data back to the server var form = document.getElementById('u2f_submission'); var response = document.getElementById('u2f_authentication_response'); // Fill and submit form. response.value = JSON.stringify(authentication_response); form.submit(); }); }, 1000); </script> </body> </html>
认证步骤 3
验证
这是认证的最后阶段。将认证响应数据与原始请求数据进行验证。
<?php require('vendor/autoload.php'); use CodeLts\U2F\U2FServer\U2FServer as U2F; session_start(); // Fictitious function representing getting the authenticated user object $user = authenticatedUser(); // Fictitious function, get U2F registrations associated with the user $registrations = $user->U2FRegistrations(); try { // Validate the authentication response against the registration request. // The output are the credentials you need to store for U2F authentication. $validatedAuthentication = U2F::authenticate( $_SESSION['authenticationRequest'], $registrations, json_decode($_POST['u2f_authentication_response']) ); // Fictitious function representing the updating of the U2F token count integer. $user->updateU2FRegistrationCount($validatedAuthentication); // Then let your user know what happened $userMessage = 'Success'; } catch( Exception $e ) { $userMessage = 'We had an error: '. $e->getMessage(); } //Fictitious view. echo View::make('template/location/u2f-authentication-result.html', [ 'userMessage' => $userMessage, ]);
再次提醒,如果您只想下载一些示例代码进行测试,请安装此仓库中为该仓库编写的完整工作示例代码,请参阅专门的示例仓库
您也可以使用以下方式安装它
$ git clone https://github.com/code-lts/U2F-php-server-examples.git
$ cd u2f-php-server-examples
$ composer install
许可证
致谢
此仓库最初基于Yubico php-u2flib-server