Issue #79: add "gradle clean" command

pull/94/head
cfig 4 years ago
parent 9123bae511
commit 5cef10203b
No known key found for this signature in database
GPG Key ID: B104C307F0FDABB7

@ -35,7 +35,7 @@ jobs:
# Runs a single command using the runners shell # Runs a single command using the runners shell
- name: Unit Test - name: Unit Test
run: ./gradlew check && ./gradlew clean run: ./gradlew check && ./gradlew clean || true
# Runs a set of commands using the runners shell # Runs a set of commands using the runners shell
- name: Integration Test - name: Integration Test
@ -59,7 +59,7 @@ jobs:
run: brew install dtc run: brew install dtc
- name: Unit Test - name: Unit Test
run: ./gradlew check && ./gradlew clean run: ./gradlew check && ./gradlew clean || true
# Runs a set of commands using the runners shell # Runs a set of commands using the runners shell
- name: Integration Test - name: Integration Test
@ -90,7 +90,7 @@ jobs:
run: choco install openssl dtc-msys2 run: choco install openssl dtc-msys2
- name: Unit Test - 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 # Runs a set of commands using the runners shell
- name: Integration Test - name: Integration Test

@ -15,7 +15,7 @@
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
plugins { plugins {
kotlin("jvm") version "1.5.31" kotlin("jvm") version "1.6.0"
application application
} }
@ -33,16 +33,16 @@ dependencies {
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8") implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
implementation("org.jetbrains.kotlin:kotlin-reflect") implementation("org.jetbrains.kotlin:kotlin-reflect")
implementation("org.slf4j:slf4j-simple:1.7.31") implementation("org.slf4j:slf4j-simple:1.7.32")
implementation("org.slf4j:slf4j-api:1.7.31") implementation("org.slf4j:slf4j-api:1.7.32")
implementation("com.fasterxml.jackson.core:jackson-annotations:2.12.3") implementation("com.fasterxml.jackson.core:jackson-annotations:2.13.0")
implementation("com.fasterxml.jackson.core:jackson-databind:2.12.3") implementation("com.fasterxml.jackson.core:jackson-databind:2.13.0")
implementation("com.google.guava:guava:18.0") implementation("com.google.guava:guava:18.0")
implementation("org.apache.commons:commons-exec:1.3") 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("org.tukaani:xz:1.9")
implementation("commons-codec:commons-codec:1.15") 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("org.bouncycastle:bcprov-jdk15on:1.69")
implementation("de.vandermeer:asciitable:0.3.2") implementation("de.vandermeer:asciitable:0.3.2")
implementation(project(":helper")) implementation(project(":helper"))

@ -21,10 +21,9 @@ import avb.blob.AuxBlob
import avb.blob.Footer import avb.blob.Footer
import avb.blob.Header import avb.blob.Header
import avb.desc.* import avb.desc.*
import cfig.helper.CryptoHelper
import cfig.helper.Helper import cfig.helper.Helper
import cfig.helper.Helper.Companion.paddingWith import cfig.helper.Helper.Companion.paddingWith
import cfig.helper.KeyHelper
import cfig.helper.KeyHelper2
import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.databind.ObjectMapper
import org.apache.commons.codec.binary.Hex import org.apache.commons.codec.binary.Hex
import org.apache.commons.exec.CommandLine import org.apache.commons.exec.CommandLine
@ -195,8 +194,8 @@ class Avb {
val readHash = Helper.join(declaredAlg.padding, Helper.fromHexString(ai.authBlob!!.hash!!)) val readHash = Helper.join(declaredAlg.padding, Helper.fromHexString(ai.authBlob!!.hash!!))
if (calcHash.contentEquals(readHash)) { if (calcHash.contentEquals(readHash)) {
log.info("VERIFY($localParent->AuthBlob): verify hash... PASS") log.info("VERIFY($localParent->AuthBlob): verify hash... PASS")
val readPubKey = KeyHelper.decodeRSAkey(ai.auxBlob!!.pubkey!!.pubkey) val readPubKey = CryptoHelper.KeyBox.decodeRSAkey(ai.auxBlob!!.pubkey!!.pubkey)
val hashFromSig = KeyHelper2.rawRsa(readPubKey, Helper.fromHexString(ai.authBlob!!.signature!!)) val hashFromSig = CryptoHelper.Signer.rawRsa(readPubKey, Helper.fromHexString(ai.authBlob!!.signature!!))
if (hashFromSig.contentEquals(readHash)) { if (hashFromSig.contentEquals(readHash)) {
log.info("VERIFY($localParent->AuthBlob): verify signature... PASS") log.info("VERIFY($localParent->AuthBlob): verify signature... PASS")
} else { } else {

@ -15,9 +15,8 @@
package avb.blob package avb.blob
import avb.alg.Algorithms import avb.alg.Algorithms
import cfig.helper.CryptoHelper
import cfig.helper.Helper import cfig.helper.Helper
import cfig.helper.KeyHelper
import cfig.helper.KeyHelper2
import cfig.io.Struct3 import cfig.io.Struct3
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
import java.nio.file.Files import java.nio.file.Files
@ -56,8 +55,8 @@ data class AuthBlob(
return if (alg.name == "NONE") { return if (alg.name == "NONE") {
byteArrayOf() byteArrayOf()
} else { } else {
val k = KeyHelper.parse(Files.readAllBytes(Paths.get(alg.defaultKey.replace(".pem", ".pk8")))) as PrivateKey val k = CryptoHelper.KeyBox.parse(Files.readAllBytes(Paths.get(alg.defaultKey.replace(".pem", ".pk8")))) as PrivateKey
KeyHelper2.rawRsa(k, Helper.join(alg.padding, hash)) CryptoHelper.Signer.rawRsa(k, Helper.join(alg.padding, hash))
} }
} }

@ -14,11 +14,10 @@
package avb.blob package avb.blob
import avb.AVBInfo
import avb.alg.Algorithm import avb.alg.Algorithm
import avb.desc.* import avb.desc.*
import cfig.helper.CryptoHelper
import cfig.helper.Helper import cfig.helper.Helper
import cfig.helper.KeyHelper
import cfig.io.Struct3 import cfig.io.Struct3
import com.fasterxml.jackson.annotation.JsonIgnoreProperties import com.fasterxml.jackson.annotation.JsonIgnoreProperties
import org.bouncycastle.asn1.pkcs.RSAPrivateKey import org.bouncycastle.asn1.pkcs.RSAPrivateKey
@ -140,8 +139,8 @@ class AuxBlob(
if (key == null) { if (key == null) {
algKey = Files.readAllBytes((Paths.get(alg.defaultKey))) algKey = Files.readAllBytes((Paths.get(alg.defaultKey)))
} }
val rsa = KeyHelper.parse(algKey!!) as RSAPrivateKey //BC RSA val rsa = CryptoHelper.KeyBox.parse(algKey!!) as RSAPrivateKey //BC RSA
encodedKey = KeyHelper.encodeRSAkey(rsa) encodedKey = CryptoHelper.KeyBox.encodeRSAkey(rsa)
assert(alg.public_key_num_bytes == encodedKey.size) assert(alg.public_key_num_bytes == encodedKey.size)
} else { } else {
log.info("encodePubKey(): No key to encode for algorithm " + alg.name) log.info("encodePubKey(): No key to encode for algorithm " + alg.name)

@ -15,8 +15,8 @@
package avb.desc package avb.desc
import avb.blob.Header import avb.blob.Header
import cfig.helper.CryptoHelper
import cfig.helper.Helper import cfig.helper.Helper
import cfig.helper.KeyHelper2
import cfig.io.Struct3 import cfig.io.Struct3
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
import java.io.* import java.io.*
@ -117,8 +117,8 @@ class HashTreeDescriptor(
fis.skip(this.tree_offset) fis.skip(this.tree_offset)
fis.read(readTree) fis.read(readTree)
} }
val ourHtHash = KeyHelper2.sha256(File("hash.tree").readBytes()) val ourHtHash = CryptoHelper.Hasher.sha256(File("hash.tree").readBytes())
val diskHtHash = KeyHelper2.sha256(readTree) val diskHtHash = CryptoHelper.Hasher.sha256(readTree)
if (!ourHtHash.contentEquals(diskHtHash)) { if (!ourHtHash.contentEquals(diskHtHash)) {
return arrayOf(false, "MerkleTree corrupted") return arrayOf(false, "MerkleTree corrupted")
} else { } else {
@ -136,7 +136,7 @@ class HashTreeDescriptor(
} }
private fun calcSingleHashSize(padded: Boolean = false): Int { 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 val padSize = Helper.round_to_pow2(digSize.toLong()) - digSize
return (digSize + (if (padded) padSize else 0)).toInt() return (digSize + (if (padded) padSize else 0)).toInt()
} }
@ -159,7 +159,7 @@ class HashTreeDescriptor(
var totalRead = 0L var totalRead = 0L
while (true) { while (true) {
val data = ByteArray(blockSz) 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) val bytesRead = inputStream.read(data)
if (bytesRead <= 0) { if (bytesRead <= 0) {
return@hashing return@hashing

@ -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
}
}

@ -15,10 +15,11 @@
package cfig.packable package cfig.packable
import avb.blob.Footer import avb.blob.Footer
import cfig.Avb
import cfig.bootimg.Common.Companion.probeHeaderVersion import cfig.bootimg.Common.Companion.probeHeaderVersion
import cfig.bootimg.v2.BootV2 import cfig.bootimg.v2.BootV2
import cfig.bootimg.v2.BootV2Dialects
import cfig.bootimg.v3.BootV3 import cfig.bootimg.v3.BootV3
import cfig.helper.Helper.Companion.deleteIfExists
import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.databind.ObjectMapper
import de.vandermeer.asciitable.AsciiTable import de.vandermeer.asciitable.AsciiTable
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
@ -37,20 +38,31 @@ class BootImgParser : IPackable {
cleanUp() cleanUp()
val hv = probeHeaderVersion(fileName) val hv = probeHeaderVersion(fileName)
log.info("header version $hv") log.info("header version $hv")
if (hv in 0..2) { when (hv) {
val b2 = BootV2 in 0..2 -> {
.parse(fileName) val b2 = BootV2
.extractImages() .parse(fileName)
.extractVBMeta() .extractImages()
.printSummary() .extractVBMeta()
log.debug(b2.toString()) .printSummary()
} else { log.debug(b2.toString())
val b3 = BootV3 }
.parse(fileName) in 3..4 -> {
.extractImages() val b3 = BootV3
.extractVBMeta() .parse(fileName)
.printSummary() .extractImages()
log.debug(b3.toString()) .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) 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 { companion object {
private val log = LoggerFactory.getLogger(BootImgParser::class.java) private val log = LoggerFactory.getLogger(BootImgParser::class.java)
} }

@ -15,6 +15,7 @@
package cfig.packable package cfig.packable
import cfig.helper.Helper import cfig.helper.Helper
import cfig.helper.Helper.Companion.deleteIfExists
import cfig.utils.DTC import cfig.utils.DTC
import cfig.utils.EnvironmentVerifier import cfig.utils.EnvironmentVerifier
import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.databind.ObjectMapper
@ -60,6 +61,14 @@ class DtboParser(val workDir: File) : IPackable {
super.`@verify`(fileName) 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) { private fun execInDirectory(cmd: CommandLine, inWorkDir: File) {
DefaultExecutor().let { DefaultExecutor().let {
it.workingDirectory = inWorkDir it.workingDirectory = inWorkDir

@ -16,6 +16,7 @@ package cfig.packable
import avb.AVBInfo import avb.AVBInfo
import cfig.Avb import cfig.Avb
import cfig.helper.Helper.Companion.deleteIfExists
import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.databind.ObjectMapper
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
import java.io.File import java.io.File
@ -59,5 +60,12 @@ class VBMetaParser: IPackable {
super.pull(fileName, deviceName) super.pull(fileName, deviceName)
} }
fun clean(fileName: String) {
super.cleanUp()
listOf("", ".signed").forEach {
"$fileName$it".deleteIfExists()
}
}
private val log = LoggerFactory.getLogger(VBMetaParser::class.java) private val log = LoggerFactory.getLogger(VBMetaParser::class.java)
} }

@ -15,6 +15,7 @@
package cfig.packable package cfig.packable
import cfig.bootimg.v3.VendorBoot import cfig.bootimg.v3.VendorBoot
import cfig.helper.Helper.Companion.deleteIfExists
import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.databind.ObjectMapper
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
import java.io.File import java.io.File
@ -54,6 +55,14 @@ class VendorBootParser : IPackable {
super.pull(fileName, deviceName) 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) { override fun flash(fileName: String, deviceName: String) {
val stem = fileName.substring(0, fileName.indexOf(".")) val stem = fileName.substring(0, fileName.indexOf("."))
super.flash("$fileName.signed", stem) super.flash("$fileName.signed", stem)

@ -13,9 +13,8 @@
// limitations under the License. // limitations under the License.
import avb.alg.Algorithms import avb.alg.Algorithms
import cfig.helper.CryptoHelper
import cfig.helper.Helper import cfig.helper.Helper
import cfig.helper.KeyHelper
import cfig.helper.KeyHelper2
import com.google.common.math.BigIntegerMath import com.google.common.math.BigIntegerMath
import org.bouncycastle.jce.provider.BouncyCastleProvider import org.bouncycastle.jce.provider.BouncyCastleProvider
import org.junit.Assert.assertEquals import org.junit.Assert.assertEquals
@ -36,14 +35,14 @@ class KeyUtilTest {
fun parseKeys() { fun parseKeys() {
val keyFile = "../" + Algorithms.get("SHA256_RSA2048")!!.defaultKey val keyFile = "../" + Algorithms.get("SHA256_RSA2048")!!.defaultKey
println("Key: $keyFile") 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.privateExponent)
println(k.modulus) 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.privateExponent)
println(k2.modulus) println(k2.modulus)
//KeyHelper2.parseRsaPk8(FileInputStream(keyFile).readAllBytes()) //KeyHelper2.parseRsaPk8(FileInputStream(keyFile).readAllBytes())
KeyHelper.parse(File(keyFile.replace("pem", "pk8")).readBytes()) CryptoHelper.KeyBox.parse(File(keyFile.replace("pem", "pk8")).readBytes())
} }
@Test @Test
@ -88,8 +87,8 @@ class KeyUtilTest {
val expectedSig = val expectedSig =
"28e17bc57406650ed78785fd558e7c1861cc4014c900d72b61c03cdbab1039e713b5bb19b556d04d276b46aae9b8a3999ccbac533a1cce00f83cfb83e2beb35ed7329f71ffec04fc2839a9b44e50abd66ea6c3d3bea6705e93e9139ecd0331170db18eba36a85a78bc49a5447260a30ed19d956cb2f8a71f6b19e57fdca43e052d1bb7840bf4c3efb47111f4d77764236d2e013fbf3b2577e4a3e01c9d166a5e890ef96210882e6e88ceca2fe3a2201f4961210d4ec6167f5dfd0e038e4a146f960caecab7d15ba65f6edcf5dbd25f5af543cfb8da4338bdbc872eec3f8e72aa8db679099e70952d3f7176c0b9111bf20ad1390eab1d09a859105816fdf92fbb" "28e17bc57406650ed78785fd558e7c1861cc4014c900d72b61c03cdbab1039e713b5bb19b556d04d276b46aae9b8a3999ccbac533a1cce00f83cfb83e2beb35ed7329f71ffec04fc2839a9b44e50abd66ea6c3d3bea6705e93e9139ecd0331170db18eba36a85a78bc49a5447260a30ed19d956cb2f8a71f6b19e57fdca43e052d1bb7840bf4c3efb47111f4d77764236d2e013fbf3b2577e4a3e01c9d166a5e890ef96210882e6e88ceca2fe3a2201f4961210d4ec6167f5dfd0e038e4a146f960caecab7d15ba65f6edcf5dbd25f5af543cfb8da4338bdbc872eec3f8e72aa8db679099e70952d3f7176c0b9111bf20ad1390eab1d09a859105816fdf92fbb"
val privkFile = "../" + Algorithms.get("SHA256_RSA2048")!!.defaultKey.replace("pem", "pk8") val privkFile = "../" + Algorithms.get("SHA256_RSA2048")!!.defaultKey.replace("pem", "pk8")
val k = KeyHelper.parse(Files.readAllBytes(Paths.get(privkFile))) as PrivateKey val k = CryptoHelper.KeyBox.parse(Files.readAllBytes(Paths.get(privkFile))) as PrivateKey
val encData = KeyHelper2.rawRsa(k, data) val encData = CryptoHelper.Signer.rawRsa(k, data)
assertEquals(expectedSig, Helper.toHexString(encData)) assertEquals(expectedSig, Helper.toHexString(encData))
} }
@ -99,7 +98,7 @@ class KeyUtilTest {
Helper.fromHexString("0001ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff003031300d0609608648016503040201050004206317a4c8d86accc8258c1ac23ef0ebd18bc33010d7afb43b241802646360b4ab") Helper.fromHexString("0001ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff003031300d0609608648016503040201050004206317a4c8d86accc8258c1ac23ef0ebd18bc33010d7afb43b241802646360b4ab")
val expectedSig = val expectedSig =
"28e17bc57406650ed78785fd558e7c1861cc4014c900d72b61c03cdbab1039e713b5bb19b556d04d276b46aae9b8a3999ccbac533a1cce00f83cfb83e2beb35ed7329f71ffec04fc2839a9b44e50abd66ea6c3d3bea6705e93e9139ecd0331170db18eba36a85a78bc49a5447260a30ed19d956cb2f8a71f6b19e57fdca43e052d1bb7840bf4c3efb47111f4d77764236d2e013fbf3b2577e4a3e01c9d166a5e890ef96210882e6e88ceca2fe3a2201f4961210d4ec6167f5dfd0e038e4a146f960caecab7d15ba65f6edcf5dbd25f5af543cfb8da4338bdbc872eec3f8e72aa8db679099e70952d3f7176c0b9111bf20ad1390eab1d09a859105816fdf92fbb" "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)) assertEquals(expectedSig, Helper.toHexString(sig))
} }
@ -109,7 +108,7 @@ class KeyUtilTest {
Helper.fromHexString("0001ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff003031300d0609608648016503040201050004206317a4c8d86accc8258c1ac23ef0ebd18bc3301033") Helper.fromHexString("0001ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff003031300d0609608648016503040201050004206317a4c8d86accc8258c1ac23ef0ebd18bc3301033")
val signature = Signature.getInstance("NONEwithRSA") val signature = Signature.getInstance("NONEwithRSA")
val keyFile = "../" + Algorithms.get("SHA256_RSA2048")!!.defaultKey.replace("pem", "pk8") 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.initSign(k)
signature.update(data) signature.update(data)
println("data size " + data.size) println("data size " + data.size)
@ -188,18 +187,18 @@ class KeyUtilTest {
@Test @Test
fun listAll() { fun listAll() {
KeyHelper.listAll() CryptoHelper.listAll()
} }
@Test @Test
fun signData() { fun signData() {
val data = KeyUtilTest::class.java.classLoader.getResourceAsStream("data").readAllBytes() val data = KeyUtilTest::class.java.classLoader.getResourceAsStream("data").readAllBytes()
println(Helper.toHexString(data)) println(Helper.toHexString(data))
val privKey = KeyHelper.parse( val privKey = CryptoHelper.KeyBox.parse(
KeyUtilTest::class.java.classLoader.getResourceAsStream("testkey.pk8").readAllBytes() KeyUtilTest::class.java.classLoader.getResourceAsStream("testkey.pk8").readAllBytes()
) as PrivateKey ) as PrivateKey
println("sha256=" + Helper.toHexString(KeyHelper2.sha256(data))) println("sha256=" + Helper.toHexString(CryptoHelper.Hasher.sha256(data)))
val signedHash = KeyHelper2.sha256rsa(data, privKey) val signedHash = CryptoHelper.Signer.sha256rsa(data, privKey)
println("Signed Hash: " + Helper.toHexString(signedHash)) println("Signed Hash: " + Helper.toHexString(signedHash))
} }
} }

@ -16,13 +16,12 @@ package avb
import avb.alg.Algorithms import avb.alg.Algorithms
import avb.blob.AuxBlob import avb.blob.AuxBlob
import cfig.helper.CryptoHelper
import cfig.helper.Helper import cfig.helper.Helper
import cfig.helper.KeyHelper
import org.apache.commons.codec.binary.Hex import org.apache.commons.codec.binary.Hex
import org.bouncycastle.asn1.pkcs.RSAPrivateKey import org.bouncycastle.asn1.pkcs.RSAPrivateKey
import org.junit.Assert.assertEquals import org.junit.Assert.assertEquals
import org.junit.Test import org.junit.Test
import java.io.ByteArrayInputStream
class BlobTest { class BlobTest {
@Test @Test
@ -34,9 +33,9 @@ class BlobTest {
"00000800c9d87d7bc65551dd3224a2e00ebc7efdbda2538058697ef54a4087959054593d55caff36341afae1e0902a1a32685bf3dfad0bf9b1d0f7eaab471f76be1b984b67a362fadfe6b5f8ee73165fb8b182de4989d53dd7a842998175c8d8847bbd54a8226444bc3406103c89c2d1f32c036591b1a0d1c82156159948202774ef017a76a50b6bfde3faed0df90f7a41fa76053749fe344f4b0149e498f7898ecd36aa391da97d5d6b5a52d17569a8df7cde1c1bf9d9195bb7474cb9702eade5d6887ced926e460810b576033e09ac4db62ccd1200bdd4a703d31b910823365b11feaf5969b33c8824372d61bac599511897f92342969f872ecdb24d5fa924f245dae26526264d1efa3b53abc5d379532f66b82194669a936f3526438c8f96b9ffaccdf4006ebeb673548450854653d5dd43feb26a784079569f86f3d3813b3d409035329a517ff8c34bc7d6a1ca30fb1bfd270ab8644134c117dea1769aebcf0c50d913f50d0b2c9924cbb5b4f8c60ad026b15bfd4d44669db076aa799dc05c3b9436c18ffec9d25a6aa046e1a28b2f516151a3369183b4fbcda9403446988a1a91dfd92c3abf573a464620f2f0bc315e29fe5890325c6697999a8e1523eba947d363c618bb8ed2209f78af71b32e0889a1f17db130a0e61bbd6ec8f633e1dab0bd1641fe076f6e978b6a33d2d780036a4de70582281fef6aa9757ee14ee2955b4fe6dc03b981" "00000800c9d87d7bc65551dd3224a2e00ebc7efdbda2538058697ef54a4087959054593d55caff36341afae1e0902a1a32685bf3dfad0bf9b1d0f7eaab471f76be1b984b67a362fadfe6b5f8ee73165fb8b182de4989d53dd7a842998175c8d8847bbd54a8226444bc3406103c89c2d1f32c036591b1a0d1c82156159948202774ef017a76a50b6bfde3faed0df90f7a41fa76053749fe344f4b0149e498f7898ecd36aa391da97d5d6b5a52d17569a8df7cde1c1bf9d9195bb7474cb9702eade5d6887ced926e460810b576033e09ac4db62ccd1200bdd4a703d31b910823365b11feaf5969b33c8824372d61bac599511897f92342969f872ecdb24d5fa924f245dae26526264d1efa3b53abc5d379532f66b82194669a936f3526438c8f96b9ffaccdf4006ebeb673548450854653d5dd43feb26a784079569f86f3d3813b3d409035329a517ff8c34bc7d6a1ca30fb1bfd270ab8644134c117dea1769aebcf0c50d913f50d0b2c9924cbb5b4f8c60ad026b15bfd4d44669db076aa799dc05c3b9436c18ffec9d25a6aa046e1a28b2f516151a3369183b4fbcda9403446988a1a91dfd92c3abf573a464620f2f0bc315e29fe5890325c6697999a8e1523eba947d363c618bb8ed2209f78af71b32e0889a1f17db130a0e61bbd6ec8f633e1dab0bd1641fe076f6e978b6a33d2d780036a4de70582281fef6aa9757ee14ee2955b4fe6dc03b981"
assertEquals(expectedKeyEnc, Helper.toHexString(encodedKey)) assertEquals(expectedKeyEnc, Helper.toHexString(encodedKey))
run {//decode pub key and check 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.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.modulus.equals(decodedKey.modulus))
assert(rsa.publicExponent.equals(decodedKey.publicExponent)) assert(rsa.publicExponent.equals(decodedKey.publicExponent))
} }

@ -14,7 +14,7 @@
package avb.desc package avb.desc
import cfig.helper.KeyHelper2 import cfig.helper.CryptoHelper
import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.databind.ObjectMapper
import org.apache.commons.codec.binary.Hex import org.apache.commons.codec.binary.Hex
import org.junit.Assert.assertEquals import org.junit.Assert.assertEquals
@ -46,7 +46,7 @@ class HashTreeDescriptorTest {
@Test @Test
fun x1() { fun x1() {
HashTreeDescriptor.calcMerkleTree(120721408, 4096, 32) HashTreeDescriptor.calcMerkleTree(120721408, 4096, 32)
println(MessageDigest.getInstance(KeyHelper2.pyAlg2java("sha1")).digest().size) println(MessageDigest.getInstance(CryptoHelper.Hasher.pyAlg2java("sha1")).digest().size)
println(MessageDigest.getInstance(KeyHelper2.pyAlg2java("sha256")).digest().size) println(MessageDigest.getInstance(CryptoHelper.Hasher.pyAlg2java("sha256")).digest().size)
} }
} }

@ -73,6 +73,16 @@ tasks {
} }
pullTask.dependsOn("bbootimg:jar") 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 //sparse image dependencies
if (bHackingMode) { if (bHackingMode) {
logger.info("Hacking mode!") logger.info("Hacking mode!")

@ -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

@ -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

@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists 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 zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists

@ -15,8 +15,9 @@
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
plugins { plugins {
id("org.jetbrains.kotlin.jvm") version "1.5.31" id("org.jetbrains.kotlin.jvm") version "1.6.0"
`java-library` `java-library`
application
} }
repositories { repositories {
@ -33,18 +34,19 @@ dependencies {
implementation("org.jetbrains.kotlin:kotlin-reflect") implementation("org.jetbrains.kotlin:kotlin-reflect")
implementation("com.google.guava:guava:18.0") implementation("com.google.guava:guava:18.0")
implementation("org.slf4j:slf4j-api:1.7.31") implementation("org.slf4j:slf4j-api:1.7.32")
implementation("org.slf4j:slf4j-simple:1.7.31") implementation("org.slf4j:slf4j-simple:1.7.32")
implementation("org.apache.commons:commons-exec:1.3") implementation("org.apache.commons:commons-exec:1.3")
implementation("org.bouncycastle:bcprov-jdk15on:1.69") 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("org.tukaani:xz:1.9")
implementation("com.github.freva:ascii-table:1.2.0") implementation("com.github.freva:ascii-table:1.2.0")
testImplementation("org.jetbrains.kotlin:kotlin-test") testImplementation("org.jetbrains.kotlin:kotlin-test")
testImplementation("org.jetbrains.kotlin:kotlin-test-junit") testImplementation("org.jetbrains.kotlin:kotlin-test-junit")
testImplementation("com.fasterxml.jackson.core:jackson-annotations:2.12.3") testImplementation("com.fasterxml.jackson.core:jackson-annotations:2.13.0")
testImplementation("com.fasterxml.jackson.core:jackson-databind:2.12.3") testImplementation("com.fasterxml.jackson.core:jackson-databind:2.13.0")
} }
tasks.withType<KotlinCompile>().all { tasks.withType<KotlinCompile>().all {
@ -54,3 +56,24 @@ tasks.withType<KotlinCompile>().all {
jvmTarget = "11" 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
}
}
}

@ -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 <file>
*/
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")
}
}
}
}

@ -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")
}
}
}
}

@ -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 <file>
*/
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)
}
}
}

@ -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 <file>")
println("\tcrypto.key.genrsa <key_len> <out>")
println("\tcrypto.key.1 <file>")
}
}
}
fun main(args: Array<String>) {
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
}

