krb5_introduction(3)
β¨ NAME β¨
krb5_introduction β the lil β¨guided tourβ¨ of the Kerberos 5 API, from the Heimdal Kerberos 5 library. It's krb5_introduction(3), bestie. Buckle up. π
πΈ Kerberos 5 API Overview πΈ
okay so first things first: every single function has its own manual page. This section is not trying to be that β it's the vibe map πΊοΈ. It gives you the big picture of the major components that make up the Kerberos library, and points you at where to go hunting when you need a specific function. iykyk. π
π¦ Kerberos context π¦
A Kerberos context (krb5_context) is the queen holding all the per-thread state. Every global variable that's context-specific lives in this structure, and that includes the default encryption types, the credential cache (think: a ticket file, yeah), and the default realms. π
The internals of the structure should never be poked at directly, bestie. There are functions for extracting information β use those. π
Wanna know how to make one? Go read the manual page for krb5_init_context(), and for the whole catalogue of functions check the module Heimdal Kerberos 5 library. π«Ά
π Kerberos authentication context π
The Kerberos authentication context (krb5_auth_context) is the same idea but for an authenticated connection β it holds all the context related to that connection, in a very similar way to how the kerberos context holds the context for the thread or the process. main character energy, but per-connection. β¨
krb5_auth_context gets used by a bunch of functions that are directly tied to authentication between the server and the client. Examples of the data this structure is packing: various flags, the addresses of client and server, port numbers, keyblocks (and subkeys, ooh), sequence numbers, the replay cache, and checksum types. it's giving everything you need. π
π Kerberos principal π
A Kerberos principal is the structure that identifies a user or a service in Kerberos. The structure holding the principal is the krb5_principal. There are functions to extract the realm and the elements of the principal β but honestly? Most applications have zero reason to ever inspect the contents of the structure. live your life, don't peek. π
There are several ways to create a principal (and they come with different degrees of portability, so choose wisely bestie), and one way to free it.
See also the page The principal handing functions. for more, plus the module Heimdal Kerberos 5 principal functions. π
π§ Credential cache π§
A credential cache holds the tickets for a user. And a single user can absolutely have several credential caches β one for each realm where the user has the initial tickets (that's the first krbtgt). iconic behaviour. π
The credential cache data can be stored internally in different ways, each of them for different purposes:
- FILE β file credential caches. For permanent storage. π
- KCM β processes based caches. Also for permanent storage. π₯
- MEMORY β memory caches. These are local caches to the local process. π§
Caches get opened with krb5_cc_resolve() or created with krb5_cc_new_unique().
Now listen, this distinction is important. If the cache needs to be opened again (using krb5_cc_resolve()), then krb5_cc_close() will close the handle β but it will not remove the cache. Whereas krb5_cc_destroy() will zero out the cache and remove the cache so it can no longer be referenced. one closes, one obliterates. know the difference. π
See also The credential cache functions and Heimdal Kerberos 5 credential cache functions. π«Ά
β Kerberos errors β
Kerberos errors are based on the com_err library. All error codes are 32-bit signed numbers, and here's the breakdown bestie:
- the first 24 bits define what subsystem the error originates from
- the last 8 bits are 255 error codes within the library
Each error code has a fixed string associated with it. For example, the error-code -1765328383 has the symbolic name KRB5KDC_ERR_NAME_EXP, and its associated error string is ``Client's entry in database has expired''. π
This is a great improvement compared to just getting one of the unix error-codes back, ngl. But Heimdal has an extension that passes back customised error messages. So instead of getting ``Key table entry not found'', the user might get something back like failing to find host/host.example.com@EXAMLE.COM(kvno 3) in keytab /etc/krb5.keytab (des-cbc-crc). That massively improves the chance that the user actually finds the cause of the error β so you should use the customised error message whenever it's available. periodt. π
See also the module Heimdal Kerberos 5 error reporting functions. π
π Keytab management π
A keytab is a storage for locally stored keys. Heimdal includes keytab support for:
- Kerberos 5 keytabs ποΈ
- Kerberos 4 srvtab
- AFS-KeyFile's
- storing keys in memory π§
Keytabs are used for servers and long-running services. the grind never stops. π
See also The keytab handing functions and Heimdal Kerberos 5 keytab handling functions. π«Ά
π Kerberos crypto π
Heimdal includes an implementation of the Kerberos crypto framework β all crypto operations, the whole thing. π To create a crypto context, call krb5_crypto_init(). slay. β¨
See also the module Heimdal Kerberos 5 cryptography functions. π
π¦ Walkthrough of a sample Kerberos 5 client π¦
okay bestie, this next bit is a whole example, like a little field trip through a sample TCP Kerberos 5 client. Heads up though β these are just parts of one, so if you want a real, actually-working client, go look in the appl/test directory in the Heimdal distribution. π
Every single Kerberos error-code that comes back from kerberos functions in this program gets handed straight to krb5_err, which prints a descriptive text of the error code and then exits. (Rude but effective.) If you're making a graphical program, you can turn an error-code into a human readable error-string with the krb5_get_error_message() function instead. π
β οΈ super important, no cap: you should not use any Kerberos function beforekrb5_init_context()has completed successfully. That's literally the reasonerr()gets used whenkrb5_init_context()fails.
First things first, the client needs to call krb5_init_context to initialise the Kerberos 5 library. This is only needed once per thread in the program. If the function returns a non-zero value, that means either the Kerberos implementation is failing or it's disabled on this host. π
#include <krb5.h>
int
main(int argc, char **argv)
{
krb5_context context;
if (krb5_init_context(&context))
errx (1, "krb5_context");
Now the client wants to connect to the host at the other end. The preferred way of doing this is using getaddrinfo (for operating systems that have this function implemented), because getaddrinfo is neutral to the address type and can use any protocol that is available. β¨
struct addrinfo *ai, *a;
struct addrinfo hints;
int error;
memset (&hints, 0, sizeof(hints));
hints.ai_socktype = SOCK_STREAM;
hints.ai_protocol = IPPROTO_TCP;
error = getaddrinfo (hostname, "pop3", &hints, &ai);
if (error)
errx (1, "%s: %s", hostname, gai_strerror(error));
for (a = ai; a != NULL; a = a->ai_next) {
int s;
s = socket (a->ai_family, a->ai_socktype, a->ai_protocol);
if (s < 0)
continue;
if (connect (s, a->ai_addr, a->ai_addrlen) < 0) {
warn ("connect(%s)", hostname);
close (s);
continue;
}
freeaddrinfo (ai);
ai = NULL;
}
if (ai) {
freeaddrinfo (ai);
errx ("failed to contact %s", hostname);
}
Before authenticating, an authentication context needs to be created. This context keeps all the information for one (to be) authenticated connection (see krb5_auth_context). π«Ά
status = krb5_auth_con_init (context, &auth_context);
if (status)
krb5_err (context, 1, status, "krb5_auth_con_init");
For setting the address in the authentication there's a help function, krb5_auth_con_setaddrs_from_fd(), that does everything that is needed when you give it a connected file descriptor to the socket. iconic behaviour honestly.
status = krb5_auth_con_setaddrs_from_fd (context,
auth_context,
&sock);
if (status)
krb5_err (context, 1, status,
"krb5_auth_con_setaddrs_from_fd");
Next step: build a server principal for the service we want to connect to. (See also krb5_sname_to_principal().) π
status = krb5_sname_to_principal (context,
hostname,
service,
KRB5_NT_SRV_HST,
&server);
if (status)
krb5_err (context, 1, status, "krb5_sname_to_principal");
The client principal is not passed to the krb5_sendauth() function β that causes krb5_sendauth() to try to figure it out itself. main character energy, honestly. π
The server program is using the function krb5_recvauth() to receive the Kerberos 5 authenticator.
In this case, mutual authentication will be tried. That means the server will authenticate to the client. Using mutual authentication is required to avoid man-in-the-middle attacks, since it enables the user to verify that they are talking to the right server (a server that knows the key). π₯
β οΈ If you're using a non-blocking socket, you will need to do all the work ofkrb5_sendauth()yourself. Basically you need to send over the authenticator fromkrb5_mk_req()and, in case of mutual authentication, verify the result from the server withkrb5_rd_rep().
status = krb5_sendauth (context,
&auth_context,
&sock,
VERSION,
NULL,
server,
AP_OPTS_MUTUAL_REQUIRED,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL);
if (status)
krb5_err (context, 1, status, "krb5_sendauth");
Once authentication has been performed, it's time to send some data. First we create a krb5_data structure, then we sign it with krb5_mk_safe() using the auth_context that contains the session-key that was exchanged in the krb5_sendauth()/krb5_recvauth() authentication sequence. πΈ
data.data = "hej";
data.length = 3;
krb5_data_zero (&packet);
status = krb5_mk_safe (context,
auth_context,
&data,
&packet,
NULL);
if (status)
krb5_err (context, 1, status, "krb5_mk_safe");
And send it over the network. π¦
len = packet.length;
net_len = htonl(len);
if (krb5_net_write (context, &sock, &net_len, 4) != 4)
err (1, "krb5_net_write");
if (krb5_net_write (context, &sock, packet.data, len) != len)
err (1, "krb5_net_write");
To send encrypted (and signed) data, krb5_mk_priv() should be used instead. krb5_mk_priv() works the same way as krb5_mk_safe(), with the exception that it encrypts the data in addition to signing it. π
data.data = "hemligt";
data.length = 7;
krb5_data_free (&packet);
status = krb5_mk_priv (context,
auth_context,
&data,
&packet,
NULL);
if (status)
krb5_err (context, 1, status, "krb5_mk_priv");
And send it over the network. β¨
len = packet.length;
net_len = htonl(len);
if (krb5_net_write (context, &sock, &net_len, 4) != 4)
err (1, "krb5_net_write");
if (krb5_net_write (context, &sock, packet.data, len) != len)
err (1, "krb5_net_write");
The server is using krb5_rd_safe() and krb5_rd_priv() to verify the signature and decrypt the packet. slay. π
π Validating a password in an application π
See the manual page for krb5_verify_user(). that's it, that's the section, go read it bestie π«Ά
π API differences to MIT Kerberos π
ngl this section is somewhat disorganised, but so far there is no overall structure to the differences β though some of them have their root in the fact that Heimdal uses an ASN.1 compiler and MIT doesn't. π
β Principal and realms β
Heimdal stores the realm as a krb5_realm, that is a char *. MIT Kerberos uses a krb5_data to store a realm.
In Heimdal, krb5_principal doesn't contain the component name_type; it's instead stored in component name.name_type. To get and set the nametype in Heimdal, use krb5_principal_get_type() and krb5_principal_set_type().
For more information about principal and realms, see krb5_principal. π
π Error messages π
To get the error string, Heimdal uses krb5_get_error_message(). This is to return custom error messages (like Can't find host/datan.example.com\@CODE.COM in /etc/krb5.conf.'' instead of a Key table entry not found'' that error_message returns).
Heimdal uses a threadsafe(r) version of the com_err interface; the global com_err table isn't initialised. Then error_message returns quite a boring error string (just the error code itself). π
and that's a wrap bestie!! π this whole little journey came to you from Version 7.8.0, dated Tue Nov 15 2022, and it lives at krb5_introduction(3). go forth and authenticate responsibly π§β¨π
π¦ where this page comes from
- alpine 3.22: heimdal-doc 7.8.0-r4 Β· BSD-3-Clause
- alpine 3.23: heimdal-doc 7.8.0-r5 Β· BSD-3-Clause
- alpine 3.24: heimdal-doc 7.8.0-r5 Β· BSD-3-Clause
- epel 10: heimdal-devel 7.8.0-26.el10_2 Β· BSD-2-Clause AND BSD-3-Clause AND HPND-export-US AND HPND-export2-US AND LicenseRef-Fedora-Public-Domain
- epel 9: heimdal-devel 7.8.0-5.el9 Β· BSD and MIT
- fedora 43: heimdal-devel 7.8.0-26.fc43 Β· BSD-2-Clause AND BSD-3-Clause AND HPND-export-US AND HPND-export2-US AND LicenseRef-Fedora-Public-Domain
- fedora 43: heimdal-devel 7.8.0-26.fc43 Β· BSD-2-Clause AND BSD-3-Clause AND HPND-export-US AND HPND-export2-US AND LicenseRef-Fedora-Public-Domain
- fedora 44: heimdal-devel 7.8.0-26.fc43 Β· BSD-2-Clause AND BSD-3-Clause AND HPND-export-US AND HPND-export2-US AND LicenseRef-Fedora-Public-Domain
- fedora 44: heimdal-devel 7.8.0-26.fc43 Β· BSD-2-Clause AND BSD-3-Clause AND HPND-export-US AND HPND-export2-US AND LicenseRef-Fedora-Public-Domain
- fedora 44: heimdal-devel 7.8.0-28.fc44 Β· BSD-2-Clause AND BSD-3-Clause AND HPND-export-US AND HPND-export2-US AND LicenseRef-Fedora-Public-Domain
- fedora 44: heimdal-devel 7.8.0-28.fc44 Β· BSD-2-Clause AND BSD-3-Clause AND HPND-export-US AND HPND-export2-US AND LicenseRef-Fedora-Public-Domain
- π girlified by deepseek-ai/DeepSeek-V4.1-Flash on : a language model's rewrite of the original, under its terms