Hero Image

Updating x.509 server certificate when sending mails over SMTP with STARTTLS in Jenkins


Problem

Jenkins suddenly stops sending notification mails through pipeline scripts or UI-based build jobs. /var/log/jenkins/jenkins.log does not show any errors. When trying to send a test mail through Jenkins' administration UI, you'll receive the errror

sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
    at java.base/sun.security.provider.certpath.SunCertPathBuilder.build(SunCertPathBuilder.java:141)
    at java.base/sun.security.provider.certpath.SunCertPathBuilder.engineBuild(SunCertPathBuilder.java:126)
    at java.base/java.security.cert.CertPathBuilder.build(CertPathBuilder.java:297)
    at java.base/sun.security.validator.PKIXValidator.doBuild(PKIXValidator.java:380)
Caused: sun.security.validator.ValidatorException: PKIX path building failed
    at java.base/sun.security.validator.PKIXValidator.doBuild(PKIXValidator.java:385)
    at java.base/sun.security.validator.PKIXValidator.engineValidate(PKIXValidator.java:290)
    at java.base/sun.security.validator.Validator.validate(Validator.java:264)
    at java.base/sun.security.ssl.X509TrustManagerImpl.validate(X509TrustManagerImpl.java:313)
    at java.base/sun.security.ssl.X509TrustManagerImpl.checkTrusted(X509TrustManagerImpl.java:222)
    at java.base/sun.security.ssl.X509TrustManagerImpl.checkServerTrusted(X509TrustManagerImpl.java:129)
    at java.base/sun.security.ssl.CertificateMessage$T12CertificateConsumer.checkServerCerts(CertificateMessage.java:625)
Caused: javax.net.ssl.SSLHandshakeException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
    at java.base/sun.security.ssl.Alert.createSSLException(Alert.java:131)
    at java.base/sun.security.ssl.TransportContext.fatal(TransportContext.java:320)
    at java.base/sun.security.ssl.TransportContext.fatal(TransportContext.java:263)
    at java.base/sun.security.ssl.TransportContext.fatal(TransportContext.java:258)
    at java.base/sun.security.ssl.CertificateMessage$T12CertificateConsumer.checkServerCerts(CertificateMessage.java:641)
    at java.base/sun.security.ssl.CertificateMessage$T12CertificateConsumer.onCertificate(CertificateMessage.java:460)
    at java.base/sun.security.ssl.CertificateMessage$T12CertificateConsumer.consume(CertificateMessage.java:360)
    at java.base/sun.security.ssl.SSLHandshake.consume(SSLHandshake.java:392)
    at java.base/sun.security.ssl.HandshakeContext.dispatch(HandshakeContext.java:443)
    at java.base/sun.security.ssl.HandshakeContext.dispatch(HandshakeContext.java:421)
    at java.base/sun.security.ssl.TransportContext.dispatch(TransportContext.java:177)
    at java.base/sun.security.ssl.SSLTransport.decode(SSLTransport.java:164)
    at java.base/sun.security.ssl.SSLSocketImpl.decode(SSLSocketImpl.java:1152)
    at java.base/sun.security.ssl.SSLSocketImpl.readHandshakeRecord(SSLSocketImpl.java:1063)
    at java.base/sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:402)
    at com.sun.mail.util.SocketFetcher.configureSSLSocket(SocketFetcher.java:626)
    at com.sun.mail.util.SocketFetcher.startTLS(SocketFetcher.java:553)
    at com.sun.mail.smtp.SMTPTransport.startTLS(SMTPTransport.java:2150)

Jenkins is configured (e.g. through JAVA_ARGS in /etc/default/jenkins) to always use STARTTLS for SMTP by passing -Dmail.smtp.starttls.enable=true as start paramater.

Solution

The error occurs if an invalid x.509 certificate has been provided by the SMTP server, e.g. if the certificate has expired. First, check the certificate of the SMTP server:

openssl s_client -showcerts -connect ${SMTP_SERVER}:25 -starttls smtp

You can either use an online certificate decoder or get the expiration date from the following command:

 openssl s_client -showcerts -connect ${SMTP_SERVER}:25 -starttls smtp 2>/dev/null | openssl x509 -noout -dates

Now, check the certificate in Java's keystore. On Debian, Java's keystore can be found at /etc/ssl/certs/java/cacerts. /usr/lib/jvm/${JAVA_DIST}/lib/security/cacerts is just a symlink to it. Use changeit as default password for the keystore.

keytool -list -v -keystore /etc/ssl/certs/java/cacerts  | grep  -A 10 "${SMTP_SERVER}" | grep "until"

Convert binary certificate

If the keystore has still the old certificate in it, you have to export either the SMTP server's active certificate or the root CA. For a Windows-environment with an Exchange server, you can convert the binary certificate from CER to PEM with

openssl x509  -inform der -in ${SMTP_SERVER}.cer -out ${SMTP_SERVER}.pem

Remove old certificate

Find the alias of the old certificate and then remove it

keytool -list -v -cacerts  | grep -A 10 "${SMTP_SERVER}" | grep "Alias name:" # note alias
keytool -delete -alias "${ALIAS_NAME}" -v -cacerts

Import converted certificate into Java's keystore

Copy the ${SMTP_SERVER}.pem to your host.

keytool -importcert -alias rootca -keystore /etc/ssl/certs/java/cacerts -file ${SMTP_SERVER}.pem
# restart Jenkins to apply keystore changes
systemctl restart jenkins