All Downloads are FREE. Search and download functionalities are using the official Maven repository.

dev.fitko.fitconnect.core.validation.DefaultValidationService Maven / Gradle / Ivy

Go to download

Library that provides client access to the FIT-Connect api-endpoints for sending, subscribing and routing

The newest version!
package dev.fitko.fitconnect.core.validation;

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonFactoryBuilder;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.StreamReadConstraints;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.networknt.schema.JsonSchemaFactory;
import com.networknt.schema.SchemaValidatorsConfig;
import com.networknt.schema.SpecVersion;
import com.networknt.schema.ValidationMessage;
import com.nimbusds.jose.jwk.KeyOperation;
import com.nimbusds.jose.jwk.RSAKey;
import dev.fitko.fitconnect.api.config.ApplicationConfig;
import dev.fitko.fitconnect.api.domain.model.attachment.Fragment;
import dev.fitko.fitconnect.api.domain.model.destination.Destination;
import dev.fitko.fitconnect.api.domain.model.destination.DestinationService;
import dev.fitko.fitconnect.api.domain.model.event.EventClaimFields;
import dev.fitko.fitconnect.api.domain.model.event.authtags.AuthenticationTags;
import dev.fitko.fitconnect.api.domain.model.event.problems.Problem;
import dev.fitko.fitconnect.api.domain.model.event.problems.attachment.AttachmentHashMismatch;
import dev.fitko.fitconnect.api.domain.model.event.problems.attachment.IncorrectAttachmentAuthenticationTag;
import dev.fitko.fitconnect.api.domain.model.event.problems.data.DataHashMismatch;
import dev.fitko.fitconnect.api.domain.model.event.problems.data.DataJsonSyntaxViolation;
import dev.fitko.fitconnect.api.domain.model.event.problems.data.DataSchemaViolation;
import dev.fitko.fitconnect.api.domain.model.event.problems.data.DataXmlSyntaxViolation;
import dev.fitko.fitconnect.api.domain.model.event.problems.data.IncorrectDataAuthenticationTag;
import dev.fitko.fitconnect.api.domain.model.event.problems.metadata.AttachmentsMismatch;
import dev.fitko.fitconnect.api.domain.model.event.problems.metadata.IncorrectMetadataAuthenticationTag;
import dev.fitko.fitconnect.api.domain.model.event.problems.metadata.MetadataJsonSyntaxViolation;
import dev.fitko.fitconnect.api.domain.model.event.problems.metadata.MetadataSchemaViolation;
import dev.fitko.fitconnect.api.domain.model.event.problems.metadata.MissingData;
import dev.fitko.fitconnect.api.domain.model.event.problems.metadata.UnsupportedDataSchema;
import dev.fitko.fitconnect.api.domain.model.event.problems.metadata.UnsupportedMetadataSchema;
import dev.fitko.fitconnect.api.domain.model.event.problems.metadata.UnsupportedReplyChannel;
import dev.fitko.fitconnect.api.domain.model.event.problems.metadata.UnsupportedService;
import dev.fitko.fitconnect.api.domain.model.metadata.Metadata;
import dev.fitko.fitconnect.api.domain.model.metadata.attachment.ApiAttachment;
import dev.fitko.fitconnect.api.domain.model.metadata.attachment.AttachmentForValidation;
import dev.fitko.fitconnect.api.domain.model.metadata.attachment.Purpose;
import dev.fitko.fitconnect.api.domain.model.metadata.data.Data;
import dev.fitko.fitconnect.api.domain.model.metadata.data.MimeType;
import dev.fitko.fitconnect.api.domain.model.metadata.data.SubmissionSchema;
import dev.fitko.fitconnect.api.domain.model.reply.Reply;
import dev.fitko.fitconnect.api.domain.model.reply.replychannel.ReplyChannel;
import dev.fitko.fitconnect.api.domain.model.submission.ServiceType;
import dev.fitko.fitconnect.api.domain.model.submission.Submission;
import dev.fitko.fitconnect.api.domain.validation.ValidationResult;
import dev.fitko.fitconnect.api.exceptions.internal.DataIntegrityException;
import dev.fitko.fitconnect.api.exceptions.internal.SchemaNotFoundException;
import dev.fitko.fitconnect.api.exceptions.internal.ValidationException;
import dev.fitko.fitconnect.api.services.crypto.MessageDigestService;
import dev.fitko.fitconnect.api.services.schema.SchemaProvider;
import dev.fitko.fitconnect.api.services.validation.ValidationService;
import dev.fitko.fitconnect.jwkvalidator.JWKValidator;
import dev.fitko.fitconnect.jwkvalidator.exceptions.JWKValidationException;
import dev.fitko.fitconnect.jwkvalidator.exceptions.LogLevel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;

import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParserFactory;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.nio.file.Files;
import java.text.SimpleDateFormat;
import java.time.Instant;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static dev.fitko.fitconnect.core.crypto.constants.CryptoConstants.HASH_OF_ZERO_BYTES;
import static dev.fitko.fitconnect.core.utils.EventLogUtil.getAuthenticationTagFromEncryptedData;
import static java.util.Objects.isNull;

public class DefaultValidationService implements ValidationService {

    private static final Logger LOGGER = LoggerFactory.getLogger(DefaultValidationService.class);
    private static final ObjectMapper MAPPER = createObjectMapper();
    private static final JsonSchemaFactory SCHEMA_FACTORY_DRAFT_2020 = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V202012);
    private static final JsonSchemaFactory SCHEMA_FACTORY_DRAFT_2007 = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V7);
    private static final SchemaValidatorsConfig VALIDATORS_CONFIG = new SchemaValidatorsConfig();
    public static final String VALID_SCHEMA_URL_EXPRESSION = "https://schema\\.fitko\\.de/fit-connect/metadata/1\\.\\d+\\.\\d+/metadata.schema.json";
    private final MessageDigestService messageDigestService;
    private final SchemaProvider schemaProvider;
    private final ApplicationConfig config;
    private final JWKValidator jwkValidator;

    public DefaultValidationService(final ApplicationConfig config, final MessageDigestService messageDigestService,
                                    final SchemaProvider schemaProvider, final List trustedRootCertificates) {
        this.config = config;
        this.messageDigestService = messageDigestService;
        this.schemaProvider = schemaProvider;
        jwkValidator = createValidator(config, trustedRootCertificates);

        VALIDATORS_CONFIG.setFormatAssertionsEnabled(true);
        VALIDATORS_CONFIG.setLocale(Locale.US);
    }

    @Override
    public ValidationResult validatePublicKey(final RSAKey publicKey, final KeyOperation keyOperation) {
        try {
            return validateKey(publicKey, keyOperation);
        } catch (final Exception e) {
            return ValidationResult.error(e);
        }
    }

    @Override
    public ValidationResult validateSetEventSchema(final String setEventPayload) {
        try {
            final JsonNode inputNode = MAPPER.readTree(setEventPayload);
            final URI schemaUri = URI.create(inputNode.get(EventClaimFields.CLAIM_SCHEMA).asText());
            if (schemaProvider.isAllowedSetSchema(schemaUri)) {
                return validate2020JsonSchema(schemaProvider.loadSetSchema(config.getSetSchemaWriteVersion()), inputNode);
            } else {
                return ValidationResult.error(new SchemaNotFoundException("SET payload schema not supported: " + schemaUri));
            }
        } catch (final JsonProcessingException | IllegalArgumentException e) {
            return ValidationResult.error(e);
        }
    }

    @Override
    public ValidationResult validateMetadataSchema(final Metadata metadata) {

        if (metadata.getSchema() != null && !metadata.getSchema().isEmpty() && !metadata.getSchema().matches(VALID_SCHEMA_URL_EXPRESSION)) {
            // https://docs.fitko.de/fit-connect/docs/receiving/verification/#required-schema-reference
            // https://docs.fitko.de/fit-connect/docs/receiving/verification/#metadatenschema
            return ValidationResult.problem(new UnsupportedMetadataSchema(metadata.getSchema()));
        }

        try {
            final String metadataJson = MAPPER.writeValueAsString(metadata);
            final JsonNode inputNode = MAPPER.readTree(metadataJson);
            return validate2020JsonSchema(schemaProvider.loadMetadataSchema(config.getMetadataSchemaWriteVersion()), inputNode);
        } catch (final JsonProcessingException | SchemaNotFoundException e) {
            // https://docs.fitko.de/fit-connect/docs/receiving/verification/#schema-pr%C3%BCfung
            return ValidationResult.withErrorAndProblem(e, new MetadataSchemaViolation());
        }
    }

    @Override
    public ValidationResult validateDestinationSchema(final Map destinationPayload) {
        try {
            final String destinationPayloadJson = MAPPER.writeValueAsString(destinationPayload);
            final JsonNode inputNode = MAPPER.readTree(destinationPayloadJson);
            final String schema = schemaProvider.loadDestinationSchema(config.getDestinationSchemaUri());
            return returnValidationResult(SCHEMA_FACTORY_DRAFT_2007.getSchema(schema, VALIDATORS_CONFIG).validate(inputNode));
        } catch (final JsonProcessingException e) {
            return ValidationResult.error(e);
        }
    }

    @Override
    public ValidationResult validateHashIntegrity(final String originalHexHash, final byte[] data) {
        try {
            final byte[] originalHash = messageDigestService.fromHexString(originalHexHash);
            final boolean hashesAreNotEqual = !messageDigestService.verify(originalHash, data);
            if (hashesAreNotEqual) {
                return ValidationResult.error(new DataIntegrityException("Metadata contains invalid hash value"));
            }
            return ValidationResult.ok();
        } catch (final IllegalArgumentException | NullPointerException e) {
            return ValidationResult.error(e);
        }
    }

    @Override
    public ValidationResult validateHashIntegrity(final String originalHexHash, final InputStream inputStream) {
        try {
            final byte[] originalHash = messageDigestService.fromHexString(originalHexHash);
            final boolean hashesAreNotEqual = !messageDigestService.verify(originalHash, inputStream);
            if (hashesAreNotEqual) {
                return ValidationResult.error(new DataIntegrityException("Metadata contains invalid hash value"));
            }
            return ValidationResult.ok();
        } catch (final IllegalArgumentException | NullPointerException e) {
            return ValidationResult.error(e);
        }
    }


    @Override
    public ValidationResult validateSubmissionDataSchema(final byte[] json, final URI schemaUri) {

        if (config.isSkipSubmissionDataValidation()) {
            LOGGER.warn("Submission data validation is deactivated. This should be done only on secure test environments.");
            return ValidationResult.ok();
        }

        final String schema = schemaProvider.loadSubmissionDataSchema(schemaUri);
        try {
            return returnValidationResult(SCHEMA_FACTORY_DRAFT_2020.getSchema(schema, VALIDATORS_CONFIG).validate(MAPPER.readTree(json)));
        } catch (final IOException e) {
            return ValidationResult.error(e);
        }
    }

    @Override
    public ValidationResult validateJsonFormat(final byte[] json) {
        try {
            MAPPER.readTree(json);
        } catch (final IOException e) {
            return ValidationResult.error(e);
        }
        return ValidationResult.ok();
    }

    @Override
    public ValidationResult validateXmlFormat(final byte[] xml) {
        try {
            final XMLReader xmlReader = getXmlReader();
            xmlReader.parse(new InputSource(new ByteArrayInputStream(xml)));
        } catch (final ParserConfigurationException | IOException | SAXException e) {
            return ValidationResult.error(e);
        }
        return ValidationResult.ok();
    }

    @Override
    public ValidationResult validateCallback(final String hmac, final Long timestampInSec, final String httpBody, final String callbackSecret) {

        final ZonedDateTime providedTimeInSeconds = ZonedDateTime.ofInstant(Instant.ofEpochSecond(timestampInSec), ZoneId.systemDefault());
        final ZonedDateTime currentTimeFiveMinutesAgo = ZonedDateTime.now().minusMinutes(5);

        if (providedTimeInSeconds.isBefore(currentTimeFiveMinutesAgo)) {
            return ValidationResult.error(new ValidationException("Timestamp provided by callback is expired."));
        }

        final String expectedHmac = messageDigestService.calculateHMAC(timestampInSec + "." + httpBody, callbackSecret);

        if (!hmac.equals(expectedHmac)) {
            return ValidationResult.error(new ValidationException("HMAC provided by callback does not match the expected result."));
        }

        return ValidationResult.ok();
    }

    @Override
    public ValidationResult validateData(final byte[] decryptedData, final String encryptedData, final Metadata metadata, final String eventAuthenticationTags) {

        // https://docs.fitko.de/fit-connect/docs/receiving/verification/#authentication-tag-pr%C3%BCfen-1
        final var dataAuthTag = getAuthenticationTagFromEncryptedData(encryptedData);
        if (!eventAuthenticationTags.equals(dataAuthTag)) {
            return ValidationResult.problem(new IncorrectDataAuthenticationTag());
        }

        final Data data = metadata.getContentStructure().getData();

        if (validateIfDataIsNotTransferredAsAttachment(metadata, data)) {
            // https://docs.fitko.de/fit-connect/docs/receiving/verification/#submission-data-hash
            final String hashFromSender = data.getHash().getContent();
            final ValidationResult hashValidation = validateHashIntegrity(hashFromSender, decryptedData);
            if (hashValidation.hasError()) {
                return ValidationResult.problem(new DataHashMismatch());
            }
        }

        // https://docs.fitko.de/fit-connect/docs/receiving/verification/#syntax-validierung-1
        if (data.getSubmissionSchema().getMimeType().equals(MimeType.APPLICATION_JSON)) {

            final ValidationResult jsonValidation = validateJsonFormat(decryptedData);
            if (jsonValidation.hasError()) {
                return ValidationResult.problem(new DataJsonSyntaxViolation());
            }

            final ValidationResult dataSchemaValidation = validateSubmissionDataSchema(decryptedData, data.getSubmissionSchema().getSchemaUri());
            if (dataSchemaValidation.hasError()) {
                return ValidationResult.problem(new DataSchemaViolation());
            }
        }

        if (data.getSubmissionSchema().getMimeType().equals(MimeType.APPLICATION_XML)) {
            final ValidationResult xmlValidation = validateXmlFormat(decryptedData);
            if (xmlValidation.hasError()) {
                return ValidationResult.problem(new DataXmlSyntaxViolation());
            }
        }

        return ValidationResult.ok();
    }

    @Override
    public ValidationResult validateAttachments(final List attachmentsForValidation, final AuthenticationTags authenticationTags) {

        final Map eventAuthTags = authenticationTags.getAttachments();
        final List validationProblems = new ArrayList<>();

        for (AttachmentForValidation attachment : attachmentsForValidation) {
            if (attachment.hasFragmentedPayload()) {
                final ValidationResult validationResult = validateFragmentedAttachment(attachment, eventAuthTags, validationProblems);
                if (validationResult.hasError()) {
                    return validationResult;
                }
            } else {
                validateAttachment(attachment, eventAuthTags, validationProblems);
            }
        }

        return validationProblems.isEmpty() ? ValidationResult.ok() : ValidationResult.problems(validationProblems);
    }

    private void validateAttachment(AttachmentForValidation attachment, Map eventAuthTags, List validationProblems) {
        final UUID attachmentId = attachment.getAttachmentId();
        // https://docs.fitko.de/fit-connect/docs/receiving/verification/#authentication-tag-pr%C3%BCfen-2
        final String authTagFromEvent = eventAuthTags.get(attachmentId);
        if (!authTagFromEvent.equals(attachment.getAuthTag())) {
            validationProblems.add(new IncorrectAttachmentAuthenticationTag(attachmentId));
        }
        // https://docs.fitko.de/fit-connect/docs/receiving/verification/#attachment-hash
        final ValidationResult validationResult = validateHashIntegrity(attachment.getHash(), attachment.getDecryptedData());
        if (validationResult.hasError()) {
            validationProblems.add(new AttachmentHashMismatch(attachment.getAttachmentId()));
        }
    }

    private ValidationResult validateFragmentedAttachment(AttachmentForValidation attachment, Map eventAuthTags, List validationProblems) {
        for (final Fragment fragment : attachment.getFragments()) {
            // https://docs.fitko.de/fit-connect/docs/receiving/verification/#authentication-tag-pr%C3%BCfen-2
            final String authTagFromEvent = eventAuthTags.get(fragment.getFragmentId());
            if (!authTagFromEvent.equals(fragment.getAuthTag())) {
                validationProblems.add(new IncorrectAttachmentAuthenticationTag(fragment.getFragmentId()));
            }
        }
        // https://docs.fitko.de/fit-connect/docs/receiving/verification/#attachment-hash
        try (InputStream is = Files.newInputStream(attachment.getDataFile().toPath())) {
            final ValidationResult validationResult = validateHashIntegrity(attachment.getHash(), is);
            if (validationResult.hasError()) {
                validationProblems.add(new AttachmentHashMismatch(attachment.getAttachmentId()));
            }
        } catch (final IOException e) {
            return ValidationResult.error(e);
        }
        return ValidationResult.ok();
    }

    @Override
    public ValidationResult validateSubmissionMetadata(final Metadata metadata, final Submission submission, final Destination destination, final AuthenticationTags eventAuthenticationTags) {
        return validateMetadata(metadata, submission.getEncryptedMetadata(), destination, submission.getServiceType(), submission.getAttachments(), eventAuthenticationTags);

    }

    @Override
    public ValidationResult validateReplyMetadata(final Metadata metadata, final Reply reply, final Destination destination, final AuthenticationTags eventAuthenticationTags) {
        return validateMetadata(metadata, reply.getEncryptedMetadata(), destination, null, reply.getAttachments(), eventAuthenticationTags);
    }

    private ValidationResult validateMetadata(final Metadata metadata, final String encryptedMetadata, final Destination destination, final ServiceType serviceType, final List attachments, final AuthenticationTags eventAuthenticationTags) {

        // https://docs.fitko.de/fit-connect/docs/receiving/verification/#authentication-tag-pr%C3%BCfen
        final var metadataAuthTag = getAuthenticationTagFromEncryptedData(encryptedMetadata);
        if (!eventAuthenticationTags.getMetadata().equals(metadataAuthTag)) {
            return ValidationResult.problem(new IncorrectMetadataAuthenticationTag());
        }

        // https://docs.fitko.de/fit-connect/docs/receiving/verification/#syntax-validierung
        if (invalidJsonSyntax(metadata)) {
            return ValidationResult.problem(new MetadataJsonSyntaxViolation());
        }

        // https://docs.fitko.de/fit-connect/docs/receiving/verification/#fachdatensatz
        if (metadata.getContentStructure().getData() == null) {
            return ValidationResult.problem(new MissingData());
        }

        final ValidationResult validationResult = validateMetadataSchema(metadata);
        if (validationResult.hasProblems()) {
            return validationResult;
        }

        // https://docs.fitko.de/fit-connect/docs/receiving/verification/#fachdatenschema
        if (destination.getServices().isEmpty()) {
            return ValidationResult.problem(new UnsupportedDataSchema());
        }
        final SubmissionSchema submissionSchema = metadata.getContentStructure().getData().getSubmissionSchema();
        final boolean matchingSchemas = matchingDestinationAndSubmissionSchema(destination, submissionSchema.getSchemaUri());
        if (!matchingSchemas) {
            return ValidationResult.problem(new UnsupportedDataSchema());
        }

        // https://docs.fitko.de/fit-connect/docs/receiving/verification/#verwaltungsleistung-abgleichen-ii
        if (serviceType != null) {
            final var destinationService = destination.getServices().stream()
                    .map(DestinationService::getIdentifier)
                    .filter(identifier -> identifier.equals(serviceType.getIdentifier()))
                    .findFirst();

            if (destinationService.isEmpty()) {
                return ValidationResult.problem(new UnsupportedService());
            }
        }

        // https://docs.fitko.de/fit-connect/docs/receiving/verification/#liste-der-anlagen-abgleichen
        final ValidationResult attachmentValidation = checkAttachmentCount(attachments, metadata, eventAuthenticationTags);
        if (attachmentValidation.hasProblems()) {
            return attachmentValidation;
        }

        // https://docs.fitko.de/fit-connect/docs/receiving/verification/#r%C3%BCckkanal
        if (metadata.getReplyChannel() != null && serviceType != null) {
            final Optional matchingServiceReplyChannel = destination.getServices().stream()
                    .filter(service -> service.getIdentifier().equals(serviceType.getIdentifier()))
                    .filter(service -> service.getSubmissionSchemas().stream().anyMatch(schema -> schema.equals(submissionSchema)))
                    .map(DestinationService::getReplyChannels)
                    .filter(Objects::nonNull)
                    .filter(destinationAllowsSubmissionReplyChannel(metadata.getReplyChannel()))
                    .findFirst();

            if (matchingServiceReplyChannel.isEmpty()) {
                return ValidationResult.problem(new UnsupportedReplyChannel());
            }
        }

        return ValidationResult.ok();
    }

    private boolean validateIfDataIsNotTransferredAsAttachment(Metadata metadata, Data data) {
        final boolean noAttachmentWithDataPurpose = metadata.getContentStructure()
                .getAttachments()
                .stream()
                .map(ApiAttachment::getPurpose)
                .noneMatch(purpose -> purpose.equals(Purpose.DATA));

        return noAttachmentWithDataPurpose && !data.getHash().getContent().equals(HASH_OF_ZERO_BYTES);
    }

    private Predicate destinationAllowsSubmissionReplyChannel(final ReplyChannel submissionReplyChannel) {
        return replyChannel -> {
            final var destinationReplyChannelTypes = getNonNullReplyChannels(replyChannel);
            final var submissionReplyChannelTypes = getNonNullReplyChannels(submissionReplyChannel);
            return new HashSet<>(destinationReplyChannelTypes).containsAll(submissionReplyChannelTypes);
        };
    }

    private List> getNonNullReplyChannels(final ReplyChannel replyChannel) {
        return Stream.of(replyChannel.getEMail(),
                        replyChannel.getDeMail(),
                        replyChannel.getFink(),
                        replyChannel.getElster(),
                        replyChannel.getFitConnect(),
                        replyChannel.getIdBundDeMailbox())
                .filter(Objects::nonNull)
                // Getting the type is sufficient. We do not want an actual content comparison of objects ,
                // since the destinations reply channel content will be null even if they are set.
                .map(Object::getClass).collect(Collectors.toList());
    }

    private static boolean matchingDestinationAndSubmissionSchema(final Destination destination, final URI submissionDataSchemaUri) {
        return destination.getServices().stream()
                .flatMap(service -> service.getSubmissionSchemas().stream())
                .map(SubmissionSchema::getSchemaUri)
                .anyMatch(submissionDataSchemaUri::equals);
    }

    private ValidationResult validate2020JsonSchema(final String schema, final JsonNode inputNode) {
        return returnValidationResult(SCHEMA_FACTORY_DRAFT_2020.getSchema(schema, VALIDATORS_CONFIG).validate(inputNode));
    }


    private ValidationResult returnValidationResult(final Set errors) {
        if (errors.isEmpty()) {
            return ValidationResult.ok();
        }
        return ValidationResult.withErrorAndProblem(new ValidationException(errorsToSingleString(errors)), new MetadataSchemaViolation());
    }

    private ValidationResult validateKey(final RSAKey publicKey, final KeyOperation purpose) throws JWKValidationException {
        if (config.isAllowInsecurePublicKey()) {
            return validateWithoutCertChain(publicKey, purpose);
        } else {
            return validateWithCertChain(publicKey, purpose);
        }
    }

    private ValidationResult validateWithoutCertChain(final RSAKey publicKey, final KeyOperation purpose) throws JWKValidationException {
        LOGGER.debug("Validating public key {} without XC5 certificate chain", publicKey.getKeyID());
        jwkValidator.validate(publicKey, purpose);
        return ValidationResult.ok();
    }

    private ValidationResult validateWithCertChain(final RSAKey publicKey, final KeyOperation purpose) throws JWKValidationException {
        LOGGER.info("Validating public key with x5c certificate chain checks");
        if (publicKey.getParsedX509CertChain() == null) {
            throw new IllegalStateException("Public key with id '" + publicKey.getKeyID() + "' does not contain an x5c certificate chain");
        }
        jwkValidator.validate(publicKey, purpose);
        return ValidationResult.ok();
    }

    private String errorsToSingleString(final Set errors) {
        return errors.stream()
                .map(ValidationMessage::getMessage)
                .collect(Collectors.joining("\n"));
    }

    private static XMLReader getXmlReader() throws ParserConfigurationException, SAXException {
        final SAXParserFactory saxParserFactory = SAXParserFactory.newInstance();
        saxParserFactory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
        return saxParserFactory.newSAXParser().getXMLReader();
    }

    private boolean invalidJsonSyntax(final Metadata metadata) {
        try {
            return metadata == null || validateJsonFormat(MAPPER.writeValueAsBytes(metadata)).hasError();
        } catch (final JsonProcessingException | NullPointerException e) {
            return true;
        }
    }

    private ValidationResult checkAttachmentCount(final List attachmentIds, final Metadata metadata, final AuthenticationTags authenticationTags) {

        final List metadataAttachments = metadata.getContentStructure().getAttachments();
        final Map attachmentAuthTags = authenticationTags.getAttachments();

        if ((isNull(attachmentIds) || attachmentIds.isEmpty()) && (isNull(attachmentAuthTags) || attachmentAuthTags.isEmpty())) {
            return ValidationResult.ok();
        }

        final List actualIdsIncludingFragments = new ArrayList<>();
        for (final ApiAttachment metadataAttachment : metadataAttachments) {
            final List fragments = metadataAttachment.getFragments();
            if (metadataAttachment.hasFragments()) {
                actualIdsIncludingFragments.addAll(fragments);
            } else {
                actualIdsIncludingFragments.add(metadataAttachment.getAttachmentId());
            }
        }

        if (actualIdsIncludingFragments.size() != attachmentIds.size()
                || attachmentAuthTags.size() != attachmentIds.size()
                || !attachmentAuthTags.keySet().containsAll(attachmentIds)
                || !attachmentIds.stream().allMatch(id -> actualIdsIncludingFragments.stream().anyMatch(actualId -> actualId.equals(id)))
                || !attachmentIds.stream().allMatch(attachmentAuthTags::containsKey)) {
            return ValidationResult.problem(new AttachmentsMismatch());
        }

        return ValidationResult.ok();
    }

    private JWKValidator createValidator(final ApplicationConfig config, final List trustedRootCertificates) {
        if (config.isAllowInsecurePublicKey()) {
            return JWKValidator.withoutX5CValidation()
                    .withErrorLogLevel(LogLevel.WARN)
                    .build();
        }
        return JWKValidator.withRecommendedDefaults()
                .withProxy(config.getHttpConfig().getProxyConfig().getHttpProxy())
                .withRootCertificatesAsPEM(trustedRootCertificates)
                .build();
    }

    private static ObjectMapper createObjectMapper() {
        final JsonFactory jsonFactory = new JsonFactoryBuilder().streamReadConstraints(
                StreamReadConstraints.builder()
                        .maxStringLength(Integer.MAX_VALUE)
                        .build()).build();
        return new ObjectMapper(jsonFactory)
                .setDateFormat(new SimpleDateFormat("yyyy-MM-dd"))
                .setSerializationInclusion(JsonInclude.Include.NON_NULL);
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy