You are viewing a plain text version of this content. The canonical link for it is here.
Posted to oak-commits@jackrabbit.apache.org by ad...@apache.org on 2021/07/08 14:28:51 UTC

[jackrabbit-oak] branch 1.22 updated: OAK-9451 - Cold Standby SSL certificates should be configurable Backport from trunk (Oak-9451 Oak-9473)

This is an automated email from the ASF dual-hosted git repository.

adulceanu pushed a commit to branch 1.22
in repository https://gitbox.apache.org/repos/asf/jackrabbit-oak.git


The following commit(s) were added to refs/heads/1.22 by this push:
     new e7dc8b2  OAK-9451 - Cold Standby SSL certificates should be configurable Backport from trunk (Oak-9451 Oak-9473)
e7dc8b2 is described below

commit e7dc8b2122ae0a0fd7fa29c87ac1156c6b9d6154
Author: Andrei Dulceanu <ad...@apache.org>
AuthorDate: Fri Jun 4 15:45:52 2021 +0000

    OAK-9451 - Cold Standby SSL certificates should be configurable
    Backport from trunk (Oak-9451 Oak-9473)
---
 .../jackrabbit/oak/fixture/SegmentTarFixture.java  |  12 +-
 .../oak/segment/standby/client/StandbyClient.java  | 109 ++-
 .../segment/standby/client/StandbyClientSync.java  | 121 ++-
 .../segment/standby/netty/SSLSubjectMatcher.java   |  59 ++
 .../oak/segment/standby/server/StandbyServer.java  |  55 +-
 .../segment/standby/server/StandbyServerSync.java  |  61 +-
 .../segment/standby/store/StandbyStoreService.java |  86 ++-
 .../oak/segment/standby/BrokenNetworkIT.java       |  45 +-
 .../oak/segment/standby/DataStoreTestBase.java     |  71 +-
 .../oak/segment/standby/FailoverIPRangeIT.java     |  10 +-
 .../standby/FailoverMultipleClientsTestIT.java     |  20 +-
 .../oak/segment/standby/FailoverSslTestIT.java     |  30 +-
 .../jackrabbit/oak/segment/standby/MBeanIT.java    |  32 +-
 .../oak/segment/standby/RecoverTestIT.java         |  10 +-
 .../segment/standby/StandbySegmentBlobTestIT.java  |  10 +-
 .../oak/segment/standby/StandbyTestIT.java         | 812 ++++++++++++++++++++-
 .../jackrabbit/oak/segment/standby/TestBase.java   | 288 ++++++++
 .../oak/segment/standby/server/SlowServerIT.java   |  10 +-
 18 files changed, 1770 insertions(+), 71 deletions(-)

