lint and fix and use domains.generateKeypair
This commit is contained in:
		
							parent
							
								
									d63d8e1aed
								
							
						
					
					
						commit
						2cc5a41268
					
				| @ -444,7 +444,7 @@ | |||||||
|         }; |         }; | ||||||
|       } |       } | ||||||
| 
 | 
 | ||||||
|       return BACME.accounts.generateKeypair(opts).then(function (serverJwk) { |       return BACME.domains.generateKeypair(opts).then(function (serverJwk) { | ||||||
|         localStorage.setItem('server:' + key, JSON.stringify(serverJwk)); |         localStorage.setItem('server:' + key, JSON.stringify(serverJwk)); | ||||||
|         return serverJwk; |         return serverJwk; | ||||||
|       }); |       }); | ||||||
|  | |||||||
							
								
								
									
										434
									
								
								app/js/bacme.js
									
									
									
									
									
								
							
							
						
						
									
										434
									
								
								app/js/bacme.js
									
									
									
									
									
								
							| @ -4,6 +4,8 @@ | |||||||
| var BACME = exports.BACME = {}; | var BACME = exports.BACME = {}; | ||||||
| var webFetch = exports.fetch; | var webFetch = exports.fetch; | ||||||
| var webCrypto = exports.crypto; | var webCrypto = exports.crypto; | ||||||
|  | var Promise = window.Promise; | ||||||
|  | var CSR = window.CSR; | ||||||
| 
 | 
 | ||||||
| var directoryUrl = 'https://acme-staging-v02.api.letsencrypt.org/directory'; | var directoryUrl = 'https://acme-staging-v02.api.letsencrypt.org/directory'; | ||||||
| var directory; | var directory; | ||||||
| @ -15,7 +17,6 @@ var accountKeypair; | |||||||
| var accountJwk; | var accountJwk; | ||||||
| 
 | 
 | ||||||
| var accountUrl; | var accountUrl; | ||||||
| var signedAccount; |  | ||||||
| 
 | 
 | ||||||
| BACME.challengePrefixes = { | BACME.challengePrefixes = { | ||||||
|   'http-01': '/.well-known/acme-challenge' |   'http-01': '/.well-known/acme-challenge' | ||||||
| @ -23,38 +24,38 @@ BACME.challengePrefixes = { | |||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| BACME._logHeaders = function (resp) { | BACME._logHeaders = function (resp) { | ||||||
| 	console.log('Headers:'); |   console.log('Headers:'); | ||||||
| 	Array.from(resp.headers.entries()).forEach(function (h) { console.log(h[0] + ': ' + h[1]); }); |   Array.from(resp.headers.entries()).forEach(function (h) { console.log(h[0] + ': ' + h[1]); }); | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| BACME._logBody = function (body) { | BACME._logBody = function (body) { | ||||||
| 	console.log('Body:'); |   console.log('Body:'); | ||||||
| 	console.log(JSON.stringify(body, null, 2)); |   console.log(JSON.stringify(body, null, 2)); | ||||||
| 	console.log(''); |   console.log(''); | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| BACME.directory = function (opts) { | BACME.directory = function (opts) { | ||||||
| 	return webFetch(opts.directoryUrl || directoryUrl, { mode: 'cors' }).then(function (resp) { |   return webFetch(opts.directoryUrl || directoryUrl, { mode: 'cors' }).then(function (resp) { | ||||||
| 		BACME._logHeaders(resp); |     BACME._logHeaders(resp); | ||||||
| 		return resp.json().then(function (body) { |     return resp.json().then(function (body) { | ||||||
| 			directory = body; |       directory = body; | ||||||
|       nonceUrl = directory.newNonce || 'https://acme-staging-v02.api.letsencrypt.org/acme/new-nonce'; |       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'; |       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"; |       orderUrl = directory.newOrder || "https://acme-staging-v02.api.letsencrypt.org/acme/new-order"; | ||||||
|       BACME._logBody(body); |       BACME._logBody(body); | ||||||
|       return body; |       return body; | ||||||
| 		}); |     }); | ||||||
| 	}); |   }); | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| BACME.nonce = function () { | BACME.nonce = function () { | ||||||
| 	return webFetch(nonceUrl, { mode: 'cors' }).then(function (resp) { |   return webFetch(nonceUrl, { mode: 'cors' }).then(function (resp) { | ||||||
|     BACME._logHeaders(resp); |     BACME._logHeaders(resp); | ||||||
| 		nonce = resp.headers.get('replay-nonce'); |     nonce = resp.headers.get('replay-nonce'); | ||||||
| 		console.log('Nonce:', nonce); |     console.log('Nonce:', nonce); | ||||||
| 		// resp.body is empty
 |     // resp.body is empty
 | ||||||
| 		return resp.headers.get('replay-nonce'); |     return resp.headers.get('replay-nonce'); | ||||||
| 	}); |   }); | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| BACME.accounts = {}; | BACME.accounts = {}; | ||||||
| @ -62,66 +63,38 @@ BACME.accounts = {}; | |||||||
| // type = ECDSA
 | // type = ECDSA
 | ||||||
| // bitlength = 256
 | // bitlength = 256
 | ||||||
| BACME.accounts.generateKeypair = function (opts) { | BACME.accounts.generateKeypair = function (opts) { | ||||||
|   var wcOpts = {}; |   return BACME.generateKeypair(opts).then(function (result) { | ||||||
|  |     accountKeypair = result; | ||||||
| 
 | 
 | ||||||
|   // ECDSA has only the P curves and an associated bitlength
 |     return webCrypto.subtle.exportKey( | ||||||
|   if (/^EC/i.test(opts.type)) { |       "jwk" | ||||||
|     wcOpts.name = 'ECDSA'; |     , result.privateKey | ||||||
|     if (/256/.test(opts.bitlength)) { |     ).then(function (privJwk) { | ||||||
|       wcOpts.namedCurve = 'P-256'; |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
| 
 | 
 | ||||||
|   // RSA-PSS is another option, but I don't think it's used for Let's Encrypt
 |       accountJwk = privJwk; | ||||||
|   // I think the hash is only necessary for signing, not generation or import
 |       console.log('private jwk:'); | ||||||
|   if (/^RS/i.test(opts.type)) { |       console.log(JSON.stringify(privJwk, null, 2)); | ||||||
|     wcOpts.name = 'RSASSA-PKCS1-v1_5'; |  | ||||||
|     wcOpts.modulusLength = opts.bitlength; |  | ||||||
|     if (opts.bitlength < 2048) { |  | ||||||
|       wcOpts.modulusLength = opts.bitlength * 8; |  | ||||||
|     } |  | ||||||
|     wcOpts.publicExponent = new Uint8Array([0x01, 0x00, 0x01]); |  | ||||||
|     wcOpts.hash = { name: "SHA-256" }; |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
| 	// https://github.com/diafygi/webcrypto-examples#ecdsa---generatekey
 |  | ||||||
| 	var extractable = true; |  | ||||||
| 	return webCrypto.subtle.generateKey( |  | ||||||
| 		wcOpts |  | ||||||
| 	, extractable |  | ||||||
| 	, [ 'sign', 'verify' ] |  | ||||||
| 	).then(function (result) { |  | ||||||
| 		accountKeypair = result; |  | ||||||
| 
 |  | ||||||
| 		return webCrypto.subtle.exportKey( |  | ||||||
| 			"jwk" |  | ||||||
| 		, result.privateKey |  | ||||||
| 		).then(function (privJwk) { |  | ||||||
| 
 |  | ||||||
| 			accountJwk = privJwk; |  | ||||||
| 			console.log('private jwk:'); |  | ||||||
| 			console.log(JSON.stringify(privJwk, null, 2)); |  | ||||||
| 
 | 
 | ||||||
|       return privJwk; |       return privJwk; | ||||||
|       /* |       /* | ||||||
| 			return webCrypto.subtle.exportKey( |       return webCrypto.subtle.exportKey( | ||||||
| 				"pkcs8" |         "pkcs8" | ||||||
| 			, result.privateKey |       , result.privateKey | ||||||
| 			).then(function (keydata) { |       ).then(function (keydata) { | ||||||
| 				console.log('pkcs8:'); |         console.log('pkcs8:'); | ||||||
| 				console.log(Array.from(new Uint8Array(keydata))); |         console.log(Array.from(new Uint8Array(keydata))); | ||||||
| 
 | 
 | ||||||
|         return privJwk; |         return privJwk; | ||||||
|         //return accountKeypair;
 |         //return accountKeypair;
 | ||||||
| 			}); |       }); | ||||||
|       */ |       */ | ||||||
| 		}) |     }); | ||||||
| 	}); |   }); | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| // json to url-safe base64
 | // json to url-safe base64
 | ||||||
| BACME._jsto64 = function (json) { | BACME._jsto64 = function (json) { | ||||||
| 	return btoa(JSON.stringify(json)).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/g, ''); |   return btoa(JSON.stringify(json)).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/g, ''); | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| var textEncoder = new TextEncoder(); | var textEncoder = new TextEncoder(); | ||||||
| @ -158,7 +131,7 @@ BACME._importKey = function (jwk) { | |||||||
|       e: priv.e |       e: priv.e | ||||||
|     , kty: priv.kty |     , kty: priv.kty | ||||||
|     , n: priv.n |     , n: priv.n | ||||||
|     } |     }; | ||||||
|     if (!priv.p) { |     if (!priv.p) { | ||||||
|       priv = null; |       priv = null; | ||||||
|     } |     } | ||||||
| @ -167,7 +140,7 @@ BACME._importKey = function (jwk) { | |||||||
|   return window.crypto.subtle.importKey( |   return window.crypto.subtle.importKey( | ||||||
|     "jwk" |     "jwk" | ||||||
|   , pub |   , pub | ||||||
| 	, wcOpts |   , wcOpts | ||||||
|   , extractable |   , extractable | ||||||
|   , [ "verify" ] |   , [ "verify" ] | ||||||
|   ).then(function (publicKey) { |   ).then(function (publicKey) { | ||||||
| @ -271,8 +244,8 @@ BACME.accounts.sign = function (opts) { | |||||||
|       protectedJson |       protectedJson | ||||||
|     ); |     ); | ||||||
| 
 | 
 | ||||||
| 		// Note: this function hashes before signing so send data, not the hash
 |     // Note: this function hashes before signing so send data, not the hash
 | ||||||
| 		return BACME._sign({ |     return BACME._sign({ | ||||||
|       abstractKey: abstractKey |       abstractKey: abstractKey | ||||||
|     , payload64: payload64 |     , payload64: payload64 | ||||||
|     , protected64: protected64 |     , protected64: protected64 | ||||||
| @ -280,30 +253,29 @@ BACME.accounts.sign = function (opts) { | |||||||
|   }); |   }); | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| var account; |  | ||||||
| var accountId; | var accountId; | ||||||
| 
 | 
 | ||||||
| BACME.accounts.set = function (opts) { | BACME.accounts.set = function (opts) { | ||||||
| 	nonce = null; |   nonce = null; | ||||||
| 	return window.fetch(accountUrl, { |   return window.fetch(accountUrl, { | ||||||
| 		mode: 'cors' |     mode: 'cors' | ||||||
| 	, method: 'POST' |   , method: 'POST' | ||||||
| 	, headers: { 'Content-Type': 'application/jose+json' } |   , headers: { 'Content-Type': 'application/jose+json' } | ||||||
| 	, body: JSON.stringify(opts.signedAccount) |   , body: JSON.stringify(opts.signedAccount) | ||||||
| 	}).then(function (resp) { |   }).then(function (resp) { | ||||||
| 		BACME._logHeaders(resp); |     BACME._logHeaders(resp); | ||||||
| 		nonce = resp.headers.get('replay-nonce'); |     nonce = resp.headers.get('replay-nonce'); | ||||||
| 		accountId = resp.headers.get('location'); |     accountId = resp.headers.get('location'); | ||||||
| 		console.log('Next nonce:', nonce); |     console.log('Next nonce:', nonce); | ||||||
| 		console.log('Location/kid:', accountId); |     console.log('Location/kid:', accountId); | ||||||
| 
 | 
 | ||||||
| 		if (!resp.headers.get('content-type')) { |     if (!resp.headers.get('content-type')) { | ||||||
| 		 console.log('Body: <none>'); |      console.log('Body: <none>'); | ||||||
| 
 | 
 | ||||||
| 		 return { kid: accountId }; |      return { kid: accountId }; | ||||||
| 		} |     } | ||||||
| 
 | 
 | ||||||
| 		return resp.json().then(function (result) { |     return resp.json().then(function (result) { | ||||||
|       if (/^Error/i.test(result.detail)) { |       if (/^Error/i.test(result.detail)) { | ||||||
|         return Promise.reject(new Error(result.detail)); |         return Promise.reject(new Error(result.detail)); | ||||||
|       } |       } | ||||||
| @ -311,21 +283,20 @@ BACME.accounts.set = function (opts) { | |||||||
|       BACME._logBody(result); |       BACME._logBody(result); | ||||||
| 
 | 
 | ||||||
|       return result; |       return result; | ||||||
| 		}); |     }); | ||||||
| 	}); |   }); | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| var orderUrl; | var orderUrl; | ||||||
| var signedOrder; |  | ||||||
| 
 | 
 | ||||||
| BACME.orders = {}; | BACME.orders = {}; | ||||||
| 
 | 
 | ||||||
| // identifiers = [ { type: 'dns', value: 'example.com' }, { type: 'dns', value: '*.example.com' } ]
 | // identifiers = [ { type: 'dns', value: 'example.com' }, { type: 'dns', value: '*.example.com' } ]
 | ||||||
| // signedAccount
 | // signedAccount
 | ||||||
| BACME.orders.sign = function (opts) { | BACME.orders.sign = function (opts) { | ||||||
| 	var payload64 = BACME._jsto64({ identifiers: opts.identifiers }); |   var payload64 = BACME._jsto64({ identifiers: opts.identifiers }); | ||||||
| 
 | 
 | ||||||
| 	return BACME._importKey(opts.jwk).then(function (abstractKey) { |   return BACME._importKey(opts.jwk).then(function (abstractKey) { | ||||||
|     var protected64 = BACME._jsto64( |     var protected64 = BACME._jsto64( | ||||||
|       { nonce: nonce, alg: abstractKey.meta.alg/*'ES256'*/, url: orderUrl, kid: opts.kid } |       { nonce: nonce, alg: abstractKey.meta.alg/*'ES256'*/, url: orderUrl, kid: opts.kid } | ||||||
|     ); |     ); | ||||||
| @ -345,36 +316,35 @@ BACME.orders.sign = function (opts) { | |||||||
|   }); |   }); | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| var order; |  | ||||||
| var currentOrderUrl; | var currentOrderUrl; | ||||||
| var authorizationUrls; | var authorizationUrls; | ||||||
| var finalizeUrl; | var finalizeUrl; | ||||||
| 
 | 
 | ||||||
| BACME.orders.create = function (opts) { | BACME.orders.create = function (opts) { | ||||||
| 	nonce = null; |   nonce = null; | ||||||
| 	return window.fetch(orderUrl, { |   return window.fetch(orderUrl, { | ||||||
| 		mode: 'cors' |     mode: 'cors' | ||||||
| 	, method: 'POST' |   , method: 'POST' | ||||||
| 	, headers: { 'Content-Type': 'application/jose+json' } |   , headers: { 'Content-Type': 'application/jose+json' } | ||||||
| 	, body: JSON.stringify(opts.signedOrder) |   , body: JSON.stringify(opts.signedOrder) | ||||||
| 	}).then(function (resp) { |   }).then(function (resp) { | ||||||
|     BACME._logHeaders(resp); |     BACME._logHeaders(resp); | ||||||
| 		currentOrderUrl = resp.headers.get('location'); |     currentOrderUrl = resp.headers.get('location'); | ||||||
| 		nonce = resp.headers.get('replay-nonce'); |     nonce = resp.headers.get('replay-nonce'); | ||||||
| 		console.log('Next nonce:', nonce); |     console.log('Next nonce:', nonce); | ||||||
| 
 | 
 | ||||||
| 		return resp.json().then(function (result) { |     return resp.json().then(function (result) { | ||||||
|       if (/^Error/i.test(result.detail)) { |       if (/^Error/i.test(result.detail)) { | ||||||
|         return Promise.reject(new Error(result.detail)); |         return Promise.reject(new Error(result.detail)); | ||||||
|       } |       } | ||||||
| 			authorizationUrls = result.authorizations; |       authorizationUrls = result.authorizations; | ||||||
| 			finalizeUrl = result.finalize; |       finalizeUrl = result.finalize; | ||||||
|       BACME._logBody(result); |       BACME._logBody(result); | ||||||
| 
 | 
 | ||||||
|       result.url = currentOrderUrl; |       result.url = currentOrderUrl; | ||||||
|       return result; |       return result; | ||||||
| 		}); |     }); | ||||||
| 	}); |   }); | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| BACME.challenges = {}; | BACME.challenges = {}; | ||||||
| @ -395,22 +365,22 @@ BACME.challenges.all = function () { | |||||||
|   return next(); |   return next(); | ||||||
| }; | }; | ||||||
| BACME.challenges.view = function () { | BACME.challenges.view = function () { | ||||||
| 	var authzUrl = authorizationUrls.pop(); |   var authzUrl = authorizationUrls.pop(); | ||||||
| 	var token; |   var token; | ||||||
| 	var challengeDomain; |   var challengeDomain; | ||||||
| 	var challengeUrl; |   var challengeUrl; | ||||||
| 
 | 
 | ||||||
| 	return window.fetch(authzUrl, { |   return window.fetch(authzUrl, { | ||||||
| 		mode: 'cors' |     mode: 'cors' | ||||||
| 	}).then(function (resp) { |   }).then(function (resp) { | ||||||
|     BACME._logHeaders(resp); |     BACME._logHeaders(resp); | ||||||
| 
 | 
 | ||||||
| 		return resp.json().then(function (result) { |     return resp.json().then(function (result) { | ||||||
| 			// Note: select the challenge you wish to use
 |       // Note: select the challenge you wish to use
 | ||||||
| 			var challenge = result.challenges.slice(0).pop(); |       var challenge = result.challenges.slice(0).pop(); | ||||||
| 			token = challenge.token; |       token = challenge.token; | ||||||
| 			challengeUrl = challenge.url; |       challengeUrl = challenge.url; | ||||||
| 			challengeDomain = result.identifier.value; |       challengeDomain = result.identifier.value; | ||||||
| 
 | 
 | ||||||
|       BACME._logBody(result); |       BACME._logBody(result); | ||||||
| 
 | 
 | ||||||
| @ -424,8 +394,8 @@ BACME.challenges.view = function () { | |||||||
|       //, url: challenge.url
 |       //, url: challenge.url
 | ||||||
|       //, domain: result.identifier.value,
 |       //, domain: result.identifier.value,
 | ||||||
|       }; |       }; | ||||||
| 		}); |     }); | ||||||
| 	}); |   }); | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| var thumbprint; | var thumbprint; | ||||||
| @ -435,7 +405,7 @@ var dnsAuth; | |||||||
| var dnsRecord; | var dnsRecord; | ||||||
| 
 | 
 | ||||||
| BACME.thumbprint = function (opts) { | BACME.thumbprint = function (opts) { | ||||||
| 	// https://stackoverflow.com/questions/42588786/how-to-fingerprint-a-jwk
 |   // https://stackoverflow.com/questions/42588786/how-to-fingerprint-a-jwk
 | ||||||
| 
 | 
 | ||||||
|   var accountJwk = opts.jwk; |   var accountJwk = opts.jwk; | ||||||
|   var keys; |   var keys; | ||||||
| @ -446,34 +416,34 @@ BACME.thumbprint = function (opts) { | |||||||
|     keys = [ 'e', 'kty', 'n' ]; |     keys = [ 'e', 'kty', 'n' ]; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
| 	var accountPublicStr = '{' + keys.map(function (key) { |   var accountPublicStr = '{' + keys.map(function (key) { | ||||||
| 		return '"' + key + '":"' + accountJwk[key] + '"'; |     return '"' + key + '":"' + accountJwk[key] + '"'; | ||||||
| 	}).join(',') + '}'; |   }).join(',') + '}'; | ||||||
| 
 | 
 | ||||||
| 	return window.crypto.subtle.digest( |   return window.crypto.subtle.digest( | ||||||
| 		{ name: "SHA-256" } // SHA-256 is spec'd, non-optional
 |     { name: "SHA-256" } // SHA-256 is spec'd, non-optional
 | ||||||
| 	, textEncoder.encode(accountPublicStr) |   , textEncoder.encode(accountPublicStr) | ||||||
| 	).then(function (hash) { |   ).then(function (hash) { | ||||||
| 		thumbprint = btoa(Array.prototype.map.call(new Uint8Array(hash), function (ch) { |     thumbprint = btoa(Array.prototype.map.call(new Uint8Array(hash), function (ch) { | ||||||
| 			return String.fromCharCode(ch); |       return String.fromCharCode(ch); | ||||||
| 		}).join('')).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/g, ''); |     }).join('')).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/g, ''); | ||||||
| 
 | 
 | ||||||
| 		console.log('Thumbprint:'); |     console.log('Thumbprint:'); | ||||||
| 		console.log(opts); |     console.log(opts); | ||||||
| 		console.log(accountPublicStr); |     console.log(accountPublicStr); | ||||||
| 		console.log(thumbprint); |     console.log(thumbprint); | ||||||
| 
 | 
 | ||||||
|     return thumbprint; |     return thumbprint; | ||||||
| 	}); |   }); | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| // { token, thumbprint, challengeDomain }
 | // { token, thumbprint, challengeDomain }
 | ||||||
| BACME.challenges['http-01'] = function (opts) { | BACME.challenges['http-01'] = function (opts) { | ||||||
| 	// The contents of the key authorization file
 |   // The contents of the key authorization file
 | ||||||
| 	keyAuth = opts.token + '.' + opts.thumbprint; |   keyAuth = opts.token + '.' + opts.thumbprint; | ||||||
| 
 | 
 | ||||||
| 	// Where the key authorization file goes
 |   // Where the key authorization file goes
 | ||||||
| 	httpPath = 'http://' + opts.challengeDomain + '/.well-known/acme-challenge/' + opts.token; |   httpPath = 'http://' + opts.challengeDomain + '/.well-known/acme-challenge/' + opts.token; | ||||||
| 
 | 
 | ||||||
|   console.log("echo '" + keyAuth + "' > '" + httpPath + "'"); |   console.log("echo '" + keyAuth + "' > '" + httpPath + "'"); | ||||||
| 
 | 
 | ||||||
| @ -487,28 +457,28 @@ BACME.challenges['http-01'] = function (opts) { | |||||||
| BACME.challenges['dns-01'] = function (opts) { | BACME.challenges['dns-01'] = function (opts) { | ||||||
|   console.log('opts.keyAuth for DNS:'); |   console.log('opts.keyAuth for DNS:'); | ||||||
|   console.log(opts.keyAuth); |   console.log(opts.keyAuth); | ||||||
| 	return window.crypto.subtle.digest( |   return window.crypto.subtle.digest( | ||||||
| 		{ name: "SHA-256", } |     { name: "SHA-256", } | ||||||
| 	, textEncoder.encode(opts.keyAuth) |   , textEncoder.encode(opts.keyAuth) | ||||||
| 	).then(function (hash) { |   ).then(function (hash) { | ||||||
| 		dnsAuth = btoa(Array.prototype.map.call(new Uint8Array(hash), function (ch) { |     dnsAuth = btoa(Array.prototype.map.call(new Uint8Array(hash), function (ch) { | ||||||
| 			return String.fromCharCode(ch); |       return String.fromCharCode(ch); | ||||||
| 		}).join('')).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/g, ''); |     }).join('')).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/g, ''); | ||||||
| 
 | 
 | ||||||
| 		dnsRecord = '_acme-challenge.' + opts.challengeDomain; |     dnsRecord = '_acme-challenge.' + opts.challengeDomain; | ||||||
| 
 | 
 | ||||||
| 		console.log('DNS TXT Auth:'); |     console.log('DNS TXT Auth:'); | ||||||
| 		// The name of the record
 |     // The name of the record
 | ||||||
| 		console.log(dnsRecord); |     console.log(dnsRecord); | ||||||
| 		// The TXT record value
 |     // The TXT record value
 | ||||||
| 		console.log(dnsAuth); |     console.log(dnsAuth); | ||||||
| 
 | 
 | ||||||
|     return { |     return { | ||||||
|       type: 'TXT' |       type: 'TXT' | ||||||
|     , host: dnsRecord |     , host: dnsRecord | ||||||
|     , answer: dnsAuth |     , answer: dnsAuth | ||||||
|     }; |     }; | ||||||
| 	}); |   }); | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| var challengePollUrl; | var challengePollUrl; | ||||||
| @ -516,84 +486,108 @@ var challengePollUrl; | |||||||
| // { jwk, challengeUrl, accountId (kid) }
 | // { jwk, challengeUrl, accountId (kid) }
 | ||||||
| BACME.challenges.accept = function (opts) { | BACME.challenges.accept = function (opts) { | ||||||
|   var payload64 = BACME._jsto64( |   var payload64 = BACME._jsto64( | ||||||
| 		{} |     {} | ||||||
| 	); |   ); | ||||||
| 
 | 
 | ||||||
|   return BACME._importKey(opts.jwk).then(function (abstractKey) { |   return BACME._importKey(opts.jwk).then(function (abstractKey) { | ||||||
|     var protected64 = BACME._jsto64( |     var protected64 = BACME._jsto64( | ||||||
|       { nonce: nonce, alg: abstractKey.meta.alg/*'ES256'*/, url: opts.challengeUrl, kid: opts.accountId } |       { nonce: nonce, alg: abstractKey.meta.alg/*'ES256'*/, url: opts.challengeUrl, kid: opts.accountId } | ||||||
|     ); |     ); | ||||||
| 		return BACME._sign({ |     return BACME._sign({ | ||||||
|       abstractKey: abstractKey |       abstractKey: abstractKey | ||||||
|     , payload64: payload64 |     , payload64: payload64 | ||||||
|     , protected64: protected64 |     , protected64: protected64 | ||||||
|     }); |     }); | ||||||
|   }).then(function (signedAccept) { |   }).then(function (signedAccept) { | ||||||
| 
 | 
 | ||||||
| 	  nonce = null; |     nonce = null; | ||||||
| 		return window.fetch( |     return window.fetch( | ||||||
| 			opts.challengeUrl |       opts.challengeUrl | ||||||
| 		, { mode: 'cors' |     , { mode: 'cors' | ||||||
| 			, method: 'POST' |       , method: 'POST' | ||||||
| 			, headers: { 'Content-Type': 'application/jose+json' } |       , headers: { 'Content-Type': 'application/jose+json' } | ||||||
| 			, body: JSON.stringify(signedAccept) |       , body: JSON.stringify(signedAccept) | ||||||
| 			} |       } | ||||||
| 		).then(function (resp) { |     ).then(function (resp) { | ||||||
|       BACME._logHeaders(resp); |       BACME._logHeaders(resp); | ||||||
| 			nonce = resp.headers.get('replay-nonce'); |       nonce = resp.headers.get('replay-nonce'); | ||||||
|       console.log("ACCEPT NONCE:", nonce); |       console.log("ACCEPT NONCE:", nonce); | ||||||
| 
 | 
 | ||||||
| 			return resp.json().then(function (reply) { |       return resp.json().then(function (reply) { | ||||||
|         challengePollUrl = reply.url; |         challengePollUrl = reply.url; | ||||||
| 
 | 
 | ||||||
| 				console.log('Challenge ACK:'); |         console.log('Challenge ACK:'); | ||||||
| 				console.log(JSON.stringify(reply)); |         console.log(JSON.stringify(reply)); | ||||||
|         return reply; |         return reply; | ||||||
| 			}); |       }); | ||||||
| 		}); |     }); | ||||||
| 	}); |   }); | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| BACME.challenges.check = function (opts) { | BACME.challenges.check = function (opts) { | ||||||
| 	return window.fetch(opts.challengePollUrl, { mode: 'cors' }).then(function (resp) { |   return window.fetch(opts.challengePollUrl, { mode: 'cors' }).then(function (resp) { | ||||||
|     BACME._logHeaders(resp); |     BACME._logHeaders(resp); | ||||||
| 
 | 
 | ||||||
| 		return resp.json().then(function (reply) { |     return resp.json().then(function (reply) { | ||||||
| 			challengePollUrl = reply.url; |       challengePollUrl = reply.url; | ||||||
| 
 | 
 | ||||||
|       BACME._logBody(reply); |       BACME._logBody(reply); | ||||||
| 
 | 
 | ||||||
| 			return reply; |       return reply; | ||||||
| 		}); |     }); | ||||||
| 	}); |   }); | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| var domainKeypair; | var domainKeypair; | ||||||
| var domainJwk; | 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( | ||||||
|  |     { name: "ECDSA", namedCurve: "P-256" } | ||||||
|  |   , extractable | ||||||
|  |   , [ 'sign', 'verify' ] | ||||||
|  |   ); | ||||||
|  | }; | ||||||
| BACME.domains = {}; | BACME.domains = {}; | ||||||
| // TODO factor out from BACME.accounts.generateKeypair
 | // TODO factor out from BACME.accounts.generateKeypair even more
 | ||||||
| BACME.domains.generateKeypair = function () { | BACME.domains.generateKeypair = function (opts) { | ||||||
| 	var extractable = true; |   return BACME.generateKeypair(opts).then(function (result) { | ||||||
| 	return window.crypto.subtle.generateKey( |     domainKeypair = result; | ||||||
| 		{ name: "ECDSA", namedCurve: "P-256" } |  | ||||||
| 	, extractable |  | ||||||
| 	, [ 'sign', 'verify' ] |  | ||||||
| 	).then(function (result) { |  | ||||||
| 		domainKeypair = result; |  | ||||||
| 
 | 
 | ||||||
| 		return window.crypto.subtle.exportKey( |     return window.crypto.subtle.exportKey( | ||||||
| 			"jwk" |       "jwk" | ||||||
| 		, result.privateKey |     , result.privateKey | ||||||
| 		).then(function (jwk) { |     ).then(function (privJwk) { | ||||||
| 
 | 
 | ||||||
| 			domainJwk = jwk; |       domainJwk = privJwk; | ||||||
| 			console.log('private jwk:'); |       console.log('private jwk:'); | ||||||
| 			console.log(JSON.stringify(jwk, null, 2)); |       console.log(JSON.stringify(privJwk, null, 2)); | ||||||
| 
 | 
 | ||||||
|       return domainKeypair; |       return privJwk; | ||||||
| 		}) |     }); | ||||||
| 	}); |   }); | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| // { serverJwk, domains }
 | // { serverJwk, domains }
 | ||||||
| @ -607,41 +601,41 @@ var certificateUrl; | |||||||
| 
 | 
 | ||||||
| // { csr, jwk, finalizeUrl, accountId }
 | // { csr, jwk, finalizeUrl, accountId }
 | ||||||
| BACME.orders.finalize = function (opts) { | BACME.orders.finalize = function (opts) { | ||||||
| 	var payload64 = BACME._jsto64( |   var payload64 = BACME._jsto64( | ||||||
| 		{ csr: opts.csr } |     { csr: opts.csr } | ||||||
| 	); |   ); | ||||||
| 
 | 
 | ||||||
|   return BACME._importKey(opts.jwk).then(function (abstractKey) { |   return BACME._importKey(opts.jwk).then(function (abstractKey) { | ||||||
|     var protected64 = BACME._jsto64( |     var protected64 = BACME._jsto64( | ||||||
|       { nonce: nonce, alg: abstractKey.meta.alg/*'ES256'*/, url: opts.finalizeUrl, kid: opts.accountId } |       { nonce: nonce, alg: abstractKey.meta.alg/*'ES256'*/, url: opts.finalizeUrl, kid: opts.accountId } | ||||||
|     ); |     ); | ||||||
| 		return BACME._sign({ |     return BACME._sign({ | ||||||
|       abstractKey: abstractKey |       abstractKey: abstractKey | ||||||
|     , payload64: payload64 |     , payload64: payload64 | ||||||
|     , protected64: protected64 |     , protected64: protected64 | ||||||
|     }); |     }); | ||||||
|   }).then(function (signedFinal) { |   }).then(function (signedFinal) { | ||||||
| 
 | 
 | ||||||
| 	  nonce = null; |     nonce = null; | ||||||
| 		return window.fetch( |     return window.fetch( | ||||||
| 			opts.finalizeUrl |       opts.finalizeUrl | ||||||
| 		, { mode: 'cors' |     , { mode: 'cors' | ||||||
| 			, method: 'POST' |       , method: 'POST' | ||||||
| 			, headers: { 'Content-Type': 'application/jose+json' } |       , headers: { 'Content-Type': 'application/jose+json' } | ||||||
| 			, body: JSON.stringify(signedFinal) |       , body: JSON.stringify(signedFinal) | ||||||
| 			} |       } | ||||||
| 		).then(function (resp) { |     ).then(function (resp) { | ||||||
|       BACME._logHeaders(resp); |       BACME._logHeaders(resp); | ||||||
| 			nonce = resp.headers.get('replay-nonce'); |       nonce = resp.headers.get('replay-nonce'); | ||||||
| 
 | 
 | ||||||
| 			return resp.json().then(function (reply) { |       return resp.json().then(function (reply) { | ||||||
| 				certificateUrl = reply.certificate; |         certificateUrl = reply.certificate; | ||||||
|         BACME._logBody(reply); |         BACME._logBody(reply); | ||||||
| 
 | 
 | ||||||
|         return reply; |         return reply; | ||||||
| 			}); |       }); | ||||||
| 		}); |     }); | ||||||
| 	}); |   }); | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| BACME.orders.receive = function (opts) { | BACME.orders.receive = function (opts) { | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user