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