also parse CSR for info
This commit is contained in:
		
							parent
							
								
									9a89e43263
								
							
						
					
					
						commit
						266d8a0ba0
					
				
							
								
								
									
										9
									
								
								app.js
									
									
									
									
									
								
							
							
						
						
									
										9
									
								
								app.js
									
									
									
									
									
								
							| @ -151,10 +151,13 @@ | |||||||
|       ev.stopPropagation(); |       ev.stopPropagation(); | ||||||
|       var domains = ($('.js-domains').value||'example.com').split(/[, ]+/g); |       var domains = ($('.js-domains').value||'example.com').split(/[, ]+/g); | ||||||
|       var privJwk = JSON.parse($('.js-jwk').innerText).private; |       var privJwk = JSON.parse($('.js-jwk').innerText).private; | ||||||
|       return CSR({ jwk: privJwk, domains: domains }).then(function (web64) { |       return CSR({ jwk: privJwk, domains: domains }).then(function (pem) { | ||||||
|         // Verify with https://www.sslshopper.com/csr-decoder.html
 |         // Verify with https://www.sslshopper.com/csr-decoder.html
 | ||||||
|         console.log('urlBase64 CSR:'); |         console.log('CSR:'); | ||||||
|         console.log(web64); |         console.log(pem); | ||||||
|  | 
 | ||||||
|  |         console.log('CSR info:'); | ||||||
|  |         console.log(CSR._info(pem)); | ||||||
|       }); |       }); | ||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -128,6 +128,7 @@ | |||||||
|     <script src="./lib/ecdsa.js"></script> |     <script src="./lib/ecdsa.js"></script> | ||||||
|     <script src="./lib/rsa.js"></script> |     <script src="./lib/rsa.js"></script> | ||||||
|     <script src="./lib/keypairs.js"></script> |     <script src="./lib/keypairs.js"></script> | ||||||
|  |     <script src="./lib/asn1-parser.js"></script> | ||||||
|     <script src="./lib/csr.js"></script> |     <script src="./lib/csr.js"></script> | ||||||
|     <script src="./lib/acme.js"></script> |     <script src="./lib/acme.js"></script> | ||||||
|     <script src="./app.js"></script> |     <script src="./app.js"></script> | ||||||
|  | |||||||
							
								
								
									
										14
									
								
								lib/acme.js
									
									
									
									
									
								
							
							
						
						
									
										14
									
								
								lib/acme.js
									
									
									
									
									
								
							| @ -8,6 +8,7 @@ | |||||||
| 
 | 
 | ||||||
| var ACME = exports.ACME = {}; | var ACME = exports.ACME = {}; | ||||||
| //var Keypairs = exports.Keypairs || {};
 | //var Keypairs = exports.Keypairs || {};
 | ||||||
|  | //var CSR = exports.CSR;
 | ||||||
| var Enc = exports.Enc || {}; | var Enc = exports.Enc || {}; | ||||||
| var Crypto = exports.Crypto || {}; | var Crypto = exports.Crypto || {}; | ||||||
| 
 | 
 | ||||||
