Java tutorial
/** * The MIT License * Copyright (c) 2015 Estonian Information System Authority (RIA), Population Register Centre (VRK) * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package ee.ria.xroad.signer.console; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.security.cert.X509Certificate; import java.util.Arrays; import java.util.Calendar; import java.util.Date; import java.util.GregorianCalendar; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import akka.actor.ActorSystem; import asg.cliche.CLIException; import asg.cliche.Command; import asg.cliche.InputConverter; import asg.cliche.Param; import asg.cliche.Shell; import asg.cliche.ShellFactory; import com.typesafe.config.ConfigFactory; import org.apache.commons.cli.BasicParser; import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.CommandLineParser; import org.apache.commons.cli.Options; import org.apache.commons.lang.StringUtils; import ee.ria.xroad.common.AuditLogger; import ee.ria.xroad.common.SystemProperties; import ee.ria.xroad.common.SystemPropertiesLoader; import ee.ria.xroad.common.identifier.ClientId; import ee.ria.xroad.common.identifier.SecurityServerId; import ee.ria.xroad.common.util.CryptoUtils; import ee.ria.xroad.common.util.PasswordStore; import ee.ria.xroad.signer.protocol.SignerClient; import ee.ria.xroad.signer.protocol.dto.AuthKeyInfo; import ee.ria.xroad.signer.protocol.dto.CertificateInfo; import ee.ria.xroad.signer.protocol.dto.KeyInfo; import ee.ria.xroad.signer.protocol.dto.KeyUsageInfo; import ee.ria.xroad.signer.protocol.dto.MemberSigningInfo; import ee.ria.xroad.signer.protocol.dto.TokenInfo; import ee.ria.xroad.signer.protocol.message.*; import static ee.ria.xroad.common.AuditLogger.XROAD_USER; import static ee.ria.xroad.common.SystemProperties.CONF_FILE_SIGNER; import static ee.ria.xroad.common.util.CryptoUtils.*; import static ee.ria.xroad.signer.console.AuditLogEventsAndParams.*; import static ee.ria.xroad.signer.console.Utils.*; /** * Signer command line interface. */ public class SignerCLI { private static final int BENCHMARK_ITERATIONS = 10; static boolean verbose; static { SystemPropertiesLoader.create().withCommonAndLocal().with(CONF_FILE_SIGNER).load(); } /** * Shell input converters * @see <a href="http://cliche.sourceforge.net/">Cliche Manual</a> */ @SuppressWarnings({ "squid:S1873", "squid:S2386" }) public static final InputConverter[] CLI_INPUT_CONVERTERS = { new InputConverter() { @Override @SuppressWarnings("rawtypes") public Object convertInput(String original, Class toClass) throws Exception { if (toClass.equals(ClientId.class)) { return createClientId(original); } else { return null; } } }, }; /** * Lists all tokens. * @throws Exception if an error occurs */ @Command(description = "Lists all tokens") public void listTokens() throws Exception { List<TokenInfo> tokens = SignerClient.execute(new ListTokens()); tokens.forEach(t -> printTokenInfo(t, verbose)); } /** * Lists all keys on all tokens. * @throws Exception if an error occurs */ @Command(description = "Lists all keys on all tokens") public void listKeys() throws Exception { List<TokenInfo> tokens = SignerClient.execute(new ListTokens()); tokens.forEach(t -> { printTokenInfo(t, verbose); if (verbose) { System.out.println("Keys: "); } t.getKeyInfo().forEach(k -> printKeyInfo(k, verbose, "\t")); System.out.println(); }); } /** * Lists all certs on all keys on all tokens. * @throws Exception if an error occurs */ @Command(description = "Lists all certs on all keys on all tokens") public void listCerts() throws Exception { List<TokenInfo> tokens = SignerClient.execute(new ListTokens()); tokens.forEach(t -> { printTokenInfo(t, verbose); if (verbose) { System.out.println("Keys: "); } t.getKeyInfo().forEach(k -> { printKeyInfo(k, verbose, "\t"); if (verbose) { System.out.println("\tCerts: "); } printCertInfo(k, verbose, "\t\t"); }); System.out.println(); }); } /** * Sets token friendly name. * @param tokenId token id * @param friendlyName friendly name * @throws Exception if an error occurs */ @Command(description = "Sets token friendly name") public void setTokenFriendlyName(@Param(name = "tokenId", description = "Token ID") String tokenId, @Param(name = "friendlyName", description = "Friendly name") String friendlyName) throws Exception { Map<String, Object> logData = new LinkedHashMap<>(); logData.put(TOKEN_ID_PARAM, tokenId); logData.put(TOKEN_FRIENDLY_NAME_PARAM, friendlyName); try { SignerClient.execute(new SetTokenFriendlyName(tokenId, friendlyName)); AuditLogger.log(SET_A_FRIENDLY_NAME_TO_THE_TOKEN_EVENT, XROAD_USER, logData); } catch (Exception e) { AuditLogger.log(SET_A_FRIENDLY_NAME_TO_THE_TOKEN_EVENT, XROAD_USER, e.getMessage(), logData); throw e; } } /** * Sets key friendly name. * @param keyId key id * @param friendlyName friendly name * @throws Exception if an error occurs */ @Command(description = "Sets key friendly name") public void setKeyFriendlyName(@Param(name = "keyId", description = "Key ID") String keyId, @Param(name = "friendlyName", description = "Friendly name") String friendlyName) throws Exception { Map<String, Object> logData = new LinkedHashMap<>(); logData.put(KEY_ID_PARAM, keyId); logData.put(KEY_FRIENDLY_NAME_PARAM, friendlyName); try { SignerClient.execute(new SetKeyFriendlyName(keyId, friendlyName)); AuditLogger.log(SET_A_FRIENDLY_NAME_TO_THE_KEY_EVENT, XROAD_USER, logData); } catch (Exception e) { AuditLogger.log(SET_A_FRIENDLY_NAME_TO_THE_KEY_EVENT, XROAD_USER, e.getMessage(), logData); throw e; } } /** * Returns key ID for certificate hash. * @param certHash certificate hash * @throws Exception if an error occurs */ @Command(description = "Returns key ID for certificate hash") public void getKeyIdForCertHash(@Param(name = "certHash", description = "Certificare hash") String certHash) throws Exception { GetKeyIdForCertHashResponse response = SignerClient.execute(new GetKeyIdForCertHash(certHash)); System.out.println(response.getKeyId()); } /** * Returns all certificates of a member. * @param memberId member if * @throws Exception if an error occurs */ @Command(description = "Returns all certificates of a member") public void getMemberCerts(@Param(name = "memberId", description = "Member identifier") ClientId memberId) throws Exception { GetMemberCertsResponse response = SignerClient.execute(new GetMemberCerts(memberId)); System.out.println("Certs of member " + memberId + ":"); for (CertificateInfo cert : response.getCerts()) { System.out.println("\tId:\t" + cert.getId()); System.out.println("\t\tStatus:\t" + cert.getStatus()); System.out.println("\t\tActive:\t" + cert.isActive()); } } /** * Activates a certificate. * @param certId certificate id * @throws Exception if an error occurs */ @Command(description = "Activates a certificate") public void activateCertificate(@Param(name = "certId", description = "Certificate ID") String certId) throws Exception { Map<String, Object> logData = new LinkedHashMap<>(); logData.put(CERT_ID_PARAM, certId); try { SignerClient.execute(new ActivateCert(certId, true)); AuditLogger.log(ACTIVATE_THE_CERTIFICATE_EVENT, XROAD_USER, logData); } catch (Exception e) { AuditLogger.log(ACTIVATE_THE_CERTIFICATE_EVENT, XROAD_USER, e.getMessage(), logData); throw e; } } /** * Deactivates a certificate. * @param certId certificate id * @throws Exception if an error occurs */ @Command(description = "Deactivates a certificate") public void deactivateCertificate(@Param(name = "certId", description = "Certificate ID") String certId) throws Exception { Map<String, Object> logData = new LinkedHashMap<>(); logData.put(CERT_ID_PARAM, certId); try { SignerClient.execute(new ActivateCert(certId, false)); AuditLogger.log(DEACTIVATE_THE_CERTIFICATE_EVENT, XROAD_USER, logData); } catch (Exception e) { AuditLogger.log(DEACTIVATE_THE_CERTIFICATE_EVENT, XROAD_USER, e.getMessage(), logData); throw e; } } /** * Deletes a key. * @param keyId key id * @throws Exception if an error occurs */ @Command(description = "Deletes a key") public void deleteKey(@Param(name = "keyId", description = "Key ID") String keyId) throws Exception { Map<String, Object> logData = new LinkedHashMap<>(); logData.put(KEY_ID_PARAM, keyId); try { SignerClient.execute(new DeleteKey(keyId, true)); AuditLogger.log(DELETE_THE_KEY_EVENT, XROAD_USER, logData); } catch (Exception e) { AuditLogger.log(DELETE_THE_KEY_EVENT, XROAD_USER, e.getMessage(), logData); throw e; } } /** * Deletes a certificate. * @param certId certificate id * @throws Exception if an error occurs */ @Command(description = "Deletes a certificate") public void deleteCertificate(@Param(name = "certId", description = "Certificate ID") String certId) throws Exception { Map<String, Object> logData = new LinkedHashMap<>(); logData.put(CERT_ID_PARAM, certId); try { SignerClient.execute(new DeleteCert(certId)); AuditLogger.log(DELETE_THE_CERT_EVENT, XROAD_USER, logData); } catch (Exception e) { AuditLogger.log(DELETE_THE_CERT_EVENT, XROAD_USER, e.getMessage(), logData); throw e; } } /** * Deletes a certificate request. * @param certReqId certificate request id * @throws Exception if an error occurs */ @Command(description = "Deletes a certificate request") public void deleteCertificateRequest( @Param(name = "certReqId", description = "Certificate request ID") String certReqId) throws Exception { Map<String, Object> logData = new LinkedHashMap<>(); logData.put(CERT_REQUEST_ID_PARAM, certReqId); try { SignerClient.execute(new DeleteCertRequest(certReqId)); AuditLogger.log(DELETE_THE_CERT_REQUEST_EVENT, XROAD_USER, logData); } catch (Exception e) { AuditLogger.log(DELETE_THE_CERT_REQUEST_EVENT, XROAD_USER, e.getMessage(), logData); throw e; } } /** * Returns suitable authentication key for security server. * @param clientId client id * @param serverCode server code * @throws Exception if an error occurs */ @Command(description = "Returns suitable authentication key for security server") public void getAuthenticationKey(@Param(name = "clientId", description = "Member identifier") ClientId clientId, @Param(name = "serverCode", description = "Security server code") String serverCode) throws Exception { SecurityServerId serverId = SecurityServerId.create(clientId, serverCode); AuthKeyInfo authKey = SignerClient.execute(new GetAuthKey(serverId)); System.out.println("Auth key:"); System.out.println("\tAlias:\t" + authKey.getAlias()); System.out.println("\tKeyStore:\t" + authKey.getKeyStoreFileName()); System.out.println("\tCert: " + authKey.getCert()); } /** * Returns signing info for member. * @param clientId client id * @throws Exception if an error occurs */ @Command(description = "Returns signing info for member") public void getMemberSigningInfo(@Param(name = "clientId", description = "Member identifier") ClientId clientId) throws Exception { MemberSigningInfo response = SignerClient.execute(new GetMemberSigningInfo(clientId)); System.out.println("Signing info for member " + clientId + ":"); System.out.println("\tKey id: " + response.getKeyId()); System.out.println("\tCert: " + response.getCert()); System.out.println("\tSign mechanism: " + response.getSignMechanismName()); } /** * Imports a certificate. * @param file file * @param clientId client id * @throws Exception if an error occurs */ @Command(description = "Imports a certificate") public void importCertificate(@Param(name = "file", description = "Certificate file (PEM)") String file, @Param(name = "clientId", description = "Member identifier") ClientId clientId) throws Exception { Map<String, Object> logData = new LinkedHashMap<>(); logData.put(CERT_FILE_NAME_PARAM, file); logData.put(CLIENT_IDENTIFIER_PARAM, clientId); try { byte[] certBytes = fileToBytes(file); ImportCertResponse response = SignerClient .execute(new ImportCert(certBytes, CertificateInfo.STATUS_REGISTERED, clientId)); logData.put(KEY_ID_PARAM, response.getKeyId()); AuditLogger.log(IMPORT_A_CERTIFICATE_FROM_THE_FILE, XROAD_USER, logData); System.out.println(response.getKeyId()); } catch (Exception e) { AuditLogger.log(IMPORT_A_CERTIFICATE_FROM_THE_FILE, XROAD_USER, e.getMessage(), logData); System.out.println("ERROR: " + e); } } /** * Log in token. * @param tokenId token id * @throws Exception if an error occurs */ @Command(description = "Log in token", abbrev = "li") public void loginToken(@Param(name = "tokenId", description = "Token ID") String tokenId) throws Exception { char[] pin = System.console().readPassword("PIN: "); Map<String, Object> logData = new LinkedHashMap<>(); logData.put(TOKEN_ID_PARAM, tokenId); try { PasswordStore.storePassword(tokenId, pin); SignerClient.execute(new ActivateToken(tokenId, true)); AuditLogger.log(LOG_INTO_THE_TOKEN, XROAD_USER, logData); } catch (Exception e) { AuditLogger.log(LOG_INTO_THE_TOKEN, XROAD_USER, e.getMessage(), logData); throw e; } } /** * Log out token. * @param tokenId token id * @throws Exception if an error occurs */ @Command(description = "Log out token", abbrev = "lo") public void logoutToken(@Param(name = "tokenId", description = "Token ID") String tokenId) throws Exception { Map<String, Object> logData = new LinkedHashMap<>(); logData.put(TOKEN_ID_PARAM, tokenId); try { PasswordStore.storePassword(tokenId, null); SignerClient.execute(new ActivateToken(tokenId, false)); AuditLogger.log(LOGOUT_FROM_THE_TOKEN_EVENT, XROAD_USER, logData); } catch (Exception e) { AuditLogger.log(LOGOUT_FROM_THE_TOKEN_EVENT, XROAD_USER, e.getMessage(), logData); throw e; } } /** * Initialize software token * @throws Exception if an error occurs */ @Command(description = "Initialize software token") public void initSoftwareToken() throws Exception { char[] pin = System.console().readPassword("PIN: "); char[] pin2 = System.console().readPassword("retype PIN: "); if (!Arrays.equals(pin, pin2)) { System.out.println("ERROR: PINs do not match"); return; } try { SignerClient.execute(new InitSoftwareToken(pin)); AuditLogger.log(INITIALIZE_THE_SOFTWARE_TOKEN_EVENT, XROAD_USER, null); } catch (Exception e) { AuditLogger.log(INITIALIZE_THE_SOFTWARE_TOKEN_EVENT, XROAD_USER, e.getMessage(), null); throw e; } } /** * Sign some data * @param keyId the key id * @param data the data * @throws Exception if an error occurs */ @Command(description = "Sign some data") public void sign(@Param(name = "keyId", description = "Key ID") String keyId, @Param(name = "data", description = "Data to sign (<data1> <data2> ...)") String... data) throws Exception { String digestAlgoId = CryptoUtils.SHA512_ID; GetSignMechanismResponse mechanismResponse = SignerClient.execute(new GetSignMechanism(keyId)); String signAlgoId = CryptoUtils.getSignatureAlgorithmId(digestAlgoId, mechanismResponse.getSignMechanismName()); for (String d : data) { byte[] digest = calculateDigest(digestAlgoId, d.getBytes(StandardCharsets.UTF_8)); SignResponse response = SignerClient.execute(new Sign(keyId, signAlgoId, digest)); System.out.println("Signature: " + Arrays.toString(response.getSignature())); } } /** * Sign a file. * @param keyId the key id * @param fileName the file name * @throws Exception if an error occurs */ @Command(description = "Sign a file") public void signFile(@Param(name = "keyId", description = "Key ID") String keyId, @Param(name = "fileName", description = "File name") String fileName) throws Exception { String digestAlgoId = CryptoUtils.SHA512_ID; GetSignMechanismResponse mechanismResponse = SignerClient.execute(new GetSignMechanism(keyId)); String signAlgoId = CryptoUtils.getSignatureAlgorithmId(digestAlgoId, mechanismResponse.getSignMechanismName()); byte[] digest = calculateDigest(digestAlgoId, fileToBytes(fileName)); SignResponse response = SignerClient.execute(new Sign(keyId, signAlgoId, digest)); System.out.println("Signature: " + Arrays.toString(response.getSignature())); } /** * Benchmark signing. * @param keyId key id * @throws Exception if an error occurs */ @Command(description = "Benchmark signing") public void signBenchmark(@Param(name = "keyId", description = "Key ID") String keyId) throws Exception { String data = "Hello world!"; String digestAlgoId = CryptoUtils.SHA512_ID; GetSignMechanismResponse mechanismResponse = SignerClient.execute(new GetSignMechanism(keyId)); String signAlgoId = CryptoUtils.getSignatureAlgorithmId(digestAlgoId, mechanismResponse.getSignMechanismName()); byte[] digest = calculateDigest(digestAlgoId, data.getBytes(StandardCharsets.UTF_8)); long startTime = System.currentTimeMillis(); for (int i = 0; i < BENCHMARK_ITERATIONS; i++) { SignerClient.execute(new Sign(keyId, signAlgoId, digest)); } long duration = System.currentTimeMillis() - startTime; System.out.println("Signed " + BENCHMARK_ITERATIONS + " times in " + duration + " milliseconds"); } /** * Generate key on token. * @param tokenId token id * @param label label * @throws Exception if an error occurs */ @Command(description = "Generate key on token") public void generateKey(@Param(name = "tokenId", description = "Token ID") String tokenId, @Param(name = "label", description = "Key label") String label) throws Exception { Map<String, Object> logData = new LinkedHashMap<>(); logData.put(TOKEN_ID_PARAM, tokenId); logData.put(KEY_LABEL_PARAM, label); KeyInfo response; try { response = SignerClient.execute(new GenerateKey(tokenId, label)); logData.put(KEY_ID_PARAM, response.getId()); AuditLogger.log(GENERATE_A_KEY_ON_THE_TOKEN_EVENT, XROAD_USER, logData); } catch (Exception e) { AuditLogger.log(GENERATE_A_KEY_ON_THE_TOKEN_EVENT, XROAD_USER, e.getMessage(), logData); throw e; } System.out.println(response.getId()); } /** * Generate certificate request. * @param keyId key id * @param memberId member id * @param usage usage * @param subjectName subject name * @param format request format * @throws Exception if an error occurs */ @Command(description = "Generate certificate request") public void generateCertRequest(@Param(name = "keyId", description = "Key ID") String keyId, @Param(name = "memberId", description = "Member identifier") ClientId memberId, @Param(name = "usage", description = "Key usage (a - auth, s - sign)") String usage, @Param(name = "subjectName", description = "Subject name") String subjectName, @Param(name = "format", description = "Format of request (der/pem)") String format) throws Exception { KeyUsageInfo keyUsage = "a".equals(usage) ? KeyUsageInfo.AUTHENTICATION : KeyUsageInfo.SIGNING; GenerateCertRequest.RequestFormat requestFormat = format.equalsIgnoreCase("der") ? GenerateCertRequest.RequestFormat.DER : GenerateCertRequest.RequestFormat.PEM; Map<String, Object> logData = new LinkedHashMap<>(); logData.put(KEY_ID_PARAM, keyId); logData.put(CLIENT_IDENTIFIER_PARAM, memberId); logData.put(KEY_USAGE_PARAM, keyUsage.name()); logData.put(SUBJECT_NAME_PARAM, subjectName); logData.put(CSR_FORMAT_PARAM, requestFormat.name()); GenerateCertRequestResponse response; try { GenerateCertRequest request = new GenerateCertRequest(keyId, memberId, keyUsage, subjectName, requestFormat); response = SignerClient.execute(request); AuditLogger.log(GENERATE_A_CERT_REQUEST_EVENT, XROAD_USER, logData); } catch (Exception e) { AuditLogger.log(GENERATE_A_CERT_REQUEST_EVENT, XROAD_USER, e.getMessage(), logData); throw e; } bytesToFile(keyId + ".csr", response.getCertRequest()); } /** * Create dummy public key certificate. * @param keyId key id * @param cn common name * @throws Exception if an error occurs */ @Command(description = "Create dummy public key certificate") public void dummyCert(@Param(name = "keyId", description = "Key ID") String keyId, @Param(name = "cn", description = "Common name") String cn) throws Exception { Calendar cal = GregorianCalendar.getInstance(); cal.add(Calendar.YEAR, -1); Date notBefore = cal.getTime(); cal.add(Calendar.YEAR, 2); Date notAfter = cal.getTime(); ClientId memberId = ClientId.create("FOO", "BAR", "BAZ"); GenerateSelfSignedCert request = new GenerateSelfSignedCert(keyId, cn, notBefore, notAfter, KeyUsageInfo.SIGNING, memberId); GenerateSelfSignedCertResponse response = SignerClient.execute(request); X509Certificate cert = readCertificate(response.getCertificateBytes()); System.out.println("Certificate base64:"); System.out.println(encodeBase64(cert.getEncoded())); bytesToFile(keyId + ".crt", cert.getEncoded()); base64ToFile(keyId + ".crt.b64", cert.getEncoded()); } /** * Check if batch signing is available on token. * @param keyId key id * @throws Exception if an error occurs */ @Command(description = "Check if batch signing is available on token") public void batchSigningEnabled(@Param(name = "keyId", description = "Key ID") String keyId) throws Exception { Boolean enabled = SignerClient.execute(new GetTokenBatchSigningEnabled(keyId)); if (enabled) { System.out.println("Batch signing is enabled"); } else { System.out.println("Batch signing is NOT enabled"); } } /** * Show certificate. * @param certId certificate id * @throws Exception if an error occurs */ @Command(description = "Show certificate") public void showCertificate(@Param(name = "certId", description = "Certificate ID") String certId) throws Exception { List<TokenInfo> tokens = SignerClient.execute(new ListTokens()); for (TokenInfo token : tokens) { for (KeyInfo key : token.getKeyInfo()) { for (CertificateInfo cert : key.getCerts()) { if (certId.equals(cert.getId())) { X509Certificate x509 = readCertificate(cert.getCertificateBytes()); System.out.println(x509); return; } } } } System.out.println("Certificate " + certId + " not found"); } // ------------------------------------------------------------------------ /** * Program entry point. * @param args arguments * @throws Exception if an error occurs */ public static void main(String[] args) throws Exception { CommandLine cmd = getCommandLine(args); if (cmd.hasOption("verbose")) { verbose = true; } if (cmd.hasOption("help")) { processCommandAndExit("?list"); return; } ActorSystem actorSystem = ActorSystem.create("SignerConsole", ConfigFactory.load().getConfig("signer-console").withFallback(ConfigFactory.load())); try { SignerClient.init(actorSystem); String[] arguments = cmd.getArgs(); if (arguments.length > 0) { processCommandAndExit(StringUtils.join(arguments, " ")); } else { startCommandLoop(); } } finally { actorSystem.shutdown(); } } private static void startCommandLoop() throws IOException { String prompt = "signer@" + SystemProperties.getSignerPort(); StringBuilder description = new StringBuilder("Enter '?list' to get list of available commands\n"); description.append("Enter '?help <command>' to get command description\n"); description.append("\nNOTE: Member identifier is entered as \"<INSTANCE> <CLASS> <CODE>\" (in quotes)\n"); getShell(prompt, description.toString()).commandLoop(); } private static void processCommandAndExit(String command) throws CLIException { getShell("", "").processLine(command); } private static CommandLine getCommandLine(String[] args) throws Exception { CommandLineParser parser = new BasicParser(); Options options = new Options(); options.addOption("h", "help", false, "shows available commands"); options.addOption("v", "verbose", false, "more detailed output"); return parser.parse(options, args); } private static Shell getShell(String prompt, String description) { return ShellFactory.createConsoleShell(prompt, description, new SignerCLI()); } }