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