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