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);
+ }
+ }
}