Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
cd30089
update libs
mariobodemann Apr 29, 2025
8af919d
remove build config
mariobodemann Apr 29, 2025
22a2166
add hints, select credentials based on them
mariobodemann Apr 29, 2025
98a2c1b
typo in release notes
mariobodemann Apr 30, 2025
e62739c
update version
mariobodemann Apr 30, 2025
1f50baa
update gh actions reflecting module changes
mariobodemann Apr 30, 2025
fedc52d
fix: use existing library version
mariobodemann Apr 30, 2025
eaa8e15
add debug override for hints
mariobodemann Apr 30, 2025
522e823
fix: hide url option in debug menu
mariobodemann Apr 30, 2025
e8cab5a
debug: move issue reporting to github issues
mariobodemann Apr 30, 2025
2fa59c5
fix: do not crash if no nfc device present
mariobodemann Apr 30, 2025
4fef6c9
fix: quieter warning about hints not valid
mariobodemann Apr 30, 2025
b6cf28d
fix: issue reporting truncates output
mariobodemann Apr 30, 2025
5e15386
cleanup debug menu options
mariobodemann Apr 30, 2025
9311201
polish: send feedback email
mariobodemann May 2, 2025
915eefe
add softauthn back as debug option
mariobodemann May 2, 2025
0cc9f0a
bas64Url >> hex | base64
mariobodemann May 2, 2025
1e5cd71
polish: harmonize access to options in softauthn
mariobodemann May 2, 2025
6e0a96f
increase readabillity for hints extraction
mariobodemann May 2, 2025
142bf8c
polish: remove custom theming, color systembar correctly
mariobodemann May 2, 2025
da2e6a5
polish: refactor main activity, split into focused classes
mariobodemann May 2, 2025
efa38c8
polish: android test update
mariobodemann May 2, 2025
a6d1c94
enforce debug menu only on debug
mariobodemann May 2, 2025
84518c0
polish: wordsmithing on issue reporting
mariobodemann May 2, 2025
1fcd234
polish: respect device ui mode
mariobodemann May 9, 2025
14226c0
polish: typo fix
mariobodemann May 9, 2025
d25d972
feature: add credential selection
mariobodemann May 9, 2025
2bf188a
polish: cleanup http logs
mariobodemann May 9, 2025
99f605a
build script: build release and debug and remove warnigns
mariobodemann May 9, 2025
435fdbb
polish: remove unused dependency (accompanist)
mariobodemann May 9, 2025
4518033
remove debug menu into debug build
mariobodemann May 9, 2025
b27cd18
polish: remove warnings
mariobodemann May 9, 2025
0bef585
update release notes
mariobodemann May 9, 2025
92ac324
build: minify release build
mariobodemann May 9, 2025
a1bdcf6
polish: remove uneeded duplication and dependency providers
mariobodemann May 9, 2025
1e60e7b
Introduce buildSrc and move release notes there
mariobodemann May 9, 2025
be8b959
add fingerprint checking (server and apk)
mariobodemann May 9, 2025
985f0f8
use env vars and check fingerprints on build
mariobodemann May 9, 2025
570356f
testing: add android tools to build
mariobodemann May 9, 2025
c0f39bc
add secrets as envvars
mariobodemann May 9, 2025
3baf7f7
ci: move secrets into jobs
mariobodemann May 9, 2025
994db2b
ci: update before install android sdk
mariobodemann May 9, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 17 additions & 2 deletions .github/workflows/push.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,23 @@ on:
jobs:
push:
runs-on: ubuntu-latest
env:
WWWALLET_ANDROID_KEY_ALIAS: ${{ secrets.WWWALLET_ANDROID_KEY_ALIAS }}
WWWALLET_ANDROID_KEY_PASSWORD: ${{ secrets.WWWALLET_ANDROID_KEY_PASSWORD }}
WWWALLET_ANDROID_STORE_PASSWORD: ${{ secrets.WWWALLET_ANDROID_STORE_PASSWORD }}
WWWALLET_ANDROID_STORE_B64: ${{ secrets.WWWALLET_ANDROID_STORE_B64 }}
WWWALLET_ANDROID_HOST: ${{ secrets.WWWALLET_ANDROID_HOST }}

steps:
- name: Update ubuntu
run: sudo apt-get update

- name: Install Android SDK
run: sudo apt-get install -y android-sdk

- name: Try and find android build-tools
run: apksigner

- name: Enable KVM
run: |
echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules
Expand All @@ -40,8 +55,8 @@ jobs:
distribution: 'temurin'
java-version: '21'

- name: Set up Gradle
uses: gradle/actions/setup-gradle@v4
- name: Test
run: ./gradlew clean assemble check checkFingerPrints

