1717package com.android.keyattestation.verifier
1818
1919import androidx.annotation.RequiresApi
20+ import com.android.keyattestation.verifier.provider.KeyAttestationCertPath
21+ import com.google.common.collect.ImmutableList
2022import com.google.errorprone.annotations.Immutable
2123import 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}
0 commit comments