| 
									
										
										
										
											2019-10-04 17:35:59 -06:00
										 |  |  | 'use strict'; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | var Keypairs = module.exports; | 
					
						
							|  |  |  | var crypto = require('crypto'); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | Keypairs._sign = function(opts, payload) { | 
					
						
							|  |  |  | 	return Keypairs._import(opts).then(function(pem) { | 
					
						
							|  |  |  | 		payload = Buffer.from(payload); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// node specifies RSA-SHAxxx even when it's actually ecdsa (it's all encoded x509 shasums anyway)
 | 
					
						
							|  |  |  | 		// TODO opts.alg = (protect||header).alg
 | 
					
						
							|  |  |  | 		var nodeAlg = 'SHA' + Keypairs._getBits(opts); | 
					
						
							|  |  |  | 		var binsig = crypto | 
					
						
							|  |  |  | 			.createSign(nodeAlg) | 
					
						
							|  |  |  | 			.update(payload) | 
					
						
							|  |  |  | 			.sign(pem); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-10-05 05:21:07 -06:00
										 |  |  | 		if ('EC' === opts.jwk.kty && !/x509|asn1/i.test(opts.format)) { | 
					
						
							| 
									
										
										
										
											2019-10-04 17:35:59 -06:00
										 |  |  | 			// ECDSA JWT signatures differ from "normal" ECDSA signatures
 | 
					
						
							|  |  |  | 			// https://tools.ietf.org/html/rfc7518#section-3.4
 | 
					
						
							|  |  |  | 			binsig = Keypairs._ecdsaAsn1SigToJoseSig(binsig); | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		return binsig; | 
					
						
							|  |  |  | 	}); | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | Keypairs._import = function(opts) { | 
					
						
							|  |  |  | 	if (opts.pem && opts.jwk) { | 
					
						
							|  |  |  | 		return Promise.resolve(opts.pem); | 
					
						
							|  |  |  | 	} else { | 
					
						
							|  |  |  | 		// XXX added by caller
 | 
					
						
							|  |  |  | 		return Keypairs.export({ jwk: opts.jwk }); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | Keypairs._ecdsaAsn1SigToJoseSig = function(binsig) { | 
					
						
							|  |  |  | 	// should have asn1 sequence header of 0x30
 | 
					
						
							|  |  |  | 	if (0x30 !== binsig[0]) { | 
					
						
							|  |  |  | 		throw new Error('Impossible EC SHA head marker'); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	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; // should be exactly 1
 | 
					
						
							|  |  |  | 		len = binsig[2]; // should be <= 130 (two 64-bit SHA-512s, plus padding)
 | 
					
						
							|  |  |  | 		index += lenlen; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	// should be of BigInt type
 | 
					
						
							|  |  |  | 	if (0x02 !== binsig[index]) { | 
					
						
							|  |  |  | 		throw new Error('Impossible EC SHA R marker'); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	index += 1; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	var rlen = binsig[index]; | 
					
						
							|  |  |  | 	var bits = 32; | 
					
						
							|  |  |  | 	if (rlen > 49) { | 
					
						
							|  |  |  | 		bits = 64; | 
					
						
							|  |  |  | 	} else if (rlen > 33) { | 
					
						
							|  |  |  | 		bits = 48; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	var r = binsig.slice(index + 1, index + 1 + rlen).toString('hex'); | 
					
						
							|  |  |  | 	var slen = binsig[index + 1 + rlen + 1]; // skip header and read length
 | 
					
						
							|  |  |  | 	var s = binsig.slice(index + 1 + rlen + 1 + 1).toString('hex'); | 
					
						
							|  |  |  | 	if (2 * slen !== s.length) { | 
					
						
							|  |  |  | 		throw new Error('Impossible EC SHA S length'); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	// There may be one byte of padding on either
 | 
					
						
							|  |  |  | 	while (r.length < 2 * bits) { | 
					
						
							|  |  |  | 		r = '00' + r; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	while (s.length < 2 * bits) { | 
					
						
							|  |  |  | 		s = '00' + s; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	if (2 * (bits + 1) === r.length) { | 
					
						
							|  |  |  | 		r = r.slice(2); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	if (2 * (bits + 1) === s.length) { | 
					
						
							|  |  |  | 		s = s.slice(2); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return Buffer.concat([Buffer.from(r, 'hex'), Buffer.from(s, 'hex')]); | 
					
						
							|  |  |  | }; |