Android Couchbase API

Already read:

Good evening,

I am trying to use push-only syncing in some data that are created and saved in Android devices.

The previous owner of the project left a mess at my hands - he said that it used to be working but now it doesn’t due to complex network configurations / self-signed certificates.

However, I have already verified connectivity through simple GET requests, by creating a custom SSLSocketFactory / HostnameVerifier (custom SSLFactory is passed to Couchbase Module, custom HostnameVerifier is set as default by HttpsURLConnection.setDefaultHostnameVerifier).

I get timeout messages when no action is taken, but nothing when everything is set. However no data appear on the server-side database. This is my first time working with Couchbase and I might be missing a lot about the proper way of doing anything there, but I think I covered all the bases.

Android:

compileSdkVersion 25
buildToolsVersion ‘24.0.2’
defaultConfig {
minSdkVersion 14
targetSdkVersion 25
}

[…]

‘com.couchbase.lite:couchbase-lite-android:1.3.1’

Couchbase Sync Gateway/master(d41bd84) (I tried to update, but unfortunately I cannot bind to the db)

Couchbase Server: Version: 4.0.0-4051 Community Edition (build-4051) (I am not sure how straightforward an update would be, since it is also built inside a Docker container, of which I have no experience of)

From your post, it sounds like you are using CBL 1.3.1.

However, I have already verified connectivity through simple GET requests, by creating a custom SSLSocketFactory / HostnameVerifier (custom SSLFactory is passed to Couchbase Module, custom HostnameVerifier is set as default by HttpsURLConnection.setDefaultHostnameVerifier).

What is the Couchbase Module? Can you provide some code to understand how the self signed certificate is passed to the HTTPClient? And whether you are using the Couchbase Lite replications or sending HTTP requests to the Sync Gateway REST API “directly”?

Hi @sntentos,

I believe you are facing the same issue with https://github.com/couchbase/couchbase-lite-android/issues/1059.

if you are using allowSelfSignedSSLCertificates() convenience method, the issue is fixed recently. you could try to use Index of /maven2/com/couchbase/lite/couchbase-lite-android/1.4-27 .

If you write SSLSocketFactory by yourself, the key is getAcceptedIssuers() must return empty array instead of null.

Hope above information could fix your issue.
Thanks,
Hideki

private void setupDb() {
try {
	AndroidContext androidContext = new AndroidContext(getApplicationContext());
	dbManager = new Manager(androidContext, Manager.DEFAULT_OPTIONS);
	Log.i(TAG, "Database manager created");
} catch (IOException e) {
	Log.e(TAG, "Database manager creation failed");
	return;
}

//Create database
if (!Manager.isValidDatabaseName(dbName)) {
	Log.e(TAG, "Invalid database name");
	return;
}

try {
	db = dbManager.getDatabase(dbName);

	Log.i(TAG, "Created database");
} catch (CouchbaseLiteException e) {
	Log.e(TAG, "Error creating database");
	return;
}

// So, this code eliminates ALL errors of SSL.
// But DB has no new data, so I need to worry about other stuff.
// Maybe find a way to verify if a document has been sent?

CouchbaseLiteHttpClientFactory f = new CouchbaseLiteHttpClientFactory(db.getPersistentCookieStore());
try {
	f.setSSLSocketFactory(initializeSecurity());
} catch (Exception E) {
	Log.e(TAG, "Setting of SSL socket factory failed with: " + E.toString());
	return;
}
dbManager.setDefaultHttpClientFactory(f);
}

private SSLSocketFactory initializeSecurity() throws IOException, CertificateException, KeyStoreException, NoSuchAlgorithmException, KeyManagementException, InterruptedException, UnrecoverableKeyException {
	try {
		KeyStore trusted = KeyStore.getInstance("BKS");
		InputStream in = new BufferedInputStream(getAssets().open("silentCSStore.bks"));
		trusted.load(in, repl_pwd.toCharArray());	// Warning: .bks store password was manually set to this string!
		in.close();

		HttpsURLConnection.setDefaultHostnameVerifier(new AdditionalHostnameVerifier(HttpsURLConnection.getDefaultHostnameVerifier()));

		return new AdditionalKeyStoresSSLSocketFactory(trusted);
	} catch (Exception e) {
		throw new AssertionError(e);
	}
}

public class AdditionalHostnameVerifier implements HostnameVerifier {
	private final HostnameVerifier original;

	public AdditionalHostnameVerifier(HostnameVerifier originalHostnameVerifier) {
		this.original = originalHostnameVerifier;
	}

	@Override
	public boolean verify(String s, SSLSession sslSession) {
		final boolean originallyVerified = original.verify(s, sslSession);

		if (originallyVerified) {
			return true;
		} else {
			if (s.equals("silent.cs.abo.fi")) {
				Log.i(MonitoringActivity.getTag(), "Force-Approving certificate for " + s);
				return true;
			} else {
				Log.w(MonitoringActivity.getTag(), "Allow-Blocking certificate for " + s);
				return false;
			}
		}
	}
}

