/*! * letiny * Copyright(c) 2015 Anatol Sommer * Some code used from https://github.com/letsencrypt/boulder/tree/master/test/js * MPL 2.0 */ 'use strict'; module.exports.create = function (deps) { var NOOP = function () { }; var log = NOOP; var acmeRequest = deps.acmeRequest; var RSA = deps.RSA; var generateSignature = RSA.signJws; function Acme(keypair) { if (!keypair) { throw new Error("no keypair given. that's bad"); } if ('string' === typeof keypair) { // backwards compat keypair = RSA.import({ privateKeyPem: keypair }); } this.keypair = keypair; this.nonces=[]; } Acme.prototype.getNonce=function(url, cb) { var self=this; acmeRequest.create().head({ url:url, }, function(err, res/*, body*/) { if (err) { return cb(err); } 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')); }); }; 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; } log('Using nonce: '+this.nonces[0]); payload=JSON.stringify(body, null, 2); jws=generateSignature( self.keypair, new Buffer(payload), this.nonces.shift() ); signed=JSON.stringify(jws, null, 2); log('Posting to '+url); log(signed); log('Payload:'+payload); //process.exit(1); //return; return acmeRequest.create().post({ url: url , body: signed , encoding: null }, function(err, res, body) { var parsed; if (err) { console.error('[letiny-core/lib/acme-client.js] post'); console.error(err.stack); return cb(err); } if (res) { log(('HTTP/1.1 '+res.statusCode)); } 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)); }); if (body && !body.toString().match(/[^\x00-\x7F]/)) { try { parsed=JSON.parse(body); log(JSON.stringify(parsed, null, 2)); } catch(err) { log(body.toString()); } } if ('replay-nonce' in res.headers) { log('Storing nonce: '+res.headers['replay-nonce']); self.nonces.push(res.headers['replay-nonce']); } 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; } return acc; }, {}); return links; } catch(err) { return null; } }; return Acme; };