From d8233ec65720cf0bb8536853588cc967c4dcca28 Mon Sep 17 00:00:00 2001 From: Weijun Wang Date: Sat, 9 Feb 2013 16:43:49 +0800 Subject: [PATCH] 8001104: Unbound SASL service: the GSSAPI/krb5 mech Reviewed-by: valeriep --- .../security/auth/module/Krb5LoginModule.java | 118 ++++++++----- .../JavaxSecurityAuthKerberosAccessImpl.java | 6 +- .../javax/security/auth/kerberos/KeyTab.java | 160 ++++++++++++++++-- .../sun/security/jgss/LoginConfigImpl.java | 1 + .../sun/security/jgss/krb5/Krb5Util.java | 16 +- .../sun/security/jgss/krb5/ServiceCreds.java | 83 ++++++--- .../sun/security/jgss/krb5/SubjectComber.java | 59 ++++--- .../krb5/JavaxSecurityAuthKerberosAccess.java | 7 +- .../security/krb5/internal/ktab/KeyTab.java | 10 ++ .../sun/security/provider/ConfigSpiFile.java | 1 + .../krb5/ServiceCredsCombination.java | 33 ++++ .../security/krb5/auto/AcceptPermissions.java | 16 +- .../sun/security/krb5/auto/GSSUnbound.java | 61 +++++++ jdk/test/sun/security/krb5/auto/OneKDC.java | 5 +- .../sun/security/krb5/auto/SaslUnbound.java | 113 +++++++++++++ .../security/krb5/auto/UnboundService.java | 85 ++++++++++ 16 files changed, 652 insertions(+), 122 deletions(-) create mode 100644 jdk/test/sun/security/krb5/auto/GSSUnbound.java create mode 100644 jdk/test/sun/security/krb5/auto/SaslUnbound.java create mode 100644 jdk/test/sun/security/krb5/auto/UnboundService.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 3d1744ba172..719aeee76d4 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 @@ -52,16 +52,19 @@ import sun.misc.HexDumpEncoder; * principal set and private credentials set are updated only when * commit is called. * When commit is called, the KerberosPrincipal - * is added to the Subject's - * principal set and KerberosTicket is + * is added to the Subject's principal set (unless the + * principal is specified as "*"). If isInitiator + * is true, the KerberosTicket is * added to the Subject's private credentials. * *

If the configuration entry for KerberosLoginModule * has the option storeKey set to true, then - * KerberosKey will also be added to the + * KerberosKey or KeyTab will also be added to the * subject's private credentials. KerberosKey, 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 KeyTab is + * the keytab used when useKeyTab is set to true. The + * KeyTab object is restricted to be used by the specified + * principal unless the principal value is "*". * *

This LoginModule recognizes the doNotPrompt * option. If set to true the user will not be prompted for the password. @@ -75,8 +78,8 @@ import sun.misc.HexDumpEncoder; * *

The principal name can be specified in the configuration entry * by using the option principal. The principal name - * can either be a simple user name or a service name such as - * host/mission.eng.sun.com. The principal can also + * can either be a simple user name, a service name such as + * host/mission.eng.sun.com, or "*". The principal can also * be set using the system property sun.security.krb5.principal. * 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; * *

The following is a list of configuration options supported * for Krb5LoginModule: - *

- *
refreshKrb5Config:
+ *
+ *
refreshKrb5Config:
*
Set this to true, if you want the configuration * to be refreshed before the login method is called.
- *

*

useTicketCache:
*
Set this to true, if you want the * TGT to be obtained @@ -112,19 +114,16 @@ import sun.misc.HexDumpEncoder; * ticketCache. * 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. - *

*

ticketCache:
*
Set this to the name of the ticket * cache that contains user's TGT. * If this is set, useTicketCache * must also be set to true; Otherwise a configuration error will * be returned.
- *

*

renewTGT:
*
Set this to true, if you want to renew * the TGT. If this is set, useTicketCache must also be * set to true; otherwise a configuration error will be returned.
- *

*

doNotPrompt:
*
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.
- *

*

useKeyTab:
*
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 * {user.home}{file.separator}krb5.keytab.
- *

*

keyTab:
*
Set this to the file name of the * keytab to get principal's secret key.
- *

*

storeKey:
- *
Set this to true to if you want the - * principal's key to be stored in the Subject's private credentials.
- *

+ *

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 isInitiator being false, if principal + * is "*", the {@link KeyTab} stored can be used by anyone, otherwise, + * it's restricted to be used by the specified principal only.
*
principal:
*
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; * sun.security.krb5.principal. 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.
- *

+ * used. + * The principal name can be set to "*" when isInitiator 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 isInitiator is true, the principal name + * cannot be set to "*". + * *

isInitiator:
*
Set this to true, if initiator. Set this to false, if acceptor only. * (Default is true). @@ -177,18 +180,20 @@ import sun.misc.HexDumpEncoder; * Configuration * options that enable you to share username and passwords across different * authentication modules: - *
+ * 
* - * useFirstPass if, true, this LoginModule retrieves the + *
useFirstPass:
+ *
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.
* - * tryFirstPass if, true, this LoginModule retrieves the + *
tryFirstPass:
+ *
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
* - * storePass if, true, this LoginModule stores the username and + *
storePass:
+ *
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.
* - * clearPass if, true, this LoginModule clears the + *
clearPass:
+ *
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. - *
+ * (login and commit) have completed.
+ *
*

If the principal system property or key is already provided, the value of * "javax.security.auth.login.name" in the shared state is ignored. *

When multiple mechanisms to retrieve a ticket or key is provided, the - * preference order looks like this: + * preference order is: *

    *
  1. ticket cache *
  2. keytab @@ -225,7 +232,7 @@ import sun.misc.HexDumpEncoder; *
  3. user prompt *
*

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 * useFirstPass=true, no user prompt is made. *

Examples of some configuration values for Krb5LoginModule in * JAAS config file and the results are: @@ -318,7 +325,7 @@ import sun.misc.HexDumpEncoder; *

useKeyTab = true * keyTab=<keytabname> * storeKey=true - * doNotPrompt=true; + * doNotPrompt=false; * *

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. + *

    + *

    isInitiator = false useKeyTab = true + * keyTab=<keytabname> + * storeKey=true + * principal=*; + *

+ *

