Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Provider: Signing #94

Merged
merged 73 commits into from
Sep 4, 2024
Merged
Show file tree
Hide file tree
Changes from 52 commits
Commits
Show all changes
73 commits
Select commit Hold shift + click to select a range
6f5c8c4
Signing
iaik-jheher Jul 8, 2024
40b96b5
android fixes + signer cleanup
iaik-jheher Aug 23, 2024
fce2734
attestation
iaik-jheher Aug 23, 2024
25a5b68
look at all of these lines of code that we aren't writing
iaik-jheher Aug 23, 2024
be68019
cleanup xcode artefacts
iaik-jheher Aug 23, 2024
0c66aa4
ios attestation adjustments
iaik-jheher Aug 23, 2024
264e2b3
ephemeral keys
iaik-jheher Aug 26, 2024
15fb42b
ios ephemeral keys
iaik-jheher Aug 26, 2024
47ec231
change defaults for ephemeral keys to be super permissive
iaik-jheher Aug 26, 2024
0dd5845
ios digest cleanup
iaik-jheher Aug 27, 2024
e17a2d6
JKS provider
iaik-jheher Aug 27, 2024
a0641bc
JKS provider
iaik-jheher Aug 27, 2024
cf5a58d
expose ephemeral key internal object
iaik-jheher Aug 27, 2024
8810095
annotate our own usages
iaik-jheher Aug 27, 2024
706959d
ephemerals arrrgh
iaik-jheher Aug 27, 2024
fd13988
Revert "ephemerals arrrgh"
iaik-jheher Aug 27, 2024
d88b319
ephemeral internals but nicer
iaik-jheher Aug 27, 2024
64bb2b9
hazmat cleanup
iaik-jheher Aug 28, 2024
1c5ed2b
experimentals opt-in
iaik-jheher Aug 28, 2024
ce91f4f
missing import
iaik-jheher Aug 28, 2024
56f30a3
bump sumpreme version
JesusMcCloud Aug 28, 2024
7ff5e4b
update kotlin+kotest
JesusMcCloud Aug 28, 2024
c0f9577
platform signer cleanup
iaik-jheher Aug 28, 2024
d0a64a3
KT-71036 workaround
iaik-jheher Aug 28, 2024
961ff67
activity lifecycle callbacks on android
iaik-jheher Aug 28, 2024
10bfc0a
Revert "update kotlin+kotest"
iaik-jheher Aug 28, 2024
522c613
dummy out platform-agnostic provider construction until KT-71036 is f…
iaik-jheher Aug 28, 2024
d75e70b
ios timeout
iaik-jheher Aug 28, 2024
de918b1
don't overwrite existing keys
iaik-jheher Aug 28, 2024
d529b2a
version updates, changelog, ang fixes
JesusMcCloud Aug 28, 2024
b18ea9f
fix jvm compile error
JesusMcCloud Aug 28, 2024
e40c349
partial documentation commit
iaik-jheher Aug 29, 2024
c9c8ae2
fix testcase after rename
iaik-jheher Aug 29, 2024
ee0e0d5
revert partial change that never happened
iaik-jheher Aug 29, 2024
3734d3a
cleanup build files
JesusMcCloud Aug 29, 2024
3bcd70e
update AGP
JesusMcCloud Aug 29, 2024
8468cd3
the documentation commit
iaik-jheher Aug 29, 2024
fd45323
update AGP
JesusMcCloud Aug 29, 2024
c6437b9
WIP
iaik-jheher Aug 29, 2024
9fa0569
build fixes
iaik-jheher Aug 29, 2024
3495542
always pre-hash on ios
iaik-jheher Aug 29, 2024
5b612d1
fix demoapp build
JesusMcCloud Aug 29, 2024
7bef269
the second coming of Signer, iOS edition
iaik-jheher Aug 30, 2024
9b6827f
now also on android
iaik-jheher Aug 30, 2024
6de1296
requested adjustments android edition
iaik-jheher Sep 2, 2024
4086154
requested changes ios edition
iaik-jheher Sep 2, 2024
0d98161
fix ios cancellation detection
iaik-jheher Sep 2, 2024
059a4f3
fix ios cancellation detection
iaik-jheher Sep 2, 2024
d64de45
pin android to single thread
iaik-jheher Sep 2, 2024
b98104c
readme
iaik-jheher Sep 2, 2024
fc72ad0
delete the readme compile tests since @JesusMcCloud deleted the depen…
iaik-jheher Sep 2, 2024
ae1f93b
test case fixes for kotlin 2.0.20
iaik-jheher Sep 2, 2024
ada7600
cleanup demoapp readme
JesusMcCloud Sep 3, 2024
01c2286
cleanup build files
JesusMcCloud Sep 3, 2024
5c6b68a
fix comment in build file
JesusMcCloud Sep 3, 2024
f86e857
fix developmen.md for demo app
JesusMcCloud Sep 3, 2024
eef462b
workaround android keystore bug
iaik-jheher Sep 3, 2024
68ff176
current-set biometry on ios
iaik-jheher Sep 3, 2024
fb96c9e
signatureresult transforming
iaik-jheher Sep 3, 2024
e67557a
star wars
iaik-jheher Sep 3, 2024
da90b11
fix jvm clash
iaik-jheher Sep 3, 2024
a3f9959
add link to readme
iaik-jheher Sep 3, 2024
becb46f
throws annotation as requested
iaik-jheher Sep 3, 2024
7c8293a
Revert "throws annotation as requested" (because it breaks iOS)
iaik-jheher Sep 3, 2024
a7072e1
requested documentation
iaik-jheher Sep 4, 2024
5767057
sign() always returns RawByteEncodable signatures
iaik-jheher Sep 4, 2024
694c2ac
fix andorid
iaik-jheher Sep 4, 2024
beeb94f
demoapp jvm support
iaik-jheher Sep 4, 2024
79275cf
it's-a-read-me, mario
iaik-jheher Sep 4, 2024
c6f0815
Oklahoma
JesusMcCloud Sep 4, 2024
0db13dc
attestation repudiation commit
iaik-jheher Sep 4, 2024
432693d
remove kmp-crypto remnants
JesusMcCloud Sep 4, 2024
910ccc6
fix jks testcase
JesusMcCloud Sep 4, 2024
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
18 changes: 17 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
*/generated
.gradle
demoapp/build
build/
!gradle/wrapper/gradle-wrapper.jar
!**/src/main/**/build/
!**/src/test/**/build/
.kotlin
provider/src/androidInstrumentedTest/kotlin/generated
*/src/androidInstrumentedTest/kotlin/generated

