separating concerns
This commit is contained in:
		
							parent
							
								
									31b25af1cb
								
							
						
					
					
						commit
						89ef517338
					
				
							
								
								
									
										77
									
								
								lib/account-old.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										77
									
								
								lib/account-old.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,77 @@ | |||||||
|  |   options.newReg=options.newReg || 'https://acme-v01.api.letsencrypt.org/acme/new-reg'; | ||||||
|  | 
 | ||||||
|  |   if (!options.email) { | ||||||
|  |     return cb(new Error('No "email" option specified!')); | ||||||
|  |   } | ||||||
|  |   if (typeof options.domains==='string') { | ||||||
|  |     state.domains=options.domains.split(/[, ]+/); | ||||||
|  |   } else if (options.domains && options.domains instanceof Array) { | ||||||
|  |     state.domains=options.domains.slice(); | ||||||
|  |   } else { | ||||||
|  |     return cb(new Error('No valid "domains" option specified!')); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   if ((_DEBUG=options.debug)) { | ||||||
|  |     if (!''.green) { | ||||||
|  |       require('colors'); | ||||||
|  |     } | ||||||
|  |     log=console.log.bind(console); | ||||||
|  |   } else { | ||||||
|  |     log=NOOP; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   if (options.fork && !~process.argv.indexOf('--letiny-fork')) { | ||||||
|  |     state.child=child.fork(__filename, ['--letiny-fork']); | ||||||
|  |     if (options.challenge) { | ||||||
|  |       return cb(new Error('fork+challenge not supported yet')); | ||||||
|  |     } | ||||||
|  |     state.child.send({request:options}); | ||||||
|  |     state.child.on('message', function(msg) { | ||||||
|  |       var res; | ||||||
|  |       if (msg.result) { | ||||||
|  |         res=msg.result; | ||||||
|  |         cb(res.err ? new Error(res.err) : null, res.cert, res.key, res.ca); | ||||||
|  |       } | ||||||
|  |     }); | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   if (options.accountKey) { | ||||||
|  |     if (options.accountKey.length>255) { | ||||||
|  |       state.accountKeyPEM=options.accountKey; | ||||||
|  |     } else { | ||||||
|  |       try { | ||||||
|  |         state.accountKeyPEM=fs.readFileSync(options.accountKey); | ||||||
|  |       } catch(err) { | ||||||
|  |         if (err.code==='ENOENT') { | ||||||
|  |           makeAccountKeyPair(true); | ||||||
|  |         } else { | ||||||
|  |           return handleErr(err, 'Failed to load accountKey'); | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |       try { | ||||||
|  |         state.accountKeyPair=cryptoUtil.importPemPrivateKey(state.accountKeyPEM); | ||||||
|  |       } catch(err) { | ||||||
|  |         return handleErr(err, 'Failed to parse accountKey'); | ||||||
|  |       } | ||||||
|  |       initAcme(); | ||||||
|  |     } | ||||||
|  |   } else { | ||||||
|  |     makeAccountKeyPair(); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   function makeAccountKeyPair(save) { | ||||||
|  |     var keypair; | ||||||
|  |     log('Generating account keypair...'); | ||||||
|  |     keypair=pki.rsa.generateKeyPair(2048); | ||||||
|  |     state.accountKeyPEM=pki.privateKeyToPem(keypair.privateKey); | ||||||
|  |     state.accountKeyPair=cryptoUtil.importPemPrivateKey(state.accountKeyPEM); | ||||||
|  |     if (save) { | ||||||
|  |       try { | ||||||
|  |         fs.writeFileSync(options.accountKey, state.accountKeyPEM); | ||||||
|  |       } catch(err) { | ||||||
|  |         return handleErr(err, 'Failed to save accountKey'); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |     initAcme(); | ||||||
|  |   } | ||||||
							
								
								
									
										243
									
								
								lib/client.js
									
									
									
									
									
								
							
							
						
						
									
										243
									
								
								lib/client.js
									
									
									
									
									
								
							| @ -103,114 +103,24 @@ Acme.prototype.post=function(url, body, cb) { | |||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| function getCert(options, cb) { | function registerNewAccount(state, options, cb) { | ||||||
|   var state={ |   if (!options.agreeToTerms) { | ||||||
|     validatedDomains:[], |     cb(new Error("options.agreeToTerms must be function (tosUrl, fn => (err, true))")); | ||||||
|     validAuthorizationURLs:[] |     return; | ||||||
|   }; |   } | ||||||
| 
 |   if (!options.newReg) { | ||||||
|   options.newReg=options.newReg || 'https://acme-v01.api.letsencrypt.org/acme/new-reg'; |     cb(new Error("options.newReg must be the a new registration url")); | ||||||
| 
 |     return; | ||||||
|  |   } | ||||||
|   if (!options.email) { |   if (!options.email) { | ||||||
|     return cb(new Error('No "email" option specified!')); |     cb(new Error("options.email must be an email")); | ||||||
|   } |  | ||||||
|   if (typeof options.domains==='string') { |  | ||||||
|     state.domains=options.domains.split(/[, ]+/); |  | ||||||
|   } else if (options.domains && options.domains instanceof Array) { |  | ||||||
|     state.domains=options.domains.slice(); |  | ||||||
|   } else { |  | ||||||
|     return cb(new Error('No valid "domains" option specified!')); |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   if ((_DEBUG=options.debug)) { |  | ||||||
|     if (!''.green) { |  | ||||||
|       require('colors'); |  | ||||||
|     } |  | ||||||
|     log=console.log.bind(console); |  | ||||||
|   } else { |  | ||||||
|     log=NOOP; |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   if (options.fork && !~process.argv.indexOf('--letiny-fork')) { |  | ||||||
|     state.child=child.fork(__filename, ['--letiny-fork']); |  | ||||||
|     if (options.challenge) { |  | ||||||
|       return cb(new Error('fork+challenge not supported yet')); |  | ||||||
|     } |  | ||||||
|     state.child.send({request:options}); |  | ||||||
|     state.child.on('message', function(msg) { |  | ||||||
|       var res; |  | ||||||
|       if (msg.result) { |  | ||||||
|         res=msg.result; |  | ||||||
|         cb(res.err ? new Error(res.err) : null, res.cert, res.key, res.ca); |  | ||||||
|       } |  | ||||||
|     }); |  | ||||||
|     return; |     return; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   if (options.accountKey) { |  | ||||||
|     if (options.accountKey.length>255) { |  | ||||||
|       state.accountKeyPEM=options.accountKey; |  | ||||||
|     } else { |  | ||||||
|       try { |  | ||||||
|         state.accountKeyPEM=fs.readFileSync(options.accountKey); |  | ||||||
|       } catch(err) { |  | ||||||
|         if (err.code==='ENOENT') { |  | ||||||
|           makeAccountKeyPair(true); |  | ||||||
|         } else { |  | ||||||
|           return handleErr(err, 'Failed to load accountKey'); |  | ||||||
|         } |  | ||||||
|       } |  | ||||||
|       try { |  | ||||||
|         state.accountKeyPair=cryptoUtil.importPemPrivateKey(state.accountKeyPEM); |  | ||||||
|       } catch(err) { |  | ||||||
|         return handleErr(err, 'Failed to parse accountKey'); |  | ||||||
|       } |  | ||||||
|       initAcme(); |  | ||||||
|     } |  | ||||||
|   } else { |  | ||||||
|     makeAccountKeyPair(); |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   function makeAccountKeyPair(save) { |  | ||||||
|     var keypair; |  | ||||||
|     log('Generating account keypair...'); |  | ||||||
|     keypair=pki.rsa.generateKeyPair(2048); |  | ||||||
|     state.accountKeyPEM=pki.privateKeyToPem(keypair.privateKey); |  | ||||||
|     state.accountKeyPair=cryptoUtil.importPemPrivateKey(state.accountKeyPEM); |  | ||||||
|     if (save) { |  | ||||||
|       try { |  | ||||||
|         fs.writeFileSync(options.accountKey, state.accountKeyPEM); |  | ||||||
|       } catch(err) { |  | ||||||
|         return handleErr(err, 'Failed to save accountKey'); |  | ||||||
|       } |  | ||||||
|     } |  | ||||||
|     initAcme(); |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   function initAcme() { |  | ||||||
|     state.acme=new Acme(state.accountKeyPair); |  | ||||||
|     makeKeyPair(); |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   function makeKeyPair() { |  | ||||||
|     var keypair; |  | ||||||
|     if (options.privateKey) { |  | ||||||
|       state.certPrivateKeyPEM=options.privateKey; |  | ||||||
|     } else { |  | ||||||
|       log('Generating cert keypair...'); |  | ||||||
|       keypair=pki.rsa.generateKeyPair(2048); |  | ||||||
|       state.certPrivateKeyPEM=pki.privateKeyToPem(keypair.privateKey); |  | ||||||
|     } |  | ||||||
|     try { |  | ||||||
|       state.certPrivateKey=cryptoUtil.importPemPrivateKey(state.certPrivateKeyPEM); |  | ||||||
|     } catch(err) { |  | ||||||
|       return handleErr(err, 'Failed to parse privateKey'); |  | ||||||
|     } |  | ||||||
|   register(); |   register(); | ||||||
|   } |  | ||||||
| 
 | 
 | ||||||
|   function register() { |   function register() { | ||||||
|     post(options.newReg, { |     state.acme.post(options.newReg, { | ||||||
|       resource:'new-reg', |       resource:'new-reg', | ||||||
|       contact:['mailto:'+options.email] |       contact:['mailto:'+options.email] | ||||||
|     }, getTerms); |     }, getTerms); | ||||||
| @ -233,15 +143,29 @@ function getCert(options, cb) { | |||||||
|     state.termsRequired=('terms-of-service' in links); |     state.termsRequired=('terms-of-service' in links); | ||||||
| 
 | 
 | ||||||
|     if (state.termsRequired) { |     if (state.termsRequired) { | ||||||
|  |       state.termsURL=links['terms-of-service']; | ||||||
|  |       options.agreeToTerms({ | ||||||
|  |         tosUrl: state.termsURL | ||||||
|  |       , email: options.email | ||||||
|  |       }, function (err, agree) { | ||||||
|  |         if (err) { | ||||||
|  |           return handleErr(err); | ||||||
|  |         } | ||||||
|  |         if (!agree) { | ||||||
|  |           return handleErr(new Error("You must agree to the terms of use at '" + state.termsURL + "'")); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         state.agreeTerms = agree; | ||||||
|         state.termsURL=links['terms-of-service']; |         state.termsURL=links['terms-of-service']; | ||||||
|         log(state.termsURL); |         log(state.termsURL); | ||||||
|         request.get(state.termsURL, getAgreement); |         request.get(state.termsURL, getAgreement); | ||||||
|  |       }); | ||||||
|     } else { |     } else { | ||||||
|       getChallenges(); |       cb(); | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   function getAgreement(err, res, body) { |   function getAgreement(err/*, res, body*/) { | ||||||
|     if (err) { |     if (err) { | ||||||
|       return handleErr(err, 'Couldn\'t get agreement'); |       return handleErr(err, 'Couldn\'t get agreement'); | ||||||
|     } |     } | ||||||
| @ -250,24 +174,61 @@ function getCert(options, cb) { | |||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   function sendAgreement() { |   function sendAgreement() { | ||||||
|     if (state.termsRequired && !options.agreeTerms) { |     if (state.termsRequired && !state.agreeTerms) { | ||||||
|       return handleErr(null, 'The CA requires your agreement to terms: '+state.termsURL); |       return handleErr(null, 'The CA requires your agreement to terms: '+state.termsURL); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     log('Posting agreement to: '+state.registrationURL); |     log('Posting agreement to: '+state.registrationURL); | ||||||
| 
 | 
 | ||||||
|     post(state.registrationURL, { |     state.acme.post(state.registrationURL, { | ||||||
|       resource:'reg', |       resource:'reg', | ||||||
|       agreement:state.termsURL |       agreement:state.termsURL | ||||||
|     }, function(err, res, body) { |     }, function(err, res, body) { | ||||||
|       if (err || Math.floor(res.statusCode/100)!==2) { |       if (err || Math.floor(res.statusCode/100)!==2) { | ||||||
|         return handleErr(err, 'Couldn\'t POST agreement back to server', body); |         return handleErr(err, 'Couldn\'t POST agreement back to server', body); | ||||||
|       } else { |       } else { | ||||||
|         nextDomain(); |         cb(null, body); | ||||||
|       } |       } | ||||||
|     }); |     }); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  |   function handleErr(err, text, info) { | ||||||
|  |     log(text, err, info); | ||||||
|  |     cb(err || new Error(text)); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function getCert(options, cb) { | ||||||
|  |   var state={ | ||||||
|  |     validatedDomains:[], | ||||||
|  |     validAuthorizationURLs:[] | ||||||
|  |   }; | ||||||
|  | 
 | ||||||
|  |   if (!options.accountPrivateKeyPem) { | ||||||
|  |     return handleErr(new Error("options.accountPrivateKeyPem must be an ascii private key pem")); | ||||||
|  |   } | ||||||
|  |   if (!options.domainPrivateKeyPem) { | ||||||
|  |     return handleErr(new Error("options.domainPrivateKeyPem must be an ascii private key pem")); | ||||||
|  |   } | ||||||
|  |   if (!options.setChallenge) { | ||||||
|  |     return handleErr(new Error("options.setChallenge must be function(hostname, challengeKey, tokenValue, done) {}")); | ||||||
|  |   } | ||||||
|  |   if (!options.removeChallenge) { | ||||||
|  |     return handleErr(new Error("options.removeChallenge must be function(hostname, challengeKey, done) {}")); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   try { | ||||||
|  |     state.accountKeyPem=options.accountPrivateKeyPem; | ||||||
|  |     state.accountKeyPair=cryptoUtil.importPemPrivateKey(state.accountKeyPEM); | ||||||
|  |     state.acme=new Acme(state.accountKeyPair); | ||||||
|  |     state.certPrivateKeyPEM=options.domainPrivateKeyPem; | ||||||
|  |     state.certPrivateKey=cryptoUtil.importPemPrivateKey(state.certPrivateKeyPEM); | ||||||
|  |   } catch(err) { | ||||||
|  |     return handleErr(err, 'Failed to parse privateKey'); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   nextDomain(); | ||||||
|  | 
 | ||||||
|   function nextDomain() { |   function nextDomain() { | ||||||
|     if (state.domains.length > 0) { |     if (state.domains.length > 0) { | ||||||
|       getChallenges(state.domains.shift()); |       getChallenges(state.domains.shift()); | ||||||
| @ -280,7 +241,7 @@ function getCert(options, cb) { | |||||||
|   function getChallenges(domain) { |   function getChallenges(domain) { | ||||||
|     state.domain=domain; |     state.domain=domain; | ||||||
| 
 | 
 | ||||||
|     post(state.newAuthorizationURL, { |     state.acme.post(state.newAuthorizationURL, { | ||||||
|       resource:'new-authz', |       resource:'new-authz', | ||||||
|       identifier:{ |       identifier:{ | ||||||
|         type:'dns', |         type:'dns', | ||||||
| @ -320,29 +281,17 @@ function getCert(options, cb) { | |||||||
|     state.responseURL=challenge['uri']; |     state.responseURL=challenge['uri']; | ||||||
|     state.path=challengePath; |     state.path=challengePath; | ||||||
| 
 | 
 | ||||||
|     if (options.webroot) { |     options.setChallenge(state.domain, '/'+challengePath, keyAuthorization, challengeDone); | ||||||
|       try { |  | ||||||
|         mkdirp(path.dirname(options.webroot+'/'+challengePath)); |  | ||||||
|         fs.writeFileSync(path.normalize(options.webroot+'/'+challengePath), keyAuthorization); |  | ||||||
|         challengeDone(); |  | ||||||
|       } catch(err) { |  | ||||||
|         handleErr(err, 'Could not write challange file to disk'); |  | ||||||
|       } |  | ||||||
|     } else if (typeof options.challenge==='function') { |  | ||||||
|       options.challenge(state.domain, '/'+challengePath, keyAuthorization, challengeDone); |  | ||||||
|     } else { |  | ||||||
|       return handleErr(null, 'No "challenge" function or "webroot" option given.'); |  | ||||||
|     } |  | ||||||
| 
 | 
 | ||||||
|     function challengeDone() { |     function challengeDone() { | ||||||
|       post(state.responseURL, { |       state.acme.post(state.responseURL, { | ||||||
|         resource:'challenge', |         resource:'challenge', | ||||||
|         keyAuthorization:keyAuthorization |         keyAuthorization:keyAuthorization | ||||||
|       }, function(err, res, body) { |       }, function(err, res, body) { | ||||||
|         ensureValidation(err, res, body, function unlink() { |         ensureValidation(err, res, body, function unlink() { | ||||||
|           if (options.webroot) { |           options.removeChallenge(state.domain, '/'+challengePath, function () { | ||||||
|             fs.unlinkSync(path.normalize(options.webroot+'/'+challengePath)); |             // ignore
 | ||||||
|           } |           }); | ||||||
|         }); |         }); | ||||||
|       }); |       }); | ||||||
|     } |     } | ||||||
| @ -382,7 +331,7 @@ function getCert(options, cb) { | |||||||
|   function getCertificate() { |   function getCertificate() { | ||||||
|     var csr=cryptoUtil.generateCSR(state.certPrivateKey, state.validatedDomains); |     var csr=cryptoUtil.generateCSR(state.certPrivateKey, state.validatedDomains); | ||||||
|     log('Requesting certificate...'); |     log('Requesting certificate...'); | ||||||
|     post(state.newCertificateURL, { |     state.acme.post(state.newCertificateURL, { | ||||||
|       resource:'new-cert', |       resource:'new-cert', | ||||||
|       csr:csr, |       csr:csr, | ||||||
|       authorizations:state.validAuthorizationURLs |       authorizations:state.validAuthorizationURLs | ||||||
| @ -440,40 +389,22 @@ function getCert(options, cb) { | |||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   function done() { |   function done() { | ||||||
|     var cert, pfx; |     var cert; | ||||||
|  | 
 | ||||||
|     try { |     try { | ||||||
|       cert=certBufferToPEM(state.certificate); |       cert=certBufferToPEM(state.certificate); | ||||||
|       if (options.certFile) { |     } catch(e) { | ||||||
|         fs.writeFileSync(options.certFile, cert); |       console.error(e.stack); | ||||||
|       } |       //cb(new Error("Could not write output files. Please check permissions!"));
 | ||||||
|       if (options.keyFile) { |       handleErr(e, 'Could not write output files. Please check permissions!'); | ||||||
|         fs.writeFileSync(options.keyFile, state.certPrivateKeyPEM); |       return; | ||||||
|       } |  | ||||||
|       if (options.caFile) { |  | ||||||
|         fs.writeFileSync(options.caFile, state.caCert); |  | ||||||
|       } |  | ||||||
|       if (options.pfxFile) { |  | ||||||
|         try { |  | ||||||
|           pfx=forge.pkcs12.toPkcs12Asn1( |  | ||||||
|             pki.privateKeyFromPem(state.certPrivateKeyPEM), |  | ||||||
|             [pki.certificateFromPem(cert), pki.certificateFromPem(state.caCert)], |  | ||||||
|             options.pfxPassword || '', |  | ||||||
|             options.aes ? {} : {algorithm:'3des'} |  | ||||||
|           ); |  | ||||||
|           pfx=new Buffer(forge.asn1.toDer(pfx).toHex(), 'hex'); |  | ||||||
|         } catch(err) { |  | ||||||
|           handleErr(err, 'Could not convert to PKCS#12'); |  | ||||||
|         } |  | ||||||
|         fs.writeFileSync(options.pfxFile, pfx); |  | ||||||
|       } |  | ||||||
|       cb(null, cert, state.certPrivateKeyPEM, state.caCert); |  | ||||||
|     } catch(err) { |  | ||||||
|       handleErr(err, 'Could not write output files. Please check permissions!'); |  | ||||||
|     } |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|   function post(url, body, cb) { |     cb(null, { | ||||||
|     return state.acme.post(url, body, cb); |       cert: cert | ||||||
|  |     , key: state.certPrivateKeyPEM | ||||||
|  |     , ca: state.caCert | ||||||
|  |     }); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   function handleErr(err, text, info) { |   function handleErr(err, text, info) { | ||||||
| @ -534,5 +465,5 @@ if (~process.argv.indexOf('--letiny-fork')) { | |||||||
|   }); |   }); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | exports.registerNewAccount=registerNewAccount; | ||||||
| exports.getCert=getCert; | exports.getCert=getCert; | ||||||
| 
 |  | ||||||
|  | |||||||
							
								
								
									
										23
									
								
								lib/write-old.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								lib/write-old.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,23 @@ | |||||||
|  |       if (options.certFile) { | ||||||
|  |         fs.writeFileSync(options.certFile, cert); | ||||||
|  |       } | ||||||
|  |       if (options.keyFile) { | ||||||
|  |         fs.writeFileSync(options.keyFile, state.certPrivateKeyPEM); | ||||||
|  |       } | ||||||
|  |       if (options.caFile) { | ||||||
|  |         fs.writeFileSync(options.caFile, state.caCert); | ||||||
|  |       } | ||||||
|  |       if (options.pfxFile) { | ||||||
|  |         try { | ||||||
|  |           pfx=forge.pkcs12.toPkcs12Asn1( | ||||||
|  |             pki.privateKeyFromPem(state.certPrivateKeyPEM), | ||||||
|  |             [pki.certificateFromPem(cert), pki.certificateFromPem(state.caCert)], | ||||||
|  |             options.pfxPassword || '', | ||||||
|  |             options.aes ? {} : {algorithm:'3des'} | ||||||
|  |           ); | ||||||
|  |           pfx=new Buffer(forge.asn1.toDer(pfx).toHex(), 'hex'); | ||||||
|  |         } catch(err) { | ||||||
|  |           handleErr(err, 'Could not convert to PKCS#12'); | ||||||
|  |         } | ||||||
|  |         fs.writeFileSync(options.pfxFile, pfx); | ||||||
|  |       } | ||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user