深入 Linux PAM 體系結構(轉)

post0發表於2007-08-11
深入 Linux PAM 體系結構(轉)[@more@]

本文闡述了 Linux-PAM 的概念,同時還與讀者一道分析了 Linux-PAM 的體系結構,作者希望籍此以加深讀者對 Linux-PAM 的理解,以便對其有更深層的把握。

一、什麼是Linux-PAM

為安全起見,計算機系統只有經過授權的合法使用者才能訪問,在這裡如何正確鑑別使用者的真實身份是一個關鍵的問題。所謂使用者鑑別,就是使用者向系統以一種安全的方式提交自己的身份證明,然後由系統確認使用者的身份是否屬實的過程。換句話說,使用者鑑別是系統的門戶,每個使用者進入到系統中都必須經過鑑別這一道關。

最初,Linux系統的使用者鑑別過程就像各種Unix系統的一樣:系統管理員為使用者建立一個帳號併為其指定一個口令,使用者用此指定的口令登入後重新設定自己的口令,這樣使用者就具有了一個只有他自己知道的秘密口令。一般情況下,使用者的口令經過加密處理後存放於 /etc/passwd 檔案中。使用者登入時,登入服務程式提示使用者輸入其使用者名稱和口令,然後將口令加密並與 /etc/passwd 檔案中對應帳號的加密口令進行比較,如果口令相匹配,說明使用者的身份屬實並允許此使用者訪問系統。這種思想基於只有使用者自己知道他的口令,所以輸入的口令是正確的話,那麼系統就認定他是所聲稱的那個人。

後來,還採用了許多其他的鑑別使用者的方法,如用於網路環境的 Kerberos 以及基於智慧卡的鑑別系統等。但是這些鑑別方案有一個通病:實現鑑別功能的程式碼通常作為應用程式的一部分而一起編譯,這樣問題就來了------如果發現所用演算法存在某些缺陷或想採用另一種鑑別方法時,使用者不得不重寫(修改或替換)然後重新編譯原程式。很明顯,我們原先的鑑別方案缺乏靈活性,這裡的牽一髮而動全身的情形很是讓人惱火。

鑑於以上原因,人們開始尋找一種更佳的替代方案:一方面,將鑑別功能從應用中獨立出來,單獨進行模組化設計,實現和維護;另一方面,為這些鑑別模組建立標準 API,以便各應用程式能方便的使用它們提供的各種功能;同時,鑑別機制對其上層使用者(包括應用程式和終端使用者)是透明的。直到 1995 年,SUN 的研究人員提出了一種滿足以上需求的方案--外掛式鑑別模組(PAM)機制並首次在其作業系統 Solaris 2.3 上部分實現。外掛式鑑別模組(PAM)機制採用模組化設計和外掛功能,使得我們可以輕易地在應用程式中插入新的鑑別模組或替換原先的元件,而不必對應用程式做任何修改,從而使軟體的定製、維持和升級更加輕鬆--因為鑑別機制與應用程式之間相對獨立。應用程式可以透過 PAM API 方便的使用 PAM 提供的各種鑑別功能,而不必瞭解太多的底層細節。此外,PAM的易用性也較強,主要表現在它對上層遮蔽了鑑別的具體細節,所以使用者不必被迫學習各種各樣的鑑別方式,也不必記住多個口令;又由於它實現了多鑑別機制的整合問題,所以單個程式可以輕易整合多種鑑別機制如 Kerberos 鑑別機制和 Diffie - Hellman 鑑別機制等,但使用者仍可以用同一個口令登入而感覺不到採取了各種不同鑑別方法。

在廣大開發人員的努力下,各版本的 UNIX 系統陸續提供對 PAM 的支援。其中,Linux-PAM 是專門為 Linux 機器實現的,包括 Caldera 1.3、2.2、Debian 2.2、Turbo Linux 3.6、Red Hat 5.0 以及 SuSE 6.2 及它們的後續版本都提供對 PAM 的支援。FreeBSD 從 3.1 版開始支援 PAM。需要注意的是,除了具體實現不同外,各種版本 Unix 系統上的 PAM 的框架是相同的,所以我們在這裡介紹的 Linux-PAM 框架知識具有普遍性。因此在下文介紹其框架的過程中可以看到,我們並沒有刻意區分 PAM 與 Linux-PAM 這兩個術語。

二、PAM 的分層體系結構

PAM 為了實現其外掛功能和易用性,它採取了分層設計思想:讓各鑑別模組從應用程式中獨立出來,然後透過PAM API作為兩者聯絡的紐帶,這樣應用程式就可以根據需要靈活地在其中"插入"所需鑑別功能模組,從而真正實現了"鑑別功能,隨需應變"。實際上,這一思路非常符合軟體設計中的"高內聚,低耦合"這一重要思想,PAM 的體系如下簡圖所示:

[myimg]upload/image01.png[/myimg]

圖1 PAM體系結構

從上圖可以看出,PAM API 起著承上啟下的作用,它是應用程式和鑑別模組之間聯絡的紐帶:當應用程式呼叫 PAM API 時,應用介面層按照配置檔案 pam.conf 的規定,載入相應的鑑別模組。然後把請求(即從應用程式那裡得到的引數)傳遞給底層的鑑別模組,這時鑑別模組就可以根據要求執行具體的鑑別操作了。當鑑別模組執行完相應操作後,將結果返回給應用介面層,然後由介面層根據配置的具體情況將來自鑑別模組的應答返回給應用程式。

上面描述了 PAM 的各個組成部分,以及它們作為整體的運作機理。下面將對 PAM 的關鍵的低二層分別加以介紹。

三、第一層:模組層

模組層處於整個結構的最底層,它向上為介面層提供使用者鑑別等服務,也就是說所有具體的鑑別工作都是由該層的模組來完成的。對於應用程式,有些不但需要驗證使用者的口令,還可能要求驗證使用者的帳戶是否已經過期。此外,有些應用程式也許還會要求記錄當前會話的有關資訊或改變口令等,所以 PAM 在模組層除了提供鑑別模組外,同時提供了支援帳戶管理、會話管理以及口令管理功能的模組。當然,這四種模組並不是所有應用程式所必需的,而是根據需要靈活取捨,比如雖然 login 可能要求訪問所有四種模組,但是 su 可能僅僅需要使用鑑別元件即可。至於如何取捨則涉及到介面層的 PAM API和配置檔案,這部分內容將在下文中加以介紹。

四、第二層:應用介面層

應用介面層位於 PAM 結構的中間部分,它向上為應用程式遮蔽了使用者鑑別等過程的具體細節,向下呼叫模組層中的具體模組所提供的特定服務。由圖1可以看出,它主要由 PAM API 和配置檔案兩部分組成,下面將逐一介紹。

PAM API 可以分為兩類,一類是用於呼叫下層特定模組的介面,這類介面與底層的模組相對應:

1. 鑑別類介面:pam_authenticate()用於鑑別使用者,pam_setcred()用於修改使用者的秘密資訊。

2. 帳號類介面:pam_acct_mgmt()檢查受鑑別的使用者所持帳戶是否有權登陸系統,以及該帳戶是否已過期等。

3. 會話類介面:包括用於會話管理和記帳的 pam_open_session()和 pam_close_session()函式。

4. 口令類介面:包括用於修改使用者口令的 pam_chauthtok()。

第二類介面通常並不與底層模組一一對應,它們的作用是對底層模組提供支援以及實現應用程式與模組之間的通訊等。具體如下:

1. 管理性介面

每組 PAM 事務從 pam_start()開始,結束於 pam_end()函式。介面 pam_get_item()和 pam_set_item()用來讀寫與 PAM 事務有關的狀態資訊。同時,能夠用 pam_str()輸出 PAM 介面的出錯資訊。

2. 應用程式與模組間的通訊介面

在應用程式初始化期間,某些諸如使用者名稱之類的資料可以透過 pam_start()將其存放在PAM介面層中,以備將來底層模組使用。另外,底層模組還可以使用 pam_putenv()嚮應用程式傳遞特定的環境變數,然後應用程式利用 pam_getenv() 和 pam_getenvlist() 讀取這些變數。

3. 使用者與模組間的通訊介面

pam_start()函式可以透過會話式的回撥函式,讓底層模組透過它們讀寫模組相關的鑑別資訊,比如以應用程式所規定的方式提示使用者輸入口令。

4. 模組間通訊介面

儘管各模組是獨立的,但是他們仍然能夠透過 pam_get_item()和 pam_set_item()介面共享某些與鑑別會話有關的公用資訊,諸如使用者名稱、服務名、口令等。此外,這些API還可以用於在呼叫 pam_start()之後,讓應用程式修改狀態資訊。

5. 讀寫模組狀態資訊的介面

介面 pam_get_data()和 pam_set_data()用以按照PAM控制程式碼要求訪問和更新特定模組的資訊。此外,還可以在這些模組後附加一個清除資料函式,以便當呼叫 pam_end()時清除現場。

由於 PAM 模組隨需載入,所以各模組始化任務在第一次呼叫時完成。如果某些模組的清除任務必須在鑑別會話結束時完成,則它們應該使用 pam_set_data()規定清除函式,這些執行清除任務的函式將在應用程式呼叫 pam_end()介面時被呼叫。

五、配置檔案

我們注意到,配置檔案也放在了在應用介面層中,它與 PAM API 配合使用,從而達到了在應用中靈活插入所需鑑別模組的目的。它的作用主要是為應用選定具體的鑑別模組,模組間的組合以及規定模組的行為。下面是一個示例配置檔案:

[myimg]upload/image02.png[/myimg]

圖2.示例配置檔案

我們可以看到,配置檔案有許多登記項(每行對應一個登記項)組成,每一行又分為五列(每列對應一欄),詳細解釋如下:

第一欄,service表示使用PAM的應用程式,比如login、passwd、rlogin等。這一欄中的OTHER表示所有沒在該檔案中顯式列出的應用。也就是說,如果所有程式具有相同的需求,整個配置檔案只需要一行即可,並且該行的第一欄為OTHER。本例中,因為所有應用程式使用相同的會話模組,所以實際上可以用單行,即

" OTHER auth required pam_unix_auth.so"

來代替檔案中的這些行:

"login session required pam_unix_session.so

ftp session required pam_unix_session.so

telnet session required pam_unix_session.so"。

第二欄,module_type指明程式所用PAM底層模組的型別:auth表示鑑別類模組;account表示帳戶類模組;session表示會話類模組;password表示口令類模組。注意,每行只能指定一種型別模組,如果程式需要多種模組的話,可在多行中分別規定。

第三欄,control_flag規定如何處理模組的成功和失敗情況。單個應用程式可以呼叫多種底層模組,這通常稱為"堆疊",對應於某程式的按照配置檔案中出現順序執行的所有模組成為"堆",堆中的各模組的地位與出錯時的處理由control_flag欄的取值決定,它的五種可能的取值分別為 required、Requisite、sufficient或_optional,現介紹如下:

required--它表示該模組的成功是使用者透過鑑別的必要條件,換句話說,只有當對應於應用程式的所有帶required標記的模組全部成功後,該程式才能透過鑑別。同時,如果任何帶required標記的模組出現了錯誤,PAM並不立刻將錯誤訊息返回給應用程式,而是在所有模組都呼叫完畢後才將錯誤訊息返回撥用它的程式。

Requisite--它與required相仿,只有帶此標記的模組返回成功後,使用者才能透過鑑別,不同之處在於其一旦失敗就不再執行堆中後面的其它模組,並且鑑別過程到此結束。

