Compare commits
45 Commits
22 changed files with 5365 additions and 931 deletions
@ -1,2 +1,2 @@ |
|||
js/pkijs.org |
|||
js/browser-csr |
|||
app/js/pkijs.org |
|||
app/js/browser-csr |
|||
|
@ -0,0 +1 @@ |
|||
../img/favicon-32x32.png |
Binary file not shown.
Binary file not shown.
Binary file not shown.
After Width: | Height: | Size: 6.1 KiB |
@ -0,0 +1,366 @@ |
|||
<html> |
|||
<head> |
|||
<title>Greenlock™</title> |
|||
<meta property="og:image" content="https://greenlock.domains/img/greenlock-mark-400x400.png" /> |
|||
<style> |
|||
@font-face { |
|||
font-family: 'Source Sans Pro'; |
|||
font-style: normal; |
|||
font-display: block; |
|||
font-weight: 400; |
|||
src: local('Source Sans Pro Regular'), local('SourceSansPro-Regular'), url(./fonts/6xK3dSBYKcSV-LCoeQqfX1RYOo3qOK7l.woff2) format('woff2'); |
|||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; |
|||
} |
|||
@font-face { |
|||
font-family: 'Source Sans Pro'; |
|||
font-style: normal; |
|||
font-weight: 700; |
|||
font-display: block; |
|||
src: local('Source Sans Pro Bold'), local('SourceSansPro-Bold'), url(./fonts/6xKydSBYKcSV-LCoeQqfX1RYOo3ig4vwlxdu.woff2) format('woff2'); |
|||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; |
|||
} |
|||
@font-face { |
|||
font-family: 'Source Code Pro'; |
|||
font-style: normal; |
|||
font-weight: 400; |
|||
src: local('Source Code Pro'), local('SourceCodePro-Regular'), url(./fonts/HI_SiYsKILxRpg3hIP6sJ7fM7PqlPevW.woff2) format('woff2'); |
|||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; |
|||
} |
|||
</style> |
|||
|
|||
<link href="styles/main.css" rel="stylesheet"> |
|||
<link rel="preload" href="./fonts/6xKydSBYKcSV-LCoeQqfX1RYOo3ig4vwlxdu.woff2" as="font" crossorigin="anonymous"> |
|||
<link rel="preload" href="./fonts/6xK3dSBYKcSV-LCoeQqfX1RYOo3qOK7l.woff2" as="font" crossorigin="anonymous"> |
|||
|
|||
<link rel="preload" href="./fonts/HI_SiYsKILxRpg3hIP6sJ7fM7PqlPevW.woff2" as="font" crossorigin="anonymous"> |
|||
<link rel="preload" href="./js/bacme.js" as="script"> |
|||
<link rel="preload" href="./js/app.js" as="script"> |
|||
<link rel="preload" href="./js/pkijs.org/v1.3.33/common.js" as="script"> |
|||
<link rel="preload" href="./js/pkijs.org/v1.3.33/asn1.js" as="script"> |
|||
<link rel="preload" href="./js/pkijs.org/v1.3.33/x509_schema.js" as="script"> |
|||
<link rel="preload" href="./js/pkijs.org/v1.3.33/x509_simpl.js" as="script"> |
|||
<link rel="preload" href="./js/browser-csr/v1.0.0-alpha/csr.js" as="script"> |
|||
|
|||
</head> |
|||
<body hidden> |
|||
<!-- let's define our SVG that we will reuse --> |
|||
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="0" height="0" viewBox="0 0 24 24"> |
|||
<defs> |
|||
<g id="svg-check"> |
|||
<path fill="none" d="M0 0h24v24H0z"/> |
|||
<path d="M9 16.2L4.8 12l-1.4 1.4L9 19 21 7l-1.4-1.4L9 16.2z"/> |
|||
</g> |
|||
<g id="svg-checked"> |
|||
<path d="M0 0h24v24H0z" fill="none"/> |
|||
<path d="M19 3H5c-1.11 0-2 .9-2 2v14c0 1.1.89 2 2 2h14c1.11 0 2-.9 2-2V5c0-1.1-.89-2-2-2zm-9 14l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z"/> |
|||
</g> |
|||
<g id="svg-unchecked"> |
|||
<path d="M19 5v14H5V5h14m0-2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2z"/> |
|||
<path d="M0 0h24v24H0z" fill="none"/> |
|||
</g> |
|||
<g id="svg-download"> |
|||
<path d="M19 9h-4V3H9v6H5l7 7 7-7zM5 18v2h14v-2H5z"/> |
|||
<path d="M0 0h24v24H0z" fill="none"/> |
|||
</g> |
|||
</defs> |
|||
</svg> |
|||
<div class="column-container wide"> |
|||
<div class="header-row column-row"> |
|||
<div id="js-progress-bar" class="progress-bar"> |
|||
<div class="progress-bar-step"> |
|||
<div class="circle"> |
|||
<svg display="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"> |
|||
<use xlink:href="#svg-check"></use> |
|||
</svg> |
|||
</div> |
|||
<div class="progress-step-label"><div>Details</div></div> |
|||
</div> |
|||
<div class="progress-bar-step"> |
|||
<div class="circle"> |
|||
<svg display="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"> |
|||
<use xlink:href="#svg-check"></use> |
|||
</svg> |
|||
</div> |
|||
<div class="progress-step-label"><div>Verify domain</div></div> |
|||
</div> |
|||
<div class="progress-bar-step"> |
|||
<div class="circle"> |
|||
<svg display="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"> |
|||
<use xlink:href="#svg-check"></use> |
|||
</svg> |
|||
</div> |
|||
<div class="progress-step-label"><div>Install certificates</div></div> |
|||
</div> |
|||
<!-- hide until the steps are all updated |
|||
<div class="progress-bar-step"> |
|||
<div class="circle"> |
|||
<svg display="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"> |
|||
<use xlink:href="#svg-check"></use> |
|||
</svg> |
|||
</div> |
|||
<div class="progress-step-label"><div>Done</div></div> |
|||
</div> |
|||
--> |
|||
</div> |
|||
<div class="greenlock-logo-badge"><img src="./img/greenlock-mark-400x400.png"></div> |
|||
<div class="greenlock-name">Greenlock</div> |
|||
</div> |
|||
<div class="column-row"> |
|||
<form class="js-acme-form js-acme-form-domains"> |
|||
<h1><label>What's your domain?</label></h1> |
|||
<h4>Certificates are valid for 90 days. Renewal is free :)</h4> |
|||
<input class="js-acme-domains" type="text" placeholder="example.com,*.example.com" required> |
|||
<br> |
|||
<button type="submit">Next</button> |
|||
|
|||
<br> |
|||
<br> |
|||
<br> |
|||
<label><input class="js-acme-api-type" name="acme-api-type" type="radio" value="v02" checked required> |
|||
Production</label> |
|||
<label><input class="js-acme-api-type" name="acme-api-type" type="radio" value="staging-v02" required> |
|||
Testing</label> |
|||
<br> |
|||
<input class="js-acme-directory-url" type="url" placeholder="ACME directory url"> |
|||
</form> |
|||
|
|||
<!-- Step 2 Create Account --> |
|||
<form class="js-acme-form js-acme-form-account"> |
|||
<h1><label>What's your email?</label></h1> |
|||
<input class="js-acme-account-email acme-account-email" type="email" placeholder="john@doe.family" required> |
|||
<div class="checkbox-array"> |
|||
<label> |
|||
<input class="js-acme-account-tos" type="checkbox" required> |
|||
<svg class="icon-checked-box" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"> |
|||
<use xlink:href="#svg-checked"></use> |
|||
</svg> |
|||
<svg class="icon-unchecked-box" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"> |
|||
<use xlink:href="#svg-unchecked"></use> |
|||
</svg> |
|||
Agree to  <a class="js-acme-tos-url" target="acme-tos">Let's Encrypt™ Terms of Service</a>? |
|||
</label> |
|||
<label> |
|||
<input class="js-greenlock-account-tos" type="checkbox" required> |
|||
<svg class="icon-checked-box" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"> |
|||
<use xlink:href="#svg-checked"></use> |
|||
</svg> |
|||
<svg class="icon-unchecked-box" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"> |
|||
<use xlink:href="#svg-unchecked"></use> |
|||
</svg> |
|||
Agree to  <a class="js-gl-tos" target="_blank" href="/legal/#terms">Greenlock™ Terms of Service</a>? |
|||
</label> |
|||
</div> |
|||
<!-- |
|||
<a href="#">advanced (use existing account)</a> |
|||
<br> |
|||
<br> |
|||
--> |
|||
<button class="button-next" type="submit">Next</button> |
|||
<div class="email-usage"> |
|||
Why do we need your email? We link your SSL certificates to the |
|||
email you use so you can manage your certificates in the future, |
|||
and get important email updates about them. |
|||
</div> |
|||
</form> |
|||
|
|||
<!-- Step 3 Set Challanges --> |
|||
<form class="js-acme-form js-acme-form-challenges"> |
|||
|
|||
<h1>Let's verify your domain</h1> |
|||
<div class="js-acme-challenges"> |
|||
<div class="tabbed-selector"> |
|||
<label> |
|||
<input class="js-acme-challenge-type" name="acme-challenge-type" type="radio" value="http-01" checked required> |
|||
File Upload |
|||
<div></div> |
|||
</label> |
|||
<label> |
|||
<input class="js-acme-challenge-type" name="acme-challenge-type" type="radio" value="dns-01" required> |
|||
DNS Record |
|||
<div></div> |
|||
</label> |
|||
</div> |
|||
<div class="js-acme-verification-http-01"> |
|||
<h3>Upload this file</h3> |
|||
<div class="http-verification-info file-preview"> |
|||
<div class="paper-fold"></div> |
|||
<div> |
|||
<div class="file-ver-info-header">FILENAME</div> |
|||
<pre class="js-acme-ver-file-location">...loading</pre> |
|||
</div> |
|||
<hr> |
|||
<div> |
|||
<div class="file-ver-info-header">CONTENTS</div> |
|||
<pre class="js-acme-ver-content">...loading</pre> |
|||
</div> |
|||
</div> |
|||
<div class="download-file"> |
|||
<svg class="mdicon icon-download" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"> |
|||
<use xlink:href="#svg-download"></use> |
|||
</svg> |
|||
<a class="js-download-verify-link" href="data:text/octet-stream;base64,SGVsbG8gV29ybGQuLi4=" download="hello.txt" target="_blank"> |
|||
Download |
|||
</a> |
|||
</div> |
|||
<h3>To this location</h3> |
|||
<div class="js-acme-ver-uri" class="acme-ver-uri">..loading</div> |
|||
</div> |
|||
<div class="js-acme-verification-dns-01"> |
|||
<h3>Set this DNS Record</h3> |
|||
<div class="acme-ver-dns-label">Hostname</div> |
|||
<div class="js-acme-ver-hostname">loading...</div> |
|||
<div class="acme-ver-dns-label">TXT Host</div> |
|||
<div class="js-acme-ver-txt-host">loading...</div> |
|||
<div class="acme-ver-dns-label">TXT Value</div> |
|||
<div class="js-acme-ver-txt-value">loading...</div> |
|||
</div> |
|||
|
|||
</div> |
|||
<div class="js-acme-wildcard-challenges"> |
|||
<div class="js-acme-wildcard"> |
|||
<div class="js-acme-verification-wildcard"> |
|||
<h3>Set this DNS Record</h3> |
|||
<div class="acme-ver-dns-label">Hostname</div> |
|||
<div class="js-acme-ver-hostname">loading...</div> |
|||
<div class="acme-ver-dns-label">TXT Host</div> |
|||
<div class="js-acme-ver-txt-host">loading...</div> |
|||
<div class="acme-ver-dns-label">TXT Value</div> |
|||
<div class="js-acme-ver-txt-value">loading...</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
|
|||
<button class="button-next" type="submit">Next</button> |
|||
</form> |
|||
|
|||
<!-- Step 4 Process Challanges --> |
|||
<form class="js-acme-form js-acme-form-poll"> |
|||
Verifying Domains... (give us 5 seconds or so...) |
|||
|
|||
<!-- |
|||
<table class="js-acme-table-verifying"> |
|||
<thead> |
|||
<tr> |
|||
<th>Hostname</th> |
|||
<th>Type</th> |
|||
<th>Pass</th> |
|||
</tr> |
|||
</thead> |
|||
<tbody> |
|||
<tr> |
|||
<td>example.com</td> |
|||
<td>http-01</td> |
|||
<td>-</td> |
|||
</tr> |
|||
</tbody> |
|||
</table> |
|||
|
|||
<a href="#">advanced (use existing keypair for domain)</a> |
|||
|
|||
<button type="submit">Next</button> |
|||
--> |
|||
</form> |
|||
|
|||
<!-- Step 5 Get Certs --> |
|||
<form class="js-acme-form js-acme-form-download"> |
|||
<div class="cert-download-container"> |
|||
<h2><label>privkey.pem</label></h2> |
|||
<div class="acme-result-privkey file-preview"> |
|||
<div class="paper-fold"></div> |
|||
<pre id="js-privkey"> |
|||
</pre> |
|||
</div> |
|||
<div class="download-file"> |
|||
<svg class="mdicon icon-download" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"> |
|||
<use xlink:href="#svg-download"></use> |
|||
</svg> |
|||
<a id="js-download-privkey-link" href="data:text/octet-stream;base64,SGVsbG8gV29ybGQuLi4=" download="privkey.pem" target="_blank"> |
|||
Download |
|||
</a> |
|||
</div> |
|||
<h2><label>fullchain.pem</label></h2> |
|||
<div class="acme-result-fullchain file-preview"> |
|||
<div class="paper-fold"></div> |
|||
<pre id="js-fullchain"> |
|||
</pre> |
|||
</div> |
|||
<div class="download-file"> |
|||
<svg class="mdicon icon-download" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"> |
|||
<use xlink:href="#svg-download"></use> |
|||
</svg> |
|||
<a id="js-download-fullchain-link" href="data:text/octet-stream;base64,SGVsbG8gV29ybGQuLi4=" download="fullchain.pem" target="_blank"> |
|||
Download |
|||
</a> |
|||
</div> |
|||
<div> |
|||
<h3>node.js https server example</h3> |
|||
<pre><code>'use strict'; |
|||
|
|||
var https = require('https'); |
|||
var server = https.createServer({ |
|||
key: require('fs').readFileSync('./privkey.pem') |
|||
, cert: require('fs').readFileSync('./fullchain.pem') |
|||
}, function (req, res) { |
|||
res.end("Hello, World!"); |
|||
}).listen(443, function () { |
|||
console.log('Listening on', this.address()); |
|||
}) |
|||
</code></pre> |
|||
</div> |
|||
|
|||
<!-- |
|||
TODO |
|||
<label>cert.pem</label> |
|||
<textarea class="js-cert">-</textarea> |
|||
|
|||
<label>chain.pem</label> |
|||
<textarea class="js-chain">-</textarea> |
|||
|
|||
<button type="button">Download SSL Certificates</button> |
|||
<br> |
|||
<a href="#">Advanced (copy and paste)</a> |
|||
<br> |
|||
<button type="submit">Start Over</button> |
|||
--> |
|||
</form> |
|||
</div> |
|||
|
|||
<div><small><center> |
|||
<div> |
|||
A <a href="https://rootprojects.org/" target="_blank">Root</a> Project |
|||
| <a href="https://git.coolaj86.com/coolaj86/greenlock.html" target="_blank">View Source</a> (git) |
|||
| <a href="https://rootprojects.org/legal/#terms" target="_blank">Terms of Service</a> |
|||
| <a href="https://rootprojects.org/legal/#privacy" target="_blank">Privacy Policy</a> |
|||
</div> |
|||
<!-- or |
|||
<pre><code>git clone https://git.coolaj86.com/coolaj86/greenlock.html.git</code></pre> |
|||
Or view the live site code (same as live-site branch): |
|||
<pre><code>wget https://greenlock.domains --mirror --convert-links --adjust-extension --page-requisites --no-parent</code></pre> |
|||
--> |
|||
</center></small></div> |
|||
<br> |
|||
|
|||
|
|||
<script src="./js/bacme.js"></script> |
|||
<script src="./js/app.js"></script> |
|||
|
|||
<script src="./js/pkijs.org/v1.3.33/common.js"></script> |
|||
<script src="./js/pkijs.org/v1.3.33/asn1.js"></script> |
|||
<script src="./js/pkijs.org/v1.3.33/x509_schema.js"></script> |
|||
<script src="./js/pkijs.org/v1.3.33/x509_simpl.js"></script> |
|||
<script src="./js/browser-csr/v1.0.0-alpha/csr.js"></script> |
|||
|
|||
<!-- Global site tag (gtag.js) - Google Analytics --> |
|||
<script async src="https://www.googletagmanager.com/gtag/js?id=UA-118745161-2"></script> |
|||
<script> |
|||
window.dataLayer = window.dataLayer || []; |
|||
function gtag(){dataLayer.push(arguments);} |
|||
gtag('js', new Date()); |
|||
|
|||
gtag('config', 'UA-118745161-2'); |
|||
</script> |
|||
</div> |
|||
</div> |
|||
</body> |
|||
</html> |
@ -0,0 +1,670 @@ |
|||
(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; |
|||
}()); |
File diff suppressed because it is too large
@ -0,0 +1,494 @@ |
|||
(function () { |
|||
'use strict'; |
|||
|
|||
/*global URLSearchParams,Headers*/ |
|||
var VERSION = '2'; |
|||
// ACME recommends ECDSA P-256, but RSA 2048 is still required by some old servers (like what replicated.io uses )
|
|||
// ECDSA P-384, P-521, and RSA 3072, 4096 are NOT recommend standards (and not properly supported)
|
|||
var BROWSER_SUPPORTS_RSA; |
|||
var ECDSA_OPTS = { kty: 'EC', namedCurve: 'P-256' }; |
|||
var RSA_OPTS = { kty: 'RSA', modulusLength: 2048 }; |
|||
var Promise = window.Promise; |
|||
var Keypairs = window.Keypairs; |
|||
var ACME = window.ACME; |
|||
var CSR = window.CSR; |
|||
var $qs = function (s) { return window.document.querySelector(s); }; |
|||
var $qsa = function (s) { return window.document.querySelectorAll(s); }; |
|||
var acme; |
|||
var accountStuff; |
|||
var info = {}; |
|||
var steps = {}; |
|||
var i = 1; |
|||
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); |
|||
} |
|||
|
|||
function hideForms() { |
|||
$qsa('.js-acme-form').forEach(function (el) { |
|||
el.hidden = true; |
|||
}); |
|||
} |
|||
|
|||
function updateProgress(currentStep) { |
|||
var progressSteps = $qs("#js-progress-bar").children; |
|||
var j; |
|||
for (j = 0; j < progressSteps.length; j += 1) { |
|||
if (j < currentStep) { |
|||
progressSteps[j].classList.add("js-progress-step-complete"); |
|||
progressSteps[j].classList.remove("js-progress-step-started"); |
|||
} else if (j === currentStep) { |
|||
progressSteps[j].classList.remove("js-progress-step-complete"); |
|||
progressSteps[j].classList.add("js-progress-step-started"); |
|||
} else { |
|||
progressSteps[j].classList.remove("js-progress-step-complete"); |
|||
progressSteps[j].classList.remove("js-progress-step-started"); |
|||
} |
|||
} |
|||
} |
|||
|
|||
function 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."); |
|||
}); |
|||
} |
|||
|
|||
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() { |
|||
// fix previous browsers
|
|||
var isCurrent = (localStorage.getItem('version') === VERSION); |
|||
if (!isCurrent) { |
|||
localStorage.clear(); |
|||
localStorage.setItem('version', VERSION); |
|||
} |
|||
localStorage.setItem('version', VERSION); |
|||
|
|||
return testRsaSupport().then(function () { |
|||
console.info("[crypto] RSA is supported"); |
|||
BROWSER_SUPPORTS_RSA = true; |
|||
return BROWSER_SUPPORTS_RSA; |
|||
}).catch(function () { |
|||
console.warn("[crypto] RSA is NOT fully supported"); |
|||
BROWSER_SUPPORTS_RSA = false; |
|||
return BROWSER_SUPPORTS_RSA; |
|||
}); |
|||
} |
|||
|
|||
function getServerKeypair() { |
|||
var sortedAltnames = info.identifiers.map(function (ident) { return ident.value; }).sort().join(','); |
|||
var serverJwk = JSON.parse(localStorage.getItem('server:' + sortedAltnames) || 'null'); |
|||
if (serverJwk) { |
|||
return PromiseA.resolve(serverJwk); |
|||
} |
|||
|
|||
var keypairOpts; |
|||
// TODO allow for user preference
|
|||
if (BROWSER_SUPPORTS_RSA) { |
|||
keypairOpts = RSA_OPTS; |
|||
} else { |
|||
keypairOpts = ECDSA_OPTS; |
|||
} |
|||
|
|||
return Keypairs.generate(RSA_OPTS).catch(function (err) { |
|||
console.error("[Error] Keypairs.generate(" + JSON.stringify(RSA_OPTS) + "):"); |
|||
throw err; |
|||
}).then(function (pair) { |
|||
localStorage.setItem('server:'+sortedAltnames, JSON.stringify(pair.private)); |
|||
return pair.private; |
|||
}); |
|||
} |
|||
|
|||
function getAccountKeypair(email) { |
|||
var json = localStorage.getItem('account:'+email); |
|||
if (json) { |
|||
return Promise.resolve(JSON.parse(json)); |
|||
} |
|||
|
|||
return Keypairs.generate(ECDSA_OPTS).catch(function (err) { |
|||
console.warn("[Error] Keypairs.generate(" + JSON.stringify(ECDSA_OPTS) + "):\n", err); |
|||
return Keypairs.generate(RSA_OPTS).catch(function (err) { |
|||
console.error("[Error] Keypairs.generate(" + JSON.stringify(RSA_OPTS) + "):"); |
|||
throw err; |
|||
}); |
|||
}).then(function (pair) { |
|||
localStorage.setItem('account:'+email, JSON.stringify(pair.private)); |
|||
return pair.private; |
|||
}); |
|||
} |
|||
|
|||
function updateChallengeType() { |
|||
/*jshint validthis: true*/ |
|||
var input = this || Array.prototype.filter.call( |
|||
$qsa('.js-acme-challenge-type'), function ($el) { return $el.checked; } |
|||
)[0]; |
|||
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; |
|||
} |
|||
} |
|||
|
|||
function saveContact(email, domains) { |
|||
// to be used for good, not evil
|
|||
return window.fetch('https://api.rootprojects.org/api/rootprojects.org/public/community', { |
|||
method: 'POST' |
|||
, cors: true |
|||
, headers: new Headers({ 'Content-Type': 'application/json' }) |
|||
, body: JSON.stringify({ |
|||
address: email |
|||
, project: 'greenlock-domains@rootprojects.org' |
|||
, timezone: new Intl.DateTimeFormat().resolvedOptions().timeZone |
|||
, domain: domains.join(',') |
|||
}) |
|||
}).catch(function (err) { |
|||
console.error(err); |
|||
}); |
|||
} |
|||
|
|||
steps[1] = function () { |
|||
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; } |
|||
}); |
|||
|
|||
var acmeDirectoryUrl = $qs('.js-acme-directory-url').value; |
|||
acme = ACME.create({ Keypairs: Keypairs, CSR: CSR }); |
|||
return acme.init(acmeDirectoryUrl).then(function (directory) { |
|||
$qs('.js-acme-tos-url').href = directory.meta.termsOfService; |
|||
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 ping with version and account creation
|
|||
setTimeout(saveContact, 100, email, info.identifiers.map(function (ident) { return ident.value; })); |
|||
|
|||
function checkTos(tos) { |
|||
if (info.agree) { |
|||
return tos; |
|||
} else { |
|||
return ''; |
|||
} |
|||
} |
|||
|
|||
return getAccountKeypair(email).then(function (jwk) { |
|||
// TODO save account id rather than always retrieving it?
|
|||
return acme.accounts.create({ |
|||
email: email |
|||
, agreeToTerms: checkTos |
|||
, accountKeypair: { privateKeyJwk: jwk } |
|||
}).then(function (account) { |
|||
console.log("account created result:", account); |
|||
accountStuff.account = account; |
|||
accountStuff.privateJwk = jwk; |
|||
accountStuff.email = email; |
|||
accountStuff.acme = acme; // TODO XXX remove
|
|||
}).catch(function (err) { |
|||
console.error("A bad thing happened:"); |
|||
console.error(err); |
|||
window.alert(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; |
|||
var account = accountStuff.account; |
|||
|
|||
return acme.orders.create({ |
|||
account: account |
|||
, accountKeypair: { privateKeyJwk: jwk } |
|||
, identifiers: info.identifiers |
|||
}).then(function (order) { |
|||
return acme.orders.create({ |
|||
signedOrder: signedOrder |
|||
}).then(function (order) { |
|||
accountStuff.order = order; |
|||
var claims = order.challenges; |
|||
console.log('claims:'); |
|||
console.log(claims); |
|||
|
|||
var obj = { 'dns-01': [], 'http-01': [], 'wildcard': [] }; |
|||
info.challenges = obj; |
|||
var map = { |
|||
'http-01': '.js-acme-verification-http-01' |
|||
, 'dns-01': '.js-acme-verification-dns-01' |
|||
, 'wildcard': '.js-acme-verification-wildcard' |
|||
}; |
|||
options.challengePriority = [ 'http-01', 'dns-01' ]; |
|||
|
|||
// 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 () { |
|||
options.challengeTypes = [ 'dns-01' ]; |
|||
if ('http-01' === $qs('.js-acme-challenge-type:checked').value) { |
|||
options.challengeTypes.unshift('http-01'); |
|||
} |
|||
console.log('primary challenge type is:', options.challengeTypes[0]); |
|||
|
|||
return getAccountKeypair(email).then(function (jwk) { |
|||
// for now just show the next page immediately (its a spinner)
|
|||
// TODO put a test challenge in the list
|
|||
// TODO warn about wait-time if DNS
|
|||
steps[i](); |
|||
return getServerKeypair().then(function () { |
|||
return acme.orders.finalize({ |
|||
account: accountStuff.account |
|||
, 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; |
|||
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](); |
|||
}); |
|||
}); |
|||
}).then(function () { |
|||
return submitForm(); |
|||
}); |
|||
}; |
|||
|
|||
// spinner
|
|||
steps[4] = function () { |
|||
updateProgress(1); |
|||
hideForms(); |
|||
$qs('.js-acme-form-poll').hidden = false; |
|||
}; |
|||
steps[4].submit = function () { |
|||
console.log('Congrats! Auto advancing...'); |
|||
|
|||
|
|||
}).catch(function (err) { |
|||
console.error(err.toString()); |
|||
window.alert("An error happened in the final step, but it's not your fault. Email aj@rootprojects.org and let him know."); |
|||
}); |
|||
}; |
|||
|
|||
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"; |
|||
|
|||
$qsa('.js-acme-api-type').forEach(function ($el) { |
|||
$el.addEventListener('change', updateApiType); |
|||
}); |
|||
|
|||
updateApiType(); |
|||
|
|||
$qsa('.js-acme-form').forEach(function ($el) { |
|||
$el.addEventListener('submit', function (ev) { |
|||
ev.preventDefault(); |
|||
submitForm(ev); |
|||
}); |
|||
}); |
|||
|
|||
|
|||
$qsa('.js-acme-challenge-type').forEach(function ($el) { |
|||
$el.addEventListener('change', updateChallengeType); |
|||
}); |
|||
|
|||
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; |
|||
|
|||
return testKeypairSupport().then(function (rsaSupport) { |
|||
if (rsaSupport) { |
|||
return true; |
|||
} |
|||
|
|||
return testRsaSupport().then(function () { |
|||
console.info('[crypto] RSA is supported'); |
|||
}).catch(function (err) { |
|||
console.error('[crypto] could not use either RSA nor EC.'); |
|||
console.error(err); |
|||
window.alert("Generating secure certificates requires a browser with cryptography support." |
|||
+ "Please consider a recent version of Chrome, Firefox, or Safari."); |
|||
throw err; |
|||
}); |
|||
}); |
|||
}()); |
@ -0,0 +1,263 @@ |
|||
body { |
|||
font-size: 18px; |
|||
font-family: Source Sans Pro, sans-serif; |
|||
margin: 0; |
|||
line-height: 1.33; |
|||
color: #1a1a1a; |
|||
} |
|||
|
|||
h1 { |
|||
text-align: center; |
|||
font-size: 1.77777778em; |
|||
} |
|||
|
|||
a { |
|||
color: #1a1a1a; |
|||
} |
|||
|
|||
input[type=email], input[type=text] { |
|||
font-size: 1em; |
|||
padding: 0.444444444em 0.888889em; |
|||
width: 100%; |
|||
border: solid 1px #d9d9d9; |
|||
border-radius: 2px; |
|||
} |
|||
|
|||
pre { |
|||
margin: 0; |
|||
font-family: Source Code Pro, monospace; |
|||
} |
|||
|
|||
.column-row { |
|||
width: 22.222222em; |
|||
} |
|||
|
|||
.column-container { |
|||
display: flex; |
|||
flex-direction: column; |
|||
align-items: center; |
|||
} |
|||
|
|||
.progress-bar { |
|||
height: 0; |
|||
border: solid 1px #5bc17f; |
|||
background-color: #5bc17f; |
|||
display: flex; |
|||
justify-content: space-between; |
|||
align-items: center; |
|||
width: 22em; |
|||
margin: 1.388888889em auto; |
|||
} |
|||
|
|||
.greenlock-logo-badge > img { |
|||
width: 100%; |
|||
} |
|||
|
|||
.greenlock-logo-badge { |
|||
display: inline-block; |
|||
border: solid 1px #d9d9d9; |
|||
border-radius: 500px; |
|||
width: 5.333333333em; |
|||
height: 5.333333333em; |
|||
margin-top: 4.277777778em; |
|||
} |
|||
|
|||
.header-row { |
|||
text-align: center; |
|||
} |
|||
|
|||
.progress-bar-step { |
|||
position: relative; |
|||
margin: -0.722222222em -0.166666667em; |
|||
display: inline-block; |
|||
background-color: white; |
|||
/* border-radius: 100%; */ |
|||
padding: 0 0.111111em; |
|||
} |
|||
|
|||
.progress-bar-step > .circle { |
|||
content: ""; |
|||
display: inline-block; |
|||
border: solid 0.111111111em #5bc17f; |
|||
width: 0.888888889em; |
|||
height: 0.888888889em; |
|||
border-radius: 100%; |
|||
background: white; |
|||
} |
|||
|
|||
.progress-step-label { |
|||
text-align: center; |
|||
position: absolute; |
|||
left: 50%; |
|||
=: block font-size: ; |
|||
top: 139%; |
|||
font-size: 0.722222222em; |
|||
white-space: nowrap; |
|||
} |
|||
|
|||
.progress-step-label > div { |
|||
position: relative; |
|||
right: 50%; |
|||
} |
|||
|
|||
.greenlock-name { |
|||
color: #808080; |
|||
} |
|||
|
|||
.file-preview { |
|||
background: #f7f7f7; |
|||
position: relative; |
|||
font-size: 0.833333333em; |
|||
padding: 1.6em 2.9333em 1.6em 1.6em; |
|||
} |
|||
|
|||
.js-progress-step-complete > .circle, .js-progress-step-started > .circle { |
|||
background-color: #5bc17f; |
|||
} |
|||
|
|||
.progress-bar-step.js-progress-step-complete svg { |
|||
fill: white; |
|||
/* stroke: none; */ |
|||
display: initial; |
|||
} |
|||
|
|||
.checkbox-array { |
|||
display: flex; |
|||
flex-direction: column; |
|||
padding: 1em 0; |
|||
} |
|||
|
|||
.checkbox-array input[type=checkbox] { |
|||
opacity: 0; |
|||
position: absolute; |
|||
} |
|||
|
|||
.checkbox-array input[type=checkbox] ~ .icon-checked-box { |
|||
display: none; |
|||
} |
|||
|
|||
.checkbox-array input[type=checkbox] ~ .icon-unchecked-box { |
|||
display: initial; |
|||
} |
|||
|
|||
.checkbox-array input[type=checkbox]:checked ~ .icon-checked-box { |
|||
display: initial; |
|||
} |
|||
|
|||
.checkbox-array input[type=checkbox]:checked ~ .icon-unchecked-box { |
|||
display: none; |
|||
} |
|||
|
|||
.checkbox-array .icon-checked-box, .checkbox-array .icon-unchecked-box { |
|||
width: 1.333333333em; |
|||
fill: #5bc17f; |
|||
margin-right: 0.666666667em; |
|||
} |
|||
|
|||
.checkbox-array label { |
|||
display: flex; |
|||
height: 1.333333333em; |
|||
font-size: 0.833333333em; |
|||
margin: 0.4em 0; |
|||
} |
|||
|
|||
.checkbox-array input[type=checkbox]:focus ~ .icon-checked-box, .checkbox-array input[type=checkbox]:focus ~ .icon-unchecked-box { |
|||
background: #5bc17f52; |
|||
} |
|||
|
|||
.email-usage { |
|||
color: #666666; |
|||
font-size: 0.833333333em; |
|||
margin: 2em 0; |
|||
} |
|||
|
|||
.button-next { |
|||
width: 100%; |
|||
background-color: #5bc17f; |
|||
border: none; |
|||
font-size: 1em; |
|||
color: white; |
|||
padding: 0.44444em; |
|||
margin: 1em 0; |
|||
} |
|||
|
|||
.tabbed-selector label { |
|||
width: 50%; |
|||
padding: 0.5em 0; |
|||
} |
|||
|
|||
.tabbed-selector { |
|||
display: flex; |
|||
font-weight: bold; |
|||
text-align: center; |
|||
} |
|||
|
|||
.tabbed-selector input[type=radio] { |
|||
display: none; |
|||
} |
|||
|
|||
.download-file svg { |
|||
fill: #5bc17f; |
|||
width: 1.333333333em; |
|||
} |
|||
|
|||
.download-file a { |
|||
color: #5bc17f; |
|||
} |
|||
|
|||
.mdicon { |
|||
position: relative; |
|||
top: 0.4em; |
|||
} |
|||
|
|||
.http-verification-info { |
|||
padding-right: 6.933333333em; |
|||
} |
|||
|
|||
.paper-fold { |
|||
position: absolute; |
|||
width: 2em; |
|||
height: 2em; |
|||
border-left: solid #d9d9d9 1px; |
|||
border-bottom: solid #d9d9d9 1px; |
|||
right: 0; |
|||
top: 0; |
|||
background: linear-gradient(45deg, #f7f7f7 0%,#f7f7f7 50%,#ffffff 50%,#ffffff 100%); |
|||
} |
|||
|
|||
.file-ver-info-header { |
|||
color: #808080; |
|||
} |
|||
|
|||
.http-verification-info hr { |
|||
border: none; |
|||
border-bottom: solid 1px #d9d9d9; |
|||
} |
|||
|
|||
.acme-ver-uri { |
|||
word-break: break-all; |
|||
margin: auto; |
|||
} |
|||
|
|||
.acme-ver-dns-label { |
|||
margin: 1.777777778em 0 0.444444444em 0; |
|||
border-bottom: solid 1px #d9d9d9; |
|||
font-weight: bold; |
|||
padding-bottom: 0.166666667em; |
|||
} |
|||
|
|||
.tabbed-selector input[type="radio"]:checked ~ div { |
|||
border: solid 1px #5bc17f; |
|||
background-color: #5bc17f; |
|||
} |
|||
|
|||
.file-preview pre { |
|||
white-space: pre-line; |
|||
word-break: break-all; |
|||
} |
|||
|
|||
|
|||
.cert-download-container { |
|||
margin: 0 -31%; |
|||
} |
|||
|
Binary file not shown.
Binary file not shown.
Before Width: | Height: | Size: 3.8 KiB After Width: | Height: | Size: 3.8 KiB |
@ -1,227 +1,100 @@ |
|||
<html> |
|||
<head> |
|||
<title>Greenlock™</title> |
|||
<meta property="og:image" content="https://greenlock.ppl.family/img/greenlock-mark-400x400.png" /> |
|||
<meta property="og:image" content="https://greenlock.domains/img/greenlock-mark-400x400.png" /> |
|||
<link href="styles/main.css" rel="stylesheet"> |
|||
<style> |
|||
@font-face { |
|||
font-family: 'Source Sans Pro'; |
|||
font-style: normal; |
|||
font-display: block; |
|||
font-weight: 400; |
|||
src: local('Source Sans Pro Regular'), local('SourceSansPro-Regular'), url(./fonts/6xK3dSBYKcSV-LCoeQqfX1RYOo3qOK7l.woff2) format('woff2'); |
|||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; |
|||
} |
|||
@font-face { |
|||
font-family: 'Source Sans Pro'; |
|||
font-style: normal; |
|||
font-weight: 700; |
|||
font-display: block; |
|||
src: local('Source Sans Pro Bold'), local('SourceSansPro-Bold'), url(./fonts/6xKydSBYKcSV-LCoeQqfX1RYOo3ig4vwlxdu.woff2) format('woff2'); |
|||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; |
|||
} |
|||
</style> |
|||
<link rel="preload" href="./app/fonts/6xKydSBYKcSV-LCoeQqfX1RYOo3ig4vwlxdu.woff2" as="font" crossorigin="anonymous"> |
|||
<link rel="preload" href="./app/fonts/6xK3dSBYKcSV-LCoeQqfX1RYOo3qOK7l.woff2" as="font" crossorigin="anonymous"> |
|||
|
|||
<link rel="prefetch" href="./app/fonts/HI_SiYsKILxRpg3hIP6sJ7fM7PqlPevW.woff2" as="font" crossorigin="anonymous"> |
|||
<link rel="prefetch" href="./app/js/app.js"> |
|||
<link rel="prefetch" href="./app/js/bacme.js"> |
|||
<link rel="prefetch" href="./app/js/pkijs.org/v1.3.33/common.js"> |
|||
<link rel="prefetch" href="./app/js/pkijs.org/v1.3.33/asn1.js"> |
|||
<link rel="prefetch" href="./app/js/pkijs.org/v1.3.33/x509_schema.js"> |
|||
<link rel="prefetch" href="./app/js/pkijs.org/v1.3.33/x509_simpl.js"> |
|||
<link rel="prefetch" href="./app/js/browser-csr/v1.0.0-alpha/csr.js"> |
|||
</head> |
|||
<body hidden> |
|||
<img width="410px" src="img/greenlock-820x150.png"> |
|||
<div> |
|||
<br> |
|||
<h3>Greenlock™ - Instant, Free SSL Certificates via Let's Encrypt v2</h3> |
|||
<br> |
|||
<br> |
|||
<br> |
|||
<body class="js-app-ready"> |
|||
<script> |
|||
document.querySelector('body').classList.remove("js-app-ready"); |
|||
</script> |
|||
<div class="column-container wide"> |
|||
|
|||
<div class="column-row"> |
|||
<img src="img/greenlock-146.png"> |
|||
</div> |
|||
|
|||
<!-- Step 1 Choose Domain(s) --> |
|||
<form class="js-acme-form js-acme-form-domains"> |
|||
<h1><label>What's your domain?</label></h1> |
|||
<h4>Certificates are valid for 90 days. Renewal is free :)</h4> |
|||
<input class="js-acme-domains" type="text" placeholder="example.com,*.example.com" required> |
|||
<br> |
|||
<button type="submit">Next</button> |
|||
|
|||
<br> |
|||
<br> |
|||
<br> |
|||
<label><input class="js-acme-api-type" name="acme-api-type" type="radio" value="v02" checked required> |
|||
Production</label> |
|||
<label><input class="js-acme-api-type" name="acme-api-type" type="radio" value="staging-v02" required> |
|||
Testing</label> |
|||
<br> |
|||
<input class="js-acme-directory-url" type="url" placeholder="ACME directory url"> |
|||
</form> |
|||
|
|||
<!-- Step 2 Create Account --> |
|||
<form class="js-acme-form js-acme-form-account"> |
|||
<h1><label>What's your email?</label></h1> |
|||
<input class="js-acme-account-email" type="email" placeholder="john@doe.family" required> |
|||
<br> |
|||
<br> |
|||
<label><input class="js-acme-account-tos" type="checkbox" required> |
|||
Agree to <a class="js-acme-tos-url" target="acme-tos">Let's Encrypt™ Terms of Service</a>?</label> |
|||
<br> |
|||
<br> |
|||
<label><input class="js-greenlock-account-tos" type="checkbox" required> |
|||
Agree to <a class="js-gl-tos" target="_blank" href="./legal.html">Greenlock™ Terms of Service</a>?</label> |
|||
<br> |
|||
<br> |
|||
<!-- |
|||
<a href="#">advanced (use existing account)</a> |
|||
<br> |
|||
<br> |
|||
--> |
|||
<button type="submit">Next</button> |
|||
</form> |
|||
|
|||
<!-- Step 3 Set Challanges --> |
|||
<form class="js-acme-form js-acme-form-challenges"> |
|||
|
|||
<h1>How will you validate your domain?</h1> |
|||
<br> |
|||
<label><input class="js-acme-challenge-type" name="acme-challenge-type" type="radio" value="http-01" checked required> |
|||
File Upload to HTTP Web Server</label> |
|||
<br> |
|||
<label><input class="js-acme-challenge-type" name="acme-challenge-type" type="radio" value="dns-01" required> |
|||
TXT Records on DNS Name Server</label> |
|||
<br> |
|||
|
|||
<div class="js-acme-challenges"> |
|||
|
|||
<h2>Verify Domains & Sub-Domains</h2> |
|||
|
|||
<table class="js-acme-table-http-01"> |
|||
<thead> |
|||
<tr> |
|||
<th>Hostname</th> |
|||
<th>File Location</th> |
|||
<th>File Contents</th> |
|||
</tr> |
|||
</thead> |
|||
<tbody> |
|||
<tr> |
|||
<td>example.com</td> |
|||
<td>.well-known/acme-challenge/xxx</td> |
|||
<td>sec.ret</td> |
|||
</tr> |
|||
</tbody> |
|||
</table> |
|||
|
|||
<table class="js-acme-table-dns-01"> |
|||
<thead> |
|||
<tr> |
|||
<th>Hostname</th> |
|||
<th>TXT Host</th> |
|||
<th>TXT Value</th> |
|||
</tr> |
|||
</thead> |
|||
<tbody> |
|||
<tr> |
|||
<td>example.com</td> |
|||
<td>_acme-challenge.example.com</td> |
|||
<td>4A54</td> |
|||
</tr> |
|||
</tbody> |
|||
</table> |
|||
<div class="column-row"> |
|||
<h1>Get the green lock for your website</h1> |
|||
</div> |
|||
|
|||
<div class="js-acme-wildcard"> |
|||
<h2>Verify Wildcard Domains</h2> |
|||
|
|||
<table class="js-acme-table-wildcard"> |
|||
<thead> |
|||
<tr> |
|||
<th>Hostname</th> |
|||
<th>TXT Host</th> |
|||
<th>TXT Value</th> |
|||
</tr> |
|||
</thead> |
|||
<tbody> |
|||
<tr> |
|||
<td>example.com</td> |
|||
<td>_acme-challenge.example.com</td> |
|||
<td>4A54</td> |
|||
</tr> |
|||
</tbody> |
|||
</table> |
|||
<div class="column-row"> |
|||
<div class="js-javascript-warning"> |
|||
Greenlock will process the CSR in the browser and request the certificates directly from letsencrypt.org. Please enable Javascript before continuing. |
|||
</div> |
|||
|
|||
<button type="submit">Next</button> |
|||
</form> |
|||
|
|||
<!-- Step 4 Process Challanges --> |
|||
<form class="js-acme-form js-acme-form-poll"> |
|||
Verifying Domains... (give us 5 seconds or so...) |
|||
|
|||
<!-- |
|||
<table class="js-acme-table-verifying"> |
|||
<thead> |
|||
<tr> |
|||
<th>Hostname</th> |
|||
<th>Type</th> |
|||
<th>Pass</th> |
|||
</tr> |
|||
</thead> |
|||
<tbody> |
|||
<tr> |
|||
<td>example.com</td> |
|||
<td>http-01</td> |
|||
<td>-</td> |
|||
</tr> |
|||
</tbody> |
|||
</table> |
|||
|
|||
<a href="#">advanced (use existing keypair for domain)</a> |
|||
|
|||
<button type="submit">Next</button> |
|||
--> |
|||
</form> |
|||
|
|||
<!-- Step 5 Get Certs --> |
|||
<form class="js-acme-form js-acme-form-download"> |
|||
<div> |
|||
<h2><label>privkey.pem</label></h2> |
|||
<textarea cols="80" rows="10" class="js-privkey">-</textarea> |
|||
<form id="js-acme-form" action="./app/" method=> |
|||
<div class="domain-psuedo-input"> |
|||
<span class="secure-green">Secure</span> | <span class="secure-green">https:</span>//<input aria-label="domains to secure" id="acme-domains" type="text" name="acme-domains" placeholder="Your domain name" required> |
|||
</div> |
|||
|
|||
<button type="submit">Go</button> |
|||
<div class="domain-subtext">Domain, subdomain, or wildcard domain</div> |
|||
|
|||
<div class="acme-advanced-fields"> |
|||
<label><input name="acme-api-type" type="radio" value="v02" checked required> |
|||
Production |
|||
</label> |
|||
<label><input name="acme-api-type" type="radio" value="staging-v02" required> |
|||
Testing</label> |
|||
<input id="js-acme-api-url" type="url" placeholder="ACME directory url"> |
|||
<div> |
|||
<h2><label>fullchain.pem</label></h2> |
|||
<textarea cols="80" rows="60" class="js-fullchain">-</textarea> |
|||
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> |
|||
<h3>node.js https server example</h3> |
|||
<pre><code>'use strict'; |
|||
|
|||
var https = require('https'); |
|||
var server = https.createServer({ |
|||
key: require('fs').readFileSync('./privkey.pem') |
|||
, cert: require('fs').readFileSync('./fullchain.pem') |
|||
}, function (req, res) { |
|||
res.end("Hello, World!"); |
|||
}).listen(443, function () { |
|||
console.log('Listening on', this.address()); |
|||
}) |
|||
</code></pre> |
|||
</div> |
|||
|
|||
<!-- |
|||
TODO |
|||
<label>cert.pem</label> |
|||
<textarea class="js-cert">-</textarea> |
|||
|
|||
<label>chain.pem</label> |
|||
<textarea class="js-chain">-</textarea> |
|||
|
|||
<button type="button">Download SSL Certificates</button> |
|||
<br> |
|||
<a href="#">Advanced (copy and paste)</a> |
|||
<br> |
|||
<button type="submit">Start Over</button> |
|||
--> |
|||
</form> |
|||
|
|||
<br> |
|||
<br> |
|||
<br> |
|||
<div><small> |
|||
<h3></h3> |
|||
<a href="https://git.coolaj86.com/coolaj86/greenlock.html">View Source</a> (git) |
|||
</div> |
|||
<div class="column-row"> |
|||
<div class="why-you-need"> |
|||
<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. |
|||
</div> |
|||
</div> |
|||
<!-- or |
|||
<pre><code>git clone https://git.coolaj86.com/coolaj86/greenlock.html.git</code></pre> |
|||
Or view the live site code (same as live-site branch): |
|||
<pre><code>wget https://greenlock.ppl.family --mirror --convert-links --adjust-extension --page-requisites --no-parent</code></pre> |
|||
</small></div> |
|||
<pre><code>wget https://greenlock.domains --mirror --convert-links --adjust-extension --page-requisites --no-parent</code></pre> |
|||
--> |
|||
|
|||
<script src="./js/bacme.js"></script> |
|||
<script src="./js/app.js"></script> |
|||
|
|||
<script src="./js/pkijs.org/v1.3.33/common.js"></script> |
|||
<script src="./js/pkijs.org/v1.3.33/asn1.js"></script> |
|||
<script src="./js/pkijs.org/v1.3.33/x509_schema.js"></script> |
|||
<script src="./js/pkijs.org/v1.3.33/x509_simpl.js"></script> |
|||
<script src="./js/browser-csr/v1.0.0-alpha/csr.js"></script> |
|||
<!-- Global site tag (gtag.js) - Google Analytics --> |
|||
<script async src="https://www.googletagmanager.com/gtag/js?id=UA-118745161-2"></script> |
|||
<script> |
|||
window.dataLayer = window.dataLayer || []; |
|||
function gtag(){dataLayer.push(arguments);} |
|||
gtag('js', new Date()); |
|||
|
|||
gtag('config', 'UA-118745161-2'); |
|||
</script> |
|||
</div> |
|||
</body> |
|||
</html> |
|||
|
@ -1,14 +1,14 @@ |
|||
#!/bin/bash |
|||
|
|||
mkdir -p js/pkijs.org/v1.3.33/ |
|||
pushd js/pkijs.org/v1.3.33/ |
|||
mkdir -p app/js/pkijs.org/v1.3.33/ |
|||
pushd app/js/pkijs.org/v1.3.33/ |
|||
wget -c https://raw.githubusercontent.com/PeculiarVentures/PKI.js/41b63af760cacb565dd850fb3466ada4ca163eff/org/pkijs/common.js |
|||
wget -c https://raw.githubusercontent.com/PeculiarVentures/PKI.js/41b63af760cacb565dd850fb3466ada4ca163eff/org/pkijs/x509_schema.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 js/browser-csr/v1.0.0-alpha/ |
|||
pushd js/browser-csr/v1.0.0-alpha/ |
|||
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 |
|||
|
@ -0,0 +1,201 @@ |
|||
<h1>Greetings!</h1> |
|||
|
|||
<p>I, AJ ONeal, am not a big fan of legalize, but I am a fan of communicating |
|||
clearly. I hope that this accomplish both defining some legal boundaries 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>This is important because it is our intent to create sustainable open 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>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: |
|||
|
|||
<ul> |
|||
<li><a href="mailto:coolaj86@gmail.com">coolaj86@gmail.com</a></li> |
|||
<li><a href="tel:+13852360466">+1 (385) 236-0466</a></li> |
|||
<li><a href="http://coolaj86.com">https://coolaj86.com</a></li> |
|||
</ul> |
|||
|
|||
<h1>Contents</h1> |
|||
<p>Here's what I've worked through so far: |
|||
|
|||
<ul> |
|||
<li><a href="#greenlock">Greelock Domains</a></li> |
|||
<li><a href="#licensing">Licensing</a></li> |
|||
<li><a href="#terms">Terms of Service</a></li> |
|||
<li><a href="#trademark">Trademark</a></li> |
|||
<li><a href="#privacy">Privacy</a></li> |
|||
</ul> |
|||
|
|||
<h1 id="greenlock">Greenlock Domains™</h1> |
|||
|
|||
<p>Greenlock Domains is a service provided by |
|||
<em><a href="https://coolaj86.com">AJ</a>, Brian, |
|||
<a href="https://jshaver.net">John</a>, & Josh</em> |
|||
(collectively <a href="https://therootcompany.com">Root</a>) |
|||
for automated TLS, SSL, and HTTPS. |
|||
|
|||
<ul> |
|||
<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"> |
|||
https://git.coolaj86.com/coolaj86/greenlock-express.js</a></li> |
|||
|
|||
<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"> |
|||
https://git.coolaj86.com/coolaj86/greenlock.html</a></li> |
|||
</ul> |
|||
|
|||
<p>Greenlock Domains is an important product / service combo to us |
|||
because it's a huge milestone on the path to a more decentralized web. |
|||
We believe in <em>ownership</em> and <em>control</em> and we're |
|||
building a <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>If we don't do this, well, with the way the cloud is headed, |
|||
renting may be the only option in the future. |
|||
|
|||
<p>We need <em>Root</em> because we want ownership. |
|||
|
|||
<p>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. |
|||
|
|||
<h1 id="licensing">Licensing</h1> |
|||
|
|||
<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). |
|||
|
|||
<p>We typically use the MIT and Apache-2.0 licenses for libraries that we |
|||
actively want others to copy, modify, use and redistribute. |
|||
|
|||
<p>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>Although we do keep some of our software proprietary and we do 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>If at any time you feel that our Licensing is in conflict with our mission or values, |
|||
please let us know. |
|||
|
|||
<h1 id="terms">Terms of Service</h1> |
|||
|
|||
<p>We want to make the world a better place. |
|||
Everyone has a different definition of what "a better place" means, |
|||
so the purpose of our terms is to rule out some things that |
|||
we think makes the world (and particularly our world) a worse place: |
|||
|
|||
<p>You agree that you will use the Greenlock™ service, code, libraries, |
|||
documentation, etc (provided by <a href="#greenlock">us</a>) |
|||
primarily for securing network connections for yourself, your customers, |
|||
on your and your customer's devices on internets, intranets, and... other nets. |
|||
|
|||
<p>You agree that you will take reasonable measures to keep up-to-date with security |
|||
releases. |
|||
|
|||
<p>You agree to not use our products or services in a way that would cause unusual |
|||
or undue burden on our servers or services, our partners servers or services, or our |
|||
customers servers or services, or in a way that harms or misrepresents the reputation |
|||
or brand value (including causing brand confusion) of the aforementioned parties |
|||
(or really anybody). |
|||
|
|||
<p>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>If you have a use case that may be in violation of these terms (particularly |
|||
the part about undue burden), but you feel contributes to making the world a better |
|||
place, we're here to help (assuming it also 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>If at any time you feel that our Terms of Service are in conflict with our |
|||
mission or values, please let us know. |
|||
|
|||
<h1 id="trademark">Trademark</h1> |
|||
|
|||
<p>"Greenlock" and the "green G lock" mark are Trademarks of |
|||
<a href="https://coolaj86.com" target="_blank">AJ ONeal</a>. |
|||
|
|||
<p>We'll be coming out with a brand guide as to how you should use |
|||
the marks. In the meantime: don't change the proportions, colors |
|||
(excepting the case of greyscale and black and white). |
|||
|
|||
<p>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>It is appropriate use the name greenlock in a plugin for Greenlock™, |
|||
as long as it is clear that it is a community contribution. |
|||
|
|||
<p>If you create a "hard" fork of our code or any products or services, |
|||
you should give your fork its own name, and not use ours. |
|||
That sound, we gladly welcome your suggestiosn and pull requests. |
|||
|
|||
<p>If you mirror our code you should make it clear that it is a mirror |
|||
and link to the official repository. |
|||
in association with usand the disclose that you use Greenlock |
|||
|
|||
<p>If at any time you feel that our Trademark policies are in conflict with our |
|||
values, please let us know. |
|||
|
|||
<h1 id="privacy">Privacy Policy</h1> |
|||
|
|||
<p>What we collect and (more importantly) <em>Why</em>: |
|||
|
|||
<p><strong>Name</strong>: |
|||
<p>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><strong>Email</strong>: |
|||
<p>There are three main purposes for which we may use your email address: |
|||
|
|||
<p>1. A one-time outreach to ask if you were able to do what you intended to do. |
|||
We want to make a great product. Although open source projects 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>2. Security and legal notifications. It's important that we have a way to contact you |
|||
if we've made a mistake or discover a mistake that needs to be addressed. This |
|||
may include vulnerabilities as well as mandatory upgrades (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>3. Opt-in updates. Many of you want to know when we have significant 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><strong>Telemetry</strong>: |
|||
<p>We believe that the current open source model needs improvement - it often |
|||
relies heavily on large centralized platforms which aggregate a lot of user |
|||
information for the platform without appropriately targeting the relationship |
|||
between authors and users of projcts (i.e. npm, github, etc). We believe that |
|||
making open source sustainable means a greater focus on empowering authors |
|||
and users. We've learned from other projects (Caddy, 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>We may use telemetry about operating system, browser, node version, code version, |
|||
and other system-level information to better understand how we can serve our users (you) |
|||
and proactively solve problems that we might not 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><strong>Other</strong>: |
|||
<p>We also use Google Analytics on our web sites for basic functionality. |
|||
Other than that, nothing else comes to mind right now. |
|||
As we consider what we will do in the future, it will be measured against our mission and values. |
|||
We never want to come across as spammy or forceful. We want to do things that help us build |
|||
our brand, acknowledge our customers; things that are proactive, and that |
|||
promote sustainable source. |
|||
|
|||
<p>If at any time you feel that our Privacy policy is in conflict with our mission or values, |
|||
please let us know. |
|||
|
|||
<br> |
|||
<br> |
|||
<p>Copyright 2018 AJ ONeal |
@ -0,0 +1 @@ |
|||
../legal.html |
@ -0,0 +1,115 @@ |
|||
.column-row { |
|||
display: flex; |
|||
flex-direction: column; |
|||
text-align: center; |
|||
align-items: center; |
|||
} |
|||
|
|||
body { |
|||
position: relative; |
|||
margin-top: 5.777777778em; |
|||
min-height: 36em; |
|||
font-size: 18px; |
|||
font-family: 'Source Sans Pro', sans-serif; |
|||
font-stretch: normal; |
|||
line-height: 1.33; |
|||
letter-spacing: -0.4px; |
|||
color: #1a1a1a; |
|||
opacity: 0; |
|||
} |
|||
|
|||
h1 { |
|||
font-size: 2.666666667em; |
|||
max-width: 8em; |
|||
text-align: center; |
|||
} |
|||
|
|||
input { |
|||
font-size: 1em; |
|||
padding: 0.444444444em; |
|||
border: solid #d9d9d9 1px; |
|||
border-radius: 2px; |
|||
font-family: inherit; |
|||
} |
|||
|
|||
button { |
|||
padding: 0.444444444em 1.2em; |
|||
font-size: 1em; |
|||
background-color: #5bc17f; |
|||
border: solid 1px #5bc17f; |
|||
border-radius: 2px; |
|||
font-weight: normal; |
|||
font-stretch: normal; |
|||
letter-spacing: -0.4px; |
|||
font-family: inherit; |
|||
text-align: center; |
|||
color: white; |
|||
height: 40px; |
|||
line-height: 1.13; |
|||
} |
|||
|
|||
.acme-advanced-fields { |
|||
position: absolute; |
|||
bottom: 0; |
|||
padding: 1em; |
|||
text-align: center; |
|||
} |
|||
|
|||
.domain-subtext { |
|||
font-size: 0.833333333em; |
|||
color: #666; |
|||
text-align: center; |
|||
margin: 0.5em; |
|||
} |
|||
|
|||
input#acme-domains:before { |
|||
content: "Secure | https://"; |
|||
} |
|||
|
|||
.domain-psuedo-input { |
|||
display: inline-block; |
|||
margin-right: .6666667em; |
|||
border: solid #d9d9d9 1px; |
|||
border-radius: 2px; |
|||
padding: 0.44444444em; |
|||
color: #d9d9d9; |
|||
} |
|||
|
|||
input#acme-domains { |
|||
border: none; |
|||
padding: 0; |
|||
padding-right: 0; |
|||
width: 17.2222222em; |
|||
color: #222; |
|||
} |
|||
|
|||
input#acme-domains:focus { |
|||
outline: none; |
|||
} |
|||
|
|||
span.secure-green { |
|||
color: #5bc17f; |
|||
} |
|||
|
|||
.why-you-need { |
|||
width: 26.555556em; |
|||
} |
|||
|
|||
body.js-app-ready { |
|||
transition: opacity 0.2s; |
|||
opacity: 1; |
|||
} |
|||
|
|||
.acme-advanced-fields > * { |
|||
margin: 0 0.5em; |
|||
} |
|||
|
|||
.js-javascript-warning { |
|||
border: solid 1px red; |
|||
background-color: #ffc0cb40; |
|||
border-radius: 2px; |
|||
margin: 0.6em; |
|||
padding: 0.5em 1em; |
|||
width: 30em; |
|||
} |
|||
|
Loading…
Reference in new issue