Compare commits

...

60 Commits
master ... v2

Author SHA1 Message Date
AJ ONeal 4f9173eb4a retry on bad nonce 5 years ago
AJ ONeal 43ccd091b2 make polling faster, enable status updates 5 years ago
AJ ONeal 52ce530aff fix POST-as-POST 5 years ago
AJ ONeal 96b9cd1a74 fix POST-as-GET 5 years ago
AJ ONeal ed1170fc7c better error handling 5 years ago
AJ ONeal 68ce90afe1 better error handling 5 years ago
AJ ONeal c7a6cafae2 ACME spec compatibility notice 5 years ago
AJ ONeal bb16c69fb8 better logging, update kickoff 5 years ago
AJ ONeal 1998c3d119 log cleanup 5 years ago
AJ ONeal d9da4c0989 multiple domains 5 years ago
AJ ONeal dd9c54c45c multiple domains 5 years ago
AJ ONeal 0405a7db90 using new acme 5 years ago
AJ ONeal 49d5346615 refactor 5 years ago
AJ ONeal 330e0e7832 worked with a domain, yay 5 years ago
AJ ONeal 19cc513174 WIP almost working 5 years ago
AJ ONeal 7484ffcd11 WIP: moving to bluecrypt 5 years ago
AJ ONeal 98c8db8973 WIP: breaking into smaller pieces 5 years ago
AJ ONeal 8ce44bc414 rebrand Root 5 years ago
AJ ONeal ab67741604 rebrand Root 5 years ago
AJ ONeal b7dd224426 update to rootprojects.org 5 years ago
John Shaver 65974b57c1 Merge branch 'live-site' of ssh://git.coolaj86.com:22042/coolaj86/greenlock.html into live-site 5 years ago
John Shaver b1fc2bcc14 fix wildcard certificate verification 5 years ago
AJ ONeal 8d31bf7754 GA -> web-only 5 years ago
AJ ONeal 1c57a342d0 reposition 5 years ago
AJ ONeal f27b386ba4 update legal link (and link to 'us') 5 years ago
AJ ONeal 55a30888ff add privacy policy 5 years ago
AJ ONeal 95e807be73 test key support BEFORE creating keys 5 years ago
AJ ONeal 268f83b49e tested working in firefox 5 years ago
AJ ONeal 09ff0b3adc use the opts we built, duh 5 years ago
AJ ONeal 8a7183ed9c don't eager assign CSR 5 years ago
AJ ONeal a7462db2c8 test ecdsa support, spaces for whitespace 5 years ago
AJ ONeal 2cc5a41268 lint and fix and use domains.generateKeypair 5 years ago
AJ ONeal d63d8e1aed lint 5 years ago
John Shaver 6f188cefb8 Now uses RSA keys for firefox browser. 5 years ago
AJ ONeal bddf85dfe6 Merge branch 'live-site' of ssh://git.coolaj86.com:22042/coolaj86/greenlock.html into live-site 5 years ago
AJ ONeal c6052bcf73 update contact info 5 years ago
John Shaver 78515f165c Fix legal link and preloading files 5 years ago
John Shaver 68253cbe54 You can now tab select check boxes. 5 years ago
John Shaver 9143545389 Added download links for certs and changed some formatting. 5 years ago
John Shaver 3e865a2fb7 Added styling to key/cert page. 5 years ago
John Shaver 6ce81beaec Fixed link to hosted app. 5 years ago
John Shaver c6c06b06f0 First 2 steps down. 5 years ago
John Shaver a32287c3f8 Added a terrible temorary fix to the tables. 5 years ago
John Shaver 6a05569ab5 Some style chages on first/second step. 5 years ago
John Shaver c3c9696799 Progress bar is in place. 5 years ago
John Shaver b86074920a Added Javascript needed warning. 5 years ago
John Shaver 8610baf0e0 fixed blink on loading strait to step2 5 years ago
John Shaver 3cfbc9339b Tied in new homepage with the rest of the app. 5 years ago
John Shaver 62f3d28a71 Some styling updates and created a separate front page. 5 years ago
John Shaver 430b589038 Center, shrink logo. 5 years ago
John Shaver 6d3c3a9e61 No longer fails due to new challenge types. 5 years ago
AJ ONeal 61b2c76822 Merge branch 'master' into live-site 6 years ago
AJ ONeal d0ea6822ea Merge branch 'master' into live-site 6 years ago
AJ ONeal 4bfef46295 Merge branch 'master' into live-site 6 years ago
AJ ONeal 42a589f5c0 add google analytics 6 years ago
AJ ONeal 5d07cec7a3 Merge branch 'master' into live-site 6 years ago
AJ ONeal ceb109e652 fix typo 6 years ago
AJ ONeal ed2739cb20 add line break 6 years ago
AJ ONeal 2be09788ce Merge branch 'master' into live-site 6 years ago
AJ ONeal 3854e6b430 save contact 6 years ago
  1. 4
      .gitignore
  2. 2
      README.md
  3. 1
      app/favicon.ico
  4. BIN
      app/fonts/6xK3dSBYKcSV-LCoeQqfX1RYOo3qOK7l.woff2
  5. BIN
      app/fonts/6xKydSBYKcSV-LCoeQqfX1RYOo3ig4vwlxdu.woff2
  6. BIN
      app/fonts/HI_SiYsKILxRpg3hIP6sJ7fM7PqlPevW.woff2
  7. 0
      app/img/greenlock-mark-400x400.png
  8. 378
      app/index.html
  9. 2897
      app/js/bluecrypt-acme.js
  10. 504
      app/js/greenlock.js
  11. 263
      app/styles/main.css
  12. BIN
      fonts/6xK3dSBYKcSV-LCoeQqfX1RYOo3qOK7l.woff2
  13. BIN
      fonts/6xKydSBYKcSV-LCoeQqfX1RYOo3ig4vwlxdu.woff2
  14. BIN
      img/greenlock-146.png
  15. 292
      index.html
  16. 14
      install.sh
  17. 493
      js/app.js
  18. 682
      js/bacme.js
  19. 201
      legal.html
  20. 1
      legal/index.html
  21. 115
      styles/main.css

4
.gitignore

@ -1,2 +1,2 @@
js/pkijs.org
js/browser-csr
app/js/pkijs.org
app/js/browser-csr

2
README.md

@ -6,7 +6,7 @@ Taking greenlock™ (Let's Encrypt v2 / ACME client) where it's never been b
Official Site
=============
This app is available at <https://greenlock.ppl.family>.
This app is available at <https://greenlock.domains>.
We expect that our hosted version will meet all of yours needs.
If it doesn't, please open an issue to let us know why.

1
app/favicon.ico

@ -0,0 +1 @@
../img/favicon-32x32.png

BIN
app/fonts/6xK3dSBYKcSV-LCoeQqfX1RYOo3qOK7l.woff2

Binary file not shown.

BIN
app/fonts/6xKydSBYKcSV-LCoeQqfX1RYOo3ig4vwlxdu.woff2

Binary file not shown.

BIN
app/fonts/HI_SiYsKILxRpg3hIP6sJ7fM7PqlPevW.woff2

Binary file not shown.

0
img/greenlock-mark-400x400.png → app/img/greenlock-mark-400x400.png

After

Width:  |  Height:  |  Size: 6.1 KiB

378
app/index.html

@ -0,0 +1,378 @@
<html>
<head>
<title>Greenlock&trade;</title>
<meta property="og:image" content="https://greenlock.domains/img/greenlock-mark-400x400.png" />
<style>
@font-face {
font-family: 'Source Sans Pro';
font-style: normal;
font-display: block;
font-weight: 400;
src: local('Source Sans Pro Regular'), local('SourceSansPro-Regular'), url(./fonts/6xK3dSBYKcSV-LCoeQqfX1RYOo3qOK7l.woff2) format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
font-family: 'Source Sans Pro';
font-style: normal;
font-weight: 700;
font-display: block;
src: local('Source Sans Pro Bold'), local('SourceSansPro-Bold'), url(./fonts/6xKydSBYKcSV-LCoeQqfX1RYOo3ig4vwlxdu.woff2) format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
font-family: 'Source Code Pro';
font-style: normal;
font-weight: 400;
src: local('Source Code Pro'), local('SourceCodePro-Regular'), url(./fonts/HI_SiYsKILxRpg3hIP6sJ7fM7PqlPevW.woff2) format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
</style>
<link href="styles/main.css" rel="stylesheet">
<link rel="preload" href="./fonts/6xKydSBYKcSV-LCoeQqfX1RYOo3ig4vwlxdu.woff2" as="font" crossorigin="anonymous">
<link rel="preload" href="./fonts/6xK3dSBYKcSV-LCoeQqfX1RYOo3qOK7l.woff2" as="font" crossorigin="anonymous">
<link rel="preload" href="./fonts/HI_SiYsKILxRpg3hIP6sJ7fM7PqlPevW.woff2" as="font" crossorigin="anonymous">
<link rel="preload" href="./js/bluecrypt-acme.js" as="script">
<link rel="preload" href="./js/greenlock.js" as="script">
</head>
<body hidden>
<!-- let's define our SVG that we will reuse -->
<svg xmlns="http://www.w3.org/2000/svg" width="0" height="0" viewBox="0 0 24 24">
<defs>
<g id="svg-check">
<path fill="none" d="M0 0h24v24H0z"/>
<path d="M9 16.2L4.8 12l-1.4 1.4L9 19 21 7l-1.4-1.4L9 16.2z"/>
</g>
<g id="svg-checked">
<path d="M0 0h24v24H0z" fill="none"/>
<path d="M19 3H5c-1.11 0-2 .9-2 2v14c0 1.1.89 2 2 2h14c1.11 0 2-.9 2-2V5c0-1.1-.89-2-2-2zm-9 14l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z"/>
</g>
<g id="svg-unchecked">
<path d="M19 5v14H5V5h14m0-2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2z"/>
<path d="M0 0h24v24H0z" fill="none"/>
</g>
<g id="svg-download">
<path d="M19 9h-4V3H9v6H5l7 7 7-7zM5 18v2h14v-2H5z"/>
<path d="M0 0h24v24H0z" fill="none"/>
</g>
</defs>
</svg>
<div class="column-container wide">
<div class="header-row column-row">
<div id="js-progress-bar" class="progress-bar">
<div class="progress-bar-step">
<div class="circle">
<svg display="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<use xlink:href="#svg-check"></use>
</svg>
</div>
<div class="progress-step-label"><div>Details</div></div>
</div>
<div class="progress-bar-step">
<div class="circle">
<svg display="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<use xlink:href="#svg-check"></use>
</svg>
</div>
<div class="progress-step-label"><div>Verify domain</div></div>
</div>
<div class="progress-bar-step">
<div class="circle">
<svg display="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<use xlink:href="#svg-check"></use>
</svg>
</div>
<div class="progress-step-label"><div>Install certificates</div></div>
</div>
<!-- hide until the steps are all updated
<div class="progress-bar-step">
<div class="circle">
<svg display="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<use xlink:href="#svg-check"></use>
</svg>
</div>
<div class="progress-step-label"><div>Done</div></div>
</div>
-->
</div>
<div class="greenlock-logo-badge"><img src="./img/greenlock-mark-400x400.png"></div>
<div class="greenlock-name">Greenlock</div>
</div>
<div class="column-row">
<form class="js-acme-form js-acme-form-domains">
<h1><label>What's your domain?</label></h1>
<h4>Certificates are valid for 90 days. Renewal is free :)</h4>
<input class="js-acme-domains" type="text" placeholder="example.com,*.example.com" required>
<br>
<button type="submit">Next</button>
<br>
<br>
<br>
<label><input class="js-acme-api-type" name="acme-api-type" type="radio" value="v02" checked required>
Production</label>
<label><input class="js-acme-api-type" name="acme-api-type" type="radio" value="staging-v02" required>
Testing</label>
<br>
<input class="js-acme-directory-url" type="url" placeholder="ACME directory url">
</form>
<!-- Step 2 Create Account -->
<form class="js-acme-form js-acme-form-account">
<h1><label>What's your email?</label></h1>
<input class="js-acme-account-email acme-account-email" type="email" placeholder="john@doe.family" required>
<div class="checkbox-array">
<label>
<input class="js-acme-account-tos" type="checkbox" required>
<svg class="icon-checked-box" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<use xlink:href="#svg-checked"></use>
</svg>
<svg class="icon-unchecked-box" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<use xlink:href="#svg-unchecked"></use>
</svg>
Agree to &nbsp<a class="js-acme-tos-url" target="acme-tos">Let's Encrypt&trade; Terms of Service</a>?
</label>
<label>
<input class="js-greenlock-account-tos" type="checkbox" required>
<svg class="icon-checked-box" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<use xlink:href="#svg-checked"></use>
</svg>
<svg class="icon-unchecked-box" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<use xlink:href="#svg-unchecked"></use>
</svg>
Agree to &nbsp<a class="js-gl-tos" target="_blank" href="/legal/#terms">Greenlock&trade; Terms of Service</a>?
</label>
</div>
<!--
<a href="#">advanced (use existing account)</a>
<br>
<br>
-->
<button class="button-next js-account-next" type="submit">Next</button>
<div class="email-usage">
Why do we need your email?
We link your SSL certificates to the email you use so that you'll
be notified before the certificate expires and so you can manage
your certificates in the future.
</div>
</form>
<!-- Step 3 Set Challanges -->
<form class="js-acme-form js-acme-form-challenges">
<h1>Let's verify your domain</h1>
<div class="js-acme-challenges">
<div class="tabbed-selector">
<label>
<input class="js-acme-challenge-type" name="acme-challenge-type" type="radio" value="http-01" checked required>
File Upload
<div></div>
</label>
<label>
<input class="js-acme-challenge-type" name="acme-challenge-type" type="radio" value="dns-01" required>
DNS Record
<div></div>
</label>
</div>
<div>
<div class="js-acme-verification-http-01">
<h3>Upload each file</h3>
<div class="js-acme-http">
<div class="http-verification-info file-preview">
<div class="paper-fold"></div>
<div>
<div class="file-ver-info-header">FILENAME</div>
<pre class="js-acme-ver-file-location">...loading</pre>
</div>
<hr>
<div>
<div class="file-ver-info-header">CONTENTS</div>
<pre class="js-acme-ver-content">...loading</pre>
</div>
<div class="download-file">
<svg class="mdicon icon-download" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<use xlink:href="#svg-download"></use>
</svg>
<a class="js-download-verify-link" href="data:text/octet-stream;base64,SGVsbG8gV29ybGQuLi4=" download="hello.txt" target="_blank">
Download
</a>
</div>
<hr>
<div>
<div class="file-ver-info-header">LOCATION</div>
<pre class="js-acme-ver-uri">..loading</pre>
</div>
</div>
<br>
</div>
</div>
<div class="js-acme-verification-dns-01">
<h3>Set each DNS Record</h3>
<div class="js-acme-dns">
<div class="acme-ver-dns-label">TXT Host</div>
<div class="js-acme-ver-txt-host">loading...</div>
<div class="acme-ver-dns-label">TXT Value</div>
<div class="js-acme-ver-txt-value">loading...</div>
<br>
</div>
<p><strong>Warning</strong>:
You should wait at least 30 seconds after setting DNS records before continuing.</p>
<p><strong>Google DNS Users</strong>:
You may need to wait up to 5 minutes.</p>
</div>
</div>
<div class="js-acme-wildcard-challenges">
<div class="js-acme-verification-wildcard">
<h3>Set each DNS Record (for wildcards)</h3>
<div class="js-acme-wildcard">
<div class="acme-ver-dns-label">TXT Host</div>
<div class="js-acme-ver-txt-host">loading...</div>
<div class="acme-ver-dns-label">TXT Value</div>
<div class="js-acme-ver-txt-value">loading...</div>
<br>
</div>
<p><strong>Warning</strong>:
You should wait at least 30 seconds after setting DNS records before continuing.</p>
<p><strong>Google DNS</strong>:
You may need to wait up to 5 minutes.</p>
</div>
</div>
</div>
<button class="button-next" type="submit">Next</button>
</form>
<!-- Step 4 Process Challanges -->
<form class="js-acme-form js-acme-form-poll">
Verifying Domains... (give us 5 seconds or so...)
<div class="js-challenge-responses" hidden>
Checking
<span class="js-challenge-response-altname">&nbsp;</span>
using <span class="js-challenge-response-type">&nbsp;</span>
: <span class="js-challenge-response-status">&nbsp;</span>
</div>
<!--
<table class="js-acme-table-verifying">
<thead>
<tr>
<th>Hostname</th>
<th>Type</th>
<th>Pass</th>
</tr>
</thead>
<tbody>
<tr>
<td>example.com</td>
<td>http-01</td>
<td>-</td>
</tr>
</tbody>
</table>
<a href="#">advanced (use existing keypair for domain)</a>
<button type="submit">Next</button>
-->
</form>
<!-- Step 5 Get Certs -->
<form class="js-acme-form js-acme-form-download">
<div class="cert-download-container">
<h2><label>privkey.pem</label></h2>
<div class="acme-result-privkey file-preview">
<div class="paper-fold"></div>
<pre id="js-privkey">
</pre>
</div>
<div class="download-file">
<svg class="mdicon icon-download" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<use xlink:href="#svg-download"></use>
</svg>
<a id="js-download-privkey-link" href="data:text/octet-stream;base64,SGVsbG8gV29ybGQuLi4=" download="privkey.pem" target="_blank">
Download
</a>
</div>
<h2><label>fullchain.pem</label></h2>
<div class="acme-result-fullchain file-preview">
<div class="paper-fold"></div>
<pre id="js-fullchain">
</pre>
</div>
<div class="download-file">
<svg class="mdicon icon-download" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<use xlink:href="#svg-download"></use>
</svg>
<a id="js-download-fullchain-link" href="data:text/octet-stream;base64,SGVsbG8gV29ybGQuLi4=" download="fullchain.pem" target="_blank">
Download
</a>
</div>
<div>
<h3>node.js https server example</h3>
<pre><code> 'use strict';
var https = require('https');
var server = https.createServer({
key: require('fs').readFileSync('./privkey.pem')
, cert: require('fs').readFileSync('./fullchain.pem')
}, function (req, res) {
res.end("Hello, World!");
}).listen(443, function () {
console.log('Listening on', this.address());
})
</code></pre>
</div>
<!--
TODO
<label>cert.pem</label>
<textarea class="js-cert">-</textarea>
<label>chain.pem</label>
<textarea class="js-chain">-</textarea>
<button type="button">Download SSL Certificates</button>
<br>
<a href="#">Advanced (copy and paste)</a>
<br>
<button type="submit">Start Over</button>
-->
</form>
</div>
<div><small><center>
<div>
A <a href="https://rootprojects.org/" target="_blank">Root</a> Project
| <a href="https://git.coolaj86.com/coolaj86/greenlock.html" target="_blank">View Source</a> (git)
| <a href="https://rootprojects.org/legal/#terms" target="_blank">Terms of Service</a>
| <a href="https://rootprojects.org/legal/#privacy" target="_blank">Privacy Policy</a>
</div>
<!-- or
<pre><code>git clone https://git.coolaj86.com/coolaj86/greenlock.html.git</code></pre>
Or view the live site code (same as live-site branch):
<pre><code>wget https://greenlock.domains --mirror --convert-links --adjust-extension --page-requisites --no-parent</code></pre>
-->
</center></small></div>
<br>
<script src="./js/bluecrypt-acme.js"></script>
<script src="./js/greenlock.js"></script>
<!-- Global site tag (gtag.js) - Google Analytics -->
<script async src="https://www.googletagmanager.com/gtag/js?id=UA-118745161-2"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'UA-118745161-2');
</script>
</div>
</div>
</body>
</html>

2897
app/js/bluecrypt-acme.js

File diff suppressed because it is too large

504
app/js/greenlock.js

@ -0,0 +1,504 @@
(function () {
'use strict';
/*global URLSearchParams,Headers*/
var PromiseA = window.Promise;
var VERSION = '2';
// ACME recommends ECDSA P-256, but RSA 2048 is still required by some old servers (like what replicated.io uses )
// ECDSA P-384, P-521, and RSA 3072, 4096 are NOT recommend standards (and not properly supported)
var BROWSER_SUPPORTS_RSA = false;
var ECDSA_OPTS = { kty: 'EC', namedCurve: 'P-256' };
var RSA_OPTS = { kty: 'RSA', modulusLength: 2048 };
var Promise = window.Promise;
var Keypairs = window.Keypairs;
var ACME = window.ACME;
var CSR = window.CSR;
var $qs = function (s) { return window.document.querySelector(s); };
var $qsa = function (s) { return window.document.querySelectorAll(s); };
var acme;
var info = {};
var steps = {};
var i = 1;
var apiUrl = 'https://acme-{{env}}.api.letsencrypt.org/directory';
// fix previous browsers
var isCurrent = (localStorage.getItem('version') === VERSION);
if (!isCurrent) {
localStorage.clear();
localStorage.setItem('version', VERSION);
}
localStorage.setItem('version', VERSION);
function updateApiType() {
/*jshint validthis: true */
var input = this || Array.prototype.filter.call(
$qsa('.js-acme-api-type'), function ($el) { return $el.checked; }
)[0];
//#console.log('ACME api type radio:', input.value);
$qs('.js-acme-directory-url').value = apiUrl.replace(/{{env}}/g, input.value);
}
function hideForms() {
$qsa('.js-acme-form').forEach(function (el) {
el.hidden = true;
});
}
function updateProgress(currentStep) {
var progressSteps = $qs("#js-progress-bar").children;
var j;
for (j = 0; j < progressSteps.length; j += 1) {
if (j < currentStep) {
progressSteps[j].classList.add("js-progress-step-complete");
progressSteps[j].classList.remove("js-progress-step-started");
} else if (j === currentStep) {
progressSteps[j].classList.remove("js-progress-step-complete");
progressSteps[j].classList.add("js-progress-step-started");
} else {
progressSteps[j].classList.remove("js-progress-step-complete");
progressSteps[j].classList.remove("js-progress-step-started");
}
}
}
function newAlert(str) {
return new Promise(function () {
setTimeout(function () {
window.alert(str);
if (window.confirm("Start over?")) {
document.location.href = document.location.href.replace(/\/app.*/, '/');
}
}, 10);
});
}
function submitForm(ev) {
var j = i;
i += 1;
return PromiseA.resolve().then(function () {
return steps[j].submit(ev);
}).catch(function (err) {
var ourfault = true;
console.error(err);
if (/failed to fetch/i.test(err.message)) {
return newAlert("Network connection failure.");
}
if ('E_ACME_CHALLENGE' === err.code) {
if ('dns-01' === err.type) {
ourfault = false;
return newAlert("It looks like the DNS record you set for "
+ err.altname + " was incorrect or did not propagate. "
+ "The error message was '" + err.message + "'");
} else if ('http-01' === err.type) {
ourfault = false;
return newAlert("It looks like the file you uploaded for "
+ err.altname + " was incorrect or could not be downloaded. "
+ "The error message was '" + err.message + "'");
}
}
if (ourfault) {
err.auth = undefined;
window.alert("Something went wrong. It's probably our fault, not yours."
+ " Please email aj@rootprojects.org to let him know. The error message is: \n"
+ JSON.stringify(err, null, 2));
return new Promise(function () {});
}
});
}
function testKeypairSupport() {
return Keypairs.generate(RSA_OPTS).then(function () {
console.info("[crypto] RSA is supported");
BROWSER_SUPPORTS_RSA = true;
}).catch(function () {
console.warn("[crypto] RSA is NOT supported");
return Keypairs.generate(ECDSA_OPTS).then(function () {
console.info('[crypto] ECDSA is supported');
}).catch(function (e) {
console.warn("[crypto] EC is NOT supported");
throw e;
});
});
}
function getServerKeypair() {
var sortedAltnames = info.identifiers.map(function (ident) { return ident.value; }).sort().join(',');
var serverJwk = JSON.parse(localStorage.getItem('server:' + sortedAltnames) || 'null');
if (serverJwk) {
return PromiseA.resolve(serverJwk);
}
var keypairOpts;
// TODO allow for user preference
if (BROWSER_SUPPORTS_RSA) {
keypairOpts = RSA_OPTS;
} else {
keypairOpts = ECDSA_OPTS;
}
return Keypairs.generate(RSA_OPTS).catch(function (err) {
console.error("[Error] Keypairs.generate(" + JSON.stringify(RSA_OPTS) + "):");
throw err;
}).then(function (pair) {
localStorage.setItem('server:'+sortedAltnames, JSON.stringify(pair.private));
return pair.private;
});
}
function getAccountKeypair(email) {
var json = localStorage.getItem('account:'+email);
if (json) {
return Promise.resolve(JSON.parse(json));
}
return Keypairs.generate(ECDSA_OPTS).catch(function (err) {
console.warn("[Error] Keypairs.generate(" + JSON.stringify(ECDSA_OPTS) + "):\n", err);
return Keypairs.generate(RSA_OPTS).catch(function (err) {
console.error("[Error] Keypairs.generate(" + JSON.stringify(RSA_OPTS) + "):");
throw err;
});
}).then(function (pair) {
localStorage.setItem('account:'+email, JSON.stringify(pair.private));
return pair.private;
});
}
function updateChallengeType() {
/*jshint validthis: true*/
var input = this || Array.prototype.filter.call(
$qsa('.js-acme-challenge-type'), function ($el) { return $el.checked; }
)[0];
$qs('.js-acme-verification-wildcard').hidden = true;
$qs('.js-acme-verification-http-01').hidden = true;
$qs('.js-acme-verification-dns-01').hidden = true;
if (info.challenges.wildcard) {
$qs('.js-acme-verification-wildcard').hidden = false;
}
if (info.challenges[input.value]) {
$qs('.js-acme-verification-' + input.value).hidden = false;
}
}
function saveContact(email, domains) {
// to be used for good, not evil
return window.fetch('https://api.rootprojects.org/api/rootprojects.org/public/community', {
method: 'POST'
, cors: true
, headers: new Headers({ 'Content-Type': 'application/json' })
, body: JSON.stringify({
address: email
, project: 'greenlock-domains@rootprojects.org'
, timezone: new Intl.DateTimeFormat().resolvedOptions().timeZone
, domain: domains.join(',')
})
}).catch(function (err) {
console.error(err);
});
}
steps[1] = function () {
console.info("\n1. Show domains form");
updateProgress(0);
hideForms();
$qs('.js-acme-form-domains').hidden = false;
};
steps[1].submit = function () {
console.info("[submit] 1. Process domains, create ACME client", info.domains);
info.domains = $qs('.js-acme-domains').value
.replace(/https?:\/\//g, ' ').replace(/[,+]/g, ' ').trim().split(/\s+/g);
console.info("[domains]", info.domains.join(' '));
info.identifiers = info.domains.map(function (hostname) {
return { type: 'dns', value: hostname.toLowerCase().trim() };
});
info.identifiers.sort(function (a, b) {
if (a === b) { return 0; }
if (a < b) { return 1; }
if (a > b) { return -1; }
});
var acmeDirectoryUrl = $qs('.js-acme-directory-url').value;
acme = ACME.create({ Keypairs: Keypairs, CSR: CSR });
return acme.init(acmeDirectoryUrl).then(function (directory) {
$qs('.js-acme-tos-url').href = directory.meta.termsOfService;
return steps[i]();
});
};
steps[2] = function () {
console.info("\n2. Show account (email, ToS) form");
updateProgress(0);
hideForms();
$qs('.js-acme-form-account').hidden = false;
};
steps[2].submit = function () {
console.info("[submit] 2. Create ACME account (get Key ID)");
var email = $qs('.js-acme-account-email').value.toLowerCase().trim();
info.email = email;
info.contact = [ 'mailto:' + email ];
info.agree = $qs('.js-acme-account-tos').checked;
//info.greenlockAgree = $qs('.js-gl-tos').checked;
// TODO ping with version and account creation
setTimeout(saveContact, 100, email, info.domains);
$qs('.js-account-next').disabled = true;
return info.cryptoCheck.then(function () {
return getAccountKeypair(email).then(function (jwk) {
// TODO save account id rather than always retrieving it?
console.info("[accounts] upsert for", email);
return acme.accounts.create({
email: email
, agreeToTerms: info.agree && true
, accountKeypair: { privateKeyJwk: jwk }
}).then(function (account) {
console.info("[accounts] result:", account);
info.account = account;
info.privateJwk = jwk;
info.email = email;
}).catch(function (err) {
console.error("[accounts] failed to upsert account:");
console.error(err);
return newAlert(err.message || JSON.stringify(err, null, 2));
});
});
}).then(function () {
var jwk = info.privateJwk;
var account = info.account;
console.info("[orders] requesting");
return acme.orders.request({
account: account
, accountKeypair: { privateKeyJwk: jwk }
, domains: info.domains
}).then(function (order) {
info.order = order;
console.info("[orders] created ", order);
var claims = order.claims;
var obj = { 'dns-01': [], 'http-01': [], 'wildcard': [] };
info.challenges = obj;
var $httpList = $qs('.js-acme-http');
var $dnsList = $qs('.js-acme-dns');
var $wildList = $qs('.js-acme-wildcard');
var httpTpl = $httpList.innerHTML;
var dnsTpl = $dnsList.innerHTML;
var wildTpl = $wildList.innerHTML;
$httpList.innerHTML = '';
$dnsList.innerHTML = '';
$wildList.innerHTML = '';
claims.forEach(function (claim) {
//#console.log("claims[i]", claim);
var hostname = claim.identifier.value;
claim.challenges.forEach(function (c) {
var auth = c;
var data = {
type: c.type
, hostname: hostname
, url: c.url
, token: c.token
, httpPath: auth.challengeUrl
, httpAuth: auth.keyAuthorization
, dnsType: 'TXT'
, dnsHost: auth.dnsHost
, dnsAnswer: auth.keyAuthorizationDigest
};
//#console.log("claims[i].challenge", data);
var $tpl = document.createElement("div");
if (claim.wildcard) {
obj.wildcard.push(data);
$tpl.innerHTML = wildTpl;
$tpl.querySelector(".js-acme-ver-txt-host").innerHTML = data.dnsHost;
$tpl.querySelector(".js-acme-ver-txt-value").innerHTML = data.dnsAnswer;
$wildList.appendChild($tpl);
} else if(obj[data.type]) {
obj[data.type].push(data);
if ('dns-01' === data.type) {
$tpl.innerHTML = dnsTpl;
$tpl.querySelector(".js-acme-ver-txt-host").innerHTML = data.dnsHost;
$tpl.querySelector(".js-acme-ver-txt-value").innerHTML = data.dnsAnswer;
$dnsList.appendChild($tpl);
} else if ('http-01' === data.type) {
$tpl.innerHTML = httpTpl;
$tpl.querySelector(".js-acme-ver-file-location").innerHTML = data.httpPath.split("/").slice(-1);
$tpl.querySelector(".js-acme-ver-content").innerHTML = data.httpAuth;
$tpl.querySelector(".js-acme-ver-uri").innerHTML = data.httpPath;
$tpl.querySelector(".js-download-verify-link").href =
"data:text/octet-stream;base64," + window.btoa(data.httpAuth);
$tpl.querySelector(".js-download-verify-link").download = data.httpPath.split("/").slice(-1);
$httpList.appendChild($tpl);
}
}
});
});
// hide wildcard if no wildcard
// hide http-01 and dns-01 if only wildcard
if (!obj.wildcard.length) {
$qs('.js-acme-wildcard-challenges').hidden = true;
}
if (!obj['http-01'].length) {
$qs('.js-acme-challenges').hidden = true;
}
console.info("[housekeeping] challenges", info.challenges);
updateChallengeType();
return steps[i]();
}).catch(function (err) {
if (err.detail || err.urn) {
console.error("(Probably) User Error:");
console.error(err);
return newAlert("There was an error, probably with your email or domain:\n" + err.message);
}
throw err;
});
}).catch(function (err) {
console.error('Step \'\' Error:');
console.error(err, err.stack);
return newAlert("An error happened (but it's not your fault)."
+ " Email aj@rootprojects.org to let him know that 'order and get challenges' failed.");
});
};
steps[3] = function () {
console.info("\n3. Present challenge options");
updateProgress(1);
hideForms();
$qs('.js-acme-form-challenges').hidden = false;
};
steps[3].submit = function () {
console.info("[submit] 3. Fulfill challenges, fetch certificate");
var challengePriority = [ 'dns-01' ];
if ('http-01' === $qs('.js-acme-challenge-type:checked').value) {
challengePriority.unshift('http-01');
}
console.info("[challenge] selected ", challengePriority[0]);
// for now just show the next page immediately (its a spinner)
steps[i]();
return getAccountKeypair(info.email).then(function (jwk) {
// TODO put a test challenge in the list
// info.order.claims.push(...)
// TODO warn about wait-time if DNS
return getServerKeypair().then(function (serverJwk) {
return acme.orders.complete({
account: info.account
, accountKeypair: { privateKeyJwk: jwk }
, order: info.order
, domains: info.domains
, domainKeypair: { privateKeyJwk: serverJwk }
, challengePriority: challengePriority
, challenges: false
, onChallengeStatus: function (details) {
$qs('.js-challenge-responses').hidden = false;
$qs('.js-challenge-response-type').innerText = details.type;
$qs('.js-challenge-response-status').innerText = details.status;
$qs('.js-challenge-response-altname').innerText = details.altname;
}
}).then(function (certs) {
return Keypairs.export({ jwk: serverJwk }).then(function (keyPem) {
console.info('WINNING!');
console.info(certs);
$qs('#js-fullchain').innerHTML = [
certs.cert.trim() + "\n"
, certs.chain + "\n"
].join("\n");
$qs("#js-download-fullchain-link").href =
"data:text/octet-stream;base64," + window.btoa(certs);
$qs('#js-privkey').innerHTML = keyPem;
$qs("#js-download-privkey-link").href =
"data:text/octet-stream;base64," + window.btoa(keyPem);
return submitForm();
});
});
});
});
};
// spinner
steps[4] = function () {
console.info('\n4. Show loading spinner');
updateProgress(1);
hideForms();
$qs('.js-acme-form-poll').hidden = false;
};
steps[4].submit = function () {
console.info('[submit] 4. Order complete');
return steps[i]();
};
steps[5] = function () {
console.info('\n5. Present certificates (yay!)');
updateProgress(2);
hideForms();
$qs('.js-acme-form-download').hidden = false;
};
function init() {
$qsa('.js-acme-api-type').forEach(function ($el) {
$el.addEventListener('change', updateApiType);
});
updateApiType();
$qsa('.js-acme-form').forEach(function ($el) {
$el.addEventListener('submit', function (ev) {
ev.preventDefault();
return submitForm(ev);
});
});
$qsa('.js-acme-challenge-type').forEach(function ($el) {
$el.addEventListener('change', updateChallengeType);
});
var params = new URLSearchParams(window.location.search);
var apiType = params.get('acme-api-type') || "staging-v02";
if (params.has('acme-domains')) {
$qs('.js-acme-domains').value = params.get('acme-domains');
$qsa('.js-acme-api-type').forEach(function(ele) {
if(ele.value === apiType) {
ele.checked = true;
}
});
updateApiType();
steps[2]();
return submitForm();
} else {
steps[1]();
}
}
init();
$qs('body').hidden = false;
// in the background
info.cryptoCheck = testKeypairSupport().then(function () {
console.info("[crypto] self-check: passed");
}).catch(function (err) {
console.error('[crypto] could not use either RSA nor EC.');
console.error(err);
window.alert("Generating secure certificates requires a browser with cryptography support."
+ "Please consider a recent version of Chrome, Firefox, or Safari.");
throw err;
});
}());

263
app/styles/main.css

@ -0,0 +1,263 @@
body {
font-size: 18px;
font-family: Source Sans Pro, sans-serif;
margin: 0;
line-height: 1.33;
color: #1a1a1a;
}
h1 {
text-align: center;
font-size: 1.77777778em;
}
a {
color: #1a1a1a;
}
input[type=email], input[type=text] {
font-size: 1em;
padding: 0.444444444em 0.888889em;
width: 100%;
border: solid 1px #d9d9d9;
border-radius: 2px;
}
pre {
margin: 0;
font-family: Source Code Pro, monospace;
}
.column-row {
width: 22.222222em;
}
.column-container {
display: flex;
flex-direction: column;
align-items: center;
}
.progress-bar {
height: 0;
border: solid 1px #5bc17f;
background-color: #5bc17f;
display: flex;
justify-content: space-between;
align-items: center;
width: 22em;
margin: 1.388888889em auto;
}
.greenlock-logo-badge > img {
width: 100%;
}
.greenlock-logo-badge {
display: inline-block;
border: solid 1px #d9d9d9;
border-radius: 500px;
width: 5.333333333em;
height: 5.333333333em;
margin-top: 4.277777778em;
}
.header-row {
text-align: center;
}
.progress-bar-step {
position: relative;
margin: -0.722222222em -0.166666667em;
display: inline-block;
background-color: white;
/* border-radius: 100%; */
padding: 0 0.111111em;
}
.progress-bar-step > .circle {
content: "";
display: inline-block;
border: solid 0.111111111em #5bc17f;
width: 0.888888889em;
height: 0.888888889em;
border-radius: 100%;
background: white;
}
.progress-step-label {
text-align: center;
position: absolute;
left: 50%;
=: block font-size: ;
top: 139%;
font-size: 0.722222222em;
white-space: nowrap;
}
.progress-step-label > div {
position: relative;
right: 50%;
}
.greenlock-name {
color: #808080;
}
.file-preview {
background: #f7f7f7;
position: relative;
font-size: 0.833333333em;
padding: 1.6em 2.9333em 1.6em 1.6em;
}
.js-progress-step-complete > .circle, .js-progress-step-started > .circle {
background-color: #5bc17f;
}
.progress-bar-step.js-progress-step-complete svg {
fill: white;
/* stroke: none; */
display: initial;
}
.checkbox-array {
display: flex;
flex-direction: column;
padding: 1em 0;
}
.checkbox-array input[type=checkbox] {
opacity: 0;
position: absolute;
}
.checkbox-array input[type=checkbox] ~ .icon-checked-box {
display: none;
}
.checkbox-array input[type=checkbox] ~ .icon-unchecked-box {
display: initial;
}
.checkbox-array input[type=checkbox]:checked ~ .icon-checked-box {
display: initial;
}
.checkbox-array input[type=checkbox]:checked ~ .icon-unchecked-box {
display: none;
}
.checkbox-array .icon-checked-box, .checkbox-array .icon-unchecked-box {
width: 1.333333333em;
fill: #5bc17f;
margin-right: 0.666666667em;
}
.checkbox-array label {
display: flex;
height: 1.333333333em;
font-size: 0.833333333em;
margin: 0.4em 0;
}
.checkbox-array input[type=checkbox]:focus ~ .icon-checked-box, .checkbox-array input[type=checkbox]:focus ~ .icon-unchecked-box {
background: #5bc17f52;
}
.email-usage {
color: #666666;
font-size: 0.833333333em;
margin: 2em 0;
}
.button-next {
width: 100%;
background-color: #5bc17f;
border: none;
font-size: 1em;
color: white;
padding: 0.44444em;
margin: 1em 0;
}
.tabbed-selector label {
width: 50%;
padding: 0.5em 0;
}
.tabbed-selector {
display: flex;
font-weight: bold;
text-align: center;
}
.tabbed-selector input[type=radio] {
display: none;
}
.download-file svg {
fill: #5bc17f;
width: 1.333333333em;
}
.download-file a {
color: #5bc17f;
}
.mdicon {
position: relative;
top: 0.4em;
}
.http-verification-info {
padding-right: 6.933333333em;
}
.paper-fold {
position: absolute;
width: 2em;
height: 2em;
border-left: solid #d9d9d9 1px;
border-bottom: solid #d9d9d9 1px;
right: 0;
top: 0;
background: linear-gradient(45deg, #f7f7f7 0%,#f7f7f7 50%,#ffffff 50%,#ffffff 100%);
}
.file-ver-info-header {
color: #808080;
}
.http-verification-info hr {
border: none;
border-bottom: solid 1px #d9d9d9;
}
.acme-ver-uri {
word-break: break-all;
margin: auto;
}
.acme-ver-dns-label {
margin: 1.777777778em 0 0.444444444em 0;
border-bottom: solid 1px #d9d9d9;
font-weight: bold;
padding-bottom: 0.166666667em;
}
.tabbed-selector input[type="radio"]:checked ~ div {
border: solid 1px #5bc17f;
background-color: #5bc17f;
}
.file-preview pre {
white-space: pre-line;
word-break: break-all;
}
.cert-download-container {
margin: 0 -31%;
}

BIN
fonts/6xK3dSBYKcSV-LCoeQqfX1RYOo3qOK7l.woff2

Binary file not shown.

BIN
fonts/6xKydSBYKcSV-LCoeQqfX1RYOo3ig4vwlxdu.woff2

Binary file not shown.

BIN
img/greenlock-146.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

After

Width:  |  Height:  |  Size: 3.8 KiB

292
index.html

@ -1,227 +1,99 @@
<html>
<head>
<title>Greenlock&trade;</title>
<meta property="og:image" content="https://greenlock.ppl.family/img/greenlock-mark-400x400.png" />
<meta property="og:image" content="https://greenlock.domains/img/greenlock-mark-400x400.png" />
<link href="styles/main.css" rel="stylesheet">
<style>
@font-face {
font-family: 'Source Sans Pro';
font-style: normal;
font-display: block;
font-weight: 400;
src: local('Source Sans Pro Regular'), local('SourceSansPro-Regular'), url(./fonts/6xK3dSBYKcSV-LCoeQqfX1RYOo3qOK7l.woff2) format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
font-family: 'Source Sans Pro';
font-style: normal;
font-weight: 700;
font-display: block;
src: local('Source Sans Pro Bold'), local('SourceSansPro-Bold'), url(./fonts/6xKydSBYKcSV-LCoeQqfX1RYOo3ig4vwlxdu.woff2) format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
</style>
<link rel="preload" href="./app/fonts/6xKydSBYKcSV-LCoeQqfX1RYOo3ig4vwlxdu.woff2" as="font" crossorigin="anonymous">
<link rel="preload" href="./app/fonts/6xK3dSBYKcSV-LCoeQqfX1RYOo3qOK7l.woff2" as="font" crossorigin="anonymous">
<link rel="prefetch" href="./app/fonts/HI_SiYsKILxRpg3hIP6sJ7fM7PqlPevW.woff2" as="font" crossorigin="anonymous">
<link rel="prefetch" href="./app/js/bluecrypt-acme.js" as="script">
<link rel="prefetch" href="./app/js/greenlock.js" as="script">
<link rel="prefetch" href="./js/app.js" as="script">
</head>
<body hidden>
<img width="410px" src="img/greenlock-820x150.png">
<div>
<br>
<h3>Greenlock&trade; - Instant, Free SSL Certificates via Let's Encrypt v2</h3>
<br>
<br>
<br>
</div>
<!-- Step 1 Choose Domain(s) -->
<form class="js-acme-form js-acme-form-domains">
<h1><label>What's your domain?</label></h1>
<h4>Certificates are valid for 90 days. Renewal is free :)</h4>
<input class="js-acme-domains" type="text" placeholder="example.com,*.example.com" required>
<br>
<button type="submit">Next</button>
<br>
<br>
<br>
<label><input class="js-acme-api-type" name="acme-api-type" type="radio" value="v02" checked required>
Production</label>
<label><input class="js-acme-api-type" name="acme-api-type" type="radio" value="staging-v02" required>
Testing</label>
<br>
<input class="js-acme-directory-url" type="url" placeholder="ACME directory url">
</form>
<!-- Step 2 Create Account -->
<form class="js-acme-form js-acme-form-account">
<h1><label>What's your email?</label></h1>
<input class="js-acme-account-email" type="email" placeholder="john@doe.family" required>
<br>
<br>
<label><input class="js-acme-account-tos" type="checkbox" required>
Agree to <a class="js-acme-tos-url" target="acme-tos">Let's Encrypt&trade; Terms of Service</a>?</label>
<br>
<br>
<label><input class="js-greenlock-account-tos" type="checkbox" required>
Agree to <a class="js-gl-tos" target="_blank" href="./legal.html">Greenlock&trade; Terms of Service</a>?</label>
<br>
<br>
<!--
<a href="#">advanced (use existing account)</a>
<br>
<br>
-->
<button type="submit">Next</button>
</form>
<!-- Step 3 Set Challanges -->
<form class="js-acme-form js-acme-form-challenges">
<h1>How will you validate your domain?</h1>
<br>
<label><input class="js-acme-challenge-type" name="acme-challenge-type" type="radio" value="http-01" checked required>
File Upload to HTTP Web Server</label>
<br>
<label><input class="js-acme-challenge-type" name="acme-challenge-type" type="radio" value="dns-01" required>
TXT Records on DNS Name Server</label>
<br>
<div class="js-acme-challenges">
<h2>Verify Domains &amp; Sub-Domains</h2>
<table class="js-acme-table-http-01">
<thead>
<tr>
<th>Hostname</th>
<th>File Location</th>
<th>File Contents</th>
</tr>
</thead>
<tbody>
<tr>
<td>example.com</td>
<td>.well-known/acme-challenge/xxx</td>
<td>sec.ret</td>
</tr>
</tbody>
</table>
<table class="js-acme-table-dns-01">
<thead>
<tr>
<th>Hostname</th>
<th>TXT Host</th>
<th>TXT Value</th>
</tr>
</thead>
<tbody>
<tr>
<td>example.com</td>
<td>_acme-challenge.example.com</td>
<td>4A54</td>
</tr>
</tbody>
</table>
<body class="js-app-ready">
<script>
document.querySelector('body').classList.remove("js-app-ready");
</script>
<div class="column-container wide">
<div class="column-row">
<img src="img/greenlock-146.png">
</div>
<div class="js-acme-wildcard">
<h2>Verify Wildcard Domains</h2>
<table class="js-acme-table-wildcard">
<thead>
<tr>
<th>Hostname</th>
<th>TXT Host</th>
<th>TXT Value</th>
</tr>
</thead>
<tbody>
<tr>
<td>example.com</td>
<td>_acme-challenge.example.com</td>
<td>4A54</td>
</tr>
</tbody>
</table>
</div>
<button type="submit">Next</button>
</form>
<!-- Step 4 Process Challanges -->
<form class="js-acme-form js-acme-form-poll">
Verifying Domains... (give us 5 seconds or so...)
<!--
<table class="js-acme-table-verifying">
<thead>
<tr>
<th>Hostname</th>
<th>Type</th>
<th>Pass</th>
</tr>
</thead>
<tbody>
<tr>
<td>example.com</td>
<td>http-01</td>
<td>-</td>
</tr>
</tbody>
</table>
<a href="#">advanced (use existing keypair for domain)</a>
<button type="submit">Next</button>
-->
</form>
<!-- Step 5 Get Certs -->
<form class="js-acme-form js-acme-form-download">
<div>
<h2><label>privkey.pem</label></h2>
<textarea cols="80" rows="10" class="js-privkey">-</textarea>
<div class="column-row">
<h1>Get the green lock for your website</h1>
</div>
<div>
<h2><label>fullchain.pem</label></h2>
<textarea cols="80" rows="60" class="js-fullchain">-</textarea>
<div class="column-row">
<div class="js-javascript-warning">
Greenlock will process the CSR in the browser and request the certificates directly from letsencrypt.org.
Please enable Javascript before continuing.
</div>
<form id="js-acme-form" action="./app/" method="GET">
<div class="domain-psuedo-input">
<span class="secure-green">Secure</span> | <span class="secure-green">https:</span>//<input aria-label="domains to secure" id="acme-domains" type="text" name="acme-domains" placeholder="Your domain name" required>
</div>
<button type="submit">Go</button>
<div class="domain-subtext">Domain, subdomain, or wildcard domain</div>
<div class="acme-advanced-fields">