The acceptor will be an unbound acceptor and it can act as any principal + * as long that principal has keys in the keytab. *

    *

    * useTicketCache=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/jdk/src/share/classes/javax/security/auth/kerberos/JavaxSecurityAuthKerberosAccessImpl.java b/jdk/src/share/classes/javax/security/auth/kerberos/JavaxSecurityAuthKerberosAccessImpl.java index 4b20626ec75..fbf74718933 100644 --- a/jdk/src/share/classes/javax/security/auth/kerberos/JavaxSecurityAuthKerberosAccessImpl.java +++ b/jdk/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/jdk/src/share/classes/javax/security/auth/kerberos/KeyTab.java b/jdk/src/share/classes/javax/security/auth/kerberos/KeyTab.java index 3ebce9975a2..32f644b5906 100644 --- a/jdk/src/share/classes/javax/security/auth/kerberos/KeyTab.java +++ b/jdk/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. *

    + * 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. + *

    + * 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. + *

    * 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 * * http://www.ioplex.com/utilities/keytab.txt. - * + *

    * @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. *

    * 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. + *

    + * 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. + *

    + * 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. + *

    + * 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. *

    * 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. + *

    + * 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. + *

    + * 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. + *

    + * 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 { *

    * Any unsupported key read from the keytab is ignored and not included * in the result. + *

    + * 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; iKeyTab */ 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. + *

    + * 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/jdk/src/share/classes/sun/security/jgss/LoginConfigImpl.java b/jdk/src/share/classes/sun/security/jgss/LoginConfigImpl.java index 41a783afef3..52e2fa4728d 100644 --- a/jdk/src/share/classes/sun/security/jgss/LoginConfigImpl.java +++ b/jdk/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/jdk/src/share/classes/sun/security/jgss/krb5/Krb5Util.java b/jdk/src/share/classes/sun/security/jgss/krb5/Krb5Util.java index f0495eef872..0d5a314ffd7 100644 --- a/jdk/src/share/classes/sun/security/jgss/krb5/Krb5Util.java +++ b/jdk/src/share/classes/sun/security/jgss/krb5/Krb5Util.java @@ -242,15 +242,25 @@ public class Krb5Util { kerbTicket.getClientAddresses()); } + /** + * 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/jdk/src/share/classes/sun/security/jgss/krb5/ServiceCreds.java b/jdk/src/share/classes/sun/security/jgss/krb5/ServiceCreds.java index 21b8f57b229..824724bf34e 100644 --- a/jdk/src/share/classes/sun/security/jgss/krb5/ServiceCreds.java +++ b/jdk/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 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 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/jdk/src/share/classes/sun/security/jgss/krb5/SubjectComber.java b/jdk/src/share/classes/sun/security/jgss/krb5/SubjectComber.java index d267dbd4b21..ad1723fe09e 100644 --- a/jdk/src/share/classes/sun/security/jgss/krb5/SubjectComber.java +++ b/jdk/src/share/classes/sun/security/jgss/krb5/SubjectComber.java @@ -86,36 +86,39 @@ class SubjectComber { List answer = (oneOnly ? null : new ArrayList()); 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; + Iterator 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 { + // 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; } } - } else { - useAll = true; - } - if (useAll) { - Iterator 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; - } else { - answer.add(credClass.cast(t)); - } + // 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) { diff --git a/jdk/src/share/classes/sun/security/krb5/JavaxSecurityAuthKerberosAccess.java b/jdk/src/share/classes/sun/security/krb5/JavaxSecurityAuthKerberosAccess.java index 3992e487108..ae3dc3d0b04 100644 --- a/jdk/src/share/classes/sun/security/krb5/JavaxSecurityAuthKerberosAccess.java +++ b/jdk/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/jdk/src/share/classes/sun/security/krb5/internal/ktab/KeyTab.java b/jdk/src/share/classes/sun/security/krb5/internal/ktab/KeyTab.java index d1023de9de3..b556c90524c 100644 --- a/jdk/src/share/classes/sun/security/krb5/internal/ktab/KeyTab.java +++ b/jdk/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 @@ -267,6 +268,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 diff --git a/jdk/src/share/classes/sun/security/provider/ConfigSpiFile.java b/jdk/src/share/classes/sun/security/provider/ConfigSpiFile.java index 3a9ca47677a..03cdc5712d0 100644 --- a/jdk/src/share/classes/sun/security/provider/ConfigSpiFile.java +++ b/jdk/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/jdk/test/sun/security/krb5/ServiceCredsCombination.java b/jdk/test/sun/security/krb5/ServiceCredsCombination.java index 3208560e788..5c6ac4b1894 100644 --- a/jdk/test/sun/security/krb5/ServiceCredsCombination.java +++ b/jdk/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/jdk/test/sun/security/krb5/auto/AcceptPermissions.java b/jdk/test/sun/security/krb5/auto/AcceptPermissions.java index 3a6d422842f..1b562eaf99a 100644 --- a/jdk/test/sun/security/krb5/auto/AcceptPermissions.java +++ b/jdk/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/jdk/test/sun/security/krb5/auto/GSSUnbound.java b/jdk/test/sun/security/krb5/auto/GSSUnbound.java new file mode 100644 index 00000000000..a1022d4f8c8 --- /dev/null +++ b/jdk/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/jdk/test/sun/security/krb5/auto/OneKDC.java b/jdk/test/sun/security/krb5/auto/OneKDC.java index 5c87abb961a..90a7e8e8748 100644 --- a/jdk/test/sun/security/krb5/auto/OneKDC.java +++ b/jdk/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/jdk/test/sun/security/krb5/auto/SaslUnbound.java b/jdk/test/sun/security/krb5/auto/SaslUnbound.java new file mode 100644 index 00000000000..64d9a1b3c6e --- /dev/null +++ b/jdk/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/jdk/test/sun/security/krb5/auto/UnboundService.java b/jdk/test/sun/security/krb5/auto/UnboundService.java new file mode 100644 index 00000000000..c6d093c9266 --- /dev/null +++ b/jdk/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(); + } +}