Using Trusted Timestamping With Java
Trusted timestamping is the process of having a trusted third party (“Time stamping authority”, TSA) certify the time of a given event in electronic form. The EU regulation eIDAS gives these timestamps legal strength – i.e. nobody can dispute the time or the content of the event if it was timestamped. It is applicable to multiple scenarios, including timestamping audit logs. (Note: timestamping is not sufficient for a good audit trail as it does not prevent a malicious actor from deleting the event altogether)
There are a number of standards for trusted timestamping, the core one being RFC 3161. As most RFCs it is hard to read. Fortunately for Java users, BouncyCastle implements the standard. Unfortunately, as with most security APIs, working with it is hard, even abysmal. I had to implement it, so I’ll share the code needed to timestamp data.
The whole gist can be found here, but I’ll try to explain the main flow. Obviously, there is a lot of code that’s there to simply follow the standard. The BouncyCastle classes are a maze that’s hard to navigate.
The main method is obviously timestamp(hash, tsaURL, username, password, tsaPolicyOid)
:
public TimestampResponseDto timestamp(byte[] hash, String tsaUrl, String tsaUsername, String tsaPassword, String tsaPolicyOid) throws IOException { MessageImprint imprint = new MessageImprint(sha512oid, hash); ASN1ObjectIdentifier tsaPolicyId = StringUtils.isNotBlank(tsaPolicyOid) ? new ASN1ObjectIdentifier(tsaPolicyOid) : baseTsaPolicyId; TimeStampReq request = new TimeStampReq(imprint, tsaPolicyOid, new ASN1Integer(random.nextLong()), ASN1Boolean.TRUE, null); byte[] body = request.getEncoded(); try { byte[] responseBytes = getTSAResponse(body, tsaUrl, tsaUsername, tsaPassword); ASN1StreamParser asn1Sp = new ASN1StreamParser(responseBytes); TimeStampResp tspResp = TimeStampResp.getInstance(asn1Sp.readObject()); TimeStampResponse tsr = new TimeStampResponse(tspResp); checkForErrors(tsaUrl, tsr); // validate communication level attributes (RFC 3161 PKIStatus) tsr.validate(new TimeStampRequest(request)); TimeStampToken token = tsr.getTimeStampToken(); TimestampResponseDto response = new TimestampResponseDto(); response.setTime(getSigningTime(token.getSignedAttributes())); response.setEncodedToken(Base64.getEncoder().encodeToString(token.getEncoded())); return response; } catch (RestClientException | TSPException | CMSException | OperatorCreationException | GeneralSecurityException e) { throw new IOException(e); } }
It prepares the request by creating the message imprint. Note that you are passing the hash itself, but also the hashing algorithm used to make the hash. Why isn’t the API hiding that from you, I don’t know. In my case the hash is obtained in a more complicated way, so it’s useful, but still. Then we get the raw form of the request and send it to the TSA (time stamping authority). It is an HTTP request, sort of simple, but you have to take care of some request and response headers that are not necessarily consistent across TSAs. The username and password are optional, some TSAs offer the service (rate-limited) without authentication. Also note the tsaPolicyOid – most TSAs have their specific policy that is documented on their page and you should get the OID from there.
When you have the raw response back, you parse it to a TimeStampResponse. Again, you have to go through 2 intermediate objects (ASN1StreamParser and TimeStampResp), which may be a proper abstraction, but is not a usable API.
Then you check if the response was successful, and you also have to validate it – the TSA may have returned a bad response. Ideally all of that could’ve been hidden from you. Validation throws an exception, which in this case I just propagate by wrapping in an IOException.
Finally, you get the token and return the response. The most important thing is the content of the token, which in my case was needed as Base64, so I encode it. It could just be the raw bytes as well. If you want to get any additional data from the token (e.g. the signing time), it’s not that simple; you have to parse the low-level attributes (seen in the gist).
Okay, you have the token now, and you can store it in a database. Occasionally you may want to validate whether timestamps have not been tampered with (which is my usecase). The code is here, and I won’t even try to explain it – it’s a ton of boilerplate that is also accounting for variations in the way TSAs respond (I’ve tried a few). The fact that a DummyCertificate class is needed either means I got something very wrong, or confirms my critique for the BouncyCastle APIs. The DummyCertificate may not be needed for some TSAs, but it is for others, and you actually can’t instantiate it that easily. You need a real certificate to construct it (which is not included in the gist; using the init() method in the next gist you can create the dummy with dummyCertificate = new DummyCertificate(certificateHolder.toASN1Structure());
). In my code these are all one class, but for presenting them I decided to split it, hence this little duplication.
Okay, now we can timestamp and validate timestamps. That should be enough; but for testing purposes (or limited internal use) you may want to do the timestamping locally instead of asking a TSA. The code can be found here. It uses spring, but you can instead pass the keystore details as arguments to the init method. You need a JKS store with a keypair and a certificate, and I used KeyStore Explorer to create them. If you are running your application in AWS, you may want to encrypt your keystore using KMS (Key Management Service), and then decrypt it on application load, but that’s out of the scope of this article. For the local timestamping validation works as expected, and for timestamping – instead of calling the external service, just call localTSA.timestamp(req);
How did I get to know which classes to instantiate and which parameters to pass – I don’t remember. Looking at tests, examples, answers, sources. It took a while, and so I’m sharing it, to potentially save some trouble of others.
A list of TSAs you can test with: SafeCreative, FreeTSA, time.centum.pl.
I realize this does not seem applicable to many scenarios, but I would recommend timestamping some critical pieces of your application data. And it is generally useful to have it in your “toolbox”, ready to use, rather than trying to read the standard and battling with BouncyCastle classes for days in order to achieve this allegedly simple task.
Published on Java Code Geeks with permission by Bozhidar Bozhanov, partner at our JCG program. See the original article here: Using Trusted Timestamping With Java Opinions expressed by Java Code Geeks contributors are their own. |