Compare commits
	
		
			59 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| b5f0c30652 | |||
| 210a7877a0 | |||
| 8aadf6db41 | |||
| 8a873eeb1d | |||
| 059b63e5d0 | |||
| 513fb992c9 | |||
| 137990002a | |||
| 21d01a326c | |||
| 67294ae3eb | |||
| 6eb325186e | |||
| 5d4785ee19 | |||
| 0b50f7976f | |||
| 26a61da3a1 | |||
| ad288b1c95 | |||
| d5e6b4331f | |||
| d4bfc608a3 | |||
| 5a229ef27f | |||
| 506280f764 | |||
| 352e334b5d | |||
| f56dafad5d | |||
| 2ca4c984f8 | |||
| 11020cbf27 | |||
| da5b9f59c3 | |||
| 82aac8a233 | |||
| aa0444646b | |||
| 71de7e7b95 | |||
| fe849f426d | |||
| 51d10a57df | |||
| e236640098 | |||
| 8b6fddad75 | |||
| a4c2da82c5 | |||
| 8a0ccfa55f | |||
| df964d76c6 | |||
| 136c9c06fe | |||
| 2fb775a822 | |||
| c66f92d506 | |||
| cda26c98b5 | |||
| 8a0e582962 | |||
| b1328d938d | |||
| 613f1ba75e | |||
| 102829c852 | |||
| 1b8c2d99a2 | |||
| fad0739f68 | |||
| 1c39971be5 | |||
| 1dc1cae50a | |||
| 174c69234a | |||
| b416813a44 | |||
| fe530836c7 | |||
| 43f0fe9faa | |||
| c7bddfdec9 | |||
| 34acb707ac | |||
| 7af2671993 | |||
| 5db9c2010f | |||
| af887f27b9 | |||
| f17e8a4183 | |||
| dec3229340 | |||
| 7ce3adacb6 | |||
| 60b082788f | |||
| 426e28d1b2 | 
							
								
								
									
										4
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @ -1,2 +1,2 @@ | |||||||
| js/pkijs.org | app/js/pkijs.org | ||||||
| js/browser-csr | app/js/browser-csr | ||||||
|  | |||||||
							
								
								
									
										4
									
								
								.prettierrc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								.prettierrc
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,4 @@ | |||||||
|  | { | ||||||
|  |   "trailingComma": "none", | ||||||
|  |   "useTabs": true | ||||||
|  | } | ||||||
							
								
								
									
										17
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										17
									
								
								README.md
									
									
									
									
									
								
							| @ -1,12 +1,10 @@ | |||||||
| 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.ppl.family>. | This app is available at <https://greenlock.domains>. | ||||||
| 
 | 
 | ||||||
| We expect that our hosted version will meet all of yours needs. | We expect that our hosted version will meet all of yours needs. | ||||||
| If it doesn't, please open an issue to let us know why. | If it doesn't, please open an issue to let us know why. | ||||||
| @ -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™ is our trademark. If you do host your own copy of this app, | Greenlock™ 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. | ||||||
| 
 | 
 | ||||||
|  | |||||||
							
								
								
									
										1
									
								
								app/favicon.ico
									
									
									
									
									
										Symbolic link
									
								
							
							
						
						
									
										1
									
								
								app/favicon.ico
									
									
									
									
									
										Symbolic link
									
								
							| @ -0,0 +1 @@ | |||||||
|  | ../img/favicon-32x32.png | ||||||
							
								
								
									
										
											BIN
										
									
								
								app/fonts/6xK3dSBYKcSV-LCoeQqfX1RYOo3qOK7l.woff2
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								app/fonts/6xK3dSBYKcSV-LCoeQqfX1RYOo3qOK7l.woff2
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								app/fonts/6xKydSBYKcSV-LCoeQqfX1RYOo3ig4vwlxdu.woff2
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								app/fonts/6xKydSBYKcSV-LCoeQqfX1RYOo3ig4vwlxdu.woff2
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								app/fonts/HI_SiYsKILxRpg3hIP6sJ7fM7PqlPevW.woff2
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								app/fonts/HI_SiYsKILxRpg3hIP6sJ7fM7PqlPevW.woff2
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| Before Width: | Height: | Size: 6.1 KiB After Width: | Height: | Size: 6.1 KiB | 
							
								
								
									
										556
									
								
								app/index.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										556
									
								
								app/index.html
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,556 @@ | |||||||
|  | <!DOCTYPE html> | ||||||
|  | <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/bluecrypt-acme.js" as="script" /> | ||||||
|  | 		<link rel="preload" href="./js/greenlock.js" as="script" /> | ||||||
|  | 	</head> | ||||||
|  | 	<body hidden> | ||||||
|  | 		<!-- let's define our SVG that we will reuse --> | ||||||
|  | 
 | ||||||
|  | 		<svg | ||||||
|  | 			xmlns="http://www.w3.org/2000/svg" | ||||||
|  | 			width="0" | ||||||
|  | 			height="0" | ||||||
|  | 			viewbox="0 0 24 24" | ||||||
|  | 		> | ||||||
|  | 			<defs> | ||||||
|  | 				<g id="svg-check"> | ||||||
|  | 					<path fill="none" d="M0 0h24v24H0z" /> | ||||||
|  | 					<path d="M9 16.2L4.8 12l-1.4 1.4L9 19 21 7l-1.4-1.4L9 16.2z" /> | ||||||
|  | 				</g> | ||||||
|  | 				<g id="svg-checked"> | ||||||
|  | 					<path d="M0 0h24v24H0z" fill="none" /> | ||||||
|  | 					<path | ||||||
|  | 						d="M19 3H5c-1.11 0-2 .9-2 2v14c0 1.1.89 2 2 2h14c1.11 0 2-.9 2-2V5c0-1.1-.89-2-2-2zm-9 14l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z" | ||||||
|  | 					/> | ||||||
|  | 				</g> | ||||||
|  | 				<g id="svg-unchecked"> | ||||||
|  | 					<path | ||||||
|  | 						d="M19 5v14H5V5h14m0-2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2z" | ||||||
|  | 					/> | ||||||
|  | 					<path d="M0 0h24v24H0z" fill="none" /> | ||||||
|  | 				</g> | ||||||
|  | 				<g id="svg-download"> | ||||||
|  | 					<path d="M19 9h-4V3H9v6H5l7 7 7-7zM5 18v2h14v-2H5z" /> | ||||||
|  | 					<path d="M0 0h24v24H0z" fill="none" /> | ||||||
|  | 				</g> | ||||||
|  | 			</defs> | ||||||
|  | 		</svg> | ||||||
|  | 		<div class="column-container wide"> | ||||||
|  | 			<div class="header-row column-row"> | ||||||
|  | 				<div id="js-progress-bar" class="progress-bar"> | ||||||
|  | 					<div class="progress-bar-step"> | ||||||
|  | 						<div class="circle"> | ||||||
|  | 							<svg | ||||||
|  | 								display="none" | ||||||
|  | 								xmlns="http://www.w3.org/2000/svg" | ||||||
|  | 								viewbox="0 0 24 24" | ||||||
|  | 							> | ||||||
|  | 								<use xlink:href="#svg-check"></use> | ||||||
|  | 							</svg> | ||||||
|  | 						</div> | ||||||
|  | 						<div class="progress-step-label"><div>Details</div></div> | ||||||
|  | 					</div> | ||||||
|  | 					<div class="progress-bar-step"> | ||||||
|  | 						<div class="circle"> | ||||||
|  | 							<svg | ||||||
|  | 								display="none" | ||||||
|  | 								xmlns="http://www.w3.org/2000/svg" | ||||||
|  | 								viewbox="0 0 24 24" | ||||||
|  | 							> | ||||||
|  | 								<use xlink:href="#svg-check"></use> | ||||||
|  | 							</svg> | ||||||
|  | 						</div> | ||||||
|  | 						<div class="progress-step-label"><div>Verify domain</div></div> | ||||||
|  | 					</div> | ||||||
|  | 					<div class="progress-bar-step"> | ||||||
|  | 						<div class="circle"> | ||||||
|  | 							<svg | ||||||
|  | 								display="none" | ||||||
|  | 								xmlns="http://www.w3.org/2000/svg" | ||||||
|  | 								viewbox="0 0 24 24" | ||||||
|  | 							> | ||||||
|  | 								<use xlink:href="#svg-check"></use> | ||||||
|  | 							</svg> | ||||||
|  | 						</div> | ||||||
|  | 						<div class="progress-step-label"> | ||||||
|  | 							<div>Install certificates</div> | ||||||
|  | 						</div> | ||||||
|  | 					</div> | ||||||
|  | 					<!-- hide until the steps are all updated | ||||||
|  |           <div class="progress-bar-step"> | ||||||
|  |             <div class="circle"> | ||||||
|  |               <svg display="none" xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24"> | ||||||
|  |                 <use xlink:href="#svg-check"></use> | ||||||
|  |               </svg> | ||||||
|  |             </div> | ||||||
|  |             <div class="progress-step-label"><div>Done</div></div> | ||||||
|  |           </div> | ||||||
|  |           --> | ||||||
|  | 				</div> | ||||||
|  | 				<div class="greenlock-logo-badge"> | ||||||
|  | 					<img src="./img/greenlock-mark-400x400.png" /> | ||||||
|  | 				</div> | ||||||
|  | 				<div class="greenlock-name">Greenlock</div> | ||||||
|  | 			</div> | ||||||
|  | 			<div class="column-row"> | ||||||
|  | 				<form class="js-acme-form js-acme-form-domains"> | ||||||
|  | 					<h1><label>What's your domain?</label></h1> | ||||||
|  | 					<h4>Certificates are valid for 90 days. Renewal is free :)</h4> | ||||||
|  | 					<input | ||||||
|  | 						class="js-acme-domains" | ||||||
|  | 						type="text" | ||||||
|  | 						placeholder="example.com,*.example.com" | ||||||
|  | 						required | ||||||
|  | 					/> | ||||||
|  | 					<br /> | ||||||
|  | 					<button type="submit">Next</button> | ||||||
|  | 
 | ||||||
|  | 					<br /> | ||||||
|  | 					<br /> | ||||||
|  | 					<br /> | ||||||
|  | 					<label | ||||||
|  | 						><input | ||||||
|  | 							class="js-acme-api-type" | ||||||
|  | 							name="acme-api-type" | ||||||
|  | 							type="radio" | ||||||
|  | 							value="v02" | ||||||
|  | 							checked | ||||||
|  | 							required | ||||||
|  | 						/> | ||||||
|  | 						Production</label | ||||||
|  | 					> | ||||||
|  | 					<label | ||||||
|  | 						><input | ||||||
|  | 							class="js-acme-api-type" | ||||||
|  | 							name="acme-api-type" | ||||||
|  | 							type="radio" | ||||||
|  | 							value="staging-v02" | ||||||
|  | 							required | ||||||
|  | 						/> | ||||||
|  | 						Testing</label | ||||||
|  | 					> | ||||||
|  | 					<br /> | ||||||
|  | 					<input | ||||||
|  | 						class="js-acme-directory-url" | ||||||
|  | 						type="url" | ||||||
|  | 						placeholder="ACME directory url" | ||||||
|  | 					/> | ||||||
|  | 				</form> | ||||||
|  | 
 | ||||||
