diff --git a/.travis.yml b/.travis.yml index 217b230..678b12d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -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 diff --git a/README.md b/README.md index b8ee27e..cb6e808 100644 --- a/README.md +++ b/README.md @@ -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 vbmeta.img ``` Your boot.img.signed and vbmeta.img.signd will be updated together. -* sparse vendor.img - -```bash -cp 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. diff --git a/aosp/avb/avbtool.v1.2.py b/aosp/avb/avbtool.v1.2.py index 1211df3..13f5594 100755 --- a/aosp/avb/avbtool.v1.2.py +++ b/aosp/avb/avbtool.v1.2.py @@ -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. diff --git a/aosp/build/tools/extract_kernel.py b/aosp/build/tools/extract_kernel.py index df2d741..44fbcdf 100755 --- a/aosp/build/tools/extract_kernel.py +++ b/aosp/build/tools/extract_kernel.py @@ -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.', diff --git a/avbImpl/build.gradle b/avbImpl/build.gradle index 8d786d1..58b060d 100644 --- a/avbImpl/build.gradle +++ b/avbImpl/build.gradle @@ -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" } diff --git a/avbImpl/src/avbVerifier/cpp/main.cpp b/avbImpl/src/avbVerifier/cpp/main.cpp index aa54ee2..a0a924c 100644 --- a/avbImpl/src/avbVerifier/cpp/main.cpp +++ b/avbImpl/src/avbVerifier/cpp/main.cpp @@ -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; } diff --git a/avbImpl/src/avbx/cpp/CfigAvbOps.cpp b/avbImpl/src/avbx/cpp/CfigAvbOps.cpp index fb5b0ee..5669a06 100644 --- a/avbImpl/src/avbx/cpp/CfigAvbOps.cpp +++ b/avbImpl/src/avbx/cpp/CfigAvbOps.cpp @@ -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; diff --git a/bbootimg/build.gradle.kts b/bbootimg/build.gradle.kts index 3228ecb..ba12742 100644 --- a/bbootimg/build.gradle.kts +++ b/bbootimg/build.gradle.kts @@ -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 { diff --git a/bbootimg/src/main/kotlin/KeyUtil.kt b/bbootimg/src/main/kotlin/KeyUtil.kt deleted file mode 100644 index ead062a..0000000 --- a/bbootimg/src/main/kotlin/KeyUtil.kt +++ /dev/null @@ -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)) - } - } -} \ No newline at end of file diff --git a/bbootimg/src/main/kotlin/avb/Avb.kt b/bbootimg/src/main/kotlin/avb/Avb.kt index f1b1133..6ea12fd 100644 --- a/bbootimg/src/main/kotlin/avb/Avb.kt +++ b/bbootimg/src/main/kotlin/avb/Avb.kt @@ -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") + } + } } } diff --git a/bbootimg/src/main/kotlin/avb/blob/AuthBlob.kt b/bbootimg/src/main/kotlin/avb/blob/AuthBlob.kt index 06bfb51..0d77817 100644 --- a/bbootimg/src/main/kotlin/avb/blob/AuthBlob.kt +++ b/bbootimg/src/main/kotlin/avb/blob/AuthBlob.kt @@ -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)) } diff --git a/bbootimg/src/main/kotlin/avb/blob/AuxBlob.kt b/bbootimg/src/main/kotlin/avb/blob/AuxBlob.kt index 7ae0b26..1605c1c 100644 --- a/bbootimg/src/main/kotlin/avb/blob/AuxBlob.kt +++ b/bbootimg/src/main/kotlin/avb/blob/AuxBlob.kt @@ -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) diff --git a/bbootimg/src/main/kotlin/avb/desc/UnknownDescriptor.kt b/bbootimg/src/main/kotlin/avb/desc/UnknownDescriptor.kt index 796ddfa..0447609 100644 --- a/bbootimg/src/main/kotlin/avb/desc/UnknownDescriptor.kt +++ b/bbootimg/src/main/kotlin/avb/desc/UnknownDescriptor.kt @@ -82,7 +82,7 @@ class UnknownDescriptor(var data: ByteArray = byteArrayOf()) : Descriptor(0, 0, } fun parseDescriptors2(stream: InputStream, totalSize: Long): List { - log.info("Parse descriptors stream, SIZE = $totalSize") + log.debug("Parse descriptors stream, SIZE = $totalSize") val ret: MutableList = mutableListOf() var currentSize = 0L var seq = 0 diff --git a/bbootimg/src/main/kotlin/bootimg/Common.kt b/bbootimg/src/main/kotlin/bootimg/Common.kt index a433963..bd65879 100644 --- a/bbootimg/src/main/kotlin/bootimg/Common.kt +++ b/bbootimg/src/main/kotlin/bootimg/Common.kt @@ -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") diff --git a/bbootimg/src/main/kotlin/bootimg/cpio/AndroidCpio.kt b/bbootimg/src/main/kotlin/bootimg/cpio/AndroidCpio.kt index 39046b2..eb7d8e7 100644 --- a/bbootimg/src/main/kotlin/bootimg/cpio/AndroidCpio.kt +++ b/bbootimg/src/main/kotlin/bootimg/cpio/AndroidCpio.kt @@ -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") } diff --git a/bbootimg/src/main/kotlin/bootimg/v2/BootV2.kt b/bbootimg/src/main/kotlin/bootimg/v2/BootV2.kt index 03154f8..04c7e0a 100644 --- a/bbootimg/src/main/kotlin/bootimg/v2/BootV2.kt +++ b/bbootimg/src/main/kotlin/bootimg/v2/BootV2.kt @@ -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 ") diff --git a/bbootimg/src/main/kotlin/bootimg/v3/BootV3.kt b/bbootimg/src/main/kotlin/bootimg/v3/BootV3.kt index 6843c9b..a732e49 100644 --- a/bbootimg/src/main/kotlin/bootimg/v3/BootV3.kt +++ b/bbootimg/src/main/kotlin/bootimg/v3/BootV3.kt @@ -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) { diff --git a/bbootimg/src/main/kotlin/helper/KeyHelper.kt b/bbootimg/src/main/kotlin/helper/KeyHelper.kt deleted file mode 100644 index 0e1bde0..0000000 --- a/bbootimg/src/main/kotlin/helper/KeyHelper.kt +++ /dev/null @@ -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++ - } - } - } -} diff --git a/bbootimg/src/main/kotlin/kernel_util/KernelExtractor.kt b/bbootimg/src/main/kotlin/kernel_util/KernelExtractor.kt index 218c770..75efd5b 100644 --- a/bbootimg/src/main/kotlin/kernel_util/KernelExtractor.kt +++ b/bbootimg/src/main/kotlin/kernel_util/KernelExtractor.kt @@ -20,8 +20,8 @@ class KernelExtractor { val ret: MutableList = 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") diff --git a/bbootimg/src/main/kotlin/packable/BootImgParser.kt b/bbootimg/src/main/kotlin/packable/BootImgParser.kt index 1cd8385..895c3bc 100644 --- a/bbootimg/src/main/kotlin/packable/BootImgParser.kt +++ b/bbootimg/src/main/kotlin/packable/BootImgParser.kt @@ -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") - } - } } } diff --git a/bbootimg/src/main/kotlin/packable/VendorBootParser.kt b/bbootimg/src/main/kotlin/packable/VendorBootParser.kt index 2f50387..fea0cbf 100644 --- a/bbootimg/src/main/kotlin/packable/VendorBootParser.kt +++ b/bbootimg/src/main/kotlin/packable/VendorBootParser.kt @@ -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") + } } } diff --git a/bbootimg/src/test/kotlin/HelperTest.kt b/bbootimg/src/test/kotlin/HelperTest.kt deleted file mode 100644 index aeec057..0000000 --- a/bbootimg/src/test/kotlin/HelperTest.kt +++ /dev/null @@ -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") - } -} diff --git a/bbootimg/src/test/kotlin/KeyUtilTest.kt b/bbootimg/src/test/kotlin/KeyUtilTest.kt index d37440d..8db5d18 100644 --- a/bbootimg/src/test/kotlin/KeyUtilTest.kt +++ b/bbootimg/src/test/kotlin/KeyUtilTest.kt @@ -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)) } } + diff --git a/bbootimg/src/test/kotlin/avb/BlobTest.kt b/bbootimg/src/test/kotlin/avb/BlobTest.kt index b508bb7..3740a7a 100644 --- a/bbootimg/src/test/kotlin/avb/BlobTest.kt +++ b/bbootimg/src/test/kotlin/avb/BlobTest.kt @@ -2,33 +2,50 @@ package avb import avb.alg.Algorithms import avb.blob.AuxBlob +import cfig.helper.Helper +import cfig.helper.KeyHelper import org.apache.commons.codec.binary.Hex +import org.bouncycastle.asn1.pkcs.RSAPrivateKey import org.junit.Assert.assertEquals import org.junit.Test +import java.io.ByteArrayInputStream @OptIn(ExperimentalUnsignedTypes::class) class BlobTest { @Test fun testEncodedKey2048() { - val keyStr = "2d2d2d2d2d424547494e205253412050524956415445204b45592d2d2d2d2d0a4d4949456f77494241414b4341514541786c56523354496b6f75414f7648373976614a54674668706676564b514965566b46525a5056584b2f7a5930477672680a344a4171476a4a6f572f50667251763573644433367174484833612b4735684c5a364e692b742f6d74666a7563785a66754c4743336b6d4a3154335871454b5a0a67585849324952377656536f496d5245764451474544794a7774487a4c414e6c6b624767306367685668575a5343416e644f3842656e616c43327639342f72740a44666b50656b48366467553353663430543073425365535939346d4f7a5461714f52327066563172576c4c5264576d6f33337a654842763532526c627430644d0a755841757265585769487a746b6d35474342433164674d2b4361784e74697a4e45674339314b63443078755243434d325778482b72316c70737a79494a4463740a596272466d5645596c2f6b6a517061666879374e736b316671535479526472695a53596d5451494441514142416f49424151432b6b4a6761437558387759416e0a5358575130666d645a6c586e4d4e5270634630613070443053417a47623152645942584d615869717479686977633533505078734344644e65636a6179494d640a6a4a56585054774c685472754f674d532f6270336763675777563334554856344c4a58474f4741452b6a625330686244424d6975644f596d6a36526d567368700a7a3947317a5a4353514e4d584861577345596b58353958707a7a6f423338346e52756c3251674574777a554e5239586c707a67744a424c6b335341436b76734e0a6d512f445738495748584c6738764c6e314c7a564a32653342313648344d6f45325443487871664d67723033494452524a6f676b656e517551734668657659540a6f2f6d4a79485357617656677a4d48473949356d2b65657046345779686a31593457794b41754d492b39644841582f68374c74385846435143683544626b56470a7a47723334735742416f4742414f73376e37595a714e616167756f7666496452527378785a7231794a41794473723677337947496d445a596a753463345759390a3565734f326b50334641347030633746685146356f4f623172427548455070333663704c346147654b38376361715466713633575a41756a6f545a7072394c700a4252626b4c37772f7847376a70512f636c70413873487a484751732f6e656c786f4f744337453131384669526776442f6a64686c4d794c39416f4742414e66580a76796f4e3170706c665432785238514f6a535a2b513335532f2b5341744d75426e4878336c307148326262426a63764d314d4e44576a6e5244796159686952750a692b4b4137747166696230392b58704233673544364f76376c732f4c647830532f56636d565774696132484b387938694c47746f6b6f425a4b513541614658320a695155382b744334683639476e4a59514b714e776743557a68382b674858355934366f4469546d52416f474159704f78386c582b637a42382f4461364d4e72570a6d495a4e543861745a4c4573447332414e455652784453496354435a4a4964372b6d31572b6e526f6179634c54574e6f775a312b3245724c765231302b4147590a62375973373957673969645961593979476e396c6e5a734d7a4169754c6579497658635371676a76414b6c565772684f51464f756768764e5776466c383559790a6f5753434d6c5069544c747437434373434b73674b7545436759426764497036475a7349666b67636c4b653068716776526f65553454523367636a4a6c4d39410a6c42546f2b704b686142656374706c783952785238416e73506f626271776361486e496641754b447a6a6b356d45764b5a6a436c6e46584634484148627941460a6e527a5a457939586b57466863383054357252705a4f3743377164786d753261694b69784d3356334c332f3055353871554c4544627562484d773962456841540a5075644938514b4267484545694d6d2f687239543431686251692f4c59616e576e6c46773175652b6f734b75463862585175786e6e484e7546542f632b392f410a76576867714736624f4548752b702f495072596d3474424d596c77737968346e5843794767444a4c624c49667a4b774b4157437448394c776e794456684f6f770a474839736864522b7357334577393778656630324b414834566c4e414e456d42563473514e7157577673597263466d32724f644c0a2d2d2d2d2d454e44205253412050524956415445204b45592d2d2d2d2d0a" - val encodedKey = AuxBlob.encodePubKey(Algorithms.get("SHA512_RSA2048")!!, Hex.decodeHex(keyStr)) - val expectedKeyEnc = "00000800c9d87d7bc65551dd3224a2e00ebc7efdbda2538058697ef54a4087959054593d55caff36341afae1e0902a1a32685bf3dfad0bf9b1d0f7eaab471f76be1b984b67a362fadfe6b5f8ee73165fb8b182de4989d53dd7a842998175c8d8847bbd54a8226444bc3406103c89c2d1f32c036591b1a0d1c82156159948202774ef017a76a50b6bfde3faed0df90f7a41fa76053749fe344f4b0149e498f7898ecd36aa391da97d5d6b5a52d17569a8df7cde1c1bf9d9195bb7474cb9702eade5d6887ced926e460810b576033e09ac4db62ccd1200bdd4a703d31b910823365b11feaf5969b33c8824372d61bac599511897f92342969f872ecdb24d5fa924f245dae26526264d1efa3b53abc5d379532f66b82194669a936f3526438c8f96b9ffaccdf4006ebeb673548450854653d5dd43feb26a784079569f86f3d3813b3d409035329a517ff8c34bc7d6a1ca30fb1bfd270ab8644134c117dea1769aebcf0c50d913f50d0b2c9924cbb5b4f8c60ad026b15bfd4d44669db076aa799dc05c3b9436c18ffec9d25a6aa046e1a28b2f516151a3369183b4fbcda9403446988a1a91dfd92c3abf573a464620f2f0bc315e29fe5890325c6697999a8e1523eba947d363c618bb8ed2209f78af71b32e0889a1f17db130a0e61bbd6ec8f633e1dab0bd1641fe076f6e978b6a33d2d780036a4de70582281fef6aa9757ee14ee2955b4fe6dc03b981" - assertEquals(expectedKeyEnc, Hex.encodeHexString(encodedKey)) + val keyStr = + "2d2d2d2d2d424547494e205253412050524956415445204b45592d2d2d2d2d0a4d4949456f77494241414b4341514541786c56523354496b6f75414f7648373976614a54674668706676564b514965566b46525a5056584b2f7a5930477672680a344a4171476a4a6f572f50667251763573644433367174484833612b4735684c5a364e692b742f6d74666a7563785a66754c4743336b6d4a3154335871454b5a0a67585849324952377656536f496d5245764451474544794a7774487a4c414e6c6b624767306367685668575a5343416e644f3842656e616c43327639342f72740a44666b50656b48366467553353663430543073425365535939346d4f7a5461714f52327066563172576c4c5264576d6f33337a654842763532526c627430644d0a755841757265585769487a746b6d35474342433164674d2b4361784e74697a4e45674339314b63443078755243434d325778482b72316c70737a79494a4463740a596272466d5645596c2f6b6a517061666879374e736b316671535479526472695a53596d5451494441514142416f49424151432b6b4a6761437558387759416e0a5358575130666d645a6c586e4d4e5270634630613070443053417a47623152645942584d615869717479686977633533505078734344644e65636a6179494d640a6a4a56585054774c685472754f674d532f6270336763675777563334554856344c4a58474f4741452b6a625330686244424d6975644f596d6a36526d567368700a7a3947317a5a4353514e4d584861577345596b58353958707a7a6f423338346e52756c3251674574777a554e5239586c707a67744a424c6b335341436b76734e0a6d512f445738495748584c6738764c6e314c7a564a32653342313648344d6f45325443487871664d67723033494452524a6f676b656e517551734668657659540a6f2f6d4a79485357617656677a4d48473949356d2b65657046345779686a31593457794b41754d492b39644841582f68374c74385846435143683544626b56470a7a47723334735742416f4742414f73376e37595a714e616167756f7666496452527378785a7231794a41794473723677337947496d445a596a753463345759390a3565734f326b50334641347030633746685146356f4f623172427548455070333663704c346147654b38376361715466713633575a41756a6f545a7072394c700a4252626b4c37772f7847376a70512f636c70413873487a484751732f6e656c786f4f744337453131384669526776442f6a64686c4d794c39416f4742414e66580a76796f4e3170706c665432785238514f6a535a2b513335532f2b5341744d75426e4878336c307148326262426a63764d314d4e44576a6e5244796159686952750a692b4b4137747166696230392b58704233673544364f76376c732f4c647830532f56636d565774696132484b387938694c47746f6b6f425a4b513541614658320a695155382b744334683639476e4a59514b714e776743557a68382b674858355934366f4469546d52416f474159704f78386c582b637a42382f4461364d4e72570a6d495a4e543861745a4c4573447332414e455652784453496354435a4a4964372b6d31572b6e526f6179634c54574e6f775a312b3245724c765231302b4147590a62375973373957673969645961593979476e396c6e5a734d7a4169754c6579497658635371676a76414b6c565772684f51464f756768764e5776466c383559790a6f5753434d6c5069544c747437434373434b73674b7545436759426764497036475a7349666b67636c4b653068716776526f65553454523367636a4a6c4d39410a6c42546f2b704b686142656374706c783952785238416e73506f626271776361486e496641754b447a6a6b356d45764b5a6a436c6e46584634484148627941460a6e527a5a457939586b57466863383054357252705a4f3743377164786d753261694b69784d3356334c332f3055353871554c4544627562484d773962456841540a5075644938514b4267484545694d6d2f687239543431686251692f4c59616e576e6c46773175652b6f734b75463862585175786e6e484e7546542f632b392f410a76576867714736624f4548752b702f495072596d3474424d596c77737968346e5843794767444a4c624c49667a4b774b4157437448394c776e794456684f6f770a474839736864522b7357334577393778656630324b414834566c4e414e456d42563473514e7157577673597263466d32724f644c0a2d2d2d2d2d454e44205253412050524956415445204b45592d2d2d2d2d0a" + val encodedKey = AuxBlob.encodePubKey(Algorithms.get("SHA512_RSA2048")!!, Helper.fromHexString(keyStr)) + val expectedKeyEnc = + "00000800c9d87d7bc65551dd3224a2e00ebc7efdbda2538058697ef54a4087959054593d55caff36341afae1e0902a1a32685bf3dfad0bf9b1d0f7eaab471f76be1b984b67a362fadfe6b5f8ee73165fb8b182de4989d53dd7a842998175c8d8847bbd54a8226444bc3406103c89c2d1f32c036591b1a0d1c82156159948202774ef017a76a50b6bfde3faed0df90f7a41fa76053749fe344f4b0149e498f7898ecd36aa391da97d5d6b5a52d17569a8df7cde1c1bf9d9195bb7474cb9702eade5d6887ced926e460810b576033e09ac4db62ccd1200bdd4a703d31b910823365b11feaf5969b33c8824372d61bac599511897f92342969f872ecdb24d5fa924f245dae26526264d1efa3b53abc5d379532f66b82194669a936f3526438c8f96b9ffaccdf4006ebeb673548450854653d5dd43feb26a784079569f86f3d3813b3d409035329a517ff8c34bc7d6a1ca30fb1bfd270ab8644134c117dea1769aebcf0c50d913f50d0b2c9924cbb5b4f8c60ad026b15bfd4d44669db076aa799dc05c3b9436c18ffec9d25a6aa046e1a28b2f516151a3369183b4fbcda9403446988a1a91dfd92c3abf573a464620f2f0bc315e29fe5890325c6697999a8e1523eba947d363c618bb8ed2209f78af71b32e0889a1f17db130a0e61bbd6ec8f633e1dab0bd1641fe076f6e978b6a33d2d780036a4de70582281fef6aa9757ee14ee2955b4fe6dc03b981" + assertEquals(expectedKeyEnc, Helper.toHexString(encodedKey)) + run {//decode pub key and check + val decodedKey = KeyHelper.decodeRSAkey(encodedKey) + //val rsa = KeyHelper.parsePemPrivateKeyBC(ByteArrayInputStream(Helper.fromHexString(keyStr))) //BC RSA + val rsa = KeyHelper.parse(Helper.fromHexString(keyStr)) as RSAPrivateKey //BC RSA + assert(rsa.modulus.equals(decodedKey.modulus)) + assert(rsa.publicExponent.equals(decodedKey.publicExponent)) + } } @Test fun testEncodeKey4096() { - val keyStr = "2d2d2d2d2d424547494e205253412050524956415445204b45592d2d2d2d2d0a4d49494a4b51494241414b43416745413241537634394f456248344e695433436a4e4d5356656c697966455058737757637174456643786c53705331466973410a757762764577645454506c6b7553683647345359694e686e704350357030766353672f334f686975564b67562f724374724458614f36306e764b2f6f307938330a4e4e5a524b3278614a396557427139727549444b2b6a43307359577a546171717778593047726a6e782f7235435865726c355072524b3750494c7a77674248620a4977784863626c74316e74675234635756704f337769716173457742444444596b3466773757364c766a42623971617633594238525636506b5a4e65525036340a676766756563712f4d584e69574f504e784c7a434552326853722f2b4a333268396a576a587372635679382b384d6c64686d72347232616e37633234376146660a757075464774554a7270524f4f382f4c584d6c356750664d706b716f61746a544d52483539674a6a4b686f743052706d47785a42766233335463424b3553644a0a583339593479637435636c6d446c4934466a6a3746757454502b623936614a654a566e596555582f4130776d6f6742616a734a526f525835652f5263675a73590a527a58594c515870725138316442576a6a6f764d4a39703858655436424e4d4643376f36736b6c464c3066484455452f6c34424e5038473175334266707a65760a534349535253373144346553346f51422b5249504642556b7a6f6d5a37726e45463342774665712b786d7766597250304c5261482b315965526175754d7552650a6b6531545a6c36393761336d456a6b4e67386e6f6132777470653745576d61756a4a66584457784a782f58456b6a474c4365347a32716b33746b6b592b4135670a5263677a6b6538675678432b654332444a74624b59666b76344c38464d464a61456877417031334d664337466c59756a4f2f42444c6c3764414e7343417745410a41514b43416741576f4c38502f57736b746a755377623573592f764b74677a634848314172393432477379737554585044793638364c7046335238542f6a4e790a6e376b3255424169613878536f5743523642625275486556356f412b504c47654f704537516153666f6e422b79632b63793078334f7233737366714573752f710a746f47487037352f38445853365745304b303478393475317264433962397350727247426c57434c477a714d306b62754a667948586464336e32536f6641554f0a62355152536778442b327448557045726f4871486e574a436166344a30516567583435796b746c664f594e4b2f50484c44515856386c792f656a6333324d34590a5476376855744f4f4a54757138564367394f575a6d325a6f3151754d3958454a54504370356c332b6f35767a4f3679686b32676f7444764433324364412b336b0a744c4a525035344d31536e2b4958623167474b4e39724b4174474a62656e5749506c4e4f626851676b627747383951642b3572664d587369507631486c31744b0a2b7471776a4438322f48332f456c61614d6e77484370656f47537039354f626c416f426a7a6a4d50324b7362764b53644c384f2f7266316333754f77392b44460a6374683053413879335a7a493131674a746232514d475572436e79356e34735047476263337833384e644c6877626b504b5a7936304f69543467326b4e7064590a644969746d414d4c326f747474694634414a4d36417261506b3859567a6b504c546b736f4c33617a50427961356c496f444932483351765474537670586b58500a794b6368734453575962647166706c71432f5830446a70322f5a64386a704e3549362b3161536d70546d6277782f4a546c6c59314e383946525a4c4964786f680a326b38314c5069586845367552626a696f4a556c626e45574970593279324e32436c6d78706a68302f496358643158496d514b4341514541375a61692b796a6a0a387869743234614f395466336d5a4258426a5361446f646a43324b533179436341495870365337614830775a6970795a70516a7973337a614251794d525946470a625171496656416136696e5779446f6f6662414a484d7535425663484642505a765353355968446a6338585a3564715343787a497a396f70497141626d2b62340a6145562f3341334a6b6935447938792f356a323147414b3459346d71514f597a6e653762444769334879753034314d474d3471664963496b53354e31654857340a73445a4a68362b4b357475784e355458336e445a53706d396c754e48386d4c47674b415a313562314c715841744d3579636f4259394876303832737550506f6d0a4f2b72307962645258366e445348382b313179324b6950326b645649554843476b776c716772757835595a796a435a50774f764550687a536f4f532b764269460a555658413869646e784e4c6b31514b4341514541364d496968445358782b33353066577168512f3351633667412f74324331354a774a392b754657412b676a640a632f686e3548636d6e6d424a4e345230346e4c472f61553953517572383761346d6e432f4d70394a4941526a486c5a2f574e54345530734a79504556526735550a5a3956616a417563577769304a794a59434f31454d4d7936384a7038716c5472694b2f4c376e624438364a4a354153786a6f6a694e2f3070734b2f506b3630460a52722b73684b5069336a52513142446a447441784f666f346374662f6e4662554d34625930464e50514d50375765736f534b55304e42435252366430643274710a59666c4d6a495148782b4e373450356a4564534348545647516d2b646a3437705574336c4c504c576330625831472f47656b775850344e5573522f37304873690a6277786b4e6e4b325453477a6b743272634f6e757450313235724a753657705637534e727139726d37774b43415141664d524f636e625776694b48716e4450510a6864522f324b39554a54764568496e41534f5332555a5770692b733172657a394275536a69674f7834776261415a3474343450573743337579743834644866550a486b495162334935626738454e4d724a704b394e4e3333796b77757a6b44774d537746635a2b4763693937685375627a6f4d6c2f496b6569694e314d61704c340a47684c556773442b33554d564c2b593953796d4b383633374967796f434764694e44362f535873613853774c4a6f3356546a717834654b70583763766c53424c0a52725278633530546d775573416873643443446c39596e5341544c6a56764a4265596c664d32746246506159776c31615238762b50576b666e4b3065666d36300a66486b69333348456e47746542504b7547713476775659706e3662594777517a2b66363333352f4132444d665a484653706a56555248506352634862434d6c610a30635578416f4942415143323565594e6b4f3437386d6f2b62426245584a6c6b6f714c6d766a417947724e466f343846396c705648365930764e75576b584a4e0a5055674c556841753652596f746a47454e71473137727a387a742f505059394f6b325033734f783874303079316d496e2f686c445a58733535464d30664f4d750a505a616973634150733748447a76794f6d4461682b667a692b5a443848324d33445332572b5945306961654a6132765a4a5332743032573042475869444933330a495a44714d794c59767777506a4f6e53684a7964457a58494434784c6c30744e6a7a4c786f3347534e41376a59716c6d627456384358496337724d534c3657560a6b7449444b4b4a636e6d706e3354634b6558364d456a615349543832704e4f5333665933506d58754c2b434d7a6677382b75373745656371373866486154694c0a50354a474d393346366d7a693139455930746d496e55424d435774514c63454e416f494241514367304b614f6b6238543336717a5072746762666f75304532440a756664704c3175676d443465644f464b51423566444651684c6e534556534a71334b5567346b57735861705164734264366b4c6478532b4b364d51724c427a720a34746630633755434631417a576b3677584d45785a386d526232526b475a595142324464796846423354506d6e71394357384a43712b366b78672f776b5534730a764d344a587a676371566f53663432514a6c2b4239776165576867304254577830316c616c34647338384876454b6d4530696b354777694462723745764444770a453655625a745163496f535449495a4467597156466652324441686f3377584a52734f58683433336c454a3858376343447a726e674662516e6c4b7270774d4c0a58676d30534955632b4e6635706f4d4d3372664c464b3737742f6f6234772b355077524b636f536e69794178724864366277796b59413856757964760a2d2d2d2d2d454e44205253412050524956415445204b45592d2d2d2d2d0a" + val keyStr = + "2d2d2d2d2d424547494e205253412050524956415445204b45592d2d2d2d2d0a4d49494a4b51494241414b43416745413241537634394f456248344e695433436a4e4d5356656c697966455058737757637174456643786c53705331466973410a757762764577645454506c6b7553683647345359694e686e704350357030766353672f334f686975564b67562f724374724458614f36306e764b2f6f307938330a4e4e5a524b3278614a396557427139727549444b2b6a43307359577a546171717778593047726a6e782f7235435865726c355072524b3750494c7a77674248620a4977784863626c74316e74675234635756704f337769716173457742444444596b3466773757364c766a42623971617633594238525636506b5a4e65525036340a676766756563712f4d584e69574f504e784c7a434552326853722f2b4a333268396a576a587372635679382b384d6c64686d72347232616e37633234376146660a757075464774554a7270524f4f382f4c584d6c356750664d706b716f61746a544d52483539674a6a4b686f743052706d47785a42766233335463424b3553644a0a583339593479637435636c6d446c4934466a6a3746757454502b623936614a654a566e596555582f4130776d6f6742616a734a526f525835652f5263675a73590a527a58594c515870725138316442576a6a6f764d4a39703858655436424e4d4643376f36736b6c464c3066484455452f6c34424e5038473175334266707a65760a534349535253373144346553346f51422b5249504642556b7a6f6d5a37726e45463342774665712b786d7766597250304c5261482b315965526175754d7552650a6b6531545a6c36393761336d456a6b4e67386e6f6132777470653745576d61756a4a66584457784a782f58456b6a474c4365347a32716b33746b6b592b4135670a5263677a6b6538675678432b654332444a74624b59666b76344c38464d464a61456877417031334d664337466c59756a4f2f42444c6c3764414e7343417745410a41514b43416741576f4c38502f57736b746a755377623573592f764b74677a634848314172393432477379737554585044793638364c7046335238542f6a4e790a6e376b3255424169613878536f5743523642625275486556356f412b504c47654f704537516153666f6e422b79632b63793078334f7233737366714573752f710a746f47487037352f38445853365745304b303478393475317264433962397350727247426c57434c477a714d306b62754a667948586464336e32536f6641554f0a62355152536778442b327448557045726f4871486e574a436166344a30516567583435796b746c664f594e4b2f50484c44515856386c792f656a6333324d34590a5476376855744f4f4a54757138564367394f575a6d325a6f3151754d3958454a54504370356c332b6f35767a4f3679686b32676f7444764433324364412b336b0a744c4a525035344d31536e2b4958623167474b4e39724b4174474a62656e5749506c4e4f626851676b627747383951642b3572664d587369507631486c31744b0a2b7471776a4438322f48332f456c61614d6e77484370656f47537039354f626c416f426a7a6a4d50324b7362764b53644c384f2f7266316333754f77392b44460a6374683053413879335a7a493131674a746232514d475572436e79356e34735047476263337833384e644c6877626b504b5a7936304f69543467326b4e7064590a644969746d414d4c326f747474694634414a4d36417261506b3859567a6b504c546b736f4c33617a50427961356c496f444932483351765474537670586b58500a794b6368734453575962647166706c71432f5830446a70322f5a64386a704e3549362b3161536d70546d6277782f4a546c6c59314e383946525a4c4964786f680a326b38314c5069586845367552626a696f4a556c626e45574970593279324e32436c6d78706a68302f496358643158496d514b4341514541375a61692b796a6a0a387869743234614f395466336d5a4258426a5361446f646a43324b533179436341495870365337614830775a6970795a70516a7973337a614251794d525946470a625171496656416136696e5779446f6f6662414a484d7535425663484642505a765353355968446a6338585a3564715343787a497a396f70497141626d2b62340a6145562f3341334a6b6935447938792f356a323147414b3459346d71514f597a6e653762444769334879753034314d474d3471664963496b53354e31654857340a73445a4a68362b4b357475784e355458336e445a53706d396c754e48386d4c47674b415a313562314c715841744d3579636f4259394876303832737550506f6d0a4f2b72307962645258366e445348382b313179324b6950326b645649554843476b776c716772757835595a796a435a50774f764550687a536f4f532b764269460a555658413869646e784e4c6b31514b4341514541364d496968445358782b33353066577168512f3351633667412f74324331354a774a392b754657412b676a640a632f686e3548636d6e6d424a4e345230346e4c472f61553953517572383761346d6e432f4d70394a4941526a486c5a2f574e54345530734a79504556526735550a5a3956616a417563577769304a794a59434f31454d4d7936384a7038716c5472694b2f4c376e624438364a4a354153786a6f6a694e2f3070734b2f506b3630460a52722b73684b5069336a52513142446a447441784f666f346374662f6e4662554d34625930464e50514d50375765736f534b55304e42435252366430643274710a59666c4d6a495148782b4e373450356a4564534348545647516d2b646a3437705574336c4c504c576330625831472f47656b775850344e5573522f37304873690a6277786b4e6e4b325453477a6b743272634f6e757450313235724a753657705637534e727139726d37774b43415141664d524f636e625776694b48716e4450510a6864522f324b39554a54764568496e41534f5332555a5770692b733172657a394275536a69674f7834776261415a3474343450573743337579743834644866550a486b495162334935626738454e4d724a704b394e4e3333796b77757a6b44774d537746635a2b4763693937685375627a6f4d6c2f496b6569694e314d61704c340a47684c556773442b33554d564c2b593953796d4b383633374967796f434764694e44362f535873613853774c4a6f3356546a717834654b70583763766c53424c0a52725278633530546d775573416873643443446c39596e5341544c6a56764a4265596c664d32746246506159776c31615238762b50576b666e4b3065666d36300a66486b69333348456e47746542504b7547713476775659706e3662594777517a2b66363333352f4132444d665a484653706a56555248506352634862434d6c610a30635578416f4942415143323565594e6b4f3437386d6f2b62426245584a6c6b6f714c6d766a417947724e466f343846396c705648365930764e75576b584a4e0a5055674c556841753652596f746a47454e71473137727a387a742f505059394f6b325033734f783874303079316d496e2f686c445a58733535464d30664f4d750a505a616973634150733748447a76794f6d4461682b667a692b5a443848324d33445332572b5945306961654a6132765a4a5332743032573042475869444933330a495a44714d794c59767777506a4f6e53684a7964457a58494434784c6c30744e6a7a4c786f3347534e41376a59716c6d627456384358496337724d534c3657560a6b7449444b4b4a636e6d706e3354634b6558364d456a615349543832704e4f5333665933506d58754c2b434d7a6677382b75373745656371373866486154694c0a50354a474d393346366d7a693139455930746d496e55424d435774514c63454e416f494241514367304b614f6b6238543336717a5072746762666f75304532440a756664704c3175676d443465644f464b51423566444651684c6e534556534a71334b5567346b57735861705164734264366b4c6478532b4b364d51724c427a720a34746630633755434631417a576b3677584d45785a386d526232526b475a595142324464796846423354506d6e71394357384a43712b366b78672f776b5534730a764d344a587a676371566f53663432514a6c2b4239776165576867304254577830316c616c34647338384876454b6d4530696b354777694462723745764444770a453655625a745163496f535449495a4467597156466652324441686f3377584a52734f58683433336c454a3858376343447a726e674662516e6c4b7270774d4c0a58676d30534955632b4e6635706f4d4d3372664c464b3737742f6f6234772b355077524b636f536e69794178724864366277796b59413856757964760a2d2d2d2d2d454e44205253412050524956415445204b45592d2d2d2d2d0a" val encodedKey = AuxBlob.encodePubKey(Algorithms.get("SHA256_RSA4096")!!, Hex.decodeHex(keyStr)) - val expectedKeyEnc = "0000100055d904add804afe3d3846c7e0d893dc28cd31255e962c9f10f5ecc1672ab447c2c654a94b5162b00bb06ef1307534cf964b9287a1b849888d867a423f9a74bdc4a0ff73a18ae54a815feb0adac35da3bad27bcafe8d32f3734d6512b6c5a27d79606af6bb880cafa30b4b185b34daaaac316341ab8e7c7faf90977ab9793eb44aecf20bcf08011db230c4771b96dd67b604787165693b7c22a9ab04c010c30d89387f0ed6e8bbe305bf6a6afdd807c455e8f91935e44feb88207ee79cabf31736258e3cdc4bcc2111da14abffe277da1f635a35ecadc572f3ef0c95d866af8af66a7edcdb8eda15fba9b851ad509ae944e3bcfcb5cc97980f7cca64aa86ad8d33111f9f602632a1a2dd11a661b1641bdbdf74dc04ae527495f7f58e3272de5c9660e52381638fb16eb533fe6fde9a25e2559d87945ff034c26a2005a8ec251a115f97bf45c819b184735d82d05e9ad0f357415a38e8bcc27da7c5de4fa04d3050bba3ab249452f47c70d413f97804d3fc1b5bb705fa737af482212452ef50f8792e28401f9120f141524ce8999eeb9c417707015eabec66c1f62b3f42d1687fb561e45abae32e45e91ed53665ebdedade612390d83c9e86b6c2da5eec45a66ae8c97d70d6c49c7f5c492318b09ee33daa937b64918f80e6045c83391ef205710be782d8326d6ca61f92fe0bf0530525a121c00a75dcc7c2ec5958ba33bf0432e5edd00db0db33799a9cd9cb743f7354421c28271ab8daab44111ec1e8dfc1482924e836a0a6b355e5de95ccc8cde39d14a5b5f63a964e00acb0bb85a7cc30be6befe8b0f7d348e026674016cca76ac7c67082f3f1aa62c60b3ffda8db8120c007fcc50a15c64a1e25f3265c99cbed60a13873c2a45470cca4282fa8965e789b48ff71ee623a5d059377992d7ce3dfde3a10bcf6c85a065f35cc64a635f6e3a3a2a8b6ab62fbbf8b24b62bc1a912566e369ca60490bf68abe3e7653c27aa8041775f1f303621b85b2b0ef8015b6d44edf71acdb2a04d4b421ba655657e8fa84a27d130eafd79a582aa381848d09a06ac1bbd9f586acbd756109e68c3d77b2ed3020e4001d97e8bfc7001b21b116e741672eec38bce51bb4062331711c49cd764a76368da3898b4a7af487c8150f3739f66d8019ef5ca866ce1b167921dfd73130c421dd345bd21a2b3e5df7eaca058eb7cb492ea0e3f4a74819109c04a7f42874c86f63202b462426191dd12c316d5a29a206a6b241cc0a27960996ac476578685198d6d8a62da0cfece274f282e397d97ed4f80b70433db17b9780d6cbd719bc630bfd4d88fe67acb8cc50b768b35bd61e25fc5f3c8db1337cb349013f71550e51ba6126faeae5b5e8aacfcd969fd6c15f5391ad05de20e751da5b9567edf4ee426570130b70141cc9e019ca5ff51d704b6c0674ecb52e77e174a1a399a0859ef1acd87e" + val expectedKeyEnc = + "0000100055d904add804afe3d3846c7e0d893dc28cd31255e962c9f10f5ecc1672ab447c2c654a94b5162b00bb06ef1307534cf964b9287a1b849888d867a423f9a74bdc4a0ff73a18ae54a815feb0adac35da3bad27bcafe8d32f3734d6512b6c5a27d79606af6bb880cafa30b4b185b34daaaac316341ab8e7c7faf90977ab9793eb44aecf20bcf08011db230c4771b96dd67b604787165693b7c22a9ab04c010c30d89387f0ed6e8bbe305bf6a6afdd807c455e8f91935e44feb88207ee79cabf31736258e3cdc4bcc2111da14abffe277da1f635a35ecadc572f3ef0c95d866af8af66a7edcdb8eda15fba9b851ad509ae944e3bcfcb5cc97980f7cca64aa86ad8d33111f9f602632a1a2dd11a661b1641bdbdf74dc04ae527495f7f58e3272de5c9660e52381638fb16eb533fe6fde9a25e2559d87945ff034c26a2005a8ec251a115f97bf45c819b184735d82d05e9ad0f357415a38e8bcc27da7c5de4fa04d3050bba3ab249452f47c70d413f97804d3fc1b5bb705fa737af482212452ef50f8792e28401f9120f141524ce8999eeb9c417707015eabec66c1f62b3f42d1687fb561e45abae32e45e91ed53665ebdedade612390d83c9e86b6c2da5eec45a66ae8c97d70d6c49c7f5c492318b09ee33daa937b64918f80e6045c83391ef205710be782d8326d6ca61f92fe0bf0530525a121c00a75dcc7c2ec5958ba33bf0432e5edd00db0db33799a9cd9cb743f7354421c28271ab8daab44111ec1e8dfc1482924e836a0a6b355e5de95ccc8cde39d14a5b5f63a964e00acb0bb85a7cc30be6befe8b0f7d348e026674016cca76ac7c67082f3f1aa62c60b3ffda8db8120c007fcc50a15c64a1e25f3265c99cbed60a13873c2a45470cca4282fa8965e789b48ff71ee623a5d059377992d7ce3dfde3a10bcf6c85a065f35cc64a635f6e3a3a2a8b6ab62fbbf8b24b62bc1a912566e369ca60490bf68abe3e7653c27aa8041775f1f303621b85b2b0ef8015b6d44edf71acdb2a04d4b421ba655657e8fa84a27d130eafd79a582aa381848d09a06ac1bbd9f586acbd756109e68c3d77b2ed3020e4001d97e8bfc7001b21b116e741672eec38bce51bb4062331711c49cd764a76368da3898b4a7af487c8150f3739f66d8019ef5ca866ce1b167921dfd73130c421dd345bd21a2b3e5df7eaca058eb7cb492ea0e3f4a74819109c04a7f42874c86f63202b462426191dd12c316d5a29a206a6b241cc0a27960996ac476578685198d6d8a62da0cfece274f282e397d97ed4f80b70433db17b9780d6cbd719bc630bfd4d88fe67acb8cc50b768b35bd61e25fc5f3c8db1337cb349013f71550e51ba6126faeae5b5e8aacfcd969fd6c15f5391ad05de20e751da5b9567edf4ee426570130b70141cc9e019ca5ff51d704b6c0674ecb52e77e174a1a399a0859ef1acd87e" assertEquals(expectedKeyEnc, Hex.encodeHexString(encodedKey)) } @Test fun testEncodeKey8192() { - val keyStr = "2d2d2d2d2d424547494e205253412050524956415445204b45592d2d2d2d2d0a4d4949534b67494241414b4342414541304433542b644953736d43486d37393777735830765666715557444a2f336d7644596f7a6c43616244686e474c6c53450a7041516266315a3854732b4f4d34705652484f4a554a4c305765624e646d5050476a737957517a367a5a4539366c515a4c3361764345587159565152363656350a3377644b2f6f68614d53526e4779454d4272716b56566246336743722b2f6972784433594b2b566f774f32574b732f3647724d647154413859354354462f4a650a707477735367354d4d6a723655614b3471446372656a33686b6742564776525633636a31736e4b3642723848755964466e7047475453306437554a6c4846676c0a74724748552f43424f393233686b48674a6157456a4330676953476a684b4b744c7a72566370445632792f6c57515039542f5434646a454149614871512b2b500a53644f535236707349475236685667536967743748436e45376e573731312f7266563555723945695670423034306d44496d4b5a6379382f2f544d6e5879644e0a314b595456642f33346664707a4d7053773569626c457262774f4c585654556d4f7a74596e706c343166654853762f6a506573487374506c666b6c494632766f0a475a456f6866397363517663754d3777454266432f615441394b33397a4d6d6b42626376535a6a4c79686d63535a574d50504f5a7949636c337a5935335168570a51432f61626d49634266493153342b72376d433469324a6e2b2b6f457675474e564772325359325a305a5a7858474c3148492f3038442f332b5463756d72636e0a34596a504b2f444d466930462b652b317834316c697075662b63782f3271524e51582f6d30325354724c59644d3665306733334b766c6e4664693262373532790a2f4f49614d777844614a76756e4d6836454d44574b4d31414862592f696f416f4b376553323648654a4c45446c6c714f342b535750333763386c4d76534557790a314769457252304863734f6a2f51775747504673656f56726f4d6941327355513049632f7467566a43546c58672b31325870556e6f754977654369384b634c2f0a6164327a4a6b6a75396842684a4c42512f32476e69764a69336c4667463447642f2f54534a36726757755846664d4b742f397a32537a33356f684558347941300a666c716c43654c496e46456f6576627a2b585439615266446536354d5a373979773354665039437256373468663152527a766544347a706933462b68635932690a4a5773483767524f5a65436d3666415835547265636433684f784a4f6641344e34727653534371364277437665625438465932355a2f564637635172485944530a696a3577366c71684d7a5848655545593930476139414b34587a6157774767657a712b52375a73303059534b7146763971594e4b645237747a33636a696a57660a39712f33523175683645514b544d5a4b6f345345436c4a6947796a4f42766d504b30396a4d465a544a763030684478616744505a426c3758704c444a352f4c6e0a31757070764c434e575759317a654a6661456c4d7971332f50714b5a4c6964463972566f41315349776b326c70645576506f7465326f466977435a6f586c775a0a4a326e636a6d5867514e7337362f38756e444a4130726a344a5071636377344d35477851376f6b62676d334634726d7a72694375763842654d53436b723272790a306d59335568706f68583477434d713047347835734555417a39465656505a4b6a786e59426d4c447a724a41522b342b4737675a736374303158444a596744640a4a5659496e465032322f63497265385672465759744862674f46644e7155695671353864653650645a472f452b7561576d455468536c527267456a54787570690a4f586667644b572f32306a317141746a4f6c714677735930393451357271554c513677507851494441514142416f4945415143686d6b6d6c68725242763432640a6659556979784b353262386174683073614a64447a36746c586d785944674a784d392f586c4f5274396f547a65446b6e6f454f356f6c752b72727834424267510a747a59696169775256585252455654575137746a7a5276614e4c2f47466b4c7439335854636370754b7779724e452f6269744c56616752627763492b485a46610a4d6b6e434f6968484d486f52746f386833464b41593934787a5341674f444d656b315747386a6867704358586d564e6e4250742b64346f44444944414741667a0a71676630334a356e6849622b38304b675a4f7a504f4b6e62764a614c36456d6c4c4862674233633432647a417737684874566d6f6659475763764c62324d49590a44564b4f3433352f73517831552f384e4448364a6a566441435a6a4c674f625848394b332f54743436445750456372504c6d443878686f633667464d2b5172300a41686b7a4b6f4259444e6b30436c6a6268644942586a6b74585536775251465a343575503265344a5a347a727a47424c722f74346c5461765a305351744c6c640a41366b4f7347682b6443574644746e736878596e6c2f7861642f79522b3361357a6d444a626f2f664a544258726c663142347266516b46744b323065744f50510a422b2b46432f726a68334d6d2f4b622f7039477a2f3275705a6441724839375a7644324c4246666a37376c466d4168714169337743526c4e2b656b755978615a0a743170425639795869673844796c64673164375838704f6e326b7972463372515544446634706137783976706e626b556c455569666f5639676e59736d646e690a71447a59427454763267364d4b7177517953586149555730594f4250624f656c6c574577784a7147595137793449665648664d306979486e65686b32745a63720a2b58617a4c6e7747652b427a347663677546684a584c7949752f2f6c414f685a74626b367231514a45557578614f4f515833777a79636545366e6b4473676d720a5035646a335a7064376653325656327679474849466e424a38384c52787265567667723651323855543237534238327a4d62376d525a545645327a65757562540a354432443158625a3077426f3657694b366552527244513248616565746b6a2f756f527936505758776e4161546d6d49727258774c71616f4a682f5531652b440a746673444c57643649784c6a66587647676c7248737274417a306f70727069785554655668675472476b394951526435727678754755596846756a56615949360a2b5155662b3333414664746e636238793943396a5a6d677838414b624a6b2b653733534c6842354a566f732b57746555376238642f4d696d356d414c6a6e4f360a5a316e2f75696d735437397353447179335853796d744b57586f2f3232556c72764743706f4575454c504d6236645346575237767772737668466e6759342f4b0a556e69746e7678626f45666c516e614951344966524c527a5a73582b73433545737177395535744874346f492b39314476334b626462634552675637334b36420a5a516743346c6b415171754658695a354149436b786a694d795a77547455394b4a37787631375875366f7977462f33417462564745545731442b336d614873440a7933444153576f6a79715a644c6a2b57477a4b5152612b737767434441594b65656b32664941584653644636337a784a3252784f4a3447696a53616f682b6d720a3448567663704461546a2b413854312b516442794d347339386775344744376b5674565147425a64576a75747948766830685776316774566d6268512f3431330a67444d4646447a48496a4c5459475965733468484c32323136396a565239735a316551787776544967334e3470443563466d307252755a5a54532b6f4a546f460a4732376142466968416f494341514479564236325a446e62785174686b2b7a49544b497a5255724a624c6f58725563414e63534866614e37696e4638374f76610a7a6537656a5439444e5345686274665a464a31473664694f596f53772b324d7a4658763067456b4c4b593064455479644b67484575366e5671356569764d67760a4434686339596b4a4d4844536c6d763246446b704c33415843416d6e5739724b702b64647474425a45436e6d6c504570484c6f6a367867427733704e613158730a49634c56666475674838364865786a366f306f4b6759666371725838555548745549322f585171674672496a386b736a6631664656574a524a46576d425871700a6e4d45735961727a4154654d316b512f6b446554315a55706f4750517430322f5871585434423541334154694574704d32752b6c343878746f675757673252790a47396c3933385374416d68556957316d37476e4b453645494676515938355776627a784f52304a5956555372374d72617346366e6e516c6859784675494a6f4a0a32682f4b4a51616f354743547647342b4774624a4a6d3463326e795a67777968697a4d7364677364636c7337396158694d6b725a5a6b616d4c56555a574f74450a3370412f6f42757a32716e4f3948776a62483148474f636371305458666d70465363455633435147594a646e6f3646793763626d7570614c34553961675134650a772b79674c31386e713548562b2b4c5374466e567267733559696a6a736b665264453947554d56446835704373643959323346796d616164344f2f32535243430a596b53737948354f7679444f4c706f79554a366736512b343548716d2f336c4734596a4e707a4655694d636e70372b33785533357143304c4b387845666565690a4d73316d54564569484e49703678482f547152645837335744372b59754b5a534c496652473764677269725536772b6d686876784435317548514b43416745410a322f316d42435235716d332f304c742b2b52516265794533746977343055657951717563472f2b567659373773534c6b492f4c78386977526c797758634c426e0a2b4134547667756b6d4164577a4373386e64674b4e7850412b67666f687642734d4f474e394b4f4231556735767667324a326b6949363476775943777a68645a0a4e5455556d4c2b474d464855715373575967366937694246635a6d7a6e72345732543362427879544d5a6b69374a53744238366533354b58727a63322f572f620a2b2f70355532484353617a444849356d4d7975436c486336476d5553564a3766374c486a4c39346a76694e716f627030566a3630337453634849536d4e725a770a544261766b765a475958736f574b767161766b376a424239517a61424c2b756e614652736c67356a5461694b6e49536a3434557331666a464b7538347869664c0a6e4a61457a6a445074375042786b6f374c5067455937774633396e4d3956706f65744937627752364e77444c535838555539374d47642b48592b4d4f315769310a7064324c61707772782f454b374f787a33333556524b344a6530615a6e61346a32547951644d4a616339667347505876345a734c66444c6a2f7744366c316a2b0a6c4c4c62427633496d64536a33324c4262687367463469434765584f384870504f2b512f68395856736e593532556d3258644e4d6e30335043476d365a76744d0a37445869532b6c50463930486a6f6c4a56485a54424e74645652724c7235337a4c7557456671543446654b72446178647469586b784c6a72422b352f565975370a6e74796b30315a513633564e664577533169726d4b6c392b715a6b54486b33484856396a4e5635527a575669776d4a493757707231597a42776d634b4342314f0a6f4755414444733851706e6b437a30786b4d56745977486a39714b5a6c716662487a72464455556346386b43676749416459765563676a662f2f6a75386d41380a3556513341635045365476796350572b6b5232447657313256634473462f73633155413764487a7a695068476e3938536d4e786c426a62387375536246505a380a5168565430574242446b6354696c774947507839617837553353366c47573256645336467151483566526d67514b5a79724356584c4f457a384267594272534a0a78752f335451415778483051746962646247486738506469353867596c574652686e394238536c6831615259484750623141684e4c4264302f6464592b3547320a397853794458646d5a67316355412b42337a41774e5371627a46786870327a552b563175587362706b344b746e595636435a4d39516c7243526a546b39694e550a645658462f716169526a667a726d3453736d4570436b4562737270374632325931626b6f6f4f52676c4d4f734e41574e716656587734774e2b7379586a31726f0a36765a3850455259724679414f52316473514d4968796d6e6d54506a4370614a34656d4b7268575479323073593731746848616b5a574a633232596f4e70625a0a453674674956734a50546c78672f342b667943434b6a3577577239326e687342314b425a50474f2f7a4668764d6c4a70765130744838573270624e3261306d490a3578394671414c6d2f716a774348665a49745377504d2b5a6f7a53687433634f6b4748646344354b58415866636673444a633453485a4b56497a71344e75734e0a353034522f6a76443147503873676c7947376f6d703735636b677a416d616b4c64784f503248685176495839746358705369724e4a36536c3262774b75754d460a77786f33722f6f2f3959393765344c6c66704559703965714d6463472b4e705239393349774b30556841575339483577646e57425355486435653478744455740a69494c4e52754f34366737522f4149687a3163535372615757516b4367674942414d6868505035433979743950496d316230655477434263746e465351494b6f0a4b734139726c6c3261622b624d4c6b396a63384d364d4c737a7930437457736f30397348663459593974696676726b4548526574684568387a736377557559750a736d326e31665469786b30756c364c5356676c35347558624d4a6179454e6e3450494b526b65773863413874536d613433343937773337686d442b4d674362310a414c7a7163636f3968666d6b676b4936666f316738436533554545434b7932594b536d5245646759634b394a46514f36315736416b46574a634478416d667a490a4a6a466b4b7773623754537737397a57694564536f4d396a6d377343504b41546436426d2f5a41416b5555547545466b666f626e39417831724a4e2f587862320a4d4b754155745176304e595930674556644736326a4974754b4c4964366e6e63483850472b7273526a504c495970577159644a704b783570556e522b34416b510a5336437352415377634634506442764444424946473658706a466f347050645168447a4c32735446386238535753424c6c4a516262374736554e7167435361750a537573434670617a7655354e66446d554d7563746f623245595661535871396a47616a366254556d447758487757696c66496b3958664c786e59665859724a360a786864497058476d4868754c517441674b324f314a744c6f50633973397150382f536b665037786a6a4736784873502f57764c37514531705073395a4d2f55490a4330314a4e484669394c4b436e386f356d625a6a4e386a556f77693766664b2b3736775a5547314c377a4d35797457514f59776f3054514266633866706d46770a2b5242524a58326b4a79444f32374578637a6f474f4b6a77714544614f444942392b397a63434b304267536f526962536d345a42766f787a57574436354b6c730a786450685a55486346475735416f494341514338694732376144386152557439344f656b363667464f4a78383451565a6568575071745a6a577956656e4475630a543864696e6b386f656a476a634b32554a755144613833617a7639306f63567145306e30726f6e5979737a74394962316a6c59432b434b3141723954594746670a5755354f57454479437a437071572f772f61473638553871684b6d304d766b4c4a522b47366576616e3954774568464556416d3369576c6c4e587339783239730a42756377794d4d4332337a73696d78596c53376441344474797656412b7a4c316f6d4c7053574862552f71747549334856314e654a7a73792b6743346d7750680a6a353274646c3636396679574c7a487a42524c65713664564f65646a6e436f2b6a6c5533644c323044456b39536157303844314350755a656b56316a56504d770a4a6f614463495268344b4c74513042595a37554a6546555473783143532f2b55717a715953504f69353761356b76723059385977526e534238644856467474580a4a5476383377545158485046534267666e484e65376c7352546649516675496b723262706955376838355551374c7371634936594861433037555263734746460a46724c5747683931717a416431646953486c6132526e59336e385050754d6e436b67754e684c557259646d794d6f6c374666574661396c77706c7375547a42710a4236796a3869616945334c4c2b512f65756c4a375336515066414932625530554a4f323359346b6f656f4969624545444d534351364b595a324e436c525252540a67613566533159666b44464563485551312f4b496b64594847424b426a6f4b4745787a69382b436769537953565359445a6c3677494f684c6a48324f5a336f6c0a6c64504e37694e41486972727867397638514f364f516c704c556b354c68702f3164536c5a36737933556a4671766178337477365a6a724c3838595035673d3d0a2d2d2d2d2d454e44205253412050524956415445204b45592d2d2d2d2d0a" + val keyStr = + "2d2d2d2d2d424547494e205253412050524956415445204b45592d2d2d2d2d0a4d4949534b67494241414b4342414541304433542b644953736d43486d37393777735830765666715557444a2f336d7644596f7a6c43616244686e474c6c53450a7041516266315a3854732b4f4d34705652484f4a554a4c305765624e646d5050476a737957517a367a5a4539366c515a4c3361764345587159565152363656350a3377644b2f6f68614d53526e4779454d4272716b56566246336743722b2f6972784433594b2b566f774f32574b732f3647724d647154413859354354462f4a650a707477735367354d4d6a723655614b3471446372656a33686b6742564776525633636a31736e4b3642723848755964466e7047475453306437554a6c4846676c0a74724748552f43424f393233686b48674a6157456a4330676953476a684b4b744c7a72566370445632792f6c57515039542f5434646a454149614871512b2b500a53644f535236707349475236685667536967743748436e45376e573731312f7266563555723945695670423034306d44496d4b5a6379382f2f544d6e5879644e0a314b595456642f33346664707a4d7053773569626c457262774f4c585654556d4f7a74596e706c343166654853762f6a506573487374506c666b6c494632766f0a475a456f6866397363517663754d3777454266432f615441394b33397a4d6d6b42626376535a6a4c79686d63535a574d50504f5a7949636c337a5935335168570a51432f61626d49634266493153342b72376d433469324a6e2b2b6f457675474e564772325359325a305a5a7858474c3148492f3038442f332b5463756d72636e0a34596a504b2f444d466930462b652b317834316c697075662b63782f3271524e51582f6d30325354724c59644d3665306733334b766c6e4664693262373532790a2f4f49614d777844614a76756e4d6836454d44574b4d31414862592f696f416f4b376553323648654a4c45446c6c714f342b535750333763386c4d76534557790a314769457252304863734f6a2f51775747504673656f56726f4d6941327355513049632f7467566a43546c58672b31325870556e6f754977654369384b634c2f0a6164327a4a6b6a75396842684a4c42512f32476e69764a69336c4667463447642f2f54534a36726757755846664d4b742f397a32537a33356f684558347941300a666c716c43654c496e46456f6576627a2b585439615266446536354d5a373979773354665039437256373468663152527a766544347a706933462b68635932690a4a5773483767524f5a65436d3666415835547265636433684f784a4f6641344e34727653534371364277437665625438465932355a2f564637635172485944530a696a3577366c71684d7a5848655545593930476139414b34587a6157774767657a712b52375a73303059534b7146763971594e4b645237747a33636a696a57660a39712f33523175683645514b544d5a4b6f345345436c4a6947796a4f42766d504b30396a4d465a544a763030684478616744505a426c3758704c444a352f4c6e0a31757070764c434e575759317a654a6661456c4d7971332f50714b5a4c6964463972566f41315349776b326c70645576506f7465326f466977435a6f586c775a0a4a326e636a6d5867514e7337362f38756e444a4130726a344a5071636377344d35477851376f6b62676d334634726d7a72694375763842654d53436b723272790a306d59335568706f68583477434d713047347835734555417a39465656505a4b6a786e59426d4c447a724a41522b342b4737675a736374303158444a596744640a4a5659496e465032322f63497265385672465759744862674f46644e7155695671353864653650645a472f452b7561576d455468536c527267456a54787570690a4f586667644b572f32306a317141746a4f6c714677735930393451357271554c513677507851494441514142416f4945415143686d6b6d6c68725242763432640a6659556979784b353262386174683073614a64447a36746c586d785944674a784d392f586c4f5274396f547a65446b6e6f454f356f6c752b72727834424267510a747a59696169775256585252455654575137746a7a5276614e4c2f47466b4c7439335854636370754b7779724e452f6269744c56616752627763492b485a46610a4d6b6e434f6968484d486f52746f386833464b41593934787a5341674f444d656b315747386a6867704358586d564e6e4250742b64346f44444944414741667a0a71676630334a356e6849622b38304b675a4f7a504f4b6e62764a614c36456d6c4c4862674233633432647a417737684874566d6f6659475763764c62324d49590a44564b4f3433352f73517831552f384e4448364a6a566441435a6a4c674f625848394b332f54743436445750456372504c6d443878686f633667464d2b5172300a41686b7a4b6f4259444e6b30436c6a6268644942586a6b74585536775251465a343575503265344a5a347a727a47424c722f74346c5461765a305351744c6c640a41366b4f7347682b6443574644746e736878596e6c2f7861642f79522b3361357a6d444a626f2f664a544258726c663142347266516b46744b323065744f50510a422b2b46432f726a68334d6d2f4b622f7039477a2f3275705a6441724839375a7644324c4246666a37376c466d4168714169337743526c4e2b656b755978615a0a743170425639795869673844796c64673164375838704f6e326b7972463372515544446634706137783976706e626b556c455569666f5639676e59736d646e690a71447a59427454763267364d4b7177517953586149555730594f4250624f656c6c574577784a7147595137793449665648664d306979486e65686b32745a63720a2b58617a4c6e7747652b427a347663677546684a584c7949752f2f6c414f685a74626b367231514a45557578614f4f515833777a79636545366e6b4473676d720a5035646a335a7064376653325656327679474849466e424a38384c52787265567667723651323855543237534238327a4d62376d525a545645327a65757562540a354432443158625a3077426f3657694b366552527244513248616565746b6a2f756f527936505758776e4161546d6d49727258774c71616f4a682f5531652b440a746673444c57643649784c6a66587647676c7248737274417a306f70727069785554655668675472476b394951526435727678754755596846756a56615949360a2b5155662b3333414664746e636238793943396a5a6d677838414b624a6b2b653733534c6842354a566f732b57746555376238642f4d696d356d414c6a6e4f360a5a316e2f75696d735437397353447179335853796d744b57586f2f3232556c72764743706f4575454c504d6236645346575237767772737668466e6759342f4b0a556e69746e7678626f45666c516e614951344966524c527a5a73582b73433545737177395535744874346f492b39314476334b626462634552675637334b36420a5a516743346c6b415171754658695a354149436b786a694d795a77547455394b4a37787631375875366f7977462f33417462564745545731442b336d614873440a7933444153576f6a79715a644c6a2b57477a4b5152612b737767434441594b65656b32664941584653644636337a784a3252784f4a3447696a53616f682b6d720a3448567663704461546a2b413854312b516442794d347339386775344744376b5674565147425a64576a75747948766830685776316774566d6268512f3431330a67444d4646447a48496a4c5459475965733468484c32323136396a565239735a316551787776544967334e3470443563466d307252755a5a54532b6f4a546f460a4732376142466968416f494341514479564236325a446e62785174686b2b7a49544b497a5255724a624c6f58725563414e63534866614e37696e4638374f76610a7a6537656a5439444e5345686274665a464a31473664694f596f53772b324d7a4658763067456b4c4b593064455479644b67484575366e5671356569764d67760a4434686339596b4a4d4844536c6d763246446b704c33415843416d6e5739724b702b64647474425a45436e6d6c504570484c6f6a367867427733704e613158730a49634c56666475674838364865786a366f306f4b6759666371725838555548745549322f585171674672496a386b736a6631664656574a524a46576d425871700a6e4d45735961727a4154654d316b512f6b446554315a55706f4750517430322f5871585434423541334154694574704d32752b6c343878746f675757673252790a47396c3933385374416d68556957316d37476e4b453645494676515938355776627a784f52304a5956555372374d72617346366e6e516c6859784675494a6f4a0a32682f4b4a51616f354743547647342b4774624a4a6d3463326e795a67777968697a4d7364677364636c7337396158694d6b725a5a6b616d4c56555a574f74450a3370412f6f42757a32716e4f3948776a62483148474f636371305458666d70465363455633435147594a646e6f3646793763626d7570614c34553961675134650a772b79674c31386e713548562b2b4c5374466e567267733559696a6a736b665264453947554d56446835704373643959323346796d616164344f2f32535243430a596b53737948354f7679444f4c706f79554a366736512b343548716d2f336c4734596a4e707a4655694d636e70372b33785533357143304c4b387845666565690a4d73316d54564569484e49703678482f547152645837335744372b59754b5a534c496652473764677269725536772b6d686876784435317548514b43416745410a322f316d42435235716d332f304c742b2b52516265794533746977343055657951717563472f2b567659373773534c6b492f4c78386977526c797758634c426e0a2b4134547667756b6d4164577a4373386e64674b4e7850412b67666f687642734d4f474e394b4f4231556735767667324a326b6949363476775943777a68645a0a4e5455556d4c2b474d464855715373575967366937694246635a6d7a6e72345732543362427879544d5a6b69374a53744238366533354b58727a63322f572f620a2b2f70355532484353617a444849356d4d7975436c486336476d5553564a3766374c486a4c39346a76694e716f627030566a3630337453634849536d4e725a770a544261766b765a475958736f574b767161766b376a424239517a61424c2b756e614652736c67356a5461694b6e49536a3434557331666a464b7538347869664c0a6e4a61457a6a445074375042786b6f374c5067455937774633396e4d3956706f65744937627752364e77444c535838555539374d47642b48592b4d4f315769310a7064324c61707772782f454b374f787a33333556524b344a6530615a6e61346a32547951644d4a616339667347505876345a734c66444c6a2f7744366c316a2b0a6c4c4c62427633496d64536a33324c4262687367463469434765584f384870504f2b512f68395856736e593532556d3258644e4d6e30335043476d365a76744d0a37445869532b6c50463930486a6f6c4a56485a54424e74645652724c7235337a4c7557456671543446654b72446178647469586b784c6a72422b352f565975370a6e74796b30315a513633564e664577533169726d4b6c392b715a6b54486b33484856396a4e5635527a575669776d4a493757707231597a42776d634b4342314f0a6f4755414444733851706e6b437a30786b4d56745977486a39714b5a6c716662487a72464455556346386b43676749416459765563676a662f2f6a75386d41380a3556513341635045365476796350572b6b5232447657313256634473462f73633155413764487a7a695068476e3938536d4e786c426a62387375536246505a380a5168565430574242446b6354696c774947507839617837553353366c47573256645336467151483566526d67514b5a79724356584c4f457a384267594272534a0a78752f335451415778483051746962646247486738506469353867596c574652686e394238536c6831615259484750623141684e4c4264302f6464592b3547320a397853794458646d5a67316355412b42337a41774e5371627a46786870327a552b563175587362706b344b746e595636435a4d39516c7243526a546b39694e550a645658462f716169526a667a726d3453736d4570436b4562737270374632325931626b6f6f4f52676c4d4f734e41574e716656587734774e2b7379586a31726f0a36765a3850455259724679414f52316473514d4968796d6e6d54506a4370614a34656d4b7268575479323073593731746848616b5a574a633232596f4e70625a0a453674674956734a50546c78672f342b667943434b6a3577577239326e687342314b425a50474f2f7a4668764d6c4a70765130744838573270624e3261306d490a3578394671414c6d2f716a774348665a49745377504d2b5a6f7a53687433634f6b4748646344354b58415866636673444a633453485a4b56497a71344e75734e0a353034522f6a76443147503873676c7947376f6d703735636b677a416d616b4c64784f503248685176495839746358705369724e4a36536c3262774b75754d460a77786f33722f6f2f3959393765344c6c66704559703965714d6463472b4e705239393349774b30556841575339483577646e57425355486435653478744455740a69494c4e52754f34366737522f4149687a3163535372615757516b4367674942414d6868505035433979743950496d316230655477434263746e465351494b6f0a4b734139726c6c3261622b624d4c6b396a63384d364d4c737a7930437457736f30397348663459593974696676726b4548526574684568387a736377557559750a736d326e31665469786b30756c364c5356676c35347558624d4a6179454e6e3450494b526b65773863413874536d613433343937773337686d442b4d674362310a414c7a7163636f3968666d6b676b4936666f316738436533554545434b7932594b536d5245646759634b394a46514f36315736416b46574a634478416d667a490a4a6a466b4b7773623754537737397a57694564536f4d396a6d377343504b41546436426d2f5a41416b5555547545466b666f626e39417831724a4e2f587862320a4d4b754155745176304e595930674556644736326a4974754b4c4964366e6e63483850472b7273526a504c495970577159644a704b783570556e522b34416b510a5336437352415377634634506442764444424946473658706a466f347050645168447a4c32735446386238535753424c6c4a516262374736554e7167435361750a537573434670617a7655354e66446d554d7563746f623245595661535871396a47616a366254556d447758487757696c66496b3958664c786e59665859724a360a786864497058476d4868754c517441674b324f314a744c6f50633973397150382f536b665037786a6a4736784873502f57764c37514531705073395a4d2f55490a4330314a4e484669394c4b436e386f356d625a6a4e386a556f77693766664b2b3736775a5547314c377a4d35797457514f59776f3054514266633866706d46770a2b5242524a58326b4a79444f32374578637a6f474f4b6a77714544614f444942392b397a63434b304267536f526962536d345a42766f787a57574436354b6c730a786450685a55486346475735416f494341514338694732376144386152557439344f656b363667464f4a78383451565a6568575071745a6a577956656e4475630a543864696e6b386f656a476a634b32554a755144613833617a7639306f63567145306e30726f6e5979737a74394962316a6c59432b434b3141723954594746670a5755354f57454479437a437071572f772f61473638553871684b6d304d766b4c4a522b47366576616e3954774568464556416d3369576c6c4e587339783239730a42756377794d4d4332337a73696d78596c53376441344474797656412b7a4c316f6d4c7053574862552f71747549334856314e654a7a73792b6743346d7750680a6a353274646c3636396679574c7a487a42524c65713664564f65646a6e436f2b6a6c5533644c323044456b39536157303844314350755a656b56316a56504d770a4a6f614463495268344b4c74513042595a37554a6546555473783143532f2b55717a715953504f69353761356b76723059385977526e534238644856467474580a4a5476383377545158485046534267666e484e65376c7352546649516675496b723262706955376838355551374c7371634936594861433037555263734746460a46724c5747683931717a416431646953486c6132526e59336e385050754d6e436b67754e684c557259646d794d6f6c374666574661396c77706c7375547a42710a4236796a3869616945334c4c2b512f65756c4a375336515066414932625530554a4f323359346b6f656f4969624545444d534351364b595a324e436c525252540a67613566533159666b44464563485551312f4b496b64594847424b426a6f4b4745787a69382b436769537953565359445a6c3677494f684c6a48324f5a336f6c0a6c64504e37694e41486972727867397638514f364f516c704c556b354c68702f3164536c5a36737933556a4671766178337477365a6a724c3838595035673d3d0a2d2d2d2d2d454e44205253412050524956415445204b45592d2d2d2d2d0a" val encodedKey = AuxBlob.encodePubKey(Algorithms.get("SHA256_RSA8192")!!, Hex.decodeHex(keyStr)) - val expectedKeyEnc = "00002000ef8168f3d03dd3f9d212b260879bbf7bc2c5f4bd57ea5160c9ff79af0d8a3394269b0e19c62e5484a4041b7f567c4ecf8e338a554473895092f459e6cd7663cf1a3b32590cfacd913dea54192f76af0845ea615411eba579df074afe885a3124671b210c06baa45556c5de00abfbf8abc43dd82be568c0ed962acffa1ab31da9303c63909317f25ea6dc2c4a0e4c323afa51a2b8a8372b7a3de19200551af455ddc8f5b272ba06bf07b987459e91864d2d1ded42651c5825b6b18753f0813bddb78641e025a5848c2d208921a384a2ad2f3ad57290d5db2fe55903fd4ff4f876310021a1ea43ef8f49d39247aa6c20647a8558128a0b7b1c29c4ee75bbd75feb7d5e54afd122569074e34983226299732f3ffd33275f274dd4a61355dff7e1f769ccca52c3989b944adbc0e2d75535263b3b589e9978d5f7874affe33deb07b2d3e57e4948176be819912885ff6c710bdcb8cef01017c2fda4c0f4adfdccc9a405b72f4998cbca199c49958c3cf399c88725df3639dd0856402fda6e621c05f2354b8fabee60b88b6267fbea04bee18d546af6498d99d196715c62f51c8ff4f03ff7f9372e9ab727e188cf2bf0cc162d05f9efb5c78d658a9b9ff9cc7fdaa44d417fe6d36493acb61d33a7b4837dcabe59c5762d9bef9db2fce21a330c43689bee9cc87a10c0d628cd401db63f8a80282bb792dba1de24b103965a8ee3e4963f7edcf2532f4845b2d46884ad1d0772c3a3fd0c1618f16c7a856ba0c880dac510d0873fb6056309395783ed765e9527a2e2307828bc29c2ff69ddb32648eef6106124b050ff61a78af262de516017819dfff4d227aae05ae5c57cc2adffdcf64b3df9a21117e320347e5aa509e2c89c51287af6f3f974fd6917c37bae4c67bf72c374df3fd0ab57be217f5451cef783e33a62dc5fa1718da2256b07ee044e65e0a6e9f017e53ade71dde13b124e7c0e0de2bbd2482aba0700af79b4fc158db967f545edc42b1d80d28a3e70ea5aa13335c7794118f7419af402b85f3696c0681eceaf91ed9b34d1848aa85bfda9834a751eedcf77238a359ff6aff7475ba1e8440a4cc64aa384840a52621b28ce06f98f2b4f6330565326fd34843c5a8033d9065ed7a4b0c9e7f2e7d6ea69bcb08d596635cde25f68494ccaadff3ea2992e2745f6b568035488c24da5a5d52f3e8b5eda8162c026685e5c192769dc8e65e040db3bebff2e9c3240d2b8f824fa9c730e0ce46c50ee891b826dc5e2b9b3ae20aebfc05e3120a4af6af2d26637521a68857e3008cab41b8c79b04500cfd15554f64a8f19d80662c3ceb24047ee3e1bb819b1cb74d570c96200dd2556089c53f6dbf708adef15ac5598b476e038574da94895ab9f1d7ba3dd646fc4fae6969844e14a546b8048d3c6ea623977e074a5bfdb48f5a80b633a5a85c2c634f78439aea50b43ac0fc51af9112e97f272e5448cc3110fce7a4081ea0613cba5637de45982ff7144d57cb6401e5186d39755af273947e526d5bb5bd4390562cfb8f28039c14be94986fb4f79a49bb97a30d2f252a8569e121993af3c57a60e98cb07dfedbdeffc887ada8d098ac4deef25fee3b33ce90be9d27ae66602da89b4b931a08ae776e7ecac8ccb30c02b72208b87f05b03b9cf83f66e4be8344fd4c4ddd08194ad7a0a3a6d6ec21058e6de8bf9c8691cb7994748e71989387340a5ecd3cd0f506b6686522673e9d2770cd13db18217cd261f706efcc0f4b3842982e4c8d333b3e0ae9348b91e15843e17371426b8294964f2e74cd1c2e08a575b66a79494d98f1db480edc741f689a3678985c379609bac92b0c192dfe5ea44fc284cf67297220888210d9ebd2d100ffc8bcb13636aad6263b2a735e3d006f309100dc391297ce7253e6df1885eefee0e7035ef318053d7177cd4acc5ec92031a1fd66def3404c4adfff0f12194a093fad509a0a6c7422011d0b0c40a6da8601fc71e48ad2544ebc652499ed591e6eb89b074fca8a920b8bbb568abfcc0af73fa4f3256cb30609cdbd11307db751d0835e09cfa1c747a58ed2919d4bd6a9a8eb2c1fb92e003bb090de173dc704b387f41b7285f95294d1ce756b269e9e3814ccb3f80efe0d7eac6d0cfe2cc51524aa4c984476d74fd34f3e4ff126cf01523a1deda0e5a0772f0e61bed8dc3615d547d8113d23d8f0bfc0097ae88258e6478cee389fbb6bf4ea68d083a938d7f781ce370c03cbd10a053e7d6a37526f610220eb684805e62204f210a790e7009cf71fb036851f5fcdb86bf640c94e0c0a412a66abf5a7f205d515b6c85d65b7da2f7e40dcf3d0567d2d851f02942a0f9a67e7f9f4427460de297d17219b940ddcfaf8310d6e906831be39c2cf1d527f2ffabd95dfe145c8f0adecfc4b167f38ae03fd8d2fe4d42bd8f2ae20f59a3688153a8b2c75ad360bd9a900d810d5ce17a5be35436cd15d6b2b881b56fd97277e4b2cd82f25df35286ec724a125280a036bcb602bd0038c5ea3026547462a06e4a45729ca0784e31d1103b7f991eaa89d7121cfd67510fb18d4d5b03068884c592d6bad56815d4eb4cffab1feb7ea27dd4e28c62db6d18f8d838b48553cd737611657463349f70cdc260ab0ad9bf163658eacc78982a274f85aadb0becf92588ecd53fc5e229bb1fe870a5f18c5c66bd154d052b2e2663004c0d6beacfcb55094ffb1898b7dbe3c9653815da4c11d53ac018b98fbb36fa61197de15258dc4614807c83c02f15420527508e63f8327b4c9862291810ff453b9badb3d7620d8c1aab8b5d50d6af59fc181161a5b1039090062f0c8995822be2df158447253b8abf913225c1bdef3e9a54a0589c1f69580e2565b28c75e9c4c8d738504ee8e08de414c64d8cee4864ecf9a60948515cc07680" + val expectedKeyEnc = + "00002000ef8168f3d03dd3f9d212b260879bbf7bc2c5f4bd57ea5160c9ff79af0d8a3394269b0e19c62e5484a4041b7f567c4ecf8e338a554473895092f459e6cd7663cf1a3b32590cfacd913dea54192f76af0845ea615411eba579df074afe885a3124671b210c06baa45556c5de00abfbf8abc43dd82be568c0ed962acffa1ab31da9303c63909317f25ea6dc2c4a0e4c323afa51a2b8a8372b7a3de19200551af455ddc8f5b272ba06bf07b987459e91864d2d1ded42651c5825b6b18753f0813bddb78641e025a5848c2d208921a384a2ad2f3ad57290d5db2fe55903fd4ff4f876310021a1ea43ef8f49d39247aa6c20647a8558128a0b7b1c29c4ee75bbd75feb7d5e54afd122569074e34983226299732f3ffd33275f274dd4a61355dff7e1f769ccca52c3989b944adbc0e2d75535263b3b589e9978d5f7874affe33deb07b2d3e57e4948176be819912885ff6c710bdcb8cef01017c2fda4c0f4adfdccc9a405b72f4998cbca199c49958c3cf399c88725df3639dd0856402fda6e621c05f2354b8fabee60b88b6267fbea04bee18d546af6498d99d196715c62f51c8ff4f03ff7f9372e9ab727e188cf2bf0cc162d05f9efb5c78d658a9b9ff9cc7fdaa44d417fe6d36493acb61d33a7b4837dcabe59c5762d9bef9db2fce21a330c43689bee9cc87a10c0d628cd401db63f8a80282bb792dba1de24b103965a8ee3e4963f7edcf2532f4845b2d46884ad1d0772c3a3fd0c1618f16c7a856ba0c880dac510d0873fb6056309395783ed765e9527a2e2307828bc29c2ff69ddb32648eef6106124b050ff61a78af262de516017819dfff4d227aae05ae5c57cc2adffdcf64b3df9a21117e320347e5aa509e2c89c51287af6f3f974fd6917c37bae4c67bf72c374df3fd0ab57be217f5451cef783e33a62dc5fa1718da2256b07ee044e65e0a6e9f017e53ade71dde13b124e7c0e0de2bbd2482aba0700af79b4fc158db967f545edc42b1d80d28a3e70ea5aa13335c7794118f7419af402b85f3696c0681eceaf91ed9b34d1848aa85bfda9834a751eedcf77238a359ff6aff7475ba1e8440a4cc64aa384840a52621b28ce06f98f2b4f6330565326fd34843c5a8033d9065ed7a4b0c9e7f2e7d6ea69bcb08d596635cde25f68494ccaadff3ea2992e2745f6b568035488c24da5a5d52f3e8b5eda8162c026685e5c192769dc8e65e040db3bebff2e9c3240d2b8f824fa9c730e0ce46c50ee891b826dc5e2b9b3ae20aebfc05e3120a4af6af2d26637521a68857e3008cab41b8c79b04500cfd15554f64a8f19d80662c3ceb24047ee3e1bb819b1cb74d570c96200dd2556089c53f6dbf708adef15ac5598b476e038574da94895ab9f1d7ba3dd646fc4fae6969844e14a546b8048d3c6ea623977e074a5bfdb48f5a80b633a5a85c2c634f78439aea50b43ac0fc51af9112e97f272e5448cc3110fce7a4081ea0613cba5637de45982ff7144d57cb6401e5186d39755af273947e526d5bb5bd4390562cfb8f28039c14be94986fb4f79a49bb97a30d2f252a8569e121993af3c57a60e98cb07dfedbdeffc887ada8d098ac4deef25fee3b33ce90be9d27ae66602da89b4b931a08ae776e7ecac8ccb30c02b72208b87f05b03b9cf83f66e4be8344fd4c4ddd08194ad7a0a3a6d6ec21058e6de8bf9c8691cb7994748e71989387340a5ecd3cd0f506b6686522673e9d2770cd13db18217cd261f706efcc0f4b3842982e4c8d333b3e0ae9348b91e15843e17371426b8294964f2e74cd1c2e08a575b66a79494d98f1db480edc741f689a3678985c379609bac92b0c192dfe5ea44fc284cf67297220888210d9ebd2d100ffc8bcb13636aad6263b2a735e3d006f309100dc391297ce7253e6df1885eefee0e7035ef318053d7177cd4acc5ec92031a1fd66def3404c4adfff0f12194a093fad509a0a6c7422011d0b0c40a6da8601fc71e48ad2544ebc652499ed591e6eb89b074fca8a920b8bbb568abfcc0af73fa4f3256cb30609cdbd11307db751d0835e09cfa1c747a58ed2919d4bd6a9a8eb2c1fb92e003bb090de173dc704b387f41b7285f95294d1ce756b269e9e3814ccb3f80efe0d7eac6d0cfe2cc51524aa4c984476d74fd34f3e4ff126cf01523a1deda0e5a0772f0e61bed8dc3615d547d8113d23d8f0bfc0097ae88258e6478cee389fbb6bf4ea68d083a938d7f781ce370c03cbd10a053e7d6a37526f610220eb684805e62204f210a790e7009cf71fb036851f5fcdb86bf640c94e0c0a412a66abf5a7f205d515b6c85d65b7da2f7e40dcf3d0567d2d851f02942a0f9a67e7f9f4427460de297d17219b940ddcfaf8310d6e906831be39c2cf1d527f2ffabd95dfe145c8f0adecfc4b167f38ae03fd8d2fe4d42bd8f2ae20f59a3688153a8b2c75ad360bd9a900d810d5ce17a5be35436cd15d6b2b881b56fd97277e4b2cd82f25df35286ec724a125280a036bcb602bd0038c5ea3026547462a06e4a45729ca0784e31d1103b7f991eaa89d7121cfd67510fb18d4d5b03068884c592d6bad56815d4eb4cffab1feb7ea27dd4e28c62db6d18f8d838b48553cd737611657463349f70cdc260ab0ad9bf163658eacc78982a274f85aadb0becf92588ecd53fc5e229bb1fe870a5f18c5c66bd154d052b2e2663004c0d6beacfcb55094ffb1898b7dbe3c9653815da4c11d53ac018b98fbb36fa61197de15258dc4614807c83c02f15420527508e63f8327b4c9862291810ff453b9badb3d7620d8c1aab8b5d50d6af59fc181161a5b1039090062f0c8995822be2df158447253b8abf913225c1bdef3e9a54a0589c1f69580e2565b28c75e9c4c8d738504ee8e08de414c64d8cee4864ecf9a60948515cc07680" assertEquals(expectedKeyEnc, Hex.encodeHexString(encodedKey)) } } diff --git a/bbootimg/src/test/kotlin/avb/FooterTest.kt b/bbootimg/src/test/kotlin/avb/FooterTest.kt index 78c280b..e884637 100644 --- a/bbootimg/src/test/kotlin/avb/FooterTest.kt +++ b/bbootimg/src/test/kotlin/avb/FooterTest.kt @@ -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 { diff --git a/bbootimg/src/test/kotlin/bootimg/AndroidCpioEntryTest.kt b/bbootimg/src/test/kotlin/bootimg/AndroidCpioEntryTest.kt index 9e8d153..2dbc4ff 100644 --- a/bbootimg/src/test/kotlin/bootimg/AndroidCpioEntryTest.kt +++ b/bbootimg/src/test/kotlin/bootimg/AndroidCpioEntryTest.kt @@ -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 diff --git a/bbootimg/src/test/kotlin/bootloader_message/BootloaderMsgTest.kt b/bbootimg/src/test/kotlin/bootloader_message/BootloaderMsgTest.kt index 9aa3b74..ec2b55f 100644 --- a/bbootimg/src/test/kotlin/bootloader_message/BootloaderMsgTest.kt +++ b/bbootimg/src/test/kotlin/bootloader_message/BootloaderMsgTest.kt @@ -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 diff --git a/bbootimg/src/test/resources/data b/bbootimg/src/test/resources/data new file mode 100644 index 0000000..4972a58 Binary files /dev/null and b/bbootimg/src/test/resources/data differ diff --git a/bbootimg/src/test/resources/testkey.pk8 b/bbootimg/src/test/resources/testkey.pk8 new file mode 100644 index 0000000..586c1bd Binary files /dev/null and b/bbootimg/src/test/resources/testkey.pk8 differ diff --git a/build.gradle.kts b/build.gradle.kts index e9feaa1..1566023 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -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") } } diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 62d4c05..e708b1c 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index a4f0001..442d913 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -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 diff --git a/gradlew b/gradlew index fbd7c51..4f906e0 100755 --- a/gradlew +++ b/gradlew @@ -130,7 +130,7 @@ fi if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then APP_HOME=`cygpath --path --mixed "$APP_HOME"` CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` - + JAVACMD=`cygpath --unix "$JAVACMD"` # We build the pattern for arguments to be converted via cygpath diff --git a/gradlew.bat b/gradlew.bat index db5e6df..ac1b06f 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -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 diff --git a/helper/build.gradle.kts b/helper/build.gradle.kts new file mode 100644 index 0000000..160daad --- /dev/null +++ b/helper/build.gradle.kts @@ -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().all { + kotlinOptions.freeCompilerArgs += "-Xopt-in=kotlin.RequiresOptIn" + kotlinOptions.jvmTarget = "1.8" +} diff --git a/helper/src/main/kotlin/cfig/helper/AndroidHelper.kt b/helper/src/main/kotlin/cfig/helper/AndroidHelper.kt new file mode 100644 index 0000000..4a3b004 --- /dev/null +++ b/helper/src/main/kotlin/cfig/helper/AndroidHelper.kt @@ -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)) + } + } +} diff --git a/bbootimg/src/main/kotlin/helper/Helper.kt b/helper/src/main/kotlin/cfig/helper/Helper.kt similarity index 95% rename from bbootimg/src/main/kotlin/helper/Helper.kt rename to helper/src/main/kotlin/cfig/helper/Helper.kt index d05b620..3cacab7 100644 --- a/bbootimg/src/main/kotlin/helper/Helper.kt +++ b/helper/src/main/kotlin/cfig/helper/Helper.kt @@ -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 { diff --git a/helper/src/main/kotlin/cfig/helper/KeyHelper.kt b/helper/src/main/kotlin/cfig/helper/KeyHelper.kt new file mode 100644 index 0000000..431f416 --- /dev/null +++ b/helper/src/main/kotlin/cfig/helper/KeyHelper.kt @@ -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") + } + } + } +} diff --git a/bbootimg/src/main/kotlin/helper/KeyHelper2.kt b/helper/src/main/kotlin/cfig/helper/KeyHelper2.kt similarity index 58% rename from bbootimg/src/main/kotlin/helper/KeyHelper2.kt rename to helper/src/main/kotlin/cfig/helper/KeyHelper2.kt index c53fcb5..4d64f4e 100644 --- a/bbootimg/src/main/kotlin/helper/KeyHelper2.kt +++ b/helper/src/main/kotlin/cfig/helper/KeyHelper2.kt @@ -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())) } diff --git a/bbootimg/src/main/kotlin/helper/OpenSslHelper.kt b/helper/src/main/kotlin/cfig/helper/OpenSslHelper.kt similarity index 100% rename from bbootimg/src/main/kotlin/helper/OpenSslHelper.kt rename to helper/src/main/kotlin/cfig/helper/OpenSslHelper.kt diff --git a/bbootimg/src/main/kotlin/helper/ZipHelper.kt b/helper/src/main/kotlin/cfig/helper/ZipHelper.kt similarity index 57% rename from bbootimg/src/main/kotlin/helper/ZipHelper.kt rename to helper/src/main/kotlin/cfig/helper/ZipHelper.kt index d983e56..09c6021 100644 --- a/bbootimg/src/main/kotlin/helper/ZipHelper.kt +++ b/helper/src/main/kotlin/cfig/helper/ZipHelper.kt @@ -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") } - } } diff --git a/bbootimg/src/main/kotlin/cfig/io/Struct3.kt b/helper/src/main/kotlin/cfig/io/Struct3.kt similarity index 100% rename from bbootimg/src/main/kotlin/cfig/io/Struct3.kt rename to helper/src/main/kotlin/cfig/io/Struct3.kt diff --git a/helper/src/test/kotlin/cfig/helper/OpenSslHelperTest.kt b/helper/src/test/kotlin/cfig/helper/OpenSslHelperTest.kt new file mode 100644 index 0000000..4b15def --- /dev/null +++ b/helper/src/test/kotlin/cfig/helper/OpenSslHelperTest.kt @@ -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") + } +} diff --git a/helper/src/test/kotlin/cfig/helper/ZipHelperTest.kt b/helper/src/test/kotlin/cfig/helper/ZipHelperTest.kt new file mode 100644 index 0000000..46bf771 --- /dev/null +++ b/helper/src/test/kotlin/cfig/helper/ZipHelperTest.kt @@ -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")) + } + } +} \ No newline at end of file diff --git a/bbootimg/src/test/kotlin/cfig/io/Struct3Test.kt b/helper/src/test/kotlin/cfig/io/Struct3Test.kt similarity index 100% rename from bbootimg/src/test/kotlin/cfig/io/Struct3Test.kt rename to helper/src/test/kotlin/cfig/io/Struct3Test.kt diff --git a/helper/src/test/resources/appcompat.zip b/helper/src/test/resources/appcompat.zip new file mode 100644 index 0000000..a06e03c Binary files /dev/null and b/helper/src/test/resources/appcompat.zip differ diff --git a/helper/src/test/resources/general.cfg b/helper/src/test/resources/general.cfg new file mode 100644 index 0000000..e69de29 diff --git a/helper/src/test/resources/platform.pk8 b/helper/src/test/resources/platform.pk8 new file mode 100644 index 0000000..e27a393 Binary files /dev/null and b/helper/src/test/resources/platform.pk8 differ diff --git a/helper/src/test/resources/platform.x509.pem b/helper/src/test/resources/platform.x509.pem new file mode 100644 index 0000000..087f02e --- /dev/null +++ b/helper/src/test/resources/platform.x509.pem @@ -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----- diff --git a/helper/src/test/resources/testkey.pk8 b/helper/src/test/resources/testkey.pk8 new file mode 100644 index 0000000..586c1bd Binary files /dev/null and b/helper/src/test/resources/testkey.pk8 differ diff --git a/helper/src/test/resources/testkey.pub b/helper/src/test/resources/testkey.pub new file mode 100644 index 0000000..47e6e4d --- /dev/null +++ b/helper/src/test/resources/testkey.pub @@ -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----- diff --git a/helper/src/test/resources/testkey.x509.pem b/helper/src/test/resources/testkey.x509.pem new file mode 100644 index 0000000..e242d83 --- /dev/null +++ b/helper/src/test/resources/testkey.x509.pem @@ -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----- diff --git a/settings.gradle.kts b/settings.gradle.kts index 4c48ece..0abcdb2 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -11,3 +11,4 @@ include("aosp:libsparse:append2simg") include("aosp:libavb1.1") include("aosp:libavb1.2") include("avbImpl") +include("helper")