yay for backwards compat tested and working
This commit is contained in:
		
							parent
							
								
									2b09af6137
								
							
						
					
					
						commit
						b12a1a7ea4
					
				
							
								
								
									
										42
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										42
									
								
								README.md
									
									
									
									
									
								
							| @ -31,27 +31,52 @@ Todo | ||||
| * export http and dns challenge tests | ||||
| * support ECDSA keys | ||||
| 
 | ||||
| ## Let's Encrypt Directory URLs | ||||
| 
 | ||||
| ``` | ||||
| # Production URL | ||||
| https://acme-v02.api.letsencrypt.org/directory | ||||
| ``` | ||||
| 
 | ||||
| ``` | ||||
| # Staging URL | ||||
| https://acme-staging-v02.api.letsencrypt.org/directory | ||||
| ``` | ||||
| 
 | ||||
| ## API | ||||
| 
 | ||||
| ``` | ||||
| var ACME = require('acme-v2.js').ACME.create({ | ||||
| var ACME = require('acme-v2').ACME.create({ | ||||
|   RSA: require('rsa-compat').RSA | ||||
| 
 | ||||
|   // other overrides | ||||
| , request: require('request') | ||||
| , promisify: require('util').promisify | ||||
| 
 | ||||
|   // used for constructing user-agent | ||||
| , os: require('os') | ||||
| , process: require('process') | ||||
| 
 | ||||
|   // used for overriding the default user-agent | ||||
| , userAgent: 'My custom UA String' | ||||
| , getUserAgentString: function (deps) { return 'My custom UA String'; } | ||||
| }); | ||||
| ``` | ||||
| 
 | ||||
| ```javascript | ||||
| // Accounts | ||||
| ACME.registerNewAccount(options, cb)        // returns "regr" registration data | ||||
| ACME.accounts.create(options)                 // returns Promise<regr> registration data | ||||
| 
 | ||||
|     { email: '<email>'                        //    valid email (server checks MX records) | ||||
|     , accountKeypair: {                       //    privateKeyPem or privateKeyJwt | ||||
|         privateKeyPem: '<ASCII PEM>' | ||||
|       } | ||||
|     , agreeToTerms: fn (tosUrl, cb) {}        //    must specify agree=tosUrl to continue (or falsey to end) | ||||
|     , agreeToTerms: fn (tosUrl) {}            //    returns Promise with tosUrl | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
| // Registration | ||||
| ACME.getCertificate(options, cb)            // returns (err, pems={ privkey (key), cert, chain (ca) }) | ||||
| ACME.certificates.create(options)             // returns Promise<pems={ privkey (key), cert, chain (ca) }> | ||||
| 
 | ||||
|     { newAuthzUrl: '<url>'                    //    specify acmeUrls.newAuthz | ||||
|     , newCertUrl: '<url>'                     //    specify acmeUrls.newCert | ||||
| @ -64,20 +89,19 @@ ACME.getCertificate(options, cb)            // returns (err, pems={ privkey (key | ||||
|       } | ||||
|     , domains: [ 'example.com' ] | ||||
| 
 | ||||
|     , setChallenge: fn (hostname, key, val, cb) | ||||
|     , removeChallenge: fn (hostname, key, cb) | ||||
|     , setChallenge: fn (hostname, key, val)   // return Promise | ||||
|     , removeChallenge: fn (hostname, key)     // return Promise | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
| // Discovery URLs | ||||
| ACME.getAcmeUrls(acmeDiscoveryUrl, cb)      // returns (err, acmeUrls={newReg,newAuthz,newCert,revokeCert}) | ||||
| ACME.init(acmeDirectoryUrl)                   // returns Promise<acmeUrls={keyChange,meta,newAccount,newNonce,newOrder,revokeCert}> | ||||
| ``` | ||||
| 
 | ||||
| Helpers & Stuff | ||||
| 
 | ||||
| ```javascript | ||||
| // Constants | ||||
| ACME.productionServerUrl                // https://acme-v02.api.letsencrypt.org/directory | ||||
| ACME.stagingServerUrl                   // https://acme-staging-v02.api.letsencrypt.org/directory | ||||
| ACME.acmeChallengePrefix                // /.well-known/acme-challenge/ | ||||
| ``` | ||||
| 
 | ||||
|  | ||||
							
								
								
									
										53
									
								
								compat.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										53
									
								
								compat.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,53 @@ | ||||
| 'use strict'; | ||||
| 
 | ||||
| var ACME2 = require('./').ACME; | ||||
| 
 | ||||
| function resolveFn(cb) { | ||||
|   return function (val) { | ||||
|     // nextTick to get out of Promise chain
 | ||||
|     process.nextTick(function () { cb(null, val); }); | ||||
|   }; | ||||
| } | ||||
| function rejectFn(cb) { | ||||
|   return function (err) { | ||||
|     console.log('reject something or other:'); | ||||
|     console.log(err.stack); | ||||
|     // nextTick to get out of Promise chain
 | ||||
|     process.nextTick(function () { cb(err); }); | ||||
|   }; | ||||
| } | ||||
| 
 | ||||
| function create(deps) { | ||||
|   deps.LeCore = {}; | ||||
|   var acme2 = ACME2.create(deps); | ||||
|   acme2.registerNewAccount = function (options, cb) { | ||||
|     acme2.accounts.create(options).then(resolveFn(cb), rejectFn(cb)); | ||||
|   }; | ||||
|   acme2.getCertificate = function (options, cb) { | ||||
|     acme2.certificates.create(options).then(resolveFn(cb), rejectFn(cb)); | ||||
|   }; | ||||
|   acme2.getAcmeUrls = function (options, cb) { | ||||
|     acme2.init(options).then(resolveFn(cb), rejectFn(cb)); | ||||
|   }; | ||||
|   acme2.stagingServerUrl = module.exports.defaults.stagingServerUrl; | ||||
|   acme2.productionServerUrl = module.exports.defaults.productionServerUrl; | ||||
|   return acme2; | ||||
| } | ||||
| 
 | ||||
| module.exports.ACME = { }; | ||||
| module.exports.defaults = { | ||||
|   productionServerUrl:    'https://acme-v02.api.letsencrypt.org/directory' | ||||
| , stagingServerUrl:       'https://acme-staging-v02.api.letsencrypt.org/directory' | ||||
| , knownEndpoints:         [ 'keyChange', 'meta', 'newAccount', 'newNonce', 'newOrder', 'revokeCert' ] | ||||
| , challengeTypes:         [ 'http-01', 'dns-01' ] | ||||
| , challengeType:          'http-01' | ||||
| , keyType:                'rsa' // ecdsa
 | ||||
| , keySize:                2048 // 256
 | ||||
| }; | ||||
| Object.keys(module.exports.defaults).forEach(function (key) { | ||||
|   module.exports.ACME[key] = module.exports.defaults[key]; | ||||
| }); | ||||
| Object.keys(ACME2).forEach(function (key) { | ||||
|   module.exports.ACME[key] = ACME2[key]; | ||||
|   module.exports.ACME.create = create; | ||||
| }); | ||||
							
								
								
									
										860
									
								
								node.js
									
									
									
									
									
								
							
							
						
						
									
										860
									
								
								node.js
									
									
									
									
									
								
							| @ -6,463 +6,487 @@ | ||||
| 'use strict'; | ||||
| /* globals Promise */ | ||||
| 
 | ||||
| var defaults = { | ||||
|   productionServerUrl:    'https://acme-v02.api.letsencrypt.org/directory' | ||||
| , stagingServerUrl:       'https://acme-staging-v02.api.letsencrypt.org/directory' | ||||
| , acmeChallengePrefix:    '/.well-known/acme-challenge/' | ||||
| , knownEndpoints:         [ 'keyChange', 'meta', 'newAccount', 'newNonce', 'newOrder', 'revokeCert' ] | ||||
| , challengeType:          'http-01' // dns-01
 | ||||
| , keyType:                'rsa' // ecdsa
 | ||||
| , keySize:                2048 // 256
 | ||||
| }; | ||||
| var ACME = module.exports.ACME = {}; | ||||
| 
 | ||||
