LDAP/SASL/GSSAPI/Kerberos programming API--krb5 client

Keywords: C++ DNS shell Unix network

There are two libraries available for krb5 API: MIT and Heimdal. The APIs of the two libraries are different. It is basically OK for one client's API to connect to the other
The two kadmin libraries in API are completely incompatible. From the kadmin application tools of MIT and Heimdal, it can be seen that it is not successful to connect the kadmin server of the other side
Kadmin aims to remotely control the Kerberos server. In general, we seldom develop Kerberos applications with this goal. We use their respective kadmin application tools directly, so kadmin is not compatible and there is no big problem
Our goal is Kerberos authentication, so it's OK to use MIT or Heimdal

MIT is the mainstream. This paper takes MIT as an example

1, Experimental environment
Platform: debian 11

I have installed a Kerberos server (KDC) in advance, the domain is CTP.NET, and created the krblinlin@CTP.NET user principal

2, Client installation development library
root@debian:/# apt-get install libkrb5-dev

3, Simplest krb5 authentication -- no bill generation
1. source code
//Source file name: krbonylogin. 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); //User subject

    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"; //Password
    printf("begin get init creds password\n");
    krberr = krb5_get_init_creds_password(context, &my_creds,kprincpw, (char *)password,NULL,NULL,0,NULL,NULL);//Also read / etc/krb5.conf or service resource record here to get KDC

    if (krberr) { //Authentication failed
            errmsg = krb5_get_error_message(context, krberr); 
            printf("Err: Failed to get init creds password -> %s\n", errmsg); 
            goto cleanup;//Sign out 
        }  

    //Certification success       
    my_creds_ptr = &my_creds;
    printf("get init creds password OK\n");

/*
If the certification is successful, continue to deal with things, such as:
Assuming that this program is a login program, execute execv to run the shell

Omission of this case
*/

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. analysis
This program only tests whether it has passed Kerberos authentication, and does not deal with anything

Typical applications such as the PAM plug-in libpam-krb5 of unix local login program

3. compilation
linlin@debian:~$ gcc -o krbonlylogin krbonlylogin.c -lkrb5

4. operation
The above client source code does not explicitly specify the address to connect to the Kerberos server. The purpose of this article is to express how to use the API in the most concise way, and I have not explored whether / how to specify parameters (such as server address) in the program

There are two ways to configure a connection to Kerberos:
/etc/krb5.conf
SRV (service) resource record
Please refer to < Kerberos + LDAP + NFSv4 to implement single sign on (continued 1) -- DNS + DHCP >( https://blog.51cto.com/13752418/2395345)

Using service resource records requires DNS, so the client machine also needs to configure the / etc/resolv.conf file, assuming the DNS server address is 10.0.3.102
linlin@debian:~$ cat /etc/resolv.conf
nameserver 10.0.3.102
linlin@debian:~$

There are two cases of test failure and success

1) When there is no / etc/krb5.conf or service resource record
linlin@debian:~$ ./krbonlylogin
begin get init creds password
Err: Failed to get init creds password -> Cannot find KDC for realm "CTP.NET"
linlin@debian:~$
Authentication failed, KDC not found

2) When there are only separate / etc/krb5.conf or separate service resource records
To facilitate the test, the password entered by the client is written in the source code. To test the correct / wrong password, you can set the krblinlin@CTP.NET user principal password on the Kerberos server

Wrong password
linlin@debian:~$ ./krbonlylogin
begin get init creds password
Err: Failed to get init creds password -> Preauthentication failed
linlin@debian:~$
Authentication failed

Correct password
linlin@debian:~$ ./krbonlylogin
begin get init creds password
get init creds password OK
linlin@debian:~$
Certification success

3) First test the successful authentication of the configuration of individual service resource record, and then create /etc/krb5.conf. The KDC address is filled in randomly, that is, the service resource record and /etc/krb5.conf exist at the same time

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:~$
The prompt indicates that the KDC server cannot be found. It means that the configuration of / etc/krb5.conf takes precedence. If the incorrect KDC address in the server causes failure, the service resource record will not be attempted

4) summary
The client does not need the krb5.conf file. When DNS is set up in the network, the KDC address can be obtained through the service resource record. When these two exist at the same time, the / etc/krb5.conf takes precedence (no matter whether it succeeds or fails), even if the service resource record is configured correctly

3, krb5 authentication - storage ticket
Generate bill based on the code above
1. source code
//Source file name: 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 -- add generated bill
    krb5_ccache ccache = NULL;

    /* 
    //Generate bills into the memory of this process. This program does not do the verification of bills, so we do not experiment with the memory bills
    //krberr = krb5_cc_resolve(context, "MEMORY:dhcp_ld_krb5_cc", &ccache);
    */

    //Generate the bill to the temporary directory, and verify whether it is valid by ldapwoami. The uid of the experimental login user is 1000, so specify the bill file name 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);//Although it is close d here, the bill "file: / TMP / krb5cc_" will not be destroyed. See below. But I don't know whether the memory bill will be destroyed
    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. analysis
The functions in the above code are all the API functions of MIT krb5 development library. The source code is short, easy to understand and no longer parsed

3. compilation
linlin@debian:~$ gcc -o krbteststore krbteststore.c -lkrb5

4. operation

linlin@debian:~$ ./krbteststore
begin get init creds password
get init creds password OK
Successfully store creds
linlin@debian:~$ ls /tmp
krb5cc_1000  
linlin@debian:~$

It can be seen that the krbteststore program generates the krb5cc? 1000 file of the bill

I have installed an LDAP server (10.0.3.11) in advance, and configured LDAP to use GSSAPI authentication. The krb5 client of the test environment has installed the LDAP client, and the following is to test whether the LDAP client can read the bills generated above and pass GSSAPI
linlin@debian:~$ ldapwhoami -Y GSSAPI -h 10.0.3.11
SASL/GSSAPI authentication started can recognize the bills generated by krbteststore
SASL username: krblinlin@CTP.NET see user principal
SASL SSF: 56
SASL data security layer installed.
dn:uid=krblinlin,cn=gssapi,cn=auth got LDAP user entry
linlin@debian:~$
Note: the generated bill is normal and the authentication is successful

Posted by AdRock on Tue, 17 Mar 2020 22:47:23 -0700