cyberchef/src/core/operations/CBOREncode.mjs
J8k3 ffc5fcbf41 Add PIN Block Translate Encrypted; fix CBOR v9 encode; fix EMV MAC tests; fix bcrypt node test
- PIN Block Translate Encrypted: new operation with 5 tests; registered in Payments category
- CBOR v9: fix Encoder streaming/Buffer pool issue; JSDoc on helpers
- EMV Generate MAC: fix empty-input hex parse, stale 3-arg test, missing padding method in verify test
- parseHexBytes: accept empty string as valid 0-byte hex
- bcrypt node test: accept $2a prefix from bcryptjs v2.4.3

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-20 19:04:35 -04:00

110 lines
3.4 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* @author Danh4 [dan.h4@ncsc.gov.uk]
* @copyright Crown Copyright 2020
* @license Apache-2.0
*/
import Operation from "../Operation.mjs";
import Cbor from "cbor";
// cbor v9: Encoder.encode/encodeCanonical return only the first byte.
// Pre-sort map keys ourselves and use a custom Map semantic type so the
// encoder writes keys in insertion order without re-sorting internally.
/**
* Returns the byte-length of a CBOR-encoded text string key (header + payload).
* Used to implement RFC 7049 canonical map key ordering.
*
* @param {string} s
* @returns {number}
*/
function cborKeyEncodedLen(s) {
const n = Buffer.byteLength(s, "utf8");
if (n < 24) return 1 + n;
if (n < 0x100) return 2 + n;
if (n < 0x10000) return 3 + n;
return 5 + n;
}
/**
* Recursively converts plain objects to pre-sorted Maps so that the CBOR
* encoder emits keys in canonical (length-first, then lexicographic) order
* without relying on the cbor library's own canonical sort, which is broken
* in cbor v9 for streamed output.
*
* @param {*} val
* @returns {*}
*/
function prepareCBOR(val) {
if (Array.isArray(val)) return val.map(prepareCBOR);
if (val !== null && typeof val === "object" && !(val instanceof Map)) {
const sorted = Object.keys(val).sort((a, b) => {
const la = cborKeyEncodedLen(a), lb = cborKeyEncodedLen(b);
if (la !== lb) return la - lb;
return Buffer.from(a, "utf8").compare(Buffer.from(b, "utf8"));
});
return new Map(sorted.map(k => [k, prepareCBOR(val[k])]));
}
return val;
}
/**
* Encodes a value as canonical CBOR using a streaming Encoder.
* Returns a Promise that resolves to a Buffer containing the full encoding.
*
* @param {*} input
* @returns {Promise<Buffer>}
*/
function cborEncodeCanonical(input) {
return new Promise((resolve, reject) => {
const enc = new Cbor.Encoder({canonical: true});
enc.addSemanticType(Map, (e, m) => {
if (!e._pushInt(m.size, 5)) return false;
for (const [k, v] of m) {
if (!e.pushAny(k) || !e.pushAny(v)) return false;
}
return true;
});
const bufs = [];
enc.on("data", b => bufs.push(b));
enc.on("error", reject);
enc.on("finish", () => resolve(Buffer.concat(bufs)));
enc.pushAny(prepareCBOR(input));
enc.end();
});
}
/**
* CBOR Encode operation
*/
class CBOREncode extends Operation {
/**
* CBOREncode constructor
*/
constructor() {
super();
this.name = "CBOR Encode";
this.module = "Serialise";
this.description = "Concise Binary Object Representation (CBOR) is a binary data serialization format loosely based on JSON. Like JSON it allows the transmission of data objects that contain namevalue pairs, but in a more concise manner. This increases processing and transfer speeds at the cost of human readability. It is defined in IETF RFC 8949.";
this.infoURL = "https://wikipedia.org/wiki/CBOR";
this.inputType = "JSON";
this.outputType = "ArrayBuffer";
this.args = [];
}
/**
* @param {JSON} input
* @param {Object[]} args
* @returns {ArrayBuffer}
*/
async run(input, args) {
const buf = await cborEncodeCanonical(input);
return buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength);
}
}
export default CBOREncode;