cosette
Version:
isomorphic Typescript COSE implementation
395 lines • 15.2 kB
JavaScript
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.read = exports.create = exports.Encrypt0Tag = exports.EncryptTag = void 0;
const cbor = __importStar(require("cbor-web"));
const crypto_1 = __importDefault(require("crypto"));
const common = __importStar(require("./common"));
const node_hkdf_sync_1 = __importDefault(require("node-hkdf-sync"));
const Tagged = cbor.Tagged;
const EMPTY_BUFFER = common.EMPTY_BUFFER;
exports.EncryptTag = 96;
exports.Encrypt0Tag = 16;
const runningInNode = common.runningInNode;
const TagToAlg = {
1: 'A128GCM',
2: 'A192GCM',
3: 'A256GCM',
10: 'AES-CCM-16-64-128',
11: 'AES-CCM-16-64-256',
12: 'AES-CCM-64-64-128',
13: 'AES-CCM-64-64-256',
30: 'AES-CCM-16-128-128',
31: 'AES-CCM-16-128-256',
32: 'AES-CCM-64-128-128',
33: 'AES-CCM-64-128-256'
};
const COSEAlgToNodeAlg = {
A128GCM: 'aes-128-gcm',
A192GCM: 'aes-192-gcm',
A256GCM: 'aes-256-gcm',
'AES-CCM-16-64-128': 'aes-128-ccm',
'AES-CCM-16-64-256': 'aes-256-ccm',
'AES-CCM-64-64-128': 'aes-128-ccm',
'AES-CCM-64-64-256': 'aes-256-ccm',
'AES-CCM-16-128-128': 'aes-128-ccm',
'AES-CCM-16-128-256': 'aes-256-ccm',
'AES-CCM-64-128-128': 'aes-128-ccm',
'AES-CCM-64-128-256': 'aes-256-ccm'
};
const isNodeAlg = {
1: true,
2: true,
3: true // A256GCM
};
const isCCMAlg = {
10: true,
11: true,
12: true,
13: true,
30: true,
31: true,
32: true,
33: true // AES-CCM-64-128-256
};
const authTagLength = {
1: 16,
2: 16,
3: 16,
10: 8,
11: 8,
12: 8,
13: 8,
30: 16,
31: 16,
32: 16,
33: 16 // AES-CCM-64-128-256
};
const ivLenght = {
1: 12,
2: 12,
3: 12,
10: 13,
11: 13,
12: 7,
13: 7,
30: 13,
31: 13,
32: 7,
33: 7 // AES-CCM-64-128-256
};
const keyLength = {
1: 16,
2: 24,
3: 32,
10: 16,
11: 32,
12: 16,
13: 32,
30: 16,
31: 32,
32: 16,
33: 32,
'P-521': 66,
'P-256': 32
};
const HKDFAlg = {
'ECDH-ES': 'sha256',
'ECDH-ES-512': 'sha512',
'ECDH-SS': 'sha256',
'ECDH-SS-512': 'sha512'
};
const nodeCRV = {
'P-521': 'secp521r1',
'P-256': 'prime256v1'
};
function createAAD(p, context, externalAAD) {
p = (!p.size) ? EMPTY_BUFFER : cbor.encode(p);
const encStructure = [
context,
p,
externalAAD
];
return cbor.encode(encStructure);
}
function _randomSource(bytes) {
return crypto_1.default.randomBytes(bytes);
}
function nodeEncrypt(payload, key, alg, iv, aad, ccm = false) {
const nodeAlg = COSEAlgToNodeAlg[TagToAlg[alg]];
const chiperOptions = ccm ? { authTagLength: authTagLength[alg] } : null;
const aadOptions = ccm ? { plaintextLength: Buffer.byteLength(payload) } : null;
const cipher = crypto_1.default.createCipheriv(nodeAlg, key, iv, chiperOptions);
cipher.setAAD(aad, aadOptions);
return Buffer.concat([
cipher.update(payload),
cipher.final(),
cipher.getAuthTag()
]);
}
function createContext(rp, alg, partyUNonce) {
return cbor.encode([
alg,
[
null,
(partyUNonce || null),
null // other
],
[
null,
null,
null // other
],
[
keyLength[alg] * 8,
rp // protected
]
]);
}
function create(headers, payload, recipients, options) {
return new Promise((resolve, reject) => {
options = options || {};
const externalAAD = options.externalAAD || EMPTY_BUFFER;
const randomSource = options.randomSource || _randomSource;
const p = common.TranslateHeaders(headers.p || {});
const u = common.TranslateHeaders(headers.u || {});
const alg = p.get(common.HeaderParameters.alg) || u.get(common.HeaderParameters.alg);
if (typeof alg !== "number") {
throw new Error('Missing mandatory parameter \'alg\'');
}
if (Array.isArray(recipients)) {
if (recipients.length === 0) {
throw new Error('There has to be at least one recipent');
}
if (recipients.length > 1) {
throw new Error('Encrypting with multiple recipents is not implemented');
}
let iv;
if (options.contextIv) {
const partialIv = randomSource(2);
iv = common.xor(partialIv, options.contextIv);
u.set(common.HeaderParameters.Partial_IV, partialIv);
}
else {
iv = randomSource(ivLenght[alg]);
u.set(common.HeaderParameters.IV, iv);
}
const aad = createAAD(p, 'Encrypt', externalAAD);
let key;
let recipientStruct;
// TODO do a more accurate check
if (recipients[0] && recipients[0].p &&
(recipients[0].p.alg === 'ECDH-ES' ||
recipients[0].p.alg === 'ECDH-ES-512' ||
recipients[0].p.alg === 'ECDH-SS' ||
recipients[0].p.alg === 'ECDH-SS-512')) {
const recipient = crypto_1.default.createECDH(nodeCRV[recipients[0].key.crv]);
const generated = crypto_1.default.createECDH(nodeCRV[recipients[0].key.crv]);
recipient.setPrivateKey(recipients[0].key.d);
let pk = randomSource(keyLength[recipients[0].key.crv]);
if (recipients[0].p.alg === 'ECDH-ES' ||
recipients[0].p.alg === 'ECDH-ES-512') {
pk = randomSource(keyLength[recipients[0].key.crv]);
pk[0] = (recipients[0].key.crv !== 'P-521' || pk[0] === 1) ? pk[0] : 0;
}
else {
pk = recipients[0].sender.d;
}
generated.setPrivateKey(pk);
const senderPublicKey = generated.getPublicKey();
const recipientPublicKey = Buffer.concat([
Buffer.from('04', 'hex'),
recipients[0].key.x,
recipients[0].key.y
]);
const generatedKey = common.TranslateKey({
crv: recipients[0].key.crv,
x: senderPublicKey.slice(1, keyLength[recipients[0].key.crv] + 1),
y: senderPublicKey.slice(keyLength[recipients[0].key.crv] + 1),
kty: 'EC2' // TODO use real value
});
const rp = cbor.encode(common.TranslateHeaders(recipients[0].p));
const ikm = generated.computeSecret(recipientPublicKey);
let partyUNonce = null;
if (recipients[0].p.alg === 'ECDH-SS' || recipients[0].p.alg === 'ECDH-SS-512') {
partyUNonce = randomSource(64); // TODO use real value
}
const context = createContext(rp, alg, partyUNonce);
const nrBytes = keyLength[alg];
const hkdf = new node_hkdf_sync_1.default(HKDFAlg[recipients[0].p.alg], undefined, ikm);
key = hkdf.derive(context, nrBytes);
let ru = recipients[0].u;
if (recipients[0].p.alg === 'ECDH-ES' ||
recipients[0].p.alg === 'ECDH-ES-512') {
ru.ephemeral_key = generatedKey;
}
else {
ru.static_key = generatedKey;
}
ru.partyUNonce = partyUNonce;
ru = common.TranslateHeaders(ru);
recipientStruct = [[rp, ru, EMPTY_BUFFER]];
}
else {
key = recipients[0].key;
const ru = common.TranslateHeaders(recipients[0].u);
recipientStruct = [[EMPTY_BUFFER, ru, EMPTY_BUFFER]];
}
let ciphertext;
if (isNodeAlg[alg]) {
ciphertext = nodeEncrypt(payload, key, alg, iv, aad);
}
else if (isCCMAlg[alg] && runningInNode()) {
ciphertext = nodeEncrypt(payload, key, alg, iv, aad, true);
}
else {
throw new Error('No implementation for algorithm, ' + alg);
}
const p_buffer = (p.size === 0 && options.encodep === 'empty')
? EMPTY_BUFFER
: cbor.encode(p);
const encrypted = [p_buffer, u, ciphertext, recipientStruct];
resolve(cbor.encode(options.excludetag ? encrypted : new Tagged(exports.EncryptTag, encrypted)));
}
else {
let iv;
if (options.contextIv) {
const partialIv = randomSource(2);
iv = common.xor(partialIv, options.contextIv);
u.set(common.HeaderParameters.Partial_IV, partialIv);
}
else {
iv = randomSource(ivLenght[alg]);
u.set(common.HeaderParameters.IV, iv);
}
let key;
if (recipients && recipients.p && recipients.p.alg === 'ECDH-ES') {
// TODO use curve from parameters
const recipient = crypto_1.default.createECDH('prime256v1');
const generated = crypto_1.default.createECDH('prime256v1');
recipient.setPrivateKey(recipients.key.d);
generated.setPrivateKey(randomSource(32)); // TODO use real alg value
const recipientPublicKey = Buffer.concat([
Buffer.from('04', 'hex'),
recipients.key.x,
recipients.key.y
]);
const ikm = generated.computeSecret(recipientPublicKey);
const context = createContext(p, 'undefined'); // TODO: provide a value for 'alg'
const nrBytes = 16; // TODO use real number based on alg
const hkdf = new node_hkdf_sync_1.default('sha256', undefined, ikm); // TODO use real alg
key = hkdf.derive(context, nrBytes);
}
else {
key = recipients.key;
}
const aad = createAAD(p, 'Encrypt0', externalAAD);
let ciphertext;
if (isNodeAlg[alg]) {
ciphertext = nodeEncrypt(payload, key, alg, iv, aad);
}
else if (isCCMAlg[alg] && runningInNode()) {
ciphertext = nodeEncrypt(payload, key, alg, iv, aad, true);
}
else {
throw new Error('No implementation for algorithm, ' + alg);
}
const p_buffer = (p.size === 0 && options.encodep === 'empty') ?
EMPTY_BUFFER
: cbor.encode(p);
const encrypted = [p_buffer, u, ciphertext];
resolve(cbor.encode(options.excludetag ? encrypted : new Tagged(exports.Encrypt0Tag, encrypted)));
}
});
}
exports.create = create;
;
function nodeDecrypt(ciphertext, key, alg, iv, tag, aad, ccm = false) {
const nodeAlg = COSEAlgToNodeAlg[TagToAlg[alg]];
const chiperOptions = ccm ? { authTagLength: authTagLength[alg] } : null;
const aadOptions = ccm ? { plaintextLength: Buffer.byteLength(ciphertext) } : null;
const decipher = crypto_1.default.createDecipheriv(nodeAlg, key, iv, chiperOptions);
decipher.setAuthTag(tag);
decipher.setAAD(aad, aadOptions);
return Buffer.concat([decipher.update(ciphertext), decipher.final()]);
}
function read(data, key, options) {
options = options || {};
const externalAAD = options.externalAAD || EMPTY_BUFFER;
return cbor.decodeFirst(data)
.then((obj) => {
let msgTag = options.defaultType ? options.defaultType : exports.EncryptTag;
if (obj instanceof Tagged) {
if (obj.tag !== exports.EncryptTag && obj.tag !== exports.Encrypt0Tag) {
throw new Error('Unknown tag, ' + obj.tag);
}
msgTag = obj.tag;
obj = obj.value;
}
if (!Array.isArray(obj)) {
throw new Error('Expecting Array');
}
if (msgTag === exports.EncryptTag && obj.length !== 4) {
throw new Error('Expecting Array of lenght 4 for COSE Encrypt message');
}
if (msgTag === exports.Encrypt0Tag && obj.length !== 3) {
throw new Error('Expecting Array of lenght 4 for COSE Encrypt0 message');
}
let [p, u, ciphertext] = obj;
p = (p.length === 0) ? EMPTY_BUFFER : cbor.decodeFirstSync(p);
p = (!p.size) ? EMPTY_BUFFER : p;
u = (!u.size) ? EMPTY_BUFFER : u;
const alg = (p !== EMPTY_BUFFER) ? p.get(common.HeaderParameters.alg) : (u !== EMPTY_BUFFER) ? u.get(common.HeaderParameters.alg) : undefined;
if (!TagToAlg[alg]) {
throw new Error('Unknown or unsupported algorithm ' + alg);
}
let iv = u.get(common.HeaderParameters.IV);
const partialIv = u.get(common.HeaderParameters.Partial_IV);
if (iv && partialIv) {
throw new Error('IV and Partial IV parameters MUST NOT both be present in the same security layer');
}
if (partialIv && !options.contextIv) {
throw new Error('Context IV must be provided when Partial IV is used');
}
if (partialIv && options.contextIv) {
iv = common.xor(partialIv, options.contextIv);
}
const tagLength = authTagLength[alg];
const tag = ciphertext.slice(ciphertext.length - tagLength, ciphertext.length);
ciphertext = ciphertext.slice(0, ciphertext.length - tagLength);
const aad = createAAD(p, (msgTag === exports.EncryptTag ? 'Encrypt' : 'Encrypt0'), externalAAD);
if (isNodeAlg[alg]) {
return nodeDecrypt(ciphertext, key, alg, iv, tag, aad);
}
else if (isCCMAlg[alg] && runningInNode()) {
return nodeDecrypt(ciphertext, key, alg, iv, tag, aad, true);
}
else {
throw new Error('No implementation for algorithm, ' + alg);
}
});
}
exports.read = read;
;
//# sourceMappingURL=encrypt.js.map