TLS 1.2 + Java 1.6 + BouncyCastle
Asked Answered
H

2

7

For supporting HTTPS connections through a Java 1.6 API to remote hosts using TLS 1.2, we have developed a customized TLS SocketConnection factory based on Bouncy Castle Libraries (v. 1.53)

It's very easy to use, just:

        String httpsURL =  xxxxxxxxxx
        URL myurl = new URL(httpsURL);      
        HttpsURLConnection  con = (HttpsURLConnection )myurl.openConnection();
        con.setSSLSocketFactory(new TSLSocketConnectionFactory());   
        InputStream ins = con.getInputStream();

During testing, I connect different web and remote hosts exposed into SSLabs Tests

90% of the time this works fine! But there are some cases in which we get an annoying error: "Internal TLS error, this could be an attack" . It has been checked that there is no attack. That's a common error based on the treatment of internal BouncyCastle exceptions. I'm trying to find a common pattern to those remote host that fails with little luck.

Updated:

Updating some code for extra information, we get this:

org.bouncycastle.crypto.tls.TlsFatalAlert: illegal_parameter(47)
    at org.bouncycastle.crypto.tls.AbstractTlsClient.checkForUnexpectedServerExtension(AbstractTlsClient.java:56)
    at org.bouncycastle.crypto.tls.AbstractTlsClient.processServerExtensions(AbstractTlsClient.java:207)
    at org.bouncycastle.crypto.tls.TlsClientProtocol.receiveServerHelloMessage(TlsClientProtocol.java:773)

The extension type i get is this:

ExtensionType:11 ExtensionData:

Acording to ExtensionType class, "ec_point_formats". This causes "UnexpectedServerExtension" --> The "UnexpectedServerExtension" causes a --> TlsFatalAlert: illegal_parameter , and at last this a "Internal TLS error, this could be an attack"

Any advise to log or trace this strange TLS Errors....???? As i say, this code works 90%...but with some remote host i get this errof

The trick consists in overriding startHandShake to use Bouncy's TLSClientProtocol:

  1. Override ClientExtensions to include "host" ExtensionType. Just ExtensionType.server_name ( maybe any more Extension to include?)
  2. Create a TlsAuthentication to include remoteCerts on Socket's peerCertificate .Also optionally check if remote certs are in default keystore (cacerts,etc..)

I share the code of TLSSocketConnectionFactory:

public class TLSSocketConnectionFactory extends SSLSocketFactory {  

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//Adding Custom BouncyCastleProvider
///////////////////////////////////////////////////////////////////////////////////////////////////////////////

