wolfTips: IoTデバイスのセキュリティライブラリ wolfSSL を使いこなすためのヒント集
--- 鍵、証明書編 ---
##概要
サーバ認証のための証明書をクライアントに組み込むでクライアント側にルートCA証明書を組み込む方法を紹介しました。今回はサーバー側(認証される側)に、証明書と秘密鍵を組み込む方法を紹介します。
正規の証明書の発行には、秘密鍵と公開鍵ペア作成、認証局から署名行ってもらうなど手順が必要ですが、今回は、簡単のために wolfSSL に同梱される(wolfSSL/certs フォルダー)証明書ファイルを使用します。wolfSSL/certs フォルダーには、各ビット種類のRSA・ECDSA署名による証明書や、鍵の実験用ファイルが収められています。今回下記の、RSA 2048ビット、およびECDSA 256ビット 署名による証明書・鍵ファイルを使用します。
表2の証明書・鍵を組み込むにあたってwolfSSLの相互認証APIでも紹介しているAPIを使用します。wolfSSLの相互認証APIに、ファイルシステム有り・無しのケースに使用するAPIの表が掲載されていますので参照ください。使用するAPIは、「登録用APIの説明」で説明します。
動作確認では、証明書・鍵ファイルを読み込むサンプルのサーバープログラムを動かしてみます。
今回の記事を読むにあたっては、下記の記事を必要な場合に参照ください。
その他使うもの:ハンドシェークの様子をみるためにWiresharkでパケットをキャプチャーしてみます。
登録用 API の説明
wolfSSL APIは組込みシステムなどでファイルシステムが無いような場合にも対応できるように、有る場合、無い場合それぞれのAPIが用意されています。
###ファイルシステム有の場合
サーバー証明書を組み込むには、wolfSSL_CTX_use_certificate_file()又は wolfSSL_use_certificate_file() を使用します。一連の処理をまとめて全体で証明書を適用したい場合には、WOLFSSL_CTX_use_certificate_file()を使用します。TLSセッション単位で証明書を指定したい場合には、wolfSSL_use_certificate_file() を使用します。他のAPIも同様に "wolfSSL_CTX_xxxx" と "wolfSSL_xxxx" という命名規則に従っています。
int wolfSSL_CTX_use_certificate_file(WOLFSSL_CTX * ctx, const char *file, int format)
int wolfSSL_use_certificate_file(WOLFSSL * ssl, const char *file, int format)
ctx : wolfSSL_CTX_new()で作成した、SSLコンテクストのポインタ
ssl : wolfSSL_new()で作成した、SSL オブジェクトへのポインタ
file: 証明書を含むファイル名へのポインタ
format : WOLFSSL_FILETYPE_ASN1(DER形式)又は WOLFSSL_FILETYPE_PEM
"CTX" の無しは、セッション毎のWOLFSSL オブジェクトに対して証明書をロードします。セッション毎に証明書のロードが行われている場合、そちらの証明書が優先的に相手方に示されます。
また、鍵の登録には、wolfSSL_CTX_use_PrivateKey_file()又は wolfSSL_use_PrivateKey_file() を使用します。
int wolfSSL_CTX_use_PrivateKey_file(WOLFSSL_CTX * ctx, const char *file, int format)
int wolfSSL_use_PrivateKey_file(WOLFSSL * ssl, const unsigned char *file, int format)
ctx : wolfSSL_CTX_new()で作成した、SSLコンテクストのポインタ
ssl : wolfSSL_new()で作成した、SSL オブジェクトへのポインタ
in : 鍵を含むバッファへのポインタ
sz : 鍵バッファのサイズ
format : WOLFSSL_FILETYPE_ASN1(DER形式) 又は WOLFSSL_FILETYPE_PEM
###ファイルシステム無しの場合
相手方に提示する証明書をファイルシステム無しに組み込む場合、wolfSSL_CTX_use_certificate_buffer()又はwolfSSL_use_certificate_buffer()を使用します。
int wolfSSL_CTX_use_certificate_buffer(WOLFSSL_CTX * ctx, const unsigned char *in, long, sz, int format)
int wolfSSL_use_certificate_buffer(WOLFSSL * ssl, const unsigned char *in, long sz, int format)
in : 証明書を含むバッファへのポインタ
sz : 証明書バッファのサイズ
また、鍵の登録には、wolfSSL_CTX_use_PrivateKey_file()又はwolfSSL_use_PrivateKey_file()を使用します。
int wolfSSL_CTX_use_PrivateKey_buffer(WOLFSSL_CTX * ctx, const char *in, long sz, int format)
int wolfSSL_use_PrivateKey_buffer(WOLFSSL * ssl, const unsigned char *in, long sz, int format)
in : 鍵を含むバッファへのポインタ
sz : 鍵バッファのサイズ
##サンプルのサーバープログラム
証明書、秘密鍵をファイルから読み込むサーバープログラムを動かしてみます。
サンプルのサーバープログラムのダウンロード、コンパイルに関して、こちらをクリックください
-
サンプルのサーバープログラムをダウンロードする
コードは、[こちら](https://github.com/wolfssl-jp/examples)の GitHub からクローンすることができます。Certs2というフォルダーに含まれます。 ``` $ git clone https://github.com/wolfssl-jp/examples Cloning into 'examples'... remote: Enumerating objects: 29, done. remote: Counting objects: 100% (29/29), done. remote: Compressing objects: 100% (25/25), done. remote: Total 29 (delta 4), reused 23 (delta 2), pack-reused 0 Unpacking objects: 100% (29/29), done. $ ls /path/to/examples/ Cert1 Cert2 README.md $ cd /path/to/examples/Cert2 $ ls Makefile certs sample_server.c
-
サンプルサーバーをコンパイルする
``` $ make gcc -c -g -Wall -MMD -MP sample_server.c gcc -o wolfssl_sample_server sample_server.o -lwolfssl ```
-
サンプルのクライアントプログラムをコンパイルする
別のウィンドウで[サンプルのクライアントプログラム](https://github.com/wolfssl-jp/examples/tree/master/Cert1)をコンパイルします。このサンプルクライアントは、[サーバ認証のための証明書をクライアントに組み込む](https://qiita.com/miyaH/items/e441dc3e9b7bcf32ea75)で作成したサンプルクライアントです。Cert1というフォルダー含まれます。 ``` $ cd /path/to/examples/Cert1 $ ls Makefile certs sample_client.c sample_client.h $ make gcc -c -g -Wall -MMD -MP sample_client.c gcc -o wolfssl_sample_client sample_client.o -lwolfssl ```
###サンプルのサーバープログラムの処理内容
次のような処理を行っています。
- コンテクスト WOLFSSL_CTX を確保
- 全体処理用にwolfSSL_CTX_use_certificate/PrivateKey_file() でRSA証明書とRSA秘密鍵ロード
- TCPソケットの処理を行い、クライアントからの接続をacceptで待つ
- WOLFSSL を確保し、確保したソケットを登録
- TLSセッション用にwolfSSL_use_certificate/Private_file() でECC証明書とECC秘密鍵をロード
- wolfSSL_accept()を呼び出し、TLSハンドシェークを開始
- wolfSSL_read()/wolfSSL_write()でメッセージを送受信
- リソース解放処理
今回は実験のため、"wolfSSL_CTX_use_certificate_file/wolfSSL_CTX_PrivateKey_file" と "wolfSSL_use_certificate_file/wolfSSL_use_PrivateKey_file"を両方使用していますが、通常はどちらか一方を使用します。今回は実験で分かりやすくするために、前者でRSA署名、後者でECC署名を使用しています。
###サンプルのサーバープログラムを実行する
-
サンプルサーバーを起動する
``` $ ./wolfssl_sample_server Waiting for a connection... ```
-
別ウィンドウでサンプルクライアントを起動する
``` $ ./wolfssl_sample_client Loaded certs files...OK! please hit any key to connect azure.microsoft.com > ```
-
サンプルクライアントを接続する
サンプルクライアントは、azure.microsoft.com、aws.amazon.com そしてローカルのサーバーに何かキーが押されると順次接続します。azure.microsoft.com、aws.amazon.com に接続するためには[ブラウザを使ってCA証明書を持ってくる](https://qiita.com/kj1/items/9fb7e0f6e2eebef935d5) を参考にルートCA証明書を Cert1/certs フォルダーに予め配置しておきます。 ``` $ ./wolfssl_sample_client Loaded certs files...OK! please hit any key to connect azure.microsoft.com ... please hit any key to connect 127.0.0.1 > I hear ya fa shizzle! ```
サンプルコードをみるには、ここをクリックしてください
int main()
{
int sockfd;
int connd;
struct sockaddr_in servAddr;
struct sockaddr_in clientAddr;
socklen_t size = sizeof(clientAddr);
char buff[256];
size_t len;
int shutdown = 0;
int ret;
const char* reply = "I hear ya fa shizzle!\n";
/* declare wolfSSL objects */
WOLFSSL_CTX* ctx;
WOLFSSL* ssl;
/* Initialize wolfSSL */
wolfSSL_Init();
/* Create a socket that uses an internet IPv4 address,
* Sets the socket to be stream based (TCP),
* 0 means choose the default protocol. */
if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
fprintf(stderr, "ERROR: failed to create the socket\n");
return -1;
}
/*1. Create and initialize WOLFSSL_CTX */
if ((ctx = wolfSSL_CTX_new(wolfTLSv1_2_server_method())) == NULL) {
fprintf(stderr, "ERROR: failed to create WOLFSSL_CTX\n");
return -1;
}
/*2. Load server certificates into WOLFSSL_CTX */
if (wolfSSL_CTX_use_certificate_file(ctx, CERT_FILE, SSL_FILETYPE_PEM)
!= SSL_SUCCESS) {
fprintf(stderr, "ERROR: failed to load %s, please check the file.\n",
CERT_FILE);
return -1;
}
/*2. Load server key into WOLFSSL_CTX */
if (wolfSSL_CTX_use_PrivateKey_file(ctx, KEY_FILE, SSL_FILETYPE_PEM)
!= SSL_SUCCESS) {
fprintf(stderr, "ERROR: failed to load %s, please check the file.\n",
KEY_FILE);
return -1;
}
/* Initialize the server address struct with zeros */
memset(&servAddr, 0, sizeof(servAddr));
/* Fill in the server address */
servAddr.sin_family = AF_INET; /* using IPv4 */
servAddr.sin_port = htons(DEFAULT_PORT); /* on DEFAULT_PORT */
servAddr.sin_addr.s_addr = INADDR_ANY; /* from anywhere */
/*3. Bind the server socket to our port */
if (bind(sockfd, (struct sockaddr*)&servAddr, sizeof(servAddr)) == -1) {
fprintf(stderr, "ERROR: failed to bind\n");
return -1;
}
/*3. Listen for a new connection, allow 5 pending connections */
if (listen(sockfd, 5) == -1) {
fprintf(stderr, "ERROR: failed to listen\n");
return -1;
}
/* Continue to accept clients until shutdown is issued */
while (!shutdown) {
printf("Waiting for a connection...\n");
/*3. Accept client connections */
if ((connd = accept(sockfd, (struct sockaddr*)&clientAddr, &size))
== -1) {
fprintf(stderr, "ERROR: failed to accept the connection\n\n");
return -1;
}
/*4. Create a WOLFSSL object */
if ((ssl = wolfSSL_new(ctx)) == NULL) {
fprintf(stderr, "ERROR: failed to create WOLFSSL object\n");
return -1;
}
/*4. Attach wolfSSL to the socket */
wolfSSL_set_fd(ssl, connd);
/*5. Load server certificates into WOLFSSL */
if (wolfSSL_use_certificate_file(ssl, ECC_CERT_FILE, SSL_FILETYPE_PEM)
!= SSL_SUCCESS) {
fprintf(stderr, "ERROR: failed to load %s, please check the file.\n",
CERT_FILE);
return -1;
}
/*5. Load server certificates into WOLFSSL */
if (wolfSSL_use_PrivateKey_file(ssl, ECC_KEY_FILE, SSL_FILETYPE_PEM)
!= SSL_SUCCESS) {
fprintf(stderr, "ERROR: failed to load %s, please check the file.\n",
KEY_FILE);
return -1;
}
/*6. Establish TLS connection */
ret = wolfSSL_accept(ssl);
if (ret != SSL_SUCCESS) {
fprintf(stderr, "wolfSSL_accept error = %d\n",
wolfSSL_get_error(ssl, ret));
return -1;
}
printf("Client connected successfully\n");
/*7. Read the client data into our buff array */
memset(buff, 0, sizeof(buff));
if (wolfSSL_read(ssl, buff, sizeof(buff)-1) == -1) {
fprintf(stderr, "ERROR: failed to read\n");
return -1;
}
/* Print to stdout any data the client sends */
printf("Client: %s\n", buff);
/* Check for server shutdown command */
if (strncmp(buff, "shutdown", 8) == 0) {
printf("Shutdown command issued!\n");
shutdown = 1;
}
/* Write our reply into buff */
memset(buff, 0, sizeof(buff));
memcpy(buff, reply, strlen(reply));
len = strnlen(buff, sizeof(buff));
/*7. Reply back to the client */
if (wolfSSL_write(ssl, buff, len) != len) {
fprintf(stderr, "ERROR: failed to write\n");
return -1;
}
/*8. Cleanup after this connection */
wolfSSL_free(ssl); /* Free the wolfSSL object */
close(connd); /* Close the connection to the client */
}
printf("Shutdown complete\n");
/*8. Cleanup and return */
wolfSSL_CTX_free(ctx); /* Free the wolfSSL context object */
wolfSSL_Cleanup(); /* Cleanup the wolfSSL environment */
close(sockfd); /* Close the socket listening for clients */
return 0; /* Return reporting a success */
}
実験
まずは、オリジナルのコードで相手方に示すサーバー証明書を WireShark で確認します。次に一部のコードをコメントアウトし、相手方に示すサーバー証明書の違いを見てみます。
オリジナルのコードで確認
サンプルサーバーから送られているサーバー証明書は、オリジナルのコードでは ECDSA署名(server-ecc.pem)されたものであることが分かります。これは、wolfSSL_use_certificate_file()で読み込んだ証明書になります。
一部のコードをコメントアウト
次に、サンプルサーバー中の wolfSSL_use_certificate_file(), wolfSSL_use_PrivateKey_file() をコメントアウトし実行してみます。
/*if (wolfSSL_use_certificate_file(ssl, ECC_CERT_FILE, SSL_FILETYPE_PEM)
!= SSL_SUCCESS) {
fprintf(stderr, "ERROR: failed to load %s, please check the file.\n",
CERT_FILE);
return -1;
}*
/* Load server key into WOLFSSL_CTX */
/*if (wolfSSL_use_PrivateKey_file(ssl, ECC_KEY_FILE, SSL_FILETYPE_PEM)
!= SSL_SUCCESS) {
fprintf(stderr, "ERROR: failed to load %s, please check the file.\n",
KEY_FILE);
return -1;
}*/
提示されている証明書はRSA署名(server-cert.pem)ものでwolfSSL_CTX_use_certificate_file()でロードされたものに変わります。
このように、セッション毎に"wolfSSL_use_certificate_xxxx()" で証明書が読み込まれてい場合は、そちらが優先されます。セッション毎に証明書を変更したい場合などに使用出来ます。
付け加えますと、サンプルのクライアントプログラムは、wolfSSL_CTX_load_verify_locations_ex()で./certs フォルダー内に含まれる RSA CA証明書、ECC CA証明書の両方を読み込んでいるため、サーバーからRSA・ECCどちらのサーバー証明書が示されても認証することができます。
##まとめ
今回は wolfSSL のAPIを使ってサーバー側に証明書及び鍵を登録する方法を紹介しました。セッション毎に証明書を変更したい場合や、ファイルシステムの有り無しなど、IoTデバイスの用途にあわせてご利用下さい。詳細なドキュメント、製品情報については、wolfSSLの下記を参照してください。
ドキュメント:https://www.wolfssl.jp/docs/
英語サイト:https://www.wolfssl.com/
日本語サイト:https://www.wolfssl.jp/
Twitter: https://twitter.com/wolfSSL_Japan/