Compare commits

..

59 Commits

Author SHA1 Message Date
John Shaver b5f0c30652 fix fullChain download link 2019-06-29 22:41:14 -07:00
AJ ONeal 210a7877a0 remove cruft 2019-06-11 22:05:01 -06:00
lastlink 8aadf6db41 windows install 2019-06-08 00:36:57 -04:00
AJ ONeal 8a873eeb1d add pointer to buttons 2019-06-01 00:24:02 -06:00
AJ ONeal 059b63e5d0 add pointer for buttons 2019-06-01 00:11:20 -06:00
AJ ONeal 513fb992c9 make Prettier™ 2019-05-31 21:52:12 -06:00
AJ ONeal 137990002a retry on bad nonce 2019-05-31 20:03:08 -06:00
AJ ONeal 21d01a326c make polling faster, enable status updates 2019-05-31 20:03:08 -06:00
AJ ONeal 67294ae3eb fix POST-as-POST 2019-05-31 20:03:08 -06:00
AJ ONeal 6eb325186e fix POST-as-GET 2019-05-31 20:03:08 -06:00
AJ ONeal 5d4785ee19 better error handling 2019-05-31 20:03:08 -06:00
AJ ONeal 0b50f7976f better error handling 2019-05-31 20:03:08 -06:00
AJ ONeal 26a61da3a1 ACME spec compatibility notice 2019-05-31 20:03:08 -06:00
AJ ONeal ad288b1c95 better logging, update kickoff 2019-05-31 20:03:08 -06:00
AJ ONeal d5e6b4331f log cleanup 2019-05-31 20:03:08 -06:00
AJ ONeal d4bfc608a3 multiple domains 2019-05-31 20:03:08 -06:00
AJ ONeal 5a229ef27f multiple domains 2019-05-31 20:03:08 -06:00
AJ ONeal 506280f764 using new acme 2019-05-31 20:03:08 -06:00
AJ ONeal 352e334b5d refactor 2019-05-31 20:03:08 -06:00
AJ ONeal f56dafad5d worked with a domain, yay 2019-05-31 20:03:08 -06:00
AJ ONeal 2ca4c984f8 WIP almost working 2019-05-31 20:03:08 -06:00
AJ ONeal 11020cbf27 WIP: moving to bluecrypt 2019-05-31 20:01:58 -06:00
AJ ONeal da5b9f59c3 WIP: breaking into smaller pieces 2019-05-31 20:01:58 -06:00
AJ ONeal 82aac8a233 rebrand Root 2019-05-31 20:01:58 -06:00
AJ ONeal aa0444646b rebrand Root 2019-05-31 20:01:58 -06:00
AJ ONeal 71de7e7b95 update to rootprojects.org 2019-05-31 20:01:58 -06:00
John Shaver fe849f426d fix wildcard certificate verification 2019-05-31 20:01:58 -06:00
AJ ONeal 51d10a57df GA -> web-only 2019-05-31 20:01:58 -06:00
AJ ONeal e236640098 reposition 2019-05-31 20:01:58 -06:00
AJ ONeal 8b6fddad75 update legal link (and link to 'us') 2019-05-31 20:01:58 -06:00
AJ ONeal a4c2da82c5 add privacy policy 2019-05-31 20:01:58 -06:00
AJ ONeal 8a0ccfa55f test key support BEFORE creating keys 2019-05-31 20:01:58 -06:00
AJ ONeal df964d76c6 tested working in firefox 2019-05-31 20:01:58 -06:00
AJ ONeal 136c9c06fe use the opts we built, duh 2019-05-31 20:01:58 -06:00
AJ ONeal 2fb775a822 don't eager assign CSR 2019-05-31 20:01:58 -06:00
AJ ONeal c66f92d506 test ecdsa support, spaces for whitespace 2019-05-31 20:01:58 -06:00
AJ ONeal cda26c98b5 lint and fix and use domains.generateKeypair 2019-05-31 20:01:58 -06:00
AJ ONeal 8a0e582962 lint 2019-05-31 20:01:58 -06:00
John Shaver b1328d938d Now uses RSA keys for firefox browser. 2019-05-31 20:01:58 -06:00
AJ ONeal 613f1ba75e update contact info 2019-05-31 20:01:58 -06:00
John Shaver 102829c852 Fix legal link and preloading files 2019-05-31 20:01:58 -06:00
John Shaver 1b8c2d99a2 You can now tab select check boxes. 2019-05-31 20:01:58 -06:00
John Shaver fad0739f68 Added download links for certs and changed some formatting. 2019-05-31 20:01:58 -06:00
John Shaver 1c39971be5 Added styling to key/cert page. 2019-05-31 20:01:58 -06:00
John Shaver 1dc1cae50a First 2 steps down. 2019-05-31 20:01:58 -06:00
John Shaver 174c69234a Added a terrible temorary fix to the tables. 2019-05-31 20:01:58 -06:00
John Shaver b416813a44 Some style chages on first/second step. 2019-05-31 20:01:58 -06:00
John Shaver fe530836c7 Progress bar is in place. 2019-05-31 20:01:58 -06:00
John Shaver 43f0fe9faa Added Javascript needed warning. 2019-05-31 20:01:58 -06:00
John Shaver c7bddfdec9 fixed blink on loading strait to step2 2019-05-31 20:01:58 -06:00
John Shaver 34acb707ac Tied in new homepage with the rest of the app. 2019-05-31 20:01:58 -06:00
John Shaver 7af2671993 Some styling updates and created a separate front page. 2019-05-31 20:01:58 -06:00
John Shaver 5db9c2010f Center, shrink logo. 2019-05-31 20:01:58 -06:00
John Shaver af887f27b9 No longer fails due to new challenge types. 2019-05-31 20:01:58 -06:00
AJ ONeal f17e8a4183 add google analytics 2019-05-31 20:01:58 -06:00
AJ ONeal dec3229340 fix typo 2019-05-31 20:01:58 -06:00
AJ ONeal 7ce3adacb6 add line break 2019-05-31 20:01:58 -06:00
AJ ONeal 60b082788f save contact 2019-05-31 20:01:58 -06:00
AJ ONeal 426e28d1b2 use correct url 2018-11-10 01:23:39 +00:00
14 changed files with 4905 additions and 5172 deletions

4
.prettierrc Normal file
View File

@ -0,0 +1,4 @@
{
"trailingComma": "none",
"useTabs": true
}

View File

