@simplewebauthn/server
Advanced tools
Comparing version 0.5.1 to 0.6.0
import { AssertionCredentialJSON, AuthenticatorDevice } from '@simplewebauthn/typescript-types'; | ||
declare type Options = { | ||
credential: AssertionCredentialJSON; | ||
expectedChallenge: string; | ||
expectedOrigin: string; | ||
expectedRPID: string; | ||
authenticator: AuthenticatorDevice; | ||
requireUserVerification?: boolean; | ||
}; | ||
/** | ||
* Verify that the user has legitimately completed the login process | ||
* | ||
* @param response Authenticator assertion response with base64url-encoded values | ||
* **Options:** | ||
* | ||
* @param credential Authenticator credential returned by browser's `startAssertion()` | ||
* @param expectedChallenge The random value provided to generateAssertionOptions for the | ||
* authenticator to sign | ||
* @param expectedOrigin Expected URL of website assertion should have occurred on | ||
* @param expectedOrigin Website URL that the attestation should have occurred on | ||
* @param expectedRPID RP ID that was specified in the attestation options | ||
* @param authenticator An internal {@link AuthenticatorDevice} matching the credential's ID | ||
* @param requireUserVerification (Optional) Enforce user verification by the authenticator | ||
* (via PIN, fingerprint, etc...) | ||
*/ | ||
export default function verifyAssertionResponse(credential: AssertionCredentialJSON, expectedChallenge: string, expectedOrigin: string, authenticator: AuthenticatorDevice): VerifiedAssertion; | ||
export default function verifyAssertionResponse(options: Options): VerifiedAssertion; | ||
/** | ||
@@ -28,1 +42,2 @@ * Result of assertion verification | ||
}; | ||
export {}; |
@@ -15,13 +15,21 @@ "use strict"; | ||
* | ||
* @param response Authenticator assertion response with base64url-encoded values | ||
* **Options:** | ||
* | ||
* @param credential Authenticator credential returned by browser's `startAssertion()` | ||
* @param expectedChallenge The random value provided to generateAssertionOptions for the | ||
* authenticator to sign | ||
* @param expectedOrigin Expected URL of website assertion should have occurred on | ||
* @param expectedOrigin Website URL that the attestation should have occurred on | ||
* @param expectedRPID RP ID that was specified in the attestation options | ||
* @param authenticator An internal {@link AuthenticatorDevice} matching the credential's ID | ||
* @param requireUserVerification (Optional) Enforce user verification by the authenticator | ||
* (via PIN, fingerprint, etc...) | ||
*/ | ||
function verifyAssertionResponse(credential, expectedChallenge, expectedOrigin, authenticator) { | ||
function verifyAssertionResponse(options) { | ||
const { credential, expectedChallenge, expectedOrigin, expectedRPID, authenticator, requireUserVerification = false, } = options; | ||
const { response } = credential; | ||
const clientDataJSON = decodeClientDataJSON_1.default(response.clientDataJSON); | ||
const { type, origin, challenge } = clientDataJSON; | ||
if (!expectedOrigin.startsWith('https://')) { | ||
expectedOrigin = `https://${expectedOrigin}`; | ||
// Make sure we're handling an assertion | ||
if (type !== 'webauthn.get') { | ||
throw new Error(`Unexpected assertion type: ${type}`); | ||
} | ||
@@ -35,13 +43,23 @@ if (challenge !== expectedChallenge) { | ||
} | ||
// Make sure we're handling an assertion | ||
if (type !== 'webauthn.get') { | ||
throw new Error(`Unexpected assertion type: ${type}`); | ||
const authDataBuffer = base64url_1.default.toBuffer(response.authenticatorData); | ||
const parsedAuthData = parseAuthenticatorData_1.default(authDataBuffer); | ||
const { rpIdHash, flags, counter } = parsedAuthData; | ||
// Make sure the response's RP ID is ours | ||
const expectedRPIDHash = toHash_1.default(Buffer.from(expectedRPID, 'ascii')); | ||
if (!rpIdHash.equals(expectedRPIDHash)) { | ||
throw new Error(`Unexpected RP ID hash`); | ||
} | ||
const authDataBuffer = base64url_1.default.toBuffer(response.authenticatorData); | ||
const authDataStruct = parseAuthenticatorData_1.default(authDataBuffer); | ||
const { flags, counter } = authDataStruct; | ||
// Make sure someone was physically present | ||
if (!flags.up) { | ||
throw new Error('User not present during assertion'); | ||
} | ||
if (counter <= authenticator.counter) { | ||
// Enforce user verification if specified | ||
if (requireUserVerification && !flags.uv) { | ||
throw new Error('User verification required, but user could not be verified'); | ||
} | ||
const clientDataHash = toHash_1.default(base64url_1.default.toBuffer(response.clientDataJSON)); | ||
const signatureBase = Buffer.concat([authDataBuffer, clientDataHash]); | ||
const publicKey = convertASN1toPEM_1.default(base64url_1.default.toBuffer(authenticator.publicKey)); | ||
const signature = base64url_1.default.toBuffer(response.signature); | ||
if ((counter > 0 || authenticator.counter > 0) && counter <= authenticator.counter) { | ||
// Error out when the counter in the DB is greater than or equal to the counter in the | ||
@@ -53,7 +71,2 @@ // dataStruct. It's related to how the authenticator maintains the number of times its been | ||
} | ||
const { rpIdHash, flagsBuf, counterBuf } = authDataStruct; | ||
const clientDataHash = toHash_1.default(base64url_1.default.toBuffer(response.clientDataJSON)); | ||
const signatureBase = Buffer.concat([rpIdHash, flagsBuf, counterBuf, clientDataHash]); | ||
const publicKey = convertASN1toPEM_1.default(base64url_1.default.toBuffer(authenticator.publicKey)); | ||
const signature = base64url_1.default.toBuffer(response.signature); | ||
const toReturn = { | ||
@@ -60,0 +73,0 @@ verified: verifySignature_1.default(signature, signatureBase, publicKey), |
@@ -16,2 +16,3 @@ import type { PublicKeyCredentialCreationOptionsJSON, Base64URLString } from '@simplewebauthn/typescript-types'; | ||
}; | ||
export declare const supportedCOSEAlgorithIdentifiers: COSEAlgorithmIdentifier[]; | ||
/** | ||
@@ -18,0 +19,0 @@ * Prepare a value to pass into navigator.credentials.create(...) for authenticator "registration" |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.supportedCOSEAlgorithIdentifiers = void 0; | ||
// Supported crypto algo identifiers | ||
// See https://w3c.github.io/webauthn/#sctn-alg-identifier | ||
exports.supportedCOSEAlgorithIdentifiers = [-7, -35, -36, -8]; | ||
/** | ||
@@ -36,11 +40,9 @@ * Prepare a value to pass into navigator.credentials.create(...) for authenticator "registration" | ||
}, | ||
pubKeyCredParams: [ | ||
{ | ||
alg: -7, | ||
type: 'public-key', | ||
}, | ||
], | ||
pubKeyCredParams: exports.supportedCOSEAlgorithIdentifiers.map(id => ({ | ||
alg: id, | ||
type: 'public-key', | ||
})), | ||
timeout, | ||
attestation: attestationType, | ||
excludeCredentials: excludedCredentialIDs.map((id) => ({ | ||
excludeCredentials: excludedCredentialIDs.map(id => ({ | ||
id, | ||
@@ -47,0 +49,0 @@ type: 'public-key', |
@@ -1,6 +0,12 @@ | ||
import type { AttestationObject } from "../../helpers/decodeAttestationObject"; | ||
import type { VerifiedAttestation } from "../verifyAttestationResponse"; | ||
/// <reference types="node" /> | ||
import type { AttestationStatement } from "../../helpers/decodeAttestationObject"; | ||
declare type Options = { | ||
attStmt: AttestationStatement; | ||
clientDataHash: Buffer; | ||
authData: Buffer; | ||
}; | ||
/** | ||
* Verify an attestation response with fmt 'android-safetynet' | ||
*/ | ||
export default function verifyAttestationAndroidSafetyNet(attestationObject: AttestationObject, base64ClientDataJSON: string): VerifiedAttestation; | ||
export default function verifyAttestationAndroidSafetyNet(options: Options): boolean; | ||
export {}; |
@@ -9,21 +9,8 @@ "use strict"; | ||
const verifySignature_1 = __importDefault(require("../../helpers/verifySignature")); | ||
const convertCOSEtoPKCS_1 = __importDefault(require("../../helpers/convertCOSEtoPKCS")); | ||
const getCertificateInfo_1 = __importDefault(require("../../helpers/getCertificateInfo")); | ||
const parseAuthenticatorData_1 = __importDefault(require("../../helpers/parseAuthenticatorData")); | ||
/** | ||
* Verify an attestation response with fmt 'android-safetynet' | ||
*/ | ||
function verifyAttestationAndroidSafetyNet(attestationObject, base64ClientDataJSON) { | ||
const { attStmt, authData, fmt } = attestationObject; | ||
const authDataStruct = parseAuthenticatorData_1.default(authData); | ||
const { counter, credentialID, COSEPublicKey, flags } = authDataStruct; | ||
if (!flags.up) { | ||
throw new Error('User was not present for attestation (None)'); | ||
} | ||
if (!COSEPublicKey) { | ||
throw new Error('No public key was provided by authenticator (SafetyNet)'); | ||
} | ||
if (!credentialID) { | ||
throw new Error('No credential ID was provided by authenticator (SafetyNet)'); | ||
} | ||
function verifyAttestationAndroidSafetyNet(options) { | ||
const { attStmt, clientDataHash, authData } = options; | ||
if (!attStmt.response) { | ||
@@ -42,3 +29,2 @@ throw new Error('No response was included in attStmt by authenticator (SafetyNet)'); | ||
const { nonce, ctsProfileMatch } = PAYLOAD; | ||
const clientDataHash = toHash_1.default(base64url_1.default.toBuffer(base64ClientDataJSON)); | ||
const nonceBase = Buffer.concat([authData, clientDataHash]); | ||
@@ -85,20 +71,7 @@ const nonceBuffer = toHash_1.default(nonceBase); | ||
const signatureBuffer = base64url_1.default.toBuffer(SIGNATURE); | ||
const toReturn = { | ||
verified: verifySignature_1.default(signatureBuffer, signatureBaseBuffer, certificate), | ||
userVerified: false, | ||
}; | ||
const verified = verifySignature_1.default(signatureBuffer, signatureBaseBuffer, certificate); | ||
/** | ||
* END Verify Signature | ||
*/ | ||
if (toReturn.verified) { | ||
toReturn.userVerified = flags.uv; | ||
const publicKey = convertCOSEtoPKCS_1.default(COSEPublicKey); | ||
toReturn.authenticatorInfo = { | ||
fmt, | ||
counter, | ||
base64PublicKey: base64url_1.default.encode(publicKey), | ||
base64CredentialID: base64url_1.default.encode(credentialID), | ||
}; | ||
} | ||
return toReturn; | ||
return verified; | ||
} | ||
@@ -105,0 +78,0 @@ exports.default = verifyAttestationAndroidSafetyNet; |
@@ -1,6 +0,14 @@ | ||
import type { AttestationObject } from "../../helpers/decodeAttestationObject"; | ||
import type { VerifiedAttestation } from "../verifyAttestationResponse"; | ||
/// <reference types="node" /> | ||
import type { AttestationStatement } from "../../helpers/decodeAttestationObject"; | ||
declare type Options = { | ||
attStmt: AttestationStatement; | ||
clientDataHash: Buffer; | ||
rpIdHash: Buffer; | ||
credentialID: Buffer; | ||
credentialPublicKey: Buffer; | ||
}; | ||
/** | ||
* Verify an attestation response with fmt 'fido-u2f' | ||
*/ | ||
export default function verifyAttestationFIDOU2F(attestationObject: AttestationObject, base64ClientDataJSON: string): VerifiedAttestation; | ||
export default function verifyAttestationFIDOU2F(options: Options): boolean; | ||
export {}; |
@@ -6,27 +6,12 @@ "use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const base64url_1 = __importDefault(require("base64url")); | ||
const toHash_1 = __importDefault(require("../../helpers/toHash")); | ||
const convertCOSEtoPKCS_1 = __importDefault(require("../../helpers/convertCOSEtoPKCS")); | ||
const convertASN1toPEM_1 = __importDefault(require("../../helpers/convertASN1toPEM")); | ||
const verifySignature_1 = __importDefault(require("../../helpers/verifySignature")); | ||
const parseAuthenticatorData_1 = __importDefault(require("../../helpers/parseAuthenticatorData")); | ||
/** | ||
* Verify an attestation response with fmt 'fido-u2f' | ||
*/ | ||
function verifyAttestationFIDOU2F(attestationObject, base64ClientDataJSON) { | ||
const { fmt, authData, attStmt } = attestationObject; | ||
const authDataStruct = parseAuthenticatorData_1.default(authData); | ||
const { flags, COSEPublicKey, rpIdHash, credentialID, counter } = authDataStruct; | ||
if (!flags.up) { | ||
throw new Error('User was NOT present during authentication (FIDOU2F)'); | ||
} | ||
if (!COSEPublicKey) { | ||
throw new Error('No public key was provided by authenticator (FIDOU2F)'); | ||
} | ||
if (!credentialID) { | ||
throw new Error('No credential ID was provided by authenticator (FIDOU2F)'); | ||
} | ||
const clientDataHash = toHash_1.default(base64url_1.default.toBuffer(base64ClientDataJSON)); | ||
function verifyAttestationFIDOU2F(options) { | ||
const { attStmt, clientDataHash, rpIdHash, credentialID, credentialPublicKey } = options; | ||
const reservedByte = Buffer.from([0x00]); | ||
const publicKey = convertCOSEtoPKCS_1.default(COSEPublicKey); | ||
const publicKey = convertCOSEtoPKCS_1.default(credentialPublicKey); | ||
const signatureBase = Buffer.concat([ | ||
@@ -47,17 +32,5 @@ reservedByte, | ||
const publicKeyCertPEM = convertASN1toPEM_1.default(x5c[0]); | ||
const toReturn = { | ||
verified: verifySignature_1.default(sig, signatureBase, publicKeyCertPEM), | ||
userVerified: flags.uv, | ||
}; | ||
if (toReturn.verified) { | ||
toReturn.authenticatorInfo = { | ||
fmt, | ||
counter, | ||
base64PublicKey: base64url_1.default.encode(publicKey), | ||
base64CredentialID: base64url_1.default.encode(credentialID), | ||
}; | ||
} | ||
return toReturn; | ||
return verifySignature_1.default(sig, signatureBase, publicKeyCertPEM); | ||
} | ||
exports.default = verifyAttestationFIDOU2F; | ||
//# sourceMappingURL=verifyFIDOU2F.js.map |
@@ -1,6 +0,13 @@ | ||
import type { AttestationObject } from "../../helpers/decodeAttestationObject"; | ||
import type { VerifiedAttestation } from "../verifyAttestationResponse"; | ||
/// <reference types="node" /> | ||
import type { AttestationStatement } from "../../helpers/decodeAttestationObject"; | ||
declare type Options = { | ||
attStmt: AttestationStatement; | ||
clientDataHash: Buffer; | ||
authData: Buffer; | ||
credentialPublicKey: Buffer; | ||
}; | ||
/** | ||
* Verify an attestation response with fmt 'packed' | ||
*/ | ||
export default function verifyAttestationPacked(attestationObject: AttestationObject, base64ClientDataJSON: string): VerifiedAttestation; | ||
export default function verifyAttestationPacked(options: Options): boolean; | ||
export {}; |
@@ -25,4 +25,2 @@ "use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const base64url_1 = __importDefault(require("base64url")); | ||
const cbor_1 = __importDefault(require("cbor")); | ||
const elliptic_1 = __importDefault(require("elliptic")); | ||
@@ -35,30 +33,15 @@ const node_rsa_1 = __importDefault(require("node-rsa")); | ||
const verifySignature_1 = __importDefault(require("../../helpers/verifySignature")); | ||
const parseAuthenticatorData_1 = __importDefault(require("../../helpers/parseAuthenticatorData")); | ||
const decodeCredentialPublicKey_1 = __importDefault(require("../../helpers/decodeCredentialPublicKey")); | ||
/** | ||
* Verify an attestation response with fmt 'packed' | ||
*/ | ||
function verifyAttestationPacked(attestationObject, base64ClientDataJSON) { | ||
const { fmt, authData, attStmt } = attestationObject; | ||
function verifyAttestationPacked(options) { | ||
const { attStmt, clientDataHash, authData, credentialPublicKey } = options; | ||
const { sig, x5c } = attStmt; | ||
const authDataStruct = parseAuthenticatorData_1.default(authData); | ||
const { COSEPublicKey, counter, credentialID, flags } = authDataStruct; | ||
if (!flags.up) { | ||
throw new Error('User was not present for attestation (Packed)'); | ||
} | ||
if (!COSEPublicKey) { | ||
throw new Error('No public key was provided by authenticator (Packed)'); | ||
} | ||
if (!credentialID) { | ||
throw new Error('No credential ID was provided by authenticator (Packed)'); | ||
} | ||
if (!sig) { | ||
throw new Error('No attestation signature provided in attestation statement (Packed)'); | ||
} | ||
const clientDataHash = toHash_1.default(base64url_1.default.toBuffer(base64ClientDataJSON)); | ||
const signatureBase = Buffer.concat([authData, clientDataHash]); | ||
const toReturn = { | ||
verified: false, | ||
userVerified: flags.uv, | ||
}; | ||
const publicKey = convertCOSEtoPKCS_1.default(COSEPublicKey); | ||
let verified = false; | ||
const pkcsPublicKey = convertCOSEtoPKCS_1.default(credentialPublicKey); | ||
if (x5c) { | ||
@@ -87,6 +70,6 @@ const leafCert = convertASN1toPEM_1.default(x5c[0]); | ||
} | ||
toReturn.verified = verifySignature_1.default(sig, signatureBase, leafCert); | ||
verified = verifySignature_1.default(sig, signatureBase, leafCert); | ||
} | ||
else { | ||
const cosePublicKey = cbor_1.default.decodeAllSync(COSEPublicKey)[0]; | ||
const cosePublicKey = decodeCredentialPublicKey_1.default(credentialPublicKey); | ||
const kty = cosePublicKey.get(convertCOSEtoPKCS_1.COSEKEYS.kty); | ||
@@ -106,3 +89,2 @@ const alg = cosePublicKey.get(convertCOSEtoPKCS_1.COSEKEYS.alg); | ||
} | ||
const pkcsPublicKey = convertCOSEtoPKCS_1.default(COSEPublicKey); | ||
const signatureBaseHash = toHash_1.default(signatureBase, hashAlg); | ||
@@ -120,3 +102,3 @@ /** | ||
const key = ec.keyFromPublic(pkcsPublicKey); | ||
toReturn.verified = key.verify(signatureBaseHash, sig); | ||
verified = key.verify(signatureBaseHash, sig); | ||
} | ||
@@ -136,3 +118,3 @@ else if (kty === COSEKTY.RSA) { | ||
}, 'components-public'); | ||
toReturn.verified = key.verify(signatureBase, sig); | ||
verified = key.verify(signatureBase, sig); | ||
} | ||
@@ -148,14 +130,6 @@ else if (kty === COSEKTY.OKP) { | ||
// TODO: is `publicKey` right here? | ||
toReturn.verified = key.verify(signatureBaseHash, sig, publicKey); | ||
verified = key.verify(signatureBaseHash, sig, pkcsPublicKey); | ||
} | ||
} | ||
if (toReturn.verified) { | ||
toReturn.authenticatorInfo = { | ||
fmt, | ||
counter, | ||
base64PublicKey: base64url_1.default.encode(publicKey), | ||
base64CredentialID: base64url_1.default.encode(credentialID), | ||
}; | ||
} | ||
return toReturn; | ||
return verified; | ||
} | ||
@@ -178,6 +152,12 @@ exports.default = verifyAttestationPacked; | ||
}; | ||
// See https://w3c.github.io/webauthn/#sctn-alg-identifier | ||
const COSECRV = { | ||
// alg: -7 | ||
1: 'p256', | ||
// alg: -35 | ||
2: 'p384', | ||
// alg: -36 | ||
3: 'p521', | ||
// alg: -8 | ||
6: 'ed25519', | ||
}; | ||
@@ -184,0 +164,0 @@ const COSEALGHASH = { |
import { AttestationCredentialJSON } from '@simplewebauthn/typescript-types'; | ||
import { ATTESTATION_FORMATS } from "../helpers/decodeAttestationObject"; | ||
declare type Options = { | ||
credential: AttestationCredentialJSON; | ||
expectedChallenge: string; | ||
expectedOrigin: string; | ||
expectedRPID: string; | ||
requireUserVerification?: boolean; | ||
}; | ||
/** | ||
* Verify that the user has legitimately completed the registration process | ||
* | ||
* **Options:** | ||
* | ||
* @param response Authenticator attestation response with base64url-encoded values | ||
* @param expectedChallenge The random value provided to generateAttestationOptions for the | ||
* authenticator to sign | ||
* @param expectedOrigin Expected URL of website attestation should have occurred on | ||
* @param expectedOrigin Website URL that the attestation should have occurred on | ||
* @param expectedRPID RP ID that was specified in the attestation options | ||
* @param requireUserVerification (Optional) Enforce user verification by the authenticator | ||
* (via PIN, fingerprint, etc...) | ||
*/ | ||
export default function verifyAttestationResponse(credential: AttestationCredentialJSON, expectedChallenge: string, expectedOrigin: string): VerifiedAttestation; | ||
export default function verifyAttestationResponse(options: Options): VerifiedAttestation; | ||
/** | ||
@@ -36,1 +48,2 @@ * Result of attestation verification | ||
}; | ||
export {}; |
@@ -25,7 +25,12 @@ "use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const base64url_1 = __importDefault(require("base64url")); | ||
const decodeAttestationObject_1 = __importStar(require("../helpers/decodeAttestationObject")); | ||
const decodeClientDataJSON_1 = __importDefault(require("../helpers/decodeClientDataJSON")); | ||
const parseAuthenticatorData_1 = __importDefault(require("../helpers/parseAuthenticatorData")); | ||
const toHash_1 = __importDefault(require("../helpers/toHash")); | ||
const decodeCredentialPublicKey_1 = __importDefault(require("../helpers/decodeCredentialPublicKey")); | ||
const convertCOSEtoPKCS_1 = __importStar(require("../helpers/convertCOSEtoPKCS")); | ||
const generateAttestationOptions_1 = require("./generateAttestationOptions"); | ||
const verifyFIDOU2F_1 = __importDefault(require("./verifications/verifyFIDOU2F")); | ||
const verifyPacked_1 = __importDefault(require("./verifications/verifyPacked")); | ||
const verifyNone_1 = __importDefault(require("./verifications/verifyNone")); | ||
const verifyAndroidSafetyNet_1 = __importDefault(require("./verifications/verifyAndroidSafetyNet")); | ||
@@ -35,12 +40,22 @@ /** | ||
* | ||
* **Options:** | ||
* | ||
* @param response Authenticator attestation response with base64url-encoded values | ||
* @param expectedChallenge The random value provided to generateAttestationOptions for the | ||
* authenticator to sign | ||
* @param expectedOrigin Expected URL of website attestation should have occurred on | ||
* @param expectedOrigin Website URL that the attestation should have occurred on | ||
* @param expectedRPID RP ID that was specified in the attestation options | ||
* @param requireUserVerification (Optional) Enforce user verification by the authenticator | ||
* (via PIN, fingerprint, etc...) | ||
*/ | ||
function verifyAttestationResponse(credential, expectedChallenge, expectedOrigin) { | ||
function verifyAttestationResponse(options) { | ||
const { credential, expectedChallenge, expectedOrigin, expectedRPID, requireUserVerification = false, } = options; | ||
const { response } = credential; | ||
const attestationObject = decodeAttestationObject_1.default(response.attestationObject); | ||
const clientDataJSON = decodeClientDataJSON_1.default(response.clientDataJSON); | ||
const { type, origin, challenge } = clientDataJSON; | ||
// Make sure we're handling an attestation | ||
if (type !== 'webauthn.create') { | ||
throw new Error(`Unexpected attestation type: ${type}`); | ||
} | ||
// Ensure the device provided the challenge we gave it | ||
if (challenge !== expectedChallenge) { | ||
@@ -53,25 +68,88 @@ throw new Error(`Unexpected attestation challenge "${challenge}", expected "${expectedChallenge}"`); | ||
} | ||
// Make sure we're handling an attestation | ||
if (type !== 'webauthn.create') { | ||
throw new Error(`Unexpected attestation type: ${type}`); | ||
const attestationObject = decodeAttestationObject_1.default(response.attestationObject); | ||
const { fmt, authData, attStmt } = attestationObject; | ||
const parsedAuthData = parseAuthenticatorData_1.default(authData); | ||
const { rpIdHash, flags, credentialID, counter, credentialPublicKey } = parsedAuthData; | ||
// Make sure the response's RP ID is ours | ||
const expectedRPIDHash = toHash_1.default(Buffer.from(expectedRPID, 'ascii')); | ||
if (!rpIdHash.equals(expectedRPIDHash)) { | ||
throw new Error(`Unexpected RP ID hash`); | ||
} | ||
const { fmt } = attestationObject; | ||
// Make sure someone was physically present | ||
if (!flags.up) { | ||
throw new Error('User not present during assertion'); | ||
} | ||
// Enforce user verification if specified | ||
if (requireUserVerification && !flags.uv) { | ||
throw new Error('User verification required, but user could not be verified'); | ||
} | ||
if (!credentialID) { | ||
throw new Error('No credential ID was provided by authenticator'); | ||
} | ||
if (!credentialPublicKey) { | ||
throw new Error('No public key was provided by authenticator'); | ||
} | ||
const decodedPublicKey = decodeCredentialPublicKey_1.default(credentialPublicKey); | ||
const alg = decodedPublicKey.get(convertCOSEtoPKCS_1.COSEKEYS.alg); | ||
if (!alg) { | ||
throw new Error('Credential public key was missing alg'); | ||
} | ||
// Make sure the key algorithm is one we specified within the attestation options | ||
if (!generateAttestationOptions_1.supportedCOSEAlgorithIdentifiers.includes(alg)) { | ||
const supported = generateAttestationOptions_1.supportedCOSEAlgorithIdentifiers.join(', '); | ||
throw new Error(`Unexpected public key alg "${alg}", expected one of "${supported}"`); | ||
} | ||
const clientDataHash = toHash_1.default(base64url_1.default.toBuffer(response.clientDataJSON)); | ||
/** | ||
* Verification can only be performed when attestation = 'direct' | ||
*/ | ||
let verified = false; | ||
if (fmt === decodeAttestationObject_1.ATTESTATION_FORMATS.FIDO_U2F) { | ||
return verifyFIDOU2F_1.default(attestationObject, response.clientDataJSON); | ||
verified = verifyFIDOU2F_1.default({ | ||
attStmt, | ||
clientDataHash, | ||
credentialID, | ||
credentialPublicKey, | ||
rpIdHash, | ||
}); | ||
} | ||
if (fmt === decodeAttestationObject_1.ATTESTATION_FORMATS.PACKED) { | ||
return verifyPacked_1.default(attestationObject, response.clientDataJSON); | ||
else if (fmt === decodeAttestationObject_1.ATTESTATION_FORMATS.PACKED) { | ||
verified = verifyPacked_1.default({ | ||
attStmt, | ||
authData, | ||
clientDataHash, | ||
credentialPublicKey, | ||
}); | ||
} | ||
if (fmt === decodeAttestationObject_1.ATTESTATION_FORMATS.ANDROID_SAFETYNET) { | ||
return verifyAndroidSafetyNet_1.default(attestationObject, response.clientDataJSON); | ||
else if (fmt === decodeAttestationObject_1.ATTESTATION_FORMATS.ANDROID_SAFETYNET) { | ||
verified = verifyAndroidSafetyNet_1.default({ | ||
attStmt, | ||
authData, | ||
clientDataHash, | ||
}); | ||
} | ||
if (fmt === decodeAttestationObject_1.ATTESTATION_FORMATS.NONE) { | ||
return verifyNone_1.default(attestationObject); | ||
else if (fmt === decodeAttestationObject_1.ATTESTATION_FORMATS.NONE) { | ||
// This is the weaker of the attestations, so there's nothing else to really check | ||
verified = true; | ||
} | ||
throw new Error(`Unsupported Attestation Format: ${fmt}`); | ||
else { | ||
throw new Error(`Unsupported Attestation Format: ${fmt}`); | ||
} | ||
const toReturn = { | ||
verified, | ||
userVerified: flags.uv, | ||
}; | ||
if (toReturn.verified) { | ||
toReturn.userVerified = flags.uv; | ||
const publicKey = convertCOSEtoPKCS_1.default(credentialPublicKey); | ||
toReturn.authenticatorInfo = { | ||
fmt, | ||
counter, | ||
base64PublicKey: base64url_1.default.encode(publicKey), | ||
base64CredentialID: base64url_1.default.encode(credentialID), | ||
}; | ||
} | ||
return toReturn; | ||
} | ||
exports.default = verifyAttestationResponse; | ||
//# sourceMappingURL=verifyAttestationResponse.js.map |
@@ -16,8 +16,9 @@ /// <reference types="node" /> | ||
fmt: ATTESTATION_FORMATS; | ||
attStmt: { | ||
sig?: Buffer; | ||
x5c?: Buffer[]; | ||
response?: Buffer; | ||
}; | ||
attStmt: AttestationStatement; | ||
authData: Buffer; | ||
}; | ||
export declare type AttestationStatement = { | ||
sig?: Buffer; | ||
x5c?: Buffer[]; | ||
response?: Buffer; | ||
}; |
/** | ||
* Decode an authenticator's base64-encoded clientDataJSON to JSON | ||
* Decode an authenticator's base64url-encoded clientDataJSON to JSON | ||
*/ | ||
@@ -4,0 +4,0 @@ export default function decodeClientDataJSON(data: string): ClientDataJSON; |
@@ -6,12 +6,12 @@ "use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const asciiToBinary_1 = __importDefault(require("./asciiToBinary")); | ||
const base64url_1 = __importDefault(require("base64url")); | ||
/** | ||
* Decode an authenticator's base64-encoded clientDataJSON to JSON | ||
* Decode an authenticator's base64url-encoded clientDataJSON to JSON | ||
*/ | ||
function decodeClientDataJSON(data) { | ||
const toString = asciiToBinary_1.default(data); | ||
const toString = base64url_1.default.decode(data); | ||
const clientData = JSON.parse(toString); | ||
// `challenge` will be Base64-encoded here. Decode it for easier comparisons with what is provided | ||
// as the expected value | ||
clientData.challenge = Buffer.from(clientData.challenge, 'base64').toString('ascii'); | ||
// `challenge` will be Base64URL-encoded here. Decode it for easier comparisons with what is | ||
// provided as the expected value | ||
clientData.challenge = base64url_1.default.decode(clientData.challenge); | ||
return clientData; | ||
@@ -18,0 +18,0 @@ } |
@@ -6,3 +6,3 @@ /// <reference types="node" /> | ||
export default function parseAuthenticatorData(authData: Buffer): ParsedAuthenticatorData; | ||
declare type ParsedAuthenticatorData = { | ||
export declare type ParsedAuthenticatorData = { | ||
rpIdHash: Buffer; | ||
@@ -21,4 +21,3 @@ flagsBuf: Buffer; | ||
credentialID?: Buffer; | ||
COSEPublicKey?: Buffer; | ||
credentialPublicKey?: Buffer; | ||
}; | ||
export {}; |
@@ -25,3 +25,3 @@ "use strict"; | ||
let credentialID = undefined; | ||
let COSEPublicKey = undefined; | ||
let credentialPublicKey = undefined; | ||
if (flags.at) { | ||
@@ -35,3 +35,3 @@ aaguid = intBuffer.slice(0, 16); | ||
intBuffer = intBuffer.slice(credIDLen); | ||
COSEPublicKey = intBuffer; | ||
credentialPublicKey = intBuffer; | ||
} | ||
@@ -46,3 +46,3 @@ return { | ||
credentialID, | ||
COSEPublicKey, | ||
credentialPublicKey, | ||
}; | ||
@@ -49,0 +49,0 @@ } |
{ | ||
"name": "@simplewebauthn/server", | ||
"version": "0.5.1", | ||
"version": "0.6.0", | ||
"description": "SimpleWebAuthn for Servers", | ||
@@ -29,3 +29,3 @@ "main": "dist/index.js", | ||
"dependencies": { | ||
"@simplewebauthn/typescript-types": "^0.5.1", | ||
"@simplewebauthn/typescript-types": "^0.6.0", | ||
"base64url": "^3.0.1", | ||
@@ -37,3 +37,3 @@ "cbor": "^5.0.2", | ||
}, | ||
"gitHead": "607fbc9c63db6de0477eefc23618a717f075d15b" | ||
"gitHead": "2180c772f4aa6de6d977710bf965edfaa1cf6737" | ||
} |
@@ -33,3 +33,3 @@ <!-- omit in toc --> | ||
Lower-level API documentation for the methods in this library is available [here](https://simplewebauthn.netlify.app/modules/_simplewebauthn_server.html). | ||
Lower-level API documentation for the methods in this library is available [here](https://docs.simplewebauthn.dev/modules/_simplewebauthn_server.html). | ||
@@ -36,0 +36,0 @@ |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
1200
74894
53
+ Added@simplewebauthn/typescript-types@0.6.0(transitive)
- Removed@simplewebauthn/typescript-types@0.5.1(transitive)