/* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * * Copyright (c) 2005 Sun Microsystems Inc. All Rights Reserved * * The contents of this file are subject to the terms * of the Common Development and Distribution License * (the License). You may not use this file except in * compliance with the License. * * You can obtain a copy of the License at * https://opensso.dev.java.net/public/CDDLv1.0.html or * opensso/legal/CDDLv1.0.txt * See the License for the specific language governing * permission and limitations under the License. * * When distributing Covered Code, include this CDDL * Header Notice in each file and include the License file * at opensso/legal/CDDLv1.0.txt. * If applicable, add the following below the CDDL Header, * with the fields enclosed by brackets [] replaced by * your own identifying information: * "Portions Copyrighted [year] [name of copyright owner]" * * $Id: Crypt.java,v 1.4 2008/08/19 19:14:54 veiming Exp $ * * Portions Copyrighted 2010-2015 ForgeRock AS. */ package com.iplanet.services.util; import java.io.BufferedReader; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.PrintStream; import java.io.StringReader; import java.io.UnsupportedEncodingException; import com.sun.identity.shared.debug.Debug; import com.sun.identity.shared.encode.Base64; import com.sun.identity.shared.configuration.SystemPropertiesManager; import com.sun.identity.security.ISSecurityPermission; /** * The class Crypt provides generic methods to encryt and decrypt * data. This class provides a pluggable architecture to encrypt and decrypt * data, using the AMEncryption interface class. A class that * implements AMEncryption must be specified via the system * property: com.iplanet.services.security.encryptor. If none is * provided, the default provided by iDSAME * com.iplanet.services.util.JCEEncryption will be used. *

* Additionally, it provides a method to check if the calling class has * permission to call these methods. To enable the additional security, the * property com.sun.identity.security.checkcaller must be set to true. */ public class Crypt { // Private static final varibales private static final String ENCRYPTOR_CLASS_PROPERTY = "com.iplanet.security.encryptor"; private static final String CHECK_CALLER_PROPERTY = "com.sun.identity.security.checkcaller"; private static final String DEFAULT_ENCRYPTOR_CLASS = "com.iplanet.services.util.JCEEncryption"; // The pwd can be changed through the config file. // But be super consious when you change it. You have to change the // encrypted versions of the admin passwords simulaneously. private static final String PROPERTY_PWD = "am.encryption.pwd"; private static final String PROPERTY_PWD_LOCAL = "com.sun.identity.client.encryptionKey"; private static final String DEFAULT_PWD = "KmhUnWR1MYWDYW4xuqdF5nbm+CXIyOVt"; private static boolean checkCaller; public static SecurityManager securityManager; private static AMEncryption encryptor; private static AMEncryption localEncryptor; private static AMEncryption hardcodedKeyEncryptor; static { initialize(); } public static synchronized void reinitialize() { initialize(); } private static void initialize() { encryptor = createInstance(SystemPropertiesManager.get(PROPERTY_PWD, DEFAULT_PWD)); localEncryptor = createInstance(SystemPropertiesManager.get( PROPERTY_PWD_LOCAL, SystemPropertiesManager.get(PROPERTY_PWD, DEFAULT_PWD))); hardcodedKeyEncryptor = createInstance(DEFAULT_PWD); // check if caller needs to be validated String cCaller = SystemPropertiesManager.get(CHECK_CALLER_PROPERTY); if ((cCaller != null) && (cCaller.equalsIgnoreCase("true"))) { checkCaller = true; securityManager = System.getSecurityManager(); } } private static AMEncryption createInstance(String password) { AMEncryption instance; // Construct the encryptor class String encClass = SystemPropertiesManager.get(ENCRYPTOR_CLASS_PROPERTY, DEFAULT_ENCRYPTOR_CLASS); try { instance = (AMEncryption) Class.forName(encClass).newInstance(); } catch (Exception e) { Debug debug = Debug.getInstance("amSDK"); debug.error("Crypt:: Unable to get class instance: " + encClass, e); instance = new JCEEncryption(); } try { ((ConfigurableKey) instance).setPassword(password); } catch (Exception e) { Debug debug = Debug.getInstance("amSDK"); if (debug != null) { debug.error("Crypt: failed to set password-based key", e); } } return instance; } /** * Check to see if security is enabled and Caller needs to be checked for * OpenAM specific Java security permissions * * @return boolean true if security check enabled, false otherwise */ public static boolean checkCaller() { return checkCaller; } /** * This is a temporary kludge which always returns an instance of * AMEncryption using hardcoded key It is necessary for backward * compatibility with 2.0 Java agents This method is to be ONLY used by * Session module for session id generation. * */ public static AMEncryption getHardcodedKeyEncryptor() { return hardcodedKeyEncryptor; } /** * Checks security permission returns true if action is allowed, false * otherwise */ private static boolean isAccessPermitted() { try { ISSecurityPermission isp = new ISSecurityPermission("access", "adminpassword"); if (securityManager != null) { securityManager.checkPermission(isp); } return true; } catch (SecurityException e) { Debug debug = Debug.getInstance("amSDK"); debug.error( "Security Alert: Unauthorized access to Encoding/Decoding" + " password utility: Returning NULL", e); } return false; } /** * Return AMEncryption instance for deployment-specific secret key * */ public static AMEncryption getEncryptor() { return isAccessPermitted() ? encryptor : null; } /** *

* Encrypt a String. *

* * @param clearText * The string to be encoded. * @return The encoded string. */ public static String encrypt(String clearText) { return encode(clearText); } /** *

* Encrypt a String using the client's encryption key *

* * @param clearText * The string to be encoded. * @return The encoded string. */ public static String encryptLocal(String clearText) { return encode(clearText, localEncryptor); } /** *

* Decrypt a String. *

* * @param encoded * The string to be decoded. * @return The decoded string. */ public static String decrypt(String encoded) { return decode(encoded); } /** *

* Decrypt a String using client's encryption key *

* * @param encoded * The string to be decoded. * @return The decoded string. */ public static String decryptLocal(String encoded) { return decode(encoded, localEncryptor); } /** *

* Encode a String. *

* * @param clearText * The string to be encoded. * @param encr * instance of AMEncryption to use * @return The encoded string. */ public static String encode(String clearText, AMEncryption encr) { if (checkCaller()) { if (!isAccessPermitted()) return null; } if (clearText == null || clearText.length() == 0) { return null; } // Encrypt the data byte[] encData = null; try { encData = encr.encrypt(clearText.getBytes("utf-8")); } catch (UnsupportedEncodingException uee) { Debug debug = Debug.getInstance("amSDK"); debug.error("Crypt:: utf-8 encoding is not supported"); encData = encryptor.encrypt(clearText.getBytes()); } // BASE64 encode the data String str = null; // Perf Improvement : Removed the sync block and newed up the Encoder // object for every call. Its a trade off b/w CPU and mem usage. str = Base64.encode(encData).trim(); // Serialize the data, i.e., remove \n and \r BufferedReader bufReader = new BufferedReader(new StringReader(str)); StringBuffer strClean = new StringBuffer(str.length()); String strTemp = null; try { while ((strTemp = bufReader.readLine()) != null) { strClean.append(strTemp); } } catch (IOException ioe) { Debug debug = Debug.getInstance("amSDK"); debug.error("Crypt:: Error while base64 encoding", ioe); } return (strClean.toString()); } /** *

* Encode a String. *

* * @param clearText * The string to be encoded. * @return The encoded string. */ public static String encode(String clearText) { return encode(clearText, encryptor); } /** * Decode an encoded string * * @param encoded * The encoded string. * @param encr * instance of AMEncryption to use * @return The decoded string. */ public static String decode(String encoded, AMEncryption encr) { if (checkCaller()) { try { ISSecurityPermission isp = new ISSecurityPermission("access", "adminpassword"); if (securityManager != null) { securityManager.checkPermission(isp); } } catch (SecurityException e) { Debug debug = Debug.getInstance("amSDK"); debug.error("Security Alert: Unauthorized access to " + "Encoding/Decoding password utility: Returning NULL", e); return null; } } if (encoded == null || encoded.length() == 0) { return (null); } // BASE64 decode the data byte[] encData = null; // Perf Improvement : Removed the sync block and newed up the Decoder // object for every call. Its a trade off b/w CPU and mem usage. encData = Base64.decode(encoded.trim()); //The return value of Base64.decode can be null //if the value isn't divisible by 4. (i.e. corrupted). if (encData == null) { return null; } // Decrypt the data byte[] rawData = encr.decrypt(encData); if (rawData == null) { return (null); } // Convert to String and return String answer = null; try { answer = new String(rawData, "utf-8"); } catch (UnsupportedEncodingException uue) { Debug debug = Debug.getInstance("amSDK"); debug.error("Crypt:: Unsupported encoding UTF-8", uue); answer = new String(rawData); } return (answer); } /** * Decode an encoded string * * @param encoded * The encoded string. * @return The decoded string. */ public static String decode(String encoded) { return decode(encoded, encryptor); } /** * Check to determine if the calling class has the privilege to execute * sensitive methods which returns passwords, decrypts data, etc. This * method uses the stack trace to determine the calling class. */ protected static boolean isCallerValid() { if (!checkCaller) { return (true); } return (isCallerValid(CLASSNAME)); } /** * Check to determine if the calling class has the privilege to execute * sensitive methods which returns passwords, decrypts data, etc. This * method uses the stack trace to determine the calling class. * * @param obj * The Java object that is performing this check */ public static boolean isCallerValid(Object obj) { if (!checkCaller) { return (true); } if (obj == null) { return (isCallerValid(CLASSNAME)); } return (isCallerValid(obj.getClass().getName())); } /** * Check to determine if the calling class has the privilege to execute * sensitive methods which returns passwords, decrypts data, etc. This * method uses the stack trace to determine the calling class. * * @param className * fully qualified class name of Object calling this function */ public static boolean isCallerValid(String className) { if (!checkCaller) { return (true); } String parentClass = getParentClass(className); // Check for Package name matches for (int i = 0; i < VALID_PACKAGES.length; i++) { if (parentClass.startsWith(VALID_PACKAGES[i])) { return (true); } } // Check for Class name matches for (int i = 0; i < VALID_CLASSES.length; i++) { if (parentClass.equals(VALID_CLASSES[i])) { return (true); } } return (false); } protected static String getParentClass(String callerClass) { String parentClass = null; try { throw (new Exception()); } catch (Exception pe) { ByteArrayOutputStream os = new ByteArrayOutputStream(); PrintStream ps = new PrintStream(os); pe.printStackTrace(ps); String stackTrace = os.toString(); String index = stackTrace.substring(stackTrace .lastIndexOf(callerClass) + callerClass.length()); stackTrace = index.substring(index.lastIndexOf(AT_NAME) + AT_NAME.length()); parentClass = stackTrace.substring(0, stackTrace.indexOf("(")); parentClass = stackTrace.substring(0, parentClass.lastIndexOf(".")); } return (parentClass); } private static final String[] VALID_PACKAGES = { "com.iplanet.services", "com.iplanet.am", "com.sun.identity.policy" }; private static final String[] VALID_CLASSES = { "com.iplanet.services.util.Crypt", "TestCrypt" }; private static final String CLASSNAME = "com.iplanet.services.util.Crypt"; private static final String AT_NAME = "at "; }