testing working

This commit is contained in:
AJ ONeal 2019-10-08 04:33:14 -06:00
parent 96b491a9c0
commit 2b0fce0869
20 changed files with 466 additions and 874 deletions

152
README.md
View File

@ -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. Lightweight. Fast. Modern Crypto. Zero dependecies.
(a port of [acme.js](https://git.coolaj86.com/coolaj86/acme-v2.js) to the browser)
# Features # Features
| 15k gzipped | 55k minified | 88k (2,500 loc) source with comments | | 15k gzipped | 55k minified | 88k (2,500 loc) source with comments |
* [x] Let's Encrypt - [x] Let's Encrypt v2.1+ (November 2019)
* [x] ACME draft 15 (supports POST-as-GET) - [x] ACME draft 15 (supports POST-as-GET)
* [x] Secure support for EC and RSA for account and server keys - [x] Secure support for EC and RSA for account and server keys
* [x] Simple and lightweight PEM, DER, ASN1, X509, and CSR implementations - [x] Simple and lightweight PEM, DER, ASN1, X509, and CSR implementations
* [x] VanillaJS, Zero Dependencies - [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 # Online Demos
* Greenlock for the Web <https://greenlock.domains> - Greenlock for the Web <https://greenlock.domains>
* Bluecrypt ACME Demo <https://rootprojects.org/acme/> - ACME.js Demo <https://rootprojects.org/acme/>
We expect that our hosted versions will meet all of yours needs. 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. 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 # 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) and [CSR.js](https://git.rootprojects.org/root/bluecrypt-csr.js)
`bluecrypt-acme.js` ## Node.js
```html
<script src="https://rootprojects.org/acme/bluecrypt-acme.js"></script> ```js
var ACME = require('@root/acme');
``` ```
`bluecrypt-acme.min.js` ## WebPack
```html
<script src="https://rootprojects.org/acme/bluecrypt-acme.min.js"></script> ```js
var ACME = require('@root/acme');
``` ```
You can see `index.html` and `app.js` in the repo for full example usage. ## Vanilla JS
### Instantiate Bluecrypt ACME ```js
var ACME = window.ACME;
```
Although built for Let's Encrypt, Bluecrypt ACME will work with any server `acme.js`
```html
<script src="https://unpkg.com/@root/acme/dist/acme.js"></script>
```
`acme.min.js`
```html
<script src="https://unpkg.com/@root/acme/dist/acme.min.js"></script>
```
## Examples
You can see `tests/index.js`, `examples/index.html`, `examples/app.js` in the repo for full example usage.
### 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). 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. The `init()` method takes a _directory url_ and initializes internal state according to its response.
```js ```js
var acme = ACME.create({}); var acme = ACME.create({});
acme.init('https://acme-staging-v02.api.letsencrypt.org/directory').then(function () { acme.init('https://acme-staging-v02.api.letsencrypt.org/directory').then(
function() {
// Ready to use, show page // Ready to use, show page
$('body').hidden = false; $('body').hidden = false;
}); }
);
``` ```
### Create ACME Account with Let's Encrypt ### Create ACME Account with Let's Encrypt
@ -72,15 +107,21 @@ var account;
Keypairs.generate({ kty: 'EC' }).then(function(pair) { Keypairs.generate({ kty: 'EC' }).then(function(pair) {
accountPrivateKey = pair.private; accountPrivateKey = pair.private;
return acme.accounts.create({ return acme.accounts
.create({
agreeToTerms: function(tos) { agreeToTerms: function(tos) {
if (window.confirm("Do you agree to the Bluecrypt and Let's Encrypt Terms of Service?")) { if (
window.confirm(
"Do you agree to the ACME.js and Let's Encrypt Terms of Service?"
)
) {
return Promise.resolve(tos); return Promise.resolve(tos);
} }
} },
, accountKeypair: { privateKeyJwk: pair.private } accountKeypair: { privateKeyJwk: pair.private },
, email: $('.js-email-input').value email: $('.js-email-input').value
}).then(function (_account) { })
.then(function(_account) {
account = _account; account = _account;
}); });
}); });
@ -100,23 +141,24 @@ var serverPrivateKey;
Keypairs.generate({ kty: 'EC' }).then(function(pair) { Keypairs.generate({ kty: 'EC' }).then(function(pair) {
serverPrivateKey = pair.private; serverPrivateKey = pair.private;
return acme.certificates.create({ return acme.certificates
.create({
agreeToTerms: function(tos) { agreeToTerms: function(tos) {
return tos; return tos;
} },
, account: account account: account,
, accountKeypair: { privateKeyJwk: accountPrivateKey } accountKeypair: { privateKeyJwk: accountPrivateKey },
, serverKeypair: { privateKeyJwk: serverPrivateKey } serverKeypair: { privateKeyJwk: serverPrivateKey },
, domains: ['example.com','www.example.com'] domains: ['example.com', 'www.example.com'],
, challenges: challenges // must be implemented challenges: challenges, // must be implemented
, skipDryRun: true skipDryRun: true
}).then(function (results) { })
.then(function(results) {
console.log('Got SSL Certificate:'); console.log('Got SSL Certificate:');
console.log(results.expires); console.log(results.expires);
console.log(results.cert); console.log(results.cert);
console.log(results.chain); console.log(results.chain);
}); });
}); });
``` ```
@ -136,10 +178,12 @@ var challenges = {
console.info('http-01 set challenge:'); console.info('http-01 set challenge:');
console.info(opts.challengeUrl); console.info(opts.challengeUrl);
console.info(opts.keyAuthorization); console.info(opts.keyAuthorization);
while (!window.confirm("Upload the challenge file before continuing.")) {} while (
!window.confirm('Upload the challenge file before continuing.')
) {}
return Promise.resolve(); return Promise.resolve();
} },
, remove: function (opts) { remove: function(opts) {
console.log('http-01 remove challenge:', opts.challengeUrl); console.log('http-01 remove challenge:', opts.challengeUrl);
return Promise.resolve(); return Promise.resolve();
} }
@ -147,6 +191,24 @@ var challenges = {
}; };
``` ```
# 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 # Full Documentation
See [acme.js](https://git.coolaj86.com/coolaj86/acme-v2.js). 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 # 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: 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, 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. 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 | MPL-2.0 |
[Terms of Use](https://therootcompany.com/legal/#terms) | [Terms of Use](https://therootcompany.com/legal/#terms) |
[Privacy Policy](https://therootcompany.com/legal/#privacy) [Privacy Policy](https://therootcompany.com/legal/#privacy)

174
examples/server.js Normal file
View File

@ -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'"
);
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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);

View File

@ -1,11 +1,13 @@
/*global Promise*/ /*global Promise*/
'use strict'; 'use strict';
require('@root/encoding/bytes');
var Enc = require('@root/encoding/base64');
var Keypairs = module.exports; var Keypairs = module.exports;
var Rasha = require('./rsa.js'); var Rasha = require('./rsa.js');
var Eckles = require('./ecdsa.js'); var Eckles = require('./ecdsa.js');
var native = require('./node/keypairs.js'); var native = require('./node/keypairs.js');
var Enc = require('omnibuffer');
Keypairs._stance = Keypairs._stance =
"We take the stance that if you're knowledgeable enough to" + "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 // Converting to a buffer, even if it was just converted to a string
if ('string' === typeof payload) { if ('string' === typeof payload) {
payload = Enc.binToBuf(payload); payload = Enc.strToBuf(payload);
} }
var protected64 = Enc.strToUrlBase64(protectedHeader); var protected64 = Enc.strToUrlBase64(protectedHeader);
@ -311,20 +313,3 @@ function setTime(time) {
return now + mult * num; 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;
};

View File

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

View File

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

View File

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

View File

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

421
package-lock.json generated
View File

@ -4,28 +4,17 @@
"lockfileVersion": 1, "lockfileVersion": 1,
"requires": true, "requires": true,
"dependencies": { "dependencies": {
"@root/encoding": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@root/encoding/-/encoding-1.0.1.tgz",
"integrity": "sha512-OaEub02ufoU038gy6bsNHQOjIn8nUjGiLcaRmJ40IUykneJkIW5fxDqKxQx48cszuNflYldsJLPPXCrGfHs8yQ=="
},
"@root/request": { "@root/request": {
"version": "1.3.10", "version": "1.3.10",
"resolved": "https://registry.npmjs.org/@root/request/-/request-1.3.10.tgz", "resolved": "https://registry.npmjs.org/@root/request/-/request-1.3.10.tgz",
"integrity": "sha512-GSn8dfsGp0juJyXS9k7B/DjYm7Axe85wiCHfPs30eQ+/V6p2aqey45e1czb3ZwP+iPmzWCKXahhWnZhSDIil6w==", "integrity": "sha512-GSn8dfsGp0juJyXS9k7B/DjYm7Axe85wiCHfPs30eQ+/V6p2aqey45e1czb3ZwP+iPmzWCKXahhWnZhSDIil6w==",
"dev": true "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": { "balanced-match": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
@ -38,24 +27,6 @@
"integrity": "sha512-FG+nFEZChJrbQ9tIccIfZJBz3J7mLrAhxakAbnrJWn8d7aKOC+LWifa0G+p4ZqKp4y13T7juYvdhq9NzKdsrjw==", "integrity": "sha512-FG+nFEZChJrbQ9tIccIfZJBz3J7mLrAhxakAbnrJWn8d7aKOC+LWifa0G+p4ZqKp4y13T7juYvdhq9NzKdsrjw==",
"dev": true "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": { "brace-expansion": {
"version": "1.1.11", "version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
@ -66,12 +37,6 @@
"concat-map": "0.0.1" "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": { "cli": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/cli/-/cli-1.0.1.tgz", "resolved": "https://registry.npmjs.org/cli/-/cli-1.0.1.tgz",
@ -82,63 +47,12 @@
"glob": "^7.1.1" "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": { "concat-map": {
"version": "0.0.1", "version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
"integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
"dev": true "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": { "dig.js": {
"version": "1.3.9", "version": "1.3.9",
"resolved": "https://registry.npmjs.org/dig.js/-/dig.js-1.3.9.tgz", "resolved": "https://registry.npmjs.org/dig.js/-/dig.js-1.3.9.tgz",
@ -177,101 +91,12 @@
"integrity": "sha512-GUE3gqcDCaMltj2++g6bRQ5rBJWtkWTmqmD0fo1RnnMuUqHNCt2oTPeDnS9n6fKYvlhn7AeBkb38lymBtWBQdA==", "integrity": "sha512-GUE3gqcDCaMltj2++g6bRQ5rBJWtkWTmqmD0fo1RnnMuUqHNCt2oTPeDnS9n6fKYvlhn7AeBkb38lymBtWBQdA==",
"dev": true "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": { "exit": {
"version": "0.1.2", "version": "0.1.2",
"resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz",
"integrity": "sha1-BjJjj42HfMghB9MKD/8aF8uhzQw=", "integrity": "sha1-BjJjj42HfMghB9MKD/8aF8uhzQw=",
"dev": true "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": { "fs.realpath": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", "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", "from": "git+https://git.coolaj86.com/coolaj86/hexdump.js#v1.0.4",
"dev": true "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": { "inflight": {
"version": "1.0.6", "version": "1.0.6",
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
@ -334,51 +138,6 @@
"integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=",
"dev": true "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": { "minimatch": {
"version": "3.0.4", "version": "3.0.4",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
@ -388,27 +147,6 @@
"brace-expansion": "^1.1.7" "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": { "once": {
"version": "1.4.0", "version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
@ -418,157 +156,16 @@
"wrappy": "1" "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": { "path-is-absolute": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
"integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=",
"dev": true "dev": true
}, },
"path-to-regexp": { "punycode": {
"version": "0.1.7", "version": "1.4.1",
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz",
"integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=", "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=",
"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=",
"dev": true "dev": true
}, },
"wrappy": { "wrappy": {

View File

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

139
server.js
View File

@ -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'");
}

View File

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

18
webpack.config.js Normal file
View File

@ -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']
}
};