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
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-bin.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-7.3-bin.zip
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
|
@ -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