/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License, Version 1.0 only
* (the "License"). You may not use this file except in compliance
* with the License.
*
* You can obtain a copy of the license at legal-notices/CDDLv1_0.txt
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at legal-notices/CDDLv1_0.txt.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information:
* Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*
*
* Copyright 2006-2009 Sun Microsystems, Inc.
* Portions Copyright 2012 ForgeRock AS
*/
/**
* This class provides a generic interface that LDAP clients can use to perform
* various kinds of authentication to the Directory Server. This handles both
* simple authentication as well as several SASL mechanisms including:
* <UL>
* <LI>ANONYMOUS</LI>
* <LI>CRAM-MD5</LI>
* <LI>DIGEST-MD5</LI>
* <LI>EXTERNAL</LI>
* <LI>GSSAPI</LI>
* <LI>PLAIN</LI>
* </UL>
* <BR><BR>
* Note that this implementation is not thread safe, so if the same
* <CODE>AuthenticationHandler</CODE> object is to be used concurrently by
* multiple threads, it must be externally synchronized.
*/
public class LDAPAuthenticationHandler
{
// The bind DN for GSSAPI authentication.
// The LDAP reader that will be used to read data from the server.
// The LDAP writer that will be used to send data to the server.
// The atomic integer that will be used to obtain message IDs for request
// messages.
// An array filled with the inner pad byte.
private byte[] iPad;
// An array filled with the outer pad byte.
private byte[] oPad;
// The authentication password for GSSAPI authentication.
private char[] gssapiAuthPW;
// The message digest that will be used to create MD5 hashes.
// The secure random number generator for use by this authentication handler.
// The authentication ID for GSSAPI authentication.
// The authorization ID for GSSAPI authentication.
// The quality of protection for GSSAPI authentication.
// The host name used to connect to the remote system.
// The SASL mechanism that will be used for callback authentication.
/**
* Creates a new instance of this authentication handler. All initialization
* will be done lazily to avoid unnecessary performance hits, particularly
* for cases in which simple authentication will be used as it does not
* require any particularly expensive processing.
*
* @param reader The LDAP reader that will be used to read data from
* the server.
* @param writer The LDAP writer that will be used to send data to
* the server.
* @param hostName The host name used to connect to the remote system
* (fully-qualified if possible).
* @param nextMessageID The atomic integer that will be used to obtain
* message IDs for request messages.
*/
{
this.nextMessageID = nextMessageID;
secureRandom = null;
}
/**
* Retrieves a list of the SASL mechanisms that are supported by this client
* library.
*
* @return A list of the SASL mechanisms that are supported by this client
* library.
*/
{
return new String[]
{
};
}
/**
* Retrieves a list of the SASL properties that may be provided for the
* specified SASL mechanism, mapped from the property names to their
* corresponding descriptions.
*
* @param mechanism The name of the SASL mechanism for which to obtain the
* list of supported properties.
*
* @return A list of the SASL properties that may be provided for the
* specified SASL mechanism, mapped from the property names to their
* corresponding descriptions.
*/
{
{
return getSASLAnonymousProperties();
}
{
return getSASLCRAMMD5Properties();
}
{
return getSASLDigestMD5Properties();
}
{
return getSASLExternalProperties();
}
{
return getSASLGSSAPIProperties();
}
{
return getSASLPlainProperties();
}
else
{
// This is an unsupported mechanism.
return null;
}
}
/**
* Processes a bind using simple authentication with the provided information.
* If the bind fails, then an exception will be thrown with information about
* the reason for the failure. If the bind is successful but there may be
* some special information that the client should be given, then it will be
* returned as a String.
*
* @param ldapVersion The LDAP protocol version to use for the bind
* request.
* @param bindDN The DN to use to bind to the Directory Server, or
* <CODE>null</CODE> if it is to be an anonymous
* bind.
* @param bindPassword The password to use to bind to the Directory
* Server, or <CODE>null</CODE> if it is to be an
* anonymous bind.
* @param requestControls The set of controls to include the request to the
* server.
* @param responseControls A list to hold the set of controls included in
* the response from the server.
*
* @return A message providing additional information about the bind if
* appropriate, or <CODE>null</CODE> if there is no special
* information available.
*
* @throws ClientException If a client-side problem prevents the bind
* attempt from succeeding.
*
* @throws LDAPException If the bind fails or some other server-side problem
* occurs during processing.
*/
throws ClientException, LDAPException
{
//Password is empty, set it to ByteString.empty.
if (bindPassword == null)
{
}
// Make sure that critical elements aren't null.
{
}
// Create the bind request and send it to the server.
try
{
}
catch (IOException ioe)
{
throw new ClientException(
}
catch (Exception e)
{
message, e);
}
// Read the response from the server.
try
{
if (responseMessage == null)
{
message);
}
}
catch (IOException ioe)
{
throw new ClientException(
}
catch (ASN1Exception ae)
{
}
catch (LDAPException le)
{
}
catch (Exception e)
{
throw new ClientException(
}
// See if there are any controls in the response. If so, then add them to
// the response controls list.
{
}
// Look at the protocol op from the response. If it's a bind response, then
// continue. If it's an extended response, then it could be a notice of
// disconnection so check for that. Otherwise, generate an error.
switch (responseMessage.getProtocolOpType())
{
case OP_TYPE_BIND_RESPONSE:
// We'll deal with this later.
break;
if ((responseOID != null) &&
{
}
else
{
message);
}
default:
throw new ClientException(
}
{
// FIXME -- Need to look for things like password expiration warning,
// reset notice, etc.
return null;
}
// FIXME -- Add support for referrals.
}
/**
* Processes a SASL bind using the provided information. If the bind fails,
* then an exception will be thrown with information about the reason for the
* failure. If the bind is successful but there may be some special
* information that the client should be given, then it will be returned as a
* String.
*
* @param bindDN The DN to use to bind to the Directory Server, or
* <CODE>null</CODE> if the authentication identity
* is to be set through some other means.
* @param bindPassword The password to use to bind to the Directory
* Server, or <CODE>null</CODE> if this is not a
* password-based SASL mechanism.
* @param mechanism The name of the SASL mechanism to use to
* authenticate to the Directory Server.
* @param saslProperties A set of additional properties that may be needed
* to process the SASL bind.
* @param requestControls The set of controls to include the request to the
* server.
* @param responseControls A list to hold the set of controls included in
* the response from the server.
*
* @return A message providing additional information about the bind if
* appropriate, or <CODE>null</CODE> if there is no special
* information available.
*
* @throws ClientException If a client-side problem prevents the bind
* attempt from succeeding.
*
* @throws LDAPException If the bind fails or some other server-side problem
* occurs during processing.
*/
throws ClientException, LDAPException
{
// Make sure that critical elements aren't null.
{
}
{
throw new ClientException(
}
// Look at the mechanism name and call the appropriate method to process
// the request.
{
}
{
}
{
}
{
}
{
}
{
}
else
{
throw new ClientException(
}
}
/**
* Processes a SASL ANONYMOUS bind with the provided information.
*
* @param bindDN The DN to use to bind to the Directory Server, or
* <CODE>null</CODE> if the authentication identity
* is to be set through some other means.
* @param saslProperties A set of additional properties that may be needed
* to process the SASL bind.
* @param requestControls The set of controls to include the request to the
* server.
* @param responseControls A list to hold the set of controls included in
* the response from the server.
*
* @return A message providing additional information about the bind if
* appropriate, or <CODE>null</CODE> if there is no special
* information available.
*
* @throws ClientException If a client-side problem prevents the bind
* attempt from succeeding.
*
* @throws LDAPException If the bind fails or some other server-side problem
* occurs during processing.
*/
throws ClientException, LDAPException
{
// Evaluate the properties provided. The only one we'll allow is the trace
// property, but it is not required.
{
// This is fine because there are no required properties for this
// mechanism.
}
else
{
while (propertyNames.hasNext())
{
{
// This is acceptable, and we'll take any single value.
{
{
message);
}
}
}
else
{
message);
}
}
}
// Construct the bind request and send it to the server.
{
}
else
{
}
try
{
}
catch (IOException ioe)
{
throw new ClientException(
}
catch (Exception e)
{
message, e);
}
// Read the response from the server.
try
{
if (responseMessage == null)
{
message);
}
}
catch (IOException ioe)
{
throw new ClientException(
}
catch (ASN1Exception ae)
{
}
catch (LDAPException le)
{
}
catch (Exception e)
{
throw new ClientException(
}
// See if there are any controls in the response. If so, then add them to
// the response controls list.
{
}
// Look at the protocol op from the response. If it's a bind response, then
// continue. If it's an extended response, then it could be a notice of
// disconnection so check for that. Otherwise, generate an error.
switch (responseMessage.getProtocolOpType())
{
case OP_TYPE_BIND_RESPONSE:
// We'll deal with this later.
break;
if ((responseOID != null) &&
{
}
else
{
message);
}
default:
message);
}
{
// FIXME -- Need to look for things like password expiration warning,
// reset notice, etc.
return null;
}
// FIXME -- Add support for referrals.
}
/**
* Retrieves the set of properties that a client may provide when performing a
* SASL ANONYMOUS bind, mapped from the property names to their corresponding
* descriptions.
*
* @return The set of properties that a client may provide when performing a
* SASL ANONYMOUS bind, mapped from the property names to their
* corresponding descriptions.
*/
{
return properties;
}
/**
* Processes a SASL CRAM-MD5 bind with the provided information.
*
* @param bindDN The DN to use to bind to the Directory Server, or
* <CODE>null</CODE> if the authentication identity
* is to be set through some other means.
* @param bindPassword The password to use to bind to the Directory
* Server.
* @param saslProperties A set of additional properties that may be needed
* to process the SASL bind.
* @param requestControls The set of controls to include the request to the
* server.
* @param responseControls A list to hold the set of controls included in
* the response from the server.
*
* @return A message providing additional information about the bind if
* appropriate, or <CODE>null</CODE> if there is no special
* information available.
*
* @throws ClientException If a client-side problem prevents the bind
* attempt from succeeding.
*
* @throws LDAPException If the bind fails or some other server-side problem
* occurs during processing.
*/
throws ClientException, LDAPException
{
// Evaluate the properties provided. The authID is required, no other
// properties are allowed.
{
throw new ClientException(
}
while (propertyNames.hasNext())
{
{
{
{
message);
}
}
}
else
{
throw new ClientException(
}
}
// Make sure that the authID was provided.
{
throw new ClientException(
}
// Set password to ByteString.empty if the password is null.
if (bindPassword == null)
{
}
// Construct the initial bind request to send to the server. In this case,
// we'll simply indicate that we want to use CRAM-MD5 so the server will
// send us the challenge.
// FIXME -- Should we include request controls in both stages or just the
// second stage?
try
{
}
catch (IOException ioe)
{
throw new ClientException(
}
catch (Exception e)
{
message, e);
}
// Read the response from the server.
try
{
if (responseMessage1 == null)
{
message);
}
}
catch (IOException ioe)
{
throw new ClientException(
}
catch (ASN1Exception ae)
{
}
catch (LDAPException le)
{
}
catch (Exception e)
{
throw new ClientException(
}
// Look at the protocol op from the response. If it's a bind response, then
// continue. If it's an extended response, then it could be a notice of
// disconnection so check for that. Otherwise, generate an error.
switch (responseMessage1.getProtocolOpType())
{
case OP_TYPE_BIND_RESPONSE:
// We'll deal with this later.
break;
if ((responseOID != null) &&
{
}
else
{
message);
}
default:
throw new ClientException(
}
// Make sure that the bind response has the "SASL bind in progress" result
// code.
{
if (errorMessage == null)
{
}
}
// Make sure that the bind response contains SASL credentials with the
// challenge to use for the next stage of the bind.
if (serverChallenge == null)
{
}
// Use the provided password and credentials to generate the CRAM-MD5
// response.
// Create and send the second bind request to the server.
try
{
}
catch (IOException ioe)
{
throw new ClientException(
}
catch (Exception e)
{
throw new ClientException(
}
// Read the response from the server.
try
{
if (responseMessage2 == null)
{
message);
}
}
catch (IOException ioe)
{
throw new ClientException(
}
catch (ASN1Exception ae)
{
}
catch (LDAPException le)
{
}
catch (Exception e)
{
throw new ClientException(
}
// See if there are any controls in the response. If so, then add them to
// the response controls list.
{
}
// Look at the protocol op from the response. If it's a bind response, then
// continue. If it's an extended response, then it could be a notice of
// disconnection so check for that. Otherwise, generate an error.
switch (responseMessage2.getProtocolOpType())
{
case OP_TYPE_BIND_RESPONSE:
// We'll deal with this later.
break;
if ((responseOID != null) &&
{
}
else
{
message);
}
default:
throw new ClientException(
}
{
// FIXME -- Need to look for things like password expiration warning,
// reset notice, etc.
return null;
}
// FIXME -- Add support for referrals.
}
/**
* Generates the appropriate HMAC-MD5 digest for a CRAM-MD5 authentication
* with the given information.
*
* @param password The clear-text password to use when generating the
* digest.
* @param challenge The server-supplied challenge to use when generating the
* digest.
*
* @return The generated HMAC-MD5 digest for CRAM-MD5 authentication.
*
* @throws ClientException If a problem occurs while attempting to perform
* the necessary initialization.
*/
throws ClientException
{
// Perform the necessary initialization if it hasn't been done yet.
{
try
{
}
catch (Exception e)
{
getExceptionMessage(e));
message, e);
}
}
{
iPad = new byte[HMAC_MD5_BLOCK_LENGTH];
oPad = new byte[HMAC_MD5_BLOCK_LENGTH];
}
// Get the byte arrays backing the password and challenge.
byte[] p = password.toByteArray();
byte[] c = challenge.toByteArray();
// If the password is longer than the HMAC-MD5 block length, then use an
// MD5 digest of the password rather than the password itself.
{
}
// Create byte arrays with data needed for the hash generation.
// Iterate through the bytes in the key and XOR them with the iPad and
// oPad as appropriate.
for (int i=0; i < p.length; i++)
{
iPadAndData[i] ^= p[i];
oPadAndHash[i] ^= p[i];
}
// Copy an MD5 digest of the iPad-XORed key and the data into the array to
// be hashed.
// Calculate an MD5 digest of the resulting array and get the corresponding
// hex string representation.
for (byte b : digestBytes)
{
}
}
/**
* Retrieves the set of properties that a client may provide when performing a
* SASL CRAM-MD5 bind, mapped from the property names to their corresponding
* descriptions.
*
* @return The set of properties that a client may provide when performing a
* SASL CRAM-MD5 bind, mapped from the property names to their
* corresponding descriptions.
*/
{
return properties;
}
/**
* Processes a SASL DIGEST-MD5 bind with the provided information.
*
* @param bindDN The DN to use to bind to the Directory Server, or
* <CODE>null</CODE> if the authentication identity
* is to be set through some other means.
* @param bindPassword The password to use to bind to the Directory
* Server.
* @param saslProperties A set of additional properties that may be needed
* to process the SASL bind.
* @param requestControls The set of controls to include the request to the
* server.
* @param responseControls A list to hold the set of controls included in
* the response from the server.
*
* @return A message providing additional information about the bind if
* appropriate, or <CODE>null</CODE> if there is no special
* information available.
*
* @throws ClientException If a client-side problem prevents the bind
* attempt from succeeding.
*
* @throws LDAPException If the bind fails or some other server-side problem
* occurs during processing.
*/
throws ClientException, LDAPException
{
boolean realmSetFromProperty = false;
// Evaluate the properties provided. The authID is required. The realm,
// QoP, digest URI, and authzID are optional.
{
message);
}
while (propertyNames.hasNext())
{
{
{
{
message);
}
}
}
{
{
realmSetFromProperty = true;
{
message);
}
}
}
{
{
{
message);
}
{
// This is always fine.
}
{
// FIXME -- Add support for integrity and confidentiality.
message);
}
else
{
// This is an illegal value.
message);
}
}
}
{
{
{
message);
}
}
}
{
{
{
message);
}
}
}
else
{
message);
}
}
// Make sure that the authID was provided.
{
message);
}
// Set password to ByteString.empty if the password is null.
if (bindPassword == null)
{
}
// Construct the initial bind request to send to the server. In this case,
// we'll simply indicate that we want to use DIGEST-MD5 so the server will
// send us the challenge.
// FIXME -- Should we include request controls in both stages or just the
// second stage?
try
{
}
catch (IOException ioe)
{
throw new ClientException(
}
catch (Exception e)
{
message, e);
}
// Read the response from the server.
try
{
if (responseMessage1 == null)
{
message);
}
}
catch (IOException ioe)
{
throw new ClientException(
}
catch (ASN1Exception ae)
{
}
catch (LDAPException le)
{
}
catch (Exception e)
{
throw new ClientException(
}
// Look at the protocol op from the response. If it's a bind response, then
// continue. If it's an extended response, then it could be a notice of
// disconnection so check for that. Otherwise, generate an error.
switch (responseMessage1.getProtocolOpType())
{
case OP_TYPE_BIND_RESPONSE:
// We'll deal with this later.
break;
if ((responseOID != null) &&
{
}
else
{
message);
}
default:
throw new ClientException(
}
// Make sure that the bind response has the "SASL bind in progress" result
// code.
{
if (errorMessage == null)
{
}
}
// Make sure that the bind response contains SASL credentials with the
// information to use for the next stage of the bind.
if (serverCredentials == null)
{
}
// Parse the server SASL credentials to get the necessary information. In
// particular, look at the realm, the nonce, the QoP modes, and the charset.
// We'll only care about the realm if none was provided in the SASL
// properties and only one was provided in the server SASL credentials.
boolean useUTF8 = false;
int pos = 0;
{
if (equalPos < 0)
{
// This is bad because we're not at the end of the string but we don't
credString, pos);
}
{
// The value must be the string "utf-8". If not, that's an error.
{
}
useUTF8 = true;
}
{
// This will only be of interest to us if there is only a single realm
// in the server credentials and none was provided as a client-side
// property.
if (! realmSetFromProperty)
{
{
// No other realm was specified, so we'll use this one for now.
realm = tokenValue;
}
else
{
// This must mean that there are multiple realms in the server
// credentials. In that case, we'll not provide any realm at all.
// To make sure that happens, pretend that the client specified the
// realm.
realmSetFromProperty = true;
}
}
}
{
nonce = tokenValue;
}
{
// The QoP modes provided by the server should be a comma-delimited
// list. Decode that list and make sure the QoP we have chosen is in
// that list.
while (tokenizer.hasMoreTokens())
{
}
{
message);
}
}
else
{
// Other values may have been provided, but they aren't of interest to
// us because they shouldn't change anything about the way we encode the
// second part of the request. Rather than attempt to examine them,
// we'll assume that the server sent a valid response.
}
}
// Make sure that the nonce was included in the response from the server.
{
}
// Generate the cnonce that we will use for this request.
// Generate the response digest, and initialize the necessary remaining
// variables to use in the generation of that digest.
try
{
}
catch (ClientException ce)
{
throw ce;
}
catch (Exception e)
{
get(getExceptionMessage(e));
throw new ClientException(
}
// Generate the SASL credentials for the second bind request.
{
}
if (useUTF8)
{
}
{
}
// Generate and send the second bind request.
try
{
}
catch (IOException ioe)
{
throw new ClientException(
}
catch (Exception e)
{
message, e);
}
// Read the response from the server.
try
{
if (responseMessage2 == null)
{
message);
}
}
catch (IOException ioe)
{
throw new ClientException(
}
catch (ASN1Exception ae)
{
}
catch (LDAPException le)
{
}
catch (Exception e)
{
throw new ClientException(
}
// See if there are any controls in the response. If so, then add them to
// the response controls list.
{
}
// Look at the protocol op from the response. If it's a bind response, then
// continue. If it's an extended response, then it could be a notice of
// disconnection so check for that. Otherwise, generate an error.
switch (responseMessage2.getProtocolOpType())
{
case OP_TYPE_BIND_RESPONSE:
// We'll deal with this later.
break;
if ((responseOID != null) &&
{
}
else
{
message);
}
default:
throw new ClientException(
}
{
// FIXME -- Add support for referrals.
null);
}
// Make sure that the bind response included server SASL credentials with
// the appropriate rspauth value.
if (rspAuthCreds == null)
{
}
{
}
byte[] serverRspAuth;
try
{
}
catch (Exception e)
{
getExceptionMessage(e));
}
byte[] clientRspAuth;
try
{
}
catch (Exception e)
{
getExceptionMessage(e));
throw new ClientException(
}
{
throw new ClientException(
}
// FIXME -- Need to look for things like password expiration warning,
// reset notice, etc.
return null;
}
/**
* Reads the next token from the provided credentials string using the
* provided information. If the token is surrounded by quotation marks, then
* the token returned will not include those quotation marks.
*
* @param credentials The credentials string from which to read the token.
* @param startPos The position of the first character of the token to
* read.
* @param length The total number of characters in the credentials
* string.
* @param token The buffer into which the token is to be placed.
*
* @return The position at which the next token should start, or a value
* greater than or equal to the length of the string if there are no
* more tokens.
*
* @throws LDAPException If a problem occurs while attempting to read the
* token.
*/
throws LDAPException
{
// If the position is greater than or equal to the length, then we shouldn't
// do anything.
{
return startPos;
}
// Look at the first character to see if it's an empty string or the string
// is quoted.
boolean isEscaped = false;
boolean isQuoted = false;
if (c == ',')
{
// This must be a zero-length token, so we'll just return the next
// position.
return pos;
}
else if (c == '"')
{
// The string is quoted, so we'll ignore this character, and we'll keep
// reading until we find the unescaped closing quote followed by a comma
// or the end of the string.
isQuoted = true;
}
else if (c == '\\')
{
// The next character is escaped, so we'll take it no matter what.
isEscaped = true;
}
else
{
// The string is not quoted, and this is the first character. Store this
// character and keep reading until we find a comma or the end of the
// string.
}
// Enter a loop, reading until we find the appropriate criteria for the end
// of the token.
{
if (isEscaped)
{
// The previous character was an escape, so we'll take this no matter
// what.
isEscaped = false;
}
else if (c == ',')
{
// If this is a quoted string, then this comma is part of the token.
// Otherwise, it's the end of the token.
if (isQuoted)
{
}
else
{
break;
}
}
else if (c == '"')
{
if (isQuoted)
{
// This should be the end of the token, but in order for it to be
// valid it must be followed by a comma or the end of the string.
{
// We have hit the end of the string, so this is fine.
break;
}
else
{
if (c2 == ',')
{
// We have hit the end of the token, so this is fine.
break;
}
else
{
// We found the closing quote before the end of the token. This
// is not fine.
message);
}
}
}
else
{
// This must be part of the value, so we'll take it.
}
}
else if (c == '\\')
{
// The next character is escaped. We'll set a flag so we know to
// accept it, but will not include the backspace itself.
isEscaped = true;
}
else
{
}
}
return pos;
}
/**
* Generates a cnonce value to use during the DIGEST-MD5 authentication
* process.
*
* @return The cnonce that should be used for DIGEST-MD5 authentication.
*/
{
if (secureRandom == null)
{
secureRandom = new SecureRandom();
}
byte[] cnonceBytes = new byte[16];
}
/**
* Generates the appropriate DIGEST-MD5 response for the provided set of
* information.
*
* @param authID The username from the authentication request.
* @param authzID The authorization ID from the request, or
* <CODE>null</CODE> if there is none.
* @param password The clear-text password for the user.
* @param realm The realm for which the authentication is to be
* performed.
* @param nonce The random data generated by the server for use in the
* digest.
* @param cnonce The random data generated by the client for use in the
* digest.
* @param nonceCount The 8-digit hex string indicating the number of times
* the provided nonce has been used by the client.
* @param digestURI The digest URI that specifies the service and host for
* which the authentication is being performed.
* @param qop The quality of protection string for the
* authentication.
* @param charset The character set used to encode the information.
*
* @return The DIGEST-MD5 response for the provided set of information.
*
* @throws ClientException If a problem occurs while attempting to
* initialize the MD5 digest.
*
* @throws UnsupportedEncodingException If the specified character set is
* invalid for some reason.
*/
{
// Perform the necessary initialization if it hasn't been done yet.
{
try
{
}
catch (Exception e)
{
getExceptionMessage(e));
message, e);
}
}
// Get a hash of "username:realm:password".
// Next, get a hash of "urpHash:nonce:cnonce[:authzid]".
{
}
// Next, get a hash of "AUTHENTICATE:digesturi".
// Get hex string representations of the last two hashes.
// Put together the final string to hash, consisting of
// "a1HashHex:nonce:nonceCount:cnonce:qop:a2HashHex" and get its digest.
}
/**
* Generates the appropriate DIGEST-MD5 rspauth digest using the provided
* information.
*
* @param authID The username from the authentication request.
* @param authzID The authorization ID from the request, or
* <CODE>null</CODE> if there is none.
* @param password The clear-text password for the user.
* @param realm The realm for which the authentication is to be
* performed.
* @param nonce The random data generated by the server for use in the
* digest.
* @param cnonce The random data generated by the client for use in the
* digest.
* @param nonceCount The 8-digit hex string indicating the number of times
* the provided nonce has been used by the client.
* @param digestURI The digest URI that specifies the service and host for
* which the authentication is being performed.
* @param qop The quality of protection string for the
* authentication.
* @param charset The character set used to encode the information.
*
* @return The DIGEST-MD5 response for the provided set of information.
*
* @throws UnsupportedEncodingException If the specified character set is
* invalid for some reason.
*/
throws UnsupportedEncodingException
{
// First, get a hash of "username:realm:password".
// Next, get a hash of "urpHash:nonce:cnonce[:authzid]".
{
}
// Next, get a hash of "AUTHENTICATE:digesturi".
{
a2String += ":00000000000000000000000000000000";
}
// Get hex string representations of the last two hashes.
// Put together the final string to hash, consisting of
// "a1HashHex:nonce:nonceCount:cnonce:qop:a2HashHex" and get its digest.
}
/**
* Retrieves a hexadecimal string representation of the contents of the
* provided byte array.
*
* @param byteArray The byte array for which to obtain the hexadecimal
* string representation.
*
* @return The hexadecimal string representation of the contents of the
* provided byte array.
*/
{
for (byte b : byteArray)
{
}
}
/**
* Retrieves the set of properties that a client may provide when performing a
* SASL DIGEST-MD5 bind, mapped from the property names to their corresponding
* descriptions.
*
* @return The set of properties that a client may provide when performing a
* SASL DIGEST-MD5 bind, mapped from the property names to their
* corresponding descriptions.
*/
{
return properties;
}
/**
* Processes a SASL EXTERNAL bind with the provided information.
*
* @param bindDN The DN to use to bind to the Directory Server, or
* <CODE>null</CODE> if the authentication identity
* is to be set through some other means.
* @param saslProperties A set of additional properties that may be needed
* to process the SASL bind. SASL EXTERNAL does not
* take any properties, so this should be empty or
* <CODE>null</CODE>.
* @param requestControls The set of controls to include the request to the
* server.
* @param responseControls A list to hold the set of controls included in
* the response from the server.
*
* @return A message providing additional information about the bind if
* appropriate, or <CODE>null</CODE> if there is no special
* information available.
*
* @throws ClientException If a client-side problem prevents the bind
* attempt from succeeding.
*
* @throws LDAPException If the bind fails or some other server-side problem
* occurs during processing.
*/
throws ClientException, LDAPException
{
// Make sure that no SASL properties were provided.
{
throw new ClientException(
}
// Construct the bind request and send it to the server.
try
{
}
catch (IOException ioe)
{
throw new ClientException(
}
catch (Exception e)
{
message, e);
}
// Read the response from the server.
try
{
if (responseMessage == null)
{
message);
}
}
catch (IOException ioe)
{
throw new ClientException(
}
catch (ASN1Exception ae)
{
}
catch (LDAPException le)
{
}
catch (Exception e)
{
throw new ClientException(
}
// See if there are any controls in the response. If so, then add them to
// the response controls list.
{
}
// Look at the protocol op from the response. If it's a bind response, then
// continue. If it's an extended response, then it could be a notice of
// disconnection so check for that. Otherwise, generate an error.
switch (responseMessage.getProtocolOpType())
{
case OP_TYPE_BIND_RESPONSE:
// We'll deal with this later.
break;
if ((responseOID != null) &&
{
}
else
{
message);
}
default:
throw new ClientException(
}
{
// FIXME -- Need to look for things like password expiration warning,
// reset notice, etc.
return null;
}
// FIXME -- Add support for referrals.
}
/**
* Retrieves the set of properties that a client may provide when performing a
* SASL EXTERNAL bind, mapped from the property names to their corresponding
* descriptions.
*
* @return The set of properties that a client may provide when performing a
* SASL EXTERNAL bind, mapped from the property names to their
* corresponding descriptions.
*/
{
// There are no properties for the SASL EXTERNAL mechanism.
}
/**
* Processes a SASL GSSAPI bind with the provided information.
*
* @param bindDN The DN to use to bind to the Directory Server, or
* <CODE>null</CODE> if the authentication identity
* is to be set through some other means.
* @param bindPassword The password to use to bind to the Directory
* Server.
* @param saslProperties A set of additional properties that may be needed
* to process the SASL bind. SASL EXTERNAL does not
* take any properties, so this should be empty or
* <CODE>null</CODE>.
* @param requestControls The set of controls to include the request to the
* server.
* @param responseControls A list to hold the set of controls included in
* the response from the server.
*
* @return A message providing additional information about the bind if
* appropriate, or <CODE>null</CODE> if there is no special
* information available.
*
* @throws ClientException If a client-side problem prevents the bind
* attempt from succeeding.
*
* @throws LDAPException If the bind fails or some other server-side problem
* occurs during processing.
*/
throws ClientException, LDAPException
{
gssapiAuthID = null;
gssapiQoP = "auth";
if (bindPassword == null)
{
gssapiAuthPW = null;
}
else
{
}
// Evaluate the properties provided. The authID is required. The authzID,
// KDC, QoP, and realm are optional.
{
throw new ClientException(
}
while (propertyNames.hasNext())
{
{
{
{
message);
}
}
}
{
{
{
message);
}
}
}
{
{
{
message);
}
}
}
{
{
{
message);
}
{
// This is always fine.
}
{
// FIXME -- Add support for integrity and confidentiality.
message);
}
else
{
// This is an illegal value.
message);
}
}
}
{
{
{
message);
}
}
}
else
{
throw new ClientException(
}
}
// Make sure that the authID was provided.
{
throw new ClientException(
}
// See if an authzID was provided. If not, then use the authID.
if (gssapiAuthzID == null)
{
}
// that will allow them to be used. Otherwise, we'll hope that the
// underlying system has a valid Kerberos client configuration.
{
}
{
}
// Since we're going to be using JAAS behind the scenes, we need to have a
// JAAS configuration. Rather than always requiring the user to provide it,
// we'll write one to a temporary file that will be deleted when the JVM
// exits.
try
{
w.newLine();
w.write(" com.sun.security.auth.module.Krb5LoginModule required " +
"client=TRUE useTicketCache=TRUE;");
w.newLine();
w.write("};");
w.newLine();
w.flush();
w.close();
}
catch (Exception e)
{
getExceptionMessage(e));
throw new ClientException(
}
// The rest of this code must be executed via JAAS, so it will have to go
// in the "run" method.
try
{
}
catch (Exception e)
{
getExceptionMessage(e));
throw new ClientException(
}
try
{
}
catch (Exception e)
{
if (e instanceof ClientException)
{
throw (ClientException) e;
}
else if (e instanceof LDAPException)
{
throw (LDAPException) e;
}
getExceptionMessage(e));
throw new ClientException(
}
// FIXME -- Need to make sure we handle request and response controls
// properly, and also check for any possible message to send back to the
// client.
return null;
}
/**
* Retrieves the set of properties that a client may provide when performing a
* SASL EXTERNAL bind, mapped from the property names to their corresponding
* descriptions.
*
* @return The set of properties that a client may provide when performing a
* SASL EXTERNAL bind, mapped from the property names to their
* corresponding descriptions.
*/
{
return properties;
}
/**
* Processes a SASL PLAIN bind with the provided information.
*
* @param bindDN The DN to use to bind to the Directory Server, or
* <CODE>null</CODE> if the authentication identity
* is to be set through some other means.
* @param bindPassword The password to use to bind to the Directory
* Server.
* @param saslProperties A set of additional properties that may be needed
* to process the SASL bind.
* @param requestControls The set of controls to include the request to the
* server.
* @param responseControls A list to hold the set of controls included in
* the response from the server.
*
* @return A message providing additional information about the bind if
* appropriate, or <CODE>null</CODE> if there is no special
* information available.
*
* @throws ClientException If a client-side problem prevents the bind
* attempt from succeeding.
*
* @throws LDAPException If the bind fails or some other server-side problem
* occurs during processing.
*/
throws ClientException, LDAPException
{
// Evaluate the properties provided. The authID is required, and authzID is
// optional.
{
throw new ClientException(
}
while (propertyNames.hasNext())
{
{
{
{
message);
}
}
}
{
{
{
message);
}
}
}
else
{
throw new ClientException(
}
}
// Make sure that at least the authID was provided.
{
throw new ClientException(
}
// Set password to ByteString.empty if the password is null.
if (bindPassword == null)
{
}
// Construct the bind request and send it to the server.
{
}
try
{
}
catch (IOException ioe)
{
throw new ClientException(
}
catch (Exception e)
{
message, e);
}
// Read the response from the server.
try
{
if (responseMessage == null)
{
message);
}
}
catch (IOException ioe)
{
throw new ClientException(
}
catch (ASN1Exception ae)
{
}
catch (LDAPException le)
{
}
catch (Exception e)
{
throw new ClientException(
}
// See if there are any controls in the response. If so, then add them to
// the response controls list.
{
}
// Look at the protocol op from the response. If it's a bind response, then
// continue. If it's an extended response, then it could be a notice of
// disconnection so check for that. Otherwise, generate an error.
switch (responseMessage.getProtocolOpType())
{
case OP_TYPE_BIND_RESPONSE:
// We'll deal with this later.
break;
if ((responseOID != null) &&
{
}
else
{
message);
}
default:
throw new ClientException(
}
{
// FIXME -- Need to look for things like password expiration warning,
// reset notice, etc.
return null;
}
// FIXME -- Add support for referrals.
}
/**
* Retrieves the set of properties that a client may provide when performing a
* SASL PLAIN bind, mapped from the property names to their corresponding
* descriptions.
*
* @return The set of properties that a client may provide when performing a
* SASL PLAIN bind, mapped from the property names to their
* corresponding descriptions.
*/
{
return properties;
}
/**
* Performs a privileged operation under JAAS so that the local authentication
* information can be available for the SASL bind to the Directory Server.
*
* @return A placeholder object in order to comply with the
* <CODE>PrivilegedExceptionAction</CODE> interface.
*
* @throws ClientException If a client-side problem occurs during the bind
* processing.
*
* @throws LDAPException If a server-side problem occurs during the bind
* processing.
*/
throws ClientException, LDAPException
{
if (saslMechanism == null)
{
throw new ClientException(
}
{
// Create the property map that will be used by the internal SASL handler.
// Create the SASL client that we will use to actually perform the
// authentication.
try
{
saslProperties, this);
}
catch (Exception e)
{
getExceptionMessage(e));
throw new ClientException(
}
// Get the SASL credentials to include in the initial bind request.
if (saslClient.hasInitialResponse())
{
try
{
}
catch (Exception e)
{
get(getExceptionMessage(e));
throw new ClientException(
message, e);
}
}
else
{
}
// FIXME -- Add controls here?
try
{
}
catch (IOException ioe)
{
throw new ClientException(
}
catch (Exception e)
{
message, e);
}
// Read the response from the server.
try
{
if (responseMessage == null)
{
message);
}
}
catch (IOException ioe)
{
throw new ClientException(
}
catch (ASN1Exception ae)
{
}
catch (LDAPException le)
{
}
catch (Exception e)
{
throw new ClientException(
}
// FIXME -- Handle response controls.
// Look at the protocol op from the response. If it's a bind response,
// then continue. If it's an extended response, then it could be a notice
// of disconnection so check for that. Otherwise, generate an error.
switch (responseMessage.getProtocolOpType())
{
case OP_TYPE_BIND_RESPONSE:
// We'll deal with this later.
break;
if ((responseOID != null) &&
{
}
else
{
message);
}
default:
message);
}
while (true)
{
{
// We should be done after this, but we still need to look for and
// handle the server SASL credentials.
if (serverSASLCredentials != null)
{
try
{
}
catch (Exception e)
{
get(getExceptionMessage(e));
message, e);
}
}
// Just to be sure, check that the login really is complete.
if (! saslClient.isComplete())
{
message);
}
break;
}
{
// Read the response and process the server SASL credentials.
byte[] credBytes;
try
{
if (serverSASLCredentials == null)
{
}
else
{
}
}
catch (Exception e)
{
get(getExceptionMessage(e));
message, e);
}
// Send the next bind in the sequence to the server.
// FIXME -- Add controls here?
try
{
}
catch (IOException ioe)
{
}
catch (Exception e)
{
message, e);
}
// Read the response from the server.
try
{
if (responseMessage == null)
{
message);
}
}
catch (IOException ioe)
{
}
catch (ASN1Exception ae)
{
}
catch (LDAPException le)
{
}
catch (Exception e)
{
getExceptionMessage(e));
message, e);
}
// FIXME -- Handle response controls.
// Look at the protocol op from the response. If it's a bind
// response, then continue. If it's an extended response, then it
// could be a notice of disconnection so check for that. Otherwise,
// generate an error.
switch (responseMessage.getProtocolOpType())
{
case OP_TYPE_BIND_RESPONSE:
// We'll deal with this later.
break;
if ((responseOID != null) &&
{
message);
}
else
{
throw new ClientException(
}
default:
message);
}
}
else
{
// This is an error.
null);
}
}
}
else
{
throw new ClientException(
}
// FIXME -- Need to look for things like password expiration warning, reset
// notice, etc.
return null;
}
/**
* Handles the authentication callbacks to provide information needed by the
* JAAS login process.
*
* @param callbacks The callbacks needed to provide information for the JAAS
* login process.
*
* @throws UnsupportedCallbackException If an unexpected callback is
* included in the provided set.
*/
throws UnsupportedCallbackException
{
if (saslMechanism == null)
{
}
{
{
if (cb instanceof NameCallback)
{
}
else if (cb instanceof PasswordCallback)
{
if (gssapiAuthPW == null)
{
}
}
else
{
}
}
}
else
{
}
}
/**
* Uses the "Who Am I?" extended operation to request that the server provide
* the client with the authorization identity for this connection.
*
* @return An ASN.1 octet string containing the authorization identity, or
* <CODE>null</CODE> if the client is not authenticated or is
* authenticated anonymously.
*
* @throws ClientException If a client-side problem occurs during the
* request processing.
*
* @throws LDAPException If a server-side problem occurs during the request
* processing.
*/
throws ClientException, LDAPException
{
// Construct the extended request and send it to the server.
try
{
}
catch (IOException ioe)
{
}
catch (Exception e)
{
message, e);
}
// Read the response from the server.
try
{
if (responseMessage == null)
{
message);
}
}
catch (IOException ioe)
{
throw new ClientException(
}
catch (ASN1Exception ae)
{
}
catch (LDAPException le)
{
}
catch (Exception e)
{
throw new ClientException(
}
// If the protocol op isn't an extended response, then that's a problem.
{
throw new ClientException(
}
// Get the extended response and see if it has the "notice of disconnection"
// OID. If so, then the server is closing the connection.
if ((responseOID != null) &&
{
}
// It isn't a notice of disconnection so it must be the "Who Am I?"
// response and the value would be the authorization ID. However, first
// check that it was successful. If it was not, then fail.
{
null);
}
// Get the authorization ID (if there is one) and return it to the caller.
{
return null;
}
{
return null;
}
return authzID;
}
}