/*
 * @(#)Security.java	1.54 97/02/06
 * 
 * Copyright (c) 1995, 1996 Sun Microsystems, Inc. All Rights Reserved.
 * 
 * This software is the confidential and proprietary information of Sun
 * Microsystems, Inc. ("Confidential Information").  You shall not
 * disclose such Confidential Information and shall use it only in
 * accordance with the terms of the license agreement you entered into
 * with Sun.
 * 
 * SUN MAKES NO REPRESENTATIONS OR WARRANTIES ABOUT THE SUITABILITY OF THE
 * SOFTWARE, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
 * PURPOSE, OR NON-INFRINGEMENT. SUN SHALL NOT BE LIABLE FOR ANY DAMAGES
 * SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR DISTRIBUTING
 * THIS SOFTWARE OR ITS DERIVATIVES.
 * 
 * CopyrightVersion 1.1_beta
 * 
 */
 
package java.security;

import java.util.*;
import java.io.*;
/**
 * <p>This class centralizes all security properties and common security
 * methods. One of its primary uses is to manage providers.
 *
 * @version 1.54 97/02/06
 * @author Benjamin Renaud */
public final class Security {

    /* Are we debugging? -- for developers */
    static boolean debug = false;

    /* Are we displaying errors? -- for users */
    static boolean error = true;

    /* The java.security properties */
    private static Properties props; 

    /* Where we cache provider properties */
    private static Properties propCache;

    /* A vector of providers, in order of priority */
    private static Vector providers;

    static {
	initialize();
    }
    
    private static void initialize() {
	props = new Properties();
	propCache = new Properties();
	providers = new Vector();

	File propFile = securityPropFile("java.security");
	if (!propFile.exists()) {
	    System.err.println("security properties not found. using defaults.");
	    initializeStatic();
	} else {
	    try {
		FileInputStream fis = new FileInputStream(propFile);
		props.load(fis);
	    } catch (IOException e) {
		error("could not load security properties file from " + propFile +
		      ". using defaults.");
		initializeStatic();
	    }
	}
	loadProviders();
    }

    /* 
     * Initialize to default values, if <java.home>/lib/java.security
     * is not found.
     */
    private static void initializeStatic() {
	props.put("security.provider.1", "sun.security.provider.Sun");
	props.put("system.scope","sun.security.provider.IdentityDatabase");
    }

    /**
     * Don't let anyone instantiate this. 
     */
    private Security() {
    }

    /**
     * Loops through provider declarations, which are expected to be
     * of the form:
     *
     * security.provider.1=sun.security.provider.Sun
     * security.provider.2=sun.security.jsafe.Jsafe
     * etc.
     *
     * The order determines the default search order when looking for 
     * an algorithm.
     */
    private static void loadProviders() {

	int i = 1;

	while(true) {

	    String name = props.getProperty("security.provider." + i++);
	    if (name == null) {
		break;

	    } else {
		Provider prov = Provider.loadProvider(name);
		if (prov != null) {
		    /* This must manipulate the datastructure
		       directly, because going through addProviders
		       causes a security check to happen, which
		       sometimes will cause the security
		       initialization to fail with bad
		       consequences. */
		    providers.addElement(prov);
		}
	    }
	}
    }

    static File securityPropFile(String filename) {
	// maybe check for a system property which will specify where to
	// look. Someday.
	String sep = File.separator;
	return new File(System.getProperty("java.home") + sep + "lib" + sep + 
			"security" + sep + filename);
    }

    /**
     * Looks up providers, and returns the property mapping the key,
     * if any. The order in which the providers are looked up is the
     * provider-preference order, as specificed in the security
     * properties file.
     */
    static String getProviderProperty(String key) {
	
	String prop = propCache.getProperty(key);
	if (prop != null) {
	    return prop;
	}

	for (int i = 0; i < providers.size(); i++) {
	    Provider prov = (Provider)providers.elementAt(i);
	    
	    prop = prov.getProperty(key);
	    if (prop != null) {
		propCache.put(key, prop);
		return prop;
	    }
	}
	return prop;
    }

    /**
     * We always map names to standard names
     */
    static String getStandardName(String alias, String engineType) {
	return getProviderProperty("Alg.Alias." + engineType + "." + alias);
    }

