近期深入研究OpenSSL編程,整理出一套完整的服務端與客戶端實作範例。核心步驟包括:使用同一CA簽發伺服器憑證(server.cer,須標註可用於服務端)與客戶端憑證(client.cer),雙向驗證確保通信安全。
#include "openssl/bio.h"
#include "openssl/ssl.h"
#include "openssl/err.h"
#include <cutil.h>
#define EXIT_IF_TRUE(x) if (x) \
do { \
fprintf(stderr, "Check '%s' is true\n", #x); \
ERR_print_errors_fp(stderr); \
exit(2); \
} while(0)
int main(int argc, char **argv) {
SSL_CTX *ctx;
SSL *ssl;
X509 *client_cert;
char szBuffer[1024];
int nLen;
struct sockaddr_in addr;
int len;
int nListenFd, nAcceptFd;
// 初始化OpenSSL
cutil_init();
cutil_log_set_level(LOG_ALL);
cutil_log_set_stderr(1);
SSLeay_add_ssl_algorithms();
OpenSSL_add_all_algorithms();
SSL_load_error_strings();
ERR_load_BIO_strings();
// 使用SSL v2/v3協議
EXIT_IF_TRUE((ctx = SSL_CTX_new(SSLv23_method())) == NULL);
// 要求驗證對方憑證
SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, NULL);
// 載入CA憑證、自身憑證與私鑰
EXIT_IF_TRUE(!SSL_CTX_load_verify_locations(ctx, "cacert.cer", NULL));
EXIT_IF_TRUE(SSL_CTX_use_certificate_file(ctx, "server.cer", SSL_FILETYPE_PEM) <= 0);
EXIT_IF_TRUE(SSL_CTX_use_PrivateKey_file(ctx, "server.key", SSL_FILETYPE_PEM) <= 0);
EXIT_IF_TRUE(!SSL_CTX_check_private_key(ctx));
// 建立Socket監聽
nListenFd = cutil_socket_new(SOCK_STREAM);
cutil_socket_bind(nListenFd, NULL, 8812, 1);
memset(&addr, 0, sizeof(addr));
len = sizeof(addr);
nAcceptFd = accept(nListenFd, (struct sockaddr *)&addr, (size_t *)&len);
cutil_log_debug("Accept a connect from [%s:%d]\n", inet_ntoa(addr.sin_addr), ntohs(addr.sin_port));
// 將連接綁定到SSL
EXIT_IF_TRUE((ssl = SSL_new(ctx)) == NULL);
SSL_set_fd(ssl, nAcceptFd);
EXIT_IF_TRUE(SSL_accept(ssl) != 1);
// 數據收發
memset(szBuffer, 0, sizeof(szBuffer));
nLen = SSL_read(ssl, szBuffer, sizeof(szBuffer));
fprintf(stderr, "Get Len %d %s ok\n", nLen, szBuffer);
strcat(szBuffer, " this is from server");
SSL_write(ssl, szBuffer, strlen(szBuffer));
// 釋放資源
SSL_free(ssl);
SSL_CTX_free(ctx);
close(nAcceptFd);
return 0;
}
#include "openssl/bio.h"
#include "openssl/ssl.h"
#include "openssl/err.h"
#include <cutil.h>
#define EXIT_IF_TRUE(x) if (x) \
do { \
fprintf(stderr, "Check '%s' is true\n", #x); \
ERR_print_errors_fp(stderr); \
exit(2); \
} while(0)
int main(int argc, char **argv) {
SSL_METHOD *meth;
SSL_CTX *ctx;
SSL *ssl;
int nFd, nLen;
char szBuffer[1024];
// 初始化OpenSSL
cutil_init();
cutil_log_set_level(LOG_ALL);
cutil_log_set_stderr(1);
SSLeay_add_ssl_algorithms();
OpenSSL_add_all_algorithms();
SSL_load_error_strings();
ERR_load_BIO_strings();
// 使用SSL v2/v3協議
EXIT_IF_TRUE((ctx = SSL_CTX_new(SSLv23_method())) == NULL);
// 要求驗證對方憑證
SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, NULL);
// 載入CA憑證、客戶端憑證與私鑰
EXIT_IF_TRUE(!SSL_CTX_load_verify_locations(ctx, "cacert.cer", NULL));
EXIT_IF_TRUE(SSL_CTX_use_certificate_file(ctx, "client.cer", SSL_FILETYPE_PEM) <= 0);
EXIT_IF_TRUE(SSL_CTX_use_PrivateKey_file(ctx, "client.key", SSL_FILETYPE_PEM) <= 0);
EXIT_IF_TRUE(!SSL_CTX_check_private_key(ctx));
// 建立連接
nFd = cutil_socket_new(SOCK_STREAM);
if (cutil_socket_connect(nFd, "127.0.0.1", 8812, 30) < 0) {
cutil_log_error("連接服務器失敗\n");
return -1;
}
// 將連接綁定到SSL並發起握手
EXIT_IF_TRUE((ssl = SSL_new(ctx)) == NULL);
SSL_set_fd(ssl, nFd);
EXIT_IF_TRUE(SSL_connect(ssl) != 1);
// 發送數據並接收回應
sprintf(szBuffer, "this is from client %d", getpid());
SSL_write(ssl, szBuffer, strlen(szBuffer));
memset(szBuffer, 0, sizeof(szBuffer));
nLen = SSL_read(ssl, szBuffer, sizeof(szBuffer));
fprintf(stderr, "Get Len %d %s ok\n", nLen, szBuffer);
// 釋放資源
SSL_free(ssl);
SSL_CTX_free(ctx);
close(nFd);
return 0;
}
SSL_VERIFY_PEER,確保通信雙方身份可信。EXIT_IF_TRUE統一處理OpenSSL調用失敗,輸出詳細錯誤堆棧。SSL_free、SSL_CTX_free、close順序釋放資源,避免洩漏。此範例可作為基於OpenSSL安全通信開發的基礎模板。