
dev.fitko.fitconnect.core.keys.PublicKeyApiService Maven / Gradle / Ivy
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.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());
final RSAKey signatureKey = filterKeysById(keyId, wellKnownKeys.getKeys());
validateSignatureKey(signatureKey);
return signatureKey;
}
@Override
public RSAKey getPortalPublicKey(final String keyId) {
final String portalUrl = config.getSelfServicePortalBaseUrl() + WELL_KNOWN_KEYS_PATH;
final ApiJwkSet wellKnownKeys = performRequest(portalUrl, ApiJwkSet.class, getHeadersWithoutAuth());
final RSAKey signatureKey = filterKeysById(keyId, wellKnownKeys.getKeys());
validateSignatureKey(signatureKey);
return signatureKey;
}
@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);
final RSAKey signatureKey = filterKeysById(keyId, wellKnownKeys.getKeys());
validateSignatureKey(signatureKey);
return signatureKey;
}
private RSAKey filterKeysById(final String keyId, final List keys) {
return keys.stream()
.filter(key -> key.getKid().equals(keyId))
.map(this::toRSAKey)
.findFirst()
.orElse(null);
}
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 - 2025 Weber Informatics LLC | Privacy Policy