Skip to content

Commit fc120c7

Browse files
authored
Introduce DeviceAttestationSoftware. (#1663)
Right now our DeviceCheck machinery only works on real iOS devices (excluding the iOS simulator) and Android devices (real or emulator) and we need them to work for the web (for the web version of Multipaz Wallet) and it would also be nice if they worked for the iOS simulator. As such, introduce `DeviceAttestationSoftware` which can be generated in any environment which lacks hardware-backed attestation. This is inherently insecure but the backend has access to the attestation so it can provide a subset of functionality to e.g. web clients. For example, for Multipaz Wallet we can allow access to shared state (bound to e.g. a Google account) for web clients but refuse access to e.g. key attestation services. Also introduce the notion of a "proof of secret" embedded in this new attestation. This is inhererently insecure (an attacker can exfiltrate this client-side secret from the binary or app process) and HW-backed attestation should be preferred but in an environment without HW-backed attestation this adds a little bit of security. Change DeviceCheck for JVM so it uses `DeviceAttestationSoftware` and also DeviceCheck for iOS when running on the iOS simulator. This way it's actually possible to develop Multipaz Wallet using the iOS simulator when using its dev backend configured to accept software attestations. Add a new screen to Compose TestApp to easily test both device attestations and assertions, both strict and non-strict. This also uncovered a bug in `ClientRegistrationImpl` where we would always generate empty challenges. Fix this. Test: Unit tests for DeviceAttestationSoftware. Test: Manually tested on Android and iOS. Signed-off-by: David Zeuthen <zeuthen@google.com>
1 parent ad8b85c commit fc120c7

27 files changed

Lines changed: 649 additions & 87 deletions

File tree

multipaz-compose/build.gradle.kts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,8 @@ kotlin {
7878
it.binaries.all {
7979
linkerOpts(
8080
"-L/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/swift/${platform}/",
81-
"-Wl,-rpath,/usr/lib/swift"
81+
"-Wl,-rpath,/usr/lib/swift",
82+
"-lsqlite3"
8283
)
8384
}
8485
}

multipaz-csa/src/main/java/org/multipaz/securearea/cloud/CloudSecureAreaServer.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -176,13 +176,15 @@ class CloudSecureAreaServer(
176176
request1.deviceAttestation.validate(
177177
DeviceAttestationValidationData(
178178
attestationChallenge = state.attestationChallenge!!,
179+
softwareAccepted = false,
180+
softwareSecrets = emptySet(),
179181
iosReleaseBuild = iosReleaseBuild,
180182
iosAppIdentifiers = iosAppIdentifiers.toSet(),
181183
androidGmsAttestation = androidGmsAttestation,
182184
androidVerifiedBootGreen = androidVerifiedBootGreen,
183185
androidAppSignatureCertificateDigests = androidAppSignatureCertificateDigests.toSet(),
184186
androidAppPackageNames = androidAppPackageNames.toSet(),
185-
androidRequiredKeyMintSecurityLevel = androidKeystoreSecurityLevel
187+
androidRequiredKeyMintSecurityLevel = androidKeystoreSecurityLevel,
186188
)
187189
)
188190
} catch (e: Exception) {

multipaz-dcapi/build.gradle.kts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,8 @@ kotlin {
6060
it.binaries.all {
6161
linkerOpts(
6262
"-L/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/swift/${platform}/",
63-
"-Wl,-rpath,/usr/lib/swift"
63+
"-Wl,-rpath,/usr/lib/swift",
64+
"-lsqlite3"
6465
)
6566
}
6667
}

multipaz-doctypes/build.gradle.kts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,8 @@ kotlin {
5252
it.binaries.all {
5353
linkerOpts(
5454
"-L/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/swift/${platform}/",
55-
"-Wl,-rpath,/usr/lib/swift"
55+
"-Wl,-rpath,/usr/lib/swift",
56+
"-lsqlite3"
5657
)
5758
}
5859
}

multipaz-longfellow/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ kotlin {
8282
linkerOpts(
8383
"-L$rootDir/multipaz-longfellow/src/iosMain/nativeLibs/$zkLibExt/lib",
8484
"-Wl,-rpath,/usr/lib/swift",
85+
"-lsqlite3",
8586
"-lmdoc_static"
8687
)
8788
}

multipaz-swiftui/build.gradle.kts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,8 @@ kotlin {
5252
it.binaries.all {
5353
linkerOpts(
5454
"-L/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/swift/${platform}/",
55-
"-Wl,-rpath,/usr/lib/swift"
55+
"-Wl,-rpath,/usr/lib/swift",
56+
"-lsqlite3"
5657
)
5758
}
5859
}

multipaz/build.gradle.kts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,8 @@ kotlin {
9696
"-L/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/swift/${platform}/",
9797
"-L$rootDir/multipaz/SwiftBridge/build/Release-${platform}/",
9898
"-lSwiftBridge",
99-
"-Wl,-rpath,/usr/lib/swift"
99+
"-Wl,-rpath,/usr/lib/swift",
100+
"-lsqlite3"
100101
)
101102
}
102103
}

