From 5cef10203becafda16a9f71d9ced4ebf48b1cd4b Mon Sep 17 00:00:00 2001 From: cfig Date: Mon, 29 Nov 2021 13:33:50 +0800 Subject: [PATCH] Issue #79: add "gradle clean" command --- .github/workflows/main.yml | 6 +- bbootimg/build.gradle.kts | 14 +- bbootimg/src/main/kotlin/avb/Avb.kt | 7 +- bbootimg/src/main/kotlin/avb/blob/AuthBlob.kt | 7 +- bbootimg/src/main/kotlin/avb/blob/AuxBlob.kt | 7 +- .../kotlin/avb/desc/HashTreeDescriptor.kt | 10 +- .../main/kotlin/bootimg/v2/BootV2Dialects.kt | 549 ++++++++++++++++++ .../src/main/kotlin/packable/BootImgParser.kt | 50 +- .../src/main/kotlin/packable/DtboParser.kt | 9 + .../src/main/kotlin/packable/VBMetaParser.kt | 8 + .../main/kotlin/packable/VendorBootParser.kt | 9 + bbootimg/src/test/kotlin/KeyUtilTest.kt | 25 +- bbootimg/src/test/kotlin/avb/BlobTest.kt | 7 +- .../kotlin/avb/desc/HashTreeDescriptorTest.kt | 6 +- build.gradle.kts | 10 + doc/Pixel6_vbmeta.puml | 41 ++ doc/system_core_libs.puml | 38 ++ gradle/wrapper/gradle-wrapper.properties | 2 +- helper/build.gradle.kts | 35 +- .../main/kotlin/cfig/helper/CryptoHelper.kt | 285 +++++++++ .../src/main/kotlin/cfig/helper/KeyHelper.kt | 161 ----- .../src/main/kotlin/cfig/helper/KeyHelper2.kt | 106 ---- .../src/main/kotlin/cfig/helper/Launcher.kt | 201 +++++++ .../main/kotlin/cfig/helper/OpenSslHelper.kt | 117 ++-- helper/src/main/resources/general.cfg | 0 .../kotlin/cfig/helper/OpenSslHelperTest.kt | 9 +- integrationTest.py | 4 + 27 files changed, 1331 insertions(+), 392 deletions(-) create mode 100644 bbootimg/src/main/kotlin/bootimg/v2/BootV2Dialects.kt create mode 100644 doc/Pixel6_vbmeta.puml create mode 100644 doc/system_core_libs.puml create mode 100644 helper/src/main/kotlin/cfig/helper/CryptoHelper.kt delete mode 100644 helper/src/main/kotlin/cfig/helper/KeyHelper.kt delete mode 100644 helper/src/main/kotlin/cfig/helper/KeyHelper2.kt create mode 100644 helper/src/main/kotlin/cfig/helper/Launcher.kt create mode 100644 helper/src/main/resources/general.cfg diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index ef38ce7..9a47f53 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -35,7 +35,7 @@ jobs: # Runs a single command using the runners shell - name: Unit Test - run: ./gradlew check && ./gradlew clean + run: ./gradlew check && ./gradlew clean || true # Runs a set of commands using the runners shell - name: Integration Test @@ -59,7 +59,7 @@ jobs: run: brew install dtc - name: Unit Test - run: ./gradlew check && ./gradlew clean + run: ./gradlew check && ./gradlew clean || true # Runs a set of commands using the runners shell - name: Integration Test @@ -90,7 +90,7 @@ jobs: run: choco install openssl dtc-msys2 - name: Unit Test - run: ./gradlew.bat check && ./gradlew.bat clean + run: ./gradlew.bat check && ./gradlew.bat clean || true # Runs a set of commands using the runners shell - name: Integration Test diff --git a/bbootimg/build.gradle.kts b/bbootimg/build.gradle.kts index b4d5712..4c35b53 100644 --- a/bbootimg/build.gradle.kts +++ b/bbootimg/build.gradle.kts @@ -15,7 +15,7 @@ import org.jetbrains.kotlin.gradle.tasks.KotlinCompile plugins { - kotlin("jvm") version "1.5.31" + kotlin("jvm") version "1.6.0" application } @@ -33,16 +33,16 @@ dependencies { implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8") implementation("org.jetbrains.kotlin:kotlin-reflect") - implementation("org.slf4j:slf4j-simple:1.7.31") - implementation("org.slf4j:slf4j-api:1.7.31") - implementation("com.fasterxml.jackson.core:jackson-annotations:2.12.3") - implementation("com.fasterxml.jackson.core:jackson-databind:2.12.3") + implementation("org.slf4j:slf4j-simple:1.7.32") + implementation("org.slf4j:slf4j-api:1.7.32") + implementation("com.fasterxml.jackson.core:jackson-annotations:2.13.0") + implementation("com.fasterxml.jackson.core:jackson-databind:2.13.0") implementation("com.google.guava:guava:18.0") implementation("org.apache.commons:commons-exec:1.3") - implementation("org.apache.commons:commons-compress:1.20") + implementation("org.apache.commons:commons-compress:1.21") implementation("org.tukaani:xz:1.9") implementation("commons-codec:commons-codec:1.15") - implementation("junit:junit:4.12") + implementation("junit:junit:4.13.2") implementation("org.bouncycastle:bcprov-jdk15on:1.69") implementation("de.vandermeer:asciitable:0.3.2") implementation(project(":helper")) diff --git a/bbootimg/src/main/kotlin/avb/Avb.kt b/bbootimg/src/main/kotlin/avb/Avb.kt index e4c0632..df34c5b 100644 --- a/bbootimg/src/main/kotlin/avb/Avb.kt +++ b/bbootimg/src/main/kotlin/avb/Avb.kt @@ -21,10 +21,9 @@ import avb.blob.AuxBlob import avb.blob.Footer import avb.blob.Header import avb.desc.* +import cfig.helper.CryptoHelper import cfig.helper.Helper import cfig.helper.Helper.Companion.paddingWith -import cfig.helper.KeyHelper -import cfig.helper.KeyHelper2 import com.fasterxml.jackson.databind.ObjectMapper import org.apache.commons.codec.binary.Hex import org.apache.commons.exec.CommandLine @@ -195,8 +194,8 @@ class Avb { val readHash = Helper.join(declaredAlg.padding, Helper.fromHexString(ai.authBlob!!.hash!!)) if (calcHash.contentEquals(readHash)) { log.info("VERIFY($localParent->AuthBlob): verify hash... PASS") - val readPubKey = KeyHelper.decodeRSAkey(ai.auxBlob!!.pubkey!!.pubkey) - val hashFromSig = KeyHelper2.rawRsa(readPubKey, Helper.fromHexString(ai.authBlob!!.signature!!)) + val readPubKey = CryptoHelper.KeyBox.decodeRSAkey(ai.auxBlob!!.pubkey!!.pubkey) + val hashFromSig = CryptoHelper.Signer.rawRsa(readPubKey, Helper.fromHexString(ai.authBlob!!.signature!!)) if (hashFromSig.contentEquals(readHash)) { log.info("VERIFY($localParent->AuthBlob): verify signature... PASS") } else { diff --git a/bbootimg/src/main/kotlin/avb/blob/AuthBlob.kt b/bbootimg/src/main/kotlin/avb/blob/AuthBlob.kt index 84f3908..d72dbee 100644 --- a/bbootimg/src/main/kotlin/avb/blob/AuthBlob.kt +++ b/bbootimg/src/main/kotlin/avb/blob/AuthBlob.kt @@ -15,9 +15,8 @@ package avb.blob import avb.alg.Algorithms +import cfig.helper.CryptoHelper import cfig.helper.Helper -import cfig.helper.KeyHelper -import cfig.helper.KeyHelper2 import cfig.io.Struct3 import org.slf4j.LoggerFactory import java.nio.file.Files @@ -56,8 +55,8 @@ data class AuthBlob( return if (alg.name == "NONE") { byteArrayOf() } else { - val k = KeyHelper.parse(Files.readAllBytes(Paths.get(alg.defaultKey.replace(".pem", ".pk8")))) as PrivateKey - KeyHelper2.rawRsa(k, Helper.join(alg.padding, hash)) + val k = CryptoHelper.KeyBox.parse(Files.readAllBytes(Paths.get(alg.defaultKey.replace(".pem", ".pk8")))) as PrivateKey + CryptoHelper.Signer.rawRsa(k, Helper.join(alg.padding, hash)) } } diff --git a/bbootimg/src/main/kotlin/avb/blob/AuxBlob.kt b/bbootimg/src/main/kotlin/avb/blob/AuxBlob.kt index e61d3e3..a0c151f 100644 --- a/bbootimg/src/main/kotlin/avb/blob/AuxBlob.kt +++ b/bbootimg/src/main/kotlin/avb/blob/AuxBlob.kt @@ -14,11 +14,10 @@ package avb.blob -import avb.AVBInfo import avb.alg.Algorithm import avb.desc.* +import cfig.helper.CryptoHelper import cfig.helper.Helper -import cfig.helper.KeyHelper import cfig.io.Struct3 import com.fasterxml.jackson.annotation.JsonIgnoreProperties import org.bouncycastle.asn1.pkcs.RSAPrivateKey @@ -140,8 +139,8 @@ class AuxBlob( if (key == null) { algKey = Files.readAllBytes((Paths.get(alg.defaultKey))) } - val rsa = KeyHelper.parse(algKey!!) as RSAPrivateKey //BC RSA - encodedKey = KeyHelper.encodeRSAkey(rsa) + val rsa = CryptoHelper.KeyBox.parse(algKey!!) as RSAPrivateKey //BC RSA + encodedKey = CryptoHelper.KeyBox.encodeRSAkey(rsa) assert(alg.public_key_num_bytes == encodedKey.size) } else { log.info("encodePubKey(): No key to encode for algorithm " + alg.name) diff --git a/bbootimg/src/main/kotlin/avb/desc/HashTreeDescriptor.kt b/bbootimg/src/main/kotlin/avb/desc/HashTreeDescriptor.kt index 2c10510..4977cd6 100644 --- a/bbootimg/src/main/kotlin/avb/desc/HashTreeDescriptor.kt +++ b/bbootimg/src/main/kotlin/avb/desc/HashTreeDescriptor.kt @@ -15,8 +15,8 @@ package avb.desc import avb.blob.Header +import cfig.helper.CryptoHelper import cfig.helper.Helper -import cfig.helper.KeyHelper2 import cfig.io.Struct3 import org.slf4j.LoggerFactory import java.io.* @@ -117,8 +117,8 @@ class HashTreeDescriptor( fis.skip(this.tree_offset) fis.read(readTree) } - val ourHtHash = KeyHelper2.sha256(File("hash.tree").readBytes()) - val diskHtHash = KeyHelper2.sha256(readTree) + val ourHtHash = CryptoHelper.Hasher.sha256(File("hash.tree").readBytes()) + val diskHtHash = CryptoHelper.Hasher.sha256(readTree) if (!ourHtHash.contentEquals(diskHtHash)) { return arrayOf(false, "MerkleTree corrupted") } else { @@ -136,7 +136,7 @@ class HashTreeDescriptor( } private fun calcSingleHashSize(padded: Boolean = false): Int { - val digSize = MessageDigest.getInstance(KeyHelper2.pyAlg2java(this.hash_algorithm)).digest().size + val digSize = MessageDigest.getInstance(CryptoHelper.Hasher.pyAlg2java(this.hash_algorithm)).digest().size val padSize = Helper.round_to_pow2(digSize.toLong()) - digSize return (digSize + (if (padded) padSize else 0)).toInt() } @@ -159,7 +159,7 @@ class HashTreeDescriptor( var totalRead = 0L while (true) { val data = ByteArray(blockSz) - MessageDigest.getInstance(KeyHelper2.pyAlg2java(this.hash_algorithm)).let { + MessageDigest.getInstance(CryptoHelper.Hasher.pyAlg2java(this.hash_algorithm)).let { val bytesRead = inputStream.read(data) if (bytesRead <= 0) { return@hashing diff --git a/bbootimg/src/main/kotlin/bootimg/v2/BootV2Dialects.kt b/bbootimg/src/main/kotlin/bootimg/v2/BootV2Dialects.kt new file mode 100644 index 0000000..96105f5 --- /dev/null +++ b/bbootimg/src/main/kotlin/bootimg/v2/BootV2Dialects.kt @@ -0,0 +1,549 @@ +// Copyright 2021 yuyezhong@gmail.com +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package cfig.bootimg.v2 + +import avb.AVBInfo +import cfig.Avb +import cfig.bootimg.Common +import cfig.bootimg.Common.Companion.deleleIfExists +import cfig.bootimg.Signer +import cfig.bootimg.v3.VendorBoot +import cfig.helper.Helper +import cfig.packable.VBMetaParser +import cfig.utils.EnvironmentVerifier +import com.fasterxml.jackson.databind.ObjectMapper +import de.vandermeer.asciitable.AsciiTable +import org.apache.commons.exec.CommandLine +import org.apache.commons.exec.DefaultExecutor +import org.slf4j.LoggerFactory +import java.io.File +import java.io.FileInputStream +import java.io.FileOutputStream +import java.nio.ByteBuffer +import java.nio.ByteOrder + +data class BootV2Dialects( + var info: MiscInfo = MiscInfo(), + var kernel: CommArgs = CommArgs(), + var ramdisk: CommArgs = CommArgs(), + var secondBootloader: CommArgs? = null, + var recoveryDtbo: CommArgsLong? = null, + var dtb: CommArgsLong? = null, + var unknownLand: CommArgsLong? = null, +) { + data class MiscInfo( + var output: String = "", + var json: String = "", + var headerVersion: Int = 0, + var headerSize: Int = 0, + var loadBase: Long = 0, + var tagsOffset: Long = 0, + var board: String? = null, + var pageSize: Int = 0, + var cmdline: String = "", + var osVersion: String? = null, + var osPatchLevel: String? = null, + var hash: ByteArray? = byteArrayOf(), + var verify: String = "", + var imageSize: Long = 0, + ) + + data class CommArgs( + var file: String? = null, + var position: Long = 0, + var size: Int = 0, + var loadOffset: Long = 0, + ) + + data class CommArgsLong( + var file: String? = null, + var position: Long = 0, + var size: Int = 0, + var loadOffset: Long = 0, + ) + + companion object { + private val log = LoggerFactory.getLogger(BootV2Dialects::class.java) + private val workDir = Helper.prop("workDir") + + fun parse(fileName: String): BootV2Dialects { + val ret = BootV2Dialects() + FileInputStream(fileName).use { fis -> + val bh2 = BootHeaderV2(fis) + ret.info.let { theInfo -> + theInfo.output = File(fileName).name + theInfo.json = File(fileName).name.removeSuffix(".img") + ".json" + theInfo.pageSize = bh2.pageSize + theInfo.headerSize = bh2.headerSize + if (true) { + bh2.dtbLength = bh2.headerVersion + bh2.headerVersion = 0 + theInfo.headerVersion = 0 + log.warn("dtb len = " + bh2.dtbLength) + } else { + //theInfo.headerVersion = bh2.headerVersion + } + theInfo.board = bh2.board + theInfo.cmdline = bh2.cmdline + theInfo.imageSize = File(fileName).length() + theInfo.tagsOffset = bh2.tagsOffset + theInfo.hash = bh2.hash + theInfo.osVersion = bh2.osVersion + theInfo.osPatchLevel = bh2.osPatchLevel + if (Avb.hasAvbFooter(fileName)) { + theInfo.verify = "VB2.0" + if (Avb.verifyAVBIntegrity(fileName, String.format(Helper.prop("avbtool"), "v1.2"))) { + theInfo.verify += " PASS" + } else { + theInfo.verify += " FAIL" + } + } else { + theInfo.verify = "VB1.0" + } + } + ret.kernel.let { theKernel -> + theKernel.file = "${workDir}kernel" + theKernel.size = bh2.kernelLength + theKernel.loadOffset = bh2.kernelOffset + theKernel.position = ret.getKernelPosition() + } + ret.ramdisk.let { theRamdisk -> + theRamdisk.size = bh2.ramdiskLength + theRamdisk.loadOffset = bh2.ramdiskOffset + theRamdisk.position = ret.getRamdiskPosition() + if (bh2.ramdiskLength > 0) { + theRamdisk.file = "${workDir}ramdisk.img" + } + } + if (bh2.secondBootloaderLength > 0) { + ret.secondBootloader = CommArgs() + ret.secondBootloader!!.size = bh2.secondBootloaderLength + ret.secondBootloader!!.loadOffset = bh2.secondBootloaderOffset + ret.secondBootloader!!.file = "${workDir}second" + ret.secondBootloader!!.position = ret.getSecondBootloaderPosition() + } + if (bh2.recoveryDtboLength > 0) { + ret.recoveryDtbo = CommArgsLong() + ret.recoveryDtbo!!.size = bh2.recoveryDtboLength + ret.recoveryDtbo!!.loadOffset = bh2.recoveryDtboOffset //Q + ret.recoveryDtbo!!.file = "${workDir}recoveryDtbo" + ret.recoveryDtbo!!.position = ret.getRecoveryDtboPosition() + } + if (bh2.dtbLength > 0) { + ret.dtb = CommArgsLong() + ret.dtb!!.size = bh2.dtbLength + ret.dtb!!.loadOffset = bh2.dtbOffset //Q + ret.dtb!!.file = "${workDir}dtb" + ret.dtb!!.position = ret.getDtbPosition() + } + } + log.warn("Land Unknown: " + ret.getUnknownLandPosision()) + return ret + } + } + + private fun getHeaderSize(pageSize: Int): Int { + val pad = (pageSize - (1648 and (pageSize - 1))) and (pageSize - 1) + return pad + 1648 + } + + private fun getKernelPosition(): Long { + return getHeaderSize(info.pageSize).toLong() + } + + private fun getRamdiskPosition(): Long { + return (getKernelPosition() + kernel.size + + Common.getPaddingSize(kernel.size, info.pageSize)) + } + + private fun getSecondBootloaderPosition(): Long { + return getRamdiskPosition() + ramdisk.size + + Common.getPaddingSize(ramdisk.size, info.pageSize) + } + + private fun getRecoveryDtboPosition(): Long { + return if (this.secondBootloader == null) { + getSecondBootloaderPosition() + } else { + getSecondBootloaderPosition() + getSecondBootloaderPosition() + secondBootloader!!.size + + Common.getPaddingSize(secondBootloader!!.size, info.pageSize) + } + } + + private fun getDtbPosition(): Long { + return if (this.recoveryDtbo == null) { + getRecoveryDtboPosition() + } else { + getRecoveryDtboPosition() + recoveryDtbo!!.size + + Common.getPaddingSize(recoveryDtbo!!.size, info.pageSize) + } + } + + private fun getUnknownLandPosision(): Long { + return if (this.dtb == null) { + getDtbPosition() + } else { + getDtbPosition() + dtb!!.size + + Common.getPaddingSize(dtb!!.size, info.pageSize) + } + } + + fun extractImages(): BootV2Dialects { + val workDir = Helper.prop("workDir") + //info + ObjectMapper().writerWithDefaultPrettyPrinter().writeValue(File(workDir + info.json), this) + //kernel + Common.dumpKernel(Helper.Slice(info.output, kernel.position.toInt(), kernel.size, kernel.file!!)) + //ramdisk + if (this.ramdisk.size > 0) { + val fmt = Common.dumpRamdisk( + Helper.Slice(info.output, ramdisk.position.toInt(), ramdisk.size, ramdisk.file!!), "${workDir}root" + ) + this.ramdisk.file = this.ramdisk.file!! + ".$fmt" + //dump info again + ObjectMapper().writerWithDefaultPrettyPrinter().writeValue(File(workDir + this.info.json), this) + } + //second bootloader + secondBootloader?.let { + Helper.extractFile( + info.output, + secondBootloader!!.file!!, + secondBootloader!!.position, + secondBootloader!!.size + ) + } + //recovery dtbo + recoveryDtbo?.let { + Helper.extractFile( + info.output, + recoveryDtbo!!.file!!, + recoveryDtbo!!.position, + recoveryDtbo!!.size + ) + } + //dtb + this.dtb?.let { _ -> + Common.dumpDtb(Helper.Slice(info.output, dtb!!.position.toInt(), dtb!!.size, dtb!!.file!!)) + } + + return this + } + + fun extractVBMeta(): BootV2Dialects { + if (this.info.verify.startsWith("VB2.0")) { + AVBInfo.parseFrom(info.output).dumpDefault(info.output) + if (File("vbmeta.img").exists()) { + log.warn("Found vbmeta.img, parsing ...") + VBMetaParser().unpack("vbmeta.img") + } + } else { + log.info("verify type is ${this.info.verify}, skip AVB parsing") + } + return this + } + + fun printSummary(): BootV2Dialects { + val workDir = Helper.prop("workDir") + val tableHeader = AsciiTable().apply { + addRule() + addRow("What", "Where") + addRule() + } + val tab = AsciiTable().let { + it.addRule() + it.addRow("image info", workDir + info.output.removeSuffix(".img") + ".json") + if (this.info.verify.startsWith("VB2.0")) { + it.addRule() + val verifyStatus = if (this.info.verify.contains("PASS")) { + "verified" + } else { + "verify fail" + } + it.addRow("AVB info [$verifyStatus]", Avb.getJsonFileName(info.output)) + } + //kernel + it.addRule() + it.addRow("kernel", this.kernel.file) + File(Helper.prop("kernelVersionFile")).let { kernelVersionFile -> + if (kernelVersionFile.exists()) { + it.addRow("\\-- version " + kernelVersionFile.readLines().toString(), kernelVersionFile.path) + } + } + File(Helper.prop("kernelConfigFile")).let { kernelConfigFile -> + if (kernelConfigFile.exists()) { + it.addRow("\\-- config", kernelConfigFile.path) + } + } + //ramdisk + if (this.ramdisk.size > 0) { + it.addRule() + it.addRow("ramdisk", this.ramdisk.file) + it.addRow("\\-- extracted ramdisk rootfs", "${workDir}root") + } + //second + this.secondBootloader?.let { theSecondBootloader -> + if (theSecondBootloader.size > 0) { + it.addRule() + it.addRow("second bootloader", theSecondBootloader.file) + } + } + //dtbo + this.recoveryDtbo?.let { theDtbo -> + if (theDtbo.size > 0) { + it.addRule() + it.addRow("recovery dtbo", theDtbo.file) + } + } + //dtb + this.dtb?.let { theDtb -> + if (theDtb.size > 0) { + it.addRule() + it.addRow("dtb", theDtb.file) + if (File(theDtb.file + ".src").exists()) { + it.addRow("\\-- decompiled dts", theDtb.file + ".src") + } + } + } + //END + it.addRule() + it + } + val tabVBMeta = AsciiTable().let { + if (File("vbmeta.img").exists()) { + it.addRule() + it.addRow("vbmeta.img", Avb.getJsonFileName("vbmeta.img")) + it.addRule() + "\n" + it.render() + } else { + "" + } + } + log.info( + "\n\t\t\tUnpack Summary of ${info.output}\n{}\n{}{}", + tableHeader.render(), tab.render(), tabVBMeta + ) + return this + } + + private fun toHeader(): BootHeaderV2 { + return BootHeaderV2( + kernelLength = kernel.size, + kernelOffset = kernel.loadOffset, + ramdiskLength = ramdisk.size, + ramdiskOffset = ramdisk.loadOffset, + secondBootloaderLength = if (secondBootloader != null) secondBootloader!!.size else 0, + secondBootloaderOffset = if (secondBootloader != null) secondBootloader!!.loadOffset else 0, + recoveryDtboLength = if (recoveryDtbo != null) recoveryDtbo!!.size else 0, + recoveryDtboOffset = if (recoveryDtbo != null) recoveryDtbo!!.loadOffset else 0, + dtbLength = if (dtb != null) dtb!!.size else 0, + dtbOffset = if (dtb != null) dtb!!.loadOffset else 0, + tagsOffset = info.tagsOffset, + pageSize = info.pageSize, + headerSize = info.headerSize, + headerVersion = info.headerVersion, + board = info.board.toString(), + cmdline = info.cmdline, + hash = info.hash, + osVersion = info.osVersion, + osPatchLevel = info.osPatchLevel + ) + } + + fun pack(): BootV2Dialects { + //refresh kernel size + this.kernel.size = File(this.kernel.file!!).length().toInt() + //refresh ramdisk size + if (this.ramdisk.file.isNullOrBlank()) { + ramdisk.file = null + ramdisk.loadOffset = 0 + } else { + if (File(this.ramdisk.file!!).exists() && !File(workDir + "root").exists()) { + //do nothing if we have ramdisk.img.gz but no /root + log.warn("Use prebuilt ramdisk file: ${this.ramdisk.file}") + } else { + File(this.ramdisk.file!!).deleleIfExists() + File(this.ramdisk.file!!.removeSuffix(".gz")).deleleIfExists() + //Common.packRootfs("${workDir}/root", this.ramdisk.file!!, Common.parseOsMajor(info.osVersion.toString())) + Common.packRootfs("${workDir}/root", this.ramdisk.file!!) + } + this.ramdisk.size = File(this.ramdisk.file!!).length().toInt() + } + //refresh second bootloader size + secondBootloader?.let { theSecond -> + theSecond.size = File(theSecond.file!!).length().toInt() + } + //refresh recovery dtbo size + recoveryDtbo?.let { theDtbo -> + theDtbo.size = File(theDtbo.file!!).length().toInt() + theDtbo.loadOffset = getRecoveryDtboPosition() + log.warn("using fake recoveryDtboOffset ${theDtbo.loadOffset} (as is in AOSP avbtool)") + } + //refresh dtb size + dtb?.let { theDtb -> + theDtb.size = File(theDtb.file!!).length().toInt() + } + //refresh image hash + info.hash = when (info.headerVersion) { + 0 -> { + Common.hashFileAndSize(kernel.file, ramdisk.file, secondBootloader?.file) + } + 1 -> { + Common.hashFileAndSize( + kernel.file, ramdisk.file, + secondBootloader?.file, recoveryDtbo?.file + ) + } + 2 -> { + Common.hashFileAndSize( + kernel.file, ramdisk.file, + secondBootloader?.file, recoveryDtbo?.file, dtb?.file + ) + } + else -> { + throw IllegalArgumentException("headerVersion ${info.headerVersion} illegal") + } + } + + val encodedHeader = this.toHeader().encode() + + //write + FileOutputStream("${info.output}.clear", false).use { fos -> + fos.write(encodedHeader) + fos.write(ByteArray((Helper.round_to_multiple(encodedHeader.size, info.pageSize) - encodedHeader.size))) + } + + log.info("Writing data ...") + //boot image size may > 64MB. Fix issue #57 + val bytesV2 = ByteBuffer.allocate(maxOf(1024 * 1024 * 64, info.imageSize.toInt())) + .let { bf -> + bf.order(ByteOrder.LITTLE_ENDIAN) + Common.writePaddedFile(bf, kernel.file!!, info.pageSize) + if (ramdisk.size > 0) { + Common.writePaddedFile(bf, ramdisk.file!!, info.pageSize) + } + secondBootloader?.let { + Common.writePaddedFile(bf, secondBootloader!!.file!!, info.pageSize) + } + recoveryDtbo?.let { + Common.writePaddedFile(bf, recoveryDtbo!!.file!!, info.pageSize) + } + dtb?.let { + Common.writePaddedFile(bf, dtb!!.file!!, info.pageSize) + } + bf + } + //write + FileOutputStream("${info.output}.clear", true).use { fos -> + fos.write(bytesV2.array(), 0, bytesV2.position()) + } + + this.toCommandLine().apply { + addArgument("${info.output}.google") + log.info(this.toString()) + DefaultExecutor().execute(this) + } + + Common.assertFileEquals("${info.output}.clear", "${info.output}.google") + + return this + } + + private fun toCommandLine(): CommandLine { + val cmdPrefix = if (EnvironmentVerifier().isWindows) "python " else "" + val ret = CommandLine.parse(cmdPrefix + Helper.prop("mkbootimg")) + ret.addArgument(" --header_version ") + ret.addArgument(info.headerVersion.toString()) + ret.addArgument(" --base ") + ret.addArgument("0x" + java.lang.Long.toHexString(0)) + ret.addArgument(" --kernel ") + ret.addArgument(kernel.file!!) + ret.addArgument(" --kernel_offset ") + ret.addArgument("0x" + Integer.toHexString(kernel.loadOffset.toInt())) + if (this.ramdisk.size > 0) { + ret.addArgument(" --ramdisk ") + ret.addArgument(ramdisk.file) + } + ret.addArgument(" --ramdisk_offset ") + ret.addArgument("0x" + Integer.toHexString(ramdisk.loadOffset.toInt())) + if (secondBootloader != null) { + ret.addArgument(" --second ") + ret.addArgument(secondBootloader!!.file!!) + ret.addArgument(" --second_offset ") + ret.addArgument("0x" + Integer.toHexString(secondBootloader!!.loadOffset.toInt())) + } + if (!info.board.isNullOrBlank()) { + ret.addArgument(" --board ") + ret.addArgument(info.board) + } + if (info.headerVersion > 0) { + if (recoveryDtbo != null) { + ret.addArgument(" --recovery_dtbo ") + ret.addArgument(recoveryDtbo!!.file!!) + } + } + if (info.headerVersion > 1) { + if (dtb != null) { + ret.addArgument("--dtb ") + ret.addArgument(dtb!!.file!!) + ret.addArgument("--dtb_offset ") + ret.addArgument("0x" + java.lang.Long.toHexString(dtb!!.loadOffset)) + } + } + ret.addArgument(" --pagesize ") + ret.addArgument(info.pageSize.toString()) + ret.addArgument(" --cmdline ") + ret.addArgument(info.cmdline, false) + if (!info.osVersion.isNullOrBlank()) { + ret.addArgument(" --os_version ") + ret.addArgument(info.osVersion) + } + if (!info.osPatchLevel.isNullOrBlank()) { + ret.addArgument(" --os_patch_level ") + ret.addArgument(info.osPatchLevel) + } + ret.addArgument(" --tags_offset ") + ret.addArgument("0x" + Integer.toHexString(info.tagsOffset.toInt())) + ret.addArgument(" --id ") + ret.addArgument(" --output ") + //ret.addArgument("boot.img" + ".google") + + log.debug("To Commandline: $ret") + + return ret + } + + fun sign(): BootV2Dialects { + //unify with v1.1/v1.2 avbtool + val avbtool = String.format(Helper.prop("avbtool"), "v1.2") + if (info.verify.startsWith("VB2.0")) { + Signer.signAVB(info.output, this.info.imageSize, avbtool) + log.info("Adding hash_footer with verified-boot 2.0 style") + } else { + Signer.signVB1(info.output + ".clear", info.output + ".signed") + } + return this + } + + fun printPackSummary(): BootV2Dialects { + VendorBoot.printPackSummary(info.output) + return this + } + + fun updateVbmeta(): BootV2Dialects { + Avb.updateVbmeta(info.output) + return this + } +} diff --git a/bbootimg/src/main/kotlin/packable/BootImgParser.kt b/bbootimg/src/main/kotlin/packable/BootImgParser.kt index e91e804..499697e 100644 --- a/bbootimg/src/main/kotlin/packable/BootImgParser.kt +++ b/bbootimg/src/main/kotlin/packable/BootImgParser.kt @@ -15,10 +15,11 @@ package cfig.packable import avb.blob.Footer -import cfig.Avb import cfig.bootimg.Common.Companion.probeHeaderVersion import cfig.bootimg.v2.BootV2 +import cfig.bootimg.v2.BootV2Dialects import cfig.bootimg.v3.BootV3 +import cfig.helper.Helper.Companion.deleteIfExists import com.fasterxml.jackson.databind.ObjectMapper import de.vandermeer.asciitable.AsciiTable import org.slf4j.LoggerFactory @@ -37,20 +38,31 @@ class BootImgParser : IPackable { cleanUp() val hv = probeHeaderVersion(fileName) log.info("header version $hv") - if (hv in 0..2) { - val b2 = BootV2 - .parse(fileName) - .extractImages() - .extractVBMeta() - .printSummary() - log.debug(b2.toString()) - } else { - val b3 = BootV3 - .parse(fileName) - .extractImages() - .extractVBMeta() - .printSummary() - log.debug(b3.toString()) + when (hv) { + in 0..2 -> { + val b2 = BootV2 + .parse(fileName) + .extractImages() + .extractVBMeta() + .printSummary() + log.debug(b2.toString()) + } + in 3..4 -> { + val b3 = BootV3 + .parse(fileName) + .extractImages() + .extractVBMeta() + .printSummary() + log.debug(b3.toString()) + } + else -> { + val b2 = BootV2Dialects + .parse(fileName) + .extractImages() + .extractVBMeta() + .printSummary() + log.debug(b2.toString()) + } } } @@ -114,6 +126,14 @@ class BootImgParser : IPackable { super.pull(fileName, deviceName) } + fun clean(fileName: String) { + super.cleanUp() + listOf("", ".clear", ".google", ".clear", ".signed", ".signed2").forEach { + "$fileName$it".deleteIfExists() + } + VBMetaParser().clean("vbmeta.img") + } + companion object { private val log = LoggerFactory.getLogger(BootImgParser::class.java) } diff --git a/bbootimg/src/main/kotlin/packable/DtboParser.kt b/bbootimg/src/main/kotlin/packable/DtboParser.kt index de1a287..32cc954 100644 --- a/bbootimg/src/main/kotlin/packable/DtboParser.kt +++ b/bbootimg/src/main/kotlin/packable/DtboParser.kt @@ -15,6 +15,7 @@ package cfig.packable import cfig.helper.Helper +import cfig.helper.Helper.Companion.deleteIfExists import cfig.utils.DTC import cfig.utils.EnvironmentVerifier import com.fasterxml.jackson.databind.ObjectMapper @@ -60,6 +61,14 @@ class DtboParser(val workDir: File) : IPackable { super.`@verify`(fileName) } + fun clean(fileName: String) { + super.cleanUp() + listOf("", ".clear", ".google", ".clear", ".signed", ".signed2").forEach { + "$fileName$it".deleteIfExists() + } + VBMetaParser().clean("vbmeta.img") + } + private fun execInDirectory(cmd: CommandLine, inWorkDir: File) { DefaultExecutor().let { it.workingDirectory = inWorkDir diff --git a/bbootimg/src/main/kotlin/packable/VBMetaParser.kt b/bbootimg/src/main/kotlin/packable/VBMetaParser.kt index bba2aca..5c08e7c 100644 --- a/bbootimg/src/main/kotlin/packable/VBMetaParser.kt +++ b/bbootimg/src/main/kotlin/packable/VBMetaParser.kt @@ -16,6 +16,7 @@ package cfig.packable import avb.AVBInfo import cfig.Avb +import cfig.helper.Helper.Companion.deleteIfExists import com.fasterxml.jackson.databind.ObjectMapper import org.slf4j.LoggerFactory import java.io.File @@ -59,5 +60,12 @@ class VBMetaParser: IPackable { super.pull(fileName, deviceName) } + fun clean(fileName: String) { + super.cleanUp() + listOf("", ".signed").forEach { + "$fileName$it".deleteIfExists() + } + } + private val log = LoggerFactory.getLogger(VBMetaParser::class.java) } diff --git a/bbootimg/src/main/kotlin/packable/VendorBootParser.kt b/bbootimg/src/main/kotlin/packable/VendorBootParser.kt index ad1ddd4..94ea8fc 100644 --- a/bbootimg/src/main/kotlin/packable/VendorBootParser.kt +++ b/bbootimg/src/main/kotlin/packable/VendorBootParser.kt @@ -15,6 +15,7 @@ package cfig.packable import cfig.bootimg.v3.VendorBoot +import cfig.helper.Helper.Companion.deleteIfExists import com.fasterxml.jackson.databind.ObjectMapper import org.slf4j.LoggerFactory import java.io.File @@ -54,6 +55,14 @@ class VendorBootParser : IPackable { super.pull(fileName, deviceName) } + fun clean(fileName: String) { + super.cleanUp() + listOf("", ".clear", ".google", ".clear", ".signed", ".signed2").forEach { + "$fileName$it".deleteIfExists() + } + VBMetaParser().clean("vbmeta.img") + } + override fun flash(fileName: String, deviceName: String) { val stem = fileName.substring(0, fileName.indexOf(".")) super.flash("$fileName.signed", stem) diff --git a/bbootimg/src/test/kotlin/KeyUtilTest.kt b/bbootimg/src/test/kotlin/KeyUtilTest.kt index 817b436..0eaa800 100644 --- a/bbootimg/src/test/kotlin/KeyUtilTest.kt +++ b/bbootimg/src/test/kotlin/KeyUtilTest.kt @@ -13,9 +13,8 @@ // limitations under the License. import avb.alg.Algorithms +import cfig.helper.CryptoHelper import cfig.helper.Helper -import cfig.helper.KeyHelper -import cfig.helper.KeyHelper2 import com.google.common.math.BigIntegerMath import org.bouncycastle.jce.provider.BouncyCastleProvider import org.junit.Assert.assertEquals @@ -36,14 +35,14 @@ class KeyUtilTest { fun parseKeys() { val keyFile = "../" + Algorithms.get("SHA256_RSA2048")!!.defaultKey println("Key: $keyFile") - val k = KeyHelper.parse(File(keyFile.replace("pem", "pk8")).readBytes()) as RSAPrivateKey + val k = CryptoHelper.KeyBox.parse(File(keyFile.replace("pem", "pk8")).readBytes()) as RSAPrivateKey println(k.privateExponent) println(k.modulus) - val k2 = KeyHelper.parse(File(keyFile).readBytes()) as org.bouncycastle.asn1.pkcs.RSAPrivateKey + val k2 = CryptoHelper.KeyBox.parse(File(keyFile).readBytes()) as org.bouncycastle.asn1.pkcs.RSAPrivateKey println(k2.privateExponent) println(k2.modulus) //KeyHelper2.parseRsaPk8(FileInputStream(keyFile).readAllBytes()) - KeyHelper.parse(File(keyFile.replace("pem", "pk8")).readBytes()) + CryptoHelper.KeyBox.parse(File(keyFile.replace("pem", "pk8")).readBytes()) } @Test @@ -88,8 +87,8 @@ class KeyUtilTest { val expectedSig = "28e17bc57406650ed78785fd558e7c1861cc4014c900d72b61c03cdbab1039e713b5bb19b556d04d276b46aae9b8a3999ccbac533a1cce00f83cfb83e2beb35ed7329f71ffec04fc2839a9b44e50abd66ea6c3d3bea6705e93e9139ecd0331170db18eba36a85a78bc49a5447260a30ed19d956cb2f8a71f6b19e57fdca43e052d1bb7840bf4c3efb47111f4d77764236d2e013fbf3b2577e4a3e01c9d166a5e890ef96210882e6e88ceca2fe3a2201f4961210d4ec6167f5dfd0e038e4a146f960caecab7d15ba65f6edcf5dbd25f5af543cfb8da4338bdbc872eec3f8e72aa8db679099e70952d3f7176c0b9111bf20ad1390eab1d09a859105816fdf92fbb" val privkFile = "../" + Algorithms.get("SHA256_RSA2048")!!.defaultKey.replace("pem", "pk8") - val k = KeyHelper.parse(Files.readAllBytes(Paths.get(privkFile))) as PrivateKey - val encData = KeyHelper2.rawRsa(k, data) + val k = CryptoHelper.KeyBox.parse(Files.readAllBytes(Paths.get(privkFile))) as PrivateKey + val encData = CryptoHelper.Signer.rawRsa(k, data) assertEquals(expectedSig, Helper.toHexString(encData)) } @@ -99,7 +98,7 @@ class KeyUtilTest { Helper.fromHexString("0001ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff003031300d0609608648016503040201050004206317a4c8d86accc8258c1ac23ef0ebd18bc33010d7afb43b241802646360b4ab") val expectedSig = "28e17bc57406650ed78785fd558e7c1861cc4014c900d72b61c03cdbab1039e713b5bb19b556d04d276b46aae9b8a3999ccbac533a1cce00f83cfb83e2beb35ed7329f71ffec04fc2839a9b44e50abd66ea6c3d3bea6705e93e9139ecd0331170db18eba36a85a78bc49a5447260a30ed19d956cb2f8a71f6b19e57fdca43e052d1bb7840bf4c3efb47111f4d77764236d2e013fbf3b2577e4a3e01c9d166a5e890ef96210882e6e88ceca2fe3a2201f4961210d4ec6167f5dfd0e038e4a146f960caecab7d15ba65f6edcf5dbd25f5af543cfb8da4338bdbc872eec3f8e72aa8db679099e70952d3f7176c0b9111bf20ad1390eab1d09a859105816fdf92fbb" - val sig = KeyHelper2.rawSignOpenSsl("../" + Algorithms.get("SHA256_RSA2048")!!.defaultKey, data) + val sig = CryptoHelper.Signer.rawSignOpenSsl("../" + Algorithms.get("SHA256_RSA2048")!!.defaultKey, data) assertEquals(expectedSig, Helper.toHexString(sig)) } @@ -109,7 +108,7 @@ class KeyUtilTest { Helper.fromHexString("0001ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff003031300d0609608648016503040201050004206317a4c8d86accc8258c1ac23ef0ebd18bc3301033") val signature = Signature.getInstance("NONEwithRSA") val keyFile = "../" + Algorithms.get("SHA256_RSA2048")!!.defaultKey.replace("pem", "pk8") - val k = KeyHelper.parse(Files.readAllBytes(Paths.get(keyFile))) as PrivateKey + val k = CryptoHelper.KeyBox.parse(Files.readAllBytes(Paths.get(keyFile))) as PrivateKey signature.initSign(k) signature.update(data) println("data size " + data.size) @@ -188,18 +187,18 @@ class KeyUtilTest { @Test fun listAll() { - KeyHelper.listAll() + CryptoHelper.listAll() } @Test fun signData() { val data = KeyUtilTest::class.java.classLoader.getResourceAsStream("data").readAllBytes() println(Helper.toHexString(data)) - val privKey = KeyHelper.parse( + val privKey = CryptoHelper.KeyBox.parse( KeyUtilTest::class.java.classLoader.getResourceAsStream("testkey.pk8").readAllBytes() ) as PrivateKey - println("sha256=" + Helper.toHexString(KeyHelper2.sha256(data))) - val signedHash = KeyHelper2.sha256rsa(data, privKey) + println("sha256=" + Helper.toHexString(CryptoHelper.Hasher.sha256(data))) + val signedHash = CryptoHelper.Signer.sha256rsa(data, privKey) println("Signed Hash: " + Helper.toHexString(signedHash)) } } diff --git a/bbootimg/src/test/kotlin/avb/BlobTest.kt b/bbootimg/src/test/kotlin/avb/BlobTest.kt index ae5f85b..902b3f2 100644 --- a/bbootimg/src/test/kotlin/avb/BlobTest.kt +++ b/bbootimg/src/test/kotlin/avb/BlobTest.kt @@ -16,13 +16,12 @@ package avb import avb.alg.Algorithms import avb.blob.AuxBlob +import cfig.helper.CryptoHelper import cfig.helper.Helper -import cfig.helper.KeyHelper import org.apache.commons.codec.binary.Hex import org.bouncycastle.asn1.pkcs.RSAPrivateKey import org.junit.Assert.assertEquals import org.junit.Test -import java.io.ByteArrayInputStream class BlobTest { @Test @@ -34,9 +33,9 @@ class BlobTest { "00000800c9d87d7bc65551dd3224a2e00ebc7efdbda2538058697ef54a4087959054593d55caff36341afae1e0902a1a32685bf3dfad0bf9b1d0f7eaab471f76be1b984b67a362fadfe6b5f8ee73165fb8b182de4989d53dd7a842998175c8d8847bbd54a8226444bc3406103c89c2d1f32c036591b1a0d1c82156159948202774ef017a76a50b6bfde3faed0df90f7a41fa76053749fe344f4b0149e498f7898ecd36aa391da97d5d6b5a52d17569a8df7cde1c1bf9d9195bb7474cb9702eade5d6887ced926e460810b576033e09ac4db62ccd1200bdd4a703d31b910823365b11feaf5969b33c8824372d61bac599511897f92342969f872ecdb24d5fa924f245dae26526264d1efa3b53abc5d379532f66b82194669a936f3526438c8f96b9ffaccdf4006ebeb673548450854653d5dd43feb26a784079569f86f3d3813b3d409035329a517ff8c34bc7d6a1ca30fb1bfd270ab8644134c117dea1769aebcf0c50d913f50d0b2c9924cbb5b4f8c60ad026b15bfd4d44669db076aa799dc05c3b9436c18ffec9d25a6aa046e1a28b2f516151a3369183b4fbcda9403446988a1a91dfd92c3abf573a464620f2f0bc315e29fe5890325c6697999a8e1523eba947d363c618bb8ed2209f78af71b32e0889a1f17db130a0e61bbd6ec8f633e1dab0bd1641fe076f6e978b6a33d2d780036a4de70582281fef6aa9757ee14ee2955b4fe6dc03b981" assertEquals(expectedKeyEnc, Helper.toHexString(encodedKey)) run {//decode pub key and check - val decodedKey = KeyHelper.decodeRSAkey(encodedKey) + val decodedKey = CryptoHelper.KeyBox.decodeRSAkey(encodedKey) //val rsa = KeyHelper.parsePemPrivateKeyBC(ByteArrayInputStream(Helper.fromHexString(keyStr))) //BC RSA - val rsa = KeyHelper.parse(Helper.fromHexString(keyStr)) as RSAPrivateKey //BC RSA + val rsa = CryptoHelper.KeyBox.parse(Helper.fromHexString(keyStr)) as RSAPrivateKey //BC RSA assert(rsa.modulus.equals(decodedKey.modulus)) assert(rsa.publicExponent.equals(decodedKey.publicExponent)) } diff --git a/bbootimg/src/test/kotlin/avb/desc/HashTreeDescriptorTest.kt b/bbootimg/src/test/kotlin/avb/desc/HashTreeDescriptorTest.kt index 8d2f521..7d74a2f 100644 --- a/bbootimg/src/test/kotlin/avb/desc/HashTreeDescriptorTest.kt +++ b/bbootimg/src/test/kotlin/avb/desc/HashTreeDescriptorTest.kt @@ -14,7 +14,7 @@ package avb.desc -import cfig.helper.KeyHelper2 +import cfig.helper.CryptoHelper import com.fasterxml.jackson.databind.ObjectMapper import org.apache.commons.codec.binary.Hex import org.junit.Assert.assertEquals @@ -46,7 +46,7 @@ class HashTreeDescriptorTest { @Test fun x1() { HashTreeDescriptor.calcMerkleTree(120721408, 4096, 32) - println(MessageDigest.getInstance(KeyHelper2.pyAlg2java("sha1")).digest().size) - println(MessageDigest.getInstance(KeyHelper2.pyAlg2java("sha256")).digest().size) + println(MessageDigest.getInstance(CryptoHelper.Hasher.pyAlg2java("sha1")).digest().size) + println(MessageDigest.getInstance(CryptoHelper.Hasher.pyAlg2java("sha256")).digest().size) } } diff --git a/build.gradle.kts b/build.gradle.kts index a2043a3..b96c2aa 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -73,6 +73,16 @@ tasks { } pullTask.dependsOn("bbootimg:jar") + val cleanTask by register("clean", JavaExec::class) { + group = GROUP_ANDROID + main = "cfig.packable.PackableLauncherKt" + classpath = files("bbootimg/build/libs/bbootimg.jar") + this.maxHeapSize = "512m" + enableAssertions = true + args("clean") + } + cleanTask.dependsOn("bbootimg:jar") + //sparse image dependencies if (bHackingMode) { logger.info("Hacking mode!") diff --git a/doc/Pixel6_vbmeta.puml b/doc/Pixel6_vbmeta.puml new file mode 100644 index 0000000..d354904 --- /dev/null +++ b/doc/Pixel6_vbmeta.puml @@ -0,0 +1,41 @@ +@startmindmap +'https://plantuml.com/mindmap-diagram + +caption oriole vbmeta structure +title Pixel 6 vbmeta + +* <&flag>main vbmeta +**[#Orange] <&pulse>boot +***[#lightgreen] <&star>boot +**[#Orange] <&pulse>vbmeta_system +***[#lightblue] <&people>system +***[#lightblue] <&people>system_ext +***[#lightblue] <&people>product +**[#Orange] <&pulse>vbmeta_vendor +***[#lightblue] <&people>vendor +**[#lightblue] <&people>vendor_dlkm +**[#lightgreen] <&star>vendor_boot +**[#lightgreen] <&star>dtbo +**[#lightgreen] <&star>abl +**[#lightgreen] <&star>bl1 +**[#lightgreen] <&star>bl2 +**[#lightgreen] <&star>bl31 +**[#lightgreen] <&star>gsa +**[#lightgreen] <&star>ldfw +**[#lightgreen] <&star>pbl +**[#lightgreen] <&star>tzsw + +header +Pixel 6 - oriole +endheader + +center footer oriole + +legend right +|BG Color| Type | +|<#FFA500>| Chain Partition| +|<#90EE90>| Hash Descriptor| +|<#ADD8E6>| HashTree Descriptor| +endlegend + +@endmindmap diff --git a/doc/system_core_libs.puml b/doc/system_core_libs.puml new file mode 100644 index 0000000..7961403 --- /dev/null +++ b/doc/system_core_libs.puml @@ -0,0 +1,38 @@ +@startuml +'Android system libs + +class libbase { +2 +} +class liblog { +1 +} +class libprocinfo { +3 +} +class libcutils { +5 +* libcutils_sockets (4) +} +class libfstab { +6 +} +class libutils { +L.1 +} +class libdm { +7 +} +class libavb { +* avb_crypto_ops_impl_sha +} +libbase --|> liblog +libprocinfo --|> libbase +libcutils --|> libbase +libcutils --|> liblog +libfstab --|> liblog +libfstab --|> libbase +libutils --|> libcutils +libutils --|> liblog +libdm --|> libbase +@enduml diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index ffed3a2..e750102 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.3-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/helper/build.gradle.kts b/helper/build.gradle.kts index c587323..2aa3a44 100644 --- a/helper/build.gradle.kts +++ b/helper/build.gradle.kts @@ -15,8 +15,9 @@ import org.jetbrains.kotlin.gradle.tasks.KotlinCompile plugins { - id("org.jetbrains.kotlin.jvm") version "1.5.31" + id("org.jetbrains.kotlin.jvm") version "1.6.0" `java-library` + application } repositories { @@ -33,18 +34,19 @@ dependencies { implementation("org.jetbrains.kotlin:kotlin-reflect") implementation("com.google.guava:guava:18.0") - implementation("org.slf4j:slf4j-api:1.7.31") - implementation("org.slf4j:slf4j-simple:1.7.31") + implementation("org.slf4j:slf4j-api:1.7.32") + implementation("org.slf4j:slf4j-simple:1.7.32") implementation("org.apache.commons:commons-exec:1.3") implementation("org.bouncycastle:bcprov-jdk15on:1.69") - implementation("org.apache.commons:commons-compress:1.20") + implementation("org.bouncycastle:bcpkix-jdk15on:1.69") //org.bouncycastle.pkcs + implementation("org.apache.commons:commons-compress:1.21") implementation("org.tukaani:xz:1.9") implementation("com.github.freva:ascii-table:1.2.0") testImplementation("org.jetbrains.kotlin:kotlin-test") testImplementation("org.jetbrains.kotlin:kotlin-test-junit") - testImplementation("com.fasterxml.jackson.core:jackson-annotations:2.12.3") - testImplementation("com.fasterxml.jackson.core:jackson-databind:2.12.3") + testImplementation("com.fasterxml.jackson.core:jackson-annotations:2.13.0") + testImplementation("com.fasterxml.jackson.core:jackson-databind:2.13.0") } tasks.withType().all { @@ -54,3 +56,24 @@ tasks.withType().all { jvmTarget = "11" } } + +application { + mainClass.set("cfig.helper.LauncherKt") +} +tasks { + jar { + manifest { + attributes["Implementation-Title"] = "Helper" + attributes["Main-Class"] = "cfig.helper.LauncherKt" + } + from(configurations.runtimeClasspath.get().map({ if (it.isDirectory) it else zipTree(it) })) + excludes.addAll(mutableSetOf("META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA")) + duplicatesStrategy = DuplicatesStrategy.EXCLUDE + } + test { + testLogging { + showExceptions = true + showStackTraces = true + } + } +} diff --git a/helper/src/main/kotlin/cfig/helper/CryptoHelper.kt b/helper/src/main/kotlin/cfig/helper/CryptoHelper.kt new file mode 100644 index 0000000..5bcdfbf --- /dev/null +++ b/helper/src/main/kotlin/cfig/helper/CryptoHelper.kt @@ -0,0 +1,285 @@ +package cfig.helper + +import cfig.io.Struct3 +import com.google.common.math.BigIntegerMath +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.bouncycastle.pkcs.PKCS10CertificationRequest +import org.bouncycastle.util.io.pem.PemReader +import org.slf4j.LoggerFactory +import java.io.ByteArrayInputStream +import java.io.ByteArrayOutputStream +import java.io.IOException +import java.io.InputStreamReader +import java.math.BigInteger +import java.math.RoundingMode +import java.security.KeyFactory +import java.security.KeyStore +import java.security.MessageDigest +import java.security.Security +import java.security.cert.CertificateFactory +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 CryptoHelper { + class KeyBox { + companion object { + fun parse(data: ByteArray): Any { + val p = PemReader(InputStreamReader(ByteArrayInputStream(data))).readPemObject() + return if (p != null) { + log.debug("parse PEM: " + p.type) + when (p.type) { + "RSA PUBLIC KEY" -> { + org.bouncycastle.asn1.pkcs.RSAPublicKey.getInstance(p.content) as org.bouncycastle.asn1.pkcs.RSAPublicKey + } + "RSA PRIVATE KEY" -> { + org.bouncycastle.asn1.pkcs.RSAPrivateKey.getInstance(p.content) as org.bouncycastle.asn1.pkcs.RSAPrivateKey + } + "PUBLIC KEY" -> { + val keySpec = X509EncodedKeySpec(p.content) + KeyFactory.getInstance("RSA") + .generatePublic(keySpec) as java.security.interfaces.RSAPublicKey + } + "PRIVATE KEY" -> { + val keySpec = PKCS8EncodedKeySpec(p.content) + KeyFactory.getInstance("RSA") + .generatePrivate(keySpec) as java.security.interfaces.RSAPrivateKey + } + "CERTIFICATE REQUEST" -> { + PKCS10CertificationRequest(p.content) + } + "CERTIFICATE" -> { + CertificateFactory.getInstance("X.509").generateCertificate(ByteArrayInputStream(p.content)) + } + else -> throw IllegalArgumentException("unsupported type: ${p.type}") + } + } else { + var bSuccess = false + var ret: Any = false + //try 1 + try { + val spec = PKCS8EncodedKeySpec(data) + val privateKey = KeyFactory.getInstance("RSA").generatePrivate(spec) + log.debug("Parse PKCS8:Private") + ret = privateKey + bSuccess = true + } catch (e: java.security.spec.InvalidKeySpecException) { + log.debug("not PKCS8:Private") + } + if (bSuccess) return ret + + //try 2 + try { + log.debug("Parse X509:Public") + val spec = X509EncodedKeySpec(data) + ret = KeyFactory.getInstance("RSA").generatePublic(spec) + bSuccess = true + } catch (e: java.security.spec.InvalidKeySpecException) { + log.debug(e.toString()) + log.debug("not X509:Public") + } + if (bSuccess) return ret + + //try 3: jks + try { + val pwdArray = "androiddebugkey".toCharArray() + val ks = KeyStore.getInstance("JKS") + ks.load(ByteArrayInputStream(data), pwdArray) + } catch (e: IOException) { + if (e.toString().contains("Keystore was tampered with, or password was incorrect")) { + log.info("JKS password wrong") + bSuccess = false + ret = true + } + } + //at last + return ret + } + } + + fun getPemContent(keyText: String): ByteArray { + val publicKeyPEM = keyText + .replace("-----BEGIN PUBLIC KEY-----", "") + .replace("-----END PUBLIC KEY-----", "") + .replace("-----BEGIN RSA PRIVATE KEY-----", "") + .replace("-----END RSA PRIVATE KEY-----", "") + .replace(System.lineSeparator().toRegex(), "") + .replace("\n", "") + .replace("\r", "") + return Base64.getDecoder().decode(publicKeyPEM) + } + + /* + in: modulus, public expo + out: PublicKey + + in: modulus, private expo + out: PrivateKey + */ + fun makeKey(modulus: BigInteger, exponent: BigInteger, isPublicExpo: Boolean): Any { + return if (isPublicExpo) { + KeyFactory.getInstance("RSA").generatePublic(RSAPublicKeySpec(modulus, exponent)) + } else { + KeyFactory.getInstance("RSA").generatePrivate(RSAPrivateKeySpec(modulus, exponent)) + } + } + + + /* + 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(rsa: org.bouncycastle.asn1.pkcs.RSAPrivateKey): ByteArray { + assert(65537.toBigInteger() == rsa.publicExponent) + val numBits: Int = BigIntegerMath.log2(rsa.modulus, RoundingMode.CEILING) + assert(rsa.modulus.bitLength() == numBits) + val b = BigInteger.valueOf(2).pow(32) + val n0inv = b.minus(rsa.modulus.modInverse(b)).toLong() + val rrModn = BigInteger.valueOf(4).pow(numBits).rem(rsa.modulus) + val unsignedModulo = rsa.modulus.toByteArray().sliceArray(1..numBits / 8) //remove sign byte + return Struct3("!II${numBits / 8}b${numBits / 8}b").pack( + numBits, + n0inv, + unsignedModulo, + rrModn.toByteArray() + ) + } + + fun decodeRSAkey(key: ByteArray): java.security.interfaces.RSAPublicKey { + val ret = Struct3("!II").unpack(ByteArrayInputStream(key)) + val numBits = (ret[0] as UInt).toInt() + val n0inv = (ret[1] as UInt).toLong() + val ret2 = Struct3("!II${numBits / 8}b${numBits / 8}b").unpack(ByteArrayInputStream(key)) + val unsignedModulo = ret2[2] as ByteArray + val rrModn = BigInteger(ret2[3] as ByteArray) + log.debug("n0inv=$n0inv, unsignedModulo=${Helper.toHexString(unsignedModulo)}, rrModn=$rrModn") + val exponent = 65537L + val modulus = BigInteger(Helper.join(Struct3("x").pack(0), unsignedModulo)) + val keySpec = RSAPublicKeySpec(modulus, BigInteger.valueOf(exponent)) + return KeyFactory.getInstance("RSA").generatePublic(keySpec) as java.security.interfaces.RSAPublicKey + } + + 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) + } + } //end-companion + } + + class Hasher { + companion object { + 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) + } + } + } + + class Signer { + companion object { + /* 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" + + python counterpart: + import Crypto.PublicKey.RSA + key = Crypto.PublicKey.RSA.construct((modulus, exponent)) + vRet = key.verify(decode_long(padding_and_digest), (decode_long(sig_blob), None)) + print("verify padded digest: %s" % binascii.hexlify(padding_and_digest)) + print("verify sig: %s" % binascii.hexlify(sig_blob)) + print("X: Verify: %s" % vRet) + */ + fun rawRsa(key: java.security.Key, data: ByteArray): ByteArray { + return Cipher.getInstance("RSA/ECB/NoPadding").let { cipher -> + cipher.init(Cipher.ENCRYPT_MODE, key) + cipher.update(data) + cipher.doFinal() + } + } + + fun rawSignOpenSsl(keyPath: String, data: ByteArray): ByteArray { + log.debug("raw input: " + Helper.toHexString(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: " + Helper.toHexString(stdout.toByteArray())) + log.debug("ERR: " + String(stderr.toByteArray())) + } + + if (ret.isEmpty()) throw RuntimeException("raw sign failed") + + return ret + } + + 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(Hasher.sha256(inData), inKey) + } + } + } + + companion object { + private val log = LoggerFactory.getLogger(CryptoHelper::class.java) + 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()) + } + + for ((i, item) in Security.getAlgorithms("Cipher").withIndex()) { + log.info("Cipher: $i -> $item") + } + } + } +} \ No newline at end of file diff --git a/helper/src/main/kotlin/cfig/helper/KeyHelper.kt b/helper/src/main/kotlin/cfig/helper/KeyHelper.kt deleted file mode 100644 index 6e05b33..0000000 --- a/helper/src/main/kotlin/cfig/helper/KeyHelper.kt +++ /dev/null @@ -1,161 +0,0 @@ -// Copyright 2021 yuyezhong@gmail.com -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package cfig.helper - -import cfig.io.Struct3 -import com.google.common.math.BigIntegerMath -import org.bouncycastle.util.io.pem.PemReader -import org.slf4j.LoggerFactory -import java.io.ByteArrayInputStream -import java.io.InputStreamReader -import java.math.BigInteger -import java.math.RoundingMode -import java.security.KeyFactory -import java.security.Security -import java.security.cert.CertificateFactory -import java.security.spec.PKCS8EncodedKeySpec -import java.security.spec.RSAPrivateKeySpec -import java.security.spec.RSAPublicKeySpec -import java.security.spec.X509EncodedKeySpec -import java.util.* - -/* -https://docs.oracle.com/javase/9/security/java-pki-programmers-guide.htm#JSSEC-GUID-650D0D53-B617-4055-AFD3-AF5C2629CBBF -https://www.baeldung.com/java-read-pem-file-keys - */ -class KeyHelper { - companion object { - private val log = LoggerFactory.getLogger(KeyHelper::class.java) - - fun getPemContent(keyText: String): ByteArray { - val publicKeyPEM = keyText - .replace("-----BEGIN PUBLIC KEY-----", "") - .replace("-----END PUBLIC KEY-----", "") - .replace("-----BEGIN RSA PRIVATE KEY-----", "") - .replace("-----END RSA PRIVATE KEY-----", "") - .replace(System.lineSeparator().toRegex(), "") - .replace("\n", "") - .replace("\r", "") - return Base64.getDecoder().decode(publicKeyPEM) - } - - /* - in: modulus, public expo - out: PublicKey - - in: modulus, private expo - out: PrivateKey - */ - fun makeKey(modulus: BigInteger, exponent: BigInteger, isPublicExpo: Boolean): Any { - return if (isPublicExpo) { - KeyFactory.getInstance("RSA").generatePublic(RSAPublicKeySpec(modulus, exponent)) - } else { - KeyFactory.getInstance("RSA").generatePrivate(RSAPrivateKeySpec(modulus, exponent)) - } - } - - fun parse(data: ByteArray): Any { - val p = PemReader(InputStreamReader(ByteArrayInputStream(data))).readPemObject() - return if (p != null) { - log.debug("parse PEM: " + p.type) - when (p.type) { - "RSA PUBLIC KEY" -> { - org.bouncycastle.asn1.pkcs.RSAPublicKey.getInstance(p.content) as org.bouncycastle.asn1.pkcs.RSAPublicKey - } - "RSA PRIVATE KEY" -> { - org.bouncycastle.asn1.pkcs.RSAPrivateKey.getInstance(p.content) as org.bouncycastle.asn1.pkcs.RSAPrivateKey - } - "PUBLIC KEY" -> { - val keySpec = X509EncodedKeySpec(p.content) - KeyFactory.getInstance("RSA").generatePublic(keySpec) as java.security.interfaces.RSAPublicKey - } - "PRIVATE KEY" -> { - val keySpec = PKCS8EncodedKeySpec(p.content) - KeyFactory.getInstance("RSA").generatePrivate(keySpec) as java.security.interfaces.RSAPrivateKey - } - "CERTIFICATE" -> { - CertificateFactory.getInstance("X.509").generateCertificate(ByteArrayInputStream(p.content)) - } - else -> throw IllegalArgumentException("unsupported type: ${p.type}") - } - } else { - try { - val spec = PKCS8EncodedKeySpec(data) - val privateKey = KeyFactory.getInstance("RSA").generatePrivate(spec) - log.debug("Parse PKCS8: Private") - privateKey - } catch (e: java.security.spec.InvalidKeySpecException) { - log.debug("Parse X509: Public") - val spec = X509EncodedKeySpec(data) - KeyFactory.getInstance("RSA").generatePublic(spec) - } - } - } - - /* - 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(rsa: org.bouncycastle.asn1.pkcs.RSAPrivateKey): ByteArray { - assert(65537.toBigInteger() == rsa.publicExponent) - val numBits: Int = BigIntegerMath.log2(rsa.modulus, RoundingMode.CEILING) - assert(rsa.modulus.bitLength() == numBits) - val b = BigInteger.valueOf(2).pow(32) - val n0inv = b.minus(rsa.modulus.modInverse(b)).toLong() - val rrModn = BigInteger.valueOf(4).pow(numBits).rem(rsa.modulus) - val unsignedModulo = rsa.modulus.toByteArray().sliceArray(1..numBits / 8) //remove sign byte - return Struct3("!II${numBits / 8}b${numBits / 8}b").pack( - numBits, - n0inv, - unsignedModulo, - rrModn.toByteArray() - ) - } - - fun decodeRSAkey(key: ByteArray): java.security.interfaces.RSAPublicKey { - val ret = Struct3("!II").unpack(ByteArrayInputStream(key)) - val numBits = (ret[0] as UInt).toInt() - val n0inv = (ret[1] as UInt).toLong() - val ret2 = Struct3("!II${numBits / 8}b${numBits / 8}b").unpack(ByteArrayInputStream(key)) - val unsignedModulo = ret2[2] as ByteArray - val rrModn = BigInteger(ret2[3] as ByteArray) - log.debug("n0inv=$n0inv, unsignedModulo=${Helper.toHexString(unsignedModulo)}, rrModn=$rrModn") - val exponent = 65537L - val modulus = BigInteger(Helper.join(Struct3("x").pack(0), unsignedModulo)) - val keySpec = RSAPublicKeySpec(modulus, BigInteger.valueOf(exponent)) - return KeyFactory.getInstance("RSA").generatePublic(keySpec) as java.security.interfaces.RSAPublicKey - } - - 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()) - } - - for ((i, item) in Security.getAlgorithms("Cipher").withIndex()) { - log.info("Cipher: $i -> $item") - } - } - } -} diff --git a/helper/src/main/kotlin/cfig/helper/KeyHelper2.kt b/helper/src/main/kotlin/cfig/helper/KeyHelper2.kt deleted file mode 100644 index 40a43a9..0000000 --- a/helper/src/main/kotlin/cfig/helper/KeyHelper2.kt +++ /dev/null @@ -1,106 +0,0 @@ -// Copyright 2021 yuyezhong@gmail.com -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package cfig.helper - -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.security.MessageDigest -import javax.crypto.Cipher - -class KeyHelper2 { - companion object { - private val log = LoggerFactory.getLogger(KeyHelper2::class.java) - - /* 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" - - python counterpart: - import Crypto.PublicKey.RSA - key = Crypto.PublicKey.RSA.construct((modulus, exponent)) - vRet = key.verify(decode_long(padding_and_digest), (decode_long(sig_blob), None)) - print("verify padded digest: %s" % binascii.hexlify(padding_and_digest)) - print("verify sig: %s" % binascii.hexlify(sig_blob)) - print("X: Verify: %s" % vRet) - */ - fun rawRsa(key: java.security.Key, data: ByteArray): ByteArray { - return Cipher.getInstance("RSA/ECB/NoPadding").let { cipher -> - cipher.init(Cipher.ENCRYPT_MODE, key) - cipher.update(data) - cipher.doFinal() - } - } - - fun rawSignOpenSsl(keyPath: String, data: ByteArray): ByteArray { - log.debug("raw input: " + Helper.toHexString(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: " + Helper.toHexString(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) - } - } -} diff --git a/helper/src/main/kotlin/cfig/helper/Launcher.kt b/helper/src/main/kotlin/cfig/helper/Launcher.kt new file mode 100644 index 0000000..8142c42 --- /dev/null +++ b/helper/src/main/kotlin/cfig/helper/Launcher.kt @@ -0,0 +1,201 @@ +package cfig.helper + +import com.google.common.io.Files +import org.bouncycastle.util.io.pem.PemReader +import org.slf4j.LoggerFactory +import java.io.ByteArrayInputStream +import java.io.File +import java.io.InputStreamReader +import kotlin.system.exitProcess +import cfig.helper.OpenSslHelper.KeyFormat + +class Launcher { + companion object { + fun help() { + println("Help:") + println("\tcrypo.list") + println("\tcrypto.key.parse ") + println("\tcrypto.key.genrsa ") + println("\tcrypto.key.1 ") + } + } +} + +fun main(args: Array) { + val log = LoggerFactory.getLogger("main") + if (args.isEmpty()) { + Launcher.help() + exitProcess(0) + } + + when (args[0]) { + "crypo.list" -> { + CryptoHelper.listAll() + } + "crypto.key.parse" -> { + val ba = File(args[1]).readBytes() + val k = CryptoHelper.KeyBox.parse(ba) + if (k is Boolean) { + if (k) { + log.info("File recognized but not parsed") + } else { + log.warn("Unrecognized file " + args[1]) + } + } else { + log.info("Recognized " + k::class) + } + } + "crypto.key.genrsa", "crypto.key.0" -> { + val kLen: Int = args[1].toInt() + val kOut = args[2] + OpenSslHelper.PK1Key.generate(kLen).apply { + writeTo(kOut) + } + log.info("RSA key(len=$kLen) written to $kOut") + } + "crypto.key.1" -> { + //Action-1: private RSA key -> RSA public key(PEM) + val hint = "private RSA key -> RSA public key(PEM)" + val kFile = args[1] + val outFile = args[2] + assert(CryptoHelper.KeyBox.parse(File(kFile).readBytes()) is org.bouncycastle.asn1.pkcs.RSAPrivateKey) + val rsa = OpenSslHelper.PK1Key(data = File(kFile).readBytes()) + val rsaPubPEM = rsa.getPublicKey(KeyFormat.PEM).apply { + writeTo(outFile) + log.info("$hint: $kFile => $outFile") + } + } + "crypto.key.2" -> { + //Action-2: private RSA key -> RSA public key(DER) + val kFile = args[1] + val outFile = args[2] + assert(CryptoHelper.KeyBox.parse(File(kFile).readBytes()) is org.bouncycastle.asn1.pkcs.RSAPrivateKey) + val rsa = OpenSslHelper.PK1Key(data = File(kFile).readBytes()) + val rsaPubDer = rsa.getPublicKey(KeyFormat.DER).apply { + writeTo(outFile) + log.info("RSA pub key(der) written to $outFile") + } + } + "crypto.key.3" -> { + //Action-3: (PEM) --> (DER) + val kFile = args[1] + val outFile = args[2] + val decodeFromPem = CryptoHelper.KeyBox.decodePem(File(kFile).readText()) + Files.write(decodeFromPem, File(outFile)) + log.info("PEM ($kFile) => raw ($outFile)") + } + "crypto.key.4" -> { + //Action-4: + var hint = "RSA private: PK1 <=> PK8(PEM)" + val kFile = args[1] + val outFile = args[2] + when (val k = CryptoHelper.KeyBox.parse(File(kFile).readBytes())) { + is org.bouncycastle.asn1.pkcs.RSAPrivateKey -> { + hint = "RSA private: PK1 => PK8(PEM)" + val rsa = OpenSslHelper.PK1Key(data = File(kFile).readBytes()) + rsa.toPk8(KeyFormat.PEM).writeTo(outFile) + } + is java.security.interfaces.RSAPrivateKey -> { + hint = "RSA private: PK8 => PK1(PEM)" + val rsa = OpenSslHelper.PK8RsaKey(data = File(kFile).readBytes()) + rsa.toPk1().writeTo(outFile) + } + else -> { + hint = "RSA private: PK1 <=> PK8(PEM)" + log.warn(hint) + throw IllegalArgumentException("unsupported $k") + } + } + log.info("$hint: $kFile => $outFile") + } + "crypto.key.5" -> { + val hint = "RSA private(PK8): => Public Key(PK8, PEM)" + val kFile = args[1] + val outFile = args[2] + assert(CryptoHelper.KeyBox.parse(File(kFile).readBytes()) is org.bouncycastle.asn1.pkcs.RSAPrivateKey) + val pk8rsa = OpenSslHelper.PK8RsaKey(KeyFormat.PEM, File(kFile).readBytes()) + pk8rsa.getPublicKey().writeTo(outFile) + log.info("$hint: $kFile => $outFile") + } + "crypto.key.7" -> { + val hint = "RSA private: PK1 => PK8(DER)" + val kFile = args[1] + val outFile = args[2] + assert(CryptoHelper.KeyBox.parse(File(kFile).readBytes()) is org.bouncycastle.asn1.pkcs.RSAPrivateKey) + val rsa = OpenSslHelper.PK1Key(data = File(kFile).readBytes()) + rsa.toPk8(KeyFormat.DER).writeTo(outFile) + log.info("$hint: $kFile => $outFile") + } + "crypto.key.10" -> { + //Action-10: + var hint = "" + val kFile = args[1] + val outFile = args[2] + val inBytes = File(kFile).readBytes() + assert(CryptoHelper.KeyBox.parse(inBytes) is java.security.interfaces.RSAPrivateKey) + val p = PemReader(InputStreamReader(ByteArrayInputStream(File(kFile).readBytes()))).readPemObject() + if (p != null) {//pem + hint = "PK8 RSA: PEM => DER" + OpenSslHelper.PK8RsaKey(KeyFormat.PEM, inBytes).transform(KeyFormat.PEM, KeyFormat.DER).writeTo(outFile) + } else {//der + hint = "PK8 RSA: DER => PEM" + OpenSslHelper.PK8RsaKey(KeyFormat.DER, inBytes).transform(KeyFormat.DER, KeyFormat.PEM).writeTo(outFile) + } + log.info("$hint: $kFile => $outFile") + } + "crypto.key.11" -> { + val hint = "RSA public(PK8): PEM => DER" + val kFile = args[1] + val outFile = args[2] + File(outFile).writeBytes( + CryptoHelper.KeyBox.decodePem(File(kFile).readText()) + ) + log.info("$hint: $kFile => $outFile") + } + "crypto.key.csr" -> { + //Action-xx: + val hint = "PK1 RSA PEM ==> CSR" + val kFile = args[1] + val outFile = args[2] + val inBytes = File(kFile).readBytes() + assert(CryptoHelper.KeyBox.parse(inBytes) is org.bouncycastle.asn1.pkcs.RSAPrivateKey) + OpenSslHelper.PK1Key(KeyFormat.PEM,inBytes).toCsr().writeTo(outFile) + log.info("$hint: $kFile => $outFile") + } + "crypto.key.crt" -> { + //Action-xx: + val hint = "PK1 RSA PEM ==> CRT" + val kFile = args[1] + val outFile = args[2] + val inBytes = File(kFile).readBytes() + assert(CryptoHelper.KeyBox.parse(inBytes) is org.bouncycastle.asn1.pkcs.RSAPrivateKey) + OpenSslHelper.PK1Key(KeyFormat.PEM,inBytes).toV1Cert().writeTo(outFile) + log.info("$hint: $kFile => $outFile") + } + "crypto.key.22" -> { + //Action-xx: + val hint = "CRT ==> JKS" + val kFile = args[1] + val outFile = args[2] + val inBytes = File(kFile).readBytes() + assert(CryptoHelper.KeyBox.parse(inBytes) is org.bouncycastle.asn1.pkcs.RSAPrivateKey) + OpenSslHelper.PK1Key(KeyFormat.PEM,inBytes).toV1Cert().writeTo(outFile) + log.info("$hint: $kFile => $outFile") + } + "crypto.key.xx" -> { + //Action-xx: + val hint = "" + val kFile = args[1] + val outFile = args[2] + File(outFile).writeBytes( + CryptoHelper.KeyBox.decodePem(File(kFile).readText()) + ) + log.info("$hint: $kFile => $outFile") + } + else -> { + Launcher.help() + exitProcess(1) + } + } + return +} diff --git a/helper/src/main/kotlin/cfig/helper/OpenSslHelper.kt b/helper/src/main/kotlin/cfig/helper/OpenSslHelper.kt index eb60d21..eed15b7 100644 --- a/helper/src/main/kotlin/cfig/helper/OpenSslHelper.kt +++ b/helper/src/main/kotlin/cfig/helper/OpenSslHelper.kt @@ -39,9 +39,11 @@ class OpenSslHelper { } } - class PK1Key(val format: KeyFormat = KeyFormat.PEM, - override val data: ByteArray = byteArrayOf(), - override val name: String = "RSA Private") : IKey { + 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 */ @@ -49,8 +51,10 @@ class OpenSslHelper { if (format != KeyFormat.PEM) { throw IllegalArgumentException("can not handle $format private key") } - val ret = Helper.powerRun("openssl rsa -in $stdin -pubout -outform ${pubKeyFormat.name}", - ByteArrayInputStream(data)) + val ret = Helper.powerRun( + "openssl rsa -in $stdin -pubout -outform ${pubKeyFormat.name}", + ByteArrayInputStream(data) + ) log.info("privateToPublic:stderr: ${String(ret[1])}") return PK1PubKey(format = pubKeyFormat, data = ret[0]) } @@ -65,8 +69,10 @@ class OpenSslHelper { if (this.format != KeyFormat.PEM) { throw java.lang.IllegalArgumentException("Only PEM key is supported") } - val ret = Helper.powerRun2("openssl rsa -in $stdin -pubout", - ByteArrayInputStream(data)) + val ret = Helper.powerRun2( + "openssl rsa -in $stdin -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) @@ -84,16 +90,18 @@ class OpenSslHelper { openssl pkcs8 -nocrypt -in - -topk8 -outform DER */ fun toPk8(pk8Format: KeyFormat): PK8RsaKey { - val ret = Helper.powerRun("openssl pkcs8 -nocrypt -in $stdin -topk8 -outform ${pk8Format.name}", - ByteArrayInputStream(data)) + val ret = Helper.powerRun( + "openssl pkcs8 -nocrypt -in $stdin -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" + fun toCsr(info: String? = null): Csr { + val defaultInfo = "/C=CN/ST=Shanghai/L=Shanghai/O=XXX/OU=infra/CN=gerrit/emailAddress=webmaster@XX.com" val cmdLine = CommandLine.parse("openssl req -new -key $stdin -subj").apply { - this.addArgument("$info", true) + this.addArgument(info ?: defaultInfo, true) } val ret = Helper.powerRun3(cmdLine, ByteArrayInputStream(data)) if (ret[0] as Boolean) { @@ -117,8 +125,10 @@ class OpenSslHelper { val tmpFile = File.createTempFile("pk1.", ".csr") tmpFile.writeBytes(csr.data) tmpFile.deleteOnExit() - val ret = Helper.powerRun2("openssl x509 -req -in ${tmpFile.path} -signkey $stdin -days 180", - ByteArrayInputStream(data)) + val ret = Helper.powerRun2( + "openssl x509 -req -in ${tmpFile.path} -signkey $stdin -days 180", + ByteArrayInputStream(data) + ) if (ret[0] as Boolean) { log.info("toCrt:error: ${String(ret[2] as ByteArray)}") return Crt(ret[1] as ByteArray) @@ -141,9 +151,11 @@ class OpenSslHelper { } } - class PK8RsaKey(val format: KeyFormat = KeyFormat.PEM, - override val data: ByteArray = byteArrayOf(), - override val name: String = "PK8 Private") : IKey { + class PK8RsaKey( + val format: KeyFormat = KeyFormat.PEM, + override val data: ByteArray = byteArrayOf(), + override val name: String = "PK8 Private" + ) : IKey { /* file based: @@ -157,8 +169,10 @@ class OpenSslHelper { if (this.format != KeyFormat.PEM) { throw IllegalArgumentException("Only pk8+pem can be converted to RSA") } - val ret = Helper.powerRun2("openssl rsa -in $stdin", - ByteArrayInputStream(data)) + val ret = Helper.powerRun2( + "openssl rsa -in $stdin", + ByteArrayInputStream(data) + ) if (ret[0] as Boolean) { log.info("toRsaPrivate:error: ${String(ret[2] as ByteArray)}") return PK1Key(KeyFormat.PEM, ret[1] as ByteArray) @@ -173,8 +187,10 @@ class OpenSslHelper { openssl pkcs8 -nocrypt -in - -inform DER */ fun transform(inFormat: KeyFormat, outFormat: KeyFormat): PK8RsaKey { - val ret = Helper.powerRun2("openssl pkcs8 -nocrypt -in $stdin -inform ${inFormat.name} -outform ${outFormat.name}", - ByteArrayInputStream(data)) + val ret = Helper.powerRun2( + "openssl pkcs8 -nocrypt -in $stdin -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) @@ -195,8 +211,10 @@ class OpenSslHelper { if (this.format != KeyFormat.PEM) { throw java.lang.IllegalArgumentException("Only PEM key is supported") } - val ret = Helper.powerRun2("openssl rsa -in $stdin -pubout", - ByteArrayInputStream(data)) + val ret = Helper.powerRun2( + "openssl rsa -in $stdin -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) @@ -209,15 +227,15 @@ class OpenSslHelper { } class PK1PubKey( - val format: KeyFormat = KeyFormat.PEM, - override val data: ByteArray = byteArrayOf(), - override val name: String = "RSA Public" + 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" + 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 @@ -226,8 +244,10 @@ class OpenSslHelper { 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) + 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)}") @@ -239,17 +259,19 @@ class OpenSslHelper { } } - class Crt(val data: ByteArray = byteArrayOf()) { + class Crt(override val data: ByteArray = byteArrayOf(), override val name: String = "crt") : IKey { //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())) + 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)}") @@ -268,9 +290,11 @@ class OpenSslHelper { } } - class Pfx(override val name: String = "androiddebugkey", - var thePassword: String = "somepassword", - override var data: ByteArray = byteArrayOf()) : IKey { + 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) @@ -295,7 +319,7 @@ class OpenSslHelper { } } - //Zkeytool -importkeystore -deststorepass $(thePassword) -destkeystore $(jks_file) -srckeystore $(pfx_cert) -srcstoretype PKCS12 -srcstorepass $(thePassword) + //keytool -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() } @@ -324,16 +348,6 @@ class OpenSslHelper { private val log = LoggerFactory.getLogger(OpenSslHelper::class.java) val stdin = if (System.getProperty("os.name").contains("Mac")) "/dev/stdin" else "-" - 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) @@ -342,7 +356,8 @@ class OpenSslHelper { 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 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)}") diff --git a/helper/src/main/resources/general.cfg b/helper/src/main/resources/general.cfg new file mode 100644 index 0000000..e69de29 diff --git a/helper/src/test/kotlin/cfig/helper/OpenSslHelperTest.kt b/helper/src/test/kotlin/cfig/helper/OpenSslHelperTest.kt index 68dc8ba..bb34ae0 100644 --- a/helper/src/test/kotlin/cfig/helper/OpenSslHelperTest.kt +++ b/helper/src/test/kotlin/cfig/helper/OpenSslHelperTest.kt @@ -14,7 +14,6 @@ package cfig.helper -import cfig.helper.OpenSslHelper.Companion.decodePem import org.junit.Test import org.slf4j.LoggerFactory import java.io.File @@ -36,7 +35,7 @@ class OpenSslHelperTest { writeTo("2_rsa_pub.pem.key") } //Action-3: RSA public key(PEM) --> RSA public key(DER) - val decodeFromPem = decodePem(String(rsaPubPEM.data)) + val decodeFromPem = CryptoHelper.KeyBox.decodePem(String(rsaPubPEM.data)) //Action-2: private RSA key -> RSA public key(DER) val rsaPubDer = rsa.getPublicKey(OpenSslHelper.KeyFormat.DER).apply { @@ -66,18 +65,18 @@ class OpenSslHelperTest { run { //check equality: 8 == 4,5 val pk8Pub = rsa.getPk8PublicKey() - val action8_11 = decodePem(String(pk8Pub.data)) + val action8_11 = CryptoHelper.KeyBox.decodePem(String(pk8Pub.data)) // val pk8Pub2 = rsa.toPk8(OpenSslHelper.KeyFormat.PEM).getPublicKey() // assert(pk8Pub.data.contentEquals(pk8Pub2.data)) } - //check equality: 4,9 == original RSA + //check equality: 4,4' == original RSA rsa.toPk8(OpenSslHelper.KeyFormat.PEM).let { pk8Pem -> val shortConversion = pk8Pem.toPk1() assert(shortConversion.data.contentEquals(rsa.data)) } - //check equality: 7,10,9 == original RSA + //check equality: 7,10,4' == original RSA rsa.toPk8(OpenSslHelper.KeyFormat.DER).let { pk8der -> val longConversion = pk8der .transform(OpenSslHelper.KeyFormat.DER, OpenSslHelper.KeyFormat.PEM) //pk8 PEM diff --git a/integrationTest.py b/integrationTest.py index a062652..9f0970b 100755 --- a/integrationTest.py +++ b/integrationTest.py @@ -71,6 +71,10 @@ def verifySingleJson(jsonFile, func = None): for k, v in verifyItems["hash"].items(): log.info("%s : %s" % (k, v)) unittest.TestCase().assertEqual(v, hashFile(k)) + try: + subprocess.check_call(gradleWrapper + " clean", shell = True) + except Exception as e: + pass def verifySingleDir(inResourceDir, inImageDir): resDir = inResourceDir