### IntelliJ IDEA ###
.idea
Expand Down Expand Up @@ -40,6 +41,21 @@ bin/

### Mac OS ###
.DS_Store
.Trashes
*.swp
*~.nib
DerivedData/
*.pbxuser
*.mode1v3
*.mode2v3
*.perspectivev3
!default.pbxuser
!default.mode1v3
!default.mode2v3
!default.perspectivev3
*.xccheckout
xcuserdata/
*.moved-aside

### Gradle ###
local.properties
8 changes: 7 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,13 @@

## 3.0

### NEXT
### 3.7.0 (Supreme 0.2.0)
* Implement supreme signing capabilities
* Introduce Attestation Data Structure
* Dependency Updates:
* Kotlin 2.0.20
* kotlinx.serialization 1.7.2 stable (bye, bye unofficial snapshot dependency!)
* kotlinx-datetime 0.6.1

### 3.6.1
* Externalise `UVarInt` to multibase
Expand Down
79 changes: 78 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,83 @@ material._

<br>

### Signature Creation

To create a signature, obtain a `Signer` instance.
You can do this using `Signer.Ephemeral` to create a signer for a throwaway keypair:
```kotlin
val signer = Signer.Ephemeral {}.getOrThrow()
val plaintext = "You have this.".encodeToByteArray()
val signature = signer.sign(plaintext).signature
println("Signed using ${signer.signatureAlgorithm}: $signature")
```

If you want to create multiple signatures using the same ephemeral key, you can obtain an `EphemeralKey` instance, then create signers from it:
```kotlin
val key = EphemeralKey { rsa {} }.getOrThrow()
val sha256Signer = key.getSigner { rsa { digest = Digests.SHA256 } }.getOrThrow()
val sha384Signer = key.getSigner { rsa { digest = Digests.SHA384 } }.getOrThrow()
```

The instances can be configured using the configuration DSL.
Any unspecified parameters use sensible, secure defaults.

#### Platform Signers

On Android and iOS, signers using the systems' secure key storage can be retrieved.
To do this, use `PlatformSigningProvider` (in common code), or interact with `AndroidKeystoreProvider`/`IosKeychainProvider` (in platform-specific code).
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

JKS signer

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

JKS signer is not a PlatformSigningProvider right now

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

added a note


New keys can be created using `createSigningKey(alias: String) { /* configuration */ }`,
and signers for existing keys can be retrieved using `getSignerForKey(alias: String) { /* configuration */ }`.

