From 14c7c3c99b662fd5471336e58aa0bb9d3d4a3a13 Mon Sep 17 00:00:00 2001 From: Abhijit Roy Date: Thu, 13 Jun 2024 19:55:31 +0530 Subject: [PATCH 1/7] Create CSR passes for RSA, but not Ed25519 Need to debug the issue. --- packages/auto-id/package.json | 4 +- packages/auto-id/src/certificateManager.ts | 241 ++++++++++++++++++ packages/auto-id/src/index.ts | 1 + packages/auto-id/src/keyManagement.ts | 16 +- .../auto-id/tests/certificateManager.test.ts | 84 ++++++ yarn.lock | 18 ++ 6 files changed, 361 insertions(+), 3 deletions(-) create mode 100644 packages/auto-id/src/certificateManager.ts create mode 100644 packages/auto-id/tests/certificateManager.test.ts diff --git a/packages/auto-id/package.json b/packages/auto-id/package.json index c3a70e4c..cbe56e52 100644 --- a/packages/auto-id/package.json +++ b/packages/auto-id/package.json @@ -12,7 +12,8 @@ "@autonomys/auto-utils": "workspace:*", "@peculiar/asn1-schema": "^2.3.8", "@peculiar/asn1-x509": "^2.3.8", - "asn1js": "^3.0.5" + "asn1js": "^3.0.5", + "node-forge": "^1.3.1" }, "files": [ "dist", @@ -21,6 +22,7 @@ "devDependencies": { "@types/jest": "^29.5.12", "@types/node": "^20.12.12", + "@types/node-forge": "^1", "jest": "^29.7.0", "ts-jest": "^29.1.4", "ts-node": "^10.9.2", diff --git a/packages/auto-id/src/certificateManager.ts b/packages/auto-id/src/certificateManager.ts new file mode 100644 index 00000000..705f9eb8 --- /dev/null +++ b/packages/auto-id/src/certificateManager.ts @@ -0,0 +1,241 @@ +//! For key generation, management, `keyManagement.ts` file is used i.e. "crypto" library. +//! And for certificate related, used "node-forge" library. + +import { blake2b_256, stringToUint8Array } from '@autonomys/auto-utils' +import { KeyObject, createPublicKey, createSign } from 'crypto' +import fs from 'fs' +import forge from 'node-forge' +import { keyToPem } from './keyManagement' + +interface CustomCertificateExtension { + altNames: { + type: number + value: string + }[] +} + +interface SigningParams { + privateKey: KeyObject + algorithm: 'sha256' | null // Only 'sha256' or null for Ed25519 +} + +class CertificateManager { + private certificate: forge.pki.Certificate | null + private privateKey: KeyObject | null + + constructor( + certificate: forge.pki.Certificate | null = null, + privateKey: KeyObject | null = null, + ) { + this.certificate = certificate + this.privateKey = privateKey + } + + protected prepareSigningParams(): SigningParams { + const privateKey = this.privateKey + if (!privateKey) { + throw new Error('Private key is not set.') + } + + if (privateKey.asymmetricKeyType === 'ed25519') { + return { privateKey: privateKey, algorithm: null } + } + if (privateKey.asymmetricKeyType === 'rsa') { + return { privateKey: privateKey, algorithm: 'sha256' } + } + + throw new Error('Unsupported key type for signing.') + } + + static toCommonName(subjectName: string): forge.pki.CertificateField[] { + return [{ name: 'commonName', value: subjectName }] + } + + static prettyPrintCertificate(cert: forge.pki.Certificate): void { + console.log('Certificate:') + console.log('============') + console.log( + `Subject: ${cert.subject.attributes.map((attr) => `${attr.name}=${attr.value}`).join(', ')}`, + ) + console.log( + `Issuer: ${cert.issuer.attributes.map((attr) => `${attr.name}=${attr.value}`).join(', ')}`, + ) + console.log(`Serial Number: ${cert.serialNumber}`) + console.log(`Not Valid Before: ${cert.validity.notBefore.toISOString()}`) + console.log(`Not Valid After: ${cert.validity.notAfter.toISOString()}`) + console.log('\nExtensions:') + cert.extensions.forEach((ext) => { + console.log(` - ${ext.name} (${ext.id}): ${JSON.stringify(ext.value)}`) + }) + console.log('\nPublic Key:') + console.log(cert.publicKey) + } + + static certificateToPem(cert: forge.pki.Certificate): string { + return forge.pki.certificateToPem(cert) + } + + static pemToCertificate(pem: string): forge.pki.Certificate { + return forge.pki.certificateFromPem(pem) + } + + static getSubjectCommonName(subjectFields: forge.pki.CertificateField[]): string | undefined { + const cnField = subjectFields.find((field) => field.name === 'commonName') + if (cnField && typeof cnField.value === 'string') { + return cnField.value + } + return undefined + } + + static getCertificateAutoId(certificate: forge.pki.Certificate): string | undefined { + const sanExtension = certificate.getExtension('subjectAltName') + if (sanExtension) { + const san = sanExtension as CustomCertificateExtension + for (const name of san.altNames) { + if (name.type === 6 && name.value.startsWith('autoid:auto:')) { + return name.value.split(':').pop() + } + } + } + return undefined + } + + static pemPublicFromPrivateKey(privateKey: KeyObject): string { + const publicKey = createPublicKey(privateKey) + return publicKey.export({ type: 'spki', format: 'pem' }).toString() + } + + static derPublicFromPrivateKey(privateKey: KeyObject): string { + const publicKey = createPublicKey(privateKey) + return publicKey.export({ type: 'spki', format: 'der' }).toString() + } + + createCSR(subjectName: string): forge.pki.CertificateSigningRequest { + const privateKey = this.privateKey + if (!privateKey) { + throw new Error('Private key is not set.') + } + let csr = forge.pki.createCertificationRequest() + csr.setSubject(CertificateManager.toCommonName(subjectName)) + + if (privateKey.asymmetricKeyType === 'ed25519') { + // Manually handle Ed25519 due to possible forge limitations + const publicKeyDer = CertificateManager.derPublicFromPrivateKey(privateKey) + + // Directly assign the public key in DER format + csr.publicKey = forge.pki.publicKeyFromAsn1(forge.asn1.fromDer(publicKeyDer)) + // csr.publicKey = forge.pki.publicKeyFromPem( + // CertificateManager.pemPublicFromPrivateKey(privateKey), + // ) + } else { + csr.publicKey = forge.pki.publicKeyFromPem( + CertificateManager.pemPublicFromPrivateKey(privateKey), + ) + } + return csr + } + + signCSR(csr: forge.pki.CertificateSigningRequest): forge.pki.CertificateSigningRequest { + const signingParams = this.prepareSigningParams() + if (this.privateKey?.asymmetricKeyType === 'ed25519') { + // Ensure cryptographic algorithm is set to sign + // if (!csr.siginfo.algorithmOid) { + // throw new Error('Signature algorithm OID must be set before signing the CSR.') + // } + + // console.log('Inspecting CSR before converting to ASN.1:', csr) + const asn1 = forge.pki.certificationRequestToAsn1(csr) + const derBuffer = forge.asn1.toDer(asn1).getBytes() + + const sign = createSign('SHA256') + sign.update(derBuffer, 'binary') // Make sure the update is called with 'binary' encoding + sign.end() + + const signature = sign.sign(signingParams.privateKey, 'binary') + csr.signature = Buffer.from(signature, 'binary') + } else { + if (signingParams.algorithm) { + const digestMethod = forge.md[signingParams.algorithm].create() + csr.sign(forge.pki.privateKeyFromPem(keyToPem(signingParams.privateKey)), digestMethod) + } else { + throw new Error('Unsupported key type or missing algorithm.') + } + } + + return csr + } + + create_and_sign_csr(subject_name: string): forge.pki.CertificateSigningRequest { + const csr = this.createCSR(subject_name) + return this.signCSR(csr) + } + + issueCertificate( + csr: forge.pki.CertificateSigningRequest, + validityPeriodDays: number = 365, + ): string { + if (!this.privateKey) { + throw new Error('Private key is not set.') + } + let issuerName + let autoId: string + if (!this.certificate) { + issuerName = csr.subject.attributes + autoId = blake2b_256( + stringToUint8Array(CertificateManager.getSubjectCommonName(csr.subject.attributes) || ''), + ) + } else { + issuerName = this.certificate.subject.attributes + const autoIdPrefix = CertificateManager.getCertificateAutoId(this.certificate) || '' + autoId = blake2b_256( + stringToUint8Array( + autoIdPrefix + + (CertificateManager.getSubjectCommonName(this.certificate.subject.attributes) || ''), + ), + ) + } + const cert = forge.pki.createCertificate() + if (!csr.publicKey) + throw new Error('CSR does not have a public key. Please provide a CSR with a public key.') + cert.publicKey = csr.publicKey + cert.publicKey = csr.publicKey + cert.serialNumber = new Date().getTime().toString(16) + cert.validity.notBefore = new Date() + cert.validity.notAfter = new Date() + cert.validity.notAfter.setDate(cert.validity.notBefore.getDate() + validityPeriodDays) + cert.setSubject(csr.subject.attributes) + cert.setIssuer(issuerName) + const attribute = csr.getAttribute({ name: 'extensionRequest' }) + if (!attribute || !attribute.extensions) { + throw new Error('CSR does not have extensions.') + } + const extensions = attribute.extensions + if (!extensions) { + throw new Error('CSR does not have extensions. Please provide a CSR with extensions.') + } + extensions.push({ + name: 'subjectAltName', + altNames: [ + { + type: 6, // URI + value: `autoid:auto:${autoId}`, + }, + ], + }) + cert.setExtensions(extensions) + + cert.sign(forge.pki.privateKeyFromPem(keyToPem(this.privateKey)), forge.md.sha256.create()) + return forge.pki.certificateToPem(cert) + } + + saveCertificate(filePath: string): void { + const certificate = this.certificate + if (!certificate) { + throw new Error('No certificate available to save.') + } + const certificatePem = CertificateManager.certificateToPem(certificate) + fs.writeFileSync(filePath, certificatePem, 'utf8') + } +} + +export default CertificateManager diff --git a/packages/auto-id/src/index.ts b/packages/auto-id/src/index.ts index 55276676..953fd9ca 100644 --- a/packages/auto-id/src/index.ts +++ b/packages/auto-id/src/index.ts @@ -1,2 +1,3 @@ +export * from './certificateManager' export * from './keyManagement' export * from './utils' diff --git a/packages/auto-id/src/keyManagement.ts b/packages/auto-id/src/keyManagement.ts index 391f2618..3530b1d4 100644 --- a/packages/auto-id/src/keyManagement.ts +++ b/packages/auto-id/src/keyManagement.ts @@ -19,6 +19,12 @@ export function generateRsaKeyPair(keySize: number = 2048): [string, string] { return [privateKey, publicKey] } +// export function generateRsaKeyPair(keySize: number = 2048): [string, string] { +// const { publicKey, privateKey } = forge.pki.rsa.generateKeyPair({ bits: keySize, e: 0x10001 }) + +// return [privateKey.toString(), publicKey.toString()] +// } + /** * Generates an Ed25519 key pair. * @returns A tuple containing the Ed25519 private key and public key. @@ -32,6 +38,12 @@ export function generateEd25519KeyPair(): [string, string] { return [privateKey, publicKey] } +// export function generateEd25519KeyPair(): [string, string] { +// const { privateKey, publicKey } = forge.pki.ed25519.generateKeyPair() + +// return [privateKey.toString(), publicKey.toString()] +// } + /** * Converts a cryptographic key object into a PEM formatted string. * This function can handle both private and public key objects. @@ -167,7 +179,7 @@ export async function loadPrivateKey(filePath: string, password?: string): Promi try { const keyData = await read(filePath) const privateKey = pemToPrivateKey(keyData, password) - return privateKey; + return privateKey } catch (error: any) { throw new Error(`Failed to load private key: ${error.message}`) } @@ -286,4 +298,4 @@ export function doPublicKeysMatch(publicKey1: KeyObject, publicKey2: KeyObject): // Compare the serialized public key data return publicKey1Der.equals(publicKey2Der) -} \ No newline at end of file +} diff --git a/packages/auto-id/tests/certificateManager.test.ts b/packages/auto-id/tests/certificateManager.test.ts new file mode 100644 index 00000000..cc9ce4e1 --- /dev/null +++ b/packages/auto-id/tests/certificateManager.test.ts @@ -0,0 +1,84 @@ +import { createPublicKey } from 'crypto' +import * as forge from 'node-forge' +import CertificateManager from '../src/certificateManager' +import { + doPublicKeysMatch, + generateEd25519KeyPair, + generateRsaKeyPair, + pemToPrivateKey, + pemToPublicKey, +} from '../src/keyManagement' + +describe('CertificateManager', () => { + it('creates and signs a CSR with an Ed25519 key', () => { + // Generate an Ed25519 key pair + const [privateKey, _] = generateEd25519KeyPair() + // const keypair = forge.pki.ed25519.generateKeyPair() + + // Define the subject name for the CSR + const subjectName = 'Test' + + // Instantiate CertificateManager with the generated private key + const manager = new CertificateManager(null, pemToPrivateKey(privateKey)) + + // Create and sign CSR + const csr = manager.create_and_sign_csr(subjectName) + + // Assert that the CSR is not null + expect(csr).toBeDefined() + + // Assert that the CSR subject name matches the provided subject name + const commonNameField = csr.subject.attributes.find((attr) => attr.name === 'commonName') + expect(commonNameField?.value).toEqual(subjectName) + + // Get the derived public key (in forge) from original private key. + // private key (PEM) -> private key(KeyObject) -> public key(PEM) + const derivedPublicKeyObj = pemToPublicKey( + CertificateManager.pemPublicFromPrivateKey(pemToPrivateKey(privateKey)), + ) + + // Assert that the CSR public key matches the public key from the key pair + if (csr.publicKey) { + // Convert forge.PublicKey format to crypto.KeyObject + const csrPublicKeyObj = createPublicKey(forge.pki.publicKeyToPem(csr.publicKey)) + + expect(doPublicKeysMatch(csrPublicKeyObj, derivedPublicKeyObj)).toBe(true) + } else { + throw new Error('CSR does not have a public key.') + } + }) + + it('create and sign CSR with RSA key', () => { + // Generate a RSA key pair + const [privateKey, _] = generateRsaKeyPair() + + // Instantiate CertificateManager with the generated private key + const certificateManager = new CertificateManager(null, pemToPrivateKey(privateKey)) + + // Create and sign a CSR + const subjectName = 'Test' + const csr = certificateManager.create_and_sign_csr(subjectName) + + expect(csr).toBeDefined() + + // Assert that the CSR subject name matches the provided subject name + const commonNameField = csr.subject.attributes.find((attr) => attr.name === 'commonName') + expect(commonNameField?.value).toEqual(subjectName) + + // Get the derived public key (in forge) from original private key. + // private key (PEM) -> private key(KeyObject) -> public key(PEM) + const derivedPublicKeyObj = pemToPublicKey( + CertificateManager.pemPublicFromPrivateKey(pemToPrivateKey(privateKey)), + ) + + // Assert that the CSR public key matches the public key from the key pair + if (csr.publicKey) { + // Convert forge.PublicKey format to crypto.KeyObject + const csrPublicKeyObj = createPublicKey(forge.pki.publicKeyToPem(csr.publicKey)) + + expect(doPublicKeysMatch(csrPublicKeyObj, derivedPublicKeyObj)).toBe(true) + } else { + throw new Error('CSR does not have a public key.') + } + }) +}) diff --git a/yarn.lock b/yarn.lock index d2d7b95a..e7562e43 100644 --- a/yarn.lock +++ b/yarn.lock @@ -36,8 +36,10 @@ __metadata: "@peculiar/asn1-x509": "npm:^2.3.8" "@types/jest": "npm:^29.5.12" "@types/node": "npm:^20.12.12" + "@types/node-forge": "npm:^1" asn1js: "npm:^3.0.5" jest: "npm:^29.7.0" + node-forge: "npm:^1.3.1" ts-jest: "npm:^29.1.4" ts-node: "npm:^10.9.2" typescript: "npm:^5.4.5" @@ -1626,6 +1628,15 @@ __metadata: languageName: node linkType: hard +"@types/node-forge@npm:^1": + version: 1.3.11 + resolution: "@types/node-forge@npm:1.3.11" + dependencies: + "@types/node": "npm:*" + checksum: 10c0/3d7d23ca0ba38ac0cf74028393bd70f31169ab9aba43f21deb787840170d307d662644bac07287495effe2812ddd7ac8a14dbd43f16c2936bbb06312e96fc3b9 + languageName: node + linkType: hard + "@types/node@npm:*": version: 20.14.0 resolution: "@types/node@npm:20.14.0" @@ -4002,6 +4013,13 @@ __metadata: languageName: node linkType: hard +"node-forge@npm:^1.3.1": + version: 1.3.1 + resolution: "node-forge@npm:1.3.1" + checksum: 10c0/e882819b251a4321f9fc1d67c85d1501d3004b4ee889af822fd07f64de3d1a8e272ff00b689570af0465d65d6bf5074df9c76e900e0aff23e60b847f2a46fbe8 + languageName: node + linkType: hard + "node-gyp@npm:latest": version: 10.1.0 resolution: "node-gyp@npm:10.1.0" From 3834b9dcac72fb38add00cfa53d82b28f806f5a3 Mon Sep 17 00:00:00 2001 From: Abhijit Roy Date: Fri, 14 Jun 2024 03:25:54 +0530 Subject: [PATCH 2/7] Improve issue certificate function. TODO: Debug verification of certificate with signature --- packages/auto-id/src/certificateManager.ts | 132 +++++++++++++----- packages/auto-id/src/utils.ts | 17 +++ .../auto-id/tests/certificateManager.test.ts | 92 +++++++----- packages/auto-utils/__test__/crypto.test.ts | 32 ++++- packages/auto-utils/src/crypto.ts | 10 ++ 5 files changed, 210 insertions(+), 73 deletions(-) diff --git a/packages/auto-id/src/certificateManager.ts b/packages/auto-id/src/certificateManager.ts index 705f9eb8..24d404af 100644 --- a/packages/auto-id/src/certificateManager.ts +++ b/packages/auto-id/src/certificateManager.ts @@ -1,11 +1,12 @@ //! For key generation, management, `keyManagement.ts` file is used i.e. "crypto" library. //! And for certificate related, used "node-forge" library. -import { blake2b_256, stringToUint8Array } from '@autonomys/auto-utils' +import { blake2b_256, concatenateUint8Arrays, stringToUint8Array } from '@autonomys/auto-utils' import { KeyObject, createPublicKey, createSign } from 'crypto' import fs from 'fs' import forge from 'node-forge' -import { keyToPem } from './keyManagement' +import { doPublicKeysMatch, keyToPem, pemToPublicKey } from './keyManagement' +import { addDaysToCurrentDate, randomSerialNumber } from './utils' interface CustomCertificateExtension { altNames: { @@ -165,7 +166,7 @@ class CertificateManager { return csr } - create_and_sign_csr(subject_name: string): forge.pki.CertificateSigningRequest { + createAndSignCSR(subject_name: string): forge.pki.CertificateSigningRequest { const csr = this.createCSR(subject_name) return this.signCSR(csr) } @@ -173,59 +174,120 @@ class CertificateManager { issueCertificate( csr: forge.pki.CertificateSigningRequest, validityPeriodDays: number = 365, - ): string { - if (!this.privateKey) { + ): forge.pki.Certificate { + const privateKey = this.privateKey + const certificate = this.certificate + if (!privateKey) { throw new Error('Private key is not set.') } - let issuerName + + let issuerName: any let autoId: string - if (!this.certificate) { + if (!certificate) { issuerName = csr.subject.attributes autoId = blake2b_256( stringToUint8Array(CertificateManager.getSubjectCommonName(csr.subject.attributes) || ''), ) } else { - issuerName = this.certificate.subject.attributes - const autoIdPrefix = CertificateManager.getCertificateAutoId(this.certificate) || '' + if ( + !doPublicKeysMatch( + createPublicKey(forge.pki.publicKeyToPem(certificate.publicKey)), + pemToPublicKey(CertificateManager.pemPublicFromPrivateKey(privateKey)), + ) + ) { + throw new Error( + 'Issuer certificate public key does not match the private key used for signing.', + ) + } + + issuerName = certificate.subject.attributes + const certificateAutoId = CertificateManager.getCertificateAutoId(certificate) || '' + const certificateSubjectCommonName = + CertificateManager.getSubjectCommonName(certificate.subject.attributes) || '' + if (certificateAutoId === '' || certificateSubjectCommonName === '') { + throw new Error( + 'Issuer certificate does not have either an auto ID or a subject common name or both.', + ) + } autoId = blake2b_256( - stringToUint8Array( - autoIdPrefix + - (CertificateManager.getSubjectCommonName(this.certificate.subject.attributes) || ''), + concatenateUint8Arrays( + stringToUint8Array(certificateAutoId), + stringToUint8Array(certificateSubjectCommonName), ), ) } + + // Prepare the certificate builder with information from the CSR const cert = forge.pki.createCertificate() if (!csr.publicKey) throw new Error('CSR does not have a public key. Please provide a CSR with a public key.') - cert.publicKey = csr.publicKey - cert.publicKey = csr.publicKey - cert.serialNumber = new Date().getTime().toString(16) - cert.validity.notBefore = new Date() - cert.validity.notAfter = new Date() - cert.validity.notAfter.setDate(cert.validity.notBefore.getDate() + validityPeriodDays) cert.setSubject(csr.subject.attributes) cert.setIssuer(issuerName) - const attribute = csr.getAttribute({ name: 'extensionRequest' }) - if (!attribute || !attribute.extensions) { - throw new Error('CSR does not have extensions.') - } - const extensions = attribute.extensions - if (!extensions) { - throw new Error('CSR does not have extensions. Please provide a CSR with extensions.') + cert.publicKey = csr.publicKey + cert.serialNumber = randomSerialNumber().toString() + cert.validity.notBefore = new Date() + cert.validity.notAfter = addDaysToCurrentDate(validityPeriodDays) + + const autoIdSan = `autoid:auto:${Buffer.from(autoId).toString('hex')}` + let sanExtensionFound = false + + // Check for existing SAN extension + const extensions = csr.getAttribute({ name: 'extensionRequest' })?.extensions + if (extensions) { + for (const ext of extensions) { + if (ext.name === 'subjectAltName') { + sanExtensionFound = true + ext.altNames = ext.altNames || [] // Ensure altNames is initialized + ext.altNames.push({ + type: 6, // URI + value: autoIdSan, + }) + break + } + } } - extensions.push({ - name: 'subjectAltName', - altNames: [ + + // If no existing SAN extension, create one + if (!sanExtensionFound) { + cert.setExtensions([ + ...cert.extensions, { - type: 6, // URI - value: `autoid:auto:${autoId}`, + name: 'subjectAltName', + altNames: [{ type: 6, value: autoIdSan }], }, - ], - }) - cert.setExtensions(extensions) + ]) + } - cert.sign(forge.pki.privateKeyFromPem(keyToPem(this.privateKey)), forge.md.sha256.create()) - return forge.pki.certificateToPem(cert) + // Copy all extensions from the CSR to the certificate + if (extensions) { + cert.setExtensions([...cert.extensions, ...extensions]) + } + + // Sign the certificate with private key + cert.sign(forge.pki.privateKeyFromPem(keyToPem(privateKey)), forge.md.sha256.create()) + + return cert + } + + /** + * Issues a self-signed certificate for the identity. + * + * @param subjectName Subject name for the certificate(common name). + * @param validityPeriodDays Number of days the certificate is valid. Defaults to 365. + * @returns Created X.509 certificate. + */ + selfIssueCertificate( + subjectName: string, + validityPeriodDays: number = 365, + ): forge.pki.Certificate { + if (!this.privateKey) { + throw new Error('Private key is not set.') + } + const csr = this.signCSR(this.createCSR(subjectName)) + const certificate = this.issueCertificate(csr, validityPeriodDays) + + this.certificate = certificate + return this.certificate } saveCertificate(filePath: string): void { diff --git a/packages/auto-id/src/utils.ts b/packages/auto-id/src/utils.ts index 62093fe2..b9630f1a 100644 --- a/packages/auto-id/src/utils.ts +++ b/packages/auto-id/src/utils.ts @@ -1,4 +1,5 @@ import { ObjectIdentifier } from 'asn1js' +import { randomBytes } from 'crypto' /** * Encodes a given string representation of an OID into its DER format. @@ -21,3 +22,19 @@ export function derEncodeSignatureAlgorithmOID(oid: string): Uint8Array { return new Uint8Array([...sequenceHeader, ...new Uint8Array(berArrayBuffer), ...nullParameter]) } + +export function randomSerialNumber(): bigint { + // Generate 20 random bytes + const bytes = randomBytes(20) + // Convert bytes to a BigInt + let serial = BigInt('0x' + bytes.toString('hex')) + // Shift right by 1 to ensure the number is positive + serial = serial >> BigInt(1) + return serial +} + +export function addDaysToCurrentDate(days: number): Date { + const currentDate = new Date() // This gives you the current date and time + currentDate.setUTCDate(currentDate.getUTCDate() + days) // Adds the specified number of days + return currentDate +} diff --git a/packages/auto-id/tests/certificateManager.test.ts b/packages/auto-id/tests/certificateManager.test.ts index cc9ce4e1..b890c29d 100644 --- a/packages/auto-id/tests/certificateManager.test.ts +++ b/packages/auto-id/tests/certificateManager.test.ts @@ -10,22 +10,24 @@ import { } from '../src/keyManagement' describe('CertificateManager', () => { - it('creates and signs a CSR with an Ed25519 key', () => { - // Generate an Ed25519 key pair - const [privateKey, _] = generateEd25519KeyPair() - // const keypair = forge.pki.ed25519.generateKeyPair() - - // Define the subject name for the CSR - const subjectName = 'Test' + it('create and sign CSR', () => { + // Generate a key pair + const [privateKey, _] = generateRsaKeyPair() + // TODO: Enable when Ed25519 key pair is supported by CertificateManager + // const [privateKey, _] = generateEd25519KeyPair() // Fails ❌ with error: "Cannot read public key. Unknown OID." // Instantiate CertificateManager with the generated private key - const manager = new CertificateManager(null, pemToPrivateKey(privateKey)) + const certificateManager = new CertificateManager(null, pemToPrivateKey(privateKey)) - // Create and sign CSR - const csr = manager.create_and_sign_csr(subjectName) + // Create and sign a CSR + const subjectName = 'Test' + const csr = certificateManager.createAndSignCSR(subjectName) - // Assert that the CSR is not null - expect(csr).toBeDefined() + expect(csr).not.toBeNull() + + // NOTE: static type-checking is already done in TypeScript at compile time unlike Python. So, ignored this assertion. + // Assert that the CSR is of type x509.CertificateSigningRequest + // assert isinstance(csr, x509.CertificateSigningRequest) // Assert that the CSR subject name matches the provided subject name const commonNameField = csr.subject.attributes.find((attr) => attr.name === 'commonName') @@ -48,37 +50,61 @@ describe('CertificateManager', () => { } }) - it('create and sign CSR with RSA key', () => { - // Generate a RSA key pair - const [privateKey, _] = generateRsaKeyPair() + it('issue certificate', () => { + const [subjectPrivateKey, subjectPublicKey] = generateRsaKeyPair() + const [issuerPrivateKey, issuerPublicKey] = generateRsaKeyPair() - // Instantiate CertificateManager with the generated private key - const certificateManager = new CertificateManager(null, pemToPrivateKey(privateKey)) + // TODO: Enable when Ed25519 key pair is supported by CertificateManager + // const [subjectPrivateKey, subjectPublicKey] = generateRsaKeyPair() + // const [issuerPrivateKey, issuerPublicKey] = generateRsaKeyPair() - // Create and sign a CSR + const issuer = new CertificateManager(null, pemToPrivateKey(issuerPrivateKey)) + const _issuerCertificate = issuer.selfIssueCertificate('issuer') + + // Define the subject name for the certificate const subjectName = 'Test' - const csr = certificateManager.create_and_sign_csr(subjectName) - expect(csr).toBeDefined() + const csrCreator = new CertificateManager(null, pemToPrivateKey(subjectPrivateKey)) + // Call the createCSR function to generate a CSR + const csr = csrCreator.createAndSignCSR(subjectName) - // Assert that the CSR subject name matches the provided subject name + // Issue a certificate using the CSR + const certificate = issuer.issueCertificate(csr) + + // Assert that the certificate is not null + expect(certificate).not.toBeNull() + + // NOTE: static type-checking is already done in TypeScript at compile time unlike Python. So, ignored this assertion. + // Assert that the certificate is of type x509.Certificate + // assert isinstance(certificate, x509.Certificate) + + // Assert that the certificate subject name matches the provided subject name const commonNameField = csr.subject.attributes.find((attr) => attr.name === 'commonName') expect(commonNameField?.value).toEqual(subjectName) - // Get the derived public key (in forge) from original private key. - // private key (PEM) -> private key(KeyObject) -> public key(PEM) - const derivedPublicKeyObj = pemToPublicKey( - CertificateManager.pemPublicFromPrivateKey(pemToPrivateKey(privateKey)), - ) - - // Assert that the CSR public key matches the public key from the key pair - if (csr.publicKey) { - // Convert forge.PublicKey format to crypto.KeyObject - const csrPublicKeyObj = createPublicKey(forge.pki.publicKeyToPem(csr.publicKey)) + // Assert that the certificate public key matches the private key's public key + if (certificate.publicKey) { + const certificatePublicKeyObj = createPublicKey( + forge.pki.publicKeyToPem(certificate.publicKey), + ) + const subjectPublicKeyObj = createPublicKey(subjectPublicKey) - expect(doPublicKeysMatch(csrPublicKeyObj, derivedPublicKeyObj)).toBe(true) + expect(doPublicKeysMatch(certificatePublicKeyObj, subjectPublicKeyObj)).toBe(true) } else { - throw new Error('CSR does not have a public key.') + throw new Error('Certificate does not have a public key.') } + + const certBytes = certificate.tbsCertificate + const signature = certificate.signature + // FIXME: Verify the certificate signature + // issuer_public_key.verify(signature, cert_bytes) + + // Convert the issuer's public key from PEM to a forge public key object + const issuerPublicKeyObj = forge.pki.publicKeyFromPem(issuerPublicKey) + + const tbsDer = forge.asn1.toDer(certBytes).getBytes() + const isValidSignature = issuerPublicKeyObj.verify(tbsDer, signature) + + expect(isValidSignature).toBe(true) }) }) diff --git a/packages/auto-utils/__test__/crypto.test.ts b/packages/auto-utils/__test__/crypto.test.ts index 636aa132..31c83385 100644 --- a/packages/auto-utils/__test__/crypto.test.ts +++ b/packages/auto-utils/__test__/crypto.test.ts @@ -1,4 +1,4 @@ -import { blake2b_256, stringToUint8Array } from '../src/crypto' +import { blake2b_256, concatenateUint8Arrays, stringToUint8Array } from '../src/crypto' describe('Verify crypto functions', () => { test('Check blake2b_256 return the hash of the data', async () => { @@ -8,9 +8,31 @@ describe('Verify crypto functions', () => { expect(hash).toEqual('0xb5da441cfe72ae042ef4d2b17742907f675de4da57462d4c3609c2e2ed755970') }) - test('Check stringToUint8Array return the byte array of the string', async () => { - const message = 'Hello, world!' - const byteArray = stringToUint8Array(message) - expect(byteArray).toBeInstanceOf(Uint8Array) + test('should encode strings to Uint8Arrays and concatenate them correctly', () => { + // Define test strings + const string1 = 'Hello' + const string2 = 'World' + + // Encode strings to Uint8Arrays + const encodedString1 = stringToUint8Array(string1) + const encodedString2 = stringToUint8Array(string2) + + // Manually create expected encoded arrays if known (for illustration) + const expectedEncoded1 = new Uint8Array([72, 101, 108, 108, 111]) // ASCII values for "Hello" + const expectedEncoded2 = new Uint8Array([87, 111, 114, 108, 100]) // ASCII values for "World" + + // Test individual encoding + expect(encodedString1).toEqual(expectedEncoded1) + expect(encodedString2).toEqual(expectedEncoded2) + + // Concatenate encoded arrays + const concatenatedArrays = concatenateUint8Arrays(encodedString1, encodedString2) + + // Manually create the expected concatenated result + const expectedConcatenation = new Uint8Array([72, 101, 108, 108, 111, 87, 111, 114, 108, 100]) // Combined ASCII + + // Test concatenation result + expect(concatenatedArrays).toEqual(expectedConcatenation) + expect(concatenatedArrays.length).toBe(encodedString1.length + encodedString2.length) }) }) diff --git a/packages/auto-utils/src/crypto.ts b/packages/auto-utils/src/crypto.ts index 1dce5c74..ae513c9f 100644 --- a/packages/auto-utils/src/crypto.ts +++ b/packages/auto-utils/src/crypto.ts @@ -29,3 +29,13 @@ export function stringToUint8Array(text: string): Uint8Array { const encoder = new TextEncoder() // Create a new TextEncoder instance return encoder.encode(text) // Encode the string to a Uint8Array using UTF-8 encoding } + +/** + * Concatenates two Uint8Array instances into a single Uint8Array. + */ +export function concatenateUint8Arrays(array1: Uint8Array, array2: Uint8Array): Uint8Array { + const combined = new Uint8Array(array1.length + array2.length) + combined.set(array1) + combined.set(array2, array1.length) + return combined +} From 90c10fe7dde89ce30838b6bc89619893e7ce46e0 Mon Sep 17 00:00:00 2001 From: Abhijit Roy Date: Mon, 17 Jun 2024 18:56:29 +0530 Subject: [PATCH 3/7] Rewrite CertificateManager class using @peculiar/x509 library TODO: tests pending --- packages/auto-id/package.json | 2 + packages/auto-id/src/certificateManager.ts | 502 ++++++++++++------ packages/auto-id/src/keyManagement.ts | 23 +- .../auto-id/tests/certificateManager.test.ts | 240 ++++++--- yarn.lock | 192 ++++++- 5 files changed, 703 insertions(+), 256 deletions(-) diff --git a/packages/auto-id/package.json b/packages/auto-id/package.json index cbe56e52..39f78a58 100644 --- a/packages/auto-id/package.json +++ b/packages/auto-id/package.json @@ -12,6 +12,8 @@ "@autonomys/auto-utils": "workspace:*", "@peculiar/asn1-schema": "^2.3.8", "@peculiar/asn1-x509": "^2.3.8", + "@peculiar/webcrypto": "^1.5.0", + "@peculiar/x509": "^1.11.0", "asn1js": "^3.0.5", "node-forge": "^1.3.1" }, diff --git a/packages/auto-id/src/certificateManager.ts b/packages/auto-id/src/certificateManager.ts index 24d404af..8b8dea4b 100644 --- a/packages/auto-id/src/certificateManager.ts +++ b/packages/auto-id/src/certificateManager.ts @@ -1,39 +1,45 @@ -//! For key generation, management, `keyManagement.ts` file is used i.e. "crypto" library. -//! And for certificate related, used "node-forge" library. +//! For key generation, management, `keyManagement.ts` file is used using "crypto" library. +//! And for certificate related, used "@peculiar/x509" library. import { blake2b_256, concatenateUint8Arrays, stringToUint8Array } from '@autonomys/auto-utils' -import { KeyObject, createPublicKey, createSign } from 'crypto' +import { AsnConvert } from '@peculiar/asn1-schema' +import { AttributeTypeAndValue } from '@peculiar/asn1-x509' +import { Crypto } from '@peculiar/webcrypto' +import * as x509 from '@peculiar/x509' +import { KeyObject, createPublicKey } from 'crypto' import fs from 'fs' -import forge from 'node-forge' -import { doPublicKeysMatch, keyToPem, pemToPublicKey } from './keyManagement' -import { addDaysToCurrentDate, randomSerialNumber } from './utils' - -interface CustomCertificateExtension { - altNames: { - type: number - value: string - }[] -} +import { doPublicKeysMatch } from './keyManagement' +import { randomSerialNumber } from './utils' + +const crypto = new Crypto() +x509.cryptoProvider.set(crypto) interface SigningParams { privateKey: KeyObject algorithm: 'sha256' | null // Only 'sha256' or null for Ed25519 } -class CertificateManager { - private certificate: forge.pki.Certificate | null +const OID_COMMON_NAME = '2.5.4.3' // OID for Common Name, not available in the library. +const OID_SUBJECT_ALT_NAME = '2.5.29.17' // OID for Subject Alternative Name, not available in the library. + +export class CertificateManager { + private certificate: x509.X509Certificate | null private privateKey: KeyObject | null + private publicKey: KeyObject | null constructor( - certificate: forge.pki.Certificate | null = null, + certificate: x509.X509Certificate | null = null, privateKey: KeyObject | null = null, + publicKey: KeyObject | null = null, ) { this.certificate = certificate this.privateKey = privateKey + this.publicKey = publicKey } protected prepareSigningParams(): SigningParams { const privateKey = this.privateKey + if (!privateKey) { throw new Error('Private key is not set.') } @@ -41,6 +47,7 @@ class CertificateManager { if (privateKey.asymmetricKeyType === 'ed25519') { return { privateKey: privateKey, algorithm: null } } + if (privateKey.asymmetricKeyType === 'rsa') { return { privateKey: privateKey, algorithm: 'sha256' } } @@ -48,50 +55,104 @@ class CertificateManager { throw new Error('Unsupported key type for signing.') } - static toCommonName(subjectName: string): forge.pki.CertificateField[] { - return [{ name: 'commonName', value: subjectName }] + protected static toCommonName(subjectName: string): x509.Name { + const commonNameAttr = new AttributeTypeAndValue({ + type: OID_COMMON_NAME, + value: subjectName, + }) + return new x509.Name([[commonNameAttr]]) } - static prettyPrintCertificate(cert: forge.pki.Certificate): void { + // protected static toCommonName(subjectName: string): JsonName { + // return [{ '2.5.4.3': [subjectName] }] // OID for commonName + // } + + private static async keyObjectToCryptoKey(keyObject: KeyObject): Promise { + const keyType = keyObject.type + const keyPem = keyObject + .export({ type: keyType === 'private' ? 'pkcs8' : 'spki', format: 'pem' }) + .toString() + const keyData = Buffer.from(keyPem.split('\n').slice(1, -1).join(''), 'base64') + + if (keyObject.asymmetricKeyType === 'ed25519') { + if (keyType === 'private') { + return crypto.subtle.importKey('pkcs8', keyData, { name: 'Ed25519' }, true, ['sign']) + } else { + return crypto.subtle.importKey('spki', keyData, { name: 'Ed25519' }, true, ['verify']) + } + } else if (keyObject.asymmetricKeyType === 'rsa') { + if (keyType === 'private') { + return crypto.subtle.importKey( + 'pkcs8', + keyData, + { + name: 'RSASSA-PKCS1-v1_5', + hash: { name: 'SHA-256' }, + }, + true, + ['sign'], + ) + } else { + return crypto.subtle.importKey( + 'spki', + keyData, + { + name: 'RSASSA-PKCS1-v1_5', + hash: { name: 'SHA-256' }, + }, + true, + ['verify'], + ) + } + } else { + throw new Error('Unsupported key type') + } + } + + static prettyPrintCertificate(cert: x509.X509Certificate): void { console.log('Certificate:') console.log('============') - console.log( - `Subject: ${cert.subject.attributes.map((attr) => `${attr.name}=${attr.value}`).join(', ')}`, - ) - console.log( - `Issuer: ${cert.issuer.attributes.map((attr) => `${attr.name}=${attr.value}`).join(', ')}`, - ) + console.log(`Subject: ${cert.subject}`) + console.log(`Issuer: ${cert.issuer}`) console.log(`Serial Number: ${cert.serialNumber}`) - console.log(`Not Valid Before: ${cert.validity.notBefore.toISOString()}`) - console.log(`Not Valid After: ${cert.validity.notAfter.toISOString()}`) + console.log(`Not Valid Before: ${cert.notBefore}`) + console.log(`Not Valid After: ${cert.notAfter}`) + console.log('\nExtensions:') cert.extensions.forEach((ext) => { - console.log(` - ${ext.name} (${ext.id}): ${JSON.stringify(ext.value)}`) + console.log(` - ${ext.type}: ${JSON.stringify(ext.value)}`) }) console.log('\nPublic Key:') console.log(cert.publicKey) } - static certificateToPem(cert: forge.pki.Certificate): string { - return forge.pki.certificateToPem(cert) + static certificateToPem(cert: x509.X509Certificate): string { + return cert.toString('pem') } - static pemToCertificate(pem: string): forge.pki.Certificate { - return forge.pki.certificateFromPem(pem) + static pemToCertificate(pem: string): x509.X509Certificate { + return new x509.X509Certificate(pem) } - static getSubjectCommonName(subjectFields: forge.pki.CertificateField[]): string | undefined { - const cnField = subjectFields.find((field) => field.name === 'commonName') - if (cnField && typeof cnField.value === 'string') { - return cnField.value - } - return undefined + static getSubjectCommonName(subject: x509.Name): string | undefined { + const commonNames = subject.getField(OID_COMMON_NAME) // OID for commonName + return commonNames.length > 0 ? commonNames[0] : undefined } - static getCertificateAutoId(certificate: forge.pki.Certificate): string | undefined { - const sanExtension = certificate.getExtension('subjectAltName') - if (sanExtension) { - const san = sanExtension as CustomCertificateExtension + // static async pemPublicFromPrivateKey(privateKey: CryptoKey): Promise { + // const publicKey = await crypto.subtle.exportKey('spki', privateKey) + // return x509.PemConverter.encode(publicKey, 'PUBLIC KEY') + // } + + // static async derPublicFromPrivateKey(privateKey: CryptoKey): Promise { + // const publicKey = await crypto.subtle.exportKey('spki', privateKey) + // return Buffer.from(publicKey).toString('hex') + // } + + static getCertificateAutoId(certificate: x509.X509Certificate): string | undefined { + const sanExtension = certificate.extensions.find((ext) => ext.type === OID_SUBJECT_ALT_NAME) // OID for subjectAltName + if (sanExtension && sanExtension.value) { + const san = sanExtension.value as any // Adjust this cast as needed based on actual SAN structure for (const name of san.altNames) { if (name.type === 6 && name.value.startsWith('autoid:auto:')) { return name.value.split(':').pop() @@ -101,98 +162,115 @@ class CertificateManager { return undefined } - static pemPublicFromPrivateKey(privateKey: KeyObject): string { - const publicKey = createPublicKey(privateKey) - return publicKey.export({ type: 'spki', format: 'pem' }).toString() - } - - static derPublicFromPrivateKey(privateKey: KeyObject): string { - const publicKey = createPublicKey(privateKey) - return publicKey.export({ type: 'spki', format: 'der' }).toString() - } - - createCSR(subjectName: string): forge.pki.CertificateSigningRequest { + async createCSR(subjectName: string): Promise { const privateKey = this.privateKey - if (!privateKey) { - throw new Error('Private key is not set.') + const publicKey = this.publicKey + + if (!privateKey || !publicKey) { + throw new Error('Private or public key is not set.') } - let csr = forge.pki.createCertificationRequest() - csr.setSubject(CertificateManager.toCommonName(subjectName)) + // Set the signing algorithm based on the key type + let signingAlgorithm: Algorithm | EcdsaParams if (privateKey.asymmetricKeyType === 'ed25519') { - // Manually handle Ed25519 due to possible forge limitations - const publicKeyDer = CertificateManager.derPublicFromPrivateKey(privateKey) - - // Directly assign the public key in DER format - csr.publicKey = forge.pki.publicKeyFromAsn1(forge.asn1.fromDer(publicKeyDer)) - // csr.publicKey = forge.pki.publicKeyFromPem( - // CertificateManager.pemPublicFromPrivateKey(privateKey), - // ) + signingAlgorithm = { name: 'Ed25519' } + } else if (privateKey.asymmetricKeyType === 'rsa') { + signingAlgorithm = { name: 'RSASSA-PKCS1-v1_5', hash: { name: 'SHA-256' } } } else { - csr.publicKey = forge.pki.publicKeyFromPem( - CertificateManager.pemPublicFromPrivateKey(privateKey), - ) + throw new Error('Unsupported key type for signing') } + + const csr = await x509.Pkcs10CertificateRequestGenerator.create({ + name: `CN=${subjectName}`, + keys: { + privateKey: await CertificateManager.keyObjectToCryptoKey(privateKey), + publicKey: await CertificateManager.keyObjectToCryptoKey(publicKey), + }, + signingAlgorithm: signingAlgorithm, + }) + return csr } - signCSR(csr: forge.pki.CertificateSigningRequest): forge.pki.CertificateSigningRequest { - const signingParams = this.prepareSigningParams() - if (this.privateKey?.asymmetricKeyType === 'ed25519') { - // Ensure cryptographic algorithm is set to sign - // if (!csr.siginfo.algorithmOid) { - // throw new Error('Signature algorithm OID must be set before signing the CSR.') - // } + async signCSR(csr: x509.Pkcs10CertificateRequest): Promise { + const privateKey = this.privateKey + if (!privateKey) { + throw new Error('Private key is not set.') + } - // console.log('Inspecting CSR before converting to ASN.1:', csr) - const asn1 = forge.pki.certificationRequestToAsn1(csr) - const derBuffer = forge.asn1.toDer(asn1).getBytes() + const privateKeyConverted = await CertificateManager.keyObjectToCryptoKey(privateKey) - const sign = createSign('SHA256') - sign.update(derBuffer, 'binary') // Make sure the update is called with 'binary' encoding - sign.end() + const _signingParams = this.prepareSigningParams() - const signature = sign.sign(signingParams.privateKey, 'binary') - csr.signature = Buffer.from(signature, 'binary') - } else { - if (signingParams.algorithm) { - const digestMethod = forge.md[signingParams.algorithm].create() - csr.sign(forge.pki.privateKeyFromPem(keyToPem(signingParams.privateKey)), digestMethod) - } else { - throw new Error('Unsupported key type or missing algorithm.') - } - } + // FIXME: check during testing + // const derBuffer = csr.rawData + const asn1 = csr.rawData + const derBuffer = AsnConvert.serialize(asn1) + const signature = await crypto.subtle.sign( + privateKeyConverted.algorithm.name, + privateKeyConverted, + derBuffer, + ) + csr.signature = new Uint8Array(signature) return csr } - createAndSignCSR(subject_name: string): forge.pki.CertificateSigningRequest { - const csr = this.createCSR(subject_name) + async createAndSignCSR(subjectName: string): Promise { + const csr = await this.createCSR(subjectName) return this.signCSR(csr) } - issueCertificate( - csr: forge.pki.CertificateSigningRequest, + // TODO: later on move to "keyManagement.ts" + private static publicKeyToKeyObject(publicKey: x509.PublicKey): KeyObject { + // Export the key data to ArrayBuffer + const keyData = publicKey.rawData // DER format + + // Create a KeyObject from the key data + const keyObject = createPublicKey({ + key: Buffer.from(keyData), + format: 'der', + type: 'spki', + }) + + return keyObject + } + + // TODO: later on move to utils. + private static stringToArrayBuffer(str: string): ArrayBuffer { + const buffer = new ArrayBuffer(str.length) + const view = new Uint8Array(buffer) + for (let i = 0; i < str.length; i++) { + view[i] = str.charCodeAt(i) + } + return buffer + } + + async issueCertificate( + csr: x509.Pkcs10CertificateRequest, validityPeriodDays: number = 365, - ): forge.pki.Certificate { + ): Promise { const privateKey = this.privateKey - const certificate = this.certificate - if (!privateKey) { - throw new Error('Private key is not set.') + const publicKey = this.publicKey + if (!privateKey || !publicKey) { + throw new Error('Private or public key is not set.') } - let issuerName: any + let issuerName: x509.Name let autoId: string + const certificate = this.certificate if (!certificate) { - issuerName = csr.subject.attributes - autoId = blake2b_256( - stringToUint8Array(CertificateManager.getSubjectCommonName(csr.subject.attributes) || ''), - ) + issuerName = csr.subjectName + const subjectCommonName = CertificateManager.getSubjectCommonName(issuerName) + if (!subjectCommonName) { + throw new Error('Subject common name not found in CSR.') + } + autoId = blake2b_256(stringToUint8Array(subjectCommonName)) } else { if ( !doPublicKeysMatch( - createPublicKey(forge.pki.publicKeyToPem(certificate.publicKey)), - pemToPublicKey(CertificateManager.pemPublicFromPrivateKey(privateKey)), + CertificateManager.publicKeyToKeyObject(certificate.publicKey), + publicKey, ) ) { throw new Error( @@ -200,10 +278,10 @@ class CertificateManager { ) } - issuerName = certificate.subject.attributes + issuerName = certificate.subjectName const certificateAutoId = CertificateManager.getCertificateAutoId(certificate) || '' const certificateSubjectCommonName = - CertificateManager.getSubjectCommonName(certificate.subject.attributes) || '' + CertificateManager.getSubjectCommonName(certificate.subjectName) || '' if (certificateAutoId === '' || certificateSubjectCommonName === '') { throw new Error( 'Issuer certificate does not have either an auto ID or a subject common name or both.', @@ -218,86 +296,184 @@ class CertificateManager { } // Prepare the certificate builder with information from the CSR - const cert = forge.pki.createCertificate() - if (!csr.publicKey) - throw new Error('CSR does not have a public key. Please provide a CSR with a public key.') - cert.setSubject(csr.subject.attributes) - cert.setIssuer(issuerName) - cert.publicKey = csr.publicKey - cert.serialNumber = randomSerialNumber().toString() - cert.validity.notBefore = new Date() - cert.validity.notAfter = addDaysToCurrentDate(validityPeriodDays) + const notBefore = new Date() + const notAfter = new Date() + notAfter.setDate(notBefore.getDate() + validityPeriodDays) + + const privateKeyConverted = await CertificateManager.keyObjectToCryptoKey(privateKey) + + let certificateBuilder = await x509.X509CertificateGenerator.create({ + serialNumber: randomSerialNumber().toString(), + issuer: csr.subject, + subject: csr.subject, + notBefore, + notAfter, + signingAlgorithm: privateKeyConverted.algorithm, + publicKey: await CertificateManager.keyObjectToCryptoKey(publicKey), + signingKey: privateKeyConverted, + }) const autoIdSan = `autoid:auto:${Buffer.from(autoId).toString('hex')}` - let sanExtensionFound = false + // FIXME: check testing // Check for existing SAN extension - const extensions = csr.getAttribute({ name: 'extensionRequest' })?.extensions - if (extensions) { - for (const ext of extensions) { - if (ext.name === 'subjectAltName') { - sanExtensionFound = true - ext.altNames = ext.altNames || [] // Ensure altNames is initialized - ext.altNames.push({ - type: 6, // URI - value: autoIdSan, - }) - break - } - } - } + // const sanExtensions = csr.extensions + const sanExtensions = csr.extensions.filter((ext) => ext.type === OID_SUBJECT_ALT_NAME) // OID for subjectAltName + if (sanExtensions) { + // const existingSan = sanExtensions[0].value + const existingSan = sanExtensions[0] as x509.SubjectAlternativeNameExtension + const generalNames = existingSan.names.toJSON() + + // Add autoIdSan to generalNames + generalNames.push({ + type: 'uniformResourceIdentifier' as x509.GeneralNameType, + value: autoIdSan, + }) + + // const newSanExtension = existingSan + CertificateManager.stringToArrayBuffer(autoIdSan) + const newSanExtension = new x509.SubjectAlternativeNameExtension( + generalNames, + existingSan.critical, + ) + certificateBuilder.extensions.push(newSanExtension) + } else { + // certificateBuilder.extensions.push( + // new x509.SubjectAlternativeNameExtension([autoIdSan]), + // false, + // ) - // If no existing SAN extension, create one - if (!sanExtensionFound) { - cert.setExtensions([ - ...cert.extensions, - { - name: 'subjectAltName', - altNames: [{ type: 6, value: autoIdSan }], - }, - ]) + certificateBuilder.extensions.push( + new x509.SubjectAlternativeNameExtension([ + { type: 'uniformResourceIdentifier' as x509.GeneralNameType, value: autoIdSan }, + ]), + ) } // Copy all extensions from the CSR to the certificate - if (extensions) { - cert.setExtensions([...cert.extensions, ...extensions]) + for (const ext of csr.extensions) { + // certificateBuilder.extensions.push(new x509.Extension(ext.value, ext.critical)) + certificateBuilder.extensions.push(ext) } - // Sign the certificate with private key - cert.sign(forge.pki.privateKeyFromPem(keyToPem(privateKey)), forge.md.sha256.create()) + const certificateSigned = await x509.X509CertificateGenerator.create({ + serialNumber: certificateBuilder.serialNumber, + issuer: certificateBuilder.issuer, + subject: certificateBuilder.subject, + notBefore: certificateBuilder.notBefore, + notAfter: certificateBuilder.notAfter, + extensions: certificateBuilder.extensions, + publicKey: certificateBuilder.publicKey, + signingAlgorithm: certificateBuilder.signatureAlgorithm, + signingKey: privateKeyConverted, + }) - return cert + return certificateSigned } - /** - * Issues a self-signed certificate for the identity. - * - * @param subjectName Subject name for the certificate(common name). - * @param validityPeriodDays Number of days the certificate is valid. Defaults to 365. - * @returns Created X.509 certificate. - */ - selfIssueCertificate( + async selfIssueCertificate( subjectName: string, validityPeriodDays: number = 365, - ): forge.pki.Certificate { + ): Promise { + if (!this.privateKey || !this.publicKey) { + throw new Error('Private or public key is not set.') + } + + const csr = await this.createAndSignCSR(subjectName) + const certificate = await this.issueCertificate(csr, validityPeriodDays) + + this.certificate = certificate + return certificate + } + + saveCertificate(filePath: string): void { + if (!this.certificate) { + throw new Error('No certificate available to save.') + } + + const certificatePem = CertificateManager.certificateToPem(this.certificate) + fs.writeFileSync(filePath, certificatePem, 'utf8') + } +} + +export class Ed25519CertificateManager { + private certificate: x509.X509Certificate | null + private privateKey: CryptoKey | null + + constructor( + certificate: x509.X509Certificate | null = null, + privateKey: CryptoKey | null = null, + ) { + this.certificate = certificate + this.privateKey = privateKey + } + + async createAndSignCSR(subjectName: string): Promise { + if (!this.privateKey) { + throw new Error('Private key is not set.') + } + + // Export the public key (ArrayBuffer) from the private key + const publicKeyArrayBuffer = await crypto.subtle.exportKey('spki', this.privateKey) + // Create a public key (CryptoKey) from the exported public key + const publicKey = await crypto.subtle.importKey( + 'spki', + publicKeyArrayBuffer, + { + name: 'Ed25519', + }, + true, + ['verify'], + ) + + const csr = await x509.Pkcs10CertificateRequestGenerator.create({ + name: `CN=${subjectName}`, + keys: { privateKey: this.privateKey, publicKey }, + signingAlgorithm: { name: 'Ed25519' }, + }) + + return csr + } + + async issueCertificate( + csr: x509.Pkcs10CertificateRequest, + validityPeriodDays: number = 365, + ): Promise { if (!this.privateKey) { throw new Error('Private key is not set.') } - const csr = this.signCSR(this.createCSR(subjectName)) - const certificate = this.issueCertificate(csr, validityPeriodDays) + + const notBefore = new Date() + const notAfter = new Date() + notAfter.setDate(notBefore.getDate() + validityPeriodDays) + + const certificate = await x509.X509CertificateGenerator.create({ + serialNumber: randomSerialNumber().toString(), + issuer: csr.subject, + subject: csr.subject, + notBefore, + notAfter, + signingAlgorithm: { name: 'Ed25519' }, + signingKey: this.privateKey, + publicKey: csr.publicKey, + }) this.certificate = certificate - return this.certificate + return certificate + } + + async selfIssueCertificate( + subjectName: string, + validityPeriodDays: number = 365, + ): Promise { + const csr = await this.createAndSignCSR(subjectName) + return this.issueCertificate(csr, validityPeriodDays) } saveCertificate(filePath: string): void { - const certificate = this.certificate - if (!certificate) { + if (!this.certificate) { throw new Error('No certificate available to save.') } - const certificatePem = CertificateManager.certificateToPem(certificate) + const certificatePem = this.certificate.toString('pem') fs.writeFileSync(filePath, certificatePem, 'utf8') } } - -export default CertificateManager diff --git a/packages/auto-id/src/keyManagement.ts b/packages/auto-id/src/keyManagement.ts index 3530b1d4..7718d40e 100644 --- a/packages/auto-id/src/keyManagement.ts +++ b/packages/auto-id/src/keyManagement.ts @@ -19,11 +19,26 @@ export function generateRsaKeyPair(keySize: number = 2048): [string, string] { return [privateKey, publicKey] } -// export function generateRsaKeyPair(keySize: number = 2048): [string, string] { -// const { publicKey, privateKey } = forge.pki.rsa.generateKeyPair({ bits: keySize, e: 0x10001 }) +import { Crypto } from '@peculiar/webcrypto' +const crypto = new Crypto() -// return [privateKey.toString(), publicKey.toString()] -// } +export async function generateEd25519KeyPair2(): Promise<[CryptoKey, CryptoKey]> { + const keyPair = await crypto.subtle.generateKey( + { + name: 'Ed25519', + namedCurve: 'Ed25519', + }, + true, + ['sign', 'verify'], + ) + + return [keyPair.privateKey, keyPair.publicKey] +} + +export async function pemToEd25519PrivateKey(pem: string): Promise { + const binaryDer = Buffer.from(pem, 'base64') + return crypto.subtle.importKey('pkcs8', binaryDer, { name: 'Ed25519' }, false, ['sign']) +} /** * Generates an Ed25519 key pair. diff --git a/packages/auto-id/tests/certificateManager.test.ts b/packages/auto-id/tests/certificateManager.test.ts index b890c29d..fa52b161 100644 --- a/packages/auto-id/tests/certificateManager.test.ts +++ b/packages/auto-id/tests/certificateManager.test.ts @@ -1,6 +1,7 @@ +import * as x509 from '@peculiar/x509' import { createPublicKey } from 'crypto' import * as forge from 'node-forge' -import CertificateManager from '../src/certificateManager' +import { CertificateManager } from '../src/certificateManager' import { doPublicKeysMatch, generateEd25519KeyPair, @@ -9,102 +10,173 @@ import { pemToPublicKey, } from '../src/keyManagement' -describe('CertificateManager', () => { - it('create and sign CSR', () => { - // Generate a key pair - const [privateKey, _] = generateRsaKeyPair() - // TODO: Enable when Ed25519 key pair is supported by CertificateManager - // const [privateKey, _] = generateEd25519KeyPair() // Fails ❌ with error: "Cannot read public key. Unknown OID." +// describe('CertificateManager', () => { +// it('create and sign CSR', () => { +// // Generate a key pair +// const [privateKey, _] = generateRsaKeyPair() +// // TODO: Enable when Ed25519 key pair is supported by CertificateManager +// // const [privateKey, _] = generateEd25519KeyPair() // Fails ❌ with error: "Cannot read public key. Unknown OID." + +// // Instantiate CertificateManager with the generated private key +// const certificateManager = new CertificateManager(null, pemToPrivateKey(privateKey)) - // Instantiate CertificateManager with the generated private key - const certificateManager = new CertificateManager(null, pemToPrivateKey(privateKey)) +// // Create and sign a CSR +// const subjectName = 'Test' +// const csr = certificateManager.createAndSignCSR(subjectName) + +// expect(csr).not.toBeNull() + +// // NOTE: static type-checking is already done in TypeScript at compile time unlike Python. So, ignored this assertion. +// // Assert that the CSR is of type x509.CertificateSigningRequest +// // assert isinstance(csr, x509.CertificateSigningRequest) + +// // Assert that the CSR subject name matches the provided subject name +// const commonNameField = csr.subject.attributes.find((attr) => attr.name === 'commonName') +// expect(commonNameField?.value).toEqual(subjectName) + +// // Get the derived public key (in forge) from original private key. +// // private key (PEM) -> private key(KeyObject) -> public key(PEM) +// const derivedPublicKeyObj = pemToPublicKey( +// CertificateManager.pemPublicFromPrivateKey(pemToPrivateKey(privateKey)), +// ) - // Create and sign a CSR - const subjectName = 'Test' - const csr = certificateManager.createAndSignCSR(subjectName) - - expect(csr).not.toBeNull() - - // NOTE: static type-checking is already done in TypeScript at compile time unlike Python. So, ignored this assertion. - // Assert that the CSR is of type x509.CertificateSigningRequest - // assert isinstance(csr, x509.CertificateSigningRequest) - - // Assert that the CSR subject name matches the provided subject name - const commonNameField = csr.subject.attributes.find((attr) => attr.name === 'commonName') - expect(commonNameField?.value).toEqual(subjectName) - - // Get the derived public key (in forge) from original private key. - // private key (PEM) -> private key(KeyObject) -> public key(PEM) - const derivedPublicKeyObj = pemToPublicKey( - CertificateManager.pemPublicFromPrivateKey(pemToPrivateKey(privateKey)), - ) - - // Assert that the CSR public key matches the public key from the key pair - if (csr.publicKey) { - // Convert forge.PublicKey format to crypto.KeyObject - const csrPublicKeyObj = createPublicKey(forge.pki.publicKeyToPem(csr.publicKey)) - - expect(doPublicKeysMatch(csrPublicKeyObj, derivedPublicKeyObj)).toBe(true) - } else { - throw new Error('CSR does not have a public key.') - } - }) - - it('issue certificate', () => { - const [subjectPrivateKey, subjectPublicKey] = generateRsaKeyPair() - const [issuerPrivateKey, issuerPublicKey] = generateRsaKeyPair() +// // Assert that the CSR public key matches the public key from the key pair +// if (csr.publicKey) { +// // Convert forge.PublicKey format to crypto.KeyObject +// const csrPublicKeyObj = createPublicKey(forge.pki.publicKeyToPem(csr.publicKey)) + +// expect(doPublicKeysMatch(csrPublicKeyObj, derivedPublicKeyObj)).toBe(true) +// } else { +// throw new Error('CSR does not have a public key.') +// } +// }) + +// it('issue certificate', () => { +// const [subjectPrivateKey, subjectPublicKey] = generateRsaKeyPair() +// const [issuerPrivateKey, issuerPublicKey] = generateRsaKeyPair() + +// // TODO: Enable when Ed25519 key pair is supported by CertificateManager +// // const [subjectPrivateKey, subjectPublicKey] = generateRsaKeyPair() +// // const [issuerPrivateKey, issuerPublicKey] = generateRsaKeyPair() + +// const issuer = new CertificateManager(null, pemToPrivateKey(issuerPrivateKey)) +// const _issuerCertificate = issuer.selfIssueCertificate('issuer') + +// // Define the subject name for the certificate +// const subjectName = 'Test' + +// const csrCreator = new CertificateManager(null, pemToPrivateKey(subjectPrivateKey)) +// // Call the createCSR function to generate a CSR +// const csr = csrCreator.createAndSignCSR(subjectName) + +// // Issue a certificate using the CSR +// const certificate = issuer.issueCertificate(csr) + +// // Assert that the certificate is not null +// expect(certificate).not.toBeNull() + +// // NOTE: static type-checking is already done in TypeScript at compile time unlike Python. So, ignored this assertion. +// // Assert that the certificate is of type x509.Certificate +// // assert isinstance(certificate, x509.Certificate) + +// // Assert that the certificate subject name matches the provided subject name +// const commonNameField = csr.subject.attributes.find((attr) => attr.name === 'commonName') +// expect(commonNameField?.value).toEqual(subjectName) + +// // Assert that the certificate public key matches the private key's public key +// if (certificate.publicKey) { +// const certificatePublicKeyObj = createPublicKey( +// forge.pki.publicKeyToPem(certificate.publicKey), +// ) +// const subjectPublicKeyObj = createPublicKey(subjectPublicKey) + +// expect(doPublicKeysMatch(certificatePublicKeyObj, subjectPublicKeyObj)).toBe(true) +// } else { +// throw new Error('Certificate does not have a public key.') +// } + +// const certBytes = certificate.tbsCertificate +// const signature = certificate.signature +// // FIXME: Verify the certificate signature +// // issuer_public_key.verify(signature, cert_bytes) + +// // Convert the issuer's public key from PEM to a forge public key object +// // const issuerPublicKeyObj = forge.pki.publicKeyFromPem(issuerPublicKey) + +// // const tbsDer = forge.asn1.toDer(certBytes).getBytes() +// // const isValidSignature = issuerPublicKeyObj.verify(tbsDer, signature) + +// // expect(isValidSignature).toBe(true) +// }) +// }) + +// describe('Using @peculiar/x509 + forge', () => { +// // Using `@peculiar/x509` library instead of `node-forge` +// test('create and sign CSR with Ed25519', async () => { +// const keypair = await generateEd25519KeyPair2() +// const certificateManager = new CertificateManager(null, pemToPrivateKey(keypair.privateKey)) - // TODO: Enable when Ed25519 key pair is supported by CertificateManager - // const [subjectPrivateKey, subjectPublicKey] = generateRsaKeyPair() - // const [issuerPrivateKey, issuerPublicKey] = generateRsaKeyPair() +// const subjectName = 'Test' +// const csr = await certificateManager.createCSR(subjectName) +// await certificateManager.signCSR(csr) - const issuer = new CertificateManager(null, pemToPrivateKey(issuerPrivateKey)) - const _issuerCertificate = issuer.selfIssueCertificate('issuer') +// expect(csr).not.toBeNull() +// const commonNameField = csr.subject.attributes.find((attr) => attr.name === 'commonName') +// expect(commonNameField?.value).toEqual(subjectName) +// }) + +// test('issue certificate and verify signature', async () => { +// const [subjectPrivateKey, subjectPublicKey] = generateRsaKeyPair() +// const [issuerPrivateKey, issuerPublicKey] = generateRsaKeyPair() + +// const issuer = new CertificateManager(null, pemToPrivateKey(issuerPrivateKey)) +// await issuer.selfIssueCertificate('issuer') + +// const subjectName = 'Test' +// const csrCreator = new CertificateManager(null, pemToPrivateKey(subjectPrivateKey)) +// const csr = await csrCreator.createCSR(subjectName) +// await csrCreator.signCSR(csr) + +// const certificate = await issuer.issueCertificate(csr) - // Define the subject name for the certificate - const subjectName = 'Test' +// expect(certificate).not.toBeNull() +// const commonNameField = certificate.subject.attributes.find( +// (attr) => attr.name === 'commonName', +// ) +// expect(commonNameField?.value).toEqual(subjectName) - const csrCreator = new CertificateManager(null, pemToPrivateKey(subjectPrivateKey)) - // Call the createCSR function to generate a CSR - const csr = csrCreator.createAndSignCSR(subjectName) +// const issuerCert = new x509.X509Certificate(issuerPublicKey) +// const certBytes = certificate +// const isValidSignature = await issuerCert.verify(certBytes) - // Issue a certificate using the CSR - const certificate = issuer.issueCertificate(csr) +// expect(isValidSignature).toBe(true) +// }) +// }) - // Assert that the certificate is not null - expect(certificate).not.toBeNull() +import { Ed25519CertificateManager } from '../src/certificateManager' +import { generateEd25519KeyPair2 } from '../src/keyManagement' - // NOTE: static type-checking is already done in TypeScript at compile time unlike Python. So, ignored this assertion. - // Assert that the certificate is of type x509.Certificate - // assert isinstance(certificate, x509.Certificate) +describe('Ed25519CertificateManager using @peculiar/x509 only', () => { + it('create and sign CSR with Ed25519', async () => { + const [privateKey, _] = await generateEd25519KeyPair2() + const certificateManager = new Ed25519CertificateManager(null, privateKey) - // Assert that the certificate subject name matches the provided subject name - const commonNameField = csr.subject.attributes.find((attr) => attr.name === 'commonName') - expect(commonNameField?.value).toEqual(subjectName) - - // Assert that the certificate public key matches the private key's public key - if (certificate.publicKey) { - const certificatePublicKeyObj = createPublicKey( - forge.pki.publicKeyToPem(certificate.publicKey), - ) - const subjectPublicKeyObj = createPublicKey(subjectPublicKey) - - expect(doPublicKeysMatch(certificatePublicKeyObj, subjectPublicKeyObj)).toBe(true) - } else { - throw new Error('Certificate does not have a public key.') - } + const subjectName = 'Test' + const csr = await certificateManager.createAndSignCSR(subjectName) - const certBytes = certificate.tbsCertificate - const signature = certificate.signature - // FIXME: Verify the certificate signature - // issuer_public_key.verify(signature, cert_bytes) + expect(csr).not.toBeNull() + const commonNameField = csr.attributes.find((attr) => attr.type === '2.5.4.3') + expect(commonNameField?.values).toContain(subjectName) + }) - // Convert the issuer's public key from PEM to a forge public key object - const issuerPublicKeyObj = forge.pki.publicKeyFromPem(issuerPublicKey) + it('self-issue certificate with Ed25519', async () => { + const [privateKey, publicKey] = await generateEd25519KeyPair2() + const certificateManager = new Ed25519CertificateManager(null, privateKey) - const tbsDer = forge.asn1.toDer(certBytes).getBytes() - const isValidSignature = issuerPublicKeyObj.verify(tbsDer, signature) + const subjectName = 'Test' + const certificate = await certificateManager.selfIssueCertificate(subjectName) - expect(isValidSignature).toBe(true) + expect(certificate).not.toBeNull() + expect(certificate.subject).toContain(`CN=${subjectName}`) }) }) diff --git a/yarn.lock b/yarn.lock index e7562e43..35438c16 100644 --- a/yarn.lock +++ b/yarn.lock @@ -34,6 +34,8 @@ __metadata: "@autonomys/auto-utils": "workspace:*" "@peculiar/asn1-schema": "npm:^2.3.8" "@peculiar/asn1-x509": "npm:^2.3.8" + "@peculiar/webcrypto": "npm:^1.5.0" + "@peculiar/x509": "npm:^1.11.0" "@types/jest": "npm:^29.5.12" "@types/node": "npm:^20.12.12" "@types/node-forge": "npm:^1" @@ -919,6 +921,97 @@ __metadata: languageName: node linkType: hard +"@peculiar/asn1-cms@npm:^2.3.8": + version: 2.3.8 + resolution: "@peculiar/asn1-cms@npm:2.3.8" + dependencies: + "@peculiar/asn1-schema": "npm:^2.3.8" + "@peculiar/asn1-x509": "npm:^2.3.8" + "@peculiar/asn1-x509-attr": "npm:^2.3.8" + asn1js: "npm:^3.0.5" + tslib: "npm:^2.6.2" + checksum: 10c0/e330c8f23df45f8bacd05a314ea62b54a375e1c3bf05875bf69a4c971ad8e4f1f389e0c0c261a770d3841dad71c8a15141ec488d8ddce74ebcfb8e63f479f02f + languageName: node + linkType: hard + +"@peculiar/asn1-csr@npm:^2.3.8": + version: 2.3.8 + resolution: "@peculiar/asn1-csr@npm:2.3.8" + dependencies: + "@peculiar/asn1-schema": "npm:^2.3.8" + "@peculiar/asn1-x509": "npm:^2.3.8" + asn1js: "npm:^3.0.5" + tslib: "npm:^2.6.2" + checksum: 10c0/1ba2414ba0679277f691e857de84b8543310af5a8e6385e2f27d672fc9179d143f9ed98cd752b775fd3d9aa4d54c2d120b157136aea0d00b1d7343cdabe74310 + languageName: node + linkType: hard + +"@peculiar/asn1-ecc@npm:^2.3.8": + version: 2.3.8 + resolution: "@peculiar/asn1-ecc@npm:2.3.8" + dependencies: + "@peculiar/asn1-schema": "npm:^2.3.8" + "@peculiar/asn1-x509": "npm:^2.3.8" + asn1js: "npm:^3.0.5" + tslib: "npm:^2.6.2" + checksum: 10c0/88a1e34e3afc9f60ec89ae6d716e7e9fe92ae841e2758fc07b997c7325e0770cf605170031113c055086b93ed3eb8d12c8f64425bad576aafe74474410bb0ae9 + languageName: node + linkType: hard + +"@peculiar/asn1-pfx@npm:^2.3.8": + version: 2.3.8 + resolution: "@peculiar/asn1-pfx@npm:2.3.8" + dependencies: + "@peculiar/asn1-cms": "npm:^2.3.8" + "@peculiar/asn1-pkcs8": "npm:^2.3.8" + "@peculiar/asn1-rsa": "npm:^2.3.8" + "@peculiar/asn1-schema": "npm:^2.3.8" + asn1js: "npm:^3.0.5" + tslib: "npm:^2.6.2" + checksum: 10c0/8591281d081e3f22aa5575908188ff0560ea218f9e30af7ed9c4d24bf34d682f3dc8091b1f78bb44cceb4b0ad3198e83bc938a0e0b56be4e274bcd7c13eaa334 + languageName: node + linkType: hard + +"@peculiar/asn1-pkcs8@npm:^2.3.8": + version: 2.3.8 + resolution: "@peculiar/asn1-pkcs8@npm:2.3.8" + dependencies: + "@peculiar/asn1-schema": "npm:^2.3.8" + "@peculiar/asn1-x509": "npm:^2.3.8" + asn1js: "npm:^3.0.5" + tslib: "npm:^2.6.2" + checksum: 10c0/33db038165f3a96a6bcbb20a2774b816a772652c4b6d67a814ecf68d3806743b75c4b2f758fecc2f5d134d18bee2864e0f42afee962e3052e4742e50e238a237 + languageName: node + linkType: hard + +"@peculiar/asn1-pkcs9@npm:^2.3.8": + version: 2.3.8 + resolution: "@peculiar/asn1-pkcs9@npm:2.3.8" + dependencies: + "@peculiar/asn1-cms": "npm:^2.3.8" + "@peculiar/asn1-pfx": "npm:^2.3.8" + "@peculiar/asn1-pkcs8": "npm:^2.3.8" + "@peculiar/asn1-schema": "npm:^2.3.8" + "@peculiar/asn1-x509": "npm:^2.3.8" + "@peculiar/asn1-x509-attr": "npm:^2.3.8" + asn1js: "npm:^3.0.5" + tslib: "npm:^2.6.2" + checksum: 10c0/6a500939366169540223570b4ff48eca4b718e40616c3cc9086bffd66ac2f6a37041a9577f029040797ad42520c3dd5e29d7e2fd35d440d5171a4f5c48a86a8d + languageName: node + linkType: hard + +"@peculiar/asn1-rsa@npm:^2.3.8": + version: 2.3.8 + resolution: "@peculiar/asn1-rsa@npm:2.3.8" + dependencies: + "@peculiar/asn1-schema": "npm:^2.3.8" + "@peculiar/asn1-x509": "npm:^2.3.8" + asn1js: "npm:^3.0.5" + tslib: "npm:^2.6.2" + checksum: 10c0/39b1a7597a210acd07c4d78bb2d55b9aedf05e3ab28fccde86ca6978322dcba35a483f2d84c155e18953ad15b728184e379010d11b01245ac2a2cbf1457a0dba + languageName: node + linkType: hard + "@peculiar/asn1-schema@npm:^2.3.8": version: 2.3.8 resolution: "@peculiar/asn1-schema@npm:2.3.8" @@ -930,6 +1023,18 @@ __metadata: languageName: node linkType: hard +"@peculiar/asn1-x509-attr@npm:^2.3.8": + version: 2.3.8 + resolution: "@peculiar/asn1-x509-attr@npm:2.3.8" + dependencies: + "@peculiar/asn1-schema": "npm:^2.3.8" + "@peculiar/asn1-x509": "npm:^2.3.8" + asn1js: "npm:^3.0.5" + tslib: "npm:^2.6.2" + checksum: 10c0/0a7c92024c326267ed16a3eb3d3956061546838acf6d8ca54bdc5c105b4139ee4cf2bffe01c3685012d1cfcf5a48c06feb7a664bd88cbeda2d61dc990dcf90d0 + languageName: node + linkType: hard + "@peculiar/asn1-x509@npm:^2.3.8": version: 2.3.8 resolution: "@peculiar/asn1-x509@npm:2.3.8" @@ -943,6 +1048,47 @@ __metadata: languageName: node linkType: hard +"@peculiar/json-schema@npm:^1.1.12": + version: 1.1.12 + resolution: "@peculiar/json-schema@npm:1.1.12" + dependencies: + tslib: "npm:^2.0.0" + checksum: 10c0/202132c66dcc6b6aca5d0af971c015be2e163da2f7f992910783c5d39c8a7db59b6ec4f4ce419459a1f954b7e1d17b6b253f0e60072c1b3d254079f4eaebc311 + languageName: node + linkType: hard + +"@peculiar/webcrypto@npm:^1.5.0": + version: 1.5.0 + resolution: "@peculiar/webcrypto@npm:1.5.0" + dependencies: + "@peculiar/asn1-schema": "npm:^2.3.8" + "@peculiar/json-schema": "npm:^1.1.12" + pvtsutils: "npm:^1.3.5" + tslib: "npm:^2.6.2" + webcrypto-core: "npm:^1.8.0" + checksum: 10c0/4f6f24b2c52c2155b9c569b6eb1d57954cb5f7bd2764a50cdaed7aea17a6dcf304b75b87b57ba318756ffec8179a07d9a76534aaf77855912b838543e5ff8983 + languageName: node + linkType: hard + +"@peculiar/x509@npm:^1.11.0": + version: 1.11.0 + resolution: "@peculiar/x509@npm:1.11.0" + dependencies: + "@peculiar/asn1-cms": "npm:^2.3.8" + "@peculiar/asn1-csr": "npm:^2.3.8" + "@peculiar/asn1-ecc": "npm:^2.3.8" + "@peculiar/asn1-pkcs9": "npm:^2.3.8" + "@peculiar/asn1-rsa": "npm:^2.3.8" + "@peculiar/asn1-schema": "npm:^2.3.8" + "@peculiar/asn1-x509": "npm:^2.3.8" + pvtsutils: "npm:^1.3.5" + reflect-metadata: "npm:^0.2.2" + tslib: "npm:^2.6.2" + tsyringe: "npm:^4.8.0" + checksum: 10c0/528b37ccc291b7d78d6ba912946fbb045cf65da325d69c8910ef5ef8e32f0eca26f52db2c8e0d36092774b3ee2ca7cf4cf677f0b0c56c9e029737388c08ca527 + languageName: node + linkType: hard + "@pkgjs/parseargs@npm:^0.11.0": version: 0.11.0 resolution: "@pkgjs/parseargs@npm:0.11.0" @@ -1836,7 +1982,7 @@ __metadata: languageName: node linkType: hard -"asn1js@npm:^3.0.5": +"asn1js@npm:^3.0.1, asn1js@npm:^3.0.5": version: 3.0.5 resolution: "asn1js@npm:3.0.5" dependencies: @@ -4366,6 +4512,13 @@ __metadata: languageName: node linkType: hard +"reflect-metadata@npm:^0.2.2": + version: 0.2.2 + resolution: "reflect-metadata@npm:0.2.2" + checksum: 10c0/1cd93a15ea291e420204955544637c264c216e7aac527470e393d54b4bb075f10a17e60d8168ec96600c7e0b9fcc0cb0bb6e91c3fbf5b0d8c9056f04e6ac1ec2 + languageName: node + linkType: hard + "require-directory@npm:^2.1.1": version: 2.1.1 resolution: "require-directory@npm:2.1.1" @@ -4868,6 +5021,20 @@ __metadata: languageName: node linkType: hard +"tslib@npm:^1.9.3": + version: 1.14.1 + resolution: "tslib@npm:1.14.1" + checksum: 10c0/69ae09c49eea644bc5ebe1bca4fa4cc2c82b7b3e02f43b84bd891504edf66dbc6b2ec0eef31a957042de2269139e4acff911e6d186a258fb14069cd7f6febce2 + languageName: node + linkType: hard + +"tslib@npm:^2.0.0, tslib@npm:^2.4.0, tslib@npm:^2.6.1": + version: 2.6.3 + resolution: "tslib@npm:2.6.3" + checksum: 10c0/2598aef53d9dbe711af75522464b2104724d6467b26a60f2bdac8297d2b5f1f6b86a71f61717384aa8fd897240467aaa7bcc36a0700a0faf751293d1331db39a + languageName: node + linkType: hard + "tslib@npm:^2.1.0, tslib@npm:^2.6.2": version: 2.6.2 resolution: "tslib@npm:2.6.2" @@ -4875,10 +5042,12 @@ __metadata: languageName: node linkType: hard -"tslib@npm:^2.4.0, tslib@npm:^2.6.1": - version: 2.6.3 - resolution: "tslib@npm:2.6.3" - checksum: 10c0/2598aef53d9dbe711af75522464b2104724d6467b26a60f2bdac8297d2b5f1f6b86a71f61717384aa8fd897240467aaa7bcc36a0700a0faf751293d1331db39a +"tsyringe@npm:^4.8.0": + version: 4.8.0 + resolution: "tsyringe@npm:4.8.0" + dependencies: + tslib: "npm:^1.9.3" + checksum: 10c0/e13810e8ff39c4093acd0649bc5db3c164825827631e1522cd9d5ca8694a018447fa1c24f059ea54e93b1020767b1131b9dc9ce598dabfc9aa41c11544bbfe19 languageName: node linkType: hard @@ -5014,6 +5183,19 @@ __metadata: languageName: node linkType: hard +"webcrypto-core@npm:^1.8.0": + version: 1.8.0 + resolution: "webcrypto-core@npm:1.8.0" + dependencies: + "@peculiar/asn1-schema": "npm:^2.3.8" + "@peculiar/json-schema": "npm:^1.1.12" + asn1js: "npm:^3.0.1" + pvtsutils: "npm:^1.3.5" + tslib: "npm:^2.6.2" + checksum: 10c0/d4158af402500eb26d0de6e088baa0fbef41c43a3e3b5f53b8326c8c517e55037b3d8a17672cf48bdccfd13526599857544ea8485e2172bb14c9ee4561d706a5 + languageName: node + linkType: hard + "which@npm:^2.0.1": version: 2.0.2 resolution: "which@npm:2.0.2" From 5d5e6d761dec08467603b9837fde2e00db5e0a16 Mon Sep 17 00:00:00 2001 From: Abhijit Roy Date: Tue, 18 Jun 2024 14:58:53 +0530 Subject: [PATCH 4/7] Finished CertificateManager with all its tests --- packages/auto-id/package.json | 6 +- packages/auto-id/src/certificateManager.ts | 309 ++++++++---------- packages/auto-id/src/keyManagement.ts | 51 ++- .../auto-id/tests/certificateManager.test.ts | 282 +++++++--------- yarn.lock | 18 - 5 files changed, 299 insertions(+), 367 deletions(-) diff --git a/packages/auto-id/package.json b/packages/auto-id/package.json index 39f78a58..792d403c 100644 --- a/packages/auto-id/package.json +++ b/packages/auto-id/package.json @@ -14,8 +14,7 @@ "@peculiar/asn1-x509": "^2.3.8", "@peculiar/webcrypto": "^1.5.0", "@peculiar/x509": "^1.11.0", - "asn1js": "^3.0.5", - "node-forge": "^1.3.1" + "asn1js": "^3.0.5" }, "files": [ "dist", @@ -24,10 +23,9 @@ "devDependencies": { "@types/jest": "^29.5.12", "@types/node": "^20.12.12", - "@types/node-forge": "^1", "jest": "^29.7.0", "ts-jest": "^29.1.4", "ts-node": "^10.9.2", "typescript": "^5.4.5" } -} +} \ No newline at end of file diff --git a/packages/auto-id/src/certificateManager.ts b/packages/auto-id/src/certificateManager.ts index 8b8dea4b..5a47ea1c 100644 --- a/packages/auto-id/src/certificateManager.ts +++ b/packages/auto-id/src/certificateManager.ts @@ -3,34 +3,34 @@ import { blake2b_256, concatenateUint8Arrays, stringToUint8Array } from '@autonomys/auto-utils' import { AsnConvert } from '@peculiar/asn1-schema' -import { AttributeTypeAndValue } from '@peculiar/asn1-x509' +import { AttributeTypeAndValue, GeneralName, GeneralNames } from '@peculiar/asn1-x509' import { Crypto } from '@peculiar/webcrypto' import * as x509 from '@peculiar/x509' import { KeyObject, createPublicKey } from 'crypto' import fs from 'fs' -import { doPublicKeysMatch } from './keyManagement' +import { doPublicKeysMatch, pemToPublicKey } from './keyManagement' import { randomSerialNumber } from './utils' const crypto = new Crypto() x509.cryptoProvider.set(crypto) interface SigningParams { - privateKey: KeyObject + privateKey: CryptoKey algorithm: 'sha256' | null // Only 'sha256' or null for Ed25519 } -const OID_COMMON_NAME = '2.5.4.3' // OID for Common Name, not available in the library. +export const OID_COMMON_NAME = '2.5.4.3' // OID for Common Name, not available in the library. const OID_SUBJECT_ALT_NAME = '2.5.29.17' // OID for Subject Alternative Name, not available in the library. export class CertificateManager { private certificate: x509.X509Certificate | null - private privateKey: KeyObject | null - private publicKey: KeyObject | null + private privateKey: CryptoKey | null + private publicKey: CryptoKey | null constructor( certificate: x509.X509Certificate | null = null, - privateKey: KeyObject | null = null, - publicKey: KeyObject | null = null, + privateKey: CryptoKey | null = null, + publicKey: CryptoKey | null = null, ) { this.certificate = certificate this.privateKey = privateKey @@ -44,11 +44,11 @@ export class CertificateManager { throw new Error('Private key is not set.') } - if (privateKey.asymmetricKeyType === 'ed25519') { + if (privateKey.algorithm.name === 'Ed25519') { return { privateKey: privateKey, algorithm: null } } - if (privateKey.asymmetricKeyType === 'rsa') { + if (privateKey.algorithm.name === 'rsa') { return { privateKey: privateKey, algorithm: 'sha256' } } @@ -67,48 +67,6 @@ export class CertificateManager { // return [{ '2.5.4.3': [subjectName] }] // OID for commonName // } - private static async keyObjectToCryptoKey(keyObject: KeyObject): Promise { - const keyType = keyObject.type - const keyPem = keyObject - .export({ type: keyType === 'private' ? 'pkcs8' : 'spki', format: 'pem' }) - .toString() - const keyData = Buffer.from(keyPem.split('\n').slice(1, -1).join(''), 'base64') - - if (keyObject.asymmetricKeyType === 'ed25519') { - if (keyType === 'private') { - return crypto.subtle.importKey('pkcs8', keyData, { name: 'Ed25519' }, true, ['sign']) - } else { - return crypto.subtle.importKey('spki', keyData, { name: 'Ed25519' }, true, ['verify']) - } - } else if (keyObject.asymmetricKeyType === 'rsa') { - if (keyType === 'private') { - return crypto.subtle.importKey( - 'pkcs8', - keyData, - { - name: 'RSASSA-PKCS1-v1_5', - hash: { name: 'SHA-256' }, - }, - true, - ['sign'], - ) - } else { - return crypto.subtle.importKey( - 'spki', - keyData, - { - name: 'RSASSA-PKCS1-v1_5', - hash: { name: 'SHA-256' }, - }, - true, - ['verify'], - ) - } - } else { - throw new Error('Unsupported key type') - } - } - static prettyPrintCertificate(cert: x509.X509Certificate): void { console.log('Certificate:') console.log('============') @@ -139,23 +97,19 @@ export class CertificateManager { return commonNames.length > 0 ? commonNames[0] : undefined } - // static async pemPublicFromPrivateKey(privateKey: CryptoKey): Promise { - // const publicKey = await crypto.subtle.exportKey('spki', privateKey) - // return x509.PemConverter.encode(publicKey, 'PUBLIC KEY') - // } - - // static async derPublicFromPrivateKey(privateKey: CryptoKey): Promise { - // const publicKey = await crypto.subtle.exportKey('spki', privateKey) - // return Buffer.from(publicKey).toString('hex') - // } - static getCertificateAutoId(certificate: x509.X509Certificate): string | undefined { - const sanExtension = certificate.extensions.find((ext) => ext.type === OID_SUBJECT_ALT_NAME) // OID for subjectAltName + const sanExtension = certificate.extensions.find((ext) => ext.type === OID_SUBJECT_ALT_NAME) + if (sanExtension && sanExtension.value) { - const san = sanExtension.value as any // Adjust this cast as needed based on actual SAN structure - for (const name of san.altNames) { - if (name.type === 6 && name.value.startsWith('autoid:auto:')) { - return name.value.split(':').pop() + // Deserialize the ArrayBuffer to GeneralNames ASN.1 object + const san = AsnConvert.parse(sanExtension.value, GeneralNames) + + for (const name of san) { + if ( + name.uniformResourceIdentifier && + name.uniformResourceIdentifier.startsWith('autoid:auto:') + ) { + return name.uniformResourceIdentifier.split(':').pop() } } } @@ -172,9 +126,9 @@ export class CertificateManager { // Set the signing algorithm based on the key type let signingAlgorithm: Algorithm | EcdsaParams - if (privateKey.asymmetricKeyType === 'ed25519') { + if (privateKey.algorithm.name === 'Ed25519') { signingAlgorithm = { name: 'Ed25519' } - } else if (privateKey.asymmetricKeyType === 'rsa') { + } else if (privateKey.algorithm.name === 'rsa') { signingAlgorithm = { name: 'RSASSA-PKCS1-v1_5', hash: { name: 'SHA-256' } } } else { throw new Error('Unsupported key type for signing') @@ -183,8 +137,8 @@ export class CertificateManager { const csr = await x509.Pkcs10CertificateRequestGenerator.create({ name: `CN=${subjectName}`, keys: { - privateKey: await CertificateManager.keyObjectToCryptoKey(privateKey), - publicKey: await CertificateManager.keyObjectToCryptoKey(publicKey), + privateKey: privateKey, + publicKey: publicKey, }, signingAlgorithm: signingAlgorithm, }) @@ -198,19 +152,10 @@ export class CertificateManager { throw new Error('Private key is not set.') } - const privateKeyConverted = await CertificateManager.keyObjectToCryptoKey(privateKey) - const _signingParams = this.prepareSigningParams() - // FIXME: check during testing - // const derBuffer = csr.rawData - const asn1 = csr.rawData - const derBuffer = AsnConvert.serialize(asn1) - const signature = await crypto.subtle.sign( - privateKeyConverted.algorithm.name, - privateKeyConverted, - derBuffer, - ) + const derBuffer = csr.rawData + const signature = await crypto.subtle.sign(privateKey.algorithm.name, privateKey, derBuffer) csr.signature = new Uint8Array(signature) return csr @@ -268,9 +213,10 @@ export class CertificateManager { autoId = blake2b_256(stringToUint8Array(subjectCommonName)) } else { if ( + // FIXME: modify !doPublicKeysMatch( CertificateManager.publicKeyToKeyObject(certificate.publicKey), - publicKey, + pemToPublicKey(await cryptoKeyToPem(publicKey)), ) ) { throw new Error( @@ -300,33 +246,34 @@ export class CertificateManager { const notAfter = new Date() notAfter.setDate(notBefore.getDate() + validityPeriodDays) - const privateKeyConverted = await CertificateManager.keyObjectToCryptoKey(privateKey) - let certificateBuilder = await x509.X509CertificateGenerator.create({ serialNumber: randomSerialNumber().toString(), issuer: csr.subject, subject: csr.subject, notBefore, notAfter, - signingAlgorithm: privateKeyConverted.algorithm, - publicKey: await CertificateManager.keyObjectToCryptoKey(publicKey), - signingKey: privateKeyConverted, + signingAlgorithm: privateKey.algorithm, + publicKey: publicKey, + signingKey: privateKey, }) const autoIdSan = `autoid:auto:${Buffer.from(autoId).toString('hex')}` // FIXME: check testing // Check for existing SAN extension - // const sanExtensions = csr.extensions + // const sanExtensions = csr.extensions // --> [] + const sanExtensions = csr.extensions.filter((ext) => ext.type === OID_SUBJECT_ALT_NAME) // OID for subjectAltName - if (sanExtensions) { + + if (sanExtensions.length) { // const existingSan = sanExtensions[0].value const existingSan = sanExtensions[0] as x509.SubjectAlternativeNameExtension + const generalNames = existingSan.names.toJSON() // Add autoIdSan to generalNames generalNames.push({ - type: 'uniformResourceIdentifier' as x509.GeneralNameType, + type: 'url' as x509.GeneralNameType, value: autoIdSan, }) @@ -344,7 +291,7 @@ export class CertificateManager { certificateBuilder.extensions.push( new x509.SubjectAlternativeNameExtension([ - { type: 'uniformResourceIdentifier' as x509.GeneralNameType, value: autoIdSan }, + { type: 'url' /* as x509.GeneralNameType */, value: autoIdSan }, ]), ) } @@ -364,7 +311,7 @@ export class CertificateManager { extensions: certificateBuilder.extensions, publicKey: certificateBuilder.publicKey, signingAlgorithm: certificateBuilder.signatureAlgorithm, - signingKey: privateKeyConverted, + signingKey: privateKey, }) return certificateSigned @@ -395,85 +342,103 @@ export class CertificateManager { } } -export class Ed25519CertificateManager { - private certificate: x509.X509Certificate | null - private privateKey: CryptoKey | null - - constructor( - certificate: x509.X509Certificate | null = null, - privateKey: CryptoKey | null = null, - ) { - this.certificate = certificate - this.privateKey = privateKey - } - - async createAndSignCSR(subjectName: string): Promise { - if (!this.privateKey) { - throw new Error('Private key is not set.') - } - - // Export the public key (ArrayBuffer) from the private key - const publicKeyArrayBuffer = await crypto.subtle.exportKey('spki', this.privateKey) - // Create a public key (CryptoKey) from the exported public key - const publicKey = await crypto.subtle.importKey( - 'spki', - publicKeyArrayBuffer, - { - name: 'Ed25519', - }, - true, - ['verify'], - ) - - const csr = await x509.Pkcs10CertificateRequestGenerator.create({ - name: `CN=${subjectName}`, - keys: { privateKey: this.privateKey, publicKey }, - signingAlgorithm: { name: 'Ed25519' }, - }) - - return csr - } - - async issueCertificate( - csr: x509.Pkcs10CertificateRequest, - validityPeriodDays: number = 365, - ): Promise { - if (!this.privateKey) { - throw new Error('Private key is not set.') - } - - const notBefore = new Date() - const notAfter = new Date() - notAfter.setDate(notBefore.getDate() + validityPeriodDays) - - const certificate = await x509.X509CertificateGenerator.create({ - serialNumber: randomSerialNumber().toString(), - issuer: csr.subject, - subject: csr.subject, - notBefore, - notAfter, - signingAlgorithm: { name: 'Ed25519' }, - signingKey: this.privateKey, - publicKey: csr.publicKey, - }) - - this.certificate = certificate - return certificate - } - - async selfIssueCertificate( - subjectName: string, - validityPeriodDays: number = 365, - ): Promise { - const csr = await this.createAndSignCSR(subjectName) - return this.issueCertificate(csr, validityPeriodDays) +// export class Ed25519CertificateManager { +// private certificate: x509.X509Certificate | null +// private privateKey: CryptoKey | null + +// constructor( +// certificate: x509.X509Certificate | null = null, +// privateKey: CryptoKey | null = null, +// ) { +// this.certificate = certificate +// this.privateKey = privateKey +// } + +// async createAndSignCSR(subjectName: string): Promise { +// if (!this.privateKey) { +// throw new Error('Private key is not set.') +// } + +// // Export the public key (ArrayBuffer) from the private key +// const publicKeyArrayBuffer = await crypto.subtle.exportKey('spki', this.privateKey) +// // Create a public key (CryptoKey) from the exported public key +// const publicKey = await crypto.subtle.importKey( +// 'spki', +// publicKeyArrayBuffer, +// { +// name: 'Ed25519', +// }, +// true, +// ['verify'], +// ) + +// const csr = await x509.Pkcs10CertificateRequestGenerator.create({ +// name: `CN=${subjectName}`, +// keys: { privateKey: this.privateKey, publicKey }, +// signingAlgorithm: { name: 'Ed25519' }, +// }) + +// return csr +// } + +// async issueCertificate( +// csr: x509.Pkcs10CertificateRequest, +// validityPeriodDays: number = 365, +// ): Promise { +// if (!this.privateKey) { +// throw new Error('Private key is not set.') +// } + +// const notBefore = new Date() +// const notAfter = new Date() +// notAfter.setDate(notBefore.getDate() + validityPeriodDays) + +// const certificate = await x509.X509CertificateGenerator.create({ +// serialNumber: randomSerialNumber().toString(), +// issuer: csr.subject, +// subject: csr.subject, +// notBefore, +// notAfter, +// signingAlgorithm: { name: 'Ed25519' }, +// signingKey: this.privateKey, +// publicKey: csr.publicKey, +// }) + +// this.certificate = certificate +// return certificate +// } + +// async selfIssueCertificate( +// subjectName: string, +// validityPeriodDays: number = 365, +// ): Promise { +// const csr = await this.createAndSignCSR(subjectName) +// return this.issueCertificate(csr, validityPeriodDays) +// } + +// saveCertificate(filePath: string): void { +// if (!this.certificate) { +// throw new Error('No certificate available to save.') +// } +// const certificatePem = this.certificate.toString('pem') +// fs.writeFileSync(filePath, certificatePem, 'utf8') +// } +// } + +function arrayBufferToBase64(buffer: ArrayBuffer): string { + let binary = '' + const bytes = new Uint8Array(buffer) + const len = bytes.byteLength + for (let i = 0; i < len; i++) { + binary += String.fromCharCode(bytes[i]) } + return btoa(binary) +} - saveCertificate(filePath: string): void { - if (!this.certificate) { - throw new Error('No certificate available to save.') - } - const certificatePem = this.certificate.toString('pem') - fs.writeFileSync(filePath, certificatePem, 'utf8') - } +async function cryptoKeyToPem(key: CryptoKey): Promise { + const exported = await crypto.subtle.exportKey(key.type === 'private' ? 'pkcs8' : 'spki', key) + const base64 = arrayBufferToBase64(exported) + const type = key.type === 'private' ? 'PRIVATE KEY' : 'PUBLIC KEY' + const pem = `-----BEGIN ${type}-----\n${base64.match(/.{1,64}/g)?.join('\n')}\n-----END ${type}-----` + return pem } diff --git a/packages/auto-id/src/keyManagement.ts b/packages/auto-id/src/keyManagement.ts index 7718d40e..a8f92a87 100644 --- a/packages/auto-id/src/keyManagement.ts +++ b/packages/auto-id/src/keyManagement.ts @@ -20,8 +20,10 @@ export function generateRsaKeyPair(keySize: number = 2048): [string, string] { } import { Crypto } from '@peculiar/webcrypto' +import * as x509 from '@peculiar/x509' const crypto = new Crypto() +// FIXME: keep one function. need to modify the tests. export async function generateEd25519KeyPair2(): Promise<[CryptoKey, CryptoKey]> { const keyPair = await crypto.subtle.generateKey( { @@ -35,11 +37,6 @@ export async function generateEd25519KeyPair2(): Promise<[CryptoKey, CryptoKey]> return [keyPair.privateKey, keyPair.publicKey] } -export async function pemToEd25519PrivateKey(pem: string): Promise { - const binaryDer = Buffer.from(pem, 'base64') - return crypto.subtle.importKey('pkcs8', binaryDer, { name: 'Ed25519' }, false, ['sign']) -} - /** * Generates an Ed25519 key pair. * @returns A tuple containing the Ed25519 private key and public key. @@ -53,12 +50,6 @@ export function generateEd25519KeyPair(): [string, string] { return [privateKey, publicKey] } -// export function generateEd25519KeyPair(): [string, string] { -// const { privateKey, publicKey } = forge.pki.ed25519.generateKeyPair() - -// return [privateKey.toString(), publicKey.toString()] -// } - /** * Converts a cryptographic key object into a PEM formatted string. * This function can handle both private and public key objects. @@ -314,3 +305,41 @@ export function doPublicKeysMatch(publicKey1: KeyObject, publicKey2: KeyObject): // Compare the serialized public key data return publicKey1Der.equals(publicKey2Der) } + +// TODO: finalize which function is to keep (from node's crypto or webcrypto) +// This can be done only after completing the AutoID package and testing +// it to see which one is useful. +// export async function doPublicKeysMatch( +// publicKey1: CryptoKey, +// publicKey2: CryptoKey, +// ): Promise { +// const publicKey1Raw = new Uint8Array(await crypto.subtle.exportKey('spki', publicKey1)) +// const publicKey2Raw = new Uint8Array(await crypto.subtle.exportKey('spki', publicKey2)) + +// const publicKey1Hex = Array.from(new Uint8Array(publicKey1Raw)) +// .map((byte) => byte.toString(16).padStart(2, '0')) +// .join('') +// const publicKey2Hex = Array.from(publicKey2Raw) +// .map((byte) => byte.toString(16).padStart(2, '0')) +// .join('') + +// return publicKey1Hex === publicKey2Hex +// } + +export async function validateCertificatePublicKey( + certPublicKey: x509.PublicKey, + derivedPublicKey: CryptoKey, +): Promise { + const derivedPublicKeyRaw = new Uint8Array( + await crypto.subtle.exportKey('spki', derivedPublicKey), + ) + + const certPublicKeyHex = Array.from(new Uint8Array(certPublicKey.rawData)) + .map((byte) => byte.toString(16).padStart(2, '0')) + .join('') + const derivedPublicKeyHex = Array.from(derivedPublicKeyRaw) + .map((byte) => byte.toString(16).padStart(2, '0')) + .join('') + + return certPublicKeyHex === derivedPublicKeyHex +} diff --git a/packages/auto-id/tests/certificateManager.test.ts b/packages/auto-id/tests/certificateManager.test.ts index fa52b161..442e4242 100644 --- a/packages/auto-id/tests/certificateManager.test.ts +++ b/packages/auto-id/tests/certificateManager.test.ts @@ -1,182 +1,140 @@ +import { AsnConvert } from '@peculiar/asn1-schema' +import { Certificate } from '@peculiar/asn1-x509' +import { Crypto } from '@peculiar/webcrypto' import * as x509 from '@peculiar/x509' -import { createPublicKey } from 'crypto' -import * as forge from 'node-forge' -import { CertificateManager } from '../src/certificateManager' -import { - doPublicKeysMatch, - generateEd25519KeyPair, - generateRsaKeyPair, - pemToPrivateKey, - pemToPublicKey, -} from '../src/keyManagement' - -// describe('CertificateManager', () => { -// it('create and sign CSR', () => { -// // Generate a key pair -// const [privateKey, _] = generateRsaKeyPair() -// // TODO: Enable when Ed25519 key pair is supported by CertificateManager -// // const [privateKey, _] = generateEd25519KeyPair() // Fails ❌ with error: "Cannot read public key. Unknown OID." - -// // Instantiate CertificateManager with the generated private key -// const certificateManager = new CertificateManager(null, pemToPrivateKey(privateKey)) - -// // Create and sign a CSR -// const subjectName = 'Test' -// const csr = certificateManager.createAndSignCSR(subjectName) - -// expect(csr).not.toBeNull() - -// // NOTE: static type-checking is already done in TypeScript at compile time unlike Python. So, ignored this assertion. -// // Assert that the CSR is of type x509.CertificateSigningRequest -// // assert isinstance(csr, x509.CertificateSigningRequest) - -// // Assert that the CSR subject name matches the provided subject name -// const commonNameField = csr.subject.attributes.find((attr) => attr.name === 'commonName') -// expect(commonNameField?.value).toEqual(subjectName) - -// // Get the derived public key (in forge) from original private key. -// // private key (PEM) -> private key(KeyObject) -> public key(PEM) -// const derivedPublicKeyObj = pemToPublicKey( -// CertificateManager.pemPublicFromPrivateKey(pemToPrivateKey(privateKey)), -// ) - -// // Assert that the CSR public key matches the public key from the key pair -// if (csr.publicKey) { -// // Convert forge.PublicKey format to crypto.KeyObject -// const csrPublicKeyObj = createPublicKey(forge.pki.publicKeyToPem(csr.publicKey)) - -// expect(doPublicKeysMatch(csrPublicKeyObj, derivedPublicKeyObj)).toBe(true) -// } else { -// throw new Error('CSR does not have a public key.') -// } -// }) - -// it('issue certificate', () => { -// const [subjectPrivateKey, subjectPublicKey] = generateRsaKeyPair() -// const [issuerPrivateKey, issuerPublicKey] = generateRsaKeyPair() - -// // TODO: Enable when Ed25519 key pair is supported by CertificateManager -// // const [subjectPrivateKey, subjectPublicKey] = generateRsaKeyPair() -// // const [issuerPrivateKey, issuerPublicKey] = generateRsaKeyPair() - -// const issuer = new CertificateManager(null, pemToPrivateKey(issuerPrivateKey)) -// const _issuerCertificate = issuer.selfIssueCertificate('issuer') - -// // Define the subject name for the certificate -// const subjectName = 'Test' - -// const csrCreator = new CertificateManager(null, pemToPrivateKey(subjectPrivateKey)) -// // Call the createCSR function to generate a CSR -// const csr = csrCreator.createAndSignCSR(subjectName) - -// // Issue a certificate using the CSR -// const certificate = issuer.issueCertificate(csr) - -// // Assert that the certificate is not null -// expect(certificate).not.toBeNull() - -// // NOTE: static type-checking is already done in TypeScript at compile time unlike Python. So, ignored this assertion. -// // Assert that the certificate is of type x509.Certificate -// // assert isinstance(certificate, x509.Certificate) - -// // Assert that the certificate subject name matches the provided subject name -// const commonNameField = csr.subject.attributes.find((attr) => attr.name === 'commonName') -// expect(commonNameField?.value).toEqual(subjectName) - -// // Assert that the certificate public key matches the private key's public key -// if (certificate.publicKey) { -// const certificatePublicKeyObj = createPublicKey( -// forge.pki.publicKeyToPem(certificate.publicKey), -// ) -// const subjectPublicKeyObj = createPublicKey(subjectPublicKey) - -// expect(doPublicKeysMatch(certificatePublicKeyObj, subjectPublicKeyObj)).toBe(true) -// } else { -// throw new Error('Certificate does not have a public key.') -// } - -// const certBytes = certificate.tbsCertificate -// const signature = certificate.signature -// // FIXME: Verify the certificate signature -// // issuer_public_key.verify(signature, cert_bytes) - -// // Convert the issuer's public key from PEM to a forge public key object -// // const issuerPublicKeyObj = forge.pki.publicKeyFromPem(issuerPublicKey) - -// // const tbsDer = forge.asn1.toDer(certBytes).getBytes() -// // const isValidSignature = issuerPublicKeyObj.verify(tbsDer, signature) - -// // expect(isValidSignature).toBe(true) -// }) -// }) - -// describe('Using @peculiar/x509 + forge', () => { -// // Using `@peculiar/x509` library instead of `node-forge` -// test('create and sign CSR with Ed25519', async () => { -// const keypair = await generateEd25519KeyPair2() -// const certificateManager = new CertificateManager(null, pemToPrivateKey(keypair.privateKey)) +import { CertificateManager, OID_COMMON_NAME } from '../src/certificateManager' +import { generateEd25519KeyPair2, validateCertificatePublicKey } from '../src/keyManagement' -// const subjectName = 'Test' -// const csr = await certificateManager.createCSR(subjectName) -// await certificateManager.signCSR(csr) +const crypto = new Crypto() -// expect(csr).not.toBeNull() -// const commonNameField = csr.subject.attributes.find((attr) => attr.name === 'commonName') -// expect(commonNameField?.value).toEqual(subjectName) -// }) - -// test('issue certificate and verify signature', async () => { -// const [subjectPrivateKey, subjectPublicKey] = generateRsaKeyPair() -// const [issuerPrivateKey, issuerPublicKey] = generateRsaKeyPair() - -// const issuer = new CertificateManager(null, pemToPrivateKey(issuerPrivateKey)) -// await issuer.selfIssueCertificate('issuer') - -// const subjectName = 'Test' -// const csrCreator = new CertificateManager(null, pemToPrivateKey(subjectPrivateKey)) -// const csr = await csrCreator.createCSR(subjectName) -// await csrCreator.signCSR(csr) - -// const certificate = await issuer.issueCertificate(csr) +function getTbsCertificate(cert: x509.X509Certificate): ArrayBuffer { + const asn1 = AsnConvert.parse(cert.rawData, Certificate) + return AsnConvert.serialize(asn1.tbsCertificate) +} -// expect(certificate).not.toBeNull() -// const commonNameField = certificate.subject.attributes.find( -// (attr) => attr.name === 'commonName', -// ) -// expect(commonNameField?.value).toEqual(subjectName) +describe('CertificateManager', () => { + it('create and sign CSR', async () => { + // Generate an Ed25519 key pair + const [privateKey, publicKey] = await generateEd25519KeyPair2() -// const issuerCert = new x509.X509Certificate(issuerPublicKey) -// const certBytes = certificate -// const isValidSignature = await issuerCert.verify(certBytes) + // Instantiate CertificateManager with the generated private key + const certificateManager = new CertificateManager(null, privateKey, publicKey) -// expect(isValidSignature).toBe(true) -// }) -// }) + // Create and sign a CSR + const subjectName = 'Test' + const csr = await certificateManager.createAndSignCSR(subjectName) + expect(csr).not.toBeNull() + + // Assert that the CSR subject name matches the provided subject name + const commonNameField = csr.subjectName.getField(OID_COMMON_NAME)[0] + expect(commonNameField).toEqual(subjectName) + + expect(await validateCertificatePublicKey(csr.publicKey, publicKey)).toBe(true) + }) -import { Ed25519CertificateManager } from '../src/certificateManager' -import { generateEd25519KeyPair2 } from '../src/keyManagement' + it('issue certificate', async () => { + // Generate an Ed25519 key pair + const [issuerPrivateKey, issuerPublicKey] = await generateEd25519KeyPair2() + const [subjectPrivateKey, subjectPublicKey] = await generateEd25519KeyPair2() -describe('Ed25519CertificateManager using @peculiar/x509 only', () => { - it('create and sign CSR with Ed25519', async () => { - const [privateKey, _] = await generateEd25519KeyPair2() - const certificateManager = new Ed25519CertificateManager(null, privateKey) + const issuer = new CertificateManager(null, issuerPrivateKey, issuerPublicKey) + const _issuerCertificate = await issuer.selfIssueCertificate('issuer') + // Define the subject name for the certificate const subjectName = 'Test' - const csr = await certificateManager.createAndSignCSR(subjectName) + const csrCreator = new CertificateManager(null, subjectPrivateKey, subjectPublicKey) - expect(csr).not.toBeNull() - const commonNameField = csr.attributes.find((attr) => attr.type === '2.5.4.3') - expect(commonNameField?.values).toContain(subjectName) + // Call the createCSR function to generate a CSR + const csr = await csrCreator.createAndSignCSR(subjectName) + + // Issue a certificate using the CSR + const certificate = await issuer.issueCertificate(csr) + + // Assert that the certificate is not null + expect(certificate).not.toBeNull() + + // Assert that the certificate subject name matches the provided subject name + const commonNameField = csr.subjectName.getField(OID_COMMON_NAME)[0] + expect(commonNameField).toEqual(subjectName) + + // Assert that the certificate public key matches the private key's public key + expect(await validateCertificatePublicKey(csr.publicKey, subjectPublicKey)).toBe(true) + + const tbsCertificateBytes = getTbsCertificate(certificate) + const signature = certificate.signature + + const isValidSignature = await crypto.subtle.verify( + { + name: 'Ed25519', + }, + issuerPublicKey, + signature, + tbsCertificateBytes, + ) + expect(isValidSignature).toBe(true) }) - it('self-issue certificate with Ed25519', async () => { + it('self issue certificate', async () => { + // Create a private key for testing const [privateKey, publicKey] = await generateEd25519KeyPair2() - const certificateManager = new Ed25519CertificateManager(null, privateKey) + const selfIssuer = new CertificateManager(null, privateKey, publicKey) + const certificate = await selfIssuer.selfIssueCertificate('Test') + // Define the subject name for the certificate const subjectName = 'Test' - const certificate = await certificateManager.selfIssueCertificate(subjectName) + // Assert that the certificate is not null expect(certificate).not.toBeNull() - expect(certificate.subject).toContain(`CN=${subjectName}`) + + // Assert that the certificate subject name matches the provided subject name + const commonNameField = certificate.subjectName.getField(OID_COMMON_NAME)[0] + expect(commonNameField).toEqual(subjectName) + + // Assert that the certificate public key matches the private key's public key + expect(await validateCertificatePublicKey(certificate.publicKey, publicKey)).toBe(true) + + const tbsCertificateBytes = getTbsCertificate(certificate) + const signature = certificate.signature + + const isValidSignature = await crypto.subtle.verify( + { + name: 'Ed25519', + }, + publicKey, + signature, + tbsCertificateBytes, + ) + expect(isValidSignature).toBe(true) + }) + + it('get subject common name', async () => { + // Create a private key for testing + const [privateKey, publicKey] = await generateEd25519KeyPair2() + const selfIssuer = new CertificateManager(null, privateKey, publicKey) + const certificate = await selfIssuer.selfIssueCertificate('Test') + + // Define the subject name for the certificate + const subjectName = 'Test' + + // Retrieve the common name from the certificate + const commonName = CertificateManager.getSubjectCommonName(certificate.subjectName) + + // Assert that the common name matches the provided subject name + expect(commonName).toEqual(subjectName) + }) + + it('Certificate to Pem and back', async () => { + // Create a private key for testing + const [privateKey, publicKey] = await generateEd25519KeyPair2() + const selfIssuer = new CertificateManager(null, privateKey, publicKey) + // Define the subject name for the certificate + const subjectName = 'Test' + const certificate = await selfIssuer.selfIssueCertificate(subjectName) + const pemCertificate = CertificateManager.certificateToPem(certificate) + + // Convert the PEM back to a certificate + const certificateFromPem = CertificateManager.pemToCertificate(pemCertificate) + + expect(certificate).toEqual(certificateFromPem) }) }) diff --git a/yarn.lock b/yarn.lock index 35438c16..fcf3f488 100644 --- a/yarn.lock +++ b/yarn.lock @@ -38,10 +38,8 @@ __metadata: "@peculiar/x509": "npm:^1.11.0" "@types/jest": "npm:^29.5.12" "@types/node": "npm:^20.12.12" - "@types/node-forge": "npm:^1" asn1js: "npm:^3.0.5" jest: "npm:^29.7.0" - node-forge: "npm:^1.3.1" ts-jest: "npm:^29.1.4" ts-node: "npm:^10.9.2" typescript: "npm:^5.4.5" @@ -1774,15 +1772,6 @@ __metadata: languageName: node linkType: hard -"@types/node-forge@npm:^1": - version: 1.3.11 - resolution: "@types/node-forge@npm:1.3.11" - dependencies: - "@types/node": "npm:*" - checksum: 10c0/3d7d23ca0ba38ac0cf74028393bd70f31169ab9aba43f21deb787840170d307d662644bac07287495effe2812ddd7ac8a14dbd43f16c2936bbb06312e96fc3b9 - languageName: node - linkType: hard - "@types/node@npm:*": version: 20.14.0 resolution: "@types/node@npm:20.14.0" @@ -4159,13 +4148,6 @@ __metadata: languageName: node linkType: hard -"node-forge@npm:^1.3.1": - version: 1.3.1 - resolution: "node-forge@npm:1.3.1" - checksum: 10c0/e882819b251a4321f9fc1d67c85d1501d3004b4ee889af822fd07f64de3d1a8e272ff00b689570af0465d65d6bf5074df9c76e900e0aff23e60b847f2a46fbe8 - languageName: node - linkType: hard - "node-gyp@npm:latest": version: 10.1.0 resolution: "node-gyp@npm:10.1.0" From d11cefa72bd6607184ef22a7bda8b6aa110e13c8 Mon Sep 17 00:00:00 2001 From: Abhijit Roy Date: Tue, 18 Jun 2024 15:05:58 +0530 Subject: [PATCH 5/7] cleanup, remove unused code --- packages/auto-id/src/certificateManager.ts | 100 +-------------------- 1 file changed, 1 insertion(+), 99 deletions(-) diff --git a/packages/auto-id/src/certificateManager.ts b/packages/auto-id/src/certificateManager.ts index 5a47ea1c..3d83526a 100644 --- a/packages/auto-id/src/certificateManager.ts +++ b/packages/auto-id/src/certificateManager.ts @@ -3,7 +3,7 @@ import { blake2b_256, concatenateUint8Arrays, stringToUint8Array } from '@autonomys/auto-utils' import { AsnConvert } from '@peculiar/asn1-schema' -import { AttributeTypeAndValue, GeneralName, GeneralNames } from '@peculiar/asn1-x509' +import { AttributeTypeAndValue, GeneralNames } from '@peculiar/asn1-x509' import { Crypto } from '@peculiar/webcrypto' import * as x509 from '@peculiar/x509' import { KeyObject, createPublicKey } from 'crypto' @@ -181,16 +181,6 @@ export class CertificateManager { return keyObject } - // TODO: later on move to utils. - private static stringToArrayBuffer(str: string): ArrayBuffer { - const buffer = new ArrayBuffer(str.length) - const view = new Uint8Array(buffer) - for (let i = 0; i < str.length; i++) { - view[i] = str.charCodeAt(i) - } - return buffer - } - async issueCertificate( csr: x509.Pkcs10CertificateRequest, validityPeriodDays: number = 365, @@ -259,12 +249,7 @@ export class CertificateManager { const autoIdSan = `autoid:auto:${Buffer.from(autoId).toString('hex')}` - // FIXME: check testing - // Check for existing SAN extension - // const sanExtensions = csr.extensions // --> [] - const sanExtensions = csr.extensions.filter((ext) => ext.type === OID_SUBJECT_ALT_NAME) // OID for subjectAltName - if (sanExtensions.length) { // const existingSan = sanExtensions[0].value const existingSan = sanExtensions[0] as x509.SubjectAlternativeNameExtension @@ -342,89 +327,6 @@ export class CertificateManager { } } -// export class Ed25519CertificateManager { -// private certificate: x509.X509Certificate | null -// private privateKey: CryptoKey | null - -// constructor( -// certificate: x509.X509Certificate | null = null, -// privateKey: CryptoKey | null = null, -// ) { -// this.certificate = certificate -// this.privateKey = privateKey -// } - -// async createAndSignCSR(subjectName: string): Promise { -// if (!this.privateKey) { -// throw new Error('Private key is not set.') -// } - -// // Export the public key (ArrayBuffer) from the private key -// const publicKeyArrayBuffer = await crypto.subtle.exportKey('spki', this.privateKey) -// // Create a public key (CryptoKey) from the exported public key -// const publicKey = await crypto.subtle.importKey( -// 'spki', -// publicKeyArrayBuffer, -// { -// name: 'Ed25519', -// }, -// true, -// ['verify'], -// ) - -// const csr = await x509.Pkcs10CertificateRequestGenerator.create({ -// name: `CN=${subjectName}`, -// keys: { privateKey: this.privateKey, publicKey }, -// signingAlgorithm: { name: 'Ed25519' }, -// }) - -// return csr -// } - -// async issueCertificate( -// csr: x509.Pkcs10CertificateRequest, -// validityPeriodDays: number = 365, -// ): Promise { -// if (!this.privateKey) { -// throw new Error('Private key is not set.') -// } - -// const notBefore = new Date() -// const notAfter = new Date() -// notAfter.setDate(notBefore.getDate() + validityPeriodDays) - -// const certificate = await x509.X509CertificateGenerator.create({ -// serialNumber: randomSerialNumber().toString(), -// issuer: csr.subject, -// subject: csr.subject, -// notBefore, -// notAfter, -// signingAlgorithm: { name: 'Ed25519' }, -// signingKey: this.privateKey, -// publicKey: csr.publicKey, -// }) - -// this.certificate = certificate -// return certificate -// } - -// async selfIssueCertificate( -// subjectName: string, -// validityPeriodDays: number = 365, -// ): Promise { -// const csr = await this.createAndSignCSR(subjectName) -// return this.issueCertificate(csr, validityPeriodDays) -// } - -// saveCertificate(filePath: string): void { -// if (!this.certificate) { -// throw new Error('No certificate available to save.') -// } -// const certificatePem = this.certificate.toString('pem') -// fs.writeFileSync(filePath, certificatePem, 'utf8') -// } -// } - function arrayBufferToBase64(buffer: ArrayBuffer): string { let binary = '' const bytes = new Uint8Array(buffer) From ac70441aaacfca98c5315744a85c61025c2609b3 Mon Sep 17 00:00:00 2001 From: Abhijit Roy Date: Wed, 19 Jun 2024 20:29:33 +0530 Subject: [PATCH 6/7] Modify the dynamic save function for client/server side rendering --- packages/auto-id/src/certificateManager.ts | 12 ++++++++---- packages/auto-utils/src/save.ts | 4 ++-- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/packages/auto-id/src/certificateManager.ts b/packages/auto-id/src/certificateManager.ts index 3d83526a..940f7365 100644 --- a/packages/auto-id/src/certificateManager.ts +++ b/packages/auto-id/src/certificateManager.ts @@ -1,13 +1,17 @@ //! For key generation, management, `keyManagement.ts` file is used using "crypto" library. //! And for certificate related, used "@peculiar/x509" library. -import { blake2b_256, concatenateUint8Arrays, stringToUint8Array } from '@autonomys/auto-utils' +import { + blake2b_256, + concatenateUint8Arrays, + save, + stringToUint8Array, +} from '@autonomys/auto-utils' import { AsnConvert } from '@peculiar/asn1-schema' import { AttributeTypeAndValue, GeneralNames } from '@peculiar/asn1-x509' import { Crypto } from '@peculiar/webcrypto' import * as x509 from '@peculiar/x509' import { KeyObject, createPublicKey } from 'crypto' -import fs from 'fs' import { doPublicKeysMatch, pemToPublicKey } from './keyManagement' import { randomSerialNumber } from './utils' @@ -317,13 +321,13 @@ export class CertificateManager { return certificate } - saveCertificate(filePath: string): void { + async saveCertificate(filePath: string): Promise { if (!this.certificate) { throw new Error('No certificate available to save.') } const certificatePem = CertificateManager.certificateToPem(this.certificate) - fs.writeFileSync(filePath, certificatePem, 'utf8') + await save(filePath, certificatePem) } } diff --git a/packages/auto-utils/src/save.ts b/packages/auto-utils/src/save.ts index 5fa575a6..27a2718c 100644 --- a/packages/auto-utils/src/save.ts +++ b/packages/auto-utils/src/save.ts @@ -16,7 +16,7 @@ export const saveOnFileSystem = async (key: string, value: any) => { // save on file system const fs = await import('fs/promises') // Check if value is already a string to avoid unnecessary JSON string conversion - const data = typeof value === 'string' ? value : JSON.stringify(value); + const data = typeof value === 'string' ? value : JSON.stringify(value) await fs.writeFile(key, JSON.stringify(data)) } else throw new Error('This function can only be used in node') -} \ No newline at end of file +} From 441c732c0bac9eb537ad7264465bdab0f75f1b5c Mon Sep 17 00:00:00 2001 From: Abhijit Roy Date: Thu, 20 Jun 2024 22:42:55 +0530 Subject: [PATCH 7/7] Removed unused code --- packages/auto-id/src/certificateManager.ts | 6 ------ packages/auto-id/src/utils.ts | 10 ---------- 2 files changed, 16 deletions(-) diff --git a/packages/auto-id/src/certificateManager.ts b/packages/auto-id/src/certificateManager.ts index 940f7365..1687c9db 100644 --- a/packages/auto-id/src/certificateManager.ts +++ b/packages/auto-id/src/certificateManager.ts @@ -13,7 +13,6 @@ import { Crypto } from '@peculiar/webcrypto' import * as x509 from '@peculiar/x509' import { KeyObject, createPublicKey } from 'crypto' import { doPublicKeysMatch, pemToPublicKey } from './keyManagement' -import { randomSerialNumber } from './utils' const crypto = new Crypto() x509.cryptoProvider.set(crypto) @@ -67,10 +66,6 @@ export class CertificateManager { return new x509.Name([[commonNameAttr]]) } - // protected static toCommonName(subjectName: string): JsonName { - // return [{ '2.5.4.3': [subjectName] }] // OID for commonName - // } - static prettyPrintCertificate(cert: x509.X509Certificate): void { console.log('Certificate:') console.log('============') @@ -241,7 +236,6 @@ export class CertificateManager { notAfter.setDate(notBefore.getDate() + validityPeriodDays) let certificateBuilder = await x509.X509CertificateGenerator.create({ - serialNumber: randomSerialNumber().toString(), issuer: csr.subject, subject: csr.subject, notBefore, diff --git a/packages/auto-id/src/utils.ts b/packages/auto-id/src/utils.ts index b9630f1a..e7ac9a94 100644 --- a/packages/auto-id/src/utils.ts +++ b/packages/auto-id/src/utils.ts @@ -23,16 +23,6 @@ export function derEncodeSignatureAlgorithmOID(oid: string): Uint8Array { return new Uint8Array([...sequenceHeader, ...new Uint8Array(berArrayBuffer), ...nullParameter]) } -export function randomSerialNumber(): bigint { - // Generate 20 random bytes - const bytes = randomBytes(20) - // Convert bytes to a BigInt - let serial = BigInt('0x' + bytes.toString('hex')) - // Shift right by 1 to ensure the number is positive - serial = serial >> BigInt(1) - return serial -} - export function addDaysToCurrentDate(days: number): Date { const currentDate = new Date() // This gives you the current date and time currentDate.setUTCDate(currentDate.getUTCDate() + days) // Adds the specified number of days