UNPKG

cose-js

Version:

JavaScript COSE implementation

168 lines (145 loc) 4.91 kB
/* jshint esversion: 6 */ /* jslint node: true */ 'use strict'; const cbor = require('cbor'); const aesCbcMac = require('aes-cbc-mac'); const crypto = require('crypto'); const Promise = require('any-promise'); const common = require('./common'); const Tagged = cbor.Tagged; const EMPTY_BUFFER = common.EMPTY_BUFFER; const MAC0Tag = exports.MAC0Tag = 17; const MACTag = exports.MACTag = 97; const AlgFromTags = { 4: 'SHA-256_64', 5: 'SHA-256', 6: 'SHA-384', 7: 'SHA-512', 14: 'AES-MAC-128/64', 15: 'AES-MAC-256/64', 25: 'AES-MAC-128/128', 26: 'AES-MAC-256/128' }; const COSEAlgToNodeAlg = { 'SHA-256_64': 'sha256', 'SHA-256': 'sha256', HS256: 'sha256', 'SHA-384': 'sha384', 'SHA-512': 'sha512', 'AES-MAC-128/64': 'aes-cbc-mac-64', 'AES-MAC-128/128': 'aes-cbc-mac-128', 'AES-MAC-256/64': 'aes-cbc-mac-64', 'AES-MAC-256/128': 'aes-cbc-mac-128' }; const CutTo = { 4: 8, 5: 32, 6: 48, 7: 64 }; const context = {}; context[MAC0Tag] = 'MAC0'; context[MACTag] = 'MAC'; function doMac (context, p, externalAAD, payload, alg, key) { return new Promise((resolve, reject) => { const MACstructure = [ context, // 'MAC0' or 'MAC1', // context p, // protected externalAAD, // bstr, payload // bstr ]; const toBeMACed = cbor.encode(MACstructure); if (alg === 'aes-cbc-mac-64') { const mac = aesCbcMac.create(key, toBeMACed, 8); resolve(mac); } else if (alg === 'aes-cbc-mac-128') { const mac = aesCbcMac.create(key, toBeMACed, 16); resolve(mac); } else { const hmac = crypto.createHmac(alg, key); hmac.end(toBeMACed, function () { resolve(hmac.read()); }); } }); } exports.create = async function (headers, payload, recipents, externalAAD, options) { options = options || {}; externalAAD = externalAAD || EMPTY_BUFFER; let u = headers.u || {}; let p = headers.p || {}; p = common.TranslateHeaders(p); u = common.TranslateHeaders(u); const alg = p.get(common.HeaderParameters.alg) || u.get(common.HeaderParameters.alg); if (!alg) { throw new Error('Missing mandatory parameter \'alg\''); } if (recipents.length === 0) { throw new Error('There has to be at least one recipent'); } const predictableP = (!p.size) ? EMPTY_BUFFER : cbor.encode(p); if (p.size === 0 && options.encodep === 'empty') { p = EMPTY_BUFFER; } else { p = cbor.encode(p); } // TODO check crit headers if (Array.isArray(recipents)) { if (recipents.length > 1) { throw new Error('MACing with multiple recipents is not implemented'); } const recipent = recipents[0]; let tag = await doMac('MAC', predictableP, externalAAD, payload, COSEAlgToNodeAlg[AlgFromTags[alg]], recipent.key); tag = tag.slice(0, CutTo[alg]); const ru = common.TranslateHeaders(recipent.u); const rp = EMPTY_BUFFER; const maced = [p, u, payload, tag, [[rp, ru, EMPTY_BUFFER]]]; return cbor.encode(options.excludetag ? maced : new Tagged(MACTag, maced)); } else { let tag = await doMac('MAC0', predictableP, externalAAD, payload, COSEAlgToNodeAlg[AlgFromTags[alg]], recipents.key); tag = tag.slice(0, CutTo[alg]); const maced = [p, u, payload, tag]; return cbor.encode(options.excludetag ? maced : new Tagged(MAC0Tag, maced)); } }; exports.read = async function (data, key, externalAAD, options) { options = options || {}; externalAAD = externalAAD || EMPTY_BUFFER; let obj = await cbor.decodeFirst(data); let type = options.defaultType ? options.defaultType : MAC0Tag; if (obj instanceof Tagged) { if (obj.tag !== MAC0Tag && obj.tag !== MACTag) { throw new Error('Unexpected cbor tag, \'' + obj.tag + '\''); } type = obj.tag; obj = obj.value; } if (!Array.isArray(obj)) { throw new Error('Expecting Array'); } if (type === MAC0Tag && obj.length !== 4) { throw new Error('Expecting Array of lenght 4'); } if (type === MACTag && obj.length !== 5) { throw new Error('Expecting Array of lenght 5'); } let [p, u, payload, tag] = obj; p = (!p.length) ? EMPTY_BUFFER : cbor.decode(p); p = (!p.size) ? EMPTY_BUFFER : p; u = (!u.size) ? EMPTY_BUFFER : u; // TODO validate protected header const alg = (p !== EMPTY_BUFFER) ? p.get(common.HeaderParameters.alg) : (u !== EMPTY_BUFFER) ? u.get(common.HeaderParameters.alg) : undefined; p = (!p.size) ? EMPTY_BUFFER : cbor.encode(p); if (!AlgFromTags[alg]) { throw new Error('Unknown algorithm, ' + alg); } if (!COSEAlgToNodeAlg[AlgFromTags[alg]]) { throw new Error('Unsupported algorithm, ' + AlgFromTags[alg]); } let calcTag = await doMac(context[type], p, externalAAD, payload, COSEAlgToNodeAlg[AlgFromTags[alg]], key); calcTag = calcTag.slice(0, CutTo[alg]); if (tag.toString('hex') !== calcTag.toString('hex')) { throw new Error('Tag mismatch'); } return payload; };