diff --git a/make/autoconf/lib-krb5.m4 b/make/autoconf/lib-krb5.m4 new file mode 100644 index 00000000000..5dfd2869523 --- /dev/null +++ b/make/autoconf/lib-krb5.m4 @@ -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) +]) diff --git a/make/autoconf/libraries.m4 b/make/autoconf/libraries.m4 index 8dc3d55ed0c..926ea599462 100644 --- a/make/autoconf/libraries.m4 +++ b/make/autoconf/libraries.m4 @@ -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 diff --git a/make/autoconf/spec.gmk.template b/make/autoconf/spec.gmk.template index 0b336721d65..41ad307f9ca 100644 --- a/make/autoconf/spec.gmk.template +++ b/make/autoconf/spec.gmk.template @@ -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@ diff --git a/make/modules/java.security.jgss/Lib.gmk b/make/modules/java.security.jgss/Lib.gmk index 4b05100e6a6..57dc5d4af53 100644 --- a/make/modules/java.security.jgss/Lib.gmk +++ b/make/modules/java.security.jgss/Lib.gmk @@ -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 ################################################################################ diff --git a/src/java.security.jgss/share/classes/sun/security/krb5/Credentials.java b/src/java.security.jgss/share/classes/sun/security/krb5/Credentials.java index f9076a9b0dd..1348f970a8d 100644 --- a/src/java.security.jgss/share/classes/sun/security/krb5/Credentials.java +++ b/src/java.security.jgss/share/classes/sun/security/krb5/Credentials.java @@ -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"); } diff --git a/src/java.security.jgss/macosx/native/libosxkrb5/nativeccache.c b/src/java.security.jgss/share/native/libkrb5shared/nativeccache.c similarity index 96% rename from src/java.security.jgss/macosx/native/libosxkrb5/nativeccache.c rename to src/java.security.jgss/share/native/libkrb5shared/nativeccache.c index 36e7a942ded..d6c7cf4f8d2 100644 --- a/src/java.security.jgss/macosx/native/libosxkrb5/nativeccache.c +++ b/src/java.security.jgss/share/native/libkrb5shared/nativeccache.c @@ -23,10 +23,28 @@ * questions. */ -#import "sun_security_krb5_Credentials.h" -#import -#import -#import +/* + * 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 +#include +#include + +#ifdef MACOSX + // Mac OS X specific includes + #import +#elif defined(LINUX) + // Linux specific includes + #include + #include + #include +#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) { diff --git a/test/jdk/sun/security/krb5/native/NativeCacheTest.java b/test/jdk/sun/security/krb5/native/NativeCacheTest.java new file mode 100644 index 00000000000..f3df9a5aeea --- /dev/null +++ b/test/jdk/sun/security/krb5/native/NativeCacheTest.java @@ -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()); + } + } +} diff --git a/test/jdk/sun/security/krb5/native/NativeCredentialCacheHelper.c b/test/jdk/sun/security/krb5/native/NativeCredentialCacheHelper.c new file mode 100644 index 00000000000..35e17cebd98 --- /dev/null +++ b/test/jdk/sun/security/krb5/native/NativeCredentialCacheHelper.c @@ -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 +#include +#include +#include +#include +#include // for access() +#include // 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; +} + + + + diff --git a/test/jdk/sun/security/krb5/native/NativeCredentialCacheHelper.java b/test/jdk/sun/security/krb5/native/NativeCredentialCacheHelper.java new file mode 100644 index 00000000000..1dfaf4be24b --- /dev/null +++ b/test/jdk/sun/security/krb5/native/NativeCredentialCacheHelper.java @@ -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); +} + + + diff --git a/test/jdk/sun/security/krb5/native/TestHosts b/test/jdk/sun/security/krb5/native/TestHosts new file mode 100644 index 00000000000..21fc88eacc5 --- /dev/null +++ b/test/jdk/sun/security/krb5/native/TestHosts @@ -0,0 +1,3 @@ +127.0.0.1 localhost +127.0.0.1 host.rabbit.hole +127.0.0.1 kdc.rabbit.hole diff --git a/test/jdk/sun/security/krb5/native/build.sh b/test/jdk/sun/security/krb5/native/build.sh new file mode 100755 index 00000000000..d6ac9936874 --- /dev/null +++ b/test/jdk/sun/security/krb5/native/build.sh @@ -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"