forked from root/acme.js
		
	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
 | 
					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
 | 
					Lightweight. Fast. Modern Crypto. Zero dependecies.
 | 
				
			||||||
  * Eckles (ECDSA)
 | 
					
 | 
				
			||||||
  * Rasha (RSA)
 | 
					(a port of [acme.js](https://git.coolaj86.com/coolaj86/acme-v2.js) to the browser)
 | 
				
			||||||
  * X509
 | 
					
 | 
				
			||||||
  * ASN1
 | 
					# 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));
 | 
					    return Array.prototype.slice.call(document.querySelectorAll(sel));
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  function checkTos(tos) {
 | 
				
			||||||
 | 
					    if ($('input[name="tos"]:checked')) {
 | 
				
			||||||
 | 
					      return tos;
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      return '';
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  function run() {
 | 
					  function run() {
 | 
				
			||||||
    console.log('hello');
 | 
					    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;
 | 
					    $('.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);
 | 
					  window.addEventListener('load', run);
 | 
				
			||||||
}());
 | 
					}());
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										45
									
								
								index.html
									
									
									
									
									
								
							
							
						
						
									
										45
									
								
								index.html
									
									
									
									
									
								
							@ -53,6 +53,39 @@
 | 
				
			|||||||
      <button class="js-generate" hidden>Generate</button>
 | 
					      <button class="js-generate" hidden>Generate</button>
 | 
				
			||||||
    </form>
 | 
					    </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>
 | 
					    <div class="js-loading" hidden>Loading</div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <details class="js-toc-jwk" hidden>
 | 
					    <details class="js-toc-jwk" hidden>
 | 
				
			||||||
@ -87,13 +120,23 @@
 | 
				
			|||||||
      <summary>PEM Public (base64-encoded SPKI/PKIX DER)</summary>
 | 
					      <summary>PEM Public (base64-encoded SPKI/PKIX DER)</summary>
 | 
				
			||||||
      <pre><code  class="js-input-pem-spki-public" ></code></pre>
 | 
					      <pre><code  class="js-input-pem-spki-public" ></code></pre>
 | 
				
			||||||
    </details>
 | 
					    </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/bluecrypt-encoding.js"></script>
 | 
				
			||||||
    <script src="./lib/asn1-packer.js"></script>
 | 
					    <script src="./lib/asn1-packer.js"></script>
 | 
				
			||||||
    <script src="./lib/x509.js"></script>
 | 
					    <script src="./lib/x509.js"></script>
 | 
				
			||||||
    <script src="./lib/ecdsa.js"></script>
 | 
					    <script src="./lib/ecdsa.js"></script>
 | 
				
			||||||
    <script src="./lib/rsa.js"></script>
 | 
					    <script src="./lib/rsa.js"></script>
 | 
				
			||||||
    <script src="./lib/keypairs.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>
 | 
					    <script src="./app.js"></script>
 | 
				
			||||||
  </body>
 | 
					  </body>
 | 
				
			||||||
</html>
 | 
					</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",
 | 
					  "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",
 | 
					  "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": {
 | 
					  "directories": {
 | 
				
			||||||
    "lib": "lib"
 | 
					    "lib": "lib"
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
@ -28,5 +29,9 @@
 | 
				
			|||||||
  "author": "AJ ONeal <coolaj86@gmail.com> (https://coolaj86.com/)",
 | 
					  "author": "AJ ONeal <coolaj86@gmail.com> (https://coolaj86.com/)",
 | 
				
			||||||
  "license": "MPL-2.0",
 | 
					  "license": "MPL-2.0",
 | 
				
			||||||
  "devDependencies": {
 | 
					  "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