and more or less, a compilable version of this code (http://stackoverflow.com/questions/32969952/android-to-server-communication-using-ssl-with-bouncy-castle/33147202)

I tried to add the repository:

repositories {
	flatDir {
		dirs 'libs'
	}
	maven {
		url "http://files.couchbase.com/maven2/"
	}
	mavenCentral()
}

Unfortunately, compile 'com.couchbase.lite:couchbase-lite-android:+' still gives me 1.3.1 to autocomplete

To compliment my answer to jamiltz, part of SSLSocketFactory has this code for getAcceptedIssuers():

		public X509Certificate[] getAcceptedIssuers() {
			final ArrayList<X509Certificate> list = new ArrayList<X509Certificate>();
			for (X509TrustManager tm : x509TrustManagers) {
				list.addAll(Arrays.asList(tm.getAcceptedIssuers()));
			}
			return list.toArray(new X509Certificate[list.size()]);
		}

I will test changing the code tomorrow though (I need to move the phone inside the network in order to run the (only) possible test (as of now)

you could write:

‘com.couchbase.lite:couchbase-lite-android:1.4-27’

I really don’t know what to make out of the results.

Using allowSelfSignedSSLCertificates() convenience method, I get

01-30 12:19:42.544 3856-4260/fi.abo.signav W/RemoteRequest: com.couchbase.lite.replicator.RemoteRequest {GET, https://server/bucket/_local/ba173e52053639e34e7f0a7e12e89bbaf041c5b7}: executeRequest() Exception: javax.net.ssl.SSLPeerUnverifiedException: Hostname server not verified:
        certificate: sha256/caHUXyhGESZo2ETOnGYWcnymS5k3RdQsdbQJ8v+Q3+I=
        subjectAltNames: [].  url: https://server/bucket/_local/ba173e52053639e34e7f0a7e12e89bbaf041c5b7
    javax.net.ssl.SSLPeerUnverifiedException: Hostname server not verified:
        certificate: sha256/caHUXyhGESZo2ETOnGYWcnymS5k3RdQsdbQJ8v+Q3+I=
        subjectAltNames: []
        at okhttp3.internal.io.RealConnection.connectTls(RealConnection.java:248)
        at okhttp3.internal.io.RealConnection.establishProtocol(RealConnection.java:196)
        at okhttp3.internal.io.RealConnection.buildConnection(RealConnection.java:171)
        at okhttp3.internal.io.RealConnection.connect(RealConnection.java:111)
        at okhttp3.internal.http.StreamAllocation.findConnection(StreamAllocation.java:187)
        at okhttp3.internal.http.StreamAllocation.findHealthyConnection(StreamAllocation.java:123)
        at okhttp3.internal.http.StreamAllocation.newStream(StreamAllocation.java:93)
        at okhttp3.internal.http.HttpEngine.connect(HttpEngine.java:296)
        at okhttp3.internal.http.HttpEngine.sendRequest(HttpEngine.java:248)
        at okhttp3.RealCall.getResponse(RealCall.java:243)
        at okhttp3.RealCall$ApplicationInterceptorChain.proceed(RealCall.java:201)
        at okhttp3.RealCall.getResponseWithInterceptorChain(RealCall.java:163)
        at okhttp3.RealCall.execute(RealCall.java:57)
        at com.couchbase.lite.replicator.RemoteRequest.executeRequest(RemoteRequest.java:261)
        at com.couchbase.lite.replicator.RemoteRequest.execute(RemoteRequest.java:165)
        at com.couchbase.lite.replicator.RemoteRequest.run(RemoteRequest.java:105)
        at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:422)
        at java.util.concurrent.FutureTask.run(FutureTask.java:237)
        at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$201(ScheduledThreadPoolExecutor.java:152)
        at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:265)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1112)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:587)
        at java.lang.Thread.run(Thread.java:818)

However, with my SSLFactory, I get no messages at all. Either with return list.toArray(new X509Certificate[list.size()]); or return new X509Certificate[0];

Both methods always talk to the server (1 rqs/sec). It’s a get request for some document (only 4 auto-created documents exist)

Hi @sntentos,

javax.net.ssl.SSLPeerUnverifiedException: Hostname server not verified:

It seems the server hostname verification causing the issue.

I filed the ticket for this issue.
https://github.com/couchbase/couchbase-lite-java-core/issues/1575

Can you share the test Sync Gateway or CouchDB URL I can test against?

Thanks!

Database is behind a firewall I don’t have access to - and they are not willing to open up ports, unfortunately.

I just need API access to give strings to a verifier method like this:

public class AdditionalHostnameVerifier implements HostnameVerifier {
	private final HostnameVerifier original;

	public AdditionalHostnameVerifier(HostnameVerifier originalHostnameVerifier) {
		this.original = originalHostnameVerifier;
	}

	@Override
	public boolean verify(String s, SSLSession sslSession) {
		final boolean originallyVerified = original.verify(s, sslSession);

		if (originallyVerified) {
			return true;
		} else {
			if (s.equals("server-hostname-string")) {
				Log.i(MonitoringActivity.getTag(), "Force-Approving certificate for " + s);
				return true;
			} else {
				Log.w(MonitoringActivity.getTag(), "Allow-Blocking certificate for " + s);
				return false;
			}
		}
	}
}

Hi @sntentos,

The build 1.4-32 includes the fix for this issue. allowSelfSignedSSLCertificates() allows to bypass the hostname verification. And CouchbaseLiteHttpClientFactory allows to set HostnameVerifier.
http://files.couchbase.com/maven2/com/couchbase/lite/couchbase-lite-android/1.4-32/

Let us know if new build satisfies your requirements.

Thanks!

I apologize for the extra-long delay. I had to refactor my code to verify that custom-made SSLSocketFactory and HostnameVerifier were usable.

Although easy, it took a lot of effort for my part :joy:

However, I do verify both solutions (including allowSelfSignedSSLCertificates() convenience method) work as expected in v1.4-41 (Always keeping up with the bleeding edge)

1 Like

@sntentos Thank you very much for confirming the fix!