aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorandrew <unknown>2020-01-12 01:59:01 +0000
committerbell-sw <liberica@bell-sw.com>2020-01-19 09:13:17 +0300
commit1be5f4ecbee488823fdcc6f64709cd41bc3decc4 (patch)
treec8fdcce34683a3596d7059782ecb9135a3f8dada
parent6adb93d8f15424ed68294b65a1c716b97a622a52 (diff)
downloadjdk8u_jdk-1be5f4ecbee488823fdcc6f64709cd41bc3decc4.tar.gz
8186884: Test native KDC, Java krb5 lib, and native krb5 lib in one test
Reviewed-by: mbalao
-rw-r--r--test/java/security/testlibrary/Proc.java15
-rw-r--r--test/sun/security/krb5/auto/BasicProc.java447
-rw-r--r--test/sun/security/krb5/auto/Context.java85
-rw-r--r--test/sun/security/krb5/auto/KDC.java554
4 files changed, 844 insertions, 257 deletions
diff --git a/test/java/security/testlibrary/Proc.java b/test/java/security/testlibrary/Proc.java
index 1ab58bf521..95192cd136 100644
--- a/test/java/security/testlibrary/Proc.java
+++ b/test/java/security/testlibrary/Proc.java
@@ -235,6 +235,13 @@ public class Proc {
br = new BufferedReader(new InputStreamReader(p.getInputStream()));
return 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 {
String s = br.readLine();
@@ -303,9 +310,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/sun/security/krb5/auto/BasicProc.java b/test/sun/security/krb5/auto/BasicProc.java
index bcc3cd051b..d9d7ef97bc 100644
--- a/test/sun/security/krb5/auto/BasicProc.java
+++ b/test/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,180 +23,319 @@
/*
* @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 -Dsun.net.spi.nameservice.provider.1=ns,mock BasicProc
+ * @run main/othervm -Dsun.net.spi.nameservice.provider.1=ns,mock 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.Collections;
+import java.util.HashSet;
+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", "")
- .prop("sun.net.spi.nameservice.provider.1", "ns,mock")
- .perm(new java.lang.RuntimePermission(
- "accessClassInPackage.sun.net.spi.nameservice"))
- .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", "")
- .prop("sun.net.spi.nameservice.provider.1", "ns,mock")
- .perm(new java.lang.RuntimePermission(
- "accessClassInPackage.sun.net.spi.nameservice"))
- .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"));
+ Set<PosixFilePermission> perms = new HashSet<>();
+ perms.add(PosixFilePermission.OWNER_READ);
+ perms.add(PosixFilePermission.OWNER_WRITE);
+ Files.setPosixFilePermissions(Paths.get(label + ".ccache"),
+ Collections.unmodifiableSet(perms));
+ 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", "")
+ .prop("sun.net.spi.nameservice.provider.1", "ns,mock")
+ .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"))
- .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", "")
- .prop("sun.net.spi.nameservice.provider.1", "ns,mock")
+ // For Krb5LoginModule::login.
.perm(new java.lang.RuntimePermission(
"accessClassInPackage.sun.net.spi.nameservice"))
- .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(
- "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.net.spi.nameservice.provider.1", "ns,mock")
- .prop("sun.security.jgss.native", "true")
- .prop("javax.security.auth.useSubjectCredsOnly", "false")
- .prop("sun.security.nativegss.debug", "true");
+ return p;
}
}
diff --git a/test/sun/security/krb5/auto/Context.java b/test/sun/security/krb5/auto/Context.java
index f6646055ae..cefbaccbf0 100644
--- a/test/sun/security/krb5/auto/Context.java
+++ b/test/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
@@ -22,14 +22,21 @@
*/
import com.sun.security.auth.module.Krb5LoginModule;
-import java.security.Key;
+import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
+import java.security.Key;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
+import java.util.Set;
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;
@@ -40,6 +47,10 @@ 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 com.sun.security.jgss.ExtendedGSSContext;
import com.sun.security.jgss.InquireType;
import com.sun.security.jgss.AuthorizationDataEntry;
@@ -154,24 +165,36 @@ public class Context {
Map<String, String> map = new HashMap<>();
Map<String, Object> 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);
@@ -616,6 +653,32 @@ public class Context {
}
/**
+ * 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<KerberosTicket> 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
* @param s the acceptor
diff --git a/test/sun/security/krb5/auto/KDC.java b/test/sun/security/krb5/auto/KDC.java
index 243abe66ac..af8fca976a 100644
--- a/test/sun/security/krb5/auto/KDC.java
+++ b/test/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.net.spi.nameservice.NameService;
import sun.net.spi.nameservice.NameServiceDescriptor;
@@ -49,6 +50,11 @@ import java.util.regex.Pattern;
/**
* 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.
* <p>
* Features:
* <ol>
@@ -129,10 +135,18 @@ public class KDC {
public static final int DEFAULT_LIFETIME = 39600;
public static final int DEFAULT_RENEWTIME = 86400;
- // 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 random generator to generate random keys (including session keys)
- private static SecureRandom secureRandom = new SecureRandom();
+ // The native KDC process
+ private Process kdcProc = null;
+
+ // 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
@@ -140,14 +154,6 @@ public class KDC {
private TreeMap<String,char[]> passwords = new TreeMap<>
(String.CASE_INSENSITIVE_ORDER);
- // Alias for referrals.
- private TreeMap<String,KDC> aliasReferrals = new TreeMap<>
- (String.CASE_INSENSITIVE_ORDER);
-
- // Alias for local resolution.
- private TreeMap<String,PrincipalName> alias2Principals = new TreeMap<>
- (String.CASE_INSENSITIVE_ORDER);
-
// Non default salts. Precisely, there should be different salts for
// different etypes, pretend they are the same at the moment.
private TreeMap<String,String> salts = new TreeMap<>
@@ -159,6 +165,14 @@ public class KDC {
private TreeMap<String,byte[]> s2kparamses = new TreeMap<>
(String.CASE_INSENSITIVE_ORDER);
+ // Alias for referrals.
+ private TreeMap<String,KDC> aliasReferrals = new TreeMap<>
+ (String.CASE_INSENSITIVE_ORDER);
+
+ // Alias for local resolution.
+ private TreeMap<String,PrincipalName> alias2Principals = new TreeMap<>
+ (String.CASE_INSENSITIVE_ORDER);
+
// Realm name
private String realm;
// KDC
@@ -276,7 +290,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);
}
@@ -316,26 +331,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<String> 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();
}
/**
@@ -392,16 +419,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);
+ }
}
}
@@ -498,12 +533,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");
@@ -511,14 +545,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());
}
/**
@@ -561,6 +593,7 @@ public class KDC {
private KDC(String realm, String kdc) {
this.realm = realm;
this.kdc = kdc;
+ this.nativeKdc = null;
}
/**
@@ -568,7 +601,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);
}
/**
@@ -577,8 +612,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';
@@ -754,7 +790,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
@@ -802,13 +841,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<String> names = (List<String>)options.get(Option.ALLOW_S4U2SELF);
+ List<String> names = (List<String>)
+ 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.
@@ -829,17 +869,19 @@ public class KDC {
EncryptionKey key = generateRandomKey(e2);
// Check time, TODO
+ KerberosTime from = body.from;
KerberosTime till = body.till;
+ if (from == null || from.isZero()) {
+ from = timeAfter(0);
+ }
KerberosTime rtime = body.rtime;
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 = timeAfter(DEFAULT_LIFETIME);
}
if (rtime == null && body.kdcOptions.get(KDCOptions.RENEWABLE)) {
- rtime = new KerberosTime(
- new Date().getTime() + 1000 * DEFAULT_RENEWTIME);
+ rtime = timeAfter(DEFAULT_RENEWTIME);
}
boolean[] bFlags = new boolean[Krb5.TKT_OPTS_MAX+1];
@@ -859,7 +901,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 = timeAfter(3600 * 24 * 7);
}
if (body.kdcOptions.get(KDCOptions.PROXIABLE)) {
bFlags[Krb5.TKT_OPTS_PROXIABLE] = true;
@@ -878,7 +920,8 @@ public class KDC {
Map<String,List<String>> map = (Map<String,List<String>>)
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();
@@ -928,8 +971,8 @@ public class KDC {
key,
cname,
new TransitedEncoding(1, new byte[0]), // TODO
- new KerberosTime(new Date()),
- body.from,
+ timeAfter(0),
+ from,
till, renewTill,
body.addresses != null // always set caddr
? body.addresses
@@ -948,15 +991,15 @@ 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, timeAfter(-10))
}),
body.getNonce(), // TODO: detect replay
- new KerberosTime(new Date().getTime() + 1000 * 3600 * 24),
+ timeAfter(3600 * 24),
// Next 5 and last MUST be same with ticket
tFlags,
- new KerberosTime(new Date()),
- body.from,
+ timeAfter(0),
+ from,
till, renewTill,
service,
body.addresses != null // always set caddr
@@ -965,7 +1008,8 @@ public class KDC {
new InetAddress[]{InetAddress.getLocalHost()}),
null
);
- 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,
@@ -986,7 +1030,7 @@ public class KDC {
+ " " +ke.returnCodeMessage());
if (kerr == null) {
kerr = new KRBError(null, null, null,
- new KerberosTime(new Date()),
+ timeAfter(0),
0,
ke.returnCode(),
body.cname,
@@ -1023,16 +1067,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];
if (body.kdcOptions.get(KDCOptions.CANONICALIZE) &&
body.cname.getNameType() == PrincipalName.KRB_NT_ENTERPRISE) {
@@ -1079,8 +1118,12 @@ 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 = timeAfter(0);
+ }
if (till == null) {
throw new KrbException(Krb5.KDC_ERR_NEVER_VALID); // TODO
} else if (till.isZero()) {
@@ -1106,7 +1149,8 @@ public class KDC {
if (body.kdcOptions.get(KDCOptions.FORWARDABLE)) {
List<String> sensitives = (List<String>)
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;
@@ -1114,7 +1158,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 = timeAfter(3600 * 24 * 7);
}
if (body.kdcOptions.get(KDCOptions.PROXIABLE)) {
bFlags[Krb5.TKT_OPTS_PROXIABLE] = true;
@@ -1136,7 +1180,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()),
@@ -1146,7 +1191,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()),
};
@@ -1241,11 +1287,14 @@ public class KDC {
} else {
EncryptionKey pakey = null;
try {
- EncryptedData data = newEncryptedData(new DerValue(inPAs[0].getValue()));
+ EncryptedData data = newEncryptedData(
+ new DerValue(inPAs[0].getValue()));
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;
for (PAData pa : inPAs) {
@@ -1267,8 +1316,8 @@ public class KDC {
key,
body.cname,
new TransitedEncoding(1, new byte[0]),
- new KerberosTime(new Date()),
- body.from,
+ timeAfter(0),
+ from,
till, rtime,
body.addresses,
null);
@@ -1279,20 +1328,21 @@ 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, timeAfter(-10))
}),
body.getNonce(), // TODO: detect replay?
- new KerberosTime(new Date().getTime() + 1000 * 3600 * 24),
+ timeAfter(3600 * 24),
// Next 5 and last MUST be same with ticket
tFlags,
- new KerberosTime(new Date()),
- body.from,
+ timeAfter(0),
+ from,
till, rtime,
service,
body.addresses,
enc_outPAs.toArray(new PAData[enc_outPAs.size()])
);
- 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,
@@ -1349,7 +1399,7 @@ public class KDC {
eData = temp.toByteArray();
}
kerr = new KRBError(null, null, null,
- new KerberosTime(new Date()),
+ timeAfter(0),
0,
ke.returnCode(),
body.cname,
@@ -1427,6 +1477,35 @@ public class KDC {
throw new KrbException("Illegal duration format " + s);
}
+ 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 }
@@ -1451,6 +1530,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);
@@ -1543,19 +1636,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
+ }
}
}
@@ -1710,6 +1821,269 @@ 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<String,String> 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<String,String> 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);
+ Map<String, String> environment = new HashMap<>();
+ environment.put("KRB5_CONFIG", base + "/krb5.conf");
+ environment.put("KRB5_TRACE", "/dev/stderr");
+ environment.put("DYLD_LIBRARY_PATH", nativePath + "/lib");
+ environment.put("LD_LIBRARY_PATH", nativePath + "/lib");
+ this.env = Collections.unmodifiableMap(environment);
+ }
+
+ @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;
+ Map<String, String> environment = new HashMap<>();
+ environment.put("KRB5_KDC_PROFILE", base + "/kdc.conf");
+ environment.put("KRB5_CONFIG", base + "/krb5.conf");
+ environment.put("KRB5_TRACE", "/dev/stderr");
+ environment.put("DYLD_LIBRARY_PATH", nativePath + "/lib");
+ environment.put("LD_LIBRARY_PATH", nativePath + "/lib");
+ this.env = Collections.unmodifiableMap(environment);
+ }
+
+ @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;
private static final Field getEType;