@ -1,10 +1,8 @@
Greenlock™ in your Browser # Greenlock™ in your Browser
=========================
Taking greenlock™ (Let's Encrypt v2 / ACME client) where it's never been before: Your browser! Taking greenlock™ (Let's Encrypt v2 / ACME client) where it's never been before: Your browser!
Official Site # Official Site
=============
This app is available at <https://greenlock.domains>. This app is available at <https://greenlock.domains>.
@ -14,14 +12,12 @@ If it doesn't, please open an issue to let us know why.
We'd much rather improve the app than have a hundred different versions running in the wild. We'd much rather improve the app than have a hundred different versions running in the wild.
However, in keeping to our values we've released the source for others to inspect, improve, and modify. However, in keeping to our values we've released the source for others to inspect, improve, and modify.
Trademark Notice # Trademark Notice
================
Greenlock&trade; is our trademark. If you do host your own copy of this app, Greenlock&trade; is our trademark. If you do host your own copy of this app,
please do provide attribution, but please also use your branding. please do provide attribution, but please also use your branding.
Install # Install
=======
```bash ```bash
git clone ssh://gitea@git.coolaj86.com:22042/coolaj86/greenlock.html.git git clone ssh://gitea@git.coolaj86.com:22042/coolaj86/greenlock.html.git
@ -30,8 +26,7 @@ pushd greenlock.html/
popd popd
``` ```
Usage # Usage
=====
Simply host from your webserver. Simply host from your webserver.

View File

@ -1,51 +1,79 @@
<!DOCTYPE html>
<html> <html>
<head> <head>
<title>Greenlock&trade;</title> <title>Greenlock&trade;</title>
<meta property="og:image" content="https://greenlock.domains/img/greenlock-mark-400x400.png" /> <meta
property="og:image"
content="https://greenlock.domains/img/greenlock-mark-400x400.png"
/>
<style> <style>
@font-face { @font-face {
font-family: 'Source Sans Pro'; font-family: "Source Sans Pro";
font-style: normal; font-style: normal;
font-display: block; font-display: block;
font-weight: 400; font-weight: 400;
src: local('Source Sans Pro Regular'), local('SourceSansPro-Regular'), url(./fonts/6xK3dSBYKcSV-LCoeQqfX1RYOo3qOK7l.woff2) format('woff2'); src: local("Source Sans Pro Regular"), local("SourceSansPro-Regular"),
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; 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-face {
font-family: 'Source Sans Pro'; font-family: "Source Sans Pro";
font-style: normal; font-style: normal;
font-weight: 700; font-weight: 700;
font-display: block; font-display: block;
src: local('Source Sans Pro Bold'), local('SourceSansPro-Bold'), url(./fonts/6xKydSBYKcSV-LCoeQqfX1RYOo3ig4vwlxdu.woff2) format('woff2'); src: local("Source Sans Pro Bold"), local("SourceSansPro-Bold"),
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; 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-face {
font-family: 'Source Code Pro'; font-family: "Source Code Pro";
font-style: normal; font-style: normal;
font-weight: 400; font-weight: 400;
src: local('Source Code Pro'), local('SourceCodePro-Regular'), url(./fonts/HI_SiYsKILxRpg3hIP6sJ7fM7PqlPevW.woff2) format('woff2'); src: local("Source Code Pro"), local("SourceCodePro-Regular"),
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; 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> </style>
<link href="styles/main.css" rel="stylesheet"> <link href="styles/main.css" rel="stylesheet" />
<link rel="preload" href="./fonts/6xKydSBYKcSV-LCoeQqfX1RYOo3ig4vwlxdu.woff2" as="font" crossorigin="anonymous"> <link
<link rel="preload" href="./fonts/6xK3dSBYKcSV-LCoeQqfX1RYOo3qOK7l.woff2" as="font" crossorigin="anonymous"> rel="preload"
href="./fonts/6xKydSBYKcSV-LCoeQqfX1RYOo3ig4vwlxdu.woff2"
<link rel="preload" href="./fonts/HI_SiYsKILxRpg3hIP6sJ7fM7PqlPevW.woff2" as="font" crossorigin="anonymous"> as="font"
<link rel="preload" href="./js/bacme.js" as="script"> crossorigin="anonymous"
<link rel="preload" href="./js/app.js" as="script"> />
<link rel="preload" href="./js/pkijs.org/v1.3.33/common.js" as="script"> <link
<link rel="preload" href="./js/pkijs.org/v1.3.33/asn1.js" as="script"> rel="preload"
<link rel="preload" href="./js/pkijs.org/v1.3.33/x509_schema.js" as="script"> href="./fonts/6xK3dSBYKcSV-LCoeQqfX1RYOo3qOK7l.woff2"
<link rel="preload" href="./js/pkijs.org/v1.3.33/x509_simpl.js" as="script"> as="font"
<link rel="preload" href="./js/browser-csr/v1.0.0-alpha/csr.js" as="script"> 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> </head>
<body hidden> <body hidden>
<!-- let's define our SVG that we will reuse --> <!-- 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"> <svg
xmlns="http://www.w3.org/2000/svg"
width="0"
height="0"
viewbox="0 0 24 24"
>
<defs> <defs>
<g id="svg-check"> <g id="svg-check">
<path fill="none" d="M0 0h24v24H0z" /> <path fill="none" d="M0 0h24v24H0z" />
@ -53,10 +81,14 @@
</g> </g>
<g id="svg-checked"> <g id="svg-checked">
<path d="M0 0h24v24H0z" fill="none" /> <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"/> <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>
<g id="svg-unchecked"> <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="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" /> <path d="M0 0h24v24H0z" fill="none" />
</g> </g>
<g id="svg-download"> <g id="svg-download">
@ -70,7 +102,11 @@
<div id="js-progress-bar" class="progress-bar"> <div id="js-progress-bar" class="progress-bar">
<div class="progress-bar-step"> <div class="progress-bar-step">
<div class="circle"> <div class="circle">
<svg display="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"> <svg
display="none"
xmlns="http://www.w3.org/2000/svg"
viewbox="0 0 24 24"
>
<use xlink:href="#svg-check"></use> <use xlink:href="#svg-check"></use>
</svg> </svg>
</div> </div>
@ -78,7 +114,11 @@
</div> </div>
<div class="progress-bar-step"> <div class="progress-bar-step">
<div class="circle"> <div class="circle">
<svg display="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"> <svg
display="none"
xmlns="http://www.w3.org/2000/svg"
viewbox="0 0 24 24"
>
<use xlink:href="#svg-check"></use> <use xlink:href="#svg-check"></use>
</svg> </svg>
</div> </div>
@ -86,16 +126,22 @@
</div> </div>
<div class="progress-bar-step"> <div class="progress-bar-step">
<div class="circle"> <div class="circle">
<svg display="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"> <svg
display="none"
xmlns="http://www.w3.org/2000/svg"
viewbox="0 0 24 24"
>
<use xlink:href="#svg-check"></use> <use xlink:href="#svg-check"></use>
</svg> </svg>
</div> </div>
<div class="progress-step-label"><div>Install certificates</div></div> <div class="progress-step-label">
<div>Install certificates</div>
</div>
</div> </div>
<!-- hide until the steps are all updated <!-- hide until the steps are all updated
<div class="progress-bar-step"> <div class="progress-bar-step">
<div class="circle"> <div class="circle">
<svg display="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"> <svg display="none" xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24">
<use xlink:href="#svg-check"></use> <use xlink:href="#svg-check"></use>
</svg> </svg>
</div> </div>
@ -103,52 +149,112 @@
</div> </div>
--> -->
</div> </div>
<div class="greenlock-logo-badge"><img src="./img/greenlock-mark-400x400.png"></div> <div class="greenlock-logo-badge">
<img src="./img/greenlock-mark-400x400.png" />
</div>
<div class="greenlock-name">Greenlock</div> <div class="greenlock-name">Greenlock</div>
</div> </div>
<div class="column-row"> <div class="column-row">
<form class="js-acme-form js-acme-form-domains"> <form class="js-acme-form js-acme-form-domains">
<h1><label>What's your domain?</label></h1> <h1><label>What's your domain?</label></h1>
<h4>Certificates are valid for 90 days. Renewal is free :)</h4> <h4>Certificates are valid for 90 days. Renewal is free :)</h4>
<input class="js-acme-domains" type="text" placeholder="example.com,*.example.com" required> <input
<br> class="js-acme-domains"
type="text"
placeholder="example.com,*.example.com"
required
/>
<br />
<button type="submit">Next</button> <button type="submit">Next</button>
<br> <br />
<br> <br />
<br> <br />
<label><input class="js-acme-api-type" name="acme-api-type" type="radio" value="v02" checked required> <label
Production</label> ><input
<label><input class="js-acme-api-type" name="acme-api-type" type="radio" value="staging-v02" required> class="js-acme-api-type"
Testing</label> name="acme-api-type"
<br> type="radio"
<input class="js-acme-directory-url" type="url" placeholder="ACME directory url"> 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> </form>
<!-- Step 2 Create Account --> <!-- Step 2 Create Account -->
<form class="js-acme-form js-acme-form-account"> <form class="js-acme-form js-acme-form-account">
<h1><label>What's your email?</label></h1> <h1><label>What's your email?</label></h1>
<input class="js-acme-account-email acme-account-email" type="email" placeholder="john@doe.family" required> <input
class="js-acme-account-email acme-account-email"
type="email"
placeholder="john@doe.family"
required
/>
<div class="checkbox-array"> <div class="checkbox-array">
<label> <label>
<input class="js-acme-account-tos" type="checkbox" required> <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"> <svg
class="icon-checked-box"
xmlns="http://www.w3.org/2000/svg"
viewbox="0 0 24 24"
>
<use xlink:href="#svg-checked"></use> <use xlink:href="#svg-checked"></use>
</svg> </svg>
<svg class="icon-unchecked-box" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"> <svg
class="icon-unchecked-box"
xmlns="http://www.w3.org/2000/svg"
viewbox="0 0 24 24"
>
<use xlink:href="#svg-unchecked"></use> <use xlink:href="#svg-unchecked"></use>
</svg> </svg>
Agree to &nbsp<a class="js-acme-tos-url" target="acme-tos">Let's Encrypt&trade; Terms of Service</a>? Agree to &nbsp;<a class="js-acme-tos-url" target="acme-tos"
>Let's Encrypt&trade; Terms of Service</a
>?
</label> </label>
<label> <label>
<input class="js-greenlock-account-tos" type="checkbox" required> <input
<svg class="icon-checked-box" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"> 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> <use xlink:href="#svg-checked"></use>
</svg> </svg>
<svg class="icon-unchecked-box" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"> <svg
class="icon-unchecked-box"
xmlns="http://www.w3.org/2000/svg"
viewbox="0 0 24 24"
>
<use xlink:href="#svg-unchecked"></use> <use xlink:href="#svg-unchecked"></use>
</svg> </svg>
Agree to &nbsp<a class="js-gl-tos" target="_blank" href="/legal/#terms">Greenlock&trade; Terms of Service</a>? Agree to &nbsp<a
class="js-gl-tos"
target="_blank"
href="/legal/#terms"
>Greenlock&trade; Terms of Service</a
>?
</label> </label>
</div> </div>
<!-- <!--
@ -156,77 +262,124 @@
<br> <br>
<br> <br>
--> -->
<button class="button-next" type="submit">Next</button> <button class="button-next js-account-next" type="submit">
Next
</button>
<div class="email-usage"> <div class="email-usage">
Why do we need your email? We link your SSL certificates to the Why do we need your email? We link your SSL certificates to the
email you use so you can manage your certificates in the future, email you use so that you'll be notified before the certificate
and get important email updates about them. expires and so you can manage your certificates in the future.
</div> </div>
</form> </form>
<!-- Step 3 Set Challanges --> <!-- Step 3 Set Challanges -->
<form class="js-acme-form js-acme-form-challenges"> <form class="js-acme-form js-acme-form-challenges">
<h1>Let's verify your domain</h1> <h1>Let's verify your domain</h1>
<div class="js-acme-challenges"> <div class="js-acme-challenges">
<div class="tabbed-selector"> <div class="tabbed-selector">
<label> <label>
<input class="js-acme-challenge-type" name="acme-challenge-type" type="radio" value="http-01" checked required> <input
class="js-acme-challenge-type"
name="acme-challenge-type"
type="radio"
value="http-01"
checked
required
/>
File Upload File Upload
<div></div> <div></div>
</label> </label>
<label> <label>
<input class="js-acme-challenge-type" name="acme-challenge-type" type="radio" value="dns-01" required> <input
class="js-acme-challenge-type"
name="acme-challenge-type"
type="radio"
value="dns-01"
required
/>
DNS Record DNS Record
<div></div> <div></div>
</label> </label>
</div> </div>
<div>
<div class="js-acme-verification-http-01"> <div class="js-acme-verification-http-01">
<h3>Upload this file</h3> <h3>Upload each file</h3>
<div class="js-acme-http">
<div class="http-verification-info file-preview"> <div class="http-verification-info file-preview">
<div class="paper-fold"></div> <div class="paper-fold"></div>
<div> <div>
<div class="file-ver-info-header">FILENAME</div> <div class="file-ver-info-header">FILENAME</div>
<pre class="js-acme-ver-file-location">...loading</pre> <pre class="js-acme-ver-file-location">...loading</pre>
</div> </div>
<hr> <hr />
<div> <div>
<div class="file-ver-info-header">CONTENTS</div> <div class="file-ver-info-header">CONTENTS</div>
<pre class="js-acme-ver-content">...loading</pre> <pre class="js-acme-ver-content">...loading</pre>
</div> </div>
</div>
<div class="download-file"> <div class="download-file">
<svg class="mdicon icon-download" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"> <svg
class="mdicon icon-download"
xmlns="http://www.w3.org/2000/svg"
viewbox="0 0 24 24"
>
<use xlink:href="#svg-download"></use> <use xlink:href="#svg-download"></use>
</svg> </svg>
<a class="js-download-verify-link" href="data:text/octet-stream;base64,SGVsbG8gV29ybGQuLi4=" download="hello.txt" target="_blank"> <a
class="js-download-verify-link"
href="data:text/octet-stream;base64,SGVsbG8gV29ybGQuLi4="
download="hello.txt"
target="_blank"
>
Download Download
</a> </a>
</div> </div>
<h3>To this location</h3> <hr />
<div class="js-acme-ver-uri" class="acme-ver-uri">..loading</div> <div>
<div class="file-ver-info-header">LOCATION</div>
<pre class="js-acme-ver-uri">..loading</pre>
</div>
</div>
<br />
</div>
</div> </div>
<div class="js-acme-verification-dns-01"> <div class="js-acme-verification-dns-01">
<h3>Set this DNS Record</h3> <h3>Set each DNS Record</h3>
<div class="acme-ver-dns-label">Hostname</div> <div class="js-acme-dns">
<div class="js-acme-ver-hostname">loading...</div>
<div class="acme-ver-dns-label">TXT Host</div> <div class="acme-ver-dns-label">TXT Host</div>
<div class="js-acme-ver-txt-host">loading...</div> <div class="js-acme-ver-txt-host">loading...</div>
<div class="acme-ver-dns-label">TXT Value</div> <div class="acme-ver-dns-label">TXT Value</div>
<div class="js-acme-ver-txt-value">loading...</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>
</div>
<div class="js-acme-wildcard-challenges"> <div class="js-acme-wildcard-challenges">
<div class="js-acme-wildcard">
<div class="js-acme-verification-wildcard"> <div class="js-acme-verification-wildcard">
<h3>Set this DNS Record</h3> <h3>Set each DNS Record (for wildcards)</h3>
<div class="acme-ver-dns-label">Hostname</div> <div class="js-acme-wildcard">
<div class="js-acme-ver-hostname">loading...</div>
<div class="acme-ver-dns-label">TXT Host</div> <div class="acme-ver-dns-label">TXT Host</div>
<div class="js-acme-ver-txt-host">loading...</div> <div class="js-acme-ver-txt-host">loading...</div>
<div class="acme-ver-dns-label">TXT Value</div> <div class="acme-ver-dns-label">TXT Value</div>
<div class="js-acme-ver-txt-value">loading...</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> </div>
</div> </div>
@ -237,6 +390,12 @@
<!-- Step 4 Process Challanges --> <!-- Step 4 Process Challanges -->
<form class="js-acme-form js-acme-form-poll"> <form class="js-acme-form js-acme-form-poll">
Verifying Domains... (give us 5 seconds or so...) 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"> <table class="js-acme-table-verifying">
@ -268,28 +427,44 @@
<h2><label>privkey.pem</label></h2> <h2><label>privkey.pem</label></h2>
<div class="acme-result-privkey file-preview"> <div class="acme-result-privkey file-preview">
<div class="paper-fold"></div> <div class="paper-fold"></div>
<pre id="js-privkey"> <pre id="js-privkey"></pre>
</pre>
</div> </div>
<div class="download-file"> <div class="download-file">
<svg class="mdicon icon-download" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"> <svg
class="mdicon icon-download"
xmlns="http://www.w3.org/2000/svg"
viewbox="0 0 24 24"
>
<use xlink:href="#svg-download"></use> <use xlink:href="#svg-download"></use>
</svg> </svg>
<a id="js-download-privkey-link" href="data:text/octet-stream;base64,SGVsbG8gV29ybGQuLi4=" download="privkey.pem" target="_blank"> <a
id="js-download-privkey-link"
href="data:text/octet-stream;base64,SGVsbG8gV29ybGQuLi4="
download="privkey.pem"
target="_blank"
>
Download Download
</a> </a>
</div> </div>
<h2><label>fullchain.pem</label></h2> <h2><label>fullchain.pem</label></h2>
<div class="acme-result-fullchain file-preview"> <div class="acme-result-fullchain file-preview">
<div class="paper-fold"></div> <div class="paper-fold"></div>
<pre id="js-fullchain"> <pre id="js-fullchain"></pre>
</pre>
</div> </div>
<div class="download-file"> <div class="download-file">
<svg class="mdicon icon-download" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"> <svg
class="mdicon icon-download"
xmlns="http://www.w3.org/2000/svg"
viewbox="0 0 24 24"
>
<use xlink:href="#svg-download"></use> <use xlink:href="#svg-download"></use>
</svg> </svg>
<a id="js-download-fullchain-link" href="data:text/octet-stream;base64,SGVsbG8gV29ybGQuLi4=" download="fullchain.pem" target="_blank"> <a
id="js-download-fullchain-link"
href="data:text/octet-stream;base64,SGVsbG8gV29ybGQuLi4="
download="fullchain.pem"
target="_blank"
>
Download Download
</a> </a>
</div> </div>
@ -322,43 +497,58 @@
<a href="#">Advanced (copy and paste)</a> <a href="#">Advanced (copy and paste)</a>
<br> <br>
<button type="submit">Start Over</button> <button type="submit">Start Over</button>
--> --></div>
</form> </form>
</div>
<div><small><center>
<div> <div>
A <a href="https://rootprojects.org/" target="_blank">Root</a> Project <small
| <a href="https://git.coolaj86.com/coolaj86/greenlock.html" target="_blank">View Source</a> (git) ><center>
| <a href="https://rootprojects.org/legal/#terms" target="_blank">Terms of Service</a> <div>
| <a href="https://rootprojects.org/legal/#privacy" target="_blank">Privacy Policy</a> 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> </div>
<!-- or <!-- or
<pre><code>git clone https://git.coolaj86.com/coolaj86/greenlock.html.git</code></pre> <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): 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> <pre><code>wget https://greenlock.domains --mirror --convert-links --adjust-extension --page-requisites --no-parent</code></pre>
--> -->
</center></small></div> </center></small
<br> >
</div>
<br />
<script src="./js/bluecrypt-acme.js"></script>
<script src="./js/bacme.js"></script> <script src="./js/greenlock.js"></script>
<script src="./js/app.js"></script>
<script src="./js/pkijs.org/v1.3.33/common.js"></script>
<script src="./js/pkijs.org/v1.3.33/asn1.js"></script>
<script src="./js/pkijs.org/v1.3.33/x509_schema.js"></script>
<script src="./js/pkijs.org/v1.3.33/x509_simpl.js"></script>
<script src="./js/browser-csr/v1.0.0-alpha/csr.js"></script>
<!-- Global site tag (gtag.js) - Google Analytics --> <!-- Global site tag (gtag.js) - Google Analytics -->
<script async src="https://www.googletagmanager.com/gtag/js?id=UA-118745161-2"></script> <script
async
src="https://www.googletagmanager.com/gtag/js?id=UA-118745161-2"
></script>
<script> <script>
window.dataLayer = window.dataLayer || []; window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);} function gtag() {
gtag('js', new Date()); dataLayer.push(arguments);
}
gtag("js", new Date());
gtag('config', 'UA-118745161-2'); gtag("config", "UA-118745161-2");
</script> </script>
</div> </div>
</div> </div>

View File

@ -1,670 +0,0 @@
(function () {
'use strict';
/*global URLSearchParams,Headers*/
var BROWSER_SUPPORTS_ECDSA;
var $qs = function (s) { return window.document.querySelector(s); };
var $qsa = function (s) { return window.document.querySelectorAll(s); };
var info = {};
var steps = {};
var nonce;
var kid;
var i = 1;
var BACME = window.BACME;
var PromiseA = window.Promise;
var crypto = window.crypto;
function testEcdsaSupport() {
var opts = {
type: 'ECDSA'
, bitlength: '256'
};
return BACME.accounts.generateKeypair(opts).then(function (jwk) {
return crypto.subtle.importKey(
"jwk"
, jwk
, { name: "ECDSA", namedCurve: "P-256" }
, true
, ["sign"]
).then(function (privateKey) {
return window.crypto.subtle.exportKey("pkcs8", privateKey);
});
});
}
function testRsaSupport() {
var opts = {
type: 'RSA'
, bitlength: '2048'
};
return BACME.accounts.generateKeypair(opts).then(function (jwk) {
return crypto.subtle.importKey(
"jwk"
, jwk
, { name: "RSASSA-PKCS1-v1_5", hash: { name: "SHA-256" } }
, true
, ["sign"]
).then(function (privateKey) {
return window.crypto.subtle.exportKey("pkcs8", privateKey);
});
});
}
function testKeypairSupport() {
return testEcdsaSupport().then(function () {
console.info("[crypto] ECDSA is supported");
BROWSER_SUPPORTS_ECDSA = true;
localStorage.setItem('version', '1');
return true;
}).catch(function () {
console.warn("[crypto] ECDSA is NOT fully supported");
BROWSER_SUPPORTS_ECDSA = false;
// fix previous firefox browsers
if (!localStorage.getItem('version')) {
localStorage.clear();
localStorage.setItem('version', '1');
}
return false;
});
}
testKeypairSupport().then(function (ecdsaSupport) {
if (ecdsaSupport) {
return true;
}
return testRsaSupport().then(function () {
console.info('[crypto] RSA is supported');
}).catch(function (err) {
console.error('[crypto] could not use either EC nor RSA.');
console.error(err);
window.alert("Your browser is cryptography support (neither RSA or EC is usable). Please use Chrome, Firefox, or Safari.");
});
});
var apiUrl = 'https://acme-{{env}}.api.letsencrypt.org/directory';
function updateApiType() {
console.log("type updated");
/*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);
}
$qsa('.js-acme-api-type').forEach(function ($el) {
$el.addEventListener('change', updateApiType);
});
updateApiType();
function hideForms() {
$qsa('.js-acme-form').forEach(function (el) {
el.hidden = true;
});
}
function updateProgress(currentStep) {
var progressSteps = $qs("#js-progress-bar").children;
for(var j = 0; j < progressSteps.length; j++) {
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 submitForm(ev) {
var j = i;
i += 1;
return PromiseA.resolve(steps[j].submit(ev)).catch(function (err) {
console.error(err);
window.alert("Something went wrong. It's our fault not yours. Please email aj@rootprojects.org and let him know that 'step " + j + "' failed.");
});
}
$qsa('.js-acme-form').forEach(function ($el) {
$el.addEventListener('submit', function (ev) {
ev.preventDefault();
submitForm(ev);
});
});
function updateChallengeType() {
/*jshint validthis: true*/
var input = this || Array.prototype.filter.call(
$qsa('.js-acme-challenge-type'), function ($el) { return $el.checked; }
)[0];
console.log('ch type radio:', input.value);
$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;
}
}
$qsa('.js-acme-challenge-type').forEach(function ($el) {
$el.addEventListener('change', updateChallengeType);
});
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'
, domain: domains.join(',')
})
}).catch(function (err) {
console.error(err);
});
}
steps[1] = function () {
updateProgress(0);
hideForms();
$qs('.js-acme-form-domains').hidden = false;
};
steps[1].submit = function () {
info.identifiers = $qs('.js-acme-domains').value.split(/\s*,\s*/g).map(function (hostname) {
return { type: 'dns', value: hostname.toLowerCase().trim() };
}).slice(0,1); //Disable multiple values for now. We'll just take the first and work with it.
info.identifiers.sort(function (a, b) {
if (a === b) { return 0; }
if (a < b) { return 1; }
if (a > b) { return -1; }
});
return BACME.directory({ directoryUrl: $qs('.js-acme-directory-url').value }).then(function (directory) {
$qs('.js-acme-tos-url').href = directory.meta.termsOfService;
return BACME.nonce().then(function (_nonce) {
nonce = _nonce;
console.log("MAGIC STEP NUMBER in 1 is:", i);
steps[i]();
});
});
};
steps[2] = function () {
updateProgress(0);
hideForms();
$qs('.js-acme-form-account').hidden = false;
};
steps[2].submit = function () {
var email = $qs('.js-acme-account-email').value.toLowerCase().trim();
info.contact = [ 'mailto:' + email ];
info.agree = $qs('.js-acme-account-tos').checked;
info.greenlockAgree = $qs('.js-gl-tos').checked;
// TODO
// options for
// * regenerate key
// * ECDSA / RSA / bitlength
// TODO ping with version and account creation
setTimeout(saveContact, 100, email, info.identifiers.map(function (ident) { return ident.value; }));
var jwk = JSON.parse(localStorage.getItem('account:' + email) || 'null');
var p;
function createKeypair() {
var opts;
if(BROWSER_SUPPORTS_ECDSA) {
opts = {
type: 'ECDSA'
, bitlength: '256'
};
} else {
opts = {
type: 'RSA'
, bitlength: '2048'
};
}
return BACME.accounts.generateKeypair(opts).then(function (jwk) {
localStorage.setItem('account:' + email, JSON.stringify(jwk));
return jwk;
});
}
if (jwk) {
p = PromiseA.resolve(jwk);
} else {
p = testKeypairSupport().then(createKeypair);
}
function createAccount(jwk) {
console.log('account jwk:');
console.log(jwk);
delete jwk.key_ops;
info.jwk = jwk;
return BACME.accounts.sign({
jwk: jwk
, contacts: [ 'mailto:' + email ]
, agree: info.agree
, nonce: nonce
, kid: kid
}).then(function (signedAccount) {
return BACME.accounts.set({
signedAccount: signedAccount
}).then(function (account) {
console.log('account:');
console.log(account);
kid = account.kid;
return kid;
});
});
}
return p.then(function (_jwk) {
jwk = _jwk;
kid = JSON.parse(localStorage.getItem('account-kid:' + email) || 'null');
var p2;
// TODO save account id rather than always retrieving it
if (kid) {
p2 = PromiseA.resolve(kid);
} else {
p2 = createAccount(jwk);
}
return p2.then(function (_kid) {
kid = _kid;
info.kid = kid;
return BACME.orders.sign({
jwk: jwk
, identifiers: info.identifiers
, kid: kid
}).then(function (signedOrder) {
return BACME.orders.create({
signedOrder: signedOrder
}).then(function (order) {
info.finalizeUrl = order.finalize;
info.orderUrl = order.url; // from header Location ???
return BACME.thumbprint({ jwk: jwk }).then(function (thumbprint) {
return BACME.challenges.all().then(function (claims) {
console.log('claims:');
console.log(claims);
var obj = { 'dns-01': [], 'http-01': [], 'wildcard': [] };
info.challenges = obj;
var map = {
'http-01': '.js-acme-verification-http-01'
, 'dns-01': '.js-acme-verification-dns-01'
, 'wildcard': '.js-acme-verification-wildcard'
};
/*
var tpls = {};
Object.keys(map).forEach(function (k) {
var sel = map[k] + ' tbody';
console.log(sel);
tpls[k] = $qs(sel).innerHTML;
$qs(map[k] + ' tbody').innerHTML = '';
});
*/
// TODO make Promise-friendly
return PromiseA.all(claims.map(function (claim) {
var hostname = claim.identifier.value;
return PromiseA.all(claim.challenges.map(function (c) {
var keyAuth = BACME.challenges['http-01']({
token: c.token
, thumbprint: thumbprint
, challengeDomain: hostname
});
return BACME.challenges['dns-01']({
keyAuth: keyAuth.value
, challengeDomain: hostname
}).then(function (dnsAuth) {
var data = {
type: c.type
, hostname: hostname
, url: c.url
, token: c.token
, keyAuthorization: keyAuth
, httpPath: keyAuth.path
, httpAuth: keyAuth.value
, dnsType: dnsAuth.type
, dnsHost: dnsAuth.host
, dnsAnswer: dnsAuth.answer
};
console.log('');
console.log('CHALLENGE');
console.log(claim);
console.log(c);
console.log(data);
console.log('');
if (claim.wildcard) {
obj.wildcard.push(data);
let verification = $qs(".js-acme-verification-wildcard");
verification.querySelector(".js-acme-ver-hostname").innerHTML = data.hostname;
verification.querySelector(".js-acme-ver-txt-host").innerHTML = data.dnsHost;
verification.querySelector(".js-acme-ver-txt-value").innerHTML = data.dnsAnswer;
} else if(obj[data.type]) {
obj[data.type].push(data);
if ('dns-01' === data.type) {
let verification = $qs(".js-acme-verification-dns-01");
verification.querySelector(".js-acme-ver-hostname").innerHTML = data.hostname;
verification.querySelector(".js-acme-ver-txt-host").innerHTML = data.dnsHost;
verification.querySelector(".js-acme-ver-txt-value").innerHTML = data.dnsAnswer;
} else if ('http-01' === data.type) {
$qs(".js-acme-ver-file-location").innerHTML = data.httpPath.split("/").slice(-1);
$qs(".js-acme-ver-content").innerHTML = data.httpAuth;
$qs(".js-acme-ver-uri").innerHTML = data.httpPath;
$qs(".js-download-verify-link").href =
"data:text/octet-stream;base64," + window.btoa(data.httpAuth);
$qs(".js-download-verify-link").download = data.httpPath.split("/").slice(-1);
}
}
});
}));
})).then(function () {
// hide wildcard if no wildcard
// hide http-01 and dns-01 if only wildcard
if (!obj.wildcard.length) {
$qs('.js-acme-wildcard-challenges').hidden = true;
}
if (!obj['http-01'].length) {
$qs('.js-acme-challenges').hidden = true;
}
updateChallengeType();
console.log("MAGIC STEP NUMBER in 2 is:", i);
steps[i]();
});
});
});
});
});
});
}).catch(function (err) {
console.error('Step \'\' Error:');
console.error(err, err.stack);
window.alert("An error happened at Step " + i + ", but it's not your fault. Email aj@rootprojects.org and let him know.");
});
};
steps[3] = function () {
updateProgress(1);
hideForms();
$qs('.js-acme-form-challenges').hidden = false;
};
steps[3].submit = function () {
var chType;
Array.prototype.some.call($qsa('.js-acme-challenge-type'), function ($el) {
if ($el.checked) {
chType = $el.value;
return true;
}
});
console.log('chType is:', chType);
var chs = [];
// do each wildcard, if any
// do each challenge, by selected type only
[ 'wildcard', chType].forEach(function (typ) {
info.challenges[typ].forEach(function (ch) {
// { jwk, challengeUrl, accountId (kid) }
chs.push({
jwk: info.jwk
, challengeUrl: ch.url
, accountId: info.kid
});
});
});
console.log("INFO.challenges !!!!!", info.challenges);
var results = [];
function nextChallenge() {
var ch = chs.pop();
if (!ch) { return results; }
return BACME.challenges.accept(ch).then(function (result) {
results.push(result);
return nextChallenge();
});
}
// for now just show the next page immediately (its a spinner)
steps[i]();
return nextChallenge().then(function (results) {
console.log('challenge status:', results);
var polls = results.slice(0);
var allsWell = true;
function checkPolls() {
return new PromiseA(function (resolve) {
setTimeout(resolve, 1000);
}).then(function () {
return PromiseA.all(polls.map(function (poll) {
return BACME.challenges.check({ challengePollUrl: poll.url });
})).then(function (polls) {
console.log(polls);
polls = polls.filter(function (poll) {
//return 'valid' !== poll.status && 'invalid' !== poll.status;
if ('pending' === poll.status) {
return true;
}
if ('invalid' === poll.status) {
allsWell = false;
window.alert("verification failed:" + poll.error.detail);
return;
}
if (poll.error) {
window.alert("verification failed:" + poll.error.detail);
return;
}
if ('valid' !== poll.status) {
allsWell = false;
console.warn('BAD POLL STATUS', poll);
window.alert("unknown error: " + JSON.stringify(poll, null, 2));
}
// TODO show status in HTML
});
if (polls.length) {
return checkPolls();
}
return true;
});
});
}
return checkPolls().then(function () {
if (allsWell) {
return submitForm();
}
});
});
};
// https://stackoverflow.com/questions/40314257/export-webcrypto-key-to-pem-format
function spkiToPEM(keydata, pemName){
var keydataS = arrayBufferToString(keydata);
var keydataB64 = window.btoa(keydataS);
var keydataB64Pem = formatAsPem(keydataB64, pemName);
return keydataB64Pem;
}
function arrayBufferToString( buffer ) {
var binary = '';
var bytes = new Uint8Array( buffer );
var len = bytes.byteLength;
for (var i = 0; i < len; i++) {
binary += String.fromCharCode( bytes[ i ] );
}
return binary;
}
function formatAsPem(str, pemName) {
var finalString = '-----BEGIN ' + pemName + ' PRIVATE KEY-----\n';
while(str.length > 0) {
finalString += str.substring(0, 64) + '\n';
str = str.substring(64);
}
finalString = finalString + '-----END ' + pemName + ' PRIVATE KEY-----';
return finalString;
}
// spinner
steps[4] = function () {
updateProgress(1);
hideForms();
$qs('.js-acme-form-poll').hidden = false;
};
steps[4].submit = function () {
console.log('Congrats! Auto advancing...');
var key = info.identifiers.map(function (ident) { return ident.value; }).join(',');
var serverJwk = JSON.parse(localStorage.getItem('server:' + key) || 'null');
var p;
function createKeypair() {
var opts;
if (BROWSER_SUPPORTS_ECDSA) {
opts = { type: 'ECDSA', bitlength: '256' };
} else {
opts = { type: 'RSA', bitlength: '2048' };
}
return BACME.domains.generateKeypair(opts).then(function (serverJwk) {
localStorage.setItem('server:' + key, JSON.stringify(serverJwk));
return serverJwk;
});
}
if (serverJwk) {
p = PromiseA.resolve(serverJwk);
} else {
p = createKeypair();
}
return p.then(function (_serverJwk) {
serverJwk = _serverJwk;
info.serverJwk = serverJwk;
// { serverJwk, domains }
return BACME.orders.generateCsr({
serverJwk: serverJwk
, domains: info.identifiers.map(function (ident) {
return ident.value;
})
}).then(function (csrweb64) {
return BACME.orders.finalize({
csr: csrweb64
, jwk: info.jwk
, finalizeUrl: info.finalizeUrl
, accountId: info.kid
});
}).then(function () {
function checkCert() {
return new PromiseA(function (resolve) {
setTimeout(resolve, 1000);
}).then(function () {
return BACME.orders.check({ orderUrl: info.orderUrl });
}).then(function (reply) {
if ('processing' === reply) {
return checkCert();
}
return reply;
});
}
return checkCert();
}).then(function (reply) {
return BACME.orders.receive({ certificateUrl: reply.certificate });
}).then(function (certs) {
console.log('WINNING!');
console.log(certs);
$qs('#js-fullchain').innerHTML = certs;
$qs("#js-download-fullchain-link").href =
"data:text/octet-stream;base64," + window.btoa(certs);
var wcOpts;
var pemName;
if (/^R/.test(info.serverJwk.kty)) {
pemName = 'RSA';
wcOpts = { name: "RSASSA-PKCS1-v1_5", hash: { name: "SHA-256" } };
} else {
pemName = 'EC';
wcOpts = { name: "ECDSA", namedCurve: "P-256" };
}
return crypto.subtle.importKey(
"jwk"
, info.serverJwk
, wcOpts
, true
, ["sign"]
).then(function (privateKey) {
return window.crypto.subtle.exportKey("pkcs8", privateKey);
}).then (function (keydata) {
var pem = spkiToPEM(keydata, pemName);
$qs('#js-privkey').innerHTML = pem;
$qs("#js-download-privkey-link").href =
"data:text/octet-stream;base64," + window.btoa(pem);
steps[i]();
});
});
}).catch(function (err) {
console.error(err.toString());
window.alert("An error happened in the final step, but it's not your fault. Email aj@rootprojects.org and let him know.");
});
};
steps[5] = function () {
updateProgress(2);
hideForms();
$qs('.js-acme-form-download').hidden = false;
};
steps[1]();
var params = new URLSearchParams(window.location.search);
var apiType = params.get('acme-api-type') || "staging-v02";
if(params.has('acme-domains')) {
console.log("acme-domains param: ", params.get('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]();
submitForm();
}
$qs('body').hidden = false;
}());

