8005523: Unbound krb5 for TLS
authorweijun
Sat, 27 Apr 2013 18:25:16 +0800
changeset 17189 9f2ae085280b
parent 17188 5e58e261911b
child 17190 7e650321026c
8005523: Unbound krb5 for TLS Reviewed-by: xuelei
jdk/src/share/classes/sun/security/ssl/KerberosClientKeyExchange.java
jdk/src/share/classes/sun/security/ssl/Krb5Helper.java
jdk/src/share/classes/sun/security/ssl/Krb5Proxy.java
jdk/src/share/classes/sun/security/ssl/ServerHandshaker.java
jdk/src/share/classes/sun/security/ssl/krb5/KerberosClientKeyExchangeImpl.java
jdk/src/share/classes/sun/security/ssl/krb5/Krb5ProxyImpl.java
jdk/test/sun/security/krb5/auto/SSL.java
--- a/jdk/src/share/classes/sun/security/ssl/KerberosClientKeyExchange.java	Fri Apr 26 16:09:53 2013 -0700
+++ b/jdk/src/share/classes/sun/security/ssl/KerberosClientKeyExchange.java	Sat Apr 27 18:25:16 2013 +0800
@@ -86,10 +86,10 @@
 
     public KerberosClientKeyExchange(ProtocolVersion protocolVersion,
         ProtocolVersion clientVersion, SecureRandom rand,
-        HandshakeInStream input, SecretKey[] serverKeys) throws IOException {
+        HandshakeInStream input, AccessControlContext acc, Object serverKeys) throws IOException {
 
         if (impl != null) {
-            init(protocolVersion, clientVersion, rand, input, serverKeys);
+            init(protocolVersion, clientVersion, rand, input, acc, serverKeys);
         } else {
             throw new IllegalStateException("Kerberos is unavailable");
         }
@@ -126,10 +126,10 @@
 
     public void init(ProtocolVersion protocolVersion,
         ProtocolVersion clientVersion, SecureRandom rand,
-        HandshakeInStream input, SecretKey[] serverKeys) throws IOException {
+        HandshakeInStream input, AccessControlContext acc, Object ServiceCreds) throws IOException {
 
         if (impl != null) {
-            impl.init(protocolVersion, clientVersion, rand, input, serverKeys);
+            impl.init(protocolVersion, clientVersion, rand, input, acc, ServiceCreds);
         }
     }
 
--- a/jdk/src/share/classes/sun/security/ssl/Krb5Helper.java	Fri Apr 26 16:09:53 2013 -0700
+++ b/jdk/src/share/classes/sun/security/ssl/Krb5Helper.java	Sat Apr 27 18:25:16 2013 +0800
@@ -94,18 +94,18 @@
     /**
      * Returns the KerberosKeys for the default server-side principal.
      */
-    public static SecretKey[] getServerKeys(AccessControlContext acc)
+    public static Object getServiceCreds(AccessControlContext acc)
             throws LoginException {
         ensureAvailable();
-        return proxy.getServerKeys(acc);
+        return proxy.getServiceCreds(acc);
     }
 
     /**
      * Returns the server-side principal name associated with the KerberosKey.
      */
-    public static String getServerPrincipalName(SecretKey kerberosKey) {
+    public static String getServerPrincipalName(Object serviceCreds) {
         ensureAvailable();
-        return proxy.getServerPrincipalName(kerberosKey);
+        return proxy.getServerPrincipalName(serviceCreds);
     }
 
     /**
@@ -124,4 +124,12 @@
         ensureAvailable();
         return proxy.getServicePermission(principalName, action);
     }
+
+    /**
+     * Determines if the Subject might contain creds for princ.
+     */
+    public static boolean isRelated(Subject subject, Principal princ) {
+        ensureAvailable();
+        return proxy.isRelated(subject, princ);
+    }
 }
--- a/jdk/src/share/classes/sun/security/ssl/Krb5Proxy.java	Fri Apr 26 16:09:53 2013 -0700
+++ b/jdk/src/share/classes/sun/security/ssl/Krb5Proxy.java	Sat Apr 27 18:25:16 2013 +0800
@@ -50,14 +50,14 @@
 
 
     /**
-     * Returns the KerberosKeys for the default server-side principal.
+     * Returns the Kerberos ServiceCreds for the default server-side principal.
      */
-    SecretKey[] getServerKeys(AccessControlContext acc) throws LoginException;
+    Object getServiceCreds(AccessControlContext acc) throws LoginException;
 
     /**
      * Returns the server-side principal name associated with the KerberosKey.
      */
-    String getServerPrincipalName(SecretKey kerberosKey);
+    String getServerPrincipalName(Object serviceCreds);
 
     /**
      * Returns the hostname embedded in the principal name.
@@ -68,4 +68,9 @@
      * Returns a ServicePermission for the principal name and action.
      */
     Permission getServicePermission(String principalName, String action);
+
+    /**
+     * Determines if the Subject might contain creds for princ.
+     */
+    boolean isRelated(Subject subject, Principal princ);
 }
