001: /*
002: * (C) Copyright 2000 - 2005 Nabh Information Systems, Inc.
003: *
004: * This program is free software; you can redistribute it and/or
005: * modify it under the terms of the GNU General Public License
006: * as published by the Free Software Foundation; either version 2
007: * of the License, or (at your option) any later version.
008: *
009: * This program is distributed in the hope that it will be useful,
010: * but WITHOUT ANY WARRANTY; without even the implied warranty of
011: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
012: * GNU General Public License for more details.
013: *
014: * You should have received a copy of the GNU General Public License
015: * along with this program; if not, write to the Free Software
016: * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
017: *
018: */
019: package com.nabhinc.portal.spi;
020:
021: import java.io.IOException;
022: import java.io.UnsupportedEncodingException;
023: import java.security.MessageDigest;
024: import java.security.NoSuchAlgorithmException;
025: import java.util.Arrays;
026:
027: import javax.naming.NamingException;
028:
029: import com.nabhinc.util.md.Base64;
030: import com.nabhinc.util.md.ByteChunk;
031: import com.nabhinc.util.md.CharChunk;
032: import com.nabhinc.util.md.HexUtils;
033: import com.nabhinc.ws.service.core.BaseJavaWebService;
034:
035: /**
036: *
037: *
038: * @author Padmanabh Dabke
039: * (c) 2005 Nabh Information Systems, Inc. All Rights Reserved.
040: */
041: public abstract class BaseUserServiceImpl extends BaseJavaWebService {
042: /**
043: * Digest algorithm used in storing passwords in a non-plaintext format.
044: * Valid values are those accepted for the algorithm name by the
045: * MessageDigest class, or <code>null</code> if no digesting should
046: * be performed.
047: */
048: protected String digest = null;
049:
050: /**
051: * The encoding charset for the digest.
052: */
053: protected String digestEncoding = null;
054:
055: /**
056: * The MessageDigest object for digesting user credentials (passwords).
057: */
058: protected MessageDigest md = null;
059:
060: /**
061: * Return the digest algorithm used for storing credentials.
062: */
063: public String getDigest() {
064:
065: return digest;
066:
067: }
068:
069: /**
070: * Set the digest algorithm used for storing credentials.
071: *
072: * @param digest The new digest algorithm
073: * @throws NoSuchAlgorithmException
074: */
075: public void setDigest(String digest)
076: throws NoSuchAlgorithmException {
077:
078: this .digest = digest;
079: if (digest == null) {
080: this .md = null;
081: } else {
082: this .md = (MessageDigest) MessageDigest.getInstance(digest);
083: try {
084: this .md = (MessageDigest) this .md.clone();
085: } catch (CloneNotSupportedException e) {
086: warn("Could not clone MessageDigest object.");
087: // Can't clone this object. Live with the singleton instance.
088: }
089: }
090: }
091:
092: /**
093: * Returns the digest encoding charset.
094: *
095: * @return The charset (may be null) for platform default
096: */
097: public String getDigestEncoding() {
098: return digestEncoding;
099: }
100:
101: /**
102: * Sets the digest encoding charset.
103: *
104: * @param charset The charset (null for platform default)
105: */
106: public void setDigestEncoding(String charset) {
107: digestEncoding = charset;
108: }
109:
110: protected boolean hasMessageDigest() {
111: return (md != null);
112: }
113:
114: /**
115: * Digest the password using the specified algorithm and
116: * convert the result to a corresponding hexadecimal string.
117: * If exception, the plain credentials string is returned.
118: *
119: * @param credentials Password or other credentials to use in
120: * authenticating this username
121: */
122: protected String digest(String credentials) {
123:
124: // If no MessageDigest instance is specified, return unchanged
125: if (!hasMessageDigest())
126: return (credentials);
127:
128: // Digest the user credentials and return as hexadecimal
129: synchronized (this ) {
130: try {
131: md.reset();
132:
133: byte[] bytes = null;
134: if (getDigestEncoding() == null) {
135: bytes = credentials.getBytes();
136: } else {
137: try {
138: bytes = credentials
139: .getBytes(getDigestEncoding());
140: } catch (UnsupportedEncodingException uee) {
141: error("Illegal digestEncoding: "
142: + getDigestEncoding(), uee);
143: throw new IllegalArgumentException(uee
144: .getMessage());
145: }
146: }
147: md.update(bytes);
148:
149: return (HexUtils.convert(md.digest()));
150: } catch (Exception e) {
151: error("realmBase.digest", e);
152: return (credentials);
153: }
154: }
155:
156: }
157:
158: /**
159: * Check whether the credentials presented by the user match those
160: * retrieved from the directory.
161: *
162: * @param password Stored password
163: * @param credentials Authentication credentials supplied by the user
164: *
165: * @exception NamingException if a directory server error occurs
166: */
167: protected boolean compareCredentials(String password,
168: String credentials) {
169:
170: if (password == null)
171: return (false);
172:
173: // Validate the credentials specified by the user
174: debug(" validating credentials");
175:
176: boolean validated = false;
177: if (hasMessageDigest()) {
178: // iPlanet support if the values starts with {SHA1}
179: // The string is in a format compatible with Base64.encode not
180: // the Hex encoding of the parent class.
181: if (password.startsWith("{SHA}")) {
182: /* sync since super.digest() does this same thing */
183: synchronized (this ) {
184: password = password.substring(5);
185: md.reset();
186: md.update(credentials.getBytes());
187: String digestedPassword = new String(Base64
188: .encode(md.digest()));
189: validated = password.equals(digestedPassword);
190: }
191: } else if (password.startsWith("{SSHA}")) {
192: // Bugzilla 32938
193: /* sync since super.digest() does this same thing */
194: synchronized (this ) {
195: password = password.substring(6);
196:
197: md.reset();
198: md.update(credentials.getBytes());
199:
200: // Decode stored password.
201: ByteChunk pwbc = new ByteChunk(password.length());
202: try {
203: pwbc.append(password.getBytes(), 0, password
204: .length());
205: } catch (IOException e) {
206: // Should never happen
207: error(
208: "Could not append password bytes to chunk: ",
209: e);
210: }
211:
212: CharChunk decoded = new CharChunk();
213: Base64.decode(pwbc, decoded);
214: char[] pwarray = decoded.getBuffer();
215:
216: // Split decoded password into hash and salt.
217: final int saltpos = 20;
218: byte[] hash = new byte[saltpos];
219: for (int i = 0; i < hash.length; i++) {
220: hash[i] = (byte) pwarray[i];
221: }
222:
223: byte[] salt = new byte[pwarray.length - saltpos];
224: for (int i = 0; i < salt.length; i++)
225: salt[i] = (byte) pwarray[i + saltpos];
226:
227: md.update(salt);
228: byte[] dp = md.digest();
229:
230: validated = Arrays.equals(dp, hash);
231: } // End synchronized(this) block
232: } else {
233: // Hex hashes should be compared case-insensitive
234: validated = (digest(credentials)
235: .equalsIgnoreCase(password));
236: }
237: } else
238: validated = (digest(credentials).equals(password));
239: return (validated);
240:
241: }
242:
243: }
|