From 30a5a0cbadff3e55dc28d40fa4eda0c49d20a297 Mon Sep 17 00:00:00 2001 From: cfig Date: Thu, 5 Nov 2020 17:09:31 +0800 Subject: [PATCH] unify helpers and remove codacy --- .codacy.yml | 3 - README.md | 1 - avbImpl/src/avbx/cpp/CfigAvbOps.cpp | 6 +- bbootimg/src/main/kotlin/avb/blob/AuthBlob.kt | 6 +- bbootimg/src/main/kotlin/avb/blob/AuxBlob.kt | 4 +- bbootimg/src/main/kotlin/bootimg/Common.kt | 3 +- bbootimg/src/main/kotlin/helper/Helper.kt | 73 ---- bbootimg/src/main/kotlin/helper/KeyHelper.kt | 93 +++++ bbootimg/src/main/kotlin/helper/KeyHelper2.kt | 187 ++++++++++ .../src/main/kotlin/helper/OpenSslHelper.kt | 341 ++++++++++++++++++ bbootimg/src/test/kotlin/HelperTest.kt | 10 +- src/integrationTest/resources | 2 +- tools/free.py | 7 +- 13 files changed, 640 insertions(+), 96 deletions(-) delete mode 100644 .codacy.yml create mode 100644 bbootimg/src/main/kotlin/helper/KeyHelper.kt create mode 100644 bbootimg/src/main/kotlin/helper/KeyHelper2.kt create mode 100644 bbootimg/src/main/kotlin/helper/OpenSslHelper.kt diff --git a/.codacy.yml b/.codacy.yml deleted file mode 100644 index 180c3b9..0000000 --- a/.codacy.yml +++ /dev/null @@ -1,3 +0,0 @@ ---- -exclude_paths: - - 'aosp/**' diff --git a/README.md b/README.md index c00380c..194ae9e 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,5 @@ # Android_boot_image_editor [![Build Status](https://travis-ci.org/cfig/Android_boot_image_editor.svg?branch=master)](https://travis-ci.org/cfig/Android_boot_image_editor) -[![Codacy Badge](https://api.codacy.com/project/badge/Grade/fa6a49bb22b84307b12e7a8878867c1e)](https://app.codacy.com/manual/cfig97/Android_boot_image_editor?utm_source=github.com&utm_medium=referral&utm_content=cfig/Android_boot_image_editor&utm_campaign=Badge_Grade_Dashboard) [![License](http://img.shields.io/:license-apache-blue.svg?style=flat-square)](http://www.apache.org/licenses/LICENSE-2.0.html) A tool for reverse engineering Android ROM images. (working on ![Linux](doc/linux24.png)(Ubuntu 18.04+) and ![Mac](doc/apple24.png)) diff --git a/avbImpl/src/avbx/cpp/CfigAvbOps.cpp b/avbImpl/src/avbx/cpp/CfigAvbOps.cpp index 7390fc2..fb5b0ee 100644 --- a/avbImpl/src/avbx/cpp/CfigAvbOps.cpp +++ b/avbImpl/src/avbx/cpp/CfigAvbOps.cpp @@ -206,7 +206,7 @@ static AvbIOResult read_from_partitionX(AvbOps *, //seek if (lseek(fd, offset, SEEK_SET) != offset) { fprintf(stderr, - "[%s()]: Error seeking to pos %lld in file %s: %s\n", + "[%s()]: Error seeking to pos %ld in file %s: %s\n", __FUNCTION__, offset, partitionFile.c_str(), @@ -215,7 +215,7 @@ static AvbIOResult read_from_partitionX(AvbOps *, return AVB_IO_RESULT_ERROR_IO; } ssize_t num_read = read(fd, buffer, num_bytes); - if (num_read < 0) { + if (num_read < 0 || num_read != num_bytes) { fprintf(stderr, "[%s()]: Error reading %zd bytes from pos %" PRId64 " in file %s: %s\n", __FUNCTION__, @@ -333,7 +333,7 @@ bool CfigAvbOps::preload_partition(std::string partition) { auto *buffer = static_cast(malloc(file_size)); ssize_t num_read = read(fd, buffer, file_size); - if (num_read != file_size) { + if (num_read < 0 || num_read != file_size) { fprintf(stderr, "[%s()]: Error reading %lld bytes from file '%s': %s\n", __FUNCTION__, diff --git a/bbootimg/src/main/kotlin/avb/blob/AuthBlob.kt b/bbootimg/src/main/kotlin/avb/blob/AuthBlob.kt index 17bd249..06bfb51 100644 --- a/bbootimg/src/main/kotlin/avb/blob/AuthBlob.kt +++ b/bbootimg/src/main/kotlin/avb/blob/AuthBlob.kt @@ -2,8 +2,11 @@ package avb.blob import avb.alg.Algorithms import cfig.helper.Helper +import cfig.helper.KeyHelper2 import cfig.io.Struct3 import org.slf4j.LoggerFactory +import java.nio.file.Files +import java.nio.file.Paths import java.security.MessageDigest @OptIn(ExperimentalUnsignedTypes::class) @@ -32,7 +35,8 @@ data class AuthBlob( update(header_data_blob) update(aux_data_blob) }.digest() - binarySignature = Helper.rawSign(alg.defaultKey.replace(".pem", ".pk8"), Helper.join(alg.padding, binaryHash)) + val k = KeyHelper2.parseRsaPk8(Files.readAllBytes(Paths.get(alg.defaultKey.replace(".pem", ".pk8")))) + binarySignature = KeyHelper2.rawSign(k, Helper.join(alg.padding, binaryHash)) } val authData = Helper.join(binaryHash, binarySignature) return Helper.join(authData, Struct3("${authBlockSize - authData.size}x").pack(0)) diff --git a/bbootimg/src/main/kotlin/avb/blob/AuxBlob.kt b/bbootimg/src/main/kotlin/avb/blob/AuxBlob.kt index cecf70e..7ae0b26 100644 --- a/bbootimg/src/main/kotlin/avb/blob/AuxBlob.kt +++ b/bbootimg/src/main/kotlin/avb/blob/AuxBlob.kt @@ -3,6 +3,8 @@ package avb.blob import avb.alg.Algorithm import avb.desc.* import cfig.helper.Helper +import cfig.helper.KeyHelper +import cfig.helper.KeyHelper2 import cfig.io.Struct3 import com.fasterxml.jackson.annotation.JsonIgnoreProperties import org.slf4j.LoggerFactory @@ -94,7 +96,7 @@ class AuxBlob( if (key == null) { algKey = Files.readAllBytes((Paths.get(alg.defaultKey))) } - encodedKey = Helper.encodeRSAkey(algKey!!) + encodedKey = KeyHelper.encodeRSAkey(algKey!!) log.info("encodePubKey(): size = ${alg.public_key_num_bytes}, algorithm key size: ${encodedKey.size}") assert(alg.public_key_num_bytes == encodedKey.size) } else { diff --git a/bbootimg/src/main/kotlin/bootimg/Common.kt b/bbootimg/src/main/kotlin/bootimg/Common.kt index 693addf..ef1aac1 100644 --- a/bbootimg/src/main/kotlin/bootimg/Common.kt +++ b/bbootimg/src/main/kotlin/bootimg/Common.kt @@ -14,6 +14,7 @@ import java.io.ByteArrayInputStream import java.io.ByteArrayOutputStream import java.io.File import java.io.FileInputStream +import java.lang.NumberFormatException import java.nio.ByteBuffer import java.nio.ByteOrder import java.security.MessageDigest @@ -291,7 +292,7 @@ class Common { ret } } - } catch (e: Exception) { + } catch (e: NumberFormatException) { log.warn("can not parse osVersion from $osVersion") 10 } diff --git a/bbootimg/src/main/kotlin/helper/Helper.kt b/bbootimg/src/main/kotlin/helper/Helper.kt index 8f7ed06..d05b620 100644 --- a/bbootimg/src/main/kotlin/helper/Helper.kt +++ b/bbootimg/src/main/kotlin/helper/Helper.kt @@ -1,6 +1,5 @@ package cfig.helper -import cfig.KeyUtil import cfig.io.Struct3 import com.google.common.math.BigIntegerMath import org.apache.commons.codec.binary.Hex @@ -137,77 +136,6 @@ class Helper { } } - /* - read RSA private key - assert exp == 65537 - num_bits = log2(modulus) - - @return: AvbRSAPublicKeyHeader formatted bytearray - https://android.googlesource.com/platform/external/avb/+/master/libavb/avb_crypto.h#158 - from avbtool::encode_rsa_key() - */ - fun encodeRSAkey(key: ByteArray): ByteArray { - val rsa = KeyUtil.parsePemPrivateKey(ByteArrayInputStream(key)) - assert(65537.toBigInteger() == rsa.publicExponent) - val numBits: Int = BigIntegerMath.log2(rsa.modulus, RoundingMode.CEILING) - log.debug("modulus: " + rsa.modulus) - log.debug("numBits: $numBits") - val b = BigInteger.valueOf(2).pow(32) - val n0inv = (b - rsa.modulus.modInverse(b)).toLong() - 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) - val unsignedModulo = rsa.modulus.toByteArray().sliceArray(1..numBits / 8) //remove sign byte - log.debug("unsigned modulo: " + Hex.encodeHexString(unsignedModulo)) - val ret = Struct3("!II${numBits / 8}b${numBits / 8}b").pack( - numBits, - n0inv, - unsignedModulo, - rrModn.toByteArray()) - log.debug("rrmodn: " + Hex.encodeHexString(rrModn.toByteArray())) - log.debug("RSA: " + Hex.encodeHexString(ret)) - 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 { - 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) - } - 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) - val stdout = ByteArrayOutputStream() - val stderr = ByteArrayOutputStream() - exe.streamHandler = PumpStreamHandler(stdout, stderr, stdin) - 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 { - log.debug("OUT: " + Hex.encodeHexString(stdout.toByteArray())) - log.debug("ERR: " + String(stderr.toByteArray())) - } - - if (ret.isEmpty()) throw RuntimeException("raw sign failed") - - return ret - } - fun pyAlg2java(alg: String): String { return when (alg) { "sha1" -> "sha-1" @@ -401,7 +329,6 @@ class Helper { return result } - private val log = LoggerFactory.getLogger("Helper") } } diff --git a/bbootimg/src/main/kotlin/helper/KeyHelper.kt b/bbootimg/src/main/kotlin/helper/KeyHelper.kt new file mode 100644 index 0000000..0e1bde0 --- /dev/null +++ b/bbootimg/src/main/kotlin/helper/KeyHelper.kt @@ -0,0 +1,93 @@ +package cfig.helper + +import cfig.io.Struct3 +import com.google.common.math.BigIntegerMath +import org.apache.commons.codec.binary.Hex +import org.bouncycastle.util.io.pem.PemReader +import org.slf4j.LoggerFactory +import java.io.ByteArrayInputStream +import java.io.InputStream +import java.io.InputStreamReader +import java.math.BigInteger +import java.math.RoundingMode +import java.security.Security + +class KeyHelper { + companion object { + private val log = LoggerFactory.getLogger(KeyHelper::class.java) + + @Throws(IllegalArgumentException::class) + fun parsePemPubkey(inputStream: InputStream): org.bouncycastle.asn1.pkcs.RSAPublicKey { + val p = PemReader(InputStreamReader(inputStream)).readPemObject() + if ("RSA PUBLIC KEY" != p.type) { + throw IllegalArgumentException("input is not valid 'RSA PUBLIC KEY'") + } + return org.bouncycastle.asn1.pkcs.RSAPublicKey.getInstance(p.content) + } + + @Throws(IllegalArgumentException::class) + fun parsePemPrivateKeyBC(inputStream: InputStream): org.bouncycastle.asn1.pkcs.RSAPrivateKey { + val p = PemReader(InputStreamReader(inputStream)).readPemObject() + if ("RSA PRIVATE KEY" != p.type) { + throw IllegalArgumentException("input is not valid 'RSA PRIVATE KEY'") + } + return org.bouncycastle.asn1.pkcs.RSAPrivateKey.getInstance(p.content) + } + +// fun parsePemPrivateKey(inputStream: InputStream): java.security.PrivateKey { +// val rsa = BC.parsePemPrivateKeyBC(inputStream) +// return generateRsaPrivateKey(rsa.modulus, rsa.privateExponent) +// } + + + /* + read RSA private key + assert exp == 65537 + num_bits = log2(modulus) + + @return: AvbRSAPublicKeyHeader formatted bytearray + https://android.googlesource.com/platform/external/avb/+/master/libavb/avb_crypto.h#158 + from avbtool::encode_rsa_key() + */ + fun encodeRSAkey(key: ByteArray): ByteArray { + val rsa = parsePemPrivateKeyBC(ByteArrayInputStream(key)) + assert(65537.toBigInteger() == rsa.publicExponent) + val numBits: Int = BigIntegerMath.log2(rsa.modulus, RoundingMode.CEILING) + log.debug("modulus: " + rsa.modulus) + log.debug("numBits: $numBits") + val b = BigInteger.valueOf(2).pow(32) + val n0inv = (b - rsa.modulus.modInverse(b)).toLong() + 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) + val unsignedModulo = rsa.modulus.toByteArray().sliceArray(1..numBits / 8) //remove sign byte + log.debug("unsigned modulo: " + Hex.encodeHexString(unsignedModulo)) + val ret = Struct3("!II${numBits / 8}b${numBits / 8}b").pack( + numBits, + n0inv, + unsignedModulo, + rrModn.toByteArray()) + log.debug("rrmodn: " + Hex.encodeHexString(rrModn.toByteArray())) + log.debug("RSA: " + Hex.encodeHexString(ret)) + return ret + } + + fun listAll() { + Security.getProviders().forEach { + val sb = StringBuilder("Provider: " + it.name + "{") + it.stringPropertyNames().forEach { key -> + sb.append(" (k=" + key + ",v=" + it.getProperty(key) + "), ") + } + sb.append("}") + log.info(sb.toString()) + } + + var i = 0 + for (item in Security.getAlgorithms("Cipher")) { + log.info("Cipher: $i -> $item") + i++ + } + } + } +} diff --git a/bbootimg/src/main/kotlin/helper/KeyHelper2.kt b/bbootimg/src/main/kotlin/helper/KeyHelper2.kt new file mode 100644 index 0000000..c53fcb5 --- /dev/null +++ b/bbootimg/src/main/kotlin/helper/KeyHelper2.kt @@ -0,0 +1,187 @@ +package cfig.helper + +import org.apache.commons.codec.binary.Hex +import org.apache.commons.exec.CommandLine +import org.apache.commons.exec.DefaultExecutor +import org.apache.commons.exec.ExecuteException +import org.apache.commons.exec.PumpStreamHandler +import org.slf4j.LoggerFactory +import java.io.ByteArrayInputStream +import java.io.ByteArrayOutputStream +import java.math.BigInteger +import java.security.* +import java.security.spec.PKCS8EncodedKeySpec +import java.security.spec.RSAPrivateKeySpec +import java.security.spec.RSAPublicKeySpec +import java.security.spec.X509EncodedKeySpec +import java.util.* +import javax.crypto.Cipher + +class KeyHelper2 { + companion object { + private val log = LoggerFactory.getLogger(KeyHelper2::class.java) + + fun parseRsaPk8(inputData: ByteArray): java.security.PrivateKey { + val spec = PKCS8EncodedKeySpec(inputData) + return KeyFactory.getInstance("RSA").generatePrivate(spec) + } + + fun parseRsaKey(keyText: String): PrivateKey { + val publicKeyPEM = keyText + .replace("-----BEGIN RSA PRIVATE KEY-----", "") + .replace(System.lineSeparator().toRegex(), "") + .replace("\n", "") + .replace("\r", "") + .replace("-----END RSA PRIVATE KEY-----", "") + log.warn("trimmed key") + log.warn(publicKeyPEM) + val encoded: ByteArray = Base64.getDecoder().decode(publicKeyPEM) + val keySpec = X509EncodedKeySpec(encoded) + return KeyFactory.getInstance("RSA").generatePrivate(keySpec) + } + + fun parsePemPubkey(keyText: String): java.security.interfaces.RSAPublicKey { + val publicKeyPEM = keyText + .replace("-----BEGIN PUBLIC KEY-----", "") + .replace(System.lineSeparator().toRegex(), "") + .replace("\n", "") + .replace("\r", "") + .replace("-----END PUBLIC KEY-----", "") + val encoded: ByteArray = Base64.getDecoder().decode(publicKeyPEM) + val keySpec = X509EncodedKeySpec(encoded) + return KeyFactory.getInstance("RSA").generatePublic(keySpec) as java.security.interfaces.RSAPublicKey + } + + + fun parsePemPubCert(keyText: String) { + val publicKeyPEM = keyText + .replace("-----BEGIN CERTIFICATE-----", "") + .replace(System.lineSeparator().toRegex(), "") + .replace("\n", "") + .replace("\r", "") + .replace("-----END CERTIFICATE-----", "") + val encoded: ByteArray = Base64.getDecoder().decode(publicKeyPEM) + val keySpec = X509EncodedKeySpec(encoded) +// return KeyFactory.getInstance("RSA").generatePublic(keySpec) as java.security.interfaces.RSAPublicKey + } + + /* + in: modulus, expo + out: PublicKey + */ + fun generateRsaPublicKey(modulus: BigInteger, publicExponent: BigInteger): java.security.PublicKey { + return KeyFactory.getInstance("RSA").generatePublic(RSAPublicKeySpec(modulus, publicExponent)) + } + + /* + in: modulus, expo + out: PrivateKey + */ + fun generateRsaPrivateKey(modulus: BigInteger, privateExponent: BigInteger): java.security.PrivateKey { + return KeyFactory.getInstance("RSA").generatePrivate(RSAPrivateKeySpec(modulus, privateExponent)) + } + + //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(privk: java.security.PrivateKey, data: ByteArray): ByteArray { + return Cipher.getInstance("RSA/ECB/NoPadding").let { cipher -> + cipher.init(Cipher.ENCRYPT_MODE, privk) + cipher.update(data) + 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) + val stdout = ByteArrayOutputStream() + val stderr = ByteArrayOutputStream() + exe.streamHandler = PumpStreamHandler(stdout, stderr, stdin) + 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 { + log.debug("OUT: " + Hex.encodeHexString(stdout.toByteArray())) + log.debug("ERR: " + String(stderr.toByteArray())) + } + + if (ret.isEmpty()) throw RuntimeException("raw sign failed") + + return ret + } + + fun pyAlg2java(alg: String): String { + return when (alg) { + "sha1" -> "sha-1" + "sha224" -> "sha-224" + "sha256" -> "sha-256" + "sha384" -> "sha-384" + "sha512" -> "sha-512" + else -> throw IllegalArgumentException("unknown algorithm: [$alg]") + } + } + + /* + openssl dgst -sha256 + */ + fun sha256(inData: ByteArray): ByteArray { + return MessageDigest.getInstance("SHA-256").digest(inData) + } + + fun rsa(inData: ByteArray, inKey: java.security.PrivateKey): ByteArray { + return Cipher.getInstance("RSA").let { + it.init(Cipher.ENCRYPT_MODE, inKey) + it.doFinal(inData) + } + } + + fun sha256rsa(inData: ByteArray, inKey: java.security.PrivateKey): ByteArray { + return rsa(sha256(inData), inKey) + } + + fun sign(inData: ByteArray, privateKey: PrivateKey): String { + val signature = Signature.getInstance("SHA256withRSA").let { + it.initSign(privateKey) + it.update(inData) + it.sign() + } + return Base64.getEncoder().encodeToString(signature) + } + + fun verify(inData: ByteArray, signature: ByteArray, pubKey: PublicKey): Boolean { + return Signature.getInstance("SHA256withRSA").let { + it.initVerify(pubKey) + it.update(inData) + it.verify(signature) + } + } + + + fun verify(inData: ByteArray, base64Signature: String, pubKey: PublicKey): Boolean { + val signatureBytes = Base64.getDecoder().decode(base64Signature) + return Signature.getInstance("SHA256withRSA").let { + it.initVerify(pubKey) + it.update(inData) + it.verify(signatureBytes) + } + } + + fun verify2(inData: ByteArray, encrypedHash: ByteArray, inKey: java.security.PublicKey): Boolean { + val calcHash = sha256(inData) + val decrypedHash = Cipher.getInstance("RSA").let { + it.init(Cipher.DECRYPT_MODE, inKey) + it.doFinal(encrypedHash) + } + return calcHash.contentEquals(decrypedHash) + } + } +} diff --git a/bbootimg/src/main/kotlin/helper/OpenSslHelper.kt b/bbootimg/src/main/kotlin/helper/OpenSslHelper.kt new file mode 100644 index 0000000..0168ac6 --- /dev/null +++ b/bbootimg/src/main/kotlin/helper/OpenSslHelper.kt @@ -0,0 +1,341 @@ +package cfig.helper + +import org.apache.commons.exec.CommandLine +import org.bouncycastle.util.encoders.Hex +import org.slf4j.LoggerFactory +import java.io.ByteArrayInputStream +import java.io.File +import java.io.FileOutputStream +import java.lang.RuntimeException +import java.util.* + +class OpenSslHelper { + enum class KeyFormat { + PEM, //header + metadata + base64 der + DER // der format + } + + interface IKey { + val name: String + val data: ByteArray + fun writeTo(fileName: String) { + FileOutputStream(fileName, false).use { + it.write(data) + } + } + } + + class PK1Key(val format: KeyFormat = KeyFormat.PEM, + override val data: ByteArray = byteArrayOf(), + override val name: String = "RSA Private") : IKey { + /* + PEM private key -> PEM/DER public key + */ + fun getPublicKey(pubKeyFormat: KeyFormat): PK1PubKey { + if (format != KeyFormat.PEM) { + throw IllegalArgumentException("can not handle $format private key") + } + val ret = Helper.powerRun("openssl rsa -in - -pubout -outform ${pubKeyFormat.name}", + ByteArrayInputStream(data)) + log.info("privateToPublic:stderr: ${String(ret[1])}") + return PK1PubKey(format = pubKeyFormat, data = ret[0]) + } + + /* + file based: + openssl rsa -in private.pem -pubout -out public.pem + stream based: + openssl rsa -in - -pubout + */ + fun getPk8PublicKey(): Pk8PubKey { + if (this.format != KeyFormat.PEM) { + throw java.lang.IllegalArgumentException("Only PEM key is supported") + } + val ret = Helper.powerRun2("openssl rsa -in - -pubout", + ByteArrayInputStream(data)) + if (ret[0] as Boolean) { + log.info("getPk8PublicKey:error: ${String(ret[2] as ByteArray)}") + return Pk8PubKey(KeyFormat.PEM, ret[1] as ByteArray) + } else { + log.error("stdout: " + String(ret[1] as ByteArray)) + log.error("stderr: " + String(ret[2] as ByteArray)) + throw RuntimeException() + } + } + + /* + file based: + openssl pkcs8 -nocrypt -in $(rsa_key) -topk8 -outform DER -out $(pk8_key) + stream based: + openssl pkcs8 -nocrypt -in - -topk8 -outform DER + */ + fun toPk8(pk8Format: KeyFormat): PK8RsaKey { + val ret = Helper.powerRun("openssl pkcs8 -nocrypt -in - -topk8 -outform ${pk8Format.name}", + ByteArrayInputStream(data)) + log.info("toPk8Private:stderr: ${String(ret[1])}") + return PK8RsaKey(format = pk8Format, data = ret[0]) + } + + fun toCsr(): Csr { + val info = "/C=CN/ST=Shanghai/L=Shanghai/O=XXX/OU=infra/CN=gerrit/emailAddress=webmaster@XX.com" + val cmdLine = CommandLine.parse("openssl req -new -key - -subj").apply { + this.addArgument("$info", true) + } + val ret = Helper.powerRun3(cmdLine, ByteArrayInputStream(data)) + if (ret[0] as Boolean) { + log.info("toCsr:error: ${String(ret[2] as ByteArray)}") + return Csr(data = ret[1] as ByteArray) + } else { + log.error("stdout: " + String(ret[1] as ByteArray)) + log.error("stderr: " + String(ret[2] as ByteArray)) + throw RuntimeException() + } + } + + fun toV1Cert(): Crt { + //full command: + // openssl x509 -req -in 2017key.csr -signkey 2017key.rsa.pem -out theCert.crt -days 180 + //send RSA key as input stream: + // openssl x509 -req -in 2017key.csr -signkey - -out theCert.crt -days 180 + //send RSA key as input stream, crt as output stream: + // openssl x509 -req -in 2017key.csr -signkey - -days 180 + val csr = this.toCsr() + val tmpFile = File.createTempFile("pk1.", ".csr") + tmpFile.writeBytes(csr.data) + tmpFile.deleteOnExit() + val ret = Helper.powerRun2("openssl x509 -req -in ${tmpFile.path} -signkey - -days 180", + ByteArrayInputStream(data)) + if (ret[0] as Boolean) { + log.info("toCrt:error: ${String(ret[2] as ByteArray)}") + return Crt(ret[1] as ByteArray) + } else { + log.error("stdout: " + String(ret[1] as ByteArray)) + log.error("stderr: " + String(ret[2] as ByteArray)) + throw RuntimeException() + } + } + + companion object { + /* + -> PEM RSA private key + */ + fun generate(keyLength: Int): PK1Key { + val ret = Helper.powerRun("openssl genrsa $keyLength", null) + log.info("generateRSA:stderr: ${String(ret[1])}") + return PK1Key(format = KeyFormat.PEM, data = ret[0]) + } + } + } + + class PK8RsaKey(val format: KeyFormat = KeyFormat.PEM, + override val data: ByteArray = byteArrayOf(), + override val name: String = "PK8 Private") : IKey { + + /* + file based: + openssl pkcs8 -nocrypt -in $(pk8_key) -inform DER -out $(rsa_key).converted.tmp + openssl rsa -in $(rsa_key).converted.tmp -out $(rsa_key).converted + stream based: + openssl pkcs8 -nocrypt -in - -inform DER + openssl rsa -in - -out - + */ + fun toPk1(): PK1Key { + if (this.format != KeyFormat.PEM) { + throw IllegalArgumentException("Only pk8+pem can be converted to RSA") + } + val ret = Helper.powerRun2("openssl rsa -in -", + ByteArrayInputStream(data)) + if (ret[0] as Boolean) { + log.info("toRsaPrivate:error: ${String(ret[2] as ByteArray)}") + return PK1Key(KeyFormat.PEM, ret[1] as ByteArray) + } else { + log.error("stdout: " + String(ret[1] as ByteArray)) + log.error("stderr: " + String(ret[2] as ByteArray)) + throw RuntimeException() + } + } + + /* + openssl pkcs8 -nocrypt -in - -inform DER + */ + fun transform(inFormat: KeyFormat, outFormat: KeyFormat): PK8RsaKey { + val ret = Helper.powerRun2("openssl pkcs8 -nocrypt -in - -inform ${inFormat.name} -outform ${outFormat.name}", + ByteArrayInputStream(data)) + if (ret[0] as Boolean) { + log.info("transform:error: ${String(ret[2] as ByteArray)}") + return PK8RsaKey(data = ret[1] as ByteArray) + } else { + log.error("stdout: " + String(ret[1] as ByteArray)) + log.error("stderr: " + String(ret[2] as ByteArray)) + throw IllegalArgumentException() + } + } + + /* + file based: + openssl rsa -in pkcs8.pem -pubout -out public_pkcs8.pem + stream based: + openssl rsa -in - -pubout + */ + fun getPublicKey(): Pk8PubKey { + if (this.format != KeyFormat.PEM) { + throw java.lang.IllegalArgumentException("Only PEM key is supported") + } + val ret = Helper.powerRun2("openssl rsa -in - -pubout", + ByteArrayInputStream(data)) + if (ret[0] as Boolean) { + log.info("getPublicKey:error: ${String(ret[2] as ByteArray)}") + return Pk8PubKey(KeyFormat.PEM, ret[1] as ByteArray) + } else { + log.error("stdout: " + String(ret[1] as ByteArray)) + log.error("stderr: " + String(ret[2] as ByteArray)) + throw RuntimeException() + } + } + } + + class PK1PubKey( + val format: KeyFormat = KeyFormat.PEM, + override val data: ByteArray = byteArrayOf(), + override val name: String = "RSA Public" + ) : IKey + + class Pk8PubKey( + val format: KeyFormat = KeyFormat.PEM, + override val data: ByteArray = byteArrayOf(), + override val name: String = "Pk8 Public" + ) : IKey + + class Csr(override val name: String = "CSR", override val data: ByteArray = byteArrayOf()) : IKey + class Jks(override val name: String = "Java Keystore", override val data: ByteArray = byteArrayOf()) : IKey { + //keytool -list -v -deststorepass $(thePassword) -keystore $(jks_file) + fun check(passWord: String = "somepassword") { + val tmpFile = File.createTempFile("tmp.", ".jks").apply { this.deleteOnExit() } + tmpFile.writeBytes(this.data) + val ret = Helper.powerRun2("keytool -list -v -deststorepass $passWord -keystore $tmpFile", + null) + if (ret[0] as Boolean) { + log.info("Jks.check:stdout: ${String(ret[1] as ByteArray)}") + log.info("Jks.check:error: ${String(ret[2] as ByteArray)}") + } else { + log.error("stdout: " + String(ret[1] as ByteArray)) + log.error("stderr: " + String(ret[2] as ByteArray)) + throw RuntimeException() + } + } + } + + class Crt(val data: ByteArray = byteArrayOf()) { + //Result: trustedCertEntry + //keytool -importcert -file 2017key.crt -deststorepass somepassword -srcstorepass somepassword -keystore 2017key.2.jks + fun toJks(paramSrcPass: String = "somepassword", paramDstPass: String = "somepassword"): Jks { + val crtFile = File.createTempFile("tmp.", ".crt").apply { this.deleteOnExit() } + crtFile.writeBytes(this.data) + val outFile = File.createTempFile("tmp.", ".jks").apply { this.delete() } + val ret = Helper.powerRun2("keytool -importcert -file ${crtFile.path}" + + " -deststorepass $paramDstPass -srcstorepass $paramSrcPass " + + " -keystore ${outFile.path}", + ByteArrayInputStream("yes\n".toByteArray())) + if (ret[0] as Boolean) { + log.info("toJks:error: ${String(ret[2] as ByteArray)}") + log.info("toJks:stdout: ${String(ret[1] as ByteArray)}") + } else { + log.error("stdout: " + String(ret[1] as ByteArray)) + log.error("stderr: " + String(ret[2] as ByteArray)) + throw RuntimeException() + } + + if (!outFile.exists()) { + throw RuntimeException() + } + val outData = outFile.readBytes() + outFile.delete() + return Jks(data = outData) + } + } + + class Pfx(override val name: String = "androiddebugkey", + var thePassword: String = "somepassword", + override var data: ByteArray = byteArrayOf()) : IKey { + fun generate(pk1: PK1Key, crt: Crt) { + val pk1File = File.createTempFile("tmp.", ".file").apply { this.deleteOnExit() } + pk1File.writeBytes(pk1.data) + + val crtFile = File.createTempFile("tmp.", ".file").apply { this.deleteOnExit() } + crtFile.writeBytes(crt.data) + + //openssl pkcs12 -export -out $(pfx_cert) -inkey $(rsa_key) -in $(crt_file) -password pass:$(thePassword) -name $(thePfxName) + val cmd = "openssl pkcs12 -export " + + " -inkey ${pk1File.path} " + + " -in ${crtFile.path} " + + " -password pass:${this.thePassword} -name ${this.name}" + val ret = Helper.powerRun2(cmd, null) + if (ret[0] as Boolean) { + log.info("toPfx:error: ${String(ret[2] as ByteArray)}") + log.info("toPfx:stdout: ${Hex.toHexString(ret[1] as ByteArray)}") + this.data = ret[1] as ByteArray + } else { + log.error("stdout: " + String(ret[1] as ByteArray)) + log.error("stderr: " + String(ret[2] as ByteArray)) + throw RuntimeException() + } + } + + //Zkeytool -importkeystore -deststorepass $(thePassword) -destkeystore $(jks_file) -srckeystore $(pfx_cert) -srcstoretype PKCS12 -srcstorepass $(thePassword) + fun toJks(): Jks { + val jksFile = File.createTempFile("tmp.", ".file").apply { this.delete() } + val thisFile = File.createTempFile("tmp.", ".file").apply { this.deleteOnExit() } + thisFile.writeBytes(this.data) + val cmd = "keytool -importkeystore " + + " -srcstorepass $thePassword -deststorepass $thePassword " + + " -destkeystore ${jksFile.path} " + + " -srckeystore $thisFile -srcstoretype PKCS12" + val ret = Helper.powerRun2(cmd, null) + if (ret[0] as Boolean) { + log.info("toJks:error: " + String(ret[2] as ByteArray)) + log.info("toJks:stdout: " + String(ret[1] as ByteArray)) + this.data = ret[1] as ByteArray + } else { + log.error("stdout: " + String(ret[1] as ByteArray)) + log.error("stderr: " + String(ret[2] as ByteArray)) + throw RuntimeException() + } + val outDate = jksFile.readBytes() + jksFile.delete() + return Jks(this.name, outDate) + } + } + + companion object { + private val log = LoggerFactory.getLogger(OpenSslHelper::class.java) + fun decodePem(keyText: String): ByteArray { + val publicKeyPEM = keyText + .replace("-----BEGIN .*-----".toRegex(), "") + .replace(System.lineSeparator().toRegex(), "") + .replace("\n", "") + .replace("\r", "") + .replace("-----END .*-----".toRegex(), "") + return Base64.getDecoder().decode(publicKeyPEM) + } + + fun toPfx(password: String = "somepassword", keyName: String = "androiddebugkey", pk1: PK1Key, crt: Crt) { + val pk1File = File.createTempFile("tmp.", ".file").apply { this.deleteOnExit() } + pk1File.writeBytes(pk1.data) + + val crtFile = File.createTempFile("tmp.", ".file").apply { this.deleteOnExit() } + crtFile.writeBytes(crt.data) + + //openssl pkcs12 -export -out $(pfx_cert) -inkey $(rsa_key) -in $(crt_file) -password pass:$(thePassword) -name $(thePfxName) + val cmd = "openssl pkcs12 -export -inkey ${pk1File.path} -in ${crtFile.path} -password pass:$password -name $keyName" + val ret = Helper.powerRun2(cmd, null) + if (ret[0] as Boolean) { + log.info("toPfx:error: ${String(ret[2] as ByteArray)}") + log.info("toPfx:stdout: ${Hex.toHexString(ret[1] as ByteArray)}") + } else { + log.error("stdout: " + String(ret[1] as ByteArray)) + log.error("stderr: " + String(ret[2] as ByteArray)) + throw RuntimeException() + } + } + } +} diff --git a/bbootimg/src/test/kotlin/HelperTest.kt b/bbootimg/src/test/kotlin/HelperTest.kt index 549fe60..aeec057 100644 --- a/bbootimg/src/test/kotlin/HelperTest.kt +++ b/bbootimg/src/test/kotlin/HelperTest.kt @@ -1,14 +1,12 @@ import avb.alg.Algorithms import cfig.KeyUtil -import cfig.helper.Helper +import cfig.helper.KeyHelper2 import com.google.common.math.BigIntegerMath import org.apache.commons.codec.binary.Hex -import org.apache.commons.compress.compressors.lz4.FramedLZ4CompressorInputStream import org.bouncycastle.jce.provider.BouncyCastleProvider import org.junit.Assert.assertEquals import org.junit.Test import org.slf4j.LoggerFactory -import java.io.* import java.math.BigInteger import java.math.RoundingMode import java.nio.file.Files @@ -31,7 +29,8 @@ class HelperTest { val data = Hex.decodeHex("0001ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff003031300d0609608648016503040201050004206317a4c8d86accc8258c1ac23ef0ebd18bc33010d7afb43b241802646360b4ab") val expectedSig = "28e17bc57406650ed78785fd558e7c1861cc4014c900d72b61c03cdbab1039e713b5bb19b556d04d276b46aae9b8a3999ccbac533a1cce00f83cfb83e2beb35ed7329f71ffec04fc2839a9b44e50abd66ea6c3d3bea6705e93e9139ecd0331170db18eba36a85a78bc49a5447260a30ed19d956cb2f8a71f6b19e57fdca43e052d1bb7840bf4c3efb47111f4d77764236d2e013fbf3b2577e4a3e01c9d166a5e890ef96210882e6e88ceca2fe3a2201f4961210d4ec6167f5dfd0e038e4a146f960caecab7d15ba65f6edcf5dbd25f5af543cfb8da4338bdbc872eec3f8e72aa8db679099e70952d3f7176c0b9111bf20ad1390eab1d09a859105816fdf92fbb" val privkFile = "../" + Algorithms.get("SHA256_RSA2048")!!.defaultKey.replace("pem", "pk8") - val encData = Helper.rawSign(privkFile, data) + val k = KeyHelper2.parseRsaPk8(Files.readAllBytes(Paths.get(privkFile))) + val encData = KeyHelper2.rawSign(k, data) assertEquals(expectedSig, Hex.encodeHexString(encData)) } @@ -39,8 +38,7 @@ class HelperTest { fun rawSignOpenSslTest() { val data = Hex.decodeHex("0001ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff003031300d0609608648016503040201050004206317a4c8d86accc8258c1ac23ef0ebd18bc33010d7afb43b241802646360b4ab") val expectedSig = "28e17bc57406650ed78785fd558e7c1861cc4014c900d72b61c03cdbab1039e713b5bb19b556d04d276b46aae9b8a3999ccbac533a1cce00f83cfb83e2beb35ed7329f71ffec04fc2839a9b44e50abd66ea6c3d3bea6705e93e9139ecd0331170db18eba36a85a78bc49a5447260a30ed19d956cb2f8a71f6b19e57fdca43e052d1bb7840bf4c3efb47111f4d77764236d2e013fbf3b2577e4a3e01c9d166a5e890ef96210882e6e88ceca2fe3a2201f4961210d4ec6167f5dfd0e038e4a146f960caecab7d15ba65f6edcf5dbd25f5af543cfb8da4338bdbc872eec3f8e72aa8db679099e70952d3f7176c0b9111bf20ad1390eab1d09a859105816fdf92fbb" - - val sig = Helper.rawSignOpenSsl("../" + Algorithms.get("SHA256_RSA2048")!!.defaultKey, data) + val sig = KeyHelper2.rawSignOpenSsl("../" + Algorithms.get("SHA256_RSA2048")!!.defaultKey, data) assertEquals(expectedSig, Hex.encodeHexString(sig)) } diff --git a/src/integrationTest/resources b/src/integrationTest/resources index 23b6360..d95de4b 160000 --- a/src/integrationTest/resources +++ b/src/integrationTest/resources @@ -1 +1 @@ -Subproject commit 23b636091aa768f141266dafa14f89fb4417eb5a +Subproject commit d95de4b8d5d5fa7d36f3696e14be23d22fcdf7f5 diff --git a/tools/free.py b/tools/free.py index fcd1aba..198f0bf 100755 --- a/tools/free.py +++ b/tools/free.py @@ -4,19 +4,14 @@ # import subprocess -""" - -""" - def run(cmd): print(cmd) subprocess.check_call(cmd, shell = True) - run("touch vbmeta.img") run("gradle pull") run("gradle unpack") -run('vim build/unzip_boot/vbmeta.avb.json -c ":19s/0/2/g" -c ":wq"') +run('vim -u NONE -N build/unzip_boot/vbmeta.avb.json -c ":19s/0/2/g" -c ":wq"') run("gradle pack") run("gradle flash")