diff options
16 files changed, 652 insertions, 122 deletions
diff --git a/src/share/classes/com/sun/security/auth/module/Krb5LoginModule.java b/src/share/classes/com/sun/security/auth/module/Krb5LoginModule.java index 3d1744ba17..719aeee76d 100644 --- a/src/share/classes/com/sun/security/auth/module/Krb5LoginModule.java +++ b/src/share/classes/com/sun/security/auth/module/Krb5LoginModule.java @@ -52,16 +52,19 @@ import sun.misc.HexDumpEncoder; * principal set and private credentials set are updated only when * <code>commit</code> is called. * When <code>commit</code> is called, the <code>KerberosPrincipal</code> - * is added to the <code>Subject</code>'s - * principal set and <code>KerberosTicket</code> is + * is added to the <code>Subject</code>'s principal set (unless the + * <code>principal</code> is specified as "*"). If <code>isInitiator</code> + * is true, the <code>KerberosTicket</code> is * added to the <code>Subject</code>'s private credentials. * * <p> If the configuration entry for <code>KerberosLoginModule</code> * has the option <code>storeKey</code> set to true, then - * <code>KerberosKey</code> will also be added to the + * <code>KerberosKey</code> or <code>KeyTab</code> will also be added to the * subject's private credentials. <code>KerberosKey</code>, the principal's - * key will be either obtained from the keytab or - * derived from user's password. + * key(s) will be derived from user's password, and <code>KeyTab</code> is + * the keytab used when <code>useKeyTab</code> is set to true. The + * <code>KeyTab</code> object is restricted to be used by the specified + * principal unless the principal value is "*". * * <p> This <code>LoginModule</code> recognizes the <code>doNotPrompt</code> * option. If set to true the user will not be prompted for the password. @@ -75,8 +78,8 @@ import sun.misc.HexDumpEncoder; * * <p> The principal name can be specified in the configuration entry * by using the option <code>principal</code>. The principal name - * can either be a simple user name or a service name such as - * <code>host/mission.eng.sun.com</code>. The principal can also + * can either be a simple user name, a service name such as + * <code>host/mission.eng.sun.com</code>, or "*". The principal can also * be set using the system property <code>sun.security.krb5.principal</code>. * This property is checked during login. If this property is not set, then * the principal name from the configuration is used. In the @@ -87,11 +90,10 @@ import sun.misc.HexDumpEncoder; * * <p> The following is a list of configuration options supported * for <code>Krb5LoginModule</code>: - * <dl> - * <blockquote><dt><b><code>refreshKrb5Config</code></b>:</dt> + * <blockquote><dl> + * <dt><b><code>refreshKrb5Config</code></b>:</dt> * <dd> Set this to true, if you want the configuration * to be refreshed before the <code>login</code> method is called.</dd> - * <P> * <dt><b><code>useTicketCache</code></b>:</dt> * <dd>Set this to true, if you want the * TGT to be obtained @@ -112,19 +114,16 @@ import sun.misc.HexDumpEncoder; * <code>ticketCache</code>. * For Windows, if a ticket cannot be retrieved from the file ticket cache, * it will use Local Security Authority (LSA) API to get the TGT. - * <P> * <dt><b><code>ticketCache</code></b>:</dt> * <dd>Set this to the name of the ticket * cache that contains user's TGT. * If this is set, <code>useTicketCache</code> * must also be set to true; Otherwise a configuration error will * be returned.</dd> - * <P> * <dt><b><code>renewTGT</code></b>:</dt> * <dd>Set this to true, if you want to renew * the TGT. If this is set, <code>useTicketCache</code> must also be * set to true; otherwise a configuration error will be returned.</dd> - * <p> * <dt><b><code>doNotPrompt</code></b>:</dt> * <dd>Set this to true if you do not want to be * prompted for the password @@ -132,7 +131,6 @@ import sun.misc.HexDumpEncoder; * or through shared state.(Default is false) * If set to true, credential must be obtained through cache, keytab, * or shared state. Otherwise, authentication will fail.</dd> - * <P> * <dt><b><code>useKeyTab</code></b>:</dt> * <dd>Set this to true if you * want the module to get the principal's key from the @@ -144,15 +142,15 @@ import sun.misc.HexDumpEncoder; * If it is not specified in the Kerberos configuration file * then it will look for the file * <code>{user.home}{file.separator}</code>krb5.keytab.</dd> - * <P> * <dt><b><code>keyTab</code></b>:</dt> * <dd>Set this to the file name of the * keytab to get principal's secret key.</dd> - * <P> * <dt><b><code>storeKey</code></b>:</dt> - * <dd>Set this to true to if you want the - * principal's key to be stored in the Subject's private credentials. </dd> - * <p> + * <dd>Set this to true to if you want the keytab or the + * principal's key to be stored in the Subject's private credentials. + * For <code>isInitiator</code> being false, if <code>principal</code> + * is "*", the {@link KeyTab} stored can be used by anyone, otherwise, + * it's restricted to be used by the specified principal only.</dd> * <dt><b><code>principal</code></b>:</dt> * <dd>The name of the principal that should * be used. The principal can be a simple username such as @@ -165,8 +163,13 @@ import sun.misc.HexDumpEncoder; * <code>sun.security.krb5.principal</code>. In addition, if this * system property is defined, then it will be used. If this property * is not set, then the principal name from the configuration will be - * used.</dd> - * <P> + * used. + * The principal name can be set to "*" when <code>isInitiator</code> is false. + * In this case, the acceptor is not bound to a single principal. It can + * act as any principal an initiator requests if keys for that principal + * can be found. When <code>isInitiator</code> is true, the principal name + * cannot be set to "*". + * </dd> * <dt><b><code>isInitiator</code></b>:</dt> * <dd>Set this to true, if initiator. Set this to false, if acceptor only. * (Default is true). @@ -177,18 +180,20 @@ import sun.misc.HexDumpEncoder; * <code>Configuration</code> * options that enable you to share username and passwords across different * authentication modules: - * <pre> + * <blockquote><dl> * - * useFirstPass if, true, this LoginModule retrieves the + * <dt><b><code>useFirstPass</code></b>:</dt> + * <dd>if, true, this LoginModule retrieves the * username and password from the module's shared state, * using "javax.security.auth.login.name" and * "javax.security.auth.login.password" as the respective * keys. The retrieved values are used for authentication. * If authentication fails, no attempt for a retry * is made, and the failure is reported back to the - * calling application. + * calling application.</dd> * - * tryFirstPass if, true, this LoginModule retrieves the + * <dt><b><code>tryFirstPass</code></b>:</dt> + * <dd>if, true, this LoginModule retrieves the * the username and password from the module's shared * state using "javax.security.auth.login.name" and * "javax.security.auth.login.password" as the respective @@ -198,26 +203,28 @@ import sun.misc.HexDumpEncoder; * CallbackHandler to retrieve a new username * and password, and another attempt to authenticate * is made. If the authentication fails, - * the failure is reported back to the calling application + * the failure is reported back to the calling application</dd> * - * storePass if, true, this LoginModule stores the username and + * <dt><b><code>storePass</code></b>:</dt> + * <dd>if, true, this LoginModule stores the username and * password obtained from the CallbackHandler in the * modules shared state, using * "javax.security.auth.login.name" and * "javax.security.auth.login.password" as the respective * keys. This is not performed if existing values already * exist for the username and password in the shared - * state, or if authentication fails. + * state, or if authentication fails.</dd> * - * clearPass if, true, this LoginModule clears the + * <dt><b><code>clearPass</code></b>:</dt> + * <dd>if, true, this LoginModule clears the * username and password stored in the module's shared * state after both phases of authentication - * (login and commit) have completed. - * </pre> + * (login and commit) have completed.</dd> + * </dl></blockquote> * <p>If the principal system property or key is already provided, the value of * "javax.security.auth.login.name" in the shared state is ignored. * <p>When multiple mechanisms to retrieve a ticket or key is provided, the - * preference order looks like this: + * preference order is: * <ol> * <li>ticket cache * <li>keytab @@ -225,7 +232,7 @@ import sun.misc.HexDumpEncoder; * <li>user prompt * </ol> * <p>Note that if any step fails, it will fallback to the next step. - * There's only one exception, it the shared state step fails and + * There's only one exception, if the shared state step fails and * <code>useFirstPass</code>=true, no user prompt is made. * <p>Examples of some configuration values for Krb5LoginModule in * JAAS config file and the results are: @@ -318,7 +325,7 @@ import sun.misc.HexDumpEncoder; * <p> <code>useKeyTab</code> = true * <code>keyTab</code>=<keytabname> * <code>storeKey</code>=true - * <code>doNotPrompt</code>=true; + * <code>doNotPrompt</code>=false; *</ul> * <p>The user will be prompted for the service principal name. * If the principal's @@ -328,6 +335,14 @@ import sun.misc.HexDumpEncoder; * If successful the TGT will be added to the * Subject's private credentials set. Otherwise the authentication will * fail. + * <ul> + * <p> <code>isInitiator</code> = false <code>useKeyTab</code> = true + * <code>keyTab</code>=<keytabname> + * <code>storeKey</code>=true + * <code>principal</code>=*; + *</ul> + * <p>The acceptor will be an unbound acceptor and it can act as any principal + * as long that principal has keys in the keytab. *<ul> * <p> * <code>useTicketCache</code>=true @@ -409,6 +424,7 @@ public class Krb5LoginModule implements LoginModule { private KerberosTicket kerbTicket = null; private KerberosKey[] kerbKeys = null; private StringBuffer krb5PrincName = null; + private boolean unboundServer = false; private char[] password = null; private static final String NAME = "javax.security.auth.login.name"; @@ -520,8 +536,6 @@ public class Krb5LoginModule implements LoginModule { */ public boolean login() throws LoginException { - int len; - validateConfiguration(); if (refreshKrb5Config) { try { if (debug) { @@ -544,6 +558,12 @@ public class Krb5LoginModule implements LoginModule { } } + validateConfiguration(); + + if (krb5PrincName != null && krb5PrincName.toString().equals("*")) { + unboundServer = true; + } + if (tryFirstPass) { try { attemptAuthentication(true); @@ -698,9 +718,17 @@ public class Krb5LoginModule implements LoginModule { * (encKeys == null) to check. */ if (useKeyTab) { - ktab = (keyTabName == null) - ? KeyTab.getInstance() - : KeyTab.getInstance(new File(keyTabName)); + if (!unboundServer) { + KerberosPrincipal kp = + new KerberosPrincipal(principal.getName()); + ktab = (keyTabName == null) + ? KeyTab.getInstance(kp) + : KeyTab.getInstance(kp, new File(keyTabName)); + } else { + ktab = (keyTabName == null) + ? KeyTab.getUnboundInstance() + : KeyTab.getUnboundInstance(new File(keyTabName)); + } if (isInitiator) { if (Krb5Util.keysFromJavaxKeyTab(ktab, principal).length == 0) { @@ -939,6 +967,13 @@ public class Krb5LoginModule implements LoginModule { ("Configuration Error" + " - either useTicketCache should be " + " true or renewTGT should be false"); + if (krb5PrincName != null && krb5PrincName.toString().equals("*")) { + if (isInitiator) { + throw new LoginException + ("Configuration Error" + + " - principal cannot be * when isInitiator is true"); + } + } } private boolean isCurrent(Credentials creds) @@ -1052,7 +1087,10 @@ public class Krb5LoginModule implements LoginModule { } // Let us add the kerbClientPrinc,kerbTicket and KeyTab/KerbKey (if // storeKey is true) - if (!princSet.contains(kerbClientPrinc)) { + + // We won't add "*" as a KerberosPrincipal + if (!unboundServer && + !princSet.contains(kerbClientPrinc)) { princSet.add(kerbClientPrinc); } diff --git a/src/share/classes/javax/security/auth/kerberos/JavaxSecurityAuthKerberosAccessImpl.java b/src/share/classes/javax/security/auth/kerberos/JavaxSecurityAuthKerberosAccessImpl.java index 4b20626ec7..fbf7471893 100644 --- a/src/share/classes/javax/security/auth/kerberos/JavaxSecurityAuthKerberosAccessImpl.java +++ b/src/share/classes/javax/security/auth/kerberos/JavaxSecurityAuthKerberosAccessImpl.java @@ -31,8 +31,8 @@ import sun.security.krb5.PrincipalName; class JavaxSecurityAuthKerberosAccessImpl implements JavaxSecurityAuthKerberosAccess { - public EncryptionKey[] keyTabGetEncryptionKeys( - KeyTab ktab, PrincipalName principal) { - return ktab.getEncryptionKeys(principal); + public sun.security.krb5.internal.ktab.KeyTab keyTabTakeSnapshot( + KeyTab ktab) { + return ktab.takeSnapshot(); } } diff --git a/src/share/classes/javax/security/auth/kerberos/KeyTab.java b/src/share/classes/javax/security/auth/kerberos/KeyTab.java index 3ebce9975a..32f644b590 100644 --- a/src/share/classes/javax/security/auth/kerberos/KeyTab.java +++ b/src/share/classes/javax/security/auth/kerberos/KeyTab.java @@ -41,6 +41,20 @@ import sun.security.krb5.RealmException; * {@link javax.security.auth.Subject Subject} during the commit phase of the * authentication process. * <p> + * If a {@code KeyTab} object is obtained from {@link #getUnboundInstance()} + * or {@link #getUnboundInstance(java.io.File)}, it is unbound and thus can be + * used by any service principal. Otherwise, if it's obtained from + * {@link #getInstance(KerberosPrincipal)} or + * {@link #getInstance(KerberosPrincipal, java.io.File)}, it is bound to the + * specific service principal and can only be used by it. + * <p> + * Please note the constructors {@link #getInstance()} and + * {@link #getInstance(java.io.File)} were created when there was no support + * for unbound keytabs. These methods should not be used anymore. An object + * created with either of these methods are considered to be bound to an + * unknown principal, which means, its {@link #isBound()} returns true and + * {@link #getPrincipal()} returns null. + * <p> * It might be necessary for the application to be granted a * {@link javax.security.auth.PrivateCredentialPermission * PrivateCredentialPermission} if it needs to access the KeyTab @@ -52,7 +66,7 @@ import sun.security.krb5.RealmException; * The keytab file format is described at * <a href="http://www.ioplex.com/utilities/keytab.txt"> * http://www.ioplex.com/utilities/keytab.txt</a>. - * + * <p> * @since 1.7 */ public final class KeyTab { @@ -74,21 +88,33 @@ public final class KeyTab { // is maintained in snapshot, this field is never "resolved". private final File file; + // Bound user: normally from the "principal" value in a JAAS krb5 + // login conf. Will be null if it's "*". + private final KerberosPrincipal princ; + + private final boolean bound; + // Set up JavaxSecurityAuthKerberosAccess in KerberosSecrets static { KerberosSecrets.setJavaxSecurityAuthKerberosAccess( new JavaxSecurityAuthKerberosAccessImpl()); } - private KeyTab(File file) { + private KeyTab(KerberosPrincipal princ, File file, boolean bound) { + this.princ = princ; this.file = file; + this.bound = bound; } /** - * Returns a {@code KeyTab} instance from a {@code File} object. + * Returns a {@code KeyTab} instance from a {@code File} object + * that is bound to an unknown service principal. * <p> * 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. + * <p> + * Developers should call {@link #getInstance(KerberosPrincipal,File)} + * when the bound service principal is known. * @param file the keytab {@code File} object, must not be null * @return the keytab instance * @throws NullPointerException if the {@code file} argument is null @@ -97,23 +123,99 @@ public final class KeyTab { if (file == null) { throw new NullPointerException("file must be non null"); } - return new KeyTab(file); + return new KeyTab(null, file, true); } /** - * Returns the default {@code KeyTab} instance. + * Returns an unbound {@code KeyTab} instance from a {@code File} + * object. + * <p> + * 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 file argument is null + * @since 1.8 + */ + public static KeyTab getUnboundInstance(File file) { + if (file == null) { + throw new NullPointerException("file must be non null"); + } + return new KeyTab(null, file, false); + } + + /** + * Returns a {@code KeyTab} instance from a {@code File} object + * that is bound to the specified service principal. + * <p> + * 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 princ the bound service principal, must not be null + * @param file the keytab {@code File} object, must not be null + * @return the keytab instance + * @throws NullPointerException if either of the arguments is null + * @since 1.8 + */ + public static KeyTab getInstance(KerberosPrincipal princ, File file) { + if (princ == null) { + throw new NullPointerException("princ must be non null"); + } + if (file == null) { + throw new NullPointerException("file must be non null"); + } + return new KeyTab(princ, file, true); + } + + /** + * Returns the default {@code KeyTab} instance that is bound + * to an unknown service principal. * <p> * 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. + * <p> + * Developers should call {@link #getInstance(KerberosPrincipal)} + * when the bound service principal is known. * @return the default keytab instance. */ public static KeyTab getInstance() { - return new KeyTab(null); + return new KeyTab(null, null, true); + } + + /** + * Returns the default unbound {@code KeyTab} instance. + * <p> + * 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 + * @since 1.8 + */ + public static KeyTab getUnboundInstance() { + return new KeyTab(null, null, false); + } + + /** + * Returns the default {@code KeyTab} instance that is bound + * to the specified service principal. + * <p> + * 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. + * @param princ the bound service principal, must not be null + * @return the default keytab instance + * @throws NullPointerException if {@code princ} is null + * @since 1.8 + */ + public static KeyTab getInstance(KerberosPrincipal princ) { + if (princ == null) { + throw new NullPointerException("princ must be non null"); + } + return new KeyTab(princ, null, true); } //Takes a snapshot of the keytab content - private sun.security.krb5.internal.ktab.KeyTab takeSnapshot() { + sun.security.krb5.internal.ktab.KeyTab takeSnapshot() { return sun.security.krb5.internal.ktab.KeyTab.getInstance(file); } @@ -147,6 +249,9 @@ public final class KeyTab { * <p> * Any unsupported key read from the keytab is ignored and not included * in the result. + * <p> + * If this keytab is bound to a specific principal, calling this method on + * another principal will return an empty array. * * @param principal the Kerberos principal, must not be null. * @return the keys (never null, may be empty) @@ -157,8 +262,11 @@ public final class KeyTab { */ public KerberosKey[] getKeys(KerberosPrincipal principal) { try { - EncryptionKey[] keys = takeSnapshot().readServiceKeys( - new PrincipalName(principal.getName())); + if (princ != null && !principal.equals(princ)) { + return new KerberosKey[0]; + } + PrincipalName pn = new PrincipalName(principal.getName()); + EncryptionKey[] keys = takeSnapshot().readServiceKeys(pn); KerberosKey[] kks = new KerberosKey[keys.length]; for (int i=0; i<kks.length; i++) { Integer tmp = keys[i].getKeyVersionNumber(); @@ -195,7 +303,10 @@ public final class KeyTab { } public String toString() { - return file == null ? "Default keytab" : file.toString(); + String s = (file == null) ? "Default keytab" : file.toString(); + if (!bound) return s; + else if (princ == null) return s + " for someone"; + else return s + " for " + princ; } /** @@ -204,7 +315,7 @@ public final class KeyTab { * @return a hashCode() for the <code>KeyTab</code> */ public int hashCode() { - return Objects.hash(file); + return Objects.hash(file, princ, bound); } /** @@ -225,6 +336,31 @@ public final class KeyTab { } KeyTab otherKtab = (KeyTab) other; - return Objects.equals(otherKtab.file, file); + return Objects.equals(otherKtab.princ, princ) && + Objects.equals(otherKtab.file, file) && + bound == otherKtab.bound; + } + + /** + * Returns the service principal this {@code KeyTab} object + * is bound to. Returns {@code null} if it's not bound. + * <p> + * Please note the deprecated constructors create a KeyTab object bound for + * some unknown principal. In this case, this method also returns null. + * User can call {@link #isBound()} to verify this case. + * @return the service principal + * @since 1.8 + */ + public KerberosPrincipal getPrincipal() { + return princ; + } + + /** + * Returns if the keytab is bound to a principal + * @return if the keytab is bound to a principal + * @since 1.8 + */ + public boolean isBound() { + return bound; } } diff --git a/src/share/classes/sun/security/jgss/LoginConfigImpl.java b/src/share/classes/sun/security/jgss/LoginConfigImpl.java index 41a783afef..52e2fa4728 100644 --- a/src/share/classes/sun/security/jgss/LoginConfigImpl.java +++ b/src/share/classes/sun/security/jgss/LoginConfigImpl.java @@ -175,6 +175,7 @@ public class LoginConfigImpl extends Configuration { options.put("useKeyTab", "true"); options.put("storeKey", "true"); options.put("doNotPrompt", "true"); + options.put("principal", "*"); options.put("isInitiator", "false"); } else { options.put("useTicketCache", "true"); diff --git a/src/share/classes/sun/security/jgss/krb5/Krb5Util.java b/src/share/classes/sun/security/jgss/krb5/Krb5Util.java index f0495eef87..0d5a314ffd 100644 --- a/src/share/classes/sun/security/jgss/krb5/Krb5Util.java +++ b/src/share/classes/sun/security/jgss/krb5/Krb5Util.java @@ -243,14 +243,24 @@ public class Krb5Util { } /** + * A helper method to get a sun..KeyTab from a javax..KeyTab + * @param ktab the javax..KeyTab object + * @return the sun..KeyTab object + */ + public static sun.security.krb5.internal.ktab.KeyTab + snapshotFromJavaxKeyTab(KeyTab ktab) { + return KerberosSecrets.getJavaxSecurityAuthKerberosAccess() + .keyTabTakeSnapshot(ktab); + } + + /** * A helper method to get EncryptionKeys from a javax..KeyTab - * @param ktab the javax..KeyTab class + * @param ktab the javax..KeyTab object * @param cname the PrincipalName * @return the EKeys, never null, might be empty */ public static EncryptionKey[] keysFromJavaxKeyTab( KeyTab ktab, PrincipalName cname) { - return KerberosSecrets.getJavaxSecurityAuthKerberosAccess(). - keyTabGetEncryptionKeys(ktab, cname); + return snapshotFromJavaxKeyTab(ktab).readServiceKeys(cname); } } diff --git a/src/share/classes/sun/security/jgss/krb5/ServiceCreds.java b/src/share/classes/sun/security/jgss/krb5/ServiceCreds.java index 21b8f57b22..824724bf34 100644 --- a/src/share/classes/sun/security/jgss/krb5/ServiceCreds.java +++ b/src/share/classes/sun/security/jgss/krb5/ServiceCreds.java @@ -101,9 +101,22 @@ public final class ServiceCreds { if (serverPrincipal != null) { // A named principal sc.kp = new KerberosPrincipal(serverPrincipal); } else { - if (sc.allPrincs.size() == 1) { // choose the only one - sc.kp = sc.allPrincs.iterator().next(); - serverPrincipal = sc.kp.getName(); + // For compatibility reason, we set the name of default principal + // to the "only possible" name it can take, which means there is + // only one KerberosPrincipal and there is no unbound keytabs + if (sc.allPrincs.size() == 1) { + boolean hasUnbound = false; + for (KeyTab ktab: SubjectComber.findMany( + subj, null, null, KeyTab.class)) { + if (!ktab.isBound()) { + hasUnbound = true; + break; + } + } + if (!hasUnbound) { + sc.kp = sc.allPrincs.iterator().next(); + serverPrincipal = sc.kp.getName(); + } } } @@ -131,20 +144,35 @@ public final class ServiceCreds { } /** - * Gets keys for someone unknown. - * Used by TLS or as a fallback in getEKeys(). Can still return an - * empty array. + * Gets keys for "someone". Used in 2 cases: + * 1. By TLS because it needs to get keys before client comes in. + * 2. As a fallback in getEKeys() below. + * This method can still return an empty array. */ public KerberosKey[] getKKeys() { if (destroyed) { throw new IllegalStateException("This object is destroyed"); } - if (kp != null) { - return getKKeys(kp); - } else if (!allPrincs.isEmpty()) { - return getKKeys(allPrincs.iterator().next()); + KerberosPrincipal one = kp; // named principal + if (one == null && !allPrincs.isEmpty()) { // or, a known principal + one = allPrincs.iterator().next(); + } + if (one == null) { // Or, some random one + for (KeyTab ktab: ktabs) { + // Must be unbound keytab, otherwise, allPrincs is not empty + PrincipalName pn = + Krb5Util.snapshotFromJavaxKeyTab(ktab).getOneName(); + if (pn != null) { + one = new KerberosPrincipal(pn.getName()); + break; + } + } + } + if (one != null) { + return getKKeys(one); + } else { + return new KerberosKey[0]; } - return new KerberosKey[0]; } /** @@ -152,15 +180,13 @@ public final class ServiceCreds { * @param princ the target name initiator requests. Not null. * @return keys for the princ, never null, might be empty */ - private KerberosKey[] getKKeys(KerberosPrincipal princ) { - ArrayList<KerberosKey> keys = new ArrayList<>(); - if (kp != null && !princ.equals(kp)) { - return new KerberosKey[0]; // Not me + public KerberosKey[] getKKeys(KerberosPrincipal princ) { + if (destroyed) { + throw new IllegalStateException("This object is destroyed"); } - if (!allPrincs.contains(princ)) { - return new KerberosKey[0]; // Not someone I know, This check - // is necessary but a KeyTab has - // no principal name recorded. + ArrayList<KerberosKey> keys = new ArrayList<>(); + if (kp != null && !princ.equals(kp)) { // named principal + return new KerberosKey[0]; } for (KerberosKey k: kk) { if (k.getPrincipal().equals(princ)) { @@ -168,6 +194,13 @@ public final class ServiceCreds { } } for (KeyTab ktab: ktabs) { + if (ktab.getPrincipal() == null && ktab.isBound()) { + // legacy bound keytab. although we don't know who + // the bound principal is, it must be in allPrincs + if (!allPrincs.contains(princ)) { + continue; // skip this legacy bound keytab + } + } for (KerberosKey k: ktab.getKeys(princ)) { keys.add(k); } @@ -186,12 +219,12 @@ public final class ServiceCreds { } KerberosKey[] kkeys = getKKeys(new KerberosPrincipal(princ.getName())); if (kkeys.length == 0) { - // Note: old JDK does not perform real name checking. If the - // acceptor starts by name A but initiator requests for B, - // as long as their keys match (i.e. A's keys can decrypt B's - // service ticket), the authentication is OK. There are real - // customers depending on this to use different names for a - // single service. + // Fallback: old JDK does not perform real name checking. If the + // acceptor has host.sun.com but initiator requests for host, + // as long as their keys match (i.e. keys for one can decrypt + // the other's service ticket), the authentication is OK. + // There are real customers depending on this to use different + // names for a single service. kkeys = getKKeys(); } EncryptionKey[] ekeys = new EncryptionKey[kkeys.length]; diff --git a/src/share/classes/sun/security/jgss/krb5/SubjectComber.java b/src/share/classes/sun/security/jgss/krb5/SubjectComber.java index d267dbd4b2..ad1723fe09 100644 --- a/src/share/classes/sun/security/jgss/krb5/SubjectComber.java +++ b/src/share/classes/sun/security/jgss/krb5/SubjectComber.java @@ -86,37 +86,40 @@ class SubjectComber { List<T> answer = (oneOnly ? null : new ArrayList<T>()); if (credClass == KeyTab.class) { - // TODO: There is currently no good way to filter out keytabs - // not for serverPrincipal. We can only check the principal - // set. If the server is not there, we can be sure none of the - // keytabs should be used, otherwise, use all for safety. - boolean useAll = false; - if (serverPrincipal != null) { - for (KerberosPrincipal princ: - subject.getPrincipals(KerberosPrincipal.class)) { - if (princ.getName().equals(serverPrincipal)) { - useAll = true; - break; - } - } - } else { - useAll = true; - } - if (useAll) { - Iterator<KeyTab> iterator = - subject.getPrivateCredentials(KeyTab.class).iterator(); - while (iterator.hasNext()) { - KeyTab t = iterator.next(); - if (DEBUG) { - System.out.println("Found " + credClass.getSimpleName() - + " " + t); - } - if (oneOnly) { - return t; + Iterator<KeyTab> iterator = + subject.getPrivateCredentials(KeyTab.class).iterator(); + while (iterator.hasNext()) { + KeyTab t = iterator.next(); + if (serverPrincipal != null && t.isBound()) { + KerberosPrincipal name = t.getPrincipal(); + if (name != null) { + if (!serverPrincipal.equals(name.getName())) { + continue; + } } else { - answer.add(credClass.cast(t)); + // legacy bound keytab. although we don't know who + // the bound principal is, it must be in allPrincs + boolean found = false; + for (KerberosPrincipal princ: + subject.getPrincipals(KerberosPrincipal.class)) { + if (princ.getName().equals(serverPrincipal)) { + found = true; + break; + } + } + if (!found) continue; } } + // Check passed, we can add now + if (DEBUG) { + System.out.println("Found " + credClass.getSimpleName() + + " " + t); + } + if (oneOnly) { + return t; + } else { + answer.add(credClass.cast(t)); + } } } else if (credClass == KerberosKey.class) { // We are looking for credentials for the serverPrincipal diff --git a/src/share/classes/sun/security/krb5/JavaxSecurityAuthKerberosAccess.java b/src/share/classes/sun/security/krb5/JavaxSecurityAuthKerberosAccess.java index 3992e48710..ae3dc3d0b0 100644 --- a/src/share/classes/sun/security/krb5/JavaxSecurityAuthKerberosAccess.java +++ b/src/share/classes/sun/security/krb5/JavaxSecurityAuthKerberosAccess.java @@ -35,9 +35,8 @@ import sun.security.krb5.PrincipalName; */ public interface JavaxSecurityAuthKerberosAccess { /** - * Returns keys for a principal in a keytab. - * @return the keys, never null, can be empty. + * Returns a snapshot to the backing keytab */ - public EncryptionKey[] keyTabGetEncryptionKeys( - KeyTab ktab, PrincipalName principal); + public sun.security.krb5.internal.ktab.KeyTab keyTabTakeSnapshot( + KeyTab ktab); } diff --git a/src/share/classes/sun/security/krb5/internal/ktab/KeyTab.java b/src/share/classes/sun/security/krb5/internal/ktab/KeyTab.java index d1023de9de..b556c90524 100644 --- a/src/share/classes/sun/security/krb5/internal/ktab/KeyTab.java +++ b/src/share/classes/sun/security/krb5/internal/ktab/KeyTab.java @@ -46,6 +46,7 @@ import java.util.HashMap; import java.util.Map; import java.util.StringTokenizer; import java.util.Vector; +import sun.security.jgss.krb5.ServiceCreds; /** * This class represents key table. The key table functions deal with storing @@ -268,6 +269,15 @@ public class KeyTab implements KeyTabConstants { } /** + * Returns a principal name in this keytab. Used by + * {@link ServiceCreds#getKKeys()}. + */ + public PrincipalName getOneName() { + int size = entries.size(); + return size > 0 ? entries.elementAt(size-1).service : null; + } + + /** * Reads all keys for a service from the keytab file that have * etypes that have been configured for use. If there are multiple * keys with same etype, the one with the highest kvno is returned. diff --git a/src/share/classes/sun/security/provider/ConfigSpiFile.java b/src/share/classes/sun/security/provider/ConfigSpiFile.java index 3a9ca47677..03cdc5712d 100644 --- a/src/share/classes/sun/security/provider/ConfigSpiFile.java +++ b/src/share/classes/sun/security/provider/ConfigSpiFile.java @@ -404,6 +404,7 @@ public final class ConfigSpiFile extends ConfigurationSpi { st.wordChars('$', '$'); st.wordChars('_', '_'); st.wordChars('-', '-'); + st.wordChars('*', '*'); st.lowerCaseMode(false); st.slashSlashComments(true); st.slashStarComments(true); diff --git a/test/sun/security/krb5/ServiceCredsCombination.java b/test/sun/security/krb5/ServiceCredsCombination.java index 3208560e78..5c6ac4b189 100644 --- a/test/sun/security/krb5/ServiceCredsCombination.java +++ b/test/sun/security/krb5/ServiceCredsCombination.java @@ -62,11 +62,38 @@ public class ServiceCredsCombination { check("b", "b", princ("a"), princ("b"), oldktab(), oldktab()); check(null, null, princ("a"), princ("b"), oldktab(), oldktab()); check("x", "NOCRED", princ("a"), princ("b"), oldktab(), oldktab()); + // bound ktab + check("c", "c", princ("c"), ktab("c")); + check(null, "c", princ("c"), ktab("c")); + // unbound ktab + check("x", "x", ktab()); + check(null, null, ktab()); + // Two bound ktab + check("c1", "c1", princ("c1"), princ("c2"), ktab("c1"), ktab("c2")); + check("c2", "c2", princ("c1"), princ("c2"), ktab("c1"), ktab("c2")); + check("x", "NOCRED", princ("c1"), princ("c2"), ktab("c1"), ktab("c2")); + check(null, null, princ("c1"), princ("c2"), ktab("c1"), ktab("c2")); + // One bound, one unbound + check("c1", "c1", princ("c1"), ktab("c1"), ktab()); + check("x", "x", princ("c1"), ktab("c1"), ktab()); + check(null, null, princ("c1"), ktab("c1"), ktab()); + // Two unbound ktab + check("x", "x", ktab(), ktab()); + check(null, null, ktab(), ktab()); // pass + old ktab check("a", "a", princ("a"), princ("b"), key("a"), oldktab()); check("b", "b", princ("a"), princ("b"), key("a"), oldktab()); check(null, null, princ("a"), princ("b"), key("a"), oldktab()); check("x", "NOCRED", princ("a"), princ("b"), key("a"), oldktab()); + // pass + bound ktab + check("a", "a", princ("a"), princ("c"), key("a"), ktab("c")); + check("c", "c", princ("a"), princ("c"), key("a"), ktab("c")); + check("x", "NOCRED", princ("a"), princ("c"), key("a"), ktab("c")); + check(null, null, princ("a"), princ("c"), key("a"), ktab("c")); + // pass + unbound ktab + check("a", "a", princ("a"), key("a"), ktab()); + check("x", "x", princ("a"), key("a"), ktab()); + check(null, null, princ("a"), key("a"), ktab()); // Compatibility, automatically add princ for keys check(null, "a", key("a")); check("x", "NOCRED", key("a")); @@ -130,4 +157,10 @@ public class ServiceCredsCombination { private static KeyTab oldktab() { return KeyTab.getInstance(); } + static KeyTab ktab(String s) { + return KeyTab.getInstance(princ(s)); + } + static KeyTab ktab() { + return KeyTab.getUnboundInstance(); + } } diff --git a/test/sun/security/krb5/auto/AcceptPermissions.java b/test/sun/security/krb5/auto/AcceptPermissions.java index 3a6d422842..1b562eaf99 100644 --- a/test/sun/security/krb5/auto/AcceptPermissions.java +++ b/test/sun/security/krb5/auto/AcceptPermissions.java @@ -26,7 +26,8 @@ * @bug 9999999 * @summary default principal can act as anyone * @compile -XDignore.symbol.file AcceptPermissions.java - * @run main/othervm AcceptPermissions + * @run main/othervm AcceptPermissions two + * @run main/othervm AcceptPermissions unbound */ import java.nio.file.Files; @@ -83,15 +84,20 @@ public class AcceptPermissions extends SecurityManager { public static void main(String[] args) throws Exception { System.setSecurityManager(new AcceptPermissions()); new OneKDC(null).writeJAASConf(); - String two = "two {\n" + String moreEntries = "two {\n" + " com.sun.security.auth.module.Krb5LoginModule required" + " principal=\"" + OneKDC.SERVER + "\" useKeyTab=true" + " isInitiator=false storeKey=true;\n" + " com.sun.security.auth.module.Krb5LoginModule required" + " principal=\"" + OneKDC.BACKEND + "\" useKeyTab=true" + " isInitiator=false storeKey=true;\n" + + "};\n" + + "unbound {" + + " com.sun.security.auth.module.Krb5LoginModule required" + + " principal=* useKeyTab=true" + + " isInitiator=false storeKey=true;\n" + "};\n"; - Files.write(Paths.get(OneKDC.JAAS_CONF), two.getBytes(), + Files.write(Paths.get(OneKDC.JAAS_CONF), moreEntries.getBytes(), StandardOpenOption.APPEND); Context c, s; @@ -114,7 +120,7 @@ public class AcceptPermissions extends SecurityManager { // Named principal (even if there are 2 JAAS modules) initPerms(OneKDC.SERVER); c = Context.fromJAAS("client"); - s = Context.fromJAAS("two"); + s = Context.fromJAAS(args[0]); c.startAsClient(OneKDC.SERVER, GSSUtil.GSS_KRB5_MECH_OID); s.startAsServer(OneKDC.SERVER, GSSUtil.GSS_KRB5_MECH_OID); checkPerms(); @@ -136,7 +142,7 @@ public class AcceptPermissions extends SecurityManager { // Default principal with no predictable name initPerms(); // permission not needed for cred !!! c = Context.fromJAAS("client"); - s = Context.fromJAAS("two"); + s = Context.fromJAAS(args[0]); c.startAsClient(OneKDC.SERVER, GSSUtil.GSS_KRB5_MECH_OID); s.startAsServer(GSSUtil.GSS_KRB5_MECH_OID); checkPerms(); diff --git a/test/sun/security/krb5/auto/GSSUnbound.java b/test/sun/security/krb5/auto/GSSUnbound.java new file mode 100644 index 0000000000..a1022d4f8c --- /dev/null +++ b/test/sun/security/krb5/auto/GSSUnbound.java @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2012, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @bug 8001104 + * @summary Unbound SASL service: the GSSAPI/krb5 mech + * @compile -XDignore.symbol.file GSSUnbound.java + * @run main/othervm GSSUnbound + */ + +import java.security.Security; +import sun.security.jgss.GSSUtil; + +// Testing JGSS without JAAS +public class GSSUnbound { + + public static void main(String[] args) throws Exception { + + new OneKDC(null); + + Context c, s; + c = Context.fromUserPass(OneKDC.USER, OneKDC.PASS, false); + s = Context.fromThinAir(); + + // This is the only setting needed for JGSS without JAAS. The default + // JAAS config entries are already created by OneKDC. + System.setProperty("javax.security.auth.useSubjectCredsOnly", "false"); + + c.startAsClient(OneKDC.BACKEND, GSSUtil.GSS_KRB5_MECH_OID); + s.startAsServer(GSSUtil.GSS_KRB5_MECH_OID); + + Context.handshake(c, s); + + Context.transmit("i say high --", c, s); + Context.transmit(" you say low", s, c); + + s.dispose(); + c.dispose(); + } +} diff --git a/test/sun/security/krb5/auto/OneKDC.java b/test/sun/security/krb5/auto/OneKDC.java index 5c87abb961..90a7e8e874 100644 --- a/test/sun/security/krb5/auto/OneKDC.java +++ b/test/sun/security/krb5/auto/OneKDC.java @@ -76,6 +76,8 @@ public class OneKDC extends KDC { Config.refresh(); writeKtab(KTAB); + Security.setProperty("auth.login.defaultCallbackHandler", + "OneKDC$CallbackForClient"); } /** @@ -93,7 +95,7 @@ public class OneKDC extends KDC { " com.sun.security.auth.module.Krb5LoginModule required;\n};\n" + "com.sun.security.jgss.krb5.accept {\n" + " com.sun.security.auth.module.Krb5LoginModule required\n" + - " principal=\"" + SERVER + "\"\n" + + " principal=\"*\"\n" + " useKeyTab=true\n" + " isInitiator=false\n" + " storeKey=true;\n};\n" + @@ -112,7 +114,6 @@ public class OneKDC extends KDC { " isInitiator=false;\n};\n" ).getBytes()); fos.close(); - Security.setProperty("auth.login.defaultCallbackHandler", "OneKDC$CallbackForClient"); } /** diff --git a/test/sun/security/krb5/auto/SaslUnbound.java b/test/sun/security/krb5/auto/SaslUnbound.java new file mode 100644 index 0000000000..64d9a1b3c6 --- /dev/null +++ b/test/sun/security/krb5/auto/SaslUnbound.java @@ -0,0 +1,113 @@ +/* + * Copyright (c) 2012, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @bug 8001104 + * @summary Unbound SASL service: the GSSAPI/krb5 mech + * @compile -XDignore.symbol.file SaslUnbound.java + * @run main/othervm SaslUnbound 0 + * @run main/othervm/fail SaslUnbound 1 + * @run main/othervm/fail SaslUnbound 2 + * @run main/othervm/fail SaslUnbound 3 + * @run main/othervm/fail SaslUnbound 4 + */ +import java.io.IOException; +import java.util.Arrays; +import java.util.HashMap; +import javax.security.auth.callback.Callback; +import javax.security.auth.callback.CallbackHandler; +import javax.security.auth.callback.UnsupportedCallbackException; +import javax.security.sasl.*; + +public class SaslUnbound { + + public static void main(String[] args) throws Exception { + + String serverProtocol, serverName; + switch (args[0].charAt(0)) { + case '1': // Using another protocol, should fail + serverProtocol = "serv"; + serverName = null; + break; + case '2': // Using another protocol, should fail + serverProtocol = "otherwise"; + serverName = null; + break; + case '3': // Using another protocol, should fail + serverProtocol = "otherwise"; + serverName = "host." + OneKDC.REALM; + break; + case '4': // Bound to another serverName, should fail. + serverProtocol = "server"; + serverName = "host2." + OneKDC.REALM; + break; + default: // Good unbound server + serverProtocol = "server"; + serverName = null; + break; + } + new OneKDC(null).writeJAASConf(); + System.setProperty("javax.security.auth.useSubjectCredsOnly", "false"); + + HashMap clntprops = new HashMap(); + clntprops.put(Sasl.QOP, "auth-conf"); + SaslClient sc = Sasl.createSaslClient( + new String[]{"GSSAPI"}, null, "server", + "host." + OneKDC.REALM, clntprops, null); + + final HashMap srvprops = new HashMap(); + srvprops.put(Sasl.QOP, "auth,auth-int,auth-conf"); + SaslServer ss = Sasl.createSaslServer("GSSAPI", serverProtocol, + serverName, srvprops, + new CallbackHandler() { + public void handle(Callback[] callbacks) + throws IOException, UnsupportedCallbackException { + for (Callback cb : callbacks) { + if (cb instanceof RealmCallback) { + ((RealmCallback) cb).setText(OneKDC.REALM); + } else if (cb instanceof AuthorizeCallback) { + ((AuthorizeCallback) cb).setAuthorized(true); + } + } + } + }); + + byte[] token = new byte[0]; + while (!sc.isComplete() || !ss.isComplete()) { + if (!sc.isComplete()) { + token = sc.evaluateChallenge(token); + } + if (!ss.isComplete()) { + token = ss.evaluateResponse(token); + } + } + System.out.println(ss.getNegotiatedProperty(Sasl.BOUND_SERVER_NAME)); + byte[] hello = "hello".getBytes(); + token = sc.wrap(hello, 0, hello.length); + token = ss.unwrap(token, 0, token.length); + if (!Arrays.equals(hello, token)) { + throw new Exception("Message altered"); + } + } +} diff --git a/test/sun/security/krb5/auto/UnboundService.java b/test/sun/security/krb5/auto/UnboundService.java new file mode 100644 index 0000000000..c6d093c926 --- /dev/null +++ b/test/sun/security/krb5/auto/UnboundService.java @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2012, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @bug 8001104 + * @summary Unbound SASL service: the GSSAPI/krb5 mech + * @compile -XDignore.symbol.file UnboundService.java + * @run main/othervm UnboundService null null + * @run main/othervm UnboundService server/host.rabbit.hole null + * @run main/othervm UnboundService server/host.rabbit.hole@RABBIT.HOLE null + * @run main/othervm/fail UnboundService backend/host.rabbit.hole null + * @run main/othervm UnboundService null server@host.rabbit.hole + * @run main/othervm UnboundService server/host.rabbit.hole server@host.rabbit.hole + * @run main/othervm UnboundService server/host.rabbit.hole@RABBIT.HOLE server@host.rabbit.hole + * @run main/othervm/fail UnboundService backend/host.rabbit.hole server@host.rabbit.hole + */ + +import java.io.File; +import java.io.FileOutputStream; +import sun.security.jgss.GSSUtil; + +public class UnboundService { + + /** + * @param args JAAS config pricipal and GSSCredential creation name + */ + public static void main(String[] args) throws Exception { + + String principal = args[0]; + if (principal.equals("null")) principal = null; + + String server = args[1]; + if (server.equals("null")) server = null; + + new OneKDC(null).writeJAASConf(); + File f = new File(OneKDC.JAAS_CONF); + try (FileOutputStream fos = new FileOutputStream(f)) { + fos.write(( + "client {\n" + + " com.sun.security.auth.module.Krb5LoginModule required;\n};\n" + + "unbound {\n" + + " com.sun.security.auth.module.Krb5LoginModule required\n" + + " useKeyTab=true\n" + + " principal=" + + (principal==null? "*" :("\"" + principal + "\"")) + "\n" + + " doNotPrompt=true\n" + + " isInitiator=false\n" + + " storeKey=true;\n};\n" + ).getBytes()); + } + + Context c, s; + c = Context.fromJAAS("client"); + s = Context.fromJAAS("unbound"); + + c.startAsClient(OneKDC.SERVER, GSSUtil.GSS_KRB5_MECH_OID); + s.startAsServer(server, GSSUtil.GSS_KRB5_MECH_OID); + + Context.handshake(c, s); + + s.dispose(); + c.dispose(); + } +} |