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,并创建了krblinlin@CTP.NET用户主体.

二.客户机安装开发库
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, "krblinlin@CTP.NET", &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服务器设置krblinlin@CTP.NET用户主体密码

错误的密码
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, "krblinlin@CTP.NET", &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: krblinlin@CTP.NET 可见到用户主体
SASL SSF: 56
SASL data security layer installed.
dn:uid=krblinlin,cn=gssapi,cn=auth 已得到LDAP用户条目
linlin@debian:~$
说明生成的票据是正常的,认证成功

LDAP/SASL/GSSAPI/Kerberos编程API(2)--krb5客户端

上一篇:在Windows 10中启动WSL2 并安装Linux( Ubuntu 为例)并运行docker


下一篇:【WPF学习】第五十九章 理解控件模板