Last active
March 26, 2019 08:20
-
-
Save Glamdring/c452531e97073a9ab259b987b62bbd77 to your computer and use it in GitHub Desktop.
Using trusted timestamps
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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