@@ -2,14 +2,17 @@ package com.github.gotify.api
22
33import android.annotation.SuppressLint
44import com.github.gotify.SSLSettings
5- import com.github.gotify.Utils
6- import java.io.IOException
5+ import java.io.File
6+ import java.io.FileInputStream
7+ import java.io.InputStream
78import java.security.GeneralSecurityException
89import java.security.KeyStore
910import java.security.SecureRandom
1011import java.security.cert.Certificate
1112import java.security.cert.CertificateFactory
1213import java.security.cert.X509Certificate
14+ import javax.net.ssl.KeyManager
15+ import javax.net.ssl.KeyManagerFactory
1316import javax.net.ssl.SSLContext
1417import javax.net.ssl.TrustManager
1518import javax.net.ssl.TrustManagerFactory
@@ -18,6 +21,9 @@ import okhttp3.OkHttpClient
1821import org.tinylog.kotlin.Logger
1922
2023internal object CertUtils {
24+ const val CA_CERT_NAME = " ca-cert.crt"
25+ const val CLIENT_CERT_NAME = " client-cert.p12"
26+
2127 @SuppressLint(" CustomX509TrustManager" )
2228 private val trustAll = object : X509TrustManager {
2329 @SuppressLint(" TrustAllX509TrustManager" )
@@ -31,10 +37,10 @@ internal object CertUtils {
3137 override fun getAcceptedIssuers () = arrayOf<X509Certificate >()
3238 }
3339
34- fun parseCertificate (cert : String ): Certificate {
40+ fun parseCertificate (inputStream : InputStream ): Certificate {
3541 try {
3642 val certificateFactory = CertificateFactory .getInstance(" X509" )
37- return certificateFactory.generateCertificate(Utils .stringToInputStream(cert) )
43+ return certificateFactory.generateCertificate(inputStream )
3844 } catch (e: Exception ) {
3945 throw IllegalArgumentException (" certificate is invalid" )
4046 }
@@ -43,24 +49,34 @@ internal object CertUtils {
4349 fun applySslSettings (builder : OkHttpClient .Builder , settings : SSLSettings ) {
4450 // Modified from ApiClient.applySslSettings in the client package.
4551 try {
46- if (! settings.validateSSL) {
47- val context = SSLContext .getInstance(" TLS" )
48- context.init (arrayOf(), arrayOf<TrustManager >(trustAll), SecureRandom ())
49- builder.sslSocketFactory(context.socketFactory, trustAll)
52+ val trustManagers = mutableSetOf<TrustManager >()
53+ val keyManagers = mutableSetOf<KeyManager >()
54+ if (settings.validateSSL) {
55+ // Custom SSL validation
56+ settings.caCertPath?.let { trustManagers.addAll(certToTrustManager(it)) }
57+ } else {
58+ // Disable SSL validation
59+ trustManagers.add(trustAll)
5060 builder.hostnameVerifier { _, _ -> true }
51- return
5261 }
53- val cert = settings.cert
54- if (cert != null ) {
55- val trustManagers = certToTrustManager(cert)
56- if (trustManagers.isNotEmpty()) {
57- val context = SSLContext .getInstance(" TLS" )
58- context.init (arrayOf(), trustManagers, SecureRandom ())
59- builder.sslSocketFactory(
60- context.socketFactory,
61- trustManagers[0 ] as X509TrustManager
62- )
62+ settings.clientCertPath?.let {
63+ keyManagers.addAll(certToKeyManager(it, settings.clientCertPassword))
64+ }
65+ if (trustManagers.isNotEmpty() || keyManagers.isNotEmpty()) {
66+ if (trustManagers.isEmpty()) {
67+ // Fall back to system trust managers
68+ trustManagers.addAll(defaultSystemTrustManager())
6369 }
70+ val context = SSLContext .getInstance(" TLS" )
71+ context.init (
72+ keyManagers.toTypedArray(),
73+ trustManagers.toTypedArray(),
74+ SecureRandom ()
75+ )
76+ builder.sslSocketFactory(
77+ context.socketFactory,
78+ trustManagers.elementAt(0 ) as X509TrustManager
79+ )
6480 }
6581 } catch (e: Exception ) {
6682 // We shouldn't have issues since the cert is verified on login.
@@ -69,12 +85,14 @@ internal object CertUtils {
6985 }
7086
7187 @Throws(GeneralSecurityException ::class )
72- private fun certToTrustManager (cert : String ): Array <TrustManager > {
88+ private fun certToTrustManager (certPath : String ): Array <TrustManager > {
7389 val certificateFactory = CertificateFactory .getInstance(" X.509" )
74- val certificates = certificateFactory.generateCertificates(Utils .stringToInputStream(cert))
90+ val certificates = FileInputStream (File (certPath)).use(
91+ certificateFactory::generateCertificates
92+ )
7593 require(certificates.isNotEmpty()) { " expected non-empty set of trusted certificates" }
7694
77- val caKeyStore = newEmptyKeyStore()
95+ val caKeyStore = KeyStore .getInstance( KeyStore .getDefaultType()). apply { load( null ) }
7896 certificates.forEachIndexed { index, certificate ->
7997 val certificateAlias = " ca$index "
8098 caKeyStore.setCertificateEntry(certificateAlias, certificate)
@@ -86,13 +104,24 @@ internal object CertUtils {
86104 }
87105
88106 @Throws(GeneralSecurityException ::class )
89- private fun newEmptyKeyStore (): KeyStore {
90- return try {
91- val keyStore = KeyStore .getInstance(KeyStore .getDefaultType())
92- keyStore.load(null , null )
93- keyStore
94- } catch (e: IOException ) {
95- throw AssertionError (e)
107+ private fun certToKeyManager (certPath : String , certPassword : String? ): Array <KeyManager > {
108+ require(certPassword != null ) { " empty client certificate password" }
109+
110+ val keyStore = KeyStore .getInstance(" PKCS12" )
111+ FileInputStream (File (certPath)).use {
112+ keyStore.load(it, certPassword.toCharArray())
96113 }
114+ val keyManagerFactory =
115+ KeyManagerFactory .getInstance(KeyManagerFactory .getDefaultAlgorithm())
116+ keyManagerFactory.init (keyStore, certPassword.toCharArray())
117+ return keyManagerFactory.keyManagers
118+ }
119+
120+ private fun defaultSystemTrustManager (): Array <TrustManager > {
121+ val trustManagerFactory = TrustManagerFactory .getInstance(
122+ TrustManagerFactory .getDefaultAlgorithm()
123+ )
124+ trustManagerFactory.init (null as KeyStore ? )
125+ return trustManagerFactory.trustManagers
97126 }
98127}
0 commit comments