Android 401 error when trying to connect to sync gateway, custom key store


#1

Hello,

I am trying to connect to a sync gateway using basic authentication over https with a custom keystore. I keep getting 401 Unauthorized error. The user is already set up on the gateway and can be accessed via httpie. I am not quite sure what is wrong with my code:

private void tryBasicAuthWithUpdatedTrustStore() throws KeyStoreException {
    this.trustStore = KeyStore.getInstance("BKS");
    InputStream in = getResources().openRawResource(R.raw.<KEYSTORE_NAME>);
    try {
        trustStore.load(in, "<PASSWORD>".toCharArray());
    } catch (IOException e) {
        e.printStackTrace();
    } catch (NoSuchAlgorithmException e) {
        e.printStackTrace();
    } catch (CertificateException e) {
        e.printStackTrace();
    }

    // Get persistent cookies
    cookieStore = database.getPersistentCookieStore();

    // Make HttpClientFactory

    HttpClientFactory httpClientFactory = new HttpClientFactory()  {
        private CookieStore cs = cookieStore;


        @Override
        public HttpClient getHttpClient() {
            SchemeRegistry schemeRegistry = new SchemeRegistry();
            schemeRegistry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80));
            try {
                schemeRegistry.register(new Scheme("https", new SSLSocketFactory(trustStore), 443));
            } catch (NoSuchAlgorithmException e) {
                e.printStackTrace();
            } catch (KeyManagementException e) {
                e.printStackTrace();
            } catch (KeyStoreException e) {
                e.printStackTrace();
            } catch (UnrecoverableKeyException e) {
                e.printStackTrace();
            }
            HttpParams params = new BasicHttpParams();

            ClientConnectionManager cm = new ThreadSafeClientConnManager(params, schemeRegistry);
            DefaultHttpClient client = new DefaultHttpClient(cm, params);

            synchronized (this) {
                client.setCookieStore(cookieStore);
            }

            return client;
        }

        @Override
        public void addCookies(List<Cookie> cookies) {
            if (cookieStore == null) {
                return;
            }
            synchronized (this) {
                for (Cookie cookie : cookies) {
                    cookieStore.addCookie(cookie);
                }
            }
        }

        @Override
        public void deleteCookie(String name) {
            if (cookieStore == null) {
                return;
            }
            List<Cookie> cookies = cookieStore.getCookies();
            List<Cookie> retainedCookies = new ArrayList<Cookie>();
            for (Cookie cookie : cookies) {
                if (!cookie.getName().equals(name)) {
                    retainedCookies.add(cookie);
                }
            }
            cookieStore.clear();
            for (Cookie retainedCookie : retainedCookies) {
                cookieStore.addCookie(retainedCookie);
            }
        }

        @Override
        public CookieStore getCookieStore() {
            return cookieStore;
        }
    };

    URL url = null;
    try {
        url = new URL(ReplicationTest.SYNC_URL);
    } catch (MalformedURLException e) {
        e.printStackTrace();
    }

    Replication pull = new Replication(this.database, url, Replication.Direction.PULL, httpClientFactory, this.manager.getWorkExecutor());

    pull.setContinuous(true);
    Authenticator auth = new BasicAuthenticator(username, password);
    pull.setAuthenticator(auth);
    pull.start();

Stack trace:

06-08 08:38:57.092 15886-15928/com.icfi.couchbasereplicationtest E/RemoteRequest﹕ Got error status: 401 for SYNC_URL. Reason: Unauthorized
06-08 08:38:57.092 15886-15928/com.icfi.couchbasereplicationtest E/Sync﹕ com.couchbase.lite.replicator.ReplicationInternal$4@40ee2038: Session check failed
org.apache.http.client.HttpResponseException: Unauthorized
at com.couchbase.lite.support.RemoteRequest.executeRequest(RemoteRequest.java:222)
at com.couchbase.lite.support.RemoteRequest.run(RemoteRequest.java:104)
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:390)
at java.util.concurrent.FutureTask.run(FutureTask.java:234)
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$201(ScheduledThreadPoolExecutor.java:153)
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:267)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1080)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:573)
at java.lang.Thread.run(Thread.java:856)
06-08 08:38:57.092 15886-15928/com.icfi.couchbasereplicationtest E/Sync﹕ com.couchbase.lite.replicator.PullerInternal@40ed58a0: Progress: set error = org.apache.http.client.HttpResponseException: Unauthorized


