progress on bacme
This commit is contained in:
		
							parent
							
								
									549771a0bc
								
							
						
					
					
						commit
						4fd5fd8bd9
					
				
							
								
								
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @ -1 +1,2 @@ | |||||||
| js/pkijs.org | js/pkijs.org | ||||||
|  | js/browser-csr | ||||||
|  | |||||||
| @ -14,6 +14,9 @@ | |||||||
|     <script src="./js/pkijs.org/v1.3.33/asn1.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_schema.js"></script> | ||||||
|     <script src="./js/pkijs.org/v1.3.33/x509_simpl.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> | ||||||
|  | 
 | ||||||
|  |     <script src="./js/bacme.js"></script> | ||||||
|     <script src="./js/app.js"></script> |     <script src="./js/app.js"></script> | ||||||
|   </body> |   </body> | ||||||
| </html> | </html> | ||||||
|  | |||||||
| @ -1,6 +1,14 @@ | |||||||
|  | #!/bin/bash | ||||||
|  | 
 | ||||||
| mkdir -p js/pkijs.org/v1.3.33/ | mkdir -p js/pkijs.org/v1.3.33/ | ||||||
| pushd js/pkijs.org/v1.3.33/ | pushd 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/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_schema.js | ||||||
|   wget -c https://raw.githubusercontent.com/PeculiarVentures/PKI.js/41b63af760cacb565dd850fb3466ada4ca163eff/org/pkijs/x509_simpl.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 |   wget -c https://raw.githubusercontent.com/PeculiarVentures/ASN1.js/f7181c21c61e53a940ea24373ab489ad86d51bc1/org/pkijs/asn1.js | ||||||
|  | popd | ||||||
|  | 
 | ||||||
|  | mkdir -p js/browser-csr/v1.0.0-alpha/ | ||||||
|  | pushd js/browser-csr/v1.0.0-alpha/ | ||||||
|  |   wget -c https://git.coolaj86.com/coolaj86/browser-csr.js/raw/commit/c513a862a4e016794da800f0c2eec858b80837ab/csr.js | ||||||
|  | popd | ||||||
|  | |||||||
| @ -1,7 +1,6 @@ | |||||||
| (function () { | (function () { | ||||||
| 'use strict'; | 'use strict'; | ||||||
| 
 | 
 | ||||||
|   console.log("Hello, World!"); |   //window.document.querySelector('.js-acme-directory-url').value = 'https://acme-v02.api.letsencrypt.org/directory';
 | ||||||
| 
 |  | ||||||
|   window.document.querySelector('.js-acme-directory-url').value = 'https://acme-staging-v02.api.letsencrypt.org/directory'; |   window.document.querySelector('.js-acme-directory-url').value = 'https://acme-staging-v02.api.letsencrypt.org/directory'; | ||||||
| }()); | }()); | ||||||
|  | |||||||
							
								
								
									
										478
									
								
								js/bacme.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										478
									
								
								js/bacme.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,478 @@ | |||||||
