diff --git a/app.js b/app.js index 3531ee2..03c7bf2 100644 --- a/app.js +++ b/app.js @@ -1,4 +1,3 @@ -/*global Promise*/ (function () { 'use strict'; @@ -6,7 +5,6 @@ var Rasha = window.Rasha; var Eckles = window.Eckles; var x509 = window.x509; - var ACME = window.ACME; function $(sel) { return document.querySelector(sel); @@ -104,69 +102,6 @@ }); }); - $('form.js-acme-account').addEventListener('submit', function (ev) { - ev.preventDefault(); - ev.stopPropagation(); - $('.js-loading').hidden = false; - var acme = ACME.create({ - Keypairs: Keypairs - }); - acme.init('https://acme-staging-v02.api.letsencrypt.org/directory').then(function (result) { - console.log('acme result', result); - var privJwk = JSON.parse($('.js-jwk').innerText).private; - var email = $('.js-email').innerText; - function checkTos(tos) { - console.log("TODO checkbox for agree to terms"); - return tos; - } - return acme.accounts.create({ - email: email - , agreeToTerms: checkTos - , accountKeypair: { privateKeyJwk: privJwk } - }).then(function (account) { - console.log("account created result:", account); - return Keypairs.generate({ - kty: 'RSA' - , modulusLength: 2048 - }).then(function (pair) { - console.log('domain keypair:', pair); - var domains = ($('.js-domains').innerText||'example.com').split(/[, ]+/g); - return acme.certificates.create({ - accountKeypair: { privateKeyJwk: privJwk } - , account: account - , domainKeypair: { privateKeyJwk: pair.private } - , email: email - , domains: domains - , agreeToTerms: checkTos - , challenges: { - 'dns-01': { - set: function (opts) { - console.log('dns-01 set challenge:'); - console.log(JSON.stringify(opts, null, 2)); - return new Promise(function (resolve) { - while (!window.confirm("Did you set the challenge?")) {} - resolve(); - }); - } - , remove: function (opts) { - console.log('dns-01 remove challenge:'); - console.log(JSON.stringify(opts, null, 2)); - return new Promise(function (resolve) { - while (!window.confirm("Did you delete the challenge?")) {} - resolve(); - }); - } - } - } - }); - }); - }).catch(function (err) { - console.error("A bad thing happened:"); - console.error(err); - window.alert(err.message || JSON.stringify(err, null, 2)); - }); - }); - }); $('.js-generate').hidden = false; $('.js-create-account').hidden = false; diff --git a/index.html b/index.html index b4d91c8..4cfdb8a 100644 --- a/index.html +++ b/index.html @@ -59,17 +59,6 @@ -

ACME Account