|  | 				<!-- Step 2 Create Account --> | ||||||
|  | 				<form class="js-acme-form js-acme-form-account"> | ||||||
|  | 					<h1><label>What's your email?</label></h1> | ||||||
|  | 					<input | ||||||
|  | 						class="js-acme-account-email acme-account-email" | ||||||
|  | 						type="email" | ||||||
|  | 						placeholder="john@doe.family" | ||||||
|  | 						required | ||||||
|  | 					/> | ||||||
|  | 					<div class="checkbox-array"> | ||||||
|  | 						<label> | ||||||
|  | 							<input class="js-acme-account-tos" type="checkbox" required /> | ||||||
|  | 							<svg | ||||||
|  | 								class="icon-checked-box" | ||||||
|  | 								xmlns="http://www.w3.org/2000/svg" | ||||||
|  | 								viewbox="0 0 24 24" | ||||||
|  | 							> | ||||||
|  | 								<use xlink:href="#svg-checked"></use> | ||||||
|  | 							</svg> | ||||||
|  | 							<svg | ||||||
|  | 								class="icon-unchecked-box" | ||||||
|  | 								xmlns="http://www.w3.org/2000/svg" | ||||||
|  | 								viewbox="0 0 24 24" | ||||||
|  | 							> | ||||||
|  | 								<use xlink:href="#svg-unchecked"></use> | ||||||
|  | 							</svg> | ||||||
|  | 							Agree to  <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 js-account-next" type="submit"> | ||||||
|  | 						Next | ||||||
|  | 					</button> | ||||||
|  | 					<div class="email-usage"> | ||||||
|  | 						Why do we need your email? We link your SSL certificates to the | ||||||
|  | 						email you use so that you'll be notified before the certificate | ||||||
|  | 						expires and so you can manage your certificates in the future. | ||||||
|  | 					</div> | ||||||
|  | 				</form> | ||||||
|  | 
 | ||||||
|  | 				<!-- Step 3 Set Challanges --> | ||||||
|  | 				<form class="js-acme-form js-acme-form-challenges"> | ||||||
|  | 					<h1>Let's verify your domain</h1> | ||||||
|  | 					<div class="js-acme-challenges"> | ||||||
|  | 						<div class="tabbed-selector"> | ||||||
|  | 							<label> | ||||||
|  | 								<input | ||||||
|  | 									class="js-acme-challenge-type" | ||||||
|  | 									name="acme-challenge-type" | ||||||
|  | 									type="radio" | ||||||
|  | 									value="http-01" | ||||||
|  | 									checked | ||||||
|  | 									required | ||||||
|  | 								/> | ||||||
|  | 								File Upload | ||||||
|  | 								<div></div> | ||||||
|  | 							</label> | ||||||
|  | 							<label> | ||||||
|  | 								<input | ||||||
|  | 									class="js-acme-challenge-type" | ||||||
|  | 									name="acme-challenge-type" | ||||||
|  | 									type="radio" | ||||||
|  | 									value="dns-01" | ||||||
|  | 									required | ||||||
|  | 								/> | ||||||
|  | 								DNS Record | ||||||
|  | 								<div></div> | ||||||
|  | 							</label> | ||||||
|  | 						</div> | ||||||
|  | 						<div> | ||||||
|  | 							<div class="js-acme-verification-http-01"> | ||||||
|  | 								<h3>Upload each file</h3> | ||||||
|  | 								<div class="js-acme-http"> | ||||||
|  | 									<div class="http-verification-info file-preview"> | ||||||
|  | 										<div class="paper-fold"></div> | ||||||
|  | 										<div> | ||||||
|  | 											<div class="file-ver-info-header">FILENAME</div> | ||||||
|  | 											<pre class="js-acme-ver-file-location">...loading</pre> | ||||||
|  | 										</div> | ||||||
|  | 										<hr /> | ||||||
|  | 										<div> | ||||||
|  | 											<div class="file-ver-info-header">CONTENTS</div> | ||||||
|  | 											<pre class="js-acme-ver-content">...loading</pre> | ||||||
|  | 										</div> | ||||||
|  | 										<div class="download-file"> | ||||||
|  | 											<svg | ||||||
|  | 												class="mdicon icon-download" | ||||||
|  | 												xmlns="http://www.w3.org/2000/svg" | ||||||
|  | 												viewbox="0 0 24 24" | ||||||
|  | 											> | ||||||
|  | 												<use xlink:href="#svg-download"></use> | ||||||
|  | 											</svg> | ||||||
|  | 											<a | ||||||
|  | 												class="js-download-verify-link" | ||||||
|  | 												href="data:text/octet-stream;base64,SGVsbG8gV29ybGQuLi4=" | ||||||
|  | 												download="hello.txt" | ||||||
|  | 												target="_blank" | ||||||
|  | 											> | ||||||
|  | 												Download | ||||||
|  | 											</a> | ||||||
|  | 										</div> | ||||||
|  | 										<hr /> | ||||||
|  | 										<div> | ||||||
|  | 											<div class="file-ver-info-header">LOCATION</div> | ||||||
|  | 											<pre class="js-acme-ver-uri">..loading</pre> | ||||||
|  | 										</div> | ||||||
|  | 									</div> | ||||||
|  | 									<br /> | ||||||
|  | 								</div> | ||||||
|  | 							</div> | ||||||
|  | 							<div class="js-acme-verification-dns-01"> | ||||||
|  | 								<h3>Set each DNS Record</h3> | ||||||
|  | 								<div class="js-acme-dns"> | ||||||
|  | 									<div class="acme-ver-dns-label">TXT Host</div> | ||||||
|  | 									<div class="js-acme-ver-txt-host">loading...</div> | ||||||
|  | 									<div class="acme-ver-dns-label">TXT Value</div> | ||||||
|  | 									<div class="js-acme-ver-txt-value">loading...</div> | ||||||
|  | 									<br /> | ||||||
|  | 								</div> | ||||||
|  | 								<p> | ||||||
|  | 									<strong>Warning</strong>: You should wait at least 30 seconds | ||||||
|  | 									after setting DNS records before continuing. | ||||||
|  | 								</p> | ||||||
|  | 								<p> | ||||||
|  | 									<strong>Google DNS Users</strong>: You may need to wait up to | ||||||
|  | 									5 minutes. | ||||||
|  | 								</p> | ||||||
|  | 							</div> | ||||||
|  | 						</div> | ||||||
|  | 
 | ||||||
|  | 						<div class="js-acme-wildcard-challenges"> | ||||||
|  | 							<div class="js-acme-verification-wildcard"> | ||||||
|  | 								<h3>Set each DNS Record (for wildcards)</h3> | ||||||
|  | 								<div class="js-acme-wildcard"> | ||||||
|  | 									<div class="acme-ver-dns-label">TXT Host</div> | ||||||
|  | 									<div class="js-acme-ver-txt-host">loading...</div> | ||||||
|  | 									<div class="acme-ver-dns-label">TXT Value</div> | ||||||
|  | 									<div class="js-acme-ver-txt-value">loading...</div> | ||||||
|  | 									<br /> | ||||||
|  | 								</div> | ||||||
|  | 								<p> | ||||||
|  | 									<strong>Warning</strong>: You should wait at least 30 seconds | ||||||
|  | 									after setting DNS records before continuing. | ||||||
|  | 								</p> | ||||||
|  | 								<p> | ||||||
|  | 									<strong>Google DNS</strong>: You may need to wait up to 5 | ||||||
|  | 									minutes. | ||||||
|  | 								</p> | ||||||
|  | 							</div> | ||||||
|  | 						</div> | ||||||
|  | 					</div> | ||||||
|  | 
 | ||||||
|  | 					<button class="button-next" type="submit">Next</button> | ||||||
|  | 				</form> | ||||||
|  | 
 | ||||||
|  | 				<!-- Step 4 Process Challanges --> | ||||||
|  | 				<form class="js-acme-form js-acme-form-poll"> | ||||||
|  | 					Verifying Domains... (give us 5 seconds or so...) | ||||||
|  | 					<div class="js-challenge-responses" hidden> | ||||||
|  | 						Checking | ||||||
|  | 						<span class="js-challenge-response-altname"> </span> | ||||||
|  | 						using <span class="js-challenge-response-type"> </span> : | ||||||
|  | 						<span class="js-challenge-response-status"> </span> | ||||||
|  | 					</div> | ||||||
|  | 
 | ||||||
|  | 					<!-- | ||||||
|  |           <table class="js-acme-table-verifying"> | ||||||
|  |             <thead> | ||||||
|  |               <tr> | ||||||
|  |                 <th>Hostname</th> | ||||||
|  |                 <th>Type</th> | ||||||
|  |                 <th>Pass</th> | ||||||
|  |               </tr> | ||||||
|  |             </thead> | ||||||
|  |             <tbody> | ||||||
|  |               <tr> | ||||||
|  |                 <td>example.com</td> | ||||||
|  |                 <td>http-01</td> | ||||||
|  |                 <td>-</td> | ||||||
|  |               </tr> | ||||||
|  |             </tbody> | ||||||
|  |           </table> | ||||||
|  | 
 | ||||||
|  |           <a href="#">advanced (use existing keypair for domain)</a> | ||||||
|  | 
 | ||||||
|  |           <button type="submit">Next</button> | ||||||
|  |           --> | ||||||
|  | 				</form> | ||||||
|  | 
 | ||||||
|  | 				<!-- Step 5 Get Certs --> | ||||||
|  | 				<form class="js-acme-form js-acme-form-download"> | ||||||
|  | 					<div class="cert-download-container"> | ||||||
|  | 						<h2><label>privkey.pem</label></h2> | ||||||
|  | 						<div class="acme-result-privkey file-preview"> | ||||||
|  | 							<div class="paper-fold"></div> | ||||||
|  | 							<pre id="js-privkey"></pre> | ||||||
|  | 						</div> | ||||||
|  | 						<div class="download-file"> | ||||||
|  | 							<svg | ||||||
|  | 								class="mdicon icon-download" | ||||||
|  | 								xmlns="http://www.w3.org/2000/svg" | ||||||
|  | 								viewbox="0 0 24 24" | ||||||
|  | 							> | ||||||
|  | 								<use xlink:href="#svg-download"></use> | ||||||
|  | 							</svg> | ||||||
|  | 							<a | ||||||
|  | 								id="js-download-privkey-link" | ||||||
|  | 								href="data:text/octet-stream;base64,SGVsbG8gV29ybGQuLi4=" | ||||||
|  | 								download="privkey.pem" | ||||||
|  | 								target="_blank" | ||||||
|  | 							> | ||||||
|  | 								Download | ||||||
|  | 							</a> | ||||||
|  | 						</div> | ||||||
|  | 						<h2><label>fullchain.pem</label></h2> | ||||||
|  | 						<div class="acme-result-fullchain file-preview"> | ||||||
|  | 							<div class="paper-fold"></div> | ||||||
|  | 							<pre id="js-fullchain"></pre> | ||||||
|  | 						</div> | ||||||
|  | 						<div class="download-file"> | ||||||
|  | 							<svg | ||||||
|  | 								class="mdicon icon-download" | ||||||
|  | 								xmlns="http://www.w3.org/2000/svg" | ||||||
|  | 								viewbox="0 0 24 24" | ||||||
|  | 							> | ||||||
|  | 								<use xlink:href="#svg-download"></use> | ||||||
|  | 							</svg> | ||||||
|  | 							<a | ||||||
|  | 								id="js-download-fullchain-link" | ||||||
|  | 								href="data:text/octet-stream;base64,SGVsbG8gV29ybGQuLi4=" | ||||||
|  | 								download="fullchain.pem" | ||||||
|  | 								target="_blank" | ||||||
|  | 							> | ||||||
|  | 								Download | ||||||
|  | 							</a> | ||||||
|  | 						</div> | ||||||
|  | 						<div> | ||||||
|  | 							<h3>node.js https server example</h3> | ||||||
|  | 							<pre><code>    'use strict'; | ||||||
|  | 
 | ||||||
|  |     var https = require('https'); | ||||||
|  |     var server = https.createServer({ | ||||||
|  |       key: require('fs').readFileSync('./privkey.pem') | ||||||
|  |     , cert: require('fs').readFileSync('./fullchain.pem') | ||||||
|  |     }, function (req, res) { | ||||||
|  |       res.end("Hello, World!"); | ||||||
|  |     }).listen(443, function () { | ||||||
|  |       console.log('Listening on', this.address()); | ||||||
|  |     }) | ||||||
|  |             </code></pre> | ||||||
|  | 						</div> | ||||||
|  | 
 | ||||||
