Tls: failed to verify certificate: x509: certificate signed by unknown authority

We have a complex product, using several 3rd party applications for e.g. reporting, such as ElasticSearch and telegraf.

For product testing, we generate our own signed certificates to distribute between components. We’re changing this code from relying on the keytool/openssl on the command line to use plain Java. Everything appears to work fine, but telegraf is unable to connect to other services (e.g. ElasticSearch) using the server key generated with the new method.

We have narrowed it down to the server.crt provided - when we use the certificate with the new tooling, we get the certificate signed by unknown authority error, but if we use the openssl command with the same input, everything is fine.

The Java code is:

        // csr is PKCS10CertificationRequest 
        X500Name issuer = new X500Name(RFC4519Style.INSTANCE, caCert.getSubjectX500Principal().getName());

        // Get the subject (CSR's subject)
        X500Name subject = csr.getSubject();

        // Generate serial number
        BigInteger serialNumber = new BigInteger(String.valueOf(caSerial.getAndIncrement())); // caSerial is a MutableLong

        // Set validity period
        Instant now = Instant.now();
        Date notBefore = Date.from(now);
        Date notAfter = Date.from(now.plus(365, ChronoUnit.DAYS));

        ContentSigner signer = new JcaContentSignerBuilder(SIGNATURE_ALGORITHM).build(caKey);
        // server cert
        var certBuilder = new JcaX509v3CertificateBuilder(issuer,
                                                          serialNumber,
                                                          notBefore,
                                                          notAfter,
                                                          subject,
                                                          new JcaPEMKeyConverter().getPublicKey(csr.getSubjectPublicKeyInfo()));
        certBuilder.addExtension(Extension.subjectAlternativeName,
                                 false,
                                 new GeneralNames(subjectAltNames));

               try (JcaPEMWriter pemWriter = new JcaPEMWriter(new FileWriter(target))) {
                 // Convert to X509Certificate and write .crt file
                 pemWriter.writeObject(new JcaX509CertificateConverter().getCertificate(certBuilder.build(signer)));
        }

The openssl command is

openssl x509 -req -in $csrFile -CA $caCert -CAkey $caKey -CAserial $caSerial -out $filename -days $validity  -extensions req_ext -extfile $extensionsFile

The above command uses the same signing request ($csrFile) , CA cert an key. The resulting certificates only differ in their dates (obviously) and Signature Algorithm (again, obviously)

We’re using telegraf v1.27.4

Connecting to elastic using

openssl s_client -connect elastic_server:9200 -CAfile server.crt

works ok, with both the certificate that doesn’t work with telegraf and the one that does (after the corresponding elastic files have been updated)

Any suggestions on what might be going wrong would be most welcome.

We found the solution:
The issue is the way Go checks if a certificate is present in the provided chain: it calls

rootCa.haveSum[sha256.Sum224(cert.Raw)]

This means that the certificates have to match exactly - even their subject’s string encodings!

Running

openssl asn1parse -in server.crt -inform PEM

for the “good” and “bad” certificates quickly revealed that some of the subject fields were UTF8STRING in one and PRINTABLESTRING in the other. The BouncyCastle library (that we chose for the Java implementation) will default the encoding of string values to PRINTABLESTRING unless there are characters that are considered “non-printable” in it. Somewhere along the line, the encoding got mixed up and as a result, the certificate was not found.

Forcing the encoding for the certificate’s subject and reconstructing the issuer X500Name resolved the issue.