--- a/jdk/src/share/classes/sun/security/ssl/ServerHandshaker.java	Fri Apr 26 16:09:53 2013 -0700
+++ b/jdk/src/share/classes/sun/security/ssl/ServerHandshaker.java	Sat Apr 27 18:25:16 2013 +0800
@@ -62,7 +62,7 @@
     private X509Certificate[]   certs;
     private PrivateKey          privateKey;
 
-    private SecretKey[]       kerberosKeys;
+    private Object              serviceCreds;
 
     // flag to check for clientCertificateVerify message
     private boolean             needClientVerify = false;
@@ -200,7 +200,8 @@
                             clientRequestedVersion,
                             sslContext.getSecureRandom(),
                             input,
-                            kerberosKeys));
+                            this.getAccSE(),
+                            serviceCreds));
                     break;
                 case K_DHE_RSA:
                 case K_DHE_DSS:
@@ -543,18 +544,15 @@
 
                         if (subject != null) {
                             // Eliminate dependency on KerberosPrincipal
-                            Set<Principal> principals =
-                                subject.getPrincipals(Principal.class);
-                            if (!principals.contains(localPrincipal)) {
+                            if (Krb5Helper.isRelated(subject, localPrincipal)) {
+                                if (debug != null && Debug.isOn("session"))
+                                    System.out.println("Subject can" +
+                                            " provide creds for princ");
+                            } else {
                                 resumingSession = false;
-                                if (debug != null && Debug.isOn("session")) {
-                                    System.out.println("Subject identity" +
-                                                        " is not the same");
-                                }
-                            } else {
                                 if (debug != null && Debug.isOn("session"))
-                                    System.out.println("Subject identity" +
-                                                        " is same");
+                                    System.out.println("Subject cannot" +
+                                            " provide creds for princ");
                             }
                         } else {
                             resumingSession = false;
@@ -1316,49 +1314,51 @@
      * @return true if successful, false if not available or invalid
      */
     private boolean setupKerberosKeys() {
-        if (kerberosKeys != null) {
+        if (serviceCreds != null) {
             return true;
         }
         try {
             final AccessControlContext acc = getAccSE();
-            kerberosKeys = AccessController.doPrivileged(
+            serviceCreds = AccessController.doPrivileged(
                 // Eliminate dependency on KerberosKey
-                new PrivilegedExceptionAction<SecretKey[]>() {
+                new PrivilegedExceptionAction<Object>() {
                 @Override
-                public SecretKey[] run() throws Exception {
+                public Object run() throws Exception {
                     // get kerberos key for the default principal
-                    return Krb5Helper.getServerKeys(acc);
+                    return Krb5Helper.getServiceCreds(acc);
                         }});
 
             // check permission to access and use the secret key of the
             // Kerberized "host" service
-            if (kerberosKeys != null && kerberosKeys.length > 0) {
+            if (serviceCreds != null) {
                 if (debug != null && Debug.isOn("handshake")) {
-                    for (SecretKey k: kerberosKeys) {
-                        System.out.println("Using Kerberos key: " +
-                            k);
+                    System.out.println("Using Kerberos creds");
+                }
+                String serverPrincipal =
+                        Krb5Helper.getServerPrincipalName(serviceCreds);
+                if (serverPrincipal != null) {
+                    // When service is bound, we check ASAP. Otherwise,
+                    // will check after client request is received
+                    // in in Kerberos ClientKeyExchange
+                    SecurityManager sm = System.getSecurityManager();
+                    try {
+                        if (sm != null) {
+                            // Eliminate dependency on ServicePermission
+                            sm.checkPermission(Krb5Helper.getServicePermission(
+                                    serverPrincipal, "accept"), acc);
+                        }
+                    } catch (SecurityException se) {
+                        serviceCreds = null;
+                        // Do not destroy keys. Will affect Subject
+                        if (debug != null && Debug.isOn("handshake")) {
+                            System.out.println("Permission to access Kerberos"
+                                    + " secret key denied");
+                        }
+                        return false;
                     }
                 }
-
-                String serverPrincipal =
-                    Krb5Helper.getServerPrincipalName(kerberosKeys[0]);
-                SecurityManager sm = System.getSecurityManager();
-                try {
-                   if (sm != null) {
-                      // Eliminate dependency on ServicePermission
-                      sm.checkPermission(Krb5Helper.getServicePermission(
-                          serverPrincipal, "accept"), acc);
-                   }
-                } catch (SecurityException se) {
-                   kerberosKeys = null;
-                   // %%% destroy keys? or will that affect Subject?
-                   if (debug != null && Debug.isOn("handshake"))
-                      System.out.println("Permission to access Kerberos"
-                                + " secret key denied");
-                   return false;
-                }
             }
-            return (kerberosKeys != null && kerberosKeys.length > 0);
+            return serviceCreds != null;
         } catch (PrivilegedActionException e) {
             // Likely exception here is LoginExceptin
             if (debug != null && Debug.isOn("handshake")) {
--- a/jdk/src/share/classes/sun/security/ssl/krb5/KerberosClientKeyExchangeImpl.java	Fri Apr 26 16:09:53 2013 -0700
+++ b/jdk/src/share/classes/sun/security/ssl/krb5/KerberosClientKeyExchangeImpl.java	Sat Apr 27 18:25:16 2013 +0800
@@ -33,8 +33,8 @@
 import java.security.PrivilegedActionException;
 import java.security.SecureRandom;
 import java.net.InetAddress;
+import java.security.PrivilegedAction;
 
-import javax.crypto.SecretKey;
 import javax.security.auth.kerberos.KerberosTicket;
 import javax.security.auth.kerberos.KerberosKey;
 import javax.security.auth.kerberos.KerberosPrincipal;
@@ -44,18 +44,19 @@
 import sun.security.krb5.EncryptionKey;
 import sun.security.krb5.EncryptedData;
 import sun.security.krb5.PrincipalName;
-import sun.security.krb5.Realm;
 import sun.security.krb5.internal.Ticket;
 import sun.security.krb5.internal.EncTicketPart;
 import sun.security.krb5.internal.crypto.KeyUsage;
 
 import sun.security.jgss.krb5.Krb5Util;
+import sun.security.jgss.krb5.ServiceCreds;
 import sun.security.krb5.KrbException;
 import sun.security.krb5.internal.Krb5;
 
 import sun.security.ssl.Debug;
 import sun.security.ssl.HandshakeInStream;
 import sun.security.ssl.HandshakeOutStream;
+import sun.security.ssl.Krb5Helper;
 import sun.security.ssl.ProtocolVersion;
 
 /**
@@ -138,16 +139,15 @@
      * @param rand random number generator used for generating random
      *          premaster secret if ticket and/or premaster verification fails
      * @param input inputstream from which to get ASN.1-encoded KerberosWrapper
-     * @param serverKey server's master secret key
+     * @param acc the AccessControlContext of the handshaker
+     * @param serviceCreds server's creds
      */
     @Override
     public void init(ProtocolVersion protocolVersion,
         ProtocolVersion clientVersion,
-        SecureRandom rand, HandshakeInStream input, SecretKey[] secretKeys)
+        SecureRandom rand, HandshakeInStream input, AccessControlContext acc, Object serviceCreds)
         throws IOException {
 
-        KerberosKey[] serverKeys = (KerberosKey[])secretKeys;
-
         // Read ticket
         encodedTicket = input.getBytes16();
 
@@ -163,9 +163,42 @@
 
             EncryptedData encPart = t.encPart;
             PrincipalName ticketSname = t.sname;
-            Realm ticketRealm = t.sname.getRealm();
+
+            final ServiceCreds creds = (ServiceCreds)serviceCreds;
+            final KerberosPrincipal princ =
+                    new KerberosPrincipal(ticketSname.toString());
 
-            String serverPrincipal = serverKeys[0].getPrincipal().getName();
+            // For bound service, permission already checked at setup
+            if (creds.getName() == null) {
+                SecurityManager sm = System.getSecurityManager();
+                try {
+                    if (sm != null) {
+                        // Eliminate dependency on ServicePermission
+                        sm.checkPermission(Krb5Helper.getServicePermission(
+                                ticketSname.toString(), "accept"), acc);
+                    }
+                } catch (SecurityException se) {
+                    serviceCreds = null;
+                    // Do not destroy keys. Will affect Subject
+                    if (debug != null && Debug.isOn("handshake")) {
+                        System.out.println("Permission to access Kerberos"
+                                + " secret key denied");
+                    }
+                    throw new IOException("Kerberos service not allowedy");
+                }
+            }
+            KerberosKey[] serverKeys = AccessController.doPrivileged(
+                    new PrivilegedAction<KerberosKey[]>() {
+                        @Override
+                        public KerberosKey[] run() {
+                            return creds.getKKeys(princ);
+                        }
+                    });
+            if (serverKeys.length == 0) {
+                throw new IOException("Found no key for " + princ +
+                        (creds.getName() == null ? "" :
+                        (", this keytab is for " + creds.getName() + " only")));
+            }
 
             /*
              * permission to access and use the secret key of the Kerberized
@@ -174,17 +207,6 @@
              * before promising the client
              */
 
-            // Check that ticket Sname matches serverPrincipal
-            String ticketPrinc = ticketSname.toString();
-            if (!ticketPrinc.equals(serverPrincipal)) {
-                if (debug != null && Debug.isOn("handshake"))
-                   System.out.println("Service principal in Ticket does not"
-                        + " match associated principal in KerberosKey");
-                throw new IOException("Server principal is " +
-                    serverPrincipal + " but ticket is for " +
-                    ticketPrinc);
-            }
-
             // See if we have the right key to decrypt the ticket to get
             // the session key.
             int encPartKeyType = encPart.getEType();
@@ -198,9 +220,8 @@
             }
             if (dkey == null) {
                 // %%% Should print string repr of etype
-                throw new IOException(
-        "Cannot find key of appropriate type to decrypt ticket - need etype " +
-                                   encPartKeyType);
+                throw new IOException("Cannot find key of appropriate type" +
+                        " to decrypt ticket - need etype " + encPartKeyType);
             }
 
             EncryptionKey secretKey = new EncryptionKey(
@@ -222,7 +243,7 @@
             sessionKey = encTicketPart.key;
 
             if (debug != null && Debug.isOn("handshake")) {
-                System.out.println("server principal: " + serverPrincipal);
+                System.out.println("server principal: " + ticketSname);
                 System.out.println("cname: " + encTicketPart.cname.toString());
             }
         } catch (IOException e) {
@@ -382,12 +403,22 @@
             KerberosKey[] keys) throws KrbException {
         int ktype;
         boolean etypeFound = false;
+
+        // When no matched kvno is found, returns tke key of the same
+        // etype with the highest kvno
+        int kvno_found = 0;
+        KerberosKey key_found = null;
+
         for (int i = 0; i < keys.length; i++) {
             ktype = keys[i].getKeyType();
             if (etype == ktype) {
+                int kv = keys[i].getVersionNumber();
                 etypeFound = true;
-                if (versionMatches(version, keys[i].getVersionNumber())) {
+                if (versionMatches(version, kv)) {
                     return keys[i];
+                } else if (kv > kvno_found) {
+                    key_found = keys[i];
+                    kvno_found = kv;
                 }
             }
         }
@@ -399,18 +430,25 @@
                 ktype = keys[i].getKeyType();
                 if (ktype == EncryptedData.ETYPE_DES_CBC_CRC ||
                         ktype == EncryptedData.ETYPE_DES_CBC_MD5) {
+                    int kv = keys[i].getVersionNumber();
                     etypeFound = true;
-                    if (versionMatches(version, keys[i].getVersionNumber())) {
+                    if (versionMatches(version, kv)) {
                         return new KerberosKey(keys[i].getPrincipal(),
                             keys[i].getEncoded(),
                             etype,
-                            keys[i].getVersionNumber());
+                            kv);
+                    } else if (kv > kvno_found) {
+                        key_found = new KerberosKey(keys[i].getPrincipal(),
+                                keys[i].getEncoded(),
+                                etype,
+                                kv);
+                        kvno_found = kv;
                     }
                 }
             }
         }
         if (etypeFound) {
-            throw new KrbException(Krb5.KRB_AP_ERR_BADKEYVER);
+            return key_found;
         }
         return null;
     }
--- a/jdk/src/share/classes/sun/security/ssl/krb5/Krb5ProxyImpl.java	Fri Apr 26 16:09:53 2013 -0700
+++ b/jdk/src/share/classes/sun/security/ssl/krb5/Krb5ProxyImpl.java	Sat Apr 27 18:25:16 2013 +0800
@@ -28,9 +28,11 @@
 import java.security.AccessControlContext;
 import java.security.Permission;
 import java.security.Principal;
+import java.util.Set;
 import javax.crypto.SecretKey;
 import javax.security.auth.Subject;
 import javax.security.auth.kerberos.KerberosKey;
+import javax.security.auth.kerberos.KeyTab;
 import javax.security.auth.kerberos.ServicePermission;
 import javax.security.auth.login.LoginException;
 
@@ -61,17 +63,16 @@
     }
 
     @Override
-    public SecretKey[] getServerKeys(AccessControlContext acc)
+    public Object getServiceCreds(AccessControlContext acc)
             throws LoginException {
         ServiceCreds serviceCreds =
             Krb5Util.getServiceCreds(GSSCaller.CALLER_SSL_SERVER, null, acc);
-        return serviceCreds != null ? serviceCreds.getKKeys() :
-                                        new KerberosKey[0];
+        return serviceCreds;
     }
 
     @Override
-    public String getServerPrincipalName(SecretKey kerberosKey) {
-        return ((KerberosKey)kerberosKey).getPrincipal().getName();
+    public String getServerPrincipalName(Object serviceCreds) {
+        return ((ServiceCreds)serviceCreds).getName();
     }
 
     @Override
@@ -100,4 +101,21 @@
             String action) {
         return new ServicePermission(principalName, action);
     }
+
+    @Override
+    public boolean isRelated(Subject subject, Principal princ) {
+        if (princ == null) return false;
+        Set<Principal> principals =
+                subject.getPrincipals(Principal.class);
+        if (principals.contains(princ)) {
+            // bound to this principal
+            return true;
+        }
+        for (KeyTab pc: subject.getPrivateCredentials(KeyTab.class)) {
+            if (!pc.isBound()) {
+                return true;
+            }
+        }
+        return false;
+    }
 }
--- a/jdk/test/sun/security/krb5/auto/SSL.java	Fri Apr 26 16:09:53 2013 -0700
+++ b/jdk/test/sun/security/krb5/auto/SSL.java	Sat Apr 27 18:25:16 2013 +0800
@@ -23,10 +23,11 @@
 
 /*
  * @test
- * @bug 6894643 6913636
+ * @bug 6894643 6913636 8005523
  * @summary Test JSSE Kerberos ciphersuite
+
  * @run main/othervm SSL TLS_KRB5_WITH_RC4_128_SHA
- * @run main/othervm SSL TLS_KRB5_WITH_RC4_128_MD5
+ * @run main/othervm SSL TLS_KRB5_WITH_RC4_128_SHA unbound
  * @run main/othervm SSL TLS_KRB5_WITH_3DES_EDE_CBC_SHA
  * @run main/othervm SSL TLS_KRB5_WITH_3DES_EDE_CBC_MD5
  * @run main/othervm SSL TLS_KRB5_WITH_DES_CBC_SHA
@@ -38,14 +39,17 @@
  */
 import java.io.*;
 import java.net.InetAddress;
+import java.security.AccessControlException;
+import java.security.Permission;
 import javax.net.ssl.*;
 import java.security.Principal;
 import java.util.Date;
+import javax.security.auth.kerberos.ServicePermission;
 import sun.security.jgss.GSSUtil;
 import sun.security.krb5.PrincipalName;
 import sun.security.krb5.internal.ktab.KeyTab;
 
-public class SSL {
+public class SSL extends SecurityManager {
 
     private static String krb5Cipher;
     private static final int LOOP_LIMIT = 3;
@@ -53,13 +57,32 @@
     private static volatile String server;
     private static volatile int port;
 
+    private static String permChecks = "";
+
     // 0-Not started, 1-Start OK, 2-Failure
     private static volatile int serverState = 0;
 
+    @Override
+    public void checkPermission(Permission perm, Object context) {
+        checkPermission(perm);
+    }
+
+    public void checkPermission(Permission perm) {
+        if (!(perm instanceof ServicePermission)) {
+            return;
+        }
+        ServicePermission p = (ServicePermission)perm;
+        permChecks = permChecks + p.getActions().toUpperCase().charAt(0);
+    }
+
     public static void main(String[] args) throws Exception {
 
         krb5Cipher = args[0];
 
+        boolean unbound = args.length > 1;
+
+        System.setSecurityManager(new SSL());
+
         KDC kdc = KDC.create(OneKDC.REALM);
         // Run this after KDC, so our own DNS service can be started
         try {
@@ -85,6 +108,7 @@
         // and use the middle one as the real key
         kdc.addPrincipal("host/" + server, "pass2".toCharArray());
 
+
         // JAAS config entry name ssl
         System.setProperty("java.security.auth.login.config", OneKDC.JAAS_CONF);
         File f = new File(OneKDC.JAAS_CONF);
@@ -92,7 +116,9 @@
         fos.write((
                 "ssl {\n" +
                 "    com.sun.security.auth.module.Krb5LoginModule required\n" +
-                "    principal=\"host/" + server + "\"\n" +
+                (unbound ?
+                    "    principal=*\n" :
+                    "    principal=\"host/" + server + "\"\n") +
                 "    useKeyTab=true\n" +
                 "    keyTab=" + OneKDC.KTAB + "\n" +
                 "    isInitiator=false\n" +
@@ -103,7 +129,6 @@
         Context c;
         final Context s = Context.fromJAAS("ssl");
 
-        // There's no keytab file when server starts.
         s.startAsServer(GSSUtil.GSS_KRB5_MECH_OID);
 
         Thread server = new Thread(new Runnable() {
@@ -127,21 +152,6 @@
             throw new Exception("Server already failed");
         }
 
-        // Now create the keytab
-
-        /*
-        // Add 3 versions of keys into keytab
-        KeyTab ktab = KeyTab.create(OneKDC.KTAB);
-        PrincipalName service = new PrincipalName(
-                "host/" + server, PrincipalName.KRB_NT_SRV_HST);
-        ktab.addEntry(service, "pass1".toCharArray(), 1);
-        ktab.addEntry(service, "pass2".toCharArray(), 2);
-        ktab.addEntry(service, "pass3".toCharArray(), 3);
-        ktab.save();
-
-        // and use the middle one as the  real key
-        kdc.addPrincipal("host/" + server, "pass2".toCharArray());
-         */
         c = Context.fromUserPass(OneKDC.USER, OneKDC.PASS, false);
         c.startAsClient("host/" + server, GSSUtil.GSS_KRB5_MECH_OID);
         c.doAs(new JsseClientAction(), null);
@@ -157,20 +167,22 @@
         c.startAsClient("host/" + server, GSSUtil.GSS_KRB5_MECH_OID);
         c.doAs(new JsseClientAction(), null);
 
-        // Revoke the old key
-        /*Thread.sleep(2000);
-        ktab = KeyTab.create(OneKDC.KTAB);
-        ktab.addEntry(service, "pass5".toCharArray(), 5, false);
-        ktab.save();
-
-        c = Context.fromUserPass(OneKDC.USER, OneKDC.PASS, false);
-        c.startAsClient("host/" + server, GSSUtil.GSS_KRB5_MECH_OID);
-        try {
-            c.doAs(new JsseClientAction(), null);
-            throw new Exception("Should fail this time.");
-        } catch (SSLException e) {
-            // Correct behavior.
-        }*/
+        // Permission checking check. Please note this is highly
+        // implementation related.
+        if (unbound) {
+            // For unbound, server does not know what name to check.
+            // Client checks "initiate", then server gets the name
+            // and checks "accept". Second connection resume.
+            if (!permChecks.equals("IA")) {
+                throw new Exception();
+            }
+        } else {
+            // For bound, JAAS checks "accept" once. Server checks again,
+            // client then checks "initiate". Second connection resume.
+            if (!permChecks.equals("AAI")) {
+                throw new Exception();
+            }
+        }
     }
 
     // Following codes copied from