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.SHA1PasswordStorageSchemeCfg; 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 * SHA-1 algorithm defined in FIPS 180-1. 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 SHA1PasswordStorageScheme 052 extends PasswordStorageScheme<SHA1PasswordStorageSchemeCfg> 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.SHA1PasswordStorageScheme"; 061 062 063 064 /** The message digest that will actually be used to generate the SHA-1 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 SHA1PasswordStorageScheme() 078 { 079 super(); 080 } 081 082 083 084 /** {@inheritDoc} */ 085 @Override 086 public void initializePasswordStorageScheme( 087 SHA1PasswordStorageSchemeCfg configuration) 088 throws ConfigException, InitializationException 089 { 090 try 091 { 092 messageDigest = MessageDigest.getInstance(MESSAGE_DIGEST_ALGORITHM_SHA_1); 093 } 094 catch (Exception e) 095 { 096 logger.traceException(e); 097 098 LocalizableMessage message = ERR_PWSCHEME_CANNOT_INITIALIZE_MESSAGE_DIGEST.get( 099 MESSAGE_DIGEST_ALGORITHM_SHA_1, e); 100 throw new InitializationException(message, e); 101 } 102 103 digestLock = new Object(); 104 } 105 106 107 108 /** {@inheritDoc} */ 109 @Override 110 public String getStorageSchemeName() 111 { 112 return STORAGE_SCHEME_NAME_SHA_1; 113 } 114 115 116 117 /** {@inheritDoc} */ 118 @Override 119 public ByteString encodePassword(ByteSequence plaintext) 120 throws DirectoryException 121 { 122 byte[] digestBytes; 123 byte[] plaintextBytes = null; 124 125 synchronized (digestLock) 126 { 127 try 128 { 129 // TODO: Can we avoid this copy? 130 plaintextBytes = plaintext.toByteArray(); 131 digestBytes = messageDigest.digest(plaintextBytes); 132 } 133 catch (Exception e) 134 { 135 logger.traceException(e); 136 137 LocalizableMessage message = ERR_PWSCHEME_CANNOT_ENCODE_PASSWORD.get( 138 CLASS_NAME, getExceptionMessage(e)); 139 throw new DirectoryException(DirectoryServer.getServerErrorResultCode(), 140 message, e); 141 } 142 finally 143 { 144 if (plaintextBytes != null) 145 { 146 Arrays.fill(plaintextBytes, (byte) 0); 147 } 148 } 149 } 150 151 return ByteString.valueOfUtf8(Base64.encode(digestBytes)); 152 } 153 154 155 156 /** {@inheritDoc} */ 157 @Override 158 public ByteString encodePasswordWithScheme(ByteSequence plaintext) 159 throws DirectoryException 160 { 161 StringBuilder buffer = new StringBuilder(); 162 buffer.append('{'); 163 buffer.append(STORAGE_SCHEME_NAME_SHA_1); 164 buffer.append('}'); 165 166 // TODO: Can we avoid this copy? 167 byte[] plaintextBytes = null; 168 byte[] digestBytes; 169 170 synchronized (digestLock) 171 { 172 try 173 { 174 plaintextBytes = plaintext.toByteArray(); 175 digestBytes = messageDigest.digest(plaintextBytes); 176 } 177 catch (Exception e) 178 { 179 logger.traceException(e); 180 181 LocalizableMessage message = ERR_PWSCHEME_CANNOT_ENCODE_PASSWORD.get( 182 CLASS_NAME, getExceptionMessage(e)); 183 throw new DirectoryException(DirectoryServer.getServerErrorResultCode(), 184 message, e); 185 } 186 finally 187 { 188 if (plaintextBytes != null) 189 { 190 Arrays.fill(plaintextBytes, (byte) 0); 191 } 192 } 193 } 194 195 buffer.append(Base64.encode(digestBytes)); 196 197 return ByteString.valueOfUtf8(buffer); 198 } 199 200 201 202 /** {@inheritDoc} */ 203 @Override 204 public boolean passwordMatches(ByteSequence plaintextPassword, 205 ByteSequence storedPassword) 206 { 207 // TODO: Can we avoid this copy? 208 byte[] plaintextPasswordBytes = null; 209 ByteString userPWDigestBytes; 210 211 synchronized (digestLock) 212 { 213 try 214 { 215 plaintextPasswordBytes = plaintextPassword.toByteArray(); 216 userPWDigestBytes = 217 ByteString.wrap(messageDigest.digest(plaintextPasswordBytes)); 218 } 219 catch (Exception e) 220 { 221 logger.traceException(e); 222 223 return false; 224 } 225 finally 226 { 227 if (plaintextPasswordBytes != null) 228 { 229 Arrays.fill(plaintextPasswordBytes, (byte) 0); 230 } 231 } 232 } 233 234 ByteString storedPWDigestBytes; 235 try 236 { 237 storedPWDigestBytes = 238 ByteString.wrap(Base64.decode(storedPassword.toString())); 239 } 240 catch (Exception e) 241 { 242 logger.traceException(e); 243 logger.error(ERR_PWSCHEME_CANNOT_BASE64_DECODE_STORED_PASSWORD, storedPassword, e); 244 return false; 245 } 246 247 return userPWDigestBytes.equals(storedPWDigestBytes); 248 } 249 250 251 252 /** {@inheritDoc} */ 253 @Override 254 public boolean supportsAuthPasswordSyntax() 255 { 256 // This storage scheme does not support the authentication password syntax. 257 return false; 258 } 259 260 261 262 /** {@inheritDoc} */ 263 @Override 264 public ByteString encodeAuthPassword(ByteSequence plaintext) 265 throws DirectoryException 266 { 267 LocalizableMessage message = 268 ERR_PWSCHEME_DOES_NOT_SUPPORT_AUTH_PASSWORD.get(getStorageSchemeName()); 269 throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message); 270 } 271 272 273 274 /** {@inheritDoc} */ 275 @Override 276 public boolean authPasswordMatches(ByteSequence plaintextPassword, 277 String authInfo, String authValue) 278 { 279 // This storage scheme does not support the authentication password syntax. 280 return false; 281 } 282 283 284 285 /** {@inheritDoc} */ 286 @Override 287 public boolean isReversible() 288 { 289 return false; 290 } 291 292 293 294 /** {@inheritDoc} */ 295 @Override 296 public ByteString getPlaintextValue(ByteSequence storedPassword) 297 throws DirectoryException 298 { 299 LocalizableMessage message = 300 ERR_PWSCHEME_NOT_REVERSIBLE.get(STORAGE_SCHEME_NAME_SHA_1); 301 throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, message); 302 } 303 304 305 306 /** {@inheritDoc} */ 307 @Override 308 public ByteString getAuthPasswordPlaintextValue(String authInfo, 309 String authValue) 310 throws DirectoryException 311 { 312 LocalizableMessage message = 313 ERR_PWSCHEME_DOES_NOT_SUPPORT_AUTH_PASSWORD.get(getStorageSchemeName()); 314 throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message); 315 } 316 317 318 319 /** {@inheritDoc} */ 320 @Override 321 public boolean isStorageSchemeSecure() 322 { 323 // SHA-1 should be considered secure. 324 return true; 325 } 326} 327