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

com.exasol.containers.tls.CertificateProvider Maven / Gradle / Ivy

Go to download

This module provides abstraction for generation, startup, shutdown and use of an Exasol database running on Docker.

The newest version!
package com.exasol.containers.tls;

import static com.exasol.errorreporting.ExaError.messageBuilder;

import java.io.*;
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.cert.*;
import java.util.Optional;
import java.util.function.Supplier;

import com.exasol.config.ClusterConfiguration;
import com.exasol.containers.*;

/**
 * This class allows reading the TLS certificate used by the Exasol container.
 */
public class CertificateProvider {
    private final Supplier> configProvider;
    private final ContainerFileOperations fileOperations;

    /**
     * Create a new instance.
     *
     * @param configProvider {@link Supplier} for {@link ClusterConfiguration}. May return an empty optional in case no
     *                       configuration is available.
     * @param fileOperations file operations allowing access to the containers files
     */
    public CertificateProvider(final Supplier> configProvider,
            final ContainerFileOperations fileOperations) {
        this.configProvider = configProvider;
        this.fileOperations = fileOperations;
    }

    CertificateProvider(final ExasolContainer> container,
            final ContainerFileOperations fileOperations) {
        this(() -> Optional.of(container.getClusterConfiguration()), fileOperations);
    }

    /**
     * Read and convert the self-signed TLS certificate used by the database in the container for database connections
     * and the RPC interface.
     *
     * @return the TLS certificate or an empty {@link Optional} when no configuration exists or the certificate file
     *         does not exist.
     */
    public Optional getCertificate() {
        return readCertificate().map(this::parseCertificate);
    }

    private Optional readCertificate() {
        final Optional configuration = this.configProvider.get();
        if (configuration.isEmpty()) {
            return Optional.empty();
        }
        final String certPath = configuration.get().getTlsCertificatePath();
        try {
            final String certContent = this.fileOperations.readFile(certPath, StandardCharsets.UTF_8);
            return Optional.of(certContent);
        } catch (final ExasolContainerException exception) {
            return Optional.empty();
        }
    }

    private X509Certificate parseCertificate(final String certContent) {
        try (final InputStream is = new ByteArrayInputStream(certContent.getBytes(StandardCharsets.UTF_8))) {
            final CertificateFactory cf = CertificateFactory.getInstance("X.509");
            return (X509Certificate) cf.generateCertificate(is);
        } catch (final CertificateException | IOException exception) {
            throw new IllegalStateException(
                    messageBuilder("F-ETC-7").message("Error parsing certificate {{certificateContent}}.", certContent)
                            .ticketMitigation().toString(),
                    exception);
        }
    }

    /**
     * Get the SHA-256 checksum of the encoded certificate as a hex string. This is required as a fingerprint when
     * connecting to the database via JDBC using property {@code validateservercertificate=1}.
     *
     * @return the SHA-256 checksum of the certificate as a hex string or an empty {@link Optional} when no
     *         configuration exists or the certificate file does not exist
     */
    public Optional getSha256Fingerprint() {
        return getEncodedCertificate() //
                .map(CertificateProvider::sha256) //
                .map(bytes -> bytesToHexWithPadding(bytes, 32));
    }

    private Optional getEncodedCertificate() {
        final Optional certificate = getCertificate();
        if (certificate.isEmpty()) {
            return Optional.empty();
        }
        try {
            return Optional.of(certificate.get().getEncoded());
        } catch (final CertificateEncodingException exception) {
            throw new IllegalStateException(messageBuilder("F-ETC-8")
                    .message("Unable get encoded certificate for {{certificate}}.", certificate.get())
                    .ticketMitigation().toString(), exception);
        }
    }

    static byte[] sha256(final byte[] data) {
        try {
            final MessageDigest md = MessageDigest.getInstance("SHA-256");
            md.update(data);
            return md.digest();
        } catch (final NoSuchAlgorithmException exception) {
            throw new IllegalStateException(messageBuilder("F-ETC-9")
                    .message("Unable to calculate SHA-256 of certificate content.").ticketMitigation().toString(),
                    exception);
        }
    }

    static String bytesToHexWithPadding(final byte[] bytes, final int byteCount) {
        final String hex = bytesToHex(bytes);
        if (hex.length() >= (byteCount * 2)) {
            return hex;
        }
        return "0".repeat((byteCount * 2) - hex.length()) + hex;
    }

    static String bytesToHex(final byte[] bytes) {
        final String hex = new BigInteger(1, bytes).toString(16);
        if ((hex.length() % 2) == 0) {
            return hex;
        } else {
            return "0" + hex;
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy