--- /dev/null
+/*\r
+\r
+ Derby - Class org.apache.derby.impl.jdbc.authentication.AuthenticationServiceBase\r
+\r
+ Licensed to the Apache Software Foundation (ASF) under one or more\r
+ contributor license agreements. See the NOTICE file distributed with\r
+ this work for additional information regarding copyright ownership.\r
+ The ASF licenses this file to you under the Apache License, Version 2.0\r
+ (the "License"); you may not use this file except in compliance with\r
+ the License. You may obtain a copy of the License at\r
+\r
+ http://www.apache.org/licenses/LICENSE-2.0\r
+\r
+ Unless required by applicable law or agreed to in writing, software\r
+ distributed under the License is distributed on an "AS IS" BASIS,\r
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+ See the License for the specific language governing permissions and\r
+ limitations under the License.\r
+\r
+ */\r
+\r
+package org.apache.derby.impl.jdbc.authentication;\r
+\r
+import org.apache.derby.authentication.UserAuthenticator;\r
+import org.apache.derby.iapi.reference.Property;\r
+import org.apache.derby.iapi.jdbc.AuthenticationService;\r
+\r
+import org.apache.derby.iapi.reference.Limits;\r
+\r
+import org.apache.derby.iapi.error.StandardException;\r
+import org.apache.derby.iapi.services.i18n.MessageService;\r
+\r
+import org.apache.derby.iapi.services.context.ContextService;\r
+import org.apache.derby.iapi.services.daemon.Serviceable;\r
+\r
+import org.apache.derby.iapi.services.monitor.ModuleSupportable;\r
+import org.apache.derby.iapi.services.monitor.ModuleControl;\r
+import org.apache.derby.iapi.services.monitor.Monitor;\r
+import org.apache.derby.iapi.store.access.AccessFactory;\r
+import org.apache.derby.iapi.services.property.PropertyFactory;\r
+import org.apache.derby.iapi.store.access.TransactionController;\r
+import org.apache.derby.iapi.services.property.PropertySetCallback;\r
+\r
+import org.apache.derby.iapi.services.sanity.SanityManager;\r
+\r
+import org.apache.derby.iapi.reference.Attribute;\r
+\r
+import org.apache.derby.iapi.services.property.PropertyUtil;\r
+import org.apache.derby.iapi.util.StringUtil;\r
+\r
+import java.security.MessageDigest;\r
+import java.security.NoSuchAlgorithmException;\r
+\r
+import java.io.Serializable;\r
+import java.util.Dictionary;\r
+import java.util.Properties;\r
+import java.util.Date;\r
+\r
+/**\r
+ * This is the authentication service base class.\r
+ * <p>\r
+ * There can be 1 Authentication Service for the whole Derby\r
+ * system and/or 1 authentication per database.\r
+ * In a near future, we intend to allow multiple authentication services\r
+ * per system and/or per database.\r
+ * <p>\r
+ * It should be extended by the specialized authentication services.\r
+ *\r
+ * IMPORTANT NOTE:\r
+ * --------------\r
+ * User passwords are encrypted using SHA-1 message digest algorithm\r
+ * if they're stored in the database; otherwise they are not encrypted\r
+ * if they were defined at the system level.\r
+ * SHA-1 digest is single hash (one way) digest and is considered very\r
+ * secure (160 bits).\r
+ *\r
+ */\r
+public abstract class AuthenticationServiceBase\r
+ implements AuthenticationService, ModuleControl, ModuleSupportable, PropertySetCallback {\r
+\r
+ protected UserAuthenticator authenticationScheme; \r
+\r
+ // required to retrieve service properties\r
+ private AccessFactory store;\r
+\r
+ /**\r
+ Trace flag to trace authentication operations\r
+ */\r
+ public static final String AuthenticationTrace =\r
+ SanityManager.DEBUG ? "AuthenticationTrace" : null;\r
+ /**\r
+ Pattern that is prefixed to the stored password in the new authentication scheme\r
+ */\r
+ public static final String ID_PATTERN_NEW_SCHEME = "3b60";\r
+\r
+ /**\r
+ Userid with Strong password substitute DRDA security mechanism\r
+ */\r
+ protected static final int SECMEC_USRSSBPWD = 8;\r
+\r
+ /**\r
+ Length of the encrypted password in the new authentication scheme\r
+ See Beetle4601\r
+ */\r
+ public static final int MAGICLEN_NEWENCRYPT_SCHEME=44;\r
+\r
+ //\r
+ // constructor\r
+ //\r
+ public AuthenticationServiceBase() {\r
+ }\r
+\r
+ protected void setAuthenticationService(UserAuthenticator aScheme) {\r
+ // specialized class is the principal caller.\r
+ this.authenticationScheme = aScheme;\r
+\r
+ if (SanityManager.DEBUG)\r
+ {\r
+ SanityManager.ASSERT(this.authenticationScheme != null, \r
+ "There is no authentication scheme for that service!");\r
+ \r
+ if (SanityManager.DEBUG_ON(AuthenticationTrace)) {\r
+\r
+ java.io.PrintWriter iDbgStream =\r
+ SanityManager.GET_DEBUG_STREAM();\r
+\r
+ iDbgStream.println("Authentication Service: [" +\r
+ this.toString() + "]");\r
+ iDbgStream.println("Authentication Scheme : [" +\r
+ this.authenticationScheme.toString() + "]");\r
+ }\r
+ }\r
+ }\r
+\r
+ /**\r
+ /*\r
+ ** Methods of module control - To be overriden\r
+ */\r
+\r
+ /**\r
+ Start this module. In this case, nothing needs to be done.\r
+ @see org.apache.derby.iapi.services.monitor.ModuleControl#boot\r
+\r
+ @exception StandardException upon failure to load/boot\r
+ the expected authentication service.\r
+ */\r
+ public void boot(boolean create, Properties properties)\r
+ throws StandardException\r
+ {\r
+ //\r
+ // we expect the Access factory to be available since we're\r
+ // at boot stage.\r
+ //\r
+ store = (AccessFactory)\r
+ Monitor.getServiceModule(this, AccessFactory.MODULE);\r
+ // register to be notified upon db properties changes\r
+ // _only_ if we're on a database context of course :)\r
+\r
+ PropertyFactory pf = (PropertyFactory)\r
+ Monitor.getServiceModule(this, org.apache.derby.iapi.reference.Module.PropertyFactory);\r
+ if (pf != null)\r
+ pf.addPropertySetNotification(this);\r
+\r
+ }\r
+\r
+ /**\r
+ * @see org.apache.derby.iapi.services.monitor.ModuleControl#stop\r
+ */\r
+ public void stop() {\r
+\r
+ // nothing special to be done yet.\r
+ }\r
+ /*\r
+ ** Methods of AuthenticationService\r
+ */\r
+\r
+ /**\r
+ * Authenticate a User inside JBMS.T his is an overload method.\r
+ *\r
+ * We're passed-in a Properties object containing user credentials information\r
+ * (as well as database name if user needs to be validated for a certain\r
+ * database access).\r
+ *\r
+ * @see\r
+ * org.apache.derby.iapi.jdbc.AuthenticationService#authenticate\r
+ *\r
+ *\r
+ */\r
+ public boolean authenticate(String databaseName, Properties userInfo) throws java.sql.SQLException\r
+ {\r
+ if (userInfo == (Properties) null)\r
+ return false;\r
+\r
+ String userName = userInfo.getProperty(Attribute.USERNAME_ATTR);\r
+ if ((userName != null) && userName.length() > Limits.DB2_MAX_USERID_LENGTH) {\r
+ // DB2 has limits on length of the user id, so we enforce the same.\r
+ // This used to be error 28000 "Invalid authorization ID", but with v82,\r
+ // DB2 changed the behavior to return a normal "authorization failure\r
+ // occurred" error; so that means just return "false" and the correct\r
+ // exception will be thrown as usual.\r
+ return false;\r
+ }\r
+\r
+ if (SanityManager.DEBUG)\r
+ {\r
+ if (SanityManager.DEBUG_ON(AuthenticationTrace)) {\r
+\r
+ java.io.PrintWriter iDbgStream =\r
+ SanityManager.GET_DEBUG_STREAM();\r
+\r
+ iDbgStream.println(\r
+ " - Authentication request: user [" +\r
+ userName + "]"+ ", database [" +\r
+ databaseName + "]");\r
+ // The following will print the stack trace of the\r
+ // authentication request to the log. \r
+ //Throwable t = new Throwable();\r
+ //istream.println("Authentication Request Stack trace:");\r
+ //t.printStackTrace(istream.getPrintWriter());\r
+ }\r
+ }\r
+ return this.authenticationScheme.authenticateUser(userName,\r
+ userInfo.getProperty(Attribute.PASSWORD_ATTR),\r
+ databaseName,\r
+ userInfo\r
+ );\r
+ }\r
+\r
+ /**\r
+ * Returns a property if it was set at the database or\r
+ * system level. Treated as SERVICE property by default.\r
+ *\r
+ * @return a property string value.\r
+ **/\r
+ public String getProperty(String key) {\r
+\r
+ String propertyValue = null;\r
+ TransactionController tc = null;\r
+\r
+ try {\r
+\r
+ if (store != null)\r
+ {\r
+ tc = store.getTransaction(\r
+ ContextService.getFactory().getCurrentContextManager());\r
+ }\r
+\r
+ propertyValue =\r
+ PropertyUtil.getServiceProperty(tc,\r
+ key,\r
+ (String) null);\r
+ if (tc != null) {\r
+ tc.commit();\r
+ tc = null;\r
+ }\r
+\r
+ } catch (StandardException se) {\r
+ // Do nothing and just return\r
+ }\r
+\r
+ return propertyValue;\r
+ }\r
+\r
+ public String getDatabaseProperty(String key) {\r
+\r
+ String propertyValue = null;\r
+ TransactionController tc = null;\r
+\r
+ try {\r
+\r
+ if (store != null)\r
+ tc = store.getTransaction(\r
+ ContextService.getFactory().getCurrentContextManager());\r
+\r
+ propertyValue =\r
+ PropertyUtil.getDatabaseProperty(tc, key);\r
+\r
+ if (tc != null) {\r
+ tc.commit();\r
+ tc = null;\r
+ }\r
+\r
+ } catch (StandardException se) {\r
+ // Do nothing and just return\r
+ }\r
+\r
+ return propertyValue;\r
+ }\r
+\r
+ public String getSystemProperty(String key) {\r
+\r
+ boolean dbOnly = false;\r
+ dbOnly = Boolean.valueOf(\r
+ this.getDatabaseProperty(\r
+ Property.DATABASE_PROPERTIES_ONLY)).booleanValue();\r
+\r
+ if (dbOnly)\r
+ return null;\r
+\r
+ return PropertyUtil.getSystemProperty(key);\r
+ }\r
+\r
+ /*\r
+ ** Methods of PropertySetCallback\r
+ */\r
+ public void init(boolean dbOnly, Dictionary p) {\r
+ // not called yet ...\r
+ }\r
+\r
+ /**\r
+ @see PropertySetCallback#validate\r
+ */\r
+ public boolean validate(String key, Serializable value, Dictionary p) {\r
+ return key.startsWith(org.apache.derby.iapi.reference.Property.USER_PROPERTY_PREFIX);\r
+ }\r
+ /**\r
+ @see PropertySetCallback#validate\r
+ */\r
+ public Serviceable apply(String key,Serializable value,Dictionary p)\r
+ {\r
+ return null;\r
+ }\r
+ /**\r
+ @see PropertySetCallback#map\r
+ @exception StandardException Thrown on error.\r
+ */\r
+ public Serializable map(String key, Serializable value, Dictionary p)\r
+ throws StandardException\r
+ {\r
+ // We only care for "derby.user." property changes\r
+ // at the moment.\r
+ if (!key.startsWith(org.apache.derby.iapi.reference.Property.USER_PROPERTY_PREFIX)) return null;\r
+ // We do not encrypt 'derby.user.<userName>' password if\r
+ // the configured authentication service is LDAP as the\r
+ // same property could be used to store LDAP user full DN (X500).\r
+ // In performing this check we only consider database properties\r
+ // not system, service or application properties.\r
+\r
+ String authService =\r
+ (String)p.get(org.apache.derby.iapi.reference.Property.AUTHENTICATION_PROVIDER_PARAMETER);\r
+\r
+ if ((authService != null) &&\r
+ (StringUtil.SQLEqualsIgnoreCase(authService, org.apache.derby.iapi.reference.Property.AUTHENTICATION_PROVIDER_LDAP)))\r
+ return null;\r
+\r
+ // Ok, we can encrypt this password in the db\r
+ String userPassword = (String) value;\r
+\r
+ if (userPassword != null) {\r
+ // encrypt (digest) the password\r
+ // the caller will retrieve the new value\r
+ userPassword = encryptPassword(userPassword);\r
+ }\r
+\r
+ return userPassword;\r
+ }\r
+\r
+\r
+ // Class implementation\r
+\r
+ protected final boolean requireAuthentication(Properties properties) {\r
+\r
+ //\r
+ // we check if derby.connection.requireAuthentication system\r
+ // property is set to true, otherwise we are the authentication\r
+ // service that should be run.\r
+ //\r
+ String requireAuthentication = PropertyUtil.getPropertyFromSet(\r
+ properties,\r
+ org.apache.derby.iapi.reference.Property.REQUIRE_AUTHENTICATION_PARAMETER\r
+ );\r
+ return Boolean.valueOf(requireAuthentication).booleanValue();\r
+ }\r
+\r
+ /**\r
+ * This method encrypts a clear user password using a\r
+ * Single Hash algorithm such as SHA-1 (SHA equivalent)\r
+ * (it is a 160 bits digest)\r
+ *\r
+ * The digest is returned as an object string.\r
+ *\r
+ * @param plainTxtUserPassword Plain text user password\r
+ *\r
+ * @return encrypted user password (digest) as a String object\r
+ */\r
+ protected String encryptPassword(String plainTxtUserPassword)\r
+ {\r
+ if (plainTxtUserPassword == null)\r
+ return null;\r
+\r
+ MessageDigest algorithm = null;\r
+ try\r
+ {\r
+ algorithm = MessageDigest.getInstance("SHA-1");\r
+ } catch (NoSuchAlgorithmException nsae)\r
+ {\r
+ // Ignore as we checked already during service boot-up\r
+ }\r
+\r
+ algorithm.reset();\r
+ byte[] bytePasswd = null;\r
+ bytePasswd = StringUtil.toHexByte(\r
+ plainTxtUserPassword,0,plainTxtUserPassword.length());\r
+ algorithm.update(bytePasswd);\r
+ byte[] encryptVal = algorithm.digest();\r
+ String hexString = ID_PATTERN_NEW_SCHEME +\r
+ StringUtil.toHexString(encryptVal,0,encryptVal.length);\r
+ return (hexString);\r
+\r
+ }\r
+\r
+ /**\r
+ * Strong Password Substitution (USRSSBPWD).\r
+ *\r
+ * This method generate a password subtitute to authenticate a client\r
+ * which is using a DRDA security mechanism such as SECMEC_USRSSBPWD.\r
+ *\r
+ * Depending how the user is defined in Derby and if BUILTIN\r
+ * is used, the stored password can be in clear-text (system level)\r
+ * or encrypted (hashed - *not decryptable*)) (database level) - If the\r
+ * user has authenticated at the network level via SECMEC_USRSSBPWD, it\r
+ * means we're presented with a password substitute and we need to\r
+ * generate a substitute password coming from the store to compare with\r
+ * the one passed-in.\r
+ *\r
+ * NOTE: A lot of this logic could be shared with the DRDA decryption\r
+ * and client encryption managers - This will be done _once_\r
+ * code sharing along with its rules are defined between the\r
+ * Derby engine, client and network code (PENDING).\r
+ * \r
+ * Substitution algorithm works as follow:\r
+ *\r
+ * PW_TOKEN = SHA-1(PW, ID)\r
+ * The password (PW) and user name (ID) can be of any length greater\r
+ * than or equal to 1 byte.\r
+ * The client generates a 20-byte password substitute (PW_SUB) as follows:\r
+ * PW_SUB = SHA-1(PW_TOKEN, RDr, RDs, ID, PWSEQs)\r
+ * \r
+ * w/ (RDs) as the random client seed and (RDr) as the server one.\r
+ * \r
+ * See PWDSSB - Strong Password Substitution Security Mechanism\r
+ * (DRDA Vol.3 - P.650)\r
+ *\r
+ * @return a substituted password.\r
+ */\r
+ protected String substitutePassword(\r
+ String userName,\r
+ String password,\r
+ Properties info,\r
+ boolean databaseUser) {\r
+\r
+ MessageDigest messageDigest = null;\r
+\r
+ // Pattern that is prefixed to the BUILTIN encrypted password\r
+ String ID_PATTERN_NEW_SCHEME = "3b60";\r
+\r
+ // PWSEQs's 8-byte value constant - See DRDA Vol 3\r
+ byte SECMEC_USRSSBPWD_PWDSEQS[] = {\r
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,\r
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x01\r
+ };\r
+ \r
+ // Generated password substitute\r
+ byte[] passwordSubstitute;\r
+\r
+ try\r
+ {\r
+ messageDigest = MessageDigest.getInstance("SHA-1");\r
+ } catch (NoSuchAlgorithmException nsae)\r
+ {\r
+ // Ignore as we checked already during service boot-up\r
+ }\r
+ // IMPORTANT NOTE: As the password is stored single-hashed in the\r
+ // database, it is impossible for us to decrypt the password and\r
+ // recompute a substitute to compare with one generated on the source\r
+ // side - Hence, we have to generate a password substitute.\r
+ // In other words, we cannot figure what the original password was -\r
+ // Strong Password Substitution (USRSSBPWD) cannot be supported for\r
+ // targets which can't access or decrypt passwords on their side.\r
+ //\r
+ messageDigest.reset();\r
+\r
+ byte[] bytePasswd = null;\r
+ byte[] userBytes = StringUtil.toHexByte(userName, 0, userName.length());\r
+\r
+ if (SanityManager.DEBUG)\r
+ {\r
+ // We must have a source and target seed \r
+ SanityManager.ASSERT(\r
+ (((String) info.getProperty(Attribute.DRDA_SECTKN_IN) != null) &&\r
+ ((String) info.getProperty(Attribute.DRDA_SECTKN_OUT) != null)), \r
+ "Unexpected: Requester or server seed not available");\r
+ }\r
+\r
+ // Retrieve source (client) and target 8-byte seeds\r
+ String sourceSeedstr = info.getProperty(Attribute.DRDA_SECTKN_IN);\r
+ String targetSeedstr = info.getProperty(Attribute.DRDA_SECTKN_OUT);\r
+\r
+ byte[] sourceSeed_ =\r
+ StringUtil.fromHexString(sourceSeedstr, 0, sourceSeedstr.length());\r
+ byte[] targetSeed_ =\r
+ StringUtil.fromHexString(targetSeedstr, 0, targetSeedstr.length());\r
+\r
+ String hexString = null;\r
+ // If user is at the database level, we don't encrypt the password\r
+ // as it is already encrypted (BUILTIN scheme) - we only do the\r
+ // BUILTIN encryption if the user is defined at the system level\r
+ // only - this is required beforehands so that we can do the password\r
+ // substitute generation right afterwards.\r
+ if (!databaseUser)\r
+ {\r
+ bytePasswd = StringUtil.toHexByte(password, 0, password.length());\r
+ messageDigest.update(bytePasswd);\r
+ byte[] encryptVal = messageDigest.digest();\r
+ hexString = ID_PATTERN_NEW_SCHEME +\r
+ StringUtil.toHexString(encryptVal, 0, encryptVal.length);\r
+ }\r
+ else\r
+ // Already encrypted from the database store\r
+ hexString = password;\r
+\r
+ // Generate the password substitute now\r
+\r
+ // Generate some 20-byte password token\r
+ messageDigest.update(userBytes);\r
+ messageDigest.update(\r
+ StringUtil.toHexByte(hexString, 0, hexString.length()));\r
+ byte[] passwordToken = messageDigest.digest();\r
+ \r
+ // Now we generate the 20-byte password substitute\r
+ messageDigest.update(passwordToken);\r
+ messageDigest.update(targetSeed_);\r
+ messageDigest.update(sourceSeed_);\r
+ messageDigest.update(userBytes);\r
+ messageDigest.update(SECMEC_USRSSBPWD_PWDSEQS);\r
+\r
+ passwordSubstitute = messageDigest.digest();\r
+\r
+ return StringUtil.toHexString(passwordSubstitute, 0,\r
+ passwordSubstitute.length);\r
+ }\r
+}\r