summaryrefslogtreecommitdiffhomepage
path: root/packages/server/src
diff options
context:
space:
mode:
authorEiji Kitamura <agektmr@google.com>2022-06-19 16:37:23 +0900
committerEiji Kitamura <agektmr@google.com>2022-07-19 15:04:32 +0900
commit88d03529a2019ec7be30f6cfe22e3a5a3af3e5d6 (patch)
treebf15bff0d0c543a183ebfd35f27687ec644a7ea1 /packages/server/src
parent97f64230bbdbf88412f9ec1ca4fff0e2d7545435 (diff)
Temporary working reg DPK impl
Diffstat (limited to 'packages/server/src')
-rw-r--r--packages/server/src/extensions/devicePubKey.test.ts1
-rw-r--r--packages/server/src/extensions/devicePubKey.ts120
-rw-r--r--packages/server/src/helpers/decodeExtensions.ts18
-rw-r--r--packages/server/src/registration/verifyRegistrationResponse.test.ts31
-rw-r--r--packages/server/src/registration/verifyRegistrationResponse.ts23
5 files changed, 62 insertions, 131 deletions
diff --git a/packages/server/src/extensions/devicePubKey.test.ts b/packages/server/src/extensions/devicePubKey.test.ts
deleted file mode 100644
index 10051c7..0000000
--- a/packages/server/src/extensions/devicePubKey.test.ts
+++ /dev/null
@@ -1 +0,0 @@
-// Placeholder
diff --git a/packages/server/src/extensions/devicePubKey.ts b/packages/server/src/extensions/devicePubKey.ts
deleted file mode 100644
index 163616b..0000000
--- a/packages/server/src/extensions/devicePubKey.ts
+++ /dev/null
@@ -1,120 +0,0 @@
-import cbor from 'cbor';
-import base64url from 'base64url';
-import { AttestationFormat, AttestationStatement } from '../helpers/decodeAttestationObject';
-import { RegistrationCredentialJSON } from '@simplewebauthn/typescript-types';
-import { CredentialPropertiesOutput, UvmEntries } from '@simplewebauthn/typescript-types';
-import { parseAuthenticatorData, verifySignature, decodeCredentialPublicKey } from 'helpers';
-import { COSEKEYS } from '../helpers/convertCOSEtoPKCS';
-
-export function decodeAttObjForDevicePublicKey(attObjForDevicePublicKey: Buffer): AttObjForDevicePublicKey {
- const toCBOR: AttObjForDevicePublicKey = cbor.decodeAllSync(attObjForDevicePublicKey)[0];
- return toCBOR;
-}
-
-export async function verifyAttObjForDevicePublicKey(
- credential: RegistrationCredentialJSON,
- attObjForDevicePublicKey: AttObjForDevicePublicKey,
- authData: Buffer,
- hash: Buffer
-): Promise<boolean> {
- const { credentialID, credentialPublicKey } = parseAuthenticatorData(authData);
- if (!credentialID) {
- throw new Error('No credential ID was provided by authenticator');
- }
- if (!credentialPublicKey) {
- throw new Error('No credential public key was provided by authenticator');
- }
- const decodedPublicKey = decodeCredentialPublicKey(credentialPublicKey);
- const alg = decodedPublicKey.get(COSEKEYS.alg);
-
- if (typeof alg !== 'number') {
- throw new Error('Credential public key was missing numeric alg');
- }
-
- // Make sure the key algorithm is one we specified within the registration options
- if (!supportedAlgorithmIDs.includes(alg as number)) {
- const supported = supportedAlgorithmIDs.join(', ');
- throw new Error(`Unexpected public key alg "${alg}", expected one of "${supported}"`);
- }
-
- const rootCertificates = settingsService.getRootCertificates({ identifier: fmt });
-
- const { sig, aaguid, dpk, nonce, fmt, attStmt } = attObjForDevicePublicKey;
-
- // Verify that `sig` is a valid signature over the concatenation of `hash` and
- // `credentialId` using the device public key `dpk` (the signature algorithm
- // is indicated by dpk’s "alg" COSEAlgorithmIdentifier value).
- const signatureBase = Buffer.concat([hash, credentialID]);
- verifySignature(sig, signatureBase, dpk);
-
- // Verify that `attStmt` is a correct attestation statement, conveying a valid
- // attestation signature, by using the attestation statement format `fmt`’s
- // verification procedure given `attStmt`, although substituting `aaguid`’s
- // value for `authenticatorData`, and substituting the concatenation of
- // `dpk`’s value and `nonce`’s value for `clientDataHash` in the attestation
- // statement format's verification procedure inputs.
- // Note: If `fmt’`s value is "none" there is no attestation signature to
- // verify.
- const clientDataHash = Buffer.concat([dpk, nonce]);
-
- // Prepare arguments to pass to the relevant verification method
- const verifierOpts: AttestationFormatVerifierOpts = {
- aaguid,
- attStmt,
- authData,
- clientDataHash,
- credentialID,
- credentialPublicKey,
- rootCertificates,
- rpIdHash,
- };
-
- /**
- * Verification can only be performed when attestation = 'direct'
- */
- let verified = false;
- if (fmt === 'fido-u2f') {
- verified = await verifyFIDOU2F(verifierOpts);
- } else if (fmt === 'packed') {
- verified = await verifyPacked(verifierOpts);
- } else if (fmt === 'android-safetynet') {
- verified = await verifyAndroidSafetynet(verifierOpts);
- } else if (fmt === 'android-key') {
- verified = await verifyAndroidKey(verifierOpts);
- } else if (fmt === 'tpm') {
- verified = await verifyTPM(verifierOpts);
- } else if (fmt === 'apple') {
- verified = await verifyApple(verifierOpts);
- } else if (fmt === 'none') {
- if (Object.keys(attStmt).length > 0) {
- throw new Error('None attestation had unexpected attestation statement');
- }
- // This is the weaker of the attestations, so there's nothing else to really check
- verified = true;
- } else {
- throw new Error(`Unsupported Attestation Format: ${fmt}`);
- }
-
-
- // Return the `aaguid`, `dpk`, `scope`, `fmt`, `attStmt` values indexed to the
- // `credential.id`.
-
- return true;
-}
-
-export type AttObjForDevicePublicKey = {
- sig: Buffer;
- aaguid: Buffer;
- dpk: Buffer;
- scope: number;
- nonce: Buffer;
- fmt: AttestationFormat;
- attStmt: AttestationStatement;
-};
-
-export type AuthenticationExtensionsClientOutputs = {
- appid?: boolean;
- credProps?: CredentialPropertiesOutput;
- uvm?: UvmEntries;
- devicePubKey?: AttObjForDevicePublicKey;
-};
diff --git a/packages/server/src/helpers/decodeExtensions.ts b/packages/server/src/helpers/decodeExtensions.ts
new file mode 100644
index 0000000..f1b5cb3
--- /dev/null
+++ b/packages/server/src/helpers/decodeExtensions.ts
@@ -0,0 +1,18 @@
+import cbor from 'cbor';
+
+/**
+ * Convert an extension data buffer to a proper object
+ *
+ * @param extensionDataBuffer Extension Data buffer
+ */
+export default function decodeExtensionDataBuffer(extensionDataBuffer: Buffer): ExtensionsJSON {
+ const toCBOR: ExtensionsJSON = cbor.decodeAllSync(extensionDataBuffer)[0];
+ return toCBOR;
+}
+
+export type ExtensionsJSON = {
+ 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 03f74ef..931ece1 100644
--- a/packages/server/src/registration/verifyRegistrationResponse.test.ts
+++ b/packages/server/src/registration/verifyRegistrationResponse.test.ts
@@ -580,6 +580,20 @@ test('should return credential backup info', async () => {
expect(verification.registrationInfo?.credentialBackedUp).toEqual(false);
});
+test('should return extension', async () => {
+ const verification = await verifyRegistrationResponse({
+ credential: attestationDPK,
+ expectedChallenge: attestationDPKChallenge,
+ 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'));
+});
+
/**
* Various Attestations Below
*/
@@ -668,3 +682,20 @@ 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';
diff --git a/packages/server/src/registration/verifyRegistrationResponse.ts b/packages/server/src/registration/verifyRegistrationResponse.ts
index 2cd4a86..899b624 100644
--- a/packages/server/src/registration/verifyRegistrationResponse.ts
+++ b/packages/server/src/registration/verifyRegistrationResponse.ts
@@ -9,7 +9,7 @@ import decodeAttestationObject, {
AttestationFormat,
AttestationStatement,
} from '../helpers/decodeAttestationObject';
-// import { decodeAttObjForDevicePublicKey, verifyAttObjForDevicePublicKey } from '../extensions/devicePubKey';
+import decodeExtensionDataBuffer, { ExtensionsJSON } from '../helpers/decodeExtensions';
import decodeClientDataJSON from '../helpers/decodeClientDataJSON';
import parseAuthenticatorData from '../helpers/parseAuthenticatorData';
import toHash from '../helpers/toHash';
@@ -135,6 +135,15 @@ export default async function verifyRegistrationResponse(
const parsedAuthData = parseAuthenticatorData(authData);
const { aaguid, rpIdHash, flags, credentialID, counter, credentialPublicKey, extensionsDataBuffer } = parsedAuthData;
+ let extensions: ExtensionsJSON = {};
+
+ // Temporarily assume that the extension is a DPK
+ // TODO: This needs to be keyed object: { devicePubKey: DPK }
+ if (flags.ed && extensionsDataBuffer) {
+ const devicePublicKey = decodeExtensionDataBuffer(extensionsDataBuffer);
+ extensions = devicePublicKey;
+ }
+
// Make sure the response's RP ID is ours
if (expectedRPID) {
if (typeof expectedRPID === 'string') {
@@ -193,15 +202,6 @@ export default async function verifyRegistrationResponse(
const clientDataHash = toHash(base64url.toBuffer(response.clientDataJSON));
const rootCertificates = settingsService.getRootCertificates({ identifier: fmt });
- // if (clientExtensionResults) {
- // if (clientExtensionResults.devicePubKey) {
- // const attObjForDevicePublicKey = decodeAttObjForDevicePublicKey(clientExtensionResults.devicePubKey);
- // if (!verifyAttObjForDevicePublicKey(credential, attObjForDevicePublicKey, authData, clientDataHash)) {
- // throw new Error('Invalid attestation object for device public key');
- // }
- // }
- // }
-
// Prepare arguments to pass to the relevant verification method
const verifierOpts: AttestationFormatVerifierOpts = {
aaguid,
@@ -258,6 +258,7 @@ export default async function verifyRegistrationResponse(
userVerified: flags.uv,
credentialDeviceType,
credentialBackedUp,
+ extensions,
};
}
@@ -284,6 +285,7 @@ export default async function verifyRegistrationResponse(
* @param registrationInfo.credentialBackedUp Whether or not the multi-device credential has been
* backed up. Always `false` for single-device credentials. **Should be kept in a DB for later
* reference!**
+ * @param registrationInfo?.extensions The extensions returned by the browser
*/
export type VerifiedRegistrationResponse = {
verified: boolean;
@@ -298,6 +300,7 @@ export type VerifiedRegistrationResponse = {
userVerified: boolean;
credentialDeviceType: CredentialDeviceType;
credentialBackedUp: boolean;
+ extensions?: ExtensionsJSON;
};
};