はじめに
企業間の受発注取引をインターネットを利用して行うB2B(企業間電子商取引)も、ロゼッタネットをはじめとして、ここ数年で導入が活発化してきています。B2Bシステムを構築する際に欠かせないのがセキュリティの確保であり、セキュリティインフラの中心となるのがSSL(Secure Socket Layer)です。本記事では、J2SE1.4から標準で用意されたJSSE(Java Secure Socket Extension)のAPIを利用した簡単なSSLサーバー/クライアントの実装例を紹介します。
対象読者
Javaプログラミングを行ったことがある方を対象とします。
必要な環境
サンプルは以下の環境で動作確認を行っています。
SSLについて
SSLは、ネットワークを通じたデータ送信時にデータの機密性および整合性を保護するために設計されたプロトコルです。SSLは、Netscape Communications社によって開発されました。またSSL3.0に若干の改良を加えて考案されたプロトコルとしてTLS(Transport Layer Security)があります。TLSはRFC2246としてIETF(Internet Engineering Task Force)で標準化が行われています。
JSSEについて
JSSEはSSL(Secure Socket Layer)、TLS(Transport Layer Security)の機能提供を目的としたAPIおよび実装です。JSSEでは、SSL・TLSの基盤となる複雑なセキュリティアルゴリズムや「ハンドシェイク」機構を抽象化していますので、開発者はセキュアな通信を使用したアプリケーションを容易に実装する事ができます。J2SE1.2とJ2SE1.3ではオプショナルパッケージという位置付けでしたが、J2SE1.4からは標準APIとして提供されています。
JSSEのパッケージ・主なクラス・インターフェース群を以下の表に示します。
パッケージ名 | 説明 | 主な構成クラス・インターフェース |
java.security | セキュリティフレームワークのクラスとインターフェースを提供 | PrivateKey、PublicKey、KeyPair、KeyStore、Signature など |
java.security.acl | このパッケージの変わりにjava.securityのクラスが使われるようになった | - |
java.security.cert | 証明書、証明書の取り消しリスト(CRL)、証明書パスを解析および管理するためのクラスとインタフェースを提供 | CRL、X509Certificate、X509CRLなど |
java.security.interfaces | RSA Laboratory Technical Note PKCS#1で定義されているRSA鍵、およびNISTのFIPS-186で定義されているDSA鍵を生成するためのインタフェースを提供。 | DSAKey、DSAKeyPairGenerator、RSAKey、RSAKeyGeneratorなど |
java.security.spec | 鍵仕様およびアルゴリズムパラメータ仕様のクラスおよびインタフェースを提供。 | AlgorithmParameterSpec、KeySpec、PKCS8EncodedKeySpec、 RSAKeyGenParameterSpecなど |
javax.net.ssl | セキュアソケットパッケージのクラスを提供 | SSLSession、SSLSessionContext、TrustManager、HttpsURLConnection、 KeyManagerFactory、SSLContext、SSLServerSocket、SSLSocket、 SSLSocketFactory、TrustManagerFactoryなど |
JSSEでは、上記表のようにSSLの技術構成要素がクラス・インターフェースとして用意されています。SSL通信をする際には、javax.net.sslのクラス群を用いて接続を確立します。
サンプルの全体図
本稿のサンプル構成を以下の図に示します。
上記の図の用語について簡単に説明します。
用語 | 説明 |
トラストストア | 自らが信頼するCA(Certificate Authority:認証局)のルート証明書または中間証明書を保存する場所(ファイル)です。JSSEのプロバイダは、SSLの通信先がサーバー証明書を送信してきた際に、トラストストアに保存されている証明書によって署名がなされているかどうかによって認証の可否を判断します。 |
キーストア | 自らのサーバー証明書を保存する場所をいいます。JKS(Java Key Store)の場合、サーバー証明書をインストールする前に、その上位のルート証明書・中間証明書も共にインストールする必要があります。これによって証明書のチェーン(ルート証明書、中間証明書、サーバー証明書の連鎖)をSSLの通信先に送信する事が可能になります。 |
本記事のサンプルでは、サーバー側に証明書をインストールするだけではなく、クライアント側にも証明書をインストールします。クライアント側にも証明書をインストールし、サーバーとクライアントでお互いに認証しあう事で、よりセキュリティ性の高いシステムを構築する事ができます。
なおサンプルに使用するサーバー証明書は、クライアント側は、日本ベリサイン社のトライアルサーバIDサービスによるサーバー証明書、そしてサーバー側は、ビートラステッド・ジャパン株式会社のトライアル証明書発行サービスをそれぞれ使用します。ビートラステッド社のトライアルサーバー証明書は、ルート証明書・中間証明書・サーバー証明書と3層構造の連鎖となっていますのでより実践的な動作確認が可能となります。
PKI(Public Key Infrastructure)について
SSLは、PKI(Public Key Infrastructure)というセキュリティ基盤の中で用いられます。PKIでは、サーバーにただ1つだけ存在する秘密鍵と、サーバー証明書と共に配布される公開鍵という2つの鍵から成り立ちます。公開鍵で暗号化されたデータは対となる秘密鍵でしか複合化できません。また秘密鍵で暗号化されたデータは対となる公開鍵でしか複合化できません。PKIはこれらの性質を利用してデータの機密性を実現しています。PKIの詳細な説明については、本稿の最後に記載されている参考資料等を参照して下さい。
SSL通信環境構築までの手順概要
クライアント認証を行うSSL通信環境を構築するには、2つの証明書ストア(キーストア--自らの証明書を保存、トラストストア--信頼するCAが発行した証明書を保存)を構築する必要があります。
キーストア構築
キーストアに保存するサーバー証明書は通常、以下の手順で用意します。
- キーペア作成(秘密鍵・公開鍵)
- CSR(Certificate Signing Request:証明書要求)の作成
- CA(Certificate Authority:認証局)へのCSRの提出
- CA(Certificate Authority:認証局)によるサーバー証明書の発行
- キーストアにサーバー証明書をインポート
トラストストア構築
トラストストアには、CAが発行したルート証明書・中間証明書など自らが信頼する証明書が保存されます。SunのJDKの場合、「$JAVA_HOME/jre/lib/security/cacerts」ファイルが標準で用意されています。本記事ではベリサイン社、ビートラステッド社が提供するトライアル用ルート証明書・中間証明書をトラストストアにインポートします。
keytoolについて
本記事では、秘密鍵作成・証明書のインポートなどサーバー証明書に関する操作をJDK付属の「keytool」で行います。
keytool実行時には、キーペア作成、CSR作成、証明書のインポート・エクスポートなどの実行したい動作を、オプションで指定します。以下にそのオプションを示します。
オプション | 説明 |
-genkey | キーペアを生成 |
-certreq | CSR(Certificate Signing Request:証明書要求)を作成 |
-import | 証明書をインポート |
-export | 証明書をエクスポート |
-delete | 証明書エントリー削除 |
-list | 証明書エントリーの一覧表示 |
次に各動作モードで共通して使用するオプションを示します。
オプション | 説明 |
-alias | 証明書・鍵をキーストア内で識別する名前。 |
-keystore | キーストアのファイル名。省略した場合は、「home」ディレクトリの「.keystore」ファイル。 |
-storetype | キーストアの実装タイプ。省略した場合はJKS(=Java Key Store。Sun が提供する独自タイプのキーストア実装)。 |
-storepass | キーストアのパスワード。 |
各動作モードごとに異なるオプション指定は、次節の「サーバー側環境の構築」で説明します。
サーバー側環境の構築
それでは、まずサーバー側の環境を構築します。コマンドはソースコード内の、「server」フォルダ直下で、実行します。
キーストア構築
自らの証明書を保存するキーストアを構築します。ファイル名は「server_keystore」とします。
(1)キーペア作成(秘密鍵・公開鍵)
最初のステップはキーペアの作成です。ここではJDKに標準で添付されているkeytoolというツールを使用して作成します。opensslなどのツールを使っても同様に作成できます。
keytool -genkey -alias ssltest -keyalg RSA -keysize 512 -keypass changeit -validity 365 -storetype JKS -keystore server_keystore -storepass changeit
以下の表に「-genkey」実行時に使用するオプションを示します。
オプション | 説明 |
-keyalg | 秘密鍵・公開鍵作成時の暗号方式。省略時した場合はDSA(Digital Signature Algorithm)。 |
-keysize | 秘密鍵・公開鍵のサイズ。省略した場合は1024。 |
-keypass | 秘密鍵のパスワード。 |
-validity | 証明書の有効日数。省略した場合は90。 |
「-genkey」オプションでkeytoolコマンドを実行すると、画面上のプロンプトでキーペア作成に必要な各種パラメータの入力を順に求められるので画面上で入力します。
項目名(日本語) | 名称(英語) | 入力値 | 説明 |
姓名 | CN(Common Name) | sslserver.wings.msn.to | ホスト名を入力。SSLの認証処理で使用される。 |
組織単位名 | OU(Organization Unit) | Codezine | 任意の部署名を入力。 |
組織名 | O(Organization) | Wings Project | 任意の組織名を入力。 |
都市名または地域名 | L(Locality) | Chiba | 任意の都道府県名を入力。 |
州名または地方名 | ST(State) | Kamagaya | 任意の都市名を入力。 |
2文字の国番号 | C(Country) | JP | 日本の場合、JP。 |
なお、これらの値は、keytool実行時に
-dname "cn=sslserver.wings.msn.to, ou=Codezine, o=Wings Project, c=JP"
とDN(Distinguished Name:識別名)形式であらかじめオプション指定する事も可能です。
(2)CSR(Certificate Signing Request:証明書要求)の作成
keytool -certreq -alias ssltest -file ssl_server.csr -keystore server_keystore -storetype JKS -storepass changeit -keypass changeit
CSRとは、CA(認証局)に証明書を発行してもらう為に、サーバーの情報を格納したデータです。CAはCSRに格納された情報をもとにサーバー証明書を発行します。
CSRの中身
作成されたCSRファイルのデータは、以下のようにテキストで参照できるBase64エンコーディング形式と記述されます。
-----BEGIN NEW CERTIFICATE REQUEST----- MIIBszCCARwCAQAwczELMAkGA1UEBhMCanAxDzANBgNVBAgTBkFkYWNoaTEOMAwGA1UE BxMFVG9reW8xFTATBgNVBAoTDHRvLm1zbi53aW5nczEXMBUGA1UECxMOV2luZ3MgQ29k ZXppbmUxEzARBgNVBAMTCkhhcnVvIFNhdG8wgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJ AoGBAMmzT+uHOwq/zkvAcpfKiyv6Y4DUwT9ZJP2cTSOCHkEeGKJFEwc9jjTYZYoQFAR3 O7HD/9EgT1ia4PMyVS0257aAM+HP5ET0R/oZOwe2LgufoEOGfLEOTAGJghRvWX/LYU/G suxr3Hb+rb3DGH5cBXSxlQyF7Y1EwuQ0Q1nRImofAgMBAAGgADANBgkqhkiG9w0BAQQF AAOBgQBJ9g230J+33Ms3BjV3t9OJgl9ypJbC2KJOFXSeVuXQdeS81Up6SpQjNlJh7pD7 kzGw8fjnSer1vF/pQ1YHUJN2fdZgCPZS53Nf9HGbaEeEY+7B3t1SGdeV7g21pTxwkgOb x30+JkgKpFAuAG9xjGDnBuXy/9uLSM0PBXCjuwXrYQ== -----END NEW CERTIFICATE REQUEST-----
(3)認証局へのCSR(証明書要求)の提出
作成したCSRファイルをテキストエディタで開き、ビートラステッド社のトライアル証明書発行サービスのWeb画面の入力フォームに貼り付けます。
(4)サーバー証明書の取得
Web画面上にBase64エンコーディングのテキスト形式で証明書が表示されるので(ビートラステッド社の場合)、コピーし、任意のファイル名でローカルに保存します。ここではファイル名を「ssl_server.cer」とします。また、証明書以外にCAのルート証明書、中間証明書を入手し、ローカルに保存します。それぞれ「betrusted_root_ca.cer」「betrusted_ca.cer」というファイル名で保存します。
(5)サーバー証明書インポート(証明書連鎖の構築)
CAによって発行された証明書をキーストアに保存します。ポイントは、-trustcacerts オプションを使用して証明書連鎖を構築することと、証明書をインポートする際に、秘密鍵を生成した際のalias名と同一の名前を指定する事です。証明書連鎖を構築すると、証明書をクライアントに送信する際に連鎖している証明書も送信されます(SSLサーバーの実装によってはルート証明書も送信されますが、TLSの仕様では必須ではありません)。証明書は、ルート証明書→中間証明書→サーバー証明書の順でインポートします。
オプション | 説明 |
-trustcacerts | 指定した場合証明書連鎖を構築してインポートする |
- ルート証明書
- 中間証明書
- 証明書
keytool -import -alias betrusted_root_ca -file betrusted_root_ca.cer -keystore server_keystore -trustcacerts -storetype JKS -keypass changeit -storepass changeit
keytool -import -alias betrusted_ca -file betrusted_ca.cer -keystore server_keystore -trustcacerts -storetype JKS -keypass changeit -storepass changeit
keytool -import -alias ssltest -file ssl_server.cer -keystore server_keystore -trustcacerts -storetype JKS -keypass changeit -storepass changeit
トラストストア構築
ルート証明書インポート
当サンプルでは、クライアント認証を使用しますので、クライアント側証明書の発行者となるベリサイン社のルート証明書をサーバー側にインポートします。これにより、ベリサイン社のルート証明書の署名がなされている証明書はサーバー側で信頼されます。
トラストストアのファイル名は「server_cacerts」とします。
keytool -import -alias verisign_root_ca -file verisign_root_ca.cer -keystore server_cacerts -storetype JKS -keypass changeit -storepass changeit
クライアント側環境の構築
次にクライアント側の環境を構築します。コマンドはソースコード内の、「client」フォルダ直下で、実行します。
キーストア構築
自らの証明書を保存するキーストアを構築します。ファイル名は「client_keystore」とします。
(1)キーペア作成
keytool -genkey -alias client_cer -keyalg RSA -keysize 512 -keypass changeit -validity 365 -storetype JKS -keystore client_keystore -storepass changeit -v
姓名 | sslclient.wings.msn.to |
組織単位名 | Codezine |
組織名 | Wings Project |
都市名または地域名 | Chiba |
州名または地方名 | Kamagaya |
2文字の国番号 | JP |
(2)CSR(Certificate Signing Request:証明書要求)の作成
keytool -certreq -alias client_cer -file client.csr -keypass changeit -storetype JKS -keystore client_keystore -storepass changeit
(3)認証局へのCSR(証明書要求)の提出
作成したCSRファイルをテキストエディタで開き、ベリサイン社のトライアル証明書発行サービスのWeb画面の入力フォームに貼り付けます。
(4)サーバー証明書の取得
ベリサイン社トライアルサーバー証明書申し込み時にWeb画面で入力したメールアドレス宛てに、サーバー証明書のテキストが送信されて来ます。その証明書部分のテキスト(Base64エンコーディング形式)をコピーしてローカルに保存します。ここではファイル名を「client.cer」とします。
(5)証明書のインポート
- テスト用ルート証明書
- サーバー証明書
keytool -import -alias verisign_root_ca -file verisign_root_ca.cer -keypass changeit -trustcacerts -storetype JKS -keystore client_keystore -storepass changeit
keytool -import -alias client_cer -file client.cer -keypass changeit -trustcacerts -storetype JKS -keystore client_keystore -storepass changeit -v
トラストストア構築
ビートラステッド社のルート証明書をインポートします。これによりビートラステッド社ルート証明書の署名が成されている証明書は信頼されます。
(1)ルート証明書のインポート
keytool -import -alias betrusted_root_ca -file betrusted_root_ca.cer -keypass changeit -storetype JKS -keystore client_cacerts -storepass changeit
以上でクライアント認証を用いたSSL通信の環境が整いました。ではいよいよ次にSSL接続を行うサンプルコードをみてみましょう。
SSLサーバー、SSLクライアントの実装
サーバー側ソースコード
System.setProperty("javax.net.ssl.trustStore" , trustStore ); System.setProperty("javax.net.ssl.trustStorePassword", props.getProperty( "ssl.trustStore.password" ) );
トラストストア用のシステム環境変数を設定しています。証明書認証処理時にこの値に指定されたファイル名・パスワードを使用してサーバー証明書が認証されます。
// KeyStoreのロード KeyStore ks = KeyStore.getInstance( "JKS" ); char[] keystorePass = props.getProperty( "ssl.keyStore.password" ) .toCharArray(); ks.load( new FileInputStream( keyStore ) , keystorePass ); KeyManagerFactory kmf = KeyManagerFactory.getInstance( "SunX509" ); kmf.init( ks, keystorePass ); SSLContext sslContext = SSLContext.getInstance( "TLS" ); sslContext.init( kmf.getKeyManagers() , null , null ); ServerSocketFactory ssf = sslContext.getServerSocketFactory(); // サーバーソケット生成 ServerSocket srvSocket = ssf.createServerSocket( port ); // クライアント認証設定 String clientAuth = props.getProperty( "client.auth" ); if ( Boolean.valueOf(clientAuth).booleanValue() ){ ((SSLServerSocket)(srvSocket)).setNeedClientAuth( true ); }
自らのサーバー証明書を保存しているキーストアをロードして、SSLのサーバー側ソケットを作成しています。プロパティファイルによってクライアント認証を行うか否かを設定しています。
// 無限ループで待機 while( true ){ System.out.println( "--------------------------------------" ); System.out.println( "SSL接続を待機しています。" ); // クライアントからの接続待ちの状態に入る Socket client = srvSocket.accept(); System.out.println( "Clientから接続されました。" ); in = new BufferedReader( new InputStreamReader ( client.getInputStream() ) ); out = new BufferedWriter( new OutputStreamWriter( client.getOutputStream() ) ); String msg = in.readLine(); System.out.println("★★★Clientからのメッセージ:" + msg ); out.write( "Hello Client\n" ); // クライアントに文字列送信 out.flush(); closeReader( in ); closeWriter( out ); }
無限ループによって、クライアント側からの接続を待機する状態をつくっています。クライアントからの接続を受け付けるとクライアントから送信されたデータを読み込み、標準出力に出力しています。その後、クライアントに文字列「Hello Client」を送信しています。なお、本記事のサーバー側実装は複数クライアントからの接続には対応していません。複数クライアントからの接続に対応するにはマルチスレッドを使って複数のソケットを待機させる必要があります。
送信側(クライアント側)ソースコード
// トラストストア設定 System.setProperty("javax.net.ssl.trustStore" , trustStore ); System.setProperty("javax.net.ssl.trustStorePassword", props.getProperty( "ssl.trustStore.password" ) );
トラストストア用のシステム環境変数を設定しています。証明書認証処理時にこの値に指定されたトラストストアファイルを使用してサーバー側から送信されたサーバー証明書が認証されます。
// キーストアロード KeyStore ks = KeyStore.getInstance ( "JKS" ); char[] keystorePass = props.getProperty( "ssl.keyStore.password" ).toCharArray(); ks.load ( new FileInputStream( keyStore ) , keystorePass ); KeyManagerFactory kmf = KeyManagerFactory.getInstance( "SunX509" ); kmf.init( ks , keystorePass ); SSLContext ctx = SSLContext.getInstance ( "TLS" ); ctx.init( kmf.getKeyManagers() , null , null ); // SSLSocket生成 SSLSocketFactory factory = ctx.getSocketFactory(); s = (SSLSocket)factory.createSocket( serverHost , port );
Javaのキーストアをロードして、SSLのクライアント側ソケットを作成しています。
// ハンドシェイク s.startHandshake(); // サーバーからの入力を読み込み、標準出力に出力 out = new PrintWriter( s.getOutputStream() ); in = new BufferedReader( new InputStreamReader(s.getInputStream() ) ); out.write( "Hello Server\n" ); // サーバーへ送信する文字列 out.flush(); // サーバーから受けとった文字列の読み込み String answer = in.readLine(); System.out.println("★★★サーバーからのメッセージ:" + answer );
SSLのハンドシェイクを実行しています。ハンドシェイクで認証が成立しなかった場合は、例外が発生します。ハンドシェイクの結果、認証された場合はサーバー側に文字列「Hello Server」を送信しています。その後サーバーから受け取った文字列を標準出力に出力しています。
実行準備
「hosts」ファイル編集
本稿で使用した「sslserver.msn.wings.to」「sslclient.msn.wings.to」というホスト名は、テスト用のホスト名なので実在しません。名前解決ができるよう「hosts」ファイルに記述します。
- (Win2000の場合)
- (WinXPの場合)
以下のように記述します。
127.0.0.1 sslserver.wings.msn.to 127.0.0.1 sslclient.wings.msn.to
デバッグモードの設定
SSLのデバッグ出力を行いたい場合、「conf」ディレクトリにある「client.properties」ファイルと「server.properties」ファイルの「ssl.debug」プロパティをtrueに設定します。この設定により、「javax.net.debug=all」という環境変数が設定され、JDKがSSLのデバッグ文字列を出力します。
実行
- SSLサーバーの起動
- SSLクライアントの実行
server_start.bat
client_start.bat
脅威の種類 | 説明 | 防衛手段 |
盗聴 | ネットワークの途中で第三者がデータを盗み取る | SSLによる暗号化 |
なりすまし | サーバー名を偽りデータをアクセスさせる | SSLによる認証 |
不正アクセス | サーバーにアタックをかける | ファイアーウォール |
改ざん | 改変したデータを送信する | ディジタル署名 |
否認 | 自分が送ったデータを送ったと認めない事 | ディジタル署名 |
まとめ
本稿では、JDKの標準APIであるJSSEを使用して、SSL通信を実行する例を紹介しました。現代のエンジニアにとってセキュリティの知識は必須となってきていますが、SSLについてはやや敷居が高く、取っ付きにくいのも確かです。本記事がSSL環境の構築に初めて取り組むエンジニアの方の一助となれば幸いです。