Restlet javax.net.ssl.SSLHandshakeException:空证书链
Restlet javax.net.ssl.SSLHandshakeException: null cert chain
我正在本地测试客户端和服务器之间的 SSL 通信。
所以我使用 OpenSSL 命令生成了证书。在 cacert 文件中添加了此证书。还生成了 .p12 文件。
我在服务器和客户端中使用相同的 .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() 添加密钥库和信任库,但没有成功。
请帮忙。提前致谢。
首先,让我们创建一个 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 和证书对我来说总是很棘手。我通常会在反复试验后让它工作。希望对您有所帮助。
此外,您可能还想考虑使用反向代理类型的 Web 服务器,如 Apache2 或 Nginx。如果您想使用它们,您必须将您的证书合并到一个文件中。如果您查看您的证书文件,您会发现每个文件(您自己的证书、中间证书和根证书)都是这样的
-----BEGIN CERTIFICATE-----
MIIDfTCCAuagAwIBAgIDErvmMA0GCSqGSIb3DQEBBQUA...
....
-----END CERTIFICATE-----
您只需将一个添加到另一个即可创建合并证书。而不是使用该证书在 Apache2 或 Nginx 上结束 SSL。这就是我通常做的。但是在客户端,您仍然需要创建 JKS 文件。
一种选择是阅读 p12/pfx-file,获取证书并使用它们以编程方式构建 KeyStores 和 TrustStores。
如果输入是一个包含 CA 根证书和相关客户端证书的 pfx 文件,
下面 class SslUtils
中显示的方法可以让您做到这一点。
但有一点需要注意:默认的 Restlet 服务器(版本 2.3.4)不会获取客户端发送的证书。
我确实设法解决了这个问题(虽然不是很好),请参阅我在 this question.
上的回答
我将在这里重点介绍安全连接的配置,但所有源代码和工作示例都可以在
restlet-clientcert Github 项目。
Github 项目是我认为我知道自己在做什么的结果,没有运气,也没有使用 Restlet 的经验,
但无论如何我都要咬紧牙关,这样我就可以感觉好一点,因为我知道我可以让这些基本的东西发挥作用。
在服务器端,使用自定义 ServerSslContextFactory
以编程方式配置所使用的 SSLContext
。
注册自定义工厂:
ServerSslContextFactory sslCtx = new ServerSslContextFactory();
sslCtx.init(certFileName, certFilePwd);
ConcurrentMap<String, Object> attribs = server.getContext().getAttributes();
attribs.put("sslContextFactory", sslCtx);
并附上 "guard" 以提取客户端证书信息:
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
class 包含 "read and reconstruct" 方法
(包含 "get email-address from certificate" 方法的完整版本可在前面提到的 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:
*
* <br>See for client certificates in Java:
*
* @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)。
I am using the same .p12 file in server and client
这已经是一个错误。客户端和服务器是不同的身份,不能有相同的私钥、public密钥或证书。
我建议您放弃所有 OpenSSL 的东西,然后使用 keytool
重新开始,如下所示:
- 在服务器端,生成一个密钥对,以及一个证书请求;签署它;使用
-trustcacerts
选项导入签名者的证书链;并使用您在创建密钥对和 CSR 时使用的相同别名导入签名证书 。
- 在客户端,同上,但使用(当然)不同的密钥库文件。
大功告成。忘了
- OpenSSL
- PKCS#12
- 自签名证书
- 所有形式的
trustAllCerts
、自定义 TrustManagers
以及任何类型的自定义代码
- 对服务器和客户端使用相同的keypair/certificate
- 将服务器证书导入客户端,反之亦然
- 除标识
javax.net.ssl.keyStore
和 javax.net.ssl.keyStorePassword
之外的任何系统属性
- 在密钥对或导入的签名证书上设置密码。
步骤 (1) 和 (2) 是它的预期完成方式。离开这些,你就会遇到麻烦和冲突。
您可能没有在密钥库中添加完整的证书链,而只是包含了密钥对本身。在这种情况下,客户端只会收到 public 密钥,但无法验证该密钥是否可信。证书链可以检查 public 密钥上的签名是否匹配,并引导至受信任的证书颁发机构。
参见示例: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 文件可以在服务器上用于 return 到客户端的证书链。
我正在本地测试客户端和服务器之间的 SSL 通信。 所以我使用 OpenSSL 命令生成了证书。在 cacert 文件中添加了此证书。还生成了 .p12 文件。
我在服务器和客户端中使用相同的 .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() 添加密钥库和信任库,但没有成功。
请帮忙。提前致谢。
首先,让我们创建一个 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 和证书对我来说总是很棘手。我通常会在反复试验后让它工作。希望对您有所帮助。
此外,您可能还想考虑使用反向代理类型的 Web 服务器,如 Apache2 或 Nginx。如果您想使用它们,您必须将您的证书合并到一个文件中。如果您查看您的证书文件,您会发现每个文件(您自己的证书、中间证书和根证书)都是这样的
-----BEGIN CERTIFICATE-----
MIIDfTCCAuagAwIBAgIDErvmMA0GCSqGSIb3DQEBBQUA...
....
-----END CERTIFICATE-----
您只需将一个添加到另一个即可创建合并证书。而不是使用该证书在 Apache2 或 Nginx 上结束 SSL。这就是我通常做的。但是在客户端,您仍然需要创建 JKS 文件。
一种选择是阅读 p12/pfx-file,获取证书并使用它们以编程方式构建 KeyStores 和 TrustStores。
如果输入是一个包含 CA 根证书和相关客户端证书的 pfx 文件,
下面 class SslUtils
中显示的方法可以让您做到这一点。
但有一点需要注意:默认的 Restlet 服务器(版本 2.3.4)不会获取客户端发送的证书。
我确实设法解决了这个问题(虽然不是很好),请参阅我在 this question.
我将在这里重点介绍安全连接的配置,但所有源代码和工作示例都可以在 restlet-clientcert Github 项目。 Github 项目是我认为我知道自己在做什么的结果,没有运气,也没有使用 Restlet 的经验, 但无论如何我都要咬紧牙关,这样我就可以感觉好一点,因为我知道我可以让这些基本的东西发挥作用。
在服务器端,使用自定义 ServerSslContextFactory
以编程方式配置所使用的 SSLContext
。
注册自定义工厂:
ServerSslContextFactory sslCtx = new ServerSslContextFactory();
sslCtx.init(certFileName, certFilePwd);
ConcurrentMap<String, Object> attribs = server.getContext().getAttributes();
attribs.put("sslContextFactory", sslCtx);
并附上 "guard" 以提取客户端证书信息:
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
class 包含 "read and reconstruct" 方法
(包含 "get email-address from certificate" 方法的完整版本可在前面提到的 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:
*
* <br>See for client certificates in Java:
*
* @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)。
I am using the same .p12 file in server and client
这已经是一个错误。客户端和服务器是不同的身份,不能有相同的私钥、public密钥或证书。
我建议您放弃所有 OpenSSL 的东西,然后使用 keytool
重新开始,如下所示:
- 在服务器端,生成一个密钥对,以及一个证书请求;签署它;使用
-trustcacerts
选项导入签名者的证书链;并使用您在创建密钥对和 CSR 时使用的相同别名导入签名证书 。 - 在客户端,同上,但使用(当然)不同的密钥库文件。
大功告成。忘了
- OpenSSL
- PKCS#12
- 自签名证书
- 所有形式的
trustAllCerts
、自定义TrustManagers
以及任何类型的自定义代码 - 对服务器和客户端使用相同的keypair/certificate
- 将服务器证书导入客户端,反之亦然
- 除标识
javax.net.ssl.keyStore
和javax.net.ssl.keyStorePassword
之外的任何系统属性
- 在密钥对或导入的签名证书上设置密码。
步骤 (1) 和 (2) 是它的预期完成方式。离开这些,你就会遇到麻烦和冲突。
您可能没有在密钥库中添加完整的证书链,而只是包含了密钥对本身。在这种情况下,客户端只会收到 public 密钥,但无法验证该密钥是否可信。证书链可以检查 public 密钥上的签名是否匹配,并引导至受信任的证书颁发机构。
参见示例: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 文件可以在服务器上用于 return 到客户端的证书链。