At 12:46  14/1/01 -0600, Glenn Nielsen wrote:
>                Tomcat 4 Java SecurityManager Proposal
>
>
>Tomcat 4 will require the Java SecurityManager, use of the
>SecurityManager will be optional when Tomcat is embedded in
>another application.

Why not always have it optional possibly determined by a system property
value ? This way you won't have people complainig about slow down if they
don't care about security ;) It will also be easier to integrate into other
products.

>Setting policies for web application contexts
>---------------------------------------------
>
>A web application has its security based on either the default grant in
>tomcat.policy or an entry for the context which uses the Context path
>file URL as the codeBase. This policy will be in affect for any Class running
>within the Context thread no matter which ClassLoader loaded the class
>which triggered a security check. A default permission to read files in
>the Context path is granted.
>
>// Default permissions for a Context, all contexts have these permissions
>grant {
>        permission java.util.PropertyPermission "file.separator", "read";
>        permission java.util.PropertyPermission "path.separator", "read";
>        permission java.util.PropertyPermission "line.separator", "read";
>};
>
>// Additional Permissions for tomcat examples context
>grant codeBase="file:${tomcat.home}/webapps/examples/- {
>        permission java.util.PropertyPermission "*", "read";
>};

I have actually implemented something similar this in Avalon about 7 hours
ago checking it in. Avalon has notion of .sars (Server Application
Archives) which are very similar to .wars in idea. I was having some issues
with JVM wide policy which you advocate above so I started playing around
with a per-sar policy.

To do this I had to subclass SecureClassLoader (I did this via
URLClassLoader) and overide it's method

protected PermissionCollection getPermissions( final CodeSource codeSource );

I overode this to access a per-sar Policy object. If no policy was given it
would give full access to the system. As I was embedding policy information
in another file (an XML file) it looked something like

    <policy>

<!--      
      <keystore name="foo-keystore" 
                location="file:${sar.home}${/}conf${/}keystore" 
                type="JKS" /> 
      <grant signed-by="Fred" code-base="file:${sar.home}/blocks/*"
key-store="foo-keystore">
        <permission class="java.io.FilePermission" target="/tmp/*"
action="read,write" />
      </grant>
 -->
      <grant code-base="file:${sar.home}${/}blocks${/}*">
        <permission class="java.security.AllPermission" />
      </grant>

      <grant code-base="file:${sar.home}${/}lib${/}*">
        <permission class="java.security.AllPermission" />
      </grant>

To do this I implemented AbstractPolicy (for generic facilities) and
SarPolicy (for configuring it from XML file). I have attached the relevent
classes just in case you are interested.

>Future enhancements
>-------------------
>                   
>See if it is possible and secure to let an individual
>web application set its security policies in its own 
>policy file located in its /WEB-INF/ directory.

This is a great facility - when implementing the equivelent for Avalon I
found it vital for a usable system ;)
/*
 * Copyright (C) The Apache Software Foundation. All rights reserved.
 *
 * This software is published under the terms of the Apache Software License
 * version 1.1, a copy of which has been included with this distribution in
 * the LICENSE file.
 */
package org.apache.avalon.engine;

import java.io.File;
import java.net.URL;
import java.net.MalformedURLException;
import java.security.AccessController;
import java.security.CodeSource;
import java.security.Permission;
import java.security.PermissionCollection;
import java.security.Permissions;
import java.security.Policy;
import java.security.PrivilegedExceptionAction;
import java.security.PrivilegedActionException;
import java.security.cert.Certificate;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.PropertyPermission;
import org.apache.avalon.util.io.FileUtil;
import org.apache.avalon.Component;
import org.apache.avalon.Loggable;
import org.apache.log.Logger;

/**
 * Abstract policy extended in avalon.
 *
 * @author <a href="mailto:[EMAIL PROTECTED]">Peter Donald</a>
 */
