Skip to content

Commit ba1c506

Browse files
committed
add ssl dir support
Signed-off-by: Bala.FA <[email protected]>
1 parent 3df3b21 commit ba1c506

File tree

1 file changed

+159
-58
lines changed

1 file changed

+159
-58
lines changed

api/src/main/java/io/minio/Http.java

Lines changed: 159 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -27,20 +27,21 @@
2727
import java.nio.channels.Channels;
2828
import java.nio.charset.StandardCharsets;
2929
import java.nio.file.Files;
30+
import java.nio.file.Path;
3031
import java.nio.file.Paths;
3132
import java.security.GeneralSecurityException;
3233
import java.security.KeyManagementException;
3334
import java.security.KeyStore;
35+
import java.security.KeyStoreException;
3436
import java.security.NoSuchAlgorithmException;
35-
import java.security.cert.Certificate;
37+
import java.security.SecureRandom;
3638
import java.security.cert.CertificateException;
3739
import java.security.cert.CertificateFactory;
3840
import java.security.cert.X509Certificate;
3941
import java.time.ZonedDateTime;
4042
import java.util.AbstractMap;
4143
import java.util.ArrayList;
4244
import java.util.Arrays;
43-
import java.util.Collection;
4445
import java.util.Collections;
4546
import java.util.HashMap;
4647
import java.util.HashSet;
@@ -54,9 +55,9 @@
5455
import java.util.concurrent.TimeUnit;
5556
import java.util.regex.Matcher;
5657
import java.util.stream.Collectors;
58+
import java.util.stream.Stream;
5759
import javax.annotation.Nonnull;
5860
import javax.net.ssl.HostnameVerifier;
59-
import javax.net.ssl.KeyManager;
6061
import javax.net.ssl.KeyManagerFactory;
6162
import javax.net.ssl.SSLContext;
6263
import javax.net.ssl.SSLSession;
@@ -354,6 +355,137 @@ public static MediaType mediaType(String value) {
354355
return mediaType;
355356
}
356357

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+
357489
private static OkHttpClient enableJKSPKCS12Certificates(
358490
OkHttpClient httpClient,
359491
String trustStorePath,
@@ -430,77 +562,46 @@ public static OkHttpClient enablePKCS12Certificates(
430562
httpClient, trustStorePath, trustStorePassword, keyStorePath, keyStorePassword, "PKCS12");
431563
}
432564

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 {
439568
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;
472571

473572
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) {
482580
throw new MinioException(e);
483581
}
484582
}
485583

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+
486591
public static OkHttpClient newDefaultClient() {
487-
OkHttpClient httpClient =
592+
OkHttpClient client =
488593
new OkHttpClient()
489594
.newBuilder()
490595
.connectTimeout(DEFAULT_TIMEOUT, TimeUnit.MILLISECONDS)
491596
.writeTimeout(DEFAULT_TIMEOUT, TimeUnit.MILLISECONDS)
492597
.readTimeout(DEFAULT_TIMEOUT, TimeUnit.MILLISECONDS)
493598
.protocols(Arrays.asList(Protocol.HTTP_1_1))
494599
.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);
502604
}
503-
return httpClient;
504605
}
505606

506607
@edu.umd.cs.findbugs.annotations.SuppressFBWarnings(

0 commit comments

Comments
 (0)