- name: Run Connected Tests
uses: reactivecircus/android-emulator-runner@v2
Expand Down
32 changes: 18 additions & 14 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,20 @@ on:
jobs:
release:
runs-on: ubuntu-latest
env:
WWWALLET_ANDROID_KEY_ALIAS: ${{ secrets.WWWALLET_ANDROID_KEY_ALIAS }}
WWWALLET_ANDROID_KEY_PASSWORD: ${{ secrets.WWWALLET_ANDROID_KEY_PASSWORD }}
WWWALLET_ANDROID_STORE_PASSWORD: ${{ secrets.WWWALLET_ANDROID_STORE_PASSWORD }}
WWWALLET_ANDROID_STORE_B64: ${{ secrets.WWWALLET_ANDROID_STORE_B64 }}
WWWALLET_ANDROID_HOST: ${{ secrets.WWWALLET_ANDROID_HOST }}

steps:
- name: Update ubuntu
run: sudo apt-get update

- name: Install Android SDK
run: sudo apt-get install -y android-sdk

- name: Checkout code
uses: actions/checkout@v4

Expand All @@ -38,7 +50,10 @@ jobs:
run: ./gradlew clean check

- name: Assemble all APKs
run: ./gradlew createReleaseNotes build
run: ./gradlew createReleaseNotes assemble

- name: Check
run: ./gradlew check checkFingerPrints

- name: Create GitHub release (with notes)
id: create_release
Expand All @@ -57,7 +72,7 @@ jobs:
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: ./webview/build/outputs/apk/release/webview-release.apk
asset_name: funke-wwwallet-${{ github.ref_name }}.apk
asset_name: wwwallet-${{ github.ref_name }}.apk
asset_content_type: application/vnd.android.package-archive

- name: Upload Debug APK
Expand All @@ -67,16 +82,5 @@ jobs:
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: ./webview/build/outputs/apk/debug/webview-debug.apk
asset_name: funke-wwwallet-debug-${{ github.ref_name }}.apk
asset_name: wwwallet-debug-${{ github.ref_name }}.apk
asset_content_type: application/vnd.android.package-archive

- name: Upload Yubkit APK
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: ./webview/build/outputs/apk/yubikit/webview-yubikit.apk
asset_name: funke-wwwallet-yubikit-${{ github.ref_name }}.apk
asset_content_type: application/vnd.android.package-archive

11 changes: 10 additions & 1 deletion RELEASE_NOTES.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,15 @@
# 0.0.15

* stability improvements
* add 'hints' to select from platform authenticator (android sdk) or security key authenticator (yubikit sdk).
* align night / day mode with phone selection
* add credential selection for multiples
* cleanup
* use debug build for debug menu and issue reporting

# 0.0.14

* fix for PRF exention
* fix for PRF extension
* github action for building, testing and releasing
* available: release, debug, yubikit apks.

Expand Down
70 changes: 22 additions & 48 deletions build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,59 +1,17 @@
import groovy.json.JsonSlurper
import java.io.ByteArrayOutputStream
import java.net.URI
import build.env
import build.getApkFingerprints
import build.getLogs
import build.getReleaseQuoteAndAuthor
import build.getServerFingerprints

// Top-level build file where you can add configuration options common to all sub-projects/modules.
plugins {
`kotlin-dsl` apply false
alias(libs.plugins.android.application) apply false
alias(libs.plugins.jetbrains.kotlin.android) apply false
alias(libs.plugins.compose.compiler) apply false
}

fun runCommand(command: String): String {
val output = ByteArrayOutputStream()

val result: ExecResult? = exec {
commandLine = listOf("sh", "-c", command)
standardOutput = output
}.assertNormalExitValue()

if (result?.exitValue == 0) {
return output.toString().lines().filter { it.isNotBlank() }.joinToString("\n")
}

throw IllegalStateException("Command '${command}' return exit value: ${result?.exitValue}.")
}

fun getLogs(): String {
val lastTag = runCommand("git tag --sort=taggerdate | tail -2 | head -1")
val start = if (lastTag.isBlank()) {
val first = runCommand("git rev-list HEAD | tail -1")
first
} else {
lastTag
}
val end = "HEAD"

return runCommand("git log $start..$end --format=\"%s\"")
}

fun getReleaseQuoteAndAuthor(): Pair<String, String> {
val body = JsonSlurper().parse(URI.create("https://zenquotes.io/api/random").toURL())
var quote: String? = null
var author: String? = null

val item = (body as? List<*>)?.first() as? Map<String, String>?
if (item?.contains("q") == true) {
quote = item["q"]
}

if (item?.contains("a") == true) {
author = item["a"]
}

return (quote ?: "") to (author ?: "")
}

tasks.register("createReleaseNotes") {
description = "Create a release log of all recent commits."

Expand Down Expand Up @@ -84,6 +42,22 @@ tasks.register("createReleaseNotes") {
}
}

tasks.register("checkFingerprints") {
description = "Check wether apk sha256fingerprint is wellknown files on server."

val host = env("WWWALLET_ANDROID_HOST")
doLast {
val fingers = getServerFingerprints(host)
val apkFingered = getApkFingerprints()
for ((apk, finger) in apkFingered) {
if (finger in fingers) {
println("Signature $finger of $apk found on $host.")
} else {
throw GradleException("Could not find signature '$finger' of '$apk' on server '$host'. ")
}
}
}
}

