From 65db78a3c5c6ddd96ed07c05c68b031468cc5a53 Mon Sep 17 00:00:00 2001 From: AJ ONeal Date: Fri, 8 Mar 2019 16:46:50 -0700 Subject: [PATCH] v1.2.11: convert ECDSA ASN.1 signature to ECDSA JWT type --- keypairs.js | 43 ++++++++++++++++++++++++++++++++++++++++--- package.json | 2 +- test.js | 1 + 3 files changed, 42 insertions(+), 4 deletions(-) diff --git a/keypairs.js b/keypairs.js index 19e7812..730fac7 100644 --- a/keypairs.js +++ b/keypairs.js @@ -229,13 +229,21 @@ Keypairs.signJws = function (opts) { } // node specifies RSA-SHAxxx even whet it's actually ecdsa (it's all encoded x509 shasums anyway) - var nodeAlg = "RSA-SHA" + (((protect||header).alg||'').replace(/^[^\d]+/, '')||'256'); + var nodeAlg = "SHA" + (((protect||header).alg||'').replace(/^[^\d]+/, '')||'256'); var protected64 = Enc.strToUrlBase64(protectedHeader); var payload64 = Enc.bufToUrlBase64(payload); - var sig = require('crypto') + var binsig = require('crypto') .createSign(nodeAlg) .update(protect ? (protected64 + "." + payload64) : payload64) - .sign(pem, 'base64') + .sign(pem) + ; + if (!opts.jwk || 'RSA' !== opts.jwk.kty) { + // ECDSA JWT signatures differ from "normal" ECDSA signatures + // https://tools.ietf.org/html/rfc7518#section-3.4 + binsig = convertIfEcdsa(binsig); + } + + var sig = binsig.toString('base64') .replace(/\+/g, '-') .replace(/\//g, '_') .replace(/=/g, '') @@ -249,6 +257,35 @@ Keypairs.signJws = function (opts) { }; } + function convertIfEcdsa(binsig) { + // should have asn1 sequence header of 0x30 + if (0x30 !== binsig[0]) { return binsig; } + var index = 2; // first ecdsa "R" header byte + var len = binsig[1]; + var lenlen = 0; + // Seek length of length if length is greater than 127 (i.e. two 512-bit / 64-byte R and S values) + if (0x80 & len) { + lenlen = len - 0x80; + // the length of the signature won't be over 256 bytes (2048 bits) for many years yet + if (1 !== lenlen) { return binsig; } + // the length is this number + len = binsig[2]; + index += lenlen; + } + // should have bigint header of 0x02 followd by a single byte of length + if (0x02 !== binsig[index]) { return binsig; } + index += 1; + var rlen = binsig[index]; + var r = binsig.slice(index + 1, index + 1 + rlen); + var slen = binsig[index + 1 + rlen + 1]; // skip header and read length + var s = binsig.slice(index + 1 + rlen + 1 + 1); + if (slen !== s.byteLength) { return binsig; } + // There may be one byte of padding on either + if (33 === r.byteLength) { r = r.slice(1); } + if (33 === s.byteLength) { s = s.slice(1); } + return Buffer.concat([r, s]); + } + if (opts.pem && opts.jwk) { return sign(opts.pem); } else { diff --git a/package.json b/package.json index f001cc3..775bbca 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "keypairs", - "version": "1.2.9", + "version": "1.2.11", "description": "Lightweight RSA/ECDSA keypair generation and JWK <-> PEM", "main": "keypairs.js", "files": [ diff --git a/test.js b/test.js index 5b99f72..4da4988 100644 --- a/test.js +++ b/test.js @@ -1,6 +1,7 @@ var Keypairs = require('./'); /* global Promise*/ +console.info("This SHOULD result in an error message:"); Keypairs.parseOrGenerate({ key: '' }).then(function (pair) { // should NOT have any warning output if (!pair.private || !pair.public) {