Restlet javax.net.ssl.SSLHandshakeException:null证书链

问题描述:

我正在测试本地客户端和服务器之间的SSL通信。 所以我使用OpenSSL命令生成证书。在cacert文件中添加了此证书。还生成.p12文件。Restlet javax.net.ssl.SSLHandshakeException:null证书链

我在服务器和客户端使用相同的.p12文件。 这是服务器代码

Server server = component.getServers().add(Protocol.HTTPS, port); 
Series<Parameter> params = server.getContext().getParameters(); 

params.add("keystorePath", ".p12 file path"); 
params.add("keystoreType", "PKCS12"); 
params.add("needClientAuthentication","true"); 

component.getDefaultHost().attach("", "/AA"), new AAClass()); 
component.start(); 

这是客户端代码:

Client client = trustAllCerts(); 
clientResource = new ClientResource(url); 
clientResource.setNext(client); 
try{ 
     clientText = clientResource.post""); 
} 
catch(ResourceException e){ 
    e.printStackTrace(); 
} 

public Client trustAllCerts() { 
    Client client = null; 
    try { 
     client = new Client(new Context(), Protocol.HTTPS); 
     Context context = client.getContext(); 


     final SSLContext sslContext = SSLContext.getInstance("TLSv1.2"); 
     context.getAttributes().put("sslContextFactory", new SslContextFactory() { 
      public void init(Series<Parameter> parameters) { 

      } 

      public SSLContext createSslContext() { 
       return sslContext; 
      } 
     }); 
     TrustManager tm = new X509TrustManager() { 
      public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { 
      } 

      public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { 
      } 

      public X509Certificate[] getAcceptedIssuers() { 
       return null; 
      } 
     }; 
     context.getAttributes().put("hostnameVerifier", new HostnameVerifier() {     
      @Override 
      public boolean verify(String arg0, SSLSession arg1) { 
       return true; 
      } 

     });   

     sslContext.init(null, new TrustManager[] { tm }, null);   

    } catch (KeyManagementException e) { 
     LOGGER.error("Exception in Key Management" + e); 
    } catch (NoSuchAlgorithmException e) { 
     LOGGER.error("Exception in Algorithm Used" + e); 
    } 
    return client; 
} 

我得到以下异常:

Restlet-1299242, fatal error: 42: null cert chain 
javax.net.ssl.SSLHandshakeException: null cert chain 
%% Invalidated: [Session-25, TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256] 
Restlet-1299242, SEND TLSv1.2 ALERT: fatal, description = bad_certificate 
Restlet-1299242, WRITE: TLSv1.2 Alert, length = 2 
Restlet-1299242, fatal: engine already closed. Rethrowing javax.net.ssl.SSLHandshakeException: null cert chain 
Restlet-1299242, called closeInbound() 
Restlet-1299242, fatal: engine already closed. Rethrowing javax.net.ssl.SSLException: Inbound closed before receiving peer's close_notify: possible truncation attack? 
Restlet-1299242, called closeOutbound() 
Restlet-1299242, closeOutboundInternal() 

我尝试添加使用System.setProperty密钥库和信任()但它没有工作。

请帮忙。提前致谢。

+0

如何创建自签名证书?你可以发布你的步骤?当你得到这个异常时(服务器启动时,客户端启动时,客户端向服务器发出请求等)?你有没有试图连接其他休息客户端,如浏览器?如果您的证书已在服务器上准备就绪,并且您通过浏览器发出GET请求,则必须收到有关证书提供者的警告。 – bhdrkn

+0

其实我从一个证书提供者那里下载证书(上面的例子是为了本地复制问题),在下载包中我得到一个根证书颁发机构证书,一个中间机构证书,一个证书和一个密钥。我尝试了所有的组合,但仍然获得空证书链异常。当客户端能够与服务器进行通信时,我也会遇到异常。同样在浏览器中,我看到了有关不可信证书的警告,因此我在浏览器中添加了证书,但仍然得到空证书链问题。 – vikas27