Android Couchbase API
#2

@jamiltz are you able to help here?


#3

Hi @kvbutler,

I don’t know why you need custom key store. Please try following code. If this does not work, please let us know!

URL url = null;
try {
    url = new URL(ReplicationTest.SYNC_URL);
} catch (MalformedURLException e) {
    e.printStackTrace();
}
Replication pull = database.createPullReplication(url);
pull.setContinuous(true);
Authenticator auth = AuthenticatorFactory.createBasicAuthenticator(username, password)
pull.setAuthenticator(auth);
pull.start();

Thanks!
Hideki


#4

Thanks for the responses guys!

Hideki:

The code produced the following errors (I tried something similar during my debugging, it’s why I figured I needed the custom key store.):

06-08 15:06:32.023 29789-29874/com.icfi.couchbasereplicationtest E/RemoteRequest﹕ io exception. url: https:/SYNC_URL/_session
javax.net.ssl.SSLPeerUnverifiedException: No peer certificate
at org.apache.harmony.xnet.provider.jsse.SSLSessionImpl.getPeerCertificates(SSLSessionImpl.java:137)
at org.apache.http.conn.ssl.AbstractVerifier.verify(AbstractVerifier.java:93)
at org.apache.http.conn.ssl.SSLSocketFactory.createSocket(SSLSocketFactory.java:381)
at org.apache.http.impl.conn.DefaultClientConnectionOperator.openConnection(DefaultClientConnectionOperator.java:165)
at org.apache.http.impl.conn.AbstractPoolEntry.open(AbstractPoolEntry.java:164)
at org.apache.http.impl.conn.AbstractPooledConnAdapter.open(AbstractPooledConnAdapter.java:119)
at org.apache.http.impl.client.DefaultRequestDirector.execute(DefaultRequestDirector.java:360)
at org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:555)
at org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:487)
at org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:465)
at com.couchbase.lite.support.RemoteRequest.executeRequest(RemoteRequest.java:201)
at com.couchbase.lite.support.RemoteRequest.run(RemoteRequest.java:104)
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:390)
at java.util.concurrent.FutureTask.run(FutureTask.java:234)
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$201(ScheduledThreadPoolExecutor.java:153)
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:267)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1080)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:573)
at java.lang.Thread.run(Thread.java:856)
06-08 15:06:32.023 29789-29874/com.icfi.couchbasereplicationtest E/Sync﹕ com.couchbase.lite.replicator.ReplicationInternal$4@40ee03d8: Session check failed
javax.net.ssl.SSLPeerUnverifiedException: No peer certificate
at org.apache.harmony.xnet.provider.jsse.SSLSessionImpl.getPeerCertificates(SSLSessionImpl.java:137)
at org.apache.http.conn.ssl.AbstractVerifier.verify(AbstractVerifier.java:93)
at org.apache.http.conn.ssl.SSLSocketFactory.createSocket(SSLSocketFactory.java:381)
at org.apache.http.impl.conn.DefaultClientConnectionOperator.openConnection(DefaultClientConnectionOperator.java:165)
at org.apache.http.impl.conn.AbstractPoolEntry.open(AbstractPoolEntry.java:164)
at org.apache.http.impl.conn.AbstractPooledConnAdapter.open(AbstractPooledConnAdapter.java:119)
at org.apache.http.impl.client.DefaultRequestDirector.execute(DefaultRequestDirector.java:360)
at org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:555)
at org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:487)
at org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:465)
at com.couchbase.lite.support.RemoteRequest.executeRequest(RemoteRequest.java:201)
at com.couchbase.lite.support.RemoteRequest.run(RemoteRequest.java:104)
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:390)
at java.util.concurrent.FutureTask.run(FutureTask.java:234)
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$201(ScheduledThreadPoolExecutor.java:153)
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:267)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1080)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:573)
at java.lang.Thread.run(Thread.java:856)
06-08 15:06:32.023 29789-29874/com.icfi.couchbasereplicationtest E/Sync﹕ com.couchbase.lite.replicator.PullerInternal@40ed1078: Progress: set error = javax.net.ssl.SSLPeerUnverifiedException: No peer certificate


#5

Hi @kvbutler,

I assume problem is caused by SSL certificate on Sync Sateway.

  1. Does replication work with HTTP (not HTTPS)
  2. Check SSL certificate by accessing Sync Gateway from browser

This entry of stackoverflow might help you.


#6

@hideki

