| 前へ << SSL/TLS の導入 (4) | SSL/TLS でアクセスしてみよう (2) >> 次へ |
| SSL 有効 | SSL 無効 |
![]() |
![]() |
SSL を使用することによって、以下の効果があります。
SSL と TLS の歴史は以下の通りです。
なお、SSL とは別に、S-HTTP (Secure HyperText Transfer Protocol) というプロトコルがありました。 これは Enterprise Integration Technologies 社が提唱したもので、 RFC 2660 として S-HTTP/1.4 が定義されています。 S-HTTP は SSL/TLS とは異なり、HTTP に特化したつくりになっています。
初期のころは SSL と S-HTTP のどちらが主流になるかわからなかったそうです。 ただし、現在では明らかに勝負はつきました。 現時点で S-HTTP を実装しているブラウザや web サーバはありません。
ほとんどの場合は
% gzip -dc OpenSSL-0.9.7b.tar.gz | tar xf - % cd OpenSSL-0.9.7b % ./config % make % make installで OK なはずです。
% cc -o https-client https-client.c -lssl -lcryptoとして下さい。Solaris ならば
% cc -o https-client https-client.c -lresolv -lsocket -lnsl -lssl -lcryptoなど。
また、/usr/lib/ 以外のディレクトリに libssl が置いてある場合は -L /usr/local/lib や -L /usr/local/ssl/lib などと、ライブラリの場所を指定することもお忘れなく。
引数なしで実行すると、 https://www2.ggn.net/cgi-bin/ssl にアクセスし、 結果を表示します。
% ./https-client
サーバからのレスポンス
HTTP/1.1 200 OK
Date: Tue, 10 Jun 2003 19:03:09 GMT
Server: Apache/1.3.26 (Unix) GGN-MM/1.3.1 mod_ssl/2.8.10 OpenSSL/0.9.6d
Connection: close
Content-Type: text/html
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2//EN">
<HTML><HEAD><TITLE>SSL Test</TITLE></HEAD>
<BODY BGCOLOR="#FFFFFF" TEXT="#000000" LINK="#0000FF" VLINK="#4040FF" ALINK="#2020FF">
<P>Hello, user from 220.145.196.26!</P>
<P>You have successfully connected using SSL (TLSv1).
You received this document using the 168-bit EDH-RSA-DES-CBC3-SHA cipher.</P>
<P>I see you're not using <A HREF="http://lynx.browser.org/">Lynx</A>.
You should give it a try.</P>
</BODY></HTML>
また、
% ./https-client host /path/file.htmlと、引数でホスト名とパスを指定すると、https://host/path/file.html にアクセスします。
20: #include <openssl/crypto.h> 21: #include <openssl/ssl.h> 22: #include <openssl/err.h> 23: #include <openssl/rand.h>必要なヘッダファイルを include します。これらのヘッダファイルはすべて OpenSSL に同梱されています。
51: servhost = gethostbyname(host);
52: if ( servhost == NULL ){
53: fprintf(stderr, "[%s] から IP アドレスへの変換に失敗しました。\n", host);
54: exit(1);
55: }
56:
57: bzero((char *)&server, sizeof(server));
58: server.sin_family = AF_INET;
59:
60: bcopy(servhost->h_addr, (char *)&server.sin_addr, servhost->h_length);
61:
62: /* ポート番号取得 */
63: service = getservbyname("https", "tcp");
64: if ( service != NULL ){
65: server.sin_port = service->s_port;
66: } else {
67: /* 取得できなかったら、ポート番号を 443 に決め打ち */
68: server.sin_port = htons(443);
69: }
70:
71: s = socket(AF_INET, SOCK_STREAM, 0);
72: if ( s < 0 ){
73: fprintf(stderr, "ソケットの生成に失敗しました。\n");
74: exit(1);
75: }
76:
77: if ( connect(s, (struct sockaddr*) &server, sizeof(server)) == -1 ){
78: fprintf(stderr, "connect に失敗しました。\n");
79: exit(1);
80: }
84: SSL_load_error_strings(); 85: SSL_library_init();SSL_load_error_strings() で OpenSSL のエラー文字列を読み込みます。 OpenSSL にはどのソースの何行目でどういうエラーが起こったのかを簡単に知る関数があるのですが、 このエラー内容は数値で管理されています。ただ、エラーが発生しても、
error:0406C06E:lib(4):func(108):reason(110)というわかりづらいエラー詳細しか得ることができません。 しかしあらかじめ SSL_load_error_strings() を実行しておけば、
error:0406C06E:rsa routines:RSA_padding_add_PKCS1_type_1:data too large for key sizeという (比較的) わかりやすいエラーメッセージが表示されるようになります。
その次の SSL_library_init() は SSL/TLS の初期化を行います。 これを呼ぶことで暗号方式やメッセージダイジェスト関数などが自動的に登録されます。
39: SSL_CTX *ctx;
86: ctx = SSL_CTX_new(SSLv23_client_method());
87: if ( ctx == NULL ){
88: ERR_print_errors_fp(stderr);
89: exit(1);
90: }
使用するプロトコルを決めます。ここでは
SSLv2、SSLv3、TLSv1 のいずれかを使用するため、
SSLv23_client_method を選んでいます。
もし SSLv2_client_method、SSLv3_client_method
TLSv1_client_method を使うと、それぞれ SSLv2・SSLv3・TLSv1
を使うことになります。
ついでにここでエラー情報を取得する方法を解説しておきます。 OpenSSL では全てのエラー情報を ERR_print_errors_fp() や ERR_error_string() などの関数で取得することができます。 このサンプルではエラーが発生すると標準エラー出力にエラー情報を出力し、 終了します。
例えば SSL_library_init() を呼ばずに SSL_CTX_new() を呼ぶとエラーとなりますが、その際以下のようなメッセージが標準エラー出力に吐かれます。
93652:error:140A90A1:SSL routines:SSL_CTX_new:library has no ciphers:ssl_lib.c:1365:
92: ssl = SSL_new(ctx);
93: if ( ssl == NULL ){
94: ERR_print_errors_fp(stderr);
95: exit(1);
96: }
SSL_new で、SSL_CTX 構造体にセットしたプロトコルや暗号化方式を元に、
コネクションを管理する SSL 構造体を新たに生成します。
SSL はサーバとのコネクションを管理するもの、 SSL_CTX は SSL/TLS における暗号化や認証方法などを管理するもの、と考えてください。
SSL 構造体と SSL_CTX 構造体の意味あいがわかりにくいかもしれませんので、 例をあげておきます。もし、2つの SSL 対応 web サーバに同時に接続するとします。 両サーバにクライアント側が提示するプロトコル・暗号化方式が同じものでよいならば、
SSL *ssl_1, *ssl_2; SSL_load_error_strings(); SSL_library_init(); SSL_CTX *ctx = SSL_CTX_new(SSLv23_client_method()); ssl_1 = SSL_new(ctx); ssl_2 = SSL_new(ctx);などと、SSL_CTX は 1つで済むわけです。 しかしコネクションは 2つ張るので SSL 構造体は 2つ必要になります。
98: ret = SSL_set_fd(ssl, s);
99: if ( ret == 0 ){
100: ERR_print_errors_fp(stderr);
101: exit(1);
102: }
SSL_set_fd() でソケットと SSL の構造体を結びつけます。
105: RAND_poll();
106: while ( RAND_status() == 0 ){
107: unsigned short rand_ret = rand() % 65536;
108: RAND_seed(&rand_ret, sizeof(rand_ret));
109: }
まず、このコードは /dev/random や /dev/urandom が存在する最近の UNIX 系 OS では必要ありません
(別に残しておいても害はありません)。
なぜなら、この後に続く SSL_connect() を実行したときに、自動的に適切な初期化が行われるからです。
以下、/dev/random や /dev/urandom が存在しない環境向けの説明です。 /dev/random・/dev/urandom が存在しない環境で上記のコードを省略すると (上記のコードなしで SSL_connect() を実行すると)、
94779:error:24064064:random number generator:SSLEAY_RAND_BYTES:PRNG not seeded:md_rand.c:506: You need to read the OpenSSL FAQ, http://www.openssl.org/support/faq.htmlというエラーになります。
ここで PRNG というものを説明しておかなくてはなりません。 暗号において乱数は非常に重要です。周期性が短いなどの質の低い乱数を使っていると、 暗号アルゴリズムには問題がなくても平文を解読されてしまうこともあります。 乱数を生成するものを PRNG (Pseudo Random Number Generator: 疑似乱数発生器) と呼びます。 OpenSSL は独自の PRNG を実装しています。
PRNG は乱数を生成するアルゴリズムでしかありませんので、 もし初期状態が同じなら全く同じ乱数を返します (OS ブート直後に 2,56,9,38 という乱数を返したとすると、 次回の OS ブート時にも 2,56,9,38 という同じ乱数が返ってくるということ)。 常に異なる値を返すために、はじめに「種」(seed、乱数の元ネタとなるもの) を与えなくてはいけません。 当然ながら毎回同じ種を与えてしまっては、結局 PRNG は同じ乱数を返しますので、 常に異なる種をセットする必要があります。 つまり、「乱数を生成するための種」を生成するのに乱数を使う、ということになります。
105: RAND_poll();RAND_poll() は PRNG に自動的に種を与える関数ですが、 /dev/random や /dev/urandom が存在しない場合、種の生成に失敗してしまいます。
106: while ( RAND_status() == 0 ){
107: unsigned short rand_ret = rand() % 65536;
108: RAND_seed(&rand_ret, sizeof(rand_ret));
109: }
そこで RAND_status() を使って十分な種が準備できたかを判定し、
もし種が足りてない場合は rand() から取得した乱数の下位 2バイトを取り、
RAND_seed() を使ってそれを PRNG に追加しています。
なお、rand() は OS にもよりますが、質の低い乱数生成関数です (参考: FreeBSD QandA 2228)。 しかし ANSI C で規定された関数なので、ここではあえて rand() を使っています。 あと、実装にもよりますが、 rand() の下位バイトを抽出するのはよくないケースがあります。 (周期が短かったり、最下位ビットが 0・1 の繰り返しだったり。参考: 良い乱数・悪い乱数)。 しかしここではサンプルということでご容赦いただきたいと思います。
/dev/random・/dev/urandom が存在するような「今どき」の OS を使うことをお勧めしておきます。 ちなみに /dev/random・/dev/urandom は OS が観測したランダムノイズを元に生成される乱数です。 ここで言うランダムノイズは、 例えばマウスの移動量やキーボードの入力タイミングなどをイメージするとよいのではないでしょうか (実際にはプロセスの状態とか、デバイスとの入出力タイミングなの値が使われているのではないかと思います)。
112: ret = SSL_connect(ssl);
113: if ( ret != 1 ){
114: ERR_print_errors_fp(stderr);
115: exit(1);
116: }
SSL_connect を呼ぶことで、自動的にサーバとハンドシェイクが行われます。
具体的には、以下のことがこの時点で決定します。
119: sprintf(request,
120: "GET %s HTTP/1.0\r\n"
121: "Host: %s\r\n\r\n",
122: path, host);
123:
124: ret = SSL_write(ssl, request, strlen(request));
125: if ( ret < 1 ){
126: ERR_print_errors_fp(stderr);
127: exit(1);
128: }
HTTPS であっても、送信するリクエストは通常の HTTP と全く同じです。
送信するための関数は、HTTP では
write(s, request, strlen(request));でしたが、HTTPS では
SSL_write(ssl, request, strlen(request));となります。
131: while (1){
132: char buf[BUF_LEN];
133: int read_size;
134: read_size = SSL_read(ssl, buf, sizeof(buf)-1);
135:
136: if ( read_size > 0 ){
137: buf[read_size] = '\0';
138: write(1, buf, read_size);
139: } else if ( read_size == 0 ){
140: /* FIN 受信 */
141: break;
142: } else {
143: ERR_print_errors_fp(stderr);
144: exit(1);
145: }
146: }
こちらも HTTP では
read_size = read(s, buf, sizeof(buf)-1);だったものが、
read_size = SSL_read(ssl, buf, sizeof(buf)-1);となっているだけで、その他は HTTP クライアントと同じです。
148: ret = SSL_shutdown(ssl);
149: if ( ret != 1 ){
150: ERR_print_errors_fp(stderr);
151: exit(1);
152: }
153: close(s);
コネクションを切断します。まずは SSL/TLS のコネクションを切り、
その後ソケットのコネクションを切っています。
155: SSL_free(ssl); 156: SSL_CTX_free(ctx); 157: ERR_free_strings();確保したメモリを開放します。
write(s, request, strlen(request)); read_size = read(s, buf, sizeof(buf)-1);でした。一方、HTTPS クライアントでは
SSL_write(ssl, request, strlen(request)); read_size = SSL_read(ssl, buf, sizeof(buf)-1);となります。
SSL/TLS 独自の初期化・設定は必要ですが、 本質的には read を SSL_read に、 write を SSL_write に変更し、 第一引数のディスクリプタ (int) を SSL 構造体に変更するだけで SSL/TLS 化は完了です。 SSL/TLS 化しても、HTTP プロトコル部分は一切変更する必要がありません。 SSL/TLS は HTTP から完全に独立しているのです。
つまり、SSL/TLS は TCP とアプリケーション層の間に暗号化・認証のレイヤを挿入する技術なのです。
| 通常のネットワーク | SSL/TLS を利用したネットワーク | |||||||||||||||
|
→ |
|
昔は HTTP に SSL を組み合わせた HTTPS しか普及していなかったのですが、 最近は POP・FTP・NNTP・IMAP などに SSL/TLS 機能を組み込んだ実装が普及してきました。
| 前へ << SSL/TLS の導入 (4) | SSL/TLS でアクセスしてみよう (2) >> 次へ |
ご意見・ご指摘は Twitter: @68user までお願いします。