8186884: Test native KDC, Java krb5 lib, and native krb5 lib in one test
Reviewed-by: asmotrak
This commit is contained in:
parent
fe19274488
commit
1e6e9203c4
@ -323,9 +323,12 @@ public class Proc {
|
|||||||
br = new BufferedReader(new InputStreamReader(p.getInputStream()));
|
br = new BufferedReader(new InputStreamReader(p.getInputStream()));
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
String getId(String prefix) {
|
String getId(String suffix) {
|
||||||
if (debug != null) return prefix + "." + debug;
|
if (debug != null) {
|
||||||
else return prefix + "." + System.identityHashCode(this);
|
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 {
|
||||||
@ -395,9 +398,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);
|
||||||
|
@ -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.
|
* 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,170 +23,312 @@
|
|||||||
|
|
||||||
/*
|
/*
|
||||||
* @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 BasicProc
|
* @run main/othervm BasicProc launcher
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import java.io.File;
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Paths;
|
||||||
|
import java.nio.file.attribute.PosixFilePermission;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.PropertyPermission;
|
||||||
|
import java.util.Random;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
import org.ietf.jgss.Oid;
|
import 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);
|
||||||
kdc.addPrincipal(USER, PASS);
|
try {
|
||||||
kdc.addPrincipalRandKey("krbtgt/" + REALM);
|
kdc.addPrincipal(USER, PASS);
|
||||||
kdc.addPrincipalRandKey(SERVER);
|
kdc.addPrincipalRandKey("krbtgt/" + REALM);
|
||||||
kdc.addPrincipalRandKey(BACKEND);
|
kdc.addPrincipalRandKey(SERVER);
|
||||||
|
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", "")
|
|
||||||
.perm(new java.util.PropertyPermission(
|
|
||||||
"sun.security.krb5.principal", "read"))
|
|
||||||
.perm(new javax.security.auth.AuthPermission(
|
|
||||||
"modifyPrincipals"))
|
|
||||||
.perm(new javax.security.auth.AuthPermission(
|
|
||||||
"modifyPrivateCredentials"))
|
|
||||||
.perm(new javax.security.auth.AuthPermission("doAs"))
|
|
||||||
.perm(new javax.security.auth.kerberos.ServicePermission(
|
|
||||||
"krbtgt/" + REALM + "@" + REALM, "initiate"))
|
|
||||||
.perm(new javax.security.auth.kerberos.ServicePermission(
|
|
||||||
"server/localhost@" + REALM, "initiate"))
|
|
||||||
.perm(new javax.security.auth.kerberos.DelegationPermission(
|
|
||||||
"\"server/localhost@" + REALM + "\" " +
|
|
||||||
"\"krbtgt/" + REALM + "@" + REALM + "\""))
|
|
||||||
.debug("C")
|
|
||||||
.start();
|
|
||||||
Proc ps = Proc.create("BasicProc")
|
|
||||||
.args("server")
|
|
||||||
.prop("java.security.krb5.conf", CONF)
|
|
||||||
.prop("java.security.manager", "")
|
|
||||||
.perm(new java.util.PropertyPermission(
|
|
||||||
"sun.security.krb5.principal", "read"))
|
|
||||||
.perm(new javax.security.auth.AuthPermission(
|
|
||||||
"modifyPrincipals"))
|
|
||||||
.perm(new javax.security.auth.AuthPermission(
|
|
||||||
"modifyPrivateCredentials"))
|
|
||||||
.perm(new javax.security.auth.AuthPermission("doAs"))
|
|
||||||
.perm(new PrivateCredentialPermission(
|
|
||||||
"javax.security.auth.kerberos.KeyTab * \"*\"",
|
|
||||||
"read"))
|
|
||||||
.perm(new javax.security.auth.kerberos.ServicePermission(
|
|
||||||
"server/localhost@" + REALM, "accept"))
|
|
||||||
.perm(new java.io.FilePermission(
|
|
||||||
cwd + File.separator + KTAB, "read"))
|
|
||||||
.perm(new javax.security.auth.kerberos.ServicePermission(
|
|
||||||
"backend/localhost@" + REALM, "initiate"))
|
|
||||||
.debug("S")
|
|
||||||
.start();
|
|
||||||
Proc pb = Proc.create("BasicProc")
|
|
||||||
.args("backend")
|
|
||||||
.prop("java.security.krb5.conf", CONF)
|
|
||||||
.prop("java.security.manager", "")
|
|
||||||
.perm(new java.util.PropertyPermission(
|
|
||||||
"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
|
// Library paths. The 1st one is always null which means
|
||||||
String token = pc.readData();
|
// Java, "" means the default native lib.
|
||||||
ps.println(token);
|
String[] libs = new String[tmp.length];
|
||||||
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) {
|
// Names for each lib above. Use in file names.
|
||||||
throw new Exception();
|
String[] names = new String[tmp.length];
|
||||||
}
|
|
||||||
} else if (args[0].equals("client")) {
|
boolean hasNative = false;
|
||||||
Context c = Context.fromUserPass(USER, PASS, false);
|
|
||||||
c.startAsClient(SERVER, oid);
|
for (int i = 0; i < tmp.length; i++) {
|
||||||
c.x().requestCredDeleg(true);
|
if (tmp[i].isEmpty()) {
|
||||||
Proc.binOut(c.take(new byte[0]));
|
throw new Exception("Invalid native.krb5.libs");
|
||||||
byte[] token = Proc.binIn();
|
}
|
||||||
c.take(token);
|
String[] pair = tmp[i].split("=", 2);
|
||||||
} else if (args[0].equals("server")) {
|
names[i] = pair[0];
|
||||||
Context s = Context.fromUserKtab(SERVER, KTAB, true);
|
if (!pair[0].equals("j")) {
|
||||||
s.startAsServer(oid);
|
libs[i] = pair.length > 1 ? pair[1] : "";
|
||||||
byte[] token = Proc.binIn();
|
hasNative = true;
|
||||||
token = s.take(token);
|
}
|
||||||
Proc.binOut(token);
|
}
|
||||||
Context s2 = s.delegated();
|
|
||||||
s2.startAsClient(BACKEND, oid);
|
if (hasNative) {
|
||||||
Proc.binOut(s2.take(new byte[0]));
|
kdc.kinit(USER, "base.ccache");
|
||||||
token = Proc.binIn();
|
}
|
||||||
s2.take(token);
|
|
||||||
byte[] msg = "Hello".getBytes();
|
// Try the same lib first
|
||||||
Proc.binOut(s2.wrap(msg, true));
|
for (int i = 0; i < libs.length; i++) {
|
||||||
s2.verifyMic(Proc.binIn(), msg);
|
once(names[i] + names[i] + names[i],
|
||||||
String in = Proc.textIn();
|
libs[i], libs[i], libs[i]);
|
||||||
if (!in.equals("Hello")) {
|
}
|
||||||
throw new Exception();
|
|
||||||
}
|
for (int i = 0; i < libs.length; i++) {
|
||||||
} else if (args[0].equals("backend")) {
|
for (int j = 0; j < libs.length; j++) {
|
||||||
Context b = Context.fromUserKtab(BACKEND, KTAB, true);
|
for (int k = 0; k < libs.length; k++) {
|
||||||
b.startAsServer(oid);
|
if (i != j || i != k) {
|
||||||
byte[] token = Proc.binIn();
|
once(names[i] + names[j] + names[k],
|
||||||
Proc.binOut(b.take(token));
|
libs[i], libs[j], libs[k]);
|
||||||
byte[] msg = b.unwrap(Proc.binIn(), true);
|
}
|
||||||
Proc.binOut(b.getMic(msg));
|
}
|
||||||
Proc.textOut(new String(msg));
|
}
|
||||||
|
}
|
||||||
|
} 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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// create a native server
|
|
||||||
private static Proc ns(Proc p) throws Exception {
|
/**
|
||||||
return p
|
* One test run.
|
||||||
.env("KRB5_CONFIG", CONF)
|
*
|
||||||
.env("KRB5_KTNAME", KTAB)
|
* @param label test label
|
||||||
.prop("sun.security.jgss.native", "true")
|
* @param lc lib of client
|
||||||
.prop("javax.security.auth.useSubjectCredsOnly", "false")
|
* @param ls lib of server
|
||||||
.prop("sun.security.nativegss.debug", "true");
|
* @param lb lib of backend
|
||||||
|
*/
|
||||||
|
private static void once(String label, String lc, String ls, String lb)
|
||||||
|
throws Exception {
|
||||||
|
|
||||||
|
Proc pc = proc(lc)
|
||||||
|
.args("client", lc == null ? "j" : "n")
|
||||||
|
.perm(new javax.security.auth.kerberos.ServicePermission(
|
||||||
|
"krbtgt/" + REALM + "@" + REALM, "initiate"))
|
||||||
|
.perm(new javax.security.auth.kerberos.ServicePermission(
|
||||||
|
SERVER + "@" + REALM, "initiate"))
|
||||||
|
.perm(new javax.security.auth.kerberos.DelegationPermission(
|
||||||
|
"\"" + SERVER + "@" + REALM + "\" " +
|
||||||
|
"\"krbtgt/" + REALM + "@" + REALM + "\""))
|
||||||
|
.debug(label + "-C");
|
||||||
|
if (lc == null) {
|
||||||
|
// for Krb5LoginModule::promptForName
|
||||||
|
pc.perm(new PropertyPermission("user.name", "read"));
|
||||||
|
} else {
|
||||||
|
Files.copy(Paths.get("base.ccache"), Paths.get(label + ".ccache"));
|
||||||
|
Files.setPosixFilePermissions(Paths.get(label + ".ccache"),
|
||||||
|
Set.of(PosixFilePermission.OWNER_READ,
|
||||||
|
PosixFilePermission.OWNER_WRITE));
|
||||||
|
pc.env("KRB5CCNAME", label + ".ccache");
|
||||||
|
// Do not try system ktab if ccache fails
|
||||||
|
pc.env("KRB5_KTNAME", "none");
|
||||||
|
}
|
||||||
|
pc.start();
|
||||||
|
|
||||||
|
Proc ps = proc(ls)
|
||||||
|
.args("server", ls == null ? "j" : "n")
|
||||||
|
.perm(new javax.security.auth.kerberos.ServicePermission(
|
||||||
|
SERVER + "@" + REALM, "accept"))
|
||||||
|
.perm(new javax.security.auth.kerberos.ServicePermission(
|
||||||
|
BACKEND + "@" + REALM, "initiate"))
|
||||||
|
.debug(label + "-S");
|
||||||
|
if (ls == null) {
|
||||||
|
ps.perm(new PrivateCredentialPermission(
|
||||||
|
"javax.security.auth.kerberos.KeyTab * \"*\"", "read"))
|
||||||
|
.perm(new java.io.FilePermission(KTAB_S, "read"));
|
||||||
|
} else {
|
||||||
|
ps.env("KRB5_KTNAME", KTAB_S);
|
||||||
|
}
|
||||||
|
ps.start();
|
||||||
|
|
||||||
|
Proc pb = proc(lb)
|
||||||
|
.args("backend", lb == null ? "j" : "n")
|
||||||
|
.perm(new javax.security.auth.kerberos.ServicePermission(
|
||||||
|
BACKEND + "@" + REALM, "accept"))
|
||||||
|
.debug(label + "-B");
|
||||||
|
if (lb == null) {
|
||||||
|
pb.perm(new PrivateCredentialPermission(
|
||||||
|
"javax.security.auth.kerberos.KeyTab * \"*\"", "read"))
|
||||||
|
.perm(new java.io.FilePermission(KTAB_B, "read"));
|
||||||
|
} else {
|
||||||
|
pb.env("KRB5_KTNAME", KTAB_B);
|
||||||
|
}
|
||||||
|
pb.start();
|
||||||
|
|
||||||
|
// Client and server handshake
|
||||||
|
ps.println(pc.readData());
|
||||||
|
pc.println(ps.readData());
|
||||||
|
|
||||||
|
// Server and backend handshake
|
||||||
|
pb.println(ps.readData());
|
||||||
|
ps.println(pb.readData());
|
||||||
|
|
||||||
|
// wrap/unwrap/getMic/verifyMic and plain text
|
||||||
|
pb.println(ps.readData());
|
||||||
|
pb.println(ps.readData());
|
||||||
|
pb.println(ps.readData());
|
||||||
|
ps.println(pb.readData());
|
||||||
|
ps.println(pb.readData());
|
||||||
|
|
||||||
|
if ((pc.waitFor() | ps.waitFor() | pb.waitFor()) != 0) {
|
||||||
|
throw new Exception("Process failed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A Proc for a child process.
|
||||||
|
*
|
||||||
|
* @param lib the library. Null is Java. "" is default native lib.
|
||||||
|
*/
|
||||||
|
private static Proc proc(String lib) throws Exception {
|
||||||
|
Proc p = Proc.create("BasicProc")
|
||||||
|
.prop("java.security.manager", "")
|
||||||
|
.perm(new javax.security.auth.AuthPermission("doAs"));
|
||||||
|
if (lib != null) {
|
||||||
|
p.env("KRB5_CONFIG", CONF)
|
||||||
|
.env("KRB5_TRACE", "/dev/stderr")
|
||||||
|
.prop("sun.security.jgss.native", "true")
|
||||||
|
.prop("sun.security.jgss.lib", lib)
|
||||||
|
.prop("javax.security.auth.useSubjectCredsOnly", "false")
|
||||||
|
.prop("sun.security.nativegss.debug", "true");
|
||||||
|
int pos = lib.lastIndexOf('/');
|
||||||
|
if (pos > 0) {
|
||||||
|
p.env("LD_LIBRARY_PATH", lib.substring(0, pos));
|
||||||
|
p.env("DYLD_LIBRARY_PATH", lib.substring(0, pos));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
p.perm(new java.util.PropertyPermission(
|
||||||
|
"sun.security.krb5.principal", "read"))
|
||||||
|
// For Krb5LoginModule::login.
|
||||||
|
.perm(new javax.security.auth.AuthPermission(
|
||||||
|
"modifyPrincipals"))
|
||||||
|
.perm(new javax.security.auth.AuthPermission(
|
||||||
|
"modifyPrivateCredentials"))
|
||||||
|
.prop("sun.security.krb5.debug", "true")
|
||||||
|
.prop("java.security.krb5.conf", CONF);
|
||||||
|
}
|
||||||
|
return p;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (c) 2008, 2013, Oracle and/or its affiliates. All rights reserved.
|
* Copyright (c) 2008, 2017, Oracle and/or its affiliates. All rights reserved.
|
||||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
* 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,6 +23,7 @@
|
|||||||
|
|
||||||
import com.sun.security.auth.module.Krb5LoginModule;
|
import com.sun.security.auth.module.Krb5LoginModule;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
import java.lang.reflect.InvocationTargetException;
|
import java.lang.reflect.InvocationTargetException;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
import java.security.PrivilegedActionException;
|
import java.security.PrivilegedActionException;
|
||||||
@ -31,6 +32,11 @@ import java.util.Arrays;
|
|||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
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;
|
||||||
@ -41,9 +47,14 @@ 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 java.io.ByteArrayInputStream;
|
import java.io.ByteArrayInputStream;
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.security.Principal;
|
import java.security.Principal;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Context of a JGSS subject, encapsulating Subject and GSSContext.
|
* Context of a JGSS subject, encapsulating Subject and GSSContext.
|
||||||
@ -149,24 +160,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 {
|
|||||||
* @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);
|
||||||
@ -618,6 +655,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
|
||||||
|
@ -28,6 +28,7 @@
|
|||||||
* java.security.jgss/sun.security.krb5.internal:+open
|
* java.security.jgss/sun.security.krb5.internal:+open
|
||||||
* java.security.jgss/sun.security.jgss
|
* java.security.jgss/sun.security.jgss
|
||||||
* java.security.jgss/sun.security.krb5:+open
|
* java.security.jgss/sun.security.krb5:+open
|
||||||
|
* java.security.jgss/sun.security.jgss.krb5
|
||||||
* java.security.jgss/sun.security.krb5.internal.ccache
|
* java.security.jgss/sun.security.krb5.internal.ccache
|
||||||
* java.security.jgss/sun.security.krb5.internal.crypto
|
* java.security.jgss/sun.security.krb5.internal.crypto
|
||||||
* java.security.jgss/sun.security.krb5.internal.ktab
|
* java.security.jgss/sun.security.krb5.internal.ktab
|
||||||
|
@ -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.security.krb5.*;
|
import sun.security.krb5.*;
|
||||||
import sun.security.krb5.internal.*;
|
import sun.security.krb5.internal.*;
|
||||||
@ -45,6 +46,11 @@ import sun.security.util.DerValue;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 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>
|
||||||
@ -125,13 +131,21 @@ 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;
|
||||||
|
|
||||||
public static String NOT_EXISTING_HOST = "not.existing.host";
|
public static final String NOT_EXISTING_HOST = "not.existing.host";
|
||||||
|
|
||||||
|
// What etypes the KDC supports. Comma-separated strings. Null for all.
|
||||||
|
// Please note native KDCs might use different names.
|
||||||
|
private static final String SUPPORTED_ETYPES
|
||||||
|
= System.getProperty("kdc.supported.enctypes");
|
||||||
|
|
||||||
|
// The native KDC
|
||||||
|
private final NativeKdc nativeKdc;
|
||||||
|
|
||||||
|
// The native KDC process
|
||||||
|
private Process kdcProc = null;
|
||||||
|
|
||||||
// Under the hood.
|
// Under the hood.
|
||||||
|
|
||||||
// The random generator to generate random keys (including session keys)
|
|
||||||
private static SecureRandom secureRandom = new SecureRandom();
|
|
||||||
|
|
||||||
// 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
|
||||||
// can still locate the principal and give back correct salt.
|
// can still locate the principal and give back correct salt.
|
||||||
@ -229,7 +243,8 @@ public class KDC {
|
|||||||
|
|
||||||
static {
|
static {
|
||||||
if (System.getProperty("jdk.net.hosts.file") == null) {
|
if (System.getProperty("jdk.net.hosts.file") == null) {
|
||||||
String hostsFileName = System.getProperty("test.src", ".") + "/TestHosts";
|
String hostsFileName
|
||||||
|
= System.getProperty("test.src", ".") + "/TestHosts";
|
||||||
System.setProperty("jdk.net.hosts.file", hostsFileName);
|
System.setProperty("jdk.net.hosts.file", hostsFileName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -273,7 +288,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);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -313,26 +329,38 @@ 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) {
|
||||||
char[] pass = passwords.get(name);
|
if (name.indexOf('@') < 0) {
|
||||||
int kvno = 0;
|
name = name + "@" + realm;
|
||||||
if (Character.isDigit(pass[pass.length-1])) {
|
|
||||||
kvno = pass[pass.length-1] - '0';
|
|
||||||
}
|
}
|
||||||
PrincipalName pn = new PrincipalName(name,
|
if (nativeKdc == null) {
|
||||||
|
char[] pass = passwords.get(name);
|
||||||
|
int kvno = 0;
|
||||||
|
if (Character.isDigit(pass[pass.length - 1])) {
|
||||||
|
kvno = pass[pass.length - 1] - '0';
|
||||||
|
}
|
||||||
|
PrincipalName pn = new PrincipalName(name,
|
||||||
name.indexOf('/') < 0 ?
|
name.indexOf('/') < 0 ?
|
||||||
PrincipalName.KRB_NT_UNKNOWN :
|
PrincipalName.KRB_NT_UNKNOWN :
|
||||||
PrincipalName.KRB_NT_SRV_HST);
|
PrincipalName.KRB_NT_SRV_HST);
|
||||||
ktab.addEntry(pn,
|
ktab.addEntry(pn,
|
||||||
getSalt(pn),
|
getSalt(pn),
|
||||||
pass,
|
pass,
|
||||||
kvno,
|
kvno,
|
||||||
true);
|
true);
|
||||||
|
} else {
|
||||||
|
nativeKdc.ktadd(name, tab);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (nativeKdc == null) {
|
||||||
|
ktab.save();
|
||||||
}
|
}
|
||||||
ktab.save();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -389,16 +417,24 @@ public class KDC {
|
|||||||
* @param salt the salt, or null if a default value will be used
|
* @param 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;
|
||||||
}
|
}
|
||||||
passwords.put(user, pass);
|
if (nativeKdc != null) {
|
||||||
if (salt != null) {
|
if (!user.equals("krbtgt/" + realm)) {
|
||||||
salts.put(user, salt);
|
nativeKdc.addPrincipal(user, new String(pass));
|
||||||
}
|
}
|
||||||
if (s2kparams != null) {
|
passwords.put(user, new char[0]);
|
||||||
s2kparamses.put(user, s2kparams);
|
} else {
|
||||||
|
passwords.put(user, pass);
|
||||||
|
if (salt != null) {
|
||||||
|
salts.put(user, salt);
|
||||||
|
}
|
||||||
|
if (s2kparams != null) {
|
||||||
|
s2kparamses.put(user, s2kparams);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -495,12 +531,11 @@ public class KDC {
|
|||||||
*/
|
*/
|
||||||
public static void saveConfig(String file, KDC kdc, Object... more)
|
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");
|
||||||
@ -508,14 +543,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();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -535,6 +568,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;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -542,7 +576,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);
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
@ -551,8 +587,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';
|
||||||
@ -688,6 +725,15 @@ public class KDC {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a KerberosTime.
|
||||||
|
*
|
||||||
|
* @param offset offset from NOW in milliseconds
|
||||||
|
*/
|
||||||
|
private static KerberosTime timeFor(long offset) {
|
||||||
|
return new KerberosTime(new Date().getTime() + offset);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Processes an incoming request and generates a response.
|
* Processes an incoming request and generates a response.
|
||||||
* @param in the request
|
* @param in the request
|
||||||
@ -719,7 +765,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
|
||||||
|
|
||||||
@ -756,13 +805,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.
|
||||||
@ -783,11 +833,15 @@ 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 = timeFor(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()) {
|
||||||
till = new KerberosTime(new Date().getTime() + 1000 * DEFAULT_LIFETIME);
|
till = timeFor(1000 * DEFAULT_LIFETIME);
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean[] bFlags = new boolean[Krb5.TKT_OPTS_MAX+1];
|
boolean[] bFlags = new boolean[Krb5.TKT_OPTS_MAX+1];
|
||||||
@ -813,7 +867,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 = timeFor(1000 * 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;
|
||||||
@ -832,7 +886,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();
|
||||||
@ -882,8 +937,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()),
|
timeFor(0),
|
||||||
body.from,
|
from,
|
||||||
till, renewTill,
|
till, renewTill,
|
||||||
body.addresses != null ? body.addresses
|
body.addresses != null ? body.addresses
|
||||||
: etp.caddr,
|
: etp.caddr,
|
||||||
@ -900,20 +955,21 @@ 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, timeFor(-10000))
|
||||||
}),
|
}),
|
||||||
body.getNonce(), // TODO: detect replay
|
body.getNonce(), // TODO: detect replay
|
||||||
new KerberosTime(new Date().getTime() + 1000 * 3600 * 24),
|
timeFor(1000 * 3600 * 24),
|
||||||
// Next 5 and last MUST be same with ticket
|
// Next 5 and last MUST be same with ticket
|
||||||
tFlags,
|
tFlags,
|
||||||
new KerberosTime(new Date()),
|
timeFor(0),
|
||||||
body.from,
|
from,
|
||||||
till, renewTill,
|
till, renewTill,
|
||||||
service,
|
service,
|
||||||
body.addresses
|
body.addresses
|
||||||
);
|
);
|
||||||
EncryptedData edata = new EncryptedData(ckey, enc_part.asn1Encode(), KeyUsage.KU_ENC_TGS_REP_PART_SESSKEY);
|
EncryptedData edata = new EncryptedData(ckey, enc_part.asn1Encode(),
|
||||||
|
KeyUsage.KU_ENC_TGS_REP_PART_SESSKEY);
|
||||||
TGSRep tgsRep = new TGSRep(null,
|
TGSRep tgsRep = new TGSRep(null,
|
||||||
cname,
|
cname,
|
||||||
t,
|
t,
|
||||||
@ -934,7 +990,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()),
|
timeFor(0),
|
||||||
0,
|
0,
|
||||||
ke.returnCode(),
|
ke.returnCode(),
|
||||||
body.cname,
|
body.cname,
|
||||||
@ -970,16 +1026,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) {
|
||||||
|
throw new KrbException(Krb5.KDC_ERR_ETYPE_NOSUPP);
|
||||||
// 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];
|
|
||||||
}
|
}
|
||||||
|
int eType = eTypes[0];
|
||||||
|
|
||||||
EncryptionKey ckey = keyForUser(body.cname, eType, false);
|
EncryptionKey ckey = keyForUser(body.cname, eType, false);
|
||||||
EncryptionKey skey = keyForUser(service, eType, true);
|
EncryptionKey skey = keyForUser(service, eType, true);
|
||||||
@ -1008,31 +1059,32 @@ 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 = timeFor(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()) {
|
||||||
till = new KerberosTime(
|
till = timeFor(1000 * DEFAULT_LIFETIME);
|
||||||
new Date().getTime() + 1000 * DEFAULT_LIFETIME);
|
} else if (till.greaterThan(timeFor(24 * 3600 * 1000))) {
|
||||||
} else if (till.greaterThan(new KerberosTime(Instant.now()
|
|
||||||
.plus(1, ChronoUnit.DAYS)))) {
|
|
||||||
// If till is more than 1 day later, make it renewable
|
// If till is more than 1 day later, make it renewable
|
||||||
till = new KerberosTime(
|
till = timeFor(1000 * DEFAULT_LIFETIME);
|
||||||
new Date().getTime() + 1000 * DEFAULT_LIFETIME);
|
|
||||||
body.kdcOptions.set(KDCOptions.RENEWABLE, true);
|
body.kdcOptions.set(KDCOptions.RENEWABLE, true);
|
||||||
if (rtime == null) rtime = till;
|
if (rtime == null) rtime = till;
|
||||||
}
|
}
|
||||||
if (rtime == null && body.kdcOptions.get(KDCOptions.RENEWABLE)) {
|
if (rtime == null && body.kdcOptions.get(KDCOptions.RENEWABLE)) {
|
||||||
rtime = new KerberosTime(
|
rtime = timeFor(1000 * DEFAULT_RENEWTIME);
|
||||||
new Date().getTime() + 1000 * DEFAULT_RENEWTIME);
|
|
||||||
}
|
}
|
||||||
//body.from
|
//body.from
|
||||||
boolean[] bFlags = new boolean[Krb5.TKT_OPTS_MAX+1];
|
boolean[] bFlags = new boolean[Krb5.TKT_OPTS_MAX+1];
|
||||||
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;
|
||||||
@ -1040,7 +1092,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 = timeFor(1000 * 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;
|
||||||
@ -1062,7 +1114,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()),
|
||||||
@ -1072,7 +1125,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()),
|
||||||
};
|
};
|
||||||
@ -1165,11 +1219,15 @@ public class KDC {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
EncryptedData data = newEncryptedData(new DerValue(inPAs[0].getValue()));
|
EncryptedData data = newEncryptedData(
|
||||||
EncryptionKey pakey = keyForUser(body.cname, data.getEType(), false);
|
new DerValue(inPAs[0].getValue()));
|
||||||
|
EncryptionKey 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;
|
||||||
}
|
}
|
||||||
@ -1180,8 +1238,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()),
|
timeFor(0),
|
||||||
body.from,
|
from,
|
||||||
till, rtime,
|
till, rtime,
|
||||||
body.addresses,
|
body.addresses,
|
||||||
null);
|
null);
|
||||||
@ -1192,19 +1250,20 @@ 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, timeFor(-10000))
|
||||||
}),
|
}),
|
||||||
body.getNonce(), // TODO: detect replay?
|
body.getNonce(), // TODO: detect replay?
|
||||||
new KerberosTime(new Date().getTime() + 1000 * 3600 * 24),
|
timeFor(1000 * 3600 * 24),
|
||||||
// Next 5 and last MUST be same with ticket
|
// Next 5 and last MUST be same with ticket
|
||||||
tFlags,
|
tFlags,
|
||||||
new KerberosTime(new Date()),
|
timeFor(0),
|
||||||
body.from,
|
from,
|
||||||
till, rtime,
|
till, rtime,
|
||||||
service,
|
service,
|
||||||
body.addresses
|
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(
|
ASRep asRep = new ASRep(
|
||||||
outPAs.toArray(new PAData[outPAs.size()]),
|
outPAs.toArray(new PAData[outPAs.size()]),
|
||||||
body.cname,
|
body.cname,
|
||||||
@ -1259,7 +1318,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()),
|
timeFor(0),
|
||||||
0,
|
0,
|
||||||
ke.returnCode(),
|
ke.returnCode(),
|
||||||
body.cname,
|
body.cname,
|
||||||
@ -1271,6 +1330,35 @@ public class KDC {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private int[] filterSupported(int[] input) {
|
||||||
|
int count = 0;
|
||||||
|
for (int i = 0; i < input.length; i++) {
|
||||||
|
if (!EType.isSupported(input[i])) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (SUPPORTED_ETYPES != null) {
|
||||||
|
boolean supported = false;
|
||||||
|
for (String se : SUPPORTED_ETYPES.split(",")) {
|
||||||
|
if (Config.getType(se) == input[i]) {
|
||||||
|
supported = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!supported) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (count != i) {
|
||||||
|
input[count] = input[i];
|
||||||
|
}
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
if (count != input.length) {
|
||||||
|
input = Arrays.copyOf(input, count);
|
||||||
|
}
|
||||||
|
return input;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generates a line for a KDC to put inside [realms] of krb5.conf
|
* 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 }
|
||||||
@ -1295,6 +1383,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);
|
||||||
@ -1391,19 +1493,37 @@ public class KDC {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void kinit(String user, String ccache) throws Exception {
|
||||||
|
if (user.indexOf('@') < 0) {
|
||||||
|
user = user + "@" + realm;
|
||||||
|
}
|
||||||
|
if (nativeKdc != null) {
|
||||||
|
nativeKdc.kinit(user, ccache);
|
||||||
|
} else {
|
||||||
|
Context.fromUserPass(user, passwords.get(user), false)
|
||||||
|
.ccache(ccache);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
boolean isReady() {
|
boolean isReady() {
|
||||||
return udpConsumerReady && tcpConsumerReady && dispatcherReady;
|
return udpConsumerReady && tcpConsumerReady && dispatcherReady;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void terminate() {
|
public void terminate() {
|
||||||
try {
|
if (nativeKdc != null) {
|
||||||
thread1.stop();
|
System.out.println("Killing kdc...");
|
||||||
thread2.stop();
|
kdcProc.destroyForcibly();
|
||||||
thread3.stop();
|
System.out.println("Done");
|
||||||
u1.close();
|
} else {
|
||||||
t1.close();
|
try {
|
||||||
} catch (Exception e) {
|
thread1.stop();
|
||||||
// OK
|
thread2.stop();
|
||||||
|
thread3.stop();
|
||||||
|
u1.close();
|
||||||
|
t1.close();
|
||||||
|
} catch (Exception e) {
|
||||||
|
// OK
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1518,6 +1638,266 @@ public class KDC {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A native KDC using the binaries in nativePath. Attention:
|
||||||
|
* this is using binaries, not an existing KDC instance.
|
||||||
|
* An implementation of this takes care of configuration,
|
||||||
|
* principal db managing and KDC startup.
|
||||||
|
*/
|
||||||
|
static abstract class NativeKdc {
|
||||||
|
|
||||||
|
protected Map<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);
|
||||||
|
this.env = Map.of(
|
||||||
|
"KRB5_CONFIG", base + "/krb5.conf",
|
||||||
|
"KRB5_TRACE", "/dev/stderr",
|
||||||
|
"DYLD_LIBRARY_PATH", nativePath + "/lib",
|
||||||
|
"LD_LIBRARY_PATH", nativePath + "/lib");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addPrincipal(String user, String pass) {
|
||||||
|
run(true, nativePath + "/bin/kadmin", "-l", "-r", realm,
|
||||||
|
"add", "-p", pass, "--use-defaults", user);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void ktadd(String user, String ktab) {
|
||||||
|
run(true, nativePath + "/bin/kadmin", "-l", "-r", realm,
|
||||||
|
"ext_keytab", "-k", ktab, user);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void init() {
|
||||||
|
run(true, nativePath + "/bin/kadmin", "-l", "-r", realm,
|
||||||
|
"init", "--realm-max-ticket-life=1day",
|
||||||
|
"--realm-max-renewable-life=1month", realm);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Process kdc() {
|
||||||
|
return run(false, nativePath + "/libexec/kdc",
|
||||||
|
"--addresses=127.0.0.1", "-P", "" + port);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void prepare() {
|
||||||
|
try {
|
||||||
|
Files.createDirectory(Paths.get(base));
|
||||||
|
Files.write(Paths.get(base + "/krb5.conf"), Arrays.asList(
|
||||||
|
"[libdefaults]",
|
||||||
|
"default_realm = " + realm,
|
||||||
|
"default_keytab_name = FILE:" + base + "/krb5.keytab",
|
||||||
|
"forwardable = true",
|
||||||
|
"dns_lookup_kdc = no",
|
||||||
|
"dns_lookup_realm = no",
|
||||||
|
"dns_canonicalize_hostname = false",
|
||||||
|
"\n[realms]",
|
||||||
|
realm + " = {",
|
||||||
|
" kdc = localhost:" + port,
|
||||||
|
"}",
|
||||||
|
"\n[kdc]",
|
||||||
|
"db-dir = " + base,
|
||||||
|
"database = {",
|
||||||
|
" label = {",
|
||||||
|
" dbname = " + base + "/current-db",
|
||||||
|
" realm = " + realm,
|
||||||
|
" mkey_file = " + base + "/mkey.file",
|
||||||
|
" acl_file = " + base + "/heimdal.acl",
|
||||||
|
" log_file = " + base + "/current.log",
|
||||||
|
" }",
|
||||||
|
"}",
|
||||||
|
SUPPORTED_ETYPES == null ? ""
|
||||||
|
: ("\n[kadmin]\ndefault_keys = "
|
||||||
|
+ (SUPPORTED_ETYPES + ",")
|
||||||
|
.replaceAll(",", ":pw-salt ")),
|
||||||
|
"\n[logging]",
|
||||||
|
"kdc = 0-/FILE:" + base + "/messages.log",
|
||||||
|
"krb5 = 0-/FILE:" + base + "/messages.log",
|
||||||
|
"default = 0-/FILE:" + base + "/messages.log"
|
||||||
|
));
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new UncheckedIOException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
void kinit(String user, String ccache) {
|
||||||
|
String tmpName = base + "/" + user + "." +
|
||||||
|
System.identityHashCode(this) + ".keytab";
|
||||||
|
ktadd(user, tmpName);
|
||||||
|
run(true, nativePath + "/bin/kinit",
|
||||||
|
"-f", "-t", tmpName, "-c", ccache, user);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MIT krb5 KDC. Make your own exploded (install == false), or
|
||||||
|
// "make install" into nativePath (install == true).
|
||||||
|
static class MIT extends NativeKdc {
|
||||||
|
|
||||||
|
private boolean install; // "make install" or "make"
|
||||||
|
|
||||||
|
MIT(boolean install, String nativePath, KDC kdc) {
|
||||||
|
super(nativePath, kdc);
|
||||||
|
this.install = install;
|
||||||
|
this.env = Map.of(
|
||||||
|
"KRB5_KDC_PROFILE", base + "/kdc.conf",
|
||||||
|
"KRB5_CONFIG", base + "/krb5.conf",
|
||||||
|
"KRB5_TRACE", "/dev/stderr",
|
||||||
|
"DYLD_LIBRARY_PATH", nativePath + "/lib",
|
||||||
|
"LD_LIBRARY_PATH", nativePath + "/lib");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addPrincipal(String user, String pass) {
|
||||||
|
run(true, nativePath +
|
||||||
|
(install ? "/sbin/" : "/kadmin/cli/") + "kadmin.local",
|
||||||
|
"-q", "addprinc -pw " + pass + " " + user);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void ktadd(String user, String ktab) {
|
||||||
|
run(true, nativePath +
|
||||||
|
(install ? "/sbin/" : "/kadmin/cli/") + "kadmin.local",
|
||||||
|
"-q", "ktadd -k " + ktab + " -norandkey " + user);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void init() {
|
||||||
|
run(true, nativePath +
|
||||||
|
(install ? "/sbin/" : "/kadmin/dbutil/") + "kdb5_util",
|
||||||
|
"create", "-s", "-W", "-P", "olala");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Process kdc() {
|
||||||
|
return run(false, nativePath +
|
||||||
|
(install ? "/sbin/" : "/kdc/") + "krb5kdc",
|
||||||
|
"-n");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void prepare() {
|
||||||
|
try {
|
||||||
|
Files.createDirectory(Paths.get(base));
|
||||||
|
Files.write(Paths.get(base + "/kdc.conf"), Arrays.asList(
|
||||||
|
"[kdcdefaults]",
|
||||||
|
"\n[realms]",
|
||||||
|
realm + "= {",
|
||||||
|
" kdc_listen = " + this.port,
|
||||||
|
" kdc_tcp_listen = " + this.port,
|
||||||
|
" database_name = " + base + "/principal",
|
||||||
|
" key_stash_file = " + base + "/.k5.ATHENA.MIT.EDU",
|
||||||
|
SUPPORTED_ETYPES == null ? ""
|
||||||
|
: (" supported_enctypes = "
|
||||||
|
+ (SUPPORTED_ETYPES + ",")
|
||||||
|
.replaceAll(",", ":normal ")),
|
||||||
|
"}"
|
||||||
|
));
|
||||||
|
Files.write(Paths.get(base + "/krb5.conf"), Arrays.asList(
|
||||||
|
"[libdefaults]",
|
||||||
|
"default_realm = " + realm,
|
||||||
|
"default_keytab_name = FILE:" + base + "/krb5.keytab",
|
||||||
|
"forwardable = true",
|
||||||
|
"dns_lookup_kdc = no",
|
||||||
|
"dns_lookup_realm = no",
|
||||||
|
"dns_canonicalize_hostname = false",
|
||||||
|
"\n[realms]",
|
||||||
|
realm + " = {",
|
||||||
|
" kdc = localhost:" + port,
|
||||||
|
"}",
|
||||||
|
"\n[logging]",
|
||||||
|
"kdc = FILE:" + base + "/krb5kdc.log"
|
||||||
|
));
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new UncheckedIOException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
void kinit(String user, String ccache) {
|
||||||
|
String tmpName = base + "/" + user + "." +
|
||||||
|
System.identityHashCode(this) + ".keytab";
|
||||||
|
ktadd(user, tmpName);
|
||||||
|
run(true, nativePath +
|
||||||
|
(install ? "/bin/" : "/clients/kinit/") + "kinit",
|
||||||
|
"-f", "-t", tmpName, "-c", ccache, user);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Calling private methods thru reflections
|
// Calling private methods thru reflections
|
||||||
private static final Field getPADataField;
|
private static final Field getPADataField;
|
||||||
|
@ -95,6 +95,7 @@ public class SSLwithPerms {
|
|||||||
.prop("javax.net.ssl", "handshake")
|
.prop("javax.net.ssl", "handshake")
|
||||||
.prop("sun.security.krb5.debug", "true")
|
.prop("sun.security.krb5.debug", "true")
|
||||||
.perm(new SecurityPermission("setProperty.jdk.tls.disabledAlgorithms"))
|
.perm(new SecurityPermission("setProperty.jdk.tls.disabledAlgorithms"))
|
||||||
|
.perm(new java.util.PropertyPermission("user.name", "read"))
|
||||||
.perm(new PropertyPermission("sun.security.krb5.principal", "read"))
|
.perm(new PropertyPermission("sun.security.krb5.principal", "read"))
|
||||||
.perm(new FilePermission("port", "read"))
|
.perm(new FilePermission("port", "read"))
|
||||||
.perm(new FilePermission(hostsFileName, "read"))
|
.perm(new FilePermission(hostsFileName, "read"))
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
modules java.base/jdk.internal.misc \
|
modules java.base/jdk.internal.misc \
|
||||||
java.base/sun.security.util \
|
java.base/sun.security.util \
|
||||||
java.security.jgss/sun.security.jgss \
|
java.security.jgss/sun.security.jgss \
|
||||||
|
java.security.jgss/sun.security.jgss.krb5 \
|
||||||
java.security.jgss/sun.security.krb5:+open \
|
java.security.jgss/sun.security.krb5:+open \
|
||||||
java.security.jgss/sun.security.krb5.internal:+open \
|
java.security.jgss/sun.security.krb5.internal:+open \
|
||||||
java.security.jgss/sun.security.krb5.internal.ccache \
|
java.security.jgss/sun.security.krb5.internal.ccache \
|
||||||
|
Loading…
Reference in New Issue
Block a user