Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CBL-1169: Implement Client Cert Auth #9

Merged
merged 2 commits into from
Aug 4, 2020
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
Implement Client Cert Authentication
* Updated AbstractCBLWebSocket to use KeyManager for client cert auth.
* Fixed native_c4listiener to setup client certs auth with rootClientCerts pr callback properly.
  • Loading branch information
pasin committed Aug 1, 2020
commit a60dd58de0cf285ecb16781f836a2115f317260e
15 changes: 11 additions & 4 deletions common/main/cpp/native_c4listener.cc
Original file line number Diff line number Diff line change
Expand Up @@ -564,14 +564,21 @@ JNICALL Java_com_couchbase_lite_internal_core_impl_NativeC4Listener_startTls(
jboolean allowPush,
jboolean allowPull,
jboolean enableDeltaSync) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

whoops! Great catch.

C4TLSConfig tlsConfig;
C4TLSConfig tlsConfig = {};
tlsConfig.privateKeyRepresentation = kC4PrivateKeyFromKey;
tlsConfig.key = (C4KeyPair *) keyPair;
tlsConfig.certificate = getCert(env, cert);

// Client Cert Authentication:
tlsConfig.requireClientCerts = requireClientCerts;
tlsConfig.rootClientCerts = getCert(env, rootClientCerts);
tlsConfig.certAuthCallback = &certAuthCallback;
tlsConfig.tlsCallbackContext = reinterpret_cast<void *>(context);
if (requireClientCerts == true) {
if (rootClientCerts != NULL) {
tlsConfig.rootClientCerts = getCert(env, rootClientCerts);
} else {
tlsConfig.certAuthCallback = &certAuthCallback;
tlsConfig.tlsCallbackContext = reinterpret_cast<void *>(context);
}
}

