sk-id-solutions / smart-id-php-client
Smart-ID Relying Party PHP Api客户端
Requires
- php: >=7.4
- ext-curl: *
- ext-json: *
- ext-openssl: *
- phpseclib/phpseclib: ~3.0
Requires (Dev)
- phpunit/phpunit: 9.*
README
Smart-ID PHP客户端
简介
Smart-ID PHP客户端可以轻松地将Smart-ID解决方案集成到信息系统中或电子服务中。
功能
- 用户身份验证的简单界面
Smart-ID PHP客户端与PHP 7.4和PHP 8+兼容
此PHP客户端不能用于创建数字签名容器,因为PHP没有类似DigiDoc4J的库。
安装
安装Smart-ID PHP客户端的推荐方式是通过Composer
composer require sk-id-solutions/smart-id-php-client "2.3.1"
有关最新发布的版本,请参阅packagist,有关详细信息,请参阅变更日志
如何使用它
配置客户端详情和https锁定
用于防止中间人攻击。更多关于使用Smart ID时的中间人攻击信息。
设置客户端以信任特定的公钥。生产环境中使用的SSL证书可以在这里找到,演示环境证书可以在这里找到。
setPublicSslKeys方法需要一个由";"分隔的公钥sha256哈希字符串。
openssl x509 -inform PEM -in certificate.pem -noout -pubkey | openssl rsa -pubin -outform der 2>/dev/null | openssl dgst -sha256 -binary | openssl enc -base64
提供的字符串应为格式sha256//sha256-hash-of-the-public-key;
$this->client = new Client(); $this->client ->setRelyingPartyUUID( '00000000-0000-0000-0000-000000000000' ) // In production replace with your UUID ->setRelyingPartyName( 'DEMO' ) // In production replace with your name ->setHostUrl( 'https://sid.demo.sk.ee/smart-id-rp/v2/' ) // In production replace with production service URL // in production replace with correct server SSL key ->setPublicSslKeys("sha256//Ps1Im3KeB0Q4AlR+/J9KFd/MOznaARdwo4gURPCLaVA=");
使用语义标识符进行身份验证
以下示例还演示了如何验证身份验证结果以及如何处理异常。
$semanticsIdentifier = SemanticsIdentifier::builder() ->withSemanticsIdentifierType('PNO') ->withCountryCode('LT') ->withIdentifier('30303039914') ->build(); // For security reasons a new hash value must be created for each new authentication request $authenticationHash = AuthenticationHash::generate(); $verificationCode = $authenticationHash->calculateVerificationCode(); // display verification code to the user echo "Verification code: " . $verificationCode . "\n"; $authenticationResponse = null; try { $authenticationResponse = $this->client->authentication() ->createAuthentication() ->withSemanticsIdentifier( $semanticsIdentifier ) ->withAuthenticationHash( $authenticationHash ) ->withCertificateLevel( CertificateLevelCode::QUALIFIED ) // Certificate level can either be "QUALIFIED" or "ADVANCED" ->withAllowedInteractionsOrder((array( Interaction::ofTypeVerificationCodeChoice("Enter awesome portal?"), Interaction::ofTypeDisplayTextAndPIN("Enter awesome portal?")))) ->authenticate(); // this blocks until user has responded } catch (UserRefusedException $e) { throw new RuntimeException("You pressed cancel in Smart-ID app."); } catch (UserSelectedWrongVerificationCodeException $e) { throw new RuntimeException("You selected wrong verification code in Smart-ID app. Please try again. "); } catch (SessionTimeoutException $e) { throw new RuntimeException("Session timed out (you didn't enter PIN1 in Smart-ID app)."); } catch (UserAccountNotFoundException $e) { throw new RuntimeException("User does not have a Smart-ID account"); } catch (UserAccountException $e) { throw new RuntimeException("Unable to authenticate due to a problem with your Smart-ID account."); } catch (EnduringSmartIdException $e) { throw new RuntimeException("Problem with connecting to Smart-ID service. Please try again later."); } catch (SmartIdException $e) { throw new RuntimeException("Smart-ID authentication process failed for uncertain reason: ". $e); } // create a folder with name "trusted_certificates" and set path to that folder here: $pathToFolderWithTrustedCertificates = __DIR__ . '/../../../resources'; $authenticationResponseValidator = new AuthenticationResponseValidator($pathToFolderWithTrustedCertificates); $authenticationResult = $authenticationResponseValidator->validate( $authenticationResponse ); if ($authenticationResult->isValid()) { echo "Hooray! Authentication result is valid"; } else { throw new RuntimeException("Error! Response is not valid! Error(s): ". implode(",", $authenticationResult->getErrors())); } $authenticationIdentity = $authenticationResult->getAuthenticationIdentity(); echo "hello name: " . $authenticationIdentity->getGivenName() . ' ' . $authenticationIdentity->getSurName() . "\n"; echo "from " . $authenticationIdentity->getCountry() . "\n"; echo "born " . $authenticationIdentity->getDateOfBirth()->format("D d F o") . "\n"; // you might need this if you want to start authentication with document number echo "Authenticated user documentNumber is: ".$authenticationResponse->getDocumentNumber(). "\n";
验证身份验证结果
为了验证身份验证结果(它是由Smart-ID签名的,而不是中间人或意外从生产连接到演示环境),您需要创建目录trusted_certificates并将smart-id证书放入其中。您可以从上述“https锁定”章节中描述的链接中获取所需的证书。
资源目录的示例路径:$resourceLocation = '/path/to/resource',其中将查找名为trusted_certificates的目录并从中读取证书。如果没有指定路径,它将采用由客户端本身提供的信任证书。它们位于src/resources/trusted_certificates。
关于验证码和验证签名的一些说明
这是幕后发生的事情(除了步骤#5之外的所有步骤都由该库执行)
- 对于每次新的身份验证,库都会生成一个随机值(存储在变量'dataToSign'中)
- 从这个随机值中计算出摘要(SHA-512、SHA-384或SHA-256)(存储在变量'hash'中)
- 显示给最终用户的验证码是从这个摘要中计算出来的。
- 将包含'hash'值的身份验证请求发送到服务器。
- 现在在用户的手机上执行签名过程,Smart-ID REST服务返回用户的签名和身份验证证书。
- 该库验证返回的签名值确实是有效的签名。 (对于验证,它使用'dataToSign'的值(而不是存储在'hash'中的摘要)与认证签名一起使用。)
提取已认证人员的出生日期
所有爱沙尼亚和立陶宛的国民身份证号都包含出生日期信息,这就是为什么getDateOfBirth()函数总是能正确返回它们的值。此外,老式的拉脱维亚国民身份证号中也包含出生日期信息。
echo "born " . $authenticationIdentity->getDateOfBirth()->format("D d F o") . "\n";
对于持有新型拉脱维亚国民身份证号的人员,出生日期是从证书的单独字段中解析出来的,但对于某些较旧的Smart-id账户(2017年7月1日至2021年5月20日之间发行的)可能缺少此值。
有关证书中此单独字段的可用性的更多信息: https://github.com/SK-EID/smart-id-documentation/wiki/FAQ#where-can-i-find-users-date-of-birth
使用文件编号进行认证
当您(由于某种原因)在短时间内重新认证用户并希望用户使用之前相同的设备时,可能需要使用文件编号而不是语义标识符。
如果用户有多个Smart-ID账户(例如,一个在手机上,一个在平板电脑上),那么当使用语义标识符进行认证时,两个设备都会启动流程(用户可以选择使用任一设备并输入PIN)。由于文件编号是设备特定的,因此当您使用文件编号时,只有用户的一个设备会启动认证流程。
成功认证后,您将获得用户的documentNumber。请参见上面的示例,其中documentNumber在最后被记录下来。
$authenticationResponse = $this->client->authentication() ->createAuthentication() ->withDocumentNumber( 'PNOLT-30303039914-MOCK-Q' ) ->withAuthenticationHash( $authenticationHash ) ->withCertificateLevel( CertificateLevelCode::QUALIFIED ) // Certificate level can either be "QUALIFIED" or "ADVANCED" ->withAllowedInteractionsOrder((array( Interaction::ofTypeVerificationCodeChoice("Enter awesome portal?"), Interaction::ofTypeDisplayTextAndPIN("Enter awesome portal?")))) ->authenticate(); // this blocks until user has responded
每5秒轮询一次进行认证
之前的示例会在用户输入PIN代码、按下取消或由于其他原因(如超时)认证失败之前阻塞。此示例演示了每5秒轮询状态。
$sessionId = null; try { $sessionId = $this->client->authentication() ->createAuthentication() ->withSemanticsIdentifier( $semanticsIdentifier ) // or with document number: ->withDocumentNumber( 'PNOEE-10101010005-Z1B2-Q' ) ->withAuthenticationHash( $authenticationHash ) ->withCertificateLevel( CertificateLevelCode::QUALIFIED ) // Certificate level can either be "QUALIFIED" or "ADVANCED" ->withAllowedInteractionsOrder((array( Interaction::ofTypeVerificationCodeChoice("Ready to poll?"), Interaction::ofTypeDisplayTextAndPIN("Ready to poll status repeatedly?")))) ->startAuthenticationAndReturnSessionId(); } catch (SmartIdException $e) { // Handle exception (more on exceptions in "Handling intentional exceptions") throw new RuntimeException("Authentication failed. NB! Use exception handling blocks from above example: ". $e); } $authenticationResponse = null; try { for ( $i = 0; $i <= 10; $i++ ) { $authenticationResponse = $this->client->authentication() ->createSessionStatusFetcher() ->withSessionId( $sessionId ) ->withAuthenticationHash( $authenticationHash ) ->withSessionStatusResponseSocketTimeoutMs( 10000 ) ->getAuthenticationResponse(); if ( !$authenticationResponse->isRunningState() ) { break; } sleep( 5 ); } } catch (SmartIdException $e) { throw new RuntimeException("Authentication failed. NB! Use exception handling blocks from above example.". $e); } // validate authentication result, get authentication person details