diff --git a/src/Constants.php b/src/Constants.php index f784e2ec..055d0a95 100644 --- a/src/Constants.php +++ b/src/Constants.php @@ -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'; diff --git a/src/DigitalIdentityClient.php b/src/DigitalIdentityClient.php index be75884a..e58f55a9 100644 --- a/src/DigitalIdentityClient.php +++ b/src/DigitalIdentityClient.php @@ -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); diff --git a/src/Identity/DigitalIdentityService.php b/src/Identity/DigitalIdentityService.php index b4970f1f..4edda1a3 100644 --- a/src/Identity/DigitalIdentityService.php +++ b/src/Identity/DigitalIdentityService.php @@ -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() @@ -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() @@ -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() @@ -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() @@ -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) @@ -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() diff --git a/src/Identity/ReceiptBuilder.php b/src/Identity/ReceiptBuilder.php index eb7b4196..9bed933f 100644 --- a/src/Identity/ReceiptBuilder.php +++ b/src/Identity/ReceiptBuilder.php @@ -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 { diff --git a/src/Identity/ReceiptItemKey.php b/src/Identity/ReceiptItemKey.php index c61863d0..e6076fd8 100644 --- a/src/Identity/ReceiptItemKey.php +++ b/src/Identity/ReceiptItemKey.php @@ -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']; } /** @@ -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(); - } } diff --git a/src/Identity/ReceiptParser.php b/src/Identity/ReceiptParser.php index aac9e467..5d2dbfbb 100644 --- a/src/Identity/ReceiptParser.php +++ b/src/Identity/ReceiptParser.php @@ -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; @@ -37,12 +37,11 @@ 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( @@ -50,13 +49,12 @@ public function createSuccess( $this->parseProfileAttr( $wrappedReceipt->getOtherPartyProfile(), $receiptKey, - $pemFile ) ) ) : null; $otherExtraData = null !== $wrappedReceipt->getOtherPartyExtraData() ? - $this->parseExtraData($wrappedReceipt->getOtherPartyExtraData(), $receiptKey, $pemFile) : + $this->parseExtraData($wrappedReceipt->getOtherPartyExtraData(), $receiptKey) : null; @@ -96,34 +94,53 @@ 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); @@ -131,12 +148,11 @@ private function parseProfileAttr(string $profile, string $wrappedKey, PemFile $ 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( diff --git a/src/Identity/Util/IdentityEncryptedData.php b/src/Identity/Util/IdentityEncryptedData.php new file mode 100644 index 00000000..5ee440d6 --- /dev/null +++ b/src/Identity/Util/IdentityEncryptedData.php @@ -0,0 +1,41 @@ +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'); + } +} diff --git a/src/Identity/WrappedReceipt.php b/src/Identity/WrappedReceipt.php index 6ea82c0b..8d7c04c4 100644 --- a/src/Identity/WrappedReceipt.php +++ b/src/Identity/WrappedReceipt.php @@ -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 $sessionData @@ -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(