Skip to content
Snippets Groups Projects
Unverified Commit 3bfeb0b5 authored by Jakob Möller's avatar Jakob Möller Committed by GitHub
Browse files

Implement SignatureCheck based on BKS Key Store Certificate for Appli… (#206)


* Implement SignatureCheck based on BKS Key Store Certificate for ApplicationConfig

Signed-off-by: default avatard067928 <jakob.moeller@sap.com>

* Lint Check

Signed-off-by: default avatard067928 <jakob.moeller@sap.com>
parent c166e4cb
No related branches found
No related tags found
No related merge requests found
......@@ -40,12 +40,16 @@ android {
buildConfigField "String", "DOWNLOAD_CDN_URL", "\"$DOWNLOAD_CDN_URL\""
buildConfigField "String", "SUBMISSION_CDN_URL", "\"$SUBMISSION_CDN_URL\""
buildConfigField "String", "VERIFICATION_CDN_URL", "\"$VERIFICATION_CDN_URL\""
buildConfigField "String", "TRUSTED_CERTS_EXPORT_KEYSTORE_PW", "\"$TRUSTED_CERTS_EXPORT_KEYSTORE_PW\""
//override URLs with local variables
Properties properties = new Properties()
def propertiesFile = project.rootProject.file('local.properties')
if (propertiesFile.exists()) {
properties.load(propertiesFile.newDataInputStream())
def secretFile = project.rootProject.file('secrets.properties')
if (secretFile.exists())
properties.load(secretFile.newDataInputStream())
def DOWNLOAD_CDN_URL = properties.getProperty('DOWNLOAD_CDN_URL')
if (DOWNLOAD_CDN_URL)
......@@ -58,6 +62,10 @@ android {
def VERIFICATION_CDN_URL = properties.getProperty('VERIFICATION_CDN_URL')
if (VERIFICATION_CDN_URL)
buildConfigField "String", "VERIFICATION_CDN_URL", "\"$VERIFICATION_CDN_URL\""
def TRUSTED_CERTS_EXPORT_KEYSTORE_PW = properties.getProperty('TRUSTED_CERTS_EXPORT_KEYSTORE_PW')
if (TRUSTED_CERTS_EXPORT_KEYSTORE_PW)
buildConfigField "String", "TRUSTED_CERTS_EXPORT_KEYSTORE_PW", "\"$TRUSTED_CERTS_EXPORT_KEYSTORE_PW\""
}
}
......
File added
package de.rki.coronawarnapp.exception
import java.lang.Exception
class ApplicationConfigurationCorruptException : Exception(
"the application configuration is corrupt"
)
package de.rki.coronawarnapp.exception
class ApplicationConfigurationInvalidException : Exception(
"the application configuration is invalid"
)
package de.rki.coronawarnapp.exception
import java.lang.Exception
class CwaSecurityException(cause: Throwable) : Exception(
"something went wrong during a critical part of the application ensuring security, please refer" +
"to the details for more information",
cause
)
......@@ -21,6 +21,9 @@ package de.rki.coronawarnapp.http
import KeyExportFormat
import android.util.Log
import com.google.protobuf.InvalidProtocolBufferException
import de.rki.coronawarnapp.exception.ApplicationConfigurationCorruptException
import de.rki.coronawarnapp.exception.ApplicationConfigurationInvalidException
import de.rki.coronawarnapp.http.requests.RegistrationTokenRequest
import de.rki.coronawarnapp.http.requests.ReqistrationRequest
import de.rki.coronawarnapp.http.requests.TanRequestBody
......@@ -40,6 +43,9 @@ import java.util.UUID
object WebRequestBuilder {
private val TAG: String? = WebRequestBuilder::class.simpleName
private const val EXPORT_BINARY_FILE_NAME = "export.bin"
private const val EXPORT_SIGNATURE_FILE_NAME = "export.sig"
private val serviceFactory = ServiceFactory()
private val distributionService = serviceFactory.distributionService()
......@@ -81,25 +87,28 @@ object WebRequestBuilder {
suspend fun asyncGetApplicationConfigurationFromServer(): ApplicationConfiguration =
withContext(Dispatchers.IO) {
var applicationConfiguration: ApplicationConfiguration? = null
var exportBinary: ByteArray? = null
var exportSignature: ByteArray? = null
distributionService.getApplicationConfiguration(
DiagnosisKeyConstants.COUNTRY_APPCONFIG_DOWNLOAD_URL
).byteStream().unzip { entry, entryContent ->
if (entry.name == "export.bin") {
val appConfig = ApplicationConfiguration.parseFrom(entryContent)
applicationConfiguration = appConfig
}
if (entry.name == "export.sig") {
val signatures = KeyExportFormat.TEKSignatureList.parseFrom(entryContent)
signatures.signaturesList.forEach {
Log.d(TAG, it.signatureInfo.toString())
}
}
if (entry.name == EXPORT_BINARY_FILE_NAME) exportBinary = entryContent.copyOf()
if (entry.name == EXPORT_SIGNATURE_FILE_NAME) exportSignature = entryContent.copyOf()
}
if (exportBinary == null || exportSignature == null) {
throw ApplicationConfigurationInvalidException()
}
if (applicationConfiguration == null) {
throw IllegalArgumentException("no file was found in the downloaded zip")
if (!SecurityHelper.exportFileIsValid(exportBinary, exportSignature)) {
throw ApplicationConfigurationCorruptException()
}
try {
return@withContext ApplicationConfiguration.parseFrom(exportBinary)
} catch (e: InvalidProtocolBufferException) {
throw ApplicationConfigurationInvalidException()
}
return@withContext applicationConfiguration!!
}
suspend fun asyncGetRegistrationToken(
......
......@@ -19,18 +19,25 @@
package de.rki.coronawarnapp.util.security
import KeyExportFormat.TEKSignatureList
import android.content.Context
import android.content.SharedPreferences
import android.security.keystore.KeyGenParameterSpec
import android.security.keystore.KeyProperties
import androidx.security.crypto.EncryptedSharedPreferences
import androidx.security.crypto.MasterKeys
import de.rki.coronawarnapp.BuildConfig
import de.rki.coronawarnapp.CoronaWarnApplication
import de.rki.coronawarnapp.exception.CwaSecurityException
import java.lang.Exception
import java.lang.NullPointerException
import java.security.KeyStore
import java.security.MessageDigest
import java.security.SecureRandom
import javax.crypto.KeyGenerator
import javax.crypto.SecretKey
import java.security.Signature
import java.security.cert.Certificate
/**
* Key Store and Password Access
......@@ -41,16 +48,23 @@ object SecurityHelper {
private const val SHARED_PREF_NAME = "shared_preferences_cwa"
private val keyGenParameterSpec = MasterKeys.AES256_GCM_SPEC
private val masterKeyAlias = MasterKeys.getOrCreate(keyGenParameterSpec)
private const val AndroidKeyStore = "AndroidKeyStore"
private val keyStore: KeyStore by lazy {
KeyStore.getInstance(AndroidKeyStore).also {
private const val EXPORT_SIGNATURE_ALGORITHM = "SHA256withECDSA"
private const val CWA_EXPORT_CERTIFICATE_NAME_NON_PROD = "cwa non-prod certificate"
private const val CWA_EXPORT_CERTIFICATE_KEY_STORE = "trusted-certs-cwa.bks"
private const val ANDROID_KEY_STORE = "AndroidKeyStore"
private val androidKeyStore: KeyStore by lazy {
KeyStore.getInstance(ANDROID_KEY_STORE).also {
it.load(null)
}
}
val globalEncryptedSharedPreferencesInstance: SharedPreferences by lazy {
CoronaWarnApplication.getAppContext().getEncryptedSharedPrefs(SHARED_PREF_NAME)
withSecurityCatch {
CoronaWarnApplication.getAppContext().getEncryptedSharedPrefs(SHARED_PREF_NAME)
}
}
/**
......@@ -73,10 +87,10 @@ object SecurityHelper {
.toCharArray()
private fun getOrGenerateDBSecretKey(): SecretKey =
keyStore.getKey(CWA_APP_SQLITE_DB_PW, null).run {
androidKeyStore.getKey(CWA_APP_SQLITE_DB_PW, null).run {
return if (this == null) {
val kg: KeyGenerator = KeyGenerator.getInstance(
KeyProperties.KEY_ALGORITHM_AES, AndroidKeyStore
KeyProperties.KEY_ALGORITHM_AES, ANDROID_KEY_STORE
)
val spec: KeyGenParameterSpec = KeyGenParameterSpec.Builder(
CWA_APP_SQLITE_DB_PW,
......@@ -97,4 +111,41 @@ object SecurityHelper {
.getInstance("SHA-256")
.digest(input.toByteArray())
.fold("", { str, it -> str + "%02x".format(it) })
fun exportFileIsValid(export: ByteArray?, sig: ByteArray?) = withSecurityCatch {
Signature.getInstance(EXPORT_SIGNATURE_ALGORITHM).run {
initVerify(trustedCertForSignature)
update(export)
verify(TEKSignatureList
.parseFrom(sig)
.signaturesList
.first()
.signature
.toByteArray()
)
}
}
private val cwaKeyStore: KeyStore by lazy {
val keystoreFile = CoronaWarnApplication.getAppContext()
.assets.open(CWA_EXPORT_CERTIFICATE_KEY_STORE)
val keystore = KeyStore.getInstance(KeyStore.getDefaultType())
val keyStorePw = BuildConfig.TRUSTED_CERTS_EXPORT_KEYSTORE_PW
val password = keyStorePw.toCharArray()
if (password.isEmpty())
throw NullPointerException("TRUSTED_CERTS_EXPORT_KEYSTORE_PW is null")
keystore.load(keystoreFile, password)
keystore
}
private val trustedCertForSignature: Certificate by lazy {
val alias = CWA_EXPORT_CERTIFICATE_NAME_NON_PROD
cwaKeyStore.getCertificate(alias)
}
private fun <T> withSecurityCatch(doInCatch: () -> T) = try {
doInCatch.invoke()
} catch (e: Exception) {
throw CwaSecurityException(e)
}
}
......@@ -21,4 +21,5 @@ org.gradle.dependency.verification.console=verbose
# Variables for Server URLs. The variables in local.properties will override these
SUBMISSION_CDN_URL=
DOWNLOAD_CDN_URL=
VERIFICATION_CDN_URL=
\ No newline at end of file
VERIFICATION_CDN_URL=
TRUSTED_CERTS_EXPORT_KEYSTORE_PW=
\ No newline at end of file
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment