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
|
|
|
|
2016-09-02 15:27:31 +00:00
|
|
|
var NOOP = function () {
|
2016-08-04 02:10:26 +00:00
|
|
|
};
|
2016-09-02 15:27:31 +00:00
|
|
|
var log = NOOP;
|
|
|
|
var request = deps.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) {
|
2016-08-04 02:10:26 +00:00
|
|
|
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(
|
2016-08-04 02:10:26 +00:00
|
|
|
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);
|
2016-08-04 02:10:26 +00:00
|
|
|
log(signed);
|
|
|
|
log('Payload:'+payload);
|
2015-12-15 14:33:53 +00:00
|
|
|
|
2016-08-04 02:10:26 +00:00
|
|
|
//process.exit(1);
|
|
|
|
//return;
|
2015-12-15 22:07:02 +00:00
|
|
|
return request.post({
|
2016-09-02 15:27:31 +00:00
|
|
|
url: url
|
|
|
|
, body: signed
|
|
|
|
, encoding: null
|
2015-12-15 22:07:02 +00:00
|
|
|
}, 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) {
|
2016-08-04 02:10:26 +00:00
|
|
|
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);
|
2016-08-04 02:10:26 +00:00
|
|
|
log((upcased+': '+value));
|
2015-12-15 22:07:02 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
if (body && !body.toString().match(/[^\x00-\x7F]/)) {
|
|
|
|
try {
|
|
|
|
parsed=JSON.parse(body);
|
2016-08-04 02:10:26 +00:00
|
|
|
log(JSON.stringify(parsed, null, 2));
|
2015-12-15 22:07:02 +00:00
|
|
|
} catch(err) {
|
2016-08-04 02:10:26 +00:00
|
|
|
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;
|
|
|
|
};
|