AJ ONeal
5 years ago
9 changed files with 2496 additions and 10 deletions
@ -1,9 +1,190 @@ |
|||
# Bluecrypt™ Keypairs |
|||
# Bluecrypt™ [ACME.js](https://git.rootprojects.org/root/bluecrypt-acme.js) | A [Root](https://rootprojects.org/acme/) project |
|||
|
|||
A port of [keypairs.js](https://git.coolaj86.com/coolaj86/keypairs.js) to the browser. |
|||
Free SSL Certificates from Let's Encrypt, right in your Web Browser |
|||
|
|||
* Keypairs |
|||
* Eckles (ECDSA) |
|||
* Rasha (RSA) |
|||
* X509 |
|||
* ASN1 |
|||
Lightweight. Fast. Modern Crypto. Zero dependecies. |
|||
|
|||
(a port of [acme.js](https://git.coolaj86.com/coolaj86/acme-v2.js) to the browser) |
|||
|
|||
# Features |
|||
|
|||
| 15k gzipped | 55k minified | 88k (2,500 loc) source with comments | |
|||
|
|||
* [x] Let's Encrypt |
|||
* [x] ACME draft 15 (supports POST-as-GET) |
|||
* [x] Secure support for EC and RSA for account and server keys |
|||
* [x] Simple and lightweight PEM, DER, ASN1, X509, and CSR implementations |
|||
* [x] VanillaJS, Zero Dependencies |
|||
|
|||
# Online Demos |
|||
|
|||
* Greenlock for the Web <https://greenlock.domains> |
|||
* Bluecrypt ACME Demo <https://rootprojects.org/acme/> |
|||
|
|||
We expect that our hosted versions will meet all of yours needs. |
|||
If they don't, please open an issue to let us know why. |
|||
|
|||
We'd much rather improve the app than have a hundred different versions running in the wild. |
|||
However, in keeping to our values we've made the source visible for others to inspect, improve, and modify. |
|||
|
|||
# QuickStart |
|||
|
|||
Bluecrypt ACME embeds [Keypairs.js](https://git.rootprojects.org/root/bluecrypt-keypairs.js) |
|||
and [CSR.js](https://git.rootprojects.org/root/bluecrypt-csr.js) |
|||
|
|||
`bluecrypt-acme.js` |
|||
```html |
|||
<script src="https://rootprojects.org/acme/bluecrypt-acme.js"></script> |
|||
``` |
|||
|
|||
`bluecrypt-acme.min.js` |
|||
```html |
|||
<script src="https://rootprojects.org/acme/bluecrypt-acme.min.js"></script> |
|||
``` |
|||
|
|||
You can see `index.html` and `app.js` in the repo for full example usage. |
|||
|
|||
### Instantiate Bluecrypt ACME |
|||
|
|||
Although built for Let's Encrypt, Bluecrypt ACME will work with any server |
|||
that supports draft-15 of the ACME spec (includes POST-as-GET support). |
|||
|
|||
The `init()` method takes a _directory url_ and initializes internal state according to its response. |
|||
|
|||
```js |
|||
var acme = ACME.create({}); |
|||
acme.init('https://acme-staging-v02.api.letsencrypt.org/directory').then(function () { |
|||
// Ready to use, show page |
|||
$('body').hidden = false; |
|||
}); |
|||
``` |
|||
|
|||
### Create ACME Account with Let's Encrypt |
|||
|
|||
ACME Accounts are key and device based, with an email address as a backup identifier. |
|||
|
|||
A public account key must be registered before an SSL certificate can be requested. |
|||
|
|||
```js |
|||
var accountPrivateKey; |
|||
var account; |
|||
|
|||
Keypairs.generate({ kty: 'EC' }).then(function (pair) { |
|||
accountPrivateKey = pair.private; |
|||
|
|||
return acme.accounts.create({ |
|||
agreeToTerms: function (tos) { |
|||
if (window.confirm("Do you agree to the Bluecrypt and Let's Encrypt Terms of Service?")) { |
|||
return Promise.resolve(tos); |
|||
} |
|||
} |
|||
, accountKeypair: { privateKeyJwk: pair.private } |
|||
, email: $('.js-email-input').value |
|||
}).then(function (_account) { |
|||
account = _account; |
|||
}); |
|||
}); |
|||
``` |
|||
|
|||
### Get Free 90-day SSL Certificate |
|||
|
|||
Creating an ACME "order" for a 90-day SSL certificate requires use of the account private key, |
|||
the names of domains to be secured, and a distinctly separate server private key. |
|||
|
|||
A domain ownership verification "challenge" (uploading a file to an unsecured HTTP url or setting a DNS record) |
|||
is a required part of the process, which requires `set` and `remove` callbacks/promises. |
|||
|
|||
```js |
|||
var serverPrivateKey; |
|||
|
|||
Keypairs.generate({ kty: 'EC' }).then(function (pair) { |
|||
serverPrivateKey = pair.private; |
|||
|
|||
return acme.certificates.create({ |
|||
agreeToTerms: function (tos) { |
|||
return tos; |
|||
} |
|||
, account: account |
|||
, accountKeypair: { privateKeyJwk: accountPrivateKey } |
|||
, serverKeypair: { privateKeyJwk: serverPrivateKey } |
|||
, domains: ['example.com','www.example.com'] |
|||
, challenges: challenges // must be implemented |
|||
, skipDryRun: true |
|||
}).then(function (results) { |
|||
console.log('Got SSL Certificate:'); |
|||
console.log(results.expires); |
|||
console.log(results.cert); |
|||
console.log(results.chain); |
|||
}); |
|||
|
|||
}); |
|||
``` |
|||
|
|||
### Example "Challenge" Implementation |
|||
|
|||
Typically here you're just presenting some sort of dialog to the user to ask them to |
|||
upload a file or set a DNS record. |
|||
|
|||
It may be possible to do something fancy like using OAuth2 to login to Google Domanis |
|||
to set a DNS address, etc, but it seems like that sort of fanciness is probably best |
|||
reserved for server-side plugins. |
|||
|
|||
```js |
|||
var challenges = { |
|||
'http-01': { |
|||
set: function (opts) { |
|||
console.info('http-01 set challenge:'); |
|||
console.info(opts.challengeUrl); |
|||
console.info(opts.keyAuthorization); |
|||
while (!window.confirm("Upload the challenge file before continuing.")) {} |
|||
return Promise.resolve(); |
|||
} |
|||
, remove: function (opts) { |
|||
console.log('http-01 remove challenge:', opts.challengeUrl); |
|||
return Promise.resolve(); |
|||
} |
|||
} |
|||
}; |
|||
``` |
|||
|
|||
# Full Documentation |
|||
|
|||
See [acme.js](https://git.coolaj86.com/coolaj86/acme-v2.js). |
|||
|
|||
Aside from the loading instructions (`npm` and `require` instead of `script` tags), |
|||
the usage is identical to the node version. |
|||
|
|||
That said, the two may leap-frog a little from time to time |
|||
(for example, the browser version is just a touch ahead at the moment). |
|||
|
|||
# Developing |
|||
|
|||
You can see `<script>` tags in the `index.html` in the repo, which references the original |
|||
source files. |
|||
|
|||
Join `@rootprojects` `#general` on [Keybase](https://keybase.io) if you'd like to chat with us. |
|||
|
|||
# Commercial Support |
|||
|
|||
We have both commercial support and commercial licensing available. |
|||
|
|||
You're welcome to [contact us](mailto:aj@therootcompany.com) in regards to IoT, On-Prem, |
|||
Enterprise, and Internal installations, integrations, and deployments. |
|||
|
|||
We also offer consulting for all-things-ACME and Let's Encrypt. |
|||
|
|||
# Legal & Rules of the Road |
|||
|
|||
Bluecrypt™ and Greenlock™ are [trademarks](https://rootprojects.org/legal/#trademark) of AJ ONeal |
|||
|
|||
The rule of thumb is "attribute, but don't confuse". For example: |
|||
|
|||
> Built with [Root](https://rootprojects.org)'s [Bluecrypt ACME](https://git.rootprojects.org/root/bluecrypt-acme.js). |
|||
|
|||
Please [contact us](mailto:aj@therootcompany.com) if have any questions in regards to our trademark, |
|||
attribution, and/or visible source policies. We want to help to community as we build great software. |
|||
|
|||
[bluecrypt.js](https://git.coolaj86.com/coolaj86/bluecrypt.js) | |
|||
MPL-2.0 | |
|||
[Terms of Use](https://therootcompany.com/legal/#terms) | |
|||
[Privacy Policy](https://therootcompany.com/legal/#privacy) |
|||
|
File diff suppressed because it is too large
@ -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)); |
@ -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 |
|||
} |
|||
} |
|||
} |
@ -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…
Reference in new issue