美文网首页
[转]Android 根证书管理与证书验证(4)

[转]Android 根证书管理与证书验证(4)

作者: 原野大神 | 来源:发表于2020-10-28 09:51 被阅读0次

    转自 https://sq.163yun.com/blog/article/188783289427574784

    私有 CA 签名证书的应用

    自签名证书是无需别的证书为其签名来证明其合法性的证书,根证书都是自签名证书。私有 CA 签名证书则是指,为域名证书签名的 CA,其合法有效性没有得到广泛的认可,该 CA 的根证书没有被内置到系统中。

    在实际的开发过程中,有时为了节省昂贵的购买证书的费用,而想要自己给自己的服务器的域名签发域名证书,这即是私有 CA 签名的证书。为了能够使用这种证书,需要在客户端预埋根证书,并对客户端证书合法性验证的过程进行干预,通过我们预埋的根证书为服务端的证书做合法性验证,而不依赖系统的根证书库。

    自定义 javax.net.ssl.SSLSocket 的代价太高,通常不会通过自定义 javax.net.ssl.SSLSocket 来修改服务端证书的合法性验证过程。以此为基础,从上面的分析中不难看出,要想定制 OpenSSLSocketImpl 的证书验证过程,则必然要改变 SSLParametersImpl,要改变 OpenSSLSocketImplSSLParametersImpl,则必然需要修改 SSLSocketFactory。修改 SSLSocketFactory 常常是一个不错的方法。

    在 Java 中,SSLContext 正是被设计用于这一目的。创建定制了 SSLParametersImpl,即定制了 TrustManagerSSLSocketFactory 的方法如下:

            TrustManager[] trustManagers = new TrustManager[] { new HelloX509TrustManager() };;
    
            SSLContext context = null;
            try {
                context = SSLContext.getInstance("TLS");
                context.init(null, trustManagers, new SecureRandom());
            } catch (NoSuchAlgorithmException e) {
                Log.i(TAG,"NoSuchAlgorithmException INFO:"+e.getMessage());
            } catch (KeyManagementException e) {
                Log.i(TAG, "KeyManagementException INFO:" + e.getMessage());
            }
    
            HttpsURLConnection.setDefaultSSLSocketFactory(context.getSocketFactory());
    
    

    SSLContext 的相关方法实现(位于libcore/ojluni/src/main/java/javax/net/ssl/SSLContext.java)如下:

        private final SSLContextSpi contextSpi;
    . . . . . .
        public static SSLContext getInstance(String protocol)
                throws NoSuchAlgorithmException {
            GetInstance.Instance instance = GetInstance.getInstance
                    ("SSLContext", SSLContextSpi.class, protocol);
            return new SSLContext((SSLContextSpi)instance.impl, instance.provider,
                    protocol);
        }
    . . . . . .
        public final void init(KeyManager[] km, TrustManager[] tm,
                                    SecureRandom random)
            throws KeyManagementException {
            contextSpi.engineInit(km, tm, random);
        }
    
        /**
         * Returns a <code>SocketFactory</code> object for this
         * context.
         *
         * @return the <code>SocketFactory</code> object
         * @throws IllegalStateException if the SSLContextImpl requires
         *          initialization and the <code>init()</code> has not been called
         */
        public final SSLSocketFactory getSocketFactory() {
            return contextSpi.engineGetSocketFactory();
        }
    
    

    其中 SSLContextSpiOpenSSLContextImpl,该类的实现(位于external/conscrypt/src/main/java/org/conscrypt/OpenSSLContextImpl.java)如下:

    package org.conscrypt;
    
    import java.io.IOException;
    import java.security.GeneralSecurityException;
    import java.security.KeyManagementException;
    import java.security.SecureRandom;
    import javax.net.ssl.KeyManager;
    import javax.net.ssl.SSLContextSpi;
    import javax.net.ssl.SSLEngine;
    import javax.net.ssl.SSLServerSocketFactory;
    import javax.net.ssl.SSLSocketFactory;
    import javax.net.ssl.TrustManager;
    
    /**
     * OpenSSL-backed SSLContext service provider interface.
     */
    public class OpenSSLContextImpl extends SSLContextSpi {
    
        /**
         * The default SSLContextImpl for use with
         * SSLContext.getInstance("Default"). Protected by the
         * DefaultSSLContextImpl.class monitor.
         */
        private static DefaultSSLContextImpl DEFAULT_SSL_CONTEXT_IMPL;
    
        /** TLS algorithm to initialize all sockets. */
        private final String[] algorithms;
    
        /** Client session cache. */
        private final ClientSessionContext clientSessionContext;
    
        /** Server session cache. */
        private final ServerSessionContext serverSessionContext;
    
        protected SSLParametersImpl sslParameters;
    
        /** Allows outside callers to get the preferred SSLContext. */
        public static OpenSSLContextImpl getPreferred() {
            return new TLSv12();
        }
    
        protected OpenSSLContextImpl(String[] algorithms) {
            this.algorithms = algorithms;
            clientSessionContext = new ClientSessionContext();
            serverSessionContext = new ServerSessionContext();
        }
    
        /**
         * Constuctor for the DefaultSSLContextImpl.
         *
         * @param dummy is null, used to distinguish this case from the public
         *            OpenSSLContextImpl() constructor.
         */
        protected OpenSSLContextImpl() throws GeneralSecurityException, IOException {
            synchronized (DefaultSSLContextImpl.class) {
                this.algorithms = null;
                if (DEFAULT_SSL_CONTEXT_IMPL == null) {
                    clientSessionContext = new ClientSessionContext();
                    serverSessionContext = new ServerSessionContext();
                    DEFAULT_SSL_CONTEXT_IMPL = (DefaultSSLContextImpl) this;
                } else {
                    clientSessionContext = DEFAULT_SSL_CONTEXT_IMPL.engineGetClientSessionContext();
                    serverSessionContext = DEFAULT_SSL_CONTEXT_IMPL.engineGetServerSessionContext();
                }
                sslParameters = new SSLParametersImpl(DEFAULT_SSL_CONTEXT_IMPL.getKeyManagers(),
                        DEFAULT_SSL_CONTEXT_IMPL.getTrustManagers(), null, clientSessionContext,
                        serverSessionContext, algorithms);
            }
        }
    
        /**
         * Initializes this {@code SSLContext} instance. All of the arguments are
         * optional, and the security providers will be searched for the required
         * implementations of the needed algorithms.
         *
         * @param kms the key sources or {@code null}
         * @param tms the trust decision sources or {@code null}
         * @param sr the randomness source or {@code null}
         * @throws KeyManagementException if initializing this instance fails
         */
        @Override
        public void engineInit(KeyManager[] kms, TrustManager[] tms, SecureRandom sr)
                throws KeyManagementException {
            sslParameters = new SSLParametersImpl(kms, tms, sr, clientSessionContext,
                    serverSessionContext, algorithms);
        }
    
        @Override
        public SSLSocketFactory engineGetSocketFactory() {
            if (sslParameters == null) {
                throw new IllegalStateException("SSLContext is not initialized.");
            }
            return Platform.wrapSocketFactoryIfNeeded(new OpenSSLSocketFactoryImpl(sslParameters));
        }
    
    

    如我们前面讨论,验证服务端证书合法性是 PKI 体系中,保障系统安全极为关键的环节。如果不验证服务端证书的合法性,则即使部署了 HTTPS,HTTPS 也将形同虚设,毫无价值。因而在我们自己实现的 X509TrustManager 中,加载预埋的根证书,并据此验证服务端证书的合法性必不可少,这一检查在 checkServerTrusted() 中完成。然而为了使我们实现的 X509TrustManager 功能更完备,在根据我们预埋的根证书验证失败后,我们再使用系统默认的 X509TrustManager 做验证,像下面这样:

        private final class HelloX509TrustManager implements X509TrustManager {
            private X509TrustManager mSystemDefaultTrustManager;
            private X509Certificate mCertificate;
    
            private HelloX509TrustManager() {
                mCertificate = loadRootCertificate();
                mSystemDefaultTrustManager = systemDefaultTrustManager();
            }
    
            private X509Certificate loadRootCertificate() {
                String certName = "netease.crt";
                X509Certificate certificate = null;
                InputStream certInput = null;
                try {
                    certInput = new BufferedInputStream(MainActivity.this.getAssets().open(certName));
                    CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
                    certificate = (X509Certificate) certificateFactory.generateCertPath(certInput).getCertificates().get(0);
                } catch (IOException e) {
                    e.printStackTrace();
                } catch (CertificateException e) {
                    e.printStackTrace();
                } finally {
                    if (certInput != null) {
                        try {
                            certInput.close();
                        } catch (IOException e) {
                        }
                    }
                }
                return certificate;
            }
    
            private X509TrustManager systemDefaultTrustManager() {
                try {
                    TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(
                            TrustManagerFactory.getDefaultAlgorithm());
                    trustManagerFactory.init((KeyStore) null);
                    TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();
                    if (trustManagers.length != 1 || !(trustManagers[0] instanceof X509TrustManager)) {
                        throw new IllegalStateException("Unexpected default trust managers:"
                                + Arrays.toString(trustManagers));
                    }
                    return (X509TrustManager) trustManagers[0];
                } catch (GeneralSecurityException e) {
                    throw new AssertionError(); // The system has no TLS. Just give up.
                }
            }
    
            @Override
            public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
                mSystemDefaultTrustManager.checkClientTrusted(chain, authType);
            }
    
            @Override
            public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
                for (X509Certificate certificate : chain) {
                    try {
                        certificate.verify(mCertificate.getPublicKey());
                        return;
                    } catch (NoSuchAlgorithmException e) {
                        e.printStackTrace();
                    } catch (InvalidKeyException e) {
                        e.printStackTrace();
                    } catch (NoSuchProviderException e) {
                        e.printStackTrace();
                    } catch (SignatureException e) {
                        e.printStackTrace();
                    }
                }
                mSystemDefaultTrustManager.checkServerTrusted(chain, authType);
            }
    
            @Override
            public X509Certificate[] getAcceptedIssuers() {
                return mSystemDefaultTrustManager.getAcceptedIssuers();
            }
        }
    
    

    此外,也可以不自己实现 X509TrustManager,而仅仅修改 X509TrustManager 所用的根证书库,就像下面这样:

        private TrustManager[] createX509TrustManager() {
            CertificateFactory cf = null;
            InputStream in = null;
            TrustManager[] trustManagers = null
            try {
                cf = CertificateFactory.getInstance("X.509");
                in = getAssets().open("ca.crt");
                Certificate ca = cf.generateCertificate(in);
    
                KeyStore keystore = KeyStore.getInstance(KeyStore.getDefaultType());
                keystore.load(null, null);
                keystore.setCertificateEntry("ca", ca);
    
                String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
                TrustManagerFactory tmf = TrustManagerFactory.getInstance(tmfAlgorithm);
                tmf.init(keystore);
    
                trustManagers = tmf.getTrustManagers();
            } catch (CertificateException e) {
                e.printStackTrace();
            } catch (NoSuchAlgorithmException e) {
                e.printStackTrace();
            } catch (KeyStoreException e) {
                e.printStackTrace();
            } catch (IOException e1) {
                e1.printStackTrace();
            } finally {
                if (in != null) {
                    try {
                        in.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
    
            return trustManagers;
        }
    
    

    自己实现 X509TrustManager 接口和通过 TrustManagerFactory,仅定制 KeyStore 这两种创建 X509TrustManager 对象的方式,当然是后一种方式更好一些了。如我们前面看到的,系统的 X509TrustManager 实现 RootTrustManager 集成自 X509ExtendedTrustManager,而不是直接实现的 X509TrustManager 接口 。JCA 的接口层也在随着新的安全协议和 SSL 库的发展在不断扩展,在具体的 Java 加密服务实现中,可能会实现并依赖这些扩展的功能,如上面看到的 X509TrustManager,而且加密服务的实现中常常通过反射,来动态依赖一些扩展的接口。因而,自己实现 X509TrustManager 接口时,以及其它加密相关的接口时,如 SSLSocket 等,可能会破坏一些功能。

    很多时候可以看到,为了使用私有 CA 签名的证书,而定制域名匹配验证的逻辑,即自己实现 HostnameVerifier。不过通常情况下,网络库都会按照规范对域名与证书的匹配性做严格的检查,因而不是那么地有必要,除非域名证书有什么不那么规范的地方。

    关于证书钉扎,在使用私有 CA 签名的证书时,通常似乎也没有那么必要。

    参考文章:

    Android https 自定义 证书 问题
    Android实现https网络通信之添加指定信任证书/信任所有证书
    HTTPS(含SNI)业务场景“IP直连”方案说明
    HTTP Public Key Pinning 介绍
    Java https请求 HttpsURLConnection
    Done。

    相关文章

      网友评论

          本文标题:[转]Android 根证书管理与证书验证(4)

          本文链接:https://www.haomeiwen.com/subject/yoxfvktx.html