國(guó)防科技大學(xué)計(jì)算機(jī)學(xué)院在讀碩士 2004 年 8 月
系統(tǒng)測(cè)試人員負(fù)責(zé)制定測(cè)試計(jì)劃并依照測(cè)試計(jì)劃進(jìn)行測(cè)試。這些測(cè)試包括功能性的測(cè)試(黑盒測(cè)試)和非功能性的測(cè)試(白盒測(cè)試)。測(cè)試人員需要良好的測(cè)試工具來(lái)輔助完成測(cè)試任務(wù),自動(dòng)化的測(cè)試工具將大幅度提高測(cè)試人員的工作效率和質(zhì)量。
本文主要通過(guò)對(duì)Linux PAM源代碼進(jìn)行分析,闡述了PAM的內(nèi)部實(shí)現(xiàn)機(jī)制和怎樣在應(yīng)用程序中應(yīng)用PAM進(jìn)行認(rèn)證,以及怎樣開發(fā)PAM服務(wù)模塊。
1 引言 身份認(rèn)證是操作系統(tǒng)安全的重要機(jī)制之一,系統(tǒng)通過(guò)認(rèn)證機(jī)制核查用戶的身份證明,并作為用戶進(jìn)入系統(tǒng)的判定條件,是防止惡意用戶進(jìn)入系統(tǒng)的第一道門檻。近年來(lái)認(rèn)證理論和技術(shù)得到了迅速發(fā)展,產(chǎn)生了各種認(rèn)證機(jī)制,如口令機(jī)制,RSA, DCE, kerberos認(rèn)證體制,S/Key和基于智能卡的身份認(rèn)證等。然而,當(dāng)系統(tǒng)中引入新的認(rèn)證機(jī)制時(shí),一些系統(tǒng)入口登錄服務(wù)如login, rlogin和telnet等應(yīng)用程序就必須改寫以適應(yīng)新的認(rèn)證機(jī)制。為了解決這個(gè)問(wèn)題,1995年Sun公司的Vipin Samar和 Charlie Lai提出了PAM(Pluggable Authentication Modules),并將其應(yīng)用在Solaris系統(tǒng)上。PAM框架將應(yīng)用程序與具體的認(rèn)證機(jī)制分離,使得系統(tǒng)改變認(rèn)證機(jī)制時(shí),不再需要修改采用認(rèn)證機(jī)制的應(yīng)用程序,而只要由管理員配置應(yīng)用程序的認(rèn)證服務(wù)模塊,極大地提高了認(rèn)證機(jī)制的通用性與靈活性。
現(xiàn)在大多數(shù)操作系統(tǒng)都采用PAM實(shí)現(xiàn)身份認(rèn)證,有Linux系統(tǒng)的Linux-PAM和FreeBSD5.x采用的OpenPAM(FreeBSD 4.x采用的是Linux-PAM)等。它們的實(shí)現(xiàn)原理一樣,只有實(shí)現(xiàn)細(xì)節(jié)不同而已。下面從PAM的應(yīng)用開發(fā)開始介紹。
2 PAM的應(yīng)用開發(fā)
2.1 PAM框架概覽 PAM即可插拔認(rèn)證模塊。它提供了對(duì)所有服務(wù)進(jìn)行認(rèn)證的中央機(jī)制,適用于login,遠(yuǎn)程登錄(telnet,rlogin,fsh,ftp,點(diǎn)對(duì)點(diǎn)協(xié)議(PPP)),su等應(yīng)用程序中。系統(tǒng)管理員通過(guò)PAM配置文件來(lái)制定不同應(yīng)用程序的不同認(rèn)證策略;應(yīng)用程序開發(fā)者通過(guò)在服務(wù)程序中使用PAM API(pam_xxxx( ))來(lái)實(shí)現(xiàn)對(duì)認(rèn)證方法的調(diào)用;而PAM服務(wù)模塊的開發(fā)者則利用PAM SPI來(lái)編寫模塊(主要是引出一些函數(shù)pam_sm_xxxx( )供PAM接口庫(kù)調(diào)用),將不同的認(rèn)證機(jī)制加入到系統(tǒng)中;PAM接口庫(kù)(libpam)則讀取配置文件,將應(yīng)用程序和相應(yīng)的PAM服務(wù)模塊聯(lián)系起來(lái)。PAM框架結(jié)構(gòu)如圖所示。
圖 PAM框架結(jié)構(gòu)圖
其中,pamh是一個(gè)pam_handle類型的結(jié)構(gòu),它是一個(gè)非常重要的處理句柄,是PAM與應(yīng)用程序通信的唯一數(shù)據(jù)結(jié)構(gòu),也是調(diào)用PAM接口庫(kù)API的唯一句柄。pam_handle數(shù)據(jù)結(jié)構(gòu)將在下面的源代碼分析一節(jié)的介紹。
另外,如上圖所示的服務(wù)模塊分auth(認(rèn)證管理)、account(賬號(hào)管理)、session(會(huì)話管理)、passwd(口令管理)四種類型,各個(gè)類型模塊的作用以及配置文件的四個(gè)組成部分模塊類型、控制標(biāo)志、模塊路徑、模塊參數(shù)等在很多講PAM的配置管理的文章里都有介紹,這里就不再贅述了。
2.2 在應(yīng)用程序中使用PAM認(rèn)證 每個(gè)使用PAM認(rèn)證的應(yīng)用程序都以pam_start開始,pam_end結(jié)束。PAM還提供了pam_get_item和pam_set_item共享有關(guān)認(rèn)證會(huì)話的某些公共信息,例如用戶名,服務(wù)名,密碼和會(huì)話函數(shù)。應(yīng)用程序在調(diào)用了pam_start ()后也能夠用這些APIs來(lái)改變狀態(tài)信息。實(shí)際做認(rèn)證工作的API函數(shù)有六個(gè)(以下將這六個(gè)函數(shù)簡(jiǎn)稱為認(rèn)證API):
- 認(rèn)證管理--包括pam_authenticate ()函數(shù)認(rèn)證用戶,pam_setcred ()設(shè)置,刷新,或銷毀用戶證書。
- 賬號(hào)管理--包括pam_acc_mgmt ()檢查認(rèn)證的用戶是否可以訪問(wèn)他們的賬戶,該函數(shù)可以實(shí)現(xiàn)口令有效期,訪問(wèn)時(shí)間限制等。
- 會(huì)話管理--包括pam_open_session ()和pam_close_session ()函數(shù)用來(lái)管理會(huì)話和記賬。例如,系統(tǒng)可以存儲(chǔ)會(huì)話的全部時(shí)間。
- 口令管理--包括pam_chauthok ()函數(shù)用來(lái)改變密碼。
下面看一個(gè)簡(jiǎn)單的login模擬程序:
/* 使用PAM所必需的兩個(gè)頭文件*/
#include <security/pam_appl.h>
#include <security/pam_misc.h>
void main(int argc, char *argv[], char **renvp)
{
/* 初始化,并提供一個(gè)回調(diào)函數(shù) */
if ((pam_start("login", user_name, &pam_conv, &pamh)) != PAM_SUCCESS)
exit(1);
/* 設(shè)置一些關(guān)于認(rèn)證用戶信息的參數(shù) */
pam_set_item(pamh, PAM_TTY, ttyn);
pam_set_item(pamh, PAM_RHOST, remote_host);
while (!authenticated && retry < MAX_RETRIES)
{
status = pam_authenticate(pamh, 0);/* 認(rèn)證,檢查用戶輸入的密碼是否正確 */
}
/* 認(rèn)證失敗則應(yīng)用程序退出*/
if (status != PAM_SUCCESS)
{
……
exit(1);
}
/* 通過(guò)了密碼認(rèn)證之后再調(diào)用賬號(hào)管理API,檢查用戶賬號(hào)是否已經(jīng)過(guò)期 */
if ((status = pam_acct_mgmt(pamh, 0)) != PAM_SUCCESS)
{
if (status == PAM_AUTHTOK_EXPIRED)
{
status = pam_chauthtok(pamh, 0); /* 過(guò)期則要求用戶更改密碼 */
if (status != PAM_SUCCESS)
exit(1);
}
}
/* 通過(guò)帳戶管理檢查之后則打開會(huì)話 */
if (status = pam_open_session(pamh, 0) != PAM_SUCCESS)
exit(status);
……
/* 建立認(rèn)證服務(wù)的用戶證書*/
status = pam_setcred(pamh, PAM_ESTABLISH_CRED);
if (status != PAM_SUCCESS)
exit(status);
……
pam_end(pamh, PAM_SUCCESS); /* PAM事務(wù)的結(jié)束 */
……
}
|
從上面程序中,我們可以了解到使用PAM認(rèn)證的一般流程,同時(shí)也可以看出PAM API使得使用認(rèn)證的應(yīng)用程序不僅不用關(guān)心底層使用的服務(wù)模塊,而且編寫起來(lái)簡(jiǎn)潔明了得多。
有關(guān)開發(fā)使用PAM的應(yīng)用程序更加詳細(xì)完整的闡述請(qǐng)參考The Linux-PAM Application Developers‘ Guide。
2.3 怎樣開發(fā)PAM服務(wù)模塊 首先在編寫的服務(wù)模塊的源程序里要包含下列頭文件:
#include <security/pam_modules.h>
|
PAM的服務(wù)模塊是一個(gè)一個(gè)的動(dòng)態(tài)鏈接庫(kù)文件(也可以是靜態(tài)庫(kù)),PAM接口庫(kù)通過(guò)dlopen來(lái)裝載這些庫(kù)。假設(shè)源程序名為pam_module-name.c,則需要用下列命令將其編譯成動(dòng)態(tài)鏈接庫(kù):
gcc -fPIC -c pam_module-name.c
ld -x --shared -o pam_module-name.so pam_module-name.o
|
選項(xiàng)-fPIC是指位置無(wú)關(guān)代碼(Position Independent Code),這類代碼支持大偏移。使用--shared選項(xiàng)將目標(biāo)代碼放進(jìn)共享目標(biāo)庫(kù)中。
四種類型的模塊各自要實(shí)現(xiàn)的函數(shù)如下表所示:
模塊類型 |
要實(shí)現(xiàn)的函數(shù) |
函數(shù)功能 |
認(rèn)證管理 |
PAM_EXTERN int pam_sm_authenticate(pam_handle_t *pamh, int flags, int argc, const char **argv) |
認(rèn)證用戶 |
PAM_EXTERN int pam_sm_setcred(pam_handle_t *pamh, int flags, int argc, const char **argv) |
設(shè)置用戶證書 |
賬號(hào)管理 |
PAM_EXTERN int pam_sm_acct_mgmt(pam_handle_t *pamh, int flags, int argc, const char **argv) |
賬號(hào)管理 |
會(huì)話管理 |
PAM_EXTERN int pam_sm_open_session(pam_handle_t *pamh, int flags, int argc, const char **argv) |
打開會(huì)話 |
PAM_EXTERN int pam_sm_close_session(pam_handle_t *pamh, int flags, int argc, const char **argv) |
關(guān)閉會(huì)話 |
口令管理 |
PAM_EXTERN int pam_sm_chauthtok(pam_handle_t *pamh, int flags, int argc, const char **argv) |
設(shè)置口令 |
當(dāng)然同一個(gè)服務(wù)模塊可以同時(shí)屬于多種類型,只要這些類型模塊要實(shí)現(xiàn)的函數(shù)都實(shí)現(xiàn)了就可以,比如PAM自帶的經(jīng)典口令認(rèn)證機(jī)制模塊pam_unix.so 就可以支持四種模塊類型。
下面來(lái)看一個(gè)最簡(jiǎn)單的pam_deny模塊的源程序pam_deny.c:
1. #define PAM_SM_AUTH
2. #define PAM_SM_ACCOUNT
3. #define PAM_SM_SESSION
4. #define PAM_SM_PASSWORD
5. #include "../../libpam/include/security/pam_modules.h"
6. /* --- 認(rèn)證管理函數(shù)的實(shí)現(xiàn)--- */
7. PAM_EXTERN int pam_sm_authenticate(pam_handle_t *pamh,int flags,int argc
8. ,const char **argv)
9. {
10. return PAM_AUTH_ERR;
11. }
12. PAM_EXTERN int pam_sm_setcred(pam_handle_t *pamh,int flags,int argc
13. ,const char **argv)
14. {
15. return PAM_CRED_UNAVAIL;
16. }
17. /* --- 賬號(hào)管理函數(shù)的實(shí)現(xiàn) --- */
18. PAM_EXTERN int pam_sm_acct_mgmt(pam_handle_t *pamh,int flags,int argc
19. ,const char **argv)
20. {
21. return PAM_ACCT_EXPIRED;
22. }
23. /* --- 口令管理函數(shù)的實(shí)現(xiàn) --- */
24. PAM_EXTERN int pam_sm_chauthtok(pam_handle_t *pamh,int flags,int argc
25. ,const char **argv)
26. {
27. return PAM_AUTHTOK_ERR;
28. }
29. /* --- 會(huì)話管理函數(shù)的實(shí)現(xiàn) --- */
30. PAM_EXTERN int pam_sm_open_session(pam_handle_t *pamh,int flags,int argc
31. ,const char **argv)
32. {
33. return PAM_SYSTEM_ERR;
34. }
35. PAM_EXTERN int pam_sm_close_session(pam_handle_t *pamh,int flags,int argc
36. ,const char **argv)
37. {
38. return PAM_SYSTEM_ERR;
39. }
40. /* 模塊定義結(jié)束 */
41. /* 靜態(tài)模塊數(shù)據(jù) */
42. #ifdef PAM_STATIC
43. struct pam_module _pam_deny_modstruct = {
44. "pam_deny",
45. pam_sm_authenticate,
46. pam_sm_setcred,
47. pam_sm_acct_mgmt,
48. pam_sm_open_session,
49. pam_sm_close_session,
50. pam_sm_chauthtok
51. };
52. #endif
|
很容易看出,pam_deny模塊支持四種模塊類型。前4行包含靜態(tài)模塊的一些原型申明,第37-39行實(shí)現(xiàn)了四種模塊類型的函數(shù),因?yàn)檫@只是一個(gè)簡(jiǎn)單的拒絕服務(wù)的模塊,所以這些函數(shù)只是簡(jiǎn)單地返回認(rèn)證錯(cuò)或系統(tǒng)錯(cuò)等PAM錯(cuò)誤。最后幾行定義了該程序被編譯成靜態(tài)模塊所需的一個(gè)模塊數(shù)據(jù)結(jié)構(gòu)。
因此,PAM SPI使得服務(wù)模塊的開發(fā)也相當(dāng)簡(jiǎn)單和專一,因?yàn)榉?wù)模塊不再需要考慮和應(yīng)用程序的交互,只要將自己采用的算法實(shí)現(xiàn)好就可以了。
模塊源程序可用的flags參數(shù)值和返回值的定義這里不作全面介紹,有興趣者請(qǐng)參考The Linux-PAM Module Writers‘ Guide。
3 PAM接口庫(kù)源代碼分析 上面我們介紹了怎樣使用PAM和怎樣開發(fā)PAM服務(wù)模塊,要想對(duì)PAM的內(nèi)部機(jī)制有個(gè)透徹的理解,還需要進(jìn)一步分析PAM接口庫(kù)的代碼。下面基于Linux 的pam-0.75-40.src.rpm包所得的源代碼進(jìn)行分析。
3.1 PAM接口庫(kù)主要數(shù)據(jù)結(jié)構(gòu) 先看一下PAM接口庫(kù)用到的一些主要數(shù)據(jù)結(jié)構(gòu)。pam_handle和其他幾個(gè)主要的數(shù)據(jù)結(jié)構(gòu)(見(jiàn)../libpam/pam_private.h)及其之間的關(guān)系如下圖所示。
其中pam_handle包含認(rèn)證的用戶的token、用戶名、應(yīng)用程序名、終端名等信息,以及一個(gè)service結(jié)構(gòu)(handlers);前面幾節(jié)提到的pamh句柄就是一個(gè)pam_handle結(jié)構(gòu)。service結(jié)構(gòu)包含服務(wù)模塊的相關(guān)信息,各個(gè)域的含義是:
1. module--該結(jié)構(gòu)包含裝載的模塊的名字、類型(靜態(tài)或動(dòng)態(tài)模塊)、鏈接句柄(裝載模塊時(shí)的句柄)。
2. modules_allocated--分配的模塊數(shù)。
3. modules_used--已使用的模塊數(shù)。
4. handlers_loaded--是否對(duì)操作(handlers結(jié)構(gòu))進(jìn)行了初始化,handlers結(jié)構(gòu)和初始化handlers見(jiàn)下面的介紹。
5. conf--由應(yīng)用程序相對(duì)應(yīng)的配置文件指定的服務(wù)模塊的handlers。
6. other--為缺省配置文件指定的服務(wù)模塊的handlers。
handlers結(jié)構(gòu)包含六個(gè)handler結(jié)構(gòu)鏈表的指針,六個(gè)指針?lè)謩e對(duì)應(yīng)六種不同的認(rèn)證API;libpam通過(guò)這些指針找到對(duì)應(yīng)模塊的SPI服務(wù)函數(shù)。如下表所示:
handler指針 |
API函數(shù) |
SPI函數(shù) |
authenticate |
pam_authenticate( ) |
pam_sm_authenticate( ) |
setcred |
pam_setcred( ) |
pam_sm_setcred( ) |
acct_mgmt |
pam_acct_mgmt( ) |
pam_sm_acct_mgmt( ) |
open_session |
pam_open_session( ) |
pam_sm_open_session( ) |
close_session |
pam_close_session( ) |
pam_sm_close_session( ) |
chauthtok |
pam_chauthtok( ) |
pam_sm_chauthtok( ) |
handler數(shù)據(jù)結(jié)構(gòu)是最直接保存服務(wù)模塊的SPI服務(wù)函數(shù)的地址及參數(shù)的結(jié)構(gòu),其包含的主要的域的含義如下:
1. (*func)--該函數(shù)指針指向handlers所裝載的服務(wù)模塊的服務(wù)函數(shù)。
2. argc、**argv--分別為*func所指向的函數(shù)的參數(shù)個(gè)數(shù)和參數(shù)列表。
3. *next--指向堆棧模塊中的下一個(gè)服務(wù)模塊的服務(wù)函數(shù)。由此指針形成所有堆棧模塊的服務(wù)函數(shù)鏈。
3.2 PAM接口庫(kù)重要內(nèi)部函數(shù)分析 PAM接口庫(kù)中有一系列_pam開頭的內(nèi)部函數(shù),那些APIs主要是調(diào)用這些內(nèi)部函數(shù)來(lái)完成其功能的。
int _pam_add_handler(pam_handle_t *pamh, int must_fail, int other, int type, int *actions, const char *mod_path, int argc, char **argv, int argvlen)
該函數(shù)負(fù)責(zé)加載服務(wù)模塊的SPI函數(shù)。這個(gè)函數(shù)代碼很長(zhǎng)有300多行,這里就不列舉了。其主要步驟如下:
1. 根據(jù)mod_path提供的模塊路徑裝載服務(wù)模塊(dl_open)。
2. 由type確定的類型來(lái)決定要裝入的SPI函數(shù)名并找到該函數(shù)(dlsym)的地址。type類型對(duì)應(yīng)配置文件中的服務(wù)模塊的四種類型標(biāo)記,type的值及其對(duì)應(yīng)的要裝入的SPI函數(shù)名見(jiàn)下表。
模塊類型 |
type |
SPI函數(shù) |
認(rèn)證管理模塊 |
PAM_T_AUTH |
pam_sm_authenticate pam_sm_setcred |
會(huì)話管理模塊 |
PAM_T_SESS |
pam_sm_open_session pam_sm_close_session |
賬號(hào)管理模塊 |
PAM_T_ACCT |
pam_sm_acct_mgmt |
口令管理模塊 |
PAM_T_PASS |
pam_sm_chauthtok |
3. 新分配一個(gè)handler結(jié)構(gòu),將dlsym找到的函數(shù)的地址賦給該handler結(jié)構(gòu)的func域,并填充其他的結(jié)構(gòu)信息。
4. 將新分配的handler結(jié)構(gòu)插入到handlers的對(duì)應(yīng)handler結(jié)構(gòu)鏈表中。
_pam_parse_conf_file函數(shù)負(fù)責(zé)讀并分析PAM配置文件,將相關(guān)的信息填充到pamh句柄中,并調(diào)用_pam_add_handlers加載服務(wù)模塊的服務(wù)函數(shù)。
_pam_init_handlers函數(shù)主要做handler的初始化工作,先判斷handler是否已初始化,若沒(méi)有則調(diào)用_pam_parse_conf_file分析配置文件加載服務(wù)模塊的服務(wù)函數(shù)。
_pam_dispatch_aux(pam_handle_t *pamh, int flags, struct handler *h,……)
該函數(shù)負(fù)責(zé)遍歷執(zhí)行模塊堆棧中的每一個(gè)服務(wù)模塊對(duì)應(yīng)的SPI函數(shù),即執(zhí)行h指向的handler結(jié)構(gòu)鏈表中的每一個(gè)func指向的函數(shù),并返回模塊堆棧的結(jié)果值。
int _pam_dispatch(pam_handle_t *pamh, int flags, int choice)
該函數(shù)首先通過(guò)調(diào)用_pam_init_handlers將模塊調(diào)度請(qǐng)求轉(zhuǎn)換為指向?qū)嶋H要運(yùn)行的模塊堆棧函數(shù)鏈表的指針,并將該指針傳遞給_pam_dispatch_aux函數(shù)來(lái)遍歷模塊堆棧執(zhí)行服務(wù)函數(shù)。該函數(shù)是實(shí)現(xiàn)六個(gè)認(rèn)證API函數(shù)的主要部分和公共調(diào)用的函數(shù),通過(guò)choice選項(xiàng)來(lái)區(qū)分是哪個(gè)認(rèn)證API函數(shù)。
下面分析我們最關(guān)心的部分,也即那些認(rèn)證API是怎樣找到和調(diào)度配置文件中配置的服務(wù)模塊的?
3.3 PAM認(rèn)證API的實(shí)現(xiàn) 為了更加清楚地說(shuō)明PAM接口庫(kù)是怎樣來(lái)調(diào)度使用模塊的,下面給出了pam_authenticate函數(shù)執(zhí)行的流程圖:
其他的認(rèn)證API函數(shù)(pam_open_session等)執(zhí)行過(guò)程前面五個(gè)步驟同上圖,只是在最后一步時(shí)傳遞給_pam_dispatch_aux的指針參數(shù)不同,傳遞3.1節(jié)表中每個(gè)API函數(shù)相對(duì)應(yīng)的那個(gè)handler型指針,然后執(zhí)行相對(duì)應(yīng)的SPI服務(wù)函數(shù)鏈。
4 小結(jié) 無(wú)論從PAM的應(yīng)用開發(fā)還是它的實(shí)現(xiàn)原理來(lái)看,這個(gè)框架及其思想都是非常完美的,所以幾乎各種版本的 UNIX 系統(tǒng)都提供對(duì) PAM 的支持。本文通過(guò)對(duì)Linux-PAM 進(jìn)行了深入仔細(xì)的分析,闡述了它的內(nèi)部實(shí)現(xiàn)機(jī)制,并且講述了怎樣在應(yīng)用程序中使用PAM和怎樣開發(fā)PAM服務(wù)模塊,希望能對(duì)大家做認(rèn)證相關(guān)的開發(fā)工作有所幫助。
參考資料
- [Vipin Samar, Charlie Lai, 1995] Making Login Services Independent of Authentication Technologies. Sun Technical report.
- [Andrew G. Morgan, 2001] The Linux-PAM Module Writers‘ Guide. Linux-PAM Documentation.
- [Andrew G. Morgan, 2001] The Linux-PAM Module Writers‘ Guide. Linux-PAM Documentation.
關(guān)于作者 程衛(wèi)芳,國(guó)防科技大學(xué)計(jì)算機(jī)學(xué)院在讀碩士,研究方向?yàn)榘踩僮飨到y(tǒng)。 Email: desertbow@sina.com |
|