-
-
Notifications
You must be signed in to change notification settings - Fork 60
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add support for Vencrypt's X509 sub-types
Certificates are verified with trust-on-first-use policy.
- Loading branch information
Showing
7 changed files
with
223 additions
and
5 deletions.
There are no files selected for viewing
86 changes: 86 additions & 0 deletions
86
app/src/androidTest/java/com/gaurav/avnc/util/KnownHostsTest.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
/* | ||
* Copyright (c) 2024 Gaurav Ujjwal. | ||
* | ||
* SPDX-License-Identifier: GPL-3.0-or-later | ||
* | ||
* See COPYING.txt for more details. | ||
*/ | ||
|
||
package com.gaurav.avnc.util | ||
|
||
import androidx.test.ext.junit.runners.AndroidJUnit4 | ||
import com.gaurav.avnc.targetContext | ||
import org.junit.Assert | ||
import org.junit.Before | ||
import org.junit.Test | ||
import org.junit.runner.RunWith | ||
import java.security.cert.CertificateFactory | ||
import java.security.cert.X509Certificate | ||
|
||
@RunWith(AndroidJUnit4::class) | ||
class KnownHostsTest { | ||
|
||
@Before | ||
fun before() { | ||
targetContext.filesDir.listFiles()?.forEach { it.deleteRecursively() } | ||
} | ||
|
||
@Test | ||
fun simpleTrustTest() { | ||
Assert.assertFalse(isCertificateTrusted(targetContext, getTestCert())) | ||
trustCertificate(targetContext, getTestCert()) | ||
Assert.assertTrue(isCertificateTrusted(targetContext, getTestCert())) | ||
} | ||
|
||
private fun getTestCert(): X509Certificate { | ||
|
||
// TLS certificate of example.com | ||
val pem = """ | ||
-----BEGIN CERTIFICATE----- | ||
MIIHbjCCBlagAwIBAgIQB1vO8waJyK3fE+Ua9K/hhzANBgkqhkiG9w0BAQsFADBZ | ||
MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMTMwMQYDVQQDEypE | ||
aWdpQ2VydCBHbG9iYWwgRzIgVExTIFJTQSBTSEEyNTYgMjAyMCBDQTEwHhcNMjQw | ||
MTMwMDAwMDAwWhcNMjUwMzAxMjM1OTU5WjCBljELMAkGA1UEBhMCVVMxEzARBgNV | ||
BAgTCkNhbGlmb3JuaWExFDASBgNVBAcTC0xvcyBBbmdlbGVzMUIwQAYDVQQKDDlJ | ||
bnRlcm5ldMKgQ29ycG9yYXRpb27CoGZvcsKgQXNzaWduZWTCoE5hbWVzwqBhbmTC | ||
oE51bWJlcnMxGDAWBgNVBAMTD3d3dy5leGFtcGxlLm9yZzCCASIwDQYJKoZIhvcN | ||
AQEBBQADggEPADCCAQoCggEBAIaFD7sO+cpf2fXgCjIsM9mqDgcpqC8IrXi9wga/ | ||
9y0rpqcnPVOmTMNLsid3INbBVEm4CNr5cKlh9rJJnWlX2vttJDRyLkfwBD+dsVvi | ||
vGYxWTLmqX6/1LDUZPVrynv/cltemtg/1Aay88jcj2ZaRoRmqBgVeacIzgU8+zmJ | ||
7236TnFSe7fkoKSclsBhPaQKcE3Djs1uszJs8sdECQTdoFX9I6UgeLKFXtg7rRf/ | ||
hcW5dI0zubhXbrW8aWXbCzySVZn0c7RkJMpnTCiZzNxnPXnHFpwr5quqqjVyN/aB | ||
KkjoP04Zmr+eRqoyk/+lslq0sS8eaYSSHbC5ja/yMWyVhvMCAwEAAaOCA/IwggPu | ||
MB8GA1UdIwQYMBaAFHSFgMBmx9833s+9KTeqAx2+7c0XMB0GA1UdDgQWBBRM/tAS | ||
TS4hz2v68vK4TEkCHTGRijCBgQYDVR0RBHoweIIPd3d3LmV4YW1wbGUub3Jnggtl | ||
eGFtcGxlLm5ldIILZXhhbXBsZS5lZHWCC2V4YW1wbGUuY29tggtleGFtcGxlLm9y | ||
Z4IPd3d3LmV4YW1wbGUuY29tgg93d3cuZXhhbXBsZS5lZHWCD3d3dy5leGFtcGxl | ||
Lm5ldDA+BgNVHSAENzA1MDMGBmeBDAECAjApMCcGCCsGAQUFBwIBFhtodHRwOi8v | ||
d3d3LmRpZ2ljZXJ0LmNvbS9DUFMwDgYDVR0PAQH/BAQDAgWgMB0GA1UdJQQWMBQG | ||
CCsGAQUFBwMBBggrBgEFBQcDAjCBnwYDVR0fBIGXMIGUMEigRqBEhkJodHRwOi8v | ||
Y3JsMy5kaWdpY2VydC5jb20vRGlnaUNlcnRHbG9iYWxHMlRMU1JTQVNIQTI1NjIw | ||
MjBDQTEtMS5jcmwwSKBGoESGQmh0dHA6Ly9jcmw0LmRpZ2ljZXJ0LmNvbS9EaWdp | ||
Q2VydEdsb2JhbEcyVExTUlNBU0hBMjU2MjAyMENBMS0xLmNybDCBhwYIKwYBBQUH | ||
AQEEezB5MCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdpY2VydC5jb20wUQYI | ||
KwYBBQUHMAKGRWh0dHA6Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEds | ||
b2JhbEcyVExTUlNBU0hBMjU2MjAyMENBMS0xLmNydDAMBgNVHRMBAf8EAjAAMIIB | ||
fQYKKwYBBAHWeQIEAgSCAW0EggFpAWcAdABOdaMnXJoQwzhbbNTfP1LrHfDgjhuN | ||
acCx+mSxYpo53wAAAY1b0vxkAAAEAwBFMEMCH0BRCgxPbBBVxhcWZ26a8JCe83P1 | ||
JZ6wmv56GsVcyMACIDgpMbEo5HJITTRPnoyT4mG8cLrWjEvhchUdEcWUuk1TAHYA | ||
fVkeEuF4KnscYWd8Xv340IdcFKBOlZ65Ay/ZDowuebgAAAGNW9L8MAAABAMARzBF | ||
AiBdv5Z3pZFbfgoM3tGpCTM3ZxBMQsxBRSdTS6d8d2NAcwIhALLoCT9mTMN9OyFz | ||
IBV5MkXVLyuTf2OAzAOa7d8x2H6XAHcA5tIxY0B3jMEQQQbXcbnOwdJA9paEhvu6 | ||
hzId/R43jlAAAAGNW9L8XwAABAMASDBGAiEA4Koh/VizdQU1tjZ2E2VGgWSXXkwn | ||
QmiYhmAeKcVLHeACIQD7JIGFsdGol7kss2pe4lYrCgPVc+iGZkuqnj26hqhr0TAN | ||
BgkqhkiG9w0BAQsFAAOCAQEABOFuAj4N4yNG9OOWNQWTNSICC4Rd4nOG1HRP/Bsn | ||
rz7KrcPORtb6D+Jx+Q0amhO31QhIvVBYs14gY4Ypyj7MzHgm4VmPXcqLvEkxb2G9 | ||
Qv9hYuEiNSQmm1fr5QAN/0AzbEbCM3cImLJ69kP5bUjfv/76KB57is8tYf9sh5ik | ||
LGKauxCM/zRIcGa3bXLDafk5S2g5Vr2hs230d/NGW1wZrE+zdGuMxfGJzJP+DAFv | ||
iBfcQnFg4+1zMEKcqS87oniOyG+60RMM0MdejBD7AS43m9us96Gsun/4kufLQUTI | ||
FfnzxLutUV++3seshgefQOy5C/ayi8y1VTNmujPCxPCi6Q== | ||
-----END CERTIFICATE-----""".trimIndent() | ||
|
||
return pem.byteInputStream().use { | ||
CertificateFactory.getInstance("X.509").generateCertificate(it) as X509Certificate | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
/* | ||
* Copyright (c) 2024 Gaurav Ujjwal. | ||
* | ||
* SPDX-License-Identifier: GPL-3.0-or-later | ||
* | ||
* See COPYING.txt for more details. | ||
*/ | ||
|
||
package com.gaurav.avnc.util | ||
|
||
import android.content.Context | ||
import android.util.Log | ||
import com.google.crypto.tink.subtle.Hex | ||
import java.io.File | ||
import java.security.MessageDigest | ||
import java.security.cert.Certificate | ||
import java.security.cert.CertificateFactory | ||
import java.security.cert.X509Certificate | ||
import javax.security.auth.x500.X500Principal | ||
|
||
// Utilities related to known hosts & certificates | ||
|
||
private fun getTrustedCertsDir(context: Context) = File(context.filesDir, "trusted_certs") | ||
|
||
private fun getFileForTrustedCertificate(context: Context, certificate: Certificate): File { | ||
val certDigest = MessageDigest.getInstance("SHA1").digest(certificate.encoded) | ||
val certFile = Hex.encode(certDigest) | ||
val certDir = getTrustedCertsDir(context) | ||
return File(certDir, certFile) | ||
} | ||
|
||
/** | ||
* Adds given [certificate] to trusted list. | ||
*/ | ||
fun trustCertificate(context: Context, certificate: Certificate) { | ||
runCatching { | ||
val certDir = getTrustedCertsDir(context) | ||
val certFile = getFileForTrustedCertificate(context, certificate) | ||
certDir.mkdirs() | ||
certFile.writeBytes(certificate.encoded) | ||
}.onFailure { | ||
Log.e("KnownHosts", "Error trusting certificate", it) | ||
} | ||
} | ||
|
||
/** | ||
* Checks whether given [certificate] is trusted. | ||
*/ | ||
fun isCertificateTrusted(context: Context, certificate: Certificate): Boolean { | ||
runCatching { | ||
val trustedFile = getFileForTrustedCertificate(context, certificate) | ||
if (!trustedFile.exists()) | ||
return false | ||
|
||
// This should always succeed once file exists | ||
val certFactory = CertificateFactory.getInstance("X.509") | ||
val trustedCert = trustedFile.inputStream().use { certFactory.generateCertificate(it) } | ||
if (trustedCert.equals(certificate)) | ||
return true | ||
}.onFailure { | ||
Log.w("KnownHosts", "Error checking certificate", it) | ||
} | ||
return false | ||
} | ||
|
||
@OptIn(ExperimentalStdlibApi::class) | ||
fun getUnknownCertificateMessage(certificate: X509Certificate): String { | ||
fun commonName(p: X500Principal) = p.name.split(',') // Doesn't handle escaped comma | ||
.find { it.startsWith("CN=", true) } | ||
?.drop(3) ?: "Unknown" | ||
|
||
val subject = commonName(certificate.subjectX500Principal) | ||
val issuer = commonName(certificate.issuerX500Principal) | ||
val fingerprint = MessageDigest.getInstance("SHA1").digest(certificate.encoded) | ||
.toHexString(HexFormat { upperCase = true; bytes { byteSeparator = " " } }) | ||
|
||
return """ | ||
Certificate received from server is not trusted. Someone might be impersonating the server. | ||
Subject: $subject | ||
Issuer: $issuer | ||
Fingerprint (SHA1): $fingerprint | ||
Make sure you are connecting to right server. Click Continue to add this certificate to trusted list. | ||
""".trimIndent() | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Submodule libvncserver
updated
3 files
+5 −0 | include/rfb/rfbclient.h | |
+39 −1 | src/libvncclient/tls_openssl.c | |
+1 −0 | src/libvncclient/vncviewer.c |