Skip to content

Commit 8c2872b

Browse files
Bug/#57 (#67)
* - reject the encoded data if it contains a triplet of characters which, when decoded, results in an unsigned integer which is greater than 65535 (ffff in base 16); * - code formatting;
1 parent c801d01 commit 8c2872b

File tree

11 files changed

+78
-63
lines changed

11 files changed

+78
-63
lines changed

decoder/src/main/java/dgca/verifier/app/decoder/DefaultCertificateDecoder.kt

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -33,11 +33,10 @@ import dgca.verifier.app.decoder.model.GreenCertificate
3333
import java.util.zip.InflaterInputStream
3434

3535
@ExperimentalUnsignedTypes
36-
class DefaultCertificateDecoder(private val base45Decoder: Base45Decoder, private val greenCertificateMapper: GreenCertificateMapper = DefaultGreenCertificateMapper()) :
37-
CertificateDecoder {
38-
companion object {
39-
const val PREFIX = "HC1:"
40-
}
36+
class DefaultCertificateDecoder(
37+
private val base45Decoder: Base45Decoder,
38+
private val greenCertificateMapper: GreenCertificateMapper = DefaultGreenCertificateMapper()
39+
) : CertificateDecoder {
4140

4241
override fun decodeCertificate(qrCodeText: String): CertificateDecodingResult {
4342
val withoutPrefix: String = if (qrCodeText.startsWith(PREFIX)) qrCodeText.drop(PREFIX.length) else qrCodeText
@@ -65,19 +64,18 @@ class DefaultCertificateDecoder(private val base45Decoder: Base45Decoder, privat
6564
return CertificateDecodingResult.Error(CertificateDecodingError.GreenCertificateDecodingError(error))
6665
}
6766

68-
6967
return CertificateDecodingResult.Success(greenCertificate)
7068
}
7169

7270

7371
private fun ByteArray.decompressBase45DecodedData(): ByteArray {
7472
// ZLIB magic headers
7573
return if (this.size >= 2 && this[0] == 0x78.toByte() && (
76-
this[1] == 0x01.toByte() || // Level 1
77-
this[1] == 0x5E.toByte() || // Level 2 - 5
78-
this[1] == 0x9C.toByte() || // Level 6
79-
this[1] == 0xDA.toByte()
80-
)
74+
this[1] == 0x01.toByte() || // Level 1
75+
this[1] == 0x5E.toByte() || // Level 2 - 5
76+
this[1] == 0x9C.toByte() || // Level 6
77+
this[1] == 0xDA.toByte()
78+
)
8179
) {
8280
InflaterInputStream(this.inputStream()).readBytes()
8381
} else this
@@ -95,6 +93,7 @@ class DefaultCertificateDecoder(private val base45Decoder: Base45Decoder, privat
9593
return CoseData(content, objunprotected)
9694
}
9795
val objProtected = CBORObject.DecodeFromBytes(rgbProtected).get(key).GetByteString()
96+
9897
return CoseData(content, objProtected)
9998
}
10099

@@ -103,7 +102,10 @@ class DefaultCertificateDecoder(private val base45Decoder: Base45Decoder, privat
103102
val hcert = map[CwtHeaderKeys.HCERT.asCBOR()]
104103
val cborObject = hcert[CBORObject.FromObject(1)]
105104

106-
return greenCertificateMapper
107-
.readValue(cborObject)
105+
return greenCertificateMapper.readValue(cborObject)
106+
}
107+
108+
companion object {
109+
const val PREFIX = "HC1:"
108110
}
109111
}

decoder/src/main/java/dgca/verifier/app/decoder/base45/Base45Decoder.kt

Lines changed: 33 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,13 @@
2222

2323
package dgca.verifier.app.decoder.base45
2424

25-
import java.math.BigInteger
25+
// Lookup tables for faster processing
26+
internal val ENCODING_CHARSET = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:".encodeToByteArray()
27+
private val DECODING_CHARSET = ByteArray(256) { -1 }.also { charset ->
28+
ENCODING_CHARSET.forEachIndexed { index, byte ->
29+
charset[byte.toInt()] = index.toByte()
30+
}
31+
}
2632