|  | (function (exports) { | ||||||
|  | 'use strict'; | ||||||
|  | 
 | ||||||
|  | var BACME = exports.BACME = {}; | ||||||
|  | var webFetch = exports.fetch; | ||||||
|  | var webCrypto = exports.crypto; | ||||||
|  | 
 | ||||||
|  | var directoryUrl = 'https://acme-staging-v02.api.letsencrypt.org/directory'; | ||||||
|  | var directory; | ||||||
|  | 
 | ||||||
|  | var nonceUrl = directory.newNonce || 'https://acme-staging-v02.api.letsencrypt.org/acme/new-nonce'; | ||||||
|  | var nonce; | ||||||
|  | 
 | ||||||
|  | var accountKeypair; | ||||||
|  | var accountJwk; | ||||||
|  | 
 | ||||||
|  | var accountUrl = directory.newAccount; | ||||||
|  | var signedAccount; | ||||||
|  | 
 | ||||||
|  | BACME.challengePrefixes = { | ||||||
|  |   'http-01': '/.well-known/acme-challenge' | ||||||
|  | , 'dns-01': '_acme-challenge' | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | BACME._logHeaders = function (resp) { | ||||||
|  | 	console.log('Headers:'); | ||||||
|  | 	Array.from(resp.headers.entries()).forEach(function (h) { console.log(h[0] + ': ' + h[1]); }); | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | BACME._logBody = function (body) { | ||||||
|  | 	console.log('Body:'); | ||||||
|  | 	console.log(JSON.stringify(body, null, 2)); | ||||||
|  | 	console.log(''); | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | BACME.directory = function (url) { | ||||||
|  | 	return webFetch(directoryUrl, { mode: 'cors' }).then(function (resp) { | ||||||
|  | 		BACME._logHeaders(resp); | ||||||
|  | 		return resp.json().then(function (body) { | ||||||
|  | 			directory = body; | ||||||
|  |       BACME._logBody(body); | ||||||
|  |       return body; | ||||||
|  | 		}); | ||||||
|  | 	}); | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | BACME.nonce = function () { | ||||||
|  | 	return webFetch(nonceUrl, { mode: 'cors' }).then(function (resp) { | ||||||
|  |     BACME._logHeaders(resp); | ||||||
|  | 		nonce = resp.headers.get('replay-nonce'); | ||||||
|  | 		console.log('Nonce:', nonce); | ||||||
|  | 		// resp.body is empty
 | ||||||
|  | 		return resp.headers.get('replay-nonce'); | ||||||
|  | 	}); | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | BACME.accounts = {}; | ||||||
|  | BACME.accounts.generateKeypair = function () { | ||||||
|  | 	// https://github.com/diafygi/webcrypto-examples#ecdsa---generatekey
 | ||||||
|  | 	var extractable = true; | ||||||
|  | 	return webCrypto.subtle.generateKey( | ||||||
|  | 		{ name: "ECDSA", namedCurve: "P-256" } | ||||||
|  | 	, extractable | ||||||
|  | 	, [ 'sign', 'verify' ] | ||||||
|  | 	).then(function (result) { | ||||||
|  | 		accountKeypair = result; | ||||||
|  | 
 | ||||||
|  | 		return webCrypto.subtle.exportKey( | ||||||
|  | 			"jwk" | ||||||
|  | 		, result.privateKey | ||||||
|  | 		).then(function (jwk) { | ||||||
|  | 
 | ||||||
|  | 			accountJwk = jwk; | ||||||
|  | 			console.log('private jwk:'); | ||||||
|  | 			console.log(JSON.stringify(jwk, null, 2)); | ||||||
|  | 
 | ||||||
|  | 			return webCrypto.subtle.exportKey( | ||||||
|  | 				"pkcs8" | ||||||
|  | 			, result.privateKey | ||||||
|  | 			).then(function (keydata) { | ||||||
|  | 				console.log('pkcs8:'); | ||||||
|  | 				console.log(Array.from(new Uint8Array(keydata))); | ||||||
|  | 
 | ||||||
|  |         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(); | ||||||
|  | 
 | ||||||
|  | // email = john.doe@gmail.com
 | ||||||
|  | BACME.accounts.sign = function (email) { | ||||||
|  | 	var payload64 = BACME._jsto64( | ||||||
|  | 		{ termsOfServiceAgreed: true | ||||||
|  | 		, onlyReturnExisting: false | ||||||
|  | 		, contact: [ 'mailto:' + email ] | ||||||
|  | 		} | ||||||
|  | 	); | ||||||
|  | 
 | ||||||
|  | 	var protected64 = BACME._jsto64( | ||||||
|  | 		{ nonce: nonce | ||||||
|  | 		, url: accountUrl | ||||||
|  | 		, alg: 'ES256' | ||||||
|  | 		, jwk: { | ||||||
|  | 				kty: accountJwk.kty | ||||||
|  | 			, crv: accountJwk.crv | ||||||
|  | 			, x: accountJwk.x | ||||||
|  | 			, y: accountJwk.y | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	); | ||||||
|  | 
 | ||||||
|  | 	// Note: this function hashes before signing so send data, not the hash
 | ||||||
|  | 	return window.crypto.subtle.sign( | ||||||
|  | 		{ name: "ECDSA", hash: { name: "SHA-256" } } | ||||||
|  | 	, accountKeypair.privateKey | ||||||
|  | 	, textEncoder.encode(protected64 + '.' + payload64) | ||||||
|  | 	).then(function (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('URL-safe Base64 Signature:'); | ||||||
|  | 		console.log(sig64); | ||||||
|  | 
 | ||||||
|  | 		signedAccount = { | ||||||
|  | 			protected: protected64 | ||||||
|  | 		, payload: payload64 | ||||||
|  | 		, signature: sig64 | ||||||
|  | 		}; | ||||||
|  | 		console.log('Signed Base64 Account:'); | ||||||
|  | 		console.log(JSON.stringify(signedAccount, null, 2)); | ||||||
|  | 	}); | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | var account; | ||||||
|  | var accountId; | ||||||
|  | 
 | ||||||
|  | BACME.accounts.set = function () { | ||||||
|  | 	nonce = null; | ||||||
|  | 	return window.fetch(accountUrl, { | ||||||
|  | 		mode: 'cors' | ||||||
|  | 	, method: 'POST' | ||||||
|  | 	, headers: { 'Content-Type': 'application/jose+json' } | ||||||
|  | 	, body: JSON.stringify(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; | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		return resp.json().then(function (result) { | ||||||
|  |       BACME._logBody(result); | ||||||
|  | 		}); | ||||||
|  | 	}); | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | var orderUrl = directory.newOrder || "https://acme-staging-v02.api.letsencrypt.org/acme/new-order"; | ||||||
|  | var signedOrder; | ||||||
|  | 
 | ||||||
|  | BACME.orders = {}; | ||||||
|  | 
 | ||||||
|  | // identifiers = [ { type: 'dns', value: 'example.com' }, { type: 'dns', value: '*.example.com' } ]
 | ||||||
|  | BACME.orders.sign = function (identifiers) { | ||||||
|  | 	var payload64 = jsto64({ identifiers: identifiers }); | ||||||
|  | 
 | ||||||
|  | 	var protected64 = jsto64( | ||||||
|  | 		{ nonce: nonce, alg: 'ES256', url: orderUrl, kid: accountId } | ||||||
|  | 	); | ||||||
|  | 
 | ||||||
|  | 	return window.crypto.subtle.sign( | ||||||
|  | 		{ name: "ECDSA", hash: { name: "SHA-256" } } | ||||||
|  | 	, accountKeypair.privateKey | ||||||
|  | 	, textEncoder.encode(protected64 + '.' + payload64) | ||||||
|  | 	).then(function (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('URL-safe Base64 Signature:'); | ||||||
|  | 		console.log(sig64); | ||||||
|  | 
 | ||||||
|  | 		signedOrder = { | ||||||
|  | 			protected: protected64 | ||||||
|  | 		, payload: payload64 | ||||||
|  | 		, signature: sig64 | ||||||
|  | 		}; | ||||||
|  | 		console.log('Signed Base64 Order:'); | ||||||
|  | 		console.log(JSON.stringify(signedAccount, null, 2)); | ||||||
|  | 
 | ||||||
|  |     return signedOrder; | ||||||
|  | 	}); | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | var order; | ||||||
|  | var currentOrderUrl; | ||||||
|  | var authorizationUrls; | ||||||
|  | var finalizeUrl; | ||||||
|  | 
 | ||||||
|  | BACME.orders.create = function () { | ||||||
|  | 	nonce = null; | ||||||
|  | 	return window.fetch(orderUrl, { | ||||||
|  | 		mode: 'cors' | ||||||
|  | 	, method: 'POST' | ||||||
|  | 	, headers: { 'Content-Type': 'application/jose+json' } | ||||||
|  | 	, body: JSON.stringify(signedOrder) | ||||||
|  | 	}).then(function (resp) { | ||||||
|  | 		console.log('Headers:'); | ||||||
|  | 		Array.from(resp.headers.entries()).forEach(function (h) { console.log(h[0] + ': ' + h[1]); }); | ||||||
|  | 		currentOrderUrl = resp.headers.get('location'); | ||||||
|  | 		nonce = resp.headers.get('replay-nonce'); | ||||||
|  | 		console.log('Next nonce:', nonce); | ||||||
|  | 
 | ||||||
|  | 		return resp.json().then(function (result) { | ||||||
|  | 			authorizationUrls = result.authorizations; | ||||||
|  | 			finalizeUrl = result.finalize; | ||||||
|  | 			console.log('Body:'); | ||||||
|  | 			console.log(JSON.stringify(result, null, 2)); | ||||||
|  | 
 | ||||||
|  |       return result; | ||||||
|  | 		}); | ||||||
|  | 	}); | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | BACME.challenges = {}; | ||||||
|  | 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 { token: challenge.token, url: challenge.url, domain: result.identifier.value, challenges: result.challenges }; | ||||||
|  | 		}); | ||||||
|  | 	}); | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | var thumbprint; | ||||||
|  | var keyAuth; | ||||||
|  | var httpPath; | ||||||
|  | var dnsAuth; | ||||||
|  | var dnsRecord; | ||||||
|  | 
 | ||||||
|  | BACME.thumbprint = function () { | ||||||
|  | 	// https://stackoverflow.com/questions/42588786/how-to-fingerprint-a-jwk
 | ||||||
|  | 
 | ||||||
|  | 	var accountPublicStr = '{' + ['crv', 'kty', 'x', 'y'].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(thumbprint); | ||||||
|  | 
 | ||||||
|  |     return thumbprint; | ||||||
|  | 	}); | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | BACME.challenges['http-01'] = function () { | ||||||
|  | 	// The contents of the key authorization file
 | ||||||
|  | 	keyAuth = token + '.' + thumbprint; | ||||||
|  | 
 | ||||||
|  | 	// Where the key authorization file goes
 | ||||||
|  | 	httpPath = 'http://' + challengeDomain + '/.well-known/acme-challenge/' + token; | ||||||
|  | 
 | ||||||
|  |   console.log("echo '" + keyAuth + "' > '" + httpPath + "'"); | ||||||
|  | 
 | ||||||
|  |   return { | ||||||
|  |     path: httpPath | ||||||
|  |   , value: keyAuth | ||||||
|  |   }; | ||||||
|  | }); | ||||||
|  | BACME.challenges['dns-01'] = function () { | ||||||
|  | 	return window.crypto.subtle.digest( | ||||||
|  | 		{ name: "SHA-256", } | ||||||
|  | 	, textEncoder.encode(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.' + 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; | ||||||
|  | 
 | ||||||
|  | BACME.challenges.accept = function () { | ||||||
|  |   var payload64 = jsto64( | ||||||
|  | 		{} | ||||||
|  | 	); | ||||||
|  | 
 | ||||||
|  | 	var protected64 = jsto64( | ||||||
|  | 		{ nonce: nonce, alg: 'ES256', url: challengeUrl, kid: accountId } | ||||||
|  | 	); | ||||||
|  | 
 | ||||||
|  | 	nonce = null; | ||||||
|  | 	return window.crypto.subtle.sign( | ||||||
|  | 		{ name: "ECDSA", hash: { name: "SHA-256" } } | ||||||
|  | 	, accountKeypair.privateKey | ||||||
|  | 	, textEncoder.encode(protected64 + '.' + payload64) | ||||||
|  | 	).then(function (signature) { | ||||||
|  | 
 | ||||||
|  | 		var sig64 = btoa(Array.prototype.map.call(new Uint8Array(signature), function (ch) { | ||||||
|  | 			return String.fromCharCode(ch); | ||||||
|  | 		}).join('')).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/g, ''); | ||||||
|  | 
 | ||||||
|  | 		var body = { | ||||||
|  | 			protected: protected64 | ||||||
|  | 		, payload: payload64 | ||||||
|  | 		, signature: sig64 | ||||||
|  | 		}; | ||||||
|  | 
 | ||||||
|  | 		return window.fetch( | ||||||
|  | 			challengeUrl | ||||||
|  | 		, { mode: 'cors' | ||||||
|  | 			, method: 'POST' | ||||||
|  | 			, headers: { 'Content-Type': 'application/jose+json' } | ||||||
|  | 			, body: JSON.stringify(body) | ||||||
|  | 			} | ||||||
|  | 		).then(function (resp) { | ||||||
|  | 			console.log('Headers:'); | ||||||
|  | 			Array.from(resp.headers.entries()).forEach(function (h) { console.log(h[0] + ': ' + h[1]); }); | ||||||
|  | 			nonce = resp.headers.get('replay-nonce'); | ||||||
|  | 
 | ||||||
|  | 			return resp.json().then(function (reply) { | ||||||
|  | 				challengePollUrl = reply.url; | ||||||
|  | 
 | ||||||
|  | 				console.log('Challenge ACK:'); | ||||||
|  | 				console.log(JSON.stringify(reply)); | ||||||
|  | 			}); | ||||||
|  | 		}); | ||||||
|  | 	}); | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | BACME.challenges.check = function () { | ||||||
|  | 	return window.fetch(challengePollUrl, { mode: 'cors' }).then(function (resp) { | ||||||
|  |     BACME._logHeaders(resp); | ||||||
|  | 		nonce = resp.headers.get('replay-nonce'); | ||||||
|  | 
 | ||||||
|  | 		return resp.json().then(function (reply) { | ||||||
|  | 			challengePollUrl = reply.url; | ||||||
|  | 
 | ||||||
|  |       BACME._logBody(reply); | ||||||
|  | 
 | ||||||
|  | 			return reply; | ||||||
|  | 		}); | ||||||
|  | 	}); | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | var domainKeypair; | ||||||
|  | var domainJwk; | ||||||
|  | 
 | ||||||
|  | BACME.domains = {}; | ||||||
|  | // TODO factor out from BACME.accounts.generateKeypair
 | ||||||
|  | BACME.domains.generateKeypair = function () { | ||||||
|  | 	var extractable = true; | ||||||
|  | 	return window.crypto.subtle.generateKey( | ||||||
|  | 		{ name: "ECDSA", namedCurve: "P-256" } | ||||||
|  | 	, extractable | ||||||
|  | 	, [ 'sign', 'verify' ] | ||||||
|  | 	).then(function (result) { | ||||||
|  | 		domainKeypair = result; | ||||||
|  | 
 | ||||||
|  | 		return window.crypto.subtle.exportKey( | ||||||
|  | 			"jwk" | ||||||
|  | 		, result.privateKey | ||||||
|  | 		).then(function (jwk) { | ||||||
|  | 
 | ||||||
|  | 			domainJwk = jwk; | ||||||
|  | 			console.log('private jwk:'); | ||||||
|  | 			console.log(JSON.stringify(jwk, null, 2)); | ||||||
|  | 
 | ||||||
|  |       return domainKeypair; | ||||||
|  | 		}) | ||||||
|  | 	}); | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | BACME.order.generateCsr = function (keypair, domains) { | ||||||
|  |   return Promise.resolve(CSR.generate(keypair, domains)); | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | var certificateUrl; | ||||||
|  | 
 | ||||||
|  | BACME.order.finalize = function () { | ||||||
|  | 	var payload64 = jsto64( | ||||||
|  | 		{ csr: csr } | ||||||
|  | 	); | ||||||
|  | 
 | ||||||
|  | 	var protected64 = jsto64( | ||||||
|  | 		{ nonce: nonce, alg: 'ES256', url: finalizeUrl, kid: accountId } | ||||||
|  | 	); | ||||||
|  | 
 | ||||||
|  | 	nonce = null; | ||||||
|  | 	return window.crypto.subtle.sign( | ||||||
|  | 		{ name: "ECDSA", hash: { name: "SHA-256" } } | ||||||
|  | 	, accountKeypair.privateKey | ||||||
|  | 	, textEncoder.encode(protected64 + '.' + payload64) | ||||||
|  | 	).then(function (signature) { | ||||||
|  | 
 | ||||||
|  | 		var sig64 = btoa(Array.prototype.map.call(new Uint8Array(signature), function (ch) { | ||||||
|  | 			return String.fromCharCode(ch); | ||||||
|  | 		}).join('')).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/g, ''); | ||||||
|  | 
 | ||||||
|  | 		var body = { | ||||||
|  | 			protected: protected64 | ||||||
|  | 		, payload: payload64 | ||||||
|  | 		, signature: sig64 | ||||||
|  | 		}; | ||||||
|  | 
 | ||||||
|  | 		return window.fetch( | ||||||
|  | 			finalizeUrl | ||||||
|  | 		, { mode: 'cors' | ||||||
|  | 			, method: 'POST' | ||||||
|  | 			, headers: { 'Content-Type': 'application/jose+json' } | ||||||
|  | 			, body: JSON.stringify(body) | ||||||
|  | 			} | ||||||
|  | 		).then(function (resp) { | ||||||
|  |       BACME._logHeaders(resp); | ||||||
|  | 			nonce = resp.headers.get('replay-nonce'); | ||||||
|  | 
 | ||||||
|  | 			return resp.json().then(function (reply) { | ||||||
|  | 				certificateUrl = reply.certificate; | ||||||
|  |         BACME._logBody(reply); | ||||||
|  | 			}); | ||||||
|  | 		}); | ||||||
|  | 	}); | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | }(window)); | ||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user