├── LICENSE ├── README.md ├── index.html └── js ├── asn1js ├── LICENSE ├── asn1.js ├── base64.js ├── hex.js └── int10.js └── index.js /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Daniel Roesler 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Get HTTPS for free! 2 | 3 | Website: https://gethttpsforfree.com 4 | 5 | This is a project that allows you to get a free HTTPS certificate without 6 | having to install any software or having to share your private keys with anyone. 7 | It uses the non-profit [Let's Encrypt](https://letsencrypt.org/) certificate 8 | authority to issue the free certificates. Hooray for free certs! 9 | 10 | ## Donate 11 | 12 | If this script is useful to you, please donate to the EFF. I don't work there, 13 | but they do fantastic work. 14 | 15 | [https://eff.org/donate/](https://eff.org/donate/) 16 | 17 | ## How to use this website 18 | 19 | Go to: https://gethttpsforfree.com 20 | 21 | The website works by generating commands for you to run in your terminal, then 22 | making requests to the Let's Encrypt ACME API to issue your certificate. Simply 23 | visit the above website and follow the steps! If you don't know how to do 24 | something, try clicking the help links that explain how to complete the step. If 25 | you're still confused, please create an issue and I'll address your issue ASAP! 26 | 27 | Requirements for your local machine: 28 | * openssl 29 | * echo 30 | 31 | Requirements for your server: 32 | * python or any webserver that can serve a static file 33 | 34 | These should all be installed by default in Linux and Mac OSX. If you're 35 | running Windows, you might need to install [Cygwin](https://cygwin.com/install.html) 36 | to get openssl and echo working on Windows. 37 | 38 | ## How this website works 39 | 40 | This website works by making requests to the Let's Encrypt [API](https://acme-v02.api.letsencrypt.org/directory) 41 | (using the [ACME](https://github.com/ietf-wg-acme/acme) protocol). There's 5 steps to the process, 42 | which are explained below. Also, I encourage you to read the source code (it's not that long) and 43 | pop open your browser's debugger to see the ajax requests that are going on. Please, audit this! 44 | 45 | ### Step 1: Account Info 46 | 47 | First, the ACME protocol requires you register a public key and contact information 48 | so you can sign all the requests you make to the API. In this step, you need to 49 | put in an email and a public key. The javascript for this section then converts the 50 | public key to a JSON Web Key ([JWK](https://tools.ietf.org/html/rfc7517)). NOTE: 51 | currently only RSA 2048 and 4096 bit public keys are accepted by Let's Encrypt. 52 | 53 | So if you paste it in this public key: 54 | ``` 55 | -----BEGIN PUBLIC KEY----- 56 | MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA5aok6d72rkrGpOPAICSS 57 | 3JPrA0tbVs3mYPWmG7c5tGEY+w1slyI+3V64NsLw8p9YqNLyX/YDsnmkOUMUx6Bu 58 | vx43daBrl//wz3hIOvidXyV4z65Nbrlto9qtLpfi+9lbEEYt2PLhr+KjguqjqOQj 59 | qi2PgqdITGG+BZkU8xIrPzZCR/UPBotV/dGBj9vO1whTGlzpkihvXLf4rEFoJoEE 60 | eOPMtqbxUp1KS41EgX2xFav9JHPVI1hm66K0eqlJrBl407j3xRNlekl4xorwfCkA 61 | xC7xclofg3JZ7RIhv3DdaNe07IZ0QYup9dDufIcCKruAgu0hwYMwDHmZNrrWxMia 62 | GQwagxs61mla6f7c1bvYY92PhfgpkQAN99MXdaTtvBbzDuY018QP+TVzzVH/hpjK 63 | aFx4JlYkcVGqbYamUiP7il4Hldqp6Mm65IH/8nxuZFrN4tJ5VyMeWeZ5sKBBrXZE 64 | 1Je8524COYnvljGnaFAVaDRhAcTSEykveY8jx/r6MB95LkWcue7FXIQyX0D3/2lU 65 | KTu/wrBCmhriqNa4FHcccLMyQkiMbs8mEoldNCwYDxvF5lYc19UDlleE855lME00 66 | E/ogStmazzFrNWCzEJ+Pa9JVlTQonKRgWqi+9cWwV+AMd+s2F3wO+H2tlexe8pLo 67 | Vw/42S44tHz4VuZuhpZvn3kCAwEAAQ== 68 | -----END PUBLIC KEY----- 69 | ``` 70 | 71 | This step converts it to this JWK: 72 | ``` 73 | { 74 | "alg": "RS256", 75 | "jwk": { 76 | "e": "AQAB", 77 | "kty": "RSA", 78 | "n": "5aok6d72rkrGpOPAICSS3JPrA0tbVs3mYPWmG7c5tGEY-w1slyI-3V64NsLw8p9YqNLyX_YDsnmkOUMUx6Buvx43daBrl__wz3hIOvidXyV4z65Nbrlto9qtLpfi-9lbEEYt2PLhr-KjguqjqOQjqi2PgqdITGG-BZkU8xIrPzZCR_UPBotV_dGBj9vO1whTGlzpkihvXLf4rEFoJoEEeOPMtqbxUp1KS41EgX2xFav9JHPVI1hm66K0eqlJrBl407j3xRNlekl4xorwfCkAxC7xclofg3JZ7RIhv3DdaNe07IZ0QYup9dDufIcCKruAgu0hwYMwDHmZNrrWxMiaGQwagxs61mla6f7c1bvYY92PhfgpkQAN99MXdaTtvBbzDuY018QP-TVzzVH_hpjKaFx4JlYkcVGqbYamUiP7il4Hldqp6Mm65IH_8nxuZFrN4tJ5VyMeWeZ5sKBBrXZE1Je8524COYnvljGnaFAVaDRhAcTSEykveY8jx_r6MB95LkWcue7FXIQyX0D3_2lUKTu_wrBCmhriqNa4FHcccLMyQkiMbs8mEoldNCwYDxvF5lYc19UDlleE855lME00E_ogStmazzFrNWCzEJ-Pa9JVlTQonKRgWqi-9cWwV-AMd-s2F3wO-H2tlexe8pLoVw_42S44tHz4VuZuhpZvn3k" 79 | } 80 | } 81 | ``` 82 | 83 | ### Step 2: Certificate Signing Request 84 | 85 | Second, you need to specify the domains you want certificates for. That's done 86 | through a certificate signing request ([CSR](https://en.wikipedia.org/wiki/Certificate_signing_request)). 87 | The javascript in this section uses the [ASN1.js](https://lapo.it/asn1js/) library 88 | to parse the CSR and read the domains. NOTE: the private key for the domain cert 89 | cannot be the same as your account private key, according to ACME. 90 | 91 | ### Step 3: Sign API Requests 92 | 93 | Third, you need tell the Let's Encrypt API that you want to register and create an order 94 | for a certificate (your CSR). These requests must be signed with your account private key, so 95 | this steps compiles the request payloads that need signatures to get the domain challenges 96 | you need to fulfill. 97 | 98 | Here's the list of requests that need to be made to the API: 99 | 100 | * `/acme/new-acct` - Register the account public key and accept the terms (discarded if already registered) 101 | * `/acme/acct/...` - Update the account with your email address 102 | * `/acme/new-order` - Creates a new order for a certificate for your domains in your CSR 103 | 104 | NOTE: Each request also requires an anti-replay nonce, so the javascript gets 105 | those by making ajax requests to the `/acme/new-nonce` endpoint. 106 | 107 | For each request the payload must be signed, and since this website doesn't ask 108 | for your private keys, you must copy-and-paste the signature commands into your 109 | terminal. 110 | 111 | These commands are structured like this: 112 | ``` 113 | PRIV_KEY=./account.key; \ #set the location of your account private key (change this location if different) 114 | echo -n "" | \ #pipe the payload into openssl 115 | openssl dgst -sha256 -hex -sign $PRIV_KEY #sign the payload using your private key and output hex 116 | ``` 117 | 118 | Once these signatures are pasted back into the inputs, the javascript makes the 119 | ajax requests to the above endpoints for `new-acct` and each `new-order`. If the 120 | account public key has already been registered the `new-acct` response is a 204 121 | No Content, which is ignored. 122 | 123 | ### Step 4: Verify Ownership 124 | 125 | The response for the `/new-order` has links to the authorization challenges needed 126 | prove you own the domain. The challenge that this website chooses is "http-01" or 127 | "dns-01", which requires that you host a specific file at a specific location 128 | or set a specific TXT value in your DNS for that domain. So, for 129 | each domain, this step shows you the file you need to host and the url you need 130 | to host it at. 131 | 132 | After the file is being hosted, you need to tell Let's Encrypt to check the 133 | verify the challenge for that domain. That request must also be signed so 134 | there's one more signature that must be performed. The reason why this wasn't 135 | included in step 3 is because the payload contains something in the response of 136 | `/new-order`. 137 | 138 | There's three options this website offers as copy-and-paste commands: python, file-based, 139 | and dns. The python command is a mini server you can copy-and-paste into your 140 | server's command line (NOTE: this needs sudo permissions!). The file-base option 141 | just lists the url where the challenge will check and the file contents that the 142 | file needs to contain. The DNS option lists the value you need to set as a TXT 143 | entry in your DNS. It's up to you to figure out how to make that happen. 144 | 145 | When you confirm that you're hosting the files, an ajax request is made to the 146 | challenge url to tell Let's Encrypt to verify the domain. Once this is done for 147 | all the domains in your CSR, the final signature is to finalize the order and 148 | sign your certificate. 149 | 150 | ### Step 5: Install Certificate 151 | 152 | The response from finalizing should be your new certificate! Congrats! This 153 | step prints the certificate and also prints the intermediate certificate you 154 | need to chain this certificate to the root certificate. 155 | 156 | ## Privacy 157 | 158 | This website is entirely static files and only makes ajax requests to the 159 | Let's Encrypt API. It does not track or remember anything when you leave. 160 | It is written with minimal extra libraries and styling to ensure that you 161 | can read through and audit the source code. 162 | 163 | Finally, since this website is completely static, it's un-hostable! Just 164 | right-click and "Save Page As...", save the complete website to your local 165 | computer, then open it in a browser. It still works when hosted locally! 166 | 167 | ## Feedback/Contributing 168 | 169 | I'd love to receive feedback, issues, and pull requests to make this script 170 | better. The main script itself, `js/index.js`, is less than 800 lines of code, so 171 | feel free to read through it! I tried to comment things well and make it crystal 172 | clear what it's doing. 173 | 174 | TODO (pull requests welcome): 175 | * `revoke.html` - A page with steps for revoking certificates 176 | * ~~Alternative file-based command instead of python server~~ 177 | * ~~Installation instructions for Apache~~ 178 | * Accept GPG public keys as account public keys 179 | 180 | ## What's NOT on the Roadmap 181 | 182 | * Third party libraries (asn1.js is the only one) 183 | * Fonts or images 184 | * CSS more than 5 kilobytes 185 | * Javascript that only changes UI 186 | * HTML that decreases source readability (added wrapping divs, etc.) 187 | 188 | This website is supposed to [work](http://motherfuckingwebsite.com/), nothing more. 189 | 190 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 22 | 23 | Get HTTPS for free! 24 | 25 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 |

71 | Get HTTPS for free! 72 | Update: Added wildcard certificate support! 73 |

74 | 79 |
80 | You can now get free https certificates (incuding wildcard certificates) 81 | from the non-profit certificate authority 82 | Let's Encrypt! 83 | This is a website that will take you through the manual steps to get your free 84 | https certificate so you can make your own website use https! This website is 85 | open source 86 | and NEVER asks for your private keys. Never trust a website 87 | that asks for your private keys! 88 | 89 |
90 | 91 |
92 | NOTE: This website is for people who know how to generate certificate signing 93 | requests (CSRs)! If you're not familiar with how to do this, please use the 94 | official Let's Encrypt official client 95 | that can automatically issue and install https certificates for you. This 96 | website is designed for people who know what they are doing and just want to get 97 | their free https certificate. 98 |
99 | 100 |
101 | If you need to renew a certificate, simply complete these steps below again. 102 |
103 | 104 |
105 | 106 | 107 | 108 | 109 |

Step 1: Account Info

110 |
111 | Let's Encrypt requires that you register an account email and public key before 112 | issuing a certificate. The email is so that they can contact you if needed, and 113 | the public key is so you can securely sign your requests to issue/revoke/renew 114 | your certificates. Keep your account private key secret! Anyone who has it 115 | can impersonate you when making requests to Let's Encrypt! 116 |
117 | 118 |
119 |
120 |
121 | 122 |
123 | 124 |
125 | 126 | 127 |
128 | How to generate a new account keypair using openssl:
129 |
    130 |
  1. 131 | Generate an account private key if you don't have one:
    132 | (KEEP ACCOUNT.KEY SECRET!)
    133 | openssl genrsa 4096 > account.key 134 |
  2. 135 |
  3. 136 | Print your public key:
    137 | openssl rsa -in account.key -pubout 138 |
  4. 139 |
  5. 140 | Copy and paste the public key into the box below.
    141 |
  6. 142 |
143 |
144 |
145 | 146 |
147 | 148 |
149 | 150 | 151 |
152 |
153 | 154 |
155 | 156 | 157 | 158 | 159 |

Step 2: Certificate Signing Request

160 |
161 | This is the certificate signing request (CSR) that you send to Let's Encrypt 162 | in order to issue you a signed certificate. It contains the website domains you 163 | want to issue certs for and the public key of your TLS private key. Keep your 164 | TLS private key secret! Anyone who has it can man-in-the-middle your website! 165 |
166 | 167 |
168 |
169 | 170 | 171 |
172 | How to generate a new Certificate Signing Request (CSR):
173 |
    174 |
  1. 175 | Generate a TLS private key if you don't have one:
    176 | (KEEP DOMAIN.KEY SECRET!)
    177 | openssl genrsa 4096 > domain.key 178 |
  2. 179 |
  3. 180 | Generate a CSR for your the domains you want certs for:
    181 | (replace "foo.com" with your domain)
    182 | Linux: 183 |
    184 |     #change "/etc/ssl/openssl.cnf" as needed:
    185 |     #  Debian: /etc/ssl/openssl.cnf
    186 |     #  RHEL and CentOS: /etc/pki/tls/openssl.cnf
    187 |     #  Mac OSX: /System/Library/OpenSSL/openssl.cnf
    188 | 
    189 |     openssl req -new -sha256 -key domain.key -subj "/" \
    190 |       -reqexts SAN -config <(cat /etc/ssl/openssl.cnf \
    191 |       <(printf "\n[SAN]\nsubjectAltName=DNS:foo.com,DNS:www.foo.com"))
    192 |     
    193 |
  4. 194 |
  5. 195 | Copy and paste the CSR into the box below.
    196 |
  6. 197 |
198 |
199 |
200 | 201 |
202 | 203 |
204 | 205 | 206 |
207 |
208 | 209 |
210 | 211 | 212 | 213 | 214 |

Step 3: Sign API Requests (waiting...)

215 | 328 | 329 |
330 | 331 | 332 | 333 | 334 |

Step 4: Verify Ownership (waiting...)

335 | 853 | 854 |
855 | 856 | 857 | 858 | 859 |

Step 5: Install Certificate (waiting...)

860 | 975 | 976 |
977 | 978 | 983 | 984 | 985 | 986 | -------------------------------------------------------------------------------- /js/asn1js/LICENSE: -------------------------------------------------------------------------------- 1 | ASN.1 JavaScript decoder Copyright (c) 2008-2014 Lapo Luchini 2 | 3 | Permission to use, copy, modify, and/or distribute this software for any 4 | purpose with or without fee is hereby granted, provided that the above 5 | copyright notice and this permission notice appear in all copies. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | 15 | -------------------------------------------------------------------------------- /js/asn1js/asn1.js: -------------------------------------------------------------------------------- 1 | // ASN.1 JavaScript decoder 2 | // Copyright (c) 2008-2014 Lapo Luchini 3 | 4 | // Permission to use, copy, modify, and/or distribute this software for any 5 | // purpose with or without fee is hereby granted, provided that the above 6 | // copyright notice and this permission notice appear in all copies. 7 | // 8 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | // ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | // OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | 16 | /*jshint browser: true, strict: true, immed: true, latedef: true, undef: true, regexdash: false */ 17 | /*global oids */ 18 | (function (undefined) { 19 | "use strict"; 20 | 21 | var Int10 = (typeof module !== 'undefined') ? require('./int10.js') : window.Int10, 22 | ellipsis = "\u2026", 23 | reTimeS = /^(\d\d)(0[1-9]|1[0-2])(0[1-9]|[12]\d|3[01])([01]\d|2[0-3])(?:([0-5]\d)(?:([0-5]\d)(?:[.,](\d{1,3}))?)?)?(Z|[-+](?:[0]\d|1[0-2])([0-5]\d)?)?$/, 24 | reTimeL = /^(\d\d\d\d)(0[1-9]|1[0-2])(0[1-9]|[12]\d|3[01])([01]\d|2[0-3])(?:([0-5]\d)(?:([0-5]\d)(?:[.,](\d{1,3}))?)?)?(Z|[-+](?:[0]\d|1[0-2])([0-5]\d)?)?$/; 25 | 26 | function stringCut(str, len) { 27 | if (str.length > len) 28 | str = str.substring(0, len) + ellipsis; 29 | return str; 30 | } 31 | 32 | function Stream(enc, pos) { 33 | if (enc instanceof Stream) { 34 | this.enc = enc.enc; 35 | this.pos = enc.pos; 36 | } else { 37 | // enc should be an array or a binary string 38 | this.enc = enc; 39 | this.pos = pos; 40 | } 41 | } 42 | Stream.prototype.get = function (pos) { 43 | if (pos === undefined) 44 | pos = this.pos++; 45 | if (pos >= this.enc.length) 46 | throw 'Requesting byte offset ' + pos + ' on a stream of length ' + this.enc.length; 47 | return (typeof this.enc == "string") ? this.enc.charCodeAt(pos) : this.enc[pos]; 48 | }; 49 | Stream.prototype.hexDigits = "0123456789ABCDEF"; 50 | Stream.prototype.hexByte = function (b) { 51 | return this.hexDigits.charAt((b >> 4) & 0xF) + this.hexDigits.charAt(b & 0xF); 52 | }; 53 | Stream.prototype.hexDump = function (start, end, raw) { 54 | var s = ""; 55 | for (var i = start; i < end; ++i) { 56 | s += this.hexByte(this.get(i)); 57 | if (raw !== true) 58 | switch (i & 0xF) { 59 | case 0x7: s += " "; break; 60 | case 0xF: s += "\n"; break; 61 | default: s += " "; 62 | } 63 | } 64 | return s; 65 | }; 66 | Stream.prototype.isASCII = function (start, end) { 67 | for (var i = start; i < end; ++i) { 68 | var c = this.get(i); 69 | if (c < 32 || c > 176) 70 | return false; 71 | } 72 | return true; 73 | }; 74 | Stream.prototype.parseStringISO = function (start, end) { 75 | var s = ""; 76 | for (var i = start; i < end; ++i) 77 | s += String.fromCharCode(this.get(i)); 78 | return s; 79 | }; 80 | Stream.prototype.parseStringUTF = function (start, end) { 81 | var s = ""; 82 | for (var i = start; i < end; ) { 83 | var c = this.get(i++); 84 | if (c < 128) 85 | s += String.fromCharCode(c); 86 | else if ((c > 191) && (c < 224)) 87 | s += String.fromCharCode(((c & 0x1F) << 6) | (this.get(i++) & 0x3F)); 88 | else 89 | s += String.fromCharCode(((c & 0x0F) << 12) | ((this.get(i++) & 0x3F) << 6) | (this.get(i++) & 0x3F)); 90 | } 91 | return s; 92 | }; 93 | Stream.prototype.parseStringBMP = function (start, end) { 94 | var str = "", hi, lo; 95 | for (var i = start; i < end; ) { 96 | hi = this.get(i++); 97 | lo = this.get(i++); 98 | str += String.fromCharCode((hi << 8) | lo); 99 | } 100 | return str; 101 | }; 102 | Stream.prototype.parseTime = function (start, end, shortYear) { 103 | var s = this.parseStringISO(start, end), 104 | m = (shortYear ? reTimeS : reTimeL).exec(s); 105 | if (!m) 106 | return "Unrecognized time: " + s; 107 | if (shortYear) { 108 | // to avoid querying the timer, use the fixed range [1970, 2069] 109 | // it will conform with ITU X.400 [-10, +40] sliding window until 2030 110 | m[1] = +m[1]; 111 | m[1] += (m[1] < 70) ? 2000 : 1900; 112 | } 113 | s = m[1] + "-" + m[2] + "-" + m[3] + " " + m[4]; 114 | if (m[5]) { 115 | s += ":" + m[5]; 116 | if (m[6]) { 117 | s += ":" + m[6]; 118 | if (m[7]) 119 | s += "." + m[7]; 120 | } 121 | } 122 | if (m[8]) { 123 | s += " UTC"; 124 | if (m[8] != 'Z') { 125 | s += m[8]; 126 | if (m[9]) 127 | s += ":" + m[9]; 128 | } 129 | } 130 | return s; 131 | }; 132 | Stream.prototype.parseInteger = function (start, end) { 133 | var v = this.get(start), 134 | neg = (v > 127), 135 | pad = neg ? 255 : 0, 136 | len, 137 | s = ''; 138 | // skip unuseful bits (not allowed in DER) 139 | while (v == pad && ++start < end) 140 | v = this.get(start); 141 | len = end - start; 142 | if (len === 0) 143 | return neg ? -1 : 0; 144 | // show bit length of huge integers 145 | if (len > 4) { 146 | s = v; 147 | len <<= 3; 148 | while (((s ^ pad) & 0x80) == 0) { 149 | s <<= 1; 150 | --len; 151 | } 152 | s = "(" + len + " bit)\n"; 153 | } 154 | // decode the integer 155 | if (neg) v = v - 256; 156 | var n = new Int10(v); 157 | for (var i = start + 1; i < end; ++i) 158 | n.mulAdd(256, this.get(i)); 159 | return s + n.toString(); 160 | }; 161 | Stream.prototype.parseBitString = function (start, end, maxLength) { 162 | var unusedBit = this.get(start), 163 | lenBit = ((end - start - 1) << 3) - unusedBit, 164 | intro = "(" + lenBit + " bit)\n", 165 | s = ""; 166 | for (var i = start + 1; i < end; ++i) { 167 | var b = this.get(i), 168 | skip = (i == end - 1) ? unusedBit : 0; 169 | for (var j = 7; j >= skip; --j) 170 | s += (b >> j) & 1 ? "1" : "0"; 171 | if (s.length > maxLength) 172 | return intro + stringCut(s, maxLength); 173 | } 174 | return intro + s; 175 | }; 176 | Stream.prototype.parseOctetString = function (start, end, maxLength) { 177 | if (this.isASCII(start, end)) 178 | return stringCut(this.parseStringISO(start, end), maxLength); 179 | var len = end - start, 180 | s = "(" + len + " byte)\n"; 181 | maxLength /= 2; // we work in bytes 182 | if (len > maxLength) 183 | end = start + maxLength; 184 | for (var i = start; i < end; ++i) 185 | s += this.hexByte(this.get(i)); 186 | if (len > maxLength) 187 | s += ellipsis; 188 | return s; 189 | }; 190 | Stream.prototype.parseOID = function (start, end, maxLength) { 191 | var s = '', 192 | n = new Int10(), 193 | bits = 0; 194 | for (var i = start; i < end; ++i) { 195 | var v = this.get(i); 196 | n.mulAdd(128, v & 0x7F); 197 | bits += 7; 198 | if (!(v & 0x80)) { // finished 199 | if (s === '') { 200 | n = n.simplify(); 201 | var m = n < 80 ? n < 40 ? 0 : 1 : 2; 202 | s = m + "." + (n - m * 40); 203 | } else 204 | s += "." + n.toString(); 205 | if (s.length > maxLength) 206 | return stringCut(s, maxLength); 207 | n = new Int10(); 208 | bits = 0; 209 | } 210 | } 211 | if (bits > 0) 212 | s += ".incomplete"; 213 | return s; 214 | }; 215 | 216 | function ASN1(stream, header, length, tag, sub) { 217 | if (!(tag instanceof ASN1Tag)) throw 'Invalid tag value.'; 218 | this.stream = stream; 219 | this.header = header; 220 | this.length = length; 221 | this.tag = tag; 222 | this.sub = sub; 223 | } 224 | ASN1.prototype.typeName = function () { 225 | switch (this.tag.tagClass) { 226 | case 0: // universal 227 | switch (this.tag.tagNumber) { 228 | case 0x00: return "EOC"; 229 | case 0x01: return "BOOLEAN"; 230 | case 0x02: return "INTEGER"; 231 | case 0x03: return "BIT_STRING"; 232 | case 0x04: return "OCTET_STRING"; 233 | case 0x05: return "NULL"; 234 | case 0x06: return "OBJECT_IDENTIFIER"; 235 | case 0x07: return "ObjectDescriptor"; 236 | case 0x08: return "EXTERNAL"; 237 | case 0x09: return "REAL"; 238 | case 0x0A: return "ENUMERATED"; 239 | case 0x0B: return "EMBEDDED_PDV"; 240 | case 0x0C: return "UTF8String"; 241 | case 0x10: return "SEQUENCE"; 242 | case 0x11: return "SET"; 243 | case 0x12: return "NumericString"; 244 | case 0x13: return "PrintableString"; // ASCII subset 245 | case 0x14: return "TeletexString"; // aka T61String 246 | case 0x15: return "VideotexString"; 247 | case 0x16: return "IA5String"; // ASCII 248 | case 0x17: return "UTCTime"; 249 | case 0x18: return "GeneralizedTime"; 250 | case 0x19: return "GraphicString"; 251 | case 0x1A: return "VisibleString"; // ASCII subset 252 | case 0x1B: return "GeneralString"; 253 | case 0x1C: return "UniversalString"; 254 | case 0x1E: return "BMPString"; 255 | } 256 | return "Universal_" + this.tag.tagNumber.toString(); 257 | case 1: return "Application_" + this.tag.tagNumber.toString(); 258 | case 2: return "[" + this.tag.tagNumber.toString() + "]"; // Context 259 | case 3: return "Private_" + this.tag.tagNumber.toString(); 260 | } 261 | }; 262 | ASN1.prototype.content = function (maxLength) { // a preview of the content (intended for humans) 263 | if (this.tag === undefined) 264 | return null; 265 | if (maxLength === undefined) 266 | maxLength = Infinity; 267 | var content = this.posContent(), 268 | len = Math.abs(this.length); 269 | if (!this.tag.isUniversal()) { 270 | if (this.sub !== null) 271 | return "(" + this.sub.length + " elem)"; 272 | return this.stream.parseOctetString(content, content + len, maxLength); 273 | } 274 | switch (this.tag.tagNumber) { 275 | case 0x01: // BOOLEAN 276 | return (this.stream.get(content) === 0) ? "false" : "true"; 277 | case 0x02: // INTEGER 278 | return this.stream.parseInteger(content, content + len); 279 | case 0x03: // BIT_STRING 280 | return this.sub ? "(" + this.sub.length + " elem)" : 281 | this.stream.parseBitString(content, content + len, maxLength); 282 | case 0x04: // OCTET_STRING 283 | return this.sub ? "(" + this.sub.length + " elem)" : 284 | this.stream.parseOctetString(content, content + len, maxLength); 285 | //case 0x05: // NULL 286 | case 0x06: // OBJECT_IDENTIFIER 287 | return this.stream.parseOID(content, content + len, maxLength); 288 | //case 0x07: // ObjectDescriptor 289 | //case 0x08: // EXTERNAL 290 | //case 0x09: // REAL 291 | //case 0x0A: // ENUMERATED 292 | //case 0x0B: // EMBEDDED_PDV 293 | case 0x10: // SEQUENCE 294 | case 0x11: // SET 295 | return "(" + this.sub.length + " elem)"; 296 | case 0x0C: // UTF8String 297 | return stringCut(this.stream.parseStringUTF(content, content + len), maxLength); 298 | case 0x12: // NumericString 299 | case 0x13: // PrintableString 300 | case 0x14: // TeletexString 301 | case 0x15: // VideotexString 302 | case 0x16: // IA5String 303 | //case 0x19: // GraphicString 304 | case 0x1A: // VisibleString 305 | //case 0x1B: // GeneralString 306 | //case 0x1C: // UniversalString 307 | return stringCut(this.stream.parseStringISO(content, content + len), maxLength); 308 | case 0x1E: // BMPString 309 | return stringCut(this.stream.parseStringBMP(content, content + len), maxLength); 310 | case 0x17: // UTCTime 311 | case 0x18: // GeneralizedTime 312 | return this.stream.parseTime(content, content + len, (this.tag.tagNumber == 0x17)); 313 | } 314 | return null; 315 | }; 316 | ASN1.prototype.toString = function () { 317 | return this.typeName() + "@" + this.stream.pos + "[header:" + this.header + ",length:" + this.length + ",sub:" + ((this.sub === null) ? 'null' : this.sub.length) + "]"; 318 | }; 319 | ASN1.prototype.toPrettyString = function (indent) { 320 | if (indent === undefined) indent = ''; 321 | var s = indent + this.typeName() + " @" + this.stream.pos; 322 | if (this.length >= 0) 323 | s += "+"; 324 | s += this.length; 325 | if (this.tag.tagConstructed) 326 | s += " (constructed)"; 327 | else if ((this.tag.isUniversal() && ((this.tag.tagNumber == 0x03) || (this.tag.tagNumber == 0x04))) && (this.sub !== null)) 328 | s += " (encapsulates)"; 329 | s += "\n"; 330 | if (this.sub !== null) { 331 | indent += ' '; 332 | for (var i = 0, max = this.sub.length; i < max; ++i) 333 | s += this.sub[i].toPrettyString(indent); 334 | } 335 | return s; 336 | }; 337 | ASN1.prototype.posStart = function () { 338 | return this.stream.pos; 339 | }; 340 | ASN1.prototype.posContent = function () { 341 | return this.stream.pos + this.header; 342 | }; 343 | ASN1.prototype.posEnd = function () { 344 | return this.stream.pos + this.header + Math.abs(this.length); 345 | }; 346 | ASN1.prototype.toHexString = function (root) { 347 | return this.stream.hexDump(this.posStart(), this.posEnd(), true); 348 | }; 349 | ASN1.decodeLength = function (stream) { 350 | var buf = stream.get(), 351 | len = buf & 0x7F; 352 | if (len == buf) 353 | return len; 354 | if (len > 6) // no reason to use Int10, as it would be a huge buffer anyways 355 | throw "Length over 48 bits not supported at position " + (stream.pos - 1); 356 | if (len === 0) 357 | return null; // undefined 358 | buf = 0; 359 | for (var i = 0; i < len; ++i) 360 | buf = (buf * 256) + stream.get(); 361 | return buf; 362 | }; 363 | function ASN1Tag(stream) { 364 | var buf = stream.get(); 365 | this.tagClass = buf >> 6; 366 | this.tagConstructed = ((buf & 0x20) !== 0); 367 | this.tagNumber = buf & 0x1F; 368 | if (this.tagNumber == 0x1F) { // long tag 369 | var n = new Int10(); 370 | do { 371 | buf = stream.get(); 372 | n.mulAdd(128, buf & 0x7F); 373 | } while (buf & 0x80); 374 | this.tagNumber = n.simplify(); 375 | } 376 | } 377 | ASN1Tag.prototype.isUniversal = function () { 378 | return this.tagClass === 0x00; 379 | }; 380 | ASN1Tag.prototype.isEOC = function () { 381 | return this.tagClass === 0x00 && this.tagNumber === 0x00; 382 | }; 383 | ASN1.decode = function (stream) { 384 | if (!(stream instanceof Stream)) 385 | stream = new Stream(stream, 0); 386 | var streamStart = new Stream(stream), 387 | tag = new ASN1Tag(stream), 388 | len = ASN1.decodeLength(stream), 389 | start = stream.pos, 390 | header = start - streamStart.pos, 391 | sub = null, 392 | getSub = function () { 393 | sub = []; 394 | if (len !== null) { 395 | // definite length 396 | var end = start + len; 397 | while (stream.pos < end) 398 | sub[sub.length] = ASN1.decode(stream); 399 | if (stream.pos != end) 400 | throw "Content size is not correct for container starting at offset " + start; 401 | } else { 402 | // undefined length 403 | try { 404 | for (;;) { 405 | var s = ASN1.decode(stream); 406 | if (s.tag.isEOC()) 407 | break; 408 | sub[sub.length] = s; 409 | } 410 | len = start - stream.pos; // undefined lengths are represented as negative values 411 | } catch (e) { 412 | throw "Exception while decoding undefined length content: " + e; 413 | } 414 | } 415 | }; 416 | if (tag.tagConstructed) { 417 | // must have valid content 418 | getSub(); 419 | } else if (tag.isUniversal() && ((tag.tagNumber == 0x03) || (tag.tagNumber == 0x04))) { 420 | // sometimes BitString and OctetString are used to encapsulate ASN.1 421 | try { 422 | if (tag.tagNumber == 0x03) 423 | if (stream.get() != 0) 424 | throw "BIT STRINGs with unused bits cannot encapsulate."; 425 | getSub(); 426 | for (var i = 0; i < sub.length; ++i) 427 | if (sub[i].tag.isEOC()) 428 | throw 'EOC is not supposed to be actual content.'; 429 | } catch (e) { 430 | // but silently ignore when they don't 431 | sub = null; 432 | } 433 | } 434 | if (sub === null) { 435 | if (len === null) 436 | throw "We can't skip over an invalid tag with undefined length at offset " + start; 437 | stream.pos = start + Math.abs(len); 438 | } 439 | return new ASN1(streamStart, header, len, tag, sub); 440 | }; 441 | 442 | // export globals 443 | if (typeof module !== 'undefined') { module.exports = ASN1; } else { window.ASN1 = ASN1; } 444 | })(); 445 | 446 | -------------------------------------------------------------------------------- /js/asn1js/base64.js: -------------------------------------------------------------------------------- 1 | // Base64 JavaScript decoder 2 | // Copyright (c) 2008-2014 Lapo Luchini 3 | 4 | // Permission to use, copy, modify, and/or distribute this software for any 5 | // purpose with or without fee is hereby granted, provided that the above 6 | // copyright notice and this permission notice appear in all copies. 7 | // 8 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | // ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | // OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | 16 | /*jshint browser: true, strict: true, immed: true, latedef: true, undef: true, regexdash: false */ 17 | (function (undefined) { 18 | "use strict"; 19 | 20 | var Base64 = {}, 21 | decoder; 22 | 23 | Base64.decode = function (a) { 24 | var i; 25 | if (decoder === undefined) { 26 | var b64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/", 27 | ignore = "= \f\n\r\t\u00A0\u2028\u2029"; 28 | decoder = []; 29 | for (i = 0; i < 64; ++i) 30 | decoder[b64.charAt(i)] = i; 31 | for (i = 0; i < ignore.length; ++i) 32 | decoder[ignore.charAt(i)] = -1; 33 | } 34 | var out = []; 35 | var bits = 0, char_count = 0; 36 | for (i = 0; i < a.length; ++i) { 37 | var c = a.charAt(i); 38 | if (c == '=') 39 | break; 40 | c = decoder[c]; 41 | if (c == -1) 42 | continue; 43 | if (c === undefined) 44 | throw 'Illegal character at offset ' + i; 45 | bits |= c; 46 | if (++char_count >= 4) { 47 | out[out.length] = (bits >> 16); 48 | out[out.length] = (bits >> 8) & 0xFF; 49 | out[out.length] = bits & 0xFF; 50 | bits = 0; 51 | char_count = 0; 52 | } else { 53 | bits <<= 6; 54 | } 55 | } 56 | switch (char_count) { 57 | case 1: 58 | throw "Base64 encoding incomplete: at least 2 bits missing"; 59 | case 2: 60 | out[out.length] = (bits >> 10); 61 | break; 62 | case 3: 63 | out[out.length] = (bits >> 16); 64 | out[out.length] = (bits >> 8) & 0xFF; 65 | break; 66 | } 67 | return out; 68 | }; 69 | 70 | Base64.re = /-----BEGIN [^-]+-----([A-Za-z0-9+\/=\s]+)-----END [^-]+-----|begin-base64[^\n]+\n([A-Za-z0-9+\/=\s]+)====/; 71 | Base64.unarmor = function (a) { 72 | var m = Base64.re.exec(a); 73 | if (m) { 74 | if (m[1]) 75 | a = m[1]; 76 | else if (m[2]) 77 | a = m[2]; 78 | else 79 | throw "RegExp out of sync"; 80 | } 81 | return Base64.decode(a); 82 | }; 83 | 84 | // export globals 85 | if (typeof module !== 'undefined') { module.exports = Base64; } else { window.Base64 = Base64; } 86 | })(); 87 | 88 | -------------------------------------------------------------------------------- /js/asn1js/hex.js: -------------------------------------------------------------------------------- 1 | // Hex JavaScript decoder 2 | // Copyright (c) 2008-2014 Lapo Luchini 3 | 4 | // Permission to use, copy, modify, and/or distribute this software for any 5 | // purpose with or without fee is hereby granted, provided that the above 6 | // copyright notice and this permission notice appear in all copies. 7 | // 8 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | // ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | // OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | 16 | /*jshint browser: true, strict: true, immed: true, latedef: true, undef: true, regexdash: false */ 17 | (function (undefined) { 18 | "use strict"; 19 | 20 | var Hex = {}, 21 | decoder; 22 | 23 | Hex.decode = function(a) { 24 | var i; 25 | if (decoder === undefined) { 26 | var hex = "0123456789ABCDEF", 27 | ignore = " \f\n\r\t\u00A0\u2028\u2029"; 28 | decoder = []; 29 | for (i = 0; i < 16; ++i) 30 | decoder[hex.charAt(i)] = i; 31 | hex = hex.toLowerCase(); 32 | for (i = 10; i < 16; ++i) 33 | decoder[hex.charAt(i)] = i; 34 | for (i = 0; i < ignore.length; ++i) 35 | decoder[ignore.charAt(i)] = -1; 36 | } 37 | var out = [], 38 | bits = 0, 39 | char_count = 0; 40 | for (i = 0; i < a.length; ++i) { 41 | var c = a.charAt(i); 42 | if (c == '=') 43 | break; 44 | c = decoder[c]; 45 | if (c == -1) 46 | continue; 47 | if (c === undefined) 48 | throw 'Illegal character at offset ' + i; 49 | bits |= c; 50 | if (++char_count >= 2) { 51 | out[out.length] = bits; 52 | bits = 0; 53 | char_count = 0; 54 | } else { 55 | bits <<= 4; 56 | } 57 | } 58 | if (char_count) 59 | throw "Hex encoding incomplete: 4 bits missing"; 60 | return out; 61 | }; 62 | 63 | // export globals 64 | if (typeof module !== 'undefined') { module.exports = Hex; } else { window.Hex = Hex; } 65 | })(); 66 | -------------------------------------------------------------------------------- /js/asn1js/int10.js: -------------------------------------------------------------------------------- 1 | // Big integer base-10 printing library 2 | // Copyright (c) 2014 Lapo Luchini 3 | 4 | // Permission to use, copy, modify, and/or distribute this software for any 5 | // purpose with or without fee is hereby granted, provided that the above 6 | // copyright notice and this permission notice appear in all copies. 7 | // 8 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | // ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | // OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | 16 | /*jshint browser: true, strict: true, immed: true, latedef: true, undef: true, regexdash: false */ 17 | (function () { 18 | "use strict"; 19 | 20 | var max = 10000000000000; // biggest integer that can still fit 2^53 when multiplied by 256 21 | 22 | function Int10(value) { 23 | this.buf = [+value || 0]; 24 | } 25 | 26 | Int10.prototype.mulAdd = function (m, c) { 27 | // assert(m <= 256) 28 | var b = this.buf, 29 | l = b.length, 30 | i, t; 31 | for (i = 0; i < l; ++i) { 32 | t = b[i] * m + c; 33 | if (t < max) 34 | c = 0; 35 | else { 36 | c = 0|(t / max); 37 | t -= c * max; 38 | } 39 | b[i] = t; 40 | } 41 | if (c > 0) 42 | b[i] = c; 43 | }; 44 | 45 | Int10.prototype.toString = function (base) { 46 | if ((base || 10) != 10) 47 | throw 'only base 10 is supported'; 48 | var b = this.buf, 49 | s = b[b.length - 1].toString(); 50 | for (var i = b.length - 2; i >= 0; --i) 51 | s += (max + b[i]).toString().substring(1); 52 | return s; 53 | }; 54 | 55 | Int10.prototype.valueOf = function () { 56 | var b = this.buf, 57 | v = 0; 58 | for (var i = b.length - 1; i >= 0; --i) 59 | v = v * max + b[i]; 60 | return v; 61 | }; 62 | 63 | Int10.prototype.simplify = function () { 64 | var b = this.buf; 65 | return (b.length == 1) ? b[0] : this; 66 | }; 67 | 68 | // export globals 69 | if (typeof module !== 'undefined') { module.exports = Int10; } else { window.Int10 = Int10; } 70 | })(); 71 | -------------------------------------------------------------------------------- /js/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This file contains the functions needed to run index.html 3 | */ 4 | 5 | // global variables 6 | var DIRECTORY_URL = "https://acme-v02.api.letsencrypt.org/directory"; 7 | //var DIRECTORY_URL = "https://acme-staging-v02.api.letsencrypt.org/directory"; 8 | var DIRECTORY = { 9 | // "keyChange": "https://... 10 | // "meta": { 11 | // "termsOfService": "https://..." 12 | // }, 13 | // "newAccount": "https://...", 14 | // "newNonce": "https://...", 15 | // "newOrder": "https://...", 16 | // "revokeCert": "https://...", 17 | }; 18 | var ACCOUNT = { 19 | // "pubkey": "-----BEGIN PUBLIC KEY...", 20 | // "alg": "RS256", 21 | // "jwk": {"e": "deadbeef...", "kty": "RSA", "n": "deadbeef..."}, 22 | // "thumbprint": "deadbeef...", 23 | // "account_uri": "https://...", 24 | // 25 | // // newAccount - account registration (or to get the account_uri) 26 | // "registration_payload_json": {"termsOfServiceAgreed": true}, 27 | // "registration_payload_b64": "deadbeef...", 28 | // "registration_protected_json": {"url": "...", "alg": "...", "nonce": "...", "jwk": {...}}, 29 | // "registration_protected_b64": "deadbeef...", 30 | // "registration_sig": "deadbeef...", 31 | // "registration_response": {"status": "valid", "contact": [..], "termsOfServiceAgreed": true, "orders": "..."}, 32 | // 33 | // // account contact update 34 | // "update_payload_json": {"contact": ["mailto:..."]}, 35 | // "update_payload_b64": "deadbeef...", 36 | // "update_protected_json": {"url": "...", "alg": "...", "nonce": "...", "kid": "..."}, 37 | // "update_protected_b64": "deadbeef...", 38 | // "update_sig": "deadbeef...", 39 | // "update_response": {"status": "valid", "contact": [..], "termsOfServiceAgreed": true, "orders": "..."}, 40 | }; 41 | var ORDER = { 42 | // "csr_pem": "-----BEGIN CERTIFICATE REQUEST...", 43 | // "csr_der": "deadbeef...", (DER encoded) 44 | // 45 | // // create order for identifiers 46 | // "order_payload_json": {"identifiers": [{"type": "dns", "value": "aaa.com"}, ...]}, 47 | // "order_payload_b64": "deadbeef...", 48 | // "order_protected_json": {"url": "...", "alg": "...", "nonce": "...", "kid": "..."}, 49 | // "order_protected_b64": "deadbeef...", 50 | // "order_sig": "deadbeef...", 51 | // "order_response": {"status": "valid", "identifiers": [...], "authorizations": [...], "finalize": "...", ...}, 52 | // "order_uri": "https://...", 53 | // 54 | // // get csr signed 55 | // "finalize_uri": "https://...", 56 | // "finalize_payload_json": {"csr": "..."}, 57 | // "finalize_payload_b64": "deadbeef...", 58 | // "finalize_protected_json": {"url": "...", "alg": "...", "nonce": "...", "kid": "..."}, 59 | // "finalize_protected_b64": "deadbeef...", 60 | // "finalize_sig": "deadbeef...", 61 | // "finalize_response": {"status": "pending", "certificate": "...", ...}, 62 | // 63 | // // re-check order after finalizing 64 | // "recheck_order_payload_json": "", // GET-as-POST has an empty payload 65 | // "recheck_order_payload_b64": "", // GET-as-POST has an empty payload 66 | // "recheck_order_protected_json": {"url": "...", "alg": "...", "nonce": "...", "kid": "..."}, 67 | // "recheck_order_protected_b64": "deadbeef...", 68 | // "recheck_order_sig": "deadbeef...", 69 | // "recheck_order_response": {"status": "valid", "certificate": "...", ...}, 70 | // 71 | // // download the generated certificate 72 | // "cert_payload_json": "", // GET-as-POST has an empty payload 73 | // "cert_payload_b64": "", // GET-as-POST has an empty payload 74 | // "cert_protected_json": {"url": "...", "alg": "...", "nonce": "...", "kid": "..."}, 75 | // "cert_protected_b64": "deadbeef...", 76 | // "cert_sig": "deadbeef...", 77 | // "cert_response": "-----BEGIN CERTIFICATE-----...", 78 | // "cert_uri": "https://...", 79 | }; 80 | var AUTHORIZATIONS = { 81 | // // one authorization for each domain 82 | // "https://...": { 83 | // // get authorization initially 84 | // "auth_payload_json": "", // GET-as-POST has an empty payload 85 | // "auth_payload_b64": "", // GET-as-POST has an empty payload 86 | // "auth_protected_json": {"url": "...", "alg": "...", "nonce": "...", "kid": "..."}, 87 | // "auth_protected_b64": "deadbeef...", 88 | // "auth_sig": "deadbeef...", 89 | // "auth_response": {"status": "valid", "identifier": {...}, "challenges": [...], "wildcard": false, ...}, 90 | // 91 | // // python server HTTP challenge 92 | // "python_challenge_uri": "https://...", 93 | // "python_challenge_object": {"type": "http-01", ...}, 94 | // "python_challenge_protected_json": {"url": "...", "alg": "...", "nonce": "...", "kid": "..."}, 95 | // "python_challenge_protected_b64": "deadbeef...", 96 | // "python_challenge_sig": "deadbeef...", 97 | // "python_challenge_response": {"type": "http-01", "url": "...", "token": "..."}, 98 | // 99 | // // file-based HTTP challenge 100 | // "file_challenge_uri": "https://...", 101 | // "file_challenge_object": {"type": "http-01", ...}, 102 | // "file_challenge_protected_json": {"url": "...", "alg": "...", "nonce": "...", "kid": "..."}, 103 | // "file_challenge_protected_b64": "deadbeef...", 104 | // "file_challenge_sig": "deadbeef...", 105 | // "file_challenge_response": {"type": "http-01", "url": "...", "token": "..."}, 106 | // 107 | // // DNS challenge 108 | // "dns_challenge_uri": "https://...", 109 | // "dns_challenge_object": {"type": "dns-01", ...}, 110 | // "dns_challenge_protected_json": {"url": "...", "alg": "...", "nonce": "...", "kid": "..."}, 111 | // "dns_challenge_protected_b64": "deadbeef...", 112 | // "dns_challenge_sig": "deadbeef...", 113 | // "dns_challenge_response": {"type": "dns-01", "url": "...", "token": "..."}, 114 | // 115 | // // post-challenge authorization check 116 | // "recheck_auth_payload_json": "", // GET-as-POST has an empty payload 117 | // "recheck_auth_payload_b64": "", // GET-as-POST has an empty payload 118 | // "recheck_auth_protected_json": {"url": "...", "alg": "...", "nonce": "...", "kid": "..."}, 119 | // "recheck_auth_protected_b64": "deadbeef...", 120 | // "recheck_auth_sig": "deadbeef...", 121 | // "recheck_auth_response": {"status": "valid", "identifier": {...}, "challenges": [...], "wildcard": false, ...}, 122 | // }, 123 | // ... 124 | }; 125 | var RESULT_PLACEHOLDER = "Paste the hex output here (e.g. \"(stdin)= f2cf67e4...\")"; 126 | 127 | /* 128 | * Helper Functions 129 | */ 130 | 131 | // display errors 132 | function fail(status_element, error_message){ 133 | // debug 134 | if(window.location.search.indexOf("debug") !== -1 && console){ 135 | console.log("DIRECTORY_URL", DIRECTORY_URL); 136 | console.log("DIRECTORY", DIRECTORY); 137 | console.log("ACCOUNT", ACCOUNT); 138 | console.log("ORDER", ORDER); 139 | console.log("AUTHORIZATIONS", AUTHORIZATIONS); 140 | } 141 | status_element.style.display = "inline"; 142 | status_element.className = status_element.className + " error"; 143 | status_element.innerHTML = ""; 144 | status_element.appendChild(document.createTextNode("Error: " + error_message)); 145 | } 146 | 147 | // show warning if no webcrypto digest 148 | window.crypto = window.crypto || window.msCrypto; //for IE11 149 | if(window.crypto && window.crypto.webkitSubtle){ 150 | window.crypto.subtle = window.crypto.webkitSubtle; //for Safari 151 | } 152 | var DIGEST = window.crypto ? (window.crypto.subtle ? window.crypto.subtle.digest : undefined) : undefined; 153 | document.getElementById("digest_error").style.display = DIGEST ? "none" : "block"; 154 | 155 | // SHA-256 shim for standard promise-based and IE11 event-based 156 | function sha256(bytes, callback){ 157 | var hash = window.crypto.subtle.digest({name: "SHA-256"}, bytes); 158 | // IE11 159 | if(!hash.then){ 160 | hash.oncomplete = function(e){ 161 | callback(new Uint8Array(e.target.result), undefined); 162 | }; 163 | hash.onerror = function(e){ 164 | callback(undefined, e); 165 | }; 166 | } 167 | // standard promise-based 168 | else{ 169 | hash.then(function(result){ 170 | callback(new Uint8Array(result), undefined); 171 | }) 172 | .catch(function(error){ 173 | callback(undefined, error); 174 | }); 175 | } 176 | } 177 | 178 | // url-safe base64 encoding 179 | function b64(bytes){ 180 | var str64 = typeof(bytes) === "string" ? window.btoa(bytes) : window.btoa(String.fromCharCode.apply(null, bytes)); 181 | return str64.replace(/\//g, "_").replace(/\+/g, "-").replace(/=/g, ""); 182 | } 183 | function b64decode(b64string){ 184 | try { return window.atob(b64string.replace(/_/g, "/").replace(/-/g, "+") + "=="); } 185 | catch (err) { 186 | if(err.name === "InvalidCharacterError"){ 187 | return window.atob(b64string.replace(/_/g, "/").replace(/-/g, "+") + "="); // only need one trailing equals 188 | } else { 189 | throw err; 190 | } 191 | } 192 | } 193 | 194 | // parse openssl hex output 195 | var OPENSSL_HEX = /(?:\(stdin\)= |)([a-f0-9]{512,1024})/ 196 | function hex2b64(hex){ 197 | if(!OPENSSL_HEX.test(hex)){ 198 | return null; 199 | } 200 | hex = OPENSSL_HEX.exec(hex)[1]; 201 | var bytes = []; 202 | while(hex.length >= 2){ 203 | bytes.push(parseInt(hex.substring(0, 2), 16)); 204 | hex = hex.substring(2, hex.length); 205 | } 206 | return b64(new Uint8Array(bytes)); 207 | } 208 | 209 | // url-safe base64 encoding 210 | function cachebuster(){ 211 | return "cachebuster=" + b64(window.crypto.getRandomValues(new Uint8Array(8))); 212 | } 213 | 214 | // helper function to get a nonce via an ajax request to the ACME directory 215 | function getNonce(callback){ 216 | var xhr = new XMLHttpRequest(); 217 | xhr.open("GET", DIRECTORY['newNonce'] + "?" + cachebuster()); 218 | xhr.onload = function(){ 219 | callback(xhr.getResponseHeader("Replay-Nonce"), undefined); 220 | }; 221 | xhr.onerror = function(){ 222 | callback(undefined, xhr); 223 | }; 224 | xhr.send(); 225 | } 226 | 227 | /* 228 | * Step 0: Let's Encrypt Directory 229 | */ 230 | 231 | // get the directory with links to all the other endpoints 232 | function populateDirectory(){ 233 | var xhr = new XMLHttpRequest(); 234 | xhr.open("GET", DIRECTORY_URL + "?" + cachebuster()); 235 | xhr.onload = function(){ 236 | // set the directory urls 237 | DIRECTORY = JSON.parse(xhr.responseText); 238 | // set the terms of service links 239 | document.getElementById("tos").setAttribute("href", DIRECTORY['meta']['termsOfService']); 240 | document.getElementById("howto_tos").setAttribute("href", DIRECTORY['meta']['termsOfService']); 241 | // enable buttons so user can continue 242 | document.getElementById("validate_account").addEventListener("submit", validateAccount); 243 | document.getElementById("validate_account_submit").removeAttribute("disabled"); 244 | document.getElementById("validate_csr").addEventListener("submit", validateCSR); 245 | document.getElementById("validate_csr_submit").removeAttribute("disabled"); 246 | document.getElementById("validate_registration").addEventListener("submit", validateRegistration); 247 | document.getElementById("validate_update").addEventListener("submit", validateUpdate); 248 | document.getElementById("validate_order").addEventListener("submit", validateOrder); 249 | document.getElementById("validate_finalize").addEventListener("submit", validateFinalize); 250 | document.getElementById("validate_recheck_order").addEventListener("submit", recheckOrder); 251 | document.getElementById("validate_cert").addEventListener("submit", getCertificate); 252 | }; 253 | xhr.onerror = function(){ 254 | fail(document.getElementById("validate_account_status"), "Let's Encrypt appears to be down. Please try again later."); 255 | }; 256 | xhr.send(); 257 | } 258 | populateDirectory(); 259 | 260 | /* 261 | * Step 1: Account Info 262 | */ 263 | 264 | // validate account info 265 | function validateAccount(e){ 266 | e.preventDefault(); 267 | 268 | // clear previous status 269 | var status = document.getElementById("validate_account_status"); 270 | status.style.display = "inline"; 271 | status.className = ""; 272 | status.innerHTML = "validating..."; 273 | 274 | // validate email 275 | var email_re = /^(([^<>()[\]\.,;:\s@\"]+(\.[^<>()[\]\.,;:\s@\"]+)*)|(\".+\"))@(([^<>()[\]\.,;:\s@\"]+\.)+[^<>()[\]\.,;:\s@\"]{2,})$/i; 276 | var email = document.getElementById("email").value; 277 | if(!email_re.test(email)){ 278 | return fail(status, "Account email doesn't look valid."); 279 | } 280 | 281 | // update email in interface 282 | document.getElementById("account_email").innerHTML = ""; 283 | document.getElementById("account_email").appendChild(document.createTextNode(email)); 284 | 285 | // parse account public key 286 | var pubkey = document.getElementById("pubkey").value; 287 | if(pubkey === ""){ 288 | return fail(status, "You need to include an account public key."); 289 | } 290 | var unarmor = /-----BEGIN PUBLIC KEY-----([A-Za-z0-9+\/=\s]+)-----END PUBLIC KEY-----/; 291 | if(!unarmor.test(pubkey)){ 292 | return fail(status, "Your public key isn't formatted correctly."); 293 | } 294 | 295 | // find RSA modulus and exponent 296 | try{ 297 | var pubkeyAsn1 = ASN1.decode(Base64.decode(unarmor.exec(pubkey)[1])); 298 | var modulusRaw = pubkeyAsn1.sub[1].sub[0].sub[0]; 299 | var modulusStart = modulusRaw.header + modulusRaw.stream.pos + 1; 300 | var modulusEnd = modulusRaw.length + modulusRaw.stream.pos + modulusRaw.header; 301 | var modulusHex = modulusRaw.stream.hexDump(modulusStart, modulusEnd); 302 | var modulus = Hex.decode(modulusHex); 303 | var exponentRaw = pubkeyAsn1.sub[1].sub[0].sub[1]; 304 | var exponentStart = exponentRaw.header + exponentRaw.stream.pos; 305 | var exponentEnd = exponentRaw.length + exponentRaw.stream.pos + exponentRaw.header; 306 | var exponentHex = exponentRaw.stream.hexDump(exponentStart, exponentEnd); 307 | var exponent = Hex.decode(exponentHex); 308 | } 309 | catch(err){ 310 | return fail(status, "Failed validating RSA public key."); 311 | } 312 | 313 | // generate the jwk header and bytes 314 | var jwk = { 315 | "e": b64(new Uint8Array(exponent)), 316 | "kty": "RSA", 317 | "n": b64(new Uint8Array(modulus)), 318 | } 319 | var jwk_json = JSON.stringify(jwk); 320 | var jwk_bytes = []; 321 | for(var i = 0; i < jwk_json.length; i++){ 322 | jwk_bytes.push(jwk_json.charCodeAt(i)); 323 | } 324 | 325 | // calculate thumbprint 326 | sha256(new Uint8Array(jwk_bytes), function(hash, err){ 327 | if(err){ 328 | return fail(status, "Thumbprint failed: " + err.message); 329 | } 330 | 331 | // update the global account object 332 | var registration_payload = {"termsOfServiceAgreed": true}; 333 | var account_payload = {"contact": ["mailto:" + email]}; 334 | ACCOUNT = { 335 | "pubkey": pubkey, 336 | "alg": "RS256", 337 | "jwk": jwk, 338 | "thumbprint": b64(hash), 339 | "account_uri": undefined, 340 | 341 | // newAccount - account registration (or to get the account_url) 342 | "registration_payload_json": registration_payload, 343 | "registration_payload_b64": b64(JSON.stringify(registration_payload)), 344 | "registration_protected_json": undefined, 345 | "registration_protected_b64": undefined, 346 | "registration_sig": undefined, 347 | "registration_response": undefined, 348 | 349 | // account contact update 350 | "update_payload_json": account_payload, 351 | "update_payload_b64": b64(JSON.stringify(account_payload)), 352 | "update_protected_json": undefined, 353 | "update_protected_b64": undefined, 354 | "update_sig": undefined, 355 | "update_response": undefined, 356 | }; 357 | 358 | // show the success text (simulate a delay so it looks like we thought hard) 359 | window.setTimeout(function(){ 360 | status.style.display = "inline"; 361 | status.className = ""; 362 | status.innerHTML = ""; 363 | status.appendChild(document.createTextNode("Looks good! Proceed to Step 2!")); 364 | }, 300); 365 | }); 366 | } 367 | 368 | /* 369 | * Step 2: CSR 370 | */ 371 | 372 | // validate CSR 373 | function validateCSR(e){ 374 | e.preventDefault(); 375 | 376 | // clear previous status 377 | var status = document.getElementById("validate_csr_status"); 378 | status.style.display = "inline"; 379 | status.className = ""; 380 | status.innerHTML = "validating..."; 381 | 382 | // hide following steps 383 | document.getElementById("step3").style.display = "none"; 384 | document.getElementById("step3_pending").style.display = "inline"; 385 | document.getElementById("step4").style.display = "none"; 386 | document.getElementById("step4_pending").style.display = "inline"; 387 | document.getElementById("step5").style.display = "none"; 388 | document.getElementById("step5_pending").style.display = "inline"; 389 | 390 | // reset registration status 391 | document.getElementById("validate_registration_sig_status").style.display = "none"; 392 | document.getElementById("validate_registration_sig_status").className = ""; 393 | document.getElementById("validate_registration_sig_status").innerHTML = ""; 394 | 395 | // reset account update signature 396 | document.getElementById("update_sig_cmd").value = "waiting until terms are accepted..."; 397 | document.getElementById("update_sig_cmd").removeAttribute("readonly"); 398 | document.getElementById("update_sig_cmd").setAttribute("disabled", ""); 399 | document.getElementById("update_sig").value = ""; 400 | document.getElementById("update_sig").setAttribute("placeholder", "waiting until terms are accepted..."); 401 | document.getElementById("update_sig").setAttribute("disabled", ""); 402 | document.getElementById("validate_update_sig").setAttribute("disabled", ""); 403 | document.getElementById("validate_update_sig_status").style.display = "none"; 404 | document.getElementById("validate_update_sig_status").className = ""; 405 | document.getElementById("validate_update_sig_status").innerHTML = ""; 406 | 407 | // reset new order signature 408 | document.getElementById("order_sig_cmd").value = "waiting until account contact is updated..."; 409 | document.getElementById("order_sig_cmd").removeAttribute("readonly"); 410 | document.getElementById("order_sig_cmd").setAttribute("disabled", ""); 411 | document.getElementById("order_sig").value = ""; 412 | document.getElementById("order_sig").setAttribute("placeholder", "waiting until account contact is updated..."); 413 | document.getElementById("order_sig").setAttribute("disabled", ""); 414 | document.getElementById("validate_order_sig").setAttribute("disabled", ""); 415 | document.getElementById("validate_order_sig_status").style.display = "none"; 416 | document.getElementById("validate_order_sig_status").className = ""; 417 | document.getElementById("validate_order_sig_status").innerHTML = ""; 418 | 419 | // make sure there's an account public key and email 420 | if(ACCOUNT['pubkey'] === undefined){ 421 | return fail(status, "Need to complete Step 1 first."); 422 | } 423 | 424 | // parse csr 425 | var csr = document.getElementById("csr").value; 426 | if(csr === ""){ 427 | return fail(status, "You need to include a CSR."); 428 | } 429 | var unarmor = /-----BEGIN CERTIFICATE REQUEST-----([A-Za-z0-9+\/=\s]+)-----END CERTIFICATE REQUEST-----/; 430 | if(!unarmor.test(csr)){ 431 | return fail(status, "Your CSR isn't formatted correctly."); 432 | } 433 | var csr_der = b64(new Uint8Array(Base64.decode(unarmor.exec(csr)[1]))); 434 | 435 | // find domains in the csr 436 | var domains = []; 437 | try{ 438 | var csrAsn1 = ASN1.decode(Base64.decode(unarmor.exec(csr)[1])); 439 | 440 | // look for commonName in attributes 441 | if(csrAsn1.sub[0].sub[1].sub){ 442 | var csrIds = csrAsn1.sub[0].sub[1].sub; 443 | for(var i = 0; i < csrIds.length; i++){ 444 | var oidRaw = csrIds[i].sub[0].sub[0]; 445 | var oidStart = oidRaw.header + oidRaw.stream.pos; 446 | var oidEnd = oidRaw.length + oidRaw.stream.pos + oidRaw.header; 447 | var oid = oidRaw.stream.parseOID(oidStart, oidEnd, Infinity); 448 | if(oid === "2.5.4.3"){ 449 | var cnRaw = csrIds[i].sub[0].sub[1]; 450 | var cnStart = cnRaw.header + cnRaw.stream.pos; 451 | var cnEnd = cnRaw.length + cnRaw.stream.pos + cnRaw.header; 452 | domains.push(cnRaw.stream.parseStringUTF(cnStart, cnEnd)); 453 | } 454 | } 455 | } 456 | 457 | // look for subjectAltNames 458 | if(csrAsn1.sub[0].sub[3].sub){ 459 | 460 | // find the PKCS#9 ExtensionRequest 461 | var xtns = csrAsn1.sub[0].sub[3].sub; 462 | for(var i = 0; i < xtns.length; i++){ 463 | var oidRaw = xtns[i].sub[0]; 464 | var oidStart = oidRaw.header + oidRaw.stream.pos; 465 | var oidEnd = oidRaw.length + oidRaw.stream.pos + oidRaw.header; 466 | var oid = oidRaw.stream.parseOID(oidStart, oidEnd, Infinity); 467 | if(oid === "1.2.840.113549.1.9.14"){ 468 | 469 | // find any subjectAltNames 470 | for(var j = 0; j < xtns[i].sub[1].sub.length ? xtns[i].sub[1].sub : 0; j++){ 471 | for(var k = 0; k < xtns[i].sub[1].sub[j].sub.length ? xtns[i].sub[1].sub[j].sub : 0; k++){ 472 | var oidRaw = xtns[i].sub[1].sub[j].sub[k].sub[0]; 473 | var oidStart = oidRaw.header + oidRaw.stream.pos; 474 | var oidEnd = oidRaw.length + oidRaw.stream.pos + oidRaw.header; 475 | var oid = oidRaw.stream.parseOID(oidStart, oidEnd, Infinity); 476 | if(oid === "2.5.29.17"){ 477 | 478 | // add each subjectAltName 479 | var sans = xtns[i].sub[1].sub[j].sub[k].sub[1].sub[0].sub; 480 | for(var s = 0; s < sans.length; s++){ 481 | var sanRaw = sans[s]; 482 | var tag = sanRaw.tag.tagNumber; 483 | if(tag !== 2) 484 | continue; // ignore any other subjectAltName type than dNSName (2) 485 | var sanStart = sanRaw.header + sanRaw.stream.pos; 486 | var sanEnd = sanRaw.length + sanRaw.stream.pos + sanRaw.header; 487 | domains.push(sanRaw.stream.parseStringUTF(sanStart, sanEnd)); 488 | } 489 | } 490 | } 491 | } 492 | } 493 | } 494 | } 495 | } 496 | catch(err){ 497 | return fail(status, "Failed validating CSR."); 498 | } 499 | 500 | // reject CSRs with no domains 501 | if(domains.length === 0){ 502 | return fail(status, "Couldn't find any domains in the CSR."); 503 | } 504 | 505 | // build order payload 506 | var finalize_payload = {"csr": csr_der}; 507 | var order_payload = {"identifiers": []}; 508 | for(var i = 0; i < domains.length; i++){ 509 | order_payload['identifiers'].push({"type": "dns", "value": domains[i]}); 510 | } 511 | 512 | // update the globals 513 | ORDER = { 514 | "csr_pem": csr, 515 | "csr_der": csr_der, 516 | 517 | // order for identifiers 518 | "order_payload_json": order_payload, 519 | "order_payload_b64": b64(JSON.stringify(order_payload)), 520 | "order_protected_json": undefined, 521 | "order_protected_b64": undefined, 522 | "order_sig": undefined, 523 | "order_response": undefined, 524 | "order_uri": undefined, 525 | 526 | // order finalizing 527 | "finalize_uri": undefined, 528 | "finalize_payload_json": finalize_payload, 529 | "finalize_payload_b64": b64(JSON.stringify(finalize_payload)), 530 | "finalize_protected_json": undefined, 531 | "finalize_protected_b64": undefined, 532 | "finalize_sig": undefined, 533 | "finalize_response": undefined, 534 | 535 | // order checking after finalizing 536 | "recheck_order_payload_json": "", // GET-as-POST has an empty payload 537 | "recheck_order_payload_b64": "", // GET-as-POST has an empty payload 538 | "recheck_order_protected_json": undefined, 539 | "recheck_order_protected_b64": undefined, 540 | "recheck_order_sig": undefined, 541 | "recheck_order_response": undefined, 542 | 543 | // certificate downloading 544 | "cert_payload_json": "", // GET-as-POST has an empty payload 545 | "cert_payload_b64": "", // GET-as-POST has an empty payload 546 | "cert_protected_json": undefined, 547 | "cert_protected_b64": undefined, 548 | "cert_sig": undefined, 549 | "cert_response": undefined, 550 | "cert_uri": undefined, 551 | }; 552 | 553 | // set the shortest domain for the ssl test at the end 554 | var shortest_domain = domains[0]; 555 | for(var d = 0; d < domains.length; d++){ 556 | if(shortest_domain.length > domains[d].length){ 557 | shortest_domain = domains[d]; 558 | } 559 | } 560 | document.getElementById("ssltest_domain").value = shortest_domain; 561 | 562 | // get nonce for registration 563 | getNonce(function(nonce, err){ 564 | if(err){ 565 | return fail(status, "Failed terms nonce request (code: " + err.status + "). " + err.responseText); 566 | } 567 | 568 | // populate registration signature (payload populated in validateAccount()) 569 | ACCOUNT['registration_protected_json'] = { 570 | "url": DIRECTORY['newAccount'], 571 | "alg": ACCOUNT['alg'], 572 | "nonce": nonce, 573 | "jwk": ACCOUNT['jwk'], 574 | } 575 | ACCOUNT['registration_protected_b64'] = b64(JSON.stringify(ACCOUNT['registration_protected_json'])); 576 | document.getElementById("registration_sig_cmd").value = "" + 577 | "PRIV_KEY=./account.key; " + 578 | "echo -n \"" + ACCOUNT['registration_protected_b64'] + "." + ACCOUNT['registration_payload_b64'] + "\" | " + 579 | "openssl dgst -sha256 -hex -sign $PRIV_KEY"; 580 | document.getElementById("registration_sig").value = ""; 581 | document.getElementById("registration_sig").setAttribute("placeholder", RESULT_PLACEHOLDER); 582 | 583 | // show step 3 584 | status.style.display = "inline"; 585 | status.className = ""; 586 | status.innerHTML = ""; 587 | status.appendChild(document.createTextNode("Found domains! Proceed to Step 3! (" + domains.join(", ") + ")")); 588 | document.getElementById("step3").style.display = "block"; 589 | document.getElementById("step3_pending").style.display = "none"; 590 | }); 591 | } 592 | 593 | /* 594 | * Step 3a: Register Account (POST /newAccount) 595 | */ 596 | function validateRegistration(e){ 597 | e.preventDefault(); 598 | 599 | // clear previous status 600 | var status = document.getElementById("validate_registration_sig_status"); 601 | status.style.display = "inline"; 602 | status.className = ""; 603 | status.innerHTML = "accepting..."; 604 | 605 | // hide following steps 606 | document.getElementById("step4").style.display = "none"; 607 | document.getElementById("step4_pending").style.display = "inline"; 608 | document.getElementById("step5").style.display = "none"; 609 | document.getElementById("step5_pending").style.display = "inline"; 610 | 611 | // reset account update signature 612 | document.getElementById("update_sig_cmd").value = "waiting until terms are accepted..."; 613 | document.getElementById("update_sig_cmd").removeAttribute("readonly"); 614 | document.getElementById("update_sig_cmd").setAttribute("disabled", ""); 615 | document.getElementById("update_sig").value = ""; 616 | document.getElementById("update_sig").setAttribute("placeholder", "waiting until terms are accepted..."); 617 | document.getElementById("update_sig").setAttribute("disabled", ""); 618 | document.getElementById("validate_update_sig").setAttribute("disabled", ""); 619 | document.getElementById("validate_update_sig_status").style.display = "none"; 620 | document.getElementById("validate_update_sig_status").className = ""; 621 | document.getElementById("validate_update_sig_status").innerHTML = ""; 622 | 623 | // reset new order signature 624 | document.getElementById("order_sig_cmd").value = "waiting until account contact is updated..."; 625 | document.getElementById("order_sig_cmd").removeAttribute("readonly"); 626 | document.getElementById("order_sig_cmd").setAttribute("disabled", ""); 627 | document.getElementById("order_sig").value = ""; 628 | document.getElementById("order_sig").setAttribute("placeholder", "waiting until account contact is updated..."); 629 | document.getElementById("order_sig").setAttribute("disabled", ""); 630 | document.getElementById("validate_order_sig").setAttribute("disabled", ""); 631 | document.getElementById("validate_order_sig_status").style.display = "none"; 632 | document.getElementById("validate_order_sig_status").className = ""; 633 | document.getElementById("validate_order_sig_status").innerHTML = ""; 634 | 635 | // validate registration payload exists 636 | if(ACCOUNT['registration_payload_b64'] === undefined){ 637 | return fail(status, "Terms payload not found. Please go back to Step 1."); 638 | } 639 | 640 | // validate the signature 641 | var registration_sig = hex2b64(document.getElementById("registration_sig").value); 642 | if(registration_sig === null){ 643 | return fail(status, "You need to run the above commands and paste the output in the text boxes below each command."); 644 | } 645 | ACCOUNT['registration_sig'] = registration_sig; 646 | 647 | // send newAccount request to CA 648 | var registration_xhr = new XMLHttpRequest(); 649 | registration_xhr.open("POST", DIRECTORY['newAccount']); 650 | registration_xhr.setRequestHeader("Content-Type", "application/jose+json"); 651 | registration_xhr.onreadystatechange = function(){ 652 | if(registration_xhr.readyState === 4){ 653 | 654 | // successful registration 655 | if(registration_xhr.status === 200 || registration_xhr.status === 201 || registration_xhr.status === 204){ 656 | 657 | // set account_uri 658 | ACCOUNT['account_uri'] = registration_xhr.getResponseHeader("Location"); 659 | 660 | // get nonce for account update 661 | getNonce(function(nonce, err){ 662 | if(err){ 663 | return fail(status, "Failed update nonce request (code: " + err.status + "). " + err.responseText); 664 | } 665 | 666 | // populate update signature (payload populated in validateAccount()) 667 | ACCOUNT['update_protected_json'] = { 668 | "url": ACCOUNT['account_uri'], 669 | "alg": ACCOUNT['alg'], 670 | "nonce": nonce, 671 | "kid": ACCOUNT['account_uri'], 672 | } 673 | ACCOUNT['update_protected_b64'] = b64(JSON.stringify(ACCOUNT['update_protected_json'])); 674 | document.getElementById("update_sig_cmd").value = "" + 675 | "PRIV_KEY=./account.key; " + 676 | "echo -n \"" + ACCOUNT['update_protected_b64'] + "." + ACCOUNT['update_payload_b64'] + "\" | " + 677 | "openssl dgst -sha256 -hex -sign $PRIV_KEY"; 678 | document.getElementById("update_sig_cmd").setAttribute("readonly", ""); 679 | document.getElementById("update_sig_cmd").removeAttribute("disabled"); 680 | document.getElementById("update_sig").value = ""; 681 | document.getElementById("update_sig").setAttribute("placeholder", RESULT_PLACEHOLDER); 682 | document.getElementById("update_sig").removeAttribute("disabled"); 683 | document.getElementById("validate_update_sig").removeAttribute("disabled"); 684 | 685 | // complete step 3a 686 | status.innerHTML = "Accepted! Proceed to next command below."; 687 | }); 688 | } 689 | 690 | // error registering 691 | else{ 692 | return fail(status, "Account registration failed. Please start back at Step 1. " + registration_xhr.responseText); 693 | } 694 | } 695 | }; 696 | registration_xhr.send(JSON.stringify({ 697 | "protected": ACCOUNT['registration_protected_b64'], 698 | "payload": ACCOUNT['registration_payload_b64'], 699 | "signature": ACCOUNT['registration_sig'], 700 | })); 701 | } 702 | 703 | /* 704 | * Step 3b: Update Account Contact (POST /ACCOUNT['account_uri']) 705 | */ 706 | function validateUpdate(e){ 707 | e.preventDefault(); 708 | 709 | // clear previous status 710 | var status = document.getElementById("validate_update_sig_status"); 711 | status.style.display = "inline"; 712 | status.className = ""; 713 | status.innerHTML = "updating..."; 714 | 715 | // hide following steps 716 | document.getElementById("step4").style.display = "none"; 717 | document.getElementById("step4_pending").style.display = "inline"; 718 | document.getElementById("step5").style.display = "none"; 719 | document.getElementById("step5_pending").style.display = "inline"; 720 | 721 | // reset new order signature 722 | document.getElementById("order_sig_cmd").value = "waiting until account contact is updated..."; 723 | document.getElementById("order_sig_cmd").removeAttribute("readonly"); 724 | document.getElementById("order_sig_cmd").setAttribute("disabled", ""); 725 | document.getElementById("order_sig").value = ""; 726 | document.getElementById("order_sig").setAttribute("placeholder", "waiting until account contact is updated..."); 727 | document.getElementById("order_sig").setAttribute("disabled", ""); 728 | document.getElementById("validate_order_sig").setAttribute("disabled", ""); 729 | document.getElementById("validate_order_sig_status").style.display = "none"; 730 | document.getElementById("validate_order_sig_status").className = ""; 731 | document.getElementById("validate_order_sig_status").innerHTML = ""; 732 | 733 | // validate update payload exists 734 | if(ACCOUNT['update_payload_b64'] === undefined){ 735 | return fail(status, "Update payload not found. Please go back to Step 1."); 736 | } 737 | 738 | // validate the signature 739 | var update_sig = hex2b64(document.getElementById("update_sig").value); 740 | if(update_sig === null){ 741 | return fail(status, "You need to run the above commands and paste the output in the text boxes below each command."); 742 | } 743 | ACCOUNT['update_sig'] = update_sig; 744 | 745 | // send update request to CA account_uri 746 | var update_xhr = new XMLHttpRequest(); 747 | update_xhr.open("POST", ACCOUNT['account_uri']); 748 | update_xhr.setRequestHeader("Content-Type", "application/jose+json"); 749 | update_xhr.onreadystatechange = function(){ 750 | if(update_xhr.readyState === 4){ 751 | 752 | // successful update 753 | if(update_xhr.status === 200){ 754 | 755 | // get nonce for new order 756 | getNonce(function(nonce, err){ 757 | if(err){ 758 | return fail(status, "Failed order nonce request (code: " + err.status + "). " + err.responseText); 759 | } 760 | 761 | // populate order signature (payload populated in validateCSR()) 762 | ORDER['order_protected_json'] = { 763 | "url": DIRECTORY['newOrder'], 764 | "alg": ACCOUNT['alg'], 765 | "nonce": nonce, 766 | "kid": ACCOUNT['account_uri'], 767 | } 768 | ORDER['order_protected_b64'] = b64(JSON.stringify(ORDER['order_protected_json'])); 769 | document.getElementById("order_sig_cmd").value = "" + 770 | "PRIV_KEY=./account.key; " + 771 | "echo -n \"" + ORDER['order_protected_b64'] + "." + ORDER['order_payload_b64'] + "\" | " + 772 | "openssl dgst -sha256 -hex -sign $PRIV_KEY"; 773 | document.getElementById("order_sig_cmd").setAttribute("readonly", ""); 774 | document.getElementById("order_sig_cmd").removeAttribute("disabled"); 775 | document.getElementById("order_sig").value = ""; 776 | document.getElementById("order_sig").setAttribute("placeholder", RESULT_PLACEHOLDER); 777 | document.getElementById("order_sig").removeAttribute("disabled"); 778 | document.getElementById("validate_order_sig").removeAttribute("disabled"); 779 | 780 | // complete step 3b 781 | status.innerHTML = "Updated! Proceed to next command below."; 782 | }); 783 | } 784 | 785 | // error registering 786 | else{ 787 | return fail(status, "Account contact update failed. Please start back at Step 1. " + update_xhr.responseText); 788 | } 789 | } 790 | }; 791 | update_xhr.send(JSON.stringify({ 792 | "protected": ACCOUNT['update_protected_b64'], 793 | "payload": ACCOUNT['update_payload_b64'], 794 | "signature": ACCOUNT['update_sig'], 795 | })); 796 | } 797 | 798 | /* 799 | * Step 3c: Create New Order (POST /newOrder) 800 | */ 801 | function validateOrder(e){ 802 | e.preventDefault(); 803 | 804 | // clear previous status 805 | var status = document.getElementById("validate_order_sig_status"); 806 | status.style.display = "inline"; 807 | status.className = ""; 808 | status.innerHTML = "ordering..."; 809 | 810 | // hide following steps 811 | document.getElementById("step4").style.display = "none"; 812 | document.getElementById("step4_pending").style.display = "inline"; 813 | document.getElementById("step5").style.display = "none"; 814 | document.getElementById("step5_pending").style.display = "inline"; 815 | 816 | // validate order payload exists 817 | if(ORDER['order_payload_b64'] === undefined){ 818 | return fail(status, "Order payload not found. Please go back to Step 1."); 819 | } 820 | 821 | // validate the signature 822 | var order_sig = hex2b64(document.getElementById("order_sig").value); 823 | if(order_sig === null){ 824 | return fail(status, "You need to run the above commands and paste the output in the text boxes below each command."); 825 | } 826 | ORDER['order_sig'] = order_sig; 827 | 828 | // send newOrder request to CA 829 | var order_xhr = new XMLHttpRequest(); 830 | order_xhr.open("POST", DIRECTORY['newOrder']); 831 | order_xhr.setRequestHeader("Content-Type", "application/jose+json"); 832 | order_xhr.onreadystatechange = function(){ 833 | if(order_xhr.readyState === 4){ 834 | 835 | // successful order 836 | if(order_xhr.status === 200 || order_xhr.status === 201){ 837 | 838 | // set order response and uri 839 | ORDER['order_response'] = JSON.parse(order_xhr.responseText); 840 | ORDER['order_uri'] = order_xhr.getResponseHeader("Location"); 841 | ORDER['finalize_uri'] = ORDER['order_response']['finalize']; 842 | 843 | // clear out any previous authorizations and challenge forms 844 | AUTHORIZATIONS = {}; 845 | document.getElementById("auths").innerHTML = ""; 846 | 847 | // add a new challenge section per authorization url 848 | for(var i = 0; i < ORDER['order_response']['authorizations'].length; i++){ 849 | 850 | // populate the authorization object 851 | var auth_url = ORDER['order_response']['authorizations'][i]; 852 | var auth_b64 = b64(auth_url); 853 | AUTHORIZATIONS[auth_url] = { 854 | // load authorization 855 | "auth_payload_json": "", // GET-as-POST has an empty payload 856 | "auth_payload_b64": "", // GET-as-POST has an empty payload 857 | "auth_protected_json": undefined, 858 | "auth_protected_b64": undefined, 859 | "auth_sig": undefined, 860 | "auth_response": undefined, 861 | 862 | // python server HTTP challenge 863 | "python_challenge_uri": undefined, 864 | "python_challenge_object": undefined, 865 | "python_challenge_protected_json": undefined, 866 | "python_challenge_protected_b64": undefined, 867 | "python_challenge_sig": undefined, 868 | "python_challenge_response": undefined, 869 | 870 | // file-based HTTP challenge 871 | "file_challenge_uri": undefined, 872 | "file_challenge_object": undefined, 873 | "file_challenge_protected_json": undefined, 874 | "file_challenge_protected_b64": undefined, 875 | "file_challenge_sig": undefined, 876 | "file_challenge_response": undefined, 877 | 878 | // DNS challenge 879 | "dns_challenge_uri": undefined, 880 | "dns_challenge_object": undefined, 881 | "dns_challenge_protected_json": undefined, 882 | "dns_challenge_protected_b64": undefined, 883 | "dns_challenge_sig": undefined, 884 | "dns_challenge_response": undefined, 885 | 886 | // post-challenge authorization check 887 | "recheck_auth_payload_json": "", // GET-as-POST has an empty payload 888 | "recheck_auth_payload_b64": "", // GET-as-POST has an empty payload 889 | "recheck_auth_protected_json": undefined, 890 | "recheck_auth_protected_b64": undefined, 891 | "recheck_auth_sig": undefined, 892 | "recheck_auth_response": undefined, 893 | }; 894 | 895 | // copy template for this authorization 896 | var template = document.getElementById("auth_template").cloneNode(true); 897 | template.querySelector(".auth_i").innerHTML = (i + 1); 898 | template.querySelector(".auth_count").innerHTML = ORDER['order_response']['authorizations'].length; 899 | 900 | // set unique ids for this authorization section 901 | template.setAttribute("id", "auth_" + auth_b64); 902 | template.querySelector(".auth_form").setAttribute("id", "auth_" + auth_b64 + "_form"); 903 | template.querySelector(".howto_auth_sig").setAttribute("id", "howto_" + auth_b64 + "_auth_sig"); 904 | template.querySelector(".howto_auth_sig_label").setAttribute("for", "howto_" + auth_b64 + "_auth_sig"); 905 | template.querySelector(".howto_auth_sig").setAttribute("id", "howto_" + auth_b64 + "_auth_sig"); 906 | template.querySelector(".howto_auth_sig_label").setAttribute("for", "howto_" + auth_b64 + "_auth_sig"); 907 | template.querySelector(".auth_sig_cmd").setAttribute("id", auth_b64 + "_auth_sig_cmd"); 908 | template.querySelector(".auth_sig").setAttribute("id", auth_b64 + "_auth_sig"); 909 | template.querySelector(".validate_auth_sig").setAttribute("id", "validate_" + auth_b64 + "_auth_sig"); 910 | template.querySelector(".validate_auth_sig_status").setAttribute("id", "validate_" + auth_b64 + "_auth_sig_status"); 911 | template.querySelector(".challenges").setAttribute("id", "challenges_" + auth_b64); 912 | 913 | // append auth template to page 914 | template.style.display = "block"; 915 | document.getElementById("auths").appendChild(template); 916 | } 917 | 918 | // populate the first authorization request 919 | buildAuthorization(0, status, function(){ 920 | 921 | // show step 4 922 | document.getElementById("step4").style.display = "block"; 923 | document.getElementById("step4_pending").style.display = "none"; 924 | 925 | // complete step 3c 926 | status.innerHTML = "Ordered! Proceed to Step 4!"; 927 | }); 928 | 929 | } 930 | 931 | // error registering 932 | else{ 933 | return fail(status, "Order failed. Please start back at Step 1. " + order_xhr.responseText); 934 | } 935 | } 936 | }; 937 | order_xhr.send(JSON.stringify({ 938 | "protected": ORDER['order_protected_b64'], 939 | "payload": ORDER['order_payload_b64'], 940 | "signature": ORDER['order_sig'], 941 | })); 942 | } 943 | 944 | /* 945 | * Step 4a: Sign request for getting an Authorization 946 | */ 947 | function buildAuthorization(n, status, callback){ 948 | 949 | // get the authorization from global order 950 | var auth_url = ORDER['order_response']['authorizations'][n]; 951 | var auth_b64 = b64(auth_url); 952 | 953 | // form fields 954 | var validate_form = document.getElementById("auth_" + auth_b64 + "_form"); 955 | var validate_cmd = document.getElementById(auth_b64 + "_auth_sig_cmd"); 956 | var validate_input = document.getElementById(auth_b64 + "_auth_sig"); 957 | var validate_submit = document.getElementById("validate_" + auth_b64 + "_auth_sig"); 958 | 959 | // hide following steps 960 | document.getElementById("step5").style.display = "none"; 961 | document.getElementById("step5_pending").style.display = "inline"; 962 | 963 | // hide challenges section until loaded 964 | var challenges = document.getElementById("challenges_" + auth_b64); 965 | challenges.style.display = "none"; 966 | 967 | // reset finalize signature 968 | document.getElementById("finalize_sig_cmd").value = "waiting until challenges are done..."; 969 | document.getElementById("finalize_sig_cmd").removeAttribute("readonly"); 970 | document.getElementById("finalize_sig_cmd").setAttribute("disabled", ""); 971 | document.getElementById("finalize_sig").value = ""; 972 | document.getElementById("finalize_sig").setAttribute("placeholder", "waiting until challenges are done..."); 973 | document.getElementById("finalize_sig").setAttribute("disabled", ""); 974 | document.getElementById("validate_finalize_sig").setAttribute("disabled", ""); 975 | document.getElementById("validate_finalize_sig_status").style.display = "none"; 976 | document.getElementById("validate_finalize_sig_status").className = ""; 977 | document.getElementById("validate_finalize_sig_status").innerHTML = ""; 978 | 979 | // reset recheck_order signature 980 | document.getElementById("recheck_order_sig_cmd").value = "waiting until order is finalized..."; 981 | document.getElementById("recheck_order_sig_cmd").removeAttribute("readonly"); 982 | document.getElementById("recheck_order_sig_cmd").setAttribute("disabled", ""); 983 | document.getElementById("recheck_order_sig").value = ""; 984 | document.getElementById("recheck_order_sig").setAttribute("placeholder", "waiting until order is finalized..."); 985 | document.getElementById("recheck_order_sig").setAttribute("disabled", ""); 986 | document.getElementById("validate_recheck_order_sig").setAttribute("disabled", ""); 987 | document.getElementById("validate_recheck_order_sig_status").style.display = "none"; 988 | document.getElementById("validate_recheck_order_sig_status").className = ""; 989 | document.getElementById("validate_recheck_order_sig_status").innerHTML = ""; 990 | 991 | // reset get cert signature 992 | document.getElementById("cert_sig_cmd").value = "waiting until certificate is generated..."; 993 | document.getElementById("cert_sig_cmd").removeAttribute("readonly"); 994 | document.getElementById("cert_sig_cmd").setAttribute("disabled", ""); 995 | document.getElementById("cert_sig").value = ""; 996 | document.getElementById("cert_sig").setAttribute("placeholder", "waiting until certificate is generated..."); 997 | document.getElementById("cert_sig").setAttribute("disabled", ""); 998 | document.getElementById("validate_cert_sig").setAttribute("disabled", ""); 999 | document.getElementById("validate_cert_sig_status").style.display = "none"; 1000 | document.getElementById("validate_cert_sig_status").className = ""; 1001 | document.getElementById("validate_cert_sig_status").innerHTML = ""; 1002 | 1003 | // status update 1004 | status.innerHTML = "loading nonce..."; 1005 | 1006 | // get nonce for loading the authorization request 1007 | getNonce(function(nonce, err){ 1008 | if(err){ 1009 | return fail(status, "Failed authorization nonce request (auth: " + auth_url + ") (code: " + err.status + "). " + err.responseText); 1010 | } 1011 | 1012 | // populate authorization request signature (payload is empty "") 1013 | var protected_json = { 1014 | "url": auth_url, 1015 | "alg": ACCOUNT['alg'], 1016 | "nonce": nonce, 1017 | "kid": ACCOUNT['account_uri'], 1018 | }; 1019 | var protected_b64 = b64(JSON.stringify(protected_json)); 1020 | AUTHORIZATIONS[auth_url]['auth_protected_json'] = protected_json 1021 | AUTHORIZATIONS[auth_url]['auth_protected_b64'] = protected_b64; 1022 | validate_cmd.value = "" + 1023 | "PRIV_KEY=./account.key; " + 1024 | "echo -n \"" + protected_b64 + "." + AUTHORIZATIONS[auth_url]['auth_payload_b64'] + "\" | " + 1025 | "openssl dgst -sha256 -hex -sign $PRIV_KEY"; 1026 | validate_cmd.setAttribute("readonly", ""); 1027 | validate_cmd.removeAttribute("disabled"); 1028 | validate_input.value = ""; 1029 | validate_input.setAttribute("placeholder", RESULT_PLACEHOLDER); 1030 | validate_input.removeAttribute("disabled"); 1031 | validate_submit.removeAttribute("disabled"); 1032 | 1033 | // set data properties so validateAuthorization() knows which challenge this is 1034 | validate_form.dataset.authurl = auth_url; 1035 | validate_form.addEventListener("submit", validateAuthorization); 1036 | 1037 | // let the caller know loading the nonce and populating the form is done 1038 | callback(); 1039 | }); 1040 | } 1041 | 1042 | 1043 | /* 1044 | * Step 4b: Load the Authorization to get its challenges (GET-as-POST /auth['url']) 1045 | */ 1046 | function validateAuthorization(e){ 1047 | e.preventDefault(); 1048 | 1049 | // clear previous status 1050 | var auth_url = e.target.dataset.authurl; 1051 | var auth_b64 = b64(auth_url); 1052 | var status = document.getElementById("validate_" + auth_b64 + "_auth_sig_status"); 1053 | var section_id = "auth_" + auth_b64; 1054 | var auth_section = document.getElementById(section_id); 1055 | status.style.display = "inline"; 1056 | status.className = "validate_auth_sig_status"; 1057 | status.innerHTML = "Loading challenges..."; 1058 | 1059 | // hide following steps 1060 | document.getElementById("step5").style.display = "none"; 1061 | document.getElementById("step5_pending").style.display = "inline"; 1062 | 1063 | // hide challenges section until re-populated 1064 | var challenges = document.getElementById("challenges_" + auth_b64); 1065 | challenges.style.display = "none"; 1066 | 1067 | // reset finalize signature 1068 | document.getElementById("finalize_sig_cmd").value = "waiting until challenges are done..."; 1069 | document.getElementById("finalize_sig_cmd").removeAttribute("readonly"); 1070 | document.getElementById("finalize_sig_cmd").setAttribute("disabled", ""); 1071 | document.getElementById("finalize_sig").value = ""; 1072 | document.getElementById("finalize_sig").setAttribute("placeholder", "waiting until challenges are done..."); 1073 | document.getElementById("finalize_sig").setAttribute("disabled", ""); 1074 | document.getElementById("validate_finalize_sig").setAttribute("disabled", ""); 1075 | document.getElementById("validate_finalize_sig_status").style.display = "none"; 1076 | document.getElementById("validate_finalize_sig_status").className = ""; 1077 | document.getElementById("validate_finalize_sig_status").innerHTML = ""; 1078 | 1079 | // reset recheck_order signature 1080 | document.getElementById("recheck_order_sig_cmd").value = "waiting until order is finalized..."; 1081 | document.getElementById("recheck_order_sig_cmd").removeAttribute("readonly"); 1082 | document.getElementById("recheck_order_sig_cmd").setAttribute("disabled", ""); 1083 | document.getElementById("recheck_order_sig").value = ""; 1084 | document.getElementById("recheck_order_sig").setAttribute("placeholder", "waiting until order is finalized..."); 1085 | document.getElementById("recheck_order_sig").setAttribute("disabled", ""); 1086 | document.getElementById("validate_recheck_order_sig").setAttribute("disabled", ""); 1087 | document.getElementById("validate_recheck_order_sig_status").style.display = "none"; 1088 | document.getElementById("validate_recheck_order_sig_status").className = ""; 1089 | document.getElementById("validate_recheck_order_sig_status").innerHTML = ""; 1090 | 1091 | // reset get cert signature 1092 | document.getElementById("cert_sig_cmd").value = "waiting until certificate is generated..."; 1093 | document.getElementById("cert_sig_cmd").removeAttribute("readonly"); 1094 | document.getElementById("cert_sig_cmd").setAttribute("disabled", ""); 1095 | document.getElementById("cert_sig").value = ""; 1096 | document.getElementById("cert_sig").setAttribute("placeholder", "waiting until certificate is generated..."); 1097 | document.getElementById("cert_sig").setAttribute("disabled", ""); 1098 | document.getElementById("validate_cert_sig").setAttribute("disabled", ""); 1099 | document.getElementById("validate_cert_sig_status").style.display = "none"; 1100 | document.getElementById("validate_cert_sig_status").className = ""; 1101 | document.getElementById("validate_cert_sig_status").innerHTML = ""; 1102 | 1103 | // validate auth payload exists 1104 | if(AUTHORIZATIONS[auth_url]['auth_payload_b64'] === undefined){ 1105 | return fail(status, "Update payload not found. Please go back to Step 1."); 1106 | } 1107 | 1108 | // validate the signature 1109 | var auth_sig = hex2b64(document.getElementById(auth_b64 + "_auth_sig").value); 1110 | if(auth_sig === null){ 1111 | return fail(status, "You need to run the above commands and paste the output in the text boxes below each command."); 1112 | } 1113 | AUTHORIZATIONS[auth_url]['auth_sig'] = auth_sig; 1114 | 1115 | // send request to CA to get the authorization 1116 | var auth_xhr = new XMLHttpRequest(); 1117 | auth_xhr.open("POST", auth_url); 1118 | auth_xhr.setRequestHeader("Content-Type", "application/jose+json"); 1119 | auth_xhr.onreadystatechange = function(){ 1120 | if(auth_xhr.readyState === 4){ 1121 | 1122 | // successful load 1123 | if(auth_xhr.status === 200){ 1124 | 1125 | // set auth response and uri 1126 | var auth_obj = JSON.parse(auth_xhr.responseText); 1127 | AUTHORIZATIONS[auth_url]['auth_response'] = auth_obj; 1128 | 1129 | // clear stale challenge objects 1130 | AUTHORIZATIONS[auth_url]['python_challenge_uri'] = undefined; 1131 | AUTHORIZATIONS[auth_url]['python_challenge_object'] = undefined; 1132 | AUTHORIZATIONS[auth_url]['file_challenge_uri'] = undefined; 1133 | AUTHORIZATIONS[auth_url]['file_challenge_object'] = undefined; 1134 | AUTHORIZATIONS[auth_url]['dns_challenge_uri'] = undefined; 1135 | AUTHORIZATIONS[auth_url]['dns_challenge_object'] = undefined; 1136 | 1137 | // update challenges in global 1138 | var challenge_dicts = AUTHORIZATIONS[auth_url]['auth_response']['challenges']; 1139 | for(var i = 0; i < challenge_dicts.length; i++){ 1140 | var challenge_dict = challenge_dicts[i]; 1141 | 1142 | // HTTP challenge 1143 | if(challenge_dict['type'] === "http-01"){ 1144 | AUTHORIZATIONS[auth_url]['python_challenge_uri'] = challenge_dict['url']; 1145 | AUTHORIZATIONS[auth_url]['python_challenge_object'] = challenge_dict; 1146 | AUTHORIZATIONS[auth_url]['file_challenge_uri'] = challenge_dict['url']; 1147 | AUTHORIZATIONS[auth_url]['file_challenge_object'] = challenge_dict; 1148 | } 1149 | 1150 | // DNS challenge 1151 | if(challenge_dict['type'] === "dns-01"){ 1152 | AUTHORIZATIONS[auth_url]['dns_challenge_uri'] = challenge_dict['url']; 1153 | AUTHORIZATIONS[auth_url]['dns_challenge_object'] = challenge_dict; 1154 | } 1155 | } 1156 | 1157 | // figure out which domain this authorization is checking 1158 | var domain = auth_obj['identifier']['value']; // domain name (e.g. foo.com) 1159 | 1160 | // domain name 1161 | challenges.querySelector(".domain").innerHTML = ""; 1162 | challenges.querySelector(".domain").appendChild(document.createTextNode(auth_obj['wildcard'] ? "*." + domain : domain)); 1163 | 1164 | // tabs 1165 | challenges.querySelector("input.challenge_python").setAttribute("name", "radio_" + auth_b64); 1166 | challenges.querySelector("input.challenge_python").setAttribute("id", "radio_" + auth_b64 + "_python"); 1167 | challenges.querySelector("label.challenge_python").setAttribute("for", "radio_" + auth_b64 + "_python"); 1168 | challenges.querySelector("label.challenge_python").style.display = "none"; 1169 | challenges.querySelector("input.challenge_file").setAttribute("name", "radio_" + auth_b64); 1170 | challenges.querySelector("input.challenge_file").setAttribute("id", "radio_" + auth_b64 + "_file"); 1171 | challenges.querySelector("label.challenge_file").setAttribute("for", "radio_" + auth_b64 + "_file"); 1172 | challenges.querySelector("label.challenge_file").style.display = "none"; 1173 | challenges.querySelector("input.challenge_dns").setAttribute("name", "radio_" + auth_b64); 1174 | challenges.querySelector("input.challenge_dns").setAttribute("id", "radio_" + auth_b64 + "_dns"); 1175 | challenges.querySelector("label.challenge_dns").setAttribute("for", "radio_" + auth_b64 + "_dns"); 1176 | challenges.querySelector("label.challenge_dns").style.display = "none"; 1177 | 1178 | // help texts 1179 | challenges.querySelector(".howto_python").setAttribute("id", "howto_" + auth_b64 + "_python"); 1180 | challenges.querySelector(".howto_python_label").setAttribute("for", "howto_" + auth_b64 + "_python"); 1181 | challenges.querySelector(".howto_python_sig").setAttribute("id", "howto_" + auth_b64 + "_python_sig"); 1182 | challenges.querySelector(".howto_python_sig_label").setAttribute("for", "howto_" + auth_b64 + "_python_sig"); 1183 | challenges.querySelector(".howto_recheck_auth_python_sig").setAttribute("id", "howto_" + auth_b64 + "_recheck_auth_python_sig"); 1184 | challenges.querySelector(".howto_recheck_auth_python_sig_label").setAttribute("for", "howto_" + auth_b64 + "_recheck_auth_python_sig"); 1185 | challenges.querySelector(".howto_file").setAttribute("id", "howto_" + auth_b64 + "_file"); 1186 | challenges.querySelector(".howto_file_label").setAttribute("for", "howto_" + auth_b64 + "_file"); 1187 | challenges.querySelector(".howto_file_sig").setAttribute("id", "howto_" + auth_b64 + "_file_sig"); 1188 | challenges.querySelector(".howto_file_sig_label").setAttribute("for", "howto_" + auth_b64 + "_file_sig"); 1189 | challenges.querySelector(".howto_recheck_auth_file_sig").setAttribute("id", "howto_" + auth_b64 + "_recheck_auth_file_sig"); 1190 | challenges.querySelector(".howto_recheck_auth_file_sig_label").setAttribute("for", "howto_" + auth_b64 + "_recheck_auth_file_sig"); 1191 | challenges.querySelector(".howto_dns").setAttribute("id", "howto_" + auth_b64 + "_dns"); 1192 | challenges.querySelector(".howto_dns_label").setAttribute("for", "howto_" + auth_b64 + "_dns"); 1193 | challenges.querySelector(".howto_dns_sig").setAttribute("id", "howto_" + auth_b64 + "_dns_sig"); 1194 | challenges.querySelector(".howto_dns_sig_label").setAttribute("for", "howto_" + auth_b64 + "_dns_sig"); 1195 | challenges.querySelector(".howto_recheck_auth_dns_sig").setAttribute("id", "howto_" + auth_b64 + "_recheck_auth_dns_sig"); 1196 | challenges.querySelector(".howto_recheck_auth_dns_sig_label").setAttribute("for", "howto_" + auth_b64 + "_recheck_auth_dns_sig"); 1197 | 1198 | // event listeners 1199 | challenges.querySelector(".confirm_python").addEventListener("submit", confirmChallenge); 1200 | challenges.querySelector(".confirm_file").addEventListener("submit", confirmChallenge); 1201 | challenges.querySelector(".confirm_dns").addEventListener("submit", confirmChallenge); 1202 | challenges.querySelector(".validate_python_sig").addEventListener("submit", validateChallenge); 1203 | challenges.querySelector(".validate_file_sig").addEventListener("submit", validateChallenge); 1204 | challenges.querySelector(".validate_dns_sig").addEventListener("submit", validateChallenge); 1205 | challenges.querySelector(".validate_recheck_auth_python_sig").addEventListener("submit", checkAuthorization); 1206 | challenges.querySelector(".validate_recheck_auth_file_sig").addEventListener("submit", checkAuthorization); 1207 | challenges.querySelector(".validate_recheck_auth_dns_sig").addEventListener("submit", checkAuthorization); 1208 | 1209 | // python option data 1210 | if(AUTHORIZATIONS[auth_url]['python_challenge_object'] !== undefined){ 1211 | 1212 | // populate values 1213 | var token = AUTHORIZATIONS[auth_url]['python_challenge_object']['token']; 1214 | var keyauth = token + "." + ACCOUNT['thumbprint']; 1215 | var link = "http://" + domain + "/.well-known/acme-challenge/" + token; 1216 | challenges.querySelector(".python_link").innerHTML = ""; 1217 | challenges.querySelector(".python_link").appendChild(document.createTextNode(link)); 1218 | challenges.querySelector(".python_link").setAttribute("href", link); 1219 | challenges.querySelector(".python_domain").innerHTML = ""; 1220 | challenges.querySelector(".python_domain").appendChild(document.createTextNode(domain)); 1221 | challenges.querySelector(".python_server").value = "" + 1222 | "sudo python2 -c \"import BaseHTTPServer; \\\n" + 1223 | " h = BaseHTTPServer.BaseHTTPRequestHandler; \\\n" + 1224 | " h.do_GET = lambda r: r.send_response(200) or r.end_headers() " + 1225 | "or r.wfile.write('" + keyauth + "'); \\\n" + 1226 | " s = BaseHTTPServer.HTTPServer(('0.0.0.0', 80), h); \\\n" + 1227 | " s.serve_forever()\""; 1228 | challenges.querySelector(".confirm_python_submit").value = "I'm now running this command on " + domain; 1229 | challenges.querySelector(".validate_python_sig_submit").value = "Submit challenge for " + domain; 1230 | challenges.querySelector("label.challenge_python").style.display = "inline-block"; 1231 | 1232 | // set data attributes 1233 | var challenge_url = AUTHORIZATIONS[auth_url]['python_challenge_object']['url']; 1234 | challenges.querySelector(".confirm_python").dataset.option = "python"; 1235 | challenges.querySelector(".confirm_python").dataset.section = section_id; 1236 | challenges.querySelector(".confirm_python").dataset.auth = auth_url; 1237 | challenges.querySelector(".confirm_python").dataset.challenge = challenge_url; 1238 | } 1239 | 1240 | // file-based option data 1241 | if(AUTHORIZATIONS[auth_url]['file_challenge_object'] !== undefined){ 1242 | 1243 | // populate values 1244 | var token = AUTHORIZATIONS[auth_url]['file_challenge_object']['token']; 1245 | 1246 | var keyauth = token + "." + ACCOUNT['thumbprint']; 1247 | var link = "http://" + domain + "/.well-known/acme-challenge/" + token; 1248 | var server_config = "" + 1249 | "#nginx example\n" + 1250 | "location /.well-known/acme-challenge/ {\n" + 1251 | " alias /path/to/www/;\n" + 1252 | " try_files $uri =404;\n" + 1253 | "}\n\n" + 1254 | "#apache example\n" + 1255 | "Alias /.well-known/acme-challenge /path/to/www/.well-known/acme-challenge"; 1256 | var echo = "echo -n \"" + keyauth + "\" > /path/to/www/.well-known/acme-challenge/" + token; 1257 | challenges.querySelector(".file_config").innerHTML = ""; 1258 | challenges.querySelector(".file_config").appendChild(document.createTextNode(server_config)); 1259 | challenges.querySelector(".file_echo").innerHTML = ""; 1260 | challenges.querySelector(".file_echo").appendChild(document.createTextNode(echo)); 1261 | challenges.querySelector(".file_link").innerHTML = ""; 1262 | challenges.querySelector(".file_link").appendChild(document.createTextNode(link)); 1263 | challenges.querySelector(".file_link").setAttribute("href", link); 1264 | challenges.querySelector(".file_url").value = link; 1265 | challenges.querySelector(".file_data").value = keyauth; 1266 | challenges.querySelector(".confirm_file_submit").value = "I'm now serving this file on " + domain; 1267 | challenges.querySelector(".validate_file_sig_submit").value = "Submit challenge for " + domain; 1268 | challenges.querySelector("label.challenge_file").style.display = "inline-block"; 1269 | 1270 | // set data attributes 1271 | var challenge_url = AUTHORIZATIONS[auth_url]['file_challenge_object']['url']; 1272 | challenges.querySelector(".confirm_file").dataset.option = "file"; 1273 | challenges.querySelector(".confirm_file").dataset.section = section_id; 1274 | challenges.querySelector(".confirm_file").dataset.auth = auth_url; 1275 | challenges.querySelector(".confirm_file").dataset.challenge = challenge_url; 1276 | } 1277 | 1278 | // DNS option data 1279 | if(AUTHORIZATIONS[auth_url]['dns_challenge_object'] !== undefined){ 1280 | 1281 | // SHA-256 digest of keyauth 1282 | var token = AUTHORIZATIONS[auth_url]['dns_challenge_object']['token']; 1283 | var keyauth = token + "." + ACCOUNT['thumbprint']; 1284 | var keyauth_bytes = []; 1285 | for(var i = 0; i < keyauth.length; i++){ 1286 | keyauth_bytes.push(keyauth.charCodeAt(i)); 1287 | } 1288 | sha256(new Uint8Array(keyauth_bytes), function(hash, err){ 1289 | if(err){ 1290 | return fail(status, "Generating DNS data failed: " + err.message); 1291 | } 1292 | var dns_data = b64(hash); 1293 | 1294 | // populate dns option 1295 | var dig = "dig +short @ns.yournameserver.com _acme-challenge." + domain + " TXT"; 1296 | challenges.querySelector(".dns_dig").innerHTML = ""; 1297 | challenges.querySelector(".dns_dig").appendChild(document.createTextNode(dig)); 1298 | challenges.querySelector(".dns_domain").innerHTML = ""; 1299 | challenges.querySelector(".dns_domain").appendChild(document.createTextNode(domain)); 1300 | challenges.querySelector(".dns_value").innerHTML = ""; 1301 | challenges.querySelector(".dns_value").appendChild(document.createTextNode(dns_data)); 1302 | challenges.querySelector(".dns_subdomain").value = "_acme-challenge." + domain; 1303 | challenges.querySelector(".dns_data").value = dns_data; 1304 | challenges.querySelector(".confirm_dns_submit").value = "I can see the TXT record for " + domain; 1305 | challenges.querySelector(".validate_dns_sig_submit").value = "Submit challenge for " + domain; 1306 | challenges.querySelector("label.challenge_dns").style.display = "inline-block"; 1307 | 1308 | // data attributes 1309 | var challenge_url = AUTHORIZATIONS[auth_url]['dns_challenge_object']['url']; 1310 | challenges.querySelector(".confirm_dns").dataset.option = "dns"; 1311 | challenges.querySelector(".confirm_dns").dataset.section = section_id; 1312 | challenges.querySelector(".confirm_dns").dataset.auth = auth_url; 1313 | challenges.querySelector(".confirm_dns").dataset.challenge = challenge_url; 1314 | 1315 | // auto-select Option 3 if no other options 1316 | if(AUTHORIZATIONS[auth_url]['python_challenge_object'] === undefined 1317 | && AUTHORIZATIONS[auth_url]['file_challenge_object'] === undefined){ 1318 | challenges.querySelector("input.challenge_python").removeAttribute("checked"); 1319 | challenges.querySelector("input.challenge_dns").setAttribute("checked", ""); 1320 | challenges.querySelector("label.challenge_dns").innerHTML = "Option 1 - DNS record (wildcard)"; 1321 | } 1322 | 1323 | // show the challenges 1324 | status.innerHTML = "Challenges loaded! Choose a challenge option below."; 1325 | challenges.style.display = "block"; 1326 | auth_section.querySelector(".challenges-status").style.display = "none"; 1327 | }); 1328 | } 1329 | 1330 | // no DNS option, so show the challenges without hashing anything 1331 | else{ 1332 | // show the challenges 1333 | status.innerHTML = "Challenges loaded! Choose a challenge option below."; 1334 | challenges.style.display = "block"; 1335 | auth_section.querySelector(".challenges-status").style.display = "none"; 1336 | } 1337 | } 1338 | 1339 | // error loading authorization 1340 | else{ 1341 | return fail(status, "Loading challenges failed. Please start back at Step 1. " + auth_xhr.responseText); 1342 | } 1343 | } 1344 | }; 1345 | auth_xhr.send(JSON.stringify({ 1346 | "protected": AUTHORIZATIONS[auth_url]['auth_protected_b64'], 1347 | "payload": AUTHORIZATIONS[auth_url]['auth_payload_b64'], 1348 | "signature": AUTHORIZATIONS[auth_url]['auth_sig'], 1349 | })); 1350 | } 1351 | 1352 | 1353 | /* 1354 | * Step 4c: Confirm Challenge 1355 | */ 1356 | function confirmChallenge(e){ 1357 | e.preventDefault(); 1358 | 1359 | // find the relevant resources 1360 | var section_id = e.target.dataset.section; // auth_... 1361 | var option = e.target.dataset.option; // "python", "file", or "dns" 1362 | var auth_url = e.target.dataset.auth; 1363 | var domain = AUTHORIZATIONS[auth_url]['auth_response']['identifier']['value']; 1364 | var challenge_url = e.target.dataset.challenge; 1365 | var section = document.getElementById(section_id); 1366 | var status = section.querySelector(".confirm_" + option + "_status"); 1367 | var validate_form = section.querySelector(".validate_" + option + "_sig"); 1368 | var validate_submit = section.querySelector(".validate_" + option + "_sig_submit"); 1369 | var validate_cmd = section.querySelector("." + option + "_sig_cmd"); 1370 | var validate_input = section.querySelector("." + option + "_sig"); 1371 | var validate_status_class = option + "_sig_status" 1372 | var validate_status = section.querySelector("." + validate_status_class); 1373 | 1374 | // clear previous status 1375 | status.style.display = "inline"; 1376 | status.className = ""; 1377 | status.innerHTML = "confirming..."; 1378 | 1379 | // hide following steps 1380 | document.getElementById("step5").style.display = "none"; 1381 | document.getElementById("step5_pending").style.display = "inline"; 1382 | 1383 | // reset validate challenge signature 1384 | validate_cmd.value = "waiting until confirmation is done..."; 1385 | validate_cmd.removeAttribute("readonly"); 1386 | validate_cmd.setAttribute("disabled", ""); 1387 | validate_input.value = ""; 1388 | validate_input.setAttribute("placeholder", "waiting until confirmation is done..."); 1389 | validate_input.setAttribute("disabled", ""); 1390 | validate_submit.setAttribute("disabled", ""); 1391 | validate_status.style.display = "none"; 1392 | validate_status.className = validate_status_class; 1393 | validate_status.innerHTML = ""; 1394 | 1395 | // reset authorization check signature 1396 | section.querySelector(".recheck_auth_" + option + "_sig_cmd").value = "waiting until you submit the challenge above..."; 1397 | section.querySelector(".recheck_auth_" + option + "_sig_cmd").removeAttribute("readonly"); 1398 | section.querySelector(".recheck_auth_" + option + "_sig_cmd").setAttribute("disabled", ""); 1399 | section.querySelector(".recheck_auth_" + option + "_sig").value = ""; 1400 | section.querySelector(".recheck_auth_" + option + "_sig").setAttribute("placeholder", "waiting until challenges are done..."); 1401 | section.querySelector(".recheck_auth_" + option + "_sig").setAttribute("disabled", ""); 1402 | section.querySelector(".validate_recheck_auth_" + option + "_sig_submit").setAttribute("disabled", ""); 1403 | section.querySelector(".validate_recheck_auth_" + option + "_sig_status").style.display = "none"; 1404 | section.querySelector(".validate_recheck_auth_" + option + "_sig_status").className = "validate_recheck_auth_" + option + "_sig_status"; 1405 | section.querySelector(".validate_recheck_auth_" + option + "_sig_status").innerHTML = ""; 1406 | 1407 | // reset finalize signature 1408 | document.getElementById("finalize_sig_cmd").value = "waiting until challenges are done..."; 1409 | document.getElementById("finalize_sig_cmd").removeAttribute("readonly"); 1410 | document.getElementById("finalize_sig_cmd").setAttribute("disabled", ""); 1411 | document.getElementById("finalize_sig").value = ""; 1412 | document.getElementById("finalize_sig").setAttribute("placeholder", "waiting until challenges are done..."); 1413 | document.getElementById("finalize_sig").setAttribute("disabled", ""); 1414 | document.getElementById("validate_finalize_sig").setAttribute("disabled", ""); 1415 | document.getElementById("validate_finalize_sig_status").style.display = "none"; 1416 | document.getElementById("validate_finalize_sig_status").className = ""; 1417 | document.getElementById("validate_finalize_sig_status").innerHTML = ""; 1418 | 1419 | // reset recheck_order signature 1420 | document.getElementById("recheck_order_sig_cmd").value = "waiting until order is finalized..."; 1421 | document.getElementById("recheck_order_sig_cmd").removeAttribute("readonly"); 1422 | document.getElementById("recheck_order_sig_cmd").setAttribute("disabled", ""); 1423 | document.getElementById("recheck_order_sig").value = ""; 1424 | document.getElementById("recheck_order_sig").setAttribute("placeholder", "waiting until order is finalized..."); 1425 | document.getElementById("recheck_order_sig").setAttribute("disabled", ""); 1426 | document.getElementById("validate_recheck_order_sig").setAttribute("disabled", ""); 1427 | document.getElementById("validate_recheck_order_sig_status").style.display = "none"; 1428 | document.getElementById("validate_recheck_order_sig_status").className = ""; 1429 | document.getElementById("validate_recheck_order_sig_status").innerHTML = ""; 1430 | 1431 | // reset get cert signature 1432 | document.getElementById("cert_sig_cmd").value = "waiting until certificate is generated..."; 1433 | document.getElementById("cert_sig_cmd").removeAttribute("readonly"); 1434 | document.getElementById("cert_sig_cmd").setAttribute("disabled", ""); 1435 | document.getElementById("cert_sig").value = ""; 1436 | document.getElementById("cert_sig").setAttribute("placeholder", "waiting until certificate is generated..."); 1437 | document.getElementById("cert_sig").setAttribute("disabled", ""); 1438 | document.getElementById("validate_cert_sig").setAttribute("disabled", ""); 1439 | document.getElementById("validate_cert_sig_status").style.display = "none"; 1440 | document.getElementById("validate_cert_sig_status").className = ""; 1441 | document.getElementById("validate_cert_sig_status").innerHTML = ""; 1442 | 1443 | // get nonce for challenge 1444 | getNonce(function(nonce, err){ 1445 | if(err){ 1446 | return fail(status, "Failed challenge nonce request (domain: " + domain + ") (code: " + err.status + "). " + err.responseText); 1447 | } 1448 | 1449 | // populate challenge signature (payload is empty {}) 1450 | var protected_json = { 1451 | "url": challenge_url, 1452 | "alg": ACCOUNT['alg'], 1453 | "nonce": nonce, 1454 | "kid": ACCOUNT['account_uri'], 1455 | }; 1456 | var protected_b64 = b64(JSON.stringify(protected_json)); 1457 | AUTHORIZATIONS[auth_url][option + '_protected_json'] = protected_json 1458 | AUTHORIZATIONS[auth_url][option + '_protected_b64'] = protected_b64; 1459 | validate_cmd.value = "" + 1460 | "PRIV_KEY=./account.key; " + 1461 | "echo -n \"" + protected_b64 + "." + b64(JSON.stringify({})) + "\" | " + 1462 | "openssl dgst -sha256 -hex -sign $PRIV_KEY"; 1463 | validate_cmd.setAttribute("readonly", ""); 1464 | validate_cmd.removeAttribute("disabled"); 1465 | validate_input.value = ""; 1466 | validate_input.setAttribute("placeholder", RESULT_PLACEHOLDER); 1467 | validate_input.removeAttribute("disabled"); 1468 | validate_submit.removeAttribute("disabled"); 1469 | 1470 | // set data properties so validateChallenge() knows which challenge this is 1471 | validate_form.dataset.option = option; 1472 | validate_form.dataset.section = section_id; 1473 | validate_form.dataset.auth = auth_url; 1474 | validate_form.dataset.challenge = challenge_url; 1475 | 1476 | // complete step 4a 1477 | status.innerHTML = "Ready for the next command!"; 1478 | }); 1479 | } 1480 | 1481 | /* 1482 | * Step 4d: Verify Ownership (POST /challenge['url'], ...) 1483 | */ 1484 | function validateChallenge(e){ 1485 | e.preventDefault(); 1486 | 1487 | // find the relevant resources 1488 | var section_id = e.target.dataset.section; // auth_... 1489 | var option = e.target.dataset.option; // "python", "file", or "dns" 1490 | var auth_url = e.target.dataset.auth; 1491 | var domain = AUTHORIZATIONS[auth_url]['auth_response']['identifier']['value']; 1492 | var challenge_url = e.target.dataset.challenge; 1493 | var section = document.getElementById(section_id); 1494 | var status_class = option + "_sig_status"; 1495 | var status = section.querySelector("." + status_class); 1496 | var sig_input = section.querySelector("." + option + "_sig"); 1497 | var recheck_form = section.querySelector(".validate_recheck_auth_" + option + "_sig"); 1498 | var recheck_submit = section.querySelector(".validate_recheck_auth_" + option + "_sig_submit"); 1499 | var recheck_cmd = section.querySelector(".recheck_auth_" + option + "_sig_cmd"); 1500 | var recheck_input = section.querySelector(".recheck_auth_" + option + "_sig"); 1501 | var recheck_status_class = "validate_recheck_auth_" + option + "_sig_status"; 1502 | var recheck_status = section.querySelector("." + recheck_status_class); 1503 | 1504 | // clear previous status 1505 | status.style.display = "inline"; 1506 | status.className = status_class; 1507 | status.innerHTML = "submitting..."; 1508 | 1509 | // hide following steps 1510 | document.getElementById("step5").style.display = "none"; 1511 | document.getElementById("step5_pending").style.display = "inline"; 1512 | 1513 | // reset authorization check signature 1514 | section.querySelector(".recheck_auth_" + option + "_sig_cmd").value = "waiting until you submit the challenge above..."; 1515 | section.querySelector(".recheck_auth_" + option + "_sig_cmd").removeAttribute("readonly"); 1516 | section.querySelector(".recheck_auth_" + option + "_sig_cmd").setAttribute("disabled", ""); 1517 | section.querySelector(".recheck_auth_" + option + "_sig").value = ""; 1518 | section.querySelector(".recheck_auth_" + option + "_sig").setAttribute("placeholder", "waiting until challenges are done..."); 1519 | section.querySelector(".recheck_auth_" + option + "_sig").setAttribute("disabled", ""); 1520 | section.querySelector(".validate_recheck_auth_" + option + "_sig_submit").setAttribute("disabled", ""); 1521 | section.querySelector(".validate_recheck_auth_" + option + "_sig_status").style.display = "none"; 1522 | section.querySelector(".validate_recheck_auth_" + option + "_sig_status").className = recheck_status_class; 1523 | section.querySelector(".validate_recheck_auth_" + option + "_sig_status").innerHTML = ""; 1524 | 1525 | // reset finalize signature 1526 | document.getElementById("finalize_sig_cmd").value = "waiting until challenges are done..."; 1527 | document.getElementById("finalize_sig_cmd").removeAttribute("readonly"); 1528 | document.getElementById("finalize_sig_cmd").setAttribute("disabled", ""); 1529 | document.getElementById("finalize_sig").value = ""; 1530 | document.getElementById("finalize_sig").setAttribute("placeholder", "waiting until challenges are done..."); 1531 | document.getElementById("finalize_sig").setAttribute("disabled", ""); 1532 | document.getElementById("validate_finalize_sig").setAttribute("disabled", ""); 1533 | document.getElementById("validate_finalize_sig_status").style.display = "none"; 1534 | document.getElementById("validate_finalize_sig_status").className = ""; 1535 | document.getElementById("validate_finalize_sig_status").innerHTML = ""; 1536 | 1537 | // reset recheck_order signature 1538 | document.getElementById("recheck_order_sig_cmd").value = "waiting until order is finalized..."; 1539 | document.getElementById("recheck_order_sig_cmd").removeAttribute("readonly"); 1540 | document.getElementById("recheck_order_sig_cmd").setAttribute("disabled", ""); 1541 | document.getElementById("recheck_order_sig").value = ""; 1542 | document.getElementById("recheck_order_sig").setAttribute("placeholder", "waiting until order is finalized..."); 1543 | document.getElementById("recheck_order_sig").setAttribute("disabled", ""); 1544 | document.getElementById("validate_recheck_order_sig").setAttribute("disabled", ""); 1545 | document.getElementById("validate_recheck_order_sig_status").style.display = "none"; 1546 | document.getElementById("validate_recheck_order_sig_status").className = ""; 1547 | document.getElementById("validate_recheck_order_sig_status").innerHTML = ""; 1548 | 1549 | // reset get cert signature 1550 | document.getElementById("cert_sig_cmd").value = "waiting until certificate is generated..."; 1551 | document.getElementById("cert_sig_cmd").removeAttribute("readonly"); 1552 | document.getElementById("cert_sig_cmd").setAttribute("disabled", ""); 1553 | document.getElementById("cert_sig").value = ""; 1554 | document.getElementById("cert_sig").setAttribute("placeholder", "waiting until certificate is generated..."); 1555 | document.getElementById("cert_sig").setAttribute("disabled", ""); 1556 | document.getElementById("validate_cert_sig").setAttribute("disabled", ""); 1557 | document.getElementById("validate_cert_sig_status").style.display = "none"; 1558 | document.getElementById("validate_cert_sig_status").className = ""; 1559 | document.getElementById("validate_cert_sig_status").innerHTML = ""; 1560 | 1561 | // validate challenge protected exists 1562 | if(AUTHORIZATIONS[auth_url][option + '_protected_b64'] === undefined){ 1563 | return fail(status, "Update payload not found. Please go back to Step 1."); 1564 | } 1565 | 1566 | // validate the signature 1567 | var challenge_sig = hex2b64(sig_input.value); 1568 | if(challenge_sig === null){ 1569 | return fail(status, "You need to run the above commands and paste the output in the text boxes below each command."); 1570 | } 1571 | AUTHORIZATIONS[auth_url][option + '_challenge_sig'] = challenge_sig; 1572 | 1573 | // submit challenge to CA 1574 | var challenge_xhr = new XMLHttpRequest(); 1575 | challenge_xhr.open("POST", challenge_url); 1576 | challenge_xhr.setRequestHeader("Content-Type", "application/jose+json"); 1577 | challenge_xhr.onreadystatechange = function(){ 1578 | if(challenge_xhr.readyState === 4){ 1579 | 1580 | // successful challenge submission 1581 | if(challenge_xhr.status === 200){ 1582 | 1583 | // set challenge response 1584 | AUTHORIZATIONS[auth_url][option + '_challenge_response'] = JSON.parse(challenge_xhr.responseText); 1585 | 1586 | // update status message before loading nonce 1587 | status.innerHTML = "Submitted! Loading next step..."; 1588 | 1589 | // get nonce for checking the authorization status 1590 | getNonce(function(nonce, err){ 1591 | if(err){ 1592 | return fail(status, "Failed challenge verify nonce request (domain: " + domain + ") (code: " + err.status + "). " + err.responseText); 1593 | } 1594 | 1595 | // populate authorization request signature (payload is empty "") 1596 | var protected_json = { 1597 | "url": auth_url, 1598 | "alg": ACCOUNT['alg'], 1599 | "nonce": nonce, 1600 | "kid": ACCOUNT['account_uri'], 1601 | }; 1602 | var protected_b64 = b64(JSON.stringify(protected_json)); 1603 | AUTHORIZATIONS[auth_url]['recheck_auth_protected_json'] = protected_json 1604 | AUTHORIZATIONS[auth_url]['recheck_auth_protected_b64'] = protected_b64; 1605 | recheck_cmd.value = "" + 1606 | "PRIV_KEY=./account.key; " + 1607 | "echo -n \"" + protected_b64 + "." + AUTHORIZATIONS[auth_url]['recheck_auth_payload_b64'] + "\" | " + 1608 | "openssl dgst -sha256 -hex -sign $PRIV_KEY"; 1609 | recheck_cmd.setAttribute("readonly", ""); 1610 | recheck_cmd.removeAttribute("disabled"); 1611 | recheck_input.value = ""; 1612 | recheck_input.setAttribute("placeholder", RESULT_PLACEHOLDER); 1613 | recheck_input.removeAttribute("disabled"); 1614 | recheck_submit.removeAttribute("disabled"); 1615 | 1616 | // set data properties so checkAuthorization() knows which auth this is 1617 | recheck_form.dataset.option = option; 1618 | recheck_form.dataset.section = section_id; 1619 | recheck_form.dataset.auth = auth_url; 1620 | 1621 | // update status 1622 | status.innerHTML = "Challenge submitted! Proceed to next command below."; 1623 | }); 1624 | } 1625 | 1626 | // error submitting challenge 1627 | else{ 1628 | return fail(status, "Challenge submission failed. Please start back at Step 1. " + challenge_xhr.responseText); 1629 | } 1630 | } 1631 | }; 1632 | challenge_xhr.send(JSON.stringify({ 1633 | "protected": AUTHORIZATIONS[auth_url][option + '_protected_b64'], 1634 | "payload": b64(JSON.stringify({})), // always empty payload 1635 | "signature": AUTHORIZATIONS[auth_url][option + '_challenge_sig'], 1636 | })); 1637 | } 1638 | 1639 | /* 1640 | * Step 4e: Check authorization status after submitting the challenge (GET-as-POST /auth['url']) 1641 | */ 1642 | function checkAuthorization(e){ 1643 | e.preventDefault(); 1644 | 1645 | // find the relevant resources 1646 | var section_id = e.target.dataset.section; // auth_... 1647 | var option = e.target.dataset.option; // "python", "file", or "dns" 1648 | var auth_url = e.target.dataset.auth; 1649 | var domain = AUTHORIZATIONS[auth_url]['auth_response']['identifier']['value']; 1650 | var section = document.getElementById(section_id); 1651 | var status_class = "validate_recheck_auth_" + option + "_sig_status"; 1652 | var status = section.querySelector("." + status_class); 1653 | var sig_input = section.querySelector(".recheck_auth_" + option + "_sig"); 1654 | var recheck_submit = section.querySelector(".validate_recheck_auth_" + option + "_sig_submit"); 1655 | var recheck_cmd = section.querySelector(".recheck_auth_" + option + "_sig_cmd"); 1656 | var recheck_input = section.querySelector(".recheck_auth_" + option + "_sig"); 1657 | 1658 | // clear previous status 1659 | status.style.display = "inline"; 1660 | status.className = status_class; 1661 | status.innerHTML = "checking..."; 1662 | 1663 | // hide following steps 1664 | document.getElementById("step5").style.display = "none"; 1665 | document.getElementById("step5_pending").style.display = "inline"; 1666 | 1667 | // reset finalize signature 1668 | document.getElementById("finalize_sig_cmd").value = "waiting until challenges are done..."; 1669 | document.getElementById("finalize_sig_cmd").removeAttribute("readonly"); 1670 | document.getElementById("finalize_sig_cmd").setAttribute("disabled", ""); 1671 | document.getElementById("finalize_sig").value = ""; 1672 | document.getElementById("finalize_sig").setAttribute("placeholder", "waiting until challenges are done..."); 1673 | document.getElementById("finalize_sig").setAttribute("disabled", ""); 1674 | document.getElementById("validate_finalize_sig").setAttribute("disabled", ""); 1675 | document.getElementById("validate_finalize_sig_status").style.display = "none"; 1676 | document.getElementById("validate_finalize_sig_status").className = ""; 1677 | document.getElementById("validate_finalize_sig_status").innerHTML = ""; 1678 | 1679 | // reset recheck_order signature 1680 | document.getElementById("recheck_order_sig_cmd").value = "waiting until order is finalized..."; 1681 | document.getElementById("recheck_order_sig_cmd").removeAttribute("readonly"); 1682 | document.getElementById("recheck_order_sig_cmd").setAttribute("disabled", ""); 1683 | document.getElementById("recheck_order_sig").value = ""; 1684 | document.getElementById("recheck_order_sig").setAttribute("placeholder", "waiting until order is finalized..."); 1685 | document.getElementById("recheck_order_sig").setAttribute("disabled", ""); 1686 | document.getElementById("validate_recheck_order_sig").setAttribute("disabled", ""); 1687 | document.getElementById("validate_recheck_order_sig_status").style.display = "none"; 1688 | document.getElementById("validate_recheck_order_sig_status").className = ""; 1689 | document.getElementById("validate_recheck_order_sig_status").innerHTML = ""; 1690 | 1691 | // reset get cert signature 1692 | document.getElementById("cert_sig_cmd").value = "waiting until certificate is generated..."; 1693 | document.getElementById("cert_sig_cmd").removeAttribute("readonly"); 1694 | document.getElementById("cert_sig_cmd").setAttribute("disabled", ""); 1695 | document.getElementById("cert_sig").value = ""; 1696 | document.getElementById("cert_sig").setAttribute("placeholder", "waiting until certificate is generated..."); 1697 | document.getElementById("cert_sig").setAttribute("disabled", ""); 1698 | document.getElementById("validate_cert_sig").setAttribute("disabled", ""); 1699 | document.getElementById("validate_cert_sig_status").style.display = "none"; 1700 | document.getElementById("validate_cert_sig_status").className = ""; 1701 | document.getElementById("validate_cert_sig_status").innerHTML = ""; 1702 | 1703 | // validate recheck_auth protected exists 1704 | if(AUTHORIZATIONS[auth_url]['recheck_auth_protected_b64'] === undefined){ 1705 | return fail(status, "Status check payload not found. Please go back to Step 1."); 1706 | } 1707 | 1708 | // validate the signature 1709 | var recheck_auth_sig = hex2b64(sig_input.value); 1710 | if(recheck_auth_sig === null){ 1711 | return fail(status, "You need to run the above commands and paste the output in the text boxes below each command."); 1712 | } 1713 | AUTHORIZATIONS[auth_url]['recheck_auth_sig'] = recheck_auth_sig; 1714 | 1715 | // send request to CA to get the authorization 1716 | var recheck_auth_xhr = new XMLHttpRequest(); 1717 | recheck_auth_xhr.open("POST", auth_url); 1718 | recheck_auth_xhr.setRequestHeader("Content-Type", "application/jose+json"); 1719 | recheck_auth_xhr.onreadystatechange = function(){ 1720 | if(recheck_auth_xhr.readyState === 4){ 1721 | 1722 | // successful load 1723 | if(recheck_auth_xhr.status === 200){ 1724 | 1725 | // set recheck_auth response 1726 | var auth_obj = JSON.parse(recheck_auth_xhr.responseText); 1727 | AUTHORIZATIONS[auth_url]['recheck_auth_response'] = auth_obj; 1728 | 1729 | // authorization pending, so ask the user to check again 1730 | if(auth_obj['status'] === "pending"){ 1731 | 1732 | // update the status before getting another nonce 1733 | status.innerHTML = "loading..."; 1734 | 1735 | // clear the existing signature 1736 | AUTHORIZATIONS[auth_url]['recheck_auth_sig'] = undefined; 1737 | 1738 | // get nonce for checking the authorization status, again 1739 | getNonce(function(nonce, err){ 1740 | if(err){ 1741 | return fail(status, "Failed status nonce request (domain: " + domain + ") (code: " + err.status + "). " + err.responseText); 1742 | } 1743 | 1744 | // populate authorization request signature (payload is empty "") 1745 | var protected_json = { 1746 | "url": auth_url, 1747 | "alg": ACCOUNT['alg'], 1748 | "nonce": nonce, 1749 | "kid": ACCOUNT['account_uri'], 1750 | }; 1751 | var protected_b64 = b64(JSON.stringify(protected_json)); 1752 | AUTHORIZATIONS[auth_url]['recheck_auth_protected_json'] = protected_json 1753 | AUTHORIZATIONS[auth_url]['recheck_auth_protected_b64'] = protected_b64; 1754 | recheck_cmd.value = "" + 1755 | "PRIV_KEY=./account.key; " + 1756 | "echo -n \"" + protected_b64 + "." + AUTHORIZATIONS[auth_url]['recheck_auth_payload_b64'] + "\" | " + 1757 | "openssl dgst -sha256 -hex -sign $PRIV_KEY"; 1758 | recheck_cmd.setAttribute("readonly", ""); 1759 | recheck_cmd.removeAttribute("disabled"); 1760 | recheck_input.value = ""; 1761 | recheck_input.setAttribute("placeholder", RESULT_PLACEHOLDER); 1762 | recheck_input.removeAttribute("disabled"); 1763 | recheck_submit.removeAttribute("disabled"); 1764 | 1765 | // update status 1766 | status.innerHTML = "Challenge still pending. Copy and run the command again to check again."; 1767 | }); 1768 | } 1769 | 1770 | // authorization valid, so proceed to next set of challenges or finalize 1771 | else if(auth_obj['status'] === "valid"){ 1772 | 1773 | // find the next authorization that doesn't have a recheck status 1774 | var next_auth_i = undefined; 1775 | for(var i = 0; i < ORDER['order_response']['authorizations'].length; i++){ 1776 | var a_url = ORDER['order_response']['authorizations'][i]; 1777 | if(AUTHORIZATIONS[a_url]['recheck_auth_response'] === undefined){ 1778 | next_auth_i = i; 1779 | break; 1780 | } 1781 | } 1782 | 1783 | // load the next authorization command 1784 | if(next_auth_i !== undefined){ 1785 | buildAuthorization(next_auth_i, status, function(){ 1786 | status.innerHTML = "Challenge complete! Proceed to load next set of challenges."; 1787 | }); 1788 | } 1789 | 1790 | // all authorizations done! so finalize the order 1791 | else{ 1792 | status.innerHTML = "loading nonce..."; 1793 | 1794 | // get nonce for finalizing 1795 | getNonce(function(nonce, err){ 1796 | if(err){ 1797 | return fail(status, "Failed finalize nonce request (code: " + err.status + "). " + err.responseText); 1798 | } 1799 | 1800 | // populate order finalize signature (payload populated in validateCSR()) 1801 | ORDER['finalize_protected_json'] = { 1802 | "url": ORDER['finalize_uri'], 1803 | "alg": ACCOUNT['alg'], 1804 | "nonce": nonce, 1805 | "kid": ACCOUNT['account_uri'], 1806 | } 1807 | ORDER['finalize_protected_b64'] = b64(JSON.stringify(ORDER['finalize_protected_json'])); 1808 | document.getElementById("finalize_sig_cmd").value = "" + 1809 | "PRIV_KEY=./account.key; " + 1810 | "echo -n \"" + ORDER['finalize_protected_b64'] + "." + ORDER['finalize_payload_b64'] + "\" | " + 1811 | "openssl dgst -sha256 -hex -sign $PRIV_KEY"; 1812 | document.getElementById("finalize_sig_cmd").setAttribute("readonly", ""); 1813 | document.getElementById("finalize_sig_cmd").removeAttribute("disabled"); 1814 | document.getElementById("finalize_sig").value = ""; 1815 | document.getElementById("finalize_sig").setAttribute("placeholder", RESULT_PLACEHOLDER); 1816 | document.getElementById("finalize_sig").removeAttribute("disabled"); 1817 | document.getElementById("validate_finalize_sig").removeAttribute("disabled"); 1818 | 1819 | // proceed finalize order 1820 | status.innerHTML = "Challenge complete! Proceed to finalize certificate order."; 1821 | }); 1822 | } 1823 | } 1824 | 1825 | // authorization failed, so show an error 1826 | else{ 1827 | return fail(status, "Domain challenge failed. Please start back at Step 1. " + JSON.stringify(auth_obj)); 1828 | } 1829 | } 1830 | // error loading authorization 1831 | else{ 1832 | return fail(status, "Loading challenge status failed. Please start back at Step 1. " + recheck_auth_xhr.responseText); 1833 | } 1834 | } 1835 | }; 1836 | recheck_auth_xhr.send(JSON.stringify({ 1837 | "protected": AUTHORIZATIONS[auth_url]['recheck_auth_protected_b64'], 1838 | "payload": AUTHORIZATIONS[auth_url]['recheck_auth_payload_b64'], 1839 | "signature": AUTHORIZATIONS[auth_url]['recheck_auth_sig'], 1840 | })); 1841 | 1842 | } 1843 | 1844 | /* 1845 | * Step 4f: Issue Certificate (POST /order['finalize']) 1846 | */ 1847 | function validateFinalize(e){ 1848 | e.preventDefault(); 1849 | 1850 | // clear previous status 1851 | var status = document.getElementById("validate_finalize_sig_status"); 1852 | status.style.display = "inline"; 1853 | status.className = ""; 1854 | status.innerHTML = "finalizing..."; 1855 | 1856 | // hide following steps 1857 | document.getElementById("step5").style.display = "none"; 1858 | document.getElementById("step5_pending").style.display = "inline"; 1859 | 1860 | // reset recheck_order signature 1861 | document.getElementById("recheck_order_sig_cmd").value = "waiting until order is finalized..."; 1862 | document.getElementById("recheck_order_sig_cmd").removeAttribute("readonly"); 1863 | document.getElementById("recheck_order_sig_cmd").setAttribute("disabled", ""); 1864 | document.getElementById("recheck_order_sig").value = ""; 1865 | document.getElementById("recheck_order_sig").setAttribute("placeholder", "waiting until order is finalized..."); 1866 | document.getElementById("recheck_order_sig").setAttribute("disabled", ""); 1867 | document.getElementById("validate_recheck_order_sig").setAttribute("disabled", ""); 1868 | document.getElementById("validate_recheck_order_sig_status").style.display = "none"; 1869 | document.getElementById("validate_recheck_order_sig_status").className = ""; 1870 | document.getElementById("validate_recheck_order_sig_status").innerHTML = ""; 1871 | 1872 | // reset get cert signature 1873 | document.getElementById("cert_sig_cmd").value = "waiting until certificate is generated..."; 1874 | document.getElementById("cert_sig_cmd").removeAttribute("readonly"); 1875 | document.getElementById("cert_sig_cmd").setAttribute("disabled", ""); 1876 | document.getElementById("cert_sig").value = ""; 1877 | document.getElementById("cert_sig").setAttribute("placeholder", "waiting until certificate is generated..."); 1878 | document.getElementById("cert_sig").setAttribute("disabled", ""); 1879 | document.getElementById("validate_cert_sig").setAttribute("disabled", ""); 1880 | document.getElementById("validate_cert_sig_status").style.display = "none"; 1881 | document.getElementById("validate_cert_sig_status").className = ""; 1882 | document.getElementById("validate_cert_sig_status").innerHTML = ""; 1883 | 1884 | // validate registration payload exists 1885 | if(ORDER['finalize_payload_b64'] === undefined){ 1886 | return fail(status, "Finalize payload not found. Please go back to Step 1."); 1887 | } 1888 | 1889 | // validate the signature 1890 | var finalize_sig = hex2b64(document.getElementById("finalize_sig").value); 1891 | if(finalize_sig === null){ 1892 | return fail(status, "You need to run the above commands and paste the output in the text boxes below each command."); 1893 | } 1894 | ORDER['finalize_sig'] = finalize_sig; 1895 | 1896 | // send update request to CA finalize_uri 1897 | var finalize_xhr = new XMLHttpRequest(); 1898 | finalize_xhr.open("POST", ORDER['finalize_uri']); 1899 | finalize_xhr.setRequestHeader("Content-Type", "application/jose+json"); 1900 | finalize_xhr.onreadystatechange = function(){ 1901 | if(finalize_xhr.readyState === 4){ 1902 | 1903 | // successful finalizing the order 1904 | if(finalize_xhr.status === 200){ 1905 | 1906 | // set finalize response 1907 | ORDER['finalize_response'] = JSON.parse(finalize_xhr.responseText); 1908 | 1909 | // get nonce for rechecking the order 1910 | getNonce(function(nonce, err){ 1911 | if(err){ 1912 | return fail(status, "Failed order checking nonce request (code: " + err.status + "). " + err.responseText); 1913 | } 1914 | 1915 | // populate recheck_order signature 1916 | ORDER['recheck_order_protected_json'] = { 1917 | "url": ORDER['order_uri'], 1918 | "alg": ACCOUNT['alg'], 1919 | "nonce": nonce, 1920 | "kid": ACCOUNT['account_uri'], 1921 | } 1922 | ORDER['recheck_order_protected_b64'] = b64(JSON.stringify(ORDER['recheck_order_protected_json'])); 1923 | document.getElementById("recheck_order_sig_cmd").value = "" + 1924 | "PRIV_KEY=./account.key; " + 1925 | "echo -n \"" + ORDER['recheck_order_protected_b64'] + "." + ORDER['recheck_order_payload_b64'] + "\" | " + 1926 | "openssl dgst -sha256 -hex -sign $PRIV_KEY"; 1927 | document.getElementById("recheck_order_sig_cmd").setAttribute("readonly", ""); 1928 | document.getElementById("recheck_order_sig_cmd").removeAttribute("disabled"); 1929 | document.getElementById("recheck_order_sig").value = ""; 1930 | document.getElementById("recheck_order_sig").setAttribute("placeholder", RESULT_PLACEHOLDER); 1931 | document.getElementById("recheck_order_sig").removeAttribute("disabled"); 1932 | document.getElementById("validate_recheck_order_sig").removeAttribute("disabled"); 1933 | 1934 | // complete step 4f 1935 | status.innerHTML = "Finalized! Proceed to next command below."; 1936 | }); 1937 | } 1938 | 1939 | // error registering 1940 | else{ 1941 | return fail(status, "Finalizing failed. Please start back at Step 1. " + finalize_xhr.responseText); 1942 | } 1943 | } 1944 | }; 1945 | finalize_xhr.send(JSON.stringify({ 1946 | "protected": ORDER['finalize_protected_b64'], 1947 | "payload": ORDER['finalize_payload_b64'], 1948 | "signature": ORDER['finalize_sig'], 1949 | })); 1950 | } 1951 | 1952 | /* 1953 | * Step 4g: Check Order Status (GET-as-POST /order['order_uri']) 1954 | */ 1955 | function recheckOrder(e){ 1956 | e.preventDefault(); 1957 | 1958 | // clear previous status 1959 | var status = document.getElementById("validate_recheck_order_sig_status"); 1960 | status.style.display = "inline"; 1961 | status.className = ""; 1962 | status.innerHTML = "checking status..."; 1963 | 1964 | // hide following steps 1965 | document.getElementById("step5").style.display = "none"; 1966 | document.getElementById("step5_pending").style.display = "inline"; 1967 | 1968 | // reset get cert signature 1969 | document.getElementById("cert_sig_cmd").value = "waiting until certificate is generated..."; 1970 | document.getElementById("cert_sig_cmd").removeAttribute("readonly"); 1971 | document.getElementById("cert_sig_cmd").setAttribute("disabled", ""); 1972 | document.getElementById("cert_sig").value = ""; 1973 | document.getElementById("cert_sig").setAttribute("placeholder", "waiting until certificate is generated..."); 1974 | document.getElementById("cert_sig").setAttribute("disabled", ""); 1975 | document.getElementById("validate_cert_sig").setAttribute("disabled", ""); 1976 | document.getElementById("validate_cert_sig_status").style.display = "none"; 1977 | document.getElementById("validate_cert_sig_status").className = ""; 1978 | document.getElementById("validate_cert_sig_status").innerHTML = ""; 1979 | 1980 | // validate registration payload exists 1981 | if(ORDER['recheck_order_payload_b64'] === undefined){ 1982 | return fail(status, "Order checking payload not found. Please go back to Step 1."); 1983 | } 1984 | 1985 | // validate the signature 1986 | var recheck_order_sig = hex2b64(document.getElementById("recheck_order_sig").value); 1987 | if(recheck_order_sig === null){ 1988 | return fail(status, "You need to run the above commands and paste the output in the text boxes below each command."); 1989 | } 1990 | ORDER['recheck_order_sig'] = recheck_order_sig; 1991 | 1992 | // send update request to CA finalize_uri 1993 | var recheck_order_xhr = new XMLHttpRequest(); 1994 | recheck_order_xhr.open("POST", ORDER['order_uri']); 1995 | recheck_order_xhr.setRequestHeader("Content-Type", "application/jose+json"); 1996 | recheck_order_xhr.onreadystatechange = function(){ 1997 | if(recheck_order_xhr.readyState === 4){ 1998 | 1999 | // successful finalizing the order 2000 | if(recheck_order_xhr.status === 200){ 2001 | 2002 | // set recheck_order response 2003 | var order = JSON.parse(recheck_order_xhr.responseText) 2004 | ORDER['recheck_order_response'] = order; 2005 | 2006 | // order still processing 2007 | if(order['status'] === "pending" || order['status'] === "processing" || order['status'] === "ready"){ 2008 | 2009 | // update the status before getting another nonce 2010 | status.innerHTML = "processing..."; 2011 | 2012 | // clear the existing signature 2013 | ORDER['recheck_order_sig'] = undefined; 2014 | 2015 | // get nonce for checking the order status, again 2016 | getNonce(function(nonce, err){ 2017 | if(err){ 2018 | return fail(status, "Failed order status nonce request (code: " + err.status + "). " + err.responseText); 2019 | } 2020 | 2021 | // populate recheck_order signature 2022 | ORDER['recheck_order_protected_json'] = { 2023 | "url": ORDER['order_uri'], 2024 | "alg": ACCOUNT['alg'], 2025 | "nonce": nonce, 2026 | "kid": ACCOUNT['account_uri'], 2027 | } 2028 | ORDER['recheck_order_protected_b64'] = b64(JSON.stringify(ORDER['recheck_order_protected_json'])); 2029 | document.getElementById("recheck_order_sig_cmd").value = "" + 2030 | "PRIV_KEY=./account.key; " + 2031 | "echo -n \"" + ORDER['recheck_order_protected_b64'] + "." + ORDER['recheck_order_payload_b64'] + "\" | " + 2032 | "openssl dgst -sha256 -hex -sign $PRIV_KEY"; 2033 | document.getElementById("recheck_order_sig_cmd").setAttribute("readonly", ""); 2034 | document.getElementById("recheck_order_sig_cmd").removeAttribute("disabled"); 2035 | document.getElementById("recheck_order_sig").value = ""; 2036 | document.getElementById("recheck_order_sig").setAttribute("placeholder", RESULT_PLACEHOLDER); 2037 | document.getElementById("recheck_order_sig").removeAttribute("disabled"); 2038 | document.getElementById("validate_recheck_order_sig").removeAttribute("disabled"); 2039 | 2040 | // update status 2041 | status.innerHTML = "Order still processing. Copy and run the command again to check again."; 2042 | }); 2043 | } 2044 | 2045 | // order is ready for finalizing 2046 | else if(order['status'] === "valid"){ 2047 | 2048 | // set the certificate uri 2049 | ORDER['cert_uri'] = order['certificate']; 2050 | 2051 | // update the status before getting another nonce 2052 | status.innerHTML = "loading nonce..."; 2053 | 2054 | // get nonce for getting the certificate 2055 | getNonce(function(nonce, err){ 2056 | if(err){ 2057 | return fail(status, "Failed certificate nonce request (code: " + err.status + "). " + err.responseText); 2058 | } 2059 | 2060 | // populate cert retrieval signature 2061 | ORDER['cert_protected_json'] = { 2062 | "url": ORDER['cert_uri'], 2063 | "alg": ACCOUNT['alg'], 2064 | "nonce": nonce, 2065 | "kid": ACCOUNT['account_uri'], 2066 | } 2067 | ORDER['cert_protected_b64'] = b64(JSON.stringify(ORDER['cert_protected_json'])); 2068 | document.getElementById("cert_sig_cmd").value = "" + 2069 | "PRIV_KEY=./account.key; " + 2070 | "echo -n \"" + ORDER['cert_protected_b64'] + "." + ORDER['cert_payload_b64'] + "\" | " + 2071 | "openssl dgst -sha256 -hex -sign $PRIV_KEY"; 2072 | document.getElementById("cert_sig_cmd").setAttribute("readonly", ""); 2073 | document.getElementById("cert_sig_cmd").removeAttribute("disabled"); 2074 | document.getElementById("cert_sig").value = ""; 2075 | document.getElementById("cert_sig").setAttribute("placeholder", RESULT_PLACEHOLDER); 2076 | document.getElementById("cert_sig").removeAttribute("disabled"); 2077 | document.getElementById("validate_cert_sig").removeAttribute("disabled"); 2078 | 2079 | // complete step 4g 2080 | status.innerHTML = "Certificate ready! Proceed to next command below."; 2081 | }); 2082 | } 2083 | 2084 | // order invalid 2085 | else{ 2086 | return fail(status, "Order processing failed. Please start back at Step 1. " + recheck_order_xhr.responseText); 2087 | } 2088 | } 2089 | 2090 | // error checking order 2091 | else{ 2092 | return fail(status, "Account registration failed. Please start back at Step 1. " + recheck_order_xhr.responseText); 2093 | } 2094 | } 2095 | }; 2096 | recheck_order_xhr.send(JSON.stringify({ 2097 | "protected": ORDER['recheck_order_protected_b64'], 2098 | "payload": ORDER['recheck_order_payload_b64'], 2099 | "signature": ORDER['recheck_order_sig'], 2100 | })); 2101 | } 2102 | 2103 | /* 2104 | * Step 4h: Get Certificate (GET-as-POST /order['cert_uri']) 2105 | */ 2106 | function getCertificate(e){ 2107 | e.preventDefault(); 2108 | 2109 | // clear previous status 2110 | var status = document.getElementById("validate_cert_sig_status"); 2111 | status.style.display = "inline"; 2112 | status.className = ""; 2113 | status.innerHTML = "retrieving certificate..."; 2114 | 2115 | // hide following steps 2116 | document.getElementById("step5").style.display = "none"; 2117 | document.getElementById("step5_pending").style.display = "inline"; 2118 | 2119 | // validate registration payload exists 2120 | if(ORDER['cert_payload_b64'] === undefined){ 2121 | return fail(status, "Certificate payload not found. Please go back to Step 1."); 2122 | } 2123 | 2124 | // validate the signature 2125 | var cert_sig = hex2b64(document.getElementById("cert_sig").value); 2126 | if(cert_sig === null){ 2127 | return fail(status, "You need to run the above commands and paste the output in the text boxes below each command."); 2128 | } 2129 | ORDER['cert_sig'] = cert_sig; 2130 | 2131 | // send update request to CA finalize_uri 2132 | var cert_xhr = new XMLHttpRequest(); 2133 | cert_xhr.open("POST", ORDER['cert_uri']); 2134 | cert_xhr.setRequestHeader("Content-Type", "application/jose+json"); 2135 | cert_xhr.onreadystatechange = function(){ 2136 | if(cert_xhr.readyState === 4){ 2137 | 2138 | // successful finalizing the order 2139 | if(cert_xhr.status === 200){ 2140 | 2141 | // format cert into PEM format 2142 | document.getElementById("crt").value = cert_xhr.responseText; 2143 | 2144 | // proceed step 5 2145 | document.getElementById("step5").style.display = "block"; 2146 | document.getElementById("step5_pending").style.display = "none"; 2147 | status.innerHTML = "Certificate retrieved! Proceed to next step."; 2148 | 2149 | // alert when navigating away 2150 | window.onbeforeunload = function(){ 2151 | return "Be sure to save your signed certificate! " + 2152 | "It will be lost if you navigate away from this " + 2153 | "page before saving it, and you might not be able " + 2154 | "to get another one issued!"; 2155 | }; 2156 | } 2157 | 2158 | // error geting certificate 2159 | else{ 2160 | return fail(status, "Certificate retrieval failed. Please start back at Step 1. " + cert_xhr.responseText); 2161 | } 2162 | } 2163 | }; 2164 | cert_xhr.send(JSON.stringify({ 2165 | "protected": ORDER['cert_protected_b64'], 2166 | "payload": ORDER['cert_payload_b64'], 2167 | "signature": ORDER['cert_sig'], 2168 | })); 2169 | } 2170 | 2171 | --------------------------------------------------------------------------------