mirror of https://github.com/ghostfolio/ghostfolio
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
76 lines
3.3 KiB
76 lines
3.3 KiB
"use strict";
|
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
exports.unwrapEC2Signature = unwrapEC2Signature;
|
|
const asn1_schema_1 = require("@peculiar/asn1-schema");
|
|
const asn1_ecc_1 = require("@peculiar/asn1-ecc");
|
|
const cose_js_1 = require("../../cose.js");
|
|
const index_js_1 = require("../index.js");
|
|
/**
|
|
* In WebAuthn, EC2 signatures are wrapped in ASN.1 structure so we need to peel r and s apart.
|
|
*
|
|
* See https://www.w3.org/TR/webauthn-2/#sctn-signature-attestation-types
|
|
*/
|
|
function unwrapEC2Signature(signature, crv) {
|
|
const parsedSignature = asn1_schema_1.AsnParser.parse(signature, asn1_ecc_1.ECDSASigValue);
|
|
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;
|
|
}
|
|
/**
|
|
* 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.
|
|
*
|
|
* See <https://www.w3.org/TR/WebCryptoAPI/#ecdsa-operations>
|
|
*/
|
|
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;
|
|
}
|
|
|