Issue #79: add "gradle clean" command
parent
9123bae511
commit
5cef10203b
@ -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
|
||||
}
|
||||
}
|
@ -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
|
||||
|
@ -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
|
||||
}
|
Loading…
Reference in New Issue