acme.js-ARCHIVED/utils.js

175 lines
4.0 KiB
JavaScript

'use strict';
var U = module.exports;
var Keypairs = require('@root/keypairs');
var UserAgent = require('./lib/node/client-user-agent.js');
// Handle nonce, signing, and request altogether
U._jwsRequest = function (me, bigopts) {
return U._getNonce(me).then(function (nonce) {
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;
}
// this will shasum the thumbprint the 2nd time
return Keypairs.signJws({
jwk: bigopts.accountKey,
protected: bigopts.protected,
payload: bigopts.payload
})
.then(function (jws) {
//#console.debug('[ACME.js] url: ' + bigopts.url + ':');
//#console.debug(jws);
return U._request(me, { url: bigopts.url, json: jws });
})
.catch(function (e) {
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;
});
});
};
U._getNonce = function (me) {
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
}).then(function (resp) {
return resp.headers['replay-nonce'];
});
};
// Handle some ACME-specific defaults
U._request = function (me, opts) {
// no-op on browser
var ua = UserAgent.get(me, opts);
// Note: the required User-Agent string will be set in node, but not browsers
if (!opts.headers) {
opts.headers = {};
}
if (ua && !opts.headers['User-Agent']) {
opts.headers['User-Agent'] = ua;
}
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';
}
}
if (!opts.method) {
opts.method = 'GET';
if (opts.body) {
opts.method = 'POST';
}
}
//console.log('\n[debug] REQUEST');
//console.log(opts);
return me.__request(opts).then(function (resp) {
if (resp.toJSON) {
resp = resp.toJSON();
}
if (resp.headers['replay-nonce']) {
U._setNonce(me, resp.headers['replay-nonce']);
}
//console.log('[debug] RESPONSE:');
//console.log(resp.headers);
//console.log(resp.body);
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;
});
};
U._setNonce = function (me, nonce) {
me._nonces.unshift({ nonce: nonce, createdAt: Date.now() });
};
U._importKeypair = function (key) {
var p;
var pub;
if (key && key.kty) {
// nix the browser jwk extras
key.key_ops = undefined;
key.ext = undefined;
pub = Keypairs.neuter({ jwk: key });
p = Promise.resolve({
private: key,
public: pub
});
} else if ('string' === typeof key) {
p = Keypairs.import({ pem: key });
} else {
throw new Error('no private key given');
}
return p.then(function (pair) {
if (pair.public.kid) {
pair = JSON.parse(JSON.stringify(pair));
delete pair.public.kid;
delete pair.private.kid;
}
return pair;
});
};