Skip to content

Commit 390bbfd

Browse files
sethmoocopybara-github
authored andcommitted
Make ConstraintConfig more generic by parameterizing the constrained attribute
This is the first step toward making the constraints truly generic. Instead of making each constraint take a specific attribute, make all the constraints accept common types. The contraints are them customize to extract the constrained attributes using mapping functions. PiperOrigin-RevId: 888423834
1 parent b1bf437 commit 390bbfd

5 files changed

Lines changed: 284 additions & 143 deletions

File tree

src/main/kotlin/ConstraintConfig.kt

Lines changed: 150 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -17,46 +17,145 @@
1717
package com.android.keyattestation.verifier
1818

1919
import androidx.annotation.RequiresApi
20+
import com.android.keyattestation.verifier.provider.KeyAttestationCertPath
21+
import com.google.common.collect.ImmutableList
2022
import com.google.errorprone.annotations.Immutable
2123
import com.google.errorprone.annotations.ThreadSafe
2224

25+
private typealias AttributeMapper = (KeyDescription, KeyAttestationCertPath) -> Any?
26+
27+
/**
28+
* An individual limit to place on the KeyDescription and/or KeyAttestationCertPath from an
29+
* attestation certificate.
30+
*/
31+
@ThreadSafe
32+
interface Constraint {
33+
fun isSatisfied(description: KeyDescription, path: KeyAttestationCertPath): Boolean
34+
35+
fun getFailureMessage(description: KeyDescription, path: KeyAttestationCertPath): String
36+
}
37+
2338
/**
2439
* Configuration for validating the attributes in an Android attestation certificate, as described
2540
* at https://source.android.com/docs/security/features/keystore/attestation.
2641
*/
2742
@ThreadSafe
28-
data class ConstraintConfig(
29-
val keyOrigin: ValidationLevel<Origin> = ValidationLevel.STRICT(Origin.GENERATED),
30-
val securityLevel: ValidationLevel<KeyDescription> = SecurityLevelValidationLevel.NOT_SOFTWARE,
31-
val rootOfTrust: ValidationLevel<RootOfTrust> = ValidationLevel.NOT_NULL,
32-
val authorizationListTagOrder: ValidationLevel<KeyDescription> = ValidationLevel.IGNORE,
33-
)
34-
35-
/** Configuration for validating a single attribute in an Android attestation certificate. */
43+
class ConstraintConfig(
44+
val keyOrigin: Constraint? = null,
45+
val securityLevel: Constraint? = null,
46+
val rootOfTrust: Constraint? = null,
47+
val additionalConstraints: ImmutableList<Constraint> = ImmutableList.of(),
48+
) {
49+
@RequiresApi(24)
50+
fun getConstraints() =
51+
ImmutableList.builder<Constraint>()
52+
.add(
53+
keyOrigin
54+
?: AttributeConstraint.STRICT("Origin", Origin.GENERATED) { kd, _ ->
55+
kd.hardwareEnforced.origin
56+
}
57+
)
58+
.add(securityLevel ?: SecurityLevelConstraint.NOT_SOFTWARE)
59+
.add(
60+
rootOfTrust
61+
?: AttributeConstraint.NOT_NULL("Root of trust") { kd, _ ->
62+
kd.hardwareEnforced.rootOfTrust
63+
}
64+
)
65+
.addAll(additionalConstraints)
66+
.build()
67+
}
68+
69+
/**
70+
* We need a builder to support creating a [ConstraintConfig], as it's a thread-safe object. A
71+
* Kotlin-idiomatic builder function is provided below.
72+
*/
73+
class ConstraintConfigBuilder() {
74+
var keyOrigin: Constraint? = null
75+
var securityLevel: Constraint? = null
76+
var rootOfTrust: Constraint? = null
77+
var additionalConstraints: MutableList<Constraint> = mutableListOf()
78+
79+
fun securityLevel(constraint: () -> Constraint) {
80+
this.securityLevel = constraint()
81+
}
82+
83+
fun keyOrigin(constraint: () -> Constraint) {
84+
this.keyOrigin = constraint()
85+
}
86+
87+
fun rootOfTrust(constraint: () -> Constraint) {
88+
this.rootOfTrust = constraint()
89+
}
90+
91+
fun additionalConstraint(constraint: () -> Constraint) {
92+
additionalConstraints.add(constraint())
93+
}
94+
95+
fun build(): ConstraintConfig =
96+
ConstraintConfig(
97+
keyOrigin,
98+
securityLevel,
99+
rootOfTrust,
100+
ImmutableList.copyOf(additionalConstraints),
101+
)
102+
}
103+
104+
/** Implements a Kotlin-style type safe builder for creating a [ConstraintConfig]. */
105+
fun constraintConfig(init: ConstraintConfigBuilder.() -> Unit): ConstraintConfig {
106+
val builder = ConstraintConfigBuilder()
107+
builder.init()
108+
return builder.build()
109+
}
110+
111+
/** Constraint that is always satisfied. */
112+
@Immutable
113+
data object IgnoredConstraint : Constraint {
114+
override fun isSatisfied(description: KeyDescription, path: KeyAttestationCertPath): Boolean =
115+
true
116+
117+
override fun getFailureMessage(
118+
description: KeyDescription,
119+
path: KeyAttestationCertPath,
120+
): String = ""
121+
}
122+
123+
/**
124+
* Constraint that checks a single attribute of the [KeyDescription] or [KeyAttestationCertPath].
125+
*/
36126
@Immutable(containerOf = ["T"])
37-
sealed interface ValidationLevel<out T> {
38-
/** Evaluates whether the [attribute] is satisfied by this [ValidationLevel]. */
39-
fun isSatisfiedBy(attribute: Any?): Boolean
127+
sealed class AttributeConstraint<out T>(val label: String, val mapper: AttributeMapper?) :
128+
Constraint {
129+
/** Evaluates whether the [description] is satisfied by this [AttributeConstraint]. */
130+
override fun isSatisfied(description: KeyDescription, path: KeyAttestationCertPath): Boolean =
131+
isSatisfiedInternal(mapper?.invoke(description, path))
132+
133+
internal abstract fun isSatisfiedInternal(attribute: Any?): Boolean
134+
135+
/** Generates an error message if the constraint is not satisfied. */
136+
override fun getFailureMessage(
137+
description: KeyDescription,
138+
path: KeyAttestationCertPath,
139+
): String = getFailureMessageInternal(mapper?.invoke(description, path))
140+
141+
internal open fun getFailureMessageInternal(attribute: Any?): String =
142+
"$label violates constraint: value=$attribute, config=$this"
40143

41144
/**
42145
* Checks that the attribute exists and matches the expected value.
43146
*
44147
* @param expectedVal The expected value of the attribute.
45148
*/
46149
@Immutable(containerOf = ["T"])
47-
data class STRICT<T>(val expectedVal: T) : ValidationLevel<T> {
48-
override fun isSatisfiedBy(attribute: Any?): Boolean = attribute == expectedVal
150+
data class STRICT<T>(val l: String, val expectedVal: T, private val m: AttributeMapper) :
151+
AttributeConstraint<T>(l, m) {
152+
override fun isSatisfiedInternal(attribute: Any?): Boolean = attribute == expectedVal
49153
}
50154

51155
/* Check that the attribute exists. */
52-
@Immutable
53-
data object NOT_NULL : ValidationLevel<Nothing> {
54-
override fun isSatisfiedBy(attribute: Any?): Boolean = attribute != null
55-
}
56-
57-
@Immutable
58-
data object IGNORE : ValidationLevel<Nothing> {
59-
override fun isSatisfiedBy(attribute: Any?): Boolean = true
156+
data class NOT_NULL(val l: String, private val m: AttributeMapper) :
157+
AttributeConstraint<Nothing>(l, m) {
158+
override fun isSatisfiedInternal(attribute: Any?): Boolean = attribute != null
60159
}
61160
}
62161

@@ -65,11 +164,16 @@ sealed interface ValidationLevel<out T> {
65164
* Android attestation certificate.
66165
*/
67166
@Immutable
68-
sealed class SecurityLevelValidationLevel : ValidationLevel<KeyDescription> {
69-
@RequiresApi(24)
70-
fun areSecurityLevelsMatching(keyDescription: KeyDescription): Boolean {
71-
return keyDescription.attestationSecurityLevel == keyDescription.keyMintSecurityLevel
72-
}
167+
@RequiresApi(24)
168+
sealed class SecurityLevelConstraint : Constraint {
169+
override fun getFailureMessage(
170+
description: KeyDescription,
171+
path: KeyAttestationCertPath,
172+
): String =
173+
"Security level violates constraint: " +
174+
"keyMintSecurityLevel=${description.attestationSecurityLevel}, " +
175+
"attestationSecurityLevel=${description.attestationSecurityLevel}, " +
176+
"config=$this"
73177

74178
/**
75179
* Checks that both the attestationSecurityLevel and keyMintSecurityLevel match the expected
@@ -78,41 +182,34 @@ sealed class SecurityLevelValidationLevel : ValidationLevel<KeyDescription> {
78182
* @param expectedVal The expected value of the security level.
79183
*/
80184
@Immutable
81-
data class STRICT(val expectedVal: SecurityLevel) : SecurityLevelValidationLevel() {
185+
data class STRICT(val expectedVal: SecurityLevel) : SecurityLevelConstraint() {
82186
@RequiresApi(24)
83-
override fun isSatisfiedBy(attribute: Any?): Boolean {
84-
val keyDescription = attribute as? KeyDescription ?: return false
85-
val securityLevelIsExpected = keyDescription.attestationSecurityLevel == this.expectedVal
86-
return areSecurityLevelsMatching(keyDescription) && securityLevelIsExpected
87-
}
187+
override fun isSatisfied(description: KeyDescription, path: KeyAttestationCertPath): Boolean =
188+
description.keyMintSecurityLevel == this.expectedVal &&
189+
description.attestationSecurityLevel == this.expectedVal
88190
}
89191

90192
/**
91193
* Checks that the attestationSecurityLevel is equal to the keyMintSecurityLevel, and that this
92194
* security level is not [SecurityLevel.SOFTWARE].
93195
*/
94196
@Immutable
95-
data object NOT_SOFTWARE : SecurityLevelValidationLevel() {
197+
data object NOT_SOFTWARE : SecurityLevelConstraint() {
96198
@RequiresApi(24)
97-
override fun isSatisfiedBy(attribute: Any?): Boolean {
98-
val keyDescription = attribute as? KeyDescription ?: return false
99-
val securityLevelIsSoftware =
100-
keyDescription.attestationSecurityLevel == SecurityLevel.SOFTWARE
101-
return areSecurityLevelsMatching(keyDescription) && !securityLevelIsSoftware
102-
}
199+
override fun isSatisfied(description: KeyDescription, path: KeyAttestationCertPath): Boolean =
200+
description.keyMintSecurityLevel == description.attestationSecurityLevel &&
201+
description.attestationSecurityLevel != SecurityLevel.SOFTWARE
103202
}
104203

105204
/**
106205
* Checks that the attestationSecurityLevel is equal to the keyMintSecurityLevel, regardless of
107206
* security level.
108207
*/
109208
@Immutable
110-
data object CONSISTENT : SecurityLevelValidationLevel() {
209+
data object CONSISTENT : SecurityLevelConstraint() {
111210
@RequiresApi(24)
112-
override fun isSatisfiedBy(attribute: Any?): Boolean {
113-
val keyDescription = attribute as? KeyDescription ?: return false
114-
return areSecurityLevelsMatching(keyDescription)
115-
}
211+
override fun isSatisfied(description: KeyDescription, path: KeyAttestationCertPath): Boolean =
212+
description.attestationSecurityLevel == description.keyMintSecurityLevel
116213
}
117214
}
118215

@@ -121,18 +218,20 @@ sealed class SecurityLevelValidationLevel : ValidationLevel<KeyDescription> {
121218
* an Android attestation certificate.
122219
*/
123220
@Immutable
124-
sealed interface TagOrderValidationLevel : ValidationLevel<KeyDescription> {
221+
sealed class TagOrderConstraint : Constraint {
222+
override fun getFailureMessage(
223+
description: KeyDescription,
224+
path: KeyAttestationCertPath,
225+
): String = "Authorization list tags must be in ascending order"
226+
125227
/**
126228
* Checks that the attributes in the AuthorizationList sequence appear in the order specified by
127229
* https://source.android.com/docs/security/features/keystore/attestation#schema.
128230
*/
129231
@Immutable
130-
data object STRICT : TagOrderValidationLevel {
232+
data object STRICT : TagOrderConstraint() {
131233
@RequiresApi(24)
132-
override fun isSatisfiedBy(attribute: Any?): Boolean {
133-
val keyDescription = attribute as? KeyDescription ?: return false
134-
return keyDescription.softwareEnforced.areTagsOrdered &&
135-
keyDescription.hardwareEnforced.areTagsOrdered
136-
}
234+
override fun isSatisfied(description: KeyDescription, path: KeyAttestationCertPath): Boolean =
235+
description.softwareEnforced.areTagsOrdered && description.hardwareEnforced.areTagsOrdered
137236
}
138237
}

src/main/kotlin/KeyAttestationReason.kt

Lines changed: 4 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -33,22 +33,10 @@ enum class KeyAttestationReason : CertPathValidatorException.Reason {
3333
// extension. This likely indicates that an attacker is trying to manipulate the key and
3434
// device properties.
3535
CHAIN_EXTENDED_WITH_FAKE_ATTESTATION_EXTENSION,
36-
// The origin violated the constraint provided in [ConstraintConfig].
37-
// Using the default config, this means the key was not generated, so the verifier cannot know
38-
// that the key has always been in the secure environment.
39-
KEY_ORIGIN_CONSTRAINT_VIOLATION,
40-
// The security level violated the constraint provided in [ConstraintConfig].
41-
// Using the default config, this means the attestation and the KeyMint security levels do not
42-
// match, which likely indicates that the attestation was generated in software and so cannot be
43-
// trusted.
44-
SECURITY_LEVEL_CONSTRAINT_VIOLATION,
45-
// The root of trust violated the constraint provided in [ConstraintConfig].
46-
// Using the default config, this means the key description is missing the root of trust, and an
47-
// Android key attestation chain without a root of trust is malformed.
48-
ROOT_OF_TRUST_CONSTRAINT_VIOLATION,
49-
// The authorization list ordering violated the constraint provided in
50-
// [ConstraintConfig].
51-
AUTHORIZATION_LIST_ORDERING_CONSTRAINT_VIOLATION,
36+
// One of the constraints provided to the verifier was violated.
37+
CONSTRAINT_VIOLATION,
5238
// There was an error parsing the key description and an unknown tag number was encountered.
5339
UNKNOWN_TAG_NUMBER,
40+
// The Android device version is too old.
41+
ANDROID_VERSION_TOO_OLD,
5442
}

src/main/kotlin/Verifier.kt

Lines changed: 7 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -291,40 +291,20 @@ constructor(
291291
}
292292
}
293293

294-
val origin = keyDescription.hardwareEnforced.origin
295-
if (!constraintConfig.keyOrigin.isSatisfiedBy(origin)) {
296-
return VerificationResult.ConstraintViolation(
297-
"Origin violates constraint: value=${origin}, config=${constraintConfig.keyOrigin}",
298-
KeyAttestationReason.KEY_ORIGIN_CONSTRAINT_VIOLATION,
299-
)
300-
}
301-
302-
val securityLevel =
303-
if (constraintConfig.securityLevel.isSatisfiedBy(keyDescription)) {
304-
minOf(keyDescription.attestationSecurityLevel, keyDescription.keyMintSecurityLevel)
305-
} else {
294+
for (constraint in constraintConfig.getConstraints()) {
295+
if (!constraint.isSatisfied(keyDescription, certPath)) {
306296
return VerificationResult.ConstraintViolation(
307-
"Security level violates constraint: value=${keyDescription.attestationSecurityLevel}, config=${constraintConfig.securityLevel}",
308-
KeyAttestationReason.SECURITY_LEVEL_CONSTRAINT_VIOLATION,
297+
constraint.getFailureMessage(keyDescription, certPath),
298+
KeyAttestationReason.CONSTRAINT_VIOLATION,
309299
)
310300
}
301+
}
311302

303+
val securityLevel =
304+
minOf(keyDescription.attestationSecurityLevel, keyDescription.keyMintSecurityLevel)
312305
val rootOfTrust = keyDescription.hardwareEnforced.rootOfTrust
313-
if (!constraintConfig.rootOfTrust.isSatisfiedBy(rootOfTrust)) {
314-
return VerificationResult.ConstraintViolation(
315-
"Root of trust violates constraint: value=${rootOfTrust}, config=${constraintConfig.rootOfTrust}",
316-
KeyAttestationReason.ROOT_OF_TRUST_CONSTRAINT_VIOLATION,
317-
)
318-
}
319306
val verifiedBootState = rootOfTrust?.verifiedBootState ?: VerifiedBootState.UNVERIFIED
320307

321-
if (!constraintConfig.authorizationListTagOrder.isSatisfiedBy(keyDescription)) {
322-
return VerificationResult.ConstraintViolation(
323-
"Authorization list ordering violates constraint: config=${constraintConfig.authorizationListTagOrder}",
324-
KeyAttestationReason.AUTHORIZATION_LIST_ORDERING_CONSTRAINT_VIOLATION,
325-
)
326-
}
327-
328308
return VerificationResult.Success(
329309
pathValidationResult.publicKey,
330310
keyDescription.attestationChallenge,

0 commit comments

Comments
 (0)