Request certificates aggregation
This commit is contained in:
parent
13ae15491f
commit
b2d9bd3edb
@ -56,6 +56,7 @@ dependencies {
|
|||||||
implementation 'com.github.eu-digital-green-certificates:dgca-app-core-android:e4ad73eef8'
|
implementation 'com.github.eu-digital-green-certificates:dgca-app-core-android:e4ad73eef8'
|
||||||
implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.13.1'
|
implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.13.1'
|
||||||
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.0'
|
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.0'
|
||||||
|
implementation 'org.bouncycastle:bcprov-jdk15to18:1.70'
|
||||||
|
|
||||||
implementation "com.squareup.retrofit2:retrofit:2.9.0"
|
implementation "com.squareup.retrofit2:retrofit:2.9.0"
|
||||||
implementation "com.squareup.okhttp3:okhttp-tls:4.9.3"
|
implementation "com.squareup.okhttp3:okhttp-tls:4.9.3"
|
||||||
|
@ -0,0 +1,43 @@
|
|||||||
|
/*
|
||||||
|
* 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 - 2020/13/05 - for the TOUS-ANTI-COVID project
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.lunabeestudio.domain.extension
|
||||||
|
|
||||||
|
import javax.crypto.SecretKey
|
||||||
|
import javax.security.auth.DestroyFailedException
|
||||||
|
|
||||||
|
fun SecretKey.safeDestroy() {
|
||||||
|
try {
|
||||||
|
destroy()
|
||||||
|
} catch (e: DestroyFailedException) {
|
||||||
|
// Destroy not implemented
|
||||||
|
} catch (e: NoSuchMethodError) {
|
||||||
|
// Destroy not implemented
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val SecretKey.safeIsDestroyed: Boolean?
|
||||||
|
get() = try {
|
||||||
|
isDestroyed
|
||||||
|
} catch (e: DestroyFailedException) {
|
||||||
|
// Destroy not implemented
|
||||||
|
null
|
||||||
|
} catch (e: NoSuchMethodError) {
|
||||||
|
// Destroy not implemented
|
||||||
|
null
|
||||||
|
}
|
||||||
|
|
||||||
|
fun <T> SecretKey.safeUse(block: (SecretKey) -> T): T {
|
||||||
|
try {
|
||||||
|
return block(this)
|
||||||
|
} finally {
|
||||||
|
safeDestroy()
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,122 @@
|
|||||||
|
/*
|
||||||
|
* 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 - 2020/20/05 - for the TOUS-ANTI-COVID project
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.lunabeestudio.framework.crypto
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
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
|
||||||
|
import java.security.SecureRandom
|
||||||
|
import java.security.spec.PKCS8EncodedKeySpec
|
||||||
|
import java.security.spec.X509EncodedKeySpec
|
||||||
|
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 {
|
||||||
|
override fun createECDHKeyPair(): KeyPair {
|
||||||
|
val ecSpec: ECParameterSpec = ECNamedCurveTable.getParameterSpec(NAMED_CURVE_SPEC)
|
||||||
|
val bouncyCastleProvider = BouncyCastleProvider()
|
||||||
|
val keyPairGenerator = KeyPairGenerator.getInstance(ALGORITHM_EC, bouncyCastleProvider)
|
||||||
|
keyPairGenerator.initialize(ecSpec, SecureRandom())
|
||||||
|
return keyPairGenerator.generateKeyPair()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getEncryptionKeys(
|
||||||
|
rawServerPublicKey: ByteArray,
|
||||||
|
rawLocalPrivateKey: ByteArray,
|
||||||
|
derivationDataArray: List<ByteArray>,
|
||||||
|
): List<ByteArray> {
|
||||||
|
val bouncyCastleProvider = BouncyCastleProvider()
|
||||||
|
val keyFactory = KeyFactory.getInstance(ALGORITHM_EC, bouncyCastleProvider)
|
||||||
|
val serverPublicKey = keyFactory.generatePublic(X509EncodedKeySpec(rawServerPublicKey))
|
||||||
|
val localPrivateKey = keyFactory.generatePrivate(PKCS8EncodedKeySpec(rawLocalPrivateKey))
|
||||||
|
|
||||||
|
val keyAgreement = KeyAgreement.getInstance(ALGORITHM_ECDH)
|
||||||
|
|
||||||
|
keyAgreement.init(localPrivateKey)
|
||||||
|
keyAgreement.doPhase(serverPublicKey, true)
|
||||||
|
|
||||||
|
return keyAgreement.generateSecret().use { sharedSecret ->
|
||||||
|
SecretKeySpec(sharedSecret, HASH_HMACSHA256).safeUse { secretKeySpec ->
|
||||||
|
val hmac = Mac.getInstance(HASH_HMACSHA256)
|
||||||
|
hmac.init(secretKeySpec)
|
||||||
|
derivationDataArray.map {
|
||||||
|
hmac.doFinal(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun decrypt(key: ByteArray, encryptedData: ByteArray): ByteArray {
|
||||||
|
val cipher = Cipher.getInstance(AES_GCM_CIPHER_TYPE)
|
||||||
|
val iv: ByteArray = encryptedData.copyOfRange(0, AES_GCM_IV_LENGTH)
|
||||||
|
val ivSpec = GCMParameterSpec(AES_GCM_TAG_LENGTH_IN_BITS, iv)
|
||||||
|
|
||||||
|
return SecretKeySpec(key, ALGORITHM_AES).safeUse { secretKey ->
|
||||||
|
cipher.init(Cipher.DECRYPT_MODE, secretKey, ivSpec)
|
||||||
|
cipher.doFinal(encryptedData, AES_GCM_IV_LENGTH, encryptedData.size - AES_GCM_IV_LENGTH)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("ObsoleteSdkInt")
|
||||||
|
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"
|
||||||
|
private const val ALGORITHM_EC = "EC"
|
||||||
|
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 BUFFER_SIZE = 4 * 256
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,69 @@
|
|||||||
|
/*
|
||||||
|
* 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/22/9 - for the TOUS-ANTI-COVID project
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.lunabeestudio.framework.crypto
|
||||||
|
|
||||||
|
import android.util.Base64
|
||||||
|
import com.lunabeestudio.robert.RobertConstant
|
||||||
|
import com.lunabeestudio.robert.datasource.SharedCryptoDataSource
|
||||||
|
import com.lunabeestudio.robert.extension.use
|
||||||
|
|
||||||
|
internal class ServerKeyAgreementHelper(
|
||||||
|
private val sharedCryptoDataSource: SharedCryptoDataSource,
|
||||||
|
) {
|
||||||
|
|
||||||
|
private var localPrivateKey: ByteArray? = null
|
||||||
|
|
||||||
|
fun encryptRequestData(
|
||||||
|
serverPublicKey: String,
|
||||||
|
encodedDataList: List<String>,
|
||||||
|
): ServerKeyAgreementData {
|
||||||
|
val rawServerKey = Base64.decode(serverPublicKey, Base64.NO_WRAP)
|
||||||
|
|
||||||
|
val keyPair = sharedCryptoDataSource.createECDHKeyPair()
|
||||||
|
|
||||||
|
val encodedLocalPublicKey = Base64.encodeToString(keyPair.public.encoded, Base64.NO_WRAP)
|
||||||
|
val serverKeyAgreementData = sharedCryptoDataSource.getEncryptionKeys(
|
||||||
|
rawServerPublicKey = rawServerKey,
|
||||||
|
rawLocalPrivateKey = keyPair.private.encoded,
|
||||||
|
derivationDataArray = listOf(
|
||||||
|
RobertConstant.CONVERSION_STRING_INPUT.toByteArray(Charsets.UTF_8),
|
||||||
|
)
|
||||||
|
).first().let {
|
||||||
|
localPrivateKey = it
|
||||||
|
val encryptedDataList = encodedDataList.map { encodedData ->
|
||||||
|
sharedCryptoDataSource.encrypt(it, encodedData.toByteArray(Charsets.UTF_8))
|
||||||
|
}
|
||||||
|
val encodedEncryptedData = encryptedDataList.map { encryptedData ->
|
||||||
|
Base64.encodeToString(encryptedData, Base64.NO_WRAP)
|
||||||
|
}
|
||||||
|
ServerKeyAgreementData(encodedLocalPublicKey, encodedEncryptedData)
|
||||||
|
}
|
||||||
|
|
||||||
|
return serverKeyAgreementData
|
||||||
|
}
|
||||||
|
|
||||||
|
@Throws(IllegalStateException::class)
|
||||||
|
fun decryptResponse(encryptedData: String): String {
|
||||||
|
val data = Base64.decode(encryptedData, Base64.NO_WRAP)
|
||||||
|
return try {
|
||||||
|
(localPrivateKey ?: throw IllegalStateException("localPrivateKey is null. getKeyAgreementData must be call first")).use {
|
||||||
|
sharedCryptoDataSource.decrypt(it, data).toString(Charsets.UTF_8)
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
localPrivateKey = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ServerKeyAgreementData(
|
||||||
|
val encodedLocalPublicKey: String,
|
||||||
|
val encryptedData: List<String>,
|
||||||
|
)
|
||||||
|
}
|
@ -0,0 +1,39 @@
|
|||||||
|
/*
|
||||||
|
* 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 - 2020/20/05 - for the TOUS-ANTI-COVID project
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.lunabeestudio.framework.utils
|
||||||
|
|
||||||
|
import com.lunabeestudio.domain.extension.safeDestroy
|
||||||
|
import java.io.InputStream
|
||||||
|
import java.io.OutputStream
|
||||||
|
import javax.crypto.Cipher
|
||||||
|
import javax.crypto.CipherInputStream
|
||||||
|
import javax.crypto.CipherOutputStream
|
||||||
|
import javax.crypto.SecretKey
|
||||||
|
|
||||||
|
class SelfDestroyCipherInputStream(inputStream: InputStream, cipher: Cipher, private val key: SecretKey) : CipherInputStream(
|
||||||
|
inputStream,
|
||||||
|
cipher
|
||||||
|
) {
|
||||||
|
override fun close() {
|
||||||
|
super.close()
|
||||||
|
key.safeDestroy()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class SelfDestroyCipherOutputStream(outputStream: OutputStream, cipher: Cipher, private val key: SecretKey) : CipherOutputStream(
|
||||||
|
outputStream,
|
||||||
|
cipher
|
||||||
|
) {
|
||||||
|
override fun close() {
|
||||||
|
super.close()
|
||||||
|
key.safeDestroy()
|
||||||
|
}
|
||||||
|
}
|
29
app/src/main/java/com/lunabeestudio/robert/RobertConstant.kt
Normal file
29
app/src/main/java/com/lunabeestudio/robert/RobertConstant.kt
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
/*
|
||||||
|
* 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 - 2020/04/05 - for the TOUS-ANTI-COVID project
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.lunabeestudio.robert
|
||||||
|
|
||||||
|
|
||||||
|
object RobertConstant {
|
||||||
|
const val STATUS_WORKER_NAME: String = "RobertManager.Status.Worker"
|
||||||
|
const val EPOCH_DURATION_S: Int = 15 * 60
|
||||||
|
const val KA_STRING_INPUT: String = "mac"
|
||||||
|
const val KEA_STRING_INPUT: String = "tuples"
|
||||||
|
const val DEFAULT_CALIBRATION_KEY: String = "DEFAULT"
|
||||||
|
const val LAST_CONTACT_DELTA_S: Long = 24 * 60 * 60
|
||||||
|
const val CONVERSION_STRING_INPUT: String = "conversion"
|
||||||
|
|
||||||
|
object PREFIX {
|
||||||
|
const val C1: Byte = 0b00000001
|
||||||
|
const val C2: Byte = 0b00000010
|
||||||
|
const val C3: Byte = 0b00000011
|
||||||
|
const val C4: Byte = 0b00000100
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,25 @@
|
|||||||
|
/*
|
||||||
|
* 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 - 2020/20/05 - for the TOUS-ANTI-COVID project
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.lunabeestudio.robert.datasource
|
||||||
|
|
||||||
|
import java.security.KeyPair
|
||||||
|
|
||||||
|
interface SharedCryptoDataSource {
|
||||||
|
fun createECDHKeyPair(): KeyPair
|
||||||
|
fun getEncryptionKeys(
|
||||||
|
rawServerPublicKey: ByteArray,
|
||||||
|
rawLocalPrivateKey: ByteArray,
|
||||||
|
derivationDataArray: List<ByteArray>
|
||||||
|
): List<ByteArray>
|
||||||
|
|
||||||
|
fun decrypt(key: ByteArray, encryptedData: ByteArray): ByteArray
|
||||||
|
fun encrypt(key: ByteArray, clearData: ByteArray): ByteArray
|
||||||
|
}
|
@ -0,0 +1,27 @@
|
|||||||
|
/*
|
||||||
|
* 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 - 2020/04/05 - for the TOUS-ANTI-COVID project
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.lunabeestudio.robert.extension
|
||||||
|
|
||||||
|
import java.security.SecureRandom
|
||||||
|
|
||||||
|
fun ByteArray.randomize() {
|
||||||
|
for (i in 0 until size) {
|
||||||
|
set(i, SecureRandom().nextInt().toByte())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun <T> ByteArray.use(block: (ByteArray) -> T): T {
|
||||||
|
return try {
|
||||||
|
block(this)
|
||||||
|
} finally {
|
||||||
|
this.randomize()
|
||||||
|
}
|
||||||
|
}
|
@ -15,3 +15,6 @@ const val TEST_ANTIGENIC = "LP217198-3"
|
|||||||
|
|
||||||
// TousAntiCovid resources
|
// TousAntiCovid resources
|
||||||
const val TAC_RES_BASE_URL = "https://app.tousanticovid.gouv.fr/json/version-38/"
|
const val TAC_RES_BASE_URL = "https://app.tousanticovid.gouv.fr/json/version-38/"
|
||||||
|
|
||||||
|
// Dcc light generation server
|
||||||
|
const val DCC_LIGHT_BASE_URL: String = "https://dcclight.tousanticovid.gouv.fr/"
|
@ -0,0 +1,93 @@
|
|||||||
|
package org.communiquons.dccaggregator.apis
|
||||||
|
|
||||||
|
import com.google.gson.Gson
|
||||||
|
import com.lunabeestudio.framework.crypto.BouncyCastleCryptoDataSource
|
||||||
|
import com.lunabeestudio.framework.crypto.ServerKeyAgreementHelper
|
||||||
|
import org.communiquons.dccaggregator.DCC_LIGHT_BASE_URL
|
||||||
|
import org.communiquons.dccaggregator.data.Certificate
|
||||||
|
import retrofit2.Response
|
||||||
|
import retrofit2.http.Body
|
||||||
|
import retrofit2.http.POST
|
||||||
|
|
||||||
|
internal class ApiAggregateRQ(
|
||||||
|
val publicKey: String,
|
||||||
|
val certificates: List<String>,
|
||||||
|
)
|
||||||
|
|
||||||
|
internal class ApiAggregateRS(
|
||||||
|
val response: String,
|
||||||
|
)
|
||||||
|
|
||||||
|
internal class ApiAggregateCertificate(
|
||||||
|
val certificate: String,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
internal interface DccLightApi {
|
||||||
|
@POST("/api/v1/aggregate")
|
||||||
|
suspend fun aggregate(
|
||||||
|
@Body apiAggregateRQ: ApiAggregateRQ,
|
||||||
|
): Response<ApiAggregateRS>
|
||||||
|
}
|
||||||
|
|
||||||
|
sealed class AggregateResult {
|
||||||
|
class Error(val error: String, val throwable: Throwable?) : AggregateResult()
|
||||||
|
class Success(val cert: Certificate) : AggregateResult()
|
||||||
|
}
|
||||||
|
|
||||||
|
class DccLightAPIClient {
|
||||||
|
private val api by lazy {
|
||||||
|
getRetrofitService(DCC_LIGHT_BASE_URL, DccLightApi::class.java)
|
||||||
|
}
|
||||||
|
|
||||||
|
private val serverKeyAgreementHelper by lazy {
|
||||||
|
ServerKeyAgreementHelper(BouncyCastleCryptoDataSource())
|
||||||
|
}
|
||||||
|
|
||||||
|
private val gson = Gson()
|
||||||
|
|
||||||
|
suspend fun aggregateCertificates(
|
||||||
|
serverPubKey: String,
|
||||||
|
certs: List<Certificate>
|
||||||
|
): AggregateResult {
|
||||||
|
// Encrypt request
|
||||||
|
val serverKeyAgreementData = try {
|
||||||
|
serverKeyAgreementHelper.encryptRequestData(serverPubKey, certs.map { c -> c.encoded })
|
||||||
|
} catch (e: Exception) {
|
||||||
|
return AggregateResult.Error(e.message ?: "Failed to cipher request", e)
|
||||||
|
}
|
||||||
|
|
||||||
|
val apiAggregateRQ = ApiAggregateRQ(
|
||||||
|
serverKeyAgreementData.encodedLocalPublicKey,
|
||||||
|
serverKeyAgreementData.encryptedData,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Suppress("BlockingMethodInNonBlockingContext")
|
||||||
|
return try {
|
||||||
|
val response = api.aggregate(apiAggregateRQ)
|
||||||
|
if (response.isSuccessful) {
|
||||||
|
response.body()?.let {
|
||||||
|
val multipassJson = serverKeyAgreementHelper.decryptResponse(it.response)
|
||||||
|
val multipass = gson.fromJson(
|
||||||
|
multipassJson,
|
||||||
|
ApiAggregateCertificate::class.java
|
||||||
|
).certificate
|
||||||
|
AggregateResult.Success(Certificate(multipass))
|
||||||
|
} ?: AggregateResult.Error(
|
||||||
|
"Response successful (${response.code()}) but body is empty",
|
||||||
|
null
|
||||||
|
)
|
||||||
|
|
||||||
|
} else {
|
||||||
|
val error = response.errorBody()?.string()
|
||||||
|
if (error != null) {
|
||||||
|
AggregateResult.Error("Aggregate request failed! $error", null)
|
||||||
|
} else {
|
||||||
|
AggregateResult.Error("Request failed with status ${response.code()}", null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
return AggregateResult.Error("Request failed", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -9,10 +9,13 @@ import kotlinx.coroutines.Dispatchers
|
|||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
|
import org.communiquons.dccaggregator.apis.AggregateResult
|
||||||
|
import org.communiquons.dccaggregator.apis.DccLightAPIClient
|
||||||
import org.communiquons.dccaggregator.apis.TousAntiCovidAPIClient
|
import org.communiquons.dccaggregator.apis.TousAntiCovidAPIClient
|
||||||
import org.communiquons.dccaggregator.helper.CertificatesList
|
import org.communiquons.dccaggregator.helper.CertificatesList
|
||||||
|
|
||||||
class CertsManagerViewModel() : ViewModel() {
|
class CertsManagerViewModel() : ViewModel() {
|
||||||
|
private val TAG = CertsManagerViewModel::class.java.canonicalName
|
||||||
|
|
||||||
lateinit var certsManager: CertificatesList
|
lateinit var certsManager: CertificatesList
|
||||||
|
|
||||||
@ -27,7 +30,17 @@ class CertsManagerViewModel() : ViewModel() {
|
|||||||
val config = TousAntiCovidAPIClient().getConfig()
|
val config = TousAntiCovidAPIClient().getConfig()
|
||||||
Log.d("Server Primary Key", config.activityPassGenerationServerPublicKey)
|
Log.d("Server Primary Key", config.activityPassGenerationServerPublicKey)
|
||||||
|
|
||||||
throw java.lang.Exception("TODO: send request")
|
// Then aggregate certificates
|
||||||
|
val res = DccLightAPIClient().aggregateCertificates(
|
||||||
|
config.activityPassGenerationServerPublicKey,
|
||||||
|
certsManager.certsList
|
||||||
|
)
|
||||||
|
|
||||||
|
if (res is AggregateResult.Success) {
|
||||||
|
Log.d(TAG, res.cert.encoded)
|
||||||
|
} else if (res is AggregateResult.Error) {
|
||||||
|
throw Exception(res.error)
|
||||||
|
}
|
||||||
|
|
||||||
withContext(Dispatchers.Main) {
|
withContext(Dispatchers.Main) {
|
||||||
_reqStatus.value = null
|
_reqStatus.value = null
|
||||||
|
Reference in New Issue
Block a user