首先,让我们创建一个JKS格式的密钥库。 PKCS12通常用于浏览器,默认的Java应用程序使用JKS(据我所知)。 Java也支持PKCS12,但我不知道它的确切参数。

准备JKS文件

让我们看看在我们的PKCS12文件,并得到我们想要提取我们的JKS文件中的证书别名。

keytool -list \ 
     -keystore [*.p12 file] \ 
     -storepass [password] \ 
     -storetype PKCS12 \ 
     -v 

请注意您要导出的别名。现在让我们创建一个JKS文件。

keytool -keystore [*.jks file path] -genkey -alias client 

这会问一堆问题。你可以随心所欲填充它们。现在,您可以将您的别名从* .p12文件导出到* .jks文件。

keytool -importkeystore \ 
     -srckeystore [*.p12 file path] \ 
     -srcstoretype pkcs12 \ 
     -srcalias [alias from first command] \ 
     -destkeystore [*.jks file path] \ 
     -deststoretype jks \ 
     -deststorepass [*.jks file password] \ 
     -destalias [new alias] 

如果你没有任何PKCS12文件,或者你的证书是在CER,DER或PEM格式,你可以使用下面的命令添加证书密钥库。

keytool -import \ 
     -alias [new alias] \ 
     -keystore [*.jks file path] \ 
     -file [*.DER file path] 

请确保您导入了证书,证书提供者的证书(中间证书)和根证书。

现在您可以检查您的JKS文件是否包含您需要的所有证书。

keytool -list \ 
     -keystore [*.jks file path] \ 
     -storepass [password] \ 
     -storetype jks \ 
     -v 

设置服务器

您可以使用JKS文件都在客户端和服务器端。根据Restlet documentation你可以使用这样的JKS文件来提供HTTPS连接。

Server server = component.getServers().add(Protocol.HTTPS, port); 
Series<Parameter> parameters = server.getContext().getParameters(); 
parameters.add("sslContextFactory","org.restlet.engine.ssl.DefaultSslContextFactory"); 
parameters.add("keyStorePath", "*.jks file"); 
parameters.add("keyStorePassword", "password"); 
parameters.add("keyPassword", "password"); 
parameters.add("keyStoreType", "JKS"); 

之后,如果您从浏览器检查您的端口,您必须看到一个安全标志。或者你可以使用一些在线工具(like this one)来检查你的证书。

设置客户端

现在让我们看一下客户端。既然你正在开发应用程序的两面,你可以使用已经创建的JKS文件。

Context con = new Context(); 
Series<Parameter> clParameters = con.getParameters(); 
clParameters.add("truststorePath", "*.jks file"); 
clParameters.add("truststorePassword", "password"); 
clParameters.add("truststoreType", "JKS"); 
Client restletClient = new Client(con, Protocol.HTTPS); 

在测试或其他情况下,您的证书主机名和您的实际主机名可能不匹配。为了禁用主机名检查,您可以将此块添加到您的应用程序中。

static{ 
    javax.net.ssl.HttpsURLConnection.setDefaultHostnameVerifier(
    new javax.net.ssl.HostnameVerifier(){ 

     public boolean verify(String hostname, 
       javax.net.ssl.SSLSession sslSession) { 
      return true ; 
     } 
    }); 
} 

的几点思考

因为我无法测试它在我的区域,我不是很确定,你的客户端和服务器JKS文件必须是相同的。您可能只需要将自己的证书添加到server.jks中。 SSL和证书对我来说总是很棘手。我经常在经过一些试验和错误后才能使用它。我希望这能帮到您。

另外,您也可以考虑使用类似Apache2或Nginx的反向代理服务器。如果要使用它们,则必须将证书合并到单个文件中。如果你看看你的证书文件,你看到的每个文件(自己的证书,中间证书和根证书)就是这样

-----BEGIN CERTIFICATE----- 
MIIDfTCCAuagAwIBAgIDErvmMA0GCSqGSIb3DQEBBQUA... 
.... 
-----END CERTIFICATE----- 

