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.
206 lines (205 loc) • 8.61 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.DefaultEdhocCryptoManager = void 0;
const edhoc_1 = require("./edhoc");
const ed25519_1 = require("@noble/curves/ed25519");
const p256_1 = require("@noble/curves/p256");
const sha256_1 = require("@noble/hashes/sha256");
const hkdf_1 = require("@noble/hashes/hkdf");
const crypto_1 = require("crypto");
class DefaultEdhocCryptoManager {
keys = {};
keyIdentifier = 1000;
constructor() {
this.keys = {};
}
addKey(keyID, key) {
const kid = keyID.toString('hex');
this.keys[kid] = key;
}
async importKey(edhoc, keyType, key) {
const keyBuffer = Buffer.alloc(4);
keyBuffer.writeInt32LE(this.keyIdentifier++);
const keyID = keyBuffer.toString('hex');
const curveKE = this.getCurveForKeyAgreement(edhoc.selectedSuite);
const curveSIG = this.getCurveForSignature(edhoc.selectedSuite);
switch (keyType) {
case edhoc_1.EdhocKeyType.KeyAgreement:
case edhoc_1.EdhocKeyType.MakeKeyPair:
this.keys[keyID] = key.byteLength > 0 ? Buffer.from(key) : Buffer.from(curveKE.utils.randomPrivateKey());
break;
case edhoc_1.EdhocKeyType.Signature:
this.keys[keyID] = key.byteLength > 0 ? Buffer.from(key) : Buffer.from(curveSIG.utils.randomPrivateKey());
break;
default:
this.keys[keyID] = Buffer.from(key);
}
return keyBuffer;
}
destroyKey(edhoc, keyID) {
const kid = keyID.toString('hex');
if (kid in this.keys === false) {
throw new Error(`Key '${kid}' not found`);
}
delete this.keys[kid];
return true;
}
makeKeyPair(edhoc, keyID, _privateKeySize, _publicKeySize) {
const key = this.getKey(keyID);
try {
const curveKE = this.getCurveForKeyAgreement(edhoc.selectedSuite);
return {
privateKey: Buffer.from(key),
publicKey: Buffer.from(curveKE.getPublicKey(key)).subarray(curveKE === p256_1.p256 ? 1 : 0)
};
}
catch (error) {
throw new Error(`Wrong key type`);
}
}
keyAgreement(edhoc, keyID, publicKey, _privateKeySize) {
const key = this.getKey(keyID);
const curveKE = this.getCurveForKeyAgreement(edhoc.selectedSuite);
const publicKeyBuffer = this.formatPublicKey(curveKE, publicKey);
const sharedSecrect = Buffer.from(curveKE.getSharedSecret(key, new Uint8Array(publicKeyBuffer)));
return sharedSecrect.subarray(curveKE === p256_1.p256 ? 1 : 0);
}
sign(edhoc, keyID, input, _signatureSize) {
const key = this.getKey(keyID);
const curveSIG = this.getCurveForSignature(edhoc.selectedSuite);
const payload = this.formatToBeSigned(curveSIG, input);
const signature = curveSIG.sign(payload, new Uint8Array(key));
if (signature instanceof Uint8Array) {
return Buffer.from(signature);
}
else if ('toCompactRawBytes' in signature) {
return Buffer.from(signature.toCompactRawBytes());
}
else {
throw new Error('Unsupported signature type');
}
}
async verify(edhoc, keyID, input, signature) {
const key = this.getKey(keyID);
const curveSIG = this.getCurveForSignature(edhoc.selectedSuite);
const publicKeyBuffer = this.formatPublicKey(curveSIG, key);
const payload = this.formatToBeSigned(curveSIG, input);
if (!curveSIG.verify(new Uint8Array(signature), payload, new Uint8Array(publicKeyBuffer))) {
throw new Error('Signature not verified');
}
return true;
}
extract(edhoc, keyID, salt, _keySize) {
const key = this.getKey(keyID);
return Buffer.from((0, hkdf_1.extract)(sha256_1.sha256, new Uint8Array(key), new Uint8Array(salt)));
}
expand(edhoc, keyID, info, keySize) {
const key = this.getKey(keyID);
const expanded = Buffer.from((0, hkdf_1.expand)(sha256_1.sha256, new Uint8Array(key), new Uint8Array(info), keySize));
return expanded;
}
encrypt(edhoc, keyID, nonce, aad, plaintext, _size) {
const key = this.getKey(keyID);
const algorithm = this.getAlgorithm(edhoc.selectedSuite);
const options = {
authTagLength: this.getTagLength(edhoc.selectedSuite)
};
const cipher = (0, crypto_1.createCipheriv)(algorithm, key, nonce, options);
cipher.setAAD(aad, { plaintextLength: Buffer.byteLength(plaintext) });
const update = Buffer.byteLength(plaintext) === 0 ? Buffer.alloc(0) : plaintext;
const encrypted = Buffer.concat([
cipher.update(update),
cipher.final(),
cipher.getAuthTag()
]);
return encrypted;
}
decrypt(edhoc, keyID, nonce, aad, ciphertext, _size) {
const key = this.getKey(keyID);
const tagLength = this.getTagLength(edhoc.selectedSuite);
const algorithm = this.getAlgorithm(edhoc.selectedSuite);
const options = { authTagLength: tagLength };
const decipher = (0, crypto_1.createDecipheriv)(algorithm, key, nonce, options);
decipher.setAuthTag(ciphertext.subarray(ciphertext.length - tagLength));
decipher.setAAD(aad, { plaintextLength: ciphertext.length - tagLength });
const decrypted = decipher.update(ciphertext.subarray(0, ciphertext.length - tagLength));
decipher.final();
return decrypted;
}
async hash(_edhoc, data, _hashSize) {
return Buffer.from((0, sha256_1.sha256)(data));
}
getKey(keyID) {
const kid = keyID.toString('hex');
if (kid in this.keys === false) {
throw new Error(`Key '${kid}' not found`);
}
return this.keys[kid];
}
formatToBeSigned(curve, payload) {
if (curve === p256_1.p256) {
return Buffer.from((0, sha256_1.sha256)(payload));
}
else if (curve === ed25519_1.ed25519) {
return payload;
}
else {
throw new Error(`Unsupported curve ${curve}`);
}
}
formatPublicKey(curve, key) {
if (curve === p256_1.p256) {
const prefix = key.byteLength === 64 ? 0x04 : (key[key.length - 1] & 1) ? 0x03 : 0x02;
return Buffer.concat([Buffer.from([prefix]), key]);
}
else if (curve === ed25519_1.ed25519 || curve === ed25519_1.x25519) {
return key;
}
else {
throw new Error(`Unsupported curve ${curve}`);
}
}
getCurveForSignature(suite) {
if ([edhoc_1.EdhocSuite.Suite2, edhoc_1.EdhocSuite.Suite3, edhoc_1.EdhocSuite.Suite5, edhoc_1.EdhocSuite.Suite6].includes(suite)) {
return p256_1.p256;
}
else if ([edhoc_1.EdhocSuite.Suite0, edhoc_1.EdhocSuite.Suite1, edhoc_1.EdhocSuite.Suite4].includes(suite)) {
return ed25519_1.ed25519;
}
else {
throw new Error(`Unsupported EDHOC suite ${suite} for signature.`);
}
}
getCurveForKeyAgreement(suite) {
if ([edhoc_1.EdhocSuite.Suite2, edhoc_1.EdhocSuite.Suite3, edhoc_1.EdhocSuite.Suite5].includes(suite)) {
return p256_1.p256;
}
else if ([edhoc_1.EdhocSuite.Suite0, edhoc_1.EdhocSuite.Suite1, edhoc_1.EdhocSuite.Suite4, edhoc_1.EdhocSuite.Suite6].includes(suite)) {
return ed25519_1.x25519;
}
else {
throw new Error(`Unsupported EDHOC suite ${suite} for key agreement.`);
}
}
getTagLength(suite) {
return [edhoc_1.EdhocSuite.Suite0, edhoc_1.EdhocSuite.Suite2].includes(suite) ? 8 : 16;
}
getAlgorithm(suite) {
if ([edhoc_1.EdhocSuite.Suite4, edhoc_1.EdhocSuite.Suite5, edhoc_1.EdhocSuite.Suite25].includes(suite)) {
return 'chacha20-poly1305';
}
else if ([edhoc_1.EdhocSuite.Suite6].includes(suite)) {
return 'aes-128-gcm';
}
else if ([edhoc_1.EdhocSuite.Suite24].includes(suite)) {
return 'aes-256-gcm';
}
else if ([edhoc_1.EdhocSuite.Suite0, edhoc_1.EdhocSuite.Suite1, edhoc_1.EdhocSuite.Suite2, edhoc_1.EdhocSuite.Suite3].includes(suite)) {
return 'aes-128-ccm';
}
else {
throw new Error(`Unsupported EDHOC suite ${suite} for encryption.`);
}
}
}
exports.DefaultEdhocCryptoManager = DefaultEdhocCryptoManager;