mirror of
				https://github.com/therootcompany/acme.js.git
				synced 2024-11-16 17:29:00 +00:00 
			
		
		
		
	not hard-coded, almost backwards compatible
This commit is contained in:
		
							parent
							
								
									afbcef688f
								
							
						
					
					
						commit
						08e6fdc1a7
					
				
							
								
								
									
										60
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										60
									
								
								README.md
									
									
									
									
									
								
							| @ -18,10 +18,64 @@ In progress | |||||||
| * Mar 20, 2018 - finalize order (submit csr) | * Mar 20, 2018 - finalize order (submit csr) | ||||||
| * Mar 20, 2018 - poll for status | * Mar 20, 2018 - poll for status | ||||||
| * Mar 20, 2018 - download certificate | * Mar 20, 2018 - download certificate | ||||||
| 
 |  | ||||||
| * Mar 20, 2018 - SUCCESS - got a test certificate (hard-coded) | * Mar 20, 2018 - SUCCESS - got a test certificate (hard-coded) | ||||||
|  | * Mar 21, 2018 - can now accept values (not hard coded) | ||||||
|  | * Mar 21, 2018 - *mostly* matches le-acme-core.js API | ||||||
| 
 | 
 | ||||||
| Todo | Todo | ||||||
| 
 | 
 | ||||||
| * match api for acme v1 (le-acme-core.js) | * completely match api for acme v1 (le-acme-core.js) | ||||||
| * make not hard-coded | * test http and dns challenges | ||||||
|  | * export http and dns challenge tests | ||||||
|  | * support ECDSA keys | ||||||
|  | 
 | ||||||
|  | ## API | ||||||
|  | 
 | ||||||
|  | ``` | ||||||
|  | var ACME = require('acme-v2.js').ACME.create({ | ||||||
|  |   RSA: require('rsa-compat').RSA | ||||||
|  | }); | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | ```javascript | ||||||
|  | // Accounts | ||||||
|  | ACME.registerNewAccount(options, cb)        // returns "regr" registration data | ||||||
|  | 
 | ||||||
