TLS 1.2 + Java 1.6 + BouncyCastle

7

我们开发了一个基于Bouncy Castle Libraries(v.1.53)的自定义TLS SocketConnection工厂,用于通过Java 1.6 API支持使用TLS 1.2连接到远程主机的HTTPS连接。

它非常易于使用,只需:

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

在测试过程中,我连接不同的Web和远程主机,这些主机都暴露在SSLabs Tests中。

90%的时间,这个过程很顺利!但是有些情况下,我们会遇到一个烦人的错误:"内部TLS错误,可能是攻击"。经过检查,确认没有受到攻击。这是一种基于内部BouncyCastle异常处理的常见错误。我正在尝试找到那些失败的远程主机的共同模式,但进展并不顺利。

更新:

为了获取额外信息,更新了一些代码,我们得到了如下结果:

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)

我得到的扩展类型是这样的:
ExtensionType:11 ExtensionData:
根据ExtensionType类,它是“ec_point_formats”。这会导致“UnexpectedServerExtension”-->“UnexpectedServerExtension”引起-->TlsFatalAlert:illegal_parameter,最后是“Internal TLS error, this could be an attack”。
有什么建议可以记录或跟踪这些奇怪的TLS错误吗?正如我所说,这段代码工作了90%...但是对于某些远程主机,我会收到这个错误。
诀窍在于覆盖startHandShake以使用Bouncy的TLSClientProtocol:
1.重写ClientExtensions以包括“host”ExtensionType。只有ExtensionType.server_name(也许还有更多要包含的扩展类型?) 2.创建一个TlsAuthentication来包含Socket的remoteCerts peerCertificate。还可以选择检查远程证书是否在默认密钥库(cacerts等)中。
我分享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

    }

}

TLS服务器是否使用了一种服务器扩展来表达其椭圆曲线代码,而该代码不受客户端支持?这在rfc4492的第5节中有描述。 - Axel Kemper
理论上,区块链提供商支持椭圆曲线扩展... - Azimuts
1
AbstractTlsClient中调用TlsECCUtils.readSupportedEllipticCurvesExtension来确定支持的扩展。看起来,这些服务器执行的操作还不被Bouncycastle支持。 - Axel Kemper
可能是远程服务器出了问题吗? - Azimuts
在其ClientHello消息中建议使用“ECC密码套件”的客户端,附加这些扩展(以及任何其他扩展),枚举其支持的曲线和可以解析的点格式。请检查您的客户端发送给服务器的内容。 - Axel Kemper
显示剩余5条评论
2个回答

4
如果您查看RFC 4492 5.2,您会发现服务器可以发送“ec_point_formats”扩展名,但只应在“协商ECC密码套件”时这样做。如果您希望TLSClient忽略额外的扩展名而不是引发异常,则建议覆盖TlsClient.allowUnexpectedServerExtension(...)以允许以与默认实现允许椭圆曲线相同的方式使用ec_point_formats:
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);
    }
}

如果这是一个普遍存在的问题,我们可能会考虑将这种情况添加到默认实现中。
对于日志记录,您可以在TLSClient实现中覆盖(TLSPeer)方法notifyAlertRaised和notifyAlertReceived。

你试过了吗?实际上你可以在子类中覆盖一个受保护的方法。你提供的代码创建了DefaultTlsClient的(匿名)子类,这就是你想要覆盖allowUnexpectedServerExtension()方法的地方。 - Peter Dettman

1
我发布这篇文章,希望能帮助一些人 - 我的BC客户端代码也出现了invalid_parameter(47)异常。当我们将BC从版本1.62(bcprov-和bctls-jdk15on-162 JAR)升级到1.71(bcprov-、bctls-、bcpkix-、bcutil-jdk15to18-171)时,就开始出现了这个错误。 虽然我的堆栈跟上面的问题不同:

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

我遇到了一些麻烦,无法调试内部的BC TLS代码。提供的JAR文件没有编译调试信息 - 我必须从Maven Central下载源文件并构建它们,在Netbeans中创建一个新项目来引用这些源文件 - 它只能使用Java 8进行构建,但最终结果可以被我的Java 6客户端代码引用。

编译带有调试信息的bctls,并将其作为我的客户端代码的引用,我实际上可以进入抛出异常的代码:

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;
        }
    ...
}

我不是很清楚“先加密后鉴别”和“先鉴别后加密”的意思,也不想深入了解它们的含义。经过一些搜索,我发现可能需要限制用于通信的密码套件列表 - 因为可能默认选择的某些密码套件会强制服务器进入某种状态,导致其行为与RFC规范不同 - 是的,至少这个方法可行:

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

我学到的其他有用信息是:


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接