Received fatal alert: bad_certificate

2020-05-20 09:10发布

问题:

I am trying to setup a SSL Socket connection (and am doing the following on the client)

  1. I generate a Certificte Signing Request to obtain a signed client certificate

  2. Now I have a private key (used during the CSR), a signed client certificate and root certificate (obtained out of band).

  3. I add the private key and signed client certificate to a cert chain and add that to the key manager. and the root cert to the trust manager. But I get a bad certificate error.

I am pretty sure I am using the right certs. Should I add the signed client cert to the trust manager as well? Tried that, no luck still.

//I add the private key and the client cert to KeyStore ks
FileInputStream certificateStream = new FileInputStream(clientCertFile);
CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
java.security.cert.Certificate[] chain = {};
chain = certificateFactory.generateCertificates(certificateStream).toArray(chain);
certificateStream.close();
String privateKeyEntryPassword = "123";
ks.setEntry("abc", new KeyStore.PrivateKeyEntry(privateKey, chain),
        new KeyStore.PasswordProtection(privateKeyEntryPassword.toCharArray()));

//Add the root certificate to keystore jks
FileInputStream is = new FileInputStream(new File(filename));
CertificateFactory cf = CertificateFactory.getInstance("X.509");
java.security.cert.X509Certificate cert = (X509Certificate) cf.generateCertificate(is);
System.out.println("Certificate Information: ");
System.out.println(cert.getSubjectDN().toString());
jks.setCertificateEntry(cert.getSubjectDN().toString(), cert);

//Initialize the keymanager and trustmanager and add them to the SSL context
KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
kmf.init(ks, "123".toCharArray());
TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");
tmf.init(jks);

Is there some sort of certificate chain that I need to create here?
I had a p12 with these components as well and upon using pretty similar code, adding the private key to the keymanager and the root cert from p12 to the trust manager I could make it work. But now I need to make it work without the p12.

EDIT: Stack trace was requested. Hope this should suffice. (NOTE: I masked the filenames)

Caused by: javax.net.ssl.SSLHandshakeException: Received fatal alert: bad_certificate
at com.sun.net.ssl.internal.ssl.Alerts.getSSLException(Alerts.java:174)
at com.sun.net.ssl.internal.ssl.Alerts.getSSLException(Alerts.java:136)
at com.sun.net.ssl.internal.ssl.SSLSocketImpl.recvAlert(SSLSocketImpl.java:1720)
at com.sun.net.ssl.internal.ssl.SSLSocketImpl.readRecord(SSLSocketImpl.java:954)
at com.sun.net.ssl.internal.ssl.SSLSocketImpl.performInitialHandshake(SSLSocketImpl.java:1138)
at com.sun.net.ssl.internal.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1165)
at com.sun.net.ssl.internal.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1149)
at client.abc2.openSocketConnection(abc2.java:33)
at client.abc1.runClient(abc1.java:63)
at screens.app.abc.validateLogin(abc.java:197)
... 32 more

回答1:

You need to add the root cert to the keystore as well.



回答2:

I got this error when I removed these 2 lines. If you know your keystore has the right certs, make sure your code is looking at the right keystore.

System.setProperty("javax.net.ssl.keyStore", <keystorePath>));
System.setProperty("javax.net.ssl.keyStorePassword",<keystorePassword>));

I also needed this VM argument: -Djavax.net.ssl.trustStore=/app/certs/keystore.jk See here for more details: https://stackoverflow.com/a/34311797/1308453



回答3:

Provided that the server certificate is signed and valid, you only need to open the connection as usual:

import java.net.*;
import java.io.*;

public class URLConnectionReader {
    public static void main(String[] args) throws Exception {
        URL google = new URL("https://www.google.com/");
        URLConnection yc = google.openConnection();
        BufferedReader in = new BufferedReader(new InputStreamReader(
                                    yc.getInputStream()));
        String inputLine;
        while ((inputLine = in.readLine()) != null) 
            System.out.println(inputLine);
        in.close();
    }
}

Note that the URL has the HTTPS schema to indicate the use of SSL.

If the server's certificate is signed but you are accessing using a different IP address/domain name than the one in the certificate, you can bypass hostname verification with this:

HostnameVerifier hv = new HostnameVerifier() {
    public boolean verify(String urlHostName,SSLSession session) {
        return true;
    }
};

HttpsURLConnection.setDefaultHostnameVerifier(hv);

If the certificate is not signed then you need to add it to the keystore used by the JVM (useful commands).