UNPKG

edhoc

Version:

A Node.js implementation of EDHOC (Ephemeral Diffie-Hellman Over COSE) protocol for lightweight authenticated key exchange in IoT and other constrained environments.

141 lines (140 loc) 5.88 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.X509CertificateCredentialManager = void 0; const edhoc_1 = require("./edhoc"); const crypto_1 = require("crypto"); class X509CertificateCredentialManager { certificates = []; peerCertificates = []; trustedCAs = []; cryptoKeyID; fetchFormat = edhoc_1.EdhocCredentialsFormat.x5chain; constructor(credentials, cryptoKeyID) { this.cryptoKeyID = cryptoKeyID; this.certificates = this.convertAndValidateCredentials(credentials); } addPeerCertificate(certificate) { this.peerCertificates.push(this.convertAndValidateSingleCredential(certificate)); } addTrustedCA(certificate) { this.trustedCAs.push(this.convertAndValidateSingleCredential(certificate)); } convertAndValidateCredentials(credentials) { return credentials.map(cred => this.convertAndValidateSingleCredential(cred)); } convertAndValidateSingleCredential(cred) { if (cred instanceof crypto_1.X509Certificate) { return cred; } else if (cred instanceof Buffer) { return new crypto_1.X509Certificate(cred); } else { throw new Error('Invalid credentials'); } } async fetch(_edhoc) { if (this.certificates.length === 0) { throw new Error('No certificates found'); } switch (this.fetchFormat) { case edhoc_1.EdhocCredentialsFormat.x5chain: { const chain = { format: edhoc_1.EdhocCredentialsFormat.x5chain, privateKeyID: this.cryptoKeyID, x5chain: { certificates: this.certificates.map(cert => cert.raw) } }; return chain; } case edhoc_1.EdhocCredentialsFormat.x5t: { if (this.certificates.length > 1) { throw new Error('x5t format only supports a single certificate'); } const hash = { format: edhoc_1.EdhocCredentialsFormat.x5t, privateKeyID: this.cryptoKeyID, x5t: { certificate: this.certificates[0].raw, hash: Buffer.from(this.certificates[0].fingerprint256.replace(/:/g, ''), 'hex').subarray(0, 8), hashAlgorithm: edhoc_1.EdhocCredentialsCertificateHashAlgorithm.Sha256_64 } }; return hash; } default: throw new Error('Unsupported credentials format'); } } async verify(edhoc, credentials) { if (credentials.format !== edhoc_1.EdhocCredentialsFormat.x5chain && credentials.format !== edhoc_1.EdhocCredentialsFormat.x5t) { throw new Error('Credentials format not supported'); } let certificates = []; if (credentials.format === edhoc_1.EdhocCredentialsFormat.x5chain) { const x5chain = credentials.x5chain; certificates = x5chain.certificates; } else if (credentials.format === edhoc_1.EdhocCredentialsFormat.x5t) { const x5t = credentials.x5t; certificates = this.peerCertificates .filter(certificate => { const checksum = Buffer.from(certificate.fingerprint256.replace(/:/g, ''), 'hex'); if (x5t.hashAlgorithm == edhoc_1.EdhocCredentialsCertificateHashAlgorithm.Sha256_64) { return checksum.subarray(0, 8).equals(x5t.hash); } else if (x5t.hashAlgorithm == edhoc_1.EdhocCredentialsCertificateHashAlgorithm.Sha256) { return checksum.equals(x5t.hash); } else { throw new Error('Unsupported hash algorithm'); } }) .flatMap(certificate => certificate.raw); x5t.certificate = certificates[0]; } if (certificates.length < 1) { throw new Error('Certificate chain must contain at least one certificate.'); } this.verifyCertificateChain(certificates); this.verifyAgainstTrustRoots(certificates[certificates.length - 1]); const token = new crypto_1.X509Certificate(certificates[0]).publicKey.export({ format: 'jwk' }); credentials.publicKey = this.extractPublicKey(token); return credentials; } verifyCertificateChain(certificates) { for (let i = 0; i < certificates.length - 1; i++) { const currentCert = new crypto_1.X509Certificate(certificates[i]); const nextCert = new crypto_1.X509Certificate(certificates[i + 1]); if (!currentCert.verify(nextCert.publicKey)) { throw new Error(`Verification failed: Certificate at index ${i} is not signed by the next certificate in the chain.`); } } } verifyAgainstTrustRoots(lastCertBuffer) { const lastCert = new crypto_1.X509Certificate(lastCertBuffer); for (const trustRoot of this.trustedCAs) { if (lastCert.verify(trustRoot.publicKey)) { return; } } throw new Error('Certificate chain not verified'); } extractPublicKey(token) { if (token.crv === 'P-256') { return Buffer.concat([ Buffer.from(token.x, 'base64'), Buffer.from(token.y, 'base64') ]); } else if (token.crv === 'Ed25519') { return Buffer.from(token.x, 'base64'); } else { throw new Error('Unsupported curve'); } } } exports.X509CertificateCredentialManager = X509CertificateCredentialManager;