alibaba/canal · critical · CertificateException

Can't verify server certificate because no trust manager is

Error message

Can't verify server certificate because no trust manager is found.

What it means

Thrown inside the X509TrustManagerWrapper.checkServerTrusted when verifyServerCert is true (VERIFY_CA/VERIFY_IDENTITY) but origTm is null, i.e. no backing X509TrustManager was supplied. The wrapper cannot validate the server certificate chain against any trust store, so it refuses to trust. This is the 'I was asked to verify but have nothing to verify against' failure.

Source

Thrown at driver/src/main/java/com/alibaba/otter/canal/parse/driver/mysql/socket/BioSocketChannelPool.java:280

                try {
                    CertPath certPath = this.certFactory.generateCertPath(Arrays.asList(chain));
                    // Validate against truststore
                    CertPathValidatorResult result = this.validator.validate(certPath, this.validatorParams);
                    // Check expiration for the CA used to validate this path
                    ((PKIXCertPathValidatorResult) result).getTrustAnchor().getTrustedCert().checkValidity();

                } catch (InvalidAlgorithmParameterException e) {
                    throw new CertificateException(e);
                } catch (CertPathValidatorException e) {
                    throw new CertificateException(e);
                }
            }

            if (this.verifyServerCert) {
                if (this.origTm != null) {
                    this.origTm.checkServerTrusted(chain, authType);
                } else {
                    throw new CertificateException(
                        "Can't verify server certificate because no trust manager is found.");
                }

                // verify server certificate identity
                if (this.hostName != null) {
                    logger.info("verify hostName: {}", this.hostName);
                    Set<String> expectHostNames = new HashSet<>();
                    for (X509Certificate certificate : chain) {
                        String dn = certificate.getSubjectX500Principal().getName(X500Principal.RFC2253);
                        String cn = null;
                        try {
                            LdapName ldapDN = new LdapName(dn);
                            for (Rdn rdn : ldapDN.getRdns()) {
                                if (rdn.getType().equalsIgnoreCase("CN")) {
                                    cn = rdn.getValue().toString();
                                    break;
                                }
                            }

View on GitHub (pinned to 87be50e876)

Solutions

  1. Provide a valid trustCertificateKeyStoreUrl (and type/password) pointing to a JKS/PKCS12 store containing the MySQL server's CA.
  2. Ensure the JVM default cacerts is present and populated when relying on fallbackToDefaultTrustStore.
  3. Verify the truststore URL is reachable and the type matches the file format (JKS vs PKCS12).
  4. If you intentionally cannot verify, use SslMode.REQUIRED instead of VERIFY_CA/VERIFY_IDENTITY.

Example fix

// before
SslInfo info = new SslInfo();
info.setSslMode(SslMode.VERIFY_CA);
// no trust store set -> handshake throws

// after
info.setTrustCertificateKeyStoreUrl("file:/etc/canal/truststore.jks");
info.setTrustCertificateKeyStoreType("JKS");
info.setTrustCertificateKeyStorePassword("changeit");
Defensive patterns

Strategy: validation

Validate before calling

boolean verify = mode == SslMode.VERIFY_CA || mode == SslMode.VERIFY_IDENTITY;
boolean hasTrustStore = StringUtils.isNotEmpty(sslInfo.getTrustCertificateKeyStoreUrl());
if (verify && !hasTrustStore) {
    // ensure default cacerts exists, else fail fast with a clear message
}

Type guard

public static boolean canVerify(SslInfo info) {
    SslMode m = info.getSslMode();
    if (m != SslMode.VERIFY_CA && m != SslMode.VERIFY_IDENTITY) return true;
    return StringUtils.isNotEmpty(info.getTrustCertificateKeyStoreUrl())
        || defaultCacertsExists();
}

Try / catch

try {
    socketFactory.createSocket(...).startHandshake();
} catch (javax.net.ssl.SSLHandshakeException e) {
    if (e.getMessage().contains("no trust manager")) {
        // configure trustCertificateKeyStoreUrl or use REQUIRED
    }
    throw e;
}

Prevention

When it happens

Trigger: Constructing an X509TrustManagerWrapper with a null origTm (the verifyServerCert-only constructor) and then calling checkServerTrusted; or the trust-manager list built in getSSLContext having no X509TrustManager to delegate to. Triggered during SSL handshake when VERIFY_CA/VERIFY_IDENTITY is set without a usable trust store.

Common situations: VERIFY_CA/VERIFY_IDENTITY configured but no trustCertificateKeyStoreUrl supplied and the default JVM truststore (cacerts) is empty/missing; the fallback wrapper path was taken (tms.size()==0 branch) and verification was requested; misconfigured truststore URL/type/password that produced no X509TrustManager.

Understand the failure class

Related errors


AI-assisted analysis of alibaba/canal@87be50e876 (2026-08-14). Data as JSON: /api/errors/a3e21de67e2c1355. Report an issue: GitHub.