From 9bf810eeedbd44b385b67af814aa25fa9d22a462 Mon Sep 17 00:00:00 2001
From: Martin Vogel <martin.vogel@sinc.de>
Date: Fri, 9 Sep 2022 10:05:33 +0000
Subject: [PATCH] =?UTF-8?q?Senden=20von=20bereits=20verschl=C3=BCsselten?=
 =?UTF-8?q?=20Antr=C3=A4gen=20inkl.=20Abruf=20von=20PK?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../fitko/fitconnect/client/SenderClient.java | 533 ++++++++----------
 .../fitconnect/client/SubscriberClient.java   |  13 +-
 .../client/cmd/CommandLineClient.java         |  35 +-
 .../client/factory/ClientFactory.java         |   4 +-
 .../client/model/AttachmentPayload.java       |  20 +
 .../fitconnect/client/model/DataPayload.java  |  19 +
 .../client/model/ReceivedSubmission.java      |  18 +
 .../client/model/ServiceTypePayload.java      |  12 +
 .../client/model/SubmissionPayload.java       |  26 +
 .../SendEncryptedSubmissionStrategy.java      |  92 +++
 .../strategies/SendNewSubmissionStrategy.java | 234 ++++++++
 .../client/strategies/SubmitStrategy.java     |  30 +
 .../client/util/SubmissionUtil.java           | 139 +++++
 .../client/ClientIntegrationTest.java         | 115 +++-
 .../fitconnect/client/SenderClientTest.java   | 126 +++--
 .../client/cmd/CommandLineClientTest.java     |  13 +-
 client/src/test/resources/test-config.yml     |   8 +-
 .../core/auth/DefaultOAuthService.java        |   2 +-
 18 files changed, 1043 insertions(+), 396 deletions(-)
 create mode 100644 client/src/main/java/de/fitko/fitconnect/client/model/AttachmentPayload.java
 create mode 100644 client/src/main/java/de/fitko/fitconnect/client/model/DataPayload.java
 create mode 100644 client/src/main/java/de/fitko/fitconnect/client/model/ReceivedSubmission.java
 create mode 100644 client/src/main/java/de/fitko/fitconnect/client/model/ServiceTypePayload.java
 create mode 100644 client/src/main/java/de/fitko/fitconnect/client/model/SubmissionPayload.java
 create mode 100644 client/src/main/java/de/fitko/fitconnect/client/strategies/SendEncryptedSubmissionStrategy.java
 create mode 100644 client/src/main/java/de/fitko/fitconnect/client/strategies/SendNewSubmissionStrategy.java
 create mode 100644 client/src/main/java/de/fitko/fitconnect/client/strategies/SubmitStrategy.java
 create mode 100644 client/src/main/java/de/fitko/fitconnect/client/util/SubmissionUtil.java

diff --git a/client/src/main/java/de/fitko/fitconnect/client/SenderClient.java b/client/src/main/java/de/fitko/fitconnect/client/SenderClient.java
index ea6225595..872f0f081 100644
--- a/client/src/main/java/de/fitko/fitconnect/client/SenderClient.java
+++ b/client/src/main/java/de/fitko/fitconnect/client/SenderClient.java
@@ -3,37 +3,30 @@ package de.fitko.fitconnect.client;
 import com.nimbusds.jose.jwk.RSAKey;
 import de.fitko.fitconnect.api.config.Environment;
 import de.fitko.fitconnect.api.domain.model.destination.Destination;
-import de.fitko.fitconnect.api.domain.model.destination.DestinationService;
-import de.fitko.fitconnect.api.domain.model.metadata.ContentStructure;
-import de.fitko.fitconnect.api.domain.model.metadata.Metadata;
-import de.fitko.fitconnect.api.domain.model.metadata.PublicServiceType;
-import de.fitko.fitconnect.api.domain.model.metadata.attachment.Attachment;
-import de.fitko.fitconnect.api.domain.model.metadata.attachment.Purpose;
-import de.fitko.fitconnect.api.domain.model.metadata.attachment.signature.AttachmentSignatureType;
-import de.fitko.fitconnect.api.domain.model.metadata.attachment.signature.Hash__1;
-import de.fitko.fitconnect.api.domain.model.metadata.data.*;
-import de.fitko.fitconnect.api.domain.model.submission.CreateSubmission;
+import de.fitko.fitconnect.api.domain.model.metadata.data.MimeType;
 import de.fitko.fitconnect.api.domain.model.submission.ServiceType;
-import de.fitko.fitconnect.api.domain.model.submission.SubmissionForPickup;
 import de.fitko.fitconnect.api.domain.model.submission.SubmitSubmission;
 import de.fitko.fitconnect.api.domain.validation.ValidationResult;
-import de.fitko.fitconnect.api.exceptions.*;
+import de.fitko.fitconnect.api.exceptions.KeyNotRetrievedException;
+import de.fitko.fitconnect.api.exceptions.RestApiException;
 import de.fitko.fitconnect.api.services.Sender;
-import lombok.Builder;
-import lombok.Getter;
-import lombok.With;
-import org.apache.tika.Tika;
+import de.fitko.fitconnect.client.model.AttachmentPayload;
+import de.fitko.fitconnect.client.model.DataPayload;
+import de.fitko.fitconnect.client.model.ServiceTypePayload;
+import de.fitko.fitconnect.client.model.SubmissionPayload;
+import de.fitko.fitconnect.client.strategies.SendEncryptedSubmissionStrategy;
+import de.fitko.fitconnect.client.strategies.SendNewSubmissionStrategy;
+import de.fitko.fitconnect.client.strategies.SubmitStrategy;
+import de.fitko.fitconnect.client.util.SubmissionUtil;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import java.io.File;
-import java.io.IOException;
-import java.net.URI;
-import java.net.URLConnection;
 import java.nio.charset.StandardCharsets;
-import java.nio.file.Files;
-import java.nio.file.Paths;
-import java.util.*;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.UUID;
 import java.util.stream.Collectors;
 
 /**
@@ -46,10 +39,35 @@ public final class SenderClient {
     private SenderClient() {
     }
 
-    public static WithDestination build(final Sender sender, final String metadataSchema, final Environment environment) {
+    public static Start build(final Sender sender, final String metadataSchema, final Environment environment) {
         return new ClientBuilder(sender, metadataSchema, environment);
     }
 
+    public interface Start {
+
+        /**
+         * Creates new submission request.
+         *
+         * @return next step to add (optional) attachments
+         */
+        WithAttachments newSubmission();
+
+        /**
+         * Creates new submission request wit already encrypted data.
+         *
+         * @return next step to add (optional) attachments
+         */
+        WithEncryptedAttachments newSubmissionWithEncryptedData();
+
+        /**
+         * Retrieve the public encryption key
+         *
+         * @param destinationId unique identifier of a {@link Destination}
+         * @return optional string containing the public JWK
+         */
+        Optional<String> getPublicKey(UUID destinationId);
+    }
+
     public interface WithDestination {
 
         /**
@@ -71,6 +89,7 @@ public final class SenderClient {
          */
         WithData withAttachments(List<File> attachments);
 
+
         /**
          * Sends the submission with an attachments
          *
@@ -79,21 +98,59 @@ public final class SenderClient {
          */
         WithData withAttachment(File attachment);
 
+
         /**
          * XML data as string.
          *
          * @param data json string
          * @return next step to submit the data
          */
-        Submit withJsonData(String data);
+        WithDestination withJsonData(String data);
 
         /**
-         * JSON data as string.
+         * XML data as string.
          *
          * @param data xml string
          * @return next step to submit the data
          */
-        Submit withXmlData(String data);
+        WithDestination withXmlData(String data);
+    }
+
+    public interface WithEncryptedAttachments {
+
+        /**
+         * Sends the submission with a list of already encrypted attachments
+         *
+         * @param encryptedAttachments map of encrypted attachments on attachment UUIDs
+         *
+         * @return the next step where additional encrypted data can be added to the submission
+         */
+        WithEncryptedData withEncryptedAttachments(Map<UUID, String> encryptedAttachments);
+
+        /**
+         * Sends the submission with an attachments
+         *
+         * @param attachmentId unique identifier attachment that is uploaded
+         * @param attachment encrypted attachment that is sent with the submission
+         * @return the next step where additional data can be added to the submission
+         */
+        WithEncryptedData withEncryptedAttachment(final UUID attachmentId, final String attachment);
+
+        /**
+         * JSON data as encrypted JWE string.
+         *
+         * @param encryptedData encrypted json string
+         * @return next step to submit the data
+         */
+        WithEncryptedMetadata withEncryptedJsonData(String encryptedData);
+
+        /**
+         * XML data as encrypted JWE string.
+         *
+         * @param encryptedData encrypted xml string
+         * @return next step to submit the data
+         */
+        WithEncryptedMetadata withEncryptedXmlData(String encryptedData);
     }
 
     public interface WithServiceType {
@@ -106,7 +163,7 @@ public final class SenderClient {
          * @return next step to add attachments
          * @see <a href="https://fimportal.de/">Search For Service Types</a>
          */
-        WithAttachments withServiceType(String name, String leikaKey);
+        Submit withServiceType(String name, String leikaKey);
 
         /**
          * Sets a {@link ServiceType} for a submission
@@ -117,7 +174,37 @@ public final class SenderClient {
          * @return next step to add attachments
          * @see <a href="https://fimportal.de/">Search For Service Types</a>
          */
-        WithAttachments withServiceType(String name, String description, String leikaKey);
+        Submit withServiceType(String name, String description, String leikaKey);
+    }
+
+    public interface WithEncryptedData {
+
+        /**
+         * JSON data as encrypted JWE string.
+         *
+         * @param encryptedData encrypted json string
+         * @return next step to add encrypted metadata
+         */
+        WithEncryptedMetadata withEncryptedJsonData(String encryptedData);
+
+        /**
+         * XML data as encrypted JWE string.
+         *
+         * @param encryptedData encrypted xml string
+         * @return next step to add encrypted metadata
+         */
+        WithEncryptedMetadata withEncryptedXmlData(String encryptedData);
+    }
+
+    public interface WithEncryptedMetadata {
+
+        /**
+         * JSON data as encrypted JWE string.
+         *
+         * @param encryptedMetadata encrypted JWE string of the metadata
+         * @return next step to add a destination
+         */
+        WithDestination withEncryptedMetadata(String encryptedMetadata);
     }
 
     public interface WithData {
@@ -128,7 +215,7 @@ public final class SenderClient {
          * @param data json string
          * @return next step to submit the data
          */
-        Submit withJsonData(String data);
+        WithDestination withJsonData(String data);
 
         /**
          * JSON data as string.
@@ -136,7 +223,7 @@ public final class SenderClient {
          * @param data xml string
          * @return next step to submit the data
          */
-        Submit withXmlData(String data);
+        WithDestination withXmlData(String data);
 
     }
 
@@ -153,346 +240,176 @@ public final class SenderClient {
     /**
      * ClientBuilder that implements all steps to guide through the API calls
      */
-    public static class ClientBuilder implements WithDestination, WithServiceType, WithAttachments, WithData, Submit {
+    public static class ClientBuilder implements
+            Start,
+            WithAttachments, WithEncryptedAttachments,
+            WithData, WithEncryptedData,
+            WithEncryptedMetadata,
+            WithDestination,
+            WithServiceType,
+            Submit {
 
         private final Sender sender;
         private final Environment environment;
         private final String metadataSchema;
 
-        private DataPayload dataPayload;
-        private UUID destinationId;
-        private ServiceType serviceType;
-        private List<File> attachments = new ArrayList<>();
+        private SubmitStrategy submitStrategy;
+        private SubmissionPayload submissionPayload;
 
         public ClientBuilder(final Sender sender, final String metadataSchema, final Environment environment) {
             this.sender = sender;
             this.metadataSchema = metadataSchema;
             this.environment = environment;
+            this.submissionPayload = new SubmissionPayload();
         }
 
         @Override
-        public WithServiceType withDestination(final UUID destinationId) {
-            this.destinationId = destinationId;
+        public WithAttachments newSubmission() {
+            this.submitStrategy = new SendNewSubmissionStrategy(sender, metadataSchema, environment);
             return this;
         }
 
         @Override
-        public WithAttachments withServiceType(final String name, final String leikaKey) {
-            this.serviceType = createServiceTypeWithoutDescription(name, leikaKey);
+        public WithEncryptedAttachments newSubmissionWithEncryptedData() {
+            this.submitStrategy = new SendEncryptedSubmissionStrategy(sender);
             return this;
         }
 
         @Override
-        public WithAttachments withServiceType(final String name, final String description, final String leikaKey) {
-            this.serviceType = createServiceTypeWithDescription(name, description, leikaKey);
-            return this;
+        public Optional<String> getPublicKey(final UUID destinationId) {
+            try {
+                return retrievePublicKey(destinationId);
+            } catch (final RestApiException | KeyNotRetrievedException e) {
+                LOGGER.error("Public encryption key could not be retrieved", e);
+            }
+            return Optional.empty();
         }
 
         @Override
-        public Submit withXmlData(final String data) {
-            this.dataPayload = getDataPayload(data, MimeType.APPLICATION_XML);
+        public WithServiceType withDestination(final UUID destinationId) {
+            this.submissionPayload = this.submissionPayload.withDestinationId(destinationId);
             return this;
         }
 
         @Override
-        public Submit withJsonData(final String data) {
-            this.dataPayload = getDataPayload(data, MimeType.APPLICATION_JSON);
+        public WithData withAttachments(final List<File> attachments) {
+            final UUID attachmentId = UUID.randomUUID();
+            final List<AttachmentPayload> attachmentPayloads = attachments.stream()
+                    .map(SubmissionUtil.getFileAttachmentPayload(attachmentId))
+                    .collect(Collectors.toList());
+            submissionPayload = submissionPayload.withAttachments(attachmentPayloads);
             return this;
         }
 
         @Override
-        public WithData withAttachment(final File attachmentFile) {
-            this.attachments = attachmentFile != null ? List.of(attachmentFile) : List.of();
+        public WithData withAttachment(final File attachment) {
+            if (attachment != null) {
+                return withAttachments(List.of(attachment));
+            }
             return this;
         }
 
         @Override
-        public WithData withAttachments(final List<File> attachmentFiles) {
-            this.attachments = attachmentFiles;
+        public WithDestination withJsonData(final String data) {
+            final DataPayload dataPayload = DataPayload.builder()
+                    .rawData(data == null ? null : data.getBytes(StandardCharsets.UTF_8))
+                    .mimeType(MimeType.APPLICATION_JSON)
+                    .build();
+            submissionPayload = submissionPayload.withData(dataPayload);
             return this;
         }
 
         @Override
-        public Optional<SubmitSubmission> submit() {
-            try {
-
-                if (!isAllNecessaryDataSet()) {
-                    return Optional.empty();
-                }
-
-                /** Get encryption key for destination **/
-                final Destination destination = sender.getDestination(destinationId);
-                final RSAKey encryptionKey = sender.getEncryptionKeyForDestination(destinationId, destination.getEncryptionKid());
-                final ValidationResult validationResult = sender.validatePublicKey(encryptionKey);
-                if (validationResult.hasError()) {
-                    LOGGER.warn("The public key is not valid, {}", validationResult.getError().getMessage());
-                    if (!environment.isSilentKeyValidation()) {
-                        return Optional.empty();
-                    }
-                }
-
-                /** Create new submission and announce attachments **/
-                final List<AttachmentPayload> attachmentPayloads = encryptAndHashFiles(encryptionKey, attachments);
-                final List<UUID> attachmentIdsToAnnounce = toAttachmentIds(attachmentPayloads);
-                final CreateSubmission newSubmission = createSubmission(attachmentIdsToAnnounce);
-                final SubmissionForPickup announcedSubmission = sender.createSubmission(newSubmission);
-                final UUID submissionId = announcedSubmission.getSubmissionId();
-                uploadAttachments(attachmentPayloads, submissionId);
-
-                /** Prepare submit submission with announced submission id **/
-                final SubmitSubmission submission = new SubmitSubmission();
-                submission.setSubmissionId(submissionId);
-
-                /** Create attachment hashes **/
-                final List<Attachment> hashedAttachments = toHashedAttachments(attachmentPayloads);
-
-                /** Set encrypted metadata with data payload and attachments  **/
-                LOGGER.info("Adding data payload with mime-type {} to submission", this.dataPayload.mimeType);
-                final DataPayload dataToSend = getEncryptedData(this.dataPayload, destination, encryptionKey);
-                final Metadata metadata = createMetadata(hashedAttachments, dataToSend);
-                final ValidationResult validatedMetadata = sender.validateMetadata(metadata, metadataSchema);
-                if (validatedMetadata.hasError()) {
-                    LOGGER.error("Metadata does not match schema", validatedMetadata.getError());
-                    sender.rejectSubmission(submissionId, destinationId, announcedSubmission.getCaseId());
-                    return Optional.empty();
-                }
-                submission.setEncryptedData(dataToSend.getEncryptedData());
-                submission.setEncryptedMetadata(sender.encryptObject(encryptionKey, metadata));
-
-                /** submit submission **/
-                sender.sendSubmission(submission);
-                LOGGER.info("SUCCESSFULLY HANDED IN SUBMISSION !");
-                return Optional.of(submission);
-
-            } catch (final EncryptionException e) {
-                LOGGER.error("Encrypting submission failed", e);
-            } catch (final RestApiException e) {
-                LOGGER.error("Sending submission failed", e);
-            } catch (final SchemaNotFoundException e) {
-                LOGGER.error("Required schema to send valid submission not found", e);
-            } catch (final SubmissionNotCreatedException e) {
-                LOGGER.error("Failed to announce new submission", e);
-            } catch (final KeyNotRetrievedException e) {
-                LOGGER.error("Getting encryption key for destination {} failed", destinationId, e);
-            } catch (final AttachmentCreationException e) {
-                LOGGER.error("Reading file failed. Attachment will not be created.", e);
-            }
-            return Optional.empty();
-        }
-
-        private DataPayload getEncryptedData(final DataPayload dataPayload, final Destination destination, final RSAKey encryptionKey) {
-            final List<SubmissionSchema> submissionSchemas = getSubmissionSchemasFromDestination(destination);
-            final Optional<URI> schemaUriForMimeType = getSchemaUriForMimeType(submissionSchemas, dataPayload.mimeType);
-            if (schemaUriForMimeType.isEmpty()) {
-                throw new SchemaNotFoundException("No data schema for mime-type " + dataPayload.mimeType + " found, please check the allowed type for destination");
-            }
-            final String hashedData = sender.createHash(dataPayload.rawData);
-            final String encryptedData = sender.encryptBytes(encryptionKey, dataPayload.rawData);
-            return dataPayload.withSchemaUri(schemaUriForMimeType.get())
-                    .withEncryptedData(encryptedData)
-                    .withHashedData(hashedData);
-        }
-
-        private List<Attachment> toHashedAttachments(final List<AttachmentPayload> attachmentPayloads) {
-            return attachmentPayloads.stream()
-                    .map(this::toHashedAttachment)
-                    .collect(Collectors.toList());
-        }
-
-        private CreateSubmission createSubmission(final List<UUID> attachmentIdsToAnnounce) {
-            return CreateSubmission.builder()
-                    .destinationId(destinationId)
-                    .announcedAttachments(attachmentIdsToAnnounce)
-                    .serviceType(serviceType)
-                    .build();
-        }
-
-        private List<UUID> toAttachmentIds(final List<AttachmentPayload> attachmentPayloads) {
-            return attachmentPayloads.stream()
-                    .map(AttachmentPayload::getAttachmentId)
-                    .collect(Collectors.toList());
-        }
-
-        private DataPayload getDataPayload(final String data, final MimeType mimeType) {
-            if (data == null) {
-                return null;
-            }
-            return DataPayload.builder()
-                    .rawData(data.getBytes(StandardCharsets.UTF_8))
-                    .mimeType(mimeType)
+        public WithDestination withXmlData(final String data) {
+            final DataPayload dataPayload = DataPayload.builder()
+                    .rawData(data == null ? null : data.getBytes(StandardCharsets.UTF_8))
+                    .mimeType(MimeType.APPLICATION_XML)
                     .build();
+            submissionPayload = submissionPayload.withData(dataPayload);
+            return this;
         }
 
-        private List<SubmissionSchema> getSubmissionSchemasFromDestination(final Destination destination) {
-            return destination.getServices().stream()
-                    .map(DestinationService::getSubmissionSchemas)
-                    .flatMap(Collection::stream)
+        @Override
+        public WithEncryptedData withEncryptedAttachments(final Map<UUID, String> encryptedAttachments) {
+            final List<AttachmentPayload> attachmentPayloads = encryptedAttachments.entrySet()
+                    .stream()
+                    .map(SubmissionUtil.getEncryptedAttachmentPayload())
                     .collect(Collectors.toList());
+            submissionPayload = submissionPayload.withAttachments(attachmentPayloads);
+            return this;
         }
 
-        private Optional<URI> getSchemaUriForMimeType(final List<SubmissionSchema> submissionSchemas, final MimeType mimeType) {
-            return submissionSchemas.stream()
-                    .filter(schema -> schema.getMimeType().equals(mimeType))
-                    .map(SubmissionSchema::getSchemaUri)
-                    .findFirst();
-        }
-
-        private void uploadAttachments(final List<AttachmentPayload> attachmentPayloads, final UUID submissionId) {
-            if (attachmentPayloads.isEmpty()) {
-                LOGGER.info("No attachments to upload");
-            } else {
-                LOGGER.info("Uploading {} attachment(s)", attachmentPayloads.size());
-            }
-            for (final AttachmentPayload payload : attachmentPayloads) {
-                sender.uploadAttachment(submissionId, payload.attachmentId, payload.encryptedData);
-            }
-        }
-
-        private ServiceType createServiceTypeWithDescription(final String name, final String description, final String leikaKey) {
-            final var serviceTypeModel = new ServiceType();
-            serviceTypeModel.setName(name);
-            serviceTypeModel.setDescription(description);
-            serviceTypeModel.setIdentifier(leikaKey);
-            return serviceTypeModel;
-        }
-
-        private ServiceType createServiceTypeWithoutDescription(final String name, final String leikaKey) {
-            final var serviceTypeModel = new ServiceType();
-            serviceTypeModel.setName(name);
-            serviceTypeModel.setIdentifier(leikaKey);
-            return serviceTypeModel;
-        }
-
-        private Data createData(final DataPayload dataPayload) {
-            final var hash = new Hash();
-            hash.setContent(dataPayload.hashedData);
-            hash.setDataSignatureType(DataSignatureType.SHA_512);
-
-            final var submissionSchema = new SubmissionSchema();
-            submissionSchema.setMimeType(dataPayload.mimeType);
-            submissionSchema.setSchemaUri(dataPayload.schemaUri);
-
-            final var data = new Data();
-            data.setSubmissionSchema(submissionSchema);
-            data.setHash(hash);
-            return data;
-        }
-
-        private Metadata createMetadata(final List<Attachment> attachments, final DataPayload dataPayload) {
-            final var contentStructure = new ContentStructure();
-            contentStructure.setAttachments(attachments);
-            contentStructure.setData(createData(dataPayload));
-
-            final var metadata = new Metadata();
-            metadata.setContentStructure(contentStructure);
-            metadata.setPublicServiceType(getPublicServiceType());
-            return metadata;
-        }
-
-        private PublicServiceType getPublicServiceType() {
-            final var publicServiceType = new PublicServiceType();
-            publicServiceType.setIdentifier(serviceType.getIdentifier());
-            publicServiceType.setName(serviceType.getName());
-            if (serviceType.getDescription() != null) {
-                publicServiceType.setDescription(serviceType.getDescription());
+        @Override
+        public WithEncryptedData withEncryptedAttachment(final UUID attachmentId, final String attachment) {
+            if (attachment != null) {
+                return withEncryptedAttachments(Map.of(attachmentId, attachment));
             }
-            return publicServiceType;
+            return this;
         }
 
-        private List<AttachmentPayload> encryptAndHashFiles(final RSAKey encryptionKey, final List<File> attachmentFiles) {
-            return attachmentFiles.stream()
-                    .map(this::readRawData)
-                    .map(this::hashBytes)
-                    .map(payload -> encryptBytes(encryptionKey, payload))
-                    .map(this::setMimeType)
-                    .collect(Collectors.toList());
+        @Override
+        public WithEncryptedMetadata withEncryptedJsonData(final String encryptedData) {
+            final DataPayload dataPayload = DataPayload.builder()
+                    .encryptedData(encryptedData)
+                    .mimeType(MimeType.APPLICATION_JSON)
+                    .build();
+            submissionPayload = submissionPayload.withData(dataPayload);
+            return this;
         }
 
-        private AttachmentPayload readRawData(final File file) {
-            try {
-                final byte[] rawData = Files.readAllBytes(Paths.get(file.getPath()));
-                return AttachmentPayload.builder().file(file).rawData(rawData).build();
-            } catch (final IOException e) {
-                throw new AttachmentCreationException("Attachment '" + file.getAbsolutePath() + "' could not be read ", e);
-            }
+        @Override
+        public WithEncryptedMetadata withEncryptedXmlData(final String encryptedData) {
+            final DataPayload dataPayload = DataPayload.builder()
+                    .encryptedData(encryptedData)
+                    .mimeType(MimeType.APPLICATION_XML)
+                    .build();
+            submissionPayload = submissionPayload.withData(dataPayload);
+            return this;
         }
 
-        private Attachment toHashedAttachment(final AttachmentPayload attachmentPayload) {
-            final var attachment = new Attachment();
-            attachment.setAttachmentId(attachmentPayload.attachmentId);
-            attachment.setPurpose(Purpose.ATTACHMENT);
-            attachment.setFilename(attachmentPayload.file.getName());
-            attachment.setMimeType(attachmentPayload.mimeType);
-
-            final var hash = new Hash__1();
-            hash.setContent(attachmentPayload.hashedData);
-            hash.setAttachmentSignatureType(AttachmentSignatureType.SHA_512);
-            attachment.setHash(hash);
-            return attachment;
+        @Override
+        public WithDestination withEncryptedMetadata(final String encryptedMetadata) {
+            submissionPayload = submissionPayload.withEncryptedMetadata(encryptedMetadata);
+            return this;
         }
 
-        private AttachmentPayload encryptBytes(final RSAKey encryptionKey, final AttachmentPayload attachmentPayload) {
-            final String encryptedAttachment = sender.encryptBytes(encryptionKey, attachmentPayload.rawData);
-            return attachmentPayload.withEncryptedData(encryptedAttachment);
+        @Override
+        public Submit withServiceType(final String name, final String leikaKey) {
+            final ServiceTypePayload serviceTypePayload = ServiceTypePayload.builder()
+                    .name(name)
+                    .leikaKey(leikaKey)
+                    .build();
+            submissionPayload = submissionPayload.withServiceTypePayLoad(serviceTypePayload);
+            return this;
         }
 
-        private AttachmentPayload hashBytes(final AttachmentPayload attachmentPayload) {
-            final String hashedBytes = sender.createHash(attachmentPayload.rawData);
-            return attachmentPayload.withHashedData(hashedBytes);
+        @Override
+        public Submit withServiceType(final String name, final String description, final String leikaKey) {
+            final ServiceTypePayload serviceTypePayload = ServiceTypePayload.builder()
+                    .name(name)
+                    .description(description)
+                    .leikaKey(leikaKey)
+                    .build();
+            submissionPayload = submissionPayload.withServiceTypePayLoad(serviceTypePayload);
+            return this;
         }
 
-        private AttachmentPayload setMimeType(final AttachmentPayload attachmentPayload) {
-            final File file = attachmentPayload.file;
-            String mimeType;
-            try {
-                final Tika mimeTypeGuesser = new Tika();
-                mimeType = mimeTypeGuesser.detect(file);
-            } catch (final IOException e) {
-                mimeType = URLConnection.guessContentTypeFromName(file.getName());
-            }
-            LOGGER.info("Detected attachment mime-type {}", mimeType);
-            return attachmentPayload.withMimeType(mimeType);
+        @Override
+        public Optional<SubmitSubmission> submit() {
+            return submitStrategy.send(submissionPayload);
         }
 
-        private boolean isAllNecessaryDataSet() {
-            if (this.dataPayload == null) {
-                LOGGER.error("Data is mandatory, but was null.");
-                return false;
-            } else if (this.serviceType.getName() == null) {
-                LOGGER.error("Service type name is mandatory, but was null.");
-                return false;
-            } else if (this.serviceType.getIdentifier() == null) {
-                LOGGER.error("Service type identifier is mandatory, but was null.");
-                return false;
-            } else if (this.destinationId == null) {
-                LOGGER.error("DestinationId is mandatory, but was null.");
-                return false;
-            } else {
-                return true;
+        private Optional<String> retrievePublicKey(final UUID destinationId) {
+            final Destination destination = sender.getDestination(destinationId);
+            final RSAKey encryptionKey = sender.getEncryptionKeyForDestination(destinationId, destination.getEncryptionKid());
+            final ValidationResult validationResult = sender.validatePublicKey(encryptionKey);
+            if (validationResult.hasError()) {
+                LOGGER.warn("The public key is not valid, {}", validationResult.getError().getMessage());
+                if (!environment.isSilentKeyValidation()) {
+                    return Optional.empty();
+                }
             }
-        }
-
-        @With
-        @Getter
-        @Builder
-        private static class AttachmentPayload {
-            private File file;
-            private byte[] rawData;
-            private String hashedData;
-            private String encryptedData;
-            private String mimeType;
-            private final UUID attachmentId = UUID.randomUUID();
-        }
-
-        @With
-        @Getter
-        @Builder
-        private static class DataPayload {
-            private byte[] rawData;
-            private String hashedData;
-            private String encryptedData;
-            private MimeType mimeType;
-            private URI schemaUri;
+            return Optional.of(encryptionKey.toJSONString());
         }
     }
 }
diff --git a/client/src/main/java/de/fitko/fitconnect/client/SubscriberClient.java b/client/src/main/java/de/fitko/fitconnect/client/SubscriberClient.java
index f471e6260..c6ebac7d7 100644
--- a/client/src/main/java/de/fitko/fitconnect/client/SubscriberClient.java
+++ b/client/src/main/java/de/fitko/fitconnect/client/SubscriberClient.java
@@ -7,15 +7,12 @@ import de.fitko.fitconnect.api.domain.model.metadata.attachment.Attachment;
 import de.fitko.fitconnect.api.domain.model.metadata.attachment.AttachmentWithData;
 import de.fitko.fitconnect.api.domain.model.metadata.attachment.signature.Hash__1;
 import de.fitko.fitconnect.api.domain.model.metadata.data.Data;
-import de.fitko.fitconnect.api.domain.model.metadata.data.MimeType;
 import de.fitko.fitconnect.api.domain.model.submission.Submission;
 import de.fitko.fitconnect.api.domain.model.submission.SubmissionForPickup;
 import de.fitko.fitconnect.api.domain.validation.ValidationResult;
 import de.fitko.fitconnect.api.exceptions.*;
 import de.fitko.fitconnect.api.services.Subscriber;
-import lombok.Builder;
-import lombok.Getter;
-import lombok.ToString;
+import de.fitko.fitconnect.client.model.ReceivedSubmission;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -244,12 +241,4 @@ public final class SubscriberClient {
         }
     }
 
-    @Getter
-    @Builder
-    @ToString
-    public static class ReceivedSubmission {
-        String data;
-        MimeType mimeType;
-        List<AttachmentWithData> attachments;
-    }
 }
diff --git a/client/src/main/java/de/fitko/fitconnect/client/cmd/CommandLineClient.java b/client/src/main/java/de/fitko/fitconnect/client/cmd/CommandLineClient.java
index 09c063b16..4b50b75e3 100644
--- a/client/src/main/java/de/fitko/fitconnect/client/cmd/CommandLineClient.java
+++ b/client/src/main/java/de/fitko/fitconnect/client/cmd/CommandLineClient.java
@@ -3,6 +3,7 @@ package de.fitko.fitconnect.client.cmd;
 import com.beust.jcommander.JCommander;
 import com.beust.jcommander.ParameterException;
 import de.fitko.fitconnect.api.domain.model.metadata.attachment.AttachmentWithData;
+import de.fitko.fitconnect.client.model.ReceivedSubmission;
 import de.fitko.fitconnect.client.SenderClient;
 import de.fitko.fitconnect.client.SubscriberClient;
 import de.fitko.fitconnect.client.cmd.commands.ListAllSubmissionsCommand;
@@ -17,6 +18,7 @@ import java.io.IOException;
 import java.nio.charset.StandardCharsets;
 import java.nio.file.Files;
 import java.nio.file.Path;
+import java.util.List;
 import java.util.stream.Collectors;
 
 import static de.fitko.fitconnect.client.cmd.util.AttachmentDataType.JSON;
@@ -32,10 +34,10 @@ public class CommandLineClient {
 
     private final JCommander jc;
 
-    private final SenderClient.WithDestination senderClient;
+    private final SenderClient.Start senderClient;
     private final SubscriberClient.RequestSubmission subscriberClient;
 
-    public CommandLineClient(final SenderClient.WithDestination senderClient,
+    public CommandLineClient(final SenderClient.Start senderClient,
                              final SubscriberClient.RequestSubmission subscriberClient) {
 
         this.sendSubmissionCommand = new SendSubmissionCommand();
@@ -108,17 +110,32 @@ public class CommandLineClient {
 
     private void sendSubmission(final SendSubmissionCommand sendSubmissionCommand) {
         LOGGER.info("Sending new submission to destination {}", sendSubmissionCommand.destinationId);
-        final var client = senderClient
-                .withDestination(sendSubmissionCommand.destinationId)
-                .withServiceType(sendSubmissionCommand.serviceName, sendSubmissionCommand.leikaKey)
-                .withAttachments(sendSubmissionCommand.attachments.stream().map(File::new).collect(Collectors.toList()));
+        final List<File> files = sendSubmissionCommand.attachments.stream().map(File::new).collect(Collectors.toList());
         if (sendSubmissionCommand.dataType == JSON) {
-            client.withJsonData(sendSubmissionCommand.data).submit();
+            sendWithJsonData(sendSubmissionCommand, files);
         } else if (sendSubmissionCommand.dataType == XML) {
-            client.withXmlData(sendSubmissionCommand.data).submit();
+            sendWithXmlData(sendSubmissionCommand, files);
         }
     }
 
+    private void sendWithXmlData(final SendSubmissionCommand sendSubmissionCommand, final List<File> files) {
+        senderClient.newSubmission()
+                .withAttachments(files)
+                .withXmlData(sendSubmissionCommand.data)
+                .withDestination(sendSubmissionCommand.destinationId)
+                .withServiceType(sendSubmissionCommand.serviceName, sendSubmissionCommand.leikaKey)
+                .submit();
+    }
+
+    private void sendWithJsonData(final SendSubmissionCommand sendSubmissionCommand, final List<File> files) {
+        senderClient.newSubmission()
+                .withAttachments(files)
+                .withJsonData(sendSubmissionCommand.data)
+                .withDestination(sendSubmissionCommand.destinationId)
+                .withServiceType(sendSubmissionCommand.serviceName, sendSubmissionCommand.leikaKey)
+                .submit();
+    }
+
     private String getTargetFolderPath(final ListOneSubmissionCommand listOneSubmissionCommand) {
         if (listOneSubmissionCommand.targetFolder != null) {
             return listOneSubmissionCommand.targetFolder + "/" + listOneSubmissionCommand.submissionId;
@@ -127,7 +144,7 @@ public class CommandLineClient {
         }
     }
 
-    private void writeData(final SubscriberClient.ReceivedSubmission submissionData, final String dataDirPath) throws IOException {
+    private void writeData(final ReceivedSubmission submissionData, final String dataDirPath) throws IOException {
         LOGGER.info("Creating data directory for submission in {}", dataDirPath);
         Files.createDirectories(Path.of(dataDirPath));
 
diff --git a/client/src/main/java/de/fitko/fitconnect/client/factory/ClientFactory.java b/client/src/main/java/de/fitko/fitconnect/client/factory/ClientFactory.java
index 48a4a8cb9..291357f70 100644
--- a/client/src/main/java/de/fitko/fitconnect/client/factory/ClientFactory.java
+++ b/client/src/main/java/de/fitko/fitconnect/client/factory/ClientFactory.java
@@ -58,7 +58,7 @@ public final class ClientFactory {
      *
      * @return the sender client
      */
-    public static SenderClient.WithDestination senderClient() {
+    public static SenderClient.Start senderClient() {
         final ApplicationConfig config = loadConfig();
         return senderClient(config);
     }
@@ -68,7 +68,7 @@ public final class ClientFactory {
      *
      * @return the sender client
      */
-    public static SenderClient.WithDestination senderClient(final ApplicationConfig config) {
+    public static SenderClient.Start senderClient(final ApplicationConfig config) {
         LOGGER.info(SENDER_BANNER);
         LOGGER.info("Initializing sender client ...");
         final Sender sender = getSender(config);
diff --git a/client/src/main/java/de/fitko/fitconnect/client/model/AttachmentPayload.java b/client/src/main/java/de/fitko/fitconnect/client/model/AttachmentPayload.java
new file mode 100644
index 000000000..bbf351500
--- /dev/null
+++ b/client/src/main/java/de/fitko/fitconnect/client/model/AttachmentPayload.java
@@ -0,0 +1,20 @@
+package de.fitko.fitconnect.client.model;
+
+import lombok.Builder;
+import lombok.Getter;
+import lombok.With;
+
+import java.io.File;
+import java.util.UUID;
+
+@With
+@Getter
+@Builder
+public class AttachmentPayload {
+    private File file;
+    private byte[] rawData;
+    private String hashedData;
+    private String encryptedData;
+    private String mimeType;
+    private UUID attachmentId;
+}
diff --git a/client/src/main/java/de/fitko/fitconnect/client/model/DataPayload.java b/client/src/main/java/de/fitko/fitconnect/client/model/DataPayload.java
new file mode 100644
index 000000000..cdd45adaa
--- /dev/null
+++ b/client/src/main/java/de/fitko/fitconnect/client/model/DataPayload.java
@@ -0,0 +1,19 @@
+package de.fitko.fitconnect.client.model;
+
+import de.fitko.fitconnect.api.domain.model.metadata.data.MimeType;
+import lombok.Builder;
+import lombok.Getter;
+import lombok.With;
+
+import java.net.URI;
+
+@With
+@Getter
+@Builder
+public class DataPayload {
+    private byte[] rawData;
+    private String hashedData;
+    private String encryptedData;
+    private MimeType mimeType;
+    private URI schemaUri;
+}
diff --git a/client/src/main/java/de/fitko/fitconnect/client/model/ReceivedSubmission.java b/client/src/main/java/de/fitko/fitconnect/client/model/ReceivedSubmission.java
new file mode 100644
index 000000000..dd3dc5b15
--- /dev/null
+++ b/client/src/main/java/de/fitko/fitconnect/client/model/ReceivedSubmission.java
@@ -0,0 +1,18 @@
+package de.fitko.fitconnect.client.model;
+
+import de.fitko.fitconnect.api.domain.model.metadata.attachment.AttachmentWithData;
+import de.fitko.fitconnect.api.domain.model.metadata.data.MimeType;
+import lombok.Builder;
+import lombok.Getter;
+import lombok.ToString;
+
+import java.util.List;
+
+@Getter
+@Builder
+@ToString
+public class ReceivedSubmission {
+    private String data;
+    private MimeType mimeType;
+    private List<AttachmentWithData> attachments;
+}
diff --git a/client/src/main/java/de/fitko/fitconnect/client/model/ServiceTypePayload.java b/client/src/main/java/de/fitko/fitconnect/client/model/ServiceTypePayload.java
new file mode 100644
index 000000000..26f505e2e
--- /dev/null
+++ b/client/src/main/java/de/fitko/fitconnect/client/model/ServiceTypePayload.java
@@ -0,0 +1,12 @@
+package de.fitko.fitconnect.client.model;
+
+import lombok.Builder;
+import lombok.Data;
+
+@Data
+@Builder
+public class ServiceTypePayload {
+    private String name;
+    private String description;
+    private String leikaKey;
+}
diff --git a/client/src/main/java/de/fitko/fitconnect/client/model/SubmissionPayload.java b/client/src/main/java/de/fitko/fitconnect/client/model/SubmissionPayload.java
new file mode 100644
index 000000000..b5f57a10c
--- /dev/null
+++ b/client/src/main/java/de/fitko/fitconnect/client/model/SubmissionPayload.java
@@ -0,0 +1,26 @@
+package de.fitko.fitconnect.client.model;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import lombok.With;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.UUID;
+
+@Data
+@With
+@AllArgsConstructor
+@NoArgsConstructor
+public class SubmissionPayload {
+
+    private UUID destinationId;
+    private DataPayload data;
+    private String encryptedMetadata;
+    private ServiceTypePayload serviceTypePayLoad;
+
+    // Optional - default empty List
+    private List<AttachmentPayload> attachments = Collections.emptyList();
+    private List<String> encryptedAttachments = Collections.emptyList();
+}
diff --git a/client/src/main/java/de/fitko/fitconnect/client/strategies/SendEncryptedSubmissionStrategy.java b/client/src/main/java/de/fitko/fitconnect/client/strategies/SendEncryptedSubmissionStrategy.java
new file mode 100644
index 000000000..5c08300d2
--- /dev/null
+++ b/client/src/main/java/de/fitko/fitconnect/client/strategies/SendEncryptedSubmissionStrategy.java
@@ -0,0 +1,92 @@
+package de.fitko.fitconnect.client.strategies;
+
+import de.fitko.fitconnect.api.domain.model.submission.CreateSubmission;
+import de.fitko.fitconnect.api.domain.model.submission.SubmissionForPickup;
+import de.fitko.fitconnect.api.domain.model.submission.SubmitSubmission;
+import de.fitko.fitconnect.api.exceptions.RestApiException;
+import de.fitko.fitconnect.api.exceptions.SubmissionNotCreatedException;
+import de.fitko.fitconnect.api.services.Sender;
+import de.fitko.fitconnect.client.model.AttachmentPayload;
+import de.fitko.fitconnect.client.model.SubmissionPayload;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.List;
+import java.util.Optional;
+import java.util.UUID;
+
+import static de.fitko.fitconnect.client.util.SubmissionUtil.buildSubmissionToAnnounce;
+import static de.fitko.fitconnect.client.util.SubmissionUtil.buildSubmitSubmission;
+
+public class SendEncryptedSubmissionStrategy implements SubmitStrategy {
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(SendEncryptedSubmissionStrategy.class);
+
+    private final Sender sender;
+
+    public SendEncryptedSubmissionStrategy(final Sender sender) {
+        this.sender = sender;
+    }
+
+    @Override
+    public Optional<SubmitSubmission> send(final SubmissionPayload submissionPayload) {
+
+        if (!hasValidPayload(submissionPayload)) {
+            return Optional.empty();
+        }
+
+        try {
+            final UUID submissionId = announceNewSubmission(submissionPayload);
+            final SubmitSubmission submission = buildSubmitSubmission(submissionPayload, submissionId);
+            uploadAttachments(submissionPayload.getAttachments(), submissionId);
+            sender.sendSubmission(submission);
+            LOGGER.info("SUCCESSFULLY HANDED IN SUBMISSION !");
+            return Optional.of(submission);
+
+        } catch (final RestApiException e) {
+            LOGGER.error("Sending submission failed", e);
+        } catch (final SubmissionNotCreatedException e) {
+            LOGGER.error("Failed to announce new submission", e);
+        }
+        return Optional.empty();
+    }
+
+    private UUID announceNewSubmission(final SubmissionPayload submissionPayload) {
+        final CreateSubmission submissionToAnnounce = buildSubmissionToAnnounce(submissionPayload);
+        final SubmissionForPickup announcedSubmission = sender.createSubmission(submissionToAnnounce);
+        return announcedSubmission.getSubmissionId();
+    }
+
+    private void uploadAttachments(final List<AttachmentPayload> attachmentPayloads, final UUID submissionId) {
+        if (attachmentPayloads.isEmpty()) {
+            LOGGER.info("No attachments to upload");
+        } else {
+            LOGGER.info("Uploading {} attachment(s)", attachmentPayloads.size());
+        }
+        for (final AttachmentPayload payload : attachmentPayloads) {
+            sender.uploadAttachment(submissionId, payload.getAttachmentId(), payload.getEncryptedData());
+        }
+    }
+
+    @Override
+    public boolean hasValidPayload(final SubmissionPayload submissionPayload) {
+        if (submissionPayload.getData().getEncryptedData() == null) {
+            LOGGER.error("Data is mandatory, but was null.");
+            return false;
+        } else if (submissionPayload.getEncryptedMetadata() == null) {
+            LOGGER.error("Metadata is mandatory, but was null.");
+            return false;
+        } else if (submissionPayload.getServiceTypePayLoad().getName() == null) {
+            LOGGER.error("Service type name is mandatory, but was null.");
+            return false;
+        } else if (submissionPayload.getServiceTypePayLoad().getLeikaKey() == null) {
+            LOGGER.error("Service type identifier is mandatory, but was null.");
+            return false;
+        } else if (submissionPayload.getDestinationId() == null) {
+            LOGGER.error("DestinationId is mandatory, but was null.");
+            return false;
+        } else {
+            return true;
+        }
+    }
+}
diff --git a/client/src/main/java/de/fitko/fitconnect/client/strategies/SendNewSubmissionStrategy.java b/client/src/main/java/de/fitko/fitconnect/client/strategies/SendNewSubmissionStrategy.java
new file mode 100644
index 000000000..b4856f1f0
--- /dev/null
+++ b/client/src/main/java/de/fitko/fitconnect/client/strategies/SendNewSubmissionStrategy.java
@@ -0,0 +1,234 @@
+package de.fitko.fitconnect.client.strategies;
+
+import com.nimbusds.jose.jwk.RSAKey;
+import de.fitko.fitconnect.api.config.Environment;
+import de.fitko.fitconnect.api.domain.model.destination.Destination;
+import de.fitko.fitconnect.api.domain.model.destination.DestinationService;
+import de.fitko.fitconnect.api.domain.model.metadata.Metadata;
+import de.fitko.fitconnect.api.domain.model.metadata.PublicServiceType;
+import de.fitko.fitconnect.api.domain.model.metadata.attachment.Attachment;
+import de.fitko.fitconnect.api.domain.model.metadata.data.Data;
+import de.fitko.fitconnect.api.domain.model.metadata.data.MimeType;
+import de.fitko.fitconnect.api.domain.model.metadata.data.SubmissionSchema;
+import de.fitko.fitconnect.api.domain.model.submission.CreateSubmission;
+import de.fitko.fitconnect.api.domain.model.submission.ServiceType;
+import de.fitko.fitconnect.api.domain.model.submission.SubmissionForPickup;
+import de.fitko.fitconnect.api.domain.model.submission.SubmitSubmission;
+import de.fitko.fitconnect.api.domain.validation.ValidationResult;
+import de.fitko.fitconnect.api.exceptions.*;
+import de.fitko.fitconnect.api.services.Sender;
+import de.fitko.fitconnect.client.model.AttachmentPayload;
+import de.fitko.fitconnect.client.model.DataPayload;
+import de.fitko.fitconnect.client.model.ServiceTypePayload;
+import de.fitko.fitconnect.client.model.SubmissionPayload;
+import de.fitko.fitconnect.client.util.SubmissionUtil;
+import org.apache.tika.Tika;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.URI;
+import java.net.URLConnection;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.util.*;
+import java.util.stream.Collectors;
+
+public class SendNewSubmissionStrategy implements SubmitStrategy {
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(SendNewSubmissionStrategy.class);
+
+    private final Sender sender;
+    private final String metadataSchema;
+    private final Environment environment;
+
+    public SendNewSubmissionStrategy(final Sender sender, final String metadataSchema, final Environment environment) {
+        this.sender = sender;
+        this.metadataSchema = metadataSchema;
+        this.environment = environment;
+    }
+
+    @Override
+    public Optional<SubmitSubmission> send(final SubmissionPayload submissionPayload) {
+
+        if (!hasValidPayload(submissionPayload)) return Optional.empty();
+
+        final UUID destinationId = submissionPayload.getDestinationId();
+        final ServiceTypePayload serviceTypePayLoad = submissionPayload.getServiceTypePayLoad();
+        final DataPayload userDataPayload = submissionPayload.getData();
+        final List<AttachmentPayload> attachments = submissionPayload.getAttachments();
+
+        try {
+
+            /** Get encryption key for destination **/
+            final Destination destination = sender.getDestination(destinationId);
+            final RSAKey encryptionKey = sender.getEncryptionKeyForDestination(destinationId, destination.getEncryptionKid());
+            final ValidationResult validationResult = sender.validatePublicKey(encryptionKey);
+            if (validationResult.hasError()) {
+                LOGGER.warn("The public key is not valid, {}", validationResult.getError().getMessage());
+                if (!environment.isSilentKeyValidation()) {
+                    return Optional.empty();
+                }
+            }
+
+            /** Create new submission and announce attachments **/
+            final List<AttachmentPayload> encryptedAttachments = encryptAndHashAttachments(encryptionKey, attachments);
+            final CreateSubmission newSubmission = buildSubmissionToAnnounce(destinationId, serviceTypePayLoad, encryptedAttachments);
+            final SubmissionForPickup announcedSubmission = sender.createSubmission(newSubmission);
+
+            final UUID submissionId = announcedSubmission.getSubmissionId();
+            uploadAttachments(encryptedAttachments, submissionId);
+
+            /** Create attachment metadata including hashes **/
+            final List<Attachment> attachmentMetadata = SubmissionUtil.toAttachmentMetadata(encryptedAttachments);
+
+            /** Build encrypted data payload from user data**/
+            LOGGER.info("Adding data payload with mime-type {} to submission", userDataPayload.getMimeType());
+            final DataPayload encryptedDataPayload = encryptDataPayload(userDataPayload, destination, encryptionKey);
+
+            /** Set encrypted metadata with data payload and attachments **/
+            final Data dataToSend = SubmissionUtil.buildData(encryptedDataPayload);
+            final PublicServiceType publicServiceType = SubmissionUtil.buildPublicServiceType(serviceTypePayLoad);
+            final Metadata metadata = SubmissionUtil.buildMetadata(attachmentMetadata, dataToSend, publicServiceType);
+
+            final ValidationResult validatedMetadata = sender.validateMetadata(metadata, metadataSchema);
+            if (validatedMetadata.hasError()) {
+                LOGGER.error("Metadata does not match schema", validatedMetadata.getError());
+                sender.rejectSubmission(submissionId, destinationId, announcedSubmission.getCaseId());
+                return Optional.empty();
+            }
+
+            /** Prepare submit submission with announced submission id **/
+            final SubmitSubmission submission = new SubmitSubmission();
+            submission.setSubmissionId(submissionId);
+            submission.setEncryptedData(encryptedDataPayload.getEncryptedData());
+            submission.setEncryptedMetadata(sender.encryptObject(encryptionKey, metadata));
+
+            /** Submit submission **/
+            sender.sendSubmission(submission);
+            LOGGER.info("SUCCESSFULLY HANDED IN SUBMISSION !");
+            return Optional.of(submission);
+
+        } catch (final EncryptionException e) {
+            LOGGER.error("Encrypting submission failed", e);
+        } catch (final RestApiException e) {
+            LOGGER.error("Sending submission failed", e);
+        } catch (final SchemaNotFoundException e) {
+            LOGGER.error("Required schema to send valid submission not found", e);
+        } catch (final SubmissionNotCreatedException e) {
+            LOGGER.error("Failed to announce new submission", e);
+        } catch (final KeyNotRetrievedException e) {
+            LOGGER.error("Getting encryption key for destination {} failed", destinationId, e);
+        } catch (final AttachmentCreationException e) {
+            LOGGER.error("Reading file failed. Attachment will not be created.", e);
+        }
+        return Optional.empty();
+    }
+
+    @Override
+    public boolean hasValidPayload(final SubmissionPayload submissionPayload) {
+        if (submissionPayload.getData().getRawData() == null) {
+            LOGGER.error("Data is mandatory, but was null.");
+            return false;
+        } else if (submissionPayload.getServiceTypePayLoad().getName() == null) {
+            LOGGER.error("Service type name is mandatory, but was null.");
+            return false;
+        } else if (submissionPayload.getServiceTypePayLoad().getLeikaKey() == null) {
+            LOGGER.error("Service type identifier is mandatory, but was null.");
+            return false;
+        } else if (submissionPayload.getDestinationId() == null) {
+            LOGGER.error("DestinationId is mandatory, but was null.");
+            return false;
+        } else {
+            return true;
+        }
+    }
+
+    private DataPayload encryptDataPayload(final DataPayload dataPayload, final Destination destination, final RSAKey encryptionKey) {
+        final List<SubmissionSchema> submissionSchemas = getSubmissionSchemasFromDestination(destination);
+        final Optional<URI> schemaUriForMimeType = getSchemaUriForMimeType(submissionSchemas, dataPayload.getMimeType());
+        if (schemaUriForMimeType.isEmpty()) {
+            throw new SchemaNotFoundException("No data schema for mime-type " + dataPayload.getMimeType() + " found, please check the allowed type for destination");
+        }
+        final String hashedData = sender.createHash(dataPayload.getRawData());
+        final String encryptedData = sender.encryptBytes(encryptionKey, dataPayload.getRawData());
+        return dataPayload.withSchemaUri(schemaUriForMimeType.get())
+                .withEncryptedData(encryptedData)
+                .withHashedData(hashedData);
+    }
+
+    private List<SubmissionSchema> getSubmissionSchemasFromDestination(final Destination destination) {
+        return destination.getServices().stream()
+                .map(DestinationService::getSubmissionSchemas)
+                .flatMap(Collection::stream)
+                .collect(Collectors.toList());
+    }
+
+    private Optional<URI> getSchemaUriForMimeType(final List<SubmissionSchema> submissionSchemas, final MimeType mimeType) {
+        return submissionSchemas.stream()
+                .filter(schema -> schema.getMimeType().equals(mimeType))
+                .map(SubmissionSchema::getSchemaUri)
+                .findFirst();
+    }
+
+    private void uploadAttachments(final List<AttachmentPayload> attachmentPayloads, final UUID submissionId) {
+        if (attachmentPayloads.isEmpty()) {
+            LOGGER.info("No attachments to upload");
+        } else {
+            LOGGER.info("Uploading {} attachment(s)", attachmentPayloads.size());
+        }
+        for (final AttachmentPayload payload : attachmentPayloads) {
+            sender.uploadAttachment(submissionId, payload.getAttachmentId(), payload.getEncryptedData());
+        }
+    }
+
+    private CreateSubmission buildSubmissionToAnnounce(final UUID destinationId, final ServiceTypePayload serviceTypePayLoad, final List<AttachmentPayload> encryptedAttachments) {
+        final ServiceType serviceType = SubmissionUtil.buildServiceType(serviceTypePayLoad);
+        final List<UUID> attachmentIdsToAnnounce = SubmissionUtil.toAttachmentIds(encryptedAttachments);
+        return SubmissionUtil.createSubmission(destinationId, serviceType, attachmentIdsToAnnounce);
+    }
+
+    private List<AttachmentPayload> encryptAndHashAttachments(final RSAKey encryptionKey, final List<AttachmentPayload> attachments) {
+        return attachments.stream()
+                .filter(Objects::nonNull)
+                .map(this::readRawData)
+                .map(this::hashBytes)
+                .map(payload -> encryptBytes(encryptionKey, payload))
+                .map(this::setMimeType)
+                .collect(Collectors.toList());
+    }
+
+    private AttachmentPayload readRawData(final AttachmentPayload attachmentPayload) {
+        final File file = attachmentPayload.getFile();
+        try {
+            final byte[] rawData = Files.readAllBytes(Paths.get(file.getPath()));
+            return attachmentPayload.withRawData(rawData);
+        } catch (final IOException e) {
+            throw new AttachmentCreationException("Attachment '" + file.getAbsolutePath() + "' could not be read ", e);
+        }
+    }
+
+    private AttachmentPayload encryptBytes(final RSAKey encryptionKey, final AttachmentPayload attachmentPayload) {
+        final String encryptedAttachment = sender.encryptBytes(encryptionKey, attachmentPayload.getRawData());
+        return attachmentPayload.withEncryptedData(encryptedAttachment);
+    }
+
+    private AttachmentPayload hashBytes(final AttachmentPayload attachmentPayload) {
+        final String hashedBytes = sender.createHash(attachmentPayload.getRawData());
+        return attachmentPayload.withHashedData(hashedBytes);
+    }
+
+    private AttachmentPayload setMimeType(final AttachmentPayload attachmentPayload) {
+        final File file = attachmentPayload.getFile();
+        String mimeType;
+        try {
+            final Tika mimeTypeGuesser = new Tika();
+            mimeType = mimeTypeGuesser.detect(file);
+        } catch (final IOException e) {
+            mimeType = URLConnection.guessContentTypeFromName(file.getName());
+        }
+        LOGGER.info("Detected attachment mime-type {}", mimeType);
+        return attachmentPayload.withMimeType(mimeType);
+    }
+}
diff --git a/client/src/main/java/de/fitko/fitconnect/client/strategies/SubmitStrategy.java b/client/src/main/java/de/fitko/fitconnect/client/strategies/SubmitStrategy.java
new file mode 100644
index 000000000..92d9e3e55
--- /dev/null
+++ b/client/src/main/java/de/fitko/fitconnect/client/strategies/SubmitStrategy.java
@@ -0,0 +1,30 @@
+package de.fitko.fitconnect.client.strategies;
+
+import de.fitko.fitconnect.api.domain.model.submission.SubmitSubmission;
+import de.fitko.fitconnect.client.model.SubmissionPayload;
+
+import java.util.Optional;
+
+/**
+ * Provides different strategies for assembling, announcing and handing in a new submission.
+ */
+public interface SubmitStrategy {
+
+    /**
+     * Send a submission based on specific assumptions on how the submission is assembled.
+     *
+     * @param submissionPayload all necessary data for creating and submitting a submission
+     *
+     * @return {@link SubmitSubmission}, empty if a validation error or technical error occurred.
+     */
+    Optional<SubmitSubmission> send(SubmissionPayload submissionPayload);
+
+    /**
+     * Validate the payload to be sufficient for submission.
+     *
+     * @param submissionPayload payload of all data needed to create a submission
+     *
+     * @return true if all checks apply, false if data is missing
+     */
+    boolean hasValidPayload(SubmissionPayload submissionPayload);
+}
diff --git a/client/src/main/java/de/fitko/fitconnect/client/util/SubmissionUtil.java b/client/src/main/java/de/fitko/fitconnect/client/util/SubmissionUtil.java
new file mode 100644
index 000000000..748fdfc56
--- /dev/null
+++ b/client/src/main/java/de/fitko/fitconnect/client/util/SubmissionUtil.java
@@ -0,0 +1,139 @@
+package de.fitko.fitconnect.client.util;
+
+import de.fitko.fitconnect.api.domain.model.metadata.ContentStructure;
+import de.fitko.fitconnect.api.domain.model.metadata.Metadata;
+import de.fitko.fitconnect.api.domain.model.metadata.PublicServiceType;
+import de.fitko.fitconnect.api.domain.model.metadata.attachment.Attachment;
+import de.fitko.fitconnect.api.domain.model.metadata.attachment.Purpose;
+import de.fitko.fitconnect.api.domain.model.metadata.attachment.signature.AttachmentSignatureType;
+import de.fitko.fitconnect.api.domain.model.metadata.attachment.signature.Hash__1;
+import de.fitko.fitconnect.api.domain.model.metadata.data.Data;
+import de.fitko.fitconnect.api.domain.model.metadata.data.DataSignatureType;
+import de.fitko.fitconnect.api.domain.model.metadata.data.Hash;
+import de.fitko.fitconnect.api.domain.model.metadata.data.SubmissionSchema;
+import de.fitko.fitconnect.api.domain.model.submission.CreateSubmission;
+import de.fitko.fitconnect.api.domain.model.submission.ServiceType;
+import de.fitko.fitconnect.api.domain.model.submission.SubmitSubmission;
+import de.fitko.fitconnect.client.model.AttachmentPayload;
+import de.fitko.fitconnect.client.model.DataPayload;
+import de.fitko.fitconnect.client.model.ServiceTypePayload;
+import de.fitko.fitconnect.client.model.SubmissionPayload;
+import de.fitko.fitconnect.core.util.Strings;
+
+import java.io.File;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+public final class SubmissionUtil {
+
+    public static CreateSubmission createSubmission(final UUID destinationId, final ServiceType serviceType, final List<UUID> attachmentIdsToAnnounce) {
+        return CreateSubmission.builder()
+                .destinationId(destinationId)
+                .announcedAttachments(attachmentIdsToAnnounce)
+                .serviceType(serviceType)
+                .build();
+    }
+
+    public static ServiceType buildServiceType(final ServiceTypePayload serviceTypePayload) {
+        final var serviceType = new ServiceType();
+        serviceType.setName(serviceTypePayload.getName());
+        serviceType.setIdentifier(serviceTypePayload.getLeikaKey());
+        if (!Strings.isNullOrEmpty(serviceTypePayload.getDescription())) {
+            serviceType.setDescription(serviceTypePayload.getDescription());
+        }
+        return serviceType;
+    }
+
+    public static PublicServiceType buildPublicServiceType(final ServiceTypePayload serviceTypePayload) {
+        final var publicServiceType = new PublicServiceType();
+        publicServiceType.setIdentifier(serviceTypePayload.getLeikaKey());
+        publicServiceType.setName(serviceTypePayload.getName());
+        if (serviceTypePayload.getDescription() != null) {
+            publicServiceType.setDescription(serviceTypePayload.getDescription());
+        }
+        return publicServiceType;
+    }
+
+    public static Data buildData(final DataPayload dataPayload) {
+        final var hash = new Hash();
+        hash.setContent(dataPayload.getHashedData());
+        hash.setDataSignatureType(DataSignatureType.SHA_512);
+
+        final var submissionSchema = new SubmissionSchema();
+        submissionSchema.setMimeType(dataPayload.getMimeType());
+        submissionSchema.setSchemaUri(dataPayload.getSchemaUri());
+
+        final var data = new Data();
+        data.setSubmissionSchema(submissionSchema);
+        data.setHash(hash);
+        return data;
+    }
+
+    public static Metadata buildMetadata(final List<Attachment> attachments, final Data data, final PublicServiceType publicServiceType) {
+        final var contentStructure = new ContentStructure();
+        contentStructure.setAttachments(attachments);
+        contentStructure.setData(data);
+
+        final var metadata = new Metadata();
+        metadata.setContentStructure(contentStructure);
+        metadata.setPublicServiceType(publicServiceType);
+        return metadata;
+    }
+
+    public static CreateSubmission buildSubmissionToAnnounce(final SubmissionPayload submissionPayload) {
+        final ServiceType serviceType = buildServiceType(submissionPayload.getServiceTypePayLoad());
+        final List<UUID> attachmentIds = toAttachmentIds(submissionPayload.getAttachments());
+        return createSubmission(submissionPayload.getDestinationId(), serviceType, attachmentIds);
+    }
+
+    public static SubmitSubmission buildSubmitSubmission(final SubmissionPayload submissionPayload, final UUID submissionId) {
+        final SubmitSubmission submission = new SubmitSubmission();
+        submission.setSubmissionId(submissionId);
+        submission.setEncryptedData(submissionPayload.getData().getEncryptedData());
+        submission.setEncryptedMetadata(submissionPayload.getEncryptedMetadata());
+        return submission;
+    }
+
+    public static Attachment toHashedAttachment(final AttachmentPayload attachmentPayload) {
+        final var attachment = new Attachment();
+        attachment.setAttachmentId(attachmentPayload.getAttachmentId());
+        attachment.setPurpose(Purpose.ATTACHMENT);
+        attachment.setFilename(attachmentPayload.getFile().getName());
+        attachment.setMimeType(attachmentPayload.getMimeType());
+
+        final var hash = new Hash__1();
+        hash.setContent(attachmentPayload.getHashedData());
+        hash.setAttachmentSignatureType(AttachmentSignatureType.SHA_512);
+        attachment.setHash(hash);
+        return attachment;
+    }
+
+    public static List<UUID> toAttachmentIds(final List<AttachmentPayload> attachmentPayloads) {
+        return attachmentPayloads.stream()
+                .map(AttachmentPayload::getAttachmentId)
+                .collect(Collectors.toList());
+    }
+
+    public static List<Attachment> toAttachmentMetadata(final List<AttachmentPayload> attachmentPayloads) {
+        return attachmentPayloads.stream()
+                .map(SubmissionUtil::toHashedAttachment)
+                .collect(Collectors.toList());
+    }
+
+    public static Function<Map.Entry<UUID, String>, AttachmentPayload> getEncryptedAttachmentPayload() {
+        return attachment -> AttachmentPayload.builder()
+                .encryptedData(attachment.getValue())
+                .attachmentId(attachment.getKey())
+                .build();
+    }
+
+    public static Function<File, AttachmentPayload> getFileAttachmentPayload(final UUID attachmentId) {
+        return file -> AttachmentPayload.builder()
+                .file(file)
+                .attachmentId(attachmentId)
+                .build();
+    }
+}
diff --git a/client/src/test/java/de/fitko/fitconnect/client/ClientIntegrationTest.java b/client/src/test/java/de/fitko/fitconnect/client/ClientIntegrationTest.java
index bc9c7cca8..885b3e3e3 100644
--- a/client/src/test/java/de/fitko/fitconnect/client/ClientIntegrationTest.java
+++ b/client/src/test/java/de/fitko/fitconnect/client/ClientIntegrationTest.java
@@ -1,12 +1,33 @@
 package de.fitko.fitconnect.client;
 
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.nimbusds.jose.jwk.RSAKey;
 import de.fitko.fitconnect.api.config.*;
+import de.fitko.fitconnect.api.domain.model.metadata.Metadata;
+import de.fitko.fitconnect.api.domain.model.metadata.PublicServiceType;
+import de.fitko.fitconnect.api.domain.model.metadata.attachment.Attachment;
+import de.fitko.fitconnect.api.domain.model.metadata.data.Data;
+import de.fitko.fitconnect.api.domain.model.metadata.data.MimeType;
 import de.fitko.fitconnect.api.domain.model.submission.SubmissionForPickup;
+import de.fitko.fitconnect.api.services.crypto.CryptoService;
 import de.fitko.fitconnect.client.factory.ClientFactory;
+import de.fitko.fitconnect.client.model.AttachmentPayload;
+import de.fitko.fitconnect.client.model.DataPayload;
+import de.fitko.fitconnect.client.model.ReceivedSubmission;
+import de.fitko.fitconnect.client.model.ServiceTypePayload;
+import de.fitko.fitconnect.client.util.SubmissionUtil;
+import de.fitko.fitconnect.core.crypto.HashService;
+import de.fitko.fitconnect.core.crypto.JWECryptoService;
+import org.apache.tika.mime.MimeTypes;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable;
 
 import java.io.File;
+import java.io.IOException;
+import java.net.URI;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.text.ParseException;
 import java.util.*;
 import java.util.stream.Collectors;
 
@@ -33,16 +54,17 @@ class ClientIntegrationTest {
         final ApplicationConfig config = getConfigWithCredentialsFromGitlab(testEnv);
 
         final var sentSubmission = ClientFactory.senderClient(config)
-                .withDestination(UUID.fromString(System.getenv("TEST_DESTINATION_ID")))
-                .withServiceType("Test Service", "urn:de:fim:leika:leistung:99400048079000")
+                .newSubmission()
                 .withAttachment(new File("src/test/resources/attachment.txt"))
                 .withJsonData("{ data: 'Beispiel Fachdaten' }")
+                .withDestination(UUID.fromString(System.getenv("TEST_DESTINATION_ID")))
+                .withServiceType("Test Service", "urn:de:fim:leika:leistung:99400048079000")
                 .submit();
 
         assertTrue(sentSubmission.isPresent());
 
         // When
-        final Optional<SubscriberClient.ReceivedSubmission> receivedSubmission =
+        final Optional<ReceivedSubmission> receivedSubmission =
                 ClientFactory.subscriberClient(config)
                         .requestSubmission(sentSubmission.get().getSubmissionId());
 
@@ -53,6 +75,79 @@ class ClientIntegrationTest {
         assertThat(new String(receivedSubmission.get().getAttachments().get(0).getDecryptedData()), is("Test attachment"));
     }
 
+    @Test
+    @EnabledIfEnvironmentVariable(named = "TEST_DESTINATION_ID", matches = ".*")
+    void testSendAndConfirmCycleWithEncryptedData() throws ParseException, IOException {
+
+        // Given
+        final UUID destinationId = UUID.fromString(System.getenv("TEST_DESTINATION_ID"));
+        final var testEnv = new Environment(authBaseUrl, submissionBaseUrl, true);
+        final ApplicationConfig config = getConfigWithCredentialsFromGitlab(testEnv);
+        final CryptoService cryptoService = new JWECryptoService(new HashService());
+        final SenderClient.Start client = ClientFactory.senderClient(config);
+
+        final Optional<String> publicKey = client.getPublicKey(destinationId);
+        assertTrue(publicKey.isPresent());
+
+        final RSAKey encryptionKey = RSAKey.parse(publicKey.get());
+
+        final String jsonData = "{ data: 'Beispiel Fachdaten' }";
+        final File attachmentFile = new File("src/test/resources/attachment.txt");
+        final String attachmentData = Files.readString(attachmentFile.toPath());
+
+        final String encryptedData = cryptoService.encryptString(encryptionKey, jsonData);
+        final String encryptedAttachment = cryptoService.encryptString(encryptionKey, attachmentData);
+
+        final DataPayload dataPayload = DataPayload.builder()
+                .encryptedData(encryptedData)
+                .mimeType(MimeType.APPLICATION_JSON)
+                .schemaUri(URI.create("https://schema.fitko.de/fim/s00000000009_1.0.0.schema.json"))
+                .hashedData(cryptoService.hashBytes(jsonData.getBytes(StandardCharsets.UTF_8)))
+                .build();
+
+        final AttachmentPayload attachmentPayload = AttachmentPayload.builder()
+                .encryptedData(encryptedAttachment)
+                .file(attachmentFile)
+                .mimeType(MimeTypes.PLAIN_TEXT)
+                .attachmentId(UUID.randomUUID())
+                .hashedData(cryptoService.hashBytes(attachmentData.getBytes(StandardCharsets.UTF_8)))
+                .build();
+
+        final ServiceTypePayload serviceTypePayload = ServiceTypePayload.builder()
+                .name("Test Service")
+                .leikaKey("urn:de:fim:leika:leistung:99400048079000")
+                .build();
+
+        final Data data = SubmissionUtil.buildData(dataPayload);
+        final PublicServiceType publicServiceType = SubmissionUtil.buildPublicServiceType(serviceTypePayload);
+        final List<Attachment> attachmentMetadata = SubmissionUtil.toAttachmentMetadata(List.of(attachmentPayload));
+        final Metadata metadata = SubmissionUtil.buildMetadata(attachmentMetadata, data, publicServiceType);
+
+        final String encryptedMetadata = cryptoService.encryptBytes(encryptionKey, new ObjectMapper().writeValueAsBytes(metadata));
+
+        // When
+        final var sentSubmission = client
+                .newSubmissionWithEncryptedData()
+                .withEncryptedAttachment(attachmentPayload.getAttachmentId(), attachmentPayload.getEncryptedData())
+                .withEncryptedJsonData(encryptedData)
+                .withEncryptedMetadata(encryptedMetadata)
+                .withDestination(destinationId)
+                .withServiceType("Test Service", "urn:de:fim:leika:leistung:99400048079000")
+                .submit();
+
+        assertTrue(sentSubmission.isPresent());
+
+        final Optional<ReceivedSubmission> receivedSubmission =
+                ClientFactory.subscriberClient(config)
+                        .requestSubmission(sentSubmission.get().getSubmissionId());
+
+        // Then
+        assertTrue(receivedSubmission.isPresent());
+        assertThat(receivedSubmission.get().getData(), is(jsonData));
+        assertThat(receivedSubmission.get().getMimeType().toString(), is("application/json"));
+        assertThat(new String(receivedSubmission.get().getAttachments().get(0).getDecryptedData()), is("Test attachment"));
+    }
+
     @Test
     @EnabledIfEnvironmentVariable(named = "TEST_DESTINATION_ID", matches = ".*")
     void testListSubmissions() {
@@ -68,16 +163,16 @@ class ClientIntegrationTest {
         final String leikaKey = "urn:de:fim:leika:leistung:99400048079000";
         final String serviceName = "Test Service";
 
-        final var submissionOne = senderClient
+        final var submissionOne = senderClient.newSubmission()
+                .withJsonData("{ data: 'Beispiel Fachdaten 1' }")
                 .withDestination(destinationId)
                 .withServiceType(serviceName, leikaKey)
-                .withJsonData("{ data: 'Beispiel Fachdaten 1' }")
                 .submit();
 
-        final var submissionTwo = senderClient
+        final var submissionTwo = senderClient.newSubmission()
+                .withJsonData("{ data: 'Beispiel Fachdaten 2' }")
                 .withDestination(destinationId)
                 .withServiceType(serviceName, leikaKey)
-                .withJsonData("{ data: 'Beispiel Fachdaten 2' }")
                 .submit();
 
         assertTrue(submissionOne.isPresent());
@@ -107,11 +202,11 @@ class ClientIntegrationTest {
         final ApplicationConfig config = getConfigWithCredentialsFromGitlab("PROD", prodEnv);
 
         // When
-        final var sentSubmission = ClientFactory.senderClient(config)
-                .withDestination(UUID.fromString(System.getenv("TEST_DESTINATION_ID")))
-                .withServiceType("Test Service", "urn:de:fim:leika:leistung:99400048079000")
+        final var sentSubmission = ClientFactory.senderClient(config).newSubmission()
                 .withAttachment(new File("src/test/resources/attachment.txt"))
                 .withJsonData("{ data: 'Beispiel Fachdaten' }")
+                .withDestination(UUID.fromString(System.getenv("TEST_DESTINATION_ID")))
+                .withServiceType("Test Service", "urn:de:fim:leika:leistung:99400048079000")
                 .submit();
 
         // Then
diff --git a/client/src/test/java/de/fitko/fitconnect/client/SenderClientTest.java b/client/src/test/java/de/fitko/fitconnect/client/SenderClientTest.java
index f2937f047..2db080a57 100644
--- a/client/src/test/java/de/fitko/fitconnect/client/SenderClientTest.java
+++ b/client/src/test/java/de/fitko/fitconnect/client/SenderClientTest.java
@@ -32,7 +32,7 @@ import static org.mockito.Mockito.when;
 public class SenderClientTest {
 
     private Sender senderMock;
-    private SenderClient.WithDestination underTest;
+    private SenderClient.Start underTest;
 
     private final LogCaptor logs = new LogCaptor();
 
@@ -65,10 +65,11 @@ public class SenderClientTest {
 
         // When
         final var submission = underTest
-                .withDestination(destinationId)
-                .withServiceType("Service", "de:fitco:test:key")
+                .newSubmission()
                 .withAttachment(new File("src/test/resources/attachment.txt"))
                 .withJsonData("{}")
+                .withDestination(destinationId)
+                .withServiceType("Service", "de:fitco:test:key")
                 .submit();
 
         // Then
@@ -98,11 +99,11 @@ public class SenderClientTest {
         when(senderMock.validateMetadata(any(), any())).thenReturn(ValidationResult.ok());
 
         // When
-        final var submission = underTest
-                .withDestination(destinationId)
-                .withServiceType("Service", "de:fitco:test:key")
+        final var submission = underTest.newSubmission()
                 .withAttachment(new File("src/test/resources/attachment.txt"))
                 .withXmlData("<xml><test>data</test></xml>")
+                .withDestination(destinationId)
+                .withServiceType("Service", "de:fitco:test:key")
                 .submit();
 
         // Then
@@ -120,11 +121,11 @@ public class SenderClientTest {
         final File file2 = new File("src/test/resources/pdf_attachment_with_no_extension");
 
         // When
-        final var submission = underTest
-                .withDestination(destinationId)
-                .withServiceType("name", "test:key")
+        final var submission = underTest.newSubmission()
                 .withAttachments(List.of(file1, file2))
                 .withJsonData("{}")
+                .withDestination(destinationId)
+                .withServiceType("name", "test:key")
                 .submit();
 
         // Then
@@ -141,10 +142,10 @@ public class SenderClientTest {
         final UUID destinationId = setupTestMocks();
 
         // When
-        final var submission = underTest
+        final var submission = underTest.newSubmission()
+                .withJsonData(null)
                 .withDestination(destinationId)
                 .withServiceType("name", "test:key")
-                .withJsonData(null)
                 .submit();
 
         // Then
@@ -159,10 +160,10 @@ public class SenderClientTest {
         setupTestMocks();
 
         // When
-        final var submission = underTest
+        final var submission = underTest.newSubmission()
+                .withJsonData("{}")
                 .withDestination(null)
                 .withServiceType("name", "test:key")
-                .withJsonData("{}")
                 .submit();
 
         // Then
@@ -187,10 +188,10 @@ public class SenderClientTest {
         when(senderMock.getEncryptionKeyForDestination(any(), any())).thenReturn(publicKey);
 
         // When
-        final var submission = underTest
+        final var submission = underTest.newSubmission()
+                .withJsonData("{}")
                 .withDestination(destinationId)
                 .withServiceType("Service", "de:fitco:test:key")
-                .withJsonData("{}")
                 .submit();
 
         // Then
@@ -205,10 +206,10 @@ public class SenderClientTest {
         final var destinationId = setupTestMocks();
 
         // When
-        final var submission = underTest
+        final var submission = underTest.newSubmission()
+                .withJsonData("{}")
                 .withDestination(destinationId)
                 .withServiceType(null, "leika:key")
-                .withJsonData("{}")
                 .submit();
 
         // Then
@@ -217,16 +218,16 @@ public class SenderClientTest {
     }
 
     @Test
-    void testWithOptionalServiceTypeIdentifier() throws Exception {
+    void testWithMissingServiceTypeIdentifier() throws Exception {
 
         // Given
         final var destinationId = setupTestMocks();
 
         // When
-        final var submission = underTest
+        final var submission = underTest.newSubmission()
+                .withJsonData("{}")
                 .withDestination(destinationId)
                 .withServiceType("Name", null)
-                .withJsonData("{}")
                 .submit();
 
         // Then
@@ -241,10 +242,10 @@ public class SenderClientTest {
         final var destinationId = setupTestMocks();
 
         // When
-        final var submission = underTest
+        final var submission = underTest.newSubmission()
+                .withJsonData("{}")
                 .withDestination(destinationId)
                 .withServiceType("Name", null, "leika:key")
-                .withJsonData("{}")
                 .submit();
 
         // Then
@@ -258,11 +259,11 @@ public class SenderClientTest {
         final UUID destinationId = setupTestMocks();
 
         // When
-        final var submission = underTest
-                .withDestination(destinationId)
-                .withServiceType("name", "test:key")
+        final var submission = underTest.newSubmission()
                 .withAttachments(List.of())
                 .withJsonData("{}")
+                .withDestination(destinationId)
+                .withServiceType("name", "test:key")
                 .submit();
 
         // Then
@@ -277,11 +278,11 @@ public class SenderClientTest {
         final UUID destinationId = setupTestMocks();
 
         // When
-        final var submission = underTest
-                .withDestination(destinationId)
-                .withServiceType("name", "test:key")
+        final var submission = underTest.newSubmission()
                 .withAttachment(null)
                 .withJsonData("{}")
+                .withDestination(destinationId)
+                .withServiceType("name", "test:key")
                 .submit();
 
         // Then
@@ -296,11 +297,11 @@ public class SenderClientTest {
         final UUID destinationId = setupTestMocks();
 
         // When
-        final var submission = underTest
-                .withDestination(destinationId)
-                .withServiceType("name", "test:key")
+        final var submission = underTest.newSubmission()
                 .withAttachment(new File("/non/existing/path"))
                 .withJsonData("{}")
+                .withDestination(destinationId)
+                .withServiceType("name", "test:key")
                 .submit();
 
         // Then
@@ -316,10 +317,10 @@ public class SenderClientTest {
         when(senderMock.validatePublicKey(any())).thenReturn(ValidationResult.error(new Exception()));
 
         // When
-        final var submission = underTest
+        final var submission = underTest.newSubmission()
+                .withJsonData("{}")
                 .withDestination(destinationId)
                 .withServiceType("name", "test:key")
-                .withJsonData("{}")
                 .submit();
 
         // Then
@@ -335,10 +336,10 @@ public class SenderClientTest {
         when(senderMock.encryptBytes(any(),any())).thenThrow(new EncryptionException("Encryption failed"));
 
         // When
-        final var submission = underTest
+        final var submission = underTest.newSubmission()
+                .withJsonData("{}")
                 .withDestination(destinationId)
                 .withServiceType("name", "test:key")
-                .withJsonData("{}")
                 .submit();
 
         // Then
@@ -354,10 +355,10 @@ public class SenderClientTest {
         when(senderMock.createSubmission(any())).thenThrow(new SubmissionNotCreatedException("Announcing submission failed"));
 
         // When
-        final var submission = underTest
+        final var submission = underTest.newSubmission()
+                .withJsonData("{}")
                 .withDestination(destinationId)
                 .withServiceType("name", "test:key")
-                .withJsonData("{}")
                 .submit();
 
         // Then
@@ -374,10 +375,10 @@ public class SenderClientTest {
         when(senderMock.getDestination(any())).thenThrow(new RestApiException("Loading destination failed"));
 
         // When
-        final Optional<SubmitSubmission> submission = underTest
+        final Optional<SubmitSubmission> submission = underTest.newSubmission()
+                .withJsonData("{}")
                 .withDestination(destinationId)
                 .withServiceType("name", "test:key")
-                .withJsonData("{}")
                 .submit();
 
         assertTrue(submission.isEmpty());
@@ -393,16 +394,55 @@ public class SenderClientTest {
         when(senderMock.getEncryptionKeyForDestination(any(), any())).thenThrow(new KeyNotRetrievedException("Getting encryption key failed"));
 
         // When
-        final Optional<SubmitSubmission> submission = underTest
+        final Optional<SubmitSubmission> submission = underTest.newSubmission()
+                .withJsonData("{}")
                 .withDestination(destinationId)
                 .withServiceType("name", "test:key")
-                .withJsonData("{}")
                 .submit();
 
         assertTrue(submission.isEmpty());
         logs.assertContains("Getting encryption key for destination "+ destinationId+" failed");
     }
 
+    @Test
+    void testSendSubmissionWithEncryptedData() throws Exception {
+
+        // Given
+        final var destinationId = setupTestMocks();
+
+        // When
+        final Optional<SubmitSubmission> submission = underTest.newSubmissionWithEncryptedData()
+                .withEncryptedAttachments(Map.of(UUID.randomUUID(),"@tt@chm$nt"))
+                .withEncryptedJsonData("$ncrypt$d d@t@")
+                .withEncryptedMetadata("$ncrypt$d m$t@d@t@")
+                .withDestination(destinationId)
+                .withServiceType("name", "test:key")
+                .submit();
+
+        assertTrue(submission.isPresent());
+        logs.assertContains("SUCCESSFULLY HANDED IN SUBMISSION");
+    }
+
+    @Test
+    void testPublicEncryptionKeyRetrieval() throws Exception {
+
+        // Given
+        final var destinationId = UUID.randomUUID();
+        final var destination = getDestination(destinationId);
+
+        final RSAKey publicKey = generateRsaKey(4096).toPublicJWK();
+
+        when(senderMock.getDestination(any())).thenReturn(destination);
+        when(senderMock.validatePublicKey(any())).thenReturn(ValidationResult.ok());
+        when(senderMock.getEncryptionKeyForDestination(any(), any())).thenReturn(publicKey);
+
+        // When
+        final Optional<String> encryptionKey = underTest.getPublicKey(destinationId);
+
+        assertTrue(encryptionKey.isPresent());
+        assertThat(encryptionKey.get(), equalTo(publicKey.toJSONString()));
+    }
+
     @Test
     void testFailOnInvalidMetadata() throws Exception {
 
@@ -411,10 +451,10 @@ public class SenderClientTest {
         when(senderMock.validateMetadata(any(), any())).thenReturn(ValidationResult.error(new ValidationException("invalid metadata")));
 
         // When
-        final Optional<SubmitSubmission> submission = underTest
+        final Optional<SubmitSubmission> submission = underTest.newSubmission()
+                .withJsonData("{}")
                 .withDestination(destinationId)
                 .withServiceType("name", "test:key")
-                .withJsonData("{}")
                 .submit();
 
         assertTrue(submission.isEmpty());
diff --git a/client/src/test/java/de/fitko/fitconnect/client/cmd/CommandLineClientTest.java b/client/src/test/java/de/fitko/fitconnect/client/cmd/CommandLineClientTest.java
index 2643026bb..8e57ed88f 100644
--- a/client/src/test/java/de/fitko/fitconnect/client/cmd/CommandLineClientTest.java
+++ b/client/src/test/java/de/fitko/fitconnect/client/cmd/CommandLineClientTest.java
@@ -1,7 +1,5 @@
 package de.fitko.fitconnect.client.cmd;
 
-import ch.qos.logback.classic.spi.LoggingEvent;
-import ch.qos.logback.core.AppenderBase;
 import de.fitko.fitconnect.api.domain.model.metadata.attachment.Attachment;
 import de.fitko.fitconnect.api.domain.model.metadata.attachment.AttachmentWithData;
 import de.fitko.fitconnect.api.domain.model.metadata.data.MimeType;
@@ -9,7 +7,7 @@ import de.fitko.fitconnect.api.domain.model.submission.SubmissionForPickup;
 import de.fitko.fitconnect.api.domain.model.submission.SubmitSubmission;
 import de.fitko.fitconnect.client.SenderClient;
 import de.fitko.fitconnect.client.SubscriberClient;
-import de.fitko.fitconnect.client.SubscriberClient.ReceivedSubmission;
+import de.fitko.fitconnect.client.model.ReceivedSubmission;
 import de.fitko.fitconnect.client.testutil.LogCaptor;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
@@ -31,11 +29,11 @@ class CommandLineClientTest {
 
     private final LogCaptor logs = new LogCaptor();
 
-    ByteArrayOutputStream sysoutCaptor = new ByteArrayOutputStream();
+    private final ByteArrayOutputStream sysoutCaptor = new ByteArrayOutputStream();
 
-    CommandLineClient underTest;
-    SenderClient.ClientBuilder senderClientMock;
-    SubscriberClient.ClientBuilder subscriberClientMock;
+    private CommandLineClient underTest;
+    private SenderClient.ClientBuilder senderClientMock;
+    private SubscriberClient.ClientBuilder subscriberClientMock;
 
     @BeforeEach
     void setUp() {
@@ -120,6 +118,7 @@ class CommandLineClientTest {
         when(senderClientMock.withAttachments(any())).thenReturn(senderClientMock);
         when(senderClientMock.withJsonData(any())).thenReturn(senderClientMock);
         when(senderClientMock.submit()).thenReturn(Optional.of(expectedSubmission));
+        when(senderClientMock.newSubmission()).thenReturn(senderClientMock);
 
         // When
         underTest.run("send",
diff --git a/client/src/test/resources/test-config.yml b/client/src/test/resources/test-config.yml
index f9b9ce501..b41a20611 100644
--- a/client/src/test/resources/test-config.yml
+++ b/client/src/test/resources/test-config.yml
@@ -3,15 +3,15 @@ httpProxyHost: ""
 httpProxyPort: 0
 requestTimeoutInSeconds: 30
 
-# Name/key of the used environment
+# Name/key of the used environment (from environments config below)
 activeEnvironment: dev
 
-# Path that references the signing key file
-privateSigningKeyPath: "client/src/test/java/resources/private_test_signing_key.json"
-
 # Path that references the metadata schema
 metadataSchemaPath: "client/src/test/java/resources/metadata_schema.json"
 
+# Path that references the signing key file
+privateSigningKeyPath: "client/src/test/java/resources/private_test_signing_key.json"
+
 senderConfig:
   clientId: "1"
   clientSecret: "123"
diff --git a/core/src/main/java/de/fitko/fitconnect/core/auth/DefaultOAuthService.java b/core/src/main/java/de/fitko/fitconnect/core/auth/DefaultOAuthService.java
index db38f08b0..7e2af3fab 100644
--- a/core/src/main/java/de/fitko/fitconnect/core/auth/DefaultOAuthService.java
+++ b/core/src/main/java/de/fitko/fitconnect/core/auth/DefaultOAuthService.java
@@ -44,7 +44,7 @@ public class DefaultOAuthService implements OAuthService {
     }
 
     @Override
-    public synchronized OAuthToken getCurrentToken() throws RestApiException {
+    public OAuthToken getCurrentToken() throws RestApiException {
         if (tokenExpired()) {
             LOGGER.info("Current token is expired, authenticating ...");
             authenticate();
-- 
GitLab