Issue #79: add "gradle clean" command

pull/94/head
cfig 3 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
- 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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

@ -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<KotlinCompile>().all {
@ -54,3 +56,24 @@ tasks.withType<KotlinCompile>().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
}
}
}

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

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

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

Loading…
Cancel
Save