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.