@simplewebauthn/server 10.0.0 → 10.0.1
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/helpers/iso/isoCrypto/unwrapEC2Signature.d.ts +2 -1
- package/esm/helpers/iso/isoCrypto/unwrapEC2Signature.js +58 -16
- package/esm/helpers/iso/isoCrypto/verify.js +6 -2
- package/package.json +1 -1
- package/script/helpers/iso/isoCrypto/unwrapEC2Signature.d.ts +2 -1
- package/script/helpers/iso/isoCrypto/unwrapEC2Signature.js +58 -16
- package/script/helpers/iso/isoCrypto/verify.js +5 -1
|
@@ -1,6 +1,7 @@
|
|
|
1
|
+
import { COSECRV } from '../../cose.js';
|
|
1
2
|
/**
|
|
2
3
|
* In WebAuthn, EC2 signatures are wrapped in ASN.1 structure so we need to peel r and s apart.
|
|
3
4
|
*
|
|
4
5
|
* See https://www.w3.org/TR/webauthn-2/#sctn-signature-attestation-types
|
|
5
6
|
*/
|
|
6
|
-
export declare function unwrapEC2Signature(signature: Uint8Array): Uint8Array;
|
|
7
|
+
export declare function unwrapEC2Signature(signature: Uint8Array, crv: COSECRV): Uint8Array;
|
|
@@ -1,30 +1,72 @@
|
|
|
1
1
|
import { AsnParser, ECDSASigValue } from '../../../deps.js';
|
|
2
|
+
import { COSECRV } from '../../cose.js';
|
|
2
3
|
import { isoUint8Array } from '../index.js';
|
|
3
4
|
/**
|
|
4
5
|
* In WebAuthn, EC2 signatures are wrapped in ASN.1 structure so we need to peel r and s apart.
|
|
5
6
|
*
|
|
6
7
|
* See https://www.w3.org/TR/webauthn-2/#sctn-signature-attestation-types
|
|
7
8
|
*/
|
|
8
|
-
export function unwrapEC2Signature(signature) {
|
|
9
|
+
export function unwrapEC2Signature(signature, crv) {
|
|
9
10
|
const parsedSignature = AsnParser.parse(signature, ECDSASigValue);
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
11
|
+
const rBytes = new Uint8Array(parsedSignature.r);
|
|
12
|
+
const sBytes = new Uint8Array(parsedSignature.s);
|
|
13
|
+
const componentLength = getSignatureComponentLength(crv);
|
|
14
|
+
const rNormalizedBytes = toNormalizedBytes(rBytes, componentLength);
|
|
15
|
+
const sNormalizedBytes = toNormalizedBytes(sBytes, componentLength);
|
|
16
|
+
const finalSignature = isoUint8Array.concat([
|
|
17
|
+
rNormalizedBytes,
|
|
18
|
+
sNormalizedBytes,
|
|
19
|
+
]);
|
|
19
20
|
return finalSignature;
|
|
20
21
|
}
|
|
21
22
|
/**
|
|
22
|
-
*
|
|
23
|
-
*
|
|
23
|
+
* The SubtleCrypto Web Crypto API expects ECDSA signatures with `r` and `s` values to be encoded
|
|
24
|
+
* to a specific length depending on the order of the curve. This function returns the expected
|
|
25
|
+
* byte-length for each of the `r` and `s` signature components.
|
|
26
|
+
*
|
|
27
|
+
* See <https://www.w3.org/TR/WebCryptoAPI/#ecdsa-operations>
|
|
28
|
+
*/
|
|
29
|
+
function getSignatureComponentLength(crv) {
|
|
30
|
+
switch (crv) {
|
|
31
|
+
case COSECRV.P256:
|
|
32
|
+
return 32;
|
|
33
|
+
case COSECRV.P384:
|
|
34
|
+
return 48;
|
|
35
|
+
case COSECRV.P521:
|
|
36
|
+
return 66;
|
|
37
|
+
default:
|
|
38
|
+
throw new Error(`Unexpected COSE crv value of ${crv} (EC2)`);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Converts the ASN.1 integer representation to bytes of a specific length `n`.
|
|
43
|
+
*
|
|
44
|
+
* DER encodes integers as big-endian byte arrays, with as small as possible representation and
|
|
45
|
+
* requires a leading `0` byte to disambiguate between negative and positive numbers. This means
|
|
46
|
+
* that `r` and `s` can potentially not be the expected byte-length that is needed by the
|
|
47
|
+
* SubtleCrypto Web Crypto API: if there are leading `0`s it can be shorter than expected, and if
|
|
48
|
+
* it has a leading `1` bit, it can be one byte longer.
|
|
24
49
|
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
50
|
+
* See <https://www.itu.int/rec/T-REC-X.690-202102-I/en>
|
|
51
|
+
* See <https://www.w3.org/TR/WebCryptoAPI/#ecdsa-operations>
|
|
27
52
|
*/
|
|
28
|
-
function
|
|
29
|
-
|
|
53
|
+
function toNormalizedBytes(bytes, componentLength) {
|
|
54
|
+
let normalizedBytes;
|
|
55
|
+
if (bytes.length < componentLength) {
|
|
56
|
+
// In case the bytes are shorter than expected, we need to pad it with leading `0`s.
|
|
57
|
+
normalizedBytes = new Uint8Array(componentLength);
|
|
58
|
+
normalizedBytes.set(bytes, componentLength - bytes.length);
|
|
59
|
+
}
|
|
60
|
+
else if (bytes.length === componentLength) {
|
|
61
|
+
normalizedBytes = bytes;
|
|
62
|
+
}
|
|
63
|
+
else if (bytes.length === componentLength + 1 && bytes[0] === 0 && (bytes[1] & 0x80) === 0x80) {
|
|
64
|
+
// The bytes contain a leading `0` to encode that the integer is positive. This leading `0`
|
|
65
|
+
// needs to be removed for compatibility with the SubtleCrypto Web Crypto API.
|
|
66
|
+
normalizedBytes = bytes.subarray(1);
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
throw new Error(`Invalid signature component length ${bytes.length}, expected ${componentLength}`);
|
|
70
|
+
}
|
|
71
|
+
return normalizedBytes;
|
|
30
72
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { COSEKEYS, isCOSEPublicKeyEC2, isCOSEPublicKeyOKP, isCOSEPublicKeyRSA, } from '../../cose.js';
|
|
1
|
+
import { COSEKEYS, isCOSECrv, isCOSEPublicKeyEC2, isCOSEPublicKeyOKP, isCOSEPublicKeyRSA, } from '../../cose.js';
|
|
2
2
|
import { verifyEC2 } from './verifyEC2.js';
|
|
3
3
|
import { verifyRSA } from './verifyRSA.js';
|
|
4
4
|
import { verifyOKP } from './verifyOKP.js';
|
|
@@ -9,7 +9,11 @@ import { unwrapEC2Signature } from './unwrapEC2Signature.js';
|
|
|
9
9
|
export function verify(opts) {
|
|
10
10
|
const { cosePublicKey, signature, data, shaHashOverride } = opts;
|
|
11
11
|
if (isCOSEPublicKeyEC2(cosePublicKey)) {
|
|
12
|
-
const
|
|
12
|
+
const crv = cosePublicKey.get(COSEKEYS.crv);
|
|
13
|
+
if (!isCOSECrv(crv)) {
|
|
14
|
+
throw new Error(`unknown COSE curve ${crv}`);
|
|
15
|
+
}
|
|
16
|
+
const unwrappedSignature = unwrapEC2Signature(signature, crv);
|
|
13
17
|
return verifyEC2({
|
|
14
18
|
cosePublicKey,
|
|
15
19
|
signature: unwrappedSignature,
|
package/package.json
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
+
import { COSECRV } from '../../cose.js';
|
|
1
2
|
/**
|
|
2
3
|
* In WebAuthn, EC2 signatures are wrapped in ASN.1 structure so we need to peel r and s apart.
|
|
3
4
|
*
|
|
4
5
|
* See https://www.w3.org/TR/webauthn-2/#sctn-signature-attestation-types
|
|
5
6
|
*/
|
|
6
|
-
export declare function unwrapEC2Signature(signature: Uint8Array): Uint8Array;
|
|
7
|
+
export declare function unwrapEC2Signature(signature: Uint8Array, crv: COSECRV): Uint8Array;
|
|
@@ -2,33 +2,75 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.unwrapEC2Signature = void 0;
|
|
4
4
|
const deps_js_1 = require("../../../deps.js");
|
|
5
|
+
const cose_js_1 = require("../../cose.js");
|
|
5
6
|
const index_js_1 = require("../index.js");
|
|
6
7
|
/**
|
|
7
8
|
* In WebAuthn, EC2 signatures are wrapped in ASN.1 structure so we need to peel r and s apart.
|
|
8
9
|
*
|
|
9
10
|
* See https://www.w3.org/TR/webauthn-2/#sctn-signature-attestation-types
|
|
10
11
|
*/
|
|
11
|
-
function unwrapEC2Signature(signature) {
|
|
12
|
+
function unwrapEC2Signature(signature, crv) {
|
|
12
13
|
const parsedSignature = deps_js_1.AsnParser.parse(signature, deps_js_1.ECDSASigValue);
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
14
|
+
const rBytes = new Uint8Array(parsedSignature.r);
|
|
15
|
+
const sBytes = new Uint8Array(parsedSignature.s);
|
|
16
|
+
const componentLength = getSignatureComponentLength(crv);
|
|
17
|
+
const rNormalizedBytes = toNormalizedBytes(rBytes, componentLength);
|
|
18
|
+
const sNormalizedBytes = toNormalizedBytes(sBytes, componentLength);
|
|
19
|
+
const finalSignature = index_js_1.isoUint8Array.concat([
|
|
20
|
+
rNormalizedBytes,
|
|
21
|
+
sNormalizedBytes,
|
|
22
|
+
]);
|
|
22
23
|
return finalSignature;
|
|
23
24
|
}
|
|
24
25
|
exports.unwrapEC2Signature = unwrapEC2Signature;
|
|
25
26
|
/**
|
|
26
|
-
*
|
|
27
|
-
*
|
|
27
|
+
* The SubtleCrypto Web Crypto API expects ECDSA signatures with `r` and `s` values to be encoded
|
|
28
|
+
* to a specific length depending on the order of the curve. This function returns the expected
|
|
29
|
+
* byte-length for each of the `r` and `s` signature components.
|
|
30
|
+
*
|
|
31
|
+
* See <https://www.w3.org/TR/WebCryptoAPI/#ecdsa-operations>
|
|
32
|
+
*/
|
|
33
|
+
function getSignatureComponentLength(crv) {
|
|
34
|
+
switch (crv) {
|
|
35
|
+
case cose_js_1.COSECRV.P256:
|
|
36
|
+
return 32;
|
|
37
|
+
case cose_js_1.COSECRV.P384:
|
|
38
|
+
return 48;
|
|
39
|
+
case cose_js_1.COSECRV.P521:
|
|
40
|
+
return 66;
|
|
41
|
+
default:
|
|
42
|
+
throw new Error(`Unexpected COSE crv value of ${crv} (EC2)`);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Converts the ASN.1 integer representation to bytes of a specific length `n`.
|
|
47
|
+
*
|
|
48
|
+
* DER encodes integers as big-endian byte arrays, with as small as possible representation and
|
|
49
|
+
* requires a leading `0` byte to disambiguate between negative and positive numbers. This means
|
|
50
|
+
* that `r` and `s` can potentially not be the expected byte-length that is needed by the
|
|
51
|
+
* SubtleCrypto Web Crypto API: if there are leading `0`s it can be shorter than expected, and if
|
|
52
|
+
* it has a leading `1` bit, it can be one byte longer.
|
|
28
53
|
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
54
|
+
* See <https://www.itu.int/rec/T-REC-X.690-202102-I/en>
|
|
55
|
+
* See <https://www.w3.org/TR/WebCryptoAPI/#ecdsa-operations>
|
|
31
56
|
*/
|
|
32
|
-
function
|
|
33
|
-
|
|
57
|
+
function toNormalizedBytes(bytes, componentLength) {
|
|
58
|
+
let normalizedBytes;
|
|
59
|
+
if (bytes.length < componentLength) {
|
|
60
|
+
// In case the bytes are shorter than expected, we need to pad it with leading `0`s.
|
|
61
|
+
normalizedBytes = new Uint8Array(componentLength);
|
|
62
|
+
normalizedBytes.set(bytes, componentLength - bytes.length);
|
|
63
|
+
}
|
|
64
|
+
else if (bytes.length === componentLength) {
|
|
65
|
+
normalizedBytes = bytes;
|
|
66
|
+
}
|
|
67
|
+
else if (bytes.length === componentLength + 1 && bytes[0] === 0 && (bytes[1] & 0x80) === 0x80) {
|
|
68
|
+
// The bytes contain a leading `0` to encode that the integer is positive. This leading `0`
|
|
69
|
+
// needs to be removed for compatibility with the SubtleCrypto Web Crypto API.
|
|
70
|
+
normalizedBytes = bytes.subarray(1);
|
|
71
|
+
}
|
|
72
|
+
else {
|
|
73
|
+
throw new Error(`Invalid signature component length ${bytes.length}, expected ${componentLength}`);
|
|
74
|
+
}
|
|
75
|
+
return normalizedBytes;
|
|
34
76
|
}
|
|
@@ -12,7 +12,11 @@ const unwrapEC2Signature_js_1 = require("./unwrapEC2Signature.js");
|
|
|
12
12
|
function verify(opts) {
|
|
13
13
|
const { cosePublicKey, signature, data, shaHashOverride } = opts;
|
|
14
14
|
if ((0, cose_js_1.isCOSEPublicKeyEC2)(cosePublicKey)) {
|
|
15
|
-
const
|
|
15
|
+
const crv = cosePublicKey.get(cose_js_1.COSEKEYS.crv);
|
|
16
|
+
if (!(0, cose_js_1.isCOSECrv)(crv)) {
|
|
17
|
+
throw new Error(`unknown COSE curve ${crv}`);
|
|
18
|
+
}
|
|
19
|
+
const unwrappedSignature = (0, unwrapEC2Signature_js_1.unwrapEC2Signature)(signature, crv);
|
|
16
20
|
return (0, verifyEC2_js_1.verifyEC2)({
|
|
17
21
|
cosePublicKey,
|
|
18
22
|
signature: unwrappedSignature,
|