TLS を介して両面認証を実現するには、サーバー側とクライアント側にキーストアが必要です。
私は通常 Portecle ツールを使用してキーストアを作成します。これは非常に使いやすい GUI Java ベースのツールです。portecle.sourceforge.net からダウンロードできます。
このツールを使用して、サーバー側用に JKS 形式のキーストアを作成し、クライアント側用に BKS 形式で別のキーストアを作成する必要があります。Android は JKS キーストアの使用をネイティブにサポートしておらず、BKS のみをサポートしており、その逆も同様であるため、形式が異なります。
2 つのキーストアを作成したら、それぞれのキー ペア (公開キーと秘密キー) を生成する必要があります。Portecle には、それを行うための「ツール」サブメニューの下にあるツールバーのボタンがあります。共通のキー アルゴリズムとサイズは RSA 2048 ビットです。その後、組織単位、名前、地域など、いくつかのパラメーターを証明書に設定する必要があります。
これで、2 つの鍵ストアと 2 つの鍵ペアができました。
クライアントがサーバーによって受信されたメッセージを復号化できるようにするには、クライアント キーストアにサーバー キーストアの公開鍵を提供する必要があります。これを行うには、サーバーのキーストアでキーペアを右クリックし、[エクスポート] オプションをクリックします。次に、「証明書チェーン」を選択し、証明書を保存するファイルシステム上の場所を選択します。
これで、公開鍵をクライアント キーストアにインポートする準備が整いました。これを行うには、[ツール] ツールバーをクリックして [信頼できる証明書のインポート] を選択し、前の手順でエクスポートした証明書ファイルを探します。信頼パスを確立できなかったことを示す警告メッセージが表示されますが、今は心配しないでください。
これで、キーペアを含むクライアント キーストアとサーバー証明書が作成されました。クライアント側ではこれで十分です。
ここで、クライアントの鍵ペアをサーバーの鍵ストアにインポートする必要があります。クライアント キーストアでキーペアを右クリックし、[エクスポート] を選択します。開いたポップアップで [秘密鍵と証明書] を選択し、PKCS #12 形式を選択します。
次に、サーバー キーストアを開き、[ツール] ツールバーの [キーペアのインポート] サブメニューを使用して、前の手順でエクスポートしたキーペアを選択します。
クライアント キーストアを BKS 形式で保存し、サーバー キーストアを JKS 形式で保存することは非常に重要です。
OK、キーストアはこれですべてです。次はコーディングです。
サーバーコードから始めましょう。次のスニペットは、私が実行している Java Spring プロジェクトから抽出されたものです。これは組み込みの tomcat であるため、構成は純粋な Java であり、従来の tomcat 構成で ssl コネクタを構成する方法を見つけるのは非常に簡単なので、組み込みバージョンのみを配置します。
private Connector createSslConnector() {
//print the client keystore
printClientKeystore();
Connector connector = new Connector("org.apache.coyote.http11.Http11NioProtocol");
Http11NioProtocol protocol = (Http11NioProtocol) connector.getProtocolHandler();
try {
//read the keystore from the jar
//and write it to a tmp file
Resource keystoreResource = context.getResource("classpath:config/server.keystore");
byte[] keystoreData = readKeystore(keystoreResource.getInputStream());
File tmpKeystoreFile = File.createTempFile("keystore", "");
writeKeystore(tmpKeystoreFile, keystoreData);
//keystore information
final String keystoreFile = tmpKeystoreFile.getAbsolutePath();
final String keystorePass = "yourKeystorePass";
final String keystoreType = "pkcs12";
final String keystoreProvider = "SunJSSE";
final String keystoreAlias = "comics_tomcat";
connector.setScheme("https");
connector.setAttribute("clientAuth", "true");
connector.setPort(HTTPS_PORT);
connector.setSecure(true);
protocol.setSSLEnabled(true);
//keystore
protocol.setKeystoreFile(keystoreFile);
protocol.setKeystorePass(keystorePass);
protocol.setKeystoreType(keystoreType);
protocol.setProperty("keystoreProvider", keystoreProvider);
protocol.setKeyAlias(keystoreAlias);
//truststore
protocol.setTruststoreFile(keystoreFile);
protocol.setTruststorePass(keystorePass);
protocol.setPort(HTTPS_PORT);
return connector;
}
catch (IOException e) {
LOGGER.error(e.getMessage(), e);
throw new IllegalStateException("cant access keystore: [" + "keystore"
+ "] or truststore: [" + "keystore" + "]", e);
}
}
サーバー側はこれですべてです。「setSecure(true)」メソッドを使用して、セキュア フラグが true の ssl コネクタができました。これにより、秘密鍵/公開鍵認証を使用したユーザー認証を要求できます。
次のコードはクライアント側用です。キーストアとトラストストアを (同じキーストアを使用して) ロードし、特定のドメインへの接続を許可するようにホスト名検証を構成してから、接続を開きます。
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
//load keystore stream
byte[] keystoreData = readInputStream(getAssets().open("client.keystore"));
//load keystore
ByteArrayInputStream bais = new ByteArrayInputStream(keystoreData);
keyStore.load(bais, KEYSTORE_PASSWORD.toCharArray());
//load truststore
bais = new ByteArrayInputStream(keystoreData);
trustStore.load(bais, KEYSTORE_PASSWORD.toCharArray());
//load trustmanager
TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(trustStore);
//init keymanager
KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
kmf.init(keyStore, KEYSTORE_PASSWORD.toCharArray());
//create ssl context
sslContext = SSLContext.getInstance("TLS");
sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
HostnameVerifier HOSTNAME_VERIFIER = new HostnameVerifier() {
@Override
public boolean verify(String hostname, SSLSession session) {
List<String> allowedHostnames = new ArrayList<String>();
allowedHostnames.add("pinterest.com");
allowedHostnames.add("192.168.1.43");
allowedHostnames.add("10.0.2.2");
return allowedHostnames.indexOf(hostname) != -1;
}
};
//open https connection
URL url = new URL("https://" + SERVER_URL + ":" + SERVER_PORT + "/api/v1/publication/getDescriptor/" + publicationId);
HttpsURLConnection urlConnection = (HttpsURLConnection) url.openConnection();
urlConnection.setSSLSocketFactory(sslContext.getSocketFactory());
urlConnection.setHostnameVerifier(HOSTNAME_VERIFIER);
//read server response
byte[] serverResult = readInputStream(urlConnection.getInputStream());
ご不明な点がございましたら、お気軽にお問い合わせください。