使用自定义X509KeyManager时,Java无法为SSL握手确定匹配的密码套件

我正在使用Java7和JAX-WS 2.2。

对于SOAP Web服务,我需要创建一个自定义X509KeyManager ,以便为JKS密钥库中的每个连接客户端找到正确的证书。

但是,我已经在努力让我的自定义密钥管理器运行。 到目前为止,我使用的是默认值(从初始化的KeyManagerFactory检索),它基本上可以工作 – 但当然它没有选择正确的证书。 所以第一个想法是创建一个自定义X509KeyManager ,它保存原始密钥管理器,只写出一些日志消息,但通常使用默认行为。

出于某种原因,根本不起作用。 无法建立SSL握手。 在ClientHello之后 ,日志显示以下错误:

 Allow unsafe renegotiation: false Allow legacy hello messages: true Is initial handshake: true Is secure renegotiation: false Thread-3, READ: TLSv1 Handshake, length = 149 *** ClientHello, TLSv1 RandomCookie: GMT: 1476877930 bytes = { 207, 226, 8, 128, 40, 207, 47, 180, 146, 211, 157, 64, 239, 13, 201, 92, 158, 111, 108, 44, 223, 136, 193, 251, 33, 202, 7, 90 } Session ID: {} Cipher Suites: [TLS_DHE_RSA_WITH_AES_128_CBC_SHA, TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA, SSL_DHE_RSA_WITH_3DES_EDE_CBC_SHA, SSL_RSA_WITH_RC4_128_SHA, TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA, TLS_ECDHE_RSA_WITH_RC4_128_SHA, TLS_ECDH_ECDSA_WITH_RC4_128_SHA, TLS_ECDHE_ECDSA_WITH_RC4_128_SHA, TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA, TLS_ECDH_RSA_WITH_RC4_128_SHA, TLS_EMPTY_RENEGOTIATION_INFO_SCSV, TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA, TLS_ECDH_RSA_WITH_AES_128_CBC_SHA, TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, TLS_RSA_WITH_AES_128_CBC_SHA, TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA, SSL_RSA_WITH_RC4_128_MD5, TLS_DHE_DSS_WITH_AES_128_CBC_SHA, SSL_DHE_DSS_WITH_3DES_EDE_CBC_SHA, SSL_RSA_WITH_3DES_EDE_CBC_SHA] Compression Methods: { 0 } Extension elliptic_curves, curve names: {secp256r1, sect163k1, sect163r2, secp192r1, secp224r1, sect233k1, sect233r1, sect283k1, sect283r1, secp384r1, sect409k1, sect409r1, secp521r1, sect571k1, sect571r1, secp160k1, secp160r1, secp160r2, sect163r1, secp192k1, sect193r1, sect193r2, secp224k1, sect239k1, secp256k1} Extension ec_point_formats, formats: [uncompressed] *** %% Initialized: [Session-3, SSL_NULL_WITH_NULL_NULL] Thread-3, fatal error: 40: no cipher suites in common javax.net.ssl.SSLHandshakeException: no cipher suites in common %% Invalidated: [Session-3, SSL_NULL_WITH_NULL_NULL] Thread-3, SEND TLSv1 ALERT: fatal, description = handshake_failure Thread-3, WRITE: TLSv1 Alert, length = 2 Thread-3, fatal: engine already closed. Rethrowing javax.net.ssl.SSLHandshakeException: no cipher suites in common 

据我所知,我根本没有删除任何密码套件! 并且可以使用相同的证书进行SSL握手。

这是我的主要经理:

 public class CustomX509KeyManager extends X509ExtendedKeyManager { private static final Logger LOG = Logger.getLogger( CustomX509KeyManager.class ); private final X509KeyManager originalKeyManager; public CustomX509KeyManager(final X509KeyManager keyManager) { super(); this.originalKeyManager = keyManager; } @Override public String chooseServerAlias(final String keyType, final Principal[] issuers, final Socket socket) { final String serverAliases= this.originalKeyManager.chooseServerAlias( keyType, issuers, socket ); CustomX509KeyManager.LOG.info( "chooseServerAlias() " + serverAliases ); return serverAliases; } ... } 

其他方法(此处未显示)也只是调用originalKeyManager的相应方法。 在测试期间,我从未看到来自chooseServerAlias()方法的日志消息。

它是从getSslContext()方法中的另一个类初始化的:

 private KeyManager[] getKeyManagers(final KeyManagerFactory keyManagerFactory) { final KeyManager[] keyManagers = keyManagerFactory.getKeyManagers(); // replace any X509KeyManager with our own implementation for ( int i = 0; i < keyManagers.length; i++ ) { if ( keyManagers[i] instanceof X509KeyManager ) { keyManagers[i] = new CustomX509KeyManager( ( X509KeyManager ) keyManagers[i] ); } } return keyManagers; } public SSLContext getSslContext() { // create the KeyStore and load the JKS file final KeyStore keyStore = createKeyStore(); // initialize key and trust manager factory final KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance( KeyManagerFactory.getDefaultAlgorithm() ); keyManagerFactory.init( keyStore, "changeit".toCharArray() ); final TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance( TrustManagerFactory.getDefaultAlgorithm() ); trustManagerFactory.init( keyStore ); // initialize the SSL context final SSLContext sslContext = SSLContext.getInstance( "TLS" ); // sslContext.init( keyManagerFactory.getKeyManagers(), // trustManagerFactory.getTrustManagers(), new SecureRandom() ); sslContext.init( getKeyManagers( keyManagerFactory ), trustManagerFactory.getTrustManagers(), new SecureRandom() ); return sslContext; } 

注释行显示默认密钥管理器的原始用法。

知道什么是错的吗? 为什么使用我的CustomX509KeyManager的行为与默认密钥管理器的不同,不能进行握手? 使用默认密钥管理器,将为TLS_DHE_RSA_WITH_AES_128_CBC_SHA算法协商加密,该算法也可用于自定义密钥管理器,但由于某些原因未选择。

更新1

我正在尝试以客户端模式将openssl连接到服务器,但服务器使用SSL遇到相同的问题。 当我使用TLS协议然后附加错误消息

不支持的扩展类型_35,数据:

出现。

更新2

我可以确认上述关于不受支持的扩展的通知也会在成功握手时出现,因此这是一个错误的痕迹。

经过几天的反复试验,我终于找到了错误!

在Java 7中,自定义密钥管理器应该扩展X509ExtendedKeyManager ,这会强制您实现接口X509KeyManager五种方法。 但是,类X509ExtendedKeyManager还有两个方法,它们未声明为abstract,但必须覆盖才能正确使用:

  • chooseEngineClientAlias(String[], Principal[], SSLEngine)
  • chooseEngineServerAlias(String, Principal[], SSLEngine)

在通过将调用委托给我的originalKeyManager (也是X509ExtendedKeyManager类型)来覆盖和实现方法之后,SSL握手终于成功了。

您似乎没有在代码段中的任何位置读取密钥文件。 这就是SSL_NULL_WITH_NULL_NULL的原因。 我建议你实现X509KeyManager并在构造函数中读取文件,这样可以用letter来选择合适的密钥。 这一行(不是为了简短回答而描述的所有必需方法):

 public class CustomX509KeyManager implements X509KeyManager { private final KeyStore keyStore; private final String alias; private final char[] password; public CustomX509KeyManager(final String keyStoreFile, final char[] password, final String alias) throws IOException, GeneralSecurityException { this.alias = alias; this.password = password; synchronized(keyStoreFile) { InputStream stream = new FileInputStream(keyStoreFile); keyStore = KeyStore.getInstance(KeyStore.getDefaultType()); keyStore.load(stream, password); stream.close(); } } @Override public PrivateKey getPrivateKey(String alias) { try { return (PrivateKey) keyStore.getKey(alias, password); } catch (Exception e) { e.printStackTrace(); return null; } } @Override public X509Certificate[] getCertificateChain(String alias) { try { java.security.cert.Certificate[] certs = keyStore.getCertificateChain(alias); if (certs == null || certs.length == 0) return null; X509Certificate[] x509 = new X509Certificate[certs.length]; for (int i = 0; i < certs.length; i++) x509[i] = (X509Certificate)certs[i]; return x509; } catch (Exception e) { e.printStackTrace(); return null; } } } 

然后像使用它一样

 sslContext.init(new X509KeyManager[] { new CustomX509KeyManager(keyStoreFile, keyStorePass.toCharArray(), alias) }, null, null);