diff --git a/oak-run-commons/src/main/java/org/apache/jackrabbit/oak/fixture/SegmentTarFixture.java b/oak-run-commons/src/main/java/org/apache/jackrabbit/oak/fixture/SegmentTarFixture.java
index 0b8ad8b..a61db8e 100644
--- a/oak-run-commons/src/main/java/org/apache/jackrabbit/oak/fixture/SegmentTarFixture.java
+++ b/oak-run-commons/src/main/java/org/apache/jackrabbit/oak/fixture/SegmentTarFixture.java
@@ -324,8 +324,16 @@ public class SegmentTarFixture extends OakFixture {
             .withBlobChunkSize(1 * MB)
             .withSecureConnection(secure)
             .build();
-        clientSyncs[i] = new StandbyClientSync("127.0.0.1", port, stores[n + i], secure, DEFAULT_TIMEOUT, false, new File(StandardSystemProperty.JAVA_IO_TMPDIR.value()));
-        
+        clientSyncs[i] = StandbyClientSync.builder()
+            .withHost("127.0.0.1")
+            .withPort(port)
+            .withFileStore(stores[n + 1])
+            .withSecureConnection(secure)
+            .withReadTimeoutMs(DEFAULT_TIMEOUT)
+            .withAutoClean(false)
+            .withSpoolFolder(new File(StandardSystemProperty.JAVA_IO_TMPDIR.value()))
+            .build();
+
         if (!oneShotRun) {
             serverSyncs[i].start();
             clientSyncs[i].start();
diff --git a/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/standby/client/StandbyClient.java b/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/standby/client/StandbyClient.java
index 9073cd6..3e92a73 100644
--- a/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/standby/client/StandbyClient.java
+++ b/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/standby/client/StandbyClient.java
@@ -23,8 +23,6 @@ import java.util.concurrent.BlockingQueue;
 import java.util.concurrent.LinkedBlockingDeque;
 import java.util.concurrent.TimeUnit;
 
-import javax.net.ssl.SSLException;
-
 import io.netty.bootstrap.Bootstrap;
 import io.netty.channel.Channel;
 import io.netty.channel.ChannelInitializer;
@@ -54,12 +52,93 @@ import org.apache.jackrabbit.oak.segment.standby.codec.GetSegmentRequest;
 import org.apache.jackrabbit.oak.segment.standby.codec.GetSegmentRequestEncoder;
 import org.apache.jackrabbit.oak.segment.standby.codec.GetSegmentResponse;
 import org.apache.jackrabbit.oak.segment.standby.codec.ResponseDecoder;
+import org.apache.jackrabbit.oak.segment.standby.netty.SSLSubjectMatcher;
 import org.jetbrains.annotations.Nullable;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 class StandbyClient implements AutoCloseable {
 
+    public static Builder builder() {
+        return new Builder();
+    }
+
+    static class Builder {
+
+        private String host;
+        private int port;
+        private NioEventLoopGroup group;
+        private String clientId;
+        private boolean secure;
+        private int readTimeoutMs;
+        private File spoolFolder;
+        private String sslKeyFile;
+        private String sslKeyPassword;
+        private String sslChainFile;
+        public String sslSubjectPattern;
+
+        private Builder() {}
+
+        public Builder withHost(String host) {
+            this.host = host;
+            return this;
+        }
+
+        public Builder withPort(int port) {
+            this.port = port;
+            return this;
+        }
+
+        public Builder withGroup(NioEventLoopGroup group) {
+            this.group = group;
+            return this;
+        }
+
+        public Builder withClientId(String clientId) {
+            this.clientId = clientId;
+            return this;
+        }
+
+        public Builder withSecure(boolean secure) {
+            this.secure = secure;
+            return this;
+        }
+
+        public Builder withReadTimeoutMs(int readTimeoutMs) {
+            this.readTimeoutMs = readTimeoutMs;
+            return this;
+        }
+
+        public Builder withSpoolFolder(File spoolFolder) {
+            this.spoolFolder = spoolFolder;
+            return this;
+        }
+
+        public Builder withSSLKeyFile(String sslKeyFile) {
+            this.sslKeyFile = sslKeyFile;
+            return this;
+        }
+
+        public Builder withSSLKeyPassword(String sslKeyPassword) {
+            this.sslKeyPassword = sslKeyPassword;
+            return this;
+        }
+
+        public Builder withSSLChainFile(String sslChainFile) {
+            this.sslChainFile = sslChainFile;
+            return this;
+        }
+
+        public Builder withSSLSubjectPattern(String sslServerSubjectPattern) {
+            this.sslSubjectPattern = sslServerSubjectPattern;
+            return this;
+        }
+
+        public StandbyClient build() throws InterruptedException {
+            return new StandbyClient(this);
+        }
+    }
+
     private static final Logger log = LoggerFactory.getLogger(StandbyClient.class);
 
     private final BlockingQueue<GetHeadResponse> headQueue = new LinkedBlockingDeque<>();
@@ -76,12 +155,12 @@ class StandbyClient implements AutoCloseable {
 
     private Channel channel;
 
-    StandbyClient(String host, int port, NioEventLoopGroup group, String clientId, boolean secure, int readTimeoutMs, File spoolFolder) throws InterruptedException {
-        this.clientId = clientId;
-        this.readTimeoutMs = readTimeoutMs;
+    StandbyClient(Builder builder) throws InterruptedException {
+        this.clientId = builder.clientId;
+        this.readTimeoutMs = builder.readTimeoutMs;
 
         Bootstrap b = new Bootstrap()
-            .group(group)
+            .group(builder.group)
             .channel(NioSocketChannel.class)
             .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, readTimeoutMs)
             .option(ChannelOption.TCP_NODELAY, true)
@@ -93,8 +172,18 @@ class StandbyClient implements AutoCloseable {
                 public void initChannel(SocketChannel ch) throws Exception {
                     ChannelPipeline p = ch.pipeline();
 
-                    if (secure) {
-                        p.addLast(SslContextBuilder.forClient().trustManager(InsecureTrustManagerFactory.INSTANCE).build().newHandler(ch.alloc()));
+                    if (builder.secure) {
+                        SslContext sslContext;
+                        if (builder.sslKeyFile != null && !"".equals(builder.sslKeyFile)) {
+                            sslContext = SslContextBuilder.forClient().keyManager(new File(builder.sslChainFile), new File(builder.sslKeyFile), builder.sslKeyPassword).build();
+                        } else {
+                            sslContext = SslContextBuilder.forClient().trustManager(InsecureTrustManagerFactory.INSTANCE).build();
+                        }
+                        p.addLast("ssl", sslContext.newHandler(ch.alloc()));
+
+                        if (builder.sslSubjectPattern != null) {
+                            p.addLast(new SSLSubjectMatcher(builder.sslSubjectPattern));
+                        }
                     }
 
                     p.addLast(new ReadTimeoutHandler(readTimeoutMs, TimeUnit.MILLISECONDS));
@@ -106,7 +195,7 @@ class StandbyClient implements AutoCloseable {
                     // The frame length limits the chunk size to max. 2.2GB
 
                     p.addLast(new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE, 0, 4));
-                    p.addLast(new ResponseDecoder(spoolFolder));
+                    p.addLast(new ResponseDecoder(builder.spoolFolder));
 
                     // Encoders
 
@@ -130,7 +219,7 @@ class StandbyClient implements AutoCloseable {
 
             });
 
-        channel = b.connect(host, port).sync().channel();
+        channel = b.connect(builder.host, builder.port).sync().channel();
     }
 
     @Override
diff --git a/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/standby/client/StandbyClientSync.java b/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/standby/client/StandbyClientSync.java
index a5b0901..ebca61d 100644
--- a/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/standby/client/StandbyClientSync.java
+++ b/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/standby/client/StandbyClientSync.java
@@ -45,6 +45,86 @@ import org.slf4j.LoggerFactory;
 
 public final class StandbyClientSync implements ClientStandbyStatusMBean, Runnable, Closeable {
 
+    public static Builder builder() {
+        return new Builder();
+    }
+
+    public static class Builder {
+
+        private String host;
+        private int port;
+        private FileStore fileStore;
+        private boolean secure;
+        private int readTimeoutMs;
+        private boolean autoClean;
+        private File spoolFolder;
+        private String sslKeyFile;
+        private String sslKeyPassword;
+        private String sslChainFile;
+        private String sslSubjectPattern;
+
+        private Builder() {}
+
+        public Builder withHost(String host) {
+            this.host = host;
+            return this;
+        }
+
+        public Builder withPort(int port) {
+            this.port = port;
+            return this;
+        }
+
+        public Builder withFileStore(FileStore fileStore) {
+            this.fileStore = fileStore;
+            return this;
+        }
+
+        public Builder withSecureConnection(boolean secure) {
+            this.secure = secure;
+            return this;
+        }
+
+        public Builder withReadTimeoutMs(int readTimeoutMs) {
+            this.readTimeoutMs = readTimeoutMs;
+            return this;
+        }
+
+        public Builder withAutoClean(boolean autoClean) {
+            this.autoClean = autoClean;
+            return this;
+        }
+
+        public Builder withSpoolFolder(File spoolFolder) {
+            this.spoolFolder = spoolFolder;
+            return this;
+        }
+
+        public Builder withSSLKeyFile(String sslKeyFile) {
+            this.sslKeyFile = sslKeyFile;
+            return this;
+        }
+
+        public Builder withSSLKeyPassword(String sslKeyPassword) {
+            this.sslKeyPassword = sslKeyPassword;
+            return this;
+        }
+
+        public Builder withSSLChainFile(String sslChainFile) {
+            this.sslChainFile = sslChainFile;
+            return this;
+        }
+
+        public Builder withSSLSubjectPattern(String sslSubjectPattern) {
+            this.sslSubjectPattern = sslSubjectPattern;
+            return this;
+        }
+
+        public StandbyClientSync build() {
+            return new StandbyClientSync(this);
+        }
+    }
+
     public static final String CLIENT_ID_PROPERTY_NAME = "standbyID";
 
     private static final Logger log = LoggerFactory.getLogger(StandbyClientSync.class);
@@ -73,6 +153,14 @@ public final class StandbyClientSync implements ClientStandbyStatusMBean, Runnab
 
     private final AtomicBoolean active = new AtomicBoolean(false);
 
+    private final String sslKeyFile;
+
+    private final String sslKeyPassword;
+
+    private final String sslChainFile;
+
+    private final String sslSubjectPattern;
+
     private int failedRequests;
 
     private long lastSuccessfulRequest;
@@ -95,22 +183,26 @@ public final class StandbyClientSync implements ClientStandbyStatusMBean, Runnab
         return s;
     }
 
-    public StandbyClientSync(String host, int port, FileStore store, boolean secure, int readTimeoutMs, boolean autoClean, File spoolFolder) {
+    private StandbyClientSync(Builder builder) {
         this.state = STATUS_INITIALIZING;
         this.lastSuccessfulRequest = -1;
         this.syncStartTimestamp = -1;
         this.syncEndTimestamp = -1;
         this.failedRequests = 0;
-        this.host = host;
-        this.port = port;
-        this.secure = secure;
-        this.readTimeoutMs = readTimeoutMs;
-        this.autoClean = autoClean;
-        this.fileStore = store;
+        this.host = builder.host;
+        this.port = builder.port;
+        this.secure = builder.secure;
+        this.readTimeoutMs = builder.readTimeoutMs;
+        this.autoClean = builder.autoClean;
+        this.fileStore = builder.fileStore;
         this.observer = new CommunicationObserver(clientId());
         this.group = new NioEventLoopGroup(0, new NamedThreadFactory("standby"));
         this.execution = new StandbyClientSyncExecution(fileStore, () -> running);
-        this.spoolFolder = spoolFolder;
+        this.spoolFolder = builder.spoolFolder;
+        this.sslKeyFile = builder.sslKeyFile;
+        this.sslKeyPassword = builder.sslKeyPassword;
+        this.sslChainFile = builder.sslChainFile;
+        this.sslSubjectPattern = builder.sslSubjectPattern;
         try {
             ManagementFactory.getPlatformMBeanServer().registerMBean(new StandardMBean(this, ClientStandbyStatusMBean.class), new ObjectName(this.getMBeanName()));
         } catch (Exception e) {
@@ -161,7 +253,18 @@ public final class StandbyClientSync implements ClientStandbyStatusMBean, Runnab
 
                 GCGeneration genBefore = headGeneration(fileStore);
 
-                try (StandbyClient client = new StandbyClient(host, port, group, observer.getID(), secure, readTimeoutMs, spoolFolder)) {
+                try (StandbyClient client = StandbyClient.builder()
+                     .withHost(host)
+                     .withPort(port)
+                     .withGroup(group)
+                     .withClientId(observer.getID())
+                     .withSecure(secure)
+                     .withReadTimeoutMs(readTimeoutMs)
+                     .withSpoolFolder(spoolFolder)
+                     .withSSLKeyFile(sslKeyFile)
+                     .withSSLKeyPassword(sslKeyPassword)
+                     .withSSLChainFile(sslChainFile)
+                     .withSSLSubjectPattern(sslSubjectPattern).build()) {
                     execution.execute(client);
                 }
 
diff --git a/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/standby/netty/SSLSubjectMatcher.java b/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/standby/netty/SSLSubjectMatcher.java
new file mode 100644
index 0000000..cf67973
--- /dev/null
+++ b/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/standby/netty/SSLSubjectMatcher.java
@@ -0,0 +1,59 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.jackrabbit.oak.segment.standby.netty;
+
+import io.netty.channel.Channel;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.channel.ChannelInboundHandlerAdapter;
+import io.netty.channel.ChannelPipeline;
+import io.netty.handler.ssl.SslHandler;
+import io.netty.handler.ssl.SslHandshakeCompletionEvent;
+
+import javax.net.ssl.SSLSession;
+import javax.security.cert.X509Certificate;
+import java.security.Principal;
+import java.util.regex.Pattern;
+
+public class SSLSubjectMatcher extends ChannelInboundHandlerAdapter {
+
+    private final Pattern pattern;
+
+    public SSLSubjectMatcher(String pattern) {
+        this.pattern = Pattern.compile(pattern);
+    }
+
+    @Override
+    public void userEventTriggered(ChannelHandlerContext channelHandlerContext, Object ev) throws Exception {
+        if (ev instanceof SslHandshakeCompletionEvent) {
+            final Channel channel = channelHandlerContext.channel();
+            final ChannelPipeline pipeline = channel.pipeline();
+            final SslHandler ssl = (SslHandler) pipeline.get("ssl");
+            final SSLSession session = ssl.engine().getSession();
+            final X509Certificate x509Certificate = session.getPeerCertificateChain()[0];
+            final Principal subjectDN = x509Certificate.getSubjectDN();
+            final String subject = subjectDN.getName();
+            if (!pattern.matcher(subject).matches()) {
+                throw new Exception(
+                    "Pattern match /" + pattern.toString() + "/ failed on: " + subject + "\n" +
+                    "Note: the Java implementation of regex always implicitly matches from beginning to\n" +
+                    "end, as in ^YOUR_PATTERN$, so if you want to match /acme/ anywhere in the subject\n" +
+                    "you'd use .*acme.*");
+            }
+        }
+    }
+}
diff --git a/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/standby/server/StandbyServer.java b/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/standby/server/StandbyServer.java
index 6935ca7..29adcba 100644
--- a/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/standby/server/StandbyServer.java
+++ b/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/standby/server/StandbyServer.java
@@ -21,6 +21,7 @@ package org.apache.jackrabbit.oak.segment.standby.server;
 
 import static com.google.common.base.Preconditions.checkState;
 
+import java.io.File;
 import java.security.cert.CertificateException;
 import java.util.concurrent.TimeUnit;
 
@@ -40,6 +41,7 @@ import io.netty.handler.codec.compression.SnappyFrameEncoder;
 import io.netty.handler.codec.string.StringDecoder;
 import io.netty.handler.ssl.SslContext;
 import io.netty.handler.ssl.SslContextBuilder;
+import io.netty.handler.ssl.SslHandler;
 import io.netty.handler.ssl.util.SelfSignedCertificate;
 import io.netty.handler.stream.ChunkedWriteHandler;
 import io.netty.util.CharsetUtil;
@@ -50,6 +52,7 @@ import org.apache.jackrabbit.oak.segment.standby.codec.GetHeadResponseEncoder;
 import org.apache.jackrabbit.oak.segment.standby.codec.GetReferencesResponseEncoder;
 import org.apache.jackrabbit.oak.segment.standby.codec.GetSegmentResponseEncoder;
 import org.apache.jackrabbit.oak.segment.standby.codec.RequestDecoder;
+import org.apache.jackrabbit.oak.segment.standby.netty.SSLSubjectMatcher;
 import org.apache.jackrabbit.oak.segment.standby.store.CommunicationObserver;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -104,6 +107,16 @@ class StandbyServer implements AutoCloseable {
 
         private StandbyBlobReader standbyBlobReader;
 
+        private String sslKeyFile;
+
+        private String sslKeyPassword;
+
+        private String sslChainFile;
+
+        private boolean sslClientValidation;
+
+        private String sslSubjectPattern;
+
         private Builder(final int port, final StoreProvider storeProvider, final int blobChunkSize) {
             this.port = port;
             this.storeProvider = storeProvider;
@@ -150,6 +163,31 @@ class StandbyServer implements AutoCloseable {
             return this;
         }
 
+        Builder withSSLKeyFile(String sslKeyFile) {
+            this.sslKeyFile = sslKeyFile;
+            return this;
+        }
+
+        Builder withSSLKeyPassword(String sslKeyPassword) {
+            this.sslKeyPassword = sslKeyPassword;
+            return this;
+        }
+
+        Builder withSSLChainFile(String sslChainFile) {
+            this.sslChainFile = sslChainFile;
+            return this;
+        }
+
+        Builder withSSLClientValidation(boolean sslValidateClient) {
+            this.sslClientValidation = sslValidateClient;
+            return this;
+        }
+
+        Builder withSSLSubjectPattern(String sslSubjectPattern) {
+            this.sslSubjectPattern = sslSubjectPattern;
+            return this;
+        }
+
         StandbyServer build() throws CertificateException, SSLException {
             checkState(storeProvider != null);
 
@@ -173,15 +211,18 @@ class StandbyServer implements AutoCloseable {
 
             return new StandbyServer(this);
         }
-
     }
 
     private StandbyServer(final Builder builder) throws CertificateException, SSLException {
         this.port = builder.port;
 
         if (builder.secure) {
-            SelfSignedCertificate ssc = new SelfSignedCertificate();
-            sslContext = SslContextBuilder.forServer(ssc.certificate(), ssc.privateKey()).build();
+            if (builder.sslKeyFile != null && !"".equals(builder.sslKeyFile)) {
+                sslContext = SslContextBuilder.forServer(new File(builder.sslChainFile), new File(builder.sslKeyFile), builder.sslKeyPassword).build();
+            } else {
+                SelfSignedCertificate ssc = new SelfSignedCertificate();
+                sslContext = SslContextBuilder.forServer(ssc.certificate(), ssc.privateKey()).build();
+            }
         }
 
         bossGroup = new NioEventLoopGroup(1, new NamedThreadFactory("primary-run"));
@@ -205,7 +246,13 @@ class StandbyServer implements AutoCloseable {
                 p.addLast(new ClientFilterHandler(new ClientIpFilter(builder.allowedClientIPRanges)));
 
                 if (sslContext != null) {
-                    p.addLast("ssl", sslContext.newHandler(ch.alloc()));
+                    SslHandler handler = sslContext.newHandler(ch.alloc());
+                    handler.engine().setNeedClientAuth(builder.sslClientValidation);
+                    p.addLast("ssl", handler);
+
+                    if (builder.sslSubjectPattern != null) {
+                        p.addLast(new SSLSubjectMatcher(builder.sslSubjectPattern));
+                    }
                 }
 
                 // Decoders
diff --git a/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/standby/server/StandbyServerSync.java b/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/standby/server/StandbyServerSync.java
index 7d5dd46..5917d5e 100644
--- a/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/standby/server/StandbyServerSync.java
+++ b/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/standby/server/StandbyServerSync.java
@@ -63,6 +63,16 @@ public class StandbyServerSync implements StandbyStatusMBean, StateConsumer, Sto
 
         private StandbySegmentReader standbySegmentReader;
 
+        private String sslKeyFile;
+
+        private String sslChainFile;
+
+        private boolean sslValidateClient;
+
+        private String sslKeyPassword;
+
+        private String sslSubjectPattern;
+
         private Builder() {
             // Prevent external instantiation
         }
@@ -119,13 +129,37 @@ public class StandbyServerSync implements StandbyStatusMBean, StateConsumer, Sto
             return this;
         }
 
+        public Builder withSSLKeyFile(String sslKeyFile) {
+            this.sslKeyFile = sslKeyFile;
+            return this;
+        }
+
+        public Builder withSSLKeyPassword(String sslKeyPassword) {
+            this.sslKeyPassword = sslKeyPassword;
+            return this;
+        }
+
+        public Builder withSSLChainFile(String sslChainFile) {
+            this.sslChainFile = sslChainFile;
+            return this;
+        }
+
+        public Builder withSSLClientValidation(boolean sslValidateClient) {
+            this.sslValidateClient = sslValidateClient;
+            return this;
+        }
+
+        public Builder withSSLSubjectPattern(String sslSubjectPattern) {
+            this.sslSubjectPattern = sslSubjectPattern;
+            return this;
+        }
+
         public StandbyServerSync build() {
             checkArgument(port > 0);
             checkArgument(fileStore != null);
             checkArgument(blobChunkSize > 0);
             return new StandbyServerSync(this);
         }
-
     }
 
     private static final Logger log = LoggerFactory.getLogger(StandbyServer.class);
@@ -156,6 +190,16 @@ public class StandbyServerSync implements StandbyStatusMBean, StateConsumer, Sto
 
     private StandbyServer server;
 
+    private final String sslKeyFile;
+
+    private final String sslKeyPassword;
+
+    private final String sslChainFile;
+
+    private final boolean sslValidateClient;
+
+    private final String sslSubjectPattern;
+
     private StandbyServerSync(Builder builder) {
         this.port = builder.port;
         this.fileStore = builder.fileStore;
@@ -166,6 +210,11 @@ public class StandbyServerSync implements StandbyStatusMBean, StateConsumer, Sto
         this.standbyHeadReader = builder.standbyHeadReader;
         this.standbyReferencesReader = builder.standbyReferencesReader;
         this.standbySegmentReader = builder.standbySegmentReader;
+        this.sslKeyFile = builder.sslKeyFile;
+        this.sslKeyPassword = builder.sslKeyPassword;
+        this.sslChainFile = builder.sslChainFile;
+        this.sslValidateClient = builder.sslValidateClient;
+        this.sslSubjectPattern = builder.sslSubjectPattern;
         this.observer = new CommunicationObserver("primary");
 
         final MBeanServer jmxServer = ManagementFactory.getPlatformMBeanServer();
@@ -196,7 +245,7 @@ public class StandbyServerSync implements StandbyStatusMBean, StateConsumer, Sto
         state = STATUS_STARTING;
 
         try {
-            server = StandbyServer.builder(port, this, blobChunkSize)
+            StandbyServer.Builder builder = StandbyServer.builder(port, this, blobChunkSize)
                 .secure(secure)
                 .allowIPRanges(allowedClientIPRanges)
                 .withStateConsumer(this)
@@ -205,7 +254,13 @@ public class StandbyServerSync implements StandbyStatusMBean, StateConsumer, Sto
                 .withStandbyHeadReader(standbyHeadReader)
                 .withStandbyReferencesReader(standbyReferencesReader)
                 .withStandbySegmentReader(standbySegmentReader)
-                .build();
+                .withSSLKeyFile(sslKeyFile)
+                .withSSLKeyPassword(sslKeyPassword)
+                .withSSLChainFile(sslChainFile)
+                .withSSLClientValidation(sslValidateClient)
+                .withSSLSubjectPattern(sslSubjectPattern);
+
+            server = builder.build();
             server.start();
 
             state = STATUS_RUNNING;
diff --git a/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/standby/store/StandbyStoreService.java b/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/standby/store/StandbyStoreService.java
index 89fcda6..8efbb9e 100644
--- a/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/standby/store/StandbyStoreService.java
+++ b/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/standby/store/StandbyStoreService.java
@@ -39,6 +39,7 @@ import org.osgi.service.component.annotations.ConfigurationPolicy;
 import org.osgi.service.component.annotations.Deactivate;
 import org.osgi.service.component.annotations.Reference;
 import org.osgi.service.metatype.annotations.AttributeDefinition;
+import org.osgi.service.metatype.annotations.AttributeType;
 import org.osgi.service.metatype.annotations.Designate;
 import org.osgi.service.metatype.annotations.ObjectClassDefinition;
 import org.osgi.service.metatype.annotations.Option;
@@ -117,6 +118,35 @@ public class StandbyStoreService {
         )
         boolean standby_autoclean() default true;
 
+        @AttributeDefinition(
+                name = "SSL Key File",
+                description = "The file name which contains the SSL key. If this is empty, a key will be generated on-the-fly."
+        )
+        String sslKeyFile();
+
+        @AttributeDefinition(
+            name = "SSL Key Password",
+            description = "Password for the SSL key. If this is empty, an unencrypted key is expected.",
+            type = AttributeType.PASSWORD)
+        String sslKeyPassword() default "";
+
+        @AttributeDefinition(
+                name = "SSL Certificate Chain File",
+                description = "The file name which contains the SSL certificate chain."
+        )
+        String sslChainFile();
+
+        @AttributeDefinition(
+                name = "SSL Validate Client",
+                description = "Validate the client's SSL certificate."
+        )
+        boolean sslValidateClient() default false;
+
+        @AttributeDefinition(
+            name = "SSL Certificate Subject Pattern",
+            description = "The peer certificate's subject must match this pattern in order to be accepted."
+        )
+        String sslSubjectPattern();
     }
 
     @Reference(policy = STATIC, policyOption = GREEDY)
@@ -137,12 +167,12 @@ public class StandbyStoreService {
         String mode = config.mode();
 
         if (mode.equals("primary")) {
-            bootstrapMaster(config, fileStore);
+            bootstrapPrimary(config, fileStore);
             return;
         }
 
         if (mode.equals("standby")) {
-            bootstrapSlave(context, config, fileStore);
+            bootstrapSecondary(context, config, fileStore);
             return;
         }
 
@@ -154,18 +184,31 @@ public class StandbyStoreService {
         closer.close();
     }
 
-    private void bootstrapMaster(Configuration config, FileStore fileStore) {
+    private void bootstrapPrimary(Configuration config, FileStore fileStore) {
         int port = config.port();
         String[] ranges = config.primary_allowed$_$client$_$ip$_$ranges();
         boolean secure = config.secure();
+        String sslKeyFile = config.sslKeyFile();
+        String sslChainFile = config.sslChainFile();
+        boolean sslValidateClient = config.sslValidateClient();
+        String sslSubjectPattern = config.sslSubjectPattern();
 
-        StandbyServerSync standbyServerSync = StandbyServerSync.builder()
+        StandbyServerSync.Builder builder = StandbyServerSync.builder()
             .withPort(port)
             .withFileStore(fileStore)
             .withBlobChunkSize(BLOB_CHUNK_SIZE)
             .withAllowedClientIPRanges(ranges)
-            .withSecureConnection(secure)
-            .build();
+            .withSecureConnection(true)
+            .withSSLKeyFile(sslKeyFile)
+            .withSSLChainFile(sslChainFile)
+            .withSSLClientValidation(sslValidateClient)
+            .withSSLSubjectPattern(sslSubjectPattern);
+
+        if (!"".equals(config.sslKeyPassword())) {
+            builder.withSSLKeyPassword(config.sslKeyPassword());
+        }
+
+        StandbyServerSync standbyServerSync = builder.build();
 
         closer.register(standbyServerSync);
         standbyServerSync.start();
@@ -173,24 +216,35 @@ public class StandbyStoreService {
         log.info("Started primary on port {} with allowed IP ranges {}", port, ranges);
     }
 
-    private void bootstrapSlave(ComponentContext context, Configuration config, FileStore fileStore) {
-        int port = config.port();
-        long interval = config.interval();
-        String host = config.primary_host();
-        boolean secure = config.secure();
-        int readTimeout = config.standby_readtimeout();
-        boolean clean = config.standby_autoclean();
+    private void bootstrapSecondary(ComponentContext context, Configuration config, FileStore fileStore) {
+
+        StandbyClientSync.Builder builder = StandbyClientSync.builder()
+            .withHost(config.primary_host())
+            .withPort(config.port())
+            .withFileStore(fileStore)
+            .withSecureConnection(config.secure())
+            .withReadTimeoutMs(config.standby_readtimeout())
+            .withAutoClean(config.standby_autoclean())
+            .withSpoolFolder(new File(StandardSystemProperty.JAVA_IO_TMPDIR.value()))
+            .withSecureConnection(config.secure())
+            .withSSLKeyFile(config.sslKeyFile())
+            .withSSLChainFile(config.sslChainFile())
+            .withSSLSubjectPattern(config.sslSubjectPattern());
+
+        if (!"".equals(config.sslKeyPassword())) {
+            builder.withSSLKeyPassword(config.sslKeyPassword());
+        }
 
-        StandbyClientSync standbyClientSync = new StandbyClientSync(host, port, fileStore, secure, readTimeout, clean, new File(StandardSystemProperty.JAVA_IO_TMPDIR.value()));
+        StandbyClientSync standbyClientSync = builder.build();
         closer.register(standbyClientSync);
 
         Dictionary<Object, Object> dictionary = new Hashtable<Object, Object>();
-        dictionary.put("scheduler.period", interval);
+        dictionary.put("scheduler.period", config.interval());
         dictionary.put("scheduler.concurrent", false);
         ServiceRegistration registration = context.getBundleContext().registerService(Runnable.class.getName(), standbyClientSync, dictionary);
         closer.register(registration::unregister);
 
-        log.info("Started standby on port {} with {}s sync frequency", port, interval);
+        log.info("Started standby on port {} with {}s sync frequency", config.port(), config.interval());
     }
 
 }
diff --git a/oak-segment-tar/src/test/java/org/apache/jackrabbit/oak/segment/standby/BrokenNetworkIT.java b/oak-segment-tar/src/test/java/org/apache/jackrabbit/oak/segment/standby/BrokenNetworkIT.java
index cb7dce2..1d90cb5 100644
--- a/oak-segment-tar/src/test/java/org/apache/jackrabbit/oak/segment/standby/BrokenNetworkIT.java
+++ b/oak-segment-tar/src/test/java/org/apache/jackrabbit/oak/segment/standby/BrokenNetworkIT.java
@@ -73,7 +73,15 @@ public class BrokenNetworkIT extends TestBase {
                 .withBlobChunkSize(MB)
                 .withSecureConnection(false)
                 .build();
-            StandbyClientSync clientSync = new StandbyClientSync(getServerHost(), serverPort.getPort(), clientStore, false, getClientTimeout(), false, folder.newFolder());
+            StandbyClientSync clientSync = StandbyClientSync.builder()
+                .withHost(getServerHost())
+                .withPort(serverPort.getPort())
+                .withFileStore(clientStore)
+                .withSecureConnection(false)
+                .withReadTimeoutMs(getClientTimeout())
+                .withAutoClean(false)
+                .withSpoolFolder(folder.newFolder())
+                .build()
         ) {
             serverSync.start();
             clientSync.run();
@@ -98,7 +106,15 @@ public class BrokenNetworkIT extends TestBase {
                 .withBlobChunkSize(MB)
                 .withSecureConnection(true)
                 .build();
-            StandbyClientSync clientSync = new StandbyClientSync(getServerHost(), serverPort.getPort(), storeC, true, getClientTimeout(), false, folder.newFolder());
+            StandbyClientSync clientSync = StandbyClientSync.builder()
+                .withHost(getServerHost())
+                .withPort(serverPort.getPort())
+                .withFileStore(storeC)
+                .withSecureConnection(true)
+                .withReadTimeoutMs(getClientTimeout())
+                .withAutoClean(false)
+                .withSpoolFolder(folder.newFolder())
+                .build()
         ) {
             serverSync.start();
             clientSync.run();
@@ -179,7 +195,15 @@ public class BrokenNetworkIT extends TestBase {
 
             try (
                 NetworkErrorProxy ignored = new NetworkErrorProxy(proxyPort.getPort(), getServerHost(), serverPort.getPort(), flipPosition, skipPosition, skipBytes);
-                StandbyClientSync clientSync = new StandbyClientSync(getServerHost(), proxyPort.getPort(), clientStore, ssl, getClientTimeout(), false, spoolFolder)
+                StandbyClientSync clientSync = StandbyClientSync.builder()
+                    .withHost(getServerHost())
+                    .withPort(proxyPort.getPort())
+                    .withFileStore(clientStore)
+                    .withSecureConnection(ssl)
+                    .withReadTimeoutMs(getClientTimeout())
+                    .withAutoClean(false)
+                    .withSpoolFolder(spoolFolder)
+                    .build()
             ) {
                 clientSync.run();
             }
@@ -191,10 +215,19 @@ public class BrokenNetworkIT extends TestBase {
                 serverStore.flush();
             }
 
-            try (StandbyClientSync clientSync = new StandbyClientSync(getServerHost(), serverPort.getPort(), clientStore, ssl, getClientTimeout(), false, spoolFolder)) {
-                clientSync.run();
+            try (StandbyClientSync clientSync = StandbyClientSync.builder()
+                 .withHost(getServerHost())
+                 .withPort(serverPort.getPort())
+                 .withFileStore(clientStore)
+                 .withSecureConnection(ssl)
+                 .withReadTimeoutMs(getClientTimeout())
+                 .withAutoClean(false)
+                 .withSpoolFolder(spoolFolder)
+                 .build()
+            ) {
+                 clientSync.run();
             }
-        }
+    }
 
         assertEquals("stores are not equal", serverStore.getHead(), clientStore.getHead());
     }
diff --git a/oak-segment-tar/src/test/java/org/apache/jackrabbit/oak/segment/standby/DataStoreTestBase.java b/oak-segment-tar/src/test/java/org/apache/jackrabbit/oak/segment/standby/DataStoreTestBase.java
index 28a845c..7b4b6e9 100644
--- a/oak-segment-tar/src/test/java/org/apache/jackrabbit/oak/segment/standby/DataStoreTestBase.java
+++ b/oak-segment-tar/src/test/java/org/apache/jackrabbit/oak/segment/standby/DataStoreTestBase.java
@@ -155,7 +155,15 @@ public abstract class DataStoreTestBase extends TestBase {
                 .withFileStore(primary)
                 .withBlobChunkSize(MB)
                 .build();
-            StandbyClientSync cl = new StandbyClientSync(getServerHost(), serverPort.getPort(), secondary, false, 4_000, false, spoolFolder)
+            StandbyClientSync cl = StandbyClientSync.builder()
+                .withHost(getServerHost())
+                .withPort(serverPort.getPort())
+                .withFileStore(secondary)
+                .withSecureConnection(false)
+                .withReadTimeoutMs(4_000)
+                .withAutoClean(false)
+                .withSpoolFolder(spoolFolder)
+                .build()
         ) {
             serverSync.start();
             // no persisted head on primary
@@ -172,7 +180,15 @@ public abstract class DataStoreTestBase extends TestBase {
                 .withFileStore(primary)
                 .withBlobChunkSize(MB)
                 .build();
-            StandbyClientSync cl = new StandbyClientSync(getServerHost(), serverPort.getPort(), secondary, false, 4_000, false, spoolFolder)
+            StandbyClientSync cl = StandbyClientSync.builder()
+                .withHost(getServerHost())
+                .withPort(serverPort.getPort())
+                .withFileStore(secondary)
+                .withSecureConnection(false)
+                .withReadTimeoutMs(4_000)
+                .withAutoClean(false)
+                .withSpoolFolder(spoolFolder)
+                .build()
         ) {
             serverSync.start();
             // this time persisted head will be available on primary
@@ -213,7 +229,15 @@ public abstract class DataStoreTestBase extends TestBase {
                 .withFileStore(primary)
                 .withBlobChunkSize(MB)
                 .build();
-            StandbyClientSync cl = new StandbyClientSync(getServerHost(), serverPort.getPort(), secondary, false, getClientTimeout(), false, folder.newFolder())
+            StandbyClientSync cl = StandbyClientSync.builder()
+                .withHost(getServerHost())
+                .withPort(serverPort.getPort())
+                .withFileStore(secondary)
+                .withSecureConnection(false)
+                .withReadTimeoutMs(getClientTimeout())
+                .withAutoClean(false)
+                .withSpoolFolder(folder.newFolder())
+                .build()
         ) {
             serverSync.start();
             primary.flush();
@@ -259,7 +283,15 @@ public abstract class DataStoreTestBase extends TestBase {
                 .withFileStore(primary)
                 .withBlobChunkSize(8 * MB)
                 .build();
-            StandbyClientSync cl = new StandbyClientSync(getServerHost(), serverPort.getPort(), secondary, false, 2 * 60 * 1000, false, folder.newFolder())
+            StandbyClientSync cl = StandbyClientSync.builder()
+                .withHost(getServerHost())
+                .withPort(serverPort.getPort())
+                .withFileStore(secondary)
+                .withSecureConnection(false)
+                .withReadTimeoutMs(2 * 60 * 1000)
+                .withAutoClean(false)
+                .withSpoolFolder(folder.newFolder())
+                .build()
         ) {
             serverSync.start();
             primary.flush();
@@ -302,7 +334,15 @@ public abstract class DataStoreTestBase extends TestBase {
                 .withFileStore(primary)
                 .withBlobChunkSize(MB)
                 .build();
-            StandbyClientSync clientSync = new StandbyClientSync(getServerHost(), serverPort.getPort(), secondary, false, getClientTimeout(), false, folder.newFolder())
+            StandbyClientSync clientSync = StandbyClientSync.builder()
+                .withHost(getServerHost())
+                .withPort(serverPort.getPort())
+                .withFileStore(secondary)
+                .withSecureConnection(false)
+                .withReadTimeoutMs(getClientTimeout())
+                .withAutoClean(false)
+                .withSpoolFolder(folder.newFolder())
+                .build()
         ) {
             serverSync.start();
 
@@ -376,7 +416,15 @@ public abstract class DataStoreTestBase extends TestBase {
 
             try (
                 NetworkErrorProxy ignored = new NetworkErrorProxy(proxyPort.getPort(), getServerHost(), serverPort.getPort(), flipPosition, skipPosition, skipBytes);
-                StandbyClientSync clientSync = new StandbyClientSync(getServerHost(), proxyPort.getPort(), secondary, false, getClientTimeout(), false, spoolFolder)
+                StandbyClientSync clientSync = StandbyClientSync.builder()
+                    .withHost(getServerHost())
+                    .withPort(proxyPort.getPort())
+                    .withFileStore(secondary)
+                    .withSecureConnection(false)
+                    .withReadTimeoutMs(getClientTimeout())
+                    .withAutoClean(false)
+                    .withSpoolFolder(spoolFolder)
+                    .build()
             ) {
                 clientSync.run();
             }
@@ -391,7 +439,16 @@ public abstract class DataStoreTestBase extends TestBase {
                 primary.flush();
             }
 
-            try (StandbyClientSync clientSync = new StandbyClientSync(getServerHost(), serverPort.getPort(), secondary, false, getClientTimeout(), false, spoolFolder)) {
+            try (StandbyClientSync clientSync = StandbyClientSync.builder()
+                .withHost(getServerHost())
+                .withPort(serverPort.getPort())
+                .withFileStore(secondary)
+                .withSecureConnection(false)
+                .withReadTimeoutMs(getClientTimeout())
+                .withAutoClean(false)
+                .withSpoolFolder(spoolFolder)
+                .build()
+            ) {
                 clientSync.run();
             }
         }
diff --git a/oak-segment-tar/src/test/java/org/apache/jackrabbit/oak/segment/standby/FailoverIPRangeIT.java b/oak-segment-tar/src/test/java/org/apache/jackrabbit/oak/segment/standby/FailoverIPRangeIT.java
index 3deb539..c50983d 100644
--- a/oak-segment-tar/src/test/java/org/apache/jackrabbit/oak/segment/standby/FailoverIPRangeIT.java
+++ b/oak-segment-tar/src/test/java/org/apache/jackrabbit/oak/segment/standby/FailoverIPRangeIT.java
@@ -163,7 +163,15 @@ public class FailoverIPRangeIT extends TestBase {
                 .withBlobChunkSize(MB)
                 .withAllowedClientIPRanges(ipRanges)
                 .build();
-            StandbyClientSync clientSync = new StandbyClientSync(host, serverPort.getPort(), storeC, false, getClientTimeout(), false, folder.newFolder())
+            StandbyClientSync clientSync = StandbyClientSync.builder()
+                .withHost(host)
+                .withPort(serverPort.getPort())
+                .withFileStore(storeC)
+                .withSecureConnection(false)
+                .withReadTimeoutMs(getClientTimeout())
+                .withAutoClean(false)
+                .withSpoolFolder(folder.newFolder())
+                .build()
         ) {
             serverSync.start();
             addTestContent(store, "server");
diff --git a/oak-segment-tar/src/test/java/org/apache/jackrabbit/oak/segment/standby/FailoverMultipleClientsTestIT.java b/oak-segment-tar/src/test/java/org/apache/jackrabbit/oak/segment/standby/FailoverMultipleClientsTestIT.java
index 0e52b53..fc1d373 100644
--- a/oak-segment-tar/src/test/java/org/apache/jackrabbit/oak/segment/standby/FailoverMultipleClientsTestIT.java
+++ b/oak-segment-tar/src/test/java/org/apache/jackrabbit/oak/segment/standby/FailoverMultipleClientsTestIT.java
@@ -68,8 +68,24 @@ public class FailoverMultipleClientsTestIT extends TestBase {
                 .withFileStore(storeS)
                 .withBlobChunkSize(MB)
                 .build();
-            StandbyClientSync cl1 = new StandbyClientSync(getServerHost(), serverPort.getPort(), storeC, false, getClientTimeout(), false, folder.newFolder());
-            StandbyClientSync cl2 = new StandbyClientSync(getServerHost(), serverPort.getPort(), storeC2, false, getClientTimeout(), false, folder.newFolder())
+            StandbyClientSync cl1 = StandbyClientSync.builder()
+                .withHost(getServerHost())
+                .withPort(serverPort.getPort())
+                .withFileStore(storeC)
+                .withSecureConnection(false)
+                .withReadTimeoutMs(getClientTimeout())
+                .withAutoClean(false)
+                .withSpoolFolder(folder.newFolder())
+                .build();
+            StandbyClientSync cl2 = StandbyClientSync.builder()
+                .withHost(getServerHost())
+                .withPort(serverPort.getPort())
+                .withFileStore(storeC2)
+                .withSecureConnection(false)
+                .withReadTimeoutMs(getClientTimeout())
+                .withAutoClean(false)
+                .withSpoolFolder(folder.newFolder())
+                .build()
         ) {
             serverSync.start();
             SegmentTestUtils.addTestContent(store, "server");
diff --git a/oak-segment-tar/src/test/java/org/apache/jackrabbit/oak/segment/standby/FailoverSslTestIT.java b/oak-segment-tar/src/test/java/org/apache/jackrabbit/oak/segment/standby/FailoverSslTestIT.java
index f45f4a0..a13d679 100644
--- a/oak-segment-tar/src/test/java/org/apache/jackrabbit/oak/segment/standby/FailoverSslTestIT.java
+++ b/oak-segment-tar/src/test/java/org/apache/jackrabbit/oak/segment/standby/FailoverSslTestIT.java
@@ -64,7 +64,15 @@ public class FailoverSslTestIT extends TestBase {
                 .withBlobChunkSize(MB)
                 .withSecureConnection(true)
                 .build();
-            StandbyClientSync clientSync = new StandbyClientSync(getServerHost(), serverPort.getPort(), storeC, true, getClientTimeout(), false, folder.newFolder());
+            StandbyClientSync clientSync = StandbyClientSync.builder()
+                .withHost(getServerHost())
+                .withPort(serverPort.getPort())
+                .withFileStore(storeC)
+                .withSecureConnection(true)
+                .withReadTimeoutMs(getClientTimeout())
+                .withAutoClean(false)
+                .withSpoolFolder(folder.newFolder())
+                .build()
         ) {
             assertTrue(synchronizeAndCompareHead(serverSync, clientSync));
         }
@@ -81,7 +89,15 @@ public class FailoverSslTestIT extends TestBase {
                 .withBlobChunkSize(MB)
                 .withSecureConnection(true)
                 .build();
-            StandbyClientSync clientSync = new StandbyClientSync(getServerHost(), serverPort.getPort(), storeC, false, getClientTimeout(), false, folder.newFolder());
+            StandbyClientSync clientSync = StandbyClientSync.builder()
+                .withHost(getServerHost())
+                .withPort(serverPort.getPort())
+                .withFileStore(storeC)
+                .withSecureConnection(false)
+                .withReadTimeoutMs(getClientTimeout())
+                .withAutoClean(false)
+                .withSpoolFolder(folder.newFolder())
+                .build()
         ) {
             assertFalse(synchronizeAndCompareHead(serverSync, clientSync));
         }
@@ -97,7 +113,15 @@ public class FailoverSslTestIT extends TestBase {
                 .withFileStore(storeS)
                 .withBlobChunkSize(MB)
                 .build();
-            StandbyClientSync clientSync = new StandbyClientSync(getServerHost(), serverPort.getPort(), storeC, true, getClientTimeout(), false, folder.newFolder());
+            StandbyClientSync clientSync = StandbyClientSync.builder()
+                .withHost(getServerHost())
+                .withPort(serverPort.getPort())
+                .withFileStore(storeC)
+                .withSecureConnection(true)
+                .withReadTimeoutMs(getClientTimeout())
+                .withAutoClean(false)
+                .withSpoolFolder(folder.newFolder())
+                .build()
         ) {
             assertFalse(synchronizeAndCompareHead(serverSync, clientSync));
         }
diff --git a/oak-segment-tar/src/test/java/org/apache/jackrabbit/oak/segment/standby/MBeanIT.java b/oak-segment-tar/src/test/java/org/apache/jackrabbit/oak/segment/standby/MBeanIT.java
index 7a138f1..0357ce1 100644
--- a/oak-segment-tar/src/test/java/org/apache/jackrabbit/oak/segment/standby/MBeanIT.java
+++ b/oak-segment-tar/src/test/java/org/apache/jackrabbit/oak/segment/standby/MBeanIT.java
@@ -99,7 +99,16 @@ public class MBeanIT extends TestBase {
     public void testClientEmptyConfigNoServer() throws Exception {
         MBeanServer jmxServer = ManagementFactory.getPlatformMBeanServer();
         ObjectName status = new ObjectName(StandbyStatusMBean.JMX_NAME + ",id=*");
-        try (StandbyClientSync clientSync = new StandbyClientSync(getServerHost(), serverPort.getPort(), clientFileStore.fileStore(), false, getClientTimeout(), false, folder.newFolder())) {
+        try (StandbyClientSync clientSync = StandbyClientSync.builder()
+            .withHost(getServerHost())
+            .withPort(serverPort.getPort())
+            .withFileStore(clientFileStore.fileStore())
+            .withSecureConnection(false)
+            .withReadTimeoutMs(getClientTimeout())
+            .withAutoClean(false)
+            .withSpoolFolder(folder.newFolder())
+            .build();
+        ) {
             clientSync.start();
             clientSync.run();
 
@@ -136,7 +145,16 @@ public class MBeanIT extends TestBase {
         System.setProperty(StandbyClientSync.CLIENT_ID_PROPERTY_NAME, "Foo");
         MBeanServer jmxServer = ManagementFactory.getPlatformMBeanServer();
         ObjectName status;
-        try (StandbyClientSync clientSync = new StandbyClientSync(getServerHost(), serverPort.getPort(), clientFileStore.fileStore(), false, getClientTimeout(), false, folder.newFolder())) {
+        try (StandbyClientSync clientSync = StandbyClientSync.builder()
+            .withHost(getServerHost())
+            .withPort(serverPort.getPort())
+            .withFileStore(clientFileStore.fileStore())
+            .withSecureConnection(false)
+            .withReadTimeoutMs(getClientTimeout())
+            .withAutoClean(false)
+            .withSpoolFolder(folder.newFolder())
+            .build();
+        ) {
             clientSync.start();
             clientSync.run();
 
@@ -164,7 +182,15 @@ public class MBeanIT extends TestBase {
                 .withFileStore(serverFileStore.fileStore())
                 .withBlobChunkSize(MB)
                 .build();
-            StandbyClientSync clientSync = new StandbyClientSync(getServerHost(), serverPort.getPort(), clientFileStore.fileStore(), false, getClientTimeout(), false, folder.newFolder())
+            StandbyClientSync clientSync = StandbyClientSync.builder()
+                .withHost(getServerHost())
+                .withPort(serverPort.getPort())
+                .withFileStore(clientFileStore.fileStore())
+                .withSecureConnection(false)
+                .withReadTimeoutMs(getClientTimeout())
+                .withAutoClean(false)
+                .withSpoolFolder(folder.newFolder())
+                .build()
         ) {
             serverSync.start();
             serverFileStore.fileStore().flush();
diff --git a/oak-segment-tar/src/test/java/org/apache/jackrabbit/oak/segment/standby/RecoverTestIT.java b/oak-segment-tar/src/test/java/org/apache/jackrabbit/oak/segment/standby/RecoverTestIT.java
index f760bb7..d2ced58 100644
--- a/oak-segment-tar/src/test/java/org/apache/jackrabbit/oak/segment/standby/RecoverTestIT.java
+++ b/oak-segment-tar/src/test/java/org/apache/jackrabbit/oak/segment/standby/RecoverTestIT.java
@@ -67,7 +67,15 @@ public class RecoverTestIT extends TestBase {
                 .withFileStore(storeS)
                 .withBlobChunkSize(MB)
                 .build();
-            StandbyClientSync cl = new StandbyClientSync(getServerHost(), serverPort.getPort(), storeC, false, getClientTimeout(), false, folder.newFolder())
+            StandbyClientSync cl = StandbyClientSync.builder()
+                .withHost(getServerHost())
+                .withPort(serverPort.getPort())
+                .withFileStore(storeC)
+                .withSecureConnection(false)
+                .withReadTimeoutMs(getClientTimeout())
+                .withAutoClean(false)
+                .withSpoolFolder(folder.newFolder())
+                .build()
         ) {
             serverSync.start();
             store = SegmentNodeStoreBuilders.builder(storeS).build();
diff --git a/oak-segment-tar/src/test/java/org/apache/jackrabbit/oak/segment/standby/StandbySegmentBlobTestIT.java b/oak-segment-tar/src/test/java/org/apache/jackrabbit/oak/segment/standby/StandbySegmentBlobTestIT.java
index 567a697..2fad0ea 100644
--- a/oak-segment-tar/src/test/java/org/apache/jackrabbit/oak/segment/standby/StandbySegmentBlobTestIT.java
+++ b/oak-segment-tar/src/test/java/org/apache/jackrabbit/oak/segment/standby/StandbySegmentBlobTestIT.java
@@ -99,7 +99,15 @@ public class StandbySegmentBlobTestIT extends TestBase {
                 .withFileStore(primary)
                 .withBlobChunkSize(MB)
                 .build();
-            StandbyClientSync clientSync = new StandbyClientSync(getServerHost(), serverPort.getPort(), secondary, false, getClientTimeout(), false, folder.newFolder())
+            StandbyClientSync clientSync = StandbyClientSync.builder()
+                .withHost(getServerHost())
+                .withPort(serverPort.getPort())
+                .withFileStore(secondary)
+                .withSecureConnection(false)
+                .withReadTimeoutMs(getClientTimeout())
+                .withAutoClean(false)
+                .withSpoolFolder(folder.newFolder())
+                .build()
         ) {
             serverSync.start();
             addTestContent(store, "server");
diff --git a/oak-segment-tar/src/test/java/org/apache/jackrabbit/oak/segment/standby/StandbyTestIT.java b/oak-segment-tar/src/test/java/org/apache/jackrabbit/oak/segment/standby/StandbyTestIT.java
index 892670b..b437b98 100644
--- a/oak-segment-tar/src/test/java/org/apache/jackrabbit/oak/segment/standby/StandbyTestIT.java
+++ b/oak-segment-tar/src/test/java/org/apache/jackrabbit/oak/segment/standby/StandbyTestIT.java
@@ -21,17 +21,23 @@ package org.apache.jackrabbit.oak.segment.standby;
 
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 
 import java.io.ByteArrayInputStream;
 import java.io.File;
+import java.io.FileOutputStream;
+import java.security.KeyStore;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateFactory;
 import java.util.Random;
 
 import com.google.common.io.ByteStreams;
 import org.apache.jackrabbit.oak.api.Blob;
 import org.apache.jackrabbit.oak.api.PropertyState;
 import org.apache.jackrabbit.oak.api.Type;
+import org.apache.jackrabbit.oak.commons.IOUtils;
 import org.apache.jackrabbit.oak.commons.junit.TemporaryPort;
 import org.apache.jackrabbit.oak.segment.SegmentNodeStoreBuilders;
 import org.apache.jackrabbit.oak.segment.file.FileStore;
@@ -42,6 +48,7 @@ import org.apache.jackrabbit.oak.spi.commit.CommitInfo;
 import org.apache.jackrabbit.oak.spi.commit.EmptyHook;
 import org.apache.jackrabbit.oak.spi.state.NodeBuilder;
 import org.apache.jackrabbit.oak.spi.state.NodeStore;
+import org.junit.Ignore;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.RuleChain;
@@ -63,6 +70,9 @@ public class StandbyTestIT extends TestBase {
             .around(serverFileStore)
             .around(clientFileStore);
 
+    /**
+     * This test syncs a few segments over an unencrypted connection.
+     */
     @Test
     public void testSync() throws Exception {
         int blobSize = 5 * MB;
@@ -76,7 +86,68 @@ public class StandbyTestIT extends TestBase {
                 .withFileStore(primary)
                 .withBlobChunkSize(MB)
                 .build();
-            StandbyClientSync clientSync = new StandbyClientSync(getServerHost(), serverPort.getPort(), secondary, false, getClientTimeout(), false, folder.newFolder())
+            StandbyClientSync clientSync = StandbyClientSync.builder()
+                .withHost(getServerHost())
+                .withPort(serverPort.getPort())
+                .withFileStore(secondary)
+                .withSecureConnection(false)
+                .withReadTimeoutMs(getClientTimeout())
+                .withAutoClean(false)
+                .withSpoolFolder(folder.newFolder())
+                .build()
+        ) {
+            serverSync.start();
+            byte[] data = addTestContent(store, "server", blobSize, 150);
+            primary.flush();
+
+            clientSync.run();
+
+            assertEquals(primary.getHead(), secondary.getHead());
+
+            assertTrue(primary.getStats().getApproximateSize() > blobSize);
+            assertTrue(secondary.getStats().getApproximateSize() > blobSize);
+
+            PropertyState ps = secondary.getHead().getChildNode("root")
+                .getChildNode("server").getProperty("testBlob");
+            assertNotNull(ps);
+            assertEquals(Type.BINARY.tag(), ps.getType().tag());
+            Blob b = ps.getValue(Type.BINARY);
+            assertEquals(blobSize, b.length());
+
+            byte[] testData = new byte[blobSize];
+            ByteStreams.readFully(b.getNewStream(), testData);
+            assertArrayEquals(data, testData);
+        }
+    }
+
+    /**
+     * This test syncs a few segments over an encrypted connection.
+     * Both server and client certificates are generated on-the-fly.
+     */
+    @Test
+    @Ignore("This test takes ~4s and is therefore disabled by default")
+    public void testSyncSSL() throws Exception {
+        int blobSize = 5 * MB;
+        FileStore primary = serverFileStore.fileStore();
+        FileStore secondary = clientFileStore.fileStore();
+
+        NodeStore store = SegmentNodeStoreBuilders.builder(primary).build();
+        try (
+            StandbyServerSync serverSync = StandbyServerSync.builder()
+                .withPort(serverPort.getPort())
+                .withFileStore(primary)
+                .withBlobChunkSize(MB)
+                .withSecureConnection(true)
+                .build();
+            StandbyClientSync clientSync = StandbyClientSync.builder()
+                .withHost(getServerHost())
+                .withPort(serverPort.getPort())
+                .withFileStore(secondary)
+                .withSecureConnection(true)
+                .withReadTimeoutMs(getClientTimeout())
+                .withAutoClean(false)
+                .withSpoolFolder(folder.newFolder())
+                .build()
         ) {
             serverSync.start();
             byte[] data = addTestContent(store, "server", blobSize, 150);
@@ -103,6 +174,735 @@ public class StandbyTestIT extends TestBase {
     }
 
     /**
+     * This test syncs a few segments over an encrypted connection.
+     * The server has a configured certificate which can be validated with the truststore.
+     * The server does not validate the client certificate.
+     * The client creates its certificate on-the-fly.
+     */
+    @Test
+    @Ignore("This test takes ~2s and is therefore disabled by default")
+    public void testSyncSSLNoClientValidation() throws Exception {
+        int blobSize = 5 * MB;
+        FileStore primary = serverFileStore.fileStore();
+        FileStore secondary = clientFileStore.fileStore();
+
+        FileOutputStream fos;
+
+        File serverKeyFile = folder.newFile();
+        fos = new FileOutputStream(serverKeyFile);
+        IOUtils.writeString(fos, serverKey);
+        fos.close();
+
+        File serverCertFile = folder.newFile();
+        fos = new FileOutputStream(serverCertFile);
+        IOUtils.writeString(fos, serverCert);
+        fos.close();
+
+        File keyStoreFile = folder.newFile();
+        KeyStore keyStore = KeyStore.getInstance("JKS");
+        keyStore.load(null, "changeit".toCharArray());
+        Certificate c = CertificateFactory.getInstance("X.509").generateCertificate(new ByteArrayInputStream(caCert.getBytes()));
+        keyStore.setCertificateEntry("the-ca-cert", c);
+        keyStore.store(new FileOutputStream(keyStoreFile), "changeit".toCharArray());
+        System.setProperty("javax.net.ssl.trustStore", keyStoreFile.getAbsolutePath());
+
+        NodeStore store = SegmentNodeStoreBuilders.builder(primary).build();
+        try (
+            StandbyServerSync serverSync = StandbyServerSync.builder()
+                .withPort(serverPort.getPort())
+                .withFileStore(primary)
+                .withBlobChunkSize(MB)
+                .withSecureConnection(true)
+                .withSSLKeyFile(serverKeyFile.getAbsolutePath())
+                .withSSLChainFile(serverCertFile.getAbsolutePath())
+                .withSSLClientValidation(false)
+                .build();
+            StandbyClientSync clientSync = StandbyClientSync.builder()
+                .withHost(getServerHost())
+                .withPort(serverPort.getPort())
+                .withFileStore(secondary)
+                .withSecureConnection(true)
+                .withReadTimeoutMs(getClientTimeout())
+                .withAutoClean(false)
+                .withSpoolFolder(folder.newFolder())
+                .build()
+        ) {
+            serverSync.start();
+            byte[] data = addTestContent(store, "server", blobSize, 1);
+            primary.flush();
+
+            clientSync.run();
+
+            assertEquals(primary.getHead(), secondary.getHead());
+
+            assertTrue(primary.getStats().getApproximateSize() > blobSize);
+            assertTrue(secondary.getStats().getApproximateSize() > blobSize);
+
+            PropertyState ps = secondary.getHead().getChildNode("root")
+                .getChildNode("server").getProperty("testBlob");
+            assertNotNull(ps);
+            assertEquals(Type.BINARY.tag(), ps.getType().tag());
+            Blob b = ps.getValue(Type.BINARY);
+            assertEquals(blobSize, b.length());
+
+            byte[] testData = new byte[blobSize];
+            ByteStreams.readFully(b.getNewStream(), testData);
+            assertArrayEquals(data, testData);
+        }
+    }
+
+    /**
+     * This test syncs a few segments over an encrypted connection.
+     * The server has a configured certificate which can be validated with the truststore.
+     * The server validates the client certificate.
+     * The client has a configured certificate which can be validated with the truststore.
+     */
+    @Test
+    @Ignore("This test takes ~2s and is therefore disabled by default")
+    public void testSyncSSLValidClient() throws Exception {
+        int blobSize = 5 * MB;
+        FileStore primary = serverFileStore.fileStore();
+        FileStore secondary = clientFileStore.fileStore();
+
+        FileOutputStream fos;
+
+        File serverKeyFile = folder.newFile();
+        fos = new FileOutputStream(serverKeyFile);
+        IOUtils.writeString(fos, serverKey);
+        fos.close();
+
+        File clientKeyFile = folder.newFile();
+        fos = new FileOutputStream(clientKeyFile);
+        IOUtils.writeString(fos, clientKey);
+        fos.close();
+
+        File serverCertFile = folder.newFile();
+        fos = new FileOutputStream(serverCertFile);
+        IOUtils.writeString(fos, serverCert);
+        fos.close();
+
+        File clientCertFile = folder.newFile();
+        fos = new FileOutputStream(clientCertFile);
+        IOUtils.writeString(fos, clientCert);
+        fos.close();
+
+        File keyStoreFile = folder.newFile();
+        KeyStore keyStore = KeyStore.getInstance("JKS");
+        keyStore.load(null, "changeit".toCharArray());
+        Certificate c = CertificateFactory.getInstance("X.509").generateCertificate(new ByteArrayInputStream(caCert.getBytes()));
+        keyStore.setCertificateEntry("the-ca-cert", c);
+        keyStore.store(new FileOutputStream(keyStoreFile), "changeit".toCharArray());
+        System.setProperty("javax.net.ssl.trustStore", keyStoreFile.getAbsolutePath());
+
+        NodeStore store = SegmentNodeStoreBuilders.builder(primary).build();
+        try (
+            StandbyServerSync serverSync = StandbyServerSync.builder()
+                .withPort(serverPort.getPort())
+                .withFileStore(primary)
+                .withBlobChunkSize(MB)
+                .withSecureConnection(true)
+                .withSSLKeyFile(serverKeyFile.getAbsolutePath())
+                .withSSLChainFile(serverCertFile.getAbsolutePath())
+                .withSSLClientValidation(true)
+                .build();
+            StandbyClientSync clientSync = StandbyClientSync.builder()
+                .withHost(getServerHost())
+                .withPort(serverPort.getPort())
+                .withFileStore(secondary)
+                .withSecureConnection(true)
+                .withReadTimeoutMs(getClientTimeout())
+                .withAutoClean(false)
+                .withSpoolFolder(folder.newFolder())
+                .withSSLKeyFile(clientKeyFile.getAbsolutePath())
+                .withSSLChainFile(clientCertFile.getAbsolutePath())
+                .build()
+        ) {
+            serverSync.start();
+            byte[] data = addTestContent(store, "server", blobSize, 1);
+            primary.flush();
+
+            clientSync.run();
+
+            assertEquals(primary.getHead(), secondary.getHead());
+
+            assertTrue(primary.getStats().getApproximateSize() > blobSize);
+            assertTrue(secondary.getStats().getApproximateSize() > blobSize);
+
+            PropertyState ps = secondary.getHead().getChildNode("root")
+                .getChildNode("server").getProperty("testBlob");
+            assertNotNull(ps);
+            assertEquals(Type.BINARY.tag(), ps.getType().tag());
+            Blob b = ps.getValue(Type.BINARY);
+            assertEquals(blobSize, b.length());
+
+            byte[] testData = new byte[blobSize];
+            ByteStreams.readFully(b.getNewStream(), testData);
+            assertArrayEquals(data, testData);
+        }
+    }
+
+    /**
+     * This test syncs a few segments over an encrypted connection.
+     * The server has a configured certificate which can be validated with the truststore.
+     * The server validates the client certificate.
+     * The client has a configured certificate which can be validated with the truststore.
+     * All the keys are encrypted.
+     */
+    @Test
+    @Ignore("This test takes ~2s and is therefore disabled by default")
+    public void testSyncSSLValidClientEncryptedKeys() throws Exception {
+        int blobSize = 5 * MB;
+        FileStore primary = serverFileStore.fileStore();
+        FileStore secondary = clientFileStore.fileStore();
+
+        FileOutputStream fos;
+
+        File serverKeyFile = folder.newFile();
+        fos = new FileOutputStream(serverKeyFile);
+        IOUtils.writeString(fos, encryptedServerKey);
+        fos.close();
+
+        File clientKeyFile = folder.newFile();
+        fos = new FileOutputStream(clientKeyFile);
+        IOUtils.writeString(fos, encryptedClientKey);
+        fos.close();
+
+        File serverCertFile = folder.newFile();
+        fos = new FileOutputStream(serverCertFile);
+        IOUtils.writeString(fos, serverCert);
+        fos.close();
+
+        File clientCertFile = folder.newFile();
+        fos = new FileOutputStream(clientCertFile);
+        IOUtils.writeString(fos, clientCert);
+        fos.close();
+
+        File keyStoreFile = folder.newFile();
+        KeyStore keyStore = KeyStore.getInstance("JKS");
+        keyStore.load(null, "changeit".toCharArray());
+        Certificate c = CertificateFactory.getInstance("X.509").generateCertificate(new ByteArrayInputStream(caCert.getBytes()));
+        keyStore.setCertificateEntry("the-ca-cert", c);
+        keyStore.store(new FileOutputStream(keyStoreFile), "changeit".toCharArray());
+        System.setProperty("javax.net.ssl.trustStore", keyStoreFile.getAbsolutePath());
+
+        NodeStore store = SegmentNodeStoreBuilders.builder(primary).build();
+        try (
+            StandbyServerSync serverSync = StandbyServerSync.builder()
+                .withPort(serverPort.getPort())
+                .withFileStore(primary)
+                .withBlobChunkSize(MB)
+                .withSecureConnection(true)
+                .withSSLKeyFile(serverKeyFile.getAbsolutePath())
+                .withSSLKeyPassword(secretPassword)
+                .withSSLChainFile(serverCertFile.getAbsolutePath())
+                .withSSLClientValidation(true)
+                .build();
+            StandbyClientSync clientSync = StandbyClientSync.builder()
+                .withHost(getServerHost())
+                .withPort(serverPort.getPort())
+                .withFileStore(secondary)
+                .withSecureConnection(true)
+                .withReadTimeoutMs(getClientTimeout())
+                .withAutoClean(false)
+                .withSpoolFolder(folder.newFolder())
+                .withSSLKeyFile(clientKeyFile.getAbsolutePath())
+                .withSSLKeyPassword(secretPassword)
+                .withSSLChainFile(clientCertFile.getAbsolutePath())
+                .build()
+        ) {
+            serverSync.start();
+            byte[] data = addTestContent(store, "server", blobSize, 1);
+            primary.flush();
+
+            clientSync.run();
+
+            assertEquals(primary.getHead(), secondary.getHead());
+
+            assertTrue(primary.getStats().getApproximateSize() > blobSize);
+            assertTrue(secondary.getStats().getApproximateSize() > blobSize);
+
+            PropertyState ps = secondary.getHead().getChildNode("root")
+                .getChildNode("server").getProperty("testBlob");
+            assertNotNull(ps);
+            assertEquals(Type.BINARY.tag(), ps.getType().tag());
+            Blob b = ps.getValue(Type.BINARY);
+            assertEquals(blobSize, b.length());
+
+            byte[] testData = new byte[blobSize];
+            ByteStreams.readFully(b.getNewStream(), testData);
+            assertArrayEquals(data, testData);
+        }
+    }
+
+    /**
+     * This test syncs a few segments over an encrypted connection.
+     * The server has a configured certificate which can be validated with the truststore.
+     * The server validates the client certificate.
+     * The client has a configured certificate which cannot be validated with the truststore.
+     * The SSL connection is expected to fail.
+     */
+    @Test
+    @Ignore("This test takes ~7s and is therefore disabled by default")
+    public void testSyncSSLInvalidClient() throws Exception {
+        int blobSize = 5 * MB;
+        FileStore primary = serverFileStore.fileStore();
+        FileStore secondary = clientFileStore.fileStore();
+
+        FileOutputStream fos;
+
+        File serverKeyFile = folder.newFile();
+        fos = new FileOutputStream(serverKeyFile);
+        IOUtils.writeString(fos, serverKey);
+        fos.close();
+
+        File clientKeyFile = folder.newFile();
+        fos = new FileOutputStream(clientKeyFile);
+        IOUtils.writeString(fos, arbitraryKey);
+        fos.close();
+
+        File serverCertFile = folder.newFile();
+        fos = new FileOutputStream(serverCertFile);
+        IOUtils.writeString(fos, serverCert);
+        fos.close();
+
+        File clientCertFile = folder.newFile();
+        fos = new FileOutputStream(clientCertFile);
+        IOUtils.writeString(fos, arbitraryCert);
+        fos.close();
+
+        File keyStoreFile = folder.newFile();
+        KeyStore keyStore = KeyStore.getInstance("JKS");
+        keyStore.load(null, "changeit".toCharArray());
+        Certificate c = CertificateFactory.getInstance("X.509").generateCertificate(new ByteArrayInputStream(caCert.getBytes()));
+        keyStore.setCertificateEntry("the-ca-cert", c);
+        keyStore.store(new FileOutputStream(keyStoreFile), "changeit".toCharArray());
+        System.setProperty("javax.net.ssl.trustStore", keyStoreFile.getAbsolutePath());
+
+        NodeStore store = SegmentNodeStoreBuilders.builder(primary).build();
+        try (
+            StandbyServerSync serverSync = StandbyServerSync.builder()
+                .withPort(serverPort.getPort())
+                .withFileStore(primary)
+                .withBlobChunkSize(MB)
+                .withSecureConnection(true)
+                .withSSLKeyFile(serverKeyFile.getAbsolutePath())
+                .withSSLChainFile(serverCertFile.getAbsolutePath())
+                .withSSLClientValidation(true)
+                .build();
+            StandbyClientSync clientSync = StandbyClientSync.builder()
+                .withHost(getServerHost())
+                .withPort(serverPort.getPort())
+                .withFileStore(secondary)
+                .withSecureConnection(true)
+                .withReadTimeoutMs(getClientTimeout())
+                .withAutoClean(false)
+                .withSpoolFolder(folder.newFolder())
+                .withSSLKeyFile(clientKeyFile.getAbsolutePath())
+                .withSSLChainFile(clientCertFile.getAbsolutePath())
+                .build()
+        ) {
+            serverSync.start();
+            addTestContent(store, "server", blobSize, 1);
+            primary.flush();
+
+            clientSync.run();
+
+            assertNotEquals(primary.getHead(), secondary.getHead());
+        }
+    }
+
+    /**
+     * This test syncs a few segments over an encrypted connection.
+     * The server has a configured certificate which cannot be validated with the truststore.
+     * The server validates the client certificate.
+     * The client has a configured certificate which can be validated with the truststore.
+     * The SSL connection is expected to fail.
+     */
+    @Test
+    @Ignore("This test takes ~7s and is therefore disabled by default")
+    public void testSyncSSLInvalidServer() throws Exception {
+        int blobSize = 5 * MB;
+        FileStore primary = serverFileStore.fileStore();
+        FileStore secondary = clientFileStore.fileStore();
+
+        FileOutputStream fos;
+
+        File serverKeyFile = folder.newFile();
+        fos = new FileOutputStream(serverKeyFile);
+        IOUtils.writeString(fos, arbitraryKey);
+        fos.close();
+
+        File clientKeyFile = folder.newFile();
+        fos = new FileOutputStream(clientKeyFile);
+        IOUtils.writeString(fos, clientKey);
+        fos.close();
+
+        File serverCertFile = folder.newFile();
+        fos = new FileOutputStream(serverCertFile);
+        IOUtils.writeString(fos, arbitraryCert);
+        fos.close();
+
+        File clientCertFile = folder.newFile();
+        fos = new FileOutputStream(clientCertFile);
+        IOUtils.writeString(fos, clientCert);
+        fos.close();
+
+        File keyStoreFile = folder.newFile();
+        KeyStore keyStore = KeyStore.getInstance("JKS");
+        keyStore.load(null, "changeit".toCharArray());
+        Certificate c = CertificateFactory.getInstance("X.509").generateCertificate(new ByteArrayInputStream(caCert.getBytes()));
+        keyStore.setCertificateEntry("the-ca-cert", c);
+        keyStore.store(new FileOutputStream(keyStoreFile), "changeit".toCharArray());
+        System.setProperty("javax.net.ssl.trustStore", keyStoreFile.getAbsolutePath());
+
+        NodeStore store = SegmentNodeStoreBuilders.builder(primary).build();
+        try (
+            StandbyServerSync serverSync = StandbyServerSync.builder()
+                .withPort(serverPort.getPort())
+                .withFileStore(primary)
+                .withBlobChunkSize(MB)
+                .withSecureConnection(true)
+                .withSSLKeyFile(serverKeyFile.getAbsolutePath())
+                .withSSLChainFile(serverCertFile.getAbsolutePath())
+                .withSSLClientValidation(true)
+                .build();
+            StandbyClientSync clientSync = StandbyClientSync.builder()
+                .withHost(getServerHost())
+                .withPort(serverPort.getPort())
+                .withFileStore(secondary)
+                .withSecureConnection(true)
+                .withReadTimeoutMs(getClientTimeout())
+                .withAutoClean(false)
+                .withSpoolFolder(folder.newFolder())
+                .withSSLKeyFile(clientKeyFile.getAbsolutePath())
+                .withSSLChainFile(clientCertFile.getAbsolutePath())
+                .build()
+        ) {
+            serverSync.start();
+            addTestContent(store, "server", blobSize, 1);
+            primary.flush();
+
+            clientSync.run();
+
+            assertNotEquals(primary.getHead(), secondary.getHead());
+        }
+    }
+
+    /**
+     * This test syncs a few segments over an encrypted connection.
+     * The server has a configured certificate which can be validated with the truststore.
+     * The server validates the client certificate.
+     * The client has a configured certificate which can be validated with the truststore,
+     * but cannot be validated against the required subject pattern.
+     * The SSL connection is expected to fail.
+     */
+    @Test
+    @Ignore("This test takes ~7s and is therefore disabled by default")
+    public void testSyncSSLInvalidClientSubject() throws Exception {
+        int blobSize = 5 * MB;
+        FileStore primary = serverFileStore.fileStore();
+        FileStore secondary = clientFileStore.fileStore();
+
+        FileOutputStream fos;
+
+        File serverKeyFile = folder.newFile();
+        fos = new FileOutputStream(serverKeyFile);
+        IOUtils.writeString(fos, serverKey);
+        fos.close();
+
+        File clientKeyFile = folder.newFile();
+        fos = new FileOutputStream(clientKeyFile);
+        IOUtils.writeString(fos, clientKey);
+        fos.close();
+
+        File serverCertFile = folder.newFile();
+        fos = new FileOutputStream(serverCertFile);
+        IOUtils.writeString(fos, serverCert);
+        fos.close();
+
+        File clientCertFile = folder.newFile();
+        fos = new FileOutputStream(clientCertFile);
+        IOUtils.writeString(fos, clientCert);
+        fos.close();
+
+        File keyStoreFile = folder.newFile();
+        KeyStore keyStore = KeyStore.getInstance("JKS");
+        keyStore.load(null, "changeit".toCharArray());
+        Certificate c = CertificateFactory.getInstance("X.509").generateCertificate(new ByteArrayInputStream(caCert.getBytes()));
+        keyStore.setCertificateEntry("the-ca-cert", c);
+        keyStore.store(new FileOutputStream(keyStoreFile), "changeit".toCharArray());
+        System.setProperty("javax.net.ssl.trustStore", keyStoreFile.getAbsolutePath());
+
+        NodeStore store = SegmentNodeStoreBuilders.builder(primary).build();
+        try (
+            StandbyServerSync serverSync = StandbyServerSync.builder()
+                .withPort(serverPort.getPort())
+                .withFileStore(primary)
+                .withBlobChunkSize(MB)
+                .withSecureConnection(true)
+                .withSSLKeyFile(serverKeyFile.getAbsolutePath())
+                .withSSLChainFile(serverCertFile.getAbsolutePath())
+                .withSSLClientValidation(true)
+                .withSSLSubjectPattern("foobar")
+                .build();
+            StandbyClientSync clientSync = StandbyClientSync.builder()
+                .withHost(getServerHost())
+                .withPort(serverPort.getPort())
+                .withFileStore(secondary)
+                .withSecureConnection(true)
+                .withReadTimeoutMs(getClientTimeout())
+                .withAutoClean(false)
+                .withSpoolFolder(folder.newFolder())
+                .withSSLKeyFile(clientKeyFile.getAbsolutePath())
+                .withSSLChainFile(clientCertFile.getAbsolutePath())
+                .build()
+        ) {
+            serverSync.start();
+            addTestContent(store, "server", blobSize, 1);
+            primary.flush();
+
+            clientSync.run();
+
+            assertNotEquals(primary.getHead(), secondary.getHead());
+        }
+    }
+
+    /**
+     * This test syncs a few segments over an encrypted connection.
+     * The server has a configured certificate which can be validated with the truststore.
+     * The server validates the client certificate.
+     * The client has a configured certificate which can be validated with the truststore,
+     * and can also be validated against the required subject pattern.
+     */
+    @Test
+    @Ignore("This test takes ~7s and is therefore disabled by default")
+    public void testSyncSSLValidClientSubject() throws Exception {
+        int blobSize = 5 * MB;
+        FileStore primary = serverFileStore.fileStore();
+        FileStore secondary = clientFileStore.fileStore();
+
+        FileOutputStream fos;
+
+        File serverKeyFile = folder.newFile();
+        fos = new FileOutputStream(serverKeyFile);
+        IOUtils.writeString(fos, serverKey);
+        fos.close();
+
+        File clientKeyFile = folder.newFile();
+        fos = new FileOutputStream(clientKeyFile);
+        IOUtils.writeString(fos, clientKey);
+        fos.close();
+
+        File serverCertFile = folder.newFile();
+        fos = new FileOutputStream(serverCertFile);
+        IOUtils.writeString(fos, serverCert);
+        fos.close();
+
+        File clientCertFile = folder.newFile();
+        fos = new FileOutputStream(clientCertFile);
+        IOUtils.writeString(fos, clientCert);
+        fos.close();
+
+        File keyStoreFile = folder.newFile();
+        KeyStore keyStore = KeyStore.getInstance("JKS");
+        keyStore.load(null, "changeit".toCharArray());
+        Certificate c = CertificateFactory.getInstance("X.509").generateCertificate(new ByteArrayInputStream(caCert.getBytes()));
+        keyStore.setCertificateEntry("the-ca-cert", c);
+        keyStore.store(new FileOutputStream(keyStoreFile), "changeit".toCharArray());
+        System.setProperty("javax.net.ssl.trustStore", keyStoreFile.getAbsolutePath());
+
+        NodeStore store = SegmentNodeStoreBuilders.builder(primary).build();
+        try (
+            StandbyServerSync serverSync = StandbyServerSync.builder()
+                .withPort(serverPort.getPort())
+                .withFileStore(primary)
+                .withBlobChunkSize(MB)
+                .withSecureConnection(true)
+                .withSSLKeyFile(serverKeyFile.getAbsolutePath())
+                .withSSLChainFile(serverCertFile.getAbsolutePath())
+                .withSSLClientValidation(true)
+                .withSSLSubjectPattern(".*.esting.*")
+                .build();
+            StandbyClientSync clientSync = StandbyClientSync.builder()
+                .withHost(getServerHost())
+                .withPort(serverPort.getPort())
+                .withFileStore(secondary)
+                .withSecureConnection(true)
+                .withReadTimeoutMs(getClientTimeout())
+                .withAutoClean(false)
+                .withSpoolFolder(folder.newFolder())
+                .withSSLKeyFile(clientKeyFile.getAbsolutePath())
+                .withSSLChainFile(clientCertFile.getAbsolutePath())
+                .build()
+        ) {
+            serverSync.start();
+            addTestContent(store, "server", blobSize, 1);
+            primary.flush();
+
+            clientSync.run();
+
+            assertEquals(primary.getHead(), secondary.getHead());
+        }
+    }
+
+    /**
+     * This test syncs a few segments over an encrypted connection.
+     * The server has a configured certificate which can be validated with the truststore,
+     * but cannot be validated against the required subject pattern.
+     * The server validates the client certificate.
+     * The client has a configured certificate which can be validated with the truststore.
+     * The SSL connection is expected to fail.
+     */
+    @Test
+    @Ignore("This test takes ~7s and is therefore disabled by default")
+    public void testSyncSSLInvalidServerSubject() throws Exception {
+        int blobSize = 5 * MB;
+        FileStore primary = serverFileStore.fileStore();
+        FileStore secondary = clientFileStore.fileStore();
+
+        FileOutputStream fos;
+
+        File serverKeyFile = folder.newFile();
+        fos = new FileOutputStream(serverKeyFile);
+        IOUtils.writeString(fos, serverKey);
+        fos.close();
+
+        File clientKeyFile = folder.newFile();
+        fos = new FileOutputStream(clientKeyFile);
+        IOUtils.writeString(fos, clientKey);
+        fos.close();
+
+        File serverCertFile = folder.newFile();
+        fos = new FileOutputStream(serverCertFile);
+        IOUtils.writeString(fos, serverCert);
+        fos.close();
+
+        File clientCertFile = folder.newFile();
+        fos = new FileOutputStream(clientCertFile);
+        IOUtils.writeString(fos, clientCert);
+        fos.close();
+
+        File keyStoreFile = folder.newFile();
+        KeyStore keyStore = KeyStore.getInstance("JKS");
+        keyStore.load(null, "changeit".toCharArray());
+        Certificate c = CertificateFactory.getInstance("X.509").generateCertificate(new ByteArrayInputStream(caCert.getBytes()));
+        keyStore.setCertificateEntry("the-ca-cert", c);
+        keyStore.store(new FileOutputStream(keyStoreFile), "changeit".toCharArray());
+        System.setProperty("javax.net.ssl.trustStore", keyStoreFile.getAbsolutePath());
+
+        NodeStore store = SegmentNodeStoreBuilders.builder(primary).build();
+        try (
+            StandbyServerSync serverSync = StandbyServerSync.builder()
+                .withPort(serverPort.getPort())
+                .withFileStore(primary)
+                .withBlobChunkSize(MB)
+                .withSecureConnection(true)
+                .withSSLKeyFile(serverKeyFile.getAbsolutePath())
+                .withSSLChainFile(serverCertFile.getAbsolutePath())
+                .withSSLClientValidation(true)
+                .build();
+            StandbyClientSync clientSync = StandbyClientSync.builder()
+                .withHost(getServerHost())
+                .withPort(serverPort.getPort())
+                .withFileStore(secondary)
+                .withSecureConnection(true)
+                .withReadTimeoutMs(getClientTimeout())
+                .withAutoClean(false)
+                .withSpoolFolder(folder.newFolder())
+                .withSSLKeyFile(clientKeyFile.getAbsolutePath())
+                .withSSLChainFile(clientCertFile.getAbsolutePath())
+                .withSSLSubjectPattern("foobar")
+                .build()
+        ) {
+            serverSync.start();
+            addTestContent(store, "server", blobSize, 1);
+            primary.flush();
+
+            clientSync.run();
+
+            assertNotEquals(primary.getHead(), secondary.getHead());
+        }
+    }
+
+    /**
+     * This test syncs a few segments over an encrypted connection.
+     * The server has a configured certificate which can be validated with the truststore,
+     * and can also be validated against the required subject pattern.
+     * The server validates the client certificate.
+     * The client has a configured certificate which can be validated with the truststore.
+     */
+    @Test
+    @Ignore("This test takes ~7s and is therefore disabled by default")
+    public void testSyncSSLValidServerSubject() throws Exception {
+        int blobSize = 5 * MB;
+        FileStore primary = serverFileStore.fileStore();
+        FileStore secondary = clientFileStore.fileStore();
+
+        FileOutputStream fos;
+
+        File serverKeyFile = folder.newFile();
+        fos = new FileOutputStream(serverKeyFile);
+        IOUtils.writeString(fos, serverKey);
+        fos.close();
+
+        File clientKeyFile = folder.newFile();
+        fos = new FileOutputStream(clientKeyFile);
+        IOUtils.writeString(fos, clientKey);
+        fos.close();
+
+        File serverCertFile = folder.newFile();
+        fos = new FileOutputStream(serverCertFile);
+        IOUtils.writeString(fos, serverCert);
+        fos.close();
+
+        File clientCertFile = folder.newFile();
+        fos = new FileOutputStream(clientCertFile);
+        IOUtils.writeString(fos, clientCert);
+        fos.close();
+
+        File keyStoreFile = folder.newFile();
+        KeyStore keyStore = KeyStore.getInstance("JKS");
+        keyStore.load(null, "changeit".toCharArray());
+        Certificate c = CertificateFactory.getInstance("X.509").generateCertificate(new ByteArrayInputStream(caCert.getBytes()));
+        keyStore.setCertificateEntry("the-ca-cert", c);
+        keyStore.store(new FileOutputStream(keyStoreFile), "changeit".toCharArray());
+        System.setProperty("javax.net.ssl.trustStore", keyStoreFile.getAbsolutePath());
+
+        NodeStore store = SegmentNodeStoreBuilders.builder(primary).build();
+        try (
+            StandbyServerSync serverSync = StandbyServerSync.builder()
+                .withPort(serverPort.getPort())
+                .withFileStore(primary)
+                .withBlobChunkSize(MB)
+                .withSecureConnection(true)
+                .withSSLKeyFile(serverKeyFile.getAbsolutePath())
+                .withSSLChainFile(serverCertFile.getAbsolutePath())
+                .withSSLClientValidation(true)
+                .build();
+            StandbyClientSync clientSync = StandbyClientSync.builder()
+                .withHost(getServerHost())
+                .withPort(serverPort.getPort())
+                .withFileStore(secondary)
+                .withSecureConnection(true)
+                .withReadTimeoutMs(getClientTimeout())
+                .withAutoClean(false)
+                .withSpoolFolder(folder.newFolder())
+                .withSSLKeyFile(clientKeyFile.getAbsolutePath())
+                .withSSLChainFile(clientCertFile.getAbsolutePath())
+                .withSSLSubjectPattern(".*.esting.*")
+                .build()
+        ) {
+            serverSync.start();
+            addTestContent(store, "server", blobSize, 1);
+            primary.flush();
+
+            clientSync.run();
+
+            assertEquals(primary.getHead(), secondary.getHead());
+        }
+    }
+
+    /**
      * OAK-2430
      */
     @Test
@@ -120,7 +920,15 @@ public class StandbyTestIT extends TestBase {
                 .withFileStore(primary)
                 .withBlobChunkSize(MB)
                 .build();
-            StandbyClientSync clientSync = new StandbyClientSync(getServerHost(), serverPort.getPort(), secondary, false, getClientTimeout(), false, folder.newFolder())
+            StandbyClientSync clientSync = StandbyClientSync.builder()
+                .withHost(getServerHost())
+                .withPort(serverPort.getPort())
+                .withFileStore(secondary)
+                .withSecureConnection(false)
+                .withReadTimeoutMs(getClientTimeout())
+                .withAutoClean(false)
+                .withSpoolFolder(folder.newFolder())
+                .build()
         ) {
             serverSync.start();
             byte[] data = addTestContent(store, "server", blobSize, dataNodes);
diff --git a/oak-segment-tar/src/test/java/org/apache/jackrabbit/oak/segment/standby/TestBase.java b/oak-segment-tar/src/test/java/org/apache/jackrabbit/oak/segment/standby/TestBase.java
index 880423f..e89e374 100644
--- a/oak-segment-tar/src/test/java/org/apache/jackrabbit/oak/segment/standby/TestBase.java
+++ b/oak-segment-tar/src/test/java/org/apache/jackrabbit/oak/segment/standby/TestBase.java
@@ -48,4 +48,292 @@ public class TestBase {
         return timeout;
     }
 
+    // A self-signed certificate representing the "Testing CA"
+    static final String caCert =
+        "-----BEGIN CERTIFICATE-----\n" +
+            "MIIDxjCCAq4CCQDWKVsDO4p3MDANBgkqhkiG9w0BAQUFADCBpDELMAkGA1UEBhMC\n" +
+            "VVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcMDVNhbiBGcmFuY2lzY28x\n" +
+            "FjAUBgNVBAoMDUNBVGVzdGluZyBDby4xEDAOBgNVBAsMB1Rlc3RpbmcxFjAUBgNV\n" +
+            "BAMMDWNhdGVzdGluZy5jb20xJjAkBgkqhkiG9w0BCQEWF2NhdGVzdGluZ0BjYXRl\n" +
+            "c3RpbmcuY29tMB4XDTIxMDUzMTEzNTczOFoXDTQ4MTAxNTEzNTczOFowgaQxCzAJ\n" +
+            "BgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRYwFAYDVQQHDA1TYW4gRnJh\n" +
+            "bmNpc2NvMRYwFAYDVQQKDA1DQVRlc3RpbmcgQ28uMRAwDgYDVQQLDAdUZXN0aW5n\n" +
+            "MRYwFAYDVQQDDA1jYXRlc3RpbmcuY29tMSYwJAYJKoZIhvcNAQkBFhdjYXRlc3Rp\n" +
+            "bmdAY2F0ZXN0aW5nLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB\n" +
+            "AKmolPe+fw1pQ91MNrlHBdRCX5J55vuV7zqbKU8UdZVuI3iajvhFtMm6xxIyISIM\n" +
+            "cO+h0JYKH0NHgCV0wqUKh+T16FEPo8BskxMCXIn0R0IQBBeUIIS3+FtNzRhLK3ff\n" +
+            "R7jtpv4YMp50tx8FVj9zIYFnLq5RObH7hENrxLTk04G3Gr8ENqapH21pfVlkb4GP\n" +
+            "RXBonxyR/q997ZGqutxiEbsCeWDMl/uvPDL3zKbd2e/Hx0Xy5kBOqAF35JTmKxax\n" +
+            "up33ld3u3r2MlFzcD7l/2uG7ur42MNDFBG2yQXsmPFUE6/Ogk/IfuGiqVYYjjRZV\n" +
+            "ixO+HK3WnICJggsNDQO6CqsCAwEAATANBgkqhkiG9w0BAQUFAAOCAQEAOjhaAvUw\n" +
+            "AeQ6PO485xoNpnadrFu+Q1RafqZCHd/UJ/c2VLeEogsBjZpLMMclF01T6k7X/6N5\n" +
+            "44oy9MS90/IVeRsfL0fT0pPGao2by4eja5di2j8UiiyuWSUI4J5PqBn48pNNYt4P\n" +
+            "W0MKS74ea/lDWmsGWw9B/mGZ/0znIo2WUCpifIGBqUfqfmBAFjMU/4aK1bMfzhZc\n" +
+            "OJNcpa2iOgrqd9Irjrs0kM5j0omN42be65FXUEoTVH5zOJr9sHaThTVM8PZ+A5Vm\n" +
+            "uSxSBG00WgrVuuCixz1DDrXI+LR/ipVU/fTGWRywBQRunrQsY7V9ODtmSipczi+o\n" +
+            "udwFdKx9QrsPEA==\n" +
+        "-----END CERTIFICATE-----\n";
+
+    // A client certificate signed by the "Testing CA"
+    static final String clientCert =
+        "-----BEGIN CERTIFICATE-----\n" +
+            "MIIDxDCCAqwCAQIwDQYJKoZIhvcNAQEFBQAwgaQxCzAJBgNVBAYTAlVTMRMwEQYD\n" +
+            "VQQIDApDYWxpZm9ybmlhMRYwFAYDVQQHDA1TYW4gRnJhbmNpc2NvMRYwFAYDVQQK\n" +
+            "DA1DQVRlc3RpbmcgQ28uMRAwDgYDVQQLDAdUZXN0aW5nMRYwFAYDVQQDDA1jYXRl\n" +
+            "c3RpbmcuY29tMSYwJAYJKoZIhvcNAQkBFhdjYXRlc3RpbmdAY2F0ZXN0aW5nLmNv\n" +
+            "bTAeFw0yMTA1MzExMzU3MzhaFw00ODEwMTUxMzU3MzhaMIGqMQswCQYDVQQGEwJV\n" +
+            "UzETMBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNjbzEU\n" +
+            "MBIGA1UECgwLRXhhbXBsZSBDby4xFzAVBgNVBAsMDlRlc3RpbmcgQ2xpZW50MRQw\n" +
+            "EgYDVQQDDAtleGFtcGxlLmNvbTEpMCcGCSqGSIb3DQEJARYadGVzdGluZy1jbGll\n" +
+            "bnRAZXhhbXBsZS5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCp\n" +
+            "izibTCEp21PffO6UN8wX7PW+dd7HTepbwqd2mT3iYqC2/IuO8mzS5Sk/mNqL2T9T\n" +
+            "GPcx4cLRGGovBw/Ig3A3nv4uBxzFVHJqtHZJl+VPgZiUDmyBaNsv0g5fqluiktcU\n" +
+            "MI0HQWABOEcwCyBYHXadxp8DUAUdjniopax7cMTujrWRFJAH7s4++yu5kK06mMwD\n" +
+            "Rsh65CGKvP5tz+cszoabo5I8dT+8tTPHrgdaCqjI9536VtbgmU3LKNG1DNkmR6nq\n" +
+            "ICo1Lg0+GFnkFnEQpbV8WsckwFempDND3MtZKw6U5VxGs+EdTj0s0UEbCNVwLkW1\n" +
+            "uBFyfcnODpWe2VY0wPafAgMBAAEwDQYJKoZIhvcNAQEFBQADggEBAHf1P4+fzuln\n" +
+            "KHIBpkZTE++0tW0KUwoXfRIWATOp3XFqYJTHQm4KtTQ+mVo5FejavC57KrgeTp73\n" +
+            "mljxKJhzT6Oh89shLxYPF/mvgfhWgDpwPLXmFkepigZ88MLeMV3UG9MJVcB5UrTv\n" +
+            "RTxire5Ga1iaWRtHHeY4OXKp1foBxPyc9l+XHfxHeAjM4Oj4UoOKdV9sr+UYKXP7\n" +
+            "N0QBXdTHa70mhzOQP6PE5VUu98HFAA6oYlbIWwetXCBDdJ1sTeh+Uw5NAzfsUw5S\n" +
+            "NGib2Ru0SeKvjrpIzOto5DO6ZQdlb3Yfbj2S4ea3HBUHe6dNYQNa+dnq4xvrA3Iv\n" +
+            "CT7KNBGK8AQ=\n" +
+        "-----END CERTIFICATE-----\n";
+
+    // A server certificate signed by the "Testing CA"
+    static final String serverCert =
+        "-----BEGIN CERTIFICATE-----\n" +
+            "MIIDxDCCAqwCAQEwDQYJKoZIhvcNAQEFBQAwgaQxCzAJBgNVBAYTAlVTMRMwEQYD\n" +
+            "VQQIDApDYWxpZm9ybmlhMRYwFAYDVQQHDA1TYW4gRnJhbmNpc2NvMRYwFAYDVQQK\n" +
+            "DA1DQVRlc3RpbmcgQ28uMRAwDgYDVQQLDAdUZXN0aW5nMRYwFAYDVQQDDA1jYXRl\n" +
+            "c3RpbmcuY29tMSYwJAYJKoZIhvcNAQkBFhdjYXRlc3RpbmdAY2F0ZXN0aW5nLmNv\n" +
+            "bTAeFw0yMTA1MzExMzU3MzhaFw00ODEwMTUxMzU3MzhaMIGqMQswCQYDVQQGEwJV\n" +
+            "UzETMBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNjbzEU\n" +
+            "MBIGA1UECgwLRXhhbXBsZSBDby4xFzAVBgNVBAsMDlRlc3RpbmcgU2VydmVyMRQw\n" +
+            "EgYDVQQDDAtleGFtcGxlLmNvbTEpMCcGCSqGSIb3DQEJARYadGVzdGluZy1zZXJ2\n" +
+            "ZXJAZXhhbXBsZS5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDA\n" +
+            "7nYmXdUcCgkDnwDPV0MEjbNQvVNxL9Sd/zJZBy02jcn2mklBJtAb18Nvra7tw14L\n" +
+            "KmEILFC4GwKVEtF1vqE6gBCxaohE0ZZ/dJGgPVvuRn+r1nzIwT4LKmDoayfne29K\n" +
+            "w2oDvxX+0vU9BI9c2N8ZN3Ge9n/r8Rp25SP0cyRKa8Q862kPXgsFsYX/D/mZpK/p\n" +
+            "KfwhLGl/6tCKTsOteetEvHPnirHIYgb81zI5NGirrckO6PJ3b+PJzdjgW3/12jWg\n" +
+            "9JrqT9Wg3Uf3VESGiUoAr9xnMun026jpWhRJL0zzZnpP0hr8RYAJ2Mfh8JpZjlx6\n" +
+            "0ePzHPTckRydLPc8tvCvAgMBAAEwDQYJKoZIhvcNAQEFBQADggEBAHq0euTu0433\n" +
+            "nPwt9CuyC10o4wyTBCW0tXupM/06Hqk0U9rOQsPw0zNuQaW/Ww1rqpyG8S3Mw27f\n" +
+            "oua6Usfnwog5eREUi1XGe2HcGeqSca+34+WPUyi6agS2NEqpXyZWiRgfLUEngo3d\n" +
+            "Ph1BFHsxOXqLk+LCouA1OxeS8WZHdMRt6lzP+3FEJhiiEBAF8YIoQD6kD1lVBo4J\n" +
+            "e5Vof1Zs6frxbi12nh/Iu7YUEgm0IZ6X5GSs5c+nj0hHdPPN86Pul3fc9tWJ8MJO\n" +
+            "KYmsEX4YiIebo+dzrFEZywSQXOG5xkMhOprdc28stiIGkJCRSf/1lPy70i5YHMjr\n" +
+            "VqwFqu16zZg=\n" +
+        "-----END CERTIFICATE-----\n";
+
+    // An arbitrary self-signed certificate
+    static final String arbitraryCert =
+        "-----BEGIN CERTIFICATE-----\n" +
+            "MIID3jCCAsYCCQD5JkW9FPJFYzANBgkqhkiG9w0BAQUFADCBsDELMAkGA1UEBhMC\n" +
+            "VVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcMDVNhbiBGcmFuY2lzY28x\n" +
+            "FDASBgNVBAoMC0V4YW1wbGUgQ28uMRowGAYDVQQLDBFUZXN0aW5nIEFyYml0cmFy\n" +
+            "eTEUMBIGA1UEAwwLZXhhbXBsZS5jb20xLDAqBgkqhkiG9w0BCQEWHXRlc3Rpbmct\n" +
+            "YXJiaXRyYXJ5QGV4YW1wbGUuY29tMB4XDTIxMDYwMjA4MDkwNloXDTQ4MTAxNzA4\n" +
+            "MDkwNlowgbAxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRYwFAYD\n" +
+            "VQQHDA1TYW4gRnJhbmNpc2NvMRQwEgYDVQQKDAtFeGFtcGxlIENvLjEaMBgGA1UE\n" +
+            "CwwRVGVzdGluZyBBcmJpdHJhcnkxFDASBgNVBAMMC2V4YW1wbGUuY29tMSwwKgYJ\n" +
+            "KoZIhvcNAQkBFh10ZXN0aW5nLWFyYml0cmFyeUBleGFtcGxlLmNvbTCCASIwDQYJ\n" +
+            "KoZIhvcNAQEBBQADggEPADCCAQoCggEBAMrvljMgkh0nLoNH9fNolHJWZ74xRDYT\n" +
+            "fjpMjNvJZqKvveQMhIWgLFkPM1fMsXQEeXxfrLMEt5bh0Crbr7GfODaoltH+HM8d\n" +
+            "xoXDqaXcDFtRdVyf4nM7RdwEQGhZHfa3oiewQG6UOrWpOU5s4GQL0Pk0oKNZFRGJ\n" +
+            "nR8hyPxnyB5p+M5VBOO/ATuOsKS+uXYjjI5ndof8bTx9xDykebiMTLmfy745req6\n" +
+            "KnaLC7NKSHsBUEvJlaQrRzyK5zouEz0Sfk3QfvnCOoLOng11WZ3I88jTl69J2X6E\n" +
+            "TLwAiNrsI24zvuCa24jKeehBvDIGjfa68+YQq7Zkdlsere9EoEe7JSMCAwEAATAN\n" +
+            "BgkqhkiG9w0BAQUFAAOCAQEAEwDID0JaJaI2WQMT0oZ9+Zz8Mf7wZbngrf9uG9MW\n" +
+            "oljQNwtFtcUYcMd6q+wsGd24bR//YBGkAXke6x+DNyRtbbTbFLFQgko/7Do6jB7U\n" +
+            "p75oNJgRBKsNO6eGIaLBxIYWxZcZ77IhRoX82WPRRLaT2iAc8p2I3QMra5Y+E+Aj\n" +
+            "f9gKqRKzwDgWOApD27KosJJZd2zddHZm/Fj+T8kWPTHFCt0FnzgGAVxKp1bFmzxX\n" +
+            "qGGFKW4IThE87fTFcgbRkUZdnrKTo6tCDDjIi2Rb2jJv7ip6BvI2cHip+UvQmvhe\n" +
+            "qRQdDY0scDekIRZ0WQYg4h/kmfNp9Zw55Ce18aMTXEwGrQ==\n" +
+        "-----END CERTIFICATE-----\n";
+
+    static final String clientKey =
+        "-----BEGIN PRIVATE KEY-----\n" +
+            "MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCpizibTCEp21Pf\n" +
+            "fO6UN8wX7PW+dd7HTepbwqd2mT3iYqC2/IuO8mzS5Sk/mNqL2T9TGPcx4cLRGGov\n" +
+            "Bw/Ig3A3nv4uBxzFVHJqtHZJl+VPgZiUDmyBaNsv0g5fqluiktcUMI0HQWABOEcw\n" +
+            "CyBYHXadxp8DUAUdjniopax7cMTujrWRFJAH7s4++yu5kK06mMwDRsh65CGKvP5t\n" +
+            "z+cszoabo5I8dT+8tTPHrgdaCqjI9536VtbgmU3LKNG1DNkmR6nqICo1Lg0+GFnk\n" +
+            "FnEQpbV8WsckwFempDND3MtZKw6U5VxGs+EdTj0s0UEbCNVwLkW1uBFyfcnODpWe\n" +
+            "2VY0wPafAgMBAAECggEAZvBnsyq66/4F46in9ogWO+ScpEJOu/XbuFDsen66ayx0\n" +
+            "5gVZ+rXISxfmPn3hG44Q+7QpyjiHn4rSVbFU7OqZBLxdGbcpycnnGlBtjWtTSD2o\n" +
+            "VSSYzs3KXzOLlJwLvR6oxdJgniocT0FLP6lRvw5MiakhvNIl+Pca3VKR8fTbLPet\n" +
+            "wXsTSQ+80GRAUyLMKMn1Xs7A6AIrXodFmwuVFB8MUdAf8g6jGEacagz26SBzl34v\n" +
+            "YfcF6RWKnBJOwz0ej0sCX8rUyB5rBqGO/z9Ey9XzuaCYJgc4SBwcohsvVsBCGD+E\n" +
+            "8aXYPC5v9nWOSBRnzZruw/hg/j8YHP8Coy8bavtTEQKBgQDWVM6AI7CDH2wbqKdL\n" +
+            "L3S4cILpQ4nP0ajF7Ql5TCAxLTWSCvryMSBeas4tnG5oM9+z5JQiIzveCOqtMd8s\n" +
+            "geysLwk39wBN50uZPiOSlq+PxraPibDo9cI/hmi5IKIXqxnJS3OQ1LqJ4vDun7Yh\n" +
+            "rsXhk0UN62irRqXTzVY0qPlZBwKBgQDKgVvi63Lm5KVTLDB8Q162Hr83QV6VEMjK\n" +
+            "yd3AEaps+Q1McyKuub6VSucj3JyTZXDpUYUSoZaRrD8qqNDZ+h/uxHs8d8PD+jW7\n" +
+            "c3z3YVkeUezAUOdwZMvQ0SXa1zdf159rRAKl7Xj+VLhR0+3HXxw6RYsxCPhfHYy5\n" +
+            "Lukh1BoHqQKBgBFrBvUm8VtWnGSLCj1z99pdWmY2lOaMtViQcOqoox0b/XSG6+nu\n" +
+            "0CCcMXFHezmArbdi5h74Gg9rThcRLH/jdyZvFCK2MhIir+QeRqnNEStwDLoRiI0G\n" +
+            "G+kptS0GV+Xwg8H2HcgxYY9/H/FkjVqjZ3VzkHMXJIR201cpIs5YxRrVAoGAM+CB\n" +
+            "vpccn2PRqoX2gc7sc3FbAPfBGCTtm22tXifoZfRDYONZ7jLtTOecYQaCIgxpqYvV\n" +
+            "sFku7nCW2gHXRxAZoBw7idkQkKMHotbKG8GXh/nq0bWoJJXd1MfPj8l0iRv+3gbV\n" +
+            "OtakGVtwwJ2vG1UVMSRhrRUkM5GpXENVO/JPHMkCgYBn8rw3sPZ6gNk2ttF1yKGA\n" +
+            "Kr3Wb2rwcG5Pf5ESCNktuvY6ipxnOGuvmbMnWMQe9KnEbFrjiEDgYTxcnHztPAt1\n" +
+            "cK8KDB8cBrj2OHIMc83Yfon5mM8VybTTWx3Cd/AAfhyNB0vohbJNeHO80sQH+2Cn\n" +
+            "6aAcVdLuUdYcYq6RrIPM8w==\n" +
+        "-----END PRIVATE KEY-----\n";
+
+    static final String serverKey =
+        "-----BEGIN PRIVATE KEY-----\n" +
+            "MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQDA7nYmXdUcCgkD\n" +
+            "nwDPV0MEjbNQvVNxL9Sd/zJZBy02jcn2mklBJtAb18Nvra7tw14LKmEILFC4GwKV\n" +
+            "EtF1vqE6gBCxaohE0ZZ/dJGgPVvuRn+r1nzIwT4LKmDoayfne29Kw2oDvxX+0vU9\n" +
+            "BI9c2N8ZN3Ge9n/r8Rp25SP0cyRKa8Q862kPXgsFsYX/D/mZpK/pKfwhLGl/6tCK\n" +
+            "TsOteetEvHPnirHIYgb81zI5NGirrckO6PJ3b+PJzdjgW3/12jWg9JrqT9Wg3Uf3\n" +
+            "VESGiUoAr9xnMun026jpWhRJL0zzZnpP0hr8RYAJ2Mfh8JpZjlx60ePzHPTckRyd\n" +
+            "LPc8tvCvAgMBAAECggEAScjpFrM8FYUg/WmJ/cH5t3wZ3/8IMnmAbwxyTOoZuItx\n" +
+            "egZ3jZsya/OQot1h0Tyucsa6ZU3NcRujWS/hO460SpM/zxpXEzq0u/nw17+fsPj1\n" +
+            "Stq0znJZMBv9A+Y3VKg4X/dsTBKAbvxvHe7ohTHL4PD7WzgapDmJTX9EyPBgKLVz\n" +
+            "+6z6wOtP2KwjtgWBipm3gMFNUSJVEtgD4XQP1nOTF+lUI51dhW9iVFbx5l85zrtX\n" +
+            "gzqTMNVxQTPpdTvILUmjJBFEgZcGrq8mGYcXSHZhdIgPYLk3G1bexvtUTVzq8zbV\n" +
+            "/vrCADb25B/1jT+u+txAkh9XpJM/sQFylUoo4EDGgQKBgQDss0JVZ4504zqlncFe\n" +
+            "IizOOEy1Ba+M5DNZCqx9RlHJdMTDCdJi0phHvvaOhwXvyKLJkJuzxByUb5QthQ5p\n" +
+            "Yc1yU14ps8K7d9Ck8HX3K035SPprYQrBd0XMHzaDS2+2zr+/c+xyf/xMTC8iaUvl\n" +
+            "lizbu8O442PlyMHUbrVONeS/xwKBgQDQqZl8biSO0ljYBgYNc8INtS/TTFq5lUqP\n" +
+            "LgFfi1He2wj44nGd8iSHuNbaJJCv0UJVjKlZYpazN4bwzJSckrlU72lp6WFNapqF\n" +
+            "CDRV295PDbwaE2NRi2pJ+inTFpN1nvRC2Rx54UVKBDDcU+bZQw/WuQJ3LQcMlSVz\n" +
+            "QYoQXb6X2QKBgBSkES3HaQnSYuPcXOdrjYKyMCY9B7D+mWezYZVPE4TA1QO5EIqj\n" +
+            "mLnw8ik9pwvg8CkpnhpQCLn8/Ov3RWl1KOhGUtjKHzof2ab4fSD/ur35WjUQ8lIq\n" +
+            "p4CEXEmYw3Yqk1gLsNvPQ14X6qhSjFbKAMFsn0W5NpXsKtLukIrwcjEzAoGAWGql\n" +
+            "KP6a6xHip5bV1blpTtmprEU8ZEsITudVmaC1TlNN1/hL4HuMUx5VnBXGYVmwXAPA\n" +
+            "dqm55bLvsPVfO4FImt7fsgs8OcukMh6p3n/OEX1maT4x5YnHvhUMx+9XCI4UPoc0\n" +
+            "88gqzhQ8h//dX8501a2Lh+hChmhkeBQbZpfyfPECgYAj5dPvdITryOyjsEzkPcex\n" +
+            "zhwrgrdrSk/B+AlhmT3y6AwQ4ZI0uABJeVMpzQE6sS7lMkg7intgGIsienW60vdH\n" +
+            "oGxJxL7KGnzcY32N66a9SRReU3k6KWC0dMbWOZi6ebLLDXcjc4DIlUscUcHFEwfS\n" +
+            "AkZ3d5ywA+rEzXzaAquoBg==\n" +
+        "-----END PRIVATE KEY-----\n";
+
+    static final String arbitraryKey =
+        "-----BEGIN PRIVATE KEY-----\n" +
+            "MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDK75YzIJIdJy6D\n" +
+            "R/XzaJRyVme+MUQ2E346TIzbyWair73kDISFoCxZDzNXzLF0BHl8X6yzBLeW4dAq\n" +
+            "26+xnzg2qJbR/hzPHcaFw6ml3AxbUXVcn+JzO0XcBEBoWR32t6InsEBulDq1qTlO\n" +
+            "bOBkC9D5NKCjWRURiZ0fIcj8Z8geafjOVQTjvwE7jrCkvrl2I4yOZ3aH/G08fcQ8\n" +
+            "pHm4jEy5n8u+Oa3quip2iwuzSkh7AVBLyZWkK0c8iuc6LhM9En5N0H75wjqCzp4N\n" +
+            "dVmdyPPI05evSdl+hEy8AIja7CNuM77gmtuIynnoQbwyBo32uvPmEKu2ZHZbHq3v\n" +
+            "RKBHuyUjAgMBAAECggEBAMi9jtosUdy8sWnzePu6a31es2iT22GSjr6kkoGnC/vJ\n" +
+            "1BENwNldxACk5KjpNnAJLRM2oOLEu8ZowT5j6bvOQBDxW5+FuoG2dnZDQkFrFl4O\n" +
+            "igWBssNB0qz9F6kg3l7671BLLLE1t42TQ7isQps0hRa+VFjA+fJLKj1tch8bmf1a\n" +
+            "Gfcscs0ornwerXlMtRXcunVh64/aRQpT7/f3RhDvucASXrly28gLh1qx7AxtpgJ0\n" +
+            "tHtaenq4yYJujZHTJfIkB6WzAwT0RhVJYL8hm+DjJePG65u9b/W2oHCt3Mi3Rj+b\n" +
+            "XlDEPFmD2SXvuu2tenkEHkOMQRQpoOh69Vc6d/bQlMECgYEA9yNHnHrctc6N56k+\n" +
+            "zCPMI4nvgWEeKPczdfVmzYQonwqNhPim5R70hAKmRMznrcvPjCNH71u63BvPfMFQ\n" +
+            "PasKCBG10TGIRlpT2V0uOplTenV7xeOM7jVkQpjaz+NN7PoApL7bv9vCLL0l5TGS\n" +
+            "t7+s8DyIjRHJqxHmgLhWTbk4aMkCgYEA0jaIv4xEq9rjhneQ3cjNNn6hbunXqRuR\n" +
+            "8hxwDYFGhl6LcxhWF7chQGNl4TTJvXtTms4Umt+vcTJL7iSkLlpITQBWuyU310cL\n" +
+            "1+DXEh5J4f3iqmgkbdsVHcYmOOLQnuYssn7+cblB6dadSB/mZui6zkIrvopnoLVb\n" +
+            "YL9DjxioQIsCgYA5NZaHN73N7GHXJcuesA66j1y9I4E61Ha6MLO6kYRhxKycAn+H\n" +
+            "/JF32bEprhFXnx2NgEFPvHlWKK3wYEO18tkgoxDmu0OjnZdZcwOXlxTG/VlIpvNh\n" +
+            "1UQ/UmkcxK6uU/VALdpq4HFjr+mM09v1404iUrD9jweTLVKhq4p29ZCEWQKBgQCO\n" +
+            "mD+a7+OFUC4XAPRb/eJ2nN+VBTstk24k9fVss8zLSUb/A/siiy8bJlHtuok+53GH\n" +
+            "CVQg2qt/9cZb/K8CYmu5EAnFWTHP7nmyLuq1d6ZWjoo7XfmYK4zfbZJv9CvgHfMk\n" +
+            "AdFIA4savGJkkn8QP764O1rBHdG9ykf6EMQbRXackQKBgH4GygOQ3ndk+h+ArM78\n" +
+            "P8v0Cn8/30qHyQH8X8y9/BEk3Wz3aJ+AwgORaW4FZxVelWiOTJgHdZNy6Rg90q7I\n" +
+            "pKHE9hkL/dxsEBXkVdaRHzrfxCBxsP3b3EmOJ4xdl1phGhedUk/+RUWGY8VSnyaq\n" +
+            "uVMy1feUqV+AebfUSPzPVubV\n" +
+        "-----END PRIVATE KEY-----\n";
+
+    static final String encryptedClientKey =
+        "-----BEGIN ENCRYPTED PRIVATE KEY-----\n" +
+            "MIIE6TAbBgkqhkiG9w0BBQMwDgQIZuy9oMDzt8wCAggABIIEyH7Rbg5VcLV/ZDqp\n" +
+            "AJ6tzPqMlnLVtspB/qk5nZRz2/n42yaqgO2eO7DJ5qNbBGZ6KzLosC6DHd1aXA7E\n" +
+            "8l5RTWQj0u16ANeKsjJ86o5/MRJkeUMh2tPLdVcG0e5nifgbWzXYpS6LuwKQpGEL\n" +
+            "ufSAWhF9BY1hMe58qzCFBsFjrd0tB232sDOnhopaLiO5tQ6h3iMvwK/EPXJGPKHX\n" +
+            "5++DlV/QNaENb/EsCvPlrRgpSWn6dAJ1kemKcvLbnHzTwK88B1n3GUr1B3Z+0jSV\n" +
+            "9IGNeIRVz97KGxW0v33SD1Kw4QpPH+PHH/HhmJ48Vv/mf1rINwA/yRE0iS89glhW\n" +
+            "NPh5O7HD2l2qZZDq74RUfvSzaW738/108ppck/uo+nOvEHnl346XJ7Etbp+fRYj1\n" +
+            "y/irfT7yZ1ZMUkms39M+yBwCp/AqdhvJemOLCAgSJcWWWsNnNqlQlJJsuvkcRPBr\n" +
+            "foYAsPWnZRcowZLyQ80gG5ZKoTa8Kyf/V2CuULHwYv16MLf0koLpGAEjd4QDLrun\n" +
+            "ybsRisrAFupgxNBLovyh6I4b97n0Qeotf/iIfmBOH7azYqpmvgJxNB6hNVwz2LAM\n" +
+            "JgnirRgY+FEdAyTd44/qPQpwXoz4ac3wrdGEU0oLwKk/J2bX1Eqh3tfziPycCO6v\n" +
+            "2IC3WGP59/ty3T1xA/LI0TqInAkUD+0Rkp1HhmPL4xEL/bGLL5FViFkybjO6pgoK\n" +
+            "xjq/dqTbrnmjyhVIjfdCbl2MFEsqUgemH/5J5o75P6mS0HOBaK6ERI9tM6dHz5Wk\n" +
+            "2AbwUUvEebLA6zR5+57dFKSQXhOOekRCQ9w8gDhPHMxyYp3YAdiFitweY/5Xhokt\n" +
+            "l3OXtLPNiqONcYp/Xkz5/CgsjoIHDGgQGy4sWYUQYCdAFXP8/ogR4zVpsUBP0Hjv\n" +
+            "9isg5MlXEEj+YOFYQr/HbON9zxP4LhzuYUWynvVJDfPxY1x7JI+aequHZRPj9Qct\n" +
+            "X6Va5c+G8q87fj/qCJCplGslhJsxBidTQw88+wNeVAVPd+456bszwIE5ey7glvTb\n" +
+            "gwmU4mXDm75T+PW3Y7Gz1HZ9ZCMuUBelBEKxVSOOXCxkO4zzfI9PVxVzkqfurVOO\n" +
+            "9R9i3gV3J6rQj98XtbvBTSrq9nylnzRiFSP2T0aRNLVRmGBahm9YIK+kll/VM2uL\n" +
+            "VzUYPJv22Q/cXR2lkwNFbScOGNzJ3UXiuWOM1CkBgadoHpKhmD4IypvMpA/B0tTL\n" +
+            "l4kL0z7Cg50kXYY77pl6cxorRnPs9fTpPWTPymqWp8Se+pfn/Fxqz11xD0P45Rlo\n" +
+            "0CHLS3536c2zX3/1Uh4ZIGTAwoWeiCfizTnaS7GbIniqJ1/KVx9L5gIu/uWHGSnY\n" +
+            "sU/gOHQw+HxkYGXYI5AqWUp79CZUEnKsNVZDAexKWraG3TBJSydZcyl3OcEfez+f\n" +
+            "ISw//WpK5Wc+Zc+KabT8eWGwN1bIV/5HP1hSx6kggtQBs6BSkEDirzzzvQeIvdol\n" +
+            "ddjGSudEjO2EBE25utg+5omiR8uCQwwInNNOr2otMikmWRgQ3AuIuqxYAF0qH1+f\n" +
+            "n6ePw/b30oy4xIKWz8Kxrn8oPLYgs1Dl3lF6LjRwq1urC2leYb6ZPaX5QjdarMJp\n" +
+            "sxOT5nOmsaG0vinlsQ==\n" +
+        "-----END ENCRYPTED PRIVATE KEY-----";
+
+    static final String encryptedServerKey =
+        "-----BEGIN ENCRYPTED PRIVATE KEY-----\n" +
+            "MIIE6TAbBgkqhkiG9w0BBQMwDgQIto3hFjULIx0CAggABIIEyNWG0f8x6yY3XeE+\n" +
+            "WZjuFYe5WG4ftC8hvKgYCY1NhkFSH3R4FIkAdRanuskUI9rU6tzXEEVKpnsMNTf5\n" +
+            "AhQz/bI4fdu57vyz/ujwmdBMtP9m5hBjyfcJq6w/g1bECC6garbeTYvXgUljyV1n\n" +
+            "h8WQ4rClFhAZ1Qrujc6DTd63BGVoGxY+Jxe6FA2mZj2Z1bCsiWFGU0ShnU+QDF/O\n" +
+            "2SPcHHy37uckIdSrSFAuY8+iwQ5fL5mRH3Gw1CopQY07R8RKyRKpuZV56zJDXmGv\n" +
+            "j0NfJul26EmqXJSTLJyMlYdIP+xSPo50qTWqOl7w37fRcAw7duVP2jIlHJEJD9Y1\n" +
+            "UJe4ypihLp402Amw19wnsaxwk9PDDUd7kN1xFQMJ/eVF5k2hEL3m+2g6PbuFoSHt\n" +
+            "FVQEMDGqeAIPvrUr9FwO0qU7x0hJ1ce/v6IyYhJwbuVDzUFP0nHalTSVM/lXOshO\n" +
+            "dUqY97hOMh+q9drNUCQM08gq3XD/HQP/zA6LYH+X9Ts9Y0R+cocOXwbxc2jKAimo\n" +
+            "MwmP9bSD9MPabGGTv4ZeTVK9JTq4uetCk3ehqNEpS7d69bq0pJ3F6xvGaL7GjiQA\n" +
+            "8YyEUFtbyaBDgg4iwIiPsy/jo7h6Gj1Io/TjcDnp38h1YMTrER2nljB8lSm3t7DA\n" +
+            "5KOzLKHvvB8gUfMiM3OzbIcXtBJIWWidELToqEljAVHWt054yaI4oX1laHOa3zPG\n" +
+            "yriTUARkp5lA2llrm9E1AevlOxpUz3cr94ohvSI40QZgehcvraHJ7FCP7SUi54uY\n" +
+            "o7MRDe27zLsjYRoSLD6saSFL1XigjCa4dpOu3yN8q/R0NyjmBySxOlPbglyl/OmD\n" +
+            "czWFvUQcHcuSsPosbp30nsDD5SIGY6p+XR8tqJdKrCh+vgFlQPb1OkvGcXD7uzjI\n" +
+            "0jYVc23pUwrM3oynAjll2uEBlMfHsdYTB5ehv/sp7tqnc3/VbPhW9WOercbQvVnq\n" +
+            "Gb13aL4ra94glERJFH5LitvmjkylD6tjOttDONYg9ow1dA5g2q/A7WD0UV4TrW2w\n" +
+            "LRze30kKG2p94DE1if54VMA91QLhU3YwntDSk8jZ6bO7rG91d98npwHWZfMzJI6J\n" +
+            "blHM5gaAmHCTvwX6/Px/tS+8HmG5l8BBBqz70gh24uYjMgsTWsfRsjfypBmXzEFt\n" +
+            "JiFJIBwGQ0uHDM2JXqIdCf1em875eK3XR6qvrQDPiA5HVXFaGdRGbx0aK7lzDHrn\n" +
+            "7cn02kNLb4/kbZnpj04p7/j6dpfGLMnkSH7Pxx4f0cfirfDvcZQwjpl/n4bHnIxT\n" +
+            "Qv6XWq67dQObvbiCZF14CayEEqNU6Q9wq+EBji2739FhAAbZ0P1Uj792wk50tOgg\n" +
+            "+EqXzCGaqSRn7RrMEiQQTGBson9L6aBlNxyBRH6LMLAsvcKzlYB5zMQHwObd/eaU\n" +
+            "43WdwOly9CLeJbTpPNvZoeDGT7jdin3XiuauMnDa4W1wwKHKgpbD543ScZfIkNm1\n" +
+            "Wcibp27rE4/5eIsNyQnxkDRN7f3x0a9iwhlgrToXGabileLsonx3Jdf/YXgiqiQY\n" +
+            "4lLGr4EIVjKg8YbJvpnrEZQNeTHwPr8/N5N51VZvoVePBq/ZnBi9EK/aCL+f+0Dx\n" +
+            "yZio9eDGsuOXwR7iOg==\n" +
+        "-----END ENCRYPTED PRIVATE KEY-----";
+
+    static final String encryptedArbitraryKey =
+        "-----BEGIN ENCRYPTED PRIVATE KEY-----\n" +
+            "MIIE6TAbBgkqhkiG9w0BBQMwDgQIT+Z6cmw0caMCAggABIIEyCCZWzPzrAGr7nsi\n" +
+            "ifNxQbX0GO6wYb3ND7yinc/0Yw+d38l0S5LTLg+5GD7SbXfoMmC44rUxSGnBV5Bj\n" +
+            "/bUW30B/s0+I3Ya1pXXyVP98vjy71Pspw/sHH3tYU73fPdqzh538Jf9qEgqgaJej\n" +
+            "MRdiQfQmXexmkyeWAYQutbONrwfuH8NqLGxuPjrc21WjKkijnv9X8CuUk6M1MCPc\n" +
+            "jN2iPhTsYiOkgKkyjF817792jDBYJHtoOuG5OmhSAdw6I3Tm5TRqAO/6IGPuDqWb\n" +
+            "7ho3ZyLBxBaLHTOawQkGm842lhwn5fACCCYNGsQhVGdbZN4KVgSYzJwvGw8vaawE\n" +
+            "VhD+zcDzhAydlhMXwFxDMgoZD5qRZujuDoBLgBYrDFDDEUfDNoUJSmWPpHXVc2tq\n" +
+            "RBiP3xb152lWiqRcXLZVHfXfxT79qZAxvlRBUgYEA/u2ud3p2/7R9UBvRzE6P6IA\n" +
+            "9ZuO6CgJgGvHXY2u721/MvvhY/75X1c1t0VJsdU2H6+E34QDtSqmQD08zBlZw5TK\n" +
+            "qltQpxgjMO1jtdEuHWPbR5Aoa5smOL9qBleptSGsm0I5zET+hv4y3f5AHIx3X3xy\n" +
+            "UH6Nw30oQtSbbZzPX4sqZuzA66W9Z4yW3Pn1EYcpuacypty/gvRZrvnwb6gYNB+h\n" +
+            "XTTaikNiGebcAZhSukcKmL3mjlpbUZC3Bjbz5AstZJyd0tMSaw0E4C8VQQHYyX5X\n" +
+            "lteW1O2TvHyGdeb0LNwsXzIMqsqmgsuNsms12Xx7KurxhPGk3Za2SdXQUASSryWX\n" +
+            "XPlKxlQDDM9DvSQsRw6BCUoUKZ/2YiXnnL/zrY1+xegXumwolgT8Dsu6CFxvzT7E\n" +
+            "16/4FeaVDB0/A5nmR71gkwm/b/JJL/Qj0H+E0rhCyZJeW9ddg4IWolEFTvCrVmHG\n" +
+            "gZ8DQIHJHSPilG8gdjzJ8cs89pctswnjuLMI2wT2/jqtM76Ibr9d0eIB6CzT17dh\n" +
+            "ASchmnUUXgjY/djAO8M3LuMUgu6OTCpIwAjfz7DOaakI42Dvrz0sTCkBwjD3XY7v\n" +
+            "1EyxoxCPoxTlHE1BLNgT5OD0B9SPC9aQLt+DOcDXVh8vQvIGtqMbxthIoi61XTeo\n" +
+            "sm35wkdiC0wSCgqPKIiz/LcyaWcOwbK2F7Q1dKluBT29L6X35FLqbp8RNZLJSX18\n" +
+            "53gJJLH4Hd7XcAn2Sat+vyuY3Z3vNiFMmx+vbho3ZgGL5KTNdmpN1fM7EqpGOUfp\n" +
+            "easRPOw3P1KuVqTVCvm/osf4VniV3xCvgwffJ/kOlHChnIrHGZpeBFmiV4xk7ucb\n" +
+            "7PSVt8BLV2+krXSTijFepeuVckitj3Kf4o++WePE6wlPi0rPYG47IE10xHojSKgr\n" +
+            "xdEonxiGL07/MEdI9+9PHLg5+ZakymRmMnEcJdpd7+WxaFy8jhvCCZ+7ppZBpYQC\n" +
+            "azdkciNz9d/YZRQH5M6VGk1VqqkQbenasUuZpXAxDOraLPlq801JyYyPqeROAgb8\n" +
+            "hTQXseAe+7lHWpmUGKuN0Y9EcV/3fiAP+PeMt/QXeGF5jcRVGWLs6nDVOaBQ/SB3\n" +
+            "syFXwJR6WtajR5OC7dEGZPTxaNfiu4GFuE73i3cVEJH2MH7+c/yB/40RJQdJ4kd6\n" +
+            "7KJYwr8P4TrNulM60Q==\n" +
+        "-----END ENCRYPTED PRIVATE KEY-----\n";
+
+    static final String secretPassword = "secret";
 }
diff --git a/oak-segment-tar/src/test/java/org/apache/jackrabbit/oak/segment/standby/server/SlowServerIT.java b/oak-segment-tar/src/test/java/org/apache/jackrabbit/oak/segment/standby/server/SlowServerIT.java
index 44e7fdf..0edecca 100644
--- a/oak-segment-tar/src/test/java/org/apache/jackrabbit/oak/segment/standby/server/SlowServerIT.java
+++ b/oak-segment-tar/src/test/java/org/apache/jackrabbit/oak/segment/standby/server/SlowServerIT.java
@@ -97,7 +97,15 @@ public class SlowServerIT {
             // binary is requested, the delay on the server guarantees that the
             // timeout on the client will expire.
 
-            StandbyClientSync client = new StandbyClientSync("localhost", serverPort.getPort(), secondary, false, 1000, false, folder.newFolder())
+            StandbyClientSync client = StandbyClientSync.builder()
+                .withHost("localhost")
+                .withPort(serverPort.getPort())
+                .withFileStore(secondary)
+                .withSecureConnection(false)
+                .withReadTimeoutMs(1000)
+                .withAutoClean(false)
+                .withSpoolFolder(folder.newFolder())
+                .build()
         ) {
             server.start();
             client.run();