2733
/**
2834
* The Base45 Data Decoding
@@ -32,35 +38,33 @@ import java.math.BigInteger
3238
@ExperimentalUnsignedTypes
3339
class Base45Decoder {
3440

35-
private val alphabet = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:"
36-
private val int45 = BigInteger.valueOf(45)
37-
38-
fun decode(input: String) =
39-
input.chunked(3).map(this::decodeThreeCharsPadded)
40-
.flatten().map { it.toByte() }.toByteArray()
41+
@Throws(Base45DecodeException::class)
42+
fun decode(input: String): ByteArray =
43+
input.toByteArray().asSequence().map {
44+
DECODING_CHARSET[it.toInt()].also { index ->
45+
if (index < 0) throw Base45DecodeException("Invalid characters in input.")
46+
}
47+
}.chunked(3) { chunk ->
48+
if (chunk.size < 2) throw Base45DecodeException("Invalid input length.")
49+
chunk.reversed().toInt(45).toBase(base = 256, count = chunk.size - 1).reversed()
50+
}.flatten().toList().toByteArray()
4151

42-
private fun decodeThreeCharsPadded(input: String): List<UByte> {
43-
val result = decodeThreeChars(input).toMutableList()
44-
when (input.length) {
45-
3 -> while (result.size < 2) result += 0U
52+
/** Converts integer to a list of [count] integers in the given [base]. */
53+
@Throws(Base45DecodeException::class)
54+
private fun Int.toBase(base: Int, count: Int): List<Byte> =
55+
mutableListOf<Byte>().apply {
56+
var tmp = this@toBase
57+
repeat(count) {
58+
add((tmp % base).toByte())
59+
tmp /= base
60+
}
61+
if (tmp != 0) throw Base45DecodeException("Invalid character sequence.")
4662
}
47-
return result.reversed()
48-
}
49-
50-
private fun decodeThreeChars(list: String) =
51-
generateSequenceByDivRem(fromThreeCharValue(list))
52-
.map { it.toUByte() }.toList()
5363

54-
private fun fromThreeCharValue(list: String): Long {
55-
return list.foldIndexed(0L, { index, acc: Long, element ->
56-
if (!alphabet.contains(element)) throw IllegalArgumentException()
57-
pow(int45, index) * alphabet.indexOf(element) + acc
58-
})
59-
}
60-
61-
private fun generateSequenceByDivRem(seed: Long) =
62-
generateSequence(seed) { if (it >= 256) it.div(256) else null }
63-
.map { it.rem(256).toInt() }
64-
65-
private fun pow(base: BigInteger, exp: Int) = base.pow(exp).toLong()
64+
/** Converts list of bytes in given [base] to an integer. */
65+
private fun List<Byte>.toInt(base: Int): Int =
66+
fold(0) { acc, i -> acc * base + i.toUByte().toInt() }
6667
}
68+
69+
/** Thrown when [Base45.decode] can't decode the input data. */
70+
class Base45DecodeException(message: String) : IllegalArgumentException(message)

decoder/src/main/java/dgca/verifier/app/decoder/cbor/CborService.kt

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,13 @@ data class GreenCertificateData(
3434
val issuedAt: ZonedDateTime,
3535
val expirationTime: ZonedDateTime
3636
) {
37+
3738
fun getNormalizedIssuingCountry(): String =
38-
(if (this.issuingCountry?.isNotBlank() == true) this.issuingCountry else this.greenCertificate.getIssuingCountry()).toLowerCase(
39-
Locale.ROOT
40-
)
39+
(if (this.issuingCountry?.isNotBlank() == true) {
40+
this.issuingCountry
41+
} else {
42+
this.greenCertificate.getIssuingCountry()
43+
}).toLowerCase(Locale.ROOT)
4144
}
4245