    /** 
     * Gets a specified property for an algorithm. The algorithm name
     * should be a standard name. See Appendix A in the <a href=
     * "../guide/security/CryptoSpec.html#AppA">
     * Java Cryptography Architecture API Specification &amp; Reference </a> 
     * for information about standard algorithm names.
     * One possible use is by specialized algorithm parsers, which may map 
     * classes to algorithms which they understand (much like Key parsers 
     * do).
     *
     * @param algName the algorithm name.
     *
     * @param propName the name of the property to get.
     * 
     * @return the value of the specified property.  
     */
    public static String getAlgorithmProperty(String algName,
					      String propName) {
	return getProviderProperty("Alg." + propName + "." + algName);
    }

    /** 
     * Given an algorithm name, returns the name of PublicKey class
     * capable of handling keys for that algorithm. The algorithm name
     * should be a standard name. See Appendix A in the <a href=
     * "../guide/security/CryptoSpec.html#AppA">
     * Java Cryptography Architecture API Specification &amp; Reference </a> 
     * for information about standard algorithm names.
     *
     * @param algName the standard algorithm name for which to get
     * a public key class name.
     */
    static String getPublicKeyClassName(String algName, String format) {

	String stdName = getStandardName(algName, "Key");

	if (stdName == null) {
	    stdName = algName;
	}

	String formatAndAlg = "PublicKey." + format + "." + stdName;
	return getProviderProperty(formatAndAlg);
    }


    /** Given an algorithm name, returns the name of PrivateKey class
     * capable of handling keys for that algorithm. The algorithm name
     * should be a standard name. See Appendix A in the <a href=
     * "../guide/security/CryptoSpec.html#AppA">
     * Java Cryptography Architecture API Specification &amp; Reference </a> 
     * for information about standard algorithm names.
     */
    static String getPrivateKeyClassName(String algName, String format) {

	String stdName = getStandardName(algName, "Key");

	if (stdName == null) {
	    stdName = algName;
	}

	return getProviderProperty("PrivateKey." + format + "." + stdName);
    }

    static String getEngineClassName(String algName,
				     String engineType)
    throws NoSuchAlgorithmException {
	/* First get the standard name */
	String stdName = getStandardName(algName, engineType);
	
	if (stdName == null) {
	    stdName = algName;
	}

	Class impl = null;

	Enumeration enum = providers.elements();

	String classname = getProviderProperty(engineType + "." + stdName);

	if (classname != null) {
	    return classname;
	}

	throw new NoSuchAlgorithmException("algorithm " + algName + 
					   " not available.");
    }

    /** Given an algorithm name, returns the name of Signature class
     * capable of handling keys for that algorithm. The algorithm name
     * should be a standard name. See Appendix A in the <a href=
     * "../guide/security/CryptoSpec.html#AppA">
     * Java Cryptography Architecture API Specification &amp; Reference </a> 
     * for information about standard algorithm names.
     */
    private static String getEngineClassName(String algName, String provider, 
					     String engineType) 
    throws NoSuchAlgorithmException, NoSuchProviderException {

	if (provider == null) {
	    return getEngineClassName(algName, engineType);
	}

	/* First get the standard name */
	String stdName = getStandardName(algName, engineType);
	
	if (stdName == null) {
	    stdName = algName;
	}

	Provider prov = getProvider(provider);
	if (prov == null) {
	    throw new NoSuchProviderException("no such provider: " +
						provider);
	}
	
	String className = prov.getProperty(engineType + "." + stdName);
	if (className == null) {
	    throw new NoSuchAlgorithmException("no such algorithm: " +
						     algName + 
						     " for provider " +
						 provider);
	}
	return className;
    }

    /**
     * Adds a new provider, at a specified position. The position is
     * the preference order in which providers are searched for
     * requested algorithms. Note that it is not guaranteed that this
     * preference will be respected. The position is 1-based, that is,
     * 1 is most preferred, followed by 2, and so on. Sometimes it
     * will be legal to add a provider, but only in the last position,
     * in which case the <code>position</code> argument will be ignored. 
     * 
     * <p>A provider cannot be added if it is already installed.
     *
     * @param provider the provider to be added.
     *
     * @param position the preference position that the caller would
     * like for this provider.
     *
     * @return the actual preference position in which the provider was 
     * added, or -1 if the provider was not added because it is
     * already installed.
     *
     * @see #getProvider
     * @see #removeProvider 
     */
    public static int insertProviderAt(Provider provider, int position) {

	check();

	/* First check if the provider is already installed */
	Provider already = getProvider(provider.getName());
	if (already != null) {
	    return -1;
	}	
		
	/* Internally, position is position - 1 */
	position--;

	if (position < 0) {
	    position = 0;
	}
	
	int size = providers.size();
	if (position > size) {
	    position = size;
	}

	providers.insertElementAt(provider, position);

	/* clear the prop caches */
	propCache = new Properties();
	
	return position;
    }

    /**
     * Adds a provider to the next position available.
     *
     * @param provider the provider to be added.
     *
     * @return the preference position in which the provider was 
     * added, or -1 if the provider was not added because it is
     * already installed.
     * 
     * @see #getProvider
     * @see #removeProvider
     */
    public static int addProvider(Provider provider) {
	
	return insertProviderAt(provider, providers.size() + 1);
    }

    /**
     * Removes the provider with the specified name.  This method
     * returns silently if the provider is not installed.
     *
     * @param name the name of the provider to remove.
     *
     * @see #getProvider
     * @see #addProvider
     */
    public static void removeProvider(String name) {

	check();
	
	Provider provider = getProvider(name);

	if (provider != null) {
	    providers.removeElement(provider);
	}
    }

    
    /**
     * Returns all providers currently installed.
     * 
     * @return an array of all providers currently installed.
     */
    public static Provider[] getProviders() {
	check();
	Provider[] result = new Provider[providers.size()];
	providers.copyInto(result);
	return result;
    }

    /**
     * Returns the provider installed with the specified name, if
     * any. Returns null if no provider with the speicified name is
     * installed.
     * 
     * @param name the name of the provider to get.
     * 
     * @return the provider of the specified name.
     *
     * @see #removeProvider
     * @see #addProvider
     */
    public static Provider getProvider(String name) {
	check();
	Enumeration enum = providers.elements();
	while (enum.hasMoreElements()) {
	    Provider prov = (Provider)enum.nextElement();
	    if (prov.getName().equals(name)) {
		return prov;
	    }
	}
	return null;
    }

    private static boolean checkSuperclass(Class subclass, Class superclass) {
	while(!subclass.equals(superclass)) {
	    subclass = subclass.getSuperclass();
	    if (subclass == null) {
		return false;
	    }
	}
	return true;
    }

    /*
     * Return an object configured to implemented type. Provider can
     * be null, in which case all providers will be searched in order
     * of preference.
     */
    static Object getImpl(String algorithm, String type, String provider)
    throws NoSuchAlgorithmException, NoSuchProviderException {	

	String className = getEngineClassName(algorithm, provider, type);

	try {
	    Class typeClass = Class.forName("java.security." + type);
	    Class cl = Class.forName(className);

	    if (checkSuperclass(cl, typeClass)) {
		return cl.newInstance();
	    } else {
		throw new NoSuchAlgorithmException("class configured for " + 
						   type + ": " + className + 
						   " not a " + type);
	    }
	} catch (ClassNotFoundException e) {
	    throw new NoSuchAlgorithmException("class configured for " + 
					       type + "(provider: " + 
					       provider + ")" + 
					       "cannot be found.\n" + 
					       e.getMessage());
	} catch (InstantiationException e) {
	    throw new NoSuchAlgorithmException("class " + className + 
					       " configured for " + type +
					       "(provider: " + provider + 
					       ") cannot be instantiated.\n" + 
					       e.getMessage());
	} catch (IllegalAccessException e) {
	    throw new NoSuchAlgorithmException("class " + className + 
					       " configured for " + type +
					       "(provider: " + provider +
					       ") cannot be accessed.\n" + 
					       e.getMessage());
	}
    }

    /**
     * Gets a security property.
     *
     * @param key the key of the property being retrieved.
     *
     * @return the valeu of the security property corresponding to key.
     */
    public static String getProperty(String key) {
	check();
	return props.getProperty(key);
    }

    /**
     * Sets a security property.
     *
     * @param key the name of the property to be set.
     *
     * @param datum the value of the property to be set.
     */
    public static void setProperty(String key, String datum) {
	check();
	props.put(key, datum);
    }

    private static void check() {
	
	SecurityManager security = System.getSecurityManager();
	if (security != null) {
	    security.checkSecurityAccess("java");
	}
    }
    
    /**
     * Print an error message that may be significant to a user.
     */
    static void error(String msg) {
	if (debug) {
	    System.err.println(msg);
	}
    }

    /**
     * Print an error message that may be significant to a user.
     */
    static void error(String msg, Throwable t) {
	error(msg);
	if (debug) {
	    t.printStackTrace();
	}
    }
	
    /**
     * Print an debugging message that may be significant to a developer.
     */
    static void debug(String msg) {
	if (debug) {
	    System.err.println(msg);
	}
    }

    /**
     * Print an debugging message that may be significant to a developer.
     */
    static void debug(String msg, Throwable t) {
	if (debug) {
	    t.printStackTrace();
	    System.err.println(msg);
	}
    }

    
	
}
	