|  | 						<!-- | ||||||
|  |             TODO | ||||||
|  |           <label>cert.pem</label> | ||||||
|  |           <textarea class="js-cert">-</textarea> | ||||||
|  | 
 | ||||||
|  |           <label>chain.pem</label> | ||||||
|  |           <textarea class="js-chain">-</textarea> | ||||||
|  | 
 | ||||||
|  |           <button type="button">Download SSL Certificates</button> | ||||||
|  |           <br> | ||||||
|  |           <a href="#">Advanced (copy and paste)</a> | ||||||
|  |           <br> | ||||||
|  |           <button type="submit">Start Over</button> | ||||||
|  |           --></div> | ||||||
|  | 				</form> | ||||||
|  | 
 | ||||||
|  | 				<div> | ||||||
|  | 					<small | ||||||
|  | 						><center> | ||||||
|  | 							<div> | ||||||
|  | 								A | ||||||
|  | 								<a href="https://rootprojects.org/" target="_blank">Root</a> | ||||||
|  | 								Project | | ||||||
|  | 								<a | ||||||
|  | 									href="https://git.coolaj86.com/coolaj86/greenlock.html" | ||||||
|  | 									target="_blank" | ||||||
|  | 									>View Source</a | ||||||
|  | 								> | ||||||
|  | 								(git) | | ||||||
|  | 								<a href="https://rootprojects.org/legal/#terms" target="_blank" | ||||||
|  | 									>Terms of Service</a | ||||||
|  | 								> | ||||||
|  | 								| | ||||||
|  | 								<a | ||||||
|  | 									href="https://rootprojects.org/legal/#privacy" | ||||||
|  | 									target="_blank" | ||||||
|  | 									>Privacy Policy</a | ||||||
|  | 								> | ||||||
|  | 							</div> | ||||||
|  | 							<!-- or | ||||||
|  |         <pre><code>git clone https://git.coolaj86.com/coolaj86/greenlock.html.git</code></pre> | ||||||
|  |         Or view the live site code (same as live-site branch): | ||||||
|  |         <pre><code>wget https://greenlock.domains --mirror --convert-links --adjust-extension --page-requisites --no-parent</code></pre> | ||||||
|  |       --> | ||||||
|  | 						</center></small | ||||||
|  | 					> | ||||||
|  | 				</div> | ||||||
|  | 				<br /> | ||||||
|  | 
 | ||||||
|  | 				<script src="./js/bluecrypt-acme.js"></script> | ||||||
|  | 				<script src="./js/greenlock.js"></script> | ||||||
|  | 
 | ||||||
|  | 				<!-- Global site tag (gtag.js) - Google Analytics --> | ||||||
|  | 				<script | ||||||
|  | 					async | ||||||
|  | 					src="https://www.googletagmanager.com/gtag/js?id=UA-118745161-2" | ||||||
|  | 				></script> | ||||||
|  | 				<script> | ||||||
|  | 					window.dataLayer = window.dataLayer || []; | ||||||
|  | 					function gtag() { | ||||||
|  | 						dataLayer.push(arguments); | ||||||
|  | 					} | ||||||
|  | 					gtag("js", new Date()); | ||||||
|  | 
 | ||||||
|  | 					gtag("config", "UA-118745161-2"); | ||||||
|  | 				</script> | ||||||
|  | 			</div> | ||||||
|  | 		</div> | ||||||
|  | 	</body> | ||||||
|  | </html> | ||||||
							
								
								
									
										3488
									
								
								app/js/bluecrypt-acme.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3488
									
								
								app/js/bluecrypt-acme.js
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										609
									
								
								app/js/greenlock.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										609
									
								
								app/js/greenlock.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,609 @@ | |||||||
|  | (function() { | ||||||
|  | 	"use strict"; | ||||||
|  | 
 | ||||||
|  | 	/*global URLSearchParams,Headers*/ | ||||||
|  | 	var PromiseA = window.Promise; | ||||||
|  | 	var VERSION = "2"; | ||||||
|  | 	// ACME recommends ECDSA P-256, but RSA 2048 is still required by some old servers (like what replicated.io uses )
 | ||||||
|  | 	// ECDSA P-384, P-521, and RSA 3072, 4096 are NOT recommend standards (and not properly supported)
 | ||||||
|  | 	var BROWSER_SUPPORTS_RSA = false; | ||||||
|  | 	var ECDSA_OPTS = { kty: "EC", namedCurve: "P-256" }; | ||||||
|  | 	var RSA_OPTS = { kty: "RSA", modulusLength: 2048 }; | ||||||
|  | 	var Promise = window.Promise; | ||||||
|  | 	var Keypairs = window.Keypairs; | ||||||
|  | 	var ACME = window.ACME; | ||||||
|  | 	var CSR = window.CSR; | ||||||
|  | 	var $qs = function(s) { | ||||||
|  | 		return window.document.querySelector(s); | ||||||
|  | 	}; | ||||||
|  | 	var $qsa = function(s) { | ||||||
|  | 		return window.document.querySelectorAll(s); | ||||||
|  | 	}; | ||||||
|  | 	var acme; | ||||||
|  | 	var info = {}; | ||||||
|  | 	var steps = {}; | ||||||
|  | 	var i = 1; | ||||||
|  | 	var apiUrl = "https://acme-{{env}}.api.letsencrypt.org/directory"; | ||||||
|  | 
 | ||||||
|  | 	// fix previous browsers
 | ||||||
|  | 	var isCurrent = localStorage.getItem("version") === VERSION; | ||||||
|  | 	if (!isCurrent) { | ||||||
|  | 		localStorage.clear(); | ||||||
|  | 		localStorage.setItem("version", VERSION); | ||||||
|  | 	} | ||||||
|  | 	localStorage.setItem("version", VERSION); | ||||||
|  | 
 | ||||||
|  | 	function updateApiType() { | ||||||
|  | 		/*jshint validthis: true */ | ||||||
|  | 		var input = | ||||||
|  | 			this || | ||||||
|  | 			Array.prototype.filter.call($qsa(".js-acme-api-type"), function($el) { | ||||||
|  | 				return $el.checked; | ||||||
|  | 			})[0]; | ||||||
|  | 		//#console.log('ACME api type radio:', input.value);
 | ||||||
|  | 		$qs(".js-acme-directory-url").value = apiUrl.replace( | ||||||
|  | 			/{{env}}/g, | ||||||
|  | 			input.value | ||||||
|  | 		); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	function hideForms() { | ||||||
|  | 		$qsa(".js-acme-form").forEach(function(el) { | ||||||
|  | 			el.hidden = true; | ||||||
|  | 		}); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	function updateProgress(currentStep) { | ||||||
|  | 		var progressSteps = $qs("#js-progress-bar").children; | ||||||
|  | 		var j; | ||||||
|  | 		for (j = 0; j < progressSteps.length; j += 1) { | ||||||
|  | 			if (j < currentStep) { | ||||||
|  | 				progressSteps[j].classList.add("js-progress-step-complete"); | ||||||
|  | 				progressSteps[j].classList.remove("js-progress-step-started"); | ||||||
|  | 			} else if (j === currentStep) { | ||||||
|  | 				progressSteps[j].classList.remove("js-progress-step-complete"); | ||||||
|  | 				progressSteps[j].classList.add("js-progress-step-started"); | ||||||
|  | 			} else { | ||||||
|  | 				progressSteps[j].classList.remove("js-progress-step-complete"); | ||||||
|  | 				progressSteps[j].classList.remove("js-progress-step-started"); | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	function newAlert(str) { | ||||||
|  | 		return new Promise(function() { | ||||||
|  | 			setTimeout(function() { | ||||||
|  | 				window.alert(str); | ||||||
|  | 				if (window.confirm("Start over?")) { | ||||||
|  | 					document.location.href = document.location.href.replace( | ||||||
|  | 						/\/app.*/, | ||||||
|  | 						"/" | ||||||
|  | 					); | ||||||
|  | 				} | ||||||
|  | 			}, 10); | ||||||
|  | 		}); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	function submitForm(ev) { | ||||||
|  | 		var j = i; | ||||||
|  | 		i += 1; | ||||||
|  | 
 | ||||||
|  | 		return PromiseA.resolve() | ||||||
|  | 			.then(function() { | ||||||
|  | 				return steps[j].submit(ev); | ||||||
|  | 			}) | ||||||
|  | 			.catch(function(err) { | ||||||
|  | 				var ourfault = true; | ||||||
|  | 				console.error(err); | ||||||
|  | 				if (/failed to fetch/i.test(err.message)) { | ||||||
|  | 					return newAlert("Network connection failure."); | ||||||
|  | 				} | ||||||
|  | 
 | ||||||
|  | 				if ("E_ACME_CHALLENGE" === err.code) { | ||||||
|  | 					if ("dns-01" === err.type) { | ||||||
|  | 						ourfault = false; | ||||||
|  | 						return newAlert( | ||||||
|  | 							"It looks like the DNS record you set for " + | ||||||
|  | 								err.altname + | ||||||
|  | 								" was incorrect or did not propagate. " + | ||||||
|  | 								"The error message was '" + | ||||||
|  | 								err.message + | ||||||
|  | 								"'" | ||||||
|  | 						); | ||||||
|  | 					} else if ("http-01" === err.type) { | ||||||
|  | 						ourfault = false; | ||||||
|  | 						return newAlert( | ||||||
|  | 							"It looks like the file you uploaded for " + | ||||||
|  | 								err.altname + | ||||||
|  | 								" was incorrect or could not be downloaded. " + | ||||||
|  | 								"The error message was '" + | ||||||
|  | 								err.message + | ||||||
|  | 								"'" | ||||||
|  | 						); | ||||||
|  | 					} | ||||||
|  | 				} | ||||||
|  | 
 | ||||||
|  | 				if (ourfault) { | ||||||
|  | 					err.auth = undefined; | ||||||
|  | 					window.alert( | ||||||
|  | 						"Something went wrong. It's probably our fault, not yours." + | ||||||
|  | 							" Please email aj@rootprojects.org to let him know. The error message is: \n" + | ||||||
|  | 							JSON.stringify(err, null, 2) | ||||||
|  | 					); | ||||||
|  | 					return new Promise(function() {}); | ||||||
|  | 				} | ||||||
|  | 			}); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	function testKeypairSupport() { | ||||||
|  | 		return Keypairs.generate(RSA_OPTS) | ||||||
|  | 			.then(function() { | ||||||
|  | 				console.info("[crypto] RSA is supported"); | ||||||
|  | 				BROWSER_SUPPORTS_RSA = true; | ||||||
|  | 			}) | ||||||
|  | 			.catch(function() { | ||||||
|  | 				console.warn("[crypto] RSA is NOT supported"); | ||||||
|  | 				return Keypairs.generate(ECDSA_OPTS) | ||||||
|  | 					.then(function() { | ||||||
|  | 						console.info("[crypto] ECDSA is supported"); | ||||||
|  | 					}) | ||||||
|  | 					.catch(function(e) { | ||||||
|  | 						console.warn("[crypto] EC is NOT supported"); | ||||||
|  | 						throw e; | ||||||
|  | 					}); | ||||||
|  | 			}); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	function getServerKeypair() { | ||||||
|  | 		var sortedAltnames = info.identifiers | ||||||
|  | 			.map(function(ident) { | ||||||
|  | 				return ident.value; | ||||||
|  | 			}) | ||||||
|  | 			.sort() | ||||||
|  | 			.join(","); | ||||||
|  | 		var serverJwk = JSON.parse( | ||||||
|  | 			localStorage.getItem("server:" + sortedAltnames) || "null" | ||||||
|  | 		); | ||||||
|  | 		if (serverJwk) { | ||||||
|  | 			return PromiseA.resolve(serverJwk); | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		var keypairOpts; | ||||||
|  | 		// TODO allow for user preference
 | ||||||
|  | 		if (BROWSER_SUPPORTS_RSA) { | ||||||
|  | 			keypairOpts = RSA_OPTS; | ||||||
|  | 		} else { | ||||||
|  | 			keypairOpts = ECDSA_OPTS; | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		return Keypairs.generate(RSA_OPTS) | ||||||
|  | 			.catch(function(err) { | ||||||
|  | 				console.error( | ||||||
|  | 					"[Error] Keypairs.generate(" + JSON.stringify(RSA_OPTS) + "):" | ||||||
|  | 				); | ||||||
|  | 				throw err; | ||||||
|  | 			}) | ||||||
|  | 			.then(function(pair) { | ||||||
|  | 				localStorage.setItem( | ||||||
|  | 					"server:" + sortedAltnames, | ||||||
|  | 					JSON.stringify(pair.private) | ||||||
|  | 				); | ||||||
|  | 				return pair.private; | ||||||
|  | 			}); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	function getAccountKeypair(email) { | ||||||
|  | 		var json = localStorage.getItem("account:" + email); | ||||||
|  | 		if (json) { | ||||||
|  | 			return Promise.resolve(JSON.parse(json)); | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		return Keypairs.generate(ECDSA_OPTS) | ||||||
|  | 			.catch(function(err) { | ||||||
|  | 				console.warn( | ||||||
|  | 					"[Error] Keypairs.generate(" + JSON.stringify(ECDSA_OPTS) + "):\n", | ||||||
|  | 					err | ||||||
|  | 				); | ||||||
|  | 				return Keypairs.generate(RSA_OPTS).catch(function(err) { | ||||||
|  | 					console.error( | ||||||
|  | 						"[Error] Keypairs.generate(" + JSON.stringify(RSA_OPTS) + "):" | ||||||
|  | 					); | ||||||
|  | 					throw err; | ||||||
|  | 				}); | ||||||
|  | 			}) | ||||||
|  | 			.then(function(pair) { | ||||||
|  | 				localStorage.setItem("account:" + email, JSON.stringify(pair.private)); | ||||||
|  | 				return pair.private; | ||||||
|  | 			}); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	function updateChallengeType() { | ||||||
|  | 		/*jshint validthis: true*/ | ||||||
|  | 		var input = | ||||||
|  | 			this || | ||||||
|  | 			Array.prototype.filter.call($qsa(".js-acme-challenge-type"), function( | ||||||
|  | 				$el | ||||||
|  | 			) { | ||||||
|  | 				return $el.checked; | ||||||
|  | 			})[0]; | ||||||
|  | 		$qs(".js-acme-verification-wildcard").hidden = true; | ||||||
|  | 		$qs(".js-acme-verification-http-01").hidden = true; | ||||||
|  | 		$qs(".js-acme-verification-dns-01").hidden = true; | ||||||
|  | 		if (info.challenges.wildcard) { | ||||||
|  | 			$qs(".js-acme-verification-wildcard").hidden = false; | ||||||
|  | 		} | ||||||
|  | 		if (info.challenges[input.value]) { | ||||||
|  | 			$qs(".js-acme-verification-" + input.value).hidden = false; | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	function saveContact(email, domains) { | ||||||
|  | 		// to be used for good, not evil
 | ||||||
|  | 		return window | ||||||
|  | 			.fetch( | ||||||
|  | 				"https://api.rootprojects.org/api/rootprojects.org/public/community", | ||||||
|  | 				{ | ||||||
|  | 					method: "POST", | ||||||
|  | 					cors: true, | ||||||
|  | 					headers: new Headers({ "Content-Type": "application/json" }), | ||||||
|  | 					body: JSON.stringify({ | ||||||
|  | 						address: email, | ||||||
|  | 						project: "greenlock-domains@rootprojects.org", | ||||||
|  | 						timezone: new Intl.DateTimeFormat().resolvedOptions().timeZone, | ||||||
|  | 						domain: domains.join(",") | ||||||
|  | 					}) | ||||||
|  | 				} | ||||||
|  | 			) | ||||||
|  | 			.catch(function(err) { | ||||||
|  | 				console.error(err); | ||||||
|  | 			}); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	steps[1] = function() { | ||||||
|  | 		console.info("\n1. Show domains form"); | ||||||
|  | 		updateProgress(0); | ||||||
|  | 		hideForms(); | ||||||
|  | 		$qs(".js-acme-form-domains").hidden = false; | ||||||
|  | 	}; | ||||||
|  | 	steps[1].submit = function() { | ||||||
|  | 		console.info( | ||||||
|  | 			"[submit] 1. Process domains, create ACME client", | ||||||
|  | 			info.domains | ||||||
|  | 		); | ||||||
|  | 		info.domains = $qs(".js-acme-domains") | ||||||
|  | 			.value.replace(/https?:\/\//g, " ") | ||||||
|  | 			.replace(/[,+]/g, " ") | ||||||
|  | 			.trim() | ||||||
|  | 			.split(/\s+/g); | ||||||
|  | 		console.info("[domains]", info.domains.join(" ")); | ||||||
|  | 
 | ||||||
|  | 		info.identifiers = info.domains.map(function(hostname) { | ||||||
|  | 			return { type: "dns", value: hostname.toLowerCase().trim() }; | ||||||
|  | 		}); | ||||||
|  | 		info.identifiers.sort(function(a, b) { | ||||||
|  | 			if (a === b) { | ||||||
|  | 				return 0; | ||||||
|  | 			} | ||||||
|  | 			if (a < b) { | ||||||
|  | 				return 1; | ||||||
|  | 			} | ||||||
|  | 			if (a > b) { | ||||||
|  | 				return -1; | ||||||
|  | 			} | ||||||
|  | 		}); | ||||||
|  | 
 | ||||||
|  | 		var acmeDirectoryUrl = $qs(".js-acme-directory-url").value; | ||||||
|  | 		acme = ACME.create({ Keypairs: Keypairs, CSR: CSR }); | ||||||
|  | 		return acme.init(acmeDirectoryUrl).then(function(directory) { | ||||||
|  | 			$qs(".js-acme-tos-url").href = directory.meta.termsOfService; | ||||||
|  | 			return steps[i](); | ||||||
|  | 		}); | ||||||
|  | 	}; | ||||||
|  | 
 | ||||||
|  | 	steps[2] = function() { | ||||||
|  | 		console.info("\n2. Show account (email, ToS) form"); | ||||||
|  | 
 | ||||||
|  | 		updateProgress(0); | ||||||
|  | 		hideForms(); | ||||||
|  | 		$qs(".js-acme-form-account").hidden = false; | ||||||
|  | 	}; | ||||||
|  | 	steps[2].submit = function() { | ||||||
|  | 		console.info("[submit] 2. Create ACME account (get Key ID)"); | ||||||
|  | 
 | ||||||
|  | 		var email = $qs(".js-acme-account-email") | ||||||
|  | 			.value.toLowerCase() | ||||||
|  | 			.trim(); | ||||||
|  | 		info.email = email; | ||||||
|  | 		info.contact = ["mailto:" + email]; | ||||||
|  | 		info.agree = $qs(".js-acme-account-tos").checked; | ||||||
|  | 		//info.greenlockAgree = $qs('.js-gl-tos').checked;
 | ||||||
|  | 
 | ||||||
|  | 		// TODO ping with version and account creation
 | ||||||
|  | 		setTimeout(saveContact, 100, email, info.domains); | ||||||
|  | 
 | ||||||
|  | 		$qs(".js-account-next").disabled = true; | ||||||
|  | 
 | ||||||
|  | 		return info.cryptoCheck | ||||||
|  | 			.then(function() { | ||||||
|  | 				return getAccountKeypair(email).then(function(jwk) { | ||||||
|  | 					// TODO save account id rather than always retrieving it?
 | ||||||
|  | 					console.info("[accounts] upsert for", email); | ||||||
|  | 					return acme.accounts | ||||||
|  | 						.create({ | ||||||
|  | 							email: email, | ||||||
|  | 							agreeToTerms: info.agree && true, | ||||||
|  | 							accountKeypair: { privateKeyJwk: jwk } | ||||||
|  | 						}) | ||||||
|  | 						.then(function(account) { | ||||||
|  | 							console.info("[accounts] result:", account); | ||||||
|  | 							info.account = account; | ||||||
|  | 							info.privateJwk = jwk; | ||||||
|  | 							info.email = email; | ||||||
|  | 						}) | ||||||
|  | 						.catch(function(err) { | ||||||
|  | 							console.error("[accounts] failed to upsert account:"); | ||||||
|  | 							console.error(err); | ||||||
|  | 							return newAlert(err.message || JSON.stringify(err, null, 2)); | ||||||
|  | 						}); | ||||||
|  | 				}); | ||||||
|  | 			}) | ||||||
|  | 			.then(function() { | ||||||
|  | 				var jwk = info.privateJwk; | ||||||
|  | 				var account = info.account; | ||||||
|  | 
 | ||||||
|  | 				console.info("[orders] requesting"); | ||||||
|  | 				return acme.orders | ||||||
|  | 					.request({ | ||||||
|  | 						account: account, | ||||||
|  | 						accountKeypair: { privateKeyJwk: jwk }, | ||||||
|  | 						domains: info.domains | ||||||
|  | 					}) | ||||||
|  | 					.then(function(order) { | ||||||
|  | 						info.order = order; | ||||||
|  | 						console.info("[orders] created ", order); | ||||||
|  | 
 | ||||||
|  | 						var claims = order.claims; | ||||||
|  | 
 | ||||||
|  | 						var obj = { "dns-01": [], "http-01": [], wildcard: [] }; | ||||||
|  | 						info.challenges = obj; | ||||||
|  | 
 | ||||||
|  | 						var $httpList = $qs(".js-acme-http"); | ||||||
|  | 						var $dnsList = $qs(".js-acme-dns"); | ||||||
|  | 						var $wildList = $qs(".js-acme-wildcard"); | ||||||
|  | 						var httpTpl = $httpList.innerHTML; | ||||||
|  | 						var dnsTpl = $dnsList.innerHTML; | ||||||
|  | 						var wildTpl = $wildList.innerHTML; | ||||||
|  | 						$httpList.innerHTML = ""; | ||||||
|  | 						$dnsList.innerHTML = ""; | ||||||
|  | 						$wildList.innerHTML = ""; | ||||||
|  | 
 | ||||||
|  | 						claims.forEach(function(claim) { | ||||||
|  | 							//#console.log("claims[i]", claim);
 | ||||||
|  | 							var hostname = claim.identifier.value; | ||||||
|  | 							claim.challenges.forEach(function(c) { | ||||||
|  | 								var auth = c; | ||||||
|  | 								var data = { | ||||||
|  | 									type: c.type, | ||||||
|  | 									hostname: hostname, | ||||||
|  | 									url: c.url, | ||||||
|  | 									token: c.token, | ||||||
|  | 									httpPath: auth.challengeUrl, | ||||||
|  | 									httpAuth: auth.keyAuthorization, | ||||||
|  | 									dnsType: "TXT", | ||||||
|  | 									dnsHost: auth.dnsHost, | ||||||
|  | 									dnsAnswer: auth.keyAuthorizationDigest | ||||||
|  | 								}; | ||||||
|  | 								//#console.log("claims[i].challenge", data);
 | ||||||
|  | 
 | ||||||
|  | 								var $tpl = document.createElement("div"); | ||||||
|  | 								if (claim.wildcard) { | ||||||
|  | 									obj.wildcard.push(data); | ||||||
|  | 									$tpl.innerHTML = wildTpl; | ||||||
|  | 									$tpl.querySelector(".js-acme-ver-txt-host").innerHTML = | ||||||
|  | 										data.dnsHost; | ||||||
|  | 									$tpl.querySelector(".js-acme-ver-txt-value").innerHTML = | ||||||
|  | 										data.dnsAnswer; | ||||||
|  | 									$wildList.appendChild($tpl); | ||||||
|  | 								} else if (obj[data.type]) { | ||||||
|  | 									obj[data.type].push(data); | ||||||
|  | 
 | ||||||
|  | 									if ("dns-01" === data.type) { | ||||||
|  | 										$tpl.innerHTML = dnsTpl; | ||||||
|  | 										$tpl.querySelector(".js-acme-ver-txt-host").innerHTML = | ||||||
|  | 											data.dnsHost; | ||||||
|  | 										$tpl.querySelector(".js-acme-ver-txt-value").innerHTML = | ||||||
|  | 											data.dnsAnswer; | ||||||
|  | 										$dnsList.appendChild($tpl); | ||||||
|  | 									} else if ("http-01" === data.type) { | ||||||
|  | 										$tpl.innerHTML = httpTpl; | ||||||
|  | 										$tpl.querySelector( | ||||||
|  | 											".js-acme-ver-file-location" | ||||||
|  | 										).innerHTML = data.httpPath.split("/").slice(-1); | ||||||
|  | 										$tpl.querySelector(".js-acme-ver-content").innerHTML = | ||||||
|  | 											data.httpAuth; | ||||||
|  | 										$tpl.querySelector(".js-acme-ver-uri").innerHTML = | ||||||
|  | 											data.httpPath; | ||||||
|  | 										$tpl.querySelector(".js-download-verify-link").href = | ||||||
|  | 											"data:text/octet-stream;base64," + | ||||||
|  | 											window.btoa(data.httpAuth); | ||||||
|  | 										$tpl.querySelector( | ||||||
|  | 											".js-download-verify-link" | ||||||
|  | 										).download = data.httpPath.split("/").slice(-1); | ||||||
|  | 										$httpList.appendChild($tpl); | ||||||
|  | 									} | ||||||
|  | 								} | ||||||
|  | 							}); | ||||||
|  | 						}); | ||||||
|  | 
 | ||||||
|  | 						// hide wildcard if no wildcard
 | ||||||
|  | 						// hide http-01 and dns-01 if only wildcard
 | ||||||
|  | 						if (!obj.wildcard.length) { | ||||||
|  | 							$qs(".js-acme-wildcard-challenges").hidden = true; | ||||||
|  | 						} | ||||||
|  | 						if (!obj["http-01"].length) { | ||||||
|  | 							$qs(".js-acme-challenges").hidden = true; | ||||||
|  | 						} | ||||||
|  | 
 | ||||||
|  | 						console.info("[housekeeping] challenges", info.challenges); | ||||||
|  | 
 | ||||||
|  | 						updateChallengeType(); | ||||||
|  | 						return steps[i](); | ||||||
|  | 					}) | ||||||
|  | 					.catch(function(err) { | ||||||
|  | 						if (err.detail || err.urn) { | ||||||
|  | 							console.error("(Probably) User Error:"); | ||||||
|  | 							console.error(err); | ||||||
|  | 							return newAlert( | ||||||
|  | 								"There was an error, probably with your email or domain:\n" + | ||||||
|  | 									err.message | ||||||
|  | 							); | ||||||
|  | 						} | ||||||
|  | 						throw err; | ||||||
|  | 					}); | ||||||
|  | 			}) | ||||||
|  | 			.catch(function(err) { | ||||||
|  | 				console.error("Step '' Error:"); | ||||||
|  | 				console.error(err, err.stack); | ||||||
|  | 				return newAlert( | ||||||
|  | 					"An error happened (but it's not your fault)." + | ||||||
|  | 						" Email aj@rootprojects.org to let him know that 'order and get challenges' failed." | ||||||
|  | 				); | ||||||
|  | 			}); | ||||||
|  | 	}; | ||||||
|  | 
 | ||||||
|  | 	steps[3] = function() { | ||||||
|  | 		console.info("\n3. Present challenge options"); | ||||||
|  | 		updateProgress(1); | ||||||
|  | 		hideForms(); | ||||||
|  | 		$qs(".js-acme-form-challenges").hidden = false; | ||||||
|  | 	}; | ||||||
|  | 	steps[3].submit = function() { | ||||||
|  | 		console.info("[submit] 3. Fulfill challenges, fetch certificate"); | ||||||
|  | 
 | ||||||
|  | 		var challengePriority = ["dns-01"]; | ||||||
|  | 		if ("http-01" === $qs(".js-acme-challenge-type:checked").value) { | ||||||
|  | 			challengePriority.unshift("http-01"); | ||||||
|  | 		} | ||||||
|  | 		console.info("[challenge] selected ", challengePriority[0]); | ||||||
|  | 
 | ||||||
|  | 		// for now just show the next page immediately (its a spinner)
 | ||||||
|  | 		steps[i](); | ||||||
|  | 
 | ||||||
|  | 		return getAccountKeypair(info.email).then(function(jwk) { | ||||||
|  | 			// TODO put a test challenge in the list
 | ||||||
|  | 			// info.order.claims.push(...)
 | ||||||
|  | 			// TODO warn about wait-time if DNS
 | ||||||
|  | 			return getServerKeypair().then(function(serverJwk) { | ||||||
|  | 				return acme.orders | ||||||
|  | 					.complete({ | ||||||
|  | 						account: info.account, | ||||||
|  | 						accountKeypair: { privateKeyJwk: jwk }, | ||||||
|  | 						order: info.order, | ||||||
|  | 						domains: info.domains, | ||||||
|  | 						domainKeypair: { privateKeyJwk: serverJwk }, | ||||||
|  | 						challengePriority: challengePriority, | ||||||
|  | 						challenges: false, | ||||||
|  | 						onChallengeStatus: function(details) { | ||||||
|  | 							$qs(".js-challenge-responses").hidden = false; | ||||||
|  | 							$qs(".js-challenge-response-type").innerText = details.type; | ||||||
|  | 							$qs(".js-challenge-response-status").innerText = details.status; | ||||||
|  | 							$qs(".js-challenge-response-altname").innerText = details.altname; | ||||||
|  | 						} | ||||||
|  | 					}) | ||||||
|  | 					.then(function(certs) { | ||||||
|  | 						return Keypairs.export({ jwk: serverJwk }).then(function(keyPem) { | ||||||
|  | 							console.info("WINNING!"); | ||||||
|  | 							console.info(certs); | ||||||
|  | 							var fullChainText = [ | ||||||
|  | 								certs.cert.trim() + "\n", | ||||||
|  | 								certs.chain + "\n" | ||||||
|  | 							].join("\n"); | ||||||
|  | 
 | ||||||
|  | 							$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 = | ||||||
|  | 								"data:text/octet-stream;base64," + window.btoa(keyPem); | ||||||
|  | 							return submitForm(); | ||||||
|  | 						}); | ||||||
|  | 					}); | ||||||
|  | 			}); | ||||||
|  | 		}); | ||||||
|  | 	}; | ||||||
|  | 
 | ||||||
|  | 	// spinner
 | ||||||
|  | 	steps[4] = function() { | ||||||
|  | 		console.info("\n4. Show loading spinner"); | ||||||
|  | 		updateProgress(1); | ||||||
|  | 		hideForms(); | ||||||
|  | 		$qs(".js-acme-form-poll").hidden = false; | ||||||
|  | 	}; | ||||||
|  | 	steps[4].submit = function() { | ||||||
|  | 		console.info("[submit] 4. Order complete"); | ||||||
|  | 
 | ||||||
|  | 		return steps[i](); | ||||||
|  | 	}; | ||||||
|  | 
 | ||||||
|  | 	steps[5] = function() { | ||||||
|  | 		console.info("\n5. Present certificates (yay!)"); | ||||||
|  | 		updateProgress(2); | ||||||
|  | 		hideForms(); | ||||||
|  | 		$qs(".js-acme-form-download").hidden = false; | ||||||
|  | 	}; | ||||||
|  | 
 | ||||||
|  | 	function init() { | ||||||
|  | 		$qsa(".js-acme-api-type").forEach(function($el) { | ||||||
|  | 			$el.addEventListener("change", updateApiType); | ||||||
|  | 		}); | ||||||
|  | 		updateApiType(); | ||||||
|  | 
 | ||||||
|  | 		$qsa(".js-acme-form").forEach(function($el) { | ||||||
|  | 			$el.addEventListener("submit", function(ev) { | ||||||
|  | 				ev.preventDefault(); | ||||||
|  | 				return submitForm(ev); | ||||||
|  | 			}); | ||||||
|  | 		}); | ||||||
|  | 
 | ||||||
|  | 		$qsa(".js-acme-challenge-type").forEach(function($el) { | ||||||
|  | 			$el.addEventListener("change", updateChallengeType); | ||||||
|  | 		}); | ||||||
|  | 
 | ||||||
|  | 		var params = new URLSearchParams(window.location.search); | ||||||
|  | 		var apiType = params.get("acme-api-type") || "staging-v02"; | ||||||
|  | 		if (params.has("acme-domains")) { | ||||||
|  | 			$qs(".js-acme-domains").value = params.get("acme-domains"); | ||||||
|  | 
 | ||||||
|  | 			$qsa(".js-acme-api-type").forEach(function(ele) { | ||||||
|  | 				if (ele.value === apiType) { | ||||||
|  | 					ele.checked = true; | ||||||
|  | 				} | ||||||
|  | 			}); | ||||||
|  | 
 | ||||||
|  | 			updateApiType(); | ||||||
|  | 			steps[2](); | ||||||
|  | 			return submitForm(); | ||||||
|  | 		} else { | ||||||
|  | 			steps[1](); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	init(); | ||||||
|  | 	$qs("body").hidden = false; | ||||||
|  | 
 | ||||||
|  | 	// in the background
 | ||||||
|  | 	info.cryptoCheck = testKeypairSupport() | ||||||
|  | 		.then(function() { | ||||||
|  | 			console.info("[crypto] self-check: passed"); | ||||||
|  | 		}) | ||||||
|  | 		.catch(function(err) { | ||||||
|  | 			console.error("[crypto] could not use either RSA nor EC."); | ||||||
|  | 			console.error(err); | ||||||
|  | 			window.alert( | ||||||
|  | 				"Generating secure certificates requires a browser with cryptography support." + | ||||||
|  | 					"Please consider a recent version of Chrome, Firefox, or Safari." | ||||||
|  | 			); | ||||||
|  | 			throw err; | ||||||
|  | 		}); | ||||||
|  | })(); | ||||||
							
								
								
									
										274
									
								
								app/styles/main.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										274
									
								
								app/styles/main.css
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,274 @@ | |||||||
|  | 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%; | ||||||
|  | 	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; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | button { | ||||||
|  | 	cursor: pointer; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .tabbed-selector label { | ||||||
|  | 	width: 50%; | ||||||
|  | 	padding: 0.5em 0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .tabbed-selector { | ||||||
|  | 	display: flex; | ||||||
|  | 	font-weight: bold; | ||||||
|  | 	text-align: center; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .tabbed-selector input[type="radio"] { | ||||||
|  | 	display: none; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .download-file svg { | ||||||
|  | 	fill: #5bc17f; | ||||||
|  | 	width: 1.333333333em; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .download-file a { | ||||||
|  | 	color: #5bc17f; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .mdicon { | ||||||
|  | 	position: relative; | ||||||
|  | 	top: 0.4em; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .http-verification-info { | ||||||
|  | 	padding-right: 6.933333333em; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .paper-fold { | ||||||
|  | 	position: absolute; | ||||||
|  | 	width: 2em; | ||||||
|  | 	height: 2em; | ||||||
|  | 	border-left: solid #d9d9d9 1px; | ||||||
|  | 	border-bottom: solid #d9d9d9 1px; | ||||||
|  | 	right: 0; | ||||||
|  | 	top: 0; | ||||||
|  | 	background: linear-gradient( | ||||||
|  | 		45deg, | ||||||
|  | 		#f7f7f7 0%, | ||||||
|  | 		#f7f7f7 50%, | ||||||
|  | 		#ffffff 50%, | ||||||
|  | 		#ffffff 100% | ||||||
|  | 	); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .file-ver-info-header { | ||||||
|  | 	color: #808080; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .http-verification-info hr { | ||||||
|  | 	border: none; | ||||||
|  | 	border-bottom: solid 1px #d9d9d9; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .acme-ver-uri { | ||||||
|  | 	word-break: break-all; | ||||||
|  | 	margin: auto; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .acme-ver-dns-label { | ||||||
|  | 	margin: 1.777777778em 0 0.444444444em 0; | ||||||
|  | 	border-bottom: solid 1px #d9d9d9; | ||||||
|  | 	font-weight: bold; | ||||||
|  | 	padding-bottom: 0.166666667em; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .tabbed-selector input[type="radio"]:checked ~ div { | ||||||
|  | 	border: solid 1px #5bc17f; | ||||||
|  | 	background-color: #5bc17f; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .file-preview pre { | ||||||
|  | 	white-space: pre-line; | ||||||
|  | 	word-break: break-all; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .cert-download-container { | ||||||
|  | 	margin: 0 -31%; | ||||||
|  | } | ||||||
							
								
								
									
										
											BIN
										
									
								
								fonts/6xK3dSBYKcSV-LCoeQqfX1RYOo3qOK7l.woff2
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								fonts/6xK3dSBYKcSV-LCoeQqfX1RYOo3qOK7l.woff2
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								fonts/6xKydSBYKcSV-LCoeQqfX1RYOo3ig4vwlxdu.woff2
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								fonts/6xKydSBYKcSV-LCoeQqfX1RYOo3ig4vwlxdu.woff2
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								img/greenlock-146.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								img/greenlock-146.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 3.8 KiB | 
							
								
								
									
										380
									
								
								index.html
									
									
									
									
									
								
							
							
						
						
									
										380
									
								
								index.html
									
									
									
									
									
								
							| @ -1,227 +1,171 @@ | |||||||
| <html> | <html> | ||||||
|   <head> | 	<head> | ||||||
|     <title>Greenlock™</title> | 		<title>Greenlock™</title> | ||||||
|     <meta property="og:image" content="https://greenlock.ppl.family/img/greenlock-mark-400x400.png" /> | 		<meta | ||||||
|   </head> | 			property="og:image" | ||||||
|   <body hidden> | 			content="https://greenlock.domains/img/greenlock-mark-400x400.png" | ||||||
|     <img width="410px" src="img/greenlock-820x150.png"> | 		/> | ||||||
|     <div> | 		<link href="styles/main.css" rel="stylesheet" /> | ||||||
|       <br> | 		<style> | ||||||
|       <h3>Greenlock™ - Instant, Free SSL Certificates via Let's Encrypt v2</h3> | 			@font-face { | ||||||
|       <br> | 				font-family: "Source Sans Pro"; | ||||||
|       <br> | 				font-style: normal; | ||||||
|       <br> | 				font-display: block; | ||||||
|     </div> | 				font-weight: 400; | ||||||
|  | 				src: local("Source Sans Pro Regular"), local("SourceSansPro-Regular"), | ||||||
|  | 					url(./fonts/6xK3dSBYKcSV-LCoeQqfX1RYOo3qOK7l.woff2) format("woff2"); | ||||||
|  | 				unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, | ||||||
|  | 					U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, | ||||||
|  | 					U+2212, U+2215, U+FEFF, U+FFFD; | ||||||
|  | 			} | ||||||
|  | 			@font-face { | ||||||
|  | 				font-family: "Source Sans Pro"; | ||||||
|  | 				font-style: normal; | ||||||
|  | 				font-weight: 700; | ||||||
|  | 				font-display: block; | ||||||
|  | 				src: local("Source Sans Pro Bold"), local("SourceSansPro-Bold"), | ||||||
|  | 					url(./fonts/6xKydSBYKcSV-LCoeQqfX1RYOo3ig4vwlxdu.woff2) | ||||||
|  | 						format("woff2"); | ||||||
|  | 				unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, | ||||||
|  | 					U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, | ||||||
|  | 					U+2212, U+2215, U+FEFF, U+FFFD; | ||||||
|  | 			} | ||||||
|  | 		</style> | ||||||
|  | 		<link | ||||||
|  | 			rel="preload" | ||||||
|  | 			href="./app/fonts/6xKydSBYKcSV-LCoeQqfX1RYOo3ig4vwlxdu.woff2" | ||||||
|  | 			as="font" | ||||||
|  | 			crossorigin="anonymous" | ||||||
|  | 		/> | ||||||
|  | 		<link | ||||||
|  | 			rel="preload" | ||||||
|  | 			href="./app/fonts/6xK3dSBYKcSV-LCoeQqfX1RYOo3qOK7l.woff2" | ||||||
|  | 			as="font" | ||||||
|  | 			crossorigin="anonymous" | ||||||
|  | 		/> | ||||||
|  | 		<link | ||||||
|  | 			rel="prefetch" | ||||||
|  | 			href="./app/fonts/HI_SiYsKILxRpg3hIP6sJ7fM7PqlPevW.woff2" | ||||||
|  | 			as="font" | ||||||
|  | 			crossorigin="anonymous" | ||||||
|  | 		/> | ||||||
|  | 		<link rel="prefetch" href="./app/js/bluecrypt-acme.js" as="script" /> | ||||||
|  | 		<link rel="prefetch" href="./app/js/greenlock.js" as="script" /> | ||||||
|  | 		<link rel="prefetch" href="./js/app.js" as="script" /> | ||||||
|  | 	</head> | ||||||
|  | 	<body class="js-app-ready"> | ||||||
|  | 		<script> | ||||||
|  | 			document.querySelector("body").classList.remove("js-app-ready"); | ||||||
|  | 		</script> | ||||||
|  | 		<div class="column-container wide"> | ||||||
|  | 			<div class="column-row"> | ||||||
|  | 				<img alt="Greenlock logo" src="img/greenlock-146.png" /> | ||||||
|  | 			</div> | ||||||
|  | 			<div class="column-row"> | ||||||
|  | 				<h1>Get the green lock for your website</h1> | ||||||
|  | 			</div> | ||||||
|  | 			<div class="column-row"> | ||||||
|  | 				<div class="js-javascript-warning"> | ||||||
|  | 					Greenlock will process the CSR in the browser and request the | ||||||
|  | 					certificates directly from letsencrypt.org. Please enable Javascript | ||||||
|  | 					before continuing. | ||||||
|  | 				</div> | ||||||
|  | 				<form id="js-acme-form" action="./app/" method="GET"> | ||||||
|  | 					<div class="domain-psuedo-input"> | ||||||
|  | 						<span class="secure-green">Secure</span> | | ||||||
|  | 						<span class="secure-green">https:</span>//<input | ||||||
|  | 							aria-label="domains to secure" | ||||||
|  | 							id="acme-domains" | ||||||
|  | 							type="text" | ||||||
|  | 							name="acme-domains" | ||||||
|  | 							placeholder="Your domain name" | ||||||
|  | 							required | ||||||
|  | 						/> | ||||||
|  | 					</div> | ||||||
|  | 					<button type="submit">Go</button> | ||||||
|  | 					<div class="domain-subtext"> | ||||||
|  | 						Domain, subdomain, or wildcard domain | ||||||
|  | 					</div> | ||||||
| 
 | 
 | ||||||
|     <!-- Step 1 Choose Domain(s) --> | 					<div class="acme-advanced-fields"> | ||||||
|     <form class="js-acme-form js-acme-form-domains"> | 						<label | ||||||
|       <h1><label>What's your domain?</label></h1> | 							><input | ||||||
|       <h4>Certificates are valid for 90 days. Renewal is free :)</h4> | 								name="acme-api-type" | ||||||
|       <input class="js-acme-domains" type="text" placeholder="example.com,*.example.com" required> | 								type="radio" | ||||||
|       <br> | 								value="v02" | ||||||
|       <button type="submit">Next</button> | 								checked | ||||||
| 
 | 								required | ||||||
|       <br> | 							/> | ||||||
|       <br> | 							Production | ||||||
|       <br> | 						</label> | ||||||
|       <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> | 								name="acme-api-type" | ||||||
|         Testing</label> | 								type="radio" | ||||||
|       <br> | 								value="staging-v02" | ||||||
|       <input class="js-acme-directory-url" type="url" placeholder="ACME directory url"> | 								required | ||||||
|     </form> | 							/> | ||||||
| 
 | 							Testing</label | ||||||
|     <!-- Step 2 Create Account --> | 						> | ||||||
|     <form class="js-acme-form js-acme-form-account"> | 						<input | ||||||
|       <h1><label>What's your email?</label></h1> | 							id="js-acme-api-url" | ||||||
|       <input class="js-acme-account-email" type="email" placeholder="john@doe.family" required> | 							type="url" | ||||||
|       <br> | 							placeholder="ACME directory url" | ||||||
|       <br> | 						/> | ||||||
|       <label><input class="js-acme-account-tos" type="checkbox" required> | 						<br /> | ||||||
|         Agree to <a class="js-acme-tos-url" target="acme-tos">Let's Encrypt™ Terms of Service</a>?</label> | 						API Compatibility: Let's Encrypt v2 / ACME draft 15 | ||||||
|       <br> | 						<div> | ||||||
|       <br> | 							<br /> | ||||||
|       <label><input class="js-greenlock-account-tos" type="checkbox" required> | 							A | ||||||
|         Agree to <a class="js-gl-tos" target="_blank" href="./legal.html">Greenlock™ Terms of Service</a>?</label> | 							<a href="https://rootprojects.org/" target="_blank">Root</a> | ||||||
|       <br> | 							Project | | ||||||
|       <br> | 							<a | ||||||
|       <!-- | 								href="https://git.coolaj86.com/coolaj86/greenlock.html" | ||||||
|       <a href="#">advanced (use existing account)</a> | 								target="_blank" | ||||||
|       <br> | 								>View Source</a | ||||||
|       <br> | 							> | ||||||
|       --> | 							(git) | | ||||||
|       <button type="submit">Next</button> | 							<a href="https://rootprojects.org/legal/#terms" target="_blank" | ||||||
|     </form> | 								>Terms of Service</a | ||||||
| 
 | 							> | ||||||
|     <!-- Step 3 Set Challanges --> | 							| | ||||||
|     <form class="js-acme-form js-acme-form-challenges"> | 							<a href="https://rootprojects.org/legal/#privacy" target="_blank" | ||||||
| 
 | 								>Privacy Policy</a | ||||||
|       <h1>How will you validate your domain?</h1> | 							> | ||||||
|       <br> | 						</div> | ||||||
|       <label><input class="js-acme-challenge-type" name="acme-challenge-type" type="radio" value="http-01" checked required> | 					</div> | ||||||
|         File Upload to HTTP Web Server</label> | 				</form> | ||||||
|       <br> | 			</div> | ||||||
|       <label><input class="js-acme-challenge-type" name="acme-challenge-type" type="radio" value="dns-01" required> | 			<div class="column-row"> | ||||||
|         TXT Records on DNS Name Server</label> | 				<div class="why-you-need"> | ||||||
|       <br> | 					<h2>Why you need HTTPS</h2> | ||||||
| 
 | 					SSL Certificates are required for secure login, accepting payments, | ||||||
|       <div class="js-acme-challenges"> | 					and for browsers like Google Chrome to stop showing security warnings | ||||||
| 
 | 					to your users. | ||||||
|       <h2>Verify Domains & Sub-Domains</h2> | 				</div> | ||||||
| 
 | 			</div> | ||||||
|       <table class="js-acme-table-http-01"> | 			<!-- or | ||||||
|         <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> |  | ||||||
| 
 |  | ||||||
|       <div class="js-acme-wildcard"> |  | ||||||
|         <h2>Verify Wildcard Domains</h2> |  | ||||||
| 
 |  | ||||||
|         <table class="js-acme-table-wildcard"> |  | ||||||
|           <thead> |  | ||||||
|             <tr> |  | ||||||
|               <th>Hostname</th> |  | ||||||
|               <th>TXT Host</th> |  | ||||||
|               <th>TXT Value</th> |  | ||||||
|             </tr> |  | ||||||
|           </thead> |  | ||||||
|           <tbody> |  | ||||||
|             <tr> |  | ||||||
|               <td>example.com</td> |  | ||||||
|               <td>_acme-challenge.example.com</td> |  | ||||||
|               <td>4A54</td> |  | ||||||
|             </tr> |  | ||||||
|           </tbody> |  | ||||||
|         </table> |  | ||||||
|       </div> |  | ||||||
| 
 |  | ||||||
|       <button type="submit">Next</button> |  | ||||||
|     </form> |  | ||||||
| 
 |  | ||||||
|     <!-- Step 4 Process Challanges --> |  | ||||||
|     <form class="js-acme-form js-acme-form-poll"> |  | ||||||
|       Verifying Domains... (give us 5 seconds or so...) |  | ||||||
| 
 |  | ||||||
|       <!-- |  | ||||||
|       <table class="js-acme-table-verifying"> |  | ||||||
|         <thead> |  | ||||||
|           <tr> |  | ||||||
|             <th>Hostname</th> |  | ||||||
|             <th>Type</th> |  | ||||||
|             <th>Pass</th> |  | ||||||
|           </tr> |  | ||||||
|         </thead> |  | ||||||
|         <tbody> |  | ||||||
|           <tr> |  | ||||||
|             <td>example.com</td> |  | ||||||
|             <td>http-01</td> |  | ||||||
|             <td>-</td> |  | ||||||
|           </tr> |  | ||||||
|         </tbody> |  | ||||||
|       </table> |  | ||||||
| 
 |  | ||||||
|       <a href="#">advanced (use existing keypair for domain)</a> |  | ||||||
| 
 |  | ||||||
|       <button type="submit">Next</button> |  | ||||||
|       --> |  | ||||||
|     </form> |  | ||||||
| 
 |  | ||||||
|     <!-- Step 5 Get Certs --> |  | ||||||
|     <form class="js-acme-form js-acme-form-download"> |  | ||||||
|       <div> |  | ||||||
|       <h2><label>privkey.pem</label></h2> |  | ||||||
|       <textarea cols="80" rows="10" class="js-privkey">-</textarea> |  | ||||||
|       </div> |  | ||||||
| 
 |  | ||||||
|       <div> |  | ||||||
|       <h2><label>fullchain.pem</label></h2> |  | ||||||
|       <textarea cols="80" rows="60" class="js-fullchain">-</textarea> |  | ||||||
|       </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) |  | ||||||
|       <!-- 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.ppl.family --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> | ||||||
|       </small></div> |  | ||||||
|       --> |       --> | ||||||
| 
 | 
 | ||||||
|     <script src="./js/bacme.js"></script> | 			<script src="./js/app.js"></script> | ||||||
|     <script src="./js/app.js"></script> |  | ||||||
| 
 | 
 | ||||||
|     <script src="./js/pkijs.org/v1.3.33/common.js"></script> | 			<!-- Global site tag (gtag.js) - Google Analytics --> | ||||||
|     <script src="./js/pkijs.org/v1.3.33/asn1.js"></script> | 			<script | ||||||
|     <script src="./js/pkijs.org/v1.3.33/x509_schema.js"></script> | 				async | ||||||
|     <script src="./js/pkijs.org/v1.3.33/x509_simpl.js"></script> | 				src="https://www.googletagmanager.com/gtag/js?id=UA-118745161-2" | ||||||
|     <script src="./js/browser-csr/v1.0.0-alpha/csr.js"></script> | 			></script> | ||||||
|  | 			<script> | ||||||
|  | 				window.dataLayer = window.dataLayer || []; | ||||||
|  | 				function gtag() { | ||||||
|  | 					dataLayer.push(arguments); | ||||||
|  | 				} | ||||||
|  | 				gtag("js", new Date()); | ||||||
| 
 | 
 | ||||||
|   </body> | 				gtag("config", "UA-118745161-2"); | ||||||
|  | 			</script> | ||||||
|  | 		</div> | ||||||
|  | 	</body> | ||||||
| </html> | </html> | ||||||
|  | |||||||
							
								
								
									
										4
									
								
								install.bat
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								install.bat
									
									
									
									
									
										Normal 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" | ||||||
							
								
								
									
										15
									
								
								install.sh
									
									
									
									
									
								
							
							
						
						
									
										15
									
								
								install.sh
									
									
									
									
									
								
							| @ -1,14 +1,7 @@ | |||||||
| #!/bin/bash | #!/bin/bash | ||||||
| 
 | 
 | ||||||
| mkdir -p js/pkijs.org/v1.3.33/ | mkdir -p app/js/ | ||||||
| pushd 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 js/browser-csr/v1.0.0-alpha/ |  | ||||||
| pushd js/browser-csr/v1.0.0-alpha/ |  | ||||||
|   wget -c https://git.coolaj86.com/coolaj86/browser-csr.js/raw/commit/01cdc0e91b5bf03f12e1b25b4129e3cde927987c/csr.js |  | ||||||
| popd | popd | ||||||
|  | |||||||
							
								
								
									
										512
									
								
								js/app.js
									
									
									
									
									
								
							
							
						
						
									
										512
									
								
								js/app.js
									
									
									
									
									
								
							| @ -1,489 +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); | ||||||
|   var info = {}; | 	}; | ||||||
|   var steps = {}; |  | ||||||
|   var nonce; |  | ||||||
|   var kid; |  | ||||||
|   var i = 1; |  | ||||||
| 
 | 
 | ||||||
|   var apiUrl = 'https://acme-{{env}}.api.letsencrypt.org/directory'; | 	$qs(".js-javascript-warning").hidden = true; | ||||||
|   function updateApiType() { |  | ||||||
|     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() { | 	var apiUrl = "https://acme-{{env}}.api.letsencrypt.org/directory"; | ||||||
|     $qsa('.js-acme-form').forEach(function (el) { |  | ||||||
|       el.hidden = true; |  | ||||||
|     }); |  | ||||||
|   } |  | ||||||
| 
 | 
 | ||||||
|   function submitForm(ev) { | 	function updateApiType() { | ||||||
|     var j = i; | 		var formData = new FormData($qs("#js-acme-form")); | ||||||
|     i += 1; |  | ||||||
|     steps[j].submit(ev); |  | ||||||
|   } |  | ||||||
|   $qsa('.js-acme-form').forEach(function ($el) { |  | ||||||
|     $el.addEventListener('submit', function (ev) { |  | ||||||
|       ev.preventDefault(); |  | ||||||
|       submitForm(ev); |  | ||||||
|     }); |  | ||||||
|   }); |  | ||||||
|   function updateChallengeType() { |  | ||||||
|     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-table-wildcard').hidden = true; |  | ||||||
|     $qs('.js-acme-table-http-01').hidden = true; |  | ||||||
|     $qs('.js-acme-table-dns-01').hidden = true; |  | ||||||
|     if (info.challenges.wildcard) { |  | ||||||
|       $qs('.js-acme-table-wildcard').hidden = false; |  | ||||||
|     } |  | ||||||
|     if (info.challenges[input.value]) { |  | ||||||
|       $qs('.js-acme-table-' + input.value).hidden = false; |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
|   $qsa('.js-acme-challenge-type').forEach(function ($el) { |  | ||||||
|     $el.addEventListener('change', updateChallengeType); |  | ||||||
|   }); |  | ||||||
| 
 | 
 | ||||||
|   steps[1] = function () { | 		console.log("ACME api type radio:"); | ||||||
|     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() }; |  | ||||||
|     }); |  | ||||||
|     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) { | 		var value = formData.get("acme-api-type"); | ||||||
|       $qs('.js-acme-tos-url').href = directory.meta.termsOfService; | 		$qs("#js-acme-api-url").value = apiUrl.replace(/{{env}}/g, value); | ||||||
|       return BACME.nonce().then(function (_nonce) { | 	} | ||||||
|         nonce = _nonce; |  | ||||||
| 
 | 
 | ||||||
|         console.log("MAGIC STEP NUMBER in 1 is:", i); | 	$qs("#js-acme-form").addEventListener("change", updateApiType); | ||||||
|         steps[i](); | 	//$qs('#js-acme-form').addEventListener('submit', prettyRedirect);
 | ||||||
|       }); |  | ||||||
|     }); |  | ||||||
|   }; |  | ||||||
| 
 | 
 | ||||||
|   steps[2] = function () { | 	updateApiType(); | ||||||
|     hideForms(); | 	try { | ||||||
|     $qs('.js-acme-form-account').hidden = false; | 		document.fonts | ||||||
|   }; | 			.load() | ||||||
|   steps[2].submit = function () { | 			.then(function() { | ||||||
|     var email = $qs('.js-acme-account-email').value.toLowerCase().trim(); | 				$qs("body").classList.add("js-app-ready"); | ||||||
| 
 | 			}) | ||||||
|     info.contact = [ 'mailto:' + email ]; | 			.catch(function(e) { | ||||||
|     info.agree = $qs('.js-acme-account-tos').checked; | 				$qs("body").classList.add("js-app-ready"); | ||||||
|     info.greenlockAgree = $qs('.js-gl-tos').checked; | 			}); | ||||||
|     // TODO
 | 	} catch (e) { | ||||||
|     // options for
 | 		setTimeout(function() { | ||||||
|     // * regenerate key
 | 			$qs("body").classList.add("js-app-ready"); | ||||||
|     // * ECDSA / RSA / bitlength
 | 		}, 200); | ||||||
| 
 | 	} | ||||||
|     // TODO ping with version and account creation
 | })(); | ||||||
| 
 |  | ||||||
|     var jwk = JSON.parse(localStorage.getItem('account:' + email) || 'null'); |  | ||||||
|     var p; |  | ||||||
| 
 |  | ||||||
|     function createKeypair() { |  | ||||||
|       return BACME.accounts.generateKeypair({ |  | ||||||
|         type: 'ECDSA' |  | ||||||
|       , bitlength: '256' |  | ||||||
|       }).then(function (jwk) { |  | ||||||
|         localStorage.setItem('account:' + email, JSON.stringify(jwk)); |  | ||||||
|         return jwk; |  | ||||||
|       }) |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     if (jwk) { |  | ||||||
|       p = Promise.resolve(jwk); |  | ||||||
|     } else { |  | ||||||
|       p = 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 = Promise.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': [] }; |  | ||||||
|                 var map = { |  | ||||||
|                   'http-01': '.js-acme-table-http-01' |  | ||||||
|                 , 'dns-01': '.js-acme-table-dns-01' |  | ||||||
|                 , 'wildcard': '.js-acme-table-wildcard' |  | ||||||
|                 } |  | ||||||
|                 var tpls = {}; |  | ||||||
|                 info.challenges = obj; |  | ||||||
|                 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 Promise.all(claims.map(function (claim) { |  | ||||||
|                   var hostname = claim.identifier.value; |  | ||||||
|                   return Promise.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); |  | ||||||
|                         $qs(map.wildcard).innerHTML += '<tr><td>' + data.hostname + '</td><td>' + data.dnsHost + '</td><td>' + data.dnsAnswer + '</td></tr>'; |  | ||||||
|                       } else { |  | ||||||
|                         obj[data.type].push(data); |  | ||||||
|                         if ('dns-01' === data.type) { |  | ||||||
|                           $qs(map[data.type]).innerHTML += '<tr><td>' + data.hostname + '</td><td>' + data.dnsHost + '</td><td>' + data.dnsAnswer + '</td></tr>'; |  | ||||||
|                         } else if ('http-01' === data.type) { |  | ||||||
|                           $qs(map[data.type]).innerHTML += '<tr><td>' + data.hostname + '</td><td>' + data.httpPath + '</td><td>' + data.httpAuth + '</td></tr>'; |  | ||||||
|                         } else { |  | ||||||
|                           throw new Error('Unexpected type: ' + data.type); |  | ||||||
|                         } |  | ||||||
|                       } |  | ||||||
| 
 |  | ||||||
|                     }); |  | ||||||
| 
 |  | ||||||
|                   })); |  | ||||||
|                 })).then(function () { |  | ||||||
| 
 |  | ||||||
|                   // hide wildcard if no wildcard
 |  | ||||||
|                   // hide http-01 and dns-01 if only wildcard
 |  | ||||||
|                   if (!obj.wildcard.length) { |  | ||||||
|                     $qs('.js-acme-wildcard').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 \'' + i + '\' Error:'); |  | ||||||
|       console.error(err); |  | ||||||
|     }); |  | ||||||
|   }; |  | ||||||
| 
 |  | ||||||
|   steps[3] = function () { |  | ||||||
|     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 Promise(function (resolve) { |  | ||||||
|           setTimeout(resolve, 1000); |  | ||||||
|         }).then(function () { |  | ||||||
|           return Promise.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 ('valid' !== poll.status) { |  | ||||||
|                 allsWell = false; |  | ||||||
|                 console.warn('BAD POLL STATUS', poll); |  | ||||||
|               } |  | ||||||
|               // TODO show status in HTML
 |  | ||||||
|             }); |  | ||||||
| 
 |  | ||||||
|             if (polls.length) { |  | ||||||
|               return checkPolls(); |  | ||||||
|             } |  | ||||||
|             return true; |  | ||||||
|           }); |  | ||||||
|         }); |  | ||||||
|       } |  | ||||||
| 
 |  | ||||||
|       return checkPolls().then(function () { |  | ||||||
|         if (allsWell) { |  | ||||||
|           return submitForm(); |  | ||||||
|         } |  | ||||||
|       }); |  | ||||||
|     }); |  | ||||||
|   }; |  | ||||||
| 
 |  | ||||||
|   // spinner
 |  | ||||||
|   steps[4] = function () { |  | ||||||
|     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() { |  | ||||||
|       return BACME.accounts.generateKeypair({ |  | ||||||
|         type: 'ECDSA' |  | ||||||
|       , bitlength: '256' |  | ||||||
|       }).then(function (serverJwk) { |  | ||||||
|         localStorage.setItem('server:' + key, JSON.stringify(serverJwk)); |  | ||||||
|         return serverJwk; |  | ||||||
|       }) |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     if (serverJwk) { |  | ||||||
|       p = Promise.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 Promise(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').value = certs; |  | ||||||
| 
 |  | ||||||
|         // https://stackoverflow.com/questions/40314257/export-webcrypto-key-to-pem-format
 |  | ||||||
| 				function spkiToPEM(keydata){ |  | ||||||
| 						var keydataS = arrayBufferToString(keydata); |  | ||||||
| 						var keydataB64 = window.btoa(keydataS); |  | ||||||
| 						var keydataB64Pem = formatAsPem(keydataB64); |  | ||||||
| 						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) { |  | ||||||
| 						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; |  | ||||||
| 				} |  | ||||||
| 
 |  | ||||||
|         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); |  | ||||||
| 					$qs('.js-privkey').value = pem; |  | ||||||
|           steps[i](); |  | ||||||
| 				}).catch(function(err){ |  | ||||||
| 					console.error(err); |  | ||||||
| 				}); |  | ||||||
|       }); |  | ||||||
|     }); |  | ||||||
|   }; |  | ||||||
| 
 |  | ||||||
|   steps[5] = function () { |  | ||||||
|     hideForms(); |  | ||||||
|     $qs('.js-acme-form-download').hidden = false; |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   steps[1](); |  | ||||||
| 
 |  | ||||||
|   $qs('body').hidden = false; |  | ||||||
| }()); |  | ||||||
|  | |||||||
							
								
								
									
										682
									
								
								js/bacme.js
									
									
									
									
									
								
							
							
						
						
									
										682
									
								
								js/bacme.js
									
									
									
									
									
								
							| @ -1,682 +0,0 @@ | |||||||
| (function (exports) { |  | ||||||
| 'use strict'; |  | ||||||
| 
 |  | ||||||
| var BACME = exports.BACME = {}; |  | ||||||
| var webFetch = exports.fetch; |  | ||||||
| var webCrypto = exports.crypto; |  | ||||||
| 
 |  | ||||||
| var directoryUrl = 'https://acme-staging-v02.api.letsencrypt.org/directory'; |  | ||||||
| var directory; |  | ||||||
| 
 |  | ||||||
| var nonceUrl; |  | ||||||
| var nonce; |  | ||||||
| 
 |  | ||||||
| var accountKeypair; |  | ||||||
| var accountJwk; |  | ||||||
| 
 |  | ||||||
| var accountUrl; |  | ||||||
| var signedAccount; |  | ||||||
| 
 |  | ||||||
| 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 (body) { |  | ||||||
| 			directory = body; |  | ||||||
|       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(body); |  | ||||||
|       return body; |  | ||||||
| 		}); |  | ||||||
| 	}); |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| 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) { |  | ||||||
|   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" }; |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
| 	// https://github.com/diafygi/webcrypto-examples#ecdsa---generatekey
 |  | ||||||
| 	var extractable = true; |  | ||||||
| 	return webCrypto.subtle.generateKey( |  | ||||||
| 		wcOpts |  | ||||||
| 	, extractable |  | ||||||
| 	, [ 'sign', 'verify' ] |  | ||||||
| 	).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 |  | ||||||
|     ); |  | ||||||
| 
 |  | ||||||
|     // TODO RSA
 |  | ||||||
|     var protectedJson = |  | ||||||
|       { nonce: opts.nonce |  | ||||||
|       , url: accountUrl |  | ||||||
|       , alg: abstractKey.meta.alg |  | ||||||
|       , jwk: { |  | ||||||
|           kty: opts.jwk.kty |  | ||||||
|         , crv: opts.jwk.crv |  | ||||||
|         , x: opts.jwk.x |  | ||||||
|         , y: opts.jwk.y |  | ||||||
|         } |  | ||||||
|       }; |  | ||||||
|     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 account; |  | ||||||
| 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; |  | ||||||
| var signedOrder; |  | ||||||
| 
 |  | ||||||
| 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 order; |  | ||||||
| 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) { |  | ||||||
| 			challengePollUrl = reply.url; |  | ||||||
| 
 |  | ||||||
|       BACME._logBody(reply); |  | ||||||
| 
 |  | ||||||
| 			return reply; |  | ||||||
| 		}); |  | ||||||
| 	}); |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| var domainKeypair; |  | ||||||
| var domainJwk; |  | ||||||
| 
 |  | ||||||
