summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorMatthew Miller <matthew@millerti.me>2021-02-05 09:12:23 -0800
committerGitHub <noreply@github.com>2021-02-05 09:12:23 -0800
commit432ff68bab6b11f6ccbf549d8be397fd71da29f9 (patch)
tree9dc17be10efa4545a0b5a5a86236cf34405d133f
parent70c1360339d0e63dafd4a04fa1824d9453f0a802 (diff)
parent897291381dd45a94daf322cf533626fe7e235349 (diff)
Merge pull request #97 from MasterKale/feature/better-passwordless-usernameless
feature/better-passwordless-usernameless
-rw-r--r--package.json2
-rw-r--r--packages/server/src/assertion/generateAssertionOptions.test.ts28
-rw-r--r--packages/server/src/assertion/generateAssertionOptions.ts9
-rw-r--r--packages/server/src/assertion/verifyAssertionResponse.test.ts25
-rw-r--r--packages/server/src/assertion/verifyAssertionResponse.ts21
-rw-r--r--packages/server/src/attestation/generateAttestationOptions.test.ts8
-rw-r--r--packages/server/src/attestation/generateAttestationOptions.ts9
-rw-r--r--packages/server/src/attestation/verifications/verifyAndroidSafetyNet.test.ts2
-rw-r--r--packages/server/src/attestation/verifyAttestationResponse.test.ts138
-rw-r--r--packages/server/src/attestation/verifyAttestationResponse.ts66
-rw-r--r--packages/server/src/helpers/convertPublicKeyToPEM.ts7
-rw-r--r--packages/server/src/helpers/decodeAttestationObject.test.ts44
-rw-r--r--packages/server/src/helpers/decodeAttestationObject.ts11
-rw-r--r--packages/server/src/metadata/metadataService.ts38
-rw-r--r--packages/typescript-types/src/index.ts6
15 files changed, 251 insertions, 163 deletions
diff --git a/package.json b/package.json
index d013745..f1b578f 100644
--- a/package.json
+++ b/package.json
@@ -33,7 +33,7 @@
"ts-morph": "^9.0.0",
"ts-node": "^8.10.2",
"ttypescript": "^1.5.12",
- "typedoc": "^0.20.0-beta.8",
+ "typedoc": "^0.20.20",
"typescript": "^4.0.5",
"typescript-transform-paths": "^1.1.15"
},
diff --git a/packages/server/src/assertion/generateAssertionOptions.test.ts b/packages/server/src/assertion/generateAssertionOptions.test.ts
index 24253bf..0208d9d 100644
--- a/packages/server/src/assertion/generateAssertionOptions.test.ts
+++ b/packages/server/src/assertion/generateAssertionOptions.test.ts
@@ -8,12 +8,12 @@ test('should generate credential request options suitable for sending via JSON',
const options = generateAssertionOptions({
allowCredentials: [
{
- id: Buffer.from('1234', 'ascii').toString('base64'),
+ id: Buffer.from('1234', 'ascii'),
type: 'public-key',
transports: ['usb', 'nfc'],
},
{
- id: Buffer.from('5678', 'ascii').toString('base64'),
+ id: Buffer.from('5678', 'ascii'),
type: 'public-key',
transports: ['internal'],
},
@@ -27,12 +27,12 @@ test('should generate credential request options suitable for sending via JSON',
challenge: 'dG90YWxseXJhbmRvbXZhbHVl',
allowCredentials: [
{
- id: 'MTIzNA==',
+ id: 'MTIzNA',
type: 'public-key',
transports: ['usb', 'nfc'],
},
{
- id: 'NTY3OA==',
+ id: 'NTY3OA',
type: 'public-key',
transports: ['internal'],
},
@@ -45,8 +45,8 @@ test('defaults to 60 seconds if no timeout is specified', () => {
const options = generateAssertionOptions({
challenge: 'totallyrandomvalue',
allowCredentials: [
- { id: Buffer.from('1234', 'ascii').toString('base64'), type: 'public-key' },
- { id: Buffer.from('5678', 'ascii').toString('base64'), type: 'public-key' },
+ { id: Buffer.from('1234', 'ascii'), type: 'public-key' },
+ { id: Buffer.from('5678', 'ascii'), type: 'public-key' },
],
});
@@ -57,8 +57,8 @@ test('should not set userVerification if not specified', () => {
const options = generateAssertionOptions({
challenge: 'totallyrandomvalue',
allowCredentials: [
- { id: Buffer.from('1234', 'ascii').toString('base64'), type: 'public-key' },
- { id: Buffer.from('5678', 'ascii').toString('base64'), type: 'public-key' },
+ { id: Buffer.from('1234', 'ascii'), type: 'public-key' },
+ { id: Buffer.from('5678', 'ascii'), type: 'public-key' },
],
});
@@ -88,8 +88,8 @@ test('should set userVerification if specified', () => {
const options = generateAssertionOptions({
challenge: 'totallyrandomvalue',
allowCredentials: [
- { id: Buffer.from('1234', 'ascii').toString('base64'), type: 'public-key' },
- { id: Buffer.from('5678', 'ascii').toString('base64'), type: 'public-key' },
+ { id: Buffer.from('1234', 'ascii'), type: 'public-key' },
+ { id: Buffer.from('5678', 'ascii'), type: 'public-key' },
],
userVerification: 'required',
});
@@ -101,8 +101,8 @@ test('should set extensions if specified', () => {
const options = generateAssertionOptions({
challenge: 'totallyrandomvalue',
allowCredentials: [
- { id: Buffer.from('1234', 'ascii').toString('base64'), type: 'public-key' },
- { id: Buffer.from('5678', 'ascii').toString('base64'), type: 'public-key' },
+ { id: Buffer.from('1234', 'ascii'), type: 'public-key' },
+ { id: Buffer.from('5678', 'ascii'), type: 'public-key' },
],
extensions: { appid: 'simplewebauthn' },
});
@@ -115,8 +115,8 @@ test('should set extensions if specified', () => {
test('should generate a challenge if one is not provided', () => {
const opts = {
allowCredentials: [
- { id: Buffer.from('1234', 'ascii').toString('base64'), type: 'public-key' },
- { id: Buffer.from('5678', 'ascii').toString('base64'), type: 'public-key' },
+ { id: Buffer.from('1234', 'ascii'), type: 'public-key' },
+ { id: Buffer.from('5678', 'ascii'), type: 'public-key' },
],
};
diff --git a/packages/server/src/assertion/generateAssertionOptions.ts b/packages/server/src/assertion/generateAssertionOptions.ts
index 370dbc8..19bce91 100644
--- a/packages/server/src/assertion/generateAssertionOptions.ts
+++ b/packages/server/src/assertion/generateAssertionOptions.ts
@@ -1,7 +1,7 @@
import type {
AuthenticationExtensionsClientInputs,
PublicKeyCredentialRequestOptionsJSON,
- PublicKeyCredentialDescriptorJSON,
+ PublicKeyCredentialDescriptor,
UserVerificationRequirement,
} from '@simplewebauthn/typescript-types';
import base64url from 'base64url';
@@ -9,7 +9,7 @@ import base64url from 'base64url';
import generateChallenge from '../helpers/generateChallenge';
type Options = {
- allowCredentials?: PublicKeyCredentialDescriptorJSON[];
+ allowCredentials?: PublicKeyCredentialDescriptor[];
challenge?: string | Buffer;
timeout?: number;
userVerification?: UserVerificationRequirement;
@@ -44,7 +44,10 @@ export default function generateAssertionOptions(
return {
challenge: base64url.encode(challenge),
- allowCredentials,
+ allowCredentials: allowCredentials?.map(cred => ({
+ ...cred,
+ id: base64url.encode(cred.id as Buffer),
+ })),
timeout,
userVerification,
extensions,
diff --git a/packages/server/src/assertion/verifyAssertionResponse.test.ts b/packages/server/src/assertion/verifyAssertionResponse.test.ts
index 4e4b64f..1708f77 100644
--- a/packages/server/src/assertion/verifyAssertionResponse.test.ts
+++ b/packages/server/src/assertion/verifyAssertionResponse.test.ts
@@ -4,6 +4,7 @@ import verifyAssertionResponse from './verifyAssertionResponse';
import * as decodeClientDataJSON from '../helpers/decodeClientDataJSON';
import * as parseAuthenticatorData from '../helpers/parseAuthenticatorData';
import toHash from '../helpers/toHash';
+import { AuthenticatorDevice } from '@simplewebauthn/typescript-types';
let mockDecodeClientData: jest.SpyInstance;
let mockParseAuthData: jest.SpyInstance;
@@ -39,8 +40,8 @@ test('should return authenticator info after verification', () => {
authenticator: authenticator,
});
- expect(verification.authenticatorInfo.counter).toEqual(144);
- expect(verification.authenticatorInfo.base64CredentialID).toEqual(authenticator.credentialID);
+ expect(verification.assertionInfo.newCounter).toEqual(144);
+ expect(verification.assertionInfo.credentialID).toEqual(authenticator.credentialID);
});
test('should throw when response challenge is not expected value', () => {
@@ -198,8 +199,8 @@ test.skip('should verify TPM assertion', () => {
expectedOrigin: assertionOrigin,
expectedRPID: 'dev.dontneeda.pw',
authenticator: {
- publicKey: 'BAEAAQ',
- credentialID: 'YJ8FMM-AmcUt73XPX341WXWd7ypBMylGjjhu0g3VzME',
+ credentialPublicKey: base64url.toBuffer('BAEAAQ'),
+ credentialID: base64url.toBuffer('YJ8FMM-AmcUt73XPX341WXWd7ypBMylGjjhu0g3VzME'),
counter: 0,
},
});
@@ -278,11 +279,13 @@ const assertionResponse = {
const assertionChallenge = base64url.encode('totallyUniqueValueEveryTime');
const assertionOrigin = 'https://dev.dontneeda.pw';
-const authenticator = {
- publicKey:
+const authenticator: AuthenticatorDevice = {
+ credentialPublicKey: base64url.toBuffer(
'pQECAyYgASFYIIheFp-u6GvFT2LNGovf3ZrT0iFVBsA_76rRysxRG9A1Ilgg8WGeA6hPmnab0HAViUYVRkwTNcN77QBf_RR0dv3lIvQ',
- credentialID:
+ ),
+ credentialID: base64url.toBuffer(
'KEbWNCc7NgaYnUyrNeFGX9_3Y-8oJ3KwzjnaiD1d1LVTxR7v3CaKfCz2Vy_g_MHSh7yJ8yL0Pxg6jo_o0hYiew',
+ ),
counter: 143,
};
@@ -303,10 +306,12 @@ const assertionFirstTimeUsedResponse = {
};
const assertionFirstTimeUsedChallenge = base64url.encode('totallyUniqueValueEveryAssertion');
const assertionFirstTimeUsedOrigin = 'https://dev.dontneeda.pw';
-const authenticatorFirstTimeUsed = {
- publicKey:
+const authenticatorFirstTimeUsed: AuthenticatorDevice = {
+ credentialPublicKey: base64url.toBuffer(
'pQECAyYgASFYIGmaxR4mBbukc2QhtW2ldhAAd555r-ljlGQN8MbcTnPPIlgg9CyUlE-0AB2fbzZbNgBvJuRa7r6o2jPphOmtyNPR_kY',
- credentialID:
+ ),
+ credentialID: base64url.toBuffer(
'wSisR0_4hlzw3Y1tj4uNwwifIhRa-ZxWJwWbnfror0pVK9qPdBPO5pW3gasPqn6wXHb0LNhXB_IrA1nFoSQJ9A',
+ ),
counter: 0,
};
diff --git a/packages/server/src/assertion/verifyAssertionResponse.ts b/packages/server/src/assertion/verifyAssertionResponse.ts
index 0c76fae..2c85e6e 100644
--- a/packages/server/src/assertion/verifyAssertionResponse.ts
+++ b/packages/server/src/assertion/verifyAssertionResponse.ts
@@ -163,7 +163,7 @@ export default function verifyAssertionResponse(options: Options): VerifiedAsser
const clientDataHash = toHash(base64url.toBuffer(response.clientDataJSON));
const signatureBase = Buffer.concat([authDataBuffer, clientDataHash]);
- const publicKey = convertPublicKeyToPEM(authenticator.publicKey);
+ const publicKey = convertPublicKeyToPEM(authenticator.credentialPublicKey);
const signature = base64url.toBuffer(response.signature);
if ((counter > 0 || authenticator.counter > 0) && counter <= authenticator.counter) {
@@ -178,9 +178,9 @@ export default function verifyAssertionResponse(options: Options): VerifiedAsser
const toReturn = {
verified: verifySignature(signature, signatureBase, publicKey),
- authenticatorInfo: {
- counter,
- base64CredentialID: credential.id,
+ assertionInfo: {
+ newCounter: counter,
+ credentialID: authenticator.credentialID,
},
};
@@ -191,16 +191,17 @@ export default function verifyAssertionResponse(options: Options): VerifiedAsser
* Result of assertion verification
*
* @param verified If the assertion response could be verified
- * @param authenticatorInfo.base64CredentialID The ID of the authenticator used during assertion.
+ * @param assertionInfo.credentialID 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!**
+ * @param assertionInfo.newCounter 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;
+ assertionInfo: {
+ credentialID: Buffer;
+ newCounter: number;
};
};
diff --git a/packages/server/src/attestation/generateAttestationOptions.test.ts b/packages/server/src/attestation/generateAttestationOptions.test.ts
index 902aae6..eb7dcd7 100644
--- a/packages/server/src/attestation/generateAttestationOptions.test.ts
+++ b/packages/server/src/attestation/generateAttestationOptions.test.ts
@@ -62,13 +62,17 @@ test('should map excluded credential IDs if specified', () => {
userID: '1234',
userName: 'usernameHere',
excludeCredentials: [
- { id: 'someIDhere', type: 'public-key', transports: ['usb', 'ble', 'nfc', 'internal'] },
+ {
+ id: Buffer.from('someIDhere', 'ascii'),
+ type: 'public-key',
+ transports: ['usb', 'ble', 'nfc', 'internal'],
+ },
],
});
expect(options.excludeCredentials).toEqual([
{
- id: 'someIDhere',
+ id: 'c29tZUlEaGVyZQ',
type: 'public-key',
transports: ['usb', 'ble', 'nfc', 'internal'],
},
diff --git a/packages/server/src/attestation/generateAttestationOptions.ts b/packages/server/src/attestation/generateAttestationOptions.ts
index 2c0f85b..a523cd1 100644
--- a/packages/server/src/attestation/generateAttestationOptions.ts
+++ b/packages/server/src/attestation/generateAttestationOptions.ts
@@ -4,7 +4,7 @@ import type {
AuthenticatorSelectionCriteria,
COSEAlgorithmIdentifier,
PublicKeyCredentialCreationOptionsJSON,
- PublicKeyCredentialDescriptorJSON,
+ PublicKeyCredentialDescriptor,
PublicKeyCredentialParameters,
} from '@simplewebauthn/typescript-types';
import base64url from 'base64url';
@@ -20,7 +20,7 @@ type Options = {
userDisplayName?: string;
timeout?: number;
attestationType?: AttestationConveyancePreference;
- excludeCredentials?: PublicKeyCredentialDescriptorJSON[];
+ excludeCredentials?: PublicKeyCredentialDescriptor[];
authenticatorSelection?: AuthenticatorSelectionCriteria;
extensions?: AuthenticationExtensionsClientInputs;
supportedAlgorithmIDs?: COSEAlgorithmIdentifier[];
@@ -145,7 +145,10 @@ export default function generateAttestationOptions(
pubKeyCredParams,
timeout,
attestation: attestationType,
- excludeCredentials,
+ excludeCredentials: excludeCredentials.map(cred => ({
+ ...cred,
+ id: base64url.encode(cred.id as Buffer),
+ })),
authenticatorSelection,
extensions,
};
diff --git a/packages/server/src/attestation/verifications/verifyAndroidSafetyNet.test.ts b/packages/server/src/attestation/verifications/verifyAndroidSafetyNet.test.ts
index d6536b9..ea16b1d 100644
--- a/packages/server/src/attestation/verifications/verifyAndroidSafetyNet.test.ts
+++ b/packages/server/src/attestation/verifications/verifyAndroidSafetyNet.test.ts
@@ -15,7 +15,7 @@ let aaguid: Buffer;
beforeEach(() => {
const { attestationObject, clientDataJSON } = attestationAndroidSafetyNet.response;
- const decodedAttestationObject = decodeAttestationObject(attestationObject);
+ const decodedAttestationObject = decodeAttestationObject(base64url.toBuffer(attestationObject));
authData = decodedAttestationObject.authData;
attStmt = decodedAttestationObject.attStmt;
diff --git a/packages/server/src/attestation/verifyAttestationResponse.test.ts b/packages/server/src/attestation/verifyAttestationResponse.test.ts
index a6e0814..dc75538 100644
--- a/packages/server/src/attestation/verifyAttestationResponse.test.ts
+++ b/packages/server/src/attestation/verifyAttestationResponse.test.ts
@@ -42,13 +42,23 @@ test('should verify FIDO U2F attestation', async () => {
});
expect(verification.verified).toEqual(true);
- expect(verification.authenticatorInfo?.fmt).toEqual('fido-u2f');
- expect(verification.authenticatorInfo?.counter).toEqual(0);
- expect(verification.authenticatorInfo?.base64PublicKey).toEqual(
- 'pQECAyYgASFYIMiRyw5pUoMhBjCrcQND6lJPaRHA0f-XWcKBb5ZwWk1eIlggFJu6aan4o7epl6qa9n9T-6KsIMvZE2PcTnLj8rN58is',
+ expect(verification.attestationInfo?.fmt).toEqual('fido-u2f');
+ expect(verification.attestationInfo?.counter).toEqual(0);
+ expect(verification.attestationInfo?.credentialPublicKey).toEqual(
+ base64url.toBuffer(
+ 'pQECAyYgASFYIMiRyw5pUoMhBjCrcQND6lJPaRHA0f-XWcKBb5ZwWk1eIlggFJu6aan4o7epl6qa9n9T-6KsIMvZE2PcTnLj8rN58is',
+ ),
);
- expect(verification.authenticatorInfo?.base64CredentialID).toEqual(
- 'VHzbxaYaJu2P8m1Y2iHn2gRNHrgK0iYbn9E978L3Qi7Q-chFeicIHwYCRophz5lth2nCgEVKcgWirxlgidgbUQ',
+ expect(verification.attestationInfo?.credentialID).toEqual(
+ base64url.toBuffer(
+ 'VHzbxaYaJu2P8m1Y2iHn2gRNHrgK0iYbn9E978L3Qi7Q-chFeicIHwYCRophz5lth2nCgEVKcgWirxlgidgbUQ',
+ ),
+ );
+ expect(verification.attestationInfo?.aaguid).toEqual('00000000-0000-0000-0000-000000000000');
+ expect(verification.attestationInfo?.credentialType).toEqual('public-key');
+ expect(verification.attestationInfo?.userVerified).toEqual(false);
+ expect(verification.attestationInfo?.attestationObject).toEqual(
+ base64url.toBuffer(attestationFIDOU2F.response.attestationObject),
);
});
@@ -61,14 +71,18 @@ test('should verify Packed (EC2) attestation', async () => {
});
expect(verification.verified).toEqual(true);
- expect(verification.authenticatorInfo?.fmt).toEqual('packed');
- expect(verification.authenticatorInfo?.counter).toEqual(1589874425);
- expect(verification.authenticatorInfo?.base64PublicKey).toEqual(
- 'pQECAyYgASFYIEoxVVqK-oIGmqoDEyO4KjmMx5R2HeMM4LQQXh8sE01PIlggtzuuoMN5fWnAIuuXdlfshOGu1k3ApBUtDJ8eKiuo_6c',
+ expect(verification.attestationInfo?.fmt).toEqual('packed');
+ expect(verification.attestationInfo?.counter).toEqual(1589874425);
+ expect(verification.attestationInfo?.credentialPublicKey).toEqual(
+ base64url.toBuffer(
+ 'pQECAyYgASFYIEoxVVqK-oIGmqoDEyO4KjmMx5R2HeMM4LQQXh8sE01PIlggtzuuoMN5fWnAIuuXdlfshOGu1k3ApBUtDJ8eKiuo_6c',
+ ),
);
- expect(verification.authenticatorInfo?.base64CredentialID).toEqual(
- 'AYThY1csINY4JrbHyGmqTl1nL_F1zjAF3hSAIngz8kAcjugmAMNVvxZRwqpEH-bNHHAIv291OX5ko9eDf_5mu3U' +
- 'B2BvsScr2K-ppM4owOpGsqwg5tZglqqmxIm1Q',
+ expect(verification.attestationInfo?.credentialID).toEqual(
+ base64url.toBuffer(
+ 'AYThY1csINY4JrbHyGmqTl1nL_F1zjAF3hSAIngz8kAcjugmAMNVvxZRwqpEH-bNHHAIv291OX5ko9eDf_5mu3U' +
+ 'B2BvsScr2K-ppM4owOpGsqwg5tZglqqmxIm1Q',
+ ),
);
});
@@ -81,13 +95,17 @@ test('should verify Packed (X5C) attestation', async () => {
});
expect(verification.verified).toEqual(true);
- expect(verification.authenticatorInfo?.fmt).toEqual('packed');
- expect(verification.authenticatorInfo?.counter).toEqual(28);
- expect(verification.authenticatorInfo?.base64PublicKey).toEqual(
- 'pQECAyYgASFYIGwlsYCNyRb4AD9cyTw6cH5VS-uzflmmO1UldGGe9eIaIlggvadzKD8p6wKLjgYfxRxldjCMGRV0YyM13osWbKIPrF8',
+ expect(verification.attestationInfo?.fmt).toEqual('packed');
+ expect(verification.attestationInfo?.counter).toEqual(28);
+ expect(verification.attestationInfo?.credentialPublicKey).toEqual(
+ base64url.toBuffer(
+ 'pQECAyYgASFYIGwlsYCNyRb4AD9cyTw6cH5VS-uzflmmO1UldGGe9eIaIlggvadzKD8p6wKLjgYfxRxldjCMGRV0YyM13osWbKIPrF8',
+ ),
);
- expect(verification.authenticatorInfo?.base64CredentialID).toEqual(
- '4rrvMciHCkdLQ2HghazIp1sMc8TmV8W8RgoX-x8tqV_1AmlqWACqUK8mBGLandr-htduQKPzgb2yWxOFV56Tlg',
+ expect(verification.attestationInfo?.credentialID).toEqual(
+ base64url.toBuffer(
+ '4rrvMciHCkdLQ2HghazIp1sMc8TmV8W8RgoX-x8tqV_1AmlqWACqUK8mBGLandr-htduQKPzgb2yWxOFV56Tlg',
+ ),
);
});
@@ -100,13 +118,17 @@ test('should verify None attestation', async () => {
});
expect(verification.verified).toEqual(true);
- expect(verification.authenticatorInfo?.fmt).toEqual('none');
- expect(verification.authenticatorInfo?.counter).toEqual(0);
- expect(verification.authenticatorInfo?.base64PublicKey).toEqual(
- 'pQECAyYgASFYID5PQTZQQg6haZFQWFzqfAOyQ_ENsMH8xxQ4GRiNPsqrIlggU8IVUOV8qpgk_Jh-OTaLuZL52KdX1fTht07X4DiQPow',
+ expect(verification.attestationInfo?.fmt).toEqual('none');
+ expect(verification.attestationInfo?.counter).toEqual(0);
+ expect(verification.attestationInfo?.credentialPublicKey).toEqual(
+ base64url.toBuffer(
+ 'pQECAyYgASFYID5PQTZQQg6haZFQWFzqfAOyQ_ENsMH8xxQ4GRiNPsqrIlggU8IVUOV8qpgk_Jh-OTaLuZL52KdX1fTht07X4DiQPow',
+ ),
);
- expect(verification.authenticatorInfo?.base64CredentialID).toEqual(
- 'AdKXJEch1aV5Wo7bj7qLHskVY4OoNaj9qu8TPdJ7kSAgUeRxWNngXlcNIGt4gexZGKVGcqZpqqWordXb_he1izY',
+ expect(verification.attestationInfo?.credentialID).toEqual(
+ base64url.toBuffer(
+ 'AdKXJEch1aV5Wo7bj7qLHskVY4OoNaj9qu8TPdJ7kSAgUeRxWNngXlcNIGt4gexZGKVGcqZpqqWordXb_he1izY',
+ ),
);
});
@@ -130,13 +152,15 @@ test('should verify None attestation w/RSA public key', async () => {
});
expect(verification.verified).toEqual(true);
- expect(verification.authenticatorInfo?.fmt).toEqual('none');
- expect(verification.authenticatorInfo?.counter).toEqual(0);
- expect(verification.authenticatorInfo?.base64PublicKey).toEqual(
- 'pAEDAzkBACBZAQDxfpXrj0ba_AH30JJ_-W7BHSOPugOD8aEDdNBKc1gjB9AmV3FPl2aL0fwiOMKtM_byI24qXb2FzcyjC7HUVkHRtzkAQnahXckI4wY_01koaY6iwXuIE3Ya0Zjs2iZyz6u4G_abGnWdObqa_kHxc3CHR7Xy5MDkAkKyX6TqU0tgHZcEhDd_Lb5ONJDwg4wvKlZBtZYElfMuZ6lonoRZ7qR_81rGkDZyFaxp6RlyvzEbo4ijeIaHQylqCz-oFm03ifZMOfRHYuF4uTjJDRH-g4BW1f3rdi7DTHk1hJnIw1IyL_VFIQ9NifkAguYjNCySCUNpYli2eMrPhAu5dYJFFjINIUMBAAE',
+ expect(verification.attestationInfo?.fmt).toEqual('none');
+ expect(verification.attestationInfo?.counter).toEqual(0);
+ expect(verification.attestationInfo?.credentialPublicKey).toEqual(
+ base64url.toBuffer(
+ 'pAEDAzkBACBZAQDxfpXrj0ba_AH30JJ_-W7BHSOPugOD8aEDdNBKc1gjB9AmV3FPl2aL0fwiOMKtM_byI24qXb2FzcyjC7HUVkHRtzkAQnahXckI4wY_01koaY6iwXuIE3Ya0Zjs2iZyz6u4G_abGnWdObqa_kHxc3CHR7Xy5MDkAkKyX6TqU0tgHZcEhDd_Lb5ONJDwg4wvKlZBtZYElfMuZ6lonoRZ7qR_81rGkDZyFaxp6RlyvzEbo4ijeIaHQylqCz-oFm03ifZMOfRHYuF4uTjJDRH-g4BW1f3rdi7DTHk1hJnIw1IyL_VFIQ9NifkAguYjNCySCUNpYli2eMrPhAu5dYJFFjINIUMBAAE',
+ ),
);
- expect(verification.authenticatorInfo?.base64CredentialID).toEqual(
- 'kGXv4RJWLeXRw8Yf3T22K3Gq_GGeDv9OKYmAHLm0Ylo',
+ expect(verification.attestationInfo?.credentialID).toEqual(
+ base64url.toBuffer('kGXv4RJWLeXRw8Yf3T22K3Gq_GGeDv9OKYmAHLm0Ylo'),
);
});
@@ -186,7 +210,9 @@ test('should throw when attestation type is not webauthn.create', async () => {
test('should throw if an unexpected attestation format is specified', async () => {
const fmt = 'fizzbuzz';
- const realAtteObj = decodeAttestationObject.default(attestationNone.response.attestationObject);
+ const realAtteObj = decodeAttestationObject.default(
+ base64url.toBuffer(attestationNone.response.attestationObject),
+ );
mockDecodeAttestation.mockReturnValue({
...realAtteObj,
@@ -205,7 +231,9 @@ test('should throw if an unexpected attestation format is specified', async () =
});
test('should throw error if assertion RP ID is unexpected value', async () => {
- const { authData } = decodeAttestationObject.default(attestationNone.response.attestationObject);
+ const { authData } = decodeAttestationObject.default(
+ base64url.toBuffer(attestationNone.response.attestationObject),
+ );
const actualAuthData = parseAuthenticatorData.default(authData);
mockParseAuthData.mockReturnValue({
@@ -325,7 +353,7 @@ test('should not include authenticator info if not verified', async () => {
});
expect(verification.verified).toBe(false);
- expect(verification.authenticatorInfo).toBeUndefined();
+ expect(verification.attestationInfo).toBeUndefined();
});
test('should throw an error if user verification is required but user was not verified', async () => {
@@ -368,13 +396,15 @@ test('should validate TPM RSA response (SHA256)', async () => {
});
expect(verification.verified).toEqual(true);
- expect(verification.authenticatorInfo?.fmt).toEqual('tpm');
- expect(verification.authenticatorInfo?.counter).toEqual(30);
- expect(verification.authenticatorInfo?.base64PublicKey).toEqual(
- 'pAEDAzkBACBZAQCtxzw59Wsl8xWP97wPTu2TSDlushwshL8GedHAHO1R62m3nNy21hCLJlQabfLepRUQ_v9mq3PCmV81tBSqtRGU5_YlK0R2yeu756SnT39c6hKC3PBPt_xdjL_ccz4H_73DunfB63QZOtdeAsswV7WPLqMARofuM-LQ_LHnNguCypDcxhADuUqQtogfwZsknTVIPxzGcfqnQ7ERF9D9AOWIQ8YjOsTi_B2zS8SOySKIFUGwwYcPG7DiCE-QJcI-fpydRDnEq6UxbkYgB7XK4BlmPKlwuXkBDX9egl_Ma4B7W2WJvYbKevu6Z8Kc5y-OITpNVDYKbBK3qKyh4yIUpB1NIUMBAAE',
+ expect(verification.attestationInfo?.fmt).toEqual('tpm');
+ expect(verification.attestationInfo?.counter).toEqual(30);
+ expect(verification.attestationInfo?.credentialPublicKey).toEqual(
+ base64url.toBuffer(
+ 'pAEDAzkBACBZAQCtxzw59Wsl8xWP97wPTu2TSDlushwshL8GedHAHO1R62m3nNy21hCLJlQabfLepRUQ_v9mq3PCmV81tBSqtRGU5_YlK0R2yeu756SnT39c6hKC3PBPt_xdjL_ccz4H_73DunfB63QZOtdeAsswV7WPLqMARofuM-LQ_LHnNguCypDcxhADuUqQtogfwZsknTVIPxzGcfqnQ7ERF9D9AOWIQ8YjOsTi_B2zS8SOySKIFUGwwYcPG7DiCE-QJcI-fpydRDnEq6UxbkYgB7XK4BlmPKlwuXkBDX9egl_Ma4B7W2WJvYbKevu6Z8Kc5y-OITpNVDYKbBK3qKyh4yIUpB1NIUMBAAE',
+ ),
);
- expect(verification.authenticatorInfo?.base64CredentialID).toEqual(
- 'lGkWHPe88VpnNYgVBxzon_MRR9-gmgODveQ16uM_bPM',
+ expect(verification.attestationInfo?.credentialID).toEqual(
+ base64url.toBuffer('lGkWHPe88VpnNYgVBxzon_MRR9-gmgODveQ16uM_bPM'),
);
});
@@ -398,13 +428,15 @@ test('should validate TPM RSA response (SHA1)', async () => {
});
expect(verification.verified).toEqual(true);
- expect(verification.authenticatorInfo?.fmt).toEqual('tpm');
- expect(verification.authenticatorInfo?.counter).toEqual(97);
- expect(verification.authenticatorInfo?.base64PublicKey).toEqual(
- 'pAEDAzn__iBZAQCzl_wD24PZ5z-po2FrwoQVdd13got_CkL8p4B_NvJBC5OwAYKDilii_wj-0CA8ManbpSInx9Tdnz6t91OhudwUT0-W_BHSLK_MqFcjZWrR5LYVmVpz1EgH3DrOTra4AlogEq2D2CYktPrPe7joE-oT3vAYXK8vzQDLRyaxI_Z1qS4KLlLCdajW8PGpw1YRjMDw6s69GZU8mXkgNPMCUh1TZ1bnCvJTO9fnmLjDjqdQGRU4bWo8tFjCL8g1-2WD_2n0-twt6n-Uox5VnR1dQJG4awMlanBCkGGpOb3WBDQ8K10YJJ2evPhJKGJahBvu2Dxmq6pLCAXCv0ma3EHj-PmDIUMBAAE',
+ expect(verification.attestationInfo?.fmt).toEqual('tpm');
+ expect(verification.attestationInfo?.counter).toEqual(97);
+ expect(verification.attestationInfo?.credentialPublicKey).toEqual(
+ base64url.toBuffer(
+ 'pAEDAzn__iBZAQCzl_wD24PZ5z-po2FrwoQVdd13got_CkL8p4B_NvJBC5OwAYKDilii_wj-0CA8ManbpSInx9Tdnz6t91OhudwUT0-W_BHSLK_MqFcjZWrR5LYVmVpz1EgH3DrOTra4AlogEq2D2CYktPrPe7joE-oT3vAYXK8vzQDLRyaxI_Z1qS4KLlLCdajW8PGpw1YRjMDw6s69GZU8mXkgNPMCUh1TZ1bnCvJTO9fnmLjDjqdQGRU4bWo8tFjCL8g1-2WD_2n0-twt6n-Uox5VnR1dQJG4awMlanBCkGGpOb3WBDQ8K10YJJ2evPhJKGJahBvu2Dxmq6pLCAXCv0ma3EHj-PmDIUMBAAE',
+ ),
);
- expect(verification.authenticatorInfo?.base64CredentialID).toEqual(
- 'oELnad0f6-g2BtzEn_78iLNoubarlq0xFtOtAMXnflU',
+ expect(verification.attestationInfo?.credentialID).toEqual(
+ base64url.toBuffer('oELnad0f6-g2BtzEn_78iLNoubarlq0xFtOtAMXnflU'),
);
});
@@ -428,13 +460,15 @@ test('should validate Android-Key response', async () => {
});
expect(verification.verified).toEqual(true);
- expect(verification.authenticatorInfo?.fmt).toEqual('android-key');
- expect(verification.authenticatorInfo?.counter).toEqual(108);
- expect(verification.authenticatorInfo?.base64PublicKey).toEqual(
- 'pQECAyYgASFYIEjCq7woGNN_42rbaqMgJvz0nuKTWNRrR29lMX3J239oIlgg6IcAXqPJPIjSrClHDAmbJv_EShYhYq0R9-G3k744n7Y',
+ expect(verification.attestationInfo?.fmt).toEqual('android-key');
+ expect(verification.attestationInfo?.counter).toEqual(108);
+ expect(verification.attestationInfo?.credentialPublicKey).toEqual(
+ base64url.toBuffer(
+ 'pQECAyYgASFYIEjCq7woGNN_42rbaqMgJvz0nuKTWNRrR29lMX3J239oIlgg6IcAXqPJPIjSrClHDAmbJv_EShYhYq0R9-G3k744n7Y',
+ ),
);
- expect(verification.authenticatorInfo?.base64CredentialID).toEqual(
- 'PPa1spYTB680cQq5q6qBtFuPLLdG1FQ73EastkT8n0o',
+ expect(verification.attestationInfo?.credentialID).toEqual(
+ base64url.toBuffer('PPa1spYTB680cQq5q6qBtFuPLLdG1FQ73EastkT8n0o'),
);
});
diff --git a/packages/server/src/attestation/verifyAttestationResponse.ts b/packages/server/src/attestation/verifyAttestationResponse.ts
index 3940ea6..023c8f2 100644
--- a/packages/server/src/attestation/verifyAttestationResponse.ts
+++ b/packages/server/src/attestation/verifyAttestationResponse.ts
@@ -4,12 +4,13 @@ import {
COSEAlgorithmIdentifier,
} from '@simplewebauthn/typescript-types';
-import decodeAttestationObject, { ATTESTATION_FORMATS } from '../helpers/decodeAttestationObject';
+import decodeAttestationObject, { ATTESTATION_FORMAT } from '../helpers/decodeAttestationObject';
import decodeClientDataJSON from '../helpers/decodeClientDataJSON';
import parseAuthenticatorData from '../helpers/parseAuthenticatorData';
import toHash from '../helpers/toHash';
import decodeCredentialPublicKey from '../helpers/decodeCredentialPublicKey';
import { COSEKEYS } from '../helpers/convertCOSEtoPKCS';
+import convertAAGUIDToString from '../helpers/convertAAGUIDToString';
import { supportedCOSEAlgorithmIdentifiers } from './generateAttestationOptions';
import verifyFIDOU2F from './verifications/verifyFIDOU2F';
@@ -110,8 +111,9 @@ export default async function verifyAttestationResponse(
}
}
- const attestationObject = decodeAttestationObject(response.attestationObject);
- const { fmt, authData, attStmt } = attestationObject;
+ const attestationObject = base64url.toBuffer(response.attestationObject);
+ const decodedAttestationObject = decodeAttestationObject(attestationObject);
+ const { fmt, authData, attStmt } = decodedAttestationObject;
const parsedAuthData = parseAuthenticatorData(authData);
const { aaguid, rpIdHash, flags, credentialID, counter, credentialPublicKey } = parsedAuthData;
@@ -177,7 +179,7 @@ export default async function verifyAttestationResponse(
* Verification can only be performed when attestation = 'direct'
*/
let verified = false;
- if (fmt === ATTESTATION_FORMATS.FIDO_U2F) {
+ if (fmt === ATTESTATION_FORMAT.FIDO_U2F) {
verified = verifyFIDOU2F({
attStmt,
clientDataHash,
@@ -186,7 +188,7 @@ export default async function verifyAttestationResponse(
rpIdHash,
aaguid,
});
- } else if (fmt === ATTESTATION_FORMATS.PACKED) {
+ } else if (fmt === ATTESTATION_FORMAT.PACKED) {
verified = await verifyPacked({
attStmt,
authData,
@@ -194,14 +196,14 @@ export default async function verifyAttestationResponse(
credentialPublicKey,
aaguid,
});
- } else if (fmt === ATTESTATION_FORMATS.ANDROID_SAFETYNET) {
+ } else if (fmt === ATTESTATION_FORMAT.ANDROID_SAFETYNET) {
verified = await verifyAndroidSafetynet({
attStmt,
authData,
clientDataHash,
aaguid,
});
- } else if (fmt === ATTESTATION_FORMATS.ANDROID_KEY) {
+ } else if (fmt === ATTESTATION_FORMAT.ANDROID_KEY) {
verified = await verifyAndroidKey({
attStmt,
authData,
@@ -209,7 +211,7 @@ export default async function verifyAttestationResponse(
credentialPublicKey,
aaguid,
});
- } else if (fmt === ATTESTATION_FORMATS.TPM) {
+ } else if (fmt === ATTESTATION_FORMAT.TPM) {
verified = await verifyTPM({
aaguid,
attStmt,
@@ -217,14 +219,14 @@ export default async function verifyAttestationResponse(
credentialPublicKey,
clientDataHash,
});
- } else if (fmt === ATTESTATION_FORMATS.APPLE) {
+ } else if (fmt === ATTESTATION_FORMAT.APPLE) {
verified = await verifyApple({
attStmt,
authData,
clientDataHash,
credentialPublicKey,
});
- } else if (fmt === ATTESTATION_FORMATS.NONE) {
+ } else if (fmt === ATTESTATION_FORMAT.NONE) {
if (Object.keys(attStmt).length > 0) {
throw new Error('None attestation had unexpected attestation statement');
}
@@ -236,17 +238,18 @@ export default async function verifyAttestationResponse(
const toReturn: VerifiedAttestation = {
verified,
- userVerified: flags.uv,
};
if (toReturn.verified) {
- toReturn.userVerified = flags.uv;
-
- toReturn.authenticatorInfo = {
+ toReturn.attestationInfo = {
fmt,
counter,
- base64PublicKey: base64url.encode(credentialPublicKey),
- base64CredentialID: base64url.encode(credentialID),
+ aaguid: convertAAGUIDToString(aaguid),
+ credentialPublicKey,
+ credentialID,
+ credentialType,
+ userVerified: flags.uv,
+ attestationObject,
};
}
@@ -257,23 +260,28 @@ export default async function verifyAttestationResponse(
* 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.
+ * @param attestationInfo.fmt Type of attestation
+ * @param attestationInfo.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!**
+ * @param attestationInfo.aaguid Authenticator's Attestation GUID indicating the type of the
+ * authenticator
+ * @param attestationInfo.credentialPublicKey The credential's public key
+ * @param attestationInfo.credentialID The credential's credential ID for the public key above
+ * @param attestationInfo.credentialType The type of the credential returned by the browser
+ * @param attestationInfo.userVerified Whether the user was uniquely identified during attestation
+ * @param attestationInfo.attestationObject The raw `response.attestationObject` Buffer returned by
+ * the authenticator
*/
export type VerifiedAttestation = {
verified: boolean;
- userVerified: boolean;
- authenticatorInfo?: {
- fmt: ATTESTATION_FORMATS;
+ attestationInfo?: {
+ fmt: ATTESTATION_FORMAT;
counter: number;
- base64PublicKey: string;
- base64CredentialID: string;
+ aaguid: string;
+ credentialPublicKey: Buffer;
+ credentialID: Buffer;
+ credentialType: string;
+ userVerified: boolean;
+ attestationObject: Buffer;
};
};
diff --git a/packages/server/src/helpers/convertPublicKeyToPEM.ts b/packages/server/src/helpers/convertPublicKeyToPEM.ts
index 834f96c..9be1e0a 100644
--- a/packages/server/src/helpers/convertPublicKeyToPEM.ts
+++ b/packages/server/src/helpers/convertPublicKeyToPEM.ts
@@ -1,15 +1,12 @@
import cbor from 'cbor';
import jwkToPem from 'jwk-to-pem';
-import base64url from 'base64url';
import { COSEKEYS, COSEKTY, COSECRV } from './convertCOSEtoPKCS';
-export default function convertPublicKeyToPEM(publicKey: string): string {
- const publicKeyBuffer = base64url.toBuffer(publicKey);
-
+export default function convertPublicKeyToPEM(publicKey: Buffer): string {
let struct;
try {
- struct = cbor.decodeAllSync(publicKeyBuffer)[0];
+ struct = cbor.decodeAllSync(publicKey)[0];
} catch (err) {
throw new Error(`Error decoding public key while converting to PEM: ${err.message}`);
}
diff --git a/packages/server/src/helpers/decodeAttestationObject.test.ts b/packages/server/src/helpers/decodeAttestationObject.test.ts
index 2f88f2a..6ba29e9 100644
--- a/packages/server/src/helpers/decodeAttestationObject.test.ts
+++ b/packages/server/src/helpers/decodeAttestationObject.test.ts
@@ -2,10 +2,13 @@ import decodeAttestationObject from './decodeAttestationObject';
test('should decode base64url-encoded indirect attestationObject', () => {
const decoded = decodeAttestationObject(
- 'o2NmbXRkbm9uZWdhdHRTdG10oGhhdXRoRGF0YVjEAbElFazplpnc037DORGDZNjDq86cN9vm6' +
- '+APoAM20wtBAAAAAAAAAAAAAAAAAAAAAAAAAAAAQKmPuEwByQJ3e89TccUSrCGDkNWquhevjLLn/' +
- 'KNZZaxQQ0steueoG2g12dvnUNbiso8kVJDyLa+6UiA34eniujWlAQIDJiABIVggiUk8wN2j' +
- '+3fkKI7KSiLBkKzs3FfhPZxHgHPnGLvOY/YiWCBv7+XyTqArnMVtQ947/8Xk8fnVCdLMRWJGM1VbNevVcQ==',
+ Buffer.from(
+ 'o2NmbXRkbm9uZWdhdHRTdG10oGhhdXRoRGF0YVjEAbElFazplpnc037DORGDZNjDq86cN9vm6' +
+ '+APoAM20wtBAAAAAAAAAAAAAAAAAAAAAAAAAAAAQKmPuEwByQJ3e89TccUSrCGDkNWquhevjLLn/' +
+ 'KNZZaxQQ0steueoG2g12dvnUNbiso8kVJDyLa+6UiA34eniujWlAQIDJiABIVggiUk8wN2j' +
+ '+3fkKI7KSiLBkKzs3FfhPZxHgHPnGLvOY/YiWCBv7+XyTqArnMVtQ947/8Xk8fnVCdLMRWJGM1VbNevVcQ==',
+ 'base64',
+ ),
);
expect(decoded.fmt).toEqual('none');
@@ -15,21 +18,24 @@ test('should decode base64url-encoded indirect attestationObject', () => {
test('should decode base64url-encoded direct attestationObject', () => {
const decoded = decodeAttestationObject(
- 'o2NmbXRoZmlkby11MmZnYXR0U3RtdKJjc2lnWEgwRgIhAK40WxA0t7py7AjEXvwGwTlmqlvrOk' +
- 's5g9lf+9zXzRiVAiEA3bv60xyXveKDOusYzniD7CDSostCet9PYK7FLdnTdZNjeDVjgVkCwTCCAr0wggGloAMCAQICBCrn' +
- 'YmMwDQYJKoZIhvcNAQELBQAwLjEsMCoGA1UEAxMjWXViaWNvIFUyRiBSb290IENBIFNlcmlhbCA0NTcyMDA2MzEwIBcNMT' +
- 'QwODAxMDAwMDAwWhgPMjA1MDA5MDQwMDAwMDBaMG4xCzAJBgNVBAYTAlNFMRIwEAYDVQQKDAlZdWJpY28gQUIxIjAgBgNV' +
- 'BAsMGUF1dGhlbnRpY2F0b3IgQXR0ZXN0YXRpb24xJzAlBgNVBAMMHll1YmljbyBVMkYgRUUgU2VyaWFsIDcxOTgwNzA3NT' +
- 'BZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABCoDhl5gQ9meEf8QqiVUV4S/Ca+Oax47MhcpIW9VEhqM2RDTmd3HaL3+SnvH' +
- '49q8YubSRp/1Z1uP+okMynSGnj+jbDBqMCIGCSsGAQQBgsQKAgQVMS4zLjYuMS40LjEuNDE0ODIuMS4xMBMGCysGAQQBgu' +
- 'UcAgEBBAQDAgQwMCEGCysGAQQBguUcAQEEBBIEEG1Eupv27C5JuTAMj+kgy3MwDAYDVR0TAQH/BAIwADANBgkqhkiG9w0B' +
- 'AQsFAAOCAQEAclfQPNzD4RVphJDW+A75W1MHI3PZ5kcyYysR3Nx3iuxr1ZJtB+F7nFQweI3jL05HtFh2/4xVIgKb6Th4eV' +
- 'cjMecncBaCinEbOcdP1sEli9Hk2eVm1XB5A0faUjXAPw/+QLFCjgXG6ReZ5HVUcWkB7riLsFeJNYitiKrTDXFPLy+sNtVN' +
- 'utcQnFsCerDKuM81TvEAigkIbKCGlq8M/NvBg5j83wIxbCYiyV7mIr3RwApHieShzLdJo1S6XydgQjC+/64G5r8C+8AVvN' +
- 'FR3zXXCpio5C3KRIj88HEEIYjf6h1fdLfqeIsq+cUUqbq5T+c4nNoZUZCysTB9v5EY4akp+GhhdXRoRGF0YVjEAbElFazp' +
- 'lpnc037DORGDZNjDq86cN9vm6+APoAM20wtBAAAAAAAAAAAAAAAAAAAAAAAAAAAAQGFYevaR71ptU5YtXOSnVzPQTsGgK+' +
- 'gLiBKnqPWBmZXNRvjISqlLxiwApzlrfkTc3lEMYMatjeACCnsijOkNEGOlAQIDJiABIVggdWLG6UvGyHFw/k/bv6/k6z/L' +
- 'LgSO5KXzXw2EcUxkEX8iWCBeaVLz/cbyoKvRIg/q+q7tan0VN+i3WR0BOBCcuNP7yw==',
+ Buffer.from(
+ 'o2NmbXRoZmlkby11MmZnYXR0U3RtdKJjc2lnWEgwRgIhAK40WxA0t7py7AjEXvwGwTlmqlvrOk' +
+ 's5g9lf+9zXzRiVAiEA3bv60xyXveKDOusYzniD7CDSostCet9PYK7FLdnTdZNjeDVjgVkCwTCCAr0wggGloAMCAQICBCrn' +
+ 'YmMwDQYJKoZIhvcNAQELBQAwLjEsMCoGA1UEAxMjWXViaWNvIFUyRiBSb290IENBIFNlcmlhbCA0NTcyMDA2MzEwIBcNMT' +
+ 'QwODAxMDAwMDAwWhgPMjA1MDA5MDQwMDAwMDBaMG4xCzAJBgNVBAYTAlNFMRIwEAYDVQQKDAlZdWJpY28gQUIxIjAgBgNV' +
+ 'BAsMGUF1dGhlbnRpY2F0b3IgQXR0ZXN0YXRpb24xJzAlBgNVBAMMHll1YmljbyBVMkYgRUUgU2VyaWFsIDcxOTgwNzA3NT' +
+ 'BZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABCoDhl5gQ9meEf8QqiVUV4S/Ca+Oax47MhcpIW9VEhqM2RDTmd3HaL3+SnvH' +
+ '49q8YubSRp/1Z1uP+okMynSGnj+jbDBqMCIGCSsGAQQBgsQKAgQVMS4zLjYuMS40LjEuNDE0ODIuMS4xMBMGCysGAQQBgu' +
+ 'UcAgEBBAQDAgQwMCEGCysGAQQBguUcAQEEBBIEEG1Eupv27C5JuTAMj+kgy3MwDAYDVR0TAQH/BAIwADANBgkqhkiG9w0B' +
+ 'AQsFAAOCAQEAclfQPNzD4RVphJDW+A75W1MHI3PZ5kcyYysR3Nx3iuxr1ZJtB+F7nFQweI3jL05HtFh2/4xVIgKb6Th4eV' +
+ 'cjMecncBaCinEbOcdP1sEli9Hk2eVm1XB5A0faUjXAPw/+QLFCjgXG6ReZ5HVUcWkB7riLsFeJNYitiKrTDXFPLy+sNtVN' +
+ 'utcQnFsCerDKuM81TvEAigkIbKCGlq8M/NvBg5j83wIxbCYiyV7mIr3RwApHieShzLdJo1S6XydgQjC+/64G5r8C+8AVvN' +
+ 'FR3zXXCpio5C3KRIj88HEEIYjf6h1fdLfqeIsq+cUUqbq5T+c4nNoZUZCysTB9v5EY4akp+GhhdXRoRGF0YVjEAbElFazp' +
+ 'lpnc037DORGDZNjDq86cN9vm6+APoAM20wtBAAAAAAAAAAAAAAAAAAAAAAAAAAAAQGFYevaR71ptU5YtXOSnVzPQTsGgK+' +
+ 'gLiBKnqPWBmZXNRvjISqlLxiwApzlrfkTc3lEMYMatjeACCnsijOkNEGOlAQIDJiABIVggdWLG6UvGyHFw/k/bv6/k6z/L' +
+ 'LgSO5KXzXw2EcUxkEX8iWCBeaVLz/cbyoKvRIg/q+q7tan0VN+i3WR0BOBCcuNP7yw==',
+ 'base64',
+ ),
);
expect(decoded.fmt).toEqual('fido-u2f');
diff --git a/packages/server/src/helpers/decodeAttestationObject.ts b/packages/server/src/helpers/decodeAttestationObject.ts
index 09d95fb..362e8a0 100644
--- a/packages/server/src/helpers/decodeAttestationObject.ts
+++ b/packages/server/src/helpers/decodeAttestationObject.ts
@@ -6,15 +6,12 @@ import cbor from 'cbor';
*
* @param base64AttestationObject Base64URL-encoded Attestation Object
*/
-export default function decodeAttestationObject(
- base64AttestationObject: string,
-): AttestationObject {
- const toBuffer = base64url.toBuffer(base64AttestationObject);
- const toCBOR: AttestationObject = cbor.decodeAllSync(toBuffer)[0];
+export default function decodeAttestationObject(attestationObject: Buffer): AttestationObject {
+ const toCBOR: AttestationObject = cbor.decodeAllSync(attestationObject)[0];
return toCBOR;
}
-export enum ATTESTATION_FORMATS {
+export enum ATTESTATION_FORMAT {
FIDO_U2F = 'fido-u2f',
PACKED = 'packed',
ANDROID_SAFETYNET = 'android-safetynet',
@@ -25,7 +22,7 @@ export enum ATTESTATION_FORMATS {
}
export type AttestationObject = {
- fmt: ATTESTATION_FORMATS;
+ fmt: ATTESTATION_FORMAT;
attStmt: AttestationStatement;
authData: Buffer;
};
diff --git a/packages/server/src/metadata/metadataService.ts b/packages/server/src/metadata/metadataService.ts
index 56163e8..dc48f28 100644
--- a/packages/server/src/metadata/metadataService.ts
+++ b/packages/server/src/metadata/metadataService.ts
@@ -8,6 +8,8 @@ import toHash from '../helpers/toHash';
import validateCertificatePath from '../helpers/validateCertificatePath';
import convertX509CertToPEM from '../helpers/convertX509CertToPEM';
import convertAAGUIDToString from '../helpers/convertAAGUIDToString';
+// TODO: Re-enable this once we figure out logging
+// import { log } from '../helpers/logging';
import parseJWT from './parseJWT';
@@ -63,7 +65,7 @@ class MetadataService {
const { mdsServers, statements } = opts;
- this.state = SERVICE_STATE.REFRESHING;
+ this.setState(SERVICE_STATE.REFRESHING);
// If metadata statements are provided, load them into the cache first
if (statements?.length) {
@@ -86,6 +88,9 @@ class MetadataService {
// If MDS servers are provided, then process them and add their statements to the cache
if (mdsServers?.length) {
+ // TODO: Re-enable this once we figure out logging
+ // const currentCacheCount = Object.keys(this.statementCache).length;
+
for (const server of mdsServers) {
try {
await this.downloadTOC({
@@ -98,11 +103,18 @@ class MetadataService {
});
} catch (err) {
// Notify of the error and move on
+ // TODO: Re-enable this once we figure out logging
+ // log('warning', `Could not download TOC from ${server.url}:`, err);
}
}
+
+ // TODO: Re-enable this once we figure out logging
+ // const newCacheCount = Object.keys(this.statementCache).length;
+ // const cacheDiff = newCacheCount - currentCacheCount;
+ // log('info', `Downloaded ${cacheDiff} statements from ${mdsServers.length} metadata servers`);
}
- this.state = SERVICE_STATE.READY;
+ this.setState(SERVICE_STATE.READY);
}
/**
@@ -142,10 +154,10 @@ class MetadataService {
const now = new Date();
if (now > mds.nextUpdate) {
try {
- this.state = SERVICE_STATE.REFRESHING;
+ this.setState(SERVICE_STATE.REFRESHING);
await this.downloadTOC(mds);
} finally {
- this.state = SERVICE_STATE.READY;
+ this.setState(SERVICE_STATE.READY);
}
}
}
@@ -309,6 +321,24 @@ class MetadataService {
return readyPromise;
}
+
+ /**
+ * Report service status on change
+ */
+ private setState(newState: SERVICE_STATE) {
+ this.state = newState;
+
+ if (newState === SERVICE_STATE.DISABLED) {
+ // TODO: Re-enable this once we figure out logging
+ // log('MetadataService is DISABLED');
+ } else if (newState === SERVICE_STATE.REFRESHING) {
+ // TODO: Re-enable this once we figure out logging
+ // log('MetadataService is REFRESHING');
+ } else if (newState === SERVICE_STATE.READY) {
+ // TODO: Re-enable this once we figure out logging
+ // log('MetadataService is READY');
+ }
+ }
}
const metadataService = new MetadataService();
diff --git a/packages/typescript-types/src/index.ts b/packages/typescript-types/src/index.ts
index c950398..8ba2297 100644
--- a/packages/typescript-types/src/index.ts
+++ b/packages/typescript-types/src/index.ts
@@ -112,9 +112,9 @@ export interface AuthenticatorAssertionResponseJSON
* A WebAuthn-compatible device and the information needed to verify assertions by it
*/
export type AuthenticatorDevice = {
- publicKey: Base64URLString;
- credentialID: Base64URLString;
- // Number of times this device is expected to have been used
+ credentialPublicKey: Buffer;
+ credentialID: Buffer;
+ // Number of times this authenticator is expected to have been used
counter: number;
// From browser's `startAttestation()` -> AttestationCredentialJSON.transports (API L2 and up)
transports?: AuthenticatorTransport[];