United StatesChange Country, Oracle Worldwide Web Sites Communities I am a... I want to...
Bug ID: JDK-7089889 Krb5LoginModule.login() throws an exception if used without a keytab
JDK-7089889 : Krb5LoginModule.login() throws an exception if used without a keytab

Details
Type:
Bug
Submit Date:
2011-09-13
Status:
Closed
Updated Date:
2013-04-24
Project Name:
JDK
Resolved Date:
2011-10-18
Component:
security-libs
OS:
windows_7
Sub-Component:
org.ietf.jgss:krb5
CPU:
x86
Priority:
P3
Resolution:
Fixed
Affected Versions:
7
Fixed Versions:

Related Reports
Backport:
Relates:

Sub Tasks

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 ----------

                                    

Comments
Although it is uncommon for an acceptor to receive username/password from a callback, this is still possible. The bug should be fixed.
                                     
2013-04-16
Verified based on 
nightly-tl results for build Java(TM) SE Runtime Environment 1.8.0 b87
+ manual execution of the sample test program attached against the existing SQE kerberos infrastructure.
Additional  tests have been identified, tested. RFE INTJDK-7603441 has been opened for this

                                     
2013-04-24
EVALUATION

http://hg.openjdk.java.net/jdk8/tl/jdk/rev/79582fcc8329
                                     
2011-09-28



Hardware and Software, Engineered to Work Together