LDAP/SASL/GSSAPI/Kerberos編程API(2)--krb5客戶端

krb5 API有兩個可用的庫:MIT和Heimdal,兩個庫的API不一樣,一方客戶端的API連接上另一方服務端基本上是沒問題的.
API中的kadmin兩個庫則是完全不兼容,可從MIT和Heimdal兩個的kadmin應用工具看出,連接對方的kadmin服務端是不成功的.
kadmin目的是爲遠程操控Kerberos服務器,一般我們開發Kerberos應用很少以此爲目標,都是直接使用它們各自的kadmin應用工具,所以kadmin不兼容也沒多大問題.
我們的目標是Kerberos認證功能,所以使用MIT或是Heimdal都沒問題.

MIT是主流,本文以此爲例

一.實驗環境
平臺 : debian 11

我已事先安裝好一臺Kerberos服務器(KDC),領域爲CTP.NET,並創建了[email protected]用戶主體.

二.客戶機安裝開發庫
root@debian:/# apt-get install libkrb5-dev

三.最簡單krb5認證--不生成票據
1.源代碼
//源文件名:krbonlylogin.c

#include <stdio.h>
#include <krb5.h>
int main(void)
{
    krb5_context context = NULL;
    krb5_error_code krberr;
    krb5_principal kprincpw = NULL;
    krb5_creds * my_creds_ptr = NULL;
    krb5_creds my_creds;
    const char * errmsg;
    krberr = krb5_init_context(&context);

    if (krberr) { 
            errmsg = krb5_get_error_message(NULL, krberr); 
            printf("Err: Kerberos context initialization failed -> %s\n", errmsg); 
            goto cleanup; 
        } 

    krberr = krb5_parse_name(context, "[email protected]", &kprincpw); //用戶主體

    if (krberr) { 
            errmsg = krb5_get_error_message(context, krberr); 
            printf("Err: Failed to parse princpal %s -> %s\n", errmsg); 
            goto cleanup; 
        } 

    const char *password="linlin"; //口令
    printf("begin get init creds password\n");
    krberr = krb5_get_init_creds_password(context, &my_creds,kprincpw, (char *)password,NULL,NULL,0,NULL,NULL);//也在此讀取/etc/krb5.conf或服務資源記錄得到KDC

    if (krberr) { //認證失敗
            errmsg = krb5_get_error_message(context, krberr); 
            printf("Err: Failed to get init creds password -> %s\n", errmsg); 
            goto cleanup;//退出 
        }  

    //認證成功       
    my_creds_ptr = &my_creds;
    printf("get init creds password OK\n");

/*
認證成功就繼續處理事情,如:
假設本程序是login(登錄主機)程序,則執行execv 運行shell

本例省略
*/

cleanup:
    if (kprincpw) krb5_free_principal(context, kprincpw);

    if (my_creds_ptr) krb5_free_cred_contents(context, &my_creds);
    if (context) krb5_free_context(context);
    return 0;
}

2.解析
本程序僅僅測試是否通過Kerberos認證,沒處理什麼事情

典型的應用如unix系統本地登錄程序PAM插件libpam-krb5

3.編譯
linlin@debian:~$ gcc -o krbonlylogin krbonlylogin.c -lkrb5

4.運行
上面的客戶端源碼沒顯式指定連接Kerberos服務器地址,本文的目的是用最簡練的方式來表達如何使用API,並且本人也沒深入探究能否/如何在程序裏指定各參數(如服務器地址).

有兩個方式可配置連接到Kerberos:
/etc/krb5.conf
SRV(服務)資源記錄
這兩個方式的配置不再介紹,請參考<Kerberos+LDAP+NFSv4 實現單點登錄(續1)--dns+dhcp>(https://blog.51cto.com/13752418/2395345)

使用服務資源記錄,要用到了DNS,所以客戶端機器還需配置/etc/resolv.conf文件,假設DNS服務器地址是10.0.3.102
linlin@debian:~$ cat /etc/resolv.conf
nameserver 10.0.3.102
linlin@debian:~$

下面測試失敗和成功兩種情況

1)當都沒有/etc/krb5.conf和服務資源記錄時
linlin@debian:~$ ./krbonlylogin
begin get init creds password
Err: Failed to get init creds password -> Cannot find KDC for realm "CTP.NET"
linlin@debian:~$
認證失敗,找不KDC

2)當只有單獨/etc/krb5.conf或單獨服務資源記錄時
爲方便測試,客戶端輸入口令是寫死在源碼裏.爲測試正確/錯誤密碼兩種情況,可到Kerberos服務器設置[email protected]用戶主體密碼

錯誤的密碼
linlin@debian:~$ ./krbonlylogin
begin get init creds password
Err: Failed to get init creds password -> Preauthentication failed
linlin@debian:~$
認證失敗

正確的密碼
linlin@debian:~$ ./krbonlylogin
begin get init creds password
get init creds password OK
linlin@debian:~$
認證成功

3)先測試單獨服務資源記錄配置成功認證,然後創建/etc/krb5.conf,其KDC地址亂填,即這時服務資源記錄和/etc/krb5.conf同時存在

