Windows XP Windows 7 Windows 2003 Windows Vista Windows 綜合教學 Linux 系統教學
Windows 10 Windows 8 Windows 2008 Windows NT Windows Server 電腦軟體教學
您现在的位置: Windows教程網 >> Windows教程 >  >> Linux 系統教學 >> Linux 教學

OpenSSL C API 開發指南:SSL/TLS 客戶端與服務端完整程式架構

編輯:Linux 教學

OpenSSL 作為業界標準的 TLS/SSL 庫,其 C API 雖然底層但靈活性極高。本文從數位證書準備、庫初始化、SSL_CTX 配置、Socket 綁定、握手通訊、資源釋放六大階段,構建完整的 OpenSSL 應用開發框架,並針對雙向認證、錯誤處理、非阻塞模式給出工程級代碼範本。

階段 0:證書與金鑰準備

角色必備檔案格式生成命令
服務端 (單向)server.key, server.crtPEMopenssl req -x509 -newkey rsa:2048 -keyout server.key -out server.crt -days 365 -nodes
服務端 (雙向)+ ca.crt (信任客戶端 CA)PEM同上 + 客戶端 CA 憑證
客戶端 (雙向)client.key, client.crt, ca.crtPEM客戶端私鑰、自簽/CA 簽發證書、信任服務端 CA

⚠️ 生產環境務必使用 **CA 簽發證書**,禁用自簽;私鑰檔案權限 chmod 600;支援 ECDSA (P-256) 以提升性能。

階段 1:庫初始化 (OpenSSL 1.1.0+ 簡化版)

#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+ 可省略

階段 2:建立並配置 SSL_CTX (上下文對象,線程安全、可複用)

// 選擇協議版本: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);

階段 3:Socket 建立與綁定 (標準 TCP 流程)


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);

階段 4:建立 SSL 對象並綁定 Socket


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");
}

階段 5:安全資料收發


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 */ }

階段 6:優雅關閉與資源釋放


// 雙向關閉握手
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

錯誤處理鐵律

  1. 所有返回值檢查:NULL/≤0 即錯誤
  2. SSL_get_error(ssl, ret) 區分 SSL_ERROR_WANT_READ/WRITE (非阻塞正常) 與真實錯誤
  3. ERR_print_errors_fp(stderr) 或 ERR_error_string(ERR_get_error(), NULL) 記錄詳細堆疊
  4. 資源釋放放在 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 安全通訊能力。

Copyright © Windows教程網 All Rights Reserved