diff --git a/src/BinaryString.php b/src/BinaryString.php index dae3f02..2831f6e 100644 --- a/src/BinaryString.php +++ b/src/BinaryString.php @@ -26,6 +26,10 @@ public function __construct( ) { } + /** + * This accepts strings encoded in either standard base64 or base64url, + * since it converts the latter to the former while decoding. + */ public static function fromBase64Url(string $base64Url): BinaryString { $base64 = strtr($base64Url, ['-' => '+', '_' => '/']); diff --git a/src/Codecs/Credential.php b/src/Codecs/Credential.php index 489866c..903068f 100644 --- a/src/Codecs/Credential.php +++ b/src/Codecs/Credential.php @@ -22,8 +22,8 @@ * an opaque string without inspection or modification. This library makes * the following promises: * - * - The opaque strings will not be outside of the base64 character range - * (A-Za-z0-9/+=) + * - The opaque strings will not be outside of the base64(+url) character range + * (A-Za-z0-9-_/+=) * - The opaque strings are versioned, and if a new version is introduced, * there will be an upgrade/conversion path * - The opaque strings will not exceed 64KiB (65535 bytes) @@ -46,7 +46,7 @@ * Format spec: * * A CredentialObj shall be encoded to a string. - * That string shall be a base64-encoded representation of: + * That string shall be a base64url-encoded representation of: * * [ version ] [ version-specific data ] * @@ -220,7 +220,7 @@ private function encodeV2(CredentialInterface $credential): string $binary = pack(self::PACK_UINT8, $version) . $versionSpecificFormat; - return base64_encode($binary); + return (new BinaryString($binary))->toBase64Url(); } /** @@ -253,10 +253,7 @@ private static function parseTransportFlags(int $flags): array public function decode(string $encoded): CredentialInterface { - $binary = base64_decode($encoded, true); - assert($binary !== false); - - $bytes = new BinaryString($binary); + $bytes = BinaryString::fromBase64Url($encoded); $version = $bytes->readUint8(); assert(($version & 0x80) === 0, 'High bit in version must not be set'); diff --git a/tests/Codecs/CredentialTest.php b/tests/Codecs/CredentialTest.php index 3262063..bcde070 100644 --- a/tests/Codecs/CredentialTest.php +++ b/tests/Codecs/CredentialTest.php @@ -146,6 +146,11 @@ public static function v2Credentials(): array 'v+hUUYF9qszFOeJYoCfBEY2BoiJYIBuUBOTsbnswM3PD9Lj61GTyVQBalOm2' . '8aW5GWVNe7kOMQ==', ], + 'no att cert base64url' => [ + 'AgsACv69Y58M4y3CsWkAADEXAAAATaUBAgMmIAEhWCBOknC_s6jMNgiYeThI' . + 'v-hUUYF9qszFOeJYoCfBEY2BoiJYIBuUBOTsbnswM3PD9Lj61GTyVQBalOm2' . + '8aW5GWVNe7kOMQ', + ], 'saved att cert' => [ 'AhgACr/Sj9YstWchvM4AADBlAAAATaUBAgMmIAEhWCBOknC/s6jMNgiYeThI' . 'v+hUUYF9qszFOeJYoCfBEY2BoiJYIBuUBOTsbnswM3PD9Lj61GTyVQBalOm2' . @@ -173,6 +178,33 @@ public static function v2Credentials(): array 'N0J5YmFvQSIsIm9yaWdpbiI6Imh0dHA6Ly9sb2NhbGhvc3Q6Nzc3NyIsInR5' . 'cGUiOiJ3ZWJhdXRobi5jcmVhdGUifQ==' ], + 'saved att cert base64url' => [ + 'AhgACr_Sj9YstWchvM4AADBlAAAATaUBAgMmIAEhWCBOknC_s6jMNgiYeThI' . + 'v-hUUYF9qszFOeJYoCfBEY2BoiJYIBuUBOTsbnswM3PD9Lj61GTyVQBalOm2' . + '8aW5GWVNe7kODAAAA20AAAB1o2NmbXRoZmlkby11MmZnYXR0U3RtdKJjeDVj' . + 'gVkCMTCCAi0wggEXoAMCAQICBAW2BXkwCwYJKoZIhvcNAQELMC4xLDAqBgNV' . + 'BAMTI1l1YmljbyBVMkYgUm9vdCBDQSBTZXJpYWwgNDU3MjAwNjMxMCAXDTE0' . + 'MDgwMTAwMDAwMFoYDzIwNTAwOTA0MDAwMDAwWjAoMSYwJAYDVQQDDB1ZdWJp' . + 'Y28gVTJGIEVFIFNlcmlhbCA5NTgxNTAzMzBZMBMGByqGSM49AgEGCCqGSM49' . + 'AwEHA0IABP243rOh7XDrY2wGbrYAaZal-XD8tduI_DswXUHllm8MG1S4Uv7w' . + 'oJB-0X87_8KdTTIbnPioSizqoDjKvTXVmN6jJjAkMCIGCSsGAQQBgsQKAgQV' . + 'MS4zLjYuMS40LjEuNDE0ODIuMS4xMAsGCSqGSIb3DQEBCwOCAQEAftP7bMwl' . + 'IBP4LyGMKjfaYDHSDn8wgdr8rrEo_H-bIzkUv7ZNYTXxfOIh-nZPRT7xJzqM' . + '6WWVZEK7Lx5HSD9zfcvJi1hTd_71CycOAon4hDbxrc9JsmIe5eMC31VbmrdC' . + 'cuBp-RgUmz3sTxIiixDA-I3javWKdLtEK4WuAFNkvaZwIFj8Hy2Hm1MBEepg' . + '6Gxj8X-llEzIPwqiaYSLPuOIpsCeawWVP8u49H6Don4AcqY8Mq1khk6SbXES' . + '-hmX94OWVvuzK-j3iJ0PAUVRmiev3Y5GsEykKQ2FQLY0uIYWHnWIyGKZ3N1k' . + 'NdFnijpvCnSCnE3T9ww1JNHd8W14rdIbZGNzaWdYSDBGAiEA6Q_IoHy9emgq' . + 'byDa_5id6H0_MJvAkT28HNb0iEO36MUCIQDD-UZZBz0PIZUrJ77OliPPmtFO' . + 'SOW_u1vzX7aYe4lcLWhhdXRoRGF0YVjESZYN5YgOjGh0NBcPZHZgW4_krrmi' . + 'hjLHmVzzuoMdl2NBAAAAAAAAAAAAAAAAAAAAAAAAAAAAQHyt9XuGzGoH2Hhm' . + 'Gh_lNyFaCv-v9V79jigJZuZ5LtnWuOw9Ph-WfrA1HeHw33tqFbQ_5AYjo6E6' . + 'atlqFXZ6NRqlAQIDJiABIVggaORWdx8A3Tw55VDl5Hi3H-RC_TxUJvuyeFjT' . + 'FHz4zHwiWCC2nNEOYCncBKKLJpU536AHVsp4sHIJWtt8fAqF5ihlmHsiY2hh' . + 'bGxlbmdlIjoiNkVScmZFSVNYaXJYTm1iX1hMa0NlM2REdml0cEdkYVlvX3FY' . + 'N0J5YmFvQSIsIm9yaWdpbiI6Imh0dHA6Ly9sb2NhbGhvc3Q6Nzc3NyIsInR5' . + 'cGUiOiJ3ZWJhdXRobi5jcmVhdGUifQ' + ], ]; }