UNPKG

undersign

Version:

Create eIDAS compatible XAdES digital signatures with certificate OCSP responses and timestamps. Works with the Estonian Id-card and Mobile-Id to create BDOCs, but isn't limited to Estonia.

140 lines (118 loc) 4.04 kB
var OcspAsn = require("./ocsp_asn") var StandardError = require("standard-error") var sha1 = require("../lib/crypto").hash.bind(null, "sha1") var fetch = require("./fetch") var SHA1_ALGORITHM = {algorithm: require("./x509_asn").SHA1} exports.OcspResponse = OcspResponse exports.OcspError = OcspError exports.parse = parse var ERROR_CODES = { malformed_request: "malformedRequest", internal_error: "internalError", try_later: "tryLater", sig_required: "sigRequired", unauthorized: "unauthorized" } exports.read = function(issuer, cert, opts) { return exports.request(issuer, cert, opts).then(parse) } exports.request = function(issuer, cert, opts) { var url = opts && opts.url || cert.ocspUrl if (url == null) throw new Error("No OCSP URL in certificate") var req = createRequest(issuer, cert, opts && opts.nonce) // OCSP over HTTP is specified in // https://tools.ietf.org/html/rfc2560#appendix-A.1. return fetch(url, { method: "POST", headers: { // NOTE: Content-Length is required. Otherwise SK's // http://demo.sk.ee/ocsp server throws malformed_request. Accept: "application/ocsp-response", "Content-Type": "application/ocsp-request", "Content-Length": req.length }, body: req }).then((res) => res.body) } function OcspResponse(asn) { this.asn = asn var status = asn.responseStatus if (status != "successful") throw new OcspError(ERROR_CODES[status] || status) var type = asn.responseBytes.responseType if (type != "id-pkix-ocsp-basic") throw new OcspError("unsupportedResponse", null, {type: type}) this.status = status this.type = type this.basic = OcspAsn.BasicOCSPResponse.decode(asn.responseBytes.response) this.certificates = this.basic.tbsResponseData.responses.map(parseCertStatus) } OcspResponse.prototype.toBuffer = function() { return OcspAsn.OCSPResponse.encode(this.asn) } OcspResponse.prototype.toString = function(fmt) { switch (fmt) { case "hex": case "base64": return this.toBuffer().toString(fmt) default: throw new RangeError("Unsupported format: " + fmt) } } function createRequest(issuer, cert, nonce) { // Fields come from RFC 2560 Section 4.1.1. return OcspAsn.OCSPRequest.encode({ tbsRequest: { requestList: [{ reqCert: { // NOTE: SK's http://demo.sk.ee/ocsp doesn't support SHA256, // returning "revoked" every time. // // RFC 2560 Section 4.3 also only lists SHA1 as the hashing algorithm. // https://tools.ietf.org/html/rfc2560#section-4.3 // // RFC 6960 alludes to SHA256, though. // https://tools.ietf.org/html/rfc6960#section-4.3 serialNumber: cert.serialNumber, hashAlgorithm: SHA1_ALGORITHM, issuerNameHash: sha1(cert.issuerDer), issuerKeyHash: sha1(issuer.publicKey) } }], // NOTE: The server MAY respond with the certificate it signed the // response with. To my knowledge there's no way to explicitly ask for // one as there is in the timestamp request. requestExtensions: [{ extnID: OcspAsn.OCSP_RESPONSE_TYPE, extnValue: OcspAsn.ResponseTypes.encode(["id-pkix-ocsp-basic"]) }, nonce ? { extnID: OcspAsn["id-pkix-ocsp-nonce"], extnValue: nonce } : null].filter(Boolean) } }) } var ERROR_MESSAGES = { // RFC 2560 Exception Cases: malformedRequest: "Malformed OCSP Request", internalError: "Internal OCSP Server Error", tryLater: "Try Later", sigRequired: "Client Signature Required", unauthorized: "OCSP Request Unauthorized", // Our specific: unsupportedResponse: "Unsupported OCSP Response Type" } function OcspError(code, msg, props) { this.code = code StandardError.call(this, ERROR_MESSAGES[code] || msg, props) } OcspError.prototype = Object.create(StandardError.prototype, { constructor: {value: OcspError, configurable: true, writeable: true} }) function parseCertStatus(status) { return { at: new Date(status.thisUpdate), serial: status.certId.serialNumber, status: status.certStatus.type } } function parse(der) { return new OcspResponse(OcspAsn.OCSPResponse.decode(der)) }