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
|
2020-07-28 22:02:42 +00:00
|
|
|
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;
|
|
|
|
});
|
|
|
|
};
|