mirror of
https://github.com/openjdk/jdk.git
synced 2026-04-04 12:08:36 +00:00
Add support for native Kerberos credential acquisition on Linux
This commit is contained in:
parent
583ff202b1
commit
c0204db9bd
113
make/autoconf/lib-krb5.m4
Normal file
113
make/autoconf/lib-krb5.m4
Normal 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)
|
||||
])
|
||||
@ -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
|
||||
|
||||
@ -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@
|
||||
|
||||
@ -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
|
||||
|
||||
################################################################################
|
||||
|
||||
@ -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");
|
||||
}
|
||||
|
||||
@ -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) {
|
||||
212
test/jdk/sun/security/krb5/native/NativeCacheTest.java
Normal file
212
test/jdk/sun/security/krb5/native/NativeCacheTest.java
Normal 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());
|
||||
}
|
||||
}
|
||||
}
|
||||
270
test/jdk/sun/security/krb5/native/NativeCredentialCacheHelper.c
Normal file
270
test/jdk/sun/security/krb5/native/NativeCredentialCacheHelper.c
Normal 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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
|
||||
|
||||
3
test/jdk/sun/security/krb5/native/TestHosts
Normal file
3
test/jdk/sun/security/krb5/native/TestHosts
Normal file
@ -0,0 +1,3 @@
|
||||
127.0.0.1 localhost
|
||||
127.0.0.1 host.rabbit.hole
|
||||
127.0.0.1 kdc.rabbit.hole
|
||||
90
test/jdk/sun/security/krb5/native/build.sh
Executable file
90
test/jdk/sun/security/krb5/native/build.sh
Executable 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"
|
||||
Loading…
x
Reference in New Issue
Block a user