@simplewebauthn/server
Advanced tools
Comparing version 10.0.0 to 10.0.1
@@ -0,1 +1,2 @@ | ||
import { COSECRV } from '../../cose.js'; | ||
/** | ||
@@ -6,2 +7,2 @@ * In WebAuthn, EC2 signatures are wrapped in ASN.1 structure so we need to peel r and s apart. | ||
*/ | ||
export declare function unwrapEC2Signature(signature: Uint8Array): Uint8Array; | ||
export declare function unwrapEC2Signature(signature: Uint8Array, crv: COSECRV): Uint8Array; |
import { AsnParser, ECDSASigValue } from '../../../deps.js'; | ||
import { COSECRV } from '../../cose.js'; | ||
import { isoUint8Array } from '../index.js'; | ||
@@ -8,24 +9,65 @@ /** | ||
*/ | ||
export function unwrapEC2Signature(signature) { | ||
export function unwrapEC2Signature(signature, crv) { | ||
const parsedSignature = AsnParser.parse(signature, ECDSASigValue); | ||
let rBytes = new Uint8Array(parsedSignature.r); | ||
let sBytes = new Uint8Array(parsedSignature.s); | ||
if (shouldRemoveLeadingZero(rBytes)) { | ||
rBytes = rBytes.slice(1); | ||
} | ||
if (shouldRemoveLeadingZero(sBytes)) { | ||
sBytes = sBytes.slice(1); | ||
} | ||
const finalSignature = isoUint8Array.concat([rBytes, sBytes]); | ||
const rBytes = new Uint8Array(parsedSignature.r); | ||
const sBytes = new Uint8Array(parsedSignature.s); | ||
const componentLength = getSignatureComponentLength(crv); | ||
const rNormalizedBytes = toNormalizedBytes(rBytes, componentLength); | ||
const sNormalizedBytes = toNormalizedBytes(sBytes, componentLength); | ||
const finalSignature = isoUint8Array.concat([ | ||
rNormalizedBytes, | ||
sNormalizedBytes, | ||
]); | ||
return finalSignature; | ||
} | ||
/** | ||
* Determine if the DER-specific `00` byte at the start of an ECDSA signature byte sequence | ||
* should be removed based on the following logic: | ||
* The SubtleCrypto Web Crypto API expects ECDSA signatures with `r` and `s` values to be encoded | ||
* to a specific length depending on the order of the curve. This function returns the expected | ||
* byte-length for each of the `r` and `s` signature components. | ||
* | ||
* "If the leading byte is 0x0, and the the high order bit on the second byte is not set to 0, | ||
* then remove the leading 0x0 byte" | ||
* See <https://www.w3.org/TR/WebCryptoAPI/#ecdsa-operations> | ||
*/ | ||
function shouldRemoveLeadingZero(bytes) { | ||
return bytes[0] === 0x0 && (bytes[1] & (1 << 7)) !== 0; | ||
function getSignatureComponentLength(crv) { | ||
switch (crv) { | ||
case COSECRV.P256: | ||
return 32; | ||
case COSECRV.P384: | ||
return 48; | ||
case COSECRV.P521: | ||
return 66; | ||
default: | ||
throw new Error(`Unexpected COSE crv value of ${crv} (EC2)`); | ||
} | ||
} | ||
/** | ||
* Converts the ASN.1 integer representation to bytes of a specific length `n`. | ||
* | ||
* DER encodes integers as big-endian byte arrays, with as small as possible representation and | ||
* requires a leading `0` byte to disambiguate between negative and positive numbers. This means | ||
* that `r` and `s` can potentially not be the expected byte-length that is needed by the | ||
* SubtleCrypto Web Crypto API: if there are leading `0`s it can be shorter than expected, and if | ||
* it has a leading `1` bit, it can be one byte longer. | ||
* | ||
* See <https://www.itu.int/rec/T-REC-X.690-202102-I/en> | ||
* See <https://www.w3.org/TR/WebCryptoAPI/#ecdsa-operations> | ||
*/ | ||
function toNormalizedBytes(bytes, componentLength) { | ||
let normalizedBytes; | ||
if (bytes.length < componentLength) { | ||
// In case the bytes are shorter than expected, we need to pad it with leading `0`s. | ||
normalizedBytes = new Uint8Array(componentLength); | ||
normalizedBytes.set(bytes, componentLength - bytes.length); | ||
} | ||
else if (bytes.length === componentLength) { | ||
normalizedBytes = bytes; | ||
} | ||
else if (bytes.length === componentLength + 1 && bytes[0] === 0 && (bytes[1] & 0x80) === 0x80) { | ||
// The bytes contain a leading `0` to encode that the integer is positive. This leading `0` | ||
// needs to be removed for compatibility with the SubtleCrypto Web Crypto API. | ||
normalizedBytes = bytes.subarray(1); | ||
} | ||
else { | ||
throw new Error(`Invalid signature component length ${bytes.length}, expected ${componentLength}`); | ||
} | ||
return normalizedBytes; | ||
} |
@@ -1,2 +0,2 @@ | ||
import { COSEKEYS, isCOSEPublicKeyEC2, isCOSEPublicKeyOKP, isCOSEPublicKeyRSA, } from '../../cose.js'; | ||
import { COSEKEYS, isCOSECrv, isCOSEPublicKeyEC2, isCOSEPublicKeyOKP, isCOSEPublicKeyRSA, } from '../../cose.js'; | ||
import { verifyEC2 } from './verifyEC2.js'; | ||
@@ -12,3 +12,7 @@ import { verifyRSA } from './verifyRSA.js'; | ||
if (isCOSEPublicKeyEC2(cosePublicKey)) { | ||
const unwrappedSignature = unwrapEC2Signature(signature); | ||
const crv = cosePublicKey.get(COSEKEYS.crv); | ||
if (!isCOSECrv(crv)) { | ||
throw new Error(`unknown COSE curve ${crv}`); | ||
} | ||
const unwrappedSignature = unwrapEC2Signature(signature, crv); | ||
return verifyEC2({ | ||
@@ -15,0 +19,0 @@ cosePublicKey, |
@@ -5,3 +5,3 @@ { | ||
"name": "@simplewebauthn/server", | ||
"version": "10.0.0", | ||
"version": "10.0.1", | ||
"description": "SimpleWebAuthn for Servers", | ||
@@ -8,0 +8,0 @@ "license": "MIT", |
@@ -0,1 +1,2 @@ | ||
import { COSECRV } from '../../cose.js'; | ||
/** | ||
@@ -6,2 +7,2 @@ * In WebAuthn, EC2 signatures are wrapped in ASN.1 structure so we need to peel r and s apart. | ||
*/ | ||
export declare function unwrapEC2Signature(signature: Uint8Array): Uint8Array; | ||
export declare function unwrapEC2Signature(signature: Uint8Array, crv: COSECRV): Uint8Array; |
@@ -5,2 +5,3 @@ "use strict"; | ||
const deps_js_1 = require("../../../deps.js"); | ||
const cose_js_1 = require("../../cose.js"); | ||
const index_js_1 = require("../index.js"); | ||
@@ -12,13 +13,13 @@ /** | ||
*/ | ||
function unwrapEC2Signature(signature) { | ||
function unwrapEC2Signature(signature, crv) { | ||
const parsedSignature = deps_js_1.AsnParser.parse(signature, deps_js_1.ECDSASigValue); | ||
let rBytes = new Uint8Array(parsedSignature.r); | ||
let sBytes = new Uint8Array(parsedSignature.s); | ||
if (shouldRemoveLeadingZero(rBytes)) { | ||
rBytes = rBytes.slice(1); | ||
} | ||
if (shouldRemoveLeadingZero(sBytes)) { | ||
sBytes = sBytes.slice(1); | ||
} | ||
const finalSignature = index_js_1.isoUint8Array.concat([rBytes, sBytes]); | ||
const rBytes = new Uint8Array(parsedSignature.r); | ||
const sBytes = new Uint8Array(parsedSignature.s); | ||
const componentLength = getSignatureComponentLength(crv); | ||
const rNormalizedBytes = toNormalizedBytes(rBytes, componentLength); | ||
const sNormalizedBytes = toNormalizedBytes(sBytes, componentLength); | ||
const finalSignature = index_js_1.isoUint8Array.concat([ | ||
rNormalizedBytes, | ||
sNormalizedBytes, | ||
]); | ||
return finalSignature; | ||
@@ -28,10 +29,51 @@ } | ||
/** | ||
* Determine if the DER-specific `00` byte at the start of an ECDSA signature byte sequence | ||
* should be removed based on the following logic: | ||
* The SubtleCrypto Web Crypto API expects ECDSA signatures with `r` and `s` values to be encoded | ||
* to a specific length depending on the order of the curve. This function returns the expected | ||
* byte-length for each of the `r` and `s` signature components. | ||
* | ||
* "If the leading byte is 0x0, and the the high order bit on the second byte is not set to 0, | ||
* then remove the leading 0x0 byte" | ||
* See <https://www.w3.org/TR/WebCryptoAPI/#ecdsa-operations> | ||
*/ | ||
function shouldRemoveLeadingZero(bytes) { | ||
return bytes[0] === 0x0 && (bytes[1] & (1 << 7)) !== 0; | ||
function getSignatureComponentLength(crv) { | ||
switch (crv) { | ||
case cose_js_1.COSECRV.P256: | ||
return 32; | ||
case cose_js_1.COSECRV.P384: | ||
return 48; | ||
case cose_js_1.COSECRV.P521: | ||
return 66; | ||
default: | ||
throw new Error(`Unexpected COSE crv value of ${crv} (EC2)`); | ||
} | ||
} | ||
/** | ||
* Converts the ASN.1 integer representation to bytes of a specific length `n`. | ||
* | ||
* DER encodes integers as big-endian byte arrays, with as small as possible representation and | ||
* requires a leading `0` byte to disambiguate between negative and positive numbers. This means | ||
* that `r` and `s` can potentially not be the expected byte-length that is needed by the | ||
* SubtleCrypto Web Crypto API: if there are leading `0`s it can be shorter than expected, and if | ||
* it has a leading `1` bit, it can be one byte longer. | ||
* | ||
* See <https://www.itu.int/rec/T-REC-X.690-202102-I/en> | ||
* See <https://www.w3.org/TR/WebCryptoAPI/#ecdsa-operations> | ||
*/ | ||
function toNormalizedBytes(bytes, componentLength) { | ||
let normalizedBytes; | ||
if (bytes.length < componentLength) { | ||
// In case the bytes are shorter than expected, we need to pad it with leading `0`s. | ||
normalizedBytes = new Uint8Array(componentLength); | ||
normalizedBytes.set(bytes, componentLength - bytes.length); | ||
} | ||
else if (bytes.length === componentLength) { | ||
normalizedBytes = bytes; | ||
} | ||
else if (bytes.length === componentLength + 1 && bytes[0] === 0 && (bytes[1] & 0x80) === 0x80) { | ||
// The bytes contain a leading `0` to encode that the integer is positive. This leading `0` | ||
// needs to be removed for compatibility with the SubtleCrypto Web Crypto API. | ||
normalizedBytes = bytes.subarray(1); | ||
} | ||
else { | ||
throw new Error(`Invalid signature component length ${bytes.length}, expected ${componentLength}`); | ||
} | ||
return normalizedBytes; | ||
} |
@@ -15,3 +15,7 @@ "use strict"; | ||
if ((0, cose_js_1.isCOSEPublicKeyEC2)(cosePublicKey)) { | ||
const unwrappedSignature = (0, unwrapEC2Signature_js_1.unwrapEC2Signature)(signature); | ||
const crv = cosePublicKey.get(cose_js_1.COSEKEYS.crv); | ||
if (!(0, cose_js_1.isCOSECrv)(crv)) { | ||
throw new Error(`unknown COSE curve ${crv}`); | ||
} | ||
const unwrappedSignature = (0, unwrapEC2Signature_js_1.unwrapEC2Signature)(signature, crv); | ||
return (0, verifyEC2_js_1.verifyEC2)({ | ||
@@ -18,0 +22,0 @@ cosePublicKey, |
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
481990
11097