Tls.kt
⎇
Raw
1package de.upapp
2
3import android.util.Log
4import org.bouncycastle.tls.AlertDescription
5import org.bouncycastle.tls.BasicTlsPSKIdentity
6import org.bouncycastle.tls.CertificateRequest
7import org.bouncycastle.tls.CipherSuite
8import org.bouncycastle.tls.DefaultTlsClient
9import org.bouncycastle.tls.PSKTlsClient
10import org.bouncycastle.tls.ProtocolVersion
11import org.bouncycastle.tls.TlsAuthentication
12import org.bouncycastle.tls.TlsClientProtocol
13import org.bouncycastle.tls.TlsContext
14import org.bouncycastle.tls.TlsCredentials
15import org.bouncycastle.tls.TlsFatalAlert
16import org.bouncycastle.tls.TlsServerCertificate
17import org.bouncycastle.tls.TlsUtils
18import org.bouncycastle.tls.crypto.impl.bc.BcTlsCrypto
19import java.io.InputStream
20import java.io.OutputStream
21import java.security.GeneralSecurityException
22import java.security.MessageDigest
23import java.security.SecureRandom
24import java.security.cert.CertPathValidator
25import java.security.cert.CertificateFactory
26import java.security.cert.PKIXParameters
27import java.security.cert.TrustAnchor
28import java.security.cert.X509Certificate
29
30private val crypto = BcTlsCrypto(SecureRandom())
31
32/** Root CA of the car's registration certificate, bundled in maps+more. */
33val VIWI_ROOT_CA: X509Certificate = parseCertificate("""
34-----BEGIN CERTIFICATE-----
35MIIFATCCA2mgAwIBAgIJAKwA93QEg4cmMA0GCSqGSIb3DQEBCwUAMIGVMQswCQYD
36VQQGEwJERTEWMBQGA1UECAwNTmllZGVyc2FjaHNlbjESMBAGA1UEBwwJV29sZnNi
37dXJnMSQwIgYDVQQKDBtWb2xrc3dhZ2VuIEdtYkggRGV1dHNjaGxhbmQxDzANBgNV
38BAsMBkVFQ0YvMTEjMCEGA1UEAwwaVmlXaSBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkw
39IBcNMTUwMjI0MTI1MzQzWhgPMjA1NTAyMTQxMjUzNDNaMIGVMQswCQYDVQQGEwJE
40RTEWMBQGA1UECAwNTmllZGVyc2FjaHNlbjESMBAGA1UEBwwJV29sZnNidXJnMSQw
41IgYDVQQKDBtWb2xrc3dhZ2VuIEdtYkggRGV1dHNjaGxhbmQxDzANBgNVBAsMBkVF
42Q0YvMTEjMCEGA1UEAwwaVmlXaSBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkwggGiMA0G
43CSqGSIb3DQEBAQUAA4IBjwAwggGKAoIBgQC9YRvqG7dq9BVRn/vJJ05v5bG8o7ls
44zzgBGgPnEYF1HGB9fYrpmSfoL0kdmifD0AzAnwkhb7281hBVV6v+HcXQVrLSQVL1
45xVPOEbm/FDCb4ZI2tKuOeg97P6npZuW/tGf8Lk47dlkTSdPg94XgyJ3rIJgtjhTX
468r1EtlL/pP0D8AF360SmEJyEYF9CfU9U6/yKMjzjdUuaQRwEqKXUakQlJaRy3GQd
47OGbgy0Eukkew5kiZO3dQhTq3NsiKB3YUIgYxK3PJzL35OMzmN5kxkR72AUtbnoO/
48FwT6G060WPuRI+nENUlX8DQaR3fPaBat30eRFk5dQvd8Cmwd/qqm77DWS65R2xyS
49+jzNx7mtPjVmVPzBY/K+rj5tWgf3QXCNaClqu+wwnIgX0Wgu22BhJNVRGDmJi9xP
50RV9obiHvqBPO2SDo6AVEpIi+r8pOL3/iaQ/nS4a00SaAL31jW1hk0+N7bsknRk7P
515tlHZKXETVS0jSZ/dXgAQTmYygwmlyyiijsCAwEAAaNQME4wHQYDVR0OBBYEFFkw
521GxlgBqWBHf1gkv4XqKEGTWzMB8GA1UdIwQYMBaAFFkw1GxlgBqWBHf1gkv4XqKE
53GTWzMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQELBQADggGBACBWE/bexDK0EHXM
54KKHMjYtg4a0xtBmdY2sQFrni2bbUc1myLISM7iI2WfPnWC4UnuMFfI/004TqdZej
55MCTntsdSW7+qCDWDEqrYJEVxkLF9vCYeNLzzVr0tBo0TwjachGNBz4y9cO16LQBO
56dfgYD7Dw6JXQptjEJgpg0AHgtrErCmVa4VF2aRadBKkon/41LKsBXxvOjHrK9gi2
57EHASasuKYKQfqb117fRXbfbqIAh5p+oWZ2Ko9mafIjRE0b2q0HsDqm31Z7R0XuiS
58m53o2FSj0Uk1cX3A9a+7h9YjALGLebkmfnZ4hD1LHVVLBuvErlPnIvSyUsKO0pue
59mZGrLvJNSMEW+VR7uAb36dxHAr0f+E4+dV/WSXL+XXUMjzEYKyHc0bby2lZKx+TK
604dqI1xtQVO2uEff/JJHqyWBlXnSiNWGx5Bnu+C5oFOQmX+hE5A6jb9aad/nqYCHh
61O0dgtlde6OVRrf6GSzWJZr7pK/IMuDfF6Y1Fa1wNhpyCMxeXkw==
62-----END CERTIFICATE-----
63""".trim().byteInputStream())
64
65private fun parseCertificate(input: InputStream) = CertificateFactory.getInstance("X.509").generateCertificate(input) as X509Certificate
66
67/** TLS for the one-time registration. Returns the connection and the PIN that the car display also shows. */
68fun registrationTls(input: InputStream, output: OutputStream, root: X509Certificate = VIWI_ROOT_CA): Pair<TlsClientProtocol, String> {
69 lateinit var pin: String
70 val tls = TlsClientProtocol(input, output)
71 tls.connect(object : DefaultTlsClient(crypto) {
72 override fun getSupportedVersions(): Array<ProtocolVersion> = ProtocolVersion.TLSv12.only()
73 override fun getSupportedCipherSuites() = intArrayOf(CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256)
74 // The car does not support RFC 5746.
75 override fun notifySecureRenegotiation(secureRenegotiation: Boolean) {}
76 override fun getAuthentication() = object : TlsAuthentication {
77 override fun notifyServerCertificate(serverCertificate: TlsServerCertificate) =
78 verifyChain(serverCertificate.certificate.certificateList.map { parseCertificate(it.encoded.inputStream()) }, root)
79 override fun getClientCredentials(certificateRequest: CertificateRequest): TlsCredentials? = null
80 }
81 override fun notifyHandshakeComplete() {
82 super.notifyHandshakeComplete()
83 pin = registrationPin(context)
84 }
85 })
86 return tls to pin
87}
88
89// Checks the VW signatures only. The car cannot renew its certificate, so dates and host name are ignored.
90private fun verifyChain(chain: List<X509Certificate>, root: X509Certificate) {
91 chain.forEach { Log.i("Tls", "Car certificate: ${it.subjectX500Principal}, valid ${it.notBefore} to ${it.notAfter}") }
92 val path = CertificateFactory.getInstance("X.509").generateCertPath(chain.filter { it != root })
93 val params = PKIXParameters(setOf(TrustAnchor(root, null))).apply {
94 isRevocationEnabled = false
95 date = (chain + root).maxOf { it.notBefore }
96 }
97 try {
98 CertPathValidator.getInstance("PKIX").validate(path, params)
99 } catch (e: GeneralSecurityException) {
100 throw TlsFatalAlert(AlertDescription.bad_certificate, "The car certificate is not signed by Volkswagen: ${e.message}", e)
101 }
102}
103
104// AES-128-GCM key block: client write key, then server write key, 16 bytes each, no MAC keys.
105internal fun registrationPin(context: TlsContext): String {
106 val sp = context.securityParametersConnection
107 val keys = TlsUtils.PRF(sp, sp.masterSecret, "key expansion", sp.serverRandom + sp.clientRandom, 32).extract()
108 return pin(keys.copyOfRange(0, 16), keys.copyOfRange(16, 32))
109}
110
111internal fun pin(clientWriteKey: ByteArray, serverWriteKey: ByteArray): String {
112 val d = MessageDigest.getInstance("SHA-256").digest(clientWriteKey + serverWriteKey)
113 val t = (0..3).sumOf { (d[it].toLong() and 0xFF) shl (8 * it) }
114 return (t % 1_000_000).toString().padStart(6, '0')
115}
116
117/** TLS with the pre-shared key from registration. */
118fun sessionTls(input: InputStream, output: OutputStream, identity: String, key: String) = TlsClientProtocol(input, output).apply {
119 connect(object : PSKTlsClient(crypto, BasicTlsPSKIdentity(identity, key.toByteArray())) {
120 override fun getSupportedVersions(): Array<ProtocolVersion> = ProtocolVersion.TLSv12.only()
121 override fun getSupportedCipherSuites() = intArrayOf(CipherSuite.TLS_PSK_WITH_AES_128_CBC_SHA256)
122 override fun notifySecureRenegotiation(secureRenegotiation: Boolean) {}
123 })
124}
125