1717package com.android.keyattestation.verifier
1818
1919import androidx.annotation.RequiresApi
20+ import com.google.common.collect.ImmutableList
2021import com.google.errorprone.annotations.Immutable
2122import com.google.errorprone.annotations.ThreadSafe
2223
24+ private typealias AttributeMapper = (KeyDescription ) -> Any?
25+
26+ /* * An individual limit to place on the KeyDescription from an attestation certificate. */
27+ @ThreadSafe
28+ sealed interface Constraint {
29+ sealed interface Result {}
30+
31+ data object Satisfied : Result
32+
33+ data class Violated (val failureMessage : String ) : Result
34+
35+ /* * Fixed label, suitable for logging or metrics. */
36+ val label: String
37+
38+ /* * Verifies that [description] satisfies this [Constraint]. */
39+ fun check (description : KeyDescription ): Result
40+ }
41+
2342/* *
2443 * Configuration for validating the attributes in an Android attestation certificate, as described
2544 * at https://source.android.com/docs/security/features/keystore/attestation.
2645 */
2746@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. */
47+ class ConstraintConfig (
48+ val keyOrigin : Constraint ? = null ,
49+ val securityLevel : Constraint ? = null ,
50+ val rootOfTrust : Constraint ? = null ,
51+ val additionalConstraints : ImmutableList <Constraint > = ImmutableList .of(),
52+ ) {
53+ @RequiresApi(24 )
54+ fun getConstraints () =
55+ ImmutableList .builder<Constraint >()
56+ .add(
57+ keyOrigin
58+ ? : AttributeConstraint .STRICT (" Origin" , Origin .GENERATED ) { it.hardwareEnforced.origin }
59+ )
60+ .add(securityLevel ? : SecurityLevelConstraint .NOT_SOFTWARE )
61+ .add(
62+ rootOfTrust
63+ ? : AttributeConstraint .NOT_NULL (" Root of trust" ) { it.hardwareEnforced.rootOfTrust }
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 val label = " Ignored"
115+
116+ override fun check (description : KeyDescription ) = Constraint .Satisfied
117+ }
118+
119+ /* * Constraint that checks a single attribute of the [KeyDescription]. */
36120@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
121+ sealed class AttributeConstraint <out T >(override val label : String , val mapper : AttributeMapper ? ) :
122+ Constraint {
123+ /* * Evaluates whether the [description] is satisfied by this [AttributeConstraint]. */
124+ override fun check (description : KeyDescription ) =
125+ if (isSatisfied(mapper?.invoke(description))) {
126+ Constraint .Satisfied
127+ } else {
128+ Constraint .Violated (getFailureMessage(mapper?.invoke(description)))
129+ }
130+
131+ internal abstract fun isSatisfied (attribute : Any? ): Boolean
132+
133+ internal open fun getFailureMessage (attribute : Any? ): String =
134+ " $label violates constraint: value=$attribute , config=$this "
40135
41136 /* *
42137 * Checks that the attribute exists and matches the expected value.
43138 *
44139 * @param expectedVal The expected value of the attribute.
45140 */
46141 @Immutable(containerOf = [" T" ])
47- data class STRICT <T >(val expectedVal : T ) : ValidationLevel<T> {
48- override fun isSatisfiedBy (attribute : Any? ): Boolean = attribute == expectedVal
142+ data class STRICT <T >(val l : String , val expectedVal : T , private val m : AttributeMapper ) :
143+ AttributeConstraint <T >(l, m) {
144+ override fun isSatisfied (attribute : Any? ): Boolean = attribute == expectedVal
49145 }
50146
51147 /* 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
148+ data class NOT_NULL (val l : String , private val m : AttributeMapper ) :
149+ AttributeConstraint <Nothing >(l, m) {
150+ override fun isSatisfied (attribute : Any? ): Boolean = attribute != null
60151 }
61152}
62153
@@ -65,74 +156,81 @@ sealed interface ValidationLevel<out T> {
65156 * Android attestation certificate.
66157 */
67158@Immutable
68- sealed class SecurityLevelValidationLevel : ValidationLevel < KeyDescription > {
69- @RequiresApi( 24 )
70- fun areSecurityLevelsMatching ( keyDescription : KeyDescription ): Boolean {
71- return keyDescription.attestationSecurityLevel == keyDescription.keyMintSecurityLevel
159+ @RequiresApi( 24 )
160+ sealed class SecurityLevelConstraint ( val isSatisfied : ( KeyDescription ) -> Boolean ) : Constraint {
161+ companion object {
162+ const val LABEL = " Security level "
72163 }
73164
165+ override val label = LABEL
166+
167+ override fun check (description : KeyDescription ) =
168+ if (isSatisfied(description)) {
169+ Constraint .Satisfied
170+ } else {
171+ Constraint .Violated (getFailureMessage(description))
172+ }
173+
174+ fun getFailureMessage (description : KeyDescription ): String =
175+ " Security level violates constraint: " +
176+ " keyMintSecurityLevel=${description.keyMintSecurityLevel} , " +
177+ " attestationSecurityLevel=${description.attestationSecurityLevel} , " +
178+ " config=$this "
179+
74180 /* *
75181 * Checks that both the attestationSecurityLevel and keyMintSecurityLevel match the expected
76182 * value.
77183 *
78184 * @param expectedVal The expected value of the security level.
79185 */
80186 @Immutable
81- data class STRICT (val expectedVal : SecurityLevel ) : SecurityLevelValidationLevel() {
82- @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- }
88- }
187+ data class STRICT (val expectedVal : SecurityLevel ) :
188+ SecurityLevelConstraint ({
189+ it.keyMintSecurityLevel == expectedVal && it.attestationSecurityLevel == expectedVal
190+ })
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 () {
96- @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- }
103- }
197+ data object NOT_SOFTWARE :
198+ SecurityLevelConstraint ({
199+ it.keyMintSecurityLevel == it.attestationSecurityLevel &&
200+ it.attestationSecurityLevel != SecurityLevel .SOFTWARE
201+ })
104202
105203 /* *
106204 * Checks that the attestationSecurityLevel is equal to the keyMintSecurityLevel, regardless of
107205 * security level.
108206 */
109207 @Immutable
110- data object CONSISTENT : SecurityLevelValidationLevel () {
111- @RequiresApi(24 )
112- override fun isSatisfiedBy (attribute : Any? ): Boolean {
113- val keyDescription = attribute as ? KeyDescription ? : return false
114- return areSecurityLevelsMatching(keyDescription)
115- }
116- }
208+ data object CONSISTENT :
209+ SecurityLevelConstraint ({ it.attestationSecurityLevel == it.keyMintSecurityLevel })
117210}
118211
119212/* *
120213 * Configuration for validating the ordering of the attributes in the AuthorizationList sequence in
121214 * an Android attestation certificate.
122215 */
123216@Immutable
124- sealed interface TagOrderValidationLevel : ValidationLevel <KeyDescription > {
217+ @RequiresApi(24 )
218+ sealed class TagOrderConstraint : Constraint {
219+ override val label = " Tag order"
220+
125221 /* *
126222 * Checks that the attributes in the AuthorizationList sequence appear in the order specified by
127223 * https://source.android.com/docs/security/features/keystore/attestation#schema.
128224 */
129225 @Immutable
130- data object STRICT : TagOrderValidationLevel {
131- @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- }
226+ data object STRICT : TagOrderConstraint () {
227+ override fun check (description : KeyDescription ) =
228+ if (
229+ description.softwareEnforced.areTagsOrdered && description.hardwareEnforced.areTagsOrdered
230+ ) {
231+ Constraint .Satisfied
232+ } else {
233+ Constraint .Violated (" Authorization list tags must be in ascending order" )
234+ }
137235 }
138236}
0 commit comments