    static {
        if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null) {
            Security.addProvider(new BouncyCastleProvider());
        }
    }   

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//SECURE RANDOM
//////////////////////////////////////////////////////////////////////////////////////////////////////////////

    private SecureRandom _secureRandom = new SecureRandom();

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//Adding Custom BouncyCastleProvider
///////////////////////////////////////////////////////////////////////////////////////////////////////////////

    @Override
    public Socket createSocket(Socket socket, final String host, int port, boolean arg3)
            throws IOException {
        if (socket == null) {
            socket = new Socket();
        }
        if (!socket.isConnected()) {
            socket.connect(new InetSocketAddress(host, port));
        }

        final TlsClientProtocol tlsClientProtocol = new TlsClientProtocol(socket.getInputStream(), socket.getOutputStream(), _secureRandom);       

        return _createSSLSocket(host, tlsClientProtocol);

    }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// SOCKET FACTORY  METHODS  
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////  

    @Override
    public String[] getDefaultCipherSuites() {
        return null;
    }

    @Override
    public String[] getSupportedCipherSuites(){
        return null;
    }

    @Override
    public Socket createSocket( String host,
                                int port) throws IOException,UnknownHostException{
        return null;
    }

    @Override
    public Socket createSocket( InetAddress host,
                                int port) throws IOException {
        return null;
    }

    @Override
    public Socket createSocket( String host, 
                                int port, 
                                InetAddress localHost,
                                int localPort) throws IOException, UnknownHostException {
        return null;
    }

    @Override
    public Socket createSocket( InetAddress address,
                                int port,
                                InetAddress localAddress,
                                int localPort) throws IOException{
        return null;
    }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//SOCKET CREATION
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

    private SSLSocket _createSSLSocket(final String host , final TlsClientProtocol tlsClientProtocol) {
        return new SSLSocket() {
            private java.security.cert.Certificate[] peertCerts;

            @Override
            public InputStream getInputStream() throws IOException {
                return tlsClientProtocol.getInputStream();
            }

            @Override
            public OutputStream getOutputStream() throws IOException {
                return tlsClientProtocol.getOutputStream();
            }

            @Override
            public synchronized void close() throws IOException {
                Log.to("util").info("\\\n::::::Close Socket");
                tlsClientProtocol.close();
            }

            @Override
            public void addHandshakeCompletedListener(HandshakeCompletedListener arg0) {

            }

            @Override
            public boolean getEnableSessionCreation() {
                return false;
            }

            @Override
            public String[] getEnabledCipherSuites() {
                return null;
            }

            @Override
            public String[] getEnabledProtocols() {
                return null;
            }

            @Override
            public boolean getNeedClientAuth(){
                return false;
            }

            @Override
            public SSLSession getSession() {
                return new SSLSession() {

                    @Override
                    public int getApplicationBufferSize() {
                        return 0;
                    }

                    @Override
                    public String getCipherSuite() {
                        throw new UnsupportedOperationException();
                    }

                    @Override
                    public long getCreationTime() {
                        throw new UnsupportedOperationException();
                    }

                    @Override
                    public byte[] getId() {
                        throw new UnsupportedOperationException();
                    }

                    @Override
                    public long getLastAccessedTime() {
                        throw new UnsupportedOperationException();
                    }

                    @Override
                    public java.security.cert.Certificate[] getLocalCertificates() {
                        throw new UnsupportedOperationException();
                    }

                    @Override
                    public Principal getLocalPrincipal() {
                        throw new UnsupportedOperationException();
                    }

                    @Override
                    public int getPacketBufferSize() {
                        throw new UnsupportedOperationException();
                    }

                    @Override
                    public X509Certificate[] getPeerCertificateChain()
                            throws SSLPeerUnverifiedException {
                        return null;
                    }

                    @Override
                    public java.security.cert.Certificate[] getPeerCertificates()throws SSLPeerUnverifiedException {
                        return peertCerts;
                    }

                    @Override
                    public String getPeerHost() {
                        throw new UnsupportedOperationException();
                    }

                    @Override
                    public int getPeerPort() {
                        return 0;
                    }

                    @Override
                    public Principal getPeerPrincipal() throws SSLPeerUnverifiedException {
                        return null;
                        //throw new UnsupportedOperationException();
                    }

                    @Override
                    public String getProtocol() {
                        throw new UnsupportedOperationException();
                    }

                    @Override
                    public SSLSessionContext getSessionContext() {
                        throw new UnsupportedOperationException();
                    }

                    @Override
                    public Object getValue(String arg0) {
                        throw new UnsupportedOperationException();
                    }

                    @Override
                    public String[] getValueNames() {
                        throw new UnsupportedOperationException();
                    }

                    @Override
                    public void invalidate() {
                        throw new UnsupportedOperationException();
                    }

                    @Override
                    public boolean isValid() {
                        throw new UnsupportedOperationException();
                    }

                    @Override
                    public void putValue(String arg0, Object arg1) {
                        throw new UnsupportedOperationException();
                    }

                    @Override
                    public void removeValue(String arg0) {
                        throw new UnsupportedOperationException();
                    }

                };
            }


            @Override
            public String[] getSupportedProtocols() {
                return null;
            }

            @Override
            public boolean getUseClientMode() {
                return false;
            }

            @Override
            public boolean getWantClientAuth() {
                return false;
            }

            @Override
            public void removeHandshakeCompletedListener(HandshakeCompletedListener arg0) {

            }

            @Override
            public void setEnableSessionCreation(boolean arg0) {

            }

            @Override
            public void setEnabledCipherSuites(String[] arg0) {

            }

            @Override
            public void setEnabledProtocols(String[] arg0) {

            }

            @Override
            public void setNeedClientAuth(boolean arg0) {

            }

            @Override
            public void setUseClientMode(boolean arg0) {

            }

            @Override
            public void setWantClientAuth(boolean arg0) {

            }

            @Override
            public String[] getSupportedCipherSuites() {
                return null;
            }
            @Override
            public void startHandshake() throws IOException {

                Log.to("util").info("TSLSocketConnectionFactory:startHandshake()");
                tlsClientProtocol.connect(new DefaultTlsClient() {
                    @SuppressWarnings("unchecked")
                    @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);
                        dos.writeByte(0); // 
                        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 {
                                    KeyStore ks = _loadKeyStore();
                                    Log.to("util").info(">>>>>>>> KeyStore : "+ks.size());

                                    CertificateFactory cf = CertificateFactory.getInstance("X.509");
                                    List<java.security.cert.Certificate> certs = new LinkedList<java.security.cert.Certificate>();
                                    boolean trustedCertificate = false;
                                    for ( org.bouncycastle.asn1.x509.Certificate c : serverCertificate.getCertificateList()) {
                                        java.security.cert.Certificate cert = cf.generateCertificate(new ByteArrayInputStream(c.getEncoded()));
                                        certs.add(cert);

                                        String alias = ks.getCertificateAlias(cert);
                                        if(alias != null) {
                                            Log.to("util").info(">>> Trusted cert\n" + c.getSubject().toString());
                                            if (cert instanceof java.security.cert.X509Certificate) {
                                                try {
                                                    ( (java.security.cert.X509Certificate) cert).checkValidity();
                                                    trustedCertificate = true;
                                                    Log.to("util").info("Certificate is active for current date\n"+cert);
                                                } catch(CertificateExpiredException cee) {
                                                    R01FLog.to("r01f.util").info("Certificate is expired...");
                                                }
                                            }
                                        } else {
                                        Log.to("util").info(">>> Unknown cert " + c.getSubject().toString());
                                            Log.to("util").fine(""+cert);
                                        }

                                    }
                                    if (!trustedCertificate) {
                                        throw new CertificateException("Unknown cert " + serverCertificate);
                                    }
                                    peertCerts = certs.toArray(new java.security.cert.Certificate[0]);
                                } catch (Exception ex) {
                                    ex.printStackTrace();
                                    throw new IOException(ex);
                                }

                            }

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

                            /**
                             * Private method to load keyStore with system or default properties.
                             * @return
                             * @throws Exception
                             */
                            private KeyStore _loadKeyStore() throws Exception {
                                FileInputStream trustStoreFis = null;
                                try {
                                    String sysTrustStore = null;
                                    File trustStoreFile = null;

                                    KeyStore localKeyStore = null;

                                    sysTrustStore = System.getProperty("javax.net.ssl.trustStore");
                                    String javaHome;
                                    if (!"NONE".equals(sysTrustStore)) {
                                        if (sysTrustStore != null) {
                                            trustStoreFile = new File(sysTrustStore);
                                            trustStoreFis = _getFileInputStream(trustStoreFile);
                                        } else {
                                            javaHome = System.getProperty("java.home");
                                            trustStoreFile = new File(javaHome + File.separator + "lib" + File.separator + "security" + File.separator + "jssecacerts");

                                            if ((trustStoreFis = _getFileInputStream(trustStoreFile)) == null) {
                                                trustStoreFile = new File(javaHome + File.separator + "lib" + File.separator + "security" + File.separator + "cacerts");
                                                trustStoreFis = _getFileInputStream(trustStoreFile);
                                            }
                                        }

                                        if (trustStoreFis != null) {
                                            sysTrustStore = trustStoreFile.getPath();
                                        } else {
                                            sysTrustStore = "No File Available, using empty keystore.";
                                        }
                                    }

                                    String trustStoreType = System.getProperty("javax.net.ssl.trustStoreType")!=null?System.getProperty("javax.net.ssl.trustStoreType"):KeyStore.getDefaultType();
                                    String trustStoreProvider = System.getProperty("javax.net.ssl.trustStoreProvider")!=null?System.getProperty("javax.net.ssl.trustStoreProvider"):"";

                                    if (trustStoreType.length() != 0) {
                                        if (trustStoreProvider.length() == 0) {
                                            localKeyStore = KeyStore.getInstance(trustStoreType);
                                        } else {
                                            localKeyStore = KeyStore.getInstance(trustStoreType, trustStoreProvider);
                                        }

                                        char[] keyStorePass = null;
                                        String str5 = System.getProperty("javax.net.ssl.trustStorePassword")!=null?System.getProperty("javax.net.ssl.trustStorePassword"):"";

                                        if (str5.length() != 0) {
                                            keyStorePass = str5.toCharArray();
                                        }

                                        localKeyStore.load(trustStoreFis, (char[]) keyStorePass);

                                        if (keyStorePass != null) {
                                            for (int i = 0; i < keyStorePass.length; i++) {
                                                keyStorePass[i] = 0;
                                            }
                                        }
                                    }
                                    return (KeyStore)localKeyStore;
                                } finally {
                                    if (trustStoreFis != null) {
                                        trustStoreFis.close();
                                    }
                                }
                            }

                            private FileInputStream _getFileInputStream(File paramFile) throws Exception {
                                if (paramFile.exists()) {
                                    return new FileInputStream(paramFile);
                                }
                                return null;
                            }

                        };

                    }

                });

            }

        };//Socket

    }

}
Hypostatize answered 4/11, 2015 at 8:56 Comment(10)
Could it be that the TLS server is using a server extension to express his Eliptic Curve Code which is not supported by the client? This is described in rfc4492 in section 5.Exposed
Theoretically the BC providers supports the eliptic curves extendions...Hypostatize
TlsECCUtils.readSupportedEllipticCurvesExtension is called in AbstractTlsClient to determine the supported extensions. It appears that the servers in question do things not supported yet by Bouncycastle.Exposed
analyzing Bouncy Castle code, This is commented at the point of the exception: " * Exception added based on field reports that some servers do send this, although the * Supported Elliptic Curves Extension is clearly intended to be client-only. If * present, we still require that it is a valid EllipticCurveList."Hypostatize
Maybe is something wrong in the remote server??Hypostatize
The received problematic extension is called in BC code: "ec_point_formats" ... According to this (tools.ietf.org/html/rfc4492) it could be a client sent extension...but no a extension sent from server ¿?¿?¿Hypostatize
A client that proposes ECC cipher suites in its ClientHello message appends these extensions (along with any others), enumerating the curves it supports and the point formats it can parse. Check what your client has sent to the server.Exposed
Just server_name extension.... :Hypostatize
final ByteArrayOutputStream baos = new ByteArrayOutputStream(); final DataOutputStream dos = new DataOutputStream(baos); dos.writeShort(host_name.length + 3); dos.writeByte(0); // dos.writeShort(host_name.length); dos.write(host_name); dos.close(); clientExtensions.put(ExtensionType.server_name, baos.toByteArray());Hypostatize
I wish you had shared all of the code for your TLSSocketConnectionFactory, including all the imports. I am trying to replicate your solution.Whiffletree
D
4

