diff --git a/NOTICE b/NOTICE index acf527be99c941e1ff05319340741802cca773bd..4ee7c6e55abd5f7713006790d7283649acb51c88 100644 --- a/NOTICE +++ b/NOTICE @@ -5,11 +5,12 @@ THE OPEN SOURCE SOFTWARE IN THIS SOFTWARE IS DISTRIBUTED IN THE HOPE THAT IT WIL Copyright Notice and License Texts -Software: bcprov-jdk16 1.69 -Copyright notice: -Copyright (c) 2000-2021 The Legion Of The Bouncy Castle Inc. (https://www.bouncycastle.org) -License: The Bouncy Castle License -Copyright (c) 2000-2021 The Legion Of The Bouncy Castle Inc. (https://www.bouncycastle.org) +------------------------------------------------------------ +Software: The Legion of the Bouncy Castle 1.76 +Path: +License: The Legion of the Bouncy Castle License +------------------------------------------------------------ +Copyright notice: Copyright (c) 2000 - 2023 The Legion of the Bouncy Castle Inc. (https://www.bouncycastle.org) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, @@ -27,10 +28,13 @@ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRA OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -Software: gson 2.8.6 -Copyright notice: -Copyright (C) 2012 Google Inc. +------------------------------------------------------------ +Software: gson 2.9.0 +Path: License: Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ +------------------------------------------------------------ +Copyright notice: Copyright (C) 2008-2021 Google Inc. + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. @@ -229,10 +233,13 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. -Software: Apache Log4j 2.17.1 -Copyright notice: -Copyright 1999-2020 The Apache Software Foundation +------------------------------------------------------------ +Software: Apache Log4j 2.18.0 +Path: License: Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ +------------------------------------------------------------ +Copyright notice: Copyright 1999-2022 The Apache Software Foundation + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. @@ -417,7 +424,7 @@ APPENDIX: How to apply the Apache License to your work. same "printed page" as the copyright notice for easier identification within third-party archives. -Copyright [yyyy] [name of copyright owner] +Copyright 1999-2005 The Apache Software Foundation Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -430,3 +437,289 @@ distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. + +------------------------------------------------------------ +Software: JUnit 5.9.2 +Path: +License: Eclipse Public License - v 2.0 +------------------------------------------------------------ +Copyright notice: Copyright (c) 2015-2021 the original + +Eclipse Public License - v 2.0 + + THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE + PUBLIC LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION + OF THE PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT. + +1. DEFINITIONS + +"Contribution" means: + + a) in the case of the initial Contributor, the initial content + Distributed under this Agreement, and + + b) in the case of each subsequent Contributor: + i) changes to the Program, and + ii) additions to the Program; + where such changes and/or additions to the Program originate from + and are Distributed by that particular Contributor. A Contribution + "originates" from a Contributor if it was added to the Program by + such Contributor itself or anyone acting on such Contributor's behalf. + Contributions do not include changes or additions to the Program that + are not Modified Works. + +"Contributor" means any person or entity that Distributes the Program. + +"Licensed Patents" mean patent claims licensable by a Contributor which +are necessarily infringed by the use or sale of its Contribution alone +or when combined with the Program. + +"Program" means the Contributions Distributed in accordance with this +Agreement. + +"Recipient" means anyone who receives the Program under this Agreement +or any Secondary License (as applicable), including Contributors. + +"Derivative Works" shall mean any work, whether in Source Code or other +form, that is based on (or derived from) the Program and for which the +editorial revisions, annotations, elaborations, or other modifications +represent, as a whole, an original work of authorship. + +"Modified Works" shall mean any work in Source Code or other form that +results from an addition to, deletion from, or modification of the +contents of the Program, including, for purposes of clarity any new file +in Source Code form that contains any contents of the Program. Modified +Works shall not include works that contain only declarations, +interfaces, types, classes, structures, or files of the Program solely +in each case in order to link to, bind by name, or subclass the Program +or Modified Works thereof. + +"Distribute" means the acts of a) distributing or b) making available +in any manner that enables the transfer of a copy. + +"Source Code" means the form of a Program preferred for making +modifications, including but not limited to software source code, +documentation source, and configuration files. + +"Secondary License" means either the GNU General Public License, +Version 2.0, or any later versions of that license, including any +exceptions or additional permissions as identified by the initial +Contributor. + +2. GRANT OF RIGHTS + + a) Subject to the terms of this Agreement, each Contributor hereby + grants Recipient a non-exclusive, worldwide, royalty-free copyright + license to reproduce, prepare Derivative Works of, publicly display, + publicly perform, Distribute and sublicense the Contribution of such + Contributor, if any, and such Derivative Works. + + b) Subject to the terms of this Agreement, each Contributor hereby + grants Recipient a non-exclusive, worldwide, royalty-free patent + license under Licensed Patents to make, use, sell, offer to sell, + import and otherwise transfer the Contribution of such Contributor, + if any, in Source Code or other form. This patent license shall + apply to the combination of the Contribution and the Program if, at + the time the Contribution is added by the Contributor, such addition + of the Contribution causes such combination to be covered by the + Licensed Patents. The patent license shall not apply to any other + combinations which include the Contribution. No hardware per se is + licensed hereunder. + + c) Recipient understands that although each Contributor grants the + licenses to its Contributions set forth herein, no assurances are + provided by any Contributor that the Program does not infringe the + patent or other intellectual property rights of any other entity. + Each Contributor disclaims any liability to Recipient for claims + brought by any other entity based on infringement of intellectual + property rights or otherwise. As a condition to exercising the + rights and licenses granted hereunder, each Recipient hereby + assumes sole responsibility to secure any other intellectual + property rights needed, if any. For example, if a third party + patent license is required to allow Recipient to Distribute the + Program, it is Recipient's responsibility to acquire that license + before distributing the Program. + + d) Each Contributor represents that to its knowledge it has + sufficient copyright rights in its Contribution, if any, to grant + the copyright license set forth in this Agreement. + + e) Notwithstanding the terms of any Secondary License, no + Contributor makes additional grants to any Recipient (other than + those set forth in this Agreement) as a result of such Recipient's + receipt of the Program under the terms of a Secondary License + (if permitted under the terms of Section 3). + +3. REQUIREMENTS + +3.1 If a Contributor Distributes the Program in any form, then: + + a) the Program must also be made available as Source Code, in + accordance with section 3.2, and the Contributor must accompany + the Program with a statement that the Source Code for the Program + is available under this Agreement, and informs Recipients how to + obtain it in a reasonable manner on or through a medium customarily + used for software exchange; and + + b) the Contributor may Distribute the Program under a license + different than this Agreement, provided that such license: + i) effectively disclaims on behalf of all other Contributors all + warranties and conditions, express and implied, including + warranties or conditions of title and non-infringement, and + implied warranties or conditions of merchantability and fitness + for a particular purpose; + + ii) effectively excludes on behalf of all other Contributors all + liability for damages, including direct, indirect, special, + incidental and consequential damages, such as lost profits; + + iii) does not attempt to limit or alter the recipients' rights + in the Source Code under section 3.2; and + + iv) requires any subsequent distribution of the Program by any + party to be under a license that satisfies the requirements + of this section 3. + +3.2 When the Program is Distributed as Source Code: + + a) it must be made available under this Agreement, or if the + Program (i) is combined with other material in a separate file or + files made available under a Secondary License, and (ii) the initial + Contributor attached to the Source Code the notice described in + Exhibit A of this Agreement, then the Program may be made available + under the terms of such Secondary Licenses, and + + b) a copy of this Agreement must be included with each copy of + the Program. + +3.3 Contributors may not remove or alter any copyright, patent, +trademark, attribution notices, disclaimers of warranty, or limitations +of liability ("notices") contained within the Program from any copy of +the Program which they Distribute, provided that Contributors may add +their own appropriate notices. + +4. COMMERCIAL DISTRIBUTION + +Commercial distributors of software may accept certain responsibilities +with respect to end users, business partners and the like. While this +license is intended to facilitate the commercial use of the Program, +the Contributor who includes the Program in a commercial product +offering should do so in a manner which does not create potential +liability for other Contributors. Therefore, if a Contributor includes +the Program in a commercial product offering, such Contributor +("Commercial Contributor") hereby agrees to defend and indemnify every +other Contributor ("Indemnified Contributor") against any losses, +damages and costs (collectively "Losses") arising from claims, lawsuits +and other legal actions brought by a third party against the Indemnified +Contributor to the extent caused by the acts or omissions of such +Commercial Contributor in connection with its distribution of the Program +in a commercial product offering. The obligations in this section do not +apply to any claims or Losses relating to any actual or alleged +intellectual property infringement. In order to qualify, an Indemnified +Contributor must: a) promptly notify the Commercial Contributor in +writing of such claim, and b) allow the Commercial Contributor to control, +and cooperate with the Commercial Contributor in, the defense and any +related settlement negotiations. The Indemnified Contributor may +participate in any such claim at its own expense. + +For example, a Contributor might include the Program in a commercial +product offering, Product X. That Contributor is then a Commercial +Contributor. If that Commercial Contributor then makes performance +claims, or offers warranties related to Product X, those performance +claims and warranties are such Commercial Contributor's responsibility +alone. Under this section, the Commercial Contributor would have to +defend claims against the other Contributors related to those performance +claims and warranties, and if a court requires any other Contributor to +pay any damages as a result, the Commercial Contributor must pay +those damages. + +5. NO WARRANTY + +EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, AND TO THE EXTENT +PERMITTED BY APPLICABLE LAW, THE PROGRAM IS PROVIDED ON AN "AS IS" +BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR +IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF +TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR +PURPOSE. Each Recipient is solely responsible for determining the +appropriateness of using and distributing the Program and assumes all +risks associated with its exercise of rights under this Agreement, +including but not limited to the risks and costs of program errors, +compliance with applicable laws, damage to or loss of data, programs +or equipment, and unavailability or interruption of operations. + +6. DISCLAIMER OF LIABILITY + +EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, AND TO THE EXTENT +PERMITTED BY APPLICABLE LAW, NEITHER RECIPIENT NOR ANY CONTRIBUTORS +SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION LOST +PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE +EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + +7. GENERAL + +If any provision of this Agreement is invalid or unenforceable under +applicable law, it shall not affect the validity or enforceability of +the remainder of the terms of this Agreement, and without further +action by the parties hereto, such provision shall be reformed to the +minimum extent necessary to make such provision valid and enforceable. + +If Recipient institutes patent litigation against any entity +(including a cross-claim or counterclaim in a lawsuit) alleging that the +Program itself (excluding combinations of the Program with other software +or hardware) infringes such Recipient's patent(s), then such Recipient's +rights granted under Section 2(b) shall terminate as of the date such +litigation is filed. + +All Recipient's rights under this Agreement shall terminate if it +fails to comply with any of the material terms or conditions of this +Agreement and does not cure such failure in a reasonable period of +time after becoming aware of such noncompliance. If all Recipient's +rights under this Agreement terminate, Recipient agrees to cease use +and distribution of the Program as soon as reasonably practicable. +However, Recipient's obligations under this Agreement and any licenses +granted by Recipient relating to the Program shall continue and survive. + +Everyone is permitted to copy and distribute copies of this Agreement, +but in order to avoid inconsistency the Agreement is copyrighted and +may only be modified in the following manner. The Agreement Steward +reserves the right to publish new versions (including revisions) of +this Agreement from time to time. No one other than the Agreement +Steward has the right to modify this Agreement. The Eclipse Foundation +is the initial Agreement Steward. The Eclipse Foundation may assign the +responsibility to serve as the Agreement Steward to a suitable separate +entity. Each new version of the Agreement will be given a distinguishing +version number. The Program (including Contributions) may always be +Distributed subject to the version of the Agreement under which it was +received. In addition, after a new version of the Agreement is published, +Contributor may elect to Distribute the Program (including its +Contributions) under the new version. + +Except as expressly stated in Sections 2(a) and 2(b) above, Recipient +receives no rights or licenses to the intellectual property of any +Contributor under this Agreement, whether expressly, by implication, +estoppel or otherwise. All rights in the Program not expressly granted +under this Agreement are reserved. Nothing in this Agreement is intended +to be enforceable by any entity that is not a Contributor or Recipient. +No third-party beneficiary rights are created under this Agreement. + +Exhibit A - Form of Secondary Licenses Notice + +"This Source Code may also be made available under the following +Secondary Licenses when the conditions for such availability set forth +in the Eclipse Public License, v. 2.0 are satisfied: {name license(s), +version(s), and exceptions or additional permissions here}." + + Simply including a copy of this Agreement, including this Exhibit A + is not sufficient to license the Source Code under Secondary Licenses. + + If it is not possible or desirable to put the notice in a particular + file, then You may include the notice in a location (such as a LICENSE + file in a relevant directory) where a recipient would be likely to + look for such a notice. + + You may add additional accurate notices of copyright ownership. + diff --git a/OAT.xml b/OAT.xml index f35a7042d43d51fe12bb04f2c8fbe3cdc08fa708..0f0759c0b04898d87e988005fa8f6942a44a2ebd 100644 --- a/OAT.xml +++ b/OAT.xml @@ -20,6 +20,7 @@ + diff --git a/README.md b/README.md index 04a052a4c0aa121b15ced6b456cc391123347bc7..0cea090fbac8c0594f3dc807da2dbf0f78bc1a1b 100644 --- a/README.md +++ b/README.md @@ -1,49 +1,55 @@ # hapsigner -#### Introduction +## Introduction -To ensure the integrity and secure source of OpenHarmony applications, the applications must be signed during the build process. Only signed applications can be installed, run, and debugged on real devices. This repository provides the source code of the HAP signing tool - hapsigner. This tool can be used to generate key pairs, certificate signing requests (CSRs), certificates, profile signatures, and HAP signatures. +To ensure that all apps and binary tools (such as lldb-server) come from a known and approved source and have not been tampered with, OpenHarmony requires that all executable code be signed. Only signed apps and binary tools can be installed, run, and debugged on real devices. +The repository provides the source code of the signing tool named hapsigner, which provides the functions such as generating a key pair, a certificate signing request (CSR), or a certificate, and signing a profile, a Harmony Ability Package (HAP), or a binary tool. +The mandatory code signing mechanism provides validity check and integrity protection for apps in runtime, eliminating execution of malicious code on devices and malicious tampering of app code by attackers. -#### Directory Structure +Code signing is enabled by default for hapsigner. If you do not need the mandatory code signing feature, you can disable it as required. Currently, hapsigner supports code signing only for apps in hap format and binary tools. + + +## Directory Structure developtools_hapsigner - ├── autosign # One-click signature script. - ├── dist # SDK preconfigured file. - ├── hapsigntool # Master code. - ├──hap_sign_tool # Application entry, used to verify input parameters. - ├──hap_sign_tool_lib # Signing tool lib, used to parse command words and parameter lists to implement logic of modules. + ├── autosign # Script for one-click signing. + ├── dist # SDK preconfigured file. + ├── hapsigntool # Code of the hapsigner tool. + ├──hap_sign_tool # Entry of the hapsigner tool, used to verify input parameters. + ├──hap_sign_tool_lib # Lib of the hapsigner tool, used to parse command words and parameter lists to implement logic of modules. ├── tools # Auto-test script. -#### Constraints -hapsigner is developed in Java and must run in JRE 8.0 or later. -The scripts, such as the one-click signature script, are developed in Python, and must run on Python 3.x. -#### Build +## Constraints +- The hapsigner tool is developed in Java and must run in JRE 8.0 or later. +- The scripts, such as the one-click signing script, are developed in Python, and must run on Python 3.5 or later. + +## Build 1. Check that Gradle 7.1 has been installed. - + gradle -v - 2. Download the code, open the file directory **developtools_hapsigner/hapsigntool**, and run the following command to build the code: - + 2. Download the code, open the directory **developtools_hapsigner/hapsigntool**, and run the following command to build the code: + gradle build or gradle jar 3. Check that **hap-sign-tool.jar** (binary files) is generated in the **./hap_sign_tool/build/libs** directory. -**** -#### Usage -##### Usage of Signature-related Files -When signing an application using the IDE, you will obtain the following files from the SDK: +## Usage +### Files Related to Signing + +When signing an app using the IDE, you will obtain the following files from the SDK: ``` KeyStore (KS) file: OpenHarmony.p12 Profile signing certificates: OpenHarmonyProfileRelease.pem and OpenHarmonyProfileDebug.pem Profile templates: UnsgnedReleasedProfileTemplate.json and UnsgnedDebugProfileTemplate.json -Signature tool: hap-sign-tool.jar +Signing tool: hap-sign-tool.jar ``` The figures below illustrate how these files are used. @@ -54,16 +60,16 @@ The figures below illustrate how these files are used. **Signing an App** ![signapp.png](figures/signapp_en.png) -##### Note +### Usage Guidelines + +In the following, the .jar package is the binary files built. -In the following, the JAR package used is the binary files generated during the build process. +#### Using Commands +You can use commands to sign a profile and a HAP or binary tool. -1. Command line signatures - Command line signatures include profile signatures and HAP signatures. +1. Sign a profile. - (1) Sign a profile. - ```shell java -jar hap-sign-tool.jar sign-profile -keyAlias "oh-profile1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -profileCertFile "result\profile1.pem" -inFile "app1-profile-release.json" -keystoreFile "result\ohtest.jks" -outFile "result\app1-profile.p7b" -keyPwd "123456" -keystorePwd "123456" ``` @@ -74,67 +80,71 @@ The parameters in the command are described as follows: ├── -keyAlias # Key alias. It is mandatory. ├── -keyPwd # Key password. It is optional. ├── -profileCertFile # Profile signing certificate (certificate chain, in the end-entity certificate, intermediate CA certificate, and root certificate order). It is mandatory. - ├── -inFile # Raw provisioning profile. It is mandatory. - ├── -signAlg # Signature algorithm, which can be SHA256withECDSA or SHA384withECDSA. It is mandatory. + ├── -inFile # Provisioning profile to be signed. It is mandatory. + ├── -signAlg # Signing algorithm, which can be SHA256withECDSA or SHA384withECDSA. It is mandatory. ├── -keystoreFile # KS file, in JKS or P12 format. It is mandatory if the signing mode is localSign. ├── -keystorePwd # KS password. It is optional. ├── -outFile # Signed provisioning profile to generate, in p7b format. It is mandatory. -(2) Sign a HAP. +2. Sign a HAP or binary tool. ```shell -java -jar hap-sign-tool.jar sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "result\app1.pem" -profileFile "result\app1-profile.p7b" -inFile "app1-unsigned.zip" -keystoreFile "result\ohtest.jks" -outFile "result\app1-unsigned.hap" -keyPwd "123456" -keystorePwd "123456" +java -jar hap-sign-tool.jar sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "result\app1.pem" -profileFile "result\app1-profile.p7b" -inFile "app1-unsigned.zip" -keystoreFile "result\ohtest.jks" -outFile "result\app1-unsigned.hap" -keyPwd "123456" -keystorePwd "123456" -signCode "1" ``` The parameters in the command are described as follows: - sign-app: Sign a HAP. + sign-app: sign a HAP or binary tool ├── -mode # Signing mode, which can be localSign or remoteSign. It is mandatory. ├── -keyAlias # Key alias. It is mandatory. ├── -keyPwd # Key password. It is optional. - ├── -appCertFile # Application signing certificate (certificate chain, in the end-entity certificate, intermediate CA certificate, and root certificate order). It is mandatory. - ├── -profileFile # Singed provisioning profile, in p7b format. It is mandatory. - ├── -profileSigned # Whether the profile is signed. The value 1 means signed, and value 0 means unsigned. The default value is 1. It is optional. - ├── -inForm # Raw file, in .zip (default) or .bin format. It is optional. - ├── -inFile # Raw application package, in .zip or .bin format. It is mandatory. - ├── -signAlg # Signature algorithm, which can be SHA256withECDSA or SHA384withECDSA. It is mandatory. - ├── -keystoreFile # KeyStore (KS) file, in JKS or P12 format. It is mandatory if the signing mode is localSign. + ├── -appCertFile # App signing certificate (certificate chain, in the end-entity certificate, intermediate CA certificate, and root certificate order). It is mandatory. + ├── -profileFile # Signed provisioning profile in p7b format. This parameter is mandatory for a HAP and optional for a binary tool. + ├── -profileSigned # Whether the profile is signed. The value 1 means the profile is signed, and the value 0 means the opposite. The default value is 1. This parameter is optional. + ├── -inForm # Format of the file to be signed. The value can be zip, elf, or bin. It is zip for a HAP, elf for a binary tool, and bin for a program running on the small system. In case of code signing, it can be zip or elf. The default value is zip. This parameter is optional. + ├── -inFile # File to be signed, which can be a HAP or an ELF or bin file. This parameter is mandatory. + ├── -signAlg # Signing algorithm, which can be SHA256withECDSA or SHA384withECDSA. It is mandatory. + ├── -keystoreFile # KS file, in JKS or P12 format. It is mandatory if the signing mode is localSign. ├── -keystorePwd # KS password. It is optional. - ├── -outFile # Signed HAP file to generate. It is mandatory. - + ├── -outFile # Signed HAP to generate. It is mandatory. + ├── -signCode # Whether to enable code signing. The value 1 means to enable code signing; the value 0 means the opposite. The default value is 1. This parameter is optional. -2. One-click signature +#### Performing One-Click Signing -To improve development efficiency, this project also provides one-click signature scripts based on the hapsigner tool. You can use these scripts to easily generate key pairs and end-entity certificates and sign profiles and HAPs, instead of entering complex commands. -The scripts and configuration files are located in the **autosign** directory. +To improve development efficiency, this project also provides scripts for one-click signing. You can use the scripts to easily generate a key pair or an end-entity certificate and sign a profile, HAP, or binary tool without entering complex commands. +The following scripts and configuration files are located in the **autosign** directory: - create_root.sh/create_root.bat - create_appcert_sign_profile.sh/create_appcert_sign_profile.bat - sign_hap.sh/sign_hap.bat + - sign_elf.sh/sign_elf.bat - createAppCertAndProfile.config - createRootAndSubCert.config - signHap.config - -Procedure: -1. Ensure that Python 3.5 or later has been installed. -2. Prepare **hap-sign-tool.jar**. For details, see section **Build**. -3. Prepare the HAP to be signed and the provisioning profile template file. -4. Use the text editor to open the **createAppCertAndProfile.config** file and **signHap.config** file and change the values of **common.keyPwd** and **common.issuerKeyPwd** to match your case. -5. Run **create_appcert_sign_profile.sh** in Linux or **create_appcert_sign_profile.bat** in Windows to generate files required for signature. -6. Run **sign_hap.sh** in Linux or **sign_hap.bat** in Windows to sign the HAP. - - > Note: To generate the KS file, root CA certificate, intermediate CA certificate, and profile signing certificate, perform the following steps: + - signElf.config + +**Procedure** +1. Check that Python 3.5 or later is available. +2. Obtain **hap-sign-tool.jar**. For details, see section **Build**. +3. Check that the HAP, binary tool, or provisioning profile to be signed is available. +4. Use the text editor to open **createAppCertAndProfile.config**, **signElf.config**, and **signHap.config** and change the values of **common.keyPwd** and **common.issuerKeyPwd** to match your case. +5. Run **create_appcert_sign_profile.sh** on Linux or **create_appcert_sign_profile.bat** on Windows to generate the files required for signing. +6. Run **sign_hap.sh** on Linux or **sign_hap.bat** on Windows to sign the HAP. Run **sign_elf.sh** on Linux or **sign_elf.bat** on Windows to sign the binary tool. + + > **NOTE** + > + > To generate a KS file, root CA certificate, intermediate CA certificate, and profile signing certificate, perform the following steps: 1. Use the text editor to open the **createRootAndSubCert.config** file and change the values of **common.keyPwd** and **common.issuerKeyPwd** to match your case. - 2. Run **create_root.sh** in Linux or run **create_root.bat** in Windows to generate the required KS file, root CA certificate, intermediate CA certificate, and profile signing certificate. - + 2. Run **create_root.sh** on Linux or run **create_root.bat** on Windows to generate the required KS file, root CA certificate, intermediate CA certificate, and profile signing certificate. **** -##### Common Operations -1.Generate a key pair. +### Common Operations +1. Generate a key pair. + ``` generate-keypair: Generate a key pair. ├── -keyAlias # Key alias. It is mandatory. ├── -keyPwd # Key password. It is optional. @@ -142,41 +152,50 @@ Procedure: ├── -keySize # Key length. It is mandatory. The key length is 2048, 3072, or 4096 bits if RSA is used and is NIST-P-256 or NIST-P-384 if ECC is used. ├── -keystoreFile # KS file, in JKS or P12 format. It is mandatory. ├── -keystorePwd # KS password. It is optional. + ``` -2.Generate a CSR. +2. Generate a CSR. + + ``` generate-csr: Generate a CSR. ├── -keyAlias # Key alias. It is mandatory. ├── -keyPwd # Key password. It is optional. ├── -subject # Certificate subject. It is mandatory. - ├── -signAlg # Signature algorithm, which can be SHA256withRSA, SHA384withRSA, SHA256withECDSA, or SHA384withECDSA. It is mandatory. + ├── -signAlg # Signing algorithm, which can be SHA256withRSA, SHA384withRSA, SHA256withECDSA, or SHA384withECDSA. It is mandatory. ├── -keystoreFile # KS file, in JKS or P12 format. It is mandatory. ├── -keystorePwd # KS password. It is optional. ├── -outFile # CSR to generate. It is optional. If you do not specify this parameter, the CSR is output to the console. + ``` -3.Generate a root CA or intermediate CA certificate. - - generate-ca: Generate a root CA or intermediate CA certificate. If the key does not exist, generate a key together with the certificate. - ├── -keyAlias # Key alias. It is mandatory. - ├── -keyPwd # Key password. It is optional. - ├── -keyAlg # Key algorithm, which can be RSA or ECC. It is mandatory. - ├── -keySize # Key length. It is mandatory. The key length is 2048, 3072, or 4096 bits if RSA is used and is NIST-P-256 or NIST-P-384 if ECC is used. - ├── -issuer # Issuer of the certificate. It is optional. It indicates a root CA certificate if not specified. - ├── -issuerKeyAlias # Key alias of the issuer. It is optional. It indicates a root CA certificate if not specified. - ├── -issuerKeyPwd # Key password of the issuer. It is optional. - ├── -subject # Certificate subject. It is mandatory. - ├── -validity # Validity period of the certificate. It is optional. The default value is 3650 days. - ├── -signAlg # Signature algorithm, which can be SHA256withRSA, SHA384withRSA, SHA256withECDSA, or SHA384withECDSA. It is mandatory. - ├── -basicConstraintsPathLen # Path length. It is optional. The default value is 0. - ├── -issuerKeystoreFile # KS file of the issuer, in JKS or P12 format. It is optional. - ├── -issuerKeystorePwd # KS password of the issuer. It is optional. - ├── -keystoreFile # KS file, in JKS or P12 format. It is mandatory. - ├── -keystorePwd # KS password. It is optional. - ├── -outFile # File to generate. It is optional. The file is output to the console if this parameter is not specified. -4.Generate an application debug or release certificate. +3. Generate a root CA or intermediate CA certificate. - generate-app-cert: Generate an application debug or release certificate. + ``` + generate-ca: Generate a root CA or intermediate CA certificate. If the key does not exist, generate a key together with the certificate. + ├── -keyAlias # Key alias. It is mandatory. + ├── -keyPwd # Key password. It is optional. + ├── -keyAlg # Key algorithm, which can be RSA or ECC. It is mandatory. + ├── -keySize # Key length. It is mandatory. The key length is 2048, 3072, or 4096 bits if RSA is used and is NIST-P-256 or NIST-P-384 if ECC is used. + ├── -issuer # Issuer of the certificate. It is optional. It indicates a root CA certificate if not specified. + ├── -issuerKeyAlias # Key alias of the issuer. It is optional. It indicates a root CA certificate if not specified. + ├── -issuerKeyPwd # Key password of the issuer. It is optional. + ├── -subject # Certificate subject. It is mandatory. + ├── -validity # Validity period of the certificate. It is optional. The default value is 3650 days. + ├── -signAlg # Signing algorithm, which can be SHA256withRSA, SHA384withRSA, SHA256withECDSA, or SHA384withECDSA. It is mandatory. + ├── -basicConstraintsPathLen # Path length. It is optional. The default value is 0. + ├── -issuerKeystoreFile # KS file of the issuer, in JKS or P12 format. It is optional. + ├── -issuerKeystorePwd # KS password of the issuer. It is optional. + ├── -keystoreFile # KS file, in JKS or P12 format. It is mandatory. + ├── -keystorePwd # KS password. It is optional. + ├── -outFile # File to generate. It is optional. The file is output to the console if this parameter is not specified. + ``` + + +4. Generate an app debug or release certificate. + + ``` + generate-app-cert: Generate an app debug or release certificate. ├── -keyAlias # Key alias. It is mandatory. ├── -keyPwd # Key password. It is optional. ├── -issuer # Issuer of the certificate. It is mandatory. @@ -184,18 +203,21 @@ Procedure: ├── -issuerKeyPwd # Key password of the issuer. It is optional. ├── -subject # Certificate subject. It is mandatory. ├── -validity # Validity period of the certificate. It is optional. The default value is 3650 days. - ├── -signAlg # Signature algoritym, which can be SHA256withECDSA or SHA384withECDSA. + ├── -signAlg # Signing algorithm, which can be SHA256withECDSA or SHA384withECDSA. ├── -keystoreFile # KS file, in JKS or P12 format. It is mandatory. ├── -keystorePwd # KS password. It is optional. ├── -issuerKeystoreFile # KS file of the issuer, in JKS or P12 format. It is optional. - ├── -issuerKeystorePwd # KS password of the issuer. It is optional. + ├── -issuerKeystorePwd # KS password of the issuer. It is optional. ├── -outForm # Format of the certificate to generate. It is optional. The value can be cert or certChain. The default value is certChain. ├── -rootCaCertFile # Root CA certificate, which is mandatory when outForm is certChain. ├── -subCaCertFile # Intermediate CA certificate file, which is mandatory when outForm is certChain. ├── -outFile # Certificate file (certificate or certificate chain) to generate. It is optional. The file is output to the console if this parameter is not specified. + ``` + -5.Generate a profile debug or release certificate. +5. Generate a profile debug or release certificate. + ``` generate-profile-cert: Generate a profile debug or release certificate. ├── -keyAlias # Key alias. It is mandatory. ├── -keyPwd # Key password. It is optional. @@ -204,88 +226,104 @@ Procedure: ├── -issuerKeyPwd # Key password of the issuer. It is optional. ├── -subject # Certificate subject. It is mandatory. ├── -validity # Validity period of the certificate. It is optional. The default value is 3650 days. - ├── -signAlg # Signature algoritym, which can be SHA256withECDSA or SHA384withECDSA. + ├── -signAlg # Signing algorithm, which can be SHA256withECDSA or SHA384withECDSA. ├── -keystoreFile # KS file, in JKS or P12 format. It is mandatory. ├── -keystorePwd # KS password. It is optional. ├── -issuerKeystoreFile # KS file of the issuer, in JKS or P12 format. It is optional. - ├── -issuerKeystorePwd # KS password of the issuer. It is optional. + ├── -issuerKeystorePwd # KS password of the issuer. It is optional. ├── -outForm # Format of the certificate to generate. It is optional. The value can be cert or certChain. The default value is certChain. ├── -rootCaCertFile # Root CA certificate, which is mandatory when outForm is certChain. ├── -subCaCertFile # Intermediate CA certificate file, which is mandatory when outForm is certChain. ├── -outFile # Certificate file (certificate or certificate chain) to generate. It is optional. The file is output to the console if this parameter is not specified. + ``` -6.Generate a common certificate, which can be used to generate a custom certificate. - generate-cert: Generate a common certificate, which can be used to generate a custom certificate. - ├── -keyAlias # Key alias. It is mandatory. - ├── -keyPwd # Key password. It is optional. - ├── -issuer # Issuer of the certificate. It is mandatory. - ├── -issuerKeyAlias # Key alias of the issuer. It is mandatory. - ├── -issuerKeyPwd # Key password of the issuer. It is optional. - ├── -subject # Certificate subject. It is mandatory. - ├── -validity # Validity period of the certificate. It is optional. The default value is 1095 days. - ├── -keyUsage # Usages of the key. It is mandatory. The key usages include digitalSignature, nonRepudiation, keyEncipherment, - ├ dataEncipherment, keyAgreement, certificateSignature, crlSignature, encipherOnly, and decipherOnly. - ├ Use a comma (,) to separate multiple values. - ├── -keyUsageCritical # Whether keyUsage is a critical option. It is optional. The default value is true. - ├── -extKeyUsage # Extended key usages. It is optional. The extended key usages include clientAuthentication, serverAuthentication, - ├ codeSignature, emailProtection, smartCardLogin, timestamp, and ocspSignature. - ├── -extKeyUsageCritical # Whether extKeyUsage is a critical option. It is optional. The default value is false. - ├── -signAlg # Signature algorithm, which can be SHA256withRSA, SHA384withRSA, SHA256withECDSA, or SHA384withECDSA. It is mandatory. - ├── -basicConstraints # Whether basicConstraints is contained. It is optional. The default value is false. - ├── -basicConstraintsCritical # Whether basicConstraints is a critical option. It is optional. The default value is false. - ├── -basicConstraintsCa # Whether it is CA. It is optional. The default value is false. - ├── -basicConstraintsPathLen # Path length. It is optional. The default value is 0. - ├── -issuerKeystoreFile # KS file of the issuer, in JKS or P12 format. It is optional. - ├── -issuerKeystorePwd # KS password of the issuer. It is optional. - ├── -keystoreFile # KS file, in JKS or P12 format. It is mandatory. - ├── -keystorePwd # KS password. It is optional. - ├── -outFile # Certificate file to generate. It is optional. The file is output to the console if this parameter is not specified. - -7.Sign a provisioning profile. +6. Generate a common certificate, which can be used to generate a custom certificate. + ``` + generate-cert: Generate a common certificate, which can be used to generate a custom certificate. + ├── -keyAlias # Key alias. It is mandatory. + ├── -keyPwd # Key password. It is optional. + ├── -issuer # Issuer of the certificate. It is mandatory. + ├── -issuerKeyAlias # Key alias of the issuer. It is mandatory. + ├── -issuerKeyPwd # Key password of the issuer. It is optional. + ├── -subject # Certificate subject. It is mandatory. + ├── -validity # Validity period of the certificate. It is optional. The default value is 1095 days. + ├── -keyUsage # Usages of the key. It is mandatory. The key usages include digitalSignature, nonRepudiation, keyEncipherment, + ├ dataEncipherment, keyAgreement, certificateSignature, crlSignature, + ├ encipherOnly, and decipherOnly. Use a comma (,) to separate multiple values. + ├── -keyUsageCritical # Whether keyUsage is a critical option. It is optional. The default value is true. + ├── -extKeyUsage # Extended key usages. It is optional. The extended key usages include clientAuthentication, serverAuthentication, + ├ codeSignature, emailProtection, smartCardLogin, timestamp, and ocspSignature. + ├── -extKeyUsageCritical # Whether extKeyUsage is a critical option. It is optional. The default value is false. + ├── -signAlg # Signing algorithm, which can be SHA256withRSA, SHA384withRSA, SHA256withECDSA, or SHA384withECDSA. It is mandatory. + ├── -basicConstraints # Whether basicConstraints is contained. It is optional. The default value is false. + ├── -basicConstraintsCritical # Whether basicConstraints is a critical option. It is optional. The default value is false. + ├── -basicConstraintsCa # Whether it is a CA. It is optional. The default value is false. + ├── -basicConstraintsPathLen # Path length. It is optional. The default value is 0. + ├── -issuerKeystoreFile # KS file of the issuer, in JKS or P12 format. It is optional. + ├── -issuerKeystorePwd # KS password of the issuer. It is optional. + ├── -keystoreFile # KS file, in JKS or P12 format. It is mandatory. + ├── -keystorePwd # KS password. It is optional. + ├── -outFile # Certificate file to generate. It is optional. The file is output to the console if this parameter is not specified. + ``` + + +7. Sign a provisioning profile. + + ``` sign-profile: Sign a provisioning profile. ├── -mode # Signing mode, which can be localSign or remoteSign. It is mandatory. ├── -keyAlias # Key alias. It is mandatory. ├── -keyPwd # Key password. It is optional. ├── -profileCertFile # Profile signing certificate (certificate chain, in the end-entity certificate, intermediate CA certificate, and root certificate order). It is mandatory. - ├── -inFile # Raw provisioning profile. It is mandatory. - ├── -signAlg # Signature algorithm, which can be SHA256withECDSA or SHA384withECDSA. It is mandatory. + ├── -inFile # Provisioning profile to be signed. It is mandatory. + ├── -signAlg # Signing algorithm, which can be SHA256withECDSA or SHA384withECDSA. It is mandatory. ├── -keystoreFile # KS file, in JKS or P12 format. It is mandatory if the signing mode is localSign. ├── -keystorePwd # KS password. It is optional. ├── -outFile # Signed provisioning profile to generate, in p7b format. It is mandatory. + ``` -8.Verify the provisioning profile signature. +8. Verify the provisioning profile signature. + + ``` verify-profile: Verify the provisioning profile signature. - ├── -inFile # Signed provisioning profile, in p7b format. It is mandatory. - ├── -outFile # Verification result file (including the verification result and profile content), in json format. It is optional. The file is output to the console if this parameter is not specified. + ├── -inFile # Signed provisioning profile, in p7b format. It is mandatory. + ├── -outFile # Verification result file (including the verification result and profile content), in json format. It is optional. The verification result is output to the console if this parameter is not specified. + ``` + + +9. Sign a HAP or binary tool -9.Sign a HAP. - - sign-app: Sign a HAP + ``` + sign-app: sign a HAP or binary tool ├── -mode # Signing mode, which can be localSign, remoteSign, or remoteResign. It is mandatory. ├── -keyAlias # Key alias. It is mandatory. ├── -keyPwd # Key password. It is optional. - ├── -appCertFile # Application signing certificate (certificate chain, in the end-entity certificate, intermediate CA certificate, and root certificate order). It is mandatory. - ├── -profileFile # Name of the signed provisioning profile. The profile is in p7b format if profileSigned is 1 and in json format if profileSigned is 0. It is mandatory. - ├── -profileSigned # Whether the profile is signed. The value 1 means signed, and value 0 means unsigned. The default value is 1. It is optional. - ├── -inForm # Raw file, in .zip (default) or .bin format. It is optional. - ├── -inFile # Raw application package, in .zip or .bin format. It is mandatory. - ├── -signAlg # Signature algorithm, which can be SHA256withECDSA or SHA384withECDSA. It is mandatory. + ├── -appCertFile # App signing certificate (certificate chain, in the end-entity certificate, intermediate CA certificate, and root certificate order). It is mandatory. + ├── -profileFile # Name of the signed provisioning profile. When profileSigned is 1, the file is in p7b format. When profileSigned is 0, the file is in JSON format. This parameter is mandatory for a HAP and optional for a binary tool. + ├── -profileSigned # Whether the profile is signed. The value 1 means the profile is signed, and the value 0 means the opposite. The default value is 1. This parameter is optional. + ├── -inForm # Format of the file to be signed. The value can be zip, elf, or bin. It is zip for a HAP, elf for a binary tool, and bin for a program running on the small system. In case of code signing, it can be zip or elf. The default value is zip. This parameter is optional. + ├── -inFile # File to be signed, which can be a HAP or an ELF or bin file. This parameter is mandatory. + ├── -signAlg # Signing algorithm, which can be SHA256withECDSA or SHA384withECDSA. It is mandatory. ├── -keystoreFile # KS file, in JKS or P12 format. It is mandatory if the signing mode is localSign. ├── -keystorePwd # KS password. It is optional. - ├── -outFile # Signed HAP file to generate. It is mandatory. + ├── -outFile # Signed HAP to generate. It is mandatory. + ├── -signCode # Whether to enable code signing. The value 1 means to enable code signing; the value 0 means the opposite. The default value is 1. This parameter is optional. + ``` -10.Verify the HAP Signature. - verify-app: Verify the HAP signature. - ├── -inFile # Signed application file, in .zip or .bin format. It is mandatory. - ├── -outCertChain # Signed certificate chain file. It is mandatory. - ├── -outProfile # Profile of the application. It is mandatory. +10. Verify the signature of a HAP or a binary tool. + ``` + verify-app: verify the signature of a HAP or a binary tool. + ├── -inFile # Signed file, which can be a HAP, an ELF file, or a bin file. This parameter is mandatory. + ├── -outCertchain # Signed certificate chain file. It is mandatory. + ├── -outProfile # Profile of the app. It is mandatory. + ├── -inForm # Format of the file to be signed. The value can be zip, elf, or bin. It is zip for a HAP, elf for a binary tool, and bin for a program running on the small system. In case of code signing, it can be zip or elf. The default value is zip. This parameter is optional. + ``` - -#### Repositories Involved +## Repositories Involved N/A diff --git a/README_ZH.md b/README_ZH.md index 5ce50a28953648bd0b855f8d0e1ac0edc18d3149..64ceea1aace5827ad7252e8d7dd3a01b4c919c4c 100644 --- a/README_ZH.md +++ b/README_ZH.md @@ -12,7 +12,9 @@ #### 简介 -为了保证OpenHarmony应用的完整性和来源可靠,在应用构建时需要对应用进行签名。经过签名的应用才能在真机设备上安装、运行、和调试。本仓提供了签名工具的源码,包含密钥对生成、CSR文件生成、证书生成、Profile文件签名、Hap包签名等功能。 +为了保证OpenHarmony应用和二进制工具(如:lldb-server)的完整性和来源可靠,需要对应用和二进制工具进行签名。经过签名的应用和二进制工具才能在真机设备上安装、运行和调试。本仓提供了签名工具的源码,包含密钥对生成、CSR文件生成、证书生成、Profile文件签名、Hap包签名、二进制工具签名等功能。 +在支持强制代码签名机制的设备上,该机制可以为应用提供运行时的合法性校验以及完整性保护,杜绝未经审核的恶意代码在端侧任意执行,或应用代码被攻击者恶意篡改。 +签名工具默认开启代码签名,若用户确定不需要强制执行代码签名,可参考以下说明,关闭代码签名功能。签名工具当前仅支持对hap格式应用和二进制工具执行代码签名。 #### 目录 @@ -29,8 +31,8 @@ #### 约束 -Hap包签名工具基于Java语言开发,需要在Java8以上Java环境运行 -(附:一键签名等脚本文件基于Python语言开发,使用需配置环境python3.x) +- Hap包签名工具基于Java语言开发,需要在Java8以上Java环境运行。 +- 一键签名等脚本文件基于Python语言开发,使用需配置环境python3.5及以上。 #### 编译构建 1. 该工具基于Gradle 7.1编译构建,请确认环境已安装配置Gradle环境,并且版本正确 @@ -69,7 +71,7 @@ Profile模板文件:UnsgnedReleasedProfileTemplate.json、UnsgnedDebugProfileT 以下说明中使用jar包为编译构建中生成的二进制文件 1.命令行签名 - 命令行签名分为profile文件签名和hap包签名。 + 命令行签名分为profile文件签名和hap包或二进制工具签名。 (1)签名profile文件的命令实例如下: @@ -92,49 +94,51 @@ java -jar hap-sign-tool.jar sign-profile -keyAlias "oh-profile1-key-v1" -signAl -(2)签名Hap包的命令实例如下: +(2)签名Hap包或二进制工具的命令实例如下: ```shell -java -jar hap-sign-tool.jar sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "result\app1.pem" -profileFile "result\app1-profile.p7b" -inFile "app1-unsigned.zip" -keystoreFile "result\ohtest.jks" -outFile "result\app1-unsigned.hap" -keyPwd "123456" -keystorePwd "123456" +java -jar hap-sign-tool.jar sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "result\app1.pem" -profileFile "result\app1-profile.p7b" -inFile "app1-unsigned.zip" -keystoreFile "result\ohtest.jks" -outFile "result\app1-unsigned.hap" -keyPwd "123456" -keystorePwd "123456" -signCode "1" ``` 该命令的参数说明如下: - sign-app : hap应用包签名 + sign-app : hap应用包和二进制工具签名 ├── -mode #签名模式,必填项,包括localSign,remoteSign ├── -keyAlias #密钥别名,必填项 ├── -keyPwd #密钥口令,可选项 ├── -appCertFile #应用签名证书文件(证书链,顺序为最终实体证书-中间CA证书-根证书),必填项 - ├── -profileFile #签名后的Provision Profile文件名,p7b格式,必填项 + ├── -profileFile #签名后的Provision Profile文件名,p7b格式,hap应用包签名必填项,二进制工具签名选填 ├── -profileSigned #指示profile文件是否带有签名,1表示有签名,0表示没有签名,默认为1。可选项 - ├── -inForm #输入的原始文件的格式,zip格式或bin格式,默认zip格式;可选项 - ├── -inFile #输入的原始APP包文件,zip格式或bin格式,必填项 + ├── -inForm #输入的原始文件的格式,枚举值:zip、elf或bin;zip和elf支持代码签名,hap 应用包对应zip,二进制工具对应elf,默认zip;可选项 + ├── -inFile #输入的原始文件,hap应用、elf或bin文件,必填项 ├── -signAlg #签名算法,必填项,包括SHA256withECDSA / SHA384withECDSA ├── -keystoreFile #密钥库文件,localSign模式时为必填项,JKS或P12格式 ├── -keystorePwd #密钥库口令,可选项 ├── -outFile #输出签名后的包文件,必填项 - + ├── -signCode #是否启用代码签名,1表示开启代码签名,0表示关闭代码签名,默认为1。可选项 2.一键签名 -为降低学习成本,提高开发效率,本项目还将基于应用签名工具提供一键签名脚本,免于输入繁杂的参数命令,脚本内容包括生成密钥对、最终实体证书、签名profile包、签名hap包的命令。 +为降低学习成本,提高开发效率,本项目还将基于应用签名工具提供一键签名脚本,免于输入繁杂的参数命令,脚本内容包括生成密钥对、实体证书、签名profile包、签名hap包和二进制工具的命令。 脚本以及配置文件位于目录 autosign 下: - create_root.sh/create_root.bat - create_appcert_sign_profile.sh/create_appcert_sign_profile.bat - sign_hap.sh/sign_hap.bat + - sign_elf.sh/sign_elf.bat - createAppCertAndProfile.config - createRootAndSubCert.config - signHap.config + - signElf.config 使用指导: 1. 准备依赖环境 python3.5 以上 2. 准备签名工具jar包:hap-sign-tool.jar(参照上文编译生成的产物) -3. 准备待签名的应用hap包和 Provision profile 模板文件 -4. 使用文本编辑器编辑 createAppCertAndProfile.config 和 signHap.config,修改配置文件中的配置信息:common.keyPwd 和 common.issuerKeyPwd 参数值改成自己定义的口令信息 +3. 准备待签名的应用hap包、二进制工具和 Provision profile 模板文件 +4. 使用文本编辑器编辑 createAppCertAndProfile.config、signElf.config 和 signHap.config,修改配置文件中的配置信息:common.keyPwd 和 common.issuerKeyPwd 参数值改成自己定义的口令信息 5. Linux运行 create_appcert_sign_profile.sh、Windows运行 create_appcert_sign_profile.bat 生成签名所需文件 -6. Linux运行 sign_hap.sh、Windows运行 sign_hap.bat 对hap包进行签名 +6. Linux运行 sign_hap.sh、Windows运行 sign_hap.bat 对hap包进行签名;Linux运行 sign_elf.sh、Windows运行 sign_elf.bat 对二进制工具进行签名 > 说明:如需自定义生成密钥库文件,根CA,中间CA证书,profile签名证书,可执行以下步骤 1.使用文本编辑器编辑 createRootAndSubCert.config 修改配置文件中的配置信息:common.keyPwd 和 common.issuerKeyPwd 参数值改成自己定义的口令信息 @@ -269,32 +273,32 @@ java -jar hap-sign-tool.jar sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256 verify-profile : ProvisionProfile文件验签 ├── -inFile # 已签名的Provision Profile文件,p7b格式,必填项 - ├── -outFile # 验证结果文件(包含验证结果和profile内容),json格式,可选项;如果不填,则直接输出到控制台 + ├── -outFile # 验证结果文件(包含验证结果和profile内容),json格式,可选项;如果不填,则直接输出到控制台 -9.hap应用包签名 +9.hap应用包和二进制工具签名 - sign-app : hap应用包签名 + sign-app : hap应用包和二进制工具签名 ├── -mode # 签名模式,必填项,包括localSign,remoteSign,remoteResign ├── -keyAlias # 密钥别名,必填项 ├── -keyPwd # 密钥口令,可选项 ├── -appCertFile # 应用签名证书文件(证书链,顺序为最终实体证书-中间CA证书-根证书),必填项 - ├── -profileFile # 签名后的Provision Profile文件名,profileSigned为1时为p7b格式,profileSigned为0时为json格式,必填项 + ├── -profileFile # 签名后的Provision Profile文件名,profileSigned为1时为p7b格式,profileSigned为0时为json格式,hap应用包签名必填项,二进制工具签名选填 ├── -profileSigned # 指示profile文件是否带有签名,1表示有签名,0表示没有签名,默认为1。可选项 - ├── -inForm # 输入的原始文件的格式,zip格式或bin格式,默认zip格式,可选项 - ├── -inFile # 输入的原始APP包文件,zip格式或bin格式,必填项 + ├── -inForm # 输入的原始文件的格式,枚举值:zip、elf或bin;zip和elf支持代码签名,hap 应用包对应zip,二进制工具对应elf,默认zip;可选项 + ├── -inFile # 输入的原始文件,hap应用、elf或bin文件,必填项 ├── -signAlg # 签名算法,必填项,包括SHA256withECDSA / SHA384withECDSA ├── -keystoreFile # 密钥库文件,localSign模式时为必填项,JKS或P12格式 ├── -keystorePwd # 密钥库口令,可选项 ├── -outFile # 输出签名后的包文件,必填项 + ├── -signCode # 是否启用代码签名,1表示开启代码签名,0表示关闭代码签名,默认为1。可选项 -10.hap应用包文件验签 +10.hap应用包和二进制工具文件验签 - verify-app : hap应用包文件验签 - ├── -inFile # 已签名的应用包文件,zip格式或bin格式,必填项 + verify-app : hap应用包和二进制工具文件验签 + ├── -inFile # 已签名的文件,hap应用、elf或bin文件,必填项 ├── -outCertChain # 签名的证书链文件,必填项 ├── -outProfile # 应用包中的profile文件,必填项 - - + ├── -inForm # 输入的原始文件的格式,枚举值:zip、elf或bin;zip和elf支持代码签名,hap 应用包对应zip,二进制工具对应elf,默认zip;可选项 #### 相关仓 diff --git a/autosign/autosign.py b/autosign/autosign.py index f7efd367a2814dfc90a973602d36e9a24b002966..a287a1a02d67d56443694e5860e743a3e1d21614 100644 --- a/autosign/autosign.py +++ b/autosign/autosign.py @@ -55,7 +55,7 @@ templates = { }, 'sign-app': { 'required': ['keyAlias', 'signAlg', 'mode', 'appCertFile', 'profileFile', 'inFile', 'keystoreFile', 'outFile'], - 'others': ['keyPwd', 'keystorePwd'] + 'others': ['keyPwd', 'keystorePwd', 'inForm', 'signCode'] }, } @@ -161,6 +161,13 @@ def do_sign_hap(jar): run_with_engine(sign_hap_engine_config, jar) +def do_sign_elf(jar): + sign_elf_engine_config = { + 'sign.app': 'sign-app' + } + run_with_engine(sign_elf_engine_config, jar) + + def do_generate(jar): cert_engine_config = { 'app.keypair': 'generate-keypair', @@ -234,7 +241,7 @@ def process_cmd(): exit(0) action = args[1] - if action not in ['createRootAndSubCert', 'createAppCertAndProfile', 'signHap']: + if action not in ['createRootAndSubCert', 'createAppCertAndProfile', 'signHap', 'signElf']: print("Not support cmd") print_help() exit(1) @@ -295,3 +302,7 @@ if __name__ == '__main__': load_config('signHap.config') jar_file = process_jar() do_sign_hap(jar_file) + elif act == 'signElf': + load_config('signElf.config') + jar_file = process_jar() + do_sign_elf(jar_file) diff --git a/autosign/signElf.config b/autosign/signElf.config new file mode 100644 index 0000000000000000000000000000000000000000..0e4177717f9742899b73728c189568af6bac5692 --- /dev/null +++ b/autosign/signElf.config @@ -0,0 +1,39 @@ +// Base configuration +// +// Location of signtool.jar + +config.signtool=../hapsigntool/hap_sign_tool/build/libs/hap-sign-tool.jar + +// All products would be put into folder +config.targetDir=result + +// Common configuration, will be overwrite by detail config +common.keystoreFile=OpenHarmony.p12 +common.keystorePwd=123456 +common.signAlg=SHA256withECDSA +common.mode=localSign + +// You must change this instead of using default +common.keyPwd=123456 +common.issuerKeyPwd=123456 + +// keypair of app signature +app.keypair.keyAlias=oh-app1-key-v1 +app.keypair.keyPwd=123456 + +// App signature cert +cert.app.outFile=app1.pem + +// Sign profile +sign.profile.outFile=app1-profile.p7b + +// Sign app +sign.app.inFile=elf-unsigned +sign.app.outFile=elf-signed +sign.app.inForm=elf + + +// Default config. Do not change it +sign.app.keyAlias=$app.keypair.keyAlias +sign.app.appCertFile=$cert.app.outFile +sign.app.profileFile=$sign.profile.outFile diff --git a/autosign/sign_elf.bat b/autosign/sign_elf.bat new file mode 100644 index 0000000000000000000000000000000000000000..59da7022fc8e4839ee6384e5c745ff2237fe35c0 --- /dev/null +++ b/autosign/sign_elf.bat @@ -0,0 +1,17 @@ +@rem Copyright (c) 2023-2023 Huawei Device Co., Ltd. +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem http://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. + +@echo off +python autosign.py signElf +pause +@echo on \ No newline at end of file diff --git a/autosign/sign_elf.sh b/autosign/sign_elf.sh new file mode 100644 index 0000000000000000000000000000000000000000..a2fb86e3a3433c68fc770a4e71a259899495dc3b --- /dev/null +++ b/autosign/sign_elf.sh @@ -0,0 +1,15 @@ +#!/bin/bash +# Copyright (c) 2023-2023 Huawei Device Co., Ltd. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +python3 autosign.py signElf \ No newline at end of file diff --git a/dist/hap-sign-tool.jar b/dist/hap-sign-tool.jar index 64788d1efc95c1c4ee190391a7558a2dc1cd1688..9fd7314cf416959f882c4eff52e098574bd73113 100644 Binary files a/dist/hap-sign-tool.jar and b/dist/hap-sign-tool.jar differ diff --git a/hapsigntool/hap_sign_tool/build.gradle b/hapsigntool/hap_sign_tool/build.gradle index 3993a1ee6b97cd6739382523c5938d35f133d100..4f7b67b6ca60c51d60c2080ed5801e1e7f0a1a83 100644 --- a/hapsigntool/hap_sign_tool/build.gradle +++ b/hapsigntool/hap_sign_tool/build.gradle @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021-2022 Huawei Device Co., Ltd. + * Copyright (c) 2021-2023 Huawei Device Co., Ltd. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at @@ -26,11 +26,11 @@ repositories { } dependencies { - implementation 'org.bouncycastle:bcpkix-jdk15on:1.69' - testImplementation 'org.junit.jupiter:junit-jupiter-api:5.7.2' - testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.7.2' - implementation group: 'org.apache.logging.log4j', name: 'log4j-api', version: '2.17.1' - implementation group: 'org.apache.logging.log4j', name: 'log4j-core', version: '2.17.1' + implementation 'org.bouncycastle:bcpkix-jdk18on:1.76' + testImplementation 'org.junit.jupiter:junit-jupiter-api:5.9.2' + testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.9.2' + implementation group: 'org.apache.logging.log4j', name: 'log4j-api', version: '2.18.0' + implementation group: 'org.apache.logging.log4j', name: 'log4j-core', version: '2.18.0' implementation 'com.google.code.gson:gson:2.9.0' implementation project(':hap_sign_tool_lib') } diff --git a/hapsigntool/hap_sign_tool/src/main/java/com/ohos/hapsigntool/HapSignTool.java b/hapsigntool/hap_sign_tool/src/main/java/com/ohos/hapsigntool/HapSignTool.java index 7c6b83c5302f80955c5b82893c33c8cc6d3eb3a1..c1533d647235d123d6c009ece71560dcf1751aa1 100644 --- a/hapsigntool/hap_sign_tool/src/main/java/com/ohos/hapsigntool/HapSignTool.java +++ b/hapsigntool/hap_sign_tool/src/main/java/com/ohos/hapsigntool/HapSignTool.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021-2022 Huawei Device Co., Ltd. + * Copyright (c) 2021-2023 Huawei Device Co., Ltd. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at @@ -15,7 +15,6 @@ package com.ohos.hapsigntool; - import com.ohos.hapsigntool.api.ServiceApi; import com.ohos.hapsigntool.api.SignToolServiceImpl; import com.ohos.hapsigntool.api.model.Options; @@ -27,9 +26,13 @@ import com.ohos.hapsigntoolcmd.CmdUtil; import com.ohos.hapsigntoolcmd.CmdUtil.Method; import com.ohos.hapsigntoolcmd.HelpDocument; import com.ohos.hapsigntoolcmd.Params; + import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import java.util.ArrayList; +import java.util.List; + /** * HapSignTool. * @@ -66,6 +69,14 @@ public final class HapSignTool { */ private static final String NOT_SIGNED = "0"; + private static List informList = new ArrayList<>(); + + static { + informList.add("bin"); + informList.add("elf"); + informList.add("zip"); + } + private HapSignTool() { } @@ -76,13 +87,14 @@ public final class HapSignTool { */ public static void main(String[] args) { try { - boolean result = processCmd(args); - if (!result) { + boolean isSuccess = processCmd(args); + if (!isSuccess) { System.exit(1); } } catch (CustomException exception) { LOGGER.debug(exception.getMessage(), exception); LOGGER.error(exception.getMessage()); + System.exit(1); } } @@ -105,67 +117,65 @@ public final class HapSignTool { Params params = CmdUtil.convert2Params(args); LOGGER.debug(params.toString()); LOGGER.info("Start {}", params.getMethod()); - boolean result; - result = dispatchParams(params, api); - if (result) { + boolean isSuccess = dispatchParams(params, api); + if (isSuccess) { LOGGER.info(String.format("%s %s", params.getMethod(), "success")); } else { LOGGER.info(String.format("%s %s", params.getMethod(), "failed")); } - return result; + return isSuccess; } return true; } private static boolean callGenerators(Params params, ServiceApi api) { - boolean result = false; + boolean isSuccess = false; switch (params.getMethod()) { case Method.GENERATE_APP_CERT: - result = runAppCert(params.getOptions(), api); + isSuccess = runAppCert(params.getOptions(), api); break; case Method.GENERATE_CA: - result = runCa(params.getOptions(), api); + isSuccess = runCa(params.getOptions(), api); break; case Method.GENERATE_CERT: - result = runCert(params.getOptions(), api); + isSuccess = runCert(params.getOptions(), api); break; case Method.GENERATE_CSR: - result = runCsr(params.getOptions(), api); + isSuccess = runCsr(params.getOptions(), api); break; case Method.GENERATE_KEYPAIR: - result = runKeypair(params.getOptions(), api); + isSuccess = runKeypair(params.getOptions(), api); break; case Method.GENERATE_PROFILE_CERT: - result = runProfileCert(params.getOptions(), api); + isSuccess = runProfileCert(params.getOptions(), api); break; default: CustomException.throwException(ERROR.COMMAND_ERROR, "Unsupported cmd"); break; } - return result; + return isSuccess; } private static boolean dispatchParams(Params params, ServiceApi api) { - boolean result; + boolean isSuccess; switch (params.getMethod()) { case Method.SIGN_APP: - result = runSignApp(params.getOptions(), api); + isSuccess = runSignApp(params.getOptions(), api); break; case Method.SIGN_PROFILE: - result = runSignProfile(params.getOptions(), api); + isSuccess = runSignProfile(params.getOptions(), api); break; case Method.VERIFY_APP: - result = runVerifyApp(params.getOptions(), api); + isSuccess = runVerifyApp(params.getOptions(), api); break; case Method.VERIFY_PROFILE: - result = runVerifyProfile(params.getOptions(), api); + isSuccess = runVerifyProfile(params.getOptions(), api); break; default: - result = callGenerators(params, api); + isSuccess = callGenerators(params, api); break; } - - return result; + return isSuccess; } private static void checkEndCertArguments(Options params) { @@ -185,7 +195,7 @@ public final class HapSignTool { String keyStoreFile = params.getString(Options.KEY_STORE_FILE); FileUtils.validFileType(keyStoreFile, "p12", "jks"); - if (params.containsKey(Options.ISSUER_KEY_STORE_FILE)){ + if (params.containsKey(Options.ISSUER_KEY_STORE_FILE)) { String issuerKeyStoreFile = params.getString(Options.ISSUER_KEY_STORE_FILE); FileUtils.validFileType(issuerKeyStoreFile, "p12", "jks"); } @@ -225,7 +235,7 @@ public final class HapSignTool { String signAlg = params.getString(Options.SIGN_ALG); CmdUtil.judgeSignAlgType(signAlg); FileUtils.validFileType(params.getString(Options.KEY_STORE_FILE), "p12", "jks"); - if (params.containsKey(Options.ISSUER_KEY_STORE_FILE)){ + if (params.containsKey(Options.ISSUER_KEY_STORE_FILE)) { String issuerKeyStoreFile = params.getString(Options.ISSUER_KEY_STORE_FILE); FileUtils.validFileType(issuerKeyStoreFile, "p12", "jks"); } @@ -262,7 +272,7 @@ public final class HapSignTool { } private static boolean runSignApp(Options params, ServiceApi api) { - params.required(Options.MODE, Options.IN_FILE, Options.OUT_FILE, Options.PROFILE_FILE, Options.SIGN_ALG); + params.required(Options.MODE, Options.IN_FILE, Options.OUT_FILE, Options.SIGN_ALG); String mode = params.getString(Options.MODE); if (!LOCAL_SIGN.equalsIgnoreCase(mode) && !REMOTE_SIGN.equalsIgnoreCase(mode) @@ -276,7 +286,7 @@ public final class HapSignTool { } checkProfile(params); String inForm = params.getString(Options.IN_FORM); - if (!StringUtils.isEmpty(inForm) && !"zip".equalsIgnoreCase(inForm) && !"bin".equalsIgnoreCase(inForm)) { + if (!StringUtils.isEmpty(inForm) && !informList.contains(inForm)) { CustomException.throwException(ERROR.NOT_SUPPORT_ERROR, "inForm params is incorrect"); } String signAlg = params.getString(Options.SIGN_ALG); @@ -286,8 +296,13 @@ public final class HapSignTool { } private static void checkProfile(Options params) { + String inForm = params.getString(Options.IN_FORM); String profileFile = params.getString(Options.PROFILE_FILE); - String profileSigned = params.getString(Options.PROFILE_SIGNED,SIGNED); + String profileSigned = params.getString(Options.PROFILE_SIGNED, SIGNED); + + if ("elf".equalsIgnoreCase(inForm) && StringUtils.isEmpty(profileFile)) { + return; + } if (!SIGNED.equals(profileSigned) && !NOT_SIGNED.equals(profileSigned)) { CustomException.throwException(ERROR.NOT_SUPPORT_ERROR, "profileSigned params is incorrect"); } @@ -320,7 +335,10 @@ public final class HapSignTool { private static boolean runVerifyApp(Options params, ServiceApi api) { params.required(Options.IN_FILE, Options.OUT_CERT_CHAIN, Options.OUT_PROFILE); - FileUtils.validFileType(params.getString(Options.IN_FILE), "hap", "bin"); + String inForm = params.getString(Options.IN_FORM, "zip"); + if (!informList.contains(inForm)) { + CustomException.throwException(ERROR.NOT_SUPPORT_ERROR, "inForm params must is " + informList); + } FileUtils.validFileType(params.getString(Options.OUT_CERT_CHAIN), "cer"); FileUtils.validFileType(params.getString(Options.OUT_PROFILE), "p7b"); return api.verifyHap(params); diff --git a/hapsigntool/hap_sign_tool/src/main/java/com/ohos/hapsigntoolcmd/CmdUtil.java b/hapsigntool/hap_sign_tool/src/main/java/com/ohos/hapsigntoolcmd/CmdUtil.java index 240bb22df059d291fa290026920ebb2249941acc..e2f05c1884dbbfb47acc1e863479d8569eb3475e 100644 --- a/hapsigntool/hap_sign_tool/src/main/java/com/ohos/hapsigntoolcmd/CmdUtil.java +++ b/hapsigntool/hap_sign_tool/src/main/java/com/ohos/hapsigntoolcmd/CmdUtil.java @@ -64,7 +64,7 @@ public final class CmdUtil { for (int i = 1; i < args.length; i++) { String value = args[i]; if (StringUtils.isEmpty(value)) { - continue; + CustomException.throwException(ERROR.COMMAND_ERROR, "param value could not be empty"); } if (readKey) { // prepare key diff --git a/hapsigntool/hap_sign_tool/src/main/resources/help.txt b/hapsigntool/hap_sign_tool/src/main/resources/help.txt index 4f293806a83eb915ac8de69a36c3b109391a92ec..5b96762531a4f2e847bddeb847763ab2b8836cdc 100644 --- a/hapsigntool/hap_sign_tool/src/main/resources/help.txt +++ b/hapsigntool/hap_sign_tool/src/main/resources/help.txt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021-2022 Huawei Device Co., Ltd. + * Copyright (c) 2021-2023 Huawei Device Co., Ltd. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at @@ -169,13 +169,13 @@ USAGE: [options] -appCertFile : application signature certificate file, required fields on localSign mode, optional fields on remoteSign mode; -profileFile : signed Provision Profile file, p7b format, required fields; -profileSigned : indicates whether the profile file has a signature.The options are as follows: 1:yes; 0:no; default value:1. optional fields; - -inFile : input original application package file, hap or bin format, required fields; + -inFile : input original application package file, .hap, .bin, and .elf format, required fields; -signAlg : signature algorithm, required fields, including SHA256withRSA/SHA384withRSA/SHA256withECDSA/SHA384withECDSA; -keystoreFile : keystore file, if signature mode is localSign, required fields on localSign mode, JKS or P12 format; -keystorePwd : keystore password, optional fields on localSign mode; -outFile : output the signed Provision Profile file, required fields; -extCfgFile : Extend Profile, optional fields; - -inForm : enter the format of the original file, the format is .zip or .bin; + -inForm : Enter the format of the original file. The supported file formats include .zip, .bin, and .elf.; -compatibleVersion : min compatible api version for running app, required fields while input original application package file format is hap; -signServer : remote signer plugin, required fields on remoteSign mode; -signerPlugin : remote sign service url, required fields on remoteSign mode; @@ -183,15 +183,17 @@ USAGE: [options] -username : user account for online auth, required fields on remoteSign mode with account auth mode; -userPwd : user password for online auth, required fields on remoteSign mode with account auth mode; -ext : extend parameters for remote signer plugin, optional fields; + -signCode : Whether the HAP file is signed code, The value 1 means enable sign code, and value 0 means disable sign code. The default value is 1. It is optional. EXAMPLE: - sign-app -mode localSign -keyAlias "oh-app1-key-v1" -appCertFile "D:\OH\app-release-cert.cer" -profileFile "D:\OH\signed-profile.p7b" -inFile "D:\OH\app1-unsigned.hap" -signAlg SHA256withECDSA -keystoreFile "D:\OH\app-keypair.jks" -keystorePwd ****** -outFile "D:\OH\app1-signed.hap -compatibleVersion 8" + sign-app -mode localSign -keyAlias "oh-app1-key-v1" -appCertFile "D:\OH\app-release-cert.cer" -profileFile "D:\OH\signed-profile.p7b" -inFile "D:\OH\app1-unsigned.hap" -signAlg SHA256withECDSA -keystoreFile "D:\OH\app-keypair.jks" -keystorePwd ****** -outFile "D:\OH\app1-signed.hap -compatibleVersion 8" -signCode "1" verify-app [options]: -inFile : signed application package file, hap or bin format, required fields; -outCertChain : signed certificate chain file, required fields; -outProfile : profile file in application package, required fields; -extCfgFile : Extend Profile, optional fields; + -inForm : Enter the format of the original file. The supported file formats include .zip, .bin, and .elf.; EXAMPLE: verify-app -inFile "D:\OH\app1-signed.hap" -outCertChain "outCertChain.cer" -outProfile "outprofile.p7b" diff --git a/hapsigntool/hap_sign_tool/src/test/java/com/ohos/hapsigntoolcmd/CmdUnitTest.java b/hapsigntool/hap_sign_tool/src/test/java/com/ohos/hapsigntoolcmd/CmdUnitTest.java index c57e4b04c5d378462cdf556edd6ccb9b4c26207b..19c97907ce9c6696dbd883a27999600f303a391b 100644 --- a/hapsigntool/hap_sign_tool/src/test/java/com/ohos/hapsigntoolcmd/CmdUnitTest.java +++ b/hapsigntool/hap_sign_tool/src/test/java/com/ohos/hapsigntoolcmd/CmdUnitTest.java @@ -15,9 +15,15 @@ package com.ohos.hapsigntoolcmd; +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + import com.ohos.hapsigntool.HapSignTool; import com.ohos.hapsigntool.key.KeyPairTools; import com.ohos.hapsigntool.utils.FileUtils; +import com.ohos.hapsigntool.zip.Zip; + import org.junit.jupiter.api.MethodOrderer; import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Test; @@ -26,14 +32,19 @@ import org.junit.platform.commons.logging.Logger; import org.junit.platform.commons.logging.LoggerFactory; import java.io.File; +import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; +import java.math.BigInteger; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; - -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; +import java.util.ArrayList; +import java.util.List; +import java.util.Random; +import java.util.zip.CRC32; +import java.util.zip.ZipEntry; +import java.util.zip.ZipOutputStream; /** * CmdUnitTest. @@ -192,6 +203,26 @@ public class CmdUnitTest { */ public static final String CMD_VALIDITY = "-validity"; + /** + * Command line parameter appCertFile. + */ + public static final String CMD_APP_CERT_FILE = "-appCertFile"; + + /** + * Command line parameter appCertFile. + */ + public static final String CMD_PROFILE_FILE = "-profileFile"; + + /** + * Command line parameter appCertFile. + */ + public static final String CMD_OUT_CERT_CHAIN = "-outCertChain"; + + /** + * Command line parameter appCertFile. + */ + public static final String CMD_OUT_PROFILE = "-outProfile"; + /** * Command line parameter false. */ @@ -724,6 +755,187 @@ public class CmdUnitTest { } } + /** + * test sign and verify hap file include multi lib + * + * @throws IOException error + */ + @Order(11) + @Test + public void testCmdMultiHap() throws IOException { + File dir = new File("test"); + dir.mkdir(); + for (FileType abcFile : FileType.values()) { + for (FileType soFile : FileType.values()) { + for (FileType anFile : FileType.values()) { + File unsignedHap = generateHapFile(abcFile, soFile, anFile, FileType.FILE_NOT_EXISTED); + signAndVerifyHap(unsignedHap.getAbsolutePath()); + + unsignedHap = generateHapFile(abcFile, soFile, anFile, FileType.FILE_UNCOMPRESSED); + signAndVerifyHap(unsignedHap.getAbsolutePath()); + + unsignedHap = generateHapFile(abcFile, soFile, anFile, FileType.FILE_COMPRESSED); + signAndVerifyHap(unsignedHap.getAbsolutePath()); + } + } + } + for (File file : dir.listFiles()) { + file.delete(); + } + dir.delete(); + } + + private File generateHapFile(FileType abc, FileType so, FileType an, FileType otherFile) throws IOException { + File unsignedHap = new File("test\\unsigned-" + new BigInteger(Long.SIZE, new Random()) + ".hap"); + try (ZipOutputStream out = new ZipOutputStream(new FileOutputStream(unsignedHap))) { + if (FileType.FILE_UNCOMPRESSED.equals(abc)) { + fillZipEntryFile(true, ".abc", out); + } else if (FileType.FILE_COMPRESSED.equals(abc)) { + fillZipEntryFile(false, ".abc", out); + } else { + fillZipEntryFile(true, "", out); + } + if (FileType.FILE_UNCOMPRESSED.equals(so)) { + fillZipEntryFile(true, ".so", out); + fillZipEntryFile(true, ".so.111", out); + fillZipEntryFile(true, ".so.111.111", out); + fillZipEntryFile(true, ".so.111.111.111", out); + } else if (FileType.FILE_COMPRESSED.equals(so)) { + fillZipEntryFile(false, ".so", out); + fillZipEntryFile(false, ".so.111", out); + fillZipEntryFile(false, ".so.111.111", out); + fillZipEntryFile(false, ".so.111.111.111", out); + } else { + fillZipEntryFile(true, "", out); + } + if (FileType.FILE_UNCOMPRESSED.equals(an)) { + fillZipEntryFile(true, ".an", out); + } else if (FileType.FILE_COMPRESSED.equals(an)) { + fillZipEntryFile(false, ".an", out); + } else { + fillZipEntryFile(true, "", out); + } + if (FileType.FILE_UNCOMPRESSED.equals(otherFile)) { + fillZipEntryFile(true, ".json", out); + } else if (FileType.FILE_COMPRESSED.equals(otherFile)) { + fillZipEntryFile(false, ".json", out); + } else { + fillZipEntryFile(true, "", out); + } + } + return unsignedHap; + } + + private void fillZipEntryFile(boolean uncompressed, String suffix, ZipOutputStream out) throws IOException { + String fileName = new BigInteger(Long.SIZE, new Random()).toString() + suffix; + if (suffix.startsWith(".so")) { + fileName = "libs\\" + fileName; + } + if (suffix.startsWith(".an")) { + fileName = "an\\" + fileName; + } + ZipEntry zipEntry = new ZipEntry(fileName); + byte[] bytes = generateChunkBytes(); + if (uncompressed) { + zipEntry.setMethod(ZipEntry.STORED); + zipEntry.setSize(bytes.length); + CRC32 crc32 = new CRC32(); + crc32.reset(); + crc32.update(bytes, 0, bytes.length); + zipEntry.setCrc(crc32.getValue()); + } else { + zipEntry.setMethod(ZipEntry.DEFLATED); + } + out.putNextEntry(zipEntry); + out.write(bytes); + out.closeEntry(); + } + + private byte[] generateChunkBytes() { + Random random = new Random(); + int size = Math.max(4096, random.nextInt(1024 * 1024 * 2)); + byte[] bytes = new byte[size]; + random.nextBytes(bytes); + return bytes; + } + + private void signAndVerifyHap(String unsignedHap) throws IOException { + String signedHap = File.createTempFile("signed-", ".hap", new File("test")).getAbsolutePath(); + boolean result = HapSignTool.processCmd(new String[] { + CmdUtil.Method.SIGN_APP, CMD_MODE, CMD_LOCAL_SIGN, CMD_KEY_ALIAS, CMD_OH_PROFILE_KEY_V1, CMD_KEY_RIGHTS, + CMD_RIGHTS_123456, CMD_APP_CERT_FILE, CMD_PROFILE_RELEASE_CERT_PATH, CMD_PROFILE_FILE, + CMD_SIGN_PROFILE_PATH, CMD_SIGN_ALG, CMD_SHA_256_WITH_ECDSA, CMD_KEY_STORE_FILE, CMD_KEY_PROFILE_STORE_PATH, + CMD_KEY_STORE_RIGHTS, CMD_RIGHTS_123456, CMD_IN_FILE, unsignedHap, CMD_OUT_FILE, signedHap + }); + assertTrue(result); + + result = HapSignTool.processCmd(new String[] { + CmdUtil.Method.VERIFY_APP, CMD_IN_FILE, signedHap, CMD_OUT_CERT_CHAIN, "test\\1.cer", CMD_OUT_PROFILE, + "test\\1.p7b" + }); + assertTrue(result); + } + + /** + * Test Method: isRunnableFile() + */ + @Test + public void testIsRunnableFile() { + List correctName = new ArrayList<>(); + correctName.add("中文.so"); + correctName.add("srtjdwrtj.an"); + correctName.add("srtjdwrtj.abc"); + correctName.add("srtjdwrtj.so"); + correctName.add("srtjdwrtj.so.1"); + correctName.add("srtjdwrtj.so.1.1"); + correctName.add("srtjdwrtj.so.1.1.1"); + correctName.add("srtjdwrtj.so.111.111.1111"); + correctName.add("libs\\srtjdwrtj.so.111.111.1111"); + correctName.add("中文.so.111.111.1111"); + for (String name : correctName) { + assertTrue(FileUtils.isRunnableFile(name)); + } + + List incorrectName = new ArrayList<>(); + incorrectName.add("srtjdwrtj.so.111.111.1111.54645"); + incorrectName.add("srtjdwrtjso.111.111.11111"); + incorrectName.add("libs\\srtjdwrtj.so.111.%%%.1111"); + incorrectName.add("srtjdwrtj.so.abc.111.111.1111"); + incorrectName.add("srtjdwrtj.so.111.111.json"); + incorrectName.add("srtjdwrtj.abc.json"); + incorrectName.add("srtjdwrtj.an.json"); + incorrectName.add("中文.so.111.111.json"); + for (String name : incorrectName) { + assertFalse(FileUtils.isRunnableFile(name)); + } + } + + + /** + * Test Method: testByteToZip() + * + * @throws IOException read file exception + */ + @Test + public void testByteToZip() throws IOException { + File dir = new File("test"); + dir.mkdir(); + for (int i = 0; i < 10; i++) { + File file = generateHapFile(FileType.FILE_UNCOMPRESSED, FileType.FILE_UNCOMPRESSED, + FileType.FILE_UNCOMPRESSED, FileType.FILE_UNCOMPRESSED); + Zip zip = new Zip(file); + String outFileName = "test/testOut.hap"; + zip.toFile(outFileName); + File outFile = new File(outFileName); + byte[] bytes = FileUtils.readFile(file); + byte[] outBytes = FileUtils.readFile(outFile); + assertArrayEquals(outBytes, bytes); + + deleteFile(file.getCanonicalPath()); + deleteFile(outFileName); + } + } + private boolean generateAppRootCa() { boolean result = HapSignTool.processCmd(new String[]{ CmdUtil.Method.GENERATE_CA, @@ -811,4 +1023,13 @@ public class CmdUnitTest { Files.delete(path); } } + + /** + * Enumerated value of file type in zip. + */ + public enum FileType { + FILE_NOT_EXISTED, + FILE_UNCOMPRESSED, + FILE_COMPRESSED; + } } diff --git a/hapsigntool/hap_sign_tool_lib/build.gradle b/hapsigntool/hap_sign_tool_lib/build.gradle index a6f0951dfbf3b5e801953294adbe99b1480e05fb..cbe1ca8338fe3920824c6496ae78609ca15890ec 100644 --- a/hapsigntool/hap_sign_tool_lib/build.gradle +++ b/hapsigntool/hap_sign_tool_lib/build.gradle @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021-2022 Huawei Device Co., Ltd. + * Copyright (c) 2021-2023 Huawei Device Co., Ltd. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at @@ -26,11 +26,11 @@ repositories { } dependencies { - testImplementation 'org.junit.jupiter:junit-jupiter-api:5.7.2' - testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.7.2' + testImplementation 'org.junit.jupiter:junit-jupiter-api:5.9.2' + testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.9.2' implementation group: 'org.apache.logging.log4j', name: 'log4j-api', version: '2.18.0' implementation group: 'org.apache.logging.log4j', name: 'log4j-core', version: '2.18.0' - implementation 'org.bouncycastle:bcpkix-jdk15on:1.69' + implementation 'org.bouncycastle:bcpkix-jdk18on:1.76' implementation 'com.google.code.gson:gson:2.9.0' } diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/api/SignToolServiceImpl.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/api/SignToolServiceImpl.java index 8fcf5c14a34db9e47080762b3d179201d86d63b5..331ba8b3d36963b4e6367a7cbc0f176ddcb79209 100644 --- a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/api/SignToolServiceImpl.java +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/api/SignToolServiceImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021-2022 Huawei Device Co., Ltd. + * Copyright (c) 2021-2023 Huawei Device Co., Ltd. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at @@ -15,22 +15,25 @@ package com.ohos.hapsigntool.api; - import com.ohos.hapsigntool.api.model.Options; import com.ohos.hapsigntool.cert.CertTools; import com.ohos.hapsigntool.error.CustomException; import com.ohos.hapsigntool.error.ERROR; +import com.ohos.hapsigntool.error.VerifyException; import com.ohos.hapsigntool.hap.provider.LocalJKSSignProvider; import com.ohos.hapsigntool.hap.provider.RemoteSignProvider; import com.ohos.hapsigntool.hap.provider.SignProvider; +import com.ohos.hapsigntool.hap.verify.VerifyElf; import com.ohos.hapsigntool.hap.verify.VerifyHap; import com.ohos.hapsigntool.profile.ProfileSignTool; import com.ohos.hapsigntool.profile.VerifyHelper; import com.ohos.hapsigntool.profile.model.VerificationResult; import com.ohos.hapsigntool.utils.CertUtils; import com.ohos.hapsigntool.utils.FileUtils; +import com.ohos.hapsigntool.utils.ParamConstants; import com.ohos.hapsigntool.utils.ProfileUtils; import com.ohos.hapsigntool.utils.StringUtils; + import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.bouncycastle.jce.provider.BouncyCastleProvider; @@ -43,10 +46,8 @@ import java.security.Security; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; import java.util.Arrays; +import java.util.List; /** * Main entry of lib. @@ -54,10 +55,6 @@ import java.util.Arrays; * @since 2021/12/28 */ public class SignToolServiceImpl implements ServiceApi { - static { - Security.addProvider(new BouncyCastleProvider()); - } - /** * App signing Capabilty Bytes. */ @@ -73,6 +70,10 @@ public class SignToolServiceImpl implements ServiceApi { */ private static final Logger logger = LogManager.getLogger(ServiceApi.class); + static { + Security.addProvider(new BouncyCastleProvider()); + } + /** * Generate keyStore. * @@ -122,7 +123,7 @@ public class SignToolServiceImpl implements ServiceApi { adapter.errorIfNotExist(issuerAlias); KeyPair subjectKeyPair = adapter.getAliasKey(false); - if (options.containsKey(Options.ISSUER_KEY_STORE_FILE)){ + if (options.containsKey(Options.ISSUER_KEY_STORE_FILE)) { adapter.setKeyStoreHelper(null); adapter.setIssuerKeyStoreFile(true); } @@ -143,28 +144,28 @@ public class SignToolServiceImpl implements ServiceApi { @Override public boolean generateCA(Options options) { LocalizationAdapter adapter = new LocalizationAdapter(options); - boolean genRootCA = StringUtils.isEmpty(options.getString(Options.ISSUER_KEY_ALIAS)); + boolean isEmpty = StringUtils.isEmpty(options.getString(Options.ISSUER_KEY_ALIAS)); KeyPair subKey = adapter.getAliasKey(true); KeyPair rootKey; String ksFile = options.getString(Options.KEY_STORE_FILE); String iksFile = options.getString(Options.ISSUER_KEY_STORE_FILE); - if (genRootCA) { - if (!StringUtils.isEmpty(iksFile) && !ksFile.equals(iksFile)){ + if (isEmpty) { + if (!StringUtils.isEmpty(iksFile) && !ksFile.equals(iksFile)) { CustomException.throwException(ERROR.WRITE_FILE_ERROR, - String.format("Parameter '%s' and parameter '%s' are inconsistent",ksFile,iksFile)); + String.format("Parameter '%s' and parameter '%s' are inconsistent", ksFile, iksFile)); } - if (options.containsKey(Options.ISSUER_KEY_STORE_RIGHTS) ){ + if (options.containsKey(Options.ISSUER_KEY_STORE_RIGHTS)) { boolean isEqual = Arrays.equals(options.getChars(Options.KEY_STORE_RIGHTS), options.getChars(Options.ISSUER_KEY_STORE_RIGHTS)); - if (!isEqual){ + if (!isEqual) { CustomException.throwException(ERROR.WRITE_FILE_ERROR, String.format("Parameter '%s' and parameter '%s' are inconsistent", - Options.KEY_STORE_RIGHTS,Options.ISSUER_KEY_STORE_RIGHTS)); + Options.KEY_STORE_RIGHTS, Options.ISSUER_KEY_STORE_RIGHTS)); } } rootKey = subKey; } else { - if (options.containsKey(Options.ISSUER_KEY_STORE_FILE)){ + if (options.containsKey(Options.ISSUER_KEY_STORE_FILE)) { FileUtils.validFileType(options.getString(Options.ISSUER_KEY_STORE_FILE), "p12", "jks"); adapter.setKeyStoreHelper(null); adapter.setIssuerKeyStoreFile(true); @@ -175,7 +176,7 @@ public class SignToolServiceImpl implements ServiceApi { byte[] csr = CertTools.generateCsr(subKey, adapter.getSignAlg(), adapter.getSubject()); X509Certificate cert; - if (genRootCA) { + if (isEmpty) { cert = CertTools.generateRootCaCert(rootKey, csr, adapter); } else { cert = CertTools.generateSubCert(rootKey, csr, adapter); @@ -193,7 +194,7 @@ public class SignToolServiceImpl implements ServiceApi { public boolean generateAppCert(Options options) { LocalizationAdapter adapter = new LocalizationAdapter(options); KeyPair keyPair = adapter.getAliasKey(false); - if (options.containsKey(Options.ISSUER_KEY_STORE_FILE)){ + if (options.containsKey(Options.ISSUER_KEY_STORE_FILE)) { adapter.setKeyStoreHelper(null); adapter.setIssuerKeyStoreFile(true); } @@ -202,15 +203,7 @@ public class SignToolServiceImpl implements ServiceApi { byte[] csr = CertTools.generateCsr(keyPair, adapter.getSignAlg(), adapter.getSubject()); X509Certificate cert = CertTools.generateEndCert(issueKeyPair, csr, adapter, APP_SIGNING_CAPABILITY); - if (adapter.isOutFormChain()) { - List certificates = new ArrayList<>(); - certificates.add(cert); - certificates.add(adapter.getSubCaCertFile()); - certificates.add(adapter.getCaCertFile()); - return outputCertChain(certificates, adapter.getOutFile()); - } else { - return outputCert(cert, adapter.getOutFile()); - } + return getOutputCert(adapter, cert); } /** @@ -223,7 +216,7 @@ public class SignToolServiceImpl implements ServiceApi { public boolean generateProfileCert(Options options) { LocalizationAdapter adapter = new LocalizationAdapter(options); KeyPair keyPair = adapter.getAliasKey(false); - if (options.containsKey(Options.ISSUER_KEY_STORE_FILE)){ + if (options.containsKey(Options.ISSUER_KEY_STORE_FILE)) { adapter.setKeyStoreHelper(null); adapter.setIssuerKeyStoreFile(true); } @@ -232,6 +225,10 @@ public class SignToolServiceImpl implements ServiceApi { byte[] csr = CertTools.generateCsr(keyPair, adapter.getSignAlg(), adapter.getSubject()); X509Certificate cert = CertTools.generateEndCert(issueKeyPair, csr, adapter, PROFILE_SIGNING_CAPABILITY); + return getOutputCert(adapter, cert); + } + + private boolean getOutputCert(LocalizationAdapter adapter, X509Certificate cert) { if (adapter.isOutFormChain()) { List certificates = new ArrayList<>(); certificates.add(cert); @@ -251,19 +248,19 @@ public class SignToolServiceImpl implements ServiceApi { */ @Override public boolean signProfile(Options options) { - boolean result; + boolean isSuccess; try { LocalizationAdapter adapter = new LocalizationAdapter(options); byte[] provisionContent = ProfileUtils.getProvisionContent(new File(adapter.getInFile())); byte[] p7b = ProfileSignTool.generateP7b(adapter, provisionContent); FileUtils.write(p7b, new File(adapter.getOutFile())); - result = true; + isSuccess = true; } catch (IOException exception) { logger.debug(exception.getMessage(), exception); logger.error(exception.getMessage()); - result = false; + isSuccess = false; } - return result; + return isSuccess; } /** @@ -274,23 +271,26 @@ public class SignToolServiceImpl implements ServiceApi { */ @Override public boolean verifyProfile(Options options) { - boolean result; + boolean isSign; try { LocalizationAdapter adapter = new LocalizationAdapter(options); VerifyHelper verifyHelper = new VerifyHelper(); byte[] p7b = FileUtils.readFile(new File(adapter.getInFile())); VerificationResult verificationResult = verifyHelper.verify(p7b); - result = verificationResult.isVerifiedPassed(); - if (!result) { + isSign = verificationResult.isVerifiedPassed(); + if (!isSign) { logger.error(verificationResult.getMessage()); } outputString(FileUtils.GSON_PRETTY_PRINT.toJson(verificationResult), adapter.getOutFile()); } catch (IOException exception) { logger.debug(exception.getMessage(), exception); logger.error(exception.getMessage()); - result = false; + isSign = false; + } catch (VerifyException e) { + CustomException.throwException(ERROR.VERIFY_ERROR, "Verify Profile Failed! " + e.getMessage()); + isSign = false; } - return result; + return isSign; } /** @@ -306,7 +306,7 @@ public class SignToolServiceImpl implements ServiceApi { SignProvider signProvider; if ("localSign".equalsIgnoreCase(mode)) { signProvider = new LocalJKSSignProvider(); - } else if ("remoteSign".equalsIgnoreCase(mode)){ + } else if ("remoteSign".equalsIgnoreCase(mode)) { signProvider = new RemoteSignProvider(); } else { logger.info("Resign mode. But not implement yet"); @@ -317,6 +317,8 @@ public class SignToolServiceImpl implements ServiceApi { String inForm = options.getString(Options.IN_FORM, "zip"); if ("zip".equalsIgnoreCase(inForm)) { return signProvider.sign(options); + } else if ("elf".equalsIgnoreCase(inForm)) { + return signProvider.signElf(options); } else { return signProvider.signBin(options); } @@ -324,8 +326,13 @@ public class SignToolServiceImpl implements ServiceApi { @Override public boolean verifyHap(Options options) { - VerifyHap hapVerify = new VerifyHap(); - return hapVerify.verify(options); + if ("zip".equals(options.getOrDefault(ParamConstants.PARAM_IN_FORM, "zip"))) { + VerifyHap hapVerify = new VerifyHap(); + return hapVerify.verify(options); + } else { + VerifyElf verifyElf = new VerifyElf(); + return verifyElf.verify(options); + } } /** diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/datastructure/CodeSignBlock.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/datastructure/CodeSignBlock.java new file mode 100644 index 0000000000000000000000000000000000000000..40679e46f2debe76c55a50d57980aaad9cd708d4 --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/datastructure/CodeSignBlock.java @@ -0,0 +1,270 @@ +/* + * Copyright (c) 2023-2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.ohos.hapsigntool.codesigning.datastructure; + +import com.ohos.hapsigntool.codesigning.sign.CodeSigning; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; + +/** + * code sign block is a chunk of bytes attached to hap package or file. + * It consists of two headers: + * 1) code sign block header + * 2) segment header + * three segments: + * 1) fs-verity info segment + * 2) hap info segment + * 3) so info segment + * one zero padding area in order to align merkle tree raw bytes + * 1) zero padding + * and one area storing merkle tree bytes: + * 1) merkle tree raw bytes + *

+ * After signing a hap, call toByteArray() method to generate a block of bytes. + * + * @since 2023/09/08 + */ +public class CodeSignBlock { + /** + * page size in bytes + */ + public static final long PAGE_SIZE_4K = 4096L; + + /** + * Segment header count, including fs-verity info, hap info, so info segment + */ + public static final int SEGMENT_HEADER_COUNT = 3; + + private CodeSignBlockHeader codeSignBlockHeader; + + private final List segmentHeaderList; + + private FsVerityInfoSegment fsVerityInfoSegment; + + private HapInfoSegment hapInfoSegment; + + private NativeLibInfoSegment nativeLibInfoSegment; + + private byte[] zeroPadding; + + private final Map merkleTreeMap; + + public CodeSignBlock() { + this.codeSignBlockHeader = new CodeSignBlockHeader.Builder().build(); + this.segmentHeaderList = new ArrayList<>(); + this.fsVerityInfoSegment = new FsVerityInfoSegment(); + this.hapInfoSegment = new HapInfoSegment(); + this.nativeLibInfoSegment = new NativeLibInfoSegment.Builder().build(); + this.merkleTreeMap = new HashMap<>(); + } + + /** + * Add one merkle tree into merkleTreeMap + * + * @param key file name + * @param merkleTree merkle tree raw bytes + */ + public void addOneMerkleTree(String key, byte[] merkleTree) { + if (merkleTree == null) { + this.merkleTreeMap.put(key, new byte[0]); + } else { + this.merkleTreeMap.put(key, merkleTree); + } + } + + /** + * Get one merkle tree from merkleTreeMap by file name + * + * @param key file name + * @return merkle tree bytes + */ + public byte[] getOneMerkleTreeByFileName(String key) { + return this.merkleTreeMap.get(key); + } + + /** + * set code sign block flag + */ + public void setCodeSignBlockFlag() { + int flags = CodeSignBlockHeader.FLAG_MERKLE_TREE_INLINED; + if (this.nativeLibInfoSegment.getSectionNum() != 0) { + flags += CodeSignBlockHeader.FLAG_NATIVE_LIB_INCLUDED; + } + this.codeSignBlockHeader.setFlags(flags); + } + + /** + * set segmentNum defined in code sign block header, equals length of segmentHeaderList + */ + public void setSegmentNum() { + this.codeSignBlockHeader.setSegmentNum(segmentHeaderList.size()); + } + + /** + * add one segment to segmentHeaderList + * + * @param sh segment header + */ + public void addToSegmentList(SegmentHeader sh) { + this.segmentHeaderList.add(sh); + } + + public List getSegmentHeaderList() { + return segmentHeaderList; + } + + /** + * set segment header list + */ + public void setSegmentHeaders() { + // fs-verity info segment + segmentHeaderList.add(new SegmentHeader(SegmentHeader.CSB_FSVERITY_INFO_SEG, this.fsVerityInfoSegment.size())); + // hap info segment + segmentHeaderList.add(new SegmentHeader(SegmentHeader.CSB_HAP_META_SEG, this.hapInfoSegment.size())); + // native lib info segment + segmentHeaderList.add( + new SegmentHeader(SegmentHeader.CSB_NATIVE_LIB_INFO_SEG, this.nativeLibInfoSegment.size())); + } + + public CodeSignBlockHeader getCodeSignBlockHeader() { + return codeSignBlockHeader; + } + + public void setCodeSignBlockHeader(CodeSignBlockHeader csbHeader) { + this.codeSignBlockHeader = csbHeader; + } + + public void setFsVerityInfoSegment(FsVerityInfoSegment fsVeritySeg) { + this.fsVerityInfoSegment = fsVeritySeg; + } + + public FsVerityInfoSegment getFsVerityInfoSegment() { + return fsVerityInfoSegment; + } + + public HapInfoSegment getHapInfoSegment() { + return hapInfoSegment; + } + + public void setHapInfoSegment(HapInfoSegment hapSeg) { + this.hapInfoSegment = hapSeg; + } + + public NativeLibInfoSegment getSoInfoSegment() { + return nativeLibInfoSegment; + } + + public void setSoInfoSegment(NativeLibInfoSegment soSeg) { + this.nativeLibInfoSegment = soSeg; + } + + /** + * Convert code sign block object to a newly created byte array + * + * @return Byte array representation of a CodeSignBlock object + */ + public byte[] toByteArray() { + ByteBuffer bf = ByteBuffer.allocate(this.codeSignBlockHeader.getBlockSize()).order(ByteOrder.LITTLE_ENDIAN); + bf.put(this.codeSignBlockHeader.toByteArray()); + for (SegmentHeader sh : this.segmentHeaderList) { + bf.put(sh.toByteArray()); + } + bf.put(this.zeroPadding); + // Hap merkle tree + if (this.hapInfoSegment.getSignInfo().getExtensionByType(MerkleTreeExtension.MERKLE_TREE_INLINED) != null) { + bf.put(merkleTreeMap.get("Hap")); + } + bf.put(this.fsVerityInfoSegment.toByteArray()); + bf.put(this.hapInfoSegment.toByteArray()); + bf.put(this.nativeLibInfoSegment.toByteArray()); + return bf.array(); + } + + /** + * SegmentOffset is the position of each segment defined in segmentHeaderList, + * based on the start position of code sign block + */ + public void computeSegmentOffset() { + // 1) the first segment is placed after merkle tree + int segmentOffset = CodeSignBlockHeader.size() + + this.segmentHeaderList.size() * SegmentHeader.SEGMENT_HEADER_LENGTH + this.zeroPadding.length + + this.getOneMerkleTreeByFileName(CodeSigning.HAP_SIGNATURE_ENTRY_NAME).length; + for (SegmentHeader sh : segmentHeaderList) { + sh.setSegmentOffset(segmentOffset); + segmentOffset += sh.getSegmentSize(); + } + } + + /** + * Compute the offset to store merkle tree raw bytes based on file start + * + * @param codeSignBlockOffset offset to store code sign block based on file start + * @return offset to store merkle tree based on the file start, it includes the codeSignBlockOffset + */ + public long computeMerkleTreeOffset(long codeSignBlockOffset) { + long sizeWithoutMerkleTree = CodeSignBlockHeader.size() + + SEGMENT_HEADER_COUNT * SegmentHeader.SEGMENT_HEADER_LENGTH; + // add code sign block offset while computing align position for merkle tree + long residual = (codeSignBlockOffset + sizeWithoutMerkleTree) % PAGE_SIZE_4K; + if (residual == 0) { + this.zeroPadding = new byte[0]; + } else { + this.zeroPadding = new byte[(int) (PAGE_SIZE_4K - residual)]; + } + return codeSignBlockOffset + sizeWithoutMerkleTree + zeroPadding.length; + } + + /** + * Convert CodeSignBlock to bytes + * + * @param fsvTreeOffset merkle tree offset + * @return byte array representing the code sign block + */ + public byte[] generateCodeSignBlockByte(long fsvTreeOffset) { + // 1) compute overall block size without merkle tree + long csbSize = CodeSignBlockHeader.size() + + (long) this.segmentHeaderList.size() * SegmentHeader.SEGMENT_HEADER_LENGTH + this.zeroPadding.length + + this.getOneMerkleTreeByFileName(CodeSigning.HAP_SIGNATURE_ENTRY_NAME).length + + this.fsVerityInfoSegment.size() + this.hapInfoSegment.size() + this.nativeLibInfoSegment.size(); + Extension ext = this.hapInfoSegment.getSignInfo().getExtensionByType(MerkleTreeExtension.MERKLE_TREE_INLINED); + if (ext instanceof MerkleTreeExtension) { + MerkleTreeExtension merkleTreeExtension = (MerkleTreeExtension) ext; + merkleTreeExtension.setMerkleTreeOffset(fsvTreeOffset); + } + this.codeSignBlockHeader.setBlockSize(csbSize); + // 2) generate byte array of complete code sign block + return toByteArray(); + } + + /** + * Return a string representation of the object + * + * @return string representation of the object + */ + public String toString() { + return String.format(Locale.ROOT, + "CodeSignBlockHeader[%s], SegmentHeaders[%s], FsVeritySeg[%s], HapInfoSeg[%s], SoInfoSeg[%s]", + this.codeSignBlockHeader, Arrays.toString(this.segmentHeaderList.toArray()), this.fsVerityInfoSegment, + this.hapInfoSegment, this.nativeLibInfoSegment); + } +} diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/datastructure/CodeSignBlockHeader.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/datastructure/CodeSignBlockHeader.java new file mode 100644 index 0000000000000000000000000000000000000000..8a1572fa42747aa2bb2c68e92ca570a3e6ec5fb7 --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/datastructure/CodeSignBlockHeader.java @@ -0,0 +1,240 @@ +/* + * Copyright (c) 2023-2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.ohos.hapsigntool.codesigning.datastructure; + +import com.ohos.hapsigntool.codesigning.exception.VerifyCodeSignException; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.Locale; + +/** + * Code sign block header + *

+ * Structure: + * 1) u64 magic: magic number + * 2) u32 version: sign tool version + * 3) u32 blockSize: size of code sign block + * 4) u32 segmentNum: number of segments, i.e. FsVerityInfoSegment, HapInfoSegment, SoInfoSegment + * 5) u32 flags + * 6) u8[8] reserved: for reservation + *

+ * The Size of Code sign Block header if fixed, getBlockLength() method returns the size. + * + * @since 2023/09/08 + */ +public class CodeSignBlockHeader { + /** + * Flag indicating that merkle tree is included in code sign block + */ + public static final int FLAG_MERKLE_TREE_INLINED = 0x1; + + /** + * Flag indicating that native lib is included in code sign block + */ + public static final int FLAG_NATIVE_LIB_INCLUDED = 0x2; + + // code signing version + private static final int CODE_SIGNING_VERSION = 1; + + // byte size of magic number + private static final byte MAGIC_BYTE_ARRAY_LENGTH = Long.BYTES; + + // lower 8 bytes of MD5 result of string "hap code sign block" (E046 C8C6 5389 FCCD) + private static final long MAGIC_NUM = ((0xE046C8C6L << 32) + 0x5389FCCDL); + + // size of byte[8] reserved + private static final byte RESERVED_BYTE_ARRAY_LENGTH = 8; + + // At all times three segment are always included in code sign block, update this if new segments are created. + private static final int SEGMENT_NUM = 3; + + private long magic; + + private int version; + + private int blockSize; + + private int segmentNum; + + private int flags; + + private byte[] reserved; + + /** + * Construct of CodeSignBlockHeader + * + * @param builder builder + */ + private CodeSignBlockHeader(Builder builder) { + this.magic = builder.magic; + this.version = builder.version; + this.blockSize = builder.blockSize; + this.segmentNum = builder.segmentNum; + this.flags = builder.flags; + this.reserved = builder.reserved; + } + + public void setSegmentNum(int num) { + this.segmentNum = num; + } + + public int getSegmentNum() { + return segmentNum; + } + + public void setBlockSize(long size) { + this.blockSize = (int) size; + } + + public int getBlockSize() { + return blockSize; + } + + public void setFlags(int flags) { + this.flags = flags; + } + + /** + * Converts code sign block headers to a newly created byte array + * + * @return Byte array representation of a code sign block header + */ + public byte[] toByteArray() { + ByteBuffer bf = ByteBuffer.allocate(size()).order(ByteOrder.LITTLE_ENDIAN); + bf.putLong(magic); + bf.putInt(version); + bf.putInt(blockSize); + bf.putInt(segmentNum); + bf.putInt(flags); + bf.put(reserved); + return bf.array(); + } + + /** + * Init the CodeSignBlockHeader by a byte array + * + * @param bytes Byte array representation of a CodeSignBlockHeader object + * @return a newly created CodeSignBlockHeader object + * @throws VerifyCodeSignException parse result invalid + */ + public static CodeSignBlockHeader fromByteArray(byte[] bytes) throws VerifyCodeSignException { + if (bytes.length != size()) { + throw new VerifyCodeSignException("Invalid size of CodeSignBlockHeader"); + } + ByteBuffer bf = ByteBuffer.allocate(bytes.length).order(ByteOrder.LITTLE_ENDIAN); + bf.put(bytes); + // after put, rewind is mandatory before get + bf.rewind(); + long inMagic = bf.getLong(); + if (inMagic != MAGIC_NUM) { + throw new VerifyCodeSignException("Invalid magic num of CodeSignBlockHeader"); + } + int inVersion = bf.getInt(); + if (inVersion != CODE_SIGNING_VERSION) { + throw new VerifyCodeSignException("Invalid version of CodeSignBlockHeader"); + } + int inBlockSize = bf.getInt(); + int inSegmentNum = bf.getInt(); + if (inSegmentNum != SEGMENT_NUM) { + throw new VerifyCodeSignException("Invalid segmentNum of CodeSignBlockHeader"); + } + int inFlags = bf.getInt(); + if (inFlags < 0 || inFlags > (FLAG_MERKLE_TREE_INLINED + FLAG_NATIVE_LIB_INCLUDED)) { + throw new VerifyCodeSignException("Invalid flags of CodeSignBlockHeader"); + } + byte[] inReserved = new byte[RESERVED_BYTE_ARRAY_LENGTH]; + bf.get(inReserved); + return new Builder().setMagic(inMagic).setVersion(inVersion).setBlockSize(inBlockSize) + .setSegmentNum(inSegmentNum).setFlags(inFlags).setReserved(inReserved).build(); + } + + /** + * Return the byte size of code sign block header + * + * @return byte size of code sign block header + */ + public static int size() { + return MAGIC_BYTE_ARRAY_LENGTH + Integer.BYTES * 4 + RESERVED_BYTE_ARRAY_LENGTH; + } + + /** + * Return a string representation of the object + * + * @return string representation of the object + */ + public String toString() { + return String.format(Locale.ROOT, + "CodeSignBlockHeader{magic: %d, version: %d, blockSize: %d, segmentNum: %d, flags: %d}", this.magic, + this.version, this.blockSize, this.segmentNum, this.flags); + } + + /** + * Builder of CodeSignBlockHeader class + */ + public static class Builder { + private long magic = MAGIC_NUM; + + private int version = CODE_SIGNING_VERSION; + + private int blockSize; + + private int segmentNum; + + private int flags; + + private byte[] reserved = new byte[RESERVED_BYTE_ARRAY_LENGTH]; + + public Builder setMagic(long magic) { + this.magic = magic; + return this; + } + + public Builder setVersion(int version) { + this.version = version; + return this; + } + + public Builder setBlockSize(int blockSize) { + this.blockSize = blockSize; + return this; + } + + public Builder setSegmentNum(int segmentNum) { + this.segmentNum = segmentNum; + return this; + } + + public Builder setFlags(int flags) { + this.flags = flags; + return this; + } + + public Builder setReserved(byte[] reserved) { + this.reserved = reserved; + return this; + } + + /** + * Create a CodeSignBlockHeader object + * + * @return a CodeSignBlockHeader object + */ + public CodeSignBlockHeader build() { + return new CodeSignBlockHeader(this); + } + } +} diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/datastructure/ElfSignBlock.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/datastructure/ElfSignBlock.java new file mode 100644 index 0000000000000000000000000000000000000000..959f9ed87a911beca73a3e3caf6c108590f68535 --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/datastructure/ElfSignBlock.java @@ -0,0 +1,199 @@ +/* + * Copyright (c) 2023-2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.ohos.hapsigntool.codesigning.datastructure; + +import com.ohos.hapsigntool.codesigning.exception.FsVerityDigestException; +import com.ohos.hapsigntool.codesigning.exception.VerifyCodeSignException; +import com.ohos.hapsigntool.codesigning.fsverity.FsVerityDescriptor; +import com.ohos.hapsigntool.codesigning.fsverity.FsVerityDescriptorWithSign; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +/** + * elf sign block is a chunk of bytes attached to elf file. + * 1) u32 type: 0x2 merkle tree + * 2) u32 length: merkle tree with padding size + * 3) u8[] merkle tree data + * 4) u32 type: 0x1 fsverity descriptor + * 5) u32 length: fsverity descriptor size + * 6) u8 version: fs-verity version + * 7) u8 hashAlgorithm: hash algorithm to use for the Merkle tree + * 8) u8 log2BlockSize: log2 of size of data and tree blocks + * 9) u8 saltSize: byte size of salt + * 10) u32 signSize: byte size of signature + * 11) u64 dataSize: byte size of data being signed + * 12) u8[64] rootHash: merkle tree root hash + * 13) u8[32] salt: salt used in signing + * 14) u32 flags + * 15) u32 reserved + * 16) u64 treeOffset: merkle tree offset + * 17) u8[127] reserved + * 18) u8 csVersion: code sign version + * 19) u8[] signature: signature after signing the data in byte array representation + * + * @since 2023/09/08 + */ +public class ElfSignBlock { + /** + * page size in bytes + */ + public static final int PAGE_SIZE_4K = 4096; + + /** + * Type of MerkleTree + */ + public static final int MERKLE_TREE_INLINED = 0x2; + + private int type = MERKLE_TREE_INLINED; + + private int treeLength; + + private byte[] merkleTreeWithPadding; + + private FsVerityDescriptorWithSign descriptorWithSign; + + /** + * Constructor of ElfSignBlock + * + * @param paddingSize padding size before merkle tree + * @param merkleTreeData merkle tree data + * @param descriptorWithSign FsVerityDescriptorWithSign object + */ + public ElfSignBlock(int paddingSize, byte[] merkleTreeData, FsVerityDescriptorWithSign descriptorWithSign) { + byte[] inMerkleTreeData = new byte[0]; + if (merkleTreeData != null) { + inMerkleTreeData = merkleTreeData; + } + this.treeLength = paddingSize + inMerkleTreeData.length; + this.merkleTreeWithPadding = new byte[this.treeLength]; + System.arraycopy(inMerkleTreeData, 0, merkleTreeWithPadding, paddingSize, inMerkleTreeData.length); + this.descriptorWithSign = descriptorWithSign; + } + + private ElfSignBlock(int type, int treeLength, byte[] merkleTreeWithPadding, + FsVerityDescriptorWithSign descriptorWithSign) { + this.type = type; + this.treeLength = treeLength; + this.merkleTreeWithPadding = merkleTreeWithPadding; + this.descriptorWithSign = descriptorWithSign; + } + + /** + * Return the byte size of code sign block + * + * @return byte size of code sign block + */ + public int size() { + return Integer.BYTES * 2 + merkleTreeWithPadding.length + descriptorWithSign.size(); + } + + /** + * return padding length by the sign block offset + * + * @param signBlockOffset sign block offset based on the start of file + * @return merkle tree raw bytes offset based on the start of file + */ + public static int computeMerkleTreePaddingLength(long signBlockOffset) { + return (int) (PAGE_SIZE_4K - (signBlockOffset + Integer.BYTES * 2) % PAGE_SIZE_4K) % PAGE_SIZE_4K; + } + + public byte[] getMerkleTreeWithPadding() { + return merkleTreeWithPadding; + } + + /** + * get DataSize + * + * @return DataSize + */ + public long getDataSize() { + return descriptorWithSign.getFsVerityDescriptor().getFileSize(); + } + + /** + * get TreeOffset + * + * @return TreeOffset + */ + public long getTreeOffset() { + return descriptorWithSign.getFsVerityDescriptor().getMerkleTreeOffset(); + } + + /** + * get Signature + * + * @return Signature + */ + public byte[] getSignature() { + return descriptorWithSign.getSignature(); + } + + /** + * Return a string representation of the object + * + * @return string representation of the object + * @throws FsVerityDigestException if error + */ + public byte[] toByteArray() throws FsVerityDigestException { + ByteBuffer bf = ByteBuffer.allocate(size()).order(ByteOrder.LITTLE_ENDIAN); + bf.putInt(type); + bf.putInt(merkleTreeWithPadding.length); + bf.put(merkleTreeWithPadding); + bf.put(descriptorWithSign.toByteArray()); + return bf.array(); + } + + /** + * Init the ElfSignBlock by a byte array + * + * @param bytes Byte array representation of a ElfSignBlock object + * @return a newly created ElfSignBlock object + * @throws VerifyCodeSignException parse result invalid + */ + public static ElfSignBlock fromByteArray(byte[] bytes) throws VerifyCodeSignException { + ByteBuffer bf = ByteBuffer.allocate(bytes.length).order(ByteOrder.LITTLE_ENDIAN); + bf.put(bytes); + // after put, rewind is mandatory before get + bf.rewind(); + int inTreeType = bf.getInt(); + if (MERKLE_TREE_INLINED != inTreeType) { + throw new VerifyCodeSignException("Invalid merkle tree type of ElfSignBlock"); + } + int inTreeLength = bf.getInt(); + byte[] treeWithPadding = new byte[inTreeLength]; + bf.get(treeWithPadding); + int inFsdType = bf.getInt(); + if (FsVerityDescriptor.FS_VERITY_DESCRIPTOR_TYPE != inFsdType) { + throw new VerifyCodeSignException("Invalid fs-verify descriptor type of ElfSignBlock"); + } + int inFsdLength = bf.getInt(); + if (bytes.length != Integer.BYTES * 2 + inTreeLength + Integer.BYTES * 2 + inFsdLength) { + throw new VerifyCodeSignException("Invalid fs-verify descriptor with signature length of ElfSignBlock"); + } + byte[] fsdArray = new byte[FsVerityDescriptor.DESCRIPTOR_SIZE]; + bf.get(fsdArray); + FsVerityDescriptor fsd = FsVerityDescriptor.fromByteArray(fsdArray); + if (inFsdLength != fsd.getSignSize() + FsVerityDescriptor.DESCRIPTOR_SIZE) { + throw new VerifyCodeSignException("Invalid sign size of ElfSignBlock"); + } + byte[] inSignature = new byte[inFsdLength - FsVerityDescriptor.DESCRIPTOR_SIZE]; + bf.get(inSignature); + FsVerityDescriptorWithSign fsVerityDescriptorWithSign = new FsVerityDescriptorWithSign(inFsdType, inFsdLength, + fsd, inSignature); + return new ElfSignBlock(inTreeType, inTreeLength, treeWithPadding, fsVerityDescriptorWithSign); + } +} diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/datastructure/Extension.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/datastructure/Extension.java new file mode 100644 index 0000000000000000000000000000000000000000..2d23c548812465c6419fea07d40299268c9121cb --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/datastructure/Extension.java @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2023-2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.ohos.hapsigntool.codesigning.datastructure; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.Locale; + +/** + * Extension is an optional field in relative to SignInfo. + * It is the base class for all types of extensions, i.e. MerkleTreeExtension. + *

+ * Structure: + * u32 type: Indicates the type of extension + *

+ * u32 size: byte size of extension data + * + * @since 2023/09/08 + */ +public class Extension { + /** + * Byte size of Extension base class. + */ + public static final int EXTENSION_HEADER_SIZE = 8; + + private final int type; + + private final int size; + + public Extension(int type, int size) { + this.type = type; + this.size = size; + } + + public int size() { + return EXTENSION_HEADER_SIZE; + } + + public boolean isType(int type) { + return this.type == type; + } + + /** + * Converts Extension to a newly created byte array + * + * @return Byte array representation of Extension + */ + public byte[] toByteArray() { + ByteBuffer bf = ByteBuffer.allocate(EXTENSION_HEADER_SIZE).order(ByteOrder.LITTLE_ENDIAN); + bf.putInt(this.type); + bf.putInt(this.size); + return bf.array(); + } + + /** + * Return a string representation of the object + * + * @return string representation of the object + */ + public String toString() { + return String.format(Locale.ROOT, "Extension: type[%d], size[%d]", this.type, this.size); + } +} diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/datastructure/FsVerityInfoSegment.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/datastructure/FsVerityInfoSegment.java new file mode 100644 index 0000000000000000000000000000000000000000..117431cf1dbb769c4f3757d3f0c955824e574cfd --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/datastructure/FsVerityInfoSegment.java @@ -0,0 +1,155 @@ +/* + * Copyright (c) 2023-2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.ohos.hapsigntool.codesigning.datastructure; + +import com.ohos.hapsigntool.codesigning.exception.VerifyCodeSignException; +import com.ohos.hapsigntool.codesigning.fsverity.FsVerityDescriptor; +import com.ohos.hapsigntool.codesigning.fsverity.FsVerityGenerator; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.Locale; + +/** + * Fs-verity info segment contains information of fs-verity protection + * More information of fs-verity can be found here + *

+ * Structure + *

+ * 1) u32 magic: magic number + *

+ * 2) u8 version: fs-verity version + *

+ * 3) u8 hashAlgorithm: hash algorithm to use for the Merkle tree + *

+ * 4) u8 log2BlockSize: log2 of size of data and tree blocks + *

+ * 5) u8[] reserved: for reservation + * + * @since 2023/09/08 + */ +public class FsVerityInfoSegment { + /** + * fs-verity info segment size in bytes + */ + public static final int FS_VERITY_INFO_SEGMENT_SIZE = 64; + + // lower 4 bytes of the MD5 result of string "fs-verity info segment" (1E38 31AB) + private static final int MAGIC = (0x1E38 << 16) + (0x31AB); + + private static final int RESERVED_BYTE_ARRAY_LENGTH = 57; + + private int magic = MAGIC; + + private byte hashAlgorithm; + + private byte version; + + private byte log2BlockSize; + + private byte[] reserved = new byte[RESERVED_BYTE_ARRAY_LENGTH]; + + /** + * Default constructor + */ + public FsVerityInfoSegment() { + } + + public FsVerityInfoSegment(byte version, byte hashAlgorithm, byte log2BlockSize) { + this(MAGIC, version, hashAlgorithm, log2BlockSize, new byte[RESERVED_BYTE_ARRAY_LENGTH]); + } + + /** + * Constructor of FsVerityInfoSegment + * + * @param magic magic num + * @param version version of fs-verity + * @param hashAlgorithm hash algorithm to use for the Merkle tree + * @param log2BlockSize log2 of size of data and tree blocks + * @param reserved for reservation + */ + public FsVerityInfoSegment(int magic, byte version, byte hashAlgorithm, byte log2BlockSize, byte[] reserved) { + this.magic = magic; + this.version = version; + this.hashAlgorithm = hashAlgorithm; + this.log2BlockSize = log2BlockSize; + this.reserved = reserved; + } + + public int size() { + return FS_VERITY_INFO_SEGMENT_SIZE; + } + + /** + * Converts FsVerityInfoSegment to a newly created byte array + * + * @return Byte array representation of FsVerityInfoSegment + */ + public byte[] toByteArray() { + ByteBuffer bf = ByteBuffer.allocate(FS_VERITY_INFO_SEGMENT_SIZE).order(ByteOrder.LITTLE_ENDIAN); + bf.putInt(this.magic); + bf.put(version); + bf.put(hashAlgorithm); + bf.put(log2BlockSize); + bf.put(reserved); + return bf.array(); + } + + /** + * Init the FsVerityInfoSegment by a byte array + * + * @param bytes Byte array representation of a FsVerityInfoSegment object + * @return a newly created FsVerityInfoSegment object + * @throws VerifyCodeSignException parsing result invalid + */ + public static FsVerityInfoSegment fromByteArray(byte[] bytes) throws VerifyCodeSignException { + if (bytes.length != FS_VERITY_INFO_SEGMENT_SIZE) { + throw new VerifyCodeSignException("Invalid size of FsVerityInfoSegment"); + } + ByteBuffer bf = ByteBuffer.allocate(bytes.length).order(ByteOrder.LITTLE_ENDIAN); + bf.put(bytes); + bf.rewind(); + int inMagic = bf.getInt(); + if (inMagic != MAGIC) { + throw new VerifyCodeSignException("Invalid magic number of FsVerityInfoSegment"); + } + byte inVersion = bf.get(); + if (inVersion != FsVerityDescriptor.VERSION) { + throw new VerifyCodeSignException("Invalid version of FsVerityInfoSegment"); + } + byte inHashAlgorithm = bf.get(); + if (inHashAlgorithm != FsVerityGenerator.getFsVerityHashAlgorithm()) { + throw new VerifyCodeSignException("Invalid hashAlgorithm of FsVerityInfoSegment"); + } + byte inLog2BlockSize = bf.get(); + if (inLog2BlockSize != FsVerityGenerator.getLog2BlockSize()) { + throw new VerifyCodeSignException("Invalid log2BlockSize of FsVerityInfoSegment"); + } + byte[] inReservedBytes = new byte[RESERVED_BYTE_ARRAY_LENGTH]; + bf.get(inReservedBytes); + return new FsVerityInfoSegment(inMagic, inVersion, inHashAlgorithm, inLog2BlockSize, inReservedBytes); + } + + /** + * Return a string representation of the object + * + * @return string representation of the object + */ + public String toString() { + return String.format(Locale.ROOT, "FsVerityInfoSeg: magic[%d], version[%d], hashAlg[%d], log2BlockSize[%d]", + this.magic, this.version, this.hashAlgorithm, this.log2BlockSize); + } +} diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/datastructure/HapInfoSegment.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/datastructure/HapInfoSegment.java new file mode 100644 index 0000000000000000000000000000000000000000..2697ea7af826915595ec20b941f6dec06f82f964 --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/datastructure/HapInfoSegment.java @@ -0,0 +1,142 @@ +/* + * Copyright (c) 2023-2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.ohos.hapsigntool.codesigning.datastructure; + +import com.ohos.hapsigntool.codesigning.exception.VerifyCodeSignException; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.Locale; + +/** + * Hap info segment + *

+ * Structure + *

+ * 1) u32 magic: magic number + *

+ * 2) SignInfo hapSignInfo: data struct of sign info, refer to SignInfo.java + * + * @since 2023/09/08 + */ +public class HapInfoSegment { + private static final int MAGIC_NUM_BYTES = 4; + + /** + * lower 4 bytes of the MD5 result of string "hap info segment" (C1B5 CC66) + */ + private static final int MAGIC_NUM = (0xC1B5 << 16) + 0xCC66; + + private int magic = MAGIC_NUM; + + private SignInfo hapSignInfo; + + /** + * Default constructor of HapInfoSegment + */ + public HapInfoSegment() { + this(MAGIC_NUM, new SignInfo(0, 0, 0, null, null)); + } + + /** + * Default constructor of HapInfoSegment + * + * @param magic magic number + * @param hapSignInfo hap sign info + */ + public HapInfoSegment(int magic, SignInfo hapSignInfo) { + this.magic = magic; + this.hapSignInfo = hapSignInfo; + } + + public void setSignInfo(SignInfo signInfo) { + this.hapSignInfo = signInfo; + } + + public SignInfo getSignInfo() { + return hapSignInfo; + } + + /** + * Returns byte size of HapInfoSegment + * + * @return byte size of HapInfoSegment + */ + public int size() { + return MAGIC_NUM_BYTES + hapSignInfo.size(); + } + + /** + * Converts HapInfoSegment to a newly created byte array + * + * @return Byte array representation of HapInfoSegment + */ + public byte[] toByteArray() { + byte[] hapSignInfoByteArray = this.hapSignInfo.toByteArray(); + // For now, only hap info segment has a merkle tree extension. So info segment + // has none extension. + ByteBuffer bf = ByteBuffer.allocate(MAGIC_NUM_BYTES + hapSignInfoByteArray.length) + .order(ByteOrder.LITTLE_ENDIAN); + bf.putInt(magic); + bf.put(hapSignInfoByteArray); + return bf.array(); + } + + /** + * Init the HapInfoSegment by a byte array + * + * @param bytes Byte array representation of a HapInfoSegment object + * @return a newly created HapInfoSegment object + * @throws VerifyCodeSignException parsing result invalid + */ + public static HapInfoSegment fromByteArray(byte[] bytes) throws VerifyCodeSignException { + ByteBuffer bf = ByteBuffer.allocate(bytes.length).order(ByteOrder.LITTLE_ENDIAN); + bf.put(bytes); + bf.rewind(); + int inMagic = bf.getInt(); + if (inMagic != MAGIC_NUM) { + throw new VerifyCodeSignException("Invalid magic number of HapInfoSegment"); + } + if (bytes.length <= MAGIC_NUM_BYTES) { + throw new VerifyCodeSignException("Invalid bytes size of HapInfoSegment"); + } + byte[] hapSignInfoByteArray = new byte[bytes.length - MAGIC_NUM_BYTES]; + bf.get(hapSignInfoByteArray); + SignInfo inHapSignInfo = SignInfo.fromByteArray(hapSignInfoByteArray); + if (inHapSignInfo.getDataSize() % CodeSignBlock.PAGE_SIZE_4K != 0) { + throw new VerifyCodeSignException( + String.format(Locale.ROOT, "Invalid dataSize number of HapInfoSegment, not a multiple of 4096: %d", + inHapSignInfo.getDataSize())); + } + if (inHapSignInfo.getExtensionNum() != SignInfo.MAX_EXTENSION_NUM) { + throw new VerifyCodeSignException("Invalid extensionNum of HapInfoSegment"); + } + if (inHapSignInfo.getExtensionByType(MerkleTreeExtension.MERKLE_TREE_INLINED) == null) { + throw new VerifyCodeSignException("No merkle tree extension is found in HapInfoSegment"); + } + return new HapInfoSegment(inMagic, inHapSignInfo); + } + + /** + * Return a string representation of the object + * + * @return string representation of the object + */ + public String toString() { + return String.format(Locale.ROOT, "HapInfoSegment: magic[%d], signInfo[%s]", this.magic, + this.hapSignInfo.toString()); + } +} diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/datastructure/MerkleTreeExtension.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/datastructure/MerkleTreeExtension.java new file mode 100644 index 0000000000000000000000000000000000000000..303d92edf13effedc56dec7605e4b2c3bd5aa908 --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/datastructure/MerkleTreeExtension.java @@ -0,0 +1,144 @@ +/* + * Copyright (c) 2023-2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.ohos.hapsigntool.codesigning.datastructure; + +import com.ohos.hapsigntool.codesigning.exception.VerifyCodeSignException; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.Arrays; +import java.util.Locale; + +/** + * Merkle tree extension is a type of Extension to store a merkle tree's information, i.e. size and root hash, ect. + *

+ * structure + *

+ * 1) u32 type + *

+ * 2) u64 merkleTreeSize: the size of merkle tree + *

+ * 3) u64 merkleTreeOffset: offset of the merkle tree by the start of the file. + *

+ * 4) u8[64] rootHash: merkle tree root hash + * + * @since 2023/09/08 + */ +public class MerkleTreeExtension extends Extension { + /** + * Type of MerkleTreeExtension + */ + public static final int MERKLE_TREE_INLINED = 0x1; + + /** + * Byte size of MerkleTreeExtension including merkleTreeSize, offset and root hash. + */ + public static final int MERKLE_TREE_EXTENSION_DATA_SIZE = 80; + + private static final int ROOT_HASH_SIZE = 64; + + private final long merkleTreeSize; + + private long merkleTreeOffset; + + private byte[] rootHash; + + /** + * Constructor for MerkleTreeExtension + * + * @param merkleTreeSize Byte array representation of merkle tree + * @param merkleTreeOffset merkle tree offset based on file start + * @param rootHash Root hash of the merkle tree + */ + public MerkleTreeExtension(long merkleTreeSize, long merkleTreeOffset, byte[] rootHash) { + super(MERKLE_TREE_INLINED, MERKLE_TREE_EXTENSION_DATA_SIZE); + this.merkleTreeSize = merkleTreeSize; + this.merkleTreeOffset = merkleTreeOffset; + if (rootHash == null) { + this.rootHash = new byte[ROOT_HASH_SIZE]; + } else { + this.rootHash = Arrays.copyOf(rootHash, ROOT_HASH_SIZE); + } + } + + @Override + public int size() { + return Extension.EXTENSION_HEADER_SIZE + MERKLE_TREE_EXTENSION_DATA_SIZE; + } + + public long getMerkleTreeSize() { + return merkleTreeSize; + } + + public long getMerkleTreeOffset() { + return merkleTreeOffset; + } + + public void setMerkleTreeOffset(long offset) { + this.merkleTreeOffset = offset; + } + + /** + * Converts MerkleTreeExtension to a newly created byte array + * + * @return Byte array representation of MerkleTreeExtension + */ + @Override + public byte[] toByteArray() { + ByteBuffer bf = ByteBuffer.allocate(Extension.EXTENSION_HEADER_SIZE + MERKLE_TREE_EXTENSION_DATA_SIZE) + .order(ByteOrder.LITTLE_ENDIAN); + bf.put(super.toByteArray()); + bf.putLong(this.merkleTreeSize); + bf.putLong(this.merkleTreeOffset); + bf.put(this.rootHash); + return bf.array(); + } + + /** + * Init the MerkleTreeExtension by a byte array + * + * @param bytes Byte array representation of a MerkleTreeExtension object + * @return a newly created MerkleTreeExtension object + * @throws VerifyCodeSignException parsing result invalid + */ + public static MerkleTreeExtension fromByteArray(byte[] bytes) throws VerifyCodeSignException { + ByteBuffer bf = ByteBuffer.allocate(bytes.length).order(ByteOrder.LITTLE_ENDIAN); + bf.put(bytes); + bf.rewind(); + long inMerkleTreeSize = bf.getLong(); + if (inMerkleTreeSize % CodeSignBlock.PAGE_SIZE_4K != 0) { + throw new VerifyCodeSignException("merkleTreeSize is not a multiple of 4096"); + } + long inMerkleTreeOffset = bf.getLong(); + if (inMerkleTreeOffset % CodeSignBlock.PAGE_SIZE_4K != 0) { + throw new VerifyCodeSignException("merkleTreeOffset is not a aligned to 4096"); + } + byte[] inRootHash = new byte[ROOT_HASH_SIZE]; + bf.get(inRootHash); + return new MerkleTreeExtension(inMerkleTreeSize, inMerkleTreeOffset, inRootHash); + } + + /** + * Return a string representation of the object + * + * @return string representation of the object + */ + @Override + public String toString() { + return String.format(Locale.ROOT, "MerkleTreeExtension: merkleTreeSize[%d], merkleTreeOffset[%d]," + + " rootHash[%s]", this.merkleTreeSize, this.merkleTreeOffset, Arrays.toString(this.rootHash)); + } +} diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/datastructure/NativeLibInfoSegment.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/datastructure/NativeLibInfoSegment.java new file mode 100644 index 0000000000000000000000000000000000000000..e7bcfc49d5db540dc34409db85df2d714e71584a --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/datastructure/NativeLibInfoSegment.java @@ -0,0 +1,334 @@ +/* + * Copyright (c) 2023-2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.ohos.hapsigntool.codesigning.datastructure; + +import com.ohos.hapsigntool.codesigning.exception.VerifyCodeSignException; +import com.ohos.hapsigntool.hap.entity.Pair; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Locale; + +/** + * SoInfoSegment consists of a header part: + *

+ * u32 magic: magic number + *

+ * u32 length: byte size of SoInfoSegment + *

+ * u32 section num: the amount of file being signed + *

+ * Followed by an area containing the offset and size of each file being signed with its signed info: + *

+ * u32 file name offset: position of file name based on the start of SoInfoSegment + *

+ * u32 file name size : byte size of file name string + *

+ * u32 sign info offset : position of signed info based on the start of SoInfoSegment + *

+ * u32 sign size: byte size of signed info + *

+ * Ends with the file name and signed info content: + *

+ * file name List : file name of each signed file + *

+ * sign info List : signed info of each file + *

+ * + * @since 2023/09/08 + */ +public class NativeLibInfoSegment { + private static final int MAGIC_LENGTH_SECNUM_BYTES = 12; + + private static final int SIGNED_FILE_POS_SIZE = 16; + + // lower 4 bytes of the MD5 result of string "native lib info segment" (0ED2 E720) + private static final int MAGIC_NUM = (0x0ED2 << 16) + 0xE720; + + private static final int ALIGNMENT_FOR_SIGNINFO = 4; + + private int magic; + + private int segmentSize; + + private int sectionNum; + + private List> soInfoList = new ArrayList<>(); + + private List signedFilePosList; + + private List fileNameList; + + private List signInfoList; + + private byte[] zeroPadding; + + private int fileNameListBlockSize; + + private int signInfoListBlockSize; + + /** + * Constructor for SoInfoSegment + * + * @param builder Builder + */ + private NativeLibInfoSegment(Builder builder) { + this.magic = builder.magic; + this.segmentSize = builder.segmentSize; + this.sectionNum = builder.sectionNum; + this.signedFilePosList = builder.signedFilePosList; + this.fileNameList = builder.fileNameList; + this.signInfoList = builder.signInfoList; + this.zeroPadding = builder.zeroPadding; + } + + /** + * set soInfoList, generate fileNameList and soInfoList + * + * @param soInfoList list of file and its signed info + */ + public void setSoInfoList(List> soInfoList) { + this.soInfoList = soInfoList; + // Once map is set, update length, sectionNum as well + this.sectionNum = soInfoList.size(); + // generate file name list and sign info list + generateList(); + } + + public int getSectionNum() { + return sectionNum; + } + + public List getFileNameList() { + return fileNameList; + } + + public List getSignInfoList() { + return signInfoList; + } + + // generate List based on current so + private void generateList() { + // empty all before generate list + this.fileNameList.clear(); + this.signInfoList.clear(); + this.signedFilePosList.clear(); + int fileNameOffset = 0; + int signInfoOffset = 0; + for (Pair soInfo : soInfoList) { + String fileName = soInfo.getFirst(); + SignInfo signInfo = soInfo.getSecond(); + int fileNameSizeInBytes = fileName.getBytes(StandardCharsets.UTF_8).length; + int signInfoSizeInBytes = signInfo.toByteArray().length; + this.fileNameList.add(fileName); + this.signInfoList.add(signInfo); + this.signedFilePosList.add( + new SignedFilePos(fileNameOffset, fileNameSizeInBytes, signInfoOffset, signInfoSizeInBytes)); + // increase fileNameOffset and signInfoOffset + fileNameOffset += fileNameSizeInBytes; + signInfoOffset += signInfoSizeInBytes; + } + this.fileNameListBlockSize = fileNameOffset; + this.signInfoListBlockSize = signInfoOffset; + // alignment for signInfo + this.zeroPadding = new byte[(ALIGNMENT_FOR_SIGNINFO - this.fileNameListBlockSize % ALIGNMENT_FOR_SIGNINFO) + % ALIGNMENT_FOR_SIGNINFO]; + // after fileNameList and signInfoList is generated, update segment size + this.segmentSize = this.size(); + // adjust file name and sign info offset base on segment start + int fileNameOffsetBase = MAGIC_LENGTH_SECNUM_BYTES + signedFilePosList.size() * SIGNED_FILE_POS_SIZE; + int signInfoOffsetBase = fileNameOffsetBase + this.fileNameListBlockSize; + for (SignedFilePos pos : this.signedFilePosList) { + pos.increaseFileNameOffset(fileNameOffsetBase); + pos.increaseSignInfoOffset(signInfoOffsetBase + this.zeroPadding.length); + } + } + + /** + * Returns byte size of SoInfoSegment + * + * @return byte size of SoInfoSegment + */ + public int size() { + int blockSize = MAGIC_LENGTH_SECNUM_BYTES; + blockSize += signedFilePosList.size() * SIGNED_FILE_POS_SIZE; + blockSize += this.fileNameListBlockSize + this.zeroPadding.length + this.signInfoListBlockSize; + return blockSize; + } + + /** + * Converts SoInfoSegment to a newly created byte array + * + * @return Byte array representation of SoInfoSegment + */ + public byte[] toByteArray() { + ByteBuffer bf = ByteBuffer.allocate(this.size()).order(ByteOrder.LITTLE_ENDIAN); + bf.putInt(magic); + bf.putInt(segmentSize); + bf.putInt(sectionNum); + for (SignedFilePos offsetAndSize : this.signedFilePosList) { + bf.putInt(offsetAndSize.getFileNameOffset()); + bf.putInt(offsetAndSize.getFileNameSize()); + bf.putInt(offsetAndSize.getSignInfoOffset()); + bf.putInt(offsetAndSize.getSignInfoSize()); + } + for (String fileName : fileNameList) { + bf.put(fileName.getBytes(StandardCharsets.UTF_8)); + } + bf.put(this.zeroPadding); + for (SignInfo signInfo : signInfoList) { + bf.put(signInfo.toByteArray()); + } + return bf.array(); + } + + /** + * Init the SoInfoSegment by a byte array + * + * @param bytes Byte array representation of a SoInfoSegment object + * @return a newly created NativeLibInfoSegment object + * @throws VerifyCodeSignException parsing result invalid + */ + public static NativeLibInfoSegment fromByteArray(byte[] bytes) throws VerifyCodeSignException { + ByteBuffer bf = ByteBuffer.allocate(bytes.length).order(ByteOrder.LITTLE_ENDIAN); + bf.put(bytes); + bf.rewind(); + int inMagic = bf.getInt(); + if (inMagic != MAGIC_NUM) { + throw new VerifyCodeSignException("Invalid magic number of NativeLibInfoSegment"); + } + int inSegmentSize = bf.getInt(); + if (inSegmentSize < 0) { + throw new VerifyCodeSignException("Invalid segmentSize of NativeLibInfoSegment"); + } + int inSectionNum = bf.getInt(); + if (inSectionNum < 0) { + throw new VerifyCodeSignException("Invalid sectionNum of NativeLibInfoSegment"); + } + List inSignedFilePosList = new ArrayList<>(); + for (int i = 0; i < inSectionNum; i++) { + byte[] entry = new byte[SIGNED_FILE_POS_SIZE]; + bf.get(entry); + inSignedFilePosList.add(SignedFilePos.fromByteArray(entry)); + } + // parse file name list + List inFileNameList = new ArrayList<>(); + int fileNameListSize = 0; + for (SignedFilePos pos : inSignedFilePosList) { + byte[] fileNameBuffer = new byte[pos.getFileNameSize()]; + fileNameListSize += pos.getFileNameSize(); + bf.get(fileNameBuffer); + inFileNameList.add(new String(fileNameBuffer, StandardCharsets.UTF_8)); + } + // parse zeroPadding + byte[] inZeroPadding = new byte[(ALIGNMENT_FOR_SIGNINFO - fileNameListSize % ALIGNMENT_FOR_SIGNINFO) + % ALIGNMENT_FOR_SIGNINFO]; + bf.get(inZeroPadding); + // parse sign info list + List inSignInfoList = new ArrayList<>(); + for (SignedFilePos pos : inSignedFilePosList) { + if (pos.getSignInfoOffset() % ALIGNMENT_FOR_SIGNINFO != 0) { + throw new VerifyCodeSignException("SignInfo not aligned in NativeLibInfoSegment"); + } + byte[] signInfoBuffer = new byte[pos.getSignInfoSize()]; + bf.get(signInfoBuffer); + inSignInfoList.add(SignInfo.fromByteArray(signInfoBuffer)); + } + return new Builder().setMagic(inMagic).setSegmentSize(inSegmentSize).setSectionNum(inSectionNum) + .setSignedFilePosList(inSignedFilePosList).setFileNameList(inFileNameList) + .setSignInfoList(inSignInfoList).setZeroPadding(inZeroPadding).build(); + } + + /** + * Return a string representation of the object + * + * @return string representation of the object + */ + public String toString() { + return String.format(Locale.ROOT, + "SoInfoSegment: magic[%d], length[%d], secNum[%d], signedFileEntryList[%s], fileNameList[%s], " + + "zeroPadding[%s], signInfoList[%s]", this.magic, this.segmentSize, this.sectionNum, + Arrays.toString(this.signedFilePosList.toArray()), Arrays.toString(this.fileNameList.toArray()), + Arrays.toString(this.zeroPadding), Arrays.toString(this.signInfoList.toArray())); + } + + /** + * Builder of NativeLibInfoSegment class + */ + public static class Builder { + private int magic = MAGIC_NUM; + + private int segmentSize; + + private int sectionNum; + + private List signedFilePosList = new ArrayList<>(); + + private List fileNameList = new ArrayList<>(); + + private List signInfoList = new ArrayList<>(); + + private byte[] zeroPadding = new byte[0]; + + public Builder setMagic(int magic) { + this.magic = magic; + return this; + } + + public Builder setSegmentSize(int segmentSize) { + this.segmentSize = segmentSize; + return this; + } + + public Builder setSectionNum(int sectionNum) { + this.sectionNum = sectionNum; + return this; + } + + public Builder setSignedFilePosList(List signedFilePosList) { + this.signedFilePosList = signedFilePosList; + return this; + } + + public Builder setFileNameList(List fileNameList) { + this.fileNameList = fileNameList; + return this; + } + + public Builder setSignInfoList(List signInfoList) { + this.signInfoList = signInfoList; + return this; + } + + public Builder setZeroPadding(byte[] zeroPadding) { + this.zeroPadding = zeroPadding; + return this; + } + + /** + * Create a NativeLibInfoSegment object + * + * @return a NativeLibInfoSegment object + */ + public NativeLibInfoSegment build() { + return new NativeLibInfoSegment(this); + } + } +} diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/datastructure/SegmentHeader.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/datastructure/SegmentHeader.java new file mode 100644 index 0000000000000000000000000000000000000000..975dda21553f8f8687784640839fc365852615de --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/datastructure/SegmentHeader.java @@ -0,0 +1,156 @@ +/* + * Copyright (c) 2023-2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.ohos.hapsigntool.codesigning.datastructure; + +import com.ohos.hapsigntool.codesigning.exception.VerifyCodeSignException; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.Locale; + +/** + * segment header has three field: + *

+ * u32 type: indicates the type of segment: fs-verity/so/hap info segment + *

+ * u32 segment offset: the segment position based on the start of code sign block + *

+ * u32 segment size: byte size of the segment + * + * @since 2023/09/08 + */ +public class SegmentHeader { + /** + * Byte size of SegmentHeader + */ + public static final int SEGMENT_HEADER_LENGTH = 12; + + /** + * Fs-verity segment type + */ + public static final int CSB_FSVERITY_INFO_SEG = 0x1; + + /** + * Hap info segment type + */ + public static final int CSB_HAP_META_SEG = 0x2; + + /** + * So info segment type + */ + public static final int CSB_NATIVE_LIB_INFO_SEG = 0x3; + + private final int type; + + private int segmentOffset; + + private final int segmentSize; + + /** + * Constructor for SegmentHeader + * + * @param type segment type + * @param segmentSize byte size of the segment + */ + public SegmentHeader(int type, int segmentSize) { + this(type, 0, segmentSize); + } + + /** + * Constructor for SegmentHeader + * + * @param type segment type + * @param segmentOffset segment offset based on the start of code sign block + * @param segmentSize byte size of segment + */ + public SegmentHeader(int type, int segmentOffset, int segmentSize) { + this.type = type; + this.segmentOffset = segmentOffset; + this.segmentSize = segmentSize; + } + + public int getType() { + return type; + } + + public void setSegmentOffset(int offset) { + this.segmentOffset = offset; + } + + public int getSegmentOffset() { + return segmentOffset; + } + + public int getSegmentSize() { + return segmentSize; + } + + /** + * Converts SegmentHeader to a newly created byte array + * + * @return Byte array representation of SegmentHeader + */ + public byte[] toByteArray() { + ByteBuffer bf = ByteBuffer.allocate(SEGMENT_HEADER_LENGTH).order(ByteOrder.LITTLE_ENDIAN); + bf.putInt(type); + bf.putInt(segmentOffset); + bf.putInt(segmentSize); + return bf.array(); + } + + /** + * Init the SegmentHeader by a byte array + * + * @param bytes Byte array representation of a SegmentHeader object + * @return a newly created SegmentHeader object + * @throws VerifyCodeSignException parsing result invalid + */ + public static SegmentHeader fromByteArray(byte[] bytes) throws VerifyCodeSignException { + if (bytes.length != SEGMENT_HEADER_LENGTH) { + throw new VerifyCodeSignException("Invalid size of SegmentHeader"); + } + ByteBuffer bf = ByteBuffer.allocate(SEGMENT_HEADER_LENGTH).order(ByteOrder.LITTLE_ENDIAN); + bf.put(bytes); + bf.rewind(); + int inType = bf.getInt(); + if ((inType != CSB_FSVERITY_INFO_SEG) && (inType != CSB_HAP_META_SEG) && (inType != CSB_NATIVE_LIB_INFO_SEG)) { + throw new VerifyCodeSignException("Invalid type of SegmentHeader"); + } + int inSegmentOffset = bf.getInt(); + // segment offset is always larger than the size of CodeSignBlockHeader + if (inSegmentOffset < CodeSignBlockHeader.size()) { + throw new VerifyCodeSignException("Invalid segmentOffset of SegmentHeader"); + } + int inSegmentSize = bf.getInt(); + if (inSegmentSize < 0) { + throw new VerifyCodeSignException("Invalid segmentSize of SegmentHeader"); + } + if ((inType == CSB_FSVERITY_INFO_SEG) && (inSegmentSize != FsVerityInfoSegment.FS_VERITY_INFO_SEGMENT_SIZE)) { + throw new VerifyCodeSignException("Invalid segmentSize of fs-verity SegmentHeader"); + } + return new SegmentHeader(inType, inSegmentOffset, inSegmentSize); + } + + /** + * Return a string representation of the object + * + * @return string representation of the object + */ + public String toString() { + return String.format(Locale.ROOT, "Segment Header: type=%d, seg_offset = %d, seg_size = %d", this.type, + this.segmentOffset, this.segmentSize); + } +} diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/datastructure/SignInfo.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/datastructure/SignInfo.java new file mode 100644 index 0000000000000000000000000000000000000000..e2d538c4f3dc2cd6d05518caf9240c7be54b9d50 --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/datastructure/SignInfo.java @@ -0,0 +1,452 @@ +/* + * Copyright (c) 2023-2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.ohos.hapsigntool.codesigning.datastructure; + +import com.ohos.hapsigntool.codesigning.exception.VerifyCodeSignException; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Locale; + +/** + * Sign info represents information after signing a file, including signature, merkle tree. + * Structure: + *

+ * 1) u32 saltSize: byte size of salt + *

+ * 2) u32 sigSize: byte size of signature + *

+ * 3) u32 flags: reserved flags + *

+ * 4) u64 dataSize: byte size of data being signed + *

+ * 5) u8[32] salt: salt used in signing + *

+ * 6) u32 extensionNum: number of extension + *

+ * 7) u32 extensionOffset + *

+ * 8) u8[] signature: signature of the data + *

+ * MerkleTree is represented as an extension of the sign info. + * Its structure is defined in MerkleTreeExtension.java + * + * @since 2023/09/08 + */ +public class SignInfo { + /** + * merkle tree extension is included in sign info + */ + public static final int FLAG_MERKLE_TREE_INCLUDED = 0x1; + + /** + * maximum of extension number + */ + public static final int MAX_EXTENSION_NUM = 1; + + /** + * sign info structure without signature in bytes, refer to toByteArray() method + */ + private static final int SIGN_INFO_SIZE_WITHOUT_SIGNATURE = 60; + + private static final int SALT_BUFFER_LENGTH = 32; + + private static final int SIGNATURE_ALIGNMENT = 4; + + private int saltSize; + + private int sigSize; + + private int flags; + + private long dataSize; + + private byte[] salt; + + private int extensionNum; + + private int extensionOffset; + + private byte[] signature; + + private byte[] zeroPadding; + + // temporary, use list instead + private List extensionList = new ArrayList<>(); + + /** + * Constructor for SignInfo + * + * @param saltSize byte size of salt + * @param flags reserved flags + * @param dataSize byte size of data being signed + * @param salt salt in byte array representation + * @param sig signature after signing the data in byte array representation + */ + public SignInfo(int saltSize, int flags, long dataSize, byte[] salt, byte[] sig) { + this.saltSize = saltSize; + this.flags = flags; + this.dataSize = dataSize; + if (salt == null) { + this.salt = new byte[SALT_BUFFER_LENGTH]; + } else { + this.salt = salt; + } + this.signature = sig; + this.sigSize = sig == null ? 0 : sig.length; + // align for extension after signature + this.zeroPadding = new byte[(SIGNATURE_ALIGNMENT - (this.sigSize % SIGNATURE_ALIGNMENT)) % SIGNATURE_ALIGNMENT]; + } + + /** + * Constructor by a SignInfoBuilder + * + * @param builder SignInfoBuilder + */ + private SignInfo(SignInfoBuilder builder) { + this.saltSize = builder.saltSize; + this.sigSize = builder.sigSize; + this.flags = builder.flags; + this.dataSize = builder.dataSize; + this.salt = builder.salt; + this.extensionNum = builder.extensionNum; + this.extensionOffset = builder.extensionOffset; + this.signature = builder.signature; + this.zeroPadding = builder.zeroPadding; + this.extensionList = builder.extensionList; + } + + /** + * Add one Extension into SignInfo Object + * + * @param extension Extension object + */ + public void addExtension(Extension extension) { + this.extensionOffset = this.size(); + this.extensionList.add(extension); + this.extensionNum = this.extensionList.size(); + } + + /** + * Get Extension from SignInfo based on extension type + * + * @param type extension type + * @return Extension object + */ + public Extension getExtensionByType(int type) { + for (Extension ext : this.extensionList) { + if (ext.isType(type)) { + return ext; + } + } + return null; + } + + /** + * Returns extensionNum + * + * @return extensionNum + */ + public int getExtensionNum() { + return extensionNum; + } + + public byte[] getSignature() { + return signature; + } + + public long getDataSize() { + return dataSize; + } + + /** + * Returns byte size of SignInfo object + * + * @return byte size of SignInfo object + */ + public int size() { + int blockSize = SIGN_INFO_SIZE_WITHOUT_SIGNATURE + this.signature.length + this.zeroPadding.length; + for (Extension ext : this.extensionList) { + blockSize += ext.size(); + } + return blockSize; + } + + /** + * Converts SignInfo to a newly created byte array + * + * @return Byte array representation of SignInfo + */ + public byte[] toByteArray() { + ByteBuffer bf = ByteBuffer.allocate(this.size()).order(ByteOrder.LITTLE_ENDIAN); + bf.putInt(this.saltSize); + bf.putInt(this.sigSize); + bf.putInt(this.flags); + bf.putLong(this.dataSize); + bf.put(this.salt); + bf.putInt(this.extensionNum); + bf.putInt(this.extensionOffset); + bf.put(this.signature); + bf.put(this.zeroPadding); + // put extension + for (Extension ext : this.extensionList) { + bf.put(ext.toByteArray()); + } + return bf.array(); + } + + /** + * Init the SignInfo by a byte array + * + * @param bytes Byte array representation of a SignInfo object + * @return a newly created SignInfo object + * @throws VerifyCodeSignException parsing result invalid + */ + public static SignInfo fromByteArray(byte[] bytes) throws VerifyCodeSignException { + ByteBuffer bf = ByteBuffer.allocate(bytes.length).order(ByteOrder.LITTLE_ENDIAN); + bf.put(bytes); + bf.rewind(); + int inSaltSize = bf.getInt(); + if (inSaltSize < 0) { + throw new VerifyCodeSignException("Invalid saltSize of SignInfo"); + } + int inSigSize = bf.getInt(); + if (inSigSize < 0) { + throw new VerifyCodeSignException("Invalid sigSize of SignInfo"); + } + int inFlags = bf.getInt(); + if (inFlags != 0 && inFlags != FLAG_MERKLE_TREE_INCLUDED) { + throw new VerifyCodeSignException("Invalid flags of SignInfo"); + } + long inDataSize = bf.getLong(); + if (inDataSize < 0) { + throw new VerifyCodeSignException("Invalid dataSize of SignInfo"); + } + byte[] inSalt = new byte[SALT_BUFFER_LENGTH]; + bf.get(inSalt); + int inExtensionNum = bf.getInt(); + if (inExtensionNum < 0 || inExtensionNum > MAX_EXTENSION_NUM) { + throw new VerifyCodeSignException("Invalid extensionNum of SignInfo"); + } + int inExtensionOffset = bf.getInt(); + if (inExtensionOffset < 0 || inExtensionOffset % 4 != 0) { + throw new VerifyCodeSignException("Invalid extensionOffset of SignInfo"); + } + byte[] inSignature = new byte[inSigSize]; + bf.get(inSignature); + byte[] inZeroPadding = new byte[(SIGNATURE_ALIGNMENT - (inSigSize % SIGNATURE_ALIGNMENT)) + % SIGNATURE_ALIGNMENT]; + bf.get(inZeroPadding); + // parse merkle tree extension + List inExtensionList = parseMerkleTreeExtension(bf, inExtensionNum); + return new SignInfoBuilder().setSaltSize(inSaltSize) + .setSigSize(inSigSize) + .setFlags(inFlags) + .setDataSize(inDataSize) + .setSalt(inSalt) + .setExtensionNum(inExtensionNum) + .setExtensionOffset(inExtensionOffset) + .setSignature(inSignature) + .setZeroPadding(inZeroPadding) + .setExtensionList(inExtensionList) + .build(); + } + + private static List parseMerkleTreeExtension(ByteBuffer bf, int inExtensionNum) + throws VerifyCodeSignException { + List inExtensionList = new ArrayList<>(); + if (inExtensionNum == 1) { + // parse merkle tree extension + int extensionType = bf.getInt(); + if (extensionType != MerkleTreeExtension.MERKLE_TREE_INLINED) { + throw new VerifyCodeSignException("Invalid extensionType of SignInfo"); + } + int extensionSize = bf.getInt(); + if (extensionSize != MerkleTreeExtension.MERKLE_TREE_EXTENSION_DATA_SIZE) { + throw new VerifyCodeSignException("Invalid extensionSize of SignInfo"); + } + byte[] merkleTreeExtension = new byte[MerkleTreeExtension.MERKLE_TREE_EXTENSION_DATA_SIZE]; + bf.get(merkleTreeExtension); + inExtensionList.add(MerkleTreeExtension.fromByteArray(merkleTreeExtension)); + } + return inExtensionList; + } + + /** + * Return a string representation of the object + * + * @return string representation of the object + */ + public String toString() { + String str = String.format(Locale.ROOT, "SignInfo: saltSize[%d], sigSize[%d]," + + "flags[%d], dataSize[%d], salt[%s], zeroPad[%s], extNum[%d], extOffset[%d]", + this.saltSize, this.sigSize, this.flags, this.dataSize, Arrays.toString(this.salt), + Arrays.toString(this.zeroPadding), this.extensionNum, this.extensionOffset); + if (this.getExtensionByType(MerkleTreeExtension.MERKLE_TREE_INLINED) != null) { + str += String.format(Locale.ROOT, "SignInfo.merkleTreeExtension[%s]", + this.getExtensionByType(MerkleTreeExtension.MERKLE_TREE_INLINED).toString()); + } + return str; + } + + /** + * Builder of SignInfo object + */ + public static class SignInfoBuilder { + private int saltSize; + + private int sigSize; + + private int flags; + + private long dataSize; + + private byte[] salt; + + private int extensionNum; + + private int extensionOffset; + + private byte[] signature; + + private byte[] zeroPadding; + + // temporary, use list instead + private List extensionList = new ArrayList<>(); + + /** + * set saltSize + * + * @param saltSize saltSize + * @return SignInfoBuilder + */ + public SignInfoBuilder setSaltSize(int saltSize) { + this.saltSize = saltSize; + return this; + } + + /** + * set sigSize + * + * @param sigSize sigSize + * @return SignInfoBuilder + */ + public SignInfoBuilder setSigSize(int sigSize) { + this.sigSize = sigSize; + return this; + } + + /** + * set flags + * + * @param flags flags + * @return SignInfoBuilder + */ + public SignInfoBuilder setFlags(int flags) { + this.flags = flags; + return this; + } + + /** + * set dataSize + * + * @param dataSize dataSize + * @return SignInfoBuilder + */ + public SignInfoBuilder setDataSize(long dataSize) { + this.dataSize = dataSize; + return this; + } + + /** + * set salt + * + * @param salt salt + * @return SignInfoBuilder + */ + public SignInfoBuilder setSalt(byte[] salt) { + this.salt = salt; + return this; + } + + /** + * set extensionNum + * + * @param extensionNum extensionNum + * @return SignInfoBuilder + */ + public SignInfoBuilder setExtensionNum(int extensionNum) { + this.extensionNum = extensionNum; + return this; + } + + /** + * set extensionOffset + * + * @param extensionOffset extensionOffset + * @return SignInfoBuilder + */ + public SignInfoBuilder setExtensionOffset(int extensionOffset) { + this.extensionOffset = extensionOffset; + return this; + } + + /** + * set signature + * + * @param signature signature + * @return SignInfoBuilder + */ + public SignInfoBuilder setSignature(byte[] signature) { + this.signature = signature; + return this; + } + + /** + * set zeroPadding + * + * @param zeroPadding zeroPadding + * @return SignInfoBuilder + */ + public SignInfoBuilder setZeroPadding(byte[] zeroPadding) { + this.zeroPadding = zeroPadding; + return this; + } + + /** + * set extensionList + * + * @param extensionList extensionList + * @return SignInfoBuilder + */ + public SignInfoBuilder setExtensionList(List extensionList) { + this.extensionList = extensionList; + return this; + } + + /** + * return a SignInfo object + * + * @return SignInfo object + */ + public SignInfo build() { + return new SignInfo(this); + } + } +} diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/datastructure/SignedFilePos.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/datastructure/SignedFilePos.java new file mode 100644 index 0000000000000000000000000000000000000000..6b2eb33593798e0390b476cbf13ea72e6a12ad39 --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/datastructure/SignedFilePos.java @@ -0,0 +1,123 @@ +/* + * Copyright (c) 2023-2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.ohos.hapsigntool.codesigning.datastructure; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.Locale; + +/** + * Sign info of file + * + * @since 2023/09/08 + */ +public class SignedFilePos { + /** + * file name offset based on start of so info segment + */ + private int fileNameOffset; + + /** + * byte size of file + */ + private final int fileNameSize; + + /** + * sign info offset based on start of so info segment + */ + private int signInfoOffset; + + /** + * byte size of sign info + */ + private final int signInfoSize; + + /** + * Constructor for SignedFilePos + * + * @param fileNameOffset file name offset based on segment start + * @param fileNameSize byte size of file name string + * @param signInfoOffset sign info offset based on segment start + * @param signInfoSize byte size of sign info + */ + public SignedFilePos(int fileNameOffset, int fileNameSize, int signInfoOffset, int signInfoSize) { + this.fileNameOffset = fileNameOffset; + this.fileNameSize = fileNameSize; + this.signInfoOffset = signInfoOffset; + this.signInfoSize = signInfoSize; + } + + public int getFileNameOffset() { + return fileNameOffset; + } + + public int getFileNameSize() { + return fileNameSize; + } + + public int getSignInfoOffset() { + return signInfoOffset; + } + + public int getSignInfoSize() { + return signInfoSize; + } + + /** + * increase file name offset + * + * @param incOffset increase value + */ + public void increaseFileNameOffset(int incOffset) { + this.fileNameOffset += incOffset; + } + + /** + * increase sign info offset + * + * @param incOffset increase value + */ + public void increaseSignInfoOffset(int incOffset) { + this.signInfoOffset += incOffset; + } + + /** + * Constructor for SignedFilePos by byte array + * + * @param bytes Byte array representation of SignedFilePos + * @return a newly created SignedFilePos object + */ + public static SignedFilePos fromByteArray(byte[] bytes) { + ByteBuffer bf = ByteBuffer.allocate(bytes.length).order(ByteOrder.LITTLE_ENDIAN); + bf.put(bytes); + bf.rewind(); + int inFileNameOffset = bf.getInt(); + int inFileNameSize = bf.getInt(); + int inSignInfoOffset = bf.getInt(); + int inSignInfoSize = bf.getInt(); + return new SignedFilePos(inFileNameOffset, inFileNameSize, inSignInfoOffset, inSignInfoSize); + } + + /** + * Return a string representation of the object + * + * @return string representation of the object + */ + public String toString() { + return String.format(Locale.ROOT, "SignedFilePos: fileNameOffset, Size[%d, %d], signInfoOffset, Size[%d, %d]", + this.fileNameOffset, this.fileNameSize, this.signInfoOffset, this.signInfoSize); + } +} diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/exception/CodeSignException.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/exception/CodeSignException.java new file mode 100644 index 0000000000000000000000000000000000000000..8263bf508fa60ecf16d5b408adf3c2482000e6db --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/exception/CodeSignException.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2023-2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.ohos.hapsigntool.codesigning.exception; + +/** + * CodeSign exception + * + * @since 2023/06/05 + */ +public class CodeSignException extends Exception { + private static final long serialVersionUID = -281871003709431259L; + + /** + * CodeSignException + * + * @param message msg + */ + public CodeSignException(String message) { + super(message); + } + + /** + * CodeSignException + * + * @param message msg + * @param cause cause + */ + public CodeSignException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/exception/FsVerityDigestException.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/exception/FsVerityDigestException.java new file mode 100644 index 0000000000000000000000000000000000000000..5ac1210bce1aaf1ef0a3cf8273dca12f0afa236a --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/exception/FsVerityDigestException.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2023-2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.ohos.hapsigntool.codesigning.exception; + +/** + * Fail to compute FsVerity digest of a file + * + * @since 2023/06/05 + */ +public class FsVerityDigestException extends Exception { + private static final long serialVersionUID = 5788641970791287892L; + + public FsVerityDigestException(String message) { + super(message); + } + + public FsVerityDigestException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/exception/VerifyCodeSignException.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/exception/VerifyCodeSignException.java new file mode 100644 index 0000000000000000000000000000000000000000..36d28623a55e49399ec3ab83ea2f78f1e0ca59dd --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/exception/VerifyCodeSignException.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2023-2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.ohos.hapsigntool.codesigning.exception; + +/** + * Exception occurs when the required parameters are missed + * + * @since 2023/06/05 + */ +public class VerifyCodeSignException extends Exception { + private static final long serialVersionUID = -8922730964374794468L; + + /** + * Exception occurs when the required parameters are not entered + * + * @param message msg + */ + public VerifyCodeSignException(String message) { + super(message); + } + + /** + * Exception occurs when the required parameters are not entered + * + * @param message msg + * @param cause cause + */ + public VerifyCodeSignException(String message, Throwable cause) { + super(message, cause); + } +} + diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/fsverity/FsVerityDescriptor.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/fsverity/FsVerityDescriptor.java new file mode 100644 index 0000000000000000000000000000000000000000..5531b7f837b0b55054bc7a1db9291d2e94f38916 --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/fsverity/FsVerityDescriptor.java @@ -0,0 +1,342 @@ +/* + * Copyright (c) 2023-2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.ohos.hapsigntool.codesigning.fsverity; + +import com.ohos.hapsigntool.codesigning.exception.FsVerityDigestException; +import com.ohos.hapsigntool.codesigning.exception.VerifyCodeSignException; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +/** + * Format of FsVerity descriptor + * uint8 version + * uint8 hashAlgorithm + * uint8 log2BlockSize + * uint8 saltSize + * uint32 signSize + * le64 dataSize + * uint8[64] rootHash + * uint8[32] salt + * uint32 flags + * uint8[4] 0 + * uint64 treeOffset + * uint8[127] 0 + * uint8 csVersion + * + * @since 2023/06/05 + */ +public class FsVerityDescriptor { + /** + * fs-verity version, must be 1 + */ + public static final byte VERSION = 1; + + /** + * page size in bytes + */ + public static final int PAGE_SIZE_4K = 4096; + + /** + * Indicating merkle tree offset is set in fs-verity descriptor + */ + public static final int FLAG_STORE_MERKLE_TREE_OFFSET = 0x1; + + /** + * Indicating fs-verity descriptor type + */ + public static final int FS_VERITY_DESCRIPTOR_TYPE = 0x1; + + /** + * code sign version + */ + public static final byte CODE_SIGN_VERSION = 0x1; + + /** + * FsVerity descriptor size + */ + public static final int DESCRIPTOR_SIZE = 256; + + /** + * root hash size + */ + public static final int ROOT_HASH_FILED_SIZE = 64; + + /** + * salt size + */ + public static final int SALT_SIZE = 32; + + /** + * reserved size + */ + public static final int RESERVED_SIZE_AFTER_FLAGS = 4; + + /** + * reserved size + */ + public static final int RESERVED_SIZE_AFTER_TREE_OFFSET = 127; + + private byte version; + + private long fileSize; + + private byte hashAlgorithm; + + private byte log2BlockSize; + + private byte saltSize; + + private int signSize; + + private byte[] salt; + + private byte[] rawRootHash; + + private int flags; + + private long merkleTreeOffset; + + private byte csVersion; + + private FsVerityDescriptor(Builder builder) { + this.version = builder.version; + this.fileSize = builder.fileSize; + this.hashAlgorithm = builder.hashAlgorithm; + this.log2BlockSize = builder.log2BlockSize; + this.saltSize = builder.saltSize; + this.signSize = builder.signSize; + this.salt = builder.salt; + this.rawRootHash = builder.rawRootHash; + this.flags = builder.flags; + this.merkleTreeOffset = builder.merkleTreeOffset; + this.csVersion = builder.csVersion; + } + + public long getFileSize() { + return fileSize; + } + + public long getMerkleTreeOffset() { + return merkleTreeOffset; + } + + public int getSignSize() { + return signSize; + } + + /** + * Init the FsVerityDescriptor by a byte array + * + * @param bytes Byte array representation of a FsVerityDescriptor object + * @return a newly created FsVerityDescriptor object + * @throws VerifyCodeSignException parse result invalid + */ + public static FsVerityDescriptor fromByteArray(byte[] bytes) throws VerifyCodeSignException { + ByteBuffer bf = ByteBuffer.allocate(bytes.length).order(ByteOrder.LITTLE_ENDIAN); + bf.put(bytes); + // after put, rewind is mandatory before get + bf.rewind(); + FsVerityDescriptor.Builder builder = new FsVerityDescriptor.Builder(); + byte inFsVersion = bf.get(); + if (FsVerityDescriptor.VERSION != inFsVersion) { + throw new VerifyCodeSignException("Invalid fs-verify descriptor version of ElfSignBlock"); + } + byte inFsHashAlgorithm = bf.get(); + byte inLog2BlockSize = bf.get(); + builder.setVersion(inFsVersion).setHashAlgorithm(inFsHashAlgorithm).setLog2BlockSize(inLog2BlockSize); + byte inSaltSize = bf.get(); + int inSignSize = bf.getInt(); + long inDataSize = bf.getLong(); + byte[] inRootHash = new byte[FsVerityDescriptor.ROOT_HASH_FILED_SIZE]; + bf.get(inRootHash); + builder.setSaltSize(inSaltSize).setSignSize(inSignSize).setFileSize(inDataSize).setRawRootHash(inRootHash); + byte[] inSalt = new byte[FsVerityDescriptor.SALT_SIZE]; + bf.get(inSalt); + int inFlags = bf.getInt(); + bf.getInt(); + long inTreeOffset = bf.getLong(); + if (inTreeOffset % PAGE_SIZE_4K != 0) { + throw new VerifyCodeSignException("Invalid merkle tree offset of ElfSignBlock"); + } + bf.get(new byte[FsVerityDescriptor.RESERVED_SIZE_AFTER_TREE_OFFSET]); + byte inCsVersion = bf.get(); + builder.setSalt(inSalt).setFlags(inFlags).setMerkleTreeOffset(inTreeOffset).setCsVersion(inCsVersion); + return builder.build(); + } + + /** + * Get FsVerity descriptor bytes + * + * @return bytes of descriptor + * @throws FsVerityDigestException if error + */ + public byte[] toByteArray() throws FsVerityDigestException { + ByteBuffer buffer = ByteBuffer.allocate(DESCRIPTOR_SIZE).order(ByteOrder.LITTLE_ENDIAN); + buffer.put(VERSION); + buffer.put(hashAlgorithm); + buffer.put(log2BlockSize); + if (this.saltSize > SALT_SIZE) { + throw new FsVerityDigestException("Salt is too long"); + } + buffer.put(this.saltSize); + buffer.putInt(signSize); + buffer.putLong(fileSize); + writeBytesWithSize(buffer, rawRootHash, ROOT_HASH_FILED_SIZE); + writeBytesWithSize(buffer, salt, SALT_SIZE); + buffer.putInt(flags); + writeBytesWithSize(buffer, null, RESERVED_SIZE_AFTER_FLAGS); + buffer.putLong(merkleTreeOffset); + writeBytesWithSize(buffer, null, RESERVED_SIZE_AFTER_TREE_OFFSET); + buffer.put(csVersion); + return buffer.array(); + } + + /** + * Get bytes for generate digest, first byte is CODE_SIGN_VERSION, sign size is 0, last 128 bytes is 0 + * + * @return bytes of descriptor + * @throws FsVerityDigestException if error + */ + public byte[] getByteForGenerateDigest() throws FsVerityDigestException { + ByteBuffer buffer = ByteBuffer.allocate(DESCRIPTOR_SIZE).order(ByteOrder.LITTLE_ENDIAN); + buffer.put(CODE_SIGN_VERSION); + buffer.put(hashAlgorithm); + buffer.put(log2BlockSize); + if (this.saltSize > SALT_SIZE) { + throw new FsVerityDigestException("Salt is too long"); + } + buffer.put(this.saltSize); + buffer.putInt(0); + buffer.putLong(fileSize); + writeBytesWithSize(buffer, rawRootHash, ROOT_HASH_FILED_SIZE); + writeBytesWithSize(buffer, salt, SALT_SIZE); + buffer.putInt(flags); + writeBytesWithSize(buffer, null, RESERVED_SIZE_AFTER_FLAGS); + buffer.putLong(merkleTreeOffset); + return buffer.array(); + } + + /** + * Write bytes to ByteBuffer with specific size + * + * @param buffer target buffer + * @param src bytes to write + * @param size size of written bytes, fill 0 if src bytes is long enough + */ + private void writeBytesWithSize(ByteBuffer buffer, byte[] src, int size) { + int pos = buffer.position(); + if (src != null) { + if (src.length > size) { + buffer.put(src, 0, size); + } else { + buffer.put(src); + } + } + buffer.position(pos + size); + } + + /** + * Builder of FsVerityDescriptor class + */ + public static class Builder { + private byte version = VERSION; + + private long fileSize; + + private byte hashAlgorithm; + + private byte log2BlockSize; + + private byte saltSize; + + private int signSize; + + private byte[] salt; + + private byte[] rawRootHash; + + private int flags; + + private long merkleTreeOffset; + + private byte csVersion; + + public Builder setVersion(byte version) { + this.version = version; + return this; + } + + public Builder setFileSize(long fileSize) { + this.fileSize = fileSize; + return this; + } + + public Builder setHashAlgorithm(byte hashAlgorithm) { + this.hashAlgorithm = hashAlgorithm; + return this; + } + + public Builder setLog2BlockSize(byte log2BlockSize) { + this.log2BlockSize = log2BlockSize; + return this; + } + + public Builder setSignSize(int signSize) { + this.signSize = signSize; + return this; + } + + public Builder setSaltSize(byte saltSize) { + this.saltSize = saltSize; + return this; + } + + public Builder setSalt(byte[] salt) { + this.salt = salt; + return this; + } + + public Builder setRawRootHash(byte[] rawRootHash) { + this.rawRootHash = rawRootHash; + return this; + } + + public Builder setFlags(int flags) { + this.flags = flags; + return this; + } + + public Builder setMerkleTreeOffset(long merkleTreeOffset) { + this.merkleTreeOffset = merkleTreeOffset; + return this; + } + + public Builder setCsVersion(byte csVersion) { + this.csVersion = csVersion; + return this; + } + + /** + * Create a FsVerityDescriptor object + * + * @return a FsVerityDescriptor object + */ + public FsVerityDescriptor build() { + return new FsVerityDescriptor(this); + } + } +} diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/fsverity/FsVerityDescriptorWithSign.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/fsverity/FsVerityDescriptorWithSign.java new file mode 100644 index 0000000000000000000000000000000000000000..6e3084b55d5b2c09f9ab451f0b540767def7dd15 --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/fsverity/FsVerityDescriptorWithSign.java @@ -0,0 +1,101 @@ +/* + * Copyright (c) 2023-2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.ohos.hapsigntool.codesigning.fsverity; + +import com.ohos.hapsigntool.codesigning.exception.FsVerityDigestException; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +/** + * fsverity descriptor and signature + * 1) u32 type: 0x1 fsverity descriptor + * 2) u32 length: fsverity descriptor size + * 3) FsVerityDescriptor: fsVerityDescriptor + * 4) u8[] signature: signature after signing the data in byte array representation + * + * @since 2023/09/08 + */ +public class FsVerityDescriptorWithSign { + private int type = FsVerityDescriptor.FS_VERITY_DESCRIPTOR_TYPE; + + private int length; + + private FsVerityDescriptor fsVerityDescriptor; + + private byte[] signature = new byte[0]; + + /** + * Constructor of FsVerityDescriptorWithSign + * + * @param fsVerityDescriptor fs-verity descriptor + * @param signature signature + */ + public FsVerityDescriptorWithSign(FsVerityDescriptor fsVerityDescriptor, byte[] signature) { + this.fsVerityDescriptor = fsVerityDescriptor; + if (signature != null) { + this.signature = signature; + } + length = FsVerityDescriptor.DESCRIPTOR_SIZE + this.signature.length; + } + + /** + * Constructor of FsVerityDescriptorWithSign + * + * @param type fs-verity descriptor type + * @param length fs-verity descriptor with signature size + * @param fsVerityDescriptor fs-verity descriptor + * @param signature signature + */ + public FsVerityDescriptorWithSign(int type, int length, FsVerityDescriptor fsVerityDescriptor, byte[] signature) { + this.type = type; + this.length = length; + this.fsVerityDescriptor = fsVerityDescriptor; + this.signature = signature; + } + + /** + * Returns byte size of FsVerityDescriptorWithSign + * + * @return byte size of FsVerityDescriptorWithSign + */ + public int size() { + return Integer.BYTES * 2 + FsVerityDescriptor.DESCRIPTOR_SIZE + signature.length; + } + + /** + * Converts FsVerityDescriptorWithSign to a newly created byte array + * + * @return Byte array representation of FsVerityDescriptorWithSign + * @throws FsVerityDigestException if error + */ + public byte[] toByteArray() throws FsVerityDigestException { + ByteBuffer buffer = ByteBuffer.allocate(size()).order(ByteOrder.LITTLE_ENDIAN); + buffer.putInt(type); + buffer.putInt(length); + buffer.put(fsVerityDescriptor.toByteArray()); + buffer.put(signature); + return buffer.array(); + } + + public FsVerityDescriptor getFsVerityDescriptor() { + return fsVerityDescriptor; + } + + public byte[] getSignature() { + return signature; + } +} diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/fsverity/FsVerityDigest.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/fsverity/FsVerityDigest.java new file mode 100644 index 0000000000000000000000000000000000000000..cff3617c9f01f933191cca4c09fd2fd5c66cafbf --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/fsverity/FsVerityDigest.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2023-2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.ohos.hapsigntool.codesigning.fsverity; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.charset.StandardCharsets; + +/** + * Format of FsVerity digest + * int8[8] magic "FSVerity" + * le16 digestAlgorithm sha256 = 1, sha512 = 2 + * le16 digestSize + * uint8[] digest + * + * @since 2023/06/05 + */ +public class FsVerityDigest { + private static final String FSVERITY_DIGEST_MAGIC = "FSVerity"; + + private static final int DIGEST_HEADER_SIZE = 12; + + /** + * Get formatted FsVerity digest + * + * @param algoID hash algorithm id + * @param digest raw digest computed from input + * @return formatted FsVerity digest bytes + */ + public static byte[] getFsVerityDigest(byte algoID, byte[] digest) { + int size = DIGEST_HEADER_SIZE + digest.length; + ByteBuffer buffer = ByteBuffer.allocate(size).order(ByteOrder.LITTLE_ENDIAN); + buffer.put(FSVERITY_DIGEST_MAGIC.getBytes(StandardCharsets.UTF_8)); + buffer.putShort(algoID); + buffer.putShort((short) digest.length); + buffer.put(digest); + return buffer.array(); + } +} diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/fsverity/FsVerityGenerator.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/fsverity/FsVerityGenerator.java new file mode 100644 index 0000000000000000000000000000000000000000..57501d09d6ab5e056e991a4fcd669340b362fa94 --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/fsverity/FsVerityGenerator.java @@ -0,0 +1,166 @@ +/* + * Copyright (c) 2023-2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.ohos.hapsigntool.codesigning.fsverity; + +import com.ohos.hapsigntool.codesigning.exception.FsVerityDigestException; +import com.ohos.hapsigntool.codesigning.utils.DigestUtils; + +import java.io.IOException; +import java.io.InputStream; +import java.security.NoSuchAlgorithmException; + +/** + * FsVerity data generator supper class + * + * @since 2023/06/05 + */ +public class FsVerityGenerator { + /** + * FsVerity hash algorithm + */ + private static final FsVerityHashAlgorithm FS_VERITY_HASH_ALGORITHM = FsVerityHashAlgorithm.SHA256; + + private static final byte LOG_2_OF_FSVERITY_HASH_PAGE_SIZE = 12; + + /** + * salt for hashing one page + */ + protected byte[] salt = null; + + private byte[] fsVerityDigest = null; + + private byte[] treeBytes = null; + + private byte[] rootHash = null; + + /** + * generate merkle tree of given input + * + * @param inputStream input stream for generate merkle tree + * @param size total size of input stream + * @param fsVerityHashAlgorithm hash algorithm for FsVerity + * @return merkle tree + * @throws FsVerityDigestException if error + */ + public MerkleTree generateMerkleTree(InputStream inputStream, long size, + FsVerityHashAlgorithm fsVerityHashAlgorithm) throws FsVerityDigestException { + MerkleTree merkleTree; + try (MerkleTreeBuilder builder = new MerkleTreeBuilder()) { + merkleTree = builder.generateMerkleTree(inputStream, size, fsVerityHashAlgorithm); + } catch (IOException e) { + throw new FsVerityDigestException("IOException: " + e.getMessage()); + } catch (NoSuchAlgorithmException e) { + throw new FsVerityDigestException("Invalid algorithm:" + e.getMessage()); + } + return merkleTree; + } + + /** + * generate FsVerity digest of given input + * + * @param inputStream input stream for generate FsVerity digest + * @param size total size of input stream + * @param fsvTreeOffset merkle tree raw bytes offset based on the start of file + * @throws FsVerityDigestException if error + */ + public void generateFsVerityDigest(InputStream inputStream, long size, long fsvTreeOffset) + throws FsVerityDigestException { + MerkleTree merkleTree; + if (size == 0) { + merkleTree = new MerkleTree(null, null, FS_VERITY_HASH_ALGORITHM); + } else { + merkleTree = generateMerkleTree(inputStream, size, FS_VERITY_HASH_ALGORITHM); + } + int flags = fsvTreeOffset == 0 ? 0 : FsVerityDescriptor.FLAG_STORE_MERKLE_TREE_OFFSET; + // sign size is 0, cs version is 0 + FsVerityDescriptor.Builder builder = new FsVerityDescriptor.Builder().setFileSize(size) + .setHashAlgorithm(FS_VERITY_HASH_ALGORITHM.getId()) + .setLog2BlockSize(LOG_2_OF_FSVERITY_HASH_PAGE_SIZE) + .setSaltSize((byte) getSaltSize()) + .setSalt(salt) + .setRawRootHash(merkleTree.rootHash) + .setFlags(flags) + .setMerkleTreeOffset(fsvTreeOffset); + byte[] fsVerityDescriptor = builder.build().getByteForGenerateDigest(); + byte[] digest; + try { + digest = DigestUtils.computeDigest(fsVerityDescriptor, FS_VERITY_HASH_ALGORITHM.getHashAlgorithm()); + } catch (NoSuchAlgorithmException e) { + throw new FsVerityDigestException("Invalid algorithm" + e.getMessage(), e); + } + fsVerityDigest = FsVerityDigest.getFsVerityDigest(FS_VERITY_HASH_ALGORITHM.getId(), digest); + treeBytes = merkleTree.tree; + rootHash = merkleTree.rootHash; + } + + /** + * Get FsVerity digest + * + * @return bytes of FsVerity digest + */ + public byte[] getFsVerityDigest() { + return fsVerityDigest; + } + + /** + * Get merkle tree in bytes + * + * @return bytes of merkle tree + */ + public byte[] getTreeBytes() { + return treeBytes; + } + + /** + * Get merkle tree rootHash in bytes + * + * @return bytes of merkle tree rootHash + */ + public byte[] getRootHash() { + return rootHash; + } + + public byte[] getSalt() { + return salt; + } + + /** + * Returns byte size of salt + * + * @return byte size of salt + */ + public int getSaltSize() { + return this.salt == null ? 0 : this.salt.length; + } + + /** + * Returns the id of fs-verity hash algorithm + * + * @return fs-verity hash algorithm id + */ + public static byte getFsVerityHashAlgorithm() { + return FS_VERITY_HASH_ALGORITHM.getId(); + } + + /** + * Returns the log2 of size of data and tree blocks + * + * @return log2 of size of data and tree blocks + */ + public static byte getLog2BlockSize() { + return LOG_2_OF_FSVERITY_HASH_PAGE_SIZE; + } +} diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/fsverity/FsVerityHashAlgorithm.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/fsverity/FsVerityHashAlgorithm.java new file mode 100644 index 0000000000000000000000000000000000000000..7d188413c321fe89efa35b064dfc8fd8597f7d47 --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/fsverity/FsVerityHashAlgorithm.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2023-2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.ohos.hapsigntool.codesigning.fsverity; + +/** + * FsVerity hash algorithm + * + * @since 2023/06/05 + */ +public enum FsVerityHashAlgorithm { + SHA256((byte) 1, "SHA-256", 256 / 8), + SHA512((byte) 2, "SHA-512", 512 / 8); + + private final byte id; + + private final String hashAlgorithm; + + private final int outputByteSize; + + FsVerityHashAlgorithm(byte id, String hashAlgorithm, int outputByteSize) { + this.id = id; + this.hashAlgorithm = hashAlgorithm; + this.outputByteSize = outputByteSize; + } + + public byte getId() { + return id; + } + + public String getHashAlgorithm() { + return hashAlgorithm; + } + + public int getOutputByteSize() { + return outputByteSize; + } +} diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/fsverity/MerkleTree.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/fsverity/MerkleTree.java new file mode 100644 index 0000000000000000000000000000000000000000..eebfdd2813cb6a445240be1f3cf4315c9bfc1e00 --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/fsverity/MerkleTree.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2023-2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.ohos.hapsigntool.codesigning.fsverity; + +/** + * Merkle tree data + * + * @since 2023/06/05 + */ +public class MerkleTree { + /** + * root hash of merkle tree + */ + public final byte[] rootHash; + + /** + * content data of merkle tree + */ + public final byte[] tree; + + /** + * hash algorithm used for merkle tree + */ + public final FsVerityHashAlgorithm fsVerityHashAlgorithm; + + MerkleTree(byte[] rootHash, byte[] tree, FsVerityHashAlgorithm fsVerityHashAlgorithm) { + this.rootHash = rootHash; + this.tree = tree; + this.fsVerityHashAlgorithm = fsVerityHashAlgorithm; + } +} diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/fsverity/MerkleTreeBuilder.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/fsverity/MerkleTreeBuilder.java new file mode 100644 index 0000000000000000000000000000000000000000..cbc198e9aa532b9a8cf62d9433040a47c16ff4ea --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/fsverity/MerkleTreeBuilder.java @@ -0,0 +1,354 @@ +/* + * Copyright (c) 2023-2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.ohos.hapsigntool.codesigning.fsverity; + +import com.ohos.hapsigntool.codesigning.utils.DigestUtils; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.ByteBuffer; +import java.security.NoSuchAlgorithmException; +import java.util.ArrayList; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Phaser; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +/** + * Merkle tree builder + * + * @since 2023/06/05 + */ +public class MerkleTreeBuilder implements AutoCloseable { + private static final int FSVERITY_HASH_PAGE_SIZE = 4096; + + private static final long INPUTSTREAM_MAX_SIZE = 4503599627370496L; + + private static final int CHUNK_SIZE = 4096; + + private static final long MAX_READ_SIZE = 4194304L; + + private static final int MAX_PROCESSORS = 32; + + private static final int BLOCKINGQUEUE = 4; + + private static final int POOL_SIZE = Math.min(MAX_PROCESSORS, Runtime.getRuntime().availableProcessors()); + + private String mAlgorithm = "SHA-256"; + + private final ExecutorService mPools = new ThreadPoolExecutor(POOL_SIZE, POOL_SIZE, 0L, TimeUnit.MILLISECONDS, + new ArrayBlockingQueue<>(BLOCKINGQUEUE), new ThreadPoolExecutor.CallerRunsPolicy()); + + /** + * Turn off multitasking + */ + public void close() { + this.mPools.shutdownNow(); + } + + /** + * set algorithm + * + * @param algorithm hash algorithm + */ + private void setAlgorithm(String algorithm) { + this.mAlgorithm = algorithm; + } + + /** + * translation inputStream to hash data + * + * @param inputStream input stream for generating merkle tree + * @param size total size of input stream + * @param outputBuffer hash data + * @throws IOException if error + */ + private void transInputStreamToHashData(InputStream inputStream, long size, ByteBuffer outputBuffer) + throws IOException { + if (size == 0) { + throw new IOException("Input size is empty"); + } else if (size > INPUTSTREAM_MAX_SIZE) { + throw new IOException("Input size is too long"); + } + int count = (int) getChunkCount(size, MAX_READ_SIZE); + int chunks = (int) getChunkCount(size, CHUNK_SIZE); + byte[][] hashes = new byte[chunks][]; + long readOffset = 0L; + Phaser tasks = new Phaser(1); + for (int i = 0; i < count; i++) { + long readLimit = Math.min(readOffset + MAX_READ_SIZE, size); + int readSize = (int) (readLimit - readOffset); + int fullChunkSize = (int) getFullChunkSize(readSize, CHUNK_SIZE, CHUNK_SIZE); + + ByteBuffer byteBuffer = ByteBuffer.allocate(fullChunkSize); + byte[] buffer = new byte[CHUNK_SIZE]; + int num; + int offset = 0; + int len = CHUNK_SIZE; + while ((num = inputStream.read(buffer, 0, len)) > 0) { + byteBuffer.put(buffer, 0, num); + offset += num; + len = Math.min(CHUNK_SIZE, readSize - offset); + if (len <= 0 || offset == readSize) { + break; + } + } + if (offset != readSize) { + throw new IOException("IOException read buffer from input errorLHJ."); + } + byteBuffer.flip(); + int readChunkIndex = (int) getFullChunkSize(MAX_READ_SIZE, CHUNK_SIZE, i); + runHashTask(hashes, tasks, byteBuffer, readChunkIndex); + readOffset += readSize; + } + tasks.arriveAndAwaitAdvance(); + for (byte[] hash : hashes) { + outputBuffer.put(hash, 0, hash.length); + } + } + + /** + * split buffer by begin and end information + * + * @param buffer original buffer + * @param begin begin position + * @param end end position + * @return slice buffer + */ + private static ByteBuffer slice(ByteBuffer buffer, int begin, int end) { + ByteBuffer tempBuffer = buffer.duplicate(); + tempBuffer.position(0); + tempBuffer.limit(end); + tempBuffer.position(begin); + return tempBuffer.slice(); + } + + /** + * calculate merkle tree level and size by data size and digest size + * + * @param dataSize original data size + * @param digestSize algorithm data size + * @return level offset list, contains the offset of + * each level from the root node to the leaf node + */ + private static int[] getOffsetArrays(long dataSize, int digestSize) { + ArrayList levelSize = getLevelSize(dataSize, digestSize); + int[] levelOffset = new int[levelSize.size() + 1]; + levelOffset[0] = 0; + for (int i = 0; i < levelSize.size(); i++) { + levelOffset[i + 1] = levelOffset[i] + Math.toIntExact(levelSize.get(levelSize.size() - i - 1)); + } + return levelOffset; + } + + /** + * calculate data size list by data size and digest size + * + * @param dataSize original data size + * @param digestSize algorithm data size + * @return data size list, contains the offset of + * each level from the root node to the leaf node + */ + private static ArrayList getLevelSize(long dataSize, int digestSize) { + ArrayList levelSize = new ArrayList<>(); + long fullChunkSize = 0L; + long originalDataSize = dataSize; + do { + fullChunkSize = getFullChunkSize(originalDataSize, CHUNK_SIZE, digestSize); + long size = getFullChunkSize(fullChunkSize, CHUNK_SIZE, CHUNK_SIZE); + levelSize.add(size); + originalDataSize = fullChunkSize; + } while (fullChunkSize > CHUNK_SIZE); + return levelSize; + } + + private void runHashTask(byte[][] hashes, Phaser tasks, ByteBuffer buffer, int readChunkIndex) { + Runnable task = () -> { + int offset = 0; + int bufferSize = buffer.capacity(); + int index = readChunkIndex; + while (offset < bufferSize) { + ByteBuffer chunk = slice(buffer, offset, offset + CHUNK_SIZE); + byte[] tempByte = new byte[CHUNK_SIZE]; + chunk.get(tempByte); + try { + hashes[index++] = DigestUtils.computeDigest(tempByte, this.mAlgorithm); + } catch (NoSuchAlgorithmException e) { + throw new IllegalStateException(e); + } + offset += CHUNK_SIZE; + } + tasks.arriveAndDeregister(); + }; + tasks.register(); + this.mPools.execute(task); + } + + /** + * hash data of buffer + * + * @param inputBuffer original data + * @param outputBuffer hash data + */ + private void transInputDataToHashData(ByteBuffer inputBuffer, ByteBuffer outputBuffer) { + long size = inputBuffer.capacity(); + int chunks = (int) getChunkCount(size, CHUNK_SIZE); + byte[][] hashes = new byte[chunks][]; + Phaser tasks = new Phaser(1); + long readOffset = 0L; + int startChunkIndex = 0; + while (readOffset < size) { + long readLimit = Math.min(readOffset + MAX_READ_SIZE, size); + ByteBuffer buffer = slice(inputBuffer, (int) readOffset, (int) readLimit); + buffer.rewind(); + int readChunkIndex = startChunkIndex; + runHashTask(hashes, tasks, buffer, readChunkIndex); + int readSize = (int) (readLimit - readOffset); + startChunkIndex += (int) getChunkCount(readSize, CHUNK_SIZE); + readOffset += readSize; + } + tasks.arriveAndAwaitAdvance(); + for (byte[] hash : hashes) { + outputBuffer.put(hash, 0, hash.length); + } + } + + /** + * generate merkle tree of given input + * + * @param inputStream input stream for generate merkle tree + * @param size total size of input stream + * @param fsVerityHashAlgorithm hash algorithm for FsVerity + * @return merkle tree + * @throws IOException if error + * @throws NoSuchAlgorithmException if error + */ + public MerkleTree generateMerkleTree(InputStream inputStream, long size, + FsVerityHashAlgorithm fsVerityHashAlgorithm) throws IOException, NoSuchAlgorithmException { + setAlgorithm(fsVerityHashAlgorithm.getHashAlgorithm()); + int digestSize = fsVerityHashAlgorithm.getOutputByteSize(); + int[] offsetArrays = getOffsetArrays(size, digestSize); + ByteBuffer allHashBuffer = ByteBuffer.allocate(offsetArrays[offsetArrays.length - 1]); + generateHashDataByInputData(inputStream, size, allHashBuffer, offsetArrays, digestSize); + generateHashDataByHashData(allHashBuffer, offsetArrays, digestSize); + return getMerkleTree(allHashBuffer, size, fsVerityHashAlgorithm); + } + + /** + * translation inputBuffer arrays to hash ByteBuffer + * + * @param inputStream input stream for generate merkle tree + * @param size total size of input stream + * @param outputBuffer hash data + * @param offsetArrays level offset + * @param digestSize algorithm output byte size + * @throws IOException if error + */ + private void generateHashDataByInputData(InputStream inputStream, long size, ByteBuffer outputBuffer, + int[] offsetArrays, int digestSize) throws IOException { + int inputDataOffsetBegin = offsetArrays[offsetArrays.length - 2]; + int inputDataOffsetEnd = offsetArrays[offsetArrays.length - 1]; + ByteBuffer hashBuffer = slice(outputBuffer, inputDataOffsetBegin, inputDataOffsetEnd); + transInputStreamToHashData(inputStream, size, hashBuffer); + dataRoundupChunkSize(hashBuffer, size, digestSize); + } + + /** + * get buffer data by level offset, transforms digest data, save in another + * memory + * + * @param buffer hash data + * @param offsetArrays level offset + * @param digestSize algorithm output byte size + */ + private void generateHashDataByHashData(ByteBuffer buffer, int[] offsetArrays, int digestSize) { + for (int i = offsetArrays.length - 3; i >= 0; i--) { + ByteBuffer generateHashBuffer = slice(buffer, offsetArrays[i], offsetArrays[i + 1]); + ByteBuffer originalHashBuffer = slice(buffer.asReadOnlyBuffer(), offsetArrays[i + 1], offsetArrays[i + 2]); + transInputDataToHashData(originalHashBuffer, generateHashBuffer); + dataRoundupChunkSize(generateHashBuffer, originalHashBuffer.capacity(), digestSize); + } + } + + /** + * generate merkle tree of given input + * + * @param dataBuffer tree data memory block + * @param inputDataSize total size of input stream + * @param fsVerityHashAlgorithm hash algorithm for FsVerity + * @return merkle tree + * @throws NoSuchAlgorithmException if error + */ + private MerkleTree getMerkleTree(ByteBuffer dataBuffer, long inputDataSize, + FsVerityHashAlgorithm fsVerityHashAlgorithm) throws NoSuchAlgorithmException { + int digestSize = fsVerityHashAlgorithm.getOutputByteSize(); + dataBuffer.flip(); + byte[] rootHash = null; + byte[] tree = null; + if (inputDataSize < FSVERITY_HASH_PAGE_SIZE) { + ByteBuffer fsVerityHashPageBuffer = slice(dataBuffer, 0, digestSize); + rootHash = new byte[digestSize]; + fsVerityHashPageBuffer.get(rootHash); + } else { + tree = dataBuffer.array(); + ByteBuffer fsVerityHashPageBuffer = slice(dataBuffer.asReadOnlyBuffer(), 0, FSVERITY_HASH_PAGE_SIZE); + byte[] fsVerityHashPage = new byte[FSVERITY_HASH_PAGE_SIZE]; + fsVerityHashPageBuffer.get(fsVerityHashPage); + rootHash = DigestUtils.computeDigest(fsVerityHashPage, this.mAlgorithm); + } + return new MerkleTree(rootHash, tree, fsVerityHashAlgorithm); + } + + /** + * generate merkle tree of given input + * + * @param data original data + * @param originalDataSize data size + * @param digestSize algorithm output byte size + */ + private void dataRoundupChunkSize(ByteBuffer data, long originalDataSize, int digestSize) { + long fullChunkSize = getFullChunkSize(originalDataSize, CHUNK_SIZE, digestSize); + int diffValue = (int) (fullChunkSize % CHUNK_SIZE); + if (diffValue > 0) { + byte[] padding = new byte[CHUNK_SIZE - diffValue]; + data.put(padding, 0, padding.length); + } + } + + /** + * get mount of chunks to store data + * + * @param dataSize data size + * @param divisor split chunk size + * @return chunk count + */ + private static long getChunkCount(long dataSize, long divisor) { + return (long) Math.ceil((double) dataSize / (double) divisor); + } + + /** + * get total size of chunk to store data + * + * @param dataSize data size + * @param divisor split chunk size + * @param multiplier chunk multiplier + * @return chunk size + */ + private static long getFullChunkSize(long dataSize, long divisor, long multiplier) { + return getChunkCount(dataSize, divisor) * multiplier; + } +} diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/sign/BcSignedDataGenerator.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/sign/BcSignedDataGenerator.java new file mode 100644 index 0000000000000000000000000000000000000000..238f5cb8a16315ee07327a825061ce02c6df730f --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/sign/BcSignedDataGenerator.java @@ -0,0 +1,264 @@ +/* + * Copyright (c) 2023-2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.ohos.hapsigntool.codesigning.sign; + +import com.ohos.hapsigntool.codesigning.exception.CodeSignException; +import com.ohos.hapsigntool.codesigning.utils.CmsUtils; +import com.ohos.hapsigntool.codesigning.utils.DigestUtils; +import com.ohos.hapsigntool.hap.config.SignerConfig; +import com.ohos.hapsigntool.hap.entity.Pair; +import com.ohos.hapsigntool.hap.sign.ContentDigestAlgorithm; +import com.ohos.hapsigntool.hap.sign.SignatureAlgorithm; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.bouncycastle.asn1.ASN1EncodableVector; +import org.bouncycastle.asn1.ASN1Encoding; +import org.bouncycastle.asn1.ASN1Integer; +import org.bouncycastle.asn1.ASN1Set; +import org.bouncycastle.asn1.BERSet; +import org.bouncycastle.asn1.DEROctetString; +import org.bouncycastle.asn1.DERSet; +import org.bouncycastle.asn1.ASN1ObjectIdentifier; +import org.bouncycastle.asn1.DERUTF8String; +import org.bouncycastle.asn1.cms.Time; +import org.bouncycastle.asn1.pkcs.Attribute; +import org.bouncycastle.asn1.pkcs.ContentInfo; +import org.bouncycastle.asn1.pkcs.IssuerAndSerialNumber; +import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers; +import org.bouncycastle.asn1.pkcs.SignedData; +import org.bouncycastle.asn1.pkcs.SignerInfo; +import org.bouncycastle.cert.jcajce.JcaX509CRLHolder; +import org.bouncycastle.cert.jcajce.JcaX509CertificateHolder; +import org.bouncycastle.cms.CMSException; +import org.bouncycastle.operator.DefaultDigestAlgorithmIdentifierFinder; +import org.bouncycastle.operator.DefaultSignatureAlgorithmIdentifierFinder; +import org.bouncycastle.operator.DigestAlgorithmIdentifierFinder; +import org.bouncycastle.operator.SignatureAlgorithmIdentifierFinder; + +import java.io.IOException; +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.security.PublicKey; +import java.security.Signature; +import java.security.SignatureException; +import java.security.cert.CRLException; +import java.security.cert.CertificateEncodingException; +import java.security.cert.X509CRL; +import java.security.cert.X509Certificate; +import java.security.spec.AlgorithmParameterSpec; +import java.util.Date; +import java.util.List; + +/** + * BC implementation + * + * @since 2023/06/05 + */ +public class BcSignedDataGenerator implements SignedDataGenerator { + /** + * OID of the signer identity + */ + public static final String SIGNER_OID = "1.3.6.1.4.1.2011.2.376.1.4.1"; + + private static final Logger LOGGER = LogManager.getLogger(BcSignedDataGenerator.class); + + private static final SignatureAlgorithmIdentifierFinder SIGN_ALG_ID_FINDER + = new DefaultSignatureAlgorithmIdentifierFinder(); + + private static final DigestAlgorithmIdentifierFinder DIGEST_ALG_ID_FINDER + = new DefaultDigestAlgorithmIdentifierFinder(); + + private String ownerID; + + public void setOwnerID(String ownerID) { + this.ownerID = ownerID; + } + @Override + public byte[] generateSignedData(byte[] content, SignerConfig signConfig) throws CodeSignException { + if (content == null) { + throw new CodeSignException("Verity digest is null"); + } + Pair pairDigestAndSignInfo = getSignInfo(content, signConfig); + // Unsupported certificate revocation, SignedData's _crls is null + SignedData signedData = new SignedData(new ASN1Integer(1), pairDigestAndSignInfo.getFirst(), + new ContentInfo(PKCSObjectIdentifiers.data, null), createBerSetFromLst(signConfig.getCertificates()), + createBerSetFromLst(null), pairDigestAndSignInfo.getSecond()); + return encodingUnsignedData(content, signedData); + } + + private Pair getSignInfo(byte[] content, SignerConfig signConfig) throws CodeSignException { + ASN1EncodableVector signInfoVector = new ASN1EncodableVector(); + ASN1EncodableVector digestVector = new ASN1EncodableVector(); + for (SignatureAlgorithm signAlgorithm : signConfig.getSignatureAlgorithms()) { + SignerInfo signInfo = createSignInfo(signAlgorithm, content, signConfig); + signInfoVector.add(signInfo); + digestVector.add(signInfo.getDigestAlgorithm()); + LOGGER.info("Create a sign info successfully."); + } + return Pair.create(new DERSet(digestVector), new DERSet(signInfoVector)); + } + + private SignerInfo createSignInfo(SignatureAlgorithm signAlgorithm, byte[] unsignedDataDigest, + SignerConfig signConfig) throws CodeSignException { + ContentDigestAlgorithm hashAlgorithm = signAlgorithm.getContentDigestAlgorithm(); + byte[] digest = computeDigest(unsignedDataDigest, hashAlgorithm.name()); + ASN1Set authed = getPKCS9Attributes(digest); + byte[] codeAuthed = getEncoded(authed); + Pair signPair = signAlgorithm.getSignatureAlgAndParams(); + byte[] signBytes = signConfig.getSigner().getSignature(codeAuthed, signPair.getFirst(), signPair.getSecond()); + if (signBytes == null) { + throw new CodeSignException("get signature failed"); + } + if (signConfig.getCertificates().isEmpty()) { + throw new CodeSignException("No certificates configured for sign"); + } + X509Certificate cert = signConfig.getCertificates().get(0); + if (!verifySignFromServer(cert.getPublicKey(), signBytes, signPair, codeAuthed)) { + throw new CodeSignException("verifySignatureFromServer failed"); + } + JcaX509CertificateHolder certificateHolder = getJcaX509CertificateHolder(cert); + return new SignerInfo(new ASN1Integer(1), + new IssuerAndSerialNumber(certificateHolder.getIssuer(), certificateHolder.getSerialNumber()), + DIGEST_ALG_ID_FINDER.find(hashAlgorithm.getDigestAlgorithm()), authed, + SIGN_ALG_ID_FINDER.find(signPair.getFirst()), new DEROctetString(signBytes), null); + } + + private byte[] computeDigest(byte[] unsignedDataDigest, String algorithm) throws CodeSignException { + byte[] digest; + try { + digest = DigestUtils.computeDigest(unsignedDataDigest, algorithm); + } catch (NoSuchAlgorithmException e) { + throw new CodeSignException("Invalid algorithm" + e.getMessage(), e); + } + return digest; + } + + private byte[] getEncoded(ASN1Set authed) throws CodeSignException { + byte[] codeAuthed; + try { + codeAuthed = authed.getEncoded(); + } catch (IOException e) { + throw new CodeSignException("cannot encode authed", e); + } + return codeAuthed; + } + + private JcaX509CRLHolder getJcaX509CRLHolder(X509CRL crl) throws CodeSignException { + JcaX509CRLHolder crlHolder; + try { + crlHolder = new JcaX509CRLHolder(crl); + } catch (CRLException e) { + throw new CodeSignException("Create crl failed", e); + } + return crlHolder; + } + + private JcaX509CertificateHolder getJcaX509CertificateHolder(X509Certificate cert) throws CodeSignException { + JcaX509CertificateHolder certificateHolder; + try { + certificateHolder = new JcaX509CertificateHolder(cert); + } catch (CertificateEncodingException e) { + throw new CodeSignException("Create sign info failed", e); + } + return certificateHolder; + } + + private ASN1Set getPKCS9Attributes(byte[] digest) { + ASN1EncodableVector table = new ASN1EncodableVector(); + Attribute signingTimeAttr = new Attribute(PKCSObjectIdentifiers.pkcs_9_at_signingTime, + new DERSet(new Time(new Date()))); + Attribute contentTypeAttr = new Attribute(PKCSObjectIdentifiers.pkcs_9_at_contentType, + new DERSet(PKCSObjectIdentifiers.data)); + Attribute messageDigestAttr = new Attribute(PKCSObjectIdentifiers.pkcs_9_at_messageDigest, + new DERSet(new DEROctetString(digest))); + table.add(signingTimeAttr); + table.add(contentTypeAttr); + table.add(messageDigestAttr); + if (ownerID != null) { + Attribute ownerIDAttr = new Attribute(new ASN1ObjectIdentifier(SIGNER_OID), + new DERSet(new DERUTF8String(ownerID))); + table.add(ownerIDAttr); + } + return new DERSet(table); + } + + private boolean verifySignFromServer(PublicKey publicKey, byte[] signBytes, + Pair signPair, byte[] authed) throws CodeSignException { + try { + Signature signature = Signature.getInstance(signPair.getFirst()); + signature.initVerify(publicKey); + if (signPair.getSecond() != null) { + signature.setParameter(signPair.getSecond()); + } + signature.update(authed); + if (!signature.verify(signBytes)) { + throw new CodeSignException("Signature verify failed"); + } + return true; + } catch (InvalidKeyException | SignatureException e) { + LOGGER.error("The generated signature could not be verified " + " using the public key in the certificate", + e); + } catch (NoSuchAlgorithmException e) { + LOGGER.error("The generated signature " + signPair.getFirst() + + " could not be verified using the public key in the certificate", e); + } catch (InvalidAlgorithmParameterException e) { + LOGGER.error("The generated signature " + signPair.getSecond() + + " could not be verified using the public key in the certificate", e); + } + return false; + } + + private ASN1Set createBerSetFromLst(List lists) throws CodeSignException { + if (lists == null || lists.size() == 0) { + return null; + } + ASN1EncodableVector vector = new ASN1EncodableVector(); + for (Object obj : lists) { + if (obj instanceof X509CRL) { + vector.add(getJcaX509CRLHolder((X509CRL) obj).toASN1Structure()); + } else if (obj instanceof X509Certificate) { + vector.add(getJcaX509CertificateHolder((X509Certificate) obj).toASN1Structure()); + } + } + return new BERSet(vector); + } + + private byte[] encodingUnsignedData(byte[] unsignedDataDigest, SignedData signedData) throws CodeSignException { + byte[] signResult; + try { + ContentInfo contentInfo = new ContentInfo(PKCSObjectIdentifiers.signedData, signedData); + signResult = contentInfo.getEncoded(ASN1Encoding.DER); + } catch (IOException e) { + throw new CodeSignException("failed to encode unsigned data!", e); + } + verifySignResult(unsignedDataDigest, signResult); + return signResult; + } + + private void verifySignResult(byte[] unsignedDataDigest, byte[] signResult) throws CodeSignException { + boolean result = false; + try { + result = CmsUtils.verifySignDataWithUnsignedDataDigest(unsignedDataDigest, signResult); + } catch (CMSException e) { + throw new CodeSignException("failed to verify signed data and unsigned data digest", e); + } + if (!result) { + throw new CodeSignException("PKCS cms data did not verify"); + } + } +} diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/sign/CentralDirectory.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/sign/CentralDirectory.java new file mode 100644 index 0000000000000000000000000000000000000000..e664912a75a6ad8abd5bbd0297205dd5db297405 --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/sign/CentralDirectory.java @@ -0,0 +1,176 @@ +/* + * Copyright (c) 2023-2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.ohos.hapsigntool.codesigning.sign; + +import com.ohos.hapsigntool.utils.FileUtils; + +import org.bouncycastle.util.Strings; + +import java.util.Locale; +import java.util.zip.ZipEntry; + +/** + * Central directory structure + * further reference to Zip Format + * + * @since 2023/09/14 + */ +public class CentralDirectory { + /** + * Byte size of all fields before "compression method" in central directory structure + */ + public static final int BYTE_SIZE_BEFORE_COMPRESSION_METHOD = 10; + + /** + * Byte size of all fields between "compression method" and "file comment length" in central directory structure + */ + public static final int BYTE_SIZE_BETWEEN_COMPRESSION_MODE_AND_FILE_SIZE = 16; + + /** + * Byte size of all fields between "file comment length" and + * "relative offset of local header" in central directory structure + */ + public static final int BYTE_SIZE_BETWEEN_FILE_COMMENT_LENGTH_AND_LOCHDR_RELATIVE_OFFSET = 8; + + private final char compressionMethod; + + private final char fileNameLength; + + private final char extraFieldLength; + + private final char fileCommentLength; + + private final long relativeOffsetOfLocalHeader; + + private final byte[] fileName; + + public CentralDirectory(Builder builder) { + this.compressionMethod = builder.compressionMethod; + this.fileNameLength = builder.fileNameLength; + this.extraFieldLength = builder.extraFieldLength; + this.fileCommentLength = builder.fileCommentLength; + this.relativeOffsetOfLocalHeader = builder.relativeOffsetOfLocalHeader; + this.fileName = builder.fileName; + } + + /** + * Return true if entry is an executable file, i.e. abc or so + * + * @return true if entry is an executable file + */ + public boolean isCodeFile() { + return FileUtils.isRunnableFile(this.getFileName()); + } + + /** + * Return true if zip entry is uncompressed + * + * @return true if zip entry is uncompressed + */ + public boolean isUncompressed() { + return this.compressionMethod == ZipEntry.STORED; + } + + public String getFileName() { + return Strings.fromByteArray(this.fileName); + } + + public long getRelativeOffsetOfLocalHeader() { + return relativeOffsetOfLocalHeader; + } + + /** + * Sum byte size of three variable fields: file name, extra field, file comment + * + * @return Sum byte size of three variable fields + */ + public char getFileNameLength() { + return fileNameLength; + } + + public char getExtraFieldLength() { + return extraFieldLength; + } + + /** + * Return a string representation of the object + * + * @return string representation of the object + */ + public String toString() { + return String.format(Locale.ROOT, + "CentralDirectory:compressionMode(%d), fileName(%s), relativeOffsetOfLocalHeader(%d), " + + "fileNameLength(%d), extraFieldLength(%d), fileCommentLength(%d)", (int) this.compressionMethod, + this.getFileName(), this.relativeOffsetOfLocalHeader, (int) this.fileNameLength, + (int) this.extraFieldLength, (int) this.fileCommentLength); + } + + /** + * Builder of CentralDirectory class + */ + public static class Builder { + private char compressionMethod; + + private char fileNameLength; + + private char extraFieldLength; + + private char fileCommentLength; + + private long relativeOffsetOfLocalHeader; + + private byte[] fileName; + + public Builder setCompressionMethod(char compressionMethod) { + this.compressionMethod = compressionMethod; + return this; + } + + public Builder setFileNameLength(char fileNameLength) { + this.fileNameLength = fileNameLength; + return this; + } + + public Builder setExtraFieldLength(char extraFieldLength) { + this.extraFieldLength = extraFieldLength; + return this; + } + + public Builder setFileCommentLength(char fileCommentLength) { + this.fileCommentLength = fileCommentLength; + return this; + } + + public Builder setRelativeOffsetOfLocalHeader(long relativeOffsetOfLocalHeader) { + this.relativeOffsetOfLocalHeader = relativeOffsetOfLocalHeader; + return this; + } + + public Builder setFileName(byte[] fileName) { + this.fileName = fileName; + return this; + } + + /** + * Create a CentralDirectory object + * + * @return a CentralDirectory object + */ + public CentralDirectory build() { + return new CentralDirectory(this); + } + } +} diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/sign/CodeSigning.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/sign/CodeSigning.java new file mode 100644 index 0000000000000000000000000000000000000000..cb9f3e10013763b8f89dc47e95e24121c2b766dd --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/sign/CodeSigning.java @@ -0,0 +1,427 @@ +/* + * Copyright (c) 2023-2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.ohos.hapsigntool.codesigning.sign; + +import com.ohos.hapsigntool.codesigning.datastructure.CodeSignBlock; +import com.ohos.hapsigntool.codesigning.datastructure.ElfSignBlock; +import com.ohos.hapsigntool.codesigning.datastructure.Extension; +import com.ohos.hapsigntool.codesigning.datastructure.FsVerityInfoSegment; +import com.ohos.hapsigntool.codesigning.datastructure.MerkleTreeExtension; +import com.ohos.hapsigntool.codesigning.datastructure.SignInfo; +import com.ohos.hapsigntool.codesigning.exception.CodeSignException; +import com.ohos.hapsigntool.codesigning.exception.FsVerityDigestException; +import com.ohos.hapsigntool.codesigning.fsverity.FsVerityDescriptor; +import com.ohos.hapsigntool.codesigning.fsverity.FsVerityDescriptorWithSign; +import com.ohos.hapsigntool.codesigning.fsverity.FsVerityGenerator; +import com.ohos.hapsigntool.codesigning.utils.HapUtils; +import com.ohos.hapsigntool.hap.config.SignerConfig; +import com.ohos.hapsigntool.hap.entity.Pair; +import com.ohos.hapsigntool.hap.exception.HapFormatException; +import com.ohos.hapsigntool.hap.exception.ProfileException; +import com.ohos.hapsigntool.signer.LocalSigner; +import com.ohos.hapsigntool.utils.FileUtils; +import com.ohos.hapsigntool.utils.StringUtils; +import com.ohos.hapsigntool.zip.UnsignedDecimalUtil; +import com.ohos.hapsigntool.zip.Zip; +import com.ohos.hapsigntool.zip.ZipEntryHeader; +import com.ohos.hapsigntool.zip.ZipEntry; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.List; +import java.util.Locale; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * core functions of code signing + * + * @since 2023/06/05 + */ +public class CodeSigning { + /** + * Only hap and hsp bundle supports code signing, concatenate then with '/' for verification usage + */ + public static final String SUPPORT_FILE_FORM = "hap/hsp"; + + /** + * Only elf file supports bin code signing, concatenate then with '/' for verification usage + */ + public static final String SUPPORT_BIN_FILE_FORM = "elf"; + + /** + * Defined entry name of hap file + */ + public static final String HAP_SIGNATURE_ENTRY_NAME = "Hap"; + + private static final Logger LOGGER = LogManager.getLogger(CodeSigning.class); + + private static final String NATIVE_LIB_AN_SUFFIX = ".an"; + + private static final String NATIVE_LIB_SO_SUFFIX = ".so"; + + private final List extractedNativeLibSuffixs = new ArrayList<>(); + + private final SignerConfig signConfig; + + private CodeSignBlock codeSignBlock; + + private long timestamp = 0L; + + /** + * provide code sign functions to sign a hap + * + * @param signConfig configuration of sign + */ + public CodeSigning(SignerConfig signConfig) { + this.signConfig = signConfig; + } + + /** + * Sign the given elf file, and pack all signature into output file + * + * @param input file to sign + * @param offset position of codesign block based on start of the file + * @param inForm file's format + * @param profileContent profile of the elf + * @return byte array of code sign block + * @throws CodeSignException code signing exception + * @throws IOException io error + * @throws FsVerityDigestException computing FsVerity digest error + * @throws ProfileException profile of elf is invalid + */ + public byte[] getElfCodeSignBlock(File input, long offset, String inForm, String profileContent) + throws CodeSignException, FsVerityDigestException, IOException, ProfileException { + if (!SUPPORT_BIN_FILE_FORM.contains(inForm)) { + throw new CodeSignException("file's format is unsupported"); + } + long fileSize = input.length(); + int paddingSize = ElfSignBlock.computeMerkleTreePaddingLength(offset); + long fsvTreeOffset = offset + Integer.BYTES * 2 + paddingSize; + try (FileInputStream inputStream = new FileInputStream(input)) { + FsVerityGenerator fsVerityGenerator = new FsVerityGenerator(); + fsVerityGenerator.generateFsVerityDigest(inputStream, fileSize, fsvTreeOffset); + byte[] fsVerityDigest = fsVerityGenerator.getFsVerityDigest(); + // ownerID should be DEBUG_LIB_ID while signing ELF + String ownerID = (profileContent == null) ? "DEBUG_LIB_ID" : HapUtils.getAppIdentifier(profileContent); + byte[] signature = generateSignature(fsVerityDigest, ownerID); + // add fs-verify info + FsVerityDescriptor.Builder fsdbuilder = new FsVerityDescriptor.Builder().setFileSize(fileSize) + .setHashAlgorithm(FsVerityGenerator.getFsVerityHashAlgorithm()) + .setLog2BlockSize(FsVerityGenerator.getLog2BlockSize()) + .setSaltSize((byte) fsVerityGenerator.getSaltSize()) + .setSignSize(signature.length) + .setFileSize(fileSize) + .setSalt(fsVerityGenerator.getSalt()) + .setRawRootHash(fsVerityGenerator.getRootHash()) + .setFlags(FsVerityDescriptor.FLAG_STORE_MERKLE_TREE_OFFSET) + .setMerkleTreeOffset(fsvTreeOffset) + .setCsVersion(FsVerityDescriptor.CODE_SIGN_VERSION); + FsVerityDescriptorWithSign fsVerityDescriptorWithSign = new FsVerityDescriptorWithSign(fsdbuilder.build(), + signature); + byte[] treeBytes = fsVerityGenerator.getTreeBytes(); + ElfSignBlock signBlock = new ElfSignBlock(paddingSize, treeBytes, fsVerityDescriptorWithSign); + LOGGER.info("Sign elf successfully."); + return signBlock.toByteArray(); + } + } + + /** + * Sign the given hap file, and pack all signature into output file + * + * @param input file to sign + * @param offset position of codesign block based on start of the file + * @param inForm file's format + * @param profileContent profile of the hap + * @param zip zip + * @return byte array of code sign block + * @throws CodeSignException code signing exception + * @throws IOException io error + * @throws HapFormatException hap format invalid + * @throws FsVerityDigestException computing FsVerity digest error + * @throws ProfileException profile of the hap error + */ + public byte[] getCodeSignBlock(File input, long offset, String inForm, String profileContent, Zip zip) + throws CodeSignException, IOException, HapFormatException, FsVerityDigestException, ProfileException { + LOGGER.info("Start to sign code."); + if (!SUPPORT_FILE_FORM.contains(inForm)) { + throw new CodeSignException("file's format is unsupported"); + } + long dataSize = computeDataSize(zip); + timestamp = System.currentTimeMillis(); + // generate CodeSignBlock + this.codeSignBlock = new CodeSignBlock(); + // compute merkle tree offset, replace with computeMerkleTreeOffset if fs-verity descriptor supports + long fsvTreeOffset = this.codeSignBlock.computeMerkleTreeOffset(offset); + // update fs-verity segment + FsVerityInfoSegment fsVerityInfoSegment = new FsVerityInfoSegment(FsVerityDescriptor.VERSION, + FsVerityGenerator.getFsVerityHashAlgorithm(), FsVerityGenerator.getLog2BlockSize()); + this.codeSignBlock.setFsVerityInfoSegment(fsVerityInfoSegment); + + LOGGER.debug("Sign hap."); + String ownerID = HapUtils.getAppIdentifier(profileContent); + + try (FileInputStream inputStream = new FileInputStream(input)) { + Pair hapSignInfoAndMerkleTreeBytesPair = signFile(inputStream, dataSize, true, + fsvTreeOffset, ownerID); + // update hap segment in CodeSignBlock + this.codeSignBlock.getHapInfoSegment().setSignInfo(hapSignInfoAndMerkleTreeBytesPair.getFirst()); + // Insert merkle tree bytes into code sign block + this.codeSignBlock.addOneMerkleTree(HAP_SIGNATURE_ENTRY_NAME, + hapSignInfoAndMerkleTreeBytesPair.getSecond()); + } + // update native lib info segment in CodeSignBlock + signNativeLibs(input, ownerID); + + // last update codeSignBlock before generating its byte array representation + updateCodeSignBlock(this.codeSignBlock); + + // complete code sign block byte array here + byte[] generated = this.codeSignBlock.generateCodeSignBlockByte(fsvTreeOffset); + LOGGER.info("Sign successfully."); + return generated; + } + + private long computeDataSize(Zip zip) throws HapFormatException { + long dataSize = 0L; + for (ZipEntry entry : zip.getZipEntries()) { + ZipEntryHeader zipEntryHeader = entry.getZipEntryData().getZipEntryHeader(); + if (FileUtils.isRunnableFile(zipEntryHeader.getFileName()) + && zipEntryHeader.getMethod() == Zip.FILE_UNCOMPRESS_METHOD_FLAG) { + continue; + } + // if the first file is not uncompressed abc or so, set dataSize to zero + if (entry.getCentralDirectory().getOffset() == 0) { + break; + } + // the first entry which is not abc/so/an is found, return its data offset + dataSize = entry.getCentralDirectory().getOffset() + ZipEntryHeader.HEADER_LENGTH + + zipEntryHeader.getFileNameLength() + zipEntryHeader.getExtraLength(); + break; + } + if ((dataSize % CodeSignBlock.PAGE_SIZE_4K) != 0) { + throw new HapFormatException( + String.format(Locale.ROOT, "Invalid dataSize(%d), not a multiple of 4096", dataSize)); + } + return dataSize; + } + + private void signNativeLibs(File input, String ownerID) throws IOException, FsVerityDigestException, + CodeSignException { + // 'an' libs are always signed + extractedNativeLibSuffixs.add(NATIVE_LIB_AN_SUFFIX); + // 'so' libs are always signed + extractedNativeLibSuffixs.add(NATIVE_LIB_SO_SUFFIX); + + // sign native files + try (JarFile inputJar = new JarFile(input, false)) { + List entryNames = getNativeEntriesFromHap(inputJar); + if (entryNames.isEmpty()) { + LOGGER.info("No native libs."); + return; + } + List> nativeLibInfoList = signFilesFromJar(entryNames, inputJar, ownerID); + // update SoInfoSegment in CodeSignBlock + this.codeSignBlock.getSoInfoSegment().setSoInfoList(nativeLibInfoList); + } + } + + /** + * Get entry name of all native files in hap + * + * @param hap the given hap + * @return list of entry name + */ + private List getNativeEntriesFromHap(JarFile hap) { + List result = new ArrayList<>(); + for (Enumeration e = hap.entries(); e.hasMoreElements();) { + JarEntry entry = e.nextElement(); + if (!entry.isDirectory()) { + if (!isNativeFile(entry.getName())) { + continue; + } + result.add(entry.getName()); + } + } + return result; + } + + /** + * Check whether the entry is a native file + * + * @param entryName the name of entry + * @return true if it is a native file, and false otherwise + */ + private boolean isNativeFile(String entryName) { + if (StringUtils.isEmpty(entryName)) { + return false; + } + if (entryName.endsWith(NATIVE_LIB_AN_SUFFIX)) { + return true; + } + if (extractedNativeLibSuffixs.contains(NATIVE_LIB_SO_SUFFIX)) { + Pattern pattern = FileUtils.SUFFIX_REGEX_MAP.get("so"); + Matcher matcher = pattern.matcher(entryName); + if (matcher.find()) { + return true; + } + } + return false; + } + + /** + * Sign specific entries in a hap + * + * @param entryNames list of entries which need to be signed + * @param hap input hap + * @param ownerID app-id in signature to identify + * @return sign info and merkle tree of each file + * @throws IOException io error + * @throws FsVerityDigestException computing FsVerity digest error + * @throws CodeSignException sign error + */ + private List> signFilesFromJar(List entryNames, JarFile hap, String ownerID) + throws IOException, FsVerityDigestException, CodeSignException { + List> nativeLibInfoList = new ArrayList<>(); + for (String name : entryNames) { + LOGGER.debug("Sign entry name = " + name); + JarEntry inEntry = hap.getJarEntry(name); + try (InputStream inputStream = hap.getInputStream(inEntry)) { + long fileSize = inEntry.getSize(); + // We don't store merkle tree in code signing of native libs + // Therefore, the second value of pair returned is ignored + Pair pairSignInfoAndMerkleTreeBytes = signFile(inputStream, fileSize, + false, 0, ownerID); + nativeLibInfoList.add(Pair.create(name, pairSignInfoAndMerkleTreeBytes.getFirst())); + } + } + return nativeLibInfoList; + } + + /** + * Sign a file from input stream + * + * @param inputStream input stream of a file + * @param fileSize size of the file + * @param storeTree whether to store merkle tree in signed info + * @param fsvTreeOffset merkle tree raw bytes offset based on the start of file + * @param ownerID app-id in signature to identify + * @return pair of signature and tree + * @throws FsVerityDigestException computing FsVerity Digest error + * @throws CodeSignException signing error + */ + public Pair signFile(InputStream inputStream, long fileSize, boolean storeTree, + long fsvTreeOffset, String ownerID) throws FsVerityDigestException, CodeSignException { + FsVerityGenerator fsVerityGenerator = new FsVerityGenerator(); + fsVerityGenerator.generateFsVerityDigest(inputStream, fileSize, fsvTreeOffset); + byte[] fsVerityDigest = fsVerityGenerator.getFsVerityDigest(); + byte[] signature = generateSignature(fsVerityDigest, ownerID); + int flags = 0; + if (storeTree) { + flags = SignInfo.FLAG_MERKLE_TREE_INCLUDED; + } + SignInfo signInfo = new SignInfo(fsVerityGenerator.getSaltSize(), flags, fileSize, fsVerityGenerator.getSalt(), + signature); + // if store merkle tree in sign info + if (storeTree) { + int merkleTreeSize = fsVerityGenerator.getTreeBytes() == null ? 0 : fsVerityGenerator.getTreeBytes().length; + Extension merkleTreeExtension = new MerkleTreeExtension(merkleTreeSize, fsvTreeOffset, + fsVerityGenerator.getRootHash()); + signInfo.addExtension(merkleTreeExtension); + } + return Pair.create(signInfo, fsVerityGenerator.getTreeBytes()); + } + + private byte[] generateSignature(byte[] signedData, String ownerID) throws CodeSignException { + // signConfig is created by SignerFactory + if ((signConfig.getSigner() instanceof LocalSigner)) { + if (signConfig.getCertificates().isEmpty()) { + throw new CodeSignException("No certificates configured for sign"); + } + } + + BcSignedDataGenerator bcSignedDataGenerator = new BcSignedDataGenerator(); + bcSignedDataGenerator.setOwnerID(ownerID); + return bcSignedDataGenerator.generateSignedData(signedData, signConfig); + } + + /** + * At here, segment header, fsverity info/hap/so info segment, merkle tree + * segment should all be generated. + * code sign block size, segment number, offset is not updated. + * Try to update whatever could be updated here. + * + * @param codeSignBlock CodeSignBlock + */ + private void updateCodeSignBlock(CodeSignBlock codeSignBlock) { + // construct segment header list + codeSignBlock.setSegmentHeaders(); + // Compute and set segment number + codeSignBlock.setSegmentNum(); + // update code sign block header flag + codeSignBlock.setCodeSignBlockFlag(); + // compute segment offset + codeSignBlock.computeSegmentOffset(); + } + + private List parseCentralDirectory(byte[] buffer, int count) { + List cdList = new ArrayList<>(); + ByteBuffer cdBuffer = ByteBuffer.allocate(buffer.length).order(ByteOrder.LITTLE_ENDIAN); + cdBuffer.put(buffer); + cdBuffer.rewind(); + for (int i = 0; i < count; i++) { + byte[] bytesBeforeCompressionMethod = new byte[CentralDirectory.BYTE_SIZE_BEFORE_COMPRESSION_METHOD]; + cdBuffer.get(bytesBeforeCompressionMethod); + char compressionMode = cdBuffer.getChar(); + CentralDirectory.Builder builder = new CentralDirectory.Builder().setCompressionMethod(compressionMode); + byte[] bytesBetweenCmprMethodAndFileNameLength = + new byte[CentralDirectory.BYTE_SIZE_BETWEEN_COMPRESSION_MODE_AND_FILE_SIZE]; + cdBuffer.get(bytesBetweenCmprMethodAndFileNameLength); + char fileNameLength = cdBuffer.getChar(); + char extraFieldLength = cdBuffer.getChar(); + char fileCommentLength = cdBuffer.getChar(); + byte[] attributes = + new byte[CentralDirectory.BYTE_SIZE_BETWEEN_FILE_COMMENT_LENGTH_AND_LOCHDR_RELATIVE_OFFSET]; + cdBuffer.get(attributes); + long locHdrOffset = UnsignedDecimalUtil.getUnsignedInt(cdBuffer); + builder.setFileNameLength(fileNameLength).setExtraFieldLength(extraFieldLength) + .setFileCommentLength(fileCommentLength).setRelativeOffsetOfLocalHeader(locHdrOffset); + byte[] fileNameBuffer = new byte[fileNameLength]; + cdBuffer.get(fileNameBuffer); + if (extraFieldLength != 0) { + cdBuffer.get(new byte[extraFieldLength]); + } + if (fileCommentLength != 0) { + cdBuffer.get(new byte[fileCommentLength]); + } + CentralDirectory cd = builder.setFileName(fileNameBuffer).build(); + cdList.add(cd); + } + + return cdList; + } +} diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/sign/SignedDataGenerator.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/sign/SignedDataGenerator.java new file mode 100644 index 0000000000000000000000000000000000000000..03d1b2bb4b52d5ccb24ef31c2369e5aa37cd0368 --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/sign/SignedDataGenerator.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2023-2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.ohos.hapsigntool.codesigning.sign; + +import com.ohos.hapsigntool.codesigning.exception.CodeSignException; +import com.ohos.hapsigntool.hap.config.SignerConfig; + +/** + * Signed data generator interface + * + * @since 2023/06/05 + */ +@FunctionalInterface +public interface SignedDataGenerator { + /** + * Create a BcSignedDataGenerator instance + */ + SignedDataGenerator BC = new BcSignedDataGenerator(); + + /** + * Generate signature data with specific content and sign configuration. + * + * @param content unsigned file digest content. + * @param signerConfig sign configurations. + * @return signed data. + * @throws CodeSignException if error. + */ + byte[] generateSignedData(byte[] content, SignerConfig signerConfig) throws CodeSignException; +} diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/sign/VerifyCodeSignature.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/sign/VerifyCodeSignature.java new file mode 100644 index 0000000000000000000000000000000000000000..3afd589518fd1478439a37f90fc67eacbd28eef5 --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/sign/VerifyCodeSignature.java @@ -0,0 +1,321 @@ +/* + * Copyright (c) 2023-2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.ohos.hapsigntool.codesigning.sign; + +import com.ohos.hapsigntool.codesigning.datastructure.CodeSignBlock; +import com.ohos.hapsigntool.codesigning.datastructure.CodeSignBlockHeader; +import com.ohos.hapsigntool.codesigning.datastructure.ElfSignBlock; +import com.ohos.hapsigntool.codesigning.datastructure.Extension; +import com.ohos.hapsigntool.codesigning.datastructure.FsVerityInfoSegment; +import com.ohos.hapsigntool.codesigning.datastructure.HapInfoSegment; +import com.ohos.hapsigntool.codesigning.datastructure.MerkleTreeExtension; +import com.ohos.hapsigntool.codesigning.datastructure.NativeLibInfoSegment; +import com.ohos.hapsigntool.codesigning.datastructure.SegmentHeader; +import com.ohos.hapsigntool.codesigning.exception.FsVerityDigestException; +import com.ohos.hapsigntool.codesigning.exception.VerifyCodeSignException; +import com.ohos.hapsigntool.codesigning.fsverity.FsVerityGenerator; +import com.ohos.hapsigntool.codesigning.utils.CmsUtils; +import com.ohos.hapsigntool.codesigning.utils.HapUtils; +import com.ohos.hapsigntool.hap.entity.Pair; +import com.ohos.hapsigntool.hap.exception.ProfileException; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.bouncycastle.asn1.ASN1ObjectIdentifier; +import org.bouncycastle.asn1.cms.Attribute; +import org.bouncycastle.asn1.cms.AttributeTable; +import org.bouncycastle.cms.CMSException; +import org.bouncycastle.cms.CMSSignedData; +import org.bouncycastle.cms.SignerInformation; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.Arrays; +import java.util.Collection; +import java.util.Locale; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; + +/** + * Verify code signature given a file with code sign block + * + * @since 2023/09/08 + */ +public class VerifyCodeSignature { + private static final Logger LOGGER = LogManager.getLogger(VerifyCodeSignature.class); + + private static void checkOwnerID(byte[] signature, String profileOwnerID, String profileType) + throws CMSException, VerifyCodeSignException { + String ownerID = profileOwnerID; + // if profileType is debug, check the app-id in signature, should be null or DEBUG_LIB_ID + if ("debug".equals(profileType)) { + ownerID = "DEBUG_LIB_ID"; + } + + CMSSignedData cmsSignedData = new CMSSignedData(signature); + Collection signers = cmsSignedData.getSignerInfos().getSigners(); + Collection results = null; + for (SignerInformation signer : signers) { + AttributeTable attrTable = signer.getSignedAttributes(); + Attribute attr = attrTable.get(new ASN1ObjectIdentifier(BcSignedDataGenerator.SIGNER_OID)); + // if app-id is null, if profileType is debug, it's ok. + if (attr == null) { + if ("debug".equals(profileType)) { + continue; + } + if (ownerID == null) { + continue; + } else { + throw new VerifyCodeSignException("app-identifier is not in the signature"); + } + } + if (ownerID == null) { + throw new VerifyCodeSignException("app-identifier in profile is null, but is not null in signature"); + } + // if app-id in signature exists, it should be equal to the app-id in profile. + String resultOwnerID = attr.getAttrValues().getObjectAt(0).toString(); + if (!ownerID.equals(resultOwnerID)) { + throw new VerifyCodeSignException("app-identifier in signature is invalid"); + } + } + } + + /** + * Verify a signed elf's signature + * + * @param file signed elf file + * @param offset start position of code sign block based on the start of the elf file + * @param length byte size of code sign block + * @param fileFormat elf or hqf or hsp, etc. + * @param profileContent profile json string + * @return true if signature verify succeed and false otherwise + * @throws IOException If an input or output exception occurred + * @throws VerifyCodeSignException parsing result invalid + * @throws FsVerityDigestException if fs-verity digest generation failed + * @throws CMSException if signature verify failed + * @throws ProfileException if verify profile failed + */ + public static boolean verifyElf(File file, long offset, long length, String fileFormat, String profileContent) + throws IOException, VerifyCodeSignException, FsVerityDigestException, CMSException, ProfileException { + if (!CodeSigning.SUPPORT_BIN_FILE_FORM.contains(fileFormat)) { + LOGGER.info("Not elf file, skip code signing verify"); + return true; + } + // 1) parse sign block to ElfCodeSignBlock object + ElfSignBlock elfSignBlock; + try (FileInputStream signedElf = new FileInputStream(file)) { + byte[] codeSignBlockBytes = new byte[(int) length]; + signedElf.skip(offset); + signedElf.read(codeSignBlockBytes); + elfSignBlock = ElfSignBlock.fromByteArray(codeSignBlockBytes); + } + // 2) verify file data + try (FileInputStream signedElf = new FileInputStream(file)) { + int paddingSize = ElfSignBlock.computeMerkleTreePaddingLength(offset); + byte[] merkleTreeWithPadding = elfSignBlock.getMerkleTreeWithPadding(); + byte[] merkleTree = Arrays.copyOfRange(merkleTreeWithPadding, paddingSize, merkleTreeWithPadding.length); + verifySingleFile(signedElf, elfSignBlock.getDataSize(), elfSignBlock.getSignature(), + elfSignBlock.getTreeOffset(), merkleTree); + } + if (profileContent != null) { + Pair pairResult = HapUtils.parseAppIdentifier(profileContent); + checkOwnerID(elfSignBlock.getSignature(), pairResult.getFirst(), pairResult.getSecond()); + } + return true; + } + + /** + * Verify a signed hap's signature + * + * @param file signed hap file + * @param offset start position of code sign block based on the start of the hap file + * @param length byte size of code sign block + * @param fileFormat hap or hqf or hsp, etc. + * @param profileContent profile of the hap + * @return true if signature verify succeed and false otherwise + * @throws IOException If an input or output exception occurred + * @throws VerifyCodeSignException parsing result invalid + * @throws FsVerityDigestException if fs-verity digest generation failed + * @throws CMSException if signature verify failed + * @throws ProfileException profile of the hap failed + */ + public static boolean verifyHap(File file, long offset, long length, String fileFormat, String profileContent) + throws IOException, VerifyCodeSignException, FsVerityDigestException, CMSException, ProfileException { + if (!CodeSigning.SUPPORT_FILE_FORM.contains(fileFormat)) { + LOGGER.info("Not hap or hsp file, skip code signing verify"); + return true; + } + Pair pairResult = HapUtils.parseAppIdentifier(profileContent); + + CodeSignBlock csb = generateCodeSignBlock(file, offset, length); + // 2) verify hap + try (FileInputStream hap = new FileInputStream(file)) { + long dataSize = csb.getHapInfoSegment().getSignInfo().getDataSize(); + byte[] signature = csb.getHapInfoSegment().getSignInfo().getSignature(); + Extension extension = csb.getHapInfoSegment() + .getSignInfo() + .getExtensionByType(MerkleTreeExtension.MERKLE_TREE_INLINED); + MerkleTreeExtension mte = new MerkleTreeExtension(0, 0, null); + if (extension instanceof MerkleTreeExtension) { + mte = (MerkleTreeExtension) extension; + } + // temporary: merkle tree offset set to zero, change to merkleTreeOffset + verifySingleFile(hap, dataSize, signature, mte.getMerkleTreeOffset(), + csb.getOneMerkleTreeByFileName(CodeSigning.HAP_SIGNATURE_ENTRY_NAME)); + checkOwnerID(signature, pairResult.getFirst(), pairResult.getSecond()); + } + // 3) verify native libs + try (JarFile inputJar = new JarFile(file, false)) { + for (int i = 0; i < csb.getSoInfoSegment().getSectionNum(); i++) { + String entryName = csb.getSoInfoSegment().getFileNameList().get(i); + byte[] entrySig = csb.getSoInfoSegment().getSignInfoList().get(i).getSignature(); + JarEntry entry = inputJar.getJarEntry(entryName); + if (entry.getSize() != csb.getSoInfoSegment().getSignInfoList().get(i).getDataSize()) { + throw new VerifyCodeSignException( + String.format(Locale.ROOT, "Invalid dataSize of native lib %s", entryName)); + } + InputStream entryInputStream = inputJar.getInputStream(entry); + // temporary merkleTreeOffset 0 + verifySingleFile(entryInputStream, entry.getSize(), entrySig, 0, null); + checkOwnerID(entrySig, pairResult.getFirst(), pairResult.getSecond()); + } + } + return true; + } + + private static CodeSignBlock generateCodeSignBlock(File file, long offset, long length) + throws IOException, VerifyCodeSignException { + CodeSignBlock csb = new CodeSignBlock(); + // 1) parse sign block to CodeSignBlock object + try (FileInputStream signedHap = new FileInputStream(file)) { + int fileReadOffset = 0; + // 0) skip data part, but fileReadOffset remains at start(0) + signedHap.skip(offset); + // 1) parse codeSignBlockHeader + byte[] codeSignBlockHeaderByteArray = new byte[CodeSignBlockHeader.size()]; + fileReadOffset += signedHap.read(codeSignBlockHeaderByteArray); + csb.setCodeSignBlockHeader(CodeSignBlockHeader.fromByteArray(codeSignBlockHeaderByteArray)); + if (csb.getCodeSignBlockHeader().getBlockSize() != length) { + throw new VerifyCodeSignException("Invalid code Sign block size of setCodeSignBlockHeader"); + } + // 2) parse segment headers + for (int i = 0; i < csb.getCodeSignBlockHeader().getSegmentNum(); i++) { + byte[] segmentHeaderByteArray = new byte[SegmentHeader.SEGMENT_HEADER_LENGTH]; + fileReadOffset += signedHap.read(segmentHeaderByteArray); + csb.addToSegmentList(SegmentHeader.fromByteArray(segmentHeaderByteArray)); + } + // compute merkle tree offset by alignment, based on file start + long computedTreeOffset = getAlignmentAddr(CodeSignBlock.PAGE_SIZE_4K, fileReadOffset + offset); + // skip zero padding before merkle tree, adds zero padding length to fileReadOffset + fileReadOffset += signedHap.skip(computedTreeOffset - offset - fileReadOffset); + parseMerkleTree(csb, fileReadOffset, signedHap, computedTreeOffset); + } + return csb; + } + + private static void parseMerkleTree(CodeSignBlock csb, int readOffset, FileInputStream signedHap, + long computedTreeOffset) throws VerifyCodeSignException, IOException { + // check segment offset and segment size + byte[] merkleTreeBytes = new byte[0]; + int fileReadOffset = readOffset; + for (SegmentHeader segmentHeader : csb.getSegmentHeaderList()) { + if (fileReadOffset > segmentHeader.getSegmentOffset()) { + throw new VerifyCodeSignException("Invaild offset of merkle tree and segment header"); + } + // get merkle tree bytes + if (fileReadOffset < segmentHeader.getSegmentOffset()) { + merkleTreeBytes = new byte[segmentHeader.getSegmentOffset() - fileReadOffset]; + fileReadOffset += signedHap.read(merkleTreeBytes); + } + byte[] sh = new byte[segmentHeader.getSegmentSize()]; + fileReadOffset += signedHap.read(sh); + if (segmentHeader.getType() == SegmentHeader.CSB_FSVERITY_INFO_SEG) { + // 3) parse fs-verity info segment + csb.setFsVerityInfoSegment(FsVerityInfoSegment.fromByteArray(sh)); + } else if (segmentHeader.getType() == SegmentHeader.CSB_HAP_META_SEG) { + // 4) parse hap info segment + csb.setHapInfoSegment(HapInfoSegment.fromByteArray(sh)); + } else if (segmentHeader.getType() == SegmentHeader.CSB_NATIVE_LIB_INFO_SEG) { + // 5) parse so info segment + csb.setSoInfoSegment(NativeLibInfoSegment.fromByteArray(sh)); + } + } + if (fileReadOffset != csb.getCodeSignBlockHeader().getBlockSize()) { + throw new VerifyCodeSignException("Invalid blockSize of getCodeSignBlockHeader"); + } + // parse merkle tree + Extension extension = csb.getHapInfoSegment() + .getSignInfo() + .getExtensionByType(MerkleTreeExtension.MERKLE_TREE_INLINED); + if (extension == null) { + throw new VerifyCodeSignException("Missing merkleTreeExtension in verifycation"); + } + if (extension instanceof MerkleTreeExtension) { + MerkleTreeExtension mte = (MerkleTreeExtension) extension; + if (computedTreeOffset != mte.getMerkleTreeOffset()) { + throw new VerifyCodeSignException("Invalid merkle tree offset"); + } + if (merkleTreeBytes.length != mte.getMerkleTreeSize()) { + throw new VerifyCodeSignException("Invalid merkle tree size"); + } + csb.addOneMerkleTree(CodeSigning.HAP_SIGNATURE_ENTRY_NAME, merkleTreeBytes); + } + } + + private static long getAlignmentAddr(long alignment, long input) { + long residual = input % alignment; + if (residual == 0) { + return input; + } else { + return input + (alignment - residual); + } + } + + /** + * Verifies the signature of a given file with its signature in pkcs#7 format + * + * @param input file being verified in InputStream representation + * @param length size of signed data in the file + * @param signature byte array of signature in pkcs#7 format + * @param merkleTreeOffset merkle tree offset based on file start + * @param inMerkleTreeBytes merkle tree raw bytes + * @throws FsVerityDigestException if fs-verity digest generation failed + * @throws CMSException if signature verify failed + * @throws VerifyCodeSignException parsing code sign block error + */ + public static void verifySingleFile(InputStream input, long length, byte[] signature, long merkleTreeOffset, + byte[] inMerkleTreeBytes) throws FsVerityDigestException, CMSException, VerifyCodeSignException { + Pair pairResult = generateFsVerityDigest(input, length, merkleTreeOffset); + byte[] generatedMerkleTreeBytes = pairResult.getSecond(); + if (generatedMerkleTreeBytes == null) { + generatedMerkleTreeBytes = new byte[0]; + } + // For native libs, inMerkleTreeBytes is null, skip check here + if ((inMerkleTreeBytes != null) && !Arrays.equals(inMerkleTreeBytes, generatedMerkleTreeBytes)) { + throw new VerifyCodeSignException("verify merkle tree bytes failed"); + } + CmsUtils.verifySignDataWithUnsignedDataDigest(pairResult.getFirst(), signature); + } + + private static Pair generateFsVerityDigest(InputStream inputStream, long size, + long merkleTreeOffset) throws FsVerityDigestException { + FsVerityGenerator fsVerityGenerator = new FsVerityGenerator(); + fsVerityGenerator.generateFsVerityDigest(inputStream, size, merkleTreeOffset); + return Pair.create(fsVerityGenerator.getFsVerityDigest(), fsVerityGenerator.getTreeBytes()); + } +} diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/utils/CmsUtils.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/utils/CmsUtils.java new file mode 100644 index 0000000000000000000000000000000000000000..df4b11a7d18f1de22400abd3c26d36f17946ec53 --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/utils/CmsUtils.java @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2023-2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.ohos.hapsigntool.codesigning.utils; + +import org.bouncycastle.cert.X509CertificateHolder; +import org.bouncycastle.cms.CMSException; +import org.bouncycastle.cms.CMSProcessableByteArray; +import org.bouncycastle.cms.CMSSignedData; +import org.bouncycastle.cms.jcajce.JcaSimpleSignerInfoVerifierBuilder; +import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.bouncycastle.operator.OperatorCreationException; + +import java.security.Security; +import java.security.cert.CertificateException; +import java.util.Collection; + +/** + * CMS utils class + * + * @since 2023/06/05 + */ +public class CmsUtils { + static { + if (Security.getProvider("BC") == null) { + Security.addProvider(new BouncyCastleProvider()); + } + } + + /** + * Private constructor + */ + private CmsUtils() { + } + + private static void isCollectionValid(Collection collection) + throws OperatorCreationException { + if (collection == null) { + throw new OperatorCreationException("No matched cert: " + collection); + } + if (collection.size() != 1) { + throw new OperatorCreationException( + "More than one matched certs, matched certs size: " + collection.size()); + } + } + + @SuppressWarnings("unchecked") + private static boolean verifyCmsSignedData(CMSSignedData cmsSignedData) throws CMSException { + return cmsSignedData.verifySignatures(signId -> { + Collection collection = cmsSignedData.getCertificates().getMatches(signId); + isCollectionValid(collection); + X509CertificateHolder cert = collection.iterator().next(); + try { + return new JcaSimpleSignerInfoVerifierBuilder().setProvider("BC").build(cert); + } catch (CertificateException e) { + throw new OperatorCreationException("Verify BC signatures failed: " + e.getMessage(), e); + } + }); + } + + /** + * Verify signed data using an unsigned data digest + * + * @param unsignedDataDigest unsigned data digest + * @param signedData signed data + * @return true if verify success + * @throws CMSException if error + */ + public static boolean verifySignDataWithUnsignedDataDigest(byte[] unsignedDataDigest, byte[] signedData) + throws CMSException { + CMSSignedData cmsSignedData = new CMSSignedData(new CMSProcessableByteArray(unsignedDataDigest), signedData); + return verifyCmsSignedData(cmsSignedData); + } +} diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/utils/DigestUtils.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/utils/DigestUtils.java new file mode 100644 index 0000000000000000000000000000000000000000..aafbaf577149d08d65b1f4052d54ed1e5836bd0b --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/utils/DigestUtils.java @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2023-2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.ohos.hapsigntool.codesigning.utils; + +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; + +/** + * Digest util class + * + * @since 2023/06/05 + */ +public class DigestUtils { + /** + * digest the inputContent with specific algorithm + * + * @param inputContentArray input Content Array + * @param algorithm hash algorithm + * @return the result of digest, is a byte array + * @throws NoSuchAlgorithmException if error + */ + public static byte[] computeDigest(byte[] inputContentArray, String algorithm) throws NoSuchAlgorithmException { + MessageDigest md = MessageDigest.getInstance(algorithm); + md.update(inputContentArray); + return md.digest(); + } +} diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/utils/HapUtils.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/utils/HapUtils.java new file mode 100644 index 0000000000000000000000000000000000000000..6d92964a6acc31f27f53044e463949cd98b616b5 --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/utils/HapUtils.java @@ -0,0 +1,183 @@ +/* + * Copyright (c) 2023-2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.ohos.hapsigntool.codesigning.utils; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import com.google.gson.JsonSyntaxException; +import com.ohos.hapsigntool.hap.entity.Pair; +import com.ohos.hapsigntool.hap.exception.ProfileException; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Queue; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; + +/** + * utility for check hap configs + * + * @since 2023/06/05 + */ +public class HapUtils { + private static final Logger LOGGER = LogManager.getLogger(HapUtils.class); + + private static final String COMPRESS_NATIVE_LIBS_OPTION = "compressNativeLibs"; + + private static final List HAP_CONFIG_FILES = new ArrayList<>(); + + private static final String HAP_FA_CONFIG_JSON_FILE = "config.json"; + + private static final String HAP_STAGE_MODULE_JSON_FILE = "module.json"; + + private static final String HAP_DEBUG_OWNER_ID = "DEBUG_LIB_ID"; + + private static final int MAX_APP_ID_LEN = 32; // max app-identifier in profile + + static { + HAP_CONFIG_FILES.add(HAP_FA_CONFIG_JSON_FILE); + HAP_CONFIG_FILES.add(HAP_STAGE_MODULE_JSON_FILE); + } + + private HapUtils() { + } + + /** + * Check configuration in hap to find out whether the native libs are compressed + * + * @param hapFile the given hap + * @return boolean value of parsing result + * @throws IOException io error + */ + public static boolean checkCompressNativeLibs(File hapFile) throws IOException { + try (JarFile inputJar = new JarFile(hapFile, false)) { + for (String configFile : HAP_CONFIG_FILES) { + JarEntry entry = inputJar.getJarEntry(configFile); + if (entry == null) { + continue; + } + try (InputStream data = inputJar.getInputStream(entry)) { + String jsonString = new String(InputStreamUtils.toByteArray(data, (int) entry.getSize()), + StandardCharsets.UTF_8); + return checkCompressNativeLibs(jsonString); + } + } + } + return true; + } + + /** + * Check whether the native libs are compressed by parsing config json + * + * @param jsonString the config json string + * @return boolean value of parsing result + */ + public static boolean checkCompressNativeLibs(String jsonString) { + JsonObject jsonObject = JsonParser.parseString(jsonString).getAsJsonObject(); + Queue queue = new LinkedList<>(); + queue.offer(jsonObject); + while (queue.size() > 0) { + JsonObject curJsonObject = queue.poll(); + JsonElement jsonElement = curJsonObject.get(COMPRESS_NATIVE_LIBS_OPTION); + if (jsonElement != null) { + return jsonElement.getAsBoolean(); + } + for (Map.Entry entry : curJsonObject.entrySet()) { + if (entry.getValue().isJsonObject()) { + queue.offer(entry.getValue().getAsJsonObject()); + } + } + } + // default to compress native libs + return true; + } + + /** + * get app-id from profile + * + * @param profileContent the content of profile + * @return string value of app-id + * @throws ProfileException profile is invalid + */ + public static String getAppIdentifier(String profileContent) throws ProfileException { + Pair resultPair = parseAppIdentifier(profileContent); + String ownerID = resultPair.getFirst(); + String profileType = resultPair.getSecond(); + if ("debug".equals(profileType)) { + return HAP_DEBUG_OWNER_ID; + } else if ("release".equals(profileType)) { + return ownerID; + } else { + throw new ProfileException("unsupported profile type"); + } + } + + /** + * parse app-id and profileType from profile + * + * @param profileContent the content of profile + * @return Pair value of app-id and profileType + * @throws ProfileException profile is invalid + */ + public static Pair parseAppIdentifier(String profileContent) throws ProfileException { + String ownerID = null; + String profileType = null; + try { + JsonElement parser = JsonParser.parseString(profileContent); + JsonObject profileJson = parser.getAsJsonObject(); + String profileTypeKey = "type"; + if (!profileJson.has(profileTypeKey)) { + throw new ProfileException("profile has no type key"); + } + + profileType = profileJson.get(profileTypeKey).getAsString(); + if (profileType == null || profileType.length() == 0) { + throw new ProfileException("Get profile type error"); + } + + String appIdentifier = "app-identifier"; + String buildInfoMember = "bundle-info"; + JsonObject buildInfoObject = profileJson.getAsJsonObject(buildInfoMember); + if (buildInfoObject == null) { + throw new ProfileException("can not find bundle-info"); + } + if (buildInfoObject.has(appIdentifier)) { + JsonElement ownerIDElement = buildInfoObject.get(appIdentifier); + if (!ownerIDElement.getAsJsonPrimitive().isString()) { + throw new ProfileException("value of app-identifier is not string"); + } + ownerID = ownerIDElement.getAsString(); + if (ownerID.isEmpty() || ownerID.length() > MAX_APP_ID_LEN) { + throw new ProfileException("app-id length in profile is invalid"); + } + + } + } catch (JsonSyntaxException | UnsupportedOperationException e) { + LOGGER.error(e.getMessage()); + throw new ProfileException("profile json is invalid"); + } + return Pair.create(ownerID, profileType); + } +} diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/utils/InputStreamUtils.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/utils/InputStreamUtils.java new file mode 100644 index 0000000000000000000000000000000000000000..816ea38c1138737404e0bdadab2e196c5b8d3cfd --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/utils/InputStreamUtils.java @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2023-2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.ohos.hapsigntool.codesigning.utils; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +/** + * InputStream util class + * + * @since 2023/08/10 + */ +public class InputStreamUtils { + private static final int BUFFER_SIZE = 4096; + + /** + * get byte array by inputStream and size + * + * @param inputStream inputStream data + * @param inputStreamSize inputStream size + * @return byte array value + * @throws IOException io error + */ + public static byte[] toByteArray(InputStream inputStream, int inputStreamSize) throws IOException { + if (inputStreamSize == 0) { + return new byte[0]; + } + if (inputStreamSize < 0) { + throw new IllegalArgumentException("inputStreamSize: " + inputStreamSize + "is less than zero: "); + } + ByteArrayOutputStream output = new ByteArrayOutputStream(); + copy(inputStream, inputStreamSize, output); + return output.toByteArray(); + } + + private static int copy(InputStream inputStream, int inputStreamSize, OutputStream output) throws IOException { + byte[] buffer = new byte[BUFFER_SIZE]; + int readSize = 0; + int count = 0; + while (readSize < inputStreamSize && (readSize = inputStream.read(buffer)) != -1) { + output.write(buffer, 0, readSize); + count += readSize; + } + if (count != inputStreamSize) { + throw new IOException("read size err. readSizeCount: " + count + ", inputStreamSize: " + inputStreamSize); + } + return count; + } +} diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/error/ERROR.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/error/ERROR.java index 286031eef28f9834822ad4c033407b001f43f3fd..2f2627faa8bcc285f2e648dde255108359461151 100644 --- a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/error/ERROR.java +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/error/ERROR.java @@ -88,7 +88,12 @@ public enum ERROR { /** * Enum constant IO_CSR_ERROR. */ - IO_CSR_ERROR(118); + IO_CSR_ERROR(118), + + /** + * Enum constant ZIP_ERROR. + */ + ZIP_ERROR(119); /** * Field errorCode. diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/error/VerifyException.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/error/VerifyException.java new file mode 100644 index 0000000000000000000000000000000000000000..fb204b53f01dafcae06eaa3887ba8040a4638b79 --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/error/VerifyException.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2023-2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.ohos.hapsigntool.error; + +/** + * verify exception. + * + * @since 2023/08/26 + */ +public class VerifyException extends Exception { + /** + * Create VerifyException with params. + * + * @param message Error msg to throw + */ + public VerifyException(String message) { + super(message); + } +} diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/error/ZipException.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/error/ZipException.java new file mode 100644 index 0000000000000000000000000000000000000000..6ad0dfb29d8ae3c5ce6463094d3064dcebfc02ed --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/error/ZipException.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2023-2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.ohos.hapsigntool.error; + +import java.io.IOException; + +/** + * Zip exception for programs. + * + * @since 2023/12/07 + */ +public class ZipException extends IOException { + /** + * new ZipException + * + * @param message exception message + */ + public ZipException(String message) { + super(message); + } + + /** + * new ZipException + * + * @param message exception message + * @param e exception + */ + public ZipException(String message, Exception e) { + super(message, e); + } +} diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/entity/ElfBlockData.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/entity/ElfBlockData.java new file mode 100644 index 0000000000000000000000000000000000000000..b8cdd1ad6ed0b53ae902d9485f8235f7533ac6dc --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/entity/ElfBlockData.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2021-2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.ohos.hapsigntool.hap.entity; + +/** + * Elf block info + * + * @since 2023/11/20 + */ +public class ElfBlockData { + int blockNum; + int blockStart; + + public ElfBlockData(int blockNum, int blockStart) { + this.blockNum = blockNum; + this.blockStart = blockStart; + } + + public int getBlockNum() { + return blockNum; + } + + public void setBlockNum(int blockNum) { + this.blockNum = blockNum; + } + + public int getBlockStart() { + return blockStart; + } + + public void setBlockStart(int blockStart) { + this.blockStart = blockStart; + } +} diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/entity/HwBlockHead.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/entity/HwBlockHead.java index ba283115a5b10dda774221383ee316b0730d446b..c790e885625a4e9d09e75bd74bd77a96849f5899 100644 --- a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/entity/HwBlockHead.java +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/entity/HwBlockHead.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022 Huawei Device Co., Ltd. + * Copyright (c) 2022-2023 Huawei Device Co., Ltd. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at @@ -15,11 +15,24 @@ package com.ohos.hapsigntool.hap.entity; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + /** * define class of hap signature sub-block head + * + * @since 2023/11/07 */ public class HwBlockHead { - private static final int BLOCK_LEN = 8; // current block length + /** + * bin file sign block length is 8 byte + */ + public static final int BLOCK_LEN = 8; + + /** + * elf file sign block length is 12 byte + */ + public static final int ELF_BLOCK_LEN = 12; private static final int BIT_SIZE = 8; @@ -27,15 +40,26 @@ public class HwBlockHead { private static final int TRIPLE_BIT_SIZE = 24; + /** + * get bin block length + * + * @return return bin block length + */ public static int getBlockLen() { return BLOCK_LEN; } - private HwBlockHead() { + /** + * get elf block length + * + * @return return elf block length + */ + public static int getElfBlockLen() { + return ELF_BLOCK_LEN; } /** - * get serialization of HwBlockHead + * get serialization of file type bin BlockHead * * @param type type of signature block * @param tag tags of signature block @@ -55,4 +79,22 @@ public class HwBlockHead { (byte) (offset & 0xff) }; } + + /** + * get serialization of file type elf BlockHead + * + * @param type type of signature block + * @param tag tags of signature block + * @param length the length of block data + * @param offset Byte offset of the data block relative to the start position of the signature block + * @return Byte array after serialization of HwBlockHead + */ + public static byte[] getBlockHeadLittleEndian(char type, char tag, int length, int offset) { + ByteBuffer bf = ByteBuffer.allocate(HwBlockHead.ELF_BLOCK_LEN).order(ByteOrder.LITTLE_ENDIAN); + bf.putChar(type); + bf.putChar(tag); + bf.putInt(length); + bf.putInt(offset); + return bf.array(); + } } \ No newline at end of file diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/entity/HwSignHead.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/entity/HwSignHead.java index 6584253be06e08e60d4453b73e2680644787b073..46ce01fe83e56c0f74f4367349864afdf3f538c9 100644 --- a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/entity/HwSignHead.java +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/entity/HwSignHead.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021-2022 Huawei Device Co., Ltd. + * Copyright (c) 2021-2023 Huawei Device Co., Ltd. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at @@ -18,6 +18,8 @@ package com.ohos.hapsigntool.hap.entity; import com.ohos.hapsigntool.utils.ByteArrayUtils; import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; /** * define class of hap signature block head @@ -30,14 +32,29 @@ public class HwSignHead { */ public static final int SIGN_HEAD_LEN = 32; - private static final char[] MAGIC = "hw signed app ".toCharArray(); // 16Bytes-Magic - private static final char[] VERSION = "1000".toCharArray(); // 4-Bytes, version is 1.0.0.0 + /** + * sign hap magic string 16Bytes-Magic + */ + public static final char[] MAGIC = "hw signed app ".toCharArray(); + + /** + * sign elf magic string 16Bytes-Magic + */ + public static final char[] ELF_MAGIC = "elf sign block ".toCharArray(); + + /** + * sign block version 4-Bytes, version is 1.0.0.0 + */ + public static final char[] VERSION = "1000".toCharArray(); + private static final int NUM_OF_BLOCK = 2; // number of sub-block + private static final int RESERVE_LENGTH = 4; + private char[] reserve = new char[RESERVE_LENGTH]; /** - * get serialization of HwSignHead + * get serialization of HwSignHead file type of bin * * @param subBlockSize the total size of all sub-blocks * @return Byte array after serialization of HwSignHead @@ -72,4 +89,27 @@ public class HwSignHead { } return signHead; } + + /** + * get serialization of HwSignHead file type of elf + * + * @param subBlockSize the total size of all sub-blocks + * @param subBlockNum the sign block num + * @return Byte array after serialization of HwSignHead + */ + public byte[] getSignHeadLittleEndian(int subBlockSize, int subBlockNum) { + ByteBuffer bf = ByteBuffer.allocate(SIGN_HEAD_LEN).order(ByteOrder.LITTLE_ENDIAN); + for (char c : ELF_MAGIC) { + bf.put((byte) c); + } + for (char c : VERSION) { + bf.put((byte) c); + } + bf.putInt(subBlockSize); + bf.putInt(subBlockNum); + for (char c : reserve) { + bf.put((byte) c); + } + return bf.array(); + } } diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/entity/SignBlockData.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/entity/SignBlockData.java new file mode 100644 index 0000000000000000000000000000000000000000..2ad9874f082283de6f42c1ab2bee9bd95cf96c27 --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/entity/SignBlockData.java @@ -0,0 +1,94 @@ +/* + * Copyright (c) 2023-2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.ohos.hapsigntool.hap.entity; + +import com.ohos.hapsigntool.utils.FileUtils; + +/** + * sign block data + * + * @since 2023-11-07 + */ +public class SignBlockData { + private char type; + private byte[] blockHead; + private byte[] signData; + private String signFile; + private long len; + private boolean isByte; + + public SignBlockData(byte[] signData, char type) { + this.signData = signData; + this.type = type; + this.len = signData == null ? 0 : signData.length; + this.isByte = true; + } + + public SignBlockData(String signFile, char type) { + this.signFile = signFile; + this.type = type; + this.len = FileUtils.getFileLen(signFile); + this.isByte = false; + } + + public char getType() { + return type; + } + + public void setType(char type) { + this.type = type; + } + + public byte[] getBlockHead() { + return blockHead; + } + + public void setBlockHead(byte[] blockHead) { + this.blockHead = blockHead; + } + + public byte[] getSignData() { + return signData; + } + + public void setSignData(byte[] signData) { + this.signData = signData; + } + + public String getSignFile() { + return signFile; + } + + public void setSignFile(String signFile) { + this.signFile = signFile; + } + + public long getLen() { + return len; + } + + public void setLen(long len) { + this.len = len; + } + + public boolean isByte() { + return isByte; + } + + public void setByte(boolean isByte) { + this.isByte = isByte; + } +} diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/entity/SigningBlock.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/entity/SigningBlock.java index 0794f0d781f781bdeafbf385b88bb149c2f34728..663dd56ee4a81f39bc2fa96a21ee394c43de0cea 100644 --- a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/entity/SigningBlock.java +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/entity/SigningBlock.java @@ -25,6 +25,14 @@ public class SigningBlock { private int length; private byte[] value; + private int offset; + + /** + * Init Signing Block type and value + * + * @param type signing type + * @param value signing value + */ public SigningBlock(int type, byte[] value) { super(); this.type = type; @@ -32,6 +40,21 @@ public class SigningBlock { this.value = value; } + /** + * Init Signing Block type and value + * + * @param type signing type + * @param value signing value + * @param offset signing block offset + */ + public SigningBlock(int type, byte[] value, int offset) { + super(); + this.type = type; + this.length = value.length; + this.value = value; + this.offset = offset; + } + public int getType() { return type; } @@ -43,4 +66,8 @@ public class SigningBlock { public byte[] getValue() { return value; } + + public int getOffset() { + return offset; + } } \ No newline at end of file diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/provider/LocalJKSSignProvider.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/provider/LocalJKSSignProvider.java index 731abb15719b00e6f84d2dc039efbc807455c522..b0e1fa3fb6a0851dddaf2ce08fabb213a01b8cbf 100644 --- a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/provider/LocalJKSSignProvider.java +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/provider/LocalJKSSignProvider.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021-2022 Huawei Device Co., Ltd. + * Copyright (c) 2021-2023 Huawei Device Co., Ltd. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at @@ -21,6 +21,7 @@ import com.ohos.hapsigntool.hap.exception.MissingParamsException; import com.ohos.hapsigntool.utils.FileUtils; import com.ohos.hapsigntool.utils.ParamConstants; import com.ohos.hapsigntool.utils.ParamProcessUtil; + import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -87,9 +88,9 @@ public class LocalJKSSignProvider extends SignProvider { public void checkParams(Options options) throws InvalidParamsException, MissingParamsException { super.checkParams(options); String[] paramFileds = { - ParamConstants.PARAM_LOCAL_JKS_KEYSTORE, - ParamConstants.PARAM_LOCAL_JKS_KEYSTORE_CODE, - ParamConstants.PARAM_LOCAL_JKS_KEYALIAS_CODE + ParamConstants.PARAM_LOCAL_JKS_KEYSTORE, + ParamConstants.PARAM_LOCAL_JKS_KEYSTORE_CODE, + ParamConstants.PARAM_LOCAL_JKS_KEYALIAS_CODE }; Set paramSet = ParamProcessUtil.initParamField(paramFileds); @@ -103,7 +104,7 @@ public class LocalJKSSignProvider extends SignProvider { } } } - + checkSignCode(); checkPublicKeyPath(); } } diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/provider/SignProvider.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/provider/SignProvider.java index e5da2793d25edba4ad9865b44d7f2785c1a6523c..3d0a6d494dded3cc29ee61699b6a364f122efb62 100644 --- a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/provider/SignProvider.java +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/provider/SignProvider.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021-2022 Huawei Device Co., Ltd. + * Copyright (c) 2021-2023 Huawei Device Co., Ltd. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at @@ -15,21 +15,28 @@ package com.ohos.hapsigntool.hap.provider; +import static com.ohos.hapsigntool.codesigning.sign.CodeSigning.SUPPORT_BIN_FILE_FORM; +import static com.ohos.hapsigntool.codesigning.sign.CodeSigning.SUPPORT_FILE_FORM; + import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.JsonParseException; import com.google.gson.JsonParser; import com.ohos.hapsigntool.api.model.Options; +import com.ohos.hapsigntool.codesigning.exception.CodeSignException; +import com.ohos.hapsigntool.codesigning.exception.FsVerityDigestException; +import com.ohos.hapsigntool.codesigning.sign.CodeSigning; import com.ohos.hapsigntool.error.CustomException; import com.ohos.hapsigntool.hap.config.SignerConfig; import com.ohos.hapsigntool.hap.entity.SigningBlock; +import com.ohos.hapsigntool.hap.exception.HapFormatException; import com.ohos.hapsigntool.hap.exception.InvalidParamsException; import com.ohos.hapsigntool.hap.exception.MissingParamsException; import com.ohos.hapsigntool.hap.exception.ProfileException; import com.ohos.hapsigntool.hap.exception.SignatureException; import com.ohos.hapsigntool.hap.exception.VerifyCertificateChainException; -import com.ohos.hapsigntool.hap.exception.HapFormatException; import com.ohos.hapsigntool.hap.sign.SignBin; +import com.ohos.hapsigntool.hap.sign.SignElf; import com.ohos.hapsigntool.hap.sign.SignHap; import com.ohos.hapsigntool.hap.sign.SignatureAlgorithm; import com.ohos.hapsigntool.hap.verify.VerifyUtils; @@ -44,6 +51,7 @@ import com.ohos.hapsigntool.utils.StringUtils; import com.ohos.hapsigntool.zip.ByteBufferZipDataInput; import com.ohos.hapsigntool.zip.RandomAccessFileZipDataInput; import com.ohos.hapsigntool.zip.RandomAccessFileZipDataOutput; +import com.ohos.hapsigntool.zip.Zip; import com.ohos.hapsigntool.zip.ZipDataInput; import com.ohos.hapsigntool.zip.ZipDataOutput; import com.ohos.hapsigntool.zip.ZipFileInfo; @@ -56,10 +64,10 @@ import org.bouncycastle.cms.CMSSignedData; import org.bouncycastle.jce.provider.BouncyCastleProvider; import java.io.File; -import java.io.FileOutputStream; import java.io.IOException; import java.io.RandomAccessFile; import java.nio.ByteBuffer; +import java.nio.ByteOrder; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.StandardCopyOption; @@ -73,11 +81,8 @@ import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.Set; -import java.util.TimeZone; import java.util.Optional; -import java.util.jar.JarFile; -import java.util.jar.JarOutputStream; +import java.util.Set; /** * Sign provider super class @@ -101,6 +106,7 @@ public abstract class SignProvider { VALID_SIGN_ALG_NAME.add(ParamConstants.HAP_SIG_ALGORITHM_SHA256_RSA_MGF1); VALID_SIGN_ALG_NAME.add(ParamConstants.HAP_SIG_ALGORITHM_SHA384_RSA_MGF1); VALID_SIGN_ALG_NAME.add(ParamConstants.HAP_SIG_ALGORITHM_SHA512_RSA_MGF1); + Security.addProvider(new BouncyCastleProvider()); } static { @@ -119,6 +125,8 @@ public abstract class SignProvider { */ protected Map signParams = new HashMap(); + private String profileContent; + /** * Read data of optional blocks from file user inputted. * @@ -219,7 +227,7 @@ public abstract class SignProvider { List signatureAlgorithms = new ArrayList(); signatureAlgorithms.add( - ParamProcessUtil.getSignatureAlgorithm(this.signParams.get(ParamConstants.PARAM_BASIC_SIGANTURE_ALG))); + ParamProcessUtil.getSignatureAlgorithm(this.signParams.get(ParamConstants.PARAM_BASIC_SIGANTURE_ALG))); signerConfig.setSignatureAlgorithms(signatureAlgorithms); if (!crl.equals(Optional.empty())) { @@ -235,7 +243,6 @@ public abstract class SignProvider { * @return true, if sign successfully. */ public boolean signBin(Options options) { - Security.addProvider(new BouncyCastleProvider()); List publicCert = null; SignerConfig signerConfig; try { @@ -261,6 +268,47 @@ public abstract class SignProvider { return true; } + /** + * sign elf file + * + * @param options parameters used to sign elf file + * @return true, if sign successfully. + */ + public boolean signElf(Options options) { + List publicCert = null; + SignerConfig signerConfig = null; + try { + publicCert = getX509Certificates(options); + + // Get x509 CRL + Optional crl = getCrl(); + + // Create signer configs, which contains public cert and crl info. + signerConfig = createSignerConfigs(publicCert, crl, options); + } catch (InvalidKeyException | InvalidParamsException | MissingParamsException | ProfileException e) { + LOGGER.error("create signer configs failed.", e); + printErrorLogWithoutStack(e); + return false; + } + + if (ParamConstants.ProfileSignFlag.DISABLE_SIGN_CODE.getSignFlag().equals( + signParams.get(ParamConstants.PARAM_BASIC_PROFILE_SIGNED))) { + LOGGER.error("hap-sign-tool: error: Sign elf can not use unsigned profile."); + return false; + } + + if (profileContent != null) { + signParams.put(ParamConstants.PARAM_PROFILE_JSON_CONTENT, profileContent); + } + /* 6. make signed file into output file. */ + if (!SignElf.sign(signerConfig, signParams)) { + LOGGER.error("hap-sign-tool: error: Sign elf internal failed."); + return false; + } + LOGGER.info("Sign success"); + return true; + } + /** * sign hap file * @@ -268,7 +316,6 @@ public abstract class SignProvider { * @return true, if sign successfully */ public boolean sign(Options options) { - Security.addProvider(new BouncyCastleProvider()); List publicCerts = null; File output = null; File tmpOutput = null; @@ -279,15 +326,16 @@ public abstract class SignProvider { checkCompatibleVersion(); File input = new File(signParams.get(ParamConstants.PARAM_BASIC_INPUT_FILE)); output = new File(signParams.get(ParamConstants.PARAM_BASIC_OUTPUT_FILE)); + String suffix = getFileSuffix(input); if (input.getCanonicalPath().equals(output.getCanonicalPath())) { - tmpOutput = File.createTempFile("signedHap", ".hap"); + tmpOutput = File.createTempFile("signedHap", "." + suffix); isPathOverlap = true; } else { tmpOutput = output; } // copy file and Alignment int alignment = Integer.parseInt(signParams.get(ParamConstants.PARAM_BASIC_ALIGNMENT)); - copyFileAndAlignment(input, tmpOutput, alignment); + Zip zip = copyFileAndAlignment(input, tmpOutput, alignment); // generate sign block and output signedHap try (RandomAccessFile outputHap = new RandomAccessFile(tmpOutput, "rw")) { ZipDataInput outputHapIn = new RandomAccessFileZipDataInput(outputHap); @@ -295,9 +343,8 @@ public abstract class SignProvider { long centralDirectoryOffset = zipInfo.getCentralDirectoryOffset(); ZipDataInput beforeCentralDir = outputHapIn.slice(0, centralDirectoryOffset); ByteBuffer centralDirBuffer = - outputHapIn.createByteBuffer(centralDirectoryOffset, zipInfo.getCentralDirectorySize()); + outputHapIn.createByteBuffer(centralDirectoryOffset, zipInfo.getCentralDirectorySize()); ZipDataInput centralDirectory = new ByteBufferZipDataInput(centralDirBuffer); - ByteBuffer eocdBuffer = zipInfo.getEocd(); ZipDataInput eocd = new ByteBufferZipDataInput(eocdBuffer); @@ -306,6 +353,7 @@ public abstract class SignProvider { signerConfig.setCompatibleVersion(Integer.parseInt( signParams.get(ParamConstants.PARAM_BASIC_COMPATIBLE_VERSION))); ZipDataInput[] contents = {beforeCentralDir, centralDirectory, eocd}; + appendCodeSignBlock(signerConfig, tmpOutput, suffix, centralDirectoryOffset, zip); byte[] signingBlock = SignHap.sign(contents, signerConfig, optionalBlocks); long newCentralDirectoryOffset = centralDirectoryOffset + signingBlock.length; ZipUtils.setCentralDirectoryOffset(eocdBuffer, newCentralDirectoryOffset); @@ -314,8 +362,8 @@ public abstract class SignProvider { outputSignedFile(outputHap, centralDirectoryOffset, signingBlock, centralDirectory, eocdBuffer); isRet = true; } - } catch (IOException | InvalidKeyException | HapFormatException | MissingParamsException - | InvalidParamsException | ProfileException | NumberFormatException | CustomException e) { + } catch (FsVerityDigestException | InvalidKeyException | HapFormatException | MissingParamsException +|InvalidParamsException |ProfileException |NumberFormatException |CustomException |IOException |CodeSignException e) { printErrorLogWithoutStack(e); } catch (SignatureException e) { printErrorLog(e); @@ -323,6 +371,60 @@ public abstract class SignProvider { return doAfterSign(isRet, isPathOverlap, tmpOutput, output); } + /** + * append code signBlock + * + * @param signerConfig signerConfig + * @param tmpOutput temp output file + * @param suffix suffix + * @param centralDirectoryOffset central directory offset + * @param zip zip + * @throws FsVerityDigestException FsVerity digest on error + * @throws CodeSignException code sign on error + * @throws IOException IO error + * @throws HapFormatException hap format on error + * @throws ProfileException profile of app is invalid + */ + private void appendCodeSignBlock(SignerConfig signerConfig, File tmpOutput, String suffix, + long centralDirectoryOffset, Zip zip) + throws FsVerityDigestException, CodeSignException, IOException, HapFormatException, ProfileException { + if (!SUPPORT_BIN_FILE_FORM.contains(suffix) && !SUPPORT_FILE_FORM.contains(suffix)) { + LOGGER.warn("no need to sign code for :" + suffix); + return; + } + if (signParams.get(ParamConstants.PARAM_SIGN_CODE) + .equals(ParamConstants.SignCodeFlag.ENABLE_SIGN_CODE.getSignCodeFlag())) { + // 4 means hap format occupy 4 byte storage location,2 means optional blocks reserve 2 storage location + long codeSignOffset = centralDirectoryOffset + ((4 + 4 + 4) * (optionalBlocks.size() + 2) + (4 + 4 + 4)); + // create CodeSigning Object + CodeSigning codeSigning = new CodeSigning(signerConfig); + byte[] codeSignArray = codeSigning.getCodeSignBlock(tmpOutput, codeSignOffset, suffix, profileContent, zip); + ByteBuffer result = ByteBuffer.allocate(codeSignArray.length + (4 + 4 + 4)); + result.order(ByteOrder.LITTLE_ENDIAN); + result.putInt(HapUtils.HAP_CODE_SIGN_BLOCK_ID); // type + result.putInt(codeSignArray.length); // length + result.putInt((int) codeSignOffset); // offset + result.put(codeSignArray); + SigningBlock propertyBlock = new SigningBlock(HapUtils.HAP_PROPERTY_BLOCK_ID, result.array()); + optionalBlocks.add(0, propertyBlock); + } + } + + /** + * obtain file name suffix + * + * @param output output file + * @return suffix + * @throws HapFormatException hap format error + */ + private String getFileSuffix(File output) throws HapFormatException { + String[] fileNameArray = output.getName().split("\\."); + if (fileNameArray.length < ParamConstants.FILE_NAME_MIN_LENGTH) { + throw new HapFormatException("hap format error :" + output); + } + return fileNameArray[fileNameArray.length - 1]; + } + /** * Load certificate chain from input parameters * @@ -341,6 +443,10 @@ public abstract class SignProvider { publicCerts = getPublicCerts(); // 3. load optionalBlocks loadOptionalBlocks(); + if ("elf".equals(options.getString(ParamConstants.PARAM_IN_FORM)) + && StringUtils.isEmpty(options.getString(ParamConstants.PARAM_BASIC_PROFILE))) { + return publicCerts; + } checkProfileValid(publicCerts); return publicCerts; } @@ -353,9 +459,9 @@ public abstract class SignProvider { outputHapOut.write(eocdBuffer); } - private boolean doAfterSign(boolean isSuccess, boolean pathOverlap, File tmpOutput, File output) { + private boolean doAfterSign(boolean isSuccess, boolean isPathOverlap, File tmpOutput, File output) { boolean isRet = isSuccess; - if (isRet && pathOverlap) { + if (isRet && isPathOverlap) { try { Files.move(tmpOutput.toPath(), output.toPath(), StandardCopyOption.REPLACE_EXISTING); } catch (IOException e) { @@ -390,18 +496,20 @@ public abstract class SignProvider { * @param input file input * @param tmpOutput file tmpOutput * @param alignment alignment - * @throws IOException io error + * @return zip zip + * @throws IOException io error + * @throws HapFormatException hap format error */ - private void copyFileAndAlignment(File input, File tmpOutput, int alignment) throws IOException { - try (JarFile inputJar = new JarFile(input, false); - FileOutputStream outputFile = new FileOutputStream(tmpOutput); - JarOutputStream outputJar = new JarOutputStream(outputFile)) { - long timestamp = TIMESTAMP; - timestamp -= TimeZone.getDefault().getOffset(timestamp); - outputJar.setLevel(COMPRESSION_MODE); - List entryNames = SignHap.getEntryNamesFromHap(inputJar); - SignHap.copyFiles(entryNames, inputJar, outputJar, timestamp, alignment); - } + private Zip copyFileAndAlignment(File input, File tmpOutput, int alignment) + throws IOException, HapFormatException { + Zip zip = new Zip(input); + zip.alignment(alignment); + zip.removeSignBlock(); + long start = System.currentTimeMillis(); + zip.toFile(tmpOutput.getCanonicalPath()); + long end = System.currentTimeMillis(); + LOGGER.debug("zip to file use {} ms", end - start); + return zip; } /** @@ -492,9 +600,8 @@ public abstract class SignProvider { private void checkProfileValid(List inputCerts) throws ProfileException { try { byte[] profile = findProfileFromOptionalBlocks(); - boolean isProfileWithoutSign = ParamConstants.ProfileSignFlag.UNSIGNED_PROFILE.getSignFlag().equals( + boolean isProfileWithoutSign = ParamConstants.ProfileSignFlag.DISABLE_SIGN_CODE.getSignFlag().equals( signParams.get(ParamConstants.PARAM_BASIC_PROFILE_SIGNED)); - String content; if (!isProfileWithoutSign) { CMSSignedData cmsSignedData = new CMSSignedData(profile); boolean isVerify = VerifyUtils.verifyCmsSignedData(cmsSignedData); @@ -505,11 +612,11 @@ public abstract class SignProvider { if (!(contentObj instanceof byte[])) { throw new ProfileException("Check profile failed, signed profile content is not byte array!"); } - content = new String((byte[]) contentObj, StandardCharsets.UTF_8); + profileContent = new String((byte[]) contentObj, StandardCharsets.UTF_8); } else { - content = new String(profile, StandardCharsets.UTF_8); + profileContent = new String(profile, StandardCharsets.UTF_8); } - JsonElement parser = JsonParser.parseString(content); + JsonElement parser = JsonParser.parseString(profileContent); JsonObject profileJson = parser.getAsJsonObject(); checkProfileInfo(profileJson, inputCerts); } catch (CMSException e) { @@ -536,7 +643,7 @@ public abstract class SignProvider { throw new ProfileException("Unsupported profile type!"); } if (!inputCerts.isEmpty() && !checkInputCertMatchWithProfile(inputCerts.get(0), certInProfile)) { - throw new ProfileException("input certificates do not match with profile!"); + throw new ProfileException("input certificates do not match with profile!"); } String cn = getCertificateCN(certInProfile); LOGGER.info("certificate in profile: {}", cn); @@ -565,18 +672,20 @@ public abstract class SignProvider { */ public void checkParams(Options options) throws MissingParamsException, InvalidParamsException { String[] paramFileds = { - ParamConstants.PARAM_BASIC_ALIGNMENT, - ParamConstants.PARAM_BASIC_SIGANTURE_ALG, - ParamConstants.PARAM_BASIC_INPUT_FILE, - ParamConstants.PARAM_BASIC_OUTPUT_FILE, - ParamConstants.PARAM_BASIC_PRIVATE_KEY, - ParamConstants.PARAM_BASIC_PROFILE, - ParamConstants.PARAM_BASIC_PROOF, - ParamConstants.PARAM_BASIC_PROPERTY, - ParamConstants.PARAM_REMOTE_SERVER, - ParamConstants.PARAM_BASIC_PROFILE_SIGNED, - ParamConstants.PARAM_LOCAL_PUBLIC_CERT, - ParamConstants.PARAM_BASIC_COMPATIBLE_VERSION + ParamConstants.PARAM_BASIC_ALIGNMENT, + ParamConstants.PARAM_BASIC_SIGANTURE_ALG, + ParamConstants.PARAM_BASIC_INPUT_FILE, + ParamConstants.PARAM_BASIC_OUTPUT_FILE, + ParamConstants.PARAM_BASIC_PRIVATE_KEY, + ParamConstants.PARAM_BASIC_PROFILE, + ParamConstants.PARAM_BASIC_PROOF, + ParamConstants.PARAM_BASIC_PROPERTY, + ParamConstants.PARAM_REMOTE_SERVER, + ParamConstants.PARAM_BASIC_PROFILE_SIGNED, + ParamConstants.PARAM_LOCAL_PUBLIC_CERT, + ParamConstants.PARAM_BASIC_COMPATIBLE_VERSION, + ParamConstants.PARAM_SIGN_CODE, + ParamConstants.PARAM_IN_FORM }; Set paramSet = ParamProcessUtil.initParamField(paramFileds); @@ -588,10 +697,29 @@ public abstract class SignProvider { if (!signParams.containsKey(ParamConstants.PARAM_BASIC_PROFILE_SIGNED)) { signParams.put(ParamConstants.PARAM_BASIC_PROFILE_SIGNED, "1"); } + checkSignCode(); checkSignatureAlg(); checkSignAlignment(); } + /** + * Check code sign, if param do not have code sign default "1". + * + * @throws InvalidParamsException invalid param + */ + protected void checkSignCode() throws InvalidParamsException { + if (!signParams.containsKey(ParamConstants.PARAM_SIGN_CODE)) { + signParams.put(ParamConstants.PARAM_SIGN_CODE, + ParamConstants.SignCodeFlag.ENABLE_SIGN_CODE.getSignCodeFlag()); + return; + } + String codeSign = signParams.get(ParamConstants.PARAM_SIGN_CODE); + if (!codeSign.equals(ParamConstants.SignCodeFlag.ENABLE_SIGN_CODE.getSignCodeFlag()) + && !codeSign.equals(ParamConstants.SignCodeFlag.DISABLE_SIGN_CODE.getSignCodeFlag())) { + throw new InvalidParamsException("Invalid parameter: " + ParamConstants.PARAM_SIGN_CODE); + } + } + /** * Check compatible version, if param do not have compatible version default 9. * diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/sign/SignBin.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/sign/SignBin.java index bc94668de46bccd15069f025448f3fe36159d1da..670eea6c335fa89964638c1aced1a3217cebd910 100644 --- a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/sign/SignBin.java +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/sign/SignBin.java @@ -60,6 +60,10 @@ public class SignBin { public static boolean sign(SignerConfig signerConfig, Map signParams) { boolean result = false; /* 1. Make block head, write to output file. */ + String codesign = signParams.get(ParamConstants.PARAM_SIGN_CODE); + if (ParamConstants.ProfileSignFlag.ENABLE_SIGN_CODE.getSignFlag().equals(codesign)) { + LOGGER.warn("can not sign bin with codesign"); + } String inputFile = signParams.get(ParamConstants.PARAM_BASIC_INPUT_FILE); String outputFile = signParams.get(ParamConstants.PARAM_BASIC_OUTPUT_FILE); String profileFile = signParams.get(ParamConstants.PARAM_BASIC_PROFILE); @@ -172,7 +176,7 @@ public class SignBin { } HwSignHead signHeadData = new HwSignHead(); byte[] signHeadByte = signHeadData.getSignHead((int) size); - if (signHeadByte == null) { + if (signHeadByte == null || signHeadByte.length == 0) { LOGGER.error("Failed to get sign head data."); return false; } diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/sign/SignElf.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/sign/SignElf.java new file mode 100644 index 0000000000000000000000000000000000000000..8f990e1d55fa5cda222671cd6adc3e37dd9b3fcd --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/sign/SignElf.java @@ -0,0 +1,280 @@ +/* + * Copyright (c) 2023-2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.ohos.hapsigntool.hap.sign; + +import com.ohos.hapsigntool.codesigning.exception.CodeSignException; +import com.ohos.hapsigntool.codesigning.exception.FsVerityDigestException; +import com.ohos.hapsigntool.codesigning.sign.CodeSigning; +import com.ohos.hapsigntool.hap.config.SignerConfig; +import com.ohos.hapsigntool.hap.entity.HwBlockHead; +import com.ohos.hapsigntool.hap.entity.HwSignHead; +import com.ohos.hapsigntool.hap.entity.SignBlockData; +import com.ohos.hapsigntool.hap.entity.SignatureBlockTags; +import com.ohos.hapsigntool.hap.entity.SignatureBlockTypes; +import com.ohos.hapsigntool.hap.exception.HapFormatException; +import com.ohos.hapsigntool.hap.exception.ProfileException; +import com.ohos.hapsigntool.utils.FileUtils; +import com.ohos.hapsigntool.utils.ParamConstants; +import com.ohos.hapsigntool.utils.ParamProcessUtil; +import com.ohos.hapsigntool.utils.StringUtils; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.io.DataOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.Map; + +/** + * elf file Signature signer. + * + * @since 2023/11/21 + */ +public class SignElf { + /** + * codesign sign block type + */ + public static final char CODESIGN_BLOCK_TYPE = 3; + + private static final Logger LOGGER = LogManager.getLogger(SignElf.class); + + private static final String CODESIGN_OFF = "0"; + + private static int blockNum = 0; + + private static final int PAGE_SIZE = 4096; + + private static final int FILE_BUFFER_BLOCK = 16384; + + /** + * Constructor of Method + */ + private SignElf() { + } + + /** + * Sign the elf file. + * + * @param signerConfig Config of the elf file to be signed. + * @param signParams The input parameters of sign elf. + * @return true if sign successfully; false otherwise. + */ + public static boolean sign(SignerConfig signerConfig, Map signParams) { + boolean isSuccess = false; + /* 1. Make block head, write to output file. */ + String inputFile = signParams.get(ParamConstants.PARAM_BASIC_INPUT_FILE); + String tmpFile = alignFileBy4kBytes(inputFile); + if (tmpFile == null) { + LOGGER.error("copy input File failed"); + return isSuccess; + } + String outputFile = signParams.get(ParamConstants.PARAM_BASIC_OUTPUT_FILE); + String profileSigned = signParams.get(ParamConstants.PARAM_BASIC_PROFILE_SIGNED); + if (!writeBlockDataToFile(signerConfig, tmpFile, outputFile, profileSigned, signParams)) { + LOGGER.error("The block head data made failed.`"); + ParamProcessUtil.delDir(new File(outputFile)); + return isSuccess; + } + LOGGER.info("The block head data made success."); + + /* 2. Make sign data, and write to output file */ + if (!writeSignHeadDataToOutputFile(tmpFile, outputFile, blockNum)) { + LOGGER.error("The sign head data made failed."); + ParamProcessUtil.delDir(new File(outputFile)); + } else { + isSuccess = true; + } + return isSuccess; + } + + private static String alignFileBy4kBytes(String inputFile) { + String tmp = "tmpFile" + new Date().getTime(); + File tmpFile = new File(tmp); + try { + tmpFile.createNewFile(); + } catch (IOException e) { + LOGGER.error("create tmp file Failed"); + return null; + } + try (FileOutputStream output = new FileOutputStream(tmpFile); + FileInputStream input = new FileInputStream(inputFile)) { + byte[] buffer = new byte[FILE_BUFFER_BLOCK]; + int read; + while ((read = input.read(buffer)) != FileUtils.FILE_END) { + output.write(buffer, 0, read); + } + + long addLength = PAGE_SIZE - (tmpFile.length() % PAGE_SIZE); + if (isLongOverflowInteger(addLength)) { + LOGGER.error("File alignment error"); + return null; + } + byte[] bytes = new byte[(int) addLength]; + java.util.Arrays.fill(bytes, (byte) 0); + FileUtils.writeByteToOutFile(bytes, tmp); + } catch (IOException e) { + LOGGER.error("copy inFile Failed"); + return null; + } + return tmp; + } + + private static boolean writeBlockDataToFile(SignerConfig signerConfig, + String inputFile, String outputFile, String profileSigned, Map signParams) { + try { + String profileFile = signParams.get(ParamConstants.PARAM_BASIC_PROFILE); + + List signDataList = new ArrayList<>(); + + long binFileLen = FileUtils.getFileLen(inputFile); + if (binFileLen == -1) { + LOGGER.error("file length is invalid, elf file len: " + binFileLen); + throw new IOException(); + } + // 1. generate sign data + if (!StringUtils.isEmpty(signParams.get(ParamConstants.PARAM_BASIC_PROFILE))) { + signDataList.add(generateProfileSignByte(profileFile, profileSigned)); + } + blockNum = signDataList.size() + 1; // other sign block num + codesign block 1 + SignBlockData codeSign = generateCodeSignByte(signerConfig, signParams, inputFile, blockNum, binFileLen); + if (codeSign != null) { + signDataList.add(0, codeSign); + } + blockNum = signDataList.size(); + // 2. use sign data generate offset and sign block head + generateSignBlockHead(signDataList); + + return writeSignedElf(inputFile, signDataList, outputFile); + } catch (IOException e) { + LOGGER.error("writeBlockDataToFile failed.", e); + return false; + } catch (FsVerityDigestException | CodeSignException | HapFormatException | ProfileException e) { + LOGGER.error("codesign failed.", e); + return false; + } + } + + private static boolean writeSignedElf(String inputFile, List signBlockList, String outputFile) { + try (FileOutputStream fileOutputStream = new FileOutputStream(outputFile); + DataOutputStream dataOutputStream = new DataOutputStream(fileOutputStream)) { + // 1. write the input file to the output file. + if (!FileUtils.writeFileToDos(inputFile, dataOutputStream)) { + LOGGER.error("Failed to write information of input file: " + inputFile + + " to outputFile: " + outputFile); + throw new IOException(); + } + + // 2. write block head to the output file. + for (SignBlockData signBlockData : signBlockList) { + if (!FileUtils.writeByteToDos(signBlockData.getBlockHead(), dataOutputStream)) { + LOGGER.error("Failed to write Block Head to output file: " + outputFile); + throw new IOException(); + } + } + + // 3. write block data to the output file. + for (SignBlockData signBlockData : signBlockList) { + boolean isSuccess; + if (signBlockData.isByte()) { + isSuccess = FileUtils.writeByteToDos(signBlockData.getSignData(), dataOutputStream); + } else { + isSuccess = FileUtils.writeFileToDos(signBlockData.getSignFile(), dataOutputStream); + } + + if (!isSuccess) { + LOGGER.error("Failed to write Block Data to output file: " + outputFile); + throw new IOException(); + } + } + } catch (IOException e) { + LOGGER.error("writeSignedElf failed.", e); + return false; + } + return true; + } + + private static void generateSignBlockHead(List signDataList) + throws IOException { + long offset = (long) HwBlockHead.getElfBlockLen() * signDataList.size(); + + for (int i = 0; i < signDataList.size(); i++) { + SignBlockData signBlockData = signDataList.get(i); + + signBlockData.setBlockHead(HwBlockHead.getBlockHeadLittleEndian(signBlockData.getType(), + SignatureBlockTags.DEFAULT, (int) signBlockData.getLen(), (int) offset)); + offset += signBlockData.getLen(); + if (isLongOverflowInteger(offset)) { + LOGGER.error("The sign block " + i + "offset is overflow integer, offset: " + offset); + throw new IOException(); + } + } + } + + private static SignBlockData generateProfileSignByte(String profileFile, String profileSigned) throws IOException { + long profileDataLen = FileUtils.getFileLen(profileFile); + + if (profileDataLen == -1 || isLongOverflowShort(profileDataLen)) { + LOGGER.error("file length is invalid, profileDataLen: " + profileDataLen); + throw new IOException(); + } + + char isSigned = SignatureBlockTypes.getProfileBlockTypes(profileSigned); + return new SignBlockData(profileFile, isSigned); + } + + private static SignBlockData generateCodeSignByte(SignerConfig signerConfig, Map signParams, + String inputFile, int blockNum, long binFileLen) throws IOException, + FsVerityDigestException, CodeSignException, HapFormatException, ProfileException { + if (CODESIGN_OFF.equals(signParams.get(ParamConstants.PARAM_SIGN_CODE))) { + return null; + } + CodeSigning codeSigning = new CodeSigning(signerConfig); + long offset = binFileLen + (long) HwBlockHead.getElfBlockLen() * blockNum; + String profileContent = signParams.get(ParamConstants.PARAM_PROFILE_JSON_CONTENT); + byte[] codesignData = codeSigning.getElfCodeSignBlock(new File(inputFile), offset, + signParams.get(ParamConstants.PARAM_IN_FORM), profileContent); + return new SignBlockData(codesignData, CODESIGN_BLOCK_TYPE); + } + + private static boolean writeSignHeadDataToOutputFile(String inputFile, String outputFile, int blockNum) { + long size = FileUtils.getFileLen(outputFile) - FileUtils.getFileLen(inputFile); + if (isLongOverflowInteger(size)) { + LOGGER.error("File size is Overflow integer range."); + return false; + } + HwSignHead signHeadData = new HwSignHead(); + byte[] signHeadByte = signHeadData.getSignHeadLittleEndian((int) size, blockNum); + if (signHeadByte == null) { + LOGGER.error("Failed to get sign head data."); + return false; + } + return FileUtils.writeByteToOutFile(signHeadByte, outputFile); + } + + private static boolean isLongOverflowInteger(long num) { + return (num - (num & 0xffffffffL)) != 0; + } + + private static boolean isLongOverflowShort(long num) { + return (num - (num & 0xffffL)) != 0; + } +} diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/sign/SignHap.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/sign/SignHap.java index eede0eaab64946ffd2011a6bd7bfcc4e20fd9e18..3ea5a842ae688887d7e2a1bee4c435ee5a45fe52 100644 --- a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/sign/SignHap.java +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/sign/SignHap.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021-2022 Huawei Device Co., Ltd. + * Copyright (c) 2021-2023 Huawei Device Co., Ltd. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at @@ -19,8 +19,11 @@ import com.ohos.hapsigntool.api.model.Options; import com.ohos.hapsigntool.hap.config.SignerConfig; import com.ohos.hapsigntool.hap.entity.Pair; import com.ohos.hapsigntool.hap.entity.SigningBlock; +import com.ohos.hapsigntool.hap.exception.HapFormatException; import com.ohos.hapsigntool.hap.exception.SignatureException; +import com.ohos.hapsigntool.utils.FileUtils; import com.ohos.hapsigntool.utils.HapUtils; +import com.ohos.hapsigntool.utils.StringUtils; import com.ohos.hapsigntool.zip.ZipDataInput; import java.io.IOException; @@ -29,8 +32,6 @@ import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.security.DigestException; import java.util.ArrayList; -import java.util.Collections; -import java.util.Enumeration; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -39,8 +40,10 @@ import java.util.Set; import java.util.jar.JarEntry; import java.util.jar.JarFile; import java.util.jar.JarOutputStream; +import java.util.stream.Collectors; /** + * * Hap Signature Scheme signer * * @since 2021/12/21 @@ -63,96 +66,168 @@ public abstract class SignHap { return BLOCK_SIZE; } - /** - * Get all entries' name from hap which is opened as a jar-file. - * - * @param hap input hap-file which is opened as a jar-file. - * @return list of entries' names. - */ - public static List getEntryNamesFromHap(JarFile hap) { - List result = new ArrayList(); - for (Enumeration e = hap.entries(); e.hasMoreElements();) { - JarEntry entry = e.nextElement(); - if (!entry.isDirectory()) { - result.add(entry.getName()); - } - } - return result; - } - /** * Copy the jar file and align the storage entries. * - * @param entryNames list of entries' name * @param in input hap-file which is opened as a jar-file. * @param out output stream of jar. * @param timestamp ZIP file timestamps * @param defaultAlignment default value of alignment. * @throws IOException io error. + * @throws HapFormatException hap format error. */ - public static void copyFiles(List entryNames, JarFile in, - JarOutputStream out, long timestamp, int defaultAlignment) throws IOException { - Collections.sort(entryNames); + public static void copyFiles(JarFile in, + JarOutputStream out, long timestamp, int defaultAlignment) throws IOException, HapFormatException { + // split compressed and uncompressed + List entryListStored = in.stream() + .filter(jarFile -> jarFile.getMethod() == JarEntry.STORED).collect(Collectors.toList()); + + // uncompressed special files and place in front + entryListStored = storedEntryListOfSort(entryListStored); long offset = INIT_OFFSET_LEN; - for (String name : entryNames) { - JarEntry inEntry = in.getJarEntry(name); - if (inEntry.getMethod() != JarEntry.STORED) { + String lastAlignmentEntryName = ""; + for (JarEntry inEntry : entryListStored) { + String entryName = inEntry.getName(); + if (!FileUtils.isRunnableFile(entryName)) { + lastAlignmentEntryName = entryName; + break; + } + } + for (JarEntry inEntry : entryListStored) { + if (inEntry == null) { continue; } offset += JarFile.LOCHDR; - JarEntry outEntry = new JarEntry(inEntry); - outEntry.setTime(timestamp); - - outEntry.setComment(null); - outEntry.setExtra(null); - + JarEntry outEntry = getJarEntry(timestamp, inEntry); offset += outEntry.getName().length(); - int alignment = getStoredEntryDataAlignment(name, defaultAlignment); + int alignment = getStoredEntryDataAlignment(inEntry.getName(), defaultAlignment, lastAlignmentEntryName); if (alignment > 0 && (offset % alignment != 0)) { int needed = alignment - (int) (offset % alignment); outEntry.setExtra(new byte[needed]); offset += needed; } + out.putNextEntry(outEntry); + offset = writeOutputStreamAndGetOffset(in, out, inEntry, offset); + } + List entryListNotStored = in.stream() + .filter(jarFile -> jarFile.getMethod() != JarEntry.STORED).collect(Collectors.toList()); + // process byte alignment of the first compressed file + boolean isAlignmentFlag = StringUtils.isEmpty(lastAlignmentEntryName); + if (isAlignmentFlag) { + if (entryListNotStored.isEmpty()) { + throw new HapFormatException("Hap format is error, file missing"); + } + JarEntry firstEntry = entryListNotStored.get(0); + offset += JarFile.LOCHDR; + JarEntry outEntry = getFirstJarEntry(firstEntry, offset, timestamp); out.putNextEntry(outEntry); byte[] buffer = new byte[BUFFER_LENGTH]; - try (InputStream data = in.getInputStream(inEntry)) { - int num; - while ((num = data.read(buffer)) > 0) { - out.write(buffer, 0, num); - offset += num; - } - out.flush(); + writeOutputStream(in, out, firstEntry, buffer); + } + + copyFilesExceptStoredFile(entryListNotStored, in, out, timestamp, isAlignmentFlag); + } + + /** + * uncompressed special files are placed in front + * + * @param entryListStored stored file entry list + * @return List jarEntryList + */ + private static List storedEntryListOfSort(List entryListStored) { + return entryListStored.stream().sorted((entry1, entry2) -> { + String name1 = entry1.getName(); + String name2 = entry2.getName(); + // files ending with .abc or .so are placed before other files + boolean isSpecial1 = FileUtils.isRunnableFile(name1); + boolean isSpecial2 = FileUtils.isRunnableFile(name2); + if (isSpecial1 && !isSpecial2) { + return -1; + } else if (!isSpecial1 && isSpecial2) { + return 1; + } else { + // if all files are special files or none of them are special files,the files are sorted lexically + return name1.compareTo(name2); } + }).collect(Collectors.toList()); + } + + private static JarEntry getFirstJarEntry(JarEntry firstEntry, long offset, long timestamp) { + long currentOffset = offset; + JarEntry outEntry = getJarEntry(timestamp, firstEntry); + currentOffset += outEntry.getName().length(); + if (currentOffset % STORED_ENTRY_SO_ALIGNMENT != 0) { + int needed = STORED_ENTRY_SO_ALIGNMENT - (int) (currentOffset % STORED_ENTRY_SO_ALIGNMENT); + outEntry.setExtra(new byte[needed]); } + return outEntry; + } - copyFilesExceptStoredFile(entryNames, in, out, timestamp); + /** + * write first not stored entry to outputStream + * + * @param in jar file + * @param out jarOutputStream + * @param firstEntry jarEntry + * @param buffer byte[] + * @throws IOException IOExpcetion + */ + private static void writeOutputStream(JarFile in, JarOutputStream out, JarEntry firstEntry, byte[] buffer) + throws IOException { + try (InputStream data = in.getInputStream(firstEntry)) { + int num; + while ((num = data.read(buffer)) > 0) { + out.write(buffer, 0, num); + } + out.flush(); + } } - private static void copyFilesExceptStoredFile(List entryNames, JarFile in, - JarOutputStream out, long timestamp) throws IOException { + private static long writeOutputStreamAndGetOffset(JarFile in, JarOutputStream out, JarEntry inEntry, long offset) + throws IOException { byte[] buffer = new byte[BUFFER_LENGTH]; + long currentOffset = offset; + try (InputStream data = in.getInputStream(inEntry)) { + int num; + while ((num = data.read(buffer)) > 0) { + out.write(buffer, 0, num); + currentOffset += num; + } + out.flush(); + } + return currentOffset; + } + + private static JarEntry getJarEntry(long timestamp, JarEntry inEntry) { + JarEntry outEntry = new JarEntry(inEntry); + outEntry.setTime(timestamp); - for (String name : entryNames) { - JarEntry inEntry = in.getJarEntry(name); - if (inEntry.getMethod() == JarEntry.STORED) { + outEntry.setComment(null); + outEntry.setExtra(null); + return outEntry; + } + + private static void copyFilesExceptStoredFile(List entryListNotStored, JarFile in, + JarOutputStream out, long timestamp, boolean isAlignmentFlag) throws IOException { + byte[] buffer = new byte[BUFFER_LENGTH]; + int index = 0; + if (isAlignmentFlag) { + index = 1; + } + for (; index < entryListNotStored.size(); index++) { + JarEntry inEntry = entryListNotStored.get(index); + if (inEntry == null || inEntry.getMethod() == JarEntry.STORED) { continue; } - JarEntry outEntry = new JarEntry(name); + JarEntry outEntry = new JarEntry(inEntry.getName()); outEntry.setTime(timestamp); out.putNextEntry(outEntry); - - try (InputStream data = in.getInputStream(inEntry);) { - int num; - while ((num = data.read(buffer)) > 0) { - out.write(buffer, 0, num); - } - out.flush(); - } + writeOutputStream(in, out, inEntry, buffer); } } @@ -161,13 +236,18 @@ public abstract class SignHap { * * @param entryName name of entry * @param defaultAlignment default value of alignment. + * @param lastAlignmentEntryName lastAlignmentEntryName * @return value of alignment. */ - private static int getStoredEntryDataAlignment(String entryName, int defaultAlignment) { + private static int getStoredEntryDataAlignment(String entryName, int defaultAlignment, + String lastAlignmentEntryName) { if (defaultAlignment <= 0) { return 0; } - if (entryName.endsWith(".so")) { + if (!StringUtils.isEmpty(lastAlignmentEntryName) && entryName.equals(lastAlignmentEntryName)) { + return STORED_ENTRY_SO_ALIGNMENT; + } + if (FileUtils.isRunnableFile(entryName)) { return STORED_ENTRY_SO_ALIGNMENT; } return defaultAlignment; @@ -178,7 +258,7 @@ public abstract class SignHap { List optionalBlocks, SignerConfig signerConfig, ZipDataInput[] hapData) - throws SignatureException { + throws SignatureException { /** * Compute digests of Hap contents * Sign the digests and wrap the signature and signer info into the Hap Signing Block @@ -186,7 +266,7 @@ public abstract class SignHap { byte[] hapSignatureBytes = null; try { Map contentDigests = - HapUtils.computeDigests(contentDigestAlgorithms, hapData, optionalBlocks); + HapUtils.computeDigests(contentDigestAlgorithms, hapData, optionalBlocks); hapSignatureBytes = generateHapSigningBlock(signerConfig, contentDigests, optionalBlocks); } catch (DigestException | IOException e) { throw new SignatureException("Failed to compute digests of HAP", e); @@ -204,35 +284,29 @@ public abstract class SignHap { } private static byte[] generateHapSigningBlock(byte[] hapSignatureSchemeBlock, - List optionalBlocks, int compatibleVersion) { + List optionalBlocks, int compatibleVersion) { // FORMAT: // Proof-of-Rotation pairs(optional): // uint32:type // uint32:length // uint32:offset - // Property pairs(optional): // uint32:type // uint32:length // uint32:offset - // Profile capability pairs(optional): // uint32:type // uint32:length // uint32:offset - // length bytes : app signing pairs // uint32:type // uint32:length // uint32:offset - // repeated ID-value pairs(reserved extensions): // length bytes : Proof-of-Rotation values // length bytes : property values // length bytes : profile capability values // length bytes : signature schema values - - // uint32: block count // uint64: size // uint128: magic // uint32: version @@ -240,7 +314,6 @@ public abstract class SignHap { for (SigningBlock optionalBlock : optionalBlocks) { optionalBlockSize += optionalBlock.getLength(); } - long resultSize = ((OPTIONAL_TYPE_SIZE + OPTIONAL_LENGTH_SIZE + OPTIONAL_OFFSET_SIZE) * (optionalBlocks.size() + 1)) + optionalBlockSize // optional pair @@ -256,8 +329,8 @@ public abstract class SignHap { result.order(ByteOrder.LITTLE_ENDIAN); Map typeAndOffsetMap = new HashMap(); - int currentOffset = ((OPTIONAL_TYPE_SIZE + OPTIONAL_LENGTH_SIZE + - OPTIONAL_OFFSET_SIZE) * (optionalBlocks.size() + 1)); + int currentOffset = ((OPTIONAL_TYPE_SIZE + OPTIONAL_LENGTH_SIZE + + OPTIONAL_OFFSET_SIZE) * (optionalBlocks.size() + 1)); int currentOffsetInBlockValue = 0; int blockValueSizes = (int) (optionalBlockSize + hapSignatureSchemeBlock.length); byte[] blockValues = new byte[blockValueSizes]; @@ -274,20 +347,12 @@ public abstract class SignHap { hapSignatureSchemeBlock, 0, blockValues, currentOffsetInBlockValue, hapSignatureSchemeBlock.length); typeAndOffsetMap.put(HapUtils.HAP_SIGNATURE_SCHEME_V1_BLOCK_ID, currentOffset); - int offset = 0; - for (SigningBlock optionalBlock : optionalBlocks) { - result.putInt(optionalBlock.getType()); // type - result.putInt(optionalBlock.getLength()); // length - offset = typeAndOffsetMap.get(optionalBlock.getType()); - result.putInt(offset); // offset - } + extractedResult(optionalBlocks, result, typeAndOffsetMap); result.putInt(HapUtils.HAP_SIGNATURE_SCHEME_V1_BLOCK_ID); // type result.putInt(hapSignatureSchemeBlock.length); // length - offset = typeAndOffsetMap.get(HapUtils.HAP_SIGNATURE_SCHEME_V1_BLOCK_ID); + int offset = typeAndOffsetMap.get(HapUtils.HAP_SIGNATURE_SCHEME_V1_BLOCK_ID); result.putInt(offset); // offset - result.put(blockValues); - result.putInt(optionalBlocks.size() + 1); // Signing block count result.putLong(resultSize); // length of hap signing block result.put(HapUtils.getHapSigningBlockMagic(compatibleVersion)); // magic @@ -295,6 +360,17 @@ public abstract class SignHap { return result.array(); } + private static void extractedResult(List optionalBlocks, ByteBuffer result, + Map typeAndOffsetMap) { + int offset; + for (SigningBlock optionalBlock : optionalBlocks) { + result.putInt(optionalBlock.getType()); // type + result.putInt(optionalBlock.getLength()); // length + offset = typeAndOffsetMap.get(optionalBlock.getType()); + result.putInt(offset); // offset + } + } + private static byte[] generateHapSignatureSchemeBlock( SignerConfig signerConfig, Map contentDigests) throws SignatureException { byte[] signerBlock = null; @@ -310,7 +386,7 @@ public abstract class SignHap { SignerConfig signerConfig, Map contentDigests) throws SignatureException { String mode = signerConfig.getOptions().getString(Options.MODE); if (!("remoteSign".equalsIgnoreCase(mode)) && signerConfig.getCertificates().isEmpty()) { - throw new SignatureException("No certificates configured for signer"); + throw new SignatureException("No certificates configured for signer"); } List> digests = @@ -342,7 +418,7 @@ public abstract class SignHap { * @throws SignatureException if an error occurs when sign hap file. */ public static byte[] sign(ZipDataInput[] contents, SignerConfig signerConfig, List optionalBlocks) - throws SignatureException { + throws SignatureException { Set contentDigestAlgorithms = new HashSet(); for (SignatureAlgorithm signatureAlgorithm : signerConfig.getSignatureAlgorithms()) { contentDigestAlgorithms.add(signatureAlgorithm.getContentDigestAlgorithm()); diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/verify/HapVerify.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/verify/HapVerify.java index abfb465b46bd6e8263c11c1b473000eba997d5f6..a889fc9c67586920213c49af039563eb53fb7d38 100644 --- a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/verify/HapVerify.java +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/verify/HapVerify.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021-2022 Huawei Device Co., Ltd. + * Copyright (c) 2021-2023 Huawei Device Co., Ltd. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at @@ -91,8 +91,17 @@ public class HapVerify { private JcaX509CRLConverter crlConverter = new JcaX509CRLConverter(); - private boolean printCert; + private boolean isPrintCert; + /** + * Init Zip HapVerify + * + * @param beforeApkSigningBlock beforeApkSigningBlock + * @param signatureSchemeBlock signatureSchemeBlock + * @param centralDirectoryBlock centralDirectoryBlock + * @param eocd eocd + * @param optionalBlocks optionalBlocks + */ public HapVerify( ZipDataInput beforeApkSigningBlock, ByteBuffer signatureSchemeBlock, @@ -106,6 +115,12 @@ public class HapVerify { this.optionalBlocks = optionalBlocks; } + /** + * init HapVerify + */ + public HapVerify() { + } + /** * Verify hap signature. * @@ -115,12 +130,22 @@ public class HapVerify { return parserSigner(signatureSchemeBlock); } - public void setPrintCert(boolean printCert) { - this.printCert = printCert; + /** + * Verify elf signature. + * + * @param profile profile byte + * @return verify result. + */ + public VerifyResult verifyElfProfile(byte[] profile) { + return parserSigner(ByteBuffer.wrap(profile), false); + } + + public void setIsPrintCert(boolean isPrintCert) { + this.isPrintCert = isPrintCert; } private boolean checkCRL(X509CRL crl, List certificates) { - boolean ret = false; + boolean isRet = false; for (X509Certificate cert : certificates) { if (!crl.getIssuerDN().getName().equals(cert.getIssuerDN().getName())) { continue; @@ -129,12 +154,12 @@ public class HapVerify { if (entry != null) { LOGGER.info("cert(subject DN = {}) is revoked by crl (IssuerDN = {})", cert.getSubjectDN().getName(), crl.getIssuerDN().getName()); - ret = false; + isRet = false; break; } - ret = true; + isRet = true; } - return ret; + return isRet; } private boolean verifyCRL(X509CRL crl, X509Certificate cert, List certificates) @@ -152,33 +177,33 @@ public class HapVerify { } private boolean verifyCRL(X509CRL crl, List certificates) throws SignatureException { - boolean revoked = true; + boolean isRevoked = true; for (X509Certificate cert : certificates) { if (!crl.getIssuerDN().getName().equals(cert.getSubjectDN().getName())) { continue; } if (!verifyCRL(crl, cert, certificates)) { - revoked = false; + isRevoked = false; } } - return revoked; + return isRevoked; } private void verifyCRLs(List crls, List certificates) throws VerifyHapException { if (crls == null) { return; } - boolean revoked = true; + boolean isRevoked = true; try { for (X509CRL crl : crls) { if (!verifyCRL(crl, certificates)) { - revoked = false; + isRevoked = false; } } } catch (SignatureException e) { throw new VerifyHapException("Verify CRL error!", e); } - if (!revoked) { + if (!isRevoked) { throw new VerifyHapException("Certificate is revoked!"); } } @@ -186,8 +211,8 @@ public class HapVerify { private CMSSignedData verifyCmsSignedData(byte[] signingBlock) throws VerifyHapException { try { CMSSignedData cmsSignedData = new CMSSignedData(signingBlock); - boolean verifyResult = VerifyUtils.verifyCmsSignedData(cmsSignedData); - if (!verifyResult) { + boolean isVerifyResult = VerifyUtils.verifyCmsSignedData(cmsSignedData); + if (!isVerifyResult) { throw new VerifyHapException("Verify PKCS7 cms data failed!"); } return cmsSignedData; @@ -197,6 +222,10 @@ public class HapVerify { } private VerifyResult parserSigner(ByteBuffer signer) { + return parserSigner(signer, true); + } + + private VerifyResult parserSigner(ByteBuffer signer, boolean verifyContent) { byte[] signingBlock = new byte[signer.remaining()]; signer.get(signingBlock); try { @@ -204,7 +233,9 @@ public class HapVerify { List certificates = getCertChain(cmsSignedData); List crlList = getCrlList(cmsSignedData); verifyCRLs(crlList, certificates); - checkContentDigest(cmsSignedData); + if (verifyContent) { + checkContentDigest(cmsSignedData); + } List signerInfos = getSignerInformations(cmsSignedData); VerifyResult result = new VerifyResult(true, VerifyResult.RET_SUCCESS, "Verify success"); result.setCrls(crlList); @@ -214,7 +245,7 @@ public class HapVerify { result.setOptionalBlocks(optionalBlocks); return result; } catch (VerifyHapException e) { - LOGGER.error("Verify Hap error!", e); + LOGGER.error("Verify profile error!", e); return new VerifyResult(false, VerifyResult.RET_UNKNOWN_ERROR, e.getMessage()); } } @@ -238,8 +269,8 @@ public class HapVerify { throw new VerifyHapException("PKCS cms content is not a byte array!"); } try { - boolean checkResult = parserContentinfo(contentBytes); - if (!checkResult) { + boolean isCheckResult = parserContentinfo(contentBytes); + if (!isCheckResult) { throw new VerifyHapException("Hap content digest check failed."); } } catch (DigestException | SignatureException | IOException e) { @@ -254,7 +285,7 @@ public class HapVerify { if (certificateList == null || certificateList.size() == 0) { throw new VerifyHapException("Certificate chain is empty!"); } - if (printCert) { + if (isPrintCert) { for (int i = 0; i < certificateList.size(); i++) { LOGGER.info("+++++++++++++++++++++++++++certificate #{} +++++++++++++++++++++++++++++++", i); printCert(certificateList.get(i)); @@ -289,7 +320,7 @@ public class HapVerify { } private List certStoreToCertList(Store certificates) - throws CertificateException { + throws CertificateException { if (certificates == null) { return Collections.emptyList(); } @@ -308,7 +339,6 @@ public class HapVerify { private boolean parserContentinfo(byte[] data) throws DigestException, SignatureException, IOException { - boolean result = true; ByteBuffer digestDatas = ByteBuffer.wrap(data).order(ByteOrder.LITTLE_ENDIAN); while (digestDatas.remaining() > 4) { /** @@ -344,22 +374,22 @@ public class HapVerify { Set keySet = digestMap.keySet(); Map actualDigestMap = HapUtils.computeDigests( keySet, new ZipDataInput[]{beforeApkSigningBlock, centralDirectoryBlock, eocd}, optionalBlocks); - + boolean isResult = true; for (Entry entry : digestMap.entrySet()) { ContentDigestAlgorithm digestAlg = entry.getKey(); byte[] exceptDigest = entry.getValue(); byte[] actualDigest = actualDigestMap.get(digestAlg); if (!Arrays.equals(actualDigest, exceptDigest)) { - result = false; + isResult = false; LOGGER.error( - "degist data do not match! DigestAlgorithm: {}, actualDigest: <{}> VS exceptDigest : <{}>", - digestAlg.getDigestAlgorithm(), - HapUtils.toHex(actualDigest, ""), - HapUtils.toHex(exceptDigest, "")); + "degist data do not match! DigestAlgorithm: {}, actualDigest: <{}> VS exceptDigest : <{}>", + digestAlg.getDigestAlgorithm(), + HapUtils.toHex(actualDigest, ""), + HapUtils.toHex(exceptDigest, "")); } - LOGGER.info("Digest verify result: {}, DigestAlgorithm: {}", result, digestAlg.getDigestAlgorithm()); + LOGGER.info("Digest verify result: {}, DigestAlgorithm: {}", isResult, digestAlg.getDigestAlgorithm()); } - return result; + return isResult; } private void printCert(X509Certificate cert) throws CertificateEncodingException { diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/verify/VerifyElf.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/verify/VerifyElf.java new file mode 100644 index 0000000000000000000000000000000000000000..1a1fc80f736f96e236a115287ced1244cd90e884 --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/verify/VerifyElf.java @@ -0,0 +1,302 @@ +/* + * Copyright (c) 2021-2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.ohos.hapsigntool.hap.verify; + +import com.ohos.hapsigntool.api.model.Options; +import com.ohos.hapsigntool.codesigning.exception.FsVerityDigestException; +import com.ohos.hapsigntool.codesigning.exception.VerifyCodeSignException; +import com.ohos.hapsigntool.codesigning.sign.VerifyCodeSignature; +import com.ohos.hapsigntool.hap.entity.ElfBlockData; +import com.ohos.hapsigntool.hap.entity.HwBlockHead; +import com.ohos.hapsigntool.hap.entity.HwSignHead; +import com.ohos.hapsigntool.hap.entity.SignatureBlockTypes; +import com.ohos.hapsigntool.hap.entity.SigningBlock; +import com.ohos.hapsigntool.hap.exception.ProfileException; +import com.ohos.hapsigntool.hap.sign.SignElf; +import com.ohos.hapsigntool.utils.FileUtils; +import com.ohos.hapsigntool.utils.ParamConstants; +import com.ohos.hapsigntool.utils.StringUtils; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.bouncycastle.cms.CMSException; +import org.bouncycastle.cms.CMSSignedData; +import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.bouncycastle.openssl.jcajce.JcaPEMWriter; + +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.io.OutputStream; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.security.Security; +import java.security.cert.X509Certificate; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Class of verify ELF. + * + * @since 2023/11/23 + */ +public class VerifyElf { + private static final Logger LOGGER = LogManager.getLogger(VerifyElf.class); + + static { + Security.addProvider(new BouncyCastleProvider()); + } + + private static String getProfileContent(byte[] profile) throws ProfileException { + try { + CMSSignedData cmsSignedData = new CMSSignedData(profile); + if (!VerifyUtils.verifyCmsSignedData(cmsSignedData)) { + throw new ProfileException("Verify profile pkcs7 failed! Profile is invalid"); + } + Object contentObj = cmsSignedData.getSignedContent().getContent(); + if (!(contentObj instanceof byte[])) { + throw new ProfileException("Check profile failed, signed profile content is not byte array!"); + } + return new String((byte[]) contentObj, StandardCharsets.UTF_8); + } catch (CMSException e) { + return new String(profile, StandardCharsets.UTF_8); + } + } + + + /** + * Check whether parameters are valid + * + * @param options input parameters used to verify ELF. + * @return true, if all parameters are valid. + */ + public boolean checkParams(Options options) { + if (!options.containsKey(ParamConstants.PARAM_VERIFY_CERTCHAIN_FILE)) { + LOGGER.error("Missing parameter: {}", ParamConstants.PARAM_VERIFY_CERTCHAIN_FILE); + return false; + } + if (!options.containsKey(ParamConstants.PARAM_VERIFY_PROFILE_FILE)) { + LOGGER.error("Missing parameter: {}", ParamConstants.PARAM_VERIFY_PROFILE_FILE); + return false; + } + if (!options.containsKey(ParamConstants.PARAM_VERIFY_PROOF_FILE)) { + LOGGER.warn("Missing parameter: {}", ParamConstants.PARAM_VERIFY_PROOF_FILE); + } + return true; + } + + /** + * verify elf file. + * + * @param options input parameters used to verify elf. + * @return true, if verify successfully. + */ + public boolean verify(Options options) { + VerifyResult verifyResult; + try { + if (!checkParams(options)) { + LOGGER.error("Check params failed!"); + throw new IOException(); + } + String filePath = options.getString(ParamConstants.PARAM_BASIC_INPUT_FILE); + if (StringUtils.isEmpty(filePath)) { + LOGGER.error("Not found verify file path!"); + throw new IOException(); + } + File signedFile = new File(filePath); + if (!checkSignFile(signedFile)) { + LOGGER.error("Check input signature ELF false!"); + throw new IOException(); + } + verifyResult = verifyElf(filePath); + if (!verifyResult.isVerified()) { + LOGGER.error("verify: {}", verifyResult.getMessage()); + throw new IOException(); + } + String outputCertPath = options.getString(ParamConstants.PARAM_VERIFY_CERTCHAIN_FILE); + if (verifyResult.getCertificates() != null) { + writeCertificate(outputCertPath, verifyResult.getCertificates()); + } + } catch (IOException e) { + LOGGER.error("Write certificate chain error", e); + return false; + } + + String outputProfileFile = options.getString(ParamConstants.PARAM_VERIFY_PROFILE_FILE); + try { + outputOptionalBlocks(outputProfileFile, verifyResult); + } catch (IOException e) { + LOGGER.error("Output optional blocks error", e); + return false; + } + + LOGGER.info("verify: {}", verifyResult.getMessage()); + return true; + } + + private void writeCertificate(String destFile, List certificates) throws IOException { + try (JcaPEMWriter writer = new JcaPEMWriter(new FileWriter(destFile))) { + for (final X509Certificate cert : certificates) { + writer.write(cert.getSubjectDN().toString() + System.lineSeparator()); + writer.writeObject(cert); + } + LOGGER.info("Write certificate chain success!"); + } + } + + private void outputOptionalBlocks(String outputProfileFile, VerifyResult verifyResult) throws IOException { + byte[] profile = verifyResult.getProfile(); + if (profile != null) { + writeOptionalBytesToFile(profile, outputProfileFile); + } + } + + private void writeOptionalBytesToFile(byte[] data, String outputFile) throws IOException { + if (outputFile == null || outputFile.isEmpty()) { + return; + } + try (OutputStream out = Files.newOutputStream(Paths.get(outputFile))) { + out.write(data); + out.flush(); + } + } + + private boolean checkSignFile(File signedFile) { + try { + FileUtils.isValidFile(signedFile); + } catch (IOException e) { + LOGGER.error("signedFile is invalid.", e); + return false; + } + return true; + } + + /** + * Verify elf file. + * + * @param binFile path of elf file. + * @return true, if verify successfully. + */ + public VerifyResult verifyElf(String binFile) { + VerifyResult result = new VerifyResult(true, VerifyResult.RET_SUCCESS, "verify signature success"); + File bin = new File(binFile); + try { + byte[] bytes = FileUtils.readFile(bin); + ElfBlockData elfSignBlockData = getElfSignBlockData(bytes); + String profileJson; + byte[] profileByte; + Map signBlock = getSignBlock(bytes, elfSignBlockData); + if (signBlock.containsKey(SignatureBlockTypes.PROFILE_NOSIGNED_BLOCK)) { + profileByte = signBlock.get(SignatureBlockTypes.PROFILE_NOSIGNED_BLOCK).getValue(); + profileJson = new String(profileByte, StandardCharsets.UTF_8); + result.setProfile(profileByte); + LOGGER.warn("profile is not signed"); + } else if (signBlock.containsKey(SignatureBlockTypes.PROFILE_SIGNED_BLOCK)) { + // verify signed profile + SigningBlock profileSign = signBlock.get(SignatureBlockTypes.PROFILE_SIGNED_BLOCK); + profileByte = profileSign.getValue(); + profileJson = getProfileContent(profileByte); + result = new HapVerify().verifyElfProfile(profileSign.getValue()); + result.setProfile(profileByte); + LOGGER.info("verify profile success"); + } else { + LOGGER.warn("can not found profile sign block"); + profileJson = null; + } + + if (signBlock.containsKey(SignElf.CODESIGN_BLOCK_TYPE)) { + // verify codesign + SigningBlock codesign = signBlock.get(SignElf.CODESIGN_BLOCK_TYPE); + if (!VerifyCodeSignature.verifyElf(bin, codesign.getOffset(), codesign.getLength(), + "elf", profileJson)) { + String errMsg = "Verify codesign error!"; + result = new VerifyResult(false, VerifyResult.RET_IO_ERROR, errMsg); + } + LOGGER.info("verify codesign success"); + } else { + LOGGER.warn("can not found code sign block"); + } + } catch (IOException e) { + LOGGER.error("Verify file has IO error!", e); + result = new VerifyResult(false, VerifyResult.RET_IO_ERROR, e.getMessage()); + } catch (FsVerityDigestException | VerifyCodeSignException e) { + LOGGER.error("Verify codesign error!", e); + result = new VerifyResult(false, VerifyResult.RET_IO_ERROR, e.getMessage()); + } catch (CMSException | ProfileException e) { + LOGGER.error("Verify profile error!", e); + result = new VerifyResult(false, VerifyResult.RET_IO_ERROR, e.getMessage()); + } + return result; + } + + private ElfBlockData getElfSignBlockData(byte[] bytes) throws IOException { + int offset = bytes.length - HwSignHead.SIGN_HEAD_LEN; + byte[] magicByte = readByteArrayOffset(bytes, offset, HwSignHead.ELF_MAGIC.length); + offset += HwSignHead.ELF_MAGIC.length; + byte[] versionByte = readByteArrayOffset(bytes, offset, HwSignHead.VERSION.length); + offset += HwSignHead.VERSION.length; + for (int i = 0; i < HwSignHead.ELF_MAGIC.length; i++) { + if (HwSignHead.ELF_MAGIC[i] != magicByte[i]) { + throw new IOException("elf magic verify failed"); + } + } + for (int i = 0; i < HwSignHead.VERSION.length; i++) { + if (HwSignHead.VERSION[i] != versionByte[i]) { + throw new IOException("elf sign version verify failed"); + } + } + int intByteLength = 4; + byte[] blockSizeByte = readByteArrayOffset(bytes, offset, intByteLength); + offset += intByteLength; + byte[] blockNumByte = readByteArrayOffset(bytes, offset, intByteLength); + ByteBuffer blockNumBf = ByteBuffer.wrap(blockNumByte).order(ByteOrder.LITTLE_ENDIAN); + int blockNum = blockNumBf.getInt(); + + ByteBuffer blockSizeBf = ByteBuffer.wrap(blockSizeByte).order(ByteOrder.LITTLE_ENDIAN); + int blockSize = blockSizeBf.getInt(); + + int blockStart = bytes.length - HwSignHead.SIGN_HEAD_LEN - blockSize; + return new ElfBlockData(blockNum, blockStart); + } + + private Map getSignBlock(byte[] bytes, ElfBlockData elfBlockData) throws ProfileException { + int offset = elfBlockData.getBlockStart(); + + Map blockMap = new HashMap<>(); + for (int i = 0; i < elfBlockData.getBlockNum(); i++) { + byte[] blockByte = readByteArrayOffset(bytes, offset, HwBlockHead.ELF_BLOCK_LEN); + ByteBuffer blockBuffer = ByteBuffer.wrap(blockByte).order(ByteOrder.LITTLE_ENDIAN); + char type = blockBuffer.getChar(); + char tag = blockBuffer.getChar(); + int length = blockBuffer.getInt(); + int blockOffset = blockBuffer.getInt(); + byte[] value = readByteArrayOffset(bytes, elfBlockData.getBlockStart() + blockOffset, length); + blockMap.put(type, new SigningBlock(type, value, elfBlockData.getBlockStart() + blockOffset)); + offset += HwBlockHead.ELF_BLOCK_LEN; + } + return blockMap; + } + + private byte[] readByteArrayOffset(byte[] bytes, int offset, int length) { + byte[] output = new byte[length]; + System.arraycopy(bytes, offset, output, 0, length); + return output; + } +} \ No newline at end of file diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/verify/VerifyHap.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/verify/VerifyHap.java index c5c5834cd78020f1436df6da0773e29b6104fc07..56e634bd9b0d06442a5bf00560bdc8bc454595a6 100644 --- a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/verify/VerifyHap.java +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/verify/VerifyHap.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021-2022 Huawei Device Co., Ltd. + * Copyright (c) 2021-2023 Huawei Device Co., Ltd. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at @@ -16,9 +16,13 @@ package com.ohos.hapsigntool.hap.verify; import com.ohos.hapsigntool.api.model.Options; +import com.ohos.hapsigntool.codesigning.exception.FsVerityDigestException; +import com.ohos.hapsigntool.codesigning.exception.VerifyCodeSignException; +import com.ohos.hapsigntool.codesigning.sign.VerifyCodeSignature; import com.ohos.hapsigntool.hap.entity.Pair; import com.ohos.hapsigntool.hap.entity.SigningBlock; import com.ohos.hapsigntool.hap.exception.HapFormatException; +import com.ohos.hapsigntool.hap.exception.ProfileException; import com.ohos.hapsigntool.hap.exception.SignatureNotFoundException; import com.ohos.hapsigntool.utils.FileUtils; import com.ohos.hapsigntool.utils.HapUtils; @@ -26,34 +30,41 @@ import com.ohos.hapsigntool.utils.ParamConstants; import com.ohos.hapsigntool.utils.StringUtils; import com.ohos.hapsigntool.zip.ByteBufferZipDataInput; import com.ohos.hapsigntool.zip.RandomAccessFileZipDataInput; +import com.ohos.hapsigntool.zip.UnsignedDecimalUtil; import com.ohos.hapsigntool.zip.ZipDataInput; import com.ohos.hapsigntool.zip.ZipFileInfo; import com.ohos.hapsigntool.zip.ZipUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.bouncycastle.cms.CMSException; +import org.bouncycastle.cms.CMSSignedData; import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.bouncycastle.openssl.jcajce.JcaPEMWriter; import org.bouncycastle.util.Arrays; import java.io.File; -import java.io.FileOutputStream; import java.io.FileWriter; import java.io.IOException; import java.io.OutputStream; import java.io.RandomAccessFile; import java.nio.ByteBuffer; import java.nio.ByteOrder; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Paths; import java.security.Security; import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; /** * Class of verify hap. * - * @2021/12/23 + * @since 2021/12/23 */ public class VerifyHap { private static final Logger LOGGER = LogManager.getLogger(VerifyHap.class); @@ -61,20 +72,37 @@ public class VerifyHap { private static final int ZIP_HEAD_OF_SIGNING_BLOCK_COUNT_OFFSET_REVERSE = 28; private static final int ZIP_HEAD_OF_SUBSIGNING_BLOCK_LENGTH = 12; - private final boolean printCert; + static { + Security.addProvider(new BouncyCastleProvider()); + } + + private final boolean isPrintCert; public VerifyHap() { this(true); } - public VerifyHap(boolean printCert) { - this.printCert = printCert; + public VerifyHap(boolean isPrintCert) { + this.isPrintCert = isPrintCert; } - static { - Security.addProvider(new BouncyCastleProvider()); + private static String getProfileContent(byte[] profile) throws ProfileException { + try { + CMSSignedData cmsSignedData = new CMSSignedData(profile); + if (!VerifyUtils.verifyCmsSignedData(cmsSignedData)) { + throw new ProfileException("Verify profile pkcs7 failed! Profile is invalid"); + } + Object contentObj = cmsSignedData.getSignedContent().getContent(); + if (!(contentObj instanceof byte[])) { + throw new ProfileException("Check profile failed, signed profile content is not byte array!"); + } + return new String((byte[]) contentObj, StandardCharsets.UTF_8); + } catch (CMSException e) { + return new String(profile, StandardCharsets.UTF_8); + } } + /** * Check whether parameters are valid * @@ -110,7 +138,6 @@ public class VerifyHap { throw new IOException(); } String filePath = options.getString(ParamConstants.PARAM_BASIC_INPUT_FILE); - String outputCertPath = options.getString(ParamConstants.PARAM_VERIFY_CERTCHAIN_FILE); if (StringUtils.isEmpty(filePath)) { LOGGER.error("Not found verify file path!"); throw new IOException(); @@ -125,8 +152,10 @@ public class VerifyHap { LOGGER.error("verify: {}", verifyResult.getMessage()); throw new IOException(); } - - writeCertificate(outputCertPath, verifyResult.getCertificates()); + String outputCertPath = options.getString(ParamConstants.PARAM_VERIFY_CERTCHAIN_FILE); + if (verifyResult.getCertificates() != null) { + writeCertificate(outputCertPath, verifyResult.getCertificates()); + } } catch (IOException e) { LOGGER.error("Write certificate chain error", e); return false; @@ -157,7 +186,7 @@ public class VerifyHap { } private void outputOptionalBlocks(String outputProfileFile, String outputProofFile, String outputPropertyFile, - VerifyResult verifyResult) throws IOException { + VerifyResult verifyResult) throws IOException { List optionalBlocks = verifyResult.getOptionalBlocks(); if (optionalBlocks != null && optionalBlocks.size() > 0) { for (SigningBlock optionalBlock : optionalBlocks) { @@ -177,13 +206,17 @@ public class VerifyHap { } } } + byte[] profile = verifyResult.getProfile(); + if (profile != null) { + writeOptionalBytesToFile(profile, outputProfileFile); + } } private void writeOptionalBytesToFile(byte[] data, String outputFile) throws IOException { if (outputFile == null || outputFile.isEmpty()) { return; } - try (OutputStream out = new FileOutputStream(outputFile)) { + try (OutputStream out = Files.newOutputStream(Paths.get(outputFile))) { out.write(data); out.flush(); } @@ -199,31 +232,6 @@ public class VerifyHap { return true; } - /** - * Verify signature of hap. - * - * @param hapFilePath path of hap file - * @param outCertPath path to output certificate file - * @param outProvisionFile path to output provision file - * @return verify result - */ - public VerifyResult verifyHap(String hapFilePath, String outCertPath, String outProvisionFile) { - VerifyResult verifyResult = verifyHap(hapFilePath); - if (!verifyResult.isVerified()) { - return verifyResult; - } - List certificates = verifyResult.getCertificates(); - try { - writeCertificate(outCertPath, certificates); - outputOptionalBlocks(outProvisionFile, null, null, verifyResult); - } catch (IOException e) { - LOGGER.error("Write certificate chain or profile error", e); - verifyResult.setResult(false); - return verifyResult; - } - return verifyResult; - } - /** * Verify hap file. * @@ -248,16 +256,12 @@ public class VerifyHap { ByteBuffer signatureSchemeBlock = blockPair.getFirst(); List optionalBlocks = blockPair.getSecond(); Collections.reverse(optionalBlocks); - long signingBlockOffset = hapSigningBlockAndOffsetInFile.getOffset(); - ZipDataInput beforeHapSigningBlock = hapFile.slice(0, signingBlockOffset); - ZipDataInput centralDirectoryBlock = hapFile.slice(zipInfo.getCentralDirectoryOffset(), - zipInfo.getCentralDirectorySize()); - ByteBuffer eocdBbyteBuffer = zipInfo.getEocd(); - ZipUtils.setCentralDirectoryOffset(eocdBbyteBuffer, signingBlockOffset); - ZipDataInput eocdBlock = new ByteBufferZipDataInput(eocdBbyteBuffer); - HapVerify verifyEngine = new HapVerify(beforeHapSigningBlock, signatureSchemeBlock, - centralDirectoryBlock, eocdBlock, optionalBlocks); - verifyEngine.setPrintCert(printCert); + if (!checkCodeSign(hapFilePath, optionalBlocks)) { + String errMsg = "code sign verify failed"; + return new VerifyResult(false, VerifyResult.RET_CODESIGN_DATA_ERROR, errMsg); + } + HapVerify verifyEngine = getHapVerify(hapFile, zipInfo, hapSigningBlockAndOffsetInFile, + signatureSchemeBlock, optionalBlocks); result = verifyEngine.verify(); result.setSignBlockVersion(hapSigningBlockAndOffsetInFile.getVersion()); } catch (IOException e) { @@ -269,19 +273,97 @@ public class VerifyHap { } catch (HapFormatException e) { LOGGER.error("Verify Hap failed, unsupported format hap.", e); result = new VerifyResult(false, VerifyResult.RET_UNSUPPORTED_FORMAT_ERROR, e.getMessage()); + } catch (FsVerityDigestException e) { + LOGGER.error("Verify Hap failed, fs-verity digest generate failed.", e); + result = new VerifyResult(false, VerifyResult.RET_DIGEST_ERROR, e.getMessage()); + } catch (VerifyCodeSignException e) { + LOGGER.error("Verify Hap failed, code sign block verify failed.", e); + result = new VerifyResult(false, VerifyResult.RET_CODE_SIGN_BLOCK_ERROR, e.getMessage()); + } catch (CMSException e) { + LOGGER.error("Verify Hap failed, code signature verify failed.", e); + result = new VerifyResult(false, VerifyResult.RET_SIGNATURE_ERROR, e.getMessage()); + } catch (ProfileException e) { + LOGGER.error("Verify Hap failed, parse app-identifier from profile failed, profile is invalid", e); + return new VerifyResult(false, VerifyResult.RET_CODE_SIGN_BLOCK_ERROR, e.getMessage()); } return result; } + private HapVerify getHapVerify(ZipDataInput hapFile, ZipFileInfo zipInfo, + HapUtils.HapSignBlockInfo hapSigningBlockAndOffsetInFile, + ByteBuffer signatureSchemeBlock, List optionalBlocks) { + long signingBlockOffset = hapSigningBlockAndOffsetInFile.getOffset(); + ZipDataInput beforeHapSigningBlock = hapFile.slice(0, signingBlockOffset); + ZipDataInput centralDirectoryBlock = hapFile.slice(zipInfo.getCentralDirectoryOffset(), + zipInfo.getCentralDirectorySize()); + ByteBuffer eocdBbyteBuffer = zipInfo.getEocd(); + ZipUtils.setCentralDirectoryOffset(eocdBbyteBuffer, signingBlockOffset); + ZipDataInput eocdBlock = new ByteBufferZipDataInput(eocdBbyteBuffer); + HapVerify verifyEngine = new HapVerify(beforeHapSigningBlock, signatureSchemeBlock, + centralDirectoryBlock, eocdBlock, optionalBlocks); + verifyEngine.setIsPrintCert(isPrintCert); + return verifyEngine; + } + + /** + * code sign check + * + * @param hapFilePath hap file path + * @param optionalBlocks optional blocks + * @return true or false + * @throws FsVerityDigestException FsVerity digest on error + * @throws IOException IO error + * @throws VerifyCodeSignException verify code sign on error + * @throws CMSException cms on error + * @throws ProfileException profile of the hap error + */ + private boolean checkCodeSign(String hapFilePath, List optionalBlocks) + throws FsVerityDigestException, IOException, VerifyCodeSignException, CMSException, ProfileException { + Map map = optionalBlocks.stream() + .collect(Collectors.toMap(SigningBlock::getType, SigningBlock::getValue)); + byte[] propertyBlockArray = map.get(HapUtils.HAP_PROPERTY_BLOCK_ID); + if (propertyBlockArray != null && propertyBlockArray.length > 0) { + LOGGER.info("trying verify codesign block"); + String[] fileNameArray = hapFilePath.split("\\."); + if (fileNameArray.length < ParamConstants.FILE_NAME_MIN_LENGTH) { + LOGGER.error("ZIP64 format not supported"); + return false; + } + ByteBuffer byteBuffer = ByteBuffer.wrap(propertyBlockArray); + ByteBuffer header = HapUtils.reverseSliceBuffer(byteBuffer, 0, ZIP_HEAD_OF_SUBSIGNING_BLOCK_LENGTH); + long blockOffset = UnsignedDecimalUtil.getUnsignedInt(header); + int blockLength = header.getInt(); + int blockType = header.getInt(); + if (blockType != HapUtils.HAP_CODE_SIGN_BLOCK_ID) { + LOGGER.error("Verify Hap has no code sign data error!"); + return false; + } + File outputFile = new File(hapFilePath); + byte[] profileArray = map.get(HapUtils.HAP_PROFILE_BLOCK_ID); + String profileContent = getProfileContent(profileArray); + String suffix = fileNameArray[fileNameArray.length - 1]; + boolean isCodeSign = VerifyCodeSignature.verifyHap(outputFile, blockOffset, blockLength, + suffix, profileContent); + if (!isCodeSign) { + LOGGER.error("Verify Hap has no code sign data error!"); + return false; + } + LOGGER.info("verify codesign success"); + return true; + } + LOGGER.info("can not find codesign block"); + return true; + } + private Pair> getHapSignatureSchemeBlockAndOptionalBlocks(ByteBuffer hapSigningBlock) throws SignatureNotFoundException { try { ByteBuffer header = HapUtils.reverseSliceBuffer( - hapSigningBlock, - hapSigningBlock.capacity() - ZIP_HEAD_OF_SIGNING_BLOCK_LENGTH, - hapSigningBlock.capacity()); + hapSigningBlock, + hapSigningBlock.capacity() - ZIP_HEAD_OF_SIGNING_BLOCK_LENGTH, + hapSigningBlock.capacity()); ByteBuffer value = HapUtils.reverseSliceBuffer(hapSigningBlock, 0, - hapSigningBlock.capacity() - ZIP_HEAD_OF_SIGNING_BLOCK_LENGTH); + hapSigningBlock.capacity() - ZIP_HEAD_OF_SIGNING_BLOCK_LENGTH); byte[] signatureValueBytes = new byte[value.capacity()]; value.get(signatureValueBytes, 0, signatureValueBytes.length); @@ -301,8 +383,8 @@ public class VerifyHap { blockLength = value.getInt(); blockType = value.getInt(); if (blockOffset + blockLength > signatureValueBytes.length) { - throw new SignatureNotFoundException("block end pos: " + (blockOffset + blockLength) + - " is larger than block len: " + signatureValueBytes.length); + throw new SignatureNotFoundException("block end pos: " + (blockOffset + blockLength) + + " is larger than block len: " + signatureValueBytes.length); } if (HapUtils.getHapSignatureOptionalBlockIds().contains(blockType)) { byte[] blockValue = Arrays.copyOfRange(signatureValueBytes, blockOffset, blockOffset + blockLength); diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/verify/VerifyResult.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/verify/VerifyResult.java index 484a23c6eb9f463ba89886f394c4ee4bbb55697e..ba032e84625eca6fdf6e1c75f217f20462f8ddec 100644 --- a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/verify/VerifyResult.java +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/verify/VerifyResult.java @@ -16,6 +16,7 @@ package com.ohos.hapsigntool.hap.verify; import com.ohos.hapsigntool.hap.entity.SigningBlock; + import org.bouncycastle.cert.X509CertificateHolder; import org.bouncycastle.cms.SignerInformation; import org.bouncycastle.util.Store; @@ -90,7 +91,17 @@ public class VerifyResult { */ public static final int RET_CRL_ERROR = 10011; - private boolean result; + /** + * Return code of file code sign data error. + */ + public static final int RET_CODESIGN_DATA_ERROR = 10012; + + /** + * Return code of verify code sign error. + */ + public static final int RET_CODE_SIGN_BLOCK_ERROR = 10013; + + private boolean isResult; private int code; private String message; @@ -106,6 +117,8 @@ public class VerifyResult { private int signBlockVersion; + private byte[] profile; + /** * Empty constructor */ @@ -115,22 +128,22 @@ public class VerifyResult { /** * Verify result constructor * - * @param result verify result + * @param isResult verify result * @param code error code * @param message error message */ - public VerifyResult(boolean result, int code, String message) { - this.result = result; + public VerifyResult(boolean isResult, int code, String message) { + this.isResult = isResult; this.code = code; this.message = message; } public boolean isVerified() { - return result; + return isResult; } - public void setResult(boolean result) { - this.result = result; + public void setIsResult(boolean isResult) { + this.isResult = isResult; } public int getCode() { @@ -196,4 +209,12 @@ public class VerifyResult { public void setSignBlockVersion(int signBlockVersion) { this.signBlockVersion = signBlockVersion; } + + public byte[] getProfile() { + return profile; + } + + public void setProfile(byte[] profile) { + this.profile = profile; + } } diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/profile/IProvisionVerifier.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/profile/IProvisionVerifier.java index db420e14af926081391e4cb38f4e4e2b2263cdae..3c88f3cebf24d80b24118b48dedec1d4d504da72 100644 --- a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/profile/IProvisionVerifier.java +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/profile/IProvisionVerifier.java @@ -15,6 +15,7 @@ package com.ohos.hapsigntool.profile; +import com.ohos.hapsigntool.error.VerifyException; import com.ohos.hapsigntool.profile.model.VerificationResult; /** @@ -29,6 +30,7 @@ public interface IProvisionVerifier { * * @param p7b signed p7b content * @return result + * @throws VerifyException verify p7b failed */ - VerificationResult verify(byte[] p7b); + VerificationResult verify(byte[] p7b) throws VerifyException; } diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/profile/ProfileSignTool.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/profile/ProfileSignTool.java index cc58c3fb869b9eff68541c0f9f05761573edb06f..a71cbdde570b0798ab8cdef5e5ea48c630ae40a3 100644 --- a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/profile/ProfileSignTool.java +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/profile/ProfileSignTool.java @@ -18,6 +18,7 @@ package com.ohos.hapsigntool.profile; import com.ohos.hapsigntool.api.LocalizationAdapter; import com.ohos.hapsigntool.error.CustomException; import com.ohos.hapsigntool.error.ERROR; +import com.ohos.hapsigntool.error.VerifyException; import com.ohos.hapsigntool.profile.model.VerificationResult; import com.ohos.hapsigntool.signer.ISigner; import com.ohos.hapsigntool.signer.SignerFactory; @@ -88,7 +89,12 @@ public final class ProfileSignTool { ISigner signer = new SignerFactory().getSigner(adapter); byte[] p7b = signProfile(content, signer, adapter.getSignAlg()); VerifyHelper verifyHelper = new VerifyHelper(); - VerificationResult verificationResult = verifyHelper.verify(p7b); + VerificationResult verificationResult = null; + try { + verificationResult = verifyHelper.verify(p7b); + } catch (VerifyException e) { + CustomException.throwException(ERROR.VERIFY_ERROR, "Generate Profile Failed! " + e.getMessage()); + } ValidateUtils.throwIfNotMatches(verificationResult.isVerifiedPassed(), ERROR.SIGN_ERROR, verificationResult.getMessage()); return p7b; diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/profile/VerifyHelper.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/profile/VerifyHelper.java index 575d50d2a624e26260991e3a5e94fc164bf885e3..71d691cd11ced2bd27bf9f11abb34abd403afa71 100644 --- a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/profile/VerifyHelper.java +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/profile/VerifyHelper.java @@ -18,6 +18,7 @@ package com.ohos.hapsigntool.profile; import com.google.gson.JsonObject; import com.ohos.hapsigntool.error.CustomException; import com.ohos.hapsigntool.error.ERROR; +import com.ohos.hapsigntool.error.VerifyException; import com.ohos.hapsigntool.hap.verify.VerifyUtils; import com.ohos.hapsigntool.profile.model.VerificationResult; import com.ohos.hapsigntool.utils.CertChainUtils; @@ -129,9 +130,10 @@ public class VerifyHelper implements IProvisionVerifier { * * @param p7b signed p7b content * @return result + * @throws VerifyException verify p7b failed */ @Override - public VerificationResult verify(byte[] p7b) { + public VerificationResult verify(byte[] p7b) throws VerifyException { VerificationResult result = new VerificationResult(); try { diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/utils/CertChainUtils.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/utils/CertChainUtils.java index 1ddd9dce9bd8a44715d028a809629625fe88b4a8..28f4e3e8b468f6fdec282de5ed72a9c8be0f0dc9 100644 --- a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/utils/CertChainUtils.java +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/utils/CertChainUtils.java @@ -17,6 +17,7 @@ package com.ohos.hapsigntool.utils; import com.ohos.hapsigntool.error.CustomException; import com.ohos.hapsigntool.error.ERROR; +import com.ohos.hapsigntool.error.VerifyException; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -88,9 +89,10 @@ public class CertChainUtils { * @param serial serial number * @param root root cert * @param signTime signing time + * @throws VerifyException verifyException */ public static void verifyCertChain(List certificates, X500Principal issuer, BigInteger serial, - X509Certificate root, Date signTime) { + X509Certificate root, Date signTime) throws VerifyException { try { KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType()); trustStore.load(null, null); @@ -115,8 +117,7 @@ public class CertChainUtils { } } catch (InvalidAlgorithmParameterException | NoSuchAlgorithmException | IOException | CertificateException | KeyStoreException | CertPathBuilderException | CertPathValidatorException exception) { - LOGGER.debug(exception.getMessage(), exception); - CustomException.throwException(ERROR.VERIFY_ERROR, "Failed to verify signature: " + exception.getMessage()); + throw new VerifyException("Cert chain verify failed! " + exception.getMessage()); } } } diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/utils/FileUtils.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/utils/FileUtils.java index cc8d917f487594fcac05e88c72c3d915ebec73bd..b61266aeeb79be65d51c146f8f46d65c6e84570f 100644 --- a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/utils/FileUtils.java +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/utils/FileUtils.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021-2022 Huawei Device Co., Ltd. + * Copyright (c) 2021-2023 Huawei Device Co., Ltd. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at @@ -18,6 +18,7 @@ package com.ohos.hapsigntool.utils; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.ohos.hapsigntool.error.ERROR; + import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -35,6 +36,10 @@ import java.io.OutputStream; import java.io.OutputStreamWriter; import java.nio.charset.Charset; import java.nio.file.Files; +import java.util.HashMap; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; /** * Common file operation. @@ -42,27 +47,36 @@ import java.nio.file.Files; * @since 2021/12/28 */ public final class FileUtils { - /** * LOGGER. */ private static final Logger LOGGER = LogManager.getLogger(FileUtils.class); + + /** + * suffix regex map + */ + public static final Map SUFFIX_REGEX_MAP = new HashMap<>(); + /** * add GSON static. */ public static final Gson GSON = (new GsonBuilder()).disableHtmlEscaping().create(); + /** * add GSON_PRETTY_PRINT static. */ public static final Gson GSON_PRETTY_PRINT = (new GsonBuilder()).disableHtmlEscaping().setPrettyPrinting().create(); + /** * File reader block size */ - public static final int FILE_BUFFER_BLOCK = 4096; + public static final int FILE_BUFFER_BLOCK = 1024 * 1024; + /** * File end */ public static final int FILE_END = -1; + /** * Expected split string length */ @@ -70,6 +84,10 @@ public final class FileUtils { private static final byte[] EMPTY_BYTE_ARRAY = new byte[0]; + static { + SUFFIX_REGEX_MAP.put("so", Pattern.compile("\\.so(\\.[0-9]*){0,3}$")); + } + private FileUtils() { } @@ -96,7 +114,7 @@ public final class FileUtils { * @throws IOException Read failed */ public static byte[] readFile(File file) throws IOException { - return read(new FileInputStream(file)); + return read(Files.newInputStream(file.toPath())); } /** @@ -119,6 +137,68 @@ public final class FileUtils { } } + /** + * Read byte from input file. + * + * @param file input file + * @param offset offset + * @param length length + * @return data bytes + */ + public static byte[] readFileByOffsetAndLength(File file, long offset, long length) throws IOException { + try (FileInputStream input = new FileInputStream(file)) { + return readInputByOffsetAndLength(input, offset, length); + } + } + + /** + * Read byte from input stream. + * + * @param input input stream + * @param offset offset + * @param length length + * @return data bytes + * @throws IOException read exception + */ + public static byte[] readInputByOffsetAndLength(InputStream input, long offset, long length) throws IOException { + input.skip(offset); + return readInputByLength(input, length); + } + + /** + * Read byte from input stream. + * + * @param input InputStream + * @param length length + * @return data bytes + */ + public static byte[] readInputByLength(InputStream input, long length) throws IOException { + try (ByteArrayOutputStream output = new ByteArrayOutputStream()) { + if (length > Integer.MAX_VALUE) { + throw new IOException("Size cannot be greater than Integer max value: " + length); + } + writeInputToOutPut(input, output, length); + return output.toByteArray(); + } + } + + /** + * write input to output by length + */ + private static void writeInputToOutPut(InputStream input, OutputStream output, long length) throws IOException { + byte[] buffer = new byte[FILE_BUFFER_BLOCK]; + long hasReadLen = 0L; + while (hasReadLen < length) { + int readLen = (int) Math.min(length - hasReadLen, FILE_BUFFER_BLOCK); + int len = input.read(buffer, 0, readLen); + if (len != readLen) { + throw new IOException("read" + hasReadLen + "bytes data less than " + length); + } + output.write(buffer, 0, len); + hasReadLen += len; + } + } + /** * Out put content to file. * @@ -134,6 +214,29 @@ public final class FileUtils { } } + /** + * Write data in file to output stream + * + * @param inFile input file path. + * @param out output file path. + * @param offset file read offset + * @param size file read size + * @return true, if write successfully. + */ + public static boolean appendWriteFileByOffsetToFile(String inFile, FileOutputStream out, long offset, long size) { + File inputFile = new File(inFile); + try (FileInputStream fis = new FileInputStream(inputFile)) { + fis.skip(offset); + writeInputToOutPut(fis, out, size); + return true; + } catch (FileNotFoundException e) { + LOGGER.error("Failed to get input stream object."); + } catch (IOException e) { + LOGGER.error("Failed to read or write data."); + } + return false; + } + /** * Check file exist or not. * @@ -221,10 +324,13 @@ public final class FileUtils { * @return true, if write successfully. */ public static boolean writeByteToDos(byte[] data, DataOutputStream dos) { + if (data == null) { + return true; + } try { dos.write(data); } catch (IOException e) { - LOGGER.error("Faile to write data to output stream."); + LOGGER.error("Failed to write data to output stream."); return false; } return true; @@ -247,14 +353,32 @@ public final class FileUtils { /** * Write byte array data to output file. * - * @param signHeadByte byte array data. + * @param bytes byte array data. * @param outFile output file path. * @return true, if write successfully. */ - public static boolean writeByteToOutFile(byte[] signHeadByte, String outFile) { + public static boolean writeByteToOutFile(byte[] bytes, String outFile) { try (OutputStream ops = new FileOutputStream(outFile, true)) { - ops.write(signHeadByte, 0, signHeadByte.length); - ops.flush(); + return writeByteToOutFile(bytes, ops); + } catch (FileNotFoundException e) { + LOGGER.error("Failed to get output stream object, outfile: " + outFile); + } catch (IOException e) { + LOGGER.error("Failed to write data to ops, outfile: " + outFile); + } + return false; + } + + /** + * Write byte array data to output file. + * + * @param bytes byte array data. + * @param outFile output file path. + * @return true, if write successfully. + */ + public static boolean writeByteToOutFile(byte[] bytes, OutputStream outFile) { + try { + outFile.write(bytes, 0, bytes.length); + outFile.flush(); return true; } catch (FileNotFoundException e) { LOGGER.error("Failed to get output stream object, outfile: " + outFile); @@ -286,6 +410,7 @@ public final class FileUtils { /** * Open an input stream of input file safely. + * * @param file input file. * @return an input stream of input file * @throws IOException file is a directory or can't be read. @@ -366,4 +491,26 @@ public final class FileUtils { } } } + + /** + * regex filename + * + * @param name filename + * @return boolean + */ + public static boolean isRunnableFile(String name) { + if (StringUtils.isEmpty(name)) { + return false; + } + if (name.endsWith(".an") || name.endsWith(".abc")) { + return true; + } + for (Pattern pattern : SUFFIX_REGEX_MAP.values()) { + Matcher matcher = pattern.matcher(name); + if (matcher.find()) { + return true; + } + } + return false; + } } diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/utils/HapUtils.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/utils/HapUtils.java index e51a3f59505ded067c6a171757b0e1966c9843db..172f8a75789ce473b2aa459476b2d4cef5f80169 100644 --- a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/utils/HapUtils.java +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/utils/HapUtils.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021-2022 Huawei Device Co., Ltd. + * Copyright (c) 2021-2023 Huawei Device Co., Ltd. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at @@ -38,12 +38,12 @@ import java.nio.ByteOrder; import java.security.DigestException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; +import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; -import java.util.Collections; /** * Hap util, parse hap, find signature block. @@ -73,6 +73,11 @@ public class HapUtils { */ public static final int HAP_PROPERTY_BLOCK_ID = 0x20000003; + /** + * ID of property block + */ + public static final int HAP_CODE_SIGN_BLOCK_ID = 0x30000001; + /** * The size of data block used to get digest */ @@ -97,12 +102,12 @@ public class HapUtils { /** * int size */ - public static final int INT_SIZE = 4; + public static final int INT_SIZE = 4; /** * block number */ - public static final int BLOCK_NUMBER = 1; + public static final int BLOCK_NUMBER = 1; /** * hap sign schema v2 signature block version @@ -124,55 +129,67 @@ public class HapUtils { */ public static final long HAP_SIG_BLOCK_MAGIC_HI_V2 = 0x3234206b636f6c42L; - private HapUtils() { - } - /** - * The set of IDs of optional blocks in hap signature block. + * The value of lower 8 bytes of magic word */ - private static final Set HAP_SIGNATURE_OPTIONAL_BLOCK_IDS ; + public static final long HAP_SIG_BLOCK_MAGIC_LO_V3 = 0x676973207061683cL; /** - * Minimum api version for hap sign schema v3. + * The value of higher 8 bytes of magic word */ - private static final int MIN_COMPATIBLE_VERSION_FOR_SCHEMA_V3 = 8; + public static final long HAP_SIG_BLOCK_MAGIC_HI_V3 = 0x3e6b636f6c62206eL; /** - * Magic word of hap signature block v2 + * Size of hap signature block header */ - private static final byte[] HAP_SIGNING_BLOCK_MAGIC_V2 = - new byte[] {0x48, 0x41, 0x50, 0x20, 0x53, 0x69, 0x67, 0x20, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x20, 0x34, 0x32}; + public static final int HAP_SIG_BLOCK_HEADER_SIZE = 32; /** - * Magic word of hap signature block + * The min size of hap signature block */ - private static final byte[] HAP_SIGNING_BLOCK_MAGIC_V3 = - new byte[] {0x3c, 0x68, 0x61, 0x70, 0x20, 0x73, 0x69, 0x67, 0x6e, 0x20, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x3e}; + public static final int HAP_SIG_BLOCK_MIN_SIZE = HAP_SIG_BLOCK_HEADER_SIZE; /** - * The value of lower 8 bytes of magic word + * The set of IDs of optional blocks in hap signature block. */ - public static final long HAP_SIG_BLOCK_MAGIC_LO_V3 = 0x676973207061683cL; + private static final Set HAP_SIGNATURE_OPTIONAL_BLOCK_IDS ; /** - * The value of higher 8 bytes of magic word + * Minimum api version for hap sign schema v3. */ - public static final long HAP_SIG_BLOCK_MAGIC_HI_V3 = 0x3e6b636f6c62206eL; + private static final int MIN_COMPATIBLE_VERSION_FOR_SCHEMA_V3 = 8; /** - * Size of hap signature block header + * Magic word of hap signature block v2 */ - public static final int HAP_SIG_BLOCK_HEADER_SIZE = 32; + private static final byte[] HAP_SIGNING_BLOCK_MAGIC_V2 = + new byte[] {0x48, 0x41, 0x50, 0x20, 0x53, 0x69, 0x67, 0x20, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x20, 0x34, 0x32}; /** - * The min size of hap signature block + * Magic word of hap signature block */ - public static final int HAP_SIG_BLOCK_MIN_SIZE = HAP_SIG_BLOCK_HEADER_SIZE; + private static final byte[] HAP_SIGNING_BLOCK_MAGIC_V3 = + new byte[] {0x3c, 0x68, 0x61, 0x70, 0x20, 0x73, 0x69, 0x67, 0x6e, 0x20, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x3e}; private static final byte ZIP_FIRST_LEVEL_CHUNK_PREFIX = 0x5a; private static final byte ZIP_SECOND_LEVEL_CHUNK_PREFIX = (byte) 0xa5; private static final int DIGEST_PRIFIX_LENGTH = 5; private static final int BUFFER_LENGTH = 4096; + private static final char[] HEX_CHAR_ARRAY = "0123456789ABCDEF".toCharArray(); + + /** + * The set of IDs of optional blocks in hap signature block. + */ + static { + Set blockIds = new HashSet(); + blockIds.add(HAP_PROOF_OF_ROTATION_BLOCK_ID); + blockIds.add(HAP_PROFILE_BLOCK_ID); + blockIds.add(HAP_PROPERTY_BLOCK_ID); + HAP_SIGNATURE_OPTIONAL_BLOCK_IDS = Collections.unmodifiableSet(blockIds); + } + + private HapUtils() { + } /** * Get HAP_SIGNATURE_OPTIONAL_BLOCK_IDS @@ -209,17 +226,6 @@ public class HapUtils { return HAP_SIGN_SCHEME_V2_BLOCK_VERSION; } - /** - * The set of IDs of optional blocks in hap signature block. - */ - static { - Set blockIds = new HashSet(); - blockIds.add(HAP_PROOF_OF_ROTATION_BLOCK_ID); - blockIds.add(HAP_PROFILE_BLOCK_ID); - blockIds.add(HAP_PROPERTY_BLOCK_ID); - HAP_SIGNATURE_OPTIONAL_BLOCK_IDS = Collections.unmodifiableSet(blockIds); - } - /** * Read data from hap file. * @@ -229,7 +235,7 @@ public class HapUtils { */ public static byte[] readFileToByte(String file) throws IOException { try (FileInputStream in = new FileInputStream(file); - ByteArrayOutputStream out = new ByteArrayOutputStream(in.available());) { + ByteArrayOutputStream out = new ByteArrayOutputStream(in.available());) { byte[] buf = new byte[BUFFER_LENGTH]; int len = 0; while ((len = in.read(buf)) != -1) { @@ -242,8 +248,8 @@ public class HapUtils { private static long getChunkCount(ZipDataInput[] contents) { long chunkCount = 0L; for (ZipDataInput content : contents) { - chunkCount += ((content.size() + CONTENT_DIGESTED_CHUNK_MAX_SIZE_BYTES - 1) / - CONTENT_DIGESTED_CHUNK_MAX_SIZE_BYTES); + chunkCount += ((content.size() + CONTENT_DIGESTED_CHUNK_MAX_SIZE_BYTES - 1) + / CONTENT_DIGESTED_CHUNK_MAX_SIZE_BYTES); } return chunkCount; } @@ -267,7 +273,7 @@ public class HapUtils { } int chunkCount = (int) chunkCountLong; ContentDigestAlgorithm[] contentDigestAlgorithms = digestAlgorithms.toArray( - new ContentDigestAlgorithm[digestAlgorithms.size()]); + new ContentDigestAlgorithm[digestAlgorithms.size()]); MessageDigest[] messageDigests = new MessageDigest[contentDigestAlgorithms.length]; int[] digestOutputSizes = new int[contentDigestAlgorithms.length]; byte[][] digestOfChunks = new byte[contentDigestAlgorithms.length][]; @@ -306,7 +312,7 @@ public class HapUtils { for (int i = 0; i < contentDigestAlgorithms.length; i++) { int expectedDigestSizeBytes = digestOutputSizes[i]; int actualDigestSizeBytes = messageDigests[i].digest(digestOfChunks[i], - chunkIndex * expectedDigestSizeBytes + DIGEST_PRIFIX_LENGTH, expectedDigestSizeBytes); + chunkIndex * expectedDigestSizeBytes + DIGEST_PRIFIX_LENGTH, expectedDigestSizeBytes); if (actualDigestSizeBytes != expectedDigestSizeBytes) { throw new DigestException("Unexpected output size of " + messageDigests[i].getAlgorithm() + " digest: " + actualDigestSizeBytes); @@ -334,7 +340,7 @@ public class HapUtils { } private static Map getContentDigestAlgorithmMap(List optionalBlocks, - ContentDigestAlgorithm[] contentDigestAlgorithms, MessageDigest[] messageDigests, byte[][] digestOfChunks) { + ContentDigestAlgorithm[] contentDigestAlgorithms, MessageDigest[] messageDigests, byte[][] digestOfChunks) { Map result = new HashMap<>(contentDigestAlgorithms.length); for (int i = 0; i < contentDigestAlgorithms.length; i++) { messageDigests[i].update(digestOfChunks[i]); @@ -352,8 +358,6 @@ public class HapUtils { } } - private static final char[] HEX_CHAR_ARRAY = "0123456789ABCDEF".toCharArray(); - /** * Slice buffer to target size. * @@ -384,7 +388,7 @@ public class HapUtils { int capacity = source.capacity(); if (startPos < 0 || endPos < startPos || endPos > capacity) { throw new IllegalArgumentException( - "startPos: " + startPos + ", endPos: " + endPos + ", capacity: " + capacity); + "startPos: " + startPos + ", endPos: " + endPos + ", capacity: " + capacity); } int limit = source.limit(); int position = source.position(); @@ -438,7 +442,7 @@ public class HapUtils { int encodeSize = 0; encodeSize += INT_SIZE + INT_SIZE; for (Pair pair : pairList) { - encodeSize += INT_SIZE+INT_SIZE+INT_SIZE + pair.getSecond().length; + encodeSize += INT_SIZE + INT_SIZE + INT_SIZE + pair.getSecond().length; } ByteBuffer encodeBytes = ByteBuffer.allocate(encodeSize); encodeBytes.order(ByteOrder.LITTLE_ENDIAN); @@ -446,7 +450,7 @@ public class HapUtils { encodeBytes.putInt(BLOCK_NUMBER); // block number for (Pair pair : pairList) { byte[] second = pair.getSecond(); - encodeBytes.putInt(INT_SIZE+INT_SIZE + second.length); + encodeBytes.putInt(INT_SIZE + INT_SIZE + second.length); encodeBytes.putInt(pair.getFirst()); encodeBytes.putInt(second.length); encodeBytes.put(second); @@ -507,11 +511,21 @@ public class HapUtils { long hapSignBlockMagicLo = hapSigningBlockHeader.getLong(); long hapSignBlockMagicHi = hapSigningBlockHeader.getLong(); int version = hapSigningBlockHeader.getInt(); + long hapSigningBlockOffset = verifySignBlock(hapSigBlockSize, + hapSignBlockMagicLo, hapSignBlockMagicHi, version, centralDirectoryStartOffset); + ByteBuffer hapSigningBlockByteBuffer = hap.createByteBuffer(hapSigningBlockOffset, (int) hapSigBlockSize) + .order(ByteOrder.LITTLE_ENDIAN); + LOGGER.info("Find Hap Signing Block success, version: {}, block count: {}", version, blockCount); + return new HapSignBlockInfo(hapSigningBlockOffset, version, hapSigningBlockByteBuffer); + } + + private static long verifySignBlock(long hapSigBlockSize, long hapSignBlockMagicLo, + long hapSignBlockMagicHi, int version, long centralDirectoryStartOffset) throws SignatureNotFoundException { if (!isVersionAndMagicNumValid(version, hapSignBlockMagicLo, hapSignBlockMagicHi)) { throw new SignatureNotFoundException("No Hap Signing Block before ZIP Central Directory"); } - if ((hapSigBlockSize < HAP_SIG_BLOCK_HEADER_SIZE) || - (hapSigBlockSize > Integer.MAX_VALUE - SignHap.getBlockSize())) { + if ((hapSigBlockSize < HAP_SIG_BLOCK_HEADER_SIZE) + || (hapSigBlockSize > Integer.MAX_VALUE - SignHap.getBlockSize())) { throw new SignatureNotFoundException("Hap Signing Block size out of range: " + hapSigBlockSize); } int totalSize = (int) hapSigBlockSize; @@ -519,10 +533,7 @@ public class HapUtils { if (hapSigningBlockOffset < 0) { throw new SignatureNotFoundException("Hap Signing Block offset out of range: " + hapSigningBlockOffset); } - ByteBuffer hapSigningBlockByteBuffer = hap.createByteBuffer(hapSigningBlockOffset, totalSize) - .order(ByteOrder.LITTLE_ENDIAN); - LOGGER.info("Find Hap Signing Block success, version: {}, block count: {}", version, blockCount); - return new HapSignBlockInfo(hapSigningBlockOffset, version, hapSigningBlockByteBuffer); + return hapSigningBlockOffset; } private static boolean isVersionAndMagicNumValid(int version, long hapSignBlockMagicLo, long hapSignBlockMagicHi) { diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/utils/ParamConstants.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/utils/ParamConstants.java index 3b1dbde6f783da007716b57a82f6bc557d008fa6..e856abe65d9af2f2033b0febaab2f58ab94f6604 100644 --- a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/utils/ParamConstants.java +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/utils/ParamConstants.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021-2022 Huawei Device Co., Ltd. + * Copyright (c) 2021-2023 Huawei Device Co., Ltd. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at @@ -136,6 +136,11 @@ public class ParamConstants { */ public static final String PARAM_BASIC_PROFILE = "profileFile"; + /** + * json type content of Hap-file's capability profile + */ + public static final String PARAM_PROFILE_JSON_CONTENT = "profileContent"; + /** * Hap-file's proof-of-rotation */ @@ -236,12 +241,27 @@ public class ParamConstants { */ public static final String PARAM_RESIGN_CONFIG_FILE = "resignconfig"; + /** + * sign file type bin or zip or elf + */ + public static final String PARAM_IN_FORM = "inForm"; + + /** + * The code sign params of resign hap + */ + public static final String PARAM_SIGN_CODE = "signCode"; + + /** + * file name split . of min length + */ + public static final int FILE_NAME_MIN_LENGTH = 2; + /** * Enumerated value of whether a profile is signed */ public enum ProfileSignFlag { - UNSIGNED_PROFILE("0"), - SIGNED_PROFILE("1"); + DISABLE_SIGN_CODE("0"), + ENABLE_SIGN_CODE("1"); private String signFlag; @@ -253,4 +273,22 @@ public class ParamConstants { return signFlag; } } + + /** + * Enumerated value of whether a code sign is signed. + */ + public enum SignCodeFlag { + DISABLE_SIGN_CODE("0"), + ENABLE_SIGN_CODE("1"); + + private String signCodeFlag; + + SignCodeFlag(String signCodeFlag) { + this.signCodeFlag = signCodeFlag; + } + + public String getSignCodeFlag() { + return signCodeFlag; + } + } } \ No newline at end of file diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/zip/CentralDirectory.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/zip/CentralDirectory.java new file mode 100644 index 0000000000000000000000000000000000000000..04adc9b4260579ab487baa6b3a972068313d03bd --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/zip/CentralDirectory.java @@ -0,0 +1,409 @@ +/* + * Copyright (c) 2023-2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.ohos.hapsigntool.zip; + +import com.ohos.hapsigntool.error.ZipException; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.charset.StandardCharsets; + +/** + * resolve zip CentralDirectory data + * CentralDirectory format for: + * central file header signature 4 bytes (0x02014b50) + * version made by 2 bytes + * version needed to extract 2 bytes + * general purpose bit flag 2 bytes + * compression method 2 bytes + * last mod file time 2 bytes + * last mod file date 2 bytes + * crc-32 4 bytes + * compressed size 4 bytes + * uncompressed size 4 bytes + * file name length 2 bytes + * extra field length 2 bytes + * file comment length 2 bytes + * disk number start 2 bytes + * internal file attributes 2 bytes + * external file attributes 4 bytes + * relative offset of local header 4 bytes + * file name (variable size) + * extra field (variable size) + * file comment (variable size) + * + * @since 2023/12/02 + */ +public class CentralDirectory { + /** + * central directory invariable bytes length + */ + public static final int CD_LENGTH = 46; + + /** + * 4 bytes , central directory signature + */ + public static final int SIGNATURE = 0x02014b50; + + /** + * 2 bytes + */ + private short version; + + /** + * 2 bytes + */ + private short versionExtra; + + /** + * 2 bytes + */ + private short flag; + + /** + * 2 bytes + */ + private short method; + + /** + * 2 bytes + */ + private short lastTime; + + /** + * 2 bytes + */ + private short lastDate; + + /** + * 4 bytes + */ + private int crc32; + + /** + * 4 bytes + */ + private long compressedSize; + + /** + * 4 bytes + */ + private long unCompressedSize; + + /** + * 2 bytes + */ + private int fileNameLength; + + /** + * 2 bytes + */ + private int extraLength; + + /** + * 2 bytes + */ + private int commentLength; + + /** + * 2 bytes + */ + private int diskNumStart; + + /** + * 2 bytes + */ + private short internalFile; + + /** + * 4 bytes + */ + private int externalFile; + + /** + * 4 bytes + */ + private long offset; + + /** + * n bytes + */ + private String fileName; + + /** + * n bytes + */ + private byte[] extraData; + + /** + * n bytes + */ + private byte[] comment; + + private int length; + + /** + * get Central Directory + * + * @param bf ByteBuffer + * @return CentralDirectory + * @throws ZipException read Central Directory exception + */ + public static CentralDirectory getCentralDirectory(ByteBuffer bf) throws ZipException { + CentralDirectory cd = new CentralDirectory(); + if (bf.getInt() != SIGNATURE) { + throw new ZipException("find zip central directory failed"); + } + + cd.setVersion(bf.getShort()); + cd.setVersionExtra(bf.getShort()); + cd.setFlag(bf.getShort()); + cd.setMethod(bf.getShort()); + cd.setLastTime(bf.getShort()); + cd.setLastDate(bf.getShort()); + cd.setCrc32(bf.getInt()); + cd.setCompressedSize(UnsignedDecimalUtil.getUnsignedInt(bf)); + cd.setUnCompressedSize(UnsignedDecimalUtil.getUnsignedInt(bf)); + cd.setFileNameLength(UnsignedDecimalUtil.getUnsignedShort(bf)); + cd.setExtraLength(UnsignedDecimalUtil.getUnsignedShort(bf)); + cd.setCommentLength(UnsignedDecimalUtil.getUnsignedShort(bf)); + cd.setDiskNumStart(UnsignedDecimalUtil.getUnsignedShort(bf)); + cd.setInternalFile(bf.getShort()); + cd.setExternalFile(bf.getInt()); + cd.setOffset(UnsignedDecimalUtil.getUnsignedInt(bf)); + if (cd.getFileNameLength() > 0) { + byte[] readFileName = new byte[cd.getFileNameLength()]; + bf.get(readFileName); + cd.setFileName(new String(readFileName, StandardCharsets.UTF_8)); + } + if (cd.getExtraLength() > 0) { + byte[] extra = new byte[cd.getExtraLength()]; + bf.get(extra); + cd.setExtraData(extra); + } + if (cd.getCommentLength() > 0) { + byte[] readComment = new byte[cd.getCommentLength()]; + bf.get(readComment); + cd.setComment(readComment); + } + cd.setLength(CD_LENGTH + cd.getFileNameLength() + cd.getExtraLength() + cd.getCommentLength()); + return cd; + } + + /** + * change Central Directory to bytes + * + * @return bytes + */ + public byte[] toBytes() { + ByteBuffer bf = ByteBuffer.allocate(length).order(ByteOrder.LITTLE_ENDIAN); + bf.putInt(SIGNATURE); + UnsignedDecimalUtil.setUnsignedShort(bf, version); + UnsignedDecimalUtil.setUnsignedShort(bf, versionExtra); + UnsignedDecimalUtil.setUnsignedShort(bf, flag); + UnsignedDecimalUtil.setUnsignedShort(bf, method); + UnsignedDecimalUtil.setUnsignedShort(bf, lastTime); + UnsignedDecimalUtil.setUnsignedShort(bf, lastDate); + UnsignedDecimalUtil.setUnsignedInt(bf, crc32); + UnsignedDecimalUtil.setUnsignedInt(bf, compressedSize); + UnsignedDecimalUtil.setUnsignedInt(bf, unCompressedSize); + UnsignedDecimalUtil.setUnsignedShort(bf, fileNameLength); + UnsignedDecimalUtil.setUnsignedShort(bf, extraLength); + UnsignedDecimalUtil.setUnsignedShort(bf, commentLength); + UnsignedDecimalUtil.setUnsignedShort(bf, diskNumStart); + UnsignedDecimalUtil.setUnsignedShort(bf, internalFile); + UnsignedDecimalUtil.setUnsignedInt(bf, externalFile); + UnsignedDecimalUtil.setUnsignedInt(bf, offset); + if (fileNameLength > 0) { + bf.put(fileName.getBytes(StandardCharsets.UTF_8)); + } + if (extraLength > 0) { + bf.put(extraData); + } + if (commentLength > 0) { + bf.put(extraData); + } + return bf.array(); + } + + public static int getCdLength() { + return CD_LENGTH; + } + + public static int getSIGNATURE() { + return SIGNATURE; + } + + public short getVersion() { + return version; + } + + public void setVersion(short version) { + this.version = version; + } + + public short getVersionExtra() { + return versionExtra; + } + + public void setVersionExtra(short versionExtra) { + this.versionExtra = versionExtra; + } + + public short getFlag() { + return flag; + } + + public void setFlag(short flag) { + this.flag = flag; + } + + public short getMethod() { + return method; + } + + public void setMethod(short method) { + this.method = method; + } + + public short getLastTime() { + return lastTime; + } + + public void setLastTime(short lastTime) { + this.lastTime = lastTime; + } + + public short getLastDate() { + return lastDate; + } + + public void setLastDate(short lastDate) { + this.lastDate = lastDate; + } + + public int getCrc32() { + return crc32; + } + + public void setCrc32(int crc32) { + this.crc32 = crc32; + } + + public long getCompressedSize() { + return compressedSize; + } + + public void setCompressedSize(long compressedSize) { + this.compressedSize = compressedSize; + } + + public long getUnCompressedSize() { + return unCompressedSize; + } + + public void setUnCompressedSize(long unCompressedSize) { + this.unCompressedSize = unCompressedSize; + } + + public int getFileNameLength() { + return fileNameLength; + } + + public void setFileNameLength(int fileNameLength) { + this.fileNameLength = fileNameLength; + } + + public int getExtraLength() { + return extraLength; + } + + public void setExtraLength(int extraLength) { + this.extraLength = extraLength; + } + + public int getCommentLength() { + return commentLength; + } + + public void setCommentLength(int commentLength) { + this.commentLength = commentLength; + } + + public int getDiskNumStart() { + return diskNumStart; + } + + public void setDiskNumStart(int diskNumStart) { + this.diskNumStart = diskNumStart; + } + + public short getInternalFile() { + return internalFile; + } + + public void setInternalFile(short internalFile) { + this.internalFile = internalFile; + } + + public int getExternalFile() { + return externalFile; + } + + public void setExternalFile(int externalFile) { + this.externalFile = externalFile; + } + + public long getOffset() { + return offset; + } + + public void setOffset(long offset) { + this.offset = offset; + } + + public String getFileName() { + return fileName; + } + + public void setFileName(String fileName) { + this.fileName = fileName; + } + + public byte[] getExtraData() { + return extraData; + } + + public void setExtraData(byte[] extraData) { + this.extraData = extraData; + } + + public byte[] getComment() { + return comment; + } + + public void setComment(byte[] comment) { + this.comment = comment; + } + + public int getLength() { + return length; + } + + public void setLength(int length) { + this.length = length; + } +} \ No newline at end of file diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/zip/DataDescriptor.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/zip/DataDescriptor.java new file mode 100644 index 0000000000000000000000000000000000000000..fb521bf5cad9dd9074172b11a4138597cd460d17 --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/zip/DataDescriptor.java @@ -0,0 +1,126 @@ +/* + * Copyright (c) 2023-2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.ohos.hapsigntool.zip; + +import com.ohos.hapsigntool.error.ZipException; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +/** + * resolve zip DataDescriptor data + * DataDescriptor format: + * crc-32 4 bytes + * compressed size 4 bytes + * uncompressed size 4 bytes + * + * @since 2023/12/02 + */ +public class DataDescriptor { + /** + * DataDescriptor invariable bytes length + */ + public static final int DES_LENGTH = 16; + + /** + * 4 bytes , DataDescriptor signature + */ + public static final int SIGNATURE = 0x08074b50; + + /** + * 4 bytes + */ + private int crc32; + + /** + * 4 bytes + */ + private long compressedSize; + + /** + * 4 bytes + */ + private long unCompressedSize; + + /** + * get Data Descriptor + * + * @param bytes DataDescriptor bytes + * @return DataDescriptor + * @throws ZipException read data descriptor exception + */ + public static DataDescriptor getDataDescriptor(byte[] bytes) throws ZipException { + if (bytes.length != DES_LENGTH) { + throw new ZipException("read Data Descriptor failed"); + } + ByteBuffer bf = ByteBuffer.wrap(bytes); + bf.order(ByteOrder.LITTLE_ENDIAN); + DataDescriptor data = new DataDescriptor(); + if (bf.getInt() != SIGNATURE) { + throw new ZipException("read Data Descriptor failed"); + } + data.setCrc32(bf.getInt()); + data.setCompressedSize(UnsignedDecimalUtil.getUnsignedInt(bf)); + data.setUnCompressedSize(UnsignedDecimalUtil.getUnsignedInt(bf)); + return data; + } + + /** + * change DataDescriptor to bytes + * + * @return bytes + */ + public byte[] toBytes() { + ByteBuffer bf = ByteBuffer.allocate(DES_LENGTH).order(ByteOrder.LITTLE_ENDIAN); + bf.putInt(SIGNATURE); + bf.putInt(crc32); + UnsignedDecimalUtil.setUnsignedInt(bf, compressedSize); + UnsignedDecimalUtil.setUnsignedInt(bf, unCompressedSize); + return bf.array(); + } + + public static int getDesLength() { + return DES_LENGTH; + } + + public static int getSIGNATURE() { + return SIGNATURE; + } + + public int getCrc32() { + return crc32; + } + + public void setCrc32(int crc32) { + this.crc32 = crc32; + } + + public long getCompressedSize() { + return compressedSize; + } + + public void setCompressedSize(long compressedSize) { + this.compressedSize = compressedSize; + } + + public long getUnCompressedSize() { + return unCompressedSize; + } + + public void setUnCompressedSize(long unCompressedSize) { + this.unCompressedSize = unCompressedSize; + } +} diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/zip/EndOfCentralDirectory.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/zip/EndOfCentralDirectory.java new file mode 100644 index 0000000000000000000000000000000000000000..92d211b305612c48bfd453e3478ab2ab3d7be970 --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/zip/EndOfCentralDirectory.java @@ -0,0 +1,245 @@ +/* + * Copyright (c) 2023-2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.ohos.hapsigntool.zip; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.Optional; + +/** + * resolve zip EndOfCentralDirectory data + * EndOfCentralDirectory format for: + * end of central dir signature 4 bytes (0x06054b50) + * number of this disk 2 bytes + * number of the disk with the + * start of the central directory 2 bytes + * total number of entries in the + * central directory on this disk 2 bytes + * total number of entries in + * the central directory 2 bytes + * size of the central directory 4 bytes + * offset of start of central + * directory with respect to + * the starting disk number 4 bytes + * .ZIP file comment length 2 bytes + * .ZIP file comment (variable size) + * + * @since 2023/12/04 + */ +public class EndOfCentralDirectory { + /** + * EndOfCentralDirectory invariable bytes length + */ + public static final int EOCD_LENGTH = 22; + + /** + * 4 bytes , central directory signature + */ + public static final int SIGNATURE = 0x06054b50; + + /** + * 2 bytes + */ + private int diskNum; + + /** + * 2 bytes + */ + private int cDStartDiskNum; + + /** + * 2 bytes + */ + private int thisDiskCDNum; + + /** + * 2 bytes + */ + private int cDTotal; + + /** + * 4 bytes + */ + private long cDSize; + + /** + * 4 bytes + */ + private long offset; + + /** + * 2 bytes + */ + private int commentLength; + + /** + * n bytes + */ + private byte[] comment; + + private int length; + + /** + * init End Of Central Directory, default offset is 0 + * + * @param bytes End Of Central Directory bytes + * @return End Of Central Directory + */ + public static Optional getEOCDByBytes(byte[] bytes) { + return getEOCDByBytes(bytes, 0); + } + + /** + * init End Of Central Directory + * + * @param bytes End Of Central Directory bytes + * @param offset offset + * @return End Of Central Directory + */ + public static Optional getEOCDByBytes(byte[] bytes, int offset) { + EndOfCentralDirectory eocd = new EndOfCentralDirectory(); + int remainingDataLen = bytes.length - offset; + if (remainingDataLen < EOCD_LENGTH) { + return Optional.empty(); + } + ByteBuffer bf = ByteBuffer.wrap(bytes, offset, remainingDataLen); + bf.order(ByteOrder.LITTLE_ENDIAN); + if (bf.getInt() != SIGNATURE) { + return Optional.empty(); + } + eocd.setDiskNum(UnsignedDecimalUtil.getUnsignedShort(bf)); + eocd.setcDStartDiskNum(UnsignedDecimalUtil.getUnsignedShort(bf)); + eocd.setThisDiskCDNum(UnsignedDecimalUtil.getUnsignedShort(bf)); + eocd.setcDTotal(UnsignedDecimalUtil.getUnsignedShort(bf)); + eocd.setcDSize(UnsignedDecimalUtil.getUnsignedInt(bf)); + eocd.setOffset(UnsignedDecimalUtil.getUnsignedInt(bf)); + eocd.setCommentLength(UnsignedDecimalUtil.getUnsignedShort(bf)); + if (bf.remaining() != eocd.getCommentLength()) { + return Optional.empty(); + } + if (eocd.getCommentLength() > 0) { + byte[] readComment = new byte[eocd.getCommentLength()]; + bf.get(readComment); + eocd.setComment(readComment); + } + eocd.setLength(EOCD_LENGTH + eocd.getCommentLength()); + if (bf.remaining() != 0) { + return Optional.empty(); + } + return Optional.of(eocd); + } + + /** + * change End Of Central Directory to bytes + * + * @return bytes + */ + public byte[] toBytes() { + ByteBuffer bf = ByteBuffer.allocate(length).order(ByteOrder.LITTLE_ENDIAN); + bf.putInt(SIGNATURE); + UnsignedDecimalUtil.setUnsignedShort(bf, diskNum); + UnsignedDecimalUtil.setUnsignedShort(bf, cDStartDiskNum); + UnsignedDecimalUtil.setUnsignedShort(bf, thisDiskCDNum); + UnsignedDecimalUtil.setUnsignedShort(bf, cDTotal); + UnsignedDecimalUtil.setUnsignedInt(bf, cDSize); + UnsignedDecimalUtil.setUnsignedInt(bf, offset); + UnsignedDecimalUtil.setUnsignedShort(bf, commentLength); + if (commentLength > 0) { + bf.put(comment); + } + return bf.array(); + } + + public static int getEocdLength() { + return EOCD_LENGTH; + } + + public static int getSIGNATURE() { + return SIGNATURE; + } + + public int getDiskNum() { + return diskNum; + } + + public void setDiskNum(int diskNum) { + this.diskNum = diskNum; + } + + public int getcDStartDiskNum() { + return cDStartDiskNum; + } + + public void setcDStartDiskNum(int cDStartDiskNum) { + this.cDStartDiskNum = cDStartDiskNum; + } + + public int getThisDiskCDNum() { + return thisDiskCDNum; + } + + public void setThisDiskCDNum(int thisDiskCDNum) { + this.thisDiskCDNum = thisDiskCDNum; + } + + public int getcDTotal() { + return cDTotal; + } + + public void setcDTotal(int cDTotal) { + this.cDTotal = cDTotal; + } + + public long getcDSize() { + return cDSize; + } + + public void setcDSize(long cDSize) { + this.cDSize = cDSize; + } + + public long getOffset() { + return offset; + } + + public void setOffset(long offset) { + this.offset = offset; + } + + public int getCommentLength() { + return commentLength; + } + + public void setCommentLength(int commentLength) { + this.commentLength = commentLength; + } + + public byte[] getComment() { + return comment; + } + + public void setComment(byte[] comment) { + this.comment = comment; + } + + public int getLength() { + return length; + } + + public void setLength(int length) { + this.length = length; + } +} \ No newline at end of file diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/zip/UnsignedDecimalUtil.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/zip/UnsignedDecimalUtil.java new file mode 100644 index 0000000000000000000000000000000000000000..0625b2fc78f54d04227f2d031f541b0fed694ee3 --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/zip/UnsignedDecimalUtil.java @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2023-2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.ohos.hapsigntool.zip; + +import java.nio.ByteBuffer; + +/** + * Unsigned Decimal Util + * + * @since 2023/12/09 + */ +public class UnsignedDecimalUtil { + /** + * max unsigned int value + */ + public static final long MAX_UNSIGNED_INT_VALUE = 0xFFFFFFFFL; + + /** + * max unsigned int value + */ + public static final int MAX_UNSIGNED_SHORT_VALUE = 0xFFFF; + + private static final int BIT_SIZE = 8; + + private static final int DOUBLE_BIT_SIZE = 16; + + private static final int TRIPLE_BIT_SIZE = 24; + + /** + * get unsigned int to long + * + * @param bf byteBuffer + * @return long + */ + public static long getUnsignedInt(ByteBuffer bf) { + return bf.getInt() & MAX_UNSIGNED_INT_VALUE; + } + + /** + * get unsigned short to int + * + * @param bf byteBuffer + * @return int + */ + public static int getUnsignedShort(ByteBuffer bf) { + return bf.getShort() & MAX_UNSIGNED_SHORT_VALUE; + } + + /** + * set long to unsigned int + * + * @param bf byteBuffer + * @param value long + */ + public static void setUnsignedInt(ByteBuffer bf, long value) { + byte[] bytes = new byte[] { + (byte) (value & 0xFF), + (byte) ((value >> BIT_SIZE) & 0xFF), + (byte) ((value >> DOUBLE_BIT_SIZE) & 0xFF), + (byte) ((value >> TRIPLE_BIT_SIZE) & 0xFF) + }; + bf.put(bytes); + } + + /** + * set int to unsigned short + * + * @param bf byteBuffer + * @param value int + */ + public static void setUnsignedShort(ByteBuffer bf, int value) { + byte[] bytes = new byte[] { + (byte) (value & 0xFF), + (byte) ((value >> BIT_SIZE) & 0xFF) + }; + bf.put(bytes); + } +} diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/zip/Zip.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/zip/Zip.java new file mode 100644 index 0000000000000000000000000000000000000000..7ab9bdc2ed2b9a31894b6837620bd6b1d93411f9 --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/zip/Zip.java @@ -0,0 +1,364 @@ +/* + * Copyright (c) 2023-2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.ohos.hapsigntool.zip; + +import com.ohos.hapsigntool.error.CustomException; +import com.ohos.hapsigntool.error.ERROR; +import com.ohos.hapsigntool.error.ZipException; +import com.ohos.hapsigntool.utils.FileUtils; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +/** + * resolve zip data + * + * @since 2023/12/02 + */ +public class Zip { + private static final Logger LOGGER = LogManager.getLogger(Zip.class); + + /** + * file is uncompress file flag + */ + public static final int FILE_UNCOMPRESS_METHOD_FLAG = 0; + + /** + * max comment length + */ + public static final int MAX_COMMENT_LENGTH = 65535; + + private List zipEntries; + + private long signingOffset; + + private byte[] signingBlock; + + private long cDOffset; + + private long eOCDOffset; + + private EndOfCentralDirectory endOfCentralDirectory; + + private String file; + + /** + * create Zip by file + * + * @param inputFile file + */ + public Zip(File inputFile) { + try { + this.file = inputFile.getCanonicalPath(); + if (!inputFile.exists()) { + throw new ZipException("read zip file failed"); + } + long start = System.currentTimeMillis(); + // 1. get eocd data + endOfCentralDirectory = getZipEndOfCentralDirectory(inputFile); + cDOffset = endOfCentralDirectory.getOffset(); + long eocdEnd = System.currentTimeMillis(); + LOGGER.debug("getZipEndOfCentralDirectory use {} ms", eocdEnd - start); + // 2. use eocd's cd offset, get cd data + getZipCentralDirectory(inputFile); + long cdEnd = System.currentTimeMillis(); + LOGGER.debug("getZipCentralDirectory use {} ms", cdEnd - start); + // 3. use cd's entry offset and file size, get entry data + getZipEntries(inputFile); + ZipEntry endEntry = zipEntries.get(zipEntries.size() - 1); + CentralDirectory endCD = endEntry.getCentralDirectory(); + ZipEntryData endEntryData = endEntry.getZipEntryData(); + signingOffset = endCD.getOffset() + endEntryData.getLength(); + long entryEnd = System.currentTimeMillis(); + LOGGER.debug("getZipEntries use {} ms", entryEnd - start); + // 4. file all data - eocd - cd - entry = sign block + signingBlock = getSigningBlock(inputFile); + } catch (IOException e) { + CustomException.throwException(ERROR.ZIP_ERROR, e.getMessage()); + } + } + + private EndOfCentralDirectory getZipEndOfCentralDirectory(File file) throws IOException { + if (file.length() < EndOfCentralDirectory.EOCD_LENGTH) { + throw new ZipException("find zip eocd failed"); + } + + // try to read EOCD without comment + int eocdLength = EndOfCentralDirectory.EOCD_LENGTH; + eOCDOffset = file.length() - eocdLength; + byte[] bytes = FileUtils.readFileByOffsetAndLength(file, eOCDOffset, eocdLength); + Optional eocdByBytes = EndOfCentralDirectory.getEOCDByBytes(bytes); + if (eocdByBytes.isPresent()) { + return eocdByBytes.get(); + } + + // try to search EOCD with comment + long eocdMaxLength = Math.min(EndOfCentralDirectory.EOCD_LENGTH + MAX_COMMENT_LENGTH, file.length()); + eOCDOffset = file.length() - eocdMaxLength; + bytes = FileUtils.readFileByOffsetAndLength(file, eOCDOffset, eocdMaxLength); + for (int start = 0; start < eocdMaxLength; start++) { + eocdByBytes = EndOfCentralDirectory.getEOCDByBytes(bytes, start); + if (eocdByBytes.isPresent()) { + eOCDOffset += start; + return eocdByBytes.get(); + } + } + throw new ZipException("read zip failed: can not find eocd in file"); + } + + private void getZipCentralDirectory(File file) throws IOException { + zipEntries = new ArrayList<>(endOfCentralDirectory.getcDTotal()); + // read full central directory bytes + byte[] cdBytes = FileUtils.readFileByOffsetAndLength(file, cDOffset, endOfCentralDirectory.getcDSize()); + if (cdBytes.length < CentralDirectory.CD_LENGTH) { + throw new ZipException("find zip cd failed"); + } + ByteBuffer bf = ByteBuffer.wrap(cdBytes); + bf.order(ByteOrder.LITTLE_ENDIAN); + int offset = 0; + // one by one format central directory + while (offset < cdBytes.length) { + CentralDirectory cd = CentralDirectory.getCentralDirectory(bf); + ZipEntry entry = new ZipEntry(); + entry.setCentralDirectory(cd); + zipEntries.add(entry); + offset += cd.getLength(); + } + if (offset + cDOffset != eOCDOffset) { + throw new ZipException("cd end offset not equals to eocd offset, maybe this is a zip64 file"); + } + } + + private byte[] getSigningBlock(File file) throws IOException { + long size = cDOffset - signingOffset; + if (size < 0) { + throw new ZipException("signing offset in front of entry end"); + } + if (size == 0) { + return new byte[0]; + } + return FileUtils.readFileByOffsetAndLength(file, signingOffset, size); + } + + private void getZipEntries(File file) throws IOException { + // use central directory data, find entry data + for (ZipEntry entry : zipEntries) { + CentralDirectory cd = entry.getCentralDirectory(); + long offset = cd.getOffset(); + long unCompressedSize = cd.getUnCompressedSize(); + long compressedSize = cd.getCompressedSize(); + long fileSize = cd.getMethod() == FILE_UNCOMPRESS_METHOD_FLAG ? unCompressedSize : compressedSize; + + ZipEntryData zipEntryData = ZipEntryData.getZipEntry(file, offset, fileSize); + if (cDOffset - offset < zipEntryData.getLength()) { + throw new ZipException("cd offset in front of entry end"); + } + entry.setZipEntryData(zipEntryData); + } + } + + /** + * output zip to zip file + * + * @param outFile file path + */ + public void toFile(String outFile) { + try (FileOutputStream fos = new FileOutputStream(outFile)) { + for (ZipEntry entry : zipEntries) { + ZipEntryData zipEntryData = entry.getZipEntryData(); + FileUtils.writeByteToOutFile(zipEntryData.getZipEntryHeader().toBytes(), fos); + boolean isSuccess = FileUtils.appendWriteFileByOffsetToFile(file, fos, + zipEntryData.getFileOffset(), zipEntryData.getFileSize()); + if (!isSuccess) { + throw new ZipException("write zip data failed"); + } + if (zipEntryData.getDataDescriptor() != null) { + FileUtils.writeByteToOutFile(zipEntryData.getDataDescriptor().toBytes(), fos); + } + } + if (signingBlock != null) { + FileUtils.writeByteToOutFile(signingBlock, fos); + } + for (ZipEntry entry : zipEntries) { + CentralDirectory cd = entry.getCentralDirectory(); + FileUtils.writeByteToOutFile(cd.toBytes(), fos); + } + FileUtils.writeByteToOutFile(endOfCentralDirectory.toBytes(), fos); + } catch (IOException e) { + CustomException.throwException(ERROR.ZIP_ERROR, e.getMessage()); + } + } + + /** + * alignment uncompress entry + * + * @param alignment int alignment + */ + public void alignment(int alignment) { + try { + sort(); + boolean isFirstUnRunnableFile = true; + for (ZipEntry entry : zipEntries) { + ZipEntryData zipEntryData = entry.getZipEntryData(); + short method = zipEntryData.getZipEntryHeader().getMethod(); + if (method != FILE_UNCOMPRESS_METHOD_FLAG && !isFirstUnRunnableFile) { + // only align uncompressed entry and the first compress entry. + break; + } + int alignBytes; + if (method == FILE_UNCOMPRESS_METHOD_FLAG && FileUtils.isRunnableFile( + zipEntryData.getZipEntryHeader().getFileName())) { + // .abc and .so file align 4096 byte. + alignBytes = 4096; + } else if (isFirstUnRunnableFile) { + // the first file after runnable file, align 4096 byte. + alignBytes = 4096; + isFirstUnRunnableFile = false; + } else { + // normal file align 4 byte. + alignBytes = alignment; + } + int add = entry.alignment(alignBytes); + if (add > 0) { + resetOffset(); + } + } + } catch (ZipException e) { + CustomException.throwException(ERROR.ZIP_ERROR, e.getMessage()); + } + } + + /** + * remove sign block + */ + public void removeSignBlock() { + signingBlock = null; + resetOffset(); + } + + /** + * sort uncompress entry in the front. + */ + private void sort() { + // sort uncompress file (so, abc, an) - other uncompress file - compress file + zipEntries.sort((entry1, entry2) -> { + short entry1Method = entry1.getZipEntryData().getZipEntryHeader().getMethod(); + short entry2Method = entry2.getZipEntryData().getZipEntryHeader().getMethod(); + String entry1FileName = entry1.getZipEntryData().getZipEntryHeader().getFileName(); + String entry2FileName = entry2.getZipEntryData().getZipEntryHeader().getFileName(); + if (entry1Method == FILE_UNCOMPRESS_METHOD_FLAG && entry2Method == FILE_UNCOMPRESS_METHOD_FLAG) { + boolean isRunnableFile1 = FileUtils.isRunnableFile(entry1FileName); + boolean isRunnableFile2 = FileUtils.isRunnableFile(entry2FileName); + if (isRunnableFile1 && isRunnableFile2) { + return entry1FileName.compareTo(entry2FileName); + } else if (isRunnableFile1) { + return -1; + } else if (isRunnableFile2) { + return 1; + } + } else if (entry1Method == FILE_UNCOMPRESS_METHOD_FLAG) { + return -1; + } else if (entry2Method == FILE_UNCOMPRESS_METHOD_FLAG) { + return 1; + } + return entry1FileName.compareTo(entry2FileName); + }); + resetOffset(); + } + + private void resetOffset() { + long offset = 0L; + long cdLength = 0L; + for (ZipEntry entry : zipEntries) { + entry.getCentralDirectory().setOffset(offset); + offset += entry.getZipEntryData().getLength(); + cdLength += entry.getCentralDirectory().getLength(); + } + if (signingBlock != null) { + offset += signingBlock.length; + } + cDOffset = offset; + endOfCentralDirectory.setOffset(offset); + endOfCentralDirectory.setcDSize(cdLength); + offset += cdLength; + eOCDOffset = offset; + } + + public List getZipEntries() { + return zipEntries; + } + + public void setZipEntries(List zipEntries) { + this.zipEntries = zipEntries; + } + + public long getSigningOffset() { + return signingOffset; + } + + public void setSigningOffset(long signingOffset) { + this.signingOffset = signingOffset; + } + + public byte[] getSigningBlock() { + return signingBlock; + } + + public void setSigningBlock(byte[] signingBlock) { + this.signingBlock = signingBlock; + } + + public long getCDOffset() { + return cDOffset; + } + + public void setCDOffset(long cDOffset) { + this.cDOffset = cDOffset; + } + + public long getEOCDOffset() { + return eOCDOffset; + } + + public void setEOCDOffset(long eOCDOffset) { + this.eOCDOffset = eOCDOffset; + } + + public EndOfCentralDirectory getEndOfCentralDirectory() { + return endOfCentralDirectory; + } + + public void setEndOfCentralDirectory(EndOfCentralDirectory endOfCentralDirectory) { + this.endOfCentralDirectory = endOfCentralDirectory; + } + + public String getFile() { + return file; + } + + public void setFile(String file) { + this.file = file; + } +} \ No newline at end of file diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/zip/ZipEntry.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/zip/ZipEntry.java new file mode 100644 index 0000000000000000000000000000000000000000..ff66339ad690b1ddacba48b9ec301cb80c16dc5e --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/zip/ZipEntry.java @@ -0,0 +1,118 @@ +/* + * Copyright (c) 2023-2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.ohos.hapsigntool.zip; + +import com.ohos.hapsigntool.error.ZipException; + +import java.util.Arrays; + +/** + * ZipEntry and CentralDirectory data + * + * @since 2023/12/02 + */ +public class ZipEntry { + private ZipEntryData zipEntryData; + + private CentralDirectory fileEntryIncentralDirectory; + + /** + * alignment one entry + * + * @param alignNum need align bytes length + * @return add bytes length + * @throws ZipException alignment exception + */ + public int alignment(int alignNum) throws ZipException { + // if cd extra len bigger than entry extra len, make cd and entry extra length equals + int padding = calZeroPaddingLengthForEntryExtra(); + int remainder = (int) ((zipEntryData.getZipEntryHeader().getLength() + + fileEntryIncentralDirectory.getOffset()) % alignNum); + + if (remainder == 0) { + return padding; + } + int add = alignNum - remainder; + int newExtraLength = zipEntryData.getZipEntryHeader().getExtraLength() + add; + if (newExtraLength > UnsignedDecimalUtil.MAX_UNSIGNED_SHORT_VALUE) { + throw new ZipException("can not align " + zipEntryData.getZipEntryHeader().getFileName()); + } + setEntryHeaderNewExtraLength(newExtraLength); + setCenterDirectoryNewExtraLength(newExtraLength); + + return add; + } + + private int calZeroPaddingLengthForEntryExtra() throws ZipException { + int entryExtraLen = zipEntryData.getZipEntryHeader().getExtraLength(); + int cdExtraLen = fileEntryIncentralDirectory.getExtraLength(); + if (cdExtraLen > entryExtraLen) { + setEntryHeaderNewExtraLength(cdExtraLen); + return cdExtraLen - entryExtraLen; + } + if (cdExtraLen < entryExtraLen) { + setCenterDirectoryNewExtraLength(entryExtraLen); + return entryExtraLen - cdExtraLen; + } + return 0; + } + + private void setCenterDirectoryNewExtraLength(int newLength) throws ZipException { + byte[] newCDExtra = getAlignmentNewExtra(newLength, fileEntryIncentralDirectory.getExtraData()); + fileEntryIncentralDirectory.setExtraData(newCDExtra); + fileEntryIncentralDirectory.setExtraLength(newLength); + fileEntryIncentralDirectory.setLength(CentralDirectory.CD_LENGTH + + fileEntryIncentralDirectory.getFileNameLength() + + fileEntryIncentralDirectory.getExtraLength() + fileEntryIncentralDirectory.getCommentLength()); + } + + private void setEntryHeaderNewExtraLength(int newLength) throws ZipException { + ZipEntryHeader zipEntryHeader = zipEntryData.getZipEntryHeader(); + byte[] newExtra = getAlignmentNewExtra(newLength, zipEntryHeader.getExtraData()); + zipEntryHeader.setExtraData(newExtra); + zipEntryHeader.setExtraLength(newLength); + zipEntryHeader.setLength(ZipEntryHeader.HEADER_LENGTH + zipEntryHeader.getExtraLength() + + zipEntryHeader.getFileNameLength()); + zipEntryData.setLength(zipEntryHeader.getLength() + zipEntryData.getFileSize() + + (zipEntryData.getDataDescriptor() == null ? 0 : DataDescriptor.DES_LENGTH)); + } + + private byte[] getAlignmentNewExtra(int newLength, byte[] old) throws ZipException { + if (old == null) { + return new byte[newLength]; + } + if (newLength < old.length) { + throw new ZipException("can not align " + zipEntryData.getZipEntryHeader().getFileName()); + } + return Arrays.copyOf(old, newLength); + } + + public ZipEntryData getZipEntryData() { + return zipEntryData; + } + + public void setZipEntryData(ZipEntryData zipEntryData) { + this.zipEntryData = zipEntryData; + } + + public CentralDirectory getCentralDirectory() { + return fileEntryIncentralDirectory; + } + + public void setCentralDirectory(CentralDirectory centralDirectory) { + this.fileEntryIncentralDirectory = centralDirectory; + } +} \ No newline at end of file diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/zip/ZipEntryData.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/zip/ZipEntryData.java new file mode 100644 index 0000000000000000000000000000000000000000..35b19e7ad5833b31343f479a92d321a808507651 --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/zip/ZipEntryData.java @@ -0,0 +1,143 @@ +/* + * Copyright (c) 2023-2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.ohos.hapsigntool.zip; + +import com.ohos.hapsigntool.utils.FileUtils; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; + +/** + * resolve zip ZipEntry data + * + * @since 2023/12/04 + */ +public class ZipEntryData { + /** + * data descriptor has or not mask + */ + public static final short HAS_DATA_DESCRIPTOR_MASK = 0x08; + + /** + * data descriptor has or not flag mask + */ + public static final short NOT_HAS_DATA_DESCRIPTOR_FLAG = 0; + + private ZipEntryHeader zipEntryHeader; + + private long fileOffset; + + private long fileSize; + + private DataDescriptor dataDescriptor; + + private long length; + + public ZipEntryHeader getZipEntryHeader() { + return zipEntryHeader; + } + + /** + * init zip entry by file + * + * @param file zip file + * @param entryOffset entry start offset + * @param fileSize compress file size + * @return zip entry + * @throws IOException read zip exception + */ + public static ZipEntryData getZipEntry(File file, long entryOffset, long fileSize) + throws IOException { + try (FileInputStream input = new FileInputStream(file)) { + long offset = entryOffset; + // read entry header by file and offset. + byte[] headBytes = FileUtils.readInputByOffsetAndLength(input, entryOffset, ZipEntryHeader.HEADER_LENGTH); + ZipEntryHeader entryHeader = ZipEntryHeader.getZipEntryHeader(headBytes); + offset += ZipEntryHeader.HEADER_LENGTH; + + // read entry file name and extra by offset. + if (entryHeader.getFileNameLength() > 0) { + byte[] fileNameBytes = FileUtils.readInputByLength(input, entryHeader.getFileNameLength()); + entryHeader.readFileName(fileNameBytes); + offset += entryHeader.getFileNameLength(); + } + + if (entryHeader.getExtraLength() > 0) { + byte[] extraBytes = FileUtils.readInputByLength(input, entryHeader.getExtraLength()); + entryHeader.readExtra(extraBytes); + offset += entryHeader.getExtraLength(); + } + + // skip file data , save file offset and size. + ZipEntryData entry = new ZipEntryData(); + entry.setFileOffset(offset); + entry.setFileSize(fileSize); + input.skip(fileSize); + + long entryLength = entryHeader.getLength() + fileSize; + short flag = entryHeader.getFlag(); + // set desc null flag + boolean hasDesc = (flag & HAS_DATA_DESCRIPTOR_MASK) != NOT_HAS_DATA_DESCRIPTOR_FLAG; + if (hasDesc) { + // if entry has data descriptor, read entry data descriptor. + byte[] desBytes = FileUtils.readInputByLength(input, DataDescriptor.DES_LENGTH); + DataDescriptor dataDesc = DataDescriptor.getDataDescriptor(desBytes); + entryLength += DataDescriptor.DES_LENGTH; + entry.setDataDescriptor(dataDesc); + } + entry.setZipEntryHeader(entryHeader); + entry.setLength(entryLength); + return entry; + } + } + + public void setZipEntryHeader(ZipEntryHeader zipEntryHeader) { + this.zipEntryHeader = zipEntryHeader; + } + + public DataDescriptor getDataDescriptor() { + return dataDescriptor; + } + + public void setDataDescriptor(DataDescriptor dataDescriptor) { + this.dataDescriptor = dataDescriptor; + } + + public long getFileOffset() { + return fileOffset; + } + + public void setFileOffset(long fileOffset) { + this.fileOffset = fileOffset; + } + + public long getFileSize() { + return fileSize; + } + + public void setFileSize(long fileSize) { + this.fileSize = fileSize; + } + + public long getLength() { + return length; + } + + public void setLength(long length) { + this.length = length; + } +} \ No newline at end of file diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/zip/ZipEntryHeader.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/zip/ZipEntryHeader.java new file mode 100644 index 0000000000000000000000000000000000000000..7c880a6bea5a14cb38fa5da1a9e7a777a0ef890f --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/zip/ZipEntryHeader.java @@ -0,0 +1,312 @@ +/* + * Copyright (c) 2023-2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.ohos.hapsigntool.zip; + +import com.ohos.hapsigntool.error.ZipException; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.charset.StandardCharsets; + +/** + * resolve zip ZipEntryHeader data + * end of central dir signature 4 bytes (0x06054b50) + * number of this disk 2 bytes + * number of the disk with the + * start of the central directory 2 bytes + * total number of entries in the + * central directory on this disk 2 bytes + * total number of entries in + * the central directory 2 bytes + * size of the central directory 4 bytes + * offset of start of central + * directory with respect to + * the starting disk number 4 bytes + * .ZIP file comment length 2 bytes + * .ZIP file comment (variable size) + * + * @since 2023/12/02 + */ +public class ZipEntryHeader { + /** + * ZipEntryHeader invariable bytes length + */ + public static final int HEADER_LENGTH = 30; + + /** + * 4 bytes , entry header signature + */ + public static final int SIGNATURE = 0x04034b50; + + /** + * 2 bytes + */ + private short version; + + /** + * 2 bytes + */ + private short flag; + + /** + * 2 bytes + */ + private short method; + + /** + * 2 bytes + */ + private short lastTime; + + /** + * 2 bytes + */ + private short lastDate; + + /** + * 4 bytes + */ + private int crc32; + + /** + * 4 bytes + */ + private long compressedSize; + + /** + * 4 bytes + */ + private long unCompressedSize; + + /** + * 2 bytes + */ + private int fileNameLength; + + /** + * 2 bytes + */ + private int extraLength; + + /** + * n bytes + */ + private String fileName; + + /** + * n bytes + */ + private byte[] extraData; + + private int length; + + /** + * get Zip Entry Header + * + * @param bytes ZipEntryHeader bytes + * @return ZipEntryHeader + * @throws ZipException read entry header exception + */ + public static ZipEntryHeader getZipEntryHeader(byte[] bytes) throws ZipException { + ZipEntryHeader entryHeader = new ZipEntryHeader(); + ByteBuffer bf = ByteBuffer.wrap(bytes); + bf.order(ByteOrder.LITTLE_ENDIAN); + if (bf.getInt() != ZipEntryHeader.SIGNATURE) { + throw new ZipException("find zip entry head failed"); + } + entryHeader.setVersion(bf.getShort()); + entryHeader.setFlag(bf.getShort()); + entryHeader.setMethod(bf.getShort()); + entryHeader.setLastTime(bf.getShort()); + entryHeader.setLastDate(bf.getShort()); + entryHeader.setCrc32(bf.getInt()); + entryHeader.setCompressedSize(UnsignedDecimalUtil.getUnsignedInt(bf)); + entryHeader.setUnCompressedSize(UnsignedDecimalUtil.getUnsignedInt(bf)); + entryHeader.setFileNameLength(UnsignedDecimalUtil.getUnsignedShort(bf)); + entryHeader.setExtraLength(UnsignedDecimalUtil.getUnsignedShort(bf)); + entryHeader.setLength(HEADER_LENGTH + entryHeader.getFileNameLength() + entryHeader.getExtraLength()); + return entryHeader; + } + + /** + * set entry header name + * + * @param bytes name bytes + */ + public void readFileName(byte[] bytes) { + ByteBuffer bf = ByteBuffer.wrap(bytes); + bf.order(ByteOrder.LITTLE_ENDIAN); + if (fileNameLength > 0) { + byte[] nameBytes = new byte[fileNameLength]; + bf.get(nameBytes); + this.fileName = new String(nameBytes, StandardCharsets.UTF_8); + } + } + + /** + * set entry header extra + * + * @param bytes extra bytes + */ + public void readExtra(byte[] bytes) { + ByteBuffer bf = ByteBuffer.wrap(bytes); + bf.order(ByteOrder.LITTLE_ENDIAN); + if (extraLength > 0) { + byte[] extra = new byte[extraLength]; + bf.get(extra); + this.extraData = extra; + } + } + + /** + * change Zip Entry Header to bytes + * + * @return bytes + */ + public byte[] toBytes() { + ByteBuffer bf = ByteBuffer.allocate(length).order(ByteOrder.LITTLE_ENDIAN); + bf.putInt(SIGNATURE); + bf.putShort(version); + bf.putShort(flag); + bf.putShort(method); + bf.putShort(lastTime); + bf.putShort(lastDate); + bf.putInt(crc32); + UnsignedDecimalUtil.setUnsignedInt(bf, compressedSize); + UnsignedDecimalUtil.setUnsignedInt(bf, unCompressedSize); + UnsignedDecimalUtil.setUnsignedShort(bf, fileNameLength); + UnsignedDecimalUtil.setUnsignedShort(bf, extraLength); + if (fileNameLength > 0) { + bf.put(fileName.getBytes(StandardCharsets.UTF_8)); + } + if (extraLength > 0) { + bf.put(extraData); + } + return bf.array(); + } + + public static int getHeaderLength() { + return HEADER_LENGTH; + } + + public static int getSIGNATURE() { + return SIGNATURE; + } + + public short getVersion() { + return version; + } + + public void setVersion(short version) { + this.version = version; + } + + public short getFlag() { + return flag; + } + + public void setFlag(short flag) { + this.flag = flag; + } + + public short getMethod() { + return method; + } + + public void setMethod(short method) { + this.method = method; + } + + public short getLastTime() { + return lastTime; + } + + public void setLastTime(short lastTime) { + this.lastTime = lastTime; + } + + public short getLastDate() { + return lastDate; + } + + public void setLastDate(short lastDate) { + this.lastDate = lastDate; + } + + public int getCrc32() { + return crc32; + } + + public void setCrc32(int crc32) { + this.crc32 = crc32; + } + + public long getCompressedSize() { + return compressedSize; + } + + public void setCompressedSize(long compressedSize) { + this.compressedSize = compressedSize; + } + + public long getUnCompressedSize() { + return unCompressedSize; + } + + public void setUnCompressedSize(long unCompressedSize) { + this.unCompressedSize = unCompressedSize; + } + + public int getFileNameLength() { + return fileNameLength; + } + + public void setFileNameLength(int fileNameLength) { + this.fileNameLength = fileNameLength; + } + + public int getExtraLength() { + return extraLength; + } + + public void setExtraLength(int extraLength) { + this.extraLength = extraLength; + } + + public String getFileName() { + return fileName; + } + + public void setFileName(String fileName) { + this.fileName = fileName; + } + + public byte[] getExtraData() { + return extraData; + } + + public void setExtraData(byte[] extraData) { + this.extraData = extraData; + } + + public int getLength() { + return length; + } + + public void setLength(int length) { + this.length = length; + } +} \ No newline at end of file diff --git a/hapsigntool/hap_sign_tool_lib/src/test/java/com/ohos/hapsigntool/ProfileTest.java b/hapsigntool/hap_sign_tool_lib/src/test/java/com/ohos/hapsigntool/ProfileTest.java index df437f6e4e1de5ee03f0f997578ec9cb3a3a7731..5fc3110c399d28ac3fb7f185526180b2112e2755 100644 --- a/hapsigntool/hap_sign_tool_lib/src/test/java/com/ohos/hapsigntool/ProfileTest.java +++ b/hapsigntool/hap_sign_tool_lib/src/test/java/com/ohos/hapsigntool/ProfileTest.java @@ -17,6 +17,9 @@ package com.ohos.hapsigntool; import com.ohos.hapsigntool.api.LocalizationAdapter; import com.ohos.hapsigntool.api.model.Options; +import com.ohos.hapsigntool.error.CustomException; +import com.ohos.hapsigntool.error.ERROR; +import com.ohos.hapsigntool.error.VerifyException; import com.ohos.hapsigntool.key.KeyPairTools; import com.ohos.hapsigntool.profile.ProfileSignTool; import com.ohos.hapsigntool.profile.VerifyHelper; @@ -140,7 +143,12 @@ public class ProfileTest { byte[] p7b = ProfileSignTool.signProfile(provisionContent, signer, adapter.getSignAlg()); assertNotNull(p7b); VerifyHelper verifyHelper = new VerifyHelper(); - VerificationResult verificationResult = verifyHelper.verify(p7b); + VerificationResult verificationResult = null; + try { + verificationResult = verifyHelper.verify(p7b); + } catch (VerifyException e) { + CustomException.throwException(ERROR.VERIFY_ERROR, e.getMessage()); + } assertTrue(verificationResult.isVerifiedPassed()); try { diff --git a/tools/app-sign-srv-ca1.cer b/tools/app-sign-srv-ca1.cer deleted file mode 100644 index b89ed745b57f35376992a54a8fd0d2ee21acdb39..0000000000000000000000000000000000000000 --- a/tools/app-sign-srv-ca1.cer +++ /dev/null @@ -1,14 +0,0 @@ ------BEGIN CERTIFICATE----- -MIICHDCCAcKgAwIBAgIFAM1hgkMwCgYIKoZIzj0EAwIwVTELMAkGA1UEBhMCQ04x -FDASBgNVBAoMC09wZW5IYXJtb255MR4wHAYDVQQLDBVPcGVuSGFybW9ueSBDb21t -dW5pdHkxEDAOBgNVBAMMB1Jvb3QgQ0EwHhcNMjIwMzA0MDI0NjUxWhcNMjMwMzA0 -MDI0NjUxWjBuMQswCQYDVQQGEwJDTjEUMBIGA1UECgwLT3Blbkhhcm1vbnkxHjAc -BgNVBAsMFU9wZW5IYXJtb255IENvbW11bml0eTEpMCcGA1UEAwwgQXBwbGljYXRp -b24gU2lnbmF0dXJlIFNlcnZpY2UgQ0EwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNC -AAQrUbzV/Qa2LjwtTzp1MUpXwN73BNQRluZLCahN9e6IWLNsCifvTNuD+aDhZiA0 -AZ5SpWoY7J1GjwOMmLP7MGEeo2YwZDAdBgNVHQ4EFgQUkWt6dNOoPEKVXkGNtC5Z -hyWkGdMwHwYDVR0jBBgwFoAUcsPzleL32qFRbaDiIHxr3U7dMo8wEgYDVR0TAQH/ -BAgwBgEB/wIBADAOBgNVHQ8BAf8EBAMCAQYwCgYIKoZIzj0EAwIDSAAwRQIhAPHT -mL3jEbeAf1Uo7j0h5kdfwF/wSCoTKyDfljvawkghAiAeidOkKzA+GjSapKkW27Xx -piaCQxb8O7hL6BGL8M4SZw== ------END CERTIFICATE----- diff --git a/tools/auto_test.py b/tools/auto_test.py index f569d86445b0b17263089166527c6f76192dd1b3..3cf38a9a1310ccef3f6a7cbd93d0d6ecc01111e1 100644 --- a/tools/auto_test.py +++ b/tools/auto_test.py @@ -298,10 +298,11 @@ def run_target(case, cmd): if len(error) > 0: f.writelines(cmd + "\r\n") for line in error: - success = False f.writelines(str(line.strip()) + "\r\n") - command.wait() + code = command.wait() + if code != 0: + success = False end = time.time() case_result['total_cost'] = case_result['total_cost'] + (end - start) diff --git a/tools/commands.config b/tools/commands.config index 559d8c1c93c508bd9a1bb432dad4127d1eac8096..f82dfe209dc4d6eb92dca9dbd8ecaea6e7d680f6 100644 --- a/tools/commands.config +++ b/tools/commands.config @@ -85,11 +85,33 @@ 'verify-profile -inFile "app1-profile1.p7b"', 'verify-profile -inFile "app1-profile1.p7b" -outFile', 'verify-profile -inFile "app1-profile-cert-outTime.p7b" -outFile "result.json"', - 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456"', - 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app2.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_nopass.jks" -outFile "app1-signed.hap" -inForm "zip" -profileSigned "1" -extCfgFile "111.txt"', - 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "profile.json" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456" -inForm "bin" ', - 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456" -inForm "bin"', - 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "profile.json" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456" -profileSigned "0"' + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signedcode.hap" -keyPwd "123456" -keystorePwd "123456"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-unsignedcode.hap" -keyPwd "123456" -keystorePwd "123456" -signCode "0"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signedcode.hap" -keyPwd "123456" -keystorePwd "123456" -signCode "1"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app2.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_nopass.jks" -outFile "app1-signedcode.hap" -inForm "zip" -profileSigned "1" -extCfgFile "111.txt"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app2.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_nopass.jks" -outFile "app1-unsignedcode.hap" -inForm "zip" -profileSigned "1" -extCfgFile "111.txt" -signCode "0"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app2.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_nopass.jks" -outFile "app1-signedcode.hap" -inForm "zip" -profileSigned "1" -extCfgFile "111.txt" -signCode "1"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "profile.json" -keystoreFile "ohtest_pass.jks" -outFile "app1-signedcode.hap" -keyPwd "123456" -keystorePwd "123456" -inForm "bin" ', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "profile.json" -keystoreFile "ohtest_pass.jks" -outFile "app1-unsignedcode.hap" -keyPwd "123456" -keystorePwd "123456" -inForm "bin" -signCode "0"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "profile.json" -keystoreFile "ohtest_pass.jks" -outFile "app1-signedcode.hap" -keyPwd "123456" -keystorePwd "123456" -inForm "bin" -signCode "1"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signedcode.hap" -keyPwd "123456" -keystorePwd "123456" -inForm "bin"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-unsignedcode.hap" -keyPwd "123456" -keystorePwd "123456" -inForm "bin" -signCode "0"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signedcode.hap" -keyPwd "123456" -keystorePwd "123456" -inForm "bin" -signCode "1"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "profile.json" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signedcode.hap" -keyPwd "123456" -keystorePwd "123456" -profileSigned "0"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "profile.json" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-unsignedcode.hap" -keyPwd "123456" -keystorePwd "123456" -profileSigned "0" -signCode "0"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "profile.json" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signedcode.hap" -keyPwd "123456" -keystorePwd "123456" -profileSigned "0" -signCode "1"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/elf_unittest.elf" -keystoreFile "ohtest_pass.jks" -outFile "output-elf-codesign-profile" -keyPwd "123456" -keystorePwd "123456" -profileSigned "1" -signCode "1" -inForm elf', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -inFile "test/elf_unittest.elf" -keystoreFile "ohtest_pass.jks" -outFile "output-elf-codesign" -keyPwd "123456" -keystorePwd "123456" -signCode "1" -inForm elf', + 'verify-app -inFile "app1-signedcode.hap" -outCertChain "app-sign-srv-ca1.cer" -outProfile "app1-profile.p7b"', + 'verify-app -inFile "output-elf-codesign-profile" -outCertChain "app-sign-srv-ca1.cer" -outProfile "app1-profile.p7b" -inForm elf', + 'verify-app -inFile "output-elf-codesign" -outCertChain "app-sign-srv-ca1.cer" -outProfile "app1-profile.p7b" -inForm elf', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "profile.json" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-unsignedcode.hap" -keyPwd "123456" -keystorePwd "123456" -profileSigned "0" -signCode "0"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "profile.json" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signedcode.hap" -keyPwd "123456" -keystorePwd "123456" -profileSigned "0" -signCode "1"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/elf_unittest.elf" -keystoreFile "ohtest_pass.jks" -outFile "output-elf-codesign-profile" -keyPwd "123456" -keystorePwd "123456" -profileSigned "1" -signCode "1" -inForm elf', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -inFile "test/elf_unittest.elf" -keystoreFile "ohtest_pass.jks" -outFile "output-elf-codesign" -keyPwd "123456" -keystorePwd "123456" -signCode "1" -inForm elf', + 'verify-app -inFile "app1-signedcode.hap" -outCertChain "app-sign-srv-ca1.cer" -outProfile "app1-profile1.p7b"', + 'verify-app -inFile "output-elf-codesign-profile" -outCertChain "app-sign-srv-ca1.cer" -outProfile "app1-profile1.p7b" -inForm elf', + 'verify-app -inFile "output-elf-codesign" -outCertChain "app-sign-srv-ca1.cer" -outProfile "app1-profile1.p7b" -inForm elf' ], 'case-assert-false': [ 'generate-keypair -keyPwd 123456 -keyAlg ECC -keySize NIST-P-384 -keystoreFile "ohtest.jks" -keystorePwd 123456 -extCfgFile "111.txt"', @@ -339,45 +361,126 @@ 'verify-profile -outFile "verify-result.json" -inFile "app1-profile1.jks"', 'verify-profile -inFile "app1-profile1-changed.p7b" -outFile "verify-result.json"', 'verify-profile -inFile "app1-profile1.p7b" -outFile "verify-result.js00on"', - 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "remoteSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456"', - 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456"', - 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456"', - 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "rewrw@%$" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456"', - 'sign-app -keyAlias "oh-app1-key-v1-222" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456"', - 'sign-app -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456"', - 'sign-app -keyAlias -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456"', - 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456789" -keystorePwd "123456"', - 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keystorePwd "123456"', - 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456"', - 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456"', - 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile1-changed.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456"', - 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "profile.json" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456" -profileSigned "1"', - 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456" -profileSigned "0"', - 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456" -profileSigned "5"', - 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456" -profileSigned "String"', - 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456"', - 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456"', - 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "notexist\test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456"', - 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "profile.json" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456"', - 'sign-app -keyAlias "oh-app1-key-v1" -signAlg -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456"', - 'sign-app -keyAlias "oh-app1-key-v1" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456"', - 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "HMAC" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456"', - 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withRSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456"', - 'sign-app -keyAlias "oh-app1-key-v2" -signAlg "SHA256withRSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456"', - 'sign-app -keyAlias "oh-app1-key-v2" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456"', - 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456"', - 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohnull.p12" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456"', - 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.txt" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456"', - 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app2.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_nopass.jks" -outFile "app1-signed.hap" -keystorePwd "123456"', - 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456789"', - 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "remoteSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signedcode.hap" -keyPwd "123456" -keystorePwd "123456"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "remoteSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-unsignedcode.hap" -keyPwd "123456" -keystorePwd "123456" -signCode "0"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "remoteSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signedcode.hap" -keyPwd "123456" -keystorePwd "123456" -signCode "1"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signedcode.hap" -keyPwd "123456" -keystorePwd "123456"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-unsignedcode.hap" -keyPwd "123456" -keystorePwd "123456" -signCode "0"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signedcode.hap" -keyPwd "123456" -keystorePwd "123456" -signCode "1"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signedcode.hap" -keyPwd "123456" -keystorePwd "123456"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-unsignedcode.hap" -keyPwd "123456" -keystorePwd "123456" -signCode "0"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signedcode.hap" -keyPwd "123456" -keystorePwd "123456" -signCode "1"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "rewrw@%$" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signedcode.hap" -keyPwd "123456" -keystorePwd "123456"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "rewrw@%$" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-unsignedcode.hap" -keyPwd "123456" -keystorePwd "123456" -signCode "0"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "rewrw@%$" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signedcode.hap" -keyPwd "123456" -keystorePwd "123456" -signCode "1"', + 'sign-app -keyAlias "oh-app1-key-v1-222" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signedcode.hap" -keyPwd "123456" -keystorePwd "123456"', + 'sign-app -keyAlias "oh-app1-key-v1-222" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-unsignedcode.hap" -keyPwd "123456" -keystorePwd "123456" -signCode "0"', + 'sign-app -keyAlias "oh-app1-key-v1-222" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signedcode.hap" -keyPwd "123456" -keystorePwd "123456" -signCode "1"', + 'sign-app -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signedcode.hap" -keyPwd "123456" -keystorePwd "123456"', + 'sign-app -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-unsignedcode.hap" -keyPwd "123456" -keystorePwd "123456" -signCode "0"', + 'sign-app -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signedcode.hap" -keyPwd "123456" -keystorePwd "123456" -signCode "1"', + 'sign-app -keyAlias -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signedcode.hap" -keyPwd "123456" -keystorePwd "123456"', + 'sign-app -keyAlias -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-unsignedcode.hap" -keyPwd "123456" -keystorePwd "123456" -signCode "0"', + 'sign-app -keyAlias -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signedcode.hap" -keyPwd "123456" -keystorePwd "123456" -signCode "1"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signedcode.hap" -keyPwd "123456789" -keystorePwd "123456"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-unsignedcode.hap" -keyPwd "123456789" -keystorePwd "123456" -signCode "0"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signedcode.hap" -keyPwd "123456789" -keystorePwd "123456" -signCode "1"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signedcode.hap" -keystorePwd "123456"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-unsignedcode.hap" -keystorePwd "123456" -signCode "0"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signedcode.hap" -keystorePwd "123456" -signCode "1"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signedcode.hap" -keyPwd "123456" -keystorePwd "123456"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-unsignedcode.hap" -keyPwd "123456" -keystorePwd "123456" -signCode "0"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signedcode.hap" -keyPwd "123456" -keystorePwd "123456" -signCode "1"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signedcode.hap" -keyPwd "123456" -keystorePwd "123456"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-unsignedcode.hap" -keyPwd "123456" -keystorePwd "123456" -signCode "0"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signedcode.hap" -keyPwd "123456" -keystorePwd "123456" -signCode "1"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile1-changed.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signedcode.hap" -keyPwd "123456" -keystorePwd "123456"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile1-changed.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-unsignedcode.hap" -keyPwd "123456" -keystorePwd "123456" -signCode "0"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile1-changed.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signedcode.hap" -keyPwd "123456" -keystorePwd "123456" -signCode "1"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "profile.json" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signedcode.hap" -keyPwd "123456" -keystorePwd "123456" -profileSigned "1"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "profile.json" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-unsignedcode.hap" -keyPwd "123456" -keystorePwd "123456" -profileSigned "1" -signCode "0"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "profile.json" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signedcode.hap" -keyPwd "123456" -keystorePwd "123456" -profileSigned "1" -signCode "1"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signedcode.hap" -keyPwd "123456" -keystorePwd "123456" -profileSigned "0"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-unsignedcode.hap" -keyPwd "123456" -keystorePwd "123456" -profileSigned "0" -signCode "0"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signedcode.hap" -keyPwd "123456" -keystorePwd "123456" -profileSigned "0" -signCode "1"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signedcode.hap" -keyPwd "123456" -keystorePwd "123456" -profileSigned "5"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-unsignedcode.hap" -keyPwd "123456" -keystorePwd "123456" -profileSigned "5" -signCode "0"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signedcode.hap" -keyPwd "123456" -keystorePwd "123456" -profileSigned "5" -signCode "1"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signedcode.hap" -keyPwd "123456" -keystorePwd "123456" -profileSigned "String"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-unsignedcode.hap" -keyPwd "123456" -keystorePwd "123456" -profileSigned "String" -signCode "0"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signedcode.hap" -keyPwd "123456" -keystorePwd "123456" -profileSigned "String" -signCode "1"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -keystoreFile "ohtest_pass.jks" -outFile "app1-signedcode.hap" -keyPwd "123456" -keystorePwd "123456"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -keystoreFile "ohtest_pass.jks" -outFile "app1-unsignedcode.hap" -keyPwd "123456" -keystorePwd "123456" -signCode "0"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -keystoreFile "ohtest_pass.jks" -outFile "app1-signedcode.hap" -keyPwd "123456" -keystorePwd "123456" -signCode "1"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile -keystoreFile "ohtest_pass.jks" -outFile "app1-signedcode.hap" -keyPwd "123456" -keystorePwd "123456"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile -keystoreFile "ohtest_pass.jks" -outFile "app1-unsignedcode.hap" -keyPwd "123456" -keystorePwd "123456" -signCode "0"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile -keystoreFile "ohtest_pass.jks" -outFile "app1-signedcode.hap" -keyPwd "123456" -keystorePwd "123456" -signCode "1"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "notexist\test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signedcode.hap" -keyPwd "123456" -keystorePwd "123456"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "notexist\test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-unsignedcode.hap" -keyPwd "123456" -keystorePwd "123456" -signCode "0"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "notexist\test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signedcode.hap" -keyPwd "123456" -keystorePwd "123456" -signCode "1"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "profile.json" -keystoreFile "ohtest_pass.jks" -outFile "app1-signedcode.hap" -keyPwd "123456" -keystorePwd "123456"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "profile.json" -keystoreFile "ohtest_pass.jks" -outFile "app1-unsignedcode.hap" -keyPwd "123456" -keystorePwd "123456" -signCode "0"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "profile.json" -keystoreFile "ohtest_pass.jks" -outFile "app1-signedcode.hap" -keyPwd "123456" -keystorePwd "123456" -signCode "1"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signedcode.hap" -keyPwd "123456" -keystorePwd "123456"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-unsignedcode.hap" -keyPwd "123456" -keystorePwd "123456" -signCode "0"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signedcode.hap" -keyPwd "123456" -keystorePwd "123456" -signCode "1"', + 'sign-app -keyAlias "oh-app1-key-v1" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signedcode.hap" -keyPwd "123456" -keystorePwd "123456"', + 'sign-app -keyAlias "oh-app1-key-v1" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-unsignedcode.hap" -keyPwd "123456" -keystorePwd "123456" -signCode "0"', + 'sign-app -keyAlias "oh-app1-key-v1" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signedcode.hap" -keyPwd "123456" -keystorePwd "123456" -signCode "1"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "HMAC" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signedcode.hap" -keyPwd "123456" -keystorePwd "123456"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "HMAC" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-unsignedcode.hap" -keyPwd "123456" -keystorePwd "123456" -signCode "0"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "HMAC" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signedcode.hap" -keyPwd "123456" -keystorePwd "123456" -signCode "1"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withRSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signedcode.hap" -keyPwd "123456" -keystorePwd "123456"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withRSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-unsignedcode.hap" -keyPwd "123456" -keystorePwd "123456" -signCode "0"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withRSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signedcode.hap" -keyPwd "123456" -keystorePwd "123456" -signCode "1"', + 'sign-app -keyAlias "oh-app1-key-v2" -signAlg "SHA256withRSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signedcode.hap" -keyPwd "123456" -keystorePwd "123456"', + 'sign-app -keyAlias "oh-app1-key-v2" -signAlg "SHA256withRSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-unsignedcode.hap" -keyPwd "123456" -keystorePwd "123456" -signCode "0"', + 'sign-app -keyAlias "oh-app1-key-v2" -signAlg "SHA256withRSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signedcode.hap" -keyPwd "123456" -keystorePwd "123456" -signCode "1"', + 'sign-app -keyAlias "oh-app1-key-v2" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signedcode.hap" -keyPwd "123456" -keystorePwd "123456"', + 'sign-app -keyAlias "oh-app1-key-v2" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-unsignedcode.hap" -keyPwd "123456" -keystorePwd "123456" -signCode "0"', + 'sign-app -keyAlias "oh-app1-key-v2" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signedcode.hap" -keyPwd "123456" -keystorePwd "123456" -signCode "1"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -outFile "app1-signedcode.hap" -keyPwd "123456" -keystorePwd "123456"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -outFile "app1-unsignedcode.hap" -keyPwd "123456" -keystorePwd "123456" -signCode "0"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -outFile "app1-signedcode.hap" -keyPwd "123456" -keystorePwd "123456" -signCode "1"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohnull.p12" -outFile "app1-signedcode.hap" -keyPwd "123456" -keystorePwd "123456"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohnull.p12" -outFile "app1-unsignedcode.hap" -keyPwd "123456" -keystorePwd "123456" -signCode "0"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohnull.p12" -outFile "app1-signedcode.hap" -keyPwd "123456" -keystorePwd "123456" -signCode "1"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.txt" -outFile "app1-signedcode.hap" -keyPwd "123456" -keystorePwd "123456"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.txt" -outFile "app1-unsignedcode.hap" -keyPwd "123456" -keystorePwd "123456" -signCode "0"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.txt" -outFile "app1-signedcode.hap" -keyPwd "123456" -keystorePwd "123456" -signCode "1"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app2.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_nopass.jks" -outFile "app1-signedcode.hap" -keystorePwd "123456"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app2.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_nopass.jks" -outFile "app1-unsignedcode.hap" -keystorePwd "123456" -signCode "0"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app2.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_nopass.jks" -outFile "app1-signedcode.hap" -keystorePwd "123456" -signCode "1"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signedcode.hap" -keyPwd "123456" -keystorePwd "123456789"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-unsignedcode.hap" -keyPwd "123456" -keystorePwd "123456789" -signCode "0"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signedcode.hap" -keyPwd "123456" -keystorePwd "123456789" -signCode "1"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signedcode.hap" -keyPwd "123456"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-unsignedcode.hap" -keyPwd "123456" -signCode "0"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signedcode.hap" -keyPwd "123456" -signCode "1"', 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -keyPwd "123456" -keystorePwd "123456"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -keyPwd "123456" -keystorePwd "123456" -signCode "0"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -keyPwd "123456" -keystorePwd "123456" -signCode "1"', 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile -keyPwd "123456" -keystorePwd "123456"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile -keyPwd "123456" -keystorePwd "123456" -signCode "0"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile -keyPwd "123456" -keystorePwd "123456" -signCode "1"', 'sign-profile -keyAlias "oh-profile1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -profileCertFile "profile1_error.pem" -inFile "app1-profile-release.json" -keystoreFile "ohtest_pass.jks" -outFile "app1-profile.p7b" -keyPwd "123456" -keystorePwd "123456"', 'sign-profile -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -profileCertFile "profile1.pem" -inFile "app1-profile-release.json" -keystoreFile "ohtest.jks" -outFile "app1-profile.p7b" -keyPwd "123456" -keystorePwd "123456"', - 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1_error.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456"', - 'sign-app -keyAlias "oh-profile1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456"', - 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app2.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_nopass.jks" -outFile "app1-signed.hap" -inForm -profileSigned -extCfgFile ', - 'sign-app -keyAlias "oh-app1-key-v1" -keyPwd "123456" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app2.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_nopass.jks" -outFile "app1-signed.hap" -inForm -profileSigned -extCfgFile ' + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1_error.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest.jks" -outFile "app1-signedcode.hap" -keyPwd "123456" -keystorePwd "123456"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1_error.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest.jks" -outFile "app1-unsignedcode.hap" -keyPwd "123456" -keystorePwd "123456" -signCode "0"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1_error.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest.jks" -outFile "app1-signedcode.hap" -keyPwd "123456" -keystorePwd "123456" -signCode "1"', + 'sign-app -keyAlias "oh-profile1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest.jks" -outFile "app1-signedcode.hap" -keyPwd "123456" -keystorePwd "123456"', + 'sign-app -keyAlias "oh-profile1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest.jks" -outFile "app1-unsignedcode.hap" -keyPwd "123456" -keystorePwd "123456" -signCode "0"', + 'sign-app -keyAlias "oh-profile1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest.jks" -outFile "app1-signedcode.hap" -keyPwd "123456" -keystorePwd "123456" -signCode "1"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app2.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_nopass.jks" -outFile "app1-signedcode.hap" -inForm -profileSigned -extCfgFile ', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app2.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_nopass.jks" -outFile "app1-unsignedcode.hap" -inForm -profileSigned -extCfgFile -signCode "0"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app2.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_nopass.jks" -outFile "app1-signedcode.hap" -inForm -profileSigned -extCfgFile -signCode "1"', + 'sign-app -keyAlias "oh-app1-key-v1" -keyPwd "123456" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app2.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_nopass.jks" -outFile "app1-signedcode.hap" -inForm -profileSigned -extCfgFile ', + 'sign-app -keyAlias "oh-app1-key-v1" -keyPwd "123456" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app2.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_nopass.jks" -outFile "app1-unsignedcode.hap" -inForm -profileSigned -extCfgFile -signCode "0"', + 'sign-app -keyAlias "oh-app1-key-v1" -keyPwd "123456" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app2.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_nopass.jks" -outFile "app1-signedcode.hap" -inForm -profileSigned -extCfgFile -signCode "1"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "profile.json" -inFile "test/elf_unittest" -keystoreFile "ohtest_pass.jks" -outFile "output-elf-codesign-profile" -keyPwd "123456" -keystorePwd "123456" -profileSigned "0" -signCode "1"', + 'verify-app -inFile "app1-signedcode.hap" -outCertChain "" -outProfile "app1-profile-error.p7b"', + 'verify-app -inFile "app1-signedcode.hap" -outCertChain "app1.cer" -outProfile ""', + 'verify-app -inFile "app1-signedcode.hap" -outCertChain "" -outProfile ""', + 'verify-app -inFile "app1-signedcode.hap" -outCertChain "app1.ce7778r" -outProfile "app1-profile-error.p7b"', ] } \ No newline at end of file diff --git a/tools/root-ca1.cer b/tools/root-ca1.cer deleted file mode 100644 index af02c6a4e41249f644f3745f3801f20c2e2cffc9..0000000000000000000000000000000000000000 --- a/tools/root-ca1.cer +++ /dev/null @@ -1,13 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIB4TCCAYegAwIBAgIEJlDuCzAKBggqhkjOPQQDAjBVMQswCQYDVQQGEwJDTjEU -MBIGA1UECgwLT3Blbkhhcm1vbnkxHjAcBgNVBAsMFU9wZW5IYXJtb255IENvbW11 -bml0eTEQMA4GA1UEAwwHUm9vdCBDQTAeFw0yMjAzMDQwMjQ2NDdaFw0yMzAzMDQw -MjQ2NDdaMFUxCzAJBgNVBAYTAkNOMRQwEgYDVQQKDAtPcGVuSGFybW9ueTEeMBwG -A1UECwwVT3Blbkhhcm1vbnkgQ29tbXVuaXR5MRAwDgYDVQQDDAdSb290IENBMFkw -EwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE0kXaGsjyBd94GqKieIAcoXOjVIx6lPEk -RlzjHUh7ixAYQtyPo+pBG8VX7l6bMtICj9AdXz9lguaINvrSJyXzm6NFMEMwHQYD -VR0OBBYEFHLD85Xi99qhUW2g4iB8a91O3TKPMBIGA1UdEwEB/wQIMAYBAf8CAQAw -DgYDVR0PAQH/BAQDAgEGMAoGCCqGSM49BAMCA0gAMEUCIQCrZwpSiqyrGc7L978Q -cwydhj2zKhOzPsVQxWiJAgWFLwIgac56zDrBGwDegtnN1/FgfNE3od/qiJy5Yf9g -Bwm84pM= ------END CERTIFICATE----- diff --git a/tools/test/elf_unittest.elf b/tools/test/elf_unittest.elf new file mode 100644 index 0000000000000000000000000000000000000000..deffa1c72a2447c57c35616d89c8f459401c45ea --- /dev/null +++ b/tools/test/elf_unittest.elf @@ -0,0 +1,16 @@ +/* + * Copyright (c) 2023-2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + elf test file \ No newline at end of file