お仕事でBurpSuiteを使っているのだけど、暫く前から気になってしょうがない点がある。HTTPSの時に、なんでBurpは適切なCNでPortSwiggerによる証明書を生成できるのだろう?
というわけで、その辺のもやもやを解決するためにはまずSSLを学ばないと駄目かなと思い、資料漁ったり入り口としてJavaでSSLを使うにはどうするのか、とか手をつけてみました。
今回はそのお勉強メモで、SSLでechoサーバをJavaで作ってみようという、少しぐぐればサンプルがゴロゴロ転がってるやつです。ただ、そのサンプルが「なぜそれで正常に動作するのか?」を理解するだけで実に丸一日使い果たした・・・。
まず資料。後でまとめ直しますが・・・
仕組みと仕様:
実装サンプル:
Java特化リファレンス:
SSLサーバ側が提示する証明書は、SSLContextをinitするときにKeyManagerとして渡される。SSLContextのinitにはKeyManagerとTrustManagerの二種類があるけど、接続を受け付けて先に提示するサーバ側が使うのがKeyManager, 提示された証明書を検証するときに信頼された証明書がストアされるのがTrustManager。かなり混乱してたのだけれど、単純にサーバ側は秘密鍵が必要なので、公開鍵・秘密鍵のペアを保存しているKeyManagerに。クライアントが証明書の検証を行うにはルートCAからの公開鍵のチェインがあればよいので、信頼された証明書を管理するTrustManager、ということになる。
結果だけ取り急ぎメモ。こんなシェルスクリプト作りました。
makeks.sh:
#!/bin/sh -x KEYSTORE=testks01 PASSWORD=changeit ALIAS=testkey DNAME="cn=TestName, ou=TestUnit, o=TestOrg, l=Japan, st=Tokyo, c=JP" CERT_PEM=testkey.pem rm -i $KEYSTORE keytool -J-Dfile.encoding=UTF-8 \ -keystore $KEYSTORE \ -storepass $PASSWORD \ -storetype jks \ -genkeypair \ -alias $ALIAS \ -validity 3650 \ -dname "$DNAME" \ -keypass $PASSWORD keytool -J-Dfile.encoding=UTF-8 \ -keystore $KEYSTORE \ -storepass $PASSWORD \ -storetype jks \ -list -v keytool -J-Dfile.encoding=UTF-8 \ -keystore $KEYSTORE \ -storepass $PASSWORD \ -storetype jks \ -exportcert \ -alias $ALIAS \ -rfc \ -file $CERT_PEM
最後にexportしてるのは、後でopenssl s_clientで確認する時用に"-rfc"付き=PEM形式で出力された証明書が欲しいから。
で、keytoolが操作するデフォルトのKeyStoreは"JKS"という形式です。これはkeytoolのドキュメントに書かれています。
キーストアには、Sun が提供する組み込みのデフォルトの実装があります。これは、JKS という名前の独自のキーストアタイプ (形式) を利用するもので、キーストアをファイルとして実装しています。この実装では、個々の非公開鍵は個別のパスワードによって保護され、キーストア全体の整合性も (非公開鍵とは別の) パスワードによって保護されます。
では、どこに「デフォルトはJKSである」という指定があるのか?これはJavaのセキュリティプロファイルの設定に書かれているようです。同じくkeytoolのドキュメントにあります。
デフォルトのキーストアタイプは JKS (Sun が提供する独自のタイプのキーストアの実装) です。これは、セキュリティープロパティーファイル内の次の行によって指定されています。
keystore.type=jks
これにより、Javaのコードからkeytool用のKeyStoreを操作するには以下のようなコードを書けば良いことが導かれます。
KeyStore ks1 = KeyStore.getInstance("JKS");
MacOSX 10.7.2 のJDKでは以下の場所に記述されていました。
/System/Library/Java/JavaVirtualMachines/1.6.0.jdk/Contents/Home/lib/security/java.security:
keystore.type=jks
こんなクラスでなんとか動かせました。
package test; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.FileInputStream; import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.net.ServerSocket; import java.net.Socket; import java.security.KeyStore; import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.SSLContext; import javax.net.ssl.TrustManagerFactory; public class KeyStore1 { public static void main(String[] args) throws Exception { char[] commonPassword = "changeit".toCharArray(); KeyStore ks1 = KeyStore.getInstance("JKS"); ks1.load(new FileInputStream("testks01"), commonPassword); System.out.println("KeyStore's size = " + ks1.size()); KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509"); kmf.init(ks1, commonPassword); // 明示的に空のTrustManagerを使ってみます。 KeyStore emptyTrustStore = KeyStore.getInstance("JKS"); emptyTrustStore.load(null, "".toCharArray()); // nullで空のKeyStoreを指定したことになります。 TrustManagerFactory emptyTMF = TrustManagerFactory.getInstance("PKIX"); emptyTMF.init(emptyTrustStore); SSLContext sslContext = SSLContext.getInstance("TLSv1"); sslContext.init(kmf.getKeyManagers(), emptyTMF.getTrustManagers(), null); ServerSocket serverSocket = sslContext.getServerSocketFactory().createServerSocket(18080); BufferedReader br = null; BufferedWriter bw = null; while (true) { Socket clientSocket = serverSocket.accept(); br = new BufferedReader(new InputStreamReader(clientSocket.getInputStream())); bw = new BufferedWriter(new OutputStreamWriter(clientSocket.getOutputStream())); String recv = br.readLine(); System.out.println("Received Message = [" + recv + "]"); bw.write("Hello, your message = [" + recv + "]"); bw.flush(); try { br.close(); } catch (Exception e){} try { bw.close(); } catch (Exception e){} } } }
$ openssl s_client -connect localhost:18080 CONNECTED(00000003) depth=0 /C=JP/ST=Tokyo/L=Japan/O=TestOrg/OU=TestUnit/CN=TestName verify error:num=18:self signed certificate verify return:1 depth=0 /C=JP/ST=Tokyo/L=Japan/O=TestOrg/OU=TestUnit/CN=TestName verify return:1 --- Certificate chain 0 s:/C=JP/ST=Tokyo/L=Japan/O=TestOrg/OU=TestUnit/CN=TestName i:/C=JP/ST=Tokyo/L=Japan/O=TestOrg/OU=TestUnit/CN=TestName --- Server certificate -----BEGIN CERTIFICATE----- MIIDBDCCAsKgAwIBAgIET5PQxjALBgcqhkjOOAQDBQAwZTELMAkGA1UEBhMCSlAx DjAMBgNVBAgTBVRva3lvMQ4wDAYDVQQHEwVKYXBhbjEQMA4GA1UEChMHVGVzdE9y ZzERMA8GA1UECxMIVGVzdFVuaXQxETAPBgNVBAMTCFRlc3ROYW1lMB4XDTEyMDQy MjA5MzUwMloXDTIyMDQyMDA5MzUwMlowZTELMAkGA1UEBhMCSlAxDjAMBgNVBAgT BVRva3lvMQ4wDAYDVQQHEwVKYXBhbjEQMA4GA1UEChMHVGVzdE9yZzERMA8GA1UE CxMIVGVzdFVuaXQxETAPBgNVBAMTCFRlc3ROYW1lMIIBuDCCASwGByqGSM44BAEw ggEfAoGBAP1/U4EddRIpUt9KnC7s5Of2EbdSPO9EAMMeP4C2USZpRV1AIlH7WT2N WPq/xfW6MPbLm1Vs14E7gB00b/JmYLdrmVClpJ+f6AR7ECLCT7up1/63xhv4O1fn xqimFQ8E+4P208UewwI1VBNaFpEy9nXzrith1yrv8iIDGZ3RSAHHAhUAl2BQjxUj C8yykrmCouuEC/BYHPUCgYEA9+GghdabPd7LvKtcNrhXuXmUr7v6OuqC+VdMCz0H gmdRWVeOutRZT+ZxBxCBgLRJFnEj6EwoFhO3zwkyjMim4TwWeotUfI0o4KOuHiuz pnWRbqN/C/ohNWLx+2J6ASQ7zKTxvqhRkImog9/hWuWfBpKLZl6Ae1UlZAFMO/7P SSoDgYUAAoGBAOb3/ZEzhB9iwQ771XuMeppRoSgeYxa1XsWgpWsem7+RqEZnF77s EucxVA4eHtZylnipv7bGuwUAhrq8iCXIwUMlijrLk4jqiShdLBmZxJowR8VUhnm9 KyPyBeGQLrlLDXARwsM63drvWmTeMLaksocS2UhfeSS/t5MVS5oVr454MAsGByqG SM44BAMFAAMvADAsAhQbqbi4HhYtG8LopueO/noca1OhIAIUFCYPCOaNPQcSfvOU b/x7qdXQiuU= -----END CERTIFICATE----- subject=/C=JP/ST=Tokyo/L=Japan/O=TestOrg/OU=TestUnit/CN=TestName issuer=/C=JP/ST=Tokyo/L=Japan/O=TestOrg/OU=TestUnit/CN=TestName --- No client certificate CA names sent --- SSL handshake has read 1282 bytes and written 296 bytes --- New, TLSv1/SSLv3, Cipher is DHE-DSS-AES256-SHA Server public key is 1024 bit Secure Renegotiation IS supported Compression: NONE Expansion: NONE SSL-Session: Protocol : TLSv1 Cipher : DHE-DSS-AES256-SHA Session-ID: 4F93DB16D389C93A4D45034E1EB62BC8D9E7D7766997AF5F6C64A77DDA61E405 Session-ID-ctx: Master-Key: F7A0B6B5CF9A13BFBC7EBF7DBB51468251CCF64A2E5206649F45CF16E393BD5F03BADDA29A9AFF8F83496EDC8613C52A Key-Arg : None Start Time: 1335089942 Timeout : 300 (sec) Verify return code: 18 (self signed certificate) --- Hello Hello, your message = [Hello]closed
$ openssl s_client -connect localhost:18080 -CAfile testkey.pem CONNECTED(00000003) depth=0 /C=JP/ST=Tokyo/L=Japan/O=TestOrg/OU=TestUnit/CN=TestName verify return:1 --- Certificate chain 0 s:/C=JP/ST=Tokyo/L=Japan/O=TestOrg/OU=TestUnit/CN=TestName i:/C=JP/ST=Tokyo/L=Japan/O=TestOrg/OU=TestUnit/CN=TestName --- Server certificate -----BEGIN CERTIFICATE----- MIIDBDCCAsKgAwIBAgIET5PQxjALBgcqhkjOOAQDBQAwZTELMAkGA1UEBhMCSlAx DjAMBgNVBAgTBVRva3lvMQ4wDAYDVQQHEwVKYXBhbjEQMA4GA1UEChMHVGVzdE9y ZzERMA8GA1UECxMIVGVzdFVuaXQxETAPBgNVBAMTCFRlc3ROYW1lMB4XDTEyMDQy MjA5MzUwMloXDTIyMDQyMDA5MzUwMlowZTELMAkGA1UEBhMCSlAxDjAMBgNVBAgT BVRva3lvMQ4wDAYDVQQHEwVKYXBhbjEQMA4GA1UEChMHVGVzdE9yZzERMA8GA1UE CxMIVGVzdFVuaXQxETAPBgNVBAMTCFRlc3ROYW1lMIIBuDCCASwGByqGSM44BAEw ggEfAoGBAP1/U4EddRIpUt9KnC7s5Of2EbdSPO9EAMMeP4C2USZpRV1AIlH7WT2N WPq/xfW6MPbLm1Vs14E7gB00b/JmYLdrmVClpJ+f6AR7ECLCT7up1/63xhv4O1fn xqimFQ8E+4P208UewwI1VBNaFpEy9nXzrith1yrv8iIDGZ3RSAHHAhUAl2BQjxUj C8yykrmCouuEC/BYHPUCgYEA9+GghdabPd7LvKtcNrhXuXmUr7v6OuqC+VdMCz0H gmdRWVeOutRZT+ZxBxCBgLRJFnEj6EwoFhO3zwkyjMim4TwWeotUfI0o4KOuHiuz pnWRbqN/C/ohNWLx+2J6ASQ7zKTxvqhRkImog9/hWuWfBpKLZl6Ae1UlZAFMO/7P SSoDgYUAAoGBAOb3/ZEzhB9iwQ771XuMeppRoSgeYxa1XsWgpWsem7+RqEZnF77s EucxVA4eHtZylnipv7bGuwUAhrq8iCXIwUMlijrLk4jqiShdLBmZxJowR8VUhnm9 KyPyBeGQLrlLDXARwsM63drvWmTeMLaksocS2UhfeSS/t5MVS5oVr454MAsGByqG SM44BAMFAAMvADAsAhQbqbi4HhYtG8LopueO/noca1OhIAIUFCYPCOaNPQcSfvOU b/x7qdXQiuU= -----END CERTIFICATE----- subject=/C=JP/ST=Tokyo/L=Japan/O=TestOrg/OU=TestUnit/CN=TestName issuer=/C=JP/ST=Tokyo/L=Japan/O=TestOrg/OU=TestUnit/CN=TestName --- No client certificate CA names sent --- SSL handshake has read 1282 bytes and written 296 bytes --- --- New, TLSv1/SSLv3, Cipher is DHE-DSS-AES256-SHA Server public key is 1024 bit Secure Renegotiation IS supported Compression: NONE Expansion: NONE SSL-Session: Protocol : TLSv1 Cipher : DHE-DSS-AES256-SHA Session-ID: 4F93DB59D0CF63BF0AD9DA123066DE22AFF5781398C7F17B07BD07E4DF0E94C1 Session-ID-ctx: Master-Key: 617EB17722AEDBF398245F4C65F910DD36C261E4283516329F7B11F689C4A0661E64960E5FEB967551592B40706F84B2 Key-Arg : None Start Time: 1335090009 Timeout : 300 (sec) Verify return code: 0 (ok) --- Hello Hello, your message = [Hello]closed
取り急ぎ以上。