return reinterpret_cast<jlong>(startListener(
env,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@

import javax.net.ssl.KeyManager;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLHandshakeException;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
Expand All @@ -57,6 +58,7 @@
import com.couchbase.lite.internal.core.C4Replicator;
import com.couchbase.lite.internal.core.C4Socket;
import com.couchbase.lite.internal.core.C4WebSocketCloseCode;
import com.couchbase.lite.internal.core.NativeContext;
import com.couchbase.lite.internal.fleece.FLEncoder;
import com.couchbase.lite.internal.fleece.FLValue;
import com.couchbase.lite.internal.support.Log;
Expand Down Expand Up @@ -225,11 +227,20 @@ else if (scheme.equalsIgnoreCase(C4Replicator.C4_REPLICATOR_TLS_SCHEME_2)) {
}

try { return new CBLWebSocket(handle, scheme, hostname, port, path, fleeceOptions, serverCertsListener); }
catch (Exception e) { Log.e(TAG, "Failed to instantiate CBLWebSocket", e); }
catch (Exception e) {
Log.e(TAG, "Failed to instantiate CBLWebSocket", e);
}

return null;
}

//-------------------------------------------------------------------------
// Client Certificate Authentication Identities
//-------------------------------------------------------------------------

@NonNull
public static final NativeContext<KeyManager> CLIENT_CERT_AUTH_KEY_MANAGER = new NativeContext<>();

//-------------------------------------------------------------------------
// Member Variables
//-------------------------------------------------------------------------
Expand Down Expand Up @@ -310,7 +321,7 @@ private OkHttpClient setupOkHttpClient() throws GeneralSecurityException {
final OkHttpClient.Builder builder = BASE_HTTP_CLIENT.newBuilder();

// authenticator
final Authenticator authenticator = setupAuthenticator();
final Authenticator authenticator = setupBasicAuthenticator();
if (authenticator != null) { builder.authenticator(authenticator); }

// setup SSLFactory and trusted certificate (pinned certificate)
Expand All @@ -319,11 +330,15 @@ private OkHttpClient setupOkHttpClient() throws GeneralSecurityException {
return builder.build();
}

private Authenticator setupAuthenticator() {
private Authenticator setupBasicAuthenticator() {
if (options != null && options.containsKey(C4Replicator.REPLICATOR_OPTION_AUTHENTICATION)) {
@SuppressWarnings("unchecked") final Map<String, Object> auth
@SuppressWarnings("unchecked")
final Map<String, Object> auth
= (Map<String, Object>) options.get(C4Replicator.REPLICATOR_OPTION_AUTHENTICATION);
if (auth != null) {
if (auth == null) { return null; }

final String authType = (String) auth.get(C4Replicator.REPLICATOR_AUTH_TYPE);
if (C4Replicator.AUTH_TYPE_BASIC.equals(authType)) {
final String username = (String) auth.get(C4Replicator.REPLICATOR_AUTH_USER_NAME);
final String password = (String) auth.get(C4Replicator.REPLICATOR_AUTH_PASSWORD);
if (username != null && password != null) {
Expand All @@ -346,7 +361,6 @@ private Authenticator setupAuthenticator() {
}
}
}

return null;
};
}
Expand Down Expand Up @@ -471,6 +485,14 @@ private void didClose(Throwable error) {
return;
}

if (error instanceof SSLHandshakeException) {
closed(
C4Constants.ErrorDomain.NETWORK,
C4Constants.NetworkError.TLS_HANDSHAKE_FAILED,
null);
return;
}

closed(C4Constants.ErrorDomain.WEB_SOCKET, 0, null);
}

Expand All @@ -490,28 +512,56 @@ private String checkScheme(String scheme) {
}

private void setupSSLSocketFactory(OkHttpClient.Builder builder) throws GeneralSecurityException {
byte[] pin = null;
byte[] pinnedServerCert = null;
boolean acceptOnlySelfSignedServerCert = false;
KeyManager clientCertAuthKeyManager = null;
if (options != null) {
// Pinned Certificate:
if (options.containsKey(C4Replicator.REPLICATOR_OPTION_PINNED_SERVER_CERT)) {
pin = (byte[]) options.get(C4Replicator.REPLICATOR_OPTION_PINNED_SERVER_CERT);
pinnedServerCert = (byte[]) options.get(C4Replicator.REPLICATOR_OPTION_PINNED_SERVER_CERT);
}

// Accept only self-signed server cert mode:
if (options.containsKey(C4Replicator.REPLICATOR_OPTION_SELF_SIGNED_SERVER_CERT)) {
acceptOnlySelfSignedServerCert =
(boolean) options.get(C4Replicator.REPLICATOR_OPTION_SELF_SIGNED_SERVER_CERT);
acceptOnlySelfSignedServerCert = (boolean)
options.get(C4Replicator.REPLICATOR_OPTION_SELF_SIGNED_SERVER_CERT);
}

// Client Certificate Authentication:
@SuppressWarnings("unchecked")
final Map<String, Object> auth
= (Map<String, Object>) options.get(C4Replicator.REPLICATOR_OPTION_AUTHENTICATION);
if (auth != null) {
final String authType = (String) auth.get(C4Replicator.REPLICATOR_AUTH_TYPE);
if (C4Replicator.AUTH_TYPE_CLIENT_CERT.equals(authType)) {
final long token = (long) auth.get(C4Replicator.REPLICATOR_AUTH_CLIENT_CERT_KEY);
clientCertAuthKeyManager = CLIENT_CERT_AUTH_KEY_MANAGER.getObjFromContext(token);
if (clientCertAuthKeyManager == null) {
Log.w(TAG, "No key manager configured for client certificate authentication");
}
}
}
}

// KeyManager for client cert authentication:
KeyManager[] keyManagers = null;
if (clientCertAuthKeyManager != null) {
keyManagers = new KeyManager[] { clientCertAuthKeyManager };
}

// TrustManager for server cert verification:
final X509TrustManager trustManager = new CBLTrustManager(
pin, acceptOnlySelfSignedServerCert, serverCertsListener);
pinnedServerCert, acceptOnlySelfSignedServerCert, serverCertsListener);

SSLContext.getInstance("TLS").init(null, new TrustManager[] {trustManager}, null);
final SSLSocketFactory sslSocketFactory = new TLSSocketFactory(null, new TrustManager[] {trustManager}, null);
// SSLSocketFactory:
final SSLSocketFactory sslSocketFactory = new TLSSocketFactory(
keyManagers, new TrustManager[] { trustManager }, null);
builder.sslSocketFactory(sslSocketFactory, trustManager);

if (pin != null || acceptOnlySelfSignedServerCert) {
// As the certificate will need to be matched with the pinned certificate, accepts any
// host name specified in the certificate.
// HostnameVerifier:
if (pinnedServerCert != null || acceptOnlySelfSignedServerCert) {
// As the certificate will need to be matched with the pinned certificate,
// accepts any host name specified in the certificate.
builder.hostnameVerifier((s, sslSession) -> true);
}
}
Expand Down
15 changes: 15 additions & 0 deletions common/test/java/com/couchbase/lite/BaseReplicatorTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,21 @@ protected final Replicator run(URI url, boolean push, boolean pull, boolean cont
return run(config);
}

protected final Replicator run(
int expectedErrorCode,
String expectedErrorDomain,
URI url,
boolean push,
boolean pull,
boolean continuous,
Authenticator auth,
Certificate pinnedServerCert)
throws CouchbaseLiteException {
final ReplicatorConfiguration config = makeConfig(push, pull, continuous, new URLEndpoint(url), pinnedServerCert);
if (auth != null) { config.setAuthenticator(auth); }
return run(config, expectedErrorCode, expectedErrorDomain, false, false, null);
}

protected final Replicator run(
ReplicatorConfiguration config,
int expectedErrorCode,
Expand Down