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