|  |     { email: '<email>'                        //    valid email (server checks MX records) | ||||||
|  |     , accountKeypair: {                       //    privateKeyPem or privateKeyJwt | ||||||
|  |         privateKeyPem: '<ASCII PEM>' | ||||||
|  |       } | ||||||
|  |     , agreeToTerms: fn (tosUrl, cb) {}        //    must specify agree=tosUrl to continue (or falsey to end) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | // Registration | ||||||
|  | ACME.getCertificate(options, cb)            // returns (err, pems={ privkey (key), cert, chain (ca) }) | ||||||
|  | 
 | ||||||
|  |     { newAuthzUrl: '<url>'                    //    specify acmeUrls.newAuthz | ||||||
|  |     , newCertUrl: '<url>'                     //    specify acmeUrls.newCert | ||||||
|  | 
 | ||||||
|  |     , domainKeypair: { | ||||||
|  |         privateKeyPem: '<ASCII PEM>' | ||||||
|  |       } | ||||||
|  |     , accountKeypair: { | ||||||
|  |         privateKeyPem: '<ASCII PEM>' | ||||||
|  |       } | ||||||
|  |     , domains: [ 'example.com' ] | ||||||
|  | 
 | ||||||
|  |     , setChallenge: fn (hostname, key, val, cb) | ||||||
|  |     , removeChallenge: fn (hostname, key, cb) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | // Discovery URLs | ||||||
|  | ACME.getAcmeUrls(acmeDiscoveryUrl, cb)      // returns (err, acmeUrls={newReg,newAuthz,newCert,revokeCert}) | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | Helpers & Stuff | ||||||
|  | 
 | ||||||
|  | ```javascript | ||||||
|  | // Constants | ||||||
|  | ACME.productionServerUrl                // https://acme-v02.api.letsencrypt.org/directory | ||||||
|  | ACME.stagingServerUrl                   // https://acme-staging-v02.api.letsencrypt.org/directory | ||||||
|  | ACME.acmeChallengePrefix                // /.well-known/acme-challenge/ | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | |||||||
							
								
								
									
										562
									
								
								node.js
									
									
									
									
									
								
							
							
						
						
									
										562
									
								
								node.js
									
									
									
									
									
								
							| @ -2,8 +2,9 @@ | |||||||
|  * acme-v2.js |  * acme-v2.js | ||||||
|  * Copyright(c) 2018 AJ ONeal <aj@ppl.family> https://ppl.family
 |  * Copyright(c) 2018 AJ ONeal <aj@ppl.family> https://ppl.family
 | ||||||
|  * Apache-2.0 OR MIT (and hence also MPL 2.0) |  * Apache-2.0 OR MIT (and hence also MPL 2.0) | ||||||
| */ |  */ | ||||||
| 'use strict'; | 'use strict'; | ||||||
|  | /* globals Promise */ | ||||||
| 
 | 
 | ||||||
| var defaults = { | var defaults = { | ||||||
|   productionServerUrl:    'https://acme-v02.api.letsencrypt.org/directory' |   productionServerUrl:    'https://acme-v02.api.letsencrypt.org/directory' | ||||||
| @ -52,57 +53,13 @@ function create(deps) { | |||||||
|     }); |     }); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  |   var RSA = deps.RSA || require('rsa-compat').RSA; | ||||||
|   deps.request = deps.request || require('request'); |   deps.request = deps.request || require('request'); | ||||||
|   deps.promisify = deps.promisify || require('util').promisify; |   deps.promisify = deps.promisify || require('util').promisify; | ||||||
| 
 | 
 | ||||||
|   var directoryUrl = deps.directoryUrl || defaults.stagingServerUrl; |   var directoryUrl = deps.directoryUrl || defaults.stagingServerUrl; | ||||||
|   var request = deps.promisify(getRequest({})); |   var request = deps.promisify(getRequest({})); | ||||||
| 
 | 
 | ||||||
| 			var crypto = require('crypto'); |  | ||||||
| 			RSA.signJws = RSA.generateJws = RSA.generateSignatureJws = RSA.generateSignatureJwk = |  | ||||||
| 			function (keypair, payload, nonce) { |  | ||||||
|         var prot = {}; |  | ||||||
|         if (nonce) { |  | ||||||
|           if ('string' === typeof nonce) { |  | ||||||
|             prot.nonce = nonce; |  | ||||||
|           } else { |  | ||||||
|             prot = nonce; |  | ||||||
|           } |  | ||||||
|         } |  | ||||||
| 				keypair = RSA._internal.import(keypair); |  | ||||||
| 				keypair = RSA._internal.importForge(keypair); |  | ||||||
| 				keypair.publicKeyJwk = RSA.exportPublicJwk(keypair); |  | ||||||
| 
 |  | ||||||
| 				// Compute JWS signature
 |  | ||||||
| 				var protectedHeader = ""; |  | ||||||
| 				if (Object.keys(prot).length) { |  | ||||||
| 					protectedHeader = JSON.stringify(prot); // { alg: prot.alg, nonce: prot.nonce, url: prot.url });
 |  | ||||||
| 				} |  | ||||||
| 				var protected64 = RSA.utils.toWebsafeBase64(new Buffer(protectedHeader).toString('base64')); |  | ||||||
| 				var payload64 = RSA.utils.toWebsafeBase64(payload.toString('base64')); |  | ||||||
| 				var raw = protected64 + "." + payload64; |  | ||||||
| 				var sha256Buf = crypto.createHash('sha256').update(raw).digest(); |  | ||||||
| 				var sig64; |  | ||||||
| 
 |  | ||||||
| 				if (RSA._URSA) { |  | ||||||
| 					sig64 = RSA._ursaGenerateSig(keypair, sha256Buf); |  | ||||||
| 				} else { |  | ||||||
| 					sig64 = RSA._forgeGenerateSig(keypair, sha256Buf); |  | ||||||
| 				} |  | ||||||
| 
 |  | ||||||
| 				return { |  | ||||||
|           /* |  | ||||||
| 					header: { |  | ||||||
| 						alg: "RS256" |  | ||||||
| 					, jwk: keypair.publicKeyJwk |  | ||||||
| 					} |  | ||||||
|           */ |  | ||||||
| 				  protected: protected64 |  | ||||||
| 				, payload: payload64 |  | ||||||
| 				, signature: sig64 |  | ||||||
| 				}; |  | ||||||
| 			}; |  | ||||||
| 
 |  | ||||||
|   var acme2 = { |   var acme2 = { | ||||||
|     getAcmeUrls: function () { |     getAcmeUrls: function () { | ||||||
|       var me = this; |       var me = this; | ||||||
| @ -114,116 +71,138 @@ function create(deps) { | |||||||
|     } |     } | ||||||
|   , getNonce: function () { |   , getNonce: function () { | ||||||
|       var me = this; |       var me = this; | ||||||
|  |       if (me._nonce) { return new Promise(function (resolve) { resolve(me._nonce); return; }); } | ||||||
|       return request({ method: 'HEAD', url: me._directoryUrls.newNonce }).then(function (resp) { |       return request({ method: 'HEAD', url: me._directoryUrls.newNonce }).then(function (resp) { | ||||||
|         me._nonce = resp.toJSON().headers['replay-nonce']; |         me._nonce = resp.toJSON().headers['replay-nonce']; | ||||||
|         return me._nonce; |         return me._nonce; | ||||||
|       }); |       }); | ||||||
|     } |     } | ||||||
| 		// ACME RFC Section 7.3 Account Creation
 |     // ACME RFC Section 7.3 Account Creation
 | ||||||
| 		/* |     /* | ||||||
| 		 { |      { | ||||||
| 			 "protected": base64url({ |        "protected": base64url({ | ||||||
| 				 "alg": "ES256", |          "alg": "ES256", | ||||||
| 				 "jwk": {...}, |          "jwk": {...}, | ||||||
| 				 "nonce": "6S8IqOGY7eL2lsGoTZYifg", |          "nonce": "6S8IqOGY7eL2lsGoTZYifg", | ||||||
| 				 "url": "https://example.com/acme/new-account" |          "url": "https://example.com/acme/new-account" | ||||||
| 			 }), |        }), | ||||||
| 			 "payload": base64url({ |        "payload": base64url({ | ||||||
| 				 "termsOfServiceAgreed": true, |          "termsOfServiceAgreed": true, | ||||||
| 				 "onlyReturnExisting": false, |          "onlyReturnExisting": false, | ||||||
| 				 "contact": [ |          "contact": [ | ||||||
| 					 "mailto:cert-admin@example.com", |            "mailto:cert-admin@example.com", | ||||||
| 					 "mailto:admin@example.com" |            "mailto:admin@example.com" | ||||||
| 				 ] |          ] | ||||||
| 			 }), |        }), | ||||||
| 			 "signature": "RZPOnYoPs1PhjszF...-nh6X1qtOFPB519I" |        "signature": "RZPOnYoPs1PhjszF...-nh6X1qtOFPB519I" | ||||||
| 		 } |      } | ||||||
| 		*/ |     */ | ||||||
|   , registerNewAccount: function (options) { |   , registerNewAccount: function (options) { | ||||||
|       var me = this; |       var me = this; | ||||||
| 
 | 
 | ||||||
|       var body = { |       console.log('[acme-v2] registerNewAccount'); | ||||||
|         termsOfServiceAgreed: true |  | ||||||
|       , onlyReturnExisting: false |  | ||||||
|       , contact: [ 'mailto:' + options.email ] |  | ||||||
|       /* |  | ||||||
|        "externalAccountBinding": { |  | ||||||
|          "protected": base64url({ |  | ||||||
|            "alg": "HS256", |  | ||||||
|            "kid": /* key identifier from CA *//*, |  | ||||||
|            "url": "https://example.com/acme/new-account" |  | ||||||
|          }), |  | ||||||
|          "payload": base64url(/* same as in "jwk" above *//*), |  | ||||||
|          "signature": /* MAC using MAC key from CA *//* |  | ||||||
|        } |  | ||||||
|       */ |  | ||||||
|       }; |  | ||||||
| 			var payload = JSON.stringify(body, null, 2); |  | ||||||
| 			var jws = RSA.signJws( |  | ||||||
|         options.keypair |  | ||||||
|       , new Buffer(payload) |  | ||||||
|       , { nonce: me._nonce, alg: 'RS256', url: me._directoryUrls.newAccount, jwk: RSA.exportPublicJwk(options.keypair) } |  | ||||||
| 			); |  | ||||||
| 
 | 
 | ||||||
|       console.log('jws:'); |       return me.getNonce().then(function () { | ||||||
|       console.log(jws); |         return new Promise(function (resolve, reject) { | ||||||
|       return request({ | 
 | ||||||
|         method: 'POST' |           function agree(err, tosUrl) { | ||||||
|       , url: me._directoryUrls.newAccount |             if (err) { reject(err); return; } | ||||||
|       , headers: { 'Content-Type': 'application/jose+json' } |             if (me._tos !== tosUrl) { | ||||||
|       , json: jws |               err = new Error("You must agree to the ToS at '" + me._tos + "'"); | ||||||
|       }).then(function (resp) { |               err.code = "E_AGREE_TOS"; | ||||||
|         me._nonce = resp.toJSON().headers['replay-nonce']; |               reject(err); | ||||||
|         var location = resp.toJSON().headers['location']; |               return; | ||||||
|         console.log(location); // the account id url
 |             } | ||||||
|         console.log(resp.toJSON()); | 
 | ||||||
| 				me._kid = location; |             var jwk = RSA.exportPublicJwk(options.accountKeypair); | ||||||
|         return resp.body; |             var body = { | ||||||
|  |               termsOfServiceAgreed: tosUrl === me._tos | ||||||
|  |             , onlyReturnExisting: false | ||||||
|  |             , contact: [ 'mailto:' + options.email ] | ||||||
|  |             }; | ||||||
|  |             if (options.externalAccount) { | ||||||
|  |               body.externalAccountBinding = RSA.signJws( | ||||||
|  |                 options.externalAccount.secret | ||||||
|  |               , undefined | ||||||
|  |               , { alg: "HS256" | ||||||
|  |                 , kid: options.externalAccount.id | ||||||
|  |                 , url: me._directoryUrls.newAccount | ||||||
|  |                 } | ||||||
|  |               , new Buffer(JSON.stringify(jwk)) | ||||||
|  |               ); | ||||||
|  |             } | ||||||
|  |             var payload = JSON.stringify(body); | ||||||
|  |             var jws = RSA.signJws( | ||||||
|  |               options.accountKeypair | ||||||
|  |             , undefined | ||||||
|  |             , { nonce: me._nonce | ||||||
|  |               , alg: 'RS256' | ||||||
|  |               , url: me._directoryUrls.newAccount | ||||||
|  |               , jwk: jwk | ||||||
|  |               } | ||||||
|  |             , new Buffer(payload) | ||||||
|  |             ); | ||||||
|  | 
 | ||||||
|  |             console.log('[acme-v2] registerNewAccount JSON body:'); | ||||||
|  |             delete jws.header; | ||||||
|  |             console.log(jws); | ||||||
|  |             me._nonce = null; | ||||||
|  |             return request({ | ||||||
|  |               method: 'POST' | ||||||
|  |             , url: me._directoryUrls.newAccount | ||||||
|  |             , headers: { 'Content-Type': 'application/jose+json' } | ||||||
|  |             , json: jws | ||||||
|  |             }).then(function (resp) { | ||||||
|  |               me._nonce = resp.toJSON().headers['replay-nonce']; | ||||||
|  |               var location = resp.toJSON().headers.location; | ||||||
|  |               console.log('[DEBUG] new account location:'); // the account id url
 | ||||||
|  |               console.log(location); // the account id url
 | ||||||
|  |               console.log(resp.toJSON()); | ||||||
|  |               me._kid = location; | ||||||
|  |               return resp.body; | ||||||
|  |             }).then(resolve); | ||||||
|  |           } | ||||||
|  | 
 | ||||||
|  |           console.log('[acme-v2] agreeToTerms'); | ||||||
|  |           options.agreeToTerms(me._tos, agree); | ||||||
|  |         }); | ||||||
|       }); |       }); | ||||||
|     } |     } | ||||||
| 		/* |     /* | ||||||
| 		 POST /acme/new-order HTTP/1.1 |      POST /acme/new-order HTTP/1.1 | ||||||
| 		 Host: example.com |      Host: example.com | ||||||
| 		 Content-Type: application/jose+json |      Content-Type: application/jose+json | ||||||
| 
 | 
 | ||||||
| 		 { |      { | ||||||
| 			 "protected": base64url({ |        "protected": base64url({ | ||||||
| 				 "alg": "ES256", |          "alg": "ES256", | ||||||
| 				 "kid": "https://example.com/acme/acct/1", |          "kid": "https://example.com/acme/acct/1", | ||||||
| 				 "nonce": "5XJ1L3lEkMG7tR6pA00clA", |          "nonce": "5XJ1L3lEkMG7tR6pA00clA", | ||||||
| 				 "url": "https://example.com/acme/new-order" |          "url": "https://example.com/acme/new-order" | ||||||
| 			 }), |        }), | ||||||
| 			 "payload": base64url({ |        "payload": base64url({ | ||||||
| 				 "identifiers": [{"type:"dns","value":"example.com"}], |          "identifiers": [{"type:"dns","value":"example.com"}], | ||||||
| 				 "notBefore": "2016-01-01T00:00:00Z", |          "notBefore": "2016-01-01T00:00:00Z", | ||||||
| 				 "notAfter": "2016-01-08T00:00:00Z" |          "notAfter": "2016-01-08T00:00:00Z" | ||||||
| 			 }), |        }), | ||||||
| 			 "signature": "H6ZXtGjTZyUnPeKn...wEA4TklBdh3e454g" |        "signature": "H6ZXtGjTZyUnPeKn...wEA4TklBdh3e454g" | ||||||
| 		 } |      } | ||||||
| 		*/ |     */ | ||||||
|   , _getChallenges: function (options, auth) { |   , _getChallenges: function (options, auth) { | ||||||
|       console.log('\n[DEBUG] getChallenges\n'); |       console.log('\n[DEBUG] getChallenges\n'); | ||||||
|       return request({ method: 'GET', url: auth, json: true }).then(function (resp) { |       return request({ method: 'GET', url: auth, json: true }).then(function (resp) { | ||||||
|         console.log('Authorization:'); |         return resp.body; | ||||||
|         console.log(resp.body.challenges); |  | ||||||
|         return resp.body.challenges; |  | ||||||
|       }); |       }); | ||||||
|     } |     } | ||||||
|     // https://tools.ietf.org/html/draft-ietf-acme-acme-10#section-7.5.1
 |     // https://tools.ietf.org/html/draft-ietf-acme-acme-10#section-7.5.1
 | ||||||
|   , _postChallenge: function (options, ch) { |   , _postChallenge: function (options, identifier, ch) { | ||||||
| 			var me = this; |       var me = this; | ||||||
| 
 | 
 | ||||||
|       var body = { }; |       var body = { }; | ||||||
| 
 | 
 | ||||||
| 			var payload = JSON.stringify(body); |       var payload = JSON.stringify(body); | ||||||
| 			//var payload = JSON.stringify(body, null, 2);
 |  | ||||||
| 			var jws = RSA.signJws( |  | ||||||
|         options.keypair |  | ||||||
|       , new Buffer(payload) |  | ||||||
|       , { nonce: me._nonce, alg: 'RS256', url: ch.url, kid: me._kid } |  | ||||||
| 			); |  | ||||||
| 
 | 
 | ||||||
|       var thumbprint = RSA.thumbprint(options.keypair); |       var thumbprint = RSA.thumbprint(options.accountKeypair); | ||||||
|       var keyAuthorization = ch.token + '.' + thumbprint; |       var keyAuthorization = ch.token + '.' + thumbprint; | ||||||
|       //   keyAuthorization = token || '.' || base64url(JWK_Thumbprint(accountKey))
 |       //   keyAuthorization = token || '.' || base64url(JWK_Thumbprint(accountKey))
 | ||||||
|       //   /.well-known/acme-challenge/:token
 |       //   /.well-known/acme-challenge/:token
 | ||||||
| @ -235,67 +214,115 @@ function create(deps) { | |||||||
|       console.log(thumbprint); |       console.log(thumbprint); | ||||||
|       console.log('keyAuthorization:'); |       console.log('keyAuthorization:'); | ||||||
|       console.log(keyAuthorization); |       console.log(keyAuthorization); | ||||||
|       /* | 
 | ||||||
|       options.setChallenge(ch.token, thumbprint, keyAuthorization, function (err) { |       return new Promise(function (resolve, reject) { | ||||||
|  |         if (options.setupChallenge) { | ||||||
|  |           options.setupChallenge( | ||||||
|  |             { identifier: identifier | ||||||
|  |             , hostname: identifier.value | ||||||
|  |             , type: ch.type | ||||||
|  |             , token: ch.token | ||||||
|  |             , thumbprint: thumbprint | ||||||
|  |             , keyAuthorization: keyAuthorization | ||||||
|  |             , dnsAuthorization: RSA.utils.toWebsafeBase64( | ||||||
|  |                 require('crypto').createHash('sha256').update(keyAuthorization).digest('base64') | ||||||
|  |               ) | ||||||
|  |             } | ||||||
|  |           , testChallenge | ||||||
|  |           ); | ||||||
|  |         } else { | ||||||
|  |           options.setChallenge(identifier.value, ch.token, keyAuthorization, testChallenge); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         function testChallenge(err) { | ||||||
|  |           if (err) { reject(err); return; } | ||||||
|  | 
 | ||||||
|  |           // TODO put check dns / http checks here?
 | ||||||
|  |           // http-01: GET https://example.org/.well-known/acme-challenge/{{token}} => {{keyAuth}}
 | ||||||
|  |           // dns-01: TXT _acme-challenge.example.org. => "{{urlSafeBase64(sha256(keyAuth))}}"
 | ||||||
|  | 
 | ||||||
|  |           function wait(ms) { | ||||||
|  |             return new Promise(function (resolve) { | ||||||
|  |               setTimeout(resolve, (ms || 1100)); | ||||||
|  |             }); | ||||||
|  |           } | ||||||
|  | 
 | ||||||
|  |           function pollStatus() { | ||||||
|  |             console.log('\n[DEBUG] statusChallenge\n'); | ||||||
|  |             return request({ method: 'GET', url: ch.url, json: true }).then(function (resp) { | ||||||
|  |               console.error('poll: resp.body:'); | ||||||
|  |               console.error(resp.body); | ||||||
|  | 
 | ||||||
|  |               if ('pending' === resp.body.status) { | ||||||
|  |                 console.log('poll: again'); | ||||||
|  |                 return wait().then(pollStatus); | ||||||
|  |               } | ||||||
|  | 
 | ||||||
|  |               if ('valid' === resp.body.status) { | ||||||
|  |                 console.log('poll: valid'); | ||||||
|  |                 try { | ||||||
|  |                   if (options.teardownChallenge) { | ||||||
|  |                     options.teardownChallenge( | ||||||
|  |                       { identifier: identifier | ||||||
|  |                       , type: ch.type | ||||||
|  |                       , token: ch.token | ||||||
|  |                       } | ||||||
|  |                     , function () {} | ||||||
|  |                     ); | ||||||
|  |                   } else { | ||||||
|  |                     options.removeChallenge(identifier.value, ch.token, function () {}); | ||||||
|  |                   } | ||||||
|  |                 } catch(e) {} | ||||||
|  |                 return resp.body; | ||||||
|  |               } | ||||||
|  | 
 | ||||||
|  |               if (!resp.body.status) { | ||||||
|  |                 console.error("[acme-v2] (y) bad challenge state:"); | ||||||
|  |               } | ||||||
|  |               else if ('invalid' === resp.body.status) { | ||||||
|  |                 console.error("[acme-v2] (x) invalid challenge state:"); | ||||||
|  |               } | ||||||
|  |               else { | ||||||
|  |                 console.error("[acme-v2] (z) bad challenge state:"); | ||||||
|  |               } | ||||||
|  | 
 | ||||||
|  |               return Promise.reject(new Error("[acme-v2] bad challenge state")); | ||||||
|  |             }); | ||||||
|  |           } | ||||||
|  | 
 | ||||||
|  |           console.log('\n[DEBUG] postChallenge\n'); | ||||||
|  |           //console.log('\n[DEBUG] stop to fix things\n'); return;
 | ||||||
|  | 
 | ||||||
|  |           function post() { | ||||||
|  |             var jws = RSA.signJws( | ||||||
|  |               options.accountKeypair | ||||||
|  |             , undefined | ||||||
|  |             , { nonce: me._nonce, alg: 'RS256', url: ch.url, kid: me._kid } | ||||||
|  |             , new Buffer(payload) | ||||||
|  |             ); | ||||||
|  |             me._nonce = null; | ||||||
|  |             return request({ | ||||||
|  |               method: 'POST' | ||||||
|  |             , url: ch.url | ||||||
|  |             , headers: { 'Content-Type': 'application/jose+json' } | ||||||
|  |             , json: jws | ||||||
|  |             }).then(function (resp) { | ||||||
|  |               me._nonce = resp.toJSON().headers['replay-nonce']; | ||||||
|  |               console.log('respond to challenge: resp.body:'); | ||||||
|  |               console.log(resp.body); | ||||||
|  |               return wait().then(pollStatus).then(resolve, reject); | ||||||
|  |             }); | ||||||
|  |           } | ||||||
|  | 
 | ||||||
|  |           return wait(20 * 1000).then(post); | ||||||
|  |         } | ||||||
|       }); |       }); | ||||||
|       */ |  | ||||||
|       function wait(ms) { |  | ||||||
|         return new Promise(function (resolve) { |  | ||||||
|           setTimeout(resolve, (ms || 1100)); |  | ||||||
|         }); |  | ||||||
|       } |  | ||||||
|       function pollStatus() { |  | ||||||
|         console.log('\n[DEBUG] statusChallenge\n'); |  | ||||||
|         return request({ method: 'GET', url: ch.url, json: true }).then(function (resp) { |  | ||||||
|           console.error('poll: resp.body:'); |  | ||||||
|           console.error(resp.body); |  | ||||||
| 
 |  | ||||||
|           if ('pending' === resp.body.status) { |  | ||||||
|             console.log('poll: again'); |  | ||||||
|             return wait().then(pollStatus); |  | ||||||
|           } |  | ||||||
| 
 |  | ||||||
|           if ('valid' === resp.body.status) { |  | ||||||
|             console.log('poll: valid'); |  | ||||||
|             return resp.body; |  | ||||||
|           } |  | ||||||
| 
 |  | ||||||
|           if (!resp.body.status) { |  | ||||||
|             console.error("[acme-v2] (y) bad challenge state:"); |  | ||||||
|           } |  | ||||||
|           else if ('invalid' === resp.body.status) { |  | ||||||
|             console.error("[acme-v2] (x) invalid challenge state:"); |  | ||||||
|           } |  | ||||||
|           else { |  | ||||||
|             console.error("[acme-v2] (z) bad challenge state:"); |  | ||||||
|           } |  | ||||||
|         }); |  | ||||||
|       } |  | ||||||
| 
 |  | ||||||
|       console.log('\n[DEBUG] postChallenge\n'); |  | ||||||
|       //console.log('\n[DEBUG] stop to fix things\n'); return;
 |  | ||||||
| 
 |  | ||||||
|       function post() { |  | ||||||
|         return request({ |  | ||||||
|           method: 'POST' |  | ||||||
|         , url: ch.url |  | ||||||
|         , headers: { 'Content-Type': 'application/jose+json' } |  | ||||||
|         , json: jws |  | ||||||
|         }).then(function (resp) { |  | ||||||
|           me._nonce = resp.toJSON().headers['replay-nonce']; |  | ||||||
|           console.log('respond to challenge: resp.body:'); |  | ||||||
|           console.log(resp.body); |  | ||||||
|           return wait().then(pollStatus); |  | ||||||
|         }); |  | ||||||
|       } |  | ||||||
| 
 |  | ||||||
|       return wait(20 * 1000).then(post); |  | ||||||
|     } |     } | ||||||
|   , _finalizeOrder: function (options, validatedDomains) { |   , _finalizeOrder: function (options, validatedDomains) { | ||||||
|       console.log('finalizeOrder:'); |       console.log('finalizeOrder:'); | ||||||
| 			var me = this; |       var me = this; | ||||||
| 
 | 
 | ||||||
|       var csr = RSA.generateCsrWeb64(options.certificateKeypair, validatedDomains); |       var csr = RSA.generateCsrWeb64(options.domainKeypair, validatedDomains); | ||||||
|       var body = { csr: csr }; |       var body = { csr: csr }; | ||||||
|       var payload = JSON.stringify(body); |       var payload = JSON.stringify(body); | ||||||
| 
 | 
 | ||||||
| @ -306,14 +333,15 @@ function create(deps) { | |||||||
|       } |       } | ||||||
| 
 | 
 | ||||||
|       function pollCert() { |       function pollCert() { | ||||||
|         //var payload = JSON.stringify(body, null, 2);
 |  | ||||||
|         var jws = RSA.signJws( |         var jws = RSA.signJws( | ||||||
|           options.keypair |           options.accountKeypair | ||||||
|         , new Buffer(payload) |         , undefined | ||||||
|         , { nonce: me._nonce, alg: 'RS256', url: me._finalize, kid: me._kid } |         , { nonce: me._nonce, alg: 'RS256', url: me._finalize, kid: me._kid } | ||||||
|  |         , new Buffer(payload) | ||||||
|         ); |         ); | ||||||
| 
 | 
 | ||||||
|         console.log('finalize:', me._finalize); |         console.log('finalize:', me._finalize); | ||||||
|  |         me._nonce = null; | ||||||
|         return request({ |         return request({ | ||||||
|           method: 'POST' |           method: 'POST' | ||||||
|         , url: me._finalize |         , url: me._finalize | ||||||
| @ -348,7 +376,7 @@ function create(deps) { | |||||||
| 
 | 
 | ||||||
|       return pollCert(); |       return pollCert(); | ||||||
|     } |     } | ||||||
|   , _getCertificate: function (auth) { |   , _getCertificate: function () { | ||||||
|       var me = this; |       var me = this; | ||||||
|       return request({ method: 'GET', url: me._certificate, json: true }).then(function (resp) { |       return request({ method: 'GET', url: me._certificate, json: true }).then(function (resp) { | ||||||
|         console.log('Certificate:'); |         console.log('Certificate:'); | ||||||
| @ -357,91 +385,93 @@ function create(deps) { | |||||||
|       }); |       }); | ||||||
|     } |     } | ||||||
|   , getCertificate: function (options, cb) { |   , getCertificate: function (options, cb) { | ||||||
| 			var me = this; |       console.log('[acme-v2] DEBUG get cert 1'); | ||||||
|  |       var me = this; | ||||||
| 
 | 
 | ||||||
|       var body = { |       if (!options.challengeTypes) { | ||||||
| 				identifiers: [ |         if (!options.challengeType) { | ||||||
|           { type: "dns" , value: "test.ppl.family" } |           cb(new Error("challenge type must be specified")); | ||||||
| 				/* |           return Promise.reject(new Error("challenge type must be specified")); | ||||||
|         , {	type: "dns" , value: "example.net" } |         } | ||||||
| 				*/ |         options.challengeTypes = [ options.challengeType ]; | ||||||
|         ] |       } | ||||||
|         //, "notBefore": "2016-01-01T00:00:00Z"
 |  | ||||||
|        //, "notAfter": "2016-01-08T00:00:00Z"
 |  | ||||||
|       }; |  | ||||||
| 
 | 
 | ||||||
| 			var payload = JSON.stringify(body); |       console.log('[acme-v2] getCertificate'); | ||||||
| 			//var payload = JSON.stringify(body, null, 2);
 |       return me.getNonce().then(function () { | ||||||
| 			var jws = RSA.signJws( |         var body = { | ||||||
|         options.keypair |           identifiers: options.domains.map(function (hostname) { | ||||||
|       , new Buffer(payload) |             return { type: "dns" , value: hostname }; | ||||||
|       , { nonce: me._nonce, alg: 'RS256', url: me._directoryUrls.newOrder, kid: me._kid } |           }) | ||||||
| 			); |           //, "notBefore": "2016-01-01T00:00:00Z"
 | ||||||
|  |           //, "notAfter": "2016-01-08T00:00:00Z"
 | ||||||
|  |         }; | ||||||
| 
 | 
 | ||||||
|       console.log('\n[DEBUG] newOrder\n'); |         var payload = JSON.stringify(body); | ||||||
|       return request({ |         var jws = RSA.signJws( | ||||||
|         method: 'POST' |           options.accountKeypair | ||||||
|       , url: me._directoryUrls.newOrder |         , undefined | ||||||
|       , headers: { 'Content-Type': 'application/jose+json' } |         , { nonce: me._nonce, alg: 'RS256', url: me._directoryUrls.newOrder, kid: me._kid } | ||||||
|       , json: jws |         , new Buffer(payload) | ||||||
|       }).then(function (resp) { |         ); | ||||||
|         me._nonce = resp.toJSON().headers['replay-nonce']; |  | ||||||
|         var location = resp.toJSON().headers['location']; |  | ||||||
|         console.log(location); // the account id url
 |  | ||||||
|         console.log(resp.toJSON()); |  | ||||||
|         //var body = JSON.parse(resp.body);
 |  | ||||||
|         me._authorizations = resp.body.authorizations; |  | ||||||
|         me._order = location; |  | ||||||
|         me._finalize = resp.body.finalize; |  | ||||||
|         //console.log('[DEBUG] finalize:', me._finalize); return;
 |  | ||||||
| 
 | 
 | ||||||
|         //return resp.body;
 |         console.log('\n[DEBUG] newOrder\n'); | ||||||
|         return Promise.all(me._authorizations.map(function (auth) { |         me._nonce = null; | ||||||
|           console.log('authz', auth); |         return request({ | ||||||
|           return me._getChallenges(options, auth).then(function (challenges) { |           method: 'POST' | ||||||
|             var chp; |         , url: me._directoryUrls.newOrder | ||||||
|  |         , headers: { 'Content-Type': 'application/jose+json' } | ||||||
|  |         , json: jws | ||||||
|  |         }).then(function (resp) { | ||||||
|  |           me._nonce = resp.toJSON().headers['replay-nonce']; | ||||||
|  |           var location = resp.toJSON().headers.location; | ||||||
|  |           console.log(location); // the account id url
 | ||||||
|  |           console.log(resp.toJSON()); | ||||||
|  |           //var body = JSON.parse(resp.body);
 | ||||||
|  |           me._authorizations = resp.body.authorizations; | ||||||
|  |           me._order = location; | ||||||
|  |           me._finalize = resp.body.finalize; | ||||||
|  |           //console.log('[DEBUG] finalize:', me._finalize); return;
 | ||||||
| 
 | 
 | ||||||
|             challenges.forEach(function (ch) { |           //return resp.body;
 | ||||||
|               if ('http-01' !== ch.type) { |           return Promise.all(me._authorizations.map(function (authUrl) { | ||||||
|                 return; |             return me._getChallenges(options, authUrl).then(function (results) { | ||||||
|  |               // var domain = options.domains[i]; // results.identifier.value
 | ||||||
|  |               var chType = options.challengeTypes.filter(function (chType) { | ||||||
|  |                 return results.challenges.some(function (ch) { | ||||||
|  |                   return ch.type === chType; | ||||||
|  |                 }); | ||||||
|  |               })[0]; | ||||||
|  |               var challenge = results.challenges.filter(function (ch) { | ||||||
|  |                 if (chType === ch.type) { | ||||||
|  |                   return ch; | ||||||
|  |                 } | ||||||
|  |               })[0]; | ||||||
|  | 
 | ||||||
|  |               if (!challenge) { | ||||||
|  |                 return Promise.reject(new Error("Server didn't offer any challenge we can handle.")); | ||||||
|               } |               } | ||||||
|               chp = me._postChallenge(options, ch); | 
 | ||||||
|  |               return me._postChallenge(options, results.identifier, challenge); | ||||||
|  |             }); | ||||||
|  |           })).then(function () { | ||||||
|  |             var validatedDomains = body.identifiers.map(function (ident) { | ||||||
|  |               return ident.value; | ||||||
|             }); |             }); | ||||||
| 
 | 
 | ||||||
|             return chp; |             return me._finalizeOrder(options, validatedDomains); | ||||||
|  |           }).then(function () { | ||||||
|  |             return me._getCertificate().then(function (result) { cb(null, result); return result; }, cb); | ||||||
|           }); |           }); | ||||||
|         })).then(function () { |  | ||||||
|           var validatedDomains = body.identifiers.map(function (ident) { |  | ||||||
|             return ident.value; |  | ||||||
|           }); |  | ||||||
| 
 |  | ||||||
|           return me._finalizeOrder(options, validatedDomains); |  | ||||||
|         }).then(function () { |  | ||||||
|           return me._getCertificate(); |  | ||||||
|         }); |         }); | ||||||
|       }); |       }); | ||||||
| 		} |     } | ||||||
|   }; |   }; | ||||||
|   return acme2; |   return acme2; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| var RSA = require('rsa-compat').RSA; | module.exports.ACME = { | ||||||
| var acme2 = create(); |   create: create | ||||||
| acme2.getAcmeUrls().then(function (body) { | }; | ||||||
|   console.log(body); | Object.keys(defaults).forEach(function (key) { | ||||||
|   acme2.getNonce().then(function (nonce) { |   module.exports.ACME[key] = defaults[key]; | ||||||
|     console.log(nonce); |  | ||||||
| 
 |  | ||||||
| 		var options = { |  | ||||||
| 			email: 'coolaj86@gmail.com' |  | ||||||
| 		, keypair: RSA.import({ privateKeyPem: require('fs').readFileSync(__dirname + '/account.privkey.pem') }) |  | ||||||
| 		, certificateKeypair: RSA.import({ privateKeyPem: require('fs').readFileSync(__dirname + '/privkey.pem') }) |  | ||||||
| 		}; |  | ||||||
|     acme2.registerNewAccount(options).then(function (account) { |  | ||||||
|       console.log(account); |  | ||||||
|     	acme2.getCertificate(options, function () { |  | ||||||
| 				console.log('got cert'); |  | ||||||
|       }); |  | ||||||
|     }); |  | ||||||
|   }); |  | ||||||
| }); | }); | ||||||
|  | |||||||
| @ -1,6 +1,6 @@ | |||||||
| { | { | ||||||
|   "name": "acme-v2", |   "name": "acme-v2", | ||||||
|   "version": "0.0.2", |   "version": "0.6.0", | ||||||
|   "description": "A framework for building letsencrypt clients (and other ACME v2 clients), forked from le-acme-core.js.", |   "description": "A framework for building letsencrypt clients (and other ACME v2 clients), forked from le-acme-core.js.", | ||||||
|   "main": "node.js", |   "main": "node.js", | ||||||
|   "scripts": { |   "scripts": { | ||||||
| @ -23,6 +23,6 @@ | |||||||
|   "license": "(MIT OR Apache-2.0)", |   "license": "(MIT OR Apache-2.0)", | ||||||
|   "dependencies": { |   "dependencies": { | ||||||
|     "request": "^2.85.0", |     "request": "^2.85.0", | ||||||
|     "rsa-compat": "^1.2.7" |     "rsa-compat": "^1.3.0" | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user