Skip to content

Conversation

@nrhall
Copy link

@nrhall nrhall commented Oct 30, 2025

Purpose

This PR allows Linux based applications using JAAS to acquire Kerberos TGTs natively using the local system's Kerberos libraries/configuration, building on existing support on Windows/MacOSX.

Rationale

Currently the (pure java) JAAS codebase only supports file-based credential caches (ccaches). There are many other useful types of ccache accessible via the local system libraries; this change allows credentials to be acquired natively using those libraries, and thus adds support for all other ccache types supported by the local system (e.g. KCM, in-memory and kernel types), This support already exists on MacOSX and Windows.

The code change here largely uses the MacOSX code, edited for Linux with associated build system changes. It also adds an appropriate jtreg test which uses some native test helper code to manufacture an in-memory cache, and then uses the new code to acquire these credentials natively. This has been tested on Linux/Mac and the jtreg test passes on each (I couldn't see any existing tests on MacOSX for this feature).

Additionally this PR fixes a bug that's existed for a while (see L585-588 in nativeccache.c) - without this code, this is a 100% reproducible segfault on Linux (it's unclear why this hasn't affected the Mac JVMs up to now, probably just no calling code that provides an empty list of addresses). It also fixes a (non problem) typo in the variable name in a function prototype.

Implementation Detail

Note that there were multiple possible ways of doing this:

  1. Duplicate the MacOSX nativeccache.c, edit lightly for Linux and build a new library on Linux only (liblinuxkrb5), leaving MacOSX largely unchanged, but at the expense of this code duplication.

  2. Create a new shared library used on both platforms with conditional compilation to manage the differences. This necessitates a library name change on MacOSX and potentially knock-on packaging changes on that platform, which seemed a potentially expensive side-effect.

  3. Create a shared nativeccache.c (using EXTRA_SRC in the build) and build separate MacOSX/Linux libraries. This allows the MacOSX library name to remain unchanged, and only adds a new library in Linux.

I tried all three options; 3 seemed to be the best compromise all around, although is one of the options that effectively introduces a "no-op" change on MacOSX as a result. Hopefully the additional jtreg test is sufficient to compensate for this.

Interested to hear if anyone else has any suggestions for better ideas!

Notes

It wasn't clear to me what I should do with copyright headers/updating dates in headers. I've added similar boilerplate headers seen in other files to some of the new files, but please let me know what the usual form is here.


Progress

  • Change must not contain extraneous whitespace
  • Commit message must refer to an issue
  • Change must be properly reviewed (2 reviews required, with at least 2 Reviewers)

Issue

  • JDK-8349546: Linux support for Kerberos "nativeccache" functionality (Enhancement - P4)

Reviewing

Using git

Checkout this PR locally:
$ git fetch https://git.openjdk.org/jdk.git pull/28075/head:pull/28075
$ git checkout pull/28075

Update a local copy of the PR:
$ git checkout pull/28075
$ git pull https://git.openjdk.org/jdk.git pull/28075/head

Using Skara CLI tools

Checkout this PR locally:
$ git pr checkout 28075

View PR using the GUI difftool:
$ git pr show -t 28075

Using diff file

Download this PR as a diff file:
https://git.openjdk.org/jdk/pull/28075.diff

Using Webrev

Link to Webrev Comment

@bridgekeeper
Copy link

bridgekeeper bot commented Oct 30, 2025

👋 Welcome back nrhall! A progress list of the required criteria for merging this PR into master will be added to the body of your pull request. There are additional pull request commands available for use with this pull request.

@openjdk
Copy link

openjdk bot commented Oct 30, 2025

❗ This change is not yet ready to be integrated.
See the Progress checklist in the description for automated requirements.

@openjdk openjdk bot added security security-dev@openjdk.org build build-dev@openjdk.org labels Oct 30, 2025
@openjdk
Copy link

openjdk bot commented Oct 30, 2025

@nrhall The following labels will be automatically applied to this pull request:

  • build
  • security

When this pull request is ready to be reviewed, an "RFR" email will be sent to the corresponding mailing lists. If you would like to change these labels, use the /label pull request command.

@nrhall nrhall changed the title 8349546: Linux support for Kerberos "nativeccache” functionality JDK-8349546: Linux support for Kerberos "nativeccache” functionality Oct 30, 2025
@nrhall nrhall changed the title JDK-8349546: Linux support for Kerberos "nativeccache” functionality 8349546: Linux support for Kerberos "nativeccache” functionality Oct 30, 2025
@nrhall nrhall changed the title 8349546: Linux support for Kerberos "nativeccache” functionality 8349546: Linux support for Kerberos "nativeccache" functionality Oct 30, 2025
@openjdk openjdk bot added the rfr Pull request is ready for review label Oct 30, 2025
@mlbridge
Copy link

mlbridge bot commented Oct 30, 2025

@erikj79
Copy link
Member

erikj79 commented Oct 30, 2025

Is there a particular reason for build.sh in the tests or are you just not familiar with how native test code gets automatically compiled by the makefiles based on file naming conventions? In short, any file lib*.c[pp] will get compiled into a native library (and exe*.c[pp] into an executable). You can define any special flags or platform include/exclude in make/test/JtregNativeJdk.gmk.

@nrhall
Copy link
Author

nrhall commented Oct 30, 2025

Is there a particular reason for build.sh in the tests or are you just not familiar with how native test code gets automatically compiled by the makefiles based on file naming conventions? In short, any file lib*.c[pp] will get compiled into a native library (and exe*.c[pp] into an executable). You can define any special flags or platform include/exclude in make/test/JtregNativeJdk.gmk.

Definitely the unfamiliar bit, although the build.sh was useful for doing some quick test runs. Is there an example you could point me at - I'd be happy to fix that.

Edit: found some examples - fix incoming.

@nrhall
Copy link
Author

nrhall commented Oct 30, 2025

@erikj79 I've had a go at the suggested changes - hope that's more what you were looking for?

@nrhall
Copy link
Author

nrhall commented Oct 31, 2025

Thanks for all the help and pointers @erikj79 - I've pushed a commit to (hopefully) address all of your comments!

Copy link
Member

@erikj79 erikj79 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From a build point of view this is definitely starting to look better. I would like to get input from a reviewer in the component team at this point to comment on the validity of the change before I spend more time reviewing the build aspects.

@nrhall
Copy link
Author

nrhall commented Nov 3, 2025

Thanks for these @erikj79, no idea how I managed to miss at least one of those...! All addressed in latest commit.

@nrhall nrhall requested a review from erikj79 November 3, 2025 18:47
@nrhall
Copy link
Author

nrhall commented Nov 12, 2025

@smemery have pushed fixes for your comments. I've tested the changes on Linux, but don't have a Mac here today to test the build changes - so will attend to anything that fails in the CI.

@wangweij
Copy link
Contributor

/reviewers 2 reviewer

@openjdk
Copy link

openjdk bot commented Nov 12, 2025

@wangweij
The total number of required reviews for this PR (including the jcheck configuration and the last /reviewers command) is now set to 2 (with at least 2 Reviewers).

@nrhall
Copy link
Author

nrhall commented Nov 13, 2025

@smemery @wangweij have pushed some commits addressing your requested changes - thanks!

@ecki
Copy link
Contributor

ecki commented Nov 13, 2025

Thanks for the work and maybe sorry for a dumb question but maybe it helps with documentation - if i get this right this only works for cases where the native client actually can extract the key credentials but not for things where a external component would be needed to apply them (like ssdp, tpm, sssd and the like, right?)

@nrhall
Copy link
Author

nrhall commented Nov 13, 2025

Thanks for the work and maybe sorry for a dumb question but maybe it helps with documentation - if i get this right this only works for cases where the native client actually can extract the key credentials but not for things where a external component would be needed to apply them (like ssdp, tpm, sssd and the like, right?)

Assuming I've understood your question:

  • this works for all cases where the system libraries could get to a TGT in a ccache - so FILE:, MEMORY:, KEYRING:, KCM:, etc. For things like sssd, assuming that's using the sssd KCM implementation, that should work (we use KCM here, though not sssd's implementation).
  • If something else is needed to extract/convert a secret that's stored in another place and use it as a key to get a TGT from the KDC, this wouldn't help, although as you can hopefully see from the test helper, once you've got the TGT in Java, it wouldn't be that hard to have a set of JNI helper functions so that it could be written to the right ccache type (as opposed to now, when this would have to be a FILE: ccache). My reading of the way this works in JAAS currently though is that any acquired key/keytab etc is only written to the private credentials in the javax.security.auth.Subject object, and never actually written back to the ccache.

Was that what you were getting at?

@nrhall
Copy link
Author

nrhall commented Nov 13, 2025

I'm not sure if there's precedent for it in other java.security.Principal types that JAAS can operate on, but it wouldn't be that hard to enhance the JAAS Krb5LoginModule to accept a new piece of JAAS configuration that would cause it to write the acquired credential back to an actual ccache as part of storing the keys during commit (within the limits of the local system arrangements - e.g. this wouldn't work on Windows because of the way LSASS protects the ccache, but likely works in Linux/MacOS cases using natively supported cache types).

That said, it's not clear that's needed for client applications that use the various security frameworks / principals in the JVM (where the stored keys in the principal are used), but could be useful for helper applications written in Java that do some kind of credential exchange to create a TGT, or perhaps things written partly in JNI.

public static native boolean setDefaultCache(String cacheName);
public static native boolean createInMemoryCacheFromFileCache(String inMemoryCacheName, String fileCacheName);
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are 2 newlines at the end. One is enough.

* --add-exports java.security.jgss/sun.security.krb5.internal.ktab=ALL-UNNAMED
* --add-exports java.security.jgss/sun.security.jgss.krb5=ALL-UNNAMED
* --add-exports java.base/sun.security.util=ALL-UNNAMED
* --add-exports java.base/jdk.internal.misc=ALL-UNNAMED
Copy link
Contributor

