package de.upapp import android.util.Log import org.bouncycastle.tls.AlertDescription import org.bouncycastle.tls.BasicTlsPSKIdentity import org.bouncycastle.tls.CertificateRequest import org.bouncycastle.tls.CipherSuite import org.bouncycastle.tls.DefaultTlsClient import org.bouncycastle.tls.PSKTlsClient import org.bouncycastle.tls.ProtocolVersion import org.bouncycastle.tls.TlsAuthentication import org.bouncycastle.tls.TlsClientProtocol import org.bouncycastle.tls.TlsContext import org.bouncycastle.tls.TlsCredentials import org.bouncycastle.tls.TlsFatalAlert import org.bouncycastle.tls.TlsServerCertificate import org.bouncycastle.tls.TlsUtils import org.bouncycastle.tls.crypto.impl.bc.BcTlsCrypto import java.io.InputStream import java.io.OutputStream import java.security.GeneralSecurityException import java.security.MessageDigest import java.security.SecureRandom import java.security.cert.CertPathValidator import java.security.cert.CertificateFactory import java.security.cert.PKIXParameters import java.security.cert.TrustAnchor import java.security.cert.X509Certificate private val crypto = BcTlsCrypto(SecureRandom()) /** Root CA of the car's registration certificate, bundled in maps+more. */ val VIWI_ROOT_CA: X509Certificate = parseCertificate(""" -----BEGIN CERTIFICATE----- MIIFATCCA2mgAwIBAgIJAKwA93QEg4cmMA0GCSqGSIb3DQEBCwUAMIGVMQswCQYD VQQGEwJERTEWMBQGA1UECAwNTmllZGVyc2FjaHNlbjESMBAGA1UEBwwJV29sZnNi dXJnMSQwIgYDVQQKDBtWb2xrc3dhZ2VuIEdtYkggRGV1dHNjaGxhbmQxDzANBgNV BAsMBkVFQ0YvMTEjMCEGA1UEAwwaVmlXaSBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkw IBcNMTUwMjI0MTI1MzQzWhgPMjA1NTAyMTQxMjUzNDNaMIGVMQswCQYDVQQGEwJE RTEWMBQGA1UECAwNTmllZGVyc2FjaHNlbjESMBAGA1UEBwwJV29sZnNidXJnMSQw IgYDVQQKDBtWb2xrc3dhZ2VuIEdtYkggRGV1dHNjaGxhbmQxDzANBgNVBAsMBkVF Q0YvMTEjMCEGA1UEAwwaVmlXaSBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkwggGiMA0G CSqGSIb3DQEBAQUAA4IBjwAwggGKAoIBgQC9YRvqG7dq9BVRn/vJJ05v5bG8o7ls zzgBGgPnEYF1HGB9fYrpmSfoL0kdmifD0AzAnwkhb7281hBVV6v+HcXQVrLSQVL1 xVPOEbm/FDCb4ZI2tKuOeg97P6npZuW/tGf8Lk47dlkTSdPg94XgyJ3rIJgtjhTX 8r1EtlL/pP0D8AF360SmEJyEYF9CfU9U6/yKMjzjdUuaQRwEqKXUakQlJaRy3GQd OGbgy0Eukkew5kiZO3dQhTq3NsiKB3YUIgYxK3PJzL35OMzmN5kxkR72AUtbnoO/ FwT6G060WPuRI+nENUlX8DQaR3fPaBat30eRFk5dQvd8Cmwd/qqm77DWS65R2xyS +jzNx7mtPjVmVPzBY/K+rj5tWgf3QXCNaClqu+wwnIgX0Wgu22BhJNVRGDmJi9xP RV9obiHvqBPO2SDo6AVEpIi+r8pOL3/iaQ/nS4a00SaAL31jW1hk0+N7bsknRk7P 5tlHZKXETVS0jSZ/dXgAQTmYygwmlyyiijsCAwEAAaNQME4wHQYDVR0OBBYEFFkw 1GxlgBqWBHf1gkv4XqKEGTWzMB8GA1UdIwQYMBaAFFkw1GxlgBqWBHf1gkv4XqKE GTWzMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQELBQADggGBACBWE/bexDK0EHXM KKHMjYtg4a0xtBmdY2sQFrni2bbUc1myLISM7iI2WfPnWC4UnuMFfI/004TqdZej MCTntsdSW7+qCDWDEqrYJEVxkLF9vCYeNLzzVr0tBo0TwjachGNBz4y9cO16LQBO dfgYD7Dw6JXQptjEJgpg0AHgtrErCmVa4VF2aRadBKkon/41LKsBXxvOjHrK9gi2 EHASasuKYKQfqb117fRXbfbqIAh5p+oWZ2Ko9mafIjRE0b2q0HsDqm31Z7R0XuiS m53o2FSj0Uk1cX3A9a+7h9YjALGLebkmfnZ4hD1LHVVLBuvErlPnIvSyUsKO0pue mZGrLvJNSMEW+VR7uAb36dxHAr0f+E4+dV/WSXL+XXUMjzEYKyHc0bby2lZKx+TK 4dqI1xtQVO2uEff/JJHqyWBlXnSiNWGx5Bnu+C5oFOQmX+hE5A6jb9aad/nqYCHh O0dgtlde6OVRrf6GSzWJZr7pK/IMuDfF6Y1Fa1wNhpyCMxeXkw== -----END CERTIFICATE----- """.trim().byteInputStream()) private fun parseCertificate(input: InputStream) = CertificateFactory.getInstance("X.509").generateCertificate(input) as X509Certificate /** TLS for the one-time registration. Returns the connection and the PIN that the car display also shows. */ fun registrationTls(input: InputStream, output: OutputStream, root: X509Certificate = VIWI_ROOT_CA): Pair { lateinit var pin: String val tls = TlsClientProtocol(input, output) tls.connect(object : DefaultTlsClient(crypto) { override fun getSupportedVersions(): Array = ProtocolVersion.TLSv12.only() override fun getSupportedCipherSuites() = intArrayOf(CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256) // The car does not support RFC 5746. override fun notifySecureRenegotiation(secureRenegotiation: Boolean) {} override fun getAuthentication() = object : TlsAuthentication { override fun notifyServerCertificate(serverCertificate: TlsServerCertificate) = verifyChain(serverCertificate.certificate.certificateList.map { parseCertificate(it.encoded.inputStream()) }, root) override fun getClientCredentials(certificateRequest: CertificateRequest): TlsCredentials? = null } override fun notifyHandshakeComplete() { super.notifyHandshakeComplete() pin = registrationPin(context) } }) return tls to pin } // Checks the VW signatures only. The car cannot renew its certificate, so dates and host name are ignored. private fun verifyChain(chain: List, root: X509Certificate) { chain.forEach { Log.i("Tls", "Car certificate: ${it.subjectX500Principal}, valid ${it.notBefore} to ${it.notAfter}") } val path = CertificateFactory.getInstance("X.509").generateCertPath(chain.filter { it != root }) val params = PKIXParameters(setOf(TrustAnchor(root, null))).apply { isRevocationEnabled = false date = (chain + root).maxOf { it.notBefore } } try { CertPathValidator.getInstance("PKIX").validate(path, params) } catch (e: GeneralSecurityException) { throw TlsFatalAlert(AlertDescription.bad_certificate, "The car certificate is not signed by Volkswagen: ${e.message}", e) } } // AES-128-GCM key block: client write key, then server write key, 16 bytes each, no MAC keys. internal fun registrationPin(context: TlsContext): String { val sp = context.securityParametersConnection val keys = TlsUtils.PRF(sp, sp.masterSecret, "key expansion", sp.serverRandom + sp.clientRandom, 32).extract() return pin(keys.copyOfRange(0, 16), keys.copyOfRange(16, 32)) } internal fun pin(clientWriteKey: ByteArray, serverWriteKey: ByteArray): String { val d = MessageDigest.getInstance("SHA-256").digest(clientWriteKey + serverWriteKey) val t = (0..3).sumOf { (d[it].toLong() and 0xFF) shl (8 * it) } return (t % 1_000_000).toString().padStart(6, '0') } /** TLS with the pre-shared key from registration. */ fun sessionTls(input: InputStream, output: OutputStream, identity: String, key: String) = TlsClientProtocol(input, output).apply { connect(object : PSKTlsClient(crypto, BasicTlsPSKIdentity(identity, key.toByteArray())) { override fun getSupportedVersions(): Array = ProtocolVersion.TLSv12.only() override fun getSupportedCipherSuites() = intArrayOf(CipherSuite.TLS_PSK_WITH_AES_128_CBC_SHA256) override fun notifySecureRenegotiation(secureRenegotiation: Boolean) {} }) }