首页 > 解决方案 > 在 Android 4.2.2 (API 17) 上进行 API 调用时出现 SSLHandshakeException

问题描述

我按照本教程使用 Volley 进行安全 API 调用。

我的活动

class MainActivity : AppCompatActivity() {

    private val SECURE_URL = "https://epas.vercel.app/api/v1/login"

    private var requestQueue: RequestQueue? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        sendRequest()
    }

    private fun sendRequest() {
        requestQueue = Volley.newRequestQueue(this, HurlStack(null, pinnedSSLSocketFactory()))
        val request = StringRequest(Request.Method.POST, SECURE_URL, { response ->
            println("_print::Response: $response")
        }) { error ->
            println("_print::Request failed: $error")
        }

        requestQueue!!.add(request)
    }

    private fun pinnedSSLSocketFactory(): SSLSocketFactory? {
        try {
            return TLSSocketFactory("SX23A4")
        } catch (e: KeyManagementException) {
            e.printStackTrace()
        } catch (e: NoSuchAlgorithmException) {
            e.printStackTrace()
        }
        return null
    }
} 

公钥管理器

class PubKeyManager(private val publicKey: String) : X509TrustManager {
    @Throws(CertificateException::class)
    override fun checkClientTrusted(chain: Array<X509Certificate>, authType: String) {}

    @Throws(CertificateException::class)
    override fun checkServerTrusted(chain: Array<X509Certificate>, authType: String) {
        require(chain.isNotEmpty()) { "checkServerTrusted: X509Certificate is empty" }

        // Perform customary SSL/TLS checks
        val tmf: TrustManagerFactory
        try {
            tmf = TrustManagerFactory.getInstance("X509")
            tmf.init(null as KeyStore?)
            for (trustManager in tmf.trustManagers) {
                (trustManager as X509TrustManager).checkServerTrusted(chain, authType)
            }
        } catch (e: Exception) {
            throw CertificateException(e.toString())
        }

        // Hack ahead: BigInteger and toString(). We know a DER encoded Public
        // Key starts with 0x30 (ASN.1 SEQUENCE and CONSTRUCTED), so there is
        // no leading 0x00 to drop.
        val pubkey = chain[0].publicKey as RSAPublicKey
        val encoded = BigInteger(1 /* positive */, pubkey.encoded).toString(16)

        // Pin it!
        val expected = publicKey.equals(encoded, ignoreCase = true)
        // fail if expected public key is different from our public key
        if (!expected) throw CertificateException("Not trusted")
    }

    override fun getAcceptedIssuers(): Array<X509Certificate?> = arrayOfNulls(0)
}

和 TLSSocketFactory

class TLSSocketFactory(publicKey: String?) : SSLSocketFactory() {
    private val internalSSLSocketFactory: SSLSocketFactory
    override fun getDefaultCipherSuites(): Array<String> =
        internalSSLSocketFactory.defaultCipherSuites

    override fun getSupportedCipherSuites(): Array<String> =
        internalSSLSocketFactory.supportedCipherSuites

    @Throws(IOException::class)
    override fun createSocket(s: Socket, host: String, port: Int, autoClose: Boolean): Socket {
        return enableTLSOnSocket(internalSSLSocketFactory.createSocket(s, host, port, autoClose))
    }

    @Throws(IOException::class)
    override fun createSocket(host: String, port: Int): Socket =
        enableTLSOnSocket(internalSSLSocketFactory.createSocket(host, port))

    @Throws(IOException::class)
    override fun createSocket(host: String, port: Int, localHost: InetAddress, localPort: Int): Socket =
        enableTLSOnSocket(internalSSLSocketFactory.createSocket(host, port, localHost, localPort))

    @Throws(IOException::class)
    override fun createSocket(host: InetAddress, port: Int): Socket =
        enableTLSOnSocket(internalSSLSocketFactory.createSocket(host, port))

    @Throws(IOException::class)
    override fun createSocket(address: InetAddress, port: Int, localAddress: InetAddress, localPort: Int): Socket =
        enableTLSOnSocket(internalSSLSocketFactory.createSocket(address, port, localAddress, localPort))

    private fun enableTLSOnSocket(socket: Socket): Socket {
        if (socket is SSLSocket) {
            socket.enabledProtocols = addTLS1And2(socket.enabledProtocols)
        }
        return socket
    }

    private fun addTLS1And2(currentProtocols: Array<String>): Array<String> {
        var hasTLSv1_1 = false
        var hasTLSv1_2 = false
        val tlsv11 = "TLSv1.1"
        val tlsv12 = "TLSv1.2"
        val list = ArrayList(listOf(*currentProtocols))
        for (protocol in currentProtocols) {
            if (protocol == tlsv11) hasTLSv1_1 = true
            if (protocol == tlsv12) hasTLSv1_2 = true
        }
        if (!hasTLSv1_1) list.add(tlsv11)
        if (!hasTLSv1_2) list.add(tlsv12)
        return list.toTypedArray()
    }

    init {
        val context = SSLContext.getInstance("TLS")
        val tm = arrayOf<TrustManager>(PubKeyManager(publicKey!!))
        context.init(null, tm, null)
        internalSSLSocketFactory = context.socketFactory
    }
}

当我在装有 Android 4.2.2 (API 17) 的设备上运行该应用程序时,我收到此错误:

Request failed: com.android.volley.NoConnectionError: javax.net.ssl.SSLHandshakeException: javax.net.ssl.SSLProtocolException: SSL handshake aborted: ssl=0x516e3e70: Failure in SSL library, usually a protocol error

我也试过改造,我得到了同样的错误。

是否有任何解决方法或推荐的 API 调用方式在使用 volley 或改造的旧设备上调用 MODEL_TLS 服务器?

标签: androidsslandroid-volleysslhandshakeexception

解决方案


推荐阅读