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
#include
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(服务)资源记录
这两个方式的配置不再介绍,请参考
使用服务资源记录,要用到了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
#include
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:~$
说明生成的票据是正常的,认证成功