Get server public key

This commit is contained in:
Pierre HUBERT 2022-02-16 19:01:50 +01:00
parent 8c2153a8e7
commit dacf7fbc18
18 changed files with 290 additions and 6 deletions

View File

@ -12,7 +12,9 @@
<entry key="app/src/main/res/layout/content_certs.xml" value="0.29483695652173914" />
<entry key="app/src/main/res/layout/fragment_first.xml" value="0.29483695652173914" />
<entry key="app/src/main/res/layout/fragment_second.xml" value="0.29483695652173914" />
<entry key="app/src/main/res/layout/generating_dialog.xml" value="0.20729166666666668" />
<entry key="app/src/main/res/layout/qr_entry.xml" value="0.28125" />
<entry key="app/src/main/res/menu/cert_manager_top_menu.xml" value="0.20729166666666668" />
<entry key="app/src/main/res/menu/cert_menu.xml" value="0.3697916666666667" />
<entry key="app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml" value="0.3859375" />
</map>

View File

@ -55,6 +55,14 @@ dependencies {
implementation 'com.journeyapps:zxing-android-embedded:4.3.0'
implementation 'com.github.eu-digital-green-certificates:dgca-app-core-android:e4ad73eef8'
implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.13.1'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.0'
implementation "com.squareup.retrofit2:retrofit:2.9.0"
implementation "com.squareup.okhttp3:okhttp-tls:4.9.3"
implementation "com.google.code.gson:gson:2.9.0"
implementation "com.squareup.retrofit2:converter-gson:2.9.0"
testImplementation 'junit:junit:4.+'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'

View File

@ -3,6 +3,8 @@
xmlns:tools="http://schemas.android.com/tools"
package="org.communiquons.dccaggregator">
<uses-permission android:name="android.permission.INTERNET" />
<application
android:name=".DCCAggregator"
android:allowBackup="false"

View File

@ -10,4 +10,8 @@ const val RESULT_TEST_NEGATIVE = "260415000"
const val RESULT_TEST_POSITIVE = "260373001"
const val TEST_PCR = "LP6464-4"
const val TEST_ANTIGENIC = "LP217198-3"
const val TEST_ANTIGENIC = "LP217198-3"
// TousAntiCovid resources
const val TAC_RES_BASE_URL = "https://app.tousanticovid.gouv.fr/json/version-38/"

View File

@ -1,8 +1,23 @@
package org.communiquons.dccaggregator.activities
import android.os.Bundle
import android.os.PersistableBundle
import androidx.appcompat.app.AppCompatActivity
import org.communiquons.dccaggregator.DCCAggregator
import org.communiquons.dccaggregator.apis.initOkHTTP
abstract class BaseActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
initOkHTTP(this)
}
override fun onCreate(savedInstanceState: Bundle?, persistentState: PersistableBundle?) {
super.onCreate(savedInstanceState, persistentState)
initOkHTTP(this)
}
val certsManager get() = (application as DCCAggregator).certManager
}

View File

@ -3,17 +3,20 @@ package org.communiquons.dccaggregator.activities
import android.app.AlertDialog
import android.os.Bundle
import android.view.ContextMenu
import android.view.Menu
import android.view.MenuItem
import android.view.View
import android.widget.AdapterView
import android.widget.Toast
import androidx.lifecycle.ViewModelProvider
import com.journeyapps.barcodescanner.ScanContract
import com.journeyapps.barcodescanner.ScanOptions
import org.communiquons.dccaggregator.R
import org.communiquons.dccaggregator.WALLET_URL
import org.communiquons.dccaggregator.adapters.CertificatesAdapter
import org.communiquons.dccaggregator.data.Certificate
import org.communiquons.dccaggregator.databinding.ActivityCertsManagerBinding
import org.communiquons.dccaggregator.models.Certificate
import org.communiquons.dccaggregator.viewmodel.CertsManagerViewModel
import java.net.URLDecoder
import java.nio.charset.StandardCharsets
@ -29,10 +32,17 @@ class CertsManager : BaseActivity() {
CertificatesAdapter(this, certsList)
}
private val viewModel by lazy {
val viewModel = ViewModelProvider(this).get(CertsManagerViewModel::class.java)
viewModel.certsManager = this.certsManager
viewModel
}
private val TAG = CertsManager::class.java.canonicalName
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityCertsManagerBinding.inflate(layoutInflater)
setContentView(binding.root)
@ -54,12 +64,22 @@ class CertsManager : BaseActivity() {
refreshList()
}
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
menuInflater.inflate(R.menu.cert_manager_top_menu, menu)
return true
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
if (item.itemId == android.R.id.home) {
finish()
return true
}
if (item.itemId == R.id.generate_certificate) {
generateNewCertificate()
return true
}
return super.onOptionsItemSelected(item)
}
@ -111,7 +131,7 @@ class CertsManager : BaseActivity() {
) {
super.onCreateContextMenu(menu, v, menuInfo)
if (v == binding.certsList)
menuInflater.inflate(R.menu.cert_menu, menu)
menuInflater.inflate(R.menu.certificate_menu, menu)
}
override fun onContextItemSelected(item: MenuItem): Boolean {
@ -138,4 +158,20 @@ class CertsManager : BaseActivity() {
show()
}
}
private fun generateNewCertificate() {
val dialog = AlertDialog.Builder(this)
.setView(R.layout.generating_dialog)
.show().apply {
setCancelable(false)
setCanceledOnTouchOutside(false)
}
// TODO: hum hum
viewModel.requestCertificate()
// Close dialog
dialog.cancel()
}
}

View File

@ -10,7 +10,7 @@ import com.journeyapps.barcodescanner.BarcodeEncoder
import org.communiquons.dccaggregator.R
import org.communiquons.dccaggregator.databinding.QrEntryBinding
import org.communiquons.dccaggregator.ext.toDimensSize
import org.communiquons.dccaggregator.models.Certificate
import org.communiquons.dccaggregator.data.Certificate
class CertificatesAdapter(cxt: Context, list: ArrayList<Certificate>) :
ArrayAdapter<Certificate>(cxt, 0, list) {

View File

@ -0,0 +1,55 @@
package org.communiquons.dccaggregator.apis
import android.content.Context
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.OkHttpClient
import okhttp3.tls.HandshakeCertificates
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
import java.security.cert.CertificateFactory
import java.security.cert.X509Certificate
import java.util.concurrent.TimeUnit
@Suppress("SameParameterValue")
private fun certificateFromString(context: Context, fileName: String): X509Certificate {
return CertificateFactory.getInstance("X.509").generateCertificate(
context.resources.openRawResource(
context.resources.getIdentifier(
fileName,
"raw", context.packageName
)
)
) as X509Certificate
}
private var defaultOkHTTPClient: OkHttpClient? = null
fun initOkHTTP(context: Context) {
if(defaultOkHTTPClient == null)
defaultOkHTTPClient = getDefaultOKHttpClient(context)
}
fun getDefaultOKHttpClient(context: Context): OkHttpClient {
return OkHttpClient.Builder().apply {
val certificates: HandshakeCertificates = HandshakeCertificates.Builder()
.addPlatformTrustedCertificates()
.addTrustedCertificate(certificateFromString(context, "internal_july_2017"))
.build()
sslSocketFactory(certificates.sslSocketFactory(), certificates.trustManager)
callTimeout(1L, TimeUnit.MINUTES)
connectTimeout(1L, TimeUnit.MINUTES)
readTimeout(1L, TimeUnit.MINUTES)
writeTimeout(1L, TimeUnit.MINUTES)
}.build()
}
fun <T> getRetrofitService(
baseUrl: String,
clazz: Class<T>,
): T {
return Retrofit.Builder()
.baseUrl(baseUrl.toHttpUrl())
.addConverterFactory(GsonConverterFactory.create())
.client(defaultOkHTTPClient!!)
.build().create(clazz)
}

View File

@ -0,0 +1,41 @@
package org.communiquons.dccaggregator.apis
import android.content.Context
import org.communiquons.dccaggregator.TAC_RES_BASE_URL
import retrofit2.Response
import retrofit2.http.GET
class ConfigurationEntry(
val name: String,
val value: Any,
)
class ConfigurationWrapper(val config: List<ConfigurationEntry>)
class ParsedConfig(val activityPassGenerationServerPublicKey: String)
interface TousAntiCovidAPI {
@GET("Config/config.json")
suspend fun config(): Response<ConfigurationWrapper>
}
class TousAntiCovidAPIClient {
private val tacAPI by lazy {
getRetrofitService(
TAC_RES_BASE_URL,
TousAntiCovidAPI::class.java
)
}
suspend fun getConfig(): ParsedConfig {
val res = tacAPI.config()
if (!res.isSuccessful)
throw Exception("Request failed with status ${res.code()}\n${res.errorBody()}")
return ParsedConfig(
activityPassGenerationServerPublicKey = res.body()!!.config
.find { it -> it.name == "app.activityPass.generationServerPublicKey" }!!.value as String
)
}
}

View File

@ -1,4 +1,4 @@
package org.communiquons.dccaggregator.models
package org.communiquons.dccaggregator.data
import android.content.Context
import dgca.verifier.app.decoder.CertificateDecodingResult

View File

@ -3,7 +3,7 @@ package org.communiquons.dccaggregator.helper
import android.content.Context
import androidx.preference.PreferenceManager
import org.communiquons.dccaggregator.PREF_CERTS_LIST
import org.communiquons.dccaggregator.models.Certificate
import org.communiquons.dccaggregator.data.Certificate
class CertificatesList(context: Context) {
private val prefsManager = PreferenceManager.getDefaultSharedPreferences(context)

View File

@ -0,0 +1,35 @@
package org.communiquons.dccaggregator.viewmodel
import android.util.Log
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.communiquons.dccaggregator.apis.TousAntiCovidAPIClient
import org.communiquons.dccaggregator.helper.CertificatesList
import java.lang.Exception
class CertsManagerViewModel() : ViewModel() {
lateinit var certsManager: CertificatesList
fun requestCertificate(): Job {
return viewModelScope.launch(Dispatchers.IO) {
try {
return@launch withContext(Dispatchers.IO) {
// First, fetch current signing key
val config = TousAntiCovidAPIClient().getConfig()
Log.d("testpk", config.activityPassGenerationServerPublicKey)
}
} catch (e: Exception) {
e.printStackTrace()
// return false
}
// return true
}
}
}

View File

@ -0,0 +1,5 @@
<vector android:height="24dp" android:tint="#FFFFFF"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M9,16.17L4.83,12l-1.42,1.41L9,19 21,7l-1.41,-1.41z"/>
</vector>

View File

@ -0,0 +1,29 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="32dp"
android:layout_marginBottom="32dp"
android:text="@string/generating_certificate"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<ProgressBar
android:id="@+id/progressBar"
style="?android:attr/progressBarStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="32dp"
android:layout_marginBottom="32dp"
app:layout_constraintBottom_toTopOf="@+id/textView"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/generate_certificate"
android:icon="@drawable/ic_check"
android:title="@string/generate_certificate"
app:showAsAction="always" />
</menu>

View File

@ -0,0 +1,41 @@
-----BEGIN CERTIFICATE-----
MIIHJzCCBI+gAwIBAgIJAN1HrXxMdGLnMA0GCSqGSIb3DQEBDQUAMIGoMQswCQYD
VQQGEwJGUjEOMAwGA1UECAwFTG9pcmUxFjAUBgNVBAcMDVNhaW50LUV0aWVubmUx
GjAYBgNVBAoMEUludGVybmFsIFNlcnZpY2VzMREwDwYDVQQLDAhTZWN1cml0eTEZ
MBcGA1UEAwwQSU5URVJOQUwgUk9PVCBDQTEnMCUGCSqGSIb3DQEJARYYY29udGFj
dEBjb21tdW5pcXVvbnMub3JnMCAXDTE3MDcxNzA4MjMzMFoYDzIwNjcwNzA1MDgy
MzMwWjCBqDELMAkGA1UEBhMCRlIxDjAMBgNVBAgMBUxvaXJlMRYwFAYDVQQHDA1T
YWludC1FdGllbm5lMRowGAYDVQQKDBFJbnRlcm5hbCBTZXJ2aWNlczERMA8GA1UE
CwwIU2VjdXJpdHkxGTAXBgNVBAMMEElOVEVSTkFMIFJPT1QgQ0ExJzAlBgkqhkiG
9w0BCQEWGGNvbnRhY3RAY29tbXVuaXF1b25zLm9yZzCCAqIwDQYJKoZIhvcNAQEB
BQADggKPADCCAooCggKBAM01AqYT14dlRoCHzv34j7gJUjrz3rxgnmo64+dr+EK9
P13SUlhH5RWD0zl03o+BN7GmfeSmxxhdj05NX5ImBe1+ZM31K6JOjJv4zTP+8Jvr
kVkgHkA5brP3Kyy7geFKDnH6lRjfG+66Ms+ch6TzdZr6/f5Cd6WBNpF68iJQ9oN1
sjVEjIYyjL8+HclErGWHdRP28QQZ+gjeq+DoD2l4wijVg+7cdOaU2NzbBYEpXeQD
TnLvbyUZ1wrcSCqvH1ovGdVicN1pZWYsuRC0isD1JVKs15qtFVfJcw2sFd5PfykW
PzHy3FeXwGV9UcBMwFoqvD6iW4oE3Jk7lq6+dZFJgJ7hR7HwDYBbU5eSnbVEM6pV
N7IkDEfkE8JpYd1Y0VI35jFaSVAhm1/yMrEzIfiRNQPtnlQbc069Y4cYV47mfNPB
Ms6YifY1PJ++0yZPLyZXHRvYj+rbJzTfXTcD77IPtbBe5PnAlR+T4mO4VYa6fQlM
pRhaNF2cUjWrjR2QaeIf6oBg9Pgtr2Q1JTG4ugBa972kQLYQAV3Hv1Kkm+RzB8qx
P2ZFd7ZBKXqp1f5jVdfCOa46L4jTbW3x9pgRAwX4Rz8LrepKgEzGtJNazZk0hDoO
R+BSKTBQT1F+Tg44stDK8pUfdlVS9iVcai8r9MXbLkp8f2K9Hk6/vpuv+fcjB6oP
qWNrdMBdTJsXgfoHM7RnEE4IxKwg+Gx/laH/yQHW8Y8MlrxqmderyhupjhYHBlnd
+H0hQUSWk8WzeVhvVS/rcml/MEBSIo1PS0dLmlDgBmk1ZTTR14vc7eSbV/swyhC7
LTVE0zIOLxRY+i8W2SdpMYfP2O7CXf85V1mAtVViygECAwEAAaNQME4wHQYDVR0O
BBYEFAGTweo8SYVcz2esZRkp8UnDy9bxMB8GA1UdIwQYMBaAFAGTweo8SYVcz2es
ZRkp8UnDy9bxMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQENBQADggKBAH0A76f/
iY71J9RD8qO8yNiSlp75/DuPGcY4vfJ3LaJuhISew6uc5o7s/8nMIiF89Ol+vfVP
DFUr6XeSe21hb32mAWghaZFcVhxJxJNSD3ehF6CaLoyxgY81advaCl/LQWdfQh4U
BoiYhNJlUkH/pD3oUN4pnqaCu2la0aCuyLEHFIhcAfdmpPYdLEL22o7ckXvD+TIf
0ozkF1bXysRC7Ngz5QEfxQsykMwcwu+T8F1c4mGGFG17L5Q+2cvsUHFYegCkoQO3
jeyKG4vfRmFiqvMA8zWLXO1zns2dCSEHqGGkWdqbf1Gb07vImx7oz+Gmp9jU1l+j
l4520H9bBwAmGUk+op+Kr4EHunn8fzpkP6KhjFf3Eu0eszLSWJeJFJS8Qj7vT7Vo
steSVbrjbttdrkKJKfj4IU+4kiAADcwVAK2Sng77OtylE4QezvRjeQfmYmSQXgYi
6WWfwezTU6omW993XMv4RcKrOWyR18RcbZrt4jgOkCRVN9TLX/ZJh6/61iadywkD
NW7vfoYGzzh2IBDDZGhLpfHFv8r9qxEMhvnJUzdKH9d4VZuZlwnCDPHbjkUzJqdo
VG5k5e2O3xE900U8DFO4jpImhHJO4ADKXaHw0uo2jJHllTB54iLdMXRh12kn3emS
xYlOxfYeGWVLxM0zA9kZ9b9AzH65m8OIhl/EFGX5hLIV7yK9VFr/elxCuMr+/6jT
cSQA/1eYnCctjkTZh65RD1e449mobjG5cH+SwS+LMHAUQXng8l1m6DvwPzVGUXBO
zhpfX+8K/aD2lSRT8aoyiS5yy6MXnJKP7K8KJqXbNYzbtZQvo8IN+JuD93ilXoYa
YfHRSwb9eV4oxZk=
-----END CERTIFICATE-----

View File

@ -22,4 +22,6 @@
<string name="remove_cert">Remove certificate</string>
<string name="msg_confirm_remove_cert">Do you really want to remove this certificate from the list?</string>
<string name="no">No</string>
<string name="generate_certificate">Generate certificate</string>
<string name="generating_certificate">Waiting for your certificate, please wait…</string>
</resources>