Skip to content

Commit fbe726a

Browse files
carmenyhcopybara-github
authored andcommitted
Add mechanism for adding debug logging.
PiperOrigin-RevId: 769130000
1 parent 1b5f6db commit fbe726a

17 files changed

+182
-81
lines changed

src/main/kotlin/Extension.kt

Lines changed: 33 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -100,14 +100,14 @@ data class ProvisioningInfoMap(
100100

101101
@JsonClass(generateAdapter = true)
102102
data class DeviceIdentity(
103-
val brand: String?,
104-
val device: String?,
105-
val product: String?,
106-
val serialNumber: String?,
107-
val imeis: Set<String>,
108-
val meid: String?,
109-
val manufacturer: String?,
110-
val model: String?,
103+
val brand: String? = null,
104+
val device: String? = null,
105+
val product: String? = null,
106+
val serialNumber: String? = null,
107+
val imeis: Set<String> = emptySet(),
108+
val meid: String? = null,
109+
val manufacturer: String? = null,
110+
val model: String? = null,
111111
) {
112112
companion object {
113113
@JvmStatic
@@ -133,12 +133,13 @@ data class DeviceIdentity(
133133
data class KeyDescription(
134134
val attestationVersion: BigInteger,
135135
val attestationSecurityLevel: SecurityLevel,
136-
val keymasterVersion: BigInteger,
137-
val keymasterSecurityLevel: SecurityLevel,
136+
val keyMintVersion: BigInteger,
137+
val keyMintSecurityLevel: SecurityLevel,
138138
val attestationChallenge: ByteString,
139139
val uniqueId: ByteString,
140140
val softwareEnforced: AuthorizationList,
141141
val hardwareEnforced: AuthorizationList,
142+
@SuppressWarnings("Immutable") @Transient val infoMessages: List<String>? = listOf(),
142143
) {
143144
fun asExtension(): Extension {
144145
return Extension(OID, /* critical= */ false, encodeToAsn1())
@@ -148,8 +149,8 @@ data class KeyDescription(
148149
buildList {
149150
add(attestationVersion.toAsn1())
150151
add(attestationSecurityLevel.toAsn1())
151-
add(keymasterVersion.toAsn1())
152-
add(keymasterSecurityLevel.toAsn1())
152+
add(keyMintVersion.toAsn1())
153+
add(keyMintSecurityLevel.toAsn1())
153154
add(attestationChallenge.toAsn1())
154155
add(uniqueId.toAsn1())
155156
add(softwareEnforced.toAsn1())
@@ -182,15 +183,17 @@ data class KeyDescription(
182183

183184
private fun from(seq: ASN1Sequence): KeyDescription {
184185
require(seq.size() == 8)
186+
val infoMessages = mutableListOf<String>()
185187
return KeyDescription(
186188
attestationVersion = seq.getObjectAt(0).toInt(),
187189
attestationSecurityLevel = seq.getObjectAt(1).toSecurityLevel(),
188-
keymasterVersion = seq.getObjectAt(2).toInt(),
189-
keymasterSecurityLevel = seq.getObjectAt(3).toSecurityLevel(),
190+
keyMintVersion = seq.getObjectAt(2).toInt(),
191+
keyMintSecurityLevel = seq.getObjectAt(3).toSecurityLevel(),
190192
attestationChallenge = seq.getObjectAt(4).toByteString(),
191193
uniqueId = seq.getObjectAt(5).toByteString(),
192-
softwareEnforced = seq.getObjectAt(6).toAuthorizationList(),
193-
hardwareEnforced = seq.getObjectAt(7).toAuthorizationList(),
194+
softwareEnforced = seq.getObjectAt(6).toAuthorizationList(infoMessages = infoMessages),
195+
hardwareEnforced = seq.getObjectAt(7).toAuthorizationList(infoMessages = infoMessages),
196+
infoMessages = infoMessages.toList(),
194197
)
195198
}
196199
}
@@ -396,7 +399,11 @@ data class AuthorizationList(
396399
.let { DERSequence(it.toTypedArray()) }
397400

398401
internal companion object {
399-
fun from(seq: ASN1Sequence, validateTagOrder: Boolean = false): AuthorizationList {
402+
fun from(
403+
seq: ASN1Sequence,
404+
validateTagOrder: Boolean = false,
405+
infoMessages: MutableList<String>? = null,
406+
): AuthorizationList {
400407
val objects =
401408
seq.associate {
402409
require(it is ASN1TaggedObject) {
@@ -417,8 +424,12 @@ data class AuthorizationList(
417424
* of their tag numbers.
418425
*/
419426
// TODO: b/356172932 - Add test data once an example certificate is found in the wild.
420-
if (validateTagOrder && !objects.keys.zipWithNext().all { (lhs, rhs) -> rhs > lhs }) {
421-
throw IllegalArgumentException("AuthorizationList tags must appear in ascending order")
427+
if (!objects.keys.zipWithNext().all { (lhs, rhs) -> rhs > lhs }) {
428+
if (validateTagOrder) {
429+
throw IllegalArgumentException("AuthorizationList tags must appear in ascending order")
430+
} else {
431+
infoMessages?.add("AuthorizationList tags must appear in ascending order")
432+
}
422433
}
423434

424435
return AuthorizationList(
@@ -625,10 +636,11 @@ private fun ASN1Encodable.toAttestationApplicationId(): AttestationApplicationId
625636

626637
// TODO: b/356172932 - `validateTagOrder` should default to true after making it user configurable.
627638
private fun ASN1Encodable.toAuthorizationList(
628-
validateTagOrder: Boolean = false
639+
validateTagOrder: Boolean = false,
640+
infoMessages: MutableList<String>? = null,
629641
): AuthorizationList {
630642
check(this is ASN1Sequence) { "Object must be an ASN1Sequence, was ${this::class.simpleName}" }
631-
return AuthorizationList.from(this, validateTagOrder)
643+
return AuthorizationList.from(this, validateTagOrder, infoMessages)
632644
}
633645

634646
private fun ASN1Encodable.toBoolean(): Boolean {

src/main/kotlin/Verifier.kt

Lines changed: 120 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -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
}

src/main/kotlin/testing/KeyAttestationCertFactory.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -125,8 +125,8 @@ internal class KeyAttestationCertFactory(val fakeCalendar: FakeCalendar = FakeCa
125125
KeyDescription(
126126
attestationVersion = 1.toBigInteger(),
127127
attestationSecurityLevel = SecurityLevel.TRUSTED_ENVIRONMENT,
128-
keymasterVersion = 1.toBigInteger(),
129-
keymasterSecurityLevel = SecurityLevel.TRUSTED_ENVIRONMENT,
128+
keyMintVersion = 1.toBigInteger(),
129+
keyMintSecurityLevel = SecurityLevel.TRUSTED_ENVIRONMENT,
130130
attestationChallenge = ByteString.copyFromUtf8("A random 40-byte challenge for no reason"),
131131
uniqueId = ByteString.empty(),
132132
softwareEnforced = AuthorizationList(),

src/test/kotlin/ExtensionTest.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ class ExtensionTest {
8585
@Ignore("TODO: b/356172932 - Reenable test once enabling tag order validator is configurable.")
8686
fun parseFrom_tagsNotInAscendingOrder_Throws() {
8787
assertFailsWith<IllegalArgumentException> {
88-
readCertPath("invalid/tags_not_in_accending_order.pem").leafCert().keyDescription()
88+
readCertPath("invalid/tags_not_in_ascending_order.pem").leafCert().keyDescription()
8989
}
9090
}
9191

@@ -147,8 +147,8 @@ class ExtensionTest {
147147
KeyDescription(
148148
attestationVersion = 1.toBigInteger(),
149149
attestationSecurityLevel = SecurityLevel.SOFTWARE,
150-
keymasterVersion = 1.toBigInteger(),
151-
keymasterSecurityLevel = SecurityLevel.SOFTWARE,
150+
keyMintVersion = 1.toBigInteger(),
151+
keyMintSecurityLevel = SecurityLevel.SOFTWARE,
152152
attestationChallenge = ByteString.empty(),
153153
uniqueId = ByteString.empty(),
154154
softwareEnforced = authorizationList,

testdata/akita/sdk34/SB_RSA_NONE.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
{
22
"attestationVersion": "300",
33
"attestationSecurityLevel": "STRONG_BOX",
4-
"keymasterVersion": "300",
5-
"keymasterSecurityLevel": "STRONG_BOX",
4+
"keyMintVersion": "300",
5+
"keyMintSecurityLevel": "STRONG_BOX",
66
"attestationChallenge": "Y2hhbGxlbmdl",
77
"uniqueId": "",
88
"softwareEnforced": {

testdata/akita/sdk34/TEE_EC_NONE.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
{
22
"attestationVersion": "300",
33
"attestationSecurityLevel": "TRUSTED_ENVIRONMENT",
4-
"keymasterVersion": "300",
5-
"keymasterSecurityLevel": "TRUSTED_ENVIRONMENT",
4+
"keyMintVersion": "300",
5+
"keyMintSecurityLevel": "TRUSTED_ENVIRONMENT",
66
"attestationChallenge": "Y2hhbGxlbmdl",
77
"uniqueId": "",
88
"softwareEnforced": {

0 commit comments

Comments
 (0)