changeset 9893:c8c25cfc00ec

8187218: GSSCredential.getRemainingLifetime() returns negative value for TTL > 24 days. 8131051: KDC might issue a renewable ticket even if not requested Reviewed-by: mbalao
author andrew
date Mon, 03 Feb 2020 03:52:08 +0000
parents ff51e99d392b
children 368b85934a10
files src/share/classes/sun/security/jgss/krb5/Krb5InitCredential.java src/share/classes/sun/security/krb5/KrbKdcRep.java test/sun/security/krb5/auto/KDC.java test/sun/security/krb5/auto/LongLife.java
diffstat 4 files changed, 196 insertions(+), 13 deletions(-) [+]
line wrap: on
line diff
--- a/src/share/classes/sun/security/jgss/krb5/Krb5InitCredential.java	Mon Feb 03 02:52:09 2020 +0000
+++ b/src/share/classes/sun/security/jgss/krb5/Krb5InitCredential.java	Mon Feb 03 03:52:08 2020 +0000
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2000, 2009, 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);
     }
 
     /**
--- a/src/share/classes/sun/security/krb5/KrbKdcRep.java	Mon Feb 03 02:52:09 2020 +0000
+++ b/src/share/classes/sun/security/krb5/KrbKdcRep.java	Mon Feb 03 03:52:08 2020 +0000
@@ -69,10 +69,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);
         }
 
--- a/test/sun/security/krb5/auto/KDC.java	Mon Feb 03 02:52:09 2020 +0000
+++ b/test/sun/security/krb5/auto/KDC.java	Mon Feb 03 03:52:08 2020 +0000
@@ -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
@@ -42,6 +42,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.
@@ -657,6 +659,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
@@ -913,12 +924,20 @@
             if (till == null) {
                 throw new KrbException(Krb5.KDC_ERR_NEVER_VALID); // TODO
             } else if (till.isZero()) {
-                till = new KerberosTime(
-                        new Date().getTime() + 1000 * DEFAULT_LIFETIME);
+                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 = new KerberosTime(
-                        new Date().getTime() + 1000 * DEFAULT_RENEWTIME);
+                rtime = timeAfter(DEFAULT_RENEWTIME);
             }
             //body.from
             boolean[] bFlags = new boolean[Krb5.TKT_OPTS_MAX+1];
@@ -1158,6 +1177,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	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/sun/security/krb5/auto/LongLife.java	Mon Feb 03 03:52:08 2020 +0000
@@ -0,0 +1,98 @@
+/*
+ * 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(),
+            new PrivilegedExceptionAction<GSSCredential>() {
+                @Override
+                public GSSCredential run() throws Exception {
+                    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());
+        }
+    }
+}