Skip to content

Instantly share code, notes, and snippets.

@Glamdring
Created December 17, 2017 08:39
Show Gist options
  • Save Glamdring/f4b5b73e4de5b4f6bcdb7151c3fb6c0d to your computer and use it in GitHub Desktop.
Save Glamdring/f4b5b73e4de5b4f6bcdb7151c3fb6c0d to your computer and use it in GitHub Desktop.
Validating trusted timestamps
public class TimestampValidationService {
public boolean validate(String hash, String encodedTimestampToken) {
try {
byte[] tokenBytes = Base64.getDecoder().decode(encodedTimestampToken);
CMSSignedData signedData = new CMSSignedData(tokenBytes);
TimeStampToken token = new TimeStampToken(signedData);
Optional<X509CertificateHolder> certHolder = getCertificateHolder(signedData);
BcRSASignerInfoVerifierBuilder verifierBuilder = new BcRSASignerInfoVerifierBuilder(
new DefaultCMSSignatureAlgorithmNameGenerator(),
new DefaultSignatureAlgorithmIdentifierFinder(),
new DefaultDigestAlgorithmIdentifierFinder(), new BcDigestCalculatorProvider());
if (certHolder.isPresent()) {
token.validate(verifierBuilder.build(certHolder.get()));
return validateContentHash(hash, token.getTimeStampInfo());
} else {
// do not verify the certificate, but verify everything else
boolean result = token.isSignatureValid(verifierBuilder.build(dummyCertificate));
if (result) {
return validateContentHash(timestampGroupHash, token.getTimeStampInfo());
} else {
return false;
}
}
} catch (Exception ex) {
logger.error("Failed to validate timestamp", ex);
return false;
}
}
public TimestampInfo getTimestampInfo(String encodedTimestampToken) {
try {
byte[] tokenBytes = Base64.getDecoder().decode(encodedTimestampToken);
CMSSignedData signedData = new CMSSignedData(tokenBytes);
TimeStampToken token = new TimeStampToken(signedData);
TimeStampTokenInfo rawInfo = token.getTimeStampInfo();
TimestampInfo result = new TimestampInfo();
result.setAccuracy(rawInfo.getGenTimeAccuracy() != null ? rawInfo.getGenTimeAccuracy().toString() : null);
result.setTime(rawInfo.getGenTime() != null ? rawInfo.getGenTime().getTime() : 0);
result.setPolicy(rawInfo.getPolicy() != null ? rawInfo.getPolicy().getId() : null);
result.setSerialNumber(rawInfo.getSerialNumber());
result.setTsa(rawInfo.getTsa() != null ? rawInfo.getTsa().toString() : null);
result.setEncoded(encodedTimestampToken);
return result;
} catch (Exception e) {
throw new IllegalStateException("Failed to parse token", e);
}
}
/**
* We need to validate that the hash that has been timestamped is actually the one that we expect.
*/
private boolean validateContentHash(String expectedData, TimeStampTokenInfo info) throws Exception {
byte[] contentDigest = info.getMessageImprintDigest();
return Arrays.equals(Base64.getUrlDecoder().decode(expectedData), contentDigest);
}
@SuppressWarnings("unchecked")
private Optional<X509CertificateHolder> getCertificateHolder(CMSSignedData signedData) throws IOException {
CollectionStore<X509CertificateHolder> store = (CollectionStore<X509CertificateHolder>) signedData
.getCertificates();
Iterator<X509CertificateHolder> iterator = store.iterator();
if (iterator.hasNext()) {
return Optional.of(store.iterator().next());
} else {
logger.debug("No certificate found in signed data, probably the TSA did not provide it");
return Optional.empty();
}
}
/**
* Always-valid dummy certificate holder for cases when no certificate was
* provided by TSA, but we need to pass some certificate to the BC
* validation classes
*
*/
private static final class DummyCertificate extends X509CertificateHolder {
public DummyCertificate(Certificate cert) throws IOException {
super(cert);
}
@Override
public boolean isValidOn(Date date) {
return true;
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment