diff --git a/cli/README.md b/cli/README.md index 9d9ca485e698761807456e1560309695298408cf..7c506fe50f1cb9931fde59eb285dfa8e75fa8ef1 100644 --- a/cli/README.md +++ b/cli/README.md @@ -5,24 +5,21 @@ The sdk provides a commandline-client as a runnable .jar, to be able to use the #### Setup & Build 1. Build project root wih ``./mvnw clean package`` -2. Go to client/target and find a runnable jar ``fit-connect-client.jar`` +2. Go to client/target and find a runnable jar ``fit-connect-cli.jar`` 3. Provide [config yaml](../config.yml): - 1. set environment variable ``FIT_CONNECT_CONFIG``: - 1. Linux/MacOS: ``export FIT_CONNECT_CONFIG=path/to/config.yml`` - 2. Windows: ``set FIT_CONNECT_CONFIG=C:\Path\To\config.yml`` - 2. Initialize client via ApplicationConfigLoader: - ````java - var config = ApplicationConfigLoader.loadConfig("absolute/path/to/config.yml"); - var senderClient = ClientFactory.senderClient(config); - ```` - 3. put ``config.yml`` in same path as the .jar-file -5. run client with ``java -jarfit-connect-client.jar [COMMAND] [OPTIONS]`` + * a) set environment variable **FIT_CONNECT_CONFIG** or + * b) put ``config.yml`` in same path as the .jar-file +4. run client with ``java -jarfit-connect-cli.jar [COMMAND] [OPTIONS]`` + +> **Setting Env-Variables:** <br> + Linux/MacOS: ``export FIT_CONNECT_CONFIG=path/to/config.yml`` <br> + Windows: ``set FIT_CONNECT_CONFIG=C:\Path\To\config.yml`` #### SEND Single Submission Example The send command submits a new submission to a destination. Apart from optional attachments, all options are mandatory. ````sh -java -jar fit-connect-client.jar send +java -jar fit-connect-cli.jar send --destinationId=1b7d1a24-a6c8-4050-bb71-ae3749ec432f --serviceName=Test --leikaKey=urn:de:fim:leika:leistung:99400048079000 @@ -35,7 +32,7 @@ java -jar fit-connect-client.jar send #### LIST Submissions Example The list command lists all submissionIds for a given destinationID. ````sh -java -jar fit-connect-client.jar list --destinationID=1b7d1a24-a6c8-4050-bb71-ae3749ec432f +java -jar fit-connect-cli.jar list --destinationID=1b7d1a24-a6c8-4050-bb71-ae3749ec432f ```` #### GET Single Submission Example The get command loads a submission by `submissionId` and stores data and attachments in the given target location. If no target is @@ -43,13 +40,13 @@ set, the cmd-client saves the data into in a folder named by the `submissionId` jar. ````sh -java -jar fit-connect-client.jar get --submissionID=cc9b9b3c-d4b1-4ac7-a70b-e7ca76e88608 --target=Users/submissions/data +java -jar fit-connect-cli.jar get --submissionID=cc9b9b3c-d4b1-4ac7-a70b-e7ca76e88608 --target=Users/submissions/data ```` #### Batch Mode To send multiple submission with a single command, the client can be used in batch mode: ````sh -java -jar fit-connect-client.jar batch --data=batch_data.csv +java -jar fit-connect-cli.jar batch --data=batch_data.csv ```` Currently, the import of CSV is supported. Follow the schema below, setting up your data: @@ -60,11 +57,26 @@ destinationId, serviceName, leikaKey, data, dataType, attachments 1b7d1a24-a6c8-4050-bb71-ae3749ec432d, Test2, "urn:de:fim:leika:leistung:99400048079000", /path/to/data/data2.xml, "XML", "path/to/attachment/report.pdf" ```` > Windows paths: escape windows paths with `\\`, e.g. `C:\\path\\to\\file\\file.txt` ! -> + +#### Generate JWK Test Keys +The CLI can generate public and private keys for testing purposes for both encryption and signing. +If no ``outDir`` ist specified, the cli will create a temporary folder that is logged in the console output. + +````sh +java -jar fit-connect-cli.jar keygen --outDir=C:\temp +```` +``` +[main] INFO d.f.fitconnect.cli.CommandExecutor Generating JWKs ... +[main] INFO d.f.fitconnect.cli.keygen.KeyWriter Writing keys to directory C:\temp +[main] INFO d.f.fitconnect.cli.keygen.KeyWriter Wrote Encryption Public Key (key_use=wrapKey) as publicKey_encryption.json +[main] INFO d.f.fitconnect.cli.keygen.KeyWriter Wrote Decryption Private Key (key_use=unwrapKey) as privateKey_decryption.json +[main] INFO d.f.fitconnect.cli.keygen.KeyWriter Wrote Signature Verification Public Key (key_use=verify) as publicKey_signature_verification.json +[main] INFO d.f.fitconnect.cli.keygen.KeyWriter Wrote Signing Private Key (key_use=sign) as privateKey_signing.json +``` + #### Usage And Commands ````shell -Usage: <main class> [command] [command options] Commands: send Send a submission Usage: send [options] @@ -81,6 +93,8 @@ Usage: <main class> [command] [command options] Unique destination identifier in UUID format * --leikaKey The LeikaKey of the service type + * --schemaUri + Schema URI to validate submission data * --serviceName Name of the service type @@ -111,4 +125,15 @@ Usage: <main class> [command] [command options] Options: * --data Path to submission data as csv + + keygen Generates JWK test keys for encryption, decryption, signing + and signature validation + Usage: keygen [options] + Options: + --outDir + Output directory folder where the generated test keys are written + to + --withConfig + Generates config.yaml with paths of the generated keys + Default: false ```` diff --git a/cli/pom.xml b/cli/pom.xml index 8d9e7e07d453bf4593fae15af13df69f33e0aa09..9d26020eec770c73e756671dfa1afeb87d60be04 100644 --- a/cli/pom.xml +++ b/cli/pom.xml @@ -11,7 +11,7 @@ <artifactId>cli</artifactId> <packaging>jar</packaging> - <name>FIT-Connect Java SDK - Command-Line Interface</name> + <name>FIT-Connect Java SDK - CLI</name> <dependencies> <dependency> @@ -75,7 +75,7 @@ <descriptorRefs> <descriptorRef>jar-with-dependencies</descriptorRef> </descriptorRefs> - <finalName>fit-connect-client</finalName> + <finalName>fit-connect-cli</finalName> <appendAssemblyId>false</appendAssemblyId> </configuration> <executions> diff --git a/cli/src/main/java/dev/fitko/fitconnect/cli/CommandExecutor.java b/cli/src/main/java/dev/fitko/fitconnect/cli/CommandExecutor.java index e9b3beae875cf52e967384e5b41159e935a7f190..26beed4e404d15ad4c99752f5c58bee533215e83 100644 --- a/cli/src/main/java/dev/fitko/fitconnect/cli/CommandExecutor.java +++ b/cli/src/main/java/dev/fitko/fitconnect/cli/CommandExecutor.java @@ -3,16 +3,21 @@ package dev.fitko.fitconnect.cli; import dev.fitko.fitconnect.api.domain.model.submission.SentSubmission; import dev.fitko.fitconnect.api.domain.model.submission.SubmissionForPickup; import dev.fitko.fitconnect.api.exceptions.internal.BatchImportException; +import dev.fitko.fitconnect.cli.batch.BatchImporter; import dev.fitko.fitconnect.cli.batch.ImportRecord; +import dev.fitko.fitconnect.cli.commands.CreateTestKeysCommand; import dev.fitko.fitconnect.cli.commands.GetAllSubmissionsCommand; import dev.fitko.fitconnect.cli.commands.GetOneSubmissionCommand; import dev.fitko.fitconnect.cli.commands.ListAllSubmissionsCommand; import dev.fitko.fitconnect.cli.commands.SendBatchCommand; import dev.fitko.fitconnect.cli.commands.SendSubmissionCommand; +import dev.fitko.fitconnect.cli.keygen.JWKGenerator; +import dev.fitko.fitconnect.cli.keygen.JWKPair; +import dev.fitko.fitconnect.cli.keygen.KeyWriter; +import dev.fitko.fitconnect.cli.keygen.KeyWriterSettings; +import dev.fitko.fitconnect.cli.util.AttachmentDataType; import dev.fitko.fitconnect.client.SenderClient; import dev.fitko.fitconnect.client.SubscriberClient; -import dev.fitko.fitconnect.cli.batch.BatchImporter; -import dev.fitko.fitconnect.cli.util.AttachmentDataType; import dev.fitko.fitconnect.client.sender.model.Attachment; import dev.fitko.fitconnect.client.sender.model.SendableSubmission; import dev.fitko.fitconnect.client.subscriber.ReceivedSubmission; @@ -29,42 +34,47 @@ import java.nio.file.Path; import java.util.List; import java.util.Set; import java.util.function.Function; +import java.util.function.Supplier; import java.util.stream.Collectors; import java.util.stream.Stream; +import static dev.fitko.fitconnect.cli.keygen.JWKGenerator.DEFAULT_KEY_SIZE; + class CommandExecutor { private static final Logger LOGGER = LoggerFactory.getLogger(CommandExecutor.class); private static final String IMPORTED_RECORD_TEMPLATE = "\n\n================== Imported record {} ==================\n"; - private final SenderClient senderClient; - private final SubscriberClient subscriberClient; private final BatchImporter batchImporter; + private final JWKGenerator jwkGenerator; + private final Supplier<SenderClient> senderSupplier; + private final Supplier<SubscriberClient> subscriberSupplier; - CommandExecutor(final SenderClient senderClient, final SubscriberClient subscriberClient, final BatchImporter batchImporter) { - this.senderClient = senderClient; - this.subscriberClient = subscriberClient; + CommandExecutor(final Supplier<SenderClient> senderSupplier, final Supplier<SubscriberClient> subscriberSupplier, final BatchImporter batchImporter, final JWKGenerator jwkGenerator) { this.batchImporter = batchImporter; + this.jwkGenerator = jwkGenerator; + this.senderSupplier = senderSupplier; + this.subscriberSupplier = subscriberSupplier; } void getOneSubmission(final GetOneSubmissionCommand getOneSubmissionCommand) throws IOException { LOGGER.info("Getting submission for id {}", getOneSubmissionCommand.submissionId); final var startTime = StopWatch.start(); - final var submission = subscriberClient.requestSubmission(getOneSubmissionCommand.submissionId); + final var submission = subscriberSupplier.get().requestSubmission(getOneSubmissionCommand.submissionId); LOGGER.info("Submission download took {}", StopWatch.stopWithFormattedTime(startTime)); if (submission == null) { LOGGER.info("No submission found for submission id {}", getOneSubmissionCommand.submissionId); } else { submission.acceptSubmission(); - writeData(submission, getTargetFolderPath(getOneSubmissionCommand)); + writeSubmissionData(submission, getTargetFolderPath(getOneSubmissionCommand)); } } void getAllSubmissions(final GetAllSubmissionsCommand getAllSubmissionsCommand) throws IOException { final var destinationId = getAllSubmissionsCommand.destinationId; LOGGER.info("Getting all available submissions for destination {}", destinationId); - final Set<SubmissionForPickup> submissions = subscriberClient.getAvailableSubmissionsForDestination(destinationId); + final Set<SubmissionForPickup> submissions = subscriberSupplier.get().getAvailableSubmissionsForDestination(destinationId); for (final SubmissionForPickup submission : submissions) { final GetOneSubmissionCommand getOneSubmissionCommand = new GetOneSubmissionCommand(); getOneSubmissionCommand.submissionId = submission.getSubmissionId(); @@ -76,7 +86,7 @@ class CommandExecutor { void listSubmissions(final ListAllSubmissionsCommand listAllSubmissionsCommand) { final var destinationId = listAllSubmissionsCommand.destinationId; LOGGER.info("Listing available submissions for destination {}", destinationId); - for (final SubmissionForPickup submission : subscriberClient.getAvailableSubmissionsForDestination(destinationId)) { + for (final SubmissionForPickup submission : subscriberSupplier.get().getAvailableSubmissionsForDestination(destinationId)) { LOGGER.info("caseId: {} - submissionId: {}", submission.getCaseId(), submission.getSubmissionId()); } } @@ -95,6 +105,7 @@ class CommandExecutor { LOGGER.info("Import took {}", StopWatch.stopWithFormattedTime(startTime)); return submission; } + void sendBatch(final SendBatchCommand sendBatchCommand) throws BatchImportException, IOException { final List<ImportRecord> importRecords = batchImporter.readRecords(sendBatchCommand.dataPath); LOGGER.info("Sending batch of {} submissions", importRecords.size()); @@ -111,6 +122,23 @@ class CommandExecutor { LOGGER.info("DONE ! Finished batch import of {} submissions in {}", importCount, StopWatch.stopWithFormattedTime(startTime)); } + void createTestKeys(final CreateTestKeysCommand createTestKeysCommand) { + + LOGGER.info("Generating JWKs ..."); + + final JWKPair encryptionKeyPair = jwkGenerator.generateEncryptionKeyPair(DEFAULT_KEY_SIZE); + final JWKPair signatureKeyPair = jwkGenerator.generateSignatureKeyPair(DEFAULT_KEY_SIZE); + + final KeyWriterSettings keyWriterSettings = KeyWriterSettings.builder() + .outputDir(createTestKeysCommand.outputDir) + .createConfigYaml(createTestKeysCommand.generateConfig) + .encryptionKeyPair(encryptionKeyPair) + .signatureKeyPair(signatureKeyPair) + .build(); + + new KeyWriter().writeKeys(keyWriterSettings); + } + private SentSubmission sendWithJsonData(final SendSubmissionCommand sendSubmissionCommand, final List<Attachment> attachments) throws IOException { final SendableSubmission sendableSubmission = SendableSubmission.Builder() @@ -120,7 +148,7 @@ class CommandExecutor { .addAttachments(attachments) .build(); - return senderClient.send(sendableSubmission); + return senderSupplier.get().send(sendableSubmission); } private SentSubmission sendWithXmlData(final SendSubmissionCommand sendSubmissionCommand, final List<Attachment> attachments) throws IOException { @@ -132,7 +160,7 @@ class CommandExecutor { .addAttachments(attachments) .build(); - return senderClient.send(sendableSubmission); + return senderSupplier.get().send(sendableSubmission); } private String getTargetFolderPath(final GetOneSubmissionCommand getOneSubmissionCommand) { @@ -152,7 +180,7 @@ class CommandExecutor { return path -> Attachment.fromPath(Path.of(path), mimeTypeDetector.detect(path), Path.of(path).getFileName().toString(), "attachment"); } - private void writeData(final ReceivedSubmission receivedSubmission, final String dataDirPath) throws IOException { + private void writeSubmissionData(final ReceivedSubmission receivedSubmission, final String dataDirPath) throws IOException { LOGGER.info("Creating data directory for submission in {}", dataDirPath); Files.createDirectories(Path.of(dataDirPath)); @@ -161,8 +189,10 @@ class CommandExecutor { LOGGER.info("Writing data.{}", fileEnding); Files.write(filePath, receivedSubmission.getDataAsString().getBytes(StandardCharsets.UTF_8)); - for (final Attachment attachment : receivedSubmission.getAttachments()) { - final String filename = attachment.getFileName(); + final List<Attachment> attachments = receivedSubmission.getAttachments(); + for (int i = 0; i < attachments.size(); i++) { + final Attachment attachment = attachments.get(i); + final String filename = attachment.getFileName() != null ? attachment.getFileName() : "attachment_" + i; LOGGER.info("Writing attachment {}", filename); Files.write(Path.of(dataDirPath, filename), attachment.getDataAsBytes()); } diff --git a/cli/src/main/java/dev/fitko/fitconnect/cli/CommandLineClient.java b/cli/src/main/java/dev/fitko/fitconnect/cli/CommandLineClient.java index 776e9ef3415546c9b6dafd8e9574fe8562c04608..1186083cae8f19981b882909ad8b664e213657ab 100644 --- a/cli/src/main/java/dev/fitko/fitconnect/cli/CommandLineClient.java +++ b/cli/src/main/java/dev/fitko/fitconnect/cli/CommandLineClient.java @@ -2,18 +2,21 @@ package dev.fitko.fitconnect.cli; import com.beust.jcommander.JCommander; import com.beust.jcommander.ParameterException; -import dev.fitko.fitconnect.client.SenderClient; -import dev.fitko.fitconnect.client.SubscriberClient; import dev.fitko.fitconnect.cli.batch.CsvImporter; +import dev.fitko.fitconnect.cli.commands.CreateTestKeysCommand; import dev.fitko.fitconnect.cli.commands.GetAllSubmissionsCommand; import dev.fitko.fitconnect.cli.commands.GetOneSubmissionCommand; import dev.fitko.fitconnect.cli.commands.ListAllSubmissionsCommand; import dev.fitko.fitconnect.cli.commands.SendBatchCommand; import dev.fitko.fitconnect.cli.commands.SendSubmissionCommand; +import dev.fitko.fitconnect.cli.keygen.JWKGenerator; +import dev.fitko.fitconnect.client.SenderClient; +import dev.fitko.fitconnect.client.SubscriberClient; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.IOException; +import java.util.function.Supplier; class CommandLineClient { @@ -24,20 +27,21 @@ class CommandLineClient { private final GetOneSubmissionCommand getOneSubmissionCommand; private final GetAllSubmissionsCommand getAllSubmissionsCommand; private final SendBatchCommand sendBatchCommand; - + private final CreateTestKeysCommand createTestKeysCommand; private final CommandExecutor commandExecutor; private final JCommander jc; - CommandLineClient(final SenderClient senderClient, final SubscriberClient subscriberClient) { + CommandLineClient(final Supplier<SenderClient> senderSupplier, final Supplier<SubscriberClient> subscriberSupplier) { sendSubmissionCommand = new SendSubmissionCommand(); listAllSubmissionsCommand = new ListAllSubmissionsCommand(); getOneSubmissionCommand = new GetOneSubmissionCommand(); getAllSubmissionsCommand = new GetAllSubmissionsCommand(); sendBatchCommand = new SendBatchCommand(); + createTestKeysCommand = new CreateTestKeysCommand(); - commandExecutor = new CommandExecutor(senderClient, subscriberClient, new CsvImporter()); + commandExecutor = new CommandExecutor(senderSupplier, subscriberSupplier, new CsvImporter(), new JWKGenerator()); jc = getJCommander(); } @@ -62,6 +66,7 @@ class CommandLineClient { .addCommand(getOneSubmissionCommand) .addCommand(getAllSubmissionsCommand) .addCommand(sendBatchCommand) + .addCommand(createTestKeysCommand) .build(); } @@ -82,6 +87,9 @@ class CommandLineClient { case SendBatchCommand.SEND_BATCH_CMD_NAME: commandExecutor.sendBatch(sendBatchCommand); break; + case CreateTestKeysCommand.CREATE_TEST_KEYS_COMMAND_NAME: + commandExecutor.createTestKeys(createTestKeysCommand); + break; default: break; } diff --git a/cli/src/main/java/dev/fitko/fitconnect/cli/CommandLineRunner.java b/cli/src/main/java/dev/fitko/fitconnect/cli/CommandLineRunner.java index 68cde0c97f7037f13dac4b7790802adc8696b67c..a8833206125d3861e7218dc92b068cbd6927e6b2 100644 --- a/cli/src/main/java/dev/fitko/fitconnect/cli/CommandLineRunner.java +++ b/cli/src/main/java/dev/fitko/fitconnect/cli/CommandLineRunner.java @@ -2,6 +2,7 @@ package dev.fitko.fitconnect.cli; import dev.fitko.fitconnect.api.config.ApplicationConfig; import dev.fitko.fitconnect.client.SenderClient; +import dev.fitko.fitconnect.client.SubscriberClient; import dev.fitko.fitconnect.client.bootstrap.ApplicationConfigLoader; import dev.fitko.fitconnect.client.bootstrap.ClientFactory; import org.slf4j.Logger; @@ -11,38 +12,39 @@ import java.io.BufferedReader; import java.io.InputStream; import java.io.InputStreamReader; import java.nio.file.Path; +import java.util.function.Supplier; import java.util.stream.Collectors; public final class CommandLineRunner { - + private static final Logger LOGGER = LoggerFactory.getLogger(CommandLineRunner.class); private static final String LOGO = "/splash_screen_banner.txt"; + private static final String DEFAULT_CONFIG_NAME = "config.yml"; - private static final Logger LOGGER = LoggerFactory.getLogger(CommandLineRunner.class); private CommandLineRunner() { } public static void main(final String[] args) { printSplashScreen(); - getCommandLineClient(loadConfig()).run(args); + getCommandLineClient().run(args); + } + + private static CommandLineClient getCommandLineClient() { + final Supplier<SenderClient> senderClientSupplier = () -> ClientFactory.getSenderClient(loadConfig()); + final Supplier<SubscriberClient> subscriberClientSupplier = () -> ClientFactory.getSubscriberClient(loadConfig()); + return new CommandLineClient(senderClientSupplier, subscriberClientSupplier); } private static ApplicationConfig loadConfig() { try { return ApplicationConfigLoader.loadConfigFromEnvironment(); } catch (final Exception e) { - LOGGER.warn("Could not load config from environment, loading default config {}", DEFAULT_CONFIG_NAME); + LOGGER.info("Loading config from default location {}", DEFAULT_CONFIG_NAME); return ApplicationConfigLoader.loadConfigFromPath(Path.of(DEFAULT_CONFIG_NAME)); } } - private static CommandLineClient getCommandLineClient(final ApplicationConfig config) { - final SenderClient senderClient = ClientFactory.getSenderClient(config); - final var subscriberClient = ClientFactory.getSubscriberClient(config); - return new CommandLineClient(senderClient, subscriberClient); - } - private static void printSplashScreen() { LOGGER.info("{}", getSplashScreenResource()); } diff --git a/cli/src/main/java/dev/fitko/fitconnect/cli/commands/CreateTestKeysCommand.java b/cli/src/main/java/dev/fitko/fitconnect/cli/commands/CreateTestKeysCommand.java new file mode 100644 index 0000000000000000000000000000000000000000..4901e7e49d619e475801f389febfc73366710651 --- /dev/null +++ b/cli/src/main/java/dev/fitko/fitconnect/cli/commands/CreateTestKeysCommand.java @@ -0,0 +1,20 @@ +package dev.fitko.fitconnect.cli.commands; + +import com.beust.jcommander.Parameter; +import com.beust.jcommander.Parameters; + +@Parameters( + commandNames = {CreateTestKeysCommand.CREATE_TEST_KEYS_COMMAND_NAME}, + commandDescription = "Generates JWK test keys for encryption, decryption, signing and signature validation", + separators = "=" +) +public class CreateTestKeysCommand { + + public static final String CREATE_TEST_KEYS_COMMAND_NAME = "keygen"; + + @Parameter(names = {"--outDir"}, description = "Output directory folder where the generated test keys are written to", arity = 1) + public String outputDir; + + @Parameter(names = {"--withConfig"}, description = "Generates config.yaml with paths of the generated keys", arity = 1) + public boolean generateConfig; +} diff --git a/cli/src/main/java/dev/fitko/fitconnect/cli/commands/GetAllSubmissionsCommand.java b/cli/src/main/java/dev/fitko/fitconnect/cli/commands/GetAllSubmissionsCommand.java index 1bd6e91fc1b91184fdb961df66c74d045c0f3bae..a7c1c26e96cb2453596f6341fca8b98f951017a6 100644 --- a/cli/src/main/java/dev/fitko/fitconnect/cli/commands/GetAllSubmissionsCommand.java +++ b/cli/src/main/java/dev/fitko/fitconnect/cli/commands/GetAllSubmissionsCommand.java @@ -17,10 +17,8 @@ public class GetAllSubmissionsCommand { public static final String GET_ALL_CMD_NAME = "all"; @Parameter(names = { "--destinationId" }, description = "Unique destination identifier in UUID format", converter = UUIDConverter.class, validateWith = UUIDValidator.class, arity = 1, required = true) - public - UUID destinationId; + public UUID destinationId; @Parameter(names = { "--target" }, description = "Target folder where attachments and data is written to", arity = 1) - public - String targetFolder; + public String targetFolder; } diff --git a/cli/src/main/java/dev/fitko/fitconnect/cli/commands/GetOneSubmissionCommand.java b/cli/src/main/java/dev/fitko/fitconnect/cli/commands/GetOneSubmissionCommand.java index bc573146b9a1d05419f81d1ffe94c595840875f4..d5e50ccb1a3034b4d92e95da0a238b04b77e5066 100644 --- a/cli/src/main/java/dev/fitko/fitconnect/cli/commands/GetOneSubmissionCommand.java +++ b/cli/src/main/java/dev/fitko/fitconnect/cli/commands/GetOneSubmissionCommand.java @@ -18,10 +18,8 @@ public class GetOneSubmissionCommand { public static final String GET_CMD_NAME = "get"; @Parameter(names = { "--submissionId" }, description = "Unique submission identifier in UUID format", converter = UUIDConverter.class, validateWith = UUIDValidator.class, arity = 1, required = true) - public - UUID submissionId; + public UUID submissionId; @Parameter(names = { "--target" }, description = "Target folder where attachments and data is written to", arity = 1) - public - String targetFolder; + public String targetFolder; } diff --git a/cli/src/main/java/dev/fitko/fitconnect/cli/commands/SendSubmissionCommand.java b/cli/src/main/java/dev/fitko/fitconnect/cli/commands/SendSubmissionCommand.java index d4573beb6fa7c32fa8056d27fca59dbfa3878dbf..4cce06bd5312a4313d9c3e073c5815dac483c68f 100644 --- a/cli/src/main/java/dev/fitko/fitconnect/cli/commands/SendSubmissionCommand.java +++ b/cli/src/main/java/dev/fitko/fitconnect/cli/commands/SendSubmissionCommand.java @@ -27,27 +27,21 @@ public class SendSubmissionCommand { UUID destinationId; @Parameter(names = {"--serviceName"}, arity = 1, description = "Name of the service type", required = true) - public - String serviceName; + public String serviceName; @Parameter(names = {"--leikaKey"}, arity = 1, description = "The LeikaKey of the service type", required = true) - public - String leikaKey; + public String leikaKey; @Parameter(names = {"--data"}, arity = 1, description = "Path to JSON or XML data", required = true) - public - String data; + public String data; @Parameter(names = {"--dataType"}, arity = 1, description = "Data mime type (json/xml), default JSON", required = true) - public - AttachmentDataType dataType; + public AttachmentDataType dataType; @Parameter(names = "--attachments", description = "Attachments as list of paths", variableArity = true) - public - List<String> attachments = Collections.emptyList(); + public List<String> attachments = Collections.emptyList(); @Parameter(names = "--schemaUri", description = "Schema URI to validate submission data", required = true) - public - String schemaUri; + public String schemaUri; } diff --git a/cli/src/main/java/dev/fitko/fitconnect/cli/keygen/JWKGenerator.java b/cli/src/main/java/dev/fitko/fitconnect/cli/keygen/JWKGenerator.java new file mode 100644 index 0000000000000000000000000000000000000000..f38ba5f8ba8091780313bade25e2d6af1981a4bd --- /dev/null +++ b/cli/src/main/java/dev/fitko/fitconnect/cli/keygen/JWKGenerator.java @@ -0,0 +1,130 @@ +package dev.fitko.fitconnect.cli.keygen; + +import com.nimbusds.jose.Algorithm; +import com.nimbusds.jose.JOSEException; +import com.nimbusds.jose.JWEAlgorithm; +import com.nimbusds.jose.JWSAlgorithm; +import com.nimbusds.jose.jwk.KeyOperation; +import com.nimbusds.jose.jwk.RSAKey; +import com.nimbusds.jose.util.Base64; +import org.bouncycastle.asn1.x500.X500Name; +import org.bouncycastle.cert.X509v3CertificateBuilder; +import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter; +import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder; +import org.bouncycastle.operator.ContentSigner; +import org.bouncycastle.operator.OperatorCreationException; +import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder; + +import java.math.BigInteger; +import java.security.InvalidParameterException; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.NoSuchAlgorithmException; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; +import java.security.interfaces.RSAPublicKey; +import java.time.Duration; +import java.time.Instant; +import java.util.Collections; +import java.util.Date; +import java.util.List; +import java.util.Set; +import java.util.UUID; + +/** + * JWK Test Key Generator. + * <p> + * Generates key pairs of public and private keys for encryption and signing. + */ +public class JWKGenerator { + + public static final int DEFAULT_KEY_SIZE = 4096; + + /** + * Generate a set of public encryption key and private decryption key. + * + * @param keySize size of the RSA key in bits + * @return JWKPair of public and private key + */ + public JWKPair generateEncryptionKeyPair(final int keySize) { + final KeyPair keyPair = getKeyPair(keySize); + final List<Base64> x509CertChain = getX509CertChain(keyPair); + + final String keyId = UUID.randomUUID().toString(); + final JWEAlgorithm encryptionAlgorithm = JWEAlgorithm.RSA_OAEP_256; + + final RSAKey publicEncryptionKey = buildRSAKey(keyId, keyPair, KeyOperation.WRAP_KEY, encryptionAlgorithm, x509CertChain); + final RSAKey privateDecryptionKey = buildRSAKey(keyId, keyPair, KeyOperation.UNWRAP_KEY, encryptionAlgorithm); + + return new JWKPair(publicEncryptionKey.toPublicJWK(), privateDecryptionKey); + } + + /** + * Generate a set of public signature verification key and private signature key. + * + * @param keySize size of the RSA key in bits + * @return JWKPair of signature and verification key + */ + public JWKPair generateSignatureKeyPair(final int keySize) { + final KeyPair keyPair = getKeyPair(keySize); + final List<Base64> x509CertChain = getX509CertChain(keyPair); + + final String keyId = UUID.randomUUID().toString(); + final JWSAlgorithm signingAlgorithm = JWSAlgorithm.PS512; + + final RSAKey privateSignatureKey = buildRSAKey(keyId, keyPair, KeyOperation.SIGN, signingAlgorithm); + final RSAKey publicSignatureVerificationKey = buildRSAKey(keyId, keyPair, KeyOperation.VERIFY, signingAlgorithm, x509CertChain); + + return new JWKPair(publicSignatureVerificationKey.toPublicJWK(), privateSignatureKey); + } + + private static List<Base64> getX509CertChain(final KeyPair keyPair) { + final X509Certificate cert = getX509Certificate(keyPair); + try { + return RSAKey.parse(cert).getX509CertChain(); + } catch (final JOSEException e) { + throw new RuntimeException(e); + } + } + + private static RSAKey buildRSAKey(final String keyId, final KeyPair keyPair, final KeyOperation keyOperation, final Algorithm algorithm, final List<Base64> x509CertChain) { + final RSAKey.Builder builder = new RSAKey.Builder((RSAPublicKey) keyPair.getPublic()) + .privateKey(keyPair.getPrivate()) + .keyID(keyId) + .keyOperations(Set.of(keyOperation)) + .algorithm(algorithm); + return x509CertChain.isEmpty() ? builder.build() : builder.x509CertChain(x509CertChain).build(); + } + + private static RSAKey buildRSAKey(final String keyId, final KeyPair keyPair, final KeyOperation keyOperation, final Algorithm algorithm) { + return buildRSAKey(keyId, keyPair, keyOperation, algorithm, Collections.emptyList()); + } + + private static X509Certificate getX509Certificate(final KeyPair keyPair) { + final Instant now = Instant.now(); + final Date notBefore = Date.from(now); + final Date notAfter = Date.from(now.plus(Duration.ofDays(365 * 10))); + + try { + final ContentSigner contentSigner = new JcaContentSignerBuilder("SHA512withRSA").build(keyPair.getPrivate()); + final X500Name x500Name = new X500Name("CN=localhost"); + final X500Name x500Subject = new X500Name("C=Test"); + final BigInteger serialNr = BigInteger.valueOf(now.toEpochMilli()); + final X509v3CertificateBuilder certificateBuilder = new JcaX509v3CertificateBuilder(x500Name, serialNr, notBefore, notAfter, x500Subject, keyPair.getPublic()); + return new JcaX509CertificateConverter().getCertificate(certificateBuilder.build(contentSigner)); + } catch (final CertificateException | OperatorCreationException e) { + throw new RuntimeException(e); + } + } + + private static KeyPair getKeyPair(final int keySize) { + try { + final KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA"); + keyPairGenerator.initialize(keySize); + return keyPairGenerator.generateKeyPair(); + } catch (final NoSuchAlgorithmException | InvalidParameterException e) { + throw new RuntimeException(e); + } + } + +} diff --git a/cli/src/main/java/dev/fitko/fitconnect/cli/keygen/JWKPair.java b/cli/src/main/java/dev/fitko/fitconnect/cli/keygen/JWKPair.java new file mode 100644 index 0000000000000000000000000000000000000000..dd723a45163d8f148ce90df2e0b33975ba587274 --- /dev/null +++ b/cli/src/main/java/dev/fitko/fitconnect/cli/keygen/JWKPair.java @@ -0,0 +1,11 @@ +package dev.fitko.fitconnect.cli.keygen; + +import com.nimbusds.jose.jwk.JWK; +import lombok.Value; + +@Value +public class JWKPair { + + JWK publicKey; + JWK privateKey; +} diff --git a/cli/src/main/java/dev/fitko/fitconnect/cli/keygen/KeyWriter.java b/cli/src/main/java/dev/fitko/fitconnect/cli/keygen/KeyWriter.java new file mode 100644 index 0000000000000000000000000000000000000000..84fca07628638ef5a1ab7e1ac1fbc152797cad44 --- /dev/null +++ b/cli/src/main/java/dev/fitko/fitconnect/cli/keygen/KeyWriter.java @@ -0,0 +1,105 @@ +package dev.fitko.fitconnect.cli.keygen; + +import com.nimbusds.jose.jwk.JWK; +import dev.fitko.fitconnect.api.config.defaults.DefaultEnvironments; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.yaml.snakeyaml.DumperOptions; +import org.yaml.snakeyaml.Yaml; + +import java.io.IOException; +import java.io.StringWriter; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +public final class KeyWriter { + + private static final Logger LOGGER = LoggerFactory.getLogger(KeyWriter.class); + + public static final String PUBLIC_ENCRYPTION_KEY_NAME = "publicKey_encryption.json"; + public static final String PUBLIC_ENCRYPTION_KEY_DESC = "Encryption Public Key (key_use=wrapKey)"; + public static final String PRIVATE_DECRYPTION_KEY_NAME = "privateKey_decryption.json"; + public static final String PRIVATE_DECRYPTION_KEY_DESC = "Decryption Private Key (key_use=unwrapKey)"; + public static final String PUBLIC_SIGNATURE_VERIFICATION_KEY_NAME = "publicKey_signature_verification.json"; + public static final String PUBLIC_SIGNATURE_VERIFICATION_KEY_DESC = "Signature Verification Public Key (key_use=verify)"; + public static final String PRIVATE_SIGNING_KEY_NAME = "privateKey_signing.json"; + public static final String PRIVATE_SIGNING_KEY_DESC = "Signing Private Key (key_use=sign)"; + public static final String TEMP_DIR_NAME = "testJWKs"; + + public void writeKeys(final KeyWriterSettings settings) { + + final String dir = getKeyDirectory(settings.getOutputDir()); + LOGGER.info("Writing keys to directory {}", dir); + + final JWKPair encryptionKeyPair = settings.getEncryptionKeyPair(); + final JWKPair signatureKeyPair = settings.getSignatureKeyPair(); + + writeKeyToFile(dir, PUBLIC_ENCRYPTION_KEY_NAME, PUBLIC_ENCRYPTION_KEY_DESC, encryptionKeyPair.getPublicKey()); + writeKeyToFile(dir, PRIVATE_DECRYPTION_KEY_NAME, PRIVATE_DECRYPTION_KEY_DESC, encryptionKeyPair.getPrivateKey()); + + writeKeyToFile(dir, PUBLIC_SIGNATURE_VERIFICATION_KEY_NAME, PUBLIC_SIGNATURE_VERIFICATION_KEY_DESC, signatureKeyPair.getPublicKey()); + writeKeyToFile(dir, PRIVATE_SIGNING_KEY_NAME, PRIVATE_SIGNING_KEY_DESC, signatureKeyPair.getPrivateKey()); + + if (settings.isCreateConfigYaml()) { + writeConfigYamlToFile(dir); + } + } + + private String getKeyDirectory(final String outputDir) { + if (outputDir == null) { + try { + return Files.createTempDirectory(TEMP_DIR_NAME).toFile().getAbsolutePath(); + } catch (final IOException e) { + LOGGER.error(e.getMessage(), e); + System.exit(0); + } + } + return Path.of(outputDir).toAbsolutePath().toString(); + } + + private void writeConfigYamlToFile(final String dir) { + + final Map<String, Object> data = new LinkedHashMap<>(); + data.put("senderConfig", + Map.of("clientSecret", "", + "clientId", "")); + data.put("subscriberConfig", + Map.of("clientSecret", "", + "clientId", "", + "privateDecryptionKeyPaths", List.of(Path.of(dir, PRIVATE_DECRYPTION_KEY_NAME).toAbsolutePath().toString()), + "privateSigningKeyPath", Path.of(dir,PRIVATE_SIGNING_KEY_NAME).toAbsolutePath().toString())); + data.put("activeEnvironment", DefaultEnvironments.TEST.getEnvironmentName().getName()); + + final DumperOptions options = new DumperOptions(); + options.setDefaultScalarStyle(DumperOptions.ScalarStyle.PLAIN); + options.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK); + options.setPrettyFlow(true); + + final StringWriter writer = new StringWriter(); + final Yaml yaml = new Yaml(options); + yaml.dump(data, writer); + writer.flush(); + + writeFile(Path.of(dir, "config.yml"), writer.toString()); + + LOGGER.info("Wrote config.yml"); + } + + private void writeKeyToFile(final String dir, final String filename, final String desc, final JWK jwk) { + writeFile(Path.of(dir, filename), jwk.toJSONString()); + LOGGER.info("Wrote {} as {}", desc, filename); + } + + private void writeFile(final Path path, final String content) { + try { + Files.write(path, content.getBytes(StandardCharsets.UTF_8)); + } catch (final IOException e) { + LOGGER.error(e.getMessage(), e); + System.exit(0); + } + } +} diff --git a/cli/src/main/java/dev/fitko/fitconnect/cli/keygen/KeyWriterSettings.java b/cli/src/main/java/dev/fitko/fitconnect/cli/keygen/KeyWriterSettings.java new file mode 100644 index 0000000000000000000000000000000000000000..2fab1de5690e91a30a918dd9429f33791b7a3b23 --- /dev/null +++ b/cli/src/main/java/dev/fitko/fitconnect/cli/keygen/KeyWriterSettings.java @@ -0,0 +1,15 @@ +package dev.fitko.fitconnect.cli.keygen; + +import lombok.Builder; +import lombok.Value; + +@Value +@Builder +public class KeyWriterSettings { + + String outputDir; + boolean createConfigYaml; + + JWKPair encryptionKeyPair; + JWKPair signatureKeyPair; +} diff --git a/client/src/main/resources/splash_screen_banner.txt b/cli/src/main/resources/splash_screen_banner.txt similarity index 71% rename from client/src/main/resources/splash_screen_banner.txt rename to cli/src/main/resources/splash_screen_banner.txt index 4e4892d0d632503223d08b946eb840ee00ec3074..b45dff53c9f452d0e10dd5053b043613b600f68d 100644 --- a/client/src/main/resources/splash_screen_banner.txt +++ b/cli/src/main/resources/splash_screen_banner.txt @@ -1,7 +1,8 @@ - ________________ ______ __ _________ __ - / ____/ _/_ __/ / ____/___ ____ ____ ___ _____/ /_ / ____/ (_)__ ____ / /_ - / /_ / / / /_____/ / / __ \/ __ \/ __ \/ _ \/ ___/ __/ / / / / / _ \/ __ \/ __/ - / __/ _/ / / /_____/ /___/ /_/ / / / / / / / __/ /__/ /_ / /___/ / / __/ / / / /_ -/_/ /___/ /_/ \____/\____/_/ /_/_/ /_/\___/\___/\__/ \____/_/_/\___/_/ /_/\__/ + + ________________ ______ __ ________ ____ + / ____/ _/_ __/ / ____/___ ____ ____ ___ _____/ /_ / ____/ / / _/ + / /_ / / / /_____/ / / __ \/ __ \/ __ \/ _ \/ ___/ __/ / / / / / / + / __/ _/ / / /_____/ /___/ /_/ / / / / / / / __/ /__/ /_ / /___/ /____/ / +/_/ /___/ /_/ \____/\____/_/ /_/_/ /_/\___/\___/\__/ \____/_____/___/ diff --git a/cli/src/test/java/dev/fitko/fitconnect/cli/CommandLineClientTest.java b/cli/src/test/java/dev/fitko/fitconnect/cli/CommandLineClientTest.java index 4a2ac3030c355d9baec310e6361d585b3e0b999f..71e1e09ed478847e63206f66c45e8ffaa6943bf5 100644 --- a/cli/src/test/java/dev/fitko/fitconnect/cli/CommandLineClientTest.java +++ b/cli/src/test/java/dev/fitko/fitconnect/cli/CommandLineClientTest.java @@ -6,20 +6,21 @@ import dev.fitko.fitconnect.api.domain.model.submission.SentSubmission; import dev.fitko.fitconnect.api.domain.model.submission.Submission; import dev.fitko.fitconnect.api.domain.model.submission.SubmissionForPickup; import dev.fitko.fitconnect.api.services.Subscriber; +import dev.fitko.fitconnect.cli.batch.CsvImporter; +import dev.fitko.fitconnect.cli.batch.ImportRecord; +import dev.fitko.fitconnect.cli.keygen.KeyWriter; import dev.fitko.fitconnect.client.SenderClient; import dev.fitko.fitconnect.client.SubscriberClient; - import dev.fitko.fitconnect.client.sender.model.Attachment; import dev.fitko.fitconnect.client.sender.model.SendableEncryptedSubmission; import dev.fitko.fitconnect.client.sender.model.SendableSubmission; import dev.fitko.fitconnect.client.subscriber.ReceivedSubmission; import dev.fitko.fitconnect.client.subscriber.model.ReceivedData; -import dev.fitko.fitconnect.cli.batch.CsvImporter; -import dev.fitko.fitconnect.cli.batch.ImportRecord; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; +import java.nio.file.Files; import java.nio.file.Path; import java.util.HashSet; import java.util.List; @@ -27,6 +28,7 @@ import java.util.Map; import java.util.Set; import java.util.UUID; +import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -45,7 +47,7 @@ class CommandLineClientTest { senderClientMock = mock(SenderClient.class); subscriberMock = mock(Subscriber.class); subscriberClientMock = mock(SubscriberClient.class); - underTest = new CommandLineClient(senderClientMock, subscriberClientMock); + underTest = new CommandLineClient(() -> senderClientMock, () -> subscriberClientMock); } @Test @@ -127,7 +129,6 @@ class CommandLineClientTest { void testBatchImport() { // Given - final var testDataPath = "src/test/resources/batch_data.csv"; final List<ImportRecord> importRecords = new CsvImporter().readRecords(testDataPath); @@ -141,6 +142,31 @@ class CommandLineClientTest { logs.assertContains("DONE ! Finished batch import of " + importRecords.size() + " submissions"); } + @Test + void testKeyGeneration(@TempDir final Path tempDir) { + + // When + underTest.run("keygen", "--outDir=" + tempDir.toAbsolutePath()); + + // Then + logs.assertContains("Writing keys to directory " + tempDir.toAbsolutePath()); + + assertTrue(Files.exists(Path.of(tempDir.toString(), KeyWriter.PUBLIC_ENCRYPTION_KEY_NAME))); + assertTrue(Files.exists(Path.of(tempDir.toString(), KeyWriter.PRIVATE_DECRYPTION_KEY_NAME))); + assertTrue(Files.exists(Path.of(tempDir.toString(), KeyWriter.PUBLIC_SIGNATURE_VERIFICATION_KEY_NAME))); + assertTrue(Files.exists(Path.of(tempDir.toString(), KeyWriter.PRIVATE_SIGNING_KEY_NAME))); + } + + @Test + void testKeyGenerationWithConfigOption(@TempDir final Path tempDir) { + + // When + underTest.run("keygen", "--outDir=" + tempDir.toAbsolutePath(), "--withConfig=true"); + + // Then + assertTrue(Files.exists(Path.of(tempDir.toString(), "config.yml"))); + } + private Set<SubmissionForPickup> generateSubmissions(final UUID destinationId, final int count) { final Set<SubmissionForPickup> submissions = new HashSet<>(count); for (int i = 0; i < count; i++) { diff --git a/cli/src/test/java/dev/fitko/fitconnect/cli/keygen/JWKGeneratorTest.java b/cli/src/test/java/dev/fitko/fitconnect/cli/keygen/JWKGeneratorTest.java new file mode 100644 index 0000000000000000000000000000000000000000..772162da281618efa7ceb8da6563f43897daebf9 --- /dev/null +++ b/cli/src/test/java/dev/fitko/fitconnect/cli/keygen/JWKGeneratorTest.java @@ -0,0 +1,199 @@ +package dev.fitko.fitconnect.cli.keygen; + +import com.nimbusds.jose.JWEAlgorithm; +import com.nimbusds.jose.JWSAlgorithm; +import com.nimbusds.jose.jwk.JWK; +import com.nimbusds.jose.jwk.KeyOperation; +import com.nimbusds.jose.jwk.KeyType; +import dev.fitko.fitconnect.core.crypto.JWECryptoService; +import dev.fitko.fitconnect.jwkvalidator.JWKValidator; +import dev.fitko.fitconnect.jwkvalidator.exceptions.LogLevel; +import org.hamcrest.CoreMatchers; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.nio.charset.StandardCharsets; +import java.util.Map; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.contains; +import static org.hamcrest.Matchers.hasSize; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.notNullValue; +import static org.hamcrest.Matchers.nullValue; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + +class JWKGeneratorTest { + + private JWKGenerator underTest; + + @BeforeEach + void setup() { + underTest = new JWKGenerator(); + } + + @Test + void testPublicEncryptionKey() { + + // When + final JWKPair encryptionKeyPair = underTest.generateEncryptionKeyPair(2048); + + final JWK publicKey = encryptionKeyPair.getPublicKey(); + + // Then + assertThat(publicKey, CoreMatchers.is(notNullValue())); + + + assertThat(publicKey.getKeyID(), CoreMatchers.is(notNullValue())); + + assertThat(publicKey.getKeyOperations(), hasSize(1)); + assertThat(publicKey.getKeyOperations(), contains(KeyOperation.WRAP_KEY)); + + assertThat(publicKey.getX509CertChain(), hasSize(1)); + + assertThat(publicKey.getKeyType(), CoreMatchers.is(KeyType.RSA)); + assertThat( publicKey.getAlgorithm(), CoreMatchers.is(JWEAlgorithm.RSA_OAEP_256)); + } + + @Test + void testPrivateDecryptionKey() { + + // When + final JWKPair encryptionKeyPair = underTest.generateEncryptionKeyPair(2048); + + final JWK privateKey = encryptionKeyPair.getPrivateKey(); + + // Then + assertThat(privateKey, CoreMatchers.is(CoreMatchers.notNullValue())); + + assertThat(privateKey.getKeyID(), CoreMatchers.is(notNullValue())); + + assertThat(privateKey.getKeyOperations(), hasSize(1)); + assertThat(privateKey.getKeyOperations(), contains(KeyOperation.UNWRAP_KEY)); + + assertThat(privateKey.getX509CertChain(), CoreMatchers.is(nullValue())); + + assertThat(privateKey.getKeyType(), CoreMatchers.is(KeyType.RSA)); + assertThat(privateKey.getAlgorithm(), CoreMatchers.is(JWEAlgorithm.RSA_OAEP_256)); + + final Map<String, Object> keyParams = privateKey.toRSAKey().toJSONObject(); + + assertThat(keyParams.get("d"), CoreMatchers.is(CoreMatchers.notNullValue())); + assertThat(keyParams.get("dp"), CoreMatchers.is(CoreMatchers.notNullValue())); + assertThat(keyParams.get("dq"), CoreMatchers.is(CoreMatchers.notNullValue())); + assertThat(keyParams.get("e"), CoreMatchers.is(CoreMatchers.notNullValue())); + assertThat(keyParams.get("n"), CoreMatchers.is(CoreMatchers.notNullValue())); + assertThat(keyParams.get("p"), CoreMatchers.is(CoreMatchers.notNullValue())); + assertThat(keyParams.get("q"), CoreMatchers.is(CoreMatchers.notNullValue())); + assertThat(keyParams.get("qi"), CoreMatchers.is(CoreMatchers.notNullValue())); + } + + @Test + void testEncryptionAndDecryption() { + + // Given + final JWKPair encryptionKeyPair = underTest.generateEncryptionKeyPair(4096); + + final JWK publicKey = encryptionKeyPair.getPublicKey(); + final JWK privateKey = encryptionKeyPair.getPrivateKey(); + + final var data = "test string to encrypt"; + + // When + final JWECryptoService cryptoService = new JWECryptoService(null); + + final String encryptedData = cryptoService.encryptBytes(publicKey.toRSAKey(), data.getBytes(StandardCharsets.UTF_8)); + final byte[] decryptedData = cryptoService.decryptToBytes(privateKey.toRSAKey(), encryptedData); + + // Then + assertThat(data, is(new String(decryptedData))); + } + + @Test + void testPublicEncryptionKeyValidationWithCorrectKeyLength() { + + // Given + final JWKPair encryptionKeyPair = underTest.generateEncryptionKeyPair(4096); + + final JWK publicKey = encryptionKeyPair.getPublicKey(); + + // Then + assertDoesNotThrow(() -> JWKValidator.withoutX5CValidation() + .withErrorLogLevel(LogLevel.ERROR) + .build() + .validate(publicKey.toRSAKey(), KeyOperation.WRAP_KEY)); + } + + @Test + void testPublicSignatureVerificationKey() { + + // When + final JWKPair signatureKeyPair = underTest.generateSignatureKeyPair(2048); + + final JWK publicKey = signatureKeyPair.getPublicKey(); + + // Then + assertThat(publicKey, CoreMatchers.is(CoreMatchers.notNullValue())); + + assertThat(publicKey.getKeyID(), CoreMatchers.is(notNullValue())); + + assertThat(publicKey.getKeyOperations(), hasSize(1)); + assertThat(publicKey.getKeyOperations(), contains(KeyOperation.VERIFY)); + + assertThat(publicKey.getX509CertChain(), hasSize(1)); + + assertThat(publicKey.getKeyType(), CoreMatchers.is(KeyType.RSA)); + assertThat( publicKey.getAlgorithm(), CoreMatchers.is(JWSAlgorithm.PS512)); + + } + + @Test + void testPublicSignatureKeyValidation() { + + // Given + final JWKPair signatureKeyPair = underTest.generateSignatureKeyPair(4096); + + final JWK publicKey = signatureKeyPair.getPublicKey(); + + // Then + assertDoesNotThrow(() -> JWKValidator.withoutX5CValidation() + .withErrorLogLevel(LogLevel.ERROR) + .build() + .validate(publicKey.toRSAKey(), KeyOperation.VERIFY)); + } + + @Test + void testPrivateSigningKey() { + + // When + final JWKPair signatureKeyPair = underTest.generateSignatureKeyPair(2048); + + final JWK privateKey = signatureKeyPair.getPrivateKey(); + + // Then + assertThat(privateKey, CoreMatchers.is(notNullValue())); + + + assertThat(privateKey.getKeyID(), CoreMatchers.is(notNullValue())); + + assertThat(privateKey.getKeyOperations(), hasSize(1)); + assertThat(privateKey.getKeyOperations(), contains(KeyOperation.SIGN)); + + assertThat(privateKey.getX509CertChain(), CoreMatchers.is(nullValue())); + + assertThat(privateKey.getKeyType(), CoreMatchers.is(KeyType.RSA)); + assertThat(privateKey.getAlgorithm(), CoreMatchers.is(JWSAlgorithm.PS512)); + + final Map<String, Object> keyParams = privateKey.toRSAKey().toJSONObject(); + + assertThat(keyParams.get("d"), CoreMatchers.is(CoreMatchers.notNullValue())); + assertThat(keyParams.get("dp"), CoreMatchers.is(CoreMatchers.notNullValue())); + assertThat(keyParams.get("dq"), CoreMatchers.is(CoreMatchers.notNullValue())); + assertThat(keyParams.get("e"), CoreMatchers.is(CoreMatchers.notNullValue())); + assertThat(keyParams.get("n"), CoreMatchers.is(CoreMatchers.notNullValue())); + assertThat(keyParams.get("p"), CoreMatchers.is(CoreMatchers.notNullValue())); + assertThat(keyParams.get("q"), CoreMatchers.is(CoreMatchers.notNullValue())); + assertThat(keyParams.get("qi"), CoreMatchers.is(CoreMatchers.notNullValue())); + + } +} \ No newline at end of file diff --git a/client/src/main/java/dev/fitko/fitconnect/client/sender/steps/BuilderStartStep.java b/client/src/main/java/dev/fitko/fitconnect/client/sender/steps/BuilderStartStep.java deleted file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000 diff --git a/client/src/main/java/dev/fitko/fitconnect/client/sender/strategies/SendNewSubmissionStrategy.java b/client/src/main/java/dev/fitko/fitconnect/client/sender/strategies/SendNewSubmissionStrategy.java index b1af51fa937b7a6ba67dbf85462c4edd0704d6c4..c472e83ffd3608eab1657046dccfe6a19d42942f 100644 --- a/client/src/main/java/dev/fitko/fitconnect/client/sender/strategies/SendNewSubmissionStrategy.java +++ b/client/src/main/java/dev/fitko/fitconnect/client/sender/strategies/SendNewSubmissionStrategy.java @@ -59,9 +59,7 @@ public class SendNewSubmissionStrategy { final SubmissionForPickup announcedSubmission = sender.createSubmission(newSubmission); final UUID announcedSubmissionId = announcedSubmission.getSubmissionId(); - final var startTimeAttachmentUpload = StopWatch.start(); uploadAttachments(encryptedAttachments, announcedSubmissionId); - LOGGER.info("Uploading attachments took {}", StopWatch.stopWithFormattedTime(startTimeAttachmentUpload)); LOGGER.info("Creating metadata"); final Metadata metadata = buildMetadata(sendableSubmission, encryptedAttachments); @@ -155,7 +153,9 @@ public class SendNewSubmissionStrategy { LOGGER.info("No attachments to upload"); } else { LOGGER.info("Uploading {} attachment(s)", attachmentPayloads.size()); + final var startTimeAttachmentUpload = StopWatch.start(); attachmentPayloads.forEach(a -> sender.uploadAttachment(submissionId, a.getAttachmentId(), a.getEncryptedData())); + LOGGER.info("Uploading attachments took {}", StopWatch.stopWithFormattedTime(startTimeAttachmentUpload)); } } diff --git a/core/src/main/java/dev/fitko/fitconnect/core/schema/SchemaResourceProvider.java b/core/src/main/java/dev/fitko/fitconnect/core/schema/SchemaResourceProvider.java index a791dde8ce7c65514c9e3bab74bf904944e7406a..b4bba3de097343e16eee9da72368e93eaa2b9a7a 100644 --- a/core/src/main/java/dev/fitko/fitconnect/core/schema/SchemaResourceProvider.java +++ b/core/src/main/java/dev/fitko/fitconnect/core/schema/SchemaResourceProvider.java @@ -55,7 +55,7 @@ public class SchemaResourceProvider implements SchemaProvider { populateDestinationSchemas(schemaResources.getDestinationSchemaPaths()); populateSubmissionDataSchemas(schemaResources.getSubmissionDataSchemaPath()); - LOGGER.info("Initialised sdk schemas"); + LOGGER.info("Initialised SDK schemas"); } private void populateMetadataSchemas(final List<String> metadataSchemaPaths) { diff --git a/pom.xml b/pom.xml index 60704b78d9fc65a785b52c66d36dce80240b58b3..28c3d1957ddcebfa47799afc544214f077d3f7cd 100644 --- a/pom.xml +++ b/pom.xml @@ -110,7 +110,6 @@ <module>api</module> <module>core</module> <module>client</module> - <module>demo</module> </modules> <dependencyManagement> @@ -135,6 +134,12 @@ <version>${project.version}</version> </dependency> + <dependency> + <groupId>dev.fitko.fitconnect.sdk</groupId> + <artifactId>cli</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> <groupId>dev.fitko.fitconnect.sdk</groupId> <artifactId>integration-tests</artifactId> @@ -488,6 +493,15 @@ <module>integration-tests</module> </modules> </profile> + <profile> + <id>cli-tests</id> + <activation> + <activeByDefault>true</activeByDefault> + </activation> + <modules> + <module>cli</module> + </modules> + </profile> <profile> <id>demo</id> <activation>