| @ -670,6 +671,14 @@ ACME._getCertificate = function (me, options) { | |||||||
|     return Promise.reject(new Error("options.challengeTypes (string array) must be specified" |     return Promise.reject(new Error("options.challengeTypes (string array) must be specified" | ||||||
|       + " (and in order of preferential priority).")); |       + " (and in order of preferential priority).")); | ||||||
|   } |   } | ||||||
|  |   if (options.csr) { | ||||||
|  |     // TODO validate csr signature
 | ||||||
|  |     options._csr = me.CSR._info(options.csr); | ||||||
|  |     options.domains = options._csr.altnames; | ||||||
|  |     if (options._csr.subject !== options.domains[0]) { | ||||||
|  |       return Promise.reject(new Error("certificate subject (commonName) does not match first altname (SAN)")); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|   if (!(options.domains && options.domains.length)) { |   if (!(options.domains && options.domains.length)) { | ||||||
|     return Promise.reject(new Error("options.domains must be a list of string domain names," |     return Promise.reject(new Error("options.domains must be a list of string domain names," | ||||||
|     + " with the first being the subject of the certificate (or options.subject must specified).")); |     + " with the first being the subject of the certificate (or options.subject must specified).")); | ||||||
| @ -818,8 +827,8 @@ ACME._getCertificate = function (me, options) { | |||||||
|   }); |   }); | ||||||
| }; | }; | ||||||
| ACME._generateCsrWeb64 = function (me, options, validatedDomains) { | ACME._generateCsrWeb64 = function (me, options, validatedDomains) { | ||||||
|   return ACME._importKeypair(me, options.domainKeypair).then(function (/*pair*/) { |   return ACME._importKeypair(me, options.domainKeypair).then(function (pair) { | ||||||
|     return me.Keypairs.generateCsr(options.domainKeypair, validatedDomains).then(function (der) { |     return me.CSR({ jwk: pair.private, domains: validatedDomains, encoding: 'der' }).then(function (der) { | ||||||
|       return Enc.bufToUrlBase64(der); |       return Enc.bufToUrlBase64(der); | ||||||
|     }); |     }); | ||||||
|   }); |   }); | ||||||
| @ -830,6 +839,7 @@ ACME.create = function create(me) { | |||||||
|   // me.debug = true;
 |   // me.debug = true;
 | ||||||
|   me.challengePrefixes = ACME.challengePrefixes; |   me.challengePrefixes = ACME.challengePrefixes; | ||||||
|   me.Keypairs = me.Keypairs || me.RSA || require('rsa-compat').RSA; |   me.Keypairs = me.Keypairs || me.RSA || require('rsa-compat').RSA; | ||||||
|  |   me.CSR = me.CSR || require('CSR').CSR; | ||||||
|   me._nonces = []; |   me._nonces = []; | ||||||
|   me._canCheck = {}; |   me._canCheck = {}; | ||||||
|   if (!me._baseUrl) { |   if (!me._baseUrl) { | ||||||
|  | |||||||
| @ -125,7 +125,7 @@ PEM.parseBlock = PEM.parseBlock || function (str) { | |||||||
|   var der = str.split(/\n/).filter(function (line) { |   var der = str.split(/\n/).filter(function (line) { | ||||||
|     return !/-----/.test(line); |     return !/-----/.test(line); | ||||||
|   }).join(''); |   }).join(''); | ||||||
|   return { der: Enc.base64ToBuf(der) }; |   return { bytes: Enc.base64ToBuf(der) }; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| Enc.base64ToBuf = function (b64) { | Enc.base64ToBuf = function (b64) { | ||||||
|  | |||||||
							
								
								
									
										102
									
								
								lib/csr.js
									
									
									
									
									
								
							
							
						
						
									
										102
									
								
								lib/csr.js
									
									
									
									
									
								
							| @ -155,6 +155,100 @@ X509.packCsr = function (asn1pubkey, domains) { | |||||||
|   ); |   ); | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
|  | // TODO finish this later
 | ||||||
|  | // we want to parse the domains, the public key, and verify the signature
 | ||||||
|  | CSR._info = function (der) { | ||||||
|  |   // standard base64 PEM
 | ||||||
|  |   if ('string' === typeof der && '-' === der[0]) { | ||||||
|  |     der = PEM.parseBlock(der).bytes; | ||||||
|  |   } | ||||||
|  |   // jose urlBase64 not-PEM
 | ||||||
|  |   if ('string' === typeof der) { | ||||||
|  |     der = Enc.base64ToBuf(der); | ||||||
|  |   } | ||||||
|  |   // not supporting binary-encoded bas64
 | ||||||
|  |   var c = ASN1.parse(der); | ||||||
|  |   var kty; | ||||||
|  |   // A cert has 3 parts: cert, signature meta, signature
 | ||||||
|  |   if (c.children.length !== 3) { | ||||||
|  |     throw new Error("doesn't look like a certificate request: expected 3 parts of header"); | ||||||
|  |   } | ||||||
|  |   var sig = c.children[2]; | ||||||
|  |   if (sig.children.length) { | ||||||
|  |     // ASN1/X509 EC
 | ||||||
|  |     sig = sig.children[0]; | ||||||
|  |     sig = ASN1('30', ASN1.UInt(Enc.bufToHex(sig.children[0].value)), ASN1.UInt(Enc.bufToHex(sig.children[1].value))); | ||||||
|  |     sig = Enc.hexToBuf(sig); | ||||||
|  |     kty = 'EC'; | ||||||
|  |   } else { | ||||||
|  |     // Raw RSA Sig
 | ||||||
|  |     sig = sig.value; | ||||||
|  |     kty = 'RSA'; | ||||||
|  |   } | ||||||
|  |   //c.children[1]; // signature type
 | ||||||
|  |   var req = c.children[0]; | ||||||
|  |   // TODO utf8
 | ||||||
|  |   if (4 !== req.children.length) { | ||||||
|  |     throw new Error("doesn't look like a certificate request: expected 4 parts to request"); | ||||||
|  |   } | ||||||
|  |   // 0 null
 | ||||||
|  |   // 1 commonName / subject
 | ||||||
|  |   var sub = Enc.bufToBin(req.children[1].children[0].children[0].children[1].value); | ||||||
|  |   // 3 public key (type, key)
 | ||||||
|  |   //console.log('oid', Enc.bufToHex(req.children[2].children[0].children[0].value));
 | ||||||
|  |   var pub; | ||||||
|  |   // TODO reuse ASN1 parser for these?
 | ||||||
|  |   if ('EC' === kty) { | ||||||
|  |     // throw away compression byte
 | ||||||
|  |     pub = req.children[2].children[1].value.slice(1); | ||||||
|  |     pub = { kty: kty, x: pub.slice(0, 32), y: pub.slice(32) }; | ||||||
|  |     while (0 === pub.x[0]) { pub.x = pub.x.slice(1); } | ||||||
|  |     while (0 === pub.y[0]) { pub.y = pub.y.slice(1); } | ||||||
|  |     if ((pub.x.length || pub.x.byteLength) > 48) { | ||||||
|  |       pub.crv = 'P-521'; | ||||||
|  |     } else if ((pub.x.length || pub.x.byteLength) > 32) { | ||||||
|  |       pub.crv = 'P-384'; | ||||||
|  |     } else { | ||||||
|  |       pub.crv = 'P-256'; | ||||||
|  |     } | ||||||
|  |     pub.x = Enc.bufToUrlBase64(pub.x); | ||||||
|  |     pub.y = Enc.bufToUrlBase64(pub.y); | ||||||
|  |   } else { | ||||||
|  |     pub = req.children[2].children[1].children[0]; | ||||||
|  |     pub = { kty: kty, n: pub.children[0].value, e: pub.children[1].value }; | ||||||
|  |     while (0 === pub.n[0]) { pub.n = pub.n.slice(1); } | ||||||
|  |     while (0 === pub.e[0]) { pub.e = pub.e.slice(1); } | ||||||
|  |     pub.n = Enc.bufToUrlBase64(pub.n); | ||||||
|  |     pub.e = Enc.bufToUrlBase64(pub.e); | ||||||
|  |   } | ||||||
|  |   // 4 extensions
 | ||||||
|  |   var domains = req.children[3].children.filter(function (seq) { | ||||||
|  |     //  1.2.840.113549.1.9.14 extensionRequest (PKCS #9 via CRMF)
 | ||||||
|  |     if ('2a864886f70d01090e' === Enc.bufToHex(seq.children[0].value)) { | ||||||
|  |       return true; | ||||||
|  |     } | ||||||
|  |   }).map(function (seq) { | ||||||
|  |     return seq.children[1].children[0].children.filter(function (seq2) { | ||||||
|  |       // subjectAltName (X.509 extension)
 | ||||||
|  |       if ('551d11' === Enc.bufToHex(seq2.children[0].value)) { | ||||||
|  |         return true; | ||||||
|  |       } | ||||||
|  |     }).map(function (seq2) { | ||||||
|  |       return seq2.children[1].children[0].children.map(function (name) { | ||||||
|  |         // TODO utf8
 | ||||||
|  |         return Enc.bufToBin(name.value); | ||||||
|  |       }); | ||||||
|  |     }); | ||||||
|  |   })[0]; | ||||||
|  | 
 | ||||||
|  |   return { | ||||||
|  |     subject: sub | ||||||
|  |   , altnames: domains | ||||||
|  |   , jwk: pub | ||||||
|  |   , signature: sig | ||||||
|  |   }; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
| X509.packCsrRsaPublicKey = function (jwk) { | X509.packCsrRsaPublicKey = function (jwk) { | ||||||
|   // Sequence the key
 |   // Sequence the key
 | ||||||
|   var n = ASN1.UInt(Enc.base64ToHex(jwk.n)); |   var n = ASN1.UInt(Enc.base64ToHex(jwk.n)); | ||||||
| @ -193,4 +287,12 @@ X509._oids = { | |||||||
| //, 'P-521': '2B 81 04 00 23'
 | //, 'P-521': '2B 81 04 00 23'
 | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
|  | // don't replace the full parseBlock, if it exists
 | ||||||
|  | PEM.parseBlock = PEM.parseBlock || function (str) { | ||||||
|  |   var der = str.split(/\n/).filter(function (line) { | ||||||
|  |     return !/-----/.test(line); | ||||||
|  |   }).join(''); | ||||||
|  |   return { bytes: Enc.base64ToBuf(der) }; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
| }('undefined' === typeof window ? module.exports : window)); | }('undefined' === typeof window ? module.exports : window)); | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user