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认证方式之一,否则运行时会出现下面报错。

        

Error: Unknown authentication method
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换区票据

#include <ldap.h>
#include <stdio.h>
#include <stdlib.h>
#include <krb5.h>

//krb5_get_init_creds_password函数所使用的回调函数,这个函数必须返回成功值 LDAP_SUCCESS,否则基本都失败,因为我通过循环试了1000值。不需要了解他的功能,我猜测的是这个函数能够在我们进行最后的bind之前进行其他的判断,如果失败不返回LDAP_SUCCESS,作为最后一道屏障
static int  _ldap_sasl_interact( )
{ return LDAP_SUCCESS;
}
   
int main()
{ 
  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;
  //初始化一个安全的上下文,后续的操作都是基于context,可以把他看成是基石
  krberr = krb5_init_context(&context);
  if (krberr) {
            //这个API能够解析错误码,分析出特定的错误信息,将其存储在errmsg中,然后打印到终端查看
            errmsg = krb5_get_error_message(NULL, krberr);
            printf("Err: Kerberos context initialization failed -> %s\n", errmsg);
            goto cleanup;
        }
  //这个API是你用户的信息(第二个参数)转化成krb中的格式存储在kprincpw中
    用户信息的组成"<认证的用户名>@<AD服务器的域名>"
  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");
  //使用用户的信息,密码换取TGT票据,并且拿到将凭证放my_creds中,但是这个凭证不是TGT,而是最后一次认证使用的
  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;
  
  //从内存中获取TGT票据,将其放入到ccache的变量中,便于后面换取TGS票据,就是服务器的最终通行证
  krberr = krb5_cc_resolve(context, "MEMORY:dhcp_ld_krb5_cc", &ccache);
  //从/tmp/krb5cc_1000中获取TGT票据,这个文件是自动生成的,将其存储到ccache中便于后面换取TGS票据,安全性上看来,确实没有从内存中获取TGT票据更安全,因为这个是可视化的,你甚至可以自己打开查看
  //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;
        }     
   
  //初始化ccache,相当于清空,但是你的ccache却不能是空的,我怀疑这个函数内存做的处理是,先是利用ccache中存储的TGT票据生成TGS服务票据,然后再清空。这里也能看见API还指定了用户名,说明生成的TGS是有对应关系的。
  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;
        }  
  //这个函数的作用我是有点迷的,这里又将my_creds的凭据放到ccache中又进行了一次认证,因为kerberos的认证是繁琐的,多次认证,我暂且这样理解吧,因为通过抓包发现也是进行了多次response
  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");

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

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

LDAP *ld;
  int rc;
  unsigned long version = LDAP_VERSION3;    //使用LDAP的v3版本

  //初始化一个LDAP句柄指针ld,后面的操作全是基于LDAP句柄指针,和上分的context差不多
  if (( rc = ldap_initialize(&ld,"ldap://192.168.1.11/")) != LDAP_SUCCESS)   //#12
  //if (( rc = ldap_initialize(&ld,"ldap://127.0.0.1/")) != LDAP_SUCCESS)
  {
    return(1);
  }
  //与服务器协商使用V3版本吗
  rc = ldap_set_option(ld,LDAP_OPT_PROTOCOL_VERSION,(void*)&version);

  //除了第6个参数其他都可以默认,第六个参数就是上分提到返回LDAP_SUCCESS的回调函数,这里大佬的代码是有一点问题的,因为它回调函数是返回int类型的,然而这里的参数的数据类型是一个宏定义,我这里看不了,下次补充进来,最后解决办法是将回调函数的返回类型换成这个宏定义,最后转换一下就行了。到这里就bind成功了,完成第四步的配置文件后,可以进行增删改查了
  if ((rc=ldap_sasl_interactive_bind_s(ld,NULL,"GSSAPI",NULL,NULL,LDAP_SASL_AUTOMATIC, _ldap_sasl_interact,NULL) ) != LDAP_SUCCESS)
  { printf ("Error: %s\n",ldap_err2string (rc));
    char *msg=NULL;
    ldap_get_option( ld, LDAP_OPT_DIAGNOSTIC_MESSAGE, (void*)&msg);
    printf ("Additional info: %s\n", msg);
    ldap_memfree(msg);
    return(1);
  }
  
  LDAPMessage *res;
  if ((rc=ldap_search_ext_s(ld,"",LDAP_SCOPE_ONELEVEL,NULL,NULL,0,NULL,NULL,NULL,0,&res)!=LDAP_SUCCESS))
  { printf("ldap_search  failed with 0x%x.\n",rc);         
    return(1);
  }
  printf("Success!\n");
  LDAPMessage *entry = ldap_first_entry( ld, res );
  int entry_count = ldap_count_entries(ld, res);
  for (int i = 0 ; i < entry_count; i++)
  { printf("dn: %s\n",ldap_get_dn(ld, entry));
    BerElement * ber;
    char * attribute = ldap_first_attribute(ld,entry, &ber);
    while(attribute)
    { printf ("attribute = %s\n",attribute);
      attribute = ldap_next_attribute(ld,entry, ber);
    }
    ber_free(ber,0);
    entry = ldap_next_entry(ld, entry);   
  }  
