@@ -46,15 +46,29 @@ sealed interface VerificationResult {
4646
4747 data object ChallengeMismatch : VerificationResult
4848
49- data object PathValidationFailure : VerificationResult
49+ data class PathValidationFailure ( val cause : Exception ) : VerificationResult
5050
51- data object ChainParsingFailure : VerificationResult
51+ data class ChainParsingFailure ( val cause : Exception ) : VerificationResult
5252
5353 data class ExtensionParsingFailure (val cause : Exception ) : VerificationResult
5454
5555 data class ExtensionConstraintViolation (val cause : String ) : VerificationResult
5656}
5757
58+ /* * Interface for logging info level key attestation events and information. */
59+ interface LogHook {
60+ fun logVerificationEvent (verificationEvent : VerificationEvent )
61+ }
62+
63+ data class VerificationEvent (
64+ val inputChain : List <X509Certificate >,
65+ val result : VerificationResult ,
66+ val keyDescription : KeyDescription ? = null ,
67+ val provisioningInfoMap : ProvisioningInfoMap ? = null ,
68+ val certSerialNumbers : List <String >? = null ,
69+ val infoMessages : List <String >? = null ,
70+ )
71+
5872/* *
5973 * Verifier for Android Key Attestation certificate chain.
6074 *
@@ -73,88 +87,163 @@ open class Verifier(
7387 Security .addProvider(KeyAttestationProvider ())
7488 }
7589
76- fun verify (chain : List <X509Certificate >, challenge : ByteArray? = null): VerificationResult {
90+ @JvmOverloads
91+ fun verify (
92+ chain : List <X509Certificate >,
93+ challenge : ByteArray? = null,
94+ log : LogHook ? = null,
95+ ): VerificationResult {
7796 val certPath =
7897 try {
7998 KeyAttestationCertPath (chain)
8099 } catch (e: Exception ) {
81- return VerificationResult .ChainParsingFailure
100+ val result = VerificationResult .ChainParsingFailure (e)
101+ log?.logVerificationEvent(VerificationEvent (inputChain = chain, result = result))
102+ return result
82103 }
83- return verify(certPath, challenge)
104+ val verificationEvent = internalVerify(certPath, challenge)
105+ log?.logVerificationEvent(verificationEvent)
106+ return verificationEvent.result
84107 }
85108
86109 /* *
87110 * Verifies an Android Key Attestation certificate chain.
88111 *
89112 * @param chain The attestation certificate chain to verify.
90- * @return [VerificationResult ]
113+ * @return [VerificationEvent ]
91114 *
92115 * TODO: b/366058500 - Make the challenge required after Apparat's changes are rollback safe.
93116 */
94- @JvmOverloads
95- fun verify (certPath : KeyAttestationCertPath , challenge : ByteArray? = null): VerificationResult {
117+ fun verify (
118+ certPath : KeyAttestationCertPath ,
119+ challenge : ByteArray? = null,
120+ log : LogHook ? = null,
121+ ): VerificationResult {
122+ val verificationEvent = internalVerify(certPath, challenge)
123+ log?.logVerificationEvent(verificationEvent)
124+ return verificationEvent.result
125+ }
126+
127+ internal fun internalVerify (
128+ certPath : KeyAttestationCertPath ,
129+ challenge : ByteArray? ,
130+ ): VerificationEvent {
131+ val serialNumbers =
132+ certPath.certificatesWithAnchor.subList(1 , certPath.certificatesWithAnchor.size).map {
133+ it.serialNumber.toString(16 )
134+ }
96135 val certPathValidator = CertPathValidator .getInstance(" KeyAttestation" )
97136 val certPathParameters =
98137 PKIXParameters (trustAnchorsSource()).apply {
99138 date = Date .from(instantSource.instant())
100139 addCertPathChecker(RevocationChecker (revokedSerialsSource()))
101140 }
141+
142+ val deviceInformation =
143+ if (certPath.provisioningMethod() == ProvisioningMethod .REMOTELY_PROVISIONED ) {
144+ certPath.attestationCert().provisioningInfo()
145+ } else {
146+ null
147+ }
102148 val pathValidationResult =
103149 try {
104150 certPathValidator.validate(certPath, certPathParameters) as PKIXCertPathValidatorResult
105151 } catch (e: CertPathValidatorException ) {
106- return VerificationResult .PathValidationFailure
152+ return VerificationEvent (
153+ inputChain = certPath.getCertificates(),
154+ result = VerificationResult .PathValidationFailure (e),
155+ certSerialNumbers = serialNumbers,
156+ provisioningInfoMap = deviceInformation,
157+ )
107158 }
108159
109160 val keyDescription =
110161 try {
111162 checkNotNull(certPath.leafCert().keyDescription()) { " Key attestation extension not found" }
112163 } catch (e: Exception ) {
113- return VerificationResult .ExtensionParsingFailure (e)
164+ return VerificationEvent (
165+ inputChain = certPath.getCertificates(),
166+ result = VerificationResult .ExtensionParsingFailure (e),
167+ certSerialNumbers = serialNumbers,
168+ provisioningInfoMap = deviceInformation,
169+ )
114170 }
115171
172+ val infoMessages = keyDescription.infoMessages
173+
116174 if (
117175 challenge != null &&
118176 keyDescription.attestationChallenge.asReadOnlyByteBuffer() != ByteBuffer .wrap(challenge)
119177 ) {
120- return VerificationResult .ChallengeMismatch
178+ return VerificationEvent (
179+ inputChain = certPath.getCertificates(),
180+ result = VerificationResult .ChallengeMismatch ,
181+ certSerialNumbers = serialNumbers,
182+ keyDescription = keyDescription,
183+ infoMessages = infoMessages,
184+ provisioningInfoMap = deviceInformation,
185+ )
121186 }
122187
123188 if (
124189 keyDescription.hardwareEnforced.origin == null ||
125190 keyDescription.hardwareEnforced.origin != Origin .GENERATED
126191 ) {
127- return VerificationResult .ExtensionConstraintViolation (
128- " origin != GENERATED: ${keyDescription.hardwareEnforced.origin} "
192+ return VerificationEvent (
193+ result =
194+ VerificationResult .ExtensionConstraintViolation (
195+ " hardwareEnforced.origin is not GENERATED: ${keyDescription.hardwareEnforced.origin} "
196+ ),
197+ inputChain = certPath.getCertificates(),
198+ certSerialNumbers = serialNumbers,
199+ keyDescription = keyDescription,
200+ infoMessages = infoMessages,
201+ provisioningInfoMap = deviceInformation,
129202 )
130203 }
131204
132205 val securityLevel =
133- if (keyDescription.attestationSecurityLevel == keyDescription.keymasterSecurityLevel ) {
206+ if (keyDescription.attestationSecurityLevel == keyDescription.keyMintSecurityLevel ) {
134207 keyDescription.attestationSecurityLevel
135208 } else {
136- return VerificationResult .ExtensionConstraintViolation (
137- " attestationSecurityLevel != keymasterSecurityLevel: ${keyDescription.attestationSecurityLevel} != ${keyDescription.keymasterSecurityLevel} "
209+ return VerificationEvent (
210+ result =
211+ VerificationResult .ExtensionConstraintViolation (
212+ " attestationSecurityLevel != keymintSecurityLevel: ${keyDescription.attestationSecurityLevel} != ${keyDescription.keyMintSecurityLevel} "
213+ ),
214+ inputChain = certPath.getCertificates(),
215+ certSerialNumbers = serialNumbers,
216+ keyDescription = keyDescription,
217+ infoMessages = infoMessages,
218+ provisioningInfoMap = deviceInformation,
138219 )
139220 }
140221 val rootOfTrust =
141222 keyDescription.hardwareEnforced.rootOfTrust
142- ? : return VerificationResult .ExtensionConstraintViolation (
143- " hardwareEnforced.rootOfTrust is null"
223+ ? : return VerificationEvent (
224+ result =
225+ VerificationResult .ExtensionConstraintViolation (" hardwareEnforced.rootOfTrust is null" ),
226+ keyDescription = keyDescription,
227+ inputChain = certPath.getCertificates(),
228+ certSerialNumbers = serialNumbers,
229+ infoMessages = infoMessages,
230+ provisioningInfoMap = deviceInformation,
144231 )
145- val deviceInformation =
146- if (certPath.provisioningMethod() == ProvisioningMethod .REMOTELY_PROVISIONED ) {
147- certPath.attestationCert().provisioningInfo()
148- } else {
149- null
150- }
151- return VerificationResult .Success (
152- pathValidationResult.publicKey,
153- keyDescription.attestationChallenge,
154- securityLevel,
155- rootOfTrust.verifiedBootState,
156- deviceInformation,
157- DeviceIdentity .parseFrom(keyDescription),
232+ return VerificationEvent (
233+ result =
234+ VerificationResult .Success (
235+ pathValidationResult.publicKey,
236+ keyDescription.attestationChallenge,
237+ securityLevel,
238+ rootOfTrust.verifiedBootState,
239+ deviceInformation,
240+ DeviceIdentity .parseFrom(keyDescription),
241+ ),
242+ inputChain = certPath.getCertificates(),
243+ certSerialNumbers = serialNumbers,
244+ keyDescription = keyDescription,
245+ infoMessages = infoMessages,
246+ provisioningInfoMap = deviceInformation,
158247 )
159248 }
160249}
0 commit comments