• Linux上C++通过LDAP协议使用kerberos认证AES加密连接到AD服务器


    一.前言

            记录自己在实现这个流程遇到的各种问题,因为我也是看了许多优质的文章以及组内大佬的帮助下才弄成的,这里推荐一个大佬的文章,写的非常优秀,比我这篇文章写得好得很多,最后我也是看这个大佬的代码最终才实现的,特别鸣谢。看这篇文章前提你得足够的了解AD,kerberos,ldap等,CSDN这类文章就很多,我就不献丑了。

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

    LDAP/SASL/GSSAPI/Kerberos编程API(3)--LDAP/SASL

    不止这两篇,大佬很详细可自行查看

     二,环境支持

            AD服务器:Windows server 2022 中文版

            Linux:ubuntu 20

            openldap:sudo apt-get install libldap-dev    //代码所需要的ldap.h

            GSSAPI支持:sudo apt-get install libsasl2-modules-gssapi-mit   //使LDAP支持GSSAPI认证,kerberos属于GSSAPI认证方式之一,否则运行时会出现下面报错。

            

    1. Error: Unknown authentication method
    2. Additional info: SASL(-4): no mechanism available: No worthy mechs found

            krb5环境支持:sudo apt-get install krb5-user     //krb5API的调用,因为kerberos认证过程中产生的TGT票据是我们手动调用API实现的 ,而并非使用kinit生成。

    三,API详解

          1.生成TGT票据

                因为我写的是公司项目代码,不可以拿出来,我就直接引用前言大佬的代码了,冒犯还请联系我删除。各个API的功能和参数作用我都写在代码块中了,注释是我加的,代码是大佬的。

    大致流程:1.初始化安全的上下文->2.使用用户名,密码换区TGT->3.通过TGT换区票据

    1. #include
    2. #include
    3. #include
    4. #include
    5. //krb5_get_init_creds_password函数所使用的回调函数,这个函数必须返回成功值 LDAP_SUCCESS,否则基本都失败,因为我通过循环试了1000值。不需要了解他的功能,我猜测的是这个函数能够在我们进行最后的bind之前进行其他的判断,如果失败不返回LDAP_SUCCESS,作为最后一道屏障
    6. static int _ldap_sasl_interact( )
    7. { return LDAP_SUCCESS;
    8. }
    9. int main()
    10. {
    11. krb5_context context = NULL;
    12. krb5_error_code krberr;
    13. krb5_principal kprincpw = NULL;
    14. krb5_creds * my_creds_ptr = NULL;
    15. krb5_creds my_creds;
    16. const char * errmsg;
    17. //初始化一个安全的上下文,后续的操作都是基于context,可以把他看成是基石
    18. krberr = krb5_init_context(&context);
    19. if (krberr) {
    20. //这个API能够解析错误码,分析出特定的错误信息,将其存储在errmsg中,然后打印到终端查看
    21. errmsg = krb5_get_error_message(NULL, krberr);
    22. printf("Err: Kerberos context initialization failed -> %s\n", errmsg);
    23. goto cleanup;
    24. }
    25. //这个API是你用户的信息(第二个参数)转化成krb中的格式存储在kprincpw中
    26. 用户信息的组成"<认证的用户名>@"
    27. krberr = krb5_parse_name(context, "krblinlin@CTP.NET", &kprincpw);
    28. if (krberr) {
    29. errmsg = krb5_get_error_message(context, krberr);
    30. printf("Err: Failed to parse princpal %s -> %s\n", errmsg);
    31. goto cleanup;
    32. }
    33. //用户的密码,哈哈,这个谁都看得出来吧
    34. const char *password="linlin";
    35. printf("begin get init creds password\n");
    36. //使用用户的信息,密码换取TGT票据,并且拿到将凭证放my_creds中,但是这个凭证不是TGT,而是最后一次认证使用的
    37. krberr = krb5_get_init_creds_password(context, &my_creds,kprincpw, (char *)password,NULL,NULL,0,NULL,NULL);
    38. if (krberr) {
    39. errmsg = krb5_get_error_message(context, krberr);
    40. printf("Err: Failed to get init creds password -> %s\n", errmsg);
    41. goto cleanup;
    42. }
    43. // my_creds_ptr = &my_creds;
    44. printf("get init creds password OK\n");
    45. //--v
    46. krb5_ccache ccache = NULL;
    47. //从内存中获取TGT票据,将其放入到ccache的变量中,便于后面换取TGS票据,就是服务器的最终通行证
    48. krberr = krb5_cc_resolve(context, "MEMORY:dhcp_ld_krb5_cc", &ccache);
    49. //从/tmp/krb5cc_1000中获取TGT票据,这个文件是自动生成的,将其存储到ccache中便于后面换取TGS票据,安全性上看来,确实没有从内存中获取TGT票据更安全,因为这个是可视化的,你甚至可以自己打开查看
    50. //krberr = krb5_cc_resolve(context, "FILE:/tmp/krb5cc_1000", &ccache);
    51. if (krberr) {
    52. errmsg = krb5_get_error_message(context, krberr);
    53. printf("Err: Couldnt resolve ccache -> %s\n", errmsg);
    54. goto cleanup;
    55. }
    56. //初始化ccache,相当于清空,但是你的ccache却不能是空的,我怀疑这个函数内存做的处理是,先是利用ccache中存储的TGT票据生成TGS服务票据,然后再清空。这里也能看见API还指定了用户名,说明生成的TGS是有对应关系的。
    57. krberr = krb5_cc_initialize(context, ccache, kprincpw);
    58. if (krberr) {
    59. errmsg = krb5_get_error_message(context, krberr);
    60. printf("Err: Failed to init ccache -> %s\n", errmsg);
    61. goto cleanup;
    62. }
    63. //这个函数的作用我是有点迷的,这里又将my_creds的凭据放到ccache中又进行了一次认证,因为kerberos的认证是繁琐的,多次认证,我暂且这样理解吧,因为通过抓包发现也是进行了多次response
    64. krberr = krb5_cc_store_cred(context, ccache, &my_creds);
    65. if (krberr) {
    66. errmsg = krb5_get_error_message(context, krberr);
    67. printf("Err: Failed to store credentials -> %s\n", errmsg);
    68. goto cleanup;
    69. }
    70. printf("Successfully store creds\n");

    2.将LDAP句柄指针和用户信息绑定

            因为后续需要使用LDAP协议去管理AD服务器,例如增删改查用户的信息,例如我就进行了搜索用户邮箱的操作,这里没看大佬代码,因为我觉得我的更利于直观理解。

    1. LDAP *ld;
    2. int rc;
    3. unsigned long version = LDAP_VERSION3; //使用LDAP的v3版本
    4. //初始化一个LDAP句柄指针ld,后面的操作全是基于LDAP句柄指针,和上分的context差不多
    5. if (( rc = ldap_initialize(&ld,"ldap://192.168.1.11/")) != LDAP_SUCCESS) //#12
    6. //if (( rc = ldap_initialize(&ld,"ldap://127.0.0.1/")) != LDAP_SUCCESS)
    7. {
    8. return(1);
    9. }
    10. //与服务器协商使用V3版本吗
    11. rc = ldap_set_option(ld,LDAP_OPT_PROTOCOL_VERSION,(void*)&version);
    12. //除了第6个参数其他都可以默认,第六个参数就是上分提到返回LDAP_SUCCESS的回调函数,这里大佬的代码是有一点问题的,因为它回调函数是返回int类型的,然而这里的参数的数据类型是一个宏定义,我这里看不了,下次补充进来,最后解决办法是将回调函数的返回类型换成这个宏定义,最后转换一下就行了。到这里就bind成功了,完成第四步的配置文件后,可以进行增删改查了
    13. if ((rc=ldap_sasl_interactive_bind_s(ld,NULL,"GSSAPI",NULL,NULL,LDAP_SASL_AUTOMATIC, _ldap_sasl_interact,NULL) ) != LDAP_SUCCESS)
    14. { printf ("Error: %s\n",ldap_err2string (rc));
    15. char *msg=NULL;
    16. ldap_get_option( ld, LDAP_OPT_DIAGNOSTIC_MESSAGE, (void*)&msg);
    17. printf ("Additional info: %s\n", msg);
    18. ldap_memfree(msg);
    19. return(1);
    20. }
    21. LDAPMessage *res;
    22. if ((rc=ldap_search_ext_s(ld,"",LDAP_SCOPE_ONELEVEL,NULL,NULL,0,NULL,NULL,NULL,0,&res)!=LDAP_SUCCESS))
    23. { printf("ldap_search failed with 0x%x.\n",rc);
    24. return(1);
    25. }
    26. printf("Success!\n");
    27. LDAPMessage *entry = ldap_first_entry( ld, res );
    28. int entry_count = ldap_count_entries(ld, res);
    29. for (int i = 0 ; i < entry_count; i++)
    30. { printf("dn: %s\n",ldap_get_dn(ld, entry));
    31. BerElement * ber;
    32. char * attribute = ldap_first_attribute(ld,entry, &ber);
    33. while(attribute)
    34. { printf ("attribute = %s\n",attribute);
    35. attribute = ldap_next_attribute(ld,entry, ber);
    36. }
    37. ber_free(ber,0);
    38. entry = ldap_next_entry(ld, entry);
    39. }
    40. //--^--
    41. cleanup:
    42. if (ccache) krb5_cc_close(context, ccache); //#13
    43. if (kprincpw) krb5_free_principal(context, kprincpw);
    44. if (my_creds_ptr) krb5_free_cred_contents(context, &my_creds);
    45. if (context) krb5_free_context(context);
    46. return 0;
    47. }

    四:环境的配置

    第1步.在/etc/krb5.conf中添加服务器对应的信息(ubuntu)

    1. [libdefaults]
    2. default_realm = TEST.ORG //TEST.ORG 你的服务器域名,大写
    3. dns_lookup_realm = false
    4. dns_lookup_kdc = false
    5. ticket_lifetime = 24h
    6. renew_lifetime = 7d
    7. forwardable = true
    8. [realms]
    9. TEST.ORG = { //填充你TEST.ORG的模块信息
    10. kdc = 192.168.45.141 //kdc的IP,在AD中,是集成在一块,填AD的IP即可
    11. admin_server = 192.168.45.141 //与kdc同理
    12. default_domain=TEST.ORG //可填可不填
    13. }
    14. [domain_realm] //这个模块的信息可填可不填
    15. .test.org = TEST.ORG
    16. test.org = TEST.ORG

    第2步.在/etc/hosts文件中添加KDC主机名和KDC所在域名(ubuntu)

    27.0.0.1       localhost
    127.0.1.1       ubuntu.test.org ubuntu

    192.168.45.xxx  DC.TEST.ORG     #红色为添加信息,前面填充kdc IP ,后面填写  服务器主机名.域名 例如我的主机名为DC,域名为TEST.ORG
    # The following lines are desirable for IPv6 capable hosts
    ::1     ip6-localhost ip6-loopback
    fe00::0 ip6-localnet
    ff00::0 ip6-mcastprefix
    ff02::1 ip6-allnodes
    ff02::2 ip6-allrouters

    如果第1步不配置,会找不到服务器;

    如果第2步不配置,会出现下列错误:SASL(-1): generic failure: GSSAPI Error: Unspecified GSS failure. Minor code may provide more information (Server not found in Kerberos database)

    同时你也可以在AD中添加DNS反向查询解决这个错误,而不修改/etc/hosts,当然这是用在用户基数很大的情况下,改变AD服务器可以一劳永逸,详情查看第3步。

    第3步 为AD服务器添加DNS反向查询解决这个错误

    3.1进入服务管理器,找到DNS,右击右侧服务器,进入DNS管理器

     3.2 右击反向查找区域,点击新建区域

    3.3 一直点击下一步,直到输入IP的页面,输入AD 服务器IP的前3个字段 例如我的IP为192.168.45.141,那么输入192.168.45,再一直点击下一步,直到结束。 

     3.4 右击新生成的区域,点击新建指针

     3.5 填写AD服务的IP地址,先关闭

     3.6 找到正向查找区域->域名->打开dc(你的是你自己的域名,我的是dc)

    3.7  找到FQDN,将其复制到2.3.5主机名的地方即可。

    3.8 ubuntu打开/etc/reslov.conf,修改nameserver的地址 为你AD的地址,再将你ubuntu的DNS设置为AD的IP,否则这个文件的值会一直变动

    4.结言

            直到这里,整个流程吗,包括配置文件全部结束,加油 。

  • 相关阅读:
    【Unity】ShaderGraph应用(浮动气泡)
    antd 表单校验问题记录&解决方案
    @RequestParam注解的详细介绍
    C++多态
    汽车IVI中控开发入门及进阶(十六):carplay认证
    【Vue3】全局组件,递归组件,动态组件,传送组件,缓存组件,异步组件等
    【pytorch】目标检测:新手也能彻底搞懂的YOLOv5详解
    waf、yakit和ssh免密登录
    【云原生】kubernetes应用程序包管理工具Helm
    ILRuntime使用指南
  • 原文地址:https://blog.csdn.net/weixin_46711630/article/details/134387087