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