KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > apache > derby > impl > jdbc > authentication > AuthenticationServiceBase


1 /*
2
3    Derby - Class org.apache.derby.impl.jdbc.authentication.AuthenticationServiceBase
4
5    Licensed to the Apache Software Foundation (ASF) under one or more
6    contributor license agreements. See the NOTICE file distributed with
7    this work for additional information regarding copyright ownership.
8    The ASF licenses this file to you under the Apache License, Version 2.0
9    (the "License"); you may not use this file except in compliance with
10    the License. You may obtain a copy of the License at
11
12       http://www.apache.org/licenses/LICENSE-2.0
13
14    Unless required by applicable law or agreed to in writing, software
15    distributed under the License is distributed on an "AS IS" BASIS,
16    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17    See the License for the specific language governing permissions and
18    limitations under the License.
19
20  */

21
22 package org.apache.derby.impl.jdbc.authentication;
23
24 import org.apache.derby.authentication.UserAuthenticator;
25 import org.apache.derby.iapi.reference.Property;
26 import org.apache.derby.iapi.jdbc.AuthenticationService;
27
28 import org.apache.derby.iapi.reference.Limits;
29
30 import org.apache.derby.iapi.error.StandardException;
31 import org.apache.derby.iapi.services.i18n.MessageService;
32
33 import org.apache.derby.iapi.services.context.ContextService;
34 import org.apache.derby.iapi.services.daemon.Serviceable;
35
36 import org.apache.derby.iapi.services.monitor.ModuleSupportable;
37 import org.apache.derby.iapi.services.monitor.ModuleControl;
38 import org.apache.derby.iapi.services.monitor.Monitor;
39 import org.apache.derby.iapi.store.access.AccessFactory;
40 import org.apache.derby.iapi.services.property.PropertyFactory;
41 import org.apache.derby.iapi.store.access.TransactionController;
42 import org.apache.derby.iapi.services.property.PropertySetCallback;
43
44 import org.apache.derby.iapi.services.sanity.SanityManager;
45
46 import org.apache.derby.iapi.reference.Attribute;
47
48 import org.apache.derby.iapi.services.property.PropertyUtil;
49 import org.apache.derby.iapi.util.StringUtil;
50
51 import java.security.MessageDigest JavaDoc;
52 import java.security.NoSuchAlgorithmException JavaDoc;
53
54 import java.io.Serializable JavaDoc;
55 import java.util.Dictionary JavaDoc;
56 import java.util.Properties JavaDoc;
57 import java.util.Date JavaDoc;
58
59 /**
60  * This is the authentication service base class.
61  * <p>
62  * There can be 1 Authentication Service for the whole Cloudscape
63  * system and/or 1 authentication per database.
64  * In a near future, we intend to allow multiple authentication services
65  * per system and/or per database.
66  * <p>
67  * It should be extended by the specialized authentication services.
68  *
69  * IMPORTANT NOTE:
70  * --------------
71  * User passwords are encrypted using SHA-1 message digest algorithm
72  * if they're stored in the database; otherwise they are not encrypted
73  * if they were defined at the system level.
74  * SHA-1 digest is single hash (one way) digest and is considered very
75  * secure (160 bits).
76  *
77  * @author Francois
78  */

79 public abstract class AuthenticationServiceBase
80     implements AuthenticationService, ModuleControl, ModuleSupportable, PropertySetCallback {
81
82     protected UserAuthenticator authenticationScheme;
83
84     // required to retrieve service properties
85
private AccessFactory store;
86
87     /**
88         Trace flag to trace authentication operations
89     */

90     public static final String JavaDoc AuthenticationTrace =
91                         SanityManager.DEBUG ? "AuthenticationTrace" : null;
92     /**
93         Pattern that is prefixed to the stored password in the new authentication scheme
94     */

95     public static final String JavaDoc ID_PATTERN_NEW_SCHEME = "3b60";
96
97     /**
98         Userid with Strong password substitute DRDA security mechanism
99     */

100     protected static final int SECMEC_USRSSBPWD = 8;
101
102     /**
103         Length of the encrypted password in the new authentication scheme
104         See Beetle4601
105     */

106     public static final int MAGICLEN_NEWENCRYPT_SCHEME=44;
107
108     //
109
// constructor
110
//
111
public AuthenticationServiceBase() {
112     }
113
114     protected void setAuthenticationService(UserAuthenticator aScheme) {
115         // specialized class is the principal caller.
116
this.authenticationScheme = aScheme;
117
118         if (SanityManager.DEBUG)
119         {
120             SanityManager.ASSERT(this.authenticationScheme != null,
121                 "There is no authentication scheme for that service!");
122         
123             if (SanityManager.DEBUG_ON(AuthenticationTrace)) {
124
125                 java.io.PrintWriter JavaDoc iDbgStream =
126                     SanityManager.GET_DEBUG_STREAM();
127
128                 iDbgStream.println("Authentication Service: [" +
129                                 this.toString() + "]");
130                 iDbgStream.println("Authentication Scheme : [" +
131                                 this.authenticationScheme.toString() + "]");
132             }
133         }
134     }
135
136     /**
137     /*
138     ** Methods of module control - To be overriden
139     */

140
141     /**
142         Start this module. In this case, nothing needs to be done.
143         @see org.apache.derby.iapi.services.monitor.ModuleControl#boot
144
145         @exception StandardException upon failure to load/boot
146         the expected authentication service.
147      */

148      public void boot(boolean create, Properties JavaDoc properties)
149       throws StandardException
150      {
151             //
152
// we expect the Access factory to be available since we're
153
// at boot stage.
154
//
155
store = (AccessFactory)
156                 Monitor.getServiceModule(this, AccessFactory.MODULE);
157             // register to be notified upon db properties changes
158
// _only_ if we're on a database context of course :)
159

160             PropertyFactory pf = (PropertyFactory)
161                 Monitor.getServiceModule(this, org.apache.derby.iapi.reference.Module.PropertyFactory);
162             if (pf != null)
163                 pf.addPropertySetNotification(this);
164
165      }
166
167     /**
168      * @see org.apache.derby.iapi.services.monitor.ModuleControl#stop
169      */

170     public void stop() {
171
172         // nothing special to be done yet.
173
}
174     /*
175     ** Methods of AuthenticationService
176     */

177
178     /**
179      * Authenticate a User inside JBMS.T his is an overload method.
180      *
181      * We're passed-in a Properties object containing user credentials information
182      * (as well as database name if user needs to be validated for a certain
183      * database access).
184      *
185      * @see
186      * org.apache.derby.iapi.jdbc.AuthenticationService#authenticate
187      *
188      *
189      */

190     public boolean authenticate(String JavaDoc databaseName, Properties JavaDoc userInfo) throws java.sql.SQLException JavaDoc
191     {
192         if (userInfo == (Properties JavaDoc) null)
193             return false;
194
195         String JavaDoc userName = userInfo.getProperty(Attribute.USERNAME_ATTR);
196         if ((userName != null) && userName.length() > Limits.DB2_MAX_USERID_LENGTH) {
197         // DB2 has limits on length of the user id, so we enforce the same.
198
// This used to be error 28000 "Invalid authorization ID", but with v82,
199
// DB2 changed the behavior to return a normal "authorization failure
200
// occurred" error; so that means just return "false" and the correct
201
// exception will be thrown as usual.
202
return false;
203         }
204
205         if (SanityManager.DEBUG)
206         {
207             if (SanityManager.DEBUG_ON(AuthenticationTrace)) {
208
209                 java.io.PrintWriter JavaDoc iDbgStream =
210                     SanityManager.GET_DEBUG_STREAM();
211
212                 iDbgStream.println(
213                                 " - Authentication request: user [" +
214                                 userName + "]"+ ", database [" +
215                                 databaseName + "]");
216                 // The following will print the stack trace of the
217
// authentication request to the log.
218
//Throwable t = new Throwable();
219
//istream.println("Authentication Request Stack trace:");
220
//t.printStackTrace(istream.getPrintWriter());
221
}
222         }
223         return this.authenticationScheme.authenticateUser(userName,
224                           userInfo.getProperty(Attribute.PASSWORD_ATTR),
225                           databaseName,
226                           userInfo
227                          );
228     }
229
230     /**
231      * Returns a property if it was set at the database or
232      * system level. Treated as SERVICE property by default.
233      *
234      * @return a property string value.
235      **/

236     public String JavaDoc getProperty(String JavaDoc key) {
237
238         String JavaDoc propertyValue = null;
239         TransactionController tc = null;
240
241         try {
242
243           if (store != null)
244           {
245             tc = store.getTransaction(
246                 ContextService.getFactory().getCurrentContextManager());
247           }
248
249           propertyValue =
250             PropertyUtil.getServiceProperty(tc,
251                                             key,
252                                             (String JavaDoc) null);
253           if (tc != null) {
254             tc.commit();
255             tc = null;
256           }
257
258         } catch (StandardException se) {
259             // Do nothing and just return
260
}
261
262         return propertyValue;
263     }
264
265     public String JavaDoc getDatabaseProperty(String JavaDoc key) {
266
267         String JavaDoc propertyValue = null;
268         TransactionController tc = null;
269
270         try {
271
272           if (store != null)
273             tc = store.getTransaction(
274                 ContextService.getFactory().getCurrentContextManager());
275
276           propertyValue =
277             PropertyUtil.getDatabaseProperty(tc, key);
278
279           if (tc != null) {
280             tc.commit();
281             tc = null;
282           }
283
284         } catch (StandardException se) {
285             // Do nothing and just return
286
}
287
288         return propertyValue;
289     }
290
291     public String JavaDoc getSystemProperty(String JavaDoc key) {
292
293         boolean dbOnly = false;
294         dbOnly = Boolean.valueOf(
295                     this.getDatabaseProperty(
296                             Property.DATABASE_PROPERTIES_ONLY)).booleanValue();
297
298         if (dbOnly)
299             return null;
300
301         return PropertyUtil.getSystemProperty(key);
302     }
303
304     /*
305     ** Methods of PropertySetCallback
306     */

307     public void init(boolean dbOnly, Dictionary JavaDoc p) {
308         // not called yet ...
309
}
310
311     /**
312       @see PropertySetCallback#validate
313     */

314     public boolean validate(String JavaDoc key, Serializable JavaDoc value, Dictionary JavaDoc p) {
315         return key.startsWith(org.apache.derby.iapi.reference.Property.USER_PROPERTY_PREFIX);
316     }
317     /**
318       @see PropertySetCallback#validate
319     */

320     public Serviceable apply(String JavaDoc key,Serializable JavaDoc value,Dictionary JavaDoc p)
321     {
322         return null;
323     }
324     /**
325       @see PropertySetCallback#map
326       @exception StandardException Thrown on error.
327     */

328     public Serializable JavaDoc map(String JavaDoc key, Serializable JavaDoc value, Dictionary JavaDoc p)
329         throws StandardException
330     {
331         // We only care for "derby.user." property changes
332
// at the moment.
333
if (!key.startsWith(org.apache.derby.iapi.reference.Property.USER_PROPERTY_PREFIX)) return null;
334         // We do not encrypt 'derby.user.<userName>' password if
335
// the configured authentication service is LDAP as the
336
// same property could be used to store LDAP user full DN (X500).
337
// In performing this check we only consider database properties
338
// not system, service or application properties.
339

340         String JavaDoc authService =
341             (String JavaDoc)p.get(org.apache.derby.iapi.reference.Property.AUTHENTICATION_PROVIDER_PARAMETER);
342
343         if ((authService != null) &&
344              (StringUtil.SQLEqualsIgnoreCase(authService, org.apache.derby.iapi.reference.Property.AUTHENTICATION_PROVIDER_LDAP)))
345             return null;
346
347         // Ok, we can encrypt this password in the db
348
String JavaDoc userPassword = (String JavaDoc) value;
349
350         if (userPassword != null) {
351             // encrypt (digest) the password
352
// the caller will retrieve the new value
353
userPassword = encryptPassword(userPassword);
354         }
355
356         return userPassword;
357     }
358
359
360     // Class implementation
361

362     protected final boolean requireAuthentication(Properties JavaDoc properties) {
363
364         //
365
// we check if derby.connection.requireAuthentication system
366
// property is set to true, otherwise we are the authentication
367
// service that should be run.
368
//
369
String JavaDoc requireAuthentication = PropertyUtil.getPropertyFromSet(
370                     properties,
371                     org.apache.derby.iapi.reference.Property.REQUIRE_AUTHENTICATION_PARAMETER
372                                                         );
373         return Boolean.valueOf(requireAuthentication).booleanValue();
374     }
375
376     /**
377      * This method encrypts a clear user password using a
378      * Single Hash algorithm such as SHA-1 (SHA equivalent)
379      * (it is a 160 bits digest)
380      *
381      * The digest is returned as an object string.
382      *
383      * @param plainTxtUserPassword Plain text user password
384      *
385      * @return encrypted user password (digest) as a String object
386      */

387     protected String JavaDoc encryptPassword(String JavaDoc plainTxtUserPassword)
388     {
389         if (plainTxtUserPassword == null)
390             return null;
391
392         MessageDigest JavaDoc algorithm = null;
393         try
394         {
395             algorithm = MessageDigest.getInstance("SHA-1");
396         } catch (NoSuchAlgorithmException JavaDoc nsae)
397         {
398                     // Ignore as we checked already during service boot-up
399
}
400
401         algorithm.reset();
402         byte[] bytePasswd = null;
403         bytePasswd = StringUtil.toHexByte(
404                 plainTxtUserPassword,0,plainTxtUserPassword.length());
405         algorithm.update(bytePasswd);
406         byte[] encryptVal = algorithm.digest();
407         String JavaDoc hexString = ID_PATTERN_NEW_SCHEME +
408                 StringUtil.toHexString(encryptVal,0,encryptVal.length);
409         return (hexString);
410
411     }
412
413     /**
414      * Strong Password Substitution (USRSSBPWD).
415      *
416      * This method generate a password subtitute to authenticate a client
417      * which is using a DRDA security mechanism such as SECMEC_USRSSBPWD.
418      *
419      * Depending how the user is defined in Derby and if BUILTIN
420      * is used, the stored password can be in clear-text (system level)
421      * or encrypted (hashed - *not decryptable*)) (database level) - If the
422      * user has authenticated at the network level via SECMEC_USRSSBPWD, it
423      * means we're presented with a password substitute and we need to
424      * generate a substitute password coming from the store to compare with
425      * the one passed-in.
426      *
427      * NOTE: A lot of this logic could be shared with the DRDA decryption
428      * and client encryption managers - This will be done _once_
429      * code sharing along with its rules are defined between the
430      * Derby engine, client and network code (PENDING).
431      *
432      * Substitution algorithm works as follow:
433      *
434      * PW_TOKEN = SHA-1(PW, ID)
435      * The password (PW) and user name (ID) can be of any length greater
436      * than or equal to 1 byte.
437      * The client generates a 20-byte password substitute (PW_SUB) as follows:
438      * PW_SUB = SHA-1(PW_TOKEN, RDr, RDs, ID, PWSEQs)
439      *
440      * w/ (RDs) as the random client seed and (RDr) as the server one.
441      *
442      * See PWDSSB - Strong Password Substitution Security Mechanism
443      * (DRDA Vol.3 - P.650)
444      *
445      * @return a substituted password.
446      */

447     protected String JavaDoc substitutePassword(
448                 String JavaDoc userName,
449                 String JavaDoc password,
450                 Properties JavaDoc info,
451                 boolean databaseUser) {
452
453         MessageDigest JavaDoc messageDigest = null;
454
455         // Pattern that is prefixed to the BUILTIN encrypted password
456
String JavaDoc ID_PATTERN_NEW_SCHEME = "3b60";
457
458         // PWSEQs's 8-byte value constant - See DRDA Vol 3
459
byte SECMEC_USRSSBPWD_PWDSEQS[] = {
460                 (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
461                 (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x01
462                 };
463         
464         // Generated password substitute
465
byte[] passwordSubstitute;
466
467         try
468         {
469             messageDigest = MessageDigest.getInstance("SHA-1");
470         } catch (NoSuchAlgorithmException JavaDoc nsae)
471         {
472             // Ignore as we checked already during service boot-up
473
}
474         // IMPORTANT NOTE: As the password is stored single-hashed in the
475
// database, it is impossible for us to decrypt the password and
476
// recompute a substitute to compare with one generated on the source
477
// side - Hence, we have to generate a password substitute.
478
// In other words, we cannot figure what the original password was -
479
// Strong Password Substitution (USRSSBPWD) cannot be supported for
480
// targets which can't access or decrypt passwords on their side.
481
//
482
messageDigest.reset();
483
484         byte[] bytePasswd = null;
485         byte[] userBytes = StringUtil.toHexByte(userName, 0, userName.length());
486
487         if (SanityManager.DEBUG)
488         {
489             // We must have a source and target seed
490
SanityManager.ASSERT(
491               (((String JavaDoc) info.getProperty(Attribute.DRDA_SECTKN_IN) != null) &&
492               ((String JavaDoc) info.getProperty(Attribute.DRDA_SECTKN_OUT) != null)),
493                 "Unexpected: Requester or server seed not available");
494         }
495
496         // Retrieve source (client) and target 8-byte seeds
497
String JavaDoc sourceSeedstr = info.getProperty(Attribute.DRDA_SECTKN_IN);
498         String JavaDoc targetSeedstr = info.getProperty(Attribute.DRDA_SECTKN_OUT);
499
500         byte[] sourceSeed_ =
501             StringUtil.fromHexString(sourceSeedstr, 0, sourceSeedstr.length());
502         byte[] targetSeed_ =
503             StringUtil.fromHexString(targetSeedstr, 0, targetSeedstr.length());
504
505         String JavaDoc hexString = null;
506         // If user is at the database level, we don't encrypt the password
507
// as it is already encrypted (BUILTIN scheme) - we only do the
508
// BUILTIN encryption if the user is defined at the system level
509
// only - this is required beforehands so that we can do the password
510
// substitute generation right afterwards.
511
if (!databaseUser)
512         {
513             bytePasswd = StringUtil.toHexByte(password, 0, password.length());
514             messageDigest.update(bytePasswd);
515             byte[] encryptVal = messageDigest.digest();
516             hexString = ID_PATTERN_NEW_SCHEME +
517                 StringUtil.toHexString(encryptVal, 0, encryptVal.length);
518         }
519         else
520             // Already encrypted from the database store
521
hexString = password;
522
523         // Generate the password substitute now
524

525         // Generate some 20-byte password token
526
messageDigest.update(userBytes);
527         messageDigest.update(
528                 StringUtil.toHexByte(hexString, 0, hexString.length()));
529         byte[] passwordToken = messageDigest.digest();
530         
531         // Now we generate the 20-byte password substitute
532
messageDigest.update(passwordToken);
533         messageDigest.update(targetSeed_);
534         messageDigest.update(sourceSeed_);
535         messageDigest.update(userBytes);
536         messageDigest.update(SECMEC_USRSSBPWD_PWDSEQS);
537
538         passwordSubstitute = messageDigest.digest();
539
540         return StringUtil.toHexString(passwordSubstitute, 0,
541                                       passwordSubstitute.length);
542     }
543 }
544
Popular Tags