web-dev-qa-db-ja.com

Java HTTPSクライアント証明書認証

私はHTTPS/SSL/TLSにはかなり慣れていませんが、証明書を使って認証するときにクライアントが提示するものとは少し混同しています。

私は、特定のPOSTに対して単純なURLのデータを実行する必要があるJavaクライアントを書いています。その部分はうまく動作します、唯一の問題はそれがHTTPSの上でされることになっているということです。 HTTPSの部分は(HTTPclientを使うか、またはJavaの組み込みのHTTPSサポートを使って)扱いがかなり簡単ですが、私はクライアント証明書を使った認証を続けています。ここにはすでに似たような質問があることに気づきました。私は自分のコードでまだ試していません(もうすぐ十分に出る予定です)。私が現在抱えている問題は、私が何をしても、Javaクライアントが証明書を送信しないことです(これをPCAPダンプで確認できます)。

証明書を使用して認証するときにクライアントがサーバーに提示することを正確に知りたいのですが(特にJavaの場合 - それが問題になる場合)。これはJKSファイルですか、それともPKCS#12ですか。その中にあるはずのもの。クライアント証明書だけか、それとも鍵なのかもしそうなら、どのキー?さまざまな種類のファイル、証明書の種類などについて、かなりの混乱があります。

前にも言ったように、私はHTTPS/SSL/TLSに慣れていないので、私はいくつかの背景情報も同様に感謝します(エッセイである必要はありません。私は良い記事へのリンクを決めます)。

206
tmbrggmn

最終的にすべての問題を解決することができたので、自分の質問に答えます。これらは、特定の問題を解決するために管理に使用した設定/ファイルです。

クライアントのキーストアは、PKCS#12フォーマットファイルです

  1. クライアントのpublic証明書(この場合、自己署名CAによって署名されています)
  2. クライアントのprivateキー

それを生成するために、たとえば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サイト にあります。

問題/備考/ヒント

  1. クライアント証明書認証はサーバーによってのみ実施できます。
  2. Important!)サーバーが(TLSハンドシェイクの一部として)クライアント証明書を要求すると、サーバーは証明書要求の一部として信頼できるCAのリストも提供します。認証のために提示したいクライアント証明書がこれらのCAのいずれかによって署名されたnotである場合、それはまったく提示されません(私の意見では、これは奇妙な動作ですが、それには理由があると確信しています)。これが私の問題の主な原因でした。相手がサーバーを適切に構成して自己署名クライアント証明書を受け入れなかったため、リクエストでクライアント証明書を適切に提供しなかったことが問題の原因であると想定したためです。
  3. Wiresharkを入手してください。 SSL/HTTPSパケット分析が優れており、問題のデバッグと検出に非常に役立ちます。 -Djavax.net.debug=sslに似ていますが、Java SSLデバッグ出力が気に入らない場合は、より構造化されており、(おそらく)簡単に解釈できます。
  4. 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
223
tmbrggmn

他の回答は、クライアント証明書をグローバルに設定する方法を示しています。ただし、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"));
48
Magnus

それらのJKSファイルは、証明書と鍵ペアの単なるコンテナです。クライアント側の認証シナリオでは、キーのさまざまな部分がここにあります。

  • clientのストアはクライアントのprivateとpublicキーペアを含みます。これはキーストアと呼ばれます。
  • serverのストアはクライアントのpublicキーを含みます。これはトラストストアと呼ばれます。

トラストストアとキーストアの分離は必須ではありませんが、お勧めです。それらは同じ物理ファイルにすることができます。

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
30
mhaller

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;
}

}
9
wildloop

単純に双方向認証(サーバーとクライアントの証明書)を設定したいという方のために、これら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の例と同じように機能します。

次に、次のようにしてサーバーを設定します。

https://www.digitalocean.com/community/tutorials/how-to-create-a-ssl-certificate-on-Apache-for-ubuntu-14-04

サーバーの.crtと.keyは既に作成されているので、その手順はもう必要ありません。

7
hans

私はここで修正したのはキーストア型であると思います、pkcs12(pfx)は常に秘密鍵を持ち、JKS型は秘密鍵なしで存在することができます。コードで指定するかブラウザを介して証明書を選択しない限り、サーバーはそれが相手方のクライアントを表していることを知る方法がありません。

0
ObiWanKenobi