If you look at RFC 4492 5.2, you'll see that the server CAN send the "ec_point_formats" extension, but is only supposed to do so "when negotiating an ECC cipher suite". If you want TLSClient to just ignore the extra extension instead of raising an exception, I suggest overriding TlsClient.allowUnexpectedServerExtension(...) to allow ec_point_formats in the same way the default implementation allows elliptic_curves:

protected boolean allowUnexpectedServerExtension(Integer extensionType, byte[] extensionData)
    throws IOException
{
    switch (extensionType.intValue())
    {
    case ExtensionType.ec_point_formats:
        /*
         * Exception added based on field reports that some servers send Supported
         * Point Format Extension even when not negotiating an ECC cipher suite.
         * If present, we still require that it is a valid ECPointFormatList.
         */
        TlsECCUtils.readSupportedPointFormatsExtension(extensionData);
        return true;
    default:
        return super.allowUnexpectedServerExtension(extensionType, extensionData);
    }
}

If this is a widespread problem, we might consider adding this case to the default implementation.

For logging, there are the (TLSPeer) methods notifyAlertRaised and notifyAlertReceived that you can override on your TLSClient implementation.

Dialectician answered 7/11, 2015 at 10:55 Comment(1)
Did you try it? You can in fact override a protected method in a subclass. The code you give creates an (anonymous) subclass of DefaultTlsClient, this is where you want to override allowUnexpectedServerExtension().Dialectician
G
1

