@simplewebauthn/server 8.1.1 → 8.3.2

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.
@@ -5,6 +5,7 @@ export type VerifyAuthenticationResponseOpts = {
5
5
  expectedChallenge: string | ((challenge: string) => boolean | Promise<boolean>);
6
6
  expectedOrigin: string | string[];
7
7
  expectedRPID: string | string[];
8
+ expectedType?: string | string[];
8
9
  authenticator: AuthenticatorDevice;
9
10
  requireUserVerification?: boolean;
10
11
  advancedFIDOConfig?: {
@@ -21,6 +22,7 @@ export type VerifyAuthenticationResponseOpts = {
21
22
  * `generateAuthenticationOptions()`
22
23
  * @param expectedOrigin Website URL (or array of URLs) that the registration should have occurred on
23
24
  * @param expectedRPID RP ID (or array of IDs) that was specified in the registration options
25
+ * @param expectedType (Optional) The response type expected ('webauthn.get')
24
26
  * @param authenticator An internal {@link AuthenticatorDevice} matching the credential's ID
25
27
  * @param requireUserVerification (Optional) Enforce user verification by the authenticator
26
28
  * (via PIN, fingerprint, etc...)
@@ -15,6 +15,7 @@ import { isoBase64URL, isoUint8Array } from '../helpers/iso/index.js';
15
15
  * `generateAuthenticationOptions()`
16
16
  * @param expectedOrigin Website URL (or array of URLs) that the registration should have occurred on
17
17
  * @param expectedRPID RP ID (or array of IDs) that was specified in the registration options
18
+ * @param expectedType (Optional) The response type expected ('webauthn.get')
18
19
  * @param authenticator An internal {@link AuthenticatorDevice} matching the credential's ID
19
20
  * @param requireUserVerification (Optional) Enforce user verification by the authenticator
20
21
  * (via PIN, fingerprint, etc...)
@@ -25,7 +26,7 @@ import { isoBase64URL, isoUint8Array } from '../helpers/iso/index.js';
25
26
  * unless this value is `"required"`
26
27
  */
27
28
  export async function verifyAuthenticationResponse(options) {
28
- const { response, expectedChallenge, expectedOrigin, expectedRPID, authenticator, requireUserVerification = true, advancedFIDOConfig, } = options;
29
+ const { response, expectedChallenge, expectedOrigin, expectedRPID, expectedType, authenticator, requireUserVerification = true, advancedFIDOConfig, } = options;
29
30
  const { id, rawId, type: credentialType, response: assertionResponse } = response;
30
31
  // Ensure credential specified an ID
31
32
  if (!id) {
@@ -48,7 +49,18 @@ export async function verifyAuthenticationResponse(options) {
48
49
  const clientDataJSON = decodeClientDataJSON(assertionResponse.clientDataJSON);
49
50
  const { type, origin, challenge, tokenBinding } = clientDataJSON;
50
51
  // Make sure we're handling an authentication
51
- if (type !== 'webauthn.get') {
52
+ if (Array.isArray(expectedType)) {
53
+ if (!expectedType.includes(type)) {
54
+ const joinedExpectedType = expectedType.join(', ');
55
+ throw new Error(`Unexpected authentication response type "${type}", expected one of: ${joinedExpectedType}`);
56
+ }
57
+ }
58
+ else if (expectedType) {
59
+ if (type !== expectedType) {
60
+ throw new Error(`Unexpected authentication response type "${type}", expected "${expectedType}"`);
61
+ }
62
+ }
63
+ else if (type !== 'webauthn.get') {
52
64
  throw new Error(`Unexpected authentication response type: ${type}`);
53
65
  }
54
66
  // Ensure the device provided the challenge we gave it
package/esm/deps.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  export type { AttestationConveyancePreference, AuthenticationExtensionsClientInputs, AuthenticationResponseJSON, AuthenticatorDevice, AuthenticatorSelectionCriteria, Base64URLString, COSEAlgorithmIdentifier, CredentialDeviceType, Crypto, PublicKeyCredentialCreationOptionsJSON, PublicKeyCredentialDescriptorFuture, PublicKeyCredentialParameters, PublicKeyCredentialRequestOptionsJSON, RegistrationResponseJSON, UserVerificationRequirement, } from '@simplewebauthn/typescript-types';
2
- export * as cborx from 'cbor-x';
2
+ export * as cborx from 'cbor-x/encode';
3
3
  export { default as base64 } from '@hexagon/base64';
4
4
  export { fetch as crossFetch } from 'cross-fetch';
5
5
  export { AsnParser, AsnSerializer } from '@peculiar/asn1-schema';
package/esm/deps.js CHANGED
@@ -1,5 +1,5 @@
1
1
  // cbor (a.k.a. cbor-x in Node land)
2
- export * as cborx from 'cbor-x';
2
+ export * as cborx from 'cbor-x/encode';
3
3
  // b64 (a.k.a. @hexagon/base64 in Node land)
4
4
  export { default as base64 } from '@hexagon/base64';
5
5
  // cross-fetch
@@ -34,6 +34,22 @@ export function parseAuthenticatorData(authData) {
34
34
  const credIDLen = dataView.getUint16(pointer);
35
35
  pointer += 2;
36
36
  credentialID = authData.slice(pointer, pointer += credIDLen);
37
+ /**
38
+ * Firefox 117 incorrectly CBOR-encodes authData when EdDSA (-8) is used for the public key.
39
+ * A CBOR "Map of 3 items" (0xa3) should be "Map of 4 items" (0xa4), and if we manually adjust
40
+ * the single byte there's a good chance the authData can be correctly parsed.
41
+ *
42
+ * This browser release also incorrectly uses the string labels "OKP" and "Ed25519" instead of
43
+ * their integer representations for kty and crv respectively. That's why the COSE public key
44
+ * in the hex below looks so odd.
45
+ */
46
+ // Bytes decode to `{ 1: "OKP", 3: -8, -1: "Ed25519" }` (it's missing key -2 a.k.a. COSEKEYS.x)
47
+ const badEdDSACBOR = isoUint8Array.fromHex('a301634f4b500327206745643235353139');
48
+ const bytesAtCurrentPosition = authData.slice(pointer, pointer + badEdDSACBOR.byteLength);
49
+ if (isoUint8Array.areEqual(badEdDSACBOR, bytesAtCurrentPosition)) {
50
+ // Change the bad CBOR 0xa3 to 0xa4 so that the credential public key can be recognized
51
+ authData[pointer] = 0xa4;
52
+ }
37
53
  // Decode the next CBOR item in the buffer, then re-encode it back to a Buffer
38
54
  const firstDecoded = isoCBOR.decodeFirst(authData.slice(pointer));
39
55
  const firstEncoded = Uint8Array.from(isoCBOR.encode(firstDecoded));
@@ -6,6 +6,7 @@ export type VerifyRegistrationResponseOpts = {
6
6
  expectedChallenge: string | ((challenge: string) => boolean | Promise<boolean>);
7
7
  expectedOrigin: string | string[];
8
8
  expectedRPID?: string | string[];
9
+ expectedType?: string | string[];
9
10
  requireUserVerification?: boolean;
10
11
  supportedAlgorithmIDs?: COSEAlgorithmIdentifier[];
11
12
  };
@@ -19,6 +20,7 @@ export type VerifyRegistrationResponseOpts = {
19
20
  * `generateRegistrationOptions()`
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
23
+ * @param expectedType (Optional) The response type expected ('webauthn.create')
22
24
  * @param requireUserVerification (Optional) Enforce user verification by the authenticator
23
25
  * (via PIN, fingerprint, etc...)
24
26
  * @param supportedAlgorithmIDs Array of numeric COSE algorithm identifiers supported for
@@ -26,13 +26,14 @@ import { verifyAttestationApple } from './verifications/verifyAttestationApple.j
26
26
  * `generateRegistrationOptions()`
27
27
  * @param expectedOrigin Website URL (or array of URLs) that the registration should have occurred on
28
28
  * @param expectedRPID RP ID (or array of IDs) that was specified in the registration options
29
+ * @param expectedType (Optional) The response type expected ('webauthn.create')
29
30
  * @param requireUserVerification (Optional) Enforce user verification by the authenticator
30
31
  * (via PIN, fingerprint, etc...)
31
32
  * @param supportedAlgorithmIDs Array of numeric COSE algorithm identifiers supported for
32
33
  * attestation by this RP. See https://www.iana.org/assignments/cose/cose.xhtml#algorithms
33
34
  */
34
35
  export async function verifyRegistrationResponse(options) {
35
- const { response, expectedChallenge, expectedOrigin, expectedRPID, requireUserVerification = true, supportedAlgorithmIDs = supportedCOSEAlgorithmIdentifiers, } = options;
36
+ const { response, expectedChallenge, expectedOrigin, expectedRPID, expectedType, requireUserVerification = true, supportedAlgorithmIDs = supportedCOSEAlgorithmIdentifiers, } = options;
36
37
  const { id, rawId, type: credentialType, response: attestationResponse } = response;
37
38
  // Ensure credential specified an ID
38
39
  if (!id) {
@@ -49,7 +50,18 @@ export async function verifyRegistrationResponse(options) {
49
50
  const clientDataJSON = decodeClientDataJSON(attestationResponse.clientDataJSON);
50
51
  const { type, origin, challenge, tokenBinding } = clientDataJSON;
51
52
  // Make sure we're handling an registration
52
- if (type !== 'webauthn.create') {
53
+ if (Array.isArray(expectedType)) {
54
+ if (!expectedType.includes(type)) {
55
+ const joinedExpectedType = expectedType.join(', ');
56
+ throw new Error(`Unexpected registration response type "${type}", expected one of: ${joinedExpectedType}`);
57
+ }
58
+ }
59
+ else if (expectedType) {
60
+ if (type !== expectedType) {
61
+ throw new Error(`Unexpected registration response type "${type}", expected "${expectedType}"`);
62
+ }
63
+ }
64
+ else if (type !== 'webauthn.create') {
53
65
  throw new Error(`Unexpected registration response type: ${type}`);
54
66
  }
55
67
  // Ensure the device provided the challenge we gave it
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": "8.1.1",
5
+ "version": "8.3.2",
6
6
  "description": "SimpleWebAuthn for Servers",
7
7
  "license": "MIT",
8
8
  "author": "Matthew Miller <matthew@millerti.me>",
@@ -5,6 +5,7 @@ export type VerifyAuthenticationResponseOpts = {
5
5
  expectedChallenge: string | ((challenge: string) => boolean | Promise<boolean>);
6
6
  expectedOrigin: string | string[];
7
7
  expectedRPID: string | string[];
8
+ expectedType?: string | string[];
8
9
  authenticator: AuthenticatorDevice;
9
10
  requireUserVerification?: boolean;
10
11
  advancedFIDOConfig?: {
@@ -21,6 +22,7 @@ export type VerifyAuthenticationResponseOpts = {
21
22
  * `generateAuthenticationOptions()`
22
23
  * @param expectedOrigin Website URL (or array of URLs) that the registration should have occurred on
23
24
  * @param expectedRPID RP ID (or array of IDs) that was specified in the registration options
25
+ * @param expectedType (Optional) The response type expected ('webauthn.get')
24
26
  * @param authenticator An internal {@link AuthenticatorDevice} matching the credential's ID
25
27
  * @param requireUserVerification (Optional) Enforce user verification by the authenticator
26
28
  * (via PIN, fingerprint, etc...)
@@ -18,6 +18,7 @@ const index_js_1 = require("../helpers/iso/index.js");
18
18
  * `generateAuthenticationOptions()`
19
19
  * @param expectedOrigin Website URL (or array of URLs) that the registration should have occurred on
20
20
  * @param expectedRPID RP ID (or array of IDs) that was specified in the registration options
21
+ * @param expectedType (Optional) The response type expected ('webauthn.get')
21
22
  * @param authenticator An internal {@link AuthenticatorDevice} matching the credential's ID
22
23
  * @param requireUserVerification (Optional) Enforce user verification by the authenticator
23
24
  * (via PIN, fingerprint, etc...)
@@ -28,7 +29,7 @@ const index_js_1 = require("../helpers/iso/index.js");
28
29
  * unless this value is `"required"`
29
30
  */
30
31
  async function verifyAuthenticationResponse(options) {
31
- const { response, expectedChallenge, expectedOrigin, expectedRPID, authenticator, requireUserVerification = true, advancedFIDOConfig, } = options;
32
+ const { response, expectedChallenge, expectedOrigin, expectedRPID, expectedType, authenticator, requireUserVerification = true, advancedFIDOConfig, } = options;
32
33
  const { id, rawId, type: credentialType, response: assertionResponse } = response;
33
34
  // Ensure credential specified an ID
34
35
  if (!id) {
@@ -51,7 +52,18 @@ async function verifyAuthenticationResponse(options) {
51
52
  const clientDataJSON = (0, decodeClientDataJSON_js_1.decodeClientDataJSON)(assertionResponse.clientDataJSON);
52
53
  const { type, origin, challenge, tokenBinding } = clientDataJSON;
53
54
  // Make sure we're handling an authentication
54
- if (type !== 'webauthn.get') {
55
+ if (Array.isArray(expectedType)) {
56
+ if (!expectedType.includes(type)) {
57
+ const joinedExpectedType = expectedType.join(', ');
58
+ throw new Error(`Unexpected authentication response type "${type}", expected one of: ${joinedExpectedType}`);
59
+ }
60
+ }
61
+ else if (expectedType) {
62
+ if (type !== expectedType) {
63
+ throw new Error(`Unexpected authentication response type "${type}", expected "${expectedType}"`);
64
+ }
65
+ }
66
+ else if (type !== 'webauthn.get') {
55
67
  throw new Error(`Unexpected authentication response type: ${type}`);
56
68
  }
57
69
  // Ensure the device provided the challenge we gave it
package/script/deps.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  export type { AttestationConveyancePreference, AuthenticationExtensionsClientInputs, AuthenticationResponseJSON, AuthenticatorDevice, AuthenticatorSelectionCriteria, Base64URLString, COSEAlgorithmIdentifier, CredentialDeviceType, Crypto, PublicKeyCredentialCreationOptionsJSON, PublicKeyCredentialDescriptorFuture, PublicKeyCredentialParameters, PublicKeyCredentialRequestOptionsJSON, RegistrationResponseJSON, UserVerificationRequirement, } from '@simplewebauthn/typescript-types';
2
- export * as cborx from 'cbor-x';
2
+ export * as cborx from 'cbor-x/encode';
3
3
  export { default as base64 } from '@hexagon/base64';
4
4
  export { fetch as crossFetch } from 'cross-fetch';
5
5
  export { AsnParser, AsnSerializer } from '@peculiar/asn1-schema';
package/script/deps.js CHANGED
@@ -28,7 +28,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
28
28
  Object.defineProperty(exports, "__esModule", { value: true });
29
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.cborx = void 0;
30
30
  // cbor (a.k.a. cbor-x in Node land)
31
- exports.cborx = __importStar(require("cbor-x"));
31
+ exports.cborx = __importStar(require("cbor-x/encode"));
32
32
  // b64 (a.k.a. @hexagon/base64 in Node land)
33
33
  var base64_1 = require("@hexagon/base64");
34
34
  Object.defineProperty(exports, "base64", { enumerable: true, get: function () { return __importDefault(base64_1).default; } });
@@ -37,6 +37,22 @@ function parseAuthenticatorData(authData) {
37
37
  const credIDLen = dataView.getUint16(pointer);
38
38
  pointer += 2;
39
39
  credentialID = authData.slice(pointer, pointer += credIDLen);
40
+ /**
41
+ * Firefox 117 incorrectly CBOR-encodes authData when EdDSA (-8) is used for the public key.
42
+ * A CBOR "Map of 3 items" (0xa3) should be "Map of 4 items" (0xa4), and if we manually adjust
43
+ * the single byte there's a good chance the authData can be correctly parsed.
44
+ *
45
+ * This browser release also incorrectly uses the string labels "OKP" and "Ed25519" instead of
46
+ * their integer representations for kty and crv respectively. That's why the COSE public key
47
+ * in the hex below looks so odd.
48
+ */
49
+ // Bytes decode to `{ 1: "OKP", 3: -8, -1: "Ed25519" }` (it's missing key -2 a.k.a. COSEKEYS.x)
50
+ const badEdDSACBOR = index_js_1.isoUint8Array.fromHex('a301634f4b500327206745643235353139');
51
+ const bytesAtCurrentPosition = authData.slice(pointer, pointer + badEdDSACBOR.byteLength);
52
+ if (index_js_1.isoUint8Array.areEqual(badEdDSACBOR, bytesAtCurrentPosition)) {
53
+ // Change the bad CBOR 0xa3 to 0xa4 so that the credential public key can be recognized
54
+ authData[pointer] = 0xa4;
55
+ }
40
56
  // Decode the next CBOR item in the buffer, then re-encode it back to a Buffer
41
57
  const firstDecoded = index_js_1.isoCBOR.decodeFirst(authData.slice(pointer));
42
58
  const firstEncoded = Uint8Array.from(index_js_1.isoCBOR.encode(firstDecoded));
@@ -6,6 +6,7 @@ export type VerifyRegistrationResponseOpts = {
6
6
  expectedChallenge: string | ((challenge: string) => boolean | Promise<boolean>);
7
7
  expectedOrigin: string | string[];
8
8
  expectedRPID?: string | string[];
9
+ expectedType?: string | string[];
9
10
  requireUserVerification?: boolean;
10
11
  supportedAlgorithmIDs?: COSEAlgorithmIdentifier[];
11
12
  };
@@ -19,6 +20,7 @@ export type VerifyRegistrationResponseOpts = {
19
20
  * `generateRegistrationOptions()`
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
23
+ * @param expectedType (Optional) The response type expected ('webauthn.create')
22
24
  * @param requireUserVerification (Optional) Enforce user verification by the authenticator
23
25
  * (via PIN, fingerprint, etc...)
24
26
  * @param supportedAlgorithmIDs Array of numeric COSE algorithm identifiers supported for
@@ -29,13 +29,14 @@ const verifyAttestationApple_js_1 = require("./verifications/verifyAttestationAp
29
29
  * `generateRegistrationOptions()`
30
30
  * @param expectedOrigin Website URL (or array of URLs) that the registration should have occurred on
31
31
  * @param expectedRPID RP ID (or array of IDs) that was specified in the registration options
32
+ * @param expectedType (Optional) The response type expected ('webauthn.create')
32
33
  * @param requireUserVerification (Optional) Enforce user verification by the authenticator
33
34
  * (via PIN, fingerprint, etc...)
34
35
  * @param supportedAlgorithmIDs Array of numeric COSE algorithm identifiers supported for
35
36
  * attestation by this RP. See https://www.iana.org/assignments/cose/cose.xhtml#algorithms
36
37
  */
37
38
  async function verifyRegistrationResponse(options) {
38
- const { response, expectedChallenge, expectedOrigin, expectedRPID, requireUserVerification = true, supportedAlgorithmIDs = generateRegistrationOptions_js_1.supportedCOSEAlgorithmIdentifiers, } = options;
39
+ const { response, expectedChallenge, expectedOrigin, expectedRPID, expectedType, requireUserVerification = true, supportedAlgorithmIDs = generateRegistrationOptions_js_1.supportedCOSEAlgorithmIdentifiers, } = options;
39
40
  const { id, rawId, type: credentialType, response: attestationResponse } = response;
40
41
  // Ensure credential specified an ID
41
42
  if (!id) {
@@ -52,7 +53,18 @@ async function verifyRegistrationResponse(options) {
52
53
  const clientDataJSON = (0, decodeClientDataJSON_js_1.decodeClientDataJSON)(attestationResponse.clientDataJSON);
53
54
  const { type, origin, challenge, tokenBinding } = clientDataJSON;
54
55
  // Make sure we're handling an registration
55
- if (type !== 'webauthn.create') {
56
+ if (Array.isArray(expectedType)) {
57
+ if (!expectedType.includes(type)) {
58
+ const joinedExpectedType = expectedType.join(', ');
59
+ throw new Error(`Unexpected registration response type "${type}", expected one of: ${joinedExpectedType}`);
60
+ }
61
+ }
62
+ else if (expectedType) {
63
+ if (type !== expectedType) {
64
+ throw new Error(`Unexpected registration response type "${type}", expected "${expectedType}"`);
65
+ }
66
+ }
67
+ else if (type !== 'webauthn.create') {
56
68
  throw new Error(`Unexpected registration response type: ${type}`);
57
69
  }
58
70
  // Ensure the device provided the challenge we gave it