//--^--

cleanup:
    if (ccache) krb5_cc_close(context, ccache);  //#13
    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;   
}

四:环境的配置

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

[libdefaults]
  default_realm = TEST.ORG      //TEST.ORG 你的服务器域名,大写
  dns_lookup_realm = false
  dns_lookup_kdc = false
  ticket_lifetime = 24h
  renew_lifetime = 7d
  forwardable = true
[realms]
  TEST.ORG = {                          //填充你TEST.ORG的模块信息
    kdc = 192.168.45.141             //kdc的IP,在AD中,是集成在一块,填AD的IP即可
   admin_server = 192.168.45.141   //与kdc同理
   default_domain=TEST.ORG    //可填可不填
  }

[domain_realm]            //这个模块的信息可填可不填
  .test.org = TEST.ORG
  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.结言

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/142389.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

一行JavaScrip可以做什么?

说在前面 JavaScript 提供了许多方便的方法和操作符来简化常见的任务&#xff0c;使得编程变得更加高效和便捷。无论是数学计算、字符串处理还是数据操作&#xff0c;JavaScript 都能帮助我们以简洁的方式实现所需功能。 代码 1、生成指定范围内的随机整数 const randomInt …

部分背包问题【贪心算法】

部分背包问题是一种经典的贪心问题&#xff0c;物品可以取一部分&#xff0c;也就是可以随意拆分的物品。 算法思路&#xff1a; 用列表保存每个物品的价值及总重量、平均价值&#xff08;性价比&#xff09;。输入数据同时计算每种物品的平均价值。使用自定义的compare函数以…

2023亚太杯数学建模思路 - 复盘:校园消费行为分析

文章目录 0 赛题思路1 赛题背景2 分析目标3 数据说明4 数据预处理5 数据分析5.1 食堂就餐行为分析5.2 学生消费行为分析 建模资料 0 赛题思路 &#xff08;赛题出来以后第一时间在CSDN分享&#xff09; https://blog.csdn.net/dc_sinor?typeblog 1 赛题背景 校园一卡通是集…

苹果独占鳌头,国产手机围攻,双十一“照妖镜”显露谁有真实力

随着双十一购物节的结束&#xff0c;电商平台也给出了各手机品牌的销量数据&#xff0c;苹果毫无疑问成为双十一的赢家&#xff0c;不过两家国产手机品牌也显露了他们的实力&#xff0c;已具有与苹果一战之力。 与去年双十一和今年618类似&#xff0c;苹果仍然占据热销榜前列&a…

信驰达科技加入车联网联盟(CCC),推进数字钥匙发展与应用

CCC)的会员。 图 1 深圳信驰达正式成为车联网联盟(CCC)会员 车联网联盟(CCC)是一个跨行业组织&#xff0c;致力于推动智能手机与汽车连接解决方案的技术发展。CCC涵盖了全球汽车和智能手机行业的大部分企业&#xff0c;拥有150多家成员公司。CCC成员公司包括智能手机和汽车制造…

TLP超线程技术

在实现IPL指令级并行的同时实现TLP(Thread Level Parallelism)线程级并行实现多线程有两种主要的方法超线程即同时多线程&#xff0c;在单个处理器或单个核中设置了两套线程状态部件&#xff0c;共享高速缓存和功能部件当两个线程同时需要某个资源时&#xff0c;其中一个线程必…

Mac 本地部署thinkphp8【配置环境】

