Skip to content

Commit

Permalink
Permit a timestamp response without a status string.
Browse files Browse the repository at this point in the history
  • Loading branch information
moll committed Nov 27, 2023
1 parent 8d2b973 commit 3c29a0b
Show file tree
Hide file tree
Showing 3 changed files with 75 additions and 5 deletions.
13 changes: 10 additions & 3 deletions lib/timestamp.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,11 +39,18 @@ function createRequest(digest) {

function TimestampResponse(asn) {
this.asn = asn

// Status is defined in RFC 2510 under PKIStatus:
// https://datatracker.ietf.org/doc/html/rfc2510#section-3.2.3
this.status = asn.status.status

// https://tools.ietf.org/html/rfc2510#section-3.1.1
// Every message should contain a language tag, but they don't seem to.
this.message = asn.status.statusString.join("; ")
// Every PKIFreeText[2] message in a TimeStampResp[1] should contain
// a language tag, but I've yet to see one.
//
// [1]: https://datatracker.ietf.org/doc/html/rfc3161#section-2.4.2
// [2]: https://tools.ietf.org/html/rfc2510#section-3.1.1
this.message =
asn.status.statusString && asn.status.statusString.join("; ") || null

var type = asn.timeStampToken.contentType

Expand Down
5 changes: 5 additions & 0 deletions lib/underscore.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,11 @@ exports.map = function(obj, fn) {
return array
}

exports.split2 = function(array, at) {
var index = array.indexOf(at)
return index >= 0 ? [array.slice(0, index), array.slice(index + 1)] : [array]
}

exports.fromEntries = function(array) {
return array.reduce((obj, kv) => (obj[kv[0]] = kv[1], obj), {})
}
Expand Down
62 changes: 60 additions & 2 deletions test/lib/timestamp_test.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
var _ = require("../../lib/underscore")
var X509Asn = require("../../lib/x509_asn")
var Timestamp = require("../../lib/timestamp")
var TimestampResponse = Timestamp.TimestampResponse
Expand All @@ -6,7 +7,8 @@ var TimestampRequestAsn = TimestampAsn.TimestampRequest
var TimestampResponseAsn = TimestampAsn.TimestampResponse
var wait = require("../../lib/promise").wait
var parseBody = require("../mitm").parseBody
var TIMESTAMP_URL = "http://example.com/tsa"
var parse = TimestampResponseAsn.decode.bind(TimestampResponseAsn)
var serialize = TimestampResponseAsn.encode.bind(TimestampResponseAsn)
var DIGEST = Buffer.from("foobar")
var SIGNED_DATA_OID = TimestampAsn.SIGNED_DATA_OID

Expand All @@ -15,7 +17,7 @@ describe("Timestamp", function() {
require("../mitm")()

it("must respond with timestamp", function*() {
var timestamp = Timestamp.read(TIMESTAMP_URL, DIGEST)
var timestamp = Timestamp.read("http://example.com/tsa", DIGEST)

var req = yield wait(this.mitm, "request")
req.headers.host.must.equal("example.com")
Expand Down Expand Up @@ -45,10 +47,66 @@ describe("Timestamp", function() {
timestamp.status.must.equal("granted")
timestamp.token.must.eql(TimestampAsn.ContentInfo.encode(token))
})

it("must request with basic auth if given a password", function*() {
Timestamp.read("http://foo:[email protected]/tsa", DIGEST)

var req = yield wait(this.mitm, "request")
req.headers.host.must.equal("example.com")
parseBasicAuth(req.headers.authorization).must.eql(["foo", "bar"])
req.headers["content-type"].must.equal("application/timestamp-query")
req.headers.accept.must.equal("application/timestamp-reply")
req.method.must.equal("POST")
req.url.must.equal("/tsa")
})
})

describe("TimestampResponse", function() {
it("must parse minimal response", function() {
var token = {
contentType: SIGNED_DATA_OID,
content: TimestampAsn.SignedData.encode(Buffer.from("xyz"))
}

var res = new TimestampResponse(parse(serialize({
status: {status: "granted"},
timeStampToken: token
})))

res.status.must.equal("granted")
res.must.have.property("message", null)
res.token.must.eql(TimestampAsn.ContentInfo.encode(token))
})

it("must parse statusString", function() {
var token = {
contentType: SIGNED_DATA_OID,
content: TimestampAsn.SignedData.encode(Buffer.from("xyz"))
}

var res = new TimestampResponse(parse(serialize({
status: {
status: "granted",
statusString: ["Operation Okay", "Life is Good"]
},

timeStampToken: token
})))

res.status.must.equal("granted")
res.message.must.equal("Operation Okay; Life is Good")
res.token.must.eql(TimestampAsn.ContentInfo.encode(token))
})
})
})

function respond(timestamp, req) {
req.res.setHeader("Content-Type", "application/timestamp-reply")
req.res.end(TimestampResponseAsn.encode(timestamp))
}

function parseBasicAuth(line) {
var auth = _.split2(line, " ")
if (auth[0] != "Basic") return null
return _.split2(_.parseBase64(auth[1]).toString(), ":")
}

0 comments on commit 3c29a0b

Please sign in to comment.