dev.fitko.fitconnect.core.keys.PublicKeyApiService Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of client Show documentation
Show all versions of client Show documentation
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;
}
}