WIP get challenges
This commit is contained in:
		
							parent
							
								
									488067ec20
								
							
						
					
					
						commit
						7385dd8580
					
				
							
								
								
									
										52
									
								
								app.js
									
									
									
									
									
								
							
							
						
						
									
										52
									
								
								app.js
									
									
									
									
									
								
							| @ -1,3 +1,4 @@ | |||||||
|  | /*global Promise*/ | ||||||
| (function () { | (function () { | ||||||
|   'use strict'; |   'use strict'; | ||||||
| 
 | 
 | ||||||
| @ -112,15 +113,56 @@ | |||||||
|       }); |       }); | ||||||
|       acme.init('https://acme-staging-v02.api.letsencrypt.org/directory').then(function (result) { |       acme.init('https://acme-staging-v02.api.letsencrypt.org/directory').then(function (result) { | ||||||
|         console.log('acme result', result); |         console.log('acme result', result); | ||||||
|         return acme.accounts.create({ |         var privJwk = JSON.parse($('.js-jwk').innerText).private; | ||||||
|           email: $('.js-email').innerText |         var email = $('.js-email').innerText; | ||||||
|         , agreeToTerms: function (tos) { |         function checkTos(tos) { | ||||||
|           console.log("TODO checkbox for agree to terms"); |           console.log("TODO checkbox for agree to terms"); | ||||||
|           return tos; |           return tos; | ||||||
|         } |         } | ||||||
|         , accountKeypair: { |         return acme.accounts.create({ | ||||||
|             privateKeyJwk: JSON.parse($('.js-jwk').innerText).private |           email: email | ||||||
|  |         , agreeToTerms: checkTos | ||||||
|  |         , accountKeypair: { privateKeyJwk: privJwk } | ||||||
|  |         }).then(function (account) { | ||||||
|  |           console.log("account created result:", account); | ||||||
|  |           return Keypairs.generate({ | ||||||
|  |             kty: 'RSA' | ||||||
|  |           , modulusLength: 2048 | ||||||
|  |           }).then(function (pair) { | ||||||
|  |             console.log('domain keypair:', pair); | ||||||
|  |             var domains = ($('.js-domains').innerText||'example.com').split(/[, ]+/g); | ||||||
|  |             return acme.certificates.create({ | ||||||
|  |               accountKeypair: { privateKeyJwk: privJwk } | ||||||
|  |             , account: account | ||||||
|  |             , domainKeypair: { privateKeyJwk: pair.private } | ||||||
|  |             , email: email | ||||||
|  |             , domains: domains | ||||||
|  |             , agreeToTerms: checkTos | ||||||
|  |             , challenges: { | ||||||
|  |                 'dns-01': { | ||||||
|  |                   set: function (opts) { | ||||||
|  |                     console.log('dns-01 set challenge:'); | ||||||
|  |                     console.log(JSON.stringify(opts, null, 2)); | ||||||
|  |                     return new Promise(function (resolve) { | ||||||
|  |                       while (!window.confirm("Did you set the challenge?")) {} | ||||||
|  |                       resolve(); | ||||||
|  |                     }); | ||||||
|                   } |                   } | ||||||
|  |                 , remove: function (opts) { | ||||||
|  |                     console.log('dns-01 remove challenge:'); | ||||||
|  |                     console.log(JSON.stringify(opts, null, 2)); | ||||||
|  |                     return new Promise(function (resolve) { | ||||||
|  |                       while (!window.confirm("Did you delete the challenge?")) {} | ||||||
|  |                       resolve(); | ||||||
|  |                     }); | ||||||
|  |                   } | ||||||
|  |                 } | ||||||
|  |               } | ||||||
|  |             }); | ||||||
|  |           }); | ||||||
|  |         }).catch(function (err) { | ||||||
|  |           console.error("A bad thing happened:"); | ||||||
|  |           console.error(err); | ||||||
|         }); |         }); | ||||||
|       }); |       }); | ||||||
|     }); |     }); | ||||||
|  | |||||||
| @ -63,6 +63,10 @@ | |||||||
|     <form class="js-acme-account"> |     <form class="js-acme-account"> | ||||||
|       <label for="-acmeEmail">Email:</label> |       <label for="-acmeEmail">Email:</label> | ||||||
|       <input class="js-email" type="email" id="-acmeEmail"> |       <input class="js-email" type="email" id="-acmeEmail"> | ||||||
|  |       <br> | ||||||
|  |       <label for="-acmeDomains">Domains:</label> | ||||||
|  |       <input class="js-domains" type="text" id="-acmeDomains"> | ||||||
|  |       <br> | ||||||
|       <button class="js-create-account" hidden>Create Account</button> |       <button class="js-create-account" hidden>Create Account</button> | ||||||
|     </form> |     </form> | ||||||
| 
 | 
 | ||||||
