Skip to content

Commit 35ce4cc

Browse files
authored
feat: ssl support (#944)
1 parent a87e484 commit 35ce4cc

File tree

6 files changed

+117
-39
lines changed

6 files changed

+117
-39
lines changed

sentry-spring-boot-starter/src/test/kotlin/io/sentry/spring/boot/SentryAutoConfigurationTest.kt

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,6 @@ class SentryAutoConfigurationTest {
7070
"sentry.read-timeout-millis=10",
7171
"sentry.shutdown-timeout=20",
7272
"sentry.flush-timeout-millis=30",
73-
"sentry.bypass-security=true",
7473
"sentry.debug=true",
7574
"sentry.diagnostic-level=INFO",
7675
"sentry.sentry-client-name=my-client",
@@ -89,7 +88,6 @@ class SentryAutoConfigurationTest {
8988
assertThat(options.readTimeoutMillis).isEqualTo(10)
9089
assertThat(options.shutdownTimeout).isEqualTo(20)
9190
assertThat(options.flushTimeoutMillis).isEqualTo(30)
92-
assertThat(options.isBypassSecurity).isTrue()
9391
assertThat(options.isDebug).isTrue()
9492
assertThat(options.diagnosticLevel).isEqualTo(SentryLevel.INFO)
9593
assertThat(options.maxBreadcrumbs).isEqualTo(100)

sentry/src/main/java/io/sentry/HttpTransportFactory.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,8 @@ static ITransport create(@NotNull SentryOptions options) {
2828
credentials,
2929
options.getConnectionTimeoutMillis(),
3030
options.getReadTimeoutMillis(),
31-
options.isBypassSecurity(),
31+
options.getSslSocketFactory(),
32+
options.getHostnameVerifier(),
3233
sentryUrl);
3334
}
3435
}

sentry/src/main/java/io/sentry/SentryOptions.java

Lines changed: 40 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
import java.util.ArrayList;
1414
import java.util.List;
1515
import java.util.concurrent.CopyOnWriteArrayList;
16+
import javax.net.ssl.HostnameVerifier;
17+
import javax.net.ssl.SSLSocketFactory;
1618
import org.jetbrains.annotations.ApiStatus;
1719
import org.jetbrains.annotations.NotNull;
1820
import org.jetbrains.annotations.Nullable;
@@ -194,9 +196,6 @@ public class SentryOptions {
194196
/** read timeout in milliseconds */
195197
private int readTimeoutMillis = 5000;
196198

197-
/** whether to ignore TLS errors */
198-
private boolean bypassSecurity = false;
199-
200199
/** Reads and caches envelope files in the disk */
201200
private @NotNull IEnvelopeCache envelopeDiskCache = NoOpEnvelopeCache.getInstance();
202201

@@ -206,6 +205,12 @@ public class SentryOptions {
206205
/** whether to send personal identifiable information along with events */
207206
private boolean sendDefaultPii = false;
208207

208+
/** HostnameVerifier for self-signed certificate trust* */
209+
private @Nullable HostnameVerifier hostnameVerifier;
210+
211+
/** SSLSocketFactory for self-signed certificate trust * */
212+
private @Nullable SSLSocketFactory sslSocketFactory;
213+
209214
/** list of scope observers */
210215
private final @NotNull List<IScopeObserver> observers = new ArrayList<>();
211216

@@ -874,24 +879,6 @@ public void setReadTimeoutMillis(int readTimeoutMillis) {
874879
this.readTimeoutMillis = readTimeoutMillis;
875880
}
876881

877-
/**
878-
* Returns whether to ignore TLS errors
879-
*
880-
* @return the bypassSecurity
881-
*/
882-
public boolean isBypassSecurity() {
883-
return bypassSecurity;
884-
}
885-
886-
/**
887-
* Sets whether to ignore TLS errors
888-
*
889-
* @param bypassSecurity the bypassSecurity
890-
*/
891-
public void setBypassSecurity(boolean bypassSecurity) {
892-
this.bypassSecurity = bypassSecurity;
893-
}
894-
895882
/**
896883
* Returns the EnvelopeCache interface
897884
*
@@ -940,6 +927,38 @@ public void setMaxQueueSize(int maxQueueSize) {
940927
return sdkVersion;
941928
}
942929

930+
/**
931+
* Returns SSLSocketFactory
932+
*
933+
* @return SSLSocketFactory object or null
934+
*/
935+
public @Nullable SSLSocketFactory getSslSocketFactory() {
936+
return sslSocketFactory;
937+
}
938+
939+
/**
940+
* Set custom SSLSocketFactory that is trusted to self-signed certificates
941+
*
942+
* @param sslSocketFactory SSLSocketFactory object
943+
*/
944+
public void setSslSocketFactory(final @Nullable SSLSocketFactory sslSocketFactory) {
945+
this.sslSocketFactory = sslSocketFactory;
946+
}
947+
948+
/**
949+
* Returns HostnameVerifier
950+
*
951+
* @return HostnameVerifier objecr or null
952+
*/
953+
public @Nullable HostnameVerifier getHostnameVerifier() {
954+
return hostnameVerifier;
955+
}
956+
957+
/** Set HostnameVerifier */
958+
public void setHostnameVerifier(final @Nullable HostnameVerifier hostnameVerifier) {
959+
this.hostnameVerifier = hostnameVerifier;
960+
}
961+
943962
/**
944963
* Sets the SdkVersion object
945964
*

sentry/src/main/java/io/sentry/transport/HttpTransport.java

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,9 @@
3131
import java.util.Map;
3232
import java.util.concurrent.ConcurrentHashMap;
3333
import java.util.zip.GZIPOutputStream;
34+
import javax.net.ssl.HostnameVerifier;
3435
import javax.net.ssl.HttpsURLConnection;
36+
import javax.net.ssl.SSLSocketFactory;
3537
import org.jetbrains.annotations.ApiStatus;
3638
import org.jetbrains.annotations.NotNull;
3739
import org.jetbrains.annotations.Nullable;
@@ -74,8 +76,10 @@ public String getCategory() {
7476
private final @NotNull ISerializer serializer;
7577
private final int connectionTimeout;
7678
private final int readTimeout;
77-
private final boolean bypassSecurity;
7879
private final @NotNull URL envelopeUrl;
80+
private final @Nullable SSLSocketFactory sslSocketFactory;
81+
private final @Nullable HostnameVerifier hostnameVerifier;
82+
7983
private final @NotNull SentryOptions options;
8084

8185
private final @NotNull Map<DataCategory, Date> sentryRetryAfterLimit = new ConcurrentHashMap<>();
@@ -96,22 +100,25 @@ public String getCategory() {
96100
* is sent
97101
* @param connectionTimeoutMillis connection timeout in milliseconds
98102
* @param readTimeoutMillis read timeout in milliseconds
99-
* @param bypassSecurity whether to ignore TLS errors
103+
* @param sslSocketFactory custom sslSocketFactory for self-signed certificate trust
104+
* @param hostnameVerifier custom hostnameVerifier for self-signed certificate trust
100105
* @param sentryUrl sentryUrl which is the parsed DSN
101106
*/
102107
public HttpTransport(
103108
final @NotNull SentryOptions options,
104109
final @NotNull IConnectionConfigurator connectionConfigurator,
105110
final int connectionTimeoutMillis,
106111
final int readTimeoutMillis,
107-
final boolean bypassSecurity,
112+
final @Nullable SSLSocketFactory sslSocketFactory,
113+
final @Nullable HostnameVerifier hostnameVerifier,
108114
final @NotNull URL sentryUrl) {
109115
this(
110116
options,
111117
connectionConfigurator,
112118
connectionTimeoutMillis,
113119
readTimeoutMillis,
114-
bypassSecurity,
120+
sslSocketFactory,
121+
hostnameVerifier,
115122
sentryUrl,
116123
CurrentDateProvider.getInstance());
117124
}
@@ -121,7 +128,8 @@ public HttpTransport(
121128
final @NotNull IConnectionConfigurator connectionConfigurator,
122129
final int connectionTimeoutMillis,
123130
final int readTimeoutMillis,
124-
final boolean bypassSecurity,
131+
final @Nullable SSLSocketFactory sslSocketFactory,
132+
final @Nullable HostnameVerifier hostnameVerifier,
125133
final @NotNull URL sentryUrl,
126134
final @NotNull ICurrentDateProvider currentDateProvider) {
127135
this.proxy = options.getProxy();
@@ -130,7 +138,8 @@ public HttpTransport(
130138
this.connectionTimeout = connectionTimeoutMillis;
131139
this.readTimeout = readTimeoutMillis;
132140
this.options = options;
133-
this.bypassSecurity = bypassSecurity;
141+
this.sslSocketFactory = sslSocketFactory;
142+
this.hostnameVerifier = hostnameVerifier;
134143
this.currentDateProvider =
135144
Objects.requireNonNull(currentDateProvider, "CurrentDateProvider is required.");
136145
this.logger = Objects.requireNonNull(options.getLogger(), "Logger is required.");
@@ -227,8 +236,11 @@ public boolean isRetryAfter(final @NotNull String itemType) {
227236
connection.setConnectTimeout(connectionTimeout);
228237
connection.setReadTimeout(readTimeout);
229238

230-
if (bypassSecurity && connection instanceof HttpsURLConnection) {
231-
((HttpsURLConnection) connection).setHostnameVerifier((__, ___) -> true);
239+
if (connection instanceof HttpsURLConnection && hostnameVerifier != null) {
240+
((HttpsURLConnection) connection).setHostnameVerifier(hostnameVerifier);
241+
}
242+
if (connection instanceof HttpsURLConnection && sslSocketFactory != null) {
243+
((HttpsURLConnection) connection).setSSLSocketFactory(sslSocketFactory);
232244
}
233245

234246
connection.connect();

sentry/src/test/java/io/sentry/SentryClientTest.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -387,7 +387,7 @@ class SentryClientTest {
387387
val sentryOptions: SentryOptions = SentryOptions().apply {
388388
dsn = dsnString
389389
}
390-
val transport = HttpTransport(sentryOptions, mock(), 500, 500, false, URL("https://[email protected]/proj"))
390+
val transport = HttpTransport(sentryOptions, mock(), 500, 500, null, null, URL("https://[email protected]/proj"))
391391
sentryOptions.setTransport(transport)
392392

393393
val connection = mock<AsyncConnection>()

sentry/src/test/java/io/sentry/transport/HttpTransportTest.kt

Lines changed: 54 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package io.sentry.transport
33
import com.nhaarman.mockitokotlin2.any
44
import com.nhaarman.mockitokotlin2.eq
55
import com.nhaarman.mockitokotlin2.mock
6+
import com.nhaarman.mockitokotlin2.never
67
import com.nhaarman.mockitokotlin2.verify
78
import com.nhaarman.mockitokotlin2.whenever
89
import io.sentry.ISerializer
@@ -12,10 +13,12 @@ import io.sentry.SentryOptions
1213
import io.sentry.Session
1314
import io.sentry.protocol.User
1415
import java.io.IOException
15-
import java.net.HttpURLConnection
1616
import java.net.Proxy
1717
import java.net.URI
1818
import java.net.URL
19+
import javax.net.ssl.HostnameVerifier
20+
import javax.net.ssl.HttpsURLConnection
21+
import javax.net.ssl.SSLSocketFactory
1922
import kotlin.test.Test
2023
import kotlin.test.assertEquals
2124
import kotlin.test.assertFalse
@@ -24,28 +27,33 @@ import kotlin.test.assertTrue
2427
class HttpTransportTest {
2528

2629
private class Fixture {
27-
val dsn: URL = URI.create("http://key@localhost/proj").toURL()
30+
val dsn: URL = URI.create("https://key@localhost/proj").toURL()
2831
val serializer = mock<ISerializer>()
2932
var proxy: Proxy? = null
3033
var requestUpdater = IConnectionConfigurator {}
3134
var connectionTimeout = 1000
3235
var readTimeout = 500
33-
var bypassSecurity = false
34-
val connection = mock<HttpURLConnection>()
36+
val connection = mock<HttpsURLConnection>()
3537
val currentDateProvider = mock<ICurrentDateProvider>()
38+
var sslSocketFactory: SSLSocketFactory? = null
39+
var hostnameVerifier: HostnameVerifier? = null
3640

3741
init {
3842
whenever(connection.outputStream).thenReturn(mock())
3943
whenever(connection.inputStream).thenReturn(mock())
44+
whenever(connection.setHostnameVerifier(any())).thenCallRealMethod()
45+
whenever(connection.setSSLSocketFactory(any())).thenCallRealMethod()
4046
}
4147

4248
fun getSUT(): HttpTransport {
4349
val options = SentryOptions()
4450
options.setSerializer(serializer)
4551
options.proxy = proxy
52+
options.sslSocketFactory = sslSocketFactory
53+
options.hostnameVerifier = hostnameVerifier
4654

47-
return object : HttpTransport(options, requestUpdater, connectionTimeout, readTimeout, bypassSecurity, dsn, currentDateProvider) {
48-
override fun open(): HttpURLConnection {
55+
return object : HttpTransport(options, requestUpdater, connectionTimeout, readTimeout, sslSocketFactory, hostnameVerifier, dsn, currentDateProvider) {
56+
override fun open(): HttpsURLConnection {
4957
return connection
5058
}
5159
}
@@ -265,6 +273,46 @@ class HttpTransportTest {
265273
assertTrue(transport.isRetryAfter("event"))
266274
}
267275

276+
@Test
277+
fun `When SSLSocketFactory is given, set to connection`() {
278+
val factory = mock<SSLSocketFactory>()
279+
fixture.sslSocketFactory = factory
280+
val transport = fixture.getSUT()
281+
282+
transport.send(createEnvelope())
283+
284+
verify(fixture.connection).sslSocketFactory = eq(factory)
285+
}
286+
287+
@Test
288+
fun `When SSLSocketFactory is not given, do not set to connection`() {
289+
val transport = fixture.getSUT()
290+
291+
transport.send(createEnvelope())
292+
293+
verify(fixture.connection, never()).sslSocketFactory = any()
294+
}
295+
296+
@Test
297+
fun `When HostnameVerifier is given, set to connection`() {
298+
val hostname = mock<HostnameVerifier>()
299+
fixture.hostnameVerifier = hostname
300+
val transport = fixture.getSUT()
301+
302+
transport.send(createEnvelope())
303+
304+
verify(fixture.connection).hostnameVerifier = eq(hostname)
305+
}
306+
307+
@Test
308+
fun `When HostnameVerifier is not given, do not set to connection`() {
309+
val transport = fixture.getSUT()
310+
311+
transport.send(createEnvelope())
312+
313+
verify(fixture.connection, never()).hostnameVerifier = any()
314+
}
315+
268316
private fun createSession(): Session {
269317
return Session("123", User(), "env", "release")
270318
}

0 commit comments

Comments
 (0)