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:26:17 UTC

[jackrabbit-oak] branch issues/1.22/OAK-9451 created (now a92561a)

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

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


      at a92561a  OAK-9451 - Cold Standby SSL certificates should be configurable Backport from trunk (Oak-9451 Oak-9473)

This branch includes the following new commits:

     new a92561a  OAK-9451 - Cold Standby SSL certificates should be configurable Backport from trunk (Oak-9451 Oak-9473)

The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


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

Posted by ad...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit a92561a178c068aa8a77313eedc84b8f3378a99f
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();