Skip to content

Commit d4c786c

Browse files
authored
Add TrustCustom (#5141)
Adds io.deephaven.ssl.config.TrustCustom with construction helpers for InputStream, Path, and byte[]. Fixes #5135
1 parent 8c64c1b commit d4c786c

File tree

6 files changed

+182
-1
lines changed

6 files changed

+182
-1
lines changed

ssl/config/src/main/java/io/deephaven/ssl/config/Trust.java

+3
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
* @see TrustJdk
1515
* @see TrustProperties
1616
* @see TrustSystem
17+
* @see TrustCustom
1718
* @see TrustAll
1819
* @see TrustList
1920
*/
@@ -48,6 +49,8 @@ interface Visitor<T> {
4849

4950
T visit(TrustSystem system);
5051

52+
T visit(TrustCustom custom);
53+
5154
T visit(TrustAll all);
5255

5356
T visit(TrustList list);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
/**
2+
* Copyright (c) 2016-2023 Deephaven Data Labs and Patent Pending
3+
*/
4+
package io.deephaven.ssl.config;
5+
6+
import io.deephaven.annotations.BuildableStyle;
7+
import org.immutables.value.Value.Immutable;
8+
9+
import java.io.BufferedInputStream;
10+
import java.io.ByteArrayInputStream;
11+
import java.io.IOException;
12+
import java.io.InputStream;
13+
import java.nio.file.Files;
14+
import java.nio.file.Path;
15+
import java.security.cert.Certificate;
16+
import java.security.cert.CertificateException;
17+
import java.security.cert.CertificateFactory;
18+
import java.util.List;
19+
20+
/**
21+
* A custom trust configuration provided via {@link Certificate}. This is not currently deserializable via JSON.
22+
*/
23+
@Immutable
24+
@BuildableStyle
25+
public abstract class TrustCustom extends TrustBase {
26+
private static final String X_509 = "X.509";
27+
28+
public static Builder builder() {
29+
return ImmutableTrustCustom.builder();
30+
}
31+
32+
/**
33+
* Creates a trust from the given {@code factory} and {@code in}. Equivalent to
34+
* {@code builder().addAllCertificates(factory.generateCertificates(in)).build()}.
35+
*
36+
* @param factory the certificate factory
37+
* @param in the input stream
38+
* @return the trust
39+
* @throws IOException if an IO exception occurs
40+
* @throws CertificateException on parsing errors
41+
*/
42+
public static TrustCustom of(CertificateFactory factory, InputStream in) throws IOException, CertificateException {
43+
return builder().addAllCertificates(factory.generateCertificates(in)).build();
44+
}
45+
46+
/**
47+
* Creates a trust from the given {@code factory} and {@code path}.
48+
*
49+
* @param factory the certificate factory
50+
* @param path the path
51+
* @return the trust
52+
* @throws IOException if an IO exception occurs
53+
* @throws CertificateException on parsing errors
54+
*/
55+
public static TrustCustom of(CertificateFactory factory, Path path) throws IOException, CertificateException {
56+
try (final InputStream in = new BufferedInputStream(Files.newInputStream(path))) {
57+
return of(factory, in);
58+
}
59+
}
60+
61+
/**
62+
* Creates a trust from the given {@code factory} and {@code in}. Equivalent to
63+
* {@code of(factory, new ByteArrayInputStream(in, offset, length))}.
64+
*
65+
* @param factory the certificate factory
66+
* @param in the input bytes
67+
* @param offset the input offset
68+
* @param length the input length
69+
* @return the trust
70+
* @throws IOException if an IO exception occurs
71+
* @throws CertificateException on parsing errors
72+
* @see ByteArrayInputStream#ByteArrayInputStream(byte[], int, int)
73+
* @see #of(CertificateFactory, InputStream).
74+
*/
75+
public static TrustCustom of(CertificateFactory factory, byte[] in, int offset, int length)
76+
throws IOException, CertificateException {
77+
return of(factory, new ByteArrayInputStream(in, offset, length));
78+
}
79+
80+
/**
81+
* Creates an X509 trust from the given {@code in}. Equivalent to
82+
* {@code of(CertificateFactory.getInstance("X.509"), in)}.
83+
*
84+
* @param in the input stream
85+
* @return the trust
86+
* @throws IOException if an IO exception occurs
87+
* @throws CertificateException on parsing errors
88+
* @see CertificateFactory#getInstance(String)
89+
* @see #of(CertificateFactory, InputStream)
90+
*/
91+
public static TrustCustom ofX509(InputStream in) throws CertificateException, IOException {
92+
return of(CertificateFactory.getInstance(X_509), in);
93+
}
94+
95+
/**
96+
* Creates an X509 trust from the given {@code path}. Equivalent to
97+
* {@code of(CertificateFactory.getInstance("X.509"), path)}.
98+
*
99+
* @param path the path
100+
* @return the trust
101+
* @throws IOException if an IO exception occurs
102+
* @throws CertificateException on parsing errors
103+
* @see CertificateFactory#getInstance(String)
104+
* @see #of(CertificateFactory, Path)
105+
*/
106+
public static TrustCustom ofX509(Path path) throws CertificateException, IOException {
107+
return of(CertificateFactory.getInstance(X_509), path);
108+
}
109+
110+
/**
111+
* Creates an X509 trust from the given {@code in}. Equivalent to
112+
* {@code of(CertificateFactory.getInstance("X.509"), in, offset, length)}.
113+
*
114+
* @param in the input bytes
115+
* @param offset the input offset
116+
* @param length the input length
117+
* @return the trust
118+
* @throws IOException if an IO exception occurs
119+
* @throws CertificateException on parsing errors
120+
* @see CertificateFactory#getInstance(String)
121+
* @see #of(CertificateFactory, byte[], int, int)
122+
*/
123+
public static TrustCustom ofX509(byte[] in, int offset, int length) throws CertificateException, IOException {
124+
return of(CertificateFactory.getInstance(X_509), in, offset, length);
125+
}
126+
127+
// This is structurally more specific than what we could achieve with TrustList. There's potential in the future to
128+
// extend this with Function<KeyStore, CertPathTrustManagerParameters> if callers need more configurability.
129+
130+
public abstract List<Certificate> certificates();
131+
132+
@Override
133+
public final <T> T walk(Visitor<T> visitor) {
134+
return visitor.visit(this);
135+
}
136+
137+
public interface Builder {
138+
139+
Builder addCertificates(Certificate element);
140+
141+
Builder addCertificates(Certificate... elements);
142+
143+
Builder addAllCertificates(Iterable<? extends Certificate> elements);
144+
145+
TrustCustom build();
146+
}
147+
}

ssl/config/src/main/java/io/deephaven/ssl/config/TrustStore.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ public static TrustStore of(String path, String password) {
2424
public abstract String path();
2525

2626
/**
27-
* The trust storce password.
27+
* The trust store password.
2828
*/
2929
public abstract String password();
3030

ssl/config/src/test/java/io/deephaven/ssl/config/SSLConfigTest.java

+23
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,14 @@
77
import io.deephaven.ssl.config.SSLConfig.ClientAuth;
88
import org.junit.jupiter.api.Test;
99

10+
import javax.security.auth.x500.X500Principal;
11+
import java.io.BufferedInputStream;
1012
import java.io.IOException;
13+
import java.io.InputStream;
1114
import java.net.URL;
15+
import java.security.cert.CertificateException;
16+
import java.security.cert.X509Certificate;
17+
import java.util.Objects;
1218

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

96+
@Test
97+
void trustCustom() throws IOException, CertificateException {
98+
// See server/dev-certs/README.md for information about server.chain.crt.
99+
final TrustCustom custom;
100+
try (final InputStream in = new BufferedInputStream(
101+
Objects.requireNonNull(SSLConfigTest.class.getResourceAsStream("server.chain.crt")))) {
102+
custom = TrustCustom.ofX509(in);
103+
}
104+
assertThat(custom.certificates()).hasSize(2);
105+
assertThat(custom.certificates().get(0)).isInstanceOf(X509Certificate.class);
106+
assertThat(((X509Certificate) custom.certificates().get(0)).getSubjectX500Principal())
107+
.isEqualTo(new X500Principal("CN=localhost"));
108+
assertThat(custom.certificates().get(1)).isInstanceOf(X509Certificate.class);
109+
assertThat(((X509Certificate) custom.certificates().get(1)).getSubjectX500Principal())
110+
.isEqualTo(new X500Principal("CN=deephaven-localhost-testing-ca"));
111+
}
112+
90113
@Test
91114
void ciphersExplicit() throws IOException {
92115
check("ciphers-explicit.json",
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../../../../../../../../../server/dev-certs/server.chain.crt

ssl/kickstart/src/main/java/io/deephaven/ssl/config/impl/KickstartUtils.java

+7
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import io.deephaven.ssl.config.Trust;
2525
import io.deephaven.ssl.config.TrustAll;
2626
import io.deephaven.ssl.config.TrustCertificates;
27+
import io.deephaven.ssl.config.TrustCustom;
2728
import io.deephaven.ssl.config.TrustJdk;
2829
import io.deephaven.ssl.config.TrustList;
2930
import io.deephaven.ssl.config.TrustProperties;
@@ -90,6 +91,12 @@ public Void visit(TrustSystem system) {
9091
return null;
9192
}
9293

94+
@Override
95+
public Void visit(TrustCustom custom) {
96+
builder.withTrustMaterial(custom.certificates());
97+
return null;
98+
}
99+
93100
@Override
94101
public Void visit(TrustAll all) {
95102
builder.withTrustingAllCertificatesWithoutValidation();

0 commit comments

Comments
 (0)