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 010 * trunk/opends/resource/legal-notices/OpenDS.LICENSE 011 * or https://OpenDS.dev.java.net/OpenDS.LICENSE. 012 * See the License for the specific language governing permissions 013 * and limitations under the License. 014 * 015 * When distributing Covered Code, include this CDDL HEADER in each 016 * file and include the License file at 017 * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable, 018 * add the following below this CDDL HEADER, with the fields enclosed 019 * by brackets "[]" replaced with your own identifying information: 020 * Portions Copyright [yyyy] [name of copyright owner] 021 * 022 * CDDL HEADER END 023 * 024 * 025 * Copyright 2008 Sun Microsystems, Inc. 026 */ 027 package org.opends.server.extensions; 028 029 030 031 import java.util.Arrays; 032 import java.util.Random; 033 034 import org.opends.messages.Message; 035 import org.opends.server.admin.std.server.CryptPasswordStorageSchemeCfg; 036 import org.opends.server.api.PasswordStorageScheme; 037 import org.opends.server.config.ConfigException; 038 import org.opends.server.core.DirectoryServer; 039 import org.opends.server.types.ByteString; 040 import org.opends.server.types.ByteStringFactory; 041 import org.opends.server.types.DirectoryException; 042 import org.opends.server.types.InitializationException; 043 import org.opends.server.types.ResultCode; 044 import org.opends.server.util.Crypt; 045 046 import static org.opends.messages.ExtensionMessages.*; 047 import static org.opends.server.extensions.ExtensionsConstants.*; 048 import static org.opends.server.util.StaticUtils.stackTraceToSingleLineString; 049 050 051 052 /** 053 * This class defines a Directory Server password storage scheme based on the 054 * UNIX Crypt algorithm. This is a legacy one-way digest algorithm 055 * intended only for situations where passwords have not yet been 056 * updated to modern hashes such as SHA-1 and friends. This 057 * implementation does perform weak salting, which means that it is more 058 * vulnerable to dictionary attacks than schemes with larger salts. 059 */ 060 public class CryptPasswordStorageScheme 061 extends PasswordStorageScheme<CryptPasswordStorageSchemeCfg> 062 { 063 /** 064 * The fully-qualified name of this class for debugging purposes. 065 */ 066 private static final String CLASS_NAME = 067 "org.opends.server.extensions.CryptPasswordStorageScheme"; 068 069 /** 070 * An array of values that can be used to create salt characters 071 * when encoding new crypt hashes. 072 * */ 073 private static final byte[] SALT_CHARS = 074 ("./0123456789abcdefghijklmnopqrstuvwxyz" 075 +"ABCDEFGHIJKLMNOPQRSTUVWXYZ").getBytes(); 076 077 private final Random randomSaltIndex = new Random(); 078 private final Object saltLock = new Object(); 079 private final Crypt crypt = new Crypt(); 080 081 082 083 /** 084 * Creates a new instance of this password storage scheme. Note that no 085 * initialization should be performed here, as all initialization should be 086 * done in the <CODE>initializePasswordStorageScheme</CODE> method. 087 */ 088 public CryptPasswordStorageScheme() 089 { 090 super(); 091 } 092 093 094 /** 095 * {@inheritDoc} 096 */ 097 @Override() 098 public void initializePasswordStorageScheme( 099 CryptPasswordStorageSchemeCfg configuration) 100 throws ConfigException, InitializationException { 101 // Nothing to configure 102 } 103 104 /** 105 * {@inheritDoc} 106 */ 107 @Override() 108 public String getStorageSchemeName() 109 { 110 return STORAGE_SCHEME_NAME_CRYPT; 111 } 112 113 114 /** 115 * {@inheritDoc} 116 */ 117 @Override() 118 public ByteString encodePassword(ByteString plaintext) 119 throws DirectoryException 120 { 121 122 byte[] digestBytes; 123 124 try 125 { 126 digestBytes = crypt.crypt(plaintext.value(), randomSalt()); 127 } 128 catch (Exception e) 129 { 130 Message message = ERR_PWSCHEME_CANNOT_ENCODE_PASSWORD.get( 131 CLASS_NAME, stackTraceToSingleLineString(e)); 132 throw new DirectoryException(DirectoryServer.getServerErrorResultCode(), 133 message, e); 134 } 135 136 return ByteStringFactory.create(digestBytes); 137 } 138 139 140 /** 141 * Return a random 2-byte salt. 142 * 143 * @return a random 2-byte salt 144 */ 145 private byte[] randomSalt() { 146 synchronized (saltLock) 147 { 148 byte[] salt = new byte[2]; 149 int sb1 = randomSaltIndex.nextInt(SALT_CHARS.length); 150 int sb2 = randomSaltIndex.nextInt(SALT_CHARS.length); 151 salt[0] = SALT_CHARS[sb1]; 152 salt[1] = SALT_CHARS[sb2]; 153 154 return salt; 155 } 156 } 157 158 159 /** 160 * {@inheritDoc} 161 */ 162 @Override() 163 public ByteString encodePasswordWithScheme(ByteString plaintext) 164 throws DirectoryException 165 { 166 StringBuilder buffer = 167 new StringBuilder(STORAGE_SCHEME_NAME_CRYPT.length()+12); 168 buffer.append('{'); 169 buffer.append(STORAGE_SCHEME_NAME_CRYPT); 170 buffer.append('}'); 171 172 buffer.append(encodePassword(plaintext)); 173 174 return ByteStringFactory.create(buffer.toString()); 175 } 176 177 178 179 /** 180 * {@inheritDoc} 181 */ 182 @Override() 183 public boolean passwordMatches(ByteString plaintextPassword, 184 ByteString storedPassword) 185 { 186 byte[] storedPWDigestBytes = storedPassword.value(); 187 188 byte[] userPWDigestBytes; 189 try 190 { 191 // The salt is stored as the first two bytes of the storedPassword 192 // value, and crypt.crypt() only looks at the first two bytes, so 193 // we can pass it in directly. 194 byte[] salt = storedPWDigestBytes; 195 196 userPWDigestBytes = crypt.crypt(plaintextPassword.value(), salt); 197 } 198 catch (Exception e) 199 { 200 return false; 201 } 202 203 return Arrays.equals(userPWDigestBytes, storedPWDigestBytes); 204 } 205 206 207 208 /** 209 * {@inheritDoc} 210 */ 211 @Override() 212 public boolean supportsAuthPasswordSyntax() 213 { 214 // This storage scheme does not support the authentication password syntax. 215 return false; 216 } 217 218 219 220 /** 221 * {@inheritDoc} 222 */ 223 @Override() 224 public ByteString encodeAuthPassword(ByteString plaintext) 225 throws DirectoryException 226 { 227 Message message = 228 ERR_PWSCHEME_DOES_NOT_SUPPORT_AUTH_PASSWORD.get(getStorageSchemeName()); 229 throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message); 230 } 231 232 233 234 /** 235 * {@inheritDoc} 236 */ 237 @Override() 238 public boolean authPasswordMatches(ByteString plaintextPassword, 239 String authInfo, String authValue) 240 { 241 // This storage scheme does not support the authentication password syntax. 242 return false; 243 } 244 245 246 247 /** 248 * {@inheritDoc} 249 */ 250 @Override() 251 public boolean isReversible() 252 { 253 return false; 254 } 255 256 257 258 /** 259 * {@inheritDoc} 260 */ 261 @Override() 262 public ByteString getPlaintextValue(ByteString storedPassword) 263 throws DirectoryException 264 { 265 Message message = 266 ERR_PWSCHEME_NOT_REVERSIBLE.get(STORAGE_SCHEME_NAME_CRYPT); 267 throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, message); 268 } 269 270 271 272 /** 273 * {@inheritDoc} 274 */ 275 @Override() 276 public ByteString getAuthPasswordPlaintextValue(String authInfo, 277 String authValue) 278 throws DirectoryException 279 { 280 Message message = 281 ERR_PWSCHEME_DOES_NOT_SUPPORT_AUTH_PASSWORD.get(getStorageSchemeName()); 282 throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message); 283 } 284 285 286 287 /** 288 * {@inheritDoc} 289 */ 290 @Override() 291 public boolean isStorageSchemeSecure() 292 { 293 // FIXME: 294 // Technically, this isn't quite in keeping with the original spirit of 295 // this method, since the point was to determine whether the scheme could 296 // be trivially reversed. I'm not sure I would put crypt into that 297 // category, but it's certainly a lot more vulnerable to lookup tables 298 // than most other algorithms. I'd say we can keep it this way for now, 299 // but it might be something to reconsider later. 300 // 301 // Currently, this method is unused. However, the intended purpose is 302 // eventually for use in issue #321, where we could do things like prevent 303 // even authorized users from seeing the password value over an insecure 304 // connection if it isn't considered secure. 305 306 return false; 307 } 308 } 309