public abstract class AbstractPolicy
    extends Policy
    implements Component, Loggable
{
    protected final static boolean     DEBUG         = true;

    protected final ArrayList        m_entries     = new ArrayList();

    protected Logger                 m_logger;

    /**
     * Internal Policy Entry holder class.
     */
    protected final static class PolicyEntry
    {
        CodeSource                      m_codeSource;
        Permissions                     m_permissions;
    }

    public void setLogger( final Logger logger )
    {
        m_logger = logger;
    }

    /**
     * Overide so we can have a per-application security policy with
     * no side-effects to other applications.
     *
     * @param codeSource the codeSource to get permissions for
     * @return the PermissionCollection
     */
    public PermissionCollection getPermissions( CodeSource codeSource )
    {
        codeSource = normalize( codeSource );

        getLogger().debug( "getPermissions(" + codeSource.getLocation() + ");" );

        final Permissions permissions = new Permissions();
        final int size = m_entries.size();

        for( int i = 0; i < size; i++ )
        {
            final PolicyEntry entry = (PolicyEntry)m_entries.get( i );

            if( entry.m_codeSource.implies( codeSource ) )
            {
                if( DEBUG )
                {
                    getLogger().debug( entry.m_codeSource.getLocation() + " implies " +
                                       codeSource.getLocation() );
                }

                copyPermissions( permissions, entry.m_permissions );
            }
        }

        if( DEBUG )
        {
            getLogger().debug( codeSource.getLocation() + " permissions = " + 
permissions );
        }

        return permissions;
    }

    /**
     * Refresh policy. Ignored in this implementation.
     */
    public void refresh()
    {
    }

    /**
     * Normalizing CodeSource involves removing relative addressing
     * (like .. and .) for file urls.
     *
     * @param codeSource the codeSource to be normalized
     * @return the normalized codeSource
     */
    protected CodeSource normalize( final CodeSource codeSource )
    {
        final URL initialLocation = codeSource.getLocation();

        // This is a bit of a hack.  I don't know why CodeSource should behave like 
this
        // Fear not, this only seems to be a problem for home grown classloaders.
        // - Paul Hammant, Nov 2000
        if( null == initialLocation ) return codeSource;

        String location = null;

        if( !initialLocation.getProtocol().equalsIgnoreCase( "file" ) )
        {
            location = initialLocation.getFile();
            location = FileUtil.normalize( location );
        }
        else
        {
            final File file = new File( initialLocation.getFile() );
            location = file.getAbsoluteFile().toString().replace( File.separatorChar, 
'/' );
            location =  FileUtil.normalize( location );
        }

        URL finalLocation = null;

        try
        {
            finalLocation = new URL( initialLocation.getProtocol(),
                                     initialLocation.getHost(),
                                     initialLocation.getPort(),
                                     location );
        }
        catch( final MalformedURLException mue )
        {
            getLogger().warn( "Error building codeBase", mue );
        }

        return new CodeSource( finalLocation, codeSource.getCertificates() );
    }

    protected void copyPermissions( final Permissions destination, final Permissions 
src )
    {
        final Enumeration enum = src.elements();
        while( enum.hasMoreElements() )
        {
            destination.add( (Permission)enum.nextElement() );
        }
    }

    /**
     * Create a permission set for a codeBase.
     * These are read-write permissions and can be written till until the
     * time in which they are applied to code.
     *
     * @param location the location of codes to apply permission set to.
     * @param signers a comma seperated string of thos who signed codebase
     * @return the new permission set
     * @exception MalformedURLException if location string is malformed
     */
    protected Permissions createPermissionSetFor( final String location,
                                                  final Certificate[] signers )
        throws MalformedURLException
    {
        final PolicyEntry entry = new PolicyEntry();
        entry.m_codeSource = new CodeSource( new URL( location ), signers );
        entry.m_codeSource = normalize( entry.m_codeSource );

        getLogger().debug( "createPermissionSetFor(" +
                           entry.m_codeSource.getLocation() + ");" );

        entry.m_permissions = new Permissions();

        m_entries.add( entry );
        return entry.m_permissions;
    }

    protected final Logger getLogger()
    {
        return m_logger;
    }
}
/*
 * Copyright (C) The Apache Software Foundation. All rights reserved.
 *
 * This software is published under the terms of the Apache Software License
 * version 1.1, a copy of which has been included with this distribution in
 * the LICENSE file.
 */
