当前位置: 首页 > 知识库问答 >
问题:

在运行时导入自签名证书的SSL客户端-服务器在握手时失败

杜起运
2023-03-14

编辑:我修复了代码。请参阅我的答案进行解释。

我正在尝试创建一个使用SSLSocket将数据发送到服务器的android应用程序。证书是自签名的,我正在尝试使用SSLContext将其添加到服务器KeyManager和客户端TrustManager中,按照我在其他线程上找到的说明。

但是,当我尝试发送数据时,客户端和服务器都返回异常。

请记住,这是我第一次处理SSL连接。我创建并签署了'服务器。使用Java keytool的crt证书。

客户端异常

javax.net.ssl.SSLHandshakeException: Handshake failed
at com.android.org.conscrypt.OpenSSLSocketImpl.startHandshake(OpenSSLSocketImpl.java:390)
at com.android.org.conscrypt.OpenSSLSocketImpl.waitForHandshake(OpenSSLSocketImpl.java:623)
at com.android.org.conscrypt.OpenSSLSocketImpl.getOutputStream(OpenSSLSocketImpl.java:609)
...
Caused by: javax.net.ssl.SSLProtocolException: SSL handshake aborted: ssl=0x73c6ac00: Failure in SSL library, usually a protocol error

服务器异常

Exception in thread "main" javax.net.ssl.SSLHandshakeException: no cipher suites in common
...
at java.io.InputStream.read(InputStream.java:101)

固定客户端代码(在android上运行)

AssetManager assetManager = getAssets();
InputStream keyStoreInputStream = assetManager.open("keystore.bks");

KeyStore keyStore = KeyStore.getInstance("BKS");
keyStore.load(null,null);
keyStore.load(keyStoreInputStream, KEY_PASSWORD);

TrustManagerFactory trustManagerFactory =
    TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init(keyStore);

SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, trustManagerFactory.getTrustManagers(), null);

SSLSocketFactory sslsocketfactory = sslContext.getSocketFactory();
SSLSocket sslsocket = (SSLSocket) sslsocketfactory.createSocket("192.168.1.212", 4444);

InputStream inputStream = assetManager.open(MainActivity.FILE_NAME);

OutputStream outputStream = sslsocket.getOutputStream();

int count;
byte[] buffer = new byte[12 * 1024];
while ((count = inputStream.read(buffer)) > 0) {
    outputStream.write(buffer, 0, count);
}

固定服务器代码(在windows上运行)

FileInputStream keyStoreInputStream = new FileInputStream(KEYSTORE_PATH);

KeyStore keyStore = KeyStore.getInstance("JKS");
keyStore.load(null, null);
keyStore.load(keyStoreInputStream, KEY_PASSWORD);

KeyManagerFactory keyManagerFactory =
    KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
keyManagerFactory.init(keyStore, KEY_PASSWORD);

SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(keyManagerFactory.getKeyManagers(), null, null);

SSLServerSocketFactory sslServerSocketFactory = sslContext.getServerSocketFactory();
SSLServerSocket sslServerSocket =
    (SSLServerSocket) sslServerSocketFactory.createServerSocket(4444);

while (true) {
    SSLSocket sslSocket = (SSLSocket) sslServerSocket.accept();

    InputStream inputStream = sslSocket.getInputStream();

    FileOutputStream outputStream = new FileOutputStream(OUTPUT_PATH,false);

    byte[] bytes = new byte[12 * 1024];
    int count;
    while ((count = inputStream.read(bytes)) > 0) {
        outputStream.write(bytes, 0, count);
    }
}

共有3个答案

方波娃
2023-03-14

出于测试目的,我需要启动HTTPS服务器。由于证书并不重要,我需要创建一个证书。既然我们可以在java中完成这一切,为什么要离开JVM而使用keytool呢。

这需要BouncyCastle。

static SSLContext createSSLContext() throws Exception {
    final KeyStore ks = createSelfSignedKeyStore();
    final KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
    kmf.init(ks, new char[0]);

    final TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
    tmf.init(ks);

    final SSLContext sslContext = SSLContext.getInstance("TLS");
    sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
    return sslContext;
}

private static KeyStore createSelfSignedKeyStore() throws Exception {
    final KeyStore ks = KeyStore.getInstance("JKS");
    ks.load(null);

    final KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
    keyGen.initialize(1024);
    final KeyPair keyPair = keyGen.generateKeyPair();
    final Certificate selfSignedCert = generate(InetAddress.getLocalHost().getCanonicalHostName(), keyPair);

    ks.setCertificateEntry("alias.cert", selfSignedCert);
    ks.setKeyEntry("alias.key", keyPair.getPrivate(), new char[0], new Certificate[] { selfSignedCert });
    return ks;
}