PHP开发工具 我这里选择的是VSCode,里面安装PHP插件 把thinkphp的项目放到 切换到phpenv ![在这里插入图片描述](https://img-blog.csdnimg.cn/a15cc442fab74754ad86d74f6d9942e5.png URL重写如果不改&#xff0c;在请求的时候地址是这样的‘http://tp.com/index.php…

Prim算法(C++)

目录 介绍&#xff1a; 代码&#xff1a; 结果&#xff1a; 介绍&#xff1a; Prim算法是一种用于解决最小生成树问题的贪心算法。该算法的主要思想是从一个顶点开始&#xff0c;不断向图中添加边&#xff0c;直到构成一棵包含所有顶点的生成树&#xff0c;使得树的边权之…

安全认证框架Shrio学习,入门到深度学习,SpringBoot整合Shiro小案例,含代码

权限概述 什么是权限 什么是权限 权限管理&#xff0c;一般指根据系统设置的安全策略或者安全规则&#xff0c;用户可以访问而且只能访问自己被授权的资源&#xff0c;不多不少。权限管理几乎出现在任何系统里面&#xff0c;只要有用户和密码的系统。 权限管理再系统中一般分…

麒麟信安:助力医疗行业操作系统自主创新,提升可靠性与安全性

应用场景 湖南省康复医院是省卫生健康委直属公立三级康复医院&#xff0c;也是全省唯一一所集预防、医疗、康复、科研、教学、健康管理为一体的省级三级公立康复医院。 湖南省康复医院使用的医慧管平台由湖南蓝途方鼎科技有限公司开发&#xff0c;利用互联网技术&#xff0c;…

卫星通信和800MHz双管齐下,中国电信对中国移动发起新挑战

依靠国内某科技企业的宣传&#xff0c;卫星通信大热&#xff0c;中国电信也由此成为受益者&#xff0c;日前中国电信又大举招标25万座800MHz 5G基站&#xff0c;显示出中国电信积极以技术优势挑战中国移动。 一、中国电信急起直追 自从4G时代以来&#xff0c;中国电信就在国内通…

EXTI (2)

增强版实验简介 EXTI5和EXTI9共享一个中断源 下面的类似 EXTI0到4各自拥有一个中断源 改变引脚 PA0和PA1改变为PA5 和PA6 EXTI的重映射 之前是把PA0映射到EXTI0 PA1映射到EXTI1上 现在是要把PA5和PA6分别映射到EXTI5和6上 EXTI进行初始化 NVIC初始化 编写中断函数 因为EXTI…

Apktool反编译和重新打包

Apktool 使用 1&#xff1a;linux安装apktool 可以直接查询下version apktool -v 如果未安装&#xff0c;会得到如下结果&#xff1a; Command apktool not found, but can be installed with:sudo snap install apktool # version 2.7.0, or sudo apt install apktool …

SSH全能终端工具mobaXterm(远程工具)使用教程

参考文章&#xff1a;SSH全能终端工具MobaXterm Personal v23.0 完全汉化绿色版 参考文章&#xff1a;MobaXterm 23终端控制软件 文章目录 SSH全能终端工具mobaXterm使用教程目录引言mobaXterm概述安装与配置下载mobaXterm安装过程基础设置 SSH连接创建SSH会话SSH命令行操作文…

Linux 源码包安装

SRPM 包&#xff0c;比 RPM 包多了一个“S”&#xff0c;是“Source”的首字母&#xff0c;所以 SRPM 可直译为“源代码形式的 RPM 包”。也就是说&#xff0c;SRPM 包中不再是经过编译的二进制文件&#xff0c;都是源代码文件。可以这样理解&#xff0c;SRPM 包是软件以源码形…

力扣周赛371复盘(总结与进步)

比赛结果 第一题 2932. 找出强数对的最大异或值 I - 力扣&#xff08;LeetCode&#xff09; 这个由于是简单题&#xff0c;暴力for循环即可 通过结果如下&#xff1a; class Solution {public int maximumStrongPairXor(int[] nums) {int ans0;for(int i 0;i<nums.length;…

Python 日志记录器logging 百科全书 之 日志回滚

Python 日志记录器logging 百科全书 之 日志回滚 前言 在之前的文章中&#xff0c;我们学习了关于Python日志记录的基础配置。 本文将深入探讨Python中的日志回滚机制&#xff0c;这是一种高效管理日志文件的方法&#xff0c;特别适用于长时间运行或高流量的应用。 知识点&…

深入了解springmvc响应数据

目录 一、前后端分离开发与混合开发 1.1 混合开发模式 1.2 前后端分离模式【重点】 二、页面跳转控制 2.1 通过JSP实现页面跳转 2.2 转发与重定向 三、返回JSON数据 3.1 导包与配置 3.2 使用ResponseBody 四、返回静态资源 4.1 为什么无法直接查询静态资源 4.2 配置…

45 深度学习(九):transformer

文章目录 transformer原理代码的基础准备位置编码Encoder blockmulti-head attentionFeed Forward自定义encoder block Deconder blockEncoderDecodertransformer自定义loss 和 学习率mask生成函数训练翻译 transformer 这边讲一下这几年如日中天的新的seq2seq模式的transform…

简洁高效的微信小程序分页器封装实践

前言 在现今的移动应用开发中&#xff0c;微信小程序已经成为了一个备受欢迎的平台。然而&#xff0c;随着应用的复杂性增加&#xff0c;数据的管理和加载成为了一个问题。本文将探讨微信小程序中的一个关键概念&#xff1a;封装分页器&#xff0c;它是提升小程序性能和用户体验…