| BACME.domains = {}; |  | ||||||
| // TODO factor out from BACME.accounts.generateKeypair
 |  | ||||||
| BACME.domains.generateKeypair = function () { |  | ||||||
| 	var extractable = true; |  | ||||||
| 	return window.crypto.subtle.generateKey( |  | ||||||
| 		{ name: "ECDSA", namedCurve: "P-256" } |  | ||||||
| 	, extractable |  | ||||||
| 	, [ 'sign', 'verify' ] |  | ||||||
| 	).then(function (result) { |  | ||||||
| 		domainKeypair = result; |  | ||||||
| 
 |  | ||||||
| 		return window.crypto.subtle.exportKey( |  | ||||||
| 			"jwk" |  | ||||||
| 		, result.privateKey |  | ||||||
| 		).then(function (jwk) { |  | ||||||
| 
 |  | ||||||
| 			domainJwk = jwk; |  | ||||||
| 			console.log('private jwk:'); |  | ||||||
| 			console.log(JSON.stringify(jwk, null, 2)); |  | ||||||
| 
 |  | ||||||
|       return domainKeypair; |  | ||||||
| 		}) |  | ||||||
| 	}); |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| // { 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) { |  | ||||||
| 				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) { |  | ||||||
|       BACME._logBody(reply); |  | ||||||
| 
 |  | ||||||
|       return reply; |  | ||||||
|     }); |  | ||||||
|   }); |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| }(window)); |  | ||||||
							
								
								
									
										315
									
								
								legal.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										315
									
								
								legal.html
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,315 @@ | |||||||
|  | <!DOCTYPE html> | ||||||
|  | <html> | ||||||
|  | 	<head> | ||||||
|  | 		<title>Root Legal</title> | ||||||
|  | 	</head> | ||||||
|  | 	<body> | ||||||
|  | 		<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> | ||||||
|  | 
 | ||||||
