@simplewebauthn/server 10.0.1 → 11.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/esm/authentication/verifyAuthenticationResponse.d.ts +3 -3
- package/esm/authentication/verifyAuthenticationResponse.js +7 -7
- package/esm/deps.d.ts +3 -3
- package/esm/deps.js +2 -2
- package/esm/helpers/decodeAuthenticatorExtensions.d.ts +4 -14
- package/esm/helpers/validateExtFIDOGenCEAAGUID.d.ts +6 -0
- package/esm/helpers/validateExtFIDOGenCEAAGUID.js +34 -0
- package/esm/registration/verifications/tpm/constants.js +4 -0
- package/esm/registration/verifications/tpm/verifyAttestationTPM.js +9 -2
- package/esm/registration/verifications/verifyAttestationPacked.js +10 -3
- package/esm/registration/verifyRegistrationResponse.d.ts +4 -4
- package/esm/registration/verifyRegistrationResponse.js +11 -7
- package/package.json +2 -2
- package/script/authentication/verifyAuthenticationResponse.d.ts +3 -3
- package/script/authentication/verifyAuthenticationResponse.js +7 -7
- package/script/deps.d.ts +3 -3
- package/script/deps.js +3 -1
- package/script/helpers/decodeAuthenticatorExtensions.d.ts +4 -14
- package/script/helpers/validateExtFIDOGenCEAAGUID.d.ts +6 -0
- package/script/helpers/validateExtFIDOGenCEAAGUID.js +38 -0
- package/script/registration/verifications/tpm/constants.js +4 -0
- package/script/registration/verifications/tpm/verifyAttestationTPM.js +9 -2
- package/script/registration/verifications/verifyAttestationPacked.js +10 -3
- package/script/registration/verifyRegistrationResponse.d.ts +4 -4
- package/script/registration/verifyRegistrationResponse.js +11 -7
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import type { AuthenticationResponseJSON,
|
|
1
|
+
import type { AuthenticationResponseJSON, Base64URLString, CredentialDeviceType, UserVerificationRequirement, WebAuthnCredential } from '../deps.js';
|
|
2
2
|
import { AuthenticationExtensionsAuthenticatorOutputs } from '../helpers/decodeAuthenticatorExtensions.js';
|
|
3
3
|
export type VerifyAuthenticationResponseOpts = {
|
|
4
4
|
response: AuthenticationResponseJSON;
|
|
5
5
|
expectedChallenge: string | ((challenge: string) => boolean | Promise<boolean>);
|
|
6
6
|
expectedOrigin: string | string[];
|
|
7
7
|
expectedRPID: string | string[];
|
|
8
|
-
|
|
8
|
+
credential: WebAuthnCredential;
|
|
9
9
|
expectedType?: string | string[];
|
|
10
10
|
requireUserVerification?: boolean;
|
|
11
11
|
advancedFIDOConfig?: {
|
|
@@ -21,7 +21,7 @@ export type VerifyAuthenticationResponseOpts = {
|
|
|
21
21
|
* @param expectedChallenge - The base64url-encoded `options.challenge` returned by `generateAuthenticationOptions()`
|
|
22
22
|
* @param expectedOrigin - Website URL (or array of URLs) that the registration should have occurred on
|
|
23
23
|
* @param expectedRPID - RP ID (or array of IDs) that was specified in the registration options
|
|
24
|
-
* @param
|
|
24
|
+
* @param credential - An internal {@link WebAuthnCredential} corresponding to `id` in the authentication response
|
|
25
25
|
* @param expectedType **(Optional)** - The response type expected ('webauthn.get')
|
|
26
26
|
* @param requireUserVerification **(Optional)** - Enforce user verification by the authenticator (via PIN, fingerprint, etc...) Defaults to `true`
|
|
27
27
|
* @param advancedFIDOConfig **(Optional)** - Options for satisfying more stringent FIDO RP feature requirements
|
|
@@ -14,14 +14,14 @@ import { isoBase64URL, isoUint8Array } from '../helpers/iso/index.js';
|
|
|
14
14
|
* @param expectedChallenge - The base64url-encoded `options.challenge` returned by `generateAuthenticationOptions()`
|
|
15
15
|
* @param expectedOrigin - Website URL (or array of URLs) that the registration should have occurred on
|
|
16
16
|
* @param expectedRPID - RP ID (or array of IDs) that was specified in the registration options
|
|
17
|
-
* @param
|
|
17
|
+
* @param credential - An internal {@link WebAuthnCredential} corresponding to `id` in the authentication response
|
|
18
18
|
* @param expectedType **(Optional)** - The response type expected ('webauthn.get')
|
|
19
19
|
* @param requireUserVerification **(Optional)** - Enforce user verification by the authenticator (via PIN, fingerprint, etc...) Defaults to `true`
|
|
20
20
|
* @param advancedFIDOConfig **(Optional)** - Options for satisfying more stringent FIDO RP feature requirements
|
|
21
21
|
* @param advancedFIDOConfig.userVerification **(Optional)** - Enable alternative rules for evaluating the User Presence and User Verified flags in authenticator data: UV (and UP) flags are optional unless this value is `"required"`
|
|
22
22
|
*/
|
|
23
23
|
export async function verifyAuthenticationResponse(options) {
|
|
24
|
-
const { response, expectedChallenge, expectedOrigin, expectedRPID, expectedType,
|
|
24
|
+
const { response, expectedChallenge, expectedOrigin, expectedRPID, expectedType, credential, requireUserVerification = true, advancedFIDOConfig, } = options;
|
|
25
25
|
const { id, rawId, type: credentialType, response: assertionResponse } = response;
|
|
26
26
|
// Ensure credential specified an ID
|
|
27
27
|
if (!id) {
|
|
@@ -141,24 +141,24 @@ export async function verifyAuthenticationResponse(options) {
|
|
|
141
141
|
const clientDataHash = await toHash(isoBase64URL.toBuffer(assertionResponse.clientDataJSON));
|
|
142
142
|
const signatureBase = isoUint8Array.concat([authDataBuffer, clientDataHash]);
|
|
143
143
|
const signature = isoBase64URL.toBuffer(assertionResponse.signature);
|
|
144
|
-
if ((counter > 0 ||
|
|
145
|
-
counter <=
|
|
144
|
+
if ((counter > 0 || credential.counter > 0) &&
|
|
145
|
+
counter <= credential.counter) {
|
|
146
146
|
// Error out when the counter in the DB is greater than or equal to the counter in the
|
|
147
147
|
// dataStruct. It's related to how the authenticator maintains the number of times its been
|
|
148
148
|
// used for this client. If this happens, then someone's somehow increased the counter
|
|
149
149
|
// on the device without going through this site
|
|
150
|
-
throw new Error(`Response counter value ${counter} was lower than expected ${
|
|
150
|
+
throw new Error(`Response counter value ${counter} was lower than expected ${credential.counter}`);
|
|
151
151
|
}
|
|
152
152
|
const { credentialDeviceType, credentialBackedUp } = parseBackupFlags(flags);
|
|
153
153
|
const toReturn = {
|
|
154
154
|
verified: await verifySignature({
|
|
155
155
|
signature,
|
|
156
156
|
data: signatureBase,
|
|
157
|
-
credentialPublicKey:
|
|
157
|
+
credentialPublicKey: credential.publicKey,
|
|
158
158
|
}),
|
|
159
159
|
authenticationInfo: {
|
|
160
160
|
newCounter: counter,
|
|
161
|
-
credentialID:
|
|
161
|
+
credentialID: credential.id,
|
|
162
162
|
userVerified: flags.uv,
|
|
163
163
|
credentialDeviceType,
|
|
164
164
|
credentialBackedUp,
|
package/esm/deps.d.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
export type { AttestationConveyancePreference, AuthenticationExtensionsClientInputs, AuthenticationResponseJSON,
|
|
1
|
+
export type { AttestationConveyancePreference, AuthenticationExtensionsClientInputs, AuthenticationResponseJSON, AuthenticatorSelectionCriteria, AuthenticatorTransportFuture, Base64URLString, COSEAlgorithmIdentifier, CredentialDeviceType, Crypto, PublicKeyCredentialCreationOptionsJSON, PublicKeyCredentialParameters, PublicKeyCredentialRequestOptionsJSON, RegistrationResponseJSON, UserVerificationRequirement, WebAuthnCredential, } from '@simplewebauthn/types';
|
|
2
2
|
export * as tinyCbor from '@levischuck/tiny-cbor';
|
|
3
3
|
export { default as base64 } from '@hexagon/base64';
|
|
4
4
|
export { fetch as crossFetch } from 'cross-fetch';
|
|
5
|
-
export { AsnParser, AsnSerializer } from '@peculiar/asn1-schema';
|
|
6
|
-
export { AuthorityKeyIdentifier, BasicConstraints, Certificate, CertificateList, CRLDistributionPoints, ExtendedKeyUsage, id_ce_authorityKeyIdentifier, id_ce_basicConstraints, id_ce_cRLDistributionPoints, id_ce_extKeyUsage, id_ce_subjectAltName, id_ce_subjectKeyIdentifier, Name, SubjectAlternativeName, SubjectKeyIdentifier, } from '@peculiar/asn1-x509';
|
|
5
|
+
export { AsnParser, AsnSerializer, OctetString } from '@peculiar/asn1-schema';
|
|
6
|
+
export { AuthorityKeyIdentifier, BasicConstraints, Certificate, CertificateList, CRLDistributionPoints, ExtendedKeyUsage, Extensions, id_ce_authorityKeyIdentifier, id_ce_basicConstraints, id_ce_cRLDistributionPoints, id_ce_extKeyUsage, id_ce_subjectAltName, id_ce_subjectKeyIdentifier, Name, SubjectAlternativeName, SubjectKeyIdentifier, } from '@peculiar/asn1-x509';
|
|
7
7
|
export { ECDSASigValue, ECParameters, id_ecPublicKey, id_secp256r1, id_secp384r1, } from '@peculiar/asn1-ecc';
|
|
8
8
|
export { RSAPublicKey } from '@peculiar/asn1-rsa';
|
|
9
9
|
export { id_ce_keyDescription, KeyDescription } from '@peculiar/asn1-android';
|
package/esm/deps.js
CHANGED
|
@@ -5,8 +5,8 @@ export { default as base64 } from '@hexagon/base64';
|
|
|
5
5
|
// cross-fetch
|
|
6
6
|
export { fetch as crossFetch } from 'cross-fetch';
|
|
7
7
|
// @peculiar libraries
|
|
8
|
-
export { AsnParser, AsnSerializer } from '@peculiar/asn1-schema';
|
|
9
|
-
export { AuthorityKeyIdentifier, BasicConstraints, Certificate, CertificateList, CRLDistributionPoints, ExtendedKeyUsage, id_ce_authorityKeyIdentifier, id_ce_basicConstraints, id_ce_cRLDistributionPoints, id_ce_extKeyUsage, id_ce_subjectAltName, id_ce_subjectKeyIdentifier, Name, SubjectAlternativeName, SubjectKeyIdentifier, } from '@peculiar/asn1-x509';
|
|
8
|
+
export { AsnParser, AsnSerializer, OctetString } from '@peculiar/asn1-schema';
|
|
9
|
+
export { AuthorityKeyIdentifier, BasicConstraints, Certificate, CertificateList, CRLDistributionPoints, ExtendedKeyUsage, Extensions, id_ce_authorityKeyIdentifier, id_ce_basicConstraints, id_ce_cRLDistributionPoints, id_ce_extKeyUsage, id_ce_subjectAltName, id_ce_subjectKeyIdentifier, Name, SubjectAlternativeName, SubjectKeyIdentifier, } from '@peculiar/asn1-x509';
|
|
10
10
|
export { ECDSASigValue, ECParameters, id_ecPublicKey, id_secp256r1, id_secp384r1, } from '@peculiar/asn1-ecc';
|
|
11
11
|
export { RSAPublicKey } from '@peculiar/asn1-rsa';
|
|
12
12
|
export { id_ce_keyDescription, KeyDescription } from '@peculiar/asn1-android';
|
|
@@ -4,17 +4,7 @@
|
|
|
4
4
|
* @param extensionData Authenticator Extension Data buffer
|
|
5
5
|
*/
|
|
6
6
|
export declare function decodeAuthenticatorExtensions(extensionData: Uint8Array): AuthenticationExtensionsAuthenticatorOutputs | undefined;
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
export type DevicePublicKeyAuthenticatorOutput = {
|
|
12
|
-
dpk?: Uint8Array;
|
|
13
|
-
sig?: string;
|
|
14
|
-
nonce?: Uint8Array;
|
|
15
|
-
scope?: Uint8Array;
|
|
16
|
-
aaguid?: Uint8Array;
|
|
17
|
-
};
|
|
18
|
-
export type UVMAuthenticatorOutput = {
|
|
19
|
-
uvm?: Uint8Array[];
|
|
20
|
-
};
|
|
7
|
+
/**
|
|
8
|
+
* Attempt to support authenticator extensions we might not know about in WebAuthn
|
|
9
|
+
*/
|
|
10
|
+
export type AuthenticationExtensionsAuthenticatorOutputs = unknown;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { Extensions } from '../deps.js';
|
|
2
|
+
/**
|
|
3
|
+
* Look for the id-fido-gen-ce-aaguid certificate extension. If it's present then check it against
|
|
4
|
+
* the attestation statement AAGUID.
|
|
5
|
+
*/
|
|
6
|
+
export declare function validateExtFIDOGenCEAAGUID(certExtensions: Extensions | undefined, aaguid: Uint8Array): boolean;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { AsnParser, OctetString } from '../deps.js';
|
|
2
|
+
import { isoUint8Array } from './iso/index.js';
|
|
3
|
+
/**
|
|
4
|
+
* Attestation Certificate Extension OID: `id-fido-gen-ce-aaguid`
|
|
5
|
+
*
|
|
6
|
+
* Sourced from https://fidoalliance.org/specs/fido-v2.0-ps-20150904/fido-key-attestation-v2.0-ps-20150904.html#verifying-an-attestation-statement
|
|
7
|
+
*/
|
|
8
|
+
const id_fido_gen_ce_aaguid = '1.3.6.1.4.1.45724.1.1.4';
|
|
9
|
+
/**
|
|
10
|
+
* Look for the id-fido-gen-ce-aaguid certificate extension. If it's present then check it against
|
|
11
|
+
* the attestation statement AAGUID.
|
|
12
|
+
*/
|
|
13
|
+
export function validateExtFIDOGenCEAAGUID(certExtensions, aaguid) {
|
|
14
|
+
// The certificate had no extensions so there's nothing to validate
|
|
15
|
+
if (!certExtensions) {
|
|
16
|
+
return true;
|
|
17
|
+
}
|
|
18
|
+
const extFIDOGenCEAAGUID = certExtensions.find((ext) => ext.extnID === id_fido_gen_ce_aaguid);
|
|
19
|
+
// The extension isn't present so there's nothing to validate
|
|
20
|
+
if (!extFIDOGenCEAAGUID) {
|
|
21
|
+
return true;
|
|
22
|
+
}
|
|
23
|
+
// Parse the extension value
|
|
24
|
+
const parsedExtFIDOGenCEAAGUID = AsnParser.parse(extFIDOGenCEAAGUID.extnValue, OctetString);
|
|
25
|
+
const extValue = new Uint8Array(parsedExtFIDOGenCEAAGUID.buffer);
|
|
26
|
+
// Compare the two values
|
|
27
|
+
const aaguidAndExtAreEqual = isoUint8Array.areEqual(aaguid, extValue);
|
|
28
|
+
if (!aaguidAndExtAreEqual) {
|
|
29
|
+
const _debugExtHex = isoUint8Array.toHex(extValue);
|
|
30
|
+
const _debugAAGUIDHex = isoUint8Array.toHex(aaguid);
|
|
31
|
+
throw new Error(`Certificate extension id-fido-gen-ce-aaguid (${id_fido_gen_ce_aaguid}) value of "${_debugExtHex}" was present but not equal to attestation statement AAGUID value of "${_debugAAGUIDHex}"`);
|
|
32
|
+
}
|
|
33
|
+
return true;
|
|
34
|
+
}
|
|
@@ -7,6 +7,7 @@ import { validateCertificatePath } from '../../../helpers/validateCertificatePat
|
|
|
7
7
|
import { getCertificateInfo } from '../../../helpers/getCertificateInfo.js';
|
|
8
8
|
import { verifySignature } from '../../../helpers/verifySignature.js';
|
|
9
9
|
import { isoUint8Array } from '../../../helpers/iso/index.js';
|
|
10
|
+
import { validateExtFIDOGenCEAAGUID } from '../../../helpers/validateExtFIDOGenCEAAGUID.js';
|
|
10
11
|
import { MetadataService } from '../../../services/metadataService.js';
|
|
11
12
|
import { verifyAttestationWithMetadata } from '../../../metadata/verifyAttestationWithMetadata.js';
|
|
12
13
|
import { TPM_ECC_CURVE_COSE_CRV_MAP, TPM_MANUFACTURERS } from './constants.js';
|
|
@@ -206,8 +207,14 @@ export async function verifyAttestationTPM(options) {
|
|
|
206
207
|
if (extKeyUsage[0] !== '2.23.133.8.3') {
|
|
207
208
|
throw new Error(`Unexpected extKeyUsage "${extKeyUsage[0]}", expected "2.23.133.8.3" (TPM)`);
|
|
208
209
|
}
|
|
209
|
-
//
|
|
210
|
-
|
|
210
|
+
// Validate attestation statement AAGUID against leaf cert AAGUID
|
|
211
|
+
try {
|
|
212
|
+
await validateExtFIDOGenCEAAGUID(parsedCert.tbsCertificate.extensions, aaguid);
|
|
213
|
+
}
|
|
214
|
+
catch (err) {
|
|
215
|
+
const _err = err;
|
|
216
|
+
throw new Error(`${_err.message} (TPM)`);
|
|
217
|
+
}
|
|
211
218
|
// Run some metadata checks if a statement exists for this authenticator
|
|
212
219
|
const statement = await MetadataService.getStatement(aaguid);
|
|
213
220
|
if (statement) {
|
|
@@ -4,6 +4,7 @@ import { validateCertificatePath } from '../../helpers/validateCertificatePath.j
|
|
|
4
4
|
import { getCertificateInfo } from '../../helpers/getCertificateInfo.js';
|
|
5
5
|
import { verifySignature } from '../../helpers/verifySignature.js';
|
|
6
6
|
import { isoUint8Array } from '../../helpers/iso/index.js';
|
|
7
|
+
import { validateExtFIDOGenCEAAGUID } from '../../helpers/validateExtFIDOGenCEAAGUID.js';
|
|
7
8
|
import { MetadataService } from '../../services/metadataService.js';
|
|
8
9
|
import { verifyAttestationWithMetadata } from '../../metadata/verifyAttestationWithMetadata.js';
|
|
9
10
|
/**
|
|
@@ -26,7 +27,7 @@ export async function verifyAttestationPacked(options) {
|
|
|
26
27
|
const signatureBase = isoUint8Array.concat([authData, clientDataHash]);
|
|
27
28
|
let verified = false;
|
|
28
29
|
if (x5c) {
|
|
29
|
-
const { subject, basicConstraintsCA, version, notBefore, notAfter } = getCertificateInfo(x5c[0]);
|
|
30
|
+
const { subject, basicConstraintsCA, version, notBefore, notAfter, parsedCertificate, } = getCertificateInfo(x5c[0]);
|
|
30
31
|
const { OU, CN, O, C } = subject;
|
|
31
32
|
if (OU !== 'Authenticator Attestation') {
|
|
32
33
|
throw new Error('Certificate OU was not "Authenticator Attestation" (Packed|Full)');
|
|
@@ -54,8 +55,14 @@ export async function verifyAttestationPacked(options) {
|
|
|
54
55
|
if (notAfter < now) {
|
|
55
56
|
throw new Error(`Certificate not good after "${notAfter.toString()}" (Packed|Full)`);
|
|
56
57
|
}
|
|
57
|
-
//
|
|
58
|
-
|
|
58
|
+
// Validate attestation statement AAGUID against leaf cert AAGUID
|
|
59
|
+
try {
|
|
60
|
+
await validateExtFIDOGenCEAAGUID(parsedCertificate.tbsCertificate.extensions, aaguid);
|
|
61
|
+
}
|
|
62
|
+
catch (err) {
|
|
63
|
+
const _err = err;
|
|
64
|
+
throw new Error(`${_err.message} (Packed|Full)`);
|
|
65
|
+
}
|
|
59
66
|
// If available, validate attestation alg and x5c with info in the metadata statement
|
|
60
67
|
const statement = await MetadataService.getStatement(aaguid);
|
|
61
68
|
if (statement) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { COSEAlgorithmIdentifier, CredentialDeviceType, RegistrationResponseJSON, WebAuthnCredential } from '../deps.js';
|
|
2
2
|
import { AttestationFormat, AttestationStatement } from '../helpers/decodeAttestationObject.js';
|
|
3
3
|
import { AuthenticationExtensionsAuthenticatorOutputs } from '../helpers/decodeAuthenticatorExtensions.js';
|
|
4
4
|
export type VerifyRegistrationResponseOpts = {
|
|
@@ -7,6 +7,7 @@ export type VerifyRegistrationResponseOpts = {
|
|
|
7
7
|
expectedOrigin: string | string[];
|
|
8
8
|
expectedRPID?: string | string[];
|
|
9
9
|
expectedType?: string | string[];
|
|
10
|
+
requireUserPresence?: boolean;
|
|
10
11
|
requireUserVerification?: boolean;
|
|
11
12
|
supportedAlgorithmIDs?: COSEAlgorithmIdentifier[];
|
|
12
13
|
};
|
|
@@ -20,6 +21,7 @@ export type VerifyRegistrationResponseOpts = {
|
|
|
20
21
|
* @param expectedOrigin - Website URL (or array of URLs) that the registration should have occurred on
|
|
21
22
|
* @param expectedRPID - RP ID (or array of IDs) that was specified in the registration options
|
|
22
23
|
* @param expectedType **(Optional)** - The response type expected ('webauthn.create')
|
|
24
|
+
* @param requireUserPresence **(Optional)** - Enforce user presence by the authenticator (or skip it during auto registration) Defaults to `true`
|
|
23
25
|
* @param requireUserVerification **(Optional)** - Enforce user verification by the authenticator (via PIN, fingerprint, etc...) Defaults to `true`
|
|
24
26
|
* @param supportedAlgorithmIDs **(Optional)** - Array of numeric COSE algorithm identifiers supported for attestation by this RP. See https://www.iana.org/assignments/cose/cose.xhtml#algorithms. Defaults to all supported algorithm IDs
|
|
25
27
|
*/
|
|
@@ -54,10 +56,8 @@ export type VerifiedRegistrationResponse = {
|
|
|
54
56
|
verified: boolean;
|
|
55
57
|
registrationInfo?: {
|
|
56
58
|
fmt: AttestationFormat;
|
|
57
|
-
counter: number;
|
|
58
59
|
aaguid: string;
|
|
59
|
-
|
|
60
|
-
credentialPublicKey: Uint8Array;
|
|
60
|
+
credential: WebAuthnCredential;
|
|
61
61
|
credentialType: 'public-key';
|
|
62
62
|
attestationObject: Uint8Array;
|
|
63
63
|
userVerified: boolean;
|
|
@@ -26,11 +26,12 @@ import { verifyAttestationApple } from './verifications/verifyAttestationApple.j
|
|
|
26
26
|
* @param expectedOrigin - Website URL (or array of URLs) that the registration should have occurred on
|
|
27
27
|
* @param expectedRPID - RP ID (or array of IDs) that was specified in the registration options
|
|
28
28
|
* @param expectedType **(Optional)** - The response type expected ('webauthn.create')
|
|
29
|
+
* @param requireUserPresence **(Optional)** - Enforce user presence by the authenticator (or skip it during auto registration) Defaults to `true`
|
|
29
30
|
* @param requireUserVerification **(Optional)** - Enforce user verification by the authenticator (via PIN, fingerprint, etc...) Defaults to `true`
|
|
30
31
|
* @param supportedAlgorithmIDs **(Optional)** - Array of numeric COSE algorithm identifiers supported for attestation by this RP. See https://www.iana.org/assignments/cose/cose.xhtml#algorithms. Defaults to all supported algorithm IDs
|
|
31
32
|
*/
|
|
32
33
|
export async function verifyRegistrationResponse(options) {
|
|
33
|
-
const { response, expectedChallenge, expectedOrigin, expectedRPID, expectedType, requireUserVerification = true, supportedAlgorithmIDs = supportedCOSEAlgorithmIdentifiers, } = options;
|
|
34
|
+
const { response, expectedChallenge, expectedOrigin, expectedRPID, expectedType, requireUserPresence = true, requireUserVerification = true, supportedAlgorithmIDs = supportedCOSEAlgorithmIdentifiers, } = options;
|
|
34
35
|
const { id, rawId, type: credentialType, response: attestationResponse } = response;
|
|
35
36
|
// Ensure credential specified an ID
|
|
36
37
|
if (!id) {
|
|
@@ -109,12 +110,12 @@ export async function verifyRegistrationResponse(options) {
|
|
|
109
110
|
matchedRPID = await matchExpectedRPID(rpIdHash, expectedRPIDs);
|
|
110
111
|
}
|
|
111
112
|
// Make sure someone was physically present
|
|
112
|
-
if (!flags.up) {
|
|
113
|
-
throw new Error('User not present
|
|
113
|
+
if (requireUserPresence && !flags.up) {
|
|
114
|
+
throw new Error('User presence was required, but user was not present');
|
|
114
115
|
}
|
|
115
116
|
// Enforce user verification if specified
|
|
116
117
|
if (requireUserVerification && !flags.uv) {
|
|
117
|
-
throw new Error('User verification required, but user could not be verified');
|
|
118
|
+
throw new Error('User verification was required, but user could not be verified');
|
|
118
119
|
}
|
|
119
120
|
if (!credentialID) {
|
|
120
121
|
throw new Error('No credential ID was provided by authenticator');
|
|
@@ -189,11 +190,14 @@ export async function verifyRegistrationResponse(options) {
|
|
|
189
190
|
const { credentialDeviceType, credentialBackedUp } = parseBackupFlags(flags);
|
|
190
191
|
toReturn.registrationInfo = {
|
|
191
192
|
fmt,
|
|
192
|
-
counter,
|
|
193
193
|
aaguid: convertAAGUIDToString(aaguid),
|
|
194
|
-
credentialID: isoBase64URL.fromBuffer(credentialID),
|
|
195
|
-
credentialPublicKey,
|
|
196
194
|
credentialType,
|
|
195
|
+
credential: {
|
|
196
|
+
id: isoBase64URL.fromBuffer(credentialID),
|
|
197
|
+
publicKey: credentialPublicKey,
|
|
198
|
+
counter,
|
|
199
|
+
transports: response.response.transports,
|
|
200
|
+
},
|
|
197
201
|
attestationObject,
|
|
198
202
|
userVerified: flags.uv,
|
|
199
203
|
credentialDeviceType,
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"module": "./esm/index.js",
|
|
3
3
|
"main": "./script/index.js",
|
|
4
4
|
"name": "@simplewebauthn/server",
|
|
5
|
-
"version": "
|
|
5
|
+
"version": "11.0.0",
|
|
6
6
|
"description": "SimpleWebAuthn for Servers",
|
|
7
7
|
"license": "MIT",
|
|
8
8
|
"author": "Matthew Miller <matthew@millerti.me>",
|
|
@@ -56,7 +56,7 @@
|
|
|
56
56
|
"@peculiar/asn1-rsa": "^2.3.8",
|
|
57
57
|
"@peculiar/asn1-schema": "^2.3.8",
|
|
58
58
|
"@peculiar/asn1-x509": "^2.3.8",
|
|
59
|
-
"@simplewebauthn/types": "^
|
|
59
|
+
"@simplewebauthn/types": "^11.0.0",
|
|
60
60
|
"cross-fetch": "^4.0.0"
|
|
61
61
|
},
|
|
62
62
|
"devDependencies": {
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import type { AuthenticationResponseJSON,
|
|
1
|
+
import type { AuthenticationResponseJSON, Base64URLString, CredentialDeviceType, UserVerificationRequirement, WebAuthnCredential } from '../deps.js';
|
|
2
2
|
import { AuthenticationExtensionsAuthenticatorOutputs } from '../helpers/decodeAuthenticatorExtensions.js';
|
|
3
3
|
export type VerifyAuthenticationResponseOpts = {
|
|
4
4
|
response: AuthenticationResponseJSON;
|
|
5
5
|
expectedChallenge: string | ((challenge: string) => boolean | Promise<boolean>);
|
|
6
6
|
expectedOrigin: string | string[];
|
|
7
7
|
expectedRPID: string | string[];
|
|
8
|
-
|
|
8
|
+
credential: WebAuthnCredential;
|
|
9
9
|
expectedType?: string | string[];
|
|
10
10
|
requireUserVerification?: boolean;
|
|
11
11
|
advancedFIDOConfig?: {
|
|
@@ -21,7 +21,7 @@ export type VerifyAuthenticationResponseOpts = {
|
|
|
21
21
|
* @param expectedChallenge - The base64url-encoded `options.challenge` returned by `generateAuthenticationOptions()`
|
|
22
22
|
* @param expectedOrigin - Website URL (or array of URLs) that the registration should have occurred on
|
|
23
23
|
* @param expectedRPID - RP ID (or array of IDs) that was specified in the registration options
|
|
24
|
-
* @param
|
|
24
|
+
* @param credential - An internal {@link WebAuthnCredential} corresponding to `id` in the authentication response
|
|
25
25
|
* @param expectedType **(Optional)** - The response type expected ('webauthn.get')
|
|
26
26
|
* @param requireUserVerification **(Optional)** - Enforce user verification by the authenticator (via PIN, fingerprint, etc...) Defaults to `true`
|
|
27
27
|
* @param advancedFIDOConfig **(Optional)** - Options for satisfying more stringent FIDO RP feature requirements
|
|
@@ -17,14 +17,14 @@ const index_js_1 = require("../helpers/iso/index.js");
|
|
|
17
17
|
* @param expectedChallenge - The base64url-encoded `options.challenge` returned by `generateAuthenticationOptions()`
|
|
18
18
|
* @param expectedOrigin - Website URL (or array of URLs) that the registration should have occurred on
|
|
19
19
|
* @param expectedRPID - RP ID (or array of IDs) that was specified in the registration options
|
|
20
|
-
* @param
|
|
20
|
+
* @param credential - An internal {@link WebAuthnCredential} corresponding to `id` in the authentication response
|
|
21
21
|
* @param expectedType **(Optional)** - The response type expected ('webauthn.get')
|
|
22
22
|
* @param requireUserVerification **(Optional)** - Enforce user verification by the authenticator (via PIN, fingerprint, etc...) Defaults to `true`
|
|
23
23
|
* @param advancedFIDOConfig **(Optional)** - Options for satisfying more stringent FIDO RP feature requirements
|
|
24
24
|
* @param advancedFIDOConfig.userVerification **(Optional)** - Enable alternative rules for evaluating the User Presence and User Verified flags in authenticator data: UV (and UP) flags are optional unless this value is `"required"`
|
|
25
25
|
*/
|
|
26
26
|
async function verifyAuthenticationResponse(options) {
|
|
27
|
-
const { response, expectedChallenge, expectedOrigin, expectedRPID, expectedType,
|
|
27
|
+
const { response, expectedChallenge, expectedOrigin, expectedRPID, expectedType, credential, requireUserVerification = true, advancedFIDOConfig, } = options;
|
|
28
28
|
const { id, rawId, type: credentialType, response: assertionResponse } = response;
|
|
29
29
|
// Ensure credential specified an ID
|
|
30
30
|
if (!id) {
|
|
@@ -144,24 +144,24 @@ async function verifyAuthenticationResponse(options) {
|
|
|
144
144
|
const clientDataHash = await (0, toHash_js_1.toHash)(index_js_1.isoBase64URL.toBuffer(assertionResponse.clientDataJSON));
|
|
145
145
|
const signatureBase = index_js_1.isoUint8Array.concat([authDataBuffer, clientDataHash]);
|
|
146
146
|
const signature = index_js_1.isoBase64URL.toBuffer(assertionResponse.signature);
|
|
147
|
-
if ((counter > 0 ||
|
|
148
|
-
counter <=
|
|
147
|
+
if ((counter > 0 || credential.counter > 0) &&
|
|
148
|
+
counter <= credential.counter) {
|
|
149
149
|
// Error out when the counter in the DB is greater than or equal to the counter in the
|
|
150
150
|
// dataStruct. It's related to how the authenticator maintains the number of times its been
|
|
151
151
|
// used for this client. If this happens, then someone's somehow increased the counter
|
|
152
152
|
// on the device without going through this site
|
|
153
|
-
throw new Error(`Response counter value ${counter} was lower than expected ${
|
|
153
|
+
throw new Error(`Response counter value ${counter} was lower than expected ${credential.counter}`);
|
|
154
154
|
}
|
|
155
155
|
const { credentialDeviceType, credentialBackedUp } = (0, parseBackupFlags_js_1.parseBackupFlags)(flags);
|
|
156
156
|
const toReturn = {
|
|
157
157
|
verified: await (0, verifySignature_js_1.verifySignature)({
|
|
158
158
|
signature,
|
|
159
159
|
data: signatureBase,
|
|
160
|
-
credentialPublicKey:
|
|
160
|
+
credentialPublicKey: credential.publicKey,
|
|
161
161
|
}),
|
|
162
162
|
authenticationInfo: {
|
|
163
163
|
newCounter: counter,
|
|
164
|
-
credentialID:
|
|
164
|
+
credentialID: credential.id,
|
|
165
165
|
userVerified: flags.uv,
|
|
166
166
|
credentialDeviceType,
|
|
167
167
|
credentialBackedUp,
|
package/script/deps.d.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
export type { AttestationConveyancePreference, AuthenticationExtensionsClientInputs, AuthenticationResponseJSON,
|
|
1
|
+
export type { AttestationConveyancePreference, AuthenticationExtensionsClientInputs, AuthenticationResponseJSON, AuthenticatorSelectionCriteria, AuthenticatorTransportFuture, Base64URLString, COSEAlgorithmIdentifier, CredentialDeviceType, Crypto, PublicKeyCredentialCreationOptionsJSON, PublicKeyCredentialParameters, PublicKeyCredentialRequestOptionsJSON, RegistrationResponseJSON, UserVerificationRequirement, WebAuthnCredential, } from '@simplewebauthn/types';
|
|
2
2
|
export * as tinyCbor from '@levischuck/tiny-cbor';
|
|
3
3
|
export { default as base64 } from '@hexagon/base64';
|
|
4
4
|
export { fetch as crossFetch } from 'cross-fetch';
|
|
5
|
-
export { AsnParser, AsnSerializer } from '@peculiar/asn1-schema';
|
|
6
|
-
export { AuthorityKeyIdentifier, BasicConstraints, Certificate, CertificateList, CRLDistributionPoints, ExtendedKeyUsage, id_ce_authorityKeyIdentifier, id_ce_basicConstraints, id_ce_cRLDistributionPoints, id_ce_extKeyUsage, id_ce_subjectAltName, id_ce_subjectKeyIdentifier, Name, SubjectAlternativeName, SubjectKeyIdentifier, } from '@peculiar/asn1-x509';
|
|
5
|
+
export { AsnParser, AsnSerializer, OctetString } from '@peculiar/asn1-schema';
|
|
6
|
+
export { AuthorityKeyIdentifier, BasicConstraints, Certificate, CertificateList, CRLDistributionPoints, ExtendedKeyUsage, Extensions, id_ce_authorityKeyIdentifier, id_ce_basicConstraints, id_ce_cRLDistributionPoints, id_ce_extKeyUsage, id_ce_subjectAltName, id_ce_subjectKeyIdentifier, Name, SubjectAlternativeName, SubjectKeyIdentifier, } from '@peculiar/asn1-x509';
|
|
7
7
|
export { ECDSASigValue, ECParameters, id_ecPublicKey, id_secp256r1, id_secp384r1, } from '@peculiar/asn1-ecc';
|
|
8
8
|
export { RSAPublicKey } from '@peculiar/asn1-rsa';
|
|
9
9
|
export { id_ce_keyDescription, KeyDescription } from '@peculiar/asn1-android';
|
package/script/deps.js
CHANGED
|
@@ -26,7 +26,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
26
26
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
27
27
|
};
|
|
28
28
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
29
|
-
exports.KeyDescription = exports.id_ce_keyDescription = exports.RSAPublicKey = exports.id_secp384r1 = exports.id_secp256r1 = exports.id_ecPublicKey = exports.ECParameters = exports.ECDSASigValue = exports.SubjectKeyIdentifier = exports.SubjectAlternativeName = exports.Name = exports.id_ce_subjectKeyIdentifier = exports.id_ce_subjectAltName = exports.id_ce_extKeyUsage = exports.id_ce_cRLDistributionPoints = exports.id_ce_basicConstraints = exports.id_ce_authorityKeyIdentifier = exports.ExtendedKeyUsage = exports.CRLDistributionPoints = exports.CertificateList = exports.Certificate = exports.BasicConstraints = exports.AuthorityKeyIdentifier = exports.AsnSerializer = exports.AsnParser = exports.crossFetch = exports.base64 = exports.tinyCbor = void 0;
|
|
29
|
+
exports.KeyDescription = exports.id_ce_keyDescription = exports.RSAPublicKey = exports.id_secp384r1 = exports.id_secp256r1 = exports.id_ecPublicKey = exports.ECParameters = exports.ECDSASigValue = exports.SubjectKeyIdentifier = exports.SubjectAlternativeName = exports.Name = exports.id_ce_subjectKeyIdentifier = exports.id_ce_subjectAltName = exports.id_ce_extKeyUsage = exports.id_ce_cRLDistributionPoints = exports.id_ce_basicConstraints = exports.id_ce_authorityKeyIdentifier = exports.Extensions = exports.ExtendedKeyUsage = exports.CRLDistributionPoints = exports.CertificateList = exports.Certificate = exports.BasicConstraints = exports.AuthorityKeyIdentifier = exports.OctetString = exports.AsnSerializer = exports.AsnParser = exports.crossFetch = exports.base64 = exports.tinyCbor = void 0;
|
|
30
30
|
// tiny_cbor (a.k.a. tiny-cbor in Node land)
|
|
31
31
|
exports.tinyCbor = __importStar(require("@levischuck/tiny-cbor"));
|
|
32
32
|
// b64 (a.k.a. @hexagon/base64 in Node land)
|
|
@@ -39,6 +39,7 @@ Object.defineProperty(exports, "crossFetch", { enumerable: true, get: function (
|
|
|
39
39
|
var asn1_schema_1 = require("@peculiar/asn1-schema");
|
|
40
40
|
Object.defineProperty(exports, "AsnParser", { enumerable: true, get: function () { return asn1_schema_1.AsnParser; } });
|
|
41
41
|
Object.defineProperty(exports, "AsnSerializer", { enumerable: true, get: function () { return asn1_schema_1.AsnSerializer; } });
|
|
42
|
+
Object.defineProperty(exports, "OctetString", { enumerable: true, get: function () { return asn1_schema_1.OctetString; } });
|
|
42
43
|
var asn1_x509_1 = require("@peculiar/asn1-x509");
|
|
43
44
|
Object.defineProperty(exports, "AuthorityKeyIdentifier", { enumerable: true, get: function () { return asn1_x509_1.AuthorityKeyIdentifier; } });
|
|
44
45
|
Object.defineProperty(exports, "BasicConstraints", { enumerable: true, get: function () { return asn1_x509_1.BasicConstraints; } });
|
|
@@ -46,6 +47,7 @@ Object.defineProperty(exports, "Certificate", { enumerable: true, get: function
|
|
|
46
47
|
Object.defineProperty(exports, "CertificateList", { enumerable: true, get: function () { return asn1_x509_1.CertificateList; } });
|
|
47
48
|
Object.defineProperty(exports, "CRLDistributionPoints", { enumerable: true, get: function () { return asn1_x509_1.CRLDistributionPoints; } });
|
|
48
49
|
Object.defineProperty(exports, "ExtendedKeyUsage", { enumerable: true, get: function () { return asn1_x509_1.ExtendedKeyUsage; } });
|
|
50
|
+
Object.defineProperty(exports, "Extensions", { enumerable: true, get: function () { return asn1_x509_1.Extensions; } });
|
|
49
51
|
Object.defineProperty(exports, "id_ce_authorityKeyIdentifier", { enumerable: true, get: function () { return asn1_x509_1.id_ce_authorityKeyIdentifier; } });
|
|
50
52
|
Object.defineProperty(exports, "id_ce_basicConstraints", { enumerable: true, get: function () { return asn1_x509_1.id_ce_basicConstraints; } });
|
|
51
53
|
Object.defineProperty(exports, "id_ce_cRLDistributionPoints", { enumerable: true, get: function () { return asn1_x509_1.id_ce_cRLDistributionPoints; } });
|
|
@@ -4,17 +4,7 @@
|
|
|
4
4
|
* @param extensionData Authenticator Extension Data buffer
|
|
5
5
|
*/
|
|
6
6
|
export declare function decodeAuthenticatorExtensions(extensionData: Uint8Array): AuthenticationExtensionsAuthenticatorOutputs | undefined;
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
export type DevicePublicKeyAuthenticatorOutput = {
|
|
12
|
-
dpk?: Uint8Array;
|
|
13
|
-
sig?: string;
|
|
14
|
-
nonce?: Uint8Array;
|
|
15
|
-
scope?: Uint8Array;
|
|
16
|
-
aaguid?: Uint8Array;
|
|
17
|
-
};
|
|
18
|
-
export type UVMAuthenticatorOutput = {
|
|
19
|
-
uvm?: Uint8Array[];
|
|
20
|
-
};
|
|
7
|
+
/**
|
|
8
|
+
* Attempt to support authenticator extensions we might not know about in WebAuthn
|
|
9
|
+
*/
|
|
10
|
+
export type AuthenticationExtensionsAuthenticatorOutputs = unknown;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { Extensions } from '../deps.js';
|
|
2
|
+
/**
|
|
3
|
+
* Look for the id-fido-gen-ce-aaguid certificate extension. If it's present then check it against
|
|
4
|
+
* the attestation statement AAGUID.
|
|
5
|
+
*/
|
|
6
|
+
export declare function validateExtFIDOGenCEAAGUID(certExtensions: Extensions | undefined, aaguid: Uint8Array): boolean;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.validateExtFIDOGenCEAAGUID = void 0;
|
|
4
|
+
const deps_js_1 = require("../deps.js");
|
|
5
|
+
const index_js_1 = require("./iso/index.js");
|
|
6
|
+
/**
|
|
7
|
+
* Attestation Certificate Extension OID: `id-fido-gen-ce-aaguid`
|
|
8
|
+
*
|
|
9
|
+
* Sourced from https://fidoalliance.org/specs/fido-v2.0-ps-20150904/fido-key-attestation-v2.0-ps-20150904.html#verifying-an-attestation-statement
|
|
10
|
+
*/
|
|
11
|
+
const id_fido_gen_ce_aaguid = '1.3.6.1.4.1.45724.1.1.4';
|
|
12
|
+
/**
|
|
13
|
+
* Look for the id-fido-gen-ce-aaguid certificate extension. If it's present then check it against
|
|
14
|
+
* the attestation statement AAGUID.
|
|
15
|
+
*/
|
|
16
|
+
function validateExtFIDOGenCEAAGUID(certExtensions, aaguid) {
|
|
17
|
+
// The certificate had no extensions so there's nothing to validate
|
|
18
|
+
if (!certExtensions) {
|
|
19
|
+
return true;
|
|
20
|
+
}
|
|
21
|
+
const extFIDOGenCEAAGUID = certExtensions.find((ext) => ext.extnID === id_fido_gen_ce_aaguid);
|
|
22
|
+
// The extension isn't present so there's nothing to validate
|
|
23
|
+
if (!extFIDOGenCEAAGUID) {
|
|
24
|
+
return true;
|
|
25
|
+
}
|
|
26
|
+
// Parse the extension value
|
|
27
|
+
const parsedExtFIDOGenCEAAGUID = deps_js_1.AsnParser.parse(extFIDOGenCEAAGUID.extnValue, deps_js_1.OctetString);
|
|
28
|
+
const extValue = new Uint8Array(parsedExtFIDOGenCEAAGUID.buffer);
|
|
29
|
+
// Compare the two values
|
|
30
|
+
const aaguidAndExtAreEqual = index_js_1.isoUint8Array.areEqual(aaguid, extValue);
|
|
31
|
+
if (!aaguidAndExtAreEqual) {
|
|
32
|
+
const _debugExtHex = index_js_1.isoUint8Array.toHex(extValue);
|
|
33
|
+
const _debugAAGUIDHex = index_js_1.isoUint8Array.toHex(aaguid);
|
|
34
|
+
throw new Error(`Certificate extension id-fido-gen-ce-aaguid (${id_fido_gen_ce_aaguid}) value of "${_debugExtHex}" was present but not equal to attestation statement AAGUID value of "${_debugAAGUIDHex}"`);
|
|
35
|
+
}
|
|
36
|
+
return true;
|
|
37
|
+
}
|
|
38
|
+
exports.validateExtFIDOGenCEAAGUID = validateExtFIDOGenCEAAGUID;
|
|
@@ -10,6 +10,7 @@ const validateCertificatePath_js_1 = require("../../../helpers/validateCertifica
|
|
|
10
10
|
const getCertificateInfo_js_1 = require("../../../helpers/getCertificateInfo.js");
|
|
11
11
|
const verifySignature_js_1 = require("../../../helpers/verifySignature.js");
|
|
12
12
|
const index_js_1 = require("../../../helpers/iso/index.js");
|
|
13
|
+
const validateExtFIDOGenCEAAGUID_js_1 = require("../../../helpers/validateExtFIDOGenCEAAGUID.js");
|
|
13
14
|
const metadataService_js_1 = require("../../../services/metadataService.js");
|
|
14
15
|
const verifyAttestationWithMetadata_js_1 = require("../../../metadata/verifyAttestationWithMetadata.js");
|
|
15
16
|
const constants_js_1 = require("./constants.js");
|
|
@@ -209,8 +210,14 @@ async function verifyAttestationTPM(options) {
|
|
|
209
210
|
if (extKeyUsage[0] !== '2.23.133.8.3') {
|
|
210
211
|
throw new Error(`Unexpected extKeyUsage "${extKeyUsage[0]}", expected "2.23.133.8.3" (TPM)`);
|
|
211
212
|
}
|
|
212
|
-
//
|
|
213
|
-
|
|
213
|
+
// Validate attestation statement AAGUID against leaf cert AAGUID
|
|
214
|
+
try {
|
|
215
|
+
await (0, validateExtFIDOGenCEAAGUID_js_1.validateExtFIDOGenCEAAGUID)(parsedCert.tbsCertificate.extensions, aaguid);
|
|
216
|
+
}
|
|
217
|
+
catch (err) {
|
|
218
|
+
const _err = err;
|
|
219
|
+
throw new Error(`${_err.message} (TPM)`);
|
|
220
|
+
}
|
|
214
221
|
// Run some metadata checks if a statement exists for this authenticator
|
|
215
222
|
const statement = await metadataService_js_1.MetadataService.getStatement(aaguid);
|
|
216
223
|
if (statement) {
|
|
@@ -7,6 +7,7 @@ const validateCertificatePath_js_1 = require("../../helpers/validateCertificateP
|
|
|
7
7
|
const getCertificateInfo_js_1 = require("../../helpers/getCertificateInfo.js");
|
|
8
8
|
const verifySignature_js_1 = require("../../helpers/verifySignature.js");
|
|
9
9
|
const index_js_1 = require("../../helpers/iso/index.js");
|
|
10
|
+
const validateExtFIDOGenCEAAGUID_js_1 = require("../../helpers/validateExtFIDOGenCEAAGUID.js");
|
|
10
11
|
const metadataService_js_1 = require("../../services/metadataService.js");
|
|
11
12
|
const verifyAttestationWithMetadata_js_1 = require("../../metadata/verifyAttestationWithMetadata.js");
|
|
12
13
|
/**
|
|
@@ -29,7 +30,7 @@ async function verifyAttestationPacked(options) {
|
|
|
29
30
|
const signatureBase = index_js_1.isoUint8Array.concat([authData, clientDataHash]);
|
|
30
31
|
let verified = false;
|
|
31
32
|
if (x5c) {
|
|
32
|
-
const { subject, basicConstraintsCA, version, notBefore, notAfter } = (0, getCertificateInfo_js_1.getCertificateInfo)(x5c[0]);
|
|
33
|
+
const { subject, basicConstraintsCA, version, notBefore, notAfter, parsedCertificate, } = (0, getCertificateInfo_js_1.getCertificateInfo)(x5c[0]);
|
|
33
34
|
const { OU, CN, O, C } = subject;
|
|
34
35
|
if (OU !== 'Authenticator Attestation') {
|
|
35
36
|
throw new Error('Certificate OU was not "Authenticator Attestation" (Packed|Full)');
|
|
@@ -57,8 +58,14 @@ async function verifyAttestationPacked(options) {
|
|
|
57
58
|
if (notAfter < now) {
|
|
58
59
|
throw new Error(`Certificate not good after "${notAfter.toString()}" (Packed|Full)`);
|
|
59
60
|
}
|
|
60
|
-
//
|
|
61
|
-
|
|
61
|
+
// Validate attestation statement AAGUID against leaf cert AAGUID
|
|
62
|
+
try {
|
|
63
|
+
await (0, validateExtFIDOGenCEAAGUID_js_1.validateExtFIDOGenCEAAGUID)(parsedCertificate.tbsCertificate.extensions, aaguid);
|
|
64
|
+
}
|
|
65
|
+
catch (err) {
|
|
66
|
+
const _err = err;
|
|
67
|
+
throw new Error(`${_err.message} (Packed|Full)`);
|
|
68
|
+
}
|
|
62
69
|
// If available, validate attestation alg and x5c with info in the metadata statement
|
|
63
70
|
const statement = await metadataService_js_1.MetadataService.getStatement(aaguid);
|
|
64
71
|
if (statement) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { COSEAlgorithmIdentifier, CredentialDeviceType, RegistrationResponseJSON, WebAuthnCredential } from '../deps.js';
|
|
2
2
|
import { AttestationFormat, AttestationStatement } from '../helpers/decodeAttestationObject.js';
|
|
3
3
|
import { AuthenticationExtensionsAuthenticatorOutputs } from '../helpers/decodeAuthenticatorExtensions.js';
|
|
4
4
|
export type VerifyRegistrationResponseOpts = {
|
|
@@ -7,6 +7,7 @@ export type VerifyRegistrationResponseOpts = {
|
|
|
7
7
|
expectedOrigin: string | string[];
|
|
8
8
|
expectedRPID?: string | string[];
|
|
9
9
|
expectedType?: string | string[];
|
|
10
|
+
requireUserPresence?: boolean;
|
|
10
11
|
requireUserVerification?: boolean;
|
|
11
12
|
supportedAlgorithmIDs?: COSEAlgorithmIdentifier[];
|
|
12
13
|
};
|
|
@@ -20,6 +21,7 @@ export type VerifyRegistrationResponseOpts = {
|
|
|
20
21
|
* @param expectedOrigin - Website URL (or array of URLs) that the registration should have occurred on
|
|
21
22
|
* @param expectedRPID - RP ID (or array of IDs) that was specified in the registration options
|
|
22
23
|
* @param expectedType **(Optional)** - The response type expected ('webauthn.create')
|
|
24
|
+
* @param requireUserPresence **(Optional)** - Enforce user presence by the authenticator (or skip it during auto registration) Defaults to `true`
|
|
23
25
|
* @param requireUserVerification **(Optional)** - Enforce user verification by the authenticator (via PIN, fingerprint, etc...) Defaults to `true`
|
|
24
26
|
* @param supportedAlgorithmIDs **(Optional)** - Array of numeric COSE algorithm identifiers supported for attestation by this RP. See https://www.iana.org/assignments/cose/cose.xhtml#algorithms. Defaults to all supported algorithm IDs
|
|
25
27
|
*/
|
|
@@ -54,10 +56,8 @@ export type VerifiedRegistrationResponse = {
|
|
|
54
56
|
verified: boolean;
|
|
55
57
|
registrationInfo?: {
|
|
56
58
|
fmt: AttestationFormat;
|
|
57
|
-
counter: number;
|
|
58
59
|
aaguid: string;
|
|
59
|
-
|
|
60
|
-
credentialPublicKey: Uint8Array;
|
|
60
|
+
credential: WebAuthnCredential;
|
|
61
61
|
credentialType: 'public-key';
|
|
62
62
|
attestationObject: Uint8Array;
|
|
63
63
|
userVerified: boolean;
|
|
@@ -29,11 +29,12 @@ const verifyAttestationApple_js_1 = require("./verifications/verifyAttestationAp
|
|
|
29
29
|
* @param expectedOrigin - Website URL (or array of URLs) that the registration should have occurred on
|
|
30
30
|
* @param expectedRPID - RP ID (or array of IDs) that was specified in the registration options
|
|
31
31
|
* @param expectedType **(Optional)** - The response type expected ('webauthn.create')
|
|
32
|
+
* @param requireUserPresence **(Optional)** - Enforce user presence by the authenticator (or skip it during auto registration) Defaults to `true`
|
|
32
33
|
* @param requireUserVerification **(Optional)** - Enforce user verification by the authenticator (via PIN, fingerprint, etc...) Defaults to `true`
|
|
33
34
|
* @param supportedAlgorithmIDs **(Optional)** - Array of numeric COSE algorithm identifiers supported for attestation by this RP. See https://www.iana.org/assignments/cose/cose.xhtml#algorithms. Defaults to all supported algorithm IDs
|
|
34
35
|
*/
|
|
35
36
|
async function verifyRegistrationResponse(options) {
|
|
36
|
-
const { response, expectedChallenge, expectedOrigin, expectedRPID, expectedType, requireUserVerification = true, supportedAlgorithmIDs = generateRegistrationOptions_js_1.supportedCOSEAlgorithmIdentifiers, } = options;
|
|
37
|
+
const { response, expectedChallenge, expectedOrigin, expectedRPID, expectedType, requireUserPresence = true, requireUserVerification = true, supportedAlgorithmIDs = generateRegistrationOptions_js_1.supportedCOSEAlgorithmIdentifiers, } = options;
|
|
37
38
|
const { id, rawId, type: credentialType, response: attestationResponse } = response;
|
|
38
39
|
// Ensure credential specified an ID
|
|
39
40
|
if (!id) {
|
|
@@ -112,12 +113,12 @@ async function verifyRegistrationResponse(options) {
|
|
|
112
113
|
matchedRPID = await (0, matchExpectedRPID_js_1.matchExpectedRPID)(rpIdHash, expectedRPIDs);
|
|
113
114
|
}
|
|
114
115
|
// Make sure someone was physically present
|
|
115
|
-
if (!flags.up) {
|
|
116
|
-
throw new Error('User not present
|
|
116
|
+
if (requireUserPresence && !flags.up) {
|
|
117
|
+
throw new Error('User presence was required, but user was not present');
|
|
117
118
|
}
|
|
118
119
|
// Enforce user verification if specified
|
|
119
120
|
if (requireUserVerification && !flags.uv) {
|
|
120
|
-
throw new Error('User verification required, but user could not be verified');
|
|
121
|
+
throw new Error('User verification was required, but user could not be verified');
|
|
121
122
|
}
|
|
122
123
|
if (!credentialID) {
|
|
123
124
|
throw new Error('No credential ID was provided by authenticator');
|
|
@@ -192,11 +193,14 @@ async function verifyRegistrationResponse(options) {
|
|
|
192
193
|
const { credentialDeviceType, credentialBackedUp } = (0, parseBackupFlags_js_1.parseBackupFlags)(flags);
|
|
193
194
|
toReturn.registrationInfo = {
|
|
194
195
|
fmt,
|
|
195
|
-
counter,
|
|
196
196
|
aaguid: (0, convertAAGUIDToString_js_1.convertAAGUIDToString)(aaguid),
|
|
197
|
-
credentialID: index_js_1.isoBase64URL.fromBuffer(credentialID),
|
|
198
|
-
credentialPublicKey,
|
|
199
197
|
credentialType,
|
|
198
|
+
credential: {
|
|
199
|
+
id: index_js_1.isoBase64URL.fromBuffer(credentialID),
|
|
200
|
+
publicKey: credentialPublicKey,
|
|
201
|
+
counter,
|
|
202
|
+
transports: response.response.transports,
|
|
203
|
+
},
|
|
200
204
|
attestationObject,
|
|
201
205
|
userVerified: flags.uv,
|
|
202
206
|
credentialDeviceType,
|