group = "yubico.labs"
version = findProperty("wallet.versionName")!!
1 change: 1 addition & 0 deletions buildSrc/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/build
7 changes: 7 additions & 0 deletions buildSrc/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
plugins {
`kotlin-dsl`
}

kotlin {
jvmToolchain(21)
}
20 changes: 20 additions & 0 deletions buildSrc/settings.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
pluginManagement {
repositories {
google {
content {
includeGroupByRegex("com\\.android.*")
includeGroupByRegex("com\\.google.*")
includeGroupByRegex("androidx.*")
}
}
mavenCentral()
gradlePluginPortal()
}
}

dependencyResolutionManagement {
repositories {
google()
mavenCentral()
}
}
43 changes: 43 additions & 0 deletions buildSrc/src/main/java/build/EnvUtil.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package build

import org.gradle.api.Action
import org.gradle.api.GradleException
import org.gradle.api.Project
import org.gradle.process.ExecResult
import java.io.ByteArrayOutputStream
import java.io.File
import java.util.Base64

fun Project.env(name: String): String {
val variable = System.getenv(name)

if (variable.isNullOrBlank()) {
throw GradleException("Environment variable '$name' not set or blank. Check build settings.")
} else {
return variable
}
}

fun Project.fileFromEnv(project: Project, envName: String, fileName: String): File {
val envVar = env(envName)
val bytes = Base64.getDecoder().decode(envVar)
val file = project.rootProject.file(fileName)
file.createNewFile()
file.writeBytes(bytes)
return file
}

fun Project.runCommand(command: String): String {
val output = ByteArrayOutputStream()

val result: ExecResult? = exec(Action {
commandLine = listOf("sh", "-c", command)
standardOutput = output
}).assertNormalExitValue()

if (result?.exitValue == 0) {
return output.toString().lines().filter { it.isNotBlank() }.joinToString("\n")
}

throw IllegalStateException("Command '${command}' return exit value: ${result?.exitValue}.")
}
47 changes: 47 additions & 0 deletions buildSrc/src/main/java/build/FingerPrintUtil.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package build

import groovy.json.JsonSlurper
import org.gradle.api.GradleException
import org.gradle.api.Project
import java.net.URI

fun Project.getServerFingerprints(host: String): List<String> {
val body = JsonSlurper().parse(URI.create("$host/.well-known/assetlinks.json").toURL())
val item = (body as? List<*>)?.first() as? Map<*, *>?
if (item != null) {
val target = item["target"] as? Map<*, *>?
val fingerprints = target?.get("sha256_cert_fingerprints") as? List<*>
if (fingerprints != null) {
return fingerprints.mapNotNull {
(it as? String)?.replace(":", "")
}
}
}

return emptyList()
}

fun Project.getApkFingerprints(): Map<String, String> {
val apksignerFound = runCommand("command apksigner")
if (apksignerFound.contains("command not found")) {
throw GradleException("Couldn't find 'apksigner'.")
}

val apks = runCommand("find . -iname '*apk' -type f").lines()
if (apks.isEmpty()) {
throw GradleException("No apks found, please 'assemble' first.")
}

return apks.associate { apk ->
apk to (runCommand("apksigner verify --print-certs $apk").lines().mapNotNull {
if (it.contains("SHA-256")) {
it.split(":")
.last()
.trim()
.uppercase()
} else {
null
}
}.joinToString(","))
}
}
36 changes: 36 additions & 0 deletions buildSrc/src/main/java/build/ReleaseNoteUtil.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package build

import groovy.json.JsonSlurper
import org.gradle.api.Project
import java.net.URI

fun Project.getLogs(): String {
val lastTag = runCommand("git tag --sort=taggerdate | tail -2 | head -1")
val start = if (lastTag.isBlank()) {
val first = runCommand("git rev-list HEAD | tail -1")
first
} else {
lastTag
}
val end = "HEAD"

return runCommand("git log $start..$end --format=\"%s\"")
}

fun Project.getReleaseQuoteAndAuthor(): Pair<String, String> {
val body = JsonSlurper().parse(URI.create("https://zenquotes.io/api/random").toURL())
var quote = ""
var author = ""

val item = (body as? List<*>)?.first() as? Map<*, *>?
if (item?.contains("q") == true) {
quote = item["q"] as String? ?: "<parser error>"
}

if (item?.contains("a") == true) {
author = item["a"] as String? ?: "<parser error>"
}

return quote to author
}

4 changes: 2 additions & 2 deletions gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -22,5 +22,5 @@ kotlin.code.style=official
# thereby reducing the size of the R class for that library
android.nonTransitiveRClass=true

wallet.versionCode = 14
wallet.versionName = 0.0.14
wallet.versionCode = 15
wallet.versionName = 0.0.15
Loading