|
12 | 12 | import java.lang.reflect.Method; |
13 | 13 | import java.security.cert.CertificateException; |
14 | 14 | import java.security.cert.X509Certificate; |
| 15 | +import java.util.ArrayList; |
| 16 | +import java.util.Arrays; |
15 | 17 | import java.util.Collection; |
16 | 18 | import java.util.List; |
17 | 19 | import static org.junit.jupiter.api.Assertions.assertThrows; |
@@ -271,4 +273,116 @@ public void testSecureCNParsing_preventsHostnameSpoofing() throws Exception { |
271 | 273 | assertDoesNotThrow( |
272 | 274 | () -> SQLServerCertificateUtils.validateServerNameInCertificate(spoofedCert, "attacker.com")); |
273 | 275 | } |
| 276 | + |
| 277 | + /** |
| 278 | + * Helper to create a SAN entry list. |
| 279 | + * Per X509Certificate.getSubjectAlternativeNames() spec: |
| 280 | + * - First element is an Integer representing the type (2 = dNSName, 7 = iPAddress) |
| 281 | + * - Second element is a String for dNSName and iPAddress types |
| 282 | + */ |
| 283 | + private static List<?> sanEntry(int type, String value) { |
| 284 | + return Arrays.asList(Integer.valueOf(type), value); |
| 285 | + } |
| 286 | + |
| 287 | + @Test |
| 288 | + public void testIPAddressInSAN_IPv4Match() throws Exception { |
| 289 | + // Certificate with IPv4 address in SAN (type 7) |
| 290 | + X500Principal subject = new X500Principal("CN=sqlserver.example.com"); |
| 291 | + Collection<List<?>> sans = new ArrayList<>(); |
| 292 | + sans.add(sanEntry(7, "192.168.1.100")); |
| 293 | + |
| 294 | + X509Certificate cert = mockCert(subject, sans); |
| 295 | + |
| 296 | + // Should succeed when connecting via the IP address in SAN |
| 297 | + assertDoesNotThrow( |
| 298 | + () -> SQLServerCertificateUtils.validateServerNameInCertificate(cert, "192.168.1.100")); |
| 299 | + } |
| 300 | + |
| 301 | + @Test |
| 302 | + public void testIPAddressInSAN_IPv4Mismatch() throws Exception { |
| 303 | + // Certificate with IPv4 address in SAN (type 7) |
| 304 | + X500Principal subject = new X500Principal("CN=sqlserver.example.com"); |
| 305 | + Collection<List<?>> sans = new ArrayList<>(); |
| 306 | + sans.add(sanEntry(7, "192.168.1.100")); |
| 307 | + |
| 308 | + X509Certificate cert = mockCert(subject, sans); |
| 309 | + |
| 310 | + // Should fail when connecting via a different IP address |
| 311 | + assertThrows(CertificateException.class, |
| 312 | + () -> SQLServerCertificateUtils.validateServerNameInCertificate(cert, "192.168.1.101")); |
| 313 | + } |
| 314 | + |
| 315 | + @Test |
| 316 | + public void testIPAddressInSAN_IPv6Match() throws Exception { |
| 317 | + // Certificate with IPv6 address in SAN (type 7) |
| 318 | + X500Principal subject = new X500Principal("CN=sqlserver.example.com"); |
| 319 | + Collection<List<?>> sans = new ArrayList<>(); |
| 320 | + sans.add(sanEntry(7, "2001:db8::1")); |
| 321 | + |
| 322 | + X509Certificate cert = mockCert(subject, sans); |
| 323 | + |
| 324 | + // Should succeed when connecting via the IPv6 address in SAN |
| 325 | + assertDoesNotThrow( |
| 326 | + () -> SQLServerCertificateUtils.validateServerNameInCertificate(cert, "2001:db8::1")); |
| 327 | + } |
| 328 | + |
| 329 | + @Test |
| 330 | + public void testIPAddressInSAN_MultipleEntries() throws Exception { |
| 331 | + // Certificate with both DNS name and IP address in SAN |
| 332 | + X500Principal subject = new X500Principal("CN=sqlserver.example.com"); |
| 333 | + Collection<List<?>> sans = new ArrayList<>(); |
| 334 | + sans.add(sanEntry(2, "sqlserver.example.com")); |
| 335 | + sans.add(sanEntry(7, "10.0.0.50")); |
| 336 | + sans.add(sanEntry(7, "192.168.1.100")); |
| 337 | + |
| 338 | + X509Certificate cert = mockCert(subject, sans); |
| 339 | + |
| 340 | + // Should succeed for DNS name |
| 341 | + assertDoesNotThrow( |
| 342 | + () -> SQLServerCertificateUtils.validateServerNameInCertificate(cert, "sqlserver.example.com")); |
| 343 | + |
| 344 | + // Should succeed for first IP |
| 345 | + assertDoesNotThrow( |
| 346 | + () -> SQLServerCertificateUtils.validateServerNameInCertificate(cert, "10.0.0.50")); |
| 347 | + |
| 348 | + // Should succeed for second IP |
| 349 | + assertDoesNotThrow( |
| 350 | + () -> SQLServerCertificateUtils.validateServerNameInCertificate(cert, "192.168.1.100")); |
| 351 | + |
| 352 | + // Should fail for unlisted IP |
| 353 | + assertThrows(CertificateException.class, |
| 354 | + () -> SQLServerCertificateUtils.validateServerNameInCertificate(cert, "172.16.0.1")); |
| 355 | + } |
| 356 | + |
| 357 | + @Test |
| 358 | + public void testIPAddressInSAN_CaseInsensitive() throws Exception { |
| 359 | + // Certificate with IPv6 address in SAN - test case insensitivity |
| 360 | + X500Principal subject = new X500Principal("CN=sqlserver.example.com"); |
| 361 | + Collection<List<?>> sans = new ArrayList<>(); |
| 362 | + sans.add(sanEntry(7, "2001:DB8::1")); |
| 363 | + |
| 364 | + X509Certificate cert = mockCert(subject, sans); |
| 365 | + |
| 366 | + // Should succeed with different case (IPv6 addresses are case-insensitive) |
| 367 | + assertDoesNotThrow( |
| 368 | + () -> SQLServerCertificateUtils.validateServerNameInCertificate(cert, "2001:db8::1")); |
| 369 | + } |
| 370 | + |
| 371 | + @Test |
| 372 | + public void testIPAddressInSAN_FallbackToCN() throws Exception { |
| 373 | + // Certificate with only DNS SAN, no IP - should still work via CN fallback |
| 374 | + X500Principal subject = new X500Principal("CN=sqlserver.example.com"); |
| 375 | + Collection<List<?>> sans = new ArrayList<>(); |
| 376 | + sans.add(sanEntry(2, "sqlserver.example.com")); |
| 377 | + |
| 378 | + X509Certificate cert = mockCert(subject, sans); |
| 379 | + |
| 380 | + // Should succeed for hostname matching CN/SAN |
| 381 | + assertDoesNotThrow( |
| 382 | + () -> SQLServerCertificateUtils.validateServerNameInCertificate(cert, "sqlserver.example.com")); |
| 383 | + |
| 384 | + // Should fail for IP when no IP SAN is present |
| 385 | + assertThrows(CertificateException.class, |
| 386 | + () -> SQLServerCertificateUtils.validateServerNameInCertificate(cert, "192.168.1.100")); |
| 387 | + } |
274 | 388 | } |
0 commit comments