C言語のOpenSSLでSSLクライアント・サーバを教えて!
こういった悩みにお答えします.
本記事の信頼性
- リアルタイムシステムの研究歴12年.
- 東大教員の時に,英語でOS(Linuxカーネル)の授業.
- 2012年9月~2013年8月にアメリカのノースカロライナ大学チャペルヒル校(UNC)コンピュータサイエンス学部で客員研究員として勤務.C言語でリアルタイムLinuxの研究開発.
- プログラミング歴15年以上,習得している言語: C/C++,Python,Solidity/Vyper,Java,Ruby,Go,Rust,D,HTML/CSS/JS/PHP,MATLAB,Verse(UEFN), Assembler (x64,ARM).
- 東大教員の時に,C++言語で開発した「LLVMコンパイラの拡張」,C言語で開発した独自のリアルタイムOS「Mcube Kernel」をGitHubにオープンソースとして公開.
- 2020年1月~現在はアメリカのノースカロライナ州チャペルヒルにあるGuarantee Happiness LLCのCTOとしてECサイト開発やWeb/SNSマーケティングの業務.2022年6月~現在はアメリカのノースカロライナ州チャペルヒルにあるJapanese Tar Heel, Inc.のCEO兼CTO.
- 最近は自然言語処理AIとイーサリアムに関する有益な情報発信に従事.
- (AI全般を含む)自然言語処理AIの論文の日本語訳や,AIチャットボット(ChatGPT,Auto-GPT,Gemini(旧Bard)など)の記事を50本以上執筆.アメリカのサンフランシスコ(広義のシリコンバレー)の会社でプロンプトエンジニア・マネージャー・Quality Assurance(QA)の業務委託の経験あり.
- (スマートコントラクトのプログラミングを含む)イーサリアムや仮想通貨全般の記事を200本以上執筆.イギリスのロンドンの会社で仮想通貨の英語の記事を日本語に翻訳する業務委託の経験あり.
こういった私から学べます.
C言語を独学で習得することは難しいです.
私にC言語の無料相談をしたいあなたは,公式LINE「ChishiroのC言語」の友だち追加をお願い致します.
私のキャパシティもあり,一定数に達したら終了しますので,今すぐ追加しましょう!
独学が難しいあなたは,元東大教員がおすすめするC言語を学べるオンラインプログラミングスクール5社で自分に合うスクールを見つけましょう.後悔はさせません!
目次
【C言語】OpenSSLでSSLクライアント・サーバ
C言語のOpenSSLでSSLクライアント・サーバを紹介します.
OpenSSLのライブラリ関数の一覧はこちらを参照して下さい.(関数名に大文字を利用するものが多いです.)
OpenSSLでSSLクライアント・サーバのコード
OpenSSLでSSLクライアント・サーバのコードは,以下の4ファイルになります.
- common.h,common.c:クライアント・サーバの共通コード
- openssl_client.c:クライアントのコード
- openssl_server.c:サーバのコード
クライアントとサーバがポート番号65432でローカル通信するコードになります.
これらのコードとMakefileは,こちらからダウンロードできます.
ダウンロードしたら以下の手順で解凍してmakeでビルドして下さい.
1 2 3 4 5 |
$ unzip openssl_client_server.zip $ cd openssl_client_server $ make gcc -o openssl_client openssl_client.c common.c -Wall -lcrypto -lssl gcc -o openssl_server openssl_server.c common.c -Wall -lcrypto -lssl |
参考までに,common.h,common.c,openssl_client.c,openssl_server.cは以下になります.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 |
/* * Author: Hiroyuki Chishiro * License: 2-Clause BSD */ #ifndef __COMMON_H__ #define __COMMON_H__ #include <stdbool.h> struct ssl_client { int fd; SSL *ssl; BIO *rbio; BIO *wbio; char *write_buf; size_t write_len; char *encrypt_buf; size_t encrypt_len; }; enum ssl_mode { SSL_MODE_SERVER, SSL_MODE_CLIENT }; enum ssl_status { SSL_STATUS_OK, SSL_STATUS_WANT_IO, SSL_STATUS_FAIL }; extern SSL_CTX *ctx; extern struct ssl_client client; #define BUFSIZE 1024 static inline bool ssl_client_want_write(struct ssl_client *cp) { return cp->write_len > 0; } void ssl_client_init(struct ssl_client *p, int fd, enum ssl_mode mode); void ssl_client_cleanup(struct ssl_client *p); void send_unencrypted_bytes(const char *buf, size_t len); void queue_encrypted_bytes(const char *buf, size_t len); enum ssl_status get_ssl_status(SSL *ssl, int n); enum ssl_status do_ssl_handshake(void); int on_read_cb(char *src, size_t len); int do_encrypt(void); void do_stdin_read(void); int do_sock_read(void); int do_sock_write(void); void ssl_init(const char *certfile, const char *keyfile); #endif |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 |
/* * Author: Hiroyuki Chishiro * License: 2-Clause BSD */ #define _GNU_SOURCE #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <openssl/err.h> #include <openssl/ssl.h> #include "common.h" SSL_CTX *ctx; struct ssl_client client; void ssl_client_init(struct ssl_client *p, int fd, enum ssl_mode mode) { memset(p, 0, sizeof(struct ssl_client)); p->fd = fd; p->rbio = BIO_new(BIO_s_mem()); p->wbio = BIO_new(BIO_s_mem()); p->ssl = SSL_new(ctx); if (mode == SSL_MODE_SERVER) { SSL_set_accept_state(p->ssl); } else if (mode == SSL_MODE_CLIENT) { SSL_set_connect_state(p->ssl); } else { fprintf(stderr, "Error: unknown SSL_MODE: %d\n", mode); exit(1); } SSL_set_bio(p->ssl, p->rbio, p->wbio); } void ssl_client_cleanup(struct ssl_client *p) { SSL_free(p->ssl); free(p->write_buf); free(p->encrypt_buf); } enum ssl_status get_ssl_status(SSL *ssl, int n) { switch (SSL_get_error(ssl, n)) { case SSL_ERROR_NONE: return SSL_STATUS_OK; case SSL_ERROR_WANT_WRITE: case SSL_ERROR_WANT_READ: return SSL_STATUS_WANT_IO; case SSL_ERROR_ZERO_RETURN: case SSL_ERROR_SYSCALL: default: return SSL_STATUS_FAIL; } } void send_unencrypted_bytes(const char *buf, size_t len) { size_t size = client.encrypt_len + len; if (size > 0) { if ((client.encrypt_buf = realloc(client.encrypt_buf, size)) == NULL) { fprintf(stderr, "Error: cannot allocate memory %zu bytes\n", size); exit(1); } memcpy(client.encrypt_buf + client.encrypt_len, buf, len); client.encrypt_len += len; } else { client.write_buf = NULL; } } void queue_encrypted_bytes(const char *buf, size_t len) { size_t size = client.write_len + len; if (size > 0) { if ((client.write_buf = realloc(client.write_buf, size)) == NULL) { fprintf(stderr, "Error: cannot allocate memory %zu bytes\n", size); exit(1); } memcpy(client.write_buf + client.write_len, buf, len); client.write_len += len; } else { client.write_buf = NULL; } } enum ssl_status do_ssl_handshake(void) { char buf[BUFSIZE]; enum ssl_status status; int n; n = SSL_do_handshake(client.ssl); status = get_ssl_status(client.ssl, n); if (status == SSL_STATUS_WANT_IO) { do { if ((n = BIO_read(client.wbio, buf, sizeof(buf))) > 0) { queue_encrypted_bytes(buf, n); } else if (!BIO_should_retry(client.wbio)) { return SSL_STATUS_FAIL; } } while (n > 0); } return status; } int on_read_cb(char *src, size_t len) { char buf[BUFSIZE]; enum ssl_status status; int n; while (len > 0) { if ((n = BIO_write(client.rbio, src, len)) <= 0) { return -1; } src += n; len -= n; if (!SSL_is_init_finished(client.ssl)) { if (do_ssl_handshake() == SSL_STATUS_FAIL) { return -1; } if (!SSL_is_init_finished(client.ssl)) { return 0; } } do { if ((n = SSL_read(client.ssl, buf, sizeof(buf))) > 0) { printf("%.*s", (int) n, buf); } } while (n > 0); if ((status = get_ssl_status(client.ssl, n)) == SSL_STATUS_WANT_IO) { do { if ((n = BIO_read(client.wbio, buf, sizeof(buf))) > 0) { queue_encrypted_bytes(buf, n); } else if (!BIO_should_retry(client.wbio)) { return -1; } } while (n > 0); } else if (status == SSL_STATUS_FAIL) { return -1; } } return 0; } int do_encrypt(void) { char buf[BUFSIZE]; enum ssl_status status; int n; if (!SSL_is_init_finished(client.ssl)) { return 0; } while (client.encrypt_len > 0) { n = SSL_write(client.ssl, client.encrypt_buf, client.encrypt_len); status = get_ssl_status(client.ssl, n); if (n > 0) { if ((size_t) n < client.encrypt_len) { memmove(client.encrypt_buf, client.encrypt_buf + n, client.encrypt_len - n); } client.encrypt_len -= n; if (client.encrypt_len > 0) { if ((client.encrypt_buf = realloc(client.encrypt_buf, client.encrypt_len)) == NULL) { fprintf(stderr, "Error: cannot allocate memory %zu bytes\n", client.encrypt_len); exit(1); } } do { if ((n = BIO_read(client.wbio, buf, sizeof(buf))) > 0) { queue_encrypted_bytes(buf, n); } else if (!BIO_should_retry(client.wbio)) { return -1; } } while (n > 0); } if (status == SSL_STATUS_FAIL) { return -1; } if (n == 0) { break; } } return 0; } void do_stdin_read(void) { char buf[BUFSIZE]; ssize_t n; if ((n = read(STDIN_FILENO, buf, sizeof(buf))) > 0) { send_unencrypted_bytes(buf, (size_t) n); } } int do_sock_read(void) { char buf[BUFSIZE]; ssize_t n; if ((n = read(client.fd, buf, sizeof(buf))) > 0) { return on_read_cb(buf, (size_t) n); } else { return -1; } } int do_sock_write(void) { ssize_t n; if ((n = write(client.fd, client.write_buf, client.write_len)) > 0) { if ((size_t) n < client.write_len) { memmove(client.write_buf, client.write_buf + n, client.write_len - n); } client.write_len -= n; if (client.write_len > 0) { if ((client.write_buf = realloc(client.write_buf, client.write_len)) == NULL) { fprintf(stderr, "Error: cannot allocate memory %zu bytes\n", client.write_len); exit(1); } } else { client.write_buf = NULL; } return 0; } else { return -1; } } void ssl_init(const char *certfile, const char *keyfile) { SSL_library_init(); OpenSSL_add_all_algorithms(); SSL_load_error_strings(); ERR_load_crypto_strings(); if ((ctx = SSL_CTX_new(SSLv23_method())) == NULL) { perror("SSL_CTX_new"); exit(1); } if (certfile && keyfile) { if (SSL_CTX_use_certificate_file(ctx, certfile, SSL_FILETYPE_PEM) != 1) { fprintf(stderr, "Error: SSL_CTX_use_certificate_file()\n"); ERR_print_errors_fp(stderr); exit(2); } if (SSL_CTX_use_PrivateKey_file(ctx, keyfile, SSL_FILETYPE_PEM) != 1) { fprintf(stderr, "Error: SSL_CTX_use_PrivateKey_file()\n"); ERR_print_errors_fp(stderr); exit(3); } if (SSL_CTX_check_private_key(ctx) != 1) { fprintf(stderr, "Error: SSL_CTX_check_private_key()\n"); ERR_print_errors_fp(stderr); exit(4); } } SSL_CTX_set_options(ctx, SSL_OP_ALL | SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3); } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 |
/* * Author: Hiroyuki Chishiro * License: 2-Clause BSD */ #define _GNU_SOURCE #include <stdio.h> #include <stdlib.h> #include <string.h> #include <stdbool.h> #include <unistd.h> #include <arpa/inet.h> #include <poll.h> #include <openssl/ssl.h> #include "common.h" #define PORT 65432 int main(void) { char *host = "127.0.0.1"; int sockfd; struct sockaddr_in addr; struct pollfd fdset[2]; int nready, revents; if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) { perror("socket"); exit(1); } memset(&addr, 0, sizeof(addr)); addr.sin_family = AF_INET; addr.sin_port = htons(PORT); if (inet_pton(AF_INET, host, &addr.sin_addr) <= 0) { perror("inet_pton"); exit(2); } if (connect(sockfd, (struct sockaddr *) &addr, sizeof(addr)) < 0) { perror("connect"); exit(3); } memset(&fdset, 0, sizeof(fdset)); fdset[0].fd = STDIN_FILENO; fdset[0].events = POLLIN; ssl_init(NULL, NULL); ssl_client_init(&client, sockfd, SSL_MODE_CLIENT); fdset[1].fd = sockfd; fdset[1].events = POLLERR | POLLHUP | POLLNVAL | POLLIN; fdset[1].events |= POLLRDHUP; do_ssl_handshake(); while (true) { fdset[1].events &= ~POLLOUT; fdset[1].events |= ssl_client_want_write(&client) ? POLLOUT : 0; if ((nready = poll(&fdset[0], 2, -1)) == 0) { continue; } revents = fdset[1].revents; if (revents & POLLIN) { if (do_sock_read() == -1) { break; } } if (revents & POLLOUT) { if (do_sock_write() == -1) { break; } } if ((revents & (POLLERR | POLLHUP | POLLNVAL)) || (revents & POLLRDHUP)) { break; } if (fdset[0].revents & POLLIN) { do_stdin_read(); } if (client.encrypt_len > 0) { do_encrypt(); } } if (close(fdset[1].fd) < 0) { perror("close"); exit(4); } ssl_client_cleanup(&client); return 0; } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 |
/* * Author: Hiroyuki Chishiro * License: 2-Clause BSD */ #define _GNU_SOURCE #include <stdio.h> #include <stdlib.h> #include <string.h> #include <stdbool.h> #include <unistd.h> #include <arpa/inet.h> #include <poll.h> #include <openssl/ssl.h> #include "common.h" #define PORT 65432 #define BACKLOG 1024 #define SERVER_CRT "server.crt" #define SERVER_KEY "server.key" int main(void) { char str[INET_ADDRSTRLEN]; int enable = 1; int servfd; int clientfd; struct sockaddr_in peeraddr; socklen_t peeraddr_len = sizeof(peeraddr); struct pollfd fdset[2]; int nready, revents; struct sockaddr_in servaddr; if ((servfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) { perror("socket"); exit(1); } if (setsockopt(servfd, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(enable)) < 0) { perror("setsockopt"); exit(2); } memset(&servaddr, 0, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_addr.s_addr = htonl(INADDR_ANY); servaddr.sin_port = htons(PORT); if (bind(servfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) < 0) { perror("bind"); exit(3); } if (listen(servfd, BACKLOG) < 0) { perror("listen"); exit(4); } memset(&fdset, 0, sizeof(fdset)); fdset[0].fd = STDIN_FILENO; fdset[0].events = POLLIN; ssl_init(SERVER_CRT, SERVER_KEY); while (true) { printf("waiting for next connection on port %d\n", PORT); if ((clientfd = accept(servfd, (struct sockaddr *) &peeraddr, &peeraddr_len)) < 0) { perror("accept"); exit(5); } ssl_client_init(&client, clientfd, SSL_MODE_SERVER); inet_ntop(peeraddr.sin_family, &peeraddr.sin_addr, str, INET_ADDRSTRLEN); printf("new connection from %s:%d\n", str, ntohs(peeraddr.sin_port)); fdset[1].fd = clientfd; fdset[1].events = POLLERR | POLLHUP | POLLNVAL | POLLIN; fdset[1].events |= POLLRDHUP; while (true) { fdset[1].events &= ~POLLOUT; fdset[1].events |= (ssl_client_want_write(&client) ? POLLOUT : 0); if ((nready = poll(&fdset[0], 2, -1)) == 0) { continue; } revents = fdset[1].revents; if (revents & POLLIN) { if (do_sock_read() == -1) { break; } } if (revents & POLLOUT) { if (do_sock_write() == -1) { break; } } if ((revents & (POLLERR | POLLHUP | POLLNVAL)) || (revents & POLLRDHUP)) { break; } if (fdset[0].revents & POLLIN) { do_stdin_read(); } if (client.encrypt_len > 0) { do_encrypt(); } } if (close(fdset[1].fd) < 0) { perror("close"); exit(6); } ssl_client_cleanup(&client); } return 0; } |
SSLのサーバ鍵と証明書の作成とクライアント・サーバの実行
クライアント・サーバの実行前の準備として,SSLのサーバ鍵と証明書の作成を行います.
makeでビルド済みの状態から解説していきます.
試しにサーバを実行してみると,以下のようにSSL_CTX_use_cetificate_file関数の呼び出しでエラーになります.
SSLのサーバ鍵と証明書がないからですね.
1 2 3 4 5 |
$ ./openssl_server Error: SSL_CTX_use_certificate_file() 80EB1F40617F0000:error:80000002:system library:file_ctrl:No such file or directory:../crypto/bio/bss_file.c:297:calling fopen(server.crt, r) 80EB1F40617F0000:error:10080002:BIO routines:file_ctrl:system lib:../crypto/bio/bss_file.c:300: 80EB1F40617F0000:error:0A080002:SSL routines:SSL_CTX_use_certificate_file:system lib:../ssl/ssl_rsa.c:291: |
「make gen」と入力してSSLのサーバ鍵と証明書を作成します.
入力を求められますが,デフォルトで構いませんので,エンターキーを連打します.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
$ make gen openssl genrsa -des3 -passout pass:hoge -out server.pass.key 2048 openssl rsa -passin pass:hoge -in server.pass.key -out server.key writing RSA key rm -f server.pass.key openssl req -new -key server.key -out server.csr You are about to be asked to enter information that will be incorporated into your certificate request. What you are about to enter is what is called a Distinguished Name or a DN. There are quite a few fields but you can leave some blank For some fields there will be a default value, If you enter '.', the field will be left blank. ----- Country Name (2 letter code) [AU]: State or Province Name (full name) [Some-State]: Locality Name (eg, city) []: Organization Name (eg, company) [Internet Widgits Pty Ltd]: Organizational Unit Name (eg, section) []: Common Name (e.g. server FQDN or YOUR name) []: Email Address []: Please enter the following 'extra' attributes to be sent with your certificate request A challenge password []: An optional company name []: openssl x509 -req -sha256 -days 365 -in server.csr -signkey server.key -out server.crt Certificate request self-signature ok subject=C = AU, ST = Some-State, O = Internet Widgits Pty Ltd |
lsコマンドを入力するとserver.crt,server.csr,server.keyが作成されていることがわかります.
1 2 3 |
$ ls Makefile common.h openssl_client.c openssl_server.c server.csr common.c openssl_client* openssl_server* server.crt server.key |
準備ができたので,クライアント・サーバを実行します.
まずはサーバの起動します.
ポート番号65432でコネクション待ちになっていることがわかります.
1 2 |
$ ./openssl_server waiting for next connection on port 65432 |
次に違う端末でクライアントを起動します.
何も表示されませんが,入力待ちになっていることがわかります.
1 |
$ ./openssl_client |
サーバの端末を見てみると,「new connection from 127.0.0.1:35238」の表示が追加されています.(ポート番号は実行毎に変動します.)
1 2 3 |
$ ./openssl_server waiting for next connection on port 65432 new connection from 127.0.0.1:35238 |
そうしたらクライアント・サーバのどちらかで文字を入力してエンターキーを押すと,それぞれサーバ・クライアントにデータが送れることを確認しましょう!
まとめ
C言語のOpenSSLでSSLクライアント・サーバを紹介しました.
OpenSSLでSSL通信をしたいあなたは,参考にして下さい.
C言語を独学で習得することは難しいです.
私にC言語の無料相談をしたいあなたは,公式LINE「ChishiroのC言語」の友だち追加をお願い致します.
私のキャパシティもあり,一定数に達したら終了しますので,今すぐ追加しましょう!
独学が難しいあなたは,元東大教員がおすすめするC言語を学べるオンラインプログラミングスクール5社で自分に合うスクールを見つけましょう.後悔はさせません!