Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SDK-2265 Retrieve Receipt Fix Decryption #353

Merged
merged 4 commits into from
May 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .php-cs-fixer.cache

Large diffs are not rendered by default.

8 changes: 4 additions & 4 deletions examples/profile/app/Http/Controllers/IdentityController.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,14 @@
use Illuminate\Routing\Controller as BaseController;
use Illuminate\Support\Facades\Log;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
use Yoti\DigitalIdentityClient;
use Yoti\Identity\Policy\PolicyBuilder;
use Yoti\Identity\ShareSessionRequestBuilder;
use Yoti\YotiClient;

class IdentityController extends BaseController
{
public function show(YotiClient $client)
public function show(DigitalIdentityClient $client)
{
try {
$policy = (new PolicyBuilder())->build();
Expand Down Expand Up @@ -42,7 +43,7 @@ public function show(YotiClient $client)
'createdQrCodeUri' => $createdQrCode->getUri(),
// Fetch QR code
'fetchedQrCodeExpiry' => $fetchedQrCode->getExpiry(),
'fetchedQrCodeExtensions' => $fetchedQrCode->getExtensions(),

'fetchedQrCodeRedirectUri' => $fetchedQrCode->getRedirectUri(),
'fetchedQrCodeSessionId' => $fetchedQrCode->getSession()->getId(),
'fetchedQrCodeSessionStatus' => $fetchedQrCode->getSession()->getStatus(),
Expand All @@ -53,8 +54,7 @@ public function show(YotiClient $client)
'fetchedSessionExpiry' => $sessionFetched->getExpiry(),
'fetchedSessionCreated' => $sessionFetched->getCreated(),
'fetchedSessionUpdated' => $sessionFetched->getUpdated(),
'fetchedSessionQrCodeId' => $sessionFetched->getQrCodeId(),
'fetchedSessionReceiptId' => $sessionFetched->getReceiptId(),

]);
} catch (\Throwable $e) {
Log::error($e->getTraceAsString());
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?php

namespace App\Providers;

use Illuminate\Contracts\Support\DeferrableProvider;
use Illuminate\Support\ServiceProvider;
use Yoti\DigitalIdentityClient;

class YotiDigitalIdentityServiceProvider extends ServiceProvider implements DeferrableProvider
{
/**
* @return void
*/
public function register()
{
$this->app->singleton(DigitalIdentityClient::class, function ($app) {
$config = $app['config']['yoti'];
return new DigitalIdentityClient($config['client.sdk.id'], $config['pem.file.path']);
});
}

/**
* @return array
*/
public function provides()
{
return [DigitalIdentityClient::class];
}
}
1 change: 1 addition & 0 deletions examples/profile/config/app.php
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,7 @@
* Application Service Providers...
*/
App\Providers\YotiServiceProvider::class,
App\Providers\YotiDigitalIdentityServiceProvider::class,
App\Providers\RouteServiceProvider::class,
],

Expand Down
4 changes: 1 addition & 3 deletions examples/profile/resources/views/identity.blade.php
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@
<div>
<p><strong>Fetched Session QR Code</strong></p>
<p> Expiry: {{$fetchedQrCodeExpiry}}</p>
<p> Extensions: {{$fetchedQrCodeExtensions}}</p>
<p> Redirect URI: {{$fetchedQrCodeRedirectUri}}</p>
<p> Session ID: {{$fetchedQrCodeSessionId}}</p>
<p> Session Status: {{$fetchedQrCodeSessionStatus}}</p>
Expand All @@ -51,8 +50,7 @@
<p> Updated: {{$fetchedSessionUpdated}}</p>
<p> Expiry: {{$fetchedSessionExpiry}}</p>
<p> Status: {{$fetchedSessionStatus}}</p>
<p> QR Code ID: {{$fetchedSessionQrCodeId}}</p>
<p> Receipt ID: {{$fetchedSessionReceiptId}}</p>

</div>

</section>
Expand Down
1 change: 1 addition & 0 deletions examples/profile/routes/web.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,4 @@
Route::get('/dynamic-share', 'DynamicShareController@show');

Route::get('/dbs-check', 'DbsCheckController@show');
Route::get('/generate-share', 'IdentityController@show');
6 changes: 6 additions & 0 deletions src/Constants.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,12 @@ class Constants
/** Environment variable to override the default API URL */
public const ENV_API_URL = 'YOTI_API_URL';

/** Default Digital Identity API URL */
public const DIGITAL_IDENTITY_API_URL = self::API_BASE_URL . '/share';

/** Environment variable to override the default Digital Identity API URL */
public const ENV_DIGITAL_IDENTITY_API_URL = 'YOTI_DIGITAL_IDENTITY_API_URL';

/** Default Doc Scan API URL */
public const DOC_SCAN_API_URL = self::API_BASE_URL . '/idverify/v1';

Expand Down
2 changes: 1 addition & 1 deletion src/DigitalIdentityClient.php
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ public function __construct(
$pemFile = PemFile::resolveFromString($pem);

// Set API URL from environment variable.
$options[Config::API_URL] = $options[Config::API_URL] ?? Env::get(Constants::ENV_API_URL);
$options[Config::API_URL] = $options[Config::API_URL] ?? Env::get(Constants::ENV_DIGITAL_IDENTITY_API_URL);

$config = new Config($options);

Expand Down
19 changes: 10 additions & 9 deletions src/Identity/DigitalIdentityService.php
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ public function __construct(string $sdkId, PemFile $pemFile, Config $config)
public function createShareSession(ShareSessionRequest $shareSessionRequest): ShareSessionCreated
{
$response = (new RequestBuilder($this->config))
->withBaseUrl($this->config->getApiUrl() ?? Constants::API_URL)
->withBaseUrl($this->config->getApiUrl() ?? Constants::DIGITAL_IDENTITY_API_URL)
->withEndpoint(self::IDENTITY_SESSION_CREATION)
->withHeader('X-Yoti-Auth-Id', $this->sdkId)
->withPost()
Expand All @@ -55,7 +55,7 @@ public function createShareSession(ShareSessionRequest $shareSessionRequest): Sh
public function createShareQrCode(string $sessionId): ShareSessionCreatedQrCode
{
$response = (new RequestBuilder($this->config))
->withBaseUrl($this->config->getApiUrl() ?? Constants::API_URL)
->withBaseUrl($this->config->getApiUrl() ?? Constants::DIGITAL_IDENTITY_API_URL)
->withEndpoint(sprintf(self::IDENTITY_SESSION_QR_CODE_CREATION, $sessionId))
->withHeader('X-Yoti-Auth-Id', $this->sdkId)
->withPost()
Expand All @@ -74,10 +74,10 @@ public function createShareQrCode(string $sessionId): ShareSessionCreatedQrCode
public function fetchShareQrCode(string $qrCodeId): ShareSessionFetchedQrCode
{
$response = (new RequestBuilder($this->config))
->withBaseUrl($this->config->getApiUrl() ?? Constants::API_URL)
->withBaseUrl($this->config->getApiUrl() ?? Constants::DIGITAL_IDENTITY_API_URL)
->withEndpoint(sprintf(self::IDENTITY_SESSION_QR_CODE_RETRIEVAL, $qrCodeId))
->withHeader('X-Yoti-Auth-Id', $this->sdkId)
->withPost()
->withGet()
->withPemFile($this->pemFile)
->build()
->execute();
Expand All @@ -93,10 +93,10 @@ public function fetchShareQrCode(string $qrCodeId): ShareSessionFetchedQrCode
public function fetchShareSession(string $sessionId): ShareSessionFetched
{
$response = (new RequestBuilder($this->config))
->withBaseUrl($this->config->getApiUrl() ?? Constants::API_URL)
->withBaseUrl($this->config->getApiUrl() ?? Constants::DIGITAL_IDENTITY_API_URL)
->withEndpoint(sprintf(self::IDENTITY_SESSION_RETRIEVAL, $sessionId))
->withHeader('X-Yoti-Auth-Id', $this->sdkId)
->withPost()
->withGet()
->withPemFile($this->pemFile)
->build()
->execute();
Expand Down Expand Up @@ -128,9 +128,10 @@ public function fetchShareReceipt(string $receiptId): Receipt

private function doFetchShareReceipt(string $receiptId): WrappedReceipt
{
$receiptIdUrl = strtr($receiptId, '+/', '-_');
$response = (new RequestBuilder($this->config))
->withBaseUrl($this->config->getApiUrl() ?? Constants::API_URL)
->withEndpoint(sprintf(self::IDENTITY_SESSION_RECEIPT_RETRIEVAL, $receiptId))
->withBaseUrl($this->config->getApiUrl() ?? Constants::DIGITAL_IDENTITY_API_URL)
->withEndpoint(sprintf(self::IDENTITY_SESSION_RECEIPT_RETRIEVAL, $receiptIdUrl))
->withHeader('X-Yoti-Auth-Id', $this->sdkId)
->withGet()
->withPemFile($this->pemFile)
Expand All @@ -148,7 +149,7 @@ private function doFetchShareReceipt(string $receiptId): WrappedReceipt
private function fetchShareReceiptKey(WrappedReceipt $wrappedReceipt): ReceiptItemKey
{
$response = (new RequestBuilder($this->config))
->withBaseUrl($this->config->getApiUrl() ?? Constants::API_URL)
->withBaseUrl($this->config->getApiUrl() ?? Constants::DIGITAL_IDENTITY_API_URL)
->withEndpoint(sprintf(
self::IDENTITY_SESSION_RECEIPT_KEY_RETRIEVAL,
$wrappedReceipt->getWrappedItemKeyId()
Expand Down
6 changes: 3 additions & 3 deletions src/Identity/ReceiptBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,11 @@ class ReceiptBuilder

private UserContent $userContent;

private ?string $rememberMeId;
private ?string $rememberMeId = null;

private ?string $parentRememberMeId;
private ?string $parentRememberMeId = null;

private ?string $error;
private ?string $error = null;

public function withId(string $id): self
{
Expand Down
24 changes: 2 additions & 22 deletions src/Identity/ReceiptItemKey.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,6 @@

namespace Yoti\Identity;

use Yoti\Exception\EncryptedDataException;
use Yoti\Protobuf\Compubapi\EncryptedData;

class ReceiptItemKey
{
private string $id;
Expand All @@ -19,13 +16,8 @@ class ReceiptItemKey
public function __construct(array $sessionData)
{
$this->id = $sessionData['id'];
$this->setIv($sessionData['iv']);

$decoded = base64_decode($sessionData['value'], true);
if ($decoded === false) {
throw new EncryptedDataException('Could not decode data');
}
$this->value = $decoded;
$this->iv = $sessionData['iv'];
$this->value = $sessionData['value'];
}

/**
Expand All @@ -51,16 +43,4 @@ public function getValue(): string
{
return $this->value;
}

public function setIv(string $iv): void
{
$decodedProto = base64_decode($iv, true);
if ($decodedProto === false) {
throw new EncryptedDataException('Could not decode data');
}
$encryptedDataProto = new EncryptedData();
$encryptedDataProto->mergeFromString($decodedProto);

$this->iv = $encryptedDataProto->getIv();
}
}
60 changes: 39 additions & 21 deletions src/Identity/ReceiptParser.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@

use Psr\Log\LoggerInterface;
use Yoti\Exception\EncryptedDataException;
use Yoti\Identity\Util\IdentityEncryptedData;
use Yoti\Profile\ApplicationProfile;
use Yoti\Profile\ExtraData;
use Yoti\Profile\UserProfile;
use Yoti\Profile\Util\Attribute\AttributeListConverter;
use Yoti\Profile\Util\EncryptedData;
use Yoti\Profile\Util\ExtraData\ExtraDataConverter;
use Yoti\Protobuf\Attrpubapi\AttributeList;
use Yoti\Util\Logger;
Expand Down Expand Up @@ -37,26 +37,24 @@ public function createSuccess(
AttributeListConverter::convertToYotiAttributesList($this->parseProfileAttr(
$wrappedReceipt->getProfile(),
$receiptKey,
$pemFile
))
);

$extraData = null !== $wrappedReceipt->getExtraData() ?
$this->parseExtraData($wrappedReceipt->getExtraData(), $receiptKey, $pemFile) :
$this->parseExtraData($wrappedReceipt->getExtraData(), $receiptKey) :
null;

$userProfile = null !== $wrappedReceipt->getOtherPartyProfile() ? new UserProfile(
AttributeListConverter::convertToYotiAttributesList(
$this->parseProfileAttr(
$wrappedReceipt->getOtherPartyProfile(),
$receiptKey,
$pemFile
)
)
) : null;

$otherExtraData = null !== $wrappedReceipt->getOtherPartyExtraData() ?
$this->parseExtraData($wrappedReceipt->getOtherPartyExtraData(), $receiptKey, $pemFile) :
$this->parseExtraData($wrappedReceipt->getOtherPartyExtraData(), $receiptKey) :
null;


Expand Down Expand Up @@ -96,47 +94,67 @@ public function createFailure(WrappedReceipt $wrappedReceipt): Receipt

private function decryptReceiptKey(string $wrappedKey, ReceiptItemKey $wrappedItemKey, PemFile $pemFile): string
{
openssl_private_decrypt(
$wrappedItemKey->getValue(),
$unwrappedKey,
(string)$pemFile
);
// Convert 'iv' and 'value' from base64 to binary
$iv = (string)base64_decode($wrappedItemKey->getIv(), true);
$encryptedItemKey = (string)base64_decode($wrappedItemKey->getValue(), true);

// Decrypt the 'value' field (encrypted item key) using the private key
$unwrappedKey = '';
if (
!openssl_private_decrypt(
$encryptedItemKey,
$unwrappedKey,
(string)$pemFile
)
) {
throw new EncryptedDataException('Could not decrypt the item key');
}

// Check that 'wrappedKey' is a base64-encoded string
$wrappedKey = base64_decode($wrappedKey, true);
if ($wrappedKey === false) {
throw new EncryptedDataException('wrappedKey is not a valid base64-encoded string');
}

// Decompose the 'wrappedKey' into 'cipherText' and 'tag'
$cipherText = substr($wrappedKey, 0, -16);
$tag = substr($wrappedKey, -16);

// Decrypt the 'cipherText' using the 'iv' and the decrypted item key
$receiptKey = openssl_decrypt(
$wrappedKey,
$cipherText,
'aes-256-gcm',
$unwrappedKey,
OPENSSL_RAW_DATA,
$wrappedItemKey->getIv()
$iv,
$tag
);
if ($receiptKey === false) {
throw new EncryptedDataException('Could not decrypt data');
throw new EncryptedDataException('Could not decrypt the receipt key');
}

return $receiptKey;
}

private function parseProfileAttr(string $profile, string $wrappedKey, PemFile $pemFile): AttributeList
private function parseProfileAttr(string $profile, string $wrappedKey): AttributeList
{
$attributeList = new AttributeList();

$decryptedData = EncryptedData::decrypt(
$decryptedData = IdentityEncryptedData::decrypt(
$profile,
$wrappedKey,
$pemFile
$wrappedKey
);

$attributeList->mergeFromString($decryptedData);

return $attributeList;
}

private function parseExtraData(string $extraData, string $wrappedKey, PemFile $pemFile): ExtraData
private function parseExtraData(string $extraData, string $wrappedKey): ExtraData
{
$decryptAttribute = EncryptedData::decrypt(
$decryptAttribute = IdentityEncryptedData::decrypt(
$extraData,
$wrappedKey,
$pemFile
$wrappedKey
);

return ExtraDataConverter::convertValue(
Expand Down
Loading
Loading