ビルドしてサーバーを起動させます。
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; | |
} |