diff --git a/README.md b/README.md index 28cfd49..2025292 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ [![Build Status](https://travis-ci.org/cfig/Android_boot_image_editor.svg?branch=master)](https://travis-ci.org/cfig/Android_boot_image_editor) [![License](http://img.shields.io/:license-apache-blue.svg?style=flat-square)](http://www.apache.org/licenses/LICENSE-2.0.html) -This tool focuses on editing Android boot.img(also recovery.img and recovery-two-step.img). +This tool focuses on editing Android boot.img(also recovery.img, recovery-two-step.img and vbmeta.img). ## Prerequisite #### Host OS requirement: diff --git a/bbootimg/src/main/kotlin/Avb.kt b/bbootimg/src/main/kotlin/Avb.kt index 0996c2e..8bfad8f 100755 --- a/bbootimg/src/main/kotlin/Avb.kt +++ b/bbootimg/src/main/kotlin/Avb.kt @@ -285,6 +285,9 @@ class Avb { descriptors.forEach { when (it) { + is PropertyDescriptor -> { + ai.auxBlob!!.propertyDescriptor.add(it) + } is HashDescriptor -> { ai.auxBlob!!.hashDescriptors.add(it) } diff --git a/bbootimg/src/main/kotlin/Helper.kt b/bbootimg/src/main/kotlin/Helper.kt index 7db51e3..1eb2cd0 100644 --- a/bbootimg/src/main/kotlin/Helper.kt +++ b/bbootimg/src/main/kotlin/Helper.kt @@ -1,6 +1,5 @@ package cfig -import avb.alg.Algorithms import cfig.io.Struct import com.google.common.math.BigIntegerMath import org.apache.commons.codec.binary.Hex @@ -24,6 +23,7 @@ import java.nio.file.Paths import java.security.KeyFactory import java.security.PrivateKey import java.security.spec.PKCS8EncodedKeySpec +import java.security.spec.RSAPrivateKeySpec import java.util.zip.GZIPInputStream import java.util.zip.GZIPOutputStream import javax.crypto.Cipher @@ -183,9 +183,7 @@ class Helper { https://android.googlesource.com/platform/external/avb/+/master/libavb/avb_crypto.h#158 */ 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) + val rsa = KeyUtil.parsePemPrivateKey(ByteArrayInputStream(key)) Assert.assertEquals(65537.toBigInteger(), rsa.publicExponent) val numBits: Int = BigIntegerMath.log2(rsa.modulus, RoundingMode.CEILING) log.debug("modulus: " + rsa.modulus) @@ -213,7 +211,7 @@ class Helper { // "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 { - val privk = Helper.readPrivateKey(keyPath) + val privk = KeyUtil.parsePk8PrivateKey(Files.readAllBytes(Paths.get(keyPath))) val cipher = Cipher.getInstance("RSA/ECB/NoPadding").apply { this.init(Cipher.ENCRYPT_MODE, privk) this.update(data) @@ -257,12 +255,6 @@ 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/KeyUtil.kt b/bbootimg/src/main/kotlin/KeyUtil.kt new file mode 100644 index 0000000..030169e --- /dev/null +++ b/bbootimg/src/main/kotlin/KeyUtil.kt @@ -0,0 +1,47 @@ +package cfig + +import org.bouncycastle.asn1.pkcs.RSAPrivateKey +import org.bouncycastle.util.io.pem.PemReader +import java.io.InputStream +import java.io.InputStreamReader +import java.math.BigInteger +import java.security.KeyFactory +import java.security.PrivateKey +import java.security.PublicKey +import java.security.spec.PKCS8EncodedKeySpec +import java.security.spec.RSAPrivateKeySpec +import java.security.spec.RSAPublicKeySpec + +class KeyUtil { + companion object { + @Throws(IllegalArgumentException::class) + fun parsePemPrivateKey(inputStream: InputStream): RSAPrivateKey { + val p = PemReader(InputStreamReader(inputStream)).readPemObject() + if ("RSA PRIVATE KEY" != p.type) { + throw IllegalArgumentException("input is not valid 'RSA PRIVATE KEY'") + } + return RSAPrivateKey.getInstance(p.content) + } + + fun parsePemPrivateKey2(inputStream: InputStream): PrivateKey { + val rsa = KeyUtil.parsePemPrivateKey(inputStream) + return generateRsaPrivateKey(rsa.modulus, rsa.privateExponent) + } + + @Throws(Exception::class) + fun parsePk8PrivateKey(inputData: ByteArray): PrivateKey { + val spec = PKCS8EncodedKeySpec(inputData) + return KeyFactory.getInstance("RSA").generatePrivate(spec) + } + + @Throws(Exception::class) + private fun generateRsaPublicKey(modulus: BigInteger, publicExponent: BigInteger): PublicKey { + return KeyFactory.getInstance("RSA").generatePublic(RSAPublicKeySpec(modulus, publicExponent)) + } + + @Throws(Exception::class) + private fun generateRsaPrivateKey(modulus: BigInteger, privateExponent: BigInteger): PrivateKey { + return KeyFactory.getInstance("RSA").generatePrivate(RSAPrivateKeySpec(modulus, privateExponent)) + } + } +} \ No newline at end of file diff --git a/bbootimg/src/main/kotlin/avb/AVBInfo.kt b/bbootimg/src/main/kotlin/avb/AVBInfo.kt index 8258da8..b3696e6 100755 --- a/bbootimg/src/main/kotlin/avb/AVBInfo.kt +++ b/bbootimg/src/main/kotlin/avb/AVBInfo.kt @@ -19,6 +19,7 @@ class AVBInfo(var header: Header? = null, data class AuxBlob( var pubkey: PubKeyInfo? = null, var pubkeyMeta: PubKeyMetadataInfo? = null, + var propertyDescriptor: MutableList = mutableListOf(), var hashTreeDescriptor: MutableList = mutableListOf(), var hashDescriptors: MutableList = mutableListOf(), var kernelCmdlineDescriptor: MutableList = mutableListOf(), diff --git a/bbootimg/src/main/kotlin/avb/desc/ChainPartitionDescriptor.kt b/bbootimg/src/main/kotlin/avb/desc/ChainPartitionDescriptor.kt new file mode 100644 index 0000000..1aa0284 --- /dev/null +++ b/bbootimg/src/main/kotlin/avb/desc/ChainPartitionDescriptor.kt @@ -0,0 +1,10 @@ +package avb.desc + +class ChainPartitionDescriptor { + companion object { + const val TAG = 4L + const val RESERVED = 64 + const val SIZE = 28 + RESERVED + const val FORMAT_STRING = "!2Q3L" + } +} \ No newline at end of file diff --git a/bbootimg/src/main/kotlin/avb/desc/PropertyDescriptor.kt b/bbootimg/src/main/kotlin/avb/desc/PropertyDescriptor.kt index 524a1a3..cefe77b 100755 --- a/bbootimg/src/main/kotlin/avb/desc/PropertyDescriptor.kt +++ b/bbootimg/src/main/kotlin/avb/desc/PropertyDescriptor.kt @@ -2,18 +2,22 @@ package avb.desc import cfig.Helper import cfig.io.Struct +import java.io.InputStream class PropertyDescriptor( var key: String = "", var value: String = "") : Descriptor(TAG, 0, 0) { override fun encode(): ByteArray { - this.num_bytes_following = SIZE + this.key.length + this.value.length + 2 - 16 - val nbf_with_padding = Helper.round_to_multiple(this.num_bytes_following, 8) - val padding_size = nbf_with_padding - num_bytes_following - val padding = Struct("${padding_size}x").pack(0) + if (SIZE != Struct(FORMAT_STRING).calcSize()) { + throw RuntimeException() + } + this.num_bytes_following = SIZE + this.key.length + this.value.length + 2 - 16L + val nbfWithPadding = Helper.round_to_multiple(this.num_bytes_following, 8) + val paddingSize = nbfWithPadding - num_bytes_following + val padding = Struct("${paddingSize}x").pack(0) val desc = Struct(FORMAT_STRING).pack( TAG, - nbf_with_padding, + nbfWithPadding, this.key.length, this.value.length) return Helper.join(desc, @@ -22,9 +26,26 @@ class PropertyDescriptor( padding) } + constructor(data: InputStream, seq: Int = 0) : this() { + val info = Struct(FORMAT_STRING).unpack(data) + this.tag = info[0] as Long + this.num_bytes_following = info[1] as Long + val keySize = info[2] as Long + val valueSize = info[3] as Long + val expectedSize = Helper.round_to_multiple(SIZE - 16 + keySize + 1 + valueSize + 1, 8) + if (this.tag != TAG || expectedSize != this.num_bytes_following) { + throw IllegalArgumentException("Given data does not look like a |property| descriptor") + } + this.sequence = seq + + val info2 = Struct("${keySize}sx${valueSize}s").unpack(data) + this.key = String(info2[0] as ByteArray) + this.value = String(info2[2] as ByteArray) + } + companion object { - val TAG = 0L - val SIZE = 32L - val FORMAT_STRING = "!4Q" + const val TAG = 0L + const val SIZE = 32 + const val FORMAT_STRING = "!4Q" } } \ 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 34078ad..8062e49 100755 --- a/bbootimg/src/main/kotlin/avb/desc/UnknownDescriptor.kt +++ b/bbootimg/src/main/kotlin/avb/desc/UnknownDescriptor.kt @@ -32,6 +32,9 @@ class UnknownDescriptor(var data: ByteArray = byteArrayOf()) : Descriptor(0, 0, fun analyze(): Any { return when (this.tag) { + 0L -> { + PropertyDescriptor(ByteArrayInputStream(this.encode()), this.sequence) + } 1L -> { HashTreeDescriptor(ByteArrayInputStream(this.encode()), this.sequence) } diff --git a/bbootimg/src/test/kotlin/HelperTest.kt b/bbootimg/src/test/kotlin/HelperTest.kt index 4f1665a..756496b 100644 --- a/bbootimg/src/test/kotlin/HelperTest.kt +++ b/bbootimg/src/test/kotlin/HelperTest.kt @@ -1,9 +1,15 @@ import avb.alg.Algorithms import cfig.Helper +import cfig.KeyUtil +import com.google.common.math.BigIntegerMath import org.apache.commons.codec.binary.Hex import org.bouncycastle.jce.provider.BouncyCastleProvider import org.junit.Assert.* import org.junit.Test +import java.math.BigInteger +import java.math.RoundingMode +import java.nio.file.Files +import java.nio.file.Paths import java.security.KeyFactory import java.security.KeyPairGenerator import java.security.Security @@ -11,6 +17,11 @@ import java.security.Signature import java.security.spec.PKCS8EncodedKeySpec import java.security.spec.X509EncodedKeySpec import javax.crypto.Cipher +import java.security.spec.RSAPublicKeySpec +import java.security.PublicKey +import java.security.spec.RSAPrivateKeySpec +import java.security.PrivateKey + class HelperTest { @Test @@ -35,7 +46,8 @@ class HelperTest { fun test3() { val data = Hex.decodeHex("0001ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff003031300d0609608648016503040201050004206317a4c8d86accc8258c1ac23ef0ebd18bc3301033") val signature = Signature.getInstance("NONEwithRSA") - val k = Helper.readPrivateKey("../" + Algorithms.get("SHA256_RSA2048")!!.defaultKey.replace("pem", "pk8")) + val keyFile = "../" + Algorithms.get("SHA256_RSA2048")!!.defaultKey.replace("pem", "pk8") + val k = KeyUtil.parsePk8PrivateKey(Files.readAllBytes(Paths.get(keyFile))) signature.initSign(k) signature.update(data) println("data size " + data.size) @@ -75,4 +87,46 @@ class HelperTest { val encryptedText = Hex.encodeHexString(cipher.doFinal()) println(encryptedText) } + + @Test + fun testRSA() { +// val r = BigIntegerMath.log2(BigInteger.valueOf(1024), RoundingMode.CEILING) +// println(r) +// println(BigInteger.valueOf(1024).mod(BigInteger.valueOf(2))) + + val p = BigInteger.valueOf(3) + val q = BigInteger.valueOf(7) + val modulus = p.multiply(q) + + val keyLength = BigIntegerMath.log2(modulus, RoundingMode.CEILING) + println("keyLength = $keyLength") + + //r = phi(n) = phi(p) * phi(q) = (p - 1)*(q - 1) + val r = (p.subtract(BigInteger.ONE)).multiply(q - BigInteger.ONE) + + //r ~ e + //e is released as the public key exponent + //most commonly e = 2^16 + 1 = 65,537 + val e = BigInteger.valueOf(5) + + //(d * e).mod(r) == 1 + //d is kept as the private key exponent + val d = e.modInverse(r) + + println("p = $p, q = $q, modulus = $modulus , r = $r, e = $e, d = $d") + assertEquals(1, d.multiply(e).mod(r).toInt()) + //private key: (modulus, d), d is calculated + //pub key: (modulus, e) , e is chosen + + val clearMsg = BigInteger.valueOf(10) + val encMsg = clearMsg.pow(e.toInt()).mod(modulus) + println("clear: $clearMsg, enc: $encMsg") + val decMsg = clearMsg + } + + fun gcd(a: BigInteger, b: BigInteger): BigInteger { + return if (b == BigInteger.ZERO) { + a + } else gcd(b, a.mod(b)) + } } diff --git a/bbootimg/src/test/kotlin/KeyUtilTest.kt b/bbootimg/src/test/kotlin/KeyUtilTest.kt new file mode 100644 index 0000000..890a034 --- /dev/null +++ b/bbootimg/src/test/kotlin/KeyUtilTest.kt @@ -0,0 +1,21 @@ +import avb.alg.Algorithms +import cfig.KeyUtil +import org.apache.commons.codec.binary.Hex +import org.junit.Assert.* +import org.junit.Test +import java.io.FileInputStream +import java.nio.file.Files +import java.nio.file.Paths + +class KeyUtilTest { + @Test + fun parseKeys() { + val keyFile = "../" + Algorithms.get("SHA256_RSA2048")!!.defaultKey + val k = KeyUtil.parsePk8PrivateKey(Files.readAllBytes(Paths.get(keyFile.replace("pem", "pk8")))) + + + val k2 = KeyUtil.parsePemPrivateKey2(FileInputStream(keyFile)) + println(Hex.encodeHexString(k.encoded)) + println(Hex.encodeHexString(k2.encoded)) + } +} diff --git a/build.gradle b/build.gradle index 9272ea4..31f9a38 100644 --- a/build.gradle +++ b/build.gradle @@ -108,6 +108,12 @@ void updateBootImage(String activeImg) { case "recovery.img": flashTarget = "/dev/block/by-name/recovery"; break; + case "vbmeta.img": + flashTarget = "/dev/block/by-name/vbmeta"; + if (!new File(activeImg + ".signed").exists()) { + return; + } + break; } Run("adb root") Run("adb push " + activeImg + ".signed /cache/") @@ -120,6 +126,7 @@ void updateBootImage(String activeImg) { task flash { doLast { updateBootImage(activeImg) + updateBootImage("vbmeta.img") } }