multipaz/src/androidMain/kotlin/org/multipaz/device/DeviceCheck.android.kt

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,34 @@
11
package org.multipaz.device
22

3+
import kotlinx.io.bytestring.ByteString
4+
import kotlinx.io.bytestring.isEmpty
35
import org.multipaz.securearea.AndroidKeystoreCreateKeySettings
4-
import org.multipaz.crypto.Algorithm
56
import org.multipaz.securearea.SecureArea
6-
import kotlinx.io.bytestring.ByteString
7+
import org.multipaz.util.Logger
78

89
/**
910
* Generates statements validating device/app/OS integrity. Details of these
1011
* statements are inherently platform-specific.
1112
*/
1213
actual object DeviceCheck {
14+
private const val TAG = "DeviceCheck"
15+
1316
actual suspend fun generateAttestation(
1417
secureArea: SecureArea,
15-
challenge: ByteString
18+
challenge: ByteString,
19+
secret: String?
1620
): DeviceAttestationResult {
21+
if (challenge.isEmpty()) {
22+
Logger.w(TAG, "Generating an attestation with an empty challenge is not secure")
23+
}
1724
val keySettings = AndroidKeystoreCreateKeySettings.Builder(challenge)
1825
.build()
1926
val keyInfo = secureArea.createKey(null, keySettings)
2027
return DeviceAttestationResult(
2128
deviceAttestationId = keyInfo.alias,
22-
deviceAttestation = DeviceAttestationAndroid(keyInfo.attestation.certChain!!)
29+
deviceAttestation = DeviceAttestationAndroid(
30+
certificateChain = keyInfo.attestation.certChain!!,
31+
)
2332
)
2433
}
2534

multipaz/src/commonMain/kotlin/org/multipaz/device/DeviceAttestation.kt

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,14 @@ import kotlin.time.Clock
55
import kotlin.time.Instant
66

77
/**
8-
* A platform-issued statement vouching for the integrity of the wallet app.
8+
* A platform-issued statement vouching for the integrity of an application.
99
*
1010
* Validity checks are cross-platform, as we need to be able to run them on the server
1111
* (e.g. one does not have to be on iOS to validate [DeviceAttestationIos]).
12+
*
13+
* For platforms without dedicated support, [DeviceAttestationSoftware] exists
14+
* which relies on the ability of the application to prove that it possesses
15+
* a secret.
1216
*/
1317
@CborSerializable
1418
sealed class DeviceAttestation {

multipaz/src/commonMain/kotlin/org/multipaz/device/DeviceAttestationAndroid.kt

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,20 @@ import kotlinx.coroutines.CancellationException
44
import org.multipaz.crypto.Algorithm
55
import org.multipaz.crypto.Crypto
66
import org.multipaz.crypto.EcSignature
7+
import org.multipaz.crypto.SignatureVerificationException
78
import org.multipaz.crypto.X509CertChain
89
import org.multipaz.util.validateAndroidKeyAttestation
9-
import org.multipaz.crypto.SignatureVerificationException
1010
import kotlin.time.Instant
1111

1212
/**
1313
* On Android we create a private key in secure area and use its key attestation as the
1414
* device attestation.
15+
*
16+
* @property certificateChain the certificate chain.
1517
*/
1618
data class DeviceAttestationAndroid(
1719
val certificateChain: X509CertChain
18-
) : DeviceAttestation() {
20+
): DeviceAttestation() {
1921
override suspend fun validate(
2022
validationData: DeviceAttestationValidationData,
2123
validateAt: Instant

0 commit comments

Comments
 (0)