上篇<krb5应用服务(UDP)>使用strace跟踪API,并讲到krb5_mk_req是封装了几个底层API,方便用户的使用,但无助于理解krb5运作机制 本文编写的客/服例子,仍是面向无连接UDP,客户使用底层API,目的在于跟踪票据和消息收发
票据分内存票据和文件票据,本文使用文件系统上的文件票据以方便跟踪观察 客户和应用服务器的交互比较简单,不表 客户和KDC服务器交互较为复杂,本文仅仅简单观察KDC的日志变化 本文没使用高级的跟踪手段(如进程跟踪的strace、ltrace,网络抓包的tcpdump、nmap协议分析等等)
总之,两个观察点: 文件票据(客户机上) KDC日志(KDC服务器上)
一.准备工作 1.
Kerberos服务器(KDC) vmkdc
应用服务器 vmsrv.ctp.net 192.168.1.20 仅接收 /etc/krb5.keytab(由应用服务主体所导出)
客户机 vmcln.ctp.net 192.168.1.40 仅发送
领域 CTP.NET
用户主体 krblinlin@CTP.NET
应用服务主体 mysv/vmsrv.ctp.net
linlin@vmcln:~$ id -u
1000
linlin@vmcln:~$
当前登录用户的uid是1000
linlin@vmcln:~$ klist
klist: No ticket file: /tmp/krb5cc_1000
linlin@vmcln:~$
当前登录用户的文件票据是/tmp/krb5cc_1000
不同登录用户的默认票据名称因uid不同而不同
为测试方便,以uid为1000(linlin)的用户登录,客户程序写死/tmp/krb5cc_1000
二.应用服务器 1.源代码
//源文件名:krbsrv.c
#include <krb5.h>
#include <stdio.h>
#include <netdb.h>
main()
{
krb5_context context;
krb5_auth_context auth_context = NULL;
krb5_error_code retval;
krb5_principal server;
retval = krb5_init_context(&context);
if (retval)
{
exit(1);
}
retval = krb5_sname_to_principal( context,
"vmsrv.ctp.net.", // 主机全名需含最后终结句号
"mysv", // 应用服务名
KRB5_NT_SRV_HST, &server);
if (retval)
{
exit(1);
}
int sock = -1;
struct sockaddr_in sockin;
if ((sock = socket(PF_INET, SOCK_DGRAM, 0)) < 0)
{
exit(1);
}
sockin.sin_family = AF_INET;
sockin.sin_addr.s_addr = INADDR_ANY;
sockin.sin_port = htons(12345); // 端口号
if (bind(sock, (struct sockaddr *) &sockin, sizeof(sockin)))
{
exit(1);
}
for(;;) // 循环服务器
{ int i;
socklen_t len;
krb5_data packet;
unsigned char pktbuf[BUFSIZ];
//--v--
// #1
//--^--
len = sizeof(struct sockaddr_in);
//--v-- #2
if ((i = recvfrom(sock, (char *)pktbuf, sizeof(pktbuf),0,NULL, &len)) < 0)
{
exit(1);
}
//--^--
packet.length = i;
packet.data = (krb5_pointer) pktbuf;
if ((retval = krb5_rd_req(context, &auth_context, &packet,server,NULL,NULL,NULL))) // Check authentication info
{
printf("Err while reading KRB_AP_REQ request: %s\n", krb5_get_error_message(context, retval));
exit(1);
}
printf("recv KRB_AP_REQ message OK\n");
//--v--
// #3
//--^--
krb5_auth_con_free(context, auth_context); // 释放auth_context资源
auth_context = NULL; // 必须赋NULL,否则下个循环krb5_rd_req有判断auth_context不为NULL就不创建新的auth_context,因释放而此时空资源导致段错误
};
krb5_free_principal(context, server);
krb5_free_context(context);
exit(0);
}
2.#3处省略 GET KRB_MK_SAFE MESSAGE,不是必须
3.编译
linlin@debian:~$ gcc -o krbsrv krbsrv.c -lkrb5
4.运行应用服务程序
linlin@vmsrv:~$ ./krbsrv
recv KRB_AP_REQ message OK <= 客户发出的正常KRB_AP_REQ被应用服务器验证成功
5.在#2处是GET KRB_AP_REQ MESSAGE,在其之前的#1处加sleep()代码,测试客户发出KRB_AP_REQ立即到达服务器时,应用服务延迟一段时间才recvfrom发生的情况 1)延迟略大5分钟 sleep(320);
linlin@vmsrv:~$ ./krbsrv
Err while reading KRB_AP_REQ request: Clock skew too great <= 时间偏差太大,验证失败
linlin@vmsrv:~$
2)延迟略小5分钟 sleep(280);
linlin@vmsrv:~$ ./krbsrv
recv KRB_AP_REQ message OK <= 验证成功
3)说明时间偏差默认应该是5分钟
三.客户机 1.源代码
//源文件名:krbcln.c
#include <krb5.h>
#include <stdio.h>
#include <netdb.h>
#include <string.h>
main()
{
krb5_context context;
krb5_error_code retval;
krb5_ccache ccdef;
krb5_principal kprincpw = NULL;
system("rm /tmp/krb5cc_1000"); // 为实验先删已存在票据
retval = krb5_init_context(&context);
if (retval)
{
exit(1);
}
//--v-- #4
retval = krb5_parse_name(context, "krblinlin", &kprincpw); // 可以不带@CTP.NET("krblinlin@CTP.NET"),但/etc/krb5.conf必须配置default_realm = CTP.NET
if (retval)
{
exit(1);
}
printf("-------------------\nOK: krb5_init_context、krb5_parse_name\n");
system("klist"); // 观察票据变化
printf("\nPress ENTER key to Continue\n");
getchar(); // 等待回车,先到KDC查看日志,后回车
const char *password="linlin"; // 口令
krb5_creds my_creds;
retval = krb5_get_init_creds_password(context, &my_creds,kprincpw, (char *)password,NULL,NULL,0,NULL,NULL);
if (retval)
{
exit(1);
}
printf("-------------------\nOK: krb5_get_init_creds_password\n");
system("klist");
printf("\nPress ENTER key to Continue\n");
getchar();
//--^-- 输入用户名、密码
//--v-- #5
retval = krb5_cc_resolve(context, "FILE:/tmp/krb5cc_1000", &ccdef); // #6
if (retval)
{
exit(1);
}
printf("-------------------\nOK: krb5_cc_resolve\n");
system("klist");
printf("\nPress ENTER key to Continue\n");
getchar();
retval = krb5_cc_initialize(context, ccdef, kprincpw); // initialize credentials cache
if (retval)
{
exit(1);
}
printf("-------------------\nOK: krb5_cc_initialize\n");
system("klist");
printf("\nPress ENTER key to Continue\n");
getchar();
retval = krb5_cc_store_cred(context, ccdef, &my_creds); // store credentials in credentials cache
if (retval)
{
exit(1);
}
printf("-------------------\nOK: krb5_cc_store_cred\n");
system("klist");
printf("\nPress ENTER key to Continue\n");
getchar();
//--^-- 产生krbtgt票据
//retval = krb5_cc_default(context, &ccdef); // #7
//--v-- #8
krb5_auth_context auth_context=NULL;
krb5_data packet;
krb5_creds * out_creds_ptr = NULL;
krb5_principal server;
retval = krb5_sname_to_principal(context, "vmsrv.ctp.net.", "mysv",KRB5_NT_SRV_HST, &server); // 拼接应用服务主体名
if (retval)
{
exit(1);
}
//memset(&my_creds, 0, sizeof(my_creds)); // #9
retval = krb5_copy_principal(context, server, &my_creds.server); // 必须
if (retval)
{
exit(1);
}
printf("-------------------\nOK: krb5_sname_to_principal、krb5_copy_principal\n");
system("klist");
printf("\nPress ENTER key to Continue\n");
getchar();
//krb5_cc_get_principal(context, ccdef, &my_creds.client); // #10
retval = krb5_get_credentials(context, 0, ccdef, &my_creds, &out_creds_ptr); // 必须
if (retval)
{ printf("Err: krb5_get_credentials -> %s\n", krb5_get_error_message(context, retval));
exit(1);
}
printf("-------------------\nOK: krb5_get_credentials\n");
system("klist");
printf("\nPress ENTER key to Continue\n");
getchar();
//--^-- 产生应用服务票据
if ((retval = krb5_mk_req_extended(context,&auth_context,0,NULL,out_creds_ptr,&packet) ))
{
exit(1);
}
printf("-------------------\nOK: krb5_mk_req_extended\n");
//--v--
struct sockaddr_in their_addr;
their_addr.sin_family = AF_INET;
their_addr.sin_port = htons(12345);
struct hostent *he;
if ((he=gethostbyname("vmsrv.ctp.net")) == NULL) //<netdb.h>
{
exit(1);
}
their_addr.sin_addr = *((struct in_addr *)he->h_addr);
bzero(&(their_addr.sin_zero), 8);
int sockfd;
if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) == -1)
{
exit(1);
}
if (sendto(sockfd,(char *)packet.data,(unsigned) packet.length,0,(struct sockaddr *)&their_addr,sizeof(struct sockaddr))<0)
{
exit(1);
}
printf("OK: sendto KRB_AP_REQ\n");
//--^--
krb5_free_data_contents(context, &packet);
//--v--
// #13
//--^--
krb5_auth_con_free(context, auth_context);
krb5_free_context(context);
exit(0);
}
2.解析 1)代码段#4及#5相当于kinit
2)代码段#5已产生了ccdef,所以#7处krb5_cc_default可注释掉 krb5_cc_resolve和krb5_cc_default都是产生ccdef句柄,而krb5_cc_default缺省是文件票据(/tmp/krb5cc_1000)或由环境变量决定 如果此时去掉#7处注释,会重新产生ccdef,则有可能和原先ccdef指向的票据位置不同造成麻烦(假如#5使用内存票据是用户主体1,#7缺省文件票据用户主体2,用户会想当然以为是前者)
3)代码段#8参考了krb5_mk_req的实现代码,然后注释掉#9处memset及#10处krb5_cc_get_principal 3.1)krb5_mk_req 包含了memset及krb5_cc_get_principal
memset是为了初始化为某个值,因为C语言不保证变量初始为0 不初始化或许没问题,但一旦出问题很难排查原因 因此最好显式初始化
3.2)在#4代码段里krb5_get_init_creds_password已产生填充了my_creds.client,所以可注释掉#9及#10 如果想memset初始化 要么直接去掉#9处注释(则将原来已存在的my_creds清0了),同时必须去掉#10处注释(以便krb5_cc_get_principal填充my_creds.client,否则krb5_get_credentials段错误) 要么#9处的memset调到krb5_get_init_creds_password之前,这样可不必去掉#10处注释
4)#13处省略krb5_mk_safe、发送用户数据
3.编译
linlin@debian:~$ gcc -o krbcln krbcln.c -lkrb5
4.运行跟踪 1)文件票据
linlin@vmcln:~$ ./krbcln
-------------------
OK: krb5_init_context、krb5_parse_name
klist: No ticket file: /tmp/krb5cc_1000
Press ENTER key to Continue
-------------------
OK: krb5_get_init_creds_password <= 产生KDC日志AS-REQ
klist: No ticket file: /tmp/krb5cc_1000
Press ENTER key to Continue
-------------------
OK: krb5_cc_resolve
klist: No ticket file: /tmp/krb5cc_1000
Press ENTER key to Continue
-------------------
OK: krb5_cc_initialize <= 生成空白票据文件
Credentials cache: FILE:/tmp/krb5cc_1000
Principal: krblinlin@CTP.NET
Issued Expires Principal
Press ENTER key to Continue
-------------------
OK: krb5_cc_store_cred <= 产生了krbtgt
Credentials cache: FILE:/tmp/krb5cc_1000
Principal: krblinlin@CTP.NET
Issued Expires Principal
Dec 21 04:00:49 2023 Dec 22 04:00:49 2023 krbtgt/CTP.NET@CTP.NET
Press ENTER key to Continue
-------------------
OK: krb5_sname_to_principal、krb5_copy_principal <= 票据没变化
Credentials cache: FILE:/tmp/krb5cc_1000
Principal: krblinlin@CTP.NET
Issued Expires Principal
Dec 21 04:00:49 2023 Dec 22 04:00:49 2023 krbtgt/CTP.NET@CTP.NET
Press ENTER key to Continue
-------------------
OK: krb5_get_credentials <= 产生KDC日志TGS-REQ,产生了service ticket
Credentials cache: FILE:/tmp/krb5cc_1000
Principal: krblinlin@CTP.NET
Issued Expires Principal
Dec 21 04:00:49 2023 Dec 22 04:00:49 2023 krbtgt/CTP.NET@CTP.NET
Dec 21 04:11:53 2023 Dec 22 04:00:49 2023 mysv/vmsrv.ctp.net@
Dec 21 04:11:53 2023 Dec 22 04:00:49 2023 mysv/vmsrv.ctp.net@CTP.NET
Press ENTER key to Continue
-------------------
OK: krb5_mk_req_extended
OK: sendto KRB_AP_REQ
linlin@vmcln:~$
到vmsrv查看krbsrv结果为"recv KRB_AP_REQ message OK",应用服务验证成功
客户如果向多个应用服务krb5_get_credentials,应该就逐个新增应用服务到Credentials cache
2)KDC日志 2.1)krb5_init_context、krb5_parse_name无日志
2.2)krb5_get_init_creds_password产生日志
2023-12-21T04:00:49 AS-REQ krblinlin@CTP.NET from IPv4:192.168.1.40 for krbtgt/CTP.NET@CTP.NET <= 地址192.168.1.40是客户机
2023-12-21T04:00:49 Client sent patypes: 150, REQ-ENC-PA-REP
2023-12-21T04:00:49 Looking for PK-INIT(ietf) pa-data -- krblinlin@CTP.NET
2023-12-21T04:00:49 Looking for PK-INIT(win2k) pa-data -- krblinlin@CTP.NET
2023-12-21T04:00:49 Looking for ENC-TS pa-data -- krblinlin@CTP.NET
2023-12-21T04:00:49 Need to use PA-ENC-TIMESTAMP/PA-PK-AS-REQ
2023-12-21T04:00:49 sending 293 bytes to IPv4:192.168.1.40
2023-12-21T04:00:49 AS-REQ krblinlin@CTP.NET from IPv4:192.168.1.40 for krbtgt/CTP.NET@CTP.NET
2023-12-21T04:00:49 Client sent patypes: ENC-TS, 150, REQ-ENC-PA-REP
2023-12-21T04:00:49 Looking for PK-INIT(ietf) pa-data -- krblinlin@CTP.NET
2023-12-21T04:00:49 Looking for PK-INIT(win2k) pa-data -- krblinlin@CTP.NET
2023-12-21T04:00:49 Looking for ENC-TS pa-data -- krblinlin@CTP.NET
2023-12-21T04:00:49 ENC-TS Pre-authentication succeeded -- krblinlin@CTP.NET using aes256-cts-hmac-sha1-96
2023-12-21T04:00:49 ENC-TS pre-authentication succeeded -- krblinlin@CTP.NET
2023-12-21T04:00:49 AS-REQ authtime: 2023-12-21T04:00:49 starttime: unset endtime: 2023-12-22T04:00:49 renew till: unset
2023-12-21T04:00:49 Client supported enctypes: aes256-cts-hmac-sha1-96, aes128-cts-hmac-sha1-96, aes256-cts-hmac-sha384-192, aes128-cts-hmac-sha256-128, des3-cbc-sha1, arcfour-hmac-md5, 25, 26, using aes256-cts-hmac-sha1-96/aes256-cts-hmac-sha1-96
2023-12-21T04:00:49 Requested flags: renewable-ok
2023-12-21T04:00:49 sending 681 bytes to IPv4:192.168.1.40
2.3)krb5_cc_resolve、krb5_cc_initialize、krb5_cc_store_cred、krb5_sname_to_principal、krb5_copy_principal无日志
2.4)krb5_get_credentials产生日志
2023-12-21T04:11:53 Got TGS FAST request
2023-12-21T04:11:53 TGS-REQ krblinlin@CTP.NET from IPv4:192.168.1.40 for mysv/vmsrv.ctp.net@CTP.NET [canonicalize]
2023-12-21T04:11:53 TGS-REQ authtime: 2023-12-21T04:00:49 starttime: 2023-12-21T04:11:53 endtime: 2023-12-22T04:00:49 renew till: unset
2023-12-21T04:11:53 sending 643 bytes to IPv4:192.168.1.40
2.5)krb5_mk_req_extended无日志
5.API调用关系 参考MIT krb5源码 ../src/lib/krb5/krb/mk_req.c ../src/lib/krb5/krb/get_creds.c
krb5_mk_req
|-- krb5_sname_to_principal
| |-- krb5_copy_principal
| |-- krb5_cc_get_principal
v |-- krb5_get_credentials
| |-- try_get_creds (非API)
执 | | |-- krb5_tkt_creds_init
行 | | |-- krb5_tkt_creds_get (同步)
顺 | | | |-- krb5_tkt_creds_step (异步)
序 | | | |-- krb5_sendto_kdc (非API)
| | |-- krb5_tkt_creds_get_creds
|-- krb5_mk_req_extended
|
6.客户程序主要数据结构
krb5_context context; // a krb5 library context
krb5_ccache ccdef; // Credential cache handle
krb5_auth_context auth_context; // Pre-existing or newly created auth context
krb5_creds my_creds; // credentials
krb5_creds * out_creds_ptr; // Credentials for the service with valid ticket and key
krb5_data packet; // AP-REQ message
auth_context用于用户数据krb5_mk_safe,本文没用到
7.改写客户程序 1)客户输入用户密码,但省略保存票据 客户krbcln.c代码段#5改为仅一行:
//--v-- #5
retval = krb5_cc_resolve(context, "FILE:/tmp/krb5cc_1000", &ccdef); // 没此行会导致在krb5_get_credentials因ccdef为空发生段错误
//--^--
linlin@vmcln:~$ ./krbcln-nosave
rm: 无法删除'/tmp/krb5cc_1000': 没有那个文件或目录
Err: krb5_get_credentials -> No credentials cache found (filename: /tmp/krb5cc_1000)
linlin@vmcln:~$
客户失败
2)客户不处理kinit 客户krbcln.c去掉#4及#5代码段,代而命令kinit
//源文件名:krbcln.c
#include <krb5.h>
#include <stdio.h>
#include <netdb.h>
#include <string.h>
main()
{
krb5_context context;
krb5_error_code retval;
krb5_ccache ccdef;
krb5_principal kprincpw = NULL;
krb5_data packet;
krb5_auth_context auth_context=NULL;
krb5_init_context(&context);
krb5_cc_default(context, &ccdef);
//--v-- 下面是krb5_mk_req(context,&auth_context,0,"mysv","vmsrv.ctp.net.",NULL,ccdef,&packet)的功能分解
krb5_creds my_creds;
krb5_creds * out_creds_ptr = NULL;
krb5_principal server;
krb5_sname_to_principal(context, "vmsrv.ctp.net.", "mysv",KRB5_NT_SRV_HST, &server);
krb5_copy_principal(context, server, &my_creds.server); /* --\ 原用'// --\'注释导致段错误查原因好久,原来'\'是续行,导致下句给注释掉 */
krb5_cc_get_principal(context, ccdef, &my_creds.client); /* --/ 必须在krb5_get_credentials之前填充my_creds.server、my_creds.client,否则krb5_get_credentials段错误 */
retval = krb5_get_credentials(context, 0, ccdef, &my_creds, &out_creds_ptr); // #11
if (retval)
{
exit(1);
}
printf("-------------------\nOK: krb5_get_credentials\n");
system("klist");
printf("\nPress ENTER key to Continue\n");
getchar();
if ((retval = krb5_mk_req_extended(context,&auth_context,0,NULL,out_creds_ptr,&packet) ))
{
exit(1);
}
printf("-------------------\nOK: krb5_mk_req_extended\n");
//--^--
//--v--
struct sockaddr_in their_addr;
their_addr.sin_family = AF_INET;
their_addr.sin_port = htons(12345);
struct hostent *he;
he=gethostbyname("vmsrv.ctp.net");
their_addr.sin_addr = *((struct in_addr *)he->h_addr);
bzero(&(their_addr.sin_zero), 8);
int sockfd;
sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (sendto(sockfd,(char *)packet.data,(unsigned) packet.length,0,(struct sockaddr *)&their_addr,sizeof(struct sockaddr))<0)
{
exit(1);
}
printf("OK: sendto KRB_AP_REQ\n");
//--^--
krb5_free_data_contents(context, &packet);
krb5_auth_con_free(context, auth_context);
krb5_free_context(context);
exit(0);
}
2.1)已存在krbtgt、应用服务票据
linlin@vmcln:~$ klist
Credentials cache: FILE:/tmp/krb5cc_1000
Principal: krblinlin@CTP.NET
Issued Expires Principal
Dec 21 07:48:14 2023 Dec 22 07:48:14 2023 krbtgt/CTP.NET@CTP.NET
Dec 21 07:48:15 2023 Dec 22 07:48:14 2023 mysv/vmsrv.ctp.net@
Dec 21 07:48:15 2023 Dec 22 07:48:14 2023 mysv/vmsrv.ctp.net@CTP.NET
linlin@vmcln:~$
linlin@vmcln:~$ ./krbcln-noinit
-------------------
OK: krb5_get_credentials <= 无KDC日志,票据没变化
Credentials cache: FILE:/tmp/krb5cc_1000
Principal: krblinlin@CTP.NET
Issued Expires Principal
Dec 21 07:48:14 2023 Dec 22 07:48:14 2023 krbtgt/CTP.NET@CTP.NET
Dec 21 07:48:15 2023 Dec 22 07:48:14 2023 mysv/vmsrv.ctp.net@
Dec 21 07:48:15 2023 Dec 22 07:48:14 2023 mysv/vmsrv.ctp.net@CTP.NET
Press ENTER key to Continue
-------------------
OK: krb5_mk_req_extended
OK: sendto KRB_AP_REQ
linlin@vmcln:~$
到vmsrv查看krbsrv结果为"recv KRB_AP_REQ message OK",应用服务验证成功
2.2)kinit
linlin@vmcln:~$ kinit --no-forwardable krblinlin
krblinlin@CTP.NET's Password:
linlin@vmcln:~$ klist
Credentials cache: FILE:/tmp/krb5cc_1000
Principal: krblinlin@CTP.NET
Issued Expires Principal
Dec 24 07:12:54 2023 Jun 23 22:12:50 2024 krbtgt/CTP.NET@CTP.NET
linlin@vmcln:~$
可见kinit仅产生krbtgt
linlin@vmcln:~$ ./krbcln-noinit
-------------------
OK: krb5_get_credentials <= 产生KDC日志TGS-REQ,产生了service ticket
Credentials cache: FILE:/tmp/krb5cc_1000
Principal: krblinlin@CTP.NET
Issued Expires Principal
Dec 24 07:12:54 2023 Jun 23 22:12:50 2024 krbtgt/CTP.NET@CTP.NET
Dec 24 07:15:34 2023 Jun 23 22:12:50 2024 mysv/vmsrv.ctp.net@
Dec 24 07:15:34 2023 Jun 23 22:12:50 2024 mysv/vmsrv.ctp.net@CTP.NET
Press ENTER key to Continue
-------------------
OK: krb5_mk_req_extended
OK: sendto KRB_AP_REQ
linlin@vmcln:~$
应用服务验证成功
3)下面代码代替#11处的krb5_get_credentials(context, 0, ccdef, &my_creds, &out_creds_ptr)
//--v--
krb5_tkt_creds_context ctx;
retval=krb5_tkt_creds_init(context, ccdef,&my_creds, 0,&ctx);
if (retval)
{
exit(1);
}
printf("-------------------\nOK: krb5_tkt_creds_init\n");
system("klist");
printf("\nPress ENTER key to Continue\n");
getchar();
retval=krb5_tkt_creds_get(context, ctx); // #12
if (retval)
{
printf("Err: Failed to krb5_tkt_creds_get -> %s\n", krb5_get_error_message(context, retval));
exit(1);
}
printf("-------------------\nOK: krb5_tkt_creds_get\n");
system("klist");
printf("\nPress ENTER key to Continue\n");
getchar();
krb5_creds new_creds;
retval=krb5_tkt_creds_get_creds( context, ctx,&new_creds);
if (retval)
{
printf("Err: Failed to krb5_tkt_creds_get_creds -> %s\n", krb5_get_error_message(context, retval));
exit(1);
}
printf("-------------------\nOK: krb5_tkt_creds_get_creds\n");
system("klist");
printf("\nPress ENTER key to Continue\n");
getchar();
out_creds_ptr = &new_creds;
//--^--
3.1)已存在krbtgt、应用服务票据
linlin@vmcln:~$ klist
Credentials cache: FILE:/tmp/krb5cc_1000
Principal: krblinlin@CTP.NET
Issued Expires Principal
Dec 25 04:12:54 2023 Jun 24 19:12:49 2024 krbtgt/CTP.NET@CTP.NET
Dec 25 07:06:03 2023 Jun 24 19:12:49 2024 mysv/vmsrv.ctp.net@
Dec 25 07:06:03 2023 Jun 24 19:12:49 2024 mysv/vmsrv.ctp.net@CTP.NET
linlin@vmcln:~$ ./krbcln-tkt
-------------------
OK: krb5_tkt_creds_init
Credentials cache: FILE:/tmp/krb5cc_1000
...(略,同上klist)
Press ENTER key to Continue
-------------------
OK: krb5_tkt_creds_get
Credentials cache: FILE:/tmp/krb5cc_1000
...(同上)
Press ENTER key to Continue
-------------------
OK: krb5_tkt_creds_get_creds
Credentials cache: FILE:/tmp/krb5cc_1000
...(同上)
Press ENTER key to Continue
-------------------
OK: krb5_mk_req_extended
OK: sendto KRB_AP_REQ
linlin@vmcln:~$
无KDC日志,票据没变化,应用服务验证成功
3.2)kinit
linlin@vmcln:~$ kinit --no-forwardable krblinlin
linlin@vmcln:~$ klist
Credentials cache: FILE:/tmp/krb5cc_1000
Principal: krblinlin@CTP.NET
Issued Expires Principal
Dec 25 07:12:54 2023 Jun 24 22:12:50 2024 krbtgt/CTP.NET@CTP.NET
linlin@vmcln:~$ ./krbcln-tkt
-------------------
OK: krb5_tkt_creds_init <= 无KDC日志
Credentials cache: FILE:/tmp/krb5cc_1000
...(略,同上klist)
Press ENTER key to Continue
-------------------
OK: krb5_tkt_creds_get <= 产生KDC日志TGS-REQ,产生了service ticket
Credentials cache: FILE:/tmp/krb5cc_1000
Principal: krblinlin@CTP.NET
Issued Expires Principal
Dec 25 07:12:54 2023 Jun 24 22:12:50 2024 krbtgt/CTP.NET@CTP.NET
Dec 25 07:14:44 2023 Jun 24 22:12:50 2024 mysv/vmsrv.ctp.net@
Dec 25 07:14:44 2023 Jun 24 22:12:50 2024 mysv/vmsrv.ctp.net@CTP.NET
Press ENTER key to Continue
-------------------
OK: krb5_tkt_creds_get_creds <= 无KDC日志
Credentials cache: FILE:/tmp/krb5cc_1000
...(同上krb5_tkt_creds_get)
Press ENTER key to Continue
-------------------
OK: krb5_mk_req_extended
OK: sendto KRB_AP_REQ
linlin@vmcln:~$
应用服务验证成功
KDC日志
2023-12-25T07:14:44 Got TGS FAST request
2023-12-25T07:14:44 TGS-REQ krblinlin@CTP.NET from IPv4:192.168.1.40 for mysv/vmsrv.ctp.net@CTP.NET [canonicalize]
2023-12-25T07:14:44 TGS-REQ authtime: 2023-12-25T07:12:54 starttime: 2023-12-25T07:14:44 endtime: 2024-06-24T22:12:50 renew till: unset
2023-12-25T07:14:44 sending 643 bytes to IPv4:192.168.1.40
3.3)小结 krb5_tkt_creds_get发出TGS-REQ并产生了service ticket
4)注释掉#12处的krb5_tkt_creds_get
linlin@vmcln:~$ klist
Credentials cache: FILE:/tmp/krb5cc_1000
Principal: krblinlin@CTP.NET
Issued Expires Principal
Dec 25 02:06:21 2023 Dec 26 02:06:21 2023 krbtgt/CTP.NET@CTP.NET
Dec 25 02:06:22 2023 Dec 26 02:06:21 2023 mysv/vmsrv.ctp.net@
Dec 25 02:06:22 2023 Dec 26 02:06:21 2023 mysv/vmsrv.ctp.net@CTP.NET
linlin@vmcln:~$ ./krbcln-noget
Err: Failed to krb5_tkt_creds_get_creds -> Request did not supply a ticket
linlin@vmcln:~$
即使已存在krbtgt、应用服务票据,客户也失败
查看../src/lib/krb5/krb/get_creds.c krb5_tkt_creds_init设置ctx->state为STATE_BEGIN krb5_tkt_creds_get_creds对ctx->state不是STATE_COMPLETE返回KRB5_NO_TKT_SUPPLIED(即"Request did not supply a ticket")
krb5_get_credentials
|-- krb5_tkt_creds_init([out] ctx)
|-- krb5_tkt_creds_get([in] ctx)
| |-- krb5_tkt_creds_step([in] ctx)
|-- krb5_tkt_creds_get_creds([in] ctx)
krb5_tkt_creds_get和krb5_tkt_creds_get_creds的ctx都是输入型参数啊,难道krb5_tkt_creds_get会重新写ctx->state ? 所以[in]型的不一定只读,也可能可写?
5)下面代码代替#12处的krb5_tkt_creds_get(context, ctx)
//--v--
krb5_data request,reply,realm;
unsigned int flags=0;
retval=krb5_tkt_creds_step(context,ctx,&reply,&request,&realm,&flags);
if (retval)
{
printf("Err: krb5_tkt_creds_step -> %s\n", krb5_get_error_message(context, retval));
exit(1);
}
//--^--
5.1)已存在krbtgt、应用服务票据
linlin@vmcln:~$ klist
Credentials cache: FILE:/tmp/krb5cc_1000
Principal: krblinlin@CTP.NET
Issued Expires Principal
Dec 25 07:12:54 2023 Jun 24 22:12:50 2024 krbtgt/CTP.NET@CTP.NET
Dec 25 07:14:44 2023 Jun 24 22:12:50 2024 mysv/vmsrv.ctp.net@
Dec 25 07:14:44 2023 Jun 24 22:12:50 2024 mysv/vmsrv.ctp.net@CTP.NET
linlin@vmcln:~$
linlin@vmcln:~$ ./krbcln-step
Credentials cache: FILE:/tmp/krb5cc_1000
...(略,同上klist)
-------------------
OK: krb5_tkt_creds_get_creds
Credentials cache: FILE:/tmp/krb5cc_1000
...(同上)
-------------------
OK: krb5_mk_req_extended
OK: sendto KRB_AP_REQ
linlin@vmcln:~$
无KDC日志,应用服务验证成功
krb5_tkt_creds_step([in] ctx)
|-- check_cache(非API)设置ctx->state为STATE_COMPLETE
数据结构ctx看是输入型参数,但API里边有操作改变了ctx
5.2)kinit
linlin@vmcln:~$ kinit --no-forwardable krblinlin
linlin@vmcln:~$ klist
Credentials cache: FILE:/tmp/krb5cc_1000
Principal: krblinlin@CTP.NET
Issued Expires Principal
Dec 25 07:58:34 2023 Jun 24 22:58:26 2024 krbtgt/CTP.NET@CTP.NET
linlin@vmcln:~$ ./krbcln-step
Credentials cache: FILE:/tmp/krb5cc_1000
...(略,同上klist)
Err: krb5_tkt_creds_get_creds -> Request did not supply a ticket
linlin@vmcln:~$
无KDC日志,客户失败,krb5_tkt_creds_get_creds没能产生out_creds_ptr
5.3)小结 krb5_tkt_creds_step仅构造TGS-REQ,不发送请求 krb5_tkt_creds_get_creds完成krb5_mk_req_extended仅需的out_creds_ptr
API ctx->state
---------------------------------------
krb5_tkt_creds_init STATE_BEGIN
krb5_tkt_creds_step STATE_COMPLETE
四.总结 1.
客户机 KDC
-------------------------------- ---------
| | 1. KRB_AS_REQ | |
| /|--------------->| |
|krb5_get_init_creds_password | |AS |
| \| 2. KRB_AS_REP | |
| |<---------------| |
| | | |
|krb5_cc_initialize | | |
|(空白ticket) | | |
| | | |
|krb5_cc_store_cred | | |
|(krbtgt) | | |
| | | ----- |
| | | |
| | 3. KRB_TGS_REQ | |
| /|--------------->| |
|krb5_get_credentials | | |
| |-- krb5_tkt_creds_get | |TGS |
|(Service Ticket) | | |
| \| 4. KRB_TGS_REP | |
| |<---------------| |
| | ---------
| |
|krb5_mk_req_extended |
| |
| | 应用服务器
| | ------------------
| | 5. KRB_AP_REQ | |
| |--------------->| |
| | | |
| | |/etc/krb5.keytab|
| | 6. KRB_AP_REP | |
| |<---------------| |
-------------------------------- ------------------
图A
krb5_get_init_creds_password含AS_REQ消息包及发送功能,并填充krb5_creds结构(my_creds) krb5_get_credentials、krb5_tkt_creds_get含TGS_REQ消息包及发送功能 krb5_tkt_creds_step仅构造TGS-REQ(This function constructs the next KDC request for a TGS exchange),不发送 krb5_mk_req_extended仅是构造AP_REQ消息包,不含发送功能 krb5_mk_req构造AP_REQ消息包(但不发送),中间可能会构造并发送TGS_REQ AP_REQ消息包的发送由用户自己使用套接字API发送函数
krb5_mk_safe需要由krb5_mk_req/krb5_mk_req_extended产生的auth_context,伴随而生的AP_REQ在某些场合不需发送
2.下面列出API的参数,仅列本文关心的数据结构部分
krb5_cc_resolve([out] &ccdef)/krb5_cc_default([out] &ccdef)
krb5_get_init_creds_password([out] &my_creds)
krb5_cc_initialize([in] ccdef)
krb5_cc_store_cred([in] ccdef,[in] &my_creds)
krb5_mk_req([inout] &auth_context,[in] ccdef,[out] &packet)
|-- krb5_sname_to_principal
| |-- krb5_copy_principal([out] &my_creds.server)
| |-- krb5_cc_get_principal([in] ccdef,[out] &my_creds.client)
v |-- krb5_get_credentials([in] ccdef,[in] &my_creds,[out] &out_creds_ptr)
| |-- try_get_creds (非API)
执 | | |-- krb5_tkt_creds_init([in] ccdef,[in] &my_creds,[out] &ctx)
行 | | |-- krb5_tkt_creds_get([in] ctx)
顺 | | | |-- krb5_tkt_creds_step([in] ctx,[in] &reply,[out] &request)
序 | | | |-- krb5_sendto_kdc (非API)
| | |-- krb5_tkt_creds_get_creds([in] ctx,[out] &new_creds)
| | |-- out_creds_ptr = &new_creds
|-- krb5_mk_req_extended([inout] &auth_context,[in] out_creds_ptr,[out] &packet)
1)ccdef以文件票据为例,其handle(句柄)实际指向文件名,不是文件描述符 刚开始,本人以为handle是文件描述符,经strace,每个传[in] ccdef 的API,都是openat("/tmp/krb5cc_1000")/close()成对,即每次都是重新打开票据文件,然后关闭 问题疑惑:假如两个API之间发生非本身客户程序的票据变化(如中间运行了kinit命令为别的用户主体),会什么情况?
strace到krb5_cc_initialize
...
unlink("/tmp/krb5cc_1000") = -1 ENOENT (没有那个文件或目录) <= 先删票据
openat(AT_FDCWD, "/tmp/krb5cc_1000", O_RDWR|O_CREAT|O_EXCL|O_CLOEXEC, 0600) = 3 <= 后建票据空白文件,描述符是3
...
write(3, "\5\4\0\f\0\1\0\10\0\0\0\0\0\0\0\0\0\0\0\1\0\0\0\1\0\0\0\7CTP."..., 48) = 48 <= 写文件
...
close(3) = 0 <= 关闭文件
...
其余API不含unlink
2)数据结构传递
krb5_cc_resolve/krb5_cc_default([out] &ccdef)
|
v
krb5_mk_req( [in] ccdef)
|-- krb5_copy_principal( [out] &my_creds.server)
| \
| -------------
|-- krb5_cc_get_principal( [in] ccdef,[out] &my_creds.client) |
| | --------------
| |/
| v
|-- krb5_get_credentials( [in] ccdef, [in] &my_creds,[out] &out_creds_ptr)
| | \
| | ----------------------
| |-- krb5_tkt_creds_init([in] ccdef, [in] &my_creds,[out] &ctx) |
| | | |
| | v |
| |-- krb5_tkt_creds_get( [in] ctx) |
| | |-- krb5_tkt_creds_step( [in] ctx) |
| |-- krb5_tkt_creds_get_creds( [in] ctx,[out] &new_creds) |
| |-- out_creds_ptr = &new_creds |
| |
| -------------------
| /
| v
|-- krb5_mk_req_extended( [in] out_creds_ptr)
my_creds和out_creds_ptr不知有何不同?但用户使用API时,krb5_mk_req两者都不需,krb5_mk_req_extended仅需out_creds_ptr
如果你不完全了解krb5的运作机制,请使用已完全封装好的krb5_mk_req,最为保险 如果稍懂krb5,可以使用krb5_mk_req_extended系列API 若你透彻掌握了krb5机理,完全可以使用底层的krb5_tkt_xxx系列API
五.参考 https://stuff.mit.edu/afs/sipb/user/mkgray/bar/mit/kerberos/krb5-latest/doc/appdev/refs/index.html
六.后记 1.代码段#5里的"FILE:/tmp/krb5cc_1000"改为"FILE:/tmp/krb5cc_linlin"(非缺省) 1)#7处保留注释
linlin@vmcln:~$ ./krbcln
...
OK: krb5_mk_req_extended
OK: sendto KRB_AP_REQ
linlin@vmcln:~$ klist
klist: No ticket file: /tmp/krb5cc_1000
linlin@vmcln:~$ klist -c "FILE:/tmp/krb5cc_linlin"
Credentials cache: FILE:/tmp/krb5cc_linlin
Principal: krblinlin@CTP.NET
Issued Expires Principal
Jan 15 03:17:14 2024 Jan 16 03:17:14 2024 krbtgt/CTP.NET@CTP.NET
Jan 15 03:17:30 2024 Jan 16 03:17:14 2024 mysv/vmsrv.ctp.net@
Jan 15 03:17:30 2024 Jan 16 03:17:14 2024 mysv/vmsrv.ctp.net@CTP.NET
linlin@vmcln:~$ klist -c "/tmp/krb5cc_linlin"
Credentials cache: FILE:/tmp/krb5cc_linlin
Principal: krblinlin@CTP.NET
Issued Expires Principal
...(略,同上)
linlin@vmcln:~$
应用服务验证成功
2)#7处去掉注释
linlin@vmcln:~$ ./krbcln
...
OK: krb5_sname_to_principal、krb5_copy_principal
klist: No ticket file: /tmp/krb5cc_1000
Press ENTER key to Continue
Err: krb5_get_credentials -> No credentials cache found (filename: /tmp/krb5cc_1000)
linlin@vmcln:~$
客户失败
linlin@vmcln:~$ klist -c "/tmp/krb5cc_linlin"
Credentials cache: FILE:/tmp/krb5cc_linlin
Principal: krblinlin@CTP.NET
Issued Expires Principal
Jan 15 03:27:47 2024 Jan 16 03:27:47 2024 krbtgt/CTP.NET@CTP.NET
linlin@vmcln:~$
可见krb5_cc_resolve的句柄ccdef被krb5_cc_default覆盖 除非取ccdef1、ccdef2不同变量名
3)直接krb5_cc_default代替krb5_cc_resolve
//--v-- #5
//retval = krb5_cc_resolve(context, "FILE:/tmp/krb5cc_1000", &ccdef);
retval = krb5_cc_default(context, &ccdef);
...
//--^-- 产生krbtgt票据
//retval = krb5_cc_default(context, &ccdef); // #7
linlin@vmcln:~$ ./krbcln
...
OK: krb5_get_credentials
Credentials cache: FILE:/tmp/krb5cc_1000
Principal: krblinlin@CTP.NET
Issued Expires Principal
Jan 15 05:50:53 2024 Jan 16 05:50:53 2024 krbtgt/CTP.NET@CTP.NET
Jan 15 05:51:05 2024 Jan 16 05:50:53 2024 mysv/vmsrv.ctp.net@
Jan 15 05:51:05 2024 Jan 16 05:50:53 2024 mysv/vmsrv.ctp.net@CTP.NET
Press ENTER key to Continue
-------------------
OK: krb5_mk_req_extended
OK: sendto KRB_AP_REQ
linlin@vmcln:~$ klist
Credentials cache: FILE:/tmp/krb5cc_1000
Principal: krblinlin@CTP.NET
Issued Expires Principal
...(同上)
linlin@vmcln:~$
应用服务验证成功
4)小结 krb5_cc_resolve和krb5_cc_default都是产生ccdef句柄,后续API是认ccdef这个指向票据的句柄 krb5_cc_resolve和krb5_cc_default一般不要同时使用 krb5_cc_resolve不需遵从krb5cc_1000命名规则 krb5_cc_default按缺省或环境变量
2.两个API之间发生kinit票据变化
linlin@vmcln:~$ ./krbcln
...
OK: krb5_sname_to_principal、krb5_copy_principal
Credentials cache: FILE:/tmp/krb5cc_1000
Principal: krblinlin@CTP.NET
Issued Expires Principal
Jan 15 06:26:05 2024 Jan 16 06:26:05 2024 krbtgt/CTP.NET@CTP.NET
Press ENTER key to Continue
====在等待回车期间kinit krblu
linlin@vmcln:~$ kinit --no-forwardable krblu
linlin@vmcln:~$ klist
Credentials cache: FILE:/tmp/krb5cc_1000
Principal: krblu@CTP.NET
Issued Expires Principal
Jan 15 06:27:05 2024 Jul 15 21:27:01 2024 krbtgt/CTP.NET@CTP.NET
linlin@vmcln:~$
====回车后
Err: krb5_get_credentials -> Matching credential not found (filename: /tmp/krb5cc_1000)
linlin@vmcln:~$
客户失败
如果期间仍是kinit krblinlin则应用服务能成功验证
3.krb5_tkt_creds_get实现 见../src/lib/krb5/krb/get_creds.c
for (;;) { //用死循环异步实现同步? 符合条件break
...
code = krb5_tkt_creds_step(context, ctx, &reply, &request, &realm,
&flags);
...
code = krb5_sendto_kdc(context, &request, &realm,
&reply, &use_master, tcp_only);
...
}
krb5_tkt_creds_step的帮助文档有说明reply是输入型和request是输出型 krb5_sendto_kdc非API,没见对reply、request说明,只能参见其在../src/lib/krb5/os/sendto_kdc.c中的实现代码,猜测reply是输出型,request是输入型
----------------------------
| |
v |
krb5_tkt_creds_step([in] &reply,[out] &request) |
| |
v |
krb5_sendto_kdc( [in ?] &request,[out ?] &reply)
reply 应是TGS_REP request应是TGS_REQ
krb5_tkt_creds_step仅构造TGS_REQ,不负责发送 krb5_sendto_kdc 负责发送TGS_REQ,并且负责接收TGS_REP
因为两者前后相互传参,所以要用for循环
从上值得网络应用层协议客户API设计参考: REQ构造要单独一个API,和发送分离 发送REQ和接收REP要封装在同一个API(但不构造REQ)