From 567acab4b68f69e57a63cdad368285cd9b57fe47 Mon Sep 17 00:00:00 2001 From: microshine Date: Tue, 4 Apr 2023 13:31:57 +0200 Subject: [PATCH 1/2] fix: reuse key ID Fixes #75 --- src/certs/cert.ts | 34 ++++++++++++ src/certs/csr.ts | 6 +-- src/certs/x509.ts | 7 ++- test/cert_storage.ts | 123 ++++++++++++++++++++++++++++++++++++++++--- 4 files changed, 156 insertions(+), 14 deletions(-) diff --git a/src/certs/cert.ts b/src/certs/cert.ts index 6f97ab0..3bc4540 100644 --- a/src/certs/cert.ts +++ b/src/certs/cert.ts @@ -1,4 +1,5 @@ import * as graphene from "graphene-pk11"; +import * as pvtsutils from "pvtsutils"; import * as core from "webcrypto-core"; import { Crypto } from "../crypto"; @@ -73,4 +74,37 @@ export abstract class CryptoCertificate extends Pkcs11Object implements Pkcs11Cr public abstract exportKey(): Promise; public abstract exportKey(algorithm: Algorithm, usages: KeyUsage[]): Promise; + /** + * Computes and returns the ID of a public key using the WebCrypto API. + * @returnsA Promise that resolves to a Buffer containing the ID of the public key. + */ + protected async computeID(): Promise { + // Retrieve the ID of the public key + let id = this.publicKey.p11Object.id; + + // Check if the key exists in the key storage + const indexes = await this.crypto.keyStorage.keys(); + if (!indexes.some(o => o.split("-")[2] === id.toString("hex"))) { + // If the key is not found, look for it on the token + const certKeyRaw = await this.crypto.subtle.exportKey("spki", this.publicKey); + for (const index of indexes) { + const [type] = index.split("-"); + if (type !== "public") { + continue; + } + + // Export the key and compare it to the public key + const key = await this.crypto.keyStorage.getItem(index); + const keyRaw = await this.crypto.subtle.exportKey("spki", key); + if (pvtsutils.BufferSourceConverter.isEqual(keyRaw, certKeyRaw)) { + // found + id = key.p11Object.id; + break; + } + } + } + + return id; + } + } diff --git a/src/certs/csr.ts b/src/certs/csr.ts index f04baac..e72a83f 100644 --- a/src/certs/csr.ts +++ b/src/certs/csr.ts @@ -45,17 +45,17 @@ export class X509CertificateRequest extends CryptoCertificate implements core.Cr const { token, label, sensitive, ...keyAlg } = algorithm; // remove custom attrs for key this.publicKey = await this.getData().publicKey.export(keyAlg, keyUsages, this.crypto as globalThis.Crypto) as CryptoKey; - const hashSPKI = this.publicKey.p11Object.id; + const id = await this.computeID(); const template = this.crypto.templateBuilder.build({ action: "import", type: "request", attributes: { - id: hashSPKI, + id, label: algorithm.label || "X509 Request", token: !!(algorithm.token), }, - }) + }); // set data attributes template.value = Buffer.from(data); diff --git a/src/certs/x509.ts b/src/certs/x509.ts index 8550eb9..83c8b8d 100644 --- a/src/certs/x509.ts +++ b/src/certs/x509.ts @@ -54,17 +54,16 @@ export class X509Certificate extends CryptoCertificate implements core.CryptoX50 this.parse(array.buffer as ArrayBuffer); const { token, label, sensitive, ...keyAlg } = algorithm; // remove custom attrs for key - this.publicKey = await this.getData().publicKey.export(keyAlg, keyUsages, this.crypto as globalThis.Crypto) as CryptoKey; - - const hashSPKI = this.publicKey.p11Object.id; + this.publicKey = await this.getData().publicKey.export(keyAlg, keyUsages, this.crypto) as CryptoKey; + const id = await this.computeID(); const certLabel = this.getName(); const template = this.crypto.templateBuilder.build({ action: "import", type: "x509", attributes: { - id: hashSPKI, + id, label: algorithm.label || certLabel, token: !!(algorithm.token), }, diff --git a/test/cert_storage.ts b/test/cert_storage.ts index 9e67742..445abc1 100644 --- a/test/cert_storage.ts +++ b/test/cert_storage.ts @@ -1,4 +1,5 @@ import * as assert from "assert"; +import * as graphene from "graphene-pk11"; import * as x509 from "@peculiar/x509"; import { CryptoCertificateFormat, PemConverter } from "webcrypto-core"; import { Pkcs11RsaHashedKeyAlgorithm, X509Certificate, X509CertificateRequest } from "../src"; @@ -16,13 +17,8 @@ const X509_REQUEST_PEM = PemConverter.fromBufferSource(X509_REQUEST_RAW, "CERTIF ("Certificate storage", () => { beforeEach(async () => { - let keys = await crypto.certStorage.keys(); - if (keys.length) { - await crypto.certStorage.clear(); - } - - keys = await crypto.certStorage.keys(); - assert.strictEqual(keys.length, 0); + await crypto.certStorage.clear(); + await crypto.keyStorage.clear(); }); context("indexOf", () => { @@ -244,4 +240,117 @@ const X509_REQUEST_PEM = PemConverter.fromBufferSource(X509_REQUEST_RAW, "CERTIF assert.strictEqual(keyIndex.split("-")[2], certIndex.split("-")[2]); }); + context("issue #75", () => { + + /** + * Generate RSA key pair using graphene + * @returns id of generated key + */ + function generateRsaKeys(): Buffer { + const id = crypto.getRandomValues(Buffer.alloc(10)); + + crypto.session.generateKeyPair(graphene.KeyGenMechanism.RSA, { + keyType: graphene.KeyType.RSA, + id, + modulusBits: 2048, + publicExponent: Buffer.from([1, 0, 1]), + token: true, + verify: true, + encrypt: true, + wrap: true + }, { + keyType: graphene.KeyType.RSA, + id, + token: true, + sign: true, + decrypt: true, + unwrap: true + }); + return id; + } + + interface NullableCryptoKeyPair { + privateKey: CryptoKey | null; + publicKey: CryptoKey | null; + } + + /** + * Get CryptoKeyPair using node-webcrypto-p11 + * @param id id of key pair + * @returns CryptoKeyPair + */ + async function getCryptoKeys(id: Buffer): Promise { + let privateKey: CryptoKey | null = null; + let publicKey: CryptoKey | null = null; + + const indexes = await crypto.keyStorage.keys(); + for (const index of indexes) { + if (index.split("-")[2] === id.toString("hex")) { + const key = await crypto.keyStorage.getItem(index); + if (key.type === "private") { + privateKey = key; + } else if (key.type === "public") { + publicKey = key; + } + } + } + + return { privateKey, publicKey }; + } + + it("import x509 certificate", async () => { + // generate RSA key using graphene + const id = generateRsaKeys(); + const keys = await getCryptoKeys(id); + + assert.ok(keys.privateKey, "Private key not found"); + assert.ok(keys.publicKey, "Public key not found"); + + const signingAlg = { + name: "RSASSA-PKCS1-v1_5", + hash: "SHA-256", + }; + const cert = await x509.X509CertificateGenerator.createSelfSigned({ + serialNumber: "01", + name: "CN=Test", + notBefore: new Date("2020/01/01"), + notAfter: new Date("2020/01/02"), + signingAlgorithm: signingAlg, + keys: { + privateKey: keys.privateKey, + publicKey: keys.publicKey, + }, + }, crypto); + + const p11Cert = await crypto.certStorage.importCert("raw", cert.rawData, signingAlg, ["verify"]); + assert.strictEqual(p11Cert.id.split("-")[2], id.toString("hex")); + }); + + it("import CSR", async () => { + // generate RSA key using graphene + const id = generateRsaKeys(); + const keys = await getCryptoKeys(id); + + assert.ok(keys.privateKey, "Private key not found"); + assert.ok(keys.publicKey, "Public key not found"); + + const signingAlg = { + name: "RSASSA-PKCS1-v1_5", + hash: "SHA-256", + }; + const cert = await x509.Pkcs10CertificateRequestGenerator.create({ + name: "CN=Test", + signingAlgorithm: signingAlg, + keys: { + privateKey: keys.privateKey, + publicKey: keys.publicKey, + }, + }, crypto); + + const p11Cert = await crypto.certStorage.importCert("raw", cert.rawData, signingAlg, ["verify"]); + assert.strictEqual(p11Cert.id.split("-")[2], id.toString("hex")); + }); + + }); }); + From 8526e4e26b5a5f123e9cb15e64209704127427a0 Mon Sep 17 00:00:00 2001 From: microshine Date: Tue, 4 Apr 2023 13:34:16 +0200 Subject: [PATCH 2/2] chore(deps): update dependencies --- package.json | 14 +++++------ yarn.lock | 70 ++++++++++++++++++++++++++-------------------------- 2 files changed, 42 insertions(+), 42 deletions(-) diff --git a/package.json b/package.json index c272bed..3e6b39d 100644 --- a/package.json +++ b/package.json @@ -27,15 +27,15 @@ "url": "https://github.com/PeculiarVentures/node-webcrypto-p11.git" }, "dependencies": { - "@peculiar/asn1-schema": "^2.3.3", - "@peculiar/asn1-x509": "^2.3.4", + "@peculiar/asn1-schema": "^2.3.6", + "@peculiar/asn1-x509": "^2.3.6", "@peculiar/json-schema": "^1.1.12", "@peculiar/x509": "^1.9.3", "graphene-pk11": "^2.3.2", "pkcs11js": "^1.3.1", "pvtsutils": "^1.3.2", "tslib": "^2.5.0", - "webcrypto-core": "^1.7.6" + "webcrypto-core": "^1.7.7" }, "keywords": [ "crypto", @@ -59,15 +59,15 @@ "devDependencies": { "@peculiar/webcrypto-test": "^1.0.7", "@types/mocha": "^10.0.1", - "@types/node": "^18.13.0", + "@types/node": "^18.15.11", "mocha": "^10.2.0", "nyc": "^15.1.0", - "rollup": "^3.15.0", - "rollup-plugin-dts": "^5.1.1", + "rollup": "^3.20.2", + "rollup-plugin-dts": "^5.3.0", "rollup-plugin-typescript2": "^0.34.1", "ts-node": "^10.9.1", "tslint": "^6.1.3", - "typescript": "^4.9.5" + "typescript": "^5.0.3" }, "funding": { "type": "github", diff --git a/yarn.lock b/yarn.lock index 083bd0f..49d6abf 100644 --- a/yarn.lock +++ b/yarn.lock @@ -324,10 +324,10 @@ asn1js "^3.0.5" tslib "^2.4.0" -"@peculiar/asn1-schema@^2.1.6", "@peculiar/asn1-schema@^2.3.3": - version "2.3.3" - resolved "https://registry.yarnpkg.com/@peculiar/asn1-schema/-/asn1-schema-2.3.3.tgz#21418e1f3819e0b353ceff0c2dad8ccb61acd777" - integrity sha512-6GptMYDMyWBHTUKndHaDsRZUO/XMSgIns2krxcm2L7SEExRHwawFvSwNBhqNPR9HJwv3MruAiF1bhN0we6j6GQ== +"@peculiar/asn1-schema@^2.3.3", "@peculiar/asn1-schema@^2.3.6": + version "2.3.6" + resolved "https://registry.yarnpkg.com/@peculiar/asn1-schema/-/asn1-schema-2.3.6.tgz#3dd3c2ade7f702a9a94dfb395c192f5fa5d6b922" + integrity sha512-izNRxPoaeJeg/AyH8hER6s+H7p4itk+03QCa4sbxI3lNdseQYCuxzgsuNK8bTXChtLTjpJz6NmXKA73qLa3rCA== dependencies: asn1js "^3.0.5" pvtsutils "^1.3.2" @@ -343,12 +343,12 @@ asn1js "^3.0.5" tslib "^2.4.0" -"@peculiar/asn1-x509@^2.3.4": - version "2.3.4" - resolved "https://registry.yarnpkg.com/@peculiar/asn1-x509/-/asn1-x509-2.3.4.tgz#416bfd9ccdba3c512354d9d43d67969d6084f2fa" - integrity sha512-NhA6U76kiGKTQG2WQyGfRS/piYHt7HxUsGb0IvQaiJheuucKb2CYu0/tOk1dayZcvFf6Pnf9HjFGQ/5ud/ndRQ== +"@peculiar/asn1-x509@^2.3.4", "@peculiar/asn1-x509@^2.3.6": + version "2.3.6" + resolved "https://registry.yarnpkg.com/@peculiar/asn1-x509/-/asn1-x509-2.3.6.tgz#e50154a460cdf43da8a41b23ee807a53e0036af0" + integrity sha512-dRwX31R1lcbIdzbztiMvLNTDoGptxdV7HocNx87LfKU0fEWh7fTWJjx4oV+glETSy6heF/hJHB2J4RGB3vVSYg== dependencies: - "@peculiar/asn1-schema" "^2.3.3" + "@peculiar/asn1-schema" "^2.3.6" asn1js "^3.0.5" ipaddr.js "^2.0.1" pvtsutils "^1.3.2" @@ -419,10 +419,10 @@ resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-10.0.1.tgz#2f4f65bb08bc368ac39c96da7b2f09140b26851b" integrity sha512-/fvYntiO1GeICvqbQ3doGDIP97vWmvFt83GKguJ6prmQM2iXZfFcq6YE8KteFyRtX2/h5Hf91BYvPodJKFYv5Q== -"@types/node@^18.13.0": - version "18.13.0" - resolved "https://registry.yarnpkg.com/@types/node/-/node-18.13.0.tgz#0400d1e6ce87e9d3032c19eb6c58205b0d3f7850" - integrity sha512-gC3TazRzGoOnoKAhUx+Q0t8S9Tzs74z7m0ipwGpSqQrleP14hKxP4/JUeEQcD3W1/aIpnWl8pHowI7WokuZpXg== +"@types/node@^18.15.11": + version "18.15.11" + resolved "https://registry.yarnpkg.com/@types/node/-/node-18.15.11.tgz#b3b790f09cb1696cffcec605de025b088fa4225f" + integrity sha512-E5Kwq2n4SbMzQOn6wnmBjuK9ouqlURrcZDVfbo9ftDDTFt3nk7ZKK4GMOzoYgnpQJKcxwQw+lGaBvvlMo0qN/Q== acorn-walk@^8.1.1: version "8.2.0" @@ -1168,10 +1168,10 @@ lru-cache@^6.0.0: dependencies: yallist "^4.0.0" -magic-string@^0.27.0: - version "0.27.0" - resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.27.0.tgz#e4a3413b4bab6d98d2becffd48b4a257effdbbf3" - integrity sha512-8UnnX2PeRAPZuN12svgR9j7M1uWMovg/CEnIwIG0LFkXSJJe4PdfUGiTGl8V9bsBHFUtfVINcSyYxd7q+kx9fA== +magic-string@^0.30.0: + version "0.30.0" + resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.30.0.tgz#fd58a4748c5c4547338a424e90fa5dd17f4de529" + integrity sha512-LA+31JYDJLs82r2ScLrlz1GjSgu66ZV518eyWT+S8VhyQn/JL0u9MeBOvQMGYiPk1DBiSN9DDMOcXvigJZaViQ== dependencies: "@jridgewell/sourcemap-codec" "^1.4.13" @@ -1487,12 +1487,12 @@ rimraf@^3.0.0: dependencies: glob "^7.1.3" -rollup-plugin-dts@^5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/rollup-plugin-dts/-/rollup-plugin-dts-5.1.1.tgz#8cc36ab13135b77ef0cfd6107e4af561c5dffd04" - integrity sha512-zpgo52XmnLg8w4k3MScinFHZK1+ro6r7uVe34fJ0Ee8AM45FvgvTuvfWWaRgIpA4pQ1BHJuu2ospncZhkcJVeA== +rollup-plugin-dts@^5.3.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/rollup-plugin-dts/-/rollup-plugin-dts-5.3.0.tgz#80a95988002f188e376f6db3b7e2f53679168957" + integrity sha512-8FXp0ZkyZj1iU5klkIJYLjIq/YZSwBoERu33QBDxm/1yw5UU4txrEtcmMkrq+ZiKu3Q4qvPCNqc3ovX6rjqzbQ== dependencies: - magic-string "^0.27.0" + magic-string "^0.30.0" optionalDependencies: "@babel/code-frame" "^7.18.6" @@ -1507,10 +1507,10 @@ rollup-plugin-typescript2@^0.34.1: semver "^7.3.7" tslib "^2.4.0" -rollup@^3.15.0: - version "3.15.0" - resolved "https://registry.yarnpkg.com/rollup/-/rollup-3.15.0.tgz#6f4105e8c4b8145229657b74ad660b02fbfacc05" - integrity sha512-F9hrCAhnp5/zx/7HYmftvsNBkMfLfk/dXUh73hPSM2E3CRgap65orDNJbLetoiUFwSAk6iHPLvBrZ5iHYvzqsg== +rollup@^3.20.2: + version "3.20.2" + resolved "https://registry.yarnpkg.com/rollup/-/rollup-3.20.2.tgz#f798c600317f216de2e4ad9f4d9ab30a89b690ff" + integrity sha512-3zwkBQl7Ai7MFYQE0y1MeQ15+9jsi7XxfrqwTb/9EK8D9C9+//EBR4M+CuA1KODRaNbFez/lWxA5vhEGZp4MUg== optionalDependencies: fsevents "~2.3.2" @@ -1739,10 +1739,10 @@ typedarray-to-buffer@^3.1.5: dependencies: is-typedarray "^1.0.0" -typescript@^4.9.5: - version "4.9.5" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.9.5.tgz#095979f9bcc0d09da324d58d03ce8f8374cbe65a" - integrity sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g== +typescript@^5.0.3: + version "5.0.3" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.0.3.tgz#fe976f0c826a88d0a382007681cbb2da44afdedf" + integrity sha512-xv8mOEDnigb/tN9PSMTwSEqAnUvkoXMQlicOb0IUVDBSQCgBSaAAROUZYy2IcUy5qU6XajK5jjjO7TMWqBTKZA== universalify@^2.0.0: version "2.0.0" @@ -1759,12 +1759,12 @@ v8-compile-cache-lib@^3.0.1: resolved "https://registry.yarnpkg.com/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz#6336e8d71965cb3d35a1bbb7868445a7c05264bf" integrity sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg== -webcrypto-core@^1.7.6: - version "1.7.6" - resolved "https://registry.yarnpkg.com/webcrypto-core/-/webcrypto-core-1.7.6.tgz#e32c4a12a13de4251f8f9ef336a6cba7cdec9b55" - integrity sha512-TBPiewB4Buw+HI3EQW+Bexm19/W4cP/qZG/02QJCXN+iN+T5sl074vZ3rJcle/ZtDBQSgjkbsQO/1eFcxnSBUA== +webcrypto-core@^1.7.7: + version "1.7.7" + resolved "https://registry.yarnpkg.com/webcrypto-core/-/webcrypto-core-1.7.7.tgz#06f24b3498463e570fed64d7cab149e5437b162c" + integrity sha512-7FjigXNsBfopEj+5DV2nhNpfic2vumtjjgPmeDKk45z+MJwXKKfhPB7118Pfzrmh4jqOMST6Ch37iPAHoImg5g== dependencies: - "@peculiar/asn1-schema" "^2.1.6" + "@peculiar/asn1-schema" "^2.3.6" "@peculiar/json-schema" "^1.1.12" asn1js "^3.0.1" pvtsutils "^1.3.2"