Skip to content

Commit f50b3a6

Browse files
committed
Add isBouncyCastleTlsError to cover BC's TlsFatalAlert*
1 parent 0050ab1 commit f50b3a6

1 file changed

Lines changed: 83 additions & 2 deletions

File tree

driver-core/src/main/com/mongodb/internal/connection/BackpressureErrorLabeler.java

Lines changed: 83 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
import com.mongodb.MongoException;
2020
import com.mongodb.MongoSocketException;
21+
import com.mongodb.lang.Nullable;
2122

2223
import javax.net.ssl.SSLHandshakeException;
2324
import javax.net.ssl.SSLPeerUnverifiedException;
@@ -26,7 +27,15 @@
2627
import java.security.cert.CertPathBuilderException;
2728
import java.security.cert.CertPathValidatorException;
2829
import java.security.cert.CertificateException;
30+
import java.util.Arrays;
31+
import java.util.Collections;
32+
import java.util.HashSet;
33+
import java.util.List;
2934
import java.util.Locale;
35+
import java.util.Objects;
36+
import java.util.Set;
37+
import java.util.stream.Collectors;
38+
import java.util.stream.Stream;
3039

3140
/**
3241
* Attaches {@link MongoException#SYSTEM_OVERLOADED_ERROR_LABEL} and
@@ -38,6 +47,35 @@
3847
*/
3948
final class BackpressureErrorLabeler {
4049

50+
/**
51+
* BouncyCastle TLS fatal-alert exception types resolved at class-load time. If BC isn't on the
52+
* classpath the list is empty and {@link #isBouncyCastleTlsError(Throwable)} short-circuits to false.
53+
*/
54+
private static final List<Class<?>> BOUNCY_CASTLE_TLS_FATAL_TYPES = Stream.of(
55+
"org.bouncycastle.tls.TlsFatalAlert",
56+
"org.bouncycastle.tls.TlsFatalAlertReceived",
57+
"org.bouncycastle.tls.crypto.TlsCryptoException")
58+
.map(BackpressureErrorLabeler::loadClassOrNull)
59+
.filter(Objects::nonNull)
60+
.collect(Collectors.toList());
61+
62+
/**
63+
* RFC 5246 / RFC 8446 alert descriptions that surface in BouncyCastle TLS exception messages.
64+
* See <a href="https://github.com/bcgit/bc-java/blob/main/tls/src/main/java/org/bouncycastle/tls/AlertDescription.java">AlertDescription.java</a>.
65+
* See <a href="https://datatracker.ietf.org/doc/html/rfc5246#section-7.2">(TLS) Protocol Version 1.2 - Alert Protocol</a>.
66+
*/
67+
private static final Set<String> BOUNCY_CASTLE_TLS_ALERT_DESCRIPTIONS = Collections.unmodifiableSet(
68+
new HashSet<>(Arrays.asList(
69+
"close_notify", "unexpected_message", "bad_record_mac", "decryption_failed",
70+
"record_overflow", "decompression_failure", "handshake_failure", "no_certificate",
71+
"bad_certificate", "unsupported_certificate", "certificate_revoked", "certificate_expired",
72+
"certificate_unknown", "illegal_parameter", "unknown_ca", "access_denied",
73+
"decode_error", "decrypt_error", "export_restriction", "protocol_version",
74+
"insufficient_security", "internal_error", "no_renegotiation", "unsupported_extension",
75+
"certificate_unobtainable", "unrecognized_name", "bad_certificate_status_response",
76+
"bad_certificate_hash_value", "unknown_psk_identity", "no_application_protocol",
77+
"inappropriate_fallback", "missing_extension", "certificate_required")));
78+
4179
private BackpressureErrorLabeler() {
4280
}
4381

@@ -82,12 +120,55 @@ private static boolean isTlsConfigurationError(final MongoSocketException t) {
82120
}
83121
if (cause instanceof SSLHandshakeException) {
84122
String message = cause.getMessage();
85-
if (message != null && message.toLowerCase(Locale.ROOT).contains("received fatal alert")) {
86-
return true;
123+
if (message != null) {
124+
String lowerMessage = message.toLowerCase(Locale.ROOT);
125+
if (lowerMessage.contains("verify")
126+
|| lowerMessage.contains("protocol")
127+
|| lowerMessage.contains("cipher")
128+
|| lowerMessage.contains("received fatal alert")) {
129+
return true;
130+
}
87131
}
88132
}
133+
if (isBouncyCastleTlsError(cause)) {
134+
return true;
135+
}
136+
89137
cause = cause.getCause();
90138
}
91139
return false;
92140
}
141+
142+
private static boolean isBouncyCastleTlsError(final Throwable cause) {
143+
boolean isBcType = false;
144+
for (Class<?> bcType : BOUNCY_CASTLE_TLS_FATAL_TYPES) {
145+
if (bcType.isInstance(cause)) {
146+
isBcType = true;
147+
break;
148+
}
149+
}
150+
if (!isBcType) {
151+
return false;
152+
}
153+
String message = cause.getMessage();
154+
if (message == null) {
155+
return false;
156+
}
157+
String description = message.toLowerCase(Locale.ROOT);
158+
for (String alertName : BOUNCY_CASTLE_TLS_ALERT_DESCRIPTIONS) {
159+
if (description.contains(alertName)) {
160+
return true;
161+
}
162+
}
163+
return false;
164+
}
165+
166+
@Nullable
167+
private static Class<?> loadClassOrNull(final String fqn) {
168+
try {
169+
return Class.forName(fqn);
170+
} catch (ClassNotFoundException e) {
171+
return null;
172+
}
173+
}
93174
}

0 commit comments

Comments
 (0)