Ferguson
Ferguson

Reputation: 547

Add a certificate to BouncyCastle TLSSocketConnectionFactory

have a question on how to include a certificate into Bouncy Castle TLSSocketConnectionFactory?

For example, I have used this code in previous version and it works well with TLS1.0:

        SSLContext sslcontext = SSLContext.getInstance("TLS");
        CertificateFactory cf = CertificateFactory.getInstance("X.509");
        InputStream is = new FileInputStream("c:/cert/test-tls.cer");
        InputStream caInput = new BufferedInputStream(is);
        Certificate ca;
        try {
            ca = cf.generateCertificate(caInput);
        } finally {
            caInput.close();
        }
        String keyStoreType = KeyStore.getDefaultType();
        KeyStore keyStore = KeyStore.getInstance(keyStoreType);
        keyStore.load(null, null);
        keyStore.setCertificateEntry("ca", ca);

        // Create a TrustManager that trusts the CAs in our KeyStore
        String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
        TrustManagerFactory tmf = TrustManagerFactory.getInstance(tmfAlgorithm);
        tmf.init(keyStore);

        TrustManager[] tm = tmf.getTrustManagers();
        sslcontext.init(kmf.getKeyManagers(), tm, null);
        SSLSocketFactory sslSocketFactory = sslcontext.getSocketFactory();
        HttpsURLConnection.setDefaultSSLSocketFactory(sslSocketFactory);

now I have to use Bouncy Castle because Java 1.6 does not support TLS1.1 / TLS1.2

so the first step is to extend SSLSocketFactory Class..
mine is:

@Override
    public void startHandshake() throws IOException {
        tlsClientProtocol.connect(new DefaultTlsClient() {
            @Override
            public Hashtable<Integer, byte[]> getClientExtensions() throws IOException {
                Hashtable<Integer, byte[]> clientExtensions = super.getClientExtensions();
                if (clientExtensions == null) {
                    clientExtensions = new Hashtable<Integer, byte[]>();
                }

                //Add host_name
                byte[] host_name = host.getBytes();

                final ByteArrayOutputStream baos = new ByteArrayOutputStream();
                final DataOutputStream dos = new DataOutputStream(baos);
                dos.writeShort(host_name.length + 3); // entry size
                dos.writeByte(0); // name type = hostname
                dos.writeShort(host_name.length);
                dos.write(host_name);
                dos.close();
                clientExtensions.put(ExtensionType.server_name, baos.toByteArray());
                return clientExtensions;
            }

            @Override
            public TlsAuthentication getAuthentication()
                    throws IOException {
                return new TlsAuthentication() {

                    @Override
                    public void notifyServerCertificate(Certificate serverCertificate) throws IOException {

                        try {

                            CertificateFactory cf = CertificateFactory.getInstance("X.509");

                            InputStream is = new FileInputStream("c:/ascert/test-tls.cer"); //"c:/cert/test-tls.cer");
                            InputStream caInput = new BufferedInputStream(is);
                            java.security.cert.Certificate ca;
                            try {
                                ca = cf.generateCertificate(caInput);
                                System.out.println("ca=" + ((java.security.cert.X509Certificate) ca).getSubjectDN());
                            } finally {
                                caInput.close();
                            }

                            List<java.security.cert.Certificate> certs = new LinkedList<java.security.cert.Certificate>();
                            for (org.bouncycastle.asn1.x509.Certificate c : serverCertificate.getCertificateList()) {
                                certs.add(cf.generateCertificate(new ByteArrayInputStream(c.getEncoded())));
                                System.out.println(c.getIssuer());
                            }
                            peertCerts = certs.toArray(new java.security.cert.Certificate[0]);
                        } catch (Exception e) {
                            System.out.println("Failed to cache server certs" + e);
                            throw new IOException(e);
                        }

                    }

                    @Override
                    public TlsCredentials getClientCredentials(CertificateRequest arg0)
                            throws IOException {
                        return null;
                    }

                };

When I run a simple example:

public class Test {
public static void main(String[] args)
{
   String url = "https://blagajne-test.fu.gov.si:9002/v1/cash_registers";
    try {
        URLConnection connection = new URL(url).openConnection();
        HttpsURLConnection httpsURLConnection = (HttpsURLConnection) connection;
        httpsURLConnection.setSSLSocketFactory(new TSLSocketConnectionFactory());
         int responseCode = httpsURLConnection.getResponseCode();

    } catch (MalformedURLException ex) {
        Logger.getLogger(Test.class.getName()).log(Level.SEVERE, null, ex);
    } catch (IOException ex) {
        Logger.getLogger(Test.class.getName()).log(Level.SEVERE, null, ex);
    }

}

}

I get this error: SEVERE: null org.bouncycastle.crypto.tls.TlsFatalAlertReceived: handshake_failure(40) at org.bouncycastle.crypto.tls.TlsProtocol.handleAlertMessage(Unknown Source)

I don't know how to properly write SSLSocketFactory to use a certificate for the authentication with a remote server. Thank you for any help.

Upvotes: 3

Views: 6545

Answers (1)

Peter Dettman
Peter Dettman

Reputation: 4042

The latest version of BouncyCastle includes a JSSE provider ("BCJSSE", in the class org.bouncycastle.jsse.provider.BouncyCastleJsseProvider) in the bctls-jdk15on-159 jar. If you install this provider with higher priority than the default one, then your original code sample should automatically pick up the BCJSSE provider.

BCJSSE supports TLS 1.2 back to JDK 1.5. There are also some BCJSSE-specific extensions in the org.bouncycastle.jsse package for accessing TLS features not supported by SunJSSE in earlier JDKs (or at all), e.g. Server Name Indication.

There are still minor features missing compared to SunJSSE, but for the majority of users it should be a straightforward replacement, certainly a better option than implementing SSLSocketFactory yourself.

Upvotes: 5

Related Questions