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
									
								
								.prettierrc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								.prettierrc
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,4 @@ | ||||
| { | ||||
|   "trailingComma": "none", | ||||
|   "useTabs": true | ||||
| } | ||||
							
								
								
									
										15
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										15
									
								
								README.md
									
									
									
									
									
								
							| @ -1,10 +1,8 @@ | ||||
| Greenlock™ in your Browser | ||||
| ========================= | ||||
| # Greenlock™ in your Browser | ||||
| 
 | ||||
| Taking greenlock™ (Let's Encrypt v2 / ACME client) where it's never been before: Your browser! | ||||
| 
 | ||||
| Official Site | ||||
| ============= | ||||
| # Official Site | ||||
| 
 | ||||
| This app is available at <https://greenlock.domains>. | ||||
| 
 | ||||
| @ -14,14 +12,12 @@ If it doesn't, please open an issue to let us know why. | ||||
| We'd much rather improve the app than have a hundred different versions running in the wild. | ||||
| 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, | ||||
| please do provide attribution, but please also use your branding. | ||||
| 
 | ||||
| Install | ||||
| ======= | ||||
| # Install | ||||
| 
 | ||||
| ```bash | ||||
| git clone ssh://gitea@git.coolaj86.com:22042/coolaj86/greenlock.html.git | ||||
| @ -30,8 +26,7 @@ pushd greenlock.html/ | ||||
| popd | ||||
| ``` | ||||
| 
 | ||||
| Usage | ||||
| ===== | ||||
| # Usage | ||||
| 
 | ||||
| Simply host from your webserver. | ||||
| 
 | ||||
|  | ||||
							
								
								
									
										766
									
								
								app/index.html
									
									
									
									
									
								
							
							
						
						
									
										766
									
								
								app/index.html
									
									
									
									
									
								
							| @ -1,244 +1,403 @@ | ||||
| <!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> | ||||
| 	<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 href="styles/main.css" rel="stylesheet" /> | ||||
| 		<link | ||||
| 			rel="preload" | ||||
| 			href="./fonts/6xKydSBYKcSV-LCoeQqfX1RYOo3ig4vwlxdu.woff2" | ||||
| 			as="font" | ||||
| 			crossorigin="anonymous" | ||||
| 		/> | ||||
| 		<link | ||||
| 			rel="preload" | ||||
| 			href="./fonts/6xK3dSBYKcSV-LCoeQqfX1RYOo3qOK7l.woff2" | ||||
| 			as="font" | ||||
| 			crossorigin="anonymous" | ||||
| 		/> | ||||
| 
 | ||||
|     <link rel="preload" href="./fonts/HI_SiYsKILxRpg3hIP6sJ7fM7PqlPevW.woff2" as="font" crossorigin="anonymous"> | ||||
|     <link rel="preload" href="./js/bacme.js" as="script"> | ||||
|     <link rel="preload" href="./js/app.js" as="script"> | ||||
|     <link rel="preload" href="./js/pkijs.org/v1.3.33/common.js" as="script"> | ||||
|     <link rel="preload" href="./js/pkijs.org/v1.3.33/asn1.js" as="script"> | ||||
|     <link rel="preload" href="./js/pkijs.org/v1.3.33/x509_schema.js" as="script"> | ||||
|     <link rel="preload" href="./js/pkijs.org/v1.3.33/x509_simpl.js" as="script"> | ||||
|     <link rel="preload" href="./js/browser-csr/v1.0.0-alpha/csr.js" as="script"> | ||||
| 		<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 --> | ||||
| 
 | ||||
|   </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"> | ||||
| 		<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>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"> | ||||
|               <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> | ||||
| 				</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> | ||||
| 					<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> | ||||
|           <!-- | ||||
| 				<!-- Step 2 Create Account --> | ||||
| 				<form class="js-acme-form js-acme-form-account"> | ||||
| 					<h1><label>What's your email?</label></h1> | ||||
| 					<input | ||||
| 						class="js-acme-account-email acme-account-email" | ||||
| 						type="email" | ||||
| 						placeholder="john@doe.family" | ||||
| 						required | ||||
| 					/> | ||||
| 					<div class="checkbox-array"> | ||||
| 						<label> | ||||
| 							<input class="js-acme-account-tos" type="checkbox" required /> | ||||
| 							<svg | ||||
| 								class="icon-checked-box" | ||||
| 								xmlns="http://www.w3.org/2000/svg" | ||||
| 								viewbox="0 0 24 24" | ||||
| 							> | ||||
| 								<use xlink:href="#svg-checked"></use> | ||||
| 							</svg> | ||||
| 							<svg | ||||
| 								class="icon-unchecked-box" | ||||
| 								xmlns="http://www.w3.org/2000/svg" | ||||
| 								viewbox="0 0 24 24" | ||||
| 							> | ||||
| 								<use xlink:href="#svg-unchecked"></use> | ||||
| 							</svg> | ||||
| 							Agree to  <a class="js-acme-tos-url" target="acme-tos" | ||||
| 								>Let's Encrypt™ Terms of Service</a | ||||
| 							>? | ||||
| 						</label> | ||||
| 						<label> | ||||
| 							<input | ||||
| 								class="js-greenlock-account-tos" | ||||
| 								type="checkbox" | ||||
| 								required | ||||
| 							/> | ||||
| 							<svg | ||||
| 								class="icon-checked-box" | ||||
| 								xmlns="http://www.w3.org/2000/svg" | ||||
| 								viewbox="0 0 24 24" | ||||
| 							> | ||||
| 								<use xlink:href="#svg-checked"></use> | ||||
| 							</svg> | ||||
| 							<svg | ||||
| 								class="icon-unchecked-box" | ||||
| 								xmlns="http://www.w3.org/2000/svg" | ||||
| 								viewbox="0 0 24 24" | ||||
| 							> | ||||
| 								<use xlink:href="#svg-unchecked"></use> | ||||
| 							</svg> | ||||
| 							Agree to  <a | ||||
| 								class="js-gl-tos" | ||||
| 								target="_blank" | ||||
| 								href="/legal/#terms" | ||||
| 								>Greenlock™ Terms of Service</a | ||||
| 							>? | ||||
| 						</label> | ||||
| 					</div> | ||||
| 					<!-- | ||||
|           <a href="#">advanced (use existing account)</a> | ||||
|           <br> | ||||
|           <br> | ||||
|           --> | ||||
|           <button class="button-next" type="submit">Next</button> | ||||
|           <div class="email-usage"> | ||||
|             Why do we need your email? We link your SSL certificates to the | ||||
|             email you use so you can manage your certificates in the future, | ||||
|             and get important email updates about them. | ||||
|           </div> | ||||
|         </form> | ||||
| 					<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"> | ||||
| 				<!-- 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> | ||||
| 
 | ||||
|           <h1>Let's verify your domain</h1> | ||||
|           <div class="js-acme-challenges"> | ||||
|           <div class="tabbed-selector"> | ||||
|             <label> | ||||
|               <input class="js-acme-challenge-type" name="acme-challenge-type" type="radio" value="http-01" checked required> | ||||
|               File Upload | ||||
|               <div></div> | ||||
|             </label> | ||||
|             <label> | ||||
|               <input class="js-acme-challenge-type" name="acme-challenge-type" type="radio" value="dns-01" required> | ||||
|               DNS Record | ||||
|               <div></div> | ||||
|             </label> | ||||
|           </div> | ||||
|             <div class="js-acme-verification-http-01"> | ||||
|               <h3>Upload this file</h3> | ||||
|               <div class="http-verification-info file-preview"> | ||||
|                 <div class="paper-fold"></div> | ||||
|                 <div> | ||||
|                   <div class="file-ver-info-header">FILENAME</div> | ||||
|                   <pre class="js-acme-ver-file-location">...loading</pre> | ||||
|                 </div> | ||||
|                 <hr> | ||||
|                 <div> | ||||
|                   <div class="file-ver-info-header">CONTENTS</div> | ||||
|                   <pre class="js-acme-ver-content">...loading</pre> | ||||
|                 </div> | ||||
|               </div> | ||||
|               <div class="download-file"> | ||||
|                 <svg class="mdicon icon-download" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"> | ||||
|                   <use xlink:href="#svg-download"></use> | ||||
|                 </svg> | ||||
|                 <a class="js-download-verify-link" href="data:text/octet-stream;base64,SGVsbG8gV29ybGQuLi4=" download="hello.txt" target="_blank"> | ||||
|                   Download | ||||
|                 </a> | ||||
|               </div> | ||||
|               <h3>To this location</h3> | ||||
|               <div class="js-acme-ver-uri" class="acme-ver-uri">..loading</div> | ||||
|             </div> | ||||
|             <div class="js-acme-verification-dns-01"> | ||||
|               <h3>Set this DNS Record</h3> | ||||
|               <div class="acme-ver-dns-label">Hostname</div> | ||||
|               <div class="js-acme-ver-hostname">loading...</div> | ||||
|               <div class="acme-ver-dns-label">TXT Host</div> | ||||
|               <div class="js-acme-ver-txt-host">loading...</div> | ||||
|               <div class="acme-ver-dns-label">TXT Value</div> | ||||
|               <div class="js-acme-ver-txt-value">loading...</div> | ||||
|             </div> | ||||
| 						<div 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> | ||||
| 
 | ||||
|           </div> | ||||
|           <div class="js-acme-wildcard-challenges"> | ||||
|             <div class="js-acme-wildcard"> | ||||
|               <div class="js-acme-verification-wildcard"> | ||||
|                 <h3>Set this DNS Record</h3> | ||||
|                 <div class="acme-ver-dns-label">Hostname</div> | ||||
|                 <div class="js-acme-ver-hostname">loading...</div> | ||||
|                 <div class="acme-ver-dns-label">TXT Host</div> | ||||
|                 <div class="js-acme-ver-txt-host">loading...</div> | ||||
|                 <div class="acme-ver-dns-label">TXT Value</div> | ||||
|                 <div class="js-acme-ver-txt-value">loading...</div> | ||||
|               </div> | ||||
|             </div> | ||||
|           </div> | ||||
| 					<button class="button-next" type="submit">Next</button> | ||||
| 				</form> | ||||
| 
 | ||||
|           <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> | ||||
| 
 | ||||
|         <!-- 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> | ||||
| @ -260,42 +419,58 @@ | ||||
| 
 | ||||
|           <button type="submit">Next</button> | ||||
|           --> | ||||
|         </form> | ||||
| 				</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'; | ||||
| 				<!-- 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({ | ||||
| @ -307,9 +482,9 @@ | ||||
|       console.log('Listening on', this.address()); | ||||
|     }) | ||||
|             </code></pre> | ||||
|           </div> | ||||
| 						</div> | ||||
| 
 | ||||
|           <!-- | ||||
| 						<!-- | ||||
|             TODO | ||||
|           <label>cert.pem</label> | ||||
|           <textarea class="js-cert">-</textarea> | ||||
| @ -322,45 +497,60 @@ | ||||
|           <a href="#">Advanced (copy and paste)</a> | ||||
|           <br> | ||||
|           <button type="submit">Start Over</button> | ||||
|           --> | ||||
|         </form> | ||||
|       </div> | ||||
|           --></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 | ||||
| 				<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> | ||||
| 						</center></small | ||||
| 					> | ||||
| 				</div> | ||||
| 				<br /> | ||||
| 
 | ||||
| 				<script src="./js/bluecrypt-acme.js"></script> | ||||
| 				<script src="./js/greenlock.js"></script> | ||||
| 
 | ||||
|         <script src="./js/bacme.js"></script> | ||||
|         <script src="./js/app.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()); | ||||
| 
 | ||||
|         <script src="./js/pkijs.org/v1.3.33/common.js"></script> | ||||
|         <script src="./js/pkijs.org/v1.3.33/asn1.js"></script> | ||||
|         <script src="./js/pkijs.org/v1.3.33/x509_schema.js"></script> | ||||
|         <script src="./js/pkijs.org/v1.3.33/x509_simpl.js"></script> | ||||
|         <script src="./js/browser-csr/v1.0.0-alpha/csr.js"></script> | ||||
| 
 | ||||
|         <!-- Global site tag (gtag.js) - Google Analytics --> | ||||
|         <script async src="https://www.googletagmanager.com/gtag/js?id=UA-118745161-2"></script> | ||||
|         <script> | ||||
|           window.dataLayer = window.dataLayer || []; | ||||
|           function gtag(){dataLayer.push(arguments);} | ||||
|           gtag('js', new Date()); | ||||
| 
 | ||||
|           gtag('config', 'UA-118745161-2'); | ||||
|         </script> | ||||
|       </div> | ||||
|     </div> | ||||
|   </body> | ||||
| 					gtag("config", "UA-118745161-2"); | ||||
| 				</script> | ||||
| 			</div> | ||||
| 		</div> | ||||
| 	</body> | ||||
| </html> | ||||
|  | ||||
							
								
								
									
										670
									
								
								app/js/app.js
									
									
									
									
									
								
							
							
						
						
									
										670
									
								
								app/js/app.js
									
									
									
									
									
								
							| @ -1,670 +0,0 @@ | ||||
| (function () { | ||||
| 'use strict'; | ||||
| 
 | ||||
|   /*global URLSearchParams,Headers*/ | ||||
|   var BROWSER_SUPPORTS_ECDSA; | ||||
|   var $qs = function (s) { return window.document.querySelector(s); }; | ||||
|   var $qsa = function (s) { return window.document.querySelectorAll(s); }; | ||||
|   var info = {}; | ||||
|   var steps = {}; | ||||
|   var nonce; | ||||
|   var kid; | ||||
|   var i = 1; | ||||
|   var BACME = window.BACME; | ||||
|   var PromiseA = window.Promise; | ||||
|   var crypto = window.crypto; | ||||
| 
 | ||||
|   function testEcdsaSupport() { | ||||
|     var opts = { | ||||
|       type: 'ECDSA' | ||||
|     , bitlength: '256' | ||||
|     }; | ||||
|     return BACME.accounts.generateKeypair(opts).then(function (jwk) { | ||||
|       return crypto.subtle.importKey( | ||||
|         "jwk" | ||||
|       , jwk | ||||
|       , { name: "ECDSA", namedCurve: "P-256" } | ||||
|       , true | ||||
|       , ["sign"] | ||||
|       ).then(function (privateKey) { | ||||
|         return window.crypto.subtle.exportKey("pkcs8", privateKey); | ||||
|       }); | ||||
|     }); | ||||
|   } | ||||
|   function testRsaSupport() { | ||||
|     var opts = { | ||||
|       type: 'RSA' | ||||
|     , bitlength: '2048' | ||||
|     }; | ||||
|     return BACME.accounts.generateKeypair(opts).then(function (jwk) { | ||||
|       return crypto.subtle.importKey( | ||||
|         "jwk" | ||||
|       , jwk | ||||
|       , { name: "RSASSA-PKCS1-v1_5", hash: { name: "SHA-256" } } | ||||
|       , true | ||||
|       , ["sign"] | ||||
|       ).then(function (privateKey) { | ||||
|         return window.crypto.subtle.exportKey("pkcs8", privateKey); | ||||
|       }); | ||||
|     }); | ||||
|   } | ||||
|   function testKeypairSupport() { | ||||
|     return testEcdsaSupport().then(function () { | ||||
|       console.info("[crypto] ECDSA is supported"); | ||||
|       BROWSER_SUPPORTS_ECDSA = true; | ||||
|       localStorage.setItem('version', '1'); | ||||
|       return true; | ||||
|     }).catch(function () { | ||||
|       console.warn("[crypto] ECDSA is NOT fully supported"); | ||||
|       BROWSER_SUPPORTS_ECDSA = false; | ||||
| 
 | ||||
|       // fix previous firefox browsers
 | ||||
|       if (!localStorage.getItem('version')) { | ||||
|         localStorage.clear(); | ||||
|         localStorage.setItem('version', '1'); | ||||
|       } | ||||
| 
 | ||||
|       return false; | ||||
|     }); | ||||
|   } | ||||
|   testKeypairSupport().then(function (ecdsaSupport) { | ||||
|     if (ecdsaSupport) { | ||||
|       return true; | ||||
|     } | ||||
| 
 | ||||
|     return testRsaSupport().then(function () { | ||||
|       console.info('[crypto] RSA is supported'); | ||||
|     }).catch(function (err) { | ||||
|       console.error('[crypto] could not use either EC nor RSA.'); | ||||
|       console.error(err); | ||||
|       window.alert("Your browser is cryptography support (neither RSA or EC is usable). Please use Chrome, Firefox, or Safari."); | ||||
|     }); | ||||
|   }); | ||||
| 
 | ||||
|   var apiUrl = 'https://acme-{{env}}.api.letsencrypt.org/directory'; | ||||
|   function updateApiType() { | ||||
|     console.log("type updated"); | ||||
|     /*jshint validthis: true */ | ||||
|     var input = this || Array.prototype.filter.call( | ||||
|       $qsa('.js-acme-api-type'), function ($el) { return $el.checked; } | ||||
|     )[0]; | ||||
|     console.log('ACME api type radio:', input.value); | ||||
|     $qs('.js-acme-directory-url').value = apiUrl.replace(/{{env}}/g, input.value); | ||||
|   } | ||||
|   $qsa('.js-acme-api-type').forEach(function ($el) { | ||||
|     $el.addEventListener('change', updateApiType); | ||||
|   }); | ||||
|   updateApiType(); | ||||
| 
 | ||||
|   function hideForms() { | ||||
|     $qsa('.js-acme-form').forEach(function (el) { | ||||
|       el.hidden = true; | ||||
|     }); | ||||
|   } | ||||
| 
 | ||||
|   function updateProgress(currentStep) { | ||||
|     var progressSteps = $qs("#js-progress-bar").children; | ||||
|     for(var j = 0; j < progressSteps.length; j++) { | ||||
|       if(j < currentStep) { | ||||
|         progressSteps[j].classList.add("js-progress-step-complete"); | ||||
|         progressSteps[j].classList.remove("js-progress-step-started"); | ||||
|       } else if(j === currentStep) { | ||||
|         progressSteps[j].classList.remove("js-progress-step-complete"); | ||||
|         progressSteps[j].classList.add("js-progress-step-started"); | ||||
|       } else { | ||||
|         progressSteps[j].classList.remove("js-progress-step-complete"); | ||||
|         progressSteps[j].classList.remove("js-progress-step-started"); | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   function submitForm(ev) { | ||||
|     var j = i; | ||||
|     i += 1; | ||||
| 
 | ||||
|     return PromiseA.resolve(steps[j].submit(ev)).catch(function (err) { | ||||
|       console.error(err); | ||||
|       window.alert("Something went wrong. It's our fault not yours. Please email aj@rootprojects.org and let him know that 'step " + j + "' failed."); | ||||
|     }); | ||||
|   } | ||||
| 
 | ||||
|   $qsa('.js-acme-form').forEach(function ($el) { | ||||
|     $el.addEventListener('submit', function (ev) { | ||||
|       ev.preventDefault(); | ||||
|       submitForm(ev); | ||||
|     }); | ||||
|   }); | ||||
| 
 | ||||
|   function updateChallengeType() { | ||||
|     /*jshint validthis: true*/ | ||||
|     var input = this || Array.prototype.filter.call( | ||||
|       $qsa('.js-acme-challenge-type'), function ($el) { return $el.checked; } | ||||
|     )[0]; | ||||
|     console.log('ch type radio:', input.value); | ||||
|     $qs('.js-acme-verification-wildcard').hidden = true; | ||||
|     $qs('.js-acme-verification-http-01').hidden = true; | ||||
|     $qs('.js-acme-verification-dns-01').hidden = true; | ||||
|     if (info.challenges.wildcard) { | ||||
|       $qs('.js-acme-verification-wildcard').hidden = false; | ||||
|     } | ||||
|     if (info.challenges[input.value]) { | ||||
|       $qs('.js-acme-verification-' + input.value).hidden = false; | ||||
|     } | ||||
|   } | ||||
|   $qsa('.js-acme-challenge-type').forEach(function ($el) { | ||||
|     $el.addEventListener('change', updateChallengeType); | ||||
|   }); | ||||
| 
 | ||||
|   function saveContact(email, domains) { | ||||
|     // to be used for good, not evil
 | ||||
|     return window.fetch('https://api.rootprojects.org/api/rootprojects.org/public/community', { | ||||
|       method: 'POST' | ||||
|     , cors: true | ||||
|     , headers: new Headers({ 'Content-Type': 'application/json' }) | ||||
|     , body: JSON.stringify({ | ||||
|         address: email | ||||
|       , project: 'greenlock-domains@rootprojects.org' | ||||
|       , domain: domains.join(',') | ||||
|       }) | ||||
|     }).catch(function (err) { | ||||
|       console.error(err); | ||||
|     }); | ||||
|   } | ||||
| 
 | ||||
|   steps[1] = function () { | ||||
|     updateProgress(0); | ||||
|     hideForms(); | ||||
|     $qs('.js-acme-form-domains').hidden = false; | ||||
|   }; | ||||
|   steps[1].submit = function () { | ||||
|     info.identifiers = $qs('.js-acme-domains').value.split(/\s*,\s*/g).map(function (hostname) { | ||||
|       return { type: 'dns', value: hostname.toLowerCase().trim() }; | ||||
|     }).slice(0,1); //Disable multiple values for now.  We'll just take the first and work with it.
 | ||||
|     info.identifiers.sort(function (a, b) { | ||||
|       if (a === b) { return 0; } | ||||
|       if (a < b) { return 1; } | ||||
|       if (a > b) { return -1; } | ||||
|     }); | ||||
| 
 | ||||
|     return BACME.directory({ directoryUrl: $qs('.js-acme-directory-url').value }).then(function (directory) { | ||||
|       $qs('.js-acme-tos-url').href = directory.meta.termsOfService; | ||||
|       return BACME.nonce().then(function (_nonce) { | ||||
|         nonce = _nonce; | ||||
| 
 | ||||
|         console.log("MAGIC STEP NUMBER in 1 is:", i); | ||||
|         steps[i](); | ||||
|       }); | ||||
|     }); | ||||
|   }; | ||||
| 
 | ||||
|   steps[2] = function () { | ||||
|     updateProgress(0); | ||||
|     hideForms(); | ||||
|     $qs('.js-acme-form-account').hidden = false; | ||||
|   }; | ||||
|   steps[2].submit = function () { | ||||
|     var email = $qs('.js-acme-account-email').value.toLowerCase().trim(); | ||||
| 
 | ||||
|     info.contact = [ 'mailto:' + email ]; | ||||
|     info.agree = $qs('.js-acme-account-tos').checked; | ||||
|     info.greenlockAgree = $qs('.js-gl-tos').checked; | ||||
|     // TODO
 | ||||
|     // options for
 | ||||
|     // * regenerate key
 | ||||
|     // * ECDSA / RSA / bitlength
 | ||||
| 
 | ||||
|     // TODO ping with version and account creation
 | ||||
|     setTimeout(saveContact, 100, email, info.identifiers.map(function (ident) { return ident.value; })); | ||||
| 
 | ||||
|     var jwk = JSON.parse(localStorage.getItem('account:' + email) || 'null'); | ||||
|     var p; | ||||
| 
 | ||||
|     function createKeypair() { | ||||
|       var opts; | ||||
| 
 | ||||
|       if(BROWSER_SUPPORTS_ECDSA) { | ||||
|         opts = { | ||||
|           type: 'ECDSA' | ||||
|         , bitlength: '256' | ||||
|         }; | ||||
|       } else { | ||||
|         opts = { | ||||
|           type: 'RSA' | ||||
|         , bitlength: '2048' | ||||
|         }; | ||||
|       } | ||||
| 
 | ||||
|       return BACME.accounts.generateKeypair(opts).then(function (jwk) { | ||||
|         localStorage.setItem('account:' + email, JSON.stringify(jwk)); | ||||
|         return jwk; | ||||
|       }); | ||||
|     } | ||||
| 
 | ||||
|     if (jwk) { | ||||
|       p = PromiseA.resolve(jwk); | ||||
|     } else { | ||||
|       p = testKeypairSupport().then(createKeypair); | ||||
|     } | ||||
| 
 | ||||
|     function createAccount(jwk) { | ||||
|       console.log('account jwk:'); | ||||
|       console.log(jwk); | ||||
|       delete jwk.key_ops; | ||||
|       info.jwk = jwk; | ||||
|       return BACME.accounts.sign({ | ||||
|         jwk: jwk | ||||
|       , contacts: [ 'mailto:' + email ] | ||||
|       , agree: info.agree | ||||
|       , nonce: nonce | ||||
|       , kid: kid | ||||
|       }).then(function (signedAccount) { | ||||
|         return BACME.accounts.set({ | ||||
|           signedAccount: signedAccount | ||||
|         }).then(function (account) { | ||||
|           console.log('account:'); | ||||
|           console.log(account); | ||||
|           kid = account.kid; | ||||
|           return kid; | ||||
|         }); | ||||
|       }); | ||||
|     } | ||||
| 
 | ||||
|     return p.then(function (_jwk) { | ||||
|       jwk = _jwk; | ||||
|       kid = JSON.parse(localStorage.getItem('account-kid:' + email) || 'null'); | ||||
|       var p2; | ||||
| 
 | ||||
|       // TODO save account id rather than always retrieving it
 | ||||
|       if (kid) { | ||||
|         p2 = PromiseA.resolve(kid); | ||||
|       } else { | ||||
|         p2 = createAccount(jwk); | ||||
|       } | ||||
| 
 | ||||
|       return p2.then(function (_kid) { | ||||
|         kid = _kid; | ||||
|         info.kid = kid; | ||||
|         return BACME.orders.sign({ | ||||
|           jwk: jwk | ||||
|         , identifiers: info.identifiers | ||||
|         , kid: kid | ||||
|         }).then(function (signedOrder) { | ||||
|           return BACME.orders.create({ | ||||
|             signedOrder: signedOrder | ||||
|           }).then(function (order) { | ||||
|             info.finalizeUrl = order.finalize; | ||||
|             info.orderUrl = order.url; // from header Location ???
 | ||||
|             return BACME.thumbprint({ jwk: jwk }).then(function (thumbprint) { | ||||
|               return BACME.challenges.all().then(function (claims) { | ||||
|                 console.log('claims:'); | ||||
|                 console.log(claims); | ||||
|                 var obj = { 'dns-01': [], 'http-01': [], 'wildcard': [] }; | ||||
|                 info.challenges = obj; | ||||
|                 var map = { | ||||
|                   'http-01': '.js-acme-verification-http-01' | ||||
|                 , 'dns-01': '.js-acme-verification-dns-01' | ||||
|                 , 'wildcard': '.js-acme-verification-wildcard' | ||||
|                 }; | ||||
| 
 | ||||
|                 /* | ||||
|                 var tpls = {}; | ||||
|                 Object.keys(map).forEach(function (k) { | ||||
|                   var sel = map[k] + ' tbody'; | ||||
|                   console.log(sel); | ||||
|                   tpls[k] = $qs(sel).innerHTML; | ||||
|                   $qs(map[k] + ' tbody').innerHTML = ''; | ||||
|                 }); | ||||
|                 */ | ||||
| 
 | ||||
|                 // TODO make Promise-friendly
 | ||||
|                 return PromiseA.all(claims.map(function (claim) { | ||||
|                   var hostname = claim.identifier.value; | ||||
|                   return PromiseA.all(claim.challenges.map(function (c) { | ||||
|                     var keyAuth = BACME.challenges['http-01']({ | ||||
|                       token: c.token | ||||
|                     , thumbprint: thumbprint | ||||
|                     , challengeDomain: hostname | ||||
|                     }); | ||||
|                     return BACME.challenges['dns-01']({ | ||||
|                       keyAuth: keyAuth.value | ||||
|                     , challengeDomain: hostname | ||||
|                     }).then(function (dnsAuth) { | ||||
|                       var data = { | ||||
|                         type: c.type | ||||
|                       , hostname: hostname | ||||
|                       , url: c.url | ||||
|                       , token: c.token | ||||
|                       , keyAuthorization: keyAuth | ||||
|                       , httpPath: keyAuth.path | ||||
|                       , httpAuth: keyAuth.value | ||||
|                       , dnsType: dnsAuth.type | ||||
|                       , dnsHost: dnsAuth.host | ||||
|                       , dnsAnswer: dnsAuth.answer | ||||
|                       }; | ||||
| 
 | ||||
|                       console.log(''); | ||||
|                       console.log('CHALLENGE'); | ||||
|                       console.log(claim); | ||||
|                       console.log(c); | ||||
|                       console.log(data); | ||||
|                       console.log(''); | ||||
| 
 | ||||
|                       if (claim.wildcard) { | ||||
|                         obj.wildcard.push(data); | ||||
|                         let verification = $qs(".js-acme-verification-wildcard"); | ||||
|                         verification.querySelector(".js-acme-ver-hostname").innerHTML = data.hostname; | ||||
|                         verification.querySelector(".js-acme-ver-txt-host").innerHTML = data.dnsHost; | ||||
|                         verification.querySelector(".js-acme-ver-txt-value").innerHTML = data.dnsAnswer; | ||||
| 
 | ||||
|                       } else if(obj[data.type]) { | ||||
| 
 | ||||
|                         obj[data.type].push(data); | ||||
| 
 | ||||
|                         if ('dns-01' === data.type) { | ||||
|                           let verification = $qs(".js-acme-verification-dns-01"); | ||||
|                           verification.querySelector(".js-acme-ver-hostname").innerHTML = data.hostname; | ||||
|                           verification.querySelector(".js-acme-ver-txt-host").innerHTML = data.dnsHost; | ||||
|                           verification.querySelector(".js-acme-ver-txt-value").innerHTML = data.dnsAnswer; | ||||
|                         } else if ('http-01' === data.type) { | ||||
|                           $qs(".js-acme-ver-file-location").innerHTML = data.httpPath.split("/").slice(-1); | ||||
|                           $qs(".js-acme-ver-content").innerHTML = data.httpAuth; | ||||
|                           $qs(".js-acme-ver-uri").innerHTML = data.httpPath; | ||||
|                           $qs(".js-download-verify-link").href = | ||||
|                             "data:text/octet-stream;base64," + window.btoa(data.httpAuth); | ||||
|                           $qs(".js-download-verify-link").download = data.httpPath.split("/").slice(-1); | ||||
|                         } | ||||
|                       } | ||||
| 
 | ||||
|                     }); | ||||
| 
 | ||||
|                   })); | ||||
|                 })).then(function () { | ||||
| 
 | ||||
|                   // hide wildcard if no wildcard
 | ||||
|                   // hide http-01 and dns-01 if only wildcard
 | ||||
|                   if (!obj.wildcard.length) { | ||||
|                     $qs('.js-acme-wildcard-challenges').hidden = true; | ||||
|                   } | ||||
|                   if (!obj['http-01'].length) { | ||||
|                     $qs('.js-acme-challenges').hidden = true; | ||||
|                   } | ||||
| 
 | ||||
|                   updateChallengeType(); | ||||
| 
 | ||||
|                   console.log("MAGIC STEP NUMBER in 2 is:", i); | ||||
|                   steps[i](); | ||||
|                 }); | ||||
| 
 | ||||
|               }); | ||||
|             }); | ||||
|           }); | ||||
|         }); | ||||
|       }); | ||||
|     }).catch(function (err) { | ||||
|       console.error('Step \'\' Error:'); | ||||
|       console.error(err, err.stack); | ||||
|       window.alert("An error happened at Step " + i + ", but it's not your fault. Email aj@rootprojects.org and let him know."); | ||||
|     }); | ||||
|   }; | ||||
| 
 | ||||
|   steps[3] = function () { | ||||
|     updateProgress(1); | ||||
|     hideForms(); | ||||
|     $qs('.js-acme-form-challenges').hidden = false; | ||||
|   }; | ||||
|   steps[3].submit = function () { | ||||
|     var chType; | ||||
|     Array.prototype.some.call($qsa('.js-acme-challenge-type'), function ($el) { | ||||
|       if ($el.checked) { | ||||
|         chType = $el.value; | ||||
|         return true; | ||||
|       } | ||||
|     }); | ||||
|     console.log('chType is:', chType); | ||||
|     var chs = []; | ||||
| 
 | ||||
|     // do each wildcard, if any
 | ||||
|     // do each challenge, by selected type only
 | ||||
|     [ 'wildcard', chType].forEach(function (typ) { | ||||
|       info.challenges[typ].forEach(function (ch) { | ||||
|         // { jwk, challengeUrl, accountId (kid) }
 | ||||
|         chs.push({ | ||||
|           jwk: info.jwk | ||||
|         , challengeUrl: ch.url | ||||
|         , accountId: info.kid | ||||
|         }); | ||||
|       }); | ||||
|     }); | ||||
|     console.log("INFO.challenges !!!!!", info.challenges); | ||||
| 
 | ||||
|     var results = []; | ||||
|     function nextChallenge() { | ||||
|       var ch = chs.pop(); | ||||
|       if (!ch) { return results; } | ||||
|       return BACME.challenges.accept(ch).then(function (result) { | ||||
|         results.push(result); | ||||
|         return nextChallenge(); | ||||
|       }); | ||||
|     } | ||||
| 
 | ||||
|     // for now just show the next page immediately (its a spinner)
 | ||||
|     steps[i](); | ||||
|     return nextChallenge().then(function (results) { | ||||
|       console.log('challenge status:', results); | ||||
|       var polls = results.slice(0); | ||||
|       var allsWell = true; | ||||
| 
 | ||||
|       function checkPolls() { | ||||
|         return new PromiseA(function (resolve) { | ||||
|           setTimeout(resolve, 1000); | ||||
|         }).then(function () { | ||||
|           return PromiseA.all(polls.map(function (poll) { | ||||
|             return BACME.challenges.check({ challengePollUrl: poll.url }); | ||||
|           })).then(function (polls) { | ||||
|             console.log(polls); | ||||
| 
 | ||||
|             polls = polls.filter(function (poll) { | ||||
|               //return 'valid' !== poll.status && 'invalid' !== poll.status;
 | ||||
|               if ('pending' === poll.status) { | ||||
|                 return true; | ||||
|               } | ||||
| 
 | ||||
|               if ('invalid' === poll.status) { | ||||
|                 allsWell = false; | ||||
|                 window.alert("verification failed:" + poll.error.detail); | ||||
|                 return; | ||||
|               } | ||||
| 
 | ||||
|               if (poll.error) { | ||||
|                 window.alert("verification failed:" + poll.error.detail); | ||||
|                 return; | ||||
|               } | ||||
| 
 | ||||
|               if ('valid' !== poll.status) { | ||||
|                 allsWell = false; | ||||
|                 console.warn('BAD POLL STATUS', poll); | ||||
|                 window.alert("unknown error: " + JSON.stringify(poll, null, 2)); | ||||
|               } | ||||
|               // TODO show status in HTML
 | ||||
|             }); | ||||
| 
 | ||||
|             if (polls.length) { | ||||
|               return checkPolls(); | ||||
|             } | ||||
|             return true; | ||||
|           }); | ||||
|         }); | ||||
|       } | ||||
| 
 | ||||
|       return checkPolls().then(function () { | ||||
|         if (allsWell) { | ||||
|           return submitForm(); | ||||
|         } | ||||
|       }); | ||||
|     }); | ||||
|   }; | ||||
| 
 | ||||
|   // https://stackoverflow.com/questions/40314257/export-webcrypto-key-to-pem-format
 | ||||
|   function spkiToPEM(keydata, pemName){ | ||||
|       var keydataS = arrayBufferToString(keydata); | ||||
|       var keydataB64 = window.btoa(keydataS); | ||||
|       var keydataB64Pem = formatAsPem(keydataB64, pemName); | ||||
|       return keydataB64Pem; | ||||
|   } | ||||
| 
 | ||||
|   function arrayBufferToString( buffer ) { | ||||
|       var binary = ''; | ||||
|       var bytes = new Uint8Array( buffer ); | ||||
|       var len = bytes.byteLength; | ||||
|       for (var i = 0; i < len; i++) { | ||||
|           binary += String.fromCharCode( bytes[ i ] ); | ||||
|       } | ||||
|       return binary; | ||||
|   } | ||||
| 
 | ||||
| 
 | ||||
|   function formatAsPem(str, pemName) { | ||||
|       var finalString = '-----BEGIN ' + pemName + ' PRIVATE KEY-----\n'; | ||||
| 
 | ||||
|       while(str.length > 0) { | ||||
|           finalString += str.substring(0, 64) + '\n'; | ||||
|           str = str.substring(64); | ||||
|       } | ||||
| 
 | ||||
|       finalString = finalString + '-----END ' + pemName + ' PRIVATE KEY-----'; | ||||
| 
 | ||||
|       return finalString; | ||||
|   } | ||||
| 
 | ||||
|   // spinner
 | ||||
|   steps[4] = function () { | ||||
|     updateProgress(1); | ||||
|     hideForms(); | ||||
|     $qs('.js-acme-form-poll').hidden = false; | ||||
|   }; | ||||
|   steps[4].submit = function () { | ||||
|     console.log('Congrats! Auto advancing...'); | ||||
| 
 | ||||
|     var key = info.identifiers.map(function (ident) { return ident.value; }).join(','); | ||||
|     var serverJwk = JSON.parse(localStorage.getItem('server:' + key) || 'null'); | ||||
|     var p; | ||||
| 
 | ||||
|     function createKeypair() { | ||||
|       var opts; | ||||
| 
 | ||||
|       if (BROWSER_SUPPORTS_ECDSA) { | ||||
|         opts = { type: 'ECDSA', bitlength: '256' }; | ||||
|       } else { | ||||
|         opts = { type: 'RSA', bitlength: '2048' }; | ||||
|       } | ||||
| 
 | ||||
|       return BACME.domains.generateKeypair(opts).then(function (serverJwk) { | ||||
|         localStorage.setItem('server:' + key, JSON.stringify(serverJwk)); | ||||
|         return serverJwk; | ||||
|       }); | ||||
|     } | ||||
| 
 | ||||
|     if (serverJwk) { | ||||
|       p = PromiseA.resolve(serverJwk); | ||||
|     } else { | ||||
|       p = createKeypair(); | ||||
|     } | ||||
| 
 | ||||
|     return p.then(function (_serverJwk) { | ||||
|       serverJwk = _serverJwk; | ||||
|       info.serverJwk = serverJwk; | ||||
|       // { serverJwk, domains }
 | ||||
|       return BACME.orders.generateCsr({ | ||||
|         serverJwk: serverJwk | ||||
|       , domains: info.identifiers.map(function (ident) { | ||||
|           return ident.value; | ||||
|         }) | ||||
|       }).then(function (csrweb64) { | ||||
|         return BACME.orders.finalize({ | ||||
|           csr: csrweb64 | ||||
|         , jwk: info.jwk | ||||
|         , finalizeUrl: info.finalizeUrl | ||||
|         , accountId: info.kid | ||||
|         }); | ||||
|       }).then(function () { | ||||
|         function checkCert() { | ||||
|           return new PromiseA(function (resolve) { | ||||
|             setTimeout(resolve, 1000); | ||||
|           }).then(function () { | ||||
|             return BACME.orders.check({ orderUrl: info.orderUrl }); | ||||
|           }).then(function (reply) { | ||||
|             if ('processing' === reply) { | ||||
|               return checkCert(); | ||||
|             } | ||||
|             return reply; | ||||
|           }); | ||||
|         } | ||||
| 
 | ||||
|         return checkCert(); | ||||
|       }).then(function (reply) { | ||||
|         return BACME.orders.receive({ certificateUrl: reply.certificate }); | ||||
|       }).then(function (certs) { | ||||
|         console.log('WINNING!'); | ||||
|         console.log(certs); | ||||
|         $qs('#js-fullchain').innerHTML = certs; | ||||
|         $qs("#js-download-fullchain-link").href = | ||||
|           "data:text/octet-stream;base64," + window.btoa(certs); | ||||
| 
 | ||||
|         var wcOpts; | ||||
|         var pemName; | ||||
|         if (/^R/.test(info.serverJwk.kty)) { | ||||
|           pemName = 'RSA'; | ||||
|           wcOpts = { name: "RSASSA-PKCS1-v1_5", hash: { name: "SHA-256" } }; | ||||
|         } else { | ||||
|           pemName = 'EC'; | ||||
|           wcOpts = { name: "ECDSA", namedCurve: "P-256" }; | ||||
|         } | ||||
|         return crypto.subtle.importKey( | ||||
|           "jwk" | ||||
|         , info.serverJwk | ||||
|         , wcOpts | ||||
|         , true | ||||
|         , ["sign"] | ||||
|         ).then(function (privateKey) { | ||||
|           return window.crypto.subtle.exportKey("pkcs8", privateKey); | ||||
|         }).then (function (keydata) { | ||||
|           var pem = spkiToPEM(keydata, pemName); | ||||
|           $qs('#js-privkey').innerHTML = pem; | ||||
|           $qs("#js-download-privkey-link").href = | ||||
|             "data:text/octet-stream;base64," + window.btoa(pem); | ||||
|           steps[i](); | ||||
|         }); | ||||
|       }); | ||||
|     }).catch(function (err) { | ||||
|       console.error(err.toString()); | ||||
|       window.alert("An error happened in the final step, but it's not your fault. Email aj@rootprojects.org and let him know."); | ||||
|     }); | ||||
|   }; | ||||
| 
 | ||||
|   steps[5] = function () { | ||||
|     updateProgress(2); | ||||
|     hideForms(); | ||||
|     $qs('.js-acme-form-download').hidden = false; | ||||
|   }; | ||||
|   steps[1](); | ||||
| 
 | ||||
|   var params = new URLSearchParams(window.location.search); | ||||
|   var apiType = params.get('acme-api-type') || "staging-v02"; | ||||
| 
 | ||||
|   if(params.has('acme-domains')) { | ||||
|     console.log("acme-domains param: ", params.get('acme-domains')); | ||||
|     $qs('.js-acme-domains').value = params.get('acme-domains'); | ||||
| 
 | ||||
|     $qsa('.js-acme-api-type').forEach(function(ele) { | ||||
|       if(ele.value === apiType) { | ||||
|         ele.checked = true; | ||||
|       } | ||||
|     }); | ||||
| 
 | ||||
|     updateApiType(); | ||||
|     steps[2](); | ||||
|     submitForm(); | ||||
|   } | ||||
| 
 | ||||
|   $qs('body').hidden = false; | ||||
| }()); | ||||
							
								
								
									
										699
									
								
								app/js/bacme.js
									
									
									
									
									
								
							
							
						
						
									
										699
									
								
								app/js/bacme.js
									
									
									
									
									
								
							| @ -1,699 +0,0 @@ | ||||
| /*global CSR*/ | ||||
| // CSR takes a while to load after the page load
 | ||||
| (function (exports) { | ||||
| 'use strict'; | ||||
| 
 | ||||
| var BACME = exports.BACME = {}; | ||||
| var webFetch = exports.fetch; | ||||
| var webCrypto = exports.crypto; | ||||
| var Promise = exports.Promise; | ||||
| 
 | ||||
| var directoryUrl = 'https://acme-staging-v02.api.letsencrypt.org/directory'; | ||||
| var directory; | ||||
| 
 | ||||
| var nonceUrl; | ||||
| var nonce; | ||||
| 
 | ||||
| var accountKeypair; | ||||
| var accountJwk; | ||||
| 
 | ||||
| var accountUrl; | ||||
| 
 | ||||
| BACME.challengePrefixes = { | ||||
|   'http-01': '/.well-known/acme-challenge' | ||||
| , 'dns-01': '_acme-challenge' | ||||
| }; | ||||
| 
 | ||||
| BACME._logHeaders = function (resp) { | ||||
|   console.log('Headers:'); | ||||
|   Array.from(resp.headers.entries()).forEach(function (h) { console.log(h[0] + ': ' + h[1]); }); | ||||
| }; | ||||
| 
 | ||||
| BACME._logBody = function (body) { | ||||
|   console.log('Body:'); | ||||
|   console.log(JSON.stringify(body, null, 2)); | ||||
|   console.log(''); | ||||
| }; | ||||
| 
 | ||||
| BACME.directory = function (opts) { | ||||
|   return webFetch(opts.directoryUrl || directoryUrl, { mode: 'cors' }).then(function (resp) { | ||||
|     BACME._logHeaders(resp); | ||||
|     return resp.json().then(function (reply) { | ||||
|       if (/error/.test(reply.type)) { | ||||
|         return Promise.reject(new Error(reply.detail || reply.type)); | ||||
|       } | ||||
|       directory = reply; | ||||
|       nonceUrl = directory.newNonce || 'https://acme-staging-v02.api.letsencrypt.org/acme/new-nonce'; | ||||
|       accountUrl = directory.newAccount || 'https://acme-staging-v02.api.letsencrypt.org/acme/new-account'; | ||||
|       orderUrl = directory.newOrder || "https://acme-staging-v02.api.letsencrypt.org/acme/new-order"; | ||||
|       BACME._logBody(reply); | ||||
|       return reply; | ||||
|     }); | ||||
|   }); | ||||
| }; | ||||
| 
 | ||||
| BACME.nonce = function () { | ||||
|   return webFetch(nonceUrl, { mode: 'cors' }).then(function (resp) { | ||||
|     BACME._logHeaders(resp); | ||||
|     nonce = resp.headers.get('replay-nonce'); | ||||
|     console.log('Nonce:', nonce); | ||||
|     // resp.body is empty
 | ||||
|     return resp.headers.get('replay-nonce'); | ||||
|   }); | ||||
| }; | ||||
| 
 | ||||
| BACME.accounts = {}; | ||||
| 
 | ||||
| // type = ECDSA
 | ||||
| // bitlength = 256
 | ||||
| BACME.accounts.generateKeypair = function (opts) { | ||||
|   return BACME.generateKeypair(opts).then(function (result) { | ||||
|     accountKeypair = result; | ||||
| 
 | ||||
|     return webCrypto.subtle.exportKey( | ||||
|       "jwk" | ||||
|     , result.privateKey | ||||
|     ).then(function (privJwk) { | ||||
| 
 | ||||
|       accountJwk = privJwk; | ||||
|       console.log('private jwk:'); | ||||
|       console.log(JSON.stringify(privJwk, null, 2)); | ||||
| 
 | ||||
|       return privJwk; | ||||
|       /* | ||||
|       return webCrypto.subtle.exportKey( | ||||
|         "pkcs8" | ||||
|       , result.privateKey | ||||
|       ).then(function (keydata) { | ||||
|         console.log('pkcs8:'); | ||||
|         console.log(Array.from(new Uint8Array(keydata))); | ||||
| 
 | ||||
|         return privJwk; | ||||
|         //return accountKeypair;
 | ||||
|       }); | ||||
|       */ | ||||
|     }); | ||||
|   }); | ||||
| }; | ||||
| 
 | ||||
| // json to url-safe base64
 | ||||
| BACME._jsto64 = function (json) { | ||||
|   return btoa(JSON.stringify(json)).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/g, ''); | ||||
| }; | ||||
| 
 | ||||
| var textEncoder = new TextEncoder(); | ||||
| 
 | ||||
| BACME._importKey = function (jwk) { | ||||
|   var alg; // I think the 256 refers to the hash
 | ||||
|   var wcOpts = {}; | ||||
|   var extractable = true; // TODO make optionally false?
 | ||||
|   var priv = jwk; | ||||
|   var pub; | ||||
| 
 | ||||
|   // ECDSA
 | ||||
|   if (/^EC/i.test(jwk.kty)) { | ||||
|     wcOpts.name = 'ECDSA'; | ||||
|     wcOpts.namedCurve = jwk.crv; | ||||
|     alg = 'ES256'; | ||||
|     pub = { | ||||
|       crv: priv.crv | ||||
|     , kty: priv.kty | ||||
|     , x: priv.x | ||||
|     , y: priv.y | ||||
|     }; | ||||
|     if (!priv.d) { | ||||
|       priv = null; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   // RSA
 | ||||
|   if (/^RS/i.test(jwk.kty)) { | ||||
|     wcOpts.name = 'RSASSA-PKCS1-v1_5'; | ||||
|     wcOpts.hash = { name: "SHA-256" }; | ||||
|     alg = 'RS256'; | ||||
|     pub = { | ||||
|       e: priv.e | ||||
|     , kty: priv.kty | ||||
|     , n: priv.n | ||||
|     }; | ||||
|     if (!priv.p) { | ||||
|       priv = null; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   return window.crypto.subtle.importKey( | ||||
|     "jwk" | ||||
|   , pub | ||||
|   , wcOpts | ||||
|   , extractable | ||||
|   , [ "verify" ] | ||||
|   ).then(function (publicKey) { | ||||
|     function give(privateKey) { | ||||
|       return { | ||||
|         wcPub: publicKey | ||||
|       , wcKey: privateKey | ||||
|       , wcKeypair: { publicKey: publicKey, privateKey: privateKey } | ||||
|       , meta: { | ||||
|           alg: alg | ||||
|         , name: wcOpts.name | ||||
|         , hash: wcOpts.hash | ||||
|         } | ||||
|       , jwk: jwk | ||||
|       }; | ||||
|     } | ||||
|     if (!priv) { | ||||
|       return give(); | ||||
|     } | ||||
|     return window.crypto.subtle.importKey( | ||||
|       "jwk" | ||||
|     , priv | ||||
|     , wcOpts | ||||
|     , extractable | ||||
|     , [ "sign"/*, "verify"*/ ] | ||||
|     ).then(give); | ||||
|   }); | ||||
| }; | ||||
| BACME._sign = function (opts) { | ||||
|   var wcPrivKey = opts.abstractKey.wcKeypair.privateKey; | ||||
|   var wcOpts = opts.abstractKey.meta; | ||||
|   var alg = opts.abstractKey.meta.alg; // I think the 256 refers to the hash
 | ||||
|   var signHash; | ||||
| 
 | ||||
|   console.log('kty', opts.abstractKey.jwk.kty); | ||||
|   signHash = { name: "SHA-" + alg.replace(/[a-z]+/ig, '') }; | ||||
| 
 | ||||
|   var msg = textEncoder.encode(opts.protected64 + '.' + opts.payload64); | ||||
|   console.log('msg:', msg); | ||||
|   return window.crypto.subtle.sign( | ||||
|     { name: wcOpts.name, hash: signHash } | ||||
|   , wcPrivKey | ||||
|   , msg | ||||
|   ).then(function (signature) { | ||||
|     //console.log('sig1:', signature);
 | ||||
|     //console.log('sig2:', new Uint8Array(signature));
 | ||||
|     //console.log('sig3:', Array.prototype.slice.call(new Uint8Array(signature)));
 | ||||
|     // convert buffer to urlsafe base64
 | ||||
|     var sig64 = btoa(Array.prototype.map.call(new Uint8Array(signature), function (ch) { | ||||
|       return String.fromCharCode(ch); | ||||
|     }).join('')).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/g, ''); | ||||
| 
 | ||||
|     console.log('[1] URL-safe Base64 Signature:'); | ||||
|     console.log(sig64); | ||||
| 
 | ||||
|     var signedMsg = { | ||||
|       protected: opts.protected64 | ||||
|     , payload: opts.payload64 | ||||
|     , signature: sig64 | ||||
|     }; | ||||
| 
 | ||||
|     console.log('Signed Base64 Msg:'); | ||||
|     console.log(JSON.stringify(signedMsg, null, 2)); | ||||
| 
 | ||||
|     return signedMsg; | ||||
|   }); | ||||
| }; | ||||
| // email = john.doe@gmail.com
 | ||||
| // jwk = { ... }
 | ||||
| // agree = true
 | ||||
| BACME.accounts.sign = function (opts) { | ||||
| 
 | ||||
|   return BACME._importKey(opts.jwk).then(function (abstractKey) { | ||||
| 
 | ||||
|     var payloadJson = | ||||
|       { termsOfServiceAgreed: opts.agree | ||||
|       , onlyReturnExisting: false | ||||
|       , contact: opts.contacts || [ 'mailto:' + opts.email ] | ||||
|       }; | ||||
|     console.log('payload:'); | ||||
|     console.log(payloadJson); | ||||
|     var payload64 = BACME._jsto64( | ||||
|       payloadJson | ||||
|     ); | ||||
| 
 | ||||
|     var protectedJson = | ||||
|       { nonce: opts.nonce | ||||
|       , url: accountUrl | ||||
|       , alg: abstractKey.meta.alg | ||||
|       , jwk: null | ||||
|       }; | ||||
| 
 | ||||
|     if (/EC/i.test(opts.jwk.kty)) { | ||||
|       protectedJson.jwk = { | ||||
|         crv: opts.jwk.crv | ||||
|       , kty: opts.jwk.kty | ||||
|       , x: opts.jwk.x | ||||
|       , y: opts.jwk.y | ||||
|       }; | ||||
|     } else if (/RS/i.test(opts.jwk.kty)) { | ||||
|       protectedJson.jwk = { | ||||
|         e: opts.jwk.e | ||||
|       , kty: opts.jwk.kty | ||||
|       , n: opts.jwk.n | ||||
|       }; | ||||
|     } else { | ||||
|       return Promise.reject(new Error("[acme.accounts.sign] unsupported key type '" + opts.jwk.kty + "'")); | ||||
|     } | ||||
| 
 | ||||
|     console.log('protected:'); | ||||
|     console.log(protectedJson); | ||||
|     var protected64 = BACME._jsto64( | ||||
|       protectedJson | ||||
|     ); | ||||
| 
 | ||||
|     // Note: this function hashes before signing so send data, not the hash
 | ||||
|     return BACME._sign({ | ||||
|       abstractKey: abstractKey | ||||
|     , payload64: payload64 | ||||
|     , protected64: protected64 | ||||
|     }); | ||||
|   }); | ||||
| }; | ||||
| 
 | ||||
| var accountId; | ||||
| 
 | ||||
| BACME.accounts.set = function (opts) { | ||||
|   nonce = null; | ||||
|   return window.fetch(accountUrl, { | ||||
|     mode: 'cors' | ||||
|   , method: 'POST' | ||||
|   , headers: { 'Content-Type': 'application/jose+json' } | ||||
|   , body: JSON.stringify(opts.signedAccount) | ||||
|   }).then(function (resp) { | ||||
|     BACME._logHeaders(resp); | ||||
|     nonce = resp.headers.get('replay-nonce'); | ||||
|     accountId = resp.headers.get('location'); | ||||
|     console.log('Next nonce:', nonce); | ||||
|     console.log('Location/kid:', accountId); | ||||
| 
 | ||||
|     if (!resp.headers.get('content-type')) { | ||||
|      console.log('Body: <none>'); | ||||
| 
 | ||||
|      return { kid: accountId }; | ||||
|     } | ||||
| 
 | ||||
|     return resp.json().then(function (result) { | ||||
|       if (/^Error/i.test(result.detail)) { | ||||
|         return Promise.reject(new Error(result.detail)); | ||||
|       } | ||||
|       result.kid = accountId; | ||||
|       BACME._logBody(result); | ||||
| 
 | ||||
|       return result; | ||||
|     }); | ||||
|   }); | ||||
| }; | ||||
| 
 | ||||
| var orderUrl; | ||||
| 
 | ||||
| BACME.orders = {}; | ||||
| 
 | ||||
| // identifiers = [ { type: 'dns', value: 'example.com' }, { type: 'dns', value: '*.example.com' } ]
 | ||||
| // signedAccount
 | ||||
| BACME.orders.sign = function (opts) { | ||||
|   var payload64 = BACME._jsto64({ identifiers: opts.identifiers }); | ||||
| 
 | ||||
|   return BACME._importKey(opts.jwk).then(function (abstractKey) { | ||||
|     var protected64 = BACME._jsto64( | ||||
|       { nonce: nonce, alg: abstractKey.meta.alg/*'ES256'*/, url: orderUrl, kid: opts.kid } | ||||
|     ); | ||||
|     console.log('abstractKey:'); | ||||
|     console.log(abstractKey); | ||||
|     return BACME._sign({ | ||||
|       abstractKey: abstractKey | ||||
|     , payload64: payload64 | ||||
|     , protected64: protected64 | ||||
|     }).then(function (sig) { | ||||
|       if (!sig) { | ||||
|         throw new Error('sig is undefined... nonsense!'); | ||||
|       } | ||||
|       console.log('newsig', sig); | ||||
|       return sig; | ||||
|     }); | ||||
|   }); | ||||
| }; | ||||
| 
 | ||||
| var currentOrderUrl; | ||||
| var authorizationUrls; | ||||
| var finalizeUrl; | ||||
| 
 | ||||
| BACME.orders.create = function (opts) { | ||||
|   nonce = null; | ||||
|   return window.fetch(orderUrl, { | ||||
|     mode: 'cors' | ||||
|   , method: 'POST' | ||||
|   , headers: { 'Content-Type': 'application/jose+json' } | ||||
|   , body: JSON.stringify(opts.signedOrder) | ||||
|   }).then(function (resp) { | ||||
|     BACME._logHeaders(resp); | ||||
|     currentOrderUrl = resp.headers.get('location'); | ||||
|     nonce = resp.headers.get('replay-nonce'); | ||||
|     console.log('Next nonce:', nonce); | ||||
| 
 | ||||
|     return resp.json().then(function (result) { | ||||
|       if (/^Error/i.test(result.detail)) { | ||||
|         return Promise.reject(new Error(result.detail)); | ||||
|       } | ||||
|       authorizationUrls = result.authorizations; | ||||
|       finalizeUrl = result.finalize; | ||||
|       BACME._logBody(result); | ||||
| 
 | ||||
|       result.url = currentOrderUrl; | ||||
|       return result; | ||||
|     }); | ||||
|   }); | ||||
| }; | ||||
| 
 | ||||
| BACME.challenges = {}; | ||||
| BACME.challenges.all = function () { | ||||
|   var challenges = []; | ||||
| 
 | ||||
|   function next() { | ||||
|     if (!authorizationUrls.length) { | ||||
|       return challenges; | ||||
|     } | ||||
| 
 | ||||
|     return BACME.challenges.view().then(function (challenge) { | ||||
|       challenges.push(challenge); | ||||
|       return next(); | ||||
|     }); | ||||
|   } | ||||
| 
 | ||||
|   return next(); | ||||
| }; | ||||
| BACME.challenges.view = function () { | ||||
|   var authzUrl = authorizationUrls.pop(); | ||||
|   var token; | ||||
|   var challengeDomain; | ||||
|   var challengeUrl; | ||||
| 
 | ||||
|   return window.fetch(authzUrl, { | ||||
|     mode: 'cors' | ||||
|   }).then(function (resp) { | ||||
|     BACME._logHeaders(resp); | ||||
| 
 | ||||
|     return resp.json().then(function (result) { | ||||
|       // Note: select the challenge you wish to use
 | ||||
|       var challenge = result.challenges.slice(0).pop(); | ||||
|       token = challenge.token; | ||||
|       challengeUrl = challenge.url; | ||||
|       challengeDomain = result.identifier.value; | ||||
| 
 | ||||
|       BACME._logBody(result); | ||||
| 
 | ||||
|       return { | ||||
|         challenges: result.challenges | ||||
|       , expires: result.expires | ||||
|       , identifier: result.identifier | ||||
|       , status: result.status | ||||
|       , wildcard: result.wildcard | ||||
|       //, token: challenge.token
 | ||||
|       //, url: challenge.url
 | ||||
|       //, domain: result.identifier.value,
 | ||||
|       }; | ||||
|     }); | ||||
|   }); | ||||
| }; | ||||
| 
 | ||||
| var thumbprint; | ||||
| var keyAuth; | ||||
| var httpPath; | ||||
| var dnsAuth; | ||||
| var dnsRecord; | ||||
| 
 | ||||
| BACME.thumbprint = function (opts) { | ||||
|   // https://stackoverflow.com/questions/42588786/how-to-fingerprint-a-jwk
 | ||||
| 
 | ||||
|   var accountJwk = opts.jwk; | ||||
|   var keys; | ||||
| 
 | ||||
|   if (/^EC/i.test(opts.jwk.kty)) { | ||||
|     keys = [ 'crv', 'kty', 'x', 'y' ]; | ||||
|   } else if (/^RS/i.test(opts.jwk.kty)) { | ||||
|     keys = [ 'e', 'kty', 'n' ]; | ||||
|   } | ||||
| 
 | ||||
|   var accountPublicStr = '{' + keys.map(function (key) { | ||||
|     return '"' + key + '":"' + accountJwk[key] + '"'; | ||||
|   }).join(',') + '}'; | ||||
| 
 | ||||
|   return window.crypto.subtle.digest( | ||||
|     { name: "SHA-256" } // SHA-256 is spec'd, non-optional
 | ||||
|   , textEncoder.encode(accountPublicStr) | ||||
|   ).then(function (hash) { | ||||
|     thumbprint = btoa(Array.prototype.map.call(new Uint8Array(hash), function (ch) { | ||||
|       return String.fromCharCode(ch); | ||||
|     }).join('')).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/g, ''); | ||||
| 
 | ||||
|     console.log('Thumbprint:'); | ||||
|     console.log(opts); | ||||
|     console.log(accountPublicStr); | ||||
|     console.log(thumbprint); | ||||
| 
 | ||||
|     return thumbprint; | ||||
|   }); | ||||
| }; | ||||
| 
 | ||||
| // { token, thumbprint, challengeDomain }
 | ||||
| BACME.challenges['http-01'] = function (opts) { | ||||
|   // The contents of the key authorization file
 | ||||
|   keyAuth = opts.token + '.' + opts.thumbprint; | ||||
| 
 | ||||
|   // Where the key authorization file goes
 | ||||
|   httpPath = 'http://' + opts.challengeDomain + '/.well-known/acme-challenge/' + opts.token; | ||||
| 
 | ||||
|   console.log("echo '" + keyAuth + "' > '" + httpPath + "'"); | ||||
| 
 | ||||
|   return { | ||||
|     path: httpPath | ||||
|   , value: keyAuth | ||||
|   }; | ||||
| }; | ||||
| 
 | ||||
| // { keyAuth }
 | ||||
| BACME.challenges['dns-01'] = function (opts) { | ||||
|   console.log('opts.keyAuth for DNS:'); | ||||
|   console.log(opts.keyAuth); | ||||
|   return window.crypto.subtle.digest( | ||||
|     { name: "SHA-256", } | ||||
|   , textEncoder.encode(opts.keyAuth) | ||||
|   ).then(function (hash) { | ||||
|     dnsAuth = btoa(Array.prototype.map.call(new Uint8Array(hash), function (ch) { | ||||
|       return String.fromCharCode(ch); | ||||
|     }).join('')).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/g, ''); | ||||
| 
 | ||||
|     dnsRecord = '_acme-challenge.' + opts.challengeDomain; | ||||
| 
 | ||||
|     console.log('DNS TXT Auth:'); | ||||
|     // The name of the record
 | ||||
|     console.log(dnsRecord); | ||||
|     // The TXT record value
 | ||||
|     console.log(dnsAuth); | ||||
| 
 | ||||
|     return { | ||||
|       type: 'TXT' | ||||
|     , host: dnsRecord | ||||
|     , answer: dnsAuth | ||||
|     }; | ||||
|   }); | ||||
| }; | ||||
| 
 | ||||
| var challengePollUrl; | ||||
| 
 | ||||
| // { jwk, challengeUrl, accountId (kid) }
 | ||||
| BACME.challenges.accept = function (opts) { | ||||
|   var payload64 = BACME._jsto64({}); | ||||
| 
 | ||||
|   return BACME._importKey(opts.jwk).then(function (abstractKey) { | ||||
|     var protected64 = BACME._jsto64( | ||||
|       { nonce: nonce, alg: abstractKey.meta.alg/*'ES256'*/, url: opts.challengeUrl, kid: opts.accountId } | ||||
|     ); | ||||
|     return BACME._sign({ | ||||
|       abstractKey: abstractKey | ||||
|     , payload64: payload64 | ||||
|     , protected64: protected64 | ||||
|     }); | ||||
|   }).then(function (signedAccept) { | ||||
| 
 | ||||
|     nonce = null; | ||||
|     return window.fetch( | ||||
|       opts.challengeUrl | ||||
|     , { mode: 'cors' | ||||
|       , method: 'POST' | ||||
|       , headers: { 'Content-Type': 'application/jose+json' } | ||||
|       , body: JSON.stringify(signedAccept) | ||||
|       } | ||||
|     ).then(function (resp) { | ||||
|       BACME._logHeaders(resp); | ||||
|       nonce = resp.headers.get('replay-nonce'); | ||||
|       console.log("ACCEPT NONCE:", nonce); | ||||
| 
 | ||||
|       return resp.json().then(function (reply) { | ||||
|         challengePollUrl = reply.url; | ||||
| 
 | ||||
|         console.log('Challenge ACK:'); | ||||
|         console.log(JSON.stringify(reply)); | ||||
|         return reply; | ||||
|       }); | ||||
|     }); | ||||
|   }); | ||||
| }; | ||||
| 
 | ||||
| BACME.challenges.check = function (opts) { | ||||
|   return window.fetch(opts.challengePollUrl, { mode: 'cors' }).then(function (resp) { | ||||
|     BACME._logHeaders(resp); | ||||
| 
 | ||||
|     return resp.json().then(function (reply) { | ||||
|       if (/error/.test(reply.type)) { | ||||
|         return Promise.reject(new Error(reply.detail || reply.type)); | ||||
|       } | ||||
|       challengePollUrl = reply.url; | ||||
| 
 | ||||
|       BACME._logBody(reply); | ||||
| 
 | ||||
|       return reply; | ||||
|     }); | ||||
|   }); | ||||
| }; | ||||
| 
 | ||||
| var domainKeypair; | ||||
| var domainJwk; | ||||
| 
 | ||||
| BACME.generateKeypair = function (opts) { | ||||
|   var wcOpts = {}; | ||||
| 
 | ||||
|   // ECDSA has only the P curves and an associated bitlength
 | ||||
|   if (/^EC/i.test(opts.type)) { | ||||
|     wcOpts.name = 'ECDSA'; | ||||
|     if (/256/.test(opts.bitlength)) { | ||||
|       wcOpts.namedCurve = 'P-256'; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   // RSA-PSS is another option, but I don't think it's used for Let's Encrypt
 | ||||
|   // I think the hash is only necessary for signing, not generation or import
 | ||||
|   if (/^RS/i.test(opts.type)) { | ||||
|     wcOpts.name = 'RSASSA-PKCS1-v1_5'; | ||||
|     wcOpts.modulusLength = opts.bitlength; | ||||
|     if (opts.bitlength < 2048) { | ||||
|       wcOpts.modulusLength = opts.bitlength * 8; | ||||
|     } | ||||
|     wcOpts.publicExponent = new Uint8Array([0x01, 0x00, 0x01]); | ||||
|     wcOpts.hash = { name: "SHA-256" }; | ||||
|   } | ||||
|   var extractable = true; | ||||
|   return window.crypto.subtle.generateKey( | ||||
|     wcOpts | ||||
|   , extractable | ||||
|   , [ 'sign', 'verify' ] | ||||
|   ); | ||||
| }; | ||||
| BACME.domains = {}; | ||||
| // TODO factor out from BACME.accounts.generateKeypair even more
 | ||||
| BACME.domains.generateKeypair = function (opts) { | ||||
|   return BACME.generateKeypair(opts).then(function (result) { | ||||
|     domainKeypair = result; | ||||
| 
 | ||||
|     return window.crypto.subtle.exportKey( | ||||
|       "jwk" | ||||
|     , result.privateKey | ||||
|     ).then(function (privJwk) { | ||||
| 
 | ||||
|       domainJwk = privJwk; | ||||
|       console.log('private jwk:'); | ||||
|       console.log(JSON.stringify(privJwk, null, 2)); | ||||
| 
 | ||||
|       return privJwk; | ||||
|     }); | ||||
|   }); | ||||
| }; | ||||
| 
 | ||||
| // { serverJwk, domains }
 | ||||
| BACME.orders.generateCsr = function (opts) { | ||||
|   return BACME._importKey(opts.serverJwk).then(function (abstractKey) { | ||||
|     return Promise.resolve(CSR.generate({ keypair: abstractKey.wcKeypair, domains: opts.domains })); | ||||
|   }); | ||||
| }; | ||||
| 
 | ||||
| var certificateUrl; | ||||
| 
 | ||||
| // { csr, jwk, finalizeUrl, accountId }
 | ||||
| BACME.orders.finalize = function (opts) { | ||||
|   var payload64 = BACME._jsto64( | ||||
|     { csr: opts.csr } | ||||
|   ); | ||||
| 
 | ||||
|   return BACME._importKey(opts.jwk).then(function (abstractKey) { | ||||
|     var protected64 = BACME._jsto64( | ||||
|       { nonce: nonce, alg: abstractKey.meta.alg/*'ES256'*/, url: opts.finalizeUrl, kid: opts.accountId } | ||||
|     ); | ||||
|     return BACME._sign({ | ||||
|       abstractKey: abstractKey | ||||
|     , payload64: payload64 | ||||
|     , protected64: protected64 | ||||
|     }); | ||||
|   }).then(function (signedFinal) { | ||||
| 
 | ||||
|     nonce = null; | ||||
|     return window.fetch( | ||||
|       opts.finalizeUrl | ||||
|     , { mode: 'cors' | ||||
|       , method: 'POST' | ||||
|       , headers: { 'Content-Type': 'application/jose+json' } | ||||
|       , body: JSON.stringify(signedFinal) | ||||
|       } | ||||
|     ).then(function (resp) { | ||||
|       BACME._logHeaders(resp); | ||||
|       nonce = resp.headers.get('replay-nonce'); | ||||
| 
 | ||||
|       return resp.json().then(function (reply) { | ||||
|         if (/error/.test(reply.type)) { | ||||
|           return Promise.reject(new Error(reply.detail || reply.type)); | ||||
|         } | ||||
|         certificateUrl = reply.certificate; | ||||
|         BACME._logBody(reply); | ||||
| 
 | ||||
|         return reply; | ||||
|       }); | ||||
|     }); | ||||
|   }); | ||||
| }; | ||||
| 
 | ||||
| BACME.orders.receive = function (opts) { | ||||
|   return window.fetch( | ||||
|     opts.certificateUrl | ||||
|   , { mode: 'cors' | ||||
|     , method: 'GET' | ||||
|     } | ||||
|   ).then(function (resp) { | ||||
|     BACME._logHeaders(resp); | ||||
|     nonce = resp.headers.get('replay-nonce'); | ||||
| 
 | ||||
|     return resp.text().then(function (reply) { | ||||
|       BACME._logBody(reply); | ||||
| 
 | ||||
|       return reply; | ||||
|     }); | ||||
|   }); | ||||
| }; | ||||
| 
 | ||||
| BACME.orders.check = function (opts) { | ||||
|   return window.fetch( | ||||
|     opts.orderUrl | ||||
|   , { mode: 'cors' | ||||
|     , method: 'GET' | ||||
|     } | ||||
|   ).then(function (resp) { | ||||
|     BACME._logHeaders(resp); | ||||
| 
 | ||||
|     return resp.json().then(function (reply) { | ||||
|       if (/error/.test(reply.type)) { | ||||
|         return Promise.reject(new Error(reply.detail || reply.type)); | ||||
|       } | ||||
|       BACME._logBody(reply); | ||||
| 
 | ||||
|       return reply; | ||||
|     }); | ||||
|   }); | ||||
| }; | ||||
| 
 | ||||
| }(window)); | ||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @ -1,263 +1,274 @@ | ||||
| body { | ||||
|     font-size: 18px; | ||||
|     font-family: Source Sans Pro, sans-serif; | ||||
|     margin: 0; | ||||
|     line-height: 1.33; | ||||
|     color: #1a1a1a; | ||||
| 	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; | ||||
| 	text-align: center; | ||||
| 	font-size: 1.77777778em; | ||||
| } | ||||
| 
 | ||||
| a { | ||||
|     color: #1a1a1a; | ||||
| 	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; | ||||
| 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; | ||||
| 	margin: 0; | ||||
| 	font-family: Source Code Pro, monospace; | ||||
| } | ||||
| 
 | ||||
| .column-row { | ||||
|     width: 22.222222em; | ||||
| 	width: 22.222222em; | ||||
| } | ||||
| 
 | ||||
| .column-container { | ||||
|     display: flex; | ||||
|     flex-direction: column; | ||||
|     align-items: center; | ||||
| 	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; | ||||
| 	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%; | ||||
| 	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; | ||||
| 	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; | ||||
| 	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; | ||||
| 	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; | ||||
| 	content: ""; | ||||
| 	display: inline-block; | ||||
| 	border: solid 0.111111111em #5bc17f; | ||||
| 	width: 0.888888889em; | ||||
| 	height: 0.888888889em; | ||||
| 	border-radius: 100%; | ||||
| 	background: white; | ||||
| } | ||||
| 
 | ||||
| .progress-step-label { | ||||
|     text-align: center; | ||||
|     position: absolute; | ||||
|     left: 50%; | ||||
|     =: block font-size: ; | ||||
|     top: 139%; | ||||
|     font-size: 0.722222222em; | ||||
|     white-space: nowrap; | ||||
| 	text-align: center; | ||||
| 	position: absolute; | ||||
| 	left: 50%; | ||||
| 	top: 139%; | ||||
| 	font-size: 0.722222222em; | ||||
| 	white-space: nowrap; | ||||
| } | ||||
| 
 | ||||
| .progress-step-label > div { | ||||
|     position: relative; | ||||
|     right: 50%; | ||||
| 	position: relative; | ||||
| 	right: 50%; | ||||
| } | ||||
| 
 | ||||
| .greenlock-name { | ||||
|     color: #808080; | ||||
| 	color: #808080; | ||||
| } | ||||
| 
 | ||||
| .file-preview { | ||||
|     background: #f7f7f7; | ||||
|     position: relative; | ||||
|     font-size: 0.833333333em; | ||||
|     padding: 1.6em 2.9333em 1.6em 1.6em; | ||||
| 	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; | ||||
| .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; | ||||
| 	fill: white; | ||||
| 	/* stroke: none; */ | ||||
| 	display: initial; | ||||
| } | ||||
| 
 | ||||
| .checkbox-array { | ||||
|     display: flex; | ||||
|     flex-direction: column; | ||||
|     padding: 1em 0; | ||||
| 	display: flex; | ||||
| 	flex-direction: column; | ||||
| 	padding: 1em 0; | ||||
| } | ||||
| 
 | ||||
| .checkbox-array input[type=checkbox] { | ||||
|     opacity: 0; | ||||
|     position: absolute; | ||||
| .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-checked-box { | ||||
| 	display: none; | ||||
| } | ||||
| 
 | ||||
| .checkbox-array input[type=checkbox] ~ .icon-unchecked-box { | ||||
|     display: initial; | ||||
| .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-checked-box { | ||||
| 	display: initial; | ||||
| } | ||||
| 
 | ||||
| .checkbox-array input[type=checkbox]:checked ~ .icon-unchecked-box { | ||||
|     display: none; | ||||
| .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 .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; | ||||
| 	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; | ||||
| .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; | ||||
| 	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; | ||||
| 	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; | ||||
| 	width: 50%; | ||||
| 	padding: 0.5em 0; | ||||
| } | ||||
| 
 | ||||
| .tabbed-selector { | ||||
|     display: flex; | ||||
|     font-weight: bold; | ||||
|     text-align: center; | ||||
| 	display: flex; | ||||
| 	font-weight: bold; | ||||
| 	text-align: center; | ||||
| } | ||||
| 
 | ||||
| .tabbed-selector input[type=radio] { | ||||
|     display: none; | ||||
| .tabbed-selector input[type="radio"] { | ||||
| 	display: none; | ||||
| } | ||||
| 
 | ||||
| .download-file svg { | ||||
|     fill: #5bc17f; | ||||
|     width: 1.333333333em; | ||||
| 	fill: #5bc17f; | ||||
| 	width: 1.333333333em; | ||||
| } | ||||
| 
 | ||||
| .download-file a { | ||||
|     color: #5bc17f; | ||||
| 	color: #5bc17f; | ||||
| } | ||||
| 
 | ||||
| .mdicon { | ||||
|     position: relative; | ||||
|     top: 0.4em; | ||||
| 	position: relative; | ||||
| 	top: 0.4em; | ||||
| } | ||||
| 
 | ||||
| .http-verification-info { | ||||
|     padding-right: 6.933333333em; | ||||
| 	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%); | ||||
| 	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; | ||||
| 	color: #808080; | ||||
| } | ||||
| 
 | ||||
| .http-verification-info hr { | ||||
|     border: none; | ||||
|     border-bottom: solid 1px #d9d9d9; | ||||
| 	border: none; | ||||
| 	border-bottom: solid 1px #d9d9d9; | ||||
| } | ||||
| 
 | ||||
| .acme-ver-uri { | ||||
|     word-break: break-all; | ||||
|     margin: auto; | ||||
| 	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; | ||||
| 	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; | ||||
| 	border: solid 1px #5bc17f; | ||||
| 	background-color: #5bc17f; | ||||
| } | ||||
| 
 | ||||
| .file-preview pre { | ||||
|     white-space: pre-line; | ||||
|     word-break: break-all; | ||||
| 	white-space: pre-line; | ||||
| 	word-break: break-all; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| .cert-download-container { | ||||
|     margin: 0 -31%; | ||||
| 	margin: 0 -31%; | ||||
| } | ||||
| 
 | ||||
|  | ||||
							
								
								
									
										251
									
								
								index.html
									
									
									
									
									
								
							
							
						
						
									
										251
									
								
								index.html
									
									
									
									
									
								
							| @ -1,100 +1,171 @@ | ||||
| <html> | ||||
|   <head> | ||||
|     <title>Greenlock™</title> | ||||
|     <meta property="og:image" content="https://greenlock.domains/img/greenlock-mark-400x400.png" /> | ||||
|     <link href="styles/main.css" rel="stylesheet"> | ||||
|     <style> | ||||
|       @font-face { | ||||
|         font-family: 'Source Sans Pro'; | ||||
|         font-style: normal; | ||||
|         font-display: block; | ||||
|         font-weight: 400; | ||||
|         src: local('Source Sans Pro Regular'), local('SourceSansPro-Regular'), url(./fonts/6xK3dSBYKcSV-LCoeQqfX1RYOo3qOK7l.woff2) format('woff2'); | ||||
|         unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; | ||||
|       } | ||||
|       @font-face { | ||||
|         font-family: 'Source Sans Pro'; | ||||
|         font-style: normal; | ||||
|         font-weight: 700; | ||||
|         font-display: block; | ||||
|         src: local('Source Sans Pro Bold'), local('SourceSansPro-Bold'), url(./fonts/6xKydSBYKcSV-LCoeQqfX1RYOo3ig4vwlxdu.woff2) format('woff2'); | ||||
|         unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; | ||||
|       } | ||||
|     </style> | ||||
|     <link rel="preload" href="./app/fonts/6xKydSBYKcSV-LCoeQqfX1RYOo3ig4vwlxdu.woff2" as="font" crossorigin="anonymous"> | ||||
|     <link rel="preload" href="./app/fonts/6xK3dSBYKcSV-LCoeQqfX1RYOo3qOK7l.woff2" as="font" crossorigin="anonymous"> | ||||
| 	<head> | ||||
| 		<title>Greenlock™</title> | ||||
| 		<meta | ||||
| 			property="og:image" | ||||
| 			content="https://greenlock.domains/img/greenlock-mark-400x400.png" | ||||
| 		/> | ||||
| 		<link href="styles/main.css" rel="stylesheet" /> | ||||
| 		<style> | ||||
| 			@font-face { | ||||
| 				font-family: "Source Sans Pro"; | ||||
| 				font-style: normal; | ||||
| 				font-display: block; | ||||
| 				font-weight: 400; | ||||
| 				src: local("Source Sans Pro Regular"), local("SourceSansPro-Regular"), | ||||
| 					url(./fonts/6xK3dSBYKcSV-LCoeQqfX1RYOo3qOK7l.woff2) format("woff2"); | ||||
| 				unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, | ||||
| 					U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, | ||||
| 					U+2212, U+2215, U+FEFF, U+FFFD; | ||||
| 			} | ||||
| 			@font-face { | ||||
| 				font-family: "Source Sans Pro"; | ||||
| 				font-style: normal; | ||||
| 				font-weight: 700; | ||||
| 				font-display: block; | ||||
| 				src: local("Source Sans Pro Bold"), local("SourceSansPro-Bold"), | ||||
| 					url(./fonts/6xKydSBYKcSV-LCoeQqfX1RYOo3ig4vwlxdu.woff2) | ||||
| 						format("woff2"); | ||||
| 				unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, | ||||
| 					U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, | ||||
| 					U+2212, U+2215, U+FEFF, U+FFFD; | ||||
| 			} | ||||
| 		</style> | ||||
| 		<link | ||||
| 			rel="preload" | ||||
| 			href="./app/fonts/6xKydSBYKcSV-LCoeQqfX1RYOo3ig4vwlxdu.woff2" | ||||
| 			as="font" | ||||
| 			crossorigin="anonymous" | ||||
| 		/> | ||||
| 		<link | ||||
| 			rel="preload" | ||||
| 			href="./app/fonts/6xK3dSBYKcSV-LCoeQqfX1RYOo3qOK7l.woff2" | ||||
| 			as="font" | ||||
| 			crossorigin="anonymous" | ||||
| 		/> | ||||
| 		<link | ||||
| 			rel="prefetch" | ||||
| 			href="./app/fonts/HI_SiYsKILxRpg3hIP6sJ7fM7PqlPevW.woff2" | ||||
| 			as="font" | ||||
| 			crossorigin="anonymous" | ||||
| 		/> | ||||
| 		<link rel="prefetch" href="./app/js/bluecrypt-acme.js" as="script" /> | ||||
| 		<link rel="prefetch" href="./app/js/greenlock.js" as="script" /> | ||||
| 		<link rel="prefetch" href="./js/app.js" as="script" /> | ||||
| 	</head> | ||||
| 	<body 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> | ||||
| 
 | ||||
|     <link rel="prefetch" href="./app/fonts/HI_SiYsKILxRpg3hIP6sJ7fM7PqlPevW.woff2" as="font" crossorigin="anonymous"> | ||||
|     <link rel="prefetch" href="./app/js/app.js"> | ||||
|     <link rel="prefetch" href="./app/js/bacme.js"> | ||||
|     <link rel="prefetch" href="./app/js/pkijs.org/v1.3.33/common.js"> | ||||
|     <link rel="prefetch" href="./app/js/pkijs.org/v1.3.33/asn1.js"> | ||||
|     <link rel="prefetch" href="./app/js/pkijs.org/v1.3.33/x509_schema.js"> | ||||
|     <link rel="prefetch" href="./app/js/pkijs.org/v1.3.33/x509_simpl.js"> | ||||
|     <link rel="prefetch" href="./app/js/browser-csr/v1.0.0-alpha/csr.js"> | ||||
|   </head> | ||||
|   <body class="js-app-ready"> | ||||
|     <script> | ||||
|       document.querySelector('body').classList.remove("js-app-ready"); | ||||
|     </script> | ||||
|     <div class="column-container wide"> | ||||
| 
 | ||||
|       <div class="column-row"> | ||||
|         <img src="img/greenlock-146.png"> | ||||
|       </div> | ||||
|       <div class="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=> | ||||
|           <div class="domain-psuedo-input"> | ||||
|             <span class="secure-green">Secure</span> | <span class="secure-green">https:</span>//<input aria-label="domains to secure" id="acme-domains" type="text" name="acme-domains" placeholder="Your domain name" required> | ||||
|           </div> | ||||
|           <button type="submit">Go</button> | ||||
|           <div class="domain-subtext">Domain, subdomain, or wildcard domain</div> | ||||
| 
 | ||||
|           <div class="acme-advanced-fields"> | ||||
|             <label><input name="acme-api-type" type="radio" value="v02" checked required> | ||||
|               Production | ||||
|             </label> | ||||
|             <label><input name="acme-api-type" type="radio" value="staging-v02" required> | ||||
|               Testing</label> | ||||
|             <input id="js-acme-api-url" type="url" placeholder="ACME directory url"> | ||||
|             <div> | ||||
|               A <a href="https://rootprojects.org/" target="_blank">Root</a> Project | ||||
|               | <a href="https://git.coolaj86.com/coolaj86/greenlock.html" target="_blank">View Source</a> (git) | ||||
|               | <a href="https://rootprojects.org/legal/#terms" target="_blank">Terms of Service</a> | ||||
|               | <a href="https://rootprojects.org/legal/#privacy" target="_blank">Privacy Policy</a> | ||||
|             </div> | ||||
|           </div> | ||||
|         </form> | ||||
|       </div> | ||||
|       <div class="column-row"> | ||||
|         <div class="why-you-need"> | ||||
|           <h2>Why you need HTTPS</h2> | ||||
|           SSL Certificates are required for secure login, accepting payments, and for browsers like Google Chrome to stop showing security warnings to your users. | ||||
|         </div> | ||||
|       </div> | ||||
|       <!-- or | ||||
| 					<div class="acme-advanced-fields"> | ||||
| 						<label | ||||
| 							><input | ||||
| 								name="acme-api-type" | ||||
| 								type="radio" | ||||
| 								value="v02" | ||||
| 								checked | ||||
| 								required | ||||
| 							/> | ||||
| 							Production | ||||
| 						</label> | ||||
| 						<label | ||||
| 							><input | ||||
| 								name="acme-api-type" | ||||
| 								type="radio" | ||||
| 								value="staging-v02" | ||||
| 								required | ||||
| 							/> | ||||
| 							Testing</label | ||||
| 						> | ||||
| 						<input | ||||
| 							id="js-acme-api-url" | ||||
| 							type="url" | ||||
| 							placeholder="ACME directory url" | ||||
| 						/> | ||||
| 						<br /> | ||||
| 						API Compatibility: Let's Encrypt v2 / ACME draft 15 | ||||
| 						<div> | ||||
| 							<br /> | ||||
| 							A | ||||
| 							<a href="https://rootprojects.org/" target="_blank">Root</a> | ||||
| 							Project | | ||||
| 							<a | ||||
| 								href="https://git.coolaj86.com/coolaj86/greenlock.html" | ||||
| 								target="_blank" | ||||
| 								>View Source</a | ||||
| 							> | ||||
| 							(git) | | ||||
| 							<a href="https://rootprojects.org/legal/#terms" target="_blank" | ||||
| 								>Terms of Service</a | ||||
| 							> | ||||
| 							| | ||||
| 							<a href="https://rootprojects.org/legal/#privacy" target="_blank" | ||||
| 								>Privacy Policy</a | ||||
| 							> | ||||
| 						</div> | ||||
| 					</div> | ||||
| 				</form> | ||||
| 			</div> | ||||
| 			<div class="column-row"> | ||||
| 				<div class="why-you-need"> | ||||
| 					<h2>Why you need HTTPS</h2> | ||||
| 					SSL Certificates are required for secure login, accepting payments, | ||||
| 					and for browsers like Google Chrome to stop showing security warnings | ||||
| 					to your users. | ||||
| 				</div> | ||||
| 			</div> | ||||
| 			<!-- or | ||||
|       <pre><code>git clone https://git.coolaj86.com/coolaj86/greenlock.html.git</code></pre> | ||||
|       Or view the live site code (same as live-site branch): | ||||
|       <pre><code>wget https://greenlock.domains --mirror --convert-links --adjust-extension --page-requisites --no-parent</code></pre> | ||||
|       --> | ||||
| 
 | ||||
|       <script src="./js/app.js"></script> | ||||
| 			<script src="./js/app.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()); | ||||
| 			<!-- Global site tag (gtag.js) - Google Analytics --> | ||||
| 			<script | ||||
| 				async | ||||
| 				src="https://www.googletagmanager.com/gtag/js?id=UA-118745161-2" | ||||
| 			></script> | ||||
| 			<script> | ||||
| 				window.dataLayer = window.dataLayer || []; | ||||
| 				function gtag() { | ||||
| 					dataLayer.push(arguments); | ||||
| 				} | ||||
| 				gtag("js", new Date()); | ||||
| 
 | ||||
|         gtag('config', 'UA-118745161-2'); | ||||
|       </script> | ||||
|     </div> | ||||
|   </body> | ||||
| 				gtag("config", "UA-118745161-2"); | ||||
| 			</script> | ||||
| 		</div> | ||||
| 	</body> | ||||
| </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 | ||||
| 
 | ||||
| mkdir -p app/js/pkijs.org/v1.3.33/ | ||||
| pushd app/js/pkijs.org/v1.3.33/ | ||||
|   wget -c https://raw.githubusercontent.com/PeculiarVentures/PKI.js/41b63af760cacb565dd850fb3466ada4ca163eff/org/pkijs/common.js | ||||
|   wget -c https://raw.githubusercontent.com/PeculiarVentures/PKI.js/41b63af760cacb565dd850fb3466ada4ca163eff/org/pkijs/x509_schema.js | ||||
|   wget -c https://raw.githubusercontent.com/PeculiarVentures/PKI.js/41b63af760cacb565dd850fb3466ada4ca163eff/org/pkijs/x509_simpl.js | ||||
|   wget -c https://raw.githubusercontent.com/PeculiarVentures/ASN1.js/f7181c21c61e53a940ea24373ab489ad86d51bc1/org/pkijs/asn1.js | ||||
| popd | ||||
| 
 | ||||
| mkdir -p app/js/browser-csr/v1.0.0-alpha/ | ||||
| pushd app/js/browser-csr/v1.0.0-alpha/ | ||||
|   wget -c https://git.coolaj86.com/coolaj86/browser-csr.js/raw/commit/01cdc0e91b5bf03f12e1b25b4129e3cde927987c/csr.js | ||||
| mkdir -p app/js/ | ||||
| pushd app/js/ | ||||
|   wget -c https://rootprojects.org/acme/bluecrypt-acme.js | ||||
|   wget -c https://rootprojects.org/acme/bluecrypt-acme.min.js | ||||
| popd | ||||
|  | ||||
							
								
								
									
										57
									
								
								js/app.js
									
									
									
									
									
								
							
							
						
						
									
										57
									
								
								js/app.js
									
									
									
									
									
								
							| @ -1,30 +1,39 @@ | ||||
| (function () { | ||||
| 'use strict'; | ||||
| (function() { | ||||
| 	"use strict"; | ||||
| 
 | ||||
|   var $qs = function (s) { return window.document.querySelector(s); }; | ||||
|   var $qsa = function (s) { return window.document.querySelectorAll(s); }; | ||||
| 	var $qs = function(s) { | ||||
| 		return window.document.querySelector(s); | ||||
| 	}; | ||||
| 
 | ||||
|   $qs('.js-javascript-warning').hidden = true; | ||||
| 	$qs(".js-javascript-warning").hidden = true; | ||||
| 
 | ||||
|   var apiUrl = 'https://acme-{{env}}.api.letsencrypt.org/directory'; | ||||
|   function updateApiType() { | ||||
|     var formData = new FormData($qs("#js-acme-form")); | ||||
| 	var apiUrl = "https://acme-{{env}}.api.letsencrypt.org/directory"; | ||||
| 
 | ||||
|     console.log('ACME api type radio:'); | ||||
| 	function updateApiType() { | ||||
| 		var formData = new FormData($qs("#js-acme-form")); | ||||
| 
 | ||||
|     var value = formData.get("acme-api-type"); | ||||
|     $qs('#js-acme-api-url').value = apiUrl.replace(/{{env}}/g, value); | ||||
|   } | ||||
|   $qs('#js-acme-form').addEventListener('change', updateApiType); | ||||
| 		console.log("ACME api type radio:"); | ||||
| 
 | ||||
|   updateApiType(); | ||||
|   try { | ||||
|     document.fonts.load().then(function() { | ||||
|       $qs('body').classList.add("js-app-ready"); | ||||
|     }).catch(function(error) { | ||||
|       $qs('body').classList.add("js-app-ready"); | ||||
|     }); | ||||
|   } catch(e) { | ||||
|     setTimeout(function() {$qs('body').classList.add("js-app-ready");}, 200); | ||||
|   } | ||||
| }()); | ||||
| 		var value = formData.get("acme-api-type"); | ||||
| 		$qs("#js-acme-api-url").value = apiUrl.replace(/{{env}}/g, value); | ||||
| 	} | ||||
| 
 | ||||
| 	$qs("#js-acme-form").addEventListener("change", updateApiType); | ||||
| 	//$qs('#js-acme-form').addEventListener('submit', prettyRedirect);
 | ||||
| 
 | ||||
| 	updateApiType(); | ||||
| 	try { | ||||
| 		document.fonts | ||||
| 			.load() | ||||
| 			.then(function() { | ||||
| 				$qs("body").classList.add("js-app-ready"); | ||||
| 			}) | ||||
| 			.catch(function(e) { | ||||
| 				$qs("body").classList.add("js-app-ready"); | ||||
| 			}); | ||||
| 	} catch (e) { | ||||
| 		setTimeout(function() { | ||||
| 			$qs("body").classList.add("js-app-ready"); | ||||
| 		}, 200); | ||||
| 	} | ||||
| })(); | ||||
|  | ||||
							
								
								
									
										416
									
								
								legal.html
									
									
									
									
									
								
							
							
						
						
									
										416
									
								
								legal.html
									
									
									
									
									
								
							| @ -1,201 +1,315 @@ | ||||
| <h1>Greetings!</h1> | ||||
| <!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> | ||||
| 			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> | ||||
| 			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> | ||||
| 			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> | ||||
| 		<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: | ||||
| 		<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> | ||||
| 		<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> | ||||
| 		<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> | ||||
| 			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> | ||||
| 		<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-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.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> | ||||
| 			<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> | ||||
| 			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> | ||||
| 			If we don't do this, well, with the way the cloud is headed, renting may | ||||
| 			be the only option in the future. | ||||
| 		</p> | ||||
| 
 | ||||
| <p>We need <em>Root</em> because we want ownership. | ||||
| 		<p>We need <em>Root</em> because we want ownership.</p> | ||||
| 
 | ||||
| <p>If at any time you feel that any of our messaging or practices | ||||
| are in conflict with our mission or these values, please let us know. | ||||
| 		<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> | ||||
| 		<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> | ||||
| 			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> | ||||
| 			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> | ||||
| 			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> | ||||
| 			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> | ||||
| 			If at any time you feel that our Licensing is in conflict with our mission | ||||
| 			or values, please let us know. | ||||
| 		</p> | ||||
| 
 | ||||
| <h1 id="terms">Terms of Service</h1> | ||||
| 		<h1 id="terms">Terms of Service</h1> | ||||
| 
 | ||||
| <p>We want to make the world a better place. | ||||
| 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> | ||||
| 			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> | ||||
| 			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> | ||||
| 			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> | ||||
| 			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> | ||||
| 			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> | ||||
| 			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> | ||||
| 			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> | ||||
| 		<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> | ||||
| 			"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> | ||||
| 			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> | ||||
| 			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> | ||||
| 			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> | ||||
| 			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> | ||||
| 			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> | ||||
| 			If at any time you feel that our Trademark policies are in conflict with | ||||
| 			our values, please let us know. | ||||
| 		</p> | ||||
| 
 | ||||
| <h1 id="privacy">Privacy Policy</h1> | ||||
| 		<h1 id="privacy">Privacy Policy</h1> | ||||
| 
 | ||||
| <p>What we collect and (more importantly) <em>Why</em>: | ||||
| 		<p>What we collect and (more importantly) <em>Why</em>:</p> | ||||
| 
 | ||||
| <p><strong>Name</strong>: | ||||
| <p>In the cases that we collect your name, it's because we want to know how to address you. | ||||
| All four of us want to be personable if and when we reach out. | ||||
| 		<p><strong>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>There are three main purposes for which we may use your email address: | ||||
| 		<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> | ||||
| 			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> | ||||
| 			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> | ||||
| 			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>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><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> | ||||
| 			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>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><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> | ||||
| 			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 | ||||
| 		<br /> | ||||
| 		<br /> | ||||
| 		<p>Copyright 2018 AJ ONeal</p> | ||||
| 	</body> | ||||
| </html> | ||||
|  | ||||
							
								
								
									
										138
									
								
								styles/main.css
									
									
									
									
									
								
							
							
						
						
									
										138
									
								
								styles/main.css
									
									
									
									
									
								
							| @ -1,115 +1,115 @@ | ||||
| .column-row { | ||||
|   display: flex; | ||||
|   flex-direction: column; | ||||
|   text-align: center; | ||||
|   align-items: center; | ||||
| 	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; | ||||
| 	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; | ||||
| 	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; | ||||
| 	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; | ||||
| 	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; | ||||
| 	position: absolute; | ||||
| 	bottom: 0; | ||||
| 	padding: 1em; | ||||
| 	text-align: center; | ||||
| } | ||||
| 
 | ||||
| .domain-subtext { | ||||
|   font-size: 0.833333333em; | ||||
|   color: #666; | ||||
|   text-align: center; | ||||
|   margin: 0.5em; | ||||
| 	font-size: 0.833333333em; | ||||
| 	color: #666; | ||||
| 	text-align: center; | ||||
| 	margin: 0.5em; | ||||
| } | ||||
| 
 | ||||
| input#acme-domains:before { | ||||
|   content: "Secure | https://"; | ||||
| 	content: "Secure | https://"; | ||||
| } | ||||
| 
 | ||||
| .domain-psuedo-input { | ||||
|   display: inline-block; | ||||
|   margin-right: .6666667em; | ||||
|   border: solid #d9d9d9 1px; | ||||
|   border-radius: 2px; | ||||
|   padding: 0.44444444em; | ||||
|   color: #d9d9d9; | ||||
| 	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; | ||||
| 	border: none; | ||||
| 	padding: 0; | ||||
| 	padding-right: 0; | ||||
| 	width: 17.2222222em; | ||||
| 	color: #222; | ||||
| } | ||||
| 
 | ||||
| input#acme-domains:focus { | ||||
|   outline: none; | ||||
| 	outline: none; | ||||
| } | ||||
| 
 | ||||
| span.secure-green { | ||||
|   color: #5bc17f; | ||||
| 	color: #5bc17f; | ||||
| } | ||||
| 
 | ||||
| .why-you-need { | ||||
|   width: 26.555556em; | ||||
| 	width: 26.555556em; | ||||
| } | ||||
| 
 | ||||
| body.js-app-ready { | ||||
|   transition: opacity 0.2s; | ||||
|   opacity: 1; | ||||
| 	transition: opacity 0.2s; | ||||
| 	opacity: 1; | ||||
| } | ||||
| 
 | ||||
| .acme-advanced-fields > * { | ||||
|   margin: 0 0.5em; | ||||
| 	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; | ||||
|  } | ||||
|   | ||||
| 	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