Skip to content
This repository has been archived by the owner on Jun 20, 2023. It is now read-only.

Commit

Permalink
Implement SignatureCheck based on BKS Key Store Certificate for Appli… (
Browse files Browse the repository at this point in the history
#206)

* Implement SignatureCheck based on BKS Key Store Certificate for ApplicationConfig

Signed-off-by: d067928 <[email protected]>

* Lint Check

Signed-off-by: d067928 <[email protected]>
  • Loading branch information
jakobmoellerdev authored Jun 5, 2020
1 parent c166e4c commit 3bfeb0b
Show file tree
Hide file tree
Showing 8 changed files with 111 additions and 21 deletions.
8 changes: 8 additions & 0 deletions Corona-Warn-App/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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\""
}
}

Expand Down
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package de.rki.coronawarnapp.exception

import java.lang.Exception

class ApplicationConfigurationCorruptException : Exception(
"the application configuration is corrupt"
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package de.rki.coronawarnapp.exception

class ApplicationConfigurationInvalidException : Exception(
"the application configuration is invalid"
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
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
)
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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()
Expand Down Expand Up @@ -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(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)
}
}

/**
Expand All @@ -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,
Expand All @@ -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)
}
}
3 changes: 2 additions & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -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=
VERIFICATION_CDN_URL=
TRUSTED_CERTS_EXPORT_KEYSTORE_PW=

0 comments on commit 3bfeb0b

Please sign in to comment.