acme.js/utils.js

175 lines
4.0 KiB
JavaScript
Raw Normal View History

2019-10-23 07:44:55 +00:00
'use strict';
var U = module.exports;
var Keypairs = require('@root/keypairs');
2019-10-25 00:49:42 +00:00
var UserAgent = require('./lib/node/client-user-agent.js');
2019-10-23 07:44:55 +00:00
// Handle nonce, signing, and request altogether
2020-07-28 21:53:50 +00:00
U._jwsRequest = function (me, bigopts) {
return U._getNonce(me).then(function (nonce) {
2019-10-23 07:44:55 +00:00
bigopts.protected.nonce = nonce;
bigopts.protected.url = bigopts.url;
// protected.alg: added by Keypairs.signJws
if (bigopts.protected.jwk) {
bigopts.protected.kid = false;
} else if (!('kid' in bigopts.protected)) {
// protected.kid must be provided according to ACME's interpretation of the spec
// (using the provided URL rather than the Key's Thumbprint as Key ID)
bigopts.protected.kid = bigopts.kid;
2019-10-23 07:44:55 +00:00
}
// this will shasum the thumbprint the 2nd time
return Keypairs.signJws({
2019-10-25 00:49:42 +00:00
jwk: bigopts.accountKey,
2019-10-23 07:44:55 +00:00
protected: bigopts.protected,
payload: bigopts.payload
})
2020-07-28 21:53:50 +00:00
.then(function (jws) {
2019-10-23 07:44:55 +00:00
//#console.debug('[ACME.js] url: ' + bigopts.url + ':');
//#console.debug(jws);
return U._request(me, { url: bigopts.url, json: jws });
})
2020-07-28 21:53:50 +00:00
.catch(function (e) {
2019-10-23 07:44:55 +00:00
if (/badNonce$/.test(e.urn)) {
// retry badNonces
var retryable = bigopts._retries >= 2;
if (!retryable) {
bigopts._retries = (bigopts._retries || 0) + 1;
return U._jwsRequest(me, bigopts);
}
}
throw e;
});
});
};
2020-07-28 21:53:50 +00:00
U._getNonce = function (me) {
2019-10-23 07:44:55 +00:00
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);
}
// HEAD-as-HEAD ok
return U._request(me, {
method: 'HEAD',
url: me._directoryUrls.newNonce
2020-07-28 21:53:50 +00:00
}).then(function (resp) {
2019-10-23 07:44:55 +00:00
return resp.headers['replay-nonce'];
});
};
// Handle some ACME-specific defaults
2020-07-28 21:53:50 +00:00
U._request = function (me, opts) {
2019-10-25 00:49:42 +00:00
// no-op on browser
var ua = UserAgent.get(me, opts);
// Note: the required User-Agent string will be set in node, but not browsers
2019-10-23 07:44:55 +00:00
if (!opts.headers) {
opts.headers = {};
}
2019-10-25 00:49:42 +00:00
if (ua && !opts.headers['User-Agent']) {
opts.headers['User-Agent'] = ua;
}
2019-10-25 10:54:40 +00:00
if (opts.json) {
opts.headers.Accept = 'application/json';
if (true !== opts.json) {
opts.body = JSON.stringify(opts.json);
}
if (/*opts.jose ||*/ opts.json.protected) {
opts.headers['Content-Type'] = 'application/jose+json';
}
2019-10-25 00:49:42 +00:00
}
if (!opts.method) {
opts.method = 'GET';
if (opts.body) {
2019-10-23 07:44:55 +00:00
opts.method = 'POST';
}
}
2019-10-25 00:49:42 +00:00
//console.log('\n[debug] REQUEST');
//console.log(opts);
2020-07-28 21:53:50 +00:00
return me.__request(opts).then(function (resp) {
2019-10-23 07:44:55 +00:00
if (resp.toJSON) {
resp = resp.toJSON();
}
if (resp.headers['replay-nonce']) {
U._setNonce(me, resp.headers['replay-nonce']);
}
2019-10-25 00:49:42 +00:00
//console.log('[debug] RESPONSE:');
//console.log(resp.headers);
//console.log(resp.body);
2019-10-23 07:44:55 +00:00
var e;
var err;
if (resp.body) {
err = resp.body.error;
e = new Error('');
if (400 === resp.body.status) {
err = { type: resp.body.type, detail: resp.body.detail };
}
if (err) {
e.status = resp.body.status;
e.code = 'E_ACME';
if (e.status) {
e.message = '[' + e.status + '] ';
}
e.detail = err.detail;
e.message += err.detail || JSON.stringify(err);
e.urn = err.type;
e.uri = resp.body.url;
e._rawError = err;
e._rawBody = resp.body;
throw e;
}
}
return resp;
});
};
2020-07-28 21:53:50 +00:00
U._setNonce = function (me, nonce) {
2019-10-23 07:44:55 +00:00
me._nonces.unshift({ nonce: nonce, createdAt: Date.now() });
};
2020-07-28 21:53:50 +00:00
U._importKeypair = function (key) {
2019-10-23 07:44:55 +00:00
var p;
2019-10-24 17:39:25 +00:00
var pub;
if (key && key.kty) {
2019-10-23 07:44:55 +00:00
// nix the browser jwk extras
2019-10-24 17:39:25 +00:00
key.key_ops = undefined;
key.ext = undefined;
pub = Keypairs.neuter({ jwk: key });
2019-10-23 07:44:55 +00:00
p = Promise.resolve({
2019-10-24 17:39:25 +00:00
private: key,
2019-10-23 07:44:55 +00:00
public: pub
});
2019-10-24 17:39:25 +00:00
} else if ('string' === typeof key) {
p = Keypairs.import({ pem: key });
2019-10-23 07:44:55 +00:00
} else {
2019-10-24 17:39:25 +00:00
throw new Error('no private key given');
2019-10-23 07:44:55 +00:00
}
2019-10-24 17:39:25 +00:00
2020-07-28 21:53:50 +00:00
return p.then(function (pair) {
2019-10-23 07:44:55 +00:00
if (pair.public.kid) {
pair = JSON.parse(JSON.stringify(pair));
delete pair.public.kid;
delete pair.private.kid;
}
return pair;
});
};