forked from svrjs/svrjs
197 lines
4.6 KiB
JavaScript
197 lines
4.6 KiB
JavaScript
'use strict';
|
|
|
|
var ocsp = require('../ocsp');
|
|
|
|
var http = require('http');
|
|
var util = require('util');
|
|
var crypto = require('crypto');
|
|
|
|
var async = require('async');
|
|
var rfc2560 = require('asn1.js-rfc2560');
|
|
var rfc5280 = require('asn1.js-rfc5280');
|
|
|
|
function Server(options) {
|
|
http.Server.call(this, this.handler);
|
|
|
|
this.options = util._extend({
|
|
nextUpdate: 24 * 3600 * 1e3
|
|
}, options);
|
|
|
|
this.key = this.options.key;
|
|
this.cert = rfc5280.Certificate.decode(
|
|
ocsp.utils.toDER(options.cert, 'CERTIFICATE'),
|
|
'der');
|
|
this.cert = this.cert.tbsCertificate;
|
|
|
|
var issuerName = rfc5280.Name.encode(this.cert.subject, 'der');
|
|
var issuerKey = this.cert.subjectPublicKeyInfo.subjectPublicKey.data;
|
|
|
|
this.certID = {};
|
|
Object.keys(ocsp.utils.digestRev).forEach(function(digest) {
|
|
this.certID[digest] = {
|
|
issuerNameHash: crypto.createHash(digest).update(issuerName).digest(),
|
|
issuerKeyHash: crypto.createHash(digest).update(issuerKey).digest()
|
|
};
|
|
}, this);
|
|
|
|
this.certs = {};
|
|
}
|
|
util.inherits(Server, http.Server);
|
|
module.exports = Server;
|
|
|
|
Server.create = function create(options) {
|
|
return new Server(options);
|
|
};
|
|
|
|
Server.prototype.addCert = function addCert(serial, status, info) {
|
|
this.certs[serial.toString(16)] = {
|
|
type: status,
|
|
value: info
|
|
};
|
|
};
|
|
|
|
Server.prototype.handler = function handler(req, res) {
|
|
if (req.method !== 'POST')
|
|
return res.writeHead(400);
|
|
|
|
if (req.headers['content-type'] !== 'application/ocsp-request')
|
|
return res.writeHead(400);
|
|
|
|
var chunks = [];
|
|
req.on('readable', function() {
|
|
var chunk = req.read();
|
|
if (chunk)
|
|
chunks.push(chunk);
|
|
});
|
|
|
|
function errRes(status) {
|
|
return rfc2560.OCSPResponse.encode({
|
|
responseStatus: status
|
|
}, 'der');
|
|
}
|
|
|
|
function done(out) {
|
|
res.writeHead(200, {
|
|
'Content-Type': 'application/ocsp-response',
|
|
'Content-Length': out.length
|
|
});
|
|
res.end(out);
|
|
}
|
|
|
|
var self = this;
|
|
req.on('end', function() {
|
|
var body = Buffer.concat(chunks);
|
|
var ocspReq;
|
|
try {
|
|
ocspReq = rfc2560.OCSPRequest.decode(body, 'der');
|
|
} catch (e) {
|
|
return done(errRes('malformed_request'));
|
|
}
|
|
|
|
self.getResponses(ocspReq, function(err, responses) {
|
|
// Assume not found
|
|
if (err) {
|
|
res.writeHead(404);
|
|
res.end();
|
|
return;
|
|
}
|
|
|
|
return done(responses);
|
|
});
|
|
});
|
|
|
|
};
|
|
|
|
Server.prototype.getResponses = function getResponses(req, cb) {
|
|
var self = this;
|
|
|
|
var reqList = req.tbsRequest.requestList;
|
|
|
|
// TODO(indutny): support signed requests
|
|
async.map(reqList, function(req, cb) {
|
|
self.getResponse(req, cb);
|
|
}, function(err, responses) {
|
|
if (err)
|
|
return cb(err);
|
|
|
|
// TODO(indutny): send extensions
|
|
var basic = {
|
|
tbsResponseData: {
|
|
version: 'v1',
|
|
responderID: {
|
|
type: 'byKey',
|
|
value: self.certID.sha1.issuerKeyHash
|
|
},
|
|
producedAt: new Date(),
|
|
responses: responses
|
|
},
|
|
|
|
signatureAlgorithm: {
|
|
algorithm: ocsp.utils.signRev.sha512WithRSAEncryption
|
|
},
|
|
signature: null
|
|
|
|
// TODO(indutny): send certs?
|
|
};
|
|
|
|
var sign = crypto.createSign('sha512WithRSAEncryption');
|
|
sign.update(rfc2560.ResponseData.encode(basic.tbsResponseData, 'der'));
|
|
basic.signature = {
|
|
unused: 0,
|
|
data: sign.sign(self.key)
|
|
};
|
|
|
|
var res = {
|
|
responseStatus: 'successful',
|
|
responseBytes: {
|
|
responseType: 'id-pkix-ocsp-basic',
|
|
response: rfc2560.BasicOCSPResponse.encode(basic, 'der')
|
|
}
|
|
};
|
|
|
|
cb(null, rfc2560.OCSPResponse.encode(res, 'der'));
|
|
});
|
|
};
|
|
|
|
Server.prototype.getResponse = function getResponse(req, cb) {
|
|
var certID = req.reqCert;
|
|
|
|
var digestId = certID.hashAlgorithm.algorithm.join('.');
|
|
var digest = ocsp.utils.digest[digestId];
|
|
if (!digest)
|
|
return cb(new Error('Unknown digest: ' + digestId));
|
|
|
|
var expectedID = this.certID[digest];
|
|
if (!expectedID)
|
|
return cb(new Error('No pre-generated CertID for digest: ' + digest));
|
|
|
|
if (expectedID.issuerNameHash.toString('hex') !==
|
|
certID.issuerNameHash.toString('hex')) {
|
|
return cb(new Error('Issuer name mismatch'));
|
|
}
|
|
|
|
if (expectedID.issuerKeyHash.toString('hex') !==
|
|
certID.issuerKeyHash.toString('hex')) {
|
|
return cb(new Error('Issuer key mismatch'));
|
|
}
|
|
|
|
var serial = certID.serialNumber.toString(16);
|
|
var cert = this.certs[serial];
|
|
|
|
var response = {
|
|
certId: certID,
|
|
certStatus: null,
|
|
thisUpdate: new Date(),
|
|
nextUpdate: new Date(+new Date() + this.options.nextUpdate)
|
|
};
|
|
if (cert) {
|
|
response.certStatus = cert;
|
|
} else {
|
|
response.certStatus = {
|
|
type: 'unknown',
|
|
value: null
|
|
};
|
|
}
|
|
|
|
cb(null, response);
|
|
};
|