[ENHANCEMENT] Allow to trust all certificates with S3 blobstore (#1846)

diff --git a/server/apps/distributed-app/docs/modules/ROOT/pages/configure/blobstore.adoc b/server/apps/distributed-app/docs/modules/ROOT/pages/configure/blobstore.adoc
index 1b5f267..709325a 100644
--- a/server/apps/distributed-app/docs/modules/ROOT/pages/configure/blobstore.adoc
+++ b/server/apps/distributed-app/docs/modules/ROOT/pages/configure/blobstore.adoc
@@ -143,6 +143,10 @@
 | objectstorage.s3.truststore.algorithm
 | optional: Use this specific trust store algorithm; default SunX509
 
+| objectstorage.s3.trustall
+| optional: boolean. Defaults to false. Cannot be set to true with other trustore options. Wether James should validate
+S3 endpoint SSL certificates.
+
 | objectstorage.s3.read.timeout
 | optional: HTTP read timeout. duration, default value being second. Leaving it empty relies on S3 driver defaults.
 
diff --git a/server/blob/blob-s3/src/main/java/org/apache/james/blob/objectstorage/aws/AwsS3AuthConfiguration.java b/server/blob/blob-s3/src/main/java/org/apache/james/blob/objectstorage/aws/AwsS3AuthConfiguration.java
index ecc4014..70975d7 100644
--- a/server/blob/blob-s3/src/main/java/org/apache/james/blob/objectstorage/aws/AwsS3AuthConfiguration.java
+++ b/server/blob/blob-s3/src/main/java/org/apache/james/blob/objectstorage/aws/AwsS3AuthConfiguration.java
@@ -53,6 +53,7 @@
             private final URI endpoint;
             private final String accessKeyId;
             private final String secretKey;
+            private Optional<Boolean> trustAll;
 
             private Optional<String> trustStorePath;
             private Optional<String> trustStoreType;
@@ -67,6 +68,7 @@
                 this.trustStoreType = Optional.empty();
                 this.trustStoreSecret = Optional.empty();
                 this.trustStoreAlgorithm = Optional.empty();
+                this.trustAll = Optional.empty();
             }
 
             public ReadyToBuild trustStorePath(Optional<String> trustStorePath) {
@@ -87,6 +89,11 @@
                 return trustStoreType(Optional.ofNullable(trustStoreType));
             }
 
+            public ReadyToBuild trustAll(boolean trustAll) {
+                this.trustAll = Optional.of(trustAll);
+                return this;
+            }
+
             public ReadyToBuild trustStoreSecret(Optional<String> trustStoreSecret) {
                 this.trustStoreSecret = trustStoreSecret;
                 return this;
@@ -114,8 +121,13 @@
                 Preconditions.checkNotNull(secretKey, "'secretKey' is mandatory");
                 Preconditions.checkArgument(!secretKey.isEmpty(), "'secretKey' is mandatory");
 
+                boolean trustAll = this.trustAll.orElse(false);
+                Preconditions.checkState(!(trustAll && trustStoreType.isPresent()), "Cannot specify 'trustAll' and 'trustStoreType' simultaneously");
+                Preconditions.checkState(!(trustAll && trustStorePath.isPresent()), "Cannot specify 'trustAll' and 'trustStorePath' simultaneously");
+                Preconditions.checkState(!(trustAll && trustStoreSecret.isPresent()), "Cannot specify 'trustAll' and 'trustStoreSecret' simultaneously");
+
                 return new AwsS3AuthConfiguration(endpoint, accessKeyId, secretKey,
-                    trustStorePath, trustStoreType, trustStoreSecret, trustStoreAlgorithm);
+                    trustStorePath, trustStoreType, trustStoreSecret, trustStoreAlgorithm, trustAll);
             }
         }
     }
@@ -128,6 +140,7 @@
     private final Optional<String> trustStoreType;
     private final Optional<String> trustStoreSecret;
     private final Optional<String> trustStoreAlgorithm;
+    private final boolean trustAll;
 
     private AwsS3AuthConfiguration(URI endpoint,
                                    String accessKeyId,
@@ -135,7 +148,8 @@
                                    Optional<String> trustStorePath,
                                    Optional<String> trustStoreType,
                                    Optional<String> trustStoreSecret,
-                                   Optional<String> trustStoreAlgorithm) {
+                                   Optional<String> trustStoreAlgorithm,
+                                   boolean trustAll) {
         this.endpoint = endpoint;
         this.accessKeyId = accessKeyId;
         this.secretKey = secretKey;
@@ -143,6 +157,7 @@
         this.trustStoreType = trustStoreType;
         this.trustStoreSecret = trustStoreSecret;
         this.trustStoreAlgorithm = trustStoreAlgorithm;
+        this.trustAll = trustAll;
     }
 
     public URI getEndpoint() {
@@ -173,6 +188,10 @@
         return trustStoreAlgorithm;
     }
 
+    public boolean isTrustAll() {
+        return trustAll;
+    }
+
     @Override
     public final boolean equals(Object o) {
         if (o instanceof AwsS3AuthConfiguration) {
@@ -183,7 +202,8 @@
                 Objects.equal(trustStorePath, that.trustStorePath) &&
                 Objects.equal(trustStoreType, that.trustStoreType) &&
                 Objects.equal(trustStoreSecret, that.trustStoreSecret) &&
-                Objects.equal(trustStoreAlgorithm, that.trustStoreAlgorithm);
+                Objects.equal(trustStoreAlgorithm, that.trustStoreAlgorithm) &&
+                Objects.equal(trustAll, that.trustAll);
         }
         return false;
     }
@@ -191,7 +211,8 @@
     @Override
     public final int hashCode() {
         return Objects.hashCode(endpoint, accessKeyId, secretKey,
-                trustStorePath, trustStoreType, trustStoreSecret, trustStoreAlgorithm);
+            trustStorePath, trustStoreType, trustStoreSecret, trustStoreAlgorithm,
+            trustAll);
     }
 
     @Override
@@ -203,6 +224,7 @@
             .add("trustStorePath", trustStorePath)
             .add("trustStoreSecret", trustStoreSecret)
             .add("trustStoreAlgorithm", trustStoreAlgorithm)
+            .add("trustAll", trustAll)
             .toString();
     }
 }
diff --git a/server/blob/blob-s3/src/main/java/org/apache/james/blob/objectstorage/aws/S3BlobStoreDAO.java b/server/blob/blob-s3/src/main/java/org/apache/james/blob/objectstorage/aws/S3BlobStoreDAO.java
index 725cf8d..b244601 100644
--- a/server/blob/blob-s3/src/main/java/org/apache/james/blob/objectstorage/aws/S3BlobStoreDAO.java
+++ b/server/blob/blob-s3/src/main/java/org/apache/james/blob/objectstorage/aws/S3BlobStoreDAO.java
@@ -28,6 +28,7 @@
 import java.nio.ByteBuffer;
 import java.security.GeneralSecurityException;
 import java.security.KeyStore;
+import java.security.cert.X509Certificate;
 import java.time.Duration;
 import java.util.Collection;
 import java.util.List;
@@ -35,7 +36,9 @@
 
 import javax.annotation.PreDestroy;
 import javax.inject.Inject;
+import javax.net.ssl.TrustManager;
 import javax.net.ssl.TrustManagerFactory;
+import javax.net.ssl.X509TrustManager;
 
 import org.apache.commons.io.IOUtils;
 import org.apache.james.blob.api.BlobId;
