前面學(xué)習(xí)了什么是HTTPS協(xié)議,了解了HTTPS的工作原理以及具體的工作流程,了解了HTTP協(xié)議和HTTPS協(xié)議之間的區(qū)別。當(dāng)然,紙上學(xué)來(lái)終覺(jué)淺,懂了原理還不行,還是得實(shí)際操作一遍才能真正的理解其工作流程。
下面通過(guò)之前所學(xué)的Telnet協(xié)議,HTTPS協(xié)議知識(shí),結(jié)合起來(lái)進(jìn)行實(shí)操練習(xí),給自己一個(gè)目標(biāo),就是說(shuō)實(shí)現(xiàn)一個(gè)安全性的Telnet服務(wù) —— TelnetS服務(wù)
01 | TelnetS
HTTPS = HTTP + SSL/TLS
在網(wǎng)絡(luò)編程中,主要體現(xiàn)到增加了證書(shū)校驗(yàn),傳輸加密的過(guò)程
02 | OpenSSL
www.openssl.org
加密算法有很多,這里主要通過(guò)OPENSSL
提供的API以示例代碼進(jìn)行學(xué)習(xí)
OPENSSL
是開(kāi)源的安全的套接字層的數(shù)據(jù)傳輸加密庫(kù),主要提供了多種加密算法、密鑰/證書(shū)管理、SSL協(xié)議等功能,整體開(kāi)源包分為三個(gè)部分:libssl, libcrypto, openssl
-
libssl:SSL協(xié)議庫(kù)
-
libcrypto:加密算法庫(kù)
-
openssl:總體app命令工具(函數(shù))庫(kù)
通過(guò)官方的服務(wù)端例子源碼進(jìn)行學(xué)習(xí),其中關(guān)鍵函數(shù)意義如下:
wiki.openssl.org
-
初始化
OPENSSL
庫(kù)以下函數(shù)在OPENSSL的V1.1.0版本開(kāi)始被OPENSSL_init_ssl()棄用
#include <openssl/ssl.h> int SSL_library_init(void); void SSL_load_error_strings(void); int OpenSSL_add_ssl_algorithms(void);
-
SSL_library_init()
-
描述:初始化SSL算法庫(kù)函數(shù)
-
筆記:在進(jìn)行任何其他操作之前,必須先調(diào)用該函數(shù),且不可重復(fù)調(diào)用
-
返回值:始終返回 “1"
-
-
SSL_load_error_strings()
-
描述:載入所有SSL 錯(cuò)誤消息, 為所有 libcrypto 函數(shù)注冊(cè)錯(cuò)誤字符串, 注冊(cè)libssl錯(cuò)誤字符串
-
返回值:無(wú)
-
-
OpenSSL_add_ssl_algorithms()
-
描述:載入所有SSL 算法, SSL_library_init()的同義函數(shù),作為宏實(shí)現(xiàn)
-
返回值:始終為“1”
-
-
-
加載 & 校驗(yàn)證書(shū)
#include <openssl/ssl.h> /* 描述: * * 筆記:在進(jìn)行任何其他操作之前,必須先調(diào)用該函數(shù),且不可重復(fù)調(diào)用 * */ SSL_CTX *SSL_CTX_new(const SSL_METHOD *method); int SSL_use_certificate_file(SSL *ssl, const char *file, int type); int SSL_CTX_use_PrivateKey_file(SSL_CTX *ctx, const char *file, int type); int SSL_CTX_check_private_key(const SSL_CTX *ctx);
-
SSL_CTX_new()
-
描述:創(chuàng)建一個(gè)
SSL_CTX *
實(shí)例,用來(lái)保存證書(shū)的私鑰,其中包含與 SSL/TLS 或 DTLS 會(huì)話建立相關(guān)的各種配置和數(shù)據(jù);這些內(nèi)容稍后由表示活動(dòng)會(huì)話的 SSL 對(duì)象繼承。 -
method:可通過(guò)
TLS_client_method()、TLS_server_method() 和TLS_method()
三個(gè)最新的庫(kù)函數(shù)創(chuàng)建,舊版的SSLv23_method(), SSLv23_server_method(), SSLv23_client_method()
等庫(kù)函數(shù)也依舊可用 -
返回值:
-
零:創(chuàng)建新的SSL_CTX對(duì)象失敗。檢查錯(cuò)誤堆棧以找出原因;
-
指向SSL_CTX對(duì)象的指針:返回值指向已分配的SSL_CTX對(duì)象
-
-
-
SSL_CTX_use_certificate_file()
-
描述:將存儲(chǔ)在文件中的第一個(gè)證書(shū)加載到 ctx 中。證書(shū)的格式設(shè)置類型必須從已知類型SSL_FILETYPE_PEM、SSL_FILETYPE_ASN1中指定。SSL_use_certificate_file() 將證書(shū)從文件加載到 SSL 中
-
返回值:成功后,函數(shù)返回 1。否則,請(qǐng)檢查錯(cuò)誤堆棧以找出原因
-
-
SSL_CTX_use_PrivateKey_file()
-
描述:將在文件中找到的第一個(gè)私鑰添加到 ctx。私鑰的格式設(shè)置類型必須從已知類型SSL_FILETYPE_PEM、SSL_FILETYPE_ASN1中指定
-
返回值:成功后,函數(shù)返回 1。否則,請(qǐng)檢查錯(cuò)誤堆棧以找出原因
-
-
SSL_CTX_check_private_key()
-
描述:檢查私鑰與加載到 ctx 中的相應(yīng)證書(shū)的一致性。如果安裝了多個(gè)密鑰/證書(shū)對(duì) (RSA/DSA),則將檢查最后安裝的項(xiàng)目。例如,如果最后一項(xiàng)是 RSA 證書(shū)或密鑰,則將檢查 RSA 密鑰/證書(shū)對(duì)
-
返回值:成功后,函數(shù)返回 1。否則,請(qǐng)檢查錯(cuò)誤堆棧以找出原因
-
-
-
創(chuàng)建與
socket
綁定的SSL
實(shí)例#include <openssl/ssl.h> SSL *SSL_new(SSL_CTX *ctx); int SSL_set_fd(SSL *ssl, int fd);
-
SSL_new()
-
描述:創(chuàng)建一個(gè)新的 SSL 結(jié)構(gòu),該結(jié)構(gòu)是保存 TLS/SSL 連接數(shù)據(jù)所必需的。新結(jié)構(gòu)繼承了底層上下文 ctx 的設(shè)置:連接方法、選項(xiàng)、驗(yàn)證設(shè)置、超時(shí)設(shè)置。SSL 結(jié)構(gòu)被計(jì)算為引用。首次創(chuàng)建 SSL 結(jié)構(gòu)會(huì)增加引用計(jì)數(shù)。釋放它(使用 SSL_free)會(huì)遞減它。當(dāng)引用計(jì)數(shù)降至零時(shí),將釋放分配給 SSL 結(jié)構(gòu)的任何內(nèi)存或資源
-
返回值:
-
0:新的 SSL 結(jié)構(gòu)失敗。檢查錯(cuò)誤堆棧以找出原因;
-
指向 SSL 結(jié)構(gòu)的指針:返回值指向分配的 SSL 結(jié)構(gòu)
-
-
-
SSL_set_fd()
-
描述:將文件描述符 fd 設(shè)置為 SSL 的 TLS/SSL(加密)端的輸入/輸出工具。fd 通常是網(wǎng)絡(luò)連接的套接字文件描述符。執(zhí)行該操作時(shí),會(huì)自動(dòng)創(chuàng)建一個(gè)套接字 BIO 以在 SSL 和 fd 之間進(jìn)行接口。BIO和SSL引擎繼承了fd的行為。如果 fd 是非阻塞的,則 SSL 也將具有非阻塞行為。如果已經(jīng)有一個(gè)BIO連接到ssl,將調(diào)用BIO_free()(對(duì)于讀取和寫入端,如果不同)
-
返回值
-
0:操作失敗。檢查錯(cuò)誤堆棧以找出原因;
-
1:操作成功
-
-
-
-
SSL
握手,建立SSL
連接#include <openssl/ssl.h> int SSL_accept(SSL *ssl);
-
SSL_accept()
-
描述:等待 TLS/SSL 客戶端啟動(dòng) TLS/SSL 握手。必須已通過(guò)設(shè)置基礎(chǔ) BIOS 設(shè)置通信通道并將其分配給 ssl。
-
筆記:SSL_accept() 的行為取決于底層 BIO。如果底層 BIO 阻塞,則 SSL_accept() 僅在握手完成或發(fā)生錯(cuò)誤后返回。如果底層 BIO 是非阻塞的,當(dāng)?shù)讓?BIO 無(wú)法滿足 SSL_accept() 繼續(xù)握手的需求時(shí),SSL_accept() 也會(huì)返回,通過(guò)返回值 -1 指示問(wèn)題。在這種情況下,調(diào)用返回值為 SSL_accept() 的 SSL_get_error() 將產(chǎn)生 SSL_ERROR_WANT_READ 或SSL_ERROR_WANT_WRITE。然后,調(diào)用進(jìn)程必須在采取適當(dāng)?shù)牟僮饕詽M足 SSL_accept() 的需求后重復(fù)調(diào)用。操作取決于基礎(chǔ) BIO。使用非阻塞套接字時(shí),無(wú)需執(zhí)行任何操作,但 select() 可用于檢查所需條件。使用緩沖 BIO 對(duì)(如 BIO 對(duì))時(shí),必須先將數(shù)據(jù)寫入或檢索出 BIO,然后才能繼續(xù)
-
返回值
-
0:TLS / SSL握手不成功,但被關(guān)閉,并受TLS / SSL協(xié)議規(guī)范的控制。使用返回值 ret 調(diào)用 SSL_get_error() 以找出原因
-
1:TLS/SSL 握手已成功完成,已建立 TLS/SSL 連接
-
-1:TLS/SSL 握手不成功,因?yàn)樵趨f(xié)議級(jí)別發(fā)生致命錯(cuò)誤或發(fā)生連接故障。關(guān)閉不干凈。如果需要操作以繼續(xù)非阻塞 BIOS 的操作,也會(huì)發(fā)生這種情況。使用返回值 ret 調(diào)用 SSL_get_error() 以找出原因。
-
-
-
-
數(shù)據(jù)收發(fā)處理
#include <openssl/ssl.h> int SSL_read(SSL *ssl, void *buf, int num); int SSL_write(SSL *ssl, const void *buf, int num) int SSL_get_error(const SSL *ssl, int ret);
-
SSL_read()
-
描述:將字節(jié)數(shù)從指定的 SSL 讀取到緩沖區(qū) buf 中
-
筆記:讀取函數(shù)基于 SSL/TLS 記錄工作。數(shù)據(jù)以記錄形式接收(最大記錄大小為 16kB)。只有當(dāng)記錄被完全接收到時(shí),才能對(duì)其進(jìn)行處理(解密和完整性檢查)。因此,在上次讀取調(diào)用時(shí)未檢索到的數(shù)據(jù)仍然可以在 SSL 層內(nèi)緩沖,并將在下一次讀取調(diào)用時(shí)檢索。如果 num 大于緩沖的字節(jié)數(shù),則讀取函數(shù)將返回緩沖的字節(jié)數(shù)。如果緩沖區(qū)中沒(méi)有更多字節(jié),則讀取函數(shù)將觸發(fā)下一條記錄的處理。僅當(dāng)記錄被完全接收和處理時(shí),讀取函數(shù)才會(huì)返回報(bào)告成功。最多將返回一條記錄的內(nèi)容。由于 SSL/TLS 記錄的大小可能超過(guò)底層傳輸(例如 TCP)的最大數(shù)據(jù)包大小,因此可能需要在記錄完成并且讀取調(diào)用成功之前從傳輸層讀取多個(gè)數(shù)據(jù)包。
-
返回值
-
大于0:讀取操作成功。返回值是從 TLS/SSL 連接實(shí)際讀取的字節(jié)數(shù)。
-
≤0:讀取操作未成功,因?yàn)檫B接已關(guān)閉、發(fā)生錯(cuò)誤或調(diào)用進(jìn)程必須執(zhí)行操作。使用返回值 ret 調(diào)用 SSL_get_error(3) 以找出原因
-
-
-
SSL_write()
-
描述:將緩沖區(qū) buf 中的字節(jié)數(shù)寫入指定的 SSL 連接
-
筆記:只有當(dāng)寫入長(zhǎng)度為 num 的 buf 的完整內(nèi)容時(shí),寫入函數(shù)才會(huì)成功返回。可以使用 SSL_CTX_set_mode(3) 的SSL_MODE_ENABLE_PARTIAL_WRITE選項(xiàng)更改此默認(rèn)行為。設(shè)置此標(biāo)志后,當(dāng)部分寫入成功完成時(shí),寫入函數(shù)也將返回成功。在這種情況下,寫入函數(shù)操作被視為已完成。發(fā)送字節(jié),并且必須啟動(dòng)具有新緩沖區(qū)的新寫入調(diào)用(已刪除已發(fā)送的字節(jié))。部分寫入以消息塊的大?。?6kB)執(zhí)行
-
返回值
-
大于0:寫入操作成功,返回值是實(shí)際寫入 TLS/SSL 連接的字節(jié)數(shù)
-
≤0:寫入操作未成功,因?yàn)檫B接已關(guān)閉、發(fā)生錯(cuò)誤或調(diào)用進(jìn)程必須執(zhí)行操作。使用返回值 ret 調(diào)用 SSL_get_error() 以找出原因
-
-
-
SSL_get_error()
-
描述:返回一個(gè)結(jié)果代碼(適用于 C“switch”語(yǔ)句),用于前面對(duì) SSL 上的 SSL_connect()、SSL_accept()、SSL_do_handshake()、SSL_read_ex()、SSL_read()、SSL_peek_ex()、SSL_peek()、SSL_shutdown()、SSL_write_ex() 或 SSL_write() 的調(diào)用。該 TLS/SSL I/O 函數(shù)返回的值必須傳遞給參數(shù) ret 中的 SSL_get_error()
-
筆記:除了 ssl 和 ret,SSL_get_error() 還會(huì)檢查當(dāng)前線程的 OpenSSL 錯(cuò)誤隊(duì)列。因此,SSL_get_error() 必須在執(zhí)行 TLS/SSL I/O 操作的同一線程中使用,并且不應(yīng)在兩者之間出現(xiàn)其他 OpenSSL 函數(shù)調(diào)用。在嘗試 TLS/SSL I/O 操作之前,當(dāng)前線程的錯(cuò)誤隊(duì)列必須為空,否則 SSL_get_error() 將無(wú)法可靠地工作
-
返回值:
錯(cuò)誤碼 描述 SSL_ERROR_NONE TLS/SSL I/O 操作已完成。當(dāng)且僅當(dāng) ret > 0 時(shí),才會(huì)返回此結(jié)果代碼 SSL_ERROR_ZERO_RETURN TLS/SSL 對(duì)等方已通過(guò)發(fā)送close_notify警報(bào)關(guān)閉了寫入連接。無(wú)法讀取更多數(shù)據(jù)。請(qǐng)注意,SSL_ERROR_ZERO_RETURN并不一定表示基礎(chǔ)傳輸已關(guān)閉 SSL_ERROR_WANT_READ, SSL_ERROR_WANT_WRITE 操作未完成,以后可以重試 SSL_ERROR_WANT_CONNECT, SSL_ERROR_WANT_ACCEPT 操作未完成;稍后應(yīng)再次調(diào)用相同的 TLS/SSL I/O 函數(shù)。底層 BIO 尚未連接到對(duì)等體,調(diào)用將在 connect()/accept() 中阻塞。建立連接后,應(yīng)再次調(diào)用 SSL 函數(shù) SSL_ERROR_WANT_X509_LOOKUP 操作未完成,因?yàn)?SSL_CTX_set_client_cert_cb() 設(shè)置的應(yīng)用程序回調(diào)已請(qǐng)求再次調(diào)用。稍后應(yīng)再次調(diào)用 TLS/SSL I/O 函數(shù)。詳細(xì)信息取決于應(yīng)用程序。 SSL_ERROR_WANT_ASYNC 操作未完成,因?yàn)楫惒揭嫒栽谔幚頂?shù)據(jù)。僅當(dāng)模式已設(shè)置為 SSL_MODE_ASYNC 使用 SSL_CTX_set_mode(3) 或 SSL_set_mode(3) 并且正在使用支持異步的引擎時(shí),才會(huì)發(fā)生這種情況 SSL_ERROR_WANT_ASYNC_JOB 異步作業(yè)無(wú)法啟動(dòng),因?yàn)槌刂袥](méi)有可用的異步作業(yè),僅當(dāng)模式已使用 SSL_CTX_set_mode(3) 或 SSL_set_mode(3) 設(shè)置為 SSL_MODE_ASYNC,并且已通過(guò)調(diào)用 ASYNC_init_thread(3) 在異步作業(yè)池上設(shè)置了最大限制時(shí),才會(huì)發(fā)生這種情況 SSL_ERROR_WANT_CLIENT_HELLO_CB 操作未完成,因?yàn)?SSL_CTX_set_client_hello_cb() 設(shè)置的應(yīng)用程序回調(diào)已請(qǐng)求再次調(diào)用。稍后應(yīng)再次調(diào)用 TLS/SSL I/O 函數(shù)。詳細(xì)信息取決于應(yīng)用程序 SSL_ERROR_SYSCALL 發(fā)生了一些不可恢復(fù)的致命 I/O 錯(cuò)誤。OpenSSL 錯(cuò)誤隊(duì)列可能包含有關(guān)錯(cuò)誤的詳細(xì)信息 SSL_ERROR_SSL SSL 庫(kù)中發(fā)生不可恢復(fù)的致命錯(cuò)誤,通常是協(xié)議錯(cuò)誤。OpenSSL 錯(cuò)誤隊(duì)列包含有關(guān)錯(cuò)誤的詳細(xì)信息。如果發(fā)生此錯(cuò)誤,則不應(yīng)在連接上執(zhí)行進(jìn)一步的 I/O 操作,并且不得調(diào)用 SSL_shutdown()。 -
-
-
釋放資源
#include <openssl/ssl.h> int SSL_shutdown(SSL *ssl); void SSL_free(SSL *ssl);
-
SSL_shutdown()
-
描述:關(guān)閉活動(dòng)的 TLS/SSL 連接。它將close_notify關(guān)閉警報(bào)發(fā)送到對(duì)等方;試向?qū)Φ确桨l(fā)送close_notify關(guān)閉警報(bào)。無(wú)論操作是否成功,都會(huì)設(shè)置 SSL_SENT_SHUTDOWN 標(biāo)志,并且當(dāng)前打開(kāi)的會(huì)話被視為已關(guān)閉且良好,并將保留在會(huì)話緩存中以供進(jìn)一步重用。
-
筆記
-
第一個(gè)關(guān)閉連接:當(dāng)應(yīng)用程序是第一個(gè)發(fā)送close_notify警報(bào)的一方時(shí),SSL_shutdown() 將僅發(fā)送警報(bào),然后設(shè)置 SSL_SENT_SHUTDOWN 標(biāo)志(以便會(huì)話被視為良好并將保留在緩存中)。如果成功,SSL_shutdown() 將返回 0。
-
對(duì)等方關(guān)閉連接:如果對(duì)等方已經(jīng)發(fā)送了close_notify警報(bào)**,并且**已經(jīng)在另一個(gè)函數(shù)中隱式處理了該警報(bào)(SSL_read(3)),則設(shè)置 SSL_RECEIVED_SHUTDOWN 標(biāo)志。在這種情況下,SSL_read() 將返回 <= 0,SSL_get_error() 將返回 SSL_ERROR_ZERO_RETURN。SSL_shutdown() 將發(fā)送close_notify警報(bào),設(shè)置 SSL_SENT_SHUTDOWN 標(biāo)志。如果成功,SSL_shutdown() 將返回 1。
-
-
返回值
-
0:關(guān)閉過(guò)程正在進(jìn)行中,尚未完成。對(duì)于 TLS 和 DTLS,這意味著已發(fā)送close_notify警報(bào),但對(duì)等方尚未依次回復(fù)自己的close_notify。
-
1:關(guān)閉已成功完成。對(duì)于 TLS 和 DTLS,這意味著已發(fā)送close_notify警報(bào),并收到對(duì)等方的close_notify警報(bào)。
-
<0:關(guān)閉未成功。使用返回值 ret 調(diào)用 SSL_get_error(3) 以找出原因。如果需要操作來(lái)繼續(xù)非阻塞 BIOS 的操作,則可能會(huì)發(fā)生這種情況。當(dāng)并非所有數(shù)據(jù)都使用 SSL_read() 讀取時(shí),也會(huì)發(fā)生這種情況。
-
-
-
SSL_free()
-
描述:SSL_free() 遞減 SSL 的引用計(jì)數(shù),并刪除 SSL 指向的 SSL 結(jié)構(gòu),如果引用計(jì)數(shù)達(dá)到 0,則釋放分配的內(nèi)存。如果 ssl 為空,則不執(zhí)行任何操作
-
筆記:SSL_free() 還調(diào)用間接受影響的項(xiàng)目的 free()ing 過(guò)程(如果適用):緩沖 BIO、讀寫 BIO、專門為此 SSL 創(chuàng)建的密碼列表、SSL_SESSION。不要在調(diào)用 SSL_free() 之前或之后顯式釋放這些間接釋放的項(xiàng)目,因?yàn)閲L試釋放兩次可能會(huì)導(dǎo)致程序失敗
-
返回值:無(wú)
-
-
03 | 實(shí)現(xiàn)思路
服務(wù)器處理流程
根據(jù)上面對(duì)官方服務(wù)器端例子的源碼學(xué)習(xí),大致了解了最基礎(chǔ)的服務(wù)端處理流程,框圖如下
從圖中可以看出,與平常所學(xué)所見(jiàn)的 socket
程序相比,OPENSSL
的服務(wù)器端多了三個(gè)步驟
-
通訊握手環(huán)節(jié)
創(chuàng)建
socket
前,先進(jìn)行了OPENSSL
庫(kù)初始化,然后校驗(yàn)證書(shū)和私鑰,也就是多了一個(gè)校驗(yàn)證書(shū)的環(huán)節(jié) -
關(guān)聯(lián)環(huán)節(jié)
把創(chuàng)建的
SSL
實(shí)例與創(chuàng)建的socket
進(jìn)行關(guān)聯(lián),使得后續(xù)的數(shù)據(jù)通信可以使用OPENSSL
提供的加解密處理的數(shù)據(jù)收發(fā)函數(shù) -
釋放資源
最后在關(guān)閉釋放
socket
句柄資源之前,需要先進(jìn)行SSL
資源的斷連和釋放,因?yàn)榍懊鎰?chuàng)建的SSL
實(shí)例是全局性的
客戶端處理流程
與服務(wù)器端流程類似,都是先初始化 OPENSSL
庫(kù),實(shí)例化 SSL
后,與創(chuàng)建的 socket
關(guān)聯(lián),再將客服端與服務(wù)器進(jìn)行連接,最后通過(guò) OPENSSL
的加解密處理的收發(fā)函數(shù)進(jìn)行數(shù)據(jù)傳輸,在需要斷開(kāi)連接的時(shí)候,也是先斷開(kāi) SSL
的連接,釋放 SSL
的資源后再釋放 socket
的句柄,流程框圖如下
04 | 代碼實(shí)現(xiàn)
理論知識(shí)有了一定的了解,那么最后需要通過(guò)實(shí)踐操作來(lái)檢驗(yàn)自己所學(xué)的知識(shí)是否能夠應(yīng)用到實(shí)際中。
之前學(xué)習(xí)過(guò)的Telnet協(xié)議,實(shí)現(xiàn)過(guò)簡(jiǎn)單的Telnet服務(wù),現(xiàn)在想把SSL/TLS協(xié)議和Telnet協(xié)議搭配起來(lái),實(shí)現(xiàn)一個(gè)TelnetS(Telnet + SSL/TLS)協(xié)議
服務(wù)端代碼
在官方的服務(wù)端例程上進(jìn)行了一點(diǎn)簡(jiǎn)單的修改(其實(shí)就是多加了點(diǎn)標(biāo)注和交互消息打印,方便查看交互過(guò)程而已)
#include <unistd.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <openssl/ssl.h>
#include <openssl/err.h>
#include <string>
#include <iostream>
#include <chrono>
#include <csignal>
#include <thread>
using namespace std;
/****************************************
* 函數(shù)名稱:Pikashu_ReuseAddrPort
* 作 者:Pikashu
* 設(shè)計(jì)日期:2023-06-15
* 功能描述:設(shè)置 socket fd 描述符的屬性,打開(kāi)地址、端口復(fù)用功能
* 參 數(shù):設(shè)置屬性的 socket fd
* 返 回 值:0: success | -1: SO_REUSEADDR failed | -2: SO_REUSEPORT failed
****************************************/
int Pikashu_ReuseAddrPort(int socketfd)
{
int Reuse = 1;
if (0 > setsockopt(socketfd, SOL_SOCKET, SO_REUSEADDR, &Reuse, sizeof(Reuse)))
{
printf("SO_REUSEADDR failed: %s", strerror(errno));
return -1;
}
Reuse = 1;
if (0 > setsockopt(socketfd, SOL_SOCKET, SO_REUSEPORT, &Reuse, sizeof(Reuse)))
{
BC_LOGE("SO_REUSEPORT failed: %s", strerror(errno));
return -2;
}
return 0;
}
/****************************************
* 函數(shù)名稱:Pikashu_CreateSocket
* 作 者:Pikashu
* 設(shè)計(jì)日期:2023-06-15
* 功能描述:創(chuàng)建一個(gè)監(jiān)聽(tīng)的socket
* 參 數(shù):listenPort: 監(jiān)聽(tīng)端口
* 返 回 值:創(chuàng)建的 socket fd | -1: socket failed | -2: Reuse failed | -3: bind failed | -4: listen failed
****************************************/
int Pikashu_CreateSocket(int listenPort)
{
int sockFd = 0;
sockFd = socket(AF_INET, SOCK_STREAM, 0);
if (sockFd < 0)
{
printf("create socket error: %d", errno);
return -1;
}
// SO_REUSEADDR && SO_REUSEPORT
if (0 > Pikashu_ReuseAddrPort(sockFd))
{
printf("setsockopt error: %d", errno);
close(sockFd);
return -2;
}
struct sockaddr_in Server_addr{};
bzero(&Server_addr, sizeof(Server_addr));
Server_addr.sin_family = AF_INET;
Server_addr.sin_port = htons(listenPort);
Server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
bzero(&(Server_addr.sin_zero), 8);
if (0 > bind(sockFd, (struct sockaddr *) &Server_addr, sizeof(Server_addr)))
{
printf("bind port = [%d], failure: %s\n", listenPort, strerror(errno));
return -3;
}
// 限制開(kāi)啟連接數(shù)量, 系統(tǒng)分配:SOMAXCONN、自定義:5
if (0 > listen(sockFd, 5))
{
printf("listen port = [%d], failure: %s\n",listenPort, strerror(errno));
return -4;
}
return sockFd;
}
/****************************************
* 函數(shù)名稱:Pikashu_InitOpenSSL
* 作 者:Pikashu
* 設(shè)計(jì)日期:2023-06-15
* 功能描述:全局初始化openssl庫(kù),只需要調(diào)用一次
* 參 數(shù):NULL
* 返 回 值:NULL
****************************************/
void Pikashu_InitOpenSSL()
{
SSL_library_init(); // SSL庫(kù)初始化
SSL_load_error_strings(); // 載入所有SSL 錯(cuò)誤消息
OpenSSL_add_all_algorithms(); // 加載所有支持的算法
}
/****************************************
* 函數(shù)名稱:Pikashu_CleanOpenSSL
* 作 者:Pikashu
* 設(shè)計(jì)日期:2023-06-15
* 功能描述:退出前清理openssl
* 參 數(shù):NULL
* 返 回 值:NULL
****************************************/
void Pikashu_CleanOpenSSL()
{
EVP_cleanup();
}
/****************************************
* 函數(shù)名稱:Pikashu_CreateText
* 作 者:Pikashu
* 設(shè)計(jì)日期:2023-06-15
* 功能描述:創(chuàng)建一個(gè)全局SSL_CTX,存儲(chǔ)證書(shū)等信息
* 參 數(shù):NULL
* 返 回 值:ctx: success | -1: failed
****************************************/
SSL_CTX *Pikashu_CreateText()
{
const SSL_METHOD *method;
SSL_CTX *ctx;
/* 以SSL V2 和 V3 標(biāo)準(zhǔn)兼容方式產(chǎn)生一個(gè)SSL_CTX ,即SSL Content Text */
/* 也可以用SSLv2_server_method() 或SSLv3_server_method() 單獨(dú)表示V2 或V3 標(biāo)準(zhǔn)*/
// method = SSLv3_server_method();
// method = SSLv23_server_method();
method = TLS_server_method();
ctx = SSL_CTX_new(method);
if (!ctx)
{
perror("Unable to create SSL context");
ERR_print_errors_fp(stderr);
return -1;
}
return ctx;
}
/****************************************
* 函數(shù)名稱:Pikashu_ConfigureContext
* 作 者:Pikashu
* 設(shè)計(jì)日期:2023-06-15
* 功能描述:設(shè)置證書(shū)
* 參 數(shù):ctx: SSL上下文 && certPath: 證書(shū)文件 && privateKeyPath: 私鑰文件
* 返 回 值:NULL
****************************************/
void Pikashu_ConfigureContext(SSL_CTX *ctx, string certPath, string privateKeyPath)
{
SSL_CTX_set_ecdh_auto(ctx, 1);
// 載入用戶的數(shù)字證書(shū), 此證書(shū)用來(lái)發(fā)送給客戶端。證書(shū)里包含有公鑰
if (0 >= SSL_CTX_use_certificate_file(ctx, certPath.c_str() /*"cert.pem"*/, SSL_FILETYPE_PEM))
{
ERR_print_errors_fp(stderr);
exit(EXIT_FAILURE);
}
// 載入用戶私鑰
if (0 >= SSL_CTX_use_PrivateKey_file(ctx, privateKeyPath.c_str()/*"key.pem"*/, SSL_FILETYPE_PEM))
{
ERR_print_errors_fp(stderr);
exit(EXIT_FAILURE);
}
// 檢查用戶私鑰是否正確
if (!SSL_CTX_check_private_key(ctx))
{
ERR_print_errors_fp(stdout);
exit(EXIT_FAILURE);
}
}
/****************************************
* 函數(shù)名稱:Pikashu_CheckOpensslError
* 作 者:Pikashu
* 設(shè)計(jì)日期:2023-06-15
* 功能描述:檢查OPENSSL產(chǎn)生的錯(cuò)誤,并分析錯(cuò)誤碼
* 參 數(shù):ssl: SSL實(shí)例 && retCode: SSL_read/SSL_write返回值 && isError: 是否確實(shí)發(fā)生了錯(cuò)誤
* 返 回 值:NULL
****************************************/
void Pikashu_CheckOpensslError(SSL *ssl, int retCode, bool &isError)
{
// 處理ssl的錯(cuò)誤碼
int sslErr = SSL_get_error(ssl, retCode);
isError = true;
switch (sslErr)
{
case SSL_ERROR_WANT_READ:
{
cout << "SSL_ERROR_WANT_READ" << endl;
isError = false;
break;
}
case SSL_ERROR_WANT_WRITE:
{
cout << "SSL_ERROR_WANT_WRITE" << endl;
isError = false;
break;
}
case SSL_ERROR_NONE: // 沒(méi)有錯(cuò)誤發(fā)生,這種情況好像沒(méi)怎么遇到過(guò)
{
cout << "SSL_ERROR_WANT_WRITE" << endl;
break;
}
case SSL_ERROR_ZERO_RETURN:// == 0 ,代表對(duì)端關(guān)閉了連接
{
cout << "SSL remote close the connection" << endl;
break;
}
case SSL_ERROR_SSL:
{
cout << "SSL error:" << sslErr << endl;
break;
}
default:
{
cout << "SSL unknown error:" << sslErr << endl;
break;
}
}
}
/****************************************
* 函數(shù)名稱:Pikashu_ClientMsgHandle
* 作 者:Pikashu
* 設(shè)計(jì)日期:2023-06-15
* 功能描述:接收客戶端連接,消息操作
* 參 數(shù):socketFd: 客戶端的socket文件句柄 && ctx:全局的上下文,保存有證書(shū)信息等
* 返 回 值:NULL
****************************************/
void Pikashu_ClientMsgHandle(int socketFd, SSL_CTX *ctx)
{
cout << "new connection coming" << endl;
SSL *ssl;
const char reply[] = "test\n";
// 基于ctx 產(chǎn)生一個(gè)新的SSL
ssl = SSL_new(ctx);
// 將連接用戶的socket 加入到SSL
SSL_set_fd(ssl, socketFd);
auto t1 = chrono::steady_clock::now();
// 建立SSL 連接
int ret = SSL_accept(ssl);
if (0 < ret)
{
cout << "ssl handshake success" << endl;
auto t2 = chrono::steady_clock::now();
auto timeSpan = chrono::duration_cast<chrono::duration<double>>(t2 - t1);
cout << "SSL_accept cost " << timeSpan.count() * 1000 << " ms." << endl;
while (true)
{
char tempBuf[512] = {};
int recvLen = SSL_read(ssl, tempBuf, sizeof(tempBuf));
if (0 < recvLen)
{
cout << "客戶端發(fā)來(lái)數(shù)據(jù), len = " << recvLen << ",content = " << tempBuf << endl;
// echo
cout << "SSL_write " << string(tempBuf, recvLen) << endl;
ret = SSL_write(ssl, tempBuf, recvLen);
if (0 >= ret)
{
cout << "SSL_write return <= 0, ret = " << recvLen << endl;
bool isError = false;
Pikashu_CheckOpensslError(ssl, recvLen, isError);
if (isError)
{
cout << "SSL_write error, close" << endl;
break;
}
}
}
else
{
// SSL_read <= 0 ,進(jìn)一步檢查openssl 的錯(cuò)誤碼,判斷具體原因
cout << "SSL_read return <= 0, ret = " << recvLen << endl;
bool isError = true;
Pikashu_CheckOpensslError(ssl, recvLen, isError);
if (isError)
{
cout << "SSL_read error,close" << endl;
break;
}
}
/* TCP處理的流程,針對(duì)openssl,還需進(jìn)一步針對(duì) <= 0進(jìn)行判斷
* else if (recvLen == 0)
{
cout << "客戶端主動(dòng)斷開(kāi)連接,退出接收流程" << endl;
break;
}
else
{
cout << "發(fā)生其他錯(cuò)誤, no = " << errno << ", desc = " << strerror(errno) << endl;
}*/
}
}
else
{
int code = SSL_get_error(ssl, ret);
auto reason = ERR_reason_error_string(code);
if (code == SSL_ERROR_SYSCALL)
{
cout << "ssl handshake error: errno = " << errno << ", reason: " << strerror(errno) << endl;
}
else
{
cout << "ssl handshake error: code = " << code << ", reason: " << reason << endl;
}
ERR_print_errors_fp(stderr);
}
cout << "cleanup ssl connection" << endl;
// 關(guān)閉SSL 連接
SSL_shutdown(ssl);
// 釋放SSL
SSL_free(ssl);
// 關(guān)閉socket
close(socketFd);
}
int main()
{
int sockFd;
SSL_CTX *ctx;
// 捕獲SIG_IGN信號(hào),解決Broken pipe導(dǎo)致進(jìn)程崩潰問(wèn)題
signal(SIGPIPE, SIG_IGN);
Pikashu_Init_OpenSSL();
ctx = Pikashu_CreateText();
Pikashu_ConfigureContext(ctx, "../ssl/google.com.pem", "../ssl/google.com.key");
cout << "listen at :1688" << endl;
sockFd = Pikashu_CreateSocket(1688);
/* Handle connections */
while (true)
{
struct sockaddr_in addr{};
socklen_t len = sizeof(addr);
// 阻塞,直到有新的連接到來(lái)
int clientFd = accept(sockFd, (struct sockaddr *) &addr, &len);
if (0 > clientFd)
{
perror("Unable to accept\n");
break;
}
// 單獨(dú)起1個(gè)線程處理客戶端邏輯(錯(cuò)誤的用法,這里只是為了演示,實(shí)戰(zhàn)中需要使用epoll多路復(fù)用技術(shù))
thread task(Pikashu_ClientMsgHandle, clientFd, ctx);
task.detach();
}
// 關(guān)閉監(jiān)聽(tīng)socket文件句柄
close(sockFd);
// 退出前釋放全局的上下文
SSL_CTX_free(ctx);
// 清理openssl
Pikashu_CleanOpenSSL();
return 0;
}
客戶端代碼
仿照服務(wù)端的例程,按照客戶端的流程寫的一個(gè)簡(jiǎn)單交互客戶端,略微丑陋
#include <openssl/ssl.h>
#include <openssl/err.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <sys/socket.h>
#include <resolv.h>
#include <iostream>
#include <thread>
using namespace std;
/****************************************
* 函數(shù)名稱:Pikashu_ShowCerts
* 作 者:Pikashu
* 設(shè)計(jì)日期:2023-06-15
* 功能描述:j解析顯示整數(shù)內(nèi)容
* 參 數(shù):ssl
* 返 回 值:NULL
****************************************/
void Pikashu_ShowCerts(SSL *ssl)
{
X509 *cert;
char *line;
cert = SSL_get_peer_certificate(ssl);
if (nullptr != cert)
{
cout << "數(shù)字證書(shū)信息: " << endl;
line = X509_NAME_oneline(X509_get_subject_name(cert), 0, 0);
cout << "證書(shū): " << line << endl;
free(line);
line = X509_NAME_oneline(X509_get_issuer_name(cert), 0, 0);
cout << "頒發(fā)者: " << line << endl;
free(line);
X509_free(cert);
}
else
{
cout << "無(wú)證書(shū)信息!" << endl;
}
}
/****************************************
* 函數(shù)名稱:Pikashu_InitOpenSSL
* 作 者:Pikashu
* 設(shè)計(jì)日期:2023-06-15
* 功能描述:全局初始化openssl庫(kù),只需要調(diào)用一次
* 參 數(shù):NULL
* 返 回 值:NULL
****************************************/
void Pikashu_InitOpenSSL()
{
SSL_library_init(); // SSL庫(kù)初始化
SSL_load_error_strings(); // 載入所有SSL 錯(cuò)誤消息
OpenSSL_add_all_algorithms(); // 加載所有支持的算法
}
/****************************************
* 函數(shù)名稱:Pikashu_CreateText
* 作 者:Pikashu
* 設(shè)計(jì)日期:2023-06-15
* 功能描述:創(chuàng)建一個(gè)全局SSL_CTX,存儲(chǔ)證書(shū)等信息
* 參 數(shù):NULL
* 返 回 值:ctx: success | -1: failed
****************************************/
SSL_CTX *Pikashu_CreateText()
{
const SSL_METHOD *method;
SSL_CTX *ctx;
/* 以SSL V2 和 V3 標(biāo)準(zhǔn)兼容方式產(chǎn)生一個(gè)SSL_CTX ,即SSL Content Text */
/* 也可以用SSLv2_server_method() 或SSLv3_server_method() 單獨(dú)表示V2 或V3 標(biāo)準(zhǔn)*/
//method = SSLv3_server_method();
method = SSLv23_client_method();
ctx = SSL_CTX_new(method);
if (!ctx)
{
perror("Unable to create SSL context");
ERR_print_errors_fp(stderr);
exit(EXIT_FAILURE);
}
return ctx;
}
/****************************************
* 函數(shù)名稱:Pikashu_CreateSocket
* 作 者:Pikashu
* 設(shè)計(jì)日期:2023-06-15
* 功能描述:創(chuàng)建一個(gè)監(jiān)聽(tīng)的socket
* 參 數(shù):serverIp: 服務(wù)器ip地址 && serverPort: 服務(wù)器端口
* 返 回 值:創(chuàng)建的 socket fd | -1: socket failed | -2: Reuse failed | -3: bind failed | -4: listen failed
****************************************/
int Pikashu_CreateSocket(string serverIp, uint16_t serverPort)
{
int sockFd = 0;
struct sockaddr_in Client_addr{};
bzero(&Client_addr, sizeof(Client_addr));
Client_addr.sin_family = AF_INET;
Client_addr.sin_port = htons(serverPort);
Client_addr.sin_addr.s_addr = inet_addr(serverIp.c_str());
bzero(&(Client_addr.sin_zero));
sockFd = socket(PF_INET, SOCK_STREAM, 0);
if (0 > sockFd)
{
printf("create socket error: %d", errno);
exit(EXIT_FAILURE);
}
int ret = connect(sockFd, (struct sockaddr *) &Client_addr, sizeof(sockaddr_in));
if (0 != ret)
{
cout << "Connect err: " << errno << endl;
exit(errno);
}
return sockFd;
}
/****************************************
* 函數(shù)名稱:Pikashu_CheckOpensslError
* 作 者:Pikashu
* 設(shè)計(jì)日期:2023-06-15
* 功能描述:檢查OPENSSL產(chǎn)生的錯(cuò)誤,并分析錯(cuò)誤碼
* 參 數(shù):ssl: SSL實(shí)例 && retCode: SSL_read/SSL_write返回值 && isError: 是否確實(shí)發(fā)生了錯(cuò)誤
* 返 回 值:NULL
****************************************/
void Pikashu_CheckOpensslError(SSL *ssl, int retCode, bool &isError)
{
// 處理ssl的錯(cuò)誤碼
int sslErr = SSL_get_error(ssl, retCode);
isError = true;
switch (sslErr)
{
case SSL_ERROR_WANT_READ:
{
cout << "SSL_ERROR_WANT_READ" << endl;
isError = false;
break;
}
case SSL_ERROR_WANT_WRITE:
{
cout << "SSL_ERROR_WANT_WRITE" << endl;
isError = false;
break;
}
case SSL_ERROR_NONE: // 沒(méi)有錯(cuò)誤發(fā)生,這種情況好像沒(méi)怎么遇到過(guò)
{
cout << "SSL_ERROR_WANT_WRITE" << endl;
break;
}
case SSL_ERROR_ZERO_RETURN:// == 0 ,代表對(duì)端關(guān)閉了連接
{
cout << "SSL remote close the connection" << endl;
break;
}
case SSL_ERROR_SSL:
{
cout << "SSL error:" << sslErr << endl;
break;
}
default:
{
cout << "SSL unknown error:" << sslErr << endl;
break;
}
}
}
int main()
{
SSL_CTX *ctx = nullptr;
// 初始化openssl
Pikashu_InitOpenSSL();
cout << "init openssl success" << endl;
// 初始化socket,同步連接遠(yuǎn)端服務(wù)器
int socketFd = Pikashu_CreateSocket("10.80.0.17", 1688);
cout << "tcp connect remote success" << endl;
// 創(chuàng)建SSL_CTX上下文
ctx = Pikashu_CreateText();
// 綁定socket句柄到SSL實(shí)例上
SSL *ssl = SSL_new(ctx);
SSL_set_fd(ssl, socketFd);
// 建立SSL鏈接,握手
cout << "SSL_connect 2s later will connect and do hand shake..." << endl;
this_thread::sleep_for(chrono::seconds(2));
cout << "SSL_connect " << endl;
int ret = SSL_connect(ssl);
if (0 >= ret)
{
ERR_print_errors_fp(stderr);
return 0;
}
cout << "handshake success" << endl;
// 顯示對(duì)方證書(shū)信息
cout << "Connected with " << SSL_get_cipher(ssl) << " encryption" << endl;
Pikashu_ShowCerts(ssl);
cout << "send hello server" << endl;
string msg = "hello serve";
SSL_write(ssl, msg.c_str(), msg.length());
// wait server response
char tempBuf[256] = {};
ret = SSL_read(ssl, tempBuf, sizeof(tempBuf));
if (0 >= ret)
{
cout << "SSL_read return <=0,ret=" << ret << endl;
bool isError = false;
Pikashu_CheckOpensslError(ssl, ret, isError);
if (isError)
{
cout << "SSL_read error,close" << endl;
}
}
this_thread::sleep_for(chrono::seconds(5));
cout << "exit ..." << endl;
SSL_shutdown(ssl); // 關(guān)閉SSL連接
SSL_free(ssl); // 釋放SSL資源
close(socketFd); // 關(guān)閉socket文件句柄
SSL_CTX_free(ctx); // 釋放SSL_CTX上下文資源
return 0;
}
編譯過(guò)程 & 執(zhí)行結(jié)果
- 對(duì)
openssl_xxx
函數(shù)未定義的引用
-
問(wèn)題原因:很常見(jiàn)的問(wèn)題,沒(méi)有找到
OPENSSL
的庫(kù)函數(shù) -
解決方法:編譯的時(shí)候添加靜態(tài)鏈接
-lssl -lcrypto
undefined reference to symbol 'Pthread_create@GLIBC_2.2.5'
-
問(wèn)題原因:
pthread
不是linux
下的默認(rèn)的庫(kù),在鏈接的時(shí)候,無(wú)法找到phread
庫(kù)中線程函數(shù)的入口地址,所以鏈接失敗 -
解決方法:編譯的時(shí)候添加靜態(tài)鏈接
-lpthread -lm
- 最終編譯完成
文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-491862.html
- 執(zhí)行結(jié)果
文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-491862.html
到了這里,關(guān)于計(jì)算機(jī)網(wǎng)絡(luò)基礎(chǔ)知識(shí)(九)—— 什么是TelnetS?Telnet Over TLS的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!