您需要简单地添加一个到其他创建合并证书。而不是使用该证书在Apache2或Nginx上结束SSL。这是我通常做的。但在客户端,您仍然需要创建JKS文件。

+0

我尝试了你的建议,但仍然得到相同的例外。 – vikas27

+0

@vikasTheJavaDeveloper您是否使用自定义'SSLContextFactory'和'Trustmanager'的客户端版本或使用我的版本?你使用真正的证书链还是使用自签名证书?您可以尝试使用'-Djavax.net.debug = all'标志运行这两个应用程序,以查看证书请求吗?你可以发布日志的地方(也许要点)并添加链接到你的问题? – bhdrkn

一种方法是读取p12/pfx文件,获取证书并使用它们以编程方式构造KeyStore和TrustStore。 如果输入是一个包含CA根证书和相关客户端证书的pfx文件, 下面的类SslUtils中显示的方法将允许您执行此操作。
但有一点需要注意:默认的Restlet服务器(版本2.3.4)不会提取客户端发送的证书。 我确实设法解决了这个问题(虽然这不是很好),请参阅我在this question上的回答。

我将重点介绍如何配置安全连接,但所有源代码和工作示例均可在Github项目的 restlet-clientcert中找到。 Github项目是我认为我知道自己在做什么的结果,没有运气,没有Restlet的经验,但是无论如何咬着子弹,所以我可以感觉更好一点,知道我可以得到这些基本的东西工作。

在服务器端,使用以编程方式配置使用的SSLContext的自定义ServerSslContextFactory。 注册与自定义工厂:

ServerSslContextFactory sslCtx = new ServerSslContextFactory(); 
sslCtx.init(certFileName, certFilePwd); 
ConcurrentMap<String, Object> attribs = server.getContext().getAttributes(); 
attribs.put("sslContextFactory", sslCtx); 

,重视 “保护” 中提取客户端证书信息:

CertificateAuthenticator guard = new CertificateAuthenticator(server.getContext()); 
guard.setNext(MyRestlet.class); 
component.getDefaultHost().attachDefault(guard); 

ServerSslContextFactory

public class ServerSslContextFactory extends DefaultSslContextFactory { 

    private static final Logger log = LoggerFactory.getLogger(ServerSslContextFactory.class); 

    protected DefaultSslContext wrappedCtx; 

    public void init(String certFileName, char[] certFilePwd) throws Exception { 

     if (log.isDebugEnabled()) { 
      log.debug("Loading certificates from [" + certFileName + "] and using " 
        + (certFilePwd != null && certFilePwd.length > 0 ? "a" : "no") + " password."); 
     } 
     Path certFilePath = Paths.get(Thread.currentThread().getContextClassLoader().getResource(certFileName).toURI()); 
     KeyManagerFactory kmf = SslUtils.loadKeyStore(certFilePath, certFilePwd); 
     KeyManager[] kms = kmf.getKeyManagers(); 
     List<X509Certificate> certs = SslUtils.getClientCaCerts(kms); 
     TrustManagerFactory tmf = SslUtils.createTrustStore(Constants.CERT_CA_ALIAS, certs.get(0)); 
     TrustManager[] tms = tmf.getTrustManagers(); 

     super.setNeedClientAuthentication(true); 

     SSLContext ctx = SSLContext.getInstance(SslUtils.DEFAULT_SSL_PROTOCOL); 
     ctx.init(kms, tms, null); 
     wrappedCtx = (DefaultSslContext) createWrapper(ctx); 
    } 

    @Override 
    public void init(Series<Parameter> parameters) { 
     log.debug("Not using parameters to initialize server SSL Context factory."); 
    } 

    @Override 
    public SSLContext createSslContext() throws Exception { 
     return wrappedCtx; 
    } 

    @Override 
    public boolean isNeedClientAuthentication() { 

     if (log.isDebugEnabled()) { 
      //log.debug("Needing client auth: " + super.isNeedClientAuthentication(), new RuntimeException("trace")); 
      log.debug("Needing client auth: " + super.isNeedClientAuthentication()); 
     } 
     return super.isNeedClientAuthentication(); 
    } 

} 

在客户端,类似的东西:

ClientSslContextFactory sslCtx = new ClientSslContextFactory(); 
sslCtx.init(certFileName, certFilePwd); 
attribs.put("sslContextFactory", sslCtx); 

还设置一个hostnameVerifier(如您的问题所示)不验证主机名。
ClientSslContextFactory

public class ClientSslContextFactory extends SslContextFactory { 

    private static final Logger log = LoggerFactory.getLogger(ClientSslContextFactory.class); 

    protected KeyManager[] kms; 
    protected TrustManager[] tms; 

    public void init(String certFileName, char[] certFilePwd) throws Exception { 

     log.debug("Loading certificates from [" + certFileName + "] and using " 
       + (certFilePwd != null && certFilePwd.length > 0 ? "a" : "no") + " password."); 
     Path certFilePath = Paths.get(Thread.currentThread().getContextClassLoader().getResource(certFileName).toURI()); 
     KeyManagerFactory kmf = SslUtils.loadKeyStore(certFilePath, certFilePwd); 
     kms = kmf.getKeyManagers(); 
     /* 
     List<X509Certificate> certs = SslUtils.getClientCaCerts(kms); 
     TrustManagerFactory tmf = SslUtils.createTrustStore(Constants.CERT_CA_ALIAS, certs.get(0)); 
     tms = tmf.getTrustManagers(); 
     */ 
     tms = new TrustManager[1]; 
     tms[0] = new TrustServerCertAlways(); 
    } 

    @Override 
    public void init(Series<Parameter> parameters) { 
     log.debug("Not using parameters to initialize client SSL Context factory."); 
    } 

    @Override 
    public SSLContext createSslContext() throws Exception { 

     SSLContext ctx = SSLContext.getInstance(SslUtils.DEFAULT_SSL_PROTOCOL); 
     ctx.init(kms, tms, null); 
     return ctx; 
    } 

    static class TrustServerCertAlways implements X509TrustManager { 

     @Override public void checkClientTrusted(X509Certificate[] arg0, String arg1) throws CertificateException { 
      log.debug("Trusting all client certificates."); 
     } 

     @Override public void checkServerTrusted(X509Certificate[] arg0, String arg1) throws CertificateException { 
      log.debug("Trusting all server certificates."); 
     } 

     @Override public X509Certificate[] getAcceptedIssuers() { 
      log.debug("No accepted issuers."); 
      return null; 
     } 
    } 

} 

最后包含“阅读和重建”的方法 的SslUtils类(完整版,包括“从证书获取电子邮件地址”的方法可在前面提到的Github的项目):

import java.io.InputStream; 
import java.net.Authenticator; 
import java.net.PasswordAuthentication; 
import java.nio.file.Files; 
import java.nio.file.Path; 
import java.security.KeyStore; 
import java.security.KeyStore.LoadStoreParameter; 
import java.security.cert.X509Certificate; 
import java.util.*; 

import javax.net.ssl.*; 

import org.slf4j.Logger; 
import org.slf4j.LoggerFactory; 

public class SslUtils { 

    private static final Logger log = LoggerFactory.getLogger(SslUtils.class); 

    /** 
    * List of SSL protocols (SSLv3, TLSv1.2, etc.). See also {@link SslUtils#DEFAULT_SSL_PROTOCOL}. 
    * <br>Documented at http://docs.oracle.com/javase/7/docs/technotes/guides/security/StandardNames.html#SSLContext 
    */ 
    public static final String[] SSL_PROTOCOLS = new String[] { "SSL", "SSLv2", "SSLv3", "TLS", "TLSv1", "TLSv1.1", "TLSv1.2" }; 

    /** 
    * Default SSL protocol to use ("TLSv1.2"). 
    */ 
    public static final String DEFAULT_SSL_PROTOCOL = "TLSv1.2"; 

    /** 
    * Creates a default SSL context with an empty key-store and the default JRE trust-store. 
    */ 
    public static SSLContext createDefaultSslContext() throws Exception { 
     return createSslContext(null, null, null, null); 
    } 
    /** 
    * Creates a default SSL socket factory. 
    * <br>All system properties related to trust/key-stores are ignored, eveything is done programmatically. 
    * This is because the Sun implementation reads the system-properties once and then caches the values. 
    * Among other things, this fails the unit tests. 
    * <br>For reference, the system properties (again, NOT USED): 
    * <br> - javax.net.ssl.trustStore (default cacerts.jks) 
    * <br> - javax.net.ssl.trustStorePassword 
    * <br>and for client certificate: 
    * <br> - javax.net.ssl.keyStore (set to "agent-cert.p12") 
    * <br> - javax.net.ssl.keyStoreType (set to "pkcs12") 
    * <br> - javax.net.ssl.keyStorePassword 
    * <br>See for a discussion: 
    * https://*.com/questions/6340918/trust-store-vs-key-store-creating-with-keytool 
    * <br>See for client certificates in Java: 
    * https://*.com/questions/1666052/java-https-client-certificate-authentication 
    * @param keyStoreFileName The name (ending with pfx) of the file with client certificates. 
    * @param trustStoreFileName The name (ending with jks) of the Java KeyStore with trusted (root) certificates. 
    * @return null or the SSLContext. 
    */ 
    public static SSLContext createSslContext(Path keyStoreFile, String keyStorePwd, 
      Path trustStoreFile, String trustStorePwd) throws Exception { 
     return createSslContext(keyStoreFile, keyStorePwd, trustStoreFile, trustStorePwd, DEFAULT_SSL_PROTOCOL); 
    } 