optional--它表示即便該模組失敗,使用者仍能透過鑑別。在PAM體系中,帶有該標記的模組失敗後將繼續處理下一模組。

sufficient--它表示該模組取得成功是使用者透過鑑別的充分條件,也就是說只要標記為sufficient的模組一旦成功,那麼PAM便立即嚮應用程式返回成功而不必嘗試任何其他模組。當標記為sufficient的模組失敗時,sufficient模組當做optional對待。

第四欄,module_path指出PAM模組的位置。

第五欄,options用於向特定模組傳遞相關的選項,然後由模組分析解釋這些任選項。比如使用此欄開啟模組除錯,或向某模組傳遞諸如超時值之類的引數等。另外,它還用於支援下文所述的口令對映技術。

如果任一欄出現錯誤,或某模組沒有找到,那麼所在行被忽略並將其作為嚴重錯誤進行記錄。

本例中,login程式使用UNIX口令模組進行鑑別,而ftp程式卻使用S/Key模組進行鑑別。如我們想改變ftp程式的鑑別方法,比如也用UNIX口令模組進行鑑別,那麼我們不必改動源程式,只需將配置檔案中的

ftp auth required pam_skey_auth.so debug

改為

ftp auth required pam_unix_auth.so debug

這樣,當使用者使用ftp時,將用傳統的UNIX口令鑑別方式來驗證其身份。由此可見,在PAM體制下為應用程式改變鑑別機制是一件輕鬆的事情。另外,PAM體制的堆疊功能還使得應用程式能夠支援多種鑑別機制,如下例中的login程式在配置檔案中先後出現了三項與鑑別有關的登記項:

[myimg]upload/image03.png[/myimg]

圖3.示例配置檔案

當login程式執行時先用pam_unix.so模組即傳統的UNIX口令方式鑑別使用者,然後再呼叫pam_kerb.so模組即 Kerberos對使用者進行鑑別,最後用pam_rsa.so模組即RSA方式鑑別使用者。在按上述順序鑑別使用者的過程中,如果pam_unix.so模組鑑別失敗,它將繼續呼叫下面的模組進行鑑別而非立刻向login程式返回錯誤訊息;pam_kerb.so模組也按同樣方式處理,直到順序處理完最後一個 pam_rsa.so模組後,PAM才將前面出現的錯誤資訊返回給login程式。對於該配置,即使pam_rsa.so模組順利透過,只要 pam_unix.so模組和pam_kerb.so模組中有一個出現錯誤,使用者就不能透過鑑別;相反,即使pam_rsa.so模組失敗,只要 pam_unix.so模組和pam_kerb.so模組都透過了,使用者也能透過鑑別。

六、口令對映

在同一個機器上使用多個鑑別機制,尤其是一個應用程式整合多種鑑別機制可能導致使用者需要記憶多個口令,這會讓使用者覺得很不舒服。雖然可以讓所有機制使用相同的口令來獲取易用性,但是這將削弱系統的安全性--如果其中任何一個機制的口令洩露了,則所有機制都會受到牽累。此外,不同的鑑別機制在口令長度、容許的字元、更新間隔、有效期等方面可能具有他們特有的要求,這些要求也是為多鑑別機制使用同一個口令必須考慮的一個問題。

PAM為我們提供了這樣一種解決方案,它不排除為所有鑑別機制共用一個口令,同時允許透過口令對映技術讓每個機制使用不同的口令。該方案用使用者的" 主口令"加密其他的"副口令",並且將這些經過加密的副口令存放在一個使用者能訪問的地方。主口令一旦經過驗證,鑑別模組就能用它解密那些加密的副口令從而獲得相應口令,然後將所需口令傳遞給鑑別模組。這稱為"口令對映"。如果口令對映出現錯誤,或如果對映不存在,那麼各鑑別模組應該提示使用者輸入口令。為支援口令對映,主口令應儲存在PAM第二層並且在需要時由其提供給堆疊的各個鑑別模組。同時,口令要在pam_authenticate函式返回之前清除。為了保障口令對映的安全,主口令必須足夠強壯,可以考慮使其的長度更長、組成口令字元的型別多樣化並使用混合型別的字元組成口令等有效措施。