|  | 		<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> | ||||||
|  | 
 | ||||||
|  | 		<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: | ||||||
|  | 		</p> | ||||||
|  | 
 | ||||||
|  | 		<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:</p> | ||||||
|  | 
 | ||||||
|  | 		<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. | ||||||
|  | 		</p> | ||||||
|  | 
 | ||||||
|  | 		<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> | ||||||
|  | 
 | ||||||
|  | 		<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> | ||||||
|  | 
 | ||||||
|  | 		<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 are in | ||||||
|  | 			conflict with our mission or these values, please let us know. | ||||||
|  | 		</p> | ||||||
|  | 
 | ||||||
|  | 		<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> | ||||||
|  | 
 | ||||||
|  | 		<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> | ||||||
|  | 
 | ||||||
|  | 		<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> | ||||||
|  | 
 | ||||||
|  | 		<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> | ||||||
|  | 
 | ||||||
|  | 		<p> | ||||||
|  | 			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> | ||||||
|  | 
 | ||||||
|  | 		<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> | ||||||
|  | 
 | ||||||
|  | 		<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> | ||||||
|  | 
 | ||||||
|  | 		<p> | ||||||
|  | 			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 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> | ||||||
|  | 
 | ||||||
|  | 		<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> | ||||||
|  | 
 | ||||||
|  | 		<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> | ||||||
|  | 
 | ||||||
|  | 		<p> | ||||||
|  | 			If at any time you feel that our Terms of Service are in conflict with our | ||||||
|  | 			mission or values, please let us know. | ||||||
|  | 		</p> | ||||||
|  | 
 | ||||||
|  | 		<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> | ||||||
|  | 
 | ||||||
|  | 		<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> | ||||||
|  | 
 | ||||||
|  | 		<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> | ||||||
|  | 
 | ||||||
|  | 		<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> | ||||||
|  | 
 | ||||||
|  | 		<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> | ||||||
|  | 
 | ||||||
|  | 		<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> | ||||||
|  | 
 | ||||||
|  | 		<p> | ||||||
|  | 			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> | ||||||
|  | 
 | ||||||
|  | 		<p>What we collect and (more importantly) <em>Why</em>:</p> | ||||||
|  | 
 | ||||||
|  | 		<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. All four of us want to be personable if and when we reach | ||||||
|  | 			out. | ||||||
|  | 		</p> | ||||||
|  | 
 | ||||||
|  | 		<p><strong>Email</strong>:</p> | ||||||
|  | 		<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. 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> | ||||||
|  | 
 | ||||||
|  | 		<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> | ||||||
|  | 
 | ||||||
|  | 		<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> | ||||||
|  | 
 | ||||||
|  | 		<p><strong>Telemetry</strong>:</p> | ||||||
|  | 		<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> | ||||||
|  | 
 | ||||||
|  | 		<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> | ||||||
|  | 
 | ||||||
|  | 		<p><strong>Other</strong>:</p> | ||||||
|  | 		<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> | ||||||
|  | 
 | ||||||
|  | 		<p> | ||||||
|  | 			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 /> | ||||||
|  | 		<p>Copyright 2018 AJ ONeal</p> | ||||||
|  | 	</body> | ||||||
|  | </html> | ||||||
							
								
								
									
										1
									
								
								legal/index.html
									
									
									
									
									
										Symbolic link
									
								
							
							
						
						
									
										1
									
								
								legal/index.html
									
									
									
									
									
										Symbolic link
									
								
							| @ -0,0 +1 @@ | |||||||
|  | ../legal.html | ||||||
							
								
								
									
										115
									
								
								styles/main.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										115
									
								
								styles/main.css
									
									
									
									
									
										Normal file
									
								
							| @ -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; | ||||||
|  | 	cursor: pointer; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .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: 0.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…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user