WIP copy over node csr gen
This commit is contained in:
		
							parent
							
								
									11ca005142
								
							
						
					
					
						commit
						48507da7f4
					
				
							
								
								
									
										157
									
								
								lib/csr-ec.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										157
									
								
								lib/csr-ec.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,157 @@ | |||||||
|  | // 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 }; | ||||||
|  | }; | ||||||
							
								
								
									
										213
									
								
								lib/csr.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										213
									
								
								lib/csr.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,213 @@ | |||||||
|  | 'use strict'; | ||||||
|  | 
 | ||||||
|  | var crypto = require('crypto'); | ||||||
|  | var ASN1 = require('./asn1.js'); | ||||||
|  | var Enc = require('./encoding.js'); | ||||||
|  | var PEM = require('./pem.js'); | ||||||
|  | var X509 = require('./x509.js'); | ||||||
|  | var RSA = {}; | ||||||
|  | 
 | ||||||
|  | /*global Promise*/ | ||||||
|  | var CSR = module.exports = function rsacsr(opts) { | ||||||
|  |   // We're using a Promise here to be compatible with the browser version
 | ||||||
|  |   // which will probably use the webcrypto API for some of the conversions
 | ||||||
|  |   opts = CSR._prepare(opts); | ||||||
|  | 
 | ||||||
|  |   return CSR.create(opts).then(function (bytes) { | ||||||
|  |     return CSR._encode(opts, bytes); | ||||||
|  |   }); | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | CSR._prepare = function (opts) { | ||||||
|  |   var Rasha; | ||||||
|  |   opts = JSON.parse(JSON.stringify(opts)); | ||||||
|  |   var pem, jwk; | ||||||
|  | 
 | ||||||
|  |   // We do a bit of extra error checking for user convenience
 | ||||||
|  |   if (!opts) { throw new Error("You must pass options with key and domains to rsacsr"); } | ||||||
|  |   if (!Array.isArray(opts.domains) || 0 === opts.domains.length) { | ||||||
|  |     new Error("You must pass options.domains as a non-empty array"); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   // I need to check that 例.中国 is a valid domain name
 | ||||||
|  |   if (!opts.domains.every(function (d) { | ||||||
|  |     // allow punycode? xn--
 | ||||||
|  |     if ('string' === typeof d /*&& /\./.test(d) && !/--/.test(d)*/) { | ||||||
|  |       return true; | ||||||
|  |     } | ||||||
|  |   })) { | ||||||
|  |     throw new Error("You must pass options.domains as strings"); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   if (opts.pem) { | ||||||
|  |     pem = opts.pem; | ||||||
|  |   } else if (opts.jwk) { | ||||||
|  |     jwk = opts.jwk; | ||||||
|  |   } else { | ||||||
|  |     if (!opts.key) { | ||||||
|  |       throw new Error("You must pass options.key as a JSON web key"); | ||||||
|  |     } else if (opts.key.kty) { | ||||||
|  |       jwk = opts.key; | ||||||
|  |     } else { | ||||||
|  |       pem = opts.key; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   if (pem) { | ||||||
|  |     try { | ||||||
|  |       Rasha = require('rasha'); | ||||||
|  |     } catch(e) { | ||||||
|  |       throw new Error("Rasha.js is an optional dependency for PEM-to-JWK.\n" | ||||||
|  |         + "Install it if you'd like to use it:\n" | ||||||
|  |         + "\tnpm install --save rasha\n" | ||||||
|  |         + "Otherwise supply a jwk as the private key." | ||||||
|  |       ); | ||||||
|  |     } | ||||||
|  |     jwk = Rasha.importSync({ pem: pem }); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   opts.jwk = jwk; | ||||||
|  |   return opts; | ||||||
|  | }; | ||||||
|  | CSR.sync = function (opts) { | ||||||
|  |   opts = CSR._prepare(opts); | ||||||
|  |   var bytes = CSR.createSync(opts); | ||||||
|  |   return CSR._encode(opts, bytes); | ||||||
|  | }; | ||||||
|  | CSR._encode = function (opts, bytes) { | ||||||
|  |   if ('der' === (opts.encoding||'').toLowerCase()) { | ||||||
|  |     return bytes; | ||||||
|  |   } | ||||||
|  |   return PEM.packBlock({ | ||||||
|  |     type: "CERTIFICATE REQUEST" | ||||||
|  |   , bytes: bytes /* { jwk: jwk, domains: opts.domains } */ | ||||||
|  |   }); | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | CSR.createSync = function createCsr(opts) { | ||||||
|  |   var hex = CSR.request(opts.jwk, opts.domains); | ||||||
|  |   var csr = CSR.signSync(opts.jwk, hex); | ||||||
|  |   return Enc.hexToBuf(csr); | ||||||
|  | }; | ||||||
|  | CSR.create = function createCsr(opts) { | ||||||
|  |   var hex = CSR.request(opts.jwk, opts.domains); | ||||||
|  |   return CSR.sign(opts.jwk, hex).then(function (csr) { | ||||||
|  |     return Enc.hexToBuf(csr); | ||||||
|  |   }); | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | CSR.request = function createCsrBodyEc(jwk, domains) { | ||||||
|  |   var asn1pub = X509.packCsrPublicKey(jwk); | ||||||
|  |   return X509.packCsr(asn1pub, domains); | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | CSR.signSync = function csrEcSig(jwk, request) { | ||||||
|  |   var keypem = PEM.packBlock({ type: "RSA PRIVATE KEY", bytes: X509.packPkcs1(jwk) }); | ||||||
|  |   var sig = RSA.signSync(keypem, Enc.hexToBuf(request)); | ||||||
|  |   return CSR.toDer({ request: request, signature: sig }); | ||||||
|  | }; | ||||||
|  | CSR.sign = function csrEcSig(jwk, request) { | ||||||
|  |   var keypem = PEM.packBlock({ type: "RSA PRIVATE KEY", bytes: X509.packPkcs1(jwk) }); | ||||||
|  |   return RSA.sign(keypem, Enc.hexToBuf(request)).then(function (sig) { | ||||||
|  |     return CSR.toDer({ request: request, signature: sig }); | ||||||
|  |   }); | ||||||
|  | }; | ||||||
|  | CSR.toDer = function encode(opts) { | ||||||
|  |   var sty = ASN1('30' | ||||||
|  |     // 1.2.840.113549.1.1.11 sha256WithRSAEncryption (PKCS #1)
 | ||||||
|  |   , ASN1('06', '2a864886f70d01010b') | ||||||
|  |   , ASN1('05') | ||||||
|  |   ); | ||||||
|  |   return ASN1('30' | ||||||
|  |     // The Full CSR Request Body
 | ||||||
|  |   , opts.request | ||||||
|  |     // The Signature Type
 | ||||||
|  |   , sty | ||||||
|  |     // The Signature
 | ||||||
|  |   , ASN1.BitStr(Enc.bufToHex(opts.signature)) | ||||||
|  |   ); | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | //
 | ||||||
|  | // RSA
 | ||||||
|  | //
 | ||||||
|  | 
 | ||||||
|  | // Took some tips from https://gist.github.com/codermapuche/da4f96cdb6d5ff53b7ebc156ec46a10a
 | ||||||
|  | RSA.signSync = function signRsaSync(keypem, ab) { | ||||||
|  |   // Signer is a stream
 | ||||||
|  |   var sign = crypto.createSign('SHA256'); | ||||||
|  |   sign.write(new Uint8Array(ab)); | ||||||
|  |   sign.end(); | ||||||
|  | 
 | ||||||
|  |   // The signature is ASN1 encoded, as it turns out
 | ||||||
|  |   var sig = sign.sign(keypem); | ||||||
|  | 
 | ||||||
|  |   // Convert to a JavaScript ArrayBuffer just because
 | ||||||
|  |   return new Uint8Array(sig.buffer.slice(sig.byteOffset, sig.byteOffset + sig.byteLength)); | ||||||
|  | }; | ||||||
|  | RSA.sign = function signRsa(keypem, ab) { | ||||||
|  |   return Promise.resolve().then(function () { | ||||||
|  |     return RSA.signSync(keypem, ab); | ||||||
|  |   }); | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | X509.packCsrRsa = function (asn1pubkey, domains) { | ||||||
|  |   return ASN1('30' | ||||||
|  |     // Version (0)
 | ||||||
|  |   , ASN1.UInt('00') | ||||||
|  | 
 | ||||||
|  |     // 2.5.4.3 commonName (X.520 DN component)
 | ||||||
|  |   , ASN1('30', ASN1('31', ASN1('30', ASN1('06', '550403'), ASN1('0c', Enc.utf8ToHex(domains[0]))))) | ||||||
|  | 
 | ||||||
|  |     // Public Key (RSA or EC)
 | ||||||
|  |   , asn1pubkey | ||||||
|  | 
 | ||||||
|  |     // Request Body
 | ||||||
|  |   , ASN1('a0' | ||||||
|  |     , ASN1('30' | ||||||
|  |         // 1.2.840.113549.1.9.14 extensionRequest (PKCS #9 via CRMF)
 | ||||||
|  |       , ASN1('06', '2a864886f70d01090e') | ||||||
|  |       , ASN1('31' | ||||||
|  |         , ASN1('30' | ||||||
|  |           , ASN1('30' | ||||||
|  |               // 2.5.29.17 subjectAltName (X.509 extension)
 | ||||||
|  |             , ASN1('06', '551d11') | ||||||
|  |             , ASN1('04' | ||||||
|  |               , ASN1('30', domains.map(function (d) { | ||||||
|  |                   return ASN1('82', Enc.utf8ToHex(d)); | ||||||
|  |                 }).join('')))))))) | ||||||
|  |   ); | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | X509.packPkcs1 = function (jwk) { | ||||||
|  |   var n = ASN1.UInt(Enc.base64ToHex(jwk.n)); | ||||||
|  |   var e = ASN1.UInt(Enc.base64ToHex(jwk.e)); | ||||||
|  | 
 | ||||||
|  |   if (!jwk.d) { | ||||||
|  |     return Enc.hexToBuf(ASN1('30', n, e)); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   return Enc.hexToBuf(ASN1('30' | ||||||
|  |   , ASN1.UInt('00') | ||||||
|  |   , n | ||||||
|  |   , e | ||||||
|  |   , ASN1.UInt(Enc.base64ToHex(jwk.d)) | ||||||
|  |   , ASN1.UInt(Enc.base64ToHex(jwk.p)) | ||||||
|  |   , ASN1.UInt(Enc.base64ToHex(jwk.q)) | ||||||
|  |   , ASN1.UInt(Enc.base64ToHex(jwk.dp)) | ||||||
|  |   , ASN1.UInt(Enc.base64ToHex(jwk.dq)) | ||||||
|  |   , ASN1.UInt(Enc.base64ToHex(jwk.qi)) | ||||||
|  |   )); | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | X509.packCsrRsaPublicKey = function (jwk) { | ||||||
|  |   // Sequence the key
 | ||||||
|  |   var n = ASN1.UInt(Enc.base64ToHex(jwk.n)); | ||||||
|  |   var e = ASN1.UInt(Enc.base64ToHex(jwk.e)); | ||||||
|  |   var asn1pub = ASN1('30', n, e); | ||||||
|  |   //var asn1pub = X509.packPkcs1({ kty: jwk.kty, n: jwk.n, e: jwk.e });
 | ||||||
|  | 
 | ||||||
|  |   // Add the CSR pub key header
 | ||||||
|  |   return ASN1('30', ASN1('30', ASN1('06', '2a864886f70d010101'), ASN1('05')), ASN1.BitStr(asn1pub)); | ||||||
|  | }; | ||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user