口令如何加密及其儲存完全取決於具體的實現:它能夠將加密的副口令(也稱作"對映口令")儲存在可靠或不可靠的地方,諸如智慧卡、本地檔案或目錄服務。當然,如果加密的口令儲存在一個不可靠的允許公共訪問的地方,會留下受到字典攻擊的隱患。

為實現口令對映,所有鑑別模組應支援以下四個對映選項∶

1. use_first_pass∶它表示當該模組執行時不提示使用者輸入口令,而將該模組之前的提示使用者輸入的主口令作為它們的公共口令進行驗證。如果使用者沒能透過主口令的鑑別,則該模組不提示使用者輸入口令。此選項一般說來在系統管理員想強制用同一個口令透過多模組時使用。

2. try_first_pass∶除了如果主口令不正確,將提示使用者輸入口令之外,它的用法與use_first_pass相同。

3. use_mapped_pass∶它表示使用口令對映技術得到此模組的有效口令。也就是說,該模組執行時不提示使用者輸入口令,而是用對映口令即用主口令解密得到的該模組的副口令作為本模組的口令輸入進行驗證。如果在此之前使用者沒能透過主口令的鑑別,則該模組也不會提示輸入口令。

4. try_mapped_pass∶除了如果主口令不正確,它將提示使用者輸入口令之外,該項與use_mapped_pass用法相同。

當口令更換後,PAM會儲存所有新舊口令,並且使有關模組能夠訪問到它們。其他模組能夠使用此資訊更新加密的口令而不必強制使用者再次輸入口令。

現以下面的配置檔案為例講解口令對映:

[myimg]upload/image04.png[/myimg]

圖4. 示例配置檔案

在這裡login程式整合了三種鑑別方式:傳統UNIX口令鑑別、Kerberos和RSA鑑別,但通常情況下使用者僅輸入一次口令便能透過鑑別了。當程式呼叫pam_unix.so模組時,PAM提示使用者輸入他們的UNIX口令,然後由pam_kerb.so模組對使用者輸入的UNIX口令進行鑑別。繼而呼叫pam_kerb.so模組,由於該模組的選項為use_mapped_pass,它將利用口令對映機制進行認證,也就是說,如果UNIX口令鑑別透過的話,就將其作為pam_kerb.so模組的主口令來解密其對應的對映口令從而進行Kerberos鑑別。如果pam_unix.so模組所需口令沒能透過驗證,則無法進行口令對映,那麼PAM將直接呼叫下一鑑別模組而不提示使用者輸入其Kerberos口令。最後一個模組的選項為 use_first_pass,所以pam_rsa.so模組將使用前邊輸入的主口令來鑑別使用者,如果口令錯誤也不提示使用者輸入RSA口令。所以,只要第一次輸入的口令是正確的,並且對映口令存在,則一個口令便足以透過鑑別。

七、結束語

Linux-PAM是一種使用靈活功能強大的使用者鑑別機制,本文對它的組成結構以及各部分之間的關係進行了相應的分析,希望對讀者理解PAM的機制有所幫助。

關於作者

韓波,自由撰稿人,有近十年的C語言程式設計經驗,主要感興趣的領域為TCP/IP協議,Linux核心以及資訊保安技術。作為自由撰稿人,其力求在不影響問題實質的前提下,儘量將要表達的內容以通俗易懂的形式擺在讀者的面前。您可以透過hbzzx2001@yahoo.com.cn與他聯絡。

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/8225414/viewspace-945676/,如需轉載,請註明出處,否則將追究法律責任。

相關文章