24.2 Certificate Operations and Validation
Certificate operations include generation, validation, conversion, and chain building. Proper certificate handling is crucial for secure communications.
Certificate Loading and Parsing
// Loading Certificates
public class CertificateLoading {
// Load X.509 certificate from file
public static java.security.cert.X509Certificate loadCertificate(
String certificatePath) throws Exception {
CertificateFactory cf = CertificateFactory.getInstance("X.509");
try (InputStream is = new FileInputStream(certificatePath)) {
Certificate cert = cf.generateCertificate(is);
return (java.security.cert.X509Certificate) cert;
}
}
// Load certificate from PEM string
public static java.security.cert.X509Certificate loadCertificateFromPEM(
String pemString) throws Exception {
CertificateFactory cf = CertificateFactory.getInstance("X.509");
byte[] certBytes = java.util.Base64.getDecoder().decode(
pemString.replaceAll("-----BEGIN CERTIFICATE-----", "")
.replaceAll("-----END CERTIFICATE-----", "")
.replaceAll("\\s", ""));
try (InputStream is = new ByteArrayInputStream(certBytes)) {
Certificate cert = cf.generateCertificate(is);
return (java.security.cert.X509Certificate) cert;
}
}
// Load certificate from DER bytes
public static java.security.cert.X509Certificate loadCertificateFromDER(
byte[] derBytes) throws Exception {
CertificateFactory cf = CertificateFactory.getInstance("X.509");
try (InputStream is = new ByteArrayInputStream(derBytes)) {
Certificate cert = cf.generateCertificate(is);
return (java.security.cert.X509Certificate) cert;
}
}
// Load multiple certificates (certificate chain)
public static List<java.security.cert.X509Certificate> loadCertificateChain(
String chainPath) throws Exception {
CertificateFactory cf = CertificateFactory.getInstance("X.509");
List<java.security.cert.X509Certificate> chain = new ArrayList<>();
try (InputStream is = new FileInputStream(chainPath)) {
Collection<? extends Certificate> certificates =
cf.generateCertificates(is);
for (Certificate cert : certificates) {
chain.add((java.security.cert.X509Certificate) cert);
}
}
return chain;
}
// Example: Load and display certificate info
public static void demonstrateCertificateLoading(String certPath)
throws Exception {
java.security.cert.X509Certificate cert = loadCertificate(certPath);
System.out.println("=== CERTIFICATE INFO ===");
System.out.println("Subject: " + cert.getSubjectDN());
System.out.println("Issuer: " + cert.getIssuerDN());
System.out.println("Serial: " + cert.getSerialNumber());
System.out.println("Valid from: " + cert.getNotBefore());
System.out.println("Valid until: " + cert.getNotAfter());
System.out.println("Signature algorithm: " + cert.getSigAlgName());
}
}
Certificate Export and Conversion
// Certificate Export
public class CertificateExport {
// Export certificate to PEM format
public static String exportToPEM(java.security.cert.X509Certificate cert)
throws Exception {
byte[] encoded = cert.getEncoded();
String base64 = java.util.Base64.getEncoder().encodeToString(encoded);
StringBuilder pem = new StringBuilder();
pem.append("-----BEGIN CERTIFICATE-----\n");
// Split into 64-character lines
for (int i = 0; i < base64.length(); i += 64) {
int end = Math.min(i + 64, base64.length());
pem.append(base64.substring(i, end)).append("\n");
}
pem.append("-----END CERTIFICATE-----\n");
return pem.toString();
}
// Export certificate to DER format
public static byte[] exportToDER(java.security.cert.X509Certificate cert)
throws Exception {
return cert.getEncoded();
}
// Save certificate to file (PEM)
public static void saveCertificatePEM(java.security.cert.X509Certificate cert,
String outputPath) throws Exception {
String pem = exportToPEM(cert);
Files.writeString(Paths.get(outputPath), pem);
System.out.println("Saved certificate to: " + outputPath);
}
// Save certificate to file (DER)
public static void saveCertificateDER(java.security.cert.X509Certificate cert,
String outputPath) throws Exception {
byte[] der = exportToDER(cert);
Files.write(Paths.get(outputPath), der);
System.out.println("Saved certificate (DER) to: " + outputPath);
}
// Export certificate chain
public static void exportCertificateChain(Certificate[] chain,
String outputPath) throws Exception {
StringBuilder pem = new StringBuilder();
for (Certificate cert : chain) {
java.security.cert.X509Certificate x509 =
(java.security.cert.X509Certificate) cert;
pem.append(exportToPEM(x509));
pem.append("\n");
}
Files.writeString(Paths.get(outputPath), pem.toString());
System.out.println("Saved certificate chain to: " + outputPath);
}
// Example: Export KeyStore entry to PEM files
public static void exportKeyStoreEntryToPEM(KeyStore keyStore, String alias,
String keyPassword,
String certOutputPath,
String keyOutputPath)
throws Exception {
// Export certificate
java.security.cert.X509Certificate cert =
(java.security.cert.X509Certificate) keyStore.getCertificate(alias);
saveCertificatePEM(cert, certOutputPath);
// Export private key (would need additional encoding)
PrivateKey privateKey = (PrivateKey) keyStore.getKey(
alias, keyPassword.toCharArray());
// Note: Proper private key PEM export requires additional libraries
// or manual PKCS#8 encoding
System.out.println("Private key export requires PKCS#8 encoding");
}
}
Certificate Validation
// Certificate Validation
public class CertificateValidation {
// Check certificate expiration
public static boolean isExpired(java.security.cert.X509Certificate cert) {
try {
cert.checkValidity();
return false;
} catch (CertificateExpiredException e) {
return true;
} catch (CertificateNotYetValidException e) {
return true;
}
}
// Get days until expiration
public static long getDaysUntilExpiration(
java.security.cert.X509Certificate cert) {
Date notAfter = cert.getNotAfter();
Date now = new Date();
long diff = notAfter.getTime() - now.getTime();
return java.util.concurrent.TimeUnit.MILLISECONDS.toDays(diff);
}
// Check if certificate will expire soon
public static boolean isExpiringSoon(java.security.cert.X509Certificate cert,
int warningDays) {
long daysUntilExpiry = getDaysUntilExpiration(cert);
return daysUntilExpiry >= 0 && daysUntilExpiry <= warningDays;
}
// Validate certificate is within validity period
public static void validateDateRange(java.security.cert.X509Certificate cert)
throws CertificateException {
cert.checkValidity();
System.out.println("✓ Certificate is valid");
}
// Validate certificate signature
public static void validateSignature(java.security.cert.X509Certificate cert,
PublicKey issuerPublicKey)
throws Exception {
try {
cert.verify(issuerPublicKey);
System.out.println("✓ Certificate signature is valid");
} catch (Exception e) {
System.err.println("✗ Invalid certificate signature: " + e.getMessage());
throw e;
}
}
// Validate self-signed certificate
public static boolean isSelfSigned(java.security.cert.X509Certificate cert)
throws Exception {
try {
cert.verify(cert.getPublicKey());
return true;
} catch (Exception e) {
return false;
}
}
// Comprehensive certificate validation
public static ValidationResult validateCertificate(
java.security.cert.X509Certificate cert,
java.security.cert.X509Certificate issuerCert) {
ValidationResult result = new ValidationResult();
// Check expiration
try {
cert.checkValidity();
result.validityOk = true;
} catch (CertificateException e) {
result.validityOk = false;
result.errors.add("Certificate expired or not yet valid: " + e.getMessage());
}
// Check signature
try {
if (issuerCert != null) {
cert.verify(issuerCert.getPublicKey());
result.signatureOk = true;
}
} catch (Exception e) {
result.signatureOk = false;
result.errors.add("Invalid signature: " + e.getMessage());
}
// Check issuer matches
if (issuerCert != null) {
boolean issuerMatches = cert.getIssuerDN().equals(
issuerCert.getSubjectDN());
result.issuerMatches = issuerMatches;
if (!issuerMatches) {
result.errors.add("Issuer DN mismatch");
}
}
return result;
}
static class ValidationResult {
boolean validityOk;
boolean signatureOk;
boolean issuerMatches;
List<String> errors = new ArrayList<>();
public boolean isValid() {
return validityOk && signatureOk && issuerMatches;
}
@Override
public String toString() {
return "ValidationResult{" +
"valid=" + isValid() +
", errors=" + errors +
'}';
}
}
}
Certificate Chain Building and Validation
// Certificate Chain Operations
public class CertificateChainOperations {
// Build certificate chain
public static List<java.security.cert.X509Certificate> buildChain(
java.security.cert.X509Certificate endEntity,
List<java.security.cert.X509Certificate> intermediates,
java.security.cert.X509Certificate rootCA) throws Exception {
List<java.security.cert.X509Certificate> chain = new ArrayList<>();
chain.add(endEntity);
java.security.cert.X509Certificate current = endEntity;
// Find intermediate certificates
while (!isSelfSigned(current)) {
boolean found = false;
for (java.security.cert.X509Certificate intermediate : intermediates) {
if (current.getIssuerDN().equals(intermediate.getSubjectDN())) {
chain.add(intermediate);
current = intermediate;
found = true;
break;
}
}
if (!found) {
// Check if root CA
if (current.getIssuerDN().equals(rootCA.getSubjectDN())) {
chain.add(rootCA);
break;
} else {
throw new CertificateException(
"Cannot build complete chain, missing intermediate");
}
}
}
return chain;
}
// Validate entire certificate chain
public static boolean validateChain(
List<java.security.cert.X509Certificate> chain) throws Exception {
if (chain == null || chain.isEmpty()) {
throw new IllegalArgumentException("Chain is empty");
}
// Validate each certificate in chain
for (int i = 0; i < chain.size(); i++) {
java.security.cert.X509Certificate cert = chain.get(i);
// Check expiration
cert.checkValidity();
// Verify signature (except for root which is self-signed)
if (i < chain.size() - 1) {
java.security.cert.X509Certificate issuer = chain.get(i + 1);
cert.verify(issuer.getPublicKey());
// Check issuer DN matches
if (!cert.getIssuerDN().equals(issuer.getSubjectDN())) {
throw new CertificateException(
"Issuer DN mismatch at position " + i);
}
} else {
// Root certificate should be self-signed
cert.verify(cert.getPublicKey());
}
}
System.out.println("✓ Certificate chain is valid");
return true;
}
// Order certificates into proper chain
public static List<java.security.cert.X509Certificate> orderChain(
Collection<java.security.cert.X509Certificate> certificates)
throws Exception {
List<java.security.cert.X509Certificate> unordered =
new ArrayList<>(certificates);
List<java.security.cert.X509Certificate> ordered = new ArrayList<>();
// Find end-entity certificate (leaf)
java.security.cert.X509Certificate leaf = null;
for (java.security.cert.X509Certificate cert : unordered) {
// End-entity typically has no CA:TRUE basic constraint
boolean[] keyUsage = cert.getKeyUsage();
if (!isSelfSigned(cert)) {
leaf = cert;
break;
}
}
if (leaf == null) {
throw new CertificateException("Cannot find end-entity certificate");
}
ordered.add(leaf);
unordered.remove(leaf);
// Build chain by matching issuer to subject
java.security.cert.X509Certificate current = leaf;
while (!unordered.isEmpty()) {
boolean found = false;
for (java.security.cert.X509Certificate cert : unordered) {
if (current.getIssuerDN().equals(cert.getSubjectDN())) {
ordered.add(cert);
unordered.remove(cert);
current = cert;
found = true;
break;
}
}
if (!found) {
break; // Root reached or incomplete chain
}
}
return ordered;
}
// Validate chain using CertPath
public static boolean validateChainWithCertPath(
List<java.security.cert.X509Certificate> chain,
Set<TrustAnchor> trustAnchors) throws Exception {
CertificateFactory cf = CertificateFactory.getInstance("X.509");
CertPath certPath = cf.generateCertPath(chain);
CertPathValidator validator = CertPathValidator.getInstance("PKIX");
PKIXParameters params = new PKIXParameters(trustAnchors);
params.setRevocationEnabled(false); // Disable CRL/OCSP for now
try {
validator.validate(certPath, params);
System.out.println("✓ Certificate chain validated with CertPath");
return true;
} catch (CertPathValidatorException e) {
System.err.println("✗ Chain validation failed: " + e.getMessage());
System.err.println(" Failed at index: " + e.getIndex());
return false;
}
}
// Helper: Check if certificate is self-signed
private static boolean isSelfSigned(java.security.cert.X509Certificate cert)
throws Exception {
try {
cert.verify(cert.getPublicKey());
return cert.getIssuerDN().equals(cert.getSubjectDN());
} catch (Exception e) {
return false;
}
}
}
Certificate Information Extraction
// Extracting Certificate Information
public class CertificateInformation {
// Extract all certificate details
public static void printCertificateDetails(
java.security.cert.X509Certificate cert) throws Exception {
System.out.println("=== CERTIFICATE DETAILS ===");
System.out.println("\n--- SUBJECT ---");
System.out.println(cert.getSubjectDN());
System.out.println("\n--- ISSUER ---");
System.out.println(cert.getIssuerDN());
System.out.println("\n--- VALIDITY ---");
System.out.println("Not Before: " + cert.getNotBefore());
System.out.println("Not After: " + cert.getNotAfter());
long daysUntilExpiry = CertificateValidation.getDaysUntilExpiration(cert);
System.out.println("Days until expiry: " + daysUntilExpiry);
System.out.println("\n--- IDENTIFIERS ---");
System.out.println("Serial Number: " + cert.getSerialNumber());
System.out.println("Version: " + cert.getVersion());
System.out.println("\n--- ALGORITHMS ---");
System.out.println("Signature Algorithm: " + cert.getSigAlgName());
System.out.println("Public Key Algorithm: " +
cert.getPublicKey().getAlgorithm());
System.out.println("\n--- PUBLIC KEY ---");
PublicKey publicKey = cert.getPublicKey();
if (publicKey instanceof java.security.interfaces.RSAPublicKey) {
java.security.interfaces.RSAPublicKey rsaKey =
(java.security.interfaces.RSAPublicKey) publicKey;
System.out.println("RSA Key Size: " + rsaKey.getModulus().bitLength());
} else if (publicKey instanceof java.security.interfaces.ECPublicKey) {
java.security.interfaces.ECPublicKey ecKey =
(java.security.interfaces.ECPublicKey) publicKey;
System.out.println("EC Curve: " +
ecKey.getParams().getCurve().getField().getFieldSize());
}
System.out.println("\n--- EXTENSIONS ---");
printExtensions(cert);
}
// Print certificate extensions
private static void printExtensions(java.security.cert.X509Certificate cert)
throws Exception {
java.util.Set<String> criticalExtensions = cert.getCriticalExtensionOIDs();
java.util.Set<String> nonCriticalExtensions =
cert.getNonCriticalExtensionOIDs();
if (criticalExtensions != null) {
System.out.println("Critical Extensions: " + criticalExtensions.size());
for (String oid : criticalExtensions) {
System.out.println(" " + oid);
}
}
if (nonCriticalExtensions != null) {
System.out.println("Non-Critical Extensions: " +
nonCriticalExtensions.size());
for (String oid : nonCriticalExtensions) {
System.out.println(" " + oid);
}
}
// Subject Alternative Names
Collection<List<?>> sans = cert.getSubjectAlternativeNames();
if (sans != null) {
System.out.println("\nSubject Alternative Names:");
for (List<?> san : sans) {
Integer type = (Integer) san.get(0);
String value = (String) san.get(1);
System.out.println(" " + getAltNameType(type) + ": " + value);
}
}
// Key Usage
boolean[] keyUsage = cert.getKeyUsage();
if (keyUsage != null) {
System.out.println("\nKey Usage:");
String[] usageNames = {
"digitalSignature", "nonRepudiation", "keyEncipherment",
"dataEncipherment", "keyAgreement", "keyCertSign",
"cRLSign", "encipherOnly", "decipherOnly"
};
for (int i = 0; i < keyUsage.length && i < usageNames.length; i++) {
if (keyUsage[i]) {
System.out.println(" " + usageNames[i]);
}
}
}
// Extended Key Usage
List<String> extKeyUsage = cert.getExtendedKeyUsage();
if (extKeyUsage != null) {
System.out.println("\nExtended Key Usage:");
for (String eku : extKeyUsage) {
System.out.println(" " + eku);
}
}
}
private static String getAltNameType(int type) {
return switch (type) {
case 0 -> "otherName";
case 1 -> "rfc822Name";
case 2 -> "dNSName";
case 3 -> "x400Address";
case 4 -> "directoryName";
case 5 -> "ediPartyName";
case 6 -> "uniformResourceIdentifier";
case 7 -> "iPAddress";
case 8 -> "registeredID";
default -> "unknown(" + type + ")";
};
}
// Extract common name from subject DN
public static String getCommonName(java.security.cert.X509Certificate cert) {
String dn = cert.getSubjectDN().getName();
for (String part : dn.split(",")) {
String[] keyValue = part.trim().split("=");
if (keyValue.length == 2 && "CN".equalsIgnoreCase(keyValue[0].trim())) {
return keyValue[1].trim();
}
}
return null;
}
// Get all DNS names from certificate
public static List<String> getDNSNames(java.security.cert.X509Certificate cert)
throws Exception {
List<String> dnsNames = new ArrayList<>();
// Add CN
String cn = getCommonName(cert);
if (cn != null) {
dnsNames.add(cn);
}
// Add SANs
Collection<List<?>> sans = cert.getSubjectAlternativeNames();
if (sans != null) {
for (List<?> san : sans) {
Integer type = (Integer) san.get(0);
if (type == 2) { // dNSName
dnsNames.add((String) san.get(1));
}
}
}
return dnsNames;
}
}
Best Practices
- Certificate chain validation: Always validate the entire chain to root CA.
- Expiration monitoring: Alert 30 days before expiration.
- Use standard formats: PEM for text, DER for binary.
- Verify signatures: Always verify certificate signatures.
- Check revocation: Implement CRL or OCSP checking.
- Handle SANs properly: Check Subject Alternative Names for hostname verification.
- Validate date ranges: Ensure certificates are within validity period.
- Order chains correctly: End-entity first, root last.
- Store chains complete: Include all intermediate certificates.
- Test with openssl: Verify with
openssl x509 -in cert.pem -text -noout.