Skip to content

Instantly share code, notes, and snippets.

@Glamdring
Last active March 26, 2019 08:20
Show Gist options
  • Star 9 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save Glamdring/c452531e97073a9ab259b987b62bbd77 to your computer and use it in GitHub Desktop.
Save Glamdring/c452531e97073a9ab259b987b62bbd77 to your computer and use it in GitHub Desktop.
Using trusted timestamps
public class TimestampService {
private static final Logger logger = LoggerFactory.getLogger(TimestampService.class);
private static final AlgorithmIdentifier sha512oid = getSha512Oid();
private static final ASN1ObjectIdentifier baseTsaPolicyId = new ASN1ObjectIdentifier("0.4.0.2023.1.1");
private SecureRandom random = new SecureRandom();
private static AlgorithmIdentifier getSha512Oid() {
DigestAlgorithmIdentifierFinder algorithmFinder = new DefaultDigestAlgorithmIdentifierFinder();
return algorithmFinder.find("SHA-512");
}
public TimestampResponseDto timestamp(byte[] hash, String tsaUrl, String tsaUsername, String tsaPassword, tsaPolicyOid) throws IOException {
MessageImprint imprint = new MessageImprint(sha512oid, hash);
ASN1ObjectIdentifier tsaPolicyId = StringUtils.isNotBlank(tsaPolicyOid) ? new ASN1ObjectIdentifier(tsaPolicyOid) : baseTsaPolicyId;
TimeStampReq request = new TimeStampReq(imprint, tsaPolicyId, 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);
}
}
private void checkForErrors(String tsaUrl, TimeStampResponse tsr) throws IOException {
PKIFailureInfo failure = tsr.getFailInfo();
int value = (failure == null) ? 0 : failure.intValue();
if (value != 0) {
throw new IOException("Invalid TSA '" + tsaUrl + "' response, code " + value);
}
}
private LocalDateTime getSigningTime(AttributeTable signedAttrTable) throws CMSException {
ASN1EncodableVector v = signedAttrTable.getAll(CMSAttributes.signingTime);
switch (v.size()) {
case 0:
return null;
case 1: {
Attribute t = (Attribute) v.get(0);
ASN1Set attrValues = t.getAttrValues();
if (attrValues.size() != 1) {
throw new CMSException("A signingTime attribute MUST have a single attribute value");
}
Date date = Time.getInstance(attrValues.getObjectAt(0).toASN1Primitive()).getDate();
LocalDateTime ldt = LocalDateTime.ofInstant(date.toInstant(), ZoneId.of("UTC"));
return ldt;
}
default:
throw new CMSException(
"The SignedAttributes in a signerInfo MUST NOT include multiple instances of the signingTime attribute");
}
}
protected byte[] getTSAResponse(byte[] requestBytes, String tsaUrl, String tsaUsername, String tsaPassword) throws IOException {
URL url = new URL(tsaUrl);
URLConnection tsaConnection = url.openConnection();
tsaConnection.setConnectTimeout(5000);
tsaConnection.setDoInput(true);
tsaConnection.setDoOutput(true);
tsaConnection.setUseCaches(false);
tsaConnection.setRequestProperty("Content-Type", "application/timestamp-query");
tsaConnection.setRequestProperty("Content-Transfer-Encoding", "binary");
if (StringUtils.isNotBlank(tsaUsername)) {
String userPassword = tsaUsername + ":" + tsaPassword;
tsaConnection.setRequestProperty("Authorization", "Basic "
+ Base64.getEncoder().encodeToString(userPassword.getBytes()));
}
OutputStream out = tsaConnection.getOutputStream();
out.write(requestBytes);
out.close();
byte[] respBytes = null;
try (InputStream input = tsaConnection.getInputStream()) {
respBytes = IOUtils.toByteArray(input);
}
String encoding = tsaConnection.getContentEncoding();
if (encoding != null && encoding.equalsIgnoreCase("base64")) {
respBytes = Base64.getDecoder().decode(respBytes);
}
return respBytes;
}
public static class TimestampResponseDto {
private String encodedToken;
private LocalDateTime time;
public String getEncodedToken() {
return encodedToken;
}
public void setEncodedToken(String encodedToken) {
this.encodedToken = encodedToken;
}
public LocalDateTime getTime() {
return time;
}
public void setTime(LocalDateTime time) {
this.time = time;
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment