/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.sun.jini.test.impl.end2end.e2etest;
/* JAAS imports */
import javax.security.auth.Subject;
/* Java imports */
import java.io.EOFException;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.rmi.ConnectException;
import java.rmi.ConnectIOException;
import java.rmi.MarshalledObject;
import java.rmi.MarshalException;
import java.rmi.NoSuchObjectException;
import java.rmi.NotBoundException;
import java.rmi.RemoteException;
import java.rmi.UnmarshalException;
import net.jini.io.UnsupportedConstraintException;
import net.jini.jeri.ssl.ConfidentialityStrength;
import net.jini.core.constraint.ClientAuthentication;
import net.jini.core.constraint.ClientMinPrincipal;
import net.jini.core.constraint.Confidentiality;
import net.jini.core.constraint.ConstraintAlternatives;
import net.jini.core.constraint.Delegation;
import net.jini.core.constraint.Integrity;
import net.jini.core.constraint.MethodConstraints;
import net.jini.core.constraint.RemoteMethodControl;
import net.jini.core.constraint.InvocationConstraint;
import net.jini.core.constraint.InvocationConstraints;
import net.jini.core.constraint.ServerAuthentication;
import net.jini.constraint.BasicMethodConstraints;
import net.jini.security.Security;
import net.jini.jeri.InboundRequest;
import net.jini.jeri.OutboundRequest;
import net.jini.jeri.OutboundRequestIterator;
import net.jini.jeri.connection.OutboundRequestHandle;
import net.jini.jeri.connection.Connection;
import net.jini.jeri.connection.ConnectionEndpoint;
import net.jini.jeri.connection.ServerConnection;
import java.security.AccessControlException;
import java.security.NoSuchProviderException;
import java.security.NoSuchAlgorithmException;
import java.security.Principal;
import java.security.PrivilegedAction;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import java.security.Provider;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Random;
import java.util.Set;
/* JSSE imports */
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSocket;
/* Wrapper imports */
import com.sun.jini.test.impl.end2end.jssewrapper.Bridge;
import com.sun.jini.test.impl.end2end.jssewrapper.ReadCallback;
import com.sun.jini.test.impl.end2end.jssewrapper.WriteCallback;
import com.sun.jini.test.impl.end2end.jssewrapper.EndpointWrapper;
/*
* A client that uses a proxy exported by <code>SecureServer</code>
* to execute the methods defined by <code>ConstraintsInterface</code>
* and <code>CoreInterface</code>, and verifies the approriate security
* handling of these method calls.
*/
class SecureClient implements Constants, TestClient, Runnable {
/** the counter for generating SecureClient map keys */
private static int instanceCounter = 0;
/** the map for locating SecureClient instances */
private static Map instanceMap = new HashMap();
/**
* counter for number of live threads.
*/
private static int liveThreadCount = 0;
/** lock to synchronize mods to liveThreadCount */
private static Object liveThreadLock = new Object();
/**
* the seed for the random number generator used when the coverage
* options is active. It is static so that every client initializes
* its random number generator with the same value, necessary if
* the test is to be reproducable
*/
private static long seed = 0;
/** the test coordinator */
private TestCoordinator coordinator;
/** the test display object */
private UserInterface ui;
/** synchronization object for GUI start function */
private Object goMonitor = new Object();
/** if true, abort test on first failure */
private boolean abortOnFail =
System.getProperty("end2end.abortOnFail") != null;
/**
* the coverage factor (e.g 50 => perform one test in 50 * 50,
* approximately)
*/
private int coverage;
/** the currently executing method. */
private TestMethod currentMethod;
/** the total number of tests to be run (plus unexport and call after) */
private int totalTests;
/**
* the constraints the client has imposed on the proxy. This reference
* should be valid at all times (it is set at top of loop).
*/
private InvocationConstraints clientConstraints;
/**
* the combined constraints. This reference is only
* valid after the servers readCallData method has been invoked.
*/
private InvocationConstraints combinedConstraints;
/**
* the authenticated client subject constraints. This reference is only
* valid after the servers readCallData method has been invoked.
*/
private Subject clientSubject;
/**
* the currently active cipher suite. This reference is only
* valid after the writeCallback method has been invoked.
*/
private CipherSuite cipherSuite;
/** the instance key of this instance */
private Integer myKey;
/** the first picked proxy obtained from SecureServer */
private MarshalledObject pickledStub;
/** the proxy obtained by unmarshalling pickled server stubs */
private SmartInterface origIface;
/** the proxy obtained by calls to origIface.newProxy() */
private SmartInterface unconstrainedIface;
/** the proxy used for making remote calls after applying constraints */
private SmartInterface iface;
/** the random number generator for selecting tests when coverage > 1 */
private Random random;
private static SubjectProvider subjectProvider =
ProviderManager.getSubjectProvider();
/**
* The set of constraints to be applied by the client. Note that
* if any constraints are added to this list, the call validation
* methods may need to be modified. The <code>sminp</code> is a
* ConstraintAlternatives containing ServerMinPrincipals constraints
* for all of the server principals. ServerMinPrincipal cannot be
* used as a constraint directly because the JSSE provider has a
* limitation that the client cannot select which subjects the
* server authenticates as.
*/
private static InvocationConstraint[] constraintsArray = null;
public static void initialize() {
if (ProviderManager.isKerberosProvider()) {
constraintsArray = new InvocationConstraint[] {
ServerAuthentication.YES,
ClientAuthentication.YES,
Confidentiality.YES,
Confidentiality.NO,
Integrity.YES,
Integrity.YES,
Delegation.YES,
Delegation.NO,
subjectProvider.getServerMainPrincipal()
};
} else {
constraintsArray = new InvocationConstraint[] {
ServerAuthentication.YES,
ClientAuthentication.NO,
Confidentiality.YES,
Confidentiality.NO,
Integrity.YES,
Integrity.NO,
Delegation.YES,
Delegation.NO,
subjectProvider.getServerMinPrincipal()
};
}
}
/** position in array of <code>ServerAuthentication.YES</code> */
private static final int SERVERAUTH = 1 << 0;
/** position in array of <code>ClientAuthentication.NO</code> */
private static final int CLIENTAUTH = 1 << 1;
/** position in array of <code>Conficentiality.YES</code> */
private static final int CONFYES = 1 << 2;
/** position in array of <code>Conficentiality.YES</code> */
private static final int CONFNO = 1 << 3;
/** position in array of <code>Integrity.YES</code> */
private static final int INTEGYES = 1 << 4;
/** position in array of <code>Integrity.YES</code> */
private static final int INTEGNO = 1 << 5;
/** position in array of <code>Delegation.YES</code> */
private static final int DELEGYES = 1 << 6;
/** position in array of <code>Delegation.YES</code> */
private static final int DELEGNO = 1 << 7;
/** position in array of <code>ServerMinPrincipal(serverDSA)</code> */
private static final int SMINP = 1 << 8;
/** bit mask representing conflicting Confidentiality constraints */
private final int BADCONF = CONFYES | CONFNO;
/** bit mask representing conflicting Integrity constraints */
private final int BADINTEG = INTEGYES | INTEGNO;
/** bit mask representing conflicting Delegation constraints */
private final int BADDELEG = DELEGYES | DELEGNO;
private InvocationConstraint[] preferencesArray = {
ConfidentialityStrength.STRONG,
ConfidentialityStrength.WEAK,
subjectProvider.getClientMaxPrincipal()
};
/** bit mask representing ConfidentialityStrength.STRONG */
private final int STRONG = 1 << 0 ;
/** bit mask representing ConfidentialityStrength.WEAK */
private final int WEAK = 1 << 1;
/** bit mask representing conflicting strength constraints */
private final int BADSTRENGTH = STRONG | WEAK;
/** the number of tests executed */
private int testCount;
/** the index of the test being executed */
private int testNumber;
/** the thread number */
private int threadNumber;
/** the failure count */
private int failureCount = 0;
/** the array of methods defined in <code>ConstraintsInterface</code> */
private final TestMethod[] methods =
TestMethod.getDeclaredMethods(ConstraintsInterface.class);
/** the test logger for this instance */
private Logger logger;
/**
* the start of the range of tests to execute - causes expensive setup
* to be skipped if individual tests are to be run
*/
private int startCount = 0;
/**
* the end of the range of tests to execute - causes the run to
* terminate after the last test when individual tests are run
*/
private int endCount = -1;
/**
* Get the TestClient instance associated with the given key.
* This method is called from the server to obtain the client
* instance which performed a remote call. This makes it possible,
* among other things, for the server to append to the log instance
* owned by the calling client. The serialized key is provided
* to the server as part of the wrapper/bridge callback implementation.
*
* @param key the key associated with the TestClient instance.
* @return the associated TestClient instance
*/
static TestClient getInstance(Integer key) {
synchronized (instanceMap) {
return (TestClient) instanceMap.get(key);
}
}
/**
* Creates a <code>SecureClient</code>.
*
* @param coordinator the test coordinator
* @param pickledStub the serialized proxy exported by the server
*/
SecureClient(TestCoordinator coordinator, MarshalledObject pickledStub) {
this.coordinator = coordinator;
this.pickledStub = pickledStub;
totalTests = computeTotalTestCount();
logger = new Logger();
synchronized (instanceMap) {
myKey = new Integer(instanceCounter++);
instanceMap.put(myKey,this);
}
Set testSet = coordinator.getTests();
Iterator it = testSet.iterator();
while (it.hasNext()) {
String testName = (String) it.next();
try {
int testNum = Integer.parseInt(testName);
if (startCount == 0) {
startCount = testNum;
endCount = testNum;
} else {
startCount = Math.min(startCount, testNum);
endCount = Math.max(endCount, testNum);
}
} catch (NumberFormatException e) {
}
}
coverage = Integer.getInteger("end2end.coverage", 1).intValue();
if (coverage > 1) {
synchronized (this.getClass()) {
if (seed == 0) {
seed = Long.getLong("end2end.seed", 0).longValue();
if (seed == 0) {
seed = new Date().getTime();
}
logger.log(ALWAYS, "Partial coverage factor: " + coverage);
logger.log(ALWAYS, "Random number seed: " + seed);
logger.writeLog();
}
}
random = new Random(seed);
}
}
/**
* Method called to set the client subject to verify when the
* server is executing a remote call.
*/
public void setTestClientSubject(Subject subject) {
clientSubject = subject;
}
/**
* deserialize an object. Any exceptions normally generated by
* the MarshalledObject.get method are caught and logged.
*
* @params mobj the serialized object
* @return the deserialized object, or null if the object could not be
* deserialized.
*/
private Object unpickle(MarshalledObject mobj) {
Object obj = null;
try {
obj = mobj.get();
} catch (IOException e) {
logger.log(ALWAYS, "I/O error unmarshalling stub");
logger.log(ALWAYS, e);
} catch (ClassNotFoundException e) {
logger.log(ALWAYS, "class not found while unmarshalling stub");
logger.log(ALWAYS, e);
}
return obj;
}
/**
* Start the client side of the test, running in the context of
* the client subject. An InstancePasser is installed
* in the bridge for this thread, allowing access to the logger
* by objects which don't have direct references to this SecureClient
* object. If a different object is temporarily installed in the bridge,
* it is the responsibility of the installer to restore the InstancePasser.
*
* This method increments a thread counter. This is needed at the
* end of the test; the last thread to finish has the responsibility
* of unexporting the service.
*/
public void run() {
synchronized (liveThreadLock) {
liveThreadCount++;
threadNumber = liveThreadCount;
}
InstancePasser ip = new InstancePasser(this);
Bridge.writeCallbackLocal.set(ip);
try {
Subject.doAsPrivileged( subjectProvider.getClientSubject(),
new PrivilegedExceptionAction() {
public Object run() throws RemoteException,
NotBoundException {
logger.log(CLIENTSUBJECT, "Running SecureClient "
+ "with subject " + subjectProvider.getSubject()) ;
origIface = (SmartInterface) unpickle(pickledStub);
if (origIface != null) {
runTest();
}
logger.writeLog(); // in case of lingering output
return null;
}
}, null);
} catch (PrivilegedActionException e) {
logger.log(ALWAYS, "Unexpected exception running client") ;
logger.log(ALWAYS, e) ;
logger.writeLog();
return;
}
}
/**
* Run the body of the test. For each possible value of the marshal
* control value, request a new proxy from the service, verify
* that the method constraints imposed by the server are retained
* properly in the proxy, perform all method calls using all legal
* combinations of client constraints. If this is the last thead
* alive, unexport the original proxy.
*/
void runTest() {
testCount = 0;
testNumber = 0;
/*
* get a new proxy.
*/
unconstrainedIface = origIface.newProxy(coordinator);
if (unconstrainedIface == null) {
logger.writeLog(); // just in case the buffer is not empty
throw new TestException("new proxy obtained "
+ "from server is null", null);
}
checkServerConstraints(unconstrainedIface);
boolean lastThread = false;
try {
/* perform all remote calls for this proxy */
doAllConstraints(unconstrainedIface);
synchronized (liveThreadLock) {
if (--liveThreadCount == 0) lastThread = true;
}
if (lastThread) {
/* if this is the last thread alive, unexport
the original proxy */
cleanup(origIface, true);
}
} catch (Exception e) {
e.printStackTrace();
}
/* write per client statistics, and per test statistics if last */
Date dateStamp = new Date();
System.out.println("************************************" +
"************************************");
System.out.println(dateStamp + " Summary for thread "
+ threadNumber + " ");
System.out.println(dateStamp + " Total tests run for thread "
+ threadNumber + ": " + testCount);
System.out.println(dateStamp + " Number of failures: " + failureCount);
System.out.println("************************************" +
"************************************");
if (lastThread) {
System.out.println("\nTesting Complete");
}
logger.endBoundary();
logger.writeLog();
ui.setCallStatus("Client test complete " + new Date());
}
/**
* Clean the client after a proxy is no longer needed. Performs
* remote calls to the server to request an unexport, and verifies
* that the unexported proxy can no longer perform remote calls.<p>
* The behavior of the unexport remote call depends on whether it
* is the last unexport to be performed. Non 'final' exports
* complete successfully, but the 'final' export to be done
* results in a RemoteException being thrown.
*
* @param proxy the proxy for the Remote object
* @para finalUnexport if true, this should be the last unexport
*/
private void cleanup(SmartInterface proxy, boolean finalUnexport) {
/* call unexport, and call again to see if it happened */
testNumber++;
testCount++;
writeBoundaryHeader();
try {
currentMethod = new TestMethod(
CoreInterface.class.getMethod("unexport",new Class[]{}));
} catch (NoSuchMethodException e) { // should never happen
logFailure("method unexport() "
+ "not found in CoreInterface");
}
CallHandler handler = new UnexportCallHandler(proxy, finalUnexport);
handler.handleCall(currentMethod);
logger.endBoundary();
logger.writeLog();
checkForPause();
testNumber++;
testCount++;
writeBoundaryHeader();
try {
currentMethod = new TestMethod(CoreInterface.class.getMethod(
"callAfterUnexport", new Class[]{}));
} catch (NoSuchMethodException e) { // should never happen
logFailure("method unexport() not found in CoreInterface");
}
handler = new CallAfterUnexportCallHandler(proxy, finalUnexport);
handler.handleCall(currentMethod);
logger.endBoundary();
logger.writeLog();
checkForPause();
}
/**
* Ask the <code>UserInterface</code> if a pause is requested and
* suspend this thread if so.
*/
private void checkForPause() {
if (ui.stopAfterCall()) {
synchronized (goMonitor) {
try {
goMonitor.wait();
} catch (InterruptedException e) {
}
}
}
}
/**
* The <code>CallHandler</code> for performing the
* remote <code>unexport</code> call
*/
private class UnexportCallHandler extends CallHandler {
private boolean finalUnexport;
private SmartInterface proxy;
/**
* Construct the <code>CallHandler</code>
*
* @param proxy the remote proxy
* @param finalExport if true, this is the last export call
*/
UnexportCallHandler(SmartInterface proxy, boolean finalExport) {
super(SecureClient.this, ui, coordinator);
this.finalUnexport = finalExport;
this.proxy = proxy;
}
/* inherit javadoc */
protected void doCall(TestMethod method) throws Exception {
proxy.unexport();
}
/**
* Validate the success of the call. The
* call should succeed if this is not the final
* unexport. The last unexport should throw an EOFException.
*
* @param method the unexport <code>Method</code> (unused)
*/
protected void validateSuccess(TestMethod method) {
if (finalUnexport) {
logFailure("Final call to unexport succeeded");
}
}
/**
* Validate the failure of the remote call.
*
* @param method the unexport <code>Method</code> (unused)
* @param e the exception thrown by the <code>unexport</code> call
*/
protected void validateException(TestMethod method, Exception e) {
}
}
/**
* The <code>CallHandler</code> for the <code>callAfterUnexport</code>
* method call.
*/
private class CallAfterUnexportCallHandler extends CallHandler {
private boolean finalUnexport;
private SmartInterface proxy;
/**
* Construct the <code>CallHandler</code>
*
* @param proxy the remote proxy
* @param finalUnexport if true, this was the last unexport performed
*/
CallAfterUnexportCallHandler(SmartInterface proxy,
boolean finalUnexport) {
super(SecureClient.this, ui, coordinator);
this.finalUnexport = finalUnexport;
this.proxy = proxy;
}
/* inherit javadoc
* If this is the final unexport case, then it is possible that
* cached connections will cause one or more calls to throw
* an UnmarshalException with a nested IOException. It is
* also possible that a ConnectIOException with a nested
* IOException is thrown. Therefore, in
* this case, a loop is performed to make the call a number
* of times, until the call is successful (should never happen),
* or some other exception pattern occurs. Note that I don't
* check whether the nested exception is an IOException.
*/
protected void doCall(TestMethod method) throws Exception {
final int LOOPCOUNT = 5;
RemoteException caughtEx = null;
if (finalUnexport) {
for (int i = 0; i < LOOPCOUNT; i++) {
try {
proxy.callAfterUnexport();
return; // should never happen
}
catch (Exception e) {
if (!(e instanceof UnmarshalException)||
!(e instanceof ConnectIOException)||
!(e instanceof ConnectException)) {
throw e;
}
caughtEx = (RemoteException) e;
}
}
throw caughtEx;
} else {
proxy.callAfterUnexport();
}
}
/**
* Validate the success of the call. This
* call should always throw an exception,
* so reaching this method is a test failure
*
* @param method the remote <code>Method</code> (unused)
*/
protected void validateSuccess(TestMethod method) {
String finalString = finalUnexport ? "After final unexport, "
: "After non-final unexport, ";
logFailure(finalString + "call to callAfterUnexport succeeded");
}
/**
* Validate the failure of the remote call. This
* call should have thrown a Remote exception
* so log failure if any other exception was thrown.
*
* @param method the remote <code>Method</code> (unused)
* @param e the exception thrown as a result of the remote call
*/
protected void validateException(TestMethod method, Exception e) {
String finalString = finalUnexport ? "After final unexport, "
: "After non-final unexport, ";
if (!(e instanceof RemoteException)) {
logFailure(finalString + "call to callAfterUnexport "
+ "threw non-Remote exception", e);
}
}
}
/**
* Call all methods in ConstraintsInterface, applying all combinations
* of client constraints and preferences. Client constraints are
* applied as a mixture of proxy constraints and contextual constraints.
*
* @param proxy the proxy of the exported Remote service
*/
private void doAllConstraints(SmartInterface proxy) {
logger.writeLog();
/* loop for all combinations of non-conflicting requirements */
for (int req = 0; req < (1 << constraintsArray.length); req++) {
if (((req & BADCONF) == BADCONF)||((req & BADINTEG) == BADINTEG)
|| ((req & BADDELEG) == BADDELEG)){
continue;
}
/* split constraints between proxy and context */
int flags = req;
ArrayList reqList = new ArrayList();
for (int i = 0; i < constraintsArray.length; i++) {
if ((flags & 1) != 0) {
reqList.add(constraintsArray[i]);
}
flags >>= 1;
}
/* loop for all combinations of non-conflicting preferences */
for (int pref = 0; pref < (1 << preferencesArray.length); pref++) {
logger.startBoundary("Proxy setup");
if ((pref & BADSTRENGTH) == BADSTRENGTH) {
continue;
}
if ((testNumber + methods.length) < startCount) {
testNumber += methods.length;
continue;
}
if (endCount >= 0 && testNumber > endCount) {
System.exit(1);
}
if (startCount == 0 && (coverage > 1 && random
.nextInt(coverage) != 0)) {
testNumber++;
continue;
}
/* split preferences between proxy and context */
flags = pref;
ArrayList prefList = new ArrayList();
for (int i = 0; i < preferencesArray.length; i++) {
if ((flags & 1) != 0) {
prefList.add(preferencesArray[i]);
}
flags >>= 1;
}
clientConstraints = new InvocationConstraints(reqList, prefList);
ui.setClientProxyConstraints(clientConstraints);
BasicMethodConstraints mc =
new BasicMethodConstraints(clientConstraints);
try {
currentMethod =
new TestMethod(Security.class.getMethod("verifyObjectTrust",
new Class[]{ Object.class,
ClassLoader.class, Collection.class}));
} catch (NoSuchMethodException e) { // should never happen
logFailure("method "
+ "verifyObjectTrust(Object, ClassLoader, "
+ "Collection) "
+ "not found in Security");
}
try {
logger.log(DEBUG,"calling verifyObjectTrust for " + proxy);
proxy = (SmartInterface) proxy.setConstraints(mc);
Security.verifyObjectTrust(proxy,
proxy.getClass().getClassLoader(),
Collections.singleton(mc));
iface = proxy;
if (!validVerifierCall(clientConstraints)) {
logFailure("verifyObjectTrust call succeeded "
+ "inappropriately");
}
} catch (RemoteException e) {
iface = null;
if (e instanceof ConnectIOException) {
if (validVerifierCall(clientConstraints)) {
logFailure("verifyObjectTrust call failed "
+ "inappropriately",e);
}
} else {
logFailure("verifyObjectTrust threw an "
+ "unexpected Remote Exception", e);
}
} catch (SecurityException e) {
if (validVerifierCall(clientConstraints)) {
logFailure("verifyObjectTrust threw an "
+ "unexpected SecurityException", e);
}
}
logger.endBoundary();
logger.writeLog();
/*
* verifyObjectTrust may fail differently depending on whether
* the proxy is an RMI stub or a smart proxy. This is
* because constraints are applied when validating trust
* for a non-RMI proxy, which requires contact with a
* trusted remote server. However, RMI stubs are verified
* by inspection.
*/
Object ic = Bridge.writeCallbackLocal.get();
Bridge.writeCallbackLocal.set(
new ClientSideAction(SecureClient.this));
callAllMethods();
Bridge.writeCallbackLocal.set(ic);
}
}
}
/**
* execute all methods defined in <code>ConstraintsInterface</code>
* unless restricted by command line input.
*/
private void callAllMethods() {
CallHandler callHandler = new ConstraintsCallHandler();
/*
* if non-empty, tests contains the set of methods to be
* called, or the integer test numbers to be executed,
* obtained from the command line by the <code>TestCoordinator</code>
*/
Set tests = coordinator.getTests();
for (int i=0; i<methods.length; i++) {
checkForPause();
testNumber++;
if (endCount >= 0 && testNumber > endCount) {
System.exit(1);
}
ui.setTestCount(testNumber, totalTests) ;
if (coverage > 1) {
if (random.nextInt(coverage) != 0) {
continue;
}
}
testCount++;
cipherSuite = null; // undefine from previous loop
combinedConstraints = null; // undefine from previous loop
clientSubject = null; // undefine from previous loop
ui.setTestSuite(null);
ui.setClientSubject(null);
ui.setCombinedConstraints(null);
if (iface == null) continue;
String intString = Integer.toString(testNumber) ;
if (tests.contains(methods[i].getName())
|| tests.contains(intString)
|| tests.isEmpty()) {
writeBoundaryHeader();
logger.log(PROXYCONSTRAINTS, "Client Proxy constraints: "
+ clientConstraints);
currentMethod = methods[i];
checkClientConstraints(currentMethod);
callHandler.handleCall(currentMethod);
logger.endBoundary();
logger.writeLog();
}
}
}
/**
* The <code>CallHandler</code> for making calls specified by
* the <code>ConstraintsInterface</code> interface.
*/
private class ConstraintsCallHandler extends CallHandler {
ConstraintsCallHandler() {
super(SecureClient.this, ui, coordinator);
}
/**
* Create method arguments and call the given method.
*
* @param testMethod the <code>Method</code> to call
*/
protected void doCall(TestMethod testMethod) throws Exception {
Class[] ptypes = testMethod.getParameterTypes();
Object[] args = new Object[ptypes.length];
for (int i=0; i<args.length; i++) {
if (ptypes[i] == PlainObject.class) {
args[i] = new PlainObject();
}
/*
* Note that the combined constraints passed to ReaderObject
* must be computed since the combinedConstraints class
* attribute is not valid at the time this method is called.
*/
if (ptypes[i] == ReaderObject.class) {
InstanceCarrier ic = coordinator
.getDefaultInstanceCarrier();
args[i] = new ReaderObject(getConstraints(testMethod), ic);
}
if (ptypes[i] == int.class) {
args[i] = new Integer(0);
}
}
iface.invoke(testMethod, args);
}
/**
* A method call failed. Determine whether failure was expected.
* If unexpected, log as a failure.
*
* @param method the <code>Method</code> called
* @param e the exception thrown by the method
*/
protected void validateException(TestMethod method, Exception e) {
Throwable t = e;
if (e instanceof InvocationTargetException ) {
t = ((InvocationTargetException) e).getTargetException();
}
if (!((t instanceof ConnectIOException)
|| (t instanceof SecurityException))) {
logFailure("Unexpected exception calling " + method.getName(),
t);
return;
}
if (clientSubject==null){
boolean ok = true;
Iterator it = method.parseConstraints().requirements()
.iterator();
while (it.hasNext()) {
InvocationConstraint sc = (InvocationConstraint)
it.next();
if (sc == ClientAuthentication.YES) {
ok = false;
}
}
if (ok) {
return;
}
}
if (ProviderManager.isKerberosProvider()) {
boolean ok = false;
Iterator it = method.parseConstraints().requirements()
.iterator();
while (it.hasNext()) {
InvocationConstraint sc = (InvocationConstraint)
it.next();
if (sc == ClientAuthentication.NO) {
ok = true;
}
}
if (ok) {
return;
}
}
if (validCall(method) == null) {
if (!conflictingConstraints(
InvocationConstraints.combine(
clientConstraints,
method.parseConstraints()
))){
logFailure("Method " + method.getName()
+ " failed inappropriately",t);
}
}
}
/**
* Method to determine if constraints conflict
*/
private boolean conflictingConstraints(InvocationConstraints ics) {
//Conflicting constraints were previously identified through
//reduceBy. Rather than anticipate future conflicting constraints
//this method assumes UnsupportedConstraintsException was accurately
//thrown.
return true;
}
/**
* A method call succeeded. Determine whether success was expected.
* If unexpected, log as a failure
*
* @param method the successfully called <code>Method</code>
*/
protected void validateSuccess(TestMethod method) {
String failureMessage = validCall(method);
if (failureMessage != null) {
logFailure("Method " + method.getName() + " "
+ "succeeded inappropriately\n" + failureMessage);
} else {
checkClientSubject(clientSubject);
}
}
/**
* Determine whether a method call is valid, i.e. should return
* without throwing an exception.
* <p>
* Note: it is assumed here that conflicting client
* constraints are never imposed. Also, conflicting server
* constraints cannot be imposed or the server export would fail.<p>
*
*
* If a method call is found to be invalid, the returned string
* contains a brief description of the validity check that failed.
*
* @returns a descriptive string for the following invalid cases:
* <ul>
* <li>if any server constraints conflict with any client constraints.
* <li>if any constraint is Integrity.NO, since the JSSE provider
* always imposes Integrity.YES implicitly.
* <li>if the method name is vBogus and ClientAuthentication.YES
* is asserted by the server, since this method should fail
* the access control check in the call controller when client
* authentication is asserted by the server.
* <li>if Confidentiality.NO and ServerAuthentication.NO are both
* imposed, since the JSSE provider does not provide a ciphersuite
* which can satisfy both of these constraints simultaneously.
* <li>if Delegation.YES and ClientAuthentication.YES are both
* imposed, since the JSSE provider does not support delegation.
* </ul>
* or returns null if the call is valid.
*/
private String validCall(TestMethod method) {
Set serverRequirements = method.parseConstraints().requirements();
Collection clientRequirements = // need mutable set
new ArrayList(clientConstraints.requirements());
Iterator itClient = clientRequirements.iterator();
boolean gotConfidentialityNo = false;
boolean gotServerAuthNo = false;
while (itClient.hasNext()) {
InvocationConstraint c1 = (InvocationConstraint) itClient.next();
if (c1 == Integrity.NO) {
return "Integrity.NO applied, "
+ "but is not supported by the JSSE provider";
}
if (c1 == Delegation.YES) {
if ((!ProviderManager.isKerberosProvider())
&& (clientRequirements.contains(ClientAuthentication.YES)
|| serverRequirements
.contains(ClientAuthentication.YES))) {
return "ClientAuthentication.YES and Delegation.YES "
+ "cannot be simultaneously supported by JSSE";
}
}
Iterator itServ = serverRequirements.iterator();
if (c1 == Confidentiality.NO) {
gotConfidentialityNo = true;
}
if (c1 == ServerAuthentication.NO) {
gotServerAuthNo = true;
}
if (c1 == ClientAuthentication.YES && method.getName()
.equals("vBogus")) {
return "vBogus should always fail because it is not "
+ "included in the policy file";
}
while (itServ.hasNext()) {
InvocationConstraint c2 = (InvocationConstraint) itServ.next();
if (c2 == Integrity.NO) {
return "Integrity.NO applied, but is not "
+ "supported by JSSE";
}
if (c2 == Delegation.YES) {
if (clientRequirements
.contains(ClientAuthentication.YES)
|| serverRequirements.contains(
ClientAuthentication.YES)) {
return "ClientAuthentication.YES and Delegation.YES "
+ "cannot be simultaneously supported by JSSE";
}
}
if (c2 == ClientAuthentication.YES && method.getName()
.equals("vBogus")) {
return "vBogus should always fail because it is not "
+ "included in the policy file";
}
/*if (c1.reduceBy(c2) == null || c2.reduceBy(c1) == null) {
return "the constraint " + c2 + " conflicts with "
+ "the constraint " + c1;
}*/
if (c2 == Confidentiality.NO) {
gotConfidentialityNo = true;
}
if (c2 == ServerAuthentication.NO) {
gotServerAuthNo = true;
}
}
}
/*
* special case check for the JSSE provider. None of the
* cipher suites support the combination where confidentiality
* and server authentication are both disabled, so this
* combination must result in an invalid call
*/
if (gotConfidentialityNo && gotServerAuthNo) {
return "The JSSE provider does not support simultaneous "
+ "assetion of ServerAuthentication.NO and "
+ "Confidentiality.NO";
}
return null;
}
}
/**
* Determine whether a call to verifyObjectTrust should have succeeded.
* This method assumes that a non-RMI proxy is being verified.
* The client constraints being set are used to determine whether
* verifyObjectTrust call should fail, since the constraints being set
* are also applied when a trusted server is contacted to verify
* trust in a non-RMI proxy. The servers method constraints are
* assumed to be set up so that there are no server constraints imposed
* for the verifyObjectTrust method.<p>
*
* Note: this method assumes correct behavior of the
* <code>InvocationConstraints.combine</code> method.
*
* @param clientConstraints the constraints applied to the proxy
* when verifyObjectTrust is called.
*
* @return <code>true</code> if the verifier call should have succeeded
*/
private boolean validVerifierCall(InvocationConstraints clientConstraints) {
/*
* As currently implemented, conflicting client constraints are
* never imposed, and the only client constraint which can cause
* verifyObjectTrust to fail is Integrity.NO. The other possible
* valid cause for failure is if no method constraints are
* specified.
*/
Collection clientRequirements = clientConstraints.requirements();
Collection clientPreferences = clientConstraints.preferences();
Iterator itClient = clientRequirements.iterator();
if ((clientRequirements.isEmpty())&&(clientPreferences.isEmpty())) {
return false;
}
while (itClient.hasNext()) {
InvocationConstraint c1 = (InvocationConstraint) itClient.next();
if (c1 == Integrity.NO) return false;
}
return true;
}
/**
* Computes the expected combined constraints for the given method
* by combining the current proxy and contextual client constraints
* along with the constraints implied by the method name.
*
* @param method the method to use to compute the combined constraints
* @return the computed combined constraints
*/
private InvocationConstraints getConstraints(TestMethod testMethod) {
InvocationConstraints sc = testMethod.parseConstraints();
return InvocationConstraints.combine(clientConstraints, sc);
}
/**
* Obtain the combined constraints from the JSSE call context
*
* @param context the OutboundRequestHandle obtained from the provider
* @return the combined InvocationConstraints obtained from the context
*
private InvocationConstraints getConstraints(OutboundRequestHandle context){
InvocationConstraints constraints = null;
try {
Field field = null;
if (ProviderManager.isKerberosProvider()) {
field = context.getClass().getDeclaredField("cs");
} else {
field =
context.getClass().getDeclaredField("requestedConstraints");
}
field.setAccessible(true);
constraints = (InvocationConstraints)field.get(context);
} catch (Exception e) {
logger.log(ALWAYS, "Botched reflection: " + e);
logger.log(ALWAYS, e);
}
return constraints;
}*/
/**
* check to see whether the client constraints in use match those
* which were passed to the proxy when it's trust validator was called.
*
* @param method the <code>TestMethod</code> which was called.
*/
private void checkClientConstraints(TestMethod testMethod) {
InvocationConstraints c = null;
c = iface.getConstraints().getConstraints(testMethod.getMethod());
logger.log(CLIENTCONSTRAINTS, "client constraints from proxy: " + c);
Collection imposedRequirements = new ArrayList( // need mutable set
clientConstraints.requirements());
Set observedRequirements = c.requirements();
Iterator it = observedRequirements.iterator();
while (it.hasNext()) {
Object obj = it.next();
if (!imposedRequirements.contains(obj)) {
logFailure("unexpected client constraint " + obj + " "
+ "found in proxy constraints "
+ "before calling " + testMethod.getName());
}
}
it = imposedRequirements.iterator();
while (it.hasNext()) {
Object obj = it.next();
if (!observedRequirements.contains(obj)) {
logFailure("client constraint " + obj + " "
+ "missing from proxy constraints "
+ "before calling " + testMethod.getName());
}
}
}
/*
* Validate that the combined constraints actually being used
* are consistant with those imposed by the client and server.
* <code>InvocationConstraint.reduceBy</code> is assumed to be correctly
* implemented for all constraints. The combined constraints
* passed to this method are assumed to have been obtained
* from the <code>OutboundRequestHandle</code> associated with the method call.
* This method is called from the <code>writeCallback</code> after
* obtaining the call context.
*
* @param method the method being called
* @param combinedConstraints the combined constraints being used by the
* client endpoint.
*/
private void checkCombinedConstraints(TestMethod method,
InvocationConstraints combinedConstraints) {
logger.log(INTERNALCALLS,
"Entering checkCombinedConstraints(method, context)");
logger.log(COMBINEDCONSTRAINTS,
"combined constraints: " + combinedConstraints);
InvocationConstraints sc = method.parseConstraints();
sc = InvocationConstraints.combine(sc, clientConstraints);
Set imposedRequirements = sc.requirements();
/* combined requirements must be mutable */
Set combinedRequirements =
new HashSet(combinedConstraints.requirements());
Iterator it = imposedRequirements.iterator();
while (it.hasNext()) {
InvocationConstraint c = (InvocationConstraint)it.next();
if (!combinedRequirements.contains(c)) {
logFailure("Combined constraint is missing constraint " + c);
}
combinedRequirements.remove(c);
}
if (!combinedRequirements.isEmpty()) {
InvocationConstraints residual =
new InvocationConstraints(combinedRequirements, null);
logFailure("Combined constraints contains extra "
+ "constraints: " + residual);
}
logger.log(INTERNALCALLS, "Leaving checkCombinedConstraints"
+"(method, context)");
}
/*
* An implementation of WriteCallback to be installed in the bridge.
* The writeCallback method is called 'in the middle'
* of a remote client call, before the call data is written.
* Connection objects can be accessed to perform validations.
* This class is also an InstanceCarrier, so it can be used to
* access the client instance which created it.
*/
protected static class ClientSideAction implements WriteCallback,
InstanceCarrier {
/** the client instance associated with this action */
SecureClient instance;
ClientSideAction(SecureClient instance) {
this.instance = instance;
}
/**
* The callback method. Call the client instance back to perform
* validations. It is the responsbility of the client callback
* to write a ReadCallback object to the stream.
*
* @param request the OutboundRequest of this remote call
* @param context the OutboundRequestHandle of this remote call
*/
public ReadCallback writeCallback(OutboundRequest request,
InvocationConstraints constraints) {
return instance.doWriteCallback(request, constraints);
}
/**
* Get the instance which installed this InstanceCarrier. Primarily
* used to allow client side components which do not have a direct
* reference to the SecureClient instance to obtain the logger.
*
* @return the TestClient instance which created this InstanceCarrier
*/
public TestClient getInstance() {
return instance;
}
}
/**
* The callback invoked from the writeCallback method. The actions
* performed here could have been performed directly by the
* ClientSideAction object since it executes in the calling thread.
* However, this method is called instead to maintain symmetry with the
* ServerSideAction object. In addition to performing a variety of
* validations, this method writes an instance of
* ServerSideAction (an InstanceCarrier) to the given stream to allow
* the server to obtain a reference to the calling client.
*
* @param request the OutboundRequest for this remote call
* @param context the OutboundRequestHandle for this remote call
*/
synchronized ReadCallback doWriteCallback(OutboundRequest request,
InvocationConstraints constraints) {
/*
* the writeObject must be done after setting combined constraints
* because the server side thread may execute immediately after
* writeObject is executed, and combined constraints must be
* set before the server side callback is executed. This is
* necessary if the server side readCallData method never actually
* reads data from the stream. In this case, it won't block
* waiting for the writeCallData to write data to the stream.
*/
combinedConstraints = constraints;
ui.setCombinedConstraints(combinedConstraints);
checkCombinedConstraints(currentMethod, combinedConstraints);
//checkServerSubject(request.getServerSubject(), combinedConstraints);
if (!ProviderManager.isKerberosProvider()) {
SSLSocket socket = getServerSocket(request);
cipherSuite =
CipherSuite.getSuite(socket.getSession().getCipherSuite());
checkCipherSuite(cipherSuite, socket, combinedConstraints);
ui.setTestSuite(cipherSuite);
}
return new ServerSideAction(myKey);
}
/**
* An implementation of ReadCallback/InstanceCarrier, an instance of
* which is obtained from the ObjectInputStream and installed in the
* bridge by the readCallData method in the bridge.
* The doReadcallback method is called 'in the middle'
* of a remote client call, immediately after the call to readCallData,
* so connection objects can be accessed to perform validations.
* Constraints are checked here because the server connection is
* required in order to determine whether client authentication
* is being performed. This object also implements InstanceCarrier,
* so that the server side error logging can be coordinated
* with the correct client side instance.
*/
public static class ServerSideAction implements ReadCallback,
InstanceCarrier, Serializable {
Integer key;
ServerSideAction(Integer key) {
this.key = key;
}
/**
* Obtain the SecureClient instance which invoked this remote
* call and call it's <code>doReadCallback</code> method.
*
* @param request the <code>InboundRequest</code>
* used to receive this remote call.
*
* @throws IllegalArgumentException if the key
* passed in the constructor of this object
* does not map to a SecureClient instance.
*/
public void readCallback(InboundRequest request) {
SecureClient instance = (SecureClient) getInstance();
if (instance == null) {
throw new TestException("getInstance returned a "
+ "null instance in readCallback",null) ;
}
instance.doReadCallback(request);
}
/**
* Get the instance which created this InstanceCarrier. Primarily
* used to allow server side components to obtain a
* reference to the TestClient to obtain its logger.
*
* @return the TestClient instance which created this InstanceCarrier
*/
public TestClient getInstance() {
return SecureClient.getInstance(key);
}
}
/**
* The callback invoked from the readCallback method. This method
* validates the correctness of the server constraints, the client
* subject obtained from the readCallData method, and verifies that
* the cipher suite being used is consistant with the constraints
* applied for the call.
*
* @param connection the ServerConnection provided by readCallData
* @param serverConstraints the ServerConstraints provided by readCallData
* @param clientSubject the client subject provided by readCallData
*/
public void doReadCallback(InboundRequest request) {
logger.log(DEBUG, "In readData callback, subject = " + clientSubject);
ui.setClientSubject(clientSubject);
}
/**
* An implementation of WriteCallback/InstanceCarrier solely
* intended to allow global access to the client instance.
* This objects writeCallback method is called 'in the middle'
* of a remote client call, immediately before the call to the
* writeCallData. It is set in
* <code>Bridge.writeCallback</code>
* before performing all Remote calls other than those defined by the
* <code>ConstraintsInteface</code> class.
*/
protected static class InstancePasser implements WriteCallback,
InstanceCarrier {
SecureClient instance;
InstancePasser(SecureClient instance) {
this.instance = instance;
}
/**
* Implements the WriteActionData callback method. This method
* simply returns an instance of <code>InstanceReceiver</code>
*
* @param request the <code>OutboundRequest</code> for
* the remote call
* @param context the <code>OutboundRequestHandle</code> for the remote call
* @return an instance of <code>InstanceReceiver</code>
*/
public ReadCallback writeCallback(OutboundRequest request,
InvocationConstraints constraints) {
return new InstanceReceiver(instance.getKey());
}
/**
* Get the instance which installed this InstanceCarrier. Primarily
* used to allow client side components which do not have a direct
* reference to the TestClient instance to obtain the logger.
*
* @return the TestClient instance which created this InstanceCarrier
*/
public TestClient getInstance() {
return instance;
}
}
/**
* An implementation of ReadCallback/InstanceCarrier solely
* intended to allow global access to the client instance.
*/
public static class InstanceReceiver implements ReadCallback,
InstanceCarrier, Serializable {
Integer key;
InstanceReceiver(Integer key) {
this.key = key;
}
/**
* required to satisfy the ReadCallback interface.
*/
public void readCallback(InboundRequest request) {
}
/**
* Get the instance which created this InstanceCarrier.
* Used to allow server side components to obtain a
* reference to the TestClient to obtain its logger.
*
* @return the TestClient instance which created this InstanceCarrier
*/
public TestClient getInstance() {
return SecureClient.getInstance(key);
}
}
/**
* Check the correctness of client subject. The given client subject
* is assumed to be the subject obtained from the readCallData
* method. Failure is logged under the following conditions:
* <ul>
* <li>The client subject was authenticated (non null) when
* ClientAuthentication.NO was imposed.
* <li>The client subject was not authenticated (null) when
* ClientAuthentication.YES was imposed.
* <li>ClientAuthentication.YES was imposed and ClientMinPrincipal
* was imposed, but the authenticated client did not include
* all of the principals required by ClientMinPrincipal
* </ul>
*
* @param clientSubject the client subject from readCallData
*/
private void checkClientSubject(Subject clientSubject) {
logger.log(CLIENTSUBJECT,"Authenticated client subject is "
+ clientSubject);
if (imposed(combinedConstraints, ClientAuthentication.NO)
&& clientSubject != null) {
logFailure("Client Subject Authenticated when "
+ "ClientAuthentication.NO was imposed");
return;
}
if (imposed(combinedConstraints, ClientAuthentication.YES)) {
if (clientSubject == null) {
logFailure("Client Subject is null when "
+ "ClientAuthentication.YES was imposed");
return;
}
if (currentMethod.requiresAlt1() && !hasAltPrincipal(clientSubject,
subjectProvider.getConstraintAlternatives1())) {
logFailure("Client Subject did not authenticate as "
+ "an Alt1 principal as required by "
+ "ClientMinPrincipal constraints. "
+ "Authenticated Subject: " + clientSubject);
return ;
}
if (currentMethod.requiresAlt2() && !hasAltPrincipal(clientSubject,
subjectProvider.getConstraintAlternatives2())) {
logFailure("Client Subject did not authenticate as "
+ "an Alt2 principal as required by "
+ "ClientMinPrincipal constraints. "
+ "Authenticated Subject: " + clientSubject);
return ;
}
}
}
/**
* Verifies that the server subject is valid. To be valid:
* <ul>
* <li>If ServerAuthentication.NO was applied, the subject must be null
* <li>If ServerAuthentication.YES was applied, the subject must be non
* null.
* <ul>
* Because <code>ServerMinPrincipal</code> is exercised by inclusion of
* all server principals in a <code>ConstraintAlternatives</code>, there
* should never be a failure attributable to this constraint. Therefore
* no explicit checking for this is done.
*
* @param serverSubject the authenticated server subject obtained from the
* Connection during the call
* @param combinedConstraints the combined constraints that were applied
* for this remote call
*/
private void checkServerSubject(Subject serverSubject,
InvocationConstraints combinedConstraints) {
ui.setServerSubject(serverSubject);
Set requirements = combinedConstraints.requirements();
if (requirements.contains(ServerAuthentication.NO)) {
if (serverSubject != null) {
logFailure("Server subject provided with "
+ "ServerAuthentication.NO asserted. "
+ "Subject: " + serverSubject);
}
}
if (requirements.contains(ServerAuthentication.YES)) {
if (serverSubject == null) {
logFailure("Server subject is null with "
+ "ServerAuthentication.YES asserted");
}
}
}
/**
* Check whether the given InvocationConstraints object contains
* the given required constraint
*
* @param constraints the InvocationConstraints object to check
* @param constraint the required constraint to check for
*
* @return <code>true</code> if <code>constraints</code> includes
* <code>constraint</code> in its set of required constraints.
*/
private boolean imposed(InvocationConstraints constraints,
InvocationConstraint constraint) {
Iterator it = constraints.requirements().iterator();
while (it.hasNext()) {
InvocationConstraint c = (InvocationConstraint)it.next();
if (c.equals(constraint)) {
return true;
}
}
return false;
}
/**
* Check whether the given InvocationConstraints object contains
* the given preferred constraint
*
* @param constraints the InvocationConstraints object to check
* @param constraint the required constraint to check for
*
* @return <code>true</code> if <code>constraints</code> includes
* <code>constraint</code> in its set of preferred constraints.
*/
private boolean preferred(InvocationConstraints constraints,
InvocationConstraint constraint) {
Iterator it = constraints.preferences().iterator();
while (it.hasNext()) {
InvocationConstraint c = (InvocationConstraint)it.next();
if (c.equals(constraint)) {
return true;
}
}
return false;
}
/*
* Check whether the given subject contains
* principals consistant with the given ConstraintAlternatives.
* The ConstraintAlternatives contains a set of ClientMinPrincipals,
* and the ClientMinPrincipals contains a set of Principals. The
* subject must contains all of the principals in one of
* the ClientMinPrincipals.
*
* @param subject the subject to test. This may be null.
* @param the ConstraintAlternatives the subject must conform to.
* This must not be null.
*/
private boolean hasAltPrincipal(Subject subject,
ConstraintAlternatives alt)
{
if (subject != null) {
Iterator itAlt = alt.elements().iterator();
while (itAlt.hasNext()) {
ClientMinPrincipal cminp = (ClientMinPrincipal) itAlt.next();
int matchCount = cminp.elements().size();
Iterator itCminp = cminp.elements().iterator();
while (itCminp.hasNext()) {
Principal altP = (Principal) itCminp.next();
Iterator itSub = subject.getPrincipals().iterator();
while (itSub.hasNext()) {
Principal subP = (Principal)itSub.next();
if (altP.getName().equals(subP.getName())) matchCount--;
}
if (matchCount == 0) return true;
}
}
}
return false ;
}
/**
* Extract the JSSE server socket from the server connection object
*
* @param connection the ServerConnection from readCallData
*
* @return the SSLSocket bound the the connection
*/
private SSLSocket getServerSocket(OutboundRequest request) {
SSLSocket socket = null;
Field field = null;
try {
/* based on class SecureConnectionManager.Outbound */
field = request.getClass().getDeclaredField("c");
field.setAccessible(true);
Connection c =
(Connection) field.get(request);
field = c.getClass().getDeclaredField("sslSocket");
field.setAccessible(true);
socket = (SSLSocket)field.get(c);
} catch (Exception e) {
if (field == null) {
try {
/* based on class HttpsServerEndpoint.HtttpsOutboundRequest */
field = request.getClass().getDeclaredField("connection");
field.setAccessible(true);
Connection c =(Connection) field.get(request);
field = c.getClass().getSuperclass()
.getDeclaredField("sslSocket");
field.setAccessible(true);
socket = (SSLSocket)field.get(c);
} catch (Exception eHttp) {
eHttp.printStackTrace();
logger.log(ALWAYS, "Botched reflection: " + eHttp);
logger.log(ALWAYS, eHttp);
}
} else {
e.printStackTrace();
logger.log(ALWAYS, "Botched reflection: " + e);
logger.log(ALWAYS, e);
}
}
return socket;
}
/**
* Check that the ciphersuite is consistant with the combined constraints
* <p>
* Failure is logged in the following cases for required constraints:
* <ul>
* <li>Confidentiality.YES was imposed, but the suite does not
* support encryption.
* <li>Confidentiality.NO was imposed, but the suite supports encryption.
* <li>ServerAuthentication.YES was imposed, but the suite does not support
* authentication.
* <li>ServerAuthentication.NO was imposed, but the suite supports
* authentication.
* <li>ClientAuthentication.YES was imposed, but either the suite does
* not support authentication, or client authentication was not
* turned on in the server socket.
* <li>ClientAuthentication.NO was imposed, but client authentication
* was turned on in the server socket.
* <li>Integrity.NO was imposed. All SSL cipherSuites impose Integrity.YES
* </ul>
*
* In addition, failures will be logged if Confidentiality.YES was
* imposed, and a preference of ConfidentialityStrength.WEAK was
* asserted but a strong suite was selected, and conversely if a
* preference of ConfidentialityStrength.STRONG was asserted but
* a weak suite was selected.
*
* @param suite the String containing the cipherSuite designator
* @param socket the server SSLSocket involved in the remote call
* @param constraints the server constraints for this method call
*/
private void checkCipherSuite(CipherSuite suite, SSLSocket socket,
InvocationConstraints constraints) {
boolean clientAuthenticated;
logger.log(SUITE, "Ciphersuite for this call is " + suite);
logger.log(COMBINEDCONSTRAINTS, "constraints parameter in "
+ "checkChipherSuite: " + constraints);
logger.log(COMBINEDCONSTRAINTS, "combined constraints in "
+ "checkChipherSuite: " + combinedConstraints);
if (!ProviderManager.isStrong() && suite.isStrong()) {
logFailure("Strong encryption suite selected when the provider "
+ "should not support strong encryption.");
}
Set requirements = null;
SSLSession session = socket.getSession();
if (session.getLocalCertificates()!=null) {
clientAuthenticated = true;
} else {
clientAuthenticated = false;
}
try {
requirements = constraints.requirements();
} catch (RuntimeException e) {
logger.log(ALWAYS, "Exception caught, param = " + constraints);
logger.log(ALWAYS, e);
logger.endBoundary();;
logger.writeLog();
throw new TestException("Unexpected runtime exception", e);
}
for (Iterator it = requirements.iterator(); it.hasNext(); ) {
InvocationConstraint c = (InvocationConstraint) it.next();
if ((c == Confidentiality.YES) && !suite.isConfidential()) {
logFailure("Confidentiality.YES is not supported "
+ "by suite " + suite);
}
if ((c == Confidentiality.NO) && suite.isConfidential()) {
logFailure("Confidentiality.NO is not supported "
+ "by suite " + suite);
}
if ((c == ServerAuthentication.YES) && !suite.isAuthenticated()) {
logFailure("ServerAuthentication.YES is not "
+ "supported by suite " + suite);
}
if ((c == ServerAuthentication.NO) && suite.isAuthenticated()) {
logFailure("ServerAuthentication.NO is not "
+ "supported by suite " + suite);
}
if ((c == ClientAuthentication.YES) && !suite.isAuthenticated()) {
logFailure("ClientAuthentication.YES is not "
+ "supported by suite " + suite);
}
if ((c == ClientAuthentication.YES) && !clientAuthenticated) {
logFailure("ClientAuthentication.YES is asserted, "
+ "but the client did not authenticate with the server");
}
if ((c == ClientAuthentication.NO) && clientAuthenticated) {
logFailure("ClientAuthentication.NO is asserted, "
+ "but client authenticated with the server");
}
if ((c == Integrity.YES) && !suite.isIntegrity()) {
logFailure("Integrity.YES is not supported by suite " + suite);
}
if ((c == Integrity.NO) && suite.isIntegrity()) {
logFailure("Integrity.NO is not supported by suite " + suite);
}
}
/*
* check preferences. Note that the ConfidentialityStrength
* constraint may be ignored if Confidentiality.YES is not imposed
*/
Set preferences = constraints.preferences();
for (Iterator it = preferences.iterator(); it.hasNext(); ) {
InvocationConstraint c = (InvocationConstraint) it.next();
if ((c == ConfidentialityStrength.WEAK) && !suite.isWeak()
&& imposed(constraints,Confidentiality.YES)) {
logFailure("ConfidentialityStrength.WEAK was "
+ "preferred but a strong suite "
+ "was selected:" + suite);
}
if ((c == ConfidentialityStrength.STRONG)
&& ProviderManager.isStrong()
&& suite.isWeak()
&& imposed(constraints,Confidentiality.YES)) {
logFailure("ConfidentialityStrength.STRONG was "
+ "preferred but a weak suite was selected:" + suite);
}
}
}
/**
* Log a test failure. The failure is logged, and the failure counter
* is incremented.
*
* @param errorString the message to write to the error log
*/
public void logFailure(String errorString) {
logFailure(errorString, null);
}
/**
* Log a test failure. The failure is logged, and the failure counter
* is incremented.
*
* @param errorString the message to write to the error log
* @param t the exception thrown as a result of the failure
*/
public void logFailure(String errorString, Throwable t) {
/*
* synchronized because failureCount is accessed by another
* thread in the UserInterface
*/
synchronized (this) {
failureCount++;
}
logger.log(FAILURES, "***** Test " + testNumber + " Failed *****");
logger.log(FAILURES, errorString);
if (currentMethod != null) {
logger.log(FAILURES, "Method signature: "
+ currentMethod.getSignature());
}
logger.log(FAILURES, "Imposed Client proxy constraints: "
+ clientConstraints);
if (currentMethod != null) {
logger.log(FAILURES, "Imposed Server constraints: "
+ currentMethod.parseConstraints());
}
logger.log(FAILURES, "Combined constraints: "
+ ((combinedConstraints == null) ? "Undefined"
: combinedConstraints.toString()));
logger.log(FAILURES, "Authenticated Client Subject: " + clientSubject);
logger.log(FAILURES, "Current ciphersuite: "
+ ((cipherSuite == null) ? "Undefined" : cipherSuite.toString()));
logger.dump(FAILURES);
if (t != null) {
logger.log(FAILURES, "Exception was :" + t);
t.printStackTrace();
//The following commented block of code is sometimes useful in
//debugging - but can be misleading in production runs
/*Throwable endpointException =
EndpointWrapper.getLastEndpointException();
if (endpointException !=null) {
logger.log(FAILURES, "Last Exception in the endpoint was : "
+ endpointException);
endpointException.printStackTrace();
}*/
logger.log(FAILURES, t);
}
logger.log(FAILURES, "***************************************\n");
ui.setFailureCount(failureCount);
String es = logger.getLogBuffer();
if (es != null) {
ui.showFailure(es);
}
logger.endBoundary();
logger.writeLog();
/*
* If the <code>UserInterface</code> indicates 'stop on failure',
* make this thread wait. Note that if the user interface is actually
* the <code>NullGUI</code>, then <code>stopAfterFailure</code> will
* return <code>false</code> if the <code>e2etest.abortOnFailure</code>
* property is not defined, and it will throw a
* <code>TestException</code> if the property is defined.
*/
if (ui.stopAfterFailure()) {
synchronized (goMonitor) {
try {
goMonitor.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
/**
* Get the logger associated with this instance of SecureClient
*
* @return the logger
*/
public Logger getLogger() {
return logger;
}
/**
* Get the key associated with this instance of SecureClient. Used
* by the client side InstanceCarrier to create the server side
* InstanceCarrier
*
* @return the key
*/
private Integer getKey() {
return myKey;
}
/**
* Get the combined constraints associated with this SecureClient instance
*
* @return the combined constraints
*/
public InvocationConstraints getCombinedConstraints() {
return combinedConstraints;
}
/**
* Get the unconstrained service proxy associated with this
* SecureClient. Needed on the server side to respond to the
* verifyObjectTrust mechanism.
*/
public SmartInterface getUnconstrainedProxy() {
return unconstrainedIface;
}
/**
* helper to write a standard boundary header
*/
private void writeBoundaryHeader() {
logger.startBoundary("Test " + testNumber + " "
+ "for thread " + threadNumber);
}
/**
* register the <code>UserInterface</code> to associate with this client
*
* @param ui the <code>UserInterface</code> to register
*/
public void registerUserInterface(UserInterface ui) {
this.ui = ui;
}
/**
* <code>UserInterface</code> call which will cause the test to
* run assuming it was paused.
*
*/
public void executeTests() {
synchronized (goMonitor) {
goMonitor.notifyAll();
}
}
/* inherit javadoc */
public int getTestNumber() {
return testNumber;
}
/* inherit javadoc */
public int getTestTotal() {
return totalTests;
}
/* inherit javadoc */
/* synchronized because the UserInterface is expected to access
* failureCount from a separate thread
*/
synchronized public int getFailureCount() {
return failureCount;
}
/**
* compute the total (approximate) number of remote method calls
* this client will make. The number is approximate because one
* luck thread gets to make a couple of extra calls to unexport
* the last proxy.
*
* @return the total number of calls this client should execute
*/
private int computeTotalTestCount() {
int count = 0;
for (int req = 0; req < (1 << constraintsArray.length); req++) {
if (((req & BADCONF) == BADCONF)
|| ((req & BADINTEG) == BADINTEG)
|| ((req & BADDELEG) == BADDELEG)) {
continue;
}
for (int pref = 0; pref < (1 << preferencesArray.length); pref++)
{
if ((pref & BADSTRENGTH) == BADSTRENGTH) {
continue;
}
for (int m=0; m<methods.length; m++) {
if (methods[m].getName().indexOf("Noconf") >= 0) {
continue;
}
count++;
}
}
}
count += 2; // count the unexport and callAfterUnexport calls
return count;
}
/**
* Verify that the server method constraints in the proxy are equal to
* the server constraints set by the server. This test likely just
* boils down to verify that arrays serialize properly.
*
* @param proxy the remote proxy to check
*/
private void checkServerConstraints(SmartInterface proxy)
{
try {
if (!((SmartProxy)proxy).getServerConstraints()
.equals(SmartProxy.getMethodConstraints())) {
logFailure("Server constraints obtained from proxy "
+ "do not match server constraints applied "
+ "by server");
}
} catch (Exception e) {
logFailure("Exception retrieving server constraints "
+ "from the proxy because " + e.getMessage());
}
}
}