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

Update to 3.6.0

- Fix double heart icon on API 21
- Move forms section
- Conversion encryption
- DCC blacklisting
parent 6cfad772
......@@ -12,7 +12,7 @@ package com.lunabeestudio.stopcovid.coreui
object ConfigConstant {
private const val BASE_URL: String = "https://app-static.tousanticovid.gouv.fr/"
private const val VERSION_PATH: String = "json/version-34/"
private const val VERSION_PATH: String = "json/version-35/"
private const val VERSIONED_SERVER_URL: String = BASE_URL + VERSION_PATH
object Maintenance {
......@@ -104,27 +104,27 @@ object ConfigConstant {
const val VACCIN_EUROPE_CERTIFICATE_FULL_FILE: String = "vaccin-europe-certificate-full.png"
private const val VACCIN_EUROPE_CERTIFICATE_FULL_TEMPLATE_FILE: String = "vaccin-europe-certificate-full-%s.png"
val VACCIN_EUROPE_CERTIFICATE_FULL_TEMPLATE_URL: String = URL + VACCIN_EUROPE_CERTIFICATE_FULL_TEMPLATE_FILE
const val VACCIN_EUROPE_CERTIFICATE_FULL_TEMPLATE_URL: String = URL + VACCIN_EUROPE_CERTIFICATE_FULL_TEMPLATE_FILE
const val VACCIN_EUROPE_CERTIFICATE_THUMBNAIL_FILE: String = "vaccin-europe-certificate.png"
private const val VACCIN_EUROPE_CERTIFICATE_THUMBNAIL_TEMPLATE_FILE: String = "vaccin-europe-certificate-%s.png"
val VACCIN_EUROPE_CERTIFICATE_THUMBNAIL_TEMPLATE_URL: String = URL + VACCIN_EUROPE_CERTIFICATE_THUMBNAIL_TEMPLATE_FILE
const val VACCIN_EUROPE_CERTIFICATE_THUMBNAIL_TEMPLATE_URL: String = URL + VACCIN_EUROPE_CERTIFICATE_THUMBNAIL_TEMPLATE_FILE
const val TEST_EUROPE_CERTIFICATE_THUMBNAIL_FILE: String = "test-europe-certificate.png"
private const val TEST_EUROPE_CERTIFICATE_THUMBNAIL_TEMPLATE_FILE: String = "test-europe-certificate-%s.png"
val TEST_EUROPE_CERTIFICATE_THUMBNAIL_TEMPLATE_URL: String = URL + TEST_EUROPE_CERTIFICATE_THUMBNAIL_TEMPLATE_FILE
const val TEST_EUROPE_CERTIFICATE_THUMBNAIL_TEMPLATE_URL: String = URL + TEST_EUROPE_CERTIFICATE_THUMBNAIL_TEMPLATE_FILE
const val TEST_EUROPE_CERTIFICATE_FULL_FILE: String = "test-europe-certificate-full.png"
private const val TEST_EUROPE_CERTIFICATE_FULL_TEMPLATE_FILE: String = "test-europe-certificate-full-%s.png"
val TEST_EUROPE_CERTIFICATE_FULL_TEMPLATE_URL: String = URL + TEST_EUROPE_CERTIFICATE_FULL_TEMPLATE_FILE
const val TEST_EUROPE_CERTIFICATE_FULL_TEMPLATE_URL: String = URL + TEST_EUROPE_CERTIFICATE_FULL_TEMPLATE_FILE
const val RECOVERY_EUROPE_CERTIFICATE_THUMBNAIL_FILE: String = "recovery-europe-certificate.png"
private const val RECOVERY_EUROPE_CERTIFICATE_THUMBNAIL_TEMPLATE_FILE: String = "recovery-europe-certificate-%s.png"
val RECOVERY_EUROPE_CERTIFICATE_THUMBNAIL_TEMPLATE_URL: String = URL + RECOVERY_EUROPE_CERTIFICATE_THUMBNAIL_TEMPLATE_FILE
const val RECOVERY_EUROPE_CERTIFICATE_THUMBNAIL_TEMPLATE_URL: String = URL + RECOVERY_EUROPE_CERTIFICATE_THUMBNAIL_TEMPLATE_FILE
const val RECOVERY_EUROPE_CERTIFICATE_FULL_FILE: String = "recovery-europe-certificate-full.png"
private const val RECOVERY_EUROPE_CERTIFICATE_FULL_TEMPLATE_FILE: String = "recovery-europe-certificate-full-%s.png"
val RECOVERY_EUROPE_CERTIFICATE_FULL_TEMPLATE_URL: String = URL + RECOVERY_EUROPE_CERTIFICATE_FULL_TEMPLATE_FILE
const val RECOVERY_EUROPE_CERTIFICATE_FULL_TEMPLATE_URL: String = URL + RECOVERY_EUROPE_CERTIFICATE_FULL_TEMPLATE_FILE
}
object DccCertificates {
......@@ -144,6 +144,13 @@ object ConfigConstant {
const val LOCAL_FILENAME: String = "calibrationBle.json"
}
object Blacklist {
const val FILENAME: String = "certlist.json"
const val FOLDER: String = "CertList/"
const val URL: String = VERSIONED_SERVER_URL + FOLDER + FILENAME
const val ASSET_FILE_PATH: String = FOLDER + FILENAME
}
object Store {
const val GOOGLE: String = "market://details?id=fr.gouv.android.stopcovid"
const val HUAWEI: String = "appmarket://details?id=fr.gouv.android.stopcovid"
......
......@@ -22,6 +22,7 @@ enum class EnvConstant {
override val serverPublicKey: String =
"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEAc9IDt6qJq453SwyWPB94JaLB2VfTAcL43YVtMr3HhDCd22gKaQXIbX1d+tNhfvaKM51sxeaXziPjntUzbTNiw=="
override val dccCertificatesFilename: String = "dcc-certs.json"
override val conversionBaseUrl: String = "https://portail.tacv.myservices-ingroupe.com"
};
abstract val captchaApiKey: String
......@@ -33,5 +34,6 @@ enum class EnvConstant {
abstract val calibrationFilename: String
abstract val serverPublicKey: String
abstract val dccCertificatesFilename: String
abstract val conversionBaseUrl: String
}
\ No newline at end of file
......@@ -22,6 +22,7 @@ class CaptionItem : BaseItem<CaptionItem.ViewHolder>(
R.layout.item_caption, CaptionItem::ViewHolder, R.id.item_caption
) {
var text: CharSequence? = null
var spannedText: CharSequence? = null
var gravity: Int = Gravity.NO_GRAVITY
@StyleRes
......@@ -29,7 +30,7 @@ class CaptionItem : BaseItem<CaptionItem.ViewHolder>(
override fun bindView(holder: ViewHolder, payloads: List<Any>) {
super.bindView(holder, payloads)
holder.textView.text = text.safeEmojiSpanify()
holder.textView.text = spannedText ?: text.safeEmojiSpanify()
holder.textView.gravity = gravity
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {
holder.textView.setTextAppearance(textAppearance)
......
......@@ -131,12 +131,14 @@ internal class ApiConfiguration(
val covidPlusNoTracing: Int,
@SerializedName("app.displayCertificateConversion")
val displayCertificateConversion: Boolean,
@SerializedName("app.certificateConversionUrl")
val certificateConversionUrl: String,
@SerializedName("app.wallet.vaccin.daysAfterCompletion")
val daysAfterCompletion: String,
@SerializedName("app.wallet.certificateConversionSidepOnlyCode")
val certificateConversionSidepOnlyCode: String,
@SerializedName("app.wallet.conversionPublicKey")
val conversionPublicKey: String,
@SerializedName("app.wallet.conversionApiVersion")
val conversionApiVersion: Int,
)
internal fun ApiConfiguration.toDomain(gson: Gson) = Configuration(
......@@ -212,7 +214,6 @@ internal fun ApiConfiguration.toDomain(gson: Gson) = Configuration(
covidPlusWarning = covidPlusWarning,
covidPlusNoTracing = covidPlusNoTracing,
displayCertificateConversion = displayCertificateConversion,
certificateConversionUrl = certificateConversionUrl,
daysAfterCompletion = (
gson.fromJson(
daysAfterCompletion,
......@@ -220,4 +221,6 @@ internal fun ApiConfiguration.toDomain(gson: Gson) = Configuration(
) as List<ApiDaysAfterCompletionEntry>
).associate { Pair(it.code, it.value) },
certificateConversionSidepOnlyCode = gson.fromJson(certificateConversionSidepOnlyCode, object : TypeToken<List<String>>() {}.type),
conversionPublicKey = gson.fromJson(conversionPublicKey, object : TypeToken<Map<String, String>>() {}.type),
conversionApiVersion = conversionApiVersion,
)
......@@ -67,7 +67,8 @@ class Configuration(
val covidPlusWarning: Int,
val covidPlusNoTracing: Int,
var displayCertificateConversion: Boolean,
var certificateConversionUrl: String,
var daysAfterCompletion: Map<String, Int>,
var certificateConversionSidepOnlyCode: List<String>,
var conversionPublicKey: Map<String, String>,
var conversionApiVersion: Int,
)
......@@ -89,4 +89,7 @@ dependencies {
androidTestImplementation "androidx.test.ext:truth:_"
androidTestImplementation "com.squareup.okhttp3:mockwebserver:_"
androidTestImplementation "org.apache.commons:commons-text:_"
androidTestImplementation "com.squareup.okhttp3:mockwebserver:_"
androidTestImplementation "androidx.test.ext:junit:_"
androidTestImplementation "io.mockk:mockk-android:_"
}
......@@ -50,19 +50,24 @@ class BouncyCastleCryptoDataSourceTest {
val keys = bcCryptoDataSource.getEncryptionKeys(
serverPublicKey.encoded,
localPrivateKey.encoded,
"test".toByteArray(),
"test2".toByteArray()
listOf(
"test".toByteArray(),
"test2".toByteArray(),
)
)
val kA = Base64.encodeToString(keys.first, Base64.NO_WRAP)
val kEA = Base64.encodeToString(keys.second, Base64.NO_WRAP)
val kA = Base64.encodeToString(keys[0], Base64.NO_WRAP)
val kEA = Base64.encodeToString(keys[1], Base64.NO_WRAP)
assertThat(kA).isEqualTo("mwuOwJO0qxPG7JuZibow6RzByIwDcvzEEx3jbW84t8k=")
assertThat(kEA).isEqualTo("Xl0TXEspgkuTBniuUEcNFPqQvoHM006/tpyyE4NRFyY=")
}
companion object {
private const val MOCK_SERVER_PUB_KEY: String = "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEIk7OAGcqyGpnmeTQiEDU0Uih9h3wMhwGmv6lqYuupR6I9aqLTBGSQvi6YIA+r7ZvxilaRBxzdxIuMXlTUTDxhw=="
private const val MOCK_LOCAL_KEY: String = "MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQg8Ss533Vz+z0GG/l2sxYBtA2vD0NR1WW3tgNRJ/uq67uhRANCAATkgJaihoP8jim8eAOfswWt9LcKE0iKKqc0ItWDmJrI6LxU+oa4qgI/CDEbRBQIAAYwvCCPLLNH8TJBCjf9kBfX"
private const val MOCK_SERVER_PUB_KEY: String =
"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEIk7OAGcqyGpnmeTQiEDU0Uih9h3wMhwGmv6lqYuupR6I9aqLTBGSQvi6YIA+r7ZvxilaRBxzdxIuMXlTUTDxhw=="
private const val MOCK_LOCAL_KEY: String =
"MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQg8Ss533Vz+z0GG/l2sxYBtA2vD0NR1WW3tgNRJ/uq67uhRANCAATkgJaihoP8jim8eAOfsw" +
"Wt9LcKE0iKKqc0ItWDmJrI6LxU+oa4qgI/CDEbRBQIAAYwvCCPLLNH8TJBCjf9kBfX"
}
}
/*
* 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/8/7 - for the TOUS-ANTI-COVID project
*/
/*
* 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/8/7 - for the TOUS-ANTI-COVID project
*/
package com.lunabeestudio.framework.remote
import android.util.Base64
import androidx.test.platform.app.InstrumentationRegistry
import com.google.gson.Gson
import com.lunabeestudio.domain.model.Configuration
import com.lunabeestudio.domain.model.WalletCertificateType
import com.lunabeestudio.framework.crypto.BouncyCastleCryptoDataSource
import com.lunabeestudio.framework.remote.datasource.InGroupeDatasource
import com.lunabeestudio.framework.remote.model.ApiConversionRQ
import com.lunabeestudio.framework.testutils.CryptoUtils
import com.lunabeestudio.robert.RobertManager
import com.lunabeestudio.robert.model.BackendException
import com.lunabeestudio.robert.model.RobertResultData
import io.mockk.every
import io.mockk.mockk
import kotlinx.coroutines.runBlocking
import okhttp3.mockwebserver.Dispatcher
import okhttp3.mockwebserver.MockResponse
import okhttp3.mockwebserver.MockWebServer
import okhttp3.mockwebserver.RecordedRequest
import org.junit.Before
import org.junit.Test
import timber.log.Timber
import java.security.KeyPair
class InGroupeDatasourceTest {
private val server = MockWebServer()
private val serverKeyPair: KeyPair = CryptoUtils.generateEcdhKeyPair()
private lateinit var robertManager: RobertManager
private lateinit var configuration: Configuration
@Before
fun init() {
robertManager = mockk(relaxed = true)
configuration = mockk(relaxed = true)
every { robertManager.configuration } returns configuration
server.dispatcher = object : Dispatcher() {
override fun dispatch(request: RecordedRequest): MockResponse {
return if (request.path?.startsWith(CONVERSION_PATH) == true) {
try {
val apiConversionRq = Gson().fromJson(request.body.readString(Charsets.UTF_8), ApiConversionRQ::class.java)
val sharedKey = CryptoUtils.generateKey(
serverKeyPair.private,
request.requestUrl!!.queryParameter("publicKey")!!,
)
val clearInput = CryptoUtils.decodeDecrypt(sharedKey, apiConversionRq.chainEncoded)
val encryptedOutput = CryptoUtils.encryptEncode(sharedKey, clearInput.toOutput())
MockResponse()
.setResponseCode(200)
.setBody(encryptedOutput)
} catch (e: Exception) {
val errorBody = "{ \"codeError\":\"$ERROR_CODE\", \"msgError\":\"${e.localizedMessage}\" }"
MockResponse()
.setResponseCode(400)
.setBody(errorBody)
}
} else {
MockResponse()
.setResponseCode(404)
}
}
}
server.start()
}
@Test
fun convert_certificate_success() {
val inputString = "Is NSA watching?"
val expectedOutput = inputString.toOutput()
val inGroupeDatasource = InGroupeDatasource(
InstrumentationRegistry.getInstrumentation().context,
BouncyCastleCryptoDataSource(),
robertManager,
server.url("/").toString(),
)
every { configuration.conversionPublicKey } returns hashMapOf(
Pair("good_key", Base64.encodeToString(serverKeyPair.public.encoded, Base64.NO_WRAP))
)
val convertResult = runBlocking {
inGroupeDatasource.convertCertificateV2(
inputString,
WalletCertificateType.Format.WALLET_2D,
WalletCertificateType.Format.WALLET_DCC,
)
}
assert(convertResult is RobertResultData.Success)
assert((convertResult as RobertResultData.Success).data == expectedOutput) {
Timber.e(convertResult.data)
}
}
@Test
fun convert_certificate_bad_key() {
val inputString = "Is NSA watching?"
val badServerKey = CryptoUtils.generateEcdhKeyPair().public.encoded
every { configuration.conversionPublicKey } returns hashMapOf(
Pair("bad_key", Base64.encodeToString(badServerKey, Base64.NO_WRAP))
)
val inGroupeDatasource = InGroupeDatasource(
InstrumentationRegistry.getInstrumentation().context,
BouncyCastleCryptoDataSource(),
robertManager,
server.url("/").toString(),
)
val convertResult = runBlocking {
inGroupeDatasource.convertCertificateV2(
inputString,
WalletCertificateType.Format.WALLET_2D,
WalletCertificateType.Format.WALLET_DCC,
)
}
assert(convertResult is RobertResultData.Failure)
assert(((convertResult as RobertResultData.Failure).error as BackendException).message.endsWith("($ERROR_CODE)"))
}
private fun String.toOutput(): String = "$this Yes."
companion object {
private const val CONVERSION_PATH: String = "/api/v2/client/convertor/decode/decodeDocument"
private const val ERROR_CODE: String = "SERVER_ERROR"
}
}
\ No newline at end of file
/*
* 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/9/7 - for the TOUS-ANTI-COVID project
*/
package com.lunabeestudio.framework.testutils
import android.os.Build
import android.util.Base64
import org.bouncycastle.jce.ECNamedCurveTable
import org.bouncycastle.jce.provider.BouncyCastleProvider
import org.bouncycastle.jce.spec.ECParameterSpec
import java.io.ByteArrayOutputStream
import java.security.KeyFactory
import java.security.KeyPair
import java.security.KeyPairGenerator
import java.security.PrivateKey
import java.security.SecureRandom
import java.security.spec.X509EncodedKeySpec
import javax.crypto.Cipher
import javax.crypto.CipherOutputStream
import javax.crypto.KeyAgreement
import javax.crypto.Mac
import javax.crypto.spec.GCMParameterSpec
import javax.crypto.spec.IvParameterSpec
import javax.crypto.spec.SecretKeySpec
object CryptoUtils {
private const val HASH_HMACSHA256 = "HmacSHA256"
private const val NAMED_CURVE_SPEC = "secp256r1"
private const val ALGORITHM_ECDH = "ECDH"
private const val ALGORITHM_AES = "AES"
private const val AES_GCM_CIPHER_TYPE = "AES/GCM/NoPadding"
private const val AES_GCM_IV_LENGTH = 12
private const val AES_GCM_TAG_LENGTH_IN_BITS = 128
private const val CONVERSION_STRING_INPUT: String = "conversion"
internal fun generateKey(privateKey: PrivateKey, encodedPublicKey: String): ByteArray {
val bouncyCastleProvider = BouncyCastleProvider()
val keyFactory = KeyFactory.getInstance(ALGORITHM_ECDH, bouncyCastleProvider)
val publicKey = keyFactory.generatePublic(X509EncodedKeySpec(Base64.decode(encodedPublicKey, Base64.NO_WRAP)))
val keyAgreement = KeyAgreement.getInstance(ALGORITHM_ECDH)
keyAgreement.init(privateKey)
keyAgreement.doPhase(publicKey, true)
val sharedSecret = keyAgreement.generateSecret()
val secretKeySpec = SecretKeySpec(sharedSecret, HASH_HMACSHA256)
val hmac = Mac.getInstance(HASH_HMACSHA256)
hmac.init(secretKeySpec)
return hmac.doFinal(CONVERSION_STRING_INPUT.toByteArray(Charsets.UTF_8))
}
internal fun decodeDecrypt(key: ByteArray, encodedEncryptedInput: String): String {
val rawEncryptedCertificate = Base64.decode(encodedEncryptedInput, Base64.NO_WRAP)
val cipher = Cipher.getInstance(AES_GCM_CIPHER_TYPE)
val iv: ByteArray = rawEncryptedCertificate.copyOfRange(0, AES_GCM_IV_LENGTH)
val ivSpec = GCMParameterSpec(AES_GCM_TAG_LENGTH_IN_BITS, iv)
val secretKey = SecretKeySpec(key, ALGORITHM_AES)
cipher.init(Cipher.DECRYPT_MODE, secretKey, ivSpec)
val rawDecryptedCertificate = cipher.doFinal(
rawEncryptedCertificate,
AES_GCM_IV_LENGTH,
rawEncryptedCertificate.size - AES_GCM_IV_LENGTH
)
return rawDecryptedCertificate.toString(Charsets.UTF_8)
}
internal fun encryptEncode(key: ByteArray, clearInput: String): String {
val secretKey = SecretKeySpec(key, HASH_HMACSHA256)
val bos = ByteArrayOutputStream()
val cipher = Cipher.getInstance(AES_GCM_CIPHER_TYPE)
val iv: ByteArray = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
cipher.init(Cipher.ENCRYPT_MODE, secretKey)
cipher.iv
} else {
val iv = ByteArray(AES_GCM_IV_LENGTH)
SecureRandom().nextBytes(iv)
cipher.init(Cipher.ENCRYPT_MODE, secretKey, IvParameterSpec(iv))
iv
}
bos.write(iv)
CipherOutputStream(bos, cipher).use { cos ->
clearInput
.byteInputStream(Charsets.UTF_8)
.use { input ->
input.copyTo(cos)
}
}
val encryptedData = bos.toByteArray()
return Base64.encodeToString(encryptedData, Base64.NO_WRAP)
}
internal fun generateEcdhKeyPair(): KeyPair {
val ecSpec: ECParameterSpec = ECNamedCurveTable.getParameterSpec(NAMED_CURVE_SPEC)
val bouncyCastleProvider = BouncyCastleProvider()
val keyPairGenerator = KeyPairGenerator.getInstance(ALGORITHM_ECDH, bouncyCastleProvider)
keyPairGenerator.initialize(ecSpec, SecureRandom())
return keyPairGenerator.genKeyPair()
}
}
\ No newline at end of file
......@@ -10,12 +10,15 @@
package com.lunabeestudio.framework.crypto
import android.os.Build
import com.lunabeestudio.domain.extension.safeUse
import com.lunabeestudio.framework.utils.SelfDestroyCipherOutputStream
import com.lunabeestudio.robert.datasource.SharedCryptoDataSource
import com.lunabeestudio.robert.extension.use
import org.bouncycastle.jce.ECNamedCurveTable
import org.bouncycastle.jce.provider.BouncyCastleProvider
import org.bouncycastle.jce.spec.ECParameterSpec
import java.io.ByteArrayOutputStream
import java.security.KeyFactory
import java.security.KeyPair
import java.security.KeyPairGenerator
......@@ -26,6 +29,7 @@ import javax.crypto.Cipher
import javax.crypto.KeyAgreement
import javax.crypto.Mac
import javax.crypto.spec.GCMParameterSpec
import javax.crypto.spec.IvParameterSpec
import javax.crypto.spec.SecretKeySpec
class BouncyCastleCryptoDataSource : SharedCryptoDataSource {
......@@ -40,9 +44,8 @@ class BouncyCastleCryptoDataSource : SharedCryptoDataSource {
override fun getEncryptionKeys(
rawServerPublicKey: ByteArray,
rawLocalPrivateKey: ByteArray,
kADerivation: ByteArray,
kEADerivation: ByteArray
): Pair<ByteArray, ByteArray> {
derivationDataArray: List<ByteArray>,
): List<ByteArray> {
val bouncyCastleProvider = BouncyCastleProvider()
val keyFactory = KeyFactory.getInstance(ALGORITHM_ECDH, bouncyCastleProvider)
val serverPublicKey = keyFactory.generatePublic(X509EncodedKeySpec(rawServerPublicKey))
......@@ -54,14 +57,12 @@ class BouncyCastleCryptoDataSource : SharedCryptoDataSource {
keyAgreement.doPhase(serverPublicKey, true)
return keyAgreement.generateSecret().use { sharedSecret ->
SecretKeySpec(sharedSecret, HASH_HMACSHA256).safeUse<Pair<ByteArray, ByteArray>> { secretKeySpec ->
SecretKeySpec(sharedSecret, HASH_HMACSHA256).safeUse { secretKeySpec ->
val hmac = Mac.getInstance(HASH_HMACSHA256)
hmac.init(secretKeySpec)
val kA = hmac.doFinal(kADerivation)
val kEA = hmac.doFinal(kEADerivation)
Pair(kA, kEA)
derivationDataArray.map {
hmac.doFinal(it)
}
}
}
}
......@@ -77,6 +78,32 @@ class BouncyCastleCryptoDataSource : SharedCryptoDataSource {
}
}
override fun encrypt(key: ByteArray, clearData: ByteArray): ByteArray {
val secretKey = SecretKeySpec(key, HASH_HMACSHA256)
val bos = ByteArrayOutputStream()
val cipher = Cipher.getInstance(AES_GCM_CIPHER_TYPE)
val iv: ByteArray = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
cipher.init(Cipher.ENCRYPT_MODE, secretKey)
cipher.iv
} else {
val iv = ByteArray(AES_GCM_IV_LENGTH)
SecureRandom().nextBytes(iv)
cipher.init(Cipher.ENCRYPT_MODE, secretKey, IvParameterSpec(iv))
iv
}
bos.write(iv)
SelfDestroyCipherOutputStream(bos, cipher, secretKey).use { cos ->
clearData.inputStream().use { input ->
input.copyTo(cos, BUFFER_SIZE)
}
}
return bos.toByteArray()
}
companion object {
private const val HASH_HMACSHA256 = "HmacSHA256"
private const val NAMED_CURVE_SPEC = "secp256r1"
......@@ -86,5 +113,7 @@ class BouncyCastleCryptoDataSource : SharedCryptoDataSource {
private const val AES_GCM_CIPHER_TYPE = "AES/GCM/NoPadding"
private const val AES_GCM_IV_LENGTH = 12
private const val AES_GCM_TAG_LENGTH_IN_BITS = 128
private const val BUFFER_SIZE = 4 * 256
}
}
\ No newline at end of file
......@@ -11,61 +11,57 @@
package com.lunabeestudio.framework.remote.datasource
import android.content.Context
import android.util.Base64
import com.google.gson.Gson
import com.google.gson.JsonSyntaxException
import com.lunabeestudio.analytics.manager.AnalyticsManager
import com.lunabeestudio.analytics.model.AnalyticsServiceName
import com.lunabeestudio.domain.model.WalletCertificateType
import com.lunabeestudio.framework.remote.RetrofitClient
import com.lunabeestudio.framework.remote.model.ApiConvertErrorRS
import com.lunabeestudio.framework.remote.model.ApiConvertRQ
import com.lunabeestudio.framework.remote.model.ApiConversionErrorRS
import com.lunabeestudio.framework.remote.model.ApiConversionRQ
import com.lunabeestudio.framework.remote.server.InGroupeApi
import com.lunabeestudio.robert.RobertConstant
import com.lunabeestudio.robert.RobertManager
import com.lunabeestudio.robert.datasource.RemoteCertificateDataSource
import com.lunabeestudio.robert.datasource.SharedCryptoDataSource
import com.lunabeestudio.robert.model.BackendException
import com.lunabeestudio.robert.model.RobertResultData
import com.lunabeestudio.robert.model.UnknownException