Kerberos.NET is built to be used across multiple platforms, however there are some caveats.

Historically people have tended to think .NET is Windows-only and therefore any library built on it is only going to run on Windows. This hasn't been the reality for a few years now and .NET will run mostly anywhere these days. Kerberos.NET will run on any supported .NET Standard 2.0 runtime, which today is Windows, Linux, and Mac OS. The recommended method is using the nuget package.

PM> Install-Package Kerberos.NET

However, there are some important caveats.

Cryptographic Support

The cryptographic primitives used by the Kerberos protocols are available in .NET across all platforms, however there are extensions to the protocol that rely on primitives that are not exposed at the framework level. There are two extensions that do not work for time being:

  1. RFC 4757: RC4-HMAC -- This would be better described as the MD4/RC4/HMACMD5 suite. It's the defacto suite introduced in Windows 2000 for compatibility reasons and has since been deprecated by the standards bodies. The MD4 hash is not exposed through .NET (for good reason) and must be P/Invoke'd or written in managed code. It's not a particularly complicated hashing algorithm, but it's also not worth taking on for the sake of cross-platform support. You're not affected in any way if you're using AES (as you should be).
  2. RFC 4556: PKINIT -- This relies on a Diffie-Hellman key agreement, which is also not exposed through .NET (also for good reason). This is somewhat more problematic because it's an important extension and should be supported everywhere. It's not a particularly complicated algorithm and in fact there's a poorly written implemention in the test harness. This needs to be better before it would be considered for library support. This might also be moot once Elliptic Curve support is added.

Adding Your own Cryptographic Implementation

If you're so inclined and need either of the above two extensions you can bring your own implementation by registering your own platform layer:

CryptoPal.RegisterPal(() => new MyCustomCryptoPalImplementation());

It needs to be called before any crypto calls occur, otherwise you're going to defaulted to the existing platform implementation.

DNS Resolution Support

Kerberos relies on DNS for resolving KDC locations by checking for SRV records of the Kerberos service at _kerberos.tcp.realm.net (or UDP). This is necessary if you don't know the location of the KDC you're needing to authenticate against. Unfortunately .NET doesn't expose a way to query SRV records, and the library relies on P/Invoke into the Windows DnsQuery_W function to do this. You can bring your own DNS query logic by overriding the QueryDomain method on the client transport classes TcpKerberosTransport, UdpKerberosTransport, and HttpsKerberosTransport.

Unit Tests

The unit tests are inherently Windows-centric and P/Invoke for a myriad of interop and compatibility reasons. This isn't a problem for developers just grabbing the nuget package, but it is a bit problematic for anyone wanting to extend the library or fix the above limitations on non-Windows platforms. Eventually the tests may be reorganized and tagged as platform-specific, but that's not a high priority at the moment.

There’s a common problem that many applications run in to when executing cryptographic operations, and that’s the fact that the keys they use tend to exist within the application itself. This is problematic because there’s no protection of the keys — the keys are recoverable if you get a dump of the application memory, or you’re able to execute arbitrary code within the application. The solution to this problem is relatively straightforward — keep the keys out of the application.

In order for that to be effective you need to also move the crypto operations out of the application too. This means any attacks on the application won’t yield the keys. This is the fundamental design for services like Azure Key Vault, Amazon CloudHSM, or any other HSM service or device. In fact, even Windows subscribes to this design with CNG keys using LSASS.

I was thinking about this problem over the weekend and realized there isn’t really any good reference architecture out there that shows you how to build this design into your application. The services I mentioned earlier do great jobs at protecting secrets, but they’re kind of designed with certain applications in mind — greenfield, cloud first, or disconnected. This can make it difficult to migrate existing applications to use these services, or maybe you just can’t use them for whatever reason (regulatory, legal, etc.). On top of all that, you don’t even know how to start.

So, I did something dumb. I built a reference implementation that lets you move crypto operations out of your application and into a separate process.

Introducing: Enclave.NET.

READ THIS SECURITY WARNING

This is a reference implementation. That means it’s not designed to handle production loads, and absolutely is not built to withstand attack. It’s a sample intended to show how you might offload certain operations. There are probably some horrible bugs in here and there might even be vulnerabilities.

Was that sufficiently scary enough?

The basic idea is simple. You have an application that hosts a service. The service has a set of commands available to it:

  • Generate key
  • Encrypt
  • Decrypt
  • Sign
  • Validate

Your application calls these commands with the necessary payloads, the service does the thing, and returns a result. The service is HTTP-based, and protected with pinned client certificates.

The crypto operations are real, backed by the jose-jwt library, but they’re ephemeral — the keys are just stored in memory. The idea is that you can inject your own implementations as you see fit, so any migrations you might undergo can be gradual and painless.

There are two classes that need to be implemented — the crypto operations class, and the storage service. You can use the built-in crypto operations class InMemoryCryptoProcessor at your own risk, but you absolutely need to implement the storage, lest you lose all the keys when the app shuts down.

Crypto Processor

Storage Service

You can modify the startup code on your own, or you can implement IStartupTransform and configure it:

Calling the service is simple:

For more information take a look at the sample app.

Earlier we looked at how to build and package and then deploy nuget packages. One thing (of many) I glossed over was that whole version thing. It turns out versioning is really difficult to do. It’s kind of like naming things.

I’m not going to go into the virtues of one method (like semantic versioning) over others, but really just going to show how I set it up so my silly little project always has an incrementing version number after build.

First, I created two new build variables “MajorVersion” and “MinorVersion”, setting their values to match the current static version.

Set the Variables

Once the variables were set I could use them to set the BuildNumber variable in the build options.

Set the Build Number

This uses the major and minor as the static values created earlier, the current month/day for the build, and the revision number for the build revision. This’ll generate a value along the lines of 1.5.0819.1 and will increment to 1.5.0819.2 until tomorrow, which’ll bump up to 1.5.0820.1, etc. Yes, I’m a monster.

With the build number defined we can configure the build task to explicitly set the Version property with -p:Version=$(Build.BuildNumber).

Set Build Version

Now any new builds will use this version format. This way I don’t have to worry about forgetting to increment the build numbers on small fixes and releases. I still have to increment the major and minor versions when anything big is released, but that’s reasonable and expected.