ビルドしてサーバーを起動させます。
gcc -o server server.c -lssl -lcrypto
./server
curl で HTTP リクエストを送信します。
curl -k -v https://localhost:8443
| #include <stdio.h> | |
| #include <stdlib.h> | |
| #include <string.h> | |
| #include <unistd.h> | |
| #include <sys/socket.h> | |
| #include <sys/select.h> | |
| #include <netinet/in.h> | |
| #include <arpa/inet.h> | |
| #include <errno.h> | |
| #include <fcntl.h> | |
| #include <openssl/ssl.h> | |
| #include <openssl/err.h> | |
| #include <openssl/evp.h> | |
| #include <openssl/x509.h> | |
| #include <openssl/pem.h> | |
| #define PORT 8443 | |
| #define MAX_CLIENTS 10 | |
| #define BUFFER_SIZE 4096 | |
| // クライアント接続情報 | |
| typedef struct { | |
| int fd; | |
| SSL *ssl; | |
| int tls_established; | |
| } client_info_t; | |
| // HTTPレスポンスのテンプレート | |
| const char *http_response = | |
| "HTTP/1.1 200 OK\r\n" | |
| "Content-Type: text/html\r\n" | |
| "Connection: close\r\n" | |
| "Content-Length: 147\r\n" | |
| "\r\n" | |
| "<html><body>" | |
| "<h1>Hello from C HTTPS Server!</h1>" | |
| "<p>This server uses select() with TLS/SSL encryption.</p>" | |
| "</body></html>"; | |
| // グローバル変数 | |
| SSL_CTX *ssl_ctx = NULL; | |
| client_info_t clients[MAX_CLIENTS]; | |
| // OpenSSLの初期化 | |
| int init_openssl() { | |
| SSL_library_init(); | |
| SSL_load_error_strings(); | |
| OpenSSL_add_all_algorithms(); | |
| return 1; | |
| } | |
| // OpenSSLのクリーンアップ | |
| void cleanup_openssl() { | |
| EVP_cleanup(); | |
| ERR_free_strings(); | |
| } | |
| // 自己署名証明書とプライベートキーを生成 | |
| int generate_certificate() { | |
| EVP_PKEY *pkey = NULL; | |
| X509 *x509 = NULL; | |
| FILE *fp = NULL; | |
| int ret = 0; | |
| // RSAキーペアを生成 | |
| EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, NULL); | |
| if (!ctx) goto cleanup; | |
| if (EVP_PKEY_keygen_init(ctx) <= 0) goto cleanup; | |
| if (EVP_PKEY_CTX_set_rsa_keygen_bits(ctx, 2048) <= 0) goto cleanup; | |
| if (EVP_PKEY_keygen(ctx, &pkey) <= 0) goto cleanup; | |
| // X.509証明書を作成 | |
| x509 = X509_new(); | |
| if (!x509) goto cleanup; | |
| // 証明書の設定 | |
| ASN1_INTEGER_set(X509_get_serialNumber(x509), 1); | |
| X509_gmtime_adj(X509_get_notBefore(x509), 0); | |
| X509_gmtime_adj(X509_get_notAfter(x509), 31536000L); // 1年間有効 | |
| X509_set_pubkey(x509, pkey); | |
| // 証明書の名前を設定 | |
| X509_NAME *name = X509_get_subject_name(x509); | |
| X509_NAME_add_entry_by_txt(name, "C", MBSTRING_ASC, (unsigned char*)"JP", -1, -1, 0); | |
| X509_NAME_add_entry_by_txt(name, "O", MBSTRING_ASC, (unsigned char*)"Test", -1, -1, 0); | |
| X509_NAME_add_entry_by_txt(name, "CN", MBSTRING_ASC, (unsigned char*)"localhost", -1, -1, 0); | |
| X509_set_issuer_name(x509, name); | |
| // 証明書に署名 | |
| if (!X509_sign(x509, pkey, EVP_sha256())) goto cleanup; | |
| // プライベートキーをファイルに保存 | |
| fp = fopen("server.key", "wb"); | |
| if (!fp) goto cleanup; | |
| PEM_write_PrivateKey(fp, pkey, NULL, NULL, 0, NULL, NULL); | |
| fclose(fp); | |
| fp = NULL; | |
| // 証明書をファイルに保存 | |
| fp = fopen("server.crt", "wb"); | |
| if (!fp) goto cleanup; | |
| PEM_write_X509(fp, x509); | |
| fclose(fp); | |
| fp = NULL; | |
| printf("Generated self-signed certificate: server.crt and server.key\n"); | |
| ret = 1; | |
| cleanup: | |
| if (fp) fclose(fp); | |
| if (ctx) EVP_PKEY_CTX_free(ctx); | |
| if (pkey) EVP_PKEY_free(pkey); | |
| if (x509) X509_free(x509); | |
| return ret; | |
| } | |
| // SSL contextの作成 | |
| SSL_CTX *create_ssl_context() { | |
| const SSL_METHOD *method; | |
| SSL_CTX *ctx; | |
| method = TLS_server_method(); | |
| ctx = SSL_CTX_new(method); | |
| if (!ctx) { | |
| ERR_print_errors_fp(stderr); | |
| return NULL; | |
| } | |
| // セキュリティオプションを設定 | |
| SSL_CTX_set_min_proto_version(ctx, TLS1_2_VERSION); | |
| SSL_CTX_set_options(ctx, SSL_OP_NO_SSLv3 | SSL_OP_NO_TLSv1 | SSL_OP_NO_TLSv1_1); | |
| return ctx; | |
| } | |
| // 証明書とキーを読み込み | |
| int configure_ssl_context(SSL_CTX *ctx) { | |
| // 証明書ファイルが存在しない場合は生成 | |
| if (access("server.crt", F_OK) != 0 || access("server.key", F_OK) != 0) { | |
| if (!generate_certificate()) { | |
| fprintf(stderr, "Failed to generate certificate\n"); | |
| return 0; | |
| } | |
| } | |
| // 証明書を読み込み | |
| if (SSL_CTX_use_certificate_file(ctx, "server.crt", SSL_FILETYPE_PEM) <= 0) { | |
| ERR_print_errors_fp(stderr); | |
| return 0; | |
| } | |
| // プライベートキーを読み込み | |
| if (SSL_CTX_use_PrivateKey_file(ctx, "server.key", SSL_FILETYPE_PEM) <= 0) { | |
| ERR_print_errors_fp(stderr); | |
| return 0; | |
| } | |
| // プライベートキーが証明書と一致するかチェック | |
| if (!SSL_CTX_check_private_key(ctx)) { | |
| fprintf(stderr, "Private key does not match certificate\n"); | |
| return 0; | |
| } | |
| return 1; | |
| } | |
| // ソケットを非ブロッキングモードに設定 | |
| int set_nonblocking(int fd) { | |
| int flags = fcntl(fd, F_GETFL, 0); | |
| if (flags == -1) return -1; | |
| return fcntl(fd, F_SETFL, flags | O_NONBLOCK); | |
| } | |
| // クライアント情報を初期化 | |
| void init_clients() { | |
| for (int i = 0; i < MAX_CLIENTS; i++) { | |
| clients[i].fd = -1; | |
| clients[i].ssl = NULL; | |
| clients[i].tls_established = 0; | |
| } | |
| } | |
| // 空いているクライアントスロットを見つける | |
| int find_free_client_slot() { | |
| for (int i = 0; i < MAX_CLIENTS; i++) { | |
| if (clients[i].fd == -1) { | |
| return i; | |
| } | |
| } | |
| return -1; | |
| } | |
| // ファイルディスクリプタからクライアントスロットを見つける | |
| int find_client_by_fd(int fd) { | |
| for (int i = 0; i < MAX_CLIENTS; i++) { | |
| if (clients[i].fd == fd) { | |
| return i; | |
| } | |
| } | |
| return -1; | |
| } | |
| // クライアントを削除 | |
| void remove_client(int slot) { | |
| if (slot < 0 || slot >= MAX_CLIENTS) return; | |
| if (clients[slot].ssl) { | |
| SSL_shutdown(clients[slot].ssl); | |
| SSL_free(clients[slot].ssl); | |
| clients[slot].ssl = NULL; | |
| } | |
| if (clients[slot].fd != -1) { | |
| close(clients[slot].fd); | |
| clients[slot].fd = -1; | |
| } | |
| clients[slot].tls_established = 0; | |
| } | |
| // TLSハンドシェイクを処理 | |
| int handle_tls_handshake(int slot) { | |
| int result = SSL_accept(clients[slot].ssl); | |
| if (result == 1) { | |
| // ハンドシェイク成功 | |
| clients[slot].tls_established = 1; | |
| printf("TLS handshake completed for client %d\n", clients[slot].fd); | |
| return 1; | |
| } else { | |
| int error = SSL_get_error(clients[slot].ssl, result); | |
| if (error == SSL_ERROR_WANT_READ || error == SSL_ERROR_WANT_WRITE) { | |
| // まだハンドシェイク中 | |
| return 0; | |
| } else { | |
| // ハンドシェイクエラー | |
| printf("TLS handshake failed for client %d: %d\n", clients[slot].fd, error); | |
| ERR_print_errors_fp(stderr); | |
| return -1; | |
| } | |
| } | |
| } | |
| // HTTPリクエストを処理 | |
| void handle_request(int slot) { | |
| char buffer[BUFFER_SIZE]; | |
| int bytes_read; | |
| // TLS経由でリクエストを読み込み | |
| bytes_read = SSL_read(clients[slot].ssl, buffer, sizeof(buffer) - 1); | |
| if (bytes_read > 0) { | |
| buffer[bytes_read] = '\0'; | |
| printf("HTTPS Request received from client %d:\n%s\n", clients[slot].fd, buffer); | |
| // TLS経由でHTTPレスポンスを送信 | |
| SSL_write(clients[slot].ssl, http_response, strlen(http_response)); | |
| } else { | |
| int error = SSL_get_error(clients[slot].ssl, bytes_read); | |
| if (error != SSL_ERROR_WANT_READ && error != SSL_ERROR_WANT_WRITE) { | |
| printf("SSL_read error for client %d: %d\n", clients[slot].fd, error); | |
| } | |
| } | |
| } | |
| int main() { | |
| int server_fd, client_fd; | |
| struct sockaddr_in server_addr, client_addr; | |
| socklen_t client_len = sizeof(client_addr); | |
| fd_set read_fds, master_fds; | |
| int max_fd; | |
| int opt = 1; | |
| // OpenSSLを初期化 | |
| if (!init_openssl()) { | |
| fprintf(stderr, "Failed to initialize OpenSSL\n"); | |
| exit(EXIT_FAILURE); | |
| } | |
| // SSL contextを作成 | |
| ssl_ctx = create_ssl_context(); | |
| if (!ssl_ctx) { | |
| fprintf(stderr, "Failed to create SSL context\n"); | |
| exit(EXIT_FAILURE); | |
| } | |
| // SSL contextを設定 | |
| if (!configure_ssl_context(ssl_ctx)) { | |
| fprintf(stderr, "Failed to configure SSL context\n"); | |
| exit(EXIT_FAILURE); | |
| } | |
| // クライアント情報を初期化 | |
| init_clients(); | |
| // サーバーソケットを作成 | |
| server_fd = socket(AF_INET, SOCK_STREAM, 0); | |
| if (server_fd == -1) { | |
| perror("socket"); | |
| exit(EXIT_FAILURE); | |
| } | |
| // SO_REUSEADDRオプションを設定 | |
| if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) < 0) { | |
| perror("setsockopt"); | |
| exit(EXIT_FAILURE); | |
| } | |
| // サーバーアドレスを設定 | |
| memset(&server_addr, 0, sizeof(server_addr)); | |
| server_addr.sin_family = AF_INET; | |
| server_addr.sin_addr.s_addr = INADDR_ANY; | |
| server_addr.sin_port = htons(PORT); | |
| // ソケットをバインド | |
| if (bind(server_fd, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) { | |
| perror("bind"); | |
| exit(EXIT_FAILURE); | |
| } | |
| // リスニング開始 | |
| if (listen(server_fd, MAX_CLIENTS) < 0) { | |
| perror("listen"); | |
| exit(EXIT_FAILURE); | |
| } | |
| // サーバーソケットを非ブロッキングモードに設定 | |
| if (set_nonblocking(server_fd) < 0) { | |
| perror("set_nonblocking"); | |
| exit(EXIT_FAILURE); | |
| } | |
| printf("HTTPS Server started on port %d\n", PORT); | |
| printf("Access: https://localhost:%d\n", PORT); | |
| printf("Note: You may see a security warning due to self-signed certificate\n"); | |
| // fd_setを初期化 | |
| FD_ZERO(&master_fds); | |
| FD_SET(server_fd, &master_fds); | |
| max_fd = server_fd; | |
| while (1) { | |
| // master_fdsをread_fdsにコピー | |
| read_fds = master_fds; | |
| // selectでI/O多重化 | |
| int activity = select(max_fd + 1, &read_fds, NULL, NULL, NULL); | |
| if (activity < 0) { | |
| perror("select"); | |
| break; | |
| } | |
| // 各ファイルディスクリプタをチェック | |
| for (int fd = 0; fd <= max_fd; fd++) { | |
| if (FD_ISSET(fd, &read_fds)) { | |
| if (fd == server_fd) { | |
| // 新しい接続を受け入れ | |
| client_fd = accept(server_fd, (struct sockaddr*)&client_addr, &client_len); | |
| if (client_fd >= 0) { | |
| int slot = find_free_client_slot(); | |
| if (slot == -1) { | |
| printf("Maximum clients reached, rejecting connection\n"); | |
| close(client_fd); | |
| continue; | |
| } | |
| printf("New connection from %s:%d (fd: %d, slot: %d)\n", | |
| inet_ntoa(client_addr.sin_addr), | |
| ntohs(client_addr.sin_port), | |
| client_fd, slot); | |
| // クライアントソケットを非ブロッキングモードに設定 | |
| if (set_nonblocking(client_fd) < 0) { | |
| perror("set_nonblocking client"); | |
| close(client_fd); | |
| continue; | |
| } | |
| // SSLオブジェクトを作成 | |
| SSL *ssl = SSL_new(ssl_ctx); | |
| if (!ssl) { | |
| ERR_print_errors_fp(stderr); | |
| close(client_fd); | |
| continue; | |
| } | |
| SSL_set_fd(ssl, client_fd); | |
| // クライアント情報を設定 | |
| clients[slot].fd = client_fd; | |
| clients[slot].ssl = ssl; | |
| clients[slot].tls_established = 0; | |
| // クライアントソケットをfd_setに追加 | |
| FD_SET(client_fd, &master_fds); | |
| if (client_fd > max_fd) { | |
| max_fd = client_fd; | |
| } | |
| } | |
| } else { | |
| // 既存のクライアントからのデータを処理 | |
| int slot = find_client_by_fd(fd); | |
| if (slot == -1) { | |
| // 不明なファイルディスクリプタ | |
| FD_CLR(fd, &master_fds); | |
| close(fd); | |
| continue; | |
| } | |
| if (!clients[slot].tls_established) { | |
| // TLSハンドシェイクを処理 | |
| int result = handle_tls_handshake(slot); | |
| if (result < 0) { | |
| // ハンドシェイクエラー | |
| FD_CLR(fd, &master_fds); | |
| remove_client(slot); | |
| } | |
| // result == 0の場合はまだハンドシェイク中なので継続 | |
| } else { | |
| // HTTPリクエストを処理 | |
| handle_request(slot); | |
| // 接続を閉じる(HTTP/1.1 Connection: close) | |
| FD_CLR(fd, &master_fds); | |
| remove_client(slot); | |
| printf("Client %d disconnected\n", fd); | |
| } | |
| // max_fdを更新 | |
| if (fd == max_fd) { | |
| while (max_fd > server_fd && !FD_ISSET(max_fd, &master_fds)) { | |
| max_fd--; | |
| } | |
| } | |
| } | |
| } | |
| } | |
| } | |
| // クリーンアップ | |
| close(server_fd); | |
| for (int i = 0; i < MAX_CLIENTS; i++) { | |
| remove_client(i); | |
| } | |
| SSL_CTX_free(ssl_ctx); | |
| cleanup_openssl(); | |
| return 0; | |
| } |