158 lines
		
	
	
		
			4.9 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			158 lines
		
	
	
		
			4.9 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| // 1.2.840.10045.3.1.7
 | |
| // prime256v1 (ANSI X9.62 named elliptic curve)
 | |
| var OBJ_ID_EC  = '06 08 2A8648CE3D030107'.replace(/\s+/g, '').toLowerCase();
 | |
| // 1.3.132.0.34
 | |
| // secp384r1 (SECG (Certicom) named elliptic curve)
 | |
| var OBJ_ID_EC_384 = '06 05 2B81040022'.replace(/\s+/g, '').toLowerCase();
 | |
| 
 | |
| var ECDSACSR = {};
 | |
| var ECDSA = {};
 | |
| var DER = {};
 | |
| var PEM = {};
 | |
| var ASN1;
 | |
| var Hex = {};
 | |
| var AB = {};
 | |
| 
 | |
| //
 | |
| // CSR - the main event
 | |
| //
 | |
| 
 | |
| ECDSACSR.create = function createEcCsr(keypem, domains) {
 | |
|   var pemblock = PEM.parseBlock(keypem);
 | |
|   var ecpub = PEM.parseEcPubkey(pemblock.der);
 | |
|   var request = ECDSACSR.request(ecpub, domains);
 | |
|   return AB.fromHex(ECDSACSR.sign(keypem, request));
 | |
| };
 | |
| 
 | |
| ECDSACSR.request = function createCsrBodyEc(xy, domains) {
 | |
|   var publen = xy.x.byteLength;
 | |
|   var compression = '04';
 | |
|   var hxy = '';
 | |
|   // 04 == x+y, 02 == x-only
 | |
|   if (xy.y) {
 | |
|     publen += xy.y.byteLength;
 | |
|   } else {
 | |
|     // Note: I don't intend to support compression - it isn't used by most
 | |
|     // libraries and it requir more dependencies for bigint ops to deflate.
 | |
|     // This is more just a placeholder. It won't work right now anyway
 | |
|     // because compression requires an exta bit stored (odd vs even), which
 | |
|     // I haven't learned yet, and I'm not sure if it's allowed at all
 | |
|     compression = '02';
 | |
|   }
 | |
|   hxy += Hex.fromAB(xy.x);
 | |
|   if (xy.y) { hxy += Hex.fromAB(xy.y); }
 | |
| 
 | |
|   // Sorry for the mess, but it is what it is
 | |
|   return ASN1('30'
 | |
| 
 | |
|       // Version (0)
 | |
|     , ASN1.UInt('00')
 | |
| 
 | |
|       // CN / Subject
 | |
|     , ASN1('30'
 | |
|       , ASN1('31'
 | |
|         , ASN1('30'
 | |
|             // object id (commonName)
 | |
|           , ASN1('06', '55 04 03')
 | |
|           , ASN1('0C', Hex.fromString(domains[0])))))
 | |
| 
 | |
|       // EC P-256 Public Key
 | |
|     , ASN1('30'
 | |
|       , ASN1('30'
 | |
|           // 1.2.840.10045.2.1 ecPublicKey
 | |
|           // (ANSI X9.62 public key type)
 | |
|         , ASN1('06', '2A 86 48 CE 3D 02 01')
 | |
|           // 1.2.840.10045.3.1.7 prime256v1
 | |
|           // (ANSI X9.62 named elliptic curve)
 | |
|         , ASN1('06', '2A 86 48 CE 3D 03 01 07')
 | |
|         )
 | |
|       , ASN1.BitStr(compression + hxy))
 | |
| 
 | |
|       // CSR Extension Subject Alternative Names
 | |
|     , ASN1('A0'
 | |
|       , ASN1('30'
 | |
|           // (extensionRequest (PKCS #9 via CRMF))
 | |
|         , ASN1('06', '2A 86 48 86 F7 0D 01 09 0E')
 | |
|         , ASN1('31'
 | |
|           , ASN1('30'
 | |
|             , ASN1('30'
 | |
|                 // (subjectAltName (X.509 extension))
 | |
|               , ASN1('06', '55 1D 11')
 | |
|               , ASN1('04'
 | |
|                 , ASN1('30', domains.map(function (d) {
 | |
|                     return ASN1('82', Hex.fromString(d));
 | |
|                   }).join(''))))))))
 | |
|   );
 | |
| };
 | |
