com.yahoo.security.tls.TransportSecurityOptionsJsonSerializer Maven / Gradle / Ivy
// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.security.tls;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.yahoo.security.tls.TransportSecurityOptionsEntity.AuthorizedPeer;
import com.yahoo.security.tls.TransportSecurityOptionsEntity.CredentialField;
import com.yahoo.security.tls.TransportSecurityOptionsEntity.Files;
import com.yahoo.security.tls.TransportSecurityOptionsEntity.RequiredCredential;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UncheckedIOException;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import static java.util.stream.Collectors.toSet;
/**
* @author bjorncs
*/
class TransportSecurityOptionsJsonSerializer {
private static final ObjectMapper mapper = new ObjectMapper();
TransportSecurityOptions deserialize(InputStream in) {
try {
TransportSecurityOptionsEntity entity = mapper.readValue(in, TransportSecurityOptionsEntity.class);
return toTransportSecurityOptions(entity);
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
void serialize(OutputStream out, TransportSecurityOptions options) {
try {
mapper.writerWithDefaultPrettyPrinter().writeValue(out, toTransportSecurityOptionsEntity(options));
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
private static TransportSecurityOptions toTransportSecurityOptions(TransportSecurityOptionsEntity entity) {
TransportSecurityOptions.Builder builder = new TransportSecurityOptions.Builder();
Files files = entity.files;
if (files != null) {
if (files.certificatesFile != null && files.privateKeyFile != null) {
builder.withCertificates(Paths.get(files.certificatesFile), Paths.get(files.privateKeyFile));
} else if (files.certificatesFile != null || files.privateKeyFile != null) {
throw new IllegalArgumentException("Both 'private-key' and 'certificates' must be configured together");
}
if (files.caCertificatesFile != null) {
builder.withCaCertificates(Paths.get(files.caCertificatesFile));
}
}
List authorizedPeersEntity = entity.authorizedPeers;
if (authorizedPeersEntity != null) {
if (authorizedPeersEntity.isEmpty()) {
throw new IllegalArgumentException("'authorized-peers' cannot be empty");
}
builder.withAuthorizedPeers(new AuthorizedPeers(toPeerPolicies(authorizedPeersEntity)));
}
if (entity.acceptedCiphers != null) {
if (entity.acceptedCiphers.isEmpty()) {
throw new IllegalArgumentException("'accepted-ciphers' cannot be empty");
}
builder.withAcceptedCiphers(entity.acceptedCiphers);
}
if (entity.acceptedProtocols != null) {
if (entity.acceptedProtocols.isEmpty()) {
throw new IllegalArgumentException("'accepted-protocols' cannot be empty");
}
builder.withAcceptedProtocols(entity.acceptedProtocols);
}
if (entity.isHostnameValidationDisabled != null) {
builder.withHostnameValidationDisabled(entity.isHostnameValidationDisabled);
}
return builder.build();
}
private static Set toPeerPolicies(List authorizedPeersEntity) {
return authorizedPeersEntity.stream()
.map(TransportSecurityOptionsJsonSerializer::toPeerPolicy)
.collect(toSet());
}
private static PeerPolicy toPeerPolicy(AuthorizedPeer authorizedPeer) {
if (authorizedPeer.name == null) {
throw missingFieldException("name");
}
if (authorizedPeer.requiredCredentials == null) {
throw missingFieldException("required-credentials");
}
return new PeerPolicy(authorizedPeer.name, Optional.ofNullable(authorizedPeer.description),
toCapabilities(authorizedPeer.capabilities), toRequestPeerCredentials(authorizedPeer.requiredCredentials));
}
private static Set toCapabilities(List capabilities) {
if (capabilities == null) return Set.of(CapabilitySet.ALL.toPredefinedName().get());
if (capabilities.isEmpty())
throw new IllegalArgumentException("\"capabilities\" array must either be not present " +
"(implies all capabilities) or contain at least one capability name");
return Set.copyOf(capabilities);
}
private static List toRequestPeerCredentials(List requiredCredentials) {
return requiredCredentials.stream()
.map(TransportSecurityOptionsJsonSerializer::toRequiredPeerCredential)
.toList();
}
private static RequiredPeerCredential toRequiredPeerCredential(RequiredCredential requiredCredential) {
if (requiredCredential.field == null) {
throw missingFieldException("field");
}
if (requiredCredential.matchExpression == null) {
throw missingFieldException("must-match");
}
return RequiredPeerCredential.of(toField(requiredCredential.field), requiredCredential.matchExpression);
}
private static RequiredPeerCredential.Field toField(CredentialField field) {
return switch (field) {
case CN -> RequiredPeerCredential.Field.CN;
case SAN_DNS -> RequiredPeerCredential.Field.SAN_DNS;
case SAN_URI -> RequiredPeerCredential.Field.SAN_URI;
default -> throw new IllegalArgumentException("Invalid field type: " + field);
};
}
private static TransportSecurityOptionsEntity toTransportSecurityOptionsEntity(TransportSecurityOptions options) {
TransportSecurityOptionsEntity entity = new TransportSecurityOptionsEntity();
entity.files = new Files();
options.getCaCertificatesFile().ifPresent(value -> entity.files.caCertificatesFile = value.toString());
options.getCertificatesFile().ifPresent(value -> entity.files.certificatesFile = value.toString());
options.getPrivateKeyFile().ifPresent(value -> entity.files.privateKeyFile = value.toString());
entity.authorizedPeers = options.getAuthorizedPeers().peerPolicies().stream()
// Makes tests stable
.sorted(Comparator.comparing(PeerPolicy::policyName))
.map(peerPolicy -> {
AuthorizedPeer authorizedPeer = new AuthorizedPeer();
authorizedPeer.name = peerPolicy.policyName();
authorizedPeer.requiredCredentials = new ArrayList<>();
authorizedPeer.description = peerPolicy.description().orElse(null);
CapabilitySet caps = peerPolicy.capabilities();
if (!caps.hasAll()) {
authorizedPeer.capabilities = peerPolicy.capabilityNames().stream().sorted().toList();
}
for (RequiredPeerCredential requiredPeerCredential : peerPolicy.requiredCredentials()) {
RequiredCredential requiredCredential = new RequiredCredential();
requiredCredential.field = toField(requiredPeerCredential.field());
requiredCredential.matchExpression = requiredPeerCredential.pattern().asString();
authorizedPeer.requiredCredentials.add(requiredCredential);
}
return authorizedPeer;
})
.toList();
if (!options.getAcceptedCiphers().isEmpty()) {
entity.acceptedCiphers = options.getAcceptedCiphers();
}
if (!options.getAcceptedProtocols().isEmpty()) {
entity.acceptedProtocols = options.getAcceptedProtocols();
}
if (options.isHostnameValidationDisabled()) {
entity.isHostnameValidationDisabled = true;
}
return entity;
}
private static CredentialField toField(RequiredPeerCredential.Field field) {
return switch (field) {
case CN -> CredentialField.CN;
case SAN_DNS -> CredentialField.SAN_DNS;
case SAN_URI -> CredentialField.SAN_URI;
default -> throw new IllegalArgumentException("Invalid field type: " + field);
};
}
private static IllegalArgumentException missingFieldException(String fieldName) {
return new IllegalArgumentException(String.format("'%s' missing", fieldName));
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy