certificate: say where it comes from, offer import alongside download, keep cert files out of the repo

This commit is contained in:
Seb3thehacker committed 2026-10-08 15:25:04 -07:00
1 parent 246f571a86
commit a549077c13
4 files changed
+39 -18

No files matched your search

+8 -1
View File
@@ -30,4 +30,11 @@ CLAUDE.md
# Other # Other
archive/ archive/
.wrangler/ .wrangler/
stats-worker/ stats-worker/
# Certificates and keys never go in the repo; the app downloads or imports them on the phone.
*.p12
*.pfx
*.pem
*.crt
*.key
+11
View File
@@ -118,6 +118,17 @@ keystore. Without them, the debug build still works.
[docs/BUILDING_APPS.md](docs/BUILDING_APPS.md) specifies how to write apps that Gearslip [docs/BUILDING_APPS.md](docs/BUILDING_APPS.md) specifies how to write apps that Gearslip
can render: templated apps, media apps, and the Gearslip app protocol. can render: templated apps, media apps, and the Gearslip app protocol.
## How Gearslip talks to cars
Gearslip is an independent program, written to work with car head units. What it knows of
the protocol comes from public open-source projects such as
[aasdk](https://github.com/opencardev/aasdk) and open-android-auto, and it contains none
of Google's Android Auto code.
Gearslip ships without a certificate. On first launch you choose where yours comes from:
download the one the aasdk project publishes, or import a file of your own. The
certificate stays on your phone. Gearslip never uploads, backs up, or logs it.
## Trademarks ## Trademarks
Android, Android Auto, Google Messages, and Waze are trademarks of Google LLC. Spotify is Android, Android Auto, Google Messages, and Waze are trademarks of Google LLC. Spotify is
@@ -60,7 +60,6 @@ fun CertSetupScreen(onDone: () -> Unit) {
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()
var downloadState by remember { mutableStateOf<DownloadState>(DownloadState.Idle) } var downloadState by remember { mutableStateOf<DownloadState>(DownloadState.Idle) }
var showImport by remember { mutableStateOf(false) }
var pendingUri by remember { mutableStateOf<Uri?>(null) } var pendingUri by remember { mutableStateOf<Uri?>(null) }
var manualProgress by remember { mutableStateOf(false) } var manualProgress by remember { mutableStateOf(false) }
@@ -91,7 +90,7 @@ fun CertSetupScreen(onDone: () -> Unit) {
if (installed) { if (installed) {
"Cars accept only Google-issued certificates, and you have one loaded." "Cars accept only Google-issued certificates, and you have one loaded."
} else { } else {
"Cars accept only Google-issued certificates. Download one to connect." "Cars accept only Google-issued certificates. Gearslip doesn't include one, so choose where yours comes from."
}, },
style = MaterialTheme.typography.bodyLarge, style = MaterialTheme.typography.bodyLarge,
textAlign = TextAlign.Center, textAlign = TextAlign.Center,
@@ -131,10 +130,11 @@ fun CertSetupScreen(onDone: () -> Unit) {
}, },
modifier = Modifier.fillMaxWidth().height(56.dp), modifier = Modifier.fillMaxWidth().height(56.dp),
) { ) {
Text("Download certificate") Text("Download from aasdk")
} }
Text( Text(
"Works with most cars built before 2020. Newer cars reject it.", "The open-source aasdk project publishes this certificate at github.com/opencardev/aasdk. " +
"Most cars accept it; some from around 2020 on don't.",
style = MaterialTheme.typography.bodySmall, style = MaterialTheme.typography.bodySmall,
textAlign = TextAlign.Center, textAlign = TextAlign.Center,
color = MaterialTheme.colorScheme.onSurfaceVariant, color = MaterialTheme.colorScheme.onSurfaceVariant,
@@ -177,16 +177,18 @@ fun CertSetupScreen(onDone: () -> Unit) {
Spacer(Modifier.height(16.dp)) Spacer(Modifier.height(16.dp))
if (showImport) { OutlinedButton(
OutlinedButton( onClick = { importLauncher.launch(arrayOf("*/*")) },
onClick = { importLauncher.launch(arrayOf("*/*")) }, modifier = Modifier.fillMaxWidth().height(52.dp),
modifier = Modifier.fillMaxWidth().height(52.dp), ) { Text("Import my own file") }
) { Text("Import your own .p12 cert") } Text(
Spacer(Modifier.height(16.dp)) "A .p12 file with its private key, from wherever you choose.",
} else { style = MaterialTheme.typography.bodySmall,
TextButton(onClick = { showImport = true }) { Text("Import your own certificate instead") } textAlign = TextAlign.Center,
Spacer(Modifier.height(8.dp)) color = MaterialTheme.colorScheme.onSurfaceVariant,
} modifier = Modifier.padding(top = 8.dp),
)
Spacer(Modifier.height(16.dp))
OutlinedButton( OutlinedButton(
onClick = { onClick = {
@@ -208,7 +208,7 @@ private fun CertificateSection() {
} }
SettingsDivider() SettingsDivider()
SettingsRow( SettingsRow(
if (downloading) "Downloading…" else "Download certificate", if (downloading) "Downloading…" else "Download from aasdk",
Modifier.clickable(enabled = !downloading) { Modifier.clickable(enabled = !downloading) {
downloading = true downloading = true
scope.launch { scope.launch {
@@ -222,13 +222,14 @@ private fun CertificateSection() {
} }
} }
}, },
subtitle = "The certificate the open-source aasdk project publishes on GitHub. Gearslip doesn't include one",
icon = Icons.Filled.Refresh, icon = Icons.Filled.Refresh,
) )
SettingsDivider() SettingsDivider()
SettingsRow( SettingsRow(
"Import a certificate", "Import my own file",
Modifier.clickable { picker.launch(arrayOf("*/*")) }, Modifier.clickable { picker.launch(arrayOf("*/*")) },
subtitle = "A .p12 file with its private key", subtitle = "A .p12 file with its private key, from wherever you choose",
icon = Icons.Filled.Add, icon = Icons.Filled.Add,
) )
val removable = when (s?.kind) { val removable = when (s?.kind) {