@@ -22,6 +22,7 @@ import com.android.keyattestation.verifier.provider.KeyAttestationProvider
2222import com.android.keyattestation.verifier.provider.ProvisioningMethod
2323import com.android.keyattestation.verifier.provider.RevocationChecker
2424import com.google.common.collect.ImmutableList
25+ import com.google.common.flogger.GoogleLogger
2526import com.google.common.util.concurrent.ListenableFuture
2627import com.google.errorprone.annotations.ThreadSafe
2728import com.google.protobuf.ByteString
@@ -42,28 +43,44 @@ import kotlinx.coroutines.guava.future
4243import kotlinx.coroutines.runBlocking
4344
4445/* * The result of verifying an Android Key Attestation certificate chain. */
46+ @ThreadSafe
4547sealed interface VerificationResult {
48+ @ThreadSafe
4649 data class Success (
47- val publicKey : PublicKey ,
50+ @field:ThreadSafe. Suppress (reason = " PublicKey is immutable") val publicKey : PublicKey ,
4851 val challenge : ByteString ,
4952 val securityLevel : SecurityLevel ,
5053 val verifiedBootState : VerifiedBootState ,
5154 val deviceInformation : ProvisioningInfoMap ? ,
55+ @field:ThreadSafe.Suppress (reason = "DeviceIdentity is deeply immutable")
5256 val attestedDeviceIds : DeviceIdentity ,
5357 ) : VerificationResult
5458
55- data object ChallengeMismatch : VerificationResult
59+ @ThreadSafe data object ChallengeMismatch : VerificationResult
5660
57- data class PathValidationFailure (val cause : CertPathValidatorException ) : VerificationResult
61+ @ThreadSafe
62+ data class PathValidationFailure (
63+ @field:ThreadSafe.Suppress (reason = "Exceptions are generally immutable after creation")
64+ val cause : CertPathValidatorException
65+ ) : VerificationResult
5866
59- data class ChainParsingFailure (val cause : Exception ) : VerificationResult
67+ @ThreadSafe
68+ data class ChainParsingFailure (
69+ @field:ThreadSafe.Suppress (reason = "Exceptions are generally immutable after creation")
70+ val cause : Exception
71+ ) : VerificationResult
6072
61- data class ExtensionParsingFailure (val cause : ExtensionParsingException ) : VerificationResult
73+ @ThreadSafe
74+ data class ExtensionParsingFailure (
75+ @field:ThreadSafe.Suppress (reason = "Exceptions are generally immutable after creation")
76+ val cause : ExtensionParsingException
77+ ) : VerificationResult
6278
79+ @ThreadSafe
6380 data class ExtensionConstraintViolation (val cause : String , val reason : KeyAttestationReason ) :
6481 VerificationResult
6582
66- data object SoftwareAttestationUnsupported : VerificationResult
83+ @ThreadSafe data object SoftwareAttestationUnsupported : VerificationResult
6784}
6885
6986/* *
@@ -141,6 +158,10 @@ open class Verifier(
141158 private val revokedSerialsSource : () -> Set <String >,
142159 private val instantSource : InstantSource ,
143160) {
161+ companion object {
162+ private val logger = GoogleLogger .forEnclosingClass()
163+ }
164+
144165 init {
145166 Security .addProvider(KeyAttestationProvider ())
146167 for (anchor in trustAnchorsSource()) {
@@ -176,6 +197,7 @@ open class Verifier(
176197 val certPath = KeyAttestationCertPath (chain)
177198 runBlocking { internalVerify(certPath, challengeChecker, requestLog) }
178199 } catch (e: CertificateException ) {
200+ logger.atWarning().withCause(e).log(" Failed to parse certificate chain." )
179201 requestLog?.logInputChain(chain.map { it.getEncoded().toByteString() })
180202 VerificationResult .ChainParsingFailure (e)
181203 }
@@ -209,6 +231,7 @@ open class Verifier(
209231 val certPath = KeyAttestationCertPath (immutableChain)
210232 internalVerify(certPath, challengeChecker, requestLog)
211233 } catch (e: CertificateException ) {
234+ logger.atWarning().withCause(e).log(" Failed to parse certificate chain." )
212235 requestLog?.logInputChain(immutableChain.map { it.getEncoded().toByteString() })
213236 VerificationResult .ChainParsingFailure (e)
214237 }
@@ -242,6 +265,7 @@ open class Verifier(
242265 try {
243266 certPath.attestationCert().provisioningInfo()
244267 } catch (e: Exception ) {
268+ logger.atWarning().withCause(e).log(" Failed to parse provisioning info map." )
245269 log?.logInfoMessage(" Failed to parse provisioning info map: ${e.message} " )
246270 null
247271 }
@@ -257,13 +281,20 @@ open class Verifier(
257281 e.message == " No matching trust anchor found" &&
258282 certPath.certificatesWithAnchor.last().isSoftwareRoot()
259283 ) {
284+ logger
285+ .atWarning()
286+ .withCause(e)
287+ .log(
288+ " Chain terminates in a software root and no matching trust anchor was found, so the chain was not validated."
289+ )
260290 return VerificationResult .PathValidationFailure (
261291 CertPathValidatorException (
262292 " Chain terminates in a software root and no matching trust anchor was found, so the chain was not validated." ,
263293 e,
264294 )
265295 )
266296 }
297+ logger.atWarning().withCause(e).log(" Certificate path validation failed." )
267298 return VerificationResult .PathValidationFailure (e)
268299 }
269300
@@ -276,8 +307,10 @@ open class Verifier(
276307 " Key attestation extension not found"
277308 }
278309 } catch (e: ExtensionParsingException ) {
310+ logger.atWarning().withCause(e).log(" Failed to parse key description extension." )
279311 return VerificationResult .ExtensionParsingFailure (e)
280312 } catch (e: Exception ) {
313+ logger.atWarning().withCause(e).log(" Failed to parse key description extension." )
281314 // TODO(google-internal bug): When experimental contracts aren't experimental,
282315 // update the IllegalArgumentException and IllegalStateException cases to return
283316 // ExtensionParsingException.
@@ -287,6 +320,7 @@ open class Verifier(
287320 if (challengeChecker != null ) {
288321 val checkResult = challengeChecker.checkChallenge(keyDescription.attestationChallenge).await()
289322 if (! checkResult) {
323+ logger.atWarning().log(" Attestation challenge mismatch." )
290324 return VerificationResult .ChallengeMismatch
291325 }
292326 }
@@ -295,6 +329,12 @@ open class Verifier(
295329 keyDescription.hardwareEnforced.origin == null ||
296330 keyDescription.hardwareEnforced.origin != Origin .GENERATED
297331 ) {
332+ logger
333+ .atWarning()
334+ .log(
335+ " Constraint violation: origin != GENERATED: %s" ,
336+ keyDescription.hardwareEnforced.origin,
337+ )
298338 return VerificationResult .ExtensionConstraintViolation (
299339 " origin != GENERATED: ${keyDescription.hardwareEnforced.origin} " ,
300340 KeyAttestationReason .KEY_ORIGIN_NOT_GENERATED ,
@@ -305,17 +345,28 @@ open class Verifier(
305345 if (keyDescription.attestationSecurityLevel == keyDescription.keyMintSecurityLevel) {
306346 keyDescription.attestationSecurityLevel
307347 } else {
348+ logger
349+ .atWarning()
350+ .log(
351+ " Constraint violation: attestationSecurityLevel != keyMintSecurityLevel: %s != %s" ,
352+ keyDescription.attestationSecurityLevel,
353+ keyDescription.keyMintSecurityLevel,
354+ )
308355 return VerificationResult .ExtensionConstraintViolation (
309356 " attestationSecurityLevel != keyMintSecurityLevel: ${keyDescription.attestationSecurityLevel} != ${keyDescription.keyMintSecurityLevel} " ,
310357 KeyAttestationReason .MISMATCHED_SECURITY_LEVELS ,
311358 )
312359 }
313360 val rootOfTrust =
314361 keyDescription.hardwareEnforced.rootOfTrust
315- ? : return VerificationResult .ExtensionConstraintViolation (
316- " hardwareEnforced.rootOfTrust is null" ,
317- KeyAttestationReason .ROOT_OF_TRUST_MISSING ,
318- )
362+ ? : run {
363+ logger.atWarning().log(" Constraint violation: hardwareEnforced.rootOfTrust is null" )
364+ return VerificationResult .ExtensionConstraintViolation (
365+ " hardwareEnforced.rootOfTrust is null" ,
366+ KeyAttestationReason .ROOT_OF_TRUST_MISSING ,
367+ )
368+ }
369+ logger.atInfo().log(" Attestation verification succeeded." )
319370 return VerificationResult .Success (
320371 pathValidationResult.publicKey,
321372 keyDescription.attestationChallenge,
0 commit comments