提交 30f766b9 编写于 作者: A andrew

8186884: Test native KDC, Java krb5 lib, and native krb5 lib in one test

Reviewed-by: mbalao
上级 15b81d15
...@@ -235,6 +235,13 @@ public class Proc { ...@@ -235,6 +235,13 @@ public class Proc {
br = new BufferedReader(new InputStreamReader(p.getInputStream())); br = new BufferedReader(new InputStreamReader(p.getInputStream()));
return this; 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 // Reads a line from stdout of proc
public String readLine() throws IOException { public String readLine() throws IOException {
String s = br.readLine(); String s = br.readLine();
...@@ -303,9 +310,13 @@ public class Proc { ...@@ -303,9 +310,13 @@ public class Proc {
boolean isEmpty = true; boolean isEmpty = true;
while (true) { while (true) {
int i = System.in.read(); int i = System.in.read();
if (i == -1) break; if (i == -1) {
break;
}
isEmpty = false; isEmpty = false;
if (i == '\n') break; if (i == '\n') {
break;
}
if (i != 13) { if (i != 13) {
// Force it to a char, so only simple ASCII works. // Force it to a char, so only simple ASCII works.
sb.append((char)i); sb.append((char)i);
......
/* /*
* 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. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
...@@ -23,180 +23,319 @@ ...@@ -23,180 +23,319 @@
/* /*
* @test * @test
* @bug 8009977 * @bug 8009977 8186884
* @summary A test library to launch multiple Java processes * @summary A test to launch multiple Java processes using either Java GSS
* or native GSS
* @library ../../../../java/security/testlibrary/ * @library ../../../../java/security/testlibrary/
* @compile -XDignore.symbol.file BasicProc.java * @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 org.ietf.jgss.Oid;
import sun.security.krb5.Config;
import javax.security.auth.PrivateCredentialPermission; 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 { public class BasicProc {
static String CONF = "krb5.conf"; private static final String CONF = "krb5.conf";
static String KTAB = "ktab"; 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 { 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"); Oid oid = new Oid("1.2.840.113554.1.2.2");
byte[] token, msg;
if (args.length == 0) { switch (args[0]) {
System.setProperty("java.security.krb5.conf", CONF); case "launcher":
KDC kdc = KDC.create(REALM, HOST, 0, true); KDC kdc = KDC.create(REALM, HOST, 0, true);
try {
kdc.addPrincipal(USER, PASS); kdc.addPrincipal(USER, PASS);
kdc.addPrincipalRandKey("krbtgt/" + REALM); kdc.addPrincipalRandKey("krbtgt/" + REALM);
kdc.addPrincipalRandKey(SERVER); kdc.addPrincipalRandKey(SERVER);
kdc.addPrincipalRandKey(BACKEND); kdc.addPrincipalRandKey(BACKEND);
String cwd = System.getProperty("user.dir"); // Native lib might do some name lookup
kdc.writeKtab(KTAB); KDC.saveConfig(CONF, kdc,
KDC.saveConfig(CONF, kdc, "forwardable = true"); "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);
Proc pc = Proc.create("BasicProc") String[] tmp = System.getProperty("native.krb5.libs", "j=")
.args("client") .split(",");
.prop("java.security.krb5.conf", CONF)
.prop("java.security.manager", "") // Library paths. The 1st one is always null which means
.prop("sun.net.spi.nameservice.provider.1", "ns,mock") // Java, "" means the default native lib.
.perm(new java.lang.RuntimePermission( String[] libs = new String[tmp.length];
"accessClassInPackage.sun.net.spi.nameservice"))
.perm(new java.util.PropertyPermission( // Names for each lib above. Use in file names.
"sun.security.krb5.principal", "read")) String[] names = new String[tmp.length];
.perm(new javax.security.auth.AuthPermission(
"modifyPrincipals")) boolean hasNative = false;
.perm(new javax.security.auth.AuthPermission(
"modifyPrivateCredentials")) for (int i = 0; i < tmp.length; i++) {
.perm(new javax.security.auth.AuthPermission("doAs")) 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( .perm(new javax.security.auth.kerberos.ServicePermission(
"krbtgt/" + REALM + "@" + REALM, "initiate")) "krbtgt/" + REALM + "@" + REALM, "initiate"))
.perm(new javax.security.auth.kerberos.ServicePermission( .perm(new javax.security.auth.kerberos.ServicePermission(
"server/localhost@" + REALM, "initiate")) SERVER + "@" + REALM, "initiate"))
.perm(new javax.security.auth.kerberos.DelegationPermission( .perm(new javax.security.auth.kerberos.DelegationPermission(
"\"server/localhost@" + REALM + "\" " + "\"" + SERVER + "@" + REALM + "\" " +
"\"krbtgt/" + REALM + "@" + REALM + "\"")) "\"krbtgt/" + REALM + "@" + REALM + "\""))
.debug("C") .debug(label + "-C");
.start(); if (lc == null) {
Proc ps = Proc.create("BasicProc") // for Krb5LoginModule::promptForName
.args("server") pc.perm(new PropertyPermission("user.name", "read"));
.prop("java.security.krb5.conf", CONF) } else {
.prop("java.security.manager", "") Files.copy(Paths.get("base.ccache"), Paths.get(label + ".ccache"));
.prop("sun.net.spi.nameservice.provider.1", "ns,mock") Set<PosixFilePermission> perms = new HashSet<>();
.perm(new java.lang.RuntimePermission( perms.add(PosixFilePermission.OWNER_READ);
"accessClassInPackage.sun.net.spi.nameservice")) perms.add(PosixFilePermission.OWNER_WRITE);
.perm(new java.util.PropertyPermission( Files.setPosixFilePermissions(Paths.get(label + ".ccache"),
"sun.security.krb5.principal", "read")) Collections.unmodifiableSet(perms));
.perm(new javax.security.auth.AuthPermission( pc.env("KRB5CCNAME", label + ".ccache");
"modifyPrincipals")) // Do not try system ktab if ccache fails
.perm(new javax.security.auth.AuthPermission( pc.env("KRB5_KTNAME", "none");
"modifyPrivateCredentials")) }
.perm(new javax.security.auth.AuthPermission("doAs")) pc.start();
.perm(new PrivateCredentialPermission(
"javax.security.auth.kerberos.KeyTab * \"*\"", Proc ps = proc(ls)
"read")) .args("server", ls == null ? "j" : "n")
.perm(new javax.security.auth.kerberos.ServicePermission( .perm(new javax.security.auth.kerberos.ServicePermission(
"server/localhost@" + REALM, "accept")) SERVER + "@" + REALM, "accept"))
.perm(new java.io.FilePermission(
cwd + File.separator + KTAB, "read"))
.perm(new javax.security.auth.kerberos.ServicePermission( .perm(new javax.security.auth.kerberos.ServicePermission(
"backend/localhost@" + REALM, "initiate")) BACKEND + "@" + REALM, "initiate"))
.debug("S") .debug(label + "-S");
.start(); if (ls == null) {
Proc pb = Proc.create("BasicProc") ps.perm(new PrivateCredentialPermission(
.args("backend") "javax.security.auth.kerberos.KeyTab * \"*\"", "read"))
.prop("java.security.krb5.conf", CONF) .perm(new java.io.FilePermission(KTAB_S, "read"));
.prop("java.security.manager", "") } else {
.prop("sun.net.spi.nameservice.provider.1", "ns,mock") ps.env("KRB5_KTNAME", KTAB_S);
.perm(new java.lang.RuntimePermission( }
"accessClassInPackage.sun.net.spi.nameservice")) ps.start();
.perm(new java.util.PropertyPermission(
"sun.security.krb5.principal", "read")) Proc pb = proc(lb)
.perm(new javax.security.auth.AuthPermission( .args("backend", lb == null ? "j" : "n")
"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( .perm(new javax.security.auth.kerberos.ServicePermission(
"backend/localhost@" + REALM, "accept")) BACKEND + "@" + REALM, "accept"))
.perm(new java.io.FilePermission( .debug(label + "-B");
cwd + File.separator + KTAB, "read")) if (lb == null) {
.debug("B") pb.perm(new PrivateCredentialPermission(
.start(); "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 // Client and server handshake
String token = pc.readData(); ps.println(pc.readData());
ps.println(token); pc.println(ps.readData());
token = ps.readData();
pc.println(token);
// Server and backend handshake // Server and backend handshake
token = ps.readData(); pb.println(ps.readData());
pb.println(token); ps.println(pb.readData());
token = pb.readData();
ps.println(token);
// wrap/unwrap/getMic/verifyMic and plain text // wrap/unwrap/getMic/verifyMic and plain text
token = ps.readData(); pb.println(ps.readData());
pb.println(token); pb.println(ps.readData());
token = pb.readData(); pb.println(ps.readData());
ps.println(token); ps.println(pb.readData());
token = pb.readData(); ps.println(pb.readData());
ps.println(token);
if ((pc.waitFor() | ps.waitFor() | pb.waitFor()) != 0) { if ((pc.waitFor() | ps.waitFor() | pb.waitFor()) != 0) {
throw new Exception(); throw new Exception("Process failed");
} }
} else if (args[0].equals("client")) { }
Context c = Context.fromUserPass(USER, PASS, false);
c.startAsClient(SERVER, oid); /**
c.x().requestCredDeleg(true); * A Proc for a child process.
Proc.binOut(c.take(new byte[0])); *
byte[] token = Proc.binIn(); * @param lib the library. Null is Java. "" is default native lib.
c.take(token); */
} else if (args[0].equals("server")) { private static Proc proc(String lib) throws Exception {
Context s = Context.fromUserKtab(SERVER, KTAB, true); Proc p = Proc.create("BasicProc")
s.startAsServer(oid); .prop("java.security.manager", "")
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));
}
}
// 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.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.native", "true")
.prop("sun.security.jgss.lib", lib)
.prop("javax.security.auth.useSubjectCredsOnly", "false") .prop("javax.security.auth.useSubjectCredsOnly", "false")
.prop("sun.security.nativegss.debug", "true"); .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 javax.security.auth.AuthPermission(
"modifyPrincipals"))
.perm(new javax.security.auth.AuthPermission(
"modifyPrivateCredentials"))
.prop("sun.security.krb5.debug", "true")
.prop("java.security.krb5.conf", CONF);
}
return p;
} }
} }
/* /*
* 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. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
...@@ -22,14 +22,21 @@ ...@@ -22,14 +22,21 @@
*/ */
import com.sun.security.auth.module.Krb5LoginModule; import com.sun.security.auth.module.Krb5LoginModule;
import java.security.Key; import java.io.IOException;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;
import java.security.PrivilegedActionException; import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction; import java.security.PrivilegedExceptionAction;
import java.security.Key;
import java.util.Arrays; import java.util.Arrays;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.Set;
import javax.security.auth.Subject; 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.KerberosKey;
import javax.security.auth.kerberos.KerberosTicket; import javax.security.auth.kerberos.KerberosTicket;
import javax.security.auth.login.LoginContext; import javax.security.auth.login.LoginContext;
...@@ -40,6 +47,10 @@ import org.ietf.jgss.GSSManager; ...@@ -40,6 +47,10 @@ import org.ietf.jgss.GSSManager;
import org.ietf.jgss.GSSName; import org.ietf.jgss.GSSName;
import org.ietf.jgss.MessageProp; import org.ietf.jgss.MessageProp;
import org.ietf.jgss.Oid; 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.ExtendedGSSContext;
import com.sun.security.jgss.InquireType; import com.sun.security.jgss.InquireType;
import com.sun.security.jgss.AuthorizationDataEntry; import com.sun.security.jgss.AuthorizationDataEntry;
...@@ -154,24 +165,36 @@ public class Context { ...@@ -154,24 +165,36 @@ public class Context {
Map<String, String> map = new HashMap<>(); Map<String, String> map = new HashMap<>();
Map<String, Object> shared = new HashMap<>(); Map<String, Object> shared = new HashMap<>();
if (storeKey) {
map.put("storeKey", "true");
}
if (pass != null) { if (pass != null) {
map.put("useFirstPass", "true"); krb5.initialize(out.s, new CallbackHandler() {
shared.put("javax.security.auth.login.name", user); @Override
shared.put("javax.security.auth.login.password", pass); 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 { } else {
map.put("doNotPrompt", "true"); map.put("doNotPrompt", "true");
map.put("useTicketCache", "true"); map.put("useTicketCache", "true");
if (user != null) { if (user != null) {
map.put("principal", user); map.put("principal", user);
} }
} krb5.initialize(out.s, null, shared, map);
if (storeKey) {
map.put("storeKey", "true");
} }
krb5.initialize(out.s, null, shared, map);
krb5.login(); krb5.login();
krb5.commit(); krb5.commit();
return out; return out;
} }
...@@ -529,9 +552,23 @@ public class Context { ...@@ -529,9 +552,23 @@ public class Context {
* @param s2 the receiver * @param s2 the receiver
* @throws java.lang.Exception If anything goes wrong * @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 Context s2) throws Exception {
final byte[] messageBytes = message.getBytes();
System.out.printf("-------------------- TRANSMIT from %s to %s------------------------\n", System.out.printf("-------------------- TRANSMIT from %s to %s------------------------\n",
s1.name, s2.name); s1.name, s2.name);
byte[] wrapped = s1.wrap(messageBytes, true); byte[] wrapped = s1.wrap(messageBytes, true);
...@@ -615,6 +652,32 @@ public class Context { ...@@ -615,6 +652,32 @@ public class Context {
}, in); }, in);
} }
/**
* Saves the tickets to a ccache file.
*
* @param file pathname of the ccache file
* @return true if created, false otherwise.
*/
public boolean ccache(String file) throws Exception {
Set<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 * Handshake (security context establishment process) between two Contexts
* @param c the initiator * @param c the initiator
......
...@@ -27,11 +27,12 @@ import java.lang.reflect.InvocationTargetException; ...@@ -27,11 +27,12 @@ import java.lang.reflect.InvocationTargetException;
import java.net.*; import java.net.*;
import java.io.*; import java.io.*;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.security.SecureRandom; import java.nio.file.Files;
import java.time.Instant; import java.nio.file.Paths;
import java.time.temporal.ChronoUnit;
import java.util.*; import java.util.*;
import java.util.concurrent.*; 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.NameService;
import sun.net.spi.nameservice.NameServiceDescriptor; import sun.net.spi.nameservice.NameServiceDescriptor;
...@@ -49,6 +50,11 @@ import java.util.regex.Pattern; ...@@ -49,6 +50,11 @@ import java.util.regex.Pattern;
/** /**
* A KDC server. * 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> * <p>
* Features: * Features:
* <ol> * <ol>
...@@ -129,10 +135,18 @@ public class KDC { ...@@ -129,10 +135,18 @@ public class KDC {
public static final int DEFAULT_LIFETIME = 39600; public static final int DEFAULT_LIFETIME = 39600;
public static final int DEFAULT_RENEWTIME = 86400; 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) // The native KDC process
private static SecureRandom secureRandom = new SecureRandom(); private Process kdcProc = null;
// Under the hood.
// Principal db. principal -> pass. A case-insensitive TreeMap is used // Principal db. principal -> pass. A case-insensitive TreeMap is used
// so that even if the client provides a name with different case, the KDC // so that even if the client provides a name with different case, the KDC
...@@ -140,14 +154,6 @@ public class KDC { ...@@ -140,14 +154,6 @@ public class KDC {
private TreeMap<String,char[]> passwords = new TreeMap<> private TreeMap<String,char[]> passwords = new TreeMap<>
(String.CASE_INSENSITIVE_ORDER); (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 // Non default salts. Precisely, there should be different salts for
// different etypes, pretend they are the same at the moment. // different etypes, pretend they are the same at the moment.
private TreeMap<String,String> salts = new TreeMap<> private TreeMap<String,String> salts = new TreeMap<>
...@@ -159,6 +165,14 @@ public class KDC { ...@@ -159,6 +165,14 @@ public class KDC {
private TreeMap<String,byte[]> s2kparamses = new TreeMap<> private TreeMap<String,byte[]> s2kparamses = new TreeMap<>
(String.CASE_INSENSITIVE_ORDER); (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 // Realm name
private String realm; private String realm;
// KDC // KDC
...@@ -276,7 +290,8 @@ public class KDC { ...@@ -276,7 +290,8 @@ public class KDC {
* @return the running KDC instance * @return the running KDC instance
* @throws java.io.IOException for any socket creation error * @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); return new KDC(realm, kdc, port, asDaemon);
} }
...@@ -316,14 +331,21 @@ public class KDC { ...@@ -316,14 +331,21 @@ public class KDC {
*/ */
public void writeKtab(String tab, boolean append, String... names) public void writeKtab(String tab, boolean append, String... names)
throws IOException, KrbException { 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 = Iterable<String> entries =
(names.length != 0) ? Arrays.asList(names): passwords.keySet(); (names.length != 0) ? Arrays.asList(names): passwords.keySet();
for (String name : entries) { for (String name : entries) {
if (name.indexOf('@') < 0) {
name = name + "@" + realm;
}
if (nativeKdc == null) {
char[] pass = passwords.get(name); char[] pass = passwords.get(name);
int kvno = 0; int kvno = 0;
if (Character.isDigit(pass[pass.length-1])) { if (Character.isDigit(pass[pass.length - 1])) {
kvno = pass[pass.length-1] - '0'; kvno = pass[pass.length - 1] - '0';
} }
PrincipalName pn = new PrincipalName(name, PrincipalName pn = new PrincipalName(name,
name.indexOf('/') < 0 ? name.indexOf('/') < 0 ?
...@@ -334,9 +356,14 @@ public class KDC { ...@@ -334,9 +356,14 @@ public class KDC {
pass, pass,
kvno, kvno,
true); true);
} else {
nativeKdc.ktadd(name, tab);
}
} }
if (nativeKdc == null) {
ktab.save(); ktab.save();
} }
}
/** /**
* Writes all principals' keys from multiple KDCs into one keytab file. * Writes all principals' keys from multiple KDCs into one keytab file.
...@@ -392,10 +419,17 @@ public class KDC { ...@@ -392,10 +419,17 @@ public class KDC {
* @param salt the salt, or null if a default value will be used * @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 * @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) { if (user.indexOf('@') < 0) {
user = user + "@" + realm; user = user + "@" + realm;
} }
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); passwords.put(user, pass);
if (salt != null) { if (salt != null) {
salts.put(user, salt); salts.put(user, salt);
...@@ -404,6 +438,7 @@ public class KDC { ...@@ -404,6 +438,7 @@ public class KDC {
s2kparamses.put(user, s2kparams); s2kparamses.put(user, s2kparams);
} }
} }
}
/** /**
* Adds a new principal to this realm with a random password * Adds a new principal to this realm with a random password
...@@ -498,12 +533,11 @@ public class KDC { ...@@ -498,12 +533,11 @@ public class KDC {
*/ */
public static void saveConfig(String file, KDC kdc, Object... more) public static void saveConfig(String file, KDC kdc, Object... more)
throws IOException { throws IOException {
File f = new File(file);
StringBuffer sb = new StringBuffer(); StringBuffer sb = new StringBuffer();
sb.append("[libdefaults]\ndefault_realm = "); sb.append("[libdefaults]\ndefault_realm = ");
sb.append(kdc.realm); sb.append(kdc.realm);
sb.append("\n"); sb.append("\n");
for (Object o: more) { for (Object o : more) {
if (o instanceof String) { if (o instanceof String) {
sb.append(o); sb.append(o);
sb.append("\n"); sb.append("\n");
...@@ -511,14 +545,12 @@ public class KDC { ...@@ -511,14 +545,12 @@ public class KDC {
} }
sb.append("\n[realms]\n"); sb.append("\n[realms]\n");
sb.append(kdc.realmLine()); sb.append(kdc.realmLine());
for (Object o: more) { for (Object o : more) {
if (o instanceof KDC) { if (o instanceof KDC) {
sb.append(((KDC)o).realmLine()); sb.append(((KDC) o).realmLine());
} }
} }
FileOutputStream fos = new FileOutputStream(f); Files.write(Paths.get(file), sb.toString().getBytes());
fos.write(sb.toString().getBytes());
fos.close();
} }
/** /**
...@@ -561,6 +593,7 @@ public class KDC { ...@@ -561,6 +593,7 @@ public class KDC {
private KDC(String realm, String kdc) { private KDC(String realm, String kdc) {
this.realm = realm; this.realm = realm;
this.kdc = kdc; this.kdc = kdc;
this.nativeKdc = null;
} }
/** /**
...@@ -568,7 +601,9 @@ public class KDC { ...@@ -568,7 +601,9 @@ public class KDC {
*/ */
protected KDC(String realm, String kdc, int port, boolean asDaemon) protected KDC(String realm, String kdc, int port, boolean asDaemon)
throws IOException { throws IOException {
this(realm, kdc); this.realm = realm;
this.kdc = kdc;
this.nativeKdc = NativeKdc.get(this);
startServer(port, asDaemon); startServer(port, asDaemon);
} }
/** /**
...@@ -577,8 +612,9 @@ public class KDC { ...@@ -577,8 +612,9 @@ public class KDC {
*/ */
private static char[] randomPassword() { private static char[] randomPassword() {
char[] pass = new char[32]; char[] pass = new char[32];
Random r = new Random();
for (int i=0; i<31; i++) 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() // The last char cannot be a number, otherwise, keyForUser()
// believes it's a sign of kvno // believes it's a sign of kvno
pass[31] = 'Z'; pass[31] = 'Z';
...@@ -754,7 +790,10 @@ public class KDC { ...@@ -754,7 +790,10 @@ public class KDC {
" sends TGS-REQ for " + " sends TGS-REQ for " +
service + ", " + tgsReq.reqBody.kdcOptions); service + ", " + tgsReq.reqBody.kdcOptions);
KDCReqBody body = tgsReq.reqBody; 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 e2 = eTypes[0]; // etype for outgoing session key
int e3 = eTypes[0]; // etype for outgoing ticket int e3 = eTypes[0]; // etype for outgoing ticket
...@@ -802,13 +841,14 @@ public class KDC { ...@@ -802,13 +841,14 @@ public class KDC {
PAForUserEnc p4u = new PAForUserEnc( PAForUserEnc p4u = new PAForUserEnc(
new DerValue(pa.getValue()), null); new DerValue(pa.getValue()), null);
forUserCName = p4u.name; 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); + " in the name of " + p4u.name);
} }
} }
} }
if (forUserCName != null) { 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())) { if (!names.contains(cname.toString())) {
// Mimic the normal KDC behavior. When a server is not // Mimic the normal KDC behavior. When a server is not
// allowed to send S4U2self, do not send an error. // allowed to send S4U2self, do not send an error.
...@@ -829,17 +869,19 @@ public class KDC { ...@@ -829,17 +869,19 @@ public class KDC {
EncryptionKey key = generateRandomKey(e2); EncryptionKey key = generateRandomKey(e2);
// Check time, TODO // Check time, TODO
KerberosTime from = body.from;
KerberosTime till = body.till; KerberosTime till = body.till;
if (from == null || from.isZero()) {
from = timeAfter(0);
}
KerberosTime rtime = body.rtime; KerberosTime rtime = body.rtime;
if (till == null) { if (till == null) {
throw new KrbException(Krb5.KDC_ERR_NEVER_VALID); // TODO throw new KrbException(Krb5.KDC_ERR_NEVER_VALID); // TODO
} else if (till.isZero()) { } else if (till.isZero()) {
till = new KerberosTime( till = timeAfter(DEFAULT_LIFETIME);
new Date().getTime() + 1000 * DEFAULT_LIFETIME);
} }
if (rtime == null && body.kdcOptions.get(KDCOptions.RENEWABLE)) { if (rtime == null && body.kdcOptions.get(KDCOptions.RENEWABLE)) {
rtime = new KerberosTime( rtime = timeAfter(DEFAULT_RENEWTIME);
new Date().getTime() + 1000 * DEFAULT_RENEWTIME);
} }
boolean[] bFlags = new boolean[Krb5.TKT_OPTS_MAX+1]; boolean[] bFlags = new boolean[Krb5.TKT_OPTS_MAX+1];
...@@ -859,7 +901,7 @@ public class KDC { ...@@ -859,7 +901,7 @@ public class KDC {
} }
if (body.kdcOptions.get(KDCOptions.RENEWABLE)) { if (body.kdcOptions.get(KDCOptions.RENEWABLE)) {
bFlags[Krb5.TKT_OPTS_RENEWABLE] = true; 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)) { if (body.kdcOptions.get(KDCOptions.PROXIABLE)) {
bFlags[Krb5.TKT_OPTS_PROXIABLE] = true; bFlags[Krb5.TKT_OPTS_PROXIABLE] = true;
...@@ -878,7 +920,8 @@ public class KDC { ...@@ -878,7 +920,8 @@ public class KDC {
Map<String,List<String>> map = (Map<String,List<String>>) Map<String,List<String>> map = (Map<String,List<String>>)
options.get(Option.ALLOW_S4U2PROXY); options.get(Option.ALLOW_S4U2PROXY);
Ticket second = KDCReqBodyDotFirstAdditionalTicket(body); 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); byte[] bb = second.encPart.decrypt(key2, KeyUsage.KU_TICKET);
DerInputStream derIn = new DerInputStream(bb); DerInputStream derIn = new DerInputStream(bb);
DerValue der = derIn.getDerValue(); DerValue der = derIn.getDerValue();
...@@ -928,8 +971,8 @@ public class KDC { ...@@ -928,8 +971,8 @@ public class KDC {
key, key,
cname, cname,
new TransitedEncoding(1, new byte[0]), // TODO new TransitedEncoding(1, new byte[0]), // TODO
new KerberosTime(new Date()), timeAfter(0),
body.from, from,
till, renewTill, till, renewTill,
body.addresses != null // always set caddr body.addresses != null // always set caddr
? body.addresses ? body.addresses
...@@ -948,15 +991,15 @@ public class KDC { ...@@ -948,15 +991,15 @@ public class KDC {
); );
EncTGSRepPart enc_part = new EncTGSRepPart( EncTGSRepPart enc_part = new EncTGSRepPart(
key, key,
new LastReq(new LastReqEntry[]{ new LastReq(new LastReqEntry[] {
new LastReqEntry(0, new KerberosTime(new Date().getTime() - 10000)) new LastReqEntry(0, timeAfter(-10))
}), }),
body.getNonce(), // TODO: detect replay 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 // Next 5 and last MUST be same with ticket
tFlags, tFlags,
new KerberosTime(new Date()), timeAfter(0),
body.from, from,
till, renewTill, till, renewTill,
service, service,
body.addresses != null // always set caddr body.addresses != null // always set caddr
...@@ -965,7 +1008,8 @@ public class KDC { ...@@ -965,7 +1008,8 @@ public class KDC {
new InetAddress[]{InetAddress.getLocalHost()}), new InetAddress[]{InetAddress.getLocalHost()}),
null 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, TGSRep tgsRep = new TGSRep(null,
cname, cname,
t, t,
...@@ -986,7 +1030,7 @@ public class KDC { ...@@ -986,7 +1030,7 @@ public class KDC {
+ " " +ke.returnCodeMessage()); + " " +ke.returnCodeMessage());
if (kerr == null) { if (kerr == null) {
kerr = new KRBError(null, null, null, kerr = new KRBError(null, null, null,
new KerberosTime(new Date()), timeAfter(0),
0, 0,
ke.returnCode(), ke.returnCode(),
body.cname, body.cname,
...@@ -1023,16 +1067,11 @@ public class KDC { ...@@ -1023,16 +1067,11 @@ public class KDC {
KDCReqBody body = asReq.reqBody; KDCReqBody body = asReq.reqBody;
eTypes = KDCReqBodyDotEType(body); eTypes = filterSupported(KDCReqBodyDotEType(body));
int eType = eTypes[0]; if (eTypes.length == 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); throw new KrbException(Krb5.KDC_ERR_ETYPE_NOSUPP);
} }
eType = eTypes[1]; int eType = eTypes[0];
}
if (body.kdcOptions.get(KDCOptions.CANONICALIZE) && if (body.kdcOptions.get(KDCOptions.CANONICALIZE) &&
body.cname.getNameType() == PrincipalName.KRB_NT_ENTERPRISE) { body.cname.getNameType() == PrincipalName.KRB_NT_ENTERPRISE) {
...@@ -1079,8 +1118,12 @@ public class KDC { ...@@ -1079,8 +1118,12 @@ public class KDC {
// Session key // Session key
EncryptionKey key = generateRandomKey(eType); EncryptionKey key = generateRandomKey(eType);
// Check time, TODO // Check time, TODO
KerberosTime from = body.from;
KerberosTime till = body.till; KerberosTime till = body.till;
KerberosTime rtime = body.rtime; KerberosTime rtime = body.rtime;
if (from == null || from.isZero()) {
from = timeAfter(0);
}
if (till == null) { if (till == null) {
throw new KrbException(Krb5.KDC_ERR_NEVER_VALID); // TODO throw new KrbException(Krb5.KDC_ERR_NEVER_VALID); // TODO
} else if (till.isZero()) { } else if (till.isZero()) {
...@@ -1106,7 +1149,8 @@ public class KDC { ...@@ -1106,7 +1149,8 @@ public class KDC {
if (body.kdcOptions.get(KDCOptions.FORWARDABLE)) { if (body.kdcOptions.get(KDCOptions.FORWARDABLE)) {
List<String> sensitives = (List<String>) List<String> sensitives = (List<String>)
options.get(Option.SENSITIVE_ACCOUNTS); options.get(Option.SENSITIVE_ACCOUNTS);
if (sensitives != null && sensitives.contains(body.cname.toString())) { if (sensitives != null
&& sensitives.contains(body.cname.toString())) {
// Cannot make FORWARDABLE // Cannot make FORWARDABLE
} else { } else {
bFlags[Krb5.TKT_OPTS_FORWARDABLE] = true; bFlags[Krb5.TKT_OPTS_FORWARDABLE] = true;
...@@ -1114,7 +1158,7 @@ public class KDC { ...@@ -1114,7 +1158,7 @@ public class KDC {
} }
if (body.kdcOptions.get(KDCOptions.RENEWABLE)) { if (body.kdcOptions.get(KDCOptions.RENEWABLE)) {
bFlags[Krb5.TKT_OPTS_RENEWABLE] = true; 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)) { if (body.kdcOptions.get(KDCOptions.PROXIABLE)) {
bFlags[Krb5.TKT_OPTS_PROXIABLE] = true; bFlags[Krb5.TKT_OPTS_PROXIABLE] = true;
...@@ -1136,7 +1180,8 @@ public class KDC { ...@@ -1136,7 +1180,8 @@ public class KDC {
pas2 = new DerValue[] { pas2 = new DerValue[] {
new DerValue(new ETypeInfo2(1, null, null).asn1Encode()), new DerValue(new ETypeInfo2(1, null, null).asn1Encode()),
new DerValue(new ETypeInfo2(1, "", 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[] { pas = new DerValue[] {
new DerValue(new ETypeInfo(1, null).asn1Encode()), new DerValue(new ETypeInfo(1, null).asn1Encode()),
...@@ -1146,7 +1191,8 @@ public class KDC { ...@@ -1146,7 +1191,8 @@ public class KDC {
break; break;
case 2: // we still reject non-null s2kparams and prefer E2 over E case 2: // we still reject non-null s2kparams and prefer E2 over E
pas2 = new DerValue[] { 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, null).asn1Encode()),
new DerValue(new ETypeInfo2(1, "", null).asn1Encode()), new DerValue(new ETypeInfo2(1, "", null).asn1Encode()),
}; };
...@@ -1241,11 +1287,14 @@ public class KDC { ...@@ -1241,11 +1287,14 @@ public class KDC {
} else { } else {
EncryptionKey pakey = null; EncryptionKey pakey = null;
try { try {
EncryptedData data = newEncryptedData(new DerValue(inPAs[0].getValue())); EncryptedData data = newEncryptedData(
new DerValue(inPAs[0].getValue()));
pakey = keyForUser(body.cname, data.getEType(), false); pakey = keyForUser(body.cname, data.getEType(), false);
data.decrypt(pakey, KeyUsage.KU_PA_ENC_TS); data.decrypt(pakey, KeyUsage.KU_PA_ENC_TS);
} catch (Exception e) { } 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; bFlags[Krb5.TKT_OPTS_PRE_AUTHENT] = true;
for (PAData pa : inPAs) { for (PAData pa : inPAs) {
...@@ -1267,8 +1316,8 @@ public class KDC { ...@@ -1267,8 +1316,8 @@ public class KDC {
key, key,
body.cname, body.cname,
new TransitedEncoding(1, new byte[0]), new TransitedEncoding(1, new byte[0]),
new KerberosTime(new Date()), timeAfter(0),
body.from, from,
till, rtime, till, rtime,
body.addresses, body.addresses,
null); null);
...@@ -1279,20 +1328,21 @@ public class KDC { ...@@ -1279,20 +1328,21 @@ public class KDC {
EncASRepPart enc_part = new EncASRepPart( EncASRepPart enc_part = new EncASRepPart(
key, key,
new LastReq(new LastReqEntry[]{ new LastReq(new LastReqEntry[]{
new LastReqEntry(0, new KerberosTime(new Date().getTime() - 10000)) new LastReqEntry(0, timeAfter(-10))
}), }),
body.getNonce(), // TODO: detect replay? 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 // Next 5 and last MUST be same with ticket
tFlags, tFlags,
new KerberosTime(new Date()), timeAfter(0),
body.from, from,
till, rtime, till, rtime,
service, service,
body.addresses, body.addresses,
enc_outPAs.toArray(new PAData[enc_outPAs.size()]) 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( ASRep asRep = new ASRep(
outPAs.toArray(new PAData[outPAs.size()]), outPAs.toArray(new PAData[outPAs.size()]),
body.cname, body.cname,
...@@ -1349,7 +1399,7 @@ public class KDC { ...@@ -1349,7 +1399,7 @@ public class KDC {
eData = temp.toByteArray(); eData = temp.toByteArray();
} }
kerr = new KRBError(null, null, null, kerr = new KRBError(null, null, null,
new KerberosTime(new Date()), timeAfter(0),
0, 0,
ke.returnCode(), ke.returnCode(),
body.cname, body.cname,
...@@ -1427,6 +1477,35 @@ public class KDC { ...@@ -1427,6 +1477,35 @@ public class KDC {
throw new KrbException("Illegal duration format " + s); 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 * Generates a line for a KDC to put inside [realms] of krb5.conf
* @return REALM.NAME = { kdc = host:port etc } * @return REALM.NAME = { kdc = host:port etc }
...@@ -1451,6 +1530,20 @@ public class KDC { ...@@ -1451,6 +1530,20 @@ public class KDC {
* @throws java.io.IOException for any communication error * @throws java.io.IOException for any communication error
*/ */
protected void startServer(int port, boolean asDaemon) throws IOException { 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) { if (port > 0) {
u1 = new DatagramSocket(port, InetAddress.getByName("127.0.0.1")); u1 = new DatagramSocket(port, InetAddress.getByName("127.0.0.1"));
t1 = new ServerSocket(port); t1 = new ServerSocket(port);
...@@ -1543,11 +1636,28 @@ public class KDC { ...@@ -1543,11 +1636,28 @@ 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() { boolean isReady() {
return udpConsumerReady && tcpConsumerReady && dispatcherReady; return udpConsumerReady && tcpConsumerReady && dispatcherReady;
} }
public void terminate() { public void terminate() {
if (nativeKdc != null) {
System.out.println("Killing kdc...");
kdcProc.destroyForcibly();
System.out.println("Done");
} else {
try { try {
thread1.stop(); thread1.stop();
thread2.stop(); thread2.stop();
...@@ -1558,6 +1668,7 @@ public class KDC { ...@@ -1558,6 +1668,7 @@ public class KDC {
// OK // OK
} }
} }
}
public static KDC startKDC(final String host, final String krbConfFileName, public static KDC startKDC(final String host, final String krbConfFileName,
final String realm, final Map<String, String> principals, final String realm, final Map<String, String> principals,
...@@ -1710,6 +1821,269 @@ public class KDC { ...@@ -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 // Calling private methods thru reflections
private static final Field getPADataField; private static final Field getPADataField;
private static final Field getEType; private static final Field getEType;
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册