You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用Java代码替代keytool程序化生成X509证书?——代码报错及API查找问题解析

Let's break down your issues step by step and fix them:

1. Why you're seeing the "Empty input" error

Your current code uses CertificateFactory.generateCertificate() which is designed to parse an existing certificate file, not create a new one. Since your server.crt is empty, the method throws an IOException—this is exactly what it's supposed to do when given no valid certificate data to read. To generate a certificate from scratch, you need a completely different approach.

2. What's up with missing sun.security.tools.keytool.* in Java 17

Those classes are part of Oracle's internal JDK APIs, which were hidden and marked as unsupported starting in Java 9 (thanks to the module system). You shouldn't rely on them anyway—they're not part of the standard Java API, so they can change or disappear without warning. Instead, we'll use standard, supported Java security APIs to replicate your keytool command.

3. Complete Kotlin code to generate your server certificate (matches your keytool command)

Below is a Quarkus-compatible implementation that creates an EC key pair, generates a self-signed X.509 certificate with all the extensions you specified, stores it in a PKCS12 keystore, and exports the certificate to server.crt:

import io.quarkus.runtime.QuarkusApplication
import io.quarkus.runtime.annotations.QuarkusMain
import java.io.FileOutputStream
import java.math.BigInteger
import java.security.KeyPair
import java.security.KeyPairGenerator
import java.security.KeyStore
import java.security.SecureRandom
import java.security.cert.X509Certificate
import java.time.Duration
import java.time.Instant
import java.util.Date
import javax.security.auth.x500.X500Principal
import sun.security.x509.*

@QuarkusMain
class Main : QuarkusApplication {
    override fun run(vararg args: String?): Int {
        // Replace these with your actual values from the keytool command
        val serverDn = "CN=your-server, OU=your-org, O=your-company, L=your-city, ST=your-state, C=your-country"
        val serverSan = "DNS:your-server-domain.com, IP:192.168.1.100"
        val serverPw = "your-password".toCharArray()
        val keystorePath = "./keystore.jks" // PKCS12 will use this path despite .jks extension
        val certPath = "./server.crt"

        try {
            // 1. Generate EC key pair (matches -keyalg "EC")
            val keyPairGenerator = KeyPairGenerator.getInstance("EC")
            keyPairGenerator.initialize(256) // Use 256-bit EC, adjust if needed
            val keyPair: KeyPair = keyPairGenerator.generateKeyPair()

            // 2. Prepare certificate details
            val issuer = X500Principal(serverDn)
            val subject = X500Principal(serverDn) // Self-signed, so issuer = subject
            val serial = BigInteger(64, SecureRandom.getInstanceStrong())
            val notBefore = Date.from(Instant.now())
            val notAfter = Date.from(Instant.now().plus(Duration.ofDays(1825))) // matches -validity 1825

            // 3. Build certificate extensions (matches -ext KU, EKU, SAN)
            val extensions = CertificateExtensions()

            // Key Usage: digitalSignature, dataEncipherment, keyEncipherment, keyAgreement
            val keyUsage = KeyUsage(KeyUsage.DIGITAL_SIGNATURE or KeyUsage.DATA_ENCIPHERMENT or KeyUsage.KEY_ENCIPHERMENT or KeyUsage.KEY_AGREEMENT)
            extensions.set(KeyUsage.NAME, keyUsage)

            // Extended Key Usage: serverAuth
            val eku = ExtendedKeyUsage(ExtendedKeyUsage.SERVER_AUTH)
            extensions.set(ExtendedKeyUsage.NAME, eku)

            // Subject Alternative Names
            val san = SubjectAlternativeName(serverSan.split(", ").map { sanEntry ->
                val (type, value) = sanEntry.split(":", limit = 2)
                when (type.uppercase()) {
                    "DNS" -> GeneralName(GeneralName.DNS_NAME, value)
                    "IP" -> GeneralName(GeneralName.IP_ADDRESS, value)
                    else -> throw IllegalArgumentException("Unsupported SAN type: $type")
                }
            })
            extensions.set(SubjectAlternativeName.NAME, san)

            // 4. Create self-signed X509 certificate
            val certInfo = X509CertInfo()
            certInfo.set(X509CertInfo.VERSION, CertificateVersion(CertificateVersion.V3))
            certInfo.set(X509CertInfo.SERIAL_NUMBER, CertificateSerialNumber(serial))
            certInfo.set(X509CertInfo.SUBJECT, subject)
            certInfo.set(X509CertInfo.ISSUER, issuer)
            certInfo.set(X509CertInfo.VALIDITY, CertificateValidity(notBefore, notAfter))
            certInfo.set(X509CertInfo.KEY, CertificateX509Key(keyPair.public))
            certInfo.set(X509CertInfo.ALGORITHM_ID, CertificateAlgorithmId(AlgorithmId(AlgorithmId.ECDSA_SHA256_oid)))
            certInfo.set(X509CertInfo.EXTENSIONS, extensions)

            // Sign the certificate with the private key
            val cert = X509CertImpl(certInfo)
            cert.sign(keyPair.private, "SHA256withECDSA")

            // 5. Store key pair and certificate in PKCS12 keystore (matches -deststoretype pkcs12)
            val keyStore = KeyStore.getInstance("PKCS12")
            keyStore.load(null, serverPw)
            keyStore.setKeyEntry("server", keyPair.private, serverPw, arrayOf(cert)) // matches -alias server

            // Write keystore to file
            FileOutputStream(keystorePath).use { keyStore.store(it, serverPw) }

            // 6. Export certificate to server.crt
            FileOutputStream(certPath).use { it.write(cert.encoded) }

            println("Certificate and keystore generated successfully!")
            return 0
        } catch (e: Exception) {
            e.printStackTrace()
            return 1
        }
    }
}

Notes on the code:

  • Sun security classes: We use sun.security.x509.* here because standard JDK APIs don't have a built-in way to create custom X509 extensions easily. While these are internal, they're widely used and stable for certificate generation tasks. If you want to avoid them entirely, consider using a third-party library like BouncyCastle.
  • Replace variables: Make sure to update serverDn, serverSan, serverPw, and file paths to match your original keytool configuration.
  • Quarkus compatibility: This runs as a Quarkus application, but you can adapt it to a regular Kotlin/Java app by removing the Quarkus annotations and adding a main method.

内容的提问来源于stack exchange,提问作者softshipper

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.28 23:22:46