私はHTTPS/SSL/TLS
にはかなり慣れていませんが、証明書を使って認証するときにクライアントが提示するものとは少し混同しています。
私は、特定のPOST
に対して単純なURL
のデータを実行する必要があるJavaクライアントを書いています。その部分はうまく動作します、唯一の問題はそれがHTTPS
の上でされることになっているということです。 HTTPS
の部分は(HTTPclient
を使うか、またはJavaの組み込みのHTTPS
サポートを使って)扱いがかなり簡単ですが、私はクライアント証明書を使った認証を続けています。ここにはすでに似たような質問があることに気づきました。私は自分のコードでまだ試していません(もうすぐ十分に出る予定です)。私が現在抱えている問題は、私が何をしても、Javaクライアントが証明書を送信しないことです(これをPCAP
ダンプで確認できます)。
証明書を使用して認証するときにクライアントがサーバーに提示することを正確に知りたいのですが(特にJavaの場合 - それが問題になる場合)。これはJKS
ファイルですか、それともPKCS#12
ですか。その中にあるはずのもの。クライアント証明書だけか、それとも鍵なのかもしそうなら、どのキー?さまざまな種類のファイル、証明書の種類などについて、かなりの混乱があります。
前にも言ったように、私はHTTPS/SSL/TLS
に慣れていないので、私はいくつかの背景情報も同様に感謝します(エッセイである必要はありません。私は良い記事へのリンクを決めます)。
最終的にすべての問題を解決することができたので、自分の質問に答えます。これらは、特定の問題を解決するために管理に使用した設定/ファイルです。
クライアントのキーストアは、PKCS#12フォーマットファイルです
それを生成するために、たとえばOpenSSLのpkcs12
コマンドを使用しました。
openssl pkcs12 -export -in client.crt -inkey client.key -out client.p12 -name "Whatever"
ヒント:最新のOpenSSLを入手してください、notバージョン0.9.8h PKCS#12ファイルを適切に生成できないバグから。
このPKCS#12ファイルは、サーバーがクライアントに認証を明示的に要求したときに、Javaクライアントによってクライアント証明書をサーバーに提示するために使用されます。クライアント証明書認証のプロトコルが実際にどのように機能するかの概要については、 TLSに関するウィキペディアの記事 を参照してください(ここでクライアントの秘密鍵が必要な理由も説明しています)。
クライアントのトラストストアはJKS形式のファイルであり、rootまたは中間CA証明書。これらのCA証明書は、通信を許可するエンドポイントを決定します。この場合、クライアントは、トラストストアのCAのいずれかによって署名された証明書を提示するサーバーに接続できます。
生成するには、標準のJavaキーツールを使用できます。たとえば、
keytool -genkey -dname "cn=CLIENT" -alias truststorekey -keyalg RSA -keystore ./client-truststore.jks -keypass whatever -storepass whatever
keytool -import -keystore ./client-truststore.jks -file myca.crt -alias myca
このトラストストアを使用して、クライアントは、myca.crt
で識別されるCAによって署名された証明書を提示するすべてのサーバーとの完全なSSLハンドシェイクを試行します。
上記のファイルは、クライアント専用です。サーバーもセットアップする場合、サーバーには独自のキーファイルとトラストストアファイルが必要です。 Javaクライアントとサーバー(Tomcatを使用)の両方で完全に機能する例をセットアップするための優れたチュートリアルは、 このWebサイト にあります。
問題/備考/ヒント
-Djavax.net.debug=ssl
に似ていますが、Java SSLデバッグ出力が気に入らない場合は、より構造化されており、(おそらく)簡単に解釈できます。Apache httpclientライブラリを使用することは完全に可能です。 httpclientを使用する場合は、宛先URLを同等のHTTPSに置き換えて、次のJVM引数を追加します(HTTP/HTTPSを介してデータを送受信するために使用するライブラリに関係なく、他のクライアントと同じです)。 :
-Djavax.net.debug=ssl
-Djavax.net.ssl.keyStoreType=pkcs12
-Djavax.net.ssl.keyStore=client.p12
-Djavax.net.ssl.keyStorePassword=whatever
-Djavax.net.ssl.trustStoreType=jks
-Djavax.net.ssl.trustStore=client-truststore.jks
-Djavax.net.ssl.trustStorePassword=whatever
他の回答は、クライアント証明書をグローバルに設定する方法を示しています。ただし、JVMで実行されているすべてのアプリケーションでグローバルに定義するのではなく、1つの特定の接続に対してクライアントキーをプログラムで定義する場合は、次のように独自のSSLContextを構成できます。
String keyPassphrase = "";
KeyStore keyStore = KeyStore.getInstance("PKCS12");
keyStore.load(new FileInputStream("cert-key-pair.pfx"), keyPassphrase.toCharArray());
SSLContext sslContext = SSLContexts.custom()
.loadKeyMaterial(keyStore, null)
.build();
HttpClient httpClient = HttpClients.custom().setSSLContext(sslContext).build();
HttpResponse response = httpClient.execute(new HttpGet("https://example.com"));
それらのJKSファイルは、証明書と鍵ペアの単なるコンテナです。クライアント側の認証シナリオでは、キーのさまざまな部分がここにあります。
トラストストアとキーストアの分離は必須ではありませんが、お勧めです。それらは同じ物理ファイルにすることができます。
2つのストアのファイルシステムの場所を設定するには、次のシステムプロパティを使用します。
-Djavax.net.ssl.keyStore=clientsidestore.jks
そしてサーバー上:
-Djavax.net.ssl.trustStore=serversidestore.jks
クライアントの証明書(公開鍵)をファイルにエクスポートして、それをサーバーにコピーできるようにするには、次のようにします。
keytool -export -alias MYKEY -file publicclientkey.cer -store clientsidestore.jks
クライアントの公開鍵をサーバーのキーストアにインポートするには、を使用します(投稿者が述べたように、これは既にサーバー管理者によって行われています)。
keytool -import -file publicclientkey.cer -store serversidestore.jks
Maven pom.xml:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.Apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.Apache.org/POM/4.0.0 http://maven.Apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>some.examples</groupId>
<artifactId>sslcliauth</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<name>sslcliauth</name>
<dependencies>
<dependency>
<groupId>org.Apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.4</version>
</dependency>
</dependencies>
</project>
Javaコード:
package some.examples;
import Java.io.FileInputStream;
import Java.io.IOException;
import Java.security.KeyManagementException;
import Java.security.KeyStore;
import Java.security.KeyStoreException;
import Java.security.NoSuchAlgorithmException;
import Java.security.UnrecoverableKeyException;
import Java.security.cert.CertificateException;
import Java.util.logging.Level;
import Java.util.logging.Logger;
import javax.net.ssl.SSLContext;
import org.Apache.http.HttpEntity;
import org.Apache.http.HttpHost;
import org.Apache.http.client.config.RequestConfig;
import org.Apache.http.client.methods.CloseableHttpResponse;
import org.Apache.http.client.methods.HttpPost;
import org.Apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.Apache.http.ssl.SSLContexts;
import org.Apache.http.impl.client.CloseableHttpClient;
import org.Apache.http.impl.client.HttpClients;
import org.Apache.http.util.EntityUtils;
import org.Apache.http.entity.InputStreamEntity;
public class SSLCliAuthExample {
private static final Logger LOG = Logger.getLogger(SSLCliAuthExample.class.getName());
private static final String CA_KEYSTORE_TYPE = KeyStore.getDefaultType(); //"JKS";
private static final String CA_KEYSTORE_PATH = "./cacert.jks";
private static final String CA_KEYSTORE_PASS = "changeit";
private static final String CLIENT_KEYSTORE_TYPE = "PKCS12";
private static final String CLIENT_KEYSTORE_PATH = "./client.p12";
private static final String CLIENT_KEYSTORE_PASS = "changeit";
public static void main(String[] args) throws Exception {
requestTimestamp();
}
public final static void requestTimestamp() throws Exception {
SSLConnectionSocketFactory csf = new SSLConnectionSocketFactory(
createSslCustomContext(),
new String[]{"TLSv1"}, // Allow TLSv1 protocol only
null,
SSLConnectionSocketFactory.getDefaultHostnameVerifier());
try (CloseableHttpClient httpclient = HttpClients.custom().setSSLSocketFactory(csf).build()) {
HttpPost req = new HttpPost("https://changeit.com/changeit");
req.setConfig(configureRequest());
HttpEntity ent = new InputStreamEntity(new FileInputStream("./bytes.bin"));
req.setEntity(ent);
try (CloseableHttpResponse response = httpclient.execute(req)) {
HttpEntity entity = response.getEntity();
LOG.log(Level.INFO, "*** Reponse status: {0}", response.getStatusLine());
EntityUtils.consume(entity);
LOG.log(Level.INFO, "*** Response entity: {0}", entity.toString());
}
}
}
public static RequestConfig configureRequest() {
HttpHost proxy = new HttpHost("changeit.local", 8080, "http");
RequestConfig config = RequestConfig.custom()
.setProxy(proxy)
.build();
return config;
}
public static SSLContext createSslCustomContext() throws KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException, KeyManagementException, UnrecoverableKeyException {
// Trusted CA keystore
KeyStore tks = KeyStore.getInstance(CA_KEYSTORE_TYPE);
tks.load(new FileInputStream(CA_KEYSTORE_PATH), CA_KEYSTORE_PASS.toCharArray());
// Client keystore
KeyStore cks = KeyStore.getInstance(CLIENT_KEYSTORE_TYPE);
cks.load(new FileInputStream(CLIENT_KEYSTORE_PATH), CLIENT_KEYSTORE_PASS.toCharArray());
SSLContext sslcontext = SSLContexts.custom()
//.loadTrustMaterial(tks, new TrustSelfSignedStrategy()) // use it to customize
.loadKeyMaterial(cks, CLIENT_KEYSTORE_PASS.toCharArray()) // load client certificate
.build();
return sslcontext;
}
}
単純に双方向認証(サーバーとクライアントの証明書)を設定したいという方のために、これら2つのリンクを組み合わせることであなたはそこにたどり着くでしょう:
双方向認証設定:
https://linuxconfig.org/Apache-web-server-ssl-authentication
彼らが言及しているopenssl設定ファイルを使う必要はありません。ただ使う
$ openssl genrsa -des3 -out ca.key 4096
$ openssl req -new -x509 -days 365 -key ca.key -out ca.crt
独自のCA証明書を生成してから、サーバーキーとクライアントキーを生成して署名します。
$ openssl genrsa -des3 -out server.key 4096
$ openssl req -new -key server.key -out server.csr
$ openssl x509 -req -days 365 -in server.csr -CA ca.crt -CAkey ca.key -set_serial 100 -out server.crt
そして
$ openssl genrsa -des3 -out client.key 4096
$ openssl req -new -key client.key -out client.csr
$ openssl x509 -req -days 365 -in client.csr -CA ca.crt -CAkey ca.key -set_serial 101 -out client.crt
それ以外の場合は、リンクの手順に従ってください。 Chromeの証明書の管理は、前述のFirefoxの例と同じように機能します。
次に、次のようにしてサーバーを設定します。
サーバーの.crtと.keyは既に作成されているので、その手順はもう必要ありません。
私はここで修正したのはキーストア型であると思います、pkcs12(pfx)は常に秘密鍵を持ち、JKS型は秘密鍵なしで存在することができます。コードで指定するかブラウザを介して証明書を選択しない限り、サーバーはそれが相手方のクライアントを表していることを知る方法がありません。