diff --git a/v2client/pom.xml b/v2client/pom.xml index 1434242..2ea18ba 100644 --- a/v2client/pom.xml +++ b/v2client/pom.xml @@ -46,6 +46,11 @@ commons-codec 1.4 + + commons-cli + commons-cli + 1.3.1 + diff --git a/v2client/src/main/java/com/yubico/client/v2/Cmd.java b/v2client/src/main/java/com/yubico/client/v2/Cmd.java index fc27ba8..732fd1e 100644 --- a/v2client/src/main/java/com/yubico/client/v2/Cmd.java +++ b/v2client/src/main/java/com/yubico/client/v2/Cmd.java @@ -1,16 +1,16 @@ -/* Copyright (c) 2011, Linus Widströmer. All rights reserved. +/* Copyright (c) 2016, Nicholas Sushkin. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. + notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following - disclaimer in the documentation and/or other materials provided - with the distribution. + notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, @@ -26,37 +26,174 @@ EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - Written by Linus Widströmer , January 2011. + Written by Nicholas Sushkin , August 2016. */ package com.yubico.client.v2; +import com.yubico.client.v2.exceptions.*; + +import org.apache.commons.cli.CommandLine; +import org.apache.commons.cli.CommandLineParser; +import org.apache.commons.cli.DefaultParser; +import org.apache.commons.cli.HelpFormatter; +import org.apache.commons.cli.Options; +import org.apache.commons.cli.Option; +import org.apache.commons.cli.ParseException; + public class Cmd { - public static void main (String args []) throws Exception - { - if (args.length != 3) { - System.err.println("\n*** Test your Yubikey against Yubico OTP validation server ***"); - System.err.println("\nUsage: java -jar client.jar Client_ID Client_key OTP"); - System.err.println("\nEg. java -jar client.jar 28 vvfucnlcrrnejlbuthlktguhclhvegbungldcrefbnku"); - System.err.println("\nTouch Yubikey to generate the OTP. Visit Yubico.com for more details."); - return; - } + static Options options = new Options() + .addOption("h", "help", false, "Display this help screen") + .addOption("V", "version", false, "Display version information") + .addOption("d", "debug", false, "Print debugging information") + .addOption(Option.builder("a").longOpt("apikey").hasArg().desc("API key for HMAC validation of request/response").argName("key").build()) + .addOption(Option.builder("u").longOpt("url").hasArg().desc("Yubikey validation service URL").argName("ykvalurl").build()); - String otp = args[2]; + static void printUsage() { + (new HelpFormatter()).printHelp("java -jar client.jar (--help|--version|--apikey ) [--debug] [--url ]* CLIENTID YUBIKEYOTP\n" + + "or: java -jar client.jar [--debug] [--url ]* CLIENTID key YUBIKEYOTP", + "Validate the YUBIKEYOTP one-time-password against the YubiCloud using CLIENTID as the client identifier\n\n", + options, + "\nExit status is 0 on success, 1 if there is a hard failure, 2 if the OTP was replayed, 3 for other soft OTP-related failures."); + } - YubicoClient yc = YubicoClient.getClient(Integer.parseInt(args[0]), args[1]); - VerificationResponse response = yc.verify(otp); + static String getVersion() { + return Cmd.class.getName() + " " + Version.version; + } - if(response!=null && response.getStatus() == ResponseStatus.OK) { - System.out.println("\n* OTP verified OK"); - } else { - System.out.println("\n* Failed to verify OTP"); + static void log(String description, String value) { + if (value != null) { + System.err.printf("%14s: %s\n", description, value); } + } + + public static void main (String args[]) throws Exception { + CommandLineParser parser = new DefaultParser(); + + try { + CommandLine cli = parser.parse(options, args, true); + + String[] leftoverArgs = cli.getArgs(); + + if ( cli.hasOption("h") ) { + printUsage(); + return; + } + + if ( cli.hasOption("V") ) { + System.out.println(getVersion()); + return; + } + + if ( ( leftoverArgs.length < 2 ) + || ( leftoverArgs.length > 3 ) + ) { + printUsage(); + return; + } + + boolean legacyUsage = (leftoverArgs.length == 3); + + if ( ! legacyUsage && ! cli.hasOption("a") ) { + System.err.println("Specify API key using --apikey key option"); + System.exit(1); + } - System.out.println("\n* Last response: " + response); - System.exit(0); + int clientId = -1; + try { + clientId = Integer.parseInt(leftoverArgs[0]); + } catch (NumberFormatException e) { + System.err.println("error: client identity must be a positive integer."); + System.exit(1); + } - } // End of main + if (clientId <= 0) { + System.err.println("error: client identity must be a positive integer."); + System.exit(1); + } + String apiKey = legacyUsage ? leftoverArgs[1] : cli.getOptionValue("a"); + String otp = legacyUsage ? leftoverArgs[2] : leftoverArgs[1]; + + YubicoClient yc = YubicoClient.getClient(clientId, apiKey); + + yc.setUserAgent(getVersion()); + + if ( cli.hasOption("u") ) { + yc.setWsapiUrls( cli.getOptionValues("u") ); + } + + if (otp.length() < 32) { + System.err.println("error: modhex encoded token must be at least 32 characters"); + System.exit(1); + } + + if ( cli.hasOption("d") ) { + System.err.printf("%s:\n", "Input"); + for ( String url: yc.getWsapiUrls() ) + { + System.err.printf("%14s: %s\n", "validation URL", url); + } + log("client id", Integer.toString(yc.getClientId())); + log("token", otp); + log("api key", yc.getKey()); + } + + try { + VerificationResponse response = yc.verify(otp); + ResponseStatus status = response.getStatus(); + + if ( cli.hasOption("d") ) { + System.err.printf("\n%s: %s\n", "Response", response); + + if (status != null) { + System.err.printf("%14s: (%d) %s\n", "status", status.ordinal(), status); + } + + log("otp", response.getOtp()); + log("nonce", response.getNonce()); + log("t", response.getT()); + log("timestamp", response.getTimestamp()); + log("sessioncounter", response.getSessioncounter()); + log("sessionuse", response.getSessionuse()); + log("sl", response.getSl()); + } + + if (legacyUsage) { + if (response.isOk()) { + System.out.println("\n* OTP verified OK"); + } else { + System.out.println("\n* Failed to verify OTP"); + } + } + + if (response.isOk()) { + System.exit(0); + } else if (status == ResponseStatus.REPLAYED_OTP) { + System.exit(2); + } else { + System.exit(3); + } + } + catch (YubicoVerificationException e) { + System.err.println("Validation Error: " + e); + System.exit(3); + } + catch (YubicoValidationFailure f) { + System.err.println("Validation Failure: " + f); + System.exit(3); + } + catch (IllegalArgumentException a) { + System.err.println("Incorrectly formatted OTP string: " + a); + System.exit(3); + } + + return; + } + catch (ParseException pe) { + System.err.println("Error parsing command line: " + pe.getMessage()); + System.exit(1); + } + } }