For example, creating an elliptic-curve key over P256, stored in secure hardware, and with key attestation using a random challenge provided by your server, might be done like this:
```kotlin
val serverChallenge: ByteArray = TODO("This was unpredictably chosen by your server.")
PlatformSigningProvider.createSigningKey(alias = "Swordfish") {
ec {
// you don't even need to specify the curve (P256 is the default) but we'll do it for demonstration purposes
curve = ECCurve.SECP_256_R_1
// you could specify the supported digests explicity - if you do not, the curve's native digest (for P256, this is SHA256) is supported
}
hardware {
JesusMcCloud marked this conversation as resolved.
Show resolved Hide resolved
// you could use PREFERRED if you want the operation to succeed (without hardware backing) on devices that do not support it
backing = REQUIRED
attestation { challenge = serverChallenge }
protection {
timeout = 5.seconds
factors {
biometry = true
deviceLock = false
}
}
}
}
```

If this operation succeeds, it returns a `Signer`. The same `Signer` could later be retrieved using `PlatformSigningProvider.getSignerForKey(alias: String)`.

When you use this `Signer` to sign data, the user would be prompted to authorize the signature using an enrolled fingerprint, because that's what you specified when creating the key.
You can configure the authentication prompt:
```kotlin
val plaintext = "A message".encodeToByteArray()
val signature = signer.sign(plaintext) {
unlockPrompt {
message = "Signing a message to Bobby"
}
}.signature
```
... but you cannot change the fact that you configured this key to need biometry. Consider this when creating your keys.

#### Key Attestation

The Android KeyStore offers key attestation certificates for hardware-backed keys.
These certificates are exposed by the signer's `.attestation` property.

For iOS, Apple does not provide this capability.
We instead piggy-back onto iOS App Attestation to provide a home-brew "key attestation" scheme.
The guarantees are different: you are trusting the OS, not the actual secure hardware; and you are trusting that our library properly interfaces with the OS.
Attestation types are serializable for transfer, and correspond to those in Indispensable's attestation module.

### Signature Verification

