code review request: 7089889: Krb5LoginModule.login() throws an exception if used without a keytab

Weijun Wang weijun.wang at oracle.com
Tue Sep 20 21:26:01 PDT 2011



On 09/21/2011 11:52 AM, Xuelei Fan wrote:
> On 9/20/2011 1:38 PM, Weijun Wang wrote:
>> Any more comment?
>>
>> Thanks
>> Max
>>
>> On 09/16/2011 01:57 PM, Weijun Wang wrote:
>>>
>>>
>>> On 09/16/2011 01:45 PM, Xuelei Fan wrote:
>>>> My first impression about the fix:
>>>> 1. Do you want to update getKeys() comments about isInitiator value?
>>>
>>> OK. I'll simply remove the "(in JAAS words, isInitiator=true and
>>> storeKey=true)" line.
>>>
> Do you also need to remove "This is used when the client supplies
> passowrd but need ...". I think it is the same as "in JAAS words, ...".

But that's where it's called. Maybe I should say more:

    This is used when the client supplies password but need keys
    to act as an acceptor. It can be called when the state is INIT
    and returned keys are generated with default salt, or, after
    AS-REQ is performed, might be updated by KDC.

>
>>>>
>>>> 2. It seems that when isInitiator is true, getKeys() should throw
>>>> exception if the state is not OK, right? If so, it looks like that the
>>>> update of getKeys() will not throw ISE if the state is INIT. Does INIT
>>>> state is enough in such case?
>>>
>>> The KrbAsReqBuilder class does not know about isInitiator so it cannot
>>> make any decision based on it. I'll leave that to its caller. When
>>> username/password is provided, getKeys() can be called as soon as the
>>> object is created, and it returns keys with default salt. If AS-REQ is
>>> performed and state goes OK and the method is called again, the salt
>>> might be updated in PA-DATA.
>>>
> I'm not sure whether the getKeys() return the expected keys when the
> state is INIT. It should be OK as it is an internal class, but as
> requires that the caller always looks like:
>
>      if (isInitiator) {
>         // action to update the state to OK
>      }
>      encKeys = builder.getKeys();

Correct. In Krb5LoginModule, you can see

     if (isInitiator) {
         // XXX Even if isInitiator=false, it might be
         // better to do an AS-REQ so that keys can be
         // updated with PA info
         cred = builder.action().getCreds();
     }
     if (storeKey) {
         encKeys = builder.getKeys();
         // When encKeys is empty, the login actually fails.
         // For compatibility, exception is thrown in commit().
     }

This is the only place where this method is called.

>
> Is getKeys() only be called by Krb5LoginModule.java? If not, I think we
> may need to rethink about the change of the behaviors. Otherwise, it is
> OK. However, is it a little simpler to update builder.getKeys() to
> builder.getKeys(boolean isInitiator)?

Yes. But I won't add automatic AS-REQ inside it. The argument will only 
be used for state check. Is that OK?

Thanks
Max


