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.

213 lines (212 loc) 8.53 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.encodeCborSequence = encodeCborSequence; exports.decodeCborSequence = decodeCborSequence; exports.encodeSuites = encodeSuites; exports.connectionIdToBytes = connectionIdToBytes; exports.connectionIdFromCbor = connectionIdFromCbor; exports.encodeIdCred = encodeIdCred; exports.encodeIdCredMap = encodeIdCredMap; exports.encodeCredItem = encodeCredItem; exports.decodeIdCred = decodeIdCred; exports.getCredBytes = getCredBytes; exports.encodePlaintext = encodePlaintext; exports.parsePlaintext = parsePlaintext; exports.encodeEadItems = encodeEadItems; exports.parseEadItems = parseEadItems; const cbor_1 = __importDefault(require("cbor")); const edhoc_1 = require("./edhoc"); /** Encode a CBOR sequence (concatenated CBOR items) */ function encodeCborSequence(...items) { return Buffer.concat(items.map(item => cbor_1.default.encode(item))); } /** Decode N items from a CBOR sequence buffer */ function decodeCborSequence(buf, count) { const results = []; let offset = 0; while (offset < buf.length && (count === undefined || results.length < count)) { const result = cbor_1.default.decodeFirstSync(buf.subarray(offset), { extendedResults: true }); results.push(result.value); offset += result.length; } return results; } /** Encode SUITES_I: single int if one suite, array if multiple (selected last) */ function encodeSuites(suites, selected) { if (suites.length === 1) return suites[0]; const rest = suites.filter(s => s !== selected); rest.push(selected); return rest; } /** Convert a connection ID to its raw byte representation (for OSCORE IDs). * RFC 9528 §3.3.2: the OSCORE identifier is a 1-byte bstr whose byte value * is the CBOR encoding of the integer n ∈ [-24, 23]. */ function connectionIdToBytes(cid) { if (Buffer.isBuffer(cid)) return cid; // CBOR encoding of integer n: // n >= 0 → major type 0, additional info = n → byte = n // n < 0 → major type 1, additional info = -(n+1) → byte = 0x20 | (-(n+1)) if (cid >= 0) return Buffer.from([cid]); return Buffer.from([0x20 | (-(cid + 1))]); } /** Decode a CBOR-decoded value back to an EdhocConnectionID */ function connectionIdFromCbor(value) { if (typeof value === 'number') return value; if (Buffer.isBuffer(value)) return value; if (value instanceof Uint8Array) return Buffer.from(value); throw new Error(`Invalid connection ID: ${typeof value}`); } /** Encode ID_CRED_x for PLAINTEXT / context embedding (already CBOR) */ function encodeIdCred(credentials) { switch (credentials.format) { case edhoc_1.EdhocCredentialsFormat.kid: { const kid = credentials.kid.kid; return cbor_1.default.encode(kid); } case edhoc_1.EdhocCredentialsFormat.x5chain: { const certs = credentials.x5chain.certificates; const map = new Map(); map.set(edhoc_1.EdhocCredentialsFormat.x5chain, certs.length === 1 ? certs[0] : certs); return cbor_1.default.encode(map); } case edhoc_1.EdhocCredentialsFormat.x5t: { const x5t = credentials.x5t; const map = new Map(); map.set(edhoc_1.EdhocCredentialsFormat.x5t, [x5t.hashAlgorithm, x5t.hash]); return cbor_1.default.encode(map); } default: throw new Error(`Unsupported credential format`); } } /** Encode ID_CRED_x as a CBOR map (full form for MAC context / Sig_structure). * For kid: {4: bstr(cbor(kid))}; for x5chain/x5t: same as encodeIdCred. */ function encodeIdCredMap(credentials) { switch (credentials.format) { case edhoc_1.EdhocCredentialsFormat.kid: { const kid = credentials.kid.kid; const kidCborBytes = cbor_1.default.encode(kid); const map = new Map(); map.set(edhoc_1.EdhocCredentialsFormat.kid, kidCborBytes); return cbor_1.default.encode(map); } default: return encodeIdCred(credentials); } } /** Encode CRED_x as a CBOR item for use in context / TH input. * For CCS (kid + isCBOR): credBytes is already CBOR, return as-is. * For DER certs: wrap as CBOR bstr. */ function encodeCredItem(credentials, credBytes) { if (credentials.format === edhoc_1.EdhocCredentialsFormat.kid && credentials.kid.isCBOR) { return credBytes; } return cbor_1.default.encode(credBytes); } /** Decode an ID_CRED_x value (already CBOR-decoded) into partial credentials */ function decodeIdCred(value) { if (typeof value === 'number' || Buffer.isBuffer(value) || value instanceof Uint8Array) { const kid = Buffer.isBuffer(value) || value instanceof Uint8Array ? Buffer.from(value) : value; return { format: edhoc_1.EdhocCredentialsFormat.kid, kid: { kid } }; } if (value instanceof Map) { if (value.has(edhoc_1.EdhocCredentialsFormat.x5chain)) { const d = value.get(edhoc_1.EdhocCredentialsFormat.x5chain); const certificates = Array.isArray(d) ? d.map((c) => Buffer.from(c)) : [Buffer.from(d)]; return { format: edhoc_1.EdhocCredentialsFormat.x5chain, x5chain: { certificates } }; } if (value.has(edhoc_1.EdhocCredentialsFormat.x5t)) { const arr = value.get(edhoc_1.EdhocCredentialsFormat.x5t); return { format: edhoc_1.EdhocCredentialsFormat.x5t, x5t: { hashAlgorithm: arr[0], hash: Buffer.from(arr[1]) }, }; } if (value.has(edhoc_1.EdhocCredentialsFormat.kid)) { return { format: edhoc_1.EdhocCredentialsFormat.kid, kid: { kid: value.get(edhoc_1.EdhocCredentialsFormat.kid) }, }; } } throw new Error(`Cannot decode ID_CRED_x`); } /** Get the raw credential bytes (CRED_x) from credentials */ function getCredBytes(credentials) { switch (credentials.format) { case edhoc_1.EdhocCredentialsFormat.kid: { const c = credentials.kid.credentials; if (!c) throw new Error('KID credentials require credential data'); return c; } case edhoc_1.EdhocCredentialsFormat.x5chain: return credentials.x5chain.certificates[0]; case edhoc_1.EdhocCredentialsFormat.x5t: { const cert = credentials.x5t.certificate; if (!cert) throw new Error('x5t credentials require the certificate'); return cert; } default: throw new Error(`Unsupported credential format for CRED_x`); } } /** Encode PLAINTEXT_2/3: ID_CRED_x, Signature_or_MAC_x, ?EAD */ function encodePlaintext(idCredCbor, signatureOrMac, ead) { const parts = [idCredCbor, cbor_1.default.encode(signatureOrMac)]; if (ead && ead.length > 0) parts.push(encodeEadItems(ead)); return Buffer.concat(parts); } /** Parse PLAINTEXT_2/3 into { idCredRaw, signatureOrMac, ead } */ function parsePlaintext(data) { const items = decodeCborSequence(data); if (items.length < 2) throw new Error('PLAINTEXT must contain at least 2 items'); const sigRaw = items[1]; return { idCredRaw: items[0], signatureOrMac: Buffer.isBuffer(sigRaw) ? sigRaw : Buffer.from(sigRaw), ead: items.length > 2 ? parseEadItems(items.slice(2)) : [], }; } /** Encode EAD tokens as a CBOR sequence of (label, ?value) pairs */ function encodeEadItems(tokens) { const parts = []; for (const t of tokens) { parts.push(cbor_1.default.encode(t.label)); if (t.value && t.value.length > 0) parts.push(cbor_1.default.encode(t.value)); } return Buffer.concat(parts); } /** Parse EAD items from decoded CBOR values */ function parseEadItems(items) { const result = []; let i = 0; while (i < items.length) { const label = items[i]; i++; let value = Buffer.alloc(0); if (i < items.length && typeof items[i] !== 'number') { value = Buffer.isBuffer(items[i]) ? items[i] : Buffer.from(items[i]); i++; } result.push({ label, value }); } return result; }