Commit a0079e42 authored by stopcovid@lunabee.com's avatar stopcovid@lunabee.com
Browse files

Update to 2.4.0

- Add wallet feature
parent d14a80c4
......@@ -61,6 +61,8 @@ dependencies {
api 'androidx.emoji:emoji-appcompat:_'
api 'androidx.emoji:emoji-bundled:_'
implementation "androidx.fragment:fragment-ktx:_"
api 'com.google.android.material:material:_'
api 'com.google.code.gson:gson:_'
......
......@@ -86,7 +86,7 @@ object ConfigManager {
val configList = gson.fromJson(this, ConfigurationWrapper::class.java).config
val jsonObject = JSONObject()
configList.forEach {
jsonObject.put(it.name, if (it.value is List<*>) gson.toJson(it.value) else it.value)
jsonObject.put(it.name, if (it.value is List<*> || it.value is Map<*, *>) gson.toJson(it.value) else it.value)
}
return gson.fromJson(jsonObject.toString(), ApiConfiguration::class.java).toDomain(gson)
}
......
......@@ -14,6 +14,7 @@ import com.google.gson.Gson
import com.google.gson.annotations.SerializedName
import com.google.gson.reflect.TypeToken
import com.lunabeestudio.domain.model.Configuration
import com.lunabeestudio.domain.model.WalletPublicKey
internal class ApiConfiguration(
@SerializedName("version")
......@@ -105,7 +106,17 @@ internal class ApiConfiguration(
@SerializedName("app.contagiousSpan")
val contagiousSpan: Int,
@SerializedName("app.ameliUrl")
val ameliUrl: String?
val ameliUrl: String?,
@SerializedName("app.displaySanitaryCertificatesWallet")
val displaySanitaryCertificatesWallet: Boolean,
@SerializedName("app.wallet.oldCertificateThresholdInDays")
val walletOldCertificateThresholdInDays: String,
@SerializedName("app.walletPubKeys")
val walletPublicKeys: String,
@SerializedName("app.wallet.testCertificateValidityThresholdInHours")
val testCertificateValidityThresholdInHours: Int,
@SerializedName("app.displaySanitaryCertificatesValidation")
val displaySanitaryCertificatesValidation: Boolean
)
internal fun ApiConfiguration.toDomain(gson: Gson) = Configuration(
......@@ -160,4 +171,15 @@ internal fun ApiConfiguration.toDomain(gson: Gson) = Configuration(
scanReportDelay = scanReportDelay,
contagiousSpan = contagiousSpan,
ameliUrl = ameliUrl,
displaySanitaryCertificatesWallet = displaySanitaryCertificatesWallet,
walletOldCertificateThresholdInDays = gson.fromJson(
walletOldCertificateThresholdInDays,
object : TypeToken<Map<String, Float>?>() {}.type
),
walletPublicKeys = gson.fromJson(
walletPublicKeys,
object : TypeToken<List<WalletPublicKey>?>() {}.type
),
testCertificateValidityThresholdInHours = testCertificateValidityThresholdInHours,
displaySanitaryCertificatesValidation = displaySanitaryCertificatesValidation
)
\ No newline at end of file
......@@ -20,6 +20,9 @@
<color name="color_icon_background">#FFFFFF</color>
<color name="color_on_gradient">#FFFFFF</color>
<color name="color_gray">#8A8A8A</color>
<color name="color_black_33">#55000000</color>
<color name="color_indigo">#5770BE</color>
<color name="color_no_risk">#449976</color>
<color name="color_risk">#fd706a</color>
......
......@@ -28,7 +28,7 @@
<dimen name="min_logo_height">150dp</dimen>
<dimen name="caption_font_size">15sp</dimen>
<dimen name="small_caption_font_size">12sp</dimen>
<dimen name="small_caption_font_size">13sp</dimen>
<dimen name="circle_font_size">24sp</dimen>
<dimen name="error_font_size">15sp</dimen>
<dimen name="header_font_size">18sp</dimen>
......
......@@ -42,4 +42,12 @@
<!-- Base application theme. -->
<style name="Theme.StopCovid" parent="Theme.Base.StopCovid" />
<!-- Base application theme. -->
<style name="Theme.StopCovid.Colored" parent="Theme.Base.StopCovid">
<item name="colorPrimary">@color/color_black_33</item>
<item name="colorOnPrimary">@android:color/white</item>
<item name="colorSurface">@color/color_black_33</item>
<item name="android:textColor">@android:color/white</item>
</style>
</resources>
......@@ -57,7 +57,7 @@
</style>
<style name="TextAppearance.StopCovid.Caption.Small.Grey">
<item name="android:textColor">@android:color/darker_gray</item>
<item name="android:textColor">@color/color_gray</item>
</style>
<style name="TextAppearance.StopCovid.Title.Circle">
......
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* Authors
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
* Created by Lunabee Studio / Date - 2021/30/03 - for the TOUS-ANTI-COVID project
*/
package com.lunabeestudio.domain.extension
import com.lunabeestudio.domain.model.Configuration
import com.lunabeestudio.domain.model.WalletCertificateType
import java.util.Locale
import java.util.concurrent.TimeUnit
fun Configuration.walletOldCertificateThresholdInMs(type: WalletCertificateType): Long {
return TimeUnit.DAYS.toMillis((walletOldCertificateThresholdInDays[type.code.toLowerCase(Locale.getDefault())])?.toLong() ?: 0.toLong())
}
fun Configuration.walletPublicKey(authority: String, certificateId: String): String? {
return walletPublicKeys.firstOrNull {
it.auth == authority
}?.pubKeys?.get(certificateId)
}
fun Configuration.certificateValidityThresholdInMs(): Long {
return TimeUnit.HOURS.toMillis(testCertificateValidityThresholdInHours.toLong())
}
......@@ -56,4 +56,9 @@ class Configuration(
val scanReportDelay: Int,
val contagiousSpan: Int,
val ameliUrl: String?,
var displaySanitaryCertificatesWallet: Boolean,
var walletOldCertificateThresholdInDays: Map<String, Float>,
val testCertificateValidityThresholdInHours: Int,
var walletPublicKeys: List<WalletPublicKey>,
var displaySanitaryCertificatesValidation: Boolean
)
\ No newline at end of file
package com.lunabeestudio.domain.model
class RawWalletCertificate(val type: WalletCertificateType, val value: String, val timestamp: Long)
\ No newline at end of file
package com.lunabeestudio.domain.model
enum class WalletCertificateType {
SANITARY {
override val code: String = "B2"
override val validationRegexp: Regex = "^[A-Z\\d]{4}([A-Z\\d]{4})([A-Z\\d]{4})[A-Z\\d]{8}B2([A-Z\\d]{4})F0([A-Z\\d\\s\\/]+)\\x1D?F1([A-Z\\s]+)\\x1D?F2(\\d{8})F3([FMU]{1})F4([A-Z\\d]{3,7})\\x1D?F5([PNIX]{1})F6(\\d{12})\\x1F{1}([A-Z\\d]{103})$".toRegex()
override val stringKey: String = "sanitaryCertificate"
};
abstract val code: String
abstract val stringKey: String
abstract val validationRegexp: Regex
}
\ No newline at end of file
package com.lunabeestudio.domain.model
data class WalletPublicKey(
val auth: String,
val pubKeys: Map<String, String>
)
\ No newline at end of file
......@@ -82,7 +82,7 @@ dependencies {
implementation 'com.google.protobuf:protobuf-javalite:_'
implementation 'org.bouncycastle:bcprov-jdk15on:_'
implementation 'commons-codec:commons-codec:_'
implementation project(path: ':domain')
implementation project(path: ':robert')
api project(path: ':ble')
......
......@@ -8,7 +8,7 @@
* Created by Lunabee Studio / Date - 2020/20/05 - for the TOUS-ANTI-COVID project
*/
package com.lunabeestudio.framework.sharedcrypto
package com.lunabeestudio.framework.crypto
import android.util.Base64
import com.google.common.truth.Truth.assertThat
......
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* Authors
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
* Created by Lunabee Studio / Date - 2021/29/03 - for the TOUS-ANTI-COVID project
*/
package com.lunabeestudio.framework.crypto
import com.google.common.truth.Truth.assertThat
import org.junit.Test
class BouncyCastleSignatureVerifierTest {
@Test
fun verify_signature() {
val result = BouncyCastleSignatureVerifier.verifySignature(
PUB_KEY,
MESSAGE,
SIGNATURE,
PUB_KEY_ALGORITHM,
SIGNATURE_ALGORITHM,
)
assertThat(result).isTrue()
}
companion object {
const val PUB_KEY: String = "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE6lPsrLPPis5Wl9u8Y7THfNIIfeEyq3q72MpzUm3gvr6ctYk4d3OQhn76GKSjSYX/0ZRC/cYO9479K0SUzYZ9yg=="
const val PUB_KEY_ALGORITHM: String = "ECDSA"
const val MESSAGE: String = "DC04DHI0TST11E3C1E3CB201FRF0JEAN LOUIS/EDOUARD\u001DF1DUPOND\u001DF225111980F3MF494309\u001DF5NF6110320211452"
const val SIGNATURE: String = "ZCQ5EDEXRCRYMU4U5U4YQSF5GOE2PMFFC6PDWOMZK64434TUCJWQLIXCRYMA5TWVT7TEZSF2S3ZCJSYK3JYFOBVUHNOEXQMEKWQDG3A"
const val SIGNATURE_ALGORITHM: String = "SHA256withECDSA"
}
}
package com.lunabeestudio.framework.testutils
fun ByteArray.toHexString() = "size($size) " + joinToString(" ") { "0x%02X".format(it) }
......@@ -8,7 +8,7 @@
* Created by Lunabee Studio / Date - 2020/20/05 - for the TOUS-ANTI-COVID project
*/
package com.lunabeestudio.framework.sharedcrypto
package com.lunabeestudio.framework.crypto
import com.lunabeestudio.domain.extension.safeUse
import com.lunabeestudio.robert.datasource.SharedCryptoDataSource
......
package com.lunabeestudio.framework.crypto
import android.util.Base64
import com.lunabeestudio.framework.extension.removePublicKeyDecoration
import org.apache.commons.codec.binary.Base32
import org.bouncycastle.jce.provider.BouncyCastleProvider
import java.nio.charset.StandardCharsets
import java.security.KeyFactory
import java.security.PublicKey
import java.security.Signature
import java.security.SignatureException
import java.security.spec.X509EncodedKeySpec
object BouncyCastleSignatureVerifier {
private const val DEFAULT_PUB_KEY_ALGORITHM: String = "ECDSA"
private const val DEFAULT_SIGNATURE_ALGORITHM: String = "SHA256withECDSA"
/**
* Verify the message against the provided signature
*
* @param rawPublicKey The raw public key encoded in base64
* @param message The message to verify
* @param rawSignature The raw signature used to verify the message (R & S components concatenated) encoded in base32
* @param publicKeyAlgorithm The algorithm used to generate the public key
* @param signatureKeyAlgorithm The algorithm used to generate the signature
*/
@Throws(SignatureException::class)
fun verifySignature(
rawPublicKey: String,
message: String,
rawSignature: String,
publicKeyAlgorithm: String = DEFAULT_PUB_KEY_ALGORITHM,
signatureKeyAlgorithm: String = DEFAULT_SIGNATURE_ALGORITHM,
): Boolean {
val bouncyCastleProvider = BouncyCastleProvider()
val publicKeySpec = X509EncodedKeySpec(Base64.decode(rawPublicKey.removePublicKeyDecoration(), Base64.NO_WRAP))
val keyFactory = KeyFactory.getInstance(publicKeyAlgorithm, bouncyCastleProvider)
val publicKey: PublicKey = keyFactory.generatePublic(publicKeySpec)
val ecdsaVerify: Signature = Signature.getInstance(signatureKeyAlgorithm)
val rawMessage = message.toByteArray(StandardCharsets.US_ASCII)
ecdsaVerify.initVerify(publicKey)
ecdsaVerify.update(rawMessage)
val decodedSignature = Base32().decode(rawSignature)
var r = decodedSignature.take(decodedSignature.size / 2).toByteArray()
var s = decodedSignature.takeLast(decodedSignature.size / 2).toByteArray()
// DER encoding
if (r.first() < 0x00) {
r = byteArrayOf(0x00) + r
}
if (s.first() < 0x00) {
s = byteArrayOf(0x00) + s
}
val rs = byteArrayOf(0x02, r.size.toByte()) + r + byteArrayOf(0x02, s.size.toByte()) + s
val derSignature = byteArrayOf(0x30, (rs.size).toByte()) + rs
return ecdsaVerify.verify(derSignature)
}
}
\ No newline at end of file
package com.lunabeestudio.framework.extension
fun String.removePublicKeyDecoration(): String {
return this
.replace("-----BEGIN PUBLIC KEY-----", "")
.replace("-----END PUBLIC KEY-----", "")
}
\ No newline at end of file
......@@ -21,6 +21,7 @@ import com.lunabeestudio.domain.model.Attestation
import com.lunabeestudio.domain.model.Calibration
import com.lunabeestudio.domain.model.Configuration
import com.lunabeestudio.domain.model.FormEntry
import com.lunabeestudio.domain.model.RawWalletCertificate
import com.lunabeestudio.domain.model.VenueQrCode
import com.lunabeestudio.framework.local.LocalCryptoManager
import com.lunabeestudio.robert.datasource.LocalKeystoreDataSource
......@@ -128,6 +129,16 @@ class SecureKeystoreDataSource(context: Context, private val cryptoManager: Loca
_attestationsLiveData.postValue(value)
}
override var rawWalletCertificates: List<RawWalletCertificate>?
get() = getEncryptedValue(SHARED_PREF_KEY_WALLET_CERTIFICATES, object : TypeToken<List<RawWalletCertificate>>() {}.type)
set(value) {
setEncryptedValue(SHARED_PREF_KEY_WALLET_CERTIFICATES, value)
_rawWalletCertificatesLiveData.postValue(value)
}
private var _rawWalletCertificatesLiveData: MutableLiveData<List<RawWalletCertificate>?> = MutableLiveData(rawWalletCertificates)
override val rawWalletCertificatesLiveData: LiveData<List<RawWalletCertificate>?>
get() = _rawWalletCertificatesLiveData
override var deprecatedAttestations: List<Map<String, FormEntry>>?
get() = getEncryptedValue(SHARED_PREF_KEY_DEPRECTATED_ATTESTATIONS, object : TypeToken<List<Map<String, FormEntry>>>() {}.type)
set(value) {
......@@ -295,7 +306,7 @@ class SecureKeystoreDataSource(context: Context, private val cryptoManager: Loca
sharedPreferences.edit()
.putString(
key,
cryptoManager.encryptToString(gson.toJson(value))
gson.toJson(value)
)
.apply()
} else {
......@@ -345,6 +356,7 @@ class SecureKeystoreDataSource(context: Context, private val cryptoManager: Loca
private const val SHARED_PREF_KEY_REPORT_TO_SEND_START_TIME = "shared.pref.report_to_send_start_time"
private const val SHARED_PREF_KEY_REPORT_TO_SEND_END_TIME = "shared.pref.report_to_send_end_time"
private const val SHARED_PREF_KEY_DECLARATION_TOKEN = "shared.pref.declaration_token"
private const val SHARED_PREF_KEY_WALLET_CERTIFICATES = "shared.pref.wallet_certificates"
// Add on to ROBERT for isolation
private const val SHARED_PREF_KEY_REPORT_SYMPTOMS_DATE = "shared.pref.reportSymptomsDate"
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment