From 68d5cd6f233d0215e498fce5cec57f1b68ba2f95 Mon Sep 17 00:00:00 2001 From: Weijun Wang Date: Wed, 20 Apr 2011 18:41:32 +0800 Subject: [PATCH] 6894072: always refresh keytab Reviewed-by: valeriep --- .../security/auth/module/Krb5LoginModule.java | 131 ++++++---- .../JavaxSecurityAuthKerberosAccessImpl.java | 38 +++ .../security/auth/kerberos/KerberosKey.java | 10 +- .../javax/security/auth/kerberos/KeyTab.java | 230 ++++++++++++++++++ .../misc/JavaxSecurityAuthKerberosAccess.java | 43 ++++ .../share/classes/sun/misc/SharedSecrets.java | 14 ++ .../jgss/krb5/Krb5AcceptCredential.java | 55 ++--- .../sun/security/jgss/krb5/Krb5Util.java | 193 ++++++++++++--- .../sun/security/jgss/krb5/SubjectComber.java | 73 +++--- .../classes/sun/security/krb5/Config.java | 1 - .../sun/security/krb5/EncryptionKey.java | 10 +- .../classes/sun/security/krb5/KrbAsRep.java | 37 +-- .../sun/security/krb5/KrbAsReqBuilder.java | 92 +++---- .../security/krb5/internal/ktab/KeyTab.java | 225 +++++++++-------- .../sun/security/ssl/ServerHandshaker.java | 11 +- .../sun/security/ssl/krb5/Krb5ProxyImpl.java | 6 +- .../security/krb5/internal/tools/Kinit.java | 20 +- .../security/krb5/internal/tools/Klist.java | 20 +- .../security/krb5/internal/tools/Ktab.java | 4 +- jdk/test/sun/security/krb5/auto/Context.java | 38 ++- .../sun/security/krb5/auto/DynamicKeytab.java | 140 +++++++++++ jdk/test/sun/security/krb5/auto/KDC.java | 56 ++++- .../sun/security/krb5/auto/KeyTabCompat.java | 90 +++++++ .../krb5/auto/LoginModuleOptions.java | 4 +- jdk/test/sun/security/krb5/auto/SSL.java | 60 ++++- .../sun/security/krb5/auto/TwoPrinces.java | 102 ++++++++ .../sun/security/krb5/ktab/KeyTabIndex.java | 3 +- 27 files changed, 1334 insertions(+), 372 deletions(-) create mode 100644 jdk/src/share/classes/javax/security/auth/kerberos/JavaxSecurityAuthKerberosAccessImpl.java create mode 100644 jdk/src/share/classes/javax/security/auth/kerberos/KeyTab.java create mode 100644 jdk/src/share/classes/sun/misc/JavaxSecurityAuthKerberosAccess.java create mode 100644 jdk/test/sun/security/krb5/auto/DynamicKeytab.java create mode 100644 jdk/test/sun/security/krb5/auto/KeyTabCompat.java create mode 100644 jdk/test/sun/security/krb5/auto/TwoPrinces.java diff --git a/jdk/src/share/classes/com/sun/security/auth/module/Krb5LoginModule.java b/jdk/src/share/classes/com/sun/security/auth/module/Krb5LoginModule.java index c9499f1d1a0..5da153d72ea 100644 --- a/jdk/src/share/classes/com/sun/security/auth/module/Krb5LoginModule.java +++ b/jdk/src/share/classes/com/sun/security/auth/module/Krb5LoginModule.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2000, 2010, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2000, 2011, 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 @@ -395,7 +395,13 @@ public class Krb5LoginModule implements LoginModule { private boolean succeeded = false; private boolean commitSucceeded = false; private String username; + + // Encryption keys calculated from password. Assigned when storekey == true + // and useKeyTab == false (or true but not found) private EncryptionKey[] encKeys = null; + + KeyTab ktab = null; + private Credentials cred = null; private PrincipalName principal = null; @@ -663,28 +669,49 @@ public class Krb5LoginModule implements LoginModule { (krb5PrincName.toString(), PrincipalName.KRB_NT_PRINCIPAL); } + + /* + * Before dynamic KeyTab support (6894072), here we check if + * the keytab contains keys for the principal. If no, keytab + * will not be used and password is prompted for. + * + * After 6894072, we normally don't check it, and expect the + * keys can be populated until a real connection is made. The + * check is still done when isInitiator == true, where the keys + * will be used right now. + * + * Probably tricky relations: + * + * useKeyTab is config flag, but when it's true but the ktab + * does not contains keys for principal, we would use password + * and keep the flag unchanged (for reuse?). In this method, + * we use (ktab != null) to check whether keytab is used. + * After this method (and when storeKey == true), we use + * (encKeys == null) to check. + */ if (useKeyTab) { - encKeys = - EncryptionKey.acquireSecretKeys(principal, keyTabName); - - if (debug) { - if (encKeys != null) - System.out.println - ("principal's key obtained from the keytab"); - else - System.out.println - ("Key for the principal " + - principal + - " not available in " + - ((keyTabName == null) ? - "default key tab" : keyTabName)); + ktab = (keyTabName == null) + ? KeyTab.getInstance() + : KeyTab.getInstance(new File(keyTabName)); + if (isInitiator) { + if (Krb5Util.keysFromJavaxKeyTab(ktab, principal).length + == 0) { + ktab = null; + if (debug) { + System.out.println + ("Key for the principal " + + principal + + " not available in " + + ((keyTabName == null) ? + "default key tab" : keyTabName)); + } + } } - } KrbAsReqBuilder builder; - // We can't get the key from the keytab so prompt - if (encKeys == null) { + + if (ktab == null) { promptForPass(getPasswdFromSharedState); builder = new KrbAsReqBuilder(principal, password); if (isInitiator) { @@ -693,9 +720,13 @@ public class Krb5LoginModule implements LoginModule { // updated with PA info cred = builder.action().getCreds(); } - encKeys = builder.getKeys(); + if (storeKey) { + encKeys = builder.getKeys(); + // When encKeys is empty, the login actually fails. + // For compatibility, exception is thrown in commit(). + } } else { - builder = new KrbAsReqBuilder(principal, encKeys); + builder = new KrbAsReqBuilder(principal, ktab); if (isInitiator) { cred = builder.action().getCreds(); } @@ -705,10 +736,15 @@ public class Krb5LoginModule implements LoginModule { if (debug) { System.out.println("principal is " + principal); HexDumpEncoder hd = new HexDumpEncoder(); - for (int i = 0; i < encKeys.length; i++) { - System.out.println("EncryptionKey: keyType=" + - encKeys[i].getEType() + " keyBytes (hex dump)=" + - hd.encodeBuffer(encKeys[i].getBytes())); + if (ktab != null) { + System.out.println("Will use keytab"); + } else if (storeKey) { + for (int i = 0; i < encKeys.length; i++) { + System.out.println("EncryptionKey: keyType=" + + encKeys[i].getEType() + + " keyBytes (hex dump)=" + + hd.encodeBuffer(encKeys[i].getBytes())); + } } } @@ -989,8 +1025,8 @@ public class Krb5LoginModule implements LoginModule { kerbTicket = Krb5Util.credsToTicket(cred); } - if (storeKey) { - if (encKeys == null || encKeys.length <= 0) { + if (storeKey && encKeys != null) { + if (encKeys.length == 0) { succeeded = false; throw new LoginException("Null Server Key "); } @@ -1006,10 +1042,11 @@ public class Krb5LoginModule implements LoginModule { } } - // Let us add the kerbClientPrinc,kerbTicket and kerbKey (if + // Let us add the kerbClientPrinc,kerbTicket and KeyTab/KerbKey (if // storeKey is true) - if (!princSet.contains(kerbClientPrinc)) + if (!princSet.contains(kerbClientPrinc)) { princSet.add(kerbClientPrinc); + } // add the TGT if (kerbTicket != null) { @@ -1018,19 +1055,29 @@ public class Krb5LoginModule implements LoginModule { } if (storeKey) { - for (int i = 0; i < kerbKeys.length; i++) { - if (!privCredSet.contains(kerbKeys[i])) { - privCredSet.add(kerbKeys[i]); + if (encKeys == null) { + if (!privCredSet.contains(ktab)) { + privCredSet.add(ktab); + // Compatibility; also add keys to privCredSet + for (KerberosKey key: ktab.getKeys(kerbClientPrinc)) { + privCredSet.add(new Krb5Util.KeysFromKeyTab(key)); + } } - encKeys[i].destroy(); - encKeys[i] = null; - if (debug) { - System.out.println("Added server's key" - + kerbKeys[i]); - System.out.println("\t\t[Krb5LoginModule] " + - "added Krb5Principal " + - kerbClientPrinc.toString() - + " to Subject"); + } else { + for (int i = 0; i < kerbKeys.length; i ++) { + if (!privCredSet.contains(kerbKeys[i])) { + privCredSet.add(kerbKeys[i]); + } + encKeys[i].destroy(); + encKeys[i] = null; + if (debug) { + System.out.println("Added server's key" + + kerbKeys[i]); + System.out.println("\t\t[Krb5LoginModule] " + + "added Krb5Principal " + + kerbClientPrinc.toString() + + " to Subject"); + } } } } @@ -1106,7 +1153,8 @@ public class Krb5LoginModule implements LoginModule { while (it.hasNext()) { Object o = it.next(); if (o instanceof KerberosTicket || - o instanceof KerberosKey) { + o instanceof KerberosKey || + o instanceof KeyTab) { it.remove(); } } @@ -1161,6 +1209,7 @@ public class Krb5LoginModule implements LoginModule { } else { // remove temp results for the next try encKeys = null; + ktab = null; principal = null; } username = null; diff --git a/jdk/src/share/classes/javax/security/auth/kerberos/JavaxSecurityAuthKerberosAccessImpl.java b/jdk/src/share/classes/javax/security/auth/kerberos/JavaxSecurityAuthKerberosAccessImpl.java new file mode 100644 index 00000000000..56ed40eed3e --- /dev/null +++ b/jdk/src/share/classes/javax/security/auth/kerberos/JavaxSecurityAuthKerberosAccessImpl.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2011, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ + +package javax.security.auth.kerberos; + +import sun.misc.JavaxSecurityAuthKerberosAccess; +import sun.security.krb5.EncryptionKey; +import sun.security.krb5.PrincipalName; + +class JavaxSecurityAuthKerberosAccessImpl + implements JavaxSecurityAuthKerberosAccess { + public EncryptionKey[] keyTabGetEncryptionKeys( + KeyTab ktab, PrincipalName principal) { + return ktab.getEncryptionKeys(principal); + } +} diff --git a/jdk/src/share/classes/javax/security/auth/kerberos/KerberosKey.java b/jdk/src/share/classes/javax/security/auth/kerberos/KerberosKey.java index 7eb7118a5bc..ba530f9583c 100644 --- a/jdk/src/share/classes/javax/security/auth/kerberos/KerberosKey.java +++ b/jdk/src/share/classes/javax/security/auth/kerberos/KerberosKey.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2000, 2006, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2000, 2011, 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 @@ -35,14 +35,16 @@ import javax.security.auth.DestroyFailedException; * principal.

* * All Kerberos JAAS login modules that obtain a principal's password and - * generate the secret key from it should use this class. Where available, - * the login module might even read this secret key directly from a - * Kerberos "keytab". Sometimes, such as when authenticating a server in + * generate the secret key from it should use this class. + * Sometimes, such as when authenticating a server in * the absence of user-to-user authentication, the login module will store * an instance of this class in the private credential set of a * {@link javax.security.auth.Subject Subject} during the commit phase of the * authentication process.

* + * A Kerberos service using a keytab to read secret keys should use + * the {@link KeyTab} class, where latest keys can be read when needed.

+ * * It might be necessary for the application to be granted a * {@link javax.security.auth.PrivateCredentialPermission * PrivateCredentialPermission} if it needs to access the KerberosKey diff --git a/jdk/src/share/classes/javax/security/auth/kerberos/KeyTab.java b/jdk/src/share/classes/javax/security/auth/kerberos/KeyTab.java new file mode 100644 index 00000000000..c2f8dd79478 --- /dev/null +++ b/jdk/src/share/classes/javax/security/auth/kerberos/KeyTab.java @@ -0,0 +1,230 @@ +/* + * Copyright (c) 2011, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ + +package javax.security.auth.kerberos; + +import java.io.File; +import java.util.Objects; +import sun.misc.SharedSecrets; +import sun.security.krb5.EncryptionKey; +import sun.security.krb5.PrincipalName; +import sun.security.krb5.RealmException; + +/** + * This class encapsulates a keytab file. + *

+ * A Kerberos JAAS login module that obtains long term secret keys from a + * keytab file should use this class. The login module will store + * an instance of this class in the private credential set of a + * {@link javax.security.auth.Subject Subject} during the commit phase of the + * authentication process. + *

+ * It might be necessary for the application to be granted a + * {@link javax.security.auth.PrivateCredentialPermission + * PrivateCredentialPermission} if it needs to access the KeyTab + * instance from a Subject. This permission is not needed when the + * application depends on the default JGSS Kerberos mechanism to access the + * KeyTab. In that case, however, the application will need an appropriate + * {@link javax.security.auth.kerberos.ServicePermission ServicePermission}. + *

+ * The keytab file format is described at + * + * http://www.ioplex.com/utilities/keytab.txt. + * + * @since 1.7 + */ +public final class KeyTab { + + /* + * Impl notes: + * + * This class is only a name, a permanent link to the keytab source + * (can be missing). Itself has no content. In order to read content, + * take a snapshot and read from it. + * + * The snapshot is of type sun.security.krb5.internal.ktab.KeyTab, which + * contains the content of the keytab file when the snapshot is taken. + * Itself has no refresh function and mostly an immutable class (except + * for the create/add/save methods only used by the ktab command). + */ + + // Source, null if using the default one. Note that the default name + // is maintained in snapshot, this field is never "resolved". + private final File file; + + // Set up JavaxSecurityAuthKerberosAccess in SharedSecrets + static { + SharedSecrets.setJavaxSecurityAuthKerberosAccess( + new JavaxSecurityAuthKerberosAccessImpl()); + } + + private KeyTab(File file) { + this.file = file; + } + + /** + * Returns a {@code KeyTab} instance from a {@code File} object. + *

+ * The result of this method is never null. This method only associates + * the returned {@code KeyTab} object with the file and does not read it. + * @param file the keytab {@code File} object, must not be null + * @return the keytab instance + * @throws NullPointerException if the {@code file} argument is null + */ + public static KeyTab getInstance(File file) { + if (file == null) { + throw new NullPointerException("file must be non null"); + } + return new KeyTab(file); + } + + /** + * Returns the default {@code KeyTab} instance. + *

+ * The result of this method is never null. This method only associates + * the returned {@code KeyTab} object with the default keytab file and + * does not read it. + * @return the default keytab instance. + */ + public static KeyTab getInstance() { + return new KeyTab(null); + } + + //Takes a snapshot of the keytab content + private sun.security.krb5.internal.ktab.KeyTab takeSnapshot() { + return sun.security.krb5.internal.ktab.KeyTab.getInstance(file); + } + + /** + * Returns fresh keys for the given Kerberos principal. + *

+ * Implementation of this method should make sure the returned keys match + * the latest content of the keytab file. The result is a newly created + * copy that can be modified by the caller without modifying the keytab + * object. The caller should {@link KerberosKey#destroy() destroy} the + * result keys after they are used. + *

+ * Please note that the keytab file can be created after the + * {@code KeyTab} object is instantiated and its content may change over + * time. Therefore, an application should call this method only when it + * needs to use the keys. Any previous result from an earlier invocation + * could potentially be expired. + *

+ * If there is any error (say, I/O error or format error) + * during the reading process of the KeyTab file, a saved result should be + * returned. If there is no saved result (say, this is the first time this + * method is called, or, all previous read attempts failed), an empty array + * should be returned. This can make sure the result is not drastically + * changed during the (probably slow) update of the keytab file. + *

+ * Each time this method is called and the reading of the file succeeds + * with no exception (say, I/O error or file format error), + * the result should be saved for {@code principal}. The implementation can + * also save keys for other principals having keys in the same keytab object + * if convenient. + *

+ * Any unsupported key read from the keytab is ignored and not included + * in the result. + * + * @param principal the Kerberos principal, must not be null. + * @return the keys (never null, may be empty) + * @throws NullPointerException if the {@code principal} + * argument is null + * @throws SecurityException if a security manager exists and the read + * access to the keytab file is not permitted + */ + public KerberosKey[] getKeys(KerberosPrincipal principal) { + try { + EncryptionKey[] keys = takeSnapshot().readServiceKeys( + new PrincipalName(principal.getName())); + KerberosKey[] kks = new KerberosKey[keys.length]; + for (int i=0; i + * The caller can use the result to determine if it should fallback to + * another mechanism to read the keys. + * @return true if the keytab file exists; false otherwise. + * @throws SecurityException if a security manager exists and the read + * access to the keytab file is not permitted + */ + public boolean exists() { + return !takeSnapshot().isMissing(); + } + + public String toString() { + return file == null ? "Default keytab" : file.toString(); + } + + /** + * Returns a hashcode for this KeyTab. + * + * @return a hashCode() for the KeyTab + */ + public int hashCode() { + return Objects.hash(file); + } + + /** + * Compares the specified Object with this KeyTab for equality. + * Returns true if the given object is also a + * KeyTab and the two + * KeyTab instances are equivalent. + * + * @param other the Object to compare to + * @return true if the specified object is equal to this KeyTab + */ + public boolean equals(Object other) { + if (other == this) + return true; + + if (! (other instanceof KeyTab)) { + return false; + } + + KeyTab otherKtab = (KeyTab) other; + return Objects.equals(otherKtab.file, file); + } +} diff --git a/jdk/src/share/classes/sun/misc/JavaxSecurityAuthKerberosAccess.java b/jdk/src/share/classes/sun/misc/JavaxSecurityAuthKerberosAccess.java new file mode 100644 index 00000000000..9c8f9f2d3d2 --- /dev/null +++ b/jdk/src/share/classes/sun/misc/JavaxSecurityAuthKerberosAccess.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2011, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ + +package sun.misc; + +import javax.security.auth.kerberos.KeyTab; +import sun.security.krb5.EncryptionKey; +import sun.security.krb5.PrincipalName; + +/** + * An unsafe tunnel to get non-public access to classes in the + * javax.security.auth.kerberos package. + */ +public interface JavaxSecurityAuthKerberosAccess { + /** + * Returns keys for a principal in a keytab. + * @return the keys, never null, can be empty. + */ + public EncryptionKey[] keyTabGetEncryptionKeys( + KeyTab ktab, PrincipalName principal); +} diff --git a/jdk/src/share/classes/sun/misc/SharedSecrets.java b/jdk/src/share/classes/sun/misc/SharedSecrets.java index 0bd39b4a5d4..969da9a744f 100644 --- a/jdk/src/share/classes/sun/misc/SharedSecrets.java +++ b/jdk/src/share/classes/sun/misc/SharedSecrets.java @@ -29,6 +29,7 @@ import java.util.jar.JarFile; import java.io.Console; import java.io.FileDescriptor; import java.security.ProtectionDomain; +import javax.security.auth.kerberos.KeyTab; import java.security.AccessController; @@ -51,6 +52,7 @@ public class SharedSecrets { private static JavaIOFileDescriptorAccess javaIOFileDescriptorAccess; private static JavaSecurityProtectionDomainAccess javaSecurityProtectionDomainAccess; private static JavaSecurityAccess javaSecurityAccess; + private static JavaxSecurityAuthKerberosAccess javaxSecurityAuthKerberosAccess; public static JavaUtilJarAccess javaUtilJarAccess() { if (javaUtilJarAccess == null) { @@ -139,4 +141,16 @@ public class SharedSecrets { } return javaSecurityAccess; } + + public static void setJavaxSecurityAuthKerberosAccess + (JavaxSecurityAuthKerberosAccess jsaka) { + javaxSecurityAuthKerberosAccess = jsaka; + } + + public static JavaxSecurityAuthKerberosAccess + getJavaxSecurityAuthKerberosAccess() { + if (javaxSecurityAuthKerberosAccess == null) + unsafe.ensureClassInitialized(KeyTab.class); + return javaxSecurityAuthKerberosAccess; + } } diff --git a/jdk/src/share/classes/sun/security/jgss/krb5/Krb5AcceptCredential.java b/jdk/src/share/classes/sun/security/jgss/krb5/Krb5AcceptCredential.java index 7a7156d9b1c..1df500bc524 100644 --- a/jdk/src/share/classes/sun/security/jgss/krb5/Krb5AcceptCredential.java +++ b/jdk/src/share/classes/sun/security/jgss/krb5/Krb5AcceptCredential.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2000, 2009, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2000, 2011, 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 @@ -29,7 +29,6 @@ import org.ietf.jgss.*; import sun.security.jgss.GSSCaller; import sun.security.jgss.spi.*; import sun.security.krb5.*; -import javax.security.auth.kerberos.*; import java.security.PrivilegedActionException; import java.security.PrivilegedExceptionAction; import java.security.AccessController; @@ -43,40 +42,23 @@ import javax.security.auth.DestroyFailedException; * @since 1.4 */ public class Krb5AcceptCredential - extends KerberosKey implements Krb5CredElement { private static final long serialVersionUID = 7714332137352567952L; private Krb5NameElement name; - /** - * We cache an EncryptionKey representation of this key because many - * Krb5 operation require a key in that form. At some point we might do - * away with EncryptionKey altogether and use the base class - * KerberosKey everywhere. - */ - private EncryptionKey[] krb5EncryptionKeys; + private Krb5Util.ServiceCreds screds; - private Krb5AcceptCredential(Krb5NameElement name, KerberosKey[] keys) { + private Krb5AcceptCredential(Krb5NameElement name, Krb5Util.ServiceCreds creds) { /* * Initialize this instance with the data from the acquired * KerberosKey. This class needs to be a KerberosKey too * hence we can't just store a reference. */ - super(keys[0].getPrincipal(), - keys[0].getEncoded(), - keys[0].getKeyType(), - keys[0].getVersionNumber()); this.name = name; - // Cache this for later use by the sun.security.krb5 package. - krb5EncryptionKeys = new EncryptionKey[keys.length]; - for (int i = 0; i < keys.length; i++) { - krb5EncryptionKeys[i] = new EncryptionKey(keys[i].getEncoded(), - keys[i].getKeyType(), - new Integer(keys[i].getVersionNumber())); - } + this.screds = creds; } static Krb5AcceptCredential getInstance(final GSSCaller caller, Krb5NameElement name) @@ -86,12 +68,12 @@ public class Krb5AcceptCredential name.getKrb5PrincipalName().getName()); final AccessControlContext acc = AccessController.getContext(); - KerberosKey[] keys; + Krb5Util.ServiceCreds creds = null; try { - keys = AccessController.doPrivileged( - new PrivilegedExceptionAction() { - public KerberosKey[] run() throws Exception { - return Krb5Util.getKeys( + creds = AccessController.doPrivileged( + new PrivilegedExceptionAction() { + public Krb5Util.ServiceCreds run() throws Exception { + return Krb5Util.getServiceCreds( caller == GSSCaller.CALLER_UNKNOWN ? GSSCaller.CALLER_ACCEPT: caller, serverPrinc, acc); }}); @@ -103,17 +85,17 @@ public class Krb5AcceptCredential throw ge; } - if (keys == null || keys.length == 0) + if (creds == null) throw new GSSException(GSSException.NO_CRED, -1, - "Failed to find any Kerberos Key"); + "Failed to find any Kerberos credentails"); if (name == null) { - String fullName = keys[0].getPrincipal().getName(); + String fullName = creds.getName(); name = Krb5NameElement.getInstance(fullName, Krb5MechFactory.NT_GSS_KRB5_PRINCIPAL); } - return new Krb5AcceptCredential(name, keys); + return new Krb5AcceptCredential(name, creds); } /** @@ -171,7 +153,7 @@ public class Krb5AcceptCredential } EncryptionKey[] getKrb5EncryptionKeys() { - return krb5EncryptionKeys; + return screds.getEKeys(); } /** @@ -193,13 +175,6 @@ public class Krb5AcceptCredential * destroy in the base class. */ public void destroy() throws DestroyFailedException { - if (krb5EncryptionKeys != null) { - for (int i = 0; i < krb5EncryptionKeys.length; i++) { - krb5EncryptionKeys[i].destroy(); - } - krb5EncryptionKeys = null; - } - - super.destroy(); + screds.destroy(); } } diff --git a/jdk/src/share/classes/sun/security/jgss/krb5/Krb5Util.java b/jdk/src/share/classes/sun/security/jgss/krb5/Krb5Util.java index d5b172f9249..22552a71c3f 100644 --- a/jdk/src/share/classes/sun/security/jgss/krb5/Krb5Util.java +++ b/jdk/src/share/classes/sun/security/jgss/krb5/Krb5Util.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2009, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 2011, 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,7 @@ package sun.security.jgss.krb5; import javax.security.auth.kerberos.KerberosTicket; import javax.security.auth.kerberos.KerberosKey; import javax.security.auth.kerberos.KerberosPrincipal; +import javax.security.auth.kerberos.KeyTab; import javax.security.auth.Subject; import javax.security.auth.login.LoginException; import java.security.AccessControlContext; @@ -38,7 +39,13 @@ import sun.security.krb5.Credentials; import sun.security.krb5.EncryptionKey; import sun.security.krb5.KrbException; import java.io.IOException; +import java.util.ArrayList; +import java.util.Iterator; import java.util.List; +import java.util.Objects; +import java.util.Set; +import sun.misc.SharedSecrets; +import sun.security.krb5.PrincipalName; /** * Utilities for obtaining and converting Kerberos tickets. * @@ -75,7 +82,7 @@ public class Krb5Util { // 1. Try to find service ticket in acc subject Subject accSubj = Subject.getSubject(acc); - KerberosTicket ticket = (KerberosTicket) SubjectComber.find(accSubj, + KerberosTicket ticket = SubjectComber.find(accSubj, serverPrincipal, clientPrincipal, KerberosTicket.class); if (ticket != null) { @@ -87,7 +94,7 @@ public class Krb5Util { // 2. Try to get ticket from login try { loginSubj = GSSUtil.login(caller, GSSUtil.GSS_KRB5_MECH_OID); - ticket = (KerberosTicket) SubjectComber.find(loginSubj, + ticket = SubjectComber.find(loginSubj, serverPrincipal, clientPrincipal, KerberosTicket.class); if (ticket != null) { return ticket; // found it @@ -102,13 +109,13 @@ public class Krb5Util { // Try to get TGT to acquire service ticket // 3. Try to get TGT from acc subject - KerberosTicket tgt = (KerberosTicket) SubjectComber.find(accSubj, + KerberosTicket tgt = SubjectComber.find(accSubj, tgsPrincipal, clientPrincipal, KerberosTicket.class); boolean fromAcc; if (tgt == null && loginSubj != null) { // 4. Try to get TGT from login subject - tgt = (KerberosTicket) SubjectComber.find(loginSubj, + tgt = SubjectComber.find(loginSubj, tgsPrincipal, clientPrincipal, KerberosTicket.class); fromAcc = false; } else { @@ -145,14 +152,14 @@ public class Krb5Util { // Try to get ticket from acc's Subject Subject accSubj = Subject.getSubject(acc); - KerberosTicket ticket = (KerberosTicket) + KerberosTicket ticket = SubjectComber.find(accSubj, serverPrincipal, clientPrincipal, KerberosTicket.class); // Try to get ticket from Subject obtained from GSSUtil if (ticket == null && !GSSUtil.useSubjectCredsOnly(caller)) { Subject subject = GSSUtil.login(caller, GSSUtil.GSS_KRB5_MECH_OID); - ticket = (KerberosTicket) SubjectComber.find(subject, + ticket = SubjectComber.find(subject, serverPrincipal, clientPrincipal, KerberosTicket.class); } return ticket; @@ -182,37 +189,152 @@ public class Krb5Util { return subject; } + // A special KerberosKey, used as keys read from a KeyTab object. + // Each time new keys are read from KeyTab objects in the private + // credentials set, old ones are removed and new ones added. + public static class KeysFromKeyTab extends KerberosKey { + public KeysFromKeyTab(KerberosKey key) { + super(key.getPrincipal(), key.getEncoded(), + key.getKeyType(), key.getVersionNumber()); + } + } + /** - * Retrieves the keys for the specified server principal from - * the Subject in the specified AccessControlContext. - * If the ticket can not be found in the Subject, and if - * useSubjectCredsOnly is false, then obtain keys from - * a LoginContext. + * Credentials of a service, the private secret to authenticate its + * identity, which can be: + * 1. Some KerberosKeys (generated from password) + * 2. A KeyTab (for a typical service) + * 3. A TGT (for a user2user service. Not supported yet) * - * NOTE: This method is used by JSSE Kerberos Cipher Suites + * Note that some creds can coexist. For example, a user2user service + * can use its keytab (or keys) if the client can successfully obtain a + * normal service ticket, otherwise, it can uses the TGT (actually, the + * session key of the TGT) if the client can only acquire a service ticket + * of ENC-TKT-IN-SKEY style. */ - public static KerberosKey[] getKeys(GSSCaller caller, + public static class ServiceCreds { + private KerberosPrincipal kp; + private List ktabs; + private List kk; + private Subject subj; + //private KerberosTicket tgt; // user2user, not supported yet + + private static ServiceCreds getInstance( + Subject subj, String serverPrincipal) { + + ServiceCreds sc = new ServiceCreds(); + sc.subj = subj; + + for (KerberosPrincipal p: subj.getPrincipals(KerberosPrincipal.class)) { + if (serverPrincipal == null || + p.getName().equals(serverPrincipal)) { + sc.kp = p; + serverPrincipal = p.getName(); + break; + } + } + if (sc.kp == null) { + // Compatibility with old behavior: even when there is no + // KerberosPrincipal, we can find one from KerberosKeys + List keys = SubjectComber.findMany( + subj, null, null, KerberosKey.class); + if (!keys.isEmpty()) { + sc.kp = keys.get(0).getPrincipal(); + serverPrincipal = sc.kp.getName(); + if (DEBUG) { + System.out.println(">>> ServiceCreds: no kp?" + + " find one from kk: " + serverPrincipal); + } + } else { + return null; + } + } + sc.ktabs = SubjectComber.findMany( + subj, null, null, KeyTab.class); + sc.kk = SubjectComber.findMany( + subj, serverPrincipal, null, KerberosKey.class); + if (sc.ktabs.isEmpty() && sc.kk.isEmpty()) { + return null; + } + return sc; + } + + public String getName() { + return kp.getName(); + } + + public KerberosKey[] getKKeys() { + if (ktabs.isEmpty()) { + return kk.toArray(new KerberosKey[kk.size()]); + } else { + List keys = new ArrayList<>(); + for (KeyTab ktab: ktabs) { + for (KerberosKey k: ktab.getKeys(kp)) { + keys.add(k); + } + } + // Compatibility: also add keys to privCredSet. Remove old + // ones first, only remove those from keytab. + if (!subj.isReadOnly()) { + Set pcs = subj.getPrivateCredentials(); + synchronized (pcs) { + Iterator iterator = pcs.iterator(); + while (iterator.hasNext()) { + Object obj = iterator.next(); + if (obj instanceof KeysFromKeyTab) { + KerberosKey key = (KerberosKey)obj; + if (Objects.equals(key.getPrincipal(), kp)) { + iterator.remove(); + } + } + } + } + for (KerberosKey key: keys) { + subj.getPrivateCredentials().add(new KeysFromKeyTab(key)); + } + } + return keys.toArray(new KerberosKey[keys.size()]); + } + } + + public EncryptionKey[] getEKeys() { + KerberosKey[] kkeys = getKKeys(); + EncryptionKey[] ekeys = new EncryptionKey[kkeys.length]; + for (int i=0; i kkeys = (List)SubjectComber.findMany( - accSubj, serverPrincipal, null, KerberosKey.class); - - if (kkeys == null && !GSSUtil.useSubjectCredsOnly(caller)) { + ServiceCreds sc = null; + if (accSubj != null) { + sc = ServiceCreds.getInstance(accSubj, serverPrincipal); + } + if (sc == null && !GSSUtil.useSubjectCredsOnly(caller)) { Subject subject = GSSUtil.login(caller, GSSUtil.GSS_KRB5_MECH_OID); - kkeys = (List) SubjectComber.findMany(subject, - serverPrincipal, null, KerberosKey.class); - } - - int len; - if (kkeys != null && (len = kkeys.size()) > 0) { - KerberosKey[] keys = new KerberosKey[len]; - kkeys.toArray(keys); - return keys; - } else { - return null; + sc = ServiceCreds.getInstance(subject, serverPrincipal); } + return sc; } public static KerberosTicket credsToTicket(Credentials serviceCreds) { @@ -247,4 +369,17 @@ public class Krb5Util { kerbTicket.getRenewTill(), kerbTicket.getClientAddresses()); } + + /** + * A helper method to get EncryptionKeys from a javax..KeyTab + * @param ktab the javax..KeyTab class + * @param cname the PrincipalName + * @return the EKeys, never null, might be empty + */ + public static EncryptionKey[] keysFromJavaxKeyTab( + KeyTab ktab, PrincipalName cname) { + return SharedSecrets.getJavaxSecurityAuthKerberosAccess(). + keyTabGetEncryptionKeys(ktab, cname); + } + } diff --git a/jdk/src/share/classes/sun/security/jgss/krb5/SubjectComber.java b/jdk/src/share/classes/sun/security/jgss/krb5/SubjectComber.java index e33867dedc8..c7269cb874f 100644 --- a/jdk/src/share/classes/sun/security/jgss/krb5/SubjectComber.java +++ b/jdk/src/share/classes/sun/security/jgss/krb5/SubjectComber.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2002, 2006, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2002, 2011, 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 @@ -33,10 +33,11 @@ import java.util.Iterator; import java.util.ArrayList; import java.util.List; import java.util.Set; +import javax.security.auth.kerberos.KeyTab; /** - * This utility looks through the current Subject and retrieves a ticket or key - * for the desired client/server principals. + * This utility looks through the current Subject and retrieves private + * credentials for the desired client/server principals. * * @author Ram Marti * @since 1.4.2 @@ -52,58 +53,70 @@ class SubjectComber { private SubjectComber() { // Cannot create one of these } - static Object find(Subject subject, String serverPrincipal, - String clientPrincipal, Class credClass) { + static T find(Subject subject, String serverPrincipal, + String clientPrincipal, Class credClass) { - return findAux(subject, serverPrincipal, clientPrincipal, credClass, + return (T)findAux(subject, serverPrincipal, clientPrincipal, credClass, true); } - static Object findMany(Subject subject, String serverPrincipal, - String clientPrincipal, Class credClass) { + static List findMany(Subject subject, String serverPrincipal, + String clientPrincipal, Class credClass) { - return findAux(subject, serverPrincipal, clientPrincipal, credClass, + return (List)findAux(subject, serverPrincipal, clientPrincipal, credClass, false); } /** - * Find the ticket or key for the specified client/server principals + * Find private credentials for the specified client/server principals * in the subject. Returns null if the subject is null. * - * @return the ticket or key + * @return the private credentials */ - private static Object findAux(Subject subject, String serverPrincipal, - String clientPrincipal, Class credClass, boolean oneOnly) { + private static Object findAux(Subject subject, String serverPrincipal, + String clientPrincipal, Class credClass, boolean oneOnly) { if (subject == null) { return null; } else { - List answer = (oneOnly ? null : new ArrayList()); + List answer = (oneOnly ? null : new ArrayList()); - if (credClass == KerberosKey.class) { - // We are looking for a KerberosKey credentials for the - // serverPrincipal - Iterator iterator = - subject.getPrivateCredentials(KerberosKey.class).iterator(); + if (credClass == KeyTab.class) { // Principal un-related + // We are looking for credentials unrelated to serverPrincipal + Iterator iterator = + subject.getPrivateCredentials(credClass).iterator(); while (iterator.hasNext()) { - KerberosKey key = iterator.next(); - if (serverPrincipal == null || - serverPrincipal.equals(key.getPrincipal().getName())) { + T t = iterator.next(); + if (DEBUG) { + System.out.println("Found " + credClass.getSimpleName()); + } + if (oneOnly) { + return t; + } else { + answer.add(t); + } + } + } else if (credClass == KerberosKey.class) { + // We are looking for credentials for the serverPrincipal + Iterator iterator = + subject.getPrivateCredentials(credClass).iterator(); + while (iterator.hasNext()) { + T t = iterator.next(); + String name = ((KerberosKey)t).getPrincipal().getName(); + if (serverPrincipal == null || serverPrincipal.equals(name)) { if (DEBUG) { - System.out.println("Found key for " - + key.getPrincipal() + "(" + - key.getKeyType() + ")"); + System.out.println("Found " + + credClass.getSimpleName() + " for " + name); } if (oneOnly) { - return key; + return t; } else { if (serverPrincipal == null) { // Record name so that keys returned will all // belong to the same principal - serverPrincipal = - key.getPrincipal().getName(); + serverPrincipal = name; } - answer.add(key); + answer.add(t); } } } @@ -167,7 +180,7 @@ class SubjectComber { serverPrincipal = ticket.getServer().getName(); } - answer.add(ticket); + answer.add((T)ticket); } } } diff --git a/jdk/src/share/classes/sun/security/krb5/Config.java b/jdk/src/share/classes/sun/security/krb5/Config.java index 01d97eaf599..2ec4e12bd71 100644 --- a/jdk/src/share/classes/sun/security/krb5/Config.java +++ b/jdk/src/share/classes/sun/security/krb5/Config.java @@ -110,7 +110,6 @@ public class Config { public static synchronized void refresh() throws KrbException { singleton = new Config(); - KeyTab.refresh(); KdcComm.initStatic(); } diff --git a/jdk/src/share/classes/sun/security/krb5/EncryptionKey.java b/jdk/src/share/classes/sun/security/krb5/EncryptionKey.java index 8ce51c0c659..1de032945ff 100644 --- a/jdk/src/share/classes/sun/security/krb5/EncryptionKey.java +++ b/jdk/src/share/classes/sun/security/krb5/EncryptionKey.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2000, 2010, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2000, 2011, 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 @@ -138,8 +138,7 @@ public class EncryptionKey * @returns an array of secret keys or null if none were found. */ public static EncryptionKey[] acquireSecretKeys(PrincipalName princ, - String keytab) - throws KrbException, IOException { + String keytab) { if (princ == null) throw new IllegalArgumentException( @@ -148,11 +147,6 @@ public class EncryptionKey // KeyTab getInstance(keytab) will call KeyTab.getInstance() // if keytab is null KeyTab ktab = KeyTab.getInstance(keytab); - - if (ktab == null) { - return null; - } - return ktab.readServiceKeys(princ); } diff --git a/jdk/src/share/classes/sun/security/krb5/KrbAsRep.java b/jdk/src/share/classes/sun/security/krb5/KrbAsRep.java index 16c7d87895f..29a7a5b4ccd 100644 --- a/jdk/src/share/classes/sun/security/krb5/KrbAsRep.java +++ b/jdk/src/share/classes/sun/security/krb5/KrbAsRep.java @@ -37,6 +37,8 @@ import sun.security.krb5.internal.crypto.EType; import sun.security.util.*; import java.io.IOException; import java.util.Objects; +import javax.security.auth.kerberos.KeyTab; +import sun.security.jgss.krb5.Krb5Util; /** * This class encapsulates a AS-REP message that the KDC sends to the @@ -90,29 +92,32 @@ class KrbAsRep extends KrbKdcRep { } /** - * Called by KrbAsReqBuilder to resolve a AS-REP message using keys. - * @param keys user provided keys, not null + * Called by KrbAsReqBuilder to resolve a AS-REP message using a keytab. + * @param ktab the keytab, not null * @param asReq the original AS-REQ sent, used to validate AS-REP + * @param cname the user principal name, used to locate keys in ktab */ - void decryptUsingKeys(EncryptionKey[] keys, KrbAsReq asReq) + void decryptUsingKeyTab(KeyTab ktab, KrbAsReq asReq, PrincipalName cname) throws KrbException, Asn1Exception, IOException { EncryptionKey dkey = null; int encPartKeyType = rep.encPart.getEType(); Integer encPartKvno = rep.encPart.kvno; - try { - dkey = EncryptionKey.findKey(encPartKeyType, encPartKvno, keys); - } catch (KrbException ke) { - if (ke.returnCode() == Krb5.KRB_AP_ERR_BADKEYVER) { - // Fallback to no kvno. In some cases, keytab is generated - // not by sysadmin but Java's ktab command - dkey = EncryptionKey.findKey(encPartKeyType, keys); + try { + dkey = EncryptionKey.findKey(encPartKeyType, encPartKvno, + Krb5Util.keysFromJavaxKeyTab(ktab, cname)); + } catch (KrbException ke) { + if (ke.returnCode() == Krb5.KRB_AP_ERR_BADKEYVER) { + // Fallback to no kvno. In some cases, keytab is generated + // not by sysadmin but Java's ktab command + dkey = EncryptionKey.findKey(encPartKeyType, + Krb5Util.keysFromJavaxKeyTab(ktab, cname)); + } + } + if (dkey == null) { + throw new KrbException(Krb5.API_INVALID_ARG, + "Cannot find key for type/kvno to decrypt AS REP - " + + EType.toString(encPartKeyType) + "/" + encPartKvno); } - } - if (dkey == null) { - throw new KrbException(Krb5.API_INVALID_ARG, - "Cannot find key for type/kvno to decrypt AS REP - " + - EType.toString(encPartKeyType) + "/" + encPartKvno); - } decrypt(dkey, asReq); } diff --git a/jdk/src/share/classes/sun/security/krb5/KrbAsReqBuilder.java b/jdk/src/share/classes/sun/security/krb5/KrbAsReqBuilder.java index 940d31107e5..dd9b8dcfb92 100644 --- a/jdk/src/share/classes/sun/security/krb5/KrbAsReqBuilder.java +++ b/jdk/src/share/classes/sun/security/krb5/KrbAsReqBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2010, 2011, 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 @@ -27,6 +27,8 @@ package sun.security.krb5; import java.io.IOException; import java.util.Arrays; +import javax.security.auth.kerberos.KeyTab; +import sun.security.jgss.krb5.Krb5Util; import sun.security.krb5.internal.HostAddresses; import sun.security.krb5.internal.KDCOptions; import sun.security.krb5.internal.KRBError; @@ -42,13 +44,16 @@ import sun.security.krb5.internal.crypto.EType; * 1. Gather information to create AS-REQ * 2. Create and send AS-REQ * 3. Receive AS-REP and KRB-ERROR (-KRB_ERR_RESPONSE_TOO_BIG) and parse them - * 4. Emit credentials and secret keys (for JAAS storeKey=true) + * 4. Emit credentials and secret keys (for JAAS storeKey=true with password) * * This class does not: * 1. Deal with real communications (KdcComm does it, and TGS-REQ) * a. Name of KDCs for a realm * b. Server availability, timeout, UDP or TCP * d. KRB_ERR_RESPONSE_TOO_BIG + * 2. Stores its own copy of password, this means: + * a. Do not change/wipe it before Builder finish + * b. Builder will not wipe it for you * * With this class: * 1. KrbAsReq has only one constructor @@ -70,19 +75,17 @@ public final class KrbAsReqBuilder { private HostAddresses addresses; // Secret source: can't be changed once assigned, only one (of the two - // sources) can be set and should be non-null - private EncryptionKey[] keys; - private char[] password; + // sources) can be set to non-null + private final char[] password; + private final KeyTab ktab; // Used to create a ENC-TIMESTAMP in the 2nd AS-REQ - private EncryptionKey pakey; private PAData[] paList; // PA-DATA from both KRB-ERROR and AS-REP. // Used by getKeys() only. // Only AS-REP should be enough per RFC, // combined in case etypes are different. // The generated and received: - int[] eTypes; private KrbAsReq req; private KrbAsRep rep; @@ -94,7 +97,7 @@ public final class KrbAsReqBuilder { private State state; // Called by other constructors - private KrbAsReqBuilder(PrincipalName cname) + private void init(PrincipalName cname) throws KrbException { if (cname.getRealm() == null) { cname.setRealm(Config.getInstance().getDefaultRealm()); @@ -114,14 +117,11 @@ public final class KrbAsReqBuilder { * This argument will neither be modified nor stored by the method. * @throws KrbException */ - public KrbAsReqBuilder(PrincipalName cname, EncryptionKey[] keys) + public KrbAsReqBuilder(PrincipalName cname, KeyTab ktab) throws KrbException { - this(cname); - this.keys = new EncryptionKey[keys.length]; - for (int i=0; i map = new HashMap<>(); + + // KeyTab file does not exist. Note: a missing keytab is still valid + private boolean isMissing = false; + + // KeyTab file is invalid, possibly an I/O error or a file format error. + private boolean isValid = true; + + private final String tabName; + private long lastModified; + private int kt_vno; + private Vector entries = new Vector<>(); - private KeyTab(String filename) throws IOException, RealmException { - init(filename); - } - - public static KeyTab getInstance(String s) { - name = parse(s); - if (name == null) { - return getInstance(); + /** + * Constructs a KeyTab object. + * + * If there is any I/O error or format errot during the loading, the + * isValid flag is set to false, and all half-read entries are dismissed. + * @param filename path name for the keytab file, must not be null + */ + private KeyTab(String filename) { + tabName = filename; + try { + lastModified = new File(tabName).lastModified(); + KeyTabInputStream kis = + new KeyTabInputStream(new FileInputStream(filename)); + load(kis); + kis.close(); + } catch (FileNotFoundException e) { + entries.clear(); + isMissing = true; + } catch (Exception ioe) { + entries.clear(); + isValid = false; } - return getInstance(new File(name)); } /** - * Gets the single instance of KeyTab class. + * Read a keytab file. Returns a new object and save it into cache when + * new content (modified since last read) is available. If keytab file is + * invalid, the old object will be returned. This is a safeguard for + * partial-written keytab files or non-stable network. Please note that + * a missing keytab is valid, which is equivalent to an empty keytab. + * + * @param s file name of keytab, must not be null + * @return the keytab object, can be invalid, but never null. + */ + private synchronized static KeyTab getInstance0(String s) { + long lm = new File(s).lastModified(); + KeyTab old = map.get(s); + if (old != null && old.isValid() && old.lastModified == lm) { + return old; + } + KeyTab ktab = new KeyTab(s); + if (ktab.isValid()) { // A valid new keytab + map.put(s, ktab); + return ktab; + } else if (old != null) { // An existing old one + return old; + } else { + return ktab; // first read is invalid + } + } + + /** + * Gets a KeyTab object. + * @param s the key tab file name. + * @return the KeyTab object, never null. + */ + public static KeyTab getInstance(String s) { + if (s == null) { + return getInstance(); + } else { + return getInstance0(s); + } + } + + /** + * Gets a KeyTab object. * @param file the key tab file. - * @return single instance of KeyTab; - * return null if error occurs while reading data out of the file. + * @return the KeyTab object, never null. */ public static KeyTab getInstance(File file) { - try { - if (!(file.exists())) { - singleton = null; - } else { - String fname = file.getAbsolutePath(); - // Since this class deals with file I/O operations, - // we want only one class instance existing. - if (singleton != null) { - File kfile = new File(singleton.name); - String kname = kfile.getAbsolutePath(); - if (kname.equalsIgnoreCase(fname)) { - if (DEBUG) { - System.out.println("KeyTab instance already exists"); - } - } - } else { - singleton = new KeyTab(fname); - } - } - } catch (Exception e) { - singleton = null; - if (DEBUG) { - System.out.println("Could not obtain an instance of KeyTab" + - e.getMessage()); - } + if (file == null) { + return getInstance(); + } else { + return getInstance0(file.getPath()); } - return singleton; } /** - * Gets the single instance of KeyTab class. - * @return single instance of KeyTab; return null if default keytab file - * does not exist, or error occurs while reading data from the file. + * Gets the default KeyTab object. + * @return the KeyTab object, never null. */ public static KeyTab getInstance() { - try { - name = getDefaultKeyTab(); - if (name != null) { - singleton = getInstance(new File(name)); - } - } catch (Exception e) { - singleton = null; - if (DEBUG) { - System.out.println("Could not obtain an instance of KeyTab" + - e.getMessage()); - } - } - return singleton; + return getInstance(getDefaultTabName()); + } + + public boolean isMissing() { + return isMissing; + } + + public boolean isValid() { + return isValid; } /** * The location of keytab file will be read from the configuration file * If it is not specified, consider user.home as the keytab file's * default location. + * @return never null */ - private static String getDefaultKeyTab() { - if (name != null) { - return name; + private static String getDefaultTabName() { + if (defaultTabName != null) { + return defaultTabName; } else { String kname = null; try { @@ -145,7 +192,7 @@ public class KeyTab implements KeyTabConstants { StringTokenizer st = new StringTokenizer(keytab_names, " "); while (st.hasMoreTokens()) { kname = parse(st.nextToken()); - if (kname != null) { + if (new File(kname).exists()) { break; } } @@ -165,19 +212,20 @@ public class KeyTab implements KeyTabConstants { new sun.security.action.GetPropertyAction("user.dir")); } - if (user_home != null) { - kname = user_home + File.separator + "krb5.keytab"; - } + kname = user_home + File.separator + "krb5.keytab"; } + defaultTabName = kname; return kname; } } + /** + * Parses some common keytab name formats + * @param name never null + * @return never null + */ private static String parse(String name) { - String kname = null; - if (name == null) { - return null; - } + String kname; if ((name.length() >= 5) && (name.substring(0, 5).equalsIgnoreCase("FILE:"))) { kname = name.substring(5); @@ -194,18 +242,6 @@ public class KeyTab implements KeyTabConstants { return kname; } - private synchronized void init(String filename) - throws IOException, RealmException { - - if (filename != null) { - KeyTabInputStream kis = - new KeyTabInputStream(new FileInputStream(filename)); - load(kis); - kis.close(); - name = filename; - } - } - private void load(KeyTabInputStream kis) throws IOException, RealmException { @@ -234,14 +270,13 @@ public class KeyTab implements KeyTabConstants { * etypes that have been configured for use. If there are multiple * keys with same etype, the one with the highest kvno is returned. * @param service the PrincipalName of the requested service - * @return an array containing all the service keys + * @return an array containing all the service keys, never null */ public EncryptionKey[] readServiceKeys(PrincipalName service) { KeyTabEntry entry; EncryptionKey key; int size = entries.size(); ArrayList keys = new ArrayList<>(size); - for (int i = size-1; i >= 0; i--) { entry = entries.elementAt(i); if (entry.service.match(service)) { @@ -260,10 +295,7 @@ public class KeyTab implements KeyTabConstants { } } } - size = keys.size(); - if (size == 0) - return null; EncryptionKey[] retVal = keys.toArray(new EncryptionKey[size]); // Sort keys according to default_tkt_enctypes @@ -328,10 +360,13 @@ public class KeyTab implements KeyTabConstants { return false; } - public static String tabName() { - return name; + public String tabName() { + return tabName; } + /////////////////// THE WRITE SIDE /////////////////////// + /////////////// only used by ktab tool ////////////////// + /** * Adds a new entry in the key table. * @param service the service which will have a new entry in the key table. @@ -394,7 +429,7 @@ public class KeyTab implements KeyTabConstants { */ public synchronized static KeyTab create() throws IOException, RealmException { - String dname = getDefaultKeyTab(); + String dname = getDefaultTabName(); return create(dname); } @@ -408,8 +443,7 @@ public class KeyTab implements KeyTabConstants { new KeyTabOutputStream(new FileOutputStream(name)); kos.writeVersion(KRB5_KT_VNO); kos.close(); - singleton = new KeyTab(name); - return singleton; + return new KeyTab(name); } /** @@ -417,7 +451,7 @@ public class KeyTab implements KeyTabConstants { */ public synchronized void save() throws IOException { KeyTabOutputStream kos = - new KeyTabOutputStream(new FileOutputStream(name)); + new KeyTabOutputStream(new FileOutputStream(tabName)); kos.writeVersion(kt_vno); for (int i = 0; i < entries.size(); i++) { kos.writeEntry(entries.elementAt(i)); @@ -490,13 +524,4 @@ public class KeyTab implements KeyTabConstants { kos.write16(KRB5_KT_VNO); kos.close(); } - - public static void refresh() { - if (singleton != null) { - if (DEBUG) { - System.out.println("Refreshing Keytab"); - } - singleton = null; - } - } } diff --git a/jdk/src/share/classes/sun/security/ssl/ServerHandshaker.java b/jdk/src/share/classes/sun/security/ssl/ServerHandshaker.java index ff1c8d7d5c9..125de5f3f50 100644 --- a/jdk/src/share/classes/sun/security/ssl/ServerHandshaker.java +++ b/jdk/src/share/classes/sun/security/ssl/ServerHandshaker.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1996, 2010, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1996, 2011, 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 @@ -1285,11 +1285,12 @@ final class ServerHandshaker extends Handshaker { // check permission to access and use the secret key of the // Kerberized "host" service - if (kerberosKeys != null) { - + if (kerberosKeys != null && kerberosKeys.length > 0) { if (debug != null && Debug.isOn("handshake")) { - System.out.println("Using Kerberos key: " + - kerberosKeys[0]); + for (SecretKey k: kerberosKeys) { + System.out.println("Using Kerberos key: " + + k); + } } String serverPrincipal = diff --git a/jdk/src/share/classes/sun/security/ssl/krb5/Krb5ProxyImpl.java b/jdk/src/share/classes/sun/security/ssl/krb5/Krb5ProxyImpl.java index 94d1e8e75be..652268e166f 100644 --- a/jdk/src/share/classes/sun/security/ssl/krb5/Krb5ProxyImpl.java +++ b/jdk/src/share/classes/sun/security/ssl/krb5/Krb5ProxyImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009, 2010, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2009, 2011, 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 @@ -40,7 +40,7 @@ import sun.security.krb5.PrincipalName; import sun.security.ssl.Krb5Proxy; /** - * An implementatin of Krb5Proxy that simply delegates to the appropriate + * An implementation of Krb5Proxy that simply delegates to the appropriate * Kerberos APIs. */ public class Krb5ProxyImpl implements Krb5Proxy { @@ -62,7 +62,7 @@ public class Krb5ProxyImpl implements Krb5Proxy { @Override public SecretKey[] getServerKeys(AccessControlContext acc) throws LoginException { - return Krb5Util.getKeys(GSSCaller.CALLER_SSL_SERVER, null, acc); + return Krb5Util.getServiceCreds(GSSCaller.CALLER_SSL_SERVER, null, acc).getKKeys(); } @Override diff --git a/jdk/src/windows/classes/sun/security/krb5/internal/tools/Kinit.java b/jdk/src/windows/classes/sun/security/krb5/internal/tools/Kinit.java index f6c53f81f1e..41354c88a74 100644 --- a/jdk/src/windows/classes/sun/security/krb5/internal/tools/Kinit.java +++ b/jdk/src/windows/classes/sun/security/krb5/internal/tools/Kinit.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2000, 2010, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2000, 2011, 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 @@ -30,12 +30,15 @@ package sun.security.krb5.internal.tools; +import java.io.File; import sun.security.krb5.*; import sun.security.krb5.internal.*; 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; /** * Kinit tool for obtaining Kerberos v5 tickets. @@ -153,7 +156,6 @@ public class Kinit { System.out.println("Principal is " + principal); } char[] psswd = options.password; - EncryptionKey[] skeys = null; boolean useKeytab = options.useKeytabFile(); if (!useKeytab) { if (princName == null) { @@ -186,17 +188,9 @@ public class Kinit { } } - // assert princName and principal are nonnull - skeys = EncryptionKey.acquireSecretKeys(principal, ktabName); - - if (skeys == null || skeys.length == 0) { - String msg = "No supported key found in keytab"; - if (princName != null) { - msg += " for principal " + princName; - } - throw new KrbException(msg); - } - builder = new KrbAsReqBuilder(principal, skeys); + builder = new KrbAsReqBuilder(principal, ktabName == null + ? KeyTab.getInstance() + : KeyTab.getInstance(new File(ktabName))); } KDCOptions opt = new KDCOptions(); diff --git a/jdk/src/windows/classes/sun/security/krb5/internal/tools/Klist.java b/jdk/src/windows/classes/sun/security/krb5/internal/tools/Klist.java index ad5c8aa34f0..6cbc8a3fa61 100644 --- a/jdk/src/windows/classes/sun/security/krb5/internal/tools/Klist.java +++ b/jdk/src/windows/classes/sun/security/krb5/internal/tools/Klist.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2009, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 2011, 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 @@ -95,16 +95,15 @@ public class Klist { } break; case 'k': - if (klist.name == null) { - klist.target = KeyTab.getInstance(); - klist.name = KeyTab.tabName(); - } else klist.target = KeyTab.getInstance(klist.name); - if (klist.target != null) { - klist.displayTab(); - } else { + try { + KeyTab ktab = KeyTab.getInstance(klist.name); + klist.target = ktab; + klist.name = ktab.tabName(); + } catch (Exception e) { klist.displayMessage("KeyTab"); System.exit(-1); } + klist.displayTab(); break; default: if (klist.name != null) { @@ -295,9 +294,10 @@ public class Klist { void displayMessage(String target) { if (name == null) { - name = ""; + System.out.println("Default " + target + " not found."); + } else { + System.out.println(target + " " + name + " not found."); } - System.out.println(target + " " + name + " not found."); } /** * Reformats the date from the form - diff --git a/jdk/src/windows/classes/sun/security/krb5/internal/tools/Ktab.java b/jdk/src/windows/classes/sun/security/krb5/internal/tools/Ktab.java index 216c3a30c12..fe29462add5 100644 --- a/jdk/src/windows/classes/sun/security/krb5/internal/tools/Ktab.java +++ b/jdk/src/windows/classes/sun/security/krb5/internal/tools/Ktab.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2010, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 2011, 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 @@ -321,7 +321,7 @@ public class Ktab { * Lists key table name and entries in it. */ void listKt() { - System.out.println("Keytab name: " + KeyTab.tabName()); + System.out.println("Keytab name: " + table.tabName()); KeyTabEntry[] entries = table.getEntries(); if ((entries != null) && (entries.length > 0)) { String[][] output = new String[entries.length+1][showTime?3:2]; diff --git a/jdk/test/sun/security/krb5/auto/Context.java b/jdk/test/sun/security/krb5/auto/Context.java index 676042ba993..1739f876f08 100644 --- a/jdk/test/sun/security/krb5/auto/Context.java +++ b/jdk/test/sun/security/krb5/auto/Context.java @@ -44,6 +44,7 @@ import com.sun.security.jgss.InquireType; import com.sun.security.jgss.AuthorizationDataEntry; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; +import javax.security.auth.kerberos.KeyTab; /** * Context of a JGSS subject, encapsulating Subject and GSSContext. @@ -107,15 +108,19 @@ public class Context { return out; } + public static Context fromUserPass( + String user, char[] pass, boolean storeKey) throws Exception { + return fromUserPass(null, user, pass, storeKey); + } /** * Logins with a username and a password, using Krb5LoginModule directly * @param storeKey true if key should be saved, used on acceptor side */ - public static Context fromUserPass(String user, char[] pass, boolean storeKey) - throws Exception { + public static Context fromUserPass(Subject s, + String user, char[] pass, boolean storeKey) throws Exception { Context out = new Context(); out.name = user; - out.s = new Subject(); + out.s = s == null ? new Subject() : s; Krb5LoginModule krb5 = new Krb5LoginModule(); Map map = new HashMap<>(); Map shared = new HashMap<>(); @@ -198,12 +203,25 @@ public class Context { * @throws java.lang.Exception */ public void startAsServer(final Oid mech) throws Exception { + startAsServer(null, mech); + } + + /** + * Starts as a server with the specified service name + * @param name the service name + * @param mech GSS mech + * @throws java.lang.Exception + */ + public void startAsServer(final String name, final Oid mech) throws Exception { doAs(new Action() { @Override public byte[] run(Context me, byte[] dummy) throws Exception { GSSManager m = GSSManager.getInstance(); me.x = (ExtendedGSSContext)m.createContext(m.createCredential( - null, + name == null ? null : + (name.indexOf('@') < 0 ? + m.createName(name, null) : + m.createName(name, GSSName.NT_HOSTBASED_SERVICE)), GSSCredential.INDEFINITE_LIFETIME, mech, GSSCredential.ACCEPT_ONLY)); @@ -229,6 +247,14 @@ public class Context { return x; } + /** + * Accesses the internal subject. + * @return the subject + */ + public Subject s() { + return s; + } + /** * Disposes the GSSContext within * @throws org.ietf.jgss.GSSException @@ -297,7 +323,7 @@ public class Context { } catch (Exception e) { ;// Don't care } - System.out.println("====================================="); + System.out.println("====== Private Credentials Set ======"); for (Object o : s.getPrivateCredentials()) { System.out.println(" " + o.getClass()); if (o instanceof KerberosTicket) { @@ -315,6 +341,8 @@ public class Context { for (Object k : map.keySet()) { System.out.println(" " + k + ": " + map.get(k)); } + } else { + System.out.println(" " + o); } } if (x != null && x instanceof ExtendedGSSContext) { diff --git a/jdk/test/sun/security/krb5/auto/DynamicKeytab.java b/jdk/test/sun/security/krb5/auto/DynamicKeytab.java new file mode 100644 index 00000000000..256476b0b5e --- /dev/null +++ b/jdk/test/sun/security/krb5/auto/DynamicKeytab.java @@ -0,0 +1,140 @@ +/* + * Copyright (c) 2011, 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 6894072 + * @run main/othervm DynamicKeytab + * @summary always refresh keytab + */ + +import java.io.File; +import java.io.FileOutputStream; +import org.ietf.jgss.GSSException; +import sun.security.jgss.GSSUtil; +import sun.security.krb5.KrbException; +import sun.security.krb5.internal.Krb5; + +public class DynamicKeytab { + + Context c, s; + public static void main(String[] args) + throws Exception { + new DynamicKeytab().go(); + } + + void go() throws Exception { + OneKDC k = new OneKDC(null); + k.writeJAASConf(); + + new File(OneKDC.KTAB).delete(); + + + // Starts with no keytab + c = Context.fromJAAS("client"); + s = Context.fromJAAS("com.sun.security.jgss.krb5.accept"); + + // Test 1: read new key 1 from keytab + k.addPrincipal(OneKDC.SERVER, "pass1".toCharArray()); + k.writeKtab(OneKDC.KTAB); + connect(); + + // Test 2: service key cached, find 1 in keytab (now contains 1 and 2) + k.addPrincipal(OneKDC.SERVER, "pass2".toCharArray()); + k.appendKtab(OneKDC.KTAB); + connect(); + + // Test 3: re-login. Now find 2 in keytab + c = Context.fromJAAS("client"); + connect(); + + // Test 4: re-login, KDC use 3 this time. + c = Context.fromJAAS("client"); + // Put 3 and 4 into keytab but keep the real key back to 3. + k.addPrincipal(OneKDC.SERVER, "pass3".toCharArray()); + k.appendKtab(OneKDC.KTAB); + k.addPrincipal(OneKDC.SERVER, "pass4".toCharArray()); + k.appendKtab(OneKDC.KTAB); + k.addPrincipal(OneKDC.SERVER, "pass3".toCharArray()); + connect(); + + // Test 5: invalid keytab file, should ignore + new FileOutputStream(OneKDC.KTAB).write("BADBADBAD".getBytes()); + connect(); + + // Test 6: delete keytab file, identical to revoke all + new File(OneKDC.KTAB).delete(); + try { + connect(); + throw new Exception("Should not success"); + } catch (GSSException gsse) { + System.out.println(gsse); + KrbException ke = (KrbException)gsse.getCause(); + // KrbApReq.authenticate(*) if (dkey == null)... + // This should have been Krb5.KRB_AP_ERR_NOKEY + if (ke.returnCode() != Krb5.API_INVALID_ARG) { + throw new Exception("Not expected failure code: " + + ke.returnCode()); + } + } + + // Test 7: 3 revoked, should fail (now contains only 5) + k.addPrincipal(OneKDC.SERVER, "pass5".toCharArray()); + k.writeKtab(OneKDC.KTAB); // overwrite keytab, which means + // old key is revoked + try { + connect(); + throw new Exception("Should not success"); + } catch (GSSException gsse) { + System.out.println(gsse); + KrbException ke = (KrbException)gsse.getCause(); + if (ke.returnCode() != Krb5.KRB_AP_ERR_BADKEYVER) { + throw new Exception("Not expected failure code: " + + ke.returnCode()); + } + } + + // Test 8: an empty KDC means revoke all + KDC.create("EMPTY.REALM").writeKtab(OneKDC.KTAB); + try { + connect(); + throw new Exception("Should not success"); + } catch (GSSException gsse) { + System.out.println(gsse); + KrbException ke = (KrbException)gsse.getCause(); + // KrbApReq.authenticate(*) if (dkey == null)... + // This should have been Krb5.KRB_AP_ERR_NOKEY + if (ke.returnCode() != Krb5.API_INVALID_ARG) { + throw new Exception("Not expected failure code: " + + ke.returnCode()); + } + } + } + + void connect() throws Exception { + Thread.sleep(2000); // make sure ktab timestamp is different + c.startAsClient(OneKDC.SERVER, GSSUtil.GSS_KRB5_MECH_OID); + s.startAsServer(GSSUtil.GSS_KRB5_MECH_OID); + Context.handshake(c, s); + } +} diff --git a/jdk/test/sun/security/krb5/auto/KDC.java b/jdk/test/sun/security/krb5/auto/KDC.java index 8d01dd346cd..45a3d530c4b 100644 --- a/jdk/test/sun/security/krb5/auto/KDC.java +++ b/jdk/test/sun/security/krb5/auto/KDC.java @@ -228,7 +228,33 @@ public class KDC { } /** - * Write all principals' keys from multiple KDCsinto one keytab file. + * Writes or appends KDC keys into a keytab. See doc for writeMultiKtab. + * @param append true if append, otherwise, overwrite. + */ + private static void writeKtab0(String tab, boolean append, KDC... kdcs) + throws IOException, KrbException { + KeyTab ktab = append ? KeyTab.getInstance(tab) : KeyTab.create(tab); + for (KDC kdc: kdcs) { + for (String name : kdc.passwords.keySet()) { + char[] pass = kdc.passwords.get(name); + int kvno = 0; + if (Character.isDigit(pass[pass.length-1])) { + kvno = pass[pass.length-1] - '0'; + } + ktab.addEntry(new PrincipalName(name, + name.indexOf('/') < 0 ? + PrincipalName.KRB_NT_UNKNOWN : + PrincipalName.KRB_NT_SRV_HST), + pass, + kvno, + true); + } + } + ktab.save(); + } + + /** + * Writes all principals' keys from multiple KDCs into one keytab file. * Note that the keys for the krbtgt principals will not be written. *

* Attention: This method references krb5.conf settings. If you need to @@ -252,17 +278,16 @@ public class KDC { */ public static void writeMultiKtab(String tab, KDC... kdcs) throws IOException, KrbException { - KeyTab ktab = KeyTab.create(tab); - for (KDC kdc: kdcs) { - for (String name : kdc.passwords.keySet()) { - ktab.addEntry(new PrincipalName(name, - name.indexOf('/') < 0 ? - PrincipalName.KRB_NT_UNKNOWN : - PrincipalName.KRB_NT_SRV_HST), - kdc.passwords.get(name), -1, true); - } - } - ktab.save(); + writeKtab0(tab, false, kdcs); + } + + /** + * Appends all principals' keys from multiple KDCs to one keytab file. + * See writeMultiKtab for details. + */ + public static void appendMultiKtab(String tab, KDC... kdcs) + throws IOException, KrbException { + writeKtab0(tab, true, kdcs); } /** @@ -272,6 +297,13 @@ public class KDC { KDC.writeMultiKtab(tab, this); } + /** + * Appends keys in this KDC to a ktab. + */ + public void appendKtab(String tab) throws IOException, KrbException { + KDC.appendMultiKtab(tab, this); + } + /** * Adds a new principal to this realm with a given password. * @param user the principal's name. For a service principal, use the diff --git a/jdk/test/sun/security/krb5/auto/KeyTabCompat.java b/jdk/test/sun/security/krb5/auto/KeyTabCompat.java new file mode 100644 index 00000000000..f6763510fd2 --- /dev/null +++ b/jdk/test/sun/security/krb5/auto/KeyTabCompat.java @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2011, 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 6894072 + * @compile -XDignore.symbol.file KeyTabCompat.java + * @run main/othervm KeyTabCompat + * @summary always refresh keytab + */ + +import javax.security.auth.kerberos.KerberosKey; +import sun.security.jgss.GSSUtil; + +/* + * There are 2 compat issues to check: + * + * 1. If there is only KerberosKeys in private credential set and no + * KerberosPrincipal. JAAS login should go on. + * 2. Even if KeyTab is used, user can still get KerberosKeys from + * private credentials set. + */ +public class KeyTabCompat { + + public static void main(String[] args) + throws Exception { + OneKDC kdc = new OneKDC("aes128-cts"); + kdc.writeJAASConf(); + kdc.addPrincipal(OneKDC.SERVER, "pass1".toCharArray()); + kdc.writeKtab(OneKDC.KTAB); + + Context c, s; + + // Part 1 + c = Context.fromUserPass(OneKDC.USER, OneKDC.PASS, false); + s = Context.fromUserPass(OneKDC.USER2, OneKDC.PASS2, true); + + s.s().getPrincipals().clear(); + + c.startAsClient(OneKDC.USER2, GSSUtil.GSS_KRB5_MECH_OID); + s.startAsServer(GSSUtil.GSS_KRB5_MECH_OID); + + Context.handshake(c, s); + + // Part 2 + c = Context.fromJAAS("client"); + s = Context.fromJAAS("server"); + + c.startAsClient(OneKDC.SERVER, GSSUtil.GSS_KRB5_MECH_OID); + s.startAsServer(GSSUtil.GSS_KRB5_MECH_OID); + s.status(); + + if (s.s().getPrivateCredentials(KerberosKey.class).size() != 1) { + throw new Exception("There should be one KerberosKey"); + } + + Thread.sleep(2000); // make sure ktab timestamp is different + + kdc.addPrincipal(OneKDC.SERVER, "pass2".toCharArray()); + kdc.writeKtab(OneKDC.KTAB); + + Context.handshake(c, s); + s.status(); + + if (s.s().getPrivateCredentials(KerberosKey.class).size() != 1) { + throw new Exception("There should be only one KerberosKey"); + } + + } +} diff --git a/jdk/test/sun/security/krb5/auto/LoginModuleOptions.java b/jdk/test/sun/security/krb5/auto/LoginModuleOptions.java index a6dd33b8029..2c77451d748 100644 --- a/jdk/test/sun/security/krb5/auto/LoginModuleOptions.java +++ b/jdk/test/sun/security/krb5/auto/LoginModuleOptions.java @@ -28,7 +28,6 @@ * @summary Krb5LoginModule a little too restrictive, and the doc is not clear. */ import com.sun.security.auth.module.Krb5LoginModule; -import java.io.IOException; import java.util.HashMap; import java.util.Map; import javax.security.auth.Subject; @@ -36,7 +35,6 @@ 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; public class LoginModuleOptions { @@ -61,10 +59,12 @@ public class LoginModuleOptions { // 1. ccache -> keytab login(null, "useTicketCache", "true", "ticketCache", "krbcc_non_exists", "useKeyTab", "true", "principal", "dummy"); + // 2. keytab -> shared login(null, "useKeyTab", "true", "principal", "dummy", "keyTab", "ktab_non_exist", "tryFirstPass", "true", NAME, OneKDC.USER, PWD, OneKDC.PASS); + // 3. shared -> callback // 3.1. useFirstPass, no callback boolean failed = false; diff --git a/jdk/test/sun/security/krb5/auto/SSL.java b/jdk/test/sun/security/krb5/auto/SSL.java index 7bd4601481e..eca535051fc 100644 --- a/jdk/test/sun/security/krb5/auto/SSL.java +++ b/jdk/test/sun/security/krb5/auto/SSL.java @@ -48,7 +48,7 @@ import sun.security.krb5.internal.ktab.KeyTab; public class SSL { private static String krb5Cipher; - private static final int LOOP_LIMIT = 1; + private static final int LOOP_LIMIT = 3; private static int loopCount = 0; private static volatile String server; private static volatile int port; @@ -98,13 +98,13 @@ public class SSL { fos.close(); f.deleteOnExit(); - final Context c = Context.fromUserPass(OneKDC.USER, OneKDC.PASS, false); + Context c; final Context s = Context.fromJAAS("ssl"); - c.startAsClient("host/" + server, GSSUtil.GSS_KRB5_MECH_OID); + // There's no keytab file when server starts. s.startAsServer(GSSUtil.GSS_KRB5_MECH_OID); - new Thread(new Runnable() { + Thread server = new Thread(new Runnable() { public void run() { try { s.doAs(new JsseServerAction(), null); @@ -112,12 +112,57 @@ public class SSL { e.printStackTrace(); } } - }).start(); + }); + server.setDaemon(true); + server.start(); // Warm the server Thread.sleep(2000); + // 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); + + // Add another version of key, make sure it can be loaded + Thread.sleep(2000); + ktab = KeyTab.getInstance(OneKDC.KTAB); + ktab.addEntry(service, "pass4".toCharArray(), 4, true); + ktab.save(); + kdc.addPrincipal("host/" + server, "pass4".toCharArray()); + + c = Context.fromUserPass(OneKDC.USER, OneKDC.PASS, false); + 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. + }*/ } // Following codes copied from @@ -126,6 +171,7 @@ public class SSL { public byte[] run(Context s, byte[] input) throws Exception { SSLSocketFactory sslsf = (SSLSocketFactory) SSLSocketFactory.getDefault(); + System.out.println("Connecting " + server + ":" + port); SSLSocket sslSocket = (SSLSocket) sslsf.createSocket(server, port); // Enable only a KRB5 cipher suite. @@ -154,6 +200,9 @@ public class SSL { System.out.println("Server is: " + peer.toString()); sslSocket.close(); + // This line should not be needed. It's the server's duty to + // forget the old key + //sslSocket.getSession().invalidate(); return null; } } @@ -165,6 +214,7 @@ public class SSL { SSLServerSocket sslServerSocket = (SSLServerSocket) sslssf.createServerSocket(0); // any port port = sslServerSocket.getLocalPort(); + System.out.println("Listening on " + port); // Enable only a KRB5 cipher suite. String enabledSuites[] = {krb5Cipher}; diff --git a/jdk/test/sun/security/krb5/auto/TwoPrinces.java b/jdk/test/sun/security/krb5/auto/TwoPrinces.java new file mode 100644 index 00000000000..30f16e96a25 --- /dev/null +++ b/jdk/test/sun/security/krb5/auto/TwoPrinces.java @@ -0,0 +1,102 @@ +/* + * Copyright (c) 2011, 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 6894072 + * @compile -XDignore.symbol.file TwoPrinces.java + * @run main/othervm TwoPrinces + * @summary always refresh keytab + */ + +import java.io.File; +import java.io.FileOutputStream; +import sun.security.jgss.GSSUtil; +import sun.security.krb5.Config; + +public class TwoPrinces { + + public static void main(String[] args) + throws Exception { + + KDC k1 = KDC.create("R1"); + k1.addPrincipal("u1", "hello".toCharArray()); + k1.addPrincipalRandKey("krbtgt/R1"); + k1.addPrincipalRandKey("host/same.host"); + + KDC k2 = KDC.create("R2"); + k2.addPrincipal("u2", "hello".toCharArray()); + k2.addPrincipalRandKey("krbtgt/R2"); + k2.addPrincipalRandKey("host/same.host"); + + System.setProperty("java.security.krb5.conf", "krb5.conf"); + + // R1 is the default realm now + KDC.saveConfig("krb5.conf", k1, k2); + Config.refresh(); + + k1.writeKtab("ktab1"); + k2.writeKtab("ktab2"); + + // A JAAS config file with 2 Krb5LoginModules, after commit, the + // subject with have principals and keytabs from both sides + System.setProperty("java.security.auth.login.config", "jaas.conf"); + File f = new File("jaas.conf"); + FileOutputStream fos = new FileOutputStream(f); + fos.write(( + "me {\n" + + " com.sun.security.auth.module.Krb5LoginModule required" + + " isInitiator=true principal=\"host/same.host@R1\"" + + " useKeyTab=true keyTab=ktab1 storeKey=true;\n" + + " com.sun.security.auth.module.Krb5LoginModule required" + + " isInitiator=true principal=\"host/same.host@R2\"" + + " useKeyTab=true keyTab=ktab2 storeKey=true;\n" + + "};\n" + ).getBytes()); + fos.close(); + + /* + * This server side context will be able to act as services in both + * realms. Please note that we still don't support a single instance + * of server to accept connections from two realms at the same time. + * Therefore, we must call startAsServer in a given realm to start + * working there. The same Subject never changes anyway. + */ + Context s = Context.fromJAAS("me"); + + // Default realm still R1 + s.startAsServer("host@same.host", GSSUtil.GSS_KRB5_MECH_OID); + Context c1 = Context.fromUserPass("u1", "hello".toCharArray(), false); + c1.startAsClient("host@same.host", GSSUtil.GSS_KRB5_MECH_OID); + Context.handshake(c1, s); + + KDC.saveConfig("krb5.conf", k2, k1); + Config.refresh(); + + // Default realm now R2 + s.startAsServer("host@same.host", GSSUtil.GSS_KRB5_MECH_OID); + Context c2 = Context.fromUserPass("u2", "hello".toCharArray(), false); + c2.startAsClient("host@same.host", GSSUtil.GSS_KRB5_MECH_OID); + Context.handshake(c2, s); + } +} diff --git a/jdk/test/sun/security/krb5/ktab/KeyTabIndex.java b/jdk/test/sun/security/krb5/ktab/KeyTabIndex.java index f0cd88e6a65..26eb5621939 100644 --- a/jdk/test/sun/security/krb5/ktab/KeyTabIndex.java +++ b/jdk/test/sun/security/krb5/ktab/KeyTabIndex.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2010, 2011, 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 @@ -44,7 +44,6 @@ public class KeyTabIndex { KeyTab.getInstance("ktab").getClass(); } }; - KeyTab.refresh(); for (int i=0; i<10; i++) { new Thread(t).start(); }