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