A Deep Dive into the Design of Kerberos.NET
The Kerberos.NET library is a standards-compliant-ish implementation of [RFC 4120] the Kerberos network authentication service. It also includes bits and pieces of:
- [MS-KILE] Microsoft's extensions to Kerberos
- [MS-PAC] Privilege Attribute Certificate (Windows authorization data)
- [MS-SFU] Service for User and Constrained Delegation
- [MS-KKDCP] KDC Proxy
- [MS-SPNG] Negotiate protocol
- [RFC 4556] Public Key Cryptography for Kerberos (PKINIT)
There's no guarantee the implementations of these additional protocols is strictly compliant, but they are implemented enough to be compatible with most major vendors.
The library itself is built against .NET Standard 2.0 to keep it compatible with .NET Framework. It has a Nuget package:
PM> Install-Package Kerberos.NET
There are three logical components of the Kerberos.NET library: client, KDC, and application server. Each component relies on shared services for things like cryptographic primitives or message encoding. The logical structure looks a little like this:
The Kerberos Client
The client is implemented as
KerberosClient and can be extended through overriding methods and properties in derived types. The client is transport agnostic, meaning it relies on other classes to provide on-the-wire support.
There are three built-in transport types: TCP, UDP, and HTTPS. The TCP transport is the only one enabled by default because it's the most reliable in real networks. The HTTPS transport is actually an implementation of the [MS-KKDCP] proxy protocol, which is effectively just the Kerberos TCP messages wrapped in HTTP over TLS.
The client is intended to be scoped per-user and hides most of the protocol goo from the caller for the sake of simplicity. The fundamental flow is
- Get credentials for user (username+password or X509 Certificate)
client.Authenticate(creds)to request a TGT (ticket-granting-ticket)
client.GetServiceTicket(spn)to get a ticket to a specific service
The tickets are cached in the client instance but do not persist across instantiations (an exercise left to the caller).
Kerberos messages are generated by client and all the cryptographic manipulations for encryption and signing are handled by the
KerberosCredential derived classes. There are three native credential types supported by this library (so far):
KerberosAsymmetricCredential. These credentials implement their named pre-authentication and key exchange flows.
Pa-Enc-Timestamp is the default pre-auth method and is implemented in the
KerberosCredential base class.
Password credentials are used for providing the long-term encryption key for the Pa-Enc-Timestamp pre-auth data and AS-REP session key. Passwords require key derivation with salts as provided by the KDC in Krb-Error messages in order to reliably handle AES ciphers.
Keytab credentials are optimizations on password credentials and do not require key derivation. This could be problematic for callers if the KDC decides to rekey and is only recommended in controlled environments.
Asymmetric credentials are RSA or ECC keys for use with PKINIT. They're often protected in hardware through smart cards. The PKINIT client auth flow only implements the Diffie-Hellman key exchange for forward-secrecy of session keys. DH keys are not reused, but callers can implement a caching mechanism by overriding the
StartKeyAgreement methods. ECDH is not supported as of today (Dec 2019), but it's in the works.
The Key Distribution Center (KDC)
The KDC is a server implementation that authenticates users and issues tickets. The KDC implements AS-REQ and TGS-REQ message flows through message handlers, which receive messages through a protocol listener. The only built-in protocol listener is for TCP sockets. Protocol listeners are left to the caller to implement as it's most likely going to need to be customized for their use case. A sample KDC server can be located in the KerberosKdcHostApp project.
AS-REQ and TGS-REQ Message Handling
Messages are received by a
KdcServer instance, which has message handlers registered against it. Messages are parsed by the instance by peeking at the first ASN.1 tag it sees. Each tag type is handled by dedicated handlers. There are two handlers built-in:
KdcTgsReqMessageHandler. It's up to the message handler to validate for protocol correctness and respond as necessary.
Pre-authentication is handled through a similar mechanism aptly named pre-auth handlers. A pre-auth handler is invoked after the first bit of the AS-REQ message handling has parsed the requested username and has looked up the user through the
IRealmService. Pre-authentication requirements are determined based on properties of the found user object. If a user is found to require pre-auth and the requisite pre-auth data isn't present, an error is returned to the client.
User and Service Principals
This library doesn't have a strong opinion on what principals or services should look like and only requires a small amount of information to fulfill most requests. The logical flow of a full Kerberos authentication looks like this as far as principals are concerned:
- AS-REQ is received and the Body.CName is parsed
- The CName is passed to an
- The result is IKerberosPrincipal which indicates what pre-auth types are enabled or required, what long term secrets it supports, and how to generate the user PAC
- The well-known service krbtgt is located through
- A TGT is generated for the user and encrypted to the krbtgt service key and wrapped in an AS-REP message
- The AS-REP is returned to the caller
- TGS-REQ is received and the TGT is extracted from the message
- The TGT is decrypted by looking up the krbtgt service account key through
- The TGS-REQ target service is located through
- The user principal is validated against whether they can get a ticket to the service (by default always yes)
- A new ticket for the user is created and encrypted against the service key located in step 9.
IRealmService is a simple service provider to easily expose principal queries and access realm settings.
The Application Server
The third component of this library is actually the original driver for building this library. Ticket validation is handled by an application service that has received a Kerberos ticket from a client such as web browser and wants to turn it into a ClaimsIdentity.
There are two parts two validation: decrypting the ticket and converting it into an identity.
Decrypting the ticket is handled through by
KerberosValidator which decrypts the message, checks timestamps, and verifies it's not a replay.
Conversion to ClaimsIdentity is handled by
KerberosAuthenticator which extracts principal data as well as PAC authorization data making up group memberships and claims.
The Privilege Attribute Certificate
The PAC is where authorization data like group memberships and claims are carried in Kerberos tickets. This is a Windows-specific detail and as such has some unique characteristics that don't match the rest of the Kerberos protocol.
The PAC is a sequentially ordered C-like structure containing ten or so fields of data. The primary structure is
KERB_VALIDATION_INFO, which is an NDR-encoded message (also known as RPC encoding). The NDR marshaling is handled by the
NdrBuffer class and relies on each encoded struct to provide their own marshal/unmarshal steps.
The ASN.1 Serializer
Kerberos is a protocol serialized using ASN.1 DER encoding. There aren't many good serializers out in the wild and those that are good aren't particularly developer-friendly. The .NET team has an ASN.1 library built into .NET, so I made a fork of that with some minors tweaks. The gist of these tweaks were:
- Set default precisions that match Kerberos specs
- Added GeneralString which Kerberos treats as IA5Encoding for historical reasons
- Modified the generator to output classes instead of structs (probably the most heinous tweak)
- Added message inheritance because a bunch of the messages are semantically the same
Messages are represented as entities during build time and are run through an XSLT transform producing strongly-typed classes. These classes are public and designed to be used as first-class citizens when interacting with the library. Encoding and decoding is considered an implementation detail and marked as internal.
All of the samples can be found under the root of the project on GitHub.
The /samples/ folder contains a handful of projects that show how you can use the KerberosAuthenticator to validate tickets.
The KerbDumpCore folder contains a useful tool the dump the contents of a Kerberos ticket if you have the the right key. It's useful to troubleshooting and debugging.
The KerberosClientApp folder contains a project that implements all the client side features for testing against a user-provided KDC.
The KerberosKdcHostApp folder contains a project that implements a a simple KDC server that listens on a TCP socket. Useful when used with the above client app.