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

com.yahoo.security.tls.TransportSecurityOptionsJsonSerializer Maven / Gradle / Ivy

There is a newer version: 8.411.13
Show newest version
// 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 - 2024 Weber Informatics LLC | Privacy Policy