summaryrefslogtreecommitdiffhomepage
path: root/packages/server/src
diff options
context:
space:
mode:
Diffstat (limited to 'packages/server/src')
-rw-r--r--packages/server/src/helpers/iso/isoCrypto/importKey.ts164
-rw-r--r--packages/server/src/helpers/iso/isoCrypto/index.ts1
-rw-r--r--packages/server/src/helpers/iso/isoCrypto/verify.ts105
-rw-r--r--packages/server/src/helpers/iso/isoCrypto/verifyEC2.ts115
-rw-r--r--packages/server/src/helpers/iso/isoCrypto/verifyRSA.ts82
5 files changed, 220 insertions, 247 deletions
diff --git a/packages/server/src/helpers/iso/isoCrypto/importKey.ts b/packages/server/src/helpers/iso/isoCrypto/importKey.ts
index 9e6c82b..b9961d7 100644
--- a/packages/server/src/helpers/iso/isoCrypto/importKey.ts
+++ b/packages/server/src/helpers/iso/isoCrypto/importKey.ts
@@ -1,164 +1,14 @@
import { webcrypto } from 'node:crypto';
-import { SubtleCryptoCrv } from './structs';
-import { normalizeSHAAlgorithm } from './normalizeSHAAlgorithm';
-import { mapCoseAlgToWebCryptoAlg } from './mapCoseAlgToWebCryptoAlg';
-import {
- COSECRV,
- COSEKEYS,
- COSEKTY,
- COSEALG,
- COSEPublicKeyEC2,
- COSEPublicKeyRSA,
- isCOSEAlg,
- isCOSEPublicKeyEC2,
- isCOSEPublicKeyRSA,
-} from '../../convertCOSEtoPKCS';
-import { isoBase64URL } from '../index';
-
-/**
- * Import an EC2 or RSA public key from its COSE representation
- *
- * @param publicKey A `Map` containing COSE-specific public key properties
- * @param rsaHashAlgorithm A SHA hashing identifier for use when verifying signatures with the
- * returned RSA public key (e.g. `"sha1"`, `"sha256"`, etc...), if applicable
- */
-export async function importKey(publicKey: COSEPublicKeyEC2 | COSEPublicKeyRSA, rsaHashAlgorithm?: string): Promise<CryptoKey> {
- const kty = publicKey.get(COSEKEYS.kty);
-
- if (!kty) {
- throw new Error('Public key was missing kty');
- }
-
- if (isCOSEPublicKeyEC2(publicKey)) {
- return importECKey(publicKey);
- }
-
- if (isCOSEPublicKeyRSA(publicKey)) {
- return importRSAKey(publicKey, rsaHashAlgorithm);
- }
-
- throw new Error(`Unable to import public key of kty ${kty}`);
-}
-
-/**
- * Import an EC2 public key from its COSE representation
- */
- async function importECKey(publicKey: COSEPublicKeyEC2): Promise<CryptoKey> {
- const crv = publicKey.get(COSEKEYS.crv);
- const x = publicKey.get(COSEKEYS.x);
- const y = publicKey.get(COSEKEYS.y);
-
- if (!crv) {
- throw new Error('EC2 public key was missing crv');
- }
-
- if (!x) {
- throw new Error('EC2 public key was missing x');
- }
-
- if (!y) {
- throw new Error('EC2 public key was missing y');
- }
-
- /**
- * Convert a COSE crv ID into a corresponding string value that WebCrypto APIs expect
- */
- let _crv: SubtleCryptoCrv;
- if (crv === COSECRV.P256) {
- _crv = 'P-256';
- } else if (crv === COSECRV.P384) {
- _crv = 'P-384';
- } else if (crv === COSECRV.P521) {
- _crv = 'P-521';
- } else {
- throw new Error(`Unexpected COSE crv value of ${crv}`);
- }
-
- const jwk: JsonWebKey = {
- kty: "EC",
- crv: _crv,
- x: isoBase64URL.fromBuffer(x),
- y: isoBase64URL.fromBuffer(y),
- ext: false,
- };
-
- const algorithm: EcKeyImportParams = {
- name: 'ECDSA',
- namedCurve: _crv,
- };
-
- const extractable = false;
-
- const keyUsages: KeyUsage[] = ["verify"];
-
- if (globalThis.crypto) {
- return globalThis.crypto.subtle.importKey('jwk', jwk, algorithm, extractable, keyUsages);
- } else {
- return webcrypto.subtle.importKey('jwk', jwk, algorithm, extractable, keyUsages);
- }
-}
-
-/**
- * Import an RSA public key from its COSE representation
- */
- async function importRSAKey(publicKey: COSEPublicKeyRSA, hashAlgorithm?: string): Promise<CryptoKey> {
- const alg = publicKey.get(COSEKEYS.alg);
- const n = publicKey.get(COSEKEYS.n);
- const e = publicKey.get(COSEKEYS.e);
-
- if (!alg) {
- throw new Error('Public key was missing alg (RSA)');
- }
-
- if (!isCOSEAlg(alg)) {
- throw new Error(`Public key had invalid alg ${alg} (RSA)`);
- }
-
- if (!n) {
- throw new Error('Public key was missing n (RSA)');
- }
-
- if (!e) {
- throw new Error('Public key was missing e (RSA)');
- }
-
- const jwk: JsonWebKey = {
- kty: 'RSA',
- alg: '',
- n: isoBase64URL.fromBuffer(n),
- e: isoBase64URL.fromBuffer(e),
- ext: false,
- };
-
- const keyAlgorithm = {
- name: 'RSASSA-PKCS1-v1_5',
- // This is actually the digest hash that'll get used by `.verify()`
- hash: { name: mapCoseAlgToWebCryptoAlg(alg) },
- };
-
- if (hashAlgorithm) {
- const normalized = normalizeSHAAlgorithm(hashAlgorithm);
- keyAlgorithm.hash.name = normalized;
- }
-
- if (keyAlgorithm.hash.name === 'SHA-256') {
- jwk.alg = 'RS256';
- } else if (keyAlgorithm.hash.name === 'SHA-384') {
- jwk.alg = 'RS384';
- } else if (keyAlgorithm.hash.name === 'SHA-512') {
- jwk.alg = 'RS512';
- } else if (keyAlgorithm.hash.name === 'SHA-1') {
- jwk.alg = 'RS1';
- }
-
- const extractable = false;
-
- const keyUsages: KeyUsage[] = ["verify"];
+export async function importKey(opts: {
+ keyData: JsonWebKey,
+ algorithm: AlgorithmIdentifier | RsaHashedImportParams | EcKeyImportParams,
+}): Promise<CryptoKey> {
+ const { keyData, algorithm } = opts;
if (globalThis.crypto) {
- return globalThis.crypto.subtle.importKey('jwk', jwk, keyAlgorithm, extractable, keyUsages);
+ return globalThis.crypto.subtle.importKey('jwk', keyData, algorithm, false, ['verify']);
} else {
- return webcrypto.subtle.importKey('jwk', jwk, keyAlgorithm, extractable, keyUsages);
+ return webcrypto.subtle.importKey('jwk', keyData, algorithm, false, ['verify']);
}
}
diff --git a/packages/server/src/helpers/iso/isoCrypto/index.ts b/packages/server/src/helpers/iso/isoCrypto/index.ts
index 0dc333f..7850722 100644
--- a/packages/server/src/helpers/iso/isoCrypto/index.ts
+++ b/packages/server/src/helpers/iso/isoCrypto/index.ts
@@ -1,4 +1,3 @@
export { digest } from './digest';
export { getRandomValues } from './getRandomValues';
-export { importKey } from './importKey';
export { verify } from './verify';
diff --git a/packages/server/src/helpers/iso/isoCrypto/verify.ts b/packages/server/src/helpers/iso/isoCrypto/verify.ts
index abcb769..6bf3d42 100644
--- a/packages/server/src/helpers/iso/isoCrypto/verify.ts
+++ b/packages/server/src/helpers/iso/isoCrypto/verify.ts
@@ -1,101 +1,28 @@
-import { webcrypto } from 'node:crypto';
-
-import { ECDSASigValue } from "@peculiar/asn1-ecc";
-import { AsnParser } from '@peculiar/asn1-schema';
-
-import { COSEALG, COSEKTY } from '../../convertCOSEtoPKCS';
-import { isoUint8Array } from '../index';
-import { mapCoseAlgToWebCryptoAlg } from './mapCoseAlgToWebCryptoAlg';
+import { COSEALG, COSEKEYS, COSEPublicKey, isCOSEPublicKeyEC2, isCOSEPublicKeyOKP, isCOSEPublicKeyRSA } from '../../cose';
+import { verifyEC2 } from './verifyEC2';
+import { verifyRSA } from './verifyRSA';
/**
* Verify signatures with their public key. Supports EC2 and RSA public keys.
*/
-export async function verify({
- publicKey,
- coseKty,
- coseAlg,
- signature,
- data,
-}: {
- publicKey: CryptoKey,
- coseKty: COSEKTY,
- coseAlg: COSEALG,
+export async function verify(opts: {
+ cosePublicKey: COSEPublicKey,
signature: Uint8Array,
data: Uint8Array,
+ shaHashOverride?: COSEALG,
}): Promise<boolean> {
- if (coseKty === COSEKTY.EC2) {
- // The signature is wrapped in ASN.1 structure, so we need to peel it apart
- 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 signatureBytes = isoUint8Array.concat([rBytes, sBytes]);
-
- return verifyECSignature(publicKey, signatureBytes, data, coseAlg);
- } else if (coseKty === COSEKTY.RSA) {
- return verifyRSASignature(publicKey, signature, data);
+ const { cosePublicKey, signature, data, shaHashOverride } = opts;
+
+ if (isCOSEPublicKeyEC2(cosePublicKey)) {
+ return verifyEC2({ cosePublicKey, signature, data, shaHashOverride });
+ } else if (isCOSEPublicKeyRSA(cosePublicKey)) {
+ return verifyRSA({ cosePublicKey, signature, data, shaHashOverride });
+ // } else if (isCOSEPublicKeyOKP(cosePublicKey)) {
+ // return;
}
+ const kty = cosePublicKey.get(COSEKEYS.kty);
throw new Error(
- `Signature verification with public key of kty ${coseKty} is not supported by this method`,
+ `Signature verification with public key of kty ${kty} is not supported by this method`,
);
}
-
-/**
- * Determine if the DER-specific `00` byte at the start of an ECDSA signature byte sequence
- * should be removed based on the following logic:
- *
- * "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"
- */
- function shouldRemoveLeadingZero(bytes: Uint8Array): boolean {
- return (bytes[0] === 0x0 && (bytes[1] & (1 << 7)) !== 0);
-}
-
-/**
- * Verify a signature using an EC2 public key
- */
-async function verifyECSignature(
- key: CryptoKey,
- signature: Uint8Array,
- data: Uint8Array,
- alg: COSEALG,
-): Promise<boolean> {
- const subtleAlg = mapCoseAlgToWebCryptoAlg(alg);
-
- const algorithm: EcdsaParams = {
- name: 'ECDSA',
- hash: { name: subtleAlg },
- };
- if (globalThis.crypto) {
- return globalThis.crypto.subtle.verify(algorithm, key, signature, data);
- } else {
- return webcrypto.subtle.verify(algorithm, key, signature, data);
- }
-}
-
-/**
- *
- */
-async function verifyRSASignature(
- key: CryptoKey,
- signature: Uint8Array,
- data: Uint8Array,
-): Promise<boolean> {
- const algorithm = {
- name: 'RSASSA-PKCS1-v1_5',
- };
- if (globalThis.crypto) {
- return globalThis.crypto.subtle.verify(algorithm, key, signature, data);
- } else {
- return webcrypto.subtle.verify(algorithm, key, signature, data);
- }
-}
diff --git a/packages/server/src/helpers/iso/isoCrypto/verifyEC2.ts b/packages/server/src/helpers/iso/isoCrypto/verifyEC2.ts
new file mode 100644
index 0000000..9e91d3e
--- /dev/null
+++ b/packages/server/src/helpers/iso/isoCrypto/verifyEC2.ts
@@ -0,0 +1,115 @@
+import { webcrypto } from 'node:crypto';
+import { ECDSASigValue } from "@peculiar/asn1-ecc";
+import { AsnParser } from '@peculiar/asn1-schema';
+
+import { COSEALG, COSECRV, COSEKEYS, COSEPublicKeyEC2 } from "../../cose";
+import { mapCoseAlgToWebCryptoAlg } from "./mapCoseAlgToWebCryptoAlg";
+import { importKey } from './importKey';
+import { isoBase64URL, isoUint8Array } from '../index';
+import { SubtleCryptoCrv } from "./structs";
+
+/**
+ * Verify a signature using an EC2 public key
+ */
+export async function verifyEC2(opts: {
+ cosePublicKey: COSEPublicKeyEC2,
+ signature: Uint8Array,
+ data: Uint8Array,
+ shaHashOverride?: COSEALG,
+}): Promise<boolean> {
+ const { cosePublicKey, signature, data, shaHashOverride } = opts;
+
+ // The signature is wrapped in ASN.1 structure, so we need to peel it apart
+ 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]);
+
+ // Import the public key
+ const alg = cosePublicKey.get(COSEKEYS.alg);
+ const crv = cosePublicKey.get(COSEKEYS.crv);
+ const x = cosePublicKey.get(COSEKEYS.x);
+ const y = cosePublicKey.get(COSEKEYS.y);
+
+ if (!alg) {
+ throw new Error('Public key was missing alg (EC2)');
+ }
+
+ if (!crv) {
+ throw new Error('Public key was missing crv (EC2)');
+ }
+
+ if (!x) {
+ throw new Error('Public key was missing x (EC2)');
+ }
+
+ if (!y) {
+ throw new Error('Public key was missing y (EC2)');
+ }
+
+ let _crv: SubtleCryptoCrv;
+ if (crv === COSECRV.P256) {
+ _crv = 'P-256';
+ } else if (crv === COSECRV.P384) {
+ _crv = 'P-384';
+ } else if (crv === COSECRV.P521) {
+ _crv = 'P-521';
+ } else {
+ throw new Error(`Unexpected COSE crv value of ${crv}`);
+ }
+
+ const keyData: JsonWebKey = {
+ kty: "EC",
+ crv: _crv,
+ x: isoBase64URL.fromBuffer(x),
+ y: isoBase64URL.fromBuffer(y),
+ ext: false,
+ };
+
+ const keyAlgorithm: EcKeyImportParams = {
+ name: 'ECDSA',
+ namedCurve: _crv,
+ };
+
+ const key = await importKey({
+ keyData,
+ algorithm: keyAlgorithm,
+ });
+
+ // Determine which SHA algorithm to use for signature verification
+ let subtleAlg = mapCoseAlgToWebCryptoAlg(alg);
+ if (shaHashOverride) {
+ subtleAlg = mapCoseAlgToWebCryptoAlg(shaHashOverride);
+ }
+
+ const verifyAlgorithm: EcdsaParams = {
+ name: 'ECDSA',
+ hash: { name: subtleAlg },
+ };
+
+ if (globalThis.crypto) {
+ return globalThis.crypto.subtle.verify(verifyAlgorithm, key, finalSignature, data);
+ } else {
+ return webcrypto.subtle.verify(verifyAlgorithm, key, finalSignature, data);
+ }
+}
+
+/**
+ * Determine if the DER-specific `00` byte at the start of an ECDSA signature byte sequence
+ * should be removed based on the following logic:
+ *
+ * "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"
+ */
+function shouldRemoveLeadingZero(bytes: Uint8Array): boolean {
+ return (bytes[0] === 0x0 && (bytes[1] & (1 << 7)) !== 0);
+}
diff --git a/packages/server/src/helpers/iso/isoCrypto/verifyRSA.ts b/packages/server/src/helpers/iso/isoCrypto/verifyRSA.ts
new file mode 100644
index 0000000..b479515
--- /dev/null
+++ b/packages/server/src/helpers/iso/isoCrypto/verifyRSA.ts
@@ -0,0 +1,82 @@
+import { webcrypto } from 'node:crypto';
+
+import { COSEALG, COSEKEYS, COSEPublicKeyRSA, isCOSEAlg } from "../../cose";
+import { mapCoseAlgToWebCryptoAlg } from "./mapCoseAlgToWebCryptoAlg";
+import { importKey } from './importKey';
+import { isoBase64URL } from '../index';
+
+/**
+ *
+ */
+export async function verifyRSA(opts: {
+ cosePublicKey: COSEPublicKeyRSA,
+ signature: Uint8Array,
+ data: Uint8Array,
+ shaHashOverride?: COSEALG,
+}): Promise<boolean> {
+ const { cosePublicKey, signature, data, shaHashOverride } = opts;
+
+ const alg = cosePublicKey.get(COSEKEYS.alg);
+ const n = cosePublicKey.get(COSEKEYS.n);
+ const e = cosePublicKey.get(COSEKEYS.e);
+
+ if (!alg) {
+ throw new Error('Public key was missing alg (RSA)');
+ }
+
+ if (!isCOSEAlg(alg)) {
+ throw new Error(`Public key had invalid alg ${alg} (RSA)`);
+ }
+
+ if (!n) {
+ throw new Error('Public key was missing n (RSA)');
+ }
+
+ if (!e) {
+ throw new Error('Public key was missing e (RSA)');
+ }
+
+ const keyData: JsonWebKey = {
+ kty: 'RSA',
+ alg: '',
+ n: isoBase64URL.fromBuffer(n),
+ e: isoBase64URL.fromBuffer(e),
+ ext: false,
+ };
+
+ const keyAlgorithm = {
+ // TODO: Determine this from `alg` so we might support the rarer RSA-PSS
+ name: 'RSASSA-PKCS1-v1_5',
+ // This is actually the digest hash that'll get used by `.verify()`
+ hash: { name: mapCoseAlgToWebCryptoAlg(alg) },
+ };
+
+ if (shaHashOverride) {
+ keyAlgorithm.hash.name = mapCoseAlgToWebCryptoAlg(shaHashOverride);
+ }
+
+ if (keyAlgorithm.hash.name === 'SHA-256') {
+ keyData.alg = 'RS256';
+ } else if (keyAlgorithm.hash.name === 'SHA-384') {
+ keyData.alg = 'RS384';
+ } else if (keyAlgorithm.hash.name === 'SHA-512') {
+ keyData.alg = 'RS512';
+ } else if (keyAlgorithm.hash.name === 'SHA-1') {
+ keyData.alg = 'RS1';
+ }
+
+ const key = await importKey({
+ keyData,
+ algorithm: keyAlgorithm,
+ });
+
+ const verifyAlgorithm = {
+ // TODO: Determine this from `alg` so we might support the rarer RSA-PSS
+ name: 'RSASSA-PKCS1-v1_5',
+ };
+ if (globalThis.crypto) {
+ return globalThis.crypto.subtle.verify(verifyAlgorithm, key, signature, data);
+ } else {
+ return webcrypto.subtle.verify(verifyAlgorithm, key, signature, data);
+ }
+}