View File

@ -1,699 +0,0 @@
/*global CSR*/
// CSR takes a while to load after the page load
(function (exports) {
'use strict';
var BACME = exports.BACME = {};
var webFetch = exports.fetch;
var webCrypto = exports.crypto;
var Promise = exports.Promise;
var directoryUrl = 'https://acme-staging-v02.api.letsencrypt.org/directory';
var directory;
var nonceUrl;
var nonce;
var accountKeypair;
var accountJwk;
var accountUrl;
BACME.challengePrefixes = {
'http-01': '/.well-known/acme-challenge'
, 'dns-01': '_acme-challenge'
};
BACME._logHeaders = function (resp) {
console.log('Headers:');
Array.from(resp.headers.entries()).forEach(function (h) { console.log(h[0] + ': ' + h[1]); });
};
BACME._logBody = function (body) {
console.log('Body:');
console.log(JSON.stringify(body, null, 2));
console.log('');
};
BACME.directory = function (opts) {
return webFetch(opts.directoryUrl || directoryUrl, { mode: 'cors' }).then(function (resp) {
BACME._logHeaders(resp);
return resp.json().then(function (reply) {
if (/error/.test(reply.type)) {
return Promise.reject(new Error(reply.detail || reply.type));
}
directory = reply;
nonceUrl = directory.newNonce || 'https://acme-staging-v02.api.letsencrypt.org/acme/new-nonce';
accountUrl = directory.newAccount || 'https://acme-staging-v02.api.letsencrypt.org/acme/new-account';
orderUrl = directory.newOrder || "https://acme-staging-v02.api.letsencrypt.org/acme/new-order";
BACME._logBody(reply);
return reply;
});
});
};
BACME.nonce = function () {
return webFetch(nonceUrl, { mode: 'cors' }).then(function (resp) {
BACME._logHeaders(resp);
nonce = resp.headers.get('replay-nonce');
console.log('Nonce:', nonce);
// resp.body is empty
return resp.headers.get('replay-nonce');
});
};
BACME.accounts = {};
// type = ECDSA
// bitlength = 256
BACME.accounts.generateKeypair = function (opts) {
return BACME.generateKeypair(opts).then(function (result) {
accountKeypair = result;
return webCrypto.subtle.exportKey(
"jwk"
, result.privateKey
).then(function (privJwk) {
accountJwk = privJwk;
console.log('private jwk:');
console.log(JSON.stringify(privJwk, null, 2));
return privJwk;
/*
return webCrypto.subtle.exportKey(
"pkcs8"
, result.privateKey
).then(function (keydata) {
console.log('pkcs8:');
console.log(Array.from(new Uint8Array(keydata)));
return privJwk;
//return accountKeypair;
});
*/
});
});
};
// json to url-safe base64
BACME._jsto64 = function (json) {
return btoa(JSON.stringify(json)).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/g, '');
};
var textEncoder = new TextEncoder();
BACME._importKey = function (jwk) {
var alg; // I think the 256 refers to the hash
var wcOpts = {};
var extractable = true; // TODO make optionally false?
var priv = jwk;
var pub;
// ECDSA
if (/^EC/i.test(jwk.kty)) {
wcOpts.name = 'ECDSA';
wcOpts.namedCurve = jwk.crv;
alg = 'ES256';
pub = {
crv: priv.crv
, kty: priv.kty
, x: priv.x
, y: priv.y
};
if (!priv.d) {
priv = null;
}
}
// RSA
if (/^RS/i.test(jwk.kty)) {
wcOpts.name = 'RSASSA-PKCS1-v1_5';
wcOpts.hash = { name: "SHA-256" };
alg = 'RS256';
pub = {
e: priv.e
, kty: priv.kty
, n: priv.n
};
if (!priv.p) {
priv = null;
}
}
return window.crypto.subtle.importKey(
"jwk"
, pub
, wcOpts
, extractable
, [ "verify" ]
).then(function (publicKey) {
function give(privateKey) {
return {
wcPub: publicKey
, wcKey: privateKey
, wcKeypair: { publicKey: publicKey, privateKey: privateKey }
, meta: {
alg: alg
, name: wcOpts.name
, hash: wcOpts.hash
}
, jwk: jwk
};
}
if (!priv) {
return give();
}
return window.crypto.subtle.importKey(
"jwk"
, priv
, wcOpts
, extractable
, [ "sign"/*, "verify"*/ ]
).then(give);
});
};
BACME._sign = function (opts) {
var wcPrivKey = opts.abstractKey.wcKeypair.privateKey;
var wcOpts = opts.abstractKey.meta;
var alg = opts.abstractKey.meta.alg; // I think the 256 refers to the hash
var signHash;
console.log('kty', opts.abstractKey.jwk.kty);
signHash = { name: "SHA-" + alg.replace(/[a-z]+/ig, '') };
var msg = textEncoder.encode(opts.protected64 + '.' + opts.payload64);
console.log('msg:', msg);
return window.crypto.subtle.sign(
{ name: wcOpts.name, hash: signHash }
, wcPrivKey
, msg
).then(function (signature) {
//console.log('sig1:', signature);
//console.log('sig2:', new Uint8Array(signature));
//console.log('sig3:', Array.prototype.slice.call(new Uint8Array(signature)));
// convert buffer to urlsafe base64
var sig64 = btoa(Array.prototype.map.call(new Uint8Array(signature), function (ch) {
return String.fromCharCode(ch);
}).join('')).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/g, '');
console.log('[1] URL-safe Base64 Signature:');
console.log(sig64);
var signedMsg = {
protected: opts.protected64
, payload: opts.payload64
, signature: sig64
};
console.log('Signed Base64 Msg:');
console.log(JSON.stringify(signedMsg, null, 2));
return signedMsg;
});
};
// email = john.doe@gmail.com
// jwk = { ... }
// agree = true
BACME.accounts.sign = function (opts) {
return BACME._importKey(opts.jwk).then(function (abstractKey) {
var payloadJson =
{ termsOfServiceAgreed: opts.agree
, onlyReturnExisting: false
, contact: opts.contacts || [ 'mailto:' + opts.email ]
};
console.log('payload:');
console.log(payloadJson);
var payload64 = BACME._jsto64(
payloadJson
);
var protectedJson =
{ nonce: opts.nonce
, url: accountUrl
, alg: abstractKey.meta.alg
, jwk: null
};
if (/EC/i.test(opts.jwk.kty)) {
protectedJson.jwk = {
crv: opts.jwk.crv
, kty: opts.jwk.kty
, x: opts.jwk.x
, y: opts.jwk.y
};
} else if (/RS/i.test(opts.jwk.kty)) {
protectedJson.jwk = {
e: opts.jwk.e
, kty: opts.jwk.kty
, n: opts.jwk.n
};
} else {
return Promise.reject(new Error("[acme.accounts.sign] unsupported key type '" + opts.jwk.kty + "'"));
}
console.log('protected:');
console.log(protectedJson);
var protected64 = BACME._jsto64(
protectedJson
);
// Note: this function hashes before signing so send data, not the hash
return BACME._sign({
abstractKey: abstractKey
, payload64: payload64
, protected64: protected64
});
});
};
var accountId;
BACME.accounts.set = function (opts) {
nonce = null;
return window.fetch(accountUrl, {
mode: 'cors'
, method: 'POST'
, headers: { 'Content-Type': 'application/jose+json' }
, body: JSON.stringify(opts.signedAccount)
}).then(function (resp) {
BACME._logHeaders(resp);
nonce = resp.headers.get('replay-nonce');
accountId = resp.headers.get('location');
console.log('Next nonce:', nonce);
console.log('Location/kid:', accountId);
if (!resp.headers.get('content-type')) {
console.log('Body: <none>');
return { kid: accountId };
}
return resp.json().then(function (result) {
if (/^Error/i.test(result.detail)) {
return Promise.reject(new Error(result.detail));
}
result.kid = accountId;
BACME._logBody(result);
return result;
});
});
};
var orderUrl;
BACME.orders = {};
// identifiers = [ { type: 'dns', value: 'example.com' }, { type: 'dns', value: '*.example.com' } ]
// signedAccount
BACME.orders.sign = function (opts) {
var payload64 = BACME._jsto64({ identifiers: opts.identifiers });
return BACME._importKey(opts.jwk).then(function (abstractKey) {
var protected64 = BACME._jsto64(
{ nonce: nonce, alg: abstractKey.meta.alg/*'ES256'*/, url: orderUrl, kid: opts.kid }
);
console.log('abstractKey:');
console.log(abstractKey);
return BACME._sign({
abstractKey: abstractKey
, payload64: payload64
, protected64: protected64
}).then(function (sig) {
if (!sig) {
throw new Error('sig is undefined... nonsense!');
}
console.log('newsig', sig);
return sig;
});
});
};
var currentOrderUrl;
var authorizationUrls;
var finalizeUrl;
BACME.orders.create = function (opts) {
nonce = null;
return window.fetch(orderUrl, {
mode: 'cors'
, method: 'POST'
, headers: { 'Content-Type': 'application/jose+json' }
, body: JSON.stringify(opts.signedOrder)
}).then(function (resp) {
BACME._logHeaders(resp);
currentOrderUrl = resp.headers.get('location');
nonce = resp.headers.get('replay-nonce');
console.log('Next nonce:', nonce);
return resp.json().then(function (result) {
if (/^Error/i.test(result.detail)) {
return Promise.reject(new Error(result.detail));
}
authorizationUrls = result.authorizations;
finalizeUrl = result.finalize;
BACME._logBody(result);
result.url = currentOrderUrl;
return result;
});
});
};
BACME.challenges = {};
BACME.challenges.all = function () {
var challenges = [];
function next() {
if (!authorizationUrls.length) {
return challenges;
}
return BACME.challenges.view().then(function (challenge) {
challenges.push(challenge);
return next();
});
}
return next();
};
BACME.challenges.view = function () {
var authzUrl = authorizationUrls.pop();
var token;
var challengeDomain;
var challengeUrl;
return window.fetch(authzUrl, {
mode: 'cors'
}).then(function (resp) {
BACME._logHeaders(resp);
return resp.json().then(function (result) {
// Note: select the challenge you wish to use
var challenge = result.challenges.slice(0).pop();
token = challenge.token;
challengeUrl = challenge.url;
challengeDomain = result.identifier.value;
BACME._logBody(result);
return {
challenges: result.challenges
, expires: result.expires
, identifier: result.identifier
, status: result.status
, wildcard: result.wildcard
//, token: challenge.token
//, url: challenge.url
//, domain: result.identifier.value,
};
});
});
};
var thumbprint;
var keyAuth;
var httpPath;
var dnsAuth;
var dnsRecord;
BACME.thumbprint = function (opts) {
// https://stackoverflow.com/questions/42588786/how-to-fingerprint-a-jwk
var accountJwk = opts.jwk;
var keys;
if (/^EC/i.test(opts.jwk.kty)) {
keys = [ 'crv', 'kty', 'x', 'y' ];
} else if (/^RS/i.test(opts.jwk.kty)) {
keys = [ 'e', 'kty', 'n' ];
}
var accountPublicStr = '{' + keys.map(function (key) {
return '"' + key + '":"' + accountJwk[key] + '"';
}).join(',') + '}';
return window.crypto.subtle.digest(
{ name: "SHA-256" } // SHA-256 is spec'd, non-optional
, textEncoder.encode(accountPublicStr)
).then(function (hash) {
thumbprint = btoa(Array.prototype.map.call(new Uint8Array(hash), function (ch) {
return String.fromCharCode(ch);
}).join('')).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/g, '');
console.log('Thumbprint:');
console.log(opts);
console.log(accountPublicStr);
console.log(thumbprint);
return thumbprint;
});
};
// { token, thumbprint, challengeDomain }
BACME.challenges['http-01'] = function (opts) {
// The contents of the key authorization file
keyAuth = opts.token + '.' + opts.thumbprint;
// Where the key authorization file goes
httpPath = 'http://' + opts.challengeDomain + '/.well-known/acme-challenge/' + opts.token;
console.log("echo '" + keyAuth + "' > '" + httpPath + "'");
return {
path: httpPath
, value: keyAuth
};
};
// { keyAuth }
BACME.challenges['dns-01'] = function (opts) {
console.log('opts.keyAuth for DNS:');
console.log(opts.keyAuth);
return window.crypto.subtle.digest(
{ name: "SHA-256", }
, textEncoder.encode(opts.keyAuth)
).then(function (hash) {
dnsAuth = btoa(Array.prototype.map.call(new Uint8Array(hash), function (ch) {
return String.fromCharCode(ch);
}).join('')).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/g, '');
dnsRecord = '_acme-challenge.' + opts.challengeDomain;
console.log('DNS TXT Auth:');
// The name of the record
console.log(dnsRecord);
// The TXT record value
console.log(dnsAuth);
return {
type: 'TXT'
, host: dnsRecord
, answer: dnsAuth
};
});
};
var challengePollUrl;
// { jwk, challengeUrl, accountId (kid) }
BACME.challenges.accept = function (opts) {
var payload64 = BACME._jsto64({});
return BACME._importKey(opts.jwk).then(function (abstractKey) {
var protected64 = BACME._jsto64(
{ nonce: nonce, alg: abstractKey.meta.alg/*'ES256'*/, url: opts.challengeUrl, kid: opts.accountId }
);
return BACME._sign({
abstractKey: abstractKey
, payload64: payload64
, protected64: protected64
});
}).then(function (signedAccept) {
nonce = null;
return window.fetch(
opts.challengeUrl
, { mode: 'cors'
, method: 'POST'
, headers: { 'Content-Type': 'application/jose+json' }
, body: JSON.stringify(signedAccept)
}
).then(function (resp) {
BACME._logHeaders(resp);
nonce = resp.headers.get('replay-nonce');
console.log("ACCEPT NONCE:", nonce);
return resp.json().then(function (reply) {
challengePollUrl = reply.url;
console.log('Challenge ACK:');
console.log(JSON.stringify(reply));
return reply;
});
});
});
};
BACME.challenges.check = function (opts) {
return window.fetch(opts.challengePollUrl, { mode: 'cors' }).then(function (resp) {
BACME._logHeaders(resp);
return resp.json().then(function (reply) {
if (/error/.test(reply.type)) {
return Promise.reject(new Error(reply.detail || reply.type));
}
challengePollUrl = reply.url;
BACME._logBody(reply);
return reply;
});
});
};
var domainKeypair;
var domainJwk;
BACME.generateKeypair = function (opts) {
var wcOpts = {};
// ECDSA has only the P curves and an associated bitlength
if (/^EC/i.test(opts.type)) {
wcOpts.name = 'ECDSA';
if (/256/.test(opts.bitlength)) {
wcOpts.namedCurve = 'P-256';
}
}
// RSA-PSS is another option, but I don't think it's used for Let's Encrypt
// I think the hash is only necessary for signing, not generation or import
if (/^RS/i.test(opts.type)) {
wcOpts.name = 'RSASSA-PKCS1-v1_5';
wcOpts.modulusLength = opts.bitlength;
if (opts.bitlength < 2048) {
wcOpts.modulusLength = opts.bitlength * 8;
}
wcOpts.publicExponent = new Uint8Array([0x01, 0x00, 0x01]);
wcOpts.hash = { name: "SHA-256" };
}
var extractable = true;
return window.crypto.subtle.generateKey(
wcOpts
, extractable
, [ 'sign', 'verify' ]
);
};
BACME.domains = {};
// TODO factor out from BACME.accounts.generateKeypair even more
BACME.domains.generateKeypair = function (opts) {
return BACME.generateKeypair(opts).then(function (result) {
domainKeypair = result;
return window.crypto.subtle.exportKey(
"jwk"
, result.privateKey
).then(function (privJwk) {
domainJwk = privJwk;
console.log('private jwk:');
console.log(JSON.stringify(privJwk, null, 2));
return privJwk;
});
});
};
// { serverJwk, domains }
BACME.orders.generateCsr = function (opts) {
return BACME._importKey(opts.serverJwk).then(function (abstractKey) {
return Promise.resolve(CSR.generate({ keypair: abstractKey.wcKeypair, domains: opts.domains }));
});
};
var certificateUrl;
// { csr, jwk, finalizeUrl, accountId }
BACME.orders.finalize = function (opts) {
var payload64 = BACME._jsto64(
{ csr: opts.csr }
);
return BACME._importKey(opts.jwk).then(function (abstractKey) {
var protected64 = BACME._jsto64(
{ nonce: nonce, alg: abstractKey.meta.alg/*'ES256'*/, url: opts.finalizeUrl, kid: opts.accountId }
);
return BACME._sign({
abstractKey: abstractKey
, payload64: payload64
, protected64: protected64
});
}).then(function (signedFinal) {
nonce = null;
return window.fetch(
opts.finalizeUrl
, { mode: 'cors'
, method: 'POST'
, headers: { 'Content-Type': 'application/jose+json' }
, body: JSON.stringify(signedFinal)
}
).then(function (resp) {
BACME._logHeaders(resp);
nonce = resp.headers.get('replay-nonce');
return resp.json().then(function (reply) {
if (/error/.test(reply.type)) {
return Promise.reject(new Error(reply.detail || reply.type));
}
certificateUrl = reply.certificate;
BACME._logBody(reply);
return reply;
});
});
});
};
BACME.orders.receive = function (opts) {
return window.fetch(
opts.certificateUrl
, { mode: 'cors'
, method: 'GET'
}
).then(function (resp) {
BACME._logHeaders(resp);
nonce = resp.headers.get('replay-nonce');
return resp.text().then(function (reply) {
BACME._logBody(reply);
return reply;
});
});
};
BACME.orders.check = function (opts) {
return window.fetch(
opts.orderUrl
, { mode: 'cors'
, method: 'GET'
}
).then(function (resp) {
BACME._logHeaders(resp);
return resp.json().then(function (reply) {
if (/error/.test(reply.type)) {
return Promise.reject(new Error(reply.detail || reply.type));
}
BACME._logBody(reply);
return reply;
});
});
};
}(window));

