OpenSSL 作為業界標準的 TLS/SSL 庫,其 C API 雖然底層但靈活性極高。本文從數位證書準備、庫初始化、SSL_CTX 配置、Socket 綁定、握手通訊、資源釋放六大階段,構建完整的 OpenSSL 應用開發框架,並針對雙向認證、錯誤處理、非阻塞模式給出工程級代碼範本。
| 角色 | 必備檔案 | 格式 | 生成命令 |
|---|---|---|---|
| 服務端 (單向) | server.key, server.crt | PEM | openssl req -x509 -newkey rsa:2048 -keyout server.key -out server.crt -days 365 -nodes |
| 服務端 (雙向) | + ca.crt (信任客戶端 CA) | PEM | 同上 + 客戶端 CA 憑證 |
| 客戶端 (雙向) | client.key, client.crt, ca.crt | PEM | 客戶端私鑰、自簽/CA 簽發證書、信任服務端 CA |
⚠️ 生產環境務必使用 **CA 簽發證書**,禁用自簽;私鑰檔案權限 chmod 600;支援 ECDSA (P-256) 以提升性能。
#include
#include
// OpenSSL 1.1.0+ 自動初始化,僅需載入錯誤字串 (可選)
SSL_load_error_strings(); // 人類可讀錯誤訊息
OpenSSL_add_ssl_algorithms(); // 註冊演算法 (舊版需 SSL_library_init())
// 應用退出時
ERR_free_strings();
EVP_cleanup(); // 1.1.0+ 可省略
// 選擇協議版本:TLS_method() 協商最高版本 (推薦)
const SSL_METHOD *method = TLS_server_method(); // 或 TLS_client_method()
SSL_CTX *ctx = SSL_CTX_new(method);
if (!ctx) { ERR_print_errors_fp(stderr); exit(1); }
// 1. 載入證書鏈 (服務端)
if (SSL_CTX_use_certificate_file(ctx, "server.crt", SSL_FILETYPE_PEM) <= 0) {
ERR_print_errors_fp(stderr); exit(1);
}
if (SSL_CTX_use_PrivateKey_file(ctx, "server.key", SSL_FILETYPE_PEM) <= 0) {
ERR_print_errors_fp(stderr); exit(1);
}
if (!SSL_CTX_check_private_key(ctx)) {
fprintf(stderr, "Private key does not match certificate\n"); exit(1);
}
// 2. 雙向認證:載入 CA 並啟用驗證
SSL_CTX_load_verify_locations(ctx, "ca.crt", NULL); // 目錄為 NULL 僅載入檔案
SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, NULL);
// SSL_VERIFY_PEER: 驗證對端;FAIL_IF_NO_PEER_CERT: 強制要求客戶端證書
// 3. 設定密碼回調 (若私鑰加密)
SSL_CTX_set_default_passwd_cb_userdata(ctx, "keypassword");
// 4. 可選:設定加密套件偏好
SSL_CTX_set_cipher_list(ctx, "HIGH:!aNULL:!MD5:!3DES");
// 5. 可選:啟用 Session Cache (效能)
SSL_CTX_set_session_cache_mode(ctx, SSL_SESS_CACHE_SERVER);
SSL_CTX_sess_set_cache_size(ctx, 1024);
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in addr = {0};
addr.sin_family = AF_INET;
addr.sin_port = htons(4433);
addr.sin_addr.s_addr = INADDR_ANY; // 服務端綁定所有介面
bind(sockfd, (struct sockaddr*)&addr, sizeof(addr));
listen(sockfd, SOMAXCONN);
// 客戶端連線
int clientfd = accept(sockfd, NULL, NULL);
SSL *ssl = SSL_new(ctx);
SSL_set_fd(ssl, clientfd); // 綁定讀寫 fd (內部建立 BIO)
// 服務端:接受 TLS 握手
int ret = SSL_accept(ssl);
// 客戶端:發起 TLS 握手
// int ret = SSL_connect(ssl);
if (ret <= 0) {
int err = SSL_get_error(ssl, ret);
fprintf(stderr, "TLS handshake failed: %s\n", ERR_error_string(err, NULL));
// 處理 WANT_READ/WANT_WRITE (非阻塞模式)
goto cleanup;
}
// 驗證對端證書 (雙向認證服務端)
X509 *peer_cert = SSL_get_peer_certificate(ssl);
if (peer_cert) {
char *subj = X509_NAME_oneline(X509_get_subject_name(peer_cert), 0, 0);
printf("Peer subject: %s\n", subj);
OPENSSL_free(subj);
X509_free(peer_cert);
} else {
printf("No peer certificate presented\n");
}
char buf[4096];
int bytes;
// 讀取 (阻塞模式)
bytes = SSL_read(ssl, buf, sizeof(buf) - 1);
if (bytes > 0) {
buf[bytes] = '\0';
printf("Received: %s\n", buf);
}
// 寫入
const char *resp = "HTTP/1.1 200 OK\r\nContent-Length: 2\r\n\r\nOK";
bytes = SSL_write(ssl, resp, strlen(resp));
if (bytes <= 0) { /* handle error */ }
// 雙向關閉握手
int shutdown_ret = SSL_shutdown(ssl); // 首次發送 close_notify
if (shutdown_ret == 0) {
// 等待對端回應
SSL_shutdown(ssl);
}
SSL_free(ssl); // 釋放 SSL 結構
SSL_CTX_free(ctx); // 釋放上下文 (通常在程式結束時)
close(clientfd);
close(sockfd);
// Socket 設為非阻塞
fcntl(sockfd, F_SETFL, O_NONBLOCK);
// SSL 握手循環
while (1) {
int ret = SSL_accept(ssl); // 或 SSL_connect
if (ret == 1) break; // 成功
int err = SSL_get_error(ssl, ret);
if (err == SSL_ERROR_WANT_READ) {
// 等待 socket 可讀 (epoll/kqueue/select)
wait_for_read(sockfd);
} else if (err == SSL_ERROR_WANT_WRITE) {
wait_for_write(sockfd);
} else {
// 真正錯誤
break;
}
}
// SSL_read/SSL_write 同理處理 WANT_READ/WANT_WRITE
NULL/≤0 即錯誤SSL_get_error(ssl, ret) 區分 SSL_ERROR_WANT_READ/WRITE (非阻塞正常) 與真實錯誤ERR_print_errors_fp(stderr) 或 ERR_error_string(ERR_get_error(), NULL) 記錄詳細堆疊goto cleanup 或 RAII 包裝器中,確保異常路徑不洩漏gcc -o ssl_app ssl_app.c -lssl -lcrypto -lpthread
# 或 pkg-config --cflags --libs openssl
+------------------+ +------------------+
| Application | | Application |
+--------+---------+ +--------+---------+
| |
+--------v---------+ +--------v---------+
| SSL API | | SSL API |
| (SSL_read/write) | | (SSL_read/write) |
+--------+---------+ +--------+---------+
| |
+--------v---------+ +--------v---------+
| SSL_CTX | | SSL_CTX |
| (Config/Cert/Key)| | (Config/Cert/Key)|
+--------+---------+ +--------+---------+
| |
+--------v---------+ +--------+---------+
| TCP Socket |=====| TCP Socket |
+------------------+ +------------------+
掌握這六階段標準化流程,即可在 C/C++ 專案中穩健集成企業級 TLS 安全通訊能力。