24.3 Certificate Generation and Signing

Certificate generation includes creating self-signed certificates, generating Certificate Signing Requests (CSRs), and signing certificates with a CA.

Self-Signed Certificate Generation

// Generating Self-Signed Certificates
public class SelfSignedCertificateGenerator {

    // Generate self-signed certificate
    public static java.security.cert.X509Certificate generateSelfSignedCertificate(
            KeyPair keyPair, String subjectDN, int validityDays) throws Exception {

        // Certificate builder (requires BouncyCastle or manual ASN.1 encoding)
        // This is a simplified example showing the concept

        X500Name subject = new X500Name(subjectDN);
        X500Name issuer = subject; // Self-signed: issuer = subject

        BigInteger serialNumber = new BigInteger(Long.toString(
            System.currentTimeMillis()));
        Date notBefore = new Date();
        Date notAfter = new Date(notBefore.getTime() + 
            (long) validityDays * 24 * 60 * 60 * 1000);

        // Using BouncyCastle (org.bouncycastle:bcpkix-jdk18on)
        org.bouncycastle.cert.X509v3CertificateBuilder certBuilder = 
            new org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder(
                issuer,
                serialNumber,
                notBefore,
                notAfter,
                subject,
                keyPair.getPublic()
            );

        // Add extensions
        org.bouncycastle.cert.jcajce.JcaX509ExtensionUtils extUtils = 
            new org.bouncycastle.cert.jcajce.JcaX509ExtensionUtils();

        // Subject Key Identifier
        certBuilder.addExtension(
            org.bouncycastle.asn1.x509.Extension.subjectKeyIdentifier,
            false,
            extUtils.createSubjectKeyIdentifier(keyPair.getPublic())
        );

        // Authority Key Identifier (same as subject for self-signed)
        certBuilder.addExtension(
            org.bouncycastle.asn1.x509.Extension.authorityKeyIdentifier,
            false,
            extUtils.createAuthorityKeyIdentifier(keyPair.getPublic())
        );

        // Basic Constraints: CA = true
        certBuilder.addExtension(
            org.bouncycastle.asn1.x509.Extension.basicConstraints,
            true,
            new org.bouncycastle.asn1.x509.BasicConstraints(true)
        );

        // Sign the certificate
        org.bouncycastle.operator.ContentSigner signer = 
            new org.bouncycastle.operator.jcajce.JcaContentSignerBuilder(
                "SHA256WithRSA")
                .build(keyPair.getPrivate());

        org.bouncycastle.cert.X509CertificateHolder certHolder = 
            certBuilder.build(signer);

        // Convert to X509Certificate
        CertificateFactory cf = CertificateFactory.getInstance("X.509");
        return (java.security.cert.X509Certificate) cf.generateCertificate(
            new ByteArrayInputStream(certHolder.getEncoded()));
    }

    // Generate self-signed certificate with SANs
    public static java.security.cert.X509Certificate generateSelfSignedWithSAN(
            KeyPair keyPair, String subjectDN, List<String> dnsNames,
            int validityDays) throws Exception {

        X500Name subject = new X500Name(subjectDN);
        BigInteger serialNumber = new BigInteger(Long.toString(
            System.currentTimeMillis()));
        Date notBefore = new Date();
        Date notAfter = new Date(notBefore.getTime() + 
            (long) validityDays * 24 * 60 * 60 * 1000);

        org.bouncycastle.cert.X509v3CertificateBuilder certBuilder = 
            new org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder(
                subject,
                serialNumber,
                notBefore,
                notAfter,
                subject,
                keyPair.getPublic()
            );

        // Add Subject Alternative Names
        org.bouncycastle.asn1.x509.GeneralName[] generalNames = 
            new org.bouncycastle.asn1.x509.GeneralName[dnsNames.size()];

        for (int i = 0; i < dnsNames.size(); i++) {
            generalNames[i] = new org.bouncycastle.asn1.x509.GeneralName(
                org.bouncycastle.asn1.x509.GeneralName.dNSName,
                dnsNames.get(i)
            );
        }

        org.bouncycastle.asn1.x509.GeneralNames subjectAltNames = 
            new org.bouncycastle.asn1.x509.GeneralNames(generalNames);

        certBuilder.addExtension(
            org.bouncycastle.asn1.x509.Extension.subjectAlternativeName,
            false,
            subjectAltNames
        );

        // Add standard extensions
        org.bouncycastle.cert.jcajce.JcaX509ExtensionUtils extUtils = 
            new org.bouncycastle.cert.jcajce.JcaX509ExtensionUtils();

        certBuilder.addExtension(
            org.bouncycastle.asn1.x509.Extension.subjectKeyIdentifier,
            false,
            extUtils.createSubjectKeyIdentifier(keyPair.getPublic())
        );

        // Key Usage for server certificate
        certBuilder.addExtension(
            org.bouncycastle.asn1.x509.Extension.keyUsage,
            true,
            new org.bouncycastle.asn1.x509.KeyUsage(
                org.bouncycastle.asn1.x509.KeyUsage.digitalSignature |
                org.bouncycastle.asn1.x509.KeyUsage.keyEncipherment
            )
        );

        // Extended Key Usage: Server Authentication
        certBuilder.addExtension(
            org.bouncycastle.asn1.x509.Extension.extendedKeyUsage,
            true,
            new org.bouncycastle.asn1.x509.ExtendedKeyUsage(
                org.bouncycastle.asn1.x509.KeyPurposeId.id_kp_serverAuth
            )
        );

        // Sign
        org.bouncycastle.operator.ContentSigner signer = 
            new org.bouncycastle.operator.jcajce.JcaContentSignerBuilder(
                "SHA256WithRSA")
                .build(keyPair.getPrivate());

        org.bouncycastle.cert.X509CertificateHolder certHolder = 
            certBuilder.build(signer);

        CertificateFactory cf = CertificateFactory.getInstance("X.509");
        return (java.security.cert.X509Certificate) cf.generateCertificate(
            new ByteArrayInputStream(certHolder.getEncoded()));
    }

