mirror of
				https://github.com/therootcompany/acme.js.git
				synced 2024-11-16 17:29:00 +00:00 
			
		
		
		
	ready-ish to release Bluecrypt ACME
This commit is contained in:
		
							parent
							
								
									14c24e3aea
								
							
						
					
					
						commit
						b324afe6d6
					
				
							
								
								
									
										2
									
								
								LICENSE
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								LICENSE
									
									
									
									
									
								
							| @ -1,4 +1,4 @@ | ||||
| Copyright 2017-present AJ ONeal | ||||
| Copyright 2015-2019 AJ ONeal | ||||
| 
 | ||||
| Mozilla Public License Version 2.0 | ||||
| ================================== | ||||
|  | ||||
							
								
								
									
										195
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										195
									
								
								README.md
									
									
									
									
									
								
							| @ -1,9 +1,190 @@ | ||||
| # Bluecrypt™ Keypairs | ||||
| # Bluecrypt™ [ACME.js](https://git.rootprojects.org/root/bluecrypt-acme.js) | A [Root](https://rootprojects.org/acme/) project | ||||
| 
 | ||||
| A port of [keypairs.js](https://git.coolaj86.com/coolaj86/keypairs.js) to the browser. | ||||
| Free SSL Certificates from Let's Encrypt, right in your Web Browser | ||||
| 
 | ||||
| * Keypairs | ||||
|   * Eckles (ECDSA) | ||||
|   * Rasha (RSA) | ||||
|   * X509 | ||||
|   * ASN1 | ||||
| Lightweight. Fast. Modern Crypto. Zero dependecies. | ||||
| 
 | ||||
| (a port of [acme.js](https://git.coolaj86.com/coolaj86/acme-v2.js) to the browser) | ||||
| 
 | ||||
| # Features | ||||
| 
 | ||||
| | 15k gzipped | 55k minified | 88k (2,500 loc) source with comments | | ||||
| 
 | ||||
| * [x] Let's Encrypt | ||||
|   * [x] ACME draft 15 (supports POST-as-GET) | ||||
|   * [x] Secure support for EC and RSA for account and server keys | ||||
|   * [x] Simple and lightweight PEM, DER, ASN1, X509, and CSR implementations | ||||
| * [x] VanillaJS, Zero Dependencies | ||||
| 
 | ||||
| # Online Demos | ||||
| 
 | ||||
| * Greenlock for the Web <https://greenlock.domains> | ||||
| * Bluecrypt ACME Demo <https://rootprojects.org/acme/> | ||||
| 
 | ||||
| We expect that our hosted versions will meet all of yours needs. | ||||
| If they don'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 made the source visible for others to inspect, improve, and modify. | ||||
| 
 | ||||
| # QuickStart | ||||
| 
 | ||||
| Bluecrypt ACME embeds [Keypairs.js](https://git.rootprojects.org/root/bluecrypt-keypairs.js) | ||||
| and [CSR.js](https://git.rootprojects.org/root/bluecrypt-csr.js) | ||||
| 
 | ||||
| `bluecrypt-acme.js` | ||||
| ```html | ||||
| <script src="https://rootprojects.org/acme/bluecrypt-acme.js"></script> | ||||
| ``` | ||||
| 
 | ||||
| `bluecrypt-acme.min.js` | ||||
| ```html | ||||
| <script src="https://rootprojects.org/acme/bluecrypt-acme.min.js"></script> | ||||
| ``` | ||||
| 
 | ||||
| You can see `index.html` and `app.js` in the repo for full example usage. | ||||
| 
 | ||||
| ### Instantiate Bluecrypt ACME | ||||
| 
 | ||||
| Although built for Let's Encrypt, Bluecrypt ACME will work with any server | ||||
| that supports draft-15 of the ACME spec (includes POST-as-GET support). | ||||
| 
 | ||||
| The `init()` method takes a _directory url_ and initializes internal state according to its response. | ||||
| 
 | ||||
| ```js | ||||
| var acme = ACME.create({}); | ||||
| acme.init('https://acme-staging-v02.api.letsencrypt.org/directory').then(function () { | ||||
|   // Ready to use, show page | ||||
|   $('body').hidden = false; | ||||
| }); | ||||
| ``` | ||||
| 
 | ||||
| ### Create ACME Account with Let's Encrypt | ||||
| 
 | ||||
| ACME Accounts are key and device based, with an email address as a backup identifier. | ||||
| 
 | ||||
| A public account key must be registered before an SSL certificate can be requested. | ||||
| 
 | ||||
| ```js | ||||
| var accountPrivateKey; | ||||
| var account; | ||||
| 
 | ||||
| Keypairs.generate({ kty: 'EC' }).then(function (pair) { | ||||
|   accountPrivateKey = pair.private; | ||||
| 
 | ||||
|   return acme.accounts.create({ | ||||
|     agreeToTerms: function (tos) { | ||||
|       if (window.confirm("Do you agree to the Bluecrypt and Let's Encrypt Terms of Service?")) { | ||||
|         return Promise.resolve(tos); | ||||
|       } | ||||
|     } | ||||
|   , accountKeypair: { privateKeyJwk: pair.private } | ||||
|   , email: $('.js-email-input').value | ||||
|   }).then(function (_account) { | ||||
|     account = _account; | ||||
|   }); | ||||
| }); | ||||
| ``` | ||||
| 
 | ||||
| ### Get Free 90-day SSL Certificate | ||||
| 
 | ||||
| Creating an ACME "order" for a 90-day SSL certificate requires use of the account private key, | ||||
| the names of domains to be secured, and a distinctly separate server private key. | ||||
| 
 | ||||
| A domain ownership verification "challenge" (uploading a file to an unsecured HTTP url or setting a DNS record) | ||||
| is a required part of the process, which requires `set` and `remove` callbacks/promises. | ||||
| 
 | ||||
| ```js | ||||
| var serverPrivateKey; | ||||
| 
 | ||||
| Keypairs.generate({ kty: 'EC' }).then(function (pair) { | ||||
|   serverPrivateKey = pair.private; | ||||
| 
 | ||||
|   return acme.certificates.create({ | ||||
|     agreeToTerms: function (tos) { | ||||
|       return tos; | ||||
|     } | ||||
|   , account: account | ||||
|   , accountKeypair: { privateKeyJwk: accountPrivateKey } | ||||
|   , serverKeypair: { privateKeyJwk: serverPrivateKey } | ||||
|   , domains: ['example.com','www.example.com'] | ||||
|   , challenges: challenges // must be implemented | ||||
|   , skipDryRun: true | ||||
|   }).then(function (results) { | ||||
|     console.log('Got SSL Certificate:'); | ||||
|     console.log(results.expires); | ||||
|     console.log(results.cert); | ||||
|     console.log(results.chain); | ||||
|   }); | ||||
| 
 | ||||
| }); | ||||
| ``` | ||||
| 
 | ||||
| ### Example "Challenge" Implementation | ||||
| 
 | ||||
| Typically here you're just presenting some sort of dialog to the user to ask them to | ||||
| upload a file or set a DNS record. | ||||
| 
 | ||||
| It may be possible to do something fancy like using OAuth2 to login to Google Domanis | ||||
| to set a DNS address, etc, but it seems like that sort of fanciness is probably best | ||||
| reserved for server-side plugins. | ||||
| 
 | ||||
| ```js | ||||
| var challenges = { | ||||
|   'http-01': { | ||||
|     set: function (opts) { | ||||
|       console.info('http-01 set challenge:'); | ||||
|       console.info(opts.challengeUrl); | ||||
|       console.info(opts.keyAuthorization); | ||||
|       while (!window.confirm("Upload the challenge file before continuing.")) {} | ||||
|       return Promise.resolve(); | ||||
|     } | ||||
|   , remove: function (opts) { | ||||
|       console.log('http-01 remove challenge:', opts.challengeUrl); | ||||
|       return Promise.resolve(); | ||||
|     } | ||||
|   } | ||||
| }; | ||||
| ``` | ||||
| 
 | ||||
| # Full Documentation | ||||
| 
 | ||||
| See [acme.js](https://git.coolaj86.com/coolaj86/acme-v2.js). | ||||
| 
 | ||||
| Aside from the loading instructions (`npm` and `require` instead of `script` tags), | ||||
| the usage is identical to the node version. | ||||
| 
 | ||||
| That said, the two may leap-frog a little from time to time | ||||
| (for example, the browser version is just a touch ahead at the moment). | ||||
| 
 | ||||
| # Developing | ||||
| 
 | ||||
| You can see `<script>` tags in the `index.html` in the repo, which references the original | ||||
| source files. | ||||
| 
 | ||||
| Join `@rootprojects` `#general` on [Keybase](https://keybase.io) if you'd like to chat with us. | ||||
| 
 | ||||
| # Commercial Support | ||||
| 
 | ||||
| We have both commercial support and commercial licensing available. | ||||
| 
 | ||||
| You're welcome to [contact us](mailto:aj@therootcompany.com) in regards to IoT, On-Prem, | ||||
| Enterprise, and Internal installations, integrations, and deployments. | ||||
| 
 | ||||
| We also offer consulting for all-things-ACME and Let's Encrypt. | ||||
| 
 | ||||
| # Legal & Rules of the Road | ||||
| 
 | ||||
| Bluecrypt™ and Greenlock™ are [trademarks](https://rootprojects.org/legal/#trademark) of AJ ONeal | ||||
| 
 | ||||
| The rule of thumb is "attribute, but don't confuse". For example: | ||||
| 
 | ||||
| > Built with [Root](https://rootprojects.org)'s [Bluecrypt ACME](https://git.rootprojects.org/root/bluecrypt-acme.js). | ||||
| 
 | ||||
| Please [contact us](mailto:aj@therootcompany.com) if have any questions in regards to our trademark, | ||||
| attribution, and/or visible source policies. We want to help to community as we build great software. | ||||
| 
 | ||||
| [bluecrypt.js](https://git.coolaj86.com/coolaj86/bluecrypt.js) | | ||||
| MPL-2.0 | | ||||
| [Terms of Use](https://therootcompany.com/legal/#terms) | | ||||
| [Privacy Policy](https://therootcompany.com/legal/#privacy) | ||||
|  | ||||
							
								
								
									
										156
									
								
								app.js
									
									
									
									
									
								
							
							
						
						
									
										156
									
								
								app.js
									
									
									
									
									
								
							| @ -17,6 +17,14 @@ | ||||
|     return Array.prototype.slice.call(document.querySelectorAll(sel)); | ||||
|   } | ||||
| 
 | ||||
|   function checkTos(tos) { | ||||
|     if ($('input[name="tos"]:checked')) { | ||||
|       return tos; | ||||
|     } else { | ||||
|       return ''; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   function run() { | ||||
|     console.log('hello'); | ||||
| 
 | ||||
| @ -111,8 +119,156 @@ | ||||
|       }); | ||||
|     }); | ||||
| 
 | ||||
|     $('form.js-acme-account').addEventListener('submit', function (ev) { | ||||
|       ev.preventDefault(); | ||||
|       ev.stopPropagation(); | ||||
|       $('.js-loading').hidden = false; | ||||
|       var acme = ACME.create({ | ||||
|         Keypairs: Keypairs | ||||
|       , CSR: CSR | ||||
|       }); | ||||
|       acme.init('https://acme-staging-v02.api.letsencrypt.org/directory').then(function (result) { | ||||
|         console.log('acme result', result); | ||||
|         var privJwk = JSON.parse($('.js-jwk').innerText).private; | ||||
|         var email = $('.js-email').value; | ||||
|         return acme.accounts.create({ | ||||
|           email: email | ||||
|         , agreeToTerms: checkTos | ||||
|         , accountKeypair: { privateKeyJwk: privJwk } | ||||
|         }).then(function (account) { | ||||
|           console.log("account created result:", account); | ||||
|           accountStuff.account = account; | ||||
|           accountStuff.privateJwk = privJwk; | ||||
|           accountStuff.email = email; | ||||
|           accountStuff.acme = acme; | ||||
|           $('.js-create-order').hidden = false; | ||||
|           $('.js-toc-acme-account-response').hidden = false; | ||||
|           $('.js-acme-account-response').innerText = JSON.stringify(account, null, 2); | ||||
|         }).catch(function (err) { | ||||
|           console.error("A bad thing happened:"); | ||||
|           console.error(err); | ||||
|           window.alert(err.message || JSON.stringify(err, null, 2)); | ||||
|         }); | ||||
|       }); | ||||
|     }); | ||||
| 
 | ||||
|     $('form.js-csr').addEventListener('submit', function (ev) { | ||||
|       ev.preventDefault(); | ||||
|       ev.stopPropagation(); | ||||
|       generateCsr(); | ||||
|     }); | ||||
| 
 | ||||
|     $('form.js-acme-order').addEventListener('submit', function (ev) { | ||||
|       ev.preventDefault(); | ||||
|       ev.stopPropagation(); | ||||
|       var account = accountStuff.account; | ||||
|       var privJwk = accountStuff.privateJwk; | ||||
|       var email = accountStuff.email; | ||||
|       var acme = accountStuff.acme; | ||||
| 
 | ||||
| 
 | ||||
|       var domains = ($('.js-domains').value||'example.com').split(/[, ]+/g); | ||||
|       return getDomainPrivkey().then(function (domainPrivJwk) { | ||||
|         console.log('Has CSR already?'); | ||||
|         console.log(accountStuff.csr); | ||||
|         return acme.certificates.create({ | ||||
|           accountKeypair: { privateKeyJwk: privJwk } | ||||
|         , account: account | ||||
|         , serverKeypair: { privateKeyJwk: domainPrivJwk } | ||||
|         , csr: accountStuff.csr | ||||
|         , domains: domains | ||||
|         , skipDryRun: $('input[name="skip-dryrun"]:checked') && true | ||||
|         , agreeToTerms: checkTos | ||||
|         , challenges: { | ||||
|             'dns-01': { | ||||
|               set: function (opts) { | ||||
|                 console.info('dns-01 set challenge:'); | ||||
|                 console.info('TXT', opts.dnsHost); | ||||
|                 console.info(opts.dnsAuthorization); | ||||
|                 return new Promise(function (resolve) { | ||||
|                   while (!window.confirm("Did you set the challenge?")) {} | ||||
|                   resolve(); | ||||
|                 }); | ||||
|               } | ||||
|             , remove: function (opts) { | ||||
|                 console.log('dns-01 remove challenge:'); | ||||
|                 console.info('TXT', opts.dnsHost); | ||||
|                 console.info(opts.dnsAuthorization); | ||||
|                 return new Promise(function (resolve) { | ||||
|                   while (!window.confirm("Did you delete the challenge?")) {} | ||||
|                   resolve(); | ||||
|                 }); | ||||
|               } | ||||
|             } | ||||
|           , 'http-01': { | ||||
|               set: function (opts) { | ||||
|                 console.info('http-01 set challenge:'); | ||||
|                 console.info(opts.challengeUrl); | ||||
|                 console.info(opts.keyAuthorization); | ||||
|                 return new Promise(function (resolve) { | ||||
|                   while (!window.confirm("Did you set the challenge?")) {} | ||||
|                   resolve(); | ||||
|                 }); | ||||
|               } | ||||
|             , remove: function (opts) { | ||||
|                 console.log('http-01 remove challenge:'); | ||||
|                 console.info(opts.challengeUrl); | ||||
|                 console.info(opts.keyAuthorization); | ||||
|                 return new Promise(function (resolve) { | ||||
|                   while (!window.confirm("Did you delete the challenge?")) {} | ||||
|                   resolve(); | ||||
|                 }); | ||||
|               } | ||||
|             } | ||||
|           } | ||||
|         , challengeTypes: [$('input[name="acme-challenge-type"]:checked').value] | ||||
|         }).then(function (results) { | ||||
|           console.log('Got Certificates:'); | ||||
|           console.log(results); | ||||
|           $('.js-toc-acme-order-response').hidden = false; | ||||
|           $('.js-acme-order-response').innerText = JSON.stringify(results, null, 2); | ||||
|         }).catch(function (err) { | ||||
|           console.error("challenge failed:"); | ||||
|           console.error(err); | ||||
|           window.alert("failed! " + err.message || JSON.stringify(err)); | ||||
|         }); | ||||
|       }); | ||||
|     }); | ||||
| 
 | ||||
|     $('.js-generate').hidden = false; | ||||
|   } | ||||
| 
 | ||||
|   function getDomainPrivkey() { | ||||
|     if (accountStuff.domainPrivateJwk) { return Promise.resolve(accountStuff.domainPrivateJwk); } | ||||
|     return Keypairs.generate({ | ||||
|       kty: $('input[name="kty"]:checked').value | ||||
|     , namedCurve: $('input[name="ec-crv"]:checked').value | ||||
|     , modulusLength: $('input[name="rsa-len"]:checked').value | ||||
|     }).then(function (pair) { | ||||
|       console.log('domain keypair:', pair); | ||||
|       accountStuff.domainPrivateJwk = pair.private; | ||||
|       return pair.private; | ||||
|     }); | ||||
|   } | ||||
| 
 | ||||
|   function generateCsr() { | ||||
|     var domains = ($('.js-domains').value||'example.com').split(/[, ]+/g); | ||||
|     //var privJwk = JSON.parse($('.js-jwk').innerText).private;
 | ||||
|     return getDomainPrivkey().then(function (privJwk) { | ||||
|       accountStuff.domainPrivateJwk = privJwk; | ||||
|       return CSR({ jwk: privJwk, domains: domains }).then(function (pem) { | ||||
|         // Verify with https://www.sslshopper.com/csr-decoder.html
 | ||||
|         accountStuff.csr = pem; | ||||
|         console.log('Created CSR:'); | ||||
|         console.log(pem); | ||||
| 
 | ||||
|         console.log('CSR info:'); | ||||
|         console.log(CSR._info(pem)); | ||||
| 
 | ||||
|         return pem; | ||||
|       }); | ||||
|     }); | ||||
|   } | ||||
| 
 | ||||
|   window.addEventListener('load', run); | ||||
| }()); | ||||
|  | ||||
							
								
								
									
										45
									
								
								index.html
									
									
									
									
									
								
							
							
						
						
									
										45
									
								
								index.html
									
									
									
									
									
								
							| @ -53,6 +53,39 @@ | ||||
|       <button class="js-generate" hidden>Generate</button> | ||||
|     </form> | ||||
| 
 | ||||
|     <h2>ACME Account</h2> | ||||
|     <form class="js-acme-account"> | ||||
|       <label for="-acmeEmail">Email:</label> | ||||
|       <input class="js-email" type="email" id="-acmeEmail" value="john.doe@gmail.com"> | ||||
|       <br> | ||||
|       <label for="-acmeTos"><input class="js-tos" name="tos" type="checkbox" id="-acmeTos" checked> | ||||
|         Agree to Let's Encrypt Terms of Service</label> | ||||
|       <br> | ||||
|       <button class="js-create-account" hidden>Create Account</button> | ||||
|     </form> | ||||
| 
 | ||||
|     <h2>Certificate Signing Request</h2> | ||||
|     <form class="js-csr"> | ||||
|       <label for="-acmeDomains">Domains:</label> | ||||
|       <input class="js-domains" type="text" id="-acmeDomains" value="example.com www.example.com"> | ||||
|       <br> | ||||
|       <button class="js-create-csr" hidden>Create CSR</button> | ||||
|     </form> | ||||
| 
 | ||||
|     <h2>ACME Certificate Order</h2> | ||||
|     <form class="js-acme-order"> | ||||
|       Challenge type: | ||||
|       <label for="-http01"><input type="radio" id="-http01" | ||||
|        name="acme-challenge-type" value="http-01" checked>http-01</label> | ||||
|       <label for="-dns01"><input type="radio" id="-dns01" | ||||
|        name="acme-challenge-type" value="dns-01">dns-01</label> | ||||
|       <br> | ||||
|       <label for="-skipDryrun"><input class="js-skip-dryrun" name="skip-dryrun" | ||||
|         type="checkbox" id="-skipDryrun" checked> Skip dry-run challenge</label> | ||||
|       <br> | ||||
|       <button class="js-create-order" hidden>Create Order</button> | ||||
|     </form> | ||||
| 
 | ||||
|     <div class="js-loading" hidden>Loading</div> | ||||
| 
 | ||||
|     <details class="js-toc-jwk" hidden> | ||||
| @ -87,13 +120,23 @@ | ||||
|       <summary>PEM Public (base64-encoded SPKI/PKIX DER)</summary> | ||||
|       <pre><code  class="js-input-pem-spki-public" ></code></pre> | ||||
|     </details> | ||||
| 
 | ||||
|     <details class="js-toc-acme-account-response" hidden> | ||||
|       <summary>ACME Account Request</summary> | ||||
|       <pre><code class="js-acme-account-response"> </code></pre> | ||||
|     </details> | ||||
|     <details class="js-toc-acme-order-response" hidden> | ||||
|       <summary>ACME Order Response</summary> | ||||
|       <pre><code class="js-acme-order-response"> </code></pre> | ||||
|     </details> | ||||
|     <script src="./lib/bluecrypt-encoding.js"></script> | ||||
|     <script src="./lib/asn1-packer.js"></script> | ||||
|     <script src="./lib/x509.js"></script> | ||||
|     <script src="./lib/ecdsa.js"></script> | ||||
|     <script src="./lib/rsa.js"></script> | ||||
|     <script src="./lib/keypairs.js"></script> | ||||
|     <script src="./lib/asn1-parser.js"></script> | ||||
|     <script src="./lib/csr.js"></script> | ||||
|     <script src="./lib/acme.js"></script> | ||||
|     <script src="./app.js"></script> | ||||
|   </body> | ||||
| </html> | ||||
|  | ||||
							
								
								
									
										1111
									
								
								lib/acme.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1111
									
								
								lib/acme.js
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										298
									
								
								lib/csr.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										298
									
								
								lib/csr.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,298 @@ | ||||
| // Copyright 2018-present AJ ONeal. All rights reserved
 | ||||
| /* This Source Code Form is subject to the terms of the Mozilla Public | ||||
|  * License, v. 2.0. If a copy of the MPL was not distributed with this | ||||
|  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 | ||||
| (function (exports) { | ||||
| 'use strict'; | ||||
| /*global Promise*/ | ||||
| 
 | ||||
| var ASN1 = exports.ASN1; | ||||
| var Enc = exports.Enc; | ||||
| var PEM = exports.PEM; | ||||
| var X509 = exports.x509; | ||||
| var Keypairs = exports.Keypairs; | ||||
| 
 | ||||
| // TODO find a way that the prior node-ish way of `module.exports = function () {}` isn't broken
 | ||||
| var CSR = exports.CSR = function (opts) { | ||||
|   // We're using a Promise here to be compatible with the browser version
 | ||||
|   // which will probably use the webcrypto API for some of the conversions
 | ||||
|   return CSR._prepare(opts).then(function (opts) { | ||||
|     return CSR.create(opts).then(function (bytes) { | ||||
|       return CSR._encode(opts, bytes); | ||||
|     }); | ||||
|   }); | ||||
| }; | ||||
| 
 | ||||
| CSR._prepare = function (opts) { | ||||
|   return Promise.resolve().then(function () { | ||||
|     var Keypairs; | ||||
|     opts = JSON.parse(JSON.stringify(opts)); | ||||
| 
 | ||||
|     // We do a bit of extra error checking for user convenience
 | ||||
|     if (!opts) { throw new Error("You must pass options with key and domains to rsacsr"); } | ||||
|     if (!Array.isArray(opts.domains) || 0 === opts.domains.length) { | ||||
|       new Error("You must pass options.domains as a non-empty array"); | ||||
|     } | ||||
| 
 | ||||
|     // I need to check that 例.中国 is a valid domain name
 | ||||
|     if (!opts.domains.every(function (d) { | ||||
|       // allow punycode? xn--
 | ||||
|       if ('string' === typeof d /*&& /\./.test(d) && !/--/.test(d)*/) { | ||||
|         return true; | ||||
|       } | ||||
|     })) { | ||||
|       throw new Error("You must pass options.domains as strings"); | ||||
|     } | ||||
| 
 | ||||
|     if (opts.jwk) { return opts; } | ||||
|     if (opts.key && opts.key.kty) { | ||||
|       opts.jwk = opts.key; | ||||
|       return opts; | ||||
|     } | ||||
|     if (!opts.pem && !opts.key) { | ||||
|       throw new Error("You must pass options.key as a JSON web key"); | ||||
|     } | ||||
| 
 | ||||
|     Keypairs = exports.Keypairs; | ||||
|     if (!exports.Keypairs) { | ||||
|       throw new Error("Keypairs.js is an optional dependency for PEM-to-JWK.\n" | ||||
|         + "Install it if you'd like to use it:\n" | ||||
|         + "\tnpm install --save rasha\n" | ||||
|         + "Otherwise supply a jwk as the private key." | ||||
|       ); | ||||
|     } | ||||
| 
 | ||||
|     return Keypairs.import({ pem: opts.pem || opts.key }).then(function (pair) { | ||||
|       opts.jwk = pair.private; | ||||
|       return opts; | ||||
|     }); | ||||
|   }); | ||||
| }; | ||||
| 
 | ||||
| CSR._encode = function (opts, bytes) { | ||||
|   if ('der' === (opts.encoding||'').toLowerCase()) { | ||||
|     return bytes; | ||||
|   } | ||||
|   return PEM.packBlock({ | ||||
|     type: "CERTIFICATE REQUEST" | ||||
|   , bytes: bytes /* { jwk: jwk, domains: opts.domains } */ | ||||
|   }); | ||||
| }; | ||||
| 
 | ||||
| CSR.create = function createCsr(opts) { | ||||
|   var hex = CSR.request(opts.jwk, opts.domains); | ||||
|   return CSR._sign(opts.jwk, hex).then(function (csr) { | ||||
|     return Enc.hexToBuf(csr); | ||||
|   }); | ||||
| }; | ||||
| 
 | ||||
| //
 | ||||
| // EC / RSA
 | ||||
| //
 | ||||
| CSR.request = function createCsrBodyEc(jwk, domains) { | ||||
|   var asn1pub; | ||||
|   if (/^EC/i.test(jwk.kty)) { | ||||
|     asn1pub = X509.packCsrEcPublicKey(jwk); | ||||
|   } else { | ||||
|     asn1pub = X509.packCsrRsaPublicKey(jwk); | ||||
|   } | ||||
|   return X509.packCsr(asn1pub, domains); | ||||
| }; | ||||
| 
 | ||||
| CSR._sign = function csrEcSig(jwk, request) { | ||||
|   // Took some tips from https://gist.github.com/codermapuche/da4f96cdb6d5ff53b7ebc156ec46a10a
 | ||||
|   // TODO will have to convert web ECDSA signatures to PEM ECDSA signatures (but RSA should be the same)
 | ||||
|   // TODO have a consistent non-private way to sign
 | ||||
|   return Keypairs._sign({ jwk: jwk, format: 'x509' }, Enc.hexToBuf(request)).then(function (sig) { | ||||
|     return CSR._toDer({ request: request, signature: sig, kty: jwk.kty }); | ||||
|   }); | ||||
| }; | ||||
| 
 | ||||
| CSR._toDer = function encode(opts) { | ||||
|   var sty; | ||||
|   if (/^EC/i.test(opts.kty)) { | ||||
|     // 1.2.840.10045.4.3.2 ecdsaWithSHA256 (ANSI X9.62 ECDSA algorithm with SHA256)
 | ||||
|     sty = ASN1('30', ASN1('06', '2a8648ce3d040302')); | ||||
|   } else { | ||||
|     // 1.2.840.113549.1.1.11 sha256WithRSAEncryption (PKCS #1)
 | ||||
|     sty = ASN1('30', ASN1('06', '2a864886f70d01010b'), ASN1('05')); | ||||
|   } | ||||
|   return ASN1('30' | ||||
|     // The Full CSR Request Body
 | ||||
|   , opts.request | ||||
|     // The Signature Type
 | ||||
|   , sty | ||||
|     // The Signature
 | ||||
|   , ASN1.BitStr(Enc.bufToHex(opts.signature)) | ||||
|   ); | ||||
| }; | ||||
| 
 | ||||
| X509.packCsr = function (asn1pubkey, domains) { | ||||
|   return ASN1('30' | ||||
|     // Version (0)
 | ||||
|   , ASN1.UInt('00') | ||||
| 
 | ||||
|     // 2.5.4.3 commonName (X.520 DN component)
 | ||||
|   , ASN1('30', ASN1('31', ASN1('30', ASN1('06', '550403'), ASN1('0c', Enc.utf8ToHex(domains[0]))))) | ||||
| 
 | ||||
|     // Public Key (RSA or EC)
 | ||||
|   , asn1pubkey | ||||
| 
 | ||||
|     // Request Body
 | ||||
|   , ASN1('a0' | ||||
|     , ASN1('30' | ||||
|         // 1.2.840.113549.1.9.14 extensionRequest (PKCS #9 via CRMF)
 | ||||
|       , ASN1('06', '2a864886f70d01090e') | ||||
|       , ASN1('31' | ||||
|         , ASN1('30' | ||||
|           , ASN1('30' | ||||
|               // 2.5.29.17 subjectAltName (X.509 extension)
 | ||||
|             , ASN1('06', '551d11') | ||||
|             , ASN1('04' | ||||
|               , ASN1('30', domains.map(function (d) { | ||||
|                   return ASN1('82', Enc.utf8ToHex(d)); | ||||
|                 }).join('')))))))) | ||||
|   ); | ||||
| }; | ||||
| 
 | ||||
| // TODO finish this later
 | ||||
| // we want to parse the domains, the public key, and verify the signature
 | ||||
| CSR._info = function (der) { | ||||
|   // standard base64 PEM
 | ||||
|   if ('string' === typeof der && '-' === der[0]) { | ||||
|     der = PEM.parseBlock(der).bytes; | ||||
|   } | ||||
|   // jose urlBase64 not-PEM
 | ||||
|   if ('string' === typeof der) { | ||||
|     der = Enc.base64ToBuf(der); | ||||
|   } | ||||
|   // not supporting binary-encoded bas64
 | ||||
|   var c = ASN1.parse(der); | ||||
|   var kty; | ||||
|   // A cert has 3 parts: cert, signature meta, signature
 | ||||
|   if (c.children.length !== 3) { | ||||
|     throw new Error("doesn't look like a certificate request: expected 3 parts of header"); | ||||
|   } | ||||
|   var sig = c.children[2]; | ||||
|   if (sig.children.length) { | ||||
|     // ASN1/X509 EC
 | ||||
|     sig = sig.children[0]; | ||||
|     sig = ASN1('30', ASN1.UInt(Enc.bufToHex(sig.children[0].value)), ASN1.UInt(Enc.bufToHex(sig.children[1].value))); | ||||
|     sig = Enc.hexToBuf(sig); | ||||
|     kty = 'EC'; | ||||
|   } else { | ||||
|     // Raw RSA Sig
 | ||||
|     sig = sig.value; | ||||
|     kty = 'RSA'; | ||||
|   } | ||||
|   //c.children[1]; // signature type
 | ||||
|   var req = c.children[0]; | ||||
|   // TODO utf8
 | ||||
|   if (4 !== req.children.length) { | ||||
|     throw new Error("doesn't look like a certificate request: expected 4 parts to request"); | ||||
|   } | ||||
|   // 0 null
 | ||||
|   // 1 commonName / subject
 | ||||
|   var sub = Enc.bufToBin(req.children[1].children[0].children[0].children[1].value); | ||||
|   // 3 public key (type, key)
 | ||||
|   //console.log('oid', Enc.bufToHex(req.children[2].children[0].children[0].value));
 | ||||
|   var pub; | ||||
|   // TODO reuse ASN1 parser for these?
 | ||||
|   if ('EC' === kty) { | ||||
|     // throw away compression byte
 | ||||
|     pub = req.children[2].children[1].value.slice(1); | ||||
|     pub = { kty: kty, x: pub.slice(0, 32), y: pub.slice(32) }; | ||||
|     while (0 === pub.x[0]) { pub.x = pub.x.slice(1); } | ||||
|     while (0 === pub.y[0]) { pub.y = pub.y.slice(1); } | ||||
|     if ((pub.x.length || pub.x.byteLength) > 48) { | ||||
|       pub.crv = 'P-521'; | ||||
|     } else if ((pub.x.length || pub.x.byteLength) > 32) { | ||||
|       pub.crv = 'P-384'; | ||||
|     } else { | ||||
|       pub.crv = 'P-256'; | ||||
|     } | ||||
|     pub.x = Enc.bufToUrlBase64(pub.x); | ||||
|     pub.y = Enc.bufToUrlBase64(pub.y); | ||||
|   } else { | ||||
|     pub = req.children[2].children[1].children[0]; | ||||
|     pub = { kty: kty, n: pub.children[0].value, e: pub.children[1].value }; | ||||
|     while (0 === pub.n[0]) { pub.n = pub.n.slice(1); } | ||||
|     while (0 === pub.e[0]) { pub.e = pub.e.slice(1); } | ||||
|     pub.n = Enc.bufToUrlBase64(pub.n); | ||||
|     pub.e = Enc.bufToUrlBase64(pub.e); | ||||
|   } | ||||
|   // 4 extensions
 | ||||
|   var domains = req.children[3].children.filter(function (seq) { | ||||
|     //  1.2.840.113549.1.9.14 extensionRequest (PKCS #9 via CRMF)
 | ||||
|     if ('2a864886f70d01090e' === Enc.bufToHex(seq.children[0].value)) { | ||||
|       return true; | ||||
|     } | ||||
|   }).map(function (seq) { | ||||
|     return seq.children[1].children[0].children.filter(function (seq2) { | ||||
|       // subjectAltName (X.509 extension)
 | ||||
|       if ('551d11' === Enc.bufToHex(seq2.children[0].value)) { | ||||
|         return true; | ||||
|       } | ||||
|     }).map(function (seq2) { | ||||
|       return seq2.children[1].children[0].children.map(function (name) { | ||||
|         // TODO utf8
 | ||||
|         return Enc.bufToBin(name.value); | ||||
|       }); | ||||
|     })[0]; | ||||
|   })[0]; | ||||
| 
 | ||||
|   return { | ||||
|     subject: sub | ||||
|   , altnames: domains | ||||
|   , jwk: pub | ||||
|   , signature: sig | ||||
|   }; | ||||
| }; | ||||
| 
 | ||||
| X509.packCsrRsaPublicKey = function (jwk) { | ||||
|   // Sequence the key
 | ||||
|   var n = ASN1.UInt(Enc.base64ToHex(jwk.n)); | ||||
|   var e = ASN1.UInt(Enc.base64ToHex(jwk.e)); | ||||
|   var asn1pub = ASN1('30', n, e); | ||||
| 
 | ||||
|   // Add the CSR pub key header
 | ||||
|   return ASN1('30', ASN1('30', ASN1('06', '2a864886f70d010101'), ASN1('05')), ASN1.BitStr(asn1pub)); | ||||
| }; | ||||
| 
 | ||||
| X509.packCsrEcPublicKey = function (jwk) { | ||||
|   var ecOid = X509._oids[jwk.crv]; | ||||
|   if (!ecOid) { | ||||
|     throw new Error("Unsupported namedCurve '" + jwk.crv + "'. Supported types are " + Object.keys(X509._oids)); | ||||
|   } | ||||
|   var cmp = '04'; // 04 == x+y, 02 == x-only
 | ||||
|   var hxy = ''; | ||||
|   // Placeholder. I'm not even sure if compression should be supported.
 | ||||
|   if (!jwk.y) { cmp = '02'; } | ||||
|   hxy += Enc.base64ToHex(jwk.x); | ||||
|   if (jwk.y) { hxy += Enc.base64ToHex(jwk.y); } | ||||
| 
 | ||||
|   // 1.2.840.10045.2.1 ecPublicKey
 | ||||
|   return ASN1('30', ASN1('30', ASN1('06', '2a8648ce3d0201'), ASN1('06', ecOid)), ASN1.BitStr(cmp + hxy)); | ||||
| }; | ||||
| X509._oids = { | ||||
|   // 1.2.840.10045.3.1.7 prime256v1
 | ||||
|   // (ANSI X9.62 named elliptic curve) (06 08 - 2A 86 48 CE 3D 03 01 07)
 | ||||
|   'P-256': '2a8648ce3d030107' | ||||
|   // 1.3.132.0.34 P-384 (06 05 - 2B 81 04 00 22)
 | ||||
|   // (SEC 2 recommended EC domain secp256r1)
 | ||||
| , 'P-384': '2b81040022' | ||||
|   // requires more logic and isn't a recommended standard
 | ||||
|   // 1.3.132.0.35 P-521 (06 05 - 2B 81 04 00 23)
 | ||||
|   // (SEC 2 alternate P-521)
 | ||||
| //, 'P-521': '2B 81 04 00 23'
 | ||||
| }; | ||||
| 
 | ||||
| // don't replace the full parseBlock, if it exists
 | ||||
| PEM.parseBlock = PEM.parseBlock || function (str) { | ||||
|   var der = str.split(/\n/).filter(function (line) { | ||||
|     return !/-----/.test(line); | ||||
|   }).join(''); | ||||
|   return { bytes: Enc.base64ToBuf(der) }; | ||||
| }; | ||||
| 
 | ||||
| }('undefined' === typeof window ? module.exports : window)); | ||||
							
								
								
									
										553
									
								
								package-lock.json
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										553
									
								
								package-lock.json
									
									
									
										generated
									
									
									
										Normal file
									
								
							| @ -0,0 +1,553 @@ | ||||
| { | ||||
|   "name": "bluecrypt-keypairs", | ||||
|   "version": "0.1.0", | ||||
|   "lockfileVersion": 1, | ||||
|   "requires": true, | ||||
|   "dependencies": { | ||||
|     "@root/request": { | ||||
|       "version": "1.3.10", | ||||
|       "resolved": "https://registry.npmjs.org/@root/request/-/request-1.3.10.tgz", | ||||
|       "integrity": "sha512-GSn8dfsGp0juJyXS9k7B/DjYm7Axe85wiCHfPs30eQ+/V6p2aqey45e1czb3ZwP+iPmzWCKXahhWnZhSDIil6w==", | ||||
|       "dev": true | ||||
|     }, | ||||
|     "accepts": { | ||||
|       "version": "1.3.6", | ||||
|       "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.6.tgz", | ||||
|       "integrity": "sha512-QsaoUD2dpVpjENy8JFpQnXP9vyzoZPmAoKrE3S6HtSB7qzSebkJNnmdY4p004FQUSSiHXPueENpoeuUW/7a8Ig==", | ||||
|       "dev": true, | ||||
|       "requires": { | ||||
|         "mime-types": "~2.1.24", | ||||
|         "negotiator": "0.6.1" | ||||
|       } | ||||
|     }, | ||||
|     "array-flatten": { | ||||
|       "version": "1.1.1", | ||||
|       "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", | ||||
|       "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=", | ||||
|       "dev": true | ||||
|     }, | ||||
|     "balanced-match": { | ||||
|       "version": "1.0.0", | ||||
|       "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", | ||||
|       "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", | ||||
|       "dev": true | ||||
|     }, | ||||
|     "bluebird": { | ||||
|       "version": "3.5.4", | ||||
|       "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.4.tgz", | ||||
|       "integrity": "sha512-FG+nFEZChJrbQ9tIccIfZJBz3J7mLrAhxakAbnrJWn8d7aKOC+LWifa0G+p4ZqKp4y13T7juYvdhq9NzKdsrjw==", | ||||
|       "dev": true | ||||
|     }, | ||||
|     "body-parser": { | ||||
|       "version": "1.18.3", | ||||
|       "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.18.3.tgz", | ||||
|       "integrity": "sha1-WykhmP/dVTs6DyDe0FkrlWlVyLQ=", | ||||
|       "dev": true, | ||||
|       "requires": { | ||||
|         "bytes": "3.0.0", | ||||
|         "content-type": "~1.0.4", | ||||
|         "debug": "2.6.9", | ||||
|         "depd": "~1.1.2", | ||||
|         "http-errors": "~1.6.3", | ||||
|         "iconv-lite": "0.4.23", | ||||
|         "on-finished": "~2.3.0", | ||||
|         "qs": "6.5.2", | ||||
|         "raw-body": "2.3.3", | ||||
|         "type-is": "~1.6.16" | ||||
|       } | ||||
|     }, | ||||
|     "brace-expansion": { | ||||
|       "version": "1.1.11", | ||||
|       "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", | ||||
|       "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", | ||||
|       "dev": true, | ||||
|       "requires": { | ||||
|         "balanced-match": "^1.0.0", | ||||
|         "concat-map": "0.0.1" | ||||
|       } | ||||
|     }, | ||||
|     "bytes": { | ||||
|       "version": "3.0.0", | ||||
|       "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", | ||||
|       "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=", | ||||
|       "dev": true | ||||
|     }, | ||||
|     "cli": { | ||||
|       "version": "1.0.1", | ||||
|       "resolved": "https://registry.npmjs.org/cli/-/cli-1.0.1.tgz", | ||||
|       "integrity": "sha1-IoF1NPJL+klQw01TLUjsvGIbjBQ=", | ||||
|       "dev": true, | ||||
|       "requires": { | ||||
|         "exit": "0.1.2", | ||||
|         "glob": "^7.1.1" | ||||
|       } | ||||
|     }, | ||||
|     "concat-map": { | ||||
|       "version": "0.0.1", | ||||
|       "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", | ||||
|       "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", | ||||
|       "dev": true | ||||
|     }, | ||||
|     "content-disposition": { | ||||
|       "version": "0.5.2", | ||||
|       "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz", | ||||
|       "integrity": "sha1-DPaLud318r55YcOoUXjLhdunjLQ=", | ||||
|       "dev": true | ||||
|     }, | ||||
|     "content-type": { | ||||
|       "version": "1.0.4", | ||||
|       "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", | ||||
|       "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==", | ||||
|       "dev": true | ||||
|     }, | ||||
|     "cookie": { | ||||
|       "version": "0.3.1", | ||||
|       "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz", | ||||
|       "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=", | ||||
|       "dev": true | ||||
|     }, | ||||
|     "cookie-signature": { | ||||
|       "version": "1.0.6", | ||||
|       "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", | ||||
|       "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=", | ||||
|       "dev": true | ||||
|     }, | ||||
|     "debug": { | ||||
|       "version": "2.6.9", | ||||
|       "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", | ||||
|       "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", | ||||
|       "dev": true, | ||||
|       "requires": { | ||||
|         "ms": "2.0.0" | ||||
|       } | ||||
|     }, | ||||
|     "depd": { | ||||
|       "version": "1.1.2", | ||||
|       "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", | ||||
|       "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=", | ||||
|       "dev": true | ||||
|     }, | ||||
|     "destroy": { | ||||
|       "version": "1.0.4", | ||||
|       "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", | ||||
|       "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=", | ||||
|       "dev": true | ||||
|     }, | ||||
|     "dig.js": { | ||||
|       "version": "1.3.9", | ||||
|       "resolved": "https://registry.npmjs.org/dig.js/-/dig.js-1.3.9.tgz", | ||||
|       "integrity": "sha512-O/tSWZuW7AwpjsgePPmTanwvSDL9xF+FzLTJD9byN3C6lk79iMejC/Ahz9CERAXTW4e2TXL1vtqh3T0Ug79ocA==", | ||||
|       "dev": true, | ||||
|       "requires": { | ||||
|         "cli": "^1.0.1", | ||||
|         "dns-suite": "git+https://git.coolaj86.com/coolaj86/dns-suite.js#v1.2", | ||||
|         "hexdump.js": "git+https://git.coolaj86.com/coolaj86/hexdump.js#v1.0.4" | ||||
|       }, | ||||
|       "dependencies": { | ||||
|         "dns-suite": { | ||||
|           "version": "git+https://git.coolaj86.com/coolaj86/dns-suite.js#092008f766540909d27c934211495c9e03705bf3", | ||||
|           "from": "git+https://git.coolaj86.com/coolaj86/dns-suite.js#v1.2", | ||||
|           "dev": true, | ||||
|           "requires": { | ||||
|             "bluebird": "^3.5.0", | ||||
|             "hexdump.js": "git+https://git.coolaj86.com/coolaj86/hexdump.js#v1.0.4" | ||||
|           } | ||||
|         } | ||||
|       } | ||||
|     }, | ||||
|     "dns-suite": { | ||||
|       "version": "1.2.12", | ||||
|       "resolved": "https://registry.npmjs.org/dns-suite/-/dns-suite-1.2.12.tgz", | ||||
|       "integrity": "sha512-K4LWqmJT/T2QLaGCJ+qRvrT9DicKs5XxXYXw6uIZ1apdwyfToQk7K9AZbpFd0FLRdZG809v2vAcsquPbQh+Ipg==", | ||||
|       "dev": true, | ||||
|       "requires": { | ||||
|         "bluebird": "^3.5.0", | ||||
|         "hexdump.js": "git+https://git.coolaj86.com/coolaj86/hexdump.js#v1.0.4" | ||||
|       } | ||||
|     }, | ||||
|     "ee-first": { | ||||
|       "version": "1.1.1", | ||||
|       "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", | ||||
|       "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=", | ||||
|       "dev": true | ||||
|     }, | ||||
|     "encodeurl": { | ||||
|       "version": "1.0.2", | ||||
|       "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", | ||||
|       "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=", | ||||
|       "dev": true | ||||
|     }, | ||||
|     "escape-html": { | ||||
|       "version": "1.0.3", | ||||
|       "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", | ||||
|       "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=", | ||||
|       "dev": true | ||||
|     }, | ||||
|     "etag": { | ||||
|       "version": "1.8.1", | ||||
|       "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", | ||||
|       "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=", | ||||
|       "dev": true | ||||
|     }, | ||||
|     "exit": { | ||||
|       "version": "0.1.2", | ||||
|       "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", | ||||
|       "integrity": "sha1-BjJjj42HfMghB9MKD/8aF8uhzQw=", | ||||
|       "dev": true | ||||
|     }, | ||||
|     "express": { | ||||
|       "version": "4.16.4", | ||||
|       "resolved": "https://registry.npmjs.org/express/-/express-4.16.4.tgz", | ||||
|       "integrity": "sha512-j12Uuyb4FMrd/qQAm6uCHAkPtO8FDTRJZBDd5D2KOL2eLaz1yUNdUB/NOIyq0iU4q4cFarsUCrnFDPBcnksuOg==", | ||||
|       "dev": true, | ||||
|       "requires": { | ||||
|         "accepts": "~1.3.5", | ||||
|         "array-flatten": "1.1.1", | ||||
|         "body-parser": "1.18.3", | ||||
|         "content-disposition": "0.5.2", | ||||
|         "content-type": "~1.0.4", | ||||
|         "cookie": "0.3.1", | ||||
|         "cookie-signature": "1.0.6", | ||||
|         "debug": "2.6.9", | ||||
|         "depd": "~1.1.2", | ||||
|         "encodeurl": "~1.0.2", | ||||
|         "escape-html": "~1.0.3", | ||||
|         "etag": "~1.8.1", | ||||
|         "finalhandler": "1.1.1", | ||||
|         "fresh": "0.5.2", | ||||
|         "merge-descriptors": "1.0.1", | ||||
|         "methods": "~1.1.2", | ||||
|         "on-finished": "~2.3.0", | ||||
|         "parseurl": "~1.3.2", | ||||
|         "path-to-regexp": "0.1.7", | ||||
|         "proxy-addr": "~2.0.4", | ||||
|         "qs": "6.5.2", | ||||
|         "range-parser": "~1.2.0", | ||||
|         "safe-buffer": "5.1.2", | ||||
|         "send": "0.16.2", | ||||
|         "serve-static": "1.13.2", | ||||
|         "setprototypeof": "1.1.0", | ||||
|         "statuses": "~1.4.0", | ||||
|         "type-is": "~1.6.16", | ||||
|         "utils-merge": "1.0.1", | ||||
|         "vary": "~1.1.2" | ||||
|       } | ||||
|     }, | ||||
|     "finalhandler": { | ||||
|       "version": "1.1.1", | ||||
|       "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.1.tgz", | ||||
|       "integrity": "sha512-Y1GUDo39ez4aHAw7MysnUD5JzYX+WaIj8I57kO3aEPT1fFRL4sr7mjei97FgnwhAyyzRYmQZaTHb2+9uZ1dPtg==", | ||||
|       "dev": true, | ||||
|       "requires": { | ||||
|         "debug": "2.6.9", | ||||
|         "encodeurl": "~1.0.2", | ||||
|         "escape-html": "~1.0.3", | ||||
|         "on-finished": "~2.3.0", | ||||
|         "parseurl": "~1.3.2", | ||||
|         "statuses": "~1.4.0", | ||||
|         "unpipe": "~1.0.0" | ||||
|       } | ||||
|     }, | ||||
|     "forwarded": { | ||||
|       "version": "0.1.2", | ||||
|       "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", | ||||
|       "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=", | ||||
|       "dev": true | ||||
|     }, | ||||
|     "fresh": { | ||||
|       "version": "0.5.2", | ||||
|       "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", | ||||
|       "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=", | ||||
|       "dev": true | ||||
|     }, | ||||
|     "fs.realpath": { | ||||
|       "version": "1.0.0", | ||||
|       "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", | ||||
|       "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", | ||||
|       "dev": true | ||||
|     }, | ||||
|     "glob": { | ||||
|       "version": "7.1.3", | ||||
|       "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", | ||||
|       "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", | ||||
|       "dev": true, | ||||
|       "requires": { | ||||
|         "fs.realpath": "^1.0.0", | ||||
|         "inflight": "^1.0.4", | ||||
|         "inherits": "2", | ||||
|         "minimatch": "^3.0.4", | ||||
|         "once": "^1.3.0", | ||||
|         "path-is-absolute": "^1.0.0" | ||||
|       } | ||||
|     }, | ||||
|     "hexdump.js": { | ||||
|       "version": "git+https://git.coolaj86.com/coolaj86/hexdump.js#222fa7de5036a16397de2fe703c35ac54a3d8d0c", | ||||
|       "from": "git+https://git.coolaj86.com/coolaj86/hexdump.js#v1.0.4", | ||||
|       "dev": true | ||||
|     }, | ||||
|     "http-errors": { | ||||
|       "version": "1.6.3", | ||||
|       "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", | ||||
|       "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=", | ||||
|       "dev": true, | ||||
|       "requires": { | ||||
|         "depd": "~1.1.2", | ||||
|         "inherits": "2.0.3", | ||||
|         "setprototypeof": "1.1.0", | ||||
|         "statuses": ">= 1.4.0 < 2" | ||||
|       } | ||||
|     }, | ||||
|     "iconv-lite": { | ||||
|       "version": "0.4.23", | ||||
|       "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.23.tgz", | ||||
|       "integrity": "sha512-neyTUVFtahjf0mB3dZT77u+8O0QB89jFdnBkd5P1JgYPbPaia3gXXOVL2fq8VyU2gMMD7SaN7QukTB/pmXYvDA==", | ||||
|       "dev": true, | ||||
|       "requires": { | ||||
|         "safer-buffer": ">= 2.1.2 < 3" | ||||
|       } | ||||
|     }, | ||||
|     "inflight": { | ||||
|       "version": "1.0.6", | ||||
|       "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", | ||||
|       "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", | ||||
|       "dev": true, | ||||
|       "requires": { | ||||
|         "once": "^1.3.0", | ||||
|         "wrappy": "1" | ||||
|       } | ||||
|     }, | ||||
|     "inherits": { | ||||
|       "version": "2.0.3", | ||||
|       "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", | ||||
|       "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", | ||||
|       "dev": true | ||||
|     }, | ||||
|     "ipaddr.js": { | ||||
|       "version": "1.9.0", | ||||
|       "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.0.tgz", | ||||
|       "integrity": "sha512-M4Sjn6N/+O6/IXSJseKqHoFc+5FdGJ22sXqnjTpdZweHK64MzEPAyQZyEU3R/KRv2GLoa7nNtg/C2Ev6m7z+eA==", | ||||
|       "dev": true | ||||
|     }, | ||||
|     "media-typer": { | ||||
|       "version": "0.3.0", | ||||
|       "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", | ||||
|       "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=", | ||||
|       "dev": true | ||||
|     }, | ||||
|     "merge-descriptors": { | ||||
|       "version": "1.0.1", | ||||
|       "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", | ||||
|       "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=", | ||||
|       "dev": true | ||||
|     }, | ||||
|     "methods": { | ||||
|       "version": "1.1.2", | ||||
|       "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", | ||||
|       "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=", | ||||
|       "dev": true | ||||
|     }, | ||||
|     "mime": { | ||||
|       "version": "1.4.1", | ||||
|       "resolved": "https://registry.npmjs.org/mime/-/mime-1.4.1.tgz", | ||||
|       "integrity": "sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ==", | ||||
|       "dev": true | ||||
|     }, | ||||
|     "mime-db": { | ||||
|       "version": "1.40.0", | ||||
|       "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.40.0.tgz", | ||||
|       "integrity": "sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA==", | ||||
|       "dev": true | ||||
|     }, | ||||
|     "mime-types": { | ||||
|       "version": "2.1.24", | ||||
|       "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.24.tgz", | ||||
|       "integrity": "sha512-WaFHS3MCl5fapm3oLxU4eYDw77IQM2ACcxQ9RIxfaC3ooc6PFuBMGZZsYpvoXS5D5QTWPieo1jjLdAm3TBP3cQ==", | ||||
|       "dev": true, | ||||
|       "requires": { | ||||
|         "mime-db": "1.40.0" | ||||
|       } | ||||
|     }, | ||||
|     "minimatch": { | ||||
|       "version": "3.0.4", | ||||
|       "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", | ||||
|       "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", | ||||
|       "dev": true, | ||||
|       "requires": { | ||||
|         "brace-expansion": "^1.1.7" | ||||
|       } | ||||
|     }, | ||||
|     "ms": { | ||||
|       "version": "2.0.0", | ||||
|       "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", | ||||
|       "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", | ||||
|       "dev": true | ||||
|     }, | ||||
|     "negotiator": { | ||||
|       "version": "0.6.1", | ||||
|       "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz", | ||||
|       "integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk=", | ||||
|       "dev": true | ||||
|     }, | ||||
|     "on-finished": { | ||||
|       "version": "2.3.0", | ||||
|       "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", | ||||
|       "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", | ||||
|       "dev": true, | ||||
|       "requires": { | ||||
|         "ee-first": "1.1.1" | ||||
|       } | ||||
|     }, | ||||
|     "once": { | ||||
|       "version": "1.4.0", | ||||
|       "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", | ||||
|       "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", | ||||
|       "dev": true, | ||||
|       "requires": { | ||||
|         "wrappy": "1" | ||||
|       } | ||||
|     }, | ||||
|     "parseurl": { | ||||
|       "version": "1.3.3", | ||||
|       "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", | ||||
|       "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", | ||||
|       "dev": true | ||||
|     }, | ||||
|     "path-is-absolute": { | ||||
|       "version": "1.0.1", | ||||
|       "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", | ||||
|       "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", | ||||
|       "dev": true | ||||
|     }, | ||||
|     "path-to-regexp": { | ||||
|       "version": "0.1.7", | ||||
|       "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", | ||||
|       "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=", | ||||
|       "dev": true | ||||
|     }, | ||||
|     "proxy-addr": { | ||||
|       "version": "2.0.5", | ||||
|       "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.5.tgz", | ||||
|       "integrity": "sha512-t/7RxHXPH6cJtP0pRG6smSr9QJidhB+3kXu0KgXnbGYMgzEnUxRQ4/LDdfOwZEMyIh3/xHb8PX3t+lfL9z+YVQ==", | ||||
|       "dev": true, | ||||
|       "requires": { | ||||
|         "forwarded": "~0.1.2", | ||||
|         "ipaddr.js": "1.9.0" | ||||
|       } | ||||
|     }, | ||||
|     "qs": { | ||||
|       "version": "6.5.2", | ||||
|       "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", | ||||
|       "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==", | ||||
|       "dev": true | ||||
|     }, | ||||
|     "range-parser": { | ||||
|       "version": "1.2.0", | ||||
|       "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz", | ||||
|       "integrity": "sha1-9JvmtIeJTdxA3MlKMi9hEJLgDV4=", | ||||
|       "dev": true | ||||
|     }, | ||||
|     "raw-body": { | ||||
|       "version": "2.3.3", | ||||
|       "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.3.3.tgz", | ||||
|       "integrity": "sha512-9esiElv1BrZoI3rCDuOuKCBRbuApGGaDPQfjSflGxdy4oyzqghxu6klEkkVIvBje+FF0BX9coEv8KqW6X/7njw==", | ||||
|       "dev": true, | ||||
|       "requires": { | ||||
|         "bytes": "3.0.0", | ||||
|         "http-errors": "1.6.3", | ||||
|         "iconv-lite": "0.4.23", | ||||
|         "unpipe": "1.0.0" | ||||
|       } | ||||
|     }, | ||||
|     "safe-buffer": { | ||||
|       "version": "5.1.2", | ||||
|       "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", | ||||
|       "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", | ||||
|       "dev": true | ||||
|     }, | ||||
|     "safer-buffer": { | ||||
|       "version": "2.1.2", | ||||
|       "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", | ||||
|       "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", | ||||
|       "dev": true | ||||
|     }, | ||||
|     "send": { | ||||
|       "version": "0.16.2", | ||||
|       "resolved": "https://registry.npmjs.org/send/-/send-0.16.2.tgz", | ||||
|       "integrity": "sha512-E64YFPUssFHEFBvpbbjr44NCLtI1AohxQ8ZSiJjQLskAdKuriYEP6VyGEsRDH8ScozGpkaX1BGvhanqCwkcEZw==", | ||||
|       "dev": true, | ||||
|       "requires": { | ||||
|         "debug": "2.6.9", | ||||
|         "depd": "~1.1.2", | ||||
|         "destroy": "~1.0.4", | ||||
|         "encodeurl": "~1.0.2", | ||||
|         "escape-html": "~1.0.3", | ||||
|         "etag": "~1.8.1", | ||||
|         "fresh": "0.5.2", | ||||
|         "http-errors": "~1.6.2", | ||||
|         "mime": "1.4.1", | ||||
|         "ms": "2.0.0", | ||||
|         "on-finished": "~2.3.0", | ||||
|         "range-parser": "~1.2.0", | ||||
|         "statuses": "~1.4.0" | ||||
|       } | ||||
|     }, | ||||
|     "serve-static": { | ||||
|       "version": "1.13.2", | ||||
|       "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.13.2.tgz", | ||||
|       "integrity": "sha512-p/tdJrO4U387R9oMjb1oj7qSMaMfmOyd4j9hOFoxZe2baQszgHcSWjuya/CiT5kgZZKRudHNOA0pYXOl8rQ5nw==", | ||||
|       "dev": true, | ||||
|       "requires": { | ||||
|         "encodeurl": "~1.0.2", | ||||
|         "escape-html": "~1.0.3", | ||||
|         "parseurl": "~1.3.2", | ||||
|         "send": "0.16.2" | ||||
|       } | ||||
|     }, | ||||
|     "setprototypeof": { | ||||
|       "version": "1.1.0", | ||||
|       "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", | ||||
|       "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==", | ||||
|       "dev": true | ||||
|     }, | ||||
|     "statuses": { | ||||
|       "version": "1.4.0", | ||||
|       "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", | ||||
|       "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==", | ||||
|       "dev": true | ||||
|     }, | ||||
|     "type-is": { | ||||
|       "version": "1.6.18", | ||||
|       "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", | ||||
|       "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", | ||||
|       "dev": true, | ||||
|       "requires": { | ||||
|         "media-typer": "0.3.0", | ||||
|         "mime-types": "~2.1.24" | ||||
|       } | ||||
|     }, | ||||
|     "unpipe": { | ||||
|       "version": "1.0.0", | ||||
|       "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", | ||||
|       "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=", | ||||
|       "dev": true | ||||
|     }, | ||||
|     "utils-merge": { | ||||
|       "version": "1.0.1", | ||||
|       "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", | ||||
|       "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=", | ||||
|       "dev": true | ||||
|     }, | ||||
|     "vary": { | ||||
|       "version": "1.1.2", | ||||
|       "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", | ||||
|       "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=", | ||||
|       "dev": true | ||||
|     }, | ||||
|     "wrappy": { | ||||
|       "version": "1.0.2", | ||||
|       "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", | ||||
|       "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", | ||||
|       "dev": true | ||||
|     } | ||||
|   } | ||||
| } | ||||
| @ -1,7 +1,8 @@ | ||||
| { | ||||
|   "name": "bluecrypt-keypairs", | ||||
|   "version": "0.1.1", | ||||
|   "version": "0.1.0", | ||||
|   "description": "Zero-Dependency Native Browser support for ECDSA P-256 and P-384, and RSA 2048/3072/4096 written in VanillaJS", | ||||
|   "main": "server.js", | ||||
|   "directories": { | ||||
|     "lib": "lib" | ||||
|   }, | ||||
| @ -28,5 +29,9 @@ | ||||
|   "author": "AJ ONeal <coolaj86@gmail.com> (https://coolaj86.com/)", | ||||
|   "license": "MPL-2.0", | ||||
|   "devDependencies": { | ||||
|     "@root/request": "^1.3.10", | ||||
|     "dig.js": "^1.3.9", | ||||
|     "dns-suite": "^1.2.12", | ||||
|     "express": "^4.16.4" | ||||
|   } | ||||
| } | ||||
|  | ||||
							
								
								
									
										139
									
								
								server.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										139
									
								
								server.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,139 @@ | ||||
| 'use strict'; | ||||
| 
 | ||||
| var crypto = require('crypto'); | ||||
| //var dnsjs = require('dns-suite');
 | ||||
| var dig = require('dig.js/dns-request'); | ||||
| var request = require('util').promisify(require('@root/request')); | ||||
| var express = require('express'); | ||||
| var app = express(); | ||||
| 
 | ||||
| var nameservers = require('dns').getServers(); | ||||
| var index = crypto.randomBytes(2).readUInt16BE(0) % nameservers.length; | ||||
| var nameserver = nameservers[index]; | ||||
| 
 | ||||
| app.use('/', express.static(__dirname)); | ||||
| app.use('/api', express.json()); | ||||
| app.get('/api/dns/:domain', function (req, res, next) { | ||||
|   var domain = req.params.domain; | ||||
|   var casedDomain = domain.toLowerCase().split('').map(function (ch) { | ||||
|     // dns0x20 takes advantage of the fact that the binary operation for toUpperCase is
 | ||||
|     // ch = ch | 0x20;
 | ||||
|     return Math.round(Math.random()) % 2 ? ch : ch.toUpperCase(); | ||||
|   }).join(''); | ||||
|   var typ = req.query.type; | ||||
|   var query = { | ||||
|     header: { | ||||
|       id: crypto.randomBytes(2).readUInt16BE(0) | ||||
|     , qr: 0 | ||||
|     , opcode: 0 | ||||
|     , aa: 0 // Authoritative-Only
 | ||||
|     , tc: 0                   // NA
 | ||||
|     , rd: 1 // Recurse
 | ||||
|     , ra: 0                   // NA
 | ||||
|     , rcode: 0                // NA
 | ||||
|     } | ||||
|   , question: [ | ||||
|       { name: casedDomain | ||||
|       //, type: typ || 'A'
 | ||||
|       , typeName: typ || 'A' | ||||
|       , className: 'IN' | ||||
|       } | ||||
|     ] | ||||
|   }; | ||||
|   var opts = { | ||||
|     onError: function (err) { | ||||
|       next(err); | ||||
|     } | ||||
|   , onMessage: function (packet) { | ||||
|       var fail0x20; | ||||
| 
 | ||||
|       if (packet.id !== query.id) { | ||||
|         console.error('[SECURITY] ignoring packet for \'' + packet.question[0].name + '\' due to mismatched id'); | ||||
|         console.error(packet); | ||||
|         return; | ||||
|       } | ||||
| 
 | ||||
|       packet.question.forEach(function (q) { | ||||
|         // if (-1 === q.name.lastIndexOf(cli.casedQuery))
 | ||||
|         if (q.name !== casedDomain) { | ||||
|           fail0x20 = q.name; | ||||
|         } | ||||
|       }); | ||||
| 
 | ||||
|       [ 'question', 'answer', 'authority', 'additional' ].forEach(function (group) { | ||||
|         (packet[group]||[]).forEach(function (a) { | ||||
|           var an = a.name; | ||||
|           var i = domain.toLowerCase().lastIndexOf(a.name.toLowerCase());  // answer is something like ExAMPle.cOM and query was wWw.ExAMPle.cOM
 | ||||
|           var j = a.name.toLowerCase().lastIndexOf(domain.toLowerCase());  // answer is something like www.ExAMPle.cOM and query was ExAMPle.cOM
 | ||||
| 
 | ||||
|           // it's important to note that these should only relpace changes in casing that we expected
 | ||||
|           // any abnormalities should be left intact to go "huh?" about
 | ||||
|           // TODO detect abnormalities?
 | ||||
|           if (-1 !== i) { | ||||
|             // "EXamPLE.cOm".replace("wWw.EXamPLE.cOm".substr(4), "www.example.com".substr(4))
 | ||||
|             a.name = a.name.replace(casedDomain.substr(i), domain.substr(i)); | ||||
|           } else if (-1 !== j) { | ||||
|             // "www.example.com".replace("EXamPLE.cOm", "example.com")
 | ||||
|             a.name = a.name.substr(0, j) + a.name.substr(j).replace(casedDomain, domain); | ||||
|           } | ||||
| 
 | ||||
|           // NOTE: right now this assumes that anything matching the query matches all the way to the end
 | ||||
|           // it does not handle the case of a record for example.com.uk being returned in response to a query for www.example.com correctly
 | ||||
|           // (but I don't think it should need to)
 | ||||
|           if (a.name.length !== an.length) { | ||||
|             console.error("[ERROR] question / answer mismatch: '" + an + "' != '" + a.length + "'"); | ||||
|             console.error(a); | ||||
|           } | ||||
|         }); | ||||
|       }); | ||||
| 
 | ||||
|       if (fail0x20) { | ||||
|         console.warn(";; Warning: DNS 0x20 security not implemented (or packet spoofed). Queried '" | ||||
|           + casedDomain + "' but got response for '" + fail0x20 + "'."); | ||||
|         return; | ||||
|       } | ||||
| 
 | ||||
|       res.send({ | ||||
|         header: packet.header | ||||
|       , question: packet.question | ||||
|       , answer: packet.answer | ||||
|       , authority: packet.authority | ||||
|       , additional: packet.additional | ||||
|       , edns_options: packet.edns_options | ||||
|       }); | ||||
|     } | ||||
|   , onListening: function () {} | ||||
|   , onSent: function (/*res*/) { } | ||||
|   , onTimeout: function (res) { | ||||
|       console.error('dns timeout:', res); | ||||
|       next(new Error("DNS timeout - no response")); | ||||
|     } | ||||
|   , onClose: function () { } | ||||
|   //, mdns: cli.mdns
 | ||||
|   , nameserver: nameserver | ||||
|   , port: 53 | ||||
|   , timeout: 2000 | ||||
|   }; | ||||
| 
 | ||||
|   dig.resolveJson(query, opts); | ||||
| }); | ||||
| app.get('/api/http', function (req, res) { | ||||
|   var url = req.query.url; | ||||
|   return request({ method: 'GET', url: url }).then(function (resp) { | ||||
|     res.send(resp.body); | ||||
|   }); | ||||
| }); | ||||
| app.get('/api/_acme_api_', function (req, res) { | ||||
|   res.send({ success: true }); | ||||
| }); | ||||
| 
 | ||||
| module.exports = app; | ||||
| if (require.main === module) { | ||||
|   // curl -L http://localhost:3000/api/dns/example.com?type=A
 | ||||
|   console.info("Listening on localhost:3000"); | ||||
|   app.listen(3000); | ||||
|   console.info("Try this:"); | ||||
|   console.info("\tcurl -L 'http://localhost:3000/api/_acme_api_/'"); | ||||
|   console.info("\tcurl -L 'http://localhost:3000/api/dns/example.com?type=A'"); | ||||
|   console.info("\tcurl -L 'http://localhost:3000/api/http/?url=https://example.com'"); | ||||
| } | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user