@ -39,9 +39,11 @@ class OpenSslHelper {
} }
} }
class PK1Key(val format: KeyFormat = KeyFormat.PEM, class PK1Key(
override val data: ByteArray = byteArrayOf(), val format: KeyFormat = KeyFormat.PEM,
override val name: String = "RSA Private") : IKey { override val data: ByteArray = byteArrayOf(),
override val name: String = "RSA Private"
) : IKey {
/* /*
PEM private key -> PEM/DER public key PEM private key -> PEM/DER public key
*/ */
@ -49,8 +51,10 @@ class OpenSslHelper {
if (format != KeyFormat.PEM) { if (format != KeyFormat.PEM) {
throw IllegalArgumentException("can not handle $format private key") throw IllegalArgumentException("can not handle $format private key")
} }
val ret = Helper.powerRun("openssl rsa -in $stdin -pubout -outform ${pubKeyFormat.name}", val ret = Helper.powerRun(
ByteArrayInputStream(data)) "openssl rsa -in $stdin -pubout -outform ${pubKeyFormat.name}",
ByteArrayInputStream(data)
)
log.info("privateToPublic:stderr: ${String(ret[1])}") log.info("privateToPublic:stderr: ${String(ret[1])}")
return PK1PubKey(format = pubKeyFormat, data = ret[0]) return PK1PubKey(format = pubKeyFormat, data = ret[0])
} }
@ -65,8 +69,10 @@ class OpenSslHelper {
if (this.format != KeyFormat.PEM) { if (this.format != KeyFormat.PEM) {
throw java.lang.IllegalArgumentException("Only PEM key is supported") throw java.lang.IllegalArgumentException("Only PEM key is supported")
} }
val ret = Helper.powerRun2("openssl rsa -in $stdin -pubout", val ret = Helper.powerRun2(
ByteArrayInputStream(data)) "openssl rsa -in $stdin -pubout",
ByteArrayInputStream(data)
)
if (ret[0] as Boolean) { if (ret[0] as Boolean) {
log.info("getPk8PublicKey:error: ${String(ret[2] as ByteArray)}") log.info("getPk8PublicKey:error: ${String(ret[2] as ByteArray)}")
return Pk8PubKey(KeyFormat.PEM, ret[1] as ByteArray) return Pk8PubKey(KeyFormat.PEM, ret[1] as ByteArray)
@ -84,16 +90,18 @@ class OpenSslHelper {
openssl pkcs8 -nocrypt -in - -topk8 -outform DER openssl pkcs8 -nocrypt -in - -topk8 -outform DER
*/ */
fun toPk8(pk8Format: KeyFormat): PK8RsaKey { fun toPk8(pk8Format: KeyFormat): PK8RsaKey {
val ret = Helper.powerRun("openssl pkcs8 -nocrypt -in $stdin -topk8 -outform ${pk8Format.name}", val ret = Helper.powerRun(
ByteArrayInputStream(data)) "openssl pkcs8 -nocrypt -in $stdin -topk8 -outform ${pk8Format.name}",
ByteArrayInputStream(data)
)
log.info("toPk8Private:stderr: ${String(ret[1])}") log.info("toPk8Private:stderr: ${String(ret[1])}")
return PK8RsaKey(format = pk8Format, data = ret[0]) return PK8RsaKey(format = pk8Format, data = ret[0])
} }
fun toCsr(): Csr { fun toCsr(info: String? = null): Csr {
val info = "/C=CN/ST=Shanghai/L=Shanghai/O=XXX/OU=infra/CN=gerrit/emailAddress=webmaster@XX.com" 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 { 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)) val ret = Helper.powerRun3(cmdLine, ByteArrayInputStream(data))
if (ret[0] as Boolean) { if (ret[0] as Boolean) {
@ -117,8 +125,10 @@ class OpenSslHelper {
val tmpFile = File.createTempFile("pk1.", ".csr") val tmpFile = File.createTempFile("pk1.", ".csr")
tmpFile.writeBytes(csr.data) tmpFile.writeBytes(csr.data)
tmpFile.deleteOnExit() tmpFile.deleteOnExit()
val ret = Helper.powerRun2("openssl x509 -req -in ${tmpFile.path} -signkey $stdin -days 180", val ret = Helper.powerRun2(
ByteArrayInputStream(data)) "openssl x509 -req -in ${tmpFile.path} -signkey $stdin -days 180",
ByteArrayInputStream(data)
)
if (ret[0] as Boolean) { if (ret[0] as Boolean) {
log.info("toCrt:error: ${String(ret[2] as ByteArray)}") log.info("toCrt:error: ${String(ret[2] as ByteArray)}")
return Crt(ret[1] as ByteArray) return Crt(ret[1] as ByteArray)
@ -141,9 +151,11 @@ class OpenSslHelper {
} }
} }
class PK8RsaKey(val format: KeyFormat = KeyFormat.PEM, class PK8RsaKey(
override val data: ByteArray = byteArrayOf(), val format: KeyFormat = KeyFormat.PEM,
override val name: String = "PK8 Private") : IKey { override val data: ByteArray = byteArrayOf(),
override val name: String = "PK8 Private"
) : IKey {
/* /*
file based: file based:
@ -157,8 +169,10 @@ class OpenSslHelper {
if (this.format != KeyFormat.PEM) { if (this.format != KeyFormat.PEM) {
throw IllegalArgumentException("Only pk8+pem can be converted to RSA") throw IllegalArgumentException("Only pk8+pem can be converted to RSA")
} }
val ret = Helper.powerRun2("openssl rsa -in $stdin", val ret = Helper.powerRun2(
ByteArrayInputStream(data)) "openssl rsa -in $stdin",
ByteArrayInputStream(data)
)
if (ret[0] as Boolean) { if (ret[0] as Boolean) {
log.info("toRsaPrivate:error: ${String(ret[2] as ByteArray)}") log.info("toRsaPrivate:error: ${String(ret[2] as ByteArray)}")
return PK1Key(KeyFormat.PEM, ret[1] as ByteArray) return PK1Key(KeyFormat.PEM, ret[1] as ByteArray)
@ -173,8 +187,10 @@ class OpenSslHelper {
openssl pkcs8 -nocrypt -in - -inform DER openssl pkcs8 -nocrypt -in - -inform DER
*/ */
fun transform(inFormat: KeyFormat, outFormat: KeyFormat): PK8RsaKey { fun transform(inFormat: KeyFormat, outFormat: KeyFormat): PK8RsaKey {
val ret = Helper.powerRun2("openssl pkcs8 -nocrypt -in $stdin -inform ${inFormat.name} -outform ${outFormat.name}", val ret = Helper.powerRun2(
ByteArrayInputStream(data)) "openssl pkcs8 -nocrypt -in $stdin -inform ${inFormat.name} -outform ${outFormat.name}",
ByteArrayInputStream(data)
)
if (ret[0] as Boolean) { if (ret[0] as Boolean) {
log.info("transform:error: ${String(ret[2] as ByteArray)}") log.info("transform:error: ${String(ret[2] as ByteArray)}")
return PK8RsaKey(data = ret[1] as ByteArray) return PK8RsaKey(data = ret[1] as ByteArray)
@ -195,8 +211,10 @@ class OpenSslHelper {
if (this.format != KeyFormat.PEM) { if (this.format != KeyFormat.PEM) {
throw java.lang.IllegalArgumentException("Only PEM key is supported") throw java.lang.IllegalArgumentException("Only PEM key is supported")
} }
val ret = Helper.powerRun2("openssl rsa -in $stdin -pubout", val ret = Helper.powerRun2(
ByteArrayInputStream(data)) "openssl rsa -in $stdin -pubout",
ByteArrayInputStream(data)
)
if (ret[0] as Boolean) { if (ret[0] as Boolean) {
log.info("getPublicKey:error: ${String(ret[2] as ByteArray)}") log.info("getPublicKey:error: ${String(ret[2] as ByteArray)}")
return Pk8PubKey(KeyFormat.PEM, ret[1] as ByteArray) return Pk8PubKey(KeyFormat.PEM, ret[1] as ByteArray)
@ -209,15 +227,15 @@ class OpenSslHelper {
} }
class PK1PubKey( class PK1PubKey(
val format: KeyFormat = KeyFormat.PEM, val format: KeyFormat = KeyFormat.PEM,
override val data: ByteArray = byteArrayOf(), override val data: ByteArray = byteArrayOf(),
override val name: String = "RSA Public" override val name: String = "RSA Public"
) : IKey ) : IKey
class Pk8PubKey( class Pk8PubKey(
val format: KeyFormat = KeyFormat.PEM, val format: KeyFormat = KeyFormat.PEM,
override val data: ByteArray = byteArrayOf(), override val data: ByteArray = byteArrayOf(),
override val name: String = "Pk8 Public" override val name: String = "Pk8 Public"
) : IKey ) : IKey
class Csr(override val name: String = "CSR", override val data: ByteArray = byteArrayOf()) : 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") { fun check(passWord: String = "somepassword") {
val tmpFile = File.createTempFile("tmp.", ".jks").apply { this.deleteOnExit() } val tmpFile = File.createTempFile("tmp.", ".jks").apply { this.deleteOnExit() }
tmpFile.writeBytes(this.data) tmpFile.writeBytes(this.data)
val ret = Helper.powerRun2("keytool -list -v -deststorepass $passWord -keystore $tmpFile", val ret = Helper.powerRun2(
null) "keytool -list -v -deststorepass $passWord -keystore $tmpFile",
null
)
if (ret[0] as Boolean) { if (ret[0] as Boolean) {
log.info("Jks.check:stdout: ${String(ret[1] as ByteArray)}") log.info("Jks.check:stdout: ${String(ret[1] as ByteArray)}")
log.info("Jks.check:error: ${String(ret[2] 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 //Result: trustedCertEntry
//keytool -importcert -file 2017key.crt -deststorepass somepassword -srcstorepass somepassword -keystore 2017key.2.jks //keytool -importcert -file 2017key.crt -deststorepass somepassword -srcstorepass somepassword -keystore 2017key.2.jks
fun toJks(paramSrcPass: String = "somepassword", paramDstPass: String = "somepassword"): Jks { fun toJks(paramSrcPass: String = "somepassword", paramDstPass: String = "somepassword"): Jks {
val crtFile = File.createTempFile("tmp.", ".crt").apply { this.deleteOnExit() } val crtFile = File.createTempFile("tmp.", ".crt").apply { this.deleteOnExit() }
crtFile.writeBytes(this.data) crtFile.writeBytes(this.data)
val outFile = File.createTempFile("tmp.", ".jks").apply { this.delete() } val outFile = File.createTempFile("tmp.", ".jks").apply { this.delete() }
val ret = Helper.powerRun2("keytool -importcert -file ${crtFile.path}" + val ret = Helper.powerRun2(
" -deststorepass $paramDstPass -srcstorepass $paramSrcPass " + "keytool -importcert -file ${crtFile.path}" +
" -keystore ${outFile.path}", " -deststorepass $paramDstPass -srcstorepass $paramSrcPass " +
ByteArrayInputStream("yes\n".toByteArray())) " -keystore ${outFile.path}",
ByteArrayInputStream("yes\n".toByteArray())
)
if (ret[0] as Boolean) { if (ret[0] as Boolean) {
log.info("toJks:error: ${String(ret[2] as ByteArray)}") log.info("toJks:error: ${String(ret[2] as ByteArray)}")
log.info("toJks:stdout: ${String(ret[1] as ByteArray)}") log.info("toJks:stdout: ${String(ret[1] as ByteArray)}")
@ -268,9 +290,11 @@ class OpenSslHelper {
} }
} }
class Pfx(override val name: String = "androiddebugkey", class Pfx(
var thePassword: String = "somepassword", override val name: String = "androiddebugkey",
override var data: ByteArray = byteArrayOf()) : IKey { var thePassword: String = "somepassword",
override var data: ByteArray = byteArrayOf()
) : IKey {
fun generate(pk1: PK1Key, crt: Crt) { fun generate(pk1: PK1Key, crt: Crt) {
val pk1File = File.createTempFile("tmp.", ".file").apply { this.deleteOnExit() } val pk1File = File.createTempFile("tmp.", ".file").apply { this.deleteOnExit() }
pk1File.writeBytes(pk1.data) 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 { fun toJks(): Jks {
val jksFile = File.createTempFile("tmp.", ".file").apply { this.delete() } val jksFile = File.createTempFile("tmp.", ".file").apply { this.delete() }
val thisFile = File.createTempFile("tmp.", ".file").apply { this.deleteOnExit() } val thisFile = File.createTempFile("tmp.", ".file").apply { this.deleteOnExit() }
@ -324,16 +348,6 @@ class OpenSslHelper {
private val log = LoggerFactory.getLogger(OpenSslHelper::class.java) private val log = LoggerFactory.getLogger(OpenSslHelper::class.java)
val stdin = if (System.getProperty("os.name").contains("Mac")) "/dev/stdin" else "-" 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) { fun toPfx(password: String = "somepassword", keyName: String = "androiddebugkey", pk1: PK1Key, crt: Crt) {
val pk1File = File.createTempFile("tmp.", ".file").apply { this.deleteOnExit() } val pk1File = File.createTempFile("tmp.", ".file").apply { this.deleteOnExit() }
pk1File.writeBytes(pk1.data) pk1File.writeBytes(pk1.data)
@ -342,7 +356,8 @@ class OpenSslHelper {
crtFile.writeBytes(crt.data) crtFile.writeBytes(crt.data)
//openssl pkcs12 -export -out $(pfx_cert) -inkey $(rsa_key) -in $(crt_file) -password pass:$(thePassword) -name $(thePfxName) //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) val ret = Helper.powerRun2(cmd, null)
if (ret[0] as Boolean) { if (ret[0] as Boolean) {
log.info("toPfx:error: ${String(ret[2] as ByteArray)}") log.info("toPfx:error: ${String(ret[2] as ByteArray)}")

@ -14,7 +14,6 @@
package cfig.helper package cfig.helper
import cfig.helper.OpenSslHelper.Companion.decodePem
import org.junit.Test import org.junit.Test
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
import java.io.File import java.io.File
@ -36,7 +35,7 @@ class OpenSslHelperTest {
writeTo("2_rsa_pub.pem.key") writeTo("2_rsa_pub.pem.key")
} }
//Action-3: RSA public key(PEM) --> RSA public key(DER) //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) //Action-2: private RSA key -> RSA public key(DER)
val rsaPubDer = rsa.getPublicKey(OpenSslHelper.KeyFormat.DER).apply { val rsaPubDer = rsa.getPublicKey(OpenSslHelper.KeyFormat.DER).apply {
@ -66,18 +65,18 @@ class OpenSslHelperTest {
run { //check equality: 8 == 4,5 run { //check equality: 8 == 4,5
val pk8Pub = rsa.getPk8PublicKey() 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() // val pk8Pub2 = rsa.toPk8(OpenSslHelper.KeyFormat.PEM).getPublicKey()
// assert(pk8Pub.data.contentEquals(pk8Pub2.data)) // 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 -> rsa.toPk8(OpenSslHelper.KeyFormat.PEM).let { pk8Pem ->
val shortConversion = pk8Pem.toPk1() val shortConversion = pk8Pem.toPk1()
assert(shortConversion.data.contentEquals(rsa.data)) 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 -> rsa.toPk8(OpenSslHelper.KeyFormat.DER).let { pk8der ->
val longConversion = pk8der val longConversion = pk8der
.transform(OpenSslHelper.KeyFormat.DER, OpenSslHelper.KeyFormat.PEM) //pk8 PEM .transform(OpenSslHelper.KeyFormat.DER, OpenSslHelper.KeyFormat.PEM) //pk8 PEM

@ -71,6 +71,10 @@ def verifySingleJson(jsonFile, func = None):
for k, v in verifyItems["hash"].items(): for k, v in verifyItems["hash"].items():
log.info("%s : %s" % (k, v)) log.info("%s : %s" % (k, v))
unittest.TestCase().assertEqual(v, hashFile(k)) unittest.TestCase().assertEqual(v, hashFile(k))
try:
subprocess.check_call(gradleWrapper + " clean", shell = True)
except Exception as e:
pass
def verifySingleDir(inResourceDir, inImageDir): def verifySingleDir(inResourceDir, inImageDir):
resDir = inResourceDir resDir = inResourceDir

Loading…
Cancel
Save