Skip to content

Commit

Permalink
Preparations for next version (#25)
Browse files Browse the repository at this point in the history
- Make use of CallbackHandlers and Logger from ServiceDeliverySession
- Allow to specify the output stream where FidesmoApiClient trace is written to
- Introduce timeouts and retries to service delivery (default is 10 minutes)
- Make FormHandler also implement CallbackHandler
- Introduce a special --qa argument and ability to fake a Fidesmo card
- Update findbugs plugin version
- Update version to today
  • Loading branch information
martinpaljak authored Mar 29, 2019
1 parent 4c7088b commit d60b574
Show file tree
Hide file tree
Showing 9 changed files with 242 additions and 115 deletions.
12 changes: 9 additions & 3 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@

<groupId>com.fidesmo</groupId>
<artifactId>fdsm</artifactId>
<version>19.02.22</version>
<version>19.03.29</version>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.version.windows>19.2.22.0</project.version.windows>
<project.version.windows>19.3.29.0</project.version.windows>
</properties>

<!-- Necessary metadata for Maven Central -->
Expand Down Expand Up @@ -81,6 +81,12 @@
<artifactId>commons-io</artifactId>
<version>2.6</version>
</dependency>
<!-- Logging -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.25</version>
</dependency>
<!-- Simple logging for the CLI -->
<dependency>
<groupId>org.slf4j</groupId>
Expand Down Expand Up @@ -301,7 +307,7 @@
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>findbugs-maven-plugin</artifactId>
<version>3.0.1</version>
<version>3.0.5</version>
<executions>
<execution>
<phase>verify</phase>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,17 +34,23 @@

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.PrintStream;
import java.net.URI;

public class AuthenticatedFidesmoApiClient extends FidesmoApiClient {
private AuthenticatedFidesmoApiClient(String appId, String appKey) {
super(appId, appKey);
private AuthenticatedFidesmoApiClient(String appId, String appKey, PrintStream apidump) {
super(appId, appKey, apidump);
}

public static AuthenticatedFidesmoApiClient getInstance(String appId, String appKey) throws IllegalArgumentException {
return new AuthenticatedFidesmoApiClient(appId, appKey);
return new AuthenticatedFidesmoApiClient(appId, appKey, null);
}

public static AuthenticatedFidesmoApiClient getInstance(String appId, String appKey, PrintStream apidump) throws IllegalArgumentException {
return new AuthenticatedFidesmoApiClient(appId, appKey, apidump);
}


public void put(URI uri, String json) throws IOException {
HttpPut put = new HttpPut(uri);
put.setEntity(new StringEntity(json, ContentType.APPLICATION_JSON));
Expand Down
30 changes: 29 additions & 1 deletion src/main/java/com/fidesmo/fdsm/CommandLineFormHandler.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@
*/
package com.fidesmo.fdsm;

import javax.security.auth.callback.*;
import java.io.Console;

import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
Expand Down Expand Up @@ -112,4 +112,32 @@ protected Optional<String> askForField(Field f) {
return Optional.ofNullable(console.readLine("> "));
}
}


@Override
public void handle(Callback[] callbacks) throws UnsupportedCallbackException {
for (Callback c : callbacks) {
if (c instanceof PasswordCallback) {
PasswordCallback pc = (PasswordCallback) c;
if (System.getenv().containsKey(pc.getPrompt())) {
pc.setPassword(System.getenv(pc.getPrompt()).toCharArray());
} else if (System.console() != null) {
pc.setPassword(System.console().readPassword("Enter %s: ", pc.getPrompt()));
} else
throw new UnsupportedCallbackException(c, "We can't get input for " + pc.getPrompt());
} else if (c instanceof TextOutputCallback) {
System.out.println(((TextOutputCallback) c).getMessage());
} else if (c instanceof TextInputCallback) {
TextInputCallback tc = (TextInputCallback) c;
if (predefinedFields.containsKey(tc.getPrompt())) {
tc.setText(predefinedFields.get(tc.getPrompt()));
} else if (System.console() != null) {
tc.setText(System.console().readLine("%s: ", tc.getPrompt()));
} else
throw new UnsupportedCallbackException(c, "We can't get input for " + tc.getPrompt());
} else {
throw new UnsupportedCallbackException(c, "Callback not supported");
}
}
}
}
10 changes: 8 additions & 2 deletions src/main/java/com/fidesmo/fdsm/CommandLineInterface.java
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,9 @@ abstract class CommandLineInterface {
final static String OPT_VERBOSE = "verbose";
final static String OPT_VERSION = "version";

final static String OPT_QA = "qa";
final static String OPT_TIMEOUT = "timeout";

protected static String appId = null;
protected static String appKey = null;

Expand Down Expand Up @@ -131,13 +134,16 @@ protected static OptionSet parseArguments(String[] argv) throws IOException {

parser.accepts(OPT_PARAMS, "Installation parameters").withRequiredArg().describedAs("HEX");
parser.accepts(OPT_CREATE, "Applet instance AID").withRequiredArg().describedAs("AID");
parser.accepts(OPT_UNINSTALL, "Uninstall CAP from card").withRequiredArg().ofType(File.class).describedAs("CAP file");
parser.accepts(OPT_UNINSTALL, "Uninstall CAP from card").withRequiredArg().describedAs("CAP file / AID");

parser.accepts(OPT_READER, "Specify reader to use").withRequiredArg().describedAs("reader");
parser.accepts(OPT_TRACE_API, "Trace Fidesmo API");
parser.accepts(OPT_TRACE_APDU, "Trace APDU-s");
parser.accepts(OPT_VERBOSE, "Be verbose");

parser.accepts(OPT_QA, "Run a QA support session").withOptionalArg().ofType(Integer.class).describedAs("QA number");
parser.accepts(OPT_TIMEOUT, "Timeout for services").withRequiredArg().ofType(Integer.class).describedAs("minutes");

parser.acceptsAll(Arrays.asList("V", OPT_VERSION), "Show version and check for updates");

// Parse arguments
Expand Down Expand Up @@ -172,7 +178,7 @@ protected static OptionSet parseArguments(String[] argv) throws IOException {

public static boolean requiresCard() {
String[] commands = new String[]{
OPT_INSTALL, OPT_UNINSTALL, OPT_STORE_DATA, OPT_SECURE_APDU, OPT_DELIVER, OPT_RUN, OPT_CARD_APPS, OPT_CARD_INFO
OPT_INSTALL, OPT_UNINSTALL, OPT_STORE_DATA, OPT_SECURE_APDU, OPT_DELIVER, OPT_RUN, OPT_CARD_APPS, OPT_CARD_INFO, OPT_QA
};
return Arrays.stream(commands).anyMatch(a -> args.has(a));
}
Expand Down
63 changes: 28 additions & 35 deletions src/main/java/com/fidesmo/fdsm/FidesmoApiClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,7 @@
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.*;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
Expand Down Expand Up @@ -74,8 +71,7 @@ public class FidesmoApiClient {

public static final String DEVICES_URL = "devices/%s?batchId=%s";


private boolean restdebug = false; // RPC debug
private PrintStream apidump;
private final CloseableHttpClient http;
private final HttpClientContext context = HttpClientContext.create();
protected final String appId;
Expand All @@ -93,10 +89,14 @@ public class FidesmoApiClient {
}

public FidesmoApiClient() {
this(null, null);
this(null, null, null);
}

public FidesmoApiClient(PrintStream apidump) {
this(null, null, apidump);
}

public FidesmoApiClient(String appId, String appKey) {
public FidesmoApiClient(String appId, String appKey, PrintStream apidump) {
if (appId != null && appKey != null) {
if (HexUtils.hex2bin(appId).length != 4)
throw new IllegalArgumentException("appId must be 4 bytes long (8 hex characters)");
Expand All @@ -118,24 +118,17 @@ public FidesmoApiClient(String appId, String appKey) {
this.http = HttpClientBuilder.create().useSystemProperties().setUserAgent("fdsm/" + getVersion()).build();
this.appId = appId;
this.appKey = appKey;
this.apidump = apidump;
}

public static boolean checkAppId(String appId) {
try {
return HexUtils.hex2bin(appId).length == 4;
} catch (IllegalArgumentException e) {
// Pass through
}
return false;
}

CloseableHttpResponse transmit(HttpRequestBase request) throws IOException {
public CloseableHttpResponse transmit(HttpRequestBase request) throws IOException {
if (appId != null && appKey != null) {
request.setHeader("app_id", appId);
request.setHeader("app_key", appKey);
}
if (restdebug) {
System.out.println(request.getMethod() + ": " + request.getURI());
// XXX: GET/POST get handled in rpc(), this is only for PUT
if (apidump != null && !(request.getMethod().equals("GET") || request.getMethod().equals("POST"))) {
apidump.println(request.getMethod() + ": " + request.getURI());
}

CloseableHttpResponse response = http.execute(request, context);
Expand All @@ -148,36 +141,35 @@ CloseableHttpResponse transmit(HttpRequestBase request) throws IOException {
return response;
}

JsonNode rpc(URI uri) throws IOException {
public JsonNode rpc(URI uri) throws IOException {
return rpc(uri, null);
}

JsonNode rpc(URI uri, JsonNode request) throws IOException {
HttpRequestBase req;
public JsonNode rpc(URI uri, JsonNode request) throws IOException {
final HttpRequestBase req;
if (request != null) {
HttpPost post = new HttpPost(uri);
post.setEntity(new ByteArrayEntity(mapper.writeValueAsBytes(request)));
req = post;
if (restdebug) {
System.out.println("POST: " + uri);
System.out.println(mapper.writer(printer).writeValueAsString(request));
}
} else {
HttpGet get = new HttpGet(uri);
req = get;
if (restdebug) {
System.out.println("GET: " + uri);
}
}

if (apidump != null) {
apidump.println(req.getMethod() + ": " + req.getURI());
if (req.getMethod().equals("POST"))
apidump.println(mapper.writer(printer).writeValueAsString(request));
}

req.setHeader("Accept", ContentType.APPLICATION_JSON.toString());
req.setHeader("Content-type", ContentType.APPLICATION_JSON.toString());

try (CloseableHttpResponse response = transmit(req)) {
JsonNode json = mapper.readTree(response.getEntity().getContent());
if (restdebug) {
System.out.println("RECV:");
System.out.println(mapper.writer(printer).writeValueAsString(json));
if (apidump != null) {
apidump.println("RECV:");
apidump.println(mapper.writer(printer).writeValueAsString(json));
}
return json;
}
Expand All @@ -187,12 +179,13 @@ public URI getURI(String template, String... args) {
try {
return new URI(String.format(apiurl + template, args));
} catch (URISyntaxException e) {
throw new IllegalArgumentException("Invalid stuff: " + e.getMessage(), e);
throw new IllegalArgumentException("Invalid url: " + e.getMessage(), e);
}
}

@Deprecated
public void setTrace(boolean b) {
restdebug = b;
apidump = b ? System.out : null;
}

public static String getVersion() {
Expand Down
23 changes: 17 additions & 6 deletions src/main/java/com/fidesmo/fdsm/FidesmoCard.java
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import com.payneteasy.tlv.BerTlvs;
import pro.javacard.AID;

import javax.security.auth.callback.UnsupportedCallbackException;
import javax.smartcardio.CardChannel;
import javax.smartcardio.CardException;
import javax.smartcardio.CommandAPDU;
Expand All @@ -41,6 +42,7 @@
public class FidesmoCard {
public enum ChipPlatform {

UNKNOWN(0),
JCOP242R1(1),
JCOP242R2(2),
JCOP3EMV(3),
Expand All @@ -53,12 +55,12 @@ public enum ChipPlatform {
this.v = v;
}

public static Optional<ChipPlatform> valueOf(int v) {
public static ChipPlatform valueOf(int v) {
for (ChipPlatform t : values()) {
if (t.v == v)
return Optional.of(t);
return t;
}
return Optional.empty();
return UNKNOWN;
}
}

Expand Down Expand Up @@ -107,7 +109,7 @@ public static ChipPlatform detectPlatform(byte[] cplc) {
if (Arrays.equals(e.getKey(), Arrays.copyOf(cplc, e.getKey().length)))
return e.getValue();
}
return null;
return ChipPlatform.UNKNOWN;
}

// Capabilities applet AID
Expand Down Expand Up @@ -137,11 +139,20 @@ public static FidesmoCard getInstance(CardChannel channel) throws CardException
return card;
}

public boolean deliverRecipe(AuthenticatedFidesmoApiClient client, FormHandler formHandler, String recipe) throws CardException, IOException {
public static FidesmoCard fakeInstance(CardChannel channel) throws CardException {
FidesmoCard card = new FidesmoCard(channel);
card.uid = new byte[7];
card.iin = HexUtils.hex2bin("31045199999906");
card.cin = new byte[7];
card.batchId = new byte[7];
return card;
}

public boolean deliverRecipe(AuthenticatedFidesmoApiClient client, FormHandler formHandler, String recipe) throws CardException, IOException, UnsupportedCallbackException {
return deliverRecipes(client, formHandler, Collections.singletonList(recipe));
}

public boolean deliverRecipes(AuthenticatedFidesmoApiClient client, FormHandler formHandler, List<String> recipes) throws CardException, IOException {
public boolean deliverRecipes(AuthenticatedFidesmoApiClient client, FormHandler formHandler, List<String> recipes) throws CardException, IOException, UnsupportedCallbackException {
ServiceDeliverySession session = ServiceDeliverySession.getInstance(this, client, formHandler);

for (String recipe : recipes) {
Expand Down
7 changes: 6 additions & 1 deletion src/main/java/com/fidesmo/fdsm/FormHandler.java
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
package com.fidesmo.fdsm;

import javax.security.auth.callback.CallbackHandler;
import java.util.List;
import java.util.Map;

public interface FormHandler {
// Form handler should in the future be renamed to UI handler.
// It fetches input from the user, based on a list of required fields.
// It also implements javax.security callback handler to provide for input-output
// from the mini-library in a "standard" way.
public interface FormHandler extends CallbackHandler {
Map<String, Field> processForm(List<Field> form);
}
Loading

0 comments on commit d60b574

Please sign in to comment.