summaryrefslogtreecommitdiffhomepage
path: root/packages/server/src
diff options
context:
space:
mode:
Diffstat (limited to 'packages/server/src')
-rw-r--r--packages/server/src/authentication/verifyAuthenticationResponse.test.ts34
-rw-r--r--packages/server/src/authentication/verifyAuthenticationResponse.ts12
-rw-r--r--packages/server/src/helpers/decodeExtensions.ts6
-rw-r--r--packages/server/src/registration/verifyRegistrationResponse.test.ts49
4 files changed, 75 insertions, 26 deletions
diff --git a/packages/server/src/authentication/verifyAuthenticationResponse.test.ts b/packages/server/src/authentication/verifyAuthenticationResponse.test.ts
index 1273e89..86a4601 100644
--- a/packages/server/src/authentication/verifyAuthenticationResponse.test.ts
+++ b/packages/server/src/authentication/verifyAuthenticationResponse.test.ts
@@ -8,6 +8,7 @@ import {
AuthenticatorDevice,
AuthenticationCredentialJSON,
} from '@simplewebauthn/typescript-types';
+import { anyExtendedKeyUsage } from '@peculiar/asn1-x509';
let mockDecodeClientData: jest.SpyInstance;
let mockParseAuthData: jest.SpyInstance;
@@ -308,6 +309,39 @@ test('should fail verification if custom challenge verifier returns false', () =
}).toThrow(/custom challenge verifier returned false/i);
});
+test('should return authenticator extension output', async () => {
+ const verification = verifyAuthenticationResponse({
+ credential: {
+ response: {
+ clientDataJSON: "eyJ0eXBlIjoid2ViYXV0aG4uZ2V0IiwiY2hhbGxlbmdlIjoiaVpzVkN6dHJEVzdEMlVfR0hDSWxZS0x3VjJiQ3NCVFJxVlFVbkpYbjlUayIsIm9yaWdpbiI6ImFuZHJvaWQ6YXBrLWtleS1oYXNoOmd4N3NxX3B4aHhocklRZEx5ZkcwcHhLd2lKN2hPazJESlE0eHZLZDQzOFEiLCJhbmRyb2lkUGFja2FnZU5hbWUiOiJjb20uZmlkby5leGFtcGxlLmZpZG8yYXBpZXhhbXBsZSJ9",
+ authenticatorData:"DXX8xWP9p3nbLjQ-6kiYiHWLeFSdSTpP2-oc2WqjHMSFAAAAAKFvZGV2aWNlUHVibGljS2V5pWNkcGtYTaUBAgMmIAEhWCCZGqvtneQnGp7erYgG-dyW1tzNDEdiU6VRBInsg3m-WyJYIKCXPP3tu3nif-9O50gWc_szElBN3KVDTP0jQx1q0p7aY3NpZ1hHMEUCIElSbNKK72tOYhp9WTbStQSVL8CuIxOk8DV6r_-uqWR0AiEAnVE6yu-wsyx2Wq5v66jClGhe_2P_HL8R7PIQevT-uPhlbm9uY2VAZXNjb3BlQQBmYWFndWlkULk_2WHy5kYvsSKCACJH3ng=",
+ signature:"MEYCIQDlRuxY7cYre0sb3T6TovQdfYIUb72cRZYOQv_zS9wN_wIhAOvN-fwjtyIhWRceqJV4SX74-z6oALERbC7ohk8EdVPO",
+ userHandle:"b2FPajFxcmM4MWo3QkFFel9RN2lEakh5RVNlU2RLNDF0Sl92eHpQYWV5UQ=="
+ },
+ id:"E_Pko4wN1BXE23S0ftN3eQ",
+ rawId:"E_Pko4wN1BXE23S0ftN3eQ",
+ type:"public-key",
+ clientExtensionResults: {}
+ },
+ expectedOrigin: 'android:apk-key-hash:gx7sq_pxhxhrIQdLyfG0pxKwiJ7hOk2DJQ4xvKd438Q',
+ expectedRPID: 'try-webauthn.appspot.com',
+ expectedChallenge: 'iZsVCztrDW7D2U_GHCIlYKLwV2bCsBTRqVQUnJXn9Tk',
+ authenticator: {
+ credentialID: base64url.toBuffer(
+ 'AaIBxnYfL2pDWJmIii6CYgHBruhVvFGHheWamphVioG_TnEXxKA9MW4FWnJh21zsbmRpRJso9i2JmAtWOtXfVd4oXTgYVusXwhWWsA'
+ ),
+ credentialPublicKey: base64url.toBuffer(
+ 'pQECAyYgASFYILTrxTUQv3X4DRM6L_pk65FSMebenhCx3RMsTKoBm-AxIlggEf3qk5552QLNSh1T1oQs7_2C2qysDwN4r4fCp52Hsqs'
+ ),
+ counter: 0,
+ }
+ });
+
+ expect(verification.authenticationInfo?.extensions).toMatchObject({
+ 'devicePublicKey': expect.anything()
+ });
+});
+
test('should return credential backup info', async () => {
const verification = verifyAuthenticationResponse({
credential: assertionResponse,
diff --git a/packages/server/src/authentication/verifyAuthenticationResponse.ts b/packages/server/src/authentication/verifyAuthenticationResponse.ts
index 264a2f2..1949449 100644
--- a/packages/server/src/authentication/verifyAuthenticationResponse.ts
+++ b/packages/server/src/authentication/verifyAuthenticationResponse.ts
@@ -12,6 +12,7 @@ import verifySignature from '../helpers/verifySignature';
import parseAuthenticatorData from '../helpers/parseAuthenticatorData';
import isBase64URLString from '../helpers/isBase64URLString';
import { parseBackupFlags } from '../helpers/parseBackupFlags';
+import decodeExtensionDataBuffer, { ExtensionsJSON } from '../helpers/decodeExtensions';
export type VerifyAuthenticationResponseOpts = {
credential: AuthenticationCredentialJSON;
@@ -134,7 +135,7 @@ export default function verifyAuthenticationResponse(
const authDataBuffer = base64url.toBuffer(response.authenticatorData);
const parsedAuthData = parseAuthenticatorData(authDataBuffer);
- const { rpIdHash, flags, counter } = parsedAuthData;
+ const { rpIdHash, flags, counter, extensionsDataBuffer } = parsedAuthData;
// Make sure the response's RP ID is ours
if (typeof expectedRPID === 'string') {
@@ -159,6 +160,13 @@ export default function verifyAuthenticationResponse(
throw new Error('User not present during authentication');
}
+ let extensions = {};
+
+ // Parse authenticator extensions if available
+ if (flags.ed && extensionsDataBuffer) {
+ extensions = decodeExtensionDataBuffer(extensionsDataBuffer)
+ }
+
// Enforce user verification if required
if (requireUserVerification && !flags.uv) {
throw new Error('User verification required, but user could not be verified');
@@ -189,6 +197,7 @@ export default function verifyAuthenticationResponse(
credentialID: authenticator.credentialID,
credentialDeviceType,
credentialBackedUp,
+ extensions
},
};
@@ -218,5 +227,6 @@ export type VerifiedAuthenticationResponse = {
newCounter: number;
credentialDeviceType: CredentialDeviceType;
credentialBackedUp: boolean;
+ extensions: ExtensionsJSON;
};
};
diff --git a/packages/server/src/helpers/decodeExtensions.ts b/packages/server/src/helpers/decodeExtensions.ts
index f1b5cb3..42f6973 100644
--- a/packages/server/src/helpers/decodeExtensions.ts
+++ b/packages/server/src/helpers/decodeExtensions.ts
@@ -11,8 +11,12 @@ export default function decodeExtensionDataBuffer(extensionDataBuffer: Buffer):
}
export type ExtensionsJSON = {
+ devicePublicKey?: DevicePublicKeyJSON
+}
+
+export type DevicePublicKeyJSON = {
dpk?: Buffer;
scp?: Buffer;
sig?: string;
aaguid?: Buffer;
-};
+}
diff --git a/packages/server/src/registration/verifyRegistrationResponse.test.ts b/packages/server/src/registration/verifyRegistrationResponse.test.ts
index 931ece1..9b7664a 100644
--- a/packages/server/src/registration/verifyRegistrationResponse.test.ts
+++ b/packages/server/src/registration/verifyRegistrationResponse.test.ts
@@ -580,18 +580,36 @@ test('should return credential backup info', async () => {
expect(verification.registrationInfo?.credentialBackedUp).toEqual(false);
});
-test('should return extension', async () => {
+test('should return authenticator extension output', async () => {
const verification = await verifyRegistrationResponse({
- credential: attestationDPK,
- expectedChallenge: attestationDPKChallenge,
+ credential: {
+ id: 'E_Pko4wN1BXE23S0ftN3eQ',
+ rawId: 'E_Pko4wN1BXE23S0ftN3eQ',
+ response: {
+ attestationObject:
+ 'o2NmbXRkbm9uZWdhdHRTdG10oGhhdXRoRGF0YVkBbQ11_MVj_ad52y40PupImIh1i3hUnUk6T9vqHNlqoxzExQAA' +
+ 'AAAAAAAAAAAAAAAAAAAAAAAAABAT8-SjjA3UFcTbdLR-03d5pQECAyYgASFYIJIkX8fs9wjKUv5HWBUop--6ig4S' +
+ 'zsxj8gBgJJmaX-_5IlggJ5XVdjUfCMlVlUZuHJRxCLFLzZCeK8Fg3l6OLfAIHnKhb2RldmljZVB1YmxpY0tleaVj' +
+ 'ZHBrWE2lAQIDJiABIVggmRqr7Z3kJxqe3q2IBvncltbczQxHYlOlUQSJ7IN5vlsiWCCglzz97bt54n_vTudIFnP7' +
+ 'MxJQTdylQ0z9I0MdatKe2mNzaWdYRzBFAiEA77OAdL0VuMgs8J-H-8b7PHFp6k8YBrfpCTc3QwI0W3oCICtxEwQH' +
+ 'MaDnJ9M41IVChjzmWICqeeXqdArIzNlDR5iOZW5vbmNlQGVzY29wZUEAZmFhZ3VpZFAAAAAAAAAAAAAAAAAAAAAA',
+ clientDataJSON:
+ 'eyJ0eXBlIjoid2ViYXV0aG4uY3JlYXRlIiwiY2hhbGxlbmdlIjoiQXJrcmxfRnhfTXZjSl9lSXFDVFE3LXRiRVNJ' +
+ 'U1IxNC1weVBSaDBLLTFBOCIsIm9yaWdpbiI6ImFuZHJvaWQ6YXBrLWtleS1oYXNoOmd4N3NxX3B4aHhocklRZEx5' +
+ 'ZkcwcHhLd2lKN2hPazJESlE0eHZLZDQzOFEiLCJhbmRyb2lkUGFja2FnZU5hbWUiOiJjb20uZmlkby5leGFtcGxl' +
+ 'LmZpZG8yYXBpZXhhbXBsZSJ9',
+ },
+ clientExtensionResults: {},
+ type: 'public-key',
+ },
+ expectedChallenge: 'Arkrl_Fx_MvcJ_eIqCTQ7-tbESISR14-pyPRh0K-1A8',
expectedOrigin: 'android:apk-key-hash:gx7sq_pxhxhrIQdLyfG0pxKwiJ7hOk2DJQ4xvKd438Q',
expectedRPID: 'try-webauthn.appspot.com',
});
- expect(verification.registrationInfo?.extensions?.dpk).toEqual(Buffer.from('3059301306072A8648CE3D020106082A8648CE3D030107034200046F985BC21D0AD79C63F2430FB52E8905585AE9372AE250B10491FED48822D150AAD22215E511B73C39835FF0F89D7BB4E910E18DDB5DB968CD13890F348DE867', 'hex'));
- expect(verification.registrationInfo?.extensions?.scp).toEqual('device');
- expect(verification.registrationInfo?.extensions?.sig).toEqual(Buffer.from('3046022100E28B11DB74A4450336320119A4E8E14624C4E715A22E29DBAFC6AA3A383FD63E022100CF41F069CC511AE77A7288F703C8E0F67255DEFCB5A56C83B16E35E0F38B2849', 'hex'));
- expect(verification.registrationInfo?.extensions?.aaguid).toEqual(Buffer.from('00000000000000000000000000000000', 'hex'));
+ expect(verification.registrationInfo?.extensions).toMatchObject({
+ 'devicePublicKey': expect.anything()
+ });
});
/**
@@ -682,20 +700,3 @@ const attestationNone: RegistrationCredentialJSON = {
type: 'public-key',
};
const attestationNoneChallenge = base64url.encode('hEccPWuziP00H0p5gxh2_u5_PC4NeYgd');
-
-const attestationDPK: RegistrationCredentialJSON = {
- id: 'LcGIzt53ej1NhnMiwcmv5Q',
- rawId: 'LcGIzt53ej1NhnMiwcmv5Q',
- response: {
- attestationObject:
- 'o2NmbXRkbm9uZWdhdHRTdG10oGhhdXRoRGF0YVkBZw11_MVj_ad52y40PupImIh1i3hUnUk6T9vqHNlqoxzExQAAAAAAAAAAAAAAAAAAAAAAAAAAABAtwYjO3nd6PU2GcyLBya_lpQECAyYgASFYIImWMQLU6wTo6sUQJLmAznqm-88GRLg1GSvr6HE9Szm4IlggrKYySExPTjIeD2o62JB3H4fyJD1TSBzwNcRfEZuwD9akY2Rwa1hbMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEb5hbwh0K15xj8kMPtS6JBVha6Tcq4lCxBJH-1Igi0VCq0iIV5RG3PDmDX_D4nXu06RDhjdtduWjNE4kPNI3oZ2NzY3BmZGV2aWNlY3NpZ1hIMEYCIQDiixHbdKRFAzYyARmk6OFGJMTnFaIuKduvxqo6OD_WPgIhAM9B8GnMURrnenKI9wPI4PZyVd78taVsg7FuNeDziyhJZmFhZ3VpZFAAAAAAAAAAAAAAAAAAAAAA',
- clientDataJSON:
- 'eyJ0eXBlIjoid2ViYXV0aG4uY3JlYXRlIiwiY2hhbGxlbmdlIjoid2cyZGdqcnI1aWxjWW1mQ2UtY05VWllvWWlH' +
- 'SVo1dDdIeFk0eV94NG9lOCIsIm9yaWdpbiI6ImFuZHJvaWQ6YXBrLWtleS1oYXNoOmd4N3NxX3B4aHhocklRZEx5' +
- 'ZkcwcHhLd2lKN2hPazJESlE0eHZLZDQzOFEiLCJhbmRyb2lkUGFja2FnZU5hbWUiOiJjb20uZmlkby5leGFtcGxl' +
- 'LmZpZG8yYXBpZXhhbXBsZSJ9',
- },
- clientExtensionResults: {},
- type: 'public-key',
-};
-const attestationDPKChallenge = 'wg2dgjrr5ilcYmfCe-cNUZYoYiGIZ5t7HxY4y_x4oe8';