diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..23841a6 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +.idea +.gradle diff --git a/README.expert.md b/README.expert.md index 71a850d..fe398f1 100644 --- a/README.expert.md +++ b/README.expert.md @@ -133,7 +133,7 @@ | +--------------------------------+-------------------------+ | | Padding | align by block_size | +------+--------------------------------+-------------------------+ --> + (block_size * n) - + +---------------------------------------+-------------------------+ | | | | | | @@ -141,7 +141,7 @@ | | | | | | +--------------------------------------- -------------------------+ - + +---------------------------------------+-------------------------+ --> partition_size - block_size | Padding | block_size - 64 | +---------------------------------------+-------------------------+ --> partition_size - 64 diff --git a/README.md b/README.md index 83fb5fd..28cfd49 100644 --- a/README.md +++ b/README.md @@ -35,9 +35,10 @@ Then put your boot.img at **$(CURDIR)/boot.img**, then start gradle 'unpack' tas Your get the flattened kernel and /root filesystem under **$(CURDIR)/build/unzip\_boot**: build/unzip_boot/ - ├── bootimg.json + ├── boot.img.avb.json (AVB only) + ├── bootimg.json (boot image info) ├── kernel - ├── second + ├── second (2nd bootloader, if exists) └── root Then you can edit the actual file contents, like rootfs or kernel. @@ -76,3 +77,6 @@ https://android.googlesource.com/platform/external/bouncycastle cpio / fs\_config https://android.googlesource.com/platform/system/core + +AVB +https://android.googlesource.com/platform/external/avb/ diff --git a/README.other.md b/README.other.md index 1e909e7..0b2f222 100644 --- a/README.other.md +++ b/README.other.md @@ -6,10 +6,10 @@ | - tag | 8 | --> +0 | - num_bytes_following | 8 | --> +8 | - hash algorithm | 8 | --> +16 - | - partition name | 32 | - | - salt length | 4 | - | - digest length | 4 | - | - reserved | 60 | + | - partition name | 32 | + | - salt length | 4 | + | - digest length | 4 | + | - reserved | 60 | +--------------------------------+-------------------------+ | Partition name | | +--------------------------------+-------------------------+ diff --git a/avb/avb_test_data/testkey_rsa2048.pk8 b/avb/avb_test_data/testkey_rsa2048.pk8 new file mode 100644 index 0000000..46b15b4 Binary files /dev/null and b/avb/avb_test_data/testkey_rsa2048.pk8 differ diff --git a/avb/avb_test_data/testkey_rsa4096.pk8 b/avb/avb_test_data/testkey_rsa4096.pk8 new file mode 100644 index 0000000..1bdd26b Binary files /dev/null and b/avb/avb_test_data/testkey_rsa4096.pk8 differ diff --git a/avb/avb_test_data/testkey_rsa8192.pk8 b/avb/avb_test_data/testkey_rsa8192.pk8 new file mode 100644 index 0000000..3890c1b Binary files /dev/null and b/avb/avb_test_data/testkey_rsa8192.pk8 differ diff --git a/bbootimg/src/main/java/cfig/io/Struct.java b/bbootimg/src/main/java/cfig/io/Struct.java index cee99a8..b6efbdb 100644 --- a/bbootimg/src/main/java/cfig/io/Struct.java +++ b/bbootimg/src/main/java/cfig/io/Struct.java @@ -19,8 +19,8 @@ import static org.junit.Assert.assertEquals; public class Struct { private static Logger log = LoggerFactory.getLogger(Struct.class); - public ByteOrder byteOrder = ByteOrder.LITTLE_ENDIAN; - public List formats = new ArrayList<>(); + private ByteOrder byteOrder = ByteOrder.LITTLE_ENDIAN; + private List formats = new ArrayList<>(); public Struct(String formatString) { Matcher m = Pattern.compile("(\\d*)([a-zA-Z])").matcher(formatString); @@ -97,11 +97,11 @@ public class Struct { } } - public Integer calcsize() { + public Integer calcSize() { Integer ret = 0; for (Object[] format : formats) { if (format[0] == Byte.class || format[0] == Character.class || format[0] == PadByte.class) { - ret += 1 * (int) format[1]; + ret += (int) format[1]; continue; } if (format[0] == Short.class) { @@ -145,7 +145,8 @@ public class Struct { for (Object[] format : this.formats) { //return 'null' for padding bytes if (format[0] == PadByte.class) { - iS.skip((Integer) format[1]); + long skipped = iS.skip((Integer) format[1]); + assertEquals((long) (Integer) format[1], skipped); ret.add(null); continue; } @@ -233,7 +234,7 @@ public class Struct { throw new IllegalArgumentException("argument size " + args.length + " doesn't match format size " + this.formats.size()); } - ByteBuffer bf = ByteBuffer.allocate(this.calcsize()); + ByteBuffer bf = ByteBuffer.allocate(this.calcSize()); bf.order(this.byteOrder); for (int i = 0; i < args.length; i++) { Object arg = args[i]; @@ -269,7 +270,7 @@ public class Struct { log.error("container size " + size + ", value size " + ((byte[]) arg).length); throw new IllegalArgumentException("Index[" + i + "] arg [" + arg + "] with type [" + format + "] size overflow"); } else { - //perfect match + log.debug("perfect match, paddingSize is zero"); } continue; } @@ -338,22 +339,21 @@ public class Struct { } else { throw new IllegalArgumentException("Index[" + i + "] Unsupported arg [" + arg + "] with type [" + format + "]"); } - continue; } } log.debug("Pack Result:" + Helper.Companion.toHexString(bf.array())); return bf.array(); } - public static class UnsignedInt { + private static class UnsignedInt { } - public static class UnsignedLong { + private static class UnsignedLong { } - public static class UnsignedShort { + private static class UnsignedShort { } - public static class PadByte { + private static class PadByte { } } diff --git a/bbootimg/src/main/kotlin/Avb.kt b/bbootimg/src/main/kotlin/Avb.kt index 57e2775..0996c2e 100755 --- a/bbootimg/src/main/kotlin/Avb.kt +++ b/bbootimg/src/main/kotlin/Avb.kt @@ -29,7 +29,7 @@ class Avb { partition_name: String, rollback_index: Long, common_algorithm: String, - common_key_path: String) { + inReleaseString: String?) { var original_image_size = 0L //required libavb version if (use_persistent_digest || do_not_use_ab) { @@ -81,7 +81,6 @@ class Avb { if (!use_persistent_digest) hd.digest = digest log.info("encoded hash descriptor:" + Hex.encodeHexString(hd.encode())) val vbmeta_blob = generateVbMetaBlob(common_algorithm, - common_key_path, null, arrayOf(hd as Descriptor), null, @@ -89,15 +88,8 @@ class Avb { 0, null, null, - null, - false, - null, - false, - null, - null, - null, - false, - 0) + 0, + inReleaseString) log.debug("vbmeta_blob: " + Helper.toHexString(vbmeta_blob)) if (hd.image_size % BLOCK_SIZE != 0L) { @@ -124,41 +116,33 @@ class Avb { footer.originalImageSize = original_image_size footer.vbMetaOffset = vbmeta_offset footer.vbMetaSize = vbmeta_blob.size.toLong() - val footer_blob = footer.encode() - val footer_blob_with_padding = Helper.join( - Struct("${BLOCK_SIZE - Footer.SIZE}x").pack(null), footer_blob) - log.info("footer:" + Helper.toHexString(footer_blob)) + val footerBob = footer.encode() + val footerBlobWithPadding = Helper.join( + Struct("${BLOCK_SIZE - Footer.SIZE}x").pack(null), footerBob) + log.info("footer:" + Helper.toHexString(footerBob)) log.info(footer.toString()) FileOutputStream(image_file, true).use { fos -> - fos.write(footer_blob_with_padding) + fos.write(footerBlobWithPadding) } } fun generateVbMetaBlob(algorithm_name: String, - key_path: String?, public_key_metadata_path: String?, descriptors: Array, chain_partitions: String?, inRollbackIndex: Long, inFlags: Long, props: String?, - props_from_file: String?, kernel_cmdlines: String?, - setup_rootfs_from_kernel: Boolean, - ht_desc_to_setup: String?, - include_descriptors_from_image: Boolean, - signing_helper: String?, - signing_helper_with_files: String?, - release_string: String?, - append_to_release_string: Boolean, - required_libavb_version_minor: Int): ByteArray { + required_libavb_version_minor: Int, + inReleaseString: String?): ByteArray { //encoded descriptors var encodedDesc: ByteArray = byteArrayOf() descriptors.forEach { encodedDesc = Helper.join(encodedDesc, it.encode()) } //algorithm val alg = Algorithms.get(algorithm_name)!! //encoded pubkey - val encodedKey = Blob.encodePubKey(alg, Files.readAllBytes(Paths.get(key_path))) + val encodedKey = Blob.encodePubKey(alg) //3 - whole aux blob val auxBlob = Blob.getAuxDataBlob(encodedDesc, encodedKey) @@ -179,6 +163,9 @@ class Avb { signature_offset = alg.hash_num_bytes.toLong() signature_size = alg.signature_num_bytes.toLong() + descriptors_offset = 0 + descriptors_size = encodedDesc.size.toLong() + public_key_offset = descriptors_size public_key_size = encodedKey.size.toLong() @@ -186,15 +173,16 @@ class Avb { public_key_metadata_size = 0 public_key_metadata_offset = public_key_offset + public_key_size - descriptors_offset = 0 - descriptors_size = encodedDesc.size.toLong() - rollback_index = inRollbackIndex flags = inFlags + if (inReleaseString != null) { + log.info("Using preset release string: $inReleaseString") + this.release_string = inReleaseString + } }.encode() //2 - auth blob - var authBlob = Blob.getAuthBlob(headerBlob, auxBlob, algorithm_name, key_path) + var authBlob = Blob.getAuthBlob(headerBlob, auxBlob, algorithm_name) return Helper.join(headerBlob, authBlob, auxBlob) } @@ -322,12 +310,12 @@ class Avb { return ai } - fun packVbMeta(key_path: String, info: AVBInfo? = null): ByteArray { + fun packVbMeta(info: AVBInfo? = null): ByteArray { val ai = info ?: ObjectMapper().readValue(File(getJsonFileName("vbmeta.img")), AVBInfo::class.java) val alg = Algorithms.get(ai.header!!.algorithm_type.toInt())!! val encodedDesc = ai.auxBlob!!.encodeDescriptors() //encoded pubkey - val encodedKey = Blob.encodePubKey(alg, Files.readAllBytes(Paths.get(key_path))) + val encodedKey = Blob.encodePubKey(alg, Files.readAllBytes(Paths.get(alg.defaultKey))) //3 - whole aux blob var auxBlob = byteArrayOf() @@ -348,6 +336,9 @@ class Avb { authentication_data_block_size = Helper.round_to_multiple( (alg.hash_num_bytes + alg.signature_num_bytes).toLong(), 64) + descriptors_offset = 0 + descriptors_size = encodedDesc.size.toLong() + hash_offset = 0 hash_size = alg.hash_num_bytes.toLong() @@ -360,15 +351,12 @@ class Avb { //TODO: support pubkey metadata public_key_metadata_size = 0 public_key_metadata_offset = public_key_offset + public_key_size - - descriptors_offset = 0 - descriptors_size = encodedDesc.size.toLong() }.encode() //2 - auth blob var authBlob = byteArrayOf() if (ai.authBlob != null) { - authBlob = Blob.getAuthBlob(headerBlob, auxBlob, alg.name, key_path) + authBlob = Blob.getAuthBlob(headerBlob, auxBlob, alg.name) } else { log.info("No auth blob") } @@ -376,8 +364,8 @@ class Avb { return Helper.join(headerBlob, authBlob, auxBlob) } - fun packVbMetaWithPadding(key_path: String, info: AVBInfo? = null) { - val rawBlob = packVbMeta(key_path, info) + fun packVbMetaWithPadding(info: AVBInfo? = null) { + val rawBlob = packVbMeta(info) val paddingSize = Helper.round_to_multiple(rawBlob.size.toLong(), BLOCK_SIZE) - rawBlob.size val paddedBlob = Helper.join(rawBlob, Struct("${paddingSize}x").pack(null)) log.info("raw vbmeta size ${rawBlob.size}, padding size $paddingSize, total blob size ${paddedBlob.size}") @@ -391,13 +379,8 @@ class Avb { val AVB_VERSION_MINOR = 1 val AVB_VERSION_SUB = 0 - //Keep in sync with libavb/avb_footer.h. - val AVB_FOOTER_VERSION_MAJOR = 1 - val AVB_FOOTER_VERSION_MINOR = 0 - fun getJsonFileName(image_file: String): String { val fileName = File(image_file).name -// val jsonFile = fileName.substring(0, fileName.lastIndexOf(".")) + ".json" val jsonFile = "$fileName.avb.json" return UnifiedConfig.workDir + jsonFile } diff --git a/bbootimg/src/main/kotlin/Helper.kt b/bbootimg/src/main/kotlin/Helper.kt index 7537916..7db51e3 100644 --- a/bbootimg/src/main/kotlin/Helper.kt +++ b/bbootimg/src/main/kotlin/Helper.kt @@ -1,5 +1,6 @@ package cfig +import avb.alg.Algorithms import cfig.io.Struct import com.google.common.math.BigIntegerMath import org.apache.commons.codec.binary.Hex @@ -18,8 +19,14 @@ import java.io.* import java.math.BigInteger import java.math.RoundingMode import java.nio.charset.StandardCharsets +import java.nio.file.Files +import java.nio.file.Paths +import java.security.KeyFactory +import java.security.PrivateKey +import java.security.spec.PKCS8EncodedKeySpec import java.util.zip.GZIPInputStream import java.util.zip.GZIPOutputStream +import javax.crypto.Cipher class Helper { companion object { @@ -144,7 +151,7 @@ class Helper { } } - fun extractImageData(fileName: String, outImgName: String, offset: Long, length: Int) { + fun extractFile(fileName: String, outImgName: String, offset: Long, length: Int) { if (0 == length) { return } @@ -167,7 +174,6 @@ class Helper { } } - /* read RSA private key assert exp == 65537 @@ -179,15 +185,14 @@ class Helper { fun encodeRSAkey(key: ByteArray): ByteArray { val p2 = PemReader(InputStreamReader(ByteArrayInputStream(key))).readPemObject() Assert.assertEquals("RSA PRIVATE KEY", p2.type) - val rsa = RSAPrivateKey.getInstance(p2.content) Assert.assertEquals(65537.toBigInteger(), rsa.publicExponent) val numBits: Int = BigIntegerMath.log2(rsa.modulus, RoundingMode.CEILING) log.debug("modulus: " + rsa.modulus) - log.debug("numBits: " + numBits) + log.debug("numBits: $numBits") val b = BigInteger.valueOf(2).pow(32) val n0inv = (b - rsa.modulus.modInverse(b)).toLong() - log.debug("n0inv = " + n0inv) + log.debug("n0inv = $n0inv") val r = BigInteger.valueOf(2).pow(numBits) val rrModn = (r * r).mod(rsa.modulus) log.debug("BB: " + numBits / 8 + ", mod_len: " + rsa.modulus.toByteArray().size + ", rrmodn = " + rrModn.toByteArray().size) @@ -203,9 +208,22 @@ class Helper { return ret } + //inspired by + // https://stackoverflow.com/questions/40242391/how-can-i-sign-a-raw-message-without-first-hashing-it-in-bouncy-castle + // "specifying Cipher.ENCRYPT mode or Cipher.DECRYPT mode doesn't make a difference; + // both simply perform modular exponentiation" fun rawSign(keyPath: String, data: ByteArray): ByteArray { -// openssl rsautl -sign -inkey /Users/yu/work/boot/avb/avb_test_data/testkey_rsa4096.pem -raw - log.debug("Raw sign data: SIZE = " + data.size) + val privk = Helper.readPrivateKey(keyPath) + val cipher = Cipher.getInstance("RSA/ECB/NoPadding").apply { + this.init(Cipher.ENCRYPT_MODE, privk) + this.update(data) + } + return cipher.doFinal() + } + + fun rawSignOpenSsl(keyPath: String, data: ByteArray): ByteArray { + log.debug("raw input: " + Hex.encodeHexString(data)) + log.debug("Raw sign data size = ${data.size}, key = $keyPath") var ret = byteArrayOf() val exe = DefaultExecutor() val stdin = ByteArrayInputStream(data) @@ -215,6 +233,7 @@ class Helper { try { exe.execute(CommandLine.parse("openssl rsautl -sign -inkey $keyPath -raw")) ret = stdout.toByteArray() + log.debug("Raw signature size = " + ret.size) } catch (e: ExecuteException) { log.error("Execute error") } finally { @@ -238,6 +257,12 @@ class Helper { } } + @Throws(Exception::class) + fun readPrivateKey(filename: String): PrivateKey { + val spec = PKCS8EncodedKeySpec(Files.readAllBytes(Paths.get(filename))) + return KeyFactory.getInstance("RSA").generatePrivate(spec) + } + private val log = LoggerFactory.getLogger("Helper") } } diff --git a/bbootimg/src/main/kotlin/Parser.kt b/bbootimg/src/main/kotlin/Parser.kt index c361b57..538b360 100644 --- a/bbootimg/src/main/kotlin/Parser.kt +++ b/bbootimg/src/main/kotlin/Parser.kt @@ -201,20 +201,20 @@ class Parser { log.info(imgArgs.toString()) log.info(imgInfo.toString()) - Helper.extractImageData(imgArgs.output, imgArgs.kernel, imgInfo.kernelPosition.toLong(), imgInfo.kernelLength) + Helper.extractFile(imgArgs.output, imgArgs.kernel, imgInfo.kernelPosition.toLong(), imgInfo.kernelLength) log.info("kernel dumped to ${imgArgs.kernel}") imgArgs.ramdisk?.let { ramdisk -> log.info("ramdisk dumped to ${imgArgs.ramdisk}") - Helper.extractImageData(imgArgs.output, ramdisk, imgInfo.ramdiskPosition.toLong(), imgInfo.ramdiskLength) + Helper.extractFile(imgArgs.output, ramdisk, imgInfo.ramdiskPosition.toLong(), imgInfo.ramdiskLength) Helper.unGnuzipFile(ramdisk, workDir + "ramdisk.img") unpackRamdisk(imgArgs) } imgArgs.second?.let { second -> - Helper.extractImageData(imgArgs.output, second, imgInfo.secondBootloaderPosition.toLong(), imgInfo.secondBootloaderLength) + Helper.extractFile(imgArgs.output, second, imgInfo.secondBootloaderPosition.toLong(), imgInfo.secondBootloaderLength) log.info("second bootloader dumped to ${imgArgs.second}") } imgArgs.dtbo?.let { dtbo -> - Helper.extractImageData(imgArgs.output, dtbo, imgInfo.recoveryDtboPosition.toLong(), imgInfo.recoveryDtboLength) + Helper.extractFile(imgArgs.output, dtbo, imgInfo.recoveryDtboPosition.toLong(), imgInfo.recoveryDtboLength) log.info("dtbo dumped to ${imgArgs.dtbo}") } val cfg = UnifiedConfig.fromArgs(imgArgs, imgInfo) @@ -226,10 +226,6 @@ class Parser { companion object { private val log = LoggerFactory.getLogger("Parser")!! - fun readValues(iS: InputStream, vararg key: Any) { - - } - fun readShort(iS: InputStream): Short { val bf = ByteBuffer.allocate(128) bf.order(ByteOrder.LITTLE_ENDIAN) diff --git a/bbootimg/src/main/kotlin/R.kt b/bbootimg/src/main/kotlin/R.kt index 767f40b..8078595 100755 --- a/bbootimg/src/main/kotlin/R.kt +++ b/bbootimg/src/main/kotlin/R.kt @@ -1,5 +1,6 @@ package cfig +import avb.AVBInfo import com.fasterxml.jackson.databind.ObjectMapper import org.slf4j.LoggerFactory import java.io.File @@ -15,7 +16,7 @@ fun main(args: Array) { Avb().parseVbMeta(args[1]) } "pack" -> { - Avb().packVbMetaWithPadding("/Users/yu/work/boot/avb/avb_test_data/testkey_rsa4096.pem") + Avb().packVbMetaWithPadding() } "sign" -> { log.info("vbmeta is already signed") @@ -27,10 +28,13 @@ fun main(args: Array) { if (File(UnifiedConfig.workDir).exists()) File(UnifiedConfig.workDir).deleteRecursively() File(UnifiedConfig.workDir).mkdirs() Parser().parseAndExtract(fileName = args[1], avbtool = args[3]) - Avb().parseVbMeta(args[1]) - if (File("vbmeta.img").exists()) { - Avb().parseVbMeta("vbmeta.img") + if (UnifiedConfig.readBack()[2] is ImgInfo.AvbSignature) { + log.info("continue to analyze vbmeta info in " + args[1]) + Avb().parseVbMeta(args[1]) + if (File("vbmeta.img").exists()) { + Avb().parseVbMeta("vbmeta.img") + } } } "pack" -> { @@ -38,15 +42,10 @@ fun main(args: Array) { } "sign" -> { Signer.sign(avbtool = args[3], bootSigner = args[4]) - - val readBack = ObjectMapper().readValue(File(UnifiedConfig.workDir + "bootimg.json"), - UnifiedConfig::class.java).toArgs() - val imgArgs = readBack[0] as ImgArgs - val info = readBack[1] as ImgInfo - if (imgArgs.verifyType == ImgArgs.VerifyType.AVB) { + val readBack = UnifiedConfig.readBack() + if ((readBack[0] as ImgArgs).verifyType == ImgArgs.VerifyType.AVB) { if (File("vbmeta.img").exists()) { - val sig = ObjectMapper().readValue( - Signer.mapToJson(info.signature as LinkedHashMap<*, *>), ImgInfo.AvbSignature::class.java) + val sig = readBack[2] as ImgInfo.AvbSignature val newBootImgInfo = Avb().parseVbMeta(args[1] + ".signed") val hashDesc = newBootImgInfo.auxBlob!!.hashDescriptors[0] val origVbMeta = ObjectMapper().readValue(File(Avb.getJsonFileName("vbmeta.img")), @@ -60,7 +59,7 @@ fun main(args: Array) { } ObjectMapper().writerWithDefaultPrettyPrinter().writeValue(File(Avb.getJsonFileName("vbmeta.img")), origVbMeta) log.info("vbmeta info updated") - Avb().packVbMetaWithPadding("/Users/yu/work/boot/avb/avb_test_data/testkey_rsa4096.pem") + Avb().packVbMetaWithPadding() } else { //no vbmeta provided } diff --git a/bbootimg/src/main/kotlin/Signer.kt b/bbootimg/src/main/kotlin/Signer.kt index 77b4a1d..be32914 100644 --- a/bbootimg/src/main/kotlin/Signer.kt +++ b/bbootimg/src/main/kotlin/Signer.kt @@ -1,5 +1,7 @@ package cfig +import avb.AVBInfo +import avb.alg.Algorithms import com.fasterxml.jackson.databind.ObjectMapper import org.apache.commons.exec.CommandLine import org.apache.commons.exec.DefaultExecutor @@ -13,46 +15,51 @@ class Signer { fun sign(avbtool: String, bootSigner: String) { log.info("Loading config from ${workDir}bootimg.json") - val cfg = ObjectMapper().readValue(File(workDir + "bootimg.json"), UnifiedConfig::class.java) - val readBack = cfg.toArgs() + val readBack = UnifiedConfig.readBack() val args = readBack[0] as ImgArgs - val info = readBack[1] as ImgInfo when (args.verifyType) { ImgArgs.VerifyType.VERIFY -> { log.info("Signing with verified-boot 1.0 style") - val sig = ObjectMapper().readValue( - mapToJson(info.signature as LinkedHashMap<*, *>), ImgInfo.VeritySignature::class.java) + val sig = readBack[2] as ImgInfo.VeritySignature DefaultExecutor().execute(CommandLine.parse("java -jar $bootSigner " + "${sig.path} ${args.output}.clear ${sig.verity_pk8} ${sig.verity_pem} ${args.output}.signed")) } ImgArgs.VerifyType.AVB -> { log.info("Adding hash_footer with verified-boot 2.0 style") - val sig = ObjectMapper().readValue( - mapToJson(info.signature as LinkedHashMap<*, *>), ImgInfo.AvbSignature::class.java) + val sig = readBack[2] as ImgInfo.AvbSignature File(args.output + ".clear").copyTo(File(args.output + ".signed")) - val cmdlineStr = "$avbtool add_hash_footer " + + val ai = ObjectMapper().readValue(File(Avb.getJsonFileName(args.output)), AVBInfo::class.java) + val signKey = Algorithms.get(sig.algorithm!!) + var cmdlineStr = "$avbtool add_hash_footer " + "--image ${args.output}.signed " + "--partition_size ${sig.imageSize} " + "--salt ${sig.salt} " + "--partition_name ${sig.partName} " + "--hash_algorithm ${sig.hashAlgorithm} " + - "--algorithm ${sig.algorithm} " + - "--key avb/avb_test_data/testkey_rsa4096.pem" + "--algorithm ${sig.algorithm}" + if (signKey!!.defaultKey.isNotBlank()) { + cmdlineStr += "--key $signKey" + } log.warn(cmdlineStr) - DefaultExecutor().execute(CommandLine.parse(cmdlineStr)) + val cmdLine = CommandLine.parse(cmdlineStr) + cmdLine.addArgument("--internal_release_string") + cmdLine.addArgument(ai.header!!.release_string, false) + DefaultExecutor().execute(cmdLine) verifyAVBIntegrity(args, avbtool) File(args.output + ".clear").copyTo(File(args.output + ".signed2")) + val alg = Algorithms.get(ai.header!!.algorithm_type.toInt()) Avb().add_hash_footer(args.output + ".signed2", sig.imageSize!!.toLong(), - false, false, + false, + false, salt = sig.salt, hash_algorithm = sig.hashAlgorithm!!, partition_name = sig.partName!!, - rollback_index = 0, + rollback_index = ai.header!!.rollback_index, common_algorithm = sig.algorithm!!, - common_key_path = "avb/avb_test_data/testkey_rsa4096.pem") + inReleaseString = ai.header!!.release_string) } } } diff --git a/bbootimg/src/main/kotlin/UnifiedConfig.kt b/bbootimg/src/main/kotlin/UnifiedConfig.kt index 87fccf8..17173d6 100644 --- a/bbootimg/src/main/kotlin/UnifiedConfig.kt +++ b/bbootimg/src/main/kotlin/UnifiedConfig.kt @@ -1,7 +1,9 @@ package cfig import com.fasterxml.jackson.annotation.JsonInclude +import com.fasterxml.jackson.databind.ObjectMapper import org.slf4j.LoggerFactory +import java.io.File @JsonInclude(JsonInclude.Include.NON_NULL) data class UnifiedConfig( @@ -136,5 +138,25 @@ data class UnifiedConfig( return ret } + + fun readBack(): Array { + var ret: Array = arrayOfNulls(3) + val readBack = ObjectMapper().readValue(File(workDir + "bootimg.json"), + UnifiedConfig::class.java).toArgs() + val imgArgs = readBack[0] as ImgArgs + val info = readBack[1] as ImgInfo + if (imgArgs.verifyType == ImgArgs.VerifyType.AVB) { + val sig = ObjectMapper().readValue( + Signer.mapToJson(info.signature as LinkedHashMap<*, *>), ImgInfo.AvbSignature::class.java) + ret[2] = sig + } else { + val sig2 = ObjectMapper().readValue( + Signer.mapToJson(info.signature as LinkedHashMap<*, *>), ImgInfo.VeritySignature::class.java) + ret[2] = sig2 + } + ret[0] = imgArgs + ret[1] = info + return ret + } } } diff --git a/bbootimg/src/main/kotlin/AVBInfo.kt b/bbootimg/src/main/kotlin/avb/AVBInfo.kt similarity index 98% rename from bbootimg/src/main/kotlin/AVBInfo.kt rename to bbootimg/src/main/kotlin/avb/AVBInfo.kt index 4010be0..8258da8 100755 --- a/bbootimg/src/main/kotlin/AVBInfo.kt +++ b/bbootimg/src/main/kotlin/avb/AVBInfo.kt @@ -1,7 +1,7 @@ -package cfig +package avb -import avb.* import avb.desc.* +import cfig.Helper /* a wonderfaul base64 encoder/decoder: https://cryptii.com/base64-to-hex diff --git a/bbootimg/src/main/kotlin/avb/Blob.kt b/bbootimg/src/main/kotlin/avb/Blob.kt index 88a9f25..177c3f6 100644 --- a/bbootimg/src/main/kotlin/avb/Blob.kt +++ b/bbootimg/src/main/kotlin/avb/Blob.kt @@ -12,6 +12,18 @@ import java.security.MessageDigest class Blob { companion object { + fun encodePubKey(alg: Algorithm): ByteArray { + var encodedKey = byteArrayOf() + if (alg.public_key_num_bytes > 0) { + encodedKey = Helper.encodeRSAkey(Files.readAllBytes((Paths.get(alg.defaultKey)))) + log.info("encodePubKey(): size = ${alg.public_key_num_bytes}, algorithm key size: ${encodedKey.size}") + Assert.assertEquals(alg.public_key_num_bytes, encodedKey.size) + } else { + log.info("encodePubKey(): No key to encode for algorithm " + alg.name) + } + return encodedKey + } + fun encodePubKey(alg: Algorithm, key: ByteArray): ByteArray { var encodedKey = byteArrayOf() if (alg.public_key_num_bytes > 0) { @@ -35,8 +47,7 @@ class Blob { fun getAuthBlob(header_data_blob: ByteArray, aux_data_blob: ByteArray, - algorithm_name: String, - key_path: String?): ByteArray { + algorithm_name: String): ByteArray { val alg = Algorithms.get(algorithm_name)!! val authBlockSize = Helper.round_to_multiple((alg.hash_num_bytes + alg.signature_num_bytes).toLong(), 64) if (authBlockSize == 0L) { @@ -53,7 +64,7 @@ class Blob { update(header_data_blob) update(aux_data_blob) }.digest() - binarySignature = Helper.rawSign(key_path!!, Helper.join(alg.padding, binaryHash)) + binarySignature = Helper.rawSign(alg.defaultKey.replace(".pem", ".pk8"), Helper.join(alg.padding, binaryHash)) } val authData = Helper.join(binaryHash, binarySignature) return Helper.join(authData, Struct("${authBlockSize - authData.size}x").pack(0)) diff --git a/bbootimg/src/main/kotlin/avb/Footer.kt b/bbootimg/src/main/kotlin/avb/Footer.kt index 45f0a75..15a8b6e 100644 --- a/bbootimg/src/main/kotlin/avb/Footer.kt +++ b/bbootimg/src/main/kotlin/avb/Footer.kt @@ -20,7 +20,7 @@ data class Footer constructor( private const val FORMAT_STRING = "!4s2L3Q${RESERVED}x" init { - Assert.assertEquals(SIZE, Struct(FORMAT_STRING).calcsize()) + Assert.assertEquals(SIZE, Struct(FORMAT_STRING).calcSize()) } } diff --git a/bbootimg/src/main/kotlin/avb/Header.kt b/bbootimg/src/main/kotlin/avb/Header.kt index d5ba7c7..bdeee84 100644 --- a/bbootimg/src/main/kotlin/avb/Header.kt +++ b/bbootimg/src/main/kotlin/avb/Header.kt @@ -84,7 +84,7 @@ data class Header( const val FORMAT_STRING = ("!4s2L2QL11QL${REVERSED0}x47sx" + "${REVERSED}x") init { - Assert.assertEquals(SIZE, Struct(FORMAT_STRING).calcsize()) + Assert.assertEquals(SIZE, Struct(FORMAT_STRING).calcSize()) } } } \ No newline at end of file diff --git a/bbootimg/src/main/kotlin/avb/alg/Algorithm.kt b/bbootimg/src/main/kotlin/avb/alg/Algorithm.kt index 0d9ff77..33dd92a 100644 --- a/bbootimg/src/main/kotlin/avb/alg/Algorithm.kt +++ b/bbootimg/src/main/kotlin/avb/alg/Algorithm.kt @@ -7,4 +7,5 @@ data class Algorithm( val hash_num_bytes: Int = 0, val signature_num_bytes: Int = 0, val public_key_num_bytes: Int = 0, - val padding: ByteArray = byteArrayOf()) \ No newline at end of file + val padding: ByteArray = byteArrayOf(), + val defaultKey: String ="") \ No newline at end of file diff --git a/bbootimg/src/main/kotlin/avb/alg/Algorithms.kt b/bbootimg/src/main/kotlin/avb/alg/Algorithms.kt index 8a22d37..e407336 100644 --- a/bbootimg/src/main/kotlin/avb/alg/Algorithms.kt +++ b/bbootimg/src/main/kotlin/avb/alg/Algorithms.kt @@ -34,7 +34,8 @@ class Algorithms { byteArrayOf(0x00), intArrayOf(0x30, 0x31, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, 0x05, - 0x00, 0x04, 0x20))) + 0x00, 0x04, 0x20)), + defaultKey = "avb/avb_test_data/testkey_rsa2048.pem") val SHA256_RSA4096 = Algorithm( name = "SHA256_RSA4096", @@ -50,7 +51,8 @@ class Algorithms { intArrayOf(0x30, 0x31, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, 0x05, 0x00, 0x04, 0x20) - ) + ), + defaultKey = "avb/avb_test_data/testkey_rsa4096.pem" ) val SHA256_RSA8192 = Algorithm( @@ -66,7 +68,8 @@ class Algorithms { 0x00, intArrayOf(0x30, 0x31, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, 0x05, - 0x00, 0x04, 0x20))) + 0x00, 0x04, 0x20)), + defaultKey = "avb/avb_test_data/testkey_rsa8192.pem") val SHA512_RSA2048 = Algorithm( name = "SHA512_RSA2048", @@ -81,7 +84,8 @@ class Algorithms { 0x00, intArrayOf(0x30, 0x51, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03, 0x05, - 0x00, 0x04, 0x40))) + 0x00, 0x04, 0x40)), + defaultKey = "avb/avb_test_data/testkey_rsa2048.pem") val SHA512_RSA4096 = Algorithm( name = "SHA512_RSA4096", @@ -96,7 +100,8 @@ class Algorithms { 0x00, intArrayOf(0x30, 0x51, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03, 0x05, - 0x00, 0x04, 0x40))) + 0x00, 0x04, 0x40)), + defaultKey = "avb/avb_test_data/testkey_rsa4096.pem") val SHA512_RSA8192 = Algorithm( name = "SHA512_RSA8192", @@ -112,7 +117,8 @@ class Algorithms { 0x00, intArrayOf(0x30, 0x51, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03, 0x05, - 0x00, 0x04, 0x40))) + 0x00, 0x04, 0x40)), + defaultKey = "avb/avb_test_data/testkey_rsa8192.pem") algMap[NONE.name] = NONE diff --git a/bbootimg/src/main/kotlin/avb/desc/KernelCmdlineDescriptor.kt b/bbootimg/src/main/kotlin/avb/desc/KernelCmdlineDescriptor.kt index fe21340..bdd1894 100755 --- a/bbootimg/src/main/kotlin/avb/desc/KernelCmdlineDescriptor.kt +++ b/bbootimg/src/main/kotlin/avb/desc/KernelCmdlineDescriptor.kt @@ -45,7 +45,7 @@ class KernelCmdlineDescriptor( const val flagHashTreeDisabled = 2 init { - Assert.assertEquals(SIZE, Struct(FORMAT_STRING).calcsize()) + Assert.assertEquals(SIZE, Struct(FORMAT_STRING).calcSize()) } } } \ No newline at end of file diff --git a/bbootimg/src/main/kotlin/avb/desc/UnknownDescriptor.kt b/bbootimg/src/main/kotlin/avb/desc/UnknownDescriptor.kt index 984c2ab..34078ad 100755 --- a/bbootimg/src/main/kotlin/avb/desc/UnknownDescriptor.kt +++ b/bbootimg/src/main/kotlin/avb/desc/UnknownDescriptor.kt @@ -101,7 +101,7 @@ class UnknownDescriptor(var data: ByteArray = byteArrayOf()) : Descriptor(0, 0, } init { - Assert.assertEquals(SIZE, Struct(FORMAT).calcsize()) + Assert.assertEquals(SIZE, Struct(FORMAT).calcSize()) } } } diff --git a/bbootimg/src/main/resources/simplelogger.properties b/bbootimg/src/main/resources/simplelogger.properties new file mode 100644 index 0000000..bfc9e7f --- /dev/null +++ b/bbootimg/src/main/resources/simplelogger.properties @@ -0,0 +1 @@ +org.slf4j.simpleLogger.defaultLogLevel = info diff --git a/bbootimg/src/test/kotlin/HelperTest.kt b/bbootimg/src/test/kotlin/HelperTest.kt new file mode 100644 index 0000000..4f1665a --- /dev/null +++ b/bbootimg/src/test/kotlin/HelperTest.kt @@ -0,0 +1,78 @@ +import avb.alg.Algorithms +import cfig.Helper +import org.apache.commons.codec.binary.Hex +import org.bouncycastle.jce.provider.BouncyCastleProvider +import org.junit.Assert.* +import org.junit.Test +import java.security.KeyFactory +import java.security.KeyPairGenerator +import java.security.Security +import java.security.Signature +import java.security.spec.PKCS8EncodedKeySpec +import java.security.spec.X509EncodedKeySpec +import javax.crypto.Cipher + +class HelperTest { + @Test + fun rawSignTest() { + val data = Hex.decodeHex("0001ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff003031300d0609608648016503040201050004206317a4c8d86accc8258c1ac23ef0ebd18bc33010d7afb43b241802646360b4ab") + val expectedSig = "28e17bc57406650ed78785fd558e7c1861cc4014c900d72b61c03cdbab1039e713b5bb19b556d04d276b46aae9b8a3999ccbac533a1cce00f83cfb83e2beb35ed7329f71ffec04fc2839a9b44e50abd66ea6c3d3bea6705e93e9139ecd0331170db18eba36a85a78bc49a5447260a30ed19d956cb2f8a71f6b19e57fdca43e052d1bb7840bf4c3efb47111f4d77764236d2e013fbf3b2577e4a3e01c9d166a5e890ef96210882e6e88ceca2fe3a2201f4961210d4ec6167f5dfd0e038e4a146f960caecab7d15ba65f6edcf5dbd25f5af543cfb8da4338bdbc872eec3f8e72aa8db679099e70952d3f7176c0b9111bf20ad1390eab1d09a859105816fdf92fbb" + val privkFile = "../" + Algorithms.get("SHA256_RSA2048")!!.defaultKey.replace("pem", "pk8") + val encData = Helper.rawSign(privkFile, data) + assertEquals(expectedSig, Hex.encodeHexString(encData)) + } + + @Test + fun rawSignOpenSslTest() { + val data = Hex.decodeHex("0001ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff003031300d0609608648016503040201050004206317a4c8d86accc8258c1ac23ef0ebd18bc33010d7afb43b241802646360b4ab") + val expectedSig = "28e17bc57406650ed78785fd558e7c1861cc4014c900d72b61c03cdbab1039e713b5bb19b556d04d276b46aae9b8a3999ccbac533a1cce00f83cfb83e2beb35ed7329f71ffec04fc2839a9b44e50abd66ea6c3d3bea6705e93e9139ecd0331170db18eba36a85a78bc49a5447260a30ed19d956cb2f8a71f6b19e57fdca43e052d1bb7840bf4c3efb47111f4d77764236d2e013fbf3b2577e4a3e01c9d166a5e890ef96210882e6e88ceca2fe3a2201f4961210d4ec6167f5dfd0e038e4a146f960caecab7d15ba65f6edcf5dbd25f5af543cfb8da4338bdbc872eec3f8e72aa8db679099e70952d3f7176c0b9111bf20ad1390eab1d09a859105816fdf92fbb" + + val sig = Helper.rawSignOpenSsl("../" + Algorithms.get("SHA256_RSA2048")!!.defaultKey, data) + assertEquals(expectedSig, Hex.encodeHexString(sig)) + } + + @Test + fun test3() { + val data = Hex.decodeHex("0001ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff003031300d0609608648016503040201050004206317a4c8d86accc8258c1ac23ef0ebd18bc3301033") + val signature = Signature.getInstance("NONEwithRSA") + val k = Helper.readPrivateKey("../" + Algorithms.get("SHA256_RSA2048")!!.defaultKey.replace("pem", "pk8")) + signature.initSign(k) + signature.update(data) + println("data size " + data.size) + println(signature.provider) + val sig = signature.sign() +// assertEquals(expectedSig, Hex.encodeHexString(sig)) + } + + @Test + fun testCipher() { + Security.addProvider(BouncyCastleProvider()) + for (p in Security.getProviders()) { + println(p.toString()) + for (entry in p.entries) { + println("\t" + entry.key.toString() + " -> " + entry.value) + } + println() + } + } + + @Test + fun testKeys() { + val kp = KeyPairGenerator.getInstance("rsa") + .apply { this.initialize(2048) } + .generateKeyPair() + val pk8Spec = PKCS8EncodedKeySpec(kp.private.encoded) //kp.private.format == PKCS#8 + val x509Spec = X509EncodedKeySpec(kp.public.encoded) //kp.public.format == X.509 + + val kf = KeyFactory.getInstance("rsa") + val privk = kf.generatePrivate(pk8Spec) + val pubk = kf.generatePublic(x509Spec) + + val cipher = Cipher.getInstance("RSA").apply { + this.init(Cipher.ENCRYPT_MODE, privk) + this.update("Good".toByteArray()) + } + val encryptedText = Hex.encodeHexString(cipher.doFinal()) + println(encryptedText) + } +} diff --git a/bbootimg/src/test/kotlin/cfig/io/StructTest.kt b/bbootimg/src/test/kotlin/cfig/io/StructTest.kt index 3f05f67..d545a37 100644 --- a/bbootimg/src/test/kotlin/cfig/io/StructTest.kt +++ b/bbootimg/src/test/kotlin/cfig/io/StructTest.kt @@ -1,7 +1,5 @@ -import cfig.Avb import cfig.Helper import cfig.io.Struct -import org.junit.Assert import org.junit.Test import org.junit.Assert.* @@ -10,11 +8,13 @@ import java.io.ByteArrayInputStream class StructTest { @Test fun constructTest() { - assertEquals(16, Struct("<2i4b4b").calcsize()) - assertEquals(16, Struct("h").calcsize()) - assertEquals(3, Struct(">3s").calcsize()) - assertEquals(4, Struct("!Hh").calcsize()) + assertEquals(16, Struct("<2i4b4b").calcSize()) + assertEquals(16, Struct("h").calcSize()) + assertEquals(3, Struct(">3s").calcSize()) + assertEquals(4, Struct("!Hh").calcSize()) + + Struct("<2i4b4b").dump() try { Struct("abcd")