linlin@debian:~$ ./krbonlylogin
begin get init creds password
Err: Failed to get init creds password -> Cannot contact any KDC for realm 'CTP.NET'
linlin@debian:~$
提示找不到KDC服務器,說明是以/etc/krb5.conf配置爲優先,裏邊的不正確KDC地址導致失敗後也不會去嘗試服務資源記錄

4)小結
客戶機可以不需krb5.conf文件,在網絡有搭建DNS的情況下,可通過服務資源記錄獲得KDC地址.當這兩個同時存在時,以/etc/krb5.conf爲優先(不管其成功還是失敗),即使服務資源記錄配置正確.

三.krb5認證-存儲票據
基於上面代碼增加生成票據
1.源代碼
//源文件名:krbteststore.c

#include <stdio.h>
#include <krb5.h>
int main(void)
{
    krb5_context context = NULL;
    krb5_error_code krberr;
    krb5_principal kprincpw = NULL;
    krb5_creds * my_creds_ptr = NULL;
    krb5_creds my_creds;
    const char * errmsg;
    krberr = krb5_init_context(&context);

    if (krberr) {
            errmsg = krb5_get_error_message(NULL, krberr);
            printf("Err: Kerberos context initialization failed -> %s\n", errmsg);
            goto cleanup;
        }

    krberr = krb5_parse_name(context, "[email protected]", &kprincpw);

    if (krberr) {
            errmsg = krb5_get_error_message(context, krberr);
            printf("Err: Failed to parse princpal %s -> %s\n", errmsg);
            goto cleanup;
        }

    const char *password="linlin";
    printf("begin get init creds password\n");
    krberr = krb5_get_init_creds_password(context, &my_creds,kprincpw, (char *)password,NULL,NULL,0,NULL,NULL);

    if (krberr) {
            errmsg = krb5_get_error_message(context, krberr);
            printf("Err: Failed to get init creds password -> %s\n", errmsg);
            goto cleanup;
        }        
    my_creds_ptr = &my_creds;
    printf("get init creds password OK\n");

//--v-- 增加生成票據
    krb5_ccache ccache = NULL;

    /* 
    //生成票據到本進程內存裏,本程序沒做驗證票據的事情,所以不實驗內存票據
    //krberr = krb5_cc_resolve(context, "MEMORY:dhcp_ld_krb5_cc", &ccache);
    */

    //生成票據到臨時目錄裏,由ldapwhoami驗證是否有效 ,實驗的登錄用戶的uid是1000,所以指定票據文件名krb5cc_1000
    krberr = krb5_cc_resolve(context, "FILE:/tmp/krb5cc_1000", &ccache);
    if (krberr) {
            errmsg = krb5_get_error_message(context, krberr);
            printf("Err: Couldnt resolve ccache -> %s\n", errmsg);
            goto cleanup;
        }      

    krberr = krb5_cc_initialize(context, ccache, kprincpw);
    if (krberr) {
            errmsg = krb5_get_error_message(context, krberr);
            printf("Err: Failed to init ccache -> %s\n", errmsg);
            goto cleanup;
        }    

    krberr = krb5_cc_store_cred(context, ccache, &my_creds);

    if (krberr) {
            errmsg = krb5_get_error_message(context, krberr);
            printf("Err: Failed to store credentials -> %s\n", errmsg);
            goto cleanup;
        }      

    printf("Successfully store creds\n");
//--^--

cleanup:
    if (ccache) krb5_cc_close(context, ccache);//這裏雖close,但不會銷燬票據"FILE:/tmp/krb5cc_1000",見下.但就不知是否會銷燬內存票據
    if (kprincpw) krb5_free_principal(context, kprincpw);
    if (my_creds_ptr) krb5_free_cred_contents(context, &my_creds);
    if (context) krb5_free_context(context);

    return 0;
}

2.解析
上面代碼中的函數全是MIT krb5開發庫API函數.源碼篇幅不長,很容易看懂,不再解析.

3.編譯
linlin@debian:~$ gcc -o krbteststore krbteststore.c -lkrb5

4.運行

linlin@debian:~$ ./krbteststore
begin get init creds password
get init creds password OK
Successfully store creds
linlin@debian:~$ ls /tmp
krb5cc_1000  
linlin@debian:~$

可見到krbteststore程序生成了票據krb5cc_1000文件

我已事先安裝好一臺LDAP服務器(10.0.3.11),並配置LDAP可使用GSSAPI認證.測試環境的krb5客戶機已安裝好LDAP客戶端,下面是測試LDAP客戶程序讀取上面生成的票據通過GSSAPI是否正常
linlin@debian:~$ ldapwhoami -Y GSSAPI -h 10.0.3.11
SASL/GSSAPI authentication started 已能認krbteststore生成的票據
SASL username: [email protected] 可見到用戶主體
SASL SSF: 56
SASL data security layer installed.
dn:uid=krblinlin,cn=gssapi,cn=auth 已得到LDAP用戶條目
linlin@debian:~$
說明生成的票據是正常的,認證成功

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章