Skip to content

Commit

Permalink
fix: reuse key ID
Browse files Browse the repository at this point in the history
Fixes #75
  • Loading branch information
microshine committed Apr 4, 2023
1 parent 9d91300 commit 567acab
Show file tree
Hide file tree
Showing 4 changed files with 156 additions and 14 deletions.
34 changes: 34 additions & 0 deletions src/certs/cert.ts
Original file line number Diff line number Diff line change
@@ -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";
Expand Down Expand Up @@ -73,4 +74,37 @@ export abstract class CryptoCertificate extends Pkcs11Object implements Pkcs11Cr
public abstract exportKey(): Promise<CryptoKey>;
public abstract exportKey(algorithm: Algorithm, usages: KeyUsage[]): Promise<CryptoKey>;

/**
* 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<Buffer> {
// 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;
}

}
6 changes: 3 additions & 3 deletions src/certs/csr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
7 changes: 3 additions & 4 deletions src/certs/x509.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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),
},
Expand Down
123 changes: 116 additions & 7 deletions test/cert_storage.ts
Original file line number Diff line number Diff line change
@@ -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";
Expand All @@ -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", () => {
Expand Down Expand Up @@ -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<NullableCryptoKeyPair> {
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"));
});

});
});

0 comments on commit 567acab

Please sign in to comment.