Skip to content
GitLab
Projects
Groups
Snippets
Help
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Sign in / Register
Toggle navigation
C
cert4android
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Locked Files
Merge Requests
0
Merge Requests
0
Requirements
Requirements
List
Security & Compliance
Security & Compliance
Dependency List
License Compliance
Analytics
Analytics
Code Review
Insights
Issue
Repository
Value Stream
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Commits
Open sidebar
e
apps
cert4android
Commits
846f0cd3
Commit
846f0cd3
authored
Apr 19, 2019
by
Ricki Hirner
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Use Conscrypt
parent
aae988de
Changes
5
Hide whitespace changes
Inline
Side-by-side
Showing
5 changed files
with
38 additions
and
297 deletions
+38
-297
README.md
README.md
+18
-0
build.gradle
build.gradle
+3
-1
src/androidTest/java/at/bitfire/cert4android/CertTlsSocketFactoryTest.kt
.../java/at/bitfire/cert4android/CertTlsSocketFactoryTest.kt
+0
-127
src/main/java/at/bitfire/cert4android/CertTlsSocketFactory.kt
...main/java/at/bitfire/cert4android/CertTlsSocketFactory.kt
+0
-168
src/main/java/at/bitfire/cert4android/CustomCertService.kt
src/main/java/at/bitfire/cert4android/CustomCertService.kt
+17
-1
No files found.
README.md
View file @
846f0cd3
...
...
@@ -31,6 +31,24 @@ Discussion: https://forums.bitfire.at/category/7/transport-level-security
1.
Close the instance when it's not required anymore (will disconnect from the
`CustomCertService`
, thus allowing it to be destroyed).
Example of initialzing an okhttp client:
val keyManager = ...
CustomCertManager(...).use { trustManager ->
val sslContext = SSLContext.getInstance("TLS")
sslContext.init(
if (keyManager != null) arrayOf(keyManager) else null,
arrayOf(trustManager),
null
)
val builder = OkHttpClient.Builder()
builder.sslSocketFactory(sslContext.socketFactory, trustManager)
.hostnameVerifier(hostnameVerifier)
val httpClient = builder.build()
// use httpClient
}
You can overwrite resources when you want, just have a look at the
`res/strings`
directory. Especially
`certificate_notification_connection_security`
and
`trust_certificate_unknown_certificate_found`
should contain your app name.
...
...
build.gradle
View file @
846f0cd3
...
...
@@ -2,7 +2,8 @@
buildscript
{
ext
.
versions
=
[
kotlin:
'1.3.30'
,
dokka:
'0.9.17'
dokka:
'0.9.17'
,
conscrypt:
'2.1.0'
]
repositories
{
...
...
@@ -55,6 +56,7 @@ dependencies {
implementation
'androidx.lifecycle:lifecycle-extensions:2.0.0'
implementation
'androidx.lifecycle:lifecycle-livedata:2.0.0'
implementation
'androidx.lifecycle:lifecycle-viewmodel-ktx:2.0.0'
implementation
"org.conscrypt:conscrypt-android:${versions.conscrypt}"
androidTestImplementation
'androidx.test:runner:1.1.1'
androidTestImplementation
'androidx.test:rules:1.1.1'
...
...
src/androidTest/java/at/bitfire/cert4android/CertTlsSocketFactoryTest.kt
deleted
100644 → 0
View file @
aae988de
/*
* Copyright © Ricki Hirner (bitfire web engineering).
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the GNU Public License v3.0
* which accompanies this distribution, and is available at
* http://www.gnu.org/licenses/gpl.html
*/
package
at.bitfire.cert4android
import
androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
import
okhttp3.OkHttpClient
import
okhttp3.Request
import
okhttp3.mockwebserver.MockWebServer
import
org.apache.commons.io.IOUtils
import
org.apache.commons.lang3.ArrayUtils.contains
import
org.junit.After
import
org.junit.Assert.*
import
org.junit.Before
import
org.junit.Test
import
java.net.Socket
import
java.security.KeyFactory
import
java.security.KeyStore
import
java.security.Principal
import
java.security.cert.CertificateFactory
import
java.security.cert.X509Certificate
import
java.security.spec.PKCS8EncodedKeySpec
import
javax.net.ssl.SSLSocket
import
javax.net.ssl.TrustManagerFactory
import
javax.net.ssl.X509ExtendedKeyManager
import
javax.net.ssl.X509TrustManager
class
CertTlsSocketFactoryTest
{
private
lateinit
var
certMgr
:
CustomCertManager
private
lateinit
var
factory
:
CertTlsSocketFactory
private
val
server
=
MockWebServer
()
@Before
fun
startServer
()
{
certMgr
=
CustomCertManager
(
getInstrumentation
().
context
,
false
,
true
)
factory
=
CertTlsSocketFactory
(
null
,
certMgr
)
server
.
start
()
}
@After
fun
stopServer
()
{
server
.
shutdown
()
certMgr
.
close
()
}
@Test
fun
testSendClientCertificate
()
{
var
public
:
X509Certificate
?
=
null
javaClass
.
classLoader
!!
.
getResourceAsStream
(
"sample.crt"
).
use
{
public
=
CertificateFactory
.
getInstance
(
"X509"
).
generateCertificate
(
it
)
as
?
X509Certificate
}
assertNotNull
(
public
)
val
keyFactory
=
KeyFactory
.
getInstance
(
"RSA"
)
val
private
=
keyFactory
.
generatePrivate
(
PKCS8EncodedKeySpec
(
readResource
(
"sample.key"
)))
assertNotNull
(
private
)
val
keyStore
=
KeyStore
.
getInstance
(
"AndroidKeyStore"
)
keyStore
.
load
(
null
)
val
alias
=
"sample"
keyStore
.
setKeyEntry
(
alias
,
private
,
null
,
arrayOf
(
public
))
assertTrue
(
keyStore
.
containsAlias
(
alias
))
val
trustManagerFactory
=
TrustManagerFactory
.
getInstance
(
"X509"
)
trustManagerFactory
.
init
(
null
as
KeyStore
?)
val
trustManager
=
trustManagerFactory
.
trustManagers
.
first
()
as
X509TrustManager
val
factory
=
CertTlsSocketFactory
(
object
:
X509ExtendedKeyManager
()
{
override
fun
getServerAliases
(
p0
:
String
?,
p1
:
Array
<
out
Principal
>?):
Array
<
String
>?
=
null
override
fun
chooseServerAlias
(
p0
:
String
?,
p1
:
Array
<
out
Principal
>?,
p2
:
Socket
?)
=
null
override
fun
getClientAliases
(
p0
:
String
?,
p1
:
Array
<
out
Principal
>?)
=
arrayOf
(
alias
)
override
fun
chooseClientAlias
(
p0
:
Array
<
out
String
>?,
p1
:
Array
<
out
Principal
>?,
p2
:
Socket
?)
=
alias
override
fun
getCertificateChain
(
forAlias
:
String
?)
=
arrayOf
(
public
).
takeIf
{
forAlias
==
alias
}
override
fun
getPrivateKey
(
forAlias
:
String
?)
=
private
.
takeIf
{
forAlias
==
alias
}
},
trustManager
)
/* known client cert test URLs (thanks!):
* - https://prod.idrix.eu/secure/
* - https://server.cryptomix.com/secure/
*/
val
client
=
OkHttpClient
.
Builder
()
.
sslSocketFactory
(
factory
,
trustManager
)
.
build
()
client
.
newCall
(
Request
.
Builder
()
.
get
()
.
url
(
"https://prod.idrix.eu/secure/"
)
.
build
()).
execute
().
use
{
response
->
assertTrue
(
response
.
isSuccessful
)
assertTrue
(
response
.
body
()
!!
.
string
().
contains
(
"CN=User Cert,O=Internet Widgits Pty Ltd,ST=Some-State,C=CA"
))
}
}
@Test
fun
testUpgradeTLS
()
{
val
s
=
factory
.
createSocket
(
server
.
hostName
,
server
.
port
)
assertTrue
(
s
is
SSLSocket
)
val
ssl
=
s
as
SSLSocket
assertFalse
(
contains
(
ssl
.
enabledProtocols
,
"SSLv3"
))
assertTrue
(
contains
(
ssl
.
enabledProtocols
,
"TLSv1"
))
assertTrue
(
contains
(
ssl
.
enabledProtocols
,
"TLSv1.1"
))
assertTrue
(
contains
(
ssl
.
enabledProtocols
,
"TLSv1.2"
))
}
private
fun
readResource
(
name
:
String
):
ByteArray
{
javaClass
.
classLoader
!!
.
getResourceAsStream
(
name
).
use
{
return
IOUtils
.
toByteArray
(
it
)
}
}
}
src/main/java/at/bitfire/cert4android/CertTlsSocketFactory.kt
deleted
100644 → 0
View file @
aae988de
/*
* Copyright © Ricki Hirner (bitfire web engineering).
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the GNU Public License v3.0
* which accompanies this distribution, and is available at
* http://www.gnu.org/licenses/gpl.html
*/
package
at.bitfire.cert4android
import
android.os.Build
import
android.util.Log
import
java.io.IOException
import
java.net.InetAddress
import
java.net.Socket
import
java.security.GeneralSecurityException
import
java.util.*
import
javax.net.ssl.*
/**
* Custom TLS socket factory with support for
* - enabling/disabling algorithms depending on the Android version,
* - client certificate authentication
*
* @param keyManager a key manager which provides client certificates, or null
* @param trustManager trust manager to use (most likely a [CustomCertManager] instance)
*/
class
CertTlsSocketFactory
(
keyManager
:
KeyManager
?,
trustManager
:
X509TrustManager
):
SSLSocketFactory
()
{
private
var
delegate
:
SSLSocketFactory
companion
object
{
// Android 5.0+ (API level 21) provides reasonable default settings
// but it still allows SSLv3
// https://developer.android.com/reference/javax/net/ssl/SSLSocket.html
var
protocols
:
Array
<
String
>?
=
null
var
cipherSuites
:
Array
<
String
>?
=
null
init
{
if
(
Build
.
VERSION
.
SDK_INT
>=
23
)
{
// Since Android 6.0 (API level 23),
// - TLSv1.1 and TLSv1.2 is enabled by default
// - SSLv3 is disabled by default
// - all modern ciphers are activated by default
protocols
=
null
cipherSuites
=
null
Log
.
d
(
Constants
.
TAG
,
"Using device default TLS protocols/ciphers"
)
}
else
{
(
SSLSocketFactory
.
getDefault
().
createSocket
()
as
?
SSLSocket
)
?.
use
{
socket
->
try
{
/* set reasonable protocol versions */
// - enable all supported protocols (enables TLSv1.1 and TLSv1.2 on Android <5.0)
// - remove all SSL versions (especially SSLv3) because they're insecure now
val
whichProtocols
=
LinkedList
<
String
>()
for
(
protocol
in
socket
.
supportedProtocols
.
filterNot
{
it
.
contains
(
"SSL"
,
true
)
})
whichProtocols
+=
protocol
Log
.
i
(
Constants
.
TAG
,
"Enabling (only) these TLS protocols: ${whichProtocols.joinToString("
,
")}"
)
protocols
=
whichProtocols
.
toTypedArray
()
/* set up reasonable cipher suites */
val
knownCiphers
=
arrayOf
(
// TLS 1.2
"TLS_RSA_WITH_AES_256_GCM_SHA384"
,
"TLS_RSA_WITH_AES_128_GCM_SHA256"
,
"TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256"
,
"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256"
,
"TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384"
,
"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256"
,
"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256"
,
// maximum interoperability
"TLS_RSA_WITH_3DES_EDE_CBC_SHA"
,
"SSL_RSA_WITH_3DES_EDE_CBC_SHA"
,
"TLS_RSA_WITH_AES_128_CBC_SHA"
,
// additionally
"TLS_RSA_WITH_AES_256_CBC_SHA"
,
"TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA"
,
"TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA"
,
"TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA"
,
"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA"
)
val
availableCiphers
=
socket
.
supportedCipherSuites
Log
.
i
(
Constants
.
TAG
,
"Available cipher suites: ${availableCiphers.joinToString("
,
")}"
)
/* For maximum security, preferredCiphers should *replace* enabled ciphers (thus
* disabling ciphers which are enabled by default, but have become unsecure), but for
* the security level of DAVx5 and maximum compatibility, disabling of insecure
* ciphers should be a server-side task */
// for the final set of enabled ciphers, take the ciphers enabled by default, ...
val
whichCiphers
=
LinkedList
<
String
>()
whichCiphers
.
addAll
(
socket
.
enabledCipherSuites
)
Log
.
i
(
Constants
.
TAG
,
"Cipher suites enabled by default: ${whichCiphers.joinToString("
,
")}"
)
// ... add explicitly allowed ciphers ...
whichCiphers
.
addAll
(
knownCiphers
)
// ... and keep only those which are actually available
whichCiphers
.
retainAll
(
availableCiphers
)
Log
.
i
(
Constants
.
TAG
,
"Enabling (only) these TLS ciphers: "
+
whichCiphers
.
joinToString
(
", "
))
cipherSuites
=
whichCiphers
.
toTypedArray
()
}
catch
(
e
:
IOException
)
{
Log
.
e
(
Constants
.
TAG
,
"Couldn't determine default TLS settings"
)
}
}
}
}
}
init
{
try
{
val
sslContext
=
SSLContext
.
getInstance
(
"TLS"
)
sslContext
.
init
(
if
(
keyManager
!=
null
)
arrayOf
(
keyManager
)
else
null
,
arrayOf
(
trustManager
),
null
)
delegate
=
sslContext
.
socketFactory
}
catch
(
e
:
GeneralSecurityException
)
{
throw
IllegalStateException
()
// system has no TLS
}
}
override
fun
getDefaultCipherSuites
():
Array
<
String
>?
=
cipherSuites
?:
delegate
.
defaultCipherSuites
override
fun
getSupportedCipherSuites
():
Array
<
String
>?
=
cipherSuites
?:
delegate
.
supportedCipherSuites
override
fun
createSocket
(
s
:
Socket
,
host
:
String
,
port
:
Int
,
autoClose
:
Boolean
):
Socket
{
val
ssl
=
delegate
.
createSocket
(
s
,
host
,
port
,
autoClose
)
if
(
ssl
is
SSLSocket
)
upgradeTLS
(
ssl
)
return
ssl
}
override
fun
createSocket
(
host
:
String
,
port
:
Int
):
Socket
{
val
ssl
=
delegate
.
createSocket
(
host
,
port
)
if
(
ssl
is
SSLSocket
)
upgradeTLS
(
ssl
)
return
ssl
}
override
fun
createSocket
(
host
:
String
,
port
:
Int
,
localHost
:
InetAddress
,
localPort
:
Int
):
Socket
{
val
ssl
=
delegate
.
createSocket
(
host
,
port
,
localHost
,
localPort
)
if
(
ssl
is
SSLSocket
)
upgradeTLS
(
ssl
)
return
ssl
}
override
fun
createSocket
(
host
:
InetAddress
,
port
:
Int
):
Socket
{
val
ssl
=
delegate
.
createSocket
(
host
,
port
)
if
(
ssl
is
SSLSocket
)
upgradeTLS
(
ssl
)
return
ssl
}
override
fun
createSocket
(
address
:
InetAddress
,
port
:
Int
,
localAddress
:
InetAddress
,
localPort
:
Int
):
Socket
{
val
ssl
=
delegate
.
createSocket
(
address
,
port
,
localAddress
,
localPort
)
if
(
ssl
is
SSLSocket
)
upgradeTLS
(
ssl
)
return
ssl
}
private
fun
upgradeTLS
(
ssl
:
SSLSocket
)
{
protocols
?.
let
{
ssl
.
enabledProtocols
=
it
}
cipherSuites
?.
let
{
ssl
.
enabledCipherSuites
=
it
}
}
}
src/main/java/at/bitfire/cert4android/CustomCertService.kt
View file @
846f0cd3
...
...
@@ -12,23 +12,27 @@ import android.app.PendingIntent
import
android.app.Service
import
android.content.Context
import
android.content.Intent
import
android.util.Log
import
android.widget.Toast
import
androidx.core.app.NotificationCompat
import
org.conscrypt.Conscrypt
import
java.io.ByteArrayInputStream
import
java.io.File
import
java.io.FileInputStream
import
java.io.FileOutputStream
import
java.security.KeyStore
import
java.security.KeyStoreException
import
java.security.Security
import
java.security.cert.CertificateFactory
import
java.security.cert.X509Certificate
import
java.util.*
import
java.util.logging.Level
import
javax.net.ssl.SSLContext
import
javax.net.ssl.X509TrustManager
/**
* The service which manages the certificates. Communications with
* the [CustomCertManager]s over IPC.
* the [CustomCertManager]s over IPC.
Initializes Conscrypt when class is loaded.
*
* This services is both a started and a bound service.
*/
...
...
@@ -51,6 +55,18 @@ class CustomCertService: Service() {
const
val
KEYSTORE_DIR
=
"KeyStore"
const
val
KEYSTORE_NAME
=
"KeyStore.bks"
init
{
// initialize Conscrypt
Security
.
insertProviderAt
(
Conscrypt
.
newProvider
(),
1
)
val
version
=
Conscrypt
.
version
()
Log
.
i
(
Constants
.
TAG
,
"Using Conscrypt/${version.major()}.${version.minor()}.${version.patch()} for TLS"
)
val
engine
=
SSLContext
.
getDefault
().
createSSLEngine
()
Log
.
i
(
Constants
.
TAG
,
"Enabled protocols: ${engine.enabledProtocols.joinToString("
,
")}"
)
Log
.
i
(
Constants
.
TAG
,
"Enabled ciphers: ${engine.enabledCipherSuites.joinToString("
,
")}"
)
}
}
private
lateinit
var
keyStoreFile
:
File
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment