Restlet javax.net.ssl.SSLHandshakeException:空证书链
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/32506278/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Restlet javax.net.ssl.SSLHandshakeException: null cert chain
提问by vikas27
I am testing SSL communication between client and server locally. So I generated certificate using OpenSSL commands. Added this certificate in cacert file. Also generated .p12 file.
我正在本地测试客户端和服务器之间的 SSL 通信。所以我使用 OpenSSL 命令生成了证书。在 cacert 文件中添加了此证书。还生成了 .p12 文件。
I am using the same .p12 file in server and client. This is the server code
我在服务器和客户端使用相同的 .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();
And this is client code:
这是客户端代码:
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;
}
I am getting following exception:
我收到以下异常:
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()
I tried to add keystore and truststore using System.setProperty() but it didn't work.
我尝试使用 System.setProperty() 添加密钥库和信任库,但没有成功。
Please help. Thanks in advance.
请帮忙。提前致谢。
采纳答案by bhdrkn
First, lets create a JKS formatted keystore. PKCS12 is usually used in browser and on default java applications uses JKS (as far as I know). Java also supports PKCS12 but I do not know exact parameters for it.
首先,让我们创建一个 JKS 格式的密钥库。PKCS12 通常在浏览器中使用,在默认的 Java 应用程序中使用 JKS(据我所知)。Java 也支持 PKCS12,但我不知道它的确切参数。
Preparing JKS File
准备 JKS 文件
Lets look in our PKCS12 file and get the certificate aliases that we want to extract our JKS file.
让我们查看我们的 PKCS12 文件并获取我们想要提取 JKS 文件的证书别名。
keytool -list \
-keystore [*.p12 file] \
-storepass [password] \
-storetype PKCS12 \
-v
Note the aliases you want to export. And now lets create a JKS file.
请注意要导出的别名。现在让我们创建一个 JKS 文件。
keytool -keystore [*.jks file path] -genkey -alias client
This will ask bunch of questions. You can fill them as you like. Now, you can export your aliases from *.p12 file to *.jks file.
这会问一堆问题。你可以随意填充它们。现在,您可以将别名从 *.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]
If you do not have any PKCS12 file, or your certificates are in CER, DER or PEM format you can add your certificates to your keystore using the command below.
如果您没有任何 PKCS12 文件,或者您的证书是 CER、DER 或 PEM 格式,您可以使用以下命令将您的证书添加到您的密钥库中。
keytool -import \
-alias [new alias] \
-keystore [*.jks file path] \
-file [*.DER file path]
And please be sure that you imported, your certificate, your certificate provider's certificate (intermediate certificate) and root certificate.
并且请确保您导入了您的证书、您的证书提供商的证书(中间证书)和根证书。
Now you can check that your JKS file contains all the certificates you are needed.
现在您可以检查您的 JKS 文件是否包含您需要的所有证书。
keytool -list \
-keystore [*.jks file path] \
-storepass [password] \
-storetype jks \
-v
Setting up Server
设置服务器
You can use your JKS file both on client and server side. According to Restlet documentationyou can use JKS file like this to provide HTTPS connection.
您可以在客户端和服务器端使用 JKS 文件。根据Restlet 文档,您可以像这样使用 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");
After that if you check your port from browser you must see a secure sign. Or you can use some online tool(like this one) to check your certificate.
之后,如果您从浏览器检查您的端口,您必须看到一个安全标志。或者你可以使用一些在线工具(比如这个)来检查你的证书。
Setting up Client
设置客户端
Now lets look at client side. Since you are developing both side of the application you can use already created JKS file.
现在让我们看看客户端。由于您正在开发应用程序的双方,因此您可以使用已创建的 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);
While testing or in other circumstances, your certificate hostname and your actual hostname may not match. In order to disable hostname checks you can add this block to your application.
在测试或其他情况下,您的证书主机名和您的实际主机名可能不匹配。为了禁用主机名检查,您可以将此块添加到您的应用程序中。
static{
javax.net.ssl.HttpsURLConnection.setDefaultHostnameVerifier(
new javax.net.ssl.HostnameVerifier(){
public boolean verify(String hostname,
javax.net.ssl.SSLSession sslSession ) {
return true ;
}
});
}
Some Thoughts
一些想法
Since I cannot test it on my locale, I am not exactly sure that your client and server JKS file must be the same. You may only need to add your own certificate to your server.jks. SSL and certificates are always tricky for me. I usually get it work after some trial and error. I hope this will help you.
由于我无法在我的语言环境中对其进行测试,因此我不确定您的客户端和服务器 JKS 文件是否必须相同。您可能只需要将自己的证书添加到 server.jks。SSL 和证书对我来说总是很棘手。我通常在经过反复试验后才能正常工作。我希望这能帮到您。
Also, You may also want to consider, using a reverse proxy kind of web server like Apache2 or Nginx. If you want to use them, you must merge your certificates to a single file. If you look at your certificate file you see that each file (your own certificate, intermediate certificate and root certificate) is like this
此外,您可能还需要考虑使用反向代理类型的 Web 服务器,例如 Apache2 或 Nginx。如果您想使用它们,您必须将您的证书合并到一个文件中。如果您查看您的证书文件,您会看到每个文件(您自己的证书、中间证书和根证书)都是这样的
-----BEGIN CERTIFICATE-----
MIIDfTCCAuagAwIBAgIDErvmMA0GCSqGSIb3DQEBBQUA...
....
-----END CERTIFICATE-----
You need to simply add one to other to create a merged certificate. And than use that certificate to end SSL on Apache2 or Nginx. This is what I usually do. But on client side you still need to create JKS files.
您只需将一个添加到另一个即可创建合并证书。然后使用该证书在 Apache2 或 Nginx 上结束 SSL。这是我通常做的。但是在客户端,您仍然需要创建 JKS 文件。
回答by vanOekel
One option is to read the p12/pfx-file, get the certificates and use them to programmatically construct KeyStores and TrustStores.
If the input is one pfx-file containing a CA root certificate and a related client certificate,
the methods shown in the class SslUtils
below will let you do that.
There is one caveat though: the default Restlet server (version 2.3.4) will not pickup the certificates send by the client.
I did manage to work-around this issue (it is not pretty though), see my answer on this question.
一种选择是读取 p12/pfx 文件,获取证书并使用它们以编程方式构建 KeyStores 和 TrustStores。如果输入是一个包含 CA 根证书和相关客户端证书的 pfx 文件,则SslUtils
下面类中显示的方法将允许您执行此操作。
但是有一个警告:默认的 Restlet 服务器(版本 2.3.4)不会获取客户端发送的证书。我确实设法解决了这个问题(虽然它并不漂亮),请参阅我对这个问题的回答。
I will focus on configuring the secure connections here, but all source code and a working example is available in the restlet-clientcertGithub project. The Github project is a result of me thinking I know what I'm doing, having no luck and no experience with Restlet, but biting the bullet anyway so I can feel a little bit better knowing that I could get this basic stuff to work.
我将在这里重点配置安全连接,但所有源代码和工作示例都可以在 restlet-clientcertGithub 项目中找到。Github 项目是我认为我知道我在做什么的结果,没有运气,也没有使用 Restlet 的经验,但无论如何我还是咬紧牙关,所以我知道我可以让这些基本的东西工作时感觉好一点。
On the server side, use a custom ServerSslContextFactory
that programmatically configures the used SSLContext
.
Register the custom factory with:
在服务器端,使用以ServerSslContextFactory
编程方式配置使用的SSLContext
. 注册自定义工厂:
ServerSslContextFactory sslCtx = new ServerSslContextFactory();
sslCtx.init(certFileName, certFilePwd);
ConcurrentMap<String, Object> attribs = server.getContext().getAttributes();
attribs.put("sslContextFactory", sslCtx);
and attach a "guard" to extract the client certificate info:
并附加一个“守卫”以提取客户端证书信息:
CertificateAuthenticator guard = new CertificateAuthenticator(server.getContext());
guard.setNext(MyRestlet.class);
component.getDefaultHost().attachDefault(guard);
The ServerSslContextFactory
:
的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();
}
}
On the client side, a similar thing:
在客户端,类似的事情:
ClientSslContextFactory sslCtx = new ClientSslContextFactory();
sslCtx.init(certFileName, certFilePwd);
attribs.put("sslContextFactory", sslCtx);
Also set a hostnameVerifier
(as shown in your question) to not verify hostnames.
The ClientSslContextFactory
:
还设置 a 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;
}
}
}
And finally the SslUtils
class containing the "read and reconstruct" methods
(full version including "get email-address from certificate" methods is available in the previously mentioned Github project):
最后是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://stackoverflow.com/questions/6340918/trust-store-vs-key-store-creating-with-keytool
* <br>See for client certificates in Java:
* https://stackoverflow.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()); }
}
On a side-node: Java is transitioning the default keystore type from JKS to PKCS12 (see JEP 229).
在侧节点上:Java 正在将默认密钥库类型从 JKS 转换为 PKCS12(参见JEP 229)。
回答by user207421
I am using the same .p12 file in server and client
我在服务器和客户端使用相同的 .p12 文件
This is already a mistake. The client and the server are different identities and should not have the same private key, public key, or certificate.
这已经是一个错误。客户端和服务器是不同的身份,不应具有相同的私钥、公钥或证书。
I suggest you ditch all the OpenSSL stuff and start again with the keytool
as follows:
我建议你放弃所有 OpenSSL 的东西,然后重新开始keytool
,如下所示:
- At the server,generate a keypair, and a certificate request; get it signed; import the signer's certificate chain with the
-trustcacerts
option; and import the signed certificate using the same alias you used when creating the keypair and CSR. - At the client, ditto, but using (of course) a different keystore file.
You're done. Forget about
- OpenSSL
- PKCS#12
- self-signed certificates
- all forms of
trustAllCerts
, customTrustManagers
, and custom code of any kind whatsoever - using the same keypair/certificate for the server and client
- importing the server certificate to the client, and vice versa
- any system properties other than those that identify the
javax.net.ssl.keyStore
andjavax.net.ssl.keyStorePassword
- setting a password on the keypair or the imported signed certificate.
- 在服务器端,生成密钥对和证书请求;签字;使用
-trustcacerts
选项导入签名者的证书链;并使用创建密钥对和 CSR 时使用的相同别名导入签名证书。 - 在客户端,同上,但使用(当然)不同的密钥库文件。
你完成了。把...忘了吧
- 开放式SSL
- PKCS#12
- 自签名证书
- 所有形式的
trustAllCerts
、自定义的TrustManagers
和任何类型的自定义代码 - 对服务器和客户端使用相同的密钥对/证书
- 将服务器证书导入客户端,反之亦然
- 除标识
javax.net.ssl.keyStore
和javax.net.ssl.keyStorePassword
- 在密钥对或导入的签名证书上设置密码。
Steps (1) and (2) are how it is intended to be done. Depart from those and you are in for trouble and strife.
步骤 (1) 和 (2) 是打算如何完成的。离开那些,你就会陷入麻烦和冲突。
回答by GreyFairer
You probably didn't add the full certificate chain in your keystore, and just included the keypair itself. In that case, the client just receives the public key, but it cannot validate if that key can be trusted. The certificate chain is there to be able to check if the signatures on the public key match, and lead up to a trusted certificate authority.
您可能没有在密钥库中添加完整的证书链,而只是包含了密钥对本身。在这种情况下,客户端只接收公钥,但无法验证该密钥是否可信。证书链可以检查公钥上的签名是否匹配,并连接到受信任的证书颁发机构。
See e.g: Adding certificate chain to p12(pfx) certificate
参见例如:将证书链添加到 p12(pfx) 证书
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
You can do it the java way, too, using e.g. portecle: http://portecle.sourceforge.net/import-ca-reply.html, but you also need to combine the certificate chain in one file to import. Just copy-paste all certificates after one another, starting from your own, and ending with the root CA.
您也可以使用 java 方式进行操作,例如使用 portecle:http://portecle.sourceforge.net/import-ca-reply.html ,但您还需要将证书链合并到一个文件中以进行导入。只需一个接一个地复制粘贴所有证书,从您自己的证书开始,并以根 CA 结束。
This way, the resulting pfx file can be used on the server to return the certificate chain to the client.
这样,生成的 pfx 文件可以在服务器上使用,以将证书链返回给客户端。