File diff suppressed because it is too large Load Diff

View File

@ -1,38 +1,54 @@
(function() { (function() {
'use strict'; "use strict";
/*global URLSearchParams,Headers*/ /*global URLSearchParams,Headers*/
var VERSION = '2'; 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 ) // 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) // ECDSA P-384, P-521, and RSA 3072, 4096 are NOT recommend standards (and not properly supported)
var BROWSER_SUPPORTS_RSA; var BROWSER_SUPPORTS_RSA = false;
var ECDSA_OPTS = { kty: 'EC', namedCurve: 'P-256' }; var ECDSA_OPTS = { kty: "EC", namedCurve: "P-256" };
var RSA_OPTS = { kty: 'RSA', modulusLength: 2048 }; var RSA_OPTS = { kty: "RSA", modulusLength: 2048 };
var Promise = window.Promise; var Promise = window.Promise;
var Keypairs = window.Keypairs; var Keypairs = window.Keypairs;
var ACME = window.ACME; var ACME = window.ACME;
var CSR = window.CSR; var CSR = window.CSR;
var $qs = function (s) { return window.document.querySelector(s); }; var $qs = function(s) {
var $qsa = function (s) { return window.document.querySelectorAll(s); }; return window.document.querySelector(s);
};
var $qsa = function(s) {
return window.document.querySelectorAll(s);
};
var acme; var acme;
var accountStuff;
var info = {}; var info = {};
var steps = {}; var steps = {};
var i = 1; var i = 1;
var apiUrl = 'https://acme-{{env}}.api.letsencrypt.org/directory'; 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() { function updateApiType() {
console.log("type updated");
/*jshint validthis: true */ /*jshint validthis: true */
var input = this || Array.prototype.filter.call( var input =
$qsa('.js-acme-api-type'), function ($el) { return $el.checked; } this ||
)[0]; Array.prototype.filter.call($qsa(".js-acme-api-type"), function($el) {
console.log('ACME api type radio:', input.value); return $el.checked;
$qs('.js-acme-directory-url').value = apiUrl.replace(/{{env}}/g, input.value); })[0];
//#console.log('ACME api type radio:', input.value);
$qs(".js-acme-directory-url").value = apiUrl.replace(
/{{env}}/g,
input.value
);
} }
function hideForms() { function hideForms() {
$qsa('.js-acme-form').forEach(function (el) { $qsa(".js-acme-form").forEach(function(el) {
el.hidden = true; el.hidden = true;
}); });
} }
@ -54,51 +70,100 @@
} }
} }
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) { function submitForm(ev) {
var j = i; var j = i;
i += 1; i += 1;
return PromiseA.resolve(steps[j].submit(ev)).catch(function (err) { return PromiseA.resolve()
.then(function() {
return steps[j].submit(ev);
})
.catch(function(err) {
var ourfault = true;
console.error(err); console.error(err);
window.alert("Something went wrong. It's our fault not yours. Please email aj@rootprojects.org and let him know that 'step " + j + "' failed."); 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 testEcdsaSupport() {
/*
var opts = {
kty: $('input[name="kty"]:checked').value
, namedCurve: $('input[name="ec-crv"]:checked').value
, modulusLength: $('input[name="rsa-len"]:checked').value
};
*/
}
function testRsaSupport() {
return Keypairs.generate(RSA_OPTS);
}
function testKeypairSupport() { function testKeypairSupport() {
// fix previous browsers return Keypairs.generate(RSA_OPTS)
var isCurrent = (localStorage.getItem('version') === VERSION); .then(function() {
if (!isCurrent) {
localStorage.clear();
localStorage.setItem('version', VERSION);
}
localStorage.setItem('version', VERSION);
return testRsaSupport().then(function () {
console.info("[crypto] RSA is supported"); console.info("[crypto] RSA is supported");
BROWSER_SUPPORTS_RSA = true; BROWSER_SUPPORTS_RSA = true;
return BROWSER_SUPPORTS_RSA; })
}).catch(function () { .catch(function() {
console.warn("[crypto] RSA is NOT fully supported"); console.warn("[crypto] RSA is NOT supported");
BROWSER_SUPPORTS_RSA = false; return Keypairs.generate(ECDSA_OPTS)
return BROWSER_SUPPORTS_RSA; .then(function() {
console.info("[crypto] ECDSA is supported");
})
.catch(function(e) {
console.warn("[crypto] EC is NOT supported");
throw e;
});
}); });
} }
function getServerKeypair() { function getServerKeypair() {
var sortedAltnames = info.identifiers.map(function (ident) { return ident.value; }).sort().join(','); var sortedAltnames = info.identifiers
var serverJwk = JSON.parse(localStorage.getItem('server:' + sortedAltnames) || 'null'); .map(function(ident) {
return ident.value;
})
.sort()
.join(",");
var serverJwk = JSON.parse(
localStorage.getItem("server:" + sortedAltnames) || "null"
);
if (serverJwk) { if (serverJwk) {
return PromiseA.resolve(serverJwk); return PromiseA.resolve(serverJwk);
} }
@ -111,359 +176,406 @@
keypairOpts = ECDSA_OPTS; keypairOpts = ECDSA_OPTS;
} }
return Keypairs.generate(RSA_OPTS).catch(function (err) { return Keypairs.generate(RSA_OPTS)
console.error("[Error] Keypairs.generate(" + JSON.stringify(RSA_OPTS) + "):"); .catch(function(err) {
console.error(
"[Error] Keypairs.generate(" + JSON.stringify(RSA_OPTS) + "):"
);
throw err; throw err;
}).then(function (pair) { })
localStorage.setItem('server:'+sortedAltnames, JSON.stringify(pair.private)); .then(function(pair) {
localStorage.setItem(
"server:" + sortedAltnames,
JSON.stringify(pair.private)
);
return pair.private; return pair.private;
}); });
} }
function getAccountKeypair(email) { function getAccountKeypair(email) {
var json = localStorage.getItem('account:'+email); var json = localStorage.getItem("account:" + email);
if (json) { if (json) {
return Promise.resolve(JSON.parse(json)); return Promise.resolve(JSON.parse(json));
} }
return Keypairs.generate(ECDSA_OPTS).catch(function (err) { return Keypairs.generate(ECDSA_OPTS)
console.warn("[Error] Keypairs.generate(" + JSON.stringify(ECDSA_OPTS) + "):\n", err); .catch(function(err) {
console.warn(
"[Error] Keypairs.generate(" + JSON.stringify(ECDSA_OPTS) + "):\n",
err
);
return Keypairs.generate(RSA_OPTS).catch(function(err) { return Keypairs.generate(RSA_OPTS).catch(function(err) {
console.error("[Error] Keypairs.generate(" + JSON.stringify(RSA_OPTS) + "):"); console.error(
"[Error] Keypairs.generate(" + JSON.stringify(RSA_OPTS) + "):"
);
throw err; throw err;
}); });
}).then(function (pair) { })
localStorage.setItem('account:'+email, JSON.stringify(pair.private)); .then(function(pair) {
localStorage.setItem("account:" + email, JSON.stringify(pair.private));
return pair.private; return pair.private;
}); });
} }
function updateChallengeType() { function updateChallengeType() {
/*jshint validthis: true*/ /*jshint validthis: true*/
var input = this || Array.prototype.filter.call( var input =
$qsa('.js-acme-challenge-type'), function ($el) { return $el.checked; } this ||
)[0]; Array.prototype.filter.call($qsa(".js-acme-challenge-type"), function(
console.log('ch type radio:', input.value); $el
$qs('.js-acme-verification-wildcard').hidden = true; ) {
$qs('.js-acme-verification-http-01').hidden = true; return $el.checked;
$qs('.js-acme-verification-dns-01').hidden = true; })[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) { if (info.challenges.wildcard) {
$qs('.js-acme-verification-wildcard').hidden = false; $qs(".js-acme-verification-wildcard").hidden = false;
} }
if (info.challenges[input.value]) { if (info.challenges[input.value]) {
$qs('.js-acme-verification-' + input.value).hidden = false; $qs(".js-acme-verification-" + input.value).hidden = false;
} }
} }
function saveContact(email, domains) { function saveContact(email, domains) {
// to be used for good, not evil // to be used for good, not evil
return window.fetch('https://api.rootprojects.org/api/rootprojects.org/public/community', { return window
method: 'POST' .fetch(
, cors: true "https://api.rootprojects.org/api/rootprojects.org/public/community",
, headers: new Headers({ 'Content-Type': 'application/json' }) {
, body: JSON.stringify({ method: "POST",
address: email cors: true,
, project: 'greenlock-domains@rootprojects.org' headers: new Headers({ "Content-Type": "application/json" }),
, timezone: new Intl.DateTimeFormat().resolvedOptions().timeZone body: JSON.stringify({
, domain: domains.join(',') address: email,
project: "greenlock-domains@rootprojects.org",
timezone: new Intl.DateTimeFormat().resolvedOptions().timeZone,
domain: domains.join(",")
}) })
}).catch(function (err) { }
)
.catch(function(err) {
console.error(err); console.error(err);
}); });
} }
steps[1] = function() { steps[1] = function() {
console.info("\n1. Show domains form");
updateProgress(0); updateProgress(0);
hideForms(); hideForms();
$qs('.js-acme-form-domains').hidden = false; $qs(".js-acme-form-domains").hidden = false;
}; };
steps[1].submit = function() { steps[1].submit = function() {
info.identifiers = $qs('.js-acme-domains').value.split(/\s*,\s*/g).map(function (hostname) { console.info(
return { type: 'dns', value: hostname.toLowerCase().trim() }; "[submit] 1. Process domains, create ACME client",
}).slice(0,1); //Disable multiple values for now. We'll just take the first and work with it. 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) { info.identifiers.sort(function(a, b) {
if (a === b) { return 0; } if (a === b) {
if (a < b) { return 1; } return 0;
if (a > b) { return -1; } }
if (a < b) {
return 1;
}
if (a > b) {
return -1;
}
}); });
var acmeDirectoryUrl = $qs('.js-acme-directory-url').value; var acmeDirectoryUrl = $qs(".js-acme-directory-url").value;
acme = ACME.create({ Keypairs: Keypairs, CSR: CSR }); acme = ACME.create({ Keypairs: Keypairs, CSR: CSR });
return acme.init(acmeDirectoryUrl).then(function(directory) { return acme.init(acmeDirectoryUrl).then(function(directory) {
$qs('.js-acme-tos-url').href = directory.meta.termsOfService; $qs(".js-acme-tos-url").href = directory.meta.termsOfService;
console.log("MAGIC STEP NUMBER in 1 is:", i); return steps[i]();
steps[i]();
}); });
}; };
steps[2] = function() { steps[2] = function() {
console.info("\n2. Show account (email, ToS) form");
updateProgress(0); updateProgress(0);
hideForms(); hideForms();
$qs('.js-acme-form-account').hidden = false; $qs(".js-acme-form-account").hidden = false;
}; };
steps[2].submit = function() { steps[2].submit = function() {
var email = $qs('.js-acme-account-email').value.toLowerCase().trim(); console.info("[submit] 2. Create ACME account (get Key ID)");
info.contact = [ 'mailto:' + email ]; var email = $qs(".js-acme-account-email")
info.agree = $qs('.js-acme-account-tos').checked; .value.toLowerCase()
.trim();
info.email = email;
info.contact = ["mailto:" + email];
info.agree = $qs(".js-acme-account-tos").checked;
//info.greenlockAgree = $qs('.js-gl-tos').checked; //info.greenlockAgree = $qs('.js-gl-tos').checked;
// TODO ping with version and account creation // TODO ping with version and account creation
setTimeout(saveContact, 100, email, info.identifiers.map(function (ident) { return ident.value; })); setTimeout(saveContact, 100, email, info.domains);
function checkTos(tos) { $qs(".js-account-next").disabled = true;
if (info.agree) {
return tos;
} else {
return '';
}
}
return info.cryptoCheck
.then(function() {
return getAccountKeypair(email).then(function(jwk) { return getAccountKeypair(email).then(function(jwk) {
// TODO save account id rather than always retrieving it? // TODO save account id rather than always retrieving it?
return acme.accounts.create({ console.info("[accounts] upsert for", email);
email: email return acme.accounts
, agreeToTerms: checkTos .create({
, accountKeypair: { privateKeyJwk: jwk } email: email,
}).then(function (account) { agreeToTerms: info.agree && true,
console.log("account created result:", account); accountKeypair: { privateKeyJwk: jwk }
accountStuff.account = account; })
accountStuff.privateJwk = jwk; .then(function(account) {
accountStuff.email = email; console.info("[accounts] result:", account);
accountStuff.acme = acme; // TODO XXX remove info.account = account;
}).catch(function (err) { info.privateJwk = jwk;
console.error("A bad thing happened:"); info.email = email;
})
.catch(function(err) {
console.error("[accounts] failed to upsert account:");
console.error(err); console.error(err);
window.alert(err.message || JSON.stringify(err, null, 2)); return newAlert(err.message || JSON.stringify(err, null, 2));
return new Promise(function () {
// stop the process cold
console.warn('TODO: resume at ask email?');
}); });
}); });
}).then(function () { })
var jwk = accountStuff.privateJwk; .then(function() {
var account = accountStuff.account; var jwk = info.privateJwk;
var account = info.account;
return acme.orders.create({ console.info("[orders] requesting");
account: account return acme.orders
, accountKeypair: { privateKeyJwk: jwk } .request({
, identifiers: info.identifiers account: account,
}).then(function (order) { accountKeypair: { privateKeyJwk: jwk },
return acme.orders.create({ domains: info.domains
signedOrder: signedOrder })
}).then(function (order) { .then(function(order) {
accountStuff.order = order; info.order = order;
var claims = order.challenges; console.info("[orders] created ", order);
console.log('claims:');
console.log(claims);
var obj = { 'dns-01': [], 'http-01': [], 'wildcard': [] }; var claims = order.claims;
var obj = { "dns-01": [], "http-01": [], wildcard: [] };
info.challenges = obj; info.challenges = obj;
var map = {
'http-01': '.js-acme-verification-http-01'
, 'dns-01': '.js-acme-verification-dns-01'
, 'wildcard': '.js-acme-verification-wildcard'
};
options.challengePriority = [ 'http-01', 'dns-01' ];
// TODO make Promise-friendly var $httpList = $qs(".js-acme-http");
return PromiseA.all(claims.map(function (claim) { 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; var hostname = claim.identifier.value;
return PromiseA.all(claim.challenges.map(function (c) { claim.challenges.forEach(function(c) {
var keyAuth = BACME.challenges['http-01']({ var auth = c;
token: c.token
, thumbprint: thumbprint
, challengeDomain: hostname
});
return BACME.challenges['dns-01']({
keyAuth: keyAuth.value
, challengeDomain: hostname
}).then(function (dnsAuth) {
var data = { var data = {
type: c.type type: c.type,
, hostname: hostname hostname: hostname,
, url: c.url url: c.url,
, token: c.token token: c.token,
, keyAuthorization: keyAuth httpPath: auth.challengeUrl,
, httpPath: keyAuth.path httpAuth: auth.keyAuthorization,
, httpAuth: keyAuth.value dnsType: "TXT",
, dnsType: dnsAuth.type dnsHost: auth.dnsHost,
, dnsHost: dnsAuth.host dnsAnswer: auth.keyAuthorizationDigest
, dnsAnswer: dnsAuth.answer
}; };
//#console.log("claims[i].challenge", data);
console.log(''); var $tpl = document.createElement("div");
console.log('CHALLENGE');
console.log(claim);
console.log(c);
console.log(data);
console.log('');
if (claim.wildcard) { if (claim.wildcard) {
obj.wildcard.push(data); obj.wildcard.push(data);
let verification = $qs(".js-acme-verification-wildcard"); $tpl.innerHTML = wildTpl;
verification.querySelector(".js-acme-ver-hostname").innerHTML = data.hostname; $tpl.querySelector(".js-acme-ver-txt-host").innerHTML =
verification.querySelector(".js-acme-ver-txt-host").innerHTML = data.dnsHost; data.dnsHost;
verification.querySelector(".js-acme-ver-txt-value").innerHTML = data.dnsAnswer; $tpl.querySelector(".js-acme-ver-txt-value").innerHTML =
data.dnsAnswer;
$wildList.appendChild($tpl);
} else if (obj[data.type]) { } else if (obj[data.type]) {
obj[data.type].push(data); obj[data.type].push(data);
if ('dns-01' === data.type) { if ("dns-01" === data.type) {
let verification = $qs(".js-acme-verification-dns-01"); $tpl.innerHTML = dnsTpl;
verification.querySelector(".js-acme-ver-hostname").innerHTML = data.hostname; $tpl.querySelector(".js-acme-ver-txt-host").innerHTML =
verification.querySelector(".js-acme-ver-txt-host").innerHTML = data.dnsHost; data.dnsHost;
verification.querySelector(".js-acme-ver-txt-value").innerHTML = data.dnsAnswer; $tpl.querySelector(".js-acme-ver-txt-value").innerHTML =
} else if ('http-01' === data.type) { data.dnsAnswer;
$qs(".js-acme-ver-file-location").innerHTML = data.httpPath.split("/").slice(-1); $dnsList.appendChild($tpl);
$qs(".js-acme-ver-content").innerHTML = data.httpAuth; } else if ("http-01" === data.type) {
$qs(".js-acme-ver-uri").innerHTML = data.httpPath; $tpl.innerHTML = httpTpl;
$qs(".js-download-verify-link").href = $tpl.querySelector(
"data:text/octet-stream;base64," + window.btoa(data.httpAuth); ".js-acme-ver-file-location"
$qs(".js-download-verify-link").download = data.httpPath.split("/").slice(-1); ).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);
} }
} }
}); });
});
}));
})).then(function () {
// hide wildcard if no wildcard // hide wildcard if no wildcard
// hide http-01 and dns-01 if only wildcard // hide http-01 and dns-01 if only wildcard
if (!obj.wildcard.length) { if (!obj.wildcard.length) {
$qs('.js-acme-wildcard-challenges').hidden = true; $qs(".js-acme-wildcard-challenges").hidden = true;
} }
if (!obj['http-01'].length) { if (!obj["http-01"].length) {
$qs('.js-acme-challenges').hidden = true; $qs(".js-acme-challenges").hidden = true;
} }
console.info("[housekeeping] challenges", info.challenges);
updateChallengeType(); updateChallengeType();
return steps[i]();
console.log("MAGIC STEP NUMBER in 2 is:", i); })
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:");
}).catch(function (err) {
console.error('Step \'\' Error:');
console.error(err, err.stack); console.error(err, err.stack);
window.alert("An error happened at Step " + i + ", but it's not your fault. Email aj@rootprojects.org and let him know."); 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() { steps[3] = function() {
console.info("\n3. Present challenge options");
updateProgress(1); updateProgress(1);
hideForms(); hideForms();
$qs('.js-acme-form-challenges').hidden = false; $qs(".js-acme-form-challenges").hidden = false;
}; };
steps[3].submit = function() { steps[3].submit = function() {
options.challengeTypes = [ 'dns-01' ]; console.info("[submit] 3. Fulfill challenges, fetch certificate");
if ('http-01' === $qs('.js-acme-challenge-type:checked').value) {
options.challengeTypes.unshift('http-01'); var challengePriority = ["dns-01"];
} if ("http-01" === $qs(".js-acme-challenge-type:checked").value) {
console.log('primary challenge type is:', options.challengeTypes[0]); challengePriority.unshift("http-01");
}
console.info("[challenge] selected ", challengePriority[0]);
return getAccountKeypair(email).then(function (jwk) {
// for now just show the next page immediately (its a spinner) // for now just show the next page immediately (its a spinner)
// TODO put a test challenge in the list
// TODO warn about wait-time if DNS
steps[i](); steps[i]();
return getServerKeypair().then(function () {
return acme.orders.finalize({
account: accountStuff.account
, accountKeypair: { privateKeyJwk: jwk }
, order: accountStuff.order
, domainKeypair: 'TODO'
});
}).then(function (certs) {
console.log('WINNING!');
console.log(certs);
$qs('#js-fullchain').innerHTML = certs;
$qs("#js-download-fullchain-link").href =
"data:text/octet-stream;base64," + window.btoa(certs);
var wcOpts; return getAccountKeypair(info.email).then(function(jwk) {
var pemName; // TODO put a test challenge in the list
if (/^R/.test(info.serverJwk.kty)) { // info.order.claims.push(...)
pemName = 'RSA'; // TODO warn about wait-time if DNS
wcOpts = { name: "RSASSA-PKCS1-v1_5", hash: { name: "SHA-256" } }; return getServerKeypair().then(function(serverJwk) {
} else { return acme.orders
pemName = 'EC'; .complete({
wcOpts = { name: "ECDSA", namedCurve: "P-256" }; 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;
} }
return crypto.subtle.importKey( })
"jwk" .then(function(certs) {
, info.serverJwk return Keypairs.export({ jwk: serverJwk }).then(function(keyPem) {
, wcOpts console.info("WINNING!");
, true console.info(certs);
, ["sign"] var fullChainText = [
).then(function (privateKey) { certs.cert.trim() + "\n",
return window.crypto.subtle.exportKey("pkcs8", privateKey); certs.chain + "\n"
}).then (function (keydata) { ].join("\n");
var pem = spkiToPEM(keydata, pemName);
$qs('#js-privkey').innerHTML = pem; $qs("#js-fullchain").innerHTML = fullChainText;
$qs("#js-download-fullchain-link").href =
"data:text/octet-stream;base64," + window.btoa(fullChainText);
$qs("#js-privkey").innerHTML = keyPem;
$qs("#js-download-privkey-link").href = $qs("#js-download-privkey-link").href =
"data:text/octet-stream;base64," + window.btoa(pem); "data:text/octet-stream;base64," + window.btoa(keyPem);
steps[i]();
});
});
}).then(function () {
return submitForm(); return submitForm();
}); });
});
});
});
}; };
// spinner // spinner
steps[4] = function() { steps[4] = function() {
console.info("\n4. Show loading spinner");
updateProgress(1); updateProgress(1);
hideForms(); hideForms();
$qs('.js-acme-form-poll').hidden = false; $qs(".js-acme-form-poll").hidden = false;
}; };
steps[4].submit = function() { steps[4].submit = function() {
console.log('Congrats! Auto advancing...'); console.info("[submit] 4. Order complete");
return steps[i]();
}).catch(function (err) {
console.error(err.toString());
window.alert("An error happened in the final step, but it's not your fault. Email aj@rootprojects.org and let him know.");
});
}; };
steps[5] = function() { steps[5] = function() {
console.info("\n5. Present certificates (yay!)");
updateProgress(2); updateProgress(2);
hideForms(); hideForms();
$qs('.js-acme-form-download').hidden = false; $qs(".js-acme-form-download").hidden = false;
}; };
steps[1]();
var params = new URLSearchParams(window.location.search); function init() {
var apiType = params.get('acme-api-type') || "staging-v02"; $qsa(".js-acme-api-type").forEach(function($el) {
$el.addEventListener("change", updateApiType);
$qsa('.js-acme-api-type').forEach(function ($el) {
$el.addEventListener('change', updateApiType);
}); });
updateApiType(); updateApiType();
$qsa('.js-acme-form').forEach(function ($el) { $qsa(".js-acme-form").forEach(function($el) {
$el.addEventListener('submit', function (ev) { $el.addEventListener("submit", function(ev) {
ev.preventDefault(); ev.preventDefault();
submitForm(ev); return submitForm(ev);
}); });
}); });
$qsa(".js-acme-challenge-type").forEach(function($el) {
$qsa('.js-acme-challenge-type').forEach(function ($el) { $el.addEventListener("change", updateChallengeType);
$el.addEventListener('change', updateChallengeType);
}); });
if(params.has('acme-domains')) { var params = new URLSearchParams(window.location.search);
console.log("acme-domains param: ", params.get('acme-domains')); var apiType = params.get("acme-api-type") || "staging-v02";
$qs('.js-acme-domains').value = params.get('acme-domains'); if (params.has("acme-domains")) {
$qs(".js-acme-domains").value = params.get("acme-domains");
$qsa('.js-acme-api-type').forEach(function(ele) { $qsa(".js-acme-api-type").forEach(function(ele) {
if (ele.value === apiType) { if (ele.value === apiType) {
ele.checked = true; ele.checked = true;
} }
@ -471,24 +583,27 @@
updateApiType(); updateApiType();
steps[2](); steps[2]();
submitForm(); return submitForm();
} else {
steps[1]();
}
} }
$qs('body').hidden = false; init();
$qs("body").hidden = false;
return testKeypairSupport().then(function (rsaSupport) { // in the background
if (rsaSupport) { info.cryptoCheck = testKeypairSupport()
return true; .then(function() {
} console.info("[crypto] self-check: passed");
})
return testRsaSupport().then(function () { .catch(function(err) {
console.info('[crypto] RSA is supported'); console.error("[crypto] could not use either RSA nor EC.");
}).catch(function (err) {
console.error('[crypto] could not use either RSA nor EC.');
console.error(err); console.error(err);
window.alert("Generating secure certificates requires a browser with cryptography support." window.alert(
+ "Please consider a recent version of Chrome, Firefox, or Safari."); "Generating secure certificates requires a browser with cryptography support." +
"Please consider a recent version of Chrome, Firefox, or Safari."
);
throw err; throw err;
}); });
}); })();
}());

View File

@ -15,7 +15,8 @@ a {
color: #1a1a1a; color: #1a1a1a;
} }
input[type=email], input[type=text] { input[type="email"],
input[type="text"] {
font-size: 1em; font-size: 1em;
padding: 0.444444444em 0.888889em; padding: 0.444444444em 0.888889em;
width: 100%; width: 100%;
@ -89,7 +90,6 @@ pre {
text-align: center; text-align: center;
position: absolute; position: absolute;
left: 50%; left: 50%;
=: block font-size: ;
top: 139%; top: 139%;
font-size: 0.722222222em; font-size: 0.722222222em;
white-space: nowrap; white-space: nowrap;
@ -111,7 +111,8 @@ pre {
padding: 1.6em 2.9333em 1.6em 1.6em; padding: 1.6em 2.9333em 1.6em 1.6em;
} }
.js-progress-step-complete > .circle, .js-progress-step-started > .circle { .js-progress-step-complete > .circle,
.js-progress-step-started > .circle {
background-color: #5bc17f; background-color: #5bc17f;
} }
@ -127,28 +128,29 @@ pre {
padding: 1em 0; padding: 1em 0;
} }
.checkbox-array input[type=checkbox] { .checkbox-array input[type="checkbox"] {
opacity: 0; opacity: 0;
position: absolute; position: absolute;
} }
.checkbox-array input[type=checkbox] ~ .icon-checked-box { .checkbox-array input[type="checkbox"] ~ .icon-checked-box {
display: none; display: none;
} }
.checkbox-array input[type=checkbox] ~ .icon-unchecked-box { .checkbox-array input[type="checkbox"] ~ .icon-unchecked-box {
display: initial; display: initial;
} }
.checkbox-array input[type=checkbox]:checked ~ .icon-checked-box { .checkbox-array input[type="checkbox"]:checked ~ .icon-checked-box {
display: initial; display: initial;
} }
.checkbox-array input[type=checkbox]:checked ~ .icon-unchecked-box { .checkbox-array input[type="checkbox"]:checked ~ .icon-unchecked-box {
display: none; display: none;
} }
.checkbox-array .icon-checked-box, .checkbox-array .icon-unchecked-box { .checkbox-array .icon-checked-box,
.checkbox-array .icon-unchecked-box {
width: 1.333333333em; width: 1.333333333em;
fill: #5bc17f; fill: #5bc17f;
margin-right: 0.666666667em; margin-right: 0.666666667em;
@ -161,7 +163,8 @@ pre {
margin: 0.4em 0; margin: 0.4em 0;
} }
.checkbox-array input[type=checkbox]:focus ~ .icon-checked-box, .checkbox-array input[type=checkbox]:focus ~ .icon-unchecked-box { .checkbox-array input[type="checkbox"]:focus ~ .icon-checked-box,
.checkbox-array input[type="checkbox"]:focus ~ .icon-unchecked-box {
background: #5bc17f52; background: #5bc17f52;
} }
@ -181,6 +184,10 @@ pre {
margin: 1em 0; margin: 1em 0;
} }
button {
cursor: pointer;
}
.tabbed-selector label { .tabbed-selector label {
width: 50%; width: 50%;
padding: 0.5em 0; padding: 0.5em 0;
@ -192,7 +199,7 @@ pre {
text-align: center; text-align: center;
} }
.tabbed-selector input[type=radio] { .tabbed-selector input[type="radio"] {
display: none; display: none;
} }
@ -222,7 +229,13 @@ pre {
border-bottom: solid #d9d9d9 1px; border-bottom: solid #d9d9d9 1px;
right: 0; right: 0;
top: 0; top: 0;
background: linear-gradient(45deg, #f7f7f7 0%,#f7f7f7 50%,#ffffff 50%,#ffffff 100%); background: linear-gradient(
45deg,
#f7f7f7 0%,
#f7f7f7 50%,
#ffffff 50%,
#ffffff 100%
);
} }
.file-ver-info-header { .file-ver-info-header {
@ -256,8 +269,6 @@ pre {
word-break: break-all; word-break: break-all;
} }
.cert-download-container { .cert-download-container {
margin: 0 -31%; margin: 0 -31%;
} }

View File

@ -1,73 +1,137 @@
<html> <html>
<head> <head>
<title>Greenlock&trade;</title> <title>Greenlock&trade;</title>
<meta property="og:image" content="https://greenlock.domains/img/greenlock-mark-400x400.png" /> <meta
<link href="styles/main.css" rel="stylesheet"> property="og:image"
content="https://greenlock.domains/img/greenlock-mark-400x400.png"
/>
<link href="styles/main.css" rel="stylesheet" />
<style> <style>
@font-face { @font-face {
font-family: 'Source Sans Pro'; font-family: "Source Sans Pro";
font-style: normal; font-style: normal;
font-display: block; font-display: block;
font-weight: 400; font-weight: 400;
src: local('Source Sans Pro Regular'), local('SourceSansPro-Regular'), url(./fonts/6xK3dSBYKcSV-LCoeQqfX1RYOo3qOK7l.woff2) format('woff2'); src: local("Source Sans Pro Regular"), local("SourceSansPro-Regular"),
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; 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-face {
font-family: 'Source Sans Pro'; font-family: "Source Sans Pro";
font-style: normal; font-style: normal;
font-weight: 700; font-weight: 700;
font-display: block; font-display: block;
src: local('Source Sans Pro Bold'), local('SourceSansPro-Bold'), url(./fonts/6xKydSBYKcSV-LCoeQqfX1RYOo3ig4vwlxdu.woff2) format('woff2'); src: local("Source Sans Pro Bold"), local("SourceSansPro-Bold"),
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; 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> </style>
<link rel="preload" href="./app/fonts/6xKydSBYKcSV-LCoeQqfX1RYOo3ig4vwlxdu.woff2" as="font" crossorigin="anonymous"> <link
<link rel="preload" href="./app/fonts/6xK3dSBYKcSV-LCoeQqfX1RYOo3qOK7l.woff2" as="font" crossorigin="anonymous"> rel="preload"
href="./app/fonts/6xKydSBYKcSV-LCoeQqfX1RYOo3ig4vwlxdu.woff2"
<link rel="prefetch" href="./app/fonts/HI_SiYsKILxRpg3hIP6sJ7fM7PqlPevW.woff2" as="font" crossorigin="anonymous"> as="font"
<link rel="prefetch" href="./app/js/app.js"> crossorigin="anonymous"
<link rel="prefetch" href="./app/js/bacme.js"> />
<link rel="prefetch" href="./app/js/pkijs.org/v1.3.33/common.js"> <link
<link rel="prefetch" href="./app/js/pkijs.org/v1.3.33/asn1.js"> rel="preload"
<link rel="prefetch" href="./app/js/pkijs.org/v1.3.33/x509_schema.js"> href="./app/fonts/6xK3dSBYKcSV-LCoeQqfX1RYOo3qOK7l.woff2"
<link rel="prefetch" href="./app/js/pkijs.org/v1.3.33/x509_simpl.js"> as="font"
<link rel="prefetch" href="./app/js/browser-csr/v1.0.0-alpha/csr.js"> 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> </head>
<body class="js-app-ready"> <body class="js-app-ready">
<script> <script>
document.querySelector('body').classList.remove("js-app-ready"); document.querySelector("body").classList.remove("js-app-ready");
</script> </script>
<div class="column-container wide"> <div class="column-container wide">
<div class="column-row"> <div class="column-row">
<img src="img/greenlock-146.png"> <img alt="Greenlock logo" src="img/greenlock-146.png" />
</div> </div>
<div class="column-row"> <div class="column-row">
<h1>Get the green lock for your website</h1> <h1>Get the green lock for your website</h1>
</div> </div>
<div class="column-row"> <div class="column-row">
<div class="js-javascript-warning"> <div class="js-javascript-warning">
Greenlock will process the CSR in the browser and request the certificates directly from letsencrypt.org. Please enable Javascript before continuing. Greenlock will process the CSR in the browser and request the
certificates directly from letsencrypt.org. Please enable Javascript
before continuing.
</div> </div>
<form id="js-acme-form" action="./app/" method=> <form id="js-acme-form" action="./app/" method="GET">
<div class="domain-psuedo-input"> <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> <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> </div>
<button type="submit">Go</button> <button type="submit">Go</button>
<div class="domain-subtext">Domain, subdomain, or wildcard domain</div> <div class="domain-subtext">
Domain, subdomain, or wildcard domain
</div>
<div class="acme-advanced-fields"> <div class="acme-advanced-fields">
<label><input name="acme-api-type" type="radio" value="v02" checked required> <label
><input
name="acme-api-type"
type="radio"
value="v02"
checked
required
/>
Production Production
</label> </label>
<label><input name="acme-api-type" type="radio" value="staging-v02" required> <label
Testing</label> ><input
<input id="js-acme-api-url" type="url" placeholder="ACME directory url"> name="acme-api-type"
type="radio"
value="staging-v02"
required
/>
Testing</label
>
<input
id="js-acme-api-url"
type="url"
placeholder="ACME directory url"
/>
<br />
API Compatibility: Let's Encrypt v2 / ACME draft 15
<div> <div>
A <a href="https://rootprojects.org/" target="_blank">Root</a> Project <br />
| <a href="https://git.coolaj86.com/coolaj86/greenlock.html" target="_blank">View Source</a> (git) A
| <a href="https://rootprojects.org/legal/#terms" target="_blank">Terms of Service</a> <a href="https://rootprojects.org/" target="_blank">Root</a>
| <a href="https://rootprojects.org/legal/#privacy" target="_blank">Privacy Policy</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> </div>
</div> </div>
</form> </form>
@ -75,7 +139,9 @@
<div class="column-row"> <div class="column-row">
<div class="why-you-need"> <div class="why-you-need">
<h2>Why you need HTTPS</h2> <h2>Why you need HTTPS</h2>
SSL Certificates are required for secure login, accepting payments, and for browsers like Google Chrome to stop showing security warnings to your users. SSL Certificates are required for secure login, accepting payments,
and for browsers like Google Chrome to stop showing security warnings
to your users.
</div> </div>
</div> </div>
<!-- or <!-- or
@ -87,13 +153,18 @@
<script src="./js/app.js"></script> <script src="./js/app.js"></script>
<!-- Global site tag (gtag.js) - Google Analytics --> <!-- Global site tag (gtag.js) - Google Analytics -->
<script async src="https://www.googletagmanager.com/gtag/js?id=UA-118745161-2"></script> <script
async
src="https://www.googletagmanager.com/gtag/js?id=UA-118745161-2"
></script>
<script> <script>
window.dataLayer = window.dataLayer || []; window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);} function gtag() {
gtag('js', new Date()); dataLayer.push(arguments);
}
gtag("js", new Date());
gtag('config', 'UA-118745161-2'); gtag("config", "UA-118745161-2");
</script> </script>
</div> </div>
</body> </body>

4
install.bat Normal file
View File

@ -0,0 +1,4 @@
mkdir -p app\js\
bitsadmin.exe /transfer "JobName" https://rootprojects.org/acme/bluecrypt-acme.js "%cd%\app\js\bluecrypt-acme.js"
bitsadmin.exe /transfer "JobName" https://rootprojects.org/acme/bluecrypt-acme.min.js "%cd%\app\js\bluecrypt-acme.min.js"

View File

@ -1,14 +1,7 @@
#!/bin/bash #!/bin/bash
mkdir -p app/js/pkijs.org/v1.3.33/ mkdir -p app/js/
pushd app/js/pkijs.org/v1.3.33/ pushd app/js/
wget -c https://raw.githubusercontent.com/PeculiarVentures/PKI.js/41b63af760cacb565dd850fb3466ada4ca163eff/org/pkijs/common.js wget -c https://rootprojects.org/acme/bluecrypt-acme.js
wget -c https://raw.githubusercontent.com/PeculiarVentures/PKI.js/41b63af760cacb565dd850fb3466ada4ca163eff/org/pkijs/x509_schema.js wget -c https://rootprojects.org/acme/bluecrypt-acme.min.js
wget -c https://raw.githubusercontent.com/PeculiarVentures/PKI.js/41b63af760cacb565dd850fb3466ada4ca163eff/org/pkijs/x509_simpl.js
wget -c https://raw.githubusercontent.com/PeculiarVentures/ASN1.js/f7181c21c61e53a940ea24373ab489ad86d51bc1/org/pkijs/asn1.js
popd
mkdir -p app/js/browser-csr/v1.0.0-alpha/
pushd app/js/browser-csr/v1.0.0-alpha/
wget -c https://git.coolaj86.com/coolaj86/browser-csr.js/raw/commit/01cdc0e91b5bf03f12e1b25b4129e3cde927987c/csr.js
popd popd

View File

@ -1,30 +1,39 @@
(function() { (function() {
'use strict'; "use strict";
var $qs = function (s) { return window.document.querySelector(s); }; var $qs = function(s) {
var $qsa = function (s) { return window.document.querySelectorAll(s); }; return window.document.querySelector(s);
};
$qs('.js-javascript-warning').hidden = true; $qs(".js-javascript-warning").hidden = true;
var apiUrl = "https://acme-{{env}}.api.letsencrypt.org/directory";
var apiUrl = 'https://acme-{{env}}.api.letsencrypt.org/directory';
function updateApiType() { function updateApiType() {
var formData = new FormData($qs("#js-acme-form")); var formData = new FormData($qs("#js-acme-form"));
console.log('ACME api type radio:'); console.log("ACME api type radio:");
var value = formData.get("acme-api-type"); var value = formData.get("acme-api-type");
$qs('#js-acme-api-url').value = apiUrl.replace(/{{env}}/g, value); $qs("#js-acme-api-url").value = apiUrl.replace(/{{env}}/g, value);
} }
$qs('#js-acme-form').addEventListener('change', updateApiType);
$qs("#js-acme-form").addEventListener("change", updateApiType);
//$qs('#js-acme-form').addEventListener('submit', prettyRedirect);
updateApiType(); updateApiType();
try { try {
document.fonts.load().then(function() { document.fonts
$qs('body').classList.add("js-app-ready"); .load()
}).catch(function(error) { .then(function() {
$qs('body').classList.add("js-app-ready"); $qs("body").classList.add("js-app-ready");
})
.catch(function(e) {
$qs("body").classList.add("js-app-ready");
}); });
} catch (e) { } catch (e) {
setTimeout(function() {$qs('body').classList.add("js-app-ready");}, 200); setTimeout(function() {
$qs("body").classList.add("js-app-ready");
}, 200);
} }
}()); })();

View File

@ -1,16 +1,30 @@
<!DOCTYPE html>
<html>
<head>
<title>Root Legal</title>
</head>
<body>
<h1>Greetings!</h1> <h1>Greetings!</h1>
<p>I, AJ ONeal, am not a big fan of legalize, but I am a fan of communicating <p>
clearly. I hope that this accomplish both defining some legal boundaries as well I, AJ ONeal, am not a big fan of legalize, but I am a fan of communicating
as communicating in a friendly and clear way, at least to the degree that suits clearly. I hope that this accomplish both defining some legal boundaries
our needs for the current stage of our products and services. as well as communicating in a friendly and clear way, at least to the
degree that suits our needs for the current stage of our products and
services.
</p>
<p>This is important because it is our intent to create sustainable open source <p>
projects, which means that we do want to create brand value, grow community, This is important because it is our intent to create sustainable open
and, eventually, be able to work full time on creating more great software and services. source projects, which means that we do want to create brand value, grow
community, and, eventually, be able to work full time on creating more
great software and services.
</p>
<p>If you'd like to contact me, especially if you feel that I (or we) have made <p>
a mistake in how we operate, please do so: If you'd like to contact me, especially if you feel that I (or we) have
made a mistake in how we operate, please do so:
</p>
<ul> <ul>
<li><a href="mailto:coolaj86@gmail.com">coolaj86@gmail.com</a></li> <li><a href="mailto:coolaj86@gmail.com">coolaj86@gmail.com</a></li>
@ -19,7 +33,7 @@ a mistake in how we operate, please do so:
</ul> </ul>
<h1>Contents</h1> <h1>Contents</h1>
<p>Here's what I've worked through so far: <p>Here's what I've worked through so far:</p>
<ul> <ul>
<li><a href="#greenlock">Greelock Domains</a></li> <li><a href="#greenlock">Greelock Domains</a></li>
@ -31,171 +45,271 @@ a mistake in how we operate, please do so:
<h1 id="greenlock">Greenlock Domains&trade;</h1> <h1 id="greenlock">Greenlock Domains&trade;</h1>
<p>Greenlock Domains is a service provided by <p>
<em><a href="https://coolaj86.com">AJ</a>, Brian, Greenlock Domains is a service provided by
<a href="https://jshaver.net">John</a>, &amp; Josh</em> <em
(collectively <a href="https://therootcompany.com">Root</a>) ><a href="https://coolaj86.com">AJ</a>, Brian,
for automated TLS, SSL, and HTTPS. <a href="https://jshaver.net">John</a>, &amp; Josh</em
>
(collectively <a href="https://therootcompany.com">Root</a>) for automated
TLS, SSL, and HTTPS.
</p>
<ul> <ul>
<li><a href="https://greenlock.domains" target="_blank"> <li>
https://greenlock.domains</a></li> <a href="https://greenlock.domains" target="_blank">
https://greenlock.domains</a
>
</li>
<li><a href="https://git.coolaj86.com/coolaj86/greenlock-express.js" target="_blank"> <li>
https://git.coolaj86.com/coolaj86/greenlock-express.js</a></li> <a
href="https://git.coolaj86.com/coolaj86/greenlock-express.js"
target="_blank"
>
https://git.coolaj86.com/coolaj86/greenlock-express.js</a
>
</li>
<li><a href="https://git.coolaj86.com/coolaj86/greenlock.js" target="_blank"> <li>
https://git.coolaj86.com/coolaj86/greenlock.js</a></li> <a
href="https://git.coolaj86.com/coolaj86/greenlock.js"
target="_blank"
>
https://git.coolaj86.com/coolaj86/greenlock.js</a
>
</li>
<li><a href="https://git.coolaj86.com/coolaj86/greenlock.html" target="_blank"> <li>
https://git.coolaj86.com/coolaj86/greenlock.html</a></li> <a
href="https://git.coolaj86.com/coolaj86/greenlock.html"
target="_blank"
>
https://git.coolaj86.com/coolaj86/greenlock.html</a
>
</li>
</ul> </ul>
<p>Greenlock Domains is an important product / service combo to us <p>
because it's a huge milestone on the path to a more decentralized web. Greenlock Domains is an important product / service combo to us because
We believe in <em>ownership</em> and <em>control</em> and we're it's a huge milestone on the path to a more decentralized web. We believe
building a <a href="https://therootcompany.com">Home Server</a> in
because we envision a world in which everyone is empowered to make <em>ownership</em> and <em>control</em> and we're building a
the choice of whether to rent or own their stuff. <a href="https://therootcompany.com">Home Server</a> because we envision a
world in which everyone is empowered to make the choice of whether to rent
or own their stuff.
</p>
<p>If we don't do this, well, with the way the cloud is headed, <p>
renting may be the only option in the future. If we don't do this, well, with the way the cloud is headed, renting may
be the only option in the future.
</p>
<p>We need <em>Root</em> because we want ownership. <p>We need <em>Root</em> because we want ownership.</p>
<p>If at any time you feel that any of our messaging or practices <p>
are in conflict with our mission or these values, please let us know. If at any time you feel that any of our messaging or practices are in
conflict with our mission or these values, please let us know.
</p>
<h1 id="licensing">Licensing</h1> <h1 id="licensing">Licensing</h1>
<p>Each of our products comes with its own LICENSE file and the license(s) <p>
Each of our products comes with its own LICENSE file and the license(s)
may alse be in some sort of manifest file (such as package.json). may alse be in some sort of manifest file (such as package.json).
</p>
<p>We typically use the MIT and Apache-2.0 licenses for libraries that we <p>
We typically use the MIT and Apache-2.0 licenses for libraries that we
actively want others to copy, modify, use and redistribute. actively want others to copy, modify, use and redistribute.
</p>
<p>We typically use ISC and MPL-2.0 with products for which we're a little more <p>
concerned about branding or about which we have particularly strong opinions. We typically use ISC and MPL-2.0 with products for which we're a little
more concerned about branding or about which we have particularly strong
opinions.
</p>
<p>Although we do keep some of our software proprietary and we do use trademarks, <p>
because we believe in empowerment and choice we do our best to provide usable Although we do keep some of our software proprietary and we do use
self-service forms of our products and services for personal use. trademarks, because we believe in empowerment and choice we do our best to
provide usable self-service forms of our products and services for
personal use.
</p>
<p>If at any time you feel that our Licensing is in conflict with our mission or values, <p>
please let us know. If at any time you feel that our Licensing is in conflict with our mission
or values, please let us know.
</p>
<h1 id="terms">Terms of Service</h1> <h1 id="terms">Terms of Service</h1>
<p>We want to make the world a better place. <p>
Everyone has a different definition of what "a better place" means, We want to make the world a better place. Everyone has a different
so the purpose of our terms is to rule out some things that definition of what "a better place" means, so the purpose of our terms is
we think makes the world (and particularly our world) a worse place: to rule out some things that we think makes the world (and particularly
our world) a worse place:
</p>
<p>You agree that you will use the Greenlock&trade; service, code, libraries, <p>
documentation, etc (provided by <a href="#greenlock">us</a>) You agree that you will use the Greenlock&trade; service, code, libraries,
primarily for securing network connections for yourself, your customers, documentation, etc (provided by <a href="#greenlock">us</a>) primarily for
on your and your customer's devices on internets, intranets, and... other nets. securing network connections for yourself, your customers, on your and
your customer's devices on internets, intranets, and... other nets.
</p>
<p>You agree that you will take reasonable measures to keep up-to-date with security <p>
releases. You agree that you will take reasonable measures to keep up-to-date with
security releases.
</p>
<p>You agree to not use our products or services in a way that would cause unusual <p>
or undue burden on our servers or services, our partners servers or services, or our You agree to not use our products or services in a way that would cause
customers servers or services, or in a way that harms or misrepresents the reputation unusual or undue burden on our servers or services, our partners servers
or brand value (including causing brand confusion) of the aforementioned parties or services, or our customers servers or services, or in a way that harms
(or really anybody). or misrepresents the reputation or brand value (including causing brand
confusion) of the aforementioned parties (or really anybody).
</p>
<p>This is not to say that you can't publicly have a negative opinion, but don't <p>
bite the hand that feeds and don't be vicious or misrepresentative. This is not to say that you can't publicly have a negative opinion, but
don't bite the hand that feeds and don't be vicious or misrepresentative.
</p>
<p>If you have a use case that may be in violation of these terms (particularly <p>
the part about undue burden), but you feel contributes to making the world a better If you have a use case that may be in violation of these terms
place, we're here to help (assuming it also aligns with our values). (particularly the part about undue burden), but you feel contributes to
Although it may not be appropriate to use our services, but perhaps we can help making the world a better place, we're here to help (assuming it also
you with a solution based on our no-cost, low-cost or open source products. aligns with our values). Although it may not be appropriate to use our
services, but perhaps we can help you with a solution based on our
no-cost, low-cost or open source products.
</p>
<p>If at any time you feel that our Terms of Service are in conflict with our <p>
If at any time you feel that our Terms of Service are in conflict with our
mission or values, please let us know. mission or values, please let us know.
</p>
<h1 id="trademark">Trademark</h1> <h1 id="trademark">Trademark</h1>
<p>"Greenlock" and the "green G lock" mark are Trademarks of <p>
"Greenlock" and the "green G lock" mark are Trademarks of
<a href="https://coolaj86.com" target="_blank">AJ ONeal</a>. <a href="https://coolaj86.com" target="_blank">AJ ONeal</a>.
</p>
<p>We'll be coming out with a brand guide as to how you should use <p>
the marks. In the meantime: don't change the proportions, colors We'll be coming out with a brand guide as to how you should use the marks.
(excepting the case of greyscale and black and white). In the meantime: don't change the proportions, colors (excepting the case
of greyscale and black and white).
</p>
<p>It is appropriate to use the trademark in a way that promotes the <p>
brand with proper attribution, linking to the official project repositories, etc. It is appropriate to use the trademark in a way that promotes the brand
with proper attribution, linking to the official project repositories,
etc.
</p>
<p>It is appropriate use the name greenlock in a plugin for Greenlock&trade;, <p>
It is appropriate use the name greenlock in a plugin for Greenlock&trade;,
as long as it is clear that it is a community contribution. as long as it is clear that it is a community contribution.
</p>
<p>If you create a "hard" fork of our code or any products or services, <p>
you should give your fork its own name, and not use ours. If you create a "hard" fork of our code or any products or services, you
That sound, we gladly welcome your suggestiosn and pull requests. should give your fork its own name, and not use ours. That sound, we
gladly welcome your suggestiosn and pull requests.
</p>
<p>If you mirror our code you should make it clear that it is a mirror <p>
and link to the official repository. If you mirror our code you should make it clear that it is a mirror and
in association with usand the disclose that you use Greenlock link to the official repository. in association with usand the disclose
that you use Greenlock
</p>
<p>If at any time you feel that our Trademark policies are in conflict with our <p>
values, please let us know. If at any time you feel that our Trademark policies are in conflict with
our values, please let us know.
</p>
<h1 id="privacy">Privacy Policy</h1> <h1 id="privacy">Privacy Policy</h1>
<p>What we collect and (more importantly) <em>Why</em>: <p>What we collect and (more importantly) <em>Why</em>:</p>
<p><strong>Name</strong>: <p><strong>Name</strong>:</p>
<p>In the cases that we collect your name, it's because we want to know how to address you. <p>
All four of us want to be personable if and when we reach out. In the cases that we collect your name, it's because we want to know how
to address you. All four of us want to be personable if and when we reach
out.
</p>
<p><strong>Email</strong>: <p><strong>Email</strong>:</p>
<p>There are three main purposes for which we may use your email address: <p>
There are three main purposes for which we may use your email address:
</p>
<p>1. A one-time outreach to ask if you were able to do what you intended to do. <p>
We want to make a great product. Although open source projects traditionally have 1. A one-time outreach to ask if you were able to do what you intended to
a <em>reactive</em> approach to communication (i.e. you file a bug and wait for a response), do. We want to make a great product. Although open source projects
we believe that creating sustainable open source requires a <em>proactive</em> approach. traditionally have a <em>reactive</em> approach to communication (i.e. you
file a bug and wait for a response), we believe that creating sustainable
open source requires a <em>proactive</em> approach.
</p>
<p>2. Security and legal notifications. It's important that we have a way to contact you <p>
if we've made a mistake or discover a mistake that needs to be addressed. This 2. Security and legal notifications. It's important that we have a way to
may include vulnerabilities as well as mandatory upgrades (such as when a contact you if we've made a mistake or discover a mistake that needs to be
significant change to the Let's Encrypt API is made). Making sure that our products addressed. This may include vulnerabilities as well as mandatory upgrades
work and are secure aligns with our values and contributes to our brand identity. (such as when a significant change to the Let's Encrypt API is made).
Making sure that our products work and are secure aligns with our values
and contributes to our brand identity.
</p>
<p>3. Opt-in updates. Many of you want to know when we have significant feature updates <p>
or when we have something that we believe is really valuable to share. We've created an 3. Opt-in updates. Many of you want to know when we have significant
opt-in avenue for that. And you can always opt-out as well. feature updates or when we have something that we believe is really
valuable to share. We've created an opt-in avenue for that. And you can
always opt-out as well.
</p>
<p><strong>Telemetry</strong>: <p><strong>Telemetry</strong>:</p>
<p>We believe that the current open source model needs improvement - it often <p>
relies heavily on large centralized platforms which aggregate a lot of user We believe that the current open source model needs improvement - it often
information for the platform without appropriately targeting the relationship relies heavily on large centralized platforms which aggregate a lot of
between authors and users of projcts (i.e. npm, github, etc). We believe that user information for the platform without appropriately targeting the
making open source sustainable means a greater focus on empowering authors relationship between authors and users of projcts (i.e. npm, github, etc).
and users. We've learned from other projects (Caddy, Heroku, and others) which We believe that making open source sustainable means a greater focus on
use telemetry as part of a proactive approach to open source and we believe that empowering authors and users. We've learned from other projects (Caddy,
it can be a great avenue for us to be proactive as well. Heroku, and others) which use telemetry as part of a proactive approach to
open source and we believe that it can be a great avenue for us to be
proactive as well.
</p>
<p>We may use telemetry about operating system, browser, node version, code version, <p>
and other system-level information to better understand how we can serve our users (you) We may use telemetry about operating system, browser, node version, code
and proactively solve problems that we might not otherwise hear about. For example, if version, and other system-level information to better understand how we
we see many page visits in a certain browser (or installs with a new version of node), can serve our users (you) and proactively solve problems that we might not
but few successful registrations, we know that something is wrong. otherwise hear about. For example, if we see many page visits in a certain
browser (or installs with a new version of node), but few successful
registrations, we know that something is wrong.
</p>
<p><strong>Other</strong>: <p><strong>Other</strong>:</p>
<p>We also use Google Analytics on our web sites for basic functionality. <p>
Other than that, nothing else comes to mind right now. We also use Google Analytics on our web sites for basic functionality.
As we consider what we will do in the future, it will be measured against our mission and values. Other than that, nothing else comes to mind right now. As we consider what
We never want to come across as spammy or forceful. We want to do things that help us build we will do in the future, it will be measured against our mission and
our brand, acknowledge our customers; things that are proactive, and that values. We never want to come across as spammy or forceful. We want to do
promote sustainable source. things that help us build our brand, acknowledge our customers; things
that are proactive, and that promote sustainable source.
</p>
<p>If at any time you feel that our Privacy policy is in conflict with our mission or values, <p>
please let us know. If at any time you feel that our Privacy policy is in conflict with our
mission or values, please let us know.
</p>
<br> <br />
<br> <br />
<p>Copyright 2018 AJ ONeal <p>Copyright 2018 AJ ONeal</p>
</body>
</html>

View File

@ -10,7 +10,7 @@ body {
margin-top: 5.777777778em; margin-top: 5.777777778em;
min-height: 36em; min-height: 36em;
font-size: 18px; font-size: 18px;
font-family: 'Source Sans Pro', sans-serif; font-family: "Source Sans Pro", sans-serif;
font-stretch: normal; font-stretch: normal;
line-height: 1.33; line-height: 1.33;
letter-spacing: -0.4px; letter-spacing: -0.4px;
@ -46,6 +46,7 @@ button {
color: white; color: white;
height: 40px; height: 40px;
line-height: 1.13; line-height: 1.13;
cursor: pointer;
} }
.acme-advanced-fields { .acme-advanced-fields {
@ -68,7 +69,7 @@ input#acme-domains:before {
.domain-psuedo-input { .domain-psuedo-input {
display: inline-block; display: inline-block;
margin-right: .6666667em; margin-right: 0.6666667em;
border: solid #d9d9d9 1px; border: solid #d9d9d9 1px;
border-radius: 2px; border-radius: 2px;
padding: 0.44444444em; padding: 0.44444444em;
@ -112,4 +113,3 @@ body.js-app-ready {
padding: 0.5em 1em; padding: 0.5em 1em;
width: 30em; width: 30em;
} }