From 07574afb2e2e965a849747f45d241a5623fc26ff Mon Sep 17 00:00:00 2001 From: tamaina Date: Wed, 6 Mar 2024 06:31:48 +0000 Subject: [PATCH] =?UTF-8?q?CryptoKey=E3=81=AEextractable:=20false=E3=81=8C?= =?UTF-8?q?=E5=95=8F=E9=A1=8C=E3=81=AA=E3=81=84=E3=82=88=E3=81=86=E3=81=AB?= =?UTF-8?q?=E3=81=99=E3=82=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - `genAlgorithmForSignAndVerify`の第2引数はhash文字列を指定するように - `verifyDraftSignature`のdefaultsはいらないことに気づいた - `importPrivateKey`/`importPublicKey`にextractableを指定できるように - `parseSignInfo`の第2引数がwebcrypto.CryptoKey['algorithm']を受け付けるように --- dist/draft/verify.d.ts | 4 +- dist/index.cjs | 225 +++++++++++++++++++++------------------- dist/index.mjs | 224 ++++++++++++++++++++------------------- dist/pem/spki.d.ts | 15 +++ dist/shared/verify.d.ts | 4 +- dist/utils.d.ts | 5 +- src/draft/sign.ts | 2 +- src/draft/verify.ts | 15 +-- src/pem/pkcs8.ts | 4 +- src/pem/spki.ts | 46 +++++++- src/shared/verify.ts | 30 +++--- src/types.ts | 1 + src/utils.ts | 9 +- test/unit/draft.ts | 5 +- 14 files changed, 335 insertions(+), 254 deletions(-) diff --git a/dist/draft/verify.d.ts b/dist/draft/verify.d.ts index b99e634..fe538fe 100644 --- a/dist/draft/verify.d.ts +++ b/dist/draft/verify.d.ts @@ -1,7 +1,6 @@ /// import { ParsedDraftSignature } from "../types"; import { parseSignInfo } from "../shared/verify"; -import { type SignInfoDefaults } from "../utils"; import type { webcrypto } from "node:crypto"; /** * @deprecated Use `parseSignInfo` @@ -13,5 +12,4 @@ export declare const genSignInfoDraft: typeof parseSignInfo; * @param key public key * @param errorLogger: If you want to log errors, set function */ -export declare function verifyDraftSignature(parsed: ParsedDraftSignature['value'], key: string | webcrypto.CryptoKey, errorLogger?: ((message: any) => any)): Promise; -export declare function verifyDraftSignature(parsed: ParsedDraftSignature['value'], key: string | webcrypto.CryptoKey, defaults: SignInfoDefaults, errorLogger?: (message: any) => any): Promise; +export declare function verifyDraftSignature(parsed: ParsedDraftSignature['value'], key: string | webcrypto.CryptoKey, errorLogger?: (message: any) => any): Promise; diff --git a/dist/index.cjs b/dist/index.cjs index ff6ece9..4dbfc61 100644 --- a/dist/index.cjs +++ b/dist/index.cjs @@ -79,6 +79,7 @@ __export(src_exports, { numberToUint8Array: () => numberToUint8Array, objectLcKeys: () => objectLcKeys, parseAlgorithmIdentifier: () => parseAlgorithmIdentifier, + parseAndImportPublicKey: () => parseAndImportPublicKey, parseDraftRequest: () => parseDraftRequest, parseDraftRequestSignatureHeader: () => parseDraftRequestSignatureHeader, parsePkcs1: () => parsePkcs1, @@ -164,6 +165,102 @@ function genSpkiFromPkcs1(input) { ]); } +// src/draft/const.ts +var keyHashAlgosForDraftEncofing = { + "SHA": "sha1", + "SHA-1": "sha1", + "SHA-256": "sha256", + "SHA-384": "sha384", + "SHA-512": "sha512", + "MD5": "md5" +}; +var keyHashAlgosForDraftDecoding = { + "sha1": "SHA", + "sha256": "SHA-256", + "sha384": "SHA-384", + "sha512": "SHA-512", + "md5": "MD5" +}; + +// src/shared/verify.ts +var KeyHashValidationError = class extends Error { + constructor(message) { + super(message); + } +}; +function buildErrorMessage(providedAlgorithm, real) { + return `Provided algorithm does not match the public key type: provided=${providedAlgorithm}, real=${real}`; +} +function parseSignInfo(algorithm, real, errorLogger) { + algorithm = algorithm?.toLowerCase(); + const realKeyType = typeof real === "string" ? real : "algorithm" in real ? getPublicKeyAlgorithmNameFromOid(real.algorithm) : real.name; + if (realKeyType === "RSA-PSS") { + if (algorithm === "rsa-pss-sha512") { + return { name: "RSA-PSS", hash: "SHA-512" }; + } + } + if (realKeyType === "RSASSA-PKCS1-v1_5") { + if (!algorithm || algorithm === "hs2019" || algorithm === "rsa-sha256" || algorithm === "rsa-v1_5-sha256") { + return { name: "RSASSA-PKCS1-v1_5", hash: "SHA-256" }; + } + if (algorithm === "rsa-pss-sha512") { + return { name: "RSA-PSS", hash: "SHA-512" }; + } + const [parsedName, hash] = algorithm.split("-"); + if (!hash || !(hash in keyHashAlgosForDraftDecoding)) { + throw new KeyHashValidationError(`unsupported hash: ${hash}`); + } + if (parsedName === "rsa") { + return { name: "RSASSA-PKCS1-v1_5", hash: keyHashAlgosForDraftDecoding[hash] }; + } + throw new KeyHashValidationError(buildErrorMessage(algorithm, realKeyType)); + } + if (realKeyType === "EC") { + const namedCurve = "parameter" in real ? getNistCurveFromOid(real.parameter) : real.namedCurve; + if (!namedCurve) + throw new KeyHashValidationError("could not get namedCurve"); + if (!algorithm || algorithm === "hs2019" || algorithm === "ecdsa-sha256") { + return { name: "ECDSA", hash: "SHA-256", namedCurve }; + } + if (algorithm === "ecdsa-p256-sha256") { + if (namedCurve !== "P-256") { + throw new KeyHashValidationError(`curve is not P-256: ${namedCurve}`); + } + return { name: "ECDSA", hash: "SHA-256", namedCurve }; + } + if (algorithm === "ecdsa-p384-sha384") { + if (namedCurve !== "P-384") { + throw new KeyHashValidationError(`curve is not P-384: ${namedCurve}`); + } + return { name: "ECDSA", hash: "SHA-256", namedCurve }; + } + const [dsaOrDH, hash] = algorithm.split("-"); + if (!hash || !(hash in keyHashAlgosForDraftDecoding)) { + throw new KeyHashValidationError(`unsupported hash: ${hash}`); + } + if (dsaOrDH === "ecdsa") { + return { name: "ECDSA", hash: keyHashAlgosForDraftDecoding[hash], namedCurve }; + } + if (dsaOrDH === "ecdh") { + return { name: "ECDH", hash: keyHashAlgosForDraftDecoding[hash], namedCurve }; + } + throw new KeyHashValidationError(buildErrorMessage(algorithm, realKeyType)); + } + if (realKeyType === "Ed25519") { + if (!algorithm || algorithm === "hs2019" || algorithm === "ed25519-sha512" || algorithm === "ed25519") { + return { name: "Ed25519" }; + } + throw new KeyHashValidationError(buildErrorMessage(algorithm, realKeyType)); + } + if (realKeyType === "Ed448") { + if (!algorithm || algorithm === "hs2019" || algorithm === "ed448") { + return { name: "Ed448" }; + } + throw new KeyHashValidationError(buildErrorMessage(algorithm, realKeyType)); + } + throw new KeyHashValidationError(`unsupported keyAlgorithm: ${realKeyType} (provided: ${algorithm})`); +} + // src/pem/spki.ts var SpkiParseError = class extends Error { constructor(message) { @@ -270,6 +367,22 @@ async function importPublicKey(key, keyUsages = ["verify"], defaults = defaultSi const parsedPublicKey = parsePublicKey(key); return await (await getWebcrypto()).subtle.importKey("spki", parsedPublicKey.der, genSignInfo(parsedPublicKey, defaults), false, keyUsages); } +async function parseAndImportPublicKey(source, keyUsages = ["verify"], providedAlgorithm, errorLogger) { + if (typeof source === "string" || typeof source === "object" && !("type" in source) && (source instanceof Uint8Array || source instanceof ArrayBuffer || Array.isArray(source) || "enc" in source)) { + const keyAlgorithmIdentifier = parsePublicKey(source); + const signInfo2 = parseSignInfo(providedAlgorithm, keyAlgorithmIdentifier, errorLogger); + const publicKey = await (await getWebcrypto()).subtle.importKey("spki", keyAlgorithmIdentifier.der, signInfo2, false, keyUsages); + return { + publicKey, + algorithm: genAlgorithmForSignAndVerify(publicKey.algorithm, "hash" in signInfo2 ? signInfo2.hash : null) + }; + } + const signInfo = parseSignInfo(providedAlgorithm, source.algorithm, errorLogger); + return { + publicKey: source, + algorithm: genAlgorithmForSignAndVerify(source.algorithm, "hash" in signInfo ? signInfo.hash : null) + }; +} // src/utils.ts async function getWebcrypto() { @@ -358,10 +471,10 @@ function genSignInfo(parsed, defaults = defaultSignInfoDefaults) { } throw new KeyValidationError("Unknown algorithm"); } -function genAlgorithmForSignAndVerify(algorithm, defaults = defaultSignInfoDefaults) { +function genAlgorithmForSignAndVerify(keyAlgorithm, hashAlgorithm) { return { - hash: defaults.hash, - ...algorithm + hash: hashAlgorithm, + ...keyAlgorithm }; } function splitPer64Chars(str) { @@ -404,26 +517,9 @@ function parsePkcs8(input) { async function importPrivateKey(key, keyUsages = ["sign"], defaults = defaultSignInfoDefaults) { const parsedPrivateKey = parsePkcs8(key); const importParams = genSignInfo(parsedPrivateKey, defaults); - return await (await getWebcrypto()).subtle.importKey("pkcs8", parsedPrivateKey.der, importParams, false, keyUsages); + return await (await getWebcrypto()).subtle.importKey("pkcs8", parsedPrivateKey.der, importParams, true, keyUsages); } -// src/draft/const.ts -var keyHashAlgosForDraftEncofing = { - "SHA": "sha1", - "SHA-1": "sha1", - "SHA-256": "sha256", - "SHA-384": "sha384", - "SHA-512": "sha512", - "MD5": "md5" -}; -var keyHashAlgosForDraftDecoding = { - "sha1": "SHA", - "sha256": "SHA-256", - "sha384": "SHA-384", - "sha512": "SHA-512", - "md5": "MD5" -}; - // src/draft/sign.ts function getDraftAlgoString(keyAlgorithm, hashAlgorithm) { const verifyHash = () => { @@ -898,92 +994,12 @@ async function verifyDigestHeader(request, rawBody, failOnNoDigest = true, error return true; } -// src/shared/verify.ts -var KeyHashValidationError = class extends Error { - constructor(message) { - super(message); - } -}; -function buildErrorMessage(providedAlgorithm, real) { - return `Provided algorithm does not match the public key type: provided=${providedAlgorithm}, real=${real}`; -} -function parseSignInfo(algorithm, parsed, errorLogger) { - algorithm = algorithm?.toLowerCase(); - const realKeyType = getPublicKeyAlgorithmNameFromOid(parsed.algorithm); - if (realKeyType === "RSA-PSS") { - if (algorithm === "rsa-pss-sha512") { - return { name: "RSA-PSS", hash: "SHA-512" }; - } - } - if (realKeyType === "RSASSA-PKCS1-v1_5") { - if (!algorithm || algorithm === "hs2019" || algorithm === "rsa-sha256" || algorithm === "rsa-v1_5-sha256") { - return { name: "RSASSA-PKCS1-v1_5", hash: "SHA-256" }; - } - if (algorithm === "rsa-pss-sha512") { - return { name: "RSA-PSS", hash: "SHA-512" }; - } - const [parsedName, hash] = algorithm.split("-"); - if (!hash || !(hash in keyHashAlgosForDraftDecoding)) { - throw new KeyHashValidationError(`unsupported hash: ${hash}`); - } - if (parsedName === "rsa") { - return { name: "RSASSA-PKCS1-v1_5", hash: keyHashAlgosForDraftDecoding[hash] }; - } - throw new KeyHashValidationError(buildErrorMessage(algorithm, parsed.algorithm)); - } - if (realKeyType === "EC") { - if (!algorithm || algorithm === "hs2019" || algorithm === "ecdsa-sha256") { - return { name: "ECDSA", hash: "SHA-256", namedCurve: getNistCurveFromOid(parsed.parameter) }; - } - if (algorithm === "ecdsa-p256-sha256") { - const namedCurve = getNistCurveFromOid(parsed.parameter); - if (namedCurve !== "P-256") { - throw new KeyHashValidationError(`curve is not P-256: ${namedCurve}`); - } - return { name: "ECDSA", hash: "SHA-256", namedCurve }; - } - if (algorithm === "ecdsa-p384-sha384") { - const namedCurve = getNistCurveFromOid(parsed.parameter); - if (namedCurve !== "P-384") { - throw new KeyHashValidationError(`curve is not P-384: ${namedCurve}`); - } - return { name: "ECDSA", hash: "SHA-256", namedCurve: getNistCurveFromOid(parsed.parameter) }; - } - const [dsaOrDH, hash] = algorithm.split("-"); - if (!hash || !(hash in keyHashAlgosForDraftDecoding)) { - throw new KeyHashValidationError(`unsupported hash: ${hash}`); - } - if (dsaOrDH === "ecdsa") { - return { name: "ECDSA", hash: keyHashAlgosForDraftDecoding[hash], namedCurve: getNistCurveFromOid(parsed.parameter) }; - } - if (dsaOrDH === "ecdh") { - return { name: "ECDH", hash: keyHashAlgosForDraftDecoding[hash], namedCurve: getNistCurveFromOid(parsed.parameter) }; - } - throw new KeyHashValidationError(buildErrorMessage(algorithm, parsed.algorithm)); - } - if (realKeyType === "Ed25519") { - if (!algorithm || algorithm === "hs2019" || algorithm === "ed25519-sha512" || algorithm === "ed25519") { - return { name: "Ed25519" }; - } - throw new KeyHashValidationError(buildErrorMessage(algorithm, parsed.algorithm)); - } - if (realKeyType === "Ed448") { - if (!algorithm || algorithm === "hs2019" || algorithm === "ed448") { - return { name: "Ed448" }; - } - throw new KeyHashValidationError(buildErrorMessage(algorithm, parsed.algorithm)); - } - throw new KeyHashValidationError(`unsupported keyAlgorithm: ${realKeyType} (provided: ${algorithm})`); -} - // src/draft/verify.ts var genSignInfoDraft = parseSignInfo; -async function verifyDraftSignature(parsed, key, p3, p4) { - const errorLogger = p3 && typeof p3 === "function" ? p3 : p4; - const defaults = p3 && typeof p3 === "object" ? p3 : defaultSignInfoDefaults; +async function verifyDraftSignature(parsed, key, errorLogger) { try { - const publicKey = typeof key === "string" ? await importPublicKey(key, ["verify"]) : key; - const verify = await (await getWebcrypto()).subtle.verify(genAlgorithmForSignAndVerify(publicKey.algorithm, defaults), publicKey, decodeBase64ToUint8Array(parsed.params.signature), new TextEncoder().encode(parsed.signingString)); + const { publicKey, algorithm } = await parseAndImportPublicKey(key, ["verify"], parsed.algorithm); + const verify = await (await getWebcrypto()).subtle.verify(algorithm, publicKey, decodeBase64ToUint8Array(parsed.params.signature), new TextEncoder().encode(parsed.signingString)); if (verify !== true) throw new Error(`verification simply failed, result: ${verify}`); return verify; @@ -1044,6 +1060,7 @@ async function verifyDraftSignature(parsed, key, p3, p4) { numberToUint8Array, objectLcKeys, parseAlgorithmIdentifier, + parseAndImportPublicKey, parseDraftRequest, parseDraftRequestSignatureHeader, parsePkcs1, diff --git a/dist/index.mjs b/dist/index.mjs index 1963e3a..f8f362a 100644 --- a/dist/index.mjs +++ b/dist/index.mjs @@ -63,6 +63,102 @@ function genSpkiFromPkcs1(input) { ]); } +// src/draft/const.ts +var keyHashAlgosForDraftEncofing = { + "SHA": "sha1", + "SHA-1": "sha1", + "SHA-256": "sha256", + "SHA-384": "sha384", + "SHA-512": "sha512", + "MD5": "md5" +}; +var keyHashAlgosForDraftDecoding = { + "sha1": "SHA", + "sha256": "SHA-256", + "sha384": "SHA-384", + "sha512": "SHA-512", + "md5": "MD5" +}; + +// src/shared/verify.ts +var KeyHashValidationError = class extends Error { + constructor(message) { + super(message); + } +}; +function buildErrorMessage(providedAlgorithm, real) { + return `Provided algorithm does not match the public key type: provided=${providedAlgorithm}, real=${real}`; +} +function parseSignInfo(algorithm, real, errorLogger) { + algorithm = algorithm?.toLowerCase(); + const realKeyType = typeof real === "string" ? real : "algorithm" in real ? getPublicKeyAlgorithmNameFromOid(real.algorithm) : real.name; + if (realKeyType === "RSA-PSS") { + if (algorithm === "rsa-pss-sha512") { + return { name: "RSA-PSS", hash: "SHA-512" }; + } + } + if (realKeyType === "RSASSA-PKCS1-v1_5") { + if (!algorithm || algorithm === "hs2019" || algorithm === "rsa-sha256" || algorithm === "rsa-v1_5-sha256") { + return { name: "RSASSA-PKCS1-v1_5", hash: "SHA-256" }; + } + if (algorithm === "rsa-pss-sha512") { + return { name: "RSA-PSS", hash: "SHA-512" }; + } + const [parsedName, hash] = algorithm.split("-"); + if (!hash || !(hash in keyHashAlgosForDraftDecoding)) { + throw new KeyHashValidationError(`unsupported hash: ${hash}`); + } + if (parsedName === "rsa") { + return { name: "RSASSA-PKCS1-v1_5", hash: keyHashAlgosForDraftDecoding[hash] }; + } + throw new KeyHashValidationError(buildErrorMessage(algorithm, realKeyType)); + } + if (realKeyType === "EC") { + const namedCurve = "parameter" in real ? getNistCurveFromOid(real.parameter) : real.namedCurve; + if (!namedCurve) + throw new KeyHashValidationError("could not get namedCurve"); + if (!algorithm || algorithm === "hs2019" || algorithm === "ecdsa-sha256") { + return { name: "ECDSA", hash: "SHA-256", namedCurve }; + } + if (algorithm === "ecdsa-p256-sha256") { + if (namedCurve !== "P-256") { + throw new KeyHashValidationError(`curve is not P-256: ${namedCurve}`); + } + return { name: "ECDSA", hash: "SHA-256", namedCurve }; + } + if (algorithm === "ecdsa-p384-sha384") { + if (namedCurve !== "P-384") { + throw new KeyHashValidationError(`curve is not P-384: ${namedCurve}`); + } + return { name: "ECDSA", hash: "SHA-256", namedCurve }; + } + const [dsaOrDH, hash] = algorithm.split("-"); + if (!hash || !(hash in keyHashAlgosForDraftDecoding)) { + throw new KeyHashValidationError(`unsupported hash: ${hash}`); + } + if (dsaOrDH === "ecdsa") { + return { name: "ECDSA", hash: keyHashAlgosForDraftDecoding[hash], namedCurve }; + } + if (dsaOrDH === "ecdh") { + return { name: "ECDH", hash: keyHashAlgosForDraftDecoding[hash], namedCurve }; + } + throw new KeyHashValidationError(buildErrorMessage(algorithm, realKeyType)); + } + if (realKeyType === "Ed25519") { + if (!algorithm || algorithm === "hs2019" || algorithm === "ed25519-sha512" || algorithm === "ed25519") { + return { name: "Ed25519" }; + } + throw new KeyHashValidationError(buildErrorMessage(algorithm, realKeyType)); + } + if (realKeyType === "Ed448") { + if (!algorithm || algorithm === "hs2019" || algorithm === "ed448") { + return { name: "Ed448" }; + } + throw new KeyHashValidationError(buildErrorMessage(algorithm, realKeyType)); + } + throw new KeyHashValidationError(`unsupported keyAlgorithm: ${realKeyType} (provided: ${algorithm})`); +} + // src/pem/spki.ts var SpkiParseError = class extends Error { constructor(message) { @@ -169,6 +265,22 @@ async function importPublicKey(key, keyUsages = ["verify"], defaults = defaultSi const parsedPublicKey = parsePublicKey(key); return await (await getWebcrypto()).subtle.importKey("spki", parsedPublicKey.der, genSignInfo(parsedPublicKey, defaults), false, keyUsages); } +async function parseAndImportPublicKey(source, keyUsages = ["verify"], providedAlgorithm, errorLogger) { + if (typeof source === "string" || typeof source === "object" && !("type" in source) && (source instanceof Uint8Array || source instanceof ArrayBuffer || Array.isArray(source) || "enc" in source)) { + const keyAlgorithmIdentifier = parsePublicKey(source); + const signInfo2 = parseSignInfo(providedAlgorithm, keyAlgorithmIdentifier, errorLogger); + const publicKey = await (await getWebcrypto()).subtle.importKey("spki", keyAlgorithmIdentifier.der, signInfo2, false, keyUsages); + return { + publicKey, + algorithm: genAlgorithmForSignAndVerify(publicKey.algorithm, "hash" in signInfo2 ? signInfo2.hash : null) + }; + } + const signInfo = parseSignInfo(providedAlgorithm, source.algorithm, errorLogger); + return { + publicKey: source, + algorithm: genAlgorithmForSignAndVerify(source.algorithm, "hash" in signInfo ? signInfo.hash : null) + }; +} // src/utils.ts async function getWebcrypto() { @@ -257,10 +369,10 @@ function genSignInfo(parsed, defaults = defaultSignInfoDefaults) { } throw new KeyValidationError("Unknown algorithm"); } -function genAlgorithmForSignAndVerify(algorithm, defaults = defaultSignInfoDefaults) { +function genAlgorithmForSignAndVerify(keyAlgorithm, hashAlgorithm) { return { - hash: defaults.hash, - ...algorithm + hash: hashAlgorithm, + ...keyAlgorithm }; } function splitPer64Chars(str) { @@ -303,26 +415,9 @@ function parsePkcs8(input) { async function importPrivateKey(key, keyUsages = ["sign"], defaults = defaultSignInfoDefaults) { const parsedPrivateKey = parsePkcs8(key); const importParams = genSignInfo(parsedPrivateKey, defaults); - return await (await getWebcrypto()).subtle.importKey("pkcs8", parsedPrivateKey.der, importParams, false, keyUsages); + return await (await getWebcrypto()).subtle.importKey("pkcs8", parsedPrivateKey.der, importParams, true, keyUsages); } -// src/draft/const.ts -var keyHashAlgosForDraftEncofing = { - "SHA": "sha1", - "SHA-1": "sha1", - "SHA-256": "sha256", - "SHA-384": "sha384", - "SHA-512": "sha512", - "MD5": "md5" -}; -var keyHashAlgosForDraftDecoding = { - "sha1": "SHA", - "sha256": "SHA-256", - "sha384": "SHA-384", - "sha512": "SHA-512", - "md5": "MD5" -}; - // src/draft/sign.ts function getDraftAlgoString(keyAlgorithm, hashAlgorithm) { const verifyHash = () => { @@ -797,92 +892,12 @@ async function verifyDigestHeader(request, rawBody, failOnNoDigest = true, error return true; } -// src/shared/verify.ts -var KeyHashValidationError = class extends Error { - constructor(message) { - super(message); - } -}; -function buildErrorMessage(providedAlgorithm, real) { - return `Provided algorithm does not match the public key type: provided=${providedAlgorithm}, real=${real}`; -} -function parseSignInfo(algorithm, parsed, errorLogger) { - algorithm = algorithm?.toLowerCase(); - const realKeyType = getPublicKeyAlgorithmNameFromOid(parsed.algorithm); - if (realKeyType === "RSA-PSS") { - if (algorithm === "rsa-pss-sha512") { - return { name: "RSA-PSS", hash: "SHA-512" }; - } - } - if (realKeyType === "RSASSA-PKCS1-v1_5") { - if (!algorithm || algorithm === "hs2019" || algorithm === "rsa-sha256" || algorithm === "rsa-v1_5-sha256") { - return { name: "RSASSA-PKCS1-v1_5", hash: "SHA-256" }; - } - if (algorithm === "rsa-pss-sha512") { - return { name: "RSA-PSS", hash: "SHA-512" }; - } - const [parsedName, hash] = algorithm.split("-"); - if (!hash || !(hash in keyHashAlgosForDraftDecoding)) { - throw new KeyHashValidationError(`unsupported hash: ${hash}`); - } - if (parsedName === "rsa") { - return { name: "RSASSA-PKCS1-v1_5", hash: keyHashAlgosForDraftDecoding[hash] }; - } - throw new KeyHashValidationError(buildErrorMessage(algorithm, parsed.algorithm)); - } - if (realKeyType === "EC") { - if (!algorithm || algorithm === "hs2019" || algorithm === "ecdsa-sha256") { - return { name: "ECDSA", hash: "SHA-256", namedCurve: getNistCurveFromOid(parsed.parameter) }; - } - if (algorithm === "ecdsa-p256-sha256") { - const namedCurve = getNistCurveFromOid(parsed.parameter); - if (namedCurve !== "P-256") { - throw new KeyHashValidationError(`curve is not P-256: ${namedCurve}`); - } - return { name: "ECDSA", hash: "SHA-256", namedCurve }; - } - if (algorithm === "ecdsa-p384-sha384") { - const namedCurve = getNistCurveFromOid(parsed.parameter); - if (namedCurve !== "P-384") { - throw new KeyHashValidationError(`curve is not P-384: ${namedCurve}`); - } - return { name: "ECDSA", hash: "SHA-256", namedCurve: getNistCurveFromOid(parsed.parameter) }; - } - const [dsaOrDH, hash] = algorithm.split("-"); - if (!hash || !(hash in keyHashAlgosForDraftDecoding)) { - throw new KeyHashValidationError(`unsupported hash: ${hash}`); - } - if (dsaOrDH === "ecdsa") { - return { name: "ECDSA", hash: keyHashAlgosForDraftDecoding[hash], namedCurve: getNistCurveFromOid(parsed.parameter) }; - } - if (dsaOrDH === "ecdh") { - return { name: "ECDH", hash: keyHashAlgosForDraftDecoding[hash], namedCurve: getNistCurveFromOid(parsed.parameter) }; - } - throw new KeyHashValidationError(buildErrorMessage(algorithm, parsed.algorithm)); - } - if (realKeyType === "Ed25519") { - if (!algorithm || algorithm === "hs2019" || algorithm === "ed25519-sha512" || algorithm === "ed25519") { - return { name: "Ed25519" }; - } - throw new KeyHashValidationError(buildErrorMessage(algorithm, parsed.algorithm)); - } - if (realKeyType === "Ed448") { - if (!algorithm || algorithm === "hs2019" || algorithm === "ed448") { - return { name: "Ed448" }; - } - throw new KeyHashValidationError(buildErrorMessage(algorithm, parsed.algorithm)); - } - throw new KeyHashValidationError(`unsupported keyAlgorithm: ${realKeyType} (provided: ${algorithm})`); -} - // src/draft/verify.ts var genSignInfoDraft = parseSignInfo; -async function verifyDraftSignature(parsed, key, p3, p4) { - const errorLogger = p3 && typeof p3 === "function" ? p3 : p4; - const defaults = p3 && typeof p3 === "object" ? p3 : defaultSignInfoDefaults; +async function verifyDraftSignature(parsed, key, errorLogger) { try { - const publicKey = typeof key === "string" ? await importPublicKey(key, ["verify"]) : key; - const verify = await (await getWebcrypto()).subtle.verify(genAlgorithmForSignAndVerify(publicKey.algorithm, defaults), publicKey, decodeBase64ToUint8Array(parsed.params.signature), new TextEncoder().encode(parsed.signingString)); + const { publicKey, algorithm } = await parseAndImportPublicKey(key, ["verify"], parsed.algorithm); + const verify = await (await getWebcrypto()).subtle.verify(algorithm, publicKey, decodeBase64ToUint8Array(parsed.params.signature), new TextEncoder().encode(parsed.signingString)); if (verify !== true) throw new Error(`verification simply failed, result: ${verify}`); return verify; @@ -942,6 +957,7 @@ export { numberToUint8Array, objectLcKeys, parseAlgorithmIdentifier, + parseAndImportPublicKey, parseDraftRequest, parseDraftRequestSignatureHeader, parsePkcs1, diff --git a/dist/pem/spki.d.ts b/dist/pem/spki.d.ts index 568da25..0e9ebbc 100644 --- a/dist/pem/spki.d.ts +++ b/dist/pem/spki.d.ts @@ -102,3 +102,18 @@ export declare function parsePublicKey(input: ASN1.StreamOrBinary): SpkiParsedAl * @returns CryptoKey */ export declare function importPublicKey(key: ASN1.StreamOrBinary, keyUsages?: webcrypto.KeyUsage[], defaults?: SignInfoDefaults): Promise; +/** + * Prepare public key for verification + * @param source PEM, DER or CryptoKey + * @param keyUsages e.g. ['verify'] + * @param providedAlgorithm e.g. 'rsa-sha256' or 'rsa-v1_5-sha256 + * @param errorLogger + * @returns + */ +export declare function parseAndImportPublicKey(source: ASN1.StreamOrBinary | webcrypto.CryptoKey, keyUsages?: webcrypto.KeyUsage[], providedAlgorithm?: string, errorLogger?: ((message: any) => any)): Promise<{ + publicKey: webcrypto.CryptoKey; + algorithm: { + name: string; + hash: import("../types").SignatureHashAlgorithmUpperSnake; + }; +}>; diff --git a/dist/shared/verify.d.ts b/dist/shared/verify.d.ts index 7c6eafe..05c0e42 100644 --- a/dist/shared/verify.d.ts +++ b/dist/shared/verify.d.ts @@ -1,8 +1,10 @@ /** * Verify Request (Parsed) */ +/// import type { SignInfo } from '../types.js'; import { ParsedAlgorithmIdentifier } from '../pem/spki.js'; +import type { webcrypto } from 'node:crypto'; export declare class KeyHashValidationError extends Error { constructor(message: string); } @@ -12,4 +14,4 @@ export declare class KeyHashValidationError extends Error { * @param algorithm ヘッダーのアルゴリズム * @param publicKey 実際の公開鍵 */ -export declare function parseSignInfo(algorithm: string | undefined, parsed: ParsedAlgorithmIdentifier, errorLogger?: ((message: any) => any)): SignInfo; +export declare function parseSignInfo(algorithm: string | undefined, real: ParsedAlgorithmIdentifier | webcrypto.CryptoKey['algorithm'], errorLogger?: ((message: any) => any)): SignInfo; diff --git a/dist/utils.d.ts b/dist/utils.d.ts index 137f530..cf79cf7 100644 --- a/dist/utils.d.ts +++ b/dist/utils.d.ts @@ -45,11 +45,8 @@ export declare function genSignInfo(parsed: ParsedAlgorithmIdentifier, defaults? /** * Generate algorithm for sign and verify from key algorithm and defaults, * because algorithm of ECDSA and ECDH does not have hash property. - * @param algorithm - * @param defaults default values - * @returns */ -export declare function genAlgorithmForSignAndVerify(algorithm: webcrypto.KeyAlgorithm, defaults?: SignInfoDefaults): { +export declare function genAlgorithmForSignAndVerify(keyAlgorithm: webcrypto.KeyAlgorithm, hashAlgorithm: SignatureHashAlgorithmUpperSnake): { name: string; hash: SignatureHashAlgorithmUpperSnake; }; diff --git a/src/draft/sign.ts b/src/draft/sign.ts index 591135b..c6e6e15 100644 --- a/src/draft/sign.ts +++ b/src/draft/sign.ts @@ -80,7 +80,7 @@ export function genDraftSigningString( } export async function genDraftSignature(privateKey: webcrypto.CryptoKey, signingString: string, defaults: SignInfoDefaults = defaultSignInfoDefaults) { - const signatureAB = await (await getWebcrypto()).subtle.sign(genAlgorithmForSignAndVerify(privateKey.algorithm, defaults), privateKey, new TextEncoder().encode(signingString)); + const signatureAB = await (await getWebcrypto()).subtle.sign(genAlgorithmForSignAndVerify(privateKey.algorithm, defaults.hash), privateKey, new TextEncoder().encode(signingString)); return encodeArrayBufferToBase64(signatureAB); } diff --git a/src/draft/verify.ts b/src/draft/verify.ts index 8b8db5f..036b988 100644 --- a/src/draft/verify.ts +++ b/src/draft/verify.ts @@ -1,7 +1,7 @@ import { ParsedDraftSignature } from "../types"; -import { importPublicKey } from "../pem/spki"; +import { parseAndImportPublicKey } from "../pem/spki"; import { parseSignInfo } from "../shared/verify"; -import { type SignInfoDefaults, decodeBase64ToUint8Array, defaultSignInfoDefaults, getWebcrypto, genAlgorithmForSignAndVerify } from "../utils"; +import { decodeBase64ToUint8Array, getWebcrypto } from "../utils"; import type { webcrypto } from "node:crypto"; /** @@ -15,19 +15,14 @@ export const genSignInfoDraft = parseSignInfo; * @param key public key * @param errorLogger: If you want to log errors, set function */ -export async function verifyDraftSignature(parsed: ParsedDraftSignature['value'], key: string | webcrypto.CryptoKey, errorLogger?: ((message: any) => any)): Promise -export async function verifyDraftSignature(parsed: ParsedDraftSignature['value'], key: string | webcrypto.CryptoKey, defaults: SignInfoDefaults, errorLogger?: (message: any) => any): Promise export async function verifyDraftSignature( parsed: ParsedDraftSignature['value'], key: string | webcrypto.CryptoKey, - p3?: ((message: any) => any) | SignInfoDefaults, - p4?: (message: any) => any + errorLogger?: (message: any) => any ): Promise { - const errorLogger = p3 && typeof p3 === 'function' ? p3 : p4; - const defaults = p3 && typeof p3 === 'object' ? p3 : defaultSignInfoDefaults; try { - const publicKey = typeof key === 'string' ? await importPublicKey(key, ['verify']) : key; - const verify = await (await getWebcrypto()).subtle.verify(genAlgorithmForSignAndVerify(publicKey.algorithm, defaults), publicKey, decodeBase64ToUint8Array(parsed.params.signature), (new TextEncoder()).encode(parsed.signingString)); + const { publicKey, algorithm } = await parseAndImportPublicKey(key, ['verify'], parsed.algorithm); + const verify = await (await getWebcrypto()).subtle.verify(algorithm, publicKey, decodeBase64ToUint8Array(parsed.params.signature), (new TextEncoder()).encode(parsed.signingString)); if (verify !== true) throw new Error(`verification simply failed, result: ${verify}`); return verify; } catch (e) { diff --git a/src/pem/pkcs8.ts b/src/pem/pkcs8.ts index d12caf8..d1a50ad 100644 --- a/src/pem/pkcs8.ts +++ b/src/pem/pkcs8.ts @@ -58,8 +58,8 @@ export function parsePkcs8(input: ASN1.StreamOrBinary): ParsedPkcs8 { * @param defaults * @returns CryptoKey */ -export async function importPrivateKey(key: ASN1.StreamOrBinary, keyUsages: webcrypto.KeyUsage[] = ['sign'], defaults: SignInfoDefaults = defaultSignInfoDefaults) { +export async function importPrivateKey(key: ASN1.StreamOrBinary, keyUsages: webcrypto.KeyUsage[] = ['sign'], defaults: SignInfoDefaults = defaultSignInfoDefaults, extractable = false) { const parsedPrivateKey = parsePkcs8(key); const importParams = genSignInfo(parsedPrivateKey, defaults); - return await (await getWebcrypto()).subtle.importKey('pkcs8', parsedPrivateKey.der, importParams, false, keyUsages); + return await (await getWebcrypto()).subtle.importKey('pkcs8', parsedPrivateKey.der, importParams, extractable, keyUsages); } diff --git a/src/pem/spki.ts b/src/pem/spki.ts index 240d579..7f18265 100644 --- a/src/pem/spki.ts +++ b/src/pem/spki.ts @@ -4,7 +4,8 @@ import Base64 from '@lapo/asn1js/base64.js'; import { genSpkiFromPkcs1, parsePkcs1 } from './pkcs1'; import { ECNamedCurve, KeyAlgorithmName } from '../types'; import type { webcrypto } from 'node:crypto'; -import { SignInfoDefaults, defaultSignInfoDefaults, genSignInfo, getWebcrypto } from '../utils'; +import { SignInfoDefaults, defaultSignInfoDefaults, genAlgorithmForSignAndVerify, genSignInfo, getWebcrypto } from '../utils'; +import { parseSignInfo } from '../shared/verify'; export class SpkiParseError extends Error { constructor(message: string) { super(message); } @@ -200,7 +201,46 @@ export function parsePublicKey(input: ASN1.StreamOrBinary): SpkiParsedAlgorithmI * @param defaults * @returns CryptoKey */ -export async function importPublicKey(key: ASN1.StreamOrBinary, keyUsages: webcrypto.KeyUsage[] = ['verify'], defaults: SignInfoDefaults = defaultSignInfoDefaults) { +export async function importPublicKey(key: ASN1.StreamOrBinary, keyUsages: webcrypto.KeyUsage[] = ['verify'], defaults: SignInfoDefaults = defaultSignInfoDefaults, extractable = false) { const parsedPublicKey = parsePublicKey(key); - return await (await getWebcrypto()).subtle.importKey('spki', parsedPublicKey.der, genSignInfo(parsedPublicKey, defaults), false, keyUsages); + return await (await getWebcrypto()).subtle.importKey('spki', parsedPublicKey.der, genSignInfo(parsedPublicKey, defaults), extractable, keyUsages); +} + +/** + * Prepare public key for verification + * @param source PEM, DER or CryptoKey + * @param keyUsages e.g. ['verify'] + * @param providedAlgorithm e.g. 'rsa-sha256' or 'rsa-v1_5-sha256 + * @param errorLogger + * @returns + */ +export async function parseAndImportPublicKey( + source: ASN1.StreamOrBinary | webcrypto.CryptoKey, + keyUsages: webcrypto.KeyUsage[] = ['verify'], + providedAlgorithm?: string, + errorLogger?: ((message: any) => any) +) { + if ( + typeof source === 'string' || + ( + typeof source === 'object' && !('type' in source) && + (source instanceof Uint8Array || source instanceof ArrayBuffer || Array.isArray(source) || 'enc' in source) + ) + ) { + // Not a CryptoKey + const keyAlgorithmIdentifier = parsePublicKey(source); + const signInfo = parseSignInfo(providedAlgorithm, keyAlgorithmIdentifier, errorLogger); + const publicKey = await (await getWebcrypto()).subtle.importKey('spki', keyAlgorithmIdentifier.der, signInfo, false, keyUsages); + return { + publicKey, + algorithm: genAlgorithmForSignAndVerify(publicKey.algorithm, 'hash' in signInfo ? signInfo.hash : null), + }; + } + + // Is a CryptoKey + const signInfo = parseSignInfo(providedAlgorithm, source.algorithm, errorLogger); + return { + publicKey: source, + algorithm: genAlgorithmForSignAndVerify(source.algorithm, 'hash' in signInfo ? signInfo.hash : null), + }; } diff --git a/src/shared/verify.ts b/src/shared/verify.ts index 59cf918..d779248 100644 --- a/src/shared/verify.ts +++ b/src/shared/verify.ts @@ -2,10 +2,11 @@ * Verify Request (Parsed) */ -import type { SignInfo } from '../types.js'; +import type { ECNamedCurve, SignInfo } from '../types.js'; import { ParsedAlgorithmIdentifier, getNistCurveFromOid, getPublicKeyAlgorithmNameFromOid } from '../pem/spki.js'; import type { SignatureHashAlgorithmUpperSnake } from '../types.js'; import { keyHashAlgosForDraftDecoding } from '../draft/const.js'; +import type { webcrypto } from 'node:crypto'; export class KeyHashValidationError extends Error { constructor(message: string) { super(message); } @@ -21,9 +22,9 @@ function buildErrorMessage(providedAlgorithm: string, real: string) { * @param algorithm ヘッダーのアルゴリズム * @param publicKey 実際の公開鍵 */ -export function parseSignInfo(algorithm: string | undefined, parsed: ParsedAlgorithmIdentifier, errorLogger?: ((message: any) => any)): SignInfo { +export function parseSignInfo(algorithm: string | undefined, real: ParsedAlgorithmIdentifier | webcrypto.CryptoKey['algorithm'], errorLogger?: ((message: any) => any)): SignInfo { algorithm = algorithm?.toLowerCase(); - const realKeyType = getPublicKeyAlgorithmNameFromOid(parsed.algorithm); + const realKeyType = typeof real === 'string' ? real : 'algorithm' in real ? getPublicKeyAlgorithmNameFromOid(real.algorithm) : real.name; if (realKeyType === 'RSA-PSS') { // 公開鍵にこれが使われることはないが、一応 @@ -54,30 +55,31 @@ export function parseSignInfo(algorithm: string | undefined, parsed: ParsedAlgor return { name: 'RSASSA-PKCS1-v1_5', hash: keyHashAlgosForDraftDecoding[hash] }; } //#endregion - throw new KeyHashValidationError(buildErrorMessage(algorithm, parsed.algorithm)); + throw new KeyHashValidationError(buildErrorMessage(algorithm, realKeyType)); } if (realKeyType === 'EC') { + const namedCurve = 'parameter' in real ? getNistCurveFromOid(real.parameter) : (real as webcrypto.EcKeyGenParams).namedCurve as ECNamedCurve; + if (!namedCurve) throw new KeyHashValidationError('could not get namedCurve'); + if ( !algorithm || algorithm === 'hs2019' || algorithm === 'ecdsa-sha256' ) { - return { name: 'ECDSA', hash: 'SHA-256', namedCurve: getNistCurveFromOid(parsed.parameter) }; + return { name: 'ECDSA', hash: 'SHA-256', namedCurve }; } if (algorithm === 'ecdsa-p256-sha256') { - const namedCurve = getNistCurveFromOid(parsed.parameter); if (namedCurve !== 'P-256') { throw new KeyHashValidationError(`curve is not P-256: ${namedCurve}`); } - return { name: 'ECDSA', hash: 'SHA-256', namedCurve: namedCurve }; + return { name: 'ECDSA', hash: 'SHA-256', namedCurve }; } if (algorithm === 'ecdsa-p384-sha384') { - const namedCurve = getNistCurveFromOid(parsed.parameter); if (namedCurve !== 'P-384') { throw new KeyHashValidationError(`curve is not P-384: ${namedCurve}`); } - return { name: 'ECDSA', hash: 'SHA-256', namedCurve: getNistCurveFromOid(parsed.parameter) }; + return { name: 'ECDSA', hash: 'SHA-256', namedCurve }; } //#region draft parsing @@ -86,13 +88,13 @@ export function parseSignInfo(algorithm: string | undefined, parsed: ParsedAlgor throw new KeyHashValidationError(`unsupported hash: ${hash}`); } if (dsaOrDH === 'ecdsa') { - return { name: 'ECDSA', hash: keyHashAlgosForDraftDecoding[hash], namedCurve: getNistCurveFromOid(parsed.parameter) }; + return { name: 'ECDSA', hash: keyHashAlgosForDraftDecoding[hash], namedCurve }; } if (dsaOrDH === 'ecdh') { - return { name: 'ECDH', hash: keyHashAlgosForDraftDecoding[hash], namedCurve: getNistCurveFromOid(parsed.parameter) }; + return { name: 'ECDH', hash: keyHashAlgosForDraftDecoding[hash], namedCurve }; } //#endregion - throw new KeyHashValidationError(buildErrorMessage(algorithm, parsed.algorithm)); + throw new KeyHashValidationError(buildErrorMessage(algorithm, realKeyType)); } if (realKeyType === 'Ed25519') { @@ -104,7 +106,7 @@ export function parseSignInfo(algorithm: string | undefined, parsed: ParsedAlgor ) { return { name: 'Ed25519' }; } - throw new KeyHashValidationError(buildErrorMessage(algorithm, parsed.algorithm)); + throw new KeyHashValidationError(buildErrorMessage(algorithm, realKeyType)); } if (realKeyType === 'Ed448') { if ( @@ -114,7 +116,7 @@ export function parseSignInfo(algorithm: string | undefined, parsed: ParsedAlgor ) { return { name: 'Ed448' }; } - throw new KeyHashValidationError(buildErrorMessage(algorithm, parsed.algorithm)); + throw new KeyHashValidationError(buildErrorMessage(algorithm, realKeyType)); } throw new KeyHashValidationError(`unsupported keyAlgorithm: ${realKeyType} (provided: ${algorithm})`); diff --git a/src/types.ts b/src/types.ts index ed089a9..ff4c51f 100644 --- a/src/types.ts +++ b/src/types.ts @@ -64,6 +64,7 @@ export type PrivateKeyWithCryptoKey = { }; export type PrivateKey = PrivateKeyWithPem | PrivateKeyWithCryptoKey; +// Compatible with CryptoKey.algorithm.name export type KeyAlgorithmName = 'RSA-PSS' | 'RSASSA-PKCS1-v1_5' | 'DSA' | 'DH' | 'KEA' | 'EC' | 'Ed25519' | 'Ed448'; export type ECNamedCurve = 'P-192' | 'P-224' | 'P-256' | 'P-384' | 'P-521'; export type SignatureHashAlgorithmUpperSnake = 'SHA-1' | 'SHA-256' | 'SHA-384' | 'SHA-512' | null; diff --git a/src/utils.ts b/src/utils.ts index 8f1a771..834b196 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -126,14 +126,11 @@ export function genSignInfo( /** * Generate algorithm for sign and verify from key algorithm and defaults, * because algorithm of ECDSA and ECDH does not have hash property. - * @param algorithm - * @param defaults default values - * @returns */ -export function genAlgorithmForSignAndVerify(algorithm: webcrypto.KeyAlgorithm, defaults: SignInfoDefaults = defaultSignInfoDefaults) { +export function genAlgorithmForSignAndVerify(keyAlgorithm: webcrypto.KeyAlgorithm, hashAlgorithm: SignatureHashAlgorithmUpperSnake) { return { - hash: defaults.hash, - ...algorithm, + hash: hashAlgorithm, + ...keyAlgorithm, }; } diff --git a/test/unit/draft.ts b/test/unit/draft.ts index 75f09eb..e931661 100644 --- a/test/unit/draft.ts +++ b/test/unit/draft.ts @@ -78,9 +78,10 @@ describe('draft', () => { const parsed = parseRequestSignature(request, { clockSkew: { now: theDate } }); expect(parsed.version).toBe('draft'); if (parsed.version !== 'draft') return; + expect(parsed.value.algorithm).toBe('RSA-SHA256'); - const publicKeyPreImported = await importPublicKey(keys.rsa4096.publicKey); - const verifyResult = await verifyDraftSignature(parsed.value, publicKeyPreImported, { hash: 'SHA-256', ec: 'DSA' }, errorLogger); + const publicKeyPreImported = await importPublicKey(keys.rsa4096.publicKey, ['verify'], undefined); + const verifyResult = await verifyDraftSignature(parsed.value, publicKeyPreImported, errorLogger); expect(verifyResult).toBe(true); });