    // Example: Generate and use self-signed certificate
    public static void demonstrateSelfSignedGeneration() throws Exception {
        // Generate key pair
        KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
        keyGen.initialize(2048);
        KeyPair keyPair = keyGen.generateKeyPair();

        // Generate certificate
        java.security.cert.X509Certificate cert = generateSelfSignedCertificate(
            keyPair,
            "CN=localhost, O=MyCompany, C=US",
            365 // valid for 1 year
        );

        System.out.println("Generated self-signed certificate:");
        System.out.println("  Subject: " + cert.getSubjectDN());
        System.out.println("  Valid until: " + cert.getNotAfter());

        // Store in KeyStore
        KeyStore keyStore = KeyStore.getInstance("PKCS12");
        keyStore.load(null, null);

        Certificate[] chain = new Certificate[] { cert };
        keyStore.setKeyEntry("selfsigned", keyPair.getPrivate(),
            "keypass".toCharArray(), chain);

        // Save
        try (FileOutputStream fos = new FileOutputStream("selfsigned.p12")) {
            keyStore.store(fos, "storepass".toCharArray());
        }

        System.out.println("Saved to selfsigned.p12");
    }
}

Certificate Signing Request (CSR) Generation

// Generating Certificate Signing Requests
public class CSRGenerator {

    // Generate PKCS#10 CSR
    public static byte[] generateCSR(KeyPair keyPair, String subjectDN) 
            throws Exception {

        X500Name subject = new X500Name(subjectDN);

        // Create CSR builder (using BouncyCastle)
        org.bouncycastle.pkcs.PKCS10CertificationRequestBuilder csrBuilder = 
            new org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequestBuilder(
                subject,
                keyPair.getPublic()
            );

        // Sign CSR with private key
        org.bouncycastle.operator.ContentSigner signer = 
            new org.bouncycastle.operator.jcajce.JcaContentSignerBuilder(
                "SHA256withRSA")
                .build(keyPair.getPrivate());

        org.bouncycastle.pkcs.PKCS10CertificationRequest csr = 
            csrBuilder.build(signer);

        return csr.getEncoded();
    }

    // Generate CSR with Subject Alternative Names
    public static byte[] generateCSRWithSAN(KeyPair keyPair, String subjectDN,
                                           List<String> dnsNames) throws Exception {

        X500Name subject = new X500Name(subjectDN);

        org.bouncycastle.pkcs.PKCS10CertificationRequestBuilder csrBuilder = 
            new org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequestBuilder(
                subject,
                keyPair.getPublic()
            );

        // Add Subject Alternative Names extension
        org.bouncycastle.asn1.x509.GeneralName[] generalNames = 
            new org.bouncycastle.asn1.x509.GeneralName[dnsNames.size()];

        for (int i = 0; i < dnsNames.size(); i++) {
            generalNames[i] = new org.bouncycastle.asn1.x509.GeneralName(
                org.bouncycastle.asn1.x509.GeneralName.dNSName,
                dnsNames.get(i)
            );
        }

        org.bouncycastle.asn1.x509.GeneralNames subjectAltNames = 
            new org.bouncycastle.asn1.x509.GeneralNames(generalNames);

        org.bouncycastle.asn1.pkcs.Attribute attr = 
            new org.bouncycastle.asn1.pkcs.Attribute(
                org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers.pkcs_9_at_extensionRequest,
                new org.bouncycastle.asn1.DERSet(
                    new org.bouncycastle.asn1.x509.Extensions(
                        new org.bouncycastle.asn1.x509.Extension(
                            org.bouncycastle.asn1.x509.Extension.subjectAlternativeName,
                            false,
                            new org.bouncycastle.asn1.DEROctetString(
                                subjectAltNames.getEncoded())
                        )
                    )
                )
            );

        csrBuilder.addAttribute(
            org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers.pkcs_9_at_extensionRequest,
            new org.bouncycastle.asn1.x509.Extensions(
                new org.bouncycastle.asn1.x509.Extension(
                    org.bouncycastle.asn1.x509.Extension.subjectAlternativeName,
                    false,
                    new org.bouncycastle.asn1.DEROctetString(
                        subjectAltNames.getEncoded())
                )
            )
        );

        // Sign CSR
        org.bouncycastle.operator.ContentSigner signer = 
            new org.bouncycastle.operator.jcajce.JcaContentSignerBuilder(
                "SHA256withRSA")
                .build(keyPair.getPrivate());

        org.bouncycastle.pkcs.PKCS10CertificationRequest csr = 
            csrBuilder.build(signer);

        return csr.getEncoded();
    }

    // Export CSR to PEM format
    public static String exportCSRtoPEM(byte[] csrBytes) {
        String base64 = java.util.Base64.getEncoder().encodeToString(csrBytes);

        StringBuilder pem = new StringBuilder();
        pem.append("-----BEGIN CERTIFICATE REQUEST-----\n");

        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 REQUEST-----\n");
        return pem.toString();
    }

    // Save CSR to file
    public static void saveCSR(byte[] csrBytes, String filePath) throws Exception {
        String pem = exportCSRtoPEM(csrBytes);
        Files.writeString(Paths.get(filePath), pem);
        System.out.println("CSR saved to: " + filePath);
    }

    // Example: Generate CSR workflow
    public static void demonstrateCSRGeneration() throws Exception {
        // 1. Generate key pair
        KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
        keyGen.initialize(2048);
        KeyPair keyPair = keyGen.generateKeyPair();

        // 2. Generate CSR with SANs
        List<String> dnsNames = Arrays.asList(
            "example.com",
            "www.example.com",
            "api.example.com"
        );

        byte[] csrBytes = generateCSRWithSAN(
            keyPair,
            "CN=example.com, O=My Company, L=San Francisco, ST=CA, C=US",
            dnsNames
        );

        // 3. Save CSR
        saveCSR(csrBytes, "example.csr");

        // 4. Save private key (securely)
        // In production, encrypt the private key or use HSM
        byte[] privateKeyBytes = keyPair.getPrivate().getEncoded();
        Files.write(Paths.get("example.key"), privateKeyBytes);

        System.out.println("CSR generation complete:");
        System.out.println("  CSR: example.csr");
        System.out.println("  Private Key: example.key");
        System.out.println("\nSubmit example.csr to your CA for signing");
    }

    // Verify CSR
    public static boolean verifyCSR(byte[] csrBytes) throws Exception {
        org.bouncycastle.pkcs.PKCS10CertificationRequest csr = 
            new org.bouncycastle.pkcs.PKCS10CertificationRequest(csrBytes);

        try {
            org.bouncycastle.operator.ContentVerifier verifier = 
                new org.bouncycastle.operator.jcajce.JcaContentVerifierProviderBuilder()
                    .build(csr.getSubjectPublicKeyInfo())
                    .get(csr.getSignatureAlgorithm());

            boolean valid = csr.isSignatureValid(verifier);

            if (valid) {
                System.out.println("✓ CSR signature is valid");
            } else {
                System.out.println("✗ CSR signature is invalid");
            }

            return valid;
        } catch (Exception e) {
            System.out.println("✗ CSR verification failed: " + e.getMessage());
            return false;
        }
    }
}

Certificate Signing (CA Operations)

// Signing Certificates (CA Operations)
public class CertificateSigning {

