changeset 9894:368b85934a10

8186884: Test native KDC, Java krb5 lib, and native krb5 lib in one test Reviewed-by: mbalao
author andrew
date Mon, 03 Feb 2020 04:44:21 +0000
parents c8c25cfc00ec
children a0c0ea0b736f
files test/java/security/testlibrary/Proc.java test/sun/security/krb5/auto/BasicProc.java test/sun/security/krb5/auto/Context.java test/sun/security/krb5/auto/KDC.java
diffstat 4 files changed, 842 insertions(+), 241 deletions(-) [+]
line wrap: on
line diff
--- a/test/java/security/testlibrary/Proc.java	Mon Feb 03 03:52:08 2020 +0000
+++ b/test/java/security/testlibrary/Proc.java	Mon Feb 03 04:44:21 2020 +0000
@@ -237,6 +237,13 @@
         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();
@@ -305,9 +312,13 @@
         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);
--- a/test/sun/security/krb5/auto/BasicProc.java	Mon Feb 03 03:52:08 2020 +0000
+++ b/test/sun/security/krb5/auto/BasicProc.java	Mon Feb 03 04:44:21 2020 +0000
@@ -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);
+        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]);
+                    }
 
-            String cwd = System.getProperty("user.dir");
-            kdc.writeKtab(KTAB);
-            KDC.saveConfig(CONF, kdc, "forwardable = true");
+                    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.create("BasicProc")
-                    .args("client")
-                    .prop("java.security.krb5.conf", CONF)
-                    .prop("java.security.manager", "")
-                    .prop("sun.net.spi.nameservice.provider.1", "ns,mock")
+        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"))
+                            // 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 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(
-                            "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")
-                    .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;
     }
 }
--- a/test/sun/security/krb5/auto/Context.java	Mon Feb 03 03:52:08 2020 +0000
+++ b/test/sun/security/krb5/auto/Context.java	Mon Feb 03 04:44:21 2020 +0000
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2008, 2011, 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,13 +22,20 @@
  */
 
 import com.sun.security.auth.module.Krb5LoginModule;
+import java.io.IOException;
 import java.security.Key;
 import java.security.PrivilegedActionException;
 import java.security.PrivilegedExceptionAction;
 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;
@@ -39,6 +46,10 @@
 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;
@@ -127,7 +138,8 @@
      * @param storeKey true if key should be saved, used on acceptor side
      */
     public static Context fromUserPass(Subject s,
-            String user, char[] pass, boolean storeKey) throws Exception {
+            final String user, final char[] pass,
+            boolean storeKey) throws Exception {
         Context out = new Context();
         out.name = user;
         out.s = s == null ? new Subject() : s;
@@ -135,24 +147,36 @@
         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;
     }
 
@@ -477,9 +501,23 @@
      * @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);
@@ -535,6 +573,32 @@
     }
 
     /**
+     * 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
--- a/test/sun/security/krb5/auto/KDC.java	Mon Feb 03 03:52:08 2020 +0000
+++ b/test/sun/security/krb5/auto/KDC.java	Mon Feb 03 04:44:21 2020 +0000
@@ -27,7 +27,9 @@
 import java.net.*;
 import java.io.*;
 import java.lang.reflect.Method;
-import java.security.SecureRandom;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Paths;
 import java.util.*;
 import java.util.concurrent.*;
 
@@ -47,6 +49,11 @@
 
 /**
  * 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>
@@ -127,10 +134,18 @@
     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 random generator to generate random keys (including session keys)
-    private static SecureRandom secureRandom = new SecureRandom();
+    // The native KDC
+    private final NativeKdc nativeKdc;
+
+    // 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
@@ -245,7 +260,8 @@
      * @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);
     }
 
@@ -285,24 +301,37 @@
      */
     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;
             }
-            ktab.addEntry(new PrincipalName(name,
-                    name.indexOf('/') < 0 ?
-                        PrincipalName.KRB_NT_UNKNOWN :
-                        PrincipalName.KRB_NT_SRV_HST),
-                        pass,
-                        kvno,
-                        true);
+            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,
+                              pass,
+                              kvno,
+                              true);
+            } else {
+                nativeKdc.ktadd(name, tab);
+            }
         }
-        ktab.save();
+        if (nativeKdc == null) {
+            ktab.save();
+        }
     }
 
     /**
@@ -359,16 +388,24 @@
      * @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);
+            }
         }
     }
 
@@ -465,12 +502,11 @@
      */
     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");
@@ -478,14 +514,12 @@
         }
         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());
     }
 
     /**
@@ -505,6 +539,7 @@
     private KDC(String realm, String kdc) {
         this.realm = realm;
         this.kdc = kdc;
+        this.nativeKdc = null;
     }
 
     /**
@@ -512,7 +547,9 @@
      */
     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);
     }
     /**
@@ -521,8 +558,9 @@
      */
     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';