>
> Xuelei
>
>>> Thanks
>>> Max
>>>
>>>
>>>>
>>>> Xuelei
>>>>
>>>> On Sep 16, 2011, at 12:00 PM, Weijun Wang<weijun.wang at oracle.com>  wrote:
>>>>
>>>>> Hi All
>>>>>
>>>>> Code changes at
>>>>>
>>>>> http://cr.openjdk.java.net/~weijun/7089889/webrev.00/
>>>>>
>>>>> KrbAsReqBuilder maintains a state enforcement: at the beginning it's
>>>>> INIT, after doing an AS-REQ it's OK, and we used to only allow
>>>>> getKeys() to be called when the state is OK. Now if an acceptor has
>>>>> isInitiator=false, the OK state will never be reached. In most cases,
>>>>> an acceptor uses a keytab so getKeys() is not called. However, if it
>>>>> uses username/password (most likely in a peer-to-peer case and the
>>>>> acceptor chooses not to login), getKeys() is needed and an exception
>>>>> is thrown.
>>>>>
>>>>> This code change makes getKeys() callable at both INIT and OK states.
>>>>> In order to do this, the checkState() method now accepts varargs.
>>>>>
>>>>> I'll backport this to 7u2, and since 7u2 is now in phase 2, I think I
>>>>> need at least 2 reviewers.
>>>>>
>>>>> Thanks
>>>>> Max
>>>>>
>>>>> -------- Original Message --------
>>>>>
>>>>> *Change Request ID*: 7089889
>>>>>
>>>>> *Synopsis*: Krb5LoginModule.login() throws an exception if used
>>>>> without a keytab
>>>>>
>>>>>
>>>>> === *Description*
>>>>> ============================================================
>>>>> FULL PRODUCT VERSION :
>>>>> java version "1.7.0"
>>>>> Java(TM) SE Runtime Environment (build 1.7.0-b147)
>>>>> Java HotSpot(TM) Client VM (build 21.0-b17, mixed mode, sharing)
>>>>>
>>>>> A DESCRIPTION OF THE PROBLEM :
>>>>> The call to LoginContext.login() fails with an exception if the
>>>>> LoginContext is configured to use Krb5LoginModule with the following
>>>>> options:
>>>>>
>>>>> storeKey="true"
>>>>> useKeyTab="false"
>>>>> isInitiator="false"
>>>>>
>>>>> This appears to be a regression due to an attempt to solve bug
>>>>> '6941083'.
>>>>>
>>>>> The following source code is taken from
>>>>> Kb5LoginModule.attemptAuthentication(false):
>>>>>
>>>>> if (ktab == null) {
>>>>> promptForPass(getPasswdFromSharedState);
>>>>> builder = new KrbAsReqBuilder(principal, password);
>>>>> if (isInitiator) {
>>>>> // XXX Even if isInitiator=false, it might be
>>>>> // better to do an AS-REQ so that keys can be
>>>>> // updated with PA info
>>>>> cred = builder.action().getCreds();
>>>>> }
>>>>> if (storeKey) {
>>>>> encKeys = builder.getKeys();
>>>>> // When encKeys is empty, the login actually fails.
>>>>> // For compatibility, exception is thrown in commit().
>>>>> }
>>>>> }
>>>>>
>>>>> This code path results builder.getGeys() being called while the
>>>>> builder's state is 'INIT'.
>>>>> The builder asserts via checkState() that the state is REQ_OK, hence
>>>>> an exception.
>>>>>
>>>>> This is a regression, as JRE6 and prior versions called
>>>>> EncryptionKey.acquireSecretKeys()
>>>>> to obtain the keys in this case.
>>>>>
>>>>> REGRESSION. Last worked in version 6u26
>>>>>
>>>>> STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
>>>>> See the executable test case below. Program should be run on a
>>>>> Windows machine that is joined to a domain. Replace 'REALM', 'KDC',
>>>>> 'DOMAIN_USER' and 'DOMAIN_USER_PWD' as appropriate before running
>>>>>
>>>>> EXPECTED VERSUS ACTUAL BEHAVIOR :
>>>>> EXPECTED -
>>>>> "Login succeeded" printed to console
>>>>> ACTUAL -
>>>>> "Login failed" printed to console.
>>>>> Exception stacktrace printed to strerr
>>>>>
>>>>> ERROR MESSAGES/STACK TRACES THAT OCCUR :
>>>>> javax.security.auth.login.LoginException:
>>>>> java.lang.IllegalStateException: Cannot get keys at REQ_OK state
>>>>> at sun.security.krb5.KrbAsReqBuilder.checkState(Unknown Source)
>>>>> at sun.security.krb5.KrbAsReqBuilder.getKeys(Unknown Source)
>>>>> at
>>>>> com.sun.security.auth.module.Krb5LoginModule.attemptAuthentication(Unknown
>>>>>
>>>>> Source)
>>>>> at com.sun.security.auth.module.Krb5LoginModule.login(Unknown Source)
>>>>> at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
>>>>> at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
>>>>> at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
>>>>> at java.lang.reflect.Method.invoke(Unknown Source)
>>>>> at javax.security.auth.login.LoginContext.invoke(Unknown Source)
>>>>> at javax.security.auth.login.LoginContext.access$000(Unknown Source)
>>>>> at javax.security.auth.login.LoginContext$4.run(Unknown Source)
>>>>> at javax.security.auth.login.LoginContext$4.run(Unknown Source)
>>>>> at java.security.AccessController.doPrivileged(Native Method)
>>>>> at javax.security.auth.login.LoginContext.invokePriv(Unknown Source)
>>>>> at javax.security.auth.login.LoginContext.login(Unknown Source)
>>>>> at LoginModuleExample.main(LoginModuleExample.java:43)
>>>>>
>>>>> at javax.security.auth.login.LoginContext.invoke(Unknown Source)
>>>>> at javax.security.auth.login.LoginContext.access$000(Unknown Source)
>>>>> at javax.security.auth.login.LoginContext$4.run(Unknown Source)
>>>>> at javax.security.auth.login.LoginContext$4.run(Unknown Source)
>>>>> at java.security.AccessController.doPrivileged(Native Method)
>>>>> at javax.security.auth.login.LoginContext.invokePriv(Unknown Source)
>>>>> at javax.security.auth.login.LoginContext.login(Unknown Source)
>>>>> at LoginModuleExample.main(LoginModuleExample.java:43)
>>>>>
>>>>> REPRODUCIBILITY :
>>>>> This bug can be reproduced always.
>>>>>
>>>>> ---------- BEGIN SOURCE ----------
>>>>> import java.util.HashMap;
>>>>> import java.util.Map;
>>>>>
>>>>> import javax.security.auth.callback.Callback;
>>>>> import javax.security.auth.callback.CallbackHandler;
>>>>> import javax.security.auth.callback.NameCallback;
>>>>> import javax.security.auth.callback.PasswordCallback;
>>>>> import javax.security.auth.login.AppConfigurationEntry;
>>>>> import
>>>>> javax.security.auth.login.AppConfigurationEntry.LoginModuleControlFlag;
>>>>> import javax.security.auth.login.Configuration;
>>>>> import javax.security.auth.login.LoginContext;
>>>>>
>>>>> import com.sun.security.auth.module.Krb5LoginModule;
>>>>>
>>>>> public class LoginModuleExample {
>>>>>
>>>>> // Replace with the realm and KDC of the Windows domain.
>>>>> private static final String REALM = "DEV-DEM.RECOMMIND.COM";
>>>>> private static final String KDC = "AU-DEV-DC01.dev-dem.recommind.com";
>>>>>
>>>>> // Replace with the username and password of any account on the
>>>>> // above domain. Account needs to be enabled and not locked out.
>>>>> private static final String DOMAIN_USER = "sgr";
>>>>> private static final String DOMAIN_USER_PWD = "0rodriguez)";
>>>>>
>>>>> private static final String EXAMPLE_SERVER_LOGIN = "example-server";
>>>>>
>>>>> /**
>>>>> * @param args
>>>>> */
>>>>> public static void main(String[] args) {
>>>>>
>>>>> System.setProperty("java.security.krb5.realm", REALM);
>>>>> System.setProperty("java.security.krb5.kdc", KDC);
>>>>>
>>>>> Configuration.setConfiguration(new CustomConfiguration());
>>>>>
>>>>> try {
>>>>> CallbackHandler handler = getUsernamePasswordHandler(DOMAIN_USER,
>>>>> DOMAIN_USER_PWD);
>>>>>
>>>>> LoginContext loginContext = new LoginContext(EXAMPLE_SERVER_LOGIN,
>>>>> handler);
>>>>>
>>>>> loginContext.login();
>>>>>
>>>>> System.out.println("Login succeeded");
>>>>> }
>>>>> catch (Exception e) {
>>>>>
>>>>> System.out.println("Login failed");
>>>>> e.printStackTrace();
>>>>> }
>>>>>
>>>>> }
>>>>>
>>>>> private static CallbackHandler getUsernamePasswordHandler(final
>>>>> String username, final String password) {
>>>>>
>>>>> final CallbackHandler handler = new CallbackHandler() {
>>>>>
>>>>> public void handle(final Callback[] callback) {
>>>>> for (int i = 0; i<  callback.length; i++) {
>>>>> if (callback[i] instanceof NameCallback) {
>>>>> final NameCallback nameCallback = (NameCallback) callback[i];
>>>>> nameCallback.setName(username);
>>>>> }
>>>>> else if (callback[i] instanceof PasswordCallback) {
>>>>> final PasswordCallback passCallback = (PasswordCallback) callback[i];
>>>>> passCallback.setPassword(password.toCharArray());
>>>>> }
>>>>> }
>>>>> }
>>>>> };
>>>>>
>>>>> return handler;
>>>>> }
>>>>>
>>>>> final static class CustomConfiguration extends Configuration {
>>>>>
>>>>> @Override
>>>>> public AppConfigurationEntry[] getAppConfigurationEntry(String name) {
>>>>>
>>>>> AppConfigurationEntry[] entries = new AppConfigurationEntry[0];
>>>>>
>>>>> if (name.equals(EXAMPLE_SERVER_LOGIN)) {
>>>>> String krbModule = Krb5LoginModule.class.getName();
>>>>> LoginModuleControlFlag flag = LoginModuleControlFlag.REQUIRED;
>>>>> Map<String, String>  options = new HashMap<String, String>();
>>>>>
>>>>> options.put("storeKey", "true");
>>>>> options.put("useKeyTab", "false");
>>>>> options.put("isInitiator", "false");
>>>>>
>>>>> AppConfigurationEntry entry = new AppConfigurationEntry(krbModule,
>>>>> flag, options);
>>>>>
>>>>> entries = new AppConfigurationEntry[] { entry };
>>>>> }
>>>>>
>>>>> return entries;
>>>>> }
>>>>> }
>>>>> }
>>>>>
>>>>> ---------- END SOURCE ----------
>>>>>
>>>>>
>



More information about the security-dev mailing list