Browse Source

testing working

wip-v3
AJ ONeal 5 years ago
parent
commit
2b0fce0869
  1. 208
      README.md
  2. 0
      examples/app.js
  3. 0
      examples/index.html
  4. 174
      examples/server.js
  5. 123
      lib/acme.js
  6. 2
      lib/asn1/packer.js
  7. 2
      lib/asn1/parser.js
  8. 16
      lib/csr.js
  9. 3
      lib/ecdsa.js
  10. 151
      lib/encoding.js
  11. 23
      lib/keypairs.js
  12. 2
      lib/node/rsa.js
  13. 2
      lib/pem.js
  14. 2
      lib/rsa.js
  15. 2
      lib/x509.js
  16. 421
      package-lock.json
  17. 8
      package.json
  18. 139
      server.js
  19. 44
      tests/index.js
  20. 18
      webpack.config.js

208
README.md

@ -1,25 +1,35 @@
# Bluecrypt™ [ACME.js](https://git.rootprojects.org/root/bluecrypt-acme.js) | A [Root](https://rootprojects.org/acme/) project
# [ACME.js](https://git.rootprojects.org/root/bluecrypt-acme.js)
Free SSL Certificates from Let's Encrypt, right in your Web Browser
Free SSL Certificates from Let's Encrypt, for Node.js and Web Browsers
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
- [x] Let's Encrypt v2.1+ (November 2019)
- [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] Supports International Domain Names (i.e. `.中国`)
- [x] VanillaJS, Zero External Dependencies
- [x] Node.js
- [x] WebPack
# Want Quick and Easy?
ACME.js is a low-level tool for building Let's Encrypt clients in Node and Browsers.
If you're looking for maximum convenience, try
[Greenlock.js](https://git.rootprojects.org/root/greenlock-express.js).
- <https://git.rootprojects.org/root/greenlock-express.js>
# Online Demos
* Greenlock for the Web <https://greenlock.domains>
* Bluecrypt ACME Demo <https://rootprojects.org/acme/>
- Greenlock for the Web <https://greenlock.domains>
- ACME.js 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.
@ -29,34 +39,59 @@ However, in keeping to our values we've made the source visible for others to in
# QuickStart
Bluecrypt ACME embeds [Keypairs.js](https://git.rootprojects.org/root/bluecrypt-keypairs.js)
To make it easy to generate, encode, and decode keys and certificates,
ACME.js 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`
## Node.js
```js
var ACME = require('@root/acme');
```
## WebPack
```js
var ACME = require('@root/acme');
```
## Vanilla JS
```js
var ACME = window.ACME;
```
`acme.js`
```html
<script src="https://rootprojects.org/acme/bluecrypt-acme.js"></script>
<script src="https://unpkg.com/@root/acme/dist/acme.js"></script>
```
`bluecrypt-acme.min.js`
`acme.min.js`
```html
<script src="https://rootprojects.org/acme/bluecrypt-acme.min.js"></script>
<script src="https://unpkg.com/@root/acme/dist/acme.min.js"></script>
```
You can see `index.html` and `app.js` in the repo for full example usage.
## Examples
### Instantiate Bluecrypt ACME
You can see `tests/index.js`, `examples/index.html`, `examples/app.js` in the repo for full example usage.
Although built for Let's Encrypt, Bluecrypt ACME will work with any server
### Instantiate ACME.js
Although built for Let's Encrypt, ACME.js 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;
});
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
@ -69,20 +104,26 @@ A public account key must be registered before an SSL certificate can be request
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;
});
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 ACME.js 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;
});
});
```
@ -97,26 +138,27 @@ is a required part of the process, which requires `set` and `remove` callbacks/p
```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);
});
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);
});
});
```
@ -131,22 +173,42 @@ 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();
}
}
'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();
}
}
};
```
# IDN - International Domain Names
Convert domain names to `punycode` before creating the certificate:
```js
var punycode = require('punycode');
acme.certificates.create({
// ...
domains: ['example.com', 'www.example.com'].map(function(name) {
return punycode.toASCII(name);
})
});
```
The punycode library itself is lightweight and dependency-free.
It is available both in node and for browsers.
# Full Documentation
See [acme.js](https://git.coolaj86.com/coolaj86/acme-v2.js).
@ -175,16 +237,16 @@ We also offer consulting for all-things-ACME and Let's Encrypt.
# Legal &amp; Rules of the Road
Bluecrypt&trade; and Greenlock&trade; are [trademarks](https://rootprojects.org/legal/#trademark) of AJ ONeal
Greenlock&trade; is a [trademark](https://rootprojects.org/legal/#trademark) of AJ ONeal
The rule of thumb is "attribute, but don't confuse". For example:
> Built with [Bluecrypt ACME](https://git.rootprojects.org/root/bluecrypt-acme.js) (a [Root](https://rootprojects.org) project).
> Built with [ACME.js](https://git.rootprojects.org/root/bluecrypt-acme.js) (a [Root](https://rootprojects.org) project).
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 build great software and a great community.
[bluecrypt.js](https://git.coolaj86.com/coolaj86/bluecrypt.js) |
[ACME.js](https://git.rootprojects.org/root/acme.js) |
MPL-2.0 |
[Terms of Use](https://therootcompany.com/legal/#terms) |
[Privacy Policy](https://therootcompany.com/legal/#privacy)

0
app.js → examples/app.js

0
index.html → examples/index.html

174
examples/server.js

@ -0,0 +1,174 @@
'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'"
);
}

123
lib/acme.js

@ -5,10 +5,11 @@
'use strict';
/* globals Promise */
require('@root/encoding/bytes');
var Enc = require('@root/encoding/base64');
var ACME = module.exports;
//var Keypairs = exports.Keypairs || {};
//var CSR = exports.CSR;
var Enc = require('omnibuffer');
var sha2 = require('./node/sha2.js');
var http = require('./node/http.js');
@ -37,21 +38,22 @@ ACME.challengePrefixes = {
};
ACME.challengeTests = {
'http-01': function(me, auth) {
return me.http01(auth).then(function(keyAuth) {
var ch = auth.challenge;
return me.http01(ch).then(function(keyAuth) {
var err;
// TODO limit the number of bytes that are allowed to be downloaded
if (auth.keyAuthorization === (keyAuth || '').trim()) {
if (ch.keyAuthorization === (keyAuth || '').trim()) {
return true;
}
err = new Error(
'Error: Failed HTTP-01 Pre-Flight / Dry Run.\n' +
"curl '" +
auth.challengeUrl +
ch.challengeUrl +
"'\n" +
"Expected: '" +
auth.keyAuthorization +
ch.keyAuthorization +
"'\n" +
"Got: '" +
keyAuth +
@ -64,12 +66,13 @@ ACME.challengeTests = {
},
'dns-01': function(me, auth) {
// remove leading *. on wildcard domains
return me.dns01(auth).then(function(ans) {
var ch = auth.challenge;
return me.dns01(ch).then(function(ans) {
var err;
if (
ans.answer.some(function(txt) {
return auth.dnsAuthorization === txt.data[0];
return ch.dnsAuthorization === txt.data[0];
})
) {
return true;
@ -78,9 +81,9 @@ ACME.challengeTests = {
err = new Error(
'Error: Failed DNS-01 Pre-Flight Dry Run.\n' +
"dig TXT '" +
auth.dnsHost +
ch.dnsHost +
"' does not return '" +
auth.dnsAuthorization +
ch.dnsAuthorization +
"'\n" +
'See https://git.coolaj86.com/coolaj86/acme-v2.js/issues/4'
);
@ -565,16 +568,21 @@ ACME._challengeToAuth = function(
// For backwards compat with the v2.7 plugins
auth.challenge = auth;
// TODO can we use just { challenge: auth }?
auth.request = function() {
// TODO see https://git.rootprojects.org/root/acme.js/issues/###
console.warn(
"[warn] deprecated use of request on '" +
auth.type +
"' challenge object. Receive from challenger.init() instead."
);
me.request.apply(null, arguments);
// auth.request = ;
// TODO get rid of no-challenge backwards compat challenge
return {
challenge: auth,
request: function() {
// TODO see https://git.rootprojects.org/root/acme.js/issues/###
console.warn(
"[warn] deprecated use of request on '" +
auth.type +
"' challenge object. Receive from challenger.init() instead."
);
me.request.apply(null, arguments);
}
};
return auth;
});
};
@ -592,8 +600,9 @@ ACME._postChallenge = function(me, options, auth) {
var MAX_POLL = me.retryPoll || 8;
var MAX_PEND = me.retryPending || 4;
var count = 0;
var ch = auth.challenge;
var altname = ACME._untame(auth.identifier.value, auth.wildcard);
var altname = ACME._untame(ch.identifier.value, ch.wildcard);
/*
POST /acme/authz/1234 HTTP/1.1
@ -619,7 +628,7 @@ ACME._postChallenge = function(me, options, auth) {
}
return ACME._jwsRequest(me, {
options: options,
url: auth.url,
url: ch.url,
protected: { kid: options._kid },
payload: Enc.strToBuf(JSON.stringify({ status: 'deactivated' }))
}).then(function(resp) {
@ -651,11 +660,11 @@ ACME._postChallenge = function(me, options, auth) {
}
// TODO POST-as-GET
return me
.request({ method: 'GET', url: auth.url, json: true })
.request({ method: 'GET', url: ch.url, json: true })
.then(function(resp) {
if ('processing' === resp.body.status) {
if (me.debug) {
console.debug('poll: again', auth.url);
console.debug('poll: again', ch.url);
}
return ACME._wait(RETRY_INTERVAL).then(pollStatus);
}
@ -668,7 +677,7 @@ ACME._postChallenge = function(me, options, auth) {
.then(respondToChallenge);
}
if (me.debug) {
console.debug('poll: again', auth.url);
console.debug('poll: again', ch.url);
}
return ACME._wait(RETRY_INTERVAL).then(respondToChallenge);
}
@ -719,7 +728,7 @@ ACME._postChallenge = function(me, options, auth) {
}
return ACME._jwsRequest(me, {
options: options,
url: auth.url,
url: ch.url,
protected: { kid: options._kid },
payload: Enc.strToBuf(JSON.stringify({}))
}).then(function(resp) {
@ -736,13 +745,14 @@ ACME._postChallenge = function(me, options, auth) {
return respondToChallenge();
};
ACME._setChallenge = function(me, options, auth) {
var ch = auth.challenge;
return Promise.resolve().then(function() {
var challengers = options.challenges || {};
var challenger = challengers[auth.type] && challengers[auth.type].set;
var challenger = challengers[ch.type] && challengers[ch.type].set;
if (!challenger) {
throw new Error(
"options.challenges did not have a valid entry for '" +
auth.type +
ch.type +
"'"
);
}
@ -760,7 +770,7 @@ ACME._setChallenge = function(me, options, auth) {
});
} else {
throw new Error(
"Bad function signature for '" + auth.type + "' challenge.set()"
"Bad function signature for '" + ch.type + "' challenge.set()"
);
}
});
@ -957,6 +967,17 @@ ACME._getCertificate = function(me, options) {
);
}
// a cheap check to see if there are non-ascii characters in any of the domains
var nonAsciiDomains = options.domains.some(function(d) {
// IDN / unicode / utf-8 / punycode
return Enc.strToBin(d) !== d;
});
if (nonAsciiDomains) {
throw new Error(
"please use the 'punycode' module to convert unicode domain names to punycode"
);
}
// It's just fine if there's no account, we'll go get the key id we need via the existing key
options._kid =
options._kid ||
@ -1034,7 +1055,10 @@ ACME._getCertificate = function(me, options) {
return 0;
})
.map(function(hostname) {
return { type: 'dns', value: hostname };
return {
type: 'dns',
value: hostname
};
})
//, "notBefore": "2016-01-01T00:00:00Z"
//, "notAfter": "2016-01-08T00:00:00Z"
@ -1164,14 +1188,15 @@ ACME._getCertificate = function(me, options) {
return;
}
if (!me._canUse[auth.type] || me.skipChallengeTest) {
var ch = auth.challenge;
if (!me._canUse[ch.type] || me.skipChallengeTest) {
// not so much "valid" as "not invalid"
// but in this case we can't confirm either way
validAuths.push(auth);
return checkNext();
}
return ACME.challengeTests[auth.type](me, auth)
return ACME.challengeTests[ch.type](me, auth)
.then(function() {
validAuths.push(auth);
})
@ -1272,10 +1297,7 @@ ACME._generateCsrWeb64 = function(me, options, validatedDomains) {
return Promise.resolve(csr);
}
return ACME._importKeypair(
me,
options.serverKeypair || options.domainKeypair
).then(function(pair) {
return ACME._importKeypair(me, options.serverKeypair).then(function(pair) {
return me.CSR.csr({
jwk: pair.private,
domains: validatedDomains,
@ -1302,8 +1324,8 @@ ACME.create = function create(me) {
//me.Keypairs = me.Keypairs || require('keypairs');
//me.request = me.request || require('@root/request');
if (!me.dns01) {
me.dns01 = function(auth) {
return ACME._dns01(me, auth);
me.dns01 = function(ch) {
return ACME._dns01(me, ch);
};
}
// backwards compat
@ -1311,8 +1333,8 @@ ACME.create = function create(me) {
me.dig = me.dns01;
}
if (!me.http01) {
me.http01 = function(auth) {
return ACME._http01(me, auth);
me.http01 = function(ch) {
return ACME._http01(me, ch);
};
}
@ -1505,9 +1527,9 @@ ACME._prnd = function(n) {
ACME._toHex = function(pair) {
return parseInt(pair, 10).toString(16);
};
ACME._dns01 = function(me, auth) {
ACME._dns01 = function(me, ch) {
return new me.request({
url: me._baseUrl + '/api/dns/' + auth.dnsHost + '?type=TXT'
url: me._baseUrl + '/api/dns/' + ch.dnsHost + '?type=TXT'
}).then(function(resp) {
var err;
if (!resp.body || !Array.isArray(resp.body.answer)) {
@ -1527,8 +1549,8 @@ ACME._dns01 = function(me, auth) {
};
});
};
ACME._http01 = function(me, auth) {
var url = encodeURIComponent(auth.challengeUrl);
ACME._http01 = function(me, ch) {
var url = encodeURIComponent(ch.challengeUrl);
return new me.request({
url: me._baseUrl + '/api/http?url=' + url
}).then(function(resp) {
@ -1537,20 +1559,27 @@ ACME._http01 = function(me, auth) {
};
ACME._removeChallenge = function(me, options, auth) {
var challengers = options.challenges || {};
var removeChallenge =
challengers[auth.type] && challengers[auth.type].remove;
var ch = auth.challenge;
var removeChallenge = challengers[ch.type] && challengers[ch.type].remove;
if (!removeChallenge) {
throw new Error('challenge plugin is missing remove()');
}
if (1 === removeChallenge.length) {
return Promise.resolve(removeChallenge(auth)).then(
function() {},
function() {}
function(e) {
console.error('Error during remove challenge:');
console.error(e);
}
);
} else if (2 === removeChallenge.length) {
return new Promise(function(resolve) {
removeChallenge(auth, function(err) {
resolve();
if (err) {
console.error('Error during remove challenge:');
console.error(err);
}
return err;
});
});
@ -1585,13 +1614,11 @@ ACME._getZones = function(me, presenter, dnsHosts) {
dnsHosts: dnsHosts,
request: me.request
};
// back/forwards-compat
challenge.challenge = challenge;
return ACME._wrapCb(
me,
presenter,
'zones',
challenge,
{ challenge: challenge },
'an array of zone names'
);
};

2
lib/asn1/packer.js

@ -1,7 +1,7 @@
'use strict';
var ASN1 = module.exports;
var Enc = require('omnibuffer');
var Enc = require('@root/encoding/hex');
//
// Packer

2
lib/asn1/parser.js

@ -5,7 +5,7 @@
'use strict';
var ASN1 = module.exports;
var Enc = require('omnibuffer');
var Enc = require('@root/encoding/hex');
//
// Parser

16
lib/csr.js

@ -5,12 +5,13 @@
'use strict';
/*global Promise*/
var Enc = require('@root/encoding');
var ASN1 = require('./asn1/packer.js'); // DER, actually
var Asn1 = ASN1.Any;
var BitStr = ASN1.BitStr;
var UInt = ASN1.UInt;
var Asn1Parser = require('./asn1/parser.js');
var Enc = require('omnibuffer');
var PEM = require('./pem.js');
var X509 = require('./x509.js');
var Keypairs = require('./keypairs');
@ -155,7 +156,8 @@ X509.packCsr = function(asn1pubkey, domains) {
Asn1(
'30',
Asn1('06', '550403'),
Asn1('0c', Enc.utf8ToHex(domains[0]))
// TODO utf8 => punycode
Asn1('0c', Enc.strToHex(domains[0]))
)
)
),
@ -184,7 +186,8 @@ X509.packCsr = function(asn1pubkey, domains) {
'30',
domains
.map(function(d) {
return Asn1('82', Enc.utf8ToHex(d));
// TODO utf8 => punycode
return Asn1('82', Enc.strToHex(d));
})
.join('')
)
@ -235,7 +238,6 @@ CSR._info = function(der) {
}
//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"
@ -243,7 +245,7 @@ CSR._info = function(der) {
}
// 0 null
// 1 commonName / subject
var sub = Enc.bufToBin(
var sub = Enc.bufToStr(
req.children[1].children[0].children[0].children[1].value
);
// 3 public key (type, key)
@ -305,8 +307,8 @@ CSR._info = function(der) {
return seq2.children[1].children[0].children.map(function(
name
) {
// TODO utf8
return Enc.bufToBin(name.value);
// TODO utf8 => punycode
return Enc.bufToStr(name.value);
});
})[0];
})[0];

3
lib/ecdsa.js

@ -1,6 +1,8 @@
/*global Promise*/
'use strict';
var Enc = require('@root/encoding');
var EC = module.exports;
var native = require('./node/ecdsa.js');
@ -10,7 +12,6 @@ var SSH;
var x509 = require('./x509.js');
var PEM = require('./pem.js');
//var SSH = require('./ssh-keys.js');
var Enc = require('omnibuffer');
var sha2 = require('./node/sha2.js');
// 1.2.840.10045.3.1.7

151
lib/encoding.js

@ -1,151 +0,0 @@
(function(exports) {
var Enc = (exports.Enc = {});
Enc.bufToBin = function(buf) {
var bin = '';
// cannot use .map() because Uint8Array would return only 0s
buf.forEach(function(ch) {
bin += String.fromCharCode(ch);
});
return bin;
};
Enc.bufToHex = function toHex(u8) {
var hex = [];
var i, h;
var len = u8.byteLength || u8.length;
for (i = 0; i < len; i += 1) {
h = u8[i].toString(16);
if (h.length % 2) {
h = '0' + h;
}
hex.push(h);
}
return hex.join('').toLowerCase();
};
Enc.urlBase64ToBase64 = function urlsafeBase64ToBase64(str) {
var r = str % 4;
if (2 === r) {
str += '==';
} else if (3 === r) {
str += '=';
}
return str.replace(/-/g, '+').replace(/_/g, '/');
};
Enc.base64ToBuf = function(b64) {
return Enc.binToBuf(atob(b64));
};
Enc.binToBuf = function(bin) {
var arr = bin.split('').map(function(ch) {
return ch.charCodeAt(0);
});
return 'undefined' !== typeof Uint8Array ? new Uint8Array(arr) : arr;
};
Enc.bufToHex = function(u8) {
var hex = [];
var i, h;
var len = u8.byteLength || u8.length;
for (i = 0; i < len; i += 1) {
h = u8[i].toString(16);
if (h.length % 2) {
h = '0' + h;
}
hex.push(h);
}
return hex.join('').toLowerCase();
};
Enc.numToHex = function(d) {
d = d.toString(16);
if (d.length % 2) {
return '0' + d;
}
return d;
};
Enc.bufToUrlBase64 = function(u8) {
return Enc.base64ToUrlBase64(Enc.bufToBase64(u8));
};
Enc.base64ToUrlBase64 = function(str) {
return str
.replace(/\+/g, '-')
.replace(/\//g, '_')
.replace(/=/g, '');
};
Enc.bufToBase64 = function(u8) {
var bin = '';
u8.forEach(function(i) {
bin += String.fromCharCode(i);
});
return btoa(bin);
};
Enc.hexToBuf = function(hex) {
var arr = [];
hex.match(/.{2}/g).forEach(function(h) {
arr.push(parseInt(h, 16));
});
return 'undefined' !== typeof Uint8Array ? new Uint8Array(arr) : arr;
};
Enc.numToHex = function(d) {
d = d.toString(16);
if (d.length % 2) {
return '0' + d;
}
return d;
};
//
// JWK to SSH (tested working)
//
Enc.base64ToHex = function(b64) {
var bin = atob(Enc.urlBase64ToBase64(b64));
return Enc.binToHex(bin);
};
Enc.binToHex = function(bin) {
return bin
.split('')
.map(function(ch) {
var h = ch.charCodeAt(0).toString(16);
if (h.length % 2) {
h = '0' + h;
}
return h;
})
.join('');
};
// TODO are there any nuance differences here?
Enc.utf8ToHex = Enc.binToHex;
Enc.hexToBase64 = function(hex) {
return btoa(Enc.hexToBin(hex));
};
Enc.hexToBin = function(hex) {
return hex
.match(/.{2}/g)
.map(function(h) {
return String.fromCharCode(parseInt(h, 16));
})
.join('');
};
Enc.urlBase64ToBase64 = function urlsafeBase64ToBase64(str) {
var r = str % 4;
if (2 === r) {
str += '==';
} else if (3 === r) {
str += '=';
}
return str.replace(/-/g, '+').replace(/_/g, '/');
};
})('undefined' !== typeof exports ? module.exports : window);

23
lib/keypairs.js

@ -1,11 +1,13 @@
/*global Promise*/
'use strict';
require('@root/encoding/bytes');
var Enc = require('@root/encoding/base64');
var Keypairs = module.exports;
var Rasha = require('./rsa.js');
var Eckles = require('./ecdsa.js');
var native = require('./node/keypairs.js');
var Enc = require('omnibuffer');
Keypairs._stance =
"We take the stance that if you're knowledgeable enough to" +
@ -224,7 +226,7 @@ Keypairs.signJws = function(opts) {
}
// Converting to a buffer, even if it was just converted to a string
if ('string' === typeof payload) {
payload = Enc.binToBuf(payload);
payload = Enc.strToBuf(payload);
}
var protected64 = Enc.strToUrlBase64(protectedHeader);
@ -311,20 +313,3 @@ function setTime(time) {
return now + mult * num;
}
Enc.hexToBuf = function(hex) {
var arr = [];
hex.match(/.{2}/g).forEach(function(h) {
arr.push(parseInt(h, 16));
});
return 'undefined' !== typeof Uint8Array ? new Uint8Array(arr) : arr;
};
Enc.strToUrlBase64 = function(str) {
return Enc.bufToUrlBase64(Enc.binToBuf(str));
};
Enc.binToBuf = function(bin) {
var arr = bin.split('').map(function(ch) {
return ch.charCodeAt(0);
});
return 'undefined' !== typeof Uint8Array ? new Uint8Array(arr) : arr;
};

2
lib/node/rsa.js

@ -118,7 +118,7 @@ function toJwks(oldpair) {
}
// TODO
var Enc = require('omnibuffer');
var Enc = require('@root/encoding/base64');
x509.parsePkcs1 = function parseRsaPkcs1(buf, asn1, jwk) {
if (
!asn1.children.every(function(el) {

2
lib/pem.js

@ -1,7 +1,7 @@
'use strict';
var Enc = require('@root/encoding/base64');
var PEM = module.exports;
var Enc = require('omnibuffer');
PEM.packBlock = function(opts) {
// TODO allow for headers?

2
lib/rsa.js

@ -7,7 +7,7 @@ var x509 = require('./x509.js');
var PEM = require('./pem.js');
//var SSH = require('./ssh-keys.js');
var sha2 = require('./node/sha2.js');
var Enc = require('omnibuffer');
var Enc = require('@root/encoding/base64');
RSA._universal =
'Bluecrypt only supports crypto with standard cross-browser and cross-platform support.';

2
lib/x509.js

@ -5,7 +5,7 @@ var ASN1 = require('./asn1/packer.js');
var Asn1 = ASN1.Any;
var UInt = ASN1.UInt;
var BitStr = ASN1.BitStr;
var Enc = require('omnibuffer');
var Enc = require('@root/encoding');
// 1.2.840.10045.3.1.7
// prime256v1 (ANSI X9.62 named elliptic curve)

421
package-lock.json

@ -4,28 +4,17 @@
"lockfileVersion": 1,
"requires": true,
"dependencies": {
"@root/encoding": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@root/encoding/-/encoding-1.0.1.tgz",
"integrity": "sha512-OaEub02ufoU038gy6bsNHQOjIn8nUjGiLcaRmJ40IUykneJkIW5fxDqKxQx48cszuNflYldsJLPPXCrGfHs8yQ=="
},
"@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",
@ -38,24 +27,6 @@
"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",
@ -66,12 +37,6 @@
"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",
@ -82,63 +47,12 @@
"glob": "^7.1.1"
}
},
"commander": {
"version": "2.20.1",
"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.1.tgz",
"integrity": "sha512-cCuLsMhJeWQ/ZpsFTbE765kvVfoeSddc4nU3up4fV+fDBcfUXnbITJ+JzhkdjzOqhURjZgujxaioam4RM9yGUg==",
"dev": true
},
"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",
@ -177,101 +91,12 @@
"integrity": "sha512-GUE3gqcDCaMltj2++g6bRQ5rBJWtkWTmqmD0fo1RnnMuUqHNCt2oTPeDnS9n6fKYvlhn7AeBkb38lymBtWBQdA==",
"dev": true
},
"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",
@ -297,27 +122,6 @@
"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",
@ -334,51 +138,6 @@
"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",
@ -388,27 +147,6 @@
"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",
@ -418,157 +156,16 @@
"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
},
"source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
"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"
}
},
"uglify-js": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.6.0.tgz",
"integrity": "sha512-W+jrUHJr3DXKhrsS7NUVxn3zqMOFn0hL/Ei6v0anCIMoKC93TjcflTagwIHLW7SfMFfiQuktQyFVCFHGUE0+yg==",
"dev": true,
"requires": {
"commander": "~2.20.0",
"source-map": "~0.6.1"
}
},
"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=",
"punycode": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz",
"integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=",
"dev": true
},
"wrappy": {

8
package.json

@ -19,7 +19,7 @@
"dist"
],
"scripts": {
"build": "node bin/bundle.js",
"build": "nodex bin/bundle.js",
"lint": "jshint lib bin",
"test": "node server.js",
"start": "node server.js"
@ -41,12 +41,14 @@
],
"author": "AJ ONeal <coolaj86@gmail.com> (https://coolaj86.com/)",
"license": "MPL-2.0",
"dependencies": {
"@root/encoding": "^1.0.1"
},
"devDependencies": {
"@root/request": "^1.3.10",
"dig.js": "^1.3.9",
"dns-suite": "^1.2.12",
"dotenv": "^8.1.0",
"express": "^4.16.4",
"uglify-js": "^3.6.0"
"punycode": "^1.4.1"
}
}

139
server.js

@ -1,139 +0,0 @@
'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'");
}

44
tests/index.js

@ -2,6 +2,7 @@
require('dotenv').config();
var punycode = require('punycode');
var ACME = require('../');
var Keypairs = require('../lib/keypairs.js');
var acme = ACME.create({
@ -73,7 +74,7 @@ async function happyPath(accKty, srvKty, rnd) {
if (config.debug) {
console.info('ACME.js initialized');
console.info(metadata);
console.info('');
console.info();
console.info();
}
@ -81,7 +82,7 @@ async function happyPath(accKty, srvKty, rnd) {
if (config.debug) {
console.info('Account Key Created');
console.info(JSON.stringify(accountKeypair, null, 2));
console.info('');
console.info();
console.info();
}
@ -96,7 +97,7 @@ async function happyPath(accKty, srvKty, rnd) {
if (config.debug) {
console.info('Agreeing to Terms of Service:');
console.info(tos);
console.info('');
console.info();
console.info();
}
agreed = true;
@ -123,7 +124,18 @@ async function happyPath(accKty, srvKty, rnd) {
var domains = randomDomains(rnd);
if (config.debug) {
console.info('Get certificates for random domains:');
console.info(domains);
console.info(
domains
.map(function(puny) {
var uni = punycode.toUnicode(puny);
if (puny !== uni) {
return puny + ' (' + uni + ')';
}
return puny;
})
.join('\n')
);
console.info();
}
var results = await acme.certificates.create({
account: account,
@ -140,8 +152,8 @@ async function happyPath(accKty, srvKty, rnd) {
console.info(results.expires);
console.info(results.cert);
console.info(results.chain);
console.info('');
console.info('');
console.info();
console.info();
}
}
@ -163,18 +175,20 @@ happyPath('EC', 'RSA', rnd)
function randomDomains(rnd) {
return ['foo-acmejs', 'bar-acmejs', '*.baz-acmejs', 'baz-acmejs'].map(
function(pre) {
return pre + '-' + rnd + '.' + config.domain;
return punycode.toASCII(pre + '-' + rnd + '.' + config.domain);
}
);
}
function random() {
return parseInt(
Math.random()
.toString()
.slice(2, 99),
10
)
.toString(16)
.slice(0, 4);
return (
parseInt(
Math.random()
.toString()
.slice(2, 99),
10
)
.toString(16)
.slice(0, 4) + '例'
);
}

18
webpack.config.js

@ -0,0 +1,18 @@
'use strict';
var path = require('path');
module.exports = {
entry: './lib/acme.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'acme.js',
library: 'acme',
libraryTarget: 'umd',
globalObject: "typeof self !== 'undefined' ? self : this"
},
resolve: {
aliasFields: ['webpack', 'browser'],
mainFields: ['browser']
}
};
Loading…
Cancel
Save