i am posting this so maybe some people might find this helpful - i was also getting invalid_parameter(47) exception from my BC client code. this error started to happen when we upgraded BC from version 1.62 (bcprov- and bctls-jdk15on-162 JARs) to 1.71 (bcprov-, bctls-, bcpkix-, bcutil-jdk15to18-171). my stacktrace was different from the above problem, though:

org.bouncycastle.tls.TlsFatalAlert: illegal_parameter(47) at org.bouncycastle.tls.TlsClientProtocol.processServerHello(Unknown Source) at org.bouncycastle.tls.TlsClientProtocol.handleHandshakeMessage(Unknown Source) ...

I had some trouble figuring out how to debug the internal BC TLS code. Тhe provided JARs are compiled without the debug info - I had to download the source files from Maven Central and build from them, creating a new project in Netbeans with existing sources - the only Java version it can be built with is 8, but the end result could be referenced by my Java 6 client code.

Having compiled bctls with debug info, and referencing it from my client, I could actually land in the code that was throwing the exception:

protected void processServerHello(ServerHello serverHello)
    throws IOException
{
    ...
    
    if (sessionServerExtensions != null && !sessionServerExtensions.isEmpty())
    {
        {
            /*
             * RFC 7366 3. If a server receives an encrypt-then-MAC request extension from a client
             * and then selects a stream or Authenticated Encryption with Associated Data (AEAD)
             * ciphersuite, it MUST NOT send an encrypt-then-MAC response extension back to the
             * client.
             */
            boolean serverSentEncryptThenMAC = TlsExtensionsUtils.hasEncryptThenMACExtension(sessionServerExtensions);
            if (serverSentEncryptThenMAC && !TlsUtils.isBlockCipherSuite(securityParameters.getCipherSuite()))
            {
                throw new TlsFatalAlert(AlertDescription.illegal_parameter);
            }
            securityParameters.encryptThenMAC = serverSentEncryptThenMAC;
        }
    ...
}

I don't really know what encrypt-then-MAC and MAC-then-encrypt is, and I didn't want to get deep into their meaning. After some searching, I figured out that possibly I had to limit the list of the cipher suites used for the communication - because possibly some cipher suite that is chosen by default forces the server into some condition where it behaves differently from the RFC specification - and yes, this at least worked:

TrustManagerFactory tmf = ...
SSLContext sc = SSLContext.getInstance("TLSv1.2", BouncyCastleJsseProvider.PROVIDER_NAME);
sc.init(null, tmf.getTrustManagers(), new SecureRandom());

SSLConnectionSocketFactory ret = new SSLConnectionSocketFactory(sc, 
        new String[] { "TLSv1.2" }, 
        new String[] { "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384", "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384", "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256", "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256", 
            "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384", "TLS_DHE_DSS_WITH_AES_256_GCM_SHA384", "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", "TLS_DHE_DSS_WITH_AES_128_GCM_SHA256" }, 
        SSLConnectionSocketFactory.getDefaultHostnameVerifier());

// the created SSLConnectionSocketFactory is then used to create socketFactoryRegistry 
// which is then passed to constructor of Apache HttpComponents' PoolingHttpClientConnectionManager 
// which is used to create a new instance of Apache HttpClient

Other useful stuff that I learned is

Glaucoma answered 17/4, 2022 at 10:39 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.