All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.couchbase.lite.internal.replicator.CBLTrustManager Maven / Gradle / Ivy

package com.couchbase.lite.internal.replicator;

import android.support.annotation.NonNull;
import android.support.annotation.Nullable;

import java.security.InvalidKeyException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.SignatureException;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;

import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager;

import com.couchbase.lite.internal.utils.Fn;


/**
 * The trust manager that supports the followings:
 * 1. Supports pinned server certificate.
 * 2. Supports acceptOnlySelfSignedServerCertificate mode.
 * 3. Supports default trust manager for validating certs when the pinned server
 *    certificate and acceptOnlySelfSignedServerCertificate are not used.
 * 4. Allows to listen for the server certificates.
 */
public final class CBLTrustManager implements X509TrustManager {
    @Nullable
    private final byte[] pinnedServerCertificate;

    private final boolean acceptOnlySelfSignedServerCertificate;

    @NonNull
    private final Fn.Consumer> serverCertslistener;

    @NonNull
    private final AtomicReference defaultTrustManager = new AtomicReference<>();

    public CBLTrustManager(
        @Nullable byte[] pinnedServerCert,
        boolean acceptOnlySelfSignedServerCertificate,
        @NonNull Fn.Consumer> serverCertslistener) {
        this.pinnedServerCertificate = (pinnedServerCert != null ? pinnedServerCert.clone() : null);
        this.acceptOnlySelfSignedServerCertificate = acceptOnlySelfSignedServerCertificate;
        this.serverCertslistener = serverCertslistener;
    }

    @Override
    public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
        throw new UnsupportedOperationException("Checking Client Trust is a server operation");
    }

    @Override
    public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
        try { doCheckServerTrusted(chain, authType); }
        finally {
            serverCertslistener.accept(Collections.unmodifiableList(Arrays.asList(chain)));
        }
    }

    private void doCheckServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
        // Use default trust manager if the pinned server certificate
        // and acceptOnlySelfSignedServerCertificate are not used:
        if (useDefaultTrustManager()) {
            getDefaultTrustManager().checkServerTrusted(chain, authType);
            return;
        }

        // Check chain and authType precondition and throws IllegalArgumentException according to
        // https://docs.oracle.com/javase/8/docs/api/javax/net/ssl/X509TrustManager.html:
        if (chain == null || chain.length == 0) {
            throw new IllegalArgumentException("No server certificates");
        }
        if (authType == null || authType.length() == 0) {
            throw new IllegalArgumentException("Invalid auth type: " + authType);
        }

        // Validate certificate:
        final X509Certificate cert = chain[0];
        cert.checkValidity();

        // pinnedServerCertificate takes precedence over acceptOnlySelfSignedServerCertificate
        if (pinnedServerCertificate != null) {
            // Compare pinnedServerCertificate and the received cert:
            if (!Arrays.equals(pinnedServerCertificate, cert.getEncoded())) {
                throw new CertificateException("Server certificate does not match pinned certificate");
            }
        } else {
            // Accept only self-signed certificate:
            if (chain.length > 1 || !isSelfSignedCertificate(cert)) {
                throw new CertificateException("Server certificate is not self-signed");
            }
        }
    }

    @Override
    public X509Certificate[] getAcceptedIssuers() {
        if (useDefaultTrustManager()) { return getDefaultTrustManager().getAcceptedIssuers(); }
        return new X509Certificate[0];
    }

    /**
     * Check if the default trust manager should be used.
     * When the pinned server certificate and acceptOnlySelfSignedServerCertificate
     * are not used, the default trust manager will be used.
     */
    private boolean useDefaultTrustManager() {
        return pinnedServerCertificate == null && !acceptOnlySelfSignedServerCertificate;
    }

    /**
     * Get the default trust manager.
     */
    private X509TrustManager getDefaultTrustManager() {
        final X509TrustManager trustManager = defaultTrustManager.get();
        if (trustManager != null) { return trustManager; }

        final TrustManager[] trustManagers;
        try {
            final TrustManagerFactory trustManagerFactory
                = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
            trustManagerFactory.init((KeyStore) null);
            trustManagers = trustManagerFactory.getTrustManagers();
        }
        catch (NoSuchAlgorithmException | KeyStoreException e) {
            throw new IllegalStateException("Cannot find the default trust manager", e);
        }

        if (trustManagers == null || trustManagers.length == 0) {
            throw new IllegalStateException("Cannot find the default trust manager");
        }

        defaultTrustManager.compareAndSet(null, (X509TrustManager) trustManagers[0]);
        return defaultTrustManager.get();
    }

    /**
     * Check if the certificate is a self-signed certificate.
     */
    private boolean isSelfSignedCertificate(X509Certificate cert) {
        if (!cert.getSubjectDN().equals(cert.getIssuerDN())) { return false; }

        try {
            cert.verify(cert.getPublicKey());
            return true;
        } catch (CertificateException | NoSuchAlgorithmException |
                 InvalidKeyException | NoSuchProviderException |
                 SignatureException e) {
            return false;
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy