001: /*
002:
003: Derby - Class org.apache.derby.impl.jdbc.authentication.AuthenticationServiceBase
004:
005: Licensed to the Apache Software Foundation (ASF) under one or more
006: contributor license agreements. See the NOTICE file distributed with
007: this work for additional information regarding copyright ownership.
008: The ASF licenses this file to you under the Apache License, Version 2.0
009: (the "License"); you may not use this file except in compliance with
010: the License. You may obtain a copy of the License at
011:
012: http://www.apache.org/licenses/LICENSE-2.0
013:
014: Unless required by applicable law or agreed to in writing, software
015: distributed under the License is distributed on an "AS IS" BASIS,
016: WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
017: See the License for the specific language governing permissions and
018: limitations under the License.
019:
020: */
021:
022: package org.apache.derby.impl.jdbc.authentication;
023:
024: import org.apache.derby.authentication.UserAuthenticator;
025: import org.apache.derby.iapi.reference.Property;
026: import org.apache.derby.iapi.jdbc.AuthenticationService;
027:
028: import org.apache.derby.iapi.reference.Limits;
029:
030: import org.apache.derby.iapi.error.StandardException;
031: import org.apache.derby.iapi.services.i18n.MessageService;
032:
033: import org.apache.derby.iapi.services.context.ContextService;
034: import org.apache.derby.iapi.services.daemon.Serviceable;
035:
036: import org.apache.derby.iapi.services.monitor.ModuleSupportable;
037: import org.apache.derby.iapi.services.monitor.ModuleControl;
038: import org.apache.derby.iapi.services.monitor.Monitor;
039: import org.apache.derby.iapi.store.access.AccessFactory;
040: import org.apache.derby.iapi.services.property.PropertyFactory;
041: import org.apache.derby.iapi.store.access.TransactionController;
042: import org.apache.derby.iapi.services.property.PropertySetCallback;
043:
044: import org.apache.derby.iapi.services.sanity.SanityManager;
045:
046: import org.apache.derby.iapi.reference.Attribute;
047:
048: import org.apache.derby.iapi.services.property.PropertyUtil;
049: import org.apache.derby.iapi.util.StringUtil;
050:
051: import java.security.MessageDigest;
052: import java.security.NoSuchAlgorithmException;
053:
054: import java.io.Serializable;
055: import java.util.Dictionary;
056: import java.util.Properties;
057: import java.util.Date;
058:
059: /**
060: * This is the authentication service base class.
061: * <p>
062: * There can be 1 Authentication Service for the whole Cloudscape
063: * system and/or 1 authentication per database.
064: * In a near future, we intend to allow multiple authentication services
065: * per system and/or per database.
066: * <p>
067: * It should be extended by the specialized authentication services.
068: *
069: * IMPORTANT NOTE:
070: * --------------
071: * User passwords are encrypted using SHA-1 message digest algorithm
072: * if they're stored in the database; otherwise they are not encrypted
073: * if they were defined at the system level.
074: * SHA-1 digest is single hash (one way) digest and is considered very
075: * secure (160 bits).
076: *
077: * @author Francois
078: */
079: public abstract class AuthenticationServiceBase implements
080: AuthenticationService, ModuleControl, ModuleSupportable,
081: PropertySetCallback {
082:
083: protected UserAuthenticator authenticationScheme;
084:
085: // required to retrieve service properties
086: private AccessFactory store;
087:
088: /**
089: Trace flag to trace authentication operations
090: */
091: public static final String AuthenticationTrace = SanityManager.DEBUG ? "AuthenticationTrace"
092: : null;
093: /**
094: Pattern that is prefixed to the stored password in the new authentication scheme
095: */
096: public static final String ID_PATTERN_NEW_SCHEME = "3b60";
097:
098: /**
099: Userid with Strong password substitute DRDA security mechanism
100: */
101: protected static final int SECMEC_USRSSBPWD = 8;
102:
103: /**
104: Length of the encrypted password in the new authentication scheme
105: See Beetle4601
106: */
107: public static final int MAGICLEN_NEWENCRYPT_SCHEME = 44;
108:
109: //
110: // constructor
111: //
112: public AuthenticationServiceBase() {
113: }
114:
115: protected void setAuthenticationService(UserAuthenticator aScheme) {
116: // specialized class is the principal caller.
117: this .authenticationScheme = aScheme;
118:
119: if (SanityManager.DEBUG) {
120: SanityManager
121: .ASSERT(this .authenticationScheme != null,
122: "There is no authentication scheme for that service!");
123:
124: if (SanityManager.DEBUG_ON(AuthenticationTrace)) {
125:
126: java.io.PrintWriter iDbgStream = SanityManager
127: .GET_DEBUG_STREAM();
128:
129: iDbgStream.println("Authentication Service: ["
130: + this .toString() + "]");
131: iDbgStream.println("Authentication Scheme : ["
132: + this .authenticationScheme.toString() + "]");
133: }
134: }
135: }
136:
137: /**
138: /*
139: ** Methods of module control - To be overriden
140: */
141:
142: /**
143: Start this module. In this case, nothing needs to be done.
144: @see org.apache.derby.iapi.services.monitor.ModuleControl#boot
145:
146: @exception StandardException upon failure to load/boot
147: the expected authentication service.
148: */
149: public void boot(boolean create, Properties properties)
150: throws StandardException {
151: //
152: // we expect the Access factory to be available since we're
153: // at boot stage.
154: //
155: store = (AccessFactory) Monitor.getServiceModule(this ,
156: 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) Monitor
161: .getServiceModule(
162: this ,
163: org.apache.derby.iapi.reference.Module.PropertyFactory);
164: if (pf != null)
165: pf.addPropertySetNotification(this );
166:
167: }
168:
169: /**
170: * @see org.apache.derby.iapi.services.monitor.ModuleControl#stop
171: */
172: public void stop() {
173:
174: // nothing special to be done yet.
175: }
176:
177: /*
178: ** Methods of AuthenticationService
179: */
180:
181: /**
182: * Authenticate a User inside JBMS.T his is an overload method.
183: *
184: * We're passed-in a Properties object containing user credentials information
185: * (as well as database name if user needs to be validated for a certain
186: * database access).
187: *
188: * @see
189: * org.apache.derby.iapi.jdbc.AuthenticationService#authenticate
190: *
191: *
192: */
193: public boolean authenticate(String databaseName, Properties userInfo)
194: throws java.sql.SQLException {
195: if (userInfo == (Properties) null)
196: return false;
197:
198: String userName = userInfo.getProperty(Attribute.USERNAME_ATTR);
199: if ((userName != null)
200: && userName.length() > Limits.DB2_MAX_USERID_LENGTH) {
201: // DB2 has limits on length of the user id, so we enforce the same.
202: // This used to be error 28000 "Invalid authorization ID", but with v82,
203: // DB2 changed the behavior to return a normal "authorization failure
204: // occurred" error; so that means just return "false" and the correct
205: // exception will be thrown as usual.
206: return false;
207: }
208:
209: if (SanityManager.DEBUG) {
210: if (SanityManager.DEBUG_ON(AuthenticationTrace)) {
211:
212: java.io.PrintWriter iDbgStream = SanityManager
213: .GET_DEBUG_STREAM();
214:
215: iDbgStream.println(" - Authentication request: user ["
216: + userName + "]" + ", database ["
217: + databaseName + "]");
218: // The following will print the stack trace of the
219: // authentication request to the log.
220: //Throwable t = new Throwable();
221: //istream.println("Authentication Request Stack trace:");
222: //t.printStackTrace(istream.getPrintWriter());
223: }
224: }
225: return this .authenticationScheme.authenticateUser(userName,
226: userInfo.getProperty(Attribute.PASSWORD_ATTR),
227: databaseName, userInfo);
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 getProperty(String key) {
237:
238: String propertyValue = null;
239: TransactionController tc = null;
240:
241: try {
242:
243: if (store != null) {
244: tc = store.getTransaction(ContextService.getFactory()
245: .getCurrentContextManager());
246: }
247:
248: propertyValue = PropertyUtil.getServiceProperty(tc, key,
249: (String) null);
250: if (tc != null) {
251: tc.commit();
252: tc = null;
253: }
254:
255: } catch (StandardException se) {
256: // Do nothing and just return
257: }
258:
259: return propertyValue;
260: }
261:
262: public String getDatabaseProperty(String key) {
263:
264: String propertyValue = null;
265: TransactionController tc = null;
266:
267: try {
268:
269: if (store != null)
270: tc = store.getTransaction(ContextService.getFactory()
271: .getCurrentContextManager());
272:
273: propertyValue = PropertyUtil.getDatabaseProperty(tc, key);
274:
275: if (tc != null) {
276: tc.commit();
277: tc = null;
278: }
279:
280: } catch (StandardException se) {
281: // Do nothing and just return
282: }
283:
284: return propertyValue;
285: }
286:
287: public String getSystemProperty(String key) {
288:
289: boolean dbOnly = false;
290: dbOnly = Boolean
291: .valueOf(
292: this
293: .getDatabaseProperty(Property.DATABASE_PROPERTIES_ONLY))
294: .booleanValue();
295:
296: if (dbOnly)
297: return null;
298:
299: return PropertyUtil.getSystemProperty(key);
300: }
301:
302: /*
303: ** Methods of PropertySetCallback
304: */
305: public void init(boolean dbOnly, Dictionary p) {
306: // not called yet ...
307: }
308:
309: /**
310: @see PropertySetCallback#validate
311: */
312: public boolean validate(String key, Serializable value, Dictionary p) {
313: return key
314: .startsWith(org.apache.derby.iapi.reference.Property.USER_PROPERTY_PREFIX);
315: }
316:
317: /**
318: @see PropertySetCallback#validate
319: */
320: public Serviceable apply(String key, Serializable value,
321: Dictionary p) {
322: return null;
323: }
324:
325: /**
326: @see PropertySetCallback#map
327: @exception StandardException Thrown on error.
328: */
329: public Serializable map(String key, Serializable value, Dictionary p)
330: throws StandardException {
331: // We only care for "derby.user." property changes
332: // at the moment.
333: if (!key
334: .startsWith(org.apache.derby.iapi.reference.Property.USER_PROPERTY_PREFIX))
335: return null;
336: // We do not encrypt 'derby.user.<userName>' password if
337: // the configured authentication service is LDAP as the
338: // same property could be used to store LDAP user full DN (X500).
339: // In performing this check we only consider database properties
340: // not system, service or application properties.
341:
342: String authService = (String) p
343: .get(org.apache.derby.iapi.reference.Property.AUTHENTICATION_PROVIDER_PARAMETER);
344:
345: if ((authService != null)
346: && (StringUtil
347: .SQLEqualsIgnoreCase(
348: authService,
349: org.apache.derby.iapi.reference.Property.AUTHENTICATION_PROVIDER_LDAP)))
350: return null;
351:
352: // Ok, we can encrypt this password in the db
353: String userPassword = (String) value;
354:
355: if (userPassword != null) {
356: // encrypt (digest) the password
357: // the caller will retrieve the new value
358: userPassword = encryptPassword(userPassword);
359: }
360:
361: return userPassword;
362: }
363:
364: // Class implementation
365:
366: protected final boolean requireAuthentication(Properties properties) {
367:
368: //
369: // we check if derby.connection.requireAuthentication system
370: // property is set to true, otherwise we are the authentication
371: // service that should be run.
372: //
373: String requireAuthentication = PropertyUtil
374: .getPropertyFromSet(
375: properties,
376: org.apache.derby.iapi.reference.Property.REQUIRE_AUTHENTICATION_PARAMETER);
377: return Boolean.valueOf(requireAuthentication).booleanValue();
378: }
379:
380: /**
381: * This method encrypts a clear user password using a
382: * Single Hash algorithm such as SHA-1 (SHA equivalent)
383: * (it is a 160 bits digest)
384: *
385: * The digest is returned as an object string.
386: *
387: * @param plainTxtUserPassword Plain text user password
388: *
389: * @return encrypted user password (digest) as a String object
390: */
391: protected String encryptPassword(String plainTxtUserPassword) {
392: if (plainTxtUserPassword == null)
393: return null;
394:
395: MessageDigest algorithm = null;
396: try {
397: algorithm = MessageDigest.getInstance("SHA-1");
398: } catch (NoSuchAlgorithmException nsae) {
399: // Ignore as we checked already during service boot-up
400: }
401:
402: algorithm.reset();
403: byte[] bytePasswd = null;
404: bytePasswd = StringUtil.toHexByte(plainTxtUserPassword, 0,
405: plainTxtUserPassword.length());
406: algorithm.update(bytePasswd);
407: byte[] encryptVal = algorithm.digest();
408: String hexString = ID_PATTERN_NEW_SCHEME
409: + StringUtil.toHexString(encryptVal, 0,
410: encryptVal.length);
411: return (hexString);
412:
413: }
414:
415: /**
416: * Strong Password Substitution (USRSSBPWD).
417: *
418: * This method generate a password subtitute to authenticate a client
419: * which is using a DRDA security mechanism such as SECMEC_USRSSBPWD.
420: *
421: * Depending how the user is defined in Derby and if BUILTIN
422: * is used, the stored password can be in clear-text (system level)
423: * or encrypted (hashed - *not decryptable*)) (database level) - If the
424: * user has authenticated at the network level via SECMEC_USRSSBPWD, it
425: * means we're presented with a password substitute and we need to
426: * generate a substitute password coming from the store to compare with
427: * the one passed-in.
428: *
429: * NOTE: A lot of this logic could be shared with the DRDA decryption
430: * and client encryption managers - This will be done _once_
431: * code sharing along with its rules are defined between the
432: * Derby engine, client and network code (PENDING).
433: *
434: * Substitution algorithm works as follow:
435: *
436: * PW_TOKEN = SHA-1(PW, ID)
437: * The password (PW) and user name (ID) can be of any length greater
438: * than or equal to 1 byte.
439: * The client generates a 20-byte password substitute (PW_SUB) as follows:
440: * PW_SUB = SHA-1(PW_TOKEN, RDr, RDs, ID, PWSEQs)
441: *
442: * w/ (RDs) as the random client seed and (RDr) as the server one.
443: *
444: * See PWDSSB - Strong Password Substitution Security Mechanism
445: * (DRDA Vol.3 - P.650)
446: *
447: * @return a substituted password.
448: */
449: protected String substitutePassword(String userName,
450: String password, Properties info, boolean databaseUser) {
451:
452: MessageDigest messageDigest = null;
453:
454: // Pattern that is prefixed to the BUILTIN encrypted password
455: String ID_PATTERN_NEW_SCHEME = "3b60";
456:
457: // PWSEQs's 8-byte value constant - See DRDA Vol 3
458: byte SECMEC_USRSSBPWD_PWDSEQS[] = { (byte) 0x00, (byte) 0x00,
459: (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
460: (byte) 0x00, (byte) 0x01 };
461:
462: // Generated password substitute
463: byte[] passwordSubstitute;
464:
465: try {
466: messageDigest = MessageDigest.getInstance("SHA-1");
467: } catch (NoSuchAlgorithmException nsae) {
468: // Ignore as we checked already during service boot-up
469: }
470: // IMPORTANT NOTE: As the password is stored single-hashed in the
471: // database, it is impossible for us to decrypt the password and
472: // recompute a substitute to compare with one generated on the source
473: // side - Hence, we have to generate a password substitute.
474: // In other words, we cannot figure what the original password was -
475: // Strong Password Substitution (USRSSBPWD) cannot be supported for
476: // targets which can't access or decrypt passwords on their side.
477: //
478: messageDigest.reset();
479:
480: byte[] bytePasswd = null;
481: byte[] userBytes = StringUtil.toHexByte(userName, 0, userName
482: .length());
483:
484: if (SanityManager.DEBUG) {
485: // We must have a source and target seed
486: SanityManager
487: .ASSERT(
488: (((String) info
489: .getProperty(Attribute.DRDA_SECTKN_IN) != null) && ((String) info
490: .getProperty(Attribute.DRDA_SECTKN_OUT) != null)),
491: "Unexpected: Requester or server seed not available");
492: }
493:
494: // Retrieve source (client) and target 8-byte seeds
495: String sourceSeedstr = info
496: .getProperty(Attribute.DRDA_SECTKN_IN);
497: String targetSeedstr = info
498: .getProperty(Attribute.DRDA_SECTKN_OUT);
499:
500: byte[] sourceSeed_ = StringUtil.fromHexString(sourceSeedstr, 0,
501: sourceSeedstr.length());
502: byte[] targetSeed_ = StringUtil.fromHexString(targetSeedstr, 0,
503: targetSeedstr.length());
504:
505: String 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: bytePasswd = StringUtil.toHexByte(password, 0, password
513: .length());
514: messageDigest.update(bytePasswd);
515: byte[] encryptVal = messageDigest.digest();
516: hexString = ID_PATTERN_NEW_SCHEME
517: + StringUtil.toHexString(encryptVal, 0,
518: encryptVal.length);
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(StringUtil.toHexByte(hexString, 0,
528: 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: }
|