diff --git a/bbootimg/src/main/kotlin/avb/AVBInfo.kt b/bbootimg/src/main/kotlin/avb/AVBInfo.kt index d121aab..322ef87 100644 --- a/bbootimg/src/main/kotlin/avb/AVBInfo.kt +++ b/bbootimg/src/main/kotlin/avb/AVBInfo.kt @@ -14,15 +14,198 @@ package avb +import avb.alg.Algorithms import avb.blob.AuthBlob import avb.blob.AuxBlob import avb.blob.Footer import avb.blob.Header +import avb.desc.* +import cfig.Avb +import cfig.helper.Helper +import cfig.helper.Helper.Companion.paddingWith +import com.fasterxml.jackson.databind.ObjectMapper +import org.apache.commons.codec.binary.Hex +import org.slf4j.LoggerFactory +import java.io.ByteArrayInputStream +import java.io.File +import java.io.FileInputStream /* a wonderfaul base64 encoder/decoder: https://cryptii.com/base64-to-hex */ -class AVBInfo(var header: Header? = null, - var authBlob: AuthBlob? = null, - var auxBlob: AuxBlob? = null, - var footer: Footer? = null) +class AVBInfo( + var header: Header? = null, + var authBlob: AuthBlob? = null, + var auxBlob: AuxBlob? = null, + var footer: Footer? = null, +) { + fun encode(): ByteArray { + val alg = Algorithms.get(header!!.algorithm_type)!! + //3 - whole aux blob + val newAuxBlob = auxBlob?.encode(alg) ?: byteArrayOf() + //1 - whole header blob + val headerBlob = this.header!!.apply { + auxiliary_data_block_size = newAuxBlob.size.toLong() + authentication_data_block_size = Helper.round_to_multiple( + (alg.hash_num_bytes + alg.signature_num_bytes).toLong(), 64 + ) + + descriptors_offset = 0 + descriptors_size = auxBlob?.descriptorSize?.toLong() ?: 0 + + hash_offset = 0 + hash_size = alg.hash_num_bytes.toLong() + + signature_offset = alg.hash_num_bytes.toLong() + signature_size = alg.signature_num_bytes.toLong() + + public_key_offset = descriptors_size + public_key_size = AuxBlob.encodePubKey(alg).size.toLong() + + public_key_metadata_size = auxBlob!!.pubkeyMeta?.pkmd?.size?.toLong() ?: 0L + public_key_metadata_offset = public_key_offset + public_key_size + log.info("pkmd size: $public_key_metadata_size, pkmd offset : $public_key_metadata_offset") + }.encode() + //2 - auth blob + val authBlob = AuthBlob.createBlob(headerBlob, newAuxBlob, alg.name) + val ret = Helper.join(headerBlob, authBlob, newAuxBlob) + //Helper.dumpToFile("_debug_vbmeta_", ret) + return ret + } + + fun encodePadded(): ByteArray { + return encode().paddingWith(Avb.BLOCK_SIZE.toUInt()) + } + + fun dumpDefault(imageFile: String): AVBInfo { + val jsonFile = Avb.getJsonFileName(imageFile) + mapper.writerWithDefaultPrettyPrinter().writeValue(File(jsonFile), this) + log.info("VBMeta: $imageFile -> $jsonFile") + return this + } + + companion object { + private val log = LoggerFactory.getLogger(AVBInfo::class.java) + private val mapper = ObjectMapper() + + fun parseFrom(imageFile: String): AVBInfo { + log.info("parseFrom($imageFile) ...") + var footer: Footer? = null + var vbMetaOffset: Long = 0 + // footer + FileInputStream(imageFile).use { fis -> + fis.skip(File(imageFile).length() - Footer.SIZE) + try { + footer = Footer(fis) + vbMetaOffset = footer!!.vbMetaOffset + log.info("$imageFile: $footer") + } catch (e: IllegalArgumentException) { + log.info("image $imageFile has no AVB Footer") + } + } + // header + val rawHeaderBlob = ByteArray(Header.SIZE).apply { + FileInputStream(imageFile).use { fis -> + fis.skip(vbMetaOffset) + fis.read(this) + } + } + val vbMetaHeader = Header(ByteArrayInputStream(rawHeaderBlob)) + log.debug(vbMetaHeader.toString()) + log.debug(mapper.writerWithDefaultPrettyPrinter().writeValueAsString(vbMetaHeader)) + + val authBlockOffset = vbMetaOffset + Header.SIZE + val auxBlockOffset = authBlockOffset + vbMetaHeader.authentication_data_block_size + + val ai = AVBInfo(vbMetaHeader, null, AuxBlob(), footer) + // Auth blob + if (vbMetaHeader.authentication_data_block_size > 0) { + FileInputStream(imageFile).use { fis -> + fis.skip(vbMetaOffset) + fis.skip(Header.SIZE.toLong()) + fis.skip(vbMetaHeader.hash_offset) + val ba = ByteArray(vbMetaHeader.hash_size.toInt()) + fis.read(ba) + log.debug("Parsed Auth Hash (Header & Aux Blob): " + Hex.encodeHexString(ba)) + val bb = ByteArray(vbMetaHeader.signature_size.toInt()) + fis.read(bb) + log.debug("Parsed Auth Signature (of hash): " + Hex.encodeHexString(bb)) + ai.authBlob = AuthBlob() + ai.authBlob!!.offset = authBlockOffset + ai.authBlob!!.size = vbMetaHeader.authentication_data_block_size + ai.authBlob!!.hash = Hex.encodeHexString(ba) + ai.authBlob!!.signature = Hex.encodeHexString(bb) + } + } + // aux + val rawAuxBlob = ByteArray(vbMetaHeader.auxiliary_data_block_size.toInt()).apply { + FileInputStream(imageFile).use { fis -> + fis.skip(auxBlockOffset) + fis.read(this) + } + } + // aux - desc + var descriptors: List + if (vbMetaHeader.descriptors_size > 0) { + ByteArrayInputStream(rawAuxBlob).use { bis -> + bis.skip(vbMetaHeader.descriptors_offset) + descriptors = UnknownDescriptor.parseDescriptors2(bis, vbMetaHeader.descriptors_size) + } + descriptors.forEach { + log.debug(it.toString()) + when (it) { + is PropertyDescriptor -> { + ai.auxBlob!!.propertyDescriptors.add(it) + } + is HashDescriptor -> { + ai.auxBlob!!.hashDescriptors.add(it) + } + is KernelCmdlineDescriptor -> { + ai.auxBlob!!.kernelCmdlineDescriptors.add(it) + } + is HashTreeDescriptor -> { + ai.auxBlob!!.hashTreeDescriptors.add(it) + } + is ChainPartitionDescriptor -> { + ai.auxBlob!!.chainPartitionDescriptors.add(it) + } + is UnknownDescriptor -> { + ai.auxBlob!!.unknownDescriptors.add(it) + } + else -> { + throw IllegalArgumentException("invalid descriptor: $it") + } + } + } + } + // aux - pubkey + if (vbMetaHeader.public_key_size > 0) { + ai.auxBlob!!.pubkey = AuxBlob.PubKeyInfo() + ai.auxBlob!!.pubkey!!.offset = vbMetaHeader.public_key_offset + ai.auxBlob!!.pubkey!!.size = vbMetaHeader.public_key_size + + ByteArrayInputStream(rawAuxBlob).use { bis -> + bis.skip(vbMetaHeader.public_key_offset) + ai.auxBlob!!.pubkey!!.pubkey = ByteArray(vbMetaHeader.public_key_size.toInt()) + bis.read(ai.auxBlob!!.pubkey!!.pubkey) + log.debug("Parsed Pub Key: " + Hex.encodeHexString(ai.auxBlob!!.pubkey!!.pubkey)) + } + } + // aux - pkmd + if (vbMetaHeader.public_key_metadata_size > 0) { + ai.auxBlob!!.pubkeyMeta = AuxBlob.PubKeyMetadataInfo() + ai.auxBlob!!.pubkeyMeta!!.offset = vbMetaHeader.public_key_metadata_offset + ai.auxBlob!!.pubkeyMeta!!.size = vbMetaHeader.public_key_metadata_size + + ByteArrayInputStream(rawAuxBlob).use { bis -> + bis.skip(vbMetaHeader.public_key_metadata_offset) + ai.auxBlob!!.pubkeyMeta!!.pkmd = ByteArray(vbMetaHeader.public_key_metadata_size.toInt()) + bis.read(ai.auxBlob!!.pubkeyMeta!!.pkmd) + log.debug("Parsed Pub Key Metadata: " + Helper.toHexString(ai.auxBlob!!.pubkeyMeta!!.pkmd)) + } + } + log.debug("vbmeta info of [$imageFile] has been analyzed") + return ai + } + } +} \ No newline at end of file diff --git a/bbootimg/src/main/kotlin/avb/Avb.kt b/bbootimg/src/main/kotlin/avb/Avb.kt index 487868b..e4c0632 100644 --- a/bbootimg/src/main/kotlin/avb/Avb.kt +++ b/bbootimg/src/main/kotlin/avb/Avb.kt @@ -25,13 +25,11 @@ import cfig.helper.Helper import cfig.helper.Helper.Companion.paddingWith import cfig.helper.KeyHelper import cfig.helper.KeyHelper2 -import cfig.io.Struct3 import com.fasterxml.jackson.databind.ObjectMapper import org.apache.commons.codec.binary.Hex import org.apache.commons.exec.CommandLine import org.apache.commons.exec.DefaultExecutor import org.slf4j.LoggerFactory -import java.io.ByteArrayInputStream import java.io.File import java.io.FileInputStream import java.io.FileOutputStream @@ -42,12 +40,10 @@ import java.nio.file.StandardOpenOption class Avb { private val MAX_VBMETA_SIZE = 64 * 1024 private val MAX_FOOTER_SIZE = 4096 - private val BLOCK_SIZE = 4096 - private val DEBUG = false //migrated from: avbtool::Avb::addHashFooter fun addHashFooter( - image_file: String, + image_file: String, //file to be hashed and signed partition_size: Long, //aligned by Avb::BLOCK_SIZE partition_name: String, newAvbInfo: AVBInfo @@ -77,18 +73,13 @@ class Avb { this.auxBlob!!.hashDescriptors.add(hd) } - val vbmetaBlob = packVbMeta(newAvbInfo) - log.debug("vbmeta_blob: " + Helper.toHexString(vbmetaBlob)) - if (DEBUG) { - Helper.dumpToFile("hashDescriptor.vbmeta.blob", vbmetaBlob) - } - // image + padding val imgPaddingNeeded = Helper.round_to_multiple(newImageSize, BLOCK_SIZE) - newImageSize // + vbmeta + padding + val vbmetaBlob = newAvbInfo.encode() val vbmetaOffset = newImageSize + imgPaddingNeeded - val vbmetaBlobWithPadding = vbmetaBlob.paddingWith(BLOCK_SIZE.toUInt()) + val vbmetaBlobWithPadding = newAvbInfo.encodePadded() // + DONT_CARE chunk val vbmetaEndOffset = vbmetaOffset + vbmetaBlobWithPadding.size @@ -172,138 +163,6 @@ class Avb { } } - fun parseVbMeta(image_file: String, dumpFile: Boolean = true): AVBInfo { - log.info("parseVbMeta($image_file) ...") - val jsonFile = getJsonFileName(image_file) - var footer: Footer? = null - var vbMetaOffset: Long = 0 - // footer - FileInputStream(image_file).use { fis -> - fis.skip(File(image_file).length() - Footer.SIZE) - try { - footer = Footer(fis) - vbMetaOffset = footer!!.vbMetaOffset - log.info("$image_file: $footer") - } catch (e: IllegalArgumentException) { - log.info("image $image_file has no AVB Footer") - } - } - - // header - val rawHeaderBlob = ByteArray(Header.SIZE).apply { - FileInputStream(image_file).use { fis -> - fis.skip(vbMetaOffset) - fis.read(this) - } - } - val vbMetaHeader = Header(ByteArrayInputStream(rawHeaderBlob)) - log.debug(vbMetaHeader.toString()) - log.debug(ObjectMapper().writerWithDefaultPrettyPrinter().writeValueAsString(vbMetaHeader)) - - val authBlockOffset = vbMetaOffset + Header.SIZE - val auxBlockOffset = authBlockOffset + vbMetaHeader.authentication_data_block_size - - val ai = AVBInfo(vbMetaHeader, null, AuxBlob(), footer) - - // Auth blob - if (vbMetaHeader.authentication_data_block_size > 0) { - FileInputStream(image_file).use { fis -> - fis.skip(vbMetaOffset) - fis.skip(Header.SIZE.toLong()) - fis.skip(vbMetaHeader.hash_offset) - val ba = ByteArray(vbMetaHeader.hash_size.toInt()) - fis.read(ba) - log.debug("Parsed Auth Hash (Header & Aux Blob): " + Hex.encodeHexString(ba)) - val bb = ByteArray(vbMetaHeader.signature_size.toInt()) - fis.read(bb) - log.debug("Parsed Auth Signature (of hash): " + Hex.encodeHexString(bb)) - - ai.authBlob = AuthBlob() - ai.authBlob!!.offset = authBlockOffset - ai.authBlob!!.size = vbMetaHeader.authentication_data_block_size - ai.authBlob!!.hash = Hex.encodeHexString(ba) - ai.authBlob!!.signature = Hex.encodeHexString(bb) - } - } - - // aux - val rawAuxBlob = ByteArray(vbMetaHeader.auxiliary_data_block_size.toInt()).apply { - FileInputStream(image_file).use { fis -> - fis.skip(auxBlockOffset) - fis.read(this) - } - } - // aux - desc - var descriptors: List - if (vbMetaHeader.descriptors_size > 0) { - ByteArrayInputStream(rawAuxBlob).use { bis -> - bis.skip(vbMetaHeader.descriptors_offset) - descriptors = UnknownDescriptor.parseDescriptors2(bis, vbMetaHeader.descriptors_size) - } - descriptors.forEach { - log.debug(it.toString()) - when (it) { - is PropertyDescriptor -> { - ai.auxBlob!!.propertyDescriptors.add(it) - } - is HashDescriptor -> { - ai.auxBlob!!.hashDescriptors.add(it) - } - is KernelCmdlineDescriptor -> { - ai.auxBlob!!.kernelCmdlineDescriptors.add(it) - } - is HashTreeDescriptor -> { - ai.auxBlob!!.hashTreeDescriptors.add(it) - } - is ChainPartitionDescriptor -> { - ai.auxBlob!!.chainPartitionDescriptors.add(it) - } - is UnknownDescriptor -> { - ai.auxBlob!!.unknownDescriptors.add(it) - } - else -> { - throw IllegalArgumentException("invalid descriptor: $it") - } - } - } - } - // aux - pubkey - if (vbMetaHeader.public_key_size > 0) { - ai.auxBlob!!.pubkey = AuxBlob.PubKeyInfo() - ai.auxBlob!!.pubkey!!.offset = vbMetaHeader.public_key_offset - ai.auxBlob!!.pubkey!!.size = vbMetaHeader.public_key_size - - ByteArrayInputStream(rawAuxBlob).use { bis -> - bis.skip(vbMetaHeader.public_key_offset) - ai.auxBlob!!.pubkey!!.pubkey = ByteArray(vbMetaHeader.public_key_size.toInt()) - bis.read(ai.auxBlob!!.pubkey!!.pubkey) - log.debug("Parsed Pub Key: " + Hex.encodeHexString(ai.auxBlob!!.pubkey!!.pubkey)) - } - } - // aux - pkmd - if (vbMetaHeader.public_key_metadata_size > 0) { - ai.auxBlob!!.pubkeyMeta = AuxBlob.PubKeyMetadataInfo() - ai.auxBlob!!.pubkeyMeta!!.offset = vbMetaHeader.public_key_metadata_offset - ai.auxBlob!!.pubkeyMeta!!.size = vbMetaHeader.public_key_metadata_size - - ByteArrayInputStream(rawAuxBlob).use { bis -> - bis.skip(vbMetaHeader.public_key_metadata_offset) - ai.auxBlob!!.pubkeyMeta!!.pkmd = ByteArray(vbMetaHeader.public_key_metadata_size.toInt()) - bis.read(ai.auxBlob!!.pubkeyMeta!!.pkmd) - log.debug("Parsed Pub Key Metadata: " + Helper.toHexString(ai.auxBlob!!.pubkeyMeta!!.pkmd)) - } - } - - if (dumpFile) { - ObjectMapper().writerWithDefaultPrettyPrinter().writeValue(File(jsonFile), ai) - log.info("parseVbMeta($image_file) done. Result: $jsonFile") - } else { - log.debug("vbmeta info of [$image_file] has been analyzed, no dummping") - } - - return ai - } - fun verify(ai: AVBInfo, image_file: String, parent: String = ""): Array { val ret: Array = arrayOf(true, "") val localParent = if (parent.isEmpty()) image_file else parent @@ -397,54 +256,9 @@ class Avb { return ret } - private fun packVbMeta(info: AVBInfo? = null, image_file: String? = null): ByteArray { - val ai = info ?: ObjectMapper().readValue(File(getJsonFileName(image_file!!)), AVBInfo::class.java) - val alg = Algorithms.get(ai.header!!.algorithm_type)!! - - //3 - whole aux blob - val auxBlob = ai.auxBlob?.encode(alg) ?: byteArrayOf() - - //1 - whole header blob - val headerBlob = ai.header!!.apply { - auxiliary_data_block_size = auxBlob.size.toLong() - authentication_data_block_size = Helper.round_to_multiple( - (alg.hash_num_bytes + alg.signature_num_bytes).toLong(), 64 - ) - - descriptors_offset = 0 - descriptors_size = ai.auxBlob?.descriptorSize?.toLong() ?: 0 - - hash_offset = 0 - hash_size = alg.hash_num_bytes.toLong() - - signature_offset = alg.hash_num_bytes.toLong() - signature_size = alg.signature_num_bytes.toLong() - - public_key_offset = descriptors_size - public_key_size = AuxBlob.encodePubKey(alg).size.toLong() - - public_key_metadata_size = ai.auxBlob!!.pubkeyMeta?.pkmd?.size?.toLong() ?: 0L - public_key_metadata_offset = public_key_offset + public_key_size - log.info("pkmd size: $public_key_metadata_size, pkmd offset : $public_key_metadata_offset") - }.encode() - - //2 - auth blob - val authBlob = AuthBlob.createBlob(headerBlob, auxBlob, alg.name) - - return Helper.join(headerBlob, authBlob, auxBlob) - } - - fun packVbMetaWithPadding(image_file: String? = null, info: AVBInfo? = null) { - val rawBlob = packVbMeta(info, image_file) - val paddingSize = Helper.round_to_multiple(rawBlob.size.toLong(), BLOCK_SIZE) - rawBlob.size - val paddedBlob = Helper.join(rawBlob, Struct3("${paddingSize}x").pack(null)) - log.info("raw vbmeta size ${rawBlob.size}, padding size $paddingSize, total blob size ${paddedBlob.size}") - log.info("Writing padded vbmeta to file: $image_file.signed") - Files.write(Paths.get("$image_file.signed"), paddedBlob, StandardOpenOption.CREATE) - } - companion object { private val log = LoggerFactory.getLogger(Avb::class.java) + const val BLOCK_SIZE = 4096 const val AVB_VERSION_MAJOR = 1 const val AVB_VERSION_MINOR = 1 const val AVB_VERSION_SUB = 0 @@ -483,7 +297,8 @@ class Avb { ObjectMapper().readValue(File(getJsonFileName(fileName)), AVBInfo::class.java).let { it.auxBlob!!.hashDescriptors.get(0).partition_name } - val newHashDesc = Avb().parseVbMeta("$fileName.signed", dumpFile = false) + //read hashDescriptor from image + val newHashDesc = AVBInfo.parseFrom("$fileName.signed") assert(newHashDesc.auxBlob!!.hashDescriptors.size == 1) var seq = -1 //means not found //main vbmeta @@ -501,9 +316,11 @@ class Avb { if (-1 == seq) { log.warn("main vbmeta doesn't have $partitionName hashDescriptor, won't update vbmeta.img") } else { + //add hashDescriptor back to main vbmeta val hd = newHashDesc.auxBlob!!.hashDescriptors.get(0).apply { this.sequence = seq } this.auxBlob!!.hashDescriptors.add(hd) - Avb().packVbMetaWithPadding("vbmeta.img", this) + log.info("Writing padded vbmeta to file: vbmeta.img.signed") + Files.write(Paths.get("vbmeta.img.signed"), encodePadded(), StandardOpenOption.CREATE) log.info("Updating vbmeta.img side by side (partition=$partitionName, seq=$seq) done") } } diff --git a/bbootimg/src/main/kotlin/avb/desc/ChainPartitionDescriptor.kt b/bbootimg/src/main/kotlin/avb/desc/ChainPartitionDescriptor.kt index d79c140..1db033a 100644 --- a/bbootimg/src/main/kotlin/avb/desc/ChainPartitionDescriptor.kt +++ b/bbootimg/src/main/kotlin/avb/desc/ChainPartitionDescriptor.kt @@ -14,6 +14,7 @@ package avb.desc +import avb.AVBInfo import cfig.Avb import cfig.helper.Helper import cfig.io.Struct3 @@ -84,7 +85,7 @@ class ChainPartitionDescriptor( val ret: Array = arrayOf(false, "file not found") for (item in image_files) { if (File(item).exists()) { - val subAi = Avb().parseVbMeta(item, false) + val subAi = AVBInfo.parseFrom(item) if (pubkey.contentEquals(subAi.auxBlob!!.pubkey!!.pubkey)) { log.info("VERIFY($parent): public key matches, PASS") return Avb().verify(subAi, item, parent) diff --git a/bbootimg/src/main/kotlin/bootimg/Signer.kt b/bbootimg/src/main/kotlin/bootimg/Signer.kt index c94a4b2..be17cf2 100644 --- a/bbootimg/src/main/kotlin/bootimg/Signer.kt +++ b/bbootimg/src/main/kotlin/bootimg/Signer.kt @@ -33,7 +33,7 @@ class Signer { fun signAVB(output: String, imageSize: Long, avbtool: String) { log.info("Adding hash_footer with verified-boot 2.0 style") val ai = ObjectMapper().readValue(File(getJsonFileName(output)), AVBInfo::class.java) - val alg = Algorithms.get(ai.header!!.algorithm_type.toInt()) + val alg = Algorithms.get(ai.header!!.algorithm_type) val bootDesc = ai.auxBlob!!.hashDescriptors[0] val newAvbInfo = ObjectMapper().readValue(File(getJsonFileName(output)), AVBInfo::class.java) diff --git a/bbootimg/src/main/kotlin/bootimg/v2/BootV2.kt b/bbootimg/src/main/kotlin/bootimg/v2/BootV2.kt index f5234f6..afb1c46 100644 --- a/bbootimg/src/main/kotlin/bootimg/v2/BootV2.kt +++ b/bbootimg/src/main/kotlin/bootimg/v2/BootV2.kt @@ -14,6 +14,7 @@ package cfig.bootimg.v2 +import avb.AVBInfo import cfig.Avb import cfig.EnvironmentVerifier import cfig.bootimg.Common @@ -38,7 +39,7 @@ data class BootV2( var ramdisk: CommArgs = CommArgs(), var secondBootloader: CommArgs? = null, var recoveryDtbo: CommArgsLong? = null, - var dtb: CommArgsLong? = null + var dtb: CommArgsLong? = null, ) { data class MiscInfo( var output: String = "", @@ -54,21 +55,21 @@ data class BootV2( var osPatchLevel: String? = null, var hash: ByteArray? = byteArrayOf(), var verify: String = "", - var imageSize: Long = 0 + var imageSize: Long = 0, ) data class CommArgs( var file: String? = null, var position: Long = 0, var size: Int = 0, - var loadOffset: Long = 0 + var loadOffset: Long = 0, ) data class CommArgsLong( var file: String? = null, var position: Long = 0, var size: Int = 0, - var loadOffset: Long = 0 + var loadOffset: Long = 0, ) companion object { @@ -223,7 +224,7 @@ data class BootV2( fun extractVBMeta(): BootV2 { if (this.info.verify.startsWith("VB2.0")) { - Avb().parseVbMeta(info.output) + AVBInfo.parseFrom(info.output).dumpDefault(info.output) if (File("vbmeta.img").exists()) { log.warn("Found vbmeta.img, parsing ...") VBMetaParser().unpack("vbmeta.img") diff --git a/bbootimg/src/main/kotlin/bootimg/v3/BootV3.kt b/bbootimg/src/main/kotlin/bootimg/v3/BootV3.kt index af6bdcb..241cb25 100644 --- a/bbootimg/src/main/kotlin/bootimg/v3/BootV3.kt +++ b/bbootimg/src/main/kotlin/bootimg/v3/BootV3.kt @@ -14,6 +14,9 @@ package cfig.bootimg.v3 +import avb.AVBInfo +import avb.alg.Algorithms +import avb.blob.AuxBlob import cfig.Avb import cfig.EnvironmentVerifier import cfig.bootimg.Common.Companion.deleleIfExists @@ -37,10 +40,11 @@ data class BootV3( var info: MiscInfo = MiscInfo(), var kernel: CommArgs = CommArgs(), val ramdisk: CommArgs = CommArgs(), - var bootSignature: CommArgs = CommArgs() + var bootSignature: CommArgs = CommArgs(), ) { companion object { private val log = LoggerFactory.getLogger(BootV3::class.java) + private val mapper = ObjectMapper() private val workDir = Helper.prop("workDir") fun parse(fileName: String): BootV3 { @@ -89,13 +93,13 @@ data class BootV3( var osVersion: String = "", var osPatchLevel: String = "", var imageSize: Long = 0, - var signatureSize: Int = 0 + var signatureSize: Int = 0, ) data class CommArgs( var file: String = "", var position: Int = 0, - var size: Int = 0 + var size: Int = 0, ) fun pack(): BootV3 { @@ -129,11 +133,31 @@ data class BootV3( bf.order(ByteOrder.LITTLE_ENDIAN) C.writePaddedFile(bf, this.kernel.file, this.info.pageSize) C.writePaddedFile(bf, this.ramdisk.file, this.info.pageSize) - //write + //write V3 data FileOutputStream("${this.info.output}.clear", true).use { fos -> fos.write(bf.array(), 0, bf.position()) } + //write V4 boot sig + if (this.info.headerVersion > 3) { + val bootSigJson = File(Avb.getJsonFileName(this.bootSignature.file)) + var bootSigBytes = ByteArray(this.bootSignature.size) + if (bootSigJson.exists()) { + log.warn("V4 BootImage has GKI boot signature") + val readBackBootSig = mapper.readValue(bootSigJson, AVBInfo::class.java) + val alg = Algorithms.get(readBackBootSig.header!!.algorithm_type)!! + //replace new pub key + readBackBootSig.auxBlob!!.pubkey!!.pubkey = AuxBlob.encodePubKey(alg) + //update hash and sig + readBackBootSig.auxBlob!!.hashDescriptors.get(0).update(this.info.output + ".clear") + bootSigBytes = readBackBootSig.encodePadded() + } + //write V4 data + FileOutputStream("${this.info.output}.clear", true).use { fos -> + fos.write(bootSigBytes) + } + } + //google way this.toCommandLine().addArgument(this.info.output + ".google").let { log.info(it.toString()) @@ -161,14 +185,15 @@ data class BootV3( osVersion = info.osVersion, osPatchLevel = info.osPatchLevel, headerSize = info.headerSize, - cmdline = info.cmdline + cmdline = info.cmdline, + signatureSize = info.signatureSize ) } fun extractImages(): BootV3 { val workDir = Helper.prop("workDir") //info - ObjectMapper().writerWithDefaultPrettyPrinter().writeValue(File(workDir + this.info.json), this) + mapper.writerWithDefaultPrettyPrinter().writeValue(File(workDir + this.info.json), this) //kernel C.dumpKernel(Helper.Slice(info.output, kernel.position, kernel.size, kernel.file)) //ramdisk @@ -183,20 +208,20 @@ data class BootV3( this.bootSignature.position.toLong(), this.bootSignature.size ) try { - Avb().parseVbMeta(this.bootSignature.file) + AVBInfo.parseFrom(this.bootSignature.file).dumpDefault(this.bootSignature.file) } catch (e: IllegalArgumentException) { log.warn("boot signature is invalid") } } //dump info again - ObjectMapper().writerWithDefaultPrettyPrinter().writeValue(File(workDir + this.info.json), this) + mapper.writerWithDefaultPrettyPrinter().writeValue(File(workDir + this.info.json), this) return this } fun extractVBMeta(): BootV3 { try { - Avb().parseVbMeta(info.output) + AVBInfo.parseFrom(info.output).dumpDefault(info.output) if (File("vbmeta.img").exists()) { log.warn("Found vbmeta.img, parsing ...") VBMetaParser().unpack("vbmeta.img") @@ -290,6 +315,12 @@ data class BootV3( ret.addArgument(" --os_patch_level") ret.addArgument(info.osPatchLevel) } + if (this.bootSignature.size > 0 && File(Avb.getJsonFileName(this.bootSignature.file)).exists()) { + val origSig = mapper.readValue(File(Avb.getJsonFileName(this.bootSignature.file)), AVBInfo::class.java) + val alg = Algorithms.get(origSig.header!!.algorithm_type)!! + ret.addArgument("--gki_signing_algorithm").addArgument(alg.name) + ret.addArgument("--gki_signing_key").addArgument(alg.defaultKey) + } ret.addArgument(" --id ") ret.addArgument(" --output ") //ret.addArgument("boot.img" + ".google") diff --git a/bbootimg/src/main/kotlin/bootimg/v3/VendorBoot.kt b/bbootimg/src/main/kotlin/bootimg/v3/VendorBoot.kt index 95597bb..5094c86 100644 --- a/bbootimg/src/main/kotlin/bootimg/v3/VendorBoot.kt +++ b/bbootimg/src/main/kotlin/bootimg/v3/VendorBoot.kt @@ -14,6 +14,7 @@ package cfig.bootimg.v3 +import avb.AVBInfo import cfig.Avb import cfig.EnvironmentVerifier import cfig.bootimg.Common.Companion.deleleIfExists @@ -37,13 +38,13 @@ data class VendorBoot( var ramdisk: CommArgs = CommArgs(), var dtb: CommArgs = CommArgs(), var ramdisk_table: Vrt = Vrt(), - var bootconfig: CommArgs = CommArgs() + var bootconfig: CommArgs = CommArgs(), ) { data class CommArgs( var file: String = "", var position: Long = 0, var size: Int = 0, - var loadAddr: Long = 0 + var loadAddr: Long = 0, ) data class MiscInfo( @@ -56,7 +57,7 @@ data class VendorBoot( var cmdline: String = "", var tagsLoadAddr: Long = 0, var kernelLoadAddr: Long = 0, - var imageSize: Long = 0 + var imageSize: Long = 0, ) enum class VrtType { @@ -82,7 +83,7 @@ data class VendorBoot( var size: Int = 0, var eachEntrySize: Int = 0, var position: Long = 0, - var ramdidks: MutableList = mutableListOf() + var ramdidks: MutableList = mutableListOf(), ) { fun update(): Vrt { var totalSz = 0 @@ -113,11 +114,10 @@ data class VendorBoot( var name: String = "", //32s var boardId: ByteArray = byteArrayOf(), //16I (aka. 64 bytes) var boardIdStr: String = "", - var file: String = "" + var file: String = "", ) { companion object { - private val log = LoggerFactory.getLogger(VrtEntry::class.java) - const val VENDOR_RAMDISK_NAME_SIZE = 32 + private const val VENDOR_RAMDISK_NAME_SIZE = 32 const val VENDOR_RAMDISK_TABLE_ENTRY_BOARD_ID_SIZE = 16 //const val FORMAT_STRING = "3I${VENDOR_RAMDISK_NAME_SIZE}s${VENDOR_RAMDISK_TABLE_ENTRY_BOARD_ID_SIZE}I" @@ -156,7 +156,6 @@ data class VendorBoot( companion object { private val log = LoggerFactory.getLogger(VendorBoot::class.java) private val workDir = Helper.prop("workDir") - private val VENDOR_RAMDISK_TABLE_ENTRY_V4_SIZE = 108 fun parse(fileName: String): VendorBoot { val ret = VendorBoot() FileInputStream(fileName).use { fis -> @@ -342,7 +341,7 @@ data class VendorBoot( fun extractVBMeta(): VendorBoot { try { - Avb().parseVbMeta(info.output) + AVBInfo.parseFrom(info.output).dumpDefault(info.output) } catch (e: Exception) { log.error("extraceVBMeta(): $e") } diff --git a/bbootimg/src/main/kotlin/packable/IPackable.kt b/bbootimg/src/main/kotlin/packable/IPackable.kt index a9148dd..a162c27 100644 --- a/bbootimg/src/main/kotlin/packable/IPackable.kt +++ b/bbootimg/src/main/kotlin/packable/IPackable.kt @@ -14,6 +14,7 @@ package cfig.packable +import avb.AVBInfo import cfig.Avb import cfig.helper.Helper import cfig.helper.Helper.Companion.check_call @@ -62,7 +63,7 @@ interface IPackable { // invoked solely by reflection fun `@verify`(fileName: String) { - val ai = Avb().parseVbMeta(fileName, true) + val ai = AVBInfo.parseFrom(fileName).dumpDefault(fileName) Avb().verify(ai, fileName) } diff --git a/bbootimg/src/main/kotlin/packable/VBMetaParser.kt b/bbootimg/src/main/kotlin/packable/VBMetaParser.kt index 358af67..e0e6ea2 100644 --- a/bbootimg/src/main/kotlin/packable/VBMetaParser.kt +++ b/bbootimg/src/main/kotlin/packable/VBMetaParser.kt @@ -14,9 +14,15 @@ package cfig.packable +import avb.AVBInfo import cfig.Avb import cfig.helper.Helper +import com.fasterxml.jackson.databind.ObjectMapper +import org.slf4j.LoggerFactory import java.io.File +import java.nio.file.Files +import java.nio.file.Paths +import java.nio.file.StandardOpenOption class VBMetaParser: IPackable { override val loopNo: Int @@ -32,11 +38,13 @@ class VBMetaParser: IPackable { override fun unpack(fileName: String) { cleanUp() - Avb().parseVbMeta(fileName) + AVBInfo.parseFrom(fileName).dumpDefault(fileName) } override fun pack(fileName: String) { - Avb().packVbMetaWithPadding(fileName) + val blob = ObjectMapper().readValue(File(Avb.getJsonFileName(fileName)), AVBInfo::class.java).encodePadded() + log.info("Writing padded vbmeta to file: $fileName.signed") + Files.write(Paths.get("$fileName.signed"), blob, StandardOpenOption.CREATE) } override fun flash(fileName: String, deviceName: String) { @@ -51,4 +59,6 @@ class VBMetaParser: IPackable { override fun pull(fileName: String, deviceName: String) { super.pull(fileName, deviceName) } + + private val log = LoggerFactory.getLogger(VBMetaParser::class.java) } diff --git a/helper/src/main/kotlin/cfig/io/Struct3.kt b/helper/src/main/kotlin/cfig/io/Struct3.kt index 24a1ca0..a7155f5 100644 --- a/helper/src/main/kotlin/cfig/io/Struct3.kt +++ b/helper/src/main/kotlin/cfig/io/Struct3.kt @@ -46,7 +46,6 @@ import java.util.regex.Pattern import kotlin.random.Random class Struct3 { - private val log = LoggerFactory.getLogger(Struct3::class.java) private val formatString: String private var byteOrder = ByteOrder.LITTLE_ENDIAN private val formats = ArrayList>() @@ -135,10 +134,10 @@ class Struct3 { return ret } + @Throws(IllegalArgumentException::class) fun pack(vararg args: Any?): ByteArray { if (args.size != this.formats.size) { - throw IllegalArgumentException("argument size " + args.size + - " doesn't match format size " + this.formats.size) + throw IllegalArgumentException("argument size " + args.size + " doesn't match format size " + this.formats.size) } val bf = ByteBuffer.allocate(this.calcSize()) bf.order(this.byteOrder) @@ -157,8 +156,7 @@ class Struct3 { null -> bf.appendPadding(0, multiple) is Byte -> bf.appendPadding(arg, multiple) is Int -> bf.appendPadding(arg.toByte(), multiple) - else -> throw IllegalArgumentException("Index[" + i + "] Unsupported arg [" - + arg + "] with type [" + formats[i][0] + "]") + else -> throw IllegalArgumentException("Index[" + i + "] Unsupported arg [" + arg + "] with type [" + formats[i][0] + "]") } continue } @@ -288,7 +286,7 @@ class Struct3 { return bf.array() } - @Throws(IOException::class) + @Throws(IOException::class, IllegalArgumentException::class) fun unpack(iS: InputStream): List<*> { val ret = ArrayList() for (format in this.formats) { @@ -314,6 +312,7 @@ class Struct3 { companion object { private val log = LoggerFactory.getLogger(ByteBufferExt::class.java) + @Throws(IllegalArgumentException::class) fun ByteBuffer.appendPadding(b: Byte, bufSize: Int) { when { bufSize == 0 -> { @@ -334,15 +333,17 @@ class Struct3 { fun ByteBuffer.appendByteArray(inIntArray: IntArray, bufSize: Int) { val arg2 = mutableListOf() - inIntArray.toMutableList().mapTo(arg2, { - if (it in Byte.MIN_VALUE..Byte.MAX_VALUE) + inIntArray.toMutableList().mapTo(arg2) { + if (it in Byte.MIN_VALUE..Byte.MAX_VALUE) { it.toByte() - else + } else { throw IllegalArgumentException("$it is not valid Byte") - }) + } + } appendByteArray(arg2.toByteArray(), bufSize) } + @Throws(IllegalArgumentException::class) fun ByteBuffer.appendByteArray(inByteArray: ByteArray, bufSize: Int) { val paddingSize = bufSize - inByteArray.size if (paddingSize < 0) throw IllegalArgumentException("arg length [${inByteArray.size}] exceeds limit: $bufSize") @@ -355,18 +356,19 @@ class Struct3 { fun ByteBuffer.appendUByteArray(inIntArray: IntArray, bufSize: Int) { val arg2 = mutableListOf() - inIntArray.toMutableList().mapTo(arg2, { + inIntArray.toMutableList().mapTo(arg2) { if (it in UByte.MIN_VALUE.toInt()..UByte.MAX_VALUE.toInt()) it.toUByte() - else + else { throw IllegalArgumentException("$it is not valid Byte") - }) + } + } appendUByteArray(arg2.toUByteArray(), bufSize) } fun ByteBuffer.appendUByteArray(inUByteArray: UByteArray, bufSize: Int) { val bl = mutableListOf() - inUByteArray.toMutableList().mapTo(bl, { it.toByte() }) + inUByteArray.toMutableList().mapTo(bl) { it.toByte() } this.appendByteArray(bl.toByteArray(), bufSize) } } @@ -426,7 +428,7 @@ class Struct3 { val data = ByteArray(inSize) assert(inSize == this.read(data)) val innerData2 = mutableListOf() - data.toMutableList().mapTo(innerData2, { it.toUByte() }) + data.toMutableList().mapTo(innerData2) { it.toUByte() } return innerData2.toUByteArray() } @@ -451,92 +453,78 @@ class Struct3 { fun ByteArray.toShort(inByteOrder: ByteOrder): Short { val typeSize = Short.SIZE_BYTES / Byte.SIZE_BYTES assert(typeSize == this.size) { "Short must have $typeSize bytes" } - var ret: Short - ByteBuffer.allocate(typeSize).let { + return ByteBuffer.allocate(this.size).let { it.order(inByteOrder) it.put(this) it.flip() - ret = it.getShort() + it.getShort() } - return ret } fun ByteArray.toInt(inByteOrder: ByteOrder): Int { val typeSize = Int.SIZE_BYTES / Byte.SIZE_BYTES assert(typeSize == this.size) { "Int must have $typeSize bytes" } - var ret: Int - ByteBuffer.allocate(typeSize).let { + return ByteBuffer.allocate(this.size).let { it.order(inByteOrder) it.put(this) it.flip() - ret = it.getInt() + it.getInt() } - return ret } fun ByteArray.toLong(inByteOrder: ByteOrder): Long { val typeSize = Long.SIZE_BYTES / Byte.SIZE_BYTES assert(typeSize == this.size) { "Long must have $typeSize bytes" } - var ret: Long - ByteBuffer.allocate(typeSize).let { + return ByteBuffer.allocate(this.size).let { it.order(inByteOrder) it.put(this) it.flip() - ret = it.getLong() + it.getLong() } - return ret } fun ByteArray.toUShort(inByteOrder: ByteOrder): UShort { val typeSize = UShort.SIZE_BYTES / Byte.SIZE_BYTES assert(typeSize == this.size) { "UShort must have $typeSize bytes" } - var ret: UShort - ByteBuffer.allocate(typeSize).let { + return ByteBuffer.allocate(this.size).let { it.order(inByteOrder) it.put(this) it.flip() - ret = it.getShort().toUShort() + it.getShort().toUShort() } - return ret } fun ByteArray.toUInt(inByteOrder: ByteOrder): UInt { val typeSize = UInt.SIZE_BYTES / Byte.SIZE_BYTES assert(typeSize == this.size) { "UInt must have $typeSize bytes" } - var ret: UInt - ByteBuffer.allocate(typeSize).let { + return ByteBuffer.allocate(this.size).let { it.order(inByteOrder) it.put(this) it.flip() - ret = it.getInt().toUInt() + it.getInt().toUInt() } - return ret } fun ByteArray.toULong(inByteOrder: ByteOrder): ULong { val typeSize = ULong.SIZE_BYTES / Byte.SIZE_BYTES assert(typeSize == this.size) { "ULong must have $typeSize bytes" } - var ret: ULong - ByteBuffer.allocate(typeSize).let { + return ByteBuffer.allocate(this.size).let { it.order(inByteOrder) it.put(this) it.flip() - ret = it.getLong().toULong() + it.getLong().toULong() } - return ret } //similar to this.toString(StandardCharsets.UTF_8).replace("${Character.MIN_VALUE}", "") // not Deprecated for now, "1.3.41 experimental api: ByteArray.decodeToString()") is a little different fun ByteArray.toCString(): String { - val str = this.toString(StandardCharsets.UTF_8) - val nullPos = str.indexOf(Character.MIN_VALUE) - return if (nullPos >= 0) { - str.substring(0, nullPos) - } else { - str + return this.toString(StandardCharsets.UTF_8).let { str -> + str.indexOf(Character.MIN_VALUE).let { nullPos -> + if (nullPos >= 0) str.substring(0, nullPos) else str + } } } - } - } + }//end-of-Companion + }//end-of-ByteArrayExt }