|  | |||||||
							
								
								
									
										100
									
								
								lib/acme.js
									
									
									
									
									
								
							
							
						
						
									
										100
									
								
								lib/acme.js
									
									
									
									
									
								
							| @ -7,7 +7,7 @@ | |||||||
| /* globals Promise */ | /* globals Promise */ | ||||||
| 
 | 
 | ||||||
| var ACME = exports.ACME = {}; | var ACME = exports.ACME = {}; | ||||||
| var Keypairs = exports.Keypairs || {}; | //var Keypairs = exports.Keypairs || {};
 | ||||||
| var Enc = exports.Enc || {}; | var Enc = exports.Enc || {}; | ||||||
| var Crypto = exports.Crypto || {}; | var Crypto = exports.Crypto || {}; | ||||||
| 
 | 
 | ||||||
| @ -90,7 +90,7 @@ ACME._getNonce = function (me) { | |||||||
|       break; |       break; | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|   if (nonce) { return Promise.resolve(nonce); } |   if (nonce) { return Promise.resolve(nonce.nonce); } | ||||||
|   return me.request({ method: 'HEAD', url: me._directoryUrls.newNonce }).then(function (resp) { |   return me.request({ method: 'HEAD', url: me._directoryUrls.newNonce }).then(function (resp) { | ||||||
|     return resp.headers['replay-nonce']; |     return resp.headers['replay-nonce']; | ||||||
|   }); |   }); | ||||||
| @ -132,26 +132,7 @@ ACME._registerAccount = function (me, options) { | |||||||
|         return; |         return; | ||||||
|       } |       } | ||||||
| 
 | 
 | ||||||
|       var jwk = options.accountKeypair.privateKeyJwk; |       return ACME._importKeypair(me, options.accountKeypair).then(function (pair) { | ||||||
|       var p; |  | ||||||
|       if (jwk) { |  | ||||||
|         // nix the browser jwk extras
 |  | ||||||
|         jwk.key_ops = undefined; |  | ||||||
|         jwk.ext = undefined; |  | ||||||
|         p = Promise.resolve({ private: jwk, public: Keypairs.neuter({ jwk: jwk }) }); |  | ||||||
|       } else { |  | ||||||
|         p = Keypairs.import({ pem: options.accountKeypair.privateKeyPem }); |  | ||||||
|       } |  | ||||||
|       return p.then(function (pair) { |  | ||||||
|         options.accountKeypair.privateKeyJwk = pair.private; |  | ||||||
|         options.accountKeypair.publicKeyJwk = pair.public; |  | ||||||
|         if (pair.public.kid) { |  | ||||||
|           pair = JSON.parse(JSON.stringify(pair)); |  | ||||||
|           delete pair.public.kid; |  | ||||||
|           delete pair.private.kid; |  | ||||||
|         } |  | ||||||
|         return pair; |  | ||||||
|       }).then(function (pair) { |  | ||||||
|         var contact; |         var contact; | ||||||
|         if (options.contact) { |         if (options.contact) { | ||||||
|           contact = options.contact.slice(0); |           contact = options.contact.slice(0); | ||||||
| @ -209,7 +190,7 @@ ACME._registerAccount = function (me, options) { | |||||||
|               status: 'valid' |               status: 'valid' | ||||||
|             } |             } | ||||||
|             */ |             */ | ||||||
|             if (!account) { account = { _emptyResponse: true, key: {} }; } |             if (!account) { account = { _emptyResponse: true }; } | ||||||
|             // https://git.coolaj86.com/coolaj86/acme-v2.js/issues/8
 |             // https://git.coolaj86.com/coolaj86/acme-v2.js/issues/8
 | ||||||
|             if (!account.key) { account.key = {}; } |             if (!account.key) { account.key = {}; } | ||||||
|             account.key.kid = options._kid; |             account.key.kid = options._kid; | ||||||
| @ -346,11 +327,12 @@ ACME._testChallenges = function (me, options) { | |||||||
|       , wildcard: identifierValue.includes('*.') || undefined |       , wildcard: identifierValue.includes('*.') || undefined | ||||||
|       }; |       }; | ||||||
|       var dryrun = true; |       var dryrun = true; | ||||||
|       var auth = ACME._challengeToAuth(me, options, results, challenge, dryrun); |       return ACME._challengeToAuth(me, options, results, challenge, dryrun).then(function (auth) { | ||||||
|         return ACME._setChallenge(me, options, auth).then(function () { |         return ACME._setChallenge(me, options, auth).then(function () { | ||||||
|           return auth; |           return auth; | ||||||
|         }); |         }); | ||||||
|       }); |       }); | ||||||
|  |     }); | ||||||
|   })).then(function (auths) { |   })).then(function (auths) { | ||||||
|     return ACME._wait(CHECK_DELAY).then(function () { |     return ACME._wait(CHECK_DELAY).then(function () { | ||||||
|       return Promise.all(auths.map(function (auth) { |       return Promise.all(auths.map(function (auth) { | ||||||
| @ -402,7 +384,8 @@ ACME._challengeToAuth = function (me, options, request, challenge, dryrun) { | |||||||
|   auth.hostname = auth.identifier.value; |   auth.hostname = auth.identifier.value; | ||||||
|   // because I'm not 100% clear if the wildcard identifier does or doesn't have the leading *. in all cases
 |   // because I'm not 100% clear if the wildcard identifier does or doesn't have the leading *. in all cases
 | ||||||
|   auth.altname = ACME._untame(auth.identifier.value, auth.wildcard); |   auth.altname = ACME._untame(auth.identifier.value, auth.wildcard); | ||||||
|   return me.Keypairs.thumbprint({ jwk: options.accountKeypair.publicKeyJwk }).then(function (thumb) { |   return ACME._importKeypair(me, options.accountKeypair).then(function (pair) { | ||||||
|  |     return me.Keypairs.thumbprint({ jwk: pair.public }).then(function (thumb) { | ||||||
|       auth.thumbprint = thumb; |       auth.thumbprint = thumb; | ||||||
|       //   keyAuthorization = token || '.' || base64url(JWK_Thumbprint(accountKey))
 |       //   keyAuthorization = token || '.' || base64url(JWK_Thumbprint(accountKey))
 | ||||||
|       auth.keyAuthorization = challenge.token + '.' + auth.thumbprint; |       auth.keyAuthorization = challenge.token + '.' + auth.thumbprint; | ||||||
| @ -415,6 +398,7 @@ ACME._challengeToAuth = function (me, options, request, challenge, dryrun) { | |||||||
|         return auth; |         return auth; | ||||||
|       }); |       }); | ||||||
|     }); |     }); | ||||||
|  |   }); | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| ACME._untame = function (name, wild) { | ACME._untame = function (name, wild) { | ||||||
| @ -542,15 +526,20 @@ ACME._postChallenge = function (me, options, auth) { | |||||||
|   return respondToChallenge(); |   return respondToChallenge(); | ||||||
| }; | }; | ||||||
| ACME._setChallenge = function (me, options, auth) { | ACME._setChallenge = function (me, options, auth) { | ||||||
|  |   console.log('challenge auth:', auth); | ||||||
|  |   console.log('challenges:', options.challenges); | ||||||
|   return new Promise(function (resolve, reject) { |   return new Promise(function (resolve, reject) { | ||||||
|  |     var challengers = options.challenges || {}; | ||||||
|  |     var challenger = (challengers[auth.type] && challengers[auth.type].set) || options.setChallenge; | ||||||
|     try { |     try { | ||||||
|       if (1 === options.setChallenge.length) { |       if (1 === challenger.length) { | ||||||
|         options.setChallenge(auth).then(resolve).catch(reject); |         challenger(auth).then(resolve).catch(reject); | ||||||
|       } else if (2 === options.setChallenge.length) { |       } else if (2 === challenger.length) { | ||||||
|         options.setChallenge(auth, function (err) { |         challenger(auth, function (err) { | ||||||
|           if(err) { reject(err); } else { resolve(); } |           if(err) { reject(err); } else { resolve(); } | ||||||
|         }); |         }); | ||||||
|       } else { |       } else { | ||||||
|  |         // TODO remove this old backwards-compat
 | ||||||
|         var challengeCb = function(err) { |         var challengeCb = function(err) { | ||||||
|           if(err) { reject(err); } else { resolve(); } |           if(err) { reject(err); } else { resolve(); } | ||||||
|         }; |         }; | ||||||
| @ -563,7 +552,7 @@ ACME._setChallenge = function (me, options, auth) { | |||||||
|           console.warn("The API has been changed for compatibility with all ACME / Let's Encrypt challenge types."); |           console.warn("The API has been changed for compatibility with all ACME / Let's Encrypt challenge types."); | ||||||
|           ACME._setChallengeWarn = true; |           ACME._setChallengeWarn = true; | ||||||
|         } |         } | ||||||
|         options.setChallenge(auth.identifier.value, auth.token, auth.keyAuthorization, challengeCb); |         challenger(auth.identifier.value, auth.token, auth.keyAuthorization, challengeCb); | ||||||
|       } |       } | ||||||
|     } catch(e) { |     } catch(e) { | ||||||
|       reject(e); |       reject(e); | ||||||
| @ -577,7 +566,7 @@ ACME._setChallenge = function (me, options, auth) { | |||||||
| }; | }; | ||||||
| ACME._finalizeOrder = function (me, options, validatedDomains) { | ACME._finalizeOrder = function (me, options, validatedDomains) { | ||||||
|   if (me.debug) { console.debug('finalizeOrder:'); } |   if (me.debug) { console.debug('finalizeOrder:'); } | ||||||
|   var csr = me.Keypairs.generateCsrWeb64(options.domainKeypair, validatedDomains); |   return ACME._generateCsrWeb64(me, options, validatedDomains).then(function (csr) { | ||||||
|     var body = { csr: csr }; |     var body = { csr: csr }; | ||||||
|     var payload = JSON.stringify(body); |     var payload = JSON.stringify(body); | ||||||
| 
 | 
 | ||||||
| @ -652,6 +641,7 @@ ACME._finalizeOrder = function (me, options, validatedDomains) { | |||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     return pollCert(); |     return pollCert(); | ||||||
|  |   }); | ||||||
| }; | }; | ||||||
| // _kid
 | // _kid
 | ||||||
| // registerAccount
 | // registerAccount
 | ||||||
| @ -686,16 +676,18 @@ ACME._getCertificate = function (me, options) { | |||||||
|   } |   } | ||||||
|   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 domain (or options.subject must specified).")); |     + " with the first being the subject of the certificate (or options.subject must specified).")); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   // It's just fine if there's no account, we'll go get the key id we need via the public key
 |   // It's just fine if there's no account, we'll go get the key id we need via the existing key
 | ||||||
|   if (options.accountKid || options.account && options.account.kid) { |   options._kid = options._kid || options.accountKid | ||||||
|     options._kid = options.accountKid || options.account.kid; |     || (options.account && (options.account.kid | ||||||
|   } else { |       || (options.account.key && options.account.key.kid))); | ||||||
|  |   if (!options._kid) { | ||||||
|     //return Promise.reject(new Error("must include KeyID"));
 |     //return Promise.reject(new Error("must include KeyID"));
 | ||||||
|     // This is an idempotent request. It'll return the same account for the same public key.
 |     // This is an idempotent request. It'll return the same account for the same public key.
 | ||||||
|     return ACME._registerAccount(me, options).then(function () { |     return ACME._registerAccount(me, options).then(function (account) { | ||||||
|  |       options._kid = account.key.kid; | ||||||
|       // start back from the top
 |       // start back from the top
 | ||||||
|       return ACME._getCertificate(me, options); |       return ACME._getCertificate(me, options); | ||||||
|     }); |     }); | ||||||
| @ -720,9 +712,6 @@ ACME._getCertificate = function (me, options) { | |||||||
|     }; |     }; | ||||||
| 
 | 
 | ||||||
|     var payload = JSON.stringify(body); |     var payload = JSON.stringify(body); | ||||||
|     // determine the signing algorithm to use in protected header // TODO isn't that handled by the signer?
 |  | ||||||
|     options._kty = (options.accountKeypair.privateKeyJwk && options.accountKeypair.privateKeyJwk.kty || 'RSA'); |  | ||||||
|     options._alg = ('EC' === options._kty) ? 'ES256' : 'RS256'; // TODO vary with bitwidth of key (if not handled)
 |  | ||||||
|     if (me.debug) { console.debug('\n[DEBUG] newOrder\n'); } |     if (me.debug) { console.debug('\n[DEBUG] newOrder\n'); } | ||||||
|     return ACME._jwsRequest({ |     return ACME._jwsRequest({ | ||||||
|       options: options |       options: options | ||||||
| @ -815,6 +804,13 @@ ACME._getCertificate = function (me, options) { | |||||||
|     }); |     }); | ||||||
|   }); |   }); | ||||||
| }; | }; | ||||||
|  | ACME._generateCsrWeb64 = function (me, options, validatedDomains) { | ||||||
|  |   return ACME._importKeypair(me, options.domainKeypair).then(function (/*pair*/) { | ||||||
|  |     return me.Keypairs.generateCsr(options.domainKeypair, validatedDomains).then(function (der) { | ||||||
|  |       return Enc.bufToUrlBase64(der); | ||||||
|  |     }); | ||||||
|  |   }); | ||||||
|  | }; | ||||||
| 
 | 
 | ||||||
| ACME.create = function create(me) { | ACME.create = function create(me) { | ||||||
|   if (!me) { me = {}; } |   if (!me) { me = {}; } | ||||||
| @ -942,6 +938,30 @@ ACME._defaultRequest = function (opts) { | |||||||
|     }); |     }); | ||||||
|   }); |   }); | ||||||
| }; | }; | ||||||
|  | 
 | ||||||
|  | ACME._importKeypair = function (me, kp) { | ||||||
|  |   var jwk = kp.privateKeyJwk; | ||||||
|  |   var p; | ||||||
|  |   if (jwk) { | ||||||
|  |     // nix the browser jwk extras
 | ||||||
|  |     jwk.key_ops = undefined; | ||||||
|  |     jwk.ext = undefined; | ||||||
|  |     p = Promise.resolve({ private: jwk, public: me.Keypairs.neuter({ jwk: jwk }) }); | ||||||
|  |   } else { | ||||||
|  |     p = me.Keypairs.import({ pem: kp.privateKeyPem }); | ||||||
|  |   } | ||||||
|  |   return p.then(function (pair) { | ||||||
|  |     kp.privateKeyJwk = pair.private; | ||||||
|  |     kp.publicKeyJwk = pair.public; | ||||||
|  |     if (pair.public.kid) { | ||||||
|  |       pair = JSON.parse(JSON.stringify(pair)); | ||||||
|  |       delete pair.public.kid; | ||||||
|  |       delete pair.private.kid; | ||||||
|  |     } | ||||||
|  |     return pair; | ||||||
|  |   }); | ||||||
|  | }; | ||||||
|  | 
 | ||||||
| /* | /* | ||||||
| TODO | TODO | ||||||
| Per-Order State Params | Per-Order State Params | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user