    /** 
    * See {@link #createSslContext(Path, String, Path, String)}. 
    * @param sslProtocol a value from {@link #SSL_PROTOCOLS}. 
    */ 
    public static SSLContext createSslContext(Path keyStoreFile, String keyStorePwd, 
      Path trustStoreFile, String trustStorePwd, String sslProtocol) throws Exception { 

     KeyManagerFactory kmf = loadKeyStore(keyStoreFile, keyStorePwd == null ? null : keyStorePwd.toCharArray()); 
     TrustManagerFactory tmf = loadTrustStore(trustStoreFile, trustStorePwd == null ? null : trustStorePwd.toCharArray()); 
     //set an Authenticator to generate username and password 
     SSLContext ctx = SSLContext.getInstance(sslProtocol); 
     ctx.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null); 
     return ctx; 
    } 

    /** 
    * Calls {@link #createSslContextFromClientKeyStore(Path, String, Path, String)} with the {@link #DEFAULT_SSL_PROTOCOL}. 
    */ 
    public static SSLContext createSslContextFromClientKeyStore(Path keyStoreFile, String keyStorePwd, 
      String caAlias) throws Exception { 
     return createSslContextFromClientKeyStore(keyStoreFile, keyStorePwd, caAlias, DEFAULT_SSL_PROTOCOL); 
    } 

    /** 
    * Creates a SSL context from the given key-store containing a client certificate and a (CA) root certificate. 
    * The root certificate is set in the trust-store of the SSL context. 
    * @param keyStoreFileName key-store file name (ending with .pfx). 
    * @param keyStorePwd key-store password 
    * @param caAlias the alias to use for the CA (root) certificate (e.g. "mycaroot"). 
    * @param sslProtocol the ssl-protocol (e.g. {@link #DEFAULT_SSL_PROTOCOL}). 
    */ 
    public static SSLContext createSslContextFromClientKeyStore(Path keyStoreFile, String keyStorePwd, 
      String caAlias, String sslProtocol) throws Exception { 

     KeyManagerFactory kmf = loadKeyStore(keyStoreFile, keyStorePwd == null ? null : keyStorePwd.toCharArray()); 
     List<X509Certificate> certs = getClientCaCerts(kmf.getKeyManagers()); 
     if (certs.size() < 1) { 
      throw new Exception("Cannot find CA (root) certificate in key-managers from key store " + keyStoreFile.getFileName()); 
     } 
     TrustManagerFactory tmf = createTrustStore(caAlias, certs.get(0)); 
     SSLContext ctx = SSLContext.getInstance(sslProtocol); 
     ctx.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null); 
     return ctx; 
    } 

    public static KeyManagerFactory loadKeyStore(Path storeFile) throws Exception { 
     return loadKeyStore(storeFile, null); 
    } 

    public static KeyManagerFactory loadKeyStore(Path storeFile, char[] storePwd) throws Exception { 
     return loadKeyStore(storeFile, storePwd, null, null); 
    } 

    public static KeyManagerFactory loadKeyStore(Path storeFile, char[] storePwd, 
      String storeType, String algorithm) throws Exception { 

     KeyManagerFactory kmf = null; 
     if (storeFile == null) { 
      kmf = loadKeyStore((InputStream)null, storePwd, storeType, algorithm); 
     } else { 
      try (InputStream storeIn = Files.newInputStream(storeFile)) { 
       kmf = loadKeyStore(storeIn, storePwd, storeType, algorithm); 
       log.info("Initialized certificate key-store from [" + storeFile.getFileName() + "]"); 
      } 
     } 
     return kmf; 
    } 

    public static KeyManagerFactory loadKeyStore(InputStream storeIn, char[] storePwd, 
      String storeType, String algorithm) throws Exception { 

     if (storePwd == null && storeIn != null) { 
      storePwd = "changeit".toCharArray(); 
      log.debug("Using default key store password."); 
     } 
     if (storeType == null) { 
      storeType = "pkcs12"; 
      log.debug("Using default key store type " + storeType); 
     } 
     if (algorithm == null) { 
      algorithm = KeyManagerFactory.getDefaultAlgorithm(); // "SunX509" 
      log.debug("Using default key store algorithm " + algorithm); 
     } 
     KeyManagerFactory kmf = null; 
     KeyStore keyStore = loadStore(storeIn, storePwd, storeType); 
     kmf = KeyManagerFactory.getInstance(algorithm); 
     kmf.init(keyStore, storePwd); 
     if (storeIn == null) { 
      log.info("Initialized a default certificate key-store"); 
     } 
     return kmf; 
    } 

    /** 
    * Creates a trust-store with the given CA (root) certificate. 
    * @param certAlias the alias for the certificate (e.g. "mycaroot") 
    * @param caCert the CA (root) certificate 
    * @return an initialized trust manager factory. 
    */ 
    public static TrustManagerFactory createTrustStore(String certAlias, X509Certificate caCert) throws Exception { 

     KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType()); 
     ks.load((LoadStoreParameter)null); // must initialize the key-store 
     ks.setCertificateEntry(certAlias, caCert); 
     TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); 
     tmf.init(ks); 
     return tmf; 
    } 

    public static TrustManagerFactory loadTrustStore(Path storeFile) throws Exception { 
     return loadTrustStore(storeFile, null); 
    } 

    public static TrustManagerFactory loadTrustStore(Path storeFile, char[] storePwd) throws Exception { 
     return loadTrustStore(storeFile, storePwd, null, null); 
    } 

    public static TrustManagerFactory loadTrustStore(Path storeFile, char[] storePwd, 
      String storeType, String algorithm) throws Exception { 

     TrustManagerFactory tmf = null; 
     if (storeFile == null) { 
      tmf = loadTrustStore((InputStream)null, storePwd, storeType, algorithm); 
     } else { 
      try (InputStream storeIn = Files.newInputStream(storeFile)) { 
       tmf = loadTrustStore(storeIn, storePwd, storeType, algorithm); 
      } 
      log.info("Initialized certificate trust-store from [" + storeFile.getFileName() + "]"); 
     } 
     return tmf; 
    } 

    public static TrustManagerFactory loadTrustStore(InputStream storeIn, char[] storePwd, 
      String storeType, String algorithm) throws Exception { 

     if (storePwd == null && storeIn != null) { 
      storePwd = "changeit".toCharArray(); 
      log.debug("Using default trust store password."); 
     } 
     if (storeType == null) { 
      storeType = KeyStore.getDefaultType(); 
      log.debug("Using default trust store type " + storeType); 
     } 
     if (algorithm == null) { 
      algorithm = TrustManagerFactory.getDefaultAlgorithm(); 
      log.debug("Using default trust store algorithm " + algorithm); 
     } 
     TrustManagerFactory tmf = null; 
     KeyStore trustStore = loadStore(storeIn, storePwd, storeType); 
     tmf = TrustManagerFactory.getInstance(algorithm); 
     tmf.init(trustStore); 
     if (storeIn == null) { 
      log.info("Initialized a default certificate trust-store"); 
     } 
     return tmf; 
    } 

    /** 
    * Creates a default trust store containing the JRE certificates in {@code JAVA_HOME\lib\security\cacerts.jks} 
    * <br>To view loaded certificates call 
    * <br>{@code System.setProperty("javax.net.debug", "ssl,trustmanager");} 
    * <br>before calling this method. 
    */ 
    public static TrustManagerFactory createDefaultTrustStore() throws Exception { 

     TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); 
     tmf.init((KeyStore)null); 
     return tmf; 
    } 

    /** 
    * @param in if null, null is returned. 
    */ 
    public static KeyStore loadStore(InputStream in, char[] pwd, String type) throws Exception { 

     if (in == null) { 
      return null; 
     } 
     KeyStore ks = KeyStore.getInstance(type); 
     ks.load(in, pwd); 
     return ks; 
    } 

    /** 
    * Finds any CA (root) certificates present in client certificate chains. 
    * <br>Uses {@link #getClientAliases(KeyManager)} 
    * @param kms key-managers (from a key-store). 
    * @return an empty list or a list containing CA (root) certificates. 
    */ 
    public static List<X509Certificate> getClientCaCerts(KeyManager[] kms) { 

     List<X509Certificate> caCerts = new LinkedList<X509Certificate>(); 
     for (int i = 0; i < kms.length; i++) { 
      if (!(kms[i] instanceof X509KeyManager)) { 
       continue; 
      } 
      X509KeyManager km = (X509KeyManager) kms[i]; 
      List<String> aliases = getClientAliases(km); 
      for (String alias: aliases) { 
       X509Certificate[] cchain = km.getCertificateChain(alias); 
       if (cchain == null || cchain.length < 2) { 
        continue; 
       } 
       // first certificate in chain is the user certificate 
       // last certificate is the CA (root certificate). 
       caCerts.add(cchain[cchain.length-1]); 
       if (log.isDebugEnabled()) { 
        log.debug("Found 1 root certificate from client certificate alias " + alias); 
       } 
      } 
     } 
     return caCerts; 
    } 

    /** 
    * List of key types for client certificate aliases, used in {@link #getAliases(KeyManager)} 
    * <br>List is documented at 
    * http://docs.oracle.com/javase/7/docs/technotes/guides/security/StandardNames.html#jssenames 
    */ 
    public static final String[] KEY_TYPES = new String[] {"RSA", "DSA", "DH_RSA", "DH_DSA", "EC", "EC_EC", "EC_RSA" }; 

    /** 
    * Searches for client aliases in the given key-manager. 
    * Does nothing when the given key-manager is not an instance of {@link X509KeyManager}. 
    * @return an empty list or a list containing client aliases found in the key-manager. 
    */ 
    public static List<String> getClientAliases(KeyManager keyManager) { 

     List<String> aliases = new LinkedList<String>(); 
     if (keyManager instanceof X509KeyManager) { 
      X509KeyManager km = (X509KeyManager) keyManager; 
      for (String keyType: KEY_TYPES) { 
       String[] kmAliases = km.getClientAliases(keyType, null); 
       if (kmAliases != null) { 
        for (String alias: kmAliases) { 
         if (!isEmpty(alias)) { 
          aliases.add(alias); 
         } 
        } 
       } 
      } // for keytypes 
     } 
     return aliases; 
    } 

    /** 
    * Sets the default authenticator which can be used for example with http-request that require basic authoriation. 
    * <br>See also {@link Authenticator#setDefault(Authenticator)}. 
    */ 
    public static void setDefaultAuthenticator(final String userName, final char[] pwd) throws Exception { 

     Authenticator auth = new Authenticator() { 
      @Override 
      protected PasswordAuthentication getPasswordAuthentication() { 
       return new PasswordAuthentication(userName, pwd); 
      } 
     }; 
     Authenticator.setDefault(auth); 
    } 

    /** 
    * @return true if s is not null and not empty after trimming, false otherwise. 
    */ 
    public static boolean isEmpty(String s) { return (s == null || s.trim().isEmpty()); } 

} 

