summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--packages/browser/package-lock.json13
-rw-r--r--packages/browser/package.json5
-rw-r--r--packages/browser/src/helpers/base64URLStringToBuffer.ts33
-rw-r--r--packages/browser/src/helpers/bufferToBase64URLString.ts21
-rw-r--r--packages/browser/src/helpers/toBase64String.test.ts15
-rw-r--r--packages/browser/src/helpers/toBase64String.ts6
-rw-r--r--packages/browser/src/helpers/toPublicKeyCredentialDescriptor.ts8
-rw-r--r--packages/browser/src/methods/startAssertion.test.ts54
-rw-r--r--packages/browser/src/methods/startAssertion.ts28
-rw-r--r--packages/browser/src/methods/startAttestation.test.ts34
-rw-r--r--packages/browser/src/methods/startAttestation.ts19
-rw-r--r--packages/server/src/assertion/generateAssertionOptions.test.ts2
-rw-r--r--packages/server/src/assertion/generateAssertionOptions.ts9
-rw-r--r--packages/server/src/assertion/verifyAssertionResponse.test.ts30
-rw-r--r--packages/server/src/assertion/verifyAssertionResponse.ts39
-rw-r--r--packages/server/src/attestation/generateAttestationOptions.test.ts2
-rw-r--r--packages/server/src/attestation/generateAttestationOptions.ts11
-rw-r--r--packages/server/src/attestation/verifications/verifyAndroidSafetyNet.ts27
-rw-r--r--packages/server/src/attestation/verifications/verifyFIDOU2F.ts4
-rw-r--r--packages/server/src/attestation/verifications/verifyNone.ts4
-rw-r--r--packages/server/src/attestation/verifications/verifyPacked.ts14
-rw-r--r--packages/server/src/attestation/verifyAttestationResponse.test.ts333
-rw-r--r--packages/server/src/attestation/verifyAttestationResponse.ts50
-rw-r--r--packages/server/src/helpers/convertCOSEtoPKCS.test.ts3
-rw-r--r--packages/server/src/helpers/convertCOSEtoPKCS.ts13
-rw-r--r--packages/server/src/helpers/decodeAttestationObject.ts22
-rw-r--r--packages/server/src/helpers/decodeClientDataJSON.ts8
-rw-r--r--packages/server/src/helpers/getCertificateInfo.ts7
-rw-r--r--packages/server/src/helpers/parseAuthenticatorData.ts19
-rw-r--r--packages/typescript-types/src/index.ts182
30 files changed, 541 insertions, 474 deletions
diff --git a/packages/browser/package-lock.json b/packages/browser/package-lock.json
index 5145834..f9896f4 100644
--- a/packages/browser/package-lock.json
+++ b/packages/browser/package-lock.json
@@ -4,10 +4,8 @@
"lockfileVersion": 1,
"requires": true,
"dependencies": {
- "@types/base64-js": {
- "version": "1.2.5",
- "resolved": "https://registry.npmjs.org/@types/base64-js/-/base64-js-1.2.5.tgz",
- "integrity": "sha1-WCskdhaabLpGCiFNR2x0REHYc9U=",
+ "@simplewebauthn/typescript-types": {
+ "version": "file:../typescript-types",
"dev": true
},
"@webassemblyjs/ast": {
@@ -185,10 +183,6 @@
"@xtuc/long": "4.2.2"
}
},
- "@simplewebauthn/typescript-types": {
- "version": "file:../typescript-types",
- "dev": true
- },
"@xtuc/ieee754": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz",
@@ -416,7 +410,8 @@
"base64-js": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz",
- "integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g=="
+ "integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g==",
+ "dev": true
},
"big.js": {
"version": "5.2.2",
diff --git a/packages/browser/package.json b/packages/browser/package.json
index c6c9990..d3d348d 100644
--- a/packages/browser/package.json
+++ b/packages/browser/package.json
@@ -22,12 +22,9 @@
"typescript",
"umd"
],
- "dependencies": {
- "base64-js": "^1.3.1"
- },
+ "dependencies": {},
"devDependencies": {
"@simplewebauthn/typescript-types": "file:../typescript-types",
- "@types/base64-js": "^1.2.5",
"ts-loader": "^7.0.4",
"webpack": "^4.43.0",
"webpack-auto-inject-version": "^1.2.2",
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.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, '_');
-}
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),
};
}
diff --git a/packages/browser/src/methods/startAssertion.test.ts b/packages/browser/src/methods/startAssertion.test.ts
index db401dc..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<any> => {
return new Promise(resolve => {
- resolve({ response: {} });
+ resolve({
+ response: {},
+ getClientExtensionResults: () => ({}),
+ });
});
},
);
@@ -53,31 +52,30 @@ 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);
- const credentialID = 'foobar';
-
mockNavigatorGet.mockImplementation(
(): Promise<AssertionCredential> => {
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',
@@ -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('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/startAssertion.ts b/packages/browser/src/methods/startAssertion.ts
index 093cf30..b65325b 100644
--- a/packages/browser/src/methods/startAssertion.ts
+++ b/packages/browser/src/methods/startAssertion.ts
@@ -1,11 +1,11 @@
import {
PublicKeyCredentialRequestOptionsJSON,
- AuthenticatorAssertionResponseJSON,
AssertionCredential,
+ AssertionCredentialJSON,
} 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';
@@ -16,7 +16,7 @@ import toPublicKeyCredentialDescriptor from '../helpers/toPublicKeyCredentialDes
*/
export default async function startAssertion(
requestOptionsJSON: PublicKeyCredentialRequestOptionsJSON,
-): Promise<AuthenticatorAssertionResponseJSON> {
+): Promise<AssertionCredentialJSON> {
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 { id, rawId, response, type } = credential;
- let base64UserHandle = undefined;
+ let userHandle = undefined;
if (response.userHandle) {
- base64UserHandle = toBase64String(response.userHandle);
+ userHandle = bufferToBase64URLString(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,
+ id,
+ rawId: bufferToBase64URLString(rawId),
+ response: {
+ authenticatorData: bufferToBase64URLString(response.authenticatorData),
+ clientDataJSON: bufferToBase64URLString(response.clientDataJSON),
+ signature: bufferToBase64URLString(response.signature),
+ userHandle,
+ },
+ type,
};
}
diff --git a/packages/browser/src/methods/startAttestation.test.ts b/packages/browser/src/methods/startAttestation.test.ts
index ae79235..bf6ab9b 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,19 +62,22 @@ 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();
});
-test('should return base64-encoded response values', async done => {
+test('should return base64url-encoded response values', async done => {
mockSupportsWebauthn.mockReturnValue(true);
mockNavigatorCreate.mockImplementation(
@@ -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',
@@ -98,10 +99,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('bW9ja0F0dGU');
+ expect(response.response.clientDataJSON).toEqual('bW9ja0NsaWU');
done();
});
diff --git a/packages/browser/src/methods/startAttestation.ts b/packages/browser/src/methods/startAttestation.ts
index ea05fa8..d5e540f 100644
--- a/packages/browser/src/methods/startAttestation.ts
+++ b/packages/browser/src/methods/startAttestation.ts
@@ -1,11 +1,11 @@
import {
PublicKeyCredentialCreationOptionsJSON,
- AuthenticatorAttestationResponseJSON,
AttestationCredential,
+ AttestationCredentialJSON,
} 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';
@@ -16,7 +16,7 @@ import toPublicKeyCredentialDescriptor from '../helpers/toPublicKeyCredentialDes
*/
export default async function startAttestation(
creationOptionsJSON: PublicKeyCredentialCreationOptionsJSON,
-): Promise<AuthenticatorAttestationResponseJSON> {
+): Promise<AttestationCredentialJSON> {
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 { id, rawId, response, type } = credential;
// Convert values to base64 to make it easier to send back to the server
return {
- base64AttestationObject: toBase64String(response.attestationObject),
- base64ClientDataJSON: toBase64String(response.clientDataJSON),
+ id,
+ rawId: bufferToBase64URLString(rawId),
+ response: {
+ attestationObject: bufferToBase64URLString(response.attestationObject),
+ clientDataJSON: bufferToBase64URLString(response.clientDataJSON),
+ },
+ type,
};
}
diff --git a/packages/server/src/assertion/generateAssertionOptions.test.ts b/packages/server/src/assertion/generateAssertionOptions.test.ts
index bd2d48d..9cae665 100644
--- a/packages/server/src/assertion/generateAssertionOptions.test.ts
+++ b/packages/server/src/assertion/generateAssertionOptions.test.ts
@@ -61,7 +61,7 @@ test('should set extensions if specified', () => {
const goodOpts1 = {
challenge: 'totallyrandomvalue',
- allowedBase64CredentialIDs: [
+ allowedCredentialIDs: [
Buffer.from('1234', 'ascii').toString('base64'),
Buffer.from('5678', 'ascii').toString('base64'),
],
diff --git a/packages/server/src/assertion/generateAssertionOptions.ts b/packages/server/src/assertion/generateAssertionOptions.ts
index 9444a54..6645e2e 100644
--- a/packages/server/src/assertion/generateAssertionOptions.ts
+++ b/packages/server/src/assertion/generateAssertionOptions.ts
@@ -1,10 +1,11 @@
import type {
PublicKeyCredentialRequestOptionsJSON,
+ Base64URLString,
} from '@simplewebauthn/typescript-types';
type Options = {
challenge: string,
- allowedBase64CredentialIDs: string[],
+ allowedCredentialIDs: Base64URLString[],
suggestedTransports?: AuthenticatorTransport[],
timeout?: number,
userVerification?: UserVerificationRequirement,
@@ -15,7 +16,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 allowedCredentialIDs 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
@@ -28,7 +29,7 @@ export default function generateAssertionOptions(
): PublicKeyCredentialRequestOptionsJSON {
const {
challenge,
- allowedBase64CredentialIDs,
+ allowedCredentialIDs,
suggestedTransports = ['usb', 'ble', 'nfc', 'internal'],
timeout = 60000,
userVerification,
@@ -37,7 +38,7 @@ export default function generateAssertionOptions(
return {
challenge,
- allowCredentials: allowedBase64CredentialIDs.map(id => ({
+ allowCredentials: allowedCredentialIDs.map(id => ({
id,
type: 'public-key',
transports: suggestedTransports,
diff --git a/packages/server/src/assertion/verifyAssertionResponse.test.ts b/packages/server/src/assertion/verifyAssertionResponse.test.ts
index 5895b66..9da06ce 100644
--- a/packages/server/src/assertion/verifyAssertionResponse.test.ts
+++ b/packages/server/src/assertion/verifyAssertionResponse.test.ts
@@ -48,7 +48,7 @@ test('should return authenticator info after verification', () => {
expect(verification.authenticatorInfo.counter).toEqual(144);
expect(verification.authenticatorInfo.base64CredentialID).toEqual(
- authenticator.base64CredentialID,
+ authenticator.credentialID,
);
});
@@ -111,24 +111,28 @@ test('should throw error if previous counter value is not less than in response'
});
const assertionResponse = {
- base64CredentialID:
- 'KEbWNCc7NgaYnUyrNeFGX9_3Y-8oJ3KwzjnaiD1d1LVTxR7v3CaKfCz2Vy_g_MHSh7yJ8yL0Px' + 'g6jo_o0hYiew',
- base64AuthenticatorData: 'PdxHEOnAiLIp26idVjIguzn3Ipr_RlsKZWsa-5qK-KABAAAAkA==',
- base64ClientDataJSON:
- 'eyJjaGFsbGVuZ2UiOiJkRzkwWVd4c2VWVnVhWEYxWlZaaGJIVmxSWFpsY25sVWFXMWwiLCJj' +
- 'bGllbnRFeHRlbnNpb25zIjp7fSwiaGFzaEFsZ29yaXRobSI6IlNIQS0yNTYiLCJvcmlnaW4iOiJodHRwczovL2Rldi5k' +
- 'b250bmVlZGEucHciLCJ0eXBlIjoid2ViYXV0aG4uZ2V0In0=',
- base64Signature:
- 'MEUCIQDYXBOpCWSWq2Ll4558GJKD2RoWg958lvJSB_GdeokxogIgWuEVQ7ee6AswQY0OsuQ6y8Ks6' +
- 'jhd45bDx92wjXKs900=',
+ id: 'KEbWNCc7NgaYnUyrNeFGX9_3Y-8oJ3KwzjnaiD1d1LVTxR7v3CaKfCz2Vy_g_MHSh7yJ8yL0Pxg6jo_o0hYiew',
+ rawId: '',
+ response: {
+ authenticatorData: 'PdxHEOnAiLIp26idVjIguzn3Ipr_RlsKZWsa-5qK-KABAAAAkA==',
+ clientDataJSON:
+ 'eyJjaGFsbGVuZ2UiOiJkRzkwWVd4c2VWVnVhWEYxWlZaaGJIVmxSWFpsY25sVWFXMWwiLCJj' +
+ 'bGllbnRFeHRlbnNpb25zIjp7fSwiaGFzaEFsZ29yaXRobSI6IlNIQS0yNTYiLCJvcmlnaW4iOiJodHRwczovL2Rldi5k' +
+ 'b250bmVlZGEucHciLCJ0eXBlIjoid2ViYXV0aG4uZ2V0In0=',
+ signature:
+ 'MEUCIQDYXBOpCWSWq2Ll4558GJKD2RoWg958lvJSB_GdeokxogIgWuEVQ7ee6AswQY0OsuQ6y8Ks6' +
+ 'jhd45bDx92wjXKs900=',
+ },
+ getClientExtensionResults: () => ({}),
+ type: 'webauthn.get',
};
const assertionChallenge = 'totallyUniqueValueEveryTime';
const assertionOrigin = 'https://dev.dontneeda.pw';
const authenticator = {
- base64PublicKey:
+ publicKey:
'BIheFp-u6GvFT2LNGovf3ZrT0iFVBsA_76rRysxRG9A18WGeA6hPmnab0HAViUYVRkwTNcN77QBf_' + 'RR0dv3lIvQ',
- base64CredentialID:
+ credentialID:
'KEbWNCc7NgaYnUyrNeFGX9_3Y-8oJ3KwzjnaiD1d1LVTxR7v3CaKfCz2Vy_g_MHSh7yJ8yL0Px' + 'g6jo_o0hYiew',
counter: 0,
};
diff --git a/packages/server/src/assertion/verifyAssertionResponse.ts b/packages/server/src/assertion/verifyAssertionResponse.ts
index 24ee17e..7d13271 100644
--- a/packages/server/src/assertion/verifyAssertionResponse.ts
+++ b/packages/server/src/assertion/verifyAssertionResponse.ts
@@ -1,8 +1,7 @@
import base64url from 'base64url';
import {
- AuthenticatorAssertionResponseJSON,
+ AssertionCredentialJSON,
AuthenticatorDevice,
- VerifiedAssertion,
} from '@simplewebauthn/typescript-types';
import decodeClientDataJSON from '../helpers/decodeClientDataJSON';
@@ -14,19 +13,19 @@ 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
*/
export default function verifyAssertionResponse(
- response: AuthenticatorAssertionResponseJSON,
+ credential: AssertionCredentialJSON,
expectedChallenge: string,
expectedOrigin: string,
authenticator: AuthenticatorDevice,
): VerifiedAssertion {
- const { base64AuthenticatorData, base64ClientDataJSON, base64Signature } = response;
- const clientDataJSON = decodeClientDataJSON(base64ClientDataJSON);
+ const { response } = credential;
+ const clientDataJSON = decodeClientDataJSON(response.clientDataJSON);
const { type, origin, challenge } = clientDataJSON;
@@ -50,7 +49,7 @@ export default function verifyAssertionResponse(
throw new Error(`Unexpected assertion type: ${type}`);
}
- const authDataBuffer = base64url.toBuffer(base64AuthenticatorData);
+ const authDataBuffer = base64url.toBuffer(response.authenticatorData);
const authDataStruct = parseAuthenticatorData(authDataBuffer);
const { flags, counter } = authDataStruct;
@@ -70,19 +69,37 @@ export default function verifyAssertionResponse(
const { rpIdHash, flagsBuf, counterBuf } = authDataStruct;
- const clientDataHash = toHash(base64url.toBuffer(base64ClientDataJSON));
+ const clientDataHash = toHash(base64url.toBuffer(response.clientDataJSON));
const signatureBase = Buffer.concat([rpIdHash, flagsBuf, counterBuf, clientDataHash]);
- const publicKey = convertASN1toPEM(base64url.toBuffer(authenticator.base64PublicKey));
- const signature = base64url.toBuffer(base64Signature);
+ const publicKey = convertASN1toPEM(base64url.toBuffer(authenticator.publicKey));
+ const signature = base64url.toBuffer(response.signature);
const toReturn = {
verified: verifySignature(signature, signatureBase, publicKey),
authenticatorInfo: {
counter,
- base64CredentialID: response.base64CredentialID,
+ base64CredentialID: credential.id,
},
};
return toReturn;
}
+
+/**
+ * Result of assertion verification
+ *
+ * @param verified If the assertion response could be verified
+ * @param authenticatorInfo.base64CredentialID The ID of the authenticator used during assertion.
+ * Should be used to identify which DB authenticator entry needs its `counter` updated to the value
+ * below
+ * @param authenticatorInfo.counter The number of times the authenticator identified above reported
+ * it has been used. **Should be kept in a DB for later reference to help prevent replay attacks!**
+ */
+export type VerifiedAssertion = {
+ verified: boolean;
+ authenticatorInfo: {
+ counter: number;
+ base64CredentialID: string;
+ };
+};
diff --git a/packages/server/src/attestation/generateAttestationOptions.test.ts b/packages/server/src/attestation/generateAttestationOptions.test.ts
index 2f5c439..2b83fa9 100644
--- a/packages/server/src/attestation/generateAttestationOptions.test.ts
+++ b/packages/server/src/attestation/generateAttestationOptions.test.ts
@@ -49,7 +49,7 @@ test('should map excluded credential IDs if specified', () => {
challenge: 'totallyrandomvalue',
userID: '1234',
userName: 'usernameHere',
- excludedBase64CredentialIDs: ['someIDhere'],
+ excludedCredentialIDs: ['someIDhere'],
});
expect(options.excludeCredentials).toEqual([{
diff --git a/packages/server/src/attestation/generateAttestationOptions.ts b/packages/server/src/attestation/generateAttestationOptions.ts
index 89ac86a..d142740 100644
--- a/packages/server/src/attestation/generateAttestationOptions.ts
+++ b/packages/server/src/attestation/generateAttestationOptions.ts
@@ -1,5 +1,6 @@
-import {
+import type {
PublicKeyCredentialCreationOptionsJSON,
+ Base64URLString,
} from '@simplewebauthn/typescript-types';
type Options = {
@@ -11,7 +12,7 @@ type Options = {
userDisplayName?: string,
timeout?: number,
attestationType?: AttestationConveyancePreference,
- excludedBase64CredentialIDs?: string[],
+ excludedCredentialIDs?: Base64URLString[],
suggestedTransports?: AuthenticatorTransport[],
authenticatorSelection?: AuthenticatorSelectionCriteria,
extensions?: AuthenticationExtensionsClientInputs,
@@ -30,7 +31,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 excludedCredentialIDs 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
@@ -49,7 +50,7 @@ export default function generateAttestationOptions(
userDisplayName = userName,
timeout = 60000,
attestationType = 'none',
- excludedBase64CredentialIDs = [],
+ excludedCredentialIDs = [],
suggestedTransports = ['usb', 'ble', 'nfc', 'internal'],
authenticatorSelection,
extensions,
@@ -74,7 +75,7 @@ export default function generateAttestationOptions(
],
timeout,
attestation: attestationType,
- excludeCredentials: excludedBase64CredentialIDs.map((id) => ({
+ excludeCredentials: excludedCredentialIDs.map((id) => ({
id,
type: 'public-key',
transports: suggestedTransports,
diff --git a/packages/server/src/attestation/verifications/verifyAndroidSafetyNet.ts b/packages/server/src/attestation/verifications/verifyAndroidSafetyNet.ts
index 0f604d0..a5dc89a 100644
--- a/packages/server/src/attestation/verifications/verifyAndroidSafetyNet.ts
+++ b/packages/server/src/attestation/verifications/verifyAndroidSafetyNet.ts
@@ -1,11 +1,7 @@
import base64url from 'base64url';
-import {
- AttestationObject,
- VerifiedAttestation,
- SafetyNetJWTHeader,
- SafetyNetJWTPayload,
- SafetyNetJWTSignature,
-} from '@simplewebauthn/typescript-types';
+
+import type { AttestationObject } from '../../helpers/decodeAttestationObject';
+import type { VerifiedAttestation } from '../verifyAttestationResponse';
import toHash from '../../helpers/toHash';
import verifySignature from '../../helpers/verifySignature';
@@ -151,3 +147,20 @@ const GlobalSignRootCAR2 =
'7mpM0sYmsL4h4hO291xNBrBVNpGP-DTKqttVCL1OmLNIG-6KYnX3ZHu01yiPqFbQfXf5WRDLenVOavSot-3i9DAgBkcRcA' +
'tjOj4LaR0VknFBbVPFd5uRHg5h6h-u_N5GJG79G-dwfCMNYxdAfvDbbnvRG15RjF-Cv6pgsH_76tuIMRQyV-dTZsXjAzlA' +
'cmgQWpzU_qlULRuJQ_7TBj0_VLZjmmx6BEP3ojY-x1J96relc8geMJgEtslQIxq_H5COEBkEveegeGTLg';
+
+type SafetyNetJWTHeader = {
+ alg: 'string';
+ x5c: string[];
+};
+
+type SafetyNetJWTPayload = {
+ nonce: string;
+ timestampMs: number;
+ apkPackageName: string;
+ apkDigestSha256: string;
+ ctsProfileMatch: boolean;
+ apkCertificateDigestSha256: string[];
+ basicIntegrity: boolean;
+};
+
+type SafetyNetJWTSignature = string;
diff --git a/packages/server/src/attestation/verifications/verifyFIDOU2F.ts b/packages/server/src/attestation/verifications/verifyFIDOU2F.ts
index e518dc8..5842a3c 100644
--- a/packages/server/src/attestation/verifications/verifyFIDOU2F.ts
+++ b/packages/server/src/attestation/verifications/verifyFIDOU2F.ts
@@ -1,5 +1,7 @@
import base64url from 'base64url';
-import { AttestationObject, VerifiedAttestation } from '@simplewebauthn/typescript-types';
+
+import type { AttestationObject } from '../../helpers/decodeAttestationObject';
+import type { VerifiedAttestation } from '../verifyAttestationResponse';
import toHash from '../../helpers/toHash';
import convertCOSEtoPKCS from '../../helpers/convertCOSEtoPKCS';
diff --git a/packages/server/src/attestation/verifications/verifyNone.ts b/packages/server/src/attestation/verifications/verifyNone.ts
index 423f4fd..66fd7da 100644
--- a/packages/server/src/attestation/verifications/verifyNone.ts
+++ b/packages/server/src/attestation/verifications/verifyNone.ts
@@ -1,5 +1,7 @@
import base64url from 'base64url';
-import { AttestationObject, VerifiedAttestation } from '@simplewebauthn/typescript-types';
+
+import type { AttestationObject } from '../../helpers/decodeAttestationObject';
+import type { VerifiedAttestation } from '../verifyAttestationResponse';
import convertCOSEtoPKCS from '../../helpers/convertCOSEtoPKCS';
import parseAuthenticatorData from '../../helpers/parseAuthenticatorData';
diff --git a/packages/server/src/attestation/verifications/verifyPacked.ts b/packages/server/src/attestation/verifications/verifyPacked.ts
index f7b9932..16acdfd 100644
--- a/packages/server/src/attestation/verifications/verifyPacked.ts
+++ b/packages/server/src/attestation/verifications/verifyPacked.ts
@@ -2,14 +2,14 @@ import base64url from 'base64url';
import cbor from 'cbor';
import elliptic from 'elliptic';
import NodeRSA, { SigningSchemeHash } from 'node-rsa';
-import {
- AttestationObject,
- VerifiedAttestation,
- COSEKEYS,
- COSEPublicKey as COSEPublicKeyType,
-} from '@simplewebauthn/typescript-types';
-import convertCOSEtoPKCS from '../../helpers/convertCOSEtoPKCS';
+import type { AttestationObject } from '../../helpers/decodeAttestationObject';
+import type { VerifiedAttestation } from '../verifyAttestationResponse';
+
+import convertCOSEtoPKCS, {
+ COSEKEYS,
+ COSEPublicKey as COSEPublicKeyType
+} from '../../helpers/convertCOSEtoPKCS';
import toHash from '../../helpers/toHash';
import convertASN1toPEM from '../../helpers/convertASN1toPEM';
import getCertificateInfo from '../../helpers/getCertificateInfo';
diff --git a/packages/server/src/attestation/verifyAttestationResponse.test.ts b/packages/server/src/attestation/verifyAttestationResponse.test.ts
index 375264a..1e4cc0d 100644
--- a/packages/server/src/attestation/verifyAttestationResponse.test.ts
+++ b/packages/server/src/attestation/verifyAttestationResponse.test.ts
@@ -161,172 +161,203 @@ test('should throw if an unexpected attestation format is specified', () => {
});
const attestationFIDOU2F = {
- base64AttestationObject:
- 'o2NmbXRoZmlkby11MmZnYXR0U3RtdKJjc2lnWEgwRgIhAK40WxA0t7py7AjEXvwGw' +
- 'TlmqlvrOks5g9lf+9zXzRiVAiEA3bv60xyXveKDOusYzniD7CDSostCet9PYK7FLdnTdZNjeDVjgVkCwTCCAr0wg' +
- 'gGloAMCAQICBCrnYmMwDQYJKoZIhvcNAQELBQAwLjEsMCoGA1UEAxMjWXViaWNvIFUyRiBSb290IENBIFNlcmlhb' +
- 'CA0NTcyMDA2MzEwIBcNMTQwODAxMDAwMDAwWhgPMjA1MDA5MDQwMDAwMDBaMG4xCzAJBgNVBAYTAlNFMRIwEAYDV' +
- 'QQKDAlZdWJpY28gQUIxIjAgBgNVBAsMGUF1dGhlbnRpY2F0b3IgQXR0ZXN0YXRpb24xJzAlBgNVBAMMHll1Ymljb' +
- 'yBVMkYgRUUgU2VyaWFsIDcxOTgwNzA3NTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABCoDhl5gQ9meEf8QqiVUV' +
- '4S/Ca+Oax47MhcpIW9VEhqM2RDTmd3HaL3+SnvH49q8YubSRp/1Z1uP+okMynSGnj+jbDBqMCIGCSsGAQQBgsQKA' +
- 'gQVMS4zLjYuMS40LjEuNDE0ODIuMS4xMBMGCysGAQQBguUcAgEBBAQDAgQwMCEGCysGAQQBguUcAQEEBBIEEG1Eu' +
- 'pv27C5JuTAMj+kgy3MwDAYDVR0TAQH/BAIwADANBgkqhkiG9w0BAQsFAAOCAQEAclfQPNzD4RVphJDW+A75W1MHI' +
- '3PZ5kcyYysR3Nx3iuxr1ZJtB+F7nFQweI3jL05HtFh2/4xVIgKb6Th4eVcjMecncBaCinEbOcdP1sEli9Hk2eVm1' +
- 'XB5A0faUjXAPw/+QLFCjgXG6ReZ5HVUcWkB7riLsFeJNYitiKrTDXFPLy+sNtVNutcQnFsCerDKuM81TvEAigkIb' +
- 'KCGlq8M/NvBg5j83wIxbCYiyV7mIr3RwApHieShzLdJo1S6XydgQjC+/64G5r8C+8AVvNFR3zXXCpio5C3KRIj88' +
- 'HEEIYjf6h1fdLfqeIsq+cUUqbq5T+c4nNoZUZCysTB9v5EY4akp+GhhdXRoRGF0YVjEAbElFazplpnc037DORGDZ' +
- 'NjDq86cN9vm6+APoAM20wtBAAAAAAAAAAAAAAAAAAAAAAAAAAAAQGFYevaR71ptU5YtXOSnVzPQTsGgK+gLiBKnq' +
- 'PWBmZXNRvjISqlLxiwApzlrfkTc3lEMYMatjeACCnsijOkNEGOlAQIDJiABIVggdWLG6UvGyHFw/k/bv6/k6z/LL' +
- 'gSO5KXzXw2EcUxkEX8iWCBeaVLz/cbyoKvRIg/q+q7tan0VN+i3WR0BOBCcuNP7yw==',
- base64ClientDataJSON:
- 'eyJjaGFsbGVuZ2UiOiJVMmQ0TjNZME0wOU1jbGRQYjFSNVpFeG5UbG95IiwiY2xpZW50' +
- 'RXh0ZW5zaW9ucyI6e30sImhhc2hBbGdvcml0aG0iOiJTSEEtMjU2Iiwib3JpZ2luIjoiaHR0cHM6Ly9jbG92ZXIu' +
- 'bWlsbGVydGltZS5kZXY6MzAwMCIsInR5cGUiOiJ3ZWJhdXRobi5jcmVhdGUifQ==',
+ id: 'YVh69pHvWm1Tli1c5KdXM9BOwaAr6AuIEqeo9YGZlc1G-MhKqUvGLACnOWt-RNzeUQxgxq2N4AIKeyKM6Q0QYw',
+ rawId: 'YVh69pHvWm1Tli1c5KdXM9BOwaAr6AuIEqeo9YGZlc1G+MhKqUvGLACnOWt+RNzeUQxgxq2N4AIKeyKM6Q0QYw==',
+ response: {
+ attestationObject:
+ 'o2NmbXRoZmlkby11MmZnYXR0U3RtdKJjc2lnWEgwRgIhAK40WxA0t7py7AjEXvwGw' +
+ 'TlmqlvrOks5g9lf+9zXzRiVAiEA3bv60xyXveKDOusYzniD7CDSostCet9PYK7FLdnTdZNjeDVjgVkCwTCCAr0wg' +
+ 'gGloAMCAQICBCrnYmMwDQYJKoZIhvcNAQELBQAwLjEsMCoGA1UEAxMjWXViaWNvIFUyRiBSb290IENBIFNlcmlhb' +
+ 'CA0NTcyMDA2MzEwIBcNMTQwODAxMDAwMDAwWhgPMjA1MDA5MDQwMDAwMDBaMG4xCzAJBgNVBAYTAlNFMRIwEAYDV' +
+ 'QQKDAlZdWJpY28gQUIxIjAgBgNVBAsMGUF1dGhlbnRpY2F0b3IgQXR0ZXN0YXRpb24xJzAlBgNVBAMMHll1Ymljb' +
+ 'yBVMkYgRUUgU2VyaWFsIDcxOTgwNzA3NTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABCoDhl5gQ9meEf8QqiVUV' +
+ '4S/Ca+Oax47MhcpIW9VEhqM2RDTmd3HaL3+SnvH49q8YubSRp/1Z1uP+okMynSGnj+jbDBqMCIGCSsGAQQBgsQKA' +
+ 'gQVMS4zLjYuMS40LjEuNDE0ODIuMS4xMBMGCysGAQQBguUcAgEBBAQDAgQwMCEGCysGAQQBguUcAQEEBBIEEG1Eu' +
+ 'pv27C5JuTAMj+kgy3MwDAYDVR0TAQH/BAIwADANBgkqhkiG9w0BAQsFAAOCAQEAclfQPNzD4RVphJDW+A75W1MHI' +
+ '3PZ5kcyYysR3Nx3iuxr1ZJtB+F7nFQweI3jL05HtFh2/4xVIgKb6Th4eVcjMecncBaCinEbOcdP1sEli9Hk2eVm1' +
+ 'XB5A0faUjXAPw/+QLFCjgXG6ReZ5HVUcWkB7riLsFeJNYitiKrTDXFPLy+sNtVNutcQnFsCerDKuM81TvEAigkIb' +
+ 'KCGlq8M/NvBg5j83wIxbCYiyV7mIr3RwApHieShzLdJo1S6XydgQjC+/64G5r8C+8AVvNFR3zXXCpio5C3KRIj88' +
+ 'HEEIYjf6h1fdLfqeIsq+cUUqbq5T+c4nNoZUZCysTB9v5EY4akp+GhhdXRoRGF0YVjEAbElFazplpnc037DORGDZ' +
+ 'NjDq86cN9vm6+APoAM20wtBAAAAAAAAAAAAAAAAAAAAAAAAAAAAQGFYevaR71ptU5YtXOSnVzPQTsGgK+gLiBKnq' +
+ 'PWBmZXNRvjISqlLxiwApzlrfkTc3lEMYMatjeACCnsijOkNEGOlAQIDJiABIVggdWLG6UvGyHFw/k/bv6/k6z/LL' +
+ 'gSO5KXzXw2EcUxkEX8iWCBeaVLz/cbyoKvRIg/q+q7tan0VN+i3WR0BOBCcuNP7yw==',
+ clientDataJSON:
+ 'eyJjaGFsbGVuZ2UiOiJVMmQ0TjNZME0wOU1jbGRQYjFSNVpFeG5UbG95IiwiY2xpZW50' +
+ 'RXh0ZW5zaW9ucyI6e30sImhhc2hBbGdvcml0aG0iOiJTSEEtMjU2Iiwib3JpZ2luIjoiaHR0cHM6Ly9jbG92ZXIu' +
+ 'bWlsbGVydGltZS5kZXY6MzAwMCIsInR5cGUiOiJ3ZWJhdXRobi5jcmVhdGUifQ==',
+ },
+ getClientExtensionResults: () => ({}),
+ type: 'webauthn.create',
};
const attestationFIDOU2FChallenge = 'Sgx7v43OLrWOoTydLgNZ2';
const attestationPacked = {
- base64AttestationObject:
- 'o2NmbXRmcGFja2VkZ2F0dFN0bXSiY2FsZyZjc2lnWEcwRQIhANvrPZMUFrl_rvlgR' +
- 'qz6lCPlF6B4y885FYUCCrhrzAYXAiAb4dQKXbP3IimsTTadkwXQlrRVdxzlbmPXt847-Oh6r2hhdXRoRGF0YVjhP' +
- 'dxHEOnAiLIp26idVjIguzn3Ipr_RlsKZWsa-5qK-KBFXsOO-a3OAAI1vMYKZIsLJfHwVQMAXQGE4WNXLCDWOCa2x' +
- '8hpqk5dZy_xdc4wBd4UgCJ4M_JAHI7oJgDDVb8WUcKqRB_mzRxwCL9vdTl-ZKPXg3_-Zrt1Adgb7EnK9ivqaTOKM' +
- 'DqRrKsIObWYJaqpsSJtUKUBAgMmIAEhWCBKMVVaivqCBpqqAxMjuCo5jMeUdh3jDOC0EF4fLBNNTyJYILc7rqDDe' +
- 'X1pwCLrl3ZX7IThrtZNwKQVLQyfHiorqP-n',
- base64ClientDataJSON:
- 'eyJjaGFsbGVuZ2UiOiJjelpRU1dKQ2JsQlFibkpIVGxOQ2VFNWtkRVJ5VkRkVmNsWlpT' +
- 'a3M1U0UwIiwib3JpZ2luIjoiaHR0cHM6Ly9kZXYuZG9udG5lZWRhLnB3IiwidHlwZSI6IndlYmF1dGhuLmNyZWF0' +
- 'ZSJ9',
+ id: '',
+ rawId: '',
+ response: {
+ attestationObject:
+ 'o2NmbXRmcGFja2VkZ2F0dFN0bXSiY2FsZyZjc2lnWEcwRQIhANvrPZMUFrl_rvlgR' +
+ 'qz6lCPlF6B4y885FYUCCrhrzAYXAiAb4dQKXbP3IimsTTadkwXQlrRVdxzlbmPXt847-Oh6r2hhdXRoRGF0YVjhP' +
+ 'dxHEOnAiLIp26idVjIguzn3Ipr_RlsKZWsa-5qK-KBFXsOO-a3OAAI1vMYKZIsLJfHwVQMAXQGE4WNXLCDWOCa2x' +
+ '8hpqk5dZy_xdc4wBd4UgCJ4M_JAHI7oJgDDVb8WUcKqRB_mzRxwCL9vdTl-ZKPXg3_-Zrt1Adgb7EnK9ivqaTOKM' +
+ 'DqRrKsIObWYJaqpsSJtUKUBAgMmIAEhWCBKMVVaivqCBpqqAxMjuCo5jMeUdh3jDOC0EF4fLBNNTyJYILc7rqDDe' +
+ 'X1pwCLrl3ZX7IThrtZNwKQVLQyfHiorqP-n',
+ clientDataJSON:
+ 'eyJjaGFsbGVuZ2UiOiJjelpRU1dKQ2JsQlFibkpIVGxOQ2VFNWtkRVJ5VkRkVmNsWlpT' +
+ 'a3M1U0UwIiwib3JpZ2luIjoiaHR0cHM6Ly9kZXYuZG9udG5lZWRhLnB3IiwidHlwZSI6IndlYmF1dGhuLmNyZWF0' +
+ 'ZSJ9',
+ },
+ getClientExtensionResults: () => ({}),
+ type: 'webauthn.create',
};
const attestationPackedChallenge = 's6PIbBnPPnrGNSBxNdtDrT7UrVYJK9HM';
const attestationPackedX5C = {
- base64AttestationObject:
- 'o2NmbXRmcGFja2VkZ2F0dFN0bXSjY2FsZyZjc2lnWEcwRQIhAIMt_hGMtdgpIVIwMOeKK' +
- 'w0IkUUFkXSY8arKh3Q0c5QQAiB9Sv9JavAEmppeH_XkZjB7TFM3jfxsgl97iIkvuJOUImN4NWOBWQLBMIICvTCCAaWgA' +
- 'wIBAgIEKudiYzANBgkqhkiG9w0BAQsFADAuMSwwKgYDVQQDEyNZdWJpY28gVTJGIFJvb3QgQ0EgU2VyaWFsIDQ1NzIwM' +
- 'DYzMTAgFw0xNDA4MDEwMDAwMDBaGA8yMDUwMDkwNDAwMDAwMFowbjELMAkGA1UEBhMCU0UxEjAQBgNVBAoMCVl1Ymljb' +
- 'yBBQjEiMCAGA1UECwwZQXV0aGVudGljYXRvciBBdHRlc3RhdGlvbjEnMCUGA1UEAwweWXViaWNvIFUyRiBFRSBTZXJpY' +
- 'WwgNzE5ODA3MDc1MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEKgOGXmBD2Z4R_xCqJVRXhL8Jr45rHjsyFykhb1USG' +
- 'ozZENOZ3cdovf5Ke8fj2rxi5tJGn_VnW4_6iQzKdIaeP6NsMGowIgYJKwYBBAGCxAoCBBUxLjMuNi4xLjQuMS40MTQ4M' +
- 'i4xLjEwEwYLKwYBBAGC5RwCAQEEBAMCBDAwIQYLKwYBBAGC5RwBAQQEEgQQbUS6m_bsLkm5MAyP6SDLczAMBgNVHRMBA' +
- 'f8EAjAAMA0GCSqGSIb3DQEBCwUAA4IBAQByV9A83MPhFWmEkNb4DvlbUwcjc9nmRzJjKxHc3HeK7GvVkm0H4XucVDB4j' +
- 'eMvTke0WHb_jFUiApvpOHh5VyMx5ydwFoKKcRs5x0_WwSWL0eTZ5WbVcHkDR9pSNcA_D_5AsUKOBcbpF5nkdVRxaQHuu' +
- 'IuwV4k1iK2IqtMNcU8vL6w21U261xCcWwJ6sMq4zzVO8QCKCQhsoIaWrwz828GDmPzfAjFsJiLJXuYivdHACkeJ5KHMt' +
- '0mjVLpfJ2BCML7_rgbmvwL7wBW80VHfNdcKmKjkLcpEiPzwcQQhiN_qHV90t-p4iyr5xRSpurlP5zic2hlRkLKxMH2_k' +
- 'RjhqSn4aGF1dGhEYXRhWMQ93EcQ6cCIsinbqJ1WMiC7Ofcimv9GWwplaxr7mor4oEEAAAAcbUS6m_bsLkm5MAyP6SDLc' +
- 'wBA4rrvMciHCkdLQ2HghazIp1sMc8TmV8W8RgoX-x8tqV_1AmlqWACqUK8mBGLandr-htduQKPzgb2yWxOFV56TlqUBA' +
- 'gMmIAEhWCBsJbGAjckW-AA_XMk8OnB-VUvrs35ZpjtVJXRhnvXiGiJYIL2ncyg_KesCi44GH8UcZXYwjBkVdGMjNd6LF' +
- 'myiD6xf',
- base64ClientDataJSON:
- 'eyJ0eXBlIjoid2ViYXV0aG4uY3JlYXRlIiwiY2hhbGxlbmdlIjoiZEc5MFlXeHNlVlZ1YVhG' +
- 'MVpWWmhiSFZsUlhabGNubFVhVzFsIiwib3JpZ2luIjoiaHR0cHM6Ly9kZXYuZG9udG5lZWRhLnB3In0=',
+ // TODO: Grab these from another iPhone attestation
+ id: '',
+ rawId: '',
+ response: {
+ attestationObject:
+ 'o2NmbXRmcGFja2VkZ2F0dFN0bXSjY2FsZyZjc2lnWEcwRQIhAIMt_hGMtdgpIVIwMOeKK' +
+ 'w0IkUUFkXSY8arKh3Q0c5QQAiB9Sv9JavAEmppeH_XkZjB7TFM3jfxsgl97iIkvuJOUImN4NWOBWQLBMIICvTCCAaWgA' +
+ 'wIBAgIEKudiYzANBgkqhkiG9w0BAQsFADAuMSwwKgYDVQQDEyNZdWJpY28gVTJGIFJvb3QgQ0EgU2VyaWFsIDQ1NzIwM' +
+ 'DYzMTAgFw0xNDA4MDEwMDAwMDBaGA8yMDUwMDkwNDAwMDAwMFowbjELMAkGA1UEBhMCU0UxEjAQBgNVBAoMCVl1Ymljb' +
+ 'yBBQjEiMCAGA1UECwwZQXV0aGVudGljYXRvciBBdHRlc3RhdGlvbjEnMCUGA1UEAwweWXViaWNvIFUyRiBFRSBTZXJpY' +
+ 'WwgNzE5ODA3MDc1MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEKgOGXmBD2Z4R_xCqJVRXhL8Jr45rHjsyFykhb1USG' +
+ 'ozZENOZ3cdovf5Ke8fj2rxi5tJGn_VnW4_6iQzKdIaeP6NsMGowIgYJKwYBBAGCxAoCBBUxLjMuNi4xLjQuMS40MTQ4M' +
+ 'i4xLjEwEwYLKwYBBAGC5RwCAQEEBAMCBDAwIQYLKwYBBAGC5RwBAQQEEgQQbUS6m_bsLkm5MAyP6SDLczAMBgNVHRMBA' +
+ 'f8EAjAAMA0GCSqGSIb3DQEBCwUAA4IBAQByV9A83MPhFWmEkNb4DvlbUwcjc9nmRzJjKxHc3HeK7GvVkm0H4XucVDB4j' +
+ 'eMvTke0WHb_jFUiApvpOHh5VyMx5ydwFoKKcRs5x0_WwSWL0eTZ5WbVcHkDR9pSNcA_D_5AsUKOBcbpF5nkdVRxaQHuu' +
+ 'IuwV4k1iK2IqtMNcU8vL6w21U261xCcWwJ6sMq4zzVO8QCKCQhsoIaWrwz828GDmPzfAjFsJiLJXuYivdHACkeJ5KHMt' +
+ '0mjVLpfJ2BCML7_rgbmvwL7wBW80VHfNdcKmKjkLcpEiPzwcQQhiN_qHV90t-p4iyr5xRSpurlP5zic2hlRkLKxMH2_k' +
+ 'RjhqSn4aGF1dGhEYXRhWMQ93EcQ6cCIsinbqJ1WMiC7Ofcimv9GWwplaxr7mor4oEEAAAAcbUS6m_bsLkm5MAyP6SDLc' +
+ 'wBA4rrvMciHCkdLQ2HghazIp1sMc8TmV8W8RgoX-x8tqV_1AmlqWACqUK8mBGLandr-htduQKPzgb2yWxOFV56TlqUBA' +
+ 'gMmIAEhWCBsJbGAjckW-AA_XMk8OnB-VUvrs35ZpjtVJXRhnvXiGiJYIL2ncyg_KesCi44GH8UcZXYwjBkVdGMjNd6LF' +
+ 'myiD6xf',
+ clientDataJSON:
+ 'eyJ0eXBlIjoid2ViYXV0aG4uY3JlYXRlIiwiY2hhbGxlbmdlIjoiZEc5MFlXeHNlVlZ1YVhG' +
+ 'MVpWWmhiSFZsUlhabGNubFVhVzFsIiwib3JpZ2luIjoiaHR0cHM6Ly9kZXYuZG9udG5lZWRhLnB3In0=',
+ },
+ getClientExtensionResults: () => ({}),
+ type: 'webauthn.create',
};
const attestationPackedX5CChallenge = 'totallyUniqueValueEveryTime';
const attestationNone = {
- base64AttestationObject:
- 'o2NmbXRkbm9uZWdhdHRTdG10oGhhdXRoRGF0YVjFPdxHEOnAiLIp26idVjIguzn3I' +
- 'pr_RlsKZWsa-5qK-KBFAAAAAAAAAAAAAAAAAAAAAAAAAAAAQQHSlyRHIdWleVqO24-6ix7JFWODqDWo_arvEz3Se' +
- '5EgIFHkcVjZ4F5XDSBreIHsWRilRnKmaaqlqK3V2_4XtYs2pQECAyYgASFYID5PQTZQQg6haZFQWFzqfAOyQ_ENs' +
- 'MH8xxQ4GRiNPsqrIlggU8IVUOV8qpgk_Jh-OTaLuZL52KdX1fTht07X4DiQPow',
- base64ClientDataJSON:
- 'eyJ0eXBlIjoid2ViYXV0aG4uY3JlYXRlIiwiY2hhbGxlbmdlIjoiYUVWalkxQlhkWHBw' +
- 'VURBd1NEQndOV2Q0YURKZmRUVmZVRU0wVG1WWloyUSIsIm9yaWdpbiI6Imh0dHBzOlwvXC9kZXYuZG9udG5lZWRh' +
- 'LnB3IiwiYW5kcm9pZFBhY2thZ2VOYW1lIjoib3JnLm1vemlsbGEuZmlyZWZveCJ9',
+ id: 'AdKXJEch1aV5Wo7bj7qLHskVY4OoNaj9qu8TPdJ7kSAgUeRxWNngXlcNIGt4gexZGKVGcqZpqqWordXb_he1izY',
+ rawId: 'AdKXJEch1aV5Wo7bj7qLHskVY4OoNaj9qu8TPdJ7kSAgUeRxWNngXlcNIGt4gexZGKVGcqZpqqWordXb_he1izY',
+ response: {
+ attestationObject:
+ 'o2NmbXRkbm9uZWdhdHRTdG10oGhhdXRoRGF0YVjFPdxHEOnAiLIp26idVjIguzn3I' +
+ 'pr_RlsKZWsa-5qK-KBFAAAAAAAAAAAAAAAAAAAAAAAAAAAAQQHSlyRHIdWleVqO24-6ix7JFWODqDWo_arvEz3Se' +
+ '5EgIFHkcVjZ4F5XDSBreIHsWRilRnKmaaqlqK3V2_4XtYs2pQECAyYgASFYID5PQTZQQg6haZFQWFzqfAOyQ_ENs' +
+ 'MH8xxQ4GRiNPsqrIlggU8IVUOV8qpgk_Jh-OTaLuZL52KdX1fTht07X4DiQPow',
+ clientDataJSON:
+ 'eyJ0eXBlIjoid2ViYXV0aG4uY3JlYXRlIiwiY2hhbGxlbmdlIjoiYUVWalkxQlhkWHBw' +
+ 'VURBd1NEQndOV2Q0YURKZmRUVmZVRU0wVG1WWloyUSIsIm9yaWdpbiI6Imh0dHBzOlwvXC9kZXYuZG9udG5lZWRh' +
+ 'LnB3IiwiYW5kcm9pZFBhY2thZ2VOYW1lIjoib3JnLm1vemlsbGEuZmlyZWZveCJ9',
+ },
+ getClientExtensionResults: () => ({}),
+ type: 'webauthn.create',
};
const attestationNoneChallenge = 'hEccPWuziP00H0p5gxh2_u5_PC4NeYgd';
const attestationAndroidSafetyNet = {
- base64AttestationObject:
- 'o2NmbXRxYW5kcm9pZC1zYWZldHluZXRnYXR0U3RtdKJjdmVyaDE3MTIyMDM3aHJlc' +
- '3BvbnNlWRS9ZXlKaGJHY2lPaUpTVXpJMU5pSXNJbmcxWXlJNld5Sk5TVWxHYTJwRFEwSkljV2RCZDBsQ1FXZEpVV' +
- 'kpZY205T01GcFBaRkpyUWtGQlFVRkJRVkIxYm5wQlRrSm5hM0ZvYTJsSE9YY3dRa0ZSYzBaQlJFSkRUVkZ6ZDBOU' +
- 'ldVUldVVkZIUlhkS1ZsVjZSV1ZOUW5kSFFURlZSVU5vVFZaU01qbDJXako0YkVsR1VubGtXRTR3U1VaT2JHTnVXb' +
- 'kJaTWxaNlRWSk5kMFZSV1VSV1VWRkVSWGR3U0ZaR1RXZFJNRVZuVFZVNGVFMUNORmhFVkVVMFRWUkJlRTFFUVROT' +
- 'lZHc3dUbFp2V0VSVVJUVk5WRUYzVDFSQk0wMVVhekJPVm05M1lrUkZURTFCYTBkQk1WVkZRbWhOUTFaV1RYaEZla' +
- '0ZTUW1kT1ZrSkJaMVJEYTA1b1lrZHNiV0l6U25WaFYwVjRSbXBCVlVKblRsWkNRV05VUkZVeGRtUlhOVEJaVjJ4M' +
- 'VNVWmFjRnBZWTNoRmVrRlNRbWRPVmtKQmIxUkRhMlIyWWpKa2MxcFRRazFVUlUxNFIzcEJXa0puVGxaQ1FVMVVSV' +
- 'zFHTUdSSFZucGtRelZvWW0xU2VXSXliR3RNYlU1MllsUkRRMEZUU1hkRVVWbEtTMjlhU1doMlkwNUJVVVZDUWxGQ' +
- 'lJHZG5SVkJCUkVORFFWRnZRMmRuUlVKQlRtcFlhM293WlVzeFUwVTBiU3N2UnpWM1QyOHJXRWRUUlVOeWNXUnVPR' +
- 'Gh6UTNCU04yWnpNVFJtU3pCU2FETmFRMWxhVEVaSWNVSnJOa0Z0V2xaM01rczVSa2N3VHpseVVsQmxVVVJKVmxKN' +
- 'VJUTXdVWFZ1VXpsMVowaEROR1ZuT1c5MmRrOXRLMUZrV2pKd09UTllhSHAxYmxGRmFGVlhXRU40UVVSSlJVZEtTe' +
- 'k5UTW1GQlpucGxPVGxRVEZNeU9XaE1ZMUYxV1ZoSVJHRkROMDlhY1U1dWIzTnBUMGRwWm5NNGRqRnFhVFpJTDNob' +
- '2JIUkRXbVV5YkVvck4wZDFkSHBsZUV0d2VIWndSUzkwV2xObVlsazVNRFZ4VTJ4Q2FEbG1jR293TVRWamFtNVJSb' +
- 'XRWYzBGVmQyMUxWa0ZWZFdWVmVqUjBTMk5HU3pSd1pYWk9UR0Y0UlVGc0swOXJhV3hOZEVsWlJHRmpSRFZ1Wld3M' +
- 'GVFcHBlWE0wTVROb1lXZHhWekJYYUdnMVJsQXpPV2hIYXpsRkwwSjNVVlJxWVhwVGVFZGtkbGd3YlRaNFJsbG9hQ' +
- 'zh5VmsxNVdtcFVORXQ2VUVwRlEwRjNSVUZCWVU5RFFXeG5kMmRuU2xWTlFUUkhRVEZWWkVSM1JVSXZkMUZGUVhkS' +
- 'lJtOUVRVlJDWjA1V1NGTlZSVVJFUVV0Q1oyZHlRbWRGUmtKUlkwUkJWRUZOUW1kT1ZraFNUVUpCWmpoRlFXcEJRV' +
- 'TFDTUVkQk1WVmtSR2RSVjBKQ1VYRkNVWGRIVjI5S1FtRXhiMVJMY1hWd2J6UlhObmhVTm1veVJFRm1RbWRPVmtoV' +
- 'FRVVkhSRUZYWjBKVFdUQm1hSFZGVDNaUWJTdDRaMjU0YVZGSE5rUnlabEZ1T1V0NlFtdENaMmR5UW1kRlJrSlJZM' +
- 'EpCVVZKWlRVWlpkMHAzV1VsTGQxbENRbEZWU0UxQlIwZEhNbWd3WkVoQk5reDVPWFpaTTA1M1RHNUNjbUZUTlc1a' +
- 'U1qbHVUREprTUdONlJuWk5WRUZ5UW1kbmNrSm5SVVpDVVdOM1FXOVpabUZJVWpCalJHOTJURE5DY21GVE5XNWlNa' +
- 'mx1VERKa2VtTnFTWFpTTVZKVVRWVTRlRXh0VG5sa1JFRmtRbWRPVmtoU1JVVkdha0ZWWjJoS2FHUklVbXhqTTFGM' +
- 'VdWYzFhMk50T1hCYVF6VnFZakl3ZDBsUldVUldVakJuUWtKdmQwZEVRVWxDWjFwdVoxRjNRa0ZuU1hkRVFWbExTM' +
- '2RaUWtKQlNGZGxVVWxHUVhwQmRrSm5UbFpJVWpoRlMwUkJiVTFEVTJkSmNVRm5hR2cxYjJSSVVuZFBhVGgyV1ROS' +
- '2MweHVRbkpoVXpWdVlqSTVia3d3WkZWVmVrWlFUVk0xYW1OdGQzZG5aMFZGUW1kdmNrSm5SVVZCWkZvMVFXZFJRM' +
- 'EpKU0RGQ1NVaDVRVkJCUVdSM1EydDFVVzFSZEVKb1dVWkpaVGRGTmt4TldqTkJTMUJFVjFsQ1VHdGlNemRxYW1RN' +
- 'E1FOTVRVE5qUlVGQlFVRlhXbVJFTTFCTVFVRkJSVUYzUWtsTlJWbERTVkZEVTFwRFYyVk1Tblp6YVZaWE5rTm5LM' +
- 'mRxTHpsM1dWUktVbnAxTkVocGNXVTBaVmswWXk5dGVYcHFaMGxvUVV4VFlta3ZWR2g2WTNweGRHbHFNMlJyTTNaa' +
- 'VRHTkpWek5NYkRKQ01HODNOVWRSWkdoTmFXZGlRbWRCU0ZWQlZtaFJSMjFwTDFoM2RYcFVPV1ZIT1ZKTVNTdDRNR' +
- 'm95ZFdKNVdrVldla0UzTlZOWlZtUmhTakJPTUVGQlFVWnRXRkU1ZWpWQlFVRkNRVTFCVW1wQ1JVRnBRbU5EZDBFN' +
- 'WFqZE9WRWRZVURJM09IbzBhSEl2ZFVOSWFVRkdUSGx2UTNFeVN6QXJlVXhTZDBwVlltZEpaMlk0WjBocWRuQjNNb' +
- 'TFDTVVWVGFuRXlUMll6UVRCQlJVRjNRMnR1UTJGRlMwWlZlVm8zWmk5UmRFbDNSRkZaU2t0dldrbG9kbU5PUVZGR' +
- 'lRFSlJRVVJuWjBWQ1FVazVibFJtVWt0SlYyZDBiRmRzTTNkQ1REVTFSVlJXTm10aGVuTndhRmN4ZVVGak5VUjFiV' +
- 'FpZVHpReGExcDZkMG8yTVhkS2JXUlNVbFF2VlhORFNYa3hTMFYwTW1Nd1JXcG5iRzVLUTBZeVpXRjNZMFZYYkV4U' +
- 'ldUSllVRXg1Um1wclYxRk9ZbE5vUWpGcE5GY3lUbEpIZWxCb2RETnRNV0kwT1doaWMzUjFXRTAyZEZnMVEzbEZTR' +
- 'zVVYURoQ2IyMDBMMWRzUm1sb2VtaG5iamd4Ukd4a2IyZDZMMHN5VlhkTk5sTTJRMEl2VTBWNGEybFdabllyZW1KS' +
- '01ISnFkbWM1TkVGc1pHcFZabFYzYTBrNVZrNU5ha1ZRTldVNGVXUkNNMjlNYkRabmJIQkRaVVkxWkdkbVUxZzBWV' +
- 'Gw0TXpWdmFpOUpTV1F6VlVVdlpGQndZaTl4WjBkMmMydG1aR1Y2ZEcxVmRHVXZTMU50Y21sM1kyZFZWMWRsV0daV' +
- 'Vlra3plbk5wYTNkYVltdHdiVkpaUzIxcVVHMW9kalJ5YkdsNlIwTkhkRGhRYmpod2NUaE5Na3RFWmk5UU0ydFdiM' +
- '1F6WlRFNFVUMGlMQ0pOU1VsRlUycERRMEY2UzJkQmQwbENRV2RKVGtGbFR6QnRjVWRPYVhGdFFrcFhiRkYxUkVGT' +
- '1FtZHJjV2hyYVVjNWR6QkNRVkZ6UmtGRVFrMU5VMEYzU0dkWlJGWlJVVXhGZUdSSVlrYzVhVmxYZUZSaFYyUjFTV' +
- 'VpLZG1JelVXZFJNRVZuVEZOQ1UwMXFSVlJOUWtWSFFURlZSVU5vVFV0U01uaDJXVzFHYzFVeWJHNWlha1ZVVFVKR' +
- 'lIwRXhWVVZCZUUxTFVqSjRkbGx0Um5OVk1teHVZbXBCWlVaM01IaE9la0V5VFZSVmQwMUVRWGRPUkVwaFJuY3dlV' +
- 'TFVUlhsTlZGVjNUVVJCZDA1RVNtRk5SVWw0UTNwQlNrSm5UbFpDUVZsVVFXeFdWRTFTTkhkSVFWbEVWbEZSUzBWN' +
- 'FZraGlNamx1WWtkVloxWklTakZqTTFGblZUSldlV1J0YkdwYVdFMTRSWHBCVWtKblRsWkNRVTFVUTJ0a1ZWVjVRa' +
- '1JSVTBGNFZIcEZkMmRuUldsTlFUQkhRMU54UjFOSllqTkVVVVZDUVZGVlFVRTBTVUpFZDBGM1oyZEZTMEZ2U1VKQ' +
- 'lVVUlJSMDA1UmpGSmRrNHdOWHByVVU4NUszUk9NWEJKVW5aS2VucDVUMVJJVnpWRWVrVmFhRVF5WlZCRGJuWlZRV' +
- 'EJSYXpJNFJtZEpRMlpMY1VNNVJXdHpRelJVTW1aWFFsbHJMMnBEWmtNelVqTldXazFrVXk5a1RqUmFTME5GVUZwU' +
- '2NrRjZSSE5wUzFWRWVsSnliVUpDU2pWM2RXUm5lbTVrU1UxWlkweGxMMUpIUjBac05YbFBSRWxMWjJwRmRpOVRTa' +
- '2d2VlV3clpFVmhiSFJPTVRGQ2JYTkxLMlZSYlUxR0t5dEJZM2hIVG1oeU5UbHhUUzg1YVd3M01Va3laRTQ0Umtkb' +
- 'VkyUmtkM1ZoWldvMFlsaG9jREJNWTFGQ1ltcDRUV05KTjBwUU1HRk5NMVEwU1N0RWMyRjRiVXRHYzJKcWVtRlVUa' +
- '001ZFhwd1JteG5UMGxuTjNKU01qVjRiM2x1VlhoMk9IWk9iV3R4TjNwa1VFZElXR3Q0VjFrM2IwYzVhaXRLYTFKN' +
- 'VFrRkNhemRZY2twbWIzVmpRbHBGY1VaS1NsTlFhemRZUVRCTVMxY3dXVE42Tlc5Nk1rUXdZekYwU2t0M1NFRm5UV' +
- 'UpCUVVkcVoyZEZlazFKU1VKTWVrRlBRbWRPVmtoUk9FSkJaamhGUWtGTlEwRlpXWGRJVVZsRVZsSXdiRUpDV1hkR' +
- '1FWbEpTM2RaUWtKUlZVaEJkMFZIUTBOelIwRlJWVVpDZDAxRFRVSkpSMEV4VldSRmQwVkNMM2RSU1UxQldVSkJaa' +
- 'mhEUVZGQmQwaFJXVVJXVWpCUFFrSlpSVVpLYWxJclJ6UlJOamdyWWpkSFEyWkhTa0ZpYjA5ME9VTm1NSEpOUWpoS' +
- 'FFURlZaRWwzVVZsTlFtRkJSa3AyYVVJeFpHNUlRamRCWVdkaVpWZGlVMkZNWkM5alIxbFpkVTFFVlVkRFEzTkhRV' +
- 'kZWUmtKM1JVSkNRMnQzU25wQmJFSm5aM0pDWjBWR1FsRmpkMEZaV1ZwaFNGSXdZMFJ2ZGt3eU9XcGpNMEYxWTBkM' +
- 'GNFeHRaSFppTW1OMldqTk9lVTFxUVhsQ1owNVdTRkk0UlV0NlFYQk5RMlZuU21GQmFtaHBSbTlrU0ZKM1QyazRkb' +
- 'Gt6U25OTWJrSnlZVk0xYm1JeU9XNU1NbVI2WTJwSmRsb3pUbmxOYVRWcVkyMTNkMUIzV1VSV1VqQm5Ra1JuZDA1c' +
- 'VFUQkNaMXB1WjFGM1FrRm5TWGRMYWtGdlFtZG5ja0puUlVaQ1VXTkRRVkpaWTJGSVVqQmpTRTAyVEhrNWQyRXlhM' +
- '1ZhTWpsMlduazVlVnBZUW5aak1td3dZak5LTlV4NlFVNUNaMnR4YUd0cFJ6bDNNRUpCVVhOR1FVRlBRMEZSUlVGS' +
- 'GIwRXJUbTV1TnpoNU5uQlNhbVE1V0d4UlYwNWhOMGhVWjJsYUwzSXpVazVIYTIxVmJWbElVRkZ4TmxOamRHazVVR' +
- 'VZoYW5aM1VsUXlhVmRVU0ZGeU1ESm1aWE54VDNGQ1dUSkZWRlYzWjFwUksyeHNkRzlPUm5ab2MwODVkSFpDUTA5S' +
- 'llYcHdjM2RYUXpsaFNqbDRhblUwZEZkRVVVZzRUbFpWTmxsYVdpOVlkR1ZFVTBkVk9WbDZTbkZRYWxrNGNUTk5SS' +
- 'Gh5ZW0xeFpYQkNRMlkxYnpodGR5OTNTalJoTWtjMmVIcFZjalpHWWpaVU9FMWpSRTh5TWxCTVVrdzJkVE5OTkZSN' +
- 'mN6TkJNazB4YWpaaWVXdEtXV2s0ZDFkSlVtUkJka3RNVjFwMUwyRjRRbFppZWxsdGNXMTNhMjAxZWt4VFJGYzFia' +
- '2xCU21KRlRFTlJRMXAzVFVnMU5uUXlSSFp4YjJaNGN6WkNRbU5EUmtsYVZWTndlSFUyZURaMFpEQldOMU4yU2tOR' +
- 'GIzTnBjbE50U1dGMGFpODVaRk5UVmtSUmFXSmxkRGh4THpkVlN6UjJORnBWVGpnd1lYUnVXbm94ZVdjOVBTSmRmU' +
- 'S5leUp1YjI1alpTSTZJbkZyYjB4dE9XSnJUeXNyYzJoMFZITnZheXRqUW1GRmJFcEJXa1pXTUcxRlFqQTVVbWcxV' +
- 'TNKWVpGVTlJaXdpZEdsdFpYTjBZVzF3VFhNaU9qRTFOalUwTWpReU5qSTNOek1zSW1Gd2ExQmhZMnRoWjJWT1lXM' +
- 'WxJam9pWTI5dExtZHZiMmRzWlM1aGJtUnliMmxrTG1kdGN5SXNJbUZ3YTBScFoyVnpkRk5vWVRJMU5pSTZJaXR0Y' +
- '0ZKQ016RjRRemRTYUdsaWN5OWxWbUVyTDNWQ05XNTFaMVVyV0UxRFFXa3plSFZKZGpaMGIwMDlJaXdpWTNSelVIS' +
- 'nZabWxzWlUxaGRHTm9JanAwY25WbExDSmhjR3REWlhKMGFXWnBZMkYwWlVScFoyVnpkRk5vWVRJMU5pSTZXeUk0V' +
- 'URGelZ6QkZVRXBqYzJ4M04xVjZVbk5wV0V3Mk5IY3JUelV3UldRclVrSkpRM1JoZVRGbk1qUk5QU0pkTENKaVlYT' +
- 'nBZMGx1ZEdWbmNtbDBlU0k2ZEhKMVpYMC5yUW5Ib2FZVGgxTEU2VVZwaU1lZWFidDdUeWJ3dzdXZk42RzJ5R01tZ' +
- 'kVjbTFabjRWalZkenpoY1BqTS1WR052aWl1RGxyZ2VuWEViZ082V05YNlYzc0hHVjN1VGxGMlBuOUZsY3YxWmItS' +
- '2NGVHZUd29iYnY3LUp5VUZzTlhTSnhHZFRTOWxwNU5EdDFnWGJ6OVpORWhzVXI3ajBqbWNyaU9rR29PRzM4MXRSa' +
- '0Vqdk5aa0hpMkF1UDF2MWM4RXg3cEpZc09ISzJxaDlmSHFuSlAzcGowUFc3WThpcDBSTVZaNF9xZzFqc0dMMnZ0O' +
- 'G12cEJFMjg5dE1fcnROdm94TWU2aEx0Q1ZkdE9ZRjIzMWMtWVFJd2FEbnZWdDcwYW5XLUZYdUx3R1J5dWhfRlpNM' +
- '3FCSlhhcXdCNjNITk5uMmh5MFRDdHQ4RDdIMmI4MGltWkZRX1FoYXV0aERhdGFYxT3cRxDpwIiyKduonVYyILs59' +
- 'yKa_0ZbCmVrGvuaivigRQAAAAC5P9lh8uZGL7EiggAiR954AEEBDL2BKZVhBca7N3j3asDaoSrA3tJgT_E4KN25T' +
- 'hBVqBHCdffSZt9bvku7hPBcd76BzU7Y-ckXslUkD13Imbzde6UBAgMmIAEhWCCT4hId3ByJ_agRyznv1xIazx2nl' +
- 'VEGyvN7intoZr7C2CJYIKo3XB-cca9aUOLC-xhp3GfhyfTS0hjws5zL_bT_N1AL',
- base64ClientDataJSON:
- 'eyJ0eXBlIjoid2ViYXV0aG4uY3JlYXRlIiwiY2hhbGxlbmdlIjoiWDNaV1VHOUZOREpF' +
- 'YUMxM2F6Tmlka2h0WVd0MGFWWjJSVmxETFV4M1FsZyIsIm9yaWdpbiI6Imh0dHBzOlwvXC9kZXYuZG9udG5lZWRh' +
- 'LnB3IiwiYW5kcm9pZFBhY2thZ2VOYW1lIjoiY29tLmFuZHJvaWQuY2hyb21lIn0',
+ id: 'AQy9gSmVYQXGuzd492rA2qEqwN7SYE_xOCjduU4QVagRwnX30mbfW75Lu4TwXHe-gc1O2PnJF7JVJA9dyJm83Xs',
+ rawId: 'AQy9gSmVYQXGuzd492rA2qEqwN7SYE_xOCjduU4QVagRwnX30mbfW75Lu4TwXHe-gc1O2PnJF7JVJA9dyJm83Xs',
+ response: {
+ attestationObject:
+ 'o2NmbXRxYW5kcm9pZC1zYWZldHluZXRnYXR0U3RtdKJjdmVyaDE3MTIyMDM3aHJlc' +
+ '3BvbnNlWRS9ZXlKaGJHY2lPaUpTVXpJMU5pSXNJbmcxWXlJNld5Sk5TVWxHYTJwRFEwSkljV2RCZDBsQ1FXZEpVV' +
+ 'kpZY205T01GcFBaRkpyUWtGQlFVRkJRVkIxYm5wQlRrSm5hM0ZvYTJsSE9YY3dRa0ZSYzBaQlJFSkRUVkZ6ZDBOU' +
+ 'ldVUldVVkZIUlhkS1ZsVjZSV1ZOUW5kSFFURlZSVU5vVFZaU01qbDJXako0YkVsR1VubGtXRTR3U1VaT2JHTnVXb' +
+ 'kJaTWxaNlRWSk5kMFZSV1VSV1VWRkVSWGR3U0ZaR1RXZFJNRVZuVFZVNGVFMUNORmhFVkVVMFRWUkJlRTFFUVROT' +
+ 'lZHc3dUbFp2V0VSVVJUVk5WRUYzVDFSQk0wMVVhekJPVm05M1lrUkZURTFCYTBkQk1WVkZRbWhOUTFaV1RYaEZla' +
+ '0ZTUW1kT1ZrSkJaMVJEYTA1b1lrZHNiV0l6U25WaFYwVjRSbXBCVlVKblRsWkNRV05VUkZVeGRtUlhOVEJaVjJ4M' +
+ 'VNVWmFjRnBZWTNoRmVrRlNRbWRPVmtKQmIxUkRhMlIyWWpKa2MxcFRRazFVUlUxNFIzcEJXa0puVGxaQ1FVMVVSV' +
+ 'zFHTUdSSFZucGtRelZvWW0xU2VXSXliR3RNYlU1MllsUkRRMEZUU1hkRVVWbEtTMjlhU1doMlkwNUJVVVZDUWxGQ' +
+ 'lJHZG5SVkJCUkVORFFWRnZRMmRuUlVKQlRtcFlhM293WlVzeFUwVTBiU3N2UnpWM1QyOHJXRWRUUlVOeWNXUnVPR' +
+ 'Gh6UTNCU04yWnpNVFJtU3pCU2FETmFRMWxhVEVaSWNVSnJOa0Z0V2xaM01rczVSa2N3VHpseVVsQmxVVVJKVmxKN' +
+ 'VJUTXdVWFZ1VXpsMVowaEROR1ZuT1c5MmRrOXRLMUZrV2pKd09UTllhSHAxYmxGRmFGVlhXRU40UVVSSlJVZEtTe' +
+ 'k5UTW1GQlpucGxPVGxRVEZNeU9XaE1ZMUYxV1ZoSVJHRkROMDlhY1U1dWIzTnBUMGRwWm5NNGRqRnFhVFpJTDNob' +
+ '2JIUkRXbVV5YkVvck4wZDFkSHBsZUV0d2VIWndSUzkwV2xObVlsazVNRFZ4VTJ4Q2FEbG1jR293TVRWamFtNVJSb' +
+ 'XRWYzBGVmQyMUxWa0ZWZFdWVmVqUjBTMk5HU3pSd1pYWk9UR0Y0UlVGc0swOXJhV3hOZEVsWlJHRmpSRFZ1Wld3M' +
+ 'GVFcHBlWE0wTVROb1lXZHhWekJYYUdnMVJsQXpPV2hIYXpsRkwwSjNVVlJxWVhwVGVFZGtkbGd3YlRaNFJsbG9hQ' +
+ 'zh5VmsxNVdtcFVORXQ2VUVwRlEwRjNSVUZCWVU5RFFXeG5kMmRuU2xWTlFUUkhRVEZWWkVSM1JVSXZkMUZGUVhkS' +
+ 'lJtOUVRVlJDWjA1V1NGTlZSVVJFUVV0Q1oyZHlRbWRGUmtKUlkwUkJWRUZOUW1kT1ZraFNUVUpCWmpoRlFXcEJRV' +
+ 'TFDTUVkQk1WVmtSR2RSVjBKQ1VYRkNVWGRIVjI5S1FtRXhiMVJMY1hWd2J6UlhObmhVTm1veVJFRm1RbWRPVmtoV' +
+ 'FRVVkhSRUZYWjBKVFdUQm1hSFZGVDNaUWJTdDRaMjU0YVZGSE5rUnlabEZ1T1V0NlFtdENaMmR5UW1kRlJrSlJZM' +
+ 'EpCVVZKWlRVWlpkMHAzV1VsTGQxbENRbEZWU0UxQlIwZEhNbWd3WkVoQk5reDVPWFpaTTA1M1RHNUNjbUZUTlc1a' +
+ 'U1qbHVUREprTUdONlJuWk5WRUZ5UW1kbmNrSm5SVVpDVVdOM1FXOVpabUZJVWpCalJHOTJURE5DY21GVE5XNWlNa' +
+ 'mx1VERKa2VtTnFTWFpTTVZKVVRWVTRlRXh0VG5sa1JFRmtRbWRPVmtoU1JVVkdha0ZWWjJoS2FHUklVbXhqTTFGM' +
+ 'VdWYzFhMk50T1hCYVF6VnFZakl3ZDBsUldVUldVakJuUWtKdmQwZEVRVWxDWjFwdVoxRjNRa0ZuU1hkRVFWbExTM' +
+ '2RaUWtKQlNGZGxVVWxHUVhwQmRrSm5UbFpJVWpoRlMwUkJiVTFEVTJkSmNVRm5hR2cxYjJSSVVuZFBhVGgyV1ROS' +
+ '2MweHVRbkpoVXpWdVlqSTVia3d3WkZWVmVrWlFUVk0xYW1OdGQzZG5aMFZGUW1kdmNrSm5SVVZCWkZvMVFXZFJRM' +
+ 'EpKU0RGQ1NVaDVRVkJCUVdSM1EydDFVVzFSZEVKb1dVWkpaVGRGTmt4TldqTkJTMUJFVjFsQ1VHdGlNemRxYW1RN' +
+ 'E1FOTVRVE5qUlVGQlFVRlhXbVJFTTFCTVFVRkJSVUYzUWtsTlJWbERTVkZEVTFwRFYyVk1Tblp6YVZaWE5rTm5LM' +
+ 'mRxTHpsM1dWUktVbnAxTkVocGNXVTBaVmswWXk5dGVYcHFaMGxvUVV4VFlta3ZWR2g2WTNweGRHbHFNMlJyTTNaa' +
+ 'VRHTkpWek5NYkRKQ01HODNOVWRSWkdoTmFXZGlRbWRCU0ZWQlZtaFJSMjFwTDFoM2RYcFVPV1ZIT1ZKTVNTdDRNR' +
+ 'm95ZFdKNVdrVldla0UzTlZOWlZtUmhTakJPTUVGQlFVWnRXRkU1ZWpWQlFVRkNRVTFCVW1wQ1JVRnBRbU5EZDBFN' +
+ 'WFqZE9WRWRZVURJM09IbzBhSEl2ZFVOSWFVRkdUSGx2UTNFeVN6QXJlVXhTZDBwVlltZEpaMlk0WjBocWRuQjNNb' +
+ 'TFDTVVWVGFuRXlUMll6UVRCQlJVRjNRMnR1UTJGRlMwWlZlVm8zWmk5UmRFbDNSRkZaU2t0dldrbG9kbU5PUVZGR' +
+ 'lRFSlJRVVJuWjBWQ1FVazVibFJtVWt0SlYyZDBiRmRzTTNkQ1REVTFSVlJXTm10aGVuTndhRmN4ZVVGak5VUjFiV' +
+ 'FpZVHpReGExcDZkMG8yTVhkS2JXUlNVbFF2VlhORFNYa3hTMFYwTW1Nd1JXcG5iRzVLUTBZeVpXRjNZMFZYYkV4U' +
+ 'ldUSllVRXg1Um1wclYxRk9ZbE5vUWpGcE5GY3lUbEpIZWxCb2RETnRNV0kwT1doaWMzUjFXRTAyZEZnMVEzbEZTR' +
+ 'zVVYURoQ2IyMDBMMWRzUm1sb2VtaG5iamd4Ukd4a2IyZDZMMHN5VlhkTk5sTTJRMEl2VTBWNGEybFdabllyZW1KS' +
+ '01ISnFkbWM1TkVGc1pHcFZabFYzYTBrNVZrNU5ha1ZRTldVNGVXUkNNMjlNYkRabmJIQkRaVVkxWkdkbVUxZzBWV' +
+ 'Gw0TXpWdmFpOUpTV1F6VlVVdlpGQndZaTl4WjBkMmMydG1aR1Y2ZEcxVmRHVXZTMU50Y21sM1kyZFZWMWRsV0daV' +
+ 'Vlra3plbk5wYTNkYVltdHdiVkpaUzIxcVVHMW9kalJ5YkdsNlIwTkhkRGhRYmpod2NUaE5Na3RFWmk5UU0ydFdiM' +
+ '1F6WlRFNFVUMGlMQ0pOU1VsRlUycERRMEY2UzJkQmQwbENRV2RKVGtGbFR6QnRjVWRPYVhGdFFrcFhiRkYxUkVGT' +
+ '1FtZHJjV2hyYVVjNWR6QkNRVkZ6UmtGRVFrMU5VMEYzU0dkWlJGWlJVVXhGZUdSSVlrYzVhVmxYZUZSaFYyUjFTV' +
+ 'VpLZG1JelVXZFJNRVZuVEZOQ1UwMXFSVlJOUWtWSFFURlZSVU5vVFV0U01uaDJXVzFHYzFVeWJHNWlha1ZVVFVKR' +
+ 'lIwRXhWVVZCZUUxTFVqSjRkbGx0Um5OVk1teHVZbXBCWlVaM01IaE9la0V5VFZSVmQwMUVRWGRPUkVwaFJuY3dlV' +
+ 'TFVUlhsTlZGVjNUVVJCZDA1RVNtRk5SVWw0UTNwQlNrSm5UbFpDUVZsVVFXeFdWRTFTTkhkSVFWbEVWbEZSUzBWN' +
+ 'FZraGlNamx1WWtkVloxWklTakZqTTFGblZUSldlV1J0YkdwYVdFMTRSWHBCVWtKblRsWkNRVTFVUTJ0a1ZWVjVRa' +
+ '1JSVTBGNFZIcEZkMmRuUldsTlFUQkhRMU54UjFOSllqTkVVVVZDUVZGVlFVRTBTVUpFZDBGM1oyZEZTMEZ2U1VKQ' +
+ 'lVVUlJSMDA1UmpGSmRrNHdOWHByVVU4NUszUk9NWEJKVW5aS2VucDVUMVJJVnpWRWVrVmFhRVF5WlZCRGJuWlZRV' +
+ 'EJSYXpJNFJtZEpRMlpMY1VNNVJXdHpRelJVTW1aWFFsbHJMMnBEWmtNelVqTldXazFrVXk5a1RqUmFTME5GVUZwU' +
+ '2NrRjZSSE5wUzFWRWVsSnliVUpDU2pWM2RXUm5lbTVrU1UxWlkweGxMMUpIUjBac05YbFBSRWxMWjJwRmRpOVRTa' +
+ '2d2VlV3clpFVmhiSFJPTVRGQ2JYTkxLMlZSYlUxR0t5dEJZM2hIVG1oeU5UbHhUUzg1YVd3M01Va3laRTQ0Umtkb' +
+ 'VkyUmtkM1ZoWldvMFlsaG9jREJNWTFGQ1ltcDRUV05KTjBwUU1HRk5NMVEwU1N0RWMyRjRiVXRHYzJKcWVtRlVUa' +
+ '001ZFhwd1JteG5UMGxuTjNKU01qVjRiM2x1VlhoMk9IWk9iV3R4TjNwa1VFZElXR3Q0VjFrM2IwYzVhaXRLYTFKN' +
+ 'VFrRkNhemRZY2twbWIzVmpRbHBGY1VaS1NsTlFhemRZUVRCTVMxY3dXVE42Tlc5Nk1rUXdZekYwU2t0M1NFRm5UV' +
+ 'UpCUVVkcVoyZEZlazFKU1VKTWVrRlBRbWRPVmtoUk9FSkJaamhGUWtGTlEwRlpXWGRJVVZsRVZsSXdiRUpDV1hkR' +
+ '1FWbEpTM2RaUWtKUlZVaEJkMFZIUTBOelIwRlJWVVpDZDAxRFRVSkpSMEV4VldSRmQwVkNMM2RSU1UxQldVSkJaa' +
+ 'mhEUVZGQmQwaFJXVVJXVWpCUFFrSlpSVVpLYWxJclJ6UlJOamdyWWpkSFEyWkhTa0ZpYjA5ME9VTm1NSEpOUWpoS' +
+ 'FFURlZaRWwzVVZsTlFtRkJSa3AyYVVJeFpHNUlRamRCWVdkaVpWZGlVMkZNWkM5alIxbFpkVTFFVlVkRFEzTkhRV' +
+ 'kZWUmtKM1JVSkNRMnQzU25wQmJFSm5aM0pDWjBWR1FsRmpkMEZaV1ZwaFNGSXdZMFJ2ZGt3eU9XcGpNMEYxWTBkM' +
+ 'GNFeHRaSFppTW1OMldqTk9lVTFxUVhsQ1owNVdTRkk0UlV0NlFYQk5RMlZuU21GQmFtaHBSbTlrU0ZKM1QyazRkb' +
+ 'Gt6U25OTWJrSnlZVk0xYm1JeU9XNU1NbVI2WTJwSmRsb3pUbmxOYVRWcVkyMTNkMUIzV1VSV1VqQm5Ra1JuZDA1c' +
+ 'VFUQkNaMXB1WjFGM1FrRm5TWGRMYWtGdlFtZG5ja0puUlVaQ1VXTkRRVkpaWTJGSVVqQmpTRTAyVEhrNWQyRXlhM' +
+ '1ZhTWpsMlduazVlVnBZUW5aak1td3dZak5LTlV4NlFVNUNaMnR4YUd0cFJ6bDNNRUpCVVhOR1FVRlBRMEZSUlVGS' +
+ 'GIwRXJUbTV1TnpoNU5uQlNhbVE1V0d4UlYwNWhOMGhVWjJsYUwzSXpVazVIYTIxVmJWbElVRkZ4TmxOamRHazVVR' +
+ 'VZoYW5aM1VsUXlhVmRVU0ZGeU1ESm1aWE54VDNGQ1dUSkZWRlYzWjFwUksyeHNkRzlPUm5ab2MwODVkSFpDUTA5S' +
+ 'llYcHdjM2RYUXpsaFNqbDRhblUwZEZkRVVVZzRUbFpWTmxsYVdpOVlkR1ZFVTBkVk9WbDZTbkZRYWxrNGNUTk5SS' +
+ 'Gh5ZW0xeFpYQkNRMlkxYnpodGR5OTNTalJoTWtjMmVIcFZjalpHWWpaVU9FMWpSRTh5TWxCTVVrdzJkVE5OTkZSN' +
+ 'mN6TkJNazB4YWpaaWVXdEtXV2s0ZDFkSlVtUkJka3RNVjFwMUwyRjRRbFppZWxsdGNXMTNhMjAxZWt4VFJGYzFia' +
+ '2xCU21KRlRFTlJRMXAzVFVnMU5uUXlSSFp4YjJaNGN6WkNRbU5EUmtsYVZWTndlSFUyZURaMFpEQldOMU4yU2tOR' +
+ 'GIzTnBjbE50U1dGMGFpODVaRk5UVmtSUmFXSmxkRGh4THpkVlN6UjJORnBWVGpnd1lYUnVXbm94ZVdjOVBTSmRmU' +
+ 'S5leUp1YjI1alpTSTZJbkZyYjB4dE9XSnJUeXNyYzJoMFZITnZheXRqUW1GRmJFcEJXa1pXTUcxRlFqQTVVbWcxV' +
+ 'TNKWVpGVTlJaXdpZEdsdFpYTjBZVzF3VFhNaU9qRTFOalUwTWpReU5qSTNOek1zSW1Gd2ExQmhZMnRoWjJWT1lXM' +
+ 'WxJam9pWTI5dExtZHZiMmRzWlM1aGJtUnliMmxrTG1kdGN5SXNJbUZ3YTBScFoyVnpkRk5vWVRJMU5pSTZJaXR0Y' +
+ '0ZKQ016RjRRemRTYUdsaWN5OWxWbUVyTDNWQ05XNTFaMVVyV0UxRFFXa3plSFZKZGpaMGIwMDlJaXdpWTNSelVIS' +
+ 'nZabWxzWlUxaGRHTm9JanAwY25WbExDSmhjR3REWlhKMGFXWnBZMkYwWlVScFoyVnpkRk5vWVRJMU5pSTZXeUk0V' +
+ 'URGelZ6QkZVRXBqYzJ4M04xVjZVbk5wV0V3Mk5IY3JUelV3UldRclVrSkpRM1JoZVRGbk1qUk5QU0pkTENKaVlYT' +
+ 'nBZMGx1ZEdWbmNtbDBlU0k2ZEhKMVpYMC5yUW5Ib2FZVGgxTEU2VVZwaU1lZWFidDdUeWJ3dzdXZk42RzJ5R01tZ' +
+ 'kVjbTFabjRWalZkenpoY1BqTS1WR052aWl1RGxyZ2VuWEViZ082V05YNlYzc0hHVjN1VGxGMlBuOUZsY3YxWmItS' +
+ '2NGVHZUd29iYnY3LUp5VUZzTlhTSnhHZFRTOWxwNU5EdDFnWGJ6OVpORWhzVXI3ajBqbWNyaU9rR29PRzM4MXRSa' +
+ '0Vqdk5aa0hpMkF1UDF2MWM4RXg3cEpZc09ISzJxaDlmSHFuSlAzcGowUFc3WThpcDBSTVZaNF9xZzFqc0dMMnZ0O' +
+ 'G12cEJFMjg5dE1fcnROdm94TWU2aEx0Q1ZkdE9ZRjIzMWMtWVFJd2FEbnZWdDcwYW5XLUZYdUx3R1J5dWhfRlpNM' +
+ '3FCSlhhcXdCNjNITk5uMmh5MFRDdHQ4RDdIMmI4MGltWkZRX1FoYXV0aERhdGFYxT3cRxDpwIiyKduonVYyILs59' +
+ 'yKa_0ZbCmVrGvuaivigRQAAAAC5P9lh8uZGL7EiggAiR954AEEBDL2BKZVhBca7N3j3asDaoSrA3tJgT_E4KN25T' +
+ 'hBVqBHCdffSZt9bvku7hPBcd76BzU7Y-ckXslUkD13Imbzde6UBAgMmIAEhWCCT4hId3ByJ_agRyznv1xIazx2nl' +
+ 'VEGyvN7intoZr7C2CJYIKo3XB-cca9aUOLC-xhp3GfhyfTS0hjws5zL_bT_N1AL',
+ clientDataJSON:
+ 'eyJ0eXBlIjoid2ViYXV0aG4uY3JlYXRlIiwiY2hhbGxlbmdlIjoiWDNaV1VHOUZOREpF' +
+ 'YUMxM2F6Tmlka2h0WVd0MGFWWjJSVmxETFV4M1FsZyIsIm9yaWdpbiI6Imh0dHBzOlwvXC9kZXYuZG9udG5lZWRh' +
+ 'LnB3IiwiYW5kcm9pZFBhY2thZ2VOYW1lIjoiY29tLmFuZHJvaWQuY2hyb21lIn0',
+ },
+ getClientExtensionResults: () => ({}),
+ type: 'webauthn.create',
};
const attestationAndroidSafetyNetChallenge = '_vVPoE42Dh-wk3bvHmaktiVvEYC-LwBX';
diff --git a/packages/server/src/attestation/verifyAttestationResponse.ts b/packages/server/src/attestation/verifyAttestationResponse.ts
index e336d2a..ed4ac5c 100644
--- a/packages/server/src/attestation/verifyAttestationResponse.ts
+++ b/packages/server/src/attestation/verifyAttestationResponse.ts
@@ -1,11 +1,10 @@
-import decodeAttestationObject from '../helpers/decodeAttestationObject';
-import decodeClientDataJSON from '../helpers/decodeClientDataJSON';
import {
- ATTESTATION_FORMATS,
- AuthenticatorAttestationResponseJSON,
- VerifiedAttestation,
+ AttestationCredentialJSON,
} from '@simplewebauthn/typescript-types';
+import decodeAttestationObject, { ATTESTATION_FORMATS } from '../helpers/decodeAttestationObject';
+import decodeClientDataJSON from '../helpers/decodeClientDataJSON';
+
import verifyFIDOU2F from './verifications/verifyFIDOU2F';
import verifyPacked from './verifications/verifyPacked';
import verifyNone from './verifications/verifyNone';
@@ -14,19 +13,19 @@ 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
*/
export default function verifyAttestationResponse(
- response: AuthenticatorAttestationResponseJSON,
+ credential: AttestationCredentialJSON,
expectedChallenge: string,
expectedOrigin: string,
): VerifiedAttestation {
- const { base64AttestationObject, base64ClientDataJSON } = response;
- const attestationObject = decodeAttestationObject(base64AttestationObject);
- const clientDataJSON = decodeClientDataJSON(base64ClientDataJSON);
+ const { response } = credential;
+ const attestationObject = decodeAttestationObject(response.attestationObject);
+ const clientDataJSON = decodeClientDataJSON(response.clientDataJSON);
const { type, origin, challenge } = clientDataJSON;
@@ -52,15 +51,15 @@ export default function verifyAttestationResponse(
* Verification can only be performed when attestation = 'direct'
*/
if (fmt === ATTESTATION_FORMATS.FIDO_U2F) {
- return verifyFIDOU2F(attestationObject, base64ClientDataJSON);
+ return verifyFIDOU2F(attestationObject, response.clientDataJSON);
}
if (fmt === ATTESTATION_FORMATS.PACKED) {
- return verifyPacked(attestationObject, base64ClientDataJSON);
+ return verifyPacked(attestationObject, response.clientDataJSON);
}
if (fmt === ATTESTATION_FORMATS.ANDROID_SAFETYNET) {
- return verifyAndroidSafetynet(attestationObject, base64ClientDataJSON);
+ return verifyAndroidSafetynet(attestationObject, response.clientDataJSON);
}
if (fmt === ATTESTATION_FORMATS.NONE) {
@@ -69,3 +68,28 @@ export default function verifyAttestationResponse(
throw new Error(`Unsupported Attestation Format: ${fmt}`);
}
+
+/**
+ * Result of attestation verification
+ *
+ * @param verified If the assertion response could be verified
+ * @param userVerified Whether the user was uniquely identified during attestation
+ * @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 Base64URL-encoded ArrayBuffer containing the
+ * authenticator's public key. **Should be kept in a DB for later reference!**
+ * @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!**
+ */
+export type VerifiedAttestation = {
+ verified: boolean;
+ userVerified: boolean;
+ authenticatorInfo?: {
+ fmt: ATTESTATION_FORMATS;
+ counter: number;
+ base64PublicKey: string;
+ base64CredentialID: string;
+ };
+};
diff --git a/packages/server/src/helpers/convertCOSEtoPKCS.test.ts b/packages/server/src/helpers/convertCOSEtoPKCS.test.ts
index d17d4bd..e914cc7 100644
--- a/packages/server/src/helpers/convertCOSEtoPKCS.test.ts
+++ b/packages/server/src/helpers/convertCOSEtoPKCS.test.ts
@@ -1,7 +1,6 @@
import cbor from 'cbor';
-import { COSEKEYS } from '@simplewebauthn/typescript-types';
-import convertCOSEtoPKCS from './convertCOSEtoPKCS';
+import convertCOSEtoPKCS, { COSEKEYS } from './convertCOSEtoPKCS';
test('should throw an error curve if, somehow, curve coordinate x is missing', () => {
const mockCOSEKey = new Map<number, number | Buffer>();
diff --git a/packages/server/src/helpers/convertCOSEtoPKCS.ts b/packages/server/src/helpers/convertCOSEtoPKCS.ts
index 5b03b1a..3039415 100644
--- a/packages/server/src/helpers/convertCOSEtoPKCS.ts
+++ b/packages/server/src/helpers/convertCOSEtoPKCS.ts
@@ -1,5 +1,4 @@
import cbor from 'cbor';
-import { COSEKEYS, COSEPublicKey } from '@simplewebauthn/typescript-types';
/**
* Takes COSE-encoded public key and converts it to PKCS key
@@ -40,3 +39,15 @@ export default function convertCOSEtoPKCS(cosePublicKey: Buffer): Buffer {
return Buffer.concat([tag, x as Buffer, y as Buffer]);
}
+
+export type COSEPublicKey = Map<COSEAlgorithmIdentifier, number | Buffer>;
+
+export enum COSEKEYS {
+ kty = 1,
+ alg = 3,
+ crv = -1,
+ x = -2,
+ y = -3,
+ n = -1,
+ e = -2,
+}
diff --git a/packages/server/src/helpers/decodeAttestationObject.ts b/packages/server/src/helpers/decodeAttestationObject.ts
index 3e66e67..2eb9997 100644
--- a/packages/server/src/helpers/decodeAttestationObject.ts
+++ b/packages/server/src/helpers/decodeAttestationObject.ts
@@ -1,11 +1,10 @@
import base64url from 'base64url';
import cbor from 'cbor';
-import { AttestationObject } from '@simplewebauthn/typescript-types';
/**
- * 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,
@@ -14,3 +13,20 @@ export default function decodeAttestationObject(
const toCBOR: AttestationObject = cbor.decodeAllSync(toBuffer)[0];
return toCBOR;
}
+
+export enum ATTESTATION_FORMATS {
+ FIDO_U2F = 'fido-u2f',
+ PACKED = 'packed',
+ ANDROID_SAFETYNET = 'android-safetynet',
+ NONE = 'none',
+}
+
+export type AttestationObject = {
+ fmt: ATTESTATION_FORMATS;
+ attStmt: {
+ sig?: Buffer;
+ x5c?: Buffer[];
+ response?: Buffer;
+ };
+ authData: Buffer;
+};
diff --git a/packages/server/src/helpers/decodeClientDataJSON.ts b/packages/server/src/helpers/decodeClientDataJSON.ts
index fb909cf..c0ebb2b 100644
--- a/packages/server/src/helpers/decodeClientDataJSON.ts
+++ b/packages/server/src/helpers/decodeClientDataJSON.ts
@@ -1,5 +1,3 @@
-import { ClientDataJSON } from '@simplewebauthn/typescript-types';
-
import asciiToBinary from './asciiToBinary';
/**
@@ -15,3 +13,9 @@ export default function decodeClientDataJSON(data: string): ClientDataJSON {
return clientData;
}
+
+type ClientDataJSON = {
+ type: string;
+ challenge: string;
+ origin: string;
+};
diff --git a/packages/server/src/helpers/getCertificateInfo.ts b/packages/server/src/helpers/getCertificateInfo.ts
index b6d8e26..3741fac 100644
--- a/packages/server/src/helpers/getCertificateInfo.ts
+++ b/packages/server/src/helpers/getCertificateInfo.ts
@@ -1,5 +1,10 @@
import jsrsasign from 'jsrsasign';
-import { CertificateInfo } from '@simplewebauthn/typescript-types';
+
+export type CertificateInfo = {
+ subject: { [key: string]: string };
+ version: number;
+ basicConstraintsCA: boolean;
+};
type ExtInfo = {
critical: boolean;
diff --git a/packages/server/src/helpers/parseAuthenticatorData.ts b/packages/server/src/helpers/parseAuthenticatorData.ts
index 62c1cb1..3177dd5 100644
--- a/packages/server/src/helpers/parseAuthenticatorData.ts
+++ b/packages/server/src/helpers/parseAuthenticatorData.ts
@@ -1,5 +1,3 @@
-import { ParsedAuthenticatorData } from '@simplewebauthn/typescript-types';
-
/**
* Make sense of the authData buffer contained in an Attestation
*/
@@ -57,3 +55,20 @@ export default function parseAuthenticatorData(authData: Buffer): ParsedAuthenti
COSEPublicKey,
};
}
+
+type ParsedAuthenticatorData = {
+ rpIdHash: Buffer;
+ flagsBuf: Buffer;
+ flags: {
+ up: boolean;
+ uv: boolean;
+ at: boolean;
+ ed: boolean;
+ flagsInt: number;
+ };
+ counter: number;
+ counterBuf: Buffer;
+ aaguid?: Buffer;
+ credentialID?: Buffer;
+ COSEPublicKey?: Buffer;
+};
diff --git a/packages/typescript-types/src/index.ts b/packages/typescript-types/src/index.ts
index dcd88a9..da063a5 100644
--- a/packages/typescript-types/src/index.ts
+++ b/packages/typescript-types/src/index.ts
@@ -10,9 +10,8 @@
export interface PublicKeyCredentialCreationOptionsJSON extends Omit<
PublicKeyCredentialCreationOptions, 'challenge' | 'user' | 'excludeCredentials'
> {
- // Will be converted to a Uint8Array in the browser
user: PublicKeyCredentialUserEntityJSON;
- challenge: string;
+ challenge: Base64URLString;
excludeCredentials: PublicKeyCredentialDescriptorJSON[];
}
@@ -23,23 +22,20 @@ PublicKeyCredentialCreationOptions, 'challenge' | 'user' | 'excludeCredentials'
export interface PublicKeyCredentialRequestOptionsJSON extends Omit<
PublicKeyCredentialRequestOptions, 'challenge' |'allowCredentials'
> {
- // Will be converted to a Uint8Array in the browser
- challenge: string;
+ challenge: Base64URLString;
allowCredentials: PublicKeyCredentialDescriptorJSON[];
}
export interface PublicKeyCredentialDescriptorJSON extends Omit<
PublicKeyCredentialDescriptor, 'id'
> {
- // Should be a Base64-encoded credential ID. Will be converted to a Uint8Array in the browser
- id: string;
+ 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: string;
+ id: Base64URLString;
}
/**
@@ -50,6 +46,16 @@ export interface AttestationCredential extends PublicKeyCredential {
}
/**
+ * A slightly-modified AttestationCredential to simplify working with ArrayBuffers that
+ * are base64url-encoded in the browser so that they can be sent as JSON to the server.
+ */
+export interface AttestationCredentialJSON
+ extends Omit<AttestationCredential, 'response' | 'rawId' | 'getClientExtensionResults'> {
+ rawId: Base64URLString;
+ response: AuthenticatorAttestationResponseJSON;
+}
+
+/**
* The value returned from navigator.credentials.get()
*/
export interface AssertionCredential extends PublicKeyCredential {
@@ -57,155 +63,43 @@ export interface AssertionCredential extends PublicKeyCredential {
}
/**
- * A slightly-modified AuthenticatorAttestationResponse to simplify working with ArrayBuffers that
- * are base64-encoded in the browser so that they can be sent as JSON to the server.
+ * A slightly-modified AssertionCredential to simplify working with ArrayBuffers that
+ * are base64url-encoded in the browser so that they can be sent as JSON to the server.
*/
-export interface AuthenticatorAttestationResponseJSON
+export interface AssertionCredentialJSON
+ extends Omit<AssertionCredential, 'response' | 'rawId' | 'getClientExtensionResults'> {
+ rawId: Base64URLString;
+ response: AuthenticatorAssertionResponseJSON;
+}
+
+interface AuthenticatorAttestationResponseJSON
extends Omit<AuthenticatorAttestationResponse, 'clientDataJSON' | 'attestationObject'> {
- base64ClientDataJSON: string;
- base64AttestationObject: string;
+ clientDataJSON: Base64URLString;
+ attestationObject: Base64URLString;
}
-/**
- * A slightly-modified AuthenticatorAttestationResponse to simplify working with ArrayBuffers that
- * are base64-encoded in the browser so that they can be sent as JSON to the server.
- */
-export interface AuthenticatorAssertionResponseJSON
+interface AuthenticatorAssertionResponseJSON
extends Omit<
AuthenticatorAssertionResponse,
- 'clientDataJSON' | 'authenticatorData' | 'signature' | 'userHandle'
+ 'authenticatorData' | 'clientDataJSON' | 'signature' | 'userHandle'
> {
- base64CredentialID: string;
- base64AuthenticatorData: string;
- base64ClientDataJSON: string;
- base64Signature: string;
- base64UserHandle?: string;
-}
-
-export enum ATTESTATION_FORMATS {
- FIDO_U2F = 'fido-u2f',
- PACKED = 'packed',
- ANDROID_SAFETYNET = 'android-safetynet',
- NONE = 'none',
-}
-
-export type AttestationObject = {
- fmt: ATTESTATION_FORMATS;
- attStmt: {
- sig?: Buffer;
- x5c?: Buffer[];
- response?: Buffer;
- };
- authData: Buffer;
-};
-
-export type ParsedAuthenticatorData = {
- rpIdHash: Buffer;
- flagsBuf: Buffer;
- flags: {
- up: boolean;
- uv: boolean;
- at: boolean;
- ed: boolean;
- flagsInt: number;
- };
- counter: number;
- counterBuf: Buffer;
- aaguid?: Buffer;
- credentialID?: Buffer;
- COSEPublicKey?: Buffer;
-};
-
-export type ClientDataJSON = {
- type: string;
- challenge: string;
- origin: string;
-};
-
-/**
- * Result of attestation verification
- *
- * @param verified If the assertion response could be verified
- * @param userVerified Whether the user was uniquely identified during attestation
- * @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
- * authenticator's public key. **Should be kept in a DB for later reference!**
- * @param authenticatorInfo.base64CredentialID Base64-encoded ArrayBuffer containing the
- * authenticator's credential ID for the public key above. **Should be kept in a DB for later
- * reference!**
- */
-export type VerifiedAttestation = {
- verified: boolean;
- userVerified: boolean;
- authenticatorInfo?: {
- fmt: ATTESTATION_FORMATS;
- counter: number;
- base64PublicKey: string;
- base64CredentialID: string;
- };
-};
-
-/**
- * Result of assertion verification
- *
- * @param verified If the assertion response could be verified
- * @param authenticatorInfo.base64CredentialID The ID of the authenticator used during assertion.
- * Should be used to identify which DB authenticator entry needs its `counter` updated to the value
- * below
- * @param authenticatorInfo.counter The number of times the authenticator identified above reported
- * it has been used. **Should be kept in a DB for later reference to help prevent replay attacks!**
- */
-export type VerifiedAssertion = {
- verified: boolean;
- authenticatorInfo: {
- counter: number;
- base64CredentialID: string;
- };
-};
-
-export type CertificateInfo = {
- subject: { [key: string]: string };
- version: number;
- basicConstraintsCA: boolean;
-};
-
-export enum COSEKEYS {
- kty = 1,
- alg = 3,
- crv = -1,
- x = -2,
- y = -3,
- n = -1,
- e = -2,
+ authenticatorData: Base64URLString;
+ clientDataJSON: Base64URLString;
+ signature: Base64URLString;
+ userHandle?: Base64URLString;
}
-export type COSEPublicKey = Map<COSEAlgorithmIdentifier, number | Buffer>;
-
-export type SafetyNetJWTHeader = {
- alg: 'string';
- x5c: string[];
-};
-
-export type SafetyNetJWTPayload = {
- nonce: string;
- timestampMs: number;
- apkPackageName: string;
- apkDigestSha256: string;
- ctsProfileMatch: boolean;
- apkCertificateDigestSha256: string[];
- basicIntegrity: boolean;
-};
-
-export type SafetyNetJWTSignature = string;
-
/**
* A WebAuthn-compatible device and the information needed to verify assertions by it
*/
export type AuthenticatorDevice = {
- base64PublicKey: string;
- base64CredentialID: string;
+ publicKey: Base64URLString;
+ credentialID: Base64URLString;
// Number of times this device is expected to have been used
counter: number;
};
+
+/**
+ * An attempt to communicate that this isn't just any string, but a base64url-encoded string
+ */
+export type Base64URLString = string;