|
27 | 27 | import java.nio.channels.Channels; |
28 | 28 | import java.nio.charset.StandardCharsets; |
29 | 29 | import java.nio.file.Files; |
| 30 | +import java.nio.file.Path; |
30 | 31 | import java.nio.file.Paths; |
31 | 32 | import java.security.GeneralSecurityException; |
32 | 33 | import java.security.KeyManagementException; |
33 | 34 | import java.security.KeyStore; |
| 35 | +import java.security.KeyStoreException; |
34 | 36 | import java.security.NoSuchAlgorithmException; |
35 | | -import java.security.cert.Certificate; |
| 37 | +import java.security.SecureRandom; |
36 | 38 | import java.security.cert.CertificateException; |
37 | 39 | import java.security.cert.CertificateFactory; |
38 | 40 | import java.security.cert.X509Certificate; |
39 | 41 | import java.time.ZonedDateTime; |
40 | 42 | import java.util.AbstractMap; |
41 | 43 | import java.util.ArrayList; |
42 | 44 | import java.util.Arrays; |
43 | | -import java.util.Collection; |
44 | 45 | import java.util.Collections; |
45 | 46 | import java.util.HashMap; |
46 | 47 | import java.util.HashSet; |
|
54 | 55 | import java.util.concurrent.TimeUnit; |
55 | 56 | import java.util.regex.Matcher; |
56 | 57 | import java.util.stream.Collectors; |
| 58 | +import java.util.stream.Stream; |
57 | 59 | import javax.annotation.Nonnull; |
58 | 60 | import javax.net.ssl.HostnameVerifier; |
59 | | -import javax.net.ssl.KeyManager; |
60 | 61 | import javax.net.ssl.KeyManagerFactory; |
61 | 62 | import javax.net.ssl.SSLContext; |
62 | 63 | import javax.net.ssl.SSLSession; |
@@ -354,6 +355,137 @@ public static MediaType mediaType(String value) { |
354 | 355 | return mediaType; |
355 | 356 | } |
356 | 357 |
|
| 358 | + private static X509TrustManager createCompositeTrustManager( |
| 359 | + List<X509TrustManager> trustManagers) { |
| 360 | + return new X509TrustManager() { |
| 361 | + @Override |
| 362 | + public void checkClientTrusted(X509Certificate[] chain, String authType) |
| 363 | + throws CertificateException { |
| 364 | + for (X509TrustManager tm : trustManagers) { |
| 365 | + try { |
| 366 | + tm.checkClientTrusted(chain, authType); |
| 367 | + return; |
| 368 | + } catch (CertificateException ignored) { |
| 369 | + } |
| 370 | + } |
| 371 | + throw new CertificateException( |
| 372 | + "None of the TrustManagers trust this client certificate chain"); |
| 373 | + } |
| 374 | + |
| 375 | + @Override |
| 376 | + public void checkServerTrusted(X509Certificate[] chain, String authType) |
| 377 | + throws CertificateException { |
| 378 | + for (X509TrustManager tm : trustManagers) { |
| 379 | + try { |
| 380 | + tm.checkServerTrusted(chain, authType); |
| 381 | + return; |
| 382 | + } catch (CertificateException ignored) { |
| 383 | + } |
| 384 | + } |
| 385 | + throw new CertificateException( |
| 386 | + "None of the TrustManagers trust this server certificate chain"); |
| 387 | + } |
| 388 | + |
| 389 | + @Override |
| 390 | + public X509Certificate[] getAcceptedIssuers() { |
| 391 | + return trustManagers.stream() |
| 392 | + .flatMap(tm -> Arrays.stream(tm.getAcceptedIssuers())) |
| 393 | + .toArray(X509Certificate[]::new); |
| 394 | + } |
| 395 | + }; |
| 396 | + } |
| 397 | + |
| 398 | + private static X509TrustManager buildTrustManagerFromKeyStore(KeyStore ks) |
| 399 | + throws KeyStoreException, NoSuchAlgorithmException { |
| 400 | + TrustManagerFactory factory = |
| 401 | + TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); |
| 402 | + factory.init(ks); |
| 403 | + for (TrustManager tm : factory.getTrustManagers()) { |
| 404 | + if (tm instanceof X509TrustManager) { |
| 405 | + return (X509TrustManager) tm; |
| 406 | + } |
| 407 | + } |
| 408 | + return null; |
| 409 | + } |
| 410 | + |
| 411 | + private static int setCertificateEntry( |
| 412 | + CertificateFactory cf, KeyStore ks, Path file, String namePrefix) |
| 413 | + throws CertificateException, IOException, KeyStoreException { |
| 414 | + try (InputStream in = Files.newInputStream(file)) { |
| 415 | + int index = 0; |
| 416 | + while (in.available() > 0) { |
| 417 | + X509Certificate cert = (X509Certificate) cf.generateCertificate(in); |
| 418 | + ks.setCertificateEntry(namePrefix + (index++), cert); |
| 419 | + } |
| 420 | + // } catch (Exception e) { |
| 421 | + // System.err.println( |
| 422 | + // "Skipping invalid TLS certificate from file " + filePath + ";" + e.getMessage()); |
| 423 | + // return null; |
| 424 | + return index; |
| 425 | + } |
| 426 | + } |
| 427 | + |
| 428 | + private static X509TrustManager getTrustManagerFromFile(String filePath) |
| 429 | + throws CertificateException, IOException, KeyStoreException, NoSuchAlgorithmException { |
| 430 | + CertificateFactory cf = CertificateFactory.getInstance("X.509"); |
| 431 | + KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType()); |
| 432 | + ks.load(null); |
| 433 | + if (setCertificateEntry(cf, ks, Paths.get(filePath), "cert-file-") == 0) return null; |
| 434 | + return buildTrustManagerFromKeyStore(ks); |
| 435 | + } |
| 436 | + |
| 437 | + private static X509TrustManager getTrustManagerFromDir(String dirPath) |
| 438 | + throws CertificateException, IOException, KeyStoreException, NoSuchAlgorithmException { |
| 439 | + CertificateFactory cf = CertificateFactory.getInstance("X.509"); |
| 440 | + KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType()); |
| 441 | + ks.load(null); |
| 442 | + |
| 443 | + int index = 0; |
| 444 | + try (Stream<Path> paths = Files.walk(Paths.get(dirPath))) { |
| 445 | + int number = 0; |
| 446 | + for (Path file : (Iterable<Path>) paths.filter(Files::isRegularFile)::iterator) { |
| 447 | + index += setCertificateEntry(cf, ks, file, "cert-dir-file-" + (number++) + "-"); |
| 448 | + } |
| 449 | + } |
| 450 | + |
| 451 | + if (index == 0) return null; |
| 452 | + |
| 453 | + return buildTrustManagerFromKeyStore(ks); |
| 454 | + } |
| 455 | + |
| 456 | + private static X509TrustManager getDefaultTrustManager() |
| 457 | + throws CertificateException, IOException, KeyStoreException, NoSuchAlgorithmException { |
| 458 | + TrustManagerFactory factory = |
| 459 | + TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); |
| 460 | + factory.init((KeyStore) null); |
| 461 | + for (TrustManager tm : factory.getTrustManagers()) { |
| 462 | + if (tm instanceof X509TrustManager) return (X509TrustManager) tm; |
| 463 | + } |
| 464 | + return null; |
| 465 | + } |
| 466 | + |
| 467 | + private static X509TrustManager getCompositeTrustManager(String filePath, String dirPath) |
| 468 | + throws CertificateException, IOException, KeyStoreException, NoSuchAlgorithmException { |
| 469 | + List<X509TrustManager> trustManagers = new ArrayList<>(); |
| 470 | + |
| 471 | + X509TrustManager defaultTm = getDefaultTrustManager(); |
| 472 | + if (defaultTm != null) trustManagers.add(defaultTm); |
| 473 | + |
| 474 | + if (dirPath != null && !dirPath.isEmpty()) { |
| 475 | + X509TrustManager dirTm = getTrustManagerFromDir(dirPath); |
| 476 | + if (dirTm != null) trustManagers.add(dirTm); |
| 477 | + } |
| 478 | + |
| 479 | + if (filePath != null && !filePath.isEmpty()) { |
| 480 | + X509TrustManager fileTm = getTrustManagerFromFile(filePath); |
| 481 | + if (fileTm != null) trustManagers.add(fileTm); |
| 482 | + } |
| 483 | + |
| 484 | + if (trustManagers.isEmpty()) return null; |
| 485 | + |
| 486 | + return createCompositeTrustManager(trustManagers); |
| 487 | + } |
| 488 | + |
357 | 489 | private static OkHttpClient enableJKSPKCS12Certificates( |
358 | 490 | OkHttpClient httpClient, |
359 | 491 | String trustStorePath, |
@@ -430,77 +562,46 @@ public static OkHttpClient enablePKCS12Certificates( |
430 | 562 | httpClient, trustStorePath, trustStorePassword, keyStorePath, keyStorePassword, "PKCS12"); |
431 | 563 | } |
432 | 564 |
|
433 | | - /** |
434 | | - * copied logic from |
435 | | - * https://github.com/square/okhttp/blob/master/samples/guide/src/main/java/okhttp3/recipes/CustomTrust.java |
436 | | - */ |
437 | | - public static OkHttpClient enableExternalCertificates(OkHttpClient httpClient, String filename) |
438 | | - throws MinioException { |
| 565 | + /** Enable external TLS certificates from given file path and all valid files from dir path. */ |
| 566 | + public static OkHttpClient enableExternalCertificates( |
| 567 | + OkHttpClient client, String filePath, String dirPath) throws MinioException { |
439 | 568 | try { |
440 | | - Collection<? extends Certificate> certificates = null; |
441 | | - try (InputStream inputStream = Files.newInputStream(Paths.get(filename))) { |
442 | | - certificates = CertificateFactory.getInstance("X.509").generateCertificates(inputStream); |
443 | | - } |
444 | | - |
445 | | - if (certificates == null || certificates.isEmpty()) { |
446 | | - throw new IllegalArgumentException("expected non-empty set of trusted certificates"); |
447 | | - } |
448 | | - |
449 | | - char[] password = "password".toCharArray(); // Any password will work. |
450 | | - |
451 | | - // Put the certificates a key store. |
452 | | - KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType()); |
453 | | - // By convention, 'null' creates an empty key store. |
454 | | - keyStore.load(null, password); |
455 | | - |
456 | | - int index = 0; |
457 | | - for (Certificate certificate : certificates) { |
458 | | - String certificateAlias = Integer.toString(index++); |
459 | | - keyStore.setCertificateEntry(certificateAlias, certificate); |
460 | | - } |
461 | | - |
462 | | - // Use it to build an X509 trust manager. |
463 | | - KeyManagerFactory keyManagerFactory = |
464 | | - KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); |
465 | | - keyManagerFactory.init(keyStore, password); |
466 | | - TrustManagerFactory trustManagerFactory = |
467 | | - TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); |
468 | | - trustManagerFactory.init(keyStore); |
469 | | - |
470 | | - final KeyManager[] keyManagers = keyManagerFactory.getKeyManagers(); |
471 | | - final TrustManager[] trustManagers = trustManagerFactory.getTrustManagers(); |
| 569 | + X509TrustManager tm = getCompositeTrustManager(filePath, dirPath); |
| 570 | + if (tm == null) return client; |
472 | 571 |
|
473 | 572 | SSLContext sslContext = SSLContext.getInstance("TLS"); |
474 | | - sslContext.init(keyManagers, trustManagers, null); |
475 | | - SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory(); |
476 | | - |
477 | | - return httpClient |
478 | | - .newBuilder() |
479 | | - .sslSocketFactory(sslSocketFactory, (X509TrustManager) trustManagers[0]) |
480 | | - .build(); |
481 | | - } catch (GeneralSecurityException | IOException e) { |
| 573 | + sslContext.init(null, new TrustManager[] {tm}, new SecureRandom()); |
| 574 | + return client.newBuilder().sslSocketFactory(sslContext.getSocketFactory(), tm).build(); |
| 575 | + } catch (CertificateException |
| 576 | + | IOException |
| 577 | + | KeyManagementException |
| 578 | + | KeyStoreException |
| 579 | + | NoSuchAlgorithmException e) { |
482 | 580 | throw new MinioException(e); |
483 | 581 | } |
484 | 582 | } |
485 | 583 |
|
| 584 | + /** Enable external TLS certificates from environment variable SSL_CERT_FILE and SSL_CERT_DIR. */ |
| 585 | + public static OkHttpClient enableExternalCertificatesFromEnv(OkHttpClient client) |
| 586 | + throws MinioException { |
| 587 | + return enableExternalCertificates( |
| 588 | + client, System.getenv("SSL_CERT_FILE"), System.getenv("SSL_CERT_DIR")); |
| 589 | + } |
| 590 | + |
486 | 591 | public static OkHttpClient newDefaultClient() { |
487 | | - OkHttpClient httpClient = |
| 592 | + OkHttpClient client = |
488 | 593 | new OkHttpClient() |
489 | 594 | .newBuilder() |
490 | 595 | .connectTimeout(DEFAULT_TIMEOUT, TimeUnit.MILLISECONDS) |
491 | 596 | .writeTimeout(DEFAULT_TIMEOUT, TimeUnit.MILLISECONDS) |
492 | 597 | .readTimeout(DEFAULT_TIMEOUT, TimeUnit.MILLISECONDS) |
493 | 598 | .protocols(Arrays.asList(Protocol.HTTP_1_1)) |
494 | 599 | .build(); |
495 | | - String filename = System.getenv("SSL_CERT_FILE"); |
496 | | - if (filename != null && !filename.isEmpty()) { |
497 | | - try { |
498 | | - httpClient = enableExternalCertificates(httpClient, filename); |
499 | | - } catch (MinioException e) { |
500 | | - throw new RuntimeException(e); |
501 | | - } |
| 600 | + try { |
| 601 | + return enableExternalCertificatesFromEnv(client); |
| 602 | + } catch (MinioException e) { |
| 603 | + throw new RuntimeException(e); |
502 | 604 | } |
503 | | - return httpClient; |
504 | 605 | } |
505 | 606 |
|
506 | 607 | @edu.umd.cs.findbugs.annotations.SuppressFBWarnings( |
|
0 commit comments