mirror of
https://github.com/twinksoftware/Gearslip.git
synced 2026-11-12 00:32:26 +01:00
A certificate can be downloaded from any link and converted, and fetched again later
A .p12, with or without a password, or a certificate and key as PEM or DER, in one file or two. Gearslip ships no address of its own; the driver pastes one, and it's kept for Download again.
This commit is contained in:
1 parent
85c8133442
commit
ba86685935
21 files changed
+562
No files matched your search
@@ -0,0 +1,175 @@
|
||||
package app.seb3thehacker.gearslip
|
||||
|
||||
import android.content.Context
|
||||
import java.io.ByteArrayInputStream
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.net.HttpURLConnection
|
||||
import java.net.URL
|
||||
import java.security.KeyFactory
|
||||
import java.security.KeyStore
|
||||
import java.security.PrivateKey
|
||||
import java.security.cert.CertificateFactory
|
||||
import java.security.cert.X509Certificate
|
||||
import java.security.spec.PKCS8EncodedKeySpec
|
||||
import java.util.Base64
|
||||
|
||||
/**
|
||||
* A certificate fetched from a link the driver pastes (issue #15), in whatever form it comes:
|
||||
* a PKCS#12 file, with or without a password, or a certificate and its private key as PEM or
|
||||
* DER, together in one file or in two. Whatever arrives is converted to the PKCS#12 identity
|
||||
* Gearslip presents to the car, and stored as an imported certificate.
|
||||
*
|
||||
* Gearslip ships no address of its own. The links are kept on the phone so the same files can
|
||||
* be fetched again from Settings, and they're only ever fetched when the driver asks.
|
||||
*/
|
||||
object CertFromLink {
|
||||
|
||||
sealed interface Outcome {
|
||||
/** Imported, and in use from the next connection. */
|
||||
data object Done : Outcome
|
||||
/** A PKCS#12 file that needs its password. */
|
||||
data object NeedsPassword : Outcome
|
||||
/** A certificate on its own: its private key is at another address. */
|
||||
data object NeedsKey : Outcome
|
||||
}
|
||||
|
||||
private const val KEY_CERT_LINK = "cert_link"
|
||||
private const val KEY_KEY_LINK = "cert_key_link"
|
||||
private const val MAX_BYTES = 1L * 1024 * 1024
|
||||
/** The password the converted file is stored under; it never leaves the app's storage. */
|
||||
private const val STORE_PASSWORD = "gearslip"
|
||||
|
||||
/** The link used last, for Download again, or null when none was ever used. */
|
||||
fun savedLink(context: Context): Pair<String, String?>? {
|
||||
val cert = AppSettings.getString(context, KEY_CERT_LINK, "").ifEmpty { return null }
|
||||
return cert to AppSettings.getString(context, KEY_KEY_LINK, "").ifEmpty { null }
|
||||
}
|
||||
|
||||
/** Runs on a background thread. Throws with a message a driver can read when it can't be used. */
|
||||
fun import(context: Context, certLink: String, keyLink: String?, password: String?): Outcome {
|
||||
val first = download(certLink)
|
||||
// The key's link is fetched only when the first file turns out to hold no key.
|
||||
var second: ByteArray? = null
|
||||
var converted = convert(first, null, password)
|
||||
if (converted is Converted.NeedsKey && !keyLink.isNullOrBlank()) {
|
||||
second = download(keyLink)
|
||||
converted = convert(first, second, password)
|
||||
}
|
||||
return when (converted) {
|
||||
Converted.NeedsKey -> Outcome.NeedsKey
|
||||
Converted.NeedsPassword -> Outcome.NeedsPassword
|
||||
is Converted.Ready -> {
|
||||
CertProvider.importFromBytes(context, converted.p12, converted.password)
|
||||
remember(context, certLink, keyLink.takeIf { second != null })
|
||||
Outcome.Done
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed interface Converted {
|
||||
class Ready(val p12: ByteArray, val password: String) : Converted
|
||||
data object NeedsPassword : Converted
|
||||
data object NeedsKey : Converted
|
||||
}
|
||||
|
||||
/**
|
||||
* Turns what was downloaded into a PKCS#12 file with its password. [first] is the file at
|
||||
* the first link; [second] the key from the second, once asked for.
|
||||
*/
|
||||
internal fun convert(first: ByteArray, second: ByteArray?, password: String?): Converted {
|
||||
// A PKCS#12 file is DER too, so the certificate parser gets the first try.
|
||||
val certs = certificates(first)
|
||||
if (certs.isEmpty()) {
|
||||
val pass = password.orEmpty()
|
||||
val loaded = runCatching { KeyStore.getInstance("PKCS12").apply { load(ByteArrayInputStream(first), pass.toCharArray()) } }
|
||||
if (loaded.isFailure) {
|
||||
if (password == null) return Converted.NeedsPassword
|
||||
error("That password doesn't open the file, or it isn't a certificate.")
|
||||
}
|
||||
val store = loaded.getOrThrow()
|
||||
require(store.aliases().asSequence().any { store.isKeyEntry(it) }) { "The file holds no private key, so the car can't be shown it." }
|
||||
return Converted.Ready(first, pass)
|
||||
}
|
||||
|
||||
val key = privateKey(first) ?: second?.let {
|
||||
privateKey(it) ?: error("The second link isn't a private key in a form Gearslip reads.")
|
||||
} ?: return Converted.NeedsKey
|
||||
// The certificate that goes with the key comes first, and any issuers after it.
|
||||
val leaf = certs.firstOrNull { belongs(key, it) } ?: error("The private key doesn't belong to this certificate.")
|
||||
val chain = listOf(leaf) + certs.filter { it !== leaf }
|
||||
val store = KeyStore.getInstance("PKCS12").apply {
|
||||
load(null, null)
|
||||
setKeyEntry("phone", key, STORE_PASSWORD.toCharArray(), chain.toTypedArray())
|
||||
}
|
||||
val bytes = ByteArrayOutputStream().use { store.store(it, STORE_PASSWORD.toCharArray()); it.toByteArray() }
|
||||
GearslipLog.i("certificate from a link: ${leaf.subjectX500Principal}")
|
||||
return Converted.Ready(bytes, STORE_PASSWORD)
|
||||
}
|
||||
|
||||
private fun remember(context: Context, cert: String, key: String?) {
|
||||
AppSettings.putString(context, KEY_CERT_LINK, cert)
|
||||
AppSettings.putString(context, KEY_KEY_LINK, key.orEmpty())
|
||||
}
|
||||
|
||||
private fun download(link: String): ByteArray {
|
||||
val url = link.trim()
|
||||
require(url.startsWith("https://") || url.startsWith("http://")) { "That isn't a web address." }
|
||||
val connection = (URL(url).openConnection() as HttpURLConnection).apply {
|
||||
connectTimeout = 15_000
|
||||
readTimeout = 15_000
|
||||
instanceFollowRedirects = true
|
||||
}
|
||||
try {
|
||||
if (connection.responseCode !in 200..299) error("The server answered ${connection.responseCode}.")
|
||||
val out = ByteArrayOutputStream()
|
||||
connection.inputStream.use { input ->
|
||||
val buffer = ByteArray(16 * 1024)
|
||||
while (true) {
|
||||
val read = input.read(buffer)
|
||||
if (read < 0) break
|
||||
out.write(buffer, 0, read)
|
||||
require(out.size() <= MAX_BYTES) { "That file is far too big to be a certificate." }
|
||||
}
|
||||
}
|
||||
return out.toByteArray()
|
||||
} finally {
|
||||
connection.disconnect()
|
||||
}
|
||||
}
|
||||
|
||||
/** Every certificate in PEM text, or the one certificate a DER file holds. */
|
||||
private fun certificates(bytes: ByteArray): List<X509Certificate> {
|
||||
val factory = CertificateFactory.getInstance("X.509")
|
||||
val pem = pemBlocks(bytes, "CERTIFICATE")
|
||||
if (pem.isNotEmpty()) return pem.map { factory.generateCertificate(ByteArrayInputStream(it)) as X509Certificate }
|
||||
return listOfNotNull(runCatching { factory.generateCertificate(ByteArrayInputStream(bytes)) as X509Certificate }.getOrNull())
|
||||
}
|
||||
|
||||
/** A PKCS#8 or PKCS#1 key, as PEM or as PKCS#8 DER; null when there's none. */
|
||||
private fun privateKey(bytes: ByteArray): PrivateKey? {
|
||||
require(pemBlocks(bytes, "ENCRYPTED PRIVATE KEY").isEmpty()) { "The private key is encrypted. Gearslip needs one without a passphrase, or a .p12 file." }
|
||||
pemBlocks(bytes, "PRIVATE KEY").firstOrNull()?.let { return pkcs8(it) }
|
||||
if (pemBlocks(bytes, "RSA PRIVATE KEY").isNotEmpty()) return PemKeyParser.parsePrivateKey(String(bytes)).getPrivate()
|
||||
if (String(bytes).contains("-----BEGIN")) return null
|
||||
return runCatching { pkcs8(bytes) }.getOrNull()
|
||||
}
|
||||
|
||||
private fun pkcs8(der: ByteArray): PrivateKey = runCatching {
|
||||
KeyFactory.getInstance("RSA").generatePrivate(PKCS8EncodedKeySpec(der))
|
||||
}.getOrElse { KeyFactory.getInstance("EC").generatePrivate(PKCS8EncodedKeySpec(der)) }
|
||||
|
||||
/**
|
||||
* Whether [key] is [cert]'s. RSA keys are matched by modulus, which every Android provider
|
||||
* exposes, even one that hides the rest of a PKCS#8 key. Any other kind is taken on trust.
|
||||
*/
|
||||
private fun belongs(key: PrivateKey, cert: X509Certificate): Boolean {
|
||||
val public = cert.publicKey
|
||||
return if (key is java.security.interfaces.RSAKey && public is java.security.interfaces.RSAKey) key.modulus == public.modulus else key.algorithm == public.algorithm
|
||||
}
|
||||
|
||||
private fun pemBlocks(bytes: ByteArray, type: String): List<ByteArray> {
|
||||
val text = runCatching { String(bytes, Charsets.US_ASCII) }.getOrDefault("")
|
||||
return Regex("-----BEGIN $type-----([A-Za-z0-9+/=\\s]+)-----END $type-----").findAll(text)
|
||||
.map { Base64.getMimeDecoder().decode(it.groupValues[1]) }.toList()
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
package app.seb3thehacker.gearslip.ui
|
||||
|
||||
import androidx.compose.material.icons.filled.Create
|
||||
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.res.vectorResource
|
||||
import app.seb3thehacker.gearslip.R
|
||||
@@ -356,6 +358,8 @@ private fun CertificateSection() {
|
||||
var notice by remember { mutableStateOf<String?>(null) }
|
||||
var downloading by remember { mutableStateOf(false) }
|
||||
var detailsOpen by remember { mutableStateOf(false) }
|
||||
var linkDialog by remember { mutableStateOf<LinkRequest?>(null) }
|
||||
val savedLink = remember(refresh) { app.seb3thehacker.gearslip.CertFromLink.savedLink(context) }
|
||||
val picker = rememberLauncherForActivityResult(ActivityResultContracts.OpenDocument()) { uri ->
|
||||
if (uri != null) pendingUri = uri
|
||||
}
|
||||
@@ -413,6 +417,22 @@ private fun CertificateSection() {
|
||||
icon = Icons.Filled.Refresh,
|
||||
)
|
||||
SettingsDivider()
|
||||
SettingsRow(
|
||||
stringResource(R.string.cert_link),
|
||||
Modifier.clickable { linkDialog = LinkRequest() },
|
||||
subtitle = stringResource(R.string.cert_link_sub),
|
||||
icon = Icons.Filled.Create,
|
||||
)
|
||||
savedLink?.let { (cert, key) ->
|
||||
SettingsDivider()
|
||||
SettingsRow(
|
||||
stringResource(R.string.cert_link_again),
|
||||
Modifier.clickable { linkDialog = LinkRequest(cert, key, start = true) },
|
||||
subtitle = cert.substringAfter("://").substringBefore('/'),
|
||||
icon = Icons.Filled.Refresh,
|
||||
)
|
||||
}
|
||||
SettingsDivider()
|
||||
SettingsRow(
|
||||
stringResource(R.string.cert_import),
|
||||
Modifier.clickable { picker.launch(arrayOf("*/*")) },
|
||||
@@ -440,6 +460,14 @@ private fun CertificateSection() {
|
||||
}
|
||||
}
|
||||
|
||||
linkDialog?.let { request ->
|
||||
LinkDialog(request, onDismiss = { linkDialog = null }) {
|
||||
linkDialog = null
|
||||
notice = context.getString(R.string.cert_link_done)
|
||||
refresh++
|
||||
}
|
||||
}
|
||||
|
||||
pendingUri?.let { uri ->
|
||||
PasswordDialog(
|
||||
onDismiss = { pendingUri = null },
|
||||
@@ -461,6 +489,75 @@ private fun CertificateSection() {
|
||||
}
|
||||
}
|
||||
|
||||
/** What the link dialog opens with: blank, or the saved link to fetch again straight away. */
|
||||
private class LinkRequest(val cert: String = "https://", val key: String? = null, val start: Boolean = false)
|
||||
|
||||
/**
|
||||
* Fetches a certificate from a link (issue #15). It asks for more only when the file needs it:
|
||||
* the password of a .p12, or the link to a key that came separately.
|
||||
*/
|
||||
@Composable
|
||||
private fun LinkDialog(request: LinkRequest, onDismiss: () -> Unit, onDone: () -> Unit) {
|
||||
val context = LocalContext.current
|
||||
val scope = rememberCoroutineScope()
|
||||
var cert by remember { mutableStateOf(request.cert) }
|
||||
var key by remember { mutableStateOf(request.key) }
|
||||
var password by remember { mutableStateOf<String?>(null) }
|
||||
var error by remember { mutableStateOf<String?>(null) }
|
||||
var busy by remember { mutableStateOf(false) }
|
||||
fun go() {
|
||||
busy = true
|
||||
error = null
|
||||
scope.launch {
|
||||
val result = withContext(Dispatchers.IO) {
|
||||
runCatching { app.seb3thehacker.gearslip.CertFromLink.import(context, cert, key?.takeIf { it.isNotBlank() }, password) }
|
||||
}
|
||||
busy = false
|
||||
result.onSuccess { outcome ->
|
||||
when (outcome) {
|
||||
app.seb3thehacker.gearslip.CertFromLink.Outcome.Done -> onDone()
|
||||
app.seb3thehacker.gearslip.CertFromLink.Outcome.NeedsKey -> if (key == null) key = "https://" else error = context.getString(R.string.cert_link_key_note)
|
||||
app.seb3thehacker.gearslip.CertFromLink.Outcome.NeedsPassword -> if (password == null) password = "" else error = context.getString(R.string.cert_import_failed)
|
||||
}
|
||||
}.onFailure { error = context.getString(R.string.cert_link_failed, it.message.orEmpty()) }
|
||||
}
|
||||
}
|
||||
LaunchedEffect(request) { if (request.start) go() }
|
||||
AlertDialog(
|
||||
onDismissRequest = onDismiss,
|
||||
title = { Text(stringResource(R.string.cert_link_title)) },
|
||||
text = {
|
||||
Column(verticalArrangement = Arrangement.spacedBy(12.dp)) {
|
||||
Text(stringResource(R.string.cert_link_note))
|
||||
OutlinedTextField(
|
||||
cert, { cert = it }, label = { Text(stringResource(R.string.cert_link_label)) }, singleLine = true,
|
||||
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Uri),
|
||||
)
|
||||
key?.let { k ->
|
||||
Text(stringResource(R.string.cert_link_key_note), style = MaterialTheme.typography.bodySmall)
|
||||
OutlinedTextField(
|
||||
k, { key = it }, label = { Text(stringResource(R.string.cert_link_key)) }, singleLine = true,
|
||||
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Uri),
|
||||
)
|
||||
}
|
||||
password?.let { p ->
|
||||
Text(stringResource(R.string.cert_link_password_note), style = MaterialTheme.typography.bodySmall)
|
||||
OutlinedTextField(
|
||||
p, { password = it }, label = { Text(stringResource(R.string.cert_link_password)) }, singleLine = true,
|
||||
visualTransformation = androidx.compose.ui.text.input.PasswordVisualTransformation(),
|
||||
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password),
|
||||
)
|
||||
}
|
||||
error?.let { Text(it, color = MaterialTheme.colorScheme.error, style = MaterialTheme.typography.bodySmall) }
|
||||
}
|
||||
},
|
||||
confirmButton = {
|
||||
TextButton(::go, enabled = !busy) { Text(stringResource(if (busy) R.string.cert_downloading else R.string.cert_link_download)) }
|
||||
},
|
||||
dismissButton = { TextButton(onDismiss) { Text(stringResource(R.string.cancel)) } },
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun Detail(label: String, value: String) {
|
||||
Column {
|
||||
|
||||
@@ -992,4 +992,17 @@
|
||||
<string name="car_apps_as_messaging_note">إشعاراته، تُقرأ بصوت عالٍ، مع الردود.</string>
|
||||
<string name="car_apps_added_by_you">أضفته أنت</string>
|
||||
<string name="car_apps_remove">إزالة %1$s</string>
|
||||
<string name="cert_link">تنزيل من رابط</string>
|
||||
<string name="cert_link_sub">ملف شهادة تثق به، من عنوان تلصقه</string>
|
||||
<string name="cert_link_title">شهادة من رابط</string>
|
||||
<string name="cert_link_note">الصق عنوان ملف .p12، أو عنوان شهادة ومفتاحها بصيغة PEM أو DER. يحوّلها Gearslip إلى الصيغة التي تحتاجها السيارة.</string>
|
||||
<string name="cert_link_label">رابط الشهادة أو ملف .p12</string>
|
||||
<string name="cert_link_key">رابط المفتاح الخاص</string>
|
||||
<string name="cert_link_key_note">هذه شهادة بلا مفتاح. الصق رابط المفتاح أيضًا.</string>
|
||||
<string name="cert_link_password">كلمة المرور</string>
|
||||
<string name="cert_link_password_note">لهذا الملف كلمة مرور.</string>
|
||||
<string name="cert_link_download">تنزيل</string>
|
||||
<string name="cert_link_again">التنزيل مجددًا</string>
|
||||
<string name="cert_link_done">نُزّلت وحُوّلت. تُستخدم من الاتصال التالي.</string>
|
||||
<string name="cert_link_failed">تعذّر استخدامها. %1$s</string>
|
||||
</resources>
|
||||
@@ -972,4 +972,17 @@
|
||||
<string name="car_apps_as_messaging_note">Její oznámení, předčítaná nahlas, s odpověďmi.</string>
|
||||
<string name="car_apps_added_by_you">Přidáno vámi</string>
|
||||
<string name="car_apps_remove">Odebrat %1$s</string>
|
||||
<string name="cert_link">Stáhnout z odkazu</string>
|
||||
<string name="cert_link_sub">Soubor certifikátu, kterému věříte, z adresy, kterou vložíte</string>
|
||||
<string name="cert_link_title">Certifikát z odkazu</string>
|
||||
<string name="cert_link_note">Vložte adresu souboru .p12 nebo certifikátu a klíče ve formátu PEM či DER. Gearslip ho převede do podoby, kterou auto potřebuje.</string>
|
||||
<string name="cert_link_label">Odkaz na certifikát nebo .p12</string>
|
||||
<string name="cert_link_key">Odkaz na soukromý klíč</string>
|
||||
<string name="cert_link_key_note">Je to certifikát bez klíče. Vložte i odkaz na klíč.</string>
|
||||
<string name="cert_link_password">Heslo</string>
|
||||
<string name="cert_link_password_note">Tento soubor má heslo.</string>
|
||||
<string name="cert_link_download">Stáhnout</string>
|
||||
<string name="cert_link_again">Stáhnout znovu</string>
|
||||
<string name="cert_link_done">Staženo a převedeno. Použije se od dalšího připojení.</string>
|
||||
<string name="cert_link_failed">Nelze použít. %1$s</string>
|
||||
</resources>
|
||||
@@ -834,4 +834,17 @@
|
||||
<string name="car_apps_as_messaging_note">Ihre Benachrichtigungen, vorgelesen, mit Antworten.</string>
|
||||
<string name="car_apps_added_by_you">Von dir hinzugefügt</string>
|
||||
<string name="car_apps_remove">%1$s entfernen</string>
|
||||
<string name="cert_link">Über einen Link herunterladen</string>
|
||||
<string name="cert_link_sub">Eine Zertifikatsdatei, der du vertraust, unter einer Adresse, die du einfügst</string>
|
||||
<string name="cert_link_title">Zertifikat über einen Link</string>
|
||||
<string name="cert_link_note">Füge die Adresse einer .p12-Datei ein oder die eines Zertifikats mit Schlüssel als PEM oder DER. Gearslip wandelt sie in die Form um, die das Auto braucht.</string>
|
||||
<string name="cert_link_label">Link zum Zertifikat oder zur .p12-Datei</string>
|
||||
<string name="cert_link_key">Link zum privaten Schlüssel</string>
|
||||
<string name="cert_link_key_note">Das ist ein Zertifikat ohne Schlüssel. Füge auch den Link zum Schlüssel ein.</string>
|
||||
<string name="cert_link_password">Passwort</string>
|
||||
<string name="cert_link_password_note">Diese Datei hat ein Passwort.</string>
|
||||
<string name="cert_link_download">Herunterladen</string>
|
||||
<string name="cert_link_again">Erneut herunterladen</string>
|
||||
<string name="cert_link_done">Heruntergeladen und umgewandelt. Es gilt ab der nächsten Verbindung.</string>
|
||||
<string name="cert_link_failed">Nicht verwendbar. %1$s</string>
|
||||
</resources>
|
||||
@@ -836,4 +836,17 @@
|
||||
<string name="car_apps_as_messaging_note">Sus notificaciones, leídas en voz alta, con respuestas.</string>
|
||||
<string name="car_apps_added_by_you">Añadida por ti</string>
|
||||
<string name="car_apps_remove">Quitar %1$s</string>
|
||||
<string name="cert_link">Descargar desde un enlace</string>
|
||||
<string name="cert_link_sub">Un archivo de certificado de confianza, en una dirección que pegues</string>
|
||||
<string name="cert_link_title">Certificado desde un enlace</string>
|
||||
<string name="cert_link_note">Pega la dirección de un archivo .p12, o de un certificado y su clave en PEM o DER. Gearslip lo convierte al formato que necesita el coche.</string>
|
||||
<string name="cert_link_label">Enlace del certificado o del .p12</string>
|
||||
<string name="cert_link_key">Enlace de la clave privada</string>
|
||||
<string name="cert_link_key_note">Es un certificado sin su clave. Pega también el enlace de la clave.</string>
|
||||
<string name="cert_link_password">Contraseña</string>
|
||||
<string name="cert_link_password_note">Este archivo tiene contraseña.</string>
|
||||
<string name="cert_link_download">Descargar</string>
|
||||
<string name="cert_link_again">Descargar de nuevo</string>
|
||||
<string name="cert_link_done">Descargado y convertido. Se usará en la próxima conexión.</string>
|
||||
<string name="cert_link_failed">No se pudo usar. %1$s</string>
|
||||
</resources>
|
||||
@@ -962,4 +962,17 @@
|
||||
<string name="car_apps_as_messaging_note">Ses notifications, lues à voix haute, avec réponses.</string>
|
||||
<string name="car_apps_added_by_you">Ajoutée par vous</string>
|
||||
<string name="car_apps_remove">Retirer %1$s</string>
|
||||
<string name="cert_link">Télécharger depuis un lien</string>
|
||||
<string name="cert_link_sub">Un fichier de certificat de confiance, à une adresse que vous collez</string>
|
||||
<string name="cert_link_title">Certificat depuis un lien</string>
|
||||
<string name="cert_link_note">Collez l’adresse d’un fichier .p12, ou d’un certificat et de sa clé en PEM ou DER. Gearslip le convertit au format dont la voiture a besoin.</string>
|
||||
<string name="cert_link_label">Lien du certificat ou du .p12</string>
|
||||
<string name="cert_link_key">Lien de la clé privée</string>
|
||||
<string name="cert_link_key_note">C’est un certificat sans sa clé. Collez aussi le lien de la clé.</string>
|
||||
<string name="cert_link_password">Mot de passe</string>
|
||||
<string name="cert_link_password_note">Ce fichier a un mot de passe.</string>
|
||||
<string name="cert_link_download">Télécharger</string>
|
||||
<string name="cert_link_again">Télécharger à nouveau</string>
|
||||
<string name="cert_link_done">Téléchargé et converti. Il sera utilisé à la prochaine connexion.</string>
|
||||
<string name="cert_link_failed">Impossible de l’utiliser. %1$s</string>
|
||||
</resources>
|
||||
@@ -962,4 +962,17 @@
|
||||
<string name="car_apps_as_messaging_note">Le sue notifiche, lette ad alta voce, con risposte.</string>
|
||||
<string name="car_apps_added_by_you">Aggiunta da te</string>
|
||||
<string name="car_apps_remove">Rimuovi %1$s</string>
|
||||
<string name="cert_link">Scarica da un link</string>
|
||||
<string name="cert_link_sub">Un file di certificato di cui ti fidi, a un indirizzo che incolli</string>
|
||||
<string name="cert_link_title">Certificato da un link</string>
|
||||
<string name="cert_link_note">Incolla l’indirizzo di un file .p12, o di un certificato e della sua chiave in PEM o DER. Gearslip lo converte nel formato che serve all’auto.</string>
|
||||
<string name="cert_link_label">Link del certificato o del .p12</string>
|
||||
<string name="cert_link_key">Link della chiave privata</string>
|
||||
<string name="cert_link_key_note">È un certificato senza la sua chiave. Incolla anche il link della chiave.</string>
|
||||
<string name="cert_link_password">Password</string>
|
||||
<string name="cert_link_password_note">Questo file ha una password.</string>
|
||||
<string name="cert_link_download">Scarica</string>
|
||||
<string name="cert_link_again">Scarica di nuovo</string>
|
||||
<string name="cert_link_done">Scaricato e convertito. Verrà usato dalla prossima connessione.</string>
|
||||
<string name="cert_link_failed">Impossibile usarlo. %1$s</string>
|
||||
</resources>
|
||||
@@ -942,4 +942,17 @@
|
||||
<string name="car_apps_as_messaging_note">通知を読み上げ、返信できます。</string>
|
||||
<string name="car_apps_added_by_you">自分で追加</string>
|
||||
<string name="car_apps_remove">%1$s を削除</string>
|
||||
<string name="cert_link">リンクからダウンロード</string>
|
||||
<string name="cert_link_sub">信頼できる証明書ファイルを、貼り付けたアドレスから</string>
|
||||
<string name="cert_link_title">リンクから証明書</string>
|
||||
<string name="cert_link_note">.p12 ファイル、または PEM か DER の証明書と鍵のアドレスを貼り付けてください。Gearslip が車に必要な形式に変換します。</string>
|
||||
<string name="cert_link_label">証明書または .p12 のリンク</string>
|
||||
<string name="cert_link_key">秘密鍵のリンク</string>
|
||||
<string name="cert_link_key_note">鍵のない証明書です。鍵のリンクも貼り付けてください。</string>
|
||||
<string name="cert_link_password">パスワード</string>
|
||||
<string name="cert_link_password_note">このファイルにはパスワードがあります。</string>
|
||||
<string name="cert_link_download">ダウンロード</string>
|
||||
<string name="cert_link_again">もう一度ダウンロード</string>
|
||||
<string name="cert_link_done">ダウンロードして変換しました。次の接続から使われます。</string>
|
||||
<string name="cert_link_failed">使用できませんでした。%1$s</string>
|
||||
</resources>
|
||||
@@ -942,4 +942,17 @@
|
||||
<string name="car_apps_as_messaging_note">알림을 소리 내어 읽고 답장할 수 있습니다.</string>
|
||||
<string name="car_apps_added_by_you">직접 추가함</string>
|
||||
<string name="car_apps_remove">%1$s 삭제</string>
|
||||
<string name="cert_link">링크에서 다운로드</string>
|
||||
<string name="cert_link_sub">신뢰하는 인증서 파일을 붙여넣은 주소에서</string>
|
||||
<string name="cert_link_title">링크로 인증서 가져오기</string>
|
||||
<string name="cert_link_note">.p12 파일 또는 PEM이나 DER 형식의 인증서와 키 주소를 붙여넣으세요. Gearslip이 차량에 필요한 형식으로 변환합니다.</string>
|
||||
<string name="cert_link_label">인증서 또는 .p12 링크</string>
|
||||
<string name="cert_link_key">개인 키 링크</string>
|
||||
<string name="cert_link_key_note">키가 없는 인증서입니다. 키 링크도 붙여넣으세요.</string>
|
||||
<string name="cert_link_password">비밀번호</string>
|
||||
<string name="cert_link_password_note">이 파일에는 비밀번호가 있습니다.</string>
|
||||
<string name="cert_link_download">다운로드</string>
|
||||
<string name="cert_link_again">다시 다운로드</string>
|
||||
<string name="cert_link_done">다운로드하고 변환했습니다. 다음 연결부터 사용됩니다.</string>
|
||||
<string name="cert_link_failed">사용할 수 없습니다. %1$s</string>
|
||||
</resources>
|
||||
@@ -952,4 +952,17 @@
|
||||
<string name="car_apps_as_messaging_note">De meldingen, voorgelezen, met antwoorden.</string>
|
||||
<string name="car_apps_added_by_you">Door jou toegevoegd</string>
|
||||
<string name="car_apps_remove">%1$s verwijderen</string>
|
||||
<string name="cert_link">Downloaden via een link</string>
|
||||
<string name="cert_link_sub">Een certificaatbestand dat je vertrouwt, op een adres dat je plakt</string>
|
||||
<string name="cert_link_title">Certificaat via een link</string>
|
||||
<string name="cert_link_note">Plak het adres van een .p12-bestand, of van een certificaat en sleutel in PEM of DER. Gearslip zet het om naar de vorm die de auto nodig heeft.</string>
|
||||
<string name="cert_link_label">Link naar certificaat of .p12</string>
|
||||
<string name="cert_link_key">Link naar de privésleutel</string>
|
||||
<string name="cert_link_key_note">Dat is een certificaat zonder sleutel. Plak ook de link naar de sleutel.</string>
|
||||
<string name="cert_link_password">Wachtwoord</string>
|
||||
<string name="cert_link_password_note">Dit bestand heeft een wachtwoord.</string>
|
||||
<string name="cert_link_download">Downloaden</string>
|
||||
<string name="cert_link_again">Opnieuw downloaden</string>
|
||||
<string name="cert_link_done">Gedownload en omgezet. Het wordt gebruikt vanaf de volgende verbinding.</string>
|
||||
<string name="cert_link_failed">Kon het niet gebruiken. %1$s</string>
|
||||
</resources>
|
||||
@@ -972,4 +972,17 @@
|
||||
<string name="car_apps_as_messaging_note">Jej powiadomienia, czytane na głos, z odpowiedziami.</string>
|
||||
<string name="car_apps_added_by_you">Dodana przez Ciebie</string>
|
||||
<string name="car_apps_remove">Usuń %1$s</string>
|
||||
<string name="cert_link">Pobierz z linku</string>
|
||||
<string name="cert_link_sub">Plik certyfikatu, któremu ufasz, spod wklejonego adresu</string>
|
||||
<string name="cert_link_title">Certyfikat z linku</string>
|
||||
<string name="cert_link_note">Wklej adres pliku .p12 albo certyfikatu i klucza w PEM lub DER. Gearslip przekształci go do postaci, której potrzebuje samochód.</string>
|
||||
<string name="cert_link_label">Link do certyfikatu lub pliku .p12</string>
|
||||
<string name="cert_link_key">Link do klucza prywatnego</string>
|
||||
<string name="cert_link_key_note">To certyfikat bez klucza. Wklej też link do klucza.</string>
|
||||
<string name="cert_link_password">Hasło</string>
|
||||
<string name="cert_link_password_note">Ten plik ma hasło.</string>
|
||||
<string name="cert_link_download">Pobierz</string>
|
||||
<string name="cert_link_again">Pobierz ponownie</string>
|
||||
<string name="cert_link_done">Pobrano i przekształcono. Zostanie użyty od następnego połączenia.</string>
|
||||
<string name="cert_link_failed">Nie można go użyć. %1$s</string>
|
||||
</resources>
|
||||
@@ -962,4 +962,17 @@
|
||||
<string name="car_apps_as_messaging_note">As notificações dele, lidas em voz alta, com respostas.</string>
|
||||
<string name="car_apps_added_by_you">Adicionado por você</string>
|
||||
<string name="car_apps_remove">Remover %1$s</string>
|
||||
<string name="cert_link">Baixar de um link</string>
|
||||
<string name="cert_link_sub">Um arquivo de certificado confiável, em um endereço que você cola</string>
|
||||
<string name="cert_link_title">Certificado de um link</string>
|
||||
<string name="cert_link_note">Cole o endereço de um arquivo .p12, ou de um certificado e sua chave em PEM ou DER. O Gearslip converte para o formato que o carro precisa.</string>
|
||||
<string name="cert_link_label">Link do certificado ou do .p12</string>
|
||||
<string name="cert_link_key">Link da chave privada</string>
|
||||
<string name="cert_link_key_note">É um certificado sem a chave. Cole também o link da chave.</string>
|
||||
<string name="cert_link_password">Senha</string>
|
||||
<string name="cert_link_password_note">Este arquivo tem senha.</string>
|
||||
<string name="cert_link_download">Baixar</string>
|
||||
<string name="cert_link_again">Baixar de novo</string>
|
||||
<string name="cert_link_done">Baixado e convertido. Será usado a partir da próxima conexão.</string>
|
||||
<string name="cert_link_failed">Não foi possível usar. %1$s</string>
|
||||
</resources>
|
||||
@@ -844,4 +844,17 @@
|
||||
<string name="car_apps_as_messaging_note">Notificările ei, citite cu voce tare, cu răspunsuri.</string>
|
||||
<string name="car_apps_added_by_you">Adăugată de tine</string>
|
||||
<string name="car_apps_remove">Elimină %1$s</string>
|
||||
<string name="cert_link">Descarcă dintr-un link</string>
|
||||
<string name="cert_link_sub">Un fișier de certificat de încredere, de la o adresă pe care o lipești</string>
|
||||
<string name="cert_link_title">Certificat dintr-un link</string>
|
||||
<string name="cert_link_note">Lipește adresa unui fișier .p12 sau a unui certificat și a cheii lui în PEM ori DER. Gearslip îl convertește în forma de care are nevoie mașina.</string>
|
||||
<string name="cert_link_label">Linkul certificatului sau al .p12</string>
|
||||
<string name="cert_link_key">Linkul cheii private</string>
|
||||
<string name="cert_link_key_note">Este un certificat fără cheie. Lipește și linkul cheii.</string>
|
||||
<string name="cert_link_password">Parolă</string>
|
||||
<string name="cert_link_password_note">Fișierul are o parolă.</string>
|
||||
<string name="cert_link_download">Descarcă</string>
|
||||
<string name="cert_link_again">Descarcă din nou</string>
|
||||
<string name="cert_link_done">Descărcat și convertit. Se folosește de la următoarea conectare.</string>
|
||||
<string name="cert_link_failed">Nu a putut fi folosit. %1$s</string>
|
||||
</resources>
|
||||
@@ -972,4 +972,17 @@
|
||||
<string name="car_apps_as_messaging_note">Его уведомления, прочитанные вслух, с ответами.</string>
|
||||
<string name="car_apps_added_by_you">Добавлено вами</string>
|
||||
<string name="car_apps_remove">Удалить %1$s</string>
|
||||
<string name="cert_link">Скачать по ссылке</string>
|
||||
<string name="cert_link_sub">Файл сертификата, которому вы доверяете, по адресу, который вы вставите</string>
|
||||
<string name="cert_link_title">Сертификат по ссылке</string>
|
||||
<string name="cert_link_note">Вставьте адрес файла .p12 или сертификата с ключом в формате PEM или DER. Gearslip преобразует его в нужный машине вид.</string>
|
||||
<string name="cert_link_label">Ссылка на сертификат или .p12</string>
|
||||
<string name="cert_link_key">Ссылка на закрытый ключ</string>
|
||||
<string name="cert_link_key_note">Это сертификат без ключа. Вставьте также ссылку на ключ.</string>
|
||||
<string name="cert_link_password">Пароль</string>
|
||||
<string name="cert_link_password_note">У этого файла есть пароль.</string>
|
||||
<string name="cert_link_download">Скачать</string>
|
||||
<string name="cert_link_again">Скачать снова</string>
|
||||
<string name="cert_link_done">Скачано и преобразовано. Будет использоваться со следующего подключения.</string>
|
||||
<string name="cert_link_failed">Не удалось использовать. %1$s</string>
|
||||
</resources>
|
||||
@@ -952,4 +952,17 @@
|
||||
<string name="car_apps_as_messaging_note">Dess aviseringar, upplästa, med svar.</string>
|
||||
<string name="car_apps_added_by_you">Tillagd av dig</string>
|
||||
<string name="car_apps_remove">Ta bort %1$s</string>
|
||||
<string name="cert_link">Ladda ner från en länk</string>
|
||||
<string name="cert_link_sub">En certifikatfil du litar på, från en adress du klistrar in</string>
|
||||
<string name="cert_link_title">Certifikat från en länk</string>
|
||||
<string name="cert_link_note">Klistra in adressen till en .p12-fil, eller till ett certifikat och en nyckel i PEM eller DER. Gearslip gör om den till den form bilen behöver.</string>
|
||||
<string name="cert_link_label">Länk till certifikat eller .p12</string>
|
||||
<string name="cert_link_key">Länk till den privata nyckeln</string>
|
||||
<string name="cert_link_key_note">Det är ett certifikat utan nyckel. Klistra in nyckelns länk också.</string>
|
||||
<string name="cert_link_password">Lösenord</string>
|
||||
<string name="cert_link_password_note">Filen har ett lösenord.</string>
|
||||
<string name="cert_link_download">Ladda ner</string>
|
||||
<string name="cert_link_again">Ladda ner igen</string>
|
||||
<string name="cert_link_done">Nedladdat och omgjort. Det används från nästa anslutning.</string>
|
||||
<string name="cert_link_failed">Det gick inte att använda. %1$s</string>
|
||||
</resources>
|
||||
@@ -952,4 +952,17 @@
|
||||
<string name="car_apps_as_messaging_note">Bildirimleri, sesli okunur ve yanıtlanabilir.</string>
|
||||
<string name="car_apps_added_by_you">Sizin eklediğiniz</string>
|
||||
<string name="car_apps_remove">%1$s kaldır</string>
|
||||
<string name="cert_link">Bir bağlantıdan indir</string>
|
||||
<string name="cert_link_sub">Güvendiğiniz bir sertifika dosyası, yapıştırdığınız bir adreste</string>
|
||||
<string name="cert_link_title">Bağlantıdan sertifika</string>
|
||||
<string name="cert_link_note">Bir .p12 dosyasının ya da PEM veya DER biçiminde bir sertifika ve anahtarın adresini yapıştırın. Gearslip bunu aracın istediği biçime dönüştürür.</string>
|
||||
<string name="cert_link_label">Sertifika veya .p12 bağlantısı</string>
|
||||
<string name="cert_link_key">Özel anahtarın bağlantısı</string>
|
||||
<string name="cert_link_key_note">Bu, anahtarı olmayan bir sertifika. Anahtarın bağlantısını da yapıştırın.</string>
|
||||
<string name="cert_link_password">Parola</string>
|
||||
<string name="cert_link_password_note">Bu dosyanın parolası var.</string>
|
||||
<string name="cert_link_download">İndir</string>
|
||||
<string name="cert_link_again">Yeniden indir</string>
|
||||
<string name="cert_link_done">İndirildi ve dönüştürüldü. Bir sonraki bağlantıdan itibaren kullanılır.</string>
|
||||
<string name="cert_link_failed">Kullanılamadı. %1$s</string>
|
||||
</resources>
|
||||
@@ -972,4 +972,17 @@
|
||||
<string name="car_apps_as_messaging_note">Його сповіщення, прочитані вголос, із відповідями.</string>
|
||||
<string name="car_apps_added_by_you">Додано вами</string>
|
||||
<string name="car_apps_remove">Видалити %1$s</string>
|
||||
<string name="cert_link">Завантажити за посиланням</string>
|
||||
<string name="cert_link_sub">Файл сертифіката, якому ви довіряєте, за адресою, яку ви вставите</string>
|
||||
<string name="cert_link_title">Сертифікат за посиланням</string>
|
||||
<string name="cert_link_note">Вставте адресу файлу .p12 або сертифіката з ключем у форматі PEM чи DER. Gearslip перетворить його на потрібний авто вигляд.</string>
|
||||
<string name="cert_link_label">Посилання на сертифікат або .p12</string>
|
||||
<string name="cert_link_key">Посилання на закритий ключ</string>
|
||||
<string name="cert_link_key_note">Це сертифікат без ключа. Вставте також посилання на ключ.</string>
|
||||
<string name="cert_link_password">Пароль</string>
|
||||
<string name="cert_link_password_note">Цей файл має пароль.</string>
|
||||
<string name="cert_link_download">Завантажити</string>
|
||||
<string name="cert_link_again">Завантажити знову</string>
|
||||
<string name="cert_link_done">Завантажено й перетворено. Використовуватиметься з наступного підключення.</string>
|
||||
<string name="cert_link_failed">Не вдалося використати. %1$s</string>
|
||||
</resources>
|
||||
@@ -942,4 +942,17 @@
|
||||
<string name="car_apps_as_messaging_note">朗读其通知,并可回复。</string>
|
||||
<string name="car_apps_added_by_you">由你添加</string>
|
||||
<string name="car_apps_remove">移除 %1$s</string>
|
||||
<string name="cert_link">从链接下载</string>
|
||||
<string name="cert_link_sub">从你粘贴的地址下载你信任的证书文件</string>
|
||||
<string name="cert_link_title">从链接获取证书</string>
|
||||
<string name="cert_link_note">粘贴 .p12 文件的地址,或 PEM、DER 格式的证书和密钥的地址。Gearslip 会把它转换成汽车需要的格式。</string>
|
||||
<string name="cert_link_label">证书或 .p12 链接</string>
|
||||
<string name="cert_link_key">私钥链接</string>
|
||||
<string name="cert_link_key_note">这是没有密钥的证书。请同时粘贴密钥的链接。</string>
|
||||
<string name="cert_link_password">密码</string>
|
||||
<string name="cert_link_password_note">此文件有密码。</string>
|
||||
<string name="cert_link_download">下载</string>
|
||||
<string name="cert_link_again">重新下载</string>
|
||||
<string name="cert_link_done">已下载并转换。将在下次连接时使用。</string>
|
||||
<string name="cert_link_failed">无法使用。%1$s</string>
|
||||
</resources>
|
||||
@@ -1009,4 +1009,17 @@
|
||||
<string name="car_apps_as_messaging_note">Its notifications, read aloud, with replies.</string>
|
||||
<string name="car_apps_added_by_you">Added by you</string>
|
||||
<string name="car_apps_remove">Remove %1$s</string>
|
||||
<string name="cert_link">Download from a link</string>
|
||||
<string name="cert_link_sub">A certificate file you trust, at an address you paste</string>
|
||||
<string name="cert_link_title">Certificate from a link</string>
|
||||
<string name="cert_link_note">Paste the address of a .p12 file, or of a certificate and key in PEM or DER. Gearslip converts it to the form the car needs.</string>
|
||||
<string name="cert_link_label">Certificate or .p12 link</string>
|
||||
<string name="cert_link_key">Link to the private key</string>
|
||||
<string name="cert_link_key_note">That\'s a certificate without its key. Paste the key\'s link too.</string>
|
||||
<string name="cert_link_password">Password</string>
|
||||
<string name="cert_link_password_note">This file has a password.</string>
|
||||
<string name="cert_link_download">Download</string>
|
||||
<string name="cert_link_again">Download again</string>
|
||||
<string name="cert_link_done">Downloaded and converted. It\'s used from the next connection.</string>
|
||||
<string name="cert_link_failed">Couldn\'t use it. %1$s</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,56 @@
|
||||
package app.seb3thehacker.gearslip
|
||||
|
||||
import java.io.ByteArrayInputStream
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.security.KeyStore
|
||||
import java.security.PrivateKey
|
||||
import java.util.Base64
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Assert.fail
|
||||
import org.junit.Test
|
||||
|
||||
/** The forms a certificate can arrive in from a link (issue #15), each converted to a usable PKCS#12. */
|
||||
class CertFromLinkTest {
|
||||
|
||||
private val generated = SelfSignedCert.generate()
|
||||
private val key = generated.keyStore.getKey(SelfSignedCert.ALIAS, SelfSignedCert.PASSWORD) as PrivateKey
|
||||
private val certPem = pem("CERTIFICATE", generated.certificate.encoded)
|
||||
private val keyPem = pem("PRIVATE KEY", key.encoded)
|
||||
|
||||
private fun pem(type: String, der: ByteArray) =
|
||||
"-----BEGIN $type-----\n" + Base64.getMimeEncoder(64, "\n".toByteArray()).encodeToString(der) + "\n-----END $type-----\n"
|
||||
|
||||
private fun assertUsable(result: CertFromLink.Converted) {
|
||||
assertTrue("expected a ready file, got $result", result is CertFromLink.Converted.Ready)
|
||||
val ready = result as CertFromLink.Converted.Ready
|
||||
val store = KeyStore.getInstance("PKCS12").apply { load(ByteArrayInputStream(ready.p12), ready.password.toCharArray()) }
|
||||
val alias = store.aliases().asSequence().first { store.isKeyEntry(it) }
|
||||
assertEquals(generated.certificate, store.getCertificate(alias))
|
||||
}
|
||||
|
||||
@Test fun certificateAndKeyInOnePem() = assertUsable(CertFromLink.convert((certPem + keyPem).toByteArray(), null, null))
|
||||
|
||||
@Test fun certificateAloneAsksForTheKey() =
|
||||
assertEquals(CertFromLink.Converted.NeedsKey, CertFromLink.convert(certPem.toByteArray(), null, null))
|
||||
|
||||
@Test fun certificateAndKeyInTwoFiles() = assertUsable(CertFromLink.convert(certPem.toByteArray(), keyPem.toByteArray(), null))
|
||||
|
||||
@Test fun derCertificateAndDerKey() = assertUsable(CertFromLink.convert(generated.certificate.encoded, key.encoded, null))
|
||||
|
||||
@Test fun p12WithPassword() {
|
||||
val bytes = ByteArrayOutputStream().use { generated.keyStore.store(it, "secret".toCharArray()); it.toByteArray() }
|
||||
assertEquals(CertFromLink.Converted.NeedsPassword, CertFromLink.convert(bytes, null, null))
|
||||
assertUsable(CertFromLink.convert(bytes, null, "secret"))
|
||||
}
|
||||
|
||||
@Test fun keyFromAnotherCertificateIsRefused() {
|
||||
val other = SelfSignedCert.generate()
|
||||
val otherKey = other.keyStore.getKey(SelfSignedCert.ALIAS, SelfSignedCert.PASSWORD) as PrivateKey
|
||||
try {
|
||||
CertFromLink.convert(certPem.toByteArray(), pem("PRIVATE KEY", otherKey.encoded).toByteArray(), null)
|
||||
fail("a key from another certificate was accepted")
|
||||
} catch (_: IllegalStateException) {
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user