/************************************************************************** * Copyright (c) 2001 by Punch Telematix. All rights reserved. * * * * Redistribution and use in source and binary forms, with or without * * modification, are permitted provided that the following conditions * * are met: * * 1. Redistributions of source code must retain the above copyright * * notice, this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright * * notice, this list of conditions and the following disclaimer in the * * documentation and/or other materials provided with the distribution. * * 3. Neither the name of Punch Telematix nor the names of * * other contributors may be used to endorse or promote products * * derived from this software without specific prior written permission.* * * * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED * * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF * * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * * IN NO EVENT SHALL PUNCH TELEMATIX OR OTHER CONTRIBUTORS BE LIABLE * * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR * * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE * * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN * * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * **************************************************************************/ package wonka.security; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.StreamTokenizer; import java.net.URL; import java.security.CodeSource; import java.security.GeneralSecurityException; import java.security.KeyStore; import java.security.KeyStoreException; import java.security.Permission; import java.security.PermissionCollection; import java.security.PrivilegedAction; import java.security.Security; import java.security.cert.Certificate; import java.util.HashMap; import java.util.StringTokenizer; final class PolicyReader implements PrivilegedAction { private static final String TRUE = "true"; private static final String URLSTRING = "policy.url."; private static final String PK = ";"; private static final String K = ","; private static final String DACO = "${"; private static final String ACO = "{"; private static final char ACC = '}'; private HashMap collections; private KeyStore store; private StreamTokenizer st; private boolean expand; private boolean validEntry = true; private String fileSep; private ClassLoader cl; public Object run(){ try { fileSep = System.getProperty("file.Separator","/"); cl = ClassLoader.getSystemClassLoader(); collections = new HashMap(); expand = TRUE.equals(Security.getProperty("policy.expandProperties")); int nr = 1; String url = Security.getProperty(URLSTRING+nr); if(TRUE.equals(Security.getProperty("policy.allowSystemProperty"))){ String userURL = System.getProperty("java.security.policy"); if(userURL != null){ if(userURL.startsWith("=")){ url = null; } loadPolicyFile(userURL); } } while (url != null){ if(loadPolicyFile(url)){ break; } nr++; url = Security.getProperty(URLSTRING+nr); } //IF no default CodeSource is there we make one with no Permissions ... if(collections.get(DefaultPolicy.DEFAULT_CS) == null){ collections.put(DefaultPolicy.DEFAULT_CS,new PolicyPermissionCollection()); } return collections; } catch(Exception e){ //System.out.println("PolicyReader detected '"+e+"' -- Default Policy will not be updated"); e.printStackTrace(); return null; } } private boolean hasMoreEntryTokens() throws Exception { int type = st.nextToken(); if(type == ','){ return true; } if(type == ';'){ return false; } throw new GeneralSecurityException("bad entry syntax encountered "+st); } private String expandString(String name, boolean inCodeBase){ //TODO ... replace '\' by '/' in certain occasions int index = name.indexOf(DACO); if(index == -1){ return name; } StringBuffer buf = new StringBuffer(); int end = 0; do { buf.append(name.substring(end,index)); index += 2; end = name.indexOf(ACC, index); if(end == -1){ buf.append(name.substring(index-2)); return buf.toString(); } String key = name.substring(index,end); if(key == "/"){ buf.append(fileSep); } else { String prop = System.getProperty(key); if(prop == null){ validEntry = false; return name; } if(inCodeBase){ prop = prop.replace(fileSep.charAt(0),'/'); } buf.append(prop); } end++; index = name.indexOf(DACO, end); } while (index != -1); buf.append(name.substring(end)); return buf.toString(); } private Certificate[] getCertificates(String aliases) throws GeneralSecurityException, KeyStoreException { if(expand){ aliases = expandString(aliases, false); } StringTokenizer str = new StringTokenizer(aliases, K); int count = str.countTokens(); Certificate[] certs = new Certificate[count]; for (int i = 0; i < count ; i++){ //TODO check behaviour... what if the store doesn't contain the alias. Do we put in a 'null' certificate ... // or should we throw a GeneralSecurityException ... certs[i] = store.getCertificate(str.nextToken()); } return certs; } /** ** while using getToken we don't always check if we get a non null value back and ** by donig so trigger NullPointerExceptions. This is done on purpose because we expect a valid ** token and if we don't find one the file is corrupt and not trustworthy. */ private String getToken()throws IOException { int type = st.nextToken(); if(type == StreamTokenizer.TT_WORD || type == '"'){ return st.sval; } if(type == StreamTokenizer.TT_EOF){ return null; } return String.valueOf((char)type); } /** ** returns true if the url cannot be connected ... */ private boolean loadPolicyFile(String url) throws Exception { if(expand){ url = expandString(url, true); } URL loc = new URL(url); InputStream in = null; try { in = loc.openStream(); } catch(IOException ioe){ return true; } st = new StreamTokenizer(new InputStreamReader(in)); st.slashSlashComments(true); //WE ARE SETUP NOW ... String token = getToken(); //At this point token indicates the start of an entry (or null) ... while(token != null){ if(token.equalsIgnoreCase("grant")){ //System.out.println("PARSING 'grant' BLOCK"); parseGrantBlock(loc); } else if(token.equalsIgnoreCase("keystore")){ parseKeyStoreBlock(loc); } else { throw new GeneralSecurityException("unknown start of policy file entry '"+token+"'"); } token = getToken(); } return false; } private void parseGrantBlock(URL baseURL)throws Exception { URL url = null; Certificate[] signers = null; do { String token = getToken(); //System.out.println("checking token '"+token+"' in 'grant' BLOCK"); if(token.equals(ACO)){ break; } if(token.equalsIgnoreCase("signedBy")){ if(signers != null){ throw new GeneralSecurityException("INVALID GRANT ENTRY: more then one signedBy token found"); } signers = getCertificates(getToken()); } else if (token.equalsIgnoreCase("codeBase")){ if(url != null){ throw new GeneralSecurityException("INVALID GRANT ENTRY: more then one codeBase token found"); } String location = getToken(); if(expand){ location = expandString(location, true); } url = new URL(baseURL, location); } else { throw new GeneralSecurityException("unknown token '"+token+"' in 'grant' entry header"); } } while(true); //System.out.println("PARSING 'grant' BLOCK: looking for permission entries"); //At this point we've parsed the opening line of the grant entry. It contains the information //to construct a CodeSource. The CodeSource might be encountered in previous parsed grant entries ... PermissionCollection pc; if(validEntry){ CodeSource cs = new CodeSource(url, signers); pc = (PermissionCollection)collections.get(cs); if(pc == null){ pc = new PolicyPermissionCollection(); if(url != null){ //read permission for this URL ... pc.add(url.openConnection().getPermission()); } collections.put(cs, pc); } } else { pc = new PolicyPermissionCollection(); } //now we start reading the permission entries until we get '};' do { String token = getToken(); //System.out.println("checking token '"+token+"' in 'grant' BODY BLOCK"); if(token.equalsIgnoreCase("permission")){ parsePermissionBlock(pc); continue; } if(token.equals("}") && st.nextToken() == ';'){ break; } throw new GeneralSecurityException("unknown token '"+token+"' in 'grant' entry body"); } while(true); } private void parseKeyStoreBlock(URL baseURL) throws Exception { //a keystore entry exist of two parts: URL and type String url = getToken(); if(st.nextToken() != ','){ throw new GeneralSecurityException("unknown token '"+((char)st.ttype)+"' in 'keystore' entry (,)"); } String type = getToken(); if(st.nextToken() != ';'){ throw new GeneralSecurityException("unknown token '"+((char)st.ttype)+"' in 'keystore' entry (;)"); } //if we already have a keystore we ignore this entry ... if(store == null){ if(expand){ type = expandString(type, false); url = expandString(url, true); } if(validEntry){ store = KeyStore.getInstance(type); store.load(new URL(baseURL, url).openStream(), null); } else { validEntry = true; } } } private void parsePermissionBlock(PermissionCollection pc) throws Exception { //TODO ... make sure we use the correct ClassLoader !!! String classname = getToken(); String target = getToken(); Class permission = Class.forName(classname,true, cl); if(target.equals(PK)){ Permission perm = (Permission) permission.getDeclaredConstructor(new Class[0]).newInstance(new Object[0]); pc.add(perm); return; } String[] args; if(hasMoreEntryTokens()){ String action = getToken(); if(hasMoreEntryTokens()){ verifySigners(permission); } if(expand){ action = expandString(action, false); } args = new String[2]; args[1] = action; } else { args = new String[1]; } if(expand){ target = expandString(target, false); } if(validEntry){ args[0] = target; Class[] params = new Class[args.length]; params[0] = target.getClass(); if(params.length == 2){ params[1] = params[0]; } //System.out.println("CONSTRUCTING "+permission+" with target '"+args[0]+"' and action "+(args.length == 2 ? args[1]:"no action")); Permission perm = (Permission) permission.getDeclaredConstructor(params).newInstance(args); pc.add(perm); } else { validEntry = true; } } private void verifySigners(Class cl) throws Exception { String sign = getToken(); String aliases = getToken(); if(!sign.equalsIgnoreCase("signedBy") || st.nextToken() != ';'){ throw new GeneralSecurityException("INVALID PERMISSION ENTRY: signedBy = '"+sign+"' and aliases = '"+aliases+"'"); } //if this fails we don't trust the line ... (a property might be missing then it means we must disregard this line) Certificate[] certs = getCertificates(aliases); //this is tricky business we should ignore all signers if it system permission... ClassLoader loader = cl.getClassLoader(); //TODO make sure this is true for all System classes. if(loader != null){// && loader != ClassLoader.getSystemClassLoader()){ Object[] signers = cl.getSigners(); if(signers != null){ for (int i = 0 ; i < signers.length ; i++){ for(int j = 0 ; j < certs.length ; j++){ //TODO verify this is safe way to make sure the class file was signed by one aliases mentioned if(signers[i].equals(certs[j])){ return; } } } } validEntry = false; } } }