4346
/**
@@ -47,10 +50,7 @@ interface CborService {
4750

4851
fun decode(input: ByteArray, verificationResult: VerificationResult): GreenCertificate?
4952

50-
fun decodeData(
51-
input: ByteArray,
52-
verificationResult: VerificationResult
53-
): GreenCertificateData?
53+
fun decodeData(input: ByteArray, verificationResult: VerificationResult): GreenCertificateData?
5454

5555
fun getPayload(input: ByteArray): ByteArray?
5656
}

decoder/src/main/java/dgca/verifier/app/decoder/cbor/DefaultCborService.kt

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,8 @@ import java.time.ZoneOffset
3535
class DefaultCborService(private val greenCertificateMapper: GreenCertificateMapper = DefaultGreenCertificateMapper()) :
3636
CborService {
3737

38-
override fun decode(
39-
input: ByteArray,
40-
verificationResult: VerificationResult
41-
): GreenCertificate? = decodeData(input, verificationResult)?.greenCertificate
38+
override fun decode(input: ByteArray, verificationResult: VerificationResult): GreenCertificate? =
39+
decodeData(input, verificationResult)?.greenCertificate
4240

4341
override fun decodeData(
4442
input: ByteArray,

decoder/src/main/java/dgca/verifier/app/decoder/cbor/GreenCertificateMapper.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,5 +26,6 @@ import com.upokecenter.cbor.CBORObject
2626
import dgca.verifier.app.decoder.model.GreenCertificate
2727

2828
interface GreenCertificateMapper {
29+
2930
fun readValue(cborObject: CBORObject): GreenCertificate
3031
}

decoder/src/main/java/dgca/verifier/app/decoder/compression/DefaultCompressorService.kt

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -40,11 +40,12 @@ class DefaultCompressorService : CompressorService {
4040
override fun decode(input: ByteArray, verificationResult: VerificationResult): ByteArray? {
4141
verificationResult.zlibDecoded = false
4242
if (input.size >= 2 && input[0] == 0x78.toByte() // ZLIB magic headers
43-
&& (input[1] == 0x01.toByte() || // Level 1
44-
input[1] == 0x5E.toByte() || // Level 2 - 5
45-
input[1] == 0x9C.toByte() || // Level 6
46-
input[1] == 0xDA.toByte() // Level 7 - 9
47-
)) {
43+
&& (input[1] == 0x01.toByte() || // Level 1
44+
input[1] == 0x5E.toByte() || // Level 2 - 5
45+
input[1] == 0x9C.toByte() || // Level 6
46+
input[1] == 0xDA.toByte() // Level 7 - 9
47+
)
48+
) {
4849
return try {
4950
val inflaterStream = InflaterInputStream(input.inputStream())
5051
val outputStream = ByteArrayOutputStream(DEFAULT_BUFFER_SIZE)

decoder/src/main/java/dgca/verifier/app/decoder/cose/CryptoService.kt

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,5 +32,11 @@ import java.security.cert.Certificate
3232
interface CryptoService {
3333

3434
fun validate(cose: ByteArray, certificate: Certificate, verificationResult: VerificationResult)
35-
fun validate(cose: ByteArray, certificate: Certificate, verificationResult: VerificationResult, certificateType: CertificateType)
35+
36+
fun validate(
37+
cose: ByteArray,
38+
certificate: Certificate,
39+
verificationResult: VerificationResult,
40+
certificateType: CertificateType
41+
)
3642
}
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
package dgca.verifier.app.decoder.model
22

33
enum class CertificateType {
4-
UNKNOWN,VACCINATION,RECOVERY,TEST
4+
UNKNOWN, VACCINATION, RECOVERY, TEST
55
}

decoder/src/main/java/dgca/verifier/app/decoder/model/CoseData.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ data class CoseData(
2626
val cbor: ByteArray,
2727
val kid: ByteArray? = null
2828
) {
29+
2930
override fun equals(other: Any?): Boolean {
3031
if (this === other) return true
3132
if (javaClass != other?.javaClass) return false

decoder/src/main/java/dgca/verifier/app/decoder/model/RecoveryStatement.kt

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,9 +52,6 @@ data class RecoveryStatement(
5252
val certificateIdentifier: String
5353

5454
) : Serializable {
55-
companion object {
56-
private val UTC_ZONE_ID: ZoneId = ZoneId.ofOffset("", ZoneOffset.UTC).normalized()
57-
}
5855

5956
fun isCertificateNotValidAnymore(): Boolean? =
6057
certificateValidUntil.toZonedDateTimeOrUtcLocal()?.isBefore(ZonedDateTime.now())
@@ -83,4 +80,8 @@ data class RecoveryStatement(
8380
private fun String.toZonedDateTimeOrUtcLocal(): ZonedDateTime? =
8481
this.toZonedDateTime()?.withZoneSameInstant(UTC_ZONE_ID) ?: this.toLocalDateTime()
8582
?.atZone(UTC_ZONE_ID) ?: this.toLocalDate()?.atStartOfDay(UTC_ZONE_ID)
83+
84+
companion object {
85+
private val UTC_ZONE_ID: ZoneId = ZoneId.ofOffset("", ZoneOffset.UTC).normalized()
86+
}
8687
}

0 commit comments

Comments
 (0)