quarkusio/quarkus · error · RuntimeException

Failure to create a certificate

Error message

Failure to create a certificate

What it means

LetsEncryptHelpers.getCertificate() decodes a base64-encoded certificate and parses it via CertificateFactory X.509. Any exception during decoding or parsing (malformed base64, empty input, not a valid X.509 structure) is wrapped in this RuntimeException. It signals that data retrieved from the Let's Encrypt/ACME order could not be turned into a usable X509Certificate.

Source

Thrown at extensions/tls-registry/cli/src/main/java/io/quarkus/tls/cli/letsencrypt/LetsEncryptHelpers.java:259

    }

    private static JsonObject readAccountJson(File letsEncryptPath) {
        LOGGER.debugf("Reading account information from %s", letsEncryptPath);
        java.nio.file.Path accountPath = Paths.get(letsEncryptPath + "/account.json");
        try (FileInputStream fis = new FileInputStream(accountPath.toString())) {
            return new JsonObject(new String(fis.readAllBytes(), StandardCharsets.US_ASCII));
        } catch (IOException e) {
            throw new RuntimeException("Unable to read the account file, you must create account first");
        }
    }

    private static X509Certificate getCertificate(String encodedCert) {
        try {
            byte[] encodedBytes = Base64.getDecoder().decode(encodedCert);
            return (X509Certificate) CertificateFactory.getInstance("X.509")
                    .generateCertificate(new ByteArrayInputStream(encodedBytes));
        } catch (Exception ex) {
            throw new RuntimeException("Failure to create a certificate", ex);
        }
    }

    private static PrivateKey getPrivateKey(String encodedKey, String keyAlgorithm) {
        try {
            KeyFactory f = KeyFactory.getInstance((keyAlgorithm == null || "RSA".equals(keyAlgorithm) ? "RSA" : "EC"));
            byte[] encodedBytes = Base64.getDecoder().decode(encodedKey);
            PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(encodedBytes);
            return f.generatePrivate(spec);
        } catch (Exception ex) {
            throw new RuntimeException("Failure to create a private key", ex);
        }
    }

    public static void renewCertificate(AcmeClient acmeClient,
            File letsEncryptPath,
            boolean staging,
            String domain,

View on GitHub (pinned to e1c734241f)

Solutions

  1. Inspect the source file/URL the encoded certificate came from and re-download it; verify the file starts with valid base64 payload
  2. Strip PEM headers/footers and whitespace before passing the string, or decode PEM body only
  3. Validate the base64 string decodes and parses offline (openssl x509) before feeding it to the helper
  4. Delete the stale file under the letsencrypt directory and run renewal again so ACME issues a fresh certificate

Example fix

// before
X509Certificate cert = LetsEncryptHelpers.getCertificate(rawPemWithHeaders);
// after
String b64 = rawPemWithHeaders.replaceAll("-----BEGIN CERTIFICATE-----|-----END CERTIFICATE-----|\\s", "");
X509Certificate cert = LetsEncryptHelpers.getCertificate(b64);
Defensive patterns

Strategy: validation

Validate before calling

static boolean looksLikeValidCertBase64(String s) {
    if (s == null || s.isBlank()) return false;
    try {
        byte[] der = Base64.getDecoder().decode(s.replaceAll("-----[A-Z ]+-----|\\s", ""));
        CertificateFactory.getInstance("X.509").generateCertificate(new ByteArrayInputStream(der));
        return true;
    } catch (Exception e) { return false; }
}

Type guard

if (raw == null || raw.isBlank() || !looksLikeValidCertBase64(raw)) { reissueOrAbort(); }

Try / catch

try {
    X509Certificate cert = LetsEncryptHelpers.getCertificate(encoded);
} catch (RuntimeException e) {
    LOGGER.error("Corrupted certificate payload; delete stored cert and renew", e);
    renewCertificate();
}

Prevention

When it happens

Trigger: getCertificate(encodedCert) throws when the encoded string is not valid base64 (IllegalArgumentException), is empty/null, or CertificateFactory.generateCertificate fails because the bytes are not a DER/PEM X.509 certificate. Called from privateKey()/certificate() accessors during certificate renewal.

Common situations: The downloaded ACME certificate chain file is truncated, HTML error page saved instead of cert, PEM headers ('-----BEGIN CERTIFICATE-----') left in the base64 string, or an interrupted download corrupted the stored cert file.

Understand the failure class

Related errors


AI-assisted analysis of quarkusio/quarkus@e1c734241f (2026-09-05). Data as JSON: /api/errors/89aec4e803f475e5. Report an issue: GitHub.