Skip to content

Commit c5a2e8d

Browse files
carmenyhcopybara-github
authored andcommitted
Create PatchLevel class more consistent with status quo.
PiperOrigin-RevId: 772059484
1 parent fb70fd3 commit c5a2e8d

File tree

3 files changed

+67
-10
lines changed

3 files changed

+67
-10
lines changed

src/main/kotlin/Extension.kt

Lines changed: 44 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,9 @@ import java.math.BigInteger
3838
import java.nio.ByteBuffer
3939
import java.nio.charset.CodingErrorAction
4040
import java.security.cert.X509Certificate
41+
import java.time.YearMonth
42+
import java.time.format.DateTimeFormatter
43+
import java.time.format.DateTimeParseException
4144
import kotlin.text.Charsets.UTF_8
4245
import org.bouncycastle.asn1.ASN1Boolean
4346
import org.bouncycastle.asn1.ASN1Encodable
@@ -291,7 +294,7 @@ data class AuthorizationList(
291294
val rollbackResistant: Boolean? = null,
292295
val rootOfTrust: RootOfTrust? = null,
293296
val osVersion: BigInteger? = null,
294-
val osPatchLevel: BigInteger? = null,
297+
val osPatchLevel: PatchLevel? = null,
295298
val attestationApplicationId: AttestationApplicationId? = null,
296299
val attestationIdBrand: String? = null,
297300
val attestationIdDevice: String? = null,
@@ -301,8 +304,8 @@ data class AuthorizationList(
301304
val attestationIdMeid: String? = null,
302305
val attestationIdManufacturer: String? = null,
303306
val attestationIdModel: String? = null,
304-
val vendorPatchLevel: BigInteger? = null,
305-
val bootPatchLevel: BigInteger? = null,
307+
val vendorPatchLevel: PatchLevel? = null,
308+
val bootPatchLevel: PatchLevel? = null,
306309
val attestationIdSecondImei: String? = null,
307310
val moduleHash: ByteString? = null,
308311
) {
@@ -431,7 +434,7 @@ data class AuthorizationList(
431434
rollbackResistant = if (objects.containsKey(KeyMintTag.ROLLBACK_RESISTANT)) true else null,
432435
rootOfTrust = objects[KeyMintTag.ROOT_OF_TRUST]?.toRootOfTrust(),
433436
osVersion = objects[KeyMintTag.OS_VERSION]?.toInt(),
434-
osPatchLevel = objects[KeyMintTag.OS_PATCH_LEVEL]?.toInt(),
437+
osPatchLevel = objects[KeyMintTag.OS_PATCH_LEVEL]?.toPatchLevel(),
435438
attestationApplicationId =
436439
objects[KeyMintTag.ATTESTATION_APPLICATION_ID]?.toAttestationApplicationId(),
437440
attestationIdBrand = objects[KeyMintTag.ATTESTATION_ID_BRAND]?.toStr(),
@@ -442,15 +445,48 @@ data class AuthorizationList(
442445
attestationIdMeid = objects[KeyMintTag.ATTESTATION_ID_MEID]?.toStr(),
443446
attestationIdManufacturer = objects[KeyMintTag.ATTESTATION_ID_MANUFACTURER]?.toStr(),
444447
attestationIdModel = objects[KeyMintTag.ATTESTATION_ID_MODEL]?.toStr(),
445-
vendorPatchLevel = objects[KeyMintTag.VENDOR_PATCH_LEVEL]?.toInt(),
446-
bootPatchLevel = objects[KeyMintTag.BOOT_PATCH_LEVEL]?.toInt(),
448+
vendorPatchLevel = objects[KeyMintTag.VENDOR_PATCH_LEVEL]?.toPatchLevel(),
449+
bootPatchLevel = objects[KeyMintTag.BOOT_PATCH_LEVEL]?.toPatchLevel(),
447450
attestationIdSecondImei = objects[KeyMintTag.ATTESTATION_ID_SECOND_IMEI]?.toStr(),
448451
moduleHash = objects[KeyMintTag.MODULE_HASH]?.toByteString(),
449452
)
450453
}
451454
}
452455
}
453456