@@ -83,6 +86,23 @@
 import software.amazon.awssdk.services.s3.model.S3Object;
 
 public class S3BlobStoreDAO implements BlobStoreDAO, Startable, Closeable {
+    private static final TrustManager DUMMY_TRUST_MANAGER = new X509TrustManager() {
+        @Override
+        public X509Certificate[] getAcceptedIssuers() {
+            return new X509Certificate[0];
+        }
+
+        @Override
+        public void checkClientTrusted(X509Certificate[] chain, String authType) {
+            // Always trust
+        }
+
+        @Override
+        public void checkServerTrusted(X509Certificate[] chain, String authType) {
+            // Always trust
+        }
+    };
+
     private static class FileBackedOutputStreamByteSource extends ByteSource {
         private final FileBackedOutputStream stream;
         private final long size;
@@ -158,6 +178,9 @@
     }
 
     private TlsTrustManagersProvider getTrustManagerProvider(AwsS3AuthConfiguration configuration) {
+        if (configuration.isTrustAll()) {
+            return () -> ImmutableList.of(DUMMY_TRUST_MANAGER).toArray(new TrustManager[0]);
+        }
         try {
             TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(
                 configuration.getTrustStoreAlgorithm().orElse(TrustManagerFactory.getDefaultAlgorithm()));
diff --git a/server/container/guice/blob/s3/src/main/java/org/apache/james/modules/objectstorage/aws/s3/AwsS3ConfigurationReader.java b/server/container/guice/blob/s3/src/main/java/org/apache/james/modules/objectstorage/aws/s3/AwsS3ConfigurationReader.java
index 50ba125..a7618ee 100644
--- a/server/container/guice/blob/s3/src/main/java/org/apache/james/modules/objectstorage/aws/s3/AwsS3ConfigurationReader.java
+++ b/server/container/guice/blob/s3/src/main/java/org/apache/james/modules/objectstorage/aws/s3/AwsS3ConfigurationReader.java
@@ -34,6 +34,7 @@
     static final String OBJECTSTORAGE_TRUSTSTORE_TYPE = "objectstorage.s3.truststore.type";
     static final String OBJECTSTORAGE_TRUSTSTORE_SECRET = "objectstorage.s3.truststore.secret";
     static final String OBJECTSTORAGE_TRUSTSTORE_ALGORITHM = "objectstorage.s3.truststore.algorithm";
+    static final String OBJECTSTORAGE_TRUSTALL = "objectstorage.s3.trustall";
 
     public static AwsS3AuthConfiguration from(Configuration configuration) {
         String endpoint = configuration.getString(OBJECTSTORAGE_ENDPOINT);
@@ -49,6 +50,7 @@
                 .trustStoreType(configuration.getString(OBJECTSTORAGE_TRUSTSTORE_TYPE))
                 .trustStoreSecret(configuration.getString(OBJECTSTORAGE_TRUSTSTORE_SECRET))
                 .trustStoreAlgorithm(configuration.getString(OBJECTSTORAGE_TRUSTSTORE_ALGORITHM))
+                .trustAll(configuration.getBoolean(OBJECTSTORAGE_TRUSTALL, false))
                 .build();
     }
 }
diff --git a/server/container/guice/blob/s3/src/test/java/org/apache/james/modules/objectstorage/aws/s3/AwsS3ConfigurationReaderTest.java b/server/container/guice/blob/s3/src/test/java/org/apache/james/modules/objectstorage/aws/s3/AwsS3ConfigurationReaderTest.java
index 21edf20..2d675a8 100644
--- a/server/container/guice/blob/s3/src/test/java/org/apache/james/modules/objectstorage/aws/s3/AwsS3ConfigurationReaderTest.java
+++ b/server/container/guice/blob/s3/src/test/java/org/apache/james/modules/objectstorage/aws/s3/AwsS3ConfigurationReaderTest.java
@@ -20,6 +20,7 @@
 package org.apache.james.modules.objectstorage.aws.s3;
 
 import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatCode;
 import static org.assertj.core.api.Assertions.assertThatThrownBy;
 
 import java.net.URI;
@@ -89,6 +90,84 @@
         assertThat(authConfiguration).isEqualTo(expected);
     }
 
+
+    @Test
+    void trustAllAndTrustStoreShouldBeIncompatible() {
+        Configuration configuration = new PropertiesConfiguration();
+        URI endpoint = URI.create("http://myEndpoint");
+        configuration.addProperty(AwsS3ConfigurationReader.OBJECTSTORAGE_ENDPOINT, endpoint);
+        String accessKeyId = "myAccessKeyId";
+        configuration.addProperty(AwsS3ConfigurationReader.OBJECTSTORAGE_ACCESKEYID, accessKeyId);
+        String secretKey = "mySecretKey";
+        configuration.addProperty(AwsS3ConfigurationReader.OBJECTSTORAGE_SECRETKEY, secretKey);
+        String trustStorePath = "/some/where/truststore.p12";
+        configuration.addProperty(AwsS3ConfigurationReader.OBJECTSTORAGE_TRUSTSTORE_PATH, trustStorePath);
+        String trustStoreType = "PKCS12";
+        configuration.addProperty(AwsS3ConfigurationReader.OBJECTSTORAGE_TRUSTSTORE_TYPE, trustStoreType);
+        String trustStoreSecret = "myTrustStoreSecret";
+        configuration.addProperty(AwsS3ConfigurationReader.OBJECTSTORAGE_TRUSTSTORE_SECRET, trustStoreSecret);
+        String trustStoreAlgorithm = "myTrustStoreAlgorithm";
+        configuration.addProperty(AwsS3ConfigurationReader.OBJECTSTORAGE_TRUSTSTORE_ALGORITHM, trustStoreAlgorithm);
+        configuration.addProperty(AwsS3ConfigurationReader.OBJECTSTORAGE_TRUSTALL, true);
+
+        assertThatThrownBy(() -> AwsS3ConfigurationReader.from(configuration)).isInstanceOf(IllegalStateException.class);
+    }
+
+
+    @Test
+    void trustNotAllAndTrustStoreShouldBeCompatible() {
+        Configuration configuration = new PropertiesConfiguration();
+        URI endpoint = URI.create("http://myEndpoint");
+        configuration.addProperty(AwsS3ConfigurationReader.OBJECTSTORAGE_ENDPOINT, endpoint);
+        String accessKeyId = "myAccessKeyId";
+        configuration.addProperty(AwsS3ConfigurationReader.OBJECTSTORAGE_ACCESKEYID, accessKeyId);
+        String secretKey = "mySecretKey";
+        configuration.addProperty(AwsS3ConfigurationReader.OBJECTSTORAGE_SECRETKEY, secretKey);
+        String trustStorePath = "/some/where/truststore.p12";
+        configuration.addProperty(AwsS3ConfigurationReader.OBJECTSTORAGE_TRUSTSTORE_PATH, trustStorePath);
+        String trustStoreType = "PKCS12";
+        configuration.addProperty(AwsS3ConfigurationReader.OBJECTSTORAGE_TRUSTSTORE_TYPE, trustStoreType);
+        String trustStoreSecret = "myTrustStoreSecret";
+        configuration.addProperty(AwsS3ConfigurationReader.OBJECTSTORAGE_TRUSTSTORE_SECRET, trustStoreSecret);
+        String trustStoreAlgorithm = "myTrustStoreAlgorithm";
+        configuration.addProperty(AwsS3ConfigurationReader.OBJECTSTORAGE_TRUSTSTORE_ALGORITHM, trustStoreAlgorithm);
+        configuration.addProperty(AwsS3ConfigurationReader.OBJECTSTORAGE_TRUSTALL, false);
+
+        assertThatCode(() -> AwsS3ConfigurationReader.from(configuration)).doesNotThrowAnyException();
+    }
+
+    @Test
+    void trustAllShouldBeFalseByDefault() {
+        Configuration configuration = new PropertiesConfiguration();
+        URI endpoint = URI.create("http://myEndpoint");
+        configuration.addProperty(AwsS3ConfigurationReader.OBJECTSTORAGE_ENDPOINT, endpoint);
+        String accessKeyId = "myAccessKeyId";
+        configuration.addProperty(AwsS3ConfigurationReader.OBJECTSTORAGE_ACCESKEYID, accessKeyId);
+        String secretKey = "mySecretKey";
+        configuration.addProperty(AwsS3ConfigurationReader.OBJECTSTORAGE_SECRETKEY, secretKey);
+
+        AwsS3AuthConfiguration testee = AwsS3ConfigurationReader.from(configuration);
+
+        assertThat(testee.isTrustAll()).isFalse();
+    }
+
+
+    @Test
+    void trustAllShouldBeTrueWhenEnabled() {
+        Configuration configuration = new PropertiesConfiguration();
+        URI endpoint = URI.create("http://myEndpoint");
+        configuration.addProperty(AwsS3ConfigurationReader.OBJECTSTORAGE_ENDPOINT, endpoint);
+        String accessKeyId = "myAccessKeyId";
+        configuration.addProperty(AwsS3ConfigurationReader.OBJECTSTORAGE_ACCESKEYID, accessKeyId);
+        String secretKey = "mySecretKey";
+        configuration.addProperty(AwsS3ConfigurationReader.OBJECTSTORAGE_SECRETKEY, secretKey);
+        configuration.addProperty(AwsS3ConfigurationReader.OBJECTSTORAGE_TRUSTALL, true);
+
+        AwsS3AuthConfiguration testee = AwsS3ConfigurationReader.from(configuration);
+
+        assertThat(testee.isTrustAll()).isTrue();
+    }
+
     @Test
     void fromShouldWorkWithoutOptionals() {
         Configuration configuration = new PropertiesConfiguration();
diff --git a/src/site/xdoc/server/config-blobstore.xml b/src/site/xdoc/server/config-blobstore.xml
index 4ff8da2..7583ba4 100644
--- a/src/site/xdoc/server/config-blobstore.xml
+++ b/src/site/xdoc/server/config-blobstore.xml
@@ -184,6 +184,10 @@
                         <dt><strong>objectstorage.s3.truststore.algorithm</strong></dt>
                         <dd><i>optional:</i> Use this specific trust store algorithm; default SunX509</dd>
 
+                        <dt><strong>objectstorage.s3.trustall</strong></dt>
+                        <dd><i>optional:</i>  boolean. Defaults to false. Cannot be set to true with other trustore options. Wether James should validate
+                        S3 endpoint SSL certificates.</dd>
+
                         <dt><strong>objectstorage.s3.read.timeout</strong></dt>
                         <dd><i>optional:</i> HTTP read timeout. duration, default value being second. Leaving it empty relies on S3 driver defaults.</dd>