sk-id-solutions/smart-id-php-client

Smart-ID Relying Party PHP Api客户端

2.3.1 2022-08-23 20:10 UTC

This package is not auto-updated.

Last update: 2024-09-18 06:05:53 UTC


README

Build Status Latest Version License: LGPL v3

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之外的所有步骤都由该库执行)

  1. 对于每次新的身份验证,库都会生成一个随机值(存储在变量'dataToSign'中)
  2. 从这个随机值中计算出摘要(SHA-512、SHA-384或SHA-256)(存储在变量'hash'中)
  3. 显示给最终用户的验证码是从这个摘要中计算出来的。
  4. 将包含'hash'值的身份验证请求发送到服务器。
  5. 现在在用户的手机上执行签名过程,Smart-ID REST服务返回用户的签名和身份验证证书。
  6. 该库验证返回的签名值确实是有效的签名。 (对于验证,它使用'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