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
JavaScript
;
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;