提交 cce44f8f 编写于 作者: W weijun

6960894: Better AS-REQ creation and processing

Reviewed-by: valeriep
上级 be79dd59
...@@ -27,7 +27,6 @@ ...@@ -27,7 +27,6 @@
package com.sun.security.auth.module; package com.sun.security.auth.module;
import java.io.*; import java.io.*;
import java.net.*;
import java.text.MessageFormat; import java.text.MessageFormat;
import java.util.*; import java.util.*;
...@@ -38,9 +37,6 @@ import javax.security.auth.login.*; ...@@ -38,9 +37,6 @@ import javax.security.auth.login.*;
import javax.security.auth.spi.*; import javax.security.auth.spi.*;
import sun.security.krb5.*; 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.jgss.krb5.Krb5Util;
import sun.security.krb5.Credentials; import sun.security.krb5.Credentials;
import sun.misc.HexDumpEncoder; import sun.misc.HexDumpEncoder;
...@@ -685,32 +681,27 @@ public class Krb5LoginModule implements LoginModule { ...@@ -685,32 +681,27 @@ public class Krb5LoginModule implements LoginModule {
} }
} }
KrbAsReqBuilder builder;
// We can't get the key from the keytab so prompt // We can't get the key from the keytab so prompt
if (encKeys == null) { if (encKeys == null) {
promptForPass(getPasswdFromSharedState); promptForPass(getPasswdFromSharedState);
builder = new KrbAsReqBuilder(principal, password);
encKeys = EncryptionKey.acquireSecretKeys(
password, principal.getSalt());
if (isInitiator) { if (isInitiator) {
if (debug) // XXX Even if isInitiator=false, it might be
System.out.println("Acquire TGT using AS Exchange"); // better to do an AS-REQ so that keys can be
cred = Credentials.acquireTGT(principal, // updated with PA info
encKeys, password); cred = builder.action().getCreds();
// update keys after pre-auth
encKeys = EncryptionKey.acquireSecretKeys(password,
principal.getSalt());
} }
encKeys = builder.getKeys();
} else { } else {
builder = new KrbAsReqBuilder(principal, encKeys);
if (isInitiator) { if (isInitiator) {
if (debug) cred = builder.action().getCreds();
System.out.println("Acquire TGT using AS Exchange");
cred = Credentials.acquireTGT(principal,
encKeys, password);
} }
} }
builder.destroy();
// Get the TGT using AS Exchange
if (debug) { if (debug) {
System.out.println("principal is " + principal); System.out.println("principal is " + principal);
HexDumpEncoder hd = new HexDumpEncoder(); HexDumpEncoder hd = new HexDumpEncoder();
......
...@@ -111,7 +111,7 @@ public class Config { ...@@ -111,7 +111,7 @@ public class Config {
public static synchronized void refresh() throws KrbException { public static synchronized void refresh() throws KrbException {
singleton = new Config(); singleton = new Config();
KeyTab.refresh(); KeyTab.refresh();
KrbKdcReq.initStatic(); KdcComm.initStatic();
} }
......
...@@ -347,94 +347,6 @@ public class Credentials { ...@@ -347,94 +347,6 @@ public class Credentials {
} }
} }
/**
* 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. * Acquires default credentials.
* <br>The possible locations for default credentials cache is searched in * <br>The possible locations for default credentials cache is searched in
...@@ -529,29 +441,6 @@ public class Credentials { ...@@ -529,29 +441,6 @@ public class Credentials {
return CredentialsUtil.acquireServiceCreds(service, ccreds); 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() { public CredentialsCache getCache() {
return cache; return cache;
} }
......
...@@ -156,6 +156,22 @@ public class EncryptionKey ...@@ -156,6 +156,22 @@ public class EncryptionKey
return ktab.readServiceKeys(princ); return ktab.readServiceKeys(princ);
} }
/**
* 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. * Generate a list of keys using the given principal and password.
* Construct a key for each configured etype. * Construct a key for each configured etype.
...@@ -169,19 +185,8 @@ public class EncryptionKey ...@@ -169,19 +185,8 @@ public class EncryptionKey
* as the default in that case. If default_tkt_enctypes was set in * as the default in that case. If default_tkt_enctypes was set in
* the libdefaults of krb5.conf, then use that sequence. * the libdefaults of krb5.conf, then use that sequence.
*/ */
// Used in Krb5LoginModule
public static EncryptionKey[] acquireSecretKeys(char[] password, public static EncryptionKey[] acquireSecretKeys(char[] password,
String salt) throws KrbException { 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 {
int[] etypes = EType.getDefaults("default_tkt_enctypes"); int[] etypes = EType.getDefaults("default_tkt_enctypes");
if (etypes == null) { if (etypes == null) {
...@@ -191,10 +196,8 @@ public class EncryptionKey ...@@ -191,10 +196,8 @@ public class EncryptionKey
EncryptionKey[] encKeys = new EncryptionKey[etypes.length]; EncryptionKey[] encKeys = new EncryptionKey[etypes.length];
for (int i = 0; i < etypes.length; i++) { for (int i = 0; i < etypes.length; i++) {
if (EType.isSupported(etypes[i])) { if (EType.isSupported(etypes[i])) {
byte[] s2kparams = (pa_exists && etypes[i] == pa_etype)
? pa_s2kparams : null;
encKeys[i] = new EncryptionKey( encKeys[i] = new EncryptionKey(
stringToKey(password, salt, s2kparams, etypes[i]), stringToKey(password, salt, null, etypes[i]),
etypes[i], null); etypes[i], null);
} else { } else {
if (DEBUG) { if (DEBUG) {
......
...@@ -31,7 +31,6 @@ ...@@ -31,7 +31,6 @@
package sun.security.krb5; package sun.security.krb5;
import java.security.AccessController;
import java.security.PrivilegedAction; import java.security.PrivilegedAction;
import java.security.Security; import java.security.Security;
import java.util.Locale; import java.util.Locale;
...@@ -47,8 +46,13 @@ import java.util.ArrayList; ...@@ -47,8 +46,13 @@ import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
import java.util.HashSet; import java.util.HashSet;
import sun.security.krb5.internal.KRBError;
public abstract class KrbKdcReq { /**
* 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] // The following settings can be configured in [libdefaults]
// section of krb5.conf, which are global for all realms. Each of // section of krb5.conf, which are global for all realms. Each of
...@@ -160,20 +164,23 @@ public abstract class KrbKdcReq { ...@@ -160,20 +164,23 @@ public abstract class KrbKdcReq {
KdcAccessibility.reset(); KdcAccessibility.reset();
} }
protected byte[] obuf;
protected byte[] ibuf;
/** /**
* Sends the provided data to the KDC of the specified realm. * The instance fields
* 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
*/ */
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 String send(String realm) public byte[] send(byte[] obuf)
throws IOException, KrbException { throws IOException, KrbException {
int udpPrefLimit = getRealmSpecificValue( int udpPrefLimit = getRealmSpecificValue(
realm, "udp_preference_limit", defaultUdpPrefLimit); realm, "udp_preference_limit", defaultUdpPrefLimit);
...@@ -181,10 +188,10 @@ public abstract class KrbKdcReq { ...@@ -181,10 +188,10 @@ public abstract class KrbKdcReq {
boolean useTCP = (udpPrefLimit > 0 && boolean useTCP = (udpPrefLimit > 0 &&
(obuf != null && obuf.length > udpPrefLimit)); (obuf != null && obuf.length > udpPrefLimit));
return (send(realm, useTCP)); return send(obuf, useTCP);
} }
public String send(String realm, boolean useTCP) private byte[] send(byte[] obuf, boolean useTCP)
throws IOException, KrbException { throws IOException, KrbException {
if (obuf == null) if (obuf == null)
...@@ -205,10 +212,21 @@ public abstract class KrbKdcReq { ...@@ -205,10 +212,21 @@ public abstract class KrbKdcReq {
throw new KrbException("Cannot get kdc for realm " + realm); throw new KrbException("Cannot get kdc for realm " + realm);
} }
String tempKdc = null; // may include the port number also String tempKdc = null; // may include the port number also
byte[] ibuf = null;
for (String tmp: KdcAccessibility.list(kdcList)) { for (String tmp: KdcAccessibility.list(kdcList)) {
tempKdc = tmp; tempKdc = tmp;
try { try {
send(realm,tempKdc,useTCP); 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); KdcAccessibility.removeBad(tempKdc);
break; break;
} catch (Exception e) { } catch (Exception e) {
...@@ -228,16 +246,16 @@ public abstract class KrbKdcReq { ...@@ -228,16 +246,16 @@ public abstract class KrbKdcReq {
throw (KrbException) savedException; throw (KrbException) savedException;
} }
} }
return tempKdc; return ibuf;
} }
// send the AS Request to the specified KDC // send the AS Request to the specified KDC
public void send(String realm, String tempKdc, boolean useTCP) private byte[] send(byte[] obuf, String tempKdc, boolean useTCP)
throws IOException, KrbException { throws IOException, KrbException {
if (obuf == null) if (obuf == null)
return; return null;
int port = Krb5.KDC_INET_DEFAULT_PORT; int port = Krb5.KDC_INET_DEFAULT_PORT;
int retries = getRealmSpecificValue( int retries = getRealmSpecificValue(
...@@ -302,11 +320,12 @@ public abstract class KrbKdcReq { ...@@ -302,11 +320,12 @@ public abstract class KrbKdcReq {
KdcCommunication kdcCommunication = KdcCommunication kdcCommunication =
new KdcCommunication(kdc, port, useTCP, timeout, retries, obuf); new KdcCommunication(kdc, port, useTCP, timeout, retries, obuf);
try { try {
ibuf = AccessController.doPrivileged(kdcCommunication); byte[] ibuf = AccessController.doPrivileged(kdcCommunication);
if (DEBUG) { if (DEBUG) {
System.out.println(">>> KrbKdcReq send: #bytes read=" System.out.println(">>> KrbKdcReq send: #bytes read="
+ (ibuf != null ? ibuf.length : 0)); + (ibuf != null ? ibuf.length : 0));
} }
return ibuf;
} catch (PrivilegedActionException e) { } catch (PrivilegedActionException e) {
Exception wrappedException = e.getException(); Exception wrappedException = e.getException();
if (wrappedException instanceof IOException) { if (wrappedException instanceof IOException) {
...@@ -315,10 +334,6 @@ public abstract class KrbKdcReq { ...@@ -315,10 +334,6 @@ public abstract class KrbKdcReq {
throw (KrbException) wrappedException; throw (KrbException) wrappedException;
} }
} }
if (DEBUG) {
System.out.println(">>> KrbKdcReq send: #bytes read="
+ (ibuf != null ? ibuf.length : 0));
}
} }
private static class KdcCommunication private static class KdcCommunication
......
...@@ -36,25 +36,24 @@ import sun.security.krb5.internal.crypto.KeyUsage; ...@@ -36,25 +36,24 @@ import sun.security.krb5.internal.crypto.KeyUsage;
import sun.security.krb5.internal.crypto.EType; import sun.security.krb5.internal.crypto.EType;
import sun.security.util.*; import sun.security.util.*;
import java.io.IOException; import java.io.IOException;
import java.util.Objects;
/** /**
* This class encapsulates a AS-REP message that the KDC sends to the * This class encapsulates a AS-REP message that the KDC sends to the
* client. * client.
*/ */
public class KrbAsRep extends KrbKdcRep { class KrbAsRep extends KrbKdcRep {
private ASRep rep; private ASRep rep; // The AS-REP message
private Credentials creds; private Credentials creds; // The Credentials provide by the AS-REP
// message, created by initiator after calling
// the decrypt() method
private boolean DEBUG = Krb5.DEBUG; private boolean DEBUG = Krb5.DEBUG;
KrbAsRep(byte[] ibuf, EncryptionKey[] keys, KrbAsReq asReq) throws KrbAsRep(byte[] ibuf) throws
KrbException, Asn1Exception, IOException { KrbException, Asn1Exception, IOException {
if (keys == null)
throw new KrbException(Krb5.API_INVALID_ARG);
DerValue encoding = new DerValue(ibuf); DerValue encoding = new DerValue(ibuf);
ASReq req = asReq.getMessage();
ASRep rep = null;
try { try {
rep = new ASRep(encoding); rep = new ASRep(encoding);
} catch (Asn1Exception e) { } catch (Asn1Exception e) {
...@@ -83,25 +82,77 @@ public class KrbAsRep extends KrbKdcRep { ...@@ -83,25 +82,77 @@ public class KrbAsRep extends KrbKdcRep {
ke.initCause(e); ke.initCause(e);
throw ke; throw ke;
} }
}
int encPartKeyType = rep.encPart.getEType(); // KrbAsReqBuilder need to read back the PA for key generation
EncryptionKey dkey = EncryptionKey.findKey(encPartKeyType, keys); 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();
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) { if (dkey == null) {
throw new KrbException(Krb5.API_INVALID_ARG, throw new KrbException(Krb5.API_INVALID_ARG,
"Cannot find key of appropriate type to decrypt AS REP - " + "Cannot find key for type/kvno to decrypt AS REP - " +
EType.toString(encPartKeyType)); 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, byte[] enc_as_rep_bytes = rep.encPart.decrypt(dkey,
KeyUsage.KU_ENC_AS_REP_PART); KeyUsage.KU_ENC_AS_REP_PART);
byte[] enc_as_rep_part = rep.encPart.reset(enc_as_rep_bytes); 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); EncASRepPart enc_part = new EncASRepPart(encoding);
rep.ticket.sname.setRealm(rep.ticket.realm); rep.ticket.sname.setRealm(rep.ticket.realm);
rep.encKDCRepPart = enc_part; rep.encKDCRepPart = enc_part;
ASReq req = asReq.getMessage();
check(req, rep); check(req, rep);
creds = new Credentials( creds = new Credentials(
...@@ -119,17 +170,13 @@ public class KrbAsRep extends KrbKdcRep { ...@@ -119,17 +170,13 @@ public class KrbAsRep extends KrbKdcRep {
System.out.println(">>> KrbAsRep cons in KrbAsReq.getReply " + System.out.println(">>> KrbAsRep cons in KrbAsReq.getReply " +
req.reqBody.cname.getNameString()); req.reqBody.cname.getNameString());
} }
this.rep = rep;
this.creds = creds;
} }
public Credentials getCreds() { Credentials getCreds() {
return creds; return Objects.nonNull(creds, "Creds not available yet.");
} }
// made public for Kinit sun.security.krb5.internal.ccache.Credentials getCCreds() {
public sun.security.krb5.internal.ccache.Credentials setCredentials() {
return new sun.security.krb5.internal.ccache.Credentials(rep); return new sun.security.krb5.internal.ccache.Credentials(rep);
} }
} }
...@@ -32,291 +32,39 @@ ...@@ -32,291 +32,39 @@
package sun.security.krb5; package sun.security.krb5;
import sun.security.krb5.internal.*; 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.Nonce;
import sun.security.krb5.internal.crypto.KeyUsage; import sun.security.krb5.internal.crypto.KeyUsage;
import sun.security.util.*;
import java.io.IOException; 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 * This class encapsulates the KRB-AS-REQ message that the client
* sends to the KDC. * sends to the KDC.
*/ */
public class KrbAsReq extends KrbKdcReq { public class KrbAsReq {
private PrincipalName princName;
private ASReq asReqMessg; private ASReq asReqMessg;
private boolean DEBUG = Krb5.DEBUG; 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 * Constructs an AS-REQ message.
* @throws KrbException
* @throws IOException
*/ */
// Called by Credentials // Can be null? has default?
KrbAsReq(PrincipalName principal, EncryptionKey[] keys) public KrbAsReq(EncryptionKey pakey, // ok
throws KrbException, IOException { KDCOptions options, // ok, new KDCOptions()
this(keys, // for pre-authentication PrincipalName cname, // NO and must have realm
false, 0, null, null, // pre-auth values PrincipalName sname, // ok, krgtgt@CREALM
defaultKDCOptions, KerberosTime from, // ok
principal, KerberosTime till, // ok, will use
null, // PrincipalName sname KerberosTime rtime, // ok
null, // KerberosTime from int[] eTypes, // NO
null, // KerberosTime till HostAddresses addresses // ok
null, // KerberosTime rtime )
null, // int[] eTypes throws KrbException, IOException {
null, // HostAddresses addresses
null); // Ticket[] additionalTickets if (options == null) {
} options = new KDCOptions();
/**
* Creates a KRB-AS-REQ to send to the default KDC
* with pre-authentication values
*/
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());
}
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();
}
}
}
}
// 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 // check if they are valid arguments. The optional fields should be
// consistent with settings in KDCOptions. Mar 17 2000 // consistent with settings in KDCOptions. Mar 17 2000
if (options.get(KDCOptions.FORWARDED) || if (options.get(KDCOptions.FORWARDED) ||
...@@ -341,189 +89,66 @@ public class KrbAsReq extends KrbKdcReq { ...@@ -341,189 +89,66 @@ public class KrbAsReq extends KrbKdcReq {
if (rtime != null) rtime = null; if (rtime != null) rtime = null;
} }
princName = cname;
int[] tktETypes = EType.getDefaults("default_tkt_enctypes", keys);
PAData[] paData = null; PAData[] paData = null;
if (PA_ENC_TIMESTAMP_REQUIRED) { if (pakey != null) {
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");
}
PAEncTSEnc ts = new PAEncTSEnc(); PAEncTSEnc ts = new PAEncTSEnc();
byte[] temp = ts.asn1Encode(); byte[] temp = ts.asn1Encode();
if (key != null) { EncryptedData encTs = new EncryptedData(pakey, temp,
// Use first key in list KeyUsage.KU_PA_ENC_TS);
EncryptedData encTs = new EncryptedData(key, temp, paData = new PAData[1];
KeyUsage.KU_PA_ENC_TS); paData[0] = new PAData( Krb5.PA_ENC_TIMESTAMP,
paData = new PAData[1]; encTs.asn1Encode());
paData[0] = new PAData( Krb5.PA_ENC_TIMESTAMP,
encTs.asn1Encode());
}
} }
if (DEBUG) { if (cname.getRealm() == null) {
System.out.println(">>> KrbAsReq calling createMessage"); throw new RealmException(Krb5.REALM_NULL,
"default realm not specified ");
} }
if (eTypes == null) { if (DEBUG) {
eTypes = tktETypes; System.out.println(">>> KrbAsReq creating message");
} }
// check to use addresses in tickets // check to use addresses in tickets
if (Config.getInstance().useAddresses()) { if (addresses == null && Config.getInstance().useAddresses()) {
KDC_EMPTY_ADDRESSES_ALLOWED = false;
}
// get the local InetAddress if required
if (addresses == null && !KDC_EMPTY_ADDRESSES_ALLOWED) {
addresses = HostAddresses.getLocalAddresses(); 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");
}
PrincipalName req_sname = null;
if (sname == null) { if (sname == null) {
if (crealm == null) { sname = new PrincipalName("krbtgt" +
throw new RealmException(Krb5.REALM_NULL, PrincipalName.NAME_COMPONENT_SEPARATOR +
"default realm not specified "); cname.getRealmAsString(),
} PrincipalName.KRB_NT_SRV_INST);
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) { if (till == null) {
req_till = new KerberosTime(); till = new KerberosTime(0); // Choose KDC maximum allowed
} else {
req_till = till;
} }
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, cname,
crealm, cname.getRealm(),
req_sname, sname,
from, from,
req_till, till,
rtime, rtime,
Nonce.value(), Nonce.value(),
eTypes, eTypes,
addresses, addresses,
null, null,
additionalTickets); null);
return new ASReq( asReqMessg = new ASReq(
paData, paData,
kdc_req_body); kdc_req_body);
} }
byte[] encoding() throws IOException, Asn1Exception {
return asReqMessg.asn1Encode();
}
// Used by KrbAsRep to validate AS-REP
ASReq getMessage() { ASReq getMessage() {
return asReqMessg; return asReqMessg;
} }
......
/*
* 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");
}
}
}
/* /*
* 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. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
...@@ -31,20 +31,16 @@ ...@@ -31,20 +31,16 @@
package sun.security.krb5; package sun.security.krb5;
import sun.security.util.*;
import sun.security.krb5.EncryptionKey;
import sun.security.krb5.internal.*; import sun.security.krb5.internal.*;
import sun.security.krb5.internal.crypto.*; import sun.security.krb5.internal.crypto.*;
import java.io.IOException; import java.io.IOException;
import java.net.UnknownHostException; import java.net.UnknownHostException;
import java.util.StringTokenizer;
import java.io.InterruptedIOException;
/** /**
* This class encapsulates a Kerberos TGS-REQ that is sent from the * This class encapsulates a Kerberos TGS-REQ that is sent from the
* client to the KDC. * client to the KDC.
*/ */
public class KrbTgsReq extends KrbKdcReq { public class KrbTgsReq {
private PrincipalName princName; private PrincipalName princName;
private PrincipalName servName; private PrincipalName servName;
...@@ -56,7 +52,8 @@ public class KrbTgsReq extends KrbKdcReq { ...@@ -56,7 +52,8 @@ public class KrbTgsReq extends KrbKdcReq {
private static final boolean DEBUG = Krb5.DEBUG; private static final boolean DEBUG = Krb5.DEBUG;
private int defaultTimeout = 30*1000; // 30 seconds private byte[] obuf;
private byte[] ibuf;
// Used in CredentialsUtil // Used in CredentialsUtil
public KrbTgsReq(Credentials asCreds, public KrbTgsReq(Credentials asCreds,
...@@ -182,11 +179,12 @@ public class KrbTgsReq extends KrbKdcReq { ...@@ -182,11 +179,12 @@ public class KrbTgsReq extends KrbKdcReq {
* @throws KrbException * @throws KrbException
* @throws IOException * @throws IOException
*/ */
public String send() throws IOException, KrbException { public void send() throws IOException, KrbException {
String realmStr = null; String realmStr = null;
if (servName != null) if (servName != null)
realmStr = servName.getRealmString(); realmStr = servName.getRealmString();
return (send(realmStr)); KdcComm comm = new KdcComm(realmStr);
ibuf = comm.send(obuf);
} }
public KrbTgsRep getReply() public KrbTgsRep getReply()
...@@ -201,18 +199,8 @@ public class KrbTgsReq extends KrbKdcReq { ...@@ -201,18 +199,8 @@ public class KrbTgsReq extends KrbKdcReq {
public Credentials sendAndGetCreds() throws IOException, KrbException { public Credentials sendAndGetCreds() throws IOException, KrbException {
KrbTgsRep tgs_rep = null; KrbTgsRep tgs_rep = null;
String kdc = null; String kdc = null;
try { send();
kdc = send(); tgs_rep = getReply();
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;
}
}
return tgs_rep.getCreds(); return tgs_rep.getCreds();
} }
...@@ -240,7 +228,7 @@ public class KrbTgsReq extends KrbKdcReq { ...@@ -240,7 +228,7 @@ public class KrbTgsReq extends KrbKdcReq {
UnknownHostException, KrbCryptoException { UnknownHostException, KrbCryptoException {
KerberosTime req_till = null; KerberosTime req_till = null;
if (till == null) { if (till == null) {
req_till = new KerberosTime(); req_till = new KerberosTime(0);
} else { } else {
req_till = till; req_till = till;
} }
......
...@@ -511,10 +511,6 @@ public class PrincipalName ...@@ -511,10 +511,6 @@ public class PrincipalName
return salt; return salt;
} }
public void setSalt(String salt) {
this.salt = salt;
}
public String toString() { public String toString() {
StringBuffer str = new StringBuffer(); StringBuffer str = new StringBuffer();
for (int i = 0; i < nameStrings.length; i++) { for (int i = 0; i < nameStrings.length; i++) {
......
...@@ -32,7 +32,6 @@ package sun.security.krb5.internal; ...@@ -32,7 +32,6 @@ package sun.security.krb5.internal;
import sun.security.krb5.*; import sun.security.krb5.*;
import sun.security.util.*; import sun.security.util.*;
import java.util.Vector;
import java.io.IOException; import java.io.IOException;
import java.math.BigInteger; import java.math.BigInteger;
...@@ -69,7 +68,7 @@ public class KDCRep { ...@@ -69,7 +68,7 @@ public class KDCRep {
public EncKDCRepPart encKDCRepPart; //not part of ASN.1 encoding public EncKDCRepPart encKDCRepPart; //not part of ASN.1 encoding
private int pvno; private int pvno;
private int msgType; private int msgType;
private PAData[] pAData = null; //optional public PAData[] pAData = null; //optional
private boolean DEBUG = Krb5.DEBUG; private boolean DEBUG = Krb5.DEBUG;
public KDCRep( public KDCRep(
......
/* /*
* 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. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
...@@ -41,7 +41,9 @@ import sun.security.util.*; ...@@ -41,7 +41,9 @@ import sun.security.util.*;
import java.io.IOException; import java.io.IOException;
import java.io.ObjectInputStream; import java.io.ObjectInputStream;
import java.math.BigInteger; import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.List;
import sun.security.krb5.internal.util.KerberosString; import sun.security.krb5.internal.util.KerberosString;
/** /**
* Implements the ASN.1 KRBError type. * Implements the ASN.1 KRBError type.
...@@ -96,10 +98,8 @@ public class KRBError implements java.io.Serializable { ...@@ -96,10 +98,8 @@ public class KRBError implements java.io.Serializable {
private byte[] eData; //optional private byte[] eData; //optional
private Checksum eCksum; //optional private Checksum eCksum; //optional
// pre-auth info private PAData[] pa; // PA-DATA in eData
private int etype = 0; private int pa_eType; // The 1st etype appeared in salt-related PAData
private String salt = null;
private byte[] s2kparams = null;
private static boolean DEBUG = Krb5.DEBUG; private static boolean DEBUG = Krb5.DEBUG;
...@@ -260,10 +260,12 @@ public class KRBError implements java.io.Serializable { ...@@ -260,10 +260,12 @@ public class KRBError implements java.io.Serializable {
private void parsePAData(byte[] data) private void parsePAData(byte[] data)
throws IOException, Asn1Exception { throws IOException, Asn1Exception {
DerValue derPA = new DerValue(data); DerValue derPA = new DerValue(data);
List<PAData> paList = new ArrayList<PAData>();
while (derPA.data.available() > 0) { while (derPA.data.available() > 0) {
// read the PA-DATA // read the PA-DATA
DerValue tmp = derPA.data.getDerValue(); DerValue tmp = derPA.data.getDerValue();
PAData pa_data = new PAData(tmp); PAData pa_data = new PAData(tmp);
paList.add(pa_data);
int pa_type = pa_data.getType(); int pa_type = pa_data.getType();
byte[] pa_value = pa_data.getValue(); byte[] pa_value = pa_data.getValue();
if (DEBUG) { if (DEBUG) {
...@@ -280,24 +282,13 @@ public class KRBError implements java.io.Serializable { ...@@ -280,24 +282,13 @@ public class KRBError implements java.io.Serializable {
case Krb5.PA_ETYPE_INFO: case Krb5.PA_ETYPE_INFO:
if (pa_value != null) { if (pa_value != null) {
DerValue der = new DerValue(pa_value); 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) { while (der.data.available() > 0) {
value = der.data.getDerValue(); DerValue value = der.data.getDerValue();
info = new ETypeInfo(value); ETypeInfo info = new ETypeInfo(value);
if (pa_eType == 0) pa_eType = info.getEType();
if (DEBUG) { if (DEBUG) {
etype = info.getEType(); System.out.println("\t PA-ETYPE-INFO etype = " + info.getEType());
System.out.println("\t salt for " + etype System.out.println("\t PA-ETYPE-INFO salt = " + info.getSalt());
+ " is " + info.getSalt());
}
if (salt == null || salt.isEmpty()) {
salt = info.getSalt();
} }
} }
} }
...@@ -305,25 +296,13 @@ public class KRBError implements java.io.Serializable { ...@@ -305,25 +296,13 @@ public class KRBError implements java.io.Serializable {
case Krb5.PA_ETYPE_INFO2: case Krb5.PA_ETYPE_INFO2:
if (pa_value != null) { if (pa_value != null) {
DerValue der = new DerValue(pa_value); 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) { while (der.data.available() > 0) {
value = der.data.getDerValue(); DerValue value = der.data.getDerValue();
info2 = new ETypeInfo2(value); ETypeInfo2 info2 = new ETypeInfo2(value);
if (pa_eType == 0) pa_eType = info2.getEType();
if (DEBUG) { if (DEBUG) {
etype = info2.getEType(); System.out.println("\t PA-ETYPE-INFO2 etype = " + info2.getEType());
System.out.println("\t salt for " + etype System.out.println("\t PA-ETYPE-INFO2 salt = " + info2.getSalt());
+ " is " + info2.getSalt());
}
if (salt == null || salt.isEmpty()) {
salt = info2.getSalt();
} }
} }
} }
...@@ -333,6 +312,7 @@ public class KRBError implements java.io.Serializable { ...@@ -333,6 +312,7 @@ public class KRBError implements java.io.Serializable {
break; break;
} }
} }
pa = paList.toArray(new PAData[paList.size()]);
} }
public final KerberosTime getServerTime() { public final KerberosTime getServerTime() {
...@@ -356,18 +336,12 @@ public class KRBError implements java.io.Serializable { ...@@ -356,18 +336,12 @@ public class KRBError implements java.io.Serializable {
} }
// access pre-auth info // access pre-auth info
public final int getEType() { public final PAData[] getPA() {
return etype; return pa;
}
// access pre-auth info
public final String getSalt() {
return salt;
} }
// access pre-auth info public final int getEType() {
public final byte[] getParams() { return pa_eType;
return ((s2kparams == null) ? null : s2kparams.clone());
} }
public final String getErrorString() { public final String getErrorString() {
......
...@@ -77,11 +77,6 @@ public class KerberosTime implements Cloneable { ...@@ -77,11 +77,6 @@ public class KerberosTime implements Cloneable {
public static final boolean NOW = true; public static final boolean NOW = true;
public static final boolean UNADJUSTED_NOW = false; 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) { public KerberosTime(long time) {
kerberosTime = time; kerberosTime = time;
} }
......
...@@ -30,9 +30,11 @@ ...@@ -30,9 +30,11 @@
package sun.security.krb5.internal; package sun.security.krb5.internal;
import sun.security.krb5.KrbException;
import sun.security.util.*; import sun.security.util.*;
import sun.security.krb5.Asn1Exception; import sun.security.krb5.Asn1Exception;
import java.io.IOException; import java.io.IOException;
import sun.security.krb5.internal.util.KerberosString;
/** /**
* Implements the ASN.1 PA-DATA type. * Implements the ASN.1 PA-DATA type.
...@@ -135,4 +137,75 @@ public class PAData { ...@@ -135,4 +137,75 @@ public class PAData {
public byte[] getValue() { public byte[] getValue() {
return ((pADataValue == null) ? null : pADataValue.clone()); 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);
}
} }
/* /*
* 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. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
...@@ -33,11 +33,7 @@ package sun.security.krb5.internal.tools; ...@@ -33,11 +33,7 @@ package sun.security.krb5.internal.tools;
import sun.security.krb5.*; import sun.security.krb5.*;
import sun.security.krb5.internal.*; import sun.security.krb5.internal.*;
import sun.security.krb5.internal.ccache.*; import sun.security.krb5.internal.ccache.*;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.IOException; import java.io.IOException;
import java.util.StringTokenizer;
import java.io.File;
import java.util.Arrays; import java.util.Arrays;
import sun.security.util.Password; import sun.security.util.Password;
...@@ -152,6 +148,7 @@ public class Kinit { ...@@ -152,6 +148,7 @@ public class Kinit {
if (principal != null) { if (principal != null) {
princName = principal.toString(); princName = principal.toString();
} }
KrbAsReqBuilder builder;
if (DEBUG) { if (DEBUG) {
System.out.println("Principal is " + principal); System.out.println("Principal is " + principal);
} }
...@@ -172,6 +169,7 @@ public class Kinit { ...@@ -172,6 +169,7 @@ public class Kinit {
new String(psswd)); new String(psswd));
} }
} }
builder = new KrbAsReqBuilder(principal, psswd);
} else { } else {
if (DEBUG) { if (DEBUG) {
System.out.println(">>> Kinit using keytab"); System.out.println(">>> Kinit using keytab");
...@@ -198,11 +196,13 @@ public class Kinit { ...@@ -198,11 +196,13 @@ public class Kinit {
} }
throw new KrbException(msg); throw new KrbException(msg);
} }
builder = new KrbAsReqBuilder(principal, skeys);
} }
KDCOptions opt = new KDCOptions(); KDCOptions opt = new KDCOptions();
setOptions(KDCOptions.FORWARDABLE, options.forwardable, opt); setOptions(KDCOptions.FORWARDABLE, options.forwardable, opt);
setOptions(KDCOptions.PROXIABLE, options.proxiable, opt); setOptions(KDCOptions.PROXIABLE, options.proxiable, opt);
builder.setOptions(opt);
String realm = options.getKDCRealm(); String realm = options.getKDCRealm();
if (realm == null) { if (realm == null) {
realm = Config.getInstance().getDefaultRealm(); realm = Config.getInstance().getDefaultRealm();
...@@ -215,62 +215,21 @@ public class Kinit { ...@@ -215,62 +215,21 @@ public class Kinit {
PrincipalName sname = new PrincipalName("krbtgt" + "/" + realm, PrincipalName sname = new PrincipalName("krbtgt" + "/" + realm,
PrincipalName.KRB_NT_SRV_INST); PrincipalName.KRB_NT_SRV_INST);
sname.setRealm(realm); sname.setRealm(realm);
builder.setTarget(sname);
if (DEBUG) { if (DEBUG) {
System.out.println(">>> Creating KrbAsReq"); System.out.println(">>> Creating KrbAsReq");
} }
KrbAsReq as_req = null; if (options.getAddressOption())
HostAddresses addresses = null; builder.setAddresses(HostAddresses.getLocalAddresses());
try {
if (options.getAddressOption())
addresses = HostAddresses.getLocalAddresses();
if (useKeytab) { builder.action();
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());
}
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;
}
}
sun.security.krb5.internal.ccache.Credentials credentials = 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 // we always create a new cache and store the ticket we get
CredentialsCache cache = CredentialsCache cache =
CredentialsCache.create(principal, options.cachename); CredentialsCache.create(principal, options.cachename);
...@@ -296,41 +255,6 @@ public class Kinit { ...@@ -296,41 +255,6 @@ public class Kinit {
options = null; // release reference to options 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) { private static void setOptions(int flag, int option, KDCOptions opt) {
switch (option) { switch (option) {
case 0: case 0:
......
...@@ -35,7 +35,6 @@ import sun.net.spi.nameservice.NameServiceDescriptor; ...@@ -35,7 +35,6 @@ import sun.net.spi.nameservice.NameServiceDescriptor;
import sun.security.krb5.*; import sun.security.krb5.*;
import sun.security.krb5.internal.*; import sun.security.krb5.internal.*;
import sun.security.krb5.internal.ccache.CredentialsCache; 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.crypto.KeyUsage;
import sun.security.krb5.internal.ktab.KeyTab; import sun.security.krb5.internal.ktab.KeyTab;
import sun.security.util.DerInputStream; import sun.security.util.DerInputStream;
...@@ -129,8 +128,13 @@ public class KDC { ...@@ -129,8 +128,13 @@ public class KDC {
// The random generator to generate random keys (including session keys) // The random generator to generate random keys (including session keys)
private static SecureRandom secureRandom = new SecureRandom(); 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 // Realm name
private String realm; private String realm;
// KDC // KDC
...@@ -159,9 +163,13 @@ public class KDC { ...@@ -159,9 +163,13 @@ public class KDC {
*/ */
ONLY_RC4_TGT, 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 { static {
...@@ -191,6 +199,12 @@ public class KDC { ...@@ -191,6 +199,12 @@ public class KDC {
return create(realm, "kdc." + realm.toLowerCase(), 0, true); 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. * Creates and starts a KDC server.
* @param realm the realm name * @param realm the realm name
...@@ -471,7 +485,18 @@ public class KDC { ...@@ -471,7 +485,18 @@ public class KDC {
* @return the salt * @return the salt
*/ */
private String getSalt(PrincipalName p) { 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(); String s = p.getRealmString();
if (s == null) s = getRealm(); if (s == null) s = getRealm();
for (String n: p.getNameStrings()) { for (String n: p.getNameStrings()) {
...@@ -493,8 +518,6 @@ public class KDC { ...@@ -493,8 +518,6 @@ public class KDC {
try { try {
// Do not call EncryptionKey.acquireSecretKeys(), otherwise // Do not call EncryptionKey.acquireSecretKeys(), otherwise
// the krb5.conf config file would be loaded. // 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; Integer kvno = null;
// For service whose password ending with a number, use it as kvno. // For service whose password ending with a number, use it as kvno.
// Kvno must be postive. // Kvno must be postive.
...@@ -504,12 +527,9 @@ public class KDC { ...@@ -504,12 +527,9 @@ public class KDC {
kvno = pass[pass.length-1] - '0'; kvno = pass[pass.length-1] - '0';
} }
} }
return new EncryptionKey((byte[]) stringToKey.invoke( return new EncryptionKey(EncryptionKeyDotStringToKey(
null, getPassword(p, server), getSalt(p), null, etype), getPassword(p, server), getSalt(p), null, etype),
etype, kvno); etype, kvno);
} catch (InvocationTargetException ex) {
KrbException ke = (KrbException)ex.getCause();
throw ke;
} catch (KrbException ke) { } catch (KrbException ke) {
throw ke; throw ke;
} catch (Exception e) { } catch (Exception e) {
...@@ -590,12 +610,11 @@ public class KDC { ...@@ -590,12 +610,11 @@ public class KDC {
" sends TGS-REQ for " + " sends TGS-REQ for " +
tgsReq.reqBody.sname); tgsReq.reqBody.sname);
KDCReqBody body = tgsReq.reqBody; 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; PAData[] pas = kDCReqDotPAData(tgsReq);
Field f = KDCReq.class.getDeclaredField("pAData");
f.setAccessible(true);
PAData[] pas = (PAData[])f.get(tgsReq);
Ticket tkt = null; Ticket tkt = null;
EncTicketPart etp = null; EncTicketPart etp = null;
...@@ -607,9 +626,9 @@ public class KDC { ...@@ -607,9 +626,9 @@ public class KDC {
APReq apReq = new APReq(pa.getValue()); APReq apReq = new APReq(pa.getValue());
EncryptedData ed = apReq.authenticator; EncryptedData ed = apReq.authenticator;
tkt = apReq.ticket; tkt = apReq.ticket;
etype = tkt.encPart.getEType(); int te = tkt.encPart.getEType();
tkt.sname.setRealm(tkt.realm); 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); byte[] bb = tkt.encPart.decrypt(kkey, KeyUsage.KU_TICKET);
DerInputStream derIn = new DerInputStream(bb); DerInputStream derIn = new DerInputStream(bb);
DerValue der = derIn.getDerValue(); DerValue der = derIn.getDerValue();
...@@ -620,16 +639,12 @@ public class KDC { ...@@ -620,16 +639,12 @@ public class KDC {
throw new KrbException(Krb5.KDC_ERR_PADATA_TYPE_NOSUPP); 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 // Session key for original ticket, TGT
EncryptionKey ckey = etp.key; EncryptionKey ckey = etp.key;
// Session key for session with the service // Session key for session with the service
EncryptionKey key = generateRandomKey(etype); EncryptionKey key = generateRandomKey(e2);
// Check time, TODO // Check time, TODO
KerberosTime till = body.till; KerberosTime till = body.till;
...@@ -678,6 +693,10 @@ public class KDC { ...@@ -678,6 +693,10 @@ public class KDC {
till, body.rtime, till, body.rtime,
body.addresses, body.addresses,
null); null);
EncryptionKey skey = keyForUser(body.sname, e3, true);
if (skey == null) {
throw new KrbException(Krb5.KDC_ERR_SUMTYPE_NOSUPP); // TODO
}
Ticket t = new Ticket( Ticket t = new Ticket(
body.crealm, body.crealm,
body.sname, body.sname,
...@@ -741,17 +760,17 @@ public class KDC { ...@@ -741,17 +760,17 @@ public class KDC {
private byte[] processAsReq(byte[] in) throws Exception { private byte[] processAsReq(byte[] in) throws Exception {
ASReq asReq = new ASReq(in); ASReq asReq = new ASReq(in);
int[] eTypes = null; int[] eTypes = null;
List<PAData> outPAs = new ArrayList<PAData>();
try { try {
System.out.println(realm + "> " + asReq.reqBody.cname + System.out.println(realm + "> " + asReq.reqBody.cname +
" sends AS-REQ for " + " sends AS-REQ for " +
asReq.reqBody.sname); asReq.reqBody.sname);
KDCReqBody body = asReq.reqBody; KDCReqBody body = asReq.reqBody;
body.cname.setRealm(getRealm());
// Reflection: int[] eType = body.eType; eTypes = KDCReqBodyDotEType(body);
Field f = KDCReqBody.class.getDeclaredField("eType");
f.setAccessible(true);
eTypes = (int[])f.get(body);
int eType = eTypes[0]; int eType = eTypes[0];
EncryptionKey ckey = keyForUser(body.cname, eType, false); EncryptionKey ckey = keyForUser(body.cname, eType, false);
...@@ -807,19 +826,63 @@ public class KDC { ...@@ -807,19 +826,63 @@ public class KDC {
} }
bFlags[Krb5.TKT_OPTS_INITIAL] = true; bFlags[Krb5.TKT_OPTS_INITIAL] = true;
f = KDCReq.class.getDeclaredField("pAData"); // Creating PA-DATA
f.setAccessible(true); int[] epas = eTypes;
PAData[] pas = (PAData[])f.get(asReq); if (options.containsKey(KDC.Option.RC4_FIRST_PREAUTH)) {
if (pas == null || pas.length == 0) { 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); Object preauth = options.get(Option.PREAUTH_REQUIRED);
if (preauth == null || preauth.equals(Boolean.TRUE)) { if (preauth == null || preauth.equals(Boolean.TRUE)) {
throw new KrbException(Krb5.KDC_ERR_PREAUTH_REQUIRED); throw new KrbException(Krb5.KDC_ERR_PREAUTH_REQUIRED);
} }
} else { } else {
try { try {
Constructor<EncryptedData> ctor = EncryptedData.class.getDeclaredConstructor(DerValue.class); EncryptedData data = newEncryptedData(new DerValue(inPAs[0].getValue()));
ctor.setAccessible(true);
EncryptedData data = ctor.newInstance(new DerValue(pas[0].getValue()));
EncryptionKey pakey = keyForUser(body.cname, data.getEType(), false); EncryptionKey pakey = keyForUser(body.cname, data.getEType(), false);
data.decrypt(pakey, KeyUsage.KU_PA_ENC_TS); data.decrypt(pakey, KeyUsage.KU_PA_ENC_TS);
} catch (Exception e) { } catch (Exception e) {
...@@ -862,7 +925,8 @@ public class KDC { ...@@ -862,7 +925,8 @@ public class KDC {
body.addresses body.addresses
); );
EncryptedData edata = new EncryptedData(ckey, enc_part.asn1Encode(), KeyUsage.KU_ENC_AS_REP_PART); 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.crealm,
body.cname, body.cname,
t, t,
...@@ -907,36 +971,10 @@ public class KDC { ...@@ -907,36 +971,10 @@ public class KDC {
if (kerr == null) { if (kerr == null) {
if (ke.returnCode() == Krb5.KDC_ERR_PREAUTH_REQUIRED || if (ke.returnCode() == Krb5.KDC_ERR_PREAUTH_REQUIRED ||
ke.returnCode() == Krb5.KDC_ERR_PREAUTH_FAILED) { 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(); DerOutputStream bytes = new DerOutputStream();
bytes.write(new PAData(Krb5.PA_ENC_TIMESTAMP, new byte[0]).asn1Encode()); bytes.write(new PAData(Krb5.PA_ENC_TIMESTAMP, new byte[0]).asn1Encode());
bytes.write(pa.asn1Encode()); for (PAData p: outPAs) {
bytes.write(p.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());
} }
DerOutputStream temp = new DerOutputStream(); DerOutputStream temp = new DerOutputStream();
temp.write(DerValue.tag_Sequence, bytes); temp.write(DerValue.tag_Sequence, bytes);
...@@ -1146,4 +1184,61 @@ public class KDC { ...@@ -1146,4 +1184,61 @@ public class KDC {
return "ns"; 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);
}
}
} }
/*
* 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);
}
}
/* /*
* 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. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
...@@ -46,6 +46,8 @@ public class OneKDC extends KDC { ...@@ -46,6 +46,8 @@ public class OneKDC extends KDC {
public static final String USER = "dummy"; public static final String USER = "dummy";
public static final char[] PASS = "bogus".toCharArray(); 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 KRB5_CONF = "localkdc-krb5.conf";
public static final String KTAB = "localkdc.ktab"; public static final String KTAB = "localkdc.ktab";
public static final String JAAS_CONF = "localkdc-jaas.conf"; public static final String JAAS_CONF = "localkdc-jaas.conf";
...@@ -61,6 +63,7 @@ public class OneKDC extends KDC { ...@@ -61,6 +63,7 @@ public class OneKDC extends KDC {
public OneKDC(String etype) throws Exception { public OneKDC(String etype) throws Exception {
super(REALM, KDCHOST, 0, true); super(REALM, KDCHOST, 0, true);
addPrincipal(USER, PASS); addPrincipal(USER, PASS);
addPrincipal(USER2, PASS2);
addPrincipalRandKey("krbtgt/" + REALM); addPrincipalRandKey("krbtgt/" + REALM);
addPrincipalRandKey(SERVER); addPrincipalRandKey(SERVER);
addPrincipalRandKey(BACKEND); addPrincipalRandKey(BACKEND);
......
...@@ -26,6 +26,8 @@ ...@@ -26,6 +26,8 @@
* @bug 6932525 6951366 6959292 * @bug 6932525 6951366 6959292
* @summary kerberos login failure on win2008 with AD set to win2000 compat mode * @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 * 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 com.sun.security.auth.module.Krb5LoginModule;
import java.io.File; import java.io.File;
...@@ -61,14 +63,16 @@ public class W83 { ...@@ -61,14 +63,16 @@ public class W83 {
} }
ktab.save(); ktab.save();
// For 6932525 and 6951366, make sure the etypes sent in 2nd AS-REQ if (System.getProperty("6932525") != null) {
// is not restricted to that of preauth // For 6932525 and 6951366, make sure the etypes sent in 2nd AS-REQ
kdc.setOption(KDC.Option.ONLY_RC4_TGT, true); // is not restricted to that of preauth
x.go(); kdc.setOption(KDC.Option.ONLY_RC4_TGT, true);
}
// For 6959292, make sure that when etype for enc-part in 2nd AS-REQ if (System.getProperty("6959292") != null) {
// is different from that of preauth, client can still decrypt it // For 6959292, make sure that when etype for enc-part in 2nd AS-REQ
kdc.setOption(KDC.Option.ONLY_RC4_PREAUTH, true); // is different from that of preauth, client can still decrypt it
kdc.setOption(KDC.Option.RC4_FIRST_PREAUTH, true);
}
x.go(); x.go();
} }
...@@ -78,11 +82,13 @@ public class W83 { ...@@ -78,11 +82,13 @@ public class W83 {
try { try {
Context.fromUserPass(OneKDC.USER, OneKDC.PASS, false); Context.fromUserPass(OneKDC.USER, OneKDC.PASS, false);
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace();
error.append("Krb5LoginModule password login error\n"); error.append("Krb5LoginModule password login error\n");
} }
try { try {
Context.fromUserKtab(OneKDC.USER, OneKDC.KTAB, false); Context.fromUserKtab(OneKDC.USER, OneKDC.KTAB, false);
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace();
error.append("Krb5LoginModule keytab login error\n"); error.append("Krb5LoginModule keytab login error\n");
} }
try { try {
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册