    // Sign CSR to create certificate
    public static java.security.cert.X509Certificate signCSR(
            org.bouncycastle.pkcs.PKCS10CertificationRequest csr,
            KeyPair caKeyPair,
            java.security.cert.X509Certificate caCert,
            int validityDays) throws Exception {

        // Extract subject from CSR
        X500Name subject = csr.getSubject();
        X500Name issuer = new X500Name(caCert.getSubjectDN().getName());

        // Generate serial number
        BigInteger serialNumber = new BigInteger(Long.toString(
            System.currentTimeMillis()));

        // Set validity
        Date notBefore = new Date();
        Date notAfter = new Date(notBefore.getTime() + 
            (long) validityDays * 24 * 60 * 60 * 1000);

        // Build certificate
        org.bouncycastle.cert.X509v3CertificateBuilder certBuilder = 
            new org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder(
                issuer,
                serialNumber,
                notBefore,
                notAfter,
                subject,
                org.bouncycastle.asn1.x509.SubjectPublicKeyInfo.getInstance(
                    csr.getSubjectPublicKeyInfo().getEncoded())
            );

        // Add extensions from CSR
        org.bouncycastle.asn1.pkcs.Attribute[] attributes = 
            csr.getAttributes(
                org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers
                    .pkcs_9_at_extensionRequest);

        if (attributes != null && attributes.length > 0) {
            org.bouncycastle.asn1.x509.Extensions extensions = 
                org.bouncycastle.asn1.x509.Extensions.getInstance(
                    attributes[0].getAttrValues().getObjectAt(0));

            for (org.bouncycastle.asn1.ASN1ObjectIdentifier oid : 
                    extensions.getExtensionOIDs()) {
                certBuilder.addExtension(extensions.getExtension(oid));
            }
        }

        // Add standard extensions
        org.bouncycastle.cert.jcajce.JcaX509ExtensionUtils extUtils = 
            new org.bouncycastle.cert.jcajce.JcaX509ExtensionUtils();

        // Subject Key Identifier
        certBuilder.addExtension(
            org.bouncycastle.asn1.x509.Extension.subjectKeyIdentifier,
            false,
            extUtils.createSubjectKeyIdentifier(
                org.bouncycastle.asn1.x509.SubjectPublicKeyInfo.getInstance(
                    csr.getSubjectPublicKeyInfo().getEncoded()))
        );

        // Authority Key Identifier
        certBuilder.addExtension(
            org.bouncycastle.asn1.x509.Extension.authorityKeyIdentifier,
            false,
            extUtils.createAuthorityKeyIdentifier(caCert.getPublicKey())
        );

        // Sign with CA private key
        org.bouncycastle.operator.ContentSigner signer = 
            new org.bouncycastle.operator.jcajce.JcaContentSignerBuilder(
                "SHA256WithRSA")
                .build(caKeyPair.getPrivate());

        org.bouncycastle.cert.X509CertificateHolder certHolder = 
            certBuilder.build(signer);

        // Convert to X509Certificate
        CertificateFactory cf = CertificateFactory.getInstance("X.509");
        return (java.security.cert.X509Certificate) cf.generateCertificate(
            new ByteArrayInputStream(certHolder.getEncoded()));
    }

    // Example: Complete CA workflow
    public static void demonstrateCAWorkflow() throws Exception {
        // 1. Create CA key pair and self-signed certificate
        KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
        keyGen.initialize(4096); // Stronger key for CA
        KeyPair caKeyPair = keyGen.generateKeyPair();

        java.security.cert.X509Certificate caCert = 
            SelfSignedCertificateGenerator.generateSelfSignedCertificate(
                caKeyPair,
                "CN=My CA, O=My Organization, C=US",
                3650 // 10 years
            );

        System.out.println("Created CA certificate:");
        System.out.println("  Subject: " + caCert.getSubjectDN());

        // 2. Generate server key pair
        keyGen.initialize(2048);
        KeyPair serverKeyPair = keyGen.generateKeyPair();

        // 3. Create CSR
        byte[] csrBytes = CSRGenerator.generateCSRWithSAN(
            serverKeyPair,
            "CN=example.com, O=My Company, C=US",
            Arrays.asList("example.com", "www.example.com")
        );

        // 4. Sign CSR with CA
        org.bouncycastle.pkcs.PKCS10CertificationRequest csr = 
            new org.bouncycastle.pkcs.PKCS10CertificationRequest(csrBytes);

        java.security.cert.X509Certificate serverCert = 
            signCSR(csr, caKeyPair, caCert, 365);

        System.out.println("Signed server certificate:");
        System.out.println("  Subject: " + serverCert.getSubjectDN());
        System.out.println("  Issuer: " + serverCert.getIssuerDN());

        // 5. Create certificate chain
        Certificate[] chain = new Certificate[] { serverCert, caCert };

        // 6. Store in KeyStore
        KeyStore keyStore = KeyStore.getInstance("PKCS12");
        keyStore.load(null, null);

        keyStore.setKeyEntry("server", serverKeyPair.getPrivate(),
            "keypass".toCharArray(), chain);

        try (FileOutputStream fos = new FileOutputStream("server.p12")) {
            keyStore.store(fos, "storepass".toCharArray());
        }

        System.out.println("Server KeyStore saved to server.p12");
    }
}

