All Downloads are FREE. Search and download functionalities are using the official Maven repository.

edu.uiuc.ncsa.sas.webclient.Client Maven / Gradle / Ivy

There is a newer version: 5.4.3
Show newest version
package edu.uiuc.ncsa.sas.webclient;

import edu.uiuc.ncsa.sas.SASConstants;
import edu.uiuc.ncsa.sas.thing.action.*;
import edu.uiuc.ncsa.sas.thing.response.LogonResponse;
import edu.uiuc.ncsa.sas.thing.response.NewKeyResponse;
import edu.uiuc.ncsa.sas.thing.response.OutputResponse;
import edu.uiuc.ncsa.sas.thing.response.Response;
import edu.uiuc.ncsa.security.core.Identifier;
import edu.uiuc.ncsa.security.core.exceptions.GeneralException;
import edu.uiuc.ncsa.security.core.util.BasicIdentifier;
import edu.uiuc.ncsa.security.core.util.StringUtils;
import edu.uiuc.ncsa.security.servlet.ServiceClient;
import edu.uiuc.ncsa.security.storage.XMLMap;
import edu.uiuc.ncsa.security.util.cli.ExitException;
import edu.uiuc.ncsa.security.util.cli.InputLine;
import edu.uiuc.ncsa.security.util.crypto.DecryptUtils;
import edu.uiuc.ncsa.security.util.crypto.KeyUtil;
import edu.uiuc.ncsa.security.util.jwk.JSONWebKey;
import edu.uiuc.ncsa.security.util.jwk.JSONWebKeyUtil;
import edu.uiuc.ncsa.security.util.ssl.SSLConfiguration;
import net.sf.json.JSONArray;
import net.sf.json.JSONObject;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;

import javax.crypto.BadPaddingException;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import java.io.*;
import java.net.URI;
import java.security.*;
import java.security.spec.InvalidKeySpecException;
import java.util.List;
import java.util.StringTokenizer;
import java.util.UUID;
import java.util.Vector;

/**
 * 

Created by Jeff Gaynor
* on 8/18/22 at 3:14 PM */ public class Client extends ServiceClient implements SASConstants { public ResponseDeserializer getResponseDeserializer() { return responseDeserializer; } public void setResponseDeserializer(ResponseDeserializer responseDeserializer) { this.responseDeserializer = responseDeserializer; } ResponseDeserializer responseDeserializer = new ResponseDeserializer(); public Client(URI address, SSLConfiguration sslConfiguration) { super(address, sslConfiguration); } public Client(URI address) { super(address); } public static final String FLAG_EDIT = "-edit"; public static final String FLAG_CONFIG = "-cfg"; // name of config file public static final String FLAG_NEW = "-new"; // create a new configuration file public static final String FLAG_HELP = "--help"; // help public static final String FLAG_VERBOSE = "-v"; // verbosity public static final String FLAG_PRINT_PUBLIC_KEY = "-print_key"; // public static final String CONFIG_CLIENT_ID = "client_id"; public static final String CONFIG_PRIVATE_KEY = "private_key"; public static final String CONFIG_TR_FILE = "trust_root_path"; public static final String CONFIG_TR_PASSWORD = "trust_root_password"; public static final String CONFIG_TR_TYPE = "trust_root_type"; public static final String CONFIG_TR_DN = "trust_root_dn"; public static final String CONFIG_HOST = "host"; public static void say(Object x) { System.out.println(x); } /** * Read a single line * * @return * @throws IOException */ public static String readline() throws IOException { return getBufferedReader().readLine(); } public static String getInput(String prompt) { System.out.print(prompt + ">"); try { return getBufferedReader().readLine(); } catch (IOException e) { e.printStackTrace(); } return ""; } static BufferedReader bufferedReader = null; public static BufferedReader getBufferedReader() { if (bufferedReader == null) { bufferedReader = new BufferedReader(new InputStreamReader(System.in)); } return bufferedReader; } protected static XMLMap readConfig(String fileName) throws IOException { XMLMap map = new XMLMap(); map.fromXML(new FileInputStream(new File(fileName))); return map; } /* -cfg path.properties is config -type is type. -new */ /** * Create a new configuration file on the fly * * @param filename file to create (if null, user is prompted) * @return filename, either original or the one the user gave. * @throws IOException */ public static String createConfig(String filename, boolean isNew) throws IOException { if (StringUtils.isTrivial(filename)) { if (!isNew) { say("no file to edit."); return null; } filename = getInput("Enter the fully qualified file you wish to create:"); } File f = new File(filename); XMLMap xmlMap = new XMLMap(); if (f.exists()) { if (!f.canWrite()) { say("Sorry, but you do not have permissions to write to \"" + f.getAbsolutePath() + "\""); return null; } xmlMap.fromXML(new FileInputStream(f)); } if (xmlMap.isEmpty()) { newConfigFile(xmlMap); } else { editConfigFile(xmlMap); } if (f.exists()) { if (!"y".equals(getInput("overwrite " + f.getAbsolutePath() + "?"))) { say("save aborted. exiting..."); } } FileOutputStream fileOutputStream = new FileOutputStream(f); xmlMap.toXML(fileOutputStream); fileOutputStream.flush(); fileOutputStream.close(); say("done!"); return filename; // pass back what was created. } protected static void newConfigFile(XMLMap xmlMap) throws IOException { xmlMap.put(CONFIG_CLIENT_ID, getInput("enter client id")); boolean gotKeys = false; if ("y".equalsIgnoreCase(getInput("Create a new public private key pair?(y/n)"))) { KeyPair keyPair = KeyUtil.generateKeyPair(); String keyAsString = null; boolean bail = false; boolean keepLooping = true; while(keepLooping){ String type = getInput("Did you want JWK or PKCS8 format?(j/p/q)"); switch (type) { case "j": JSONWebKey jwk = JSONWebKeyUtil.create(keyPair); keyAsString = JSONWebKeyUtil.toJSON(jwk).toString(1); keepLooping = false; gotKeys = true; break; case "p": keyAsString = KeyUtil.toPKCS8PEM(keyPair.getPrivate()); keepLooping = false; gotKeys = true; case "q": bail = true; // don't do anything here. keepLooping = false; default: keepLooping = "y".equalsIgnoreCase(getInput("I didn't understand that. Try again?(y/n)")); } } if(keyAsString != null && !bail) { xmlMap.put(CONFIG_PRIVATE_KEY, keyAsString); // If a JWK, any leading blanks will cause JSON library to fail. say("key created."); } } if(!gotKeys){ if("y".equalsIgnoreCase(getInput("Did you want to paste in a key?(y/n)"))){ say("enter private key, PKCS 8 or a single JWK"); String x = multiLineInput("", CONFIG_PRIVATE_KEY); xmlMap.put(CONFIG_PRIVATE_KEY, x.trim()); // If a JWK, any leading blanks will cause JSON library to fail. }else{ say("no keys added"); } } xmlMap.put(CONFIG_HOST, getInput("enter host address")); if ("y".equals(getInput("enter ssl configuration (y/n)?"))) { xmlMap.put(CONFIG_TR_FILE, getInput("enter path to trust root file")); xmlMap.put(CONFIG_TR_PASSWORD, getInput("enter trust root password")); xmlMap.put(CONFIG_TR_TYPE, getInput("enter trust root store type, e.g. JKS")); xmlMap.put(CONFIG_TR_DN, getInput("enter trust root store cert DN")); } } protected static void editConfigFile(XMLMap xmlMap) throws IOException { updateItem(xmlMap, CONFIG_CLIENT_ID); updateItem(xmlMap, CONFIG_HOST); String oldJSON = ""; if (xmlMap.containsKey(CONFIG_PRIVATE_KEY)) { if ("y".equals(getInput("Update private key(y/n/)?"))) { oldJSON = xmlMap.getString(CONFIG_PRIVATE_KEY); } } else { if ("y".equals(getInput("Enter private key(y/n/)?"))) { say("enter private key, PKCS 8 or a single JWK"); String x = multiLineInput(oldJSON, CONFIG_PRIVATE_KEY); xmlMap.put(CONFIG_PRIVATE_KEY, x.trim()); // If a JWK, any leading blanks will cause JSON library to fail. } } updateItem(xmlMap, CONFIG_TR_FILE); updateItem(xmlMap, CONFIG_TR_PASSWORD); updateItem(xmlMap, CONFIG_TR_TYPE); updateItem(xmlMap, CONFIG_TR_DN); } protected static void updateItem(XMLMap xmlMap, String key) { String oldValue = xmlMap.getString(key); String prompt = key + " "; if (oldValue != null) { prompt = prompt + "\"" + oldValue + "\""; } prompt = prompt + ":"; String resp = getInput(prompt); if (StringUtils.isTrivial(resp)) { // keep old value } else { xmlMap.put(key, resp); } } /** * Create a new instance of this client. The arguments are *

    *
  • -cfg - path to the config file
  • *
  • -v - verbose. Be yacky (mostly for debugging the client itself
  • *
  • -edit - create a new config file if none exists.
  • *
* * @param inputLine * @return * @throws Throwable */ public static Client newInstance(InputLine inputLine) throws Throwable { boolean isVerbose = inputLine.hasArg(FLAG_VERBOSE); /* if (inputLine.hasArg(FLAG_EDIT)) { if (inputLine.hasArg(FLAG_CONFIG)) { File f = new File(inputLine.getNextArgFor(FLAG_CONFIG)); if (!f.exists()) { if ("y".equals(getInput("Create a new configuration?"))) { createConfig(f.getAbsolutePath()); return null; } } } }*/ if (!inputLine.hasArg(FLAG_CONFIG)) { say("No configuration. exiting..."); } XMLMap config; if (inputLine.hasArg(FLAG_CONFIG)) { try { config = readConfig(inputLine.getNextArgFor(FLAG_CONFIG)); } catch (Throwable t) { say("could not read config:"); if (isVerbose) { t.printStackTrace(); } return null; } } else { say("no configuration"); return null; } SSLConfiguration sslConfiguration = new SSLConfiguration(); sslConfiguration.setTrustRootPath(config.getString(CONFIG_TR_FILE)); sslConfiguration.setTrustRootPassword(config.getString(CONFIG_TR_PASSWORD)); sslConfiguration.setTrustRootType(config.getString(CONFIG_TR_TYPE)); sslConfiguration.setTrustRootCertDN(config.getString(CONFIG_TR_DN)); sslConfiguration.setUseDefaultTrustManager(false); Client client = new Client(URI.create(config.getString(CONFIG_HOST)), sslConfiguration); client.setConfig(config); String rawKey = config.getString(CONFIG_PRIVATE_KEY); KeyPair keyPair; PrivateKey privateKey = null; PublicKey publicKey = null; try { keyPair = KeyUtil.keyPairFromPKCS1(rawKey); privateKey = keyPair.getPrivate(); publicKey = keyPair.getPublic(); } catch (Throwable t) { JSONWebKey jwk = JSONWebKeyUtil.getJsonWebKey(rawKey); if (jwk != null) { privateKey = jwk.privateKey; publicKey = jwk.publicKey; } } if (privateKey == null) { say("Sorry: Could not determine private key. aborting..."); return null; } client.setPrivateKey(privateKey); client.setPublicKey(publicKey); return client; } public PublicKey getPublicKey() { return publicKey; } public void setPublicKey(PublicKey publicKey) { this.publicKey = publicKey; } PublicKey publicKey; public static void main(String[] args) throws Throwable { Vector vector = new Vector<>(); vector.add("dummy"); // Dummy zero-th arg. for (String arg : args) { vector.add(arg); } InputLine inputLine = new InputLine(vector); // now we have a bunch of utilities for this if (inputLine.hasArg(FLAG_HELP)) { showHelp(); return; } boolean newOrEdit = false; String filename = null; if (inputLine.hasArg(FLAG_EDIT)) { newOrEdit = true; if (inputLine.hasNextArgFor(FLAG_EDIT)) { filename = inputLine.getNextArgFor(FLAG_EDIT); } else { } if (filename == null || filename.startsWith("-")) { say("you must supply a file name to edit it."); return; } createConfig(filename, false); } if (inputLine.hasArg(FLAG_NEW)) { if (!newOrEdit) { newOrEdit = true; if (inputLine.hasNextArgFor(FLAG_NEW)) { filename = inputLine.getNextArgFor(FLAG_NEW); if (filename.startsWith("-")) { filename = null; // not a file name, discard } } filename = createConfig(filename, true); } } if (newOrEdit) { if (filename == null) { return; } if (getInput("Did you want to run this now?(y/n)").equals("n")) { return; } // so they want to run this. inputLine = new InputLine("dummy", FLAG_CONFIG, filename); } Client client = newInstance(inputLine); // get the configuration, confiure the client if(inputLine.hasArg(FLAG_PRINT_PUBLIC_KEY)){ switch (inputLine.getNextArgFor(FLAG_PRINT_PUBLIC_KEY).toLowerCase()){ case "jwk": KeyPair keyPair = new KeyPair(client.getPublicKey(), client.getPrivateKey()); JSONWebKey jwk = JSONWebKeyUtil.create(keyPair); say("public key in JWK format:\n" + JSONWebKeyUtil.toJSON(JSONWebKeyUtil.makePublic(jwk)).toString(1)); break; default: case "pkcs": say("public key in PKCS 5 format:\n" + KeyUtil.toX509PEM(client.getPublicKey())); } return; } client.cli(); // start it. } public XMLMap getConfig() { return config; } public void setConfig(XMLMap config) { this.config = config; } XMLMap config; /** * A very, very simple command line. This is normally not used for * anything except testing. When this class has main called, this is what you get. * * @throws Throwable */ protected void cli() throws Throwable { boolean doIt = true; while (doIt) { String lineIn = getInput("sas"); OutputResponse outputResponse = null; switch (lineIn) { case "/q": return; case "/logon": doLogon(BasicIdentifier.newID(getConfig().getString(CONFIG_CLIENT_ID))); say("login complete"); break; case "/logoff": doLogoff(); say("logged off.."); break; case "/help": case "--help": say("Testing CLI. Commands are"); say("/q - quit"); say("/logon - logon"); say("/logoff - logoff"); say("/help or --help - this message"); say("(text) - calls execute, passes text"); say("/invoke function arg0 arg1 ... - invokes function and passes space separated arguments as string"); say("/invoke function json_array ... - invokes function and passes the arguments as a JSON array"); break; case "/new_key": doNewKey(1024); break; default: if (lineIn.startsWith("/invoke ")) { lineIn = lineIn.substring(8); // ok, surgery. See if this ends with a JSON array String name = lineIn.substring(0, lineIn.indexOf(" ")); String tail = lineIn.substring(1 + lineIn.indexOf(" ")); try { JSONArray array = JSONArray.fromObject(tail.trim()); outputResponse = (OutputResponse) doInvoke(name, array); // second half parsed as an array. } catch (Throwable t) { outputResponse = (OutputResponse) doInvoke(lineIn); // Just a bunch of strings. } } else { if (lineIn.startsWith("/new_key")) { lineIn = lineIn.substring(8); int keySize = 1024; try { keySize = Integer.parseInt(lineIn); } catch (Throwable t) { say("Could not parse \"" + lineIn + "\" as an integer. Getting key with size " + keySize); } doNewKey(keySize); say("got new key"); } else { outputResponse = (OutputResponse) doExecute(lineIn); } } break; } if (outputResponse != null) { say(outputResponse.getContent()); } } } public Response doNewKey(int keySize) throws Throwable { NewKeyAction newKeyAction = new NewKeyAction(keySize); NewKeyResponse response = (NewKeyResponse) doPost(newKeyAction); sKey = response.getKey(); return response; } private static void showHelp() { say(Client.class.getName() + " " + FLAG_CONFIG + " config_file {" + FLAG_EDIT + "} {" + FLAG_HELP + "} {" + FLAG_VERBOSE + "}"); say(FLAG_CONFIG + " the name of an existing configuration file "); say(FLAG_EDIT + " edit existing file or update current "); say(FLAG_HELP + " display this help message "); say(FLAG_VERBOSE + " print more output about functioning of this. Mostly for debugging."); say(FLAG_PRINT_PUBLIC_KEY + " print the public key then exit. Arguments are jwk for JSON web key or pkcs for PKCS 5 format."); say("If you simply supply the " + FLAG_EDIT + " flag, you will be prompted to create a new configuration file."); } // /home/ncsa/dev/csd/config/sas/sas-test-config.xml UUID sessionID; public Response doLogon() throws NoSuchPaddingException, IllegalBlockSizeException, NoSuchAlgorithmException, InvalidKeySpecException, IOException, BadPaddingException, InvalidKeyException { return doLogon(BasicIdentifier.newID(getConfig().getString(CONFIG_CLIENT_ID))); } boolean loggedOn = false; public Response doLogon(Identifier identifier) throws NoSuchPaddingException, IllegalBlockSizeException, NoSuchAlgorithmException, InvalidKeySpecException, IOException, BadPaddingException, InvalidKeyException { if (loggedOn) { return null; } LogonAction logonAction = new LogonAction(); JSONObject top = new JSONObject(); top.put(KEYS_SAS, logonAction.serialize()); String raw = doPost(RSAEncrypt(top.toString()), identifier.toString(), ""); String jj = RSADecrypt(raw); List responseList = responseDeserializer.deserialize(jj); LogonResponse response = (LogonResponse) responseList.get(0); sessionID = response.getSessionID(); sKey = response.getsKey(); loggedOn = true; return response; } public Response doLogoff() throws Throwable { LogoffAction logoffAction = new LogoffAction(); loggedOn = false; // need better logic here --test logoff response for status etc. return doPost(logoffAction); } public Response doExecute(String contents) throws Throwable { ExecuteAction executeAction = new ExecuteAction(); executeAction.setArg(contents); return execute(executeAction); } /** * Call this for an arbitrary {@link Action}. It will call the SAS and return the * response. * * @param action * @return * @throws Throwable */ public Response execute(Action action) throws Throwable { return doPost(action); } public Response doInvoke(String name, JSONArray args) throws Throwable { InvokeAction invokeAction = new InvokeAction(); invokeAction.setName(name); invokeAction.setArgs(args); return execute(invokeAction); } public Response doInvoke(String x) throws Throwable { StringTokenizer stringTokenizer = new StringTokenizer(x); String name = null; JSONArray args = new JSONArray(); int i = 0; while (stringTokenizer.hasMoreTokens()) { if (0 == i++) { name = stringTokenizer.nextToken(); } args.add(stringTokenizer.nextToken()); } return doInvoke(name, args); } protected String sEncrypt(String contents) { return DecryptUtils.sEncrypt(getsKey(), contents); } protected String sDecrypt(String content64) { return DecryptUtils.sDecrypt(getsKey(), content64); } protected String RSAEncrypt(String contents) throws NoSuchAlgorithmException, InvalidKeySpecException, IOException, NoSuchPaddingException, IllegalBlockSizeException, BadPaddingException, InvalidKeyException { return DecryptUtils.encryptPrivate(getPrivateKey(), contents); } protected String RSADecrypt(String content64) throws NoSuchAlgorithmException, InvalidKeySpecException, IOException, NoSuchPaddingException, IllegalBlockSizeException, BadPaddingException, InvalidKeyException { return DecryptUtils.decryptPrivate(getPrivateKey(), content64); } public PrivateKey getPrivateKey() { return privateKey; } public void setPrivateKey(PrivateKey privateKey) { this.privateKey = privateKey; } PrivateKey privateKey; /** * The symmetric key. * * @return */ public byte[] getsKey() { return sKey; } public void setsKey(byte[] sKey) { this.sKey = sKey; } byte[] sKey; public String doPost(String content, String id, String secret) { HttpPost post = new HttpPost(host().toString()); try { post.setEntity(new StringEntity(content)); } catch (UnsupportedEncodingException e) { throw new GeneralException("error encoding form \"" + e.getMessage() + "\"", e); } return doRequest(post, id, secret); } public String doPost(String contents, boolean rsaEncrypt) throws Throwable { HttpPost post = new HttpPost(host().toString()); if (rsaEncrypt) { post.setEntity(new StringEntity(RSAEncrypt(contents))); } else { post.setEntity(new StringEntity(sEncrypt(contents))); } post.setHeader(HEADER_SESSION_ID, sessionID.toString()); return doRequest(post); } /** * Wraps the action in and does the post. It does symmetric key decryption. Assumption is a single response * * @param action * @return * @throws Throwable */ public Response doPost(Action action) throws Throwable { return doPost(action, false); } public Response doPost(Action action, boolean rsaEncrypt) throws Throwable { JSONObject top = new JSONObject(); top.put(KEYS_SAS, action.serialize()); String raw = doPost(top.toString(), rsaEncrypt); String jj; if (rsaEncrypt) { jj = RSADecrypt(raw); } else { jj = sDecrypt(raw); } List responseList = responseDeserializer.deserialize(jj); return responseList.get(0); } protected static String multiLineInput(String oldValue, String key) throws IOException { if (oldValue == null) { say("no current value for " + key); } else { say("current value for " + key + ":"); say(oldValue); } String EXIT_COMMAND = "/exit"; String CLEAR_BUFFER_COMMAND = "/c"; say("Enter new value. An empty line terminates input. Entering a line with " + EXIT_COMMAND + " will terminate input losing changes.\n " + "Hitting " + CLEAR_BUFFER_COMMAND + " will clear the contents of this."); String rawInput = ""; boolean redo = true; while (redo) { try { String inLine = readline(); while (!StringUtils.isTrivial(inLine)) { if (inLine.equals(EXIT_COMMAND)) { say("losing changes"); return null; // null means no changes } if (inLine.equals(CLEAR_BUFFER_COMMAND)) { return ""; // empty string means clear the current contents } rawInput = rawInput + inLine + "\n"; inLine = readline(); } return rawInput; } catch (ExitException x) { // ok, so user terminated input. This ends the whole thing return null; } } return null; // should never get here. } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy