sk-id-solutions / mobile-id-php-client
Mobile-ID 客户端 PHP API
Requires
- php: >=7.4||~8
- ext-curl: *
- ext-json: *
- ext-openssl: *
- sop/x509: *
Requires (Dev)
- phpunit/phpunit: 9.*
This package is auto-updated.
Last update: 2024-09-11 20:10:14 UTC
README
本地运行
运行 composer install
获取所有依赖。然后你可以运行测试 php vendor/phpunit/phpunit/phpunit
演示应用程序
有一个你可以本地运行的 演示应用程序。
功能
- 简单接口进行 Mobile-ID 认证
- 拉取用户的签名证书
由于没有像 DigiDoc4J 这样的 PHP 库,此 PHP 客户端不能用于创建数字签名的容器。
要求
- PHP 7.4 或更高版本
- PHP 必须使用 --with-gmp 选项编译以支持 GMP
安装
安装 Mobile-ID PHP 客户端的首选方式是通过 Composer
composer require sk-id-solutions/mobile-id-php-client "<VERSION>"
如何使用它
以下是一些使用 Mobile-ID PHP 客户端进行认证的示例
您需要确保您的应用程序中启用了 Composer 自动加载
require_once __DIR__ . '/vendor/autoload.php';
认证示例
有关 'use' 的类列表,请参阅 ReadmeTest.php
// step #1 - validate user input try { $phoneNumber = MidInputUtil::getValidatedPhoneNumber($this->userData['phoneNumber']); $nationalIdentityNumber = MidInputUtil::getValidatedNationalIdentityNumber($this->userData['nationalIdentityNumber']); } catch (MidInvalidPhoneNumberException $e) { echo 'The phone number you entered is invalid'; } catch (MidInvalidNationalIdentityNumberException $e) { echo 'The national identity number you entered is invalid'; } // step #2 - create client with long-polling. // withSslPinnedPublicKeys() is explained later in this document $client = MobileIdClient::newBuilder() ->withRelyingPartyUUID($this->config['relyingPartyUUID']) ->withRelyingPartyName($this->config['relyingPartyName']) ->withHostUrl($this->config['hostUrl']) ->withLongPollingTimeoutSeconds(60) ->withSslPinnedPublicKeys("sha256//k/w7/9MIvdN6O/rE1ON+HjbGx9PRh/zSnNJ61pldpCs=;sha256//some-future-ssl-host-key") ->build(); // step #3 - generate hash & calculate verification code and display to user $authenticationHash = MobileIdAuthenticationHashToSign::generateRandomHashOfDefaultType(); $verificationCode = $authenticationHash->calculateVerificationCode(); // step #4 - display $verificationCode (4 digit code) to user echo 'Verification code: '.$verificationCode."\n"; // step #5 - create request to be sent to user's phone $request = AuthenticationRequest::newBuilder() ->withPhoneNumber($phoneNumber) ->withNationalIdentityNumber($nationalIdentityNumber) ->withHashToSign($authenticationHash) ->withLanguage(ENG::asType()) ->withDisplayText("Log into self-service?") ->withDisplayTextFormat(DisplayTextFormat::GSM7) ->build(); // step #6 - send request to user's phone and catch possible errors try { $response = $client->getMobileIdConnector()->initAuthentication($request); } catch (MidNotMidClientException $e) { echo "User is not a MID client or user's certificates are revoked."; } catch (MidUnauthorizedException $e) { echo 'Integration error with Mobile-ID. Invalid MID credentials'; } catch (MissingOrInvalidParameterException $e) { echo 'Problem with MID integration'; } catch (MidInternalErrorException $e) { echo 'MID internal error'; } // step #7 - keep polling for session status until we have a final status from phone $finalSessionStatus = $client ->getSessionStatusPoller() ->fetchFinalSessionStatus($response->getSessionID()); // step #8 - get authenticationResult try { $authenticationResult = $client ->createMobileIdAuthentication($finalSessionStatus, $authenticationHash); } catch (MidUserCancellationException $e) { echo "User cancelled operation from his/her phone."; } catch (MidNotMidClientException $e) { echo "User is not a MID client or user's certificates are revoked."; } catch (MidSessionTimeoutException $e) { echo "User did not type in PIN code or communication error."; } catch (MidPhoneNotAvailableException $e) { echo "Unable to reach phone/SIM card. User needs to check if phone has coverage."; } catch (MidDeliveryException $e) { echo "Error communicating with the phone/SIM card."; } catch (MidInvalidUserConfigurationException $e) { echo "Mobile-ID configuration on user's SIM card differs from what is configured on service provider's side. User needs to contact his/her mobile operator."; } catch (MidSessionNotFoundException | MissingOrInvalidParameterException | MidUnauthorizedException | MidSslException $e) { throw new RuntimeException("Integrator-side error with MID integration or configuration. Error code:". $e->getCode()); } catch (MidServiceUnavailableException $e) { echo "MID service is currently unavailable. User shold try again later."; } catch (MidInternalErrorException $internalError) { echo "Something went wrong with Mobile-ID service"; } # step #9 - validate returned result (to protect yourself from man-in-the-middle attack) $validator = AuthenticationResponseValidator::newBuilder() ->withTrustedCaCertificatesFolder(__DIR__ . "/test_numbers_ca_certificates/") ->build(); $validator->validate($authenticationResult); # step #10 - read out authenticated person details $authenticatedPerson = $authenticationResult->constructAuthenticationIdentity(); echo 'Welcome, '.$authenticatedPerson->getGivenName().' '.$authenticatedPerson->getSurName().' '; echo ' (ID code '.$authenticatedPerson->getIdentityCode().') '; echo 'from '. $authenticatedPerson->getCountry(). '!';
有关更详细的实际示例,请参阅 mid-rest-php-demo
长轮询配置
询问认证会话状态时,您有两种选择。您可以配置长轮询,这意味着服务器不会立即对会话状态请求做出响应,而是等待用户输入(用户输入了 PIN1 或取消)或超时。然而,这会在调用方一侧阻塞线程,可能是不希望的。为此,还有一个选项是 withPollingSleepTimeoutSeconds(2),这意味着客户端每 2 秒向服务器发送一次请求。
如果您未设置 longPollingTimeoutSeconds 或 pollingSleepTimeoutSeconds 的正值,则 pollingSleepTimeoutSeconds 将默认为 3 秒。
使用长轮询
$this->client = MobileIdClient::newBuilder() ->withHostUrl("https://...") ->withRelyingPartyUUID("...") ->withRelyingPartyName("...") ->withSslPinnedPublicKeys("sha256//...") ->withLongPollingTimeoutSeconds(60) ->build();
不使用长轮询
$this->client = MobileIdClient::newBuilder() ->withHostUrl("https://...") ->withRelyingPartyUUID("...") ->withRelyingPartyName("...") ->withSslPinnedPublicKeys("sha256//...") ->withPollingSleepTimeoutSeconds(2) ->build();
检查 MID API 主机是否受信任
当与 MID API 协商 SSL 连接时,MID 服务器发送一个证书以表明其身份。从这个证书中提取一个公钥,并计算公钥的 sha256 哈希。这个哈希必须与提供给此库的哈希之一完全匹配
$this->client = MobileIdClient::newBuilder() ->withHostUrl("https://...") ->withRelyingPartyUUID("...") ->withRelyingPartyName("...") ->withSslPinnedPublicKeys("sha256//hash-of-current-mid-api-ssl-host-public-key;sha256//hash-of-future-mid-api-ssl-host-public-key") ->build();
否则,在发送或接收任何数据之前,将终止与 MID API 的连接。
库内部使用 https://curl.se/libcurl/c/CURLOPT_PINNEDPUBLICKEY.html 进行此操作。
获取生产 API 端点证书的摘要
打开 https://www.skidsolutions.eu/en/repository/certs/ 下载 mid.sk.ee 证书(PEM 格式)并将其保存为 "mid_sk_ee.PEM.cer"。
openssl x509 -in mid_sk_ee.PEM.cer -pubkey -noout > mid.sk.ee.pubkey.pem openssl asn1parse -noout -inform pem -in mid.sk.ee.pubkey.pem -out mid.sk.ee.pubkey.der openssl dgst -sha256 -binary mid.sk.ee.pubkey.der | openssl base64
复制输出(类似于 "fqp7yWK7iGGKj+3unYdm2DA3VCPDkwtyX+DrdZYSC6o=" 并在前面添加 "sha256//"),结果如下: "sha256//fqp7yWK7iGGKj+3unYdm2DA3VCPDkwtyX+DrdZYSC6o="
添加未来的生产证书
大约每年一次,服务器的 SSL 证书会更换。当发生这种情况时,SK 会通过电子邮件通知所有 RP。下载新的证书,并根据上述说明计算其 sha-256 摘要,并将摘要添加到列表中,用分号分隔。因此,值可能如下所示
"sha256//fqp7yWK7iGGKj+3unYdm2DA3VCPDkwtyX+DrdZYSC6o=;sha256//future-prod-certificate 的摘要"
获取演示API端点证书摘要
演示服务器(tsp.demo.sk.ee)证书可在以下位置获取:https://www.skidsolutions.eu/en/Repository/certs/certificates-for-testing,或您可以直接从服务器下载。
openssl s_client -servername tsp.demo.sk.ee -connect tsp.demo.sk.ee:443 < /dev/null | sed -n "/-----BEGIN/,/-----END/p" > tsp.demo.sk.ee.pem openssl x509 -in tsp.demo.sk.ee.pem -pubkey -noout > tsp.demo.sk.ee.pubkey.pem openssl asn1parse -noout -inform pem -in tsp.demo.sk.ee.pubkey.pem -out tsp.demo.sk.ee.pubkey.der openssl dgst -sha256 -binary tsp.demo.sk.ee.pubkey.der | openssl base64
设置公共IP或接口
有时服务器有多个网络接口或IP地址,客户端需要指定用于MID请求的哪个接口。这可以通过使用withNetworkInterface()参数来完成。
$this->client = MobileIdClient::newBuilder() ->withHostUrl("https://...") ->withRelyingPartyUUID("...") ->withRelyingPartyName("...") ->withSslPinnedPublicKeys("sha256//...") ->withNetworkInterface("10.11.12.13") ->build();
在内部,这将设置CURLOPT_INTERFACE标志
拉取用户的签名证书
此客户端还支持下载用户的移动-ID签名证书。
$client = MobileIdClient::newBuilder() ->withRelyingPartyUUID(TestData::DEMO_RELYING_PARTY_UUID) ->withRelyingPartyName(TestData::DEMO_RELYING_PARTY_NAME) ->withHostUrl(TestData::DEMO_HOST_URL) ->withSslPinnedPublicKeys("sha256//k/w7/9MIvdN6O/rE1ON+HjbGx9PRh/zSnNJ61pldpCs=;sha256//some-future-ssl-host-key") ->build(); $request = CertificateRequest::newBuilder() ->withPhoneNumber("+37200000766") ->withNationalIdentityNumber("60001019906") ->build(); try { $response = $client->getMobileIdConnector()->pullCertificate($request); $person = $client->parseMobileIdIdentity($response); echo 'This is a Mobile-ID user.'; echo 'Name, '.$person->getGivenName().' '.$person->getSurName().' '; echo ' (ID code '.$person->getIdentityCode().') '; echo 'from '. $person->getCountry(). '!'; } catch (MidNotMidClientException $e) { // if user is not MID client then this exception is thrown and caught already during first request (see above) echo "You are not a Mobile-ID client or your Mobile-ID certificates are revoked. Please contact your mobile operator."; } catch (MissingOrInvalidParameterException | MidUnauthorizedException $e) { throw new RuntimeException("Client side error with mobile-ID integration. Error code:". $e->getCode()); } catch (MidInternalErrorException $internalError) { echo "Something went wrong with Mobile-ID service"; }
签名
PHP库不支持签名。
设置日志记录
查看src/Util/Logger.php 文件。最基本的选择是将以下内容添加到debug_to_console()方法中。
echo $message."\n";