From db28323378648782d92f6552d478a4132d58dcdc Mon Sep 17 00:00:00 2001 From: Matthew Miller Date: Mon, 1 Jun 2020 16:16:13 -0700 Subject: Refactor browser methods to return near-raw resp. --- .../browser/src/methods/startAssertion.test.ts | 14 +++++------- packages/browser/src/methods/startAssertion.ts | 26 +++++++++++++--------- .../browser/src/methods/startAttestation.test.ts | 7 +++--- packages/browser/src/methods/startAttestation.ts | 17 +++++++++----- 4 files changed, 34 insertions(+), 30 deletions(-) (limited to 'packages/browser/src') diff --git a/packages/browser/src/methods/startAssertion.test.ts b/packages/browser/src/methods/startAssertion.test.ts index db401dc..d106d05 100644 --- a/packages/browser/src/methods/startAssertion.test.ts +++ b/packages/browser/src/methods/startAssertion.test.ts @@ -65,8 +65,6 @@ test('should convert options before passing to navigator.credentials.get(...)', test('should return base64-encoded response values', async done => { mockSupportsWebauthn.mockReturnValue(true); - const credentialID = 'foobar'; - mockNavigatorGet.mockImplementation( (): Promise => { return new Promise(resolve => { @@ -88,13 +86,11 @@ test('should return base64-encoded response values', async done => { const response = await startAssertion(goodOpts1); - expect(response).toEqual({ - base64CredentialID: credentialID, - base64AuthenticatorData: mockAuthenticatorData, - base64ClientDataJSON: mockClientDataJSON, - base64Signature: mockSignature, - base64UserHandle: mockUserHandle, - }); + expect(response.rawId).toEqual('Zm9vYmFy'); + expect(response.response.authenticatorData).toEqual(mockAuthenticatorData); + expect(response.response.clientDataJSON).toEqual(mockClientDataJSON); + expect(response.response.signature).toEqual(mockSignature); + expect(response.response.userHandle).toEqual(mockUserHandle); done(); }); diff --git a/packages/browser/src/methods/startAssertion.ts b/packages/browser/src/methods/startAssertion.ts index 093cf30..1590634 100644 --- a/packages/browser/src/methods/startAssertion.ts +++ b/packages/browser/src/methods/startAssertion.ts @@ -1,7 +1,7 @@ import { PublicKeyCredentialRequestOptionsJSON, - AuthenticatorAssertionResponseJSON, AssertionCredential, + AssertionCredentialJSON, } from '@simplewebauthn/typescript-types'; import toUint8Array from '../helpers/toUint8Array'; @@ -16,7 +16,7 @@ import toPublicKeyCredentialDescriptor from '../helpers/toPublicKeyCredentialDes */ export default async function startAssertion( requestOptionsJSON: PublicKeyCredentialRequestOptionsJSON, -): Promise { +): Promise { if (!supportsWebauthn()) { throw new Error('WebAuthn is not supported in this browser'); } @@ -31,25 +31,29 @@ export default async function startAssertion( }; // Wait for the user to complete assertion - const credential = await navigator.credentials.get({ publicKey }); + const credential = await navigator.credentials.get({ publicKey }) as AssertionCredential; if (!credential) { throw new Error('Assertion was not completed'); } - const { response } = credential as AssertionCredential; + const { rawId, response } = credential; - let base64UserHandle = undefined; + let userHandle = undefined; if (response.userHandle) { - base64UserHandle = toBase64String(response.userHandle); + userHandle = toBase64String(response.userHandle); } // Convert values to base64 to make it easier to send back to the server return { - base64CredentialID: credential.id, - base64AuthenticatorData: toBase64String(response.authenticatorData), - base64ClientDataJSON: toBase64String(response.clientDataJSON), - base64Signature: toBase64String(response.signature), - base64UserHandle, + ...credential, + rawId: toBase64String(rawId), + response: { + ...response, + authenticatorData: toBase64String(response.authenticatorData), + clientDataJSON: toBase64String(response.clientDataJSON), + signature: toBase64String(response.signature), + userHandle, + }, }; } diff --git a/packages/browser/src/methods/startAttestation.test.ts b/packages/browser/src/methods/startAttestation.test.ts index ae79235..a54e8b8 100644 --- a/packages/browser/src/methods/startAttestation.test.ts +++ b/packages/browser/src/methods/startAttestation.test.ts @@ -98,10 +98,9 @@ test('should return base64-encoded response values', async done => { const response = await startAttestation(goodOpts1); - expect(response).toEqual({ - base64AttestationObject: mockAttestationObject, - base64ClientDataJSON: mockClientDataJSON, - }); + expect(response.rawId).toEqual('Zm9vYmFy'); + expect(response.response.attestationObject).toEqual(mockAttestationObject); + expect(response.response.clientDataJSON).toEqual(mockClientDataJSON); done(); }); diff --git a/packages/browser/src/methods/startAttestation.ts b/packages/browser/src/methods/startAttestation.ts index ea05fa8..51ea6ec 100644 --- a/packages/browser/src/methods/startAttestation.ts +++ b/packages/browser/src/methods/startAttestation.ts @@ -1,7 +1,7 @@ import { PublicKeyCredentialCreationOptionsJSON, - AuthenticatorAttestationResponseJSON, AttestationCredential, + AttestationCredentialJSON, } from '@simplewebauthn/typescript-types'; import toUint8Array from '../helpers/toUint8Array'; @@ -16,7 +16,7 @@ import toPublicKeyCredentialDescriptor from '../helpers/toPublicKeyCredentialDes */ export default async function startAttestation( creationOptionsJSON: PublicKeyCredentialCreationOptionsJSON, -): Promise { +): Promise { if (!supportsWebauthn()) { throw new Error('WebAuthn is not supported in this browser'); } @@ -35,17 +35,22 @@ export default async function startAttestation( }; // Wait for the user to complete attestation - const credential = await navigator.credentials.create({ publicKey }); + const credential = await navigator.credentials.create({ publicKey }) as AttestationCredential; if (!credential) { throw new Error('Attestation was not completed'); } - const { response } = credential as AttestationCredential; + const { rawId, response } = credential; // Convert values to base64 to make it easier to send back to the server return { - base64AttestationObject: toBase64String(response.attestationObject), - base64ClientDataJSON: toBase64String(response.clientDataJSON), + ...credential, + rawId: toBase64String(rawId), + response: { + ...response, + attestationObject: toBase64String(response.attestationObject), + clientDataJSON: toBase64String(response.clientDataJSON), + } }; } -- cgit v1.2.3 From cac818173050ec1e18d73d0f880c826901cf4abd Mon Sep 17 00:00:00 2001 From: Matthew Miller Date: Tue, 2 Jun 2020 11:51:28 -0700 Subject: Add new Base64URL<->Buffer helper methods --- .../browser/src/helpers/base64URLStringToBuffer.ts | 33 ++++++++++++++++++++++ .../src/helpers/bufferToBase64URLString.test.ts | 15 ++++++++++ .../browser/src/helpers/bufferToBase64URLString.ts | 21 ++++++++++++++ .../browser/src/helpers/toBase64String.test.ts | 15 ---------- packages/browser/src/helpers/toBase64String.ts | 6 ---- 5 files changed, 69 insertions(+), 21 deletions(-) create mode 100644 packages/browser/src/helpers/base64URLStringToBuffer.ts create mode 100644 packages/browser/src/helpers/bufferToBase64URLString.test.ts create mode 100644 packages/browser/src/helpers/bufferToBase64URLString.ts delete mode 100644 packages/browser/src/helpers/toBase64String.test.ts delete mode 100644 packages/browser/src/helpers/toBase64String.ts (limited to 'packages/browser/src') diff --git a/packages/browser/src/helpers/base64URLStringToBuffer.ts b/packages/browser/src/helpers/base64URLStringToBuffer.ts new file mode 100644 index 0000000..baa258f --- /dev/null +++ b/packages/browser/src/helpers/base64URLStringToBuffer.ts @@ -0,0 +1,33 @@ +/** + * Convert from a Base64URL-encoded string to an Array Buffer. Best used when converting a + * credential ID from a JSON string to an ArrayBuffer, like in allowCredentials or + * excludeCredentials + * + * Helper method to compliment `bufferToBase64URLString` + */ +export default function base64URLStringToBuffer(base64URLString: string): ArrayBuffer { + // Convert from Base64URL to Base64 + const base64 = base64URLString.replace(/-/g, '+').replace(/_/g, '/'); + /** + * Pad with '=' until it's a multiple of four + * (4 - (85 % 4 = 1) = 3) % 4 = 3 padding + * (4 - (86 % 4 = 2) = 2) % 4 = 2 padding + * (4 - (87 % 4 = 3) = 1) % 4 = 1 padding + * (4 - (88 % 4 = 0) = 4) % 4 = 0 padding + */ + const padLength = (4 - (base64.length % 4)) % 4; + const padded = base64.padEnd(base64.length + padLength, '='); + + // Convert to a binary string + const binary = atob(padded); + + // Convert binary string to buffer + const buffer = new ArrayBuffer(binary.length); + const bytes = new Uint8Array(buffer); + + for (let i = 0; i < binary.length; i++) { + bytes[i] = binary.charCodeAt(i); + } + + return buffer; +} diff --git a/packages/browser/src/helpers/bufferToBase64URLString.test.ts b/packages/browser/src/helpers/bufferToBase64URLString.test.ts new file mode 100644 index 0000000..bbcb11b --- /dev/null +++ b/packages/browser/src/helpers/bufferToBase64URLString.test.ts @@ -0,0 +1,15 @@ +import toBase64String from './toBase64String'; + +import toUint8Array from './toUint8Array'; + +test('should convert a Buffer to a string with a length that is a multiple of 4', () => { + const base64 = toBase64String(Buffer.from('123456', 'ascii')); + + expect(base64.length % 4).toEqual(0); +}); + +test('should convert a Uint8Array to a string with a length that is a multiple of 4', () => { + const base64 = toBase64String(toUint8Array('123456')); + + expect(base64.length % 4).toEqual(0); +}); diff --git a/packages/browser/src/helpers/bufferToBase64URLString.ts b/packages/browser/src/helpers/bufferToBase64URLString.ts new file mode 100644 index 0000000..954f4b0 --- /dev/null +++ b/packages/browser/src/helpers/bufferToBase64URLString.ts @@ -0,0 +1,21 @@ +/** + * Convert the given array buffer into a Base64URL-encoded string. Ideal for converting various + * credential response ArrayBuffers to string for sending back to the server as JSON. + * + * Helper method to compliment `base64URLStringToBuffer` + */ +export default function bufferToBase64URLString(buffer: ArrayBuffer): string { + const bytes = new Uint8Array(buffer); + let str = ''; + + for (const charCode of bytes) { + str += String.fromCharCode(charCode); + } + + const base64String = btoa(str); + + return base64String + .replace(/\+/g, '-') + .replace(/\//g, '_') + .replace(/=/g, ''); +} diff --git a/packages/browser/src/helpers/toBase64String.test.ts b/packages/browser/src/helpers/toBase64String.test.ts deleted file mode 100644 index bbcb11b..0000000 --- a/packages/browser/src/helpers/toBase64String.test.ts +++ /dev/null @@ -1,15 +0,0 @@ -import toBase64String from './toBase64String'; - -import toUint8Array from './toUint8Array'; - -test('should convert a Buffer to a string with a length that is a multiple of 4', () => { - const base64 = toBase64String(Buffer.from('123456', 'ascii')); - - expect(base64.length % 4).toEqual(0); -}); - -test('should convert a Uint8Array to a string with a length that is a multiple of 4', () => { - const base64 = toBase64String(toUint8Array('123456')); - - expect(base64.length % 4).toEqual(0); -}); diff --git a/packages/browser/src/helpers/toBase64String.ts b/packages/browser/src/helpers/toBase64String.ts deleted file mode 100644 index 3178695..0000000 --- a/packages/browser/src/helpers/toBase64String.ts +++ /dev/null @@ -1,6 +0,0 @@ -import base64js from 'base64-js'; - -export default function toBase64String(buffer: ArrayBuffer): string { - // TODO: Make sure converting buffer to Uint8Array() is correct - return base64js.fromByteArray(new Uint8Array(buffer)).replace(/\+/g, '-').replace(/\//g, '_'); -} -- cgit v1.2.3 From 451ae557b51875f6b0eedb055d5561f22ad4456f Mon Sep 17 00:00:00 2001 From: Matthew Miller Date: Tue, 2 Jun 2020 11:52:12 -0700 Subject: Refactor to incorporate new helper --- packages/browser/src/helpers/toPublicKeyCredentialDescriptor.ts | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) (limited to 'packages/browser/src') diff --git a/packages/browser/src/helpers/toPublicKeyCredentialDescriptor.ts b/packages/browser/src/helpers/toPublicKeyCredentialDescriptor.ts index c37fed0..8fad78b 100644 --- a/packages/browser/src/helpers/toPublicKeyCredentialDescriptor.ts +++ b/packages/browser/src/helpers/toPublicKeyCredentialDescriptor.ts @@ -1,16 +1,14 @@ -import base64js from 'base64-js'; import type { PublicKeyCredentialDescriptorJSON } from '@simplewebauthn/typescript-types'; +import base64URLStringToBuffer from './base64URLStringToBuffer'; + export default function toPublicKeyCredentialDescriptor( descriptor: PublicKeyCredentialDescriptorJSON, ): PublicKeyCredentialDescriptor { - // Make sure the Base64'd credential ID length is a multiple of 4 or else toByteArray will throw const { id } = descriptor; - const padLength = 4 - (id.length % 4); - const paddedId = id.padEnd(id.length + padLength, '='); return { ...descriptor, - id: base64js.toByteArray(paddedId), + id: base64URLStringToBuffer(id), }; } -- cgit v1.2.3 From 5ae824965ebee5590486c36fe0365c9ad802bb23 Mon Sep 17 00:00:00 2001 From: Matthew Miller Date: Tue, 2 Jun 2020 12:08:47 -0700 Subject: Update start methods to use new helper --- packages/browser/src/methods/startAssertion.ts | 12 ++++++------ packages/browser/src/methods/startAttestation.ts | 10 +++++----- 2 files changed, 11 insertions(+), 11 deletions(-) (limited to 'packages/browser/src') diff --git a/packages/browser/src/methods/startAssertion.ts b/packages/browser/src/methods/startAssertion.ts index 1590634..81cad60 100644 --- a/packages/browser/src/methods/startAssertion.ts +++ b/packages/browser/src/methods/startAssertion.ts @@ -5,7 +5,7 @@ import { } from '@simplewebauthn/typescript-types'; import toUint8Array from '../helpers/toUint8Array'; -import toBase64String from '../helpers/toBase64String'; +import bufferToBase64URLString from '../helpers/bufferToBase64URLString'; import supportsWebauthn from '../helpers/supportsWebauthn'; import toPublicKeyCredentialDescriptor from '../helpers/toPublicKeyCredentialDescriptor'; @@ -41,18 +41,18 @@ export default async function startAssertion( let userHandle = undefined; if (response.userHandle) { - userHandle = toBase64String(response.userHandle); + userHandle = bufferToBase64URLString(response.userHandle); } // Convert values to base64 to make it easier to send back to the server return { ...credential, - rawId: toBase64String(rawId), + rawId: bufferToBase64URLString(rawId), response: { ...response, - authenticatorData: toBase64String(response.authenticatorData), - clientDataJSON: toBase64String(response.clientDataJSON), - signature: toBase64String(response.signature), + authenticatorData: bufferToBase64URLString(response.authenticatorData), + clientDataJSON: bufferToBase64URLString(response.clientDataJSON), + signature: bufferToBase64URLString(response.signature), userHandle, }, }; diff --git a/packages/browser/src/methods/startAttestation.ts b/packages/browser/src/methods/startAttestation.ts index 51ea6ec..1612961 100644 --- a/packages/browser/src/methods/startAttestation.ts +++ b/packages/browser/src/methods/startAttestation.ts @@ -5,7 +5,7 @@ import { } from '@simplewebauthn/typescript-types'; import toUint8Array from '../helpers/toUint8Array'; -import toBase64String from '../helpers/toBase64String'; +import bufferToBase64URLString from '../helpers/bufferToBase64URLString'; import supportsWebauthn from '../helpers/supportsWebauthn'; import toPublicKeyCredentialDescriptor from '../helpers/toPublicKeyCredentialDescriptor'; @@ -46,11 +46,11 @@ export default async function startAttestation( // Convert values to base64 to make it easier to send back to the server return { ...credential, - rawId: toBase64String(rawId), + rawId: bufferToBase64URLString(rawId), response: { ...response, - attestationObject: toBase64String(response.attestationObject), - clientDataJSON: toBase64String(response.clientDataJSON), - } + attestationObject: bufferToBase64URLString(response.attestationObject), + clientDataJSON: bufferToBase64URLString(response.clientDataJSON), + }, }; } -- cgit v1.2.3 From ae19fbdf22fb13dd8848370ff201963850682db7 Mon Sep 17 00:00:00 2001 From: Matthew Miller Date: Tue, 2 Jun 2020 12:52:52 -0700 Subject: Get unit tests passing again --- .../src/helpers/bufferToBase64URLString.test.ts | 15 ------- .../browser/src/methods/startAssertion.test.ts | 48 +++++++++++----------- .../browser/src/methods/startAttestation.test.ts | 29 ++++++------- 3 files changed, 39 insertions(+), 53 deletions(-) delete mode 100644 packages/browser/src/helpers/bufferToBase64URLString.test.ts (limited to 'packages/browser/src') diff --git a/packages/browser/src/helpers/bufferToBase64URLString.test.ts b/packages/browser/src/helpers/bufferToBase64URLString.test.ts deleted file mode 100644 index bbcb11b..0000000 --- a/packages/browser/src/helpers/bufferToBase64URLString.test.ts +++ /dev/null @@ -1,15 +0,0 @@ -import toBase64String from './toBase64String'; - -import toUint8Array from './toUint8Array'; - -test('should convert a Buffer to a string with a length that is a multiple of 4', () => { - const base64 = toBase64String(Buffer.from('123456', 'ascii')); - - expect(base64.length % 4).toEqual(0); -}); - -test('should convert a Uint8Array to a string with a length that is a multiple of 4', () => { - const base64 = toBase64String(toUint8Array('123456')); - - expect(base64.length % 4).toEqual(0); -}); diff --git a/packages/browser/src/methods/startAssertion.test.ts b/packages/browser/src/methods/startAssertion.test.ts index d106d05..669e8eb 100644 --- a/packages/browser/src/methods/startAssertion.test.ts +++ b/packages/browser/src/methods/startAssertion.test.ts @@ -1,13 +1,9 @@ -import base64js from 'base64-js'; - import { AssertionCredential, PublicKeyCredentialRequestOptionsJSON, } from '@simplewebauthn/typescript-types'; -import toUint8Array from '../helpers/toUint8Array'; import supportsWebauthn from '../helpers/supportsWebauthn'; -import toBase64String from '../helpers/toBase64String'; import startAssertion from './startAssertion'; @@ -16,16 +12,16 @@ jest.mock('../helpers/supportsWebauthn'); const mockNavigatorGet = window.navigator.credentials.get as jest.Mock; const mockSupportsWebauthn = supportsWebauthn as jest.Mock; -const mockAuthenticatorData = toBase64String(toUint8Array('mockAuthenticatorData')); -const mockClientDataJSON = toBase64String(toUint8Array('mockClientDataJSON')); -const mockSignature = toBase64String(toUint8Array('mockSignature')); -const mockUserHandle = toBase64String(toUint8Array('mockUserHandle')); +const mockAuthenticatorData = 'mockAuthenticatorData'; +const mockClientDataJSON = 'mockClientDataJSON'; +const mockSignature = 'mockSignature'; +const mockUserHandle = 'mockUserHandle'; const goodOpts1: PublicKeyCredentialRequestOptionsJSON = { challenge: 'fizz', allowCredentials: [ { - id: 'abcdefgfdnsdfunguisdfgs', + id: 'C0VGlvYFratUdAV1iCw-ULpUW8E-exHPXQChBfyVeJZCMfjMFcwDmOFgoMUz39LoMtCJUBW8WPlLkGT6q8qTCg', type: 'public-key', transports: ['nfc'], }, @@ -45,7 +41,10 @@ test('should convert options before passing to navigator.credentials.get(...)', mockNavigatorGet.mockImplementation( (): Promise => { return new Promise(resolve => { - resolve({ response: {} }); + resolve({ + response: {}, + getClientExtensionResults: () => ({}), + }); }); }, ); @@ -53,16 +52,17 @@ test('should convert options before passing to navigator.credentials.get(...)', await startAssertion(goodOpts1); const argsPublicKey = mockNavigatorGet.mock.calls[0][0].publicKey; - const credId = base64js.fromByteArray(argsPublicKey.allowCredentials[0].id); + const credId = argsPublicKey.allowCredentials[0].id; - expect(argsPublicKey.challenge).toEqual(toUint8Array(goodOpts1.challenge)); - // Make sure the credential ID is a proper base64 with a length that's a multiple of 4 - expect(credId.length % 4).toEqual(0); + expect(JSON.stringify(argsPublicKey.challenge)).toEqual('{"0":102,"1":105,"2":122,"3":122}'); + // Make sure the credential ID is an ArrayBuffer with a length of 64 + expect(credId instanceof ArrayBuffer).toEqual(true); + expect(credId.byteLength).toEqual(64); done(); }); -test('should return base64-encoded response values', async done => { +test('should return base64url-encoded response values', async done => { mockSupportsWebauthn.mockReturnValue(true); mockNavigatorGet.mockImplementation( @@ -70,12 +70,12 @@ test('should return base64-encoded response values', async done => { return new Promise(resolve => { resolve({ id: 'foobar', - rawId: toUint8Array('foobar'), + rawId: Buffer.from('foobar', 'ascii'), response: { - authenticatorData: base64js.toByteArray(mockAuthenticatorData), - clientDataJSON: base64js.toByteArray(mockClientDataJSON), - signature: base64js.toByteArray(mockSignature), - userHandle: base64js.toByteArray(mockUserHandle), + authenticatorData: Buffer.from(mockAuthenticatorData, 'ascii'), + clientDataJSON: Buffer.from(mockClientDataJSON, 'ascii'), + signature: Buffer.from(mockSignature, 'ascii'), + userHandle: Buffer.from(mockUserHandle, 'ascii'), }, getClientExtensionResults: () => ({}), type: 'webauthn.get', @@ -87,10 +87,10 @@ test('should return base64-encoded response values', async done => { const response = await startAssertion(goodOpts1); expect(response.rawId).toEqual('Zm9vYmFy'); - expect(response.response.authenticatorData).toEqual(mockAuthenticatorData); - expect(response.response.clientDataJSON).toEqual(mockClientDataJSON); - expect(response.response.signature).toEqual(mockSignature); - expect(response.response.userHandle).toEqual(mockUserHandle); + expect(response.response.authenticatorData).toEqual('bW9ja0F1dGhlbnRpY2F0b3JEYXRh'); + expect(response.response.clientDataJSON).toEqual('bW9ja0NsaWVudERhdGFKU09O'); + expect(response.response.signature).toEqual('bW9ja1NpZ25hdHVyZQ'); + expect(response.response.userHandle).toEqual('bW9ja1VzZXJIYW5kbGU'); done(); }); diff --git a/packages/browser/src/methods/startAttestation.test.ts b/packages/browser/src/methods/startAttestation.test.ts index a54e8b8..926db40 100644 --- a/packages/browser/src/methods/startAttestation.test.ts +++ b/packages/browser/src/methods/startAttestation.test.ts @@ -1,5 +1,3 @@ -import base64js from 'base64-js'; - import { AttestationCredential, PublicKeyCredentialCreationOptionsJSON, @@ -38,7 +36,7 @@ const goodOpts1: PublicKeyCredentialCreationOptionsJSON = { }, timeout: 1, excludeCredentials: [{ - id: 'authIdentifier', + id: 'C0VGlvYFratUdAV1iCw-ULpUW8E-exHPXQChBfyVeJZCMfjMFcwDmOFgoMUz39LoMtCJUBW8WPlLkGT6q8qTCg', type: 'public-key', transports: ['internal'], }], @@ -64,14 +62,17 @@ test('should convert options before passing to navigator.credentials.create(...) await startAttestation(goodOpts1); const argsPublicKey = mockNavigatorCreate.mock.calls[0][0].publicKey; + const credId = argsPublicKey.excludeCredentials[0].id; - expect(argsPublicKey.challenge).toEqual(toUint8Array(goodOpts1.challenge)); - expect(argsPublicKey.user.id).toEqual(toUint8Array(goodOpts1.user.id)); - expect(argsPublicKey.excludeCredentials).toEqual([{ - id: base64js.toByteArray('authIdentifier=='), - type: 'public-key', - transports: ['internal'], - }]) + // Make sure challenge and user.id are converted to Buffers + expect(JSON.stringify(argsPublicKey.challenge)).toEqual('{"0":102,"1":105,"2":122,"3":122}'); + expect(JSON.stringify(argsPublicKey.user.id)).toEqual('{"0":53,"1":54,"2":55,"3":56}'); + + // Confirm construction of excludeCredentials array + expect(credId instanceof ArrayBuffer).toEqual(true); + expect(credId.byteLength).toEqual(64); + expect(argsPublicKey.excludeCredentials[0].type).toEqual('public-key'); + expect(argsPublicKey.excludeCredentials[0].transports).toEqual(['internal']); done(); }); @@ -86,8 +87,8 @@ test('should return base64-encoded response values', async done => { id: 'foobar', rawId: toUint8Array('foobar'), response: { - attestationObject: base64js.toByteArray(mockAttestationObject), - clientDataJSON: base64js.toByteArray(mockClientDataJSON), + attestationObject: Buffer.from(mockAttestationObject, 'ascii'), + clientDataJSON: Buffer.from(mockClientDataJSON, 'ascii'), }, getClientExtensionResults: () => ({}), type: 'webauthn.create', @@ -99,8 +100,8 @@ test('should return base64-encoded response values', async done => { const response = await startAttestation(goodOpts1); expect(response.rawId).toEqual('Zm9vYmFy'); - expect(response.response.attestationObject).toEqual(mockAttestationObject); - expect(response.response.clientDataJSON).toEqual(mockClientDataJSON); + expect(response.response.attestationObject).toEqual('bW9ja0F0dGU'); + expect(response.response.clientDataJSON).toEqual('bW9ja0NsaWU'); done(); }); -- cgit v1.2.3 From 047413af015302639a1bd23934d9eb23fcbec169 Mon Sep 17 00:00:00 2001 From: Matthew Miller Date: Tue, 2 Jun 2020 13:17:18 -0700 Subject: Explicitly define id and type MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit It turns out that properties on PublicKeyCredentials are non-enumerable getters, so the spread operator won’t pick them up. This means we need to manually re-construct attestation and assertion credentials when we convert them to JSON. --- packages/browser/src/methods/startAssertion.ts | 6 +++--- packages/browser/src/methods/startAttestation.ts | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) (limited to 'packages/browser/src') diff --git a/packages/browser/src/methods/startAssertion.ts b/packages/browser/src/methods/startAssertion.ts index 81cad60..b65325b 100644 --- a/packages/browser/src/methods/startAssertion.ts +++ b/packages/browser/src/methods/startAssertion.ts @@ -37,7 +37,7 @@ export default async function startAssertion( throw new Error('Assertion was not completed'); } - const { rawId, response } = credential; + const { id, rawId, response, type } = credential; let userHandle = undefined; if (response.userHandle) { @@ -46,14 +46,14 @@ export default async function startAssertion( // Convert values to base64 to make it easier to send back to the server return { - ...credential, + id, rawId: bufferToBase64URLString(rawId), response: { - ...response, authenticatorData: bufferToBase64URLString(response.authenticatorData), clientDataJSON: bufferToBase64URLString(response.clientDataJSON), signature: bufferToBase64URLString(response.signature), userHandle, }, + type, }; } diff --git a/packages/browser/src/methods/startAttestation.ts b/packages/browser/src/methods/startAttestation.ts index 1612961..d5e540f 100644 --- a/packages/browser/src/methods/startAttestation.ts +++ b/packages/browser/src/methods/startAttestation.ts @@ -41,16 +41,16 @@ export default async function startAttestation( throw new Error('Attestation was not completed'); } - const { rawId, response } = credential; + const { id, rawId, response, type } = credential; // Convert values to base64 to make it easier to send back to the server return { - ...credential, + id, rawId: bufferToBase64URLString(rawId), response: { - ...response, attestationObject: bufferToBase64URLString(response.attestationObject), clientDataJSON: bufferToBase64URLString(response.clientDataJSON), }, + type, }; } -- cgit v1.2.3 From e82c9e9f813897015c9054aa6d279e8ca4279f07 Mon Sep 17 00:00:00 2001 From: Matthew Miller Date: Tue, 2 Jun 2020 15:14:31 -0700 Subject: Standardize on use of “base64url” where applicable MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/browser/src/methods/startAttestation.test.ts | 2 +- packages/server/src/assertion/generateAssertionOptions.ts | 2 +- packages/server/src/assertion/verifyAssertionResponse.ts | 2 +- packages/server/src/attestation/generateAttestationOptions.ts | 2 +- packages/server/src/attestation/verifyAttestationResponse.ts | 6 +++--- packages/server/src/helpers/decodeAttestationObject.ts | 4 ++-- packages/typescript-types/src/index.ts | 6 ++---- 7 files changed, 11 insertions(+), 13 deletions(-) (limited to 'packages/browser/src') diff --git a/packages/browser/src/methods/startAttestation.test.ts b/packages/browser/src/methods/startAttestation.test.ts index 926db40..bf6ab9b 100644 --- a/packages/browser/src/methods/startAttestation.test.ts +++ b/packages/browser/src/methods/startAttestation.test.ts @@ -77,7 +77,7 @@ test('should convert options before passing to navigator.credentials.create(...) done(); }); -test('should return base64-encoded response values', async done => { +test('should return base64url-encoded response values', async done => { mockSupportsWebauthn.mockReturnValue(true); mockNavigatorCreate.mockImplementation( diff --git a/packages/server/src/assertion/generateAssertionOptions.ts b/packages/server/src/assertion/generateAssertionOptions.ts index 9444a54..ca699c6 100644 --- a/packages/server/src/assertion/generateAssertionOptions.ts +++ b/packages/server/src/assertion/generateAssertionOptions.ts @@ -15,7 +15,7 @@ type Options = { * Prepare a value to pass into navigator.credentials.get(...) for authenticator "login" * * @param challenge Random string the authenticator needs to sign and pass back - * @param allowedBase64CredentialIDs Array of base64-encoded authenticator IDs registered by the + * @param allowedBase64CredentialIDs Array of base64url-encoded authenticator IDs registered by the * user for assertion * @param timeout How long (in ms) the user can take to complete assertion * @param suggestedTransports Suggested types of authenticators for assertion diff --git a/packages/server/src/assertion/verifyAssertionResponse.ts b/packages/server/src/assertion/verifyAssertionResponse.ts index f19c548..7d13271 100644 --- a/packages/server/src/assertion/verifyAssertionResponse.ts +++ b/packages/server/src/assertion/verifyAssertionResponse.ts @@ -13,7 +13,7 @@ import parseAuthenticatorData from '../helpers/parseAuthenticatorData'; /** * Verify that the user has legitimately completed the login process * - * @param response Authenticator assertion response with base64-encoded values + * @param response Authenticator assertion response with base64url-encoded values * @param expectedChallenge The random value provided to generateAssertionOptions for the * authenticator to sign * @param expectedOrigin Expected URL of website assertion should have occurred on diff --git a/packages/server/src/attestation/generateAttestationOptions.ts b/packages/server/src/attestation/generateAttestationOptions.ts index 89ac86a..25008c0 100644 --- a/packages/server/src/attestation/generateAttestationOptions.ts +++ b/packages/server/src/attestation/generateAttestationOptions.ts @@ -30,7 +30,7 @@ type Options = { * @param userDisplayName User's actual name * @param timeout How long (in ms) the user can take to complete attestation * @param attestationType Specific attestation statement - * @param excludedBase64CredentialIDs Array of base64-encoded authenticator IDs registered by the + * @param excludedBase64CredentialIDs Array of base64url-encoded authenticator IDs registered by the * user so the user can't register the same credential multiple times * @param suggestedTransports Suggested types of authenticators for attestation * @param authenticatorSelection Advanced criteria for restricting the types of authenticators that diff --git a/packages/server/src/attestation/verifyAttestationResponse.ts b/packages/server/src/attestation/verifyAttestationResponse.ts index 2f81fdc..ed4ac5c 100644 --- a/packages/server/src/attestation/verifyAttestationResponse.ts +++ b/packages/server/src/attestation/verifyAttestationResponse.ts @@ -13,7 +13,7 @@ import verifyAndroidSafetynet from './verifications/verifyAndroidSafetyNet'; /** * Verify that the user has legitimately completed the registration process * - * @param response Authenticator attestation response with base64-encoded values + * @param response Authenticator attestation response with base64url-encoded values * @param expectedChallenge The random value provided to generateAttestationOptions for the * authenticator to sign * @param expectedOrigin Expected URL of website attestation should have occurred on @@ -77,9 +77,9 @@ export default function verifyAttestationResponse( * @param authenticatorInfo.fmt Type of attestation * @param authenticatorInfo.counter The number of times the authenticator reported it has been used. * Should be kept in a DB for later reference to help prevent replay attacks - * @param authenticatorInfo.base64PublicKey Base64-encoded ArrayBuffer containing the + * @param authenticatorInfo.base64PublicKey Base64URL-encoded ArrayBuffer containing the * authenticator's public key. **Should be kept in a DB for later reference!** - * @param authenticatorInfo.base64CredentialID Base64-encoded ArrayBuffer containing the + * @param authenticatorInfo.base64CredentialID Base64URL-encoded ArrayBuffer containing the * authenticator's credential ID for the public key above. **Should be kept in a DB for later * reference!** */ diff --git a/packages/server/src/helpers/decodeAttestationObject.ts b/packages/server/src/helpers/decodeAttestationObject.ts index 374dbf4..2eb9997 100644 --- a/packages/server/src/helpers/decodeAttestationObject.ts +++ b/packages/server/src/helpers/decodeAttestationObject.ts @@ -2,9 +2,9 @@ import base64url from 'base64url'; import cbor from 'cbor'; /** - * Convert an AttestationObject from base64 string to a proper object + * Convert an AttestationObject from base64url string to a proper object * - * @param base64AttestationObject Base64-encoded Attestation Object + * @param base64AttestationObject Base64URL-encoded Attestation Object */ export default function decodeAttestationObject( base64AttestationObject: string, diff --git a/packages/typescript-types/src/index.ts b/packages/typescript-types/src/index.ts index 0f84c6a..da063a5 100644 --- a/packages/typescript-types/src/index.ts +++ b/packages/typescript-types/src/index.ts @@ -29,14 +29,12 @@ PublicKeyCredentialRequestOptions, 'challenge' |'allowCredentials' export interface PublicKeyCredentialDescriptorJSON extends Omit< PublicKeyCredentialDescriptor, 'id' > { - // Should be a Base64-encoded credential ID. Will be converted to a Uint8Array in the browser id: Base64URLString; } export interface PublicKeyCredentialUserEntityJSON extends Omit < PublicKeyCredentialUserEntity, 'id' > { - // Should be a Base64-encoded credential ID. Will be converted to a Uint8Array in the browser id: Base64URLString; } @@ -49,7 +47,7 @@ export interface AttestationCredential extends PublicKeyCredential { /** * A slightly-modified AttestationCredential to simplify working with ArrayBuffers that - * are base64-encoded in the browser so that they can be sent as JSON to the server. + * are base64url-encoded in the browser so that they can be sent as JSON to the server. */ export interface AttestationCredentialJSON extends Omit { @@ -66,7 +64,7 @@ export interface AssertionCredential extends PublicKeyCredential { /** * A slightly-modified AssertionCredential to simplify working with ArrayBuffers that - * are base64-encoded in the browser so that they can be sent as JSON to the server. + * are base64url-encoded in the browser so that they can be sent as JSON to the server. */ export interface AssertionCredentialJSON extends Omit { -- cgit v1.2.3