private static Certificate generate(final String fqdn, final KeyPair keypair) throws Exception {
    final Instant now = Instant.now();
    final Date notBefore = Date.from(now.minus(24, ChronoUnit.HOURS));
    final Date notAfter = Date.from(now.plus(24, ChronoUnit.HOURS));
    final ContentSigner selfSigner = new JcaContentSignerBuilder("SHA256WithRSAEncryption")
        .build(keypair.getPrivate());
    final X500Name owner = new X500Name("CN=" + fqdn);
    final X509CertificateHolder certHolder = new JcaX509v3CertificateBuilder(
        /*issuer*/owner,
        /*serial*/new BigInteger(64, new SecureRandom()),
        /*cert start*/notBefore,
        /*cert end*/notAfter,
        /*subject*/owner,
        /*subject public key*/keypair.getPublic())
        .build(selfSigner);
    final X509Certificate selfSignedCert = new JcaX509CertificateConverter()
        .getCertificate(certHolder);
    selfSignedCert.verify(keypair.getPublic()); // Certificate is good!
    return selfSignedCert;
}
程俊誉
2023-03-14

我认为关键信息是服务器端异常:线程“main”javax.net.ssl.SSLHandshakeException中的异常:没有共同的密码套件。您的自签名证书可能很好;看起来您在密码配置中不匹配。您需要确保客户端和服务器都有共同的密码套件。Android中SSLSocket默认支持的套件取决于版本。请参阅Android 5.0的发行说明

裴昕
2023-03-14

似乎问题出在我使用的证书上。我试图导入“*”。使用keytool创建的crt文件,然后以编程方式将其添加到密钥库。尽管我仍然不确定它之前为什么不工作,但我通过跳过证书导入来修复了这个问题,并直接导入了密钥库(“windows server上的keystore.jks”和android客户端上的keystore.bks”)。

这就是我创建密钥库的方式。jks:

keytool -genkeypair -alias serveralias -keyalg RSA -keysize 4096 -keystore keystore.jks 
        -keypass changeit -storepass changeit -validity 10950

由于JKS在android上不受支持,我复制了密钥库并将扩展重命名为。bks在新的上。然后我使用了一个名为“密钥库浏览器”的程序来转换密钥库。bks到“bks-V1”格式,因此可以在android上运行。

我更新了原始问题以包含工作代码。

 类似资料:
  • 问题内容: 我正在连接到以前成功使用过的Web服务,但是现在他们已经更改了主机名并向我发送了两个.pem文件。一个是CA,另一个是我的新客户证书。 (我将Java 1.5,Spring + Spring Web Services与Apache httpclient一起使用,但是我怀疑我的问题是证书,密钥和SSL本身。) 我已经导入了.pem文件以及从Firefox导出到cacerts中的主机的.c

  • 问题内容: 它看起来像一个标准问题,但是我在任何地方都找不到清晰的方向。 我有Java代码试图连接到可能带有自签名(或过期)证书的服务器。该代码报告以下错误: 据我了解,我必须使用keytool并告诉java允许这种连接是可以的。 解决此问题的所有说明均假定我完全精通keytool,例如 生成服务器的私钥并将其导入密钥库 是否有人可以发布详细说明? 我正在运行Unix,所以bash脚本是最好的。

  • 我有一个使用OpenSSL的C客户机,在服务器上的SSL_do_handshake()调用期间,使用服务器端验证失败的证书时,该客户机的测试失败。当应用程序使用TLS 1.2时,服务器上的SSL_do_handshake()故障将在客户端调用SSL_do_handshake()作为故障返回值时报告给客户端。 在将我的应用程序升级到OpenSSL 1.1.1和TLS 1.3时,我注意到验证错误仍在服

  • 问题内容: 我完全被困在这里。我有一个Java客户端代码,需要使用自签名证书连接到SSL服务器。 仅 当我在服务器端禁用SSLv2支持时, 才会 出现此问题。 痕迹是 在服务器端,我可以看到以下跟踪: 如果启用SSL2,我会看到 而且一切正常。 我还知道那不是服务器端的东西,因为与其他软件连接可以正常工作。 知道我在做什么错吗? 还有人知道这个“读取客户端问候A / B”是什么意思吗? 谢谢 更新

  • 我不知道我到底需要在哪里包含客户机证书。现在,我的第一个问题是我不信任服务器。我尝试使用默认的Java密钥库文件(cacerts),其中包含Thawte和Digicert,这些是我试图与之通信的服务器的根权限。我使用

  • 我有一个客户端-服务器应用程序(Android客户端、Apache Http服务器),通过相互身份验证(TLS 1.2)进行通信。问题是:有时连接(登录)会因SSL错误而失败。 这是有效的: 注册客户端证书 登录 这是行不通的: 注册客户证书 注意:在步骤4之后杀死应用程序,然后启动它并执行步骤5工作。 我能想到的可能解释是: 正在重用一些旧资源(如旧客户端证书)。看起来所有相关的东西(OkHtt