001 /*
002 * CDDL HEADER START
003 *
004 * The contents of this file are subject to the terms of the
005 * Common Development and Distribution License, Version 1.0 only
006 * (the "License"). You may not use this file except in compliance
007 * with the License.
008 *
009 * You can obtain a copy of the license at
010 * trunk/opends/resource/legal-notices/OpenDS.LICENSE
011 * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
012 * See the License for the specific language governing permissions
013 * and limitations under the License.
014 *
015 * When distributing Covered Code, include this CDDL HEADER in each
016 * file and include the License file at
017 * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
018 * add the following below this CDDL HEADER, with the fields enclosed
019 * by brackets "[]" replaced with your own identifying information:
020 * Portions Copyright [yyyy] [name of copyright owner]
021 *
022 * CDDL HEADER END
023 *
024 *
025 * Copyright 2006-2008 Sun Microsystems, Inc.
026 */
027 package org.opends.server.extensions;
028
029
030
031 import java.security.MessageDigest;
032 import java.util.Arrays;
033
034 import org.opends.messages.Message;
035 import org.opends.server.admin.std.server.MD5PasswordStorageSchemeCfg;
036 import org.opends.server.api.PasswordStorageScheme;
037 import org.opends.server.config.ConfigException;
038 import org.opends.server.core.DirectoryServer;
039 import org.opends.server.loggers.debug.DebugTracer;
040 import org.opends.server.types.ByteString;
041 import org.opends.server.types.ByteStringFactory;
042 import org.opends.server.types.DebugLogLevel;
043 import org.opends.server.types.DirectoryException;
044 import org.opends.server.types.InitializationException;
045 import org.opends.server.types.ResultCode;
046 import org.opends.server.util.Base64;
047
048 import static org.opends.messages.ExtensionMessages.*;
049 import static org.opends.server.extensions.ExtensionsConstants.*;
050 import static org.opends.server.loggers.ErrorLogger.*;
051 import static org.opends.server.loggers.debug.DebugLogger.*;
052 import static org.opends.server.util.StaticUtils.*;
053
054
055
056 /**
057 * This class defines a Directory Server password storage scheme based on the
058 * MD5 algorithm defined in RFC 1321. This is a one-way digest algorithm
059 * so there is no way to retrieve the original clear-text version of the
060 * password from the hashed value (although this means that it is not suitable
061 * for things that need the clear-text password like DIGEST-MD5). This
062 * implementation does not perform any salting, which means that it is more
063 * vulnerable to dictionary attacks than salted variants.
064 */
065 public class MD5PasswordStorageScheme
066 extends PasswordStorageScheme<MD5PasswordStorageSchemeCfg>
067 {
068 /**
069 * The tracer object for the debug logger.
070 */
071 private static final DebugTracer TRACER = getTracer();
072
073 /**
074 * The fully-qualified name of this class.
075 */
076 private static final String CLASS_NAME =
077 "org.opends.server.extensions.MD5PasswordStorageScheme";
078
079
080
081 // The message digest that will actually be used to generate the MD5 hashes.
082 private MessageDigest messageDigest;
083
084 // The lock used to provide threadsafe access to the message digest.
085 private Object digestLock;
086
087
088
089 /**
090 * Creates a new instance of this password storage scheme. Note that no
091 * initialization should be performed here, as all initialization should be
092 * done in the <CODE>initializePasswordStorageScheme</CODE> method.
093 */
094 public MD5PasswordStorageScheme()
095 {
096 super();
097
098 }
099
100
101
102 /**
103 * {@inheritDoc}
104 */
105 @Override()
106 public void initializePasswordStorageScheme(
107 MD5PasswordStorageSchemeCfg configuration)
108 throws ConfigException, InitializationException
109 {
110 try
111 {
112 messageDigest = MessageDigest.getInstance(MESSAGE_DIGEST_ALGORITHM_MD5);
113 }
114 catch (Exception e)
115 {
116 if (debugEnabled())
117 {
118 TRACER.debugCaught(DebugLogLevel.ERROR, e);
119 }
120
121 Message message = ERR_PWSCHEME_CANNOT_INITIALIZE_MESSAGE_DIGEST.get(
122 MESSAGE_DIGEST_ALGORITHM_MD5, String.valueOf(e));
123 throw new InitializationException(message, e);
124 }
125
126 digestLock = new Object();
127 }
128
129
130
131 /**
132 * {@inheritDoc}
133 */
134 @Override()
135 public String getStorageSchemeName()
136 {
137 return STORAGE_SCHEME_NAME_MD5;
138 }
139
140
141
142 /**
143 * {@inheritDoc}
144 */
145 @Override()
146 public ByteString encodePassword(ByteString plaintext)
147 throws DirectoryException
148 {
149 byte[] digestBytes;
150
151 synchronized (digestLock)
152 {
153 try
154 {
155 digestBytes = messageDigest.digest(plaintext.value());
156 }
157 catch (Exception e)
158 {
159 if (debugEnabled())
160 {
161 TRACER.debugCaught(DebugLogLevel.ERROR, e);
162 }
163
164 Message message = ERR_PWSCHEME_CANNOT_ENCODE_PASSWORD.get(
165 CLASS_NAME, getExceptionMessage(e));
166 throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
167 message, e);
168 }
169 }
170
171 return ByteStringFactory.create(Base64.encode(digestBytes));
172 }
173
174
175
176 /**
177 * {@inheritDoc}
178 */
179 @Override()
180 public ByteString encodePasswordWithScheme(ByteString plaintext)
181 throws DirectoryException
182 {
183 StringBuilder buffer = new StringBuilder();
184 buffer.append('{');
185 buffer.append(STORAGE_SCHEME_NAME_MD5);
186 buffer.append('}');
187
188 byte[] digestBytes;
189
190 synchronized (digestLock)
191 {
192 try
193 {
194 digestBytes = messageDigest.digest(plaintext.value());
195 }
196 catch (Exception e)
197 {
198 if (debugEnabled())
199 {
200 TRACER.debugCaught(DebugLogLevel.ERROR, e);
201 }
202
203 Message message = ERR_PWSCHEME_CANNOT_ENCODE_PASSWORD.get(
204 CLASS_NAME, getExceptionMessage(e));
205 throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
206 message, e);
207 }
208 }
209
210 buffer.append(Base64.encode(digestBytes));
211
212
213 return ByteStringFactory.create(buffer.toString());
214 }
215
216
217
218 /**
219 * {@inheritDoc}
220 */
221 @Override()
222 public boolean passwordMatches(ByteString plaintextPassword,
223 ByteString storedPassword)
224 {
225 byte[] userPWDigestBytes;
226
227 synchronized (digestLock)
228 {
229 try
230 {
231 userPWDigestBytes = messageDigest.digest(plaintextPassword.value());
232 }
233 catch (Exception e)
234 {
235 if (debugEnabled())
236 {
237 TRACER.debugCaught(DebugLogLevel.ERROR, e);
238 }
239
240 return false;
241 }
242 }
243
244 byte[] storedPWDigestBytes;
245 try
246 {
247 storedPWDigestBytes = Base64.decode(storedPassword.stringValue());
248 }
249 catch (Exception e)
250 {
251 if (debugEnabled())
252 {
253 TRACER.debugCaught(DebugLogLevel.ERROR, e);
254 }
255
256 logError(ERR_PWSCHEME_CANNOT_BASE64_DECODE_STORED_PASSWORD.get(
257 storedPassword.stringValue(), String.valueOf(e)));
258
259 return false;
260 }
261
262 return Arrays.equals(userPWDigestBytes, storedPWDigestBytes);
263 }
264
265
266
267 /**
268 * {@inheritDoc}
269 */
270 @Override()
271 public boolean supportsAuthPasswordSyntax()
272 {
273 // This storage scheme does not support the authentication password syntax.
274 return false;
275 }
276
277
278
279 /**
280 * {@inheritDoc}
281 */
282 @Override()
283 public ByteString encodeAuthPassword(ByteString plaintext)
284 throws DirectoryException
285 {
286 Message message =
287 ERR_PWSCHEME_DOES_NOT_SUPPORT_AUTH_PASSWORD.get(getStorageSchemeName());
288 throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message);
289 }
290
291
292
293 /**
294 * {@inheritDoc}
295 */
296 @Override()
297 public boolean authPasswordMatches(ByteString plaintextPassword,
298 String authInfo, String authValue)
299 {
300 // This storage scheme does not support the authentication password syntax.
301 return false;
302 }
303
304
305
306 /**
307 * {@inheritDoc}
308 */
309 @Override()
310 public boolean isReversible()
311 {
312 return false;
313 }
314
315
316
317 /**
318 * {@inheritDoc}
319 */
320 @Override()
321 public ByteString getPlaintextValue(ByteString storedPassword)
322 throws DirectoryException
323 {
324 Message message = ERR_PWSCHEME_NOT_REVERSIBLE.get(STORAGE_SCHEME_NAME_MD5);
325 throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, message);
326 }
327
328
329
330 /**
331 * {@inheritDoc}
332 */
333 @Override()
334 public ByteString getAuthPasswordPlaintextValue(String authInfo,
335 String authValue)
336 throws DirectoryException
337 {
338 Message message =
339 ERR_PWSCHEME_DOES_NOT_SUPPORT_AUTH_PASSWORD.get(getStorageSchemeName());
340 throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message);
341 }
342
343
344
345 /**
346 * {@inheritDoc}
347 */
348 @Override()
349 public boolean isStorageSchemeSecure()
350 {
351 // MD5 may be considered reasonably secure for this purpose.
352 return true;
353 }
354 }
355