Skip to content

Commit

Permalink
Bug/#57 (#67)
Browse files Browse the repository at this point in the history
* - 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;
  • Loading branch information
MykhailoNester committed Nov 1, 2021
1 parent c801d01 commit 8c2872b
Show file tree
Hide file tree
Showing 11 changed files with 78 additions and 63 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,10 @@ import dgca.verifier.app.decoder.model.GreenCertificate
import java.util.zip.InflaterInputStream

@ExperimentalUnsignedTypes
class DefaultCertificateDecoder(private val base45Decoder: Base45Decoder, private val greenCertificateMapper: GreenCertificateMapper = DefaultGreenCertificateMapper()) :
CertificateDecoder {
companion object {
const val PREFIX = "HC1:"
}
class DefaultCertificateDecoder(
private val base45Decoder: Base45Decoder,
private val greenCertificateMapper: GreenCertificateMapper = DefaultGreenCertificateMapper()
) : CertificateDecoder {

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


return CertificateDecodingResult.Success(greenCertificate)
}


private fun ByteArray.decompressBase45DecodedData(): ByteArray {
// ZLIB magic headers
return if (this.size >= 2 && this[0] == 0x78.toByte() && (
this[1] == 0x01.toByte() || // Level 1
this[1] == 0x5E.toByte() || // Level 2 - 5
this[1] == 0x9C.toByte() || // Level 6
this[1] == 0xDA.toByte()
)
this[1] == 0x01.toByte() || // Level 1
this[1] == 0x5E.toByte() || // Level 2 - 5
this[1] == 0x9C.toByte() || // Level 6
this[1] == 0xDA.toByte()
)
) {
InflaterInputStream(this.inputStream()).readBytes()
} else this
Expand All @@ -95,6 +93,7 @@ class DefaultCertificateDecoder(private val base45Decoder: Base45Decoder, privat
return CoseData(content, objunprotected)
}
val objProtected = CBORObject.DecodeFromBytes(rgbProtected).get(key).GetByteString()

return CoseData(content, objProtected)
}

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

return greenCertificateMapper
.readValue(cborObject)
return greenCertificateMapper.readValue(cborObject)
}

companion object {
const val PREFIX = "HC1:"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,13 @@

package dgca.verifier.app.decoder.base45

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

/**
* The Base45 Data Decoding
Expand All @@ -32,35 +38,33 @@ import java.math.BigInteger
@ExperimentalUnsignedTypes
class Base45Decoder {

private val alphabet = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:"
private val int45 = BigInteger.valueOf(45)

fun decode(input: String) =
input.chunked(3).map(this::decodeThreeCharsPadded)
.flatten().map { it.toByte() }.toByteArray()
@Throws(Base45DecodeException::class)
fun decode(input: String): ByteArray =
input.toByteArray().asSequence().map {
DECODING_CHARSET[it.toInt()].also { index ->
if (index < 0) throw Base45DecodeException("Invalid characters in input.")
}
}.chunked(3) { chunk ->
if (chunk.size < 2) throw Base45DecodeException("Invalid input length.")
chunk.reversed().toInt(45).toBase(base = 256, count = chunk.size - 1).reversed()
}.flatten().toList().toByteArray()

private fun decodeThreeCharsPadded(input: String): List<UByte> {
val result = decodeThreeChars(input).toMutableList()
when (input.length) {
3 -> while (result.size < 2) result += 0U
/** Converts integer to a list of [count] integers in the given [base]. */
@Throws(Base45DecodeException::class)
private fun Int.toBase(base: Int, count: Int): List<Byte> =
mutableListOf<Byte>().apply {
var tmp = this@toBase
repeat(count) {
add((tmp % base).toByte())
tmp /= base
}
if (tmp != 0) throw Base45DecodeException("Invalid character sequence.")
}
return result.reversed()
}

private fun decodeThreeChars(list: String) =
generateSequenceByDivRem(fromThreeCharValue(list))
.map { it.toUByte() }.toList()

private fun fromThreeCharValue(list: String): Long {
return list.foldIndexed(0L, { index, acc: Long, element ->
if (!alphabet.contains(element)) throw IllegalArgumentException()
pow(int45, index) * alphabet.indexOf(element) + acc
})
}

private fun generateSequenceByDivRem(seed: Long) =
generateSequence(seed) { if (it >= 256) it.div(256) else null }
.map { it.rem(256).toInt() }

private fun pow(base: BigInteger, exp: Int) = base.pow(exp).toLong()
/** Converts list of bytes in given [base] to an integer. */
private fun List<Byte>.toInt(base: Int): Int =
fold(0) { acc, i -> acc * base + i.toUByte().toInt() }
}

/** Thrown when [Base45.decode] can't decode the input data. */
class Base45DecodeException(message: String) : IllegalArgumentException(message)
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,13 @@ data class GreenCertificateData(
val issuedAt: ZonedDateTime,
val expirationTime: ZonedDateTime
) {

fun getNormalizedIssuingCountry(): String =
(if (this.issuingCountry?.isNotBlank() == true) this.issuingCountry else this.greenCertificate.getIssuingCountry()).toLowerCase(
Locale.ROOT
)
(if (this.issuingCountry?.isNotBlank() == true) {
this.issuingCountry
} else {
this.greenCertificate.getIssuingCountry()
}).toLowerCase(Locale.ROOT)
}

/**
Expand All @@ -47,10 +50,7 @@ interface CborService {

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

fun decodeData(
input: ByteArray,
verificationResult: VerificationResult
): GreenCertificateData?
fun decodeData(input: ByteArray, verificationResult: VerificationResult): GreenCertificateData?

fun getPayload(input: ByteArray): ByteArray?
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,8 @@ import java.time.ZoneOffset
class DefaultCborService(private val greenCertificateMapper: GreenCertificateMapper = DefaultGreenCertificateMapper()) :
CborService {

override fun decode(
input: ByteArray,
verificationResult: VerificationResult
): GreenCertificate? = decodeData(input, verificationResult)?.greenCertificate
override fun decode(input: ByteArray, verificationResult: VerificationResult): GreenCertificate? =
decodeData(input, verificationResult)?.greenCertificate

override fun decodeData(
input: ByteArray,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,5 +26,6 @@ import com.upokecenter.cbor.CBORObject
import dgca.verifier.app.decoder.model.GreenCertificate

interface GreenCertificateMapper {

fun readValue(cborObject: CBORObject): GreenCertificate
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,12 @@ class DefaultCompressorService : CompressorService {
override fun decode(input: ByteArray, verificationResult: VerificationResult): ByteArray? {
verificationResult.zlibDecoded = false
if (input.size >= 2 && input[0] == 0x78.toByte() // ZLIB magic headers
&& (input[1] == 0x01.toByte() || // Level 1
input[1] == 0x5E.toByte() || // Level 2 - 5
input[1] == 0x9C.toByte() || // Level 6
input[1] == 0xDA.toByte() // Level 7 - 9
)) {
&& (input[1] == 0x01.toByte() || // Level 1
input[1] == 0x5E.toByte() || // Level 2 - 5
input[1] == 0x9C.toByte() || // Level 6
input[1] == 0xDA.toByte() // Level 7 - 9
)
) {
return try {
val inflaterStream = InflaterInputStream(input.inputStream())
val outputStream = ByteArrayOutputStream(DEFAULT_BUFFER_SIZE)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,5 +32,11 @@ import java.security.cert.Certificate
interface CryptoService {

fun validate(cose: ByteArray, certificate: Certificate, verificationResult: VerificationResult)
fun validate(cose: ByteArray, certificate: Certificate, verificationResult: VerificationResult, certificateType: CertificateType)

fun validate(
cose: ByteArray,
certificate: Certificate,
verificationResult: VerificationResult,
certificateType: CertificateType
)
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
package dgca.verifier.app.decoder.model

enum class CertificateType {
UNKNOWN,VACCINATION,RECOVERY,TEST
UNKNOWN, VACCINATION, RECOVERY, TEST
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ data class CoseData(
val cbor: ByteArray,
val kid: ByteArray? = null
) {

override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,6 @@ data class RecoveryStatement(
val certificateIdentifier: String

) : Serializable {
companion object {
private val UTC_ZONE_ID: ZoneId = ZoneId.ofOffset("", ZoneOffset.UTC).normalized()
}

fun isCertificateNotValidAnymore(): Boolean? =
certificateValidUntil.toZonedDateTimeOrUtcLocal()?.isBefore(ZonedDateTime.now())
Expand Down Expand Up @@ -83,4 +80,8 @@ data class RecoveryStatement(
private fun String.toZonedDateTimeOrUtcLocal(): ZonedDateTime? =
this.toZonedDateTime()?.withZoneSameInstant(UTC_ZONE_ID) ?: this.toLocalDateTime()
?.atZone(UTC_ZONE_ID) ?: this.toLocalDate()?.atStartOfDay(UTC_ZONE_ID)

companion object {
private val UTC_ZONE_ID: ZoneId = ZoneId.ofOffset("", ZoneOffset.UTC).normalized()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import java.security.cert.*


class X509 {

private val OID_TEST = "1.3.6.1.4.1.1847.2021.1.1"
private val OID_ALT_TEST = "1.3.6.1.4.1.0.1847.2021.1.1"
private val OID_VACCINATION = "1.3.6.1.4.1.1847.2021.1.2"
Expand Down

0 comments on commit 8c2872b

Please sign in to comment.