/** * @author Terry Fleury (tfleury@ncsa.uiuc.edu) * @author Jarek Gawor (gawor@mcs.anl.gov) * @version 1.0 2006-03-23 * * This is a sample client application to demonstrate connecting to a web * service which has been deployed into Tomcat with proxy certificate * support. * * To compile, you will need the following jars in your CLASSPATH: * axis.jar * cog-axis.jar * cog-jglobus.jar * jaxrpc.jar * log4j.jar * * To run, you will need these ADDITIONAL jars in your CLASSPATH: * cog-url.jar * commons-discovery.jar * commons-logging.jar * cryptix32.jar * cryptix-asn1.jar * jce-jdk13-131.jar * puretls.jar * saaj.jar * wsdl4j.jar * * Run "java SampleClient -?" for a complete description of the program. */ // In standard Java import java.io.File; import java.io.FileInputStream; import java.net.URL; import java.util.Random; import java.util.Hashtable; import org.ietf.jgss.GSSCredential; // In Axis jaxrpc.jar import javax.xml.namespace.QName; import javax.xml.rpc.ParameterMode; // In axis.jar import org.apache.axis.client.Call; import org.apache.axis.client.Service; import org.apache.axis.encoding.XMLType; import org.apache.axis.configuration.SimpleProvider; import org.apache.axis.utils.Options; import org.apache.axis.SimpleTargetedChain; import org.apache.axis.transport.http.HTTPSender; // In cog-axis.jar import org.globus.axis.transport.HTTPSSender; import org.globus.axis.transport.GSIHTTPSender; import org.globus.axis.util.Util; import org.globus.axis.gsi.GSIConstants; // In cog-jglobus.jar import org.globus.common.CoGProperties; import org.globus.gsi.gssapi.auth.HostAuthorization; import org.globus.myproxy.MyProxy; import org.gridforum.jgss.ExtendedGSSManager; import org.gridforum.jgss.ExtendedGSSCredential; // In log4j.jar import org.apache.log4j.Logger; import org.apache.log4j.Level; import org.apache.log4j.BasicConfigurator; /** * This class demonstrates a sample client using proxy certificates. This * program demonstrates how to read in a proxy certificate either from a * file or from a MyProxy server. It then uses this credential to connect * to a Tomcat server which has been configured to use Globus/CoG Kit secure * connections. */ public class SampleClient { // Declare some constants private static final String MYPROXYHOST = "myproxy.ncsa.uiuc.edu"; private static final int MYPROXYPORT = 7512; private static final int MYPROXYSHORT = 60; private static final int MYPROXYLONG = 14400; private static final String TEXTTOSEND = "Testing..."; private static final String SERVICEURL = "httpg://localhost:8443/axis/SampleService.jws"; static Random rn = new Random(); static Logger logger = Logger.getLogger(SampleClient.class); static CoGProperties cogprops = CoGProperties.getDefault(); // Variables to hold options specified on command line static String myProxyHost = ""; static int myProxyPort = 0; static String myProxyUsername = ""; static String myProxyPassword = ""; static boolean useMyProxy = false; static String proxyFileName = ""; static String serviceURL = SERVICEURL; static boolean myProxyStoreCred = false; /** * Main application. * @param args Command line arguments. */ public static void main(String[] args) { // Set up the logger for outputting 'info' messages logger.setLevel((Level)Level.INFO); // Read command line options and arguments getCommandLineOptions(args); try { // Fetch the proxy credential GSSCredential gssCred = null; if (useMyProxy) { gssCred = getCredFromMyProxy(myProxyHost,myProxyPort, myProxyUsername,myProxyPassword); } else { gssCred = getCredFromFile(proxyFileName); } if (gssCred != null) { logger.info("Got Proxy DN = " + gssCred.getName()); /* IMPORTANT CODE FOR YOUR CLIENT! * * First, you must register the various transport protocols * * (httpg, https, http) and create a new Service using * * this provider (in this case a SimpleProvider). This * * code block is probably not currently in your client and * * must be added to do transport layer delegation via * * httpg. */ SimpleProvider provider = new SimpleProvider(); SimpleTargetedChain c = null; c = new SimpleTargetedChain(new GSIHTTPSender()); provider.deployTransport("httpg",c); c = new SimpleTargetedChain(new HTTPSSender()); provider.deployTransport("https",c); c = new SimpleTargetedChain(new HTTPSender()); provider.deployTransport("http",c); Util.registerTransport(); Service service = new Service(provider); /* IMPORTANT CODE FOR YOUR CLIENT! * * Using the service you created in the code block above, * * create a new Call and set a bunch of GSI properties * * so as to delegate the proxy credential. You must set * * GSI_CREDENTIALS, GSI_AUTHORIZATION, and GSI_MODE. This * * code is probably not currently in your client and must * * be added to do transport layer delegation of the * * credential. */ Call call = (Call)service.createCall(); call.setProperty(GSIConstants.GSI_CREDENTIALS,gssCred); // Note: with 'host authorization', the host in the service URL // must agree with the host certificate DN of that service host. call.setProperty(GSIConstants.GSI_AUTHORIZATION, HostAuthorization.getInstance()); // If we are using httpg, we can use the transport level // delegation facilities of the CoG Kit to do full delegation // of the proxy credential. If we are using https, then the // credential does NOT get delegated, but we can use the // poor man's method of delegation by storing the credential // to a MyProxy server with a random username and // passphrase, and a short lifetime. (Note that the '-m' // must have been specified for this to occur here.) call.setProperty(GSIConstants.GSI_MODE, GSIConstants.GSI_MODE_FULL_DELEG); call.setTargetEndpointAddress(new URL(serviceURL)); String ret = null; if (myProxyStoreCred) { // Store the GSSCredential we fetched earlier to the // default MyProxy server. The returned Hashtable // contains the host:port of the MyProxy server used, // the random username and passphrase used, and the // lifetime specified for the newly stored credential. // This information is then sent to the service which // can fetch the short lived credential for it's own // use, thus creating a poor man's delegation service. Hashtable ht = putCredToMyProxy(gssCred); call.setOperationName( new QName("SampleService","notifyGSSCredentialStored")); call.addParameter("host", XMLType.XSD_STRING,ParameterMode.IN); call.addParameter("port", XMLType.XSD_INT,ParameterMode.IN); call.addParameter("username", XMLType.XSD_STRING,ParameterMode.IN); call.addParameter("passphrase", XMLType.XSD_STRING,ParameterMode.IN); call.addParameter("lifetime", XMLType.XSD_INT,ParameterMode.IN); call.setReturnType(XMLType.XSD_STRING); // Make the actual call to web service and print response System.out.println("Notifying web service about " + "storing short lived GSSCredential"); ret = (String)call.invoke(new Object[] { (String)ht.get("host"), (Integer)ht.get("port"), (String)ht.get("username"), (String)ht.get("passphrase"), (Integer)ht.get("lifetime") }); System.out.println("Web Service Response : " + ret); call.clearOperation(); call.removeAllParameters(); } // Set up the call for the "basic" SampleService, which // takes a string to be echoed and prints out the various // GSI_* properties we set earlier. call.setOperationName( new QName("SampleService","serviceMethod")); call.addParameter("arg1",XMLType.XSD_STRING,ParameterMode.IN); call.setReturnType(XMLType.XSD_STRING); // Make the actual call to the web service and print response System.out.println("Sending message \"" + TEXTTOSEND + "\" to Web Service"); ret = (String)call.invoke(new Object[] { TEXTTOSEND } ); System.out.println("Web Service Response : " + ret); } } catch (Exception e) { e.printStackTrace(); } } /** * Scan the command line for arguments and option flags. This scans the * command line for any flags which can specify how the proxy certificate * is read in (from file or from MyProxy server). It also assumes that * the first non-flag command line argument is the URL of the web service. * Note that all of the command line arguments are set to global variables * to make the code easy. Yeah, I'm lazy. * @param args The command line arguments of the 'java' command. */ public static void getCommandLineOptions(String [] args) { String tmp; try { Options options = new Options(args); if (options.isFlagSet('?') > 0) { printHelpMessage(); System.exit(0); } if (options.isFlagSet('m') > 0) { // Store cred to MyProxy server myProxyStoreCred = true; // with random username/password } tmp = options.isValueSet('h'); // MyProxy host name if (tmp != null) { myProxyHost = tmp; } tmp = options.isValueSet('p'); // MyProxy port number if (tmp != null) { myProxyPort = Integer.valueOf(tmp).intValue(); } tmp = options.isValueSet('u'); // MyProxy user name if (tmp != null) { myProxyUsername = tmp; } tmp = options.isValueSet('w'); // MyProxy user password if (tmp != null) { myProxyPassword = tmp; } tmp = options.isValueSet('n'); // Local proxy file name if (tmp != null) { proxyFileName = tmp; } args = options.getRemainingArgs(); if ((args != null) && (args.length > 0)) { serviceURL = args[0]; // Web service URL } if ((myProxyUsername.length() > 0) && (myProxyPassword.length() > 0)) { // Get proxy from a MyProxy server useMyProxy = true; if (myProxyHost.length() == 0) myProxyHost = MYPROXYHOST; if (myProxyPort == 0) myProxyPort = MYPROXYPORT; } else { // Get proxy from local file if (proxyFileName.length() == 0) proxyFileName = cogprops.getProxyFile(); } if (useMyProxy) { logger.info("Getting proxy certificate from MyProxy Server."); logger.info("MyProxy host = " + myProxyHost); logger.info("MyProxy port = " + myProxyPort); logger.info("MyProxy user = " + myProxyUsername); } else { logger.info("Getting proxy certificate from Local File."); logger.info("Local proxy file name = " + proxyFileName); } logger.info("Web service URL = " + serviceURL); } catch (Exception e) { System.out.println("Caught exception " + e.getMessage()); } } /** * Print out a help message. This prints out detailed information on the * command line arguments and options for this sample program. */ public static void printHelpMessage() { System.out.println("\n"+ "Usage: java SampleClient [options] [serviceURL]\n"+ "\n"+ "This program sends a test message \"Testing...\" to a web service which\n"+ "responds with the DN (Distinguished Name) used for the connection and\n"+ "notification that the test message was received.\n"+ "\n"+ "The [serviceURL] is the URL of the web service. If the serviceURL is not\n"+ "specified, it defaults to " + SERVICEURL + "\n"+ "\n"+ "A proxy certificate will be read from a default local file unless\n"+ "overridden by command line options. The default proxy certificate file\n"+ "corresponds to the standard Globus file name "+cogprops.getProxyFile()+".\n"+ "You can specify an alternate file name OR you can specify options to get\n"+ "the proxy from a MyProxy server. If you want to use a MyProxy server\n"+ "you MUST specify at least the user name and password for the proxy\n"+ "certificate. Default values will be used for the MyProxy server and port\n"+ "if you do not specify them. The local proxy certificate file will NOT\n"+ "be read if you specify options for the MyProxy server.\n"+ "\n"+ "Available [options] are:\n"+ " -n The name of the local proxy certificate file. If you do NOT\n"+ " use this option AND also do NOT specify any of the following\n"+ " MyProxy options, the default local proxy file name will be\n"+ " used, i.e. " + cogprops.getProxyFile() + ".\n"+ " -u The user name to use when contacting the MyProxy server. To use\n"+ " a MyProxy server, you MUST specify at least this and the password.\n"+ " -w The password for the MyProxy user name. To use a MyProxy server,\n"+ " you MUST specify at least the user name and this.\n"+ " -h The host name for the MyProxy server holding the certficate.\n"+ " If not specified, it defaults to " + MYPROXYHOST + ".\n"+ " -p The port number for the MyProxy server. If not specified, it\n"+ " defaults to " + MYPROXYPORT + ".\n"+ " -m If present, store the proxy credential that was read in back to\n"+ " MyProxy server using a random username and passphrase. These\n"+ " values are then passed to the web service which will fetch the\n"+ " stored credential. This is a poor man's version of delegation\n"+ " for when you don't want to use the httpg protocol.\n"+ " -? Print this help text and exit."); } /** * Read in a proxy certificate from a file. If you do not specify a * filename, the default Globus filename will be used (e.g. * "/tmp/x509up_u_msmith"). * @param filename The name of the file from which to read the proxy * certificate. * @return A GSS credential for the proxy certificate in the file. */ public static GSSCredential getCredFromFile(String filename) { GSSCredential retcred = null; if (filename.length() == 0) { filename = cogprops.getProxyFile(); logger.warn("No proxy file specified. " + "Reading proxy from '" + filename + "'"); } try { File inFile = new File(filename); byte [] data = new byte[(int)inFile.length()]; FileInputStream inStream = new FileInputStream(inFile); inStream.read(data); inStream.close(); ExtendedGSSManager manager = (ExtendedGSSManager)ExtendedGSSManager.getInstance(); retcred = manager.createCredential(data, ExtendedGSSCredential.IMPEXP_OPAQUE, GSSCredential.DEFAULT_LIFETIME, null, // use default mechanism - GSI GSSCredential.INITIATE_AND_ACCEPT); } catch (Exception e) { logger.error("Could not read proxy from '" + filename + "' because " + e.getMessage()); } return retcred; } /** * Read in a proxy certificate from a MyProxy server. This is really * just a wrapper for MyProxy's old static 'get' method which is now * deprecated. * @param username The username for the proxy certificate. * @param password The password corresponding to the username. * @param host The FQDN of the MyProxy host. * @param port The MyProxy port. * @return A GSS credential for the proxy certificate from the MyProxy * server. */ public static GSSCredential getCredFromMyProxy( String host, int port, String username, String password) { GSSCredential retcred = null; if (host.length() == 0) { host = MYPROXYHOST; logger.warn("No MyProxy host specified. Using '" + host + "'"); } if (port == 0) { port = MYPROXYPORT; logger.warn("No MyProxy port specified. Using '" + port + "'"); } try { MyProxy myProxyServer = new MyProxy(host,port); retcred = myProxyServer.get(null,username,password,MYPROXYLONG); } catch (Exception e) { logger.error("Could not get proxy from '" + host + ":" + port + "' because "+ e.getMessage()); } return retcred; } /** * Store a GSSCredential to a MyProxy server. This method stores a * GSSCredential in a specified MyProxy server:port using a given * username/passphrase with a specified lifetime (in seconds). If any * of the parameters are NULL/0/empty, then default values will be used * for that parameter. Since you will probably want to know what * actual values were used in this case, a Hashtable is returned with * the keys being the names of the parameters ("host", "port", * "username", "passphrase", and "lifetime") with the values being the * actual strings/ints used. This is particularly needed when you allow * for default username/passphrase since they are random strings which * you will need to give to another process (so it can access the * credential), creating a pseudo delegated credential. * @param host The MyProxy host name string for storing the credential. * @param port The MyProxy port number for storing the credential. * @param credential A GSSCredential to be stored in a MyProxy server. * @param username A username string for storing the credential. * @param passphrase A passphrase string for storing the credential. * @param lifetime A (hopefully) short lifetime (in seconds) for the * credential. * @return A Hashtable containing the actual values used for "server", * "port", "username", "passphrase", and "lifetime". */ public static Hashtable putCredToMyProxy( String host, int port, GSSCredential credential, String username, String passphrase, int lifetime) { Hashtable rethash = new Hashtable(); if (host.length() == 0) { host = MYPROXYHOST; logger.warn("No MyProxy host specified. Using '" + host + "'"); } rethash.put("host",new String(host)); if (port == 0) { port = MYPROXYPORT; logger.warn("No MyProxy port specified. Using '" + port + "'"); } rethash.put("port",new Integer(port)); if (username.length() == 0) { username = getRandomDNFromCred(credential); logger.warn("No MyProxy username specified. Using '"+username+"'"); } rethash.put("username",new String(username)); if (passphrase.length() == 0) { passphrase = randString(16); logger.warn("No MyProxy passphrase specified. Using '" + passphrase + "'"); } rethash.put("passphrase",new String(passphrase)); if (lifetime == 0) { lifetime = MYPROXYSHORT; logger.warn("No MyProxy lifetime specified. Using '" + lifetime + "seconds."); } rethash.put("lifetime",new Integer(lifetime)); System.out.println("username = " + (String)rethash.get("username")); System.out.println("passphrase = " + (String)rethash.get("passphrase")); try { MyProxy myProxyServer = new MyProxy(host,port); myProxyServer.put(credential,username,passphrase,lifetime); } catch (Exception e) { logger.error("Could not put proxy to '" + host + ":" + port + "' because "+ e.getMessage()); } return rethash; } /** * Store a GSSCredential to a MyProxy server using all default values. * This is a shortcut if you want to use the default MyProxy server and * port, a random username based on the credential's DN, a random * passphrase, and a default (short) lifetime. * @param credential A GSSCredential to be stored in a MyProxy server. * @return A Hashtable containing the actual values used for "server", * "port", "username", "passphrase", and "lifetime". */ public static Hashtable putCredToMyProxy(GSSCredential credential) { return putCredToMyProxy(MYPROXYHOST,MYPROXYPORT,credential, getRandomDNFromCred(credential), randString(16),MYPROXYSHORT); } /** * Given a GSSCredential, return its DN (distinguished name) appended * with a hyphen plus a string of 8 random characters. This is used * when generating a 'one time token' for storing a credential in a * MyProxy server. You store the credential with a random username and * passphrase (plus a short lifetime). You can then send the random * username and passphrase to another party who would use these values * to get that short-lived credential. * @param credential A GSSCredential to be stored in a MyProxy server. * @return A string consisting of the cred's DN appended with a hyphen * ('-') and a string of 8 random characters. */ public static String getRandomDNFromCred(GSSCredential credential) { String retDN = null; try { retDN = credential.getName() + "-" + randString(8); } catch (Exception e) { logger.error("getRandomDNFromCred: " + e.getMessage()); } return retDN; } /** * Returns a random character in the ASCII char range [lo,hi]. You * call this method with a characters (e.g. 'A') as parameters since * they will be converted to ASCII values automatically. The value * returned is the random ASCII character. * @param lo The 'minimum' ASCII value of the random char. * @param hi The 'maximum' ASCII value of the random char. * @return A random ASCII character in the range [lo,hi]. */ public static char randChar(char lo, char hi) { if (hi < lo) { char tmp = lo; lo = hi; hi = tmp; } int n = (int)hi - (int)lo + 1; int i = rn.nextInt() % n; if (i < 0) i = -i; return (char)(lo + i); } /** * Returns a random string containing len characters. The characters * are alphanumeric and some symbols, according to Java's ASCII table, * which can be found at http://mindprod.com/jgloss/ascii.html . * @param len The length of the new random string. * @return A random string of len alphanumeric characters. */ public static String randString(int len) { if (len == 0) // Len = 0 implies empty return string return ""; if (len < 0) // Make sure len is not negative len = -len; char c[] = new char[len]; for (int i = 0; i < len; i++) c[i] = randChar('0','z'); return new String(c); } }