在侧面节点上:Java正在将默认密钥库类型从JKS转换为PKCS12(请参阅JEP 229)。

我使用的是相同的.p12文件在服务器和客户端

这已经是一个错误。客户端和服务器是不同的身份,不应该有相同的私钥,公钥或证书。

我建议你沟所有OpenSSL的东西,与keytool重新开始如下:

  1. 在服务器上,生成一个密钥对,以及证书请求;得到它的签名;用-trustcacerts选项导入签名者的证书链;并使用您在创建密钥对和CSR时使用的相同别名导入签名证书
  2. 在客户端,同上,但使用(当然)不同的密钥库文件。
  3. 你完成了。忘掉

    • OpenSSL的
    • PKCS#12
    • 自签名证书
    • 一切形式的trustAllCerts,定制TrustManagers,并使用相同的密钥对/证书的任何类型的自定义代码任何
    • 服务器和客户端
    • 将服务器证书导入客户端,反之亦然
    • 除识别javax.net.ssl.keyStorejavax.net.ssl.keyStorePassword
    • 以外的任何系统属性在密钥对或导入的签名证书上设置密码。

步骤(1)和(2)是它是打算如何来完成。离开这些人,你就会陷入困境和冲突。

+0

我喜欢你的答案,但看起来有点太宽泛。这个问题可以通过更新p12文件来解决,所以从头开始并为新证书支付似乎有点矫枉过正。虽然下一次他想要这样做会为他节省很多麻烦。不幸的是,openssl dance仍然被广泛记载,可能是因为它对非java用户也是有效的。 – greyfairer

