使用自定义 X509KeyManager Java 时无法确定 SSL 握手的匹配密码套件
When using a custom X509KeyManager Java is not able to determine a matching cipher suite for the SSL handshake
我正在使用 Java7 和 JAX-WS 2.2。
对于 SOAP Web 服务,我需要创建自定义 X509KeyManager
以便在 JKS 密钥库中为每个连接的客户端找到正确的证书。
但是,我已经在努力获取我的自定义密钥管理器 运行。到目前为止,我使用的是默认证书(从初始化的 KeyManagerFactory
中检索)并且它基本上可以工作 - 但当然它不是 select 正确的证书。所以第一个想法是创建一个自定义 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()
方法中的另一个 class 初始化的:
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 协议时,附加错误消息
Unsupported extension type_35, data:
出现。
更新 2
我可以确认上述关于不受支持的扩展的通知也出现在成功的握手后,所以这是一个错误的痕迹。
您似乎没有读取代码段中的任何地方的密钥文件。这就是SSL_NULL_WITH_NULL_NULL的原因。我建议你实现 X509KeyManager
并在构造函数中读取文件,因此它可以用于 select 适当键的字母。沿着这条线(为了简短的回答,没有描述所有必需的方法):
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);
经过几天的反复试验,我终于找到了我的错误!
在 Java 7 中,自定义密钥管理器应扩展 X509ExtendedKeyManager,这会强制您实现接口 X509KeyManager
的五个方法。但是,class X509ExtendedKeyManager
中还有两个额外的方法没有声明为抽象的,但是 必须 被覆盖以正确使用:
chooseEngineClientAlias(String[], Principal[], SSLEngine)
chooseEngineServerAlias(String, Principal[], SSLEngine)
通过将调用委托给我的 originalKeyManager
(也变成了 X509ExtendedKeyManager
类型)覆盖并实现方法后,SSL 握手终于成功了。
我正在使用 Java7 和 JAX-WS 2.2。
对于 SOAP Web 服务,我需要创建自定义 X509KeyManager
以便在 JKS 密钥库中为每个连接的客户端找到正确的证书。
但是,我已经在努力获取我的自定义密钥管理器 运行。到目前为止,我使用的是默认证书(从初始化的 KeyManagerFactory
中检索)并且它基本上可以工作 - 但当然它不是 select 正确的证书。所以第一个想法是创建一个自定义 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()
方法中的另一个 class 初始化的:
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 协议时,附加错误消息
Unsupported extension type_35, data:
出现。
更新 2
我可以确认上述关于不受支持的扩展的通知也出现在成功的握手后,所以这是一个错误的痕迹。
您似乎没有读取代码段中的任何地方的密钥文件。这就是SSL_NULL_WITH_NULL_NULL的原因。我建议你实现 X509KeyManager
并在构造函数中读取文件,因此它可以用于 select 适当键的字母。沿着这条线(为了简短的回答,没有描述所有必需的方法):
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);
经过几天的反复试验,我终于找到了我的错误!
在 Java 7 中,自定义密钥管理器应扩展 X509ExtendedKeyManager,这会强制您实现接口 X509KeyManager
的五个方法。但是,class X509ExtendedKeyManager
中还有两个额外的方法没有声明为抽象的,但是 必须 被覆盖以正确使用:
chooseEngineClientAlias(String[], Principal[], SSLEngine)
chooseEngineServerAlias(String, Principal[], SSLEngine)
通过将调用委托给我的 originalKeyManager
(也变成了 X509ExtendedKeyManager
类型)覆盖并实现方法后,SSL 握手终于成功了。