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/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_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/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/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/menu/cert_menu.xml" value="0.3697916666666667" />
<entry key="app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml" value="0.3859375" /> <entry key="app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml" value="0.3859375" />
</map> </map>

View File

@ -55,6 +55,14 @@ dependencies {
implementation 'com.journeyapps:zxing-android-embedded:4.3.0' implementation 'com.journeyapps:zxing-android-embedded:4.3.0'
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 "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.+' testImplementation 'junit:junit:4.+'
androidTestImplementation 'androidx.test.ext:junit:1.1.3' androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'

View File

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

View File

@ -11,3 +11,7 @@ const val RESULT_TEST_POSITIVE = "260373001"
const val TEST_PCR = "LP6464-4" 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 package org.communiquons.dccaggregator.activities
import android.os.Bundle
import android.os.PersistableBundle
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import org.communiquons.dccaggregator.DCCAggregator import org.communiquons.dccaggregator.DCCAggregator
import org.communiquons.dccaggregator.apis.initOkHTTP
abstract class BaseActivity : AppCompatActivity() { 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 val certsManager get() = (application as DCCAggregator).certManager
} }

View File

@ -3,17 +3,20 @@ package org.communiquons.dccaggregator.activities
import android.app.AlertDialog import android.app.AlertDialog
import android.os.Bundle import android.os.Bundle
import android.view.ContextMenu import android.view.ContextMenu
import android.view.Menu
import android.view.MenuItem import android.view.MenuItem
import android.view.View import android.view.View
import android.widget.AdapterView import android.widget.AdapterView
import android.widget.Toast import android.widget.Toast
import androidx.lifecycle.ViewModelProvider
import com.journeyapps.barcodescanner.ScanContract import com.journeyapps.barcodescanner.ScanContract
import com.journeyapps.barcodescanner.ScanOptions import com.journeyapps.barcodescanner.ScanOptions
import org.communiquons.dccaggregator.R import org.communiquons.dccaggregator.R
import org.communiquons.dccaggregator.WALLET_URL import org.communiquons.dccaggregator.WALLET_URL
import org.communiquons.dccaggregator.adapters.CertificatesAdapter import org.communiquons.dccaggregator.adapters.CertificatesAdapter
import org.communiquons.dccaggregator.data.Certificate
import org.communiquons.dccaggregator.databinding.ActivityCertsManagerBinding import org.communiquons.dccaggregator.databinding.ActivityCertsManagerBinding
import org.communiquons.dccaggregator.models.Certificate import org.communiquons.dccaggregator.viewmodel.CertsManagerViewModel
import java.net.URLDecoder import java.net.URLDecoder
import java.nio.charset.StandardCharsets import java.nio.charset.StandardCharsets
@ -29,10 +32,17 @@ class CertsManager : BaseActivity() {
CertificatesAdapter(this, certsList) 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 private val TAG = CertsManager::class.java.canonicalName
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
binding = ActivityCertsManagerBinding.inflate(layoutInflater) binding = ActivityCertsManagerBinding.inflate(layoutInflater)
setContentView(binding.root) setContentView(binding.root)
@ -54,12 +64,22 @@ class CertsManager : BaseActivity() {
refreshList() refreshList()
} }
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
menuInflater.inflate(R.menu.cert_manager_top_menu, menu)
return true
}
override fun onOptionsItemSelected(item: MenuItem): Boolean { override fun onOptionsItemSelected(item: MenuItem): Boolean {
if (item.itemId == android.R.id.home) { if (item.itemId == android.R.id.home) {
finish() finish()
return true return true
} }
if (item.itemId == R.id.generate_certificate) {
generateNewCertificate()
return true
}
return super.onOptionsItemSelected(item) return super.onOptionsItemSelected(item)
} }
@ -111,7 +131,7 @@ class CertsManager : BaseActivity() {
) { ) {
super.onCreateContextMenu(menu, v, menuInfo) super.onCreateContextMenu(menu, v, menuInfo)
if (v == binding.certsList) if (v == binding.certsList)
menuInflater.inflate(R.menu.cert_menu, menu) menuInflater.inflate(R.menu.certificate_menu, menu)
} }
override fun onContextItemSelected(item: MenuItem): Boolean { override fun onContextItemSelected(item: MenuItem): Boolean {
@ -138,4 +158,20 @@ class CertsManager : BaseActivity() {
show() 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.R
import org.communiquons.dccaggregator.databinding.QrEntryBinding import org.communiquons.dccaggregator.databinding.QrEntryBinding
import org.communiquons.dccaggregator.ext.toDimensSize 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>) : class CertificatesAdapter(cxt: Context, list: ArrayList<Certificate>) :
ArrayAdapter<Certificate>(cxt, 0, list) { 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 android.content.Context
import dgca.verifier.app.decoder.CertificateDecodingResult import dgca.verifier.app.decoder.CertificateDecodingResult

View File

@ -3,7 +3,7 @@ package org.communiquons.dccaggregator.helper
import android.content.Context import android.content.Context
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import org.communiquons.dccaggregator.PREF_CERTS_LIST import org.communiquons.dccaggregator.PREF_CERTS_LIST
import org.communiquons.dccaggregator.models.Certificate import org.communiquons.dccaggregator.data.Certificate
class CertificatesList(context: Context) { class CertificatesList(context: Context) {
private val prefsManager = PreferenceManager.getDefaultSharedPreferences(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="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="msg_confirm_remove_cert">Do you really want to remove this certificate from the list?</string>
<string name="no">No</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> </resources>