+0

@greyfairer在这个问题中没有证据表明他还有什么收入。这个问题的每一个表现都是自签名的证书,直到不安全的'TrustManager'代码,否则就没有意义。但即使他有,至少也要支付一个新证书,因为两端使用同一个证书是无效的,而在Java环境中使用OpenSSL只是一个可以避免的PITA。 – EJP

您可能没有在您的密钥库中添加完整的证书链,只包含密钥对本身。在这种情况下,客户端只接收公钥,但无法验证该密钥是否可信。证书链可以检查公钥上的签名是否匹配,并导致信任的证书颁发机构。

参见例如:Adding certificate chain to p12(pfx) certificate

openssl pkcs12 -in certificate.p12 -out clientcert.pem -nodes -clcerts 
openssl x509 -in trusted_ca.cer -inform DER -out trusted_ca.pem 
openssl x509 -in root_ca.cer -inform DER -out root_ca.pem 
cat clientcert.pem trusted_ca.pem root_ca.pem >> clientcertchain.pem 
openssl pkcs12 -export -in clientcertchain.pem -out clientcertchain.pfx 

你可以做Java的方式也一样,例如使用portecle:http://portecle.sourceforge.net/import-ca-reply.html,但您还需要将证书链合并到一个文件中才能导入。只需复制粘贴所有证书,从自己开始,以根CA结束。

这样,可以在服务器上使用生成的pfx文件将证书链返回给客户端。