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

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

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.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
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_HELP = "--help"; // help public static final String FLAG_VERBOSE = "-v"; // help 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 */ protected static void createConfig(String filename) throws IOException { XMLMap xmlMap = new XMLMap(); xmlMap.put(CONFIG_CLIENT_ID, getInput("enter client id")); 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. xmlMap.put(CONFIG_HOST, getInput("enter server 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")); } if (StringUtils.isTrivial(filename)) { filename = getInput("enter file name"); File f = new File(filename); 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!"); } } /** * Create a new instance of this client. The arguyments 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); PrivateKey privateKey = null; try { privateKey = KeyUtil.fromPKCS1PEM(rawKey); } catch (Throwable t) { JSONWebKey jwk = JSONWebKeyUtil.getJsonWebKey(rawKey); if (jwk != null) { privateKey = jwk.privateKey; } } if (privateKey == null) { say("Sorry: Could not determine private key. aborting..."); return null; } client.setPrivateKey(privateKey); return client; } 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; } Client client = newInstance(inputLine); if (inputLine.hasArg("-cli")) { client.cli(); } } 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("If you simply supply the " + FLAG_EDIT + " flag, you will be prompted to create a new configuration file."); } 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_SAT, 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_SAT, 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