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

dev.fitko.fitconnect.core.keys.PublicKeyApiService 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.keys;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
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.destination.Destination;
import dev.fitko.fitconnect.api.domain.model.jwk.ApiJwk;
import dev.fitko.fitconnect.api.domain.model.jwk.ApiJwkSet;
import dev.fitko.fitconnect.api.domain.validation.ValidationResult;
import dev.fitko.fitconnect.api.exceptions.internal.InvalidKeyException;
import dev.fitko.fitconnect.api.exceptions.internal.RestApiException;
import dev.fitko.fitconnect.api.services.auth.OAuthService;
import dev.fitko.fitconnect.api.services.http.HttpClient;
import dev.fitko.fitconnect.api.services.keys.KeyService;
import dev.fitko.fitconnect.api.services.validation.ValidationService;
import dev.fitko.fitconnect.core.http.HttpHeaders;
import dev.fitko.fitconnect.core.http.MimeTypes;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.nio.charset.StandardCharsets;
import java.text.ParseException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;

public class PublicKeyApiService implements KeyService {
    public static final String DESTINATIONS_KEY_PATH = "/v1/destinations/%s/keys/%s";
    public static final String WELL_KNOWN_KEYS_PATH = "/.well-known/jwks.json";

    private static final Logger LOGGER = LoggerFactory.getLogger(PublicKeyApiService.class);
    private static final ObjectMapper MAPPER = new ObjectMapper();

    private final ApplicationConfig config;
    private final ValidationService validationService;
    private final HttpClient httpClient;
    private final OAuthService authService;

    public PublicKeyApiService(
            final ApplicationConfig config,
            final HttpClient httpClient,
            final OAuthService authService,
            final ValidationService validationService) {
        this.config = config;
        this.httpClient = httpClient;
        this.authService = authService;
        this.validationService = validationService;
    }

    public PublicKeyApiService(final ApplicationConfig config, final HttpClient httpClient, final ValidationService validationService) {
        this(config, httpClient, null, validationService);
    }

    @Override
    public RSAKey getPublicEncryptionKey(final Destination destination) {
        final String destinationUrl = config.getSubmissionBaseUrl() + DESTINATIONS_KEY_PATH;
        final ApiJwk publicKey = performRequest(destinationUrl, ApiJwk.class, getHeaders(), destination.getDestinationId(), destination.getEncryptionKid());
        final RSAKey rsaKey = toRSAKey(publicKey);
        validateEncryptionKey(rsaKey);
        return rsaKey;
    }

    @Override
    public RSAKey getPublicSignatureKey(final UUID destinationId, final String keyId) {
        final String destinationUrl = config.getSubmissionBaseUrl() + DESTINATIONS_KEY_PATH;
        final ApiJwk signatureKey = performRequest(destinationUrl, ApiJwk.class, getHeaders(), destinationId, keyId);
        final RSAKey rsaKey = toRSAKey(signatureKey);
        validateSignatureKey(rsaKey);
        return rsaKey;
    }

    @Override
    public RSAKey getSubmissionServicePublicKey(final String keyId) {
        final String submissionServiceUrl = config.getSubmissionBaseUrl() + WELL_KNOWN_KEYS_PATH;
        final ApiJwkSet wellKnownKeys = performRequest(submissionServiceUrl, ApiJwkSet.class, getHeaders());
        return getValidatedKey(keyId, wellKnownKeys, submissionServiceUrl);
    }

    @Override
    public RSAKey getPortalPublicKey(final String keyId) {
        final String portalUrl = config.getSelfServicePortalBaseUrl() + WELL_KNOWN_KEYS_PATH;
        final ApiJwkSet wellKnownKeys = performRequest(portalUrl, ApiJwkSet.class, getHeadersWithoutAuth());
        return getValidatedKey(keyId, wellKnownKeys, portalUrl);
    }

    @Override
    public RSAKey getWellKnownKeysForSubmissionUrl(final String url, final String keyId) {
        final var requestUrl = !url.endsWith("/") ? url + WELL_KNOWN_KEYS_PATH : url;
        final ApiJwkSet wellKnownKeys = performRequest(requestUrl, ApiJwkSet.class, getHeadersWithoutAuth(), keyId);
        return getValidatedKey(keyId, wellKnownKeys, requestUrl);
    }

    private RSAKey getValidatedKey(String keyId, ApiJwkSet wellKnownKeys, String requestUrl) {
        final Optional signatureKey = filterKeysById(keyId, wellKnownKeys.getKeys());
        if (signatureKey.isEmpty()) {
            throw new InvalidKeyException("Key with id " + keyId + " could not be found at url " + requestUrl);
        }
        final RSAKey rsaKey = signatureKey.get();
        validateSignatureKey(rsaKey);
        return rsaKey;
    }

    private Optional filterKeysById(final String keyId, final List keys) {
        return keys.stream()
                .filter(key -> key.getKid().equals(keyId))
                .map(this::toRSAKey)
                .findFirst();
    }

    private void validateEncryptionKey(final RSAKey rsaKey) {
        final ValidationResult result = validationService.validatePublicKey(rsaKey, KeyOperation.WRAP_KEY);
        validateResult(result, "Invalid public encryption key");
    }

    private void validateSignatureKey(final RSAKey rsaKey) {
        final ValidationResult result = validationService.validatePublicKey(rsaKey, KeyOperation.VERIFY);
        validateResult(result, "Public signature key is not valid");
    }

    private void validateResult(final ValidationResult validationResult, final String message) {
        if (validationResult.hasError()) {
            if (config.isAllowInsecurePublicKey()) {
                LOGGER.warn(message, validationResult.getError());
            } else {
                throw new InvalidKeyException(message, validationResult.getError());
            }
        }
    }

    private RSAKey toRSAKey(final ApiJwk jwk) {
        try {
            return RSAKey.parse(MAPPER.writeValueAsString(jwk));
        } catch (final JsonProcessingException | ParseException e) {
            throw new InvalidKeyException("Key could not be parsed", e);
        }
    }

    private  T performRequest(final String url, final Class responseType, final Map headers, final Object... params) {
        try {
            return httpClient.get(String.format(url, params), headers, responseType).getBody();
        } catch (final RestApiException e) {
            throw new RestApiException("Request failed", e);
        }
    }

    private Map getHeadersWithoutAuth() {

        return new HashMap<>(Map.of(
                HttpHeaders.CONTENT_TYPE, MimeTypes.APPLICATION_JSON,
                HttpHeaders.ACCEPT_CHARSET, StandardCharsets.UTF_8.toString()));
    }

    private Map getHeaders() {
        final var headers = getHeadersWithoutAuth();
        headers.put(HttpHeaders.AUTHORIZATION, "Bearer " + authService.getCurrentToken().getAccessToken());
        return headers;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy