@@ -5,9 +5,12 @@ import at.asitplus.io.MultiBase
55import at.asitplus.signum.indispensable.encodeToDer
66import at.asitplus.signum.indispensable.io.Base64UrlStrict
77import at.asitplus.signum.indispensable.toKmpCertificate
8+ import at.asitplus.testballoon.matrix.CompactReport
9+ import at.asitplus.testballoon.matrix.ExecutionMode
10+ import at.asitplus.testballoon.matrix.matrixConfig
11+ import at.asitplus.testballoon.matrix.matrixSuite
812import com.android.keyattestation.verifier.KeyDescription
913import com.google.android.attestation.ParsedAttestationRecord
10- import at.asitplus.testballoon.matrix.*
1114import io.kotest.assertions.withClue
1215import io.kotest.matchers.nulls.shouldBeNull
1316import io.kotest.matchers.nulls.shouldNotBeNull
@@ -42,7 +45,7 @@ val DebugStmt.details get() = third
4245
4346
4447@OptIn(ExperimentalAtomicApi ::class )
45- val DebugStatementParserTest by matrixSuite {
48+ val DebugStatementParserTest by matrixSuite(matrixConfig { execution = ExecutionMode . Sequential }) {
4649 val json = Json { ignoreUnknownKeys = true }
4750 val classLoader = Thread .currentThread().contextClassLoader
4851
@@ -90,42 +93,12 @@ val DebugStatementParserTest by matrixSuite {
9093 " No private test data present" {}
9194 return @matrixSuite
9295 }
93-
94- data(" proofs" , proofs.withIndex().toList(), nameFn = { _, value -> " ${value.index} : ${value.value.first.size} certs" }) - { (index, it) ->
95- val attestationCertChain =
96- it.certificateChain.map {
97- val certBytes = Base64 .getUrlDecoder().decode(it)
98- catchingUnwrapped {
99- certificateFactory.generateCertificate(
100- ByteArrayInputStream (
101- certBytes
102- )
103- ) as X509Certificate
104- }.getOrNull()
105- }.filterNotNull()
106-
107- if (attestationCertChain.isNotEmpty()) {
108-
109-
110- val androidAttestationExtension = attestationCertChain.first().androidAttestationExtension
111- " from chain should be same as from leaf" {
112- androidAttestationExtension shouldBe attestationCertChain.androidAttestationExtension
113- }
114- " convert" - {
115- data(" certificates" , attestationCertChain, nameFn = { _, value -> value.subjectX500Principal.toString() }) test {
116- it.toKmpCertificate().isSuccess shouldBe true
117-
118- var own = it.toKmpCertificate().getOrThrow()
119- if (! own.encodeToDer().contentEquals(it.encoded)) {
120- System .err.println (" OWN: ${own.encodeToDer().toHexString()} " )
121- System .err.println (" ORI: ${it.encoded.toHexString()} " )
122- }
123- own.encodeToDer() shouldBe it.encoded
124- }
125- }
126-
127- if (index > 0 ) {
128- val prev = proofs[index - 1 ].certificateChain.map {
96+ compact(" ${proofs.size} collected real-world proofs" ) { report = CompactReport .FailuresOnly } - {
97+ data(
98+ proofs.withIndex().toList(),
99+ nameFn = { _, value -> " ${value.index} : ${value.value.first.size} certs" }) - { (index, it) ->
100+ val attestationCertChain =
101+ it.certificateChain.map {
129102 val certBytes = Base64 .getUrlDecoder().decode(it)
130103 catchingUnwrapped {
131104 certificateFactory.generateCertificate(
@@ -136,87 +109,126 @@ val DebugStatementParserTest by matrixSuite {
136109 }.getOrNull()
137110 }.filterNotNull()
138111
139- if (prev.isNotEmpty() && ((androidAttestationExtension != null ) && prev.androidAttestationExtension != null )) {
140- " concatenating two chains (current = $index ) should get the same exnt as just from the current chain" {
141- // leaf = fist in chain, root = last in chain. so we went the closest to CURRENT root. hence we need to prepent
142- // s.t. the chain gets extended below the leaf and not above the root
143- (prev + attestationCertChain).androidAttestationExtension shouldBe attestationCertChain.androidAttestationExtension
112+ if (attestationCertChain.isNotEmpty()) {
113+
114+
115+ val androidAttestationExtension = attestationCertChain.first().androidAttestationExtension
116+ " from chain should be same as from leaf" {
117+ androidAttestationExtension shouldBe attestationCertChain.androidAttestationExtension
118+ }
119+ " convert" - {
120+ data(
121+ " certificates" ,
122+ attestationCertChain,
123+ nameFn = { _, value -> value.subjectX500Principal.toString() }) test {
124+ it.toKmpCertificate().isSuccess shouldBe true
125+
126+ var own = it.toKmpCertificate().getOrThrow()
127+ if (! own.encodeToDer().contentEquals(it.encoded)) {
128+ System .err.println (" OWN: ${own.encodeToDer().toHexString()} " )
129+ System .err.println (" ORI: ${it.encoded.toHexString()} " )
130+ }
131+ own.encodeToDer() shouldBe it.encoded
132+ }
133+ }
134+
135+ if (index > 0 ) {
136+ val prev = proofs[index - 1 ].certificateChain.map {
137+ val certBytes = Base64 .getUrlDecoder().decode(it)
138+ catchingUnwrapped {
139+ certificateFactory.generateCertificate(
140+ ByteArrayInputStream (
141+ certBytes
142+ )
143+ ) as X509Certificate
144+ }.getOrNull()
145+ }.filterNotNull()
146+
147+ if (prev.isNotEmpty() && ((androidAttestationExtension != null ) && prev.androidAttestationExtension != null )) {
148+ " concatenating two chains (current = $index ) should get the same exnt as just from the current chain" {
149+ // leaf = fist in chain, root = last in chain. so we went the closest to CURRENT root. hence we need to prepent
150+ // s.t. the chain gets extended below the leaf and not above the root
151+ (prev + attestationCertChain).androidAttestationExtension shouldBe attestationCertChain.androidAttestationExtension
152+ }
144153 }
145154 }
146- }
147155
148- val fromGoogle =
149- catchingUnwrapped { ParsedAttestationRecord .createParsedAttestationRecord(attestationCertChain) }.getOrNull()
156+ val fromGoogle =
157+ catchingUnwrapped { ParsedAttestationRecord .createParsedAttestationRecord(attestationCertChain) }.getOrNull()
150158
151- " Own" {
152- if (fromGoogle == null ) {
153- System .err.println (" Old Google parser glitched out" )
154- val newParser = catchingUnwrapped { KeyDescription .parseFrom(attestationCertChain.first()) }
159+ " Own" {
160+ if (fromGoogle == null ) {
161+ System .err.println (" Old Google parser glitched out" )
162+ val newParser = catchingUnwrapped { KeyDescription .parseFrom(attestationCertChain.first()) }
155163
156- attestationCertChain.androidAttestationExtension.shouldBeNull()
157- return @invoke// well, well, well…}
158- }
159- androidAttestationExtension.shouldNotBeNull()
160- attestationCertChain.androidAttestationExtension shouldBe androidAttestationExtension
161- catchingUnwrapped {
162-
163- val reencoded = androidAttestationExtension.encodeToDer()
164- val original = (DEROctetString .fromByteArray(
165- attestationCertChain.first().getExtensionValue(
166- AttestationKeyDescription .oid.toString()
167- )
168- ) as ASN1OctetString ).octets
169- if (! reencoded.contentEquals(original)) {
170- System .err.println (" OWN: ${reencoded.toHexString()} " )
171- System .err.println (" ORI: ${original.toHexString()} " )
164+ attestationCertChain.androidAttestationExtension.shouldBeNull()
165+ return @invoke// well, well, well…}
172166 }
173- reencoded shouldBe original
167+ androidAttestationExtension.shouldNotBeNull()
168+ attestationCertChain.androidAttestationExtension shouldBe androidAttestationExtension
169+ catchingUnwrapped {
174170
175- }.getOrElse {
176- throw it
177- }
178- assertSemanticallyEqual(fromGoogle, androidAttestationExtension)
171+ val reencoded = androidAttestationExtension.encodeToDer()
172+ val original = (DEROctetString .fromByteArray(
173+ attestationCertChain.first().getExtensionValue(
174+ AttestationKeyDescription .oid.toString()
175+ )
176+ ) as ASN1OctetString ).octets
177+ if (! reencoded.contentEquals(original)) {
178+ System .err.println (" OWN: ${reencoded.toHexString()} " )
179+ System .err.println (" ORI: ${original.toHexString()} " )
180+ }
181+ reencoded shouldBe original
179182
183+ }.getOrElse {
184+ throw it
185+ }
186+ assertSemanticallyEqual(fromGoogle, androidAttestationExtension)
180187
181- }
182- " Roboto Engine Check" - {
183- val stmt = it
184- // not only is revocation slow. it will also have us run into rate limits
185- // plus, many have been revoked and we also want success results!
186- val rSupreme = stmt.cfg.copy(supremeParser = true , revocation = emptyList())
187- val rLegacy = stmt.cfg.copy(supremeParser = false , revocation = emptyList())
188-
189- compact(" config pairs" ) {
190- report = CompactReport .FailuresOnly
191- } - {
192- data(listOf (
193- rSupreme to rLegacy,
194- rSupreme.copy(patchLevel = PatchLevel (2025 , 1 )) to
195- rLegacy.copy(patchLevel = PatchLevel (2025 , 1 )),
196- rSupreme.copy(patchLevel = null , androidVersion = null ) to
197- rLegacy.copy(patchLevel = null , androidVersion = null ),
198- rSupreme.copy(requireRemoteKeyProvisioning = true ) to rLegacy.copy(requireRemoteKeyProvisioning = true ),
199- rSupreme.copy(ignoreLeafValidity = false ) to rLegacy.copy(ignoreLeafValidity = false ),
200- rSupreme.copy(requireStrongBox = true ) to rLegacy.copy(requireStrongBox = true ),
201- rSupreme.copy(enableSoftwareAttestation = true ) to rLegacy.copy(enableSoftwareAttestation = true ),
202- rSupreme.copy(allowBootloaderUnlock = true ) to rLegacy.copy(allowBootloaderUnlock = true ),
203- // get the "UnknownPackage" one step further
204- rSupreme.copy(
205- applications = listOf (
206- AndroidAttestationConfiguration .AppData (
207- " UnknownPackage" ,
208- signerFingerprints = setOf (byteArrayOf())
188+
189+ }
190+ " Roboto Engine Check" - {
191+ val stmt = it
192+ // not only is revocation slow. it will also have us run into rate limits
193+ // plus, many have been revoked and we also want success results!
194+ val rSupreme = stmt.cfg.copy(supremeParser = true , revocation = emptyList())
195+ val rLegacy = stmt.cfg.copy(supremeParser = false , revocation = emptyList())
196+
197+ data(
198+ " config pairs" ,
199+ listOf (
200+ rSupreme to rLegacy,
201+ rSupreme.copy(patchLevel = PatchLevel (2025 , 1 )) to
202+ rLegacy.copy(patchLevel = PatchLevel (2025 , 1 )),
203+ rSupreme.copy(patchLevel = null , androidVersion = null ) to
204+ rLegacy.copy(patchLevel = null , androidVersion = null ),
205+ rSupreme.copy(requireRemoteKeyProvisioning = true ) to rLegacy.copy(
206+ requireRemoteKeyProvisioning = true
207+ ),
208+ rSupreme.copy(ignoreLeafValidity = false ) to rLegacy.copy(ignoreLeafValidity = false ),
209+ rSupreme.copy(requireStrongBox = true ) to rLegacy.copy(requireStrongBox = true ),
210+ rSupreme.copy(enableSoftwareAttestation = true ) to rLegacy.copy(
211+ enableSoftwareAttestation = true
212+ ),
213+ rSupreme.copy(allowBootloaderUnlock = true ) to rLegacy.copy(allowBootloaderUnlock = true ),
214+ // get the "UnknownPackage" one step further
215+ rSupreme.copy(
216+ applications = listOf (
217+ AndroidAttestationConfiguration .AppData (
218+ " UnknownPackage" ,
219+ signerFingerprints = setOf (byteArrayOf())
220+ )
209221 )
210- )
211- ) to rLegacy.copy (
212- applications = listOf (
213- AndroidAttestationConfiguration . AppData (
214- " UnknownPackage " ,
215- signerFingerprints = setOf (byteArrayOf() )
222+ ) to rLegacy.copy(
223+ applications = listOf (
224+ AndroidAttestationConfiguration . AppData (
225+ " UnknownPackage " ,
226+ signerFingerprints = setOf (byteArrayOf())
227+ )
216228 )
217229 )
218230 )
219- )) test { (rSupreme, rLegacy) ->
231+ ) test { (rSupreme, rLegacy) ->
220232 val supreme = Roboto (rSupreme).verify(
221233 attestationCertChain,
222234 stmt.details.verificationTime,
@@ -234,8 +246,7 @@ val DebugStatementParserTest by matrixSuite {
234246 }
235247 }
236248 }
237- }
238-
239- } else " Empty chain" {}
249+ } else " Empty chain" {}
250+ }
240251 }
241252}
0 commit comments