@wangweij wangweij Nov 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use

@modules java.security.jgss/sun.security.krb5
         java.security.jgss/sun.security.krb5.internal
         ...

It applies to both compile and run.

@wangweij
Copy link
Contributor

wangweij commented Nov 15, 2025

Hi @erikj79, I noticed that in our CI build the library is not built and the log shows

checking for krb5 (<omitted>)... no
checking krb5.h usability... no
checking krb5.h presence... no
checking for krb5.h... no 

How can we require them to be present?

@erikj79
Copy link
Member

erikj79 commented Nov 17, 2025

Hi @erikj79, I noticed that in our CI build the library is not built and the log shows

checking for krb5 (<omitted>)... no
checking krb5.h usability... no
checking krb5.h presence... no
checking for krb5.h... no 

How can we require them to be present?

Is this intended to be a required dependency for OpenJDK or are you referring to how we would enable this feature and dependency for Oracle builds?

If the latter, they would need to be part of the devkit that we use. So the first step would be to modify the devkit creation makefiles for Linux in make/devkit/..., then create a new devkit, verify and deploy internally.

You will also need to verify that libkrb5 is reasonably stable and compatible across versions, since we only build one JDK distribution per architecture for Linux. We use a sysroot from the oldest Linux distribution we support, so you would need to verify that linking against libkrb5 in that sysroot/devkit results in a JDK that is still runtime compatible with all the Linux versions we support.

@wangweij
Copy link
Contributor

I am thinking this should be a required dependency. Otherwise, the JDK build will vary depending on the machine used. I don’t believe we’ve ever done this before (unless user explicitly specifies configure options), right?

@nrhall, a related question: what if the library is available at build time but not at runtime?

@nrhall
Copy link
Author

nrhall commented Nov 18, 2025

I am thinking this should be a required dependency. Otherwise, the JDK build will vary depending on the machine used. I don’t believe we’ve ever done this before (unless user explicitly specifies configure options), right?

@nrhall, a related question: what if the library is available at build time but not at runtime?

If libkrb5 available at build time, it will build the new JNI library/test etc as expected; if libkrb5 is then not available at runtime, the library will fail to load silently (once, and then will not be retried) - this was actually already part of the code, so I imagine this case has happened before somewhere already - see Credentials.java L446:

            if (!alreadyTried) {
                // See if there's any native code to load
                try {
                    ensureLoaded();
                } catch (Exception e) {
                    if (DEBUG != null) {
                        DEBUG.println("Can not load native ccache library");
                        e.printStackTrace();
                    }
                    alreadyTried = true;
                }
            }

I will say that libkrb5 is largely pretty stable (it's really old code with few features being added), though I'm not sure what range is required to be supported here in terms of operating system builds? Worst case though, any incompatibility will just result in this code not being used, although as it'd be a silent failure without debug flags, that's perhaps not ideal (but has always been the case, even before this change).

@nrhall
Copy link
Author

nrhall commented Nov 18, 2025

@wangweij I've attended to your code review feedback above (and cleaned up a few other bits of the jtreg directives that weren't required).

@nrhall
Copy link
Author

nrhall commented Nov 18, 2025

I will say that libkrb5 is largely pretty stable (it's really old code with few features being added), though I'm not sure what range is required to be supported here in terms of operating system builds? Worst case though, any incompatibility will just result in this code not being used, although as it'd be a silent failure without debug flags, that's perhaps not ideal (but has always been the case, even before this change).

Two data points, but the JVM/krb5 library/jtreg test compiled on RHEL8 works on Ubuntu 24.04, and these two are reasonably far apart:

- RHEL8
glibc 2.28
kernel 4.18.0
krb5 1.18

- Ubuntu 24.04
glibc 2.39
kernel 6.8.0
krb5 1.20

Obviously the reverse doesn't work, but that's to be expected.

@wangweij
Copy link
Contributor

UnsatisfiedLinkError is not an Exception. It's just a Throwable.

@nrhall
Copy link
Author

nrhall commented Nov 18, 2025

UnsatisfiedLinkError is not an Exception. It's just a Throwable.

Good point. I wonder what the intent of that exception handling code is, then. I assumed it was to silently fall back to using the pure Java code in the event a library was un-loadable / not present, etc. In this particular case, it's almost essential that this happens, because otherwise a JDK with this feature compiled in and distributed to a machine without libkrb5 installed would basically be unable to use anything that called the Credentials class methods.

Looking at the code further: ensureLoaded() calls only System.loadLibrary(), which only throws the following checked exceptions:

SecurityException
UnsatisfiedLinkError
NullPointerException

Of those, the libname is always a static string, so that just leaves the first two (or another RuntimeException). Either of those will yield an un-loadable library, but the first will be swallowed. Does that mean the code above should also catch the UnsatisfiedLinkError?

@nrhall
Copy link
Author

nrhall commented Nov 18, 2025

For what it's worth, here's how this looks using KCM on a host...

...with libkrb5 installed:

krb5loginmodule[0x3|main|Krb5LoginModule.java:647|2025-11-18 13:30:45.208]: Acquire TGT from Cache
krb5loginmodule[0x3|main|Krb5LoginModule.java:678|2025-11-18 13:30:45.252]: Principal is user-redacted@REALM.REDACTED
krb5loginmodule[0x3|main|Krb5LoginModule.java:1140|2025-11-18 13:30:45.272]: Commit Succeeded 

krb5loginmodule[0x3|main|Krb5LoginModule.java:1190|2025-11-18 13:30:45.273]:            [Krb5LoginModule]: Entering logout
krb5loginmodule[0x3|main|Krb5LoginModule.java:1218|2025-11-18 13:30:45.273]:            [Krb5LoginModule]: logged out Subject

...without libkrb5 installed:

krb5loginmodule[0x3|main|Krb5LoginModule.java:647|2025-11-18 13:34:13.604]: Acquire TGT from Cache
Exception in thread "main" java.lang.UnsatisfiedLinkError: /path/redacted/jdk/build/linux-x86_64-server-release/jdk/lib/liblinuxkrb5.so: libkrb5.so.3: cannot open shared object file: No such file or directory
        at java.base/jdk.internal.loader.NativeLibraries.load(Native Method)
        at java.base/jdk.internal.loader.NativeLibraries$NativeLibraryImpl.open(NativeLibraries.java:326)
        at java.base/jdk.internal.loader.NativeLibraries.loadLibrary(NativeLibraries.java:187)
        at java.base/jdk.internal.loader.NativeLibraries.loadLibrary(NativeLibraries.java:129)
        at java.base/jdk.internal.loader.NativeLibraries.findFromPaths(NativeLibraries.java:254)
        at java.base/jdk.internal.loader.NativeLibraries.loadLibrary(NativeLibraries.java:239)
        at java.base/java.lang.ClassLoader.loadLibrary(ClassLoader.java:2315)
        at java.base/java.lang.Runtime.loadLibrary0(Runtime.java:822)
        at java.base/java.lang.System.loadLibrary(System.java:1685)
        at java.security.jgss/sun.security.krb5.Credentials.ensureLoaded(Credentials.java:535)
        at java.security.jgss/sun.security.krb5.Credentials.acquireDefaultCreds(Credentials.java:449)
        at java.security.jgss/sun.security.krb5.Credentials.acquireTGTFromCache(Credentials.java:335)
        at jdk.security.auth/com.sun.security.auth.module.Krb5LoginModule.attemptAuthentication(Krb5LoginModule.java:649)
        at jdk.security.auth/com.sun.security.auth.module.Krb5LoginModule.login(Krb5LoginModule.java:601)
        at java.base/javax.security.auth.login.LoginContext.invoke(LoginContext.java:603)
        at java.base/javax.security.auth.login.LoginContext.login(LoginContext.java:460)
        at RunWhoAmI.main(RunWhoAmI.java:22)

...without libkrb5 installed, but with a small patch to also catch UnsatisfiedLinkError:

krb5loginmodule[0x3|main|Krb5LoginModule.java:647|2025-11-18 13:52:09.045]: Acquire TGT from Cache
krb5loginmodule[0x3|main|Krb5LoginModule.java:678|2025-11-18 13:52:09.053]: Principal is null
krb5loginmodule[0x3|main|Krb5LoginModule.java:681|2025-11-18 13:52:09.053]: null credentials from Ticket Cache
krb5loginmodule[0x3|main|Krb5LoginModule.java:608|2025-11-18 13:52:09.054]:             [Krb5LoginModule] authentication failed 
Unable to obtain Principal Name for authentication 
Login failed
javax.security.auth.login.LoginException: Unable to obtain Principal Name for authentication 
        at jdk.security.auth/com.sun.security.auth.module.Krb5LoginModule.promptForName(Krb5LoginModule.java:816)
        at jdk.security.auth/com.sun.security.auth.module.Krb5LoginModule.attemptAuthentication(Krb5LoginModule.java:693)
        at jdk.security.auth/com.sun.security.auth.module.Krb5LoginModule.login(Krb5LoginModule.java:601)
        at java.base/javax.security.auth.login.LoginContext.invoke(LoginContext.java:603)
        at java.base/javax.security.auth.login.LoginContext.login(LoginContext.java:460)
        at RunWhoAmI.main(RunWhoAmI.java:22)

@wangweij
Copy link
Contributor

Does that mean the code above should also catch the UnsatisfiedLinkError?

If we make libkrb5 a required dependency and make sure your new native code is always compiled into JDK, this is a real error and we should fail loudly. Otherwise, we need to fallback to pure Java. I guess this is also the reason why it was not caught before.

I still think it should be a required dependency.

@nrhall
Copy link
Author

nrhall commented Nov 19, 2025

There's prior art here for CUPS - this is a mandatory compile-time and run-time dependency. I guess the difference with CUPS is that Java can't print without it, whereas JAAS can auth using Kerberos without my code, it's just limited to a file ccache.

It turns out that this limitation actually works to our advantage. The existing code will first try and use the pure Java code to acquire a file ccache using a series of hard-coded defaults:

     * 1. KRB5CCNAME (bare file name without FILE:)
     * 2. /tmp/krb5cc_<uid> on unix systems
     * 3. <user.home>/krb5cc_<user.name>
     * 4. <user.home>/krb5cc (if can't get <user.name>)

(I'm not sure of the provenance of 3 and 4, but 1 and 2 are reasonable)

It then checks that the crypto is something it can handle.

If the code successfully finds a supported ccache, it will succeed whether the new native lib is loadable or not. If it does not find a supported ccache this way, it will then try and load the native lib (and potentially fail with an UnsatisfiedLinkError) at this point.

(FWIW, I've tested this locally by temporarily making the system libkrb5 library inaccessible, then running a test with a regular FILE: ccache, and it worked as above.)

Assuming we did build it in by default, I suspect most people using FILE: ccaches will not even get to the native library load - and anyone trying to use unsupported ccache types/crypto with an older Java version would have got an error anyway, it's just that now this might be an UnsatisfiedLinkError instead of a LoginException.

I imagine this code was written this way for similar reasons to the discussion we're having here, on whichever of Windows/MacOS this was first introduced.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

build build-dev@openjdk.org rfr Pull request is ready for review security security-dev@openjdk.org

Development

Successfully merging this pull request may close these issues.

5 participants