| 
 | |
| ECDSACSR.sign = function csrEcSig(keypem, request) {
 | |
|   var sig = ECDSA.sign(keypem, AB.fromHex(request));
 | |
|   var rLen = sig.r.byteLength;
 | |
|   var rc = '';
 | |
|   var sLen = sig.s.byteLength;
 | |
|   var sc = '';
 | |
| 
 | |
|   if (0x80 & new Uint8Array(sig.r)[0]) { rc = '00'; rLen += 1; }
 | |
|   if (0x80 & new Uint8Array(sig.s)[0]) { sc = '00'; sLen += 1; }
 | |
| 
 | |
|   return ASN1('30'
 | |
|       // The Full CSR Request Body
 | |
|     , request
 | |
| 
 | |
|       // The Signature Type
 | |
|     , ASN1('30'
 | |
|         // 1.2.840.10045.4.3.2 ecdsaWithSHA256
 | |
|         // (ANSI X9.62 ECDSA algorithm with SHA256)
 | |
|       , ASN1('06', '2A 86 48 CE 3D 04 03 02')
 | |
|       )
 | |
| 
 | |
|       // The Signature, embedded in a Bit Stream
 | |
|     , ASN1.BitStr(
 | |
|         // As far as I can tell this is a completely separate ASN.1 structure
 | |
|         // that just so happens to be embedded in a Bit String of another ASN.1
 | |
|         ASN1('30'
 | |
|         , ASN1.UInt(Hex.fromAB(sig.r))
 | |
|         , ASN1.UInt(Hex.fromAB(sig.s))))
 | |
|   );
 | |
| };
 | |
| 
 | |
| //
 | |
| // ECDSA
 | |
| //
 | |
| 
 | |
| // Took some tips from https://gist.github.com/codermapuche/da4f96cdb6d5ff53b7ebc156ec46a10a
 | |
| ECDSA.sign = function signEc(keypem, ab) {
 | |
|   // Signer is a stream
 | |
|   var sign = crypto.createSign('SHA256');
 | |
|   sign.write(new Uint8Array(ab));
 | |
|   sign.end();
 | |
| 
 | |
|   // The signature is ASN1 encoded
 | |
|   var sig = sign.sign(keypem);
 | |
| 
 | |
|   // Convert to a JavaScript ArrayBuffer just because
 | |
|   sig = new Uint8Array(sig.buffer.slice(sig.byteOffset, sig.byteOffset + sig.byteLength));
 | |
| 
 | |
|   // The first two bytes '30 xx' signify SEQUENCE and LENGTH
 | |
|   // The sequence length byte will be a single byte because the signature is less that 128 bytes (0x80, 1024-bit)
 | |
|   // (this would not be true for P-521, but I'm not supporting that yet)
 | |
|   // The 3rd byte will be '02', signifying INTEGER
 | |
|   // The 4th byte will tell us the length of 'r' (which, on occassion, will be less than the full 255 bytes)
 | |
|   var rIndex = 3;
 | |
|   var rLen = sig[rIndex];
 | |
|   var rEnd = rIndex + 1 + rLen;
 | |
|   var sIndex = rEnd + 1;
 | |
|   var sLen = sig[sIndex];
 | |
|   var sEnd = sIndex + 1 + sLen;
 | |
|   var r = sig.slice(rIndex + 1, rEnd);
 | |
|   var s = sig.slice(sIndex + 1, sEnd); // this should be end-of-file
 | |
| 
 | |
|   // ASN1 INTEGER types use the high-order bit to signify a negative number,
 | |
|   // hence a leading '00' is used for numbers that begin with '80' or greater
 | |
|   // which is why r length is sometimes a byte longer than its bit length
 | |
|   if (0 === s[0]) { s = s.slice(1); }
 | |
|   if (0 === r[0]) { r = r.slice(1); }
 | |
| 
 | |
|   return { raw: sig.buffer, r: r.buffer, s: s.buffer };
 | |
| };
 |