Skip to content

Commit

Permalink
SDK-2265 Retrieve Receipt Fix Decryption
Browse files Browse the repository at this point in the history
  • Loading branch information
saurabh-yoti committed Apr 24, 2024
1 parent 3806985 commit 404c706
Show file tree
Hide file tree
Showing 8 changed files with 101 additions and 53 deletions.
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
16 changes: 9 additions & 7 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,7 +74,7 @@ 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()
Expand All @@ -93,7 +93,7 @@ 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()
Expand Down Expand Up @@ -128,9 +128,11 @@ 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 +150,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
21 changes: 2 additions & 19 deletions src/Identity/ReceiptItemKey.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,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 +46,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();
}
}
54 changes: 35 additions & 19 deletions src/Identity/ReceiptParser.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@
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\Identity\Util\IdentityEncryptedData;
use Yoti\Protobuf\Attrpubapi\AttributeList;
use Yoti\Util\Logger;
use Yoti\Util\PemFile;
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,65 @@ public function createFailure(WrappedReceipt $wrappedReceipt): Receipt

private function decryptReceiptKey(string $wrappedKey, ReceiptItemKey $wrappedItemKey, PemFile $pemFile): string
{
openssl_private_decrypt(
$wrappedItemKey->getValue(),
// Convert 'iv' and 'value' from base64 to binary
$iv = base64_decode($wrappedItemKey->getIv(), true);
$encryptedItemKey = 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
41 changes: 41 additions & 0 deletions src/Identity/Util/IdentityEncryptedData.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<?php

declare(strict_types=1);

namespace Yoti\Identity\Util;

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

class IdentityEncryptedData
{
/**
* @param string $data
* @param string $wrappedKey
*
* @return string
*/
public static function decrypt(string $data, string $unwrappedKey): string
{
if ($data === false) {
throw new EncryptedDataException('Could not decode data');
}

$encryptedDataProto = new EncryptedDataProto();
$encryptedDataProto->mergeFromString($data);

$decrypted = openssl_decrypt(
$encryptedDataProto->getCipherText(),
'aes-256-cbc',
$unwrappedKey,
OPENSSL_RAW_DATA,
$encryptedDataProto->getIv()
);

if ($decrypted !== false) {
return $decrypted;
}

throw new EncryptedDataException('Could not decrypt data');
}
}
8 changes: 4 additions & 4 deletions src/Identity/WrappedReceipt.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,11 @@ class WrappedReceipt

private string $wrappedKey;

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

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

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

/**
* @param array<string, mixed> $sessionData
Expand All @@ -38,7 +38,7 @@ public function __construct(array $sessionData)
$this->sessionId = $sessionData['sessionId'];
$this->timestamp = DateTime::stringToDateTime($sessionData['timestamp']);
$this->wrappedItemKeyId = $sessionData['wrappedItemKeyId'];
$this->wrappedKey = $this->base64decode($sessionData['wrappedKey']);
$this->wrappedKey = $sessionData['wrappedKey'];

if (isset($sessionData['content'])) {
$this->content = new Content(
Expand Down

0 comments on commit 404c706

Please sign in to comment.