| function create(deps) { | ||||
|   if (!deps) { deps = {}; } | ||||
|   deps.LeCore = {}; | ||||
|   deps.pkg = deps.pkg || require('./package.json'); | ||||
|   deps.os = deps.os || require('os'); | ||||
|   deps.process = deps.process || require('process'); | ||||
| ACME.acmeChallengePrefix = '/.well-known/acme-challenge/'; | ||||
| 
 | ||||
| ACME._getUserAgentString = function (deps) { | ||||
|   var uaDefaults = { | ||||
|       pkg: "Greenlock/" + deps.pkg.version | ||||
|     , os: " (" + deps.os.type() + "; " + deps.process.arch + " " + deps.os.platform() + " " + deps.os.release() + ")" | ||||
|     , node: " Node.js/" + deps.process.version | ||||
|     , os: "(" + deps.os.type() + "; " + deps.process.arch + " " + deps.os.platform() + " " + deps.os.release() + ")" | ||||
|     , node: "Node.js/" + deps.process.version | ||||
|     , user: '' | ||||
|   }; | ||||
|   //var currentUAProps;
 | ||||
| 
 | ||||
|   function getUaString() { | ||||
|     var userAgent = ''; | ||||
|   var userAgent = []; | ||||
| 
 | ||||
|     //Object.keys(currentUAProps)
 | ||||
|     Object.keys(uaDefaults).forEach(function (key) { | ||||
|       userAgent += uaDefaults[key]; | ||||
|       //userAgent += currentUAProps[key];
 | ||||
|     }); | ||||
| 
 | ||||
|     return userAgent.trim(); | ||||
|   } | ||||
| 
 | ||||
|   function getRequest(opts) { | ||||
|     if (!opts) { opts = {}; } | ||||
| 
 | ||||
|     return deps.request.defaults({ | ||||
|       headers: { | ||||
|         'User-Agent': opts.userAgent || getUaString() | ||||
|       } | ||||
|     }); | ||||
|   } | ||||
| 
 | ||||
|   var RSA = deps.RSA || require('rsa-compat').RSA; | ||||
|   deps.request = deps.request || require('request'); | ||||
|   deps.promisify = deps.promisify || require('util').promisify; | ||||
| 
 | ||||
|   var directoryUrl = deps.directoryUrl || defaults.stagingServerUrl; | ||||
|   var request = deps.promisify(getRequest({})); | ||||
| 
 | ||||
|   var acme2 = { | ||||
|     getAcmeUrls: function (_directoryUrl) { | ||||
|       var me = this; | ||||
|       return request({ url: _directoryUrl || directoryUrl, json: true }).then(function (resp) { | ||||
|         me._directoryUrls = resp.body; | ||||
|         me._tos = me._directoryUrls.meta.termsOfService; | ||||
|         return me._directoryUrls; | ||||
|       }); | ||||
|   //Object.keys(currentUAProps)
 | ||||
|   Object.keys(uaDefaults).forEach(function (key) { | ||||
|     if (uaDefaults[key]) { | ||||
|       userAgent.push(uaDefaults[key]); | ||||
|     } | ||||
|   , getNonce: function () { | ||||
|       var me = this; | ||||
|       if (me._nonce) { return new Promise(function (resolve) { resolve(me._nonce); return; }); } | ||||
|       return request({ method: 'HEAD', url: me._directoryUrls.newNonce }).then(function (resp) { | ||||
|         me._nonce = resp.toJSON().headers['replay-nonce']; | ||||
|         return me._nonce; | ||||
|       }); | ||||
|     } | ||||
|     // ACME RFC Section 7.3 Account Creation
 | ||||
|     /* | ||||
|      { | ||||
|        "protected": base64url({ | ||||
|          "alg": "ES256", | ||||
|          "jwk": {...}, | ||||
|          "nonce": "6S8IqOGY7eL2lsGoTZYifg", | ||||
|          "url": "https://example.com/acme/new-account" | ||||
|        }), | ||||
|        "payload": base64url({ | ||||
|          "termsOfServiceAgreed": true, | ||||
|          "onlyReturnExisting": false, | ||||
|          "contact": [ | ||||
|            "mailto:cert-admin@example.com", | ||||
|            "mailto:admin@example.com" | ||||
|          ] | ||||
|        }), | ||||
|        "signature": "RZPOnYoPs1PhjszF...-nh6X1qtOFPB519I" | ||||
|      } | ||||
|     */ | ||||
|   , registerNewAccount: function (options) { | ||||
|       var me = this; | ||||
|   }); | ||||
| 
 | ||||
|       console.log('[acme-v2] registerNewAccount'); | ||||
|   return userAgent.join(' ').trim(); | ||||
| }; | ||||
| ACME._directory = function (me) { | ||||
|   return me._request({ url: me.directoryUrl, json: true }); | ||||
| }; | ||||
| ACME._getNonce = function (me) { | ||||
|   if (me._nonce) { return new Promise(function (resolve) { resolve(me._nonce); return; }); } | ||||
|   return me._request({ method: 'HEAD', url: me._directoryUrls.newNonce }).then(function (resp) { | ||||
|     me._nonce = resp.toJSON().headers['replay-nonce']; | ||||
|     return me._nonce; | ||||
|   }); | ||||
| }; | ||||
| // ACME RFC Section 7.3 Account Creation
 | ||||
| /* | ||||
|  { | ||||
|    "protected": base64url({ | ||||
|      "alg": "ES256", | ||||
|      "jwk": {...}, | ||||
|      "nonce": "6S8IqOGY7eL2lsGoTZYifg", | ||||
|      "url": "https://example.com/acme/new-account" | ||||
|    }), | ||||
|    "payload": base64url({ | ||||
|      "termsOfServiceAgreed": true, | ||||
|      "onlyReturnExisting": false, | ||||
|      "contact": [ | ||||
|        "mailto:cert-admin@example.com", | ||||
|        "mailto:admin@example.com" | ||||
|      ] | ||||
|    }), | ||||
|    "signature": "RZPOnYoPs1PhjszF...-nh6X1qtOFPB519I" | ||||
|  } | ||||
| */ | ||||
| ACME._registerAccount = function (me, options) { | ||||
|   console.log('[acme-v2] accounts.create'); | ||||
| 
 | ||||
|       return me.getNonce().then(function () { | ||||
|         return new Promise(function (resolve, reject) { | ||||
|   return ACME._getNonce(me).then(function () { | ||||
|     return new Promise(function (resolve, reject) { | ||||
| 
 | ||||
|           function agree(err, tosUrl) { | ||||
|             if (err) { reject(err); return; } | ||||
|             if (me._tos !== tosUrl) { | ||||
|               err = new Error("You must agree to the ToS at '" + me._tos + "'"); | ||||
|               err.code = "E_AGREE_TOS"; | ||||
|               reject(err); | ||||
|               return; | ||||
|             } | ||||
| 
 | ||||
|             var jwk = RSA.exportPublicJwk(options.accountKeypair); | ||||
|             var body = { | ||||
|               termsOfServiceAgreed: tosUrl === me._tos | ||||
|             , onlyReturnExisting: false | ||||
|             , contact: [ 'mailto:' + options.email ] | ||||
|             }; | ||||
|             if (options.externalAccount) { | ||||
|               body.externalAccountBinding = RSA.signJws( | ||||
|                 options.externalAccount.secret | ||||
|               , undefined | ||||
|               , { alg: "HS256" | ||||
|                 , kid: options.externalAccount.id | ||||
|                 , url: me._directoryUrls.newAccount | ||||
|                 } | ||||
|               , new Buffer(JSON.stringify(jwk)) | ||||
|               ); | ||||
|             } | ||||
|             var payload = JSON.stringify(body); | ||||
|             var jws = RSA.signJws( | ||||
|               options.accountKeypair | ||||
|             , undefined | ||||
|             , { nonce: me._nonce | ||||
|               , alg: 'RS256' | ||||
|               , url: me._directoryUrls.newAccount | ||||
|               , jwk: jwk | ||||
|               } | ||||
|             , new Buffer(payload) | ||||
|             ); | ||||
| 
 | ||||
|             console.log('[acme-v2] registerNewAccount JSON body:'); | ||||
|             delete jws.header; | ||||
|             console.log(jws); | ||||
|             me._nonce = null; | ||||
|             return request({ | ||||
|               method: 'POST' | ||||
|             , url: me._directoryUrls.newAccount | ||||
|             , headers: { 'Content-Type': 'application/jose+json' } | ||||
|             , json: jws | ||||
|             }).then(function (resp) { | ||||
|               me._nonce = resp.toJSON().headers['replay-nonce']; | ||||
|               var location = resp.toJSON().headers.location; | ||||
|               console.log('[DEBUG] new account location:'); // the account id url
 | ||||
|               console.log(location); // the account id url
 | ||||
|               console.log(resp.toJSON()); | ||||
|               me._kid = location; | ||||
|               return resp.body; | ||||
|             }).then(resolve); | ||||
|           } | ||||
| 
 | ||||
|           console.log('[acme-v2] agreeToTerms'); | ||||
|           options.agreeToTerms(me._tos, agree); | ||||
|         }); | ||||
|       }); | ||||
|     } | ||||
|     /* | ||||
|      POST /acme/new-order HTTP/1.1 | ||||
|      Host: example.com | ||||
|      Content-Type: application/jose+json | ||||
| 
 | ||||
|      { | ||||
|        "protected": base64url({ | ||||
|          "alg": "ES256", | ||||
|          "kid": "https://example.com/acme/acct/1", | ||||
|          "nonce": "5XJ1L3lEkMG7tR6pA00clA", | ||||
|          "url": "https://example.com/acme/new-order" | ||||
|        }), | ||||
|        "payload": base64url({ | ||||
|          "identifiers": [{"type:"dns","value":"example.com"}], | ||||
|          "notBefore": "2016-01-01T00:00:00Z", | ||||
|          "notAfter": "2016-01-08T00:00:00Z" | ||||
|        }), | ||||
|        "signature": "H6ZXtGjTZyUnPeKn...wEA4TklBdh3e454g" | ||||
|      } | ||||
|     */ | ||||
|   , _getChallenges: function (options, auth) { | ||||
|       console.log('\n[DEBUG] getChallenges\n'); | ||||
|       return request({ method: 'GET', url: auth, json: true }).then(function (resp) { | ||||
|         return resp.body; | ||||
|       }); | ||||
|     } | ||||
|     // https://tools.ietf.org/html/draft-ietf-acme-acme-10#section-7.5.1
 | ||||
|   , _postChallenge: function (options, identifier, ch) { | ||||
|       var me = this; | ||||
| 
 | ||||
|       var body = { }; | ||||
| 
 | ||||
|       var payload = JSON.stringify(body); | ||||
| 
 | ||||
|       var thumbprint = RSA.thumbprint(options.accountKeypair); | ||||
|       var keyAuthorization = ch.token + '.' + thumbprint; | ||||
|       //   keyAuthorization = token || '.' || base64url(JWK_Thumbprint(accountKey))
 | ||||
|       //   /.well-known/acme-challenge/:token
 | ||||
| 
 | ||||
|       return new Promise(function (resolve, reject) { | ||||
|         if (options.setupChallenge) { | ||||
|           options.setupChallenge( | ||||
|             { identifier: identifier | ||||
|             , hostname: identifier.value | ||||
|             , type: ch.type | ||||
|             , token: ch.token | ||||
|             , thumbprint: thumbprint | ||||
|             , keyAuthorization: keyAuthorization | ||||
|             , dnsAuthorization: RSA.utils.toWebsafeBase64( | ||||
|                 require('crypto').createHash('sha256').update(keyAuthorization).digest('base64') | ||||
|               ) | ||||
|             } | ||||
|           , testChallenge | ||||
|           ); | ||||
|         } else { | ||||
|           options.setChallenge(identifier.value, ch.token, keyAuthorization, testChallenge); | ||||
|         } | ||||
| 
 | ||||
|         function testChallenge(err) { | ||||
|           if (err) { reject(err); return; } | ||||
| 
 | ||||
|           // TODO put check dns / http checks here?
 | ||||
|           // http-01: GET https://example.org/.well-known/acme-challenge/{{token}} => {{keyAuth}}
 | ||||
|           // dns-01: TXT _acme-challenge.example.org. => "{{urlSafeBase64(sha256(keyAuth))}}"
 | ||||
| 
 | ||||
|           function wait(ms) { | ||||
|             return new Promise(function (resolve) { | ||||
|               setTimeout(resolve, (ms || 1100)); | ||||
|             }); | ||||
|           } | ||||
| 
 | ||||
|           function pollStatus() { | ||||
|             console.log('\n[DEBUG] statusChallenge\n'); | ||||
|             return request({ method: 'GET', url: ch.url, json: true }).then(function (resp) { | ||||
|               console.error('poll: resp.body:'); | ||||
|               console.error(resp.body); | ||||
| 
 | ||||
|               if ('pending' === resp.body.status) { | ||||
|                 console.log('poll: again'); | ||||
|                 return wait().then(pollStatus); | ||||
|               } | ||||
| 
 | ||||
|               if ('valid' === resp.body.status) { | ||||
|                 console.log('poll: valid'); | ||||
|                 try { | ||||
|                   if (options.teardownChallenge) { | ||||
|                     options.teardownChallenge( | ||||
|                       { identifier: identifier | ||||
|                       , type: ch.type | ||||
|                       , token: ch.token | ||||
|                       } | ||||
|                     , function () {} | ||||
|                     ); | ||||
|                   } else { | ||||
|                     options.removeChallenge(identifier.value, ch.token, function () {}); | ||||
|                   } | ||||
|                 } catch(e) {} | ||||
|                 return resp.body; | ||||
|               } | ||||
| 
 | ||||
|               if (!resp.body.status) { | ||||
|                 console.error("[acme-v2] (y) bad challenge state:"); | ||||
|               } | ||||
|               else if ('invalid' === resp.body.status) { | ||||
|                 console.error("[acme-v2] (x) invalid challenge state:"); | ||||
|               } | ||||
|               else { | ||||
|                 console.error("[acme-v2] (z) bad challenge state:"); | ||||
|               } | ||||
| 
 | ||||
|               return Promise.reject(new Error("[acme-v2] bad challenge state")); | ||||
|             }); | ||||
|           } | ||||
| 
 | ||||
|           console.log('\n[DEBUG] postChallenge\n'); | ||||
|           //console.log('\n[DEBUG] stop to fix things\n'); return;
 | ||||
| 
 | ||||
|           function post() { | ||||
|             var jws = RSA.signJws( | ||||
|               options.accountKeypair | ||||
|             , undefined | ||||
|             , { nonce: me._nonce, alg: 'RS256', url: ch.url, kid: me._kid } | ||||
|             , new Buffer(payload) | ||||
|             ); | ||||
|             me._nonce = null; | ||||
|             return request({ | ||||
|               method: 'POST' | ||||
|             , url: ch.url | ||||
|             , headers: { 'Content-Type': 'application/jose+json' } | ||||
|             , json: jws | ||||
|             }).then(function (resp) { | ||||
|               me._nonce = resp.toJSON().headers['replay-nonce']; | ||||
|               console.log('respond to challenge: resp.body:'); | ||||
|               console.log(resp.body); | ||||
|               return wait().then(pollStatus).then(resolve, reject); | ||||
|             }); | ||||
|           } | ||||
| 
 | ||||
|           return wait(20 * 1000).then(post); | ||||
|         } | ||||
|       }); | ||||
|     } | ||||
|   , _finalizeOrder: function (options, validatedDomains) { | ||||
|       console.log('finalizeOrder:'); | ||||
|       var me = this; | ||||
| 
 | ||||
|       var csr = RSA.generateCsrWeb64(options.domainKeypair, validatedDomains); | ||||
|       var body = { csr: csr }; | ||||
|       var payload = JSON.stringify(body); | ||||
| 
 | ||||
|       function wait(ms) { | ||||
|         return new Promise(function (resolve) { | ||||
|           setTimeout(resolve, (ms || 1100)); | ||||
|         }); | ||||
|       } | ||||
| 
 | ||||
|       function pollCert() { | ||||
|         var jws = RSA.signJws( | ||||
|           options.accountKeypair | ||||
|         , undefined | ||||
|         , { nonce: me._nonce, alg: 'RS256', url: me._finalize, kid: me._kid } | ||||
|         , new Buffer(payload) | ||||
|         ); | ||||
| 
 | ||||
|         console.log('finalize:', me._finalize); | ||||
|         me._nonce = null; | ||||
|         return request({ | ||||
|           method: 'POST' | ||||
|         , url: me._finalize | ||||
|         , headers: { 'Content-Type': 'application/jose+json' } | ||||
|         , json: jws | ||||
|         }).then(function (resp) { | ||||
|           me._nonce = resp.toJSON().headers['replay-nonce']; | ||||
| 
 | ||||
|           console.log('order finalized: resp.body:'); | ||||
|           console.log(resp.body); | ||||
| 
 | ||||
|           if ('processing' === resp.body.status) { | ||||
|             return wait().then(pollCert); | ||||
|           } | ||||
| 
 | ||||
|           if ('valid' === resp.body.status) { | ||||
|             me._expires = resp.body.expires; | ||||
|             me._certificate = resp.body.certificate; | ||||
| 
 | ||||
|             return resp.body; | ||||
|           } | ||||
| 
 | ||||
|           if ('invalid' === resp.body.status) { | ||||
|             console.error('cannot finalize: badness'); | ||||
|             return; | ||||
|           } | ||||
| 
 | ||||
|           console.error('(x) cannot finalize: badness'); | ||||
|       function agree(tosUrl) { | ||||
|         var err; | ||||
|         if (me._tos !== tosUrl) { | ||||
|           err = new Error("You must agree to the ToS at '" + me._tos + "'"); | ||||
|           err.code = "E_AGREE_TOS"; | ||||
|           reject(err); | ||||
|           return; | ||||
|         }); | ||||
|       } | ||||
| 
 | ||||
|       return pollCert(); | ||||
|     } | ||||
|   , _getCertificate: function () { | ||||
|       var me = this; | ||||
|       return request({ method: 'GET', url: me._certificate, json: true }).then(function (resp) { | ||||
|         console.log('Certificate:'); | ||||
|         console.log(resp.body); | ||||
|         return resp.body; | ||||
|       }); | ||||
|     } | ||||
|   , getCertificate: function (options, cb) { | ||||
|       console.log('[acme-v2] DEBUG get cert 1'); | ||||
|       var me = this; | ||||
| 
 | ||||
|       if (!options.challengeTypes) { | ||||
|         if (!options.challengeType) { | ||||
|           cb(new Error("challenge type must be specified")); | ||||
|           return Promise.reject(new Error("challenge type must be specified")); | ||||
|         } | ||||
|         options.challengeTypes = [ options.challengeType ]; | ||||
|       } | ||||
| 
 | ||||
|       console.log('[acme-v2] getCertificate'); | ||||
|       return me.getNonce().then(function () { | ||||
|         var jwk = me.RSA.exportPublicJwk(options.accountKeypair); | ||||
|         var body = { | ||||
|           identifiers: options.domains.map(function (hostname) { | ||||
|             return { type: "dns" , value: hostname }; | ||||
|           }) | ||||
|           //, "notBefore": "2016-01-01T00:00:00Z"
 | ||||
|           //, "notAfter": "2016-01-08T00:00:00Z"
 | ||||
|           termsOfServiceAgreed: tosUrl === me._tos | ||||
|         , onlyReturnExisting: false | ||||
|         , contact: [ 'mailto:' + options.email ] | ||||
|         }; | ||||
| 
 | ||||
|         if (options.externalAccount) { | ||||
|           body.externalAccountBinding = me.RSA.signJws( | ||||
|             options.externalAccount.secret | ||||
|           , undefined | ||||
|           , { alg: "HS256" | ||||
|             , kid: options.externalAccount.id | ||||
|             , url: me._directoryUrls.newAccount | ||||
|             } | ||||
|           , new Buffer(JSON.stringify(jwk)) | ||||
|           ); | ||||
|         } | ||||
|         var payload = JSON.stringify(body); | ||||
|         var jws = RSA.signJws( | ||||
|         var jws = me.RSA.signJws( | ||||
|           options.accountKeypair | ||||
|         , undefined | ||||
|         , { nonce: me._nonce, alg: 'RS256', url: me._directoryUrls.newOrder, kid: me._kid } | ||||
|         , { nonce: me._nonce | ||||
|           , alg: 'RS256' | ||||
|           , url: me._directoryUrls.newAccount | ||||
|           , jwk: jwk | ||||
|           } | ||||
|         , new Buffer(payload) | ||||
|         ); | ||||
| 
 | ||||
|         console.log('\n[DEBUG] newOrder\n'); | ||||
|         console.log('[acme-v2] accounts.create JSON body:'); | ||||
|         delete jws.header; | ||||
|         console.log(jws); | ||||
|         me._nonce = null; | ||||
|         return request({ | ||||
|         return me._request({ | ||||
|           method: 'POST' | ||||
|         , url: me._directoryUrls.newOrder | ||||
|         , url: me._directoryUrls.newAccount | ||||
|         , headers: { 'Content-Type': 'application/jose+json' } | ||||
|         , json: jws | ||||
|         }).then(function (resp) { | ||||
|           me._nonce = resp.toJSON().headers['replay-nonce']; | ||||
|           var location = resp.toJSON().headers.location; | ||||
|           console.log('[DEBUG] new account location:'); // the account id url
 | ||||
|           console.log(location); // the account id url
 | ||||
|           console.log(resp.toJSON()); | ||||
|           me._authorizations = resp.body.authorizations; | ||||
|           me._order = location; | ||||
|           me._finalize = resp.body.finalize; | ||||
|           //console.log('[DEBUG] finalize:', me._finalize); return;
 | ||||
|           me._kid = location; | ||||
|           return resp.body; | ||||
|         }).then(resolve, reject); | ||||
|       } | ||||
| 
 | ||||
|           //return resp.body;
 | ||||
|           return Promise.all(me._authorizations.map(function (authUrl) { | ||||
|             return me._getChallenges(options, authUrl).then(function (results) { | ||||
|               // var domain = options.domains[i]; // results.identifier.value
 | ||||
|               var chType = options.challengeTypes.filter(function (chType) { | ||||
|                 return results.challenges.some(function (ch) { | ||||
|                   return ch.type === chType; | ||||
|                 }); | ||||
|               })[0]; | ||||
|               var challenge = results.challenges.filter(function (ch) { | ||||
|                 if (chType === ch.type) { | ||||
|                   return ch; | ||||
|                 } | ||||
|               })[0]; | ||||
|       console.log('[acme-v2] agreeToTerms'); | ||||
|       if (1 === options.agreeToTerms.length) { | ||||
|         return options.agreeToTerms(me._tos).then(agree, reject); | ||||
|       } | ||||
|       else if (2 === options.agreeToTerms.length) { | ||||
|         return options.agreeToTerms(me._tos, function (err, tosUrl) { | ||||
|           if (!err) { agree(tosUrl); return; } | ||||
|           reject(err); | ||||
|         }); | ||||
|       } | ||||
|       else { | ||||
|         reject(new Error('agreeToTerms has incorrect function signature.' | ||||
|           + ' Should be fn(tos) { return Promise<tos>; }')); | ||||
|       } | ||||
|     }); | ||||
|   }); | ||||
| }; | ||||
| /* | ||||
|  POST /acme/new-order HTTP/1.1 | ||||
|  Host: example.com | ||||
|  Content-Type: application/jose+json | ||||
| 
 | ||||
|               if (!challenge) { | ||||
|                 return Promise.reject(new Error("Server didn't offer any challenge we can handle.")); | ||||
|  { | ||||
|    "protected": base64url({ | ||||
|      "alg": "ES256", | ||||
|      "kid": "https://example.com/acme/acct/1", | ||||
|      "nonce": "5XJ1L3lEkMG7tR6pA00clA", | ||||
|      "url": "https://example.com/acme/new-order" | ||||
|    }), | ||||
|    "payload": base64url({ | ||||
|      "identifiers": [{"type:"dns","value":"example.com"}], | ||||
|      "notBefore": "2016-01-01T00:00:00Z", | ||||
|      "notAfter": "2016-01-08T00:00:00Z" | ||||
|    }), | ||||
|    "signature": "H6ZXtGjTZyUnPeKn...wEA4TklBdh3e454g" | ||||
|  } | ||||
| */ | ||||
| ACME._getChallenges = function (me, options, auth) { | ||||
|   console.log('\n[DEBUG] getChallenges\n'); | ||||
|   return me._request({ method: 'GET', url: auth, json: true }).then(function (resp) { | ||||
|     return resp.body; | ||||
|   }); | ||||
| }; | ||||
| ACME._wait = function wait(ms) { | ||||
|   return new Promise(function (resolve) { | ||||
|     setTimeout(resolve, (ms || 1100)); | ||||
|   }); | ||||
| }; | ||||
| // https://tools.ietf.org/html/draft-ietf-acme-acme-10#section-7.5.1
 | ||||
| ACME._postChallenge = function (me, options, identifier, ch) { | ||||
|   var body = { }; | ||||
| 
 | ||||
|   var payload = JSON.stringify(body); | ||||
| 
 | ||||
|   var thumbprint = me.RSA.thumbprint(options.accountKeypair); | ||||
|   var keyAuthorization = ch.token + '.' + thumbprint; | ||||
|   //   keyAuthorization = token || '.' || base64url(JWK_Thumbprint(accountKey))
 | ||||
|   //   /.well-known/acme-challenge/:token
 | ||||
| 
 | ||||
|   return new Promise(function (resolve, reject) { | ||||
|     function failChallenge(err) { | ||||
|       if (err) { reject(err); return; } | ||||
|       testChallenge(); | ||||
|     } | ||||
| 
 | ||||
|     function testChallenge() { | ||||
|       // TODO put check dns / http checks here?
 | ||||
|       // http-01: GET https://example.org/.well-known/acme-challenge/{{token}} => {{keyAuth}}
 | ||||
|       // dns-01: TXT _acme-challenge.example.org. => "{{urlSafeBase64(sha256(keyAuth))}}"
 | ||||
| 
 | ||||
|       function pollStatus() { | ||||
|         console.log('\n[DEBUG] statusChallenge\n'); | ||||
|         return me._request({ method: 'GET', url: ch.url, json: true }).then(function (resp) { | ||||
|           console.error('poll: resp.body:'); | ||||
|           console.error(resp.body); | ||||
| 
 | ||||
|           if ('pending' === resp.body.status) { | ||||
|             console.log('poll: again'); | ||||
|             return ACME._wait(1 * 1000).then(pollStatus); | ||||
|           } | ||||
| 
 | ||||
|           if ('valid' === resp.body.status) { | ||||
|             console.log('poll: valid'); | ||||
|             try { | ||||
|               if (1 === options.removeChallenge.length) { | ||||
|                 options.removeChallenge( | ||||
|                   { identifier: identifier | ||||
|                   , type: ch.type | ||||
|                   , token: ch.token | ||||
|                   } | ||||
|                 ).then(function () {}, function () {}); | ||||
|               } else if (2 === options.removeChallenge.length) { | ||||
|                 options.removeChallenge( | ||||
|                   { identifier: identifier | ||||
|                   , type: ch.type | ||||
|                   , token: ch.token | ||||
|                   } | ||||
|                 , function (err) { return err; } | ||||
|                 ); | ||||
|               } else { | ||||
|                 options.removeChallenge(identifier.value, ch.token, function () {}); | ||||
|               } | ||||
|             } catch(e) {} | ||||
|             return resp.body; | ||||
|           } | ||||
| 
 | ||||
|               return me._postChallenge(options, results.identifier, challenge); | ||||
|             }); | ||||
|           })).then(function () { | ||||
|             var validatedDomains = body.identifiers.map(function (ident) { | ||||
|               return ident.value; | ||||
|             }); | ||||
|           if (!resp.body.status) { | ||||
|             console.error("[acme-v2] (y) bad challenge state:"); | ||||
|           } | ||||
|           else if ('invalid' === resp.body.status) { | ||||
|             console.error("[acme-v2] (x) invalid challenge state:"); | ||||
|           } | ||||
|           else { | ||||
|             console.error("[acme-v2] (z) bad challenge state:"); | ||||
|           } | ||||
| 
 | ||||
|             return me._finalizeOrder(options, validatedDomains); | ||||
|           }).then(function () { | ||||
|             return me._getCertificate().then(function (result) { cb(null, result); return result; }, cb); | ||||
|           }); | ||||
|           return Promise.reject(new Error("[acme-v2] bad challenge state")); | ||||
|         }); | ||||
|       } | ||||
| 
 | ||||
|       console.log('\n[DEBUG] postChallenge\n'); | ||||
|       //console.log('\n[DEBUG] stop to fix things\n'); return;
 | ||||
| 
 | ||||
|       function post() { | ||||
|         var jws = me.RSA.signJws( | ||||
|           options.accountKeypair | ||||
|         , undefined | ||||
|         , { nonce: me._nonce, alg: 'RS256', url: ch.url, kid: me._kid } | ||||
|         , new Buffer(payload) | ||||
|         ); | ||||
|         me._nonce = null; | ||||
|         return me._request({ | ||||
|           method: 'POST' | ||||
|         , url: ch.url | ||||
|         , headers: { 'Content-Type': 'application/jose+json' } | ||||
|         , json: jws | ||||
|         }).then(function (resp) { | ||||
|           me._nonce = resp.toJSON().headers['replay-nonce']; | ||||
|           console.log('respond to challenge: resp.body:'); | ||||
|           console.log(resp.body); | ||||
|           return ACME._wait(1 * 1000).then(pollStatus).then(resolve, reject); | ||||
|         }); | ||||
|       } | ||||
| 
 | ||||
|       return ACME._wait(1 * 1000).then(post); | ||||
|     } | ||||
| 
 | ||||
|     try { | ||||
|       if (1 === options.setChallenge.length) { | ||||
|         options.setChallenge( | ||||
|           { identifier: identifier | ||||
|           , hostname: identifier.value | ||||
|           , type: ch.type | ||||
|           , token: ch.token | ||||
|           , thumbprint: thumbprint | ||||
|           , keyAuthorization: keyAuthorization | ||||
|           , dnsAuthorization: me.RSA.utils.toWebsafeBase64( | ||||
|               require('crypto').createHash('sha256').update(keyAuthorization).digest('base64') | ||||
|             ) | ||||
|           } | ||||
|         ).then(testChallenge, reject); | ||||
|       } else if (2 === options.setChallenge.length) { | ||||
|         options.setChallenge( | ||||
|           { identifier: identifier | ||||
|           , hostname: identifier.value | ||||
|           , type: ch.type | ||||
|           , token: ch.token | ||||
|           , thumbprint: thumbprint | ||||
|           , keyAuthorization: keyAuthorization | ||||
|           , dnsAuthorization: me.RSA.utils.toWebsafeBase64( | ||||
|               require('crypto').createHash('sha256').update(keyAuthorization).digest('base64') | ||||
|             ) | ||||
|           } | ||||
|         , failChallenge | ||||
|         ); | ||||
|       } else { | ||||
|         options.setChallenge(identifier.value, ch.token, keyAuthorization, failChallenge); | ||||
|       } | ||||
|     } catch(e) { | ||||
|       reject(e); | ||||
|     } | ||||
|   }); | ||||
| }; | ||||
| ACME._finalizeOrder = function (me, options, validatedDomains) { | ||||
|   console.log('finalizeOrder:'); | ||||
|   var csr = me.RSA.generateCsrWeb64(options.domainKeypair, validatedDomains); | ||||
|   var body = { csr: csr }; | ||||
|   var payload = JSON.stringify(body); | ||||
| 
 | ||||
|   function pollCert() { | ||||
|     var jws = me.RSA.signJws( | ||||
|       options.accountKeypair | ||||
|     , undefined | ||||
|     , { nonce: me._nonce, alg: 'RS256', url: me._finalize, kid: me._kid } | ||||
|     , new Buffer(payload) | ||||
|     ); | ||||
| 
 | ||||
|     console.log('finalize:', me._finalize); | ||||
|     me._nonce = null; | ||||
|     return me._request({ | ||||
|       method: 'POST' | ||||
|     , url: me._finalize | ||||
|     , headers: { 'Content-Type': 'application/jose+json' } | ||||
|     , json: jws | ||||
|     }).then(function (resp) { | ||||
|       me._nonce = resp.toJSON().headers['replay-nonce']; | ||||
| 
 | ||||
|       console.log('order finalized: resp.body:'); | ||||
|       console.log(resp.body); | ||||
| 
 | ||||
|       if ('processing' === resp.body.status) { | ||||
|         return ACME._wait().then(pollCert); | ||||
|       } | ||||
| 
 | ||||
|       if ('valid' === resp.body.status) { | ||||
|         me._expires = resp.body.expires; | ||||
|         me._certificate = resp.body.certificate; | ||||
| 
 | ||||
|         return resp.body; | ||||
|       } | ||||
| 
 | ||||
|       if ('invalid' === resp.body.status) { | ||||
|         console.error('cannot finalize: badness'); | ||||
|         return; | ||||
|       } | ||||
| 
 | ||||
|       console.error('(x) cannot finalize: badness'); | ||||
|       return; | ||||
|     }); | ||||
|   } | ||||
| 
 | ||||
|   return pollCert(); | ||||
| }; | ||||
| ACME._getCertificate = function (me, options) { | ||||
|   console.log('[acme-v2] DEBUG get cert 1'); | ||||
| 
 | ||||
|   if (!options.challengeTypes) { | ||||
|     if (!options.challengeType) { | ||||
|       return Promise.reject(new Error("challenge type must be specified")); | ||||
|     } | ||||
|     options.challengeTypes = [ options.challengeType ]; | ||||
|   } | ||||
| 
 | ||||
|   console.log('[acme-v2] certificates.create'); | ||||
|   return ACME._getNonce(me).then(function () { | ||||
|     var body = { | ||||
|       identifiers: options.domains.map(function (hostname) { | ||||
|         return { type: "dns" , value: hostname }; | ||||
|       }) | ||||
|       //, "notBefore": "2016-01-01T00:00:00Z"
 | ||||
|       //, "notAfter": "2016-01-08T00:00:00Z"
 | ||||
|     }; | ||||
| 
 | ||||
|     var payload = JSON.stringify(body); | ||||
|     var jws = me.RSA.signJws( | ||||
|       options.accountKeypair | ||||
|     , undefined | ||||
|     , { nonce: me._nonce, alg: 'RS256', url: me._directoryUrls.newOrder, kid: me._kid } | ||||
|     , new Buffer(payload) | ||||
|     ); | ||||
| 
 | ||||
|     console.log('\n[DEBUG] newOrder\n'); | ||||
|     me._nonce = null; | ||||
|     return me._request({ | ||||
|       method: 'POST' | ||||
|     , url: me._directoryUrls.newOrder | ||||
|     , headers: { 'Content-Type': 'application/jose+json' } | ||||
|     , json: jws | ||||
|     }).then(function (resp) { | ||||
|       me._nonce = resp.toJSON().headers['replay-nonce']; | ||||
|       var location = resp.toJSON().headers.location; | ||||
|       console.log(location); // the account id url
 | ||||
|       console.log(resp.toJSON()); | ||||
|       me._authorizations = resp.body.authorizations; | ||||
|       me._order = location; | ||||
|       me._finalize = resp.body.finalize; | ||||
|       //console.log('[DEBUG] finalize:', me._finalize); return;
 | ||||
| 
 | ||||
|       //return resp.body;
 | ||||
|       return Promise.all(me._authorizations.map(function (authUrl, i) { | ||||
|         console.log("Authorizations map #" + i); | ||||
|         return ACME._getChallenges(me, options, authUrl).then(function (results) { | ||||
|           // var domain = options.domains[i]; // results.identifier.value
 | ||||
|           var chType = options.challengeTypes.filter(function (chType) { | ||||
|             return results.challenges.some(function (ch) { | ||||
|               return ch.type === chType; | ||||
|             }); | ||||
|           })[0]; | ||||
| 
 | ||||
|           var challenge = results.challenges.filter(function (ch) { | ||||
|             if (chType === ch.type) { | ||||
|               return ch; | ||||
|             } | ||||
|           })[0]; | ||||
| 
 | ||||
|           if (!challenge) { | ||||
|             return Promise.reject(new Error("Server didn't offer any challenge we can handle.")); | ||||
|           } | ||||
| 
 | ||||
|           return ACME._postChallenge(me, options, results.identifier, challenge); | ||||
|         }); | ||||
|       })).then(function () { | ||||
|         var validatedDomains = body.identifiers.map(function (ident) { | ||||
|           return ident.value; | ||||
|         }); | ||||
| 
 | ||||
|         return ACME._finalizeOrder(me, options, validatedDomains); | ||||
|       }).then(function () { | ||||
|         return me._request({ method: 'GET', url: me._certificate, json: true }).then(function (resp) { | ||||
|           console.log('Certificate:'); | ||||
|           console.log(resp.body); | ||||
|           return resp.body; | ||||
|         }); | ||||
|       }); | ||||
|     }); | ||||
|   }); | ||||
| }; | ||||
| 
 | ||||
| ACME.create = function create(me) { | ||||
|   if (!me) { me = {}; } | ||||
|   me.acmeChallengePrefix = ACME.acmeChallengePrefix; | ||||
|   me.RSA = me.RSA || require('rsa-compat').RSA; | ||||
|   me.request = me.request || require('request'); | ||||
|   me.promisify = me.promisify || require('util').promisify; | ||||
| 
 | ||||
| 
 | ||||
|   if ('function' !== typeof me.getUserAgentString) { | ||||
|     me.pkg = me.pkg || require('./package.json'); | ||||
|     me.os = me.os || require('os'); | ||||
|     me.process = me.process || require('process'); | ||||
|     me.userAgent = ACME._getUserAgentString(me); | ||||
|   } | ||||
| 
 | ||||
|   function getRequest(opts) { | ||||
|     if (!opts) { opts = {}; } | ||||
| 
 | ||||
|     return me.request.defaults({ | ||||
|       headers: { | ||||
|         'User-Agent': opts.userAgent || me.userAgent || me.getUserAgentString(me) | ||||
|       } | ||||
|     }); | ||||
|   } | ||||
| 
 | ||||
|   if ('function' !== typeof me._request) { | ||||
|     me._request = me.promisify(getRequest({})); | ||||
|   } | ||||
| 
 | ||||
|   me.init = function (_directoryUrl) { | ||||
|     me.directoryUrl = me.directoryUrl || _directoryUrl; | ||||
|     return ACME._directory(me).then(function (resp) { | ||||
|       me._directoryUrls = resp.body; | ||||
|       me._tos = me._directoryUrls.meta.termsOfService; | ||||
|       return me._directoryUrls; | ||||
|     }); | ||||
|   }; | ||||
|   me.accounts = { | ||||
|     create: function (options) { | ||||
|       return ACME._registerAccount(me, options); | ||||
|     } | ||||
|   }; | ||||
|   return acme2; | ||||
| } | ||||
| 
 | ||||
| module.exports.ACME = { | ||||
|   create: create | ||||
|   me.certificates = { | ||||
|     create: function (options) { | ||||
|       return ACME._getCertificate(me, options); | ||||
|     } | ||||
|   }; | ||||
|   return me; | ||||
| }; | ||||
| Object.keys(defaults).forEach(function (key) { | ||||
|   module.exports.ACME[key] = defaults[key]; | ||||
| }); | ||||
|  | ||||
							
								
								
									
										75
									
								
								test.cb.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										75
									
								
								test.cb.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,75 @@ | ||||
| 'use strict'; | ||||
| 
 | ||||
| module.exports.run = function run(web, chType, email) { | ||||
|   var RSA = require('rsa-compat').RSA; | ||||
|   var directoryUrl = 'https://acme-staging-v02.api.letsencrypt.org/directory'; | ||||
|   var acme2 = require('./compat').ACME.create({ RSA: RSA }); | ||||
|   // [ 'test.ppl.family' ] 'coolaj86@gmail.com''http-01'
 | ||||
| 	console.log(web, chType, email); | ||||
| 	return; | ||||
|   acme2.init(directoryUrl).then(function (body) { | ||||
|     console.log(body); | ||||
| 		return; | ||||
| 
 | ||||
|     var options = { | ||||
|       agreeToTerms: function (tosUrl, agree) { | ||||
|         agree(null, tosUrl); | ||||
|       } | ||||
|     , setChallenge: function (opts, cb) { | ||||
| 
 | ||||
|         console.log(""); | ||||
|         console.log('identifier:'); | ||||
|         console.log(opts.identifier); | ||||
|         console.log('hostname:'); | ||||
|         console.log(opts.hostname); | ||||
|         console.log('type:'); | ||||
|         console.log(opts.type); | ||||
|         console.log('token:'); | ||||
|         console.log(opts.token); | ||||
|         console.log('thumbprint:'); | ||||
|         console.log(opts.thumbprint); | ||||
|         console.log('keyAuthorization:'); | ||||
|         console.log(opts.keyAuthorization); | ||||
|         console.log('dnsAuthorization:'); | ||||
|         console.log(opts.dnsAuthorization); | ||||
|         console.log(""); | ||||
| 
 | ||||
|         console.log("Put the string '" + opts.keyAuthorization + "' into a file at '" + opts.hostname + "/" + opts.token + "'"); | ||||
|         console.log("\nThen hit the 'any' key to continue (must be specifically the 'any' key)..."); | ||||
| 
 | ||||
|         function onAny() { | ||||
|           process.stdin.pause(); | ||||
|           process.stdin.removeEventListener('data', onAny); | ||||
|           process.stdin.setRawMode(false); | ||||
|           cb(); | ||||
|         } | ||||
|         process.stdin.setRawMode(true); | ||||
|         process.stdin.resume(); | ||||
|         process.stdin.on('data', onAny); | ||||
|       } | ||||
|     , removeChallenge: function (opts, cb) { | ||||
| 				// hostname, key
 | ||||
|         console.log('[DEBUG] remove challenge', hostname, key); | ||||
|         setTimeout(cb, 1 * 1000); | ||||
|       } | ||||
|     , challengeType: chType | ||||
|     , email: email | ||||
|     , accountKeypair: RSA.import({ privateKeyPem: require('fs').readFileSync(__dirname + '/account.privkey.pem') }) | ||||
|     , domainKeypair: RSA.import({ privateKeyPem: require('fs').readFileSync(__dirname + '/privkey.pem') }) | ||||
|     , domains: web | ||||
|     }; | ||||
| 
 | ||||
|     acme2.registerNewAccount(options).then(function (account) { | ||||
|       console.log('account:'); | ||||
|       console.log(account); | ||||
| 
 | ||||
|       acme2.getCertificate(options, function (fullchainPem) { | ||||
|         console.log('[acme-v2] A fullchain.pem:'); | ||||
|         console.log(fullchainPem); | ||||
|       }).then(function (fullchainPem) { | ||||
|         console.log('[acme-v2] B fullchain.pem:'); | ||||
|         console.log(fullchainPem); | ||||
|       }); | ||||
|     }); | ||||
|   }); | ||||
| }; | ||||
							
								
								
									
										57
									
								
								test.compat.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										57
									
								
								test.compat.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,57 @@ | ||||
| 'use strict'; | ||||
| 
 | ||||
| var RSA = require('rsa-compat').RSA; | ||||
| 
 | ||||
| module.exports.run = function (web, chType, email) { | ||||
|   console.log('[DEBUG] run', web, chType, email); | ||||
| 
 | ||||
|   var acme2 = require('./compat.js').ACME.create({ RSA: RSA }); | ||||
|   acme2.getAcmeUrls(acme2.stagingServerUrl, function (err, body) { | ||||
|     if (err) { console.log('err 1'); throw err; } | ||||
|     console.log(body); | ||||
| 
 | ||||
|     var options = { | ||||
|       agreeToTerms: function (tosUrl, agree) { | ||||
|         agree(null, tosUrl); | ||||
|       } | ||||
|     , setChallenge: function (hostname, token, val, cb) { | ||||
|         console.log("Put the string '" + val + "' into a file at '" + hostname + "/" + acme2.acmeChallengePrefix + "/" + token + "'"); | ||||
|         console.log("echo '" + val + "' > '" + hostname + "/" + acme2.acmeChallengePrefix + "/" + token + "'"); | ||||
|         console.log("\nThen hit the 'any' key to continue (must be specifically the 'any' key)..."); | ||||
| 
 | ||||
|         function onAny() { | ||||
|           console.log("'any' key was hit"); | ||||
|           process.stdin.pause(); | ||||
|           process.stdin.removeListener('data', onAny); | ||||
|           process.stdin.setRawMode(false); | ||||
|           cb(); | ||||
|         } | ||||
| 
 | ||||
|         process.stdin.setRawMode(true); | ||||
|         process.stdin.resume(); | ||||
|         process.stdin.on('data', onAny); | ||||
|       } | ||||
|     , removeChallenge: function (hostname, key, cb) { | ||||
|         console.log('[DEBUG] remove challenge', hostname, key); | ||||
|         setTimeout(cb, 1 * 1000); | ||||
|       } | ||||
|     , challengeType: chType | ||||
|     , email: email | ||||
|     , accountKeypair: RSA.import({ privateKeyPem: require('fs').readFileSync(__dirname + '/account.privkey.pem') }) | ||||
|     , domainKeypair: RSA.import({ privateKeyPem: require('fs').readFileSync(__dirname + '/privkey.pem') }) | ||||
|     , domains: web | ||||
|     }; | ||||
| 
 | ||||
|     acme2.registerNewAccount(options, function (err, account) { | ||||
|       if (err) { console.log('err 2'); throw err; } | ||||
|       console.log('account:'); | ||||
|       console.log(account); | ||||
| 
 | ||||
|       acme2.getCertificate(options, function (err, fullchainPem) { | ||||
|         if (err) { console.log('err 3'); throw err; } | ||||
|         console.log('[acme-v2] A fullchain.pem:'); | ||||
|         console.log(fullchainPem); | ||||
|       }); | ||||
|     }); | ||||
|   }); | ||||
| }; | ||||
							
								
								
									
										95
									
								
								test.js
									
									
									
									
									
								
							
							
						
						
									
										95
									
								
								test.js
									
									
									
									
									
								
							| @ -1,56 +1,45 @@ | ||||
| 'use strict'; | ||||
| 
 | ||||
| var RSA = require('rsa-compat').RSA; | ||||
| var acme2 = require('./').ACME.create({ RSA: RSA }); | ||||
| 
 | ||||
| acme2.getAcmeUrls(acme2.stagingServerUrl).then(function (body) { | ||||
|   console.log(body); | ||||
| 
 | ||||
|   var options = { | ||||
|     agreeToTerms: function (tosUrl, agree) { | ||||
|       agree(null, tosUrl); | ||||
|     } | ||||
|     /* | ||||
|   , setupChallenge: function (opts) { | ||||
|       console.log('type:'); | ||||
|       console.log(ch.type); | ||||
|       console.log('ch.token:'); | ||||
|       console.log(ch.token); | ||||
|       console.log('thumbprint:'); | ||||
|       console.log(thumbprint); | ||||
|       console.log('keyAuthorization:'); | ||||
|       console.log(keyAuthorization); | ||||
|       console.log('dnsAuthorization:'); | ||||
|       console.log(dnsAuthorization); | ||||
|     } | ||||
|     */ | ||||
|     // teardownChallenge
 | ||||
|   , setChallenge: function (hostname, key, val, cb) { | ||||
|       console.log('[DEBUG] set challenge', hostname, key, val); | ||||
|       console.log("You have 20 seconds to put the string '" + val + "' into a file at '" + hostname + "/" + key + "'"); | ||||
|       setTimeout(cb, 20 * 1000); | ||||
|     } | ||||
|   , removeChallenge: function (hostname, key, cb) { | ||||
|       console.log('[DEBUG] remove challenge', hostname, key); | ||||
|       setTimeout(cb, 1 * 1000); | ||||
|     } | ||||
|   , challengeType: 'http-01' | ||||
|   , email: 'coolaj86@gmail.com' | ||||
|   , accountKeypair: RSA.import({ privateKeyPem: require('fs').readFileSync(__dirname + '/account.privkey.pem') }) | ||||
|   , domainKeypair: RSA.import({ privateKeyPem: require('fs').readFileSync(__dirname + '/privkey.pem') }) | ||||
|   , domains: [ 'test.ppl.family' ] | ||||
|   }; | ||||
| 
 | ||||
|   acme2.registerNewAccount(options).then(function (account) { | ||||
|     console.log('account:'); | ||||
|     console.log(account); | ||||
| 
 | ||||
|     acme2.getCertificate(options, function (fullchainPem) { | ||||
|       console.log('[acme-v2] A fullchain.pem:'); | ||||
|       console.log(fullchainPem); | ||||
|     }).then(function (fullchainPem) { | ||||
|       console.log('[acme-v2] B fullchain.pem:'); | ||||
|       console.log(fullchainPem); | ||||
|     }); | ||||
|   }); | ||||
| var readline = require('readline'); | ||||
| var rl = readline.createInterface({ | ||||
|   input: process.stdin, | ||||
|   output: process.stdout | ||||
| }); | ||||
| 
 | ||||
| function getWeb() { | ||||
| 	rl.question('What web address(es) would you like to get certificates for? (ex: example.com,*.example.com) ', function (web) { | ||||
| 		web = (web||'').trim().split(/,/g); | ||||
| 		if (!web[0]) { getWeb(); return; } | ||||
| 
 | ||||
|     if (web.some(function (w) { return '*' === w[0]; })) { | ||||
|       console.log('Wildcard domains must use dns-01'); | ||||
|       getEmail(web, 'dns-01'); | ||||
|     } else { | ||||
|       getChallengeType(web); | ||||
|     } | ||||
| 	}); | ||||
| } | ||||
| 
 | ||||
| function getChallengeType(web) { | ||||
| 	rl.question('What challenge will you be testing today? http-01 or dns-01? [http-01] ', function (chType) { | ||||
| 		chType = (chType||'').trim(); | ||||
| 		if (!chType) { chType = 'http-01'; } | ||||
| 
 | ||||
| 		getEmail(web, chType); | ||||
| 	}); | ||||
| } | ||||
| 
 | ||||
| function getEmail(web, chType) { | ||||
| 	rl.question('What email should we use? (optional) ', function (email) { | ||||
| 		email = (email||'').trim(); | ||||
| 		if (!email) { email = null; } | ||||
| 
 | ||||
|     rl.close(); | ||||
|     console.log("[DEBUG] rl blah blah"); | ||||
|     require('./test.compat.js').run(web, chType, email); | ||||
|     //require('./test.cb.js').run(web, chType, email);
 | ||||
|     //require('./test.promise.js').run(web, chType, email);
 | ||||
| 	}); | ||||
| } | ||||
| 
 | ||||
| getWeb(); | ||||
|  | ||||
							
								
								
									
										84
									
								
								test.promise.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										84
									
								
								test.promise.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,84 @@ | ||||
| 'use strict'; | ||||
| 
 | ||||
| /* global Promise */ | ||||
| 
 | ||||
| module.exports.run = function run(web, chType, email) { | ||||
|   var RSA = require('rsa-compat').RSA; | ||||
|   var directoryUrl = 'https://acme-staging-v02.api.letsencrypt.org/directory'; | ||||
|   var acme2 = require('./compat').ACME.create({ RSA: RSA }); | ||||
|   // [ 'test.ppl.family' ] 'coolaj86@gmail.com''http-01'
 | ||||
| 	console.log(web, chType, email); | ||||
| 	return; | ||||
|   acme2.init(directoryUrl).then(function (body) { | ||||
|     console.log(body); | ||||
| 		return; | ||||
| 
 | ||||
|     var options = { | ||||
|       agreeToTerms: function (tosUrl, agree) { | ||||
|         agree(null, tosUrl); | ||||
|       } | ||||
|     , setChallenge: function (opts) { | ||||
| 
 | ||||
|         console.log(""); | ||||
|         console.log('identifier:'); | ||||
|         console.log(opts.identifier); | ||||
|         console.log('hostname:'); | ||||
|         console.log(opts.hostname); | ||||
|         console.log('type:'); | ||||
|         console.log(opts.type); | ||||
|         console.log('token:'); | ||||
|         console.log(opts.token); | ||||
|         console.log('thumbprint:'); | ||||
|         console.log(opts.thumbprint); | ||||
|         console.log('keyAuthorization:'); | ||||
|         console.log(opts.keyAuthorization); | ||||
|         console.log('dnsAuthorization:'); | ||||
|         console.log(opts.dnsAuthorization); | ||||
|         console.log(""); | ||||
| 
 | ||||
|         console.log("Put the string '" + opts.keyAuthorization + "' into a file at '" + opts.hostname + "/" + opts.token + "'"); | ||||
|         console.log("\nThen hit the 'any' key to continue (must be specifically the 'any' key)..."); | ||||
| 
 | ||||
|         return new Promise(function (resolve) { | ||||
|           function onAny() { | ||||
|             process.stdin.pause(); | ||||
|             process.stdin.removeEventListener('data', onAny); | ||||
|             process.stdin.setRawMode(false); | ||||
| 
 | ||||
|             resolve(); | ||||
|           } | ||||
|           process.stdin.setRawMode(true); | ||||
|           process.stdin.resume(); | ||||
|           process.stdin.on('data', onAny); | ||||
|         }); | ||||
|       } | ||||
|     , removeChallenge: function (opts) { | ||||
| 				// hostname, key
 | ||||
|         console.log('[DEBUG] remove challenge', opts.hostname, opts.keyAuthorization); | ||||
|         console.log("Remove the file '" + opts.hostname + "/" + opts.token + "'"); | ||||
| 
 | ||||
|         return new Promise(function (resolve) { | ||||
|           setTimeout(resolve, 1 * 1000); | ||||
|         }); | ||||
|       } | ||||
|     , challengeType: chType | ||||
|     , email: email | ||||
|     , accountKeypair: RSA.import({ privateKeyPem: require('fs').readFileSync(__dirname + '/account.privkey.pem') }) | ||||
|     , domainKeypair: RSA.import({ privateKeyPem: require('fs').readFileSync(__dirname + '/privkey.pem') }) | ||||
|     , domains: web | ||||
|     }; | ||||
| 
 | ||||
|     acme2.registerNewAccount(options).then(function (account) { | ||||
|       console.log('account:'); | ||||
|       console.log(account); | ||||
| 
 | ||||
|       acme2.getCertificate(options, function (fullchainPem) { | ||||
|         console.log('[acme-v2] A fullchain.pem:'); | ||||
|         console.log(fullchainPem); | ||||
|       }).then(function (fullchainPem) { | ||||
|         console.log('[acme-v2] B fullchain.pem:'); | ||||
|         console.log(fullchainPem); | ||||
|       }); | ||||
|     }); | ||||
|   }); | ||||
| }; | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user