Skip to content

Commit

Permalink
Misc wallet server changes. (#615)
Browse files Browse the repository at this point in the history
- Introduce settings to require GMS attestation, Verified Boot Green, or a
  particular digest of an app signing key. For now just set these to false,
  for production these should be true and bound to a particluar application.

- Change default wallet server URL so things work out-of-the-box in the
  emulator. An upcoming PR will add UI to change this.

- Don't store message-encryption key in Configuration, use Storage instead.

Also fix some build errors for unit-tests in identity-android and
identity-android-legacy.

Signed-off-by: David Zeuthen <zeuthen@google.com>
  • Loading branch information
davidz25 committed May 24, 2024
1 parent a13a911 commit c75648b
Show file tree
Hide file tree
Showing 12 changed files with 267 additions and 107 deletions.
7 changes: 7 additions & 0 deletions identity-android-legacy/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,13 @@ android {
lint {
lintConfig file('lint.xml')
}

packagingOptions {
resources {
excludes += '/META-INF/{AL2.0,LGPL2.1}'
excludes += '/META-INF/versions/9/OSGI-INF/MANIFEST.MF'
}
}
}

dependencies {
Expand Down
7 changes: 7 additions & 0 deletions identity-android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,13 @@ android {
lint {
lintConfig file('lint.xml')
}

packagingOptions {
resources {
excludes += '/META-INF/{AL2.0,LGPL2.1}'
excludes += '/META-INF/versions/9/OSGI-INF/MANIFEST.MF'
}
}
}

dependencies {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
package com.android.identity.android.document

import androidx.test.InstrumentationRegistry
import com.android.identity.AndroidAttestationExtensionParser
import com.android.identity.util.AndroidAttestationExtensionParser
import com.android.identity.android.securearea.AndroidKeystoreCreateKeySettings
import com.android.identity.android.securearea.AndroidKeystoreSecureArea
import com.android.identity.android.securearea.UserAuthenticationType
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import android.os.Build
import android.security.keystore.KeyGenParameterSpec
import android.security.keystore.KeyProperties
import androidx.test.InstrumentationRegistry
import com.android.identity.AndroidAttestationExtensionParser
import com.android.identity.util.AndroidAttestationExtensionParser
import com.android.identity.android.storage.AndroidStorageEngine
import com.android.identity.crypto.Algorithm
import com.android.identity.crypto.Crypto
Expand Down Expand Up @@ -702,13 +702,13 @@ class AndroidKeystoreSecureAreaTest {
// tag 400: https://source.android.com/docs/security/features/keystore/tags#active_datetime
Assert.assertEquals(
validFrom.toEpochMilli(),
parser.getSoftwareAuthorizationLong(400).get()
parser.getSoftwareAuthorizationLong(400)
)

// tag 401: https://source.android.com/docs/security/features/keystore/tags#origination_expire_datetime
Assert.assertEquals(
validUntil.toEpochMilli(),
parser.getSoftwareAuthorizationLong(401).get()
parser.getSoftwareAuthorizationLong(401)
)
}

Expand Down
1 change: 1 addition & 0 deletions identity/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ dependencies {
implementation libs.kotlinx.io.bytestring
implementation libs.kotlinx.datetime
implementation libs.kotlinx.coroutines.core
implementation libs.kotlinx.serialization
implementation libs.tink

testImplementation libs.bundles.unit.testing
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,45 @@
package com.android.identity.flow.environment

import kotlinx.serialization.json.Json
import kotlinx.serialization.json.jsonArray

import com.android.identity.util.Logger
import kotlinx.serialization.json.jsonPrimitive

/**
* Simple interface to access configuration parameters.
*
* Parameters can be organized in tree-like structures with dots separating the names of the
* nodes in that tree, i.e. "componentClass.componentName.valueName".
*/
interface Configuration {
companion object {
private val TAG = "Configuration"
}

fun getProperty(name: String): String?

fun getBool(name: String, defaultValue: Boolean = false): Boolean {
val value = getProperty(name)
if (value == null) {
Logger.d(TAG, "getBool: No value configuration value with key $name, return default value $defaultValue")
return defaultValue
}
if (value == "true") {
return true
} else if (value == "false") {
return false
}
Logger.d(TAG, "getBool: Unexpected value '$value' with key $name, return default value $defaultValue")
return defaultValue
}

fun getStringList(key: String): List<String> {
val value = getProperty(key)
if (value == null) {
Logger.d(TAG, "getStringList: No value configuration value with key $key")
return emptyList()
}
return Json.parseToJsonElement(value).jsonArray.map { elem -> elem.jsonPrimitive.content }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.identity
package com.android.identity.util

import org.bouncycastle.asn1.ASN1Boolean
import org.bouncycastle.asn1.ASN1Encodable
Expand All @@ -23,9 +23,9 @@ import org.bouncycastle.asn1.ASN1Integer
import org.bouncycastle.asn1.ASN1OctetString
import org.bouncycastle.asn1.ASN1Primitive
import org.bouncycastle.asn1.ASN1Sequence
import org.bouncycastle.asn1.ASN1Set
import org.bouncycastle.asn1.ASN1TaggedObject
import java.security.cert.X509Certificate
import java.util.Optional

// This code is based on https://github.com/google/android-key-attestation
class AndroidAttestationExtensionParser(cert: X509Certificate) {
Expand All @@ -35,62 +35,35 @@ class AndroidAttestationExtensionParser(cert: X509Certificate) {
STRONG_BOX
}

var attestationVersion = 0
var keymasterSecurityLevel = SecurityLevel.SOFTWARE
var keymasterVersion = 0

val attestationChallenge: ByteArray
val uniqueId: ByteArray

private val softwareEnforcedAuthorizations: Map<Int, ASN1Primitive?>
private val teeEnforcedAuthorizations: Map<Int, ASN1Primitive?>
val softwareEnforcedAuthorizationTags: Set<Int>
get() = softwareEnforcedAuthorizations.keys
val teeEnforcedAuthorizationTags: Set<Int>
get() = teeEnforcedAuthorizations.keys

fun getSoftwareAuthorizationInteger(tag: Int): Optional<Int> {
val entry = findAuthorizationListEntry(softwareEnforcedAuthorizations, tag)
return Optional.ofNullable(entry)
.map { asn1Value: ASN1Primitive -> getIntegerFromAsn1(asn1Value) }
enum class VerifiedBootState {
UNKNOWN,
GREEN,
YELLOW,
ORANGE,
RED
}

fun getSoftwareAuthorizationLong(tag: Int): Optional<Long> {
val entry = findAuthorizationListEntry(softwareEnforcedAuthorizations, tag)
return Optional.ofNullable(entry)
.map { asn1Value: ASN1Primitive -> getLongFromAsn1(asn1Value) }
}
val attestationSecurityLevel: SecurityLevel
val attestationVersion: Int

fun getTeeAuthorizationInteger(tag: Int): Optional<Int> {
val entry = findAuthorizationListEntry(teeEnforcedAuthorizations, tag)
return Optional.ofNullable(entry)
.map { asn1Value: ASN1Primitive -> getIntegerFromAsn1(asn1Value) }
}
val keymasterSecurityLevel: SecurityLevel
val keymasterVersion: Int

fun getSoftwareAuthorizationBoolean(tag: Int): Boolean {
val entry = findAuthorizationListEntry(softwareEnforcedAuthorizations, tag)
return entry != null
}
val attestationChallenge: ByteArray
val uniqueId: ByteArray

fun getTeeAuthorizationBoolean(tag: Int): Boolean {
val entry = findAuthorizationListEntry(teeEnforcedAuthorizations, tag)
return entry != null
}
val verifiedBootState: VerifiedBootState

fun getSoftwareAuthorizationByteString(tag: Int): Optional<ByteArray> {
val entry =
findAuthorizationListEntry(softwareEnforcedAuthorizations, tag) as ASN1OctetString?
return Optional.ofNullable(entry).map { obj: ASN1OctetString -> obj.octets }
}
val applicationSignatureDigests: List<ByteArray>

fun getTeeAuthorizationByteString(tag: Int): Optional<ByteArray> {
val entry = findAuthorizationListEntry(teeEnforcedAuthorizations, tag) as ASN1OctetString?
return Optional.ofNullable(entry).map { obj: ASN1OctetString -> obj.octets }
}
private val softwareEnforcedAuthorizations: Map<Int, ASN1Primitive?>
private val teeEnforcedAuthorizations: Map<Int, ASN1Primitive?>

init {
val attestationExtensionBytes = cert.getExtensionValue(KEY_DESCRIPTION_OID)
require(!(attestationExtensionBytes == null || attestationExtensionBytes.size == 0)) { "Couldn't find keystore attestation extension." }
require(attestationExtensionBytes != null && attestationExtensionBytes.size > 0) {
"Couldn't find keystore attestation extension."
}
var seq: ASN1Sequence
ASN1InputStream(attestationExtensionBytes).use { asn1InputStream ->
// The extension contains one object, a sequence, in the
Expand All @@ -101,25 +74,101 @@ class AndroidAttestationExtensionParser(cert: X509Certificate) {
seq = seqInputStream.readObject() as ASN1Sequence
}
}

attestationVersion = getIntegerFromAsn1(seq.getObjectAt(ATTESTATION_VERSION_INDEX))
this.keymasterSecurityLevel = securityLevelToEnum(
this.attestationSecurityLevel = securityLevelToEnum(
getIntegerFromAsn1(
seq.getObjectAt(ATTESTATION_SECURITY_LEVEL_INDEX)
)
)

keymasterVersion = getIntegerFromAsn1(seq.getObjectAt(KEYMASTER_VERSION_INDEX))
this.keymasterSecurityLevel = securityLevelToEnum(
getIntegerFromAsn1(seq.getObjectAt(KEYMASTER_SECURITY_LEVEL_INDEX))
)
attestationChallenge =
(seq.getObjectAt(ATTESTATION_CHALLENGE_INDEX) as ASN1OctetString).octets
uniqueId = (seq.getObjectAt(UNIQUE_ID_INDEX) as ASN1OctetString).octets

softwareEnforcedAuthorizations = getAuthorizationMap(
(seq.getObjectAt(SW_ENFORCED_INDEX) as ASN1Sequence).toArray()
)
teeEnforcedAuthorizations = getAuthorizationMap(
(seq.getObjectAt(TEE_ENFORCED_INDEX) as ASN1Sequence).toArray()
)

val rootOfTrustSeq = findAuthorizationListEntry(teeEnforcedAuthorizations, 704) as ASN1Sequence?
verifiedBootState =
if (rootOfTrustSeq != null) {
when (getIntegerFromAsn1(rootOfTrustSeq.getObjectAt(2))) {
0 -> VerifiedBootState.GREEN
1 -> VerifiedBootState.YELLOW
2 -> VerifiedBootState.ORANGE
3 -> VerifiedBootState.RED
else -> VerifiedBootState.UNKNOWN
}
} else {
VerifiedBootState.UNKNOWN
}

val encodedAttestationApplicationId = getSoftwareAuthorizationByteString(709)

val attestationApplicationIdSeq =
ASN1InputStream(encodedAttestationApplicationId).readObject() as ASN1Sequence

val signatureDigestSet = attestationApplicationIdSeq.getObjectAt(1) as ASN1Set
val digests = mutableListOf<ByteArray>()
for (n in 0 until signatureDigestSet.size()) {
val octetString = signatureDigestSet.getObjectAt(n) as ASN1OctetString
digests.add(octetString.octets)
}
applicationSignatureDigests = digests
}

val softwareEnforcedAuthorizationTags: Set<Int>
get() = softwareEnforcedAuthorizations.keys
val teeEnforcedAuthorizationTags: Set<Int>
get() = teeEnforcedAuthorizations.keys

fun getSoftwareAuthorizationInteger(tag: Int): Int? {
val entry = findAuthorizationListEntry(softwareEnforcedAuthorizations, tag)
return entry?.let { getIntegerFromAsn1(it) }
}

fun getSoftwareAuthorizationLong(tag: Int): Long? {
val entry = findAuthorizationListEntry(softwareEnforcedAuthorizations, tag)
return entry?.let { getLongFromAsn1(it) }
}

fun getTeeAuthorizationInteger(tag: Int): Int? {
val entry = findAuthorizationListEntry(teeEnforcedAuthorizations, tag)
return entry?.let { getIntegerFromAsn1(it) }
}

fun getTeeAuthorizationLong(tag: Int): Long? {
val entry = findAuthorizationListEntry(teeEnforcedAuthorizations, tag)
return entry?.let { getLongFromAsn1(it) }
}

fun getSoftwareAuthorizationBoolean(tag: Int): Boolean {
val entry = findAuthorizationListEntry(softwareEnforcedAuthorizations, tag)
return entry != null
}

fun getTeeAuthorizationBoolean(tag: Int): Boolean {
val entry = findAuthorizationListEntry(teeEnforcedAuthorizations, tag)
return entry != null
}

fun getSoftwareAuthorizationByteString(tag: Int): ByteArray? {
val entry =
findAuthorizationListEntry(softwareEnforcedAuthorizations, tag) as ASN1OctetString?
return entry?.octets
}

fun getTeeAuthorizationByteString(tag: Int): ByteArray? {
val entry = findAuthorizationListEntry(teeEnforcedAuthorizations, tag) as ASN1OctetString?
return entry?.octets
}

companion object {
Expand Down
Loading

0 comments on commit c75648b

Please sign in to comment.