package keywhiz.service.resources.automation.v2;
import com.google.common.base.Throwables;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.time.Instant;
import java.util.Iterator;
import javax.annotation.Nullable;
import org.bouncycastle.openpgp.PGPException;
import org.bouncycastle.openpgp.PGPPublicKey;
import org.bouncycastle.openpgp.PGPPublicKeyRing;
import org.bouncycastle.openpgp.jcajce.JcaPGPPublicKeyRingCollection;
import org.bouncycastle.util.io.pem.PemObject;
import org.bouncycastle.util.io.pem.PemReader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static java.nio.charset.StandardCharsets.UTF_8;
import static java.util.Collections.list;
/** Helper class to extract expirations from secrets contents (best effort) */
public final class ExpirationExtractor {
private static final Logger logger = LoggerFactory.getLogger(ExpirationExtractor.class);
private ExpirationExtractor() {}
@Nullable public static Instant expirationFromKeystore(String type, String password, byte[] content) {
KeyStore ks;
try {
ks = KeyStore.getInstance(type);
} catch (KeyStoreException e) {
// Should never occur (assuming JCE is installed)
throw Throwables.propagate(e);
}
try {
ks.load(new ByteArrayInputStream(content), password.toCharArray());
} catch (IOException | NoSuchAlgorithmException | CertificateException e) {
// Failed to parse
logger.info("Failed to parse keystore", e);
return null;
}
Instant earliest = null;
try {
for (String alias : list(ks.aliases())) {
Certificate[] chain = ks.getCertificateChain(alias);
if (chain == null) {
Certificate certificate = ks.getCertificate(alias);
if (certificate == null) {
// No certs in this entry
continue;
}
chain = new Certificate[]{certificate};
}
for (Certificate cert : chain) {
if (cert instanceof X509Certificate) {
X509Certificate c = (X509Certificate) cert;
if (earliest == null || c.getNotAfter().toInstant().isBefore(earliest)) {
earliest = c.getNotAfter().toInstant();
}
}
}
}
} catch (KeyStoreException e) {
// Should never occur (ks was initialized)
throw Throwables.propagate(e);
}
return earliest;
}
@Nullable public static Instant expirationFromOpenPGP(byte[] content) {
JcaPGPPublicKeyRingCollection collection;
try {
collection = new JcaPGPPublicKeyRingCollection(new ByteArrayInputStream(content));
} catch (IOException | PGPException e) {
// Unable to parse
logger.info("Failed to parse OpenPGP keyring", e);
return null;
}
Instant earliest = null;
// Iterate over all key rings in file
Iterator rings = collection.getKeyRings();
while (rings.hasNext()) {
Object ringItem = rings.next();
if (ringItem instanceof PGPPublicKeyRing) {
PGPPublicKeyRing ring = (PGPPublicKeyRing) ringItem;
// Iterate over all keys in ring
Iterator keys = ring.getPublicKeys();
while (keys.hasNext()) {
Object keyItem = keys.next();
if (keyItem instanceof PGPPublicKey) {
PGPPublicKey key = (PGPPublicKey) keyItem;
// Get validity for key (zero means no expiry)
long validSeconds = key.getValidSeconds();
if (validSeconds > 0) {
Instant expiry = key.getCreationTime().toInstant().plusSeconds(validSeconds);
if (earliest == null || expiry.isBefore(earliest)) {
earliest = expiry;
}
}
}
}
}
}
return earliest;
}
@Nullable public static Instant expirationFromEncodedCertificateChain(byte[] content) {
PemReader reader = new PemReader(new InputStreamReader(new ByteArrayInputStream(content), UTF_8));
PemObject object;
try {
object = reader.readPemObject();
} catch (IOException e) {
// Should never occur (reading form byte array)
throw Throwables.propagate(e);
}
Instant earliest = null;
while (object != null) {
if (object.getType().equals("CERTIFICATE")) {
Instant expiry = expirationFromRawCertificate(object.getContent());
if (earliest == null || expiry.isBefore(earliest)) {
earliest = expiry;
}
}
try {
object = reader.readPemObject();
} catch (IOException e) {
// Should never occur (reading form byte array)
throw Throwables.propagate(e);
}
}
return earliest;
}
@Nullable public static Instant expirationFromRawCertificate(byte[] content) {
CertificateFactory cf;
try {
cf = CertificateFactory.getInstance("X.509");
} catch (CertificateException e) {
// Should never happen (X.509 supported by default)
throw Throwables.propagate(e);
}
X509Certificate cert;
try {
cert = (X509Certificate) cf.generateCertificate(new ByteArrayInputStream(content));
} catch (CertificateException e) {
// Certificate must have been invalid
logger.info("Failed to parse certificate", e);
return null;
}
return cert.getNotAfter().toInstant();
}
}