@@ -38,6 +38,9 @@ import java.math.BigInteger
3838import java.nio.ByteBuffer
3939import java.nio.charset.CodingErrorAction
4040import java.security.cert.X509Certificate
41+ import java.time.YearMonth
42+ import java.time.format.DateTimeFormatter
43+ import java.time.format.DateTimeParseException
4144import kotlin.text.Charsets.UTF_8
4245import org.bouncycastle.asn1.ASN1Boolean
4346import 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+
612650private fun ASN1Encodable.toRootOfTrust (): RootOfTrust {
613651 check(this is ASN1Sequence ) { " Object must be an ASN1Sequence, was ${this ::class .simpleName} " }
614652 return RootOfTrust .from(this )
0 commit comments