Skip to content

Commit

Permalink
Add TrustCustom (#5141)
Browse files Browse the repository at this point in the history
Adds io.deephaven.ssl.config.TrustCustom with construction helpers for InputStream, Path, and byte[].

Fixes #5135
  • Loading branch information
devinrsmith authored Feb 15, 2024
1 parent 8c64c1b commit d4c786c
Show file tree
Hide file tree
Showing 6 changed files with 182 additions and 1 deletion.
3 changes: 3 additions & 0 deletions ssl/config/src/main/java/io/deephaven/ssl/config/Trust.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
* @see TrustJdk
* @see TrustProperties
* @see TrustSystem
* @see TrustCustom
* @see TrustAll
* @see TrustList
*/
Expand Down Expand Up @@ -48,6 +49,8 @@ interface Visitor<T> {

T visit(TrustSystem system);

T visit(TrustCustom custom);

T visit(TrustAll all);

T visit(TrustList list);
Expand Down
147 changes: 147 additions & 0 deletions ssl/config/src/main/java/io/deephaven/ssl/config/TrustCustom.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
/**
* Copyright (c) 2016-2023 Deephaven Data Labs and Patent Pending
*/
package io.deephaven.ssl.config;

import io.deephaven.annotations.BuildableStyle;
import org.immutables.value.Value.Immutable;

import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.util.List;

/**
* A custom trust configuration provided via {@link Certificate}. This is not currently deserializable via JSON.
*/
@Immutable
@BuildableStyle
public abstract class TrustCustom extends TrustBase {
private static final String X_509 = "X.509";

public static Builder builder() {
return ImmutableTrustCustom.builder();
}

/**
* Creates a trust from the given {@code factory} and {@code in}. Equivalent to
* {@code builder().addAllCertificates(factory.generateCertificates(in)).build()}.
*
* @param factory the certificate factory
* @param in the input stream
* @return the trust
* @throws IOException if an IO exception occurs
* @throws CertificateException on parsing errors
*/
public static TrustCustom of(CertificateFactory factory, InputStream in) throws IOException, CertificateException {
return builder().addAllCertificates(factory.generateCertificates(in)).build();
}

/**
* Creates a trust from the given {@code factory} and {@code path}.
*
* @param factory the certificate factory
* @param path the path
* @return the trust
* @throws IOException if an IO exception occurs
* @throws CertificateException on parsing errors
*/
public static TrustCustom of(CertificateFactory factory, Path path) throws IOException, CertificateException {
try (final InputStream in = new BufferedInputStream(Files.newInputStream(path))) {
return of(factory, in);
}
}

/**
* Creates a trust from the given {@code factory} and {@code in}. Equivalent to
* {@code of(factory, new ByteArrayInputStream(in, offset, length))}.
*
* @param factory the certificate factory
* @param in the input bytes
* @param offset the input offset
* @param length the input length
* @return the trust
* @throws IOException if an IO exception occurs
* @throws CertificateException on parsing errors
* @see ByteArrayInputStream#ByteArrayInputStream(byte[], int, int)
* @see #of(CertificateFactory, InputStream).
*/
public static TrustCustom of(CertificateFactory factory, byte[] in, int offset, int length)
throws IOException, CertificateException {
return of(factory, new ByteArrayInputStream(in, offset, length));
}

/**
* Creates an X509 trust from the given {@code in}. Equivalent to
* {@code of(CertificateFactory.getInstance("X.509"), in)}.
*
* @param in the input stream
* @return the trust
* @throws IOException if an IO exception occurs
* @throws CertificateException on parsing errors
* @see CertificateFactory#getInstance(String)
* @see #of(CertificateFactory, InputStream)
*/
public static TrustCustom ofX509(InputStream in) throws CertificateException, IOException {
return of(CertificateFactory.getInstance(X_509), in);
}

/**
* Creates an X509 trust from the given {@code path}. Equivalent to
* {@code of(CertificateFactory.getInstance("X.509"), path)}.
*
* @param path the path
* @return the trust
* @throws IOException if an IO exception occurs
* @throws CertificateException on parsing errors
* @see CertificateFactory#getInstance(String)
* @see #of(CertificateFactory, Path)
*/
public static TrustCustom ofX509(Path path) throws CertificateException, IOException {
return of(CertificateFactory.getInstance(X_509), path);
}

/**
* Creates an X509 trust from the given {@code in}. Equivalent to
* {@code of(CertificateFactory.getInstance("X.509"), in, offset, length)}.
*
* @param in the input bytes
* @param offset the input offset
* @param length the input length
* @return the trust
* @throws IOException if an IO exception occurs
* @throws CertificateException on parsing errors
* @see CertificateFactory#getInstance(String)
* @see #of(CertificateFactory, byte[], int, int)
*/
public static TrustCustom ofX509(byte[] in, int offset, int length) throws CertificateException, IOException {
return of(CertificateFactory.getInstance(X_509), in, offset, length);
}

// This is structurally more specific than what we could achieve with TrustList. There's potential in the future to
// extend this with Function<KeyStore, CertPathTrustManagerParameters> if callers need more configurability.

public abstract List<Certificate> certificates();

@Override
public final <T> T walk(Visitor<T> visitor) {
return visitor.visit(this);
}

public interface Builder {

Builder addCertificates(Certificate element);

Builder addCertificates(Certificate... elements);

Builder addAllCertificates(Iterable<? extends Certificate> elements);

TrustCustom build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ public static TrustStore of(String path, String password) {
public abstract String path();

/**
* The trust storce password.
* The trust store password.
*/
public abstract String password();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,14 @@
import io.deephaven.ssl.config.SSLConfig.ClientAuth;
import org.junit.jupiter.api.Test;

import javax.security.auth.x500.X500Principal;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.Objects;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.failBecauseExceptionWasNotThrown;
Expand Down Expand Up @@ -87,6 +93,23 @@ void trustAll() throws IOException {
check("trust-all.json", SSLConfig.builder().trust(TrustAll.of()).build());
}

@Test
void trustCustom() throws IOException, CertificateException {
// See server/dev-certs/README.md for information about server.chain.crt.
final TrustCustom custom;
try (final InputStream in = new BufferedInputStream(
Objects.requireNonNull(SSLConfigTest.class.getResourceAsStream("server.chain.crt")))) {
custom = TrustCustom.ofX509(in);
}
assertThat(custom.certificates()).hasSize(2);
assertThat(custom.certificates().get(0)).isInstanceOf(X509Certificate.class);
assertThat(((X509Certificate) custom.certificates().get(0)).getSubjectX500Principal())
.isEqualTo(new X500Principal("CN=localhost"));
assertThat(custom.certificates().get(1)).isInstanceOf(X509Certificate.class);
assertThat(((X509Certificate) custom.certificates().get(1)).getSubjectX500Principal())
.isEqualTo(new X500Principal("CN=deephaven-localhost-testing-ca"));
}

@Test
void ciphersExplicit() throws IOException {
check("ciphers-explicit.json",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import io.deephaven.ssl.config.Trust;
import io.deephaven.ssl.config.TrustAll;
import io.deephaven.ssl.config.TrustCertificates;
import io.deephaven.ssl.config.TrustCustom;
import io.deephaven.ssl.config.TrustJdk;
import io.deephaven.ssl.config.TrustList;
import io.deephaven.ssl.config.TrustProperties;
Expand Down Expand Up @@ -90,6 +91,12 @@ public Void visit(TrustSystem system) {
return null;
}

@Override
public Void visit(TrustCustom custom) {
builder.withTrustMaterial(custom.certificates());
return null;
}

@Override
public Void visit(TrustAll all) {
builder.withTrustingAllCertificatesWithoutValidation();
Expand Down

0 comments on commit d4c786c

Please sign in to comment.