Stunnel:用途廣泛的SSL加密封裝器
stunnel用來對遠程客戶端和本地機(可啟動inetd的:inetd-startable)或遠程服務器間的SSL加密進行封裝。它可以在不修改任何代碼的情況下,為一般的使用inetd daemon的POP2、POP3和IMAP服務器添加SSL功能。它通過使用OpenSSL或SSLeay庫建立SSL連接。
下載鏈接:http://down.51cto.com/data/157863
有一種方法可以將加密功能無縫添加到網絡連接中,而不會將您原來的代碼段基址搞亂。Stunnel 是一個程序,可以使用 OpenSSL 庫對任意 TCP 會話進行加密。它作為服務器運行在程序外部。Stunnel 服務器主要執行兩個功能:一,首先,接收未加密的數據流,進行 SSL 加密,然后將其通過網絡發送;二,對已進行 SSL 加密的數據流進行解密,并將其通過網絡發送給另一個程序(該程序通常駐留在同一機器上,以避免本地網絡上的窺探攻擊)。
這樣,在必要時,您就可以很容易地運行未加密的程序,當您想要“嗅探”網絡,看看到底有什么東西正在通過網絡時,這一點很有用。
即使您是一個系統管理員,而不是一個開發者,Stunnel 對您來說也是一個強大的武器,因為它可以向不啟用 SSL 的服務器端軟件添加 SSL。例如,我已經使用 Stunnel 來保護 POP、SMTP 和 IMAP 服務器。唯一不太盡人意的地方是要使用這些服務器的安全版本,客戶機必須是可識別 SSL 的。
Stunnel 要求已經安裝了 OpenSSL。它已被移植到了 Windows,以及大多數 UNIX 平臺。
一旦安裝了 Stunnel,用它來保護服務器就很輕松。例如,您可以通過將常規服務綁定到本地主機使 IMAP 服務器啟用 SSL,然后在外部 IP 地址(假設 IMAP 服務器已經在運行,且外部地址為 192.168.100.1)運行 Stunnel:
- stunnel -d 192.168.100.1:imap2 -r 127.0.0.1:imap2
-d 標志指定我們希望用來運行自己的安全服務的端口。 imap2 字符串指定使用標準 IMAP 端口;我們也可以將其設為 143。Stunnel 檢查 "/etc/services" 文件以便將符號名映射到端口號。并非所有的機器都擁有這個文件(有些機器并不列出所有的標準服務),所以使用數字比使用服務名更方便。
-r 標志指定未加密的 IMAP 服務器運行所在的端口。
這個解決方案要求您的 IMAP 服務器只在回送(loopback)接口上偵聽。如果 IMAP 服務器綁定到 IP 地址“0.0.0.0”,那么它將偵聽機器上每個 IP 地址上的信息,包括 192.168.100.1;這會導致出現一條出錯消息,指出我們的安全服務端口已在使用中。大多數服務都可以配置為只綁定到一個接口。不然的話,可能要更改一行代碼。
另外,您可以將一個未加密的服務器設在一個非標準端口上。例如,您可以在端口 1143 上運行 IMAP,然后將安全的 IMAP 數據流轉發到該端口。一般情況下,您不希望其它機器上的用戶訪問您未加密的服務。運行服務的機器上的個人防火墻可以加強這種策略。
使用 Stunnel 來保護如 IMAP 等服務面臨的一個問題是服務器只接收來自我們提供的 Stunnel 服務器的連接。因此,所有的連接都看起來好象是來自本地機器。在 Linux 上,可以通過傳遞 -T 標志避開這個問題,傳遞 -T 標志可以使 Stunnel 服務器透明地代理信息包,這樣真正的服務器就可以得到接收到的所有信息包中的正確的源地址。#p#
用于客戶機的Stunnel
還可以使用客戶機的 Stunnel 與服務器連接,不過要多做一些工作。首先,必須生成 Stunnel 作為外部進程。在基于 UNIX 的系統上,執行這個操作的最好方法是 fork() 客戶機,并讓子進程 execv() stunnel 。父進程必須準備兩套文件描述符用來與子進程通信 ― 一對用于從 Stunnel 進程讀取數據,另一對用于通過網絡發送數據。這個工作量不小。實現這項功能的示例代碼,提供一個簡單的函數調用 run_cmd ,掩蓋潛在的復雜性; run_cmd 使用一個字符串指出要運行的命令,并返回一個 PIPE 對象,該對象有一個文件描述符,套接字使用該文件描述符進行讀寫操作:
- pipe.h:
- #ifndef POPEN_H__
- #define POPEN_H__
- #include <sys/types.h>
- #include <stdio.h>
- #define EXITVAL 127
- typedef struct pipe_st {
- FILE *read_ptr;
- FILE *write_ptr;
- pid_t pid;
- } PIPE;
- PIPE *run_cmd(char *cmd);
- int pipe_close(PIPE *p);
- #endif POPEN_H__
- pipe.c:
- #include <sys/wait.h>
- #include <unistd.h>
- #include <fcntl.h>
- #include <signal.h>
- #include <errno.h>
- #include <stdlib.h>
- #include "pipe.h"
- /* We allow double quotes and \ to escape spaces.
- * All backslashes are "processed", despite the value
- * of the next character. (Though \\ -> \).
- * We don't care if there's a missing trailing quote,
- * even if it should really be a syntax error.
- */
- static char **
- to_words(char *arg) {
- char **arr;
- char *p = arg;
- int nw = 1;
- int slc = 0;
- int slm = 0;
- char c;
- short quote = 0;
- char *cur;
- /* Build a rough approximation of the number of words,
- * simply so we don't malloc too low.
- */
- while((c = *p++)) {
- if(c == '"' || c == ' ') {
- nw++;
- if(slm < slc) slm = slc;
- slc = 0;
- }
- }
- arr = (char **)malloc(sizeof(char *)*(nw+1));
- quote = nw = slc = 0;
- p = arg;
- cur = (char *)malloc(sizeof(char)*(slm+1));
- arr[nw++] = cur;
- while((c = *p++)) {
- switch(c) {
- case '"':
- quote = !quote;
- continue;
- case ' ':
- if(quote) {
- *cur++ = c;
- slc++;
- continue;
- } else {
- if(!slc) continue;
- *cur = 0;
- cur = (char *)malloc(sizeof(char)*(slm+1));
- arr[nw++] = cur;
- slc = 0;
- continue;
- }
- case '\\':
- if(*p) {
- *cur++ = *p++;
- slc++;
- continue;
- }
- default:
- *cur++ = c;
- slc++;
- continue;
- }
- }
- *cur = 0;
- arr[nw] = 0;
- return arr;
- }
- PIPE *
- run_cmd(char *cmd) {
- int prpd[2];
- int pwpd[2];
- pid_t pid;
- char **args;
- PIPE *ret;
- args = to_words(cmd);
- if(pipe(prpd) < 0 || pipe(pwpd) < 0) {
- return 0; /* Pipe failed. */
- }
- pid = fork();
- switch(pid) {
- case -1:
- close(prpd[STDIN_FILENO]);
- close(prpd[STDOUT_FILENO]);
- close(pwpd[STDIN_FILENO]);
- close(pwpd[STDOUT_FILENO]);
- return 0; /* Fork failed. */
- /* Here we can only exit on error. */
- case 0:
- /* Child... */
- if(dup2(pwpd[STDIN_FILENO], STDIN_FILENO) < 0) {
- exit(EXITVAL);
- }
- if(dup2(prpd[STDOUT_FILENO], STDOUT_FILENO) < 0) {
- exit(EXITVAL);
- }
- close(pwpd[STDIN_FILENO]);
- close(pwpd[STDOUT_FILENO]);
- close(prpd[STDIN_FILENO]);
- close(prpd[STDOUT_FILENO]);
- execv(args[0], args);
- exit(EXITVAL);
- default:
- ret = (PIPE *)malloc(sizeof(PIPE));
- ret->read_ptr = ret->write_ptr = 0;
- ret->pid = pid;
- close(pwpd[0]);
- fcntl(pwpd[1], F_SETFD, FD_CLOEXEC);
- ret->write_ptr = fdopen(pwpd[1], "wb");
- if(!ret->write_ptr) {
- int old = errno;
- kill(pid, SIGKILL);
- close(pwpd[1]);
- waitpid(pid, 0, 0);
- errno = old;
- free(ret);
- return 0;
- }
- close(prpd[1]);
- fcntl(prpd[0], F_SETFD, FD_CLOEXEC);
- ret->read_ptr = fdopen(prpd[0], "rb");
- if(!ret->read_ptr) {
- int old = errno;
- kill(pid, SIGKILL);
- close(prpd[0]);
- waitpid(pid, 0, 0);
- errno = old;
- free(ret);
- return 0;
- }
- return ret;
- }
- }
- int
- pipe_close(PIPE *p) {
- int status;
- if(!(p->read_ptr || p->write_ptr)) {
- return -1;
- }
- if(p->read_ptr && fclose(p->read_ptr)) {
- return -1;
- }
- if(p->write_ptr && fclose(p->write_ptr)) {
- return -1;
- }
- if(waitpid(p->pid, &status, 0) != p->pid) {
- return -1;
- }
- p->read_ptr = p->write_ptr = 0;
- return status;
- }
#p#
例如,要使用此函數建立一個連接到上面啟用 SSL 的 IMAP 服務器的客戶機,我們可以編寫如下代碼:
- PIPE *p;
- p = run_cmd("stunnel -c -r 192.168.100.1:imap2 -A /etc/ca_certs -v 3");
在上面的代碼中,我們這樣調用 Stunnel:
- stunnel -c -r 192.168.100.1:imap2 -A /etc/ca_certs -v 3
最后兩個選項并非必需;客戶機可以不考慮這兩個選項進行連接。但是,如果我們省去了這兩個選項,客戶機將無法對服務器證書進行充分的驗證,會使客戶機隨時可能遭到 man-in-the-middle 攻擊。在這種攻擊中,有人創建了一個虛假服務器,使客戶機把它當作真正的服務器,而與之進行通信。虛假服務器代理到真正的服務器的連接,讀取所有的數據。使用現成的工具如 Dsniff(請參閱 參考資料)很容易發動這種攻擊。
v 參數指定驗證級別。缺省值為 0,適用于使用其它方法驗證客戶機的服務器,也是如此。但級別 1 和級別 2 更好一些。級別 1 檢查(非強制地)服務器證書是否有效,但可與無證書的服務器連接。級別 2 要求有效的服務器證書,但并不檢查證書是否由權威認證中心如 VeriSign 簽署。
A 參數指定一個必須包含可信證書列表的文件。一個服務器證書要被接受,它必須是在 A 參數指定的文件內,或者是一個用于簽署推薦證書(通常是來自權威認證中心,或稱 CA,如 VeriSign 的證書)的證書,該證書必須在指定的文件內。在 參考資料部分,您會找到幾個主要認證中心的當前有效證書的鏈接,您可以將它作為必需內容放在這個文件中。
在使用大型 CA 時,甚至這種方法也會出現問題。例如,確保某個特定的證書是由 VeriSign 簽署的是不錯的。但您如何確保證書是來自您想要連接的站點呢?
不幸的是,在寫這篇文章的時候,Stunnel 還不能使調用程序訪問驗證過的證書的信息。因此,您無法確定服務器正在使用的是誰的證書。例如,您可能如愿以償被連接到 Amazon,或者可能被連接到一個服務器,該服務器使用的是偷來的(但有效的)Microsoft 證書。
由于這種局限性,您只能局限于下面四個選擇:
希望沒有人發動 man-in-the-middle 攻擊(不正確的想法)。
在客戶機端對每個可能的服務器證書進行硬編碼(Hardcode)。
運行自己的認證中心,這樣可以動態地添加可信的服務器。
在應用程序中使用 SSL 代替外部的通道,這樣可以通過編程檢查。
即使這些解決方案都可使用,也沒有一個理想的。