package org.apache.avalon.engine;

import java.net.URL;
import java.net.URLClassLoader;
import java.security.CodeSource;
import java.security.PermissionCollection;
import java.security.Permissions;
import java.security.Policy;

/**
 * Classloader that applies correct policy information.
 *
 * @author <a href="mailto:[EMAIL PROTECTED]">Peter Donald</a>
 */
public class SarClassLoader
    extends URLClassLoader
{
    protected Policy      m_policy;

    public SarClassLoader( final URL[] urls,
                           final ClassLoader classLoader,
                           final Policy policy )
    {
        super( urls, classLoader );
        m_policy = policy;
    }

    /**
     * Overide so we can have a per-application security policy with
     * no side-effects to other applications.
     *
     * @param codeSource the codeSource to get permissions for
     * @return the PermissionCollection
     */
    protected PermissionCollection getPermissions( final CodeSource codeSource )
    {
        if( null == m_policy )
        {
            final Permissions permissions = new Permissions();
            permissions.add( new java.security.AllPermission() );
            return permissions;
        }
        else
        {
            return m_policy.getPermissions( codeSource );
        }
    }
}
/*
 * Copyright (C) The Apache Software Foundation. All rights reserved.
 *
 * This software is published under the terms of the Apache Software License
 * version 1.1, a copy of which has been included with this distribution in
 * the LICENSE file.
 */
package org.apache.avalon.engine;

import java.io.InputStream;
import java.io.File;
import java.lang.reflect.Constructor;
import java.net.MalformedURLException;
import java.net.URL;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.Permission;
import java.security.PermissionCollection;
import java.security.Permissions;
import java.security.UnresolvedPermission;
import java.security.cert.Certificate;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.PropertyPermission;
import java.util.StringTokenizer;
import org.apache.avalon.Component;
import org.apache.avalon.Configurable;
import org.apache.avalon.Configuration;
import org.apache.avalon.ConfigurationException;
import org.apache.avalon.Context;
import org.apache.avalon.DefaultContext;
import org.apache.avalon.Contextualizable;
import org.apache.avalon.Initializable;
import org.apache.avalon.util.PropertyException;
import org.apache.avalon.util.PropertyUtil;
import org.apache.avalon.util.io.FileUtil;
import org.apache.log.Logger;

/**
 * Policy that extracts information from policy files.
 *
 * @author <a href="mailto:[EMAIL PROTECTED]">Peter Donald</a>
 */
public class SarPolicy
    extends AbstractPolicy
    implements Component, Contextualizable, Configurable, Initializable
{
    protected DefaultContext    m_context;

    public void contextualize( final Context context )
    {
        m_context = new DefaultContext( System.getProperties(), context );
        m_context.put( "/", File.separator );
    }

    public void configure( final Configuration configuration )
        throws ConfigurationException
    {
        final Iterator keyStoreConfigurations = configuration.getChildren( "keystore" 
);
        final HashMap keyStores = configureKeyStores( keyStoreConfigurations );

        final Iterator grants = configuration.getChildren( "grant" );
        configureGrants( grants, keyStores );
    }

    public void init()
        throws Exception
    {
        //these properties straight out ot ${java.home}/lib/security/java.policy
        final Permissions permissions = createPermissionSetFor( "file:/-", null );

        permissions.add( new PropertyPermission( "os.name", "read" ) );
        permissions.add( new PropertyPermission( "os.arch", "read" ) );
        permissions.add( new PropertyPermission( "os.version", "read" ) );
        permissions.add( new PropertyPermission( "file.separator", "read" ) );
        permissions.add( new PropertyPermission( "path.separator", "read" ) );
        permissions.add( new PropertyPermission( "line.separator", "read" ) );

        permissions.add( new PropertyPermission( "java.version", "read" ) );
        permissions.add( new PropertyPermission( "java.vendor", "read" ) );
        permissions.add( new PropertyPermission( "java.vendor.url", "read" ) );

        permissions.add( new PropertyPermission( "java.class.version", "read" ) );
        permissions.add( new PropertyPermission( "java.vm.version", "read" ) );
        permissions.add( new PropertyPermission( "java.vm.vendor", "read" ) );
        permissions.add( new PropertyPermission( "java.vm.name", "read" ) );

        permissions.add( new PropertyPermission( "java.specification.version", "read" 
) );
        permissions.add( new PropertyPermission( "java.specification.vendor", "read" ) 
);
        permissions.add( new PropertyPermission( "java.specification.name", "read" ) );
        permissions.add( new PropertyPermission( "java.vm.specification.version", 
"read" ) );
        permissions.add( new PropertyPermission( "java.vm.specification.vendor", 
"read" ) );
        permissions.add( new PropertyPermission( "java.vm.specification.name", "read" 
) );
    }

    protected HashMap configureKeyStores( final Iterator configurations )
        throws ConfigurationException
    {
        final HashMap keyStores = new HashMap();

        while( configurations.hasNext() )
        {
            final Configuration configuration = (Configuration)configurations.next();

            final String type = configuration.getAttribute( "type" );
            final String location = configuration.getAttribute( "location" );
            final String name = configuration.getAttribute( "name" );

            try
            {
                final KeyStore keyStore = KeyStore.getInstance( type );
                final URL url = new URL( location );
                final InputStream ins = url.openStream();

                keyStore.load( ins, null );

                keyStores.put( name, keyStore );
            }
            catch( final Exception e )
            {
                throw new ConfigurationException( "Error configuring keystore " + 
name, e );
            }
        }

        return keyStores;
    }

    protected void configureGrants( final Iterator configurations, final HashMap 
keyStores )
        throws ConfigurationException
    {
        while( configurations.hasNext() )
        {
            final Configuration configuration = (Configuration)configurations.next();
            configureGrant( configuration, keyStores );
        }
    }

    protected void configureGrant( final Configuration configuration, final HashMap 
keyStores )
        throws ConfigurationException
    {
        //<grant signed-by="Fred" code-base="file:${sar.home}/blocks/*" 
key-store="foo-keystore">
        //<permission class="java.io.FilePermission" target="/tmp/*" 
action="read,write" />
        //</grant>

        final String signedBy = configuration.getAttribute( "signed-by", null );
        final String keyStoreName = configuration.getAttribute( "key-store", null );

        String codeBase = configuration.getAttribute( "code-base", null );
        if( null != codeBase )
        {
            codeBase = expand( codeBase );
        }

        final Certificate[] signers = getSigners( signedBy, keyStoreName, keyStores );

        Permissions permissions = null;

        try { permissions = createPermissionSetFor( codeBase, signers ); }
        catch( final MalformedURLException mue )
        {
            throw new ConfigurationException( "Malformed code-base " + codeBase, mue );
        }


        configurePermissions( configuration.getChildren( "permission" ),
                              permissions,
                              keyStores );
    }

    protected void configurePermissions( final Iterator configurations,
                                         final Permissions permissions,
                                         final HashMap keyStores )
        throws ConfigurationException
    {
        while( configurations.hasNext() )
        {
            final Configuration configuration = (Configuration)configurations.next();
            configurePermission( configuration, permissions, keyStores );
        }
    }

    protected void configurePermission( final Configuration configuration,
                                        final Permissions permissions,
                                        final HashMap keyStores )
        throws ConfigurationException
    {
        final String type = configuration.getAttribute( "class" );
        final String actions = configuration.getAttribute( "actions", null );
        final String signedBy = configuration.getAttribute( "signed-by", null );
        final String keyStoreName = configuration.getAttribute( "key-store", null );

        String target = configuration.getAttribute( "target", null );
        if( null != target )
        {
            target = expand( target );
        }

        final Certificate[] signers = getSigners( signedBy, keyStoreName, keyStores );
        final Permission permission = createPermission( type, target, actions, signers 
);

        permissions.add( permission );
    }

    protected String expand( final String value )
        throws ConfigurationException
    {
        try
        {
            final Object resolvedValue = PropertyUtil.resolveProperty( value, 
m_context, false );
            return resolvedValue.toString();
        }
        catch( final PropertyException pe )
        {
            throw new ConfigurationException( "Error resolving property " + value, pe 
);
        }
    }

    protected Permission createPermission( final String type,
                                           final String target,
                                           final String actions,
                                           final Certificate[] signers )
        throws ConfigurationException
    {
        if( null != signers )
        {
            return createUnresolvedPermission( type, target, actions, signers );
        }

        try
        {
            final Class c = Class.forName( type );

            Class paramClasses[] = null;
            Object params[] = null;

            if( null == actions && null == target )
            {
                paramClasses = new Class[ 0 ];
                params = new Object[ 0 ];
            }
            else if( null == actions )
            {
                paramClasses = new Class[1];
                paramClasses[0] = String.class;
                params = new Object[1];
                params[0] = target;
            }
            else
            {
                paramClasses = new Class[2];
                paramClasses[0] = String.class;
                paramClasses[1] = String.class;
                params = new Object[2];
                params[0] = target;
                params[1] = actions;
            }

            final Constructor constructor = c.getConstructor( paramClasses );
            final Object o = constructor.newInstance( params );
            return (Permission)o;
        }
        catch( final ClassNotFoundException cnfe )
        {
            return createUnresolvedPermission( type, target, actions, signers );
        }
        catch( final Exception e )
        {
            throw new ConfigurationException( "Failed to create permission " + type +
                                              " due to " + e, e );
        }
    }

    protected Permission createUnresolvedPermission( final String type,
                                                     final String target,
                                                     final String actions,
                                                     final Certificate[] signers )
    {
        return new UnresolvedPermission( type, target, actions, signers );
    }

    protected Certificate[] getSigners( final String signedBy,
                                        String keyStoreName,
                                        final HashMap keyStores )
        throws ConfigurationException
    {
        if( null != signedBy && null == keyStoreName )
        {
            keyStoreName = "default";
        }

        Certificate[] signers = null;

        if( null != signedBy )
        {
            signers = getCertificates( signedBy, keyStoreName, keyStores );
        }

        return signers;
    }

    protected Certificate[] getCertificates( final String signedBy,
                                             final String keyStoreName,
                                             final HashMap keyStores )
        throws ConfigurationException
    {
        final KeyStore keyStore = (KeyStore)keyStores.get( keyStoreName );

        if( null == keyStore )
        {
            throw new ConfigurationException( "Unable to aquire keyStore " + 
keyStoreName );
        }

        final ArrayList certificateSet = new ArrayList();

        final StringTokenizer tokenizer = new StringTokenizer( signedBy, "," );

        while( tokenizer.hasMoreTokens() )
        {
            final String alias = ((String)tokenizer.nextToken()).trim();
            Certificate certificate = null;

            try { certificate = keyStore.getCertificate( alias ); }
            catch( final KeyStoreException kse )
            {
                throw new ConfigurationException( "Error aquiring certificate " + 
alias,
                                                  kse );
            }

            if( null == certificate )
            {
                throw new ConfigurationException( "Unable to locate alias " + alias +
                                                  " in keystore named " + keyStoreName 
);
            }

            if( !certificateSet.contains( certificate ) )
            {
                if( DEBUG ) getLogger().debug( "Certificate " + certificate );
                certificateSet.add( certificate );
            }
        }

        return (Certificate[])certificateSet.toArray( new Certificate[ 0 ] );
    }
}

Cheers,

Pete

*-----------------------------------------------------*
| "Faced with the choice between changing one's mind, |
| and proving that there is no need to do so - almost |
| everyone gets busy on the proof."                   |
|              - John Kenneth Galbraith               |
*-----------------------------------------------------*

---------------------------------------------------------------------
To unsubscribe, e-mail: [EMAIL PROTECTED]
For additional commands, email: [EMAIL PROTECTED]

Reply via email to