lots of stuff

- extract helper for more projects
 - kotlin 1.4.31
 - gradle 6.8.3
 - vendor_boot flash/pull
 - fix comanion vbmeta update: boot.img, vendor_boot.img
 - refine libavb
pull/53/head
cfig 4 years ago
parent 50273a9085
commit 6c662a54da
No known key found for this signature in database
GPG Key ID: B104C307F0FDABB7

@ -19,8 +19,8 @@ addons:
- python-all
before_install:
- if [ "$TRAVIS_OS_NAME" = "osx" ]; then brew update ; fi
- if [ "$TRAVIS_OS_NAME" = "osx" ]; then brew install lz4 dtc gradle; fi
- if [ "$TRAVIS_OS_NAME" = "osx" ]; then brew install lz4 dtc ; fi
before_script:
- ./gradlew check && ./gradlew clean
script:
- ./gradlew check
- ./gradlew clean
- ./integrationTest.py
- ./integrationTest.py

@ -1,5 +1,5 @@
# Android_boot_image_editor
[![Build Status](https://travis-ci.org/cfig/Android_boot_image_editor.svg?branch=master)](https://travis-ci.org/cfig/Android_boot_image_editor)
[![Build Status](https://www.travis-ci.com/cfig/Android_boot_image_editor.svg?branch=master)](https://www.travis-ci.com/cfig/Android_boot_image_editor)
[![License](http://img.shields.io/:license-apache-blue.svg?style=flat-square)](http://www.apache.org/licenses/LICENSE-2.0.html)
A tool for reverse engineering Android ROM images.
@ -10,7 +10,7 @@ A tool for reverse engineering Android ROM images.
Mac: `brew install lz4 xz`
Linux: `sudo apt install device-tree-compiler lz4 xz zlib1g-dev openjdk-11-jdk`
Linux: `sudo apt install git device-tree-compiler lz4 xz-utils zlib1g-dev openjdk-11-jdk gcc g++ python3`
Windows: Make sure you have `python3`, `JDK9+` and `openssl` properly installed.
An easy way is to install [Anaconda](https://www.anaconda.com/products/individual#windows) and [Oracle JDK 11](https://www.oracle.com/java/technologies/javase-jdk11-downloads.html), then run the program under anaconda PowerShell.
@ -60,7 +60,6 @@ Well done you did it! The last step is to star this repo :smile
| boot images | boot.img, vendor_boot.img | |
| recovery images | recovery.img, recovery-two-step.img | |
| vbmeta images | vbmeta.img, vbmeta_system.img etc. | |
| sparse images | system.img, vendor.img etc. | |
| dtbo images | dtbo.img | |
Please note that the boot.img MUST follows AOSP verified boot flow, either [Boot image signature](https://source.android.com/security/verifiedboot/verified-boot#signature_format) in VBoot 1.0 or [AVB HASH footer](https://android.googlesource.com/platform/external/avb/+/master/README.md#The-VBMeta-struct) (a.k.a. AVB) in VBoot 2.0.
@ -106,20 +105,6 @@ cp <your_vbmeta_image> vbmeta.img
```
Your boot.img.signed and vbmeta.img.signd will be updated together.
* sparse vendor.img
```bash
cp <your_vendor_image> vendor.img
./gradlew unpack
./gradlew pack
```
You get vendor.img.unsparse, then you can mount it.
```bash
mkdir mnt
sudo mount -o ro vendor.img mnt
```
## boot.img layout
Read [layout](doc/layout.md) of Android boot.img and vendor\_boot.img.

@ -27,6 +27,7 @@
import argparse
import binascii
import bisect
import binascii
import hashlib
import json
import math
@ -577,6 +578,7 @@ def verify_vbmeta_signature(vbmeta_header, vbmeta_blob):
ha.update(header_blob)
ha.update(aux_blob)
computed_digest = ha.digest()
print("computed %s hash : %s" % (alg.hash_name, binascii.hexlify(computed_digest)))
if computed_digest != digest_blob:
return False
@ -586,6 +588,7 @@ def verify_vbmeta_signature(vbmeta_header, vbmeta_blob):
(num_bits,) = struct.unpack('!I', pubkey_blob[0:4])
modulus_blob = pubkey_blob[8:8 + num_bits//8]
modulus = decode_long(modulus_blob)
print("modulus = %s" % modulus)
exponent = 65537
# We used to have this:
@ -2528,6 +2531,8 @@ class Avb(object):
if not verify_vbmeta_signature(header, vbmeta_blob):
raise AvbError('Signature check failed for {} vbmeta struct {}'
.format(alg_name, image_filename))
else:
print("Sig check done")
if key_blob:
# The embedded public key is in the auxiliary block at an offset.

@ -69,7 +69,7 @@ def dump_from_release(input_bytes, key):
value = get_from_release(input_bytes, idx, key)
if value:
return value
return value.encode()
idx += len(LINUX_BANNER_PREFIX)
@ -176,16 +176,18 @@ def dump_to_file(f, dump_fn, input_bytes, desc):
if f is not None:
o = decompress_dump(dump_fn, input_bytes)
if o:
if isinstance(o, str):
f.write(o.encode())
else:
f.write(o)
f.write(o)
else:
sys.stderr.write(
"Cannot extract kernel {}".format(desc))
return False
return True
def to_bytes_io(b):
"""
Make b, which is either sys.stdout or sys.stdin, receive bytes as arguments.
"""
return b.buffer if sys.version_info.major == 3 else b
def main():
parser = argparse.ArgumentParser(
@ -197,35 +199,35 @@ def main():
help='Input kernel image. If not specified, use stdin',
metavar='FILE',
type=argparse.FileType('rb'),
default=sys.stdin)
default=to_bytes_io(sys.stdin))
parser.add_argument('--output-configs',
help='If specified, write configs. Use stdout if no file '
'is specified.',
metavar='FILE',
nargs='?',
type=argparse.FileType('wb'),
const=sys.stdout)
const=to_bytes_io(sys.stdout))
parser.add_argument('--output-version',
help='If specified, write version. Use stdout if no file '
'is specified.',
metavar='FILE',
nargs='?',
type=argparse.FileType('wb'),
const=sys.stdout)
const=to_bytes_io(sys.stdout))
parser.add_argument('--output-release',
help='If specified, write kernel release. Use stdout if '
'no file is specified.',
metavar='FILE',
nargs='?',
type=argparse.FileType('wb'),
const=sys.stdout)
const=to_bytes_io(sys.stdout))
parser.add_argument('--output-compiler',
help='If specified, write the compiler information. Use stdout if no file '
'is specified.',
metavar='FILE',
nargs='?',
type=argparse.FileType('wb'),
const=sys.stdout)
const=to_bytes_io(sys.stdout))
parser.add_argument('--tools',
help='Decompression tools to use. If not specified, PATH '
'is searched.',

@ -37,7 +37,7 @@ model {
task v1(type:Exec) {
workingDir "."
environment preloads: "vbmeta boot", requests: "boot dtbo", suffix: ""
environment preloads: "vbmeta boot", requests: "boot dtbo vendor_boot", suffix: ""
commandLine "./build/exe/avbVerifier/avbVerifier"
}

@ -69,9 +69,9 @@ int main(int, char**) {
}
}
bool isDeviceLocked = true;
cfigOps.avb_ops_.read_is_device_unlocked(NULL, &isDeviceLocked);
if (isDeviceLocked) {
bool isDeviceUnlocked = false;
cfigOps.avb_ops_.read_is_device_unlocked(NULL, &isDeviceUnlocked);
if (isDeviceUnlocked) {
flags |= AVB_SLOT_VERIFY_FLAGS_ALLOW_VERIFICATION_ERROR;
}
std::cout << "[" << __FUNCTION__ << "]: flags: " << flags << std::endl;
@ -94,6 +94,28 @@ int main(int, char**) {
std::cout << "Run:\n python -m json.tool " << outFile << std::endl;
}
if (slotData) { avb_slot_verify_data_free(slotData); }
std::cerr << "\n\tVerify Result: " << toString(result) << std::endl;
std::cout << "\n\tVerify Result: " << toString(result) << std::endl;
if (isDeviceUnlocked) {
switch (result) {
case AVB_SLOT_VERIFY_RESULT_OK:
std::cout << "\tVerify Flow: [orange] continue";
break;
case AVB_SLOT_VERIFY_RESULT_ERROR_VERIFICATION:
case AVB_SLOT_VERIFY_RESULT_ERROR_PUBLIC_KEY_REJECTED:
case AVB_SLOT_VERIFY_RESULT_ERROR_ROLLBACK_INDEX:
std::cout << "\tVerify Flow: [orange] allowed errors found: " << toString(result) << std::endl;
break;
default:
std::cout<< "\tVerify Flow: [orange] but fatal errors found" << std::endl;
}
} else {
switch (result) {
case AVB_SLOT_VERIFY_RESULT_OK:
std::cout << "\tVerify Flow: [green] continue";
break;
default:
std::cout << "\tVerify Flow: [?????] halt";
}
}
return 0;
}

@ -44,11 +44,10 @@ static AvbIOResult read_is_device_unlockedX(AvbOps *, bool *out_is_unlocked) {
std::string line = read_line(lockStatusFile);
if ("0" == line) {
*out_is_unlocked = true;
std::cout << "[" << __FUNCTION__ << "], device is unlocked" << std::endl;
} else {
*out_is_unlocked = false;
std::cout << "[" << __FUNCTION__ << "], device is locked" << std::endl;
}
std::cout << "[" << __FUNCTION__ << "], device is " << ((*out_is_unlocked) ? "unlocked" : "locked") << std::endl;
return AVB_IO_RESULT_OK;
}
@ -152,16 +151,16 @@ static AvbIOResult get_size_of_partitionX(AvbOps *,
auto file_size = get_file_size(partitionFile.c_str());
if (-1 == file_size) {
std::cout << "[" << __FUNCTION__ << "(" << partition << ")]: ";
std::cout << ": error when accessing file [" << partitionFile << "]" << std::endl;
std::cout << "error when accessing file [" << partitionFile << "]" << std::endl;
return AVB_IO_RESULT_ERROR_IO;
} else {
std::cout << "[" << __FUNCTION__ << "(" << partition << ")]: ";
std::cout << ": partition " << partitionFile << " size: " << file_size << std::endl;
std::cout << "partition " << partitionFile << " size: " << file_size << std::endl;
if (out_size_num_bytes != nullptr) {
*out_size_num_bytes = file_size;
} else {
std::cerr << "[" << __FUNCTION__ << "(" << partition << ")]: ";
std::cerr << ": size is not passed back" << std::endl;
std::cerr << "size is not passed back" << std::endl;
}
}
return AVB_IO_RESULT_OK;
@ -215,7 +214,7 @@ static AvbIOResult read_from_partitionX(AvbOps *,
return AVB_IO_RESULT_ERROR_IO;
}
ssize_t num_read = read(fd, buffer, num_bytes);
if (num_read < 0 || num_read != num_bytes) {
if (num_read < 0) {
fprintf(stderr,
"[%s()]: Error reading %zd bytes from pos %" PRId64 " in file %s: %s\n",
__FUNCTION__,
@ -230,11 +229,21 @@ static AvbIOResult read_from_partitionX(AvbOps *,
if (out_num_read != nullptr) {
*out_num_read = num_read;
}
fprintf(stdout,
"[%s()]: Read %ld bytes from partition %s\n",
__FUNCTION__,
num_read,
partition);
if (num_read != num_bytes) {
fprintf(stderr,
"[%s()]: read fewer bytes from pos %" PRId64 " in file %s: exp=%zd, act=%zd\n",
__FUNCTION__,
offset,
partitionFile.c_str(),
num_bytes,
num_read);
} else {
fprintf(stdout,
"[%s()]: Read %ld bytes from partition %s\n",
__FUNCTION__,
num_read,
partition);
}
// cout << hexStr((unsigned char *) buffer, num_read) << endl;
return AVB_IO_RESULT_OK;

@ -1,7 +1,7 @@
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
plugins {
kotlin("jvm") version "1.4.21"
kotlin("jvm") version "1.4.31"
application
}
@ -12,19 +12,19 @@ repositories {
dependencies {
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
implementation("org.jetbrains.kotlin:kotlin-reflect")
implementation("org.slf4j:slf4j-simple:1.7.30")
implementation("org.slf4j:slf4j-api:1.7.30")
implementation("com.fasterxml.jackson.core:jackson-annotations:2.11.3")
implementation("com.fasterxml.jackson.core:jackson-databind:2.11.3")
implementation("com.fasterxml.jackson.core:jackson-annotations:2.12.1")
implementation("com.fasterxml.jackson.core:jackson-databind:2.12.1")
implementation("com.google.guava:guava:18.0")
implementation("org.apache.commons:commons-exec:1.3")
implementation("org.apache.commons:commons-compress:1.20")
implementation("org.tukaani:xz:1.8")
implementation("commons-codec:commons-codec:1.15")
implementation("junit:junit:4.12")
implementation("org.bouncycastle:bcprov-jdk15on:1.57")
implementation("org.bouncycastle:bcprov-jdk15on:1.68")
implementation("de.vandermeer:asciitable:0.3.2")
implementation(project(":helper"))
testImplementation("org.jetbrains.kotlin:kotlin-test")
testImplementation("org.jetbrains.kotlin:kotlin-test-junit")
@ -46,6 +46,7 @@ tasks {
}
from(configurations.runtimeClasspath.get().map({ if (it.isDirectory) it else zipTree(it) }))
excludes.addAll(mutableSetOf("META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA"))
dependsOn(":helper:jar")
}
test {
testLogging {

@ -1,47 +0,0 @@
package cfig
import org.bouncycastle.asn1.pkcs.RSAPrivateKey
import org.bouncycastle.util.io.pem.PemReader
import java.io.InputStream
import java.io.InputStreamReader
import java.math.BigInteger
import java.security.KeyFactory
import java.security.PrivateKey
import java.security.PublicKey
import java.security.spec.PKCS8EncodedKeySpec
import java.security.spec.RSAPrivateKeySpec
import java.security.spec.RSAPublicKeySpec
class KeyUtil {
companion object {
@Throws(IllegalArgumentException::class)
fun parsePemPrivateKey(inputStream: InputStream): RSAPrivateKey {
val p = PemReader(InputStreamReader(inputStream)).readPemObject()
if ("RSA PRIVATE KEY" != p.type) {
throw IllegalArgumentException("input is not valid 'RSA PRIVATE KEY'")
}
return RSAPrivateKey.getInstance(p.content)
}
fun parsePemPrivateKey2(inputStream: InputStream): PrivateKey {
val rsa = parsePemPrivateKey(inputStream)
return generateRsaPrivateKey(rsa.modulus, rsa.privateExponent)
}
@Throws(Exception::class)
fun parsePk8PrivateKey(inputData: ByteArray): PrivateKey {
val spec = PKCS8EncodedKeySpec(inputData)
return KeyFactory.getInstance("RSA").generatePrivate(spec)
}
@Throws(Exception::class)
private fun generateRsaPublicKey(modulus: BigInteger, publicExponent: BigInteger): PublicKey {
return KeyFactory.getInstance("RSA").generatePublic(RSAPublicKeySpec(modulus, publicExponent))
}
@Throws(Exception::class)
private fun generateRsaPrivateKey(modulus: BigInteger, privateExponent: BigInteger): PrivateKey {
return KeyFactory.getInstance("RSA").generatePrivate(RSAPrivateKeySpec(modulus, privateExponent))
}
}
}

@ -9,6 +9,7 @@ import avb.blob.Header
import avb.desc.*
import cfig.helper.Helper
import cfig.helper.Helper.Companion.paddingWith
import cfig.helper.KeyHelper
import cfig.io.Struct3
import com.fasterxml.jackson.databind.ObjectMapper
import org.apache.commons.codec.binary.Hex
@ -30,10 +31,12 @@ class Avb {
private val DEBUG = false
//migrated from: avbtool::Avb::addHashFooter
fun addHashFooter(image_file: String,
partition_size: Long, //aligned by Avb::BLOCK_SIZE
partition_name: String,
newAvbInfo: AVBInfo) {
fun addHashFooter(
image_file: String,
partition_size: Long, //aligned by Avb::BLOCK_SIZE
partition_name: String,
newAvbInfo: AVBInfo
) {
log.info("addHashFooter($image_file) ...")
imageSizeCheck(partition_size, image_file)
@ -115,8 +118,10 @@ class Avb {
}
footer?.let {
FileOutputStream(File(image_file), true).channel.use { fc ->
log.info("original image $image_file has AVB footer, " +
"truncate it to original SIZE: ${it.originalImageSize}")
log.info(
"original image $image_file has AVB footer, " +
"truncate it to original SIZE: ${it.originalImageSize}"
)
fc.truncate(it.originalImageSize)
}
}
@ -126,8 +131,10 @@ class Avb {
//image size sanity check
val maxMetadataSize = MAX_VBMETA_SIZE + MAX_FOOTER_SIZE
if (partition_size < maxMetadataSize) {
throw IllegalArgumentException("Parition SIZE of $partition_size is too small. " +
"Needs to be at least $maxMetadataSize")
throw IllegalArgumentException(
"Parition SIZE of $partition_size is too small. " +
"Needs to be at least $maxMetadataSize"
)
}
val maxImageSize = partition_size - maxMetadataSize
log.info("max_image_size: $maxImageSize")
@ -135,14 +142,18 @@ class Avb {
//TODO: typical block size = 4096L, from avbtool::Avb::ImageHandler::block_size
//since boot.img is not in sparse format, we are safe to hardcode it to 4096L for now
if (partition_size % BLOCK_SIZE != 0L) {
throw IllegalArgumentException("Partition SIZE of $partition_size is not " +
"a multiple of the image block SIZE 4096")
throw IllegalArgumentException(
"Partition SIZE of $partition_size is not " +
"a multiple of the image block SIZE 4096"
)
}
val originalFileSize = File(image_file).length()
if (originalFileSize > maxImageSize) {
throw IllegalArgumentException("Image size of $originalFileSize exceeds maximum image size " +
"of $maxImageSize in order to fit in a partition size of $partition_size.")
throw IllegalArgumentException(
"Image size of $originalFileSize exceeds maximum image size " +
"of $maxImageSize in order to fit in a partition size of $partition_size."
)
}
}
@ -169,7 +180,7 @@ class Avb {
fis.skip(vbMetaOffset)
vbMetaHeader = Header(fis)
}
log.info(vbMetaHeader.toString())
log.debug(vbMetaHeader.toString())
log.debug(ObjectMapper().writerWithDefaultPrettyPrinter().writeValueAsString(vbMetaHeader))
val authBlockOffset = vbMetaOffset + Header.SIZE
@ -262,6 +273,39 @@ class Avb {
}
}
//FIXME
val declaredAlg = Algorithms.get(ai.header!!.algorithm_type)
if (declaredAlg!!.public_key_num_bytes > 0) {
if (AuxBlob.encodePubKey(declaredAlg).contentEquals(ai.auxBlob!!.pubkey!!.pubkey)) {
log.warn("VERIFY: vbmeta is signed with the same key as us")
val calcHash =
AuthBlob.calcHash(ai.header!!.encode(), ai.auxBlob!!.encode(declaredAlg), declaredAlg.name)
val calcSig = AuthBlob.calcSignature(calcHash, declaredAlg.name)
if (Helper.toHexString(calcHash) != ai.authBlob!!.hash) {
log.error("calculated AuthBlob hash mismatch")
throw IllegalArgumentException("calculated AuthBlob hash mismatch")
} else {
log.info("VERIFY: AuthBlob hash matches")
}
if (Helper.toHexString(calcSig) != ai.authBlob!!.signature) {
log.error("calculated AuthBlob signature mismatch")
throw IllegalArgumentException("calculated AuthBlob signature mismatch")
} else {
log.info("VERIFY: AuthBlob signature matches")
}
} else {
val custPubKey = KeyHelper.decodeRSAkey(ai.auxBlob!!.pubkey!!.pubkey)
log.warn("VERIFY: vbmeta is signed with different key as us")
log.debug("modulus :" + custPubKey.modulus)
log.debug("exponent :" + custPubKey.publicExponent)
}
//FIXME
ai.auxBlob!!.pubkey
} else {
log.debug("no key for current algorithm")
}
//FIXME
if (dumpFile) {
ObjectMapper().writerWithDefaultPrettyPrinter().writeValue(File(jsonFile), ai)
log.info("vbmeta info of [$image_file] has been analyzed")
@ -284,7 +328,8 @@ class Avb {
val headerBlob = ai.header!!.apply {
auxiliary_data_block_size = auxBlob.size.toLong()
authentication_data_block_size = Helper.round_to_multiple(
(alg.hash_num_bytes + alg.signature_num_bytes).toLong(), 64)
(alg.hash_num_bytes + alg.signature_num_bytes).toLong(), 64
)
descriptors_offset = 0
descriptors_size = ai.auxBlob?.descriptorSize?.toLong() ?: 0
@ -348,5 +393,41 @@ class Avb {
throw IllegalArgumentException("$fileName failed integrity check by \"$cmdline\"")
}
}
fun updateVbmeta(fileName: String) {
if (File("vbmeta.img").exists()) {
log.info("Updating vbmeta.img side by side ...")
val partitionName =
ObjectMapper().readValue(File(getJsonFileName(fileName)), AVBInfo::class.java).let {
it.auxBlob!!.hashDescriptors.get(0).partition_name
}
val newHashDesc = Avb().parseVbMeta("$fileName.signed", dumpFile = false)
assert(newHashDesc.auxBlob!!.hashDescriptors.size == 1)
var seq = -1 //means not found
//main vbmeta
ObjectMapper().readValue(File(getJsonFileName("vbmeta.img")), AVBInfo::class.java).apply {
val itr = this.auxBlob!!.hashDescriptors.iterator()
while (itr.hasNext()) {
val itrValue = itr.next()
if (itrValue.partition_name == partitionName) {
log.info("Found $partitionName in vbmeta, update it")
seq = itrValue.sequence
itr.remove()
break
}
}
if (-1 == seq) {
log.warn("main vbmeta doesn't have $partitionName hashDescriptor, skip")
} else {
val hd = newHashDesc.auxBlob!!.hashDescriptors.get(0).apply { this.sequence = seq }
this.auxBlob!!.hashDescriptors.add(hd)
Avb().packVbMetaWithPadding("vbmeta.img", this)
log.info("Updating vbmeta.img side by side (partition=$partitionName, seq=$seq) done")
}
}
} else {
log.info("no companion vbmeta.img")
}
}
}
}

@ -1,24 +1,59 @@
package avb.blob
import avb.alg.Algorithm
import avb.alg.Algorithms
import cfig.helper.Helper
import cfig.helper.KeyHelper
import cfig.helper.KeyHelper2
import cfig.io.Struct3
import org.slf4j.LoggerFactory
import java.nio.file.Files
import java.nio.file.Paths
import java.security.MessageDigest
import java.security.PrivateKey
@OptIn(ExperimentalUnsignedTypes::class)
data class AuthBlob(
var offset: Long = 0,
var size: Long = 0,
var hash: String? = null,
var signature: String? = null) {
var offset: Long = 0,
var size: Long = 0,
var hash: String? = null,
var signature: String? = null
) {
companion object {
fun createBlob(header_data_blob: ByteArray,
aux_data_blob: ByteArray,
algorithm_name: String): ByteArray {
fun calcHash(
header_data_blob: ByteArray,
aux_data_blob: ByteArray,
algorithm_name: String
): ByteArray {
val alg = Algorithms.get(algorithm_name)!!
return if (alg.name == "NONE") {
log.debug("calc hash: NONE")
byteArrayOf()
} else {
MessageDigest.getInstance(Helper.pyAlg2java(alg.hash_name)).apply {
update(header_data_blob)
update(aux_data_blob)
}.digest().apply {
log.debug("calc hash = " + Helper.toHexString(this))
}
}
}
fun calcSignature(hash: ByteArray, algorithm_name: String): ByteArray {
val alg = Algorithms.get(algorithm_name)!!
return if (alg.name == "NONE") {
byteArrayOf()
} else {
val k = KeyHelper.parse(Files.readAllBytes(Paths.get(alg.defaultKey.replace(".pem", ".pk8")))) as PrivateKey
KeyHelper2.rawSign(k, Helper.join(alg.padding, hash))
}
}
fun createBlob(
header_data_blob: ByteArray,
aux_data_blob: ByteArray,
algorithm_name: String
): ByteArray {
val alg = Algorithms.get(algorithm_name)!!
val authBlockSize = Helper.round_to_multiple((alg.hash_num_bytes + alg.signature_num_bytes).toLong(), 64)
if (0L == authBlockSize) {
@ -27,17 +62,8 @@ data class AuthBlob(
}
//hash & signature
var binaryHash: ByteArray = byteArrayOf()
var binarySignature: ByteArray = byteArrayOf()
if (algorithm_name != "NONE") {
val hasher = MessageDigest.getInstance(Helper.pyAlg2java(alg.hash_name))
binaryHash = hasher.apply {
update(header_data_blob)
update(aux_data_blob)
}.digest()
val k = KeyHelper2.parseRsaPk8(Files.readAllBytes(Paths.get(alg.defaultKey.replace(".pem", ".pk8"))))
binarySignature = KeyHelper2.rawSign(k, Helper.join(alg.padding, binaryHash))
}
val binaryHash = calcHash(header_data_blob, aux_data_blob, algorithm_name)
var binarySignature = calcSignature(binaryHash, algorithm_name)
val authData = Helper.join(binaryHash, binarySignature)
return Helper.join(authData, Struct3("${authBlockSize - authData.size}x").pack(0))
}

@ -7,7 +7,9 @@ import cfig.helper.KeyHelper
import cfig.helper.KeyHelper2
import cfig.io.Struct3
import com.fasterxml.jackson.annotation.JsonIgnoreProperties
import org.bouncycastle.asn1.pkcs.RSAPrivateKey
import org.slf4j.LoggerFactory
import java.io.ByteArrayInputStream
import java.nio.file.Files
import java.nio.file.Paths
@ -91,13 +93,13 @@ class AuxBlob(
companion object {
fun encodePubKey(alg: Algorithm, key: ByteArray? = null): ByteArray {
var encodedKey = byteArrayOf()
var algKey: ByteArray? = key
if (alg.public_key_num_bytes > 0) {
var algKey: ByteArray? = key
if (key == null) {
algKey = Files.readAllBytes((Paths.get(alg.defaultKey)))
}
encodedKey = KeyHelper.encodeRSAkey(algKey!!)
log.info("encodePubKey(): size = ${alg.public_key_num_bytes}, algorithm key size: ${encodedKey.size}")
val rsa = KeyHelper.parse(algKey!!) as RSAPrivateKey //BC RSA
encodedKey = KeyHelper.encodeRSAkey(rsa)
assert(alg.public_key_num_bytes == encodedKey.size)
} else {
log.info("encodePubKey(): No key to encode for algorithm " + alg.name)

@ -82,7 +82,7 @@ class UnknownDescriptor(var data: ByteArray = byteArrayOf()) : Descriptor(0, 0,
}
fun parseDescriptors2(stream: InputStream, totalSize: Long): List<Descriptor> {
log.info("Parse descriptors stream, SIZE = $totalSize")
log.debug("Parse descriptors stream, SIZE = $totalSize")
val ret: MutableList<Descriptor> = mutableListOf()
var currentSize = 0L
var seq = 0

@ -124,14 +124,14 @@ class Common {
Files.move(
Paths.get(s.dumpFile), Paths.get(s.dumpFile + ".gz"),
java.nio.file.StandardCopyOption.REPLACE_EXISTING)
ZipHelper.unGnuzipFile(s.dumpFile + ".gz", s.dumpFile)
ZipHelper.zcat(s.dumpFile + ".gz", s.dumpFile)
}
ZipHelper.isLZ4(s.dumpFile) -> {
ZipHelper.isLz4(s.dumpFile) -> {
log.info("ramdisk is compressed lz4")
Files.move(
Paths.get(s.dumpFile), Paths.get(s.dumpFile + ".lz4"),
java.nio.file.StandardCopyOption.REPLACE_EXISTING)
ZipHelper.decompressLZ4Ext(s.dumpFile + ".lz4", s.dumpFile)
ZipHelper.lz4cat(s.dumpFile + ".lz4", s.dumpFile)
ret = "lz4"
}
else -> {
@ -220,10 +220,10 @@ class Common {
}
when {
ramdiskGz.endsWith(".gz") -> {
ZipHelper.gnuZipFile2(ramdiskGz, ByteArrayInputStream(outputStream.toByteArray()))
ZipHelper.minigzip(ramdiskGz, ByteArrayInputStream(outputStream.toByteArray()))
}
ramdiskGz.endsWith(".lz4") -> {
ZipHelper.compressLZ4(ramdiskGz, ByteArrayInputStream(outputStream.toByteArray()))
ZipHelper.lz4(ramdiskGz, ByteArrayInputStream(outputStream.toByteArray()))
}
else -> {
throw IllegalArgumentException("$ramdiskGz is not supported")
@ -240,12 +240,12 @@ class Common {
ramdiskGz.endsWith(".gz") -> {
val f = ramdiskGz.removeSuffix(".gz")
AndroidCpio().pack(rootDir, f, fsConfig)
ZipHelper.gnuZipFile2(ramdiskGz, FileInputStream(f))
ZipHelper.minigzip(ramdiskGz, FileInputStream(f))
}
ramdiskGz.endsWith(".lz4") -> {
val f = ramdiskGz.removeSuffix(".lz4")
AndroidCpio().pack(rootDir, f, fsConfig)
ZipHelper.compressLZ4(ramdiskGz, FileInputStream(f))
ZipHelper.lz4(ramdiskGz, FileInputStream(f))
}
else -> {
throw IllegalArgumentException("$ramdiskGz is not supported")

@ -8,6 +8,7 @@ import org.apache.commons.compress.archivers.cpio.CpioArchiveInputStream
import org.apache.commons.compress.archivers.cpio.CpioConstants
import org.slf4j.LoggerFactory
import java.io.*
import java.nio.charset.Charset
import java.nio.file.Files
import java.nio.file.Paths
import java.util.regex.Pattern
@ -246,18 +247,19 @@ class AndroidCpio {
}
val bytesRead = cis.bytesRead
cis.close()
val fis = FileInputStream(cpioFile)
fis.skip(bytesRead - 128)
val remaining = fis.readAllBytes()
val foundIndex = String(remaining).lastIndexOf("070701")
val remaining = FileInputStream(cpioFile).use { fis ->
fis.skip(bytesRead - 128)
fis.readBytes()
}
val foundIndex = String(remaining, Charsets.UTF_8).lastIndexOf("070701")
val entryInfo = AndroidCpioEntry(
name = CpioConstants.CPIO_TRAILER,
statMode = java.lang.Long.valueOf("755", 8)
)
if (foundIndex != -1) {
entryInfo.statMode =
java.lang.Long.valueOf(String(remaining).substring(foundIndex + 14, foundIndex + 22), 16)
log.info("cpio trailer found, mode=" + String(remaining).substring(foundIndex + 14, foundIndex + 22))
val statusModeStr = String(remaining, Charsets.UTF_8).substring(foundIndex + 14, foundIndex + 22)
entryInfo.statMode = java.lang.Long.valueOf(statusModeStr, 16)
log.info("cpio trailer found, mode=$statusModeStr")
} else {
log.error("no cpio trailer found")
}

@ -1,6 +1,7 @@
package cfig.bootimg.v2
import cfig.Avb
import cfig.EnvironmentVerifier
import cfig.bootimg.Common
import cfig.bootimg.Common.Companion.deleleIfExists
import cfig.bootimg.Common.Slice
@ -20,40 +21,43 @@ import java.nio.ByteOrder
@OptIn(ExperimentalUnsignedTypes::class)
data class BootV2(
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 info: MiscInfo = MiscInfo(),
var kernel: CommArgs = CommArgs(),
var ramdisk: CommArgs = CommArgs(),
var secondBootloader: CommArgs? = null,
var recoveryDtbo: CommArgsLong? = null,
var dtb: 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)
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)
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)
var file: String? = null,
var position: Long = 0,
var size: Int = 0,
var loadOffset: Long = 0
)
companion object {
private val log = LoggerFactory.getLogger(BootV2::class.java)
@ -168,25 +172,31 @@ data class BootV2(
Common.dumpKernel(Slice(info.output, kernel.position.toInt(), kernel.size, kernel.file!!))
//ramdisk
if (this.ramdisk.size > 0) {
val fmt = Common.dumpRamdisk(Slice(info.output, ramdisk.position.toInt(), ramdisk.size, ramdisk.file!!),
"${workDir}root")
val fmt = Common.dumpRamdisk(
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)
Helper.extractFile(
info.output,
secondBootloader!!.file!!,
secondBootloader!!.position,
secondBootloader!!.size
)
}
//recovery dtbo
recoveryDtbo?.let {
Helper.extractFile(info.output,
recoveryDtbo!!.file!!,
recoveryDtbo!!.position,
recoveryDtbo!!.size)
Helper.extractFile(
info.output,
recoveryDtbo!!.file!!,
recoveryDtbo!!.position,
recoveryDtbo!!.size
)
}
//dtb
this.dtb?.let { _ ->
@ -280,32 +290,34 @@ data class BootV2(
""
}
}
log.info("\n\t\t\tUnpack Summary of ${info.output}\n{}\n{}{}",
tableHeader.render(), tab.render(), tabVBMeta)
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
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
)
}
@ -348,12 +360,16 @@ data class BootV2(
Common.hashFileAndSize(kernel.file, ramdisk.file, secondBootloader?.file)
}
1 -> {
Common.hashFileAndSize(kernel.file, ramdisk.file,
secondBootloader?.file, recoveryDtbo?.file)
Common.hashFileAndSize(
kernel.file, ramdisk.file,
secondBootloader?.file, recoveryDtbo?.file
)
}
2 -> {
Common.hashFileAndSize(kernel.file, ramdisk.file,
secondBootloader?.file, recoveryDtbo?.file, dtb?.file)
Common.hashFileAndSize(
kernel.file, ramdisk.file,
secondBootloader?.file, recoveryDtbo?.file, dtb?.file
)
}
else -> {
throw IllegalArgumentException("headerVersion ${info.headerVersion} illegal")
@ -370,23 +386,23 @@ data class BootV2(
log.info("Writing data ...")
val bytesV2 = ByteBuffer.allocate(1024 * 1024 * 64)//assume total SIZE small than 64MB
.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
.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())
@ -404,8 +420,8 @@ data class BootV2(
}
private fun toCommandLine(): CommandLine {
val ret = CommandLine("python")
ret.addArgument(Helper.prop("mkbootimg"))
val cmdPrefix = if (EnvironmentVerifier().isWindows) "python " else ""
val ret = CommandLine(cmdPrefix + Helper.prop("mkbootimg"))
ret.addArgument(" --header_version ")
ret.addArgument(info.headerVersion.toString())
ret.addArgument(" --base ")

@ -1,6 +1,7 @@
package cfig.bootimg.v3
import cfig.Avb
import cfig.EnvironmentVerifier
import cfig.helper.Helper
import cfig.bootimg.Common.Companion.deleleIfExists
import cfig.bootimg.Common.Companion.getPaddingSize
@ -204,8 +205,8 @@ data class BootV3(var info: MiscInfo = MiscInfo(),
}
private fun toCommandLine(): CommandLine {
return CommandLine("python").let { ret ->
ret.addArgument(Helper.prop("mkbootimg"))
val cmdPrefix = if (EnvironmentVerifier().isWindows) "python " else ""
return CommandLine(cmdPrefix + Helper.prop("mkbootimg")).let { ret ->
ret.addArgument("--header_version")
ret.addArgument(info.headerVersion.toString())
if (kernel.size > 0) {

@ -1,93 +0,0 @@
package cfig.helper
import cfig.io.Struct3
import com.google.common.math.BigIntegerMath
import org.apache.commons.codec.binary.Hex
import org.bouncycastle.util.io.pem.PemReader
import org.slf4j.LoggerFactory
import java.io.ByteArrayInputStream
import java.io.InputStream
import java.io.InputStreamReader
import java.math.BigInteger
import java.math.RoundingMode
import java.security.Security
class KeyHelper {
companion object {
private val log = LoggerFactory.getLogger(KeyHelper::class.java)
@Throws(IllegalArgumentException::class)
fun parsePemPubkey(inputStream: InputStream): org.bouncycastle.asn1.pkcs.RSAPublicKey {
val p = PemReader(InputStreamReader(inputStream)).readPemObject()
if ("RSA PUBLIC KEY" != p.type) {
throw IllegalArgumentException("input is not valid 'RSA PUBLIC KEY'")
}
return org.bouncycastle.asn1.pkcs.RSAPublicKey.getInstance(p.content)
}
@Throws(IllegalArgumentException::class)
fun parsePemPrivateKeyBC(inputStream: InputStream): org.bouncycastle.asn1.pkcs.RSAPrivateKey {
val p = PemReader(InputStreamReader(inputStream)).readPemObject()
if ("RSA PRIVATE KEY" != p.type) {
throw IllegalArgumentException("input is not valid 'RSA PRIVATE KEY'")
}
return org.bouncycastle.asn1.pkcs.RSAPrivateKey.getInstance(p.content)
}
// fun parsePemPrivateKey(inputStream: InputStream): java.security.PrivateKey {
// val rsa = BC.parsePemPrivateKeyBC(inputStream)
// return generateRsaPrivateKey(rsa.modulus, rsa.privateExponent)
// }
/*
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(key: ByteArray): ByteArray {
val rsa = parsePemPrivateKeyBC(ByteArrayInputStream(key))
assert(65537.toBigInteger() == rsa.publicExponent)
val numBits: Int = BigIntegerMath.log2(rsa.modulus, RoundingMode.CEILING)
log.debug("modulus: " + rsa.modulus)
log.debug("numBits: $numBits")
val b = BigInteger.valueOf(2).pow(32)
val n0inv = (b - rsa.modulus.modInverse(b)).toLong()
log.debug("n0inv = $n0inv")
val r = BigInteger.valueOf(2).pow(numBits)
val rrModn = (r * r).mod(rsa.modulus)
log.debug("BB: " + numBits / 8 + ", mod_len: " + rsa.modulus.toByteArray().size + ", rrmodn = " + rrModn.toByteArray().size)
val unsignedModulo = rsa.modulus.toByteArray().sliceArray(1..numBits / 8) //remove sign byte
log.debug("unsigned modulo: " + Hex.encodeHexString(unsignedModulo))
val ret = Struct3("!II${numBits / 8}b${numBits / 8}b").pack(
numBits,
n0inv,
unsignedModulo,
rrModn.toByteArray())
log.debug("rrmodn: " + Hex.encodeHexString(rrModn.toByteArray()))
log.debug("RSA: " + Hex.encodeHexString(ret))
return ret
}
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())
}
var i = 0
for (item in Security.getAlgorithms("Cipher")) {
log.info("Cipher: $i -> $item")
i++
}
}
}
}

@ -20,8 +20,8 @@ class KernelExtractor {
val ret: MutableList<String> = mutableListOf()
val kernelVersionFile = Helper.prop("kernelVersionFile")
val kernelConfigFile = Helper.prop("kernelConfigFile")
val cmd = CommandLine.parse("python").let {
it.addArgument(Helper.prop("kernelExtracter"))
val cmdPrefix = if (EnvironmentVerifier().isWindows) "python " else ""
val cmd = CommandLine.parse(cmdPrefix + Helper.prop("kernelExtracter")).let {
it.addArgument("--input")
it.addArgument(fileName)
it.addArgument("--output-configs")

@ -29,18 +29,18 @@ class BootImgParser() : IPackable {
log.info("header version $hv")
if (hv == 3) {
val b3 = BootV3
.parse(fileName)
.extractImages()
.extractVBMeta()
.printSummary()
.parse(fileName)
.extractImages()
.extractVBMeta()
.printSummary()
log.debug(b3.toString())
return
} else {
val b2 = BootV2
.parse(fileName)
.extractImages()
.extractVBMeta()
.printSummary()
.parse(fileName)
.extractImages()
.extractVBMeta()
.printSummary()
log.debug(b2.toString())
}
} catch (e: IllegalArgumentException) {
@ -54,15 +54,14 @@ class BootImgParser() : IPackable {
log.info("Loading config from $cfgFile")
if (3 == probeHeaderVersion(fileName)) {
ObjectMapper().readValue(File(cfgFile), BootV3::class.java)
.pack()
.sign(fileName)
updateVbmeta(fileName)
.pack()
.sign(fileName)
} else {
ObjectMapper().readValue(File(cfgFile), BootV2::class.java)
.pack()
.sign()
updateVbmeta(fileName)
.pack()
.sign()
}
Avb.updateVbmeta(fileName)
}
override fun flash(fileName: String, deviceName: String) {
@ -93,34 +92,5 @@ class BootImgParser() : IPackable {
companion object {
private val log = LoggerFactory.getLogger(BootImgParser::class.java)
fun updateVbmeta(fileName: String) {
if (File("vbmeta.img").exists()) {
log.info("Updating vbmeta.img side by side ...")
val partitionName = ObjectMapper().readValue(File(Avb.getJsonFileName(fileName)), AVBInfo::class.java).let {
it.auxBlob!!.hashDescriptors.get(0).partition_name
}
val newHashDesc = Avb().parseVbMeta("$fileName.signed", dumpFile = false)
assert(newHashDesc.auxBlob!!.hashDescriptors.size == 1)
val mainVBMeta = ObjectMapper().readValue(File(Avb.getJsonFileName("vbmeta.img")), AVBInfo::class.java).apply {
val itr = this.auxBlob!!.hashDescriptors.iterator()
var seq = 0
while (itr.hasNext()) {
val itrValue = itr.next()
if (itrValue.partition_name == partitionName) {
log.info("Found $partitionName in vbmeta, update it")
seq = itrValue.sequence
itr.remove()
break
}
}
val hd = newHashDesc.auxBlob!!.hashDescriptors.get(0).apply { this.sequence = seq }
this.auxBlob!!.hashDescriptors.add(hd)
}
Avb().packVbMetaWithPadding("vbmeta.img", mainVBMeta)
} else {
log.info("no companion vbmeta.img")
}
}
}
}

@ -1,8 +1,8 @@
package cfig.packable
import cfig.Avb
import cfig.helper.Helper
import cfig.bootimg.v3.VendorBoot
import cfig.packable.BootImgParser.Companion.updateVbmeta
import com.fasterxml.jackson.databind.ObjectMapper
import org.slf4j.LoggerFactory
import java.io.File
@ -31,6 +31,19 @@ class VendorBootParser : IPackable {
ObjectMapper().readValue(File(cfgFile), VendorBoot::class.java)
.pack()
.sign()
updateVbmeta(fileName)
Avb.updateVbmeta(fileName)
}
override fun pull(fileName: String, deviceName: String) {
super.pull(fileName, deviceName)
}
override fun flash(fileName: String, deviceName: String) {
val stem = fileName.substring(0, fileName.indexOf("."))
super.flash("$fileName.signed", stem)
if (File("vbmeta.img.signed").exists()) {
super.flash("vbmeta.img.signed", "vbmeta")
}
}
}

@ -1,126 +0,0 @@
import avb.alg.Algorithms
import cfig.KeyUtil
import cfig.helper.KeyHelper2
import com.google.common.math.BigIntegerMath
import org.apache.commons.codec.binary.Hex
import org.bouncycastle.jce.provider.BouncyCastleProvider
import org.junit.Assert.assertEquals
import org.junit.Test
import org.slf4j.LoggerFactory
import java.math.BigInteger
import java.math.RoundingMode
import java.nio.file.Files
import java.nio.file.Paths
import java.security.KeyFactory
import java.security.KeyPairGenerator
import java.security.Security
import java.security.Signature
import java.security.spec.PKCS8EncodedKeySpec
import java.security.spec.X509EncodedKeySpec
import javax.crypto.Cipher
@OptIn(ExperimentalUnsignedTypes::class)
class HelperTest {
private val log = LoggerFactory.getLogger(HelperTest::class.java)
@Test
fun rawSignTest() {
val data = Hex.decodeHex("0001ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff003031300d0609608648016503040201050004206317a4c8d86accc8258c1ac23ef0ebd18bc33010d7afb43b241802646360b4ab")
val expectedSig = "28e17bc57406650ed78785fd558e7c1861cc4014c900d72b61c03cdbab1039e713b5bb19b556d04d276b46aae9b8a3999ccbac533a1cce00f83cfb83e2beb35ed7329f71ffec04fc2839a9b44e50abd66ea6c3d3bea6705e93e9139ecd0331170db18eba36a85a78bc49a5447260a30ed19d956cb2f8a71f6b19e57fdca43e052d1bb7840bf4c3efb47111f4d77764236d2e013fbf3b2577e4a3e01c9d166a5e890ef96210882e6e88ceca2fe3a2201f4961210d4ec6167f5dfd0e038e4a146f960caecab7d15ba65f6edcf5dbd25f5af543cfb8da4338bdbc872eec3f8e72aa8db679099e70952d3f7176c0b9111bf20ad1390eab1d09a859105816fdf92fbb"
val privkFile = "../" + Algorithms.get("SHA256_RSA2048")!!.defaultKey.replace("pem", "pk8")
val k = KeyHelper2.parseRsaPk8(Files.readAllBytes(Paths.get(privkFile)))
val encData = KeyHelper2.rawSign(k, data)
assertEquals(expectedSig, Hex.encodeHexString(encData))
}
@Test
fun rawSignOpenSslTest() {
val data = Hex.decodeHex("0001ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff003031300d0609608648016503040201050004206317a4c8d86accc8258c1ac23ef0ebd18bc33010d7afb43b241802646360b4ab")
val expectedSig = "28e17bc57406650ed78785fd558e7c1861cc4014c900d72b61c03cdbab1039e713b5bb19b556d04d276b46aae9b8a3999ccbac533a1cce00f83cfb83e2beb35ed7329f71ffec04fc2839a9b44e50abd66ea6c3d3bea6705e93e9139ecd0331170db18eba36a85a78bc49a5447260a30ed19d956cb2f8a71f6b19e57fdca43e052d1bb7840bf4c3efb47111f4d77764236d2e013fbf3b2577e4a3e01c9d166a5e890ef96210882e6e88ceca2fe3a2201f4961210d4ec6167f5dfd0e038e4a146f960caecab7d15ba65f6edcf5dbd25f5af543cfb8da4338bdbc872eec3f8e72aa8db679099e70952d3f7176c0b9111bf20ad1390eab1d09a859105816fdf92fbb"
val sig = KeyHelper2.rawSignOpenSsl("../" + Algorithms.get("SHA256_RSA2048")!!.defaultKey, data)
assertEquals(expectedSig, Hex.encodeHexString(sig))
}
@Test
fun test3() {
val data = Hex.decodeHex("0001ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff003031300d0609608648016503040201050004206317a4c8d86accc8258c1ac23ef0ebd18bc3301033")
val signature = Signature.getInstance("NONEwithRSA")
val keyFile = "../" + Algorithms.get("SHA256_RSA2048")!!.defaultKey.replace("pem", "pk8")
val k = KeyUtil.parsePk8PrivateKey(Files.readAllBytes(Paths.get(keyFile)))
signature.initSign(k)
signature.update(data)
println("data size " + data.size)
println(signature.provider)
val sig = signature.sign()
println(sig)
}
@Test
fun testCipher() {
Security.addProvider(BouncyCastleProvider())
for (p in Security.getProviders()) {
println(p.toString())
for (entry in p.entries) {
println("\t" + entry.key.toString() + " -> " + entry.value)
}
println()
}
}
@Test
fun testKeys() {
val kp = KeyPairGenerator.getInstance("rsa")
.apply { this.initialize(2048) }
.generateKeyPair()
val pk8Spec = PKCS8EncodedKeySpec(kp.private.encoded) //kp.private.format == PKCS#8
val x509Spec = X509EncodedKeySpec(kp.public.encoded) //kp.public.format == X.509
val kf = KeyFactory.getInstance("rsa")
val privk = kf.generatePrivate(pk8Spec)
val pubk = kf.generatePublic(x509Spec)
println(pubk)
val cipher = Cipher.getInstance("RSA").apply {
this.init(Cipher.ENCRYPT_MODE, privk)
this.update("Good".toByteArray())
}
val encryptedText = Hex.encodeHexString(cipher.doFinal())
println(encryptedText)
}
@Test
fun testRSA() {
// val r = BigIntegerMath.log2(BigInteger.valueOf(1024), RoundingMode.CEILING)
// println(r)
// println(BigInteger.valueOf(1024).mod(BigInteger.valueOf(2)))
val p = BigInteger.valueOf(3)
val q = BigInteger.valueOf(7)
val modulus = p.multiply(q)
val keyLength = BigIntegerMath.log2(modulus, RoundingMode.CEILING)
println("keyLength = $keyLength")
//r = phi(n) = phi(p) * phi(q) = (p - 1)*(q - 1)
val r = (p.subtract(BigInteger.ONE)).multiply(q - BigInteger.ONE)
//r ~ e
//e is released as the public key exponent
//most commonly e = 2^16 + 1 = 65,537
val e = BigInteger.valueOf(5)
//(d * e).mod(r) == 1
//d is kept as the private key exponent
val d = e.modInverse(r)
println("p = $p, q = $q, modulus = $modulus , r = $r, e = $e, d = $d")
assertEquals(1, d.multiply(e).mod(r).toInt())
//private key: (modulus, d), d is calculated
//pub key: (modulus, e) , e is chosen
val clearMsg = BigInteger.valueOf(10)
val encMsg = clearMsg.pow(e.toInt()).mod(modulus)
println("clear: $clearMsg, enc: $encMsg")
}
}

@ -1,22 +1,193 @@
import avb.alg.Algorithms
import cfig.KeyUtil
import org.apache.commons.codec.binary.Hex
import org.junit.Assert.*
import cfig.helper.Helper
import cfig.helper.KeyHelper
import cfig.helper.KeyHelper2
import com.google.common.math.BigIntegerMath
import org.bouncycastle.jce.provider.BouncyCastleProvider
import org.junit.Assert.assertEquals
import org.junit.Test
import java.io.FileInputStream
import java.io.File
import java.math.BigInteger
import java.math.RoundingMode
import java.nio.file.Files
import java.nio.file.Paths
import java.security.*
import java.security.interfaces.RSAPrivateKey
import java.security.spec.PKCS8EncodedKeySpec
import java.security.spec.X509EncodedKeySpec
import javax.crypto.Cipher
@OptIn(ExperimentalUnsignedTypes::class)
class KeyUtilTest {
@Test
fun parseKeys() {
val keyFile = "../" + Algorithms.get("SHA256_RSA2048")!!.defaultKey
val k = KeyUtil.parsePk8PrivateKey(Files.readAllBytes(Paths.get(keyFile.replace("pem", "pk8"))))
println("Key: $keyFile")
val k = KeyHelper.parse(File(keyFile.replace("pem", "pk8")).readBytes()) as RSAPrivateKey
println(k.privateExponent)
println(k.modulus)
val k2 = KeyHelper.parse(File(keyFile).readBytes()) as org.bouncycastle.asn1.pkcs.RSAPrivateKey
println(k2.privateExponent)
println(k2.modulus)
//KeyHelper2.parseRsaPk8(FileInputStream(keyFile).readAllBytes())
KeyHelper.parse(File(keyFile.replace("pem", "pk8")).readBytes())
}
@Test
fun x1() {
val p = BigInteger.valueOf(61L)
val q = BigInteger.valueOf(53L)
val modulus = p * q
println(modulus)
val exponent = 17
val x = calcPrivateKey(p, q, exponent)
println(x)
//private key: modulus, x
//public key: modulus, exponent
val data = 123L
val encryptedData = enc(data, exponent, modulus)
println("enc2 = " + encryptedData)
val decryptedData = dec(encryptedData, x, modulus)
println("dec2 data = " + decryptedData)
}
fun calcPrivateKey(p: BigInteger, q: BigInteger, exponent: Int): Int {
val modulus = p * q
val phi = (p - BigInteger.ONE) * (q - BigInteger.ONE)
return BigInteger.valueOf(exponent.toLong()).modInverse(phi).toInt()
}
//data^{exp}
fun enc(data: Long, exponent: Int, modulus: BigInteger): Long {
return BigInteger.valueOf(data).pow(exponent).rem(modulus).toLong()
}
//data^{privateKey}
fun dec(data: Long, privateKey: Int, modulus: BigInteger): Long {
return BigInteger.valueOf(data).pow(privateKey).rem(modulus).toLong()
}
@Test
fun rawSignTest() {
val data =
Helper.fromHexString("0001ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff003031300d0609608648016503040201050004206317a4c8d86accc8258c1ac23ef0ebd18bc33010d7afb43b241802646360b4ab")
val expectedSig =
"28e17bc57406650ed78785fd558e7c1861cc4014c900d72b61c03cdbab1039e713b5bb19b556d04d276b46aae9b8a3999ccbac533a1cce00f83cfb83e2beb35ed7329f71ffec04fc2839a9b44e50abd66ea6c3d3bea6705e93e9139ecd0331170db18eba36a85a78bc49a5447260a30ed19d956cb2f8a71f6b19e57fdca43e052d1bb7840bf4c3efb47111f4d77764236d2e013fbf3b2577e4a3e01c9d166a5e890ef96210882e6e88ceca2fe3a2201f4961210d4ec6167f5dfd0e038e4a146f960caecab7d15ba65f6edcf5dbd25f5af543cfb8da4338bdbc872eec3f8e72aa8db679099e70952d3f7176c0b9111bf20ad1390eab1d09a859105816fdf92fbb"
val privkFile = "../" + Algorithms.get("SHA256_RSA2048")!!.defaultKey.replace("pem", "pk8")
val k = KeyHelper.parse(Files.readAllBytes(Paths.get(privkFile))) as PrivateKey
val encData = KeyHelper2.rawSign(k, data)
assertEquals(expectedSig, Helper.toHexString(encData))
}
@Test
fun rawSignOpenSslTest() {
val data =
Helper.fromHexString("0001ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff003031300d0609608648016503040201050004206317a4c8d86accc8258c1ac23ef0ebd18bc33010d7afb43b241802646360b4ab")
val expectedSig =
"28e17bc57406650ed78785fd558e7c1861cc4014c900d72b61c03cdbab1039e713b5bb19b556d04d276b46aae9b8a3999ccbac533a1cce00f83cfb83e2beb35ed7329f71ffec04fc2839a9b44e50abd66ea6c3d3bea6705e93e9139ecd0331170db18eba36a85a78bc49a5447260a30ed19d956cb2f8a71f6b19e57fdca43e052d1bb7840bf4c3efb47111f4d77764236d2e013fbf3b2577e4a3e01c9d166a5e890ef96210882e6e88ceca2fe3a2201f4961210d4ec6167f5dfd0e038e4a146f960caecab7d15ba65f6edcf5dbd25f5af543cfb8da4338bdbc872eec3f8e72aa8db679099e70952d3f7176c0b9111bf20ad1390eab1d09a859105816fdf92fbb"
val sig = KeyHelper2.rawSignOpenSsl("../" + Algorithms.get("SHA256_RSA2048")!!.defaultKey, data)
assertEquals(expectedSig, Helper.toHexString(sig))
}
@Test
fun test3() {
val data =
Helper.fromHexString("0001ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff003031300d0609608648016503040201050004206317a4c8d86accc8258c1ac23ef0ebd18bc3301033")
val signature = Signature.getInstance("NONEwithRSA")
val keyFile = "../" + Algorithms.get("SHA256_RSA2048")!!.defaultKey.replace("pem", "pk8")
val k = KeyHelper.parse(Files.readAllBytes(Paths.get(keyFile))) as PrivateKey
signature.initSign(k)
signature.update(data)
println("data size " + data.size)
println(signature.provider)
val sig = signature.sign()
println(sig)
}
@Test
fun testCipher() {
Security.addProvider(BouncyCastleProvider())
for (p in Security.getProviders()) {
println(p.toString())
for (entry in p.entries) {
println("\t" + entry.key.toString() + " -> " + entry.value)
}
println()
}
}
@Test
fun testKeys() {
val kp = KeyPairGenerator.getInstance("rsa")
.apply { this.initialize(2048) }
.generateKeyPair()
val pk8Spec = PKCS8EncodedKeySpec(kp.private.encoded) //kp.private.format == PKCS#8
val x509Spec = X509EncodedKeySpec(kp.public.encoded) //kp.public.format == X.509
val kf = KeyFactory.getInstance("rsa")
val privk = kf.generatePrivate(pk8Spec)
val pubk = kf.generatePublic(x509Spec)
println(pubk)
val k2 = KeyUtil.parsePemPrivateKey2(FileInputStream(keyFile))
println(Hex.encodeHexString(k.encoded))
println(Hex.encodeHexString(k2.encoded))
val cipher = Cipher.getInstance("RSA").apply {
this.init(Cipher.ENCRYPT_MODE, privk)
this.update("Good".toByteArray())
}
val encryptedText = Helper.toHexString(cipher.doFinal())
println(encryptedText)
}
@Test
fun testRSA() {
// val r = BigIntegerMath.log2(BigInteger.valueOf(1024), RoundingMode.CEILING)
// println(r)
// println(BigInteger.valueOf(1024).mod(BigInteger.valueOf(2)))
val p = BigInteger.valueOf(3)
val q = BigInteger.valueOf(7)
val modulus = p.multiply(q)
val keyLength = BigIntegerMath.log2(modulus, RoundingMode.CEILING)
println("keyLength = $keyLength")
//r = phi(n) = phi(p) * phi(q) = (p - 1)*(q - 1)
val r = (p.subtract(BigInteger.ONE)).multiply(q - BigInteger.ONE)
//r ~ e
//e is released as the public key exponent
//most commonly e = 2^16 + 1 = 65,537
val e = BigInteger.valueOf(5)
//(d * e).mod(r) == 1
//d is kept as the private key exponent
val d = e.modInverse(r)
println("p = $p, q = $q, modulus = $modulus , r = $r, e = $e, d = $d")
assertEquals(1, d.multiply(e).mod(r).toInt())
//private key: (modulus, d), d is calculated
//pub key: (modulus, e) , e is chosen
val clearMsg = BigInteger.valueOf(10)
val encMsg = clearMsg.pow(e.toInt()).mod(modulus)
println("clear: $clearMsg, enc: $encMsg")
}
@Test
fun listAll() {
KeyHelper.listAll()
}
@Test
fun signData() {
val data = KeyUtilTest::class.java.classLoader.getResourceAsStream("data").readAllBytes()
println(Helper.toHexString(data))
val privKey = KeyHelper.parse(
KeyUtilTest::class.java.classLoader.getResourceAsStream("testkey.pk8").readAllBytes()
) as PrivateKey
println("sha256=" + Helper.toHexString(KeyHelper2.sha256(data)))
val signedHash = KeyHelper2.sha256rsa(data, privKey)
println("Signed Hash: " + Helper.toHexString(signedHash))
}
}

File diff suppressed because one or more lines are too long

@ -1,14 +1,10 @@
package avb
import avb.blob.Footer
import cfig.bootimg.cpio.AndroidCpioEntry
import org.apache.commons.codec.binary.Hex
import org.junit.Assert.assertEquals
import org.junit.Test
import org.junit.Assert.*
import java.io.ByteArrayInputStream
import java.nio.file.Files
import java.nio.file.Paths
@OptIn(ExperimentalUnsignedTypes::class)
class FooterTest {

@ -3,7 +3,6 @@ package bootimg
import cfig.bootimg.cpio.AndroidCpio
import cfig.bootimg.cpio.AndroidCpioEntry
import cfig.helper.Helper
import org.junit.Assert
import org.junit.Assert.assertTrue
import org.junit.Test
@ -13,8 +12,8 @@ class AndroidCpioEntryTest {
run {//dir, fileMode 040755
val entry1 = AndroidCpioEntry(name = "acct", statMode = 0x41ed, data = byteArrayOf(), ino = 300000)
val exp = Helper.fromHexString("3037303730313030303439336530303030303431656430303030303030303030303030303030303030303030303130303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030353030303030303030616363740000")
Assert.assertTrue(entry1.encode().contentEquals(exp))
Assert.assertTrue(entry1.encode2().contentEquals(exp))
assertTrue(entry1.encode().contentEquals(exp))
assertTrue(entry1.encode2().contentEquals(exp))
}
run {//dir, fileMode 040755

@ -4,8 +4,6 @@ import cfig.bootimg.Common.Companion.deleleIfExists
import cfig.bootloader_message.BootloaderMsg
import org.junit.After
import org.junit.Test
import org.junit.Assert.*
import org.slf4j.LoggerFactory
import java.io.File

Binary file not shown.

@ -5,7 +5,7 @@ import org.apache.commons.exec.DefaultExecutor
import org.apache.commons.exec.PumpStreamHandler
val GROUP_ANDROID = "android"
val localHack = false
val bHackingMode = true
if (parseGradleVersion(gradle.gradleVersion) < 6) {
logger.error("ERROR: Gradle Version MUST >= 6.0, current is {}", gradle.gradleVersion)
throw RuntimeException("ERROR: Gradle Version")
@ -74,20 +74,24 @@ tasks {
pullTask.dependsOn("bbootimg:jar")
//sparse image dependencies
if (localHack) {
if (bHackingMode) {
logger.info("Hacking mode!")
//C++ mkbootfs
packTask.dependsOn("aosp:mkbootfs.10:mkbootfsExecutable")
packTask.dependsOn("aosp:mkbootfs.11:mkbootfsExecutable")
unpackTask.dependsOn("aosp:mkbootfs.10:mkbootfsExecutable")
unpackTask.dependsOn("aosp:mkbootfs.11:mkbootfsExecutable")
}
if (System.getProperty("os.name").contains("Mac")) {
unpackTask.dependsOn("aosp:libsparse:simg2img:installReleaseMacos")
packTask.dependsOn("aosp:libsparse:img2simg:installReleaseMacos")
} else if (System.getProperty("os.name").contains("Linux")) {
unpackTask.dependsOn("aosp:libsparse:simg2img:installReleaseLinux")
packTask.dependsOn("aosp:libsparse:img2simg:installReleaseLinux")
if (System.getProperty("os.name").contains("Mac")) {
unpackTask.dependsOn("aosp:libsparse:simg2img:installReleaseMacos")
packTask.dependsOn("aosp:libsparse:img2simg:installReleaseMacos")
} else if (System.getProperty("os.name").contains("Linux")) {
unpackTask.dependsOn("aosp:libsparse:simg2img:installReleaseLinux")
packTask.dependsOn("aosp:libsparse:img2simg:installReleaseLinux")
} else {
logger.info("Disable C++ modules on Window$")
}
} else {
logger.info("Disable C++ modules on Window$")
logger.info("Release mode")
}
}

Binary file not shown.

@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.4.1-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-6.8.3-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

24
gradlew.bat vendored

@ -21,9 +21,6 @@
@rem
@rem ##########################################################################
@rem set path for lz4
@IF EXIST tools\bin SET PATH=%PATH%;tools\bin
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
@ -43,7 +40,7 @@ if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init
if "%ERRORLEVEL%" == "0" goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
@ -57,7 +54,7 @@ goto fail
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init
if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
@ -67,21 +64,6 @@ echo location of your Java installation.
goto fail
:init
@rem Get command-line arguments, handling Windows variants
if not "%OS%" == "Windows_NT" goto win9xME_args
:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2
:win9xME_args_slurp
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
:execute
@rem Setup the command line
@ -89,7 +71,7 @@ set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:end
@rem End local scope for the variables with windows NT shell

@ -0,0 +1,32 @@
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
plugins {
id("org.jetbrains.kotlin.jvm") version "1.4.31"
`java-library`
}
repositories {
mavenCentral()
}
dependencies {
implementation(platform("org.jetbrains.kotlin:kotlin-bom"))
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
implementation("org.jetbrains.kotlin:kotlin-reflect")
implementation("com.google.guava:guava:18.0")
implementation("org.slf4j:slf4j-api:1.7.30")
implementation("org.slf4j:slf4j-simple:1.7.30")
implementation("org.apache.commons:commons-exec:1.3")
implementation("org.bouncycastle:bcprov-jdk15on:1.57")
implementation("org.apache.commons:commons-compress:1.20")
testImplementation("org.jetbrains.kotlin:kotlin-test")
testImplementation("org.jetbrains.kotlin:kotlin-test-junit")
testImplementation("com.fasterxml.jackson.core:jackson-annotations:2.12.1")
testImplementation("com.fasterxml.jackson.core:jackson-databind:2.12.1")
}
tasks.withType<KotlinCompile>().all {
kotlinOptions.freeCompilerArgs += "-Xopt-in=kotlin.RequiresOptIn"
kotlinOptions.jvmTarget = "1.8"
}

@ -0,0 +1,26 @@
package cfig.helper
import org.apache.commons.exec.CommandLine
import org.apache.commons.exec.DefaultExecutor
import org.slf4j.LoggerFactory
class AndroidHelper {
companion object {
private val log = LoggerFactory.getLogger(AndroidHelper::class.java)
fun signFile(signer: String,
inFile: String,
outFile: String,
pemKey: String = "aosp/security/testkey.x509.pem",
pk8Key: String = "aosp/security/testkey.pk8") {
var cmd = "java -Xmx2048m -jar $signer "
cmd += " -w "
cmd += " $pemKey "
cmd += " $pk8Key "
cmd += " $inFile "
cmd += " $outFile "
log.info("signFile: $cmd")
DefaultExecutor().execute(CommandLine.parse(cmd))
}
}
}

@ -1,30 +1,17 @@
package cfig.helper
import cfig.io.Struct3
import com.google.common.math.BigIntegerMath
import org.apache.commons.codec.binary.Hex
import org.apache.commons.compress.compressors.gzip.GzipCompressorOutputStream
import org.apache.commons.compress.compressors.gzip.GzipParameters
import org.apache.commons.compress.compressors.xz.XZCompressorInputStream
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.*
import java.math.BigInteger
import java.math.RoundingMode
import java.nio.ByteBuffer
import java.nio.ByteOrder
import java.nio.file.Files
import java.nio.file.Paths
import java.nio.file.attribute.PosixFilePermission
import java.security.MessageDigest
import java.util.*
import java.util.zip.GZIPInputStream
import java.util.zip.GZIPOutputStream
import java.util.zip.ZipException
import javax.crypto.Cipher
@OptIn(ExperimentalUnsignedTypes::class)
class Helper {

@ -0,0 +1,148 @@
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
*/
@OptIn(ExperimentalUnsignedTypes::class)
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,6 +1,5 @@
package cfig.helper
import org.apache.commons.codec.binary.Hex
import org.apache.commons.exec.CommandLine
import org.apache.commons.exec.DefaultExecutor
import org.apache.commons.exec.ExecuteException
@ -8,12 +7,10 @@ import org.apache.commons.exec.PumpStreamHandler
import org.slf4j.LoggerFactory
import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream
import java.math.BigInteger
import java.security.*
import java.security.spec.PKCS8EncodedKeySpec
import java.security.spec.RSAPrivateKeySpec
import java.security.spec.RSAPublicKeySpec
import java.security.spec.X509EncodedKeySpec
import java.security.MessageDigest
import java.security.PrivateKey
import java.security.PublicKey
import java.security.Signature
import java.util.*
import javax.crypto.Cipher
@ -21,66 +18,6 @@ class KeyHelper2 {
companion object {
private val log = LoggerFactory.getLogger(KeyHelper2::class.java)
fun parseRsaPk8(inputData: ByteArray): java.security.PrivateKey {
val spec = PKCS8EncodedKeySpec(inputData)
return KeyFactory.getInstance("RSA").generatePrivate(spec)
}
fun parseRsaKey(keyText: String): PrivateKey {
val publicKeyPEM = keyText
.replace("-----BEGIN RSA PRIVATE KEY-----", "")
.replace(System.lineSeparator().toRegex(), "")
.replace("\n", "")
.replace("\r", "")
.replace("-----END RSA PRIVATE KEY-----", "")
log.warn("trimmed key")
log.warn(publicKeyPEM)
val encoded: ByteArray = Base64.getDecoder().decode(publicKeyPEM)
val keySpec = X509EncodedKeySpec(encoded)
return KeyFactory.getInstance("RSA").generatePrivate(keySpec)
}
fun parsePemPubkey(keyText: String): java.security.interfaces.RSAPublicKey {
val publicKeyPEM = keyText
.replace("-----BEGIN PUBLIC KEY-----", "")
.replace(System.lineSeparator().toRegex(), "")
.replace("\n", "")
.replace("\r", "")
.replace("-----END PUBLIC KEY-----", "")
val encoded: ByteArray = Base64.getDecoder().decode(publicKeyPEM)
val keySpec = X509EncodedKeySpec(encoded)
return KeyFactory.getInstance("RSA").generatePublic(keySpec) as java.security.interfaces.RSAPublicKey
}
fun parsePemPubCert(keyText: String) {
val publicKeyPEM = keyText
.replace("-----BEGIN CERTIFICATE-----", "")
.replace(System.lineSeparator().toRegex(), "")
.replace("\n", "")
.replace("\r", "")
.replace("-----END CERTIFICATE-----", "")
val encoded: ByteArray = Base64.getDecoder().decode(publicKeyPEM)
val keySpec = X509EncodedKeySpec(encoded)
// return KeyFactory.getInstance("RSA").generatePublic(keySpec) as java.security.interfaces.RSAPublicKey
}
/*
in: modulus, expo
out: PublicKey
*/
fun generateRsaPublicKey(modulus: BigInteger, publicExponent: BigInteger): java.security.PublicKey {
return KeyFactory.getInstance("RSA").generatePublic(RSAPublicKeySpec(modulus, publicExponent))
}
/*
in: modulus, expo
out: PrivateKey
*/
fun generateRsaPrivateKey(modulus: BigInteger, privateExponent: BigInteger): java.security.PrivateKey {
return KeyFactory.getInstance("RSA").generatePrivate(RSAPrivateKeySpec(modulus, privateExponent))
}
//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;
@ -93,9 +30,8 @@ class KeyHelper2 {
}
}
fun rawSignOpenSsl(keyPath: String, data: ByteArray): ByteArray {
log.debug("raw input: " + Hex.encodeHexString(data))
log.debug("raw input: " + Helper.toHexString(data))
log.debug("Raw sign data size = ${data.size}, key = $keyPath")
var ret = byteArrayOf()
val exe = DefaultExecutor()
@ -110,7 +46,7 @@ class KeyHelper2 {
} catch (e: ExecuteException) {
log.error("Execute error")
} finally {
log.debug("OUT: " + Hex.encodeHexString(stdout.toByteArray()))
log.debug("OUT: " + Helper.toHexString(stdout.toByteArray()))
log.debug("ERR: " + String(stderr.toByteArray()))
}

@ -1,28 +1,25 @@
package cfig.helper
import cfig.bootimg.cpio.AndroidCpioEntry
import cfig.helper.Helper.Companion.check_call
import cfig.helper.Helper.Companion.check_output
import cfig.io.Struct3
import com.fasterxml.jackson.databind.ObjectMapper
import org.apache.commons.compress.archivers.cpio.CpioArchiveInputStream
import org.apache.commons.compress.archivers.cpio.CpioConstants
import org.apache.commons.compress.archivers.zip.*
import org.apache.commons.compress.archivers.zip.ZipArchiveEntry
import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream
import org.apache.commons.compress.archivers.zip.ZipArchiveInputStream
import org.apache.commons.compress.archivers.zip.ZipFile
import org.apache.commons.compress.archivers.zip.ZipMethod
import org.apache.commons.compress.compressors.gzip.GzipCompressorOutputStream
import org.apache.commons.compress.compressors.gzip.GzipParameters
import org.apache.commons.compress.compressors.lz4.FramedLZ4CompressorInputStream
import org.apache.commons.compress.compressors.xz.XZCompressorInputStream
import org.apache.commons.compress.utils.IOUtils
import org.apache.commons.exec.CommandLine
import org.apache.commons.exec.DefaultExecutor
import org.apache.commons.exec.PumpStreamHandler
import org.slf4j.LoggerFactory
import java.io.*
import java.lang.IllegalArgumentException
import java.lang.RuntimeException
import java.net.URI
import java.nio.file.FileSystems
import java.nio.file.Files
import java.nio.file.Paths
import java.nio.file.StandardCopyOption
import java.util.zip.GZIPInputStream
import java.util.zip.GZIPOutputStream
@ -36,132 +33,42 @@ class ZipHelper {
companion object {
private val log = LoggerFactory.getLogger("ZipHelper")
fun unZipFile2(fileName: String, outDir: String) {
val zis = ZipArchiveInputStream(BufferedInputStream(FileInputStream(fileName)))
while (true) {
val entry = zis.nextZipEntry ?: break
val entryOut = File(outDir + "/" + entry.name)
when {
entry.isDirectory -> {
log.error("Found dir : " + entry.name)
throw IllegalArgumentException("this should not happen")
}
entry.isUnixSymlink -> {
log.error("Found link: " + entry.name)
throw IllegalArgumentException("this should not happen")
}
else -> {
if (entry.name.contains("/")) {
log.debug("Createing dir: " + entryOut.parentFile.canonicalPath)
entryOut.parentFile.mkdirs()
}
log.info("Unzipping " + entry.name)
IOUtils.copy(zis, FileOutputStream(entryOut))
}
}
}
}
/*
https://github.com/python/cpython/blob/3.8/Lib/zipfile.py
The "local file header" structure, magic number, size, and indices
(section V.A in the format document)
structFileHeader = "<4s2B4HL2L2H"
stringFileHeader = b"PK\003\004"
sizeFileHeader = struct.calcsize(structFileHeader)
*/
fun ZipArchiveEntry.getEntryOffset(): Long {
val zipFileHeaderSize = Struct3("<4s2B4HL2L2H").calcSize()
val funGetLocalHeaderOffset = ZipArchiveEntry::class.declaredFunctions.filter { funcItem ->
funcItem.name == "getLocalHeaderOffset"
}[0]
funGetLocalHeaderOffset.isAccessible = true
val headerOffset = funGetLocalHeaderOffset.call(this) as Long
val offset: Long = headerOffset + zipFileHeaderSize + this.localFileDataExtra.size + this.name.length
log.debug("headerOffset = $headerOffset")
log.debug("calcSize: $zipFileHeaderSize")
return offset
}
fun dumpZipEntry(inFile: String, entryName: String, outFile: String) {
log.info("dumping: $inFile#$entryName -> $outFile")
val zf = ZipFile(inFile)
val entry = zf.getEntry(entryName)
FileOutputStream(outFile).use { outStream ->
zf.getInputStream(entry).copyTo(outStream)
}
zf.close()
}
fun getEntryStream(zipFile: ZipFile, entryName: String): InputStream {
return zipFile.getInputStream(zipFile.getEntry(entryName))
}
fun ZipFile.dumpEntryIfExists(entryName: String, outFile: File) {
val entry = this.getEntry(entryName)
if (entry != null) {
log.info("dumping entry: $entryName -> $outFile")
FileOutputStream(outFile).use { outStream ->
this.getInputStream(entry).copyTo(outStream)
unzip(): unzip fileName to outDir
*/
fun unzip(fileName: String, outDir: String) {
log.info("unzip: $fileName --> $outDir")
if (File(outDir).exists()) {
if (!File(outDir).isDirectory) {
throw RuntimeException("$outDir exists but is not directory")
}
} else {
log.info("dumping entry: $entryName : entry not found, skip")
}
}
fun ZipFile.dumpEntry(entryName: String, outFile: File) {
log.info("dumping entry: $entryName -> $outFile")
val entry = this.getEntry(entryName)
FileOutputStream(outFile).use { outStream ->
this.getInputStream(entry).copyTo(outStream)
log.info("Creating $outDir ...")
File(outDir).mkdirs()
}
}
fun ZipFile.dumpEntry(entryName: String, outFile: String) {
log.info("dumping entry: $entryName -> $outFile")
val entry = this.getEntry(entryName)
FileOutputStream(outFile).use { outStream ->
this.getInputStream(entry).copyTo(outStream)
ZipArchiveInputStream(FileInputStream(fileName)).use { zis ->
while (true) {
val entry = zis.nextZipEntry ?: break
when {
entry.isDirectory -> {
val entryOut = File(outDir + "/" + entry.name)
entryOut.mkdir()
log.debug("Unzipping[d]: ${entry.name}")
}
entry.isUnixSymlink -> {
throw IllegalArgumentException("this should not happen: Found dir ${entry.name}")
}
else -> {
val entryOut = File(outDir + "/" + entry.name)
log.debug("Unzipping[f]: ${entry.name}")
FileOutputStream(entryOut).use {
zis.copyTo(it)
}
}
}
}
}
}
fun ZipArchiveOutputStream.packFile(
inFile: File,
entryName: String,
zipMethod: ZipMethod = ZipMethod.DEFLATED
) {
log.info("packing $entryName($zipMethod) from file $inFile (size=${inFile.length()} ...")
val entry = ZipArchiveEntry(inFile, entryName)
entry.method = zipMethod.ordinal
this.putArchiveEntry(entry)
IOUtils.copy(Files.newInputStream(inFile.toPath()), this)
this.closeArchiveEntry()
}
fun ZipArchiveOutputStream.packEntry(
inBuf: ByteArray,
entryName: String,
zipMethod: ZipMethod = ZipMethod.DEFLATED
) {
log.info("packing $entryName($zipMethod) from memory data (size=${inBuf.size}...")
val entry = ZipArchiveEntry(entryName)
entry.method = zipMethod.ordinal
this.putArchiveEntry(entry)
IOUtils.copy(ByteArrayInputStream(inBuf), this)
this.closeArchiveEntry()
}
fun ZipArchiveOutputStream.packStream(
inStream: InputStream,
entryName: String,
zipMethod: ZipMethod = ZipMethod.DEFLATED
) {
log.info("packing $entryName($zipMethod) from input stream (size=unknown...")
val entry = ZipArchiveEntry(entryName)
entry.method = zipMethod.ordinal
this.putArchiveEntry(entry)
IOUtils.copy(inStream, this)
this.closeArchiveEntry()
log.info("unzip: $fileName --> $outDir Done.")
}
fun zipDelete(zipFile: File, entryName: String) {
@ -181,7 +88,7 @@ class ZipHelper {
while (e.hasMoreElements()) {
val entry = e.nextElement()
zaos.putArchiveEntry(entry)
IOUtils.copy(zf.getInputStream(entry), zaos)
zf.getInputStream(entry).copyTo(zaos)
zaos.closeArchiveEntry()
}
zaos.finish()
@ -200,7 +107,7 @@ class ZipHelper {
val entry = ZipArchiveEntry(entryRecipe.name)
entry.method = entryRecipe.method.ordinal
zaos.putArchiveEntry(entry)
IOUtils.copy(ByteArrayInputStream(entryRecipe.data), zaos)
ByteArrayInputStream(entryRecipe.data).copyTo(zaos)
}
while (e.hasMoreElements()) {
@ -208,10 +115,10 @@ class ZipHelper {
zaos.putArchiveEntry(entry)
if (entry.name == entryRecipe.name) {
log.info("modifying existent entry [${entryRecipe.name}(${entryRecipe.method})] into [${tmpFile.canonicalPath}]")
IOUtils.copy(ByteArrayInputStream(entryRecipe.data), zaos)
ByteArrayInputStream(entryRecipe.data).copyTo(zaos)
} else {
log.debug("cloning entry ${entry.name} ...")
IOUtils.copy(zf.getInputStream(entry), zaos)
zf.getInputStream(entry).copyTo(zaos)
}
zaos.closeArchiveEntry()
}
@ -224,19 +131,71 @@ class ZipHelper {
log.info("renaming ${tmpFile.canonicalPath} --> $inFile done")
}
fun isGZ(compressedFile: String): Boolean {
return try {
FileInputStream(compressedFile).use { fis ->
GZIPInputStream(fis).use {
}
/*
https://github.com/python/cpython/blob/3.8/Lib/zipfile.py
The "local file header" structure, magic number, size, and indices
(section V.A in the format document)
structFileHeader = "<4s2B4HL2L2H"
stringFileHeader = b"PK\003\004"
sizeFileHeader = struct.calcsize(structFileHeader)
*/
fun ZipArchiveEntry.getEntryOffset(): Long {
val zipFileHeaderSize = Struct3("<4s2B4HL2L2H").calcSize()
val funGetLocalHeaderOffset = ZipArchiveEntry::class.declaredFunctions.filter { funcItem ->
funcItem.name == "getLocalHeaderOffset"
}[0]
funGetLocalHeaderOffset.isAccessible = true
val headerOffset = funGetLocalHeaderOffset.call(this) as Long
val offset: Long = headerOffset + zipFileHeaderSize + this.localFileDataExtra.size + this.name.length
log.debug("headerOffset = $headerOffset")
log.debug("calcSize: $zipFileHeaderSize")
return offset
}
fun ZipFile.dumpEntry(entryName: String, outFile: File, ignoreError: Boolean = false) {
val entry = this.getEntry(entryName)
if (entry != null) {
log.info("dumping entry: $entryName -> $outFile")
FileOutputStream(outFile).use { outStream ->
this.getInputStream(entry).copyTo(outStream)
}
} else {
if (ignoreError) {
log.info("dumping entry: $entryName : entry not found, skip")
} else {
throw IllegalArgumentException("$entryName doesn't exist")
}
true
} catch (e: ZipException) {
false
}
}
fun isXZ(compressedFile: String): Boolean {
fun ZipArchiveOutputStream.pack(
inFile: File,
entryName: String,
zipMethod: ZipMethod = ZipMethod.DEFLATED
) {
log.info("packing $entryName($zipMethod) from file $inFile (size=${inFile.length()} ...")
val entry = ZipArchiveEntry(inFile, entryName)
entry.method = zipMethod.ordinal
this.putArchiveEntry(entry)
Files.newInputStream(inFile.toPath()).copyTo(this)
this.closeArchiveEntry()
}
fun ZipArchiveOutputStream.pack(
inStream: InputStream,
entryName: String,
zipMethod: ZipMethod = ZipMethod.DEFLATED
) {
log.info("packing $entryName($zipMethod) from input stream (size=unknown...")
val entry = ZipArchiveEntry(entryName)
entry.method = zipMethod.ordinal
this.putArchiveEntry(entry)
inStream.copyTo(this)
this.closeArchiveEntry()
}
fun isXz(compressedFile: String): Boolean {
return try {
FileInputStream(compressedFile).use { fis ->
XZCompressorInputStream(fis).use {
@ -248,7 +207,7 @@ class ZipHelper {
}
}
fun isLZ4(compressedFile: String): Boolean {
fun isLz4(compressedFile: String): Boolean {
return try {
"lz4 -t $compressedFile".check_call()
true
@ -257,11 +216,11 @@ class ZipHelper {
}
}
fun decompressLZ4Ext(lz4File: String, outFile: String) {
fun lz4cat(lz4File: String, outFile: String) {
"lz4 -d -fv $lz4File $outFile".check_call()
}
fun compressLZ4(lz4File: String, inputStream: InputStream) {
fun lz4(lz4File: String, inputStream: InputStream) {
FileOutputStream(File(lz4File)).use { fos ->
val baosE = ByteArrayOutputStream()
DefaultExecutor().let { exec ->
@ -283,51 +242,28 @@ class ZipHelper {
}
}
fun decompressLZ4(framedLz4: String, outFile: String) {
FramedLZ4CompressorInputStream(
Files.newInputStream(Paths.get(framedLz4))
).use { zIn ->
Files.newOutputStream(Paths.get(outFile)).use { out ->
log.info("decompress lz4: $framedLz4 -> $outFile")
val buffer = ByteArray(8192)
var n: Int
while (-1 != zIn.read(buffer).also { n = it }) {
out.write(buffer, 0, n)
fun isGZ(compressedFile: String): Boolean {
return try {
FileInputStream(compressedFile).use { fis ->
GZIPInputStream(fis).use {
}
}
true
} catch (e: ZipException) {
false
}
}
@Throws(IOException::class)
fun gnuZipFile(compressedFile: String, decompressedFile: String) {
val buffer = ByteArray(1024)
FileOutputStream(compressedFile).use { fos ->
GZIPOutputStream(fos).use { gos ->
FileInputStream(decompressedFile).use { fis ->
var bytesRead: Int
while (true) {
bytesRead = fis.read(buffer)
if (bytesRead <= 0) break
gos.write(buffer, 0, bytesRead)
}
gos.finish()
log.info("gzip done: $decompressedFile -> $compressedFile")
}//file-input-stream
}//gzip-output-stream
}//file-output-stream
}
@Throws(IOException::class)
fun unGnuzipFile(compressedFile: String, decompressedFile: String) {
val buffer = ByteArray(1024)
/*
@function: zcat compressedFile > decompressedFile
*/
fun zcat(compressedFile: String, decompressedFile: String) {
FileInputStream(compressedFile).use { fileIn ->
//src
GZIPInputStream(fileIn).use { gZIPInputStream ->
//src
FileOutputStream(decompressedFile).use { fileOutputStream ->
var bytesRead: Int
val buffer = ByteArray(1024)
while (true) {
bytesRead = gZIPInputStream.read(buffer)
val bytesRead = gZIPInputStream.read(buffer)
if (bytesRead <= 0) break
fileOutputStream.write(buffer, 0, bytesRead)
}
@ -338,49 +274,51 @@ class ZipHelper {
}
/*
caution: about gzip header - OS (Operating System)
@function: gzip InputStream to file,
using java.util.zip.GZIPOutputStream
According to https://docs.oracle.com/javase/8/docs/api/java/util/zip/package-summary.html and
GZIP spec RFC-1952(http://www.ietf.org/rfc/rfc1952.txt), gzip files created from java.util.zip.GZIPOutputStream
will mark the OS field with
caution: about gzip header - OS (Operating System)
According to https://docs.oracle.com/javase/8/docs/api/java/util/zip/package-summary.html
and GZIP spec RFC-1952(http://www.ietf.org/rfc/rfc1952.txt),
gzip files created from java.util.zip.GZIPOutputStream will mark the OS field with
0 - FAT filesystem (MS-DOS, OS/2, NT/Win32)
But default image built from Android source code has the OS field:
3 - Unix
This MAY not be a problem, at least we didn't find it till now.
*/
@Throws(IOException::class)
@Deprecated("this function misses features")
fun gnuZipFile(compressedFile: String, fis: InputStream) {
val buffer = ByteArray(1024)
fun gzip(compressedFile: String, fis: InputStream) {
FileOutputStream(compressedFile).use { fos ->
GZIPOutputStream(fos).use { gos ->
var bytesRead: Int
val buffer = ByteArray(1024)
while (true) {
bytesRead = fis.read(buffer)
val bytesRead = fis.read(buffer)
if (bytesRead <= 0) break
gos.write(buffer, 0, bytesRead)
}
log.info("compress(gz) done: $compressedFile")
}
}
log.info("compress(gz) done: $compressedFile")
}
fun gnuZipFile2(compressedFile: String, fis: InputStream) {
val buffer = ByteArray(1024)
val p = GzipParameters()
p.operatingSystem = 3
/*
@function: gzip InputStream to file like Android minigzip,
using commons.compress GzipCompressorOutputStream,
@dependency: commons.compress
*/
fun minigzip(compressedFile: String, fis: InputStream) {
val param = GzipParameters().apply {
operatingSystem = 3 //3: Unix
}
FileOutputStream(compressedFile).use { fos ->
GzipCompressorOutputStream(fos, p).use { gos ->
var bytesRead: Int
GzipCompressorOutputStream(fos, param).use { gos ->
val buffer = ByteArray(1024)
while (true) {
bytesRead = fis.read(buffer)
val bytesRead = fis.read(buffer)
if (bytesRead <= 0) break
gos.write(buffer, 0, bytesRead)
}
log.info("compress(gz) done: $compressedFile")
}
}
log.info("compress(gz) done: $compressedFile")
}
}
}

@ -0,0 +1,103 @@
package cfig.helper
import cfig.helper.OpenSslHelper.Companion.decodePem
import org.junit.Test
import org.slf4j.LoggerFactory
import kotlin.test.assertTrue
class OpenSslHelperTest {
private val log = LoggerFactory.getLogger(OpenSslHelperTest::class.java)
@Test
fun PKCS1test() {
//private RSA key
val rsa = OpenSslHelper.PK1Key.generate(4096).apply {
val fileName = "1_rsa.key"
writeTo(fileName)
}
//Action-1: private RSA key -> RSA public key(PEM)
val rsaPubPEM = rsa.getPublicKey(OpenSslHelper.KeyFormat.PEM).apply {
writeTo("2_rsa_pub.pem.key")
}
//Action-3: RSA public key(PEM) --> RSA public key(DER)
val decodeFromPem = decodePem(String(rsaPubPEM.data))
//Action-2: private RSA key -> RSA public key(DER)
val rsaPubDer = rsa.getPublicKey(OpenSslHelper.KeyFormat.DER).apply {
writeTo("3_rsa_pub.der.key")
}
//check equality: 1,3 == 2
assertTrue(decodeFromPem.contentEquals(rsaPubDer.data))
}
@Test
fun PKCS8Test() {
//private RSA key
val rsa = OpenSslHelper.PK1Key.generate(4096)
run { //check equality: 8 == 4,5
val pk8Pub = rsa.getPk8PublicKey()
val pk8Pub2 = rsa.toPk8(OpenSslHelper.KeyFormat.PEM).getPublicKey()
assert(pk8Pub.data.contentEquals(pk8Pub2.data))
}
run { //check equality: 8 == 4,5
val pk8Pub = rsa.getPk8PublicKey()
val action8_11 = decodePem(String(pk8Pub.data))
// val pk8Pub2 = rsa.toPk8(OpenSslHelper.KeyFormat.PEM).getPublicKey()
// assert(pk8Pub.data.contentEquals(pk8Pub2.data))
}
//check equality: 4,9 == original RSA
rsa.toPk8(OpenSslHelper.KeyFormat.PEM).let { pk8Pem ->
val shortConversion = pk8Pem.toPk1()
assert(shortConversion.data.contentEquals(rsa.data))
}
//check equality: 7,10,9 == original RSA
rsa.toPk8(OpenSslHelper.KeyFormat.DER).let { pk8der ->
val longConversion = pk8der
.transform(OpenSslHelper.KeyFormat.DER, OpenSslHelper.KeyFormat.PEM) //pk8 PEM
.toPk1() //pk1 PEM
assert(longConversion.data.contentEquals(rsa.data))
}
}
@Test
fun CertTest() {
//private RSA key
val rsa = OpenSslHelper.PK1Key.generate(4096)
val crt = rsa.toV1Cert()
val jks = crt.toJks()
//jks.writeTo("good.jks")
val pfx = OpenSslHelper.Pfx(name = "androiddebugkey", thePassword = "somepassword")
pfx.generate(rsa, crt)
val jks2 = pfx.toJks()
}
@Test
fun androidCertTest() {
//platform.x509.pem: Certificate, PEM
val crt = OpenSslHelper.Crt(data = this.javaClass.classLoader.getResourceAsStream("platform.x509.pem").readBytes())
val jks = crt.toJks()
//jks.writeTo("platform.jks")
jks.check()
}
@Test
fun androidPk8Test() {
//platform.pk8: Private Key, PKCS#8, DER encoding
val pk8rsa = OpenSslHelper.PK8RsaKey(OpenSslHelper.KeyFormat.DER,
this.javaClass.classLoader.getResourceAsStream("platform.pk8").readBytes())
val pk1 = pk8rsa
.transform(OpenSslHelper.KeyFormat.DER, OpenSslHelper.KeyFormat.PEM)
.toPk1()
val crt = OpenSslHelper.Crt(data = this.javaClass.classLoader.getResourceAsStream("platform.x509.pem").readBytes())
val pfx = OpenSslHelper.Pfx(name = "androiddebugkey", thePassword = "somepassword").apply {
this.generate(pk1, crt)
}
pfx.toJks().writeTo("platform.jks")
}
}

@ -0,0 +1,38 @@
package cfig.helper
import cfig.helper.ZipHelper.Companion.dumpEntry
import org.apache.commons.compress.archivers.zip.ZipFile
import org.junit.After
import org.junit.Before
import org.junit.Test
import java.io.File
class ZipHelperTest {
@Before
fun setUp() {
File("out").let {
if (!it.exists()) it.mkdir()
}
}
@After
fun tearDown() {
File("out").deleteRecursively()
}
@Test
fun unzip() {
val zf = ZipHelperTest::class.java.classLoader.getResource("appcompat.zip").file
ZipHelper.unzip(File(zf).path, "out")
File("out").deleteRecursively()
}
@Test
fun dumpEntry() {
val zf = ZipHelperTest::class.java.classLoader.getResource("appcompat.zip").file
ZipFile(zf).use {
it.dumpEntry("webview.log", File("out/webview.log"))
}
}
}

@ -0,0 +1,27 @@
-----BEGIN CERTIFICATE-----
MIIEqDCCA5CgAwIBAgIJALOZgIbQVs/6MA0GCSqGSIb3DQEBBAUAMIGUMQswCQYD
VQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4g
VmlldzEQMA4GA1UEChMHQW5kcm9pZDEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UE
AxMHQW5kcm9pZDEiMCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbTAe
Fw0wODA0MTUyMjQwNTBaFw0zNTA5MDEyMjQwNTBaMIGUMQswCQYDVQQGEwJVUzET
MBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEQMA4G
A1UEChMHQW5kcm9pZDEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9p
ZDEiMCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbTCCASAwDQYJKoZI
hvcNAQEBBQADggENADCCAQgCggEBAJx4BZKsDV04HN6qZezIpgBuNkgMbXIHsSAR
vlCGOqvitV0Amt9xRtbyICKAx81Ne9smJDuKgGwms0sTdSOkkmgiSQTcAUk+fArP
GgXIdPabA3tgMJ2QdNJCgOFrrSqHNDYZUer3KkgtCbIEsYdeEqyYwap3PWgAuer9
5W1Yvtjo2hb5o2AJnDeoNKbf7be2tEoEngeiafzPLFSW8s821k35CjuNjzSjuqtM
9TNxqydxmzulh1StDFP8FOHbRdUeI0+76TybpO35zlQmE1DsU1YHv2mi/0qgfbX3
6iANCabBtJ4hQC+J7RGQiTqrWpGA8VLoL4WkV1PPX8GQccXuyCcCAQOjgfwwgfkw
HQYDVR0OBBYEFE/koLPdnLop9x1yh8Tnw48ghsKZMIHJBgNVHSMEgcEwgb6AFE/k
oLPdnLop9x1yh8Tnw48ghsKZoYGapIGXMIGUMQswCQYDVQQGEwJVUzETMBEGA1UE
CBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEQMA4GA1UEChMH
QW5kcm9pZDEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDEiMCAG
CSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbYIJALOZgIbQVs/6MAwGA1Ud
EwQFMAMBAf8wDQYJKoZIhvcNAQEEBQADggEBAFclUbjZOh9z3g9tRp+G2tZwFAAp
PIigzXzXeLc9r8wZf6t25iEuVsHHYc/EL9cz3lLFCuCIFM78CjtaGkNGBU2Cnx2C
tCsgSL+ItdFJKe+F9g7dEtctVWV+IuPoXQTIMdYT0Zk4u4mCJH+jISVroS0dao+S
6h2xw3Mxe6DAN/DRr/ZFrvIkl5+6bnoUvAJccbmBOM7z3fwFlhfPJIRc97QNY4L3
J17XOElatuWTG5QhdlxJG3L7aOCA29tYwgKdNHyLMozkPvaosVUz7fvpib1qSN1L
IC7alMarjdW4OZID2q4u1EYjLk/pvZYTlMYwDlE448/Shebk5INTjLixs1c=
-----END CERTIFICATE-----

@ -0,0 +1,9 @@
-----BEGIN PUBLIC KEY-----
MIIBIDANBgkqhkiG9w0BAQEFAAOCAQ0AMIIBCAKCAQEA1pMZBN7GCySx7cdi4NnY
JT4+zWzrHeL/Boyo6LyozWvTeG6nCqds5g67D5k1Wf/ZPnepQ+foPUtkuOT+otPm
VvHiZ6gbv7IwtXjCBEO+THIYuEb1IRWG8DihTonCvjh/jr7Pj8rD2h7jMMnqk9Cn
w9xK81AiDVAIBzLggJcX7moFM1nmppTsLLPyhKCkZsh6lNg7MQk6ZzcuL2QSwG5t
QvFYGN/+A4HMDNRE2mzdw7gkWBlIAbMlZBNPv96YySh3SNv1Z2pUDYFUyLvKB7ni
R1UzEcRrmvdv3uzMjmnnyKLQjngmIJQ/mXJ9PAT+cpkdmd+brjigshd/ox1bav7p
HwIBAw==
-----END PUBLIC KEY-----

@ -0,0 +1,27 @@
-----BEGIN CERTIFICATE-----
MIIEqDCCA5CgAwIBAgIJAJNurL4H8gHfMA0GCSqGSIb3DQEBBQUAMIGUMQswCQYD
VQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4g
VmlldzEQMA4GA1UEChMHQW5kcm9pZDEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UE
AxMHQW5kcm9pZDEiMCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbTAe
Fw0wODAyMjkwMTMzNDZaFw0zNTA3MTcwMTMzNDZaMIGUMQswCQYDVQQGEwJVUzET
MBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEQMA4G
A1UEChMHQW5kcm9pZDEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9p
ZDEiMCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbTCCASAwDQYJKoZI
hvcNAQEBBQADggENADCCAQgCggEBANaTGQTexgskse3HYuDZ2CU+Ps1s6x3i/waM
qOi8qM1r03hupwqnbOYOuw+ZNVn/2T53qUPn6D1LZLjk/qLT5lbx4meoG7+yMLV4
wgRDvkxyGLhG9SEVhvA4oU6Jwr44f46+z4/Kw9oe4zDJ6pPQp8PcSvNQIg1QCAcy
4ICXF+5qBTNZ5qaU7Cyz8oSgpGbIepTYOzEJOmc3Li9kEsBubULxWBjf/gOBzAzU
RNps3cO4JFgZSAGzJWQTT7/emMkod0jb9WdqVA2BVMi7yge54kdVMxHEa5r3b97s
zI5p58ii0I54JiCUP5lyfTwE/nKZHZnfm644oLIXf6MdW2r+6R8CAQOjgfwwgfkw
HQYDVR0OBBYEFEhZAFY9JyxGrhGGBaR0GawJyowRMIHJBgNVHSMEgcEwgb6AFEhZ
AFY9JyxGrhGGBaR0GawJyowRoYGapIGXMIGUMQswCQYDVQQGEwJVUzETMBEGA1UE
CBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEQMA4GA1UEChMH
QW5kcm9pZDEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDEiMCAG
CSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbYIJAJNurL4H8gHfMAwGA1Ud
EwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAHqvlozrUMRBBVEY0NqrrwFbinZa
J6cVosK0TyIUFf/azgMJWr+kLfcHCHJsIGnlw27drgQAvilFLAhLwn62oX6snb4Y
LCBOsVMR9FXYJLZW2+TcIkCRLXWG/oiVHQGo/rWuWkJgU134NDEFJCJGjDbiLCpe
+ZTWHdcwauTJ9pUbo8EvHRkU3cYfGmLaLfgn9gP+pWA7LFQNvXwBnDa6sppCccEX
31I828XzgXpJ4O+mDL1/dBd+ek8ZPUP0IgdyZm5MTYPhvVqGCHzzTy3sIeJFymwr
sBbmg2OAUNLEMO6nwmocSdN2ClirfxqCzJOLSDE4QyS9BAH6EhY6UFcOaE0=
-----END CERTIFICATE-----

@ -11,3 +11,4 @@ include("aosp:libsparse:append2simg")
include("aosp:libavb1.1")
include("aosp:libavb1.2")
include("avbImpl")
include("helper")

Loading…
Cancel
Save