le-acme-core.js/lib/acme-client.js

147 lines
3.4 KiB
JavaScript
Raw Permalink Normal View History

2015-12-15 14:33:53 +00:00
/*!
* letiny
* Copyright(c) 2015 Anatol Sommer <anatol@anatol.at>
* Some code used from https://github.com/letsencrypt/boulder/tree/master/test/js
* MPL 2.0
*/
'use strict';
2015-12-15 22:07:02 +00:00
module.exports.create = function (deps) {
2015-12-15 14:33:53 +00:00
var NOOP=function () {
};
2015-12-15 22:07:02 +00:00
var log=NOOP;
var request=require('request');
2016-08-02 00:31:43 +00:00
var RSA = deps.RSA;
var generateSignature = RSA.signJws;
2015-12-15 14:33:53 +00:00
2016-08-02 00:25:46 +00:00
function Acme(keypair) {
if (!keypair) {
throw new Error("no keypair given. that's bad");
}
2016-08-02 00:31:43 +00:00
if ('string' === typeof keypair) {
// backwards compat
keypair = RSA.import({ privateKeyPem: keypair });
}
2016-08-02 00:26:19 +00:00
this.keypair = keypair;
2015-12-15 22:07:02 +00:00
this.nonces=[];
}
2015-12-15 14:33:53 +00:00
2015-12-15 22:07:02 +00:00
Acme.prototype.getNonce=function(url, cb) {
var self=this;
2015-12-15 14:33:53 +00:00
2015-12-15 22:07:02 +00:00
request.head({
url:url,
}, function(err, res/*, body*/) {
2015-12-15 14:33:53 +00:00
if (err) {
return cb(err);
}
2015-12-15 22:07:02 +00:00
if (res && 'replay-nonce' in res.headers) {
log('Storing nonce: '+res.headers['replay-nonce']);
self.nonces.push(res.headers['replay-nonce']);
cb();
return;
}
cb(new Error('Failed to get nonce for request'));
2015-12-15 14:33:53 +00:00
});
2015-12-15 22:07:02 +00:00
};
2015-12-15 14:33:53 +00:00
2015-12-15 22:07:02 +00:00
Acme.prototype.post=function(url, body, cb) {
var self=this, payload, jws, signed;
if (this.nonces.length===0) {
this.getNonce(url, function(err) {
if (err) {
return cb(err);
}
self.post(url, body, cb);
});
return;
2015-12-15 14:33:53 +00:00
}
2015-12-15 22:07:02 +00:00
log('Using nonce: '+this.nonces[0]);
payload=JSON.stringify(body, null, 2);
jws=generateSignature(
self.keypair, new Buffer(payload), this.nonces.shift()
2015-12-15 22:07:02 +00:00
);
signed=JSON.stringify(jws, null, 2);
log('Posting to '+url);
log(signed);
log('Payload:'+payload);
2015-12-15 14:33:53 +00:00
//process.exit(1);
//return;
2015-12-15 22:07:02 +00:00
return request.post({
url:url,
body:signed,
encoding:null
}, function(err, res, body) {
var parsed;
if (err) {
2016-02-10 20:41:48 +00:00
console.error('[letiny-core/lib/acme-client.js] post');
2015-12-15 22:07:02 +00:00
console.error(err.stack);
return cb(err);
}
if (res) {
log(('HTTP/1.1 '+res.statusCode));
2015-12-15 14:33:53 +00:00
}
2015-12-15 22:07:02 +00:00
Object.keys(res.headers).forEach(function(key) {
var value, upcased;
value=res.headers[key];
upcased=key.charAt(0).toUpperCase()+key.slice(1);
log((upcased+': '+value));
2015-12-15 22:07:02 +00:00
});
if (body && !body.toString().match(/[^\x00-\x7F]/)) {
try {
parsed=JSON.parse(body);
log(JSON.stringify(parsed, null, 2));
2015-12-15 22:07:02 +00:00
} catch(err) {
log(body.toString());
2015-12-15 22:07:02 +00:00
}
}
2015-12-15 14:33:53 +00:00
2015-12-15 22:07:02 +00:00
if ('replay-nonce' in res.headers) {
log('Storing nonce: '+res.headers['replay-nonce']);
self.nonces.push(res.headers['replay-nonce']);
}
2015-12-15 14:33:53 +00:00
2015-12-15 22:07:02 +00:00
cb(err, res, body);
});
};
Acme.parseLink = function parseLink(link) {
var links;
try {
links=link.split(',').map(function(link) {
var parts, url, info;
parts=link.trim().split(';');
url=parts.shift().replace(/[<>]/g, '');
info=parts.reduce(function(acc, p) {
var m=p.trim().match(/(.+) *= *"(.+)"/);
if (m) {
acc[m[1]]=m[2];
}
return acc;
}, {});
info.url=url;
return info;
}).reduce(function(acc, link) {
if ('rel' in link) {
acc[link.rel]=link.url;
2015-12-15 14:33:53 +00:00
}
return acc;
}, {});
2015-12-15 22:07:02 +00:00
return links;
} catch(err) {
return null;
}
};
2015-12-15 14:33:53 +00:00
2015-12-15 22:07:02 +00:00
return Acme;
};