There is a chain issue with the certificate, which is why I added it to a custom keystore. There is no http server set up, only https. It doesn’t throw a security error in the browser, but httpie warns of:

http: error: SSLError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed (_ssl.c:590) while doing GET request to URL: SYNC_URL

However if I run httpie with the “–verify no” flag set, it is good to go and returns documents. I just have no idea what is wrong with my credentials in the mobile api…


#7

Hi @kvbutler,
your credentials are fine. Just need to skip SSL certificate verification. I need some research how to do it. Give us some time.
Thanks!


#8

@hideki Thanks for all the help! Looking forward to getting it working.


#9

Hi @kvbutler,

As you tried, it requires to store certificate in the KeyStore.
Please read FAQ: https://github.com/couchbase/couchbase-lite-android/wiki/FAQ-Android#q-how-can-i-connect-to-a-sync-gateway-via-https-which-does-not-have-an-ssl-cert-signed-by-a-root-authority

  1. Implement HttpClientFactory interface
    https://github.com/couchbase/couchbase-lite-java-core/blob/e619e8e1d0359628bbfef6ca5f9bf019badfa134/src/main/java/com/couchbase/lite/support/HttpClientFactory.java

NOTE: You can refer CouchbaseLiteHttpClientFactory or extends from it.

  1. Manager.setDefaultHttpClientFactory(HttpClientFactory)
    https://github.com/couchbase/couchbase-lite-java-core/blob/725e73a4e271232b9f4ad64dcf3ac1c41f3e6888/src/main/java/com/couchbase/lite/Manager.java#L324

Some useful links from the net:

Note: Storing your certificate in the KeyStore could be good. But I am not sure if ignoring/skipping certificate check is good idea from security point of view.

I hope this helps you!


#10

@hideki Thanks for the help! I fixed it the following way:

private void setKeyStore(){
    try {
        this.trustStore = KeyStore.getInstance("BKS");
    } catch (KeyStoreException e) {
        e.printStackTrace();
    }
    InputStream in = getResources().openRawResource(<LOCATION_OF_KEYSTORE_FILE>);
    try {
        trustStore.load(in, <KEYSTORE_PASSWORD>.toCharArray());
    } catch (IOException e) {
        e.printStackTrace();
    } catch (NoSuchAlgorithmException e) {
        e.printStackTrace();
    } catch (CertificateException e) {
        e.printStackTrace();
    }
}

and then…

private void setHttpClientFactory() {
    PersistentCookieStore cookieStore = database.getPersistentCookieStore();

    URL url = null;
    try {
        url = new URL(<URL_TO_SYNC_GATEWAY>");
    } catch (MalformedURLException e) {
        e.printStackTrace();
    }
    CouchbaseLiteHttpClientFactory cblHttpClientFactory = new CouchbaseLiteHttpClientFactory(cookieStore);
    try {
        cblHttpClientFactory.setSSLSocketFactory(new SSLSocketFactory(this.trustStore));
    } catch (NoSuchAlgorithmException e) {
        e.printStackTrace();
    } catch (KeyManagementException e) {
        e.printStackTrace();
    } catch (KeyStoreException e) {
        e.printStackTrace();
    } catch (UnrecoverableKeyException e) {
        e.printStackTrace();
    }

    // set CouchbaseHttpClientFactory to Manager
    manager.setDefaultHttpClientFactory(cblHttpClientFactory);
}

#11

@kvbutler I am glad to hear you could solve the problem!


#12

on doing

AssetManager am = PocApplication.applicationContext.getAssets();
    InputStream is = am.open("my_cert.pem");
    
    // Load CAs from an InputStream 
    CertificateFactory cf = CertificateFactory.getInstance("X.509");
    InputStream caInput = new BufferedInputStream(is);
    Certificate certificate; 
    try { 
        certificate = cf.generateCertificate(caInput);
    } finally { 
        caInput.close();
    } 

I get java.security.cert.CertificateException :

com.android.org.conscrypt.OpenSSLX509CertificateFactory$ParsingException: com.android.org.conscrypt.OpenSSLX509CertificateFactory$ParsingException: java.lang.RuntimeException: error:0D0680A8:asn1 encoding routines:ASN1_CHECK_TLEN:wrong tag

Anyone encountered this? using own bundled certificate in android environment…


#13

HI @nitz_couchbase

it seems certificate file formatting issue. see following link.


#14

It was cert format issue and was resolved.