UNPKG

cose-js

Version:

JavaScript COSE implementation

285 lines (255 loc) 9.18 kB
/* jshint esversion: 6 */ /* jslint node: true */ 'use strict'; const cbor = require('cbor'); const EC = require('elliptic').ec; const crypto = require('crypto'); const NodeRSA = require('node-rsa'); const common = require('./common'); const EMPTY_BUFFER = common.EMPTY_BUFFER; const Tagged = cbor.Tagged; const SignTag = exports.SignTag = 98; const Sign1Tag = exports.Sign1Tag = 18; const AlgFromTags = {}; AlgFromTags[-7] = { sign: 'ES256', digest: 'SHA-256' }; AlgFromTags[-35] = { sign: 'ES384', digest: 'SHA-384' }; AlgFromTags[-36] = { sign: 'ES512', digest: 'SHA-512' }; AlgFromTags[-37] = { sign: 'PS256', digest: 'SHA-256' }; AlgFromTags[-38] = { sign: 'PS384', digest: 'SHA-384' }; AlgFromTags[-39] = { sign: 'PS512', digest: 'SHA-512' }; AlgFromTags[-257] = { sign: 'RS256', digest: 'SHA-256' }; AlgFromTags[-258] = { sign: 'RS384', digest: 'SHA-384' }; AlgFromTags[-259] = { sign: 'RS512', digest: 'SHA-512' }; const COSEAlgToNodeAlg = { ES256: { sign: 'p256', digest: 'sha256' }, ES384: { sign: 'p384', digest: 'sha384' }, ES512: { sign: 'p521', digest: 'sha512' }, RS256: { sign: 'RSA-SHA256' }, RS384: { sign: 'RSA-SHA384' }, RS512: { sign: 'RSA-SHA512' }, PS256: { alg: 'pss-sha256', saltLen: 32 }, PS384: { alg: 'pss-sha384', saltLen: 48 }, PS512: { alg: 'pss-sha512', saltLen: 64 } }; function doSign (SigStructure, signer, alg) { if (!AlgFromTags[alg]) { throw new Error('Unknown algorithm, ' + alg); } if (!COSEAlgToNodeAlg[AlgFromTags[alg].sign]) { throw new Error('Unsupported algorithm, ' + AlgFromTags[alg].sign); } let ToBeSigned = cbor.encode(SigStructure); let sig; if (AlgFromTags[alg].sign.startsWith('ES')) { const hash = crypto.createHash(COSEAlgToNodeAlg[AlgFromTags[alg].sign].digest); hash.update(ToBeSigned); ToBeSigned = hash.digest(); const ec = new EC(COSEAlgToNodeAlg[AlgFromTags[alg].sign].sign); const key = ec.keyFromPrivate(signer.key.d); const signature = key.sign(ToBeSigned); const bitLength = Math.ceil(ec.curve._bitLength / 8); sig = Buffer.concat([signature.r.toArrayLike(Buffer, undefined, bitLength), signature.s.toArrayLike(Buffer, undefined, bitLength)]); } else if (AlgFromTags[alg].sign.startsWith('PS')) { signer.key.dmp1 = signer.key.dp; signer.key.dmq1 = signer.key.dq; signer.key.coeff = signer.key.qi; const key = new NodeRSA().importKey(signer.key, 'components-private'); key.setOptions({ signingScheme: { scheme: COSEAlgToNodeAlg[AlgFromTags[alg].sign].alg.split('-')[0], hash: COSEAlgToNodeAlg[AlgFromTags[alg].sign].alg.split('-')[1], saltLength: COSEAlgToNodeAlg[AlgFromTags[alg].sign].saltLen } }); sig = key.sign(ToBeSigned); } else { const sign = crypto.createSign(COSEAlgToNodeAlg[AlgFromTags[alg].sign].sign); sign.update(ToBeSigned); sign.end(); sig = sign.sign(signer.key); } return sig; } exports.create = function (headers, payload, signers, options) { options = options || {}; let u = headers.u || {}; let p = headers.p || {}; p = common.TranslateHeaders(p); u = common.TranslateHeaders(u); let bodyP = p || {}; bodyP = (bodyP.size === 0) ? EMPTY_BUFFER : cbor.encode(bodyP); if (Array.isArray(signers)) { if (signers.length === 0) { throw new Error('There has to be at least one signer'); } if (signers.length > 1) { throw new Error('Only one signer is supported'); } // TODO handle multiple signers const signer = signers[0]; const externalAAD = signer.externalAAD || EMPTY_BUFFER; let signerP = signer.p || {}; let signerU = signer.u || {}; signerP = common.TranslateHeaders(signerP); signerU = common.TranslateHeaders(signerU); const alg = signerP.get(common.HeaderParameters.alg); signerP = (signerP.size === 0) ? EMPTY_BUFFER : cbor.encode(signerP); const SigStructure = [ 'Signature', bodyP, signerP, externalAAD, payload ]; const sig = doSign(SigStructure, signer, alg); if (p.size === 0 && options.encodep === 'empty') { p = EMPTY_BUFFER; } else { p = cbor.encode(p); } const signed = [p, u, payload, [[signerP, signerU, sig]]]; return cbor.encodeAsync(options.excludetag ? signed : new Tagged(SignTag, signed)); } else { const signer = signers; const externalAAD = signer.externalAAD || EMPTY_BUFFER; const alg = p.get(common.HeaderParameters.alg) || u.get(common.HeaderParameters.alg); const SigStructure = [ 'Signature1', bodyP, externalAAD, payload ]; const sig = doSign(SigStructure, signer, alg); if (p.size === 0 && options.encodep === 'empty') { p = EMPTY_BUFFER; } else { p = cbor.encode(p); } const signed = [p, u, payload, sig]; return cbor.encodeAsync(options.excludetag ? signed : new Tagged(Sign1Tag, signed), { canonical: true }); } }; function doVerify (SigStructure, verifier, alg, sig) { if (!AlgFromTags[alg]) { throw new Error('Unknown algorithm, ' + alg); } const nodeAlg = COSEAlgToNodeAlg[AlgFromTags[alg].sign]; if (!nodeAlg) { throw new Error('Unsupported algorithm, ' + AlgFromTags[alg].sign); } const ToBeSigned = cbor.encode(SigStructure); if (AlgFromTags[alg].sign.startsWith('ES')) { const hash = crypto.createHash(nodeAlg.digest); hash.update(ToBeSigned); const msgHash = hash.digest(); const pub = { x: verifier.key.x, y: verifier.key.y }; const ec = new EC(nodeAlg.sign); const key = ec.keyFromPublic(pub); sig = { r: sig.slice(0, sig.length / 2), s: sig.slice(sig.length / 2) }; if (!key.verify(msgHash, sig)) { throw new Error('Signature missmatch'); } } else if (AlgFromTags[alg].sign.startsWith('PS')) { const key = new NodeRSA().importKey(verifier.key, 'components-public'); key.setOptions({ signingScheme: { scheme: COSEAlgToNodeAlg[AlgFromTags[alg].sign].alg.split('-')[0], hash: COSEAlgToNodeAlg[AlgFromTags[alg].sign].alg.split('-')[1], saltLength: COSEAlgToNodeAlg[AlgFromTags[alg].sign].saltLen } }); if (!key.verify(ToBeSigned, sig, 'buffer', 'buffer')) { throw new Error('Signature missmatch'); } } else { const verify = crypto.createVerify(nodeAlg.sign); verify.update(ToBeSigned); if (!verify.verify(verifier.key, sig)) { throw new Error('Signature missmatch'); } } } function getSigner (signers, verifier) { for (let i = 0; i < signers.length; i++) { const kid = signers[i][1].get(common.HeaderParameters.kid); // TODO create constant for header locations if (kid.equals(Buffer.from(verifier.key.kid, 'utf8'))) { return signers[i]; } } } function getCommonParameter (first, second, parameter) { let result; if (first.get) { result = first.get(parameter); } if (!result && second.get) { result = second.get(parameter); } return result; } exports.verify = async function (payload, verifier, options) { options = options || {}; const obj = await cbor.decodeFirst(payload); return verifyInternal(verifier, options, obj); }; exports.verifySync = function (payload, verifier, options) { options = options || {}; const obj = cbor.decodeFirstSync(payload); return verifyInternal(verifier, options, obj); }; function verifyInternal (verifier, options, obj) { options = options || {}; let type = options.defaultType ? options.defaultType : SignTag; if (obj instanceof Tagged) { if (obj.tag !== SignTag && obj.tag !== Sign1Tag) { throw new Error('Unexpected cbor tag, \'' + obj.tag + '\''); } type = obj.tag; obj = obj.value; } if (!Array.isArray(obj)) { throw new Error('Expecting Array'); } if (obj.length !== 4) { throw new Error('Expecting Array of lenght 4'); } let [p, u, plaintext, signers] = obj; if (type === SignTag && !Array.isArray(signers)) { throw new Error('Expecting signature Array'); } p = (!p.length) ? EMPTY_BUFFER : cbor.decodeFirstSync(p); u = (!u.size) ? EMPTY_BUFFER : u; const signer = (type === SignTag ? getSigner(signers, verifier) : signers); if (!signer) { throw new Error('Failed to find signer with kid' + verifier.key.kid); } if (type === SignTag) { const externalAAD = verifier.externalAAD || EMPTY_BUFFER; let [signerP, , sig] = signer; signerP = (!signerP.length) ? EMPTY_BUFFER : signerP; p = (!p.size) ? EMPTY_BUFFER : cbor.encode(p); const signerPMap = cbor.decode(signerP); const alg = signerPMap.get(common.HeaderParameters.alg); const SigStructure = [ 'Signature', p, signerP, externalAAD, plaintext ]; doVerify(SigStructure, verifier, alg, sig); return plaintext; } else { const externalAAD = verifier.externalAAD || EMPTY_BUFFER; const alg = getCommonParameter(p, u, common.HeaderParameters.alg); p = (!p.size) ? EMPTY_BUFFER : cbor.encode(p); const SigStructure = [ 'Signature1', p, externalAAD, plaintext ]; doVerify(SigStructure, verifier, alg, signer); return plaintext; } }