diff --git a/make/autoconf/lib-krb5.m4 b/make/autoconf/lib-krb5.m4 new file mode 100644 index 0000000000000..93c6bc35108e5 --- /dev/null +++ b/make/autoconf/lib-krb5.m4 @@ -0,0 +1,187 @@ +# +# 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], + [specify prefix directory for the krb5 package on Linux, or use "yes/no/auto" (default=auto)])]) + AC_ARG_WITH(krb5-include, [AS_HELP_STRING([--with-krb5-include], + [specify directory for the krb5 include files on Linux])]) + AC_ARG_WITH(krb5-lib, [AS_HELP_STRING([--with-krb5-lib], + [specify directory for the krb5 library on Linux])]) + + KRB5_CFLAGS= + KRB5_LIBS= + ENABLE_LIBKRB5_LINUX=false + + if test "x$OPENJDK_TARGET_OS" != "xlinux" && test "x${with_krb5}" = "xyes"; then + AC_MSG_ERROR([krb5 support is only available on Linux]) + elif test "x${with_krb5}" = "xno"; then + AC_MSG_CHECKING([for krb5]) + AC_MSG_RESULT([disabled]) + else + KRB5_FOUND=no + + if test "x${with_krb5}" != "x" && test "x${with_krb5}" != "xyes" && test "x${with_krb5}" != "xauto"; then + # if a path was provided, use it + if test "x${with_krb5}" != "x"; then + AC_MSG_CHECKING([for krb5]) + KRB5_LIBS="-L${with_krb5}/lib -lkrb5 -lcom_err" + KRB5_CFLAGS="-I${with_krb5}/include" + KRB5_FOUND=yes + AC_MSG_RESULT([${with_krb5}]) + fi + fi + + if test "x${with_krb5_include}" != "x"; then + AC_MSG_CHECKING([for krb5 includes]) + KRB5_CFLAGS="-I${with_krb5_include}" + KRB5_FOUND=yes + AC_MSG_RESULT([${with_krb5_include}]) + fi + + if test "x${with_krb5_lib}" != "x"; then + AC_MSG_CHECKING([for krb5 libs]) + KRB5_LIBS="-L${with_krb5_lib} -lkrb5 -lcom_err" + KRB5_FOUND=yes + AC_MSG_RESULT([${with_krb5_lib}]) + fi + + if test "x$KRB5_FOUND" = "xno"; then + if test "x$SYSROOT" != "x"; then + AC_MSG_CHECKING([for krb5 ($SYSROOT)]) + # Cross-compilation with SYSROOT - look at known locations in SYSROOT. + KRB5_LIB_PATH="" + COM_ERR_LIB_PATH="" + + # Look for libkrb5/libcom_err + if test -f "$SYSROOT/usr/lib64/libkrb5.so" && test "x$OPENJDK_TARGET_CPU_BITS" = x64; then + KRB5_LIB_PATH="$SYSROOT/usr/lib64" + elif test -f "$SYSROOT/usr/lib/libkrb5.so"; then + KRB5_LIB_PATH="$SYSROOT/usr/lib" + elif test -f "$SYSROOT/usr/lib/$OPENJDK_TARGET_CPU-$OPENJDK_TARGET_OS-$OPENJDK_TARGET_ABI/libkrb5.so"; then + KRB5_LIB_PATH="$SYSROOT/usr/lib/$OPENJDK_TARGET_CPU-$OPENJDK_TARGET_OS-$OPENJDK_TARGET_ABI" + elif test -f "$SYSROOT/usr/lib/$OPENJDK_TARGET_CPU_AUTOCONF-$OPENJDK_TARGET_OS-$OPENJDK_TARGET_ABI/libkrb5.so"; then + KRB5_LIB_PATH="$SYSROOT/usr/lib/$OPENJDK_TARGET_CPU_AUTOCONF-$OPENJDK_TARGET_OS-$OPENJDK_TARGET_ABI" + fi + + if test -f "$KRB5_LIB_PATH/libcom_err.so"; then + COM_ERR_LIB_PATH="$KRB5_LIB_PATH" + elif test -f "$SYSROOT/usr/lib64/libcom_err.so" && test "x$OPENJDK_TARGET_CPU_BITS" = x64; then + COM_ERR_LIB_PATH="$SYSROOT/usr/lib64" + elif test -f "$SYSROOT/usr/lib/libcom_err.so"; then + COM_ERR_LIB_PATH="$SYSROOT/usr/lib" + elif test -f "$SYSROOT/usr/lib/$OPENJDK_TARGET_CPU-$OPENJDK_TARGET_OS-$OPENJDK_TARGET_ABI/libcom_err.so"; then + COM_ERR_LIB_PATH="$SYSROOT/usr/lib/$OPENJDK_TARGET_CPU-$OPENJDK_TARGET_OS-$OPENJDK_TARGET_ABI" + elif test -f "$SYSROOT/usr/lib/$OPENJDK_TARGET_CPU_AUTOCONF-$OPENJDK_TARGET_OS-$OPENJDK_TARGET_ABI/libcom_err.so"; then + COM_ERR_LIB_PATH="$SYSROOT/usr/lib/$OPENJDK_TARGET_CPU_AUTOCONF-$OPENJDK_TARGET_OS-$OPENJDK_TARGET_ABI" + fi + + # Check for matching include files + KRB5_INCLUDE_PATH="" + COM_ERR_INCLUDE_PATH="" + + if test -f "$SYSROOT/usr/include/krb5/krb5.h"; then + KRB5_INCLUDE_PATH="$SYSROOT/usr/include" + fi + + if test -f "$SYSROOT/usr/include/com_err.h"; then + COM_ERR_INCLUDE_PATH="$SYSROOT/usr/include" + fi + + # Check everything was found and merge paths + if test "x$KRB5_LIB_PATH" != "x" && test "x$COM_ERR_LIB_PATH" != "x" && \ + test "x$KRB5_INCLUDE_PATH" != "x" && test "x$COM_ERR_INCLUDE_PATH" != "x"; then + KRB5_LIBS="-L$KRB5_LIB_PATH -lkrb5" + if test "x$COM_ERR_LIB_PATH" != "x" && test "x$COM_ERR_LIB_PATH" != "x$KRB5_LIB_PATH"; then + KRB5_LIBS="$KRB5_LIBS -L$COM_ERR_LIB_PATH" + fi + KRB5_LIBS="$KRB5_LIBS -lcom_err" + + KRB5_CFLAGS="-I$KRB5_INCLUDE_PATH" + if test "x$COM_ERR_INCLUDE_PATH" != "x" && test "x$COM_ERR_INCLUDE_PATH" != "x$KRB5_INCLUDE_PATH"; then + KRB5_CFLAGS="$KRB5_CFLAGS -I$COM_ERR_INCLUDE_PATH" + fi + + KRB5_FOUND=yes + fi + AC_MSG_RESULT([$KRB5_FOUND]) + else + if test "x$PKG_CONFIG" != "x" ; then + PKG_CHECK_MODULES(KRB5, krb5, [KRB5_FOUND=yes], [KRB5_FOUND=no]) + if test "x$KRB5_FOUND" = "xyes" ; then + AC_MSG_CHECKING([for krb5]) + AC_MSG_RESULT([yes (using pkg-config)]) + fi + fi + + if test "x$KRB5_FOUND" = "xno"; then + UTIL_LOOKUP_PROGS(KRB5CONF, krb5-config) + if test "x$KRB5CONF" != "x"; then + AC_MSG_CHECKING([for krb5 using krb5-config]) + KRB5_CFLAGS="`$KRB5CONF --cflags`" + KRB5_LIBS="`$KRB5CONF --libs`" + KRB5_FOUND=yes + AC_MSG_RESULT([$KRB5_FOUND]) + fi + fi + fi + fi + + # No sysconfig/pkg-config/krb5-config, so auto-detect + if test "x$KRB5_FOUND" = "xno"; then + AC_CHECK_HEADERS([krb5.h], [ + AC_CHECK_HEADERS([com_err.h], [ + AC_CHECK_LIB([krb5], [krb5_init_context], [ + KRB5_CFLAGS="" + KRB5_LIBS="-lkrb5" + AC_CHECK_LIB([com_err], [com_err], [ + KRB5_LIBS="$KRB5_LIBS -lcom_err" + ]) + KRB5_FOUND=yes + ]) + ]) + ]) + fi + + if test "x$KRB5_FOUND" = "xno"; then + if test "x${with_krb5}" = "xyes"; then + AC_MSG_ERROR([krb5 was required but could not be found]) + fi + KRB5_CFLAGS= + KRB5_LIBS= + ENABLE_LIBKRB5_LINUX=false + else + ENABLE_LIBKRB5_LINUX=true + fi + fi + + AC_SUBST(KRB5_CFLAGS) + AC_SUBST(KRB5_LIBS) + AC_SUBST(ENABLE_LIBKRB5_LINUX) +]) diff --git a/make/autoconf/libraries.m4 b/make/autoconf/libraries.m4 index 8dc3d55ed0c83..063a7c95b8f13 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]) @@ -117,6 +118,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 0b336721d654d..9e3f7dc7e1f0d 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_LIBKRB5_LINUX := @ENABLE_LIBKRB5_LINUX@ 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 4b05100e6a603..8024f1641d118 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_LIBKRB5_LINUX), true) + $(eval $(call SetupJdkLibrary, BUILD_LIBKRB5_LINUX, \ + 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_LIBKRB5_LINUX) + endif + endif + endif ################################################################################ diff --git a/make/test/JtregNativeJdk.gmk b/make/test/JtregNativeJdk.gmk index a204467a77b47..dece0fb4954f2 100644 --- a/make/test/JtregNativeJdk.gmk +++ b/make/test/JtregNativeJdk.gmk @@ -110,6 +110,25 @@ ifeq ($(call isTargetOs, linux), true) BUILD_JDK_JTREG_LIBRARIES_LDFLAGS_libCreationTimeHelper := -ldl endif +# Kerberos native test library configuration +ifeq ($(call isTargetOs, linux), true) + # Linux: only build if krb5 is enabled and working + ifeq ($(ENABLE_LIBKRB5_LINUX), true) + BUILD_JDK_JTREG_LIBRARIES_LDFLAGS_libNativeCredentialCacheHelper := $(KRB5_LIBS) + BUILD_JDK_JTREG_LIBRARIES_CFLAGS_libNativeCredentialCacheHelper := $(KRB5_CFLAGS) + else + # Exclude the Kerberos test library if krb5 is not available on Linux + BUILD_JDK_JTREG_EXCLUDE += libNativeCredentialCacheHelper.c + endif +else ifeq ($(call isTargetOs, macosx), true) + # macOS: build with system krb5 and disable deprecation warnings + BUILD_JDK_JTREG_LIBRARIES_LDFLAGS_libNativeCredentialCacheHelper := $(KRB5_LIBS) + BUILD_JDK_JTREG_LIBRARIES_CFLAGS_libNativeCredentialCacheHelper := $(KRB5_CFLAGS) -Wno-deprecated-declarations +else + # Other platforms: exclude the library + BUILD_JDK_JTREG_EXCLUDE += libNativeCredentialCacheHelper.c +endif + ifeq ($(ASAN_ENABLED), true) # Any executable which launches the JVM and uses a custom launcher needs to explicitly link in the # default ASan options. 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 f9076a9b0dd6c..a73a52642ae18 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 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2000, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2000, 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 @@ -326,9 +326,12 @@ public static Credentials acquireTGTFromCache(PrincipalName princ, throws KrbException, IOException { if (ticketCache == null) { - // The default ticket cache on Windows and Mac is not a file. - if (OperatingSystem.isWindows() || - OperatingSystem.isMacOS()) { + // 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 + // MacOSX is not a file, so cannot use the pure Java code) + if (OperatingSystem.isWindows() || OperatingSystem.isMacOS() + || OperatingSystem.isLinux()) { Credentials creds = acquireDefaultCreds(); if (creds == null) { if (DEBUG != null) { @@ -411,7 +414,7 @@ public static Credentials acquireTGTFromCache(PrincipalName princ, // 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, MacOSX and Linux, the native // acquireDefaultNativeCreds is also available on these platforms. public static synchronized Credentials acquireDefaultCreds() { Credentials result = null; @@ -528,6 +531,8 @@ public static void printDebug(Credentials c) { 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 97% rename from src/java.security.jgss/macosx/native/libosxkrb5/nativeccache.c rename to src/java.security.jgss/share/native/libkrb5shared/nativeccache.c index 36e7a942ded61..819d3ddeb93f2 100644 --- a/src/java.security.jgss/macosx/native/libosxkrb5/nativeccache.c +++ b/src/java.security.jgss/share/native/libkrb5shared/nativeccache.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2011, 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 @@ -23,10 +23,24 @@ * questions. */ -#import "sun_security_krb5_Credentials.h" -#import -#import -#import +/* + * Unified Kerberos native credential cache implementation for MacOSX/Linux. + */ + +#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 +86,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 +460,6 @@ JNIEXPORT jobject JNICALL Java_sun_security_krb5_Credentials_acquireDefaultNativ 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 +578,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 +622,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 0000000000000..f4b9c9e31c74d --- /dev/null +++ b/test/jdk/sun/security/krb5/native/NativeCacheTest.java @@ -0,0 +1,170 @@ +/* + * 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 + * @summary Test JAAS access to in-memory credential caches + * @library /test/lib + * @requires os.family != "windows" + * @modules java.security.jgss/sun.security.krb5:+open + * java.security.jgss/sun.security.krb5.internal:+open + * java.security.jgss/sun.security.krb5.internal.ccache + * java.security.jgss/sun.security.krb5.internal.crypto + * java.security.jgss/sun.security.krb5.internal.ktab + * java.security.jgss/sun.security.jgss.krb5 + * java.base/sun.security.util:+open + * java.base/jdk.internal.misc + * @compile NativeCacheTest.java + * @run main jdk.test.lib.FileInstaller TestHosts TestHosts + * @run main/othervm/native --enable-native-access=ALL-UNNAMED -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 in-memory credential caches + * on Linux through the native enhancement, using TGTs from OneKDC. + */ +public class NativeCacheTest { + + public static void main(String[] args) throws Exception { + try { + // Create TGT using OneKDC (in file cache) + createTGTWithOneKDC(); + + // Copy TGT to in-memory cache using JNI + String inMemoryCacheName = copyTGTToInMemoryCache(); + + // Test JAAS access to in-memory cache + testJAASAccessToInMemoryCache(inMemoryCacheName); + + } catch (UnsatisfiedLinkError e) { + System.out.println("Kerberos native library not available - test skipped"); + return; + } catch (Exception e) { + System.err.println("Test failed: " + e.getMessage()); + throw e; + } + } + + /** + * Use OneKDC to create a TGT via the JAAS LoginModule + */ + private static void createTGTWithOneKDC() 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 the 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 TGT to an in-memory cache using JNI + */ + private static String copyTGTToInMemoryCache() throws Exception { + System.out.println("Creating in-memory cache and copying TGT from file cache"); + + // Check that the FILE: ccache exists + File fileCache = new File("onekdc_cache.ccache"); + if (!fileCache.exists()) { + throw new RuntimeException("File cache does not exist: " + fileCache.getAbsolutePath()); + } + + // Create MEMORY: ccache from the FILE: ccache + String memoryCacheName = "MEMORY:test_" + System.currentTimeMillis(); + if (!NativeCredentialCacheHelper.createInMemoryCacheFromFileCache( + memoryCacheName, "FILE:" + fileCache.getAbsolutePath())) { + throw new RuntimeException("Failed to create in-memory cache from file cache"); + } + + System.setProperty("KRB5CCNAME", memoryCacheName); + + System.out.println("Successfully created in-memory cache with credentials: " + memoryCacheName); + + return memoryCacheName; + } + + /** + * Test JAAS access to an in-memory cache + */ + private static void testJAASAccessToInMemoryCache(String inMemoryCacheName) throws Exception { + System.out.println("Testing JAAS access to an in-memory cache"); + + // Verify KRB5CCNAME points to our in-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"); + } + + Credentials creds = Credentials.acquireDefaultCreds(); + + if (creds != null) { + String client = creds.getClient().toString(); + String server = creds.getServer().toString(); + + // Verify these are the OneKDC test credentials + if (client.contains("dummy") && server.contains("RABBIT.HOLE") && server.contains("krbtgt")) { + System.out.println("SUCCESS: Retrieved correct OneKDC test credentials from in-memory cache"); + System.out.println("Client: " + client); + System.out.println("Server: " + server); + } 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("ERROR: JAAS accessed in-memory cache but found no credentials"); + throw new RuntimeException("in-memory cache test failed - no credentials retrieved"); + } + } +} diff --git a/test/jdk/sun/security/krb5/native/NativeCredentialCacheHelper.h b/test/jdk/sun/security/krb5/native/NativeCredentialCacheHelper.h new file mode 100644 index 0000000000000..23ad7fa78d3a9 --- /dev/null +++ b/test/jdk/sun/security/krb5/native/NativeCredentialCacheHelper.h @@ -0,0 +1,46 @@ +/* + * 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. + */ + + /* DO NOT EDIT THIS FILE - it is machine generated */ +#include +/* Header for class NativeCredentialCacheHelper */ + +#ifndef _Included_NativeCredentialCacheHelper +#define _Included_NativeCredentialCacheHelper +#ifdef __cplusplus +extern "C" { +#endif +/* + * Class: NativeCredentialCacheHelper + * Method: createInMemoryCacheFromFileCache + * Signature: (Ljava/lang/String;Ljava/lang/String;)Z + */ +JNIEXPORT jboolean JNICALL Java_NativeCredentialCacheHelper_createInMemoryCacheFromFileCache + (JNIEnv *, jclass, jstring, jstring); + +#ifdef __cplusplus +} +#endif +#endif 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 0000000000000..27cd3b8ddefd8 --- /dev/null +++ b/test/jdk/sun/security/krb5/native/NativeCredentialCacheHelper.java @@ -0,0 +1,49 @@ +/* + * 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 in-memory credential caches and copy + * Kerberos credentials from file credential caches 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; + } + } + + /** + * Creates an in-memory credential ccache, copies credentials from a file ccache, + * and sets KRB5CCNAME to the in-memory ccache. + * + * @param inMemoryCacheName The name for the MEMORY: ccache (e.g., "MEMORY:test123") + * @param fileCacheName The FILE: ccache name to copy from (e.g., "FILE:/path/to/cache") + * @return true if all operations succeeded, false if any operation failed + */ + public static native boolean createInMemoryCacheFromFileCache(String inMemoryCacheName, String fileCacheName); +} diff --git a/test/jdk/sun/security/krb5/native/TestHosts b/test/jdk/sun/security/krb5/native/TestHosts new file mode 100644 index 0000000000000..21fc88eacc56d --- /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/libNativeCredentialCacheHelper.c b/test/jdk/sun/security/krb5/native/libNativeCredentialCacheHelper.c new file mode 100644 index 0000000000000..a9604c2b70499 --- /dev/null +++ b/test/jdk/sun/security/krb5/native/libNativeCredentialCacheHelper.c @@ -0,0 +1,223 @@ +/* + * 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 +#include + +#include "NativeCredentialCacheHelper.h" + +// Global krb5 context +static krb5_context g_context = NULL; + +/** + * Initialize krb5 context with OneKDC config + */ +static krb5_error_code ensure_context() { + // Check if OneKDC config file exists or needs to be created + 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 + if (g_context != NULL) { + krb5_free_context(g_context); + g_context = NULL; + } + } + } + + if (g_context == NULL) { + return krb5_init_context(&g_context); + } + return 0; +} + +/** + * 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); + if (result == NULL) return NULL; + + (*env)->ReleaseStringUTFChars(env, jstr, utf_chars); + return result; +} + +/** + * Print error messages 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)); + } +} + +/** + * Creates an in-memory credential ccache, copies credentials from a file ccache, + * and sets KRB5CCNAME to the in-memory ccache. + */ +JNIEXPORT jboolean JNICALL Java_NativeCredentialCacheHelper_createInMemoryCacheFromFileCache + (JNIEnv *env, jclass cls, jstring inMemoryCacheName, jstring fileCacheName) +{ + krb5_error_code ret; + krb5_ccache file_ccache = NULL; + krb5_ccache in_memory_ccache = NULL; + krb5_cc_cursor cursor; + krb5_creds creds; + char *in_memory_cache_name = NULL; + char *file_cache_name = NULL; + int copied_count = 0; + + ret = ensure_context(); + if (ret) { + print_krb5_error("ensure_context", ret); + return JNI_FALSE; + } + + in_memory_cache_name = jstring_to_cstring(env, inMemoryCacheName); + file_cache_name = jstring_to_cstring(env, fileCacheName); + if (!in_memory_cache_name || !file_cache_name) { + printf("Failed to get file or in-memory cache names\n"); + goto cleanup; + } + + printf("Creating in-memory cache: %s from file cache: %s\n", + in_memory_cache_name, file_cache_name); + + // Resolve FILE: ccache + ret = krb5_cc_resolve(g_context, file_cache_name, &file_ccache); + if (ret) { + print_krb5_error("krb5_cc_resolve (file cache)", ret); + printf("ERROR: File cache does not exist or cannot be accessed: %s\n", file_cache_name); + goto cleanup; + } + + // Resolve in-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; + } + + printf("Created in-memory cache: %s\n", in_memory_cache_name); + + // Get principal from file cache for initialization + krb5_principal principal = NULL; + ret = krb5_cc_get_principal(g_context, file_ccache, &principal); + if (ret) { + print_krb5_error("krb5_cc_get_principal", ret); + printf("ERROR: Cannot get principal from file cache: %s\n", file_cache_name); + 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; + } + + // Copy credentials from file cache to in-memory cache + ret = krb5_cc_start_seq_get(g_context, file_ccache, &cursor); + if (ret) { + print_krb5_error("krb5_cc_start_seq_get", ret); + krb5_free_principal(g_context, principal); + goto cleanup; + } + + while ((ret = krb5_cc_next_cred(g_context, file_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; + } + + // Print the actual credential names + char *client_name = NULL; + char *server_name = NULL; + if (creds.client) { + krb5_unparse_name(g_context, creds.client, &client_name); + } + if (creds.server) { + krb5_unparse_name(g_context, creds.server, &server_name); + } + + printf("Copied credential: %s -> %s\n", + client_name ? client_name : "unknown", + server_name ? server_name : "unknown"); + + if (client_name) krb5_free_unparsed_name(g_context, client_name); + if (server_name) krb5_free_unparsed_name(g_context, server_name); + + 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, file_ccache, &cursor); + + if (copied_count == 0) { + printf("ERROR: No credentials found in file cache to copy: %s\n", file_cache_name); + ret = KRB5_CC_NOTFOUND; + krb5_free_principal(g_context, principal); + goto cleanup; + } + + printf("Successfully copied %d credentials to in-memory cache: %s\n", + copied_count, in_memory_cache_name); + + // Set KRB5CCNAME environment variable to point to in-memory cache + if (setenv("KRB5CCNAME", in_memory_cache_name, 1) != 0) { + printf("ERROR: Failed to set KRB5CCNAME environment variable\n"); + ret = -1; + krb5_free_principal(g_context, principal); + goto cleanup; + } + + printf("Set KRB5CCNAME to: %s\n", in_memory_cache_name); + ret = 0; + krb5_free_principal(g_context, principal); + +cleanup: + if (file_ccache) krb5_cc_close(g_context, file_ccache); + if (file_cache_name) free(file_cache_name); + + if (in_memory_ccache) krb5_cc_close(g_context, in_memory_ccache); + if (in_memory_cache_name) free(in_memory_cache_name); + + return (ret == 0) ? JNI_TRUE : JNI_FALSE; +}