--- old/src/share/classes/sun/security/jgss/krb5/Krb5InitCredential.java 2018-09-19 13:52:41.516194356 +0530 +++ new/src/share/classes/sun/security/jgss/krb5/Krb5InitCredential.java 2018-09-19 13:52:41.216194356 +0530 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2000, 2012, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2000, 2018, 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 @@ -234,14 +234,13 @@ * @exception GSSException may be thrown */ public int getInitLifetime() throws GSSException { - int retVal = 0; Date d = getEndTime(); if (d == null) { return 0; } - retVal = (int)(d.getTime() - (new Date().getTime())); - return retVal/1000; + long retVal = d.getTime() - System.currentTimeMillis(); + return (int)(retVal/1000); } /** --- old/src/share/classes/sun/security/krb5/KrbKdcRep.java 2018-09-19 13:52:42.392194356 +0530 +++ new/src/share/classes/sun/security/krb5/KrbKdcRep.java 2018-09-19 13:52:42.024194356 +0530 @@ -75,10 +75,11 @@ } } - // XXX Can renew a ticket but not ask for a renewable renewed ticket - // See impl of Credentials.renew(). - if (req.reqBody.kdcOptions.get(KDCOptions.RENEWABLE) != - rep.encKDCRepPart.flags.get(KDCOptions.RENEWABLE)) { + // Reply to a renewable request should be renewable, but if request does + // not contain renewable, KDC is free to issue a renewable ticket (for + // example, if ticket_lifetime is too big). + if (req.reqBody.kdcOptions.get(KDCOptions.RENEWABLE) && + !rep.encKDCRepPart.flags.get(KDCOptions.RENEWABLE)) { throw new KrbApErrException(Krb5.KRB_AP_ERR_MODIFIED); } if ((req.reqBody.from == null) || req.reqBody.from.isZero()) --- old/test/sun/security/krb5/auto/KDC.java 2018-09-19 13:52:43.304194356 +0530 +++ new/test/sun/security/krb5/auto/KDC.java 2018-09-19 13:52:42.956194356 +0530 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008, 2017, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2008, 2018, 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 @@ -28,6 +28,10 @@ import java.io.*; import java.lang.reflect.Method; import java.security.SecureRandom; +import java.time.Instant; +import java.time.temporal.ChronoUnit; +import java.time.temporal.TemporalAmount; +import java.time.temporal.TemporalUnit; import java.util.*; import java.util.concurrent.*; @@ -41,6 +45,8 @@ import sun.security.util.DerInputStream; import sun.security.util.DerOutputStream; import sun.security.util.DerValue; +import java.util.regex.Matcher; +import java.util.regex.Pattern; /** * A KDC server. @@ -123,6 +129,9 @@ // Under the hood. + public static final int DEFAULT_LIFETIME = 39600; + public static final int DEFAULT_RENEWTIME = 86400; + // The random generator to generate random keys (including session keys) private static SecureRandom secureRandom = new SecureRandom(); @@ -620,6 +629,15 @@ } /** + * Returns a KerberosTime. + * + * @param offset offset from NOW in seconds + */ + private static KerberosTime timeAfter(int offset) { + return new KerberosTime(new Date().getTime() + offset * 1000L); + } + + /** * Processes an incoming request and generates a response. * @param in the request * @return the response @@ -919,11 +937,29 @@ 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); + String ttlsVal = System.getProperty("test.kdc.ttl.value"); + if (ttlsVal != null){ + till = timeAfter(duration(ttlsVal)); + if(till.greaterThan(timeAfter(24 * 3600)) && + (System.getProperty("test.kdc.force.till") == null)) { + till = timeAfter(DEFAULT_LIFETIME); + body.kdcOptions.set(KDCOptions.RENEWABLE, true); + } + + } + else{ + till = timeAfter(DEFAULT_LIFETIME); + } } + + if (rtime == null && body.kdcOptions.get(KDCOptions.RENEWABLE)) { + rtime = timeAfter(DEFAULT_RENEWTIME); + } + //body.from boolean[] bFlags = new boolean[Krb5.TKT_OPTS_MAX+1]; if (body.kdcOptions.get(KDCOptions.FORWARDABLE)) { @@ -1079,7 +1115,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( @@ -1097,7 +1133,7 @@ tFlags, new KerberosTime(new Date()), body.from, - till, body.rtime, + till, rtime, service, body.addresses ); @@ -1169,6 +1205,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); + } + + /** * Generates a line for a KDC to put inside [realms] of krb5.conf * @return REALM.NAME = { kdc = host:port etc } */ --- /dev/null 2018-09-19 08:06:20.779357000 +0530 +++ new/test/sun/security/krb5/auto/LongLife.java 2018-09-19 13:52:43.796194356 +0530 @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2018, 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 8131051 8187218 + * @summary KDC might issue a renewable ticket even if not requested + * @compile -XDignore.symbol.file LongLife.java + * @run main/othervm -Dsun.net.spi.nameservice.provider.1=ns,mock LongLife + */ + +import org.ietf.jgss.GSSCredential; +import org.ietf.jgss.GSSManager; +import sun.security.krb5.Config; +import javax.security.auth.Subject; +import javax.security.auth.kerberos.KerberosTicket; +import java.security.PrivilegedExceptionAction; + +public class LongLife { + + public static void main(String[] args) throws Exception { + + OneKDC kdc = new OneKDC(null).writeJAASConf(); + + test(kdc, "10h", false, 36000, false); + test(kdc, "2d", false, KDC.DEFAULT_LIFETIME, true); + test(kdc, "2d", true, 2 * 24 * 3600, false); + + // 8187218: getRemainingLifetime() is negative if lifetime + // is longer than 30 days. + test(kdc, "30d", true, 30 * 24 * 3600, false); + } + + static void test( + KDC kdc, + String ticketLifetime, + boolean forceTill, // if true, KDC will not try RENEWABLE + int expectedLifeTime, + boolean expectedRenewable) throws Exception { + + KDC.saveConfig(OneKDC.KRB5_CONF, kdc); + Config.refresh(); + + System.setProperty("test.kdc.ttl.value", ticketLifetime); + + if (forceTill) { + System.setProperty("test.kdc.force.till", ""); + } else { + System.clearProperty("test.kdc.force.till"); + } + + Context c = Context.fromJAAS("client"); + + GSSCredential cred = Subject.doAs(c.s(), + (PrivilegedExceptionAction) + ()-> { + GSSManager m = GSSManager.getInstance(); + return m.createCredential(GSSCredential.INITIATE_ONLY); + }); + + KerberosTicket tgt = c.s().getPrivateCredentials(KerberosTicket.class) + .iterator().next(); + System.out.println(tgt); + + int actualLifeTime = cred.getRemainingLifetime(); + if (actualLifeTime < (expectedLifeTime - 60 ) + || actualLifeTime > (expectedLifeTime + 60)) { + throw new Exception("actualLifeTime is " + actualLifeTime + + " ExpectedLifeTime is " + expectedLifeTime); + } + + if (tgt.isRenewable() != expectedRenewable) { + throw new Exception("TGT's RENEWABLE flag is " + tgt.isRenewable()); + } + } +}