Add support for native Kerberos credential acquisition on Linux

This commit is contained in:
Nick Hall 2025-10-27 13:02:28 -04:00
parent 583ff202b1
commit c0204db9bd
11 changed files with 819 additions and 13 deletions

113
make/autoconf/lib-krb5.m4 Normal file
View File

@ -0,0 +1,113 @@
#
# Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
#
# This code is free software; you can redistribute it and/or modify it
# under the terms of the GNU General Public License version 2 only, as
# published by the Free Software Foundation. Oracle designates this
# particular file as subject to the "Classpath" exception as provided
# by Oracle in the LICENSE file that accompanied this code.
#
# This code is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
# version 2 for more details (a copy is included in the LICENSE file that
# accompanied this code).
#
# You should have received a copy of the GNU General Public License version
# 2 along with this work; if not, write to the Free Software Foundation,
# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
#
# Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
# or visit www.oracle.com if you need additional information or have any
# questions.
#
################################################################################
# Setup krb5 (Kerberos 5)
################################################################################
AC_DEFUN_ONCE([LIB_SETUP_KRB5],
[
AC_ARG_WITH(krb5, [AS_HELP_STRING([--with-krb5],
[enable krb5 support (default=yes), or "no" to disable])])
# Determine if krb5 should be disabled
KRB5_DISABLED=no
if test "x${with_krb5}" = xno; then
AC_MSG_NOTICE([krb5 explicitly disabled])
KRB5_DISABLED=yes
elif test "x$NEEDS_LIB_KRB5" = xfalse; then
if test "x${with_krb5}" != x && test "x${with_krb5}" != xno; then
AC_MSG_WARN([[krb5 not used, so --with-krb5 is ignored]])
fi
KRB5_DISABLED=yes
fi
if test "x$KRB5_DISABLED" = xyes; then
KRB5_CFLAGS=
KRB5_LIBS=
ENABLE_LIBLINUXKRB5=false
else
# First try pkg-config (most modern approach)
AC_PATH_TOOL([PKG_CONFIG], [pkg-config], [no])
use_pkgconfig_for_krb5=no
if test "x$PKG_CONFIG" != "xno"; then
AC_MSG_CHECKING([if pkg-config knows about krb5])
if $PKG_CONFIG --exists krb5; then
AC_MSG_RESULT([yes])
use_pkgconfig_for_krb5=yes
else
AC_MSG_RESULT([no])
fi
fi
if test "x$use_pkgconfig_for_krb5" = "xyes"; then
# Use pkg-config to get compiler and linker flags
AC_MSG_CHECKING([for krb5 cflags via pkg-config])
KRB5_CFLAGS="`$PKG_CONFIG --cflags krb5`"
AC_MSG_RESULT([$KRB5_CFLAGS])
AC_MSG_CHECKING([for krb5 libs via pkg-config])
KRB5_LIBS="`$PKG_CONFIG --libs krb5`"
AC_MSG_RESULT([$KRB5_LIBS])
ENABLE_LIBLINUXKRB5=true
else
# Fallback: try krb5-config
AC_PATH_TOOL([KRB5CONF], [krb5-config], [no])
if test "x$KRB5CONF" != "xno"; then
# Use krb5-config to get compiler and linker flags
AC_MSG_CHECKING([for krb5 cflags via krb5-config])
KRB5_CFLAGS="`$KRB5CONF --cflags`"
AC_MSG_RESULT([$KRB5_CFLAGS])
AC_MSG_CHECKING([for krb5 libs via krb5-config])
KRB5_LIBS="`$KRB5CONF --libs`"
AC_MSG_RESULT([$KRB5_LIBS])
ENABLE_LIBLINUXKRB5=true
else
# Final fallback: try manual detection in system locations
AC_CHECK_HEADERS([krb5.h], [
AC_CHECK_LIB([krb5], [krb5_init_context], [
KRB5_CFLAGS=""
KRB5_LIBS="-lkrb5"
# Check for com_err header and library which are often required
AC_CHECK_HEADERS([com_err.h], [
AC_CHECK_LIB([com_err], [com_err], [
KRB5_LIBS="$KRB5_LIBS -lcom_err"
])
])
ENABLE_LIBLINUXKRB5=true
], [ENABLE_LIBLINUXKRB5=false])
], [ENABLE_LIBLINUXKRB5=false])
fi
fi
fi
AC_SUBST(KRB5_CFLAGS)
AC_SUBST(KRB5_LIBS)
AC_SUBST(ENABLE_LIBLINUXKRB5)
])

View File

@ -31,6 +31,7 @@ m4_include([lib-ffi.m4])
m4_include([lib-fontconfig.m4])
m4_include([lib-freetype.m4])
m4_include([lib-hsdis.m4])
m4_include([lib-krb5.m4])
m4_include([lib-std.m4])
m4_include([lib-x11.m4])
@ -81,6 +82,13 @@ AC_DEFUN_ONCE([LIB_DETERMINE_DEPENDENCIES],
NEEDS_LIB_ALSA=false
fi
# Check if krb5 is needed
if test "x$OPENJDK_TARGET_OS" = xlinux -o "x$OPENJDK_TARGET_OS" = xmacosx; then
NEEDS_LIB_KRB5=true
else
NEEDS_LIB_KRB5=false
fi
# Check if ffi is needed
if HOTSPOT_CHECK_JVM_VARIANT(zero) || test "x$ENABLE_FALLBACK_LINKER" = "xtrue"; then
NEEDS_LIB_FFI=true
@ -117,6 +125,7 @@ AC_DEFUN_ONCE([LIB_SETUP_LIBRARIES],
LIB_SETUP_FONTCONFIG
LIB_SETUP_FREETYPE
LIB_SETUP_HSDIS
LIB_SETUP_KRB5
LIB_SETUP_LIBFFI
LIB_SETUP_MISC_LIBS
LIB_SETUP_X11

View File

@ -435,6 +435,9 @@ FONTCONFIG_CFLAGS := @FONTCONFIG_CFLAGS@
CUPS_CFLAGS := @CUPS_CFLAGS@
ALSA_LIBS := @ALSA_LIBS@
ALSA_CFLAGS := @ALSA_CFLAGS@
KRB5_LIBS := @KRB5_LIBS@
KRB5_CFLAGS := @KRB5_CFLAGS@
ENABLE_LIBLINUXKRB5 := @ENABLE_LIBLINUXKRB5@
LIBFFI_LIBS := @LIBFFI_LIBS@
LIBFFI_CFLAGS := @LIBFFI_CFLAGS@
ENABLE_LIBFFI_BUNDLING := @ENABLE_LIBFFI_BUNDLING@

View File

@ -86,6 +86,7 @@ ifneq ($(BUILD_CRYPTO), false)
NAME := osxkrb5, \
OPTIMIZATION := LOW, \
EXTRA_HEADER_DIRS := java.base:libjava, \
EXTRA_SRC := $(TOPDIR)/src/java.security.jgss/share/native/libkrb5shared, \
DISABLED_WARNINGS_clang_nativeccache.c := deprecated-declarations, \
LIBS_macosx := \
-framework Cocoa \
@ -95,6 +96,23 @@ ifneq ($(BUILD_CRYPTO), false)
TARGETS += $(BUILD_LIBOSXKRB5)
endif
ifeq ($(call isTargetOs, linux), true)
ifeq ($(ENABLE_LIBLINUXKRB5), true)
$(eval $(call SetupJdkLibrary, BUILD_LIBLINUXKRB5, \
NAME := linuxkrb5, \
OPTIMIZATION := LOW, \
DISABLED_WARNINGS_clang_nativeccache.c := deprecated-declarations, \
EXTRA_HEADER_DIRS := java.base:libjava, \
EXTRA_SRC := $(TOPDIR)/src/java.security.jgss/share/native/libkrb5shared, \
CFLAGS_linux := $(KRB5_CFLAGS) $(COM_ERR_CFLAGS), \
LIBS_linux := $(KRB5_LIBS) $(COM_ERR_LIBS), \
))
TARGETS += $(BUILD_LIBLINUXKRB5)
endif
endif
endif
################################################################################

View File

@ -326,9 +326,13 @@ public class Credentials {
throws KrbException, IOException {
if (ticketCache == null) {
// The default ticket cache on Windows and Mac is not a file.
// On Windows/MacOSX/Linux, use native system library calls to acquire
// credentials from any supported credential cache types on those
// platforms (in particular, the default ticket cache on Windows and
// Mac is not a file, so cannot use the pure Java code)
if (OperatingSystem.isWindows() ||
OperatingSystem.isMacOS()) {
OperatingSystem.isMacOS() ||
OperatingSystem.isLinux()) {
Credentials creds = acquireDefaultCreds();
if (creds == null) {
if (DEBUG != null) {
@ -411,7 +415,7 @@ public class Credentials {
// It assumes that the GSS call has
// the privilege to access the default cache file.
// This method is only called on Windows and Mac OS X, the native
// This method is only called on Windows, Mac OS X and Linux, the native
// acquireDefaultNativeCreds is also available on these platforms.
public static synchronized Credentials acquireDefaultCreds() {
Credentials result = null;
@ -528,6 +532,8 @@ public class Credentials {
static void ensureLoaded() {
if (OperatingSystem.isMacOS()) {
System.loadLibrary("osxkrb5");
} else if (OperatingSystem.isLinux()) {
System.loadLibrary("linuxkrb5");
} else {
System.loadLibrary("w2k_lsa_auth");
}

View File

@ -23,10 +23,28 @@
* questions.
*/
#import "sun_security_krb5_Credentials.h"
#import <Kerberos/Kerberos.h>
#import <string.h>
#import <time.h>
/*
* Unified Kerberos native credential cache implementation for Mac OS X and Linux.
* This implementation consolidates the previously separate platform-specific
* implementations while maintaining platform-specific library names.
*
* Platform-specific differences are handled via conditional compilation.
*/
#include "sun_security_krb5_Credentials.h"
#include <string.h>
#include <time.h>
#include <stdarg.h>
#ifdef MACOSX
// Mac OS X specific includes
#import <Kerberos/Kerberos.h>
#elif defined(LINUX)
// Linux specific includes
#include <krb5/krb5.h>
#include <arpa/inet.h>
#include <com_err.h>
#endif
#include "jni_util.h"
@ -72,7 +90,7 @@ static jobject BuildClientPrincipal(JNIEnv *env, krb5_context kcontext, krb5_pri
static jobject BuildEncryptionKey(JNIEnv *env, krb5_keyblock *cryptoKey);
static jobject BuildTicketFlags(JNIEnv *env, krb5_flags flags);
static jobject BuildKerberosTime(JNIEnv *env, krb5_timestamp kerbtime);
static jobject BuildAddressList(JNIEnv *env, krb5_address **kerbtime);
static jobject BuildAddressList(JNIEnv *env, krb5_address **addresses);
static void printiferr (errcode_t err, const char *format, ...);
@ -446,9 +464,6 @@ outer_cleanup:
return krbCreds;
}
#pragma mark -
jobject BuildTicket(JNIEnv *env, krb5_data *encodedTicket)
{
// To build a Ticket, we need to make a byte array out of the EncodedTicket.
@ -567,6 +582,10 @@ jobject BuildAddressList(JNIEnv *env, krb5_address **addresses) {
p++;
}
if (addressCount == 0) {
return NULL;
}
jobject address_list = (*env)->NewObjectArray(env, addressCount, hostAddressClass, NULL);
if (address_list == NULL) {
@ -607,8 +626,6 @@ jobject BuildAddressList(JNIEnv *env, krb5_address **addresses) {
return address_list;
}
#pragma mark - Utility methods -
static void printiferr (errcode_t err, const char *format, ...)
{
if (err) {

View File

@ -0,0 +1,212 @@
/*
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
/*
* @test
* @bug 8123456
* @summary Test JAAS access to in-memory credential caches
* @library /test/lib ../auto
* @requires os.family != "windows"
* @compile -XDignore.symbol.file
* --add-exports java.security.jgss/sun.security.krb5=ALL-UNNAMED
* --add-exports java.security.jgss/sun.security.krb5.internal=ALL-UNNAMED
* --add-exports java.security.jgss/sun.security.krb5.internal.ccache=ALL-UNNAMED
* --add-exports java.security.jgss/sun.security.krb5.internal.crypto=ALL-UNNAMED
* --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
* NativeCacheTest.java
* @run shell build.sh
* @run main jdk.test.lib.FileInstaller TestHosts TestHosts
* @run main/othervm
* --add-exports java.security.jgss/sun.security.krb5=ALL-UNNAMED
* --add-exports java.security.jgss/sun.security.krb5.internal=ALL-UNNAMED
* --add-exports java.security.jgss/sun.security.krb5.internal.ccache=ALL-UNNAMED
* --add-exports java.security.jgss/sun.security.krb5.internal.crypto=ALL-UNNAMED
* --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
* --add-opens java.security.jgss/sun.security.krb5=ALL-UNNAMED
* --add-opens java.security.jgss/sun.security.krb5.internal=ALL-UNNAMED
* --add-opens java.base/sun.security.util=ALL-UNNAMED
* --enable-native-access=ALL-UNNAMED
* -Djava.library.path=${test.src}:.:${test.jdk}/lib
* -Djdk.net.hosts.file=TestHosts
* NativeCacheTest
*/
import sun.security.krb5.Credentials;
import javax.security.auth.login.LoginContext;
import javax.security.auth.Subject;
import javax.security.auth.kerberos.KerberosTicket;
import java.io.File;
/**
* Test JAAS access to in-memory credential caches.
*
* This test validates that JAAS can access MEMORY: credential caches
* on Linux through the native enhancement, using real TGTs from OneKDC.
*/
public class NativeCacheTest {
public static void main(String[] args) throws Exception {
try {
// Create real TGT using OneKDC (in isolated cache)
createRealTGTWithOneKDC();
// Copy real TGT to in-memory cache using JNI
String inMemoryCacheName = copyTGTToInMemoryCache();
// Test JAAS access to in-memory cache
testJAASAccessToInMemoryCache(inMemoryCacheName);
} catch (Exception e) {
System.err.println("Test failed: " + e.getMessage());
throw e;
}
}
/**
* Use OneKDC to create a real TGT via JAAS LoginModule
*/
private static void createRealTGTWithOneKDC() throws Exception {
System.out.println("Creating TGT via OneKDC");
OneKDC kdc = new OneKDC(null);
kdc.writeJAASConf();
// Force JAAS to save credentials to file cache for copying
System.setProperty("test.kdc.save.ccache", "onekdc_cache.ccache");
try {
// Authenticate using JAAS LoginModule
LoginContext lc = new LoginContext("com.sun.security.jgss.krb5.initiate",
new OneKDC.CallbackForClient());
lc.login();
// Verify authentication
Subject subject = lc.getSubject();
KerberosTicket ticket = subject.getPrivateCredentials(KerberosTicket.class).iterator().next();
System.out.println("JAAS authentication successful");
System.out.println("TGT: " + ticket.getClient() + " -> " + ticket.getServer());
} catch (Exception e) {
System.out.println("JAAS authentication failed: " + e.getMessage());
}
}
/**
* Copy the real TGT to memory cache using JNI
*/
private static String copyTGTToInMemoryCache() throws Exception {
System.out.println("Copying credentials to memory cache");
String memoryCacheName = "MEMORY:test_" + System.currentTimeMillis();
// Create the memory cache
if (!NativeCredentialCacheHelper.createInMemoryCache(memoryCacheName)) {
throw new RuntimeException("Failed to create memory cache");
}
System.out.println("Created memory cache: " + memoryCacheName);
// Try to copy credentials from saved cache file
boolean copied = false;
File savedCache = new File("onekdc_cache.ccache");
if (savedCache.exists()) {
System.out.println("Copying from: " + savedCache.getAbsolutePath());
copied = NativeCredentialCacheHelper.copyCredentialsToInMemoryCache(
memoryCacheName,
"FILE:" + savedCache.getAbsolutePath()
);
}
// Fallback to default cache if file cache doesn't exist
if (!copied) {
copied = NativeCredentialCacheHelper.copyCredentialsToInMemoryCache(memoryCacheName, null);
}
if (copied) {
System.out.println("Credentials copied to memory cache");
} else {
System.out.println("No credentials found to copy");
}
// Set as default cache for JAAS testing
NativeCredentialCacheHelper.setDefaultCache(memoryCacheName);
System.setProperty("KRB5CCNAME", memoryCacheName);
return memoryCacheName;
}
/**
* Test JAAS access to the memory cache (the main test)
*/
private static void testJAASAccessToInMemoryCache(String inMemoryCacheName) throws Exception {
System.out.println("Testing JAAS access to an in-memory cache");
// Verify KRB5CCNAME points to our memory cache
String krb5ccname = System.getProperty("KRB5CCNAME");
System.out.println("KRB5CCNAME is set to: " + krb5ccname);
System.out.println("Expected in-memory cache: " + inMemoryCacheName);
if (!inMemoryCacheName.equals(krb5ccname)) {
System.out.println("ERROR: KRB5CCNAME does not point to our in-memory cache");
throw new RuntimeException("test setup error - KRB5CCNAME not pointing to in-memory cache");
}
try {
Credentials creds = Credentials.acquireDefaultCreds();
if (creds != null) {
String client = creds.getClient().toString();
String server = creds.getServer().toString();
System.out.println("SUCCESS: JAAS retrieved credentials from in-memory cache");
System.out.println("Client: " + client);
System.out.println("Server: " + server);
// Verify these are the OneKDC test credentials we copied
if (client.contains("dummy") && server.contains("RABBIT.HOLE")) {
System.out.println("SUCCESS: Retrieved correct OneKDC test credentials from in-memory cache");
if (server.contains("krbtgt")) {
System.out.println("Retrieved TGT as expected");
}
} else {
System.out.println("ERROR: JAAS retrieved wrong credentials from in-memory cache");
System.out.println("Expected: dummy@RABBIT.HOLE -> krbtgt/RABBIT.HOLE@RABBIT.HOLE");
System.out.println("Found: " + client + " -> " + server);
throw new RuntimeException("in-memory cache test failed - wrong credentials retrieved");
}
} else {
System.out.println("JAAS accessed in-memory cache but found no credentials");
}
} catch (Exception e) {
System.out.println("JAAS error: " + e.getMessage());
}
}
}

View File

@ -0,0 +1,270 @@
/*
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
#include <jni.h>
#include <krb5/krb5.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h> // for access()
#include <limits.h> // for realpath()
#include "NativeCredentialCacheHelper.h"
// Global krb5 context - initialized once
static krb5_context g_context = NULL;
// Initialize krb5 context with OneKDC config if available
static krb5_error_code ensure_context() {
// Always check for OneKDC config file and set environment
if (access("localkdc-krb5.conf", F_OK) != -1) {
char *current_path = realpath("localkdc-krb5.conf", NULL);
if (current_path != NULL) {
setenv("KRB5_CONFIG", current_path, 1);
free(current_path);
// If context already exists, reinitialize it to pick up new config
if (g_context != NULL) {
krb5_free_context(g_context);
g_context = NULL;
}
}
}
if (g_context == NULL) {
return krb5_init_context(&g_context);
}
return 0;
}
// Utility function to convert Java string to C string
static char* jstring_to_cstring(JNIEnv *env, jstring jstr) {
if (jstr == NULL) return NULL;
const char *utf_chars = (*env)->GetStringUTFChars(env, jstr, NULL);
if (utf_chars == NULL) return NULL;
char *result = strdup(utf_chars);
(*env)->ReleaseStringUTFChars(env, jstr, utf_chars);
return result;
}
// Print error message for krb5 errors
static void print_krb5_error(const char *operation, krb5_error_code code) {
if (code != 0) {
printf("krb5 error in %s: %s\n", operation, error_message(code));
}
}
/**
* Create an in-memory credential cache using native krb5 API.
* Creates a MEMORY: type cache that can be used for testing JAAS access
* to in-memory credential stores.
*/
JNIEXPORT jboolean JNICALL Java_NativeCredentialCacheHelper_createInMemoryCache
(JNIEnv *env, jclass cls, jstring cacheName)
{
krb5_error_code ret;
krb5_ccache ccache;
char *cache_name = NULL;
ret = ensure_context();
if (ret) {
print_krb5_error("ensure_context", ret);
return JNI_FALSE;
}
cache_name = jstring_to_cstring(env, cacheName);
if (cache_name == NULL) {
return JNI_FALSE;
}
// Resolve the memory cache
ret = krb5_cc_resolve(g_context, cache_name, &ccache);
if (ret) {
print_krb5_error("krb5_cc_resolve", ret);
free(cache_name);
return JNI_FALSE;
}
printf("Created memory cache: %s\n", cache_name);
krb5_cc_close(g_context, ccache);
free(cache_name);
return JNI_TRUE;
}
/**
* Set the default credential cache to the specified credential cache.
* This makes the credential cache the target for credential lookups.
*/
JNIEXPORT jboolean JNICALL Java_NativeCredentialCacheHelper_setDefaultCache
(JNIEnv *env, jclass cls, jstring cacheName)
{
char *cache_name = jstring_to_cstring(env, cacheName);
if (cache_name == NULL) {
return JNI_FALSE;
}
// Set KRB5CCNAME environment variable
if (setenv("KRB5CCNAME", cache_name, 1) != 0) {
free(cache_name);
return JNI_FALSE;
}
printf("Set default cache to: %s\n", cache_name);
free(cache_name);
return JNI_TRUE;
}
/**
* Copy real Kerberos credentials from a source cache to a memory cache.
* This preserves the proper credential format so JAAS can access them.
* Used to move OneKDC-generated TGTs to in-memory caches for testing.
*/
JNIEXPORT jboolean JNICALL Java_NativeCredentialCacheHelper_copyCredentialsToInMemoryCache
(JNIEnv *env, jclass cls, jstring inMemoryCacheName, jstring sourceCacheName)
{
krb5_error_code ret;
krb5_ccache source_ccache = NULL;
krb5_ccache in_memory_ccache = NULL;
krb5_cc_cursor cursor;
krb5_creds creds;
char *in_memory_cache_name = NULL;
char *source_cache_name = NULL;
int copied_count = 0;
ret = ensure_context();
if (ret) {
print_krb5_error("ensure_context", ret);
return JNI_FALSE;
}
// Convert Java strings
in_memory_cache_name = jstring_to_cstring(env, inMemoryCacheName);
if (sourceCacheName != NULL) {
source_cache_name = jstring_to_cstring(env, sourceCacheName);
}
if (!in_memory_cache_name) {
printf("Failed to get in-memory cache name\n");
goto cleanup;
}
printf("Copying credentials to in-memory cache: %s from source: %s\n",
in_memory_cache_name,
source_cache_name ? source_cache_name : "default cache"
);
// Open source cache (default if sourceCacheName is null)
if (source_cache_name) {
ret = krb5_cc_resolve(g_context, source_cache_name, &source_ccache);
if (ret) {
print_krb5_error("krb5_cc_resolve (source)", ret);
goto cleanup;
}
} else {
ret = krb5_cc_default(g_context, &source_ccache);
if (ret) {
print_krb5_error("krb5_cc_default", ret);
goto cleanup;
}
}
// Open/resolve memory cache
ret = krb5_cc_resolve(g_context, in_memory_cache_name, &in_memory_ccache);
if (ret) {
print_krb5_error("krb5_cc_resolve (in-memory)", ret);
goto cleanup;
}
// Get principal from source cache for initialization
krb5_principal principal = NULL;
ret = krb5_cc_get_principal(g_context, source_ccache, &principal);
if (ret) {
print_krb5_error("krb5_cc_get_principal", ret);
goto cleanup;
}
// Initialize in-memory cache with the principal
ret = krb5_cc_initialize(g_context, in_memory_ccache, principal);
if (ret) {
print_krb5_error("krb5_cc_initialize", ret);
krb5_free_principal(g_context, principal);
goto cleanup;
}
// Start credential cursor on source cache
ret = krb5_cc_start_seq_get(g_context, source_ccache, &cursor);
if (ret) {
print_krb5_error("krb5_cc_start_seq_get", ret);
krb5_free_principal(g_context, principal);
goto cleanup;
}
// Copy each credential from source to memory cache
while ((ret = krb5_cc_next_cred(g_context, source_ccache, &cursor, &creds)) == 0) {
ret = krb5_cc_store_cred(g_context, in_memory_ccache, &creds);
if (ret) {
print_krb5_error("krb5_cc_store_cred", ret);
krb5_free_cred_contents(g_context, &creds);
break;
}
printf("Copied in-memory credential: %s -> %s\n",
creds.client ? "client" : "unknown",
creds.server ? "server" : "unknown");
copied_count++;
krb5_free_cred_contents(g_context, &creds);
}
// End the cursor (expected to return KRB5_CC_END)
krb5_cc_end_seq_get(g_context, source_ccache, &cursor);
// Success if we copied at least one credential
if (copied_count > 0) {
printf("Successfully copied %d credentials to in-memory cache: %s\n",
copied_count, in_memory_cache_name);
ret = 0;
} else {
printf("No credentials found in source cache to copy to in-memory cache\n");
ret = KRB5_CC_NOTFOUND;
}
krb5_free_principal(g_context, principal);
cleanup:
if (source_ccache) krb5_cc_close(g_context, source_ccache);
if (in_memory_ccache) krb5_cc_close(g_context, in_memory_ccache);
if (in_memory_cache_name) free(in_memory_cache_name);
if (source_cache_name) free(source_cache_name);
return (ret == 0) ? JNI_TRUE : JNI_FALSE;
}

View File

@ -0,0 +1,65 @@
/*
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
/**
* JNI wrapper for native Kerberos credential cache operations.
* Provides native methods to create MEMORY: credential caches and copy
* real Kerberos credentials to them for testing JAAS access.
*/
public class NativeCredentialCacheHelper {
static {
try {
System.loadLibrary("nativecredentialcachehelper");
} catch (UnsatisfiedLinkError e) {
System.err.println("Failed to load nativecredentialcachehelper library: " + e.getMessage());
throw e;
}
}
/**
* Create an in-memory credential cache using native krb5 calls.
* @param cacheName The name for the in-memory cache (e.g., "MEMORY:test123")
* @return true if cache was created successfully, false otherwise
*/
public static native boolean createInMemoryCache(String cacheName);
/**
* Copy real credentials from a source cache to the in-memory cache.
* This preserves the proper Kerberos credential format for JAAS access.
* @param inMemoryCacheName The target in-memory cache name (e.g., "MEMORY:test123")
* @param sourceCacheName The source cache name (null for default cache)
* @return true if credentials were copied successfully, false otherwise
*/
public static native boolean copyCredentialsToInMemoryCache(String inMemoryCacheName, String sourceCacheName);
/**
* Set the default credential cache to the specified credential cache.
* @param cacheName The credential cache name to set as default
* @return true if set successfully, false otherwise
*/
public static native boolean setDefaultCache(String cacheName);
}

View File

@ -0,0 +1,3 @@
127.0.0.1 localhost
127.0.0.1 host.rabbit.hole
127.0.0.1 kdc.rabbit.hole

View File

@ -0,0 +1,90 @@
#!/bin/bash
# Build script for NativeCacheTest - compiles Java classes and native library
set -e
# Use jtreg environment variables when available, fallback to manual calculation
if [ -n "$TESTJAVA" ]; then
# Running under jtreg
BUILT_JDK="$TESTJAVA"
JDK_ROOT="$(dirname $(dirname $TESTROOT))"
LIB_DIR="$JDK_ROOT/test/lib"
TEST_DIR="$TESTSRC"
else
# Running manually
TEST_DIR=$(pwd)
JDK_ROOT="$(cd ../../../../../../ && pwd)"
LIB_DIR="$JDK_ROOT/test/lib"
BUILT_JDK="$JDK_ROOT/build/linux-x86_64-server-release/jdk"
fi
export JAVA_HOME="$BUILT_JDK"
export PATH="$BUILT_JDK/bin:$PATH"
# Module exports required for Kerberos internal APIs
if [ -n "$TESTCLASSPATH" ]; then
# Use jtreg's prepared classpath
JAVA_CP="$TESTCLASSPATH"
else
# Manual execution classpath
JAVA_CP="$LIB_DIR:../auto:."
fi
MODULE_EXPORTS="--add-exports java.security.jgss/sun.security.krb5=ALL-UNNAMED \
--add-exports java.security.jgss/sun.security.krb5.internal=ALL-UNNAMED \
--add-exports java.security.jgss/sun.security.krb5.internal.ccache=ALL-UNNAMED \
--add-exports java.security.jgss/sun.security.krb5.internal.crypto=ALL-UNNAMED \
--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"
cd "$TEST_DIR"
# For jtreg, classes are already compiled by the harness
# For manual execution, compile what's needed
if [ -z "$TESTJAVA" ]; then
# Manual execution - compile everything
# Compile test library classes
cd "$LIB_DIR"
javac -cp . --add-exports java.base/jdk.internal.misc=ALL-UNNAMED jdk/test/lib/Platform.java
# Compile OneKDC and test infrastructure
cd "$TEST_DIR/../auto"
javac -cp "$LIB_DIR:." $MODULE_EXPORTS -XDignore.symbol.file \
KDC.java OneKDC.java Context.java
cd "$TEST_DIR"
# Compile test classes
javac -cp "$JAVA_CP" $MODULE_EXPORTS -XDignore.symbol.file \
NativeCredentialCacheHelper.java NativeCacheTest.java
fi
# Generate JNI header (always needed for native compilation)
cd "$TEST_DIR"
if [ -n "$TESTCLASSPATH" ]; then
javac -cp "$TESTCLASSPATH" -h . NativeCredentialCacheHelper.java
else
javac -cp . -h . NativeCredentialCacheHelper.java
fi
# get the OS
OS=$(uname -s | tr 'A-Z' 'a-z')
if [ "$OS" == "linux" ]; then
COMPILER=gcc
LIBEXT=so
elif [ "$OS" == "darwin" ]; then
COMPILER=clang
LIBEXT=dylib
else
echo "Unsupported os: ${OS}"
exit 1
fi
# Compile native library (work from test source directory)
cd "$TEST_DIR"
${COMPILER} -shared -fPIC -I"$JAVA_HOME/include" -I"$JAVA_HOME/include/${OS}" -lkrb5 \
-o libnativecredentialcachehelper.${LIBEXT} NativeCredentialCacheHelper.c
echo "Build completed successfully"