To verify a signature, obtain a `Verifier` instance using `verifierFor(k: PublicKey)`, either directly on a `SignatureAlgorithm`, or on one of the specialized algorithms (`X509SignatureAlgorithm`, `CoseAlgorithm`, ...).
Expand All @@ -108,7 +185,7 @@ As an example, here's how to verify a basic signature using a public key:
```kotlin
val publicKey: CryptoPublicKey.EC = TODO("You have this and trust it.")
val plaintext = "You want to trust this.".encodeToByteArray()
val signature = TODO("This was sent alongside the plaintext.")
val signature: CryptoSignature = TODO("This was sent alongside the plaintext.")
val verifier = SignatureAlgorithm.ECDSAwithSHA256.verifierFor(publicKey).getOrThrow()
val isValid = verifier.verify(plaintext, signature).isSuccess
println("Looks good? $isValid")
Expand Down
4 changes: 2 additions & 2 deletions build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import org.jetbrains.dokka.gradle.DokkaMultiModuleTask

plugins {
id("at.asitplus.gradle.conventions") version "2.0.0+20240725"
id("com.android.library") version "8.2.0" apply (false)
id("at.asitplus.gradle.conventions") version "2.0.20+20240829"
id("com.android.library") version "8.2.2" apply (false)
}
group = "at.asitplus.signum"

Expand Down
6 changes: 6 additions & 0 deletions demoapp/DEVELOPMENT.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
**REQUIRES a MacOS Host to build all modules**

* recursively clone this repo
* set `sdk.dir=/absulute/path/to/Android/sdk` inside `kmp-crypto/local.properties`
* `cd kmp-crypto && ./gradlew publishAllPublicationsToLocalRepository`
iaik-jheher marked this conversation as resolved.
Show resolved Hide resolved
JesusMcCloud marked this conversation as resolved.
Show resolved Hide resolved
* import the this project into Android studio
39 changes: 39 additions & 0 deletions demoapp/README.MD
JesusMcCloud marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# KMP Crypto Demo Multiplatform (iOS, Android JVM) App


![img.png](img.png)

This app showcases the KMP Crypto provider on the desktop, on Android and on iOS.
It is possible to generate key pairs, sign data, and verify the signature.

On iOS, only P-256 keys can be attested.
The JVM does not support attestation.

`*** TODO CLEANUP BELOW THIS LINE ***`

Android and iOS support mandatory authentication for key usage. While somewhat similar functionality can be achieved on both platforms, iOS comes with some peculiarities.
Most prominently: It is possible to specify a max validity duration of an authentication context. This property, however, has jwf semantics, since a once authenticated LAContext will remain in this state for eternety.
Hence, reauthentication needs to be implemented manually.

The BiometricAuthAdapter on Android requires some more polishing, but it should clearly convey the underlying idea even in its current state.

## Before running!
- check your system with [KDoctor](https://github.com/Kotlin/kdoctor)
- install JDK 17 on your machine
- add `local.properties` file to the project root and set a path to Android SDK there

### Android
To run the application on android device/emulator:
- open project in Android Studio and run imported android run configuration

To build the application bundle:
- run `./gradlew :composeApp:assembleDebug`
- find `.apk` file in `composeApp/build/outputs/apk/debug/composeApp-debug.apk`

### iOS
To run the application on iPhone device/simulator:
- Open `iosApp/iosApp.xcproject` in Xcode and run standard configuration
- Or use [Kotlin Multiplatform Mobile plugin](https://plugins.jetbrains.com/plugin/14936-kotlin-multiplatform-mobile) for Android Studio



17 changes: 17 additions & 0 deletions demoapp/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
plugins {
alias(libs.plugins.multiplatform).apply(false)
alias(libs.plugins.compose).apply(false)
alias(libs.plugins.android.application).apply(false)
alias(libs.plugins.buildConfig).apply(false)
// id("at.asitplus.gradle.conventions") version "1.9.23+20240319+1"
JesusMcCloud marked this conversation as resolved.
Show resolved Hide resolved
}

allprojects {
repositories {
maven(rootProject.projectDir.absolutePath+"/kmp-crypto/repo")
maven("https://s01.oss.sonatype.org/content/repositories/snapshots")
maven(uri("https://raw.githubusercontent.com/a-sit-plus/kotlinx.serialization/mvn/repo"))
JesusMcCloud marked this conversation as resolved.
Show resolved Hide resolved
mavenCentral()
google()
}
}
111 changes: 111 additions & 0 deletions demoapp/composeApp/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import com.android.build.api.dsl.Packaging

plugins {
alias(libs.plugins.multiplatform)
alias(libs.plugins.compose)
alias(libs.plugins.compose.runtime)
alias(libs.plugins.android.application)
alias(libs.plugins.buildConfig)
}

kotlin {
jvm()
jvmToolchain(17)
androidTarget {
compilations.all {
kotlinOptions {
jvmTarget = "17"
}
}
}

listOf(
iosX64(),
iosArm64(),
iosSimulatorArm64()
).forEach {
it.binaries.framework {
baseName = "ComposeApp"
isStatic = true
}
}

sourceSets {
all {
languageSettings {
optIn("org.jetbrains.compose.resources.ExperimentalResourceApi")
}
}
commonMain.dependencies {
implementation("at.asitplus.signum:supreme:0.2.0-SNAPSHOT") {
isChanging = true
}
implementation(compose.runtime)
implementation(compose.material3)
implementation(compose.materialIconsExtended)
@OptIn(org.jetbrains.compose.ExperimentalComposeLibrary::class)
implementation(compose.components.resources)
implementation(libs.voyager.navigator)
implementation(libs.composeImageLoader)
implementation(libs.napier)
implementation(libs.kotlinx.coroutines.core)
}

commonTest.dependencies {
implementation(kotlin("test"))
}

androidMain.dependencies {
implementation(libs.androidx.appcompat)
implementation(libs.androidx.activityCompose)
implementation(libs.compose.uitooling)
implementation(libs.kotlinx.coroutines.android)
implementation(libs.androidx.biometric)
}


jvmMain.dependencies {
implementation(compose.desktop.currentOs)
}

}
}

android {
namespace = "at.asitplus.cryptotest"
compileSdk = 34

defaultConfig {
minSdk = 33
targetSdk = 34
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

note to self: check requirements


applicationId = "at.asitplus.cryptotest.androidApp"
versionCode = 1
versionName = "1.0.0"
}
sourceSets["main"].apply {
manifest.srcFile("src/androidMain/AndroidManifest.xml")
res.srcDirs("src/androidMain/resources")
resources.srcDirs("src/commonMain/resources")
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
buildFeatures {
compose = true
}
composeOptions {
kotlinCompilerExtensionVersion = "1.5.4"
JesusMcCloud marked this conversation as resolved.
Show resolved Hide resolved
}

packaging {
resources.excludes.add("META-INF/versions/9/OSGI-INF/MANIFEST.MF")
}
}

buildConfig {
// BuildConfig configuration here.
// https://github.com/gmazzo/gradle-buildconfig-plugin#usage-in-kts
}

22 changes: 22 additions & 0 deletions demoapp/composeApp/src/androidMain/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">

<application
android:name=".AndroidApp"
android:icon="@android:drawable/ic_menu_compass"
android:label="CryptoTest App"
android:theme="@style/Theme.AppCompat.DayNight.NoActionBar">
<activity
android:name=".AppActivity"
android:configChanges="orientation|screenSize|screenLayout|keyboardHidden"
android:launchMode="singleInstance"
android:windowSoftInputMode="adjustPan"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>

</manifest>
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package at.asitplus.cryptotest

import android.app.Application
import android.os.Bundle
import androidx.activity.compose.setContent
import androidx.fragment.app.FragmentActivity

class AndroidApp : Application()

class AppActivity : FragmentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
App()
}
}
}
Loading
Loading