Skip to content

Commit

Permalink
SDK-2265 Retrieve Receipt Fix Decryption (#353)
Browse files Browse the repository at this point in the history
* SDK-2265 Retrieve Receipt Fix Decryption
  • Loading branch information
saurabh-yoti authored May 7, 2024
1 parent 3806985 commit 3dcc7a7
Show file tree
Hide file tree
Showing 17 changed files with 148 additions and 71 deletions.
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

0 comments on commit 3dcc7a7

Please sign in to comment.