From 73e55b8b7201af66b707189afa88179609e82e4c Mon Sep 17 00:00:00 2001 From: Martin Paljak Date: Tue, 17 Oct 2023 10:43:34 +0300 Subject: [PATCH] Generalize delete operation --- .../fidesmo/fdsm/CommandLineInterface.java | 10 +++-- tool/src/main/java/com/fidesmo/fdsm/Main.java | 44 ++++++++++++------- 2 files changed, 34 insertions(+), 20 deletions(-) diff --git a/tool/src/main/java/com/fidesmo/fdsm/CommandLineInterface.java b/tool/src/main/java/com/fidesmo/fdsm/CommandLineInterface.java index cf7549b..400a5a0 100644 --- a/tool/src/main/java/com/fidesmo/fdsm/CommandLineInterface.java +++ b/tool/src/main/java/com/fidesmo/fdsm/CommandLineInterface.java @@ -33,6 +33,7 @@ import java.net.MalformedURLException; import java.net.URL; import java.util.Arrays; +import java.util.List; abstract class CommandLineInterface { static OptionParser parser = new OptionParser(); @@ -55,7 +56,8 @@ abstract class CommandLineInterface { final static protected OptionSpec OPT_UPLOAD = parser.accepts("upload", "Upload CAP or recipe to Fidesmo").withRequiredArg().ofType(File.class).describedAs(".cap/.json file"); final static protected OptionSpec OPT_LIST_APPLETS = parser.accepts("list-applets", "List applets at Fidesmo"); - final static protected OptionSpec OPT_DELETE_APPLET = parser.accepts("delete-applet", "Deletes applet at Fidesmo").withRequiredArg().describedAs("ID"); + final static protected OptionSpec OPT_DELETE = parser.acceptsAll(List.of("delete", "delete-applet"), "Deletes applets and recipes at Fidesmo").withRequiredArg().describedAs("ID"); + final static protected OptionSpec OPT_CARD_APPS = parser.accepts("card-apps", "List apps on the card"); final static protected OptionSpec OPT_CARD_INFO = parser.accepts("card-info", "Show info about the card"); final static protected OptionSpec OPT_OFFLINE = parser.accepts("offline", "Do not connect to Fidesmo"); @@ -72,8 +74,8 @@ abstract class CommandLineInterface { final static protected OptionSpec OPT_UNINSTALL = parser.accepts("uninstall", "Uninstall CAP from card").withRequiredArg().describedAs("CAP file / AID"); final static protected OptionSpec OPT_APP_ID = parser.accepts("app-id", "Application identifier") - .availableIf(OPT_STORE_DATA, OPT_SECURE_APDU, OPT_UNINSTALL, OPT_INSTALL, OPT_UPLOAD, OPT_CLEANUP, OPT_LIST_APPLETS, OPT_FLUSH_APPLETS, OPT_DELETE_APPLET) - .withRequiredArg().describedAs("appId"); + .availableIf(OPT_STORE_DATA, OPT_SECURE_APDU, OPT_UNINSTALL, OPT_INSTALL, OPT_UPLOAD, OPT_CLEANUP, OPT_LIST_APPLETS, OPT_FLUSH_APPLETS, OPT_DELETE) + .withRequiredArg().describedAs("appId"); final static protected OptionSpec OPT_QA = parser.accepts("qa", "Run a QA support session").withOptionalArg().ofType(Integer.class).describedAs("QA number"); final static protected OptionSpec OPT_TIMEOUT = parser.accepts("timeout", "Timeout for services").withRequiredArg().ofType(Integer.class).describedAs("minutes"); @@ -157,7 +159,7 @@ public static boolean requiresCard() { public static boolean requiresAuthentication() { OptionSpec[] commands = new OptionSpec[]{ - OPT_INSTALL, OPT_UNINSTALL, OPT_STORE_DATA, OPT_SECURE_APDU, OPT_UPLOAD, OPT_DELETE_APPLET, OPT_FLUSH_APPLETS, OPT_CLEANUP, OPT_LIST_APPLETS, OPT_LIST_RECIPES + OPT_INSTALL, OPT_UNINSTALL, OPT_STORE_DATA, OPT_SECURE_APDU, OPT_UPLOAD, OPT_DELETE, OPT_FLUSH_APPLETS, OPT_CLEANUP, OPT_LIST_APPLETS, OPT_LIST_RECIPES }; return Arrays.stream(commands).anyMatch(a -> args.has(a)); } diff --git a/tool/src/main/java/com/fidesmo/fdsm/Main.java b/tool/src/main/java/com/fidesmo/fdsm/Main.java index 81d9d2a..e6dfaf4 100644 --- a/tool/src/main/java/com/fidesmo/fdsm/Main.java +++ b/tool/src/main/java/com/fidesmo/fdsm/Main.java @@ -30,6 +30,7 @@ import apdu4j.pcsc.TerminalManager; import apdu4j.pcsc.terminals.LoggingCardTerminal; import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.ObjectNode; import com.fidesmo.fdsm.FidesmoCard.ChipPlatform; import jnasmartcardio.Smartcardio; @@ -45,17 +46,18 @@ import java.io.FileInputStream; import java.io.IOException; import java.io.PrintStream; -import java.math.BigInteger; import java.net.URI; -import java.net.URISyntaxException; import java.nio.file.Files; import java.nio.file.NoSuchFileException; import java.nio.file.Path; import java.nio.file.Paths; import java.security.NoSuchAlgorithmException; import java.util.*; -import java.util.concurrent.*; +import java.util.concurrent.CancellationException; +import java.util.concurrent.RunnableFuture; +import java.util.concurrent.ThreadLocalRandom; import java.util.stream.Collectors; +import java.util.stream.StreamSupport; public class Main extends CommandLineInterface { static final String FDSM_SP = "8e5cdaae"; @@ -121,21 +123,31 @@ public static void main(String[] argv) { if (requiresAuthentication()) { AuthenticatedFidesmoApiClient client = getAuthenticatedClient(); - // Delete a specific applet - if (args.has(OPT_DELETE_APPLET)) { - String id = args.valueOf(OPT_DELETE_APPLET); - // DWIM: take ID or CAP file as argument - if (!id.toLowerCase().matches("[a-f0-9]{64}")) { - Path candidate = Paths.get(id); - if (Files.exists(candidate)) { - CAPFile tmp = CAPFile.fromBytes(Files.readAllBytes(candidate)); - id = HexUtils.bin2hex(tmp.getLoadFileDataHash("SHA-256")); - } else { - throw new IllegalArgumentException("Not a SHA-256: " + id); - } + // Delete a specific applet or recipe + if (args.has(OPT_DELETE)) { + String id = args.valueOf(OPT_DELETE); + + final URI toDelete; + if (id.toLowerCase().matches("[a-f0-9]{64}")) { + toDelete = client.getURI(FidesmoApiClient.CAPFILES_ID_URL, getAppId(), id); + } else if (Files.exists(Paths.get(id))) { + // DWIM: capfile + CAPFile tmp = CAPFile.fromBytes(Files.readAllBytes(Paths.get(id))); + id = HexUtils.bin2hex(tmp.getLoadFileDataHash("SHA-256")); + toDelete = client.getURI(FidesmoApiClient.CAPFILES_ID_URL, getAppId(), id); + } else { + // Maybe it is a recipe + ArrayNode recipes = (ArrayNode) client.rpc(client.getURI(FidesmoApiClient.RECIPE_SERVICES_URL, getAppId())); + String finalId = id; + id = StreamSupport.stream(recipes.spliterator(), false) + .map(r-> r.asText()) + .filter(r -> r.equals(finalId)) + .findFirst().orElseThrow( () -> new IllegalArgumentException("Not a recipe nor SHA-256: " + finalId)); + toDelete = client.getURI(FidesmoApiClient.SERVICE_RECIPE_URL, getAppId(), id); } + try { - client.delete(client.getURI(FidesmoApiClient.CAPFILES_ID_URL, getAppId(), id)); + client.delete(toDelete); System.out.println(id + " deleted."); } catch (HttpResponseException e) { if (e.getStatusCode() == 404) {