--- a/jdk/src/share/classes/com/sun/security/auth/module/Krb5LoginModule.java Fri Nov 12 07:15:47 2010 -0500
+++ b/jdk/src/share/classes/com/sun/security/auth/module/Krb5LoginModule.java Fri Nov 12 21:33:14 2010 +0800
@@ -27,7 +27,6 @@
package com.sun.security.auth.module;
import java.io.*;
-import java.net.*;
import java.text.MessageFormat;
import java.util.*;
@@ -38,9 +37,6 @@
import javax.security.auth.spi.*;
import sun.security.krb5.*;
-import sun.security.krb5.Config;
-import sun.security.krb5.RealmException;
-import sun.security.util.AuthResources;
import sun.security.jgss.krb5.Krb5Util;
import sun.security.krb5.Credentials;
import sun.misc.HexDumpEncoder;
@@ -685,32 +681,27 @@
}
}
+
+ KrbAsReqBuilder builder;
// We can't get the key from the keytab so prompt
if (encKeys == null) {
promptForPass(getPasswdFromSharedState);
-
- encKeys = EncryptionKey.acquireSecretKeys(
- password, principal.getSalt());
-
+ builder = new KrbAsReqBuilder(principal, password);
if (isInitiator) {
- if (debug)
- System.out.println("Acquire TGT using AS Exchange");
- cred = Credentials.acquireTGT(principal,
- encKeys, password);
- // update keys after pre-auth
- encKeys = EncryptionKey.acquireSecretKeys(password,
- principal.getSalt());
+ // 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();
}
+ encKeys = builder.getKeys();
} else {
+ builder = new KrbAsReqBuilder(principal, encKeys);
if (isInitiator) {
- if (debug)
- System.out.println("Acquire TGT using AS Exchange");
- cred = Credentials.acquireTGT(principal,
- encKeys, password);
+ cred = builder.action().getCreds();
}
}
+ builder.destroy();
- // Get the TGT using AS Exchange
if (debug) {
System.out.println("principal is " + principal);
HexDumpEncoder hd = new HexDumpEncoder();
--- a/jdk/src/share/classes/sun/security/krb5/Config.java Fri Nov 12 07:15:47 2010 -0500
+++ b/jdk/src/share/classes/sun/security/krb5/Config.java Fri Nov 12 21:33:14 2010 +0800
@@ -111,7 +111,7 @@
public static synchronized void refresh() throws KrbException {
singleton = new Config();
KeyTab.refresh();
- KrbKdcReq.initStatic();
+ KdcComm.initStatic();
}
--- a/jdk/src/share/classes/sun/security/krb5/Credentials.java Fri Nov 12 07:15:47 2010 -0500
+++ b/jdk/src/share/classes/sun/security/krb5/Credentials.java Fri Nov 12 21:33:14 2010 +0800
@@ -348,94 +348,6 @@
}
/**
- * Returns a TGT for the given client principal via an AS-Exchange.
- * This method causes pre-authentication data to be sent in the
- * AS-REQ.
- *
- * @param princ the client principal. This value cannot be null.
- * @param secretKey the secret key of the client principal.This value
- * cannot be null.
- * @returns the TGT credentials
- */
- public static Credentials acquireTGT(PrincipalName princ,
- EncryptionKey[] secretKeys,
- char[] password)
- throws KrbException, IOException {
-
- if (princ == null)
- throw new IllegalArgumentException(
- "Cannot have null principal to do AS-Exchange");
-
- if (secretKeys == null)
- throw new IllegalArgumentException(
- "Cannot have null secretKey to do AS-Exchange");
-
- KrbAsRep asRep = null;
- try {
- asRep = sendASRequest(princ, secretKeys, null);
- } catch (KrbException ke) {
- if ((ke.returnCode() == Krb5.KDC_ERR_PREAUTH_FAILED) ||
- (ke.returnCode() == Krb5.KDC_ERR_PREAUTH_REQUIRED)) {
- // process pre-auth info
- if (DEBUG) {
- System.out.println("AcquireTGT: PREAUTH FAILED/REQUIRED," +
- " re-send AS-REQ");
- }
-
- KRBError error = ke.getError();
- // update salt in PrincipalName
- String newSalt = error.getSalt();
- if (newSalt != null && newSalt.length() > 0) {
- princ.setSalt(newSalt);
- }
-
- // refresh keys
- if (password != null) {
- secretKeys = EncryptionKey.acquireSecretKeys(password,
- princ.getSalt(), true,
- error.getEType(), error.getParams());
- }
- asRep = sendASRequest(princ, secretKeys, ke.getError());
- } else {
- throw ke;
- }
- }
- return asRep.getCreds();
- }
-
- /**
- * Sends the AS-REQ
- */
- private static KrbAsRep sendASRequest(PrincipalName princ,
- EncryptionKey[] secretKeys, KRBError error)
- throws KrbException, IOException {
-
- // %%%
- KrbAsReq asReq = null;
- if (error == null) {
- asReq = new KrbAsReq(princ, secretKeys);
- } else {
- asReq = new KrbAsReq(princ, secretKeys, true,
- error.getEType(), error.getSalt(), error.getParams());
- }
-
- String kdc = null;
- KrbAsRep asRep = null;
- try {
- kdc = asReq.send();
- asRep = asReq.getReply(secretKeys);
- } catch (KrbException ke) {
- if (ke.returnCode() == Krb5.KRB_ERR_RESPONSE_TOO_BIG) {
- asReq.send(princ.getRealmString(), kdc, true);
- asRep = asReq.getReply(secretKeys);
- } else {
- throw ke;
- }
- }
- return asRep;
- }
-
- /**
* Acquires default credentials.
* <br>The possible locations for default credentials cache is searched in
* the following order:
@@ -529,29 +441,6 @@
return CredentialsUtil.acquireServiceCreds(service, ccreds);
}
-
- /*
- * This method does the real job to request the service credential.
- */
-
- private static Credentials serviceCreds(ServiceName service,
- Credentials ccreds)
- throws KrbException, IOException {
- return new KrbTgsReq(
- new KDCOptions(),
- ccreds,
- service,
- null, // KerberosTime from
- null, // KerberosTime till
- null, // KerberosTime rtime
- null, // int[] eTypes
- null, // HostAddresses addresses
- null, // AuthorizationData
- null, // Ticket[] additionalTickets
- null // EncryptionKey subSessionKey
- ).sendAndGetCreds();
- }
-
public CredentialsCache getCache() {
return cache;
}
--- a/jdk/src/share/classes/sun/security/krb5/EncryptionKey.java Fri Nov 12 07:15:47 2010 -0500
+++ b/jdk/src/share/classes/sun/security/krb5/EncryptionKey.java Fri Nov 12 21:33:14 2010 +0800
@@ -157,6 +157,22 @@
}
/**
+ * Obtains a key for a given etype with salt and optional s2kparams
+ * @param password NOT null
+ * @param salt NOT null
+ * @param etype
+ * @param s2kparams can be NULL
+ */
+ public static EncryptionKey acquireSecretKey(char[] password,
+ String salt, int etype, byte[] s2kparams)
+ throws KrbException {
+
+ return new EncryptionKey(
+ stringToKey(password, salt, s2kparams, etype),
+ etype, null);
+ }
+
+ /**
* Generate a list of keys using the given principal and password.
* Construct a key for each configured etype.
* Caller is responsible for clearing password.
@@ -169,19 +185,8 @@
* as the default in that case. If default_tkt_enctypes was set in
* the libdefaults of krb5.conf, then use that sequence.
*/
- // Used in Krb5LoginModule
public static EncryptionKey[] acquireSecretKeys(char[] password,
- String salt) throws KrbException {
- return (acquireSecretKeys(password, salt, false, 0, null));
- }
-
- /**
- * Generates a list of keys using the given principal, password,
- * and the pre-authentication values.
- */
- public static EncryptionKey[] acquireSecretKeys(char[] password,
- String salt, boolean pa_exists, int pa_etype, byte[] pa_s2kparams)
- throws KrbException {
+ String salt) throws KrbException {
int[] etypes = EType.getDefaults("default_tkt_enctypes");
if (etypes == null) {
@@ -191,10 +196,8 @@
EncryptionKey[] encKeys = new EncryptionKey[etypes.length];
for (int i = 0; i < etypes.length; i++) {
if (EType.isSupported(etypes[i])) {
- byte[] s2kparams = (pa_exists && etypes[i] == pa_etype)
- ? pa_s2kparams : null;
encKeys[i] = new EncryptionKey(
- stringToKey(password, salt, s2kparams, etypes[i]),
+ stringToKey(password, salt, null, etypes[i]),
etypes[i], null);
} else {
if (DEBUG) {
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/share/classes/sun/security/krb5/KdcComm.java Fri Nov 12 21:33:14 2010 +0800
@@ -0,0 +1,516 @@
+/*
+ * Copyright (c) 2000, 2010, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/*
+ *
+ * (C) Copyright IBM Corp. 1999 All Rights Reserved.
+ * Copyright 1997 The Open Group Research Institute. All rights reserved.
+ */
+
+package sun.security.krb5;
+
+import java.security.PrivilegedAction;
+import java.security.Security;
+import java.util.Locale;
+import sun.security.krb5.internal.Krb5;
+import sun.security.krb5.internal.NetClient;
+import java.io.IOException;
+import java.net.SocketTimeoutException;
+import java.util.StringTokenizer;
+import java.security.AccessController;
+import java.security.PrivilegedExceptionAction;
+import java.security.PrivilegedActionException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+import java.util.HashSet;
+import sun.security.krb5.internal.KRBError;
+
+/**
+ * KDC-REQ/KDC-REP communication. No more base class for KrbAsReq and
+ * KrbTgsReq. This class is now communication only.
+ */
+public final class KdcComm {
+
+ // The following settings can be configured in [libdefaults]
+ // section of krb5.conf, which are global for all realms. Each of
+ // them can also be defined in a realm, which overrides value here.
+
+ /**
+ * max retry time for a single KDC, default Krb5.KDC_RETRY_LIMIT (3)
+ */
+ private static int defaultKdcRetryLimit;
+ /**
+ * timeout requesting a ticket from KDC, in millisec, default 30 sec
+ */
+ private static int defaultKdcTimeout;
+ /**
+ * max UDP packet size, default unlimited (-1)
+ */
+ private static int defaultUdpPrefLimit;
+
+ private static final boolean DEBUG = Krb5.DEBUG;
+
+ private static final String BAD_POLICY_KEY = "krb5.kdc.bad.policy";
+
+ /**
+ * What to do when a KDC is unavailable, specified in the
+ * java.security file with key krb5.kdc.bad.policy.
+ * Possible values can be TRY_LAST or TRY_LESS. Reloaded when refreshed.
+ */
+ private enum BpType {
+ NONE, TRY_LAST, TRY_LESS
+ }
+ private static int tryLessMaxRetries = 1;
+ private static int tryLessTimeout = 5000;
+
+ private static BpType badPolicy;
+
+ static {
+ initStatic();
+ }
+
+ /**
+ * Read global settings
+ */
+ public static void initStatic() {
+ String value = AccessController.doPrivileged(
+ new PrivilegedAction<String>() {
+ public String run() {
+ return Security.getProperty(BAD_POLICY_KEY);
+ }
+ });
+ if (value != null) {
+ value = value.toLowerCase(Locale.ENGLISH);
+ String[] ss = value.split(":");
+ if ("tryless".equals(ss[0])) {
+ if (ss.length > 1) {
+ String[] params = ss[1].split(",");
+ try {
+ int tmp0 = Integer.parseInt(params[0]);
+ if (params.length > 1) {
+ tryLessTimeout = Integer.parseInt(params[1]);
+ }
+ // Assign here in case of exception at params[1]
+ tryLessMaxRetries = tmp0;
+ } catch (NumberFormatException nfe) {
+ // Ignored. Please note that tryLess is recognized and
+ // used, parameters using default values
+ if (DEBUG) {
+ System.out.println("Invalid " + BAD_POLICY_KEY +
+ " parameter for tryLess: " +
+ value + ", use default");
+ }
+ }
+ }
+ badPolicy = BpType.TRY_LESS;
+ } else if ("trylast".equals(ss[0])) {
+ badPolicy = BpType.TRY_LAST;
+ } else {
+ badPolicy = BpType.NONE;
+ }
+ } else {
+ badPolicy = BpType.NONE;
+ }
+
+
+ int timeout = -1;
+ int max_retries = -1;
+ int udf_pref_limit = -1;
+
+ try {
+ Config cfg = Config.getInstance();
+ String temp = cfg.getDefault("kdc_timeout", "libdefaults");
+ timeout = parsePositiveIntString(temp);
+ temp = cfg.getDefault("max_retries", "libdefaults");
+ max_retries = parsePositiveIntString(temp);
+ temp = cfg.getDefault("udp_preference_limit", "libdefaults");
+ udf_pref_limit = parsePositiveIntString(temp);
+ } catch (Exception exc) {
+ // ignore any exceptions; use default values
+ if (DEBUG) {
+ System.out.println ("Exception in getting KDC communication " +
+ "settings, using default value " +
+ exc.getMessage());
+ }
+ }
+ defaultKdcTimeout = timeout > 0 ? timeout : 30*1000; // 30 seconds
+ defaultKdcRetryLimit =
+ max_retries > 0 ? max_retries : Krb5.KDC_RETRY_LIMIT;
+ defaultUdpPrefLimit = udf_pref_limit;
+
+ KdcAccessibility.reset();
+ }
+
+ /**
+ * The instance fields
+ */
+ private String realm;
+
+ public KdcComm(String realm) throws KrbException {
+ if (realm == null) {
+ realm = Config.getInstance().getDefaultRealm();
+ if (realm == null) {
+ throw new KrbException(Krb5.KRB_ERR_GENERIC,
+ "Cannot find default realm");
+ }
+ }
+ this.realm = realm;
+ }
+
+ public byte[] send(byte[] obuf)
+ throws IOException, KrbException {
+ int udpPrefLimit = getRealmSpecificValue(
+ realm, "udp_preference_limit", defaultUdpPrefLimit);
+
+ boolean useTCP = (udpPrefLimit > 0 &&
+ (obuf != null && obuf.length > udpPrefLimit));
+
+ return send(obuf, useTCP);
+ }
+
+ private byte[] send(byte[] obuf, boolean useTCP)
+ throws IOException, KrbException {
+
+ if (obuf == null)
+ return null;
+ Exception savedException = null;
+ Config cfg = Config.getInstance();
+
+ if (realm == null) {
+ realm = cfg.getDefaultRealm();
+ if (realm == null) {
+ throw new KrbException(Krb5.KRB_ERR_GENERIC,
+ "Cannot find default realm");
+ }
+ }
+
+ String kdcList = cfg.getKDCList(realm);
+ if (kdcList == null) {
+ throw new KrbException("Cannot get kdc for realm " + realm);
+ }
+ String tempKdc = null; // may include the port number also
+ byte[] ibuf = null;
+ for (String tmp: KdcAccessibility.list(kdcList)) {
+ tempKdc = tmp;
+ try {
+ ibuf = send(obuf,tempKdc,useTCP);
+ KRBError ke = null;
+ try {
+ ke = new KRBError(ibuf);
+ } catch (Exception e) {
+ // OK
+ }
+ if (ke != null && ke.getErrorCode() ==
+ Krb5.KRB_ERR_RESPONSE_TOO_BIG) {
+ ibuf = send(obuf, tempKdc, true);
+ }
+ KdcAccessibility.removeBad(tempKdc);
+ break;
+ } catch (Exception e) {
+ if (DEBUG) {
+ System.out.println(">>> KrbKdcReq send: error trying " +
+ tempKdc);
+ e.printStackTrace(System.out);
+ }
+ KdcAccessibility.addBad(tempKdc);
+ savedException = e;
+ }
+ }
+ if (ibuf == null && savedException != null) {
+ if (savedException instanceof IOException) {
+ throw (IOException) savedException;
+ } else {
+ throw (KrbException) savedException;
+ }
+ }
+ return ibuf;
+ }
+
+ // send the AS Request to the specified KDC
+
+ private byte[] send(byte[] obuf, String tempKdc, boolean useTCP)
+ throws IOException, KrbException {
+
+ if (obuf == null)
+ return null;
+
+ int port = Krb5.KDC_INET_DEFAULT_PORT;
+ int retries = getRealmSpecificValue(
+ realm, "max_retries", defaultKdcRetryLimit);
+ int timeout = getRealmSpecificValue(
+ realm, "kdc_timeout", defaultKdcTimeout);
+ if (badPolicy == BpType.TRY_LESS &&
+ KdcAccessibility.isBad(tempKdc)) {
+ if (retries > tryLessMaxRetries) {
+ retries = tryLessMaxRetries; // less retries
+ }
+ if (timeout > tryLessTimeout) {
+ timeout = tryLessTimeout; // less time
+ }
+ }
+
+ String kdc = null;
+ String portStr = null;
+
+ if (tempKdc.charAt(0) == '[') { // Explicit IPv6 in []
+ int pos = tempKdc.indexOf(']', 1);
+ if (pos == -1) {
+ throw new IOException("Illegal KDC: " + tempKdc);
+ }
+ kdc = tempKdc.substring(1, pos);
+ if (pos != tempKdc.length() - 1) { // with port number
+ if (tempKdc.charAt(pos+1) != ':') {
+ throw new IOException("Illegal KDC: " + tempKdc);
+ }
+ portStr = tempKdc.substring(pos+2);
+ }
+ } else {
+ int colon = tempKdc.indexOf(':');
+ if (colon == -1) { // Hostname or IPv4 host only
+ kdc = tempKdc;
+ } else {
+ int nextColon = tempKdc.indexOf(':', colon+1);
+ if (nextColon > 0) { // >=2 ":", IPv6 with no port
+ kdc = tempKdc;
+ } else { // 1 ":", hostname or IPv4 with port
+ kdc = tempKdc.substring(0, colon);
+ portStr = tempKdc.substring(colon+1);
+ }
+ }
+ }
+ if (portStr != null) {
+ int tempPort = parsePositiveIntString(portStr);
+ if (tempPort > 0)
+ port = tempPort;
+ }
+
+ if (DEBUG) {
+ System.out.println(">>> KrbKdcReq send: kdc=" + kdc
+ + (useTCP ? " TCP:":" UDP:")
+ + port + ", timeout="
+ + timeout
+ + ", number of retries ="
+ + retries
+ + ", #bytes=" + obuf.length);
+ }
+
+ KdcCommunication kdcCommunication =
+ new KdcCommunication(kdc, port, useTCP, timeout, retries, obuf);
+ try {
+ byte[] ibuf = AccessController.doPrivileged(kdcCommunication);
+ if (DEBUG) {
+ System.out.println(">>> KrbKdcReq send: #bytes read="
+ + (ibuf != null ? ibuf.length : 0));
+ }
+ return ibuf;
+ } catch (PrivilegedActionException e) {
+ Exception wrappedException = e.getException();
+ if (wrappedException instanceof IOException) {
+ throw (IOException) wrappedException;
+ } else {
+ throw (KrbException) wrappedException;
+ }
+ }
+ }
+
+ private static class KdcCommunication
+ implements PrivilegedExceptionAction<byte[]> {
+
+ private String kdc;
+ private int port;
+ private boolean useTCP;
+ private int timeout;
+ private int retries;
+ private byte[] obuf;
+
+ public KdcCommunication(String kdc, int port, boolean useTCP,
+ int timeout, int retries, byte[] obuf) {
+ this.kdc = kdc;
+ this.port = port;
+ this.useTCP = useTCP;
+ this.timeout = timeout;
+ this.retries = retries;
+ this.obuf = obuf;
+ }
+
+ // The caller only casts IOException and KrbException so don't
+ // add any new ones!
+
+ public byte[] run() throws IOException, KrbException {
+
+ byte[] ibuf = null;
+
+ for (int i=1; i <= retries; i++) {
+ String proto = useTCP?"TCP":"UDP";
+ NetClient kdcClient = NetClient.getInstance(
+ proto, kdc, port, timeout);
+ if (DEBUG) {
+ System.out.println(">>> KDCCommunication: kdc=" + kdc
+ + " " + proto + ":"
+ + port + ", timeout="
+ + timeout
+ + ",Attempt =" + i
+ + ", #bytes=" + obuf.length);
+ }
+ try {
+ /*
+ * Send the data to the kdc.
+ */
+ kdcClient.send(obuf);
+ /*
+ * And get a response.
+ */
+ ibuf = kdcClient.receive();
+ break;
+ } catch (SocketTimeoutException se) {
+ if (DEBUG) {
+ System.out.println ("SocketTimeOutException with " +
+ "attempt: " + i);
+ }
+ if (i == retries) {
+ ibuf = null;
+ throw se;
+ }
+ } finally {
+ kdcClient.close();
+ }
+ }
+ return ibuf;
+ }
+ }
+
+ /**
+ * Returns krb5.conf setting of {@code key} for a specfic realm,
+ * which can be:
+ * 1. defined in the sub-stanza for the given realm inside [realms], or
+ * 2. defined in [libdefaults], or
+ * 3. defValue
+ * @param realm the given realm in which the setting is requested. Returns
+ * the global setting if null
+ * @param key the key for the setting
+ * @param defValue default value
+ * @return a value for the key
+ */
+ private int getRealmSpecificValue(String realm, String key, int defValue) {
+ int v = defValue;
+
+ if (realm == null) return v;
+
+ int temp = -1;
+ try {
+ String value =
+ Config.getInstance().getDefault(key, realm);
+ temp = parsePositiveIntString(value);
+ } catch (Exception exc) {
+ // Ignored, defValue will be picked up
+ }
+
+ if (temp > 0) v = temp;
+
+ return v;
+ }
+
+ private static int parsePositiveIntString(String intString) {
+ if (intString == null)
+ return -1;
+
+ int ret = -1;
+
+ try {
+ ret = Integer.parseInt(intString);
+ } catch (Exception exc) {
+ return -1;
+ }
+
+ if (ret >= 0)
+ return ret;
+
+ return -1;
+ }
+
+ /**
+ * Maintains a KDC accessible list. Unavailable KDCs are put into a
+ * blacklist, when a KDC in the blacklist is available, it's removed
+ * from there. No insertion order in the blacklist.
+ *
+ * There are two methods to deal with KDCs in the blacklist. 1. Only try
+ * them when there's no KDC not on the blacklist. 2. Still try them, but
+ * with lesser number of retries and smaller timeout value.
+ */
+ static class KdcAccessibility {
+ // Known bad KDCs
+ private static Set<String> bads = new HashSet<String>();
+
+ private static synchronized void addBad(String kdc) {
+ if (DEBUG) {
+ System.out.println(">>> KdcAccessibility: add " + kdc);
+ }
+ bads.add(kdc);
+ }
+
+ private static synchronized void removeBad(String kdc) {
+ if (DEBUG) {
+ System.out.println(">>> KdcAccessibility: remove " + kdc);
+ }
+ bads.remove(kdc);
+ }
+
+ private static synchronized boolean isBad(String kdc) {
+ return bads.contains(kdc);
+ }
+
+ private static synchronized void reset() {
+ if (DEBUG) {
+ System.out.println(">>> KdcAccessibility: reset");
+ }
+ bads.clear();
+ }
+
+ // Returns a preferred KDC list by putting the bad ones at the end
+ private static synchronized String[] list(String kdcList) {
+ StringTokenizer st = new StringTokenizer(kdcList);
+ List<String> list = new ArrayList<String>();
+ if (badPolicy == BpType.TRY_LAST) {
+ List<String> badkdcs = new ArrayList<String>();
+ while (st.hasMoreTokens()) {
+ String t = st.nextToken();
+ if (bads.contains(t)) badkdcs.add(t);
+ else list.add(t);
+ }
+ // Bad KDCs are put at last
+ list.addAll(badkdcs);
+ } else {
+ // All KDCs are returned in their original order,
+ // This include TRY_LESS and NONE
+ while (st.hasMoreTokens()) {
+ list.add(st.nextToken());
+ }
+ }
+ return list.toArray(new String[list.size()]);
+ }
+ }
+}
+
--- a/jdk/src/share/classes/sun/security/krb5/KrbAsRep.java Fri Nov 12 07:15:47 2010 -0500
+++ b/jdk/src/share/classes/sun/security/krb5/KrbAsRep.java Fri Nov 12 21:33:14 2010 +0800
@@ -36,25 +36,24 @@
import sun.security.krb5.internal.crypto.EType;
import sun.security.util.*;
import java.io.IOException;
+import java.util.Objects;
/**
* This class encapsulates a AS-REP message that the KDC sends to the
* client.
*/
-public class KrbAsRep extends KrbKdcRep {
+class KrbAsRep extends KrbKdcRep {
- private ASRep rep;
- private Credentials creds;
+ private ASRep rep; // The AS-REP message
+ private Credentials creds; // The Credentials provide by the AS-REP
+ // message, created by initiator after calling
+ // the decrypt() method
private boolean DEBUG = Krb5.DEBUG;
- KrbAsRep(byte[] ibuf, EncryptionKey[] keys, KrbAsReq asReq) throws
- KrbException, Asn1Exception, IOException {
- if (keys == null)
- throw new KrbException(Krb5.API_INVALID_ARG);
+ KrbAsRep(byte[] ibuf) throws
+ KrbException, Asn1Exception, IOException {
DerValue encoding = new DerValue(ibuf);
- ASReq req = asReq.getMessage();
- ASRep rep = null;
try {
rep = new ASRep(encoding);
} catch (Asn1Exception e) {
@@ -83,25 +82,77 @@
ke.initCause(e);
throw ke;
}
+ }
+ // KrbAsReqBuilder need to read back the PA for key generation
+ PAData[] getPA() {
+ return rep.pAData;
+ }
+
+ /**
+ * Called by KrbAsReqBuilder to resolve a AS-REP message using keys.
+ * @param keys user provided keys, not null
+ * @param asReq the original AS-REQ sent, used to validate AS-REP
+ */
+ void decryptUsingKeys(EncryptionKey[] keys, KrbAsReq asReq)
+ throws KrbException, Asn1Exception, IOException {
+ EncryptionKey dkey = null;
int encPartKeyType = rep.encPart.getEType();
- EncryptionKey dkey = EncryptionKey.findKey(encPartKeyType, keys);
-
+ Integer encPartKvno = rep.encPart.kvno;
+ try {
+ dkey = EncryptionKey.findKey(encPartKeyType, encPartKvno, keys);
+ } catch (KrbException ke) {
+ if (ke.returnCode() == Krb5.KRB_AP_ERR_BADKEYVER) {
+ // Fallback to no kvno. In some cases, keytab is generated
+ // not by sysadmin but Java's ktab command
+ dkey = EncryptionKey.findKey(encPartKeyType, keys);
+ }
+ }
if (dkey == null) {
throw new KrbException(Krb5.API_INVALID_ARG,
- "Cannot find key of appropriate type to decrypt AS REP - " +
- EType.toString(encPartKeyType));
+ "Cannot find key for type/kvno to decrypt AS REP - " +
+ EType.toString(encPartKeyType) + "/" + encPartKvno);
}
+ decrypt(dkey, asReq);
+ }
+ /**
+ * Called by KrbAsReqBuilder to resolve a AS-REP message using a password.
+ * @param password user provided password. not null
+ * @param asReq the original AS-REQ sent, used to validate AS-REP
+ * @param cname the user principal name, used to provide salt
+ */
+ void decryptUsingPassword(char[] password,
+ KrbAsReq asReq, PrincipalName cname)
+ throws KrbException, Asn1Exception, IOException {
+ int encPartKeyType = rep.encPart.getEType();
+ PAData.SaltAndParams snp =
+ PAData.getSaltAndParams(encPartKeyType, rep.pAData);
+ EncryptionKey dkey = null;
+ dkey = EncryptionKey.acquireSecretKey(password,
+ snp.salt == null ? cname.getSalt() : snp.salt,
+ encPartKeyType,
+ snp.params);
+ decrypt(dkey, asReq);
+ }
+
+ /**
+ * Decrypts encrypted content inside AS-REP. Called by initiator.
+ * @param dkey the decryption key to use
+ * @param asReq the original AS-REQ sent, used to validate AS-REP
+ */
+ private void decrypt(EncryptionKey dkey, KrbAsReq asReq)
+ throws KrbException, Asn1Exception, IOException {
byte[] enc_as_rep_bytes = rep.encPart.decrypt(dkey,
KeyUsage.KU_ENC_AS_REP_PART);
byte[] enc_as_rep_part = rep.encPart.reset(enc_as_rep_bytes);
- encoding = new DerValue(enc_as_rep_part);
+ DerValue encoding = new DerValue(enc_as_rep_part);
EncASRepPart enc_part = new EncASRepPart(encoding);
rep.ticket.sname.setRealm(rep.ticket.realm);
rep.encKDCRepPart = enc_part;
+ ASReq req = asReq.getMessage();
check(req, rep);
creds = new Credentials(
@@ -119,17 +170,13 @@
System.out.println(">>> KrbAsRep cons in KrbAsReq.getReply " +
req.reqBody.cname.getNameString());
}
-
- this.rep = rep;
- this.creds = creds;
}
- public Credentials getCreds() {
- return creds;
+ Credentials getCreds() {
+ return Objects.nonNull(creds, "Creds not available yet.");
}
- // made public for Kinit
- public sun.security.krb5.internal.ccache.Credentials setCredentials() {
+ sun.security.krb5.internal.ccache.Credentials getCCreds() {
return new sun.security.krb5.internal.ccache.Credentials(rep);
}
}
--- a/jdk/src/share/classes/sun/security/krb5/KrbAsReq.java Fri Nov 12 07:15:47 2010 -0500
+++ b/jdk/src/share/classes/sun/security/krb5/KrbAsReq.java Fri Nov 12 21:33:14 2010 +0800
@@ -32,290 +32,38 @@
package sun.security.krb5;
import sun.security.krb5.internal.*;
-import sun.security.krb5.internal.crypto.EType;
import sun.security.krb5.internal.crypto.Nonce;
import sun.security.krb5.internal.crypto.KeyUsage;
-import sun.security.util.*;
import java.io.IOException;
-import java.io.ByteArrayInputStream;
-import java.net.UnknownHostException;
-import java.util.StringTokenizer;
/**
* This class encapsulates the KRB-AS-REQ message that the client
* sends to the KDC.
*/
-public class KrbAsReq extends KrbKdcReq {
- private PrincipalName princName;
+public class KrbAsReq {
private ASReq asReqMessg;
private boolean DEBUG = Krb5.DEBUG;
- private static KDCOptions defaultKDCOptions = new KDCOptions();
-
- // pre-auth info
- private boolean PA_ENC_TIMESTAMP_REQUIRED = false;
- private boolean pa_exists = false;
- private int pa_etype = 0;
- private String pa_salt = null;
- private byte[] pa_s2kparams = null;
-
- // default is address-less tickets
- private boolean KDC_EMPTY_ADDRESSES_ALLOWED = true;
-
- /**
- * Creates a KRB-AS-REQ to send to the default KDC
- * @throws KrbException
- * @throws IOException
- */
- // Called by Credentials
- KrbAsReq(PrincipalName principal, EncryptionKey[] keys)
- throws KrbException, IOException {
- this(keys, // for pre-authentication
- false, 0, null, null, // pre-auth values
- defaultKDCOptions,
- principal,
- null, // PrincipalName sname
- null, // KerberosTime from
- null, // KerberosTime till
- null, // KerberosTime rtime
- null, // int[] eTypes
- null, // HostAddresses addresses
- null); // Ticket[] additionalTickets
- }
/**
- * Creates a KRB-AS-REQ to send to the default KDC
- * with pre-authentication values
+ * Constructs an AS-REQ message.
*/
- KrbAsReq(PrincipalName principal, EncryptionKey[] keys,
- boolean pa_exists, int etype, String salt, byte[] s2kparams)
- throws KrbException, IOException {
- this(keys, // for pre-authentication
- pa_exists, etype, salt, s2kparams, // pre-auth values
- defaultKDCOptions,
- principal,
- null, // PrincipalName sname
- null, // KerberosTime from
- null, // KerberosTime till
- null, // KerberosTime rtime
- null, // int[] eTypes
- null, // HostAddresses addresses
- null); // Ticket[] additionalTickets
- }
-
- private static int[] getETypesFromKeys(EncryptionKey[] keys) {
- int[] types = new int[keys.length];
- for (int i = 0; i < keys.length; i++) {
- types[i] = keys[i].getEType();
- }
- return types;
- }
-
- // update with pre-auth info
- public void updatePA(int etype, String salt, byte[] params, PrincipalName name) {
- // set the pre-auth values
- pa_exists = true;
- pa_etype = etype;
- pa_salt = salt;
- pa_s2kparams = params;
-
- // update salt in PrincipalName
- if (salt != null && salt.length() > 0) {
- name.setSalt(salt);
- if (DEBUG) {
- System.out.println("Updated salt from pre-auth = " + name.getSalt());
- }
- }
- PA_ENC_TIMESTAMP_REQUIRED = true;
- }
-
- // Used by Kinit
- public KrbAsReq(
- char[] password,
- KDCOptions options,
- PrincipalName cname,
- PrincipalName sname,
- KerberosTime from,
- KerberosTime till,
- KerberosTime rtime,
- int[] eTypes,
- HostAddresses addresses,
- Ticket[] additionalTickets)
- throws KrbException, IOException {
- this(password,
- false, 0, null, null, // pre-auth values
- options,
- cname,
- sname, // PrincipalName sname
- from, // KerberosTime from
- till, // KerberosTime till
- rtime, // KerberosTime rtime
- eTypes, // int[] eTypes
- addresses, // HostAddresses addresses
- additionalTickets); // Ticket[] additionalTickets
- }
-
- // Used by Kinit
- public KrbAsReq(
- char[] password,
- boolean pa_exists,
- int etype,
- String salt,
- byte[] s2kparams,
- KDCOptions options,
- PrincipalName cname,
- PrincipalName sname,
- KerberosTime from,
- KerberosTime till,
- KerberosTime rtime,
- int[] eTypes,
- HostAddresses addresses,
- Ticket[] additionalTickets)
- throws KrbException, IOException {
-
- EncryptionKey[] keys = null;
-
- // update with preauth info
- if (pa_exists) {
- updatePA(etype, salt, s2kparams, cname);
- }
-
- if (password != null) {
- keys = EncryptionKey.acquireSecretKeys(password, cname.getSalt(), pa_exists,
- pa_etype, pa_s2kparams);
- }
- if (DEBUG) {
- System.out.println(">>>KrbAsReq salt is " + cname.getSalt());
- }
+ // Can be null? has default?
+ public KrbAsReq(EncryptionKey pakey, // ok
+ KDCOptions options, // ok, new KDCOptions()
+ PrincipalName cname, // NO and must have realm
+ PrincipalName sname, // ok, krgtgt@CREALM
+ KerberosTime from, // ok
+ KerberosTime till, // ok, will use
+ KerberosTime rtime, // ok
+ int[] eTypes, // NO
+ HostAddresses addresses // ok
+ )
+ throws KrbException, IOException {
- try {
- init(
- keys,
- options,
- cname,
- sname,
- from,
- till,
- rtime,
- eTypes,
- addresses,
- additionalTickets);
- }
- finally {
- /*
- * Its ok to destroy the key here because we created it and are
- * now done with it.
- */
- if (keys != null) {
- for (int i = 0; i < keys.length; i++) {
- keys[i].destroy();
- }
- }
+ if (options == null) {
+ options = new KDCOptions();
}
- }
-
- // Used in Kinit
- public KrbAsReq(
- EncryptionKey[] keys,
- KDCOptions options,
- PrincipalName cname,
- PrincipalName sname,
- KerberosTime from,
- KerberosTime till,
- KerberosTime rtime,
- int[] eTypes,
- HostAddresses addresses,
- Ticket[] additionalTickets)
- throws KrbException, IOException {
- this(keys,
- false, 0, null, null, // pre-auth values
- options,
- cname,
- sname, // PrincipalName sname
- from, // KerberosTime from
- till, // KerberosTime till
- rtime, // KerberosTime rtime
- eTypes, // int[] eTypes
- addresses, // HostAddresses addresses
- additionalTickets); // Ticket[] additionalTickets
- }
-
- // Used by Kinit
- public KrbAsReq(
- EncryptionKey[] keys,
- boolean pa_exists,
- int etype,
- String salt,
- byte[] s2kparams,
- KDCOptions options,
- PrincipalName cname,
- PrincipalName sname,
- KerberosTime from,
- KerberosTime till,
- KerberosTime rtime,
- int[] eTypes,
- HostAddresses addresses,
- Ticket[] additionalTickets)
- throws KrbException, IOException {
-
- // update with preauth info
- if (pa_exists) {
- // update pre-auth info
- updatePA(etype, salt, s2kparams, cname);
-
- if (DEBUG) {
- System.out.println(">>>KrbAsReq salt is " + cname.getSalt());
- }
- }
-
- init(
- keys,
- options,
- cname,
- sname,
- from,
- till,
- rtime,
- eTypes,
- addresses,
- additionalTickets);
- }
-
- /*
- private KrbAsReq(KDCOptions options,
- PrincipalName cname,
- PrincipalName sname,
- KerberosTime from,
- KerberosTime till,
- KerberosTime rtime,
- int[] eTypes,
- HostAddresses addresses,
- Ticket[] additionalTickets)
- throws KrbException, IOException {
- init(null,
- options,
- cname,
- sname,
- from,
- till,
- rtime,
- eTypes,
- addresses,
- additionalTickets);
- }
-*/
-
- private void init(EncryptionKey[] keys,
- KDCOptions options,
- PrincipalName cname,
- PrincipalName sname,
- KerberosTime from,
- KerberosTime till,
- KerberosTime rtime,
- int[] eTypes,
- HostAddresses addresses,
- Ticket[] additionalTickets )
- throws KrbException, IOException {
// check if they are valid arguments. The optional fields should be
// consistent with settings in KDCOptions. Mar 17 2000
@@ -341,189 +89,66 @@
if (rtime != null) rtime = null;
}
- princName = cname;
- int[] tktETypes = EType.getDefaults("default_tkt_enctypes", keys);
PAData[] paData = null;
- if (PA_ENC_TIMESTAMP_REQUIRED) {
- EncryptionKey key = null;
- if (pa_etype != EncryptedData.ETYPE_NULL) {
- if (DEBUG) {
- System.out.println("Pre-Authenticaton: find key for etype = " + pa_etype);
- }
- key = EncryptionKey.findKey(pa_etype, keys);
- } else {
- if (tktETypes.length > 0) {
- key = EncryptionKey.findKey(tktETypes[0], keys);
- }
- }
- if (DEBUG) {
- System.out.println("AS-REQ: Add PA_ENC_TIMESTAMP now");
- }
+ if (pakey != null) {
PAEncTSEnc ts = new PAEncTSEnc();
byte[] temp = ts.asn1Encode();
- if (key != null) {
- // Use first key in list
- EncryptedData encTs = new EncryptedData(key, temp,
- KeyUsage.KU_PA_ENC_TS);
- paData = new PAData[1];
- paData[0] = new PAData( Krb5.PA_ENC_TIMESTAMP,
- encTs.asn1Encode());
- }
+ EncryptedData encTs = new EncryptedData(pakey, temp,
+ KeyUsage.KU_PA_ENC_TS);
+ paData = new PAData[1];
+ paData[0] = new PAData( Krb5.PA_ENC_TIMESTAMP,
+ encTs.asn1Encode());
+ }
+
+ if (cname.getRealm() == null) {
+ throw new RealmException(Krb5.REALM_NULL,
+ "default realm not specified ");
}
if (DEBUG) {
- System.out.println(">>> KrbAsReq calling createMessage");
- }
-
- if (eTypes == null) {
- eTypes = tktETypes;
+ System.out.println(">>> KrbAsReq creating message");
}
// check to use addresses in tickets
- if (Config.getInstance().useAddresses()) {
- KDC_EMPTY_ADDRESSES_ALLOWED = false;
- }
- // get the local InetAddress if required
- if (addresses == null && !KDC_EMPTY_ADDRESSES_ALLOWED) {
+ if (addresses == null && Config.getInstance().useAddresses()) {
addresses = HostAddresses.getLocalAddresses();
}
- asReqMessg = createMessage(
- paData,
- options,
- cname,
- cname.getRealm(),
- sname,
- from,
- till,
- rtime,
- eTypes,
- addresses,
- additionalTickets);
- obuf = asReqMessg.asn1Encode();
- }
-
- /**
- * Returns an AS-REP message corresponding to the AS-REQ that
- * was sent.
- * @param password The password that will be used to derive the
- * secret key that will decrypt the AS-REP from the KDC.
- * @exception KrbException if an error occurs while reading the data.
- * @exception IOException if an I/O error occurs while reading encoded data.
- */
- public KrbAsRep getReply(char[] password)
- throws KrbException, IOException {
-
- if (password == null)
- throw new KrbException(Krb5.API_INVALID_ARG);
- KrbAsRep temp = null;
- EncryptionKey[] keys = null;
- try {
- keys = EncryptionKey.acquireSecretKeys(password,
- princName.getSalt(), pa_exists, pa_etype, pa_s2kparams);
- temp = getReply(keys);
- } finally {
- /*
- * Its ok to destroy the key here because we created it and are
- * now done with it.
- */
- if (keys != null) {
- for (int i = 0; i < keys.length; i++) {
- keys[i].destroy();
- }
- }
- }
- return temp;
- }
-
- /**
- * Sends an AS request to the realm of the client.
- * returns the KDC hostname that the request was sent to
- */
-
- public String send()
- throws IOException, KrbException
- {
- String realmStr = null;
- if (princName != null)
- realmStr = princName.getRealmString();
-
- return (send(realmStr));
- }
-
- /**
- * Returns an AS-REP message corresponding to the AS-REQ that
- * was sent.
- * @param keys The secret keys that will decrypt the AS-REP from
- * the KDC; key selected depends on etype used to encrypt data.
- * @exception KrbException if an error occurs while reading the data.
- * @exception IOException if an I/O error occurs while reading encoded
- * data.
- *
- */
- public KrbAsRep getReply(EncryptionKey[] keys)
- throws KrbException,IOException {
- return new KrbAsRep(ibuf, keys, this);
- }
-
- private ASReq createMessage(
- PAData[] paData,
- KDCOptions kdc_options,
- PrincipalName cname,
- Realm crealm,
- PrincipalName sname,
- KerberosTime from,
- KerberosTime till,
- KerberosTime rtime,
- int[] eTypes,
- HostAddresses addresses,
- Ticket[] additionalTickets
- ) throws Asn1Exception, KrbApErrException,
- RealmException, UnknownHostException, IOException {
-
- if (DEBUG) {
- System.out.println(">>> KrbAsReq in createMessage");
+ if (sname == null) {
+ sname = new PrincipalName("krbtgt" +
+ PrincipalName.NAME_COMPONENT_SEPARATOR +
+ cname.getRealmAsString(),
+ PrincipalName.KRB_NT_SRV_INST);
}
- PrincipalName req_sname = null;
- if (sname == null) {
- if (crealm == null) {
- throw new RealmException(Krb5.REALM_NULL,
- "default realm not specified ");
- }
- req_sname = new PrincipalName(
- "krbtgt" +
- PrincipalName.NAME_COMPONENT_SEPARATOR +
- crealm.toString(),
- PrincipalName.KRB_NT_SRV_INST);
- } else
- req_sname = sname;
-
- KerberosTime req_till = null;
if (till == null) {
- req_till = new KerberosTime();
- } else {
- req_till = till;
+ till = new KerberosTime(0); // Choose KDC maximum allowed
}
- KDCReqBody kdc_req_body = new KDCReqBody(kdc_options,
+ // enc-authorization-data and additional-tickets never in AS-REQ
+ KDCReqBody kdc_req_body = new KDCReqBody(options,
cname,
- crealm,
- req_sname,
+ cname.getRealm(),
+ sname,
from,
- req_till,
+ till,
rtime,
Nonce.value(),
eTypes,
addresses,
null,
- additionalTickets);
+ null);
- return new ASReq(
+ asReqMessg = new ASReq(
paData,
kdc_req_body);
}
+ byte[] encoding() throws IOException, Asn1Exception {
+ return asReqMessg.asn1Encode();
+ }
+
+ // Used by KrbAsRep to validate AS-REP
ASReq getMessage() {
return asReqMessg;
}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/share/classes/sun/security/krb5/KrbAsReqBuilder.java Fri Nov 12 21:33:14 2010 +0800
@@ -0,0 +1,395 @@
+/*
+ * Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package sun.security.krb5;
+
+import java.io.IOException;
+import java.util.Arrays;
+import sun.security.krb5.internal.HostAddresses;
+import sun.security.krb5.internal.KDCOptions;
+import sun.security.krb5.internal.KRBError;
+import sun.security.krb5.internal.KerberosTime;
+import sun.security.krb5.internal.Krb5;
+import sun.security.krb5.internal.PAData;
+import sun.security.krb5.internal.crypto.EType;
+
+/**
+ * A manager class for AS-REQ communications.
+ *
+ * This class does:
+ * 1. Gather information to create AS-REQ
+ * 2. Create and send AS-REQ
+ * 3. Receive AS-REP and KRB-ERROR (-KRB_ERR_RESPONSE_TOO_BIG) and parse them
+ * 4. Emit credentials and secret keys (for JAAS storeKey=true)
+ *
+ * This class does not:
+ * 1. Deal with real communications (KdcComm does it, and TGS-REQ)
+ * a. Name of KDCs for a realm
+ * b. Server availability, timeout, UDP or TCP
+ * d. KRB_ERR_RESPONSE_TOO_BIG
+ *
+ * With this class:
+ * 1. KrbAsReq has only one constructor
+ * 2. Krb5LoginModule and Kinit call a single builder
+ * 3. Better handling of sensitive info
+ *
+ * @since 1.7
+ */
+
+public final class KrbAsReqBuilder {
+
+ // Common data for AS-REQ fields
+ private KDCOptions options;
+ private PrincipalName cname;
+ private PrincipalName sname;
+ private KerberosTime from;
+ private KerberosTime till;
+ private KerberosTime rtime;
+ private HostAddresses addresses;
+
+ // Secret source: can't be changed once assigned, only one (of the two
+ // sources) can be set and should be non-null
+ private EncryptionKey[] keys;
+ private char[] password;
+
+ // Used to create a ENC-TIMESTAMP in the 2nd AS-REQ
+ private EncryptionKey pakey;
+ private PAData[] paList; // PA-DATA from both KRB-ERROR and AS-REP.
+ // Used by getKeys() only.
+ // Only AS-REP should be enough per RFC,
+ // combined in case etypes are different.
+
+ // The generated and received:
+ int[] eTypes;
+ private KrbAsReq req;
+ private KrbAsRep rep;
+
+ private static enum State {
+ INIT, // Initialized, can still add more initialization info
+ REQ_OK, // AS-REQ performed
+ DESTROYED, // Destroyed, not usable anymore
+ }
+ private State state;
+
+ // Called by other constructors
+ private KrbAsReqBuilder(PrincipalName cname)
+ throws KrbException {
+ if (cname.getRealm() == null) {
+ cname.setRealm(Config.getInstance().getDefaultRealm());
+ }
+ this.cname = cname;
+ state = State.INIT;
+ }
+
+ /**
+ * Creates a builder to be used by {@code cname} with existing keys.
+ *
+ * @param cname the client of the AS-REQ. Must not be null. Might have no
+ * realm, where default realm will be used. This realm will be the target
+ * realm for AS-REQ. I believe a client should only get initial TGT from
+ * its own realm.
+ * @param keys must not be null. if empty, might be quite useless.
+ * This argument will neither be modified nor stored by the method.
+ * @throws KrbException
+ */
+ public KrbAsReqBuilder(PrincipalName cname, EncryptionKey[] keys)
+ throws KrbException {
+ this(cname);
+ this.keys = new EncryptionKey[keys.length];
+ for (int i=0; i<keys.length; i++) {
+ this.keys[i] = (EncryptionKey)keys[i].clone();
+ }
+ eTypes = EType.getDefaults("default_tkt_enctypes", keys);
+ }
+
+ /**
+ * Creates a builder to be used by {@code cname} with a known password.
+ *
+ * @param cname the client of the AS-REQ. Must not be null. Might have no
+ * realm, where default realm will be used. This realm will be the target
+ * realm for AS-REQ. I believe a client should only get initial TGT from
+ * its own realm.
+ * @param pass must not be null. This argument will neither be modified
+ * nor stored by the method.
+ * @throws KrbException
+ */
+ public KrbAsReqBuilder(PrincipalName cname, char[] pass)
+ throws KrbException {
+ this(cname);
+ this.password = pass.clone();
+ eTypes = EType.getDefaults("default_tkt_enctypes");
+ }
+
+ /**
+ * Retrieves an array of secret keys for the client. This is useful if
+ * the client supplies password but need keys to act as an acceptor
+ * (in JAAS words, isInitiator=true and storeKey=true)
+ * @return original keys if initiated with keys, or generated keys if
+ * password. In latter case, PA-DATA from server might be used to
+ * generate keys. All "default_tkt_enctypes" keys will be generated,
+ * Never null.
+ * @throws KrbException
+ */
+ public EncryptionKey[] getKeys() throws KrbException {
+ checkState(State.REQ_OK, "Cannot get keys");
+ if (keys != null) {
+ EncryptionKey[] result = new EncryptionKey[keys.length];
+ for (int i=0; i<keys.length; i++) {
+ result[i] = (EncryptionKey)keys[i].clone();
+ }
+ return result;
+ } else {
+ EncryptionKey[] result = new EncryptionKey[eTypes.length];
+
+ /*
+ * Returns an array of keys. Before KrbAsReqBuilder, all etypes
+ * use the same salt which is either the default one or a new salt
+ * coming from PA-DATA. After KrbAsReqBuilder, each etype uses its
+ * own new salt from PA-DATA. For an etype with no PA-DATA new salt
+ * at all, what salt should it use?
+ *
+ * Commonly, the stored keys are only to be used by an acceptor to
+ * decrypt service ticket in AP-REQ. Most impls only allow keys
+ * from a keytab on acceptor, but unfortunately (?) Java supports
+ * acceptor using password. In this case, if the service ticket is
+ * encrypted using an etype which we don't have PA-DATA new salt,
+ * using the default salt is normally wrong (say, case-insensitive
+ * user name). Instead, we would use the new salt of another etype.
+ */
+
+ String salt = null; // the saved new salt
+ for (int i=0; i<eTypes.length; i++) {
+ PAData.SaltAndParams snp =
+ PAData.getSaltAndParams(eTypes[i], paList);
+ // First round, only calculate those with new salt
+ if (snp.salt != null) {
+ salt = snp.salt;
+ result[i] = EncryptionKey.acquireSecretKey(password,
+ snp.salt,
+ eTypes[i],
+ snp.params);
+ }
+ }
+ if (salt == null) salt = cname.getSalt();
+ for (int i=0; i<eTypes.length; i++) {
+ // Second round, calculate those with no new salt
+ if (result[i] == null) {
+ PAData.SaltAndParams snp =
+ PAData.getSaltAndParams(eTypes[i], paList);
+ result[i] = EncryptionKey.acquireSecretKey(password,
+ salt,
+ eTypes[i],
+ snp.params);
+ }
+ }
+ return result;
+ }
+ }
+
+ /**
+ * Sets or clears options. If cleared, default options will be used
+ * at creation time.
+ * @param options
+ */
+ public void setOptions(KDCOptions options) {
+ checkState(State.INIT, "Cannot specify options");
+ this.options = options;
+ }
+
+ /**
+ * Sets or clears target. If cleared, KrbAsReq might choose krbtgt
+ * for cname realm
+ * @param sname
+ */
+ public void setTarget(PrincipalName sname) {
+ checkState(State.INIT, "Cannot specify target");
+ this.sname = sname;
+ }
+
+ /**
+ * Adds or clears addresses. KrbAsReq might add some if empty
+ * field not allowed
+ * @param addresses
+ */
+ public void setAddresses(HostAddresses addresses) {
+ checkState(State.INIT, "Cannot specify addresses");
+ this.addresses = addresses;
+ }
+
+ /**
+ * Build a KrbAsReq object from all info fed above. Normally this method
+ * will be called twice: initial AS-REQ and second with pakey
+ * @return the KrbAsReq object
+ * @throws KrbException
+ * @throws IOException
+ */
+ private KrbAsReq build() throws KrbException, IOException {
+ return new KrbAsReq(pakey,
+ options,
+ cname,
+ sname,
+ from,
+ till,
+ rtime,
+ eTypes,
+ addresses);
+ }
+
+ /**
+ * Parses AS-REP, decrypts enc-part, retrieves ticket and session key
+ * @throws KrbException
+ * @throws Asn1Exception
+ * @throws IOException
+ */
+ private KrbAsReqBuilder resolve() throws KrbException, Asn1Exception, IOException {
+ if (keys != null) {
+ rep.decryptUsingKeys(keys, req);
+ } else {
+ rep.decryptUsingPassword(password, req, cname);
+ }
+ if (rep.getPA() != null) {
+ if (paList == null || paList.length == 0) {
+ paList = rep.getPA();
+ } else {
+ int extraLen = rep.getPA().length;
+ if (extraLen > 0) {
+ int oldLen = paList.length;
+ paList = Arrays.copyOf(paList, paList.length + extraLen);
+ System.arraycopy(rep.getPA(), 0, paList, oldLen, extraLen);
+ }
+ }
+ }
+ return this;
+ }
+
+ /**
+ * Communication until AS-REP or non preauth-related KRB-ERROR received
+ * @throws KrbException
+ * @throws IOException
+ */
+ private KrbAsReqBuilder send() throws KrbException, IOException {
+ boolean preAuthFailedOnce = false;
+ KdcComm comm = new KdcComm(cname.getRealmAsString());
+ while (true) {
+ try {
+ req = build();
+ rep = new KrbAsRep(comm.send(req.encoding()));
+ return this;
+ } catch (KrbException ke) {
+ if (!preAuthFailedOnce && (
+ ke.returnCode() == Krb5.KDC_ERR_PREAUTH_FAILED ||
+ ke.returnCode() == Krb5.KDC_ERR_PREAUTH_REQUIRED)) {
+ if (Krb5.DEBUG) {
+ System.out.println("KrbAsReqBuilder: " +
+ "PREAUTH FAILED/REQ, re-send AS-REQ");
+ }
+ preAuthFailedOnce = true;
+ KRBError kerr = ke.getError();
+ if (password == null) {
+ pakey = EncryptionKey.findKey(kerr.getEType(), keys);
+ } else {
+ PAData.SaltAndParams snp = PAData.getSaltAndParams(
+ kerr.getEType(), kerr.getPA());
+ if (kerr.getEType() == 0) {
+ // Possible if PA-PW-SALT is in KRB-ERROR. RFC
+ // does not recommend this
+ pakey = EncryptionKey.acquireSecretKey(password,
+ snp.salt == null ? cname.getSalt() : snp.salt,
+ eTypes[0],
+ null);
+ } else {
+ pakey = EncryptionKey.acquireSecretKey(password,
+ snp.salt == null ? cname.getSalt() : snp.salt,
+ kerr.getEType(),
+ snp.params);
+ }
+ }
+ paList = kerr.getPA(); // Update current paList
+ } else {
+ throw ke;
+ }
+ }
+ }
+ }
+
+ /**
+ * Performs AS-REQ send and AS-REP receive.
+ * Maybe a state is needed here, to divide prepare process and getCreds.
+ * @throws KrbException
+ * @throws Asn1Exception
+ * @throws IOException
+ */
+ public KrbAsReqBuilder action()
+ throws KrbException, Asn1Exception, IOException {
+ checkState(State.INIT, "Cannot call action");
+ state = State.REQ_OK;
+ return send().resolve();
+ }
+
+ /**
+ * Gets Credentials object after action
+ */
+ public Credentials getCreds() {
+ checkState(State.REQ_OK, "Cannot retrieve creds");
+ return rep.getCreds();
+ }
+
+ /**
+ * Gets another type of Credentials after action
+ */
+ public sun.security.krb5.internal.ccache.Credentials getCCreds() {
+ checkState(State.REQ_OK, "Cannot retrieve CCreds");
+ return rep.getCCreds();
+ }
+
+ /**
+ * Destroys the object and clears keys and password info.
+ */
+ public void destroy() {
+ state = State.DESTROYED;
+ if (keys != null) {
+ for (EncryptionKey k: keys) {
+ k.destroy();
+ }
+ keys = null;
+ }
+ if (password != null) {
+ Arrays.fill(password, (char)0);
+ password = null;
+ }
+ }
+
+ /**
+ * Checks if the current state is the specified one.
+ * @param st the expected state
+ * @param msg error message if state is not correct
+ * @throws IllegalStateException if state is not correct
+ */
+ private void checkState(State st, String msg) {
+ if (state != st) {
+ throw new IllegalStateException(msg + " at " + st + " state");
+ }
+ }
+}
--- a/jdk/src/share/classes/sun/security/krb5/KrbKdcReq.java Fri Nov 12 07:15:47 2010 -0500
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,501 +0,0 @@
-/*
- * Copyright (c) 2000, 2010, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation. Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-/*
- *
- * (C) Copyright IBM Corp. 1999 All Rights Reserved.
- * Copyright 1997 The Open Group Research Institute. All rights reserved.
- */
-
-package sun.security.krb5;
-
-import java.security.AccessController;
-import java.security.PrivilegedAction;
-import java.security.Security;
-import java.util.Locale;
-import sun.security.krb5.internal.Krb5;
-import sun.security.krb5.internal.NetClient;
-import java.io.IOException;
-import java.net.SocketTimeoutException;
-import java.util.StringTokenizer;
-import java.security.AccessController;
-import java.security.PrivilegedExceptionAction;
-import java.security.PrivilegedActionException;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Set;
-import java.util.HashSet;
-
-public abstract class KrbKdcReq {
-
- // The following settings can be configured in [libdefaults]
- // section of krb5.conf, which are global for all realms. Each of
- // them can also be defined in a realm, which overrides value here.
-
- /**
- * max retry time for a single KDC, default Krb5.KDC_RETRY_LIMIT (3)
- */
- private static int defaultKdcRetryLimit;
- /**
- * timeout requesting a ticket from KDC, in millisec, default 30 sec
- */
- private static int defaultKdcTimeout;
- /**
- * max UDP packet size, default unlimited (-1)
- */
- private static int defaultUdpPrefLimit;
-
- private static final boolean DEBUG = Krb5.DEBUG;
-
- private static final String BAD_POLICY_KEY = "krb5.kdc.bad.policy";
-
- /**
- * What to do when a KDC is unavailable, specified in the
- * java.security file with key krb5.kdc.bad.policy.
- * Possible values can be TRY_LAST or TRY_LESS. Reloaded when refreshed.
- */
- private enum BpType {
- NONE, TRY_LAST, TRY_LESS
- }
- private static int tryLessMaxRetries = 1;
- private static int tryLessTimeout = 5000;
-
- private static BpType badPolicy;
-
- static {
- initStatic();
- }
-
- /**
- * Read global settings
- */
- public static void initStatic() {
- String value = AccessController.doPrivileged(
- new PrivilegedAction<String>() {
- public String run() {
- return Security.getProperty(BAD_POLICY_KEY);
- }
- });
- if (value != null) {
- value = value.toLowerCase(Locale.ENGLISH);
- String[] ss = value.split(":");
- if ("tryless".equals(ss[0])) {
- if (ss.length > 1) {
- String[] params = ss[1].split(",");
- try {
- int tmp0 = Integer.parseInt(params[0]);
- if (params.length > 1) {
- tryLessTimeout = Integer.parseInt(params[1]);
- }
- // Assign here in case of exception at params[1]
- tryLessMaxRetries = tmp0;
- } catch (NumberFormatException nfe) {
- // Ignored. Please note that tryLess is recognized and
- // used, parameters using default values
- if (DEBUG) {
- System.out.println("Invalid " + BAD_POLICY_KEY +
- " parameter for tryLess: " +
- value + ", use default");
- }
- }
- }
- badPolicy = BpType.TRY_LESS;
- } else if ("trylast".equals(ss[0])) {
- badPolicy = BpType.TRY_LAST;
- } else {
- badPolicy = BpType.NONE;
- }
- } else {
- badPolicy = BpType.NONE;
- }
-
-
- int timeout = -1;
- int max_retries = -1;
- int udf_pref_limit = -1;
-
- try {
- Config cfg = Config.getInstance();
- String temp = cfg.getDefault("kdc_timeout", "libdefaults");
- timeout = parsePositiveIntString(temp);
- temp = cfg.getDefault("max_retries", "libdefaults");
- max_retries = parsePositiveIntString(temp);
- temp = cfg.getDefault("udp_preference_limit", "libdefaults");
- udf_pref_limit = parsePositiveIntString(temp);
- } catch (Exception exc) {
- // ignore any exceptions; use default values
- if (DEBUG) {
- System.out.println ("Exception in getting KDC communication " +
- "settings, using default value " +
- exc.getMessage());
- }
- }
- defaultKdcTimeout = timeout > 0 ? timeout : 30*1000; // 30 seconds
- defaultKdcRetryLimit =
- max_retries > 0 ? max_retries : Krb5.KDC_RETRY_LIMIT;
- defaultUdpPrefLimit = udf_pref_limit;
-
- KdcAccessibility.reset();
- }
-
- protected byte[] obuf;
- protected byte[] ibuf;
-
- /**
- * Sends the provided data to the KDC of the specified realm.
- * Returns the response from the KDC.
- * Default realm/KDC is used if realm is null.
- * @param realm the realm of the KDC where data is to be sent.
- * @returns the kdc to which the AS request was sent to
- * @exception InterruptedIOException if timeout expires
- * @exception KrbException
- */
-
- public String send(String realm)
- throws IOException, KrbException {
- int udpPrefLimit = getRealmSpecificValue(
- realm, "udp_preference_limit", defaultUdpPrefLimit);
-
- boolean useTCP = (udpPrefLimit > 0 &&
- (obuf != null && obuf.length > udpPrefLimit));
-
- return (send(realm, useTCP));
- }
-
- public String send(String realm, boolean useTCP)
- throws IOException, KrbException {
-
- if (obuf == null)
- return null;
- Exception savedException = null;
- Config cfg = Config.getInstance();
-
- if (realm == null) {
- realm = cfg.getDefaultRealm();
- if (realm == null) {
- throw new KrbException(Krb5.KRB_ERR_GENERIC,
- "Cannot find default realm");
- }
- }
-
- String kdcList = cfg.getKDCList(realm);
- if (kdcList == null) {
- throw new KrbException("Cannot get kdc for realm " + realm);
- }
- String tempKdc = null; // may include the port number also
- for (String tmp: KdcAccessibility.list(kdcList)) {
- tempKdc = tmp;
- try {
- send(realm,tempKdc,useTCP);
- KdcAccessibility.removeBad(tempKdc);
- break;
- } catch (Exception e) {
- if (DEBUG) {
- System.out.println(">>> KrbKdcReq send: error trying " +
- tempKdc);
- e.printStackTrace(System.out);
- }
- KdcAccessibility.addBad(tempKdc);
- savedException = e;
- }
- }
- if (ibuf == null && savedException != null) {
- if (savedException instanceof IOException) {
- throw (IOException) savedException;
- } else {
- throw (KrbException) savedException;
- }
- }
- return tempKdc;
- }
-
- // send the AS Request to the specified KDC
-
- public void send(String realm, String tempKdc, boolean useTCP)
- throws IOException, KrbException {
-
- if (obuf == null)
- return;
-
- int port = Krb5.KDC_INET_DEFAULT_PORT;
- int retries = getRealmSpecificValue(
- realm, "max_retries", defaultKdcRetryLimit);
- int timeout = getRealmSpecificValue(
- realm, "kdc_timeout", defaultKdcTimeout);
- if (badPolicy == BpType.TRY_LESS &&
- KdcAccessibility.isBad(tempKdc)) {
- if (retries > tryLessMaxRetries) {
- retries = tryLessMaxRetries; // less retries
- }
- if (timeout > tryLessTimeout) {
- timeout = tryLessTimeout; // less time
- }
- }
-
- String kdc = null;
- String portStr = null;
-
- if (tempKdc.charAt(0) == '[') { // Explicit IPv6 in []
- int pos = tempKdc.indexOf(']', 1);
- if (pos == -1) {
- throw new IOException("Illegal KDC: " + tempKdc);
- }
- kdc = tempKdc.substring(1, pos);
- if (pos != tempKdc.length() - 1) { // with port number
- if (tempKdc.charAt(pos+1) != ':') {
- throw new IOException("Illegal KDC: " + tempKdc);
- }
- portStr = tempKdc.substring(pos+2);
- }
- } else {
- int colon = tempKdc.indexOf(':');
- if (colon == -1) { // Hostname or IPv4 host only
- kdc = tempKdc;
- } else {
- int nextColon = tempKdc.indexOf(':', colon+1);
- if (nextColon > 0) { // >=2 ":", IPv6 with no port
- kdc = tempKdc;
- } else { // 1 ":", hostname or IPv4 with port
- kdc = tempKdc.substring(0, colon);
- portStr = tempKdc.substring(colon+1);
- }
- }
- }
- if (portStr != null) {
- int tempPort = parsePositiveIntString(portStr);
- if (tempPort > 0)
- port = tempPort;
- }
-
- if (DEBUG) {
- System.out.println(">>> KrbKdcReq send: kdc=" + kdc
- + (useTCP ? " TCP:":" UDP:")
- + port + ", timeout="
- + timeout
- + ", number of retries ="
- + retries
- + ", #bytes=" + obuf.length);
- }
-
- KdcCommunication kdcCommunication =
- new KdcCommunication(kdc, port, useTCP, timeout, retries, obuf);
- try {
- ibuf = AccessController.doPrivileged(kdcCommunication);
- if (DEBUG) {
- System.out.println(">>> KrbKdcReq send: #bytes read="
- + (ibuf != null ? ibuf.length : 0));
- }
- } catch (PrivilegedActionException e) {
- Exception wrappedException = e.getException();
- if (wrappedException instanceof IOException) {
- throw (IOException) wrappedException;
- } else {
- throw (KrbException) wrappedException;
- }
- }
- if (DEBUG) {
- System.out.println(">>> KrbKdcReq send: #bytes read="
- + (ibuf != null ? ibuf.length : 0));
- }
- }
-
- private static class KdcCommunication
- implements PrivilegedExceptionAction<byte[]> {
-
- private String kdc;
- private int port;
- private boolean useTCP;
- private int timeout;
- private int retries;
- private byte[] obuf;
-
- public KdcCommunication(String kdc, int port, boolean useTCP,
- int timeout, int retries, byte[] obuf) {
- this.kdc = kdc;
- this.port = port;
- this.useTCP = useTCP;
- this.timeout = timeout;
- this.retries = retries;
- this.obuf = obuf;
- }
-
- // The caller only casts IOException and KrbException so don't
- // add any new ones!
-
- public byte[] run() throws IOException, KrbException {
-
- byte[] ibuf = null;
-
- for (int i=1; i <= retries; i++) {
- String proto = useTCP?"TCP":"UDP";
- NetClient kdcClient = NetClient.getInstance(
- proto, kdc, port, timeout);
- if (DEBUG) {
- System.out.println(">>> KDCCommunication: kdc=" + kdc
- + " " + proto + ":"
- + port + ", timeout="
- + timeout
- + ",Attempt =" + i
- + ", #bytes=" + obuf.length);
- }
- try {
- /*
- * Send the data to the kdc.
- */
- kdcClient.send(obuf);
- /*
- * And get a response.
- */
- ibuf = kdcClient.receive();
- break;
- } catch (SocketTimeoutException se) {
- if (DEBUG) {
- System.out.println ("SocketTimeOutException with " +
- "attempt: " + i);
- }
- if (i == retries) {
- ibuf = null;
- throw se;
- }
- } finally {
- kdcClient.close();
- }
- }
- return ibuf;
- }
- }
-
- /**
- * Returns krb5.conf setting of {@code key} for a specfic realm,
- * which can be:
- * 1. defined in the sub-stanza for the given realm inside [realms], or
- * 2. defined in [libdefaults], or
- * 3. defValue
- * @param realm the given realm in which the setting is requested. Returns
- * the global setting if null
- * @param key the key for the setting
- * @param defValue default value
- * @return a value for the key
- */
- private int getRealmSpecificValue(String realm, String key, int defValue) {
- int v = defValue;
-
- if (realm == null) return v;
-
- int temp = -1;
- try {
- String value =
- Config.getInstance().getDefault(key, realm);
- temp = parsePositiveIntString(value);
- } catch (Exception exc) {
- // Ignored, defValue will be picked up
- }
-
- if (temp > 0) v = temp;
-
- return v;
- }
-
- private static int parsePositiveIntString(String intString) {
- if (intString == null)
- return -1;
-
- int ret = -1;
-
- try {
- ret = Integer.parseInt(intString);
- } catch (Exception exc) {
- return -1;
- }
-
- if (ret >= 0)
- return ret;
-
- return -1;
- }
-
- /**
- * Maintains a KDC accessible list. Unavailable KDCs are put into a
- * blacklist, when a KDC in the blacklist is available, it's removed
- * from there. No insertion order in the blacklist.
- *
- * There are two methods to deal with KDCs in the blacklist. 1. Only try
- * them when there's no KDC not on the blacklist. 2. Still try them, but
- * with lesser number of retries and smaller timeout value.
- */
- static class KdcAccessibility {
- // Known bad KDCs
- private static Set<String> bads = new HashSet<String>();
-
- private static synchronized void addBad(String kdc) {
- if (DEBUG) {
- System.out.println(">>> KdcAccessibility: add " + kdc);
- }
- bads.add(kdc);
- }
-
- private static synchronized void removeBad(String kdc) {
- if (DEBUG) {
- System.out.println(">>> KdcAccessibility: remove " + kdc);
- }
- bads.remove(kdc);
- }
-
- private static synchronized boolean isBad(String kdc) {
- return bads.contains(kdc);
- }
-
- private static synchronized void reset() {
- if (DEBUG) {
- System.out.println(">>> KdcAccessibility: reset");
- }
- bads.clear();
- }
-
- // Returns a preferred KDC list by putting the bad ones at the end
- private static synchronized String[] list(String kdcList) {
- StringTokenizer st = new StringTokenizer(kdcList);
- List<String> list = new ArrayList<String>();
- if (badPolicy == BpType.TRY_LAST) {
- List<String> badkdcs = new ArrayList<String>();
- while (st.hasMoreTokens()) {
- String t = st.nextToken();
- if (bads.contains(t)) badkdcs.add(t);
- else list.add(t);
- }
- // Bad KDCs are put at last
- list.addAll(badkdcs);
- } else {
- // All KDCs are returned in their original order,
- // This include TRY_LESS and NONE
- while (st.hasMoreTokens()) {
- list.add(st.nextToken());
- }
- }
- return list.toArray(new String[list.size()]);
- }
- }
-}
-
--- a/jdk/src/share/classes/sun/security/krb5/KrbTgsReq.java Fri Nov 12 07:15:47 2010 -0500
+++ b/jdk/src/share/classes/sun/security/krb5/KrbTgsReq.java Fri Nov 12 21:33:14 2010 +0800
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2000, 2008, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2000, 2010, 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
@@ -31,20 +31,16 @@
package sun.security.krb5;
-import sun.security.util.*;
-import sun.security.krb5.EncryptionKey;
import sun.security.krb5.internal.*;
import sun.security.krb5.internal.crypto.*;
import java.io.IOException;
import java.net.UnknownHostException;
-import java.util.StringTokenizer;
-import java.io.InterruptedIOException;
/**
* This class encapsulates a Kerberos TGS-REQ that is sent from the
* client to the KDC.
*/
-public class KrbTgsReq extends KrbKdcReq {
+public class KrbTgsReq {
private PrincipalName princName;
private PrincipalName servName;
@@ -56,7 +52,8 @@
private static final boolean DEBUG = Krb5.DEBUG;
- private int defaultTimeout = 30*1000; // 30 seconds
+ private byte[] obuf;
+ private byte[] ibuf;
// Used in CredentialsUtil
public KrbTgsReq(Credentials asCreds,
@@ -182,11 +179,12 @@
* @throws KrbException
* @throws IOException
*/
- public String send() throws IOException, KrbException {
+ public void send() throws IOException, KrbException {
String realmStr = null;
if (servName != null)
realmStr = servName.getRealmString();
- return (send(realmStr));
+ KdcComm comm = new KdcComm(realmStr);
+ ibuf = comm.send(obuf);
}
public KrbTgsRep getReply()
@@ -201,18 +199,8 @@
public Credentials sendAndGetCreds() throws IOException, KrbException {
KrbTgsRep tgs_rep = null;
String kdc = null;
- try {
- kdc = send();
- tgs_rep = getReply();
- } catch (KrbException ke) {
- if (ke.returnCode() == Krb5.KRB_ERR_RESPONSE_TOO_BIG) {
- // set useTCP and retry
- send(servName.getRealmString(), kdc, true);
- tgs_rep = getReply();
- } else {
- throw ke;
- }
- }
+ send();
+ tgs_rep = getReply();
return tgs_rep.getCreds();
}
@@ -240,7 +228,7 @@
UnknownHostException, KrbCryptoException {
KerberosTime req_till = null;
if (till == null) {
- req_till = new KerberosTime();
+ req_till = new KerberosTime(0);
} else {
req_till = till;
}
--- a/jdk/src/share/classes/sun/security/krb5/PrincipalName.java Fri Nov 12 07:15:47 2010 -0500
+++ b/jdk/src/share/classes/sun/security/krb5/PrincipalName.java Fri Nov 12 21:33:14 2010 +0800
@@ -511,10 +511,6 @@
return salt;
}
- public void setSalt(String salt) {
- this.salt = salt;
- }
-
public String toString() {
StringBuffer str = new StringBuffer();
for (int i = 0; i < nameStrings.length; i++) {
--- a/jdk/src/share/classes/sun/security/krb5/internal/KDCRep.java Fri Nov 12 07:15:47 2010 -0500
+++ b/jdk/src/share/classes/sun/security/krb5/internal/KDCRep.java Fri Nov 12 21:33:14 2010 +0800
@@ -32,7 +32,6 @@
import sun.security.krb5.*;
import sun.security.util.*;
-import java.util.Vector;
import java.io.IOException;
import java.math.BigInteger;
@@ -69,7 +68,7 @@
public EncKDCRepPart encKDCRepPart; //not part of ASN.1 encoding
private int pvno;
private int msgType;
- private PAData[] pAData = null; //optional
+ public PAData[] pAData = null; //optional
private boolean DEBUG = Krb5.DEBUG;
public KDCRep(
--- a/jdk/src/share/classes/sun/security/krb5/internal/KRBError.java Fri Nov 12 07:15:47 2010 -0500
+++ b/jdk/src/share/classes/sun/security/krb5/internal/KRBError.java Fri Nov 12 21:33:14 2010 +0800
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2000, 2009, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2000, 2010, 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
@@ -41,7 +41,9 @@
import java.io.IOException;
import java.io.ObjectInputStream;
import java.math.BigInteger;
+import java.util.ArrayList;
import java.util.Arrays;
+import java.util.List;
import sun.security.krb5.internal.util.KerberosString;
/**
* Implements the ASN.1 KRBError type.
@@ -96,10 +98,8 @@
private byte[] eData; //optional
private Checksum eCksum; //optional
- // pre-auth info
- private int etype = 0;
- private String salt = null;
- private byte[] s2kparams = null;
+ private PAData[] pa; // PA-DATA in eData
+ private int pa_eType; // The 1st etype appeared in salt-related PAData
private static boolean DEBUG = Krb5.DEBUG;
@@ -260,10 +260,12 @@
private void parsePAData(byte[] data)
throws IOException, Asn1Exception {
DerValue derPA = new DerValue(data);
+ List<PAData> paList = new ArrayList<PAData>();
while (derPA.data.available() > 0) {
// read the PA-DATA
DerValue tmp = derPA.data.getDerValue();
PAData pa_data = new PAData(tmp);
+ paList.add(pa_data);
int pa_type = pa_data.getType();
byte[] pa_value = pa_data.getValue();
if (DEBUG) {
@@ -280,24 +282,13 @@
case Krb5.PA_ETYPE_INFO:
if (pa_value != null) {
DerValue der = new DerValue(pa_value);
- DerValue value = der.data.getDerValue();
- ETypeInfo info = new ETypeInfo(value);
- etype = info.getEType();
- salt = info.getSalt();
- if (DEBUG) {
- System.out.println("\t PA-ETYPE-INFO etype = " + etype);
- System.out.println("\t PA-ETYPE-INFO salt = " + salt);
- }
while (der.data.available() > 0) {
- value = der.data.getDerValue();
- info = new ETypeInfo(value);
+ DerValue value = der.data.getDerValue();
+ ETypeInfo info = new ETypeInfo(value);
+ if (pa_eType == 0) pa_eType = info.getEType();
if (DEBUG) {
- etype = info.getEType();
- System.out.println("\t salt for " + etype
- + " is " + info.getSalt());
- }
- if (salt == null || salt.isEmpty()) {
- salt = info.getSalt();
+ System.out.println("\t PA-ETYPE-INFO etype = " + info.getEType());
+ System.out.println("\t PA-ETYPE-INFO salt = " + info.getSalt());
}
}
}
@@ -305,25 +296,13 @@
case Krb5.PA_ETYPE_INFO2:
if (pa_value != null) {
DerValue der = new DerValue(pa_value);
- DerValue value = der.data.getDerValue();
- ETypeInfo2 info2 = new ETypeInfo2(value);
- etype = info2.getEType();
- salt = info2.getSalt();
- s2kparams = info2.getParams();
- if (DEBUG) {
- System.out.println("\t PA-ETYPE-INFO2 etype = " + etype);
- System.out.println("\t PA-ETYPE-INFO salt = " + salt);
- }
while (der.data.available() > 0) {
- value = der.data.getDerValue();
- info2 = new ETypeInfo2(value);
+ DerValue value = der.data.getDerValue();
+ ETypeInfo2 info2 = new ETypeInfo2(value);
+ if (pa_eType == 0) pa_eType = info2.getEType();
if (DEBUG) {
- etype = info2.getEType();
- System.out.println("\t salt for " + etype
- + " is " + info2.getSalt());
- }
- if (salt == null || salt.isEmpty()) {
- salt = info2.getSalt();
+ System.out.println("\t PA-ETYPE-INFO2 etype = " + info2.getEType());
+ System.out.println("\t PA-ETYPE-INFO2 salt = " + info2.getSalt());
}
}
}
@@ -333,6 +312,7 @@
break;
}
}
+ pa = paList.toArray(new PAData[paList.size()]);
}
public final KerberosTime getServerTime() {
@@ -356,18 +336,12 @@
}
// access pre-auth info
- public final int getEType() {
- return etype;
+ public final PAData[] getPA() {
+ return pa;
}
- // access pre-auth info
- public final String getSalt() {
- return salt;
- }
-
- // access pre-auth info
- public final byte[] getParams() {
- return ((s2kparams == null) ? null : s2kparams.clone());
+ public final int getEType() {
+ return pa_eType;
}
public final String getErrorString() {
--- a/jdk/src/share/classes/sun/security/krb5/internal/KerberosTime.java Fri Nov 12 07:15:47 2010 -0500
+++ b/jdk/src/share/classes/sun/security/krb5/internal/KerberosTime.java Fri Nov 12 21:33:14 2010 +0800
@@ -77,11 +77,6 @@
public static final boolean NOW = true;
public static final boolean UNADJUSTED_NOW = false;
- //defaults to zero instead of now; use setNow() for current time
- public KerberosTime() {
- kerberosTime = 0;
- }
-
public KerberosTime(long time) {
kerberosTime = time;
}
--- a/jdk/src/share/classes/sun/security/krb5/internal/PAData.java Fri Nov 12 07:15:47 2010 -0500
+++ b/jdk/src/share/classes/sun/security/krb5/internal/PAData.java Fri Nov 12 21:33:14 2010 +0800
@@ -30,9 +30,11 @@
package sun.security.krb5.internal;
+import sun.security.krb5.KrbException;
import sun.security.util.*;
import sun.security.krb5.Asn1Exception;
import java.io.IOException;
+import sun.security.krb5.internal.util.KerberosString;
/**
* Implements the ASN.1 PA-DATA type.
@@ -135,4 +137,75 @@
public byte[] getValue() {
return ((pADataValue == null) ? null : pADataValue.clone());
}
+
+ /**
+ * A place to store a pair of salt and s2kparams.
+ * An empty salt is changed to null, to be interopable
+ * with Windows 2000 server.
+ */
+ public static class SaltAndParams {
+ public final String salt;
+ public final byte[] params;
+ public SaltAndParams(String s, byte[] p) {
+ if (s != null && s.isEmpty()) s = null;
+ this.salt = s;
+ this.params = p;
+ }
+ }
+
+ /**
+ * Fetches salt and s2kparams value for eType in a series of PA-DATAs.
+ * The preference order is PA-ETYPE-INFO2 > PA-ETYPE-INFO > PA-PW-SALT.
+ * If multiple PA-DATA for the same etype appears, use the last one.
+ * (This is useful when PA-DATAs from KRB-ERROR and AS-REP are combined).
+ * @return salt and s2kparams. never null, its field might be null.
+ */
+ public static SaltAndParams getSaltAndParams(int eType, PAData[] pas)
+ throws Asn1Exception, KrbException {
+
+ if (pas == null || pas.length == 0) {
+ return new SaltAndParams(null, null);
+ }
+
+ String paPwSalt = null;
+ ETypeInfo2 info2 = null;
+ ETypeInfo info = null;
+
+ for (PAData p: pas) {
+ if (p.getValue() != null) {
+ try {
+ switch (p.getType()) {
+ case Krb5.PA_PW_SALT:
+ paPwSalt = new String(p.getValue(),
+ KerberosString.MSNAME?"UTF8":"8859_1");
+ break;
+ case Krb5.PA_ETYPE_INFO:
+ DerValue der = new DerValue(p.getValue());
+ while (der.data.available() > 0) {
+ DerValue value = der.data.getDerValue();
+ ETypeInfo tmp = new ETypeInfo(value);
+ if (tmp.getEType() == eType) info = tmp;
+ }
+ break;
+ case Krb5.PA_ETYPE_INFO2:
+ der = new DerValue(p.getValue());
+ while (der.data.available() > 0) {
+ DerValue value = der.data.getDerValue();
+ ETypeInfo2 tmp = new ETypeInfo2(value);
+ if (tmp.getEType() == eType) info2 = tmp;
+ }
+ break;
+ }
+ } catch (IOException ioe) {
+ // Ignored
+ }
+ }
+ }
+ if (info2 != null) {
+ return new SaltAndParams(info2.getSalt(), info2.getParams());
+ } else if (info != null) {
+ return new SaltAndParams(info.getSalt(), null);
+ }
+ return new SaltAndParams(paPwSalt, null);
+ }
}
--- a/jdk/src/windows/classes/sun/security/krb5/internal/tools/Kinit.java Fri Nov 12 07:15:47 2010 -0500
+++ b/jdk/src/windows/classes/sun/security/krb5/internal/tools/Kinit.java Fri Nov 12 21:33:14 2010 +0800
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2000, 2009, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2000, 2010, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@@ -33,11 +33,7 @@
import sun.security.krb5.*;
import sun.security.krb5.internal.*;
import sun.security.krb5.internal.ccache.*;
-import java.io.BufferedReader;
-import java.io.InputStreamReader;
import java.io.IOException;
-import java.util.StringTokenizer;
-import java.io.File;
import java.util.Arrays;
import sun.security.util.Password;
@@ -152,6 +148,7 @@
if (principal != null) {
princName = principal.toString();
}
+ KrbAsReqBuilder builder;
if (DEBUG) {
System.out.println("Principal is " + principal);
}
@@ -172,6 +169,7 @@
new String(psswd));
}
}
+ builder = new KrbAsReqBuilder(principal, psswd);
} else {
if (DEBUG) {
System.out.println(">>> Kinit using keytab");
@@ -198,11 +196,13 @@
}
throw new KrbException(msg);
}
+ builder = new KrbAsReqBuilder(principal, skeys);
}
KDCOptions opt = new KDCOptions();
setOptions(KDCOptions.FORWARDABLE, options.forwardable, opt);
setOptions(KDCOptions.PROXIABLE, options.proxiable, opt);
+ builder.setOptions(opt);
String realm = options.getKDCRealm();
if (realm == null) {
realm = Config.getInstance().getDefaultRealm();
@@ -215,62 +215,21 @@
PrincipalName sname = new PrincipalName("krbtgt" + "/" + realm,
PrincipalName.KRB_NT_SRV_INST);
sname.setRealm(realm);
+ builder.setTarget(sname);
if (DEBUG) {
System.out.println(">>> Creating KrbAsReq");
}
- KrbAsReq as_req = null;
- HostAddresses addresses = null;
- try {
- if (options.getAddressOption())
- addresses = HostAddresses.getLocalAddresses();
-
- if (useKeytab) {
- as_req = new KrbAsReq(skeys, opt,
- principal, sname,
- null, null, null, null, addresses, null);
- } else {
- as_req = new KrbAsReq(psswd, opt,
- principal, sname,
- null, null, null, null, addresses, null);
- }
- } catch (KrbException exc) {
- throw exc;
- } catch (Exception exc) {
- throw new KrbException(exc.toString());
- }
+ if (options.getAddressOption())
+ builder.setAddresses(HostAddresses.getLocalAddresses());
- KrbAsRep as_rep = null;
- try {
- as_rep = sendASRequest(as_req, useKeytab, realm, psswd, skeys);
- } catch (KrbException ke) {
- if ((ke.returnCode() == Krb5.KDC_ERR_PREAUTH_FAILED) ||
- (ke.returnCode() == Krb5.KDC_ERR_PREAUTH_REQUIRED)) {
- if (DEBUG) {
- System.out.println("Kinit: PREAUTH FAILED/REQ, re-send AS-REQ");
- }
- KRBError error = ke.getError();
- int etype = error.getEType();
- String salt = error.getSalt();
- byte[] s2kparams = error.getParams();
- if (useKeytab) {
- as_req = new KrbAsReq(skeys, true, etype, salt,
- s2kparams, opt, principal, sname,
- null, null, null, null, addresses, null);
- } else {
- as_req = new KrbAsReq(psswd, true, etype, salt,
- s2kparams, opt, principal, sname,
- null, null, null, null, addresses, null);
- }
- as_rep = sendASRequest(as_req, useKeytab, realm, psswd, skeys);
- } else {
- throw ke;
- }
- }
+ builder.action();
sun.security.krb5.internal.ccache.Credentials credentials =
- as_rep.setCredentials();
+ builder.getCCreds();
+ builder.destroy();
+
// we always create a new cache and store the ticket we get
CredentialsCache cache =
CredentialsCache.create(principal, options.cachename);
@@ -296,41 +255,6 @@
options = null; // release reference to options
}
- private static KrbAsRep sendASRequest(KrbAsReq as_req, boolean useKeytab,
- String realm, char[] passwd, EncryptionKey[] skeys)
- throws IOException, RealmException, KrbException {
-
- if (DEBUG) {
- System.out.println(">>> Kinit: sending as_req to realm " + realm);
- }
-
- String kdc = as_req.send(realm);
-
- if (DEBUG) {
- System.out.println(">>> reading response from kdc");
- }
- KrbAsRep as_rep = null;
- try {
- if (useKeytab) {
- as_rep = as_req.getReply(skeys);
- } else {
- as_rep = as_req.getReply(passwd);
- }
- } catch (KrbException ke) {
- if (ke.returnCode() == Krb5.KRB_ERR_RESPONSE_TOO_BIG) {
- as_req.send(realm, kdc, true); // useTCP is set
- if (useKeytab) {
- as_rep = as_req.getReply(skeys);
- } else {
- as_rep = as_req.getReply(passwd);
- }
- } else {
- throw ke;
- }
- }
- return as_rep;
- }
-
private static void setOptions(int flag, int option, KDCOptions opt) {
switch (option) {
case 0:
--- a/jdk/test/sun/security/krb5/auto/KDC.java Fri Nov 12 07:15:47 2010 -0500
+++ b/jdk/test/sun/security/krb5/auto/KDC.java Fri Nov 12 21:33:14 2010 +0800
@@ -35,7 +35,6 @@
import sun.security.krb5.*;
import sun.security.krb5.internal.*;
import sun.security.krb5.internal.ccache.CredentialsCache;
-import sun.security.krb5.internal.crypto.EType;
import sun.security.krb5.internal.crypto.KeyUsage;
import sun.security.krb5.internal.ktab.KeyTab;
import sun.security.util.DerInputStream;
@@ -129,8 +128,13 @@
// The random generator to generate random keys (including session keys)
private static SecureRandom secureRandom = new SecureRandom();
- // Principal db. principal -> pass
- private Map<String,char[]> passwords = new HashMap<String,char[]>();
+
+ // Principal db. principal -> pass. A case-insensitive TreeMap is used
+ // so that even if the client provides a name with different case, the KDC
+ // can still locate the principal and give back correct salt.
+ private TreeMap<String,char[]> passwords = new TreeMap<String,char[]>
+ (String.CASE_INSENSITIVE_ORDER);
+
// Realm name
private String realm;
// KDC
@@ -159,9 +163,13 @@
*/
ONLY_RC4_TGT,
/**
- * Only use RC4 in preauth, enc-type still using eTypes[0]
+ * Use RC4 as the first in preauth
*/
- ONLY_RC4_PREAUTH,
+ RC4_FIRST_PREAUTH,
+ /**
+ * Use only one preauth, so that some keys are not easy to generate
+ */
+ ONLY_ONE_PREAUTH,
};
static {
@@ -191,6 +199,12 @@
return create(realm, "kdc." + realm.toLowerCase(), 0, true);
}
+ public static KDC existing(String realm, String kdc, int port) {
+ KDC k = new KDC(realm, kdc);
+ k.port = port;
+ return k;
+ }
+
/**
* Creates and starts a KDC server.
* @param realm the realm name
@@ -471,7 +485,18 @@
* @return the salt
*/
private String getSalt(PrincipalName p) {
- String[] ns = p.getNameStrings();
+ String pn = p.toString();
+ if (p.getRealmString() == null) {
+ pn = pn + "@" + getRealm();
+ }
+ if (passwords.containsKey(pn)) {
+ try {
+ // Find the principal name with correct case.
+ p = new PrincipalName(passwords.ceilingEntry(pn).getKey());
+ } catch (RealmException re) {
+ // Won't happen
+ }
+ }
String s = p.getRealmString();
if (s == null) s = getRealm();
for (String n: p.getNameStrings()) {
@@ -493,8 +518,6 @@
try {
// Do not call EncryptionKey.acquireSecretKeys(), otherwise
// the krb5.conf config file would be loaded.
- Method stringToKey = EncryptionKey.class.getDeclaredMethod("stringToKey", char[].class, String.class, byte[].class, Integer.TYPE);
- stringToKey.setAccessible(true);
Integer kvno = null;
// For service whose password ending with a number, use it as kvno.
// Kvno must be postive.
@@ -504,12 +527,9 @@
kvno = pass[pass.length-1] - '0';
}
}
- return new EncryptionKey((byte[]) stringToKey.invoke(
- null, getPassword(p, server), getSalt(p), null, etype),
+ return new EncryptionKey(EncryptionKeyDotStringToKey(
+ getPassword(p, server), getSalt(p), null, etype),
etype, kvno);
- } catch (InvocationTargetException ex) {
- KrbException ke = (KrbException)ex.getCause();
- throw ke;
} catch (KrbException ke) {
throw ke;
} catch (Exception e) {
@@ -590,12 +610,11 @@
" sends TGS-REQ for " +
tgsReq.reqBody.sname);
KDCReqBody body = tgsReq.reqBody;
- int etype = 0;
+ int[] eTypes = KDCReqBodyDotEType(body);
+ int e2 = eTypes[0]; // etype for outgoing session key
+ int e3 = eTypes[0]; // etype for outgoing ticket
- // Reflection: PAData[] pas = tgsReq.pAData;
- Field f = KDCReq.class.getDeclaredField("pAData");
- f.setAccessible(true);
- PAData[] pas = (PAData[])f.get(tgsReq);
+ PAData[] pas = kDCReqDotPAData(tgsReq);
Ticket tkt = null;
EncTicketPart etp = null;
@@ -607,9 +626,9 @@
APReq apReq = new APReq(pa.getValue());
EncryptedData ed = apReq.authenticator;
tkt = apReq.ticket;
- etype = tkt.encPart.getEType();
+ int te = tkt.encPart.getEType();
tkt.sname.setRealm(tkt.realm);
- EncryptionKey kkey = keyForUser(tkt.sname, etype, true);
+ EncryptionKey kkey = keyForUser(tkt.sname, te, true);
byte[] bb = tkt.encPart.decrypt(kkey, KeyUsage.KU_TICKET);
DerInputStream derIn = new DerInputStream(bb);
DerValue der = derIn.getDerValue();
@@ -620,16 +639,12 @@
throw new KrbException(Krb5.KDC_ERR_PADATA_TYPE_NOSUPP);
}
}
- EncryptionKey skey = keyForUser(body.sname, etype, true);
- if (skey == null) {
- throw new KrbException(Krb5.KDC_ERR_SUMTYPE_NOSUPP); // TODO
- }
// Session key for original ticket, TGT
EncryptionKey ckey = etp.key;
// Session key for session with the service
- EncryptionKey key = generateRandomKey(etype);
+ EncryptionKey key = generateRandomKey(e2);
// Check time, TODO
KerberosTime till = body.till;
@@ -678,6 +693,10 @@
till, body.rtime,
body.addresses,
null);
+ EncryptionKey skey = keyForUser(body.sname, e3, true);
+ if (skey == null) {
+ throw new KrbException(Krb5.KDC_ERR_SUMTYPE_NOSUPP); // TODO
+ }
Ticket t = new Ticket(
body.crealm,
body.sname,
@@ -741,17 +760,17 @@
private byte[] processAsReq(byte[] in) throws Exception {
ASReq asReq = new ASReq(in);
int[] eTypes = null;
+ List<PAData> outPAs = new ArrayList<PAData>();
+
try {
System.out.println(realm + "> " + asReq.reqBody.cname +
" sends AS-REQ for " +
asReq.reqBody.sname);
KDCReqBody body = asReq.reqBody;
+ body.cname.setRealm(getRealm());
- // Reflection: int[] eType = body.eType;
- Field f = KDCReqBody.class.getDeclaredField("eType");
- f.setAccessible(true);
- eTypes = (int[])f.get(body);
+ eTypes = KDCReqBodyDotEType(body);
int eType = eTypes[0];
EncryptionKey ckey = keyForUser(body.cname, eType, false);
@@ -807,19 +826,63 @@
}
bFlags[Krb5.TKT_OPTS_INITIAL] = true;
- f = KDCReq.class.getDeclaredField("pAData");
- f.setAccessible(true);
- PAData[] pas = (PAData[])f.get(asReq);
- if (pas == null || pas.length == 0) {
+ // Creating PA-DATA
+ int[] epas = eTypes;
+ if (options.containsKey(KDC.Option.RC4_FIRST_PREAUTH)) {
+ for (int i=1; i<epas.length; i++) {
+ if (epas[i] == EncryptedData.ETYPE_ARCFOUR_HMAC) {
+ epas[i] = epas[0];
+ epas[0] = EncryptedData.ETYPE_ARCFOUR_HMAC;
+ break;
+ }
+ };
+ } else if (options.containsKey(KDC.Option.ONLY_ONE_PREAUTH)) {
+ epas = new int[] { eTypes[0] };
+ }
+
+ DerValue[] pas = new DerValue[epas.length];
+ for (int i=0; i<epas.length; i++) {
+ pas[i] = new DerValue(new ETypeInfo2(
+ epas[i],
+ epas[i] == EncryptedData.ETYPE_ARCFOUR_HMAC ?
+ null : getSalt(body.cname),
+ null).asn1Encode());
+ }
+ DerOutputStream eid = new DerOutputStream();
+ eid.putSequence(pas);
+
+ outPAs.add(new PAData(Krb5.PA_ETYPE_INFO2, eid.toByteArray()));
+
+ boolean allOld = true;
+ for (int i: eTypes) {
+ if (i == EncryptedData.ETYPE_AES128_CTS_HMAC_SHA1_96 ||
+ i == EncryptedData.ETYPE_AES256_CTS_HMAC_SHA1_96) {
+ allOld = false;
+ break;
+ }
+ }
+ if (allOld) {
+ for (int i=0; i<epas.length; i++) {
+ pas[i] = new DerValue(new ETypeInfo(
+ epas[i],
+ epas[i] == EncryptedData.ETYPE_ARCFOUR_HMAC ?
+ null : getSalt(body.cname)
+ ).asn1Encode());
+ }
+ eid = new DerOutputStream();
+ eid.putSequence(pas);
+ outPAs.add(new PAData(Krb5.PA_ETYPE_INFO, eid.toByteArray()));
+ }
+
+ PAData[] inPAs = kDCReqDotPAData(asReq);
+ if (inPAs == null || inPAs.length == 0) {
Object preauth = options.get(Option.PREAUTH_REQUIRED);
if (preauth == null || preauth.equals(Boolean.TRUE)) {
throw new KrbException(Krb5.KDC_ERR_PREAUTH_REQUIRED);
}
} else {
try {
- Constructor<EncryptedData> ctor = EncryptedData.class.getDeclaredConstructor(DerValue.class);
- ctor.setAccessible(true);
- EncryptedData data = ctor.newInstance(new DerValue(pas[0].getValue()));
+ EncryptedData data = newEncryptedData(new DerValue(inPAs[0].getValue()));
EncryptionKey pakey = keyForUser(body.cname, data.getEType(), false);
data.decrypt(pakey, KeyUsage.KU_PA_ENC_TS);
} catch (Exception e) {
@@ -862,7 +925,8 @@
body.addresses
);
EncryptedData edata = new EncryptedData(ckey, enc_part.asn1Encode(), KeyUsage.KU_ENC_AS_REP_PART);
- ASRep asRep = new ASRep(null,
+ ASRep asRep = new ASRep(
+ outPAs.toArray(new PAData[outPAs.size()]),
body.crealm,
body.cname,
t,
@@ -907,36 +971,10 @@
if (kerr == null) {
if (ke.returnCode() == Krb5.KDC_ERR_PREAUTH_REQUIRED ||
ke.returnCode() == Krb5.KDC_ERR_PREAUTH_FAILED) {
- PAData pa;
-
- int epa = eTypes[0];
- if (options.containsKey(KDC.Option.ONLY_RC4_PREAUTH)) {
- epa = EncryptedData.ETYPE_ARCFOUR_HMAC;
- }
- ETypeInfo2 ei2 = new ETypeInfo2(epa, null, null);
- DerOutputStream eid = new DerOutputStream();
- eid.write(DerValue.tag_Sequence, ei2.asn1Encode());
-
- pa = new PAData(Krb5.PA_ETYPE_INFO2, eid.toByteArray());
-
DerOutputStream bytes = new DerOutputStream();
bytes.write(new PAData(Krb5.PA_ENC_TIMESTAMP, new byte[0]).asn1Encode());
- bytes.write(pa.asn1Encode());
-
- boolean allOld = true;
- for (int i: eTypes) {
- if (i == EncryptedData.ETYPE_AES128_CTS_HMAC_SHA1_96 ||
- i == EncryptedData.ETYPE_AES256_CTS_HMAC_SHA1_96) {
- allOld = false;
- break;
- }
- }
- if (allOld) {
- ETypeInfo ei = new ETypeInfo(epa, null);
- eid = new DerOutputStream();
- eid.write(DerValue.tag_Sequence, ei.asn1Encode());
- pa = new PAData(Krb5.PA_ETYPE_INFO, eid.toByteArray());
- bytes.write(pa.asn1Encode());
+ for (PAData p: outPAs) {
+ bytes.write(p.asn1Encode());
}
DerOutputStream temp = new DerOutputStream();
temp.write(DerValue.tag_Sequence, bytes);
@@ -1146,4 +1184,61 @@
return "ns";
}
}
+
+ // Calling private methods thru reflections
+ private static final Field getPADataField;
+ private static final Field getEType;
+ private static final Constructor<EncryptedData> ctorEncryptedData;
+ private static final Method stringToKey;
+
+ static {
+ try {
+ ctorEncryptedData = EncryptedData.class.getDeclaredConstructor(DerValue.class);
+ ctorEncryptedData.setAccessible(true);
+ getPADataField = KDCReq.class.getDeclaredField("pAData");
+ getPADataField.setAccessible(true);
+ getEType = KDCReqBody.class.getDeclaredField("eType");
+ getEType.setAccessible(true);
+ stringToKey = EncryptionKey.class.getDeclaredMethod(
+ "stringToKey",
+ char[].class, String.class, byte[].class, Integer.TYPE);
+ stringToKey.setAccessible(true);
+ } catch (NoSuchFieldException nsfe) {
+ throw new AssertionError(nsfe);
+ } catch (NoSuchMethodException nsme) {
+ throw new AssertionError(nsme);
+ }
+ }
+ private EncryptedData newEncryptedData(DerValue der) {
+ try {
+ return ctorEncryptedData.newInstance(der);
+ } catch (Exception e) {
+ throw new AssertionError(e);
+ }
+ }
+ private static PAData[] kDCReqDotPAData(KDCReq req) {
+ try {
+ return (PAData[])getPADataField.get(req);
+ } catch (Exception e) {
+ throw new AssertionError(e);
+ }
+ }
+ private static int[] KDCReqBodyDotEType(KDCReqBody body) {
+ try {
+ return (int[]) getEType.get(body);
+ } catch (Exception e) {
+ throw new AssertionError(e);
+ }
+ }
+ private static byte[] EncryptionKeyDotStringToKey(char[] password, String salt,
+ byte[] s2kparams, int keyType) throws KrbCryptoException {
+ try {
+ return (byte[])stringToKey.invoke(
+ null, password, salt, s2kparams, keyType);
+ } catch (InvocationTargetException ex) {
+ throw (KrbCryptoException)ex.getCause();
+ } catch (Exception e) {
+ throw new AssertionError(e);
+ }
+ }
}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/sun/security/krb5/auto/NewSalt.java Fri Nov 12 21:33:14 2010 +0800
@@ -0,0 +1,64 @@
+/*
+ * Copyright (c) 2010, 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 6960894
+ * @summary Better AS-REQ creation and processing
+ * @run main NewSalt
+ * @run main/othervm -Dnopreauth NewSalt
+ * @run main/othervm -Donlyonepreauth NewSalt
+ */
+
+import sun.security.jgss.GSSUtil;
+import sun.security.krb5.Config;
+
+public class NewSalt {
+
+ public static void main(String[] args)
+ throws Exception {
+
+ // Create and start the KDC
+ KDC kdc = new OneKDC(null);
+ if (System.getProperty("onlyonepreauth") != null) {
+ KDC.saveConfig(OneKDC.KRB5_CONF, kdc,
+ "default_tgs_enctypes=des3-cbc-sha1");
+ Config.refresh();
+ kdc.setOption(KDC.Option.ONLY_ONE_PREAUTH, true);
+ }
+ if (System.getProperty("nopreauth") != null) {
+ kdc.setOption(KDC.Option.PREAUTH_REQUIRED, false);
+ }
+
+ // Use a different case of name. KDC will return correct salt
+ Context c1 = Context.fromUserPass(OneKDC.USER.toUpperCase(),
+ OneKDC.PASS, true);
+ Context c2 = Context.fromUserPass(OneKDC.USER2.toUpperCase(),
+ OneKDC.PASS2, true);
+
+ c1.startAsClient(OneKDC.USER2, GSSUtil.GSS_KRB5_MECH_OID);
+ c2.startAsServer(GSSUtil.GSS_KRB5_MECH_OID);
+
+ Context.handshake(c1, c2);
+ }
+}
--- a/jdk/test/sun/security/krb5/auto/OneKDC.java Fri Nov 12 07:15:47 2010 -0500
+++ b/jdk/test/sun/security/krb5/auto/OneKDC.java Fri Nov 12 21:33:14 2010 +0800
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2008, 2009, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2008, 2010, 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
@@ -46,6 +46,8 @@
public static final String USER = "dummy";
public static final char[] PASS = "bogus".toCharArray();
+ public static final String USER2 = "foo";
+ public static final char[] PASS2 = "bar".toCharArray();
public static final String KRB5_CONF = "localkdc-krb5.conf";
public static final String KTAB = "localkdc.ktab";
public static final String JAAS_CONF = "localkdc-jaas.conf";
@@ -61,6 +63,7 @@
public OneKDC(String etype) throws Exception {
super(REALM, KDCHOST, 0, true);
addPrincipal(USER, PASS);
+ addPrincipal(USER2, PASS2);
addPrincipalRandKey("krbtgt/" + REALM);
addPrincipalRandKey(SERVER);
addPrincipalRandKey(BACKEND);
--- a/jdk/test/sun/security/krb5/auto/W83.java Fri Nov 12 07:15:47 2010 -0500
+++ b/jdk/test/sun/security/krb5/auto/W83.java Fri Nov 12 21:33:14 2010 +0800
@@ -26,6 +26,8 @@
* @bug 6932525 6951366 6959292
* @summary kerberos login failure on win2008 with AD set to win2000 compat mode
* and cannot login if session key and preauth does not use the same etype
+ * @run main/othervm -D6932525 W83
+ * @run main/othervm -D6959292 W83
*/
import com.sun.security.auth.module.Krb5LoginModule;
import java.io.File;
@@ -61,14 +63,16 @@
}
ktab.save();
- // For 6932525 and 6951366, make sure the etypes sent in 2nd AS-REQ
- // is not restricted to that of preauth
- kdc.setOption(KDC.Option.ONLY_RC4_TGT, true);
- x.go();
-
- // For 6959292, make sure that when etype for enc-part in 2nd AS-REQ
- // is different from that of preauth, client can still decrypt it
- kdc.setOption(KDC.Option.ONLY_RC4_PREAUTH, true);
+ if (System.getProperty("6932525") != null) {
+ // For 6932525 and 6951366, make sure the etypes sent in 2nd AS-REQ
+ // is not restricted to that of preauth
+ kdc.setOption(KDC.Option.ONLY_RC4_TGT, true);
+ }
+ if (System.getProperty("6959292") != null) {
+ // For 6959292, make sure that when etype for enc-part in 2nd AS-REQ
+ // is different from that of preauth, client can still decrypt it
+ kdc.setOption(KDC.Option.RC4_FIRST_PREAUTH, true);
+ }
x.go();
}
@@ -78,11 +82,13 @@
try {
Context.fromUserPass(OneKDC.USER, OneKDC.PASS, false);
} catch (Exception e) {
+ e.printStackTrace();
error.append("Krb5LoginModule password login error\n");
}
try {
Context.fromUserKtab(OneKDC.USER, OneKDC.KTAB, false);
} catch (Exception e) {
+ e.printStackTrace();
error.append("Krb5LoginModule keytab login error\n");
}
try {