@@ -698,7 +736,10 @@
                     " sends TGS-REQ for " +
                     service);
             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
 
@@ -734,11 +775,19 @@
             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 * 3600 * 11);
+                till = timeAfter(DEFAULT_LIFETIME);
+            }
+            if (rtime == null && body.kdcOptions.get(KDCOptions.RENEWABLE)) {
+                rtime = timeAfter(DEFAULT_RENEWTIME);
             }
 
             boolean[] bFlags = new boolean[Krb5.TKT_OPTS_MAX+1];
@@ -751,7 +800,7 @@
             }
             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;
@@ -789,8 +838,8 @@
                     key,
                     etp.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
@@ -809,15 +858,15 @@
             );
             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
@@ -825,7 +874,8 @@
                             : new HostAddresses(
                                 new InetAddress[]{InetAddress.getLocalHost()})
                     );
-            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,
                     etp.cname,
                     t,
@@ -845,7 +895,7 @@
                     + " " +ke.returnCodeMessage());
             if (kerr == null) {
                 kerr = new KRBError(null, null, null,
-                        new KerberosTime(new Date()),
+                        timeAfter(0),
                         0,
                         ke.returnCode(),
                         body.cname,
@@ -881,17 +931,12 @@
 
             KDCReqBody body = asReq.reqBody;
 
-            eTypes = KDCReqBodyDotEType(body);
+            eTypes = filterSupported(KDCReqBodyDotEType(body));
+            if (eTypes.length == 0) {
+                throw new KrbException(Krb5.KDC_ERR_ETYPE_NOSUPP);
+            }
             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];
-            }
-
             EncryptionKey ckey = keyForUser(body.cname, eType, false);
             EncryptionKey skey = keyForUser(service, eType, true);
 
@@ -919,8 +964,12 @@
             // 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()) {
@@ -946,7 +995,7 @@
             }
             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;
@@ -968,7 +1017,8 @@
                         pas2 = new DerValue[] {
                             new DerValue(new ETypeInfo2(1, null, null).asn1Encode()),
                             new DerValue(new ETypeInfo2(1, "", null).asn1Encode()),
-                            new DerValue(new ETypeInfo2(1, OneKDC.REALM, new byte[]{1}).asn1Encode()),
+                            new DerValue(new ETypeInfo2(
+                                    1, OneKDC.REALM, new byte[]{1}).asn1Encode()),
                         };
                         pas = new DerValue[] {
                             new DerValue(new ETypeInfo(1, null).asn1Encode()),
@@ -1071,11 +1121,14 @@
                 }
             } else {
                 try {
-                    EncryptedData data = newEncryptedData(new DerValue(inPAs[0].getValue()));
+                    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;
             }
@@ -1086,8 +1139,8 @@
                     key,
                     body.cname,
                     new TransitedEncoding(1, new byte[0]),
-                    new KerberosTime(new Date()),
-                    body.from,
+                    timeAfter(0),
+                    from,
                     till, rtime,
                     body.addresses,
                     null);
@@ -1098,19 +1151,20 @@
             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
                     );
-            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,
@@ -1164,7 +1218,7 @@
                     eData = temp.toByteArray();
                 }
                 kerr = new KRBError(null, null, null,
-                        new KerberosTime(new Date()),
+                        timeAfter(0),
                         0,
                         ke.returnCode(),
                         body.cname,
@@ -1242,6 +1296,35 @@
         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 }
@@ -1266,6 +1349,20 @@
      * @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);
@@ -1346,15 +1443,33 @@
         thread3.start();
     }
 
+    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);
+        }
+    }
+
     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.destroy();
+            System.out.println("Done");
+        } else {
+            try {
+                thread1.stop();
+                thread2.stop();
+                thread3.stop();
+                u1.close();
+                t1.close();
+            } catch (Exception e) {
+                // OK
+            }
         }
     }
 
@@ -1511,6 +1626,278 @@
         }
     }
 
+    /**
+     * 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) {
+            StringBuilder sb = new StringBuilder();
+            for (Map.Entry<String,String> e : env.entrySet()) {
+                sb.append(e.getKey()+"="+e.getValue());
+                sb.append(" ");
+            }
+            for (int a = 0; a < cmd.length; ++a) {
+                sb.append(cmd);
+                if (a < (cmd.length - 1)) {
+                    sb.append(" ");
+                }
+            }
+            return sb.toString();
+        }
+    }
+
+    // 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"
+                ), StandardCharsets.UTF_8);
+            } catch (IOException e) {
+                throw new RuntimeException(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 ")),
+                        "}"
+                ), StandardCharsets.UTF_8);
+                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"
+                ), StandardCharsets.UTF_8);
+            } catch (IOException e) {
+                throw new RuntimeException(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;