457+
@Immutable
458+
data class PatchLevel(val yearMonth: YearMonth, val version: Int? = null) {
459+
fun toAsn1(): ASN1Encodable = ASN1Integer(this.toString().toBigInteger())
460+
461+
override fun toString(): String {
462+
val yearMonthString = DateTimeFormatter.ofPattern("yyyyMM").format(this.yearMonth)
463+
if (this.version != null) return String.format("$yearMonthString%02d", this.version)
464+
return yearMonthString
465+
}
466+
467+
companion object {
468+
fun from(patchLevel: ASN1Encodable): PatchLevel? {
469+
check(patchLevel is ASN1Integer) { "Must be an ASN1Integer, was ${this::class.simpleName}" }
470+
return from(patchLevel.value.toString())
471+
}
472+
473+
@JvmStatic
474+
fun from(patchLevel: String): PatchLevel? {
475+
if (patchLevel.length != 6 && patchLevel.length != 8) {
476+
return null
477+
}
478+
try {
479+
val yearMonth =
480+
DateTimeFormatter.ofPattern("yyyyMM").parse(patchLevel.substring(0, 6), YearMonth::from)
481+
val version = if (patchLevel.length == 8) patchLevel.substring(6).toInt() else null
482+
return PatchLevel(yearMonth, version)
483+
} catch (e: DateTimeParseException) {
484+
return null
485+
}
486+
}
487+
}
488+
}
489+
454490
/**
455491
* Representation of the AttestationApplicationId sequence contained within [AuthorizationList].
456492
*
@@ -609,6 +645,8 @@ private fun ASN1Encodable.toInt(): BigInteger {
609645
return this.value
610646
}
611647

648+
private fun ASN1Encodable.toPatchLevel(): PatchLevel? = PatchLevel.from(this)
649+
612650
private fun ASN1Encodable.toRootOfTrust(): RootOfTrust {
613651
check(this is ASN1Sequence) { "Object must be an ASN1Sequence, was ${this::class.simpleName}" }
614652
return RootOfTrust.from(this)

src/main/kotlin/testing/TestUtils.kt

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
package com.android.keyattestation.verifier.testing
1818

1919
import com.android.keyattestation.verifier.KeyDescription
20+
import com.android.keyattestation.verifier.PatchLevel
2021
import com.android.keyattestation.verifier.asX509Certificate
2122
import com.android.keyattestation.verifier.provider.KeyAttestationCertPath
2223

@@ -89,7 +90,15 @@ object BigIntegerAdapter {
8990
@ToJson fun toJson(value: BigInteger) = value.toString()
9091
}
9192

92-
private val moshi = Moshi.Builder().add(Base64ByteStringAdapter).add(BigIntegerAdapter).build()
93+
// Assumes everything is well formatted.
94+
object PatchLevelAdapter {
95+
@ToJson fun toJson(patchLevel: PatchLevel) = patchLevel.toString()
96+
97+
@FromJson fun fromJson(patchLevel: String) = PatchLevel.from(patchLevel)
98+
}
99+
100+
private val moshi =
101+
Moshi.Builder().add(Base64ByteStringAdapter).add(BigIntegerAdapter).add(PatchLevelAdapter).build()
93102
private val keyDescriptionAdapter = moshi.adapter(KeyDescription::class.java)
94103

95104
internal fun KeyDescription.toJson() = keyDescriptionAdapter.toJson(this)

src/test/kotlin/ExtensionTest.kt

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ import com.google.common.truth.Truth.assertThat
2424
import com.google.protobuf.ByteString
2525
import com.google.testing.junit.testparameterinjector.TestParameter
2626
import com.google.testing.junit.testparameterinjector.TestParameterInjector
27+
import com.google.testing.junit.testparameterinjector.TestParameters
28+
import java.time.YearMonth
2729
import java.util.Base64
2830
import kotlin.io.path.Path
2931
import kotlin.io.path.inputStream
@@ -76,6 +78,14 @@ class ExtensionTest {
7678
}
7779
}
7880

81+
@Test
82+
@TestParameters("{patchLevel: '202400'}")
83+
@TestParameters("{patchLevel: '00000000'}")
84+
@TestParameters("{patchLevel: '2000231'}")
85+
fun parseFrom_invalidPatchLevel_returnsNull(patchLevel: String) {
86+
assertThat(PatchLevel.from(patchLevel)).isNull()
87+
}
88+
7989
@Test
8090
fun keyDescription_encodeToAsn1_expectedResult() {
8191
val authorizationList =
@@ -105,7 +115,7 @@ class ExtensionTest {
105115
verifiedBootHash = ByteString.copyFromUtf8("verifiedBootHash"),
106116
),
107117
osVersion = 11.toBigInteger(),
108-
osPatchLevel = 5.toBigInteger(),
118+
osPatchLevel = PatchLevel(yearMonth = YearMonth.of(2024, 4)),
109119
attestationApplicationId =
110120
AttestationApplicationId(
111121
packages = setOf(AttestationPackageInfo(name = "name", version = 1.toBigInteger())),
@@ -119,8 +129,8 @@ class ExtensionTest {
119129
attestationIdMeid = "meid",
120130
attestationIdManufacturer = "manufacturer",
121131
attestationIdModel = "model",
122-
vendorPatchLevel = 6.toBigInteger(),
123-
bootPatchLevel = 7.toBigInteger(),
132+
vendorPatchLevel = PatchLevel(YearMonth.of(2024, 4), 5),
133+
bootPatchLevel = PatchLevel(YearMonth.of(2024, 4), 5),
124134
attestationIdSecondImei = "secondImei",
125135
)
126136
val keyDescription =

0 commit comments

Comments
 (0)