-
Notifications
You must be signed in to change notification settings - Fork 74
Expand file tree
/
Copy pathOneWaySSLTest.java
More file actions
198 lines (172 loc) · 8.47 KB
/
OneWaySSLTest.java
File metadata and controls
198 lines (172 loc) · 8.47 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
package com.marklogic.client.test.ssl;
import com.marklogic.client.DatabaseClient;
import com.marklogic.client.DatabaseClientFactory;
import com.marklogic.client.ForbiddenUserException;
import com.marklogic.client.MarkLogicIOException;
import com.marklogic.client.impl.SSLUtil;
import com.marklogic.client.test.Common;
import com.marklogic.client.test.MarkLogicVersion;
import com.marklogic.client.test.junit5.DisabledWhenUsingReverseProxyServer;
import com.marklogic.client.test.junit5.RequireSSLExtension;
import com.marklogic.client.test.junit5.RequiresML11OrLower;
import com.marklogic.client.test.junit5.RequiresML12;
import com.marklogic.mgmt.ManageClient;
import com.marklogic.mgmt.resource.appservers.ServerManager;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.DisabledOnJre;
import org.junit.jupiter.api.condition.JRE;
import org.junit.jupiter.api.extension.ExtendWith;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLException;
import javax.net.ssl.TrustManager;
import static org.junit.jupiter.api.Assertions.*;
/**
* Verifies scenarios for "one-way SSL" - i.e. the MarkLogic app server is configured with a certificate template to
* require an SSL connection, but the client only needs to trust the server - the client does not present its own
* certificate. See TwoWaySSLTest for scenarios where the client presents its own certificate which the server must
* trust.
*/
@ExtendWith({
DisabledWhenUsingReverseProxyServer.class,
RequireSSLExtension.class
})
class OneWaySSLTest {
private static ManageClient manageClient;
@BeforeAll
static void setup() {
manageClient = Common.newManageClient();
}
@AfterEach
void teardown() {
MarkLogicVersion markLogicVersion = Common.getMarkLogicVersion();
if (markLogicVersion.getMajor() >= 12) {
setAppServerMinimumTLSVersion("TLSv1.2");
}
}
private static void setAppServerMinimumTLSVersion(String minTLSVersion) {
new ServerManager(manageClient).save(
Common.newServerPayload().put("ssl-min-allow-tls", minTLSVersion).toString()
);
}
/**
* Simple check for ensuring that an SSL connection can be made when the app server requires SSL to be used. This
* uses a naive test-only "trust all" approach for trusting certificates. That is fine for this test, as the intent
* is simply to ensure that some kind of SSL connection can be made. In production, a user would be expected to
* use a real TrustManager.
*
* @throws Exception - if an error occurs with building the SSLContext object.
*/
@Test
void trustAllManager() throws Exception {
SSLContext sslContext = SSLContext.getInstance(SSLUtil.DEFAULT_PROTOCOL);
sslContext.init(null, new TrustManager[]{Common.TRUST_ALL_MANAGER}, null);
DatabaseClient client = Common.newClientBuilder()
.withSSLContext(sslContext)
.withTrustManager(Common.TRUST_ALL_MANAGER)
.withSSLHostnameVerifier(DatabaseClientFactory.SSLHostnameVerifier.ANY)
.build();
DatabaseClient.ConnectionResult result = client.checkConnection();
assertEquals(0, result.getStatusCode(), "A value of zero implies that a connection was successfully made, " +
"which should happen since a 'trust all' manager is being used");
assertNull(result.getErrorMessage());
}
/**
* Demonstrates using a custom X509TrustManager that only accepts the issuer of the public certificate associated
* with the certificate template created via RequireSSLExtension.
*/
@Test
void trustManagerThatOnlyTrustsTheCertificateFromTheCertificateTemplate() {
DatabaseClient client = Common.newClientBuilder()
.withSSLProtocol(SSLUtil.DEFAULT_PROTOCOL)
.withTrustManager(RequireSSLExtension.newSecureTrustManager())
.withSSLHostnameVerifier(DatabaseClientFactory.SSLHostnameVerifier.ANY)
.build();
DatabaseClient.ConnectionResult result = client.checkConnection();
assertEquals(0, result.getStatusCode());
assertNull(result.getErrorMessage());
}
@Test
void defaultSslContext() throws Exception {
DatabaseClient client = Common.newClientBuilder()
.withSSLContext(SSLContext.getDefault())
.withTrustManager(Common.TRUST_ALL_MANAGER)
.withSSLHostnameVerifier(DatabaseClientFactory.SSLHostnameVerifier.ANY)
.build();
MarkLogicIOException ex = assertThrows(MarkLogicIOException.class, () -> client.checkConnection(),
"The connection should fail because the JVM's default SSL Context does not have a CA certificate that " +
"corresponds to the test-only certificate that the app server is using for this test");
assertTrue(ex.getCause() instanceof SSLException, "Unexpected cause: " + ex.getCause());
}
@ExtendWith(RequiresML11OrLower.class)
@Test
void noSslContext() {
DatabaseClient client = Common.newClientBuilder().build();
DatabaseClient.ConnectionResult result = client.checkConnection();
assertEquals("Forbidden", result.getErrorMessage(), "MarkLogic is expected to return a 403 Forbidden when the " +
"user tries to access an HTTPS app server using HTTP. This behavior changes in MarkLogic 12, and it may " +
"be considered a bit surprising with MarkLogic 11 and earlier - that is, the user probably shouldn't get " +
"any response back since a connection cannot be made without using SSL.");
assertEquals(403, result.getStatusCode());
ForbiddenUserException ex = assertThrows(ForbiddenUserException.class,
() -> client.newServerEval().javascript("fn.currentDate()").evalAs(String.class));
assertEquals(
"Local message: User is not allowed to apply resource at eval. Server Message: You have attempted to access an HTTPS server using HTTP.",
ex.getMessage(),
"The user should get a clear message on why the connection failed as opposed to the previous error " +
"message of 'Server (not a REST instance?)'."
);
}
@ExtendWith(RequiresML12.class)
@Test
void noSslContextWithMarkLogic12() {
DatabaseClient client = Common.newClientBuilder().build();
MarkLogicIOException ex = assertThrows(MarkLogicIOException.class, () -> client.checkConnection());
assertTrue(ex.getMessage().contains("unexpected end of stream"), "Per MLE-17505, a change in the openssl " +
"library used by the server results in an IO exception when the client tries to connect to an " +
"app server that requires SSL, but the client does not use SSL. Actual message: " + ex.getMessage());
}
@Test
void tLS13ClientWithTLS12Server() {
DatabaseClient client = buildTrustAllClientWithSSLProtocol(SSLUtil.DEFAULT_PROTOCOL);
DatabaseClient.ConnectionResult result = client.checkConnection();
assertEquals(0, result.getStatusCode(), "A value of zero implies that a connection was successfully made, " +
"which should happen since a 'trust all' manager is being used");
assertNull(result.getErrorMessage());
}
@ExtendWith(RequiresML12.class)
// The TLSv1.3 tests are failing on Java 8, because TLSv1.3 is disabled with our version of Java 8.
// There may be a way to configure Java 8 to use TLSv1.3, but it is not currently working.
@DisabledOnJre(JRE.JAVA_8)
@Test
void tLS13ClientWithTLS13Server() {
setAppServerMinimumTLSVersion("TLSv1.3");
DatabaseClient client = buildTrustAllClientWithSSLProtocol("TLSv1.3");
DatabaseClient.ConnectionResult result = client.checkConnection();
assertEquals(0, result.getStatusCode(), "A value of zero implies that a connection was successfully made, " +
"which should happen since a 'trust all' manager is being used");
assertNull(result.getErrorMessage());
}
@ExtendWith(RequiresML12.class)
@DisabledOnJre(JRE.JAVA_8)
@Test
void tLS12ClientWithTLS13ServerShouldFail() {
setAppServerMinimumTLSVersion("TLSv1.3");
DatabaseClient client = buildTrustAllClientWithSSLProtocol("TLSv1.2");
MarkLogicIOException ex = Assertions.assertThrows(MarkLogicIOException.class, () -> client.checkConnection());
String expected = "Error occurred while calling https://localhost:8012/v1/ping; " +
"javax.net.ssl.SSLHandshakeException: Received fatal alert: protocol_version ; possible reasons for the " +
"error include that a MarkLogic app server may not be listening on the port, or MarkLogic was stopped or " +
"restarted during the request; check the MarkLogic server logs for more information.";
assertEquals(expected, ex.getMessage());
}
DatabaseClient buildTrustAllClientWithSSLProtocol(String sslProtocol) {
return Common.newClientBuilder()
.withSSLProtocol(sslProtocol)
.withTrustManager(Common.TRUST_ALL_MANAGER)
.withSSLHostnameVerifier(DatabaseClientFactory.SSLHostnameVerifier.ANY)
.build();
}
}