WIP almost working
This commit is contained in:
		
							parent
							
								
									7484ffcd11
								
							
						
					
					
						commit
						19cc513174
					
				| @ -33,13 +33,8 @@ | ||||
|     <link rel="preload" href="./fonts/6xK3dSBYKcSV-LCoeQqfX1RYOo3qOK7l.woff2" as="font" crossorigin="anonymous"> | ||||
| 
 | ||||
|     <link rel="preload" href="./fonts/HI_SiYsKILxRpg3hIP6sJ7fM7PqlPevW.woff2" as="font" crossorigin="anonymous"> | ||||
|     <link rel="preload" href="./js/bacme.js" as="script"> | ||||
|     <link rel="preload" href="./js/app.js" as="script"> | ||||
|     <link rel="preload" href="./js/pkijs.org/v1.3.33/common.js" as="script"> | ||||
|     <link rel="preload" href="./js/pkijs.org/v1.3.33/asn1.js" as="script"> | ||||
|     <link rel="preload" href="./js/pkijs.org/v1.3.33/x509_schema.js" as="script"> | ||||
|     <link rel="preload" href="./js/pkijs.org/v1.3.33/x509_simpl.js" as="script"> | ||||
|     <link rel="preload" href="./js/browser-csr/v1.0.0-alpha/csr.js" as="script"> | ||||
|     <link rel="preload" href="./js/bluecrypt-acme.js" as="script"> | ||||
|     <link rel="preload" href="./js/greenlock.js" as="script"> | ||||
| 
 | ||||
|   </head> | ||||
|   <body hidden> | ||||
| @ -342,14 +337,8 @@ | ||||
|       <br> | ||||
| 
 | ||||
| 
 | ||||
|         <script src="./js/bacme.js"></script> | ||||
|         <script src="./js/app.js"></script> | ||||
| 
 | ||||
|         <script src="./js/pkijs.org/v1.3.33/common.js"></script> | ||||
|         <script src="./js/pkijs.org/v1.3.33/asn1.js"></script> | ||||
|         <script src="./js/pkijs.org/v1.3.33/x509_schema.js"></script> | ||||
|         <script src="./js/pkijs.org/v1.3.33/x509_simpl.js"></script> | ||||
|         <script src="./js/browser-csr/v1.0.0-alpha/csr.js"></script> | ||||
|         <script src="./js/bluecrypt-acme.js"></script> | ||||
|         <script src="./js/greenlock.js"></script> | ||||
| 
 | ||||
|         <!-- Global site tag (gtag.js) - Google Analytics --> | ||||
|         <script async src="https://www.googletagmanager.com/gtag/js?id=UA-118745161-2"></script> | ||||
|  | ||||
| @ -2,8 +2,7 @@ | ||||
| /* This Source Code Form is subject to the terms of the Mozilla Public | ||||
|  * License, v. 2.0. If a copy of the MPL was not distributed with this | ||||
|  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 | ||||
| ; | ||||
| (function (exports) { | ||||
| ;(function (exports) { | ||||
| 
 | ||||
| var Enc = exports.Enc = {}; | ||||
| 
 | ||||
| @ -1962,7 +1961,12 @@ ACME._getChallenges = function (me, options, authUrl) { | ||||
|   , payload: '' | ||||
|   , url: authUrl | ||||
|   }).then(function (resp) { | ||||
|     return resp.body; | ||||
|     // Pre-emptive rather than lazy for interfaces that need to show the challenges to the user first
 | ||||
|     return ACME._challengesToAuth(me, options, resp.body, false).then(function (auths) { | ||||
|       resp.body._rawChallenges = resp.body.challenges; | ||||
|       resp.body.challenges = auths; | ||||
|       return resp.body; | ||||
|     }); | ||||
|   }); | ||||
| }; | ||||
| ACME._wait = function wait(ms) { | ||||
| @ -1987,12 +1991,6 @@ ACME._testChallengeOptions = function () { | ||||
|       "token": "test-" + chToken + "-1", | ||||
|       "_wildcard": true | ||||
|     } | ||||
|   , { | ||||
|       "type": "tls-sni-01", | ||||
|       "status": "pending", | ||||
|       "url": "https://acme-staging-v02.example.com/2", | ||||
|       "token": "test-" + chToken + "-2" | ||||
|     } | ||||
|   , { | ||||
|       "type": "tls-alpn-01", | ||||
|       "status": "pending", | ||||
| @ -2010,47 +2008,49 @@ ACME._testChallenges = function (me, options) { | ||||
|       challenges = challenges.filter(function (ch) { return ch._wildcard; }); | ||||
|     } | ||||
| 
 | ||||
|     var challenge = ACME._chooseChallenge(options, { challenges: challenges }); | ||||
|     if (!challenge) { | ||||
|       // For example, wildcards require dns-01 and, if we don't have that, we have to bail
 | ||||
|       var enabled = Object.keys(options.challenges).join(', ') || 'none'; | ||||
|       var suitable = challenges.map(function (r) { return r.type; }).join(', ') || 'none'; | ||||
|       return Promise.reject(new Error( | ||||
|         "None of the challenge types that you've enabled ( " + enabled + " )" | ||||
|           + " are suitable for validating the domain you've selected (" + identifierValue + ")." | ||||
|           + " You must enable one of ( " + suitable + " )." | ||||
|       )); | ||||
|     } | ||||
| 
 | ||||
|     // TODO remove skipChallengeTest
 | ||||
|     if (me.skipDryRun || me.skipChallengeTest) { | ||||
|       return null; | ||||
|     } | ||||
| 
 | ||||
|     if ('dns-01' === challenge.type) { | ||||
|       // Give the nameservers a moment to propagate
 | ||||
|       CHECK_DELAY = 1.5 * 1000; | ||||
|     } | ||||
| 
 | ||||
|     return Promise.resolve().then(function () { | ||||
|       var results = { | ||||
|     // The dry-run comes first in the spirit of "fail fast"
 | ||||
|     // (and protecting against challenge failure rate limits)
 | ||||
|     var dryrun = true; | ||||
|     var resp = { | ||||
|       body: { | ||||
|         identifier: { | ||||
|           type: "dns" | ||||
|         , value: identifierValue.replace(/^\*\./, '') | ||||
|         } | ||||
|       , challenges: [ challenge ] | ||||
|       , challenges: challenges | ||||
|       , expires: new Date(Date.now() + (60 * 1000)).toISOString() | ||||
|       , wildcard: identifierValue.includes('*.') || undefined | ||||
|       }; | ||||
|       } | ||||
|     }; | ||||
|     return ACME._challengesToAuth(me, options, resp.body, dryrun).then(function (auths) { | ||||
|       resp.body._rawChallenges = resp.body.challenges; | ||||
|       resp.body.challenges = auths; | ||||
| 
 | ||||
|       // The dry-run comes first in the spirit of "fail fast"
 | ||||
|       // (and protecting against challenge failure rate limits)
 | ||||
|       var dryrun = true; | ||||
|       return ACME._challengeToAuth(me, options, results, challenge, dryrun).then(function (auth) { | ||||
|         if (!me._canUse[auth.type]) { return; } | ||||
|         return ACME._setChallenge(me, options, auth).then(function () { | ||||
|           return auth; | ||||
|         }); | ||||
|       var auth = ACME._chooseAuth(options, resp.body.challenges); | ||||
|       if (!auth) { | ||||
|         // For example, wildcards require dns-01 and, if we don't have that, we have to bail
 | ||||
|         var enabled = Object.keys(options.challenges).join(', ') || 'none'; | ||||
|         var suitable = resp.body.challenges.map(function (r) { return r.type; }).join(', ') || 'none'; | ||||
|         return Promise.reject(new Error( | ||||
|           "None of the challenge types that you've enabled ( " + enabled + " )" | ||||
|             + " are suitable for validating the domain you've selected (" + identifierValue + ")." | ||||
|             + " You must enable one of ( " + suitable + " )." | ||||
|         )); | ||||
|       } | ||||
| 
 | ||||
|       // TODO remove skipChallengeTest
 | ||||
|       if (me.skipDryRun || me.skipChallengeTest) { | ||||
|         return null; | ||||
|       } | ||||
| 
 | ||||
|       if ('dns-01' === auth.type) { | ||||
|         // Give the nameservers a moment to propagate
 | ||||
|         CHECK_DELAY = 1.5 * 1000; | ||||
|       } | ||||
| 
 | ||||
|       if (!me._canUse[auth.type]) { return; } | ||||
|       return ACME._setChallenge(me, options, auth).then(function () { | ||||
|         return auth; | ||||
|       }); | ||||
|     }); | ||||
|   })).then(function (auths) { | ||||
| @ -2067,93 +2067,88 @@ ACME._testChallenges = function (me, options) { | ||||
|     }); | ||||
|   }); | ||||
| }; | ||||
| ACME._chooseChallenge = function(options, results) { | ||||
| ACME._chooseAuth = function(options, auths) { | ||||
|   // For each of the challenge types that we support
 | ||||
|   var challenge; | ||||
|   var auth; | ||||
|   var challengeTypes = Object.keys(options.challenges); | ||||
|   // ordered from most to least preferred
 | ||||
|   challengeTypes = [ 'tls-alpn-01', 'http-01', 'dns-01' ].filter(function (chType) { | ||||
|   challengeTypes = (options.challengePriority||[ 'tls-alpn-01', 'http-01', 'dns-01' ]).filter(function (chType) { | ||||
|     return challengeTypes.includes(chType); | ||||
|   }); | ||||
| 
 | ||||
|   /* | ||||
|   // Lot's of error checking to inform the user of mistakes
 | ||||
|   if (!(options.challengeTypes||[]).length) { | ||||
|     options.challengeTypes = Object.keys(options.challenges||{}); | ||||
|   } | ||||
|   if (!options.challengeTypes.length) { | ||||
|     options.challengeTypes = [ options.challengeType ].filter(Boolean); | ||||
|   } | ||||
|   if (options.challengeType) { | ||||
|     options.challengeTypes.sort(function (a, b) { | ||||
|       if (a === options.challengeType) { return -1; } | ||||
|       if (b === options.challengeType) { return 1; } | ||||
|       return 0; | ||||
|     }); | ||||
|     if (options.challengeType !== options.challengeTypes[0]) { | ||||
|       return Promise.reject(new Error("options.challengeType is '" + options.challengeType + "'," | ||||
|         + " which does not exist in the supplied types '" + options.challengeTypes.join(',') + "'")); | ||||
|     } | ||||
|   } | ||||
|   // TODO check that all challengeTypes are represented in challenges
 | ||||
|   if (!options.challengeTypes.length) { | ||||
|     return Promise.reject(new Error("options.challengeTypes (string array) must be specified" | ||||
|       + " (and in order of preferential priority).")); | ||||
|   } | ||||
|   */ | ||||
|   challengeTypes.some(function (chType) { | ||||
|     // And for each of the challenge types that are allowed
 | ||||
|     return results.challenges.some(function (ch) { | ||||
|     return auths.some(function (ch) { | ||||
|       // Check to see if there are any matches
 | ||||
|       if (ch.type === chType) { | ||||
|         challenge = ch; | ||||
|         auth = ch; | ||||
|         return true; | ||||
|       } | ||||
|     }); | ||||
|   }); | ||||
| 
 | ||||
|   return challenge; | ||||
|   return auth; | ||||
| }; | ||||
| ACME._challengeToAuth = function (me, options, request, challenge, dryrun) { | ||||
| ACME._challengesToAuth = function (me, options, request, dryrun) { | ||||
|   // we don't poison the dns cache with our dummy request
 | ||||
|   var dnsPrefix = ACME.challengePrefixes['dns-01']; | ||||
|   if (dryrun) { | ||||
|     dnsPrefix = dnsPrefix.replace('acme-challenge', 'greenlock-dryrun-' + ACME._prnd(4)); | ||||
|   } | ||||
|   var challengeTypes = Object.keys(options.challenges); | ||||
| 
 | ||||
|   var auth = {}; | ||||
| 
 | ||||
|   // straight copy from the new order response
 | ||||
|   // { identifier, status, expires, challenges, wildcard }
 | ||||
|   Object.keys(request).forEach(function (key) { | ||||
|     auth[key] = request[key]; | ||||
|   }); | ||||
| 
 | ||||
|   // copy from the challenge we've chosen
 | ||||
|   // { type, status, url, token }
 | ||||
|   // (note the duplicate status overwrites the one above, but they should be the same)
 | ||||
|   Object.keys(challenge).forEach(function (key) { | ||||
|     // don't confused devs with the id url
 | ||||
|     auth[key] = challenge[key]; | ||||
|   }); | ||||
| 
 | ||||
|   // batteries-included helpers
 | ||||
|   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
 | ||||
|   auth.altname = ACME._untame(auth.identifier.value, auth.wildcard); | ||||
|   return ACME._importKeypair(me, options.accountKeypair).then(function (pair) { | ||||
|     return me.Keypairs.thumbprint({ jwk: pair.public }).then(function (thumb) { | ||||
|       auth.thumbprint = thumb; | ||||
|       //   keyAuthorization = token || '.' || base64url(JWK_Thumbprint(accountKey))
 | ||||
|       auth.keyAuthorization = challenge.token + '.' + auth.thumbprint; | ||||
|       // conflicts with ACME challenge id url is already in use, so we call this challengeUrl instead
 | ||||
|       // TODO auth.http01Url ?
 | ||||
|       auth.challengeUrl = 'http://' + auth.identifier.value + ACME.challengePrefixes['http-01'] + '/' + auth.token; | ||||
|       auth.dnsHost = dnsPrefix + '.' + auth.hostname.replace('*.', ''); | ||||
|       return Promise.all(request.challenges.map(function (challenge) { | ||||
|         // Don't do extra work for challenges that we can't satisfy
 | ||||
|         if (!challengeTypes.includes(challenge.type)) { | ||||
|           return null; | ||||
|         } | ||||
| 
 | ||||
|       return Crypto._sha('sha256', auth.keyAuthorization).then(function (hash) { | ||||
|         auth.dnsAuthorization = hash; | ||||
|         return auth; | ||||
|         var auth = {}; | ||||
| 
 | ||||
|         // straight copy from the new order response
 | ||||
|         // { identifier, status, expires, challenges, wildcard }
 | ||||
|         Object.keys(request).forEach(function (key) { | ||||
|           auth[key] = request[key]; | ||||
|         }); | ||||
| 
 | ||||
|         // copy from the challenge we've chosen
 | ||||
|         // { type, status, url, token }
 | ||||
|         // (note the duplicate status overwrites the one above, but they should be the same)
 | ||||
|         Object.keys(challenge).forEach(function (key) { | ||||
|           // don't confused devs with the id url
 | ||||
|           auth[key] = challenge[key]; | ||||
|         }); | ||||
| 
 | ||||
|         // batteries-included helpers
 | ||||
|         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
 | ||||
|         auth.altname = ACME._untame(auth.identifier.value, auth.wildcard); | ||||
| 
 | ||||
|         auth.thumbprint = thumb; | ||||
|         //   keyAuthorization = token || '.' || base64url(JWK_Thumbprint(accountKey))
 | ||||
|         auth.keyAuthorization = challenge.token + '.' + auth.thumbprint; | ||||
| 
 | ||||
|         if ('http-01' === auth.type) { | ||||
|           // conflicts with ACME challenge id url is already in use, so we call this challengeUrl instead
 | ||||
|           // TODO auth.http01Url ?
 | ||||
|           auth.challengeUrl = 'http://' + auth.identifier.value + ACME.challengePrefixes['http-01'] + '/' + auth.token; | ||||
|           return auth; | ||||
|         } | ||||
| 
 | ||||
|         if ('dns-01' !== auth.type) { | ||||
|           return auth; | ||||
|         } | ||||
| 
 | ||||
|         return Crypto._sha('sha256', auth.keyAuthorization).then(function (hash) { | ||||
|           auth.dnsHost = dnsPrefix + '.' + auth.hostname.replace('*.', ''); | ||||
|           auth.dnsAuthorization = hash; | ||||
|           auth.keyAuthorizationDigest = hash; | ||||
|           return auth; | ||||
|         }); | ||||
|       })).then(function (auths) { | ||||
|         return auths.filter(Boolean); | ||||
|       }); | ||||
|     }); | ||||
|   }); | ||||
| @ -2241,18 +2236,21 @@ ACME._postChallenge = function (me, options, auth) { | ||||
|         return resp.body; | ||||
|       } | ||||
| 
 | ||||
|       var errmsg; | ||||
|       if (!resp.body.status) { | ||||
|         errmsg = "[acme-v2] (E_STATE_EMPTY) empty challenge state for '" + altname + "':"; | ||||
|       } | ||||
|       else if ('invalid' === resp.body.status) { | ||||
|         errmsg = "[acme-v2] (E_STATE_INVALID) challenge state for '" + altname + "': '" + resp.body.status + "'"; | ||||
|       } | ||||
|       else { | ||||
|         errmsg = "[acme-v2] (E_STATE_UKN) challenge state for '" + altname + "': '" + resp.body.status + "'"; | ||||
|       var err; | ||||
|       if (resp.body.error && resp.body.error.detail) { | ||||
|         err = new Error("[acme-v2] " + auth.altname + " state:" + resp.body.status + " " + resp.body.error.detail); | ||||
|         err.auth = auth; | ||||
|         err.altname = auth.altname; | ||||
|         err.type = auth.type; | ||||
|         err.urn = resp.body.error.type; | ||||
|         err.code = ('invalid' === resp.body.status) ? 'E_CHALLENGE_INVALID' : 'E_CHALLENGE_UNKNOWN'; | ||||
|         err.uri = resp.body.url; | ||||
|       } else { | ||||
|         err = new Error("[acme-v2] " + auth.altname + " (E_STATE_UKN): " + JSON.stringify(resp.body, null, 2)); | ||||
|         err.code = 'E_CHALLENGE_UNKNOWN'; | ||||
|       } | ||||
| 
 | ||||
|       return Promise.reject(new Error(errmsg)); | ||||
|       return Promise.reject(err); | ||||
|     }); | ||||
|   } | ||||
| 
 | ||||
| @ -2312,34 +2310,32 @@ ACME._setChallenge = function (me, options, auth) { | ||||
| ACME._setChallengesAll = function (me, options) { | ||||
|   var order = options.order; | ||||
|   var setAuths = order.authorizations.slice(0); | ||||
|   var challenges = order.challenges; | ||||
|   var claims = order.claims.slice(0); | ||||
|   var validAuths = []; | ||||
|   var auths = []; | ||||
| 
 | ||||
|   function setNext() { | ||||
|     var authUrl = setAuths.shift(); | ||||
|     var results = challenges.shift(); | ||||
|     var claim = claims.shift(); | ||||
|     if (!authUrl) { return; } | ||||
| 
 | ||||
|     // var domain = options.domains[i]; // results.identifier.value
 | ||||
|     // var domain = options.domains[i]; // claim.identifier.value
 | ||||
| 
 | ||||
|     // If it's already valid, we're golden it regardless
 | ||||
|     if (results.challenges.some(function (ch) { return 'valid' === ch.status; })) { | ||||
|     if (claim.challenges.some(function (ch) { return 'valid' === ch.status; })) { | ||||
|       return setNext(); | ||||
|     } | ||||
| 
 | ||||
|     var challenge = ACME._chooseChallenge(options, results); | ||||
|     if (!challenge) { | ||||
|     var auth = ACME._chooseAuth(options, claim.challenges); | ||||
|     if (!auth) { | ||||
|       // For example, wildcards require dns-01 and, if we don't have that, we have to bail
 | ||||
|       return Promise.reject(new Error( | ||||
|         "Server didn't offer any challenge we can handle for '" + options.domains.join() + "'." | ||||
|       )); | ||||
|     } | ||||
| 
 | ||||
|     return ACME._challengeToAuth(me, options, results, challenge, false).then(function (auth) { | ||||
|       auths.push(auth); | ||||
|       return ACME._setChallenge(me, options, auth).then(setNext); | ||||
|     }); | ||||
|     auths.push(auth); | ||||
|     return ACME._setChallenge(me, options, auth).then(setNext); | ||||
|   } | ||||
| 
 | ||||
|   function checkNext() { | ||||
| @ -2358,6 +2354,7 @@ ACME._setChallengesAll = function (me, options) { | ||||
|     }).then(checkNext); | ||||
|   } | ||||
| 
 | ||||
|   // Actually sets the challenge via ACME
 | ||||
|   function challengeNext() { | ||||
|     var auth = validAuths.shift(); | ||||
|     if (!auth) { return; } | ||||
| @ -2519,7 +2516,7 @@ ACME._createOrder = function (me, options) { | ||||
|       if (me.debug) { console.debug('[ordered]', location); } // the account id url
 | ||||
|       if (me.debug) { console.debug(resp); } | ||||
| 
 | ||||
|       if (!options.authorizations) { | ||||
|       if (!order.authorizations) { | ||||
|         return Promise.reject(new Error( | ||||
|           "[acme-v2.js] authorizations were not fetched for '" + options.domains.join() + "':\n" | ||||
|           + JSON.stringify(resp.body) | ||||
| @ -2528,25 +2525,24 @@ ACME._createOrder = function (me, options) { | ||||
| 
 | ||||
|       return order; | ||||
|     }).then(function (order) { | ||||
|       var challenges = []; | ||||
|       var claims = []; | ||||
|       if (me.debug) { console.debug("[acme-v2] POST newOrder has authorizations"); } | ||||
|       var challengeAuths = order.authorizations.slice(0); | ||||
| 
 | ||||
|       function getNext() { | ||||
|         var authUrl = challengeAuths.shift(); | ||||
|         if (!authUrl) { return challenges; } | ||||
|         if (!authUrl) { return claims; } | ||||
| 
 | ||||
|         return ACME._getChallenges(me, options, authUrl).then(function (results) { | ||||
|           // var domain = options.domains[i]; // results.identifier.value
 | ||||
|           challenges.push(results); | ||||
|         return ACME._getChallenges(me, options, authUrl).then(function (claim) { | ||||
|           // var domain = options.domains[i]; // claim.identifier.value
 | ||||
|           claims.push(claim); | ||||
|           return getNext(); | ||||
|         }); | ||||
|       } | ||||
| 
 | ||||
|       return getNext().then(function () { | ||||
|         order.challenges = challenges; | ||||
|         order.claims = claims; | ||||
|         options.order = order; | ||||
|         console.log('DEBUG 2 order (too much info for challenges?):', order); | ||||
|         return order; | ||||
|       }); | ||||
|     }); | ||||
| @ -2684,10 +2680,12 @@ ACME.create = function create(me) { | ||||
|     } | ||||
|   }; | ||||
|   me.orders = { | ||||
|     create: function (options) { | ||||
|     // create + get challlenges
 | ||||
|     request: function (options) { | ||||
|       return ACME._createOrder(me, options); | ||||
|     } | ||||
|   , finalize: function (options) { | ||||
|     // set challenges, check challenges, finalize order, return order
 | ||||
|   , complete: function (options) { | ||||
|       return ACME._finalizeOrder(me, options); | ||||
|     } | ||||
|   }; | ||||
|  | ||||
| @ -2,6 +2,7 @@ | ||||
| 'use strict'; | ||||
| 
 | ||||
|   /*global URLSearchParams,Headers*/ | ||||
|   var PromiseA = window.Promise; | ||||
|   var VERSION = '2'; | ||||
| 	// ACME recommends ECDSA P-256, but RSA 2048 is still required by some old servers (like what replicated.io uses )
 | ||||
| 	// ECDSA P-384, P-521, and RSA 3072, 4096 are NOT recommend standards (and not properly supported)
 | ||||
| @ -15,11 +16,32 @@ | ||||
|   var $qs = function (s) { return window.document.querySelector(s); }; | ||||
|   var $qsa = function (s) { return window.document.querySelectorAll(s); }; | ||||
| 	var acme; | ||||
| 	var accountStuff; | ||||
|   var info = {}; | ||||
|   var steps = {}; | ||||
|   var i = 1; | ||||
|   var apiUrl = 'https://acme-{{env}}.api.letsencrypt.org/directory'; | ||||
|   var challenges = { | ||||
|     'http-01': { | ||||
|       set: function (auth) { | ||||
|         console.log('Chose http-01 for', auth.altname, auth); | ||||
|         return Promise.resolve(); | ||||
|       } | ||||
|     , remove: function (auth) { | ||||
|         console.log('Can remove http-01 for', auth.altname, auth); | ||||
|         return Promise.resolve(); | ||||
|       } | ||||
|     } | ||||
|   , 'dns-01': { | ||||
|       set: function (auth) { | ||||
|         console.log('Chose dns-01 for', auth.altname, auth); | ||||
|         return Promise.resolve(); | ||||
|       } | ||||
|     , remove: function (auth) { | ||||
|         console.log('Can remove dns-01 for', auth.altname, auth); | ||||
|         return Promise.resolve(); | ||||
|       } | ||||
|     } | ||||
|   }; | ||||
| 
 | ||||
|   function updateApiType() { | ||||
|     console.log("type updated"); | ||||
| @ -59,8 +81,29 @@ | ||||
|     i += 1; | ||||
| 
 | ||||
|     return PromiseA.resolve(steps[j].submit(ev)).catch(function (err) { | ||||
|       var ourfault = true; | ||||
|       console.error(err); | ||||
|       window.alert("Something went wrong. It's our fault not yours. Please email aj@rootprojects.org and let him know that 'step " + j + "' failed."); | ||||
|       console.error(Object.keys(err)); | ||||
|       if ('E_CHALLENGE_INVALID' === err.code) { | ||||
|         if ('dns-01' === err.type) { | ||||
|           ourfault = false; | ||||
|           window.alert("It looks like the DNS record you set for " | ||||
|             + err.altname + " was incorrect or did not propagate. " | ||||
|             + "The error message was '" + err.message + "'"); | ||||
|         } else if ('http-01' === err.type) { | ||||
|           ourfault = false; | ||||
|           window.alert("It looks like the file you uploaded for " | ||||
|             + err.altname + " was incorrect or could not be downloaded. " | ||||
|             + "The error message was '" + err.message + "'"); | ||||
|         } | ||||
|       } | ||||
| 
 | ||||
|       if (ourfault) { | ||||
|         err.auth = undefined; | ||||
|         window.alert("Something went wrong. It's probably our fault, not yours." | ||||
|           + " Please email aj@rootprojects.org to let him know. The error message is: \n" | ||||
|           + JSON.stringify(err, null, 2)); | ||||
|       } | ||||
|     }); | ||||
|   } | ||||
| 
 | ||||
| @ -178,7 +221,8 @@ | ||||
|     $qs('.js-acme-form-domains').hidden = false; | ||||
|   }; | ||||
|   steps[1].submit = function () { | ||||
|     info.identifiers = $qs('.js-acme-domains').value.split(/\s*,\s*/g).map(function (hostname) { | ||||
|     info.domains = $qs('.js-acme-domains').value.replace(/https?:\/\//g, ' ').replace(/,/g, ' ').trim().split(/\s+/g); | ||||
|     info.identifiers = info.domains.map(function (hostname) { | ||||
|       return { type: 'dns', value: hostname.toLowerCase().trim() }; | ||||
|     }).slice(0,1); //Disable multiple values for now.  We'll just take the first and work with it.
 | ||||
|     info.identifiers.sort(function (a, b) { | ||||
| @ -204,12 +248,14 @@ | ||||
|   steps[2].submit = function () { | ||||
|     var email = $qs('.js-acme-account-email').value.toLowerCase().trim(); | ||||
| 
 | ||||
|     info.email = email; | ||||
|     info.contact = [ 'mailto:' + email ]; | ||||
|     info.agree = $qs('.js-acme-account-tos').checked; | ||||
|     //info.greenlockAgree = $qs('.js-gl-tos').checked;
 | ||||
|     info.domains = info.identifiers.map(function (ident) { return ident.value; }); | ||||
| 
 | ||||
|     // TODO ping with version and account creation
 | ||||
|     setTimeout(saveContact, 100, email, info.identifiers.map(function (ident) { return ident.value; })); | ||||
|     setTimeout(saveContact, 100, email, info.domains); | ||||
| 
 | ||||
| 		function checkTos(tos) { | ||||
| 			if (info.agree) { | ||||
| @ -227,10 +273,10 @@ | ||||
| 			, accountKeypair: { privateKeyJwk: jwk } | ||||
| 			}).then(function (account) { | ||||
| 				console.log("account created result:", account); | ||||
| 				accountStuff.account = account; | ||||
| 				accountStuff.privateJwk = jwk; | ||||
| 				accountStuff.email = email; | ||||
| 				accountStuff.acme = acme; // TODO XXX remove
 | ||||
| 				info.account = account; | ||||
| 				info.privateJwk = jwk; | ||||
| 				info.email = email; | ||||
| 				info.acme = acme; // TODO XXX remove
 | ||||
| 			}).catch(function (err) { | ||||
| 				console.error("A bad thing happened:"); | ||||
| 				console.error(err); | ||||
| @ -241,116 +287,104 @@ | ||||
| 				}); | ||||
| 			}); | ||||
| 		}).then(function () { | ||||
|       var jwk = accountStuff.privateJwk; | ||||
|       var account = accountStuff.account; | ||||
|       var jwk = info.privateJwk; | ||||
|       var account = info.account; | ||||
| 
 | ||||
| 			return acme.orders.create({ | ||||
| 			return acme.orders.request({ | ||||
| 			  account: account | ||||
| 			, accountKeypair: { privateKeyJwk: jwk } | ||||
| 			, identifiers: info.identifiers | ||||
| 			, domains: info.domains | ||||
|       , challenges: challenges | ||||
| 			}).then(function (order) { | ||||
| 				return acme.orders.create({ | ||||
| 					signedOrder: signedOrder | ||||
| 				}).then(function (order) { | ||||
| 					accountStuff.order = order; | ||||
|           var claims = order.challenges; | ||||
|           console.log('claims:'); | ||||
|           console.log(claims); | ||||
|         info.order = order; | ||||
| 
 | ||||
|           var obj = { 'dns-01': [], 'http-01': [], 'wildcard': [] }; | ||||
|           info.challenges = obj; | ||||
|           var map = { | ||||
|             'http-01': '.js-acme-verification-http-01' | ||||
|           , 'dns-01': '.js-acme-verification-dns-01' | ||||
|           , 'wildcard': '.js-acme-verification-wildcard' | ||||
|           }; | ||||
|           options.challengePriority = [ 'http-01', 'dns-01' ]; | ||||
|         var claims = order.claims; | ||||
|         console.log('claims:'); | ||||
|         console.log(claims); | ||||
| 
 | ||||
|           // TODO make Promise-friendly
 | ||||
|           return PromiseA.all(claims.map(function (claim) { | ||||
|             var hostname = claim.identifier.value; | ||||
|             return PromiseA.all(claim.challenges.map(function (c) { | ||||
|               var keyAuth = BACME.challenges['http-01']({ | ||||
|                 token: c.token | ||||
|               , thumbprint: thumbprint | ||||
|               , challengeDomain: hostname | ||||
|               }); | ||||
|               return BACME.challenges['dns-01']({ | ||||
|                 keyAuth: keyAuth.value | ||||
|               , challengeDomain: hostname | ||||
|               }).then(function (dnsAuth) { | ||||
|                 var data = { | ||||
|                   type: c.type | ||||
|                 , hostname: hostname | ||||
|                 , url: c.url | ||||
|                 , token: c.token | ||||
|                 , keyAuthorization: keyAuth | ||||
|                 , httpPath: keyAuth.path | ||||
|                 , httpAuth: keyAuth.value | ||||
|                 , dnsType: dnsAuth.type | ||||
|                 , dnsHost: dnsAuth.host | ||||
|                 , dnsAnswer: dnsAuth.answer | ||||
|                 }; | ||||
|         var obj = { 'dns-01': [], 'http-01': [], 'wildcard': [] }; | ||||
|         info.challenges = obj; | ||||
|         /* | ||||
|         var map = { | ||||
|           'http-01': '.js-acme-verification-http-01' | ||||
|         , 'dns-01': '.js-acme-verification-dns-01' | ||||
|         , 'wildcard': '.js-acme-verification-wildcard' | ||||
|         }; | ||||
|         */ | ||||
| 
 | ||||
|                 console.log(''); | ||||
|                 console.log('CHALLENGE'); | ||||
|                 console.log(claim); | ||||
|                 console.log(c); | ||||
|                 console.log(data); | ||||
|                 console.log(''); | ||||
|         claims.forEach(function (claim) { | ||||
|           console.log("Challenge (claim):"); | ||||
|           console.log(claim); | ||||
|           var hostname = claim.identifier.value; | ||||
|           claim.challenges.forEach(function (c) { | ||||
|             var auth = c; | ||||
|             var data = { | ||||
|               type: c.type | ||||
|             , hostname: hostname | ||||
|             , url: c.url | ||||
|             , token: c.token | ||||
|             , httpPath: auth.challengeUrl | ||||
|             , httpAuth: auth.keyAuthorization | ||||
|             , dnsType: 'TXT' | ||||
|             , dnsHost: auth.dnsHost | ||||
|             , dnsAnswer: auth.keyAuthorizationDigest | ||||
|             }; | ||||
| 
 | ||||
|                 if (claim.wildcard) { | ||||
|                   obj.wildcard.push(data); | ||||
|                   let verification = $qs(".js-acme-verification-wildcard"); | ||||
|                   verification.querySelector(".js-acme-ver-hostname").innerHTML = data.hostname; | ||||
|                   verification.querySelector(".js-acme-ver-txt-host").innerHTML = data.dnsHost; | ||||
|                   verification.querySelector(".js-acme-ver-txt-value").innerHTML = data.dnsAnswer; | ||||
|             console.log(''); | ||||
|             console.log('CHALLENGE'); | ||||
|             console.log(claim); | ||||
|             console.log(c); | ||||
|             console.log(data); | ||||
|             console.log(''); | ||||
| 
 | ||||
|                 } else if(obj[data.type]) { | ||||
|             var verification; | ||||
|             if (claim.wildcard) { | ||||
|               obj.wildcard.push(data); | ||||
|               verification = $qs(".js-acme-verification-wildcard"); | ||||
|               verification.querySelector(".js-acme-ver-hostname").innerHTML = data.hostname; | ||||
|               verification.querySelector(".js-acme-ver-txt-host").innerHTML = data.dnsHost; | ||||
|               verification.querySelector(".js-acme-ver-txt-value").innerHTML = data.dnsAnswer; | ||||
| 
 | ||||
|                   obj[data.type].push(data); | ||||
|             } else if(obj[data.type]) { | ||||
| 
 | ||||
|                   if ('dns-01' === data.type) { | ||||
|                     let verification = $qs(".js-acme-verification-dns-01"); | ||||
|                     verification.querySelector(".js-acme-ver-hostname").innerHTML = data.hostname; | ||||
|                     verification.querySelector(".js-acme-ver-txt-host").innerHTML = data.dnsHost; | ||||
|                     verification.querySelector(".js-acme-ver-txt-value").innerHTML = data.dnsAnswer; | ||||
|                   } else if ('http-01' === data.type) { | ||||
|                     $qs(".js-acme-ver-file-location").innerHTML = data.httpPath.split("/").slice(-1); | ||||
|                     $qs(".js-acme-ver-content").innerHTML = data.httpAuth; | ||||
|                     $qs(".js-acme-ver-uri").innerHTML = data.httpPath; | ||||
|                     $qs(".js-download-verify-link").href = | ||||
|                       "data:text/octet-stream;base64," + window.btoa(data.httpAuth); | ||||
|                     $qs(".js-download-verify-link").download = data.httpPath.split("/").slice(-1); | ||||
|                   } | ||||
|                 } | ||||
|               obj[data.type].push(data); | ||||
| 
 | ||||
|               }); | ||||
| 
 | ||||
|             })); | ||||
|           })).then(function () { | ||||
| 
 | ||||
|             // hide wildcard if no wildcard
 | ||||
|             // hide http-01 and dns-01 if only wildcard
 | ||||
|             if (!obj.wildcard.length) { | ||||
|               $qs('.js-acme-wildcard-challenges').hidden = true; | ||||
|               if ('dns-01' === data.type) { | ||||
|                 verification = $qs(".js-acme-verification-dns-01"); | ||||
|                 verification.querySelector(".js-acme-ver-hostname").innerHTML = data.hostname; | ||||
|                 verification.querySelector(".js-acme-ver-txt-host").innerHTML = data.dnsHost; | ||||
|                 verification.querySelector(".js-acme-ver-txt-value").innerHTML = data.dnsAnswer; | ||||
|               } else if ('http-01' === data.type) { | ||||
|                 $qs(".js-acme-ver-file-location").innerHTML = data.httpPath.split("/").slice(-1); | ||||
|                 $qs(".js-acme-ver-content").innerHTML = data.httpAuth; | ||||
|                 $qs(".js-acme-ver-uri").innerHTML = data.httpPath; | ||||
|                 $qs(".js-download-verify-link").href = | ||||
|                   "data:text/octet-stream;base64," + window.btoa(data.httpAuth); | ||||
|                 $qs(".js-download-verify-link").download = data.httpPath.split("/").slice(-1); | ||||
|               } | ||||
|             } | ||||
|             if (!obj['http-01'].length) { | ||||
|               $qs('.js-acme-challenges').hidden = true; | ||||
|             } | ||||
| 
 | ||||
|             updateChallengeType(); | ||||
| 
 | ||||
|             console.log("MAGIC STEP NUMBER in 2 is:", i); | ||||
|             steps[i](); | ||||
|           }); | ||||
| 
 | ||||
|         }); | ||||
| 
 | ||||
|         // hide wildcard if no wildcard
 | ||||
|         // hide http-01 and dns-01 if only wildcard
 | ||||
|         if (!obj.wildcard.length) { | ||||
|           $qs('.js-acme-wildcard-challenges').hidden = true; | ||||
|         } | ||||
|         if (!obj['http-01'].length) { | ||||
|           $qs('.js-acme-challenges').hidden = true; | ||||
|         } | ||||
| 
 | ||||
|         updateChallengeType(); | ||||
| 
 | ||||
|         console.log("MAGIC STEP NUMBER in 2 is:", i); | ||||
|         steps[i](); | ||||
|       }); | ||||
|     }).catch(function (err) { | ||||
|       console.error('Step \'\' Error:'); | ||||
|       console.error(err, err.stack); | ||||
|       window.alert("An error happened at Step " + i + ", but it's not your fault. Email aj@rootprojects.org and let him know."); | ||||
|       window.alert("An error happened (but it's not your fault)." | ||||
|         + " Email aj@rootprojects.org to let him know that 'order and get challenges' failed."); | ||||
|     }); | ||||
|   }; | ||||
| 
 | ||||
| @ -360,58 +394,44 @@ | ||||
|     $qs('.js-acme-form-challenges').hidden = false; | ||||
|   }; | ||||
|   steps[3].submit = function () { | ||||
|     options.challengeTypes = [ 'dns-01' ]; | ||||
|     var challengePriority = [ 'dns-01' ]; | ||||
|     if ('http-01' === $qs('.js-acme-challenge-type:checked').value) { | ||||
|       options.challengeTypes.unshift('http-01'); | ||||
|       challengePriority.unshift('http-01'); | ||||
|     } | ||||
|     console.log('primary challenge type is:', options.challengeTypes[0]); | ||||
|     console.log('primary challenge type is:', challengePriority[0]); | ||||
| 
 | ||||
|     return getAccountKeypair(email).then(function (jwk) { | ||||
|     steps[i](); | ||||
|     return getAccountKeypair(info.email).then(function (jwk) { | ||||
|       // for now just show the next page immediately (its a spinner)
 | ||||
|       // TODO put a test challenge in the list
 | ||||
|       // TODO warn about wait-time if DNS
 | ||||
|       steps[i](); | ||||
| 		  return getServerKeypair().then(function () { | ||||
|         return acme.orders.finalize({ | ||||
|           account: accountStuff.account | ||||
| 		  return getServerKeypair().then(function (serverJwk) { | ||||
|         return acme.orders.complete({ | ||||
|           account: info.account | ||||
|         , accountKeypair: { privateKeyJwk: jwk } | ||||
|         , order: accountStuff.order | ||||
|         , domainKeypair: 'TODO' | ||||
|         }); | ||||
|       }).then(function (certs) { | ||||
|         console.log('WINNING!'); | ||||
|         console.log(certs); | ||||
|         $qs('#js-fullchain').innerHTML = certs; | ||||
|         $qs("#js-download-fullchain-link").href = | ||||
|           "data:text/octet-stream;base64," + window.btoa(certs); | ||||
|         , order: info.order | ||||
|         , domains: info.domains | ||||
|         , domainKeypair: { privateKeyJwk: serverJwk } | ||||
|         , challengePriority: challengePriority | ||||
|         , challenges: challenges | ||||
|         }).then(function (certs) { | ||||
|           return Keypairs.export({ jwk: serverJwk }).then(function (keyPem) { | ||||
|             console.log('WINNING!'); | ||||
|             console.log(certs); | ||||
|             $qs('#js-fullchain').innerHTML = [ | ||||
|               certs.cert.trim() + "\n" | ||||
|             , certs.chain + "\n" | ||||
|             ].join("\n"); | ||||
|             $qs("#js-download-fullchain-link").href = | ||||
|               "data:text/octet-stream;base64," + window.btoa(certs); | ||||
| 
 | ||||
|         var wcOpts; | ||||
|         var pemName; | ||||
|         if (/^R/.test(info.serverJwk.kty)) { | ||||
|           pemName = 'RSA'; | ||||
|           wcOpts = { name: "RSASSA-PKCS1-v1_5", hash: { name: "SHA-256" } }; | ||||
|         } else { | ||||
|           pemName = 'EC'; | ||||
|           wcOpts = { name: "ECDSA", namedCurve: "P-256" }; | ||||
|         } | ||||
|         return crypto.subtle.importKey( | ||||
|           "jwk" | ||||
|         , info.serverJwk | ||||
|         , wcOpts | ||||
|         , true | ||||
|         , ["sign"] | ||||
|         ).then(function (privateKey) { | ||||
|           return window.crypto.subtle.exportKey("pkcs8", privateKey); | ||||
|         }).then (function (keydata) { | ||||
|           var pem = spkiToPEM(keydata, pemName); | ||||
|           $qs('#js-privkey').innerHTML = pem; | ||||
|           $qs("#js-download-privkey-link").href = | ||||
|             "data:text/octet-stream;base64," + window.btoa(pem); | ||||
|           steps[i](); | ||||
|             $qs('#js-privkey').innerHTML = keyPem; | ||||
|             $qs("#js-download-privkey-link").href = | ||||
|               "data:text/octet-stream;base64," + window.btoa(keyPem); | ||||
|             submitForm(); | ||||
|           }); | ||||
|         }); | ||||
|       }); | ||||
|     }).then(function () { | ||||
|       return submitForm(); | ||||
|     }); | ||||
|   }; | ||||
| 
 | ||||
| @ -424,11 +444,7 @@ | ||||
|   steps[4].submit = function () { | ||||
|     console.log('Congrats! Auto advancing...'); | ||||
| 
 | ||||
| 
 | ||||
|     }).catch(function (err) { | ||||
|       console.error(err.toString()); | ||||
|       window.alert("An error happened in the final step, but it's not your fault. Email aj@rootprojects.org and let him know."); | ||||
|     }); | ||||
|     window.alert("An error happened in the final step, but it's not your fault. Email aj@rootprojects.org and let him know."); | ||||
|   }; | ||||
| 
 | ||||
|   steps[5] = function () { | ||||
| @ -436,6 +452,8 @@ | ||||
|     hideForms(); | ||||
|     $qs('.js-acme-form-download').hidden = false; | ||||
|   }; | ||||
| 
 | ||||
|   // The kickoff
 | ||||
|   steps[1](); | ||||
| 
 | ||||
|   var params = new URLSearchParams(window.location.search); | ||||
| @ -481,8 +499,8 @@ | ||||
|       return true; | ||||
|     } | ||||
| 
 | ||||
|     return testRsaSupport().then(function () { | ||||
|       console.info('[crypto] RSA is supported'); | ||||
|     return testEcdsaSupport().then(function () { | ||||
|       console.info('[crypto] ECDSA is supported'); | ||||
|     }).catch(function (err) { | ||||
|       console.error('[crypto] could not use either RSA nor EC.'); | ||||
|       console.error(err); | ||||
|  | ||||
							
								
								
									
										14
									
								
								index.html
									
									
									
									
									
								
							
							
						
						
									
										14
									
								
								index.html
									
									
									
									
									
								
							| @ -23,15 +23,10 @@ | ||||
|     </style> | ||||
|     <link rel="preload" href="./app/fonts/6xKydSBYKcSV-LCoeQqfX1RYOo3ig4vwlxdu.woff2" as="font" crossorigin="anonymous"> | ||||
|     <link rel="preload" href="./app/fonts/6xK3dSBYKcSV-LCoeQqfX1RYOo3qOK7l.woff2" as="font" crossorigin="anonymous"> | ||||
| 
 | ||||
|     <link rel="prefetch" href="./app/fonts/HI_SiYsKILxRpg3hIP6sJ7fM7PqlPevW.woff2" as="font" crossorigin="anonymous"> | ||||
|     <link rel="prefetch" href="./app/js/app.js"> | ||||
|     <link rel="prefetch" href="./app/js/bacme.js"> | ||||
|     <link rel="prefetch" href="./app/js/pkijs.org/v1.3.33/common.js"> | ||||
|     <link rel="prefetch" href="./app/js/pkijs.org/v1.3.33/asn1.js"> | ||||
|     <link rel="prefetch" href="./app/js/pkijs.org/v1.3.33/x509_schema.js"> | ||||
|     <link rel="prefetch" href="./app/js/pkijs.org/v1.3.33/x509_simpl.js"> | ||||
|     <link rel="prefetch" href="./app/js/browser-csr/v1.0.0-alpha/csr.js"> | ||||
|     <link rel="prefetch" href="./app/js/bluecrypt-acme.js" as="script"> | ||||
|     <link rel="prefetch" href="./app/js/greenlock.js" as="script"> | ||||
|     <link rel="prefetch" href="./js/app.js" as="script"> | ||||
|   </head> | ||||
|   <body class="js-app-ready"> | ||||
|     <script> | ||||
| @ -47,7 +42,8 @@ | ||||
|       </div> | ||||
|       <div class="column-row"> | ||||
|         <div class="js-javascript-warning"> | ||||
|           Greenlock will process the CSR in the browser and request the certificates directly from letsencrypt.org.  Please enable Javascript before continuing. | ||||
|           Greenlock will process the CSR in the browser and request the certificates directly from letsencrypt.org. | ||||
|           Please enable Javascript before continuing. | ||||
|         </div> | ||||
|         <form id="js-acme-form" action="./app/" method=> | ||||
|           <div class="domain-psuedo-input"> | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user