001: /*
002:
003: Derby - Class org.apache.derbyTesting.functionTests.util.SecurityCheck
004:
005: Licensed to the Apache Software Foundation (ASF) under one or more
006: contributor license agreements. See the NOTICE file distributed with
007: this work for additional information regarding copyright ownership.
008: The ASF licenses this file to You under the Apache License, Version 2.0
009: (the "License"); you may not use this file except in compliance with
010: the License. You may obtain a copy of the License at
011:
012: http://www.apache.org/licenses/LICENSE-2.0
013:
014: Unless required by applicable law or agreed to in writing, software
015: distributed under the License is distributed on an "AS IS" BASIS,
016: WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
017: See the License for the specific language governing permissions and
018: limitations under the License.
019:
020: */
021:
022: package org.apache.derbyTesting.functionTests.util;
023:
024: import java.lang.reflect.Constructor;
025: import java.lang.reflect.Field;
026: import java.lang.reflect.Method;
027: import java.lang.reflect.Modifier;
028: import java.util.Iterator;
029: import java.util.SortedMap;
030: import java.util.TreeMap;
031:
032: /**
033: * Code to aid in checking the Security of Derby.
034: * This initial implementation only handles the emebdded code.
035: * Future work could expand to the client driver and network server.
036: */
037: public class SecurityCheck {
038:
039: /**
040: * List of classes in the public api for the embedded engine.
041: */
042: private static final String[] EMBEDDED_PUBLIC_API = {
043: "org.apache.derby.jdbc.EmbeddedDriver",
044: "org.apache.derby.jdbc.EmbeddedDataSource",
045: "org.apache.derby.jdbc.EmbeddedSimpleDataSource",
046: "org.apache.derby.jdbc.EmbeddedConnectionPoolDataSource",
047: "org.apache.derby.jdbc.EmbeddedXADataSource",
048: "org.apache.derby.authentication.UserAuthenticator", };
049:
050: /**
051: * Is the passed in class part of the declared public api.
052: * Currently only the emebdded public api
053: * @param c class to be checked
054: * @return true if the class is part of the public api, false otherwise.
055: */
056: private static boolean isPublicApi(Class c) {
057: for (int i = 0; i < EMBEDDED_PUBLIC_API.length; i++) {
058: if (EMBEDDED_PUBLIC_API[i].equals(c.getName()))
059: return true;
060: }
061: return false;
062: }
063:
064: /**
065: * Map of all classes that have been inspected.
066: * The key is the class name, if the value is null then
067: * the class is being inspected. Otherwise the value is
068: * a String description of the risks.
069: */
070: private static final SortedMap allInspectedClasses = new TreeMap();
071:
072: /**
073: * Perform security analysis of the public api for the embedded engine.
074: * Prints a report to System.out on completion.
075: * @throws ClassNotFoundException
076: */
077: public static void checkEmbeddedPublicApi()
078: throws ClassNotFoundException {
079: System.out
080: .println("SecurityCheck: embedded public api classes");
081: allInspectedClasses.clear();
082: for (int i = 0; i < EMBEDDED_PUBLIC_API.length; i++)
083: SecurityCheck.inspectClass(EMBEDDED_PUBLIC_API[i]);
084:
085: SecurityCheck.report(true);
086: }
087:
088: /**
089: * Produce a report on System.out of all inspected classes
090: * that have risks associated with them.
091: *
092: */
093: public static void report() {
094: SecurityCheck.report(false);
095: }
096:
097: /**
098: * Produce a report on System.out of all inspected classes
099: * that have risks associated with them. If reportClear is
100: * true then additionally all classes that have been inspected
101: * will be returned.
102: *
103: */
104: private static void report(boolean reportClear) {
105: synchronized (allInspectedClasses) {
106: for (Iterator it = allInspectedClasses.keySet().iterator(); it
107: .hasNext();) {
108: Object key = it.next();
109: Object value = allInspectedClasses.get(key);
110: if (value == null) {
111: if (reportClear)
112: System.out.println("CLEAR: " + key);
113: } else {
114: System.out.print(value);
115: }
116: }
117: }
118: }
119:
120: /**
121: * Inspect a class for security risks. No output is generated
122: * by this call, the caller must call report() to obtain the risks.
123: * @param className
124: * @throws ClassNotFoundException
125: */
126: public static void inspectClass(String className)
127: throws ClassNotFoundException {
128: SecurityCheck.inspectClass(Class.forName(className), null);
129: }
130:
131: /**
132: * Inspect the class of the passed in Object for security risks.
133: * This inspects, at this level only, the actual type of
134: * the object, not the declared type. E.g. for DriverManager.getConnection
135: * the declared type is java.sql.Connection which has no security risks,
136: * but the implementation type returned may have many.
137: *
138: * <code>
139: * Connection conn = DriverManager.getConnection(url);
140: * // will inspect the implementation call, eg. EmbedConnection30
141: * SecurityManager.inspect(conn);
142: * </code>
143: * No output is generated by this call,
144: * the caller must call report() to obtain the risks.
145: * @param o Obejct to be inspected
146: * @param declared the declared type of the object.
147: */
148: public static void inspect(Object o, String declared) {
149: if (o == null)
150: return;
151:
152: SecurityCheck.inspectClass(o.getClass(), declared);
153: }
154:
155: /**
156: * Inspect a Derby class for security risks. This includes following potential
157: * references exposed through the class.
158: * <P>
159: * Risks looked at:
160: * <UL>
161: * <LI> public constructors in non-public class -
162: * No justification for the constructor to be public.
163: * <LI> public constructors in non-final class and non-sealed package -
164: * Allows the class to be sub-classes through a injected class in
165: * the same package.
166: * <LI> public non-final field - Allows any one with a handle to
167: * the object to change the field.
168: * </UL>
169: * <P>
170: * The type of any public field or return type of any public method
171: * is also inspected. The assumption is that if such a field or method
172: * exists they have the potential to be called and return a valid object.
173: * <P>
174: * Note that this inspection is through the declared type of exposed
175: * references, not the actual runtime type. The actual runtime type
176: * might expose other classes that should be inspected.
177: * @param className
178: * @throws ClassNotFoundException
179: */
180: private static boolean inspectClass(Class c, String declared) {
181: if (!c.getName().startsWith("org.apache.derby."))
182: return false;
183:
184: // Initial focus on embedded engine
185: if (c.getName().startsWith("org.apache.derby.client."))
186: return false;
187:
188: synchronized (allInspectedClasses) {
189: if (allInspectedClasses.containsKey(c.getName()))
190: return true;
191:
192: allInspectedClasses.put(c.getName(), null);
193:
194: StringBuffer sb = new StringBuffer();
195:
196: sb.append("Class ");
197: sb.append(c.getName());
198: sb.append('\n');
199:
200: if (declared != null) {
201: allInspectedClasses
202: .put(declared, "Checked class declared as: "
203: + declared + "\n");
204:
205: }
206:
207: boolean isPublicApi = SecurityCheck.isPublicApi(c);
208:
209: boolean hasIssues = false;
210:
211: boolean isSealed = c.getPackage().isSealed();
212: boolean isFinal = Modifier.isFinal(c.getModifiers());
213: boolean isPublic = Modifier.isPublic(c.getModifiers());
214: boolean isAbstract = Modifier.isAbstract(c.getModifiers());
215:
216: Constructor[] constructors = c.getConstructors();
217:
218: boolean hasPublicConstructor = constructors.length != 0;
219:
220: if (hasPublicConstructor && !isPublic) {
221: hasIssues = true;
222:
223: // No reason for a public constructor in a non-public class
224: sb
225: .append("..public constructors in non-public class\n");
226:
227: // class can be sub-classed even though it is not public
228: if (!isFinal && !isSealed)
229: sb
230: .append("..public constructors in non-final class and non-sealed package\n");
231: }
232:
233: if (hasPublicConstructor && isPublic) {
234: // TODO: Need to work on these checks.
235: if (!isPublicApi) {
236: //hasIssues = true;
237:
238: // anyone can create instances of this class
239: //sb.append("..public constructors in public class\n");
240: }
241:
242: // and anyone can sub-class this class
243: if (!isFinal) {
244: //hasIssues = true;
245: //sb.append("..public constructors in public non-final class\n");
246: }
247: }
248:
249: for (int i = 0; i < constructors.length; i++) {
250: if (hasIssues) {
251: sb.append("..public constructor: ");
252: sb.append(constructors[i].toString());
253: sb.append('\n');
254: }
255: }
256:
257: Field[] fields = c.getFields();
258: for (int i = 0; i < fields.length; i++) {
259: Field f = fields[i];
260: boolean isStatic = Modifier.isStatic(f.getModifiers());
261:
262: Class fieldType = f.getType();
263: SecurityCheck.inspectClass(fieldType, null);
264:
265: if (Modifier.isFinal(f.getModifiers())) {
266: // TODO: Should this be a concern if non-static?
267: continue;
268: }
269:
270: hasIssues = true;
271: sb.append("..public non-final field: ");
272: sb.append(f.toString());
273: sb.append('\n');
274: }
275:
276: Method[] methods = c.getMethods();
277: for (int i = 0; i < methods.length; i++) {
278: Method m = methods[i];
279:
280: Class methodType = m.getReturnType();
281: if (SecurityCheck.inspectClass(methodType, null)) {
282: // method returns a class of interest to us.
283:
284: // just a method returning a public api
285: if (SecurityCheck.isPublicApi(methodType))
286: continue;
287:
288: /*
289: * Not sure this is a vaild risk.
290: hasIssues = true;
291: sb.append("..public method returning non-public api class: ");
292: sb.append(m.toString());
293: sb.append("\n");
294: */
295: }
296:
297: }
298: if (hasIssues)
299: allInspectedClasses.put(c.getName(), sb.toString());
300: }
301:
302: return true;
303:
304: }
305: }
|