Get server public key
This commit is contained in:
parent
8c2153a8e7
commit
dacf7fbc18
@ -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>
|
||||
|
@ -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'
|
||||
|
@ -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"
|
||||
|
@ -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/"
|
@ -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
|
||||
}
|
@ -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()
|
||||
}
|
||||
|
||||
}
|
@ -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) {
|
||||
|
@ -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)
|
||||
}
|
@ -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
|
||||
)
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package org.communiquons.dccaggregator.models
|
||||
package org.communiquons.dccaggregator.data
|
||||
|
||||
import android.content.Context
|
||||
import dgca.verifier.app.decoder.CertificateDecodingResult
|
@ -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)
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
}
|
5
app/src/main/res/drawable/ic_check.xml
Normal file
5
app/src/main/res/drawable/ic_check.xml
Normal 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>
|
29
app/src/main/res/layout/generating_dialog.xml
Normal file
29
app/src/main/res/layout/generating_dialog.xml
Normal 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>
|
9
app/src/main/res/menu/cert_manager_top_menu.xml
Normal file
9
app/src/main/res/menu/cert_manager_top_menu.xml
Normal 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>
|
41
app/src/main/res/raw/internal_july_2017.pem
Normal file
41
app/src/main/res/raw/internal_july_2017.pem
Normal 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-----
|
@ -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>
|
Loading…
Reference in New Issue
Block a user