Java tutorial
/** * OWASP Enterprise Security API (ESAPI) * * This file is part of the Open Web Application Security Project (OWASP) * Enterprise Security API (ESAPI) project. For details, please see * <a href="http://www.owasp.org/index.php/ESAPI">http://www.owasp.org/index.php/ESAPI</a>. * * Copyright (c) 2007 - The OWASP Foundation * * The ESAPI is published by OWASP under the BSD license. You should read and accept the * LICENSE before you use, modify, and/or redistribute this software. * * @author Jeff Williams <a href="http://www.aspectsecurity.com">Aspect Security</a> * @created 2007 */ package org.owasp.esapi.reference; import org.apache.commons.lang.text.StrTokenizer; import org.owasp.esapi.ESAPI; import org.owasp.esapi.Logger; import org.owasp.esapi.SecurityConfiguration; import org.owasp.esapi.configuration.EsapiPropertyManager; import org.owasp.esapi.errors.ConfigurationException; import java.io.*; import java.net.URL; import java.util.*; import java.util.regex.Pattern; import java.util.regex.PatternSyntaxException; /** * The reference {@code SecurityConfiguration} manages all the settings used by the ESAPI in a single place. In this reference * implementation, resources can be put in several locations, which are searched in the following order: * <p> * 1) Inside a directory set with a call to SecurityConfiguration.setResourceDirectory( "C:\temp\resources" ). * <p> * 2) Inside the System.getProperty( "org.owasp.esapi.resources" ) directory. * You can set this on the java command line as follows (for example): * <pre> * java -Dorg.owasp.esapi.resources="C:\temp\resources" * </pre> * You may have to add this to the start-up script that starts your web server. For example, for Tomcat, * in the "catalina" script that starts Tomcat, you can set the JAVA_OPTS variable to the {@code -D} string above. * <p> * 3) Inside the {@code System.getProperty( "user.home" ) + "/.esapi"} directory (supported for backward compatibility) or * inside the {@code System.getProperty( "user.home" ) + "/esapi"} directory. * <p> * 4) The first ".esapi" or "esapi" directory on the classpath. (The former for backward compatibility.) * <p> * Once the Configuration is initialized with a resource directory, you can edit it to set things like master * keys and passwords, logging locations, error thresholds, and allowed file extensions. * <p> * WARNING: Do not forget to update ESAPI.properties to change the master key and other security critical settings. * * @author Jeff Williams (jeff.williams .at. aspectsecurity.com) <a href="http://www.aspectsecurity.com">Aspect Security</a> * @author Jim Manico (jim .at. manico.net) <a href="http://www.manico.net">Manico.net</a> * @author Kevin Wall (kevin.w.wall .at. gmail.com) */ public class DefaultSecurityConfiguration implements SecurityConfiguration { private static volatile SecurityConfiguration instance = null; public static SecurityConfiguration getInstance() { if (instance == null) { synchronized (DefaultSecurityConfiguration.class) { if (instance == null) { instance = new DefaultSecurityConfiguration(); } } } return instance; } private Properties properties = null; private String cipherXformFromESAPIProp = null; // New in ESAPI 2.0 private String cipherXformCurrent = null; // New in ESAPI 2.0 /** The name of the ESAPI property file */ public static final String DEFAULT_RESOURCE_FILE = "ESAPI.properties"; public static final String REMEMBER_TOKEN_DURATION = "Authenticator.RememberTokenDuration"; public static final String IDLE_TIMEOUT_DURATION = "Authenticator.IdleTimeoutDuration"; public static final String ABSOLUTE_TIMEOUT_DURATION = "Authenticator.AbsoluteTimeoutDuration"; public static final String ALLOWED_LOGIN_ATTEMPTS = "Authenticator.AllowedLoginAttempts"; public static final String USERNAME_PARAMETER_NAME = "Authenticator.UsernameParameterName"; public static final String PASSWORD_PARAMETER_NAME = "Authenticator.PasswordParameterName"; public static final String MAX_OLD_PASSWORD_HASHES = "Authenticator.MaxOldPasswordHashes"; public static final String ALLOW_MULTIPLE_ENCODING = "Encoder.AllowMultipleEncoding"; public static final String ALLOW_MIXED_ENCODING = "Encoder.AllowMixedEncoding"; public static final String CANONICALIZATION_CODECS = "Encoder.DefaultCodecList"; public static final String DISABLE_INTRUSION_DETECTION = "IntrusionDetector.Disable"; public static final String MASTER_KEY = "Encryptor.MasterKey"; public static final String MASTER_SALT = "Encryptor.MasterSalt"; public static final String KEY_LENGTH = "Encryptor.EncryptionKeyLength"; public static final String ENCRYPTION_ALGORITHM = "Encryptor.EncryptionAlgorithm"; public static final String HASH_ALGORITHM = "Encryptor.HashAlgorithm"; public static final String HASH_ITERATIONS = "Encryptor.HashIterations"; public static final String CHARACTER_ENCODING = "Encryptor.CharacterEncoding"; public static final String RANDOM_ALGORITHM = "Encryptor.RandomAlgorithm"; public static final String DIGITAL_SIGNATURE_ALGORITHM = "Encryptor.DigitalSignatureAlgorithm"; public static final String DIGITAL_SIGNATURE_KEY_LENGTH = "Encryptor.DigitalSignatureKeyLength"; // ==================================// // New in ESAPI Java 2.0 // // ================================= // public static final String PREFERRED_JCE_PROVIDER = "Encryptor.PreferredJCEProvider"; public static final String CIPHER_TRANSFORMATION_IMPLEMENTATION = "Encryptor.CipherTransformation"; public static final String CIPHERTEXT_USE_MAC = "Encryptor.CipherText.useMAC"; public static final String PLAINTEXT_OVERWRITE = "Encryptor.PlainText.overwrite"; public static final String IV_TYPE = "Encryptor.ChooseIVMethod"; public static final String FIXED_IV = "Encryptor.fixedIV"; public static final String COMBINED_CIPHER_MODES = "Encryptor.cipher_modes.combined_modes"; public static final String ADDITIONAL_ALLOWED_CIPHER_MODES = "Encryptor.cipher_modes.additional_allowed"; public static final String KDF_PRF_ALG = "Encryptor.KDF.PRF"; public static final String PRINT_PROPERTIES_WHEN_LOADED = "ESAPI.printProperties"; public static final String WORKING_DIRECTORY = "Executor.WorkingDirectory"; public static final String APPROVED_EXECUTABLES = "Executor.ApprovedExecutables"; public static final String FORCE_HTTPONLYSESSION = "HttpUtilities.ForceHttpOnlySession"; public static final String FORCE_SECURESESSION = "HttpUtilities.SecureSession"; public static final String FORCE_HTTPONLYCOOKIES = "HttpUtilities.ForceHttpOnlyCookies"; public static final String FORCE_SECURECOOKIES = "HttpUtilities.ForceSecureCookies"; public static final String MAX_HTTP_HEADER_SIZE = "HttpUtilities.MaxHeaderSize"; public static final String UPLOAD_DIRECTORY = "HttpUtilities.UploadDir"; public static final String UPLOAD_TEMP_DIRECTORY = "HttpUtilities.UploadTempDir"; public static final String APPROVED_UPLOAD_EXTENSIONS = "HttpUtilities.ApprovedUploadExtensions"; public static final String MAX_UPLOAD_FILE_BYTES = "HttpUtilities.MaxUploadFileBytes"; public static final String RESPONSE_CONTENT_TYPE = "HttpUtilities.ResponseContentType"; public static final String HTTP_SESSION_ID_NAME = "HttpUtilities.HttpSessionIdName"; public static final String APPLICATION_NAME = "Logger.ApplicationName"; public static final String LOG_LEVEL = "Logger.LogLevel"; public static final String LOG_FILE_NAME = "Logger.LogFileName"; public static final String MAX_LOG_FILE_SIZE = "Logger.MaxLogFileSize"; public static final String LOG_ENCODING_REQUIRED = "Logger.LogEncodingRequired"; public static final String LOG_APPLICATION_NAME = "Logger.LogApplicationName"; public static final String LOG_SERVER_IP = "Logger.LogServerIP"; public static final String VALIDATION_PROPERTIES = "Validator.ConfigurationFile"; public static final String VALIDATION_PROPERTIES_MULTIVALUED = "Validator.ConfigurationFile.MultiValued"; public static final String ACCEPT_LENIENT_DATES = "Validator.AcceptLenientDates"; /** * The default max log file size is set to 10,000,000 bytes (10 Meg). If the current log file exceeds the current * max log file size, the logger will move the old log data into another log file. There currently is a max of * 1000 log files of the same name. If that is exceeded it will presumably start discarding the oldest logs. */ public static final int DEFAULT_MAX_LOG_FILE_SIZE = 10000000; protected final int MAX_REDIRECT_LOCATION = 1000; /** * @deprecated It is not clear whether this is intended to be the max file name length for the basename(1) of * a file or the max full path name length of a canonical full path name. Since it is not used anywhere * in the ESAPI code it is being deprecated and scheduled to be removed in release 2.1. */ protected final int MAX_FILE_NAME_LENGTH = 1000; // DISCUSS: Is this for given directory or refer to canonicalized full path name? // Too long if the former! (Usually 255 is limit there.) Hard to tell since not used // here in this class and it's protected, so not sure what it's intent is. It's not // used anywhere in the ESAPI code base. I am going to deprecate it because of this. -kww /* * Implementation Keys */ public static final String LOG_IMPLEMENTATION = "ESAPI.Logger"; public static final String AUTHENTICATION_IMPLEMENTATION = "ESAPI.Authenticator"; public static final String ENCODER_IMPLEMENTATION = "ESAPI.Encoder"; public static final String ACCESS_CONTROL_IMPLEMENTATION = "ESAPI.AccessControl"; public static final String ENCRYPTION_IMPLEMENTATION = "ESAPI.Encryptor"; public static final String INTRUSION_DETECTION_IMPLEMENTATION = "ESAPI.IntrusionDetector"; public static final String RANDOMIZER_IMPLEMENTATION = "ESAPI.Randomizer"; public static final String EXECUTOR_IMPLEMENTATION = "ESAPI.Executor"; public static final String VALIDATOR_IMPLEMENTATION = "ESAPI.Validator"; public static final String HTTP_UTILITIES_IMPLEMENTATION = "ESAPI.HTTPUtilities"; /* * Default Implementations */ public static final String DEFAULT_LOG_IMPLEMENTATION = "org.owasp.esapi.reference.JavaLogFactory"; public static final String DEFAULT_AUTHENTICATION_IMPLEMENTATION = "org.owasp.esapi.reference.FileBasedAuthenticator"; public static final String DEFAULT_ENCODER_IMPLEMENTATION = "org.owasp.esapi.reference.DefaultEncoder"; public static final String DEFAULT_ACCESS_CONTROL_IMPLEMENTATION = "org.owasp.esapi.reference.accesscontrol.DefaultAccessController"; public static final String DEFAULT_ENCRYPTION_IMPLEMENTATION = "org.owasp.esapi.reference.crypto.JavaEncryptor"; public static final String DEFAULT_INTRUSION_DETECTION_IMPLEMENTATION = "org.owasp.esapi.reference.DefaultIntrusionDetector"; public static final String DEFAULT_RANDOMIZER_IMPLEMENTATION = "org.owasp.esapi.reference.DefaultRandomizer"; public static final String DEFAULT_EXECUTOR_IMPLEMENTATION = "org.owasp.esapi.reference.DefaultExecutor"; public static final String DEFAULT_HTTP_UTILITIES_IMPLEMENTATION = "org.owasp.esapi.reference.DefaultHTTPUtilities"; public static final String DEFAULT_VALIDATOR_IMPLEMENTATION = "org.owasp.esapi.reference.DefaultValidator"; private static final Map<String, Pattern> patternCache = new HashMap<String, Pattern>(); /* * Absolute path to the user.home. No longer includes the ESAPI portion as it used to. */ private static final String userHome = System.getProperty("user.home"); /* * Absolute path to the customDirectory */ // DISCUSS: Implicit assumption here that there is no SecurityManager installed enforcing the // prevention of reading system properties. Otherwise this will fail with SecurityException. private static String customDirectory = System.getProperty("org.owasp.esapi.resources"); /* * Relative path to the resourceDirectory. Relative to the classpath. * Specifically, ClassLoader.getResource(resourceDirectory + filename) will * be used to load the file. */ private String resourceDirectory = ".esapi"; // For backward compatibility (vs. "esapi") private final String resourceFile; private EsapiPropertyManager esapiPropertyManager; // private static long lastModified = -1; /** * Instantiates a new configuration, using the provided property file name * * @param resourceFile The name of the property file to load */ DefaultSecurityConfiguration(String resourceFile) { this.resourceFile = resourceFile; this.esapiPropertyManager = new EsapiPropertyManager(); // load security configuration try { loadConfiguration(); this.setCipherXProperties(); } catch (IOException e) { logSpecial("Failed to load security configuration", e); throw new ConfigurationException("Failed to load security configuration", e); } } /** * Instantiates a new configuration with the supplied properties. * * Warning - if the setResourceDirectory() method is invoked the properties will * be re-loaded, replacing the supplied properties. * * @param properties */ public DefaultSecurityConfiguration(Properties properties) { resourceFile = DEFAULT_RESOURCE_FILE; this.properties = properties; this.setCipherXProperties(); } /** * Instantiates a new configuration. */ public DefaultSecurityConfiguration() { this(DEFAULT_RESOURCE_FILE); } private void setCipherXProperties() { // TODO: FUTURE: Replace by future CryptoControls class??? // See SecurityConfiguration.setCipherTransformation() for // explanation of this. // (Propose this in 2.1 via future email to ESAPI-DEV list.) cipherXformFromESAPIProp = getESAPIProperty(CIPHER_TRANSFORMATION_IMPLEMENTATION, "AES/CBC/PKCS5Padding"); cipherXformCurrent = cipherXformFromESAPIProp; } /** * {@inheritDoc} */ public String getApplicationName() { return getESAPIProperty(APPLICATION_NAME, "DefaultName"); } /** * {@inheritDoc} */ public String getLogImplementation() { return getESAPIProperty(LOG_IMPLEMENTATION, DEFAULT_LOG_IMPLEMENTATION); } /** * {@inheritDoc} */ public String getAuthenticationImplementation() { return getESAPIProperty(AUTHENTICATION_IMPLEMENTATION, DEFAULT_AUTHENTICATION_IMPLEMENTATION); } /** * {@inheritDoc} */ public String getEncoderImplementation() { return getESAPIProperty(ENCODER_IMPLEMENTATION, DEFAULT_ENCODER_IMPLEMENTATION); } /** * {@inheritDoc} */ public String getAccessControlImplementation() { return getESAPIProperty(ACCESS_CONTROL_IMPLEMENTATION, DEFAULT_ACCESS_CONTROL_IMPLEMENTATION); } /** * {@inheritDoc} */ public String getEncryptionImplementation() { return getESAPIProperty(ENCRYPTION_IMPLEMENTATION, DEFAULT_ENCRYPTION_IMPLEMENTATION); } /** * {@inheritDoc} */ public String getIntrusionDetectionImplementation() { return getESAPIProperty(INTRUSION_DETECTION_IMPLEMENTATION, DEFAULT_INTRUSION_DETECTION_IMPLEMENTATION); } /** * {@inheritDoc} */ public String getRandomizerImplementation() { return getESAPIProperty(RANDOMIZER_IMPLEMENTATION, DEFAULT_RANDOMIZER_IMPLEMENTATION); } /** * {@inheritDoc} */ public String getExecutorImplementation() { return getESAPIProperty(EXECUTOR_IMPLEMENTATION, DEFAULT_EXECUTOR_IMPLEMENTATION); } /** * {@inheritDoc} */ public String getHTTPUtilitiesImplementation() { return getESAPIProperty(HTTP_UTILITIES_IMPLEMENTATION, DEFAULT_HTTP_UTILITIES_IMPLEMENTATION); } /** * {@inheritDoc} */ public String getValidationImplementation() { return getESAPIProperty(VALIDATOR_IMPLEMENTATION, DEFAULT_VALIDATOR_IMPLEMENTATION); } /** * {@inheritDoc} */ public byte[] getMasterKey() { byte[] key = getESAPIPropertyEncoded(MASTER_KEY, null); if (key == null || key.length == 0) { throw new ConfigurationException( "Property '" + MASTER_KEY + "' missing or empty in ESAPI.properties file."); } return key; } /** * {@inheritDoc} */ public void setResourceDirectory(String dir) { resourceDirectory = dir; logSpecial("Reset resource directory to: " + dir, null); // reload configuration if necessary try { this.loadConfiguration(); } catch (IOException e) { logSpecial("Failed to load security configuration from " + dir, e); } } public int getEncryptionKeyLength() { return getESAPIProperty(KEY_LENGTH, 128); } /** * {@inheritDoc} */ public byte[] getMasterSalt() { byte[] salt = getESAPIPropertyEncoded(MASTER_SALT, null); if (salt == null || salt.length == 0) { throw new ConfigurationException( "Property '" + MASTER_SALT + "' missing or empty in ESAPI.properties file."); } return salt; } /** * {@inheritDoc} */ public List<String> getAllowedExecutables() { String def = ""; String[] exList = getESAPIProperty(APPROVED_EXECUTABLES, def).split(","); return Arrays.asList(exList); } /** * {@inheritDoc} */ public List<String> getAllowedFileExtensions() { String def = ".zip,.pdf,.tar,.gz,.xls,.properties,.txt,.xml"; String[] extList = getESAPIProperty(APPROVED_UPLOAD_EXTENSIONS, def).split(","); return Arrays.asList(extList); } /** * {@inheritDoc} */ public int getAllowedFileUploadSize() { return getESAPIProperty(MAX_UPLOAD_FILE_BYTES, 5000000); } private Properties loadPropertiesFromStream(InputStream is, String name) throws IOException { Properties config = new Properties(); try { config.load(is); logSpecial("Loaded '" + name + "' properties file", null); } finally { if (is != null) try { is.close(); } catch (Exception e) { } } return config; } /** * Load configuration. Never prints properties. * * @throws java.io.IOException * if the file is inaccessible */ protected void loadConfiguration() throws IOException { try { //first attempt file IO loading of properties logSpecial("Attempting to load " + resourceFile + " via file I/O."); properties = loadPropertiesFromStream(getResourceStream(resourceFile), resourceFile); } catch (Exception iae) { //if file I/O loading fails, attempt classpath based loading next logSpecial("Loading " + resourceFile + " via file I/O failed. Exception was: " + iae); logSpecial("Attempting to load " + resourceFile + " via the classpath."); try { properties = loadConfigurationFromClasspath(resourceFile); } catch (Exception e) { logSpecial(resourceFile + " could not be loaded by any means. Fail.", e); throw new ConfigurationException(resourceFile + " could not be loaded by any means. Fail.", e); } } // if properties loaded properly above, get validation properties and merge them into the main properties if (properties != null) { final Iterator<String> validationPropFileNames; //defaults to single-valued for backwards compatibility final boolean multivalued = getESAPIProperty(VALIDATION_PROPERTIES_MULTIVALUED, false); final String validationPropValue = getESAPIProperty(VALIDATION_PROPERTIES, "validation.properties"); if (multivalued) { // the following cast warning goes away if the apache commons lib is updated to current version validationPropFileNames = StrTokenizer.getCSVInstance(validationPropValue); } else { validationPropFileNames = Collections.singletonList(validationPropValue).iterator(); } //clear any cached validation patterns so they can be reloaded from validation.properties patternCache.clear(); while (validationPropFileNames.hasNext()) { String validationPropFileName = validationPropFileNames.next(); Properties validationProperties = null; try { //first attempt file IO loading of properties logSpecial("Attempting to load " + validationPropFileName + " via file I/O."); validationProperties = loadPropertiesFromStream(getResourceStream(validationPropFileName), validationPropFileName); } catch (Exception iae) { //if file I/O loading fails, attempt classpath based loading next logSpecial("Loading " + validationPropFileName + " via file I/O failed."); logSpecial("Attempting to load " + validationPropFileName + " via the classpath."); try { validationProperties = loadConfigurationFromClasspath(validationPropFileName); } catch (Exception e) { logSpecial(validationPropFileName + " could not be loaded by any means. fail.", e); } } if (validationProperties != null) { Iterator<?> i = validationProperties.keySet().iterator(); while (i.hasNext()) { String key = (String) i.next(); String value = validationProperties.getProperty(key); properties.put(key, value); } } if (shouldPrintProperties()) { //FIXME - make this chunk configurable /* logSpecial(" ========Master Configuration========", null); //logSpecial( " ResourceDirectory: " + DefaultSecurityConfiguration.resourceDirectory ); Iterator j = new TreeSet( properties.keySet() ).iterator(); while (j.hasNext()) { String key = (String)j.next(); // print out properties, but not sensitive ones like MasterKey and MasterSalt if ( !key.contains( "Master" ) ) { logSpecial(" | " + key + "=" + properties.get(key), null); } } */ } } } } /** * @param filename * @return An {@code InputStream} associated with the specified file name as * a resource stream. * @throws IOException * If the file cannot be found or opened for reading. */ public InputStream getResourceStream(String filename) throws IOException { if (filename == null) { return null; } try { File f = getResourceFile(filename); if (f != null && f.exists()) { return new FileInputStream(f); } } catch (Exception e) { } throw new FileNotFoundException(); } /** * {@inheritDoc} */ public File getResourceFile(String filename) { logSpecial("Attempting to load " + filename + " as resource file via file I/O."); if (filename == null) { logSpecial("Failed to load properties via FileIO. Filename is null."); return null; // not found. } File f = null; // first, allow command line overrides. -Dorg.owasp.esapi.resources // directory f = new File(customDirectory, filename); if (customDirectory != null && f.canRead()) { logSpecial("Found in 'org.owasp.esapi.resources' directory: " + f.getAbsolutePath()); return f; } else { logSpecial("Not found in 'org.owasp.esapi.resources' directory or file not readable: " + f.getAbsolutePath()); } // if not found, then try the programmatically set resource directory // (this defaults to SystemResource directory/resourceFile URL fileUrl = ClassLoader.getSystemResource(resourceDirectory + "/" + filename); if (fileUrl == null) { fileUrl = ClassLoader.getSystemResource("esapi/" + filename); } if (fileUrl != null) { String fileLocation = fileUrl.getFile(); f = new File(fileLocation); if (f.exists()) { logSpecial("Found in SystemResource Directory/resourceDirectory: " + f.getAbsolutePath()); return f; } else { logSpecial("Not found in SystemResource Directory/resourceDirectory (this should never happen): " + f.getAbsolutePath()); } } else { logSpecial("Not found in SystemResource Directory/resourceDirectory: " + resourceDirectory + File.separator + filename); } // If not found, then try immediately under user's home directory first in // userHome + "/.esapi" and secondly under // userHome + "/esapi" // We look in that order because of backward compatibility issues. String homeDir = userHome; if (homeDir == null) { homeDir = ""; // Without this, homeDir + "/.esapi" would produce // the string "null/.esapi" which surely is not intended. } // First look under ".esapi" (for reasons of backward compatibility). f = new File(homeDir + "/.esapi", filename); if (f.canRead()) { logSpecial("[Compatibility] Found in 'user.home' directory: " + f.getAbsolutePath()); return f; } else { // Didn't find it under old directory ".esapi" so now look under the "esapi" directory. f = new File(homeDir + "/esapi", filename); if (f.canRead()) { logSpecial("Found in 'user.home' directory: " + f.getAbsolutePath()); return f; } else { logSpecial("Not found in 'user.home' (" + homeDir + ") directory: " + f.getAbsolutePath()); } } // return null if not found return null; } /** * Used to load ESAPI.properties from a variety of different classpath locations. * * @param fileName The properties file filename. */ private Properties loadConfigurationFromClasspath(String fileName) throws IllegalArgumentException { Properties result = null; InputStream in = null; ClassLoader[] loaders = new ClassLoader[] { Thread.currentThread().getContextClassLoader(), ClassLoader.getSystemClassLoader(), getClass().getClassLoader() }; String[] classLoaderNames = { "current thread context class loader", "system class loader", "class loader for DefaultSecurityConfiguration class" }; ClassLoader currentLoader = null; for (int i = 0; i < loaders.length; i++) { if (loaders[i] != null) { currentLoader = loaders[i]; try { // try root String currentClasspathSearchLocation = "/ (root)"; in = loaders[i].getResourceAsStream(fileName); // try resourceDirectory folder if (in == null) { currentClasspathSearchLocation = resourceDirectory + "/"; in = currentLoader.getResourceAsStream(resourceDirectory + "/" + fileName); } // try .esapi folder. Look here first for backward compatibility. if (in == null) { currentClasspathSearchLocation = ".esapi/"; in = currentLoader.getResourceAsStream(".esapi/" + fileName); } // try esapi folder (new directory) if (in == null) { currentClasspathSearchLocation = "esapi/"; in = currentLoader.getResourceAsStream("esapi/" + fileName); } // try resources folder if (in == null) { currentClasspathSearchLocation = "src/main/resources/"; in = currentLoader.getResourceAsStream("src/main/resources/" + fileName); } // now load the properties if (in != null) { result = new Properties(); result.load(in); // Can throw IOException logSpecial("SUCCESSFULLY LOADED " + fileName + " via the CLASSPATH from '" + currentClasspathSearchLocation + "' using " + classLoaderNames[i] + "!"); break; // Outta here since we've found and loaded it. } } catch (Exception e) { result = null; } finally { try { in.close(); } catch (Exception e) { } } } } if (result == null) { // CHECKME: This is odd...why not ConfigurationException? throw new IllegalArgumentException("Failed to load " + resourceFile + " as a classloader resource."); } return result; } /** * Used to log errors to the console during the loading of the properties file itself. Can't use * standard logging in this case, since the Logger may not be initialized yet. Output is sent to * {@code PrintStream} {@code System.out}. * * @param message The message to send to the console. * @param e The error that occurred. (This value printed via {@code e.toString()}.) */ private void logSpecial(String message, Throwable e) { StringBuffer msg = new StringBuffer(message); if (e != null) { msg.append(" Exception was: ").append(e.toString()); } System.out.println(msg.toString()); // if ( e != null) e.printStackTrace(); // TODO ??? Do we want this? } /** * Used to log errors to the console during the loading of the properties file itself. Can't use * standard logging in this case, since the Logger may not be initialized yet. Output is sent to * {@code PrintStream} {@code System.out}. * * @param message The message to send to the console. */ private void logSpecial(String message) { System.out.println(message); } /** * {@inheritDoc} */ public String getPasswordParameterName() { return getESAPIProperty(PASSWORD_PARAMETER_NAME, "password"); } /** * {@inheritDoc} */ public String getUsernameParameterName() { return getESAPIProperty(USERNAME_PARAMETER_NAME, "username"); } /** * {@inheritDoc} */ public String getEncryptionAlgorithm() { return getESAPIProperty(ENCRYPTION_ALGORITHM, "AES"); } /** * {@inheritDoc} */ public String getCipherTransformation() { assert cipherXformCurrent != null : "Current cipher transformation is null"; return cipherXformCurrent; } /** * {@inheritDoc} */ public String setCipherTransformation(String cipherXform) { String previous = getCipherTransformation(); if (cipherXform == null) { // Special case... means set it to original value from ESAPI.properties cipherXformCurrent = cipherXformFromESAPIProp; } else { assert !cipherXform.trim() .equals("") : "Cipher transformation cannot be just white space or empty string"; cipherXformCurrent = cipherXform; // Note: No other sanity checks!!! } return previous; } /** * {@inheritDoc} */ public boolean useMACforCipherText() { return getESAPIProperty(CIPHERTEXT_USE_MAC, true); } /** * {@inheritDoc} */ public boolean overwritePlainText() { return getESAPIProperty(PLAINTEXT_OVERWRITE, true); } /** * {@inheritDoc} */ public String getIVType() { String value = getESAPIProperty(IV_TYPE, "random"); if (value.equalsIgnoreCase("fixed") || value.equalsIgnoreCase("random")) { return value; } else if (value.equalsIgnoreCase("specified")) { // This is planned for future implementation where setting // Encryptor.ChooseIVMethod=specified will require setting some // other TBD property that will specify an implementation class that // will generate appropriate IVs. The intent of this would be to use // such a class with various feedback modes where it is imperative // that for a given key, any particular IV is *NEVER* reused. For // now, we will assume that generating a random IV is usually going // to be sufficient to prevent this. throw new ConfigurationException( "'" + IV_TYPE + "=specified' is not yet implemented. Use 'fixed' or 'random'"); } else { // TODO: Once 'specified' is legal, adjust exception msg, below. // DISCUSS: Could just log this and then silently return "random" instead. throw new ConfigurationException( value + " is illegal value for " + IV_TYPE + ". Use 'random' (preferred) or 'fixed'."); } } /** * {@inheritDoc} */ public String getFixedIV() { if (getIVType().equalsIgnoreCase("fixed")) { String ivAsHex = getESAPIProperty(FIXED_IV, ""); // No default if (ivAsHex == null || ivAsHex.trim().equals("")) { throw new ConfigurationException( "Fixed IV requires property " + FIXED_IV + " to be set, but it is not."); } return ivAsHex; // We do no further checks here as we have no context. } else { // DISCUSS: Should we just log a warning here and return null instead? // If so, may cause NullPointException somewhere later. throw new ConfigurationException( "IV type not 'fixed' (set to '" + getIVType() + "'), so no fixed IV applicable."); } } /** * {@inheritDoc} */ public String getHashAlgorithm() { return getESAPIProperty(HASH_ALGORITHM, "SHA-512"); } /** * {@inheritDoc} */ public int getHashIterations() { return getESAPIProperty(HASH_ITERATIONS, 1024); } /** * {@inheritDoc} */ public String getKDFPseudoRandomFunction() { return getESAPIProperty(KDF_PRF_ALG, "HmacSHA256"); // NSA recommended SHA2 or better. } /** * {@inheritDoc} */ public String getCharacterEncoding() { return getESAPIProperty(CHARACTER_ENCODING, "UTF-8"); } /** * {@inheritDoc} */ public boolean getAllowMultipleEncoding() { return getESAPIProperty(ALLOW_MULTIPLE_ENCODING, false); } /** * {@inheritDoc} */ public boolean getAllowMixedEncoding() { return getESAPIProperty(ALLOW_MIXED_ENCODING, false); } /** * {@inheritDoc} */ public List<String> getDefaultCanonicalizationCodecs() { List<String> def = new ArrayList<String>(); def.add("org.owasp.esapi.codecs.HTMLEntityCodec"); def.add("org.owasp.esapi.codecs.PercentCodec"); def.add("org.owasp.esapi.codecs.JavaScriptCodec"); return getESAPIProperty(CANONICALIZATION_CODECS, def); } /** * {@inheritDoc} */ public String getDigitalSignatureAlgorithm() { return getESAPIProperty(DIGITAL_SIGNATURE_ALGORITHM, "SHAwithDSA"); } /** * {@inheritDoc} */ public int getDigitalSignatureKeyLength() { return getESAPIProperty(DIGITAL_SIGNATURE_KEY_LENGTH, 1024); } /** * {@inheritDoc} */ public String getRandomAlgorithm() { return getESAPIProperty(RANDOM_ALGORITHM, "SHA1PRNG"); } /** * {@inheritDoc} */ public int getAllowedLoginAttempts() { return getESAPIProperty(ALLOWED_LOGIN_ATTEMPTS, 5); } /** * {@inheritDoc} */ public int getMaxOldPasswordHashes() { return getESAPIProperty(MAX_OLD_PASSWORD_HASHES, 12); } /** * {@inheritDoc} */ public File getUploadDirectory() { String dir = getESAPIProperty(UPLOAD_DIRECTORY, "UploadDir"); return new File(dir); } /** * {@inheritDoc} */ public File getUploadTempDirectory() { String dir = getESAPIProperty(UPLOAD_TEMP_DIRECTORY, System.getProperty("java.io.tmpdir", "UploadTempDir")); return new File(dir); } /** * {@inheritDoc} */ public boolean getDisableIntrusionDetection() { String value = properties.getProperty(DISABLE_INTRUSION_DETECTION); if ("true".equalsIgnoreCase(value)) return true; return false; // Default result } /** * {@inheritDoc} */ public Threshold getQuota(String eventName) { int count = getESAPIProperty("IntrusionDetector." + eventName + ".count", 0); int interval = getESAPIProperty("IntrusionDetector." + eventName + ".interval", 0); List<String> actions = new ArrayList<String>(); String actionString = getESAPIProperty("IntrusionDetector." + eventName + ".actions", ""); if (actionString != null) { String[] actionList = actionString.split(","); actions = Arrays.asList(actionList); } if (count > 0 && interval > 0 && actions.size() > 0) { return new Threshold(eventName, count, interval, actions); } return null; } /** * {@inheritDoc} */ public int getLogLevel() { String level = getESAPIProperty(LOG_LEVEL, "WARNING"); if (level.equalsIgnoreCase("OFF")) return Logger.OFF; if (level.equalsIgnoreCase("FATAL")) return Logger.FATAL; if (level.equalsIgnoreCase("ERROR")) return Logger.ERROR; if (level.equalsIgnoreCase("WARNING")) return Logger.WARNING; if (level.equalsIgnoreCase("INFO")) return Logger.INFO; if (level.equalsIgnoreCase("DEBUG")) return Logger.DEBUG; if (level.equalsIgnoreCase("TRACE")) return Logger.TRACE; if (level.equalsIgnoreCase("ALL")) return Logger.ALL; // This error is NOT logged the normal way because the logger constructor calls getLogLevel() and if this error occurred it would cause // an infinite loop. logSpecial("The LOG-LEVEL property in the ESAPI properties file has the unrecognized value: " + level + ". Using default: WARNING", null); return Logger.WARNING; // Note: The default logging level is WARNING. } /** * {@inheritDoc} */ public String getLogFileName() { return getESAPIProperty(LOG_FILE_NAME, "ESAPI_logging_file"); } /** * {@inheritDoc} */ public int getMaxLogFileSize() { return getESAPIProperty(MAX_LOG_FILE_SIZE, DEFAULT_MAX_LOG_FILE_SIZE); } /** * {@inheritDoc} */ public boolean getLogEncodingRequired() { return getESAPIProperty(LOG_ENCODING_REQUIRED, false); } /** * {@inheritDoc} */ public boolean getLogApplicationName() { return getESAPIProperty(LOG_APPLICATION_NAME, true); } /** * {@inheritDoc} */ public boolean getLogServerIP() { return getESAPIProperty(LOG_SERVER_IP, true); } /** * {@inheritDoc} */ public boolean getForceHttpOnlySession() { return getESAPIProperty(FORCE_HTTPONLYSESSION, true); } /** * {@inheritDoc} */ public boolean getForceSecureSession() { return getESAPIProperty(FORCE_SECURESESSION, true); } /** * {@inheritDoc} */ public boolean getForceHttpOnlyCookies() { return getESAPIProperty(FORCE_HTTPONLYCOOKIES, true); } /** * {@inheritDoc} */ public boolean getForceSecureCookies() { return getESAPIProperty(FORCE_SECURECOOKIES, true); } /** * {@inheritDoc} */ public int getMaxHttpHeaderSize() { return getESAPIProperty(MAX_HTTP_HEADER_SIZE, 4096); } /** * {@inheritDoc} */ public String getResponseContentType() { return getESAPIProperty(RESPONSE_CONTENT_TYPE, "text/html; charset=UTF-8"); } /** * {@inheritDoc} */ public String getHttpSessionIdName() { return getESAPIProperty(HTTP_SESSION_ID_NAME, "JSESSIONID"); } /** * {@inheritDoc} */ public long getRememberTokenDuration() { int days = getESAPIProperty(REMEMBER_TOKEN_DURATION, 14); return (long) (1000 * 60 * 60 * 24 * days); } /** * {@inheritDoc} */ public int getSessionIdleTimeoutLength() { int minutes = getESAPIProperty(IDLE_TIMEOUT_DURATION, 20); return 1000 * 60 * minutes; } /** * {@inheritDoc} */ public int getSessionAbsoluteTimeoutLength() { int minutes = getESAPIProperty(ABSOLUTE_TIMEOUT_DURATION, 20); return 1000 * 60 * minutes; } /** * getValidationPattern returns a single pattern based upon key * * @param key * validation pattern name you'd like * @return * if key exists, the associated validation pattern, null otherwise */ public Pattern getValidationPattern(String key) { String value = getESAPIProperty("Validator." + key, ""); // check cache Pattern p = patternCache.get(value); if (p != null) return p; // compile a new pattern if (value == null || value.equals("")) return null; try { Pattern q = Pattern.compile(value); patternCache.put(value, q); return q; } catch (PatternSyntaxException e) { logSpecial( "SecurityConfiguration for " + key + " not a valid regex in ESAPI.properties. Returning null", null); return null; } } /** * getWorkingDirectory returns the default directory where processes will be executed * by the Executor. */ public File getWorkingDirectory() { String dir = getESAPIProperty(WORKING_DIRECTORY, System.getProperty("user.dir")); if (dir != null) { return new File(dir); } return null; } /** * {@inheritDoc} */ public String getPreferredJCEProvider() { return properties.getProperty(PREFERRED_JCE_PROVIDER); // No default! } /** * {@inheritDoc} */ public List<String> getCombinedCipherModes() { List<String> empty = new ArrayList<String>(); // Default is empty list return getESAPIProperty(COMBINED_CIPHER_MODES, empty); } /** * {@inheritDoc} */ public List<String> getAdditionalAllowedCipherModes() { List<String> empty = new ArrayList<String>(); // Default is empty list return getESAPIProperty(ADDITIONAL_ALLOWED_CIPHER_MODES, empty); } /** * {@inheritDoc} */ public boolean getLenientDatesAccepted() { return getESAPIProperty(ACCEPT_LENIENT_DATES, false); } protected String getESAPIProperty(String key, String def) { String value = properties.getProperty(key); if (value == null) { logSpecial("SecurityConfiguration for " + key + " not found in ESAPI.properties. Using default: " + def, null); return def; } return value; } protected boolean getESAPIProperty(String key, boolean def) { String property = properties.getProperty(key); if (property == null) { logSpecial("SecurityConfiguration for " + key + " not found in ESAPI.properties. Using default: " + def, null); return def; } if (property.equalsIgnoreCase("true") || property.equalsIgnoreCase("yes")) { return true; } if (property.equalsIgnoreCase("false") || property.equalsIgnoreCase("no")) { return false; } logSpecial("SecurityConfiguration for " + key + " not either \"true\" or \"false\" in ESAPI.properties. Using default: " + def, null); return def; } protected byte[] getESAPIPropertyEncoded(String key, byte[] def) { String property = properties.getProperty(key); if (property == null) { logSpecial("SecurityConfiguration for " + key + " not found in ESAPI.properties. Using default: " + def, null); return def; } try { return ESAPI.encoder().decodeFromBase64(property); } catch (IOException e) { logSpecial("SecurityConfiguration for " + key + " not properly Base64 encoded in ESAPI.properties. Using default: " + def, null); return null; } } protected int getESAPIProperty(String key, int def) { String property = properties.getProperty(key); if (property == null) { logSpecial("SecurityConfiguration for " + key + " not found in ESAPI.properties. Using default: " + def, null); return def; } try { return Integer.parseInt(property); } catch (NumberFormatException e) { logSpecial("SecurityConfiguration for " + key + " not an integer in ESAPI.properties. Using default: " + def, null); return def; } } /** * Returns a {@code List} representing the parsed, comma-separated property. * * @param key The specified property name * @param def A default value for the property name to return if the property * is not set. * @return A list of strings. */ protected List<String> getESAPIProperty(String key, List<String> def) { String property = properties.getProperty(key); if (property == null) { logSpecial("SecurityConfiguration for " + key + " not found in ESAPI.properties. Using default: " + def, null); return def; } String[] parts = property.split(","); return Arrays.asList(parts); } /** * {@inheritDoc} * Looks for property in three configuration files in following order: * 1.) In file defined as org.owasp.esapi.opsteam system property * 2.) In file defined as org.owasp.esapi.devteam system property * 3.) In ESAPI.properties* */ @Override public int getIntProp(String propertyName) throws ConfigurationException { try { return esapiPropertyManager.getIntProp(propertyName); } catch (ConfigurationException ex) { String property = properties.getProperty(propertyName); try { return Integer.parseInt(property); } catch (NumberFormatException e) { throw new ConfigurationException( "SecurityConfiguration for " + propertyName + " has incorrect " + "type"); } } } /** * {@inheritDoc} * Looks for property in three configuration files in following order: * 1.) In file defined as org.owasp.esapi.opsteam system property * 2.) In file defined as org.owasp.esapi.devteam system property * 3.) In ESAPI.properties */ @Override public byte[] getByteArrayProp(String propertyName) throws ConfigurationException { try { return esapiPropertyManager.getByteArrayProp(propertyName); } catch (ConfigurationException ex) { String property = properties.getProperty(propertyName); if (property == null) { throw new ConfigurationException( "SecurityConfiguration for " + propertyName + " not found in ESAPI.properties"); } try { return ESAPI.encoder().decodeFromBase64(property); } catch (IOException e) { throw new ConfigurationException( "SecurityConfiguration for " + propertyName + " has incorrect " + "type"); } } } /** * {@inheritDoc} * Looks for property in three configuration files in following order: * 1.) In file defined as org.owasp.esapi.opsteam system property * 2.) In file defined as org.owasp.esapi.devteam system property * 3.) In ESAPI.properties */ @Override public Boolean getBooleanProp(String propertyName) throws ConfigurationException { try { return esapiPropertyManager.getBooleanProp(propertyName); } catch (ConfigurationException ex) { String property = properties.getProperty(propertyName); if (property == null) { throw new ConfigurationException( "SecurityConfiguration for " + propertyName + " not found in ESAPI.properties"); } if (property.equalsIgnoreCase("true") || property.equalsIgnoreCase("yes")) { return true; } if (property.equalsIgnoreCase("false") || property.equalsIgnoreCase("no")) { return false; } throw new ConfigurationException( "SecurityConfiguration for " + propertyName + " has incorrect " + "type"); } } /** * {@inheritDoc} * Looks for property in three configuration files in following order: * 1.) In file defined as org.owasp.esapi.opsteam system property * 2.) In file defined as org.owasp.esapi.devteam system property * 3.) In ESAPI.properties */ @Override public String getStringProp(String propertyName) throws ConfigurationException { try { return esapiPropertyManager.getStringProp(propertyName); } catch (ConfigurationException ex) { String property = properties.getProperty(propertyName); if (property == null) { throw new ConfigurationException( "SecurityConfiguration for " + propertyName + " not found in ESAPI.properties"); } return property; } } protected boolean shouldPrintProperties() { return getESAPIProperty(PRINT_PROPERTIES_WHEN_LOADED, false); } protected Properties getESAPIProperties() { return properties; } }