-
- - -
- - -
- -
- - - + diff --git a/lib/acme.js b/lib/acme.js deleted file mode 100644 index d5b3438..0000000 --- a/lib/acme.js +++ /dev/null @@ -1,1017 +0,0 @@ -// Copyright 2018-present AJ ONeal. All rights reserved -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -(function (exports) { -'use strict'; -/* globals Promise */ - -var ACME = exports.ACME = {}; -//var Keypairs = exports.Keypairs || {}; -var Enc = exports.Enc || {}; -var Crypto = exports.Crypto || {}; - -ACME.formatPemChain = function formatPemChain(str) { - return str.trim().replace(/[\r\n]+/g, '\n').replace(/\-\n\-/g, '-\n\n-') + '\n'; -}; -ACME.splitPemChain = function splitPemChain(str) { - return str.trim().split(/[\r\n]{2,}/g).map(function (str) { - return str + '\n'; - }); -}; - - -// http-01: GET https://example.org/.well-known/acme-challenge/{{token}} => {{keyAuth}} -// dns-01: TXT _acme-challenge.example.org. => "{{urlSafeBase64(sha256(keyAuth))}}" -ACME.challengePrefixes = { - 'http-01': '/.well-known/acme-challenge' -, 'dns-01': '_acme-challenge' -}; -ACME.challengeTests = { - 'http-01': function (me, auth) { - var url = 'http://' + auth.hostname + ACME.challengePrefixes['http-01'] + '/' + auth.token; - return me.request({ method: 'GET', url: url }).then(function (resp) { - var err; - - // TODO limit the number of bytes that are allowed to be downloaded - if (auth.keyAuthorization === resp.body.toString('utf8').trim()) { - return true; - } - - err = new Error( - "Error: Failed HTTP-01 Pre-Flight / Dry Run.\n" - + "curl '" + url + "'\n" - + "Expected: '" + auth.keyAuthorization + "'\n" - + "Got: '" + resp.body + "'\n" - + "See https://git.coolaj86.com/coolaj86/acme-v2.js/issues/4" - ); - err.code = 'E_FAIL_DRY_CHALLENGE'; - return Promise.reject(err); - }); - } -, 'dns-01': function (me, auth) { - // remove leading *. on wildcard domains - return me.dig({ - type: 'TXT' - , name: auth.dnsHost - }).then(function (ans) { - var err; - - if (ans.answer.some(function (txt) { - return auth.dnsAuthorization === txt.data[0]; - })) { - return true; - } - - err = new Error( - "Error: Failed DNS-01 Pre-Flight Dry Run.\n" - + "dig TXT '" + auth.dnsHost + "' does not return '" + auth.dnsAuthorization + "'\n" - + "See https://git.coolaj86.com/coolaj86/acme-v2.js/issues/4" - ); - err.code = 'E_FAIL_DRY_CHALLENGE'; - return Promise.reject(err); - }); - } -}; - -ACME._directory = function (me) { - // GET-as-GET ok - return me.request({ method: 'GET', url: me.directoryUrl, json: true }); -}; -ACME._getNonce = function (me) { - // GET-as-GET, HEAD-as-HEAD ok - var nonce; - while (true) { - nonce = me._nonces.shift(); - if (!nonce) { break; } - if (Date.now() - nonce.createdAt > (15 * 60 * 1000)) { - nonce = null; - } else { - break; - } - } - if (nonce) { return Promise.resolve(nonce.nonce); } - return me.request({ method: 'HEAD', url: me._directoryUrls.newNonce }).then(function (resp) { - return resp.headers['replay-nonce']; - }); -}; -ACME._setNonce = function (me, nonce) { - me._nonces.unshift({ nonce: nonce, createdAt: Date.now() }); -}; -// ACME RFC Section 7.3 Account Creation -/* - { - "protected": base64url({ - "alg": "ES256", - "jwk": {...}, - "nonce": "6S8IqOGY7eL2lsGoTZYifg", - "url": "https://example.com/acme/new-account" - }), - "payload": base64url({ - "termsOfServiceAgreed": true, - "onlyReturnExisting": false, - "contact": [ - "mailto:cert-admin@example.com", - "mailto:admin@example.com" - ] - }), - "signature": "RZPOnYoPs1PhjszF...-nh6X1qtOFPB519I" - } -*/ -ACME._registerAccount = function (me, options) { - if (me.debug) { console.debug('[acme-v2] accounts.create'); } - - return new Promise(function (resolve, reject) { - - function agree(tosUrl) { - var err; - if (me._tos !== tosUrl) { - err = new Error("You must agree to the ToS at '" + me._tos + "'"); - err.code = "E_AGREE_TOS"; - reject(err); - return; - } - - return ACME._importKeypair(me, options.accountKeypair).then(function (pair) { - var contact; - if (options.contact) { - contact = options.contact.slice(0); - } else if (options.email) { - contact = [ 'mailto:' + options.email ]; - } - var body = { - termsOfServiceAgreed: tosUrl === me._tos - , onlyReturnExisting: false - , contact: contact - }; - var pExt; - if (options.externalAccount) { - pExt = me.Keypairs.signJws({ - // TODO is HMAC the standard, or is this arbitrary? - secret: options.externalAccount.secret - , protected: { - alg: options.externalAccount.alg || "HS256" - , kid: options.externalAccount.id - , url: me._directoryUrls.newAccount - } - , payload: Enc.strToBuf(JSON.stringify(pair.public)) - }).then(function (jws) { - body.externalAccountBinding = jws; - return body; - }); - } else { - pExt = Promise.resolve(body); - } - return pExt.then(function (body) { - var payload = JSON.stringify(body); - return ACME._jwsRequest(me, { - options: options - , url: me._directoryUrls.newAccount - , protected: { kid: false, jwk: pair.public } - , payload: Enc.binToBuf(payload) - }).then(function (resp) { - var account = resp.body; - - if (2 !== Math.floor(resp.statusCode / 100)) { - throw new Error('account error: ' + JSON.stringify(resp.body)); - } - - var location = resp.headers.location; - // the account id url - options._kid = location; - if (me.debug) { console.debug('[DEBUG] new account location:'); } - if (me.debug) { console.debug(location); } - if (me.debug) { console.debug(resp); } - - /* - { - contact: ["mailto:jon@example.com"], - orders: "https://some-url", - status: 'valid' - } - */ - if (!account) { account = { _emptyResponse: true }; } - // https://git.coolaj86.com/coolaj86/acme-v2.js/issues/8 - if (!account.key) { account.key = {}; } - account.key.kid = options._kid; - return account; - }).then(resolve, reject); - }); - }); - } - - if (me.debug) { console.debug('[acme-v2] agreeToTerms'); } - if (1 === options.agreeToTerms.length) { - // newer promise API - return Promise.resolve(options.agreeToTerms(me._tos)).then(agree, reject); - } - else if (2 === options.agreeToTerms.length) { - // backwards compat cb API - return options.agreeToTerms(me._tos, function (err, tosUrl) { - if (!err) { agree(tosUrl); return; } - reject(err); - }); - } - else { - reject(new Error('agreeToTerms has incorrect function signature.' - + ' Should be fn(tos) { return Promise; }')); - } - }); -}; -/* - POST /acme/new-order HTTP/1.1 - Host: example.com - Content-Type: application/jose+json - - { - "protected": base64url({ - "alg": "ES256", - "kid": "https://example.com/acme/acct/1", - "nonce": "5XJ1L3lEkMG7tR6pA00clA", - "url": "https://example.com/acme/new-order" - }), - "payload": base64url({ - "identifiers": [{"type:"dns","value":"example.com"}], - "notBefore": "2016-01-01T00:00:00Z", - "notAfter": "2016-01-08T00:00:00Z" - }), - "signature": "H6ZXtGjTZyUnPeKn...wEA4TklBdh3e454g" - } -*/ -ACME._getChallenges = function (me, options, authUrl) { - if (me.debug) { console.debug('\n[DEBUG] getChallenges\n'); } - // TODO POST-as-GET - - return ACME._jwsRequest(me, { - options: options - , protected: {} - , payload: '' - , url: authUrl - }).then(function (resp) { - return resp.body; - }); -}; -ACME._wait = function wait(ms) { - return new Promise(function (resolve) { - setTimeout(resolve, (ms || 1100)); - }); -}; - -ACME._testChallengeOptions = function () { - var chToken = ACME._prnd(16); - return [ - { - "type": "http-01", - "status": "pending", - "url": "https://acme-staging-v02.example.com/0", - "token": "test-" + chToken + "-0" - } - , { - "type": "dns-01", - "status": "pending", - "url": "https://acme-staging-v02.example.com/1", - "token": "test-" + chToken + "-1", - "_wildcard": true - } - , { - "type": "tls-sni-01", - "status": "pending", - "url": "https://acme-staging-v02.example.com/2", - "token": "test-" + chToken + "-2" - } - , { - "type": "tls-alpn-01", - "status": "pending", - "url": "https://acme-staging-v02.example.com/3", - "token": "test-" + chToken + "-3" - } - ]; -}; -ACME._testChallenges = function (me, options) { - if (me.skipChallengeTest) { - return Promise.resolve(); - } - - var CHECK_DELAY = 0; - return Promise.all(options.domains.map(function (identifierValue) { - // TODO we really only need one to pass, not all to pass - var challenges = ACME._testChallengeOptions(); - if (identifierValue.includes("*")) { - challenges = challenges.filter(function (ch) { return ch._wildcard; }); - } - - var challenge = ACME._chooseChallenge(options, { challenges: challenges }); - if (!challenge) { - // For example, wildcards require dns-01 and, if we don't have that, we have to bail - var enabled = options.challengeTypes.join(', ') || 'none'; - var suitable = challenges.map(function (r) { return r.type; }).join(', ') || 'none'; - return Promise.reject(new Error( - "None of the challenge types that you've enabled ( " + enabled + " )" - + " are suitable for validating the domain you've selected (" + identifierValue + ")." - + " You must enable one of ( " + suitable + " )." - )); - } - if ('dns-01' === challenge.type) { - // Give the nameservers a moment to propagate - CHECK_DELAY = 1.5 * 1000; - } - - return Promise.resolve().then(function () { - var results = { - identifier: { - type: "dns" - , value: identifierValue.replace(/^\*\./, '') - } - , challenges: [ challenge ] - , expires: new Date(Date.now() + (60 * 1000)).toISOString() - , wildcard: identifierValue.includes('*.') || undefined - }; - var dryrun = true; - return ACME._challengeToAuth(me, options, results, challenge, dryrun).then(function (auth) { - return ACME._setChallenge(me, options, auth).then(function () { - return auth; - }); - }); - }); - })).then(function (auths) { - return ACME._wait(CHECK_DELAY).then(function () { - return Promise.all(auths.map(function (auth) { - return ACME.challengeTests[auth.type](me, auth); - })); - }); - }); -}; -ACME._chooseChallenge = function(options, results) { - // For each of the challenge types that we support - var challenge; - options.challengeTypes.some(function (chType) { - // And for each of the challenge types that are allowed - return results.challenges.some(function (ch) { - // Check to see if there are any matches - if (ch.type === chType) { - challenge = ch; - return true; - } - }); - }); - - return challenge; -}; -ACME._challengeToAuth = function (me, options, request, challenge, dryrun) { - // we don't poison the dns cache with our dummy request - var dnsPrefix = ACME.challengePrefixes['dns-01']; - if (dryrun) { - dnsPrefix = dnsPrefix.replace('acme-challenge', 'greenlock-dryrun-' + ACME._prnd(4)); - } - - var auth = {}; - - // straight copy from the new order response - // { identifier, status, expires, challenges, wildcard } - Object.keys(request).forEach(function (key) { - auth[key] = request[key]; - }); - - // copy from the challenge we've chosen - // { type, status, url, token } - // (note the duplicate status overwrites the one above, but they should be the same) - Object.keys(challenge).forEach(function (key) { - // don't confused devs with the id url - auth[key] = challenge[key]; - }); - - // batteries-included helpers - auth.hostname = auth.identifier.value; - // because I'm not 100% clear if the wildcard identifier does or doesn't have the leading *. in all cases - auth.altname = ACME._untame(auth.identifier.value, auth.wildcard); - return ACME._importKeypair(me, options.accountKeypair).then(function (pair) { - return me.Keypairs.thumbprint({ jwk: pair.public }).then(function (thumb) { - auth.thumbprint = thumb; - // keyAuthorization = token || '.' || base64url(JWK_Thumbprint(accountKey)) - auth.keyAuthorization = challenge.token + '.' + auth.thumbprint; - // conflicts with ACME challenge id url is already in use, so we call this challengeUrl instead - auth.challengeUrl = 'http://' + auth.identifier.value + ACME.challengePrefixes['http-01'] + '/' + auth.token; - auth.dnsHost = dnsPrefix + '.' + auth.hostname.replace('*.', ''); - - return Crypto._sha('sha256', auth.keyAuthorization).then(function (hash) { - auth.dnsAuthorization = hash; - return auth; - }); - }); - }); -}; - -ACME._untame = function (name, wild) { - if (wild) { name = '*.' + name.replace('*.', ''); } - return name; -}; - -// https://tools.ietf.org/html/draft-ietf-acme-acme-10#section-7.5.1 -ACME._postChallenge = function (me, options, auth) { - var RETRY_INTERVAL = me.retryInterval || 1000; - var DEAUTH_INTERVAL = me.deauthWait || 10 * 1000; - var MAX_POLL = me.retryPoll || 8; - var MAX_PEND = me.retryPending || 4; - var count = 0; - - var altname = ACME._untame(auth.identifier.value, auth.wildcard); - - /* - POST /acme/authz/1234 HTTP/1.1 - Host: example.com - Content-Type: application/jose+json - - { - "protected": base64url({ - "alg": "ES256", - "kid": "https://example.com/acme/acct/1", - "nonce": "xWCM9lGbIyCgue8di6ueWQ", - "url": "https://example.com/acme/authz/1234" - }), - "payload": base64url({ - "status": "deactivated" - }), - "signature": "srX9Ji7Le9bjszhu...WTFdtujObzMtZcx4" - } - */ - function deactivate() { - if (me.debug) { console.debug('[acme-v2.js] deactivate:'); } - return ACME._jwsRequest({ - options: options - , url: auth.url - , protected: { kid: options._kid } - , payload: Enc.strToBuf(JSON.stringify({ "status": "deactivated" })) - }).then(function (resp) { - if (me.debug) { console.debug('deactivate challenge: resp.body:'); } - if (me.debug) { console.debug(resp.body); } - return ACME._wait(DEAUTH_INTERVAL); - }); - } - - function pollStatus() { - if (count >= MAX_POLL) { - return Promise.reject(new Error( - "[acme-v2] stuck in bad pending/processing state for '" + altname + "'" - )); - } - - count += 1; - - if (me.debug) { console.debug('\n[DEBUG] statusChallenge\n'); } - // TODO POST-as-GET - return me.request({ method: 'GET', url: auth.url, json: true }).then(function (resp) { - if ('processing' === resp.body.status) { - if (me.debug) { console.debug('poll: again'); } - return ACME._wait(RETRY_INTERVAL).then(pollStatus); - } - - // This state should never occur - if ('pending' === resp.body.status) { - if (count >= MAX_PEND) { - return ACME._wait(RETRY_INTERVAL).then(deactivate).then(respondToChallenge); - } - if (me.debug) { console.debug('poll: again'); } - return ACME._wait(RETRY_INTERVAL).then(respondToChallenge); - } - - if ('valid' === resp.body.status) { - if (me.debug) { console.debug('poll: valid'); } - - try { - if (1 === options.removeChallenge.length) { - options.removeChallenge(auth).then(function () {}, function () {}); - } else if (2 === options.removeChallenge.length) { - options.removeChallenge(auth, function (err) { return err; }); - } else { - if (!ACME._removeChallengeWarn) { - console.warn("Please update to acme-v2 removeChallenge(options) or removeChallenge(options, cb)."); - console.warn("The API has been changed for compatibility with all ACME / Let's Encrypt challenge types."); - ACME._removeChallengeWarn = true; - } - options.removeChallenge(auth.request.identifier, auth.token, function () {}); - } - } catch(e) {} - return resp.body; - } - - var errmsg; - if (!resp.body.status) { - errmsg = "[acme-v2] (E_STATE_EMPTY) empty challenge state for '" + altname + "':"; - } - else if ('invalid' === resp.body.status) { - errmsg = "[acme-v2] (E_STATE_INVALID) challenge state for '" + altname + "': '" + resp.body.status + "'"; - } - else { - errmsg = "[acme-v2] (E_STATE_UKN) challenge state for '" + altname + "': '" + resp.body.status + "'"; - } - - return Promise.reject(new Error(errmsg)); - }); - } - - function respondToChallenge() { - if (me.debug) { console.debug('[acme-v2.js] responding to accept challenge:'); } - return ACME._jwsRequest({ - options: options - , url: auth.url - , protected: { kid: options._kid } - , payload: Enc.strToBuf(JSON.stringify({})) - }).then(function (resp) { - if (me.debug) { console.debug('respond to challenge: resp.body:'); } - if (me.debug) { console.debug(resp.body); } - return ACME._wait(RETRY_INTERVAL).then(pollStatus); - }); - } - - return respondToChallenge(); -}; -ACME._setChallenge = function (me, options, auth) { - console.log('challenge auth:', auth); - console.log('challenges:', options.challenges); - return new Promise(function (resolve, reject) { - var challengers = options.challenges || {}; - var challenger = (challengers[auth.type] && challengers[auth.type].set) || options.setChallenge; - try { - if (1 === challenger.length) { - challenger(auth).then(resolve).catch(reject); - } else if (2 === challenger.length) { - challenger(auth, function (err) { - if(err) { reject(err); } else { resolve(); } - }); - } else { - // TODO remove this old backwards-compat - var challengeCb = function(err) { - if(err) { reject(err); } else { resolve(); } - }; - // for backwards compat adding extra keys without changing params length - Object.keys(auth).forEach(function (key) { - challengeCb[key] = auth[key]; - }); - if (!ACME._setChallengeWarn) { - console.warn("Please update to acme-v2 setChallenge(options) or setChallenge(options, cb)."); - console.warn("The API has been changed for compatibility with all ACME / Let's Encrypt challenge types."); - ACME._setChallengeWarn = true; - } - challenger(auth.identifier.value, auth.token, auth.keyAuthorization, challengeCb); - } - } catch(e) { - reject(e); - } - }).then(function () { - // TODO: Do we still need this delay? Or shall we leave it to plugins to account for themselves? - var DELAY = me.setChallengeWait || 500; - if (me.debug) { console.debug('\n[DEBUG] waitChallengeDelay %s\n', DELAY); } - return ACME._wait(DELAY); - }); -}; -ACME._finalizeOrder = function (me, options, validatedDomains) { - if (me.debug) { console.debug('finalizeOrder:'); } - return ACME._generateCsrWeb64(me, options, validatedDomains).then(function (csr) { - var body = { csr: csr }; - var payload = JSON.stringify(body); - - function pollCert() { - if (me.debug) { console.debug('[acme-v2.js] pollCert:'); } - return ACME._jwsRequest({ - options: options - , url: options._finalize - , protected: { kid: options._kid } - , payload: Enc.strToBuf(payload) - }).then(function (resp) { - if (me.debug) { console.debug('order finalized: resp.body:'); } - if (me.debug) { console.debug(resp.body); } - - // https://tools.ietf.org/html/draft-ietf-acme-acme-12#section-7.1.3 - // Possible values are: "pending" => ("invalid" || "ready") => "processing" => "valid" - if ('valid' === resp.body.status) { - options._expires = resp.body.expires; - options._certificate = resp.body.certificate; - - return resp.body; // return order - } - - if ('processing' === resp.body.status) { - return ACME._wait().then(pollCert); - } - - if (me.debug) { console.debug("Error: bad status:\n" + JSON.stringify(resp.body, null, 2)); } - - if ('pending' === resp.body.status) { - return Promise.reject(new Error( - "Did not finalize order: status 'pending'." - + " Best guess: You have not accepted at least one challenge for each domain:\n" - + "Requested: '" + options.domains.join(', ') + "'\n" - + "Validated: '" + validatedDomains.join(', ') + "'\n" - + JSON.stringify(resp.body, null, 2) - )); - } - - if ('invalid' === resp.body.status) { - return Promise.reject(new Error( - "Did not finalize order: status 'invalid'." - + " Best guess: One or more of the domain challenges could not be verified" - + " (or the order was canceled).\n" - + "Requested: '" + options.domains.join(', ') + "'\n" - + "Validated: '" + validatedDomains.join(', ') + "'\n" - + JSON.stringify(resp.body, null, 2) - )); - } - - if ('ready' === resp.body.status) { - return Promise.reject(new Error( - "Did not finalize order: status 'ready'." - + " Hmmm... this state shouldn't be possible here. That was the last state." - + " This one should at least be 'processing'.\n" - + "Requested: '" + options.domains.join(', ') + "'\n" - + "Validated: '" + validatedDomains.join(', ') + "'\n" - + JSON.stringify(resp.body, null, 2) + "\n\n" - + "Please open an issue at https://git.coolaj86.com/coolaj86/acme-v2.js" - )); - } - - return Promise.reject(new Error( - "Didn't finalize order: Unhandled status '" + resp.body.status + "'." - + " This is not one of the known statuses...\n" - + "Requested: '" + options.domains.join(', ') + "'\n" - + "Validated: '" + validatedDomains.join(', ') + "'\n" - + JSON.stringify(resp.body, null, 2) + "\n\n" - + "Please open an issue at https://git.coolaj86.com/coolaj86/acme-v2.js" - )); - }); - } - - return pollCert(); - }); -}; -// _kid -// registerAccount -// postChallenge -// finalizeOrder -// getCertificate -ACME._getCertificate = function (me, options) { - if (me.debug) { console.debug('[acme-v2] DEBUG get cert 1'); } - - // Lot's of error checking to inform the user of mistakes - if (!(options.challengeTypes||[]).length) { - options.challengeTypes = Object.keys(options.challenges||{}); - } - if (!options.challengeTypes.length) { - options.challengeTypes = [ options.challengeType ].filter(Boolean); - } - if (options.challengeType) { - options.challengeTypes.sort(function (a, b) { - if (a === options.challengeType) { return -1; } - if (b === options.challengeType) { return 1; } - return 0; - }); - if (options.challengeType !== options.challengeTypes[0]) { - return Promise.reject(new Error("options.challengeType is '" + options.challengeType + "'," - + " which does not exist in the supplied types '" + options.challengeTypes.join(',') + "'")); - } - } - // TODO check that all challengeTypes are represented in challenges - if (!options.challengeTypes.length) { - return Promise.reject(new Error("options.challengeTypes (string array) must be specified" - + " (and in order of preferential priority).")); - } - if (!(options.domains && options.domains.length)) { - return Promise.reject(new Error("options.domains must be a list of string domain names," - + " with the first being the subject of the certificate (or options.subject must specified).")); - } - - // 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.accountKid - || (options.account && (options.account.kid - || (options.account.key && options.account.key.kid))); - if (!options._kid) { - //return Promise.reject(new Error("must include KeyID")); - // This is an idempotent request. It'll return the same account for the same public key. - return ACME._registerAccount(me, options).then(function (account) { - options._kid = account.key.kid; - // start back from the top - return ACME._getCertificate(me, options); - }); - } - - // Do a little dry-run / self-test - return ACME._testChallenges(me, options).then(function () { - if (me.debug) { console.debug('[acme-v2] certificates.create'); } - var body = { - // raw wildcard syntax MUST be used here - identifiers: options.domains.sort(function (a, b) { - // the first in the list will be the subject of the certificate, I believe (and hope) - if (!options.subject) { return 0; } - if (options.subject === a) { return -1; } - if (options.subject === b) { return 1; } - return 0; - }).map(function (hostname) { - return { type: "dns", value: hostname }; - }) - //, "notBefore": "2016-01-01T00:00:00Z" - //, "notAfter": "2016-01-08T00:00:00Z" - }; - - var payload = JSON.stringify(body); - if (me.debug) { console.debug('\n[DEBUG] newOrder\n'); } - return ACME._jwsRequest({ - options: options - , url: me._directoryUrls.newOrder - , protected: { kid: options._kid } - , payload: Enc.strToBuf(payload) - }).then(function (resp) { - var location = resp.headers.location; - var setAuths; - var auths = []; - if (me.debug) { console.debug('[ordered]', location); } // the account id url - if (me.debug) { console.debug(resp); } - options._authorizations = resp.body.authorizations; - options._order = location; - options._finalize = resp.body.finalize; - //if (me.debug) console.debug('[DEBUG] finalize:', options._finalize); return; - - if (!options._authorizations) { - return Promise.reject(new Error( - "[acme-v2.js] authorizations were not fetched for '" + options.domains.join() + "':\n" - + JSON.stringify(resp.body) - )); - } - if (me.debug) { console.debug("[acme-v2] POST newOrder has authorizations"); } - setAuths = options._authorizations.slice(0); - - function setNext() { - var authUrl = setAuths.shift(); - if (!authUrl) { return; } - - return ACME._getChallenges(me, options, authUrl).then(function (results) { - // var domain = options.domains[i]; // results.identifier.value - - // If it's already valid, we're golden it regardless - if (results.challenges.some(function (ch) { return 'valid' === ch.status; })) { - return setNext(); - } - - var challenge = ACME._chooseChallenge(options, results); - if (!challenge) { - // For example, wildcards require dns-01 and, if we don't have that, we have to bail - return Promise.reject(new Error( - "Server didn't offer any challenge we can handle for '" + options.domains.join() + "'." - )); - } - - return ACME._challengeToAuth(me, options, results, challenge, false).then(function (auth) { - auths.push(auth); - return ACME._setChallenge(me, options, auth).then(setNext); - }); - }); - } - - function challengeNext() { - var auth = auths.shift(); - if (!auth) { return; } - return ACME._postChallenge(me, options, auth).then(challengeNext); - } - - // First we set every challenge - // Then we ask for each challenge to be checked - // Doing otherwise would potentially cause us to poison our own DNS cache with misses - return setNext().then(challengeNext).then(function () { - if (me.debug) { console.debug("[getCertificate] next.then"); } - var validatedDomains = body.identifiers.map(function (ident) { - return ident.value; - }); - - return ACME._finalizeOrder(me, options, validatedDomains); - }).then(function (order) { - if (me.debug) { console.debug('acme-v2: order was finalized'); } - // TODO POST-as-GET - return me.request({ method: 'GET', url: options._certificate, json: true }).then(function (resp) { - if (me.debug) { console.debug('acme-v2: csr submitted and cert received:'); } - // https://github.com/certbot/certbot/issues/5721 - var certsarr = ACME.splitPemChain(ACME.formatPemChain((resp.body||''))); - // cert, chain, fullchain, privkey, /*TODO, subject, altnames, issuedAt, expiresAt */ - var certs = { - expires: order.expires - , identifiers: order.identifiers - //, authorizations: order.authorizations - , cert: certsarr.shift() - //, privkey: privkeyPem - , chain: certsarr.join('\n') - }; - if (me.debug) { console.debug(certs); } - return certs; - }); - }); - }); - }); -}; -ACME._generateCsrWeb64 = function (me, options, validatedDomains) { - return ACME._importKeypair(me, options.domainKeypair).then(function (/*pair*/) { - return me.Keypairs.generateCsr(options.domainKeypair, validatedDomains).then(function (der) { - return Enc.bufToUrlBase64(der); - }); - }); -}; - -ACME.create = function create(me) { - if (!me) { me = {}; } - // me.debug = true; - me.challengePrefixes = ACME.challengePrefixes; - me.Keypairs = me.Keypairs || me.RSA || require('rsa-compat').RSA; - me._nonces = []; - //me.Keypairs = me.Keypairs || require('keypairs'); - //me.request = me.request || require('@root/request'); - if (!me.dig) { - me.dig = function (query) { - // TODO use digd.js - return new me.request({ url: "/api/dns/" + query.name + "?type=" + query.type }).then(function (resp) { - if (!resp.body || !Array.isArray(resp.body.answer)) { - throw new Error("failed to get DNS response"); - } - return { - answer: resp.body.answer.map(function (ans) { - return { data: ans.data, ttl: ans.ttl }; - }) - }; - }); - }; - } - - if ('function' !== typeof me.request) { - me.request = ACME._defaultRequest; - } - - me.init = function (opts) { - function fin(dir) { - me._directoryUrls = dir; - me._tos = dir.meta.termsOfService; - return dir; - } - if (opts && opts.meta && opts.termsOfService) { - return Promise.resolve(fin(opts)); - } - if (!me.directoryUrl) { me.directoryUrl = opts; } - if ('string' !== typeof me.directoryUrl) { - throw new Error("you must supply either the ACME directory url as a string or an object of the ACME urls"); - } - return ACME._directory(me).then(function (resp) { - return fin(resp.body); - }); - }; - me.accounts = { - create: function (options) { - return ACME._registerAccount(me, options); - } - }; - me.certificates = { - create: function (options) { - return ACME._getCertificate(me, options); - } - }; - return me; -}; - -// Handle nonce, signing, and request altogether -ACME._jwsRequest = function (me, bigopts) { - return ACME._getNonce(me).then(function (nonce) { - bigopts.protected.nonce = nonce; - bigopts.protected.url = bigopts.url; - // protected.alg: added by Keypairs.signJws - return me.Keypairs.signJws( - { jwk: bigopts.options.accountKeypair.privateKeyJwk - , protected: bigopts.protected - , payload: bigopts.payload - } - ).then(function (jws) { - if (me.debug) { console.debug('[acme-v2] ' + bigopts.url + ':'); } - if (me.debug) { console.debug(jws); } - return ACME._request(me, { url: bigopts.url, json: jws }); - }); - }); -}; -// Handle some ACME-specific defaults -ACME._request = function (me, opts) { - if (!opts.headers) { opts.headers = {}; } - if (opts.json && true !== opts.json) { - opts.headers['Content-Type'] = 'application/jose+json'; - opts.body = JSON.stringify(opts.json); - if (!opts.method) { opts.method = 'POST'; } - } - return me.request(opts).then(function (resp) { - resp = resp.toJSON(); - if (resp.headers['replay-nonce']) { - ACME._setNonce(me, resp.headers['replay-nonce']); - } - return resp; - }); -}; -// A very generic, swappable request lib -ACME._defaultRequest = function (opts) { - // Note: normally we'd have to supply a User-Agent string, but not here in a browser - if (!opts.headers) { opts.headers = {}; } - if (opts.json) { - opts.headers.Accept = 'application/json'; - if (true !== opts.json) { opts.body = JSON.stringify(opts.json); } - } - if (!opts.method) { - opts.method = 'GET'; - if (opts.body) { opts.method = 'POST'; } - } - opts.cors = true; - return window.fetch(opts.url, opts).then(function (resp) { - var headers = {}; - var result = { statusCode: resp.status, headers: headers, toJSON: function () { return this; } }; - Array.from(resp.headers.entries()).forEach(function (h) { headers[h[0]] = h[1]; }); - if (!headers['content-type']) { - return result; - } - if (/json/.test(headers['content-type'])) { - return resp.json().then(function (json) { - result.body = json; - return result; - }); - } - return resp.text().then(function (txt) { - result.body = txt; - return result; - }); - }); -}; - -ACME._importKeypair = function (me, kp) { - var jwk = kp.privateKeyJwk; - var p; - if (jwk) { - // nix the browser jwk extras - jwk.key_ops = undefined; - jwk.ext = undefined; - p = Promise.resolve({ private: jwk, public: me.Keypairs.neuter({ jwk: jwk }) }); - } else { - p = me.Keypairs.import({ pem: kp.privateKeyPem }); - } - return p.then(function (pair) { - kp.privateKeyJwk = pair.private; - kp.publicKeyJwk = pair.public; - if (pair.public.kid) { - pair = JSON.parse(JSON.stringify(pair)); - delete pair.public.kid; - delete pair.private.kid; - } - return pair; - }); -}; - -/* -TODO -Per-Order State Params - _kty - _alg - _finalize - _expires - _certificate - _order - _authorizations -*/ - -ACME._toWebsafeBase64 = function (b64) { - return b64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g,""); -}; - -// In v8 this is crypto random, but we're just using it for pseudorandom -ACME._prnd = function (n) { - var rnd = ''; - while (rnd.length / 2 < n) { - var num = Math.random().toString().substr(2); - if (num.length % 2) { - num = '0' + num; - } - var pairs = num.match(/(..?)/g); - rnd += pairs.map(ACME._toHex).join(''); - } - return rnd.substr(0, n*2); -}; -ACME._toHex = function (pair) { - return parseInt(pair, 10).toString(16); -}; - -Enc.bufToUrlBase64 = function (u8) { - return Enc.bufToBase64(u8) - .replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, ''); -}; -Enc.bufToBase64 = function (u8) { - var bin = ''; - u8.forEach(function (i) { - bin += String.fromCharCode(i); - }); - return btoa(bin); -}; - -Crypto._sha = function (sha, str) { - var encoder = new TextEncoder(); - var data = encoder.encode(str); - sha = 'SHA-' + sha.replace(/^sha-?/i, ''); - return window.crypto.subtle.digest(sha, data).then(function (hash) { - return Enc.bufToUrlBase64(new Uint8Array(hash)); - }); -}; - -}('undefined' === typeof window ? module.exports : window)); diff --git a/package-lock.json b/package-lock.json deleted file mode 100644 index 2060678..0000000 --- a/package-lock.json +++ /dev/null @@ -1,547 +0,0 @@ -{ - "name": "bluecrypt-keypairs", - "version": "0.1.0", - "lockfileVersion": 1, - "requires": true, - "dependencies": { - "accepts": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.6.tgz", - "integrity": "sha512-QsaoUD2dpVpjENy8JFpQnXP9vyzoZPmAoKrE3S6HtSB7qzSebkJNnmdY4p004FQUSSiHXPueENpoeuUW/7a8Ig==", - "dev": true, - "requires": { - "mime-types": "~2.1.24", - "negotiator": "0.6.1" - } - }, - "array-flatten": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=", - "dev": true - }, - "balanced-match": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", - "dev": true - }, - "bluebird": { - "version": "3.5.4", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.4.tgz", - "integrity": "sha512-FG+nFEZChJrbQ9tIccIfZJBz3J7mLrAhxakAbnrJWn8d7aKOC+LWifa0G+p4ZqKp4y13T7juYvdhq9NzKdsrjw==", - "dev": true - }, - "body-parser": { - "version": "1.18.3", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.18.3.tgz", - "integrity": "sha1-WykhmP/dVTs6DyDe0FkrlWlVyLQ=", - "dev": true, - "requires": { - "bytes": "3.0.0", - "content-type": "~1.0.4", - "debug": "2.6.9", - "depd": "~1.1.2", - "http-errors": "~1.6.3", - "iconv-lite": "0.4.23", - "on-finished": "~2.3.0", - "qs": "6.5.2", - "raw-body": "2.3.3", - "type-is": "~1.6.16" - } - }, - "brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "bytes": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", - "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=", - "dev": true - }, - "cli": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/cli/-/cli-1.0.1.tgz", - "integrity": "sha1-IoF1NPJL+klQw01TLUjsvGIbjBQ=", - "dev": true, - "requires": { - "exit": "0.1.2", - "glob": "^7.1.1" - } - }, - "concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", - "dev": true - }, - "content-disposition": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz", - "integrity": "sha1-DPaLud318r55YcOoUXjLhdunjLQ=", - "dev": true - }, - "content-type": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", - "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==", - "dev": true - }, - "cookie": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz", - "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=", - "dev": true - }, - "cookie-signature": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=", - "dev": true - }, - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "depd": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", - "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=", - "dev": true - }, - "destroy": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", - "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=", - "dev": true - }, - "dig.js": { - "version": "1.3.9", - "resolved": "https://registry.npmjs.org/dig.js/-/dig.js-1.3.9.tgz", - "integrity": "sha512-O/tSWZuW7AwpjsgePPmTanwvSDL9xF+FzLTJD9byN3C6lk79iMejC/Ahz9CERAXTW4e2TXL1vtqh3T0Ug79ocA==", - "dev": true, - "requires": { - "cli": "^1.0.1", - "dns-suite": "git+https://git.coolaj86.com/coolaj86/dns-suite.js#v1.2", - "hexdump.js": "git+https://git.coolaj86.com/coolaj86/hexdump.js#v1.0.4" - }, - "dependencies": { - "dns-suite": { - "version": "git+https://git.coolaj86.com/coolaj86/dns-suite.js#092008f766540909d27c934211495c9e03705bf3", - "from": "git+https://git.coolaj86.com/coolaj86/dns-suite.js#v1.2", - "dev": true, - "requires": { - "bluebird": "^3.5.0", - "hexdump.js": "git+https://git.coolaj86.com/coolaj86/hexdump.js#v1.0.4" - } - } - } - }, - "dns-suite": { - "version": "1.2.12", - "resolved": "https://registry.npmjs.org/dns-suite/-/dns-suite-1.2.12.tgz", - "integrity": "sha512-K4LWqmJT/T2QLaGCJ+qRvrT9DicKs5XxXYXw6uIZ1apdwyfToQk7K9AZbpFd0FLRdZG809v2vAcsquPbQh+Ipg==", - "dev": true, - "requires": { - "bluebird": "^3.5.0", - "hexdump.js": "git+https://git.coolaj86.com/coolaj86/hexdump.js#v1.0.4" - } - }, - "ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=", - "dev": true - }, - "encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=", - "dev": true - }, - "escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=", - "dev": true - }, - "etag": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=", - "dev": true - }, - "exit": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", - "integrity": "sha1-BjJjj42HfMghB9MKD/8aF8uhzQw=", - "dev": true - }, - "express": { - "version": "4.16.4", - "resolved": "https://registry.npmjs.org/express/-/express-4.16.4.tgz", - "integrity": "sha512-j12Uuyb4FMrd/qQAm6uCHAkPtO8FDTRJZBDd5D2KOL2eLaz1yUNdUB/NOIyq0iU4q4cFarsUCrnFDPBcnksuOg==", - "dev": true, - "requires": { - "accepts": "~1.3.5", - "array-flatten": "1.1.1", - "body-parser": "1.18.3", - "content-disposition": "0.5.2", - "content-type": "~1.0.4", - "cookie": "0.3.1", - "cookie-signature": "1.0.6", - "debug": "2.6.9", - "depd": "~1.1.2", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "finalhandler": "1.1.1", - "fresh": "0.5.2", - "merge-descriptors": "1.0.1", - "methods": "~1.1.2", - "on-finished": "~2.3.0", - "parseurl": "~1.3.2", - "path-to-regexp": "0.1.7", - "proxy-addr": "~2.0.4", - "qs": "6.5.2", - "range-parser": "~1.2.0", - "safe-buffer": "5.1.2", - "send": "0.16.2", - "serve-static": "1.13.2", - "setprototypeof": "1.1.0", - "statuses": "~1.4.0", - "type-is": "~1.6.16", - "utils-merge": "1.0.1", - "vary": "~1.1.2" - } - }, - "finalhandler": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.1.tgz", - "integrity": "sha512-Y1GUDo39ez4aHAw7MysnUD5JzYX+WaIj8I57kO3aEPT1fFRL4sr7mjei97FgnwhAyyzRYmQZaTHb2+9uZ1dPtg==", - "dev": true, - "requires": { - "debug": "2.6.9", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "on-finished": "~2.3.0", - "parseurl": "~1.3.2", - "statuses": "~1.4.0", - "unpipe": "~1.0.0" - } - }, - "forwarded": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", - "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=", - "dev": true - }, - "fresh": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=", - "dev": true - }, - "fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", - "dev": true - }, - "glob": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", - "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "hexdump.js": { - "version": "git+https://git.coolaj86.com/coolaj86/hexdump.js#222fa7de5036a16397de2fe703c35ac54a3d8d0c", - "from": "git+https://git.coolaj86.com/coolaj86/hexdump.js#v1.0.4", - "dev": true - }, - "http-errors": { - "version": "1.6.3", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", - "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=", - "dev": true, - "requires": { - "depd": "~1.1.2", - "inherits": "2.0.3", - "setprototypeof": "1.1.0", - "statuses": ">= 1.4.0 < 2" - } - }, - "iconv-lite": { - "version": "0.4.23", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.23.tgz", - "integrity": "sha512-neyTUVFtahjf0mB3dZT77u+8O0QB89jFdnBkd5P1JgYPbPaia3gXXOVL2fq8VyU2gMMD7SaN7QukTB/pmXYvDA==", - "dev": true, - "requires": { - "safer-buffer": ">= 2.1.2 < 3" - } - }, - "inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "dev": true, - "requires": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", - "dev": true - }, - "ipaddr.js": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.0.tgz", - "integrity": "sha512-M4Sjn6N/+O6/IXSJseKqHoFc+5FdGJ22sXqnjTpdZweHK64MzEPAyQZyEU3R/KRv2GLoa7nNtg/C2Ev6m7z+eA==", - "dev": true - }, - "media-typer": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=", - "dev": true - }, - "merge-descriptors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", - "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=", - "dev": true - }, - "methods": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", - "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=", - "dev": true - }, - "mime": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.4.1.tgz", - "integrity": "sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ==", - "dev": true - }, - "mime-db": { - "version": "1.40.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.40.0.tgz", - "integrity": "sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA==", - "dev": true - }, - "mime-types": { - "version": "2.1.24", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.24.tgz", - "integrity": "sha512-WaFHS3MCl5fapm3oLxU4eYDw77IQM2ACcxQ9RIxfaC3ooc6PFuBMGZZsYpvoXS5D5QTWPieo1jjLdAm3TBP3cQ==", - "dev": true, - "requires": { - "mime-db": "1.40.0" - } - }, - "minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "dev": true, - "requires": { - "brace-expansion": "^1.1.7" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - }, - "negotiator": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz", - "integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk=", - "dev": true - }, - "on-finished": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", - "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", - "dev": true, - "requires": { - "ee-first": "1.1.1" - } - }, - "once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "dev": true, - "requires": { - "wrappy": "1" - } - }, - "parseurl": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", - "dev": true - }, - "path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", - "dev": true - }, - "path-to-regexp": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", - "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=", - "dev": true - }, - "proxy-addr": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.5.tgz", - "integrity": "sha512-t/7RxHXPH6cJtP0pRG6smSr9QJidhB+3kXu0KgXnbGYMgzEnUxRQ4/LDdfOwZEMyIh3/xHb8PX3t+lfL9z+YVQ==", - "dev": true, - "requires": { - "forwarded": "~0.1.2", - "ipaddr.js": "1.9.0" - } - }, - "qs": { - "version": "6.5.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", - "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==", - "dev": true - }, - "range-parser": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz", - "integrity": "sha1-9JvmtIeJTdxA3MlKMi9hEJLgDV4=", - "dev": true - }, - "raw-body": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.3.3.tgz", - "integrity": "sha512-9esiElv1BrZoI3rCDuOuKCBRbuApGGaDPQfjSflGxdy4oyzqghxu6klEkkVIvBje+FF0BX9coEv8KqW6X/7njw==", - "dev": true, - "requires": { - "bytes": "3.0.0", - "http-errors": "1.6.3", - "iconv-lite": "0.4.23", - "unpipe": "1.0.0" - } - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true - }, - "safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true - }, - "send": { - "version": "0.16.2", - "resolved": "https://registry.npmjs.org/send/-/send-0.16.2.tgz", - "integrity": "sha512-E64YFPUssFHEFBvpbbjr44NCLtI1AohxQ8ZSiJjQLskAdKuriYEP6VyGEsRDH8ScozGpkaX1BGvhanqCwkcEZw==", - "dev": true, - "requires": { - "debug": "2.6.9", - "depd": "~1.1.2", - "destroy": "~1.0.4", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "fresh": "0.5.2", - "http-errors": "~1.6.2", - "mime": "1.4.1", - "ms": "2.0.0", - "on-finished": "~2.3.0", - "range-parser": "~1.2.0", - "statuses": "~1.4.0" - } - }, - "serve-static": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.13.2.tgz", - "integrity": "sha512-p/tdJrO4U387R9oMjb1oj7qSMaMfmOyd4j9hOFoxZe2baQszgHcSWjuya/CiT5kgZZKRudHNOA0pYXOl8rQ5nw==", - "dev": true, - "requires": { - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "parseurl": "~1.3.2", - "send": "0.16.2" - } - }, - "setprototypeof": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", - "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==", - "dev": true - }, - "statuses": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", - "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==", - "dev": true - }, - "type-is": { - "version": "1.6.18", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", - "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", - "dev": true, - "requires": { - "media-typer": "0.3.0", - "mime-types": "~2.1.24" - } - }, - "unpipe": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=", - "dev": true - }, - "utils-merge": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", - "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=", - "dev": true - }, - "vary": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=", - "dev": true - }, - "wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "dev": true - } - } -} diff --git a/package.json b/package.json index a29af32..3dd19e2 100644 --- a/package.json +++ b/package.json @@ -1,8 +1,7 @@ { "name": "bluecrypt-keypairs", - "version": "0.1.0", + "version": "0.1.1", "description": "Zero-Dependency Native Browser support for ECDSA P-256 and P-384, and RSA 2048/3072/4096 written in VanillaJS", - "main": "server.js", "directories": { "lib": "lib" }, @@ -29,8 +28,5 @@ "author": "AJ ONeal (https://coolaj86.com/)", "license": "MPL-2.0", "devDependencies": { - "dig.js": "^1.3.9", - "dns-suite": "^1.2.12", - "express": "^4.16.4" } } diff --git a/server.js b/server.js deleted file mode 100644 index 34938da..0000000 --- a/server.js +++ /dev/null @@ -1,125 +0,0 @@ -'use strict'; - -var crypto = require('crypto'); -//var dnsjs = require('dns-suite'); -var dig = require('dig.js/dns-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('./')); -app.use('/api', express.json()); -app.get('/api/dns/:domain', function (req, res, next) { - console.log(req.params); - 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); -}); - -// curl -L http://localhost:3000/api/dns/example.com?type=A -console.log("Listening on localhost:3000"); -app.listen(3000); -console.log("Try this:"); -console.log("\tcurl -L 'http://localhost:3000/api/dns/example.com?type=A'");