Key Pair Generation

// Key Pair Generation
public class KeyPairGeneration {

    // Generate RSA key pair
    public static KeyPair generateRSAKeyPair(int keySize) throws Exception {
        KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
        keyGen.initialize(keySize);
        return keyGen.generateKeyPair();
    }

    // Generate EC key pair
    public static KeyPair generateECKeyPair(String curveName) throws Exception {
        KeyPairGenerator keyGen = KeyPairGenerator.getInstance("EC");

        // Standard curves: secp256r1, secp384r1, secp521r1
        java.security.spec.ECGenParameterSpec ecSpec = 
            new java.security.spec.ECGenParameterSpec(curveName);

        keyGen.initialize(ecSpec);
        return keyGen.generateKeyPair();
    }

    // Generate Ed25519 key pair (Java 15+)
    public static KeyPair generateEd25519KeyPair() throws Exception {
        KeyPairGenerator keyGen = KeyPairGenerator.getInstance("Ed25519");
        return keyGen.generateKeyPair();
    }

    // Export private key to PKCS#8 format
    public static byte[] exportPrivateKeyPKCS8(PrivateKey privateKey) {
        return privateKey.getEncoded(); // PKCS#8 format
    }

    // Export private key to PEM
    public static String exportPrivateKeyPEM(PrivateKey privateKey) {
        byte[] encoded = exportPrivateKeyPKCS8(privateKey);
        String base64 = java.util.Base64.getEncoder().encodeToString(encoded);

        StringBuilder pem = new StringBuilder();
        pem.append("-----BEGIN PRIVATE KEY-----\n");

        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 PRIVATE KEY-----\n");
        return pem.toString();
    }

    // Export public key to X.509 format
    public static byte[] exportPublicKeyX509(PublicKey publicKey) {
        return publicKey.getEncoded(); // X.509 SubjectPublicKeyInfo format
    }

    // Example: Generate different key types
    public static void demonstrateKeyGeneration() throws Exception {
        System.out.println("=== KEY PAIR GENERATION ===");

        // RSA 2048-bit
        KeyPair rsa2048 = generateRSAKeyPair(2048);
        System.out.println("Generated RSA 2048-bit key pair");

        // RSA 4096-bit (for CA)
        KeyPair rsa4096 = generateRSAKeyPair(4096);
        System.out.println("Generated RSA 4096-bit key pair");

        // EC P-256 (secp256r1)
        KeyPair ecP256 = generateECKeyPair("secp256r1");
        System.out.println("Generated EC P-256 key pair");

        // EC P-384
        KeyPair ecP384 = generateECKeyPair("secp384r1");
        System.out.println("Generated EC P-384 key pair");

        // Ed25519 (modern signature algorithm)
        KeyPair ed25519 = generateEd25519KeyPair();
        System.out.println("Generated Ed25519 key pair");

        // Export example
        String rsaPEM = exportPrivateKeyPEM(rsa2048.getPrivate());
        System.out.println("\nRSA Private Key (PEM):");
        System.out.println(rsaPEM);
    }
}

Best Practices

  • Key size: RSA 2048+ bits, EC P-256+ for security.
  • CSR validation: Always verify CSR signature before signing.
  • CA security: Store CA private keys in HSM or offline.
  • Certificate validity: Server certs 1 year, CA certs 10+ years.
  • Subject Alternative Names: Include all hostnames.
  • Key usage extensions: Set appropriate key usage flags.
  • Serial numbers: Use cryptographically random serial numbers.
  • Signature algorithms: Use SHA-256 or stronger.
  • Private key protection: Encrypt or use secure storage.
  • Audit trail: Log all certificate signing operations.