8044500: Add kinit options and krb5.conf flags that allow users to obtain renewable tickets and specify ticket lifetimes
Reviewed-by: valeriep
--- a/jdk/src/java.security.jgss/share/classes/sun/security/krb5/Config.java Tue Dec 09 08:57:41 2014 +0100
+++ b/jdk/src/java.security.jgss/share/classes/sun/security/krb5/Config.java Tue Dec 09 18:28:26 2014 +0800
@@ -30,19 +30,19 @@
*/
package sun.security.krb5;
-import java.io.File;
-import java.io.FilePermission;
+import java.io.*;
import java.nio.file.DirectoryStream;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.nio.file.Path;
import java.security.PrivilegedAction;
import java.util.*;
-import java.io.IOException;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.security.AccessController;
import java.security.PrivilegedExceptionAction;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
import sun.net.dns.ResolverConfiguration;
import sun.security.krb5.internal.crypto.EType;
@@ -315,6 +315,72 @@
}
/**
+ * Translates a duration value into seconds.
+ *
+ * The format can be one of "h:m[:s]", "NdNhNmNs", and "N". See
+ * http://web.mit.edu/kerberos/krb5-devel/doc/basic/date_format.html#duration
+ * for definitions.
+ *
+ * @param s the string duration
+ * @return time in seconds
+ * @throw KrbException if format is illegal
+ */
+ public static int duration(String s) throws KrbException {
+
+ if (s.isEmpty()) {
+ throw new KrbException("Duration cannot be empty");
+ }
+
+ // N
+ if (s.matches("\\d+")) {
+ return Integer.parseInt(s);
+ }
+
+ // h:m[:s]
+ Matcher m = Pattern.compile("(\\d+):(\\d+)(:(\\d+))?").matcher(s);
+ if (m.matches()) {
+ int hr = Integer.parseInt(m.group(1));
+ int min = Integer.parseInt(m.group(2));
+ if (min >= 60) {
+ throw new KrbException("Illegal duration format " + s);
+ }
+ int result = hr * 3600 + min * 60;
+ if (m.group(4) != null) {
+ int sec = Integer.parseInt(m.group(4));
+ if (sec >= 60) {
+ throw new KrbException("Illegal duration format " + s);
+ }
+ result += sec;
+ }
+ return result;
+ }
+
+ // NdNhNmNs
+ // 120m allowed. Maybe 1h120m is not good, but still allowed
+ m = Pattern.compile(
+ "((\\d+)d)?\\s*((\\d+)h)?\\s*((\\d+)m)?\\s*((\\d+)s)?",
+ Pattern.CASE_INSENSITIVE).matcher(s);
+ if (m.matches()) {
+ int result = 0;
+ if (m.group(2) != null) {
+ result += 86400 * Integer.parseInt(m.group(2));
+ }
+ if (m.group(4) != null) {
+ result += 3600 * Integer.parseInt(m.group(4));
+ }
+ if (m.group(6) != null) {
+ result += 60 * Integer.parseInt(m.group(6));
+ }
+ if (m.group(8) != null) {
+ result += Integer.parseInt(m.group(8));
+ }
+ return result;
+ }
+
+ throw new KrbException("Illegal duration format " + s);
+ }
+
+ /**
* Gets the int value for the specified keys.
* @param keys the keys
* @return the int value, Integer.MIN_VALUE is returned if it cannot be
--- a/jdk/src/java.security.jgss/share/classes/sun/security/krb5/Credentials.java Tue Dec 09 08:57:41 2014 +0100
+++ b/jdk/src/java.security.jgss/share/classes/sun/security/krb5/Credentials.java Tue Dec 09 18:28:26 2014 +0800
@@ -527,4 +527,23 @@
return sb.toString();
}
+ public sun.security.krb5.internal.ccache.Credentials toCCacheCreds() {
+ return new sun.security.krb5.internal.ccache.Credentials(
+ getClient(), getServer(),
+ getSessionKey(),
+ date2kt(getAuthTime()),
+ date2kt(getStartTime()),
+ date2kt(getEndTime()),
+ date2kt(getRenewTill()),
+ false,
+ flags,
+ new HostAddresses(getClientAddresses()),
+ getAuthzData(),
+ getTicket(),
+ null);
+ }
+
+ private static KerberosTime date2kt(Date d) {
+ return d == null ? null : new KerberosTime(d);
+ }
}
--- a/jdk/src/java.security.jgss/share/classes/sun/security/krb5/KrbAsReq.java Tue Dec 09 08:57:41 2014 +0100
+++ b/jdk/src/java.security.jgss/share/classes/sun/security/krb5/KrbAsReq.java Tue Dec 09 18:28:26 2014 +0800
@@ -35,6 +35,7 @@
import sun.security.krb5.internal.crypto.Nonce;
import sun.security.krb5.internal.crypto.KeyUsage;
import java.io.IOException;
+import java.time.Instant;
/**
* This class encapsulates the KRB-AS-REQ message that the client
@@ -64,7 +65,6 @@
if (options == null) {
options = new KDCOptions();
}
-
// check if they are valid arguments. The optional fields should be
// consistent with settings in KDCOptions. Mar 17 2000
if (options.get(KDCOptions.FORWARDED) ||
@@ -82,12 +82,6 @@
} else {
if (from != null) from = null;
}
- if (options.get(KDCOptions.RENEWABLE)) {
- // if (rtime == null)
- // throw new KrbException(Krb5.KRB_AP_ERR_REQ_OPTIONS);
- } else {
- if (rtime != null) rtime = null;
- }
PAData[] paData = null;
if (pakey != null) {
@@ -109,8 +103,10 @@
System.out.println(">>> KrbAsReq creating message");
}
+ Config cfg = Config.getInstance();
+
// check to use addresses in tickets
- if (addresses == null && Config.getInstance().useAddresses()) {
+ if (addresses == null && cfg.useAddresses()) {
addresses = HostAddresses.getLocalAddresses();
}
@@ -120,7 +116,26 @@
}
if (till == null) {
- till = new KerberosTime(0); // Choose KDC maximum allowed
+ String d = cfg.get("libdefaults", "ticket_lifetime");
+ if (d != null) {
+ till = new KerberosTime(Instant.now().plusSeconds(Config.duration(d)));
+ } else {
+ till = new KerberosTime(0); // Choose KDC maximum allowed
+ }
+ }
+
+ if (rtime == null) {
+ String d = cfg.get("libdefaults", "renew_lifetime");
+ if (d != null) {
+ rtime = new KerberosTime(Instant.now().plusSeconds(Config.duration(d)));
+ }
+ }
+
+ if (rtime != null) {
+ options.set(KDCOptions.RENEWABLE, true);
+ if (till.greaterThan(rtime)) {
+ rtime = till;
+ }
}
// enc-authorization-data and additional-tickets never in AS-REQ
--- a/jdk/src/java.security.jgss/share/classes/sun/security/krb5/KrbAsReqBuilder.java Tue Dec 09 08:57:41 2014 +0100
+++ b/jdk/src/java.security.jgss/share/classes/sun/security/krb5/KrbAsReqBuilder.java Tue Dec 09 18:28:26 2014 +0800
@@ -224,6 +224,16 @@
this.options = options;
}
+ public void setTill(KerberosTime till) {
+ checkState(State.INIT, "Cannot specify till");
+ this.till = till;
+ }
+
+ public void setRTime(KerberosTime rtime) {
+ checkState(State.INIT, "Cannot specify rtime");
+ this.rtime = rtime;
+ }
+
/**
* Sets or clears target. If cleared, KrbAsReq might choose krbtgt
* for cname realm
--- a/jdk/src/java.security.jgss/share/classes/sun/security/krb5/KrbKdcRep.java Tue Dec 09 08:57:41 2014 +0100
+++ b/jdk/src/java.security.jgss/share/classes/sun/security/krb5/KrbKdcRep.java Tue Dec 09 18:28:26 2014 +0800
@@ -80,49 +80,41 @@
rep.encKDCRepPart.flags.get(KDCOptions.RENEWABLE)) {
throw new KrbApErrException(Krb5.KRB_AP_ERR_MODIFIED);
}
- if ((req.reqBody.from == null) || req.reqBody.from.isZero())
+
+ if ((req.reqBody.from == null) || req.reqBody.from.isZero()) {
// verify this is allowed
if ((rep.encKDCRepPart.starttime != null) &&
- !rep.encKDCRepPart.starttime.inClockSkew()) {
+ !rep.encKDCRepPart.starttime.inClockSkew()) {
rep.encKDCRepPart.key.destroy();
throw new KrbApErrException(Krb5.KRB_AP_ERR_SKEW);
}
+ }
- if ((req.reqBody.from != null) && !req.reqBody.from.isZero())
+ if ((req.reqBody.from != null) && !req.reqBody.from.isZero()) {
// verify this is allowed
if ((rep.encKDCRepPart.starttime != null) &&
- !req.reqBody.from.equals(rep.encKDCRepPart.starttime)) {
+ !req.reqBody.from.equals(rep.encKDCRepPart.starttime)) {
rep.encKDCRepPart.key.destroy();
throw new KrbApErrException(Krb5.KRB_AP_ERR_MODIFIED);
}
+ }
if (!req.reqBody.till.isZero() &&
- rep.encKDCRepPart.endtime.greaterThan(req.reqBody.till)) {
+ rep.encKDCRepPart.endtime.greaterThan(req.reqBody.till)) {
rep.encKDCRepPart.key.destroy();
throw new KrbApErrException(Krb5.KRB_AP_ERR_MODIFIED);
}
- if (req.reqBody.kdcOptions.get(KDCOptions.RENEWABLE))
- if (req.reqBody.rtime != null && !req.reqBody.rtime.isZero())
- // verify this is required
+ if (req.reqBody.kdcOptions.get(KDCOptions.RENEWABLE)) {
+ if (req.reqBody.rtime != null && !req.reqBody.rtime.isZero()) {
+ // verify this is required
if ((rep.encKDCRepPart.renewTill == null) ||
- rep.encKDCRepPart.renewTill.greaterThan(req.reqBody.rtime)
- ) {
+ rep.encKDCRepPart.renewTill.greaterThan(req.reqBody.rtime)
+ ) {
rep.encKDCRepPart.key.destroy();
throw new KrbApErrException(Krb5.KRB_AP_ERR_MODIFIED);
}
-
- if (req.reqBody.kdcOptions.get(KDCOptions.RENEWABLE_OK) &&
- rep.encKDCRepPart.flags.get(KDCOptions.RENEWABLE))
- if (!req.reqBody.till.isZero())
- // verify this is required
- if ((rep.encKDCRepPart.renewTill == null) ||
- rep.encKDCRepPart.renewTill.greaterThan(req.reqBody.till)
- ) {
- rep.encKDCRepPart.key.destroy();
- throw new KrbApErrException(Krb5.KRB_AP_ERR_MODIFIED);
- }
+ }
+ }
}
-
-
}
--- a/jdk/src/java.security.jgss/share/classes/sun/security/krb5/KrbTgsReq.java Tue Dec 09 08:57:41 2014 +0100
+++ b/jdk/src/java.security.jgss/share/classes/sun/security/krb5/KrbTgsReq.java Tue Dec 09 18:28:26 2014 +0800
@@ -35,7 +35,6 @@
import sun.security.krb5.internal.crypto.*;
import java.io.IOException;
import java.net.UnknownHostException;
-import java.util.Arrays;
/**
* This class encapsulates a Kerberos TGS-REQ that is sent from the
--- a/jdk/src/java.security.jgss/share/classes/sun/security/krb5/internal/HostAddresses.java Tue Dec 09 08:57:41 2014 +0100
+++ b/jdk/src/java.security.jgss/share/classes/sun/security/krb5/internal/HostAddresses.java Tue Dec 09 18:28:26 2014 +0800
@@ -250,6 +250,10 @@
*/
public void writeAddrs(CCacheOutputStream cos) throws IOException {
+ if (addresses == null || addresses.length == 0) {
+ cos.write32(0);
+ return;
+ }
cos.write32(addresses.length);
for (int i = 0; i < addresses.length; i++) {
cos.write16(addresses[i].addrType);
--- a/jdk/src/java.security.jgss/share/classes/sun/security/krb5/internal/KerberosTime.java Tue Dec 09 08:57:41 2014 +0100
+++ b/jdk/src/java.security.jgss/share/classes/sun/security/krb5/internal/KerberosTime.java Tue Dec 09 18:28:26 2014 +0800
@@ -38,6 +38,7 @@
import sun.security.util.DerValue;
import java.io.IOException;
+import java.time.Instant;
import java.util.Calendar;
import java.util.Date;
import java.util.TimeZone;
@@ -129,6 +130,14 @@
}
/**
+ * Creates a KerberosTime object from an Instant object
+ */
+ public KerberosTime(Instant instant) {
+ this(instant.getEpochSecond()*1000 + instant.getNano()/1000000L,
+ instant.getNano()/1000%1000);
+ }
+
+ /**
* Creates a KerberosTime object for now. It uses System.nanoTime()
* to get a more precise time than "new Date()".
*/
--- a/jdk/src/java.security.jgss/windows/classes/sun/security/krb5/internal/tools/Kinit.java Tue Dec 09 08:57:41 2014 +0100
+++ b/jdk/src/java.security.jgss/windows/classes/sun/security/krb5/internal/tools/Kinit.java Tue Dec 09 18:28:26 2014 +0800
@@ -36,7 +36,6 @@
import sun.security.krb5.internal.ccache.*;
import java.io.IOException;
import java.util.Arrays;
-import javax.security.auth.kerberos.KerberosPrincipal;
import sun.security.util.Password;
import javax.security.auth.kerberos.KeyTab;
@@ -53,22 +52,9 @@
/**
* The main method is used to accept user command line input for ticket
- * request.
- * <p>
- * Usage: kinit [-A] [-f] [-p] [-c cachename] [[-k [-t keytab_file_name]]
- * [principal] [password]
- * <ul>
- * <li> -A do not include addresses
- * <li> -f forwardable
- * <li> -p proxiable
- * <li> -c cache name (i.e., FILE://c:\temp\mykrb5cc)
- * <li> -k use keytab
- * <li> -t keytab file name
- * <li> principal the principal name (i.e., duke@java.sun.com)
- * <li> password the principal's Kerberos password
- * </ul>
- * <p>
- * Use java sun.security.krb5.tools.Kinit -help to bring up help menu.
+ * request. Read {@link KinitOptions#printHelp} for usages or call
+ * java sun.security.krb5.internal.tools.Kinit -help
+ * to bring up help menu.
* <p>
* We currently support only file-based credentials cache to
* store the tickets obtained from the KDC.
@@ -146,6 +132,49 @@
} else {
options = new KinitOptions(args);
}
+ switch (options.action) {
+ case 1:
+ acquire();
+ break;
+ case 2:
+ renew();
+ break;
+ default:
+ throw new KrbException("kinit does not support action "
+ + options.action);
+ }
+ }
+
+ private void renew()
+ throws IOException, RealmException, KrbException {
+
+ PrincipalName principal = options.getPrincipal();
+ String realm = principal.getRealmAsString();
+ CredentialsCache cache = CredentialsCache.getInstance(options.cachename);
+
+ if (cache == null) {
+ throw new IOException("Unable to find existing cache file " +
+ options.cachename);
+ }
+ sun.security.krb5.internal.ccache.Credentials credentials =
+ cache.getCreds(PrincipalName.tgsService(realm, realm));
+
+ credentials = credentials.setKrbCreds()
+ .renew()
+ .toCCacheCreds();
+
+ cache = CredentialsCache.create(principal, options.cachename);
+ if (cache == null) {
+ throw new IOException("Unable to create the cache file " +
+ options.cachename);
+ }
+ cache.update(credentials);
+ cache.save();
+ }
+
+ private void acquire()
+ throws IOException, RealmException, KrbException {
+
String princName = null;
PrincipalName principal = options.getPrincipal();
if (principal != null) {
@@ -216,6 +245,9 @@
if (options.getAddressOption())
builder.setAddresses(HostAddresses.getLocalAddresses());
+ builder.setTill(options.lifetime);
+ builder.setRTime(options.renewable_lifetime);
+
builder.action();
sun.security.krb5.internal.ccache.Credentials credentials =
--- a/jdk/src/java.security.jgss/windows/classes/sun/security/krb5/internal/tools/KinitOptions.java Tue Dec 09 08:57:41 2014 +0100
+++ b/jdk/src/java.security.jgss/windows/classes/sun/security/krb5/internal/tools/KinitOptions.java Tue Dec 09 18:28:26 2014 +0800
@@ -33,12 +33,8 @@
import sun.security.krb5.*;
import sun.security.krb5.internal.*;
import sun.security.krb5.internal.ccache.*;
-import java.io.File;
import java.io.IOException;
-import java.util.StringTokenizer;
-import java.util.Vector;
-import java.io.BufferedReader;
-import java.io.InputStreamReader;
+import java.time.Instant;
import java.io.FileInputStream;
/**
@@ -49,14 +45,15 @@
* @author Ram Marti
*/
class KinitOptions {
- public boolean validate = false;
+
+ // 1. acquire, 2. renew, 3. validate
+ public int action = 1;
// forwardable and proxiable flags have two states:
// -1 - flag set to be not forwardable or proxiable;
// 1 - flag set to be forwardable or proxiable.
- public short forwardable = -1;
- public short proxiable = -1;
- public boolean renew = false;
+ public short forwardable = 0;
+ public short proxiable = 0;
public KerberosTime lifetime;
public KerberosTime renewable_lifetime;
public String target_service;
@@ -134,6 +131,12 @@
}
useKeytab = true;
+ } else if (args[i].equals("-R")) {
+ action = 2;
+ } else if (args[i].equals("-l")) {
+ lifetime = getTime(Config.duration(args[++i]));
+ } else if (args[i].equals("-r")) {
+ renewable_lifetime = getTime(Config.duration(args[++i]));
} else if (args[i].equalsIgnoreCase("-help")) {
printHelp();
System.exit(0);
@@ -223,23 +226,28 @@
void printHelp() {
- System.out.println("Usage: kinit " +
- "[-A] [-f] [-p] [-c cachename] " +
- "[[-k [-t keytab_file_name]] [principal] " +
+ System.out.println("Usage:\n\n1. Initial ticket request:\n" +
+ " kinit [-A] [-f] [-p] [-c cachename] " +
+ "[-l lifetime] [-r renewable_time]\n" +
+ " [[-k [-t keytab_file_name]] [principal] " +
"[password]");
- System.out.println("\tavailable options to " +
+ System.out.println("2. Renew a ticket:\n" +
+ " kinit -R [-c cachename] [principal]");
+ System.out.println("\nAvailable options to " +
"Kerberos 5 ticket request:");
- System.out.println("\t -A do not include addresses");
- System.out.println("\t -f forwardable");
- System.out.println("\t -p proxiable");
- System.out.println("\t -c cache name " +
- "(i.e., FILE:\\d:\\myProfiles\\mykrb5cache)");
- System.out.println("\t -k use keytab");
- System.out.println("\t -t keytab file name");
- System.out.println("\t principal the principal name "+
- "(i.e., qweadf@ATHENA.MIT.EDU qweadf)");
- System.out.println("\t password " +
- "the principal's Kerberos password");
+ System.out.println("\t-A do not include addresses");
+ System.out.println("\t-f forwardable");
+ System.out.println("\t-p proxiable");
+ System.out.println("\t-c cache name " +
+ "(i.e., FILE:\\d:\\myProfiles\\mykrb5cache)");
+ System.out.println("\t-l lifetime");
+ System.out.println("\t-r renewable time " +
+ "(total lifetime a ticket can be renewed)");
+ System.out.println("\t-k use keytab");
+ System.out.println("\t-t keytab file name");
+ System.out.println("\tprincipal the principal name "+
+ "(i.e., qweadf@ATHENA.MIT.EDU qweadf)");
+ System.out.println("\tpassword the principal's Kerberos password");
}
public boolean getAddressOption() {
@@ -257,4 +265,8 @@
public PrincipalName getPrincipal() {
return principal;
}
+
+ private KerberosTime getTime(int s) {
+ return new KerberosTime(Instant.now().plusSeconds(s));
+ }
}
--- a/jdk/test/sun/security/krb5/auto/KDC.java Tue Dec 09 08:57:41 2014 +0100
+++ b/jdk/test/sun/security/krb5/auto/KDC.java Tue Dec 09 18:28:26 2014 +0800
@@ -35,6 +35,7 @@
import sun.security.krb5.*;
import sun.security.krb5.internal.*;
import sun.security.krb5.internal.ccache.CredentialsCache;
+import sun.security.krb5.internal.crypto.EType;
import sun.security.krb5.internal.crypto.KeyUsage;
import sun.security.krb5.internal.ktab.KeyTab;
import sun.security.util.DerInputStream;
@@ -120,6 +121,9 @@
*/
public class KDC {
+ public static final int DEFAULT_LIFETIME = 39600;
+ public static final int DEFAULT_RENEWTIME = 86400;
+
// Under the hood.
// The random generator to generate random keys (including session keys)
@@ -204,7 +208,8 @@
* A standalone KDC server.
*/
public static void main(String[] args) throws Exception {
- KDC kdc = create("RABBIT.HOLE", "kdc.rabbit.hole", 0, false);
+ int port = args.length > 0 ? Integer.parseInt(args[0]) : 0;
+ KDC kdc = create("RABBIT.HOLE", "kdc.rabbit.hole", port, false);
kdc.addPrincipal("dummy", "bogus".toCharArray());
kdc.addPrincipal("foo", "bar".toCharArray());
kdc.addPrincipalRandKey("krbtgt/RABBIT.HOLE");
@@ -871,6 +876,14 @@
eTypes = KDCReqBodyDotEType(body);
int eType = eTypes[0];
+ // Maybe server does not support aes256, but a kinit does
+ if (!EType.isSupported(eType)) {
+ if (eTypes.length < 2) {
+ throw new KrbException(Krb5.KDC_ERR_ETYPE_NOSUPP);
+ }
+ eType = eTypes[1];
+ }
+
EncryptionKey ckey = keyForUser(body.cname, eType, false);
EncryptionKey skey = keyForUser(service, eType, true);
@@ -899,10 +912,16 @@
EncryptionKey key = generateRandomKey(eType);
// Check time, TODO
KerberosTime till = body.till;
+ KerberosTime rtime = body.rtime;
if (till == null) {
throw new KrbException(Krb5.KDC_ERR_NEVER_VALID); // TODO
} else if (till.isZero()) {
- till = new KerberosTime(new Date().getTime() + 1000 * 3600 * 11);
+ till = new KerberosTime(
+ new Date().getTime() + 1000 * DEFAULT_LIFETIME);
+ }
+ if (rtime == null && body.kdcOptions.get(KDCOptions.RENEWABLE)) {
+ rtime = new KerberosTime(
+ new Date().getTime() + 1000 * DEFAULT_RENEWTIME);
}
//body.from
boolean[] bFlags = new boolean[Krb5.TKT_OPTS_MAX+1];
@@ -1053,7 +1072,7 @@
new TransitedEncoding(1, new byte[0]),
new KerberosTime(new Date()),
body.from,
- till, body.rtime,
+ till, rtime,
body.addresses,
null);
Ticket t = new Ticket(
@@ -1071,7 +1090,7 @@
tFlags,
new KerberosTime(new Date()),
body.from,
- till, body.rtime,
+ till, rtime,
service,
body.addresses
);
--- a/jdk/test/sun/security/krb5/auto/LifeTimeInSeconds.java Tue Dec 09 08:57:41 2014 +0100
+++ b/jdk/test/sun/security/krb5/auto/LifeTimeInSeconds.java Tue Dec 09 18:28:26 2014 +0800
@@ -40,7 +40,7 @@
int time = cred.getRemainingLifetime();
int time2 = cred.getRemainingInitLifetime(null);
// The test KDC issues a TGT with a default lifetime of 11 hours
- int elevenhrs = 11*3600;
+ int elevenhrs = KDC.DEFAULT_LIFETIME;
if (time > elevenhrs+60 || time < elevenhrs-60) {
throw new Exception("getRemainingLifetime returns wrong value.");
}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/sun/security/krb5/auto/Renewal.java Tue Dec 09 18:28:26 2014 +0800
@@ -0,0 +1,164 @@
+/*
+ * Copyright (c) 2012, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/*
+ * @test
+ * @bug 8044500
+ * @summary Add kinit options and krb5.conf flags that allow users to
+ * obtain renewable tickets and specify ticket lifetimes
+ * @library ../../../../java/security/testlibrary/
+ * @compile -XDignore.symbol.file Renewal.java
+ * @run main/othervm Renewal
+ */
+
+import sun.security.jgss.GSSUtil;
+import sun.security.krb5.Config;
+import sun.security.krb5.internal.ccache.Credentials;
+import sun.security.krb5.internal.ccache.FileCredentialsCache;
+
+import javax.security.auth.kerberos.KerberosTicket;
+import java.util.Date;
+import java.util.Random;
+import java.util.Set;
+
+// The basic krb5 test skeleton you can copy from
+public class Renewal {
+
+ static OneKDC kdc;
+ static String clazz = "sun.security.krb5.internal.tools.Kinit";
+
+ public static void main(String[] args) throws Exception {
+
+ kdc = new OneKDC(null);
+ kdc.writeJAASConf();
+ kdc.setOption(KDC.Option.PREAUTH_REQUIRED, false);
+
+ checkLogin(null, null, KDC.DEFAULT_LIFETIME, -1);
+ checkLogin("1h", null, 3600, -1);
+ checkLogin(null, "2d", KDC.DEFAULT_LIFETIME, 86400*2);
+ checkLogin("1h", "10h", 3600, 36000);
+ // When rtime is before till, use till as rtime
+ checkLogin("10h", "1h", 36000, 36000);
+
+ try {
+ Class.forName(clazz);
+ } catch (ClassNotFoundException cnfe) {
+ return;
+ }
+
+ checkKinit(null, null, null, null, KDC.DEFAULT_LIFETIME, -1);
+ checkKinit("1h", "10h", null, null, 3600, 36000);
+ checkKinit(null, null, "30m", "5h", 1800, 18000);
+ checkKinit("1h", "10h", "30m", "5h", 1800, 18000);
+
+ checkKinitRenew();
+ }
+
+ static int count = 0;
+
+ static void checkKinit(
+ String s1, // ticket_lifetime in krb5.conf, null if none
+ String s2, // renew_lifetime in krb5.conf, null if none
+ String c1, // -l on kinit, null if none
+ String c2, // -r on kinit, null if none
+ int t1, int t2 // expected lifetimes, -1 of unexpected
+ ) throws Exception {
+ KDC.saveConfig(OneKDC.KRB5_CONF, kdc,
+ s1 != null ? ("ticket_lifetime = " + s1) : "",
+ s2 != null ? ("renew_lifetime = " + s2) : "");
+ Proc p = Proc.create(clazz);
+ if (c1 != null) {
+ p.args("-l", c1);
+ }
+ if (c2 != null) {
+ p.args("-r", c2);
+ }
+ count++;
+ p.args(OneKDC.USER, new String(OneKDC.PASS))
+ .inheritIO()
+ .prop("sun.net.spi.nameservice.provider.1", "ns,mock")
+ .prop("java.security.krb5.conf", OneKDC.KRB5_CONF)
+ .env("KRB5CCNAME", "ccache" + count)
+ .start();
+ if (p.waitFor() != 0) {
+ throw new Exception();
+ }
+ FileCredentialsCache fcc =
+ FileCredentialsCache.acquireInstance(null, "ccache" + count);
+ Credentials cred = fcc.getDefaultCreds();
+ checkRough(cred.getEndTime().toDate(), t1);
+ if (cred.getRenewTill() == null) {
+ checkRough(null, t2);
+ } else {
+ checkRough(cred.getRenewTill().toDate(), t2);
+ }
+ }
+
+ static void checkKinitRenew() throws Exception {
+ Proc p = Proc.create(clazz)
+ .args("-R")
+ .inheritIO()
+ .prop("sun.net.spi.nameservice.provider.1", "ns,mock")
+ .prop("java.security.krb5.conf", OneKDC.KRB5_CONF)
+ .env("KRB5CCNAME", "ccache" + count)
+ .start();
+ if (p.waitFor() != 0) {
+ throw new Exception();
+ }
+ }
+
+ static void checkLogin(
+ String s1, // ticket_lifetime in krb5.conf, null if none
+ String s2, // renew_lifetime in krb5.conf, null if none
+ int t1, int t2 // expected lifetimes, -1 of unexpected
+ ) throws Exception {
+ KDC.saveConfig(OneKDC.KRB5_CONF, kdc,
+ s1 != null ? ("ticket_lifetime = " + s1) : "",
+ s2 != null ? ("renew_lifetime = " + s2) : "");
+ Config.refresh();
+
+ Context c;
+ c = Context.fromJAAS("client");
+
+ Set<KerberosTicket> tickets =
+ c.s().getPrivateCredentials(KerberosTicket.class);
+ if (tickets.size() != 1) {
+ throw new Exception();
+ }
+ KerberosTicket ticket = tickets.iterator().next();
+
+ checkRough(ticket.getEndTime(), t1);
+ checkRough(ticket.getRenewTill(), t2);
+ }
+
+ static void checkRough(Date t, int duration) throws Exception {
+ Date now = new Date();
+ if (t == null && duration == -1) {
+ return;
+ }
+ long change = (t.getTime() - System.currentTimeMillis()) / 1000;
+ if (change > duration + 20 || change < duration - 20) {
+ throw new Exception(t + " is not " + duration);
+ }
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/sun/security/krb5/config/Duration.java Tue Dec 09 18:28:26 2014 +0800
@@ -0,0 +1,71 @@
+/*
+ * Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/*
+ * @test
+ * @bug 8044500
+ * @summary Add kinit options and krb5.conf flags that allow users to
+ * obtain renewable tickets and specify ticket lifetimes
+ * @compile -XDignore.symbol.file Duration.java
+ * @run main Duration
+ */
+import sun.security.krb5.Config;
+import sun.security.krb5.KrbException;
+
+public class Duration {
+ public static void main(String[] args) throws Exception {
+ check("123", 123);
+ check("1:1", 3660);
+ check("1:1:1", 3661);
+ check("1d", 86400);
+ check("1h", 3600);
+ check("1h1m", 3660);
+ check("1h 1m", 3660);
+ check("1d 1h 1m 1s", 90061);
+ check("1d1h1m1s", 90061);
+
+ check("", -1);
+ check("abc", -1);
+ check("1ms", -1);
+ check("1d1d", -1);
+ check("1h1d", -1);
+ check("x1h", -1);
+ check("1h x 1m", -1);
+ check(":", -1);
+ check("1:60", -1);
+ check("1:1:1:1", -1);
+ check("1:1:1:", -1);
+ }
+
+ static void check(String s, int ex) throws Exception {
+ System.out.print("\u001b[1;37;41m" +s + " " + ex);
+ System.out.print("\u001b[m\n");
+ try {
+ int result = Config.duration(s);
+ if (result != ex) throw new Exception("for " + s + " is " + result);
+ } catch (KrbException ke) {
+ ke.printStackTrace();
+ if (ex != -1) throw new Exception();
+ }
+ }
+}