@@ -45,23 +45,36 @@ sealed interface VerificationResult {
4545
4646 data object ChallengeMismatch : VerificationResult
4747
48- data object PathValidationFailure : VerificationResult
48+ data class PathValidationFailure ( val cause : Exception ) : VerificationResult
4949
50- data object ChainParsingFailure : VerificationResult
50+ data class ChainParsingFailure ( val cause : Exception ) : VerificationResult
5151
5252 data class ExtensionParsingFailure (val cause : Exception ) : VerificationResult
5353
5454 data class ExtensionConstraintViolation (val cause : String ) : VerificationResult
5555}
5656
57+ /* * Interface for logging info level key attestation events and information. */
58+ interface LogHook {
59+ fun logVerificationEvent (verificationEvent : VerificationEvent )
60+ }
61+
62+ data class VerificationEvent (
63+ val inputChain : List <X509Certificate >,
64+ val result : VerificationResult ,
65+ val keyDescription : KeyDescription ? = null ,
66+ val provisioningInfoMap : ProvisioningInfoMap ? = null ,
67+ val certSerialNumbers : List <String >? = null ,
68+ val infoMessages : List <String >? = null ,
69+ )
70+
5771/* *
5872 * Verifier for Android Key Attestation certificate chain.
5973 *
6074 * https://developer.android.com/privacy-and-security/security-key-attestation
6175 *
6276 * @param anchor a [TrustAnchor] to use for certificate path verification.
6377 */
64- // TODO: b/356234568 - Verify intermediate certificate revocation status.
6578@ThreadSafe
6679open class Verifier (
6780 private val trustAnchorsSource : () -> Set <TrustAnchor >,
@@ -83,14 +96,19 @@ open class Verifier(
8396 fun verify (
8497 chain : List <X509Certificate >,
8598 challengeChecker : ChallengeChecker ? = null,
99+ log : LogHook ? = null,
86100 ): VerificationResult {
87101 val certPath =
88102 try {
89103 KeyAttestationCertPath (chain)
90104 } catch (e: Exception ) {
91- return VerificationResult .ChainParsingFailure
105+ val result = VerificationResult .ChainParsingFailure (e)
106+ log?.logVerificationEvent(VerificationEvent (inputChain = chain, result = result))
107+ return result
92108 }
93- return verify(certPath, challengeChecker)
109+ val verificationEvent = internalVerify(certPath, challengeChecker)
110+ log?.logVerificationEvent(verificationEvent)
111+ return verificationEvent.result
94112 }
95113
96114 /* *
@@ -99,77 +117,140 @@ open class Verifier(
99117 * @param chain The attestation certificate chain to verify.
100118 * @param challengeChecker The challenge checker to use for additional validation of the challenge
101119 * in the attestation chain.
102- * @return [VerificationResult ]
120+ * @return [VerificationEvent ]
103121 *
104122 * TODO: b/366058500 - Make the challenge required after Apparat's changes are rollback safe.
105123 */
106- @JvmOverloads
107124 fun verify (
108125 certPath : KeyAttestationCertPath ,
109126 challengeChecker : ChallengeChecker ? = null,
127+ log : LogHook ? = null,
110128 ): VerificationResult {
129+ val verificationEvent = internalVerify(certPath, challengeChecker)
130+ log?.logVerificationEvent(verificationEvent)
131+ return verificationEvent.result
132+ }
133+
134+ internal fun internalVerify (
135+ certPath : KeyAttestationCertPath ,
136+ challengeChecker : ChallengeChecker ? = null,
137+ ): VerificationEvent {
138+ val serialNumbers =
139+ certPath.certificatesWithAnchor.subList(1 , certPath.certificatesWithAnchor.size).map {
140+ it.serialNumber.toString(16 )
141+ }
111142 val certPathValidator = CertPathValidator .getInstance(" KeyAttestation" )
112143 val certPathParameters =
113144 PKIXParameters (trustAnchorsSource()).apply {
114145 date = Date .from(instantSource.instant())
115146 addCertPathChecker(RevocationChecker (revokedSerialsSource()))
116147 }
148+
149+ val deviceInformation =
150+ if (certPath.provisioningMethod() == ProvisioningMethod .REMOTELY_PROVISIONED ) {
151+ certPath.attestationCert().provisioningInfo()
152+ } else {
153+ null
154+ }
117155 val pathValidationResult =
118156 try {
119157 certPathValidator.validate(certPath, certPathParameters) as PKIXCertPathValidatorResult
120158 } catch (e: CertPathValidatorException ) {
121- return VerificationResult .PathValidationFailure
159+ return VerificationEvent (
160+ inputChain = certPath.getCertificates(),
161+ result = VerificationResult .PathValidationFailure (e),
162+ certSerialNumbers = serialNumbers,
163+ provisioningInfoMap = deviceInformation,
164+ )
122165 }
123166
124167 val keyDescription =
125168 try {
126169 checkNotNull(certPath.leafCert().keyDescription()) { " Key attestation extension not found" }
127170 } catch (e: Exception ) {
128- return VerificationResult .ExtensionParsingFailure (e)
171+ return VerificationEvent (
172+ inputChain = certPath.getCertificates(),
173+ result = VerificationResult .ExtensionParsingFailure (e),
174+ certSerialNumbers = serialNumbers,
175+ provisioningInfoMap = deviceInformation,
176+ )
129177 }
130178
179+ val infoMessages = keyDescription.infoMessages
180+
131181 if (
132182 challengeChecker != null &&
133183 ! challengeChecker.checkChallenge(keyDescription.attestationChallenge)
134184 ) {
135- return VerificationResult .ChallengeMismatch
185+ return VerificationEvent (
186+ inputChain = certPath.getCertificates(),
187+ result = VerificationResult .ChallengeMismatch ,
188+ certSerialNumbers = serialNumbers,
189+ keyDescription = keyDescription,
190+ infoMessages = infoMessages,
191+ provisioningInfoMap = deviceInformation,
192+ )
136193 }
137194
138195 if (
139196 keyDescription.hardwareEnforced.origin == null ||
140197 keyDescription.hardwareEnforced.origin != Origin .GENERATED
141198 ) {
142- return VerificationResult .ExtensionConstraintViolation (
143- " origin != GENERATED: ${keyDescription.hardwareEnforced.origin} "
199+ return VerificationEvent (
200+ result =
201+ VerificationResult .ExtensionConstraintViolation (
202+ " hardwareEnforced.origin is not GENERATED: ${keyDescription.hardwareEnforced.origin} "
203+ ),
204+ inputChain = certPath.getCertificates(),
205+ certSerialNumbers = serialNumbers,
206+ keyDescription = keyDescription,
207+ infoMessages = infoMessages,
208+ provisioningInfoMap = deviceInformation,
144209 )
145210 }
146211
147212 val securityLevel =
148213 if (keyDescription.attestationSecurityLevel == keyDescription.keyMintSecurityLevel) {
149214 keyDescription.attestationSecurityLevel
150215 } else {
151- return VerificationResult .ExtensionConstraintViolation (
152- " attestationSecurityLevel != keyMintSecurityLevel: ${keyDescription.attestationSecurityLevel} != ${keyDescription.keyMintSecurityLevel} "
216+ return VerificationEvent (
217+ result =
218+ VerificationResult .ExtensionConstraintViolation (
219+ " attestationSecurityLevel != keymintSecurityLevel: ${keyDescription.attestationSecurityLevel} != ${keyDescription.keyMintSecurityLevel} "
220+ ),
221+ inputChain = certPath.getCertificates(),
222+ certSerialNumbers = serialNumbers,
223+ keyDescription = keyDescription,
224+ infoMessages = infoMessages,
225+ provisioningInfoMap = deviceInformation,
153226 )
154227 }
155228 val rootOfTrust =
156229 keyDescription.hardwareEnforced.rootOfTrust
157- ? : return VerificationResult .ExtensionConstraintViolation (
158- " hardwareEnforced.rootOfTrust is null"
230+ ? : return VerificationEvent (
231+ result =
232+ VerificationResult .ExtensionConstraintViolation (" hardwareEnforced.rootOfTrust is null" ),
233+ keyDescription = keyDescription,
234+ inputChain = certPath.getCertificates(),
235+ certSerialNumbers = serialNumbers,
236+ infoMessages = infoMessages,
237+ provisioningInfoMap = deviceInformation,
159238 )
160- val deviceInformation =
161- if (certPath.provisioningMethod() == ProvisioningMethod .REMOTELY_PROVISIONED ) {
162- certPath.attestationCert().provisioningInfo()
163- } else {
164- null
165- }
166- return VerificationResult .Success (
167- pathValidationResult.publicKey,
168- keyDescription.attestationChallenge,
169- securityLevel,
170- rootOfTrust.verifiedBootState,
171- deviceInformation,
172- DeviceIdentity .parseFrom(keyDescription),
239+ return VerificationEvent (
240+ result =
241+ VerificationResult .Success (
242+ pathValidationResult.publicKey,
243+ keyDescription.attestationChallenge,
244+ securityLevel,
245+ rootOfTrust.verifiedBootState,
246+ deviceInformation,
247+ DeviceIdentity .parseFrom(keyDescription),
248+ ),
249+ inputChain = certPath.getCertificates(),
250+ certSerialNumbers = serialNumbers,
251+ keyDescription = keyDescription,
252+ infoMessages = infoMessages,
253+ provisioningInfoMap = deviceInformation,
173254 )
174255 }
175256}
0 commit comments