From 1bc4dfbfca3b3992668bff0eeb7304a5c4727e0c Mon Sep 17 00:00:00 2001 From: weijun Date: Thu, 21 Sep 2017 16:29:54 +0800 Subject: [PATCH] 8186884: Test native KDC, Java krb5 lib, and native krb5 lib in one test Reviewed-by: asmotrak --- test/jdk/java/security/testlibrary/Proc.java | 17 +- .../jdk/sun/security/krb5/auto/BasicProc.java | 434 +++++++++----- test/jdk/sun/security/krb5/auto/Context.java | 83 ++- .../krb5/auto/HttpNegotiateServer.java | 1 + test/jdk/sun/security/krb5/auto/KDC.java | 560 +++++++++++++++--- .../sun/security/krb5/auto/SSLwithPerms.java | 1 + .../sun/security/krb5/auto/TEST.properties | 1 + 7 files changed, 846 insertions(+), 251 deletions(-) diff --git a/test/jdk/java/security/testlibrary/Proc.java b/test/jdk/java/security/testlibrary/Proc.java index dc1f9e7aea..1ade38c1ff 100644 --- a/test/jdk/java/security/testlibrary/Proc.java +++ b/test/jdk/java/security/testlibrary/Proc.java @@ -323,9 +323,12 @@ public class Proc { br = new BufferedReader(new InputStreamReader(p.getInputStream())); return this; } - String getId(String prefix) { - if (debug != null) return prefix + "." + debug; - else return prefix + "." + System.identityHashCode(this); + String getId(String suffix) { + if (debug != null) { + return debug + "." + suffix; + } else { + return System.identityHashCode(this) + "." + suffix; + } } // Reads a line from stdout of proc public String readLine() throws IOException { @@ -395,9 +398,13 @@ public class Proc { boolean isEmpty = true; while (true) { int i = System.in.read(); - if (i == -1) break; + if (i == -1) { + break; + } isEmpty = false; - if (i == '\n') break; + if (i == '\n') { + break; + } if (i != 13) { // Force it to a char, so only simple ASCII works. sb.append((char)i); diff --git a/test/jdk/sun/security/krb5/auto/BasicProc.java b/test/jdk/sun/security/krb5/auto/BasicProc.java index 54b204fb42..b8383efad7 100644 --- a/test/jdk/sun/security/krb5/auto/BasicProc.java +++ b/test/jdk/sun/security/krb5/auto/BasicProc.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2013, 2017, 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,170 +23,312 @@ /* * @test - * @bug 8009977 - * @summary A test library to launch multiple Java processes + * @bug 8009977 8186884 + * @summary A test to launch multiple Java processes using either Java GSS + * or native GSS * @library ../../../../java/security/testlibrary/ * @compile -XDignore.symbol.file BasicProc.java - * @run main/othervm BasicProc + * @run main/othervm BasicProc launcher */ -import java.io.File; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.nio.file.attribute.PosixFilePermission; +import java.util.Arrays; +import java.util.PropertyPermission; +import java.util.Random; +import java.util.Set; + import org.ietf.jgss.Oid; +import sun.security.krb5.Config; import javax.security.auth.PrivateCredentialPermission; +/** + * Run this test automatically and test Java GSS with embedded KDC. + * + * Run with customized native.krb5.libs to test interop between Java GSS + * and native GSS, and native.kdc.path with a native KDC. For example, + * run the following command to test interop among Java, default native, + * MIT, and Heimdal krb5 libraries with the Heimdal KDC: + * + * jtreg -Dnative.krb5.libs=j=, + * n=, + * k=/usr/local/krb5/lib/libgssapi_krb5.so, + * h=/space/install/heimdal/lib/libgssapi.so \ + * -Dnative.kdc.path=/usr/local/heimdal \ + * BasicProc.java + * + * Note: The first 4 lines should be concatenated to make a long system + * property value with no blank around ",". This comma-separated value + * has each element being name=libpath. The special name "j" means the + * Java library and libpath is ignored. Otherwise it means a native library, + * and libpath (can be empty) will be the value for the sun.security.jgss.lib + * system property. If this system property is not set, only the Java + * library will be tested. + */ + public class BasicProc { - static String CONF = "krb5.conf"; - static String KTAB = "ktab"; + private static final String CONF = "krb5.conf"; + private static final String KTAB_S = "server.ktab"; + private static final String KTAB_B = "backend.ktab"; + + private static final String HOST = "localhost"; + private static final String SERVER = "server/" + HOST; + private static final String BACKEND = "backend/" + HOST; + private static final String USER = "user"; + private static final char[] PASS = "password".toCharArray(); + private static final String REALM = "REALM"; + + private static final int MSGSIZE = 1024; + public static void main(String[] args) throws Exception { - String HOST = "localhost"; - String SERVER = "server/" + HOST; - String BACKEND = "backend/" + HOST; - String USER = "user"; - char[] PASS = "password".toCharArray(); - String REALM = "REALM"; Oid oid = new Oid("1.2.840.113554.1.2.2"); + byte[] token, msg; - if (args.length == 0) { - System.setProperty("java.security.krb5.conf", CONF); - KDC kdc = KDC.create(REALM, HOST, 0, true); - kdc.addPrincipal(USER, PASS); - kdc.addPrincipalRandKey("krbtgt/" + REALM); - kdc.addPrincipalRandKey(SERVER); - kdc.addPrincipalRandKey(BACKEND); - - String cwd = System.getProperty("user.dir"); - kdc.writeKtab(KTAB); - KDC.saveConfig(CONF, kdc, "forwardable = true"); - - Proc pc = Proc.create("BasicProc") - .args("client") - .prop("java.security.krb5.conf", CONF) - .prop("java.security.manager", "") - .perm(new java.util.PropertyPermission( - "sun.security.krb5.principal", "read")) - .perm(new javax.security.auth.AuthPermission( - "modifyPrincipals")) - .perm(new javax.security.auth.AuthPermission( - "modifyPrivateCredentials")) - .perm(new javax.security.auth.AuthPermission("doAs")) - .perm(new javax.security.auth.kerberos.ServicePermission( - "krbtgt/" + REALM + "@" + REALM, "initiate")) - .perm(new javax.security.auth.kerberos.ServicePermission( - "server/localhost@" + REALM, "initiate")) - .perm(new javax.security.auth.kerberos.DelegationPermission( - "\"server/localhost@" + REALM + "\" " + - "\"krbtgt/" + REALM + "@" + REALM + "\"")) - .debug("C") - .start(); - Proc ps = Proc.create("BasicProc") - .args("server") - .prop("java.security.krb5.conf", CONF) - .prop("java.security.manager", "") - .perm(new java.util.PropertyPermission( - "sun.security.krb5.principal", "read")) - .perm(new javax.security.auth.AuthPermission( - "modifyPrincipals")) - .perm(new javax.security.auth.AuthPermission( - "modifyPrivateCredentials")) - .perm(new javax.security.auth.AuthPermission("doAs")) - .perm(new PrivateCredentialPermission( - "javax.security.auth.kerberos.KeyTab * \"*\"", - "read")) - .perm(new javax.security.auth.kerberos.ServicePermission( - "server/localhost@" + REALM, "accept")) - .perm(new java.io.FilePermission( - cwd + File.separator + KTAB, "read")) - .perm(new javax.security.auth.kerberos.ServicePermission( - "backend/localhost@" + REALM, "initiate")) - .debug("S") - .start(); - Proc pb = Proc.create("BasicProc") - .args("backend") - .prop("java.security.krb5.conf", CONF) - .prop("java.security.manager", "") - .perm(new java.util.PropertyPermission( + switch (args[0]) { + case "launcher": + KDC kdc = KDC.create(REALM, HOST, 0, true); + try { + kdc.addPrincipal(USER, PASS); + kdc.addPrincipalRandKey("krbtgt/" + REALM); + kdc.addPrincipalRandKey(SERVER); + kdc.addPrincipalRandKey(BACKEND); + + // Native lib might do some name lookup + KDC.saveConfig(CONF, kdc, + "dns_lookup_kdc = no", + "ticket_lifetime = 1h", + "dns_lookup_realm = no", + "dns_canonicalize_hostname = false", + "forwardable = true"); + System.setProperty("java.security.krb5.conf", CONF); + Config.refresh(); + kdc.writeKtab(KTAB_S, false, SERVER); + kdc.writeKtab(KTAB_B, false, BACKEND); + + String[] tmp = System.getProperty("native.krb5.libs", "j=") + .split(","); + + // Library paths. The 1st one is always null which means + // Java, "" means the default native lib. + String[] libs = new String[tmp.length]; + + // Names for each lib above. Use in file names. + String[] names = new String[tmp.length]; + + boolean hasNative = false; + + for (int i = 0; i < tmp.length; i++) { + if (tmp[i].isEmpty()) { + throw new Exception("Invalid native.krb5.libs"); + } + String[] pair = tmp[i].split("=", 2); + names[i] = pair[0]; + if (!pair[0].equals("j")) { + libs[i] = pair.length > 1 ? pair[1] : ""; + hasNative = true; + } + } + + if (hasNative) { + kdc.kinit(USER, "base.ccache"); + } + + // Try the same lib first + for (int i = 0; i < libs.length; i++) { + once(names[i] + names[i] + names[i], + libs[i], libs[i], libs[i]); + } + + for (int i = 0; i < libs.length; i++) { + for (int j = 0; j < libs.length; j++) { + for (int k = 0; k < libs.length; k++) { + if (i != j || i != k) { + once(names[i] + names[j] + names[k], + libs[i], libs[j], libs[k]); + } + } + } + } + } finally { + kdc.terminate(); + } + break; + case "client": + Context c = args[1].equals("n") ? + Context.fromThinAir() : + Context.fromUserPass(USER, PASS, false); + c.startAsClient(SERVER, oid); + c.x().requestCredDeleg(true); + Proc.binOut(c.take(new byte[0])); // AP-REQ + token = Proc.binIn(); // AP-REP + c.take(token); + break; + case "server": + Context s = args[1].equals("n") ? + Context.fromThinAir() : + Context.fromUserKtab(SERVER, KTAB_S, true); + s.startAsServer(oid); + token = Proc.binIn(); // AP-REQ + token = s.take(token); + Proc.binOut(token); // AP-REP + Context s2 = s.delegated(); + s2.startAsClient(BACKEND, oid); + Proc.binOut(s2.take(new byte[0])); // AP-REQ + token = Proc.binIn(); + s2.take(token); // AP-REP + Random r = new Random(); + msg = new byte[MSGSIZE]; + r.nextBytes(msg); + Proc.binOut(s2.wrap(msg, true)); // enc1 + Proc.binOut(s2.wrap(msg, true)); // enc2 + Proc.binOut(s2.wrap(msg, true)); // enc3 + s2.verifyMic(Proc.binIn(), msg); // mic + byte[] msg2 = Proc.binIn(); // msg + if (!Arrays.equals(msg, msg2)) { + throw new Exception("diff msg"); + } + break; + case "backend": + Context b = args[1].equals("n") ? + Context.fromThinAir() : + Context.fromUserKtab(BACKEND, KTAB_B, true); + b.startAsServer(oid); + token = Proc.binIn(); // AP-REQ + Proc.binOut(b.take(token)); // AP-REP + msg = b.unwrap(Proc.binIn(), true); // enc1 + if (!Arrays.equals(msg, b.unwrap(Proc.binIn(), true))) { // enc2 + throw new Exception("diff msg"); + } + if (!Arrays.equals(msg, b.unwrap(Proc.binIn(), true))) { // enc3 + throw new Exception("diff msg"); + } + Proc.binOut(b.getMic(msg)); // mic + Proc.binOut(msg); // msg + break; + } + } + + /** + * One test run. + * + * @param label test label + * @param lc lib of client + * @param ls lib of server + * @param lb lib of backend + */ + private static void once(String label, String lc, String ls, String lb) + throws Exception { + + Proc pc = proc(lc) + .args("client", lc == null ? "j" : "n") + .perm(new javax.security.auth.kerberos.ServicePermission( + "krbtgt/" + REALM + "@" + REALM, "initiate")) + .perm(new javax.security.auth.kerberos.ServicePermission( + SERVER + "@" + REALM, "initiate")) + .perm(new javax.security.auth.kerberos.DelegationPermission( + "\"" + SERVER + "@" + REALM + "\" " + + "\"krbtgt/" + REALM + "@" + REALM + "\"")) + .debug(label + "-C"); + if (lc == null) { + // for Krb5LoginModule::promptForName + pc.perm(new PropertyPermission("user.name", "read")); + } else { + Files.copy(Paths.get("base.ccache"), Paths.get(label + ".ccache")); + Files.setPosixFilePermissions(Paths.get(label + ".ccache"), + Set.of(PosixFilePermission.OWNER_READ, + PosixFilePermission.OWNER_WRITE)); + pc.env("KRB5CCNAME", label + ".ccache"); + // Do not try system ktab if ccache fails + pc.env("KRB5_KTNAME", "none"); + } + pc.start(); + + Proc ps = proc(ls) + .args("server", ls == null ? "j" : "n") + .perm(new javax.security.auth.kerberos.ServicePermission( + SERVER + "@" + REALM, "accept")) + .perm(new javax.security.auth.kerberos.ServicePermission( + BACKEND + "@" + REALM, "initiate")) + .debug(label + "-S"); + if (ls == null) { + ps.perm(new PrivateCredentialPermission( + "javax.security.auth.kerberos.KeyTab * \"*\"", "read")) + .perm(new java.io.FilePermission(KTAB_S, "read")); + } else { + ps.env("KRB5_KTNAME", KTAB_S); + } + ps.start(); + + Proc pb = proc(lb) + .args("backend", lb == null ? "j" : "n") + .perm(new javax.security.auth.kerberos.ServicePermission( + BACKEND + "@" + REALM, "accept")) + .debug(label + "-B"); + if (lb == null) { + pb.perm(new PrivateCredentialPermission( + "javax.security.auth.kerberos.KeyTab * \"*\"", "read")) + .perm(new java.io.FilePermission(KTAB_B, "read")); + } else { + pb.env("KRB5_KTNAME", KTAB_B); + } + pb.start(); + + // Client and server handshake + ps.println(pc.readData()); + pc.println(ps.readData()); + + // Server and backend handshake + pb.println(ps.readData()); + ps.println(pb.readData()); + + // wrap/unwrap/getMic/verifyMic and plain text + pb.println(ps.readData()); + pb.println(ps.readData()); + pb.println(ps.readData()); + ps.println(pb.readData()); + ps.println(pb.readData()); + + if ((pc.waitFor() | ps.waitFor() | pb.waitFor()) != 0) { + throw new Exception("Process failed"); + } + } + + /** + * A Proc for a child process. + * + * @param lib the library. Null is Java. "" is default native lib. + */ + private static Proc proc(String lib) throws Exception { + Proc p = Proc.create("BasicProc") + .prop("java.security.manager", "") + .perm(new javax.security.auth.AuthPermission("doAs")); + if (lib != null) { + p.env("KRB5_CONFIG", CONF) + .env("KRB5_TRACE", "/dev/stderr") + .prop("sun.security.jgss.native", "true") + .prop("sun.security.jgss.lib", lib) + .prop("javax.security.auth.useSubjectCredsOnly", "false") + .prop("sun.security.nativegss.debug", "true"); + int pos = lib.lastIndexOf('/'); + if (pos > 0) { + p.env("LD_LIBRARY_PATH", lib.substring(0, pos)); + p.env("DYLD_LIBRARY_PATH", lib.substring(0, pos)); + } + } else { + p.perm(new java.util.PropertyPermission( "sun.security.krb5.principal", "read")) + // For Krb5LoginModule::login. .perm(new javax.security.auth.AuthPermission( "modifyPrincipals")) .perm(new javax.security.auth.AuthPermission( "modifyPrivateCredentials")) - .perm(new javax.security.auth.AuthPermission("doAs")) - .perm(new PrivateCredentialPermission( - "javax.security.auth.kerberos.KeyTab * \"*\"", - "read")) - .perm(new javax.security.auth.kerberos.ServicePermission( - "backend/localhost@" + REALM, "accept")) - .perm(new java.io.FilePermission( - cwd + File.separator + KTAB, "read")) - .debug("B") - .start(); - - // Client and server handshake - String token = pc.readData(); - ps.println(token); - token = ps.readData(); - pc.println(token); - // Server and backend handshake - token = ps.readData(); - pb.println(token); - token = pb.readData(); - ps.println(token); - // wrap/unwrap/getMic/verifyMic and plain text - token = ps.readData(); - pb.println(token); - token = pb.readData(); - ps.println(token); - token = pb.readData(); - ps.println(token); - - if ((pc.waitFor() | ps.waitFor() | pb.waitFor()) != 0) { - throw new Exception(); - } - } else if (args[0].equals("client")) { - Context c = Context.fromUserPass(USER, PASS, false); - c.startAsClient(SERVER, oid); - c.x().requestCredDeleg(true); - Proc.binOut(c.take(new byte[0])); - byte[] token = Proc.binIn(); - c.take(token); - } else if (args[0].equals("server")) { - Context s = Context.fromUserKtab(SERVER, KTAB, true); - s.startAsServer(oid); - byte[] token = Proc.binIn(); - token = s.take(token); - Proc.binOut(token); - Context s2 = s.delegated(); - s2.startAsClient(BACKEND, oid); - Proc.binOut(s2.take(new byte[0])); - token = Proc.binIn(); - s2.take(token); - byte[] msg = "Hello".getBytes(); - Proc.binOut(s2.wrap(msg, true)); - s2.verifyMic(Proc.binIn(), msg); - String in = Proc.textIn(); - if (!in.equals("Hello")) { - throw new Exception(); - } - } else if (args[0].equals("backend")) { - Context b = Context.fromUserKtab(BACKEND, KTAB, true); - b.startAsServer(oid); - byte[] token = Proc.binIn(); - Proc.binOut(b.take(token)); - byte[] msg = b.unwrap(Proc.binIn(), true); - Proc.binOut(b.getMic(msg)); - Proc.textOut(new String(msg)); + .prop("sun.security.krb5.debug", "true") + .prop("java.security.krb5.conf", CONF); } - } - // create a native server - private static Proc ns(Proc p) throws Exception { - return p - .env("KRB5_CONFIG", CONF) - .env("KRB5_KTNAME", KTAB) - .prop("sun.security.jgss.native", "true") - .prop("javax.security.auth.useSubjectCredsOnly", "false") - .prop("sun.security.nativegss.debug", "true"); + return p; } } diff --git a/test/jdk/sun/security/krb5/auto/Context.java b/test/jdk/sun/security/krb5/auto/Context.java index e7c601deba..93073567ef 100644 --- a/test/jdk/sun/security/krb5/auto/Context.java +++ b/test/jdk/sun/security/krb5/auto/Context.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008, 2013, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2008, 2017, 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,6 +23,7 @@ import com.sun.security.auth.module.Krb5LoginModule; +import java.io.IOException; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.security.PrivilegedActionException; @@ -31,6 +32,11 @@ import java.util.Arrays; import java.util.HashMap; import java.util.Map; import javax.security.auth.Subject; +import javax.security.auth.callback.Callback; +import javax.security.auth.callback.CallbackHandler; +import javax.security.auth.callback.NameCallback; +import javax.security.auth.callback.PasswordCallback; +import javax.security.auth.callback.UnsupportedCallbackException; import javax.security.auth.kerberos.KerberosKey; import javax.security.auth.kerberos.KerberosTicket; import javax.security.auth.login.LoginContext; @@ -41,9 +47,14 @@ import org.ietf.jgss.GSSManager; import org.ietf.jgss.GSSName; import org.ietf.jgss.MessageProp; import org.ietf.jgss.Oid; +import sun.security.jgss.krb5.Krb5Util; +import sun.security.krb5.Credentials; +import sun.security.krb5.internal.ccache.CredentialsCache; + import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.security.Principal; +import java.util.Set; /** * Context of a JGSS subject, encapsulating Subject and GSSContext. @@ -149,24 +160,36 @@ public class Context { Map map = new HashMap<>(); Map shared = new HashMap<>(); + if (storeKey) { + map.put("storeKey", "true"); + } + if (pass != null) { - map.put("useFirstPass", "true"); - shared.put("javax.security.auth.login.name", user); - shared.put("javax.security.auth.login.password", pass); + krb5.initialize(out.s, new CallbackHandler() { + @Override + public void handle(Callback[] callbacks) + throws IOException, UnsupportedCallbackException { + for (Callback cb: callbacks) { + if (cb instanceof NameCallback) { + ((NameCallback)cb).setName(user); + } else if (cb instanceof PasswordCallback) { + ((PasswordCallback)cb).setPassword(pass); + } + } + } + }, shared, map); } else { map.put("doNotPrompt", "true"); map.put("useTicketCache", "true"); if (user != null) { map.put("principal", user); } - } - if (storeKey) { - map.put("storeKey", "true"); + krb5.initialize(out.s, null, shared, map); } - krb5.initialize(out.s, null, shared, map); krb5.login(); krb5.commit(); + return out; } @@ -529,9 +552,23 @@ public class Context { * @param s2 the receiver * @throws java.lang.Exception If anything goes wrong */ - static public void transmit(final String message, final Context s1, + static public void transmit(String message, final Context s1, + final Context s2) throws Exception { + transmit(message.getBytes(), s1, s2); + } + + /** + * Transmits a message from one Context to another. The sender wraps the + * message and sends it to the receiver. The receiver unwraps it, creates + * a MIC of the clear text and sends it back to the sender. The sender + * verifies the MIC against the message sent earlier. + * @param messageBytes the message + * @param s1 the sender + * @param s2 the receiver + * @throws java.lang.Exception If anything goes wrong + */ + static public void transmit(byte[] messageBytes, final Context s1, final Context s2) throws Exception { - final byte[] messageBytes = message.getBytes(); System.out.printf("-------------------- TRANSMIT from %s to %s------------------------\n", s1.name, s2.name); byte[] wrapped = s1.wrap(messageBytes, true); @@ -618,6 +655,32 @@ public class Context { }, in); } + /** + * Saves the tickets to a ccache file. + * + * @param file pathname of the ccache file + * @return true if created, false otherwise. + */ + public boolean ccache(String file) throws Exception { + Set tickets + = s.getPrivateCredentials(KerberosTicket.class); + if (tickets != null && !tickets.isEmpty()) { + CredentialsCache cc = null; + for (KerberosTicket t : tickets) { + Credentials cred = Krb5Util.ticketToCreds(t); + if (cc == null) { + cc = CredentialsCache.create(cred.getClient(), file); + } + cc.update(cred.toCCacheCreds()); + } + if (cc != null) { + cc.save(); + return true; + } + } + return false; + } + /** * Handshake (security context establishment process) between two Contexts * @param c the initiator diff --git a/test/jdk/sun/security/krb5/auto/HttpNegotiateServer.java b/test/jdk/sun/security/krb5/auto/HttpNegotiateServer.java index 1447d81cdd..e26eae648d 100644 --- a/test/jdk/sun/security/krb5/auto/HttpNegotiateServer.java +++ b/test/jdk/sun/security/krb5/auto/HttpNegotiateServer.java @@ -28,6 +28,7 @@ * java.security.jgss/sun.security.krb5.internal:+open * java.security.jgss/sun.security.jgss * java.security.jgss/sun.security.krb5:+open + * java.security.jgss/sun.security.jgss.krb5 * java.security.jgss/sun.security.krb5.internal.ccache * java.security.jgss/sun.security.krb5.internal.crypto * java.security.jgss/sun.security.krb5.internal.ktab diff --git a/test/jdk/sun/security/krb5/auto/KDC.java b/test/jdk/sun/security/krb5/auto/KDC.java index ed12419a1f..105bb7d288 100644 --- a/test/jdk/sun/security/krb5/auto/KDC.java +++ b/test/jdk/sun/security/krb5/auto/KDC.java @@ -27,11 +27,12 @@ import java.lang.reflect.InvocationTargetException; import java.net.*; import java.io.*; import java.lang.reflect.Method; -import java.security.SecureRandom; -import java.time.Instant; -import java.time.temporal.ChronoUnit; +import java.nio.file.Files; +import java.nio.file.Paths; import java.util.*; import java.util.concurrent.*; +import java.util.stream.Collectors; +import java.util.stream.Stream; import sun.security.krb5.*; import sun.security.krb5.internal.*; @@ -45,6 +46,11 @@ import sun.security.util.DerValue; /** * A KDC server. + * + * Note: By setting the system property native.kdc.path to a native + * krb5 installation, this class starts a native KDC with the + * given realm and host. It can also add new principals and save keytabs. + * Other features might not be available. *

* Features: *

    @@ -125,12 +131,20 @@ public class KDC { public static final int DEFAULT_LIFETIME = 39600; public static final int DEFAULT_RENEWTIME = 86400; - public static String NOT_EXISTING_HOST = "not.existing.host"; + public static final String NOT_EXISTING_HOST = "not.existing.host"; - // Under the hood. + // What etypes the KDC supports. Comma-separated strings. Null for all. + // Please note native KDCs might use different names. + private static final String SUPPORTED_ETYPES + = System.getProperty("kdc.supported.enctypes"); + + // The native KDC + private final NativeKdc nativeKdc; + + // The native KDC process + private Process kdcProc = null; - // The random generator to generate random keys (including session keys) - private static SecureRandom secureRandom = new SecureRandom(); + // Under the hood. // Principal db. principal -> pass. A case-insensitive TreeMap is used // so that even if the client provides a name with different case, the KDC @@ -229,7 +243,8 @@ public class KDC { static { if (System.getProperty("jdk.net.hosts.file") == null) { - String hostsFileName = System.getProperty("test.src", ".") + "/TestHosts"; + String hostsFileName + = System.getProperty("test.src", ".") + "/TestHosts"; System.setProperty("jdk.net.hosts.file", hostsFileName); } } @@ -273,7 +288,8 @@ public class KDC { * @return the running KDC instance * @throws java.io.IOException for any socket creation error */ - public static KDC create(String realm, String kdc, int port, boolean asDaemon) throws IOException { + public static KDC create(String realm, String kdc, int port, + boolean asDaemon) throws IOException { return new KDC(realm, kdc, port, asDaemon); } @@ -313,26 +329,38 @@ public class KDC { */ public void writeKtab(String tab, boolean append, String... names) throws IOException, KrbException { - KeyTab ktab = append ? KeyTab.getInstance(tab) : KeyTab.create(tab); + KeyTab ktab = null; + if (nativeKdc == null) { + ktab = append ? KeyTab.getInstance(tab) : KeyTab.create(tab); + } Iterable entries = (names.length != 0) ? Arrays.asList(names): passwords.keySet(); for (String name : entries) { - char[] pass = passwords.get(name); - int kvno = 0; - if (Character.isDigit(pass[pass.length-1])) { - kvno = pass[pass.length-1] - '0'; + if (name.indexOf('@') < 0) { + name = name + "@" + realm; } - PrincipalName pn = new PrincipalName(name, + if (nativeKdc == null) { + char[] pass = passwords.get(name); + int kvno = 0; + if (Character.isDigit(pass[pass.length - 1])) { + kvno = pass[pass.length - 1] - '0'; + } + PrincipalName pn = new PrincipalName(name, name.indexOf('/') < 0 ? - PrincipalName.KRB_NT_UNKNOWN : - PrincipalName.KRB_NT_SRV_HST); - ktab.addEntry(pn, + PrincipalName.KRB_NT_UNKNOWN : + PrincipalName.KRB_NT_SRV_HST); + ktab.addEntry(pn, getSalt(pn), pass, kvno, true); + } else { + nativeKdc.ktadd(name, tab); + } + } + if (nativeKdc == null) { + ktab.save(); } - ktab.save(); } /** @@ -389,16 +417,24 @@ public class KDC { * @param salt the salt, or null if a default value will be used * @param s2kparams the s2kparams, or null if a default value will be used */ - public void addPrincipal(String user, char[] pass, String salt, byte[] s2kparams) { + public void addPrincipal( + String user, char[] pass, String salt, byte[] s2kparams) { if (user.indexOf('@') < 0) { user = user + "@" + realm; } - passwords.put(user, pass); - if (salt != null) { - salts.put(user, salt); - } - if (s2kparams != null) { - s2kparamses.put(user, s2kparams); + if (nativeKdc != null) { + if (!user.equals("krbtgt/" + realm)) { + nativeKdc.addPrincipal(user, new String(pass)); + } + passwords.put(user, new char[0]); + } else { + passwords.put(user, pass); + if (salt != null) { + salts.put(user, salt); + } + if (s2kparams != null) { + s2kparamses.put(user, s2kparams); + } } } @@ -495,12 +531,11 @@ public class KDC { */ public static void saveConfig(String file, KDC kdc, Object... more) throws IOException { - File f = new File(file); StringBuffer sb = new StringBuffer(); sb.append("[libdefaults]\ndefault_realm = "); sb.append(kdc.realm); sb.append("\n"); - for (Object o: more) { + for (Object o : more) { if (o instanceof String) { sb.append(o); sb.append("\n"); @@ -508,14 +543,12 @@ public class KDC { } sb.append("\n[realms]\n"); sb.append(kdc.realmLine()); - for (Object o: more) { + for (Object o : more) { if (o instanceof KDC) { - sb.append(((KDC)o).realmLine()); + sb.append(((KDC) o).realmLine()); } } - FileOutputStream fos = new FileOutputStream(f); - fos.write(sb.toString().getBytes()); - fos.close(); + Files.write(Paths.get(file), sb.toString().getBytes()); } /** @@ -535,6 +568,7 @@ public class KDC { private KDC(String realm, String kdc) { this.realm = realm; this.kdc = kdc; + this.nativeKdc = null; } /** @@ -542,7 +576,9 @@ public class KDC { */ protected KDC(String realm, String kdc, int port, boolean asDaemon) throws IOException { - this(realm, kdc); + this.realm = realm; + this.kdc = kdc; + this.nativeKdc = NativeKdc.get(this); startServer(port, asDaemon); } /** @@ -551,8 +587,9 @@ public class KDC { */ private static char[] randomPassword() { char[] pass = new char[32]; + Random r = new Random(); for (int i=0; i<31; i++) - pass[i] = (char)secureRandom.nextInt(); + pass[i] = (char)('a' + r.nextInt(26)); // The last char cannot be a number, otherwise, keyForUser() // believes it's a sign of kvno pass[31] = 'Z'; @@ -688,6 +725,15 @@ public class KDC { } } + /** + * Returns a KerberosTime. + * + * @param offset offset from NOW in milliseconds + */ + private static KerberosTime timeFor(long offset) { + return new KerberosTime(new Date().getTime() + offset); + } + /** * Processes an incoming request and generates a response. * @param in the request @@ -719,7 +765,10 @@ public class KDC { " sends TGS-REQ for " + service + ", " + tgsReq.reqBody.kdcOptions); KDCReqBody body = tgsReq.reqBody; - int[] eTypes = KDCReqBodyDotEType(body); + int[] eTypes = filterSupported(KDCReqBodyDotEType(body)); + if (eTypes.length == 0) { + throw new KrbException(Krb5.KDC_ERR_ETYPE_NOSUPP); + } int e2 = eTypes[0]; // etype for outgoing session key int e3 = eTypes[0]; // etype for outgoing ticket @@ -756,13 +805,14 @@ public class KDC { PAForUserEnc p4u = new PAForUserEnc( new DerValue(pa.getValue()), null); forUserCName = p4u.name; - System.out.println(realm + "> presenting a PA_FOR_USER " + System.out.println(realm + "> See PA_FOR_USER " + " in the name of " + p4u.name); } } } if (forUserCName != null) { - List names = (List)options.get(Option.ALLOW_S4U2SELF); + List names = (List) + options.get(Option.ALLOW_S4U2SELF); if (!names.contains(cname.toString())) { // Mimic the normal KDC behavior. When a server is not // allowed to send S4U2self, do not send an error. @@ -783,11 +833,15 @@ public class KDC { EncryptionKey key = generateRandomKey(e2); // Check time, TODO + KerberosTime from = body.from; KerberosTime till = body.till; + if (from == null || from.isZero()) { + from = timeFor(0); + } if (till == null) { throw new KrbException(Krb5.KDC_ERR_NEVER_VALID); // TODO } else if (till.isZero()) { - till = new KerberosTime(new Date().getTime() + 1000 * DEFAULT_LIFETIME); + till = timeFor(1000 * DEFAULT_LIFETIME); } boolean[] bFlags = new boolean[Krb5.TKT_OPTS_MAX+1]; @@ -813,7 +867,7 @@ public class KDC { } if (body.kdcOptions.get(KDCOptions.RENEWABLE)) { bFlags[Krb5.TKT_OPTS_RENEWABLE] = true; - //renew = new KerberosTime(new Date().getTime() + 1000 * 3600 * 24 * 7); + //renew = timeFor(1000 * 3600 * 24 * 7); } if (body.kdcOptions.get(KDCOptions.PROXIABLE)) { bFlags[Krb5.TKT_OPTS_PROXIABLE] = true; @@ -832,7 +886,8 @@ public class KDC { Map> map = (Map>) options.get(Option.ALLOW_S4U2PROXY); Ticket second = KDCReqBodyDotFirstAdditionalTicket(body); - EncryptionKey key2 = keyForUser(second.sname, second.encPart.getEType(), true); + EncryptionKey key2 = keyForUser( + second.sname, second.encPart.getEType(), true); byte[] bb = second.encPart.decrypt(key2, KeyUsage.KU_TICKET); DerInputStream derIn = new DerInputStream(bb); DerValue der = derIn.getDerValue(); @@ -882,8 +937,8 @@ public class KDC { key, cname, new TransitedEncoding(1, new byte[0]), // TODO - new KerberosTime(new Date()), - body.from, + timeFor(0), + from, till, renewTill, body.addresses != null ? body.addresses : etp.caddr, @@ -900,20 +955,21 @@ public class KDC { ); EncTGSRepPart enc_part = new EncTGSRepPart( key, - new LastReq(new LastReqEntry[]{ - new LastReqEntry(0, new KerberosTime(new Date().getTime() - 10000)) + new LastReq(new LastReqEntry[] { + new LastReqEntry(0, timeFor(-10000)) }), body.getNonce(), // TODO: detect replay - new KerberosTime(new Date().getTime() + 1000 * 3600 * 24), + timeFor(1000 * 3600 * 24), // Next 5 and last MUST be same with ticket tFlags, - new KerberosTime(new Date()), - body.from, + timeFor(0), + from, till, renewTill, service, body.addresses ); - EncryptedData edata = new EncryptedData(ckey, enc_part.asn1Encode(), KeyUsage.KU_ENC_TGS_REP_PART_SESSKEY); + EncryptedData edata = new EncryptedData(ckey, enc_part.asn1Encode(), + KeyUsage.KU_ENC_TGS_REP_PART_SESSKEY); TGSRep tgsRep = new TGSRep(null, cname, t, @@ -934,7 +990,7 @@ public class KDC { + " " +ke.returnCodeMessage()); if (kerr == null) { kerr = new KRBError(null, null, null, - new KerberosTime(new Date()), + timeFor(0), 0, ke.returnCode(), body.cname, @@ -970,16 +1026,11 @@ public class KDC { KDCReqBody body = asReq.reqBody; - eTypes = KDCReqBodyDotEType(body); - int eType = eTypes[0]; - - // Maybe server does not support aes256, but a kinit does - if (!EType.isSupported(eType)) { - if (eTypes.length < 2) { - throw new KrbException(Krb5.KDC_ERR_ETYPE_NOSUPP); - } - eType = eTypes[1]; + eTypes = filterSupported(KDCReqBodyDotEType(body)); + if (eTypes.length == 0) { + throw new KrbException(Krb5.KDC_ERR_ETYPE_NOSUPP); } + int eType = eTypes[0]; EncryptionKey ckey = keyForUser(body.cname, eType, false); EncryptionKey skey = keyForUser(service, eType, true); @@ -1008,31 +1059,32 @@ public class KDC { // Session key EncryptionKey key = generateRandomKey(eType); // Check time, TODO + KerberosTime from = body.from; KerberosTime till = body.till; KerberosTime rtime = body.rtime; + if (from == null || from.isZero()) { + from = timeFor(0); + } if (till == null) { throw new KrbException(Krb5.KDC_ERR_NEVER_VALID); // TODO } else if (till.isZero()) { - till = new KerberosTime( - new Date().getTime() + 1000 * DEFAULT_LIFETIME); - } else if (till.greaterThan(new KerberosTime(Instant.now() - .plus(1, ChronoUnit.DAYS)))) { + till = timeFor(1000 * DEFAULT_LIFETIME); + } else if (till.greaterThan(timeFor(24 * 3600 * 1000))) { // If till is more than 1 day later, make it renewable - till = new KerberosTime( - new Date().getTime() + 1000 * DEFAULT_LIFETIME); + till = timeFor(1000 * DEFAULT_LIFETIME); body.kdcOptions.set(KDCOptions.RENEWABLE, true); if (rtime == null) rtime = till; } if (rtime == null && body.kdcOptions.get(KDCOptions.RENEWABLE)) { - rtime = new KerberosTime( - new Date().getTime() + 1000 * DEFAULT_RENEWTIME); + rtime = timeFor(1000 * DEFAULT_RENEWTIME); } //body.from boolean[] bFlags = new boolean[Krb5.TKT_OPTS_MAX+1]; if (body.kdcOptions.get(KDCOptions.FORWARDABLE)) { List sensitives = (List) options.get(Option.SENSITIVE_ACCOUNTS); - if (sensitives != null && sensitives.contains(body.cname.toString())) { + if (sensitives != null + && sensitives.contains(body.cname.toString())) { // Cannot make FORWARDABLE } else { bFlags[Krb5.TKT_OPTS_FORWARDABLE] = true; @@ -1040,7 +1092,7 @@ public class KDC { } if (body.kdcOptions.get(KDCOptions.RENEWABLE)) { bFlags[Krb5.TKT_OPTS_RENEWABLE] = true; - //renew = new KerberosTime(new Date().getTime() + 1000 * 3600 * 24 * 7); + //renew = timeFor(1000 * 3600 * 24 * 7); } if (body.kdcOptions.get(KDCOptions.PROXIABLE)) { bFlags[Krb5.TKT_OPTS_PROXIABLE] = true; @@ -1062,7 +1114,8 @@ public class KDC { pas2 = new DerValue[] { new DerValue(new ETypeInfo2(1, null, null).asn1Encode()), new DerValue(new ETypeInfo2(1, "", null).asn1Encode()), - new DerValue(new ETypeInfo2(1, realm, new byte[]{1}).asn1Encode()), + new DerValue(new ETypeInfo2( + 1, realm, new byte[]{1}).asn1Encode()), }; pas = new DerValue[] { new DerValue(new ETypeInfo(1, null).asn1Encode()), @@ -1072,7 +1125,8 @@ public class KDC { break; case 2: // we still reject non-null s2kparams and prefer E2 over E pas2 = new DerValue[] { - new DerValue(new ETypeInfo2(1, realm, new byte[]{1}).asn1Encode()), + new DerValue(new ETypeInfo2( + 1, realm, new byte[]{1}).asn1Encode()), new DerValue(new ETypeInfo2(1, null, null).asn1Encode()), new DerValue(new ETypeInfo2(1, "", null).asn1Encode()), }; @@ -1165,11 +1219,15 @@ public class KDC { } } else { try { - EncryptedData data = newEncryptedData(new DerValue(inPAs[0].getValue())); - EncryptionKey pakey = keyForUser(body.cname, data.getEType(), false); + EncryptedData data = newEncryptedData( + new DerValue(inPAs[0].getValue())); + EncryptionKey pakey + = keyForUser(body.cname, data.getEType(), false); data.decrypt(pakey, KeyUsage.KU_PA_ENC_TS); } catch (Exception e) { - throw new KrbException(Krb5.KDC_ERR_PREAUTH_FAILED); + KrbException ke = new KrbException(Krb5.KDC_ERR_PREAUTH_FAILED); + ke.initCause(e); + throw ke; } bFlags[Krb5.TKT_OPTS_PRE_AUTHENT] = true; } @@ -1180,8 +1238,8 @@ public class KDC { key, body.cname, new TransitedEncoding(1, new byte[0]), - new KerberosTime(new Date()), - body.from, + timeFor(0), + from, till, rtime, body.addresses, null); @@ -1192,19 +1250,20 @@ public class KDC { EncASRepPart enc_part = new EncASRepPart( key, new LastReq(new LastReqEntry[]{ - new LastReqEntry(0, new KerberosTime(new Date().getTime() - 10000)) + new LastReqEntry(0, timeFor(-10000)) }), body.getNonce(), // TODO: detect replay? - new KerberosTime(new Date().getTime() + 1000 * 3600 * 24), + timeFor(1000 * 3600 * 24), // Next 5 and last MUST be same with ticket tFlags, - new KerberosTime(new Date()), - body.from, + timeFor(0), + from, till, rtime, service, body.addresses ); - EncryptedData edata = new EncryptedData(ckey, enc_part.asn1Encode(), KeyUsage.KU_ENC_AS_REP_PART); + EncryptedData edata = new EncryptedData(ckey, enc_part.asn1Encode(), + KeyUsage.KU_ENC_AS_REP_PART); ASRep asRep = new ASRep( outPAs.toArray(new PAData[outPAs.size()]), body.cname, @@ -1259,7 +1318,7 @@ public class KDC { eData = temp.toByteArray(); } kerr = new KRBError(null, null, null, - new KerberosTime(new Date()), + timeFor(0), 0, ke.returnCode(), body.cname, @@ -1271,6 +1330,35 @@ public class KDC { } } + private int[] filterSupported(int[] input) { + int count = 0; + for (int i = 0; i < input.length; i++) { + if (!EType.isSupported(input[i])) { + continue; + } + if (SUPPORTED_ETYPES != null) { + boolean supported = false; + for (String se : SUPPORTED_ETYPES.split(",")) { + if (Config.getType(se) == input[i]) { + supported = true; + break; + } + } + if (!supported) { + continue; + } + } + if (count != i) { + input[count] = input[i]; + } + count++; + } + if (count != input.length) { + input = Arrays.copyOf(input, count); + } + return input; + } + /** * Generates a line for a KDC to put inside [realms] of krb5.conf * @return REALM.NAME = { kdc = host:port etc } @@ -1295,6 +1383,20 @@ public class KDC { * @throws java.io.IOException for any communication error */ protected void startServer(int port, boolean asDaemon) throws IOException { + if (nativeKdc != null) { + startNativeServer(port, asDaemon); + } else { + startJavaServer(port, asDaemon); + } + } + + private void startNativeServer(int port, boolean asDaemon) throws IOException { + nativeKdc.prepare(); + nativeKdc.init(); + kdcProc = nativeKdc.kdc(); + } + + private void startJavaServer(int port, boolean asDaemon) throws IOException { if (port > 0) { u1 = new DatagramSocket(port, InetAddress.getByName("127.0.0.1")); t1 = new ServerSocket(port); @@ -1391,19 +1493,37 @@ public class KDC { } } + public void kinit(String user, String ccache) throws Exception { + if (user.indexOf('@') < 0) { + user = user + "@" + realm; + } + if (nativeKdc != null) { + nativeKdc.kinit(user, ccache); + } else { + Context.fromUserPass(user, passwords.get(user), false) + .ccache(ccache); + } + } + boolean isReady() { return udpConsumerReady && tcpConsumerReady && dispatcherReady; } public void terminate() { - try { - thread1.stop(); - thread2.stop(); - thread3.stop(); - u1.close(); - t1.close(); - } catch (Exception e) { - // OK + if (nativeKdc != null) { + System.out.println("Killing kdc..."); + kdcProc.destroyForcibly(); + System.out.println("Done"); + } else { + try { + thread1.stop(); + thread2.stop(); + thread3.stop(); + u1.close(); + t1.close(); + } catch (Exception e) { + // OK + } } } @@ -1518,6 +1638,266 @@ public class KDC { } } + /** + * A native KDC using the binaries in nativePath. Attention: + * this is using binaries, not an existing KDC instance. + * An implementation of this takes care of configuration, + * principal db managing and KDC startup. + */ + static abstract class NativeKdc { + + protected Map env; + protected String nativePath; + protected String base; + protected String realm; + protected int port; + + NativeKdc(String nativePath, KDC kdc) { + if (kdc.port == 0) { + kdc.port = 8000 + new java.util.Random().nextInt(10000); + } + this.nativePath = nativePath; + this.realm = kdc.realm; + this.port = kdc.port; + this.base = Paths.get("" + port).toAbsolutePath().toString(); + } + + // Add a new principal + abstract void addPrincipal(String user, String pass); + // Add a keytab entry + abstract void ktadd(String user, String ktab); + // Initialize KDC + abstract void init(); + // Start kdc + abstract Process kdc(); + // Configuration + abstract void prepare(); + // Fill ccache + abstract void kinit(String user, String ccache); + + static NativeKdc get(KDC kdc) { + String prop = System.getProperty("native.kdc.path"); + if (prop == null) { + return null; + } else if (Files.exists(Paths.get(prop, "sbin/krb5kdc"))) { + return new MIT(true, prop, kdc); + } else if (Files.exists(Paths.get(prop, "kdc/krb5kdc"))) { + return new MIT(false, prop, kdc); + } else if (Files.exists(Paths.get(prop, "libexec/kdc"))) { + return new Heimdal(prop, kdc); + } else { + throw new IllegalArgumentException("Strange " + prop); + } + } + + Process run(boolean wait, String... cmd) { + try { + System.out.println("Running " + cmd2str(env, cmd)); + ProcessBuilder pb = new ProcessBuilder(); + pb.inheritIO(); + pb.environment().putAll(env); + Process p = pb.command(cmd).start(); + if (wait) { + if (p.waitFor() < 0) { + throw new RuntimeException("exit code is not null"); + } + return null; + } else { + return p; + } + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + private String cmd2str(Map env, String... cmd) { + return env.entrySet().stream().map(e -> e.getKey()+"="+e.getValue()) + .collect(Collectors.joining(" ")) + " " + + Stream.of(cmd).collect(Collectors.joining(" ")); + } + } + + // Heimdal KDC. Build your own and run "make install" to nativePath. + static class Heimdal extends NativeKdc { + + Heimdal(String nativePath, KDC kdc) { + super(nativePath, kdc); + this.env = Map.of( + "KRB5_CONFIG", base + "/krb5.conf", + "KRB5_TRACE", "/dev/stderr", + "DYLD_LIBRARY_PATH", nativePath + "/lib", + "LD_LIBRARY_PATH", nativePath + "/lib"); + } + + @Override + public void addPrincipal(String user, String pass) { + run(true, nativePath + "/bin/kadmin", "-l", "-r", realm, + "add", "-p", pass, "--use-defaults", user); + } + + @Override + public void ktadd(String user, String ktab) { + run(true, nativePath + "/bin/kadmin", "-l", "-r", realm, + "ext_keytab", "-k", ktab, user); + } + + @Override + public void init() { + run(true, nativePath + "/bin/kadmin", "-l", "-r", realm, + "init", "--realm-max-ticket-life=1day", + "--realm-max-renewable-life=1month", realm); + } + + @Override + public Process kdc() { + return run(false, nativePath + "/libexec/kdc", + "--addresses=127.0.0.1", "-P", "" + port); + } + + @Override + public void prepare() { + try { + Files.createDirectory(Paths.get(base)); + Files.write(Paths.get(base + "/krb5.conf"), Arrays.asList( + "[libdefaults]", + "default_realm = " + realm, + "default_keytab_name = FILE:" + base + "/krb5.keytab", + "forwardable = true", + "dns_lookup_kdc = no", + "dns_lookup_realm = no", + "dns_canonicalize_hostname = false", + "\n[realms]", + realm + " = {", + " kdc = localhost:" + port, + "}", + "\n[kdc]", + "db-dir = " + base, + "database = {", + " label = {", + " dbname = " + base + "/current-db", + " realm = " + realm, + " mkey_file = " + base + "/mkey.file", + " acl_file = " + base + "/heimdal.acl", + " log_file = " + base + "/current.log", + " }", + "}", + SUPPORTED_ETYPES == null ? "" + : ("\n[kadmin]\ndefault_keys = " + + (SUPPORTED_ETYPES + ",") + .replaceAll(",", ":pw-salt ")), + "\n[logging]", + "kdc = 0-/FILE:" + base + "/messages.log", + "krb5 = 0-/FILE:" + base + "/messages.log", + "default = 0-/FILE:" + base + "/messages.log" + )); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + @Override + void kinit(String user, String ccache) { + String tmpName = base + "/" + user + "." + + System.identityHashCode(this) + ".keytab"; + ktadd(user, tmpName); + run(true, nativePath + "/bin/kinit", + "-f", "-t", tmpName, "-c", ccache, user); + } + } + + // MIT krb5 KDC. Make your own exploded (install == false), or + // "make install" into nativePath (install == true). + static class MIT extends NativeKdc { + + private boolean install; // "make install" or "make" + + MIT(boolean install, String nativePath, KDC kdc) { + super(nativePath, kdc); + this.install = install; + this.env = Map.of( + "KRB5_KDC_PROFILE", base + "/kdc.conf", + "KRB5_CONFIG", base + "/krb5.conf", + "KRB5_TRACE", "/dev/stderr", + "DYLD_LIBRARY_PATH", nativePath + "/lib", + "LD_LIBRARY_PATH", nativePath + "/lib"); + } + + @Override + public void addPrincipal(String user, String pass) { + run(true, nativePath + + (install ? "/sbin/" : "/kadmin/cli/") + "kadmin.local", + "-q", "addprinc -pw " + pass + " " + user); + } + + @Override + public void ktadd(String user, String ktab) { + run(true, nativePath + + (install ? "/sbin/" : "/kadmin/cli/") + "kadmin.local", + "-q", "ktadd -k " + ktab + " -norandkey " + user); + } + + @Override + public void init() { + run(true, nativePath + + (install ? "/sbin/" : "/kadmin/dbutil/") + "kdb5_util", + "create", "-s", "-W", "-P", "olala"); + } + + @Override + public Process kdc() { + return run(false, nativePath + + (install ? "/sbin/" : "/kdc/") + "krb5kdc", + "-n"); + } + + @Override + public void prepare() { + try { + Files.createDirectory(Paths.get(base)); + Files.write(Paths.get(base + "/kdc.conf"), Arrays.asList( + "[kdcdefaults]", + "\n[realms]", + realm + "= {", + " kdc_listen = " + this.port, + " kdc_tcp_listen = " + this.port, + " database_name = " + base + "/principal", + " key_stash_file = " + base + "/.k5.ATHENA.MIT.EDU", + SUPPORTED_ETYPES == null ? "" + : (" supported_enctypes = " + + (SUPPORTED_ETYPES + ",") + .replaceAll(",", ":normal ")), + "}" + )); + Files.write(Paths.get(base + "/krb5.conf"), Arrays.asList( + "[libdefaults]", + "default_realm = " + realm, + "default_keytab_name = FILE:" + base + "/krb5.keytab", + "forwardable = true", + "dns_lookup_kdc = no", + "dns_lookup_realm = no", + "dns_canonicalize_hostname = false", + "\n[realms]", + realm + " = {", + " kdc = localhost:" + port, + "}", + "\n[logging]", + "kdc = FILE:" + base + "/krb5kdc.log" + )); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + @Override + void kinit(String user, String ccache) { + String tmpName = base + "/" + user + "." + + System.identityHashCode(this) + ".keytab"; + ktadd(user, tmpName); + run(true, nativePath + + (install ? "/bin/" : "/clients/kinit/") + "kinit", + "-f", "-t", tmpName, "-c", ccache, user); + } + } // Calling private methods thru reflections private static final Field getPADataField; diff --git a/test/jdk/sun/security/krb5/auto/SSLwithPerms.java b/test/jdk/sun/security/krb5/auto/SSLwithPerms.java index 0779c3dcb4..872b7b6a78 100644 --- a/test/jdk/sun/security/krb5/auto/SSLwithPerms.java +++ b/test/jdk/sun/security/krb5/auto/SSLwithPerms.java @@ -95,6 +95,7 @@ public class SSLwithPerms { .prop("javax.net.ssl", "handshake") .prop("sun.security.krb5.debug", "true") .perm(new SecurityPermission("setProperty.jdk.tls.disabledAlgorithms")) + .perm(new java.util.PropertyPermission("user.name", "read")) .perm(new PropertyPermission("sun.security.krb5.principal", "read")) .perm(new FilePermission("port", "read")) .perm(new FilePermission(hostsFileName, "read")) diff --git a/test/jdk/sun/security/krb5/auto/TEST.properties b/test/jdk/sun/security/krb5/auto/TEST.properties index fa23fd6fb0..a3941f6dc0 100644 --- a/test/jdk/sun/security/krb5/auto/TEST.properties +++ b/test/jdk/sun/security/krb5/auto/TEST.properties @@ -1,6 +1,7 @@ modules java.base/jdk.internal.misc \ java.base/sun.security.util \ java.security.jgss/sun.security.jgss \ + java.security.jgss/sun.security.jgss.krb5 \ java.security.jgss/sun.security.krb5:+open \ java.security.jgss/sun.security.krb5.internal:+open \ java.security.jgss/sun.security.krb5.internal.ccache \ -- GitLab