Trusted time

TL;DR: This is an implementation of a Time Stamp Authority (RFC 3161) using JWS. Source is available at Github.

Time stamping is an increasingly valuable complement to digital signing practices, enabling organizations to record when a digital item—such as a message, document, transaction or piece of software—was signed. For some applications, the timing of a digital signature is critical, as in the case of stock trades, lottery ticket issuance and some legal proceedings. Even when time is not intrinsic to the application, time stamping is helpful for record keeping and audit processes, because it provides a mechanism to prove whether the digital certificate was valid at the time it was used.

History

Time Stamp Authorities are not a new concept.

For example, when Robert Hooke discovered Hooke's law in 1660, he did not want to publish it yet, but wanted to be able to claim priority. So he published the anagram ceiiinosssttuv and later published the translation ut tensio sic vis (Latin for "as is the extension, so is the force").
– Wikipedia

Similarly, we may want to be able to publish a hash of some data to claim ownership of the data at a specific time.

This has been possible in trusted digital time stamping for quite some time too. So really there is little news here. RFC 3161 services have been around for quite some time, there are even a handful free servers available.

Current state of affairs

OpenSSL has some command line tools for requesting and verifying time stamps. The steps are (courtesy of DigiStamp):

Create a time stamp query

openssl ts -query -data "YOUR FILE" -cert -sha256 -no_nonce -out request.tsq

Send it to a time stamp authority

cat request.tsq | curl -s -S -H 'Content-Type: application/timestamp-query' --data-binary @- https://ACCT:PASSWORD@tsa.digistamp.com -o response.tsr

Before being able to verify the response you will need a root certificate from the Time Stamp Authority

curl -L -O https://www.digistamp.com/pubkeys/com.digistamp.bundle.pem

Finally, verify the response

openssl ts -verify -in response.tsr -data "your original timestamped file" -CAfile com.digistamp.bundle.pem

To see the trusted time stamp

openssl ts -reply -in response.tsr -text

The contents of the time stamp will look something like this:

Status info:
Status: Granted.
Status description: unspecified
Failure info: unspecified

TST info:
Version: 1
Policy OID: 2.16.840.1.113733.1.7.23.3
Hash Algorithm: sha256
Message data:
    0000 - 06 07 9c 9d 85 79 48 da-50 15 aa 83 51 5e d9 00   .....yH.P...Q^..
    0010 - bb 2d 0c d0 bb 26 cd b0-fe c5 0a 2d 94 47 b3 84   .-...&.....-.G..
Serial number: 0x9C90E88CA3EE000856460DA154626879700E534B
Time stamp: Dec 14 09:53:46 2019 GMT
Accuracy: 0x1E seconds, unspecified millis, unspecified micros
Ordering: no
Nonce: 0xAE8ACCADCFBF2D99
TSA: DirName:/C=US/O=DigiStamp/OU=DigiStamp/CN=DigiStamp SHA256 TimeStamping Signer
Extensions:

A better way

This is all nice and well, but it would be even better if there was a way to integrate it in modern tool chains.

The RFC 3161 specifies a binary protocol for requesting and providing secure time stamps. The protocol specifies a request which includes a hash of the data to be timestamped and a policy under which to time stamp it.

This project provides the same data points while utilising modern technologies to achieve the same goals. Using the proposed standard RFC 7515 to encode a signed time stamp from the Time-Stamp Authority.

The new request format

A time stamp request is now made in the form of a HTTP POST request with a JSON body:

{
  "version": 1,
  "messageImprint": {
    "hashAlgorithm": "sha1",
    "hashedMessage": "40f1f310cbae011642d33edbad742ea3c581864d"
  },
  "context": "some context",
  "nonce": "3455345232345454"
}

The new response format

The response is encoded as a JWS (one existing use is JWT, which is in use in many places). It is composed of three base 64 encoded parts, separated by a dot.

For example, a response could look like this:

eyJ0eXAiOiJUaW1lLVN0YW1wIiwiYWxnIjoiU0hBMXdpdGhSU0EifQ==.eyJzdGF0dXMiOnsic3RhdHVzIjowfSwidGltZVN0YW1wVG9rZW4iOnsiY29udGVudFR5cGUiOiJpZC1zaWduZWREYXRhIiwiY29udGVudCI6eyJ2ZXJzaW9uIjoxLCJwb2xpY3kiOiJwb2xpY3kxIiwibWVzc2FnZUltcHJpbnQiOiI0MGYxZjMxMGNiYWUwMTE2NDJkMzNlZGJhZDc0MmVhM2M1ODE4NjRkIiwic2VyaWFsTnVtYmVyIjozLCJnZW5UaW1lIjoiMjAxOTEyMTQxNDIzNTRaIiwiYWNjdXJhY3kiOnsic2Vjb25kcyI6MCwibWlsbGlzIjowLCJtaWNyb3MiOjB9LCJvcmRlcmluZyI6ZmFsc2UsIm5vbmNlIjozNDU1MzQ1MjMyMzQ1NDU0fX19.MjSEHeVaLsDgAmxOp/qhFLhGFf+tjadQMpgApRoIRy4/632C1QakRZ+U+YxjgzkiWyGxJ3bgnH0UwOM0Ytfq+GDXHabmpihws+2de32WiKoOa/ZYly88aIndP/apDQdNhH1cUaxHi4haw68x+G3WR67H/bdlS19hZROROcM7784zIthJ86JqG8We/xOVzhgX1rqnCWAeDsmRkVYGqDsrgxvz/Fe6FHqIkRqQnSHnosI44h/RYi2sSo7OI78GzNCWNN7TrIOOGXEUmGowd6m2wiFN/2EhpreaQLyy1Z6rCFzEDiSaGRcqEptvgj6nVGjerKDyQ3DV/5OfvZe4ADjP1YczRf6MJ0p6PWNTpXO4w8LrF3KbgRbQkQo1SWxlDsAJFHM0+3SvsNCjBSGg5lRezi4pNj3kK5zZ+dYxEgge74O884fM2CI+dMVr+BB07CWHOibHDAjNTAhr2ar1rZZa0UUjehbKl9t7ZPkTOy7I9apVTjddcHcFJnR/qGjMv9gY1Uj4/dzYtO2sFshe1QJUaJXPY9p8J5aF9dKqWjqmCaXtTxDDsfv8qvA4pja2LpI5/Kc6rZ2aTKxBKD+Tzonr7PaK5DnDZbt94qigb1Y8BerYUiQCp1aG8Piv/cSwuDLWZ8y/cm9rfYDgS7Gut4P8dGcBER45dFZJSEs9BnuCFxY=

The components can be found by splitting on "." and base 64 decoding them into:

Header:

{
  "alg": "RS256",
  "x5u": "https://tsa.stated.at/cert",
  "typ": "application/timestamp-reply+jws",
  "jku": "https://tsa.stated.at/jwk"
}

Payload:

{
  "status": {
    "status": 0
  },
  "timeStampToken": {
    "contentType": "id-signedData",
    "content": {
      "version": 1,
      "context": "some context",
      "messageImprint": "40f1f310cbae011642d33edbad742ea3c581864d",
      "serialNumber": 1,
      "genTime": "2019-12-17T13:52:22+0000",
      "accuracy": {
        "seconds": 0,
        "millis": 0,
        "micros": 0
      },
      "ordering": false,
      "nonce": 3455345232345454
    }
  }
}

The signature is binary data which can be used to verify the payload using the certificate which can be obtained from the endpoint found in the header.

∙   ∙   ∙

It's available today

You can use the free service available by making a POST request as described above to https://tsa.stated.at/sign or self-host. The service source code is available at tsa-json.

Libraries are available today which can read and verify JWS data:

C/C++: cjose
C#: JWT for .NET 3.5+ (and many others)
Haskell: jose-jwt
Java: jose4j (and many others)
Javascript: jsrsasign (and many others)
Ruby: json-jwt
PHP: phpOIDC
Python: pyjwkest

Example

The following is an example of time stamping some data in javascript. It is assumed that the data exists elsewhere and has been hashed using a sha1 hashing algorithm.

const request = require('request-promise');
const jose = require('jose');

(async () => {
  const response = await request({
    method: 'POST',
    uri: 'https://tsa.stated.at/sign',
    body: JSON.stringify({
      version: 1,
      messageImprint: {
        hashAlgorithm: 'sha1',
        hashedMessage: '40f1f310cbae011642d33edbad742ea3c581864d'
      },
      context: 'some context',
      nonce: '234534534534'
    })
  });
  const token = jose.JWT.decode(response, { complete: true });
  const keyset = await request(token.header.jku, { json: true });
  // Get the first key from the set of public keys,
  // for the purposes of this example
  const key = jose.JWK.asKey(keyset.keys[0]);
  const valid = jose.JWS.verify(response, key);
  if (!valid) return;
  const success = token.payload.status.status == 0;
  if (!success) return;
  const timestamp = Date.parse(token
      .payload
      .timeStampToken
      .content
      .genTime
  );
  console.log('signed time stamp: ' + timestamp);
})();

Final thoughts

Given this, we can now create trusted time stamps for data that we have available to us and publicly make claims of ownership of said data at the present time, and we can do this using current tools in modern tool chains.