diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..5607a13fa8c8d40d7979218612da5d8ce949eb8e --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +.gradle/ +.idea/ +build/ +local.properties +gradle/ +gradlew +gradlew.bat + diff --git a/README.en.md b/README.en.md index d4744b55f2115897ed451a1a6f26af7f6ef04c03..d7c32c6498f0da8d135f6dfda2bae9f6b9fe34b1 100644 --- a/README.en.md +++ b/README.en.md @@ -1,36 +1,63 @@ # developtools_hapsigner -#### Description -{**When you're done, you can delete the content in this README and update the file with details for others getting started with your repository**} - -#### Software Architecture -Software architecture description +* Description +* Installation +* Instructions +* Auto generate script +#### Description +In order to ensure the integrity and reliability of the OpenHarmony application, the application needs to be signed when the application is built, so that the application can be installed, run, and debugged on a real device. This warehouse provides a jar toolkit with functions such as certificate generation and hap package signature. #### Installation +1. Clone this git +2. Configure the operating environment :Gradle 7.1, JDK 8 +3. Command to locate developtools_hapsigner/hapsigntool/ +4. Compile project with **gradle build** or **gradle jar** +5. You can find jar at ./hap_sign_tool/build/libs/hap_sign_tool-xxxx.jar -1. xxxx -2. xxxx -3. xxxx #### Instructions +Command example: + +```shell +java -jar +``` + +Complete usage example: +```shell +java -jar hap_sign_tool.jar generate-csr -keyAlias "oh-app1-key-v1" -keyPwd ***** -subject "C=CN,O=OpenHarmony,OU=OpenHarmony Community,CN=App1 Release" -signAlg SHA256withECDSA -keystoreFile "D:\OH\ohtest.jks" -keystorePwd ***** -outFile "D:\OH\oh-app1-key-v1.csr" +``` +You can also use -help to view the complete instructions +```shell +java -jar hap_sign_tool.jar -help +``` +**** +#### Auto generate script +You can also find a script in **autosign** folder +* start_create.sh/start_create.bat +* start_sign.sh/start_sign.bat +* auto_sign_main.py +* auto_sign.conf + +Steps: +1. Environment python3.x is required +2. Also related hap_sign_tool.jar +3. Get your unsigned hap package and Provision profile templates +4. Edit auto_sign.conf and replace it with your information +5. Run start_create.sh and start_sign.sh in Linux os +6. Or run start_create.bat and start_sign.bat in Window os + +**** + +#### Command description: + +* generate-keypair : generate key pair +* generate-csr : generate certificate signing request +* generate-cert : generate certificate in full, large and complete, any certificate can be generated +* generate-ca : generate root/subject CA certificate, if the key does not exist, generate the key together +* generate-app-cert : generate application debug/release certificate +* generate-profile-cert : generate application debug/release certificate +* sign-profile : Provision Profile file signature +* verify-profile : Provision Profile file verification +* sign-app : application package signature +* verify-app : application package file verification -1. xxxx -2. xxxx -3. xxxx - -#### Contribution - -1. Fork the repository -2. Create Feat_xxx branch -3. Commit your code -4. Create Pull Request - - -#### Gitee Feature - -1. You can use Readme\_XXX.md to support different languages, such as Readme\_en.md, Readme\_zh.md -2. Gitee blog [blog.gitee.com](https://blog.gitee.com) -3. Explore open source project [https://gitee.com/explore](https://gitee.com/explore) -4. The most valuable open source project [GVP](https://gitee.com/gvp) -5. The manual of Gitee [https://gitee.com/help](https://gitee.com/help) -6. The most popular members [https://gitee.com/gitee-stars/](https://gitee.com/gitee-stars/) diff --git a/README.md b/README.md index 48851b0dcf3ea80fac8f975a8bb012686d092e0b..553dede0144eb51437c5392a26b1417e2890ea14 100644 --- a/README.md +++ b/README.md @@ -1,39 +1,183 @@ -# developtools_hapsigner +# Hap包签名工具 + +* 介绍 +* 安装教程 +* 使用说明 +* 一键签名脚本 +* (附)命令说明 #### 介绍 -{**以下是 Gitee 平台说明,您可以替换此简介** -Gitee 是 OSCHINA 推出的基于 Git 的代码托管平台(同时支持 SVN)。专为开发者提供稳定、高效、安全的云端软件开发协作平台 -无论是个人、团队、或是企业,都能够用 Gitee 实现代码托管、项目管理、协作开发。企业项目请看 [https://gitee.com/enterprises](https://gitee.com/enterprises)} +为了保证OpenHarmony应用的完整性和来源可靠,在应用构建时需要对应用进行签名,才能在使用真机设备上安装、运行和调试该应用。本仓提供了一个具有证书生成、hap包签名等功能的jar工具包。 + +#### 安装教程 +1. 配置编译环境 :Gradle 7.1,JDK 8 +2. 下载代码 +3. 命令行打开文件至developtools_hapsigner/hapsigntool目录下 +4. **gradle build** 或 **gradle jar**编译生成jar +5. 文件在./hap_sign_tool/build/libs/hap_sign_tool-xxxx.jar -#### 软件架构 -软件架构说明 +#### 使用说明 +命令示例: +```shell +java -jar <签名工具.jar> <命令> <参数...> +``` -#### 安装教程 +完整使用示例: +```shell +java -jar hap_sign_tool.jar generate-csr -keyAlias "oh-app1-key-v1" -keyPwd ***** -subject "C=CN,O=OpenHarmony,OU=OpenHarmony Community,CN=App1 Release" -signAlg SHA256withECDSA -keystoreFile "D:\OH\ohtest.jks" -keystorePwd ***** -outFile "D:\OH\oh-app1-key-v1.csr" +``` -1. xxxx -2. xxxx -3. xxxx +也可以使用 -help 查看完整使用说明 +```shell +java -jar hap_sign_tool.jar -help +``` +**** -#### 使用说明 +#### 一键签名脚本 +使用一键签名脚本,免于输入繁杂的命令 + +打开本项目子目录autosign可见: +* start_create.sh/start_create.bat +* start_sign.sh/start_sign.bat +* auto_sign_main.py +* auto_sign.conf + +操作流程 +1. 脚本依赖环境python3.x +2. 脚本依赖hap_sign_tool.jar(参照上文编译生成的产物) +3. 准备好待签名的应用hap包和Provision profile模板文件 +4. 使用文本编辑器编辑auto_sign.conf,补全配置文件中的配置 +5. Linux运行start_create.sh、Windows运行start_create.bat生成签名所需文件 +6. Linux运行start_sign.sh、Windows运行start_sign.bat对hap包进行签名 + +**** +#### (附)命令说明: +##### 生成密钥对 +* generate-keypair : 生成密钥对 + * -keyAlias : 密钥别名,必填项; + * -keyPwd : 密钥口令,可选项; + * -keyAlg : 密钥算法,必填项,包括RSA/ECC; + * -keySize : 密钥长度,必填项,RSA算法的长度为2048/3072/4096,ECC算法的长度NIST-P-256/NIST-P-384; + * -keystoreFile : 密钥库文件,必填项,JKS或P12格式; + * -keystorePwd : 密钥库口令,可选项; + +##### 生成证书签名请求 +* generate-csr : 生成证书签名请求 + * -keyAlias : 密钥别名,必填项; + * -keyPwd : 密钥口令,可选项; + * -subject : 证书主题,必填项; + * -signAlg : 签名算法,必填项,包括SHA256withRSA / SHA384withRSA / SHA256withECDSA / SHA384withECDSA; + * -keystoreFile : 密钥库文件,必填项,JKS或P12格式; + * -keystorePwd : 密钥库口令,可选项; + * -outFile : 输出文件,可选项,如果不填,则直接输出到控制台; + +##### 生成根CA/子CA证书 +* generate-ca : 生成根CA/子CA证书,如果密钥不存在,一起生成密钥 + * -keyAlias : 密钥别名,必填项; + * -keyPwd : 密钥口令,可选项; + * -keyAlg : 密钥算法,必填项,包括RSA/ECC; + * -keySize : 密钥长度,必填项,RSA算法的长度为2048/3072/4096,ECC算法的长度NIST-P-256/NIST-P-384; + * -issuer : 颁发者的主题,可选项,如果不填,表示根CA + * -issuerKeyAlias : 颁发者的密钥别名,可选项,如果不填,表示根CA; + * -issuerKeyPwd : 颁发者的密钥口令,可选项 + * -subject : 证书主题,必填项; + * -validity : 证书有效期,可选项,默认为3650天; + * -signAlg : 签名算法,必填项,包括SHA256withRSA / SHA384withRSA / SHA256withECDSA / SHA384withECDSA; + * -basicConstraintsPathLen : 路径长度,可选项,默认为0; + * -keystoreFile : 密钥库文件,必填项,JKS或P12格式; + * -keystorePwd : 密钥库口令,可选项; + * -outFile : 输出证书文件,可选项,如果不填,则直接输出到控制台; + +##### 生成应用调试/发布证书 +* generate-app-cert : 生成应用调试/发布证书 + * -keyAlias : 密钥别名,必填项; + * -keyPwd : 密钥口令,可选项; + * -issuer : 颁发者的主题,必填项; + * -issuerKeyAlias : 颁发者的密钥别名,必填项; + * -issuerKeyPwd : 颁发者的密钥口令,可选项; + * -subject : 证书主题,必填项; + * -validity : 证书有效期,可选项,默认为1095天; + * -signAlg : 签名算法,必填项,包括SHA256withRSA / SHA384withRSA / SHA256withECDSA / SHA384withECDSA; + * -keystoreFile : 密钥库文件,必填项,JKS或P12格式; + * -keystorePwd : 密钥库口令,可选项; + * -outForm: 输出证书文件的格式,包括 cert / certChain,可选项,默认为cert; + * -rootCaCertFile: outForm为certChain时必填,根CA证书文件; + * -subCaCertFile: outForm为certChain时必填,二级子CA证书文件; + * -outFile : 输出证书文件(证书或证书链),可选项,如果不填,则直接输出到控制台; -1. xxxx -2. xxxx -3. xxxx +##### 生成应用调试/发布证书 +* generate-profile-cert : 生成profile调试/发布证书 + * -keyAlias : 密钥别名,必填项; + * -keyPwd : 密钥口令,可选项; + * -issuer : 颁发者的主题,必填项; + * -issuerKeyAlias : 颁发者的密钥别名,必填项; + * -issuerKeyPwd : 颁发者的密钥口令,可选项; + * -subject : 证书主题,必填项; + * -validity : 证书有效期,可选项,默认为1095天; + * -signAlg : 签名算法,必填项,包括SHA256withRSA / SHA384withRSA / SHA256withECDSA / SHA384withECDSA; + * -keystoreFile : 密钥库文件,必填项,JKS或P12格式; + * -keystorePwd : 密钥库口令,可选项; + * -outForm: 输出证书文件的格式,包括 cert / certChain,可选项,默认为cert; + * -rootCaCertFile: outForm为certChain时必填,根CA证书文件; + * -subCaCertFile: outForm为certChain时必填,二级子CA证书文件; + * -outFile : 输出证书文件(证书或证书链),可选项,如果不填,则直接输出到控制台; -#### 参与贡献 +##### 通用证书生成,可以生成自定义证书 +* generate-cert : 通用证书生成,可以生成自定义证书 + * -keyAlias : 密钥别名,必填项; + * -keyPwd : 密钥口令,可选项; + * -issuer : 颁发者的主题,必填项; + * -issuerKeyAlias : 颁发者的密钥别名,必填项; + * -issuerKeyPwd : 颁发者的密钥口令,可选项; + * -subject : 证书主题,必填项; + * -validity : 证书有效期,可选项,默认为1095天; + * -keyUsage : 密钥用法,必选项,包括digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment, keyAgreement, certificateSignature, crlSignature, encipherOnly和decipherOnly,如果证书包括多个密钥用法,用逗号分隔; + * -keyUsageCritical : keyUsage是否为关键项,可选项,默认为是; + * -extKeyUsage : 扩展密钥用法,可选项,包括clientAuthentication,serverAuthentication,codeSignature,emailProtection,smartCardLogin,timestamp,ocspSignature; + * -extKeyUsageCritical : extKeyUsage是否为关键项,可选项,默认为否; + * -signAlg : 签名算法,必填项,包括SHA256withRSA / SHA384withRSA / SHA256withECDSA / SHA384withECDSA; + * -basicConstraints : 是否包含basicConstraints,可选项,默认为否; + * -basicConstraintsCritical : basicConstraints是否包含为关键项,可选项,默认为否; + * -basicConstraintsCa : 是否为CA,可选项,默认为否; + * -basicConstraintsPathLen : 路径长度,可选项,默认为0; + * -keystoreFile : 密钥库文件,必填项,JKS或P12格式; + * -keystorePwd : 密钥库口令,可选项; + * -outFile : 输出证书文件,可选项,如果不填,则直接输出到控制台; -1. Fork 本仓库 -2. 新建 Feat_xxx 分支 -3. 提交代码 -4. 新建 Pull Request +##### ProvisionProfile文件签名 +* sign-profile : ProvisionProfile文件签名 + * -mode : 签名模式,必填项,包括localSign,remoteSign; + * -keyAlias : 密钥别名,必填项; + * -keyPwd : 密钥口令,可选项; + * -profileCertFile : Profile签名证书(证书链,顺序为三级-二级-根),必填项; + * -inFile : 输入的原始Provision Profile文件,必填项; + * -signAlg : 签名算法,必填项,包括SHA256withRSA / SHA384withRSA / SHA256withECDSA / SHA384withECDSA; + * -keystoreFile : 密钥库文件,localSign模式时为必填项,JKS或P12格式; + * -keystorePwd : 密钥库口令,可选项; + * -outFile : 输出签名后的Provision Profile文件,p7b格式,必填项; +##### ProvisionProfile文件验签 +* verify-profile : ProvisionProfile文件验签 + * -inFile:已签名的Provision Profile文件,p7b格式,必填项; + * -outFile:验证结果文件(包含验证结果和profile内容),json格式,可选项;如果不填,则直接输出到控制台; -#### 特技 +##### hap应用包签名 +* sign-app : hap应用包签名 + * -mode:签名模式,必填项,包括localSign,remoteSign,remoteResign; + * -keyAlias:密钥别名,必填项; + * -keyPwd:密钥口令,可选项; + * -appCertFile:应用签名证书文件(证书链,顺序为三级-二级-根),必填项; + * -profileFile:签名后的Provision Profile文件名,p7b格式,必填项; + * -inFile:输入的原始APP包文件,hap格式或bin格式,必填项; + * -signAlg:签名算法,必填项,包括SHA256withRSA / SHA384withRSA / SHA256withECDSA / SHA384withECDSA; + * -keystoreFile:密钥库文件,localSign模式时为必填项,JKS或P12格式; + * -keystorePwd: 密钥库口令,可选项; + * -outFile: 输出签名后的包文件,必填项; -1. 使用 Readme\_XXX.md 来支持不同的语言,例如 Readme\_en.md, Readme\_zh.md -2. Gitee 官方博客 [blog.gitee.com](https://blog.gitee.com) -3. 你可以 [https://gitee.com/explore](https://gitee.com/explore) 这个地址来了解 Gitee 上的优秀开源项目 -4. [GVP](https://gitee.com/gvp) 全称是 Gitee 最有价值开源项目,是综合评定出的优秀开源项目 -5. Gitee 官方提供的使用手册 [https://gitee.com/help](https://gitee.com/help) -6. Gitee 封面人物是一档用来展示 Gitee 会员风采的栏目 [https://gitee.com/gitee-stars/](https://gitee.com/gitee-stars/) +##### hap应用包文件验签 +* verify-app : hap应用包文件验签 + * -inFile:已签名的应用包文件,hap格式或bin格式,必填项; + * -outCertchain:签名的证书链文件,必填项; + * -outProfile:应用包中的profile文件,必填项; + \ No newline at end of file diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/hapsigntool/hap_sign_tool/build.gradle b/hapsigntool/hap_sign_tool/build.gradle new file mode 100644 index 0000000000000000000000000000000000000000..3205c9ba810b5d78bbf0caa99ae628511c517d47 --- /dev/null +++ b/hapsigntool/hap_sign_tool/build.gradle @@ -0,0 +1,43 @@ +plugins { + id 'java' + id 'application' +} + +group 'com.ohos' +version '1.0-SNAPSHOT' + +repositories { + mavenCentral() +} + +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.0' + implementation group: 'org.apache.logging.log4j', name: 'log4j-core', version: '2.17.0' + implementation 'com.google.code.gson:gson:2.8.6' + implementation project(':hap_sign_tool_lib') +} + +test { + useJUnitPlatform() +} + +jar { + duplicatesStrategy = DuplicatesStrategy.EXCLUDE + + from sourceSets.main.output + + dependsOn configurations.runtimeClasspath + from { + configurations.runtimeClasspath.findAll { it.name.endsWith('jar') }.collect { zipTree(it) } + } + exclude 'META-INF/*.DF' + exclude 'META-INF/*.DSA' + + manifest { + attributes 'Main-Class': 'com.ohos.hapsigntool.HapSignTool' + } + +} \ No newline at end of file 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 new file mode 100644 index 0000000000000000000000000000000000000000..f8a5c02c09acd9cde5f8ead4329875180a3f3d93 --- /dev/null +++ b/hapsigntool/hap_sign_tool/src/main/java/com/ohos/hapsigntool/HapSignTool.java @@ -0,0 +1,313 @@ +/* + * Copyright (c) 2021-2022 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; + + +import com.ohos.hapsigntool.api.ServiceApi; +import com.ohos.hapsigntool.api.SignToolServiceImpl; +import com.ohos.hapsigntool.api.model.Options; +import com.ohos.hapsigntool.error.CustomException; +import com.ohos.hapsigntool.error.ERROR; +import com.ohos.hapsigntool.utils.FileUtils; +import com.ohos.hapsigntool.utils.StringUtils; +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; + +/** + * HapSignTool. + * + * @since 2021/12/28 + */ +public final class HapSignTool { + /** + * Add log info. + */ + private static final Logger LOGGER = LogManager.getLogger(HapSignTool.class); + + /** + * Tool version. + */ + private static final String VERSION = "1.0.0"; + + /** + * Local sign. + */ + private static final String LOCAL_SIGN = "localSign"; + /** + * Remote sign. + */ + private static final String REMOTE_SIGN = "remoteSign"; + + private HapSignTool() { + } + + /** + * Main entry. + * + * @param args args + */ + public static void main(String[] args) { + try { + processCmd(args); + } catch (CustomException exception) { + LOGGER.debug(exception.getMessage(), exception); + LOGGER.error(exception.getMessage()); + } + } + + /** + * Process command. + * + * @param args args + * @return result + * @throws CustomException Failed exception + */ + public static boolean processCmd(String[] args) throws CustomException { + if (args.length == 0 || StringUtils.isEmpty(args[0])) { + help(); + } else if (args[0].equals("-h") || args[0].contains("-help")) { + help(); + } else if (args[0].equals("-v") || args[0].contains("-version")) { + version(); + } else { + ServiceApi api = new SignToolServiceImpl(); + Params params = CmdUtil.convert2Params(args); + LOGGER.debug(params.toString()); + LOGGER.info("Start " + params.getMethod()); + boolean result; + result = dispatchParams(params, api); + if (result) { + LOGGER.info(String.format("%s %s", params.getMethod(), "success")); + } else { + LOGGER.info(String.format("%s %s", params.getMethod(), "failed")); + } + return result; + } + return true; + } + + private static boolean callGenerators(Params params, ServiceApi api) { + boolean result = false; + switch (params.getMethod()) { + case Method.GENERATE_APP_CERT: + result = runAppCert(params.getOptions(), api); + break; + case Method.GENERATE_CA: + result = runCa(params.getOptions(), api); + break; + case Method.GENERATE_CERT: + result = runCert(params.getOptions(), api); + break; + case Method.GENERATE_CSR: + result = runCsr(params.getOptions(), api); + break; + case Method.GENERATE_KEYPAIR: + result = runKeypair(params.getOptions(), api); + break; + case Method.GENERATE_PROFILE_CERT: + result = runProfileCert(params.getOptions(), api); + break; + default: + CustomException.throwException(ERROR.COMMAND_ERROR, "Not support cmd"); + break; + } + return result; + } + + private static boolean dispatchParams(Params params, ServiceApi api) { + boolean result; + switch (params.getMethod()) { + case Method.SIGN_APP: + result = runSignApp(params.getOptions(), api); + break; + case Method.SIGN_PROFILE: + result = runSignProfile(params.getOptions(), api); + break; + case Method.VERIFY_APP: + result = runVerifyApp(params.getOptions(), api); + break; + case Method.VERIFY_PROFILE: + result = runVerifyProfile(params.getOptions(), api); + break; + default: + result = callGenerators(params, api); + break; + } + + return result; + } + + private static void checkEndCertArguments(Options params) { + params.required(Options.KEY_ALIAS, Options.ISSUER, Options.ISSUER_KEY_ALIAS, Options.SUBJECT, + Options.SIGN_ALG, Options.KEY_STORE_FILE); + String signAlg = params.getString(Options.SIGN_ALG); + CmdUtil.judgeSignAlgType(signAlg); + String outForm = params.getString(Options.OUT_FORM); + if (!StringUtils.isEmpty(outForm)) { + CmdUtil.verifyType(outForm, Options.OUT_FORM_SCOPE); + params.required(Options.SUB_CA_CERT_FILE, Options.CA_CERT_FILE); + FileUtils.validFileType(params.getString(Options.SUB_CA_CERT_FILE), "cer"); + FileUtils.validFileType(params.getString(Options.CA_CERT_FILE), "cer"); + } + String keyStoreFile = params.getString(Options.KEY_STORE_FILE); + FileUtils.validFileType(keyStoreFile, "p12", "jks"); + String outFile = params.getString(Options.OUT_FILE); + if (!StringUtils.isEmpty(outFile)) { + FileUtils.validFileType(outFile, "cer", "pem"); + } + } + + private static boolean runAppCert(Options params, ServiceApi api) { + checkEndCertArguments(params); + return api.generateAppCert(params); + } + + private static boolean runCa(Options params, ServiceApi api) { + params.required(Options.KEY_ALIAS, Options.KEY_ALG, Options.KEY_SIZE, Options.SUBJECT, + Options.SIGN_ALG, Options.KEY_STORE_FILE); + String keyAlg = params.getString(Options.KEY_ALG); + CmdUtil.judgeAlgType(keyAlg); + String size = params.getString(Options.KEY_SIZE); + CmdUtil.judgeSize(size, keyAlg); + String signAlg = params.getString(Options.SIGN_ALG); + CmdUtil.judgeSignAlgType(signAlg); + FileUtils.validFileType(params.getString(Options.KEY_STORE_FILE), "p12", "jks"); + params.put(Options.KEY_SIZE, CmdUtil.convertAlgSize(size)); + + return api.generateCA(params); + } + + private static boolean runCert(Options params, ServiceApi api) { + params.required(Options.KEY_ALIAS, Options.ISSUER, Options.ISSUER_KEY_ALIAS, Options.SUBJECT, + Options.KEY_USAGE, Options.SIGN_ALG, Options.KEY_STORE_FILE); + String keyUsage = params.getString(Options.KEY_USAGE); + CmdUtil.verifyType(keyUsage, Options.KEY_USAGE_SCOPE); + String extKeyUsage = params.getString(Options.EXT_KEY_USAGE); + CmdUtil.verifyType(extKeyUsage, Options.EXT_KEY_USAGE_SCOPE); + String signAlg = params.getString(Options.SIGN_ALG); + CmdUtil.judgeSignAlgType(signAlg); + FileUtils.validFileType(params.getString(Options.KEY_STORE_FILE), "p12", "jks"); + + return api.generateCert(params); + } + + private static boolean runCsr(Options params, ServiceApi api) { + params.required(Options.KEY_ALIAS, Options.SUBJECT, Options.SIGN_ALG, Options.KEY_STORE_FILE); + String signAlg = params.getString(Options.SIGN_ALG); + CmdUtil.judgeSignAlgType(signAlg); + FileUtils.validFileType(params.getString(Options.KEY_STORE_FILE), "p12", "jks"); + + return api.generateCsr(params); + } + + private static boolean runKeypair(Options params, ServiceApi api) { + params.required(Options.KEY_ALIAS, Options.KEY_ALG, Options.KEY_SIZE, Options.KEY_STORE_FILE); + String keyAlg = params.getString(Options.KEY_ALG); + CmdUtil.judgeAlgType(keyAlg); + String size = params.getString(Options.KEY_SIZE); + CmdUtil.judgeSize(size, keyAlg); + params.put(Options.KEY_SIZE, CmdUtil.convertAlgSize(size)); + FileUtils.validFileType(params.getString(Options.KEY_STORE_FILE), "p12", "jks"); + + return api.generateKeyStore(params); + } + + private static boolean runProfileCert(Options params, ServiceApi api) { + checkEndCertArguments(params); + return api.generateProfileCert(params); + } + + private static boolean runSignApp(Options params, ServiceApi api) { + params.required(Options.MODE, Options.IN_FILE, Options.OUT_FILE, Options.PROFILE_FILE, Options.SIGN_ALG); + String mode = params.getString(Options.MODE); + if (!LOCAL_SIGN.equalsIgnoreCase(mode) + && !REMOTE_SIGN.equalsIgnoreCase(mode) + && !"remoteResign".equalsIgnoreCase(mode)) { + CustomException.throwException(ERROR.COMMAND_ERROR, "mode params is incorrect"); + } + + if (LOCAL_SIGN.equalsIgnoreCase(mode)) { + params.required(Options.KEY_STORE_FILE, Options.KEY_ALIAS, Options.APP_CERT_FILE); + } + + String profileFile = params.getString(Options.PROFILE_FILE); + FileUtils.validFileType(profileFile, "p7b"); + String infile = params.getString(Options.IN_FILE); + FileUtils.validFileType(infile, "hap", "bin", "zip"); + String signAlg = params.getString(Options.SIGN_ALG); + CmdUtil.judgeSignAlgType(signAlg); + FileUtils.validFileType(params.getString(Options.KEY_STORE_FILE), "p12", "jks"); + + return api.signHap(params); + } + + private static boolean runSignProfile(Options params, ServiceApi api) { + params.required(Options.MODE, Options.SIGN_ALG, Options.OUT_FILE, Options.IN_FILE); + String mode = params.getString(Options.MODE); + if (!LOCAL_SIGN.equalsIgnoreCase(mode) && !REMOTE_SIGN.equalsIgnoreCase(mode)) { + CustomException.throwException(ERROR.COMMAND_ERROR, "mode params is incorrect"); + } + if (LOCAL_SIGN.equalsIgnoreCase(mode)) { + params.required(Options.KEY_STORE_FILE, Options.KEY_ALIAS, Options.PROFILE_CERT_FILE); + FileUtils.validFileType(params.getString(Options.KEY_STORE_FILE), "p12", "jks"); + } + + String signAlg = params.getString(Options.SIGN_ALG); + CmdUtil.judgeSignAlgType(signAlg); + String outFile = params.getString(Options.OUT_FILE); + FileUtils.validFileType(outFile, "p7b"); + + return api.signProfile(params); + } + + 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"); + FileUtils.validFileType(params.getString(Options.OUT_CERT_CHAIN), "cer"); + FileUtils.validFileType(params.getString(Options.OUT_PROFILE), "p7b"); + return api.verifyHap(params); + } + + private static boolean runVerifyProfile(Options params, ServiceApi api) { + params.required(Options.IN_FILE); + FileUtils.validFileType(params.getString(Options.IN_FILE), "p7b"); + String outFile = params.getString(Options.OUT_FILE); + if (!StringUtils.isEmpty(outFile)) { + FileUtils.validFileType(outFile, "json"); + } + + return api.verifyProfile(params); + } + + /** + * Software version. + */ + public static void version() { + LOGGER.info(VERSION); + } + + /** + * Print help to console. + */ + public static void help() { + HelpDocument.printHelp(LOGGER); + } +} 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 new file mode 100644 index 0000000000000000000000000000000000000000..d9cc73c2cebdae8c32439b2f9ea1a31f080023b2 --- /dev/null +++ b/hapsigntool/hap_sign_tool/src/main/java/com/ohos/hapsigntoolcmd/CmdUtil.java @@ -0,0 +1,234 @@ +/* + * Copyright (c) 2021-2022 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.hapsigntoolcmd; + +import com.ohos.hapsigntool.error.CustomException; +import com.ohos.hapsigntool.error.ERROR; +import com.ohos.hapsigntool.utils.StringUtils; +import com.ohos.hapsigntool.utils.ValidateUtils; + +import java.util.Arrays; +import java.util.List; +import java.util.regex.Pattern; + +/** + * CmdUtil. + * + * @since 2021/12/28 + */ +public final class CmdUtil { + /** + * Minimum length of input args. + */ + private static final int ARGS_MIN_LEN = 2; + + /** + * Match size String. + */ + private static final Pattern INTEGER_PATTERN = Pattern.compile("\\d{1,10}"); + + private CmdUtil() { + } + + /** + * Analysis and convert args to Params object. + * + * @param args Command line args + * @return Params + */ + public static Params convert2Params(String[] args) { + ValidateUtils.throwIfNotMatches(args.length >= ARGS_MIN_LEN, ERROR.COMMAND_ERROR, ""); + + Params params = new Params(); + params.setMethod(args[0]); + String keyStandBy = null; + for (int i = 1; i < args.length; i++) { + String value = args[i]; + // prepare key + if (value != null && (value.startsWith("-"))) { + keyStandBy = value.substring(1); + } else { + // prepare value + boolean success = validAndPutParam(params, keyStandBy, value); + if (success) { + keyStandBy = null; + } + } + } + return params; + } + + private static boolean validAndPutParam(Params params, String key, String value) { + boolean result; + if (StringUtils.isEmpty(key)) { + result = false; + } else if (StringUtils.isEmpty(value)) { + CustomException.throwException(ERROR.COMMAND_ERROR, + String.format("Command -%s could not be empty", key)); + result = false; + } else if (params.getOptions().containsKey(key)) { + CustomException.throwException(ERROR.COMMAND_ERROR, + String.format("Duplicate param '%s'. Stop processing", key)); + result = false; + } else if (key.toLowerCase().endsWith("pwd")) { + params.getOptions().put(key, value.toCharArray()); + result = true; + } else { + params.getOptions().put(key, value); + result = true; + } + return result; + } + + /** + * Alg type must between RSA and ECC. + * + * @param alg Incoming string + */ + public static void judgeAlgType(String alg) { + if (!"RSA".equalsIgnoreCase(alg) && !"ECC".equalsIgnoreCase(alg)) { + CustomException.throwException(ERROR.COMMAND_ERROR, + "KeyAlg params is incorrect"); + } + } + + /** + * Alg size must in below scope. + * + * @param alg Incoming string + */ + public static void judgeSize(String size, String alg) { + String[] array = {"2048", "3072", "4096", "NIST-P-256", "NIST-P-384"}; + List arrayList = Arrays.asList(array); + if (!arrayList.contains(size)) { + CustomException.throwException(ERROR.COMMAND_ERROR, String.format("KeySize '%s' is incorrect", size)); + } + + if ("RSA".equalsIgnoreCase(alg)) { + if (!"2048".equals(size) && !"3072".equals(size) && !"4096".equals(size)) { + CustomException.throwException(ERROR.COMMAND_ERROR, + String.format("KeySize of '%s' is incorrect", alg)); + } + } else { + if (!"NIST-P-256".equalsIgnoreCase(size) && !"NIST-P-384".equalsIgnoreCase(size)) { + CustomException.throwException(ERROR.COMMAND_ERROR, + String.format("KeySize of '%s' is incorrect", alg)); + } + } + } + + /** + * Sign alg must in the scope. + * + * @param signAlg sign alg + */ + public static void judgeSignAlgType(String signAlg) { + List arrayList = Arrays.asList("SHA256withRSA", "SHA384withRSA", "SHA256withECDSA", + "SHA384withECDSA"); + if (!arrayList.contains(signAlg)) { + CustomException.throwException(ERROR.COMMAND_ERROR, + "SignAlg params is incorrect"); + } + } + + /** + * verifyType. + * + * @param inputType Types with ',' + * @param supportTypes Target types with ',' + */ + public static void verifyType(String inputType, String supportTypes) { + String[] types = inputType.split(","); + List supportList = Arrays.asList(supportTypes.split(",")); + for (String t : types) { + if (StringUtils.isEmpty(t)) { + continue; + } + if (!supportList.contains(t.trim())) { + CustomException.throwException(ERROR.COMMAND_ERROR, + "'" + t + "' in params '" + inputType + "' is not support"); + } + } + } + + /** + * Convert passed in args to 'integer'. + * + * @param size String passed in + * @return 'integer' String + */ + public static String convertAlgSize(String size) { + if (size.startsWith("NIST-P-")) { + return size.replace("NIST-P-", ""); + } else if (INTEGER_PATTERN.matcher(size).matches()) { + return size; + } else { + CustomException.throwException(ERROR.COMMAND_ERROR, + String.format("KeySize '%s' is incorrect", size)); + return size; + } + } + + public static final class Method { + /** + * Generate app cert method name. + */ + public static final String GENERATE_APP_CERT = "generate-app-cert"; + /** + * Generate ca method name. + */ + public static final String GENERATE_CA = "generate-ca"; + /** + * Generate cert method name. + */ + public static final String GENERATE_CERT = "generate-cert"; + /** + * Generate csr method name. + */ + public static final String GENERATE_CSR = "generate-csr"; + /** + * Generate key pair method name. + */ + public static final String GENERATE_KEYPAIR = "generate-keypair"; + /** + * Generate profile cert method name. + */ + public static final String GENERATE_PROFILE_CERT = "generate-profile-cert"; + /** + * Sign app method name. + */ + public static final String SIGN_APP = "sign-app"; + /** + * Sign profile method name. + */ + public static final String SIGN_PROFILE = "sign-profile"; + /** + * Verify app method name. + */ + public static final String VERIFY_APP = "verify-app"; + /** + * Verify profile method name. + */ + public static final String VERIFY_PROFILE = "verify-profile"; + + /** + * Constructor of Method. + */ + private Method() { + //Empty constructor of Method. + } + } +} diff --git a/hapsigntool/hap_sign_tool/src/main/java/com/ohos/hapsigntoolcmd/HelpDocument.java b/hapsigntool/hap_sign_tool/src/main/java/com/ohos/hapsigntoolcmd/HelpDocument.java new file mode 100644 index 0000000000000000000000000000000000000000..163b79ead013f0a27e6db344567d21dd7dfc1f6b --- /dev/null +++ b/hapsigntool/hap_sign_tool/src/main/java/com/ohos/hapsigntoolcmd/HelpDocument.java @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2021-2022 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.hapsigntoolcmd; + +import com.ohos.hapsigntool.error.CustomException; +import com.ohos.hapsigntool.error.ERROR; +import com.ohos.hapsigntool.utils.FileUtils; +import org.apache.logging.log4j.Logger; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; + +/** + * HelpDocument. + * + * @since 2021/12/28 + */ +public final class HelpDocument { + private HelpDocument() { + } + + /** + * Print help.txt into logger.info. + * + * @param logger log4j + */ + public static void printHelp(Logger logger) { + ClassLoader classLoader = HelpDocument.class.getClassLoader(); + if (classLoader == null) { + return; + } + + String page = "help.txt"; + InputStream inputStream = classLoader.getResourceAsStream(page); + if (inputStream == null) { + return; + } + + try { + byte[] helpData = FileUtils.read(inputStream); + String helpStr = new String(helpData, StandardCharsets.UTF_8); + logger.info(helpStr); + } catch (IOException ioe) { + logger.debug(ioe.getMessage(), ioe); + CustomException.throwException(ERROR.READ_FILE_ERROR, "Failed to read " + page + " resource"); + } + } +} diff --git a/hapsigntool/hap_sign_tool/src/main/java/com/ohos/hapsigntoolcmd/Params.java b/hapsigntool/hap_sign_tool/src/main/java/com/ohos/hapsigntoolcmd/Params.java new file mode 100644 index 0000000000000000000000000000000000000000..793100a3062f7649da0e402fa141e0c60c18f5d9 --- /dev/null +++ b/hapsigntool/hap_sign_tool/src/main/java/com/ohos/hapsigntoolcmd/Params.java @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2021-2022 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.hapsigntoolcmd; + +import com.ohos.hapsigntool.api.model.Options; + +import java.util.Map; + +/** + * Params. + * + * @since 2021/12/28 + */ +public class Params { + /** + * Method names in the command line. + */ + private String method; + /** + * Hashmap for storing parameters. + */ + private final Options options = new Options(); + + /** + * Constructor of Params. + */ + public Params() { + //Empty constructor of Params + } + + public String getMethod() { + return method; + } + + public void setMethod(String method) { + this.method = method; + } + + public Options getOptions() { + return options; + } + + @Override + public String toString() { + StringBuilder stringBuilder = new StringBuilder(); + stringBuilder.append("Params{ method: "); + stringBuilder.append(method); + stringBuilder.append(", params: "); + for (Map.Entry item : options.entrySet()) { + stringBuilder.append('-'); + stringBuilder.append(item.getKey()); + stringBuilder.append("='"); + stringBuilder.append(item.getValue()); + stringBuilder.append("';"); + } + stringBuilder.append('}'); + + return stringBuilder.toString(); + } +} diff --git a/hapsigntool/hap_sign_tool/src/main/resources/help.txt b/hapsigntool/hap_sign_tool/src/main/resources/help.txt new file mode 100644 index 0000000000000000000000000000000000000000..1be752afd3dd3c2f8378f367eb696d65d60813ad --- /dev/null +++ b/hapsigntool/hap_sign_tool/src/main/resources/help.txt @@ -0,0 +1,167 @@ +USAGE: [options] +USAGE: [options] + + generate-keypair [options]: + -keyAlias : key alias, required fields; + -keyPwd : key password, optional fields; + -keyAlg : key algorithm, required fields, including RSA/ECC; + -keySize : key size, required fields, the size of the RSA algorithm is 2048/3072/4096, and the size of the ECC algorithm is NIST-P-256/NIST-P-384; + -keystoreFile : keystore file, required fields, JKS or P12 format; + -keystorePwd : keystore password, optional fields; + + EXAMPLE: + generate-keypair -keyAlias "oh-app1-key-v1" -keyPwd ****** -keyAlg ECC -keySize NIST-P-256 -keystoreFile "D:\OH\app-keypair.jks" -keystorePwd ****** + generate-keypair -keyAlias "oh-profile-key-v1" -keyPwd ****** -keyAlg RSA -keySize 2048 -keystoreFile "D:\OH\profile-keypair.jks" -keystorePwd ****** + + generate-csr [options]: + -keyAlias : key alias, required fields; + -keyPwd : key password, optional fields; + -subject : certificate subject, required fields; + -signAlg : signature algorithm, required fields, including SHA256withRSA/SHA384withRSA/SHA256withECDSA/SHA384withECDSA; + -keystoreFile : keystore file, required fields, JKS or P12 format; + -keystorePwd : keystore password, optional fields; + -outFile : output file, optional fields, if not filled, it will be directly output to the console; + + EXAMPLE: + generate-csr -keyAlias "oh-app1-key-v1" -keyPwd ****** -subject "C=CN,O=OpenHarmony,OU=OpenHarmony Community,CN=App1 Release" -signAlg SHA256withECDSA -keystoreFile "D:\OH\app-keypair.jks" -keystorePwd ****** -outFile "D:\OH\oh-app1-key-v1.csr" + + generate-cert [options]: + -keyAlias : key alias, required fields; + -keyPwd : key password, optional fields; + -issuer : issuer subject, required fields; + -issuerKeyAlias : issuer key alias, required fields; + -issuerKeyPwd : issuer key password, optional fields; + -subject : certificate subject, required fields; + -validity : certificate validity, optional fields, the default is 1095 days; + -keyUsage : key usage, required fields, including digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment, keyAgreement, certificateSignature, crlSignature, encipherOnly and decipherOnly, if the certificate includes multiple key usages, separate them with commas; + -keyUsageCritical : whether keyUsage is a key item, optional fields, the default is true; + -extKeyUsage : extended key usage, optional fields, including clientAuthentication, serverAuthentication, codeSignature, emailProtection, smartCardLogin, timestamp, ocspSignature; + -extKeyUsageCritical : whether extKeyUsage is a key item, optional fields, the default is false; + -signAlg : signature algorithm, required fields, including SHA256withRSA/SHA384withRSA/SHA256withECDSA/SHA384withECDSA; + -basicConstraints : whether to include basicConstraints, optional fields, the default is false; + -basicConstraintsCritical : whether basicConstraints is a key item, optional fields, the default is false; + -basicConstraintsCa : whether it is CA, optional fields, the default is false; + -basicConstraintsPathLen : basicConstraints path length, optional fields, the default is 0; + -keystoreFile : keystore file, required fields, JKS or P12 format; + -keystorePwd : keystore password, optional fields; + -outFile : output file, optional fields, if not filled, it will be directly output to the console; + + EXAMPLE: + generate-cert -keyAlias "oh-app1-key-v1" -keyPwd ****** -issuer "C=CN,O=OpenHarmony,OU=OpenHarmony Community,CN=Application Signature Service CA" -issuerKeyAlias "oh-app-sign-srv-ca-key-v1" -issuerKeyPwd ****** -subject "C=CN,O=OpenHarmony,OU=OpenHarmony Community,CN=App1 Release" -validity 365 -keyUsage digitalSignature -extKeyUsage codeSignature -signAlg SHA256withECDSA -keystoreFile "D:\OH\app-keypair.jks" -keystorePwd ****** -outFile "D:\OH\app1.cer" + + generate-ca [options]: + -keyAlias : key alias, required fields; + -keyPwd : key password, optional fields; + -keyAlg : key algorithm, required fields, including RSA/ECC; + -keySize : key size, required fields, the size of the RSA algorithm is 2048/3072/4096, and the size of the ECC algorithm is NIST-P-256/NIST-P-384; + -issuer : issuer subject, optional fields, if it is empty, it means root CA; + -issuerKeyAlias : issuer key alias, optional fields, if it is empty, it means root CA; + -issuerKeyPwd : issuer key password, optional fields; + -subject : certificate subject, required fields; + -validity : certificate validity, optional fields, the default is 3650 days; + -signAlg : signature algorithm, required fields, including SHA256withRSA/SHA384withRSA/SHA256withECDSA/SHA384withECDSA; + -basicConstraintsPathLen : basicConstraints path length, optional fields, the default is 0; + -keystoreFile : keystore file, required fields, JKS or P12 format; + -keystorePwd : keystore password, optional fields; + -outFile : output file, optional fields, if not filled, it will be directly output to the console; + + EXAMPLE: + generate-ca -keyAlias "oh-root-ca-key-v1" -subject "C=CN,O=OpenHarmony,OU=OpenHarmony Community,CN=Root CA" -validity 365 -signAlg SHA384withECDSA -keystoreFile "D:\OH\app-keypair.jks" -keystorePwd ****** -outFile "D:\OH\root-ca.cer" -keyAlg RSA -keySize 2048 + generate-ca -keyAlias "oh-app1-key-v1" -keyAlg RSA -keySize 2048 -issuer "C=CN,O=OpenHarmony,OU=OpenHarmony Community,CN=Root CA" -issuerKeyAlias "oh-sub-app-ca-key-v1" -issuerKeyPwd ****** -subject "C=CN,O=OpenHarmony,OU=OpenHarmony Community,CN= Application Signature Service CA" -validity 365 -signAlg SHA384withECDSA -keystoreFile "D:\OH\app-keypair.jks" -keystorePwd ****** -outFile "D:\OH\sub-app-sign-srv-ca.cer" + generate-ca -keyAlias "oh-profile-key-v1" -keyAlg RSA -keySize 4096 -issuer "C=CN,O=OpenHarmony,OU=OpenHarmony Community,CN=Root CA" -issuerKeyAlias "oh-sub-profile-ca-key-v1" -issuerKeyPwd ****** -subject "C=CN,O=OpenHarmony,OU=OpenHarmony Community,CN= Profile Signature Service CA" -validity 365 -signAlg SHA384withECDSA -keystoreFile "D:\OH\profile-keypair.jks" -keystorePwd ****** -outFile "D:\OH\sub-profile-sign-srv-ca.cer" + + generate-app-cert [options]: + -keyAlias : key alias, required fields; + -keyPwd : key password, optional fields; + -issuer : issuer subject, required fields; + -issuerKeyAlias : issuer key alias, required fields; + -issuerKeyPwd : issuer key password, optional fields; + -subject : certificate subject, required fields; + -validity : certificate validity, optional fields, the default is 1095 days; + -signAlg : signature algorithm, required fields, including SHA256withRSA/SHA384withRSA/SHA256withECDSA/SHA384withECDSA; + -keystoreFile : keystore file, required fields, JKS or P12 format; + -keystorePwd : keystore password, optional fields; + -outForm : the format of the output certificate file, including cert/certChain, optional fields, the default is cert; + -rootCaCertFile : root CA certificate file, required when outForm is certChain; + -subCaCertFile : secondary sub-CA certificate file, required when outForm is certChain; + -outFile : output certificate file (certificate or certificate chain), optional fields, if not filled, it will be directly output to the console; + + EXAMPLE: + generate-app-cert -keyAlias "oh-app1-key-v1" -keyPwd ****** -issuer "C=CN,O=OpenHarmony,OU=OpenHarmony Community,CN=Application Debug Signature Service CA" -issuerKeyAlias "oh-app-sign-debug-srv-ca-key-v1" -subject "C=CN,O=OpenHarmony,OU=OpenHarmony Community,CN=App1 Debug" -validity 365 -signAlg SHA256withECDSA -rootCaCertFile "D:\OH\root-ca.cer" -subCaCertFile "D:\OH\sub-app-sign-srv-ca.cer" -keystoreFile "D:\OH\app-keypair.jks" -keystorePwd ****** -outForm certChain -outFile "D:\OH\app-debug-cert.cer" + generate-app-cert -keyAlias "oh-app1-key-v1" -keyPwd ****** -issuer "C=CN,O=OpenHarmony,OU=OpenHarmony Community,CN=Application Release Signature Service CA" -issuerKeyAlias "oh-app-sign-release-srv-ca-key-v1" -subject "C=CN,O=OpenHarmony,OU=OpenHarmony Community,CN=App1 Release" -validity 365 -signAlg SHA256withECDSA -rootCaCertFile "D:\OH\root-ca.cer" -subCaCertFile "D:\OH\sub-app-sign-srv-ca.cer" -keystoreFile "D:\OH\app-keypair.jks" -keystorePwd ****** -outForm certChain -outFile "D:\OH\app-release-cert.cer" + + generate-profile-cert [options]: + -keyAlias : key alias, required fields; + -keyPwd : key password, optional fields; + -issuer : issuer subject, required fields; + -issuerKeyAlias : issuer key alias, required fields; + -issuerKeyPwd : issuer key password, optional fields; + -subject : certificate subject, required fields; + -validity : certificate validity, optional fields, the default is 1095 days; + -signAlg : signature algorithm, required fields, including SHA256withRSA/SHA384withRSA/SHA256withECDSA/SHA384withECDSA; + -keystoreFile : keystore file, required fields, JKS or P12 format; + -keystorePwd : keystore password, optional fields; + -outForm : the format of the output certificate file, including cert/certChain, optional fields, the default is cert; + -rootCaCertFile : root CA certificate file, required when outForm is certChain; + -subCaCertFile : secondary sub-CA certificate file, required when outForm is certChain; + -outFile : output file, optional fields, if not filled, it will be directly output to the console; + + EXAMPLE: + generate-profile-cert -keyAlias "oh-profile-key-v1" -keyPwd ****** -issuer "C=CN,O=OpenHarmony,OU=OpenHarmony Community,CN=Provision Profile Debug Signature Service CA" -issuerKeyAlias "oh-profile-sign-debug-srv-ca-key-v1" -issuerKeyPwd ****** -subject "C=CN,O=OpenHarmony,OU=OpenHarmony Community,CN=Provision Profile Debug" -validity 365 -signAlg SHA256withECDSA -rootCaCertFile "D:\OH\root-ca.cer" -subCaCertFile "D:\OH\sub-profile-sign-srv-ca.cer" -keystoreFile "D:\OH\profile-keypair.jks" -keystorePwd ****** -outForm certChain -outFile "D:\OH\provision-profile-debug.cer" + generate-profile-cert -keyAlias "oh-profile-key-v1" -keyPwd ****** -issuer "C=CN,O=OpenHarmony,OU=OpenHarmony Community,CN=Provision Profile Release Signature Service CA" -issuerKeyAlias "oh-profile-sign-release-srv-ca-key-v1" -issuerKeyPwd ****** -subject "C=CN,O=OpenHarmony,OU=OpenHarmony Community,CN=Provision Profile Release" -validity 365 -signAlg SHA256withECDSA -rootCaCertFile "D:\OH\root-ca.cer" -subCaCertFile "D:\OH\sub-profile-sign-srv-ca.cer" -keystoreFile "D:\OH\profile-keypair.jks" -keystorePwd ****** -outForm certChain -outFile "D:\OH\provision-profile-release.cer" + + sign-profile [options]: + -mode : signature mode, required fields, including localSign/remoteSign; + -keyAlias : key alias, required fields; + -keyPwd : key password, optional fields; + -profileCertFile : profile signing certificate (certificate chain, the order is three-level-two-root), required fields; + -inFile : input original Provision Profile file, required fields; + -signAlg : signature algorithm, required fields, including SHA256withRSA/SHA384withRSA/SHA256withECDSA/SHA384withECDSA; + -keystoreFile : keystore file, if signature mode is localSign, required fields, JKS or P12 format; + -keystorePwd : keystore password, optional fields; + -outFile : output the signed Provision Profile file, p7b format, required fields; + + EXAMPLE: + sign-profile -mode localSign -keyAlias "oh-profile-key-v1" -keyPwd ****** -profileCertFile "D:\OH\provision-profile-release.cer" -inFile "D:\OH\app1-profile-release.json" -signAlg SHA256withECDSA -keystoreFile "D:\OH\profile-keypair.jks" -keystorePwd ****** -outFile "D:\OH\signed-profile.p7b" + + verify-profile [options]: + -inFile : signed Provision Profile file, p7b format, required fields; + -outFile : Verification result file (including verification result and profile content), json format, optional; if not filled, it will be directly output to the console; + + EXAMPLE: + verify-profile -inFile "D:\OH\signed-profile.p7b" -outFile "D:\OH\VerifyResult.json" + + sign-app [options]: + -mode : signature mode, required fields, including localSign/remoteSign/remoteResign; + -keyAlias : key alias, required fields; + -keyPwd : key password, optional fields; + -appCertFIle : application signature certificate file, required fields; + -profileFile : signed Provision Profile file, p7b format, required fields; + -inFile : input original application package file, hap or bin format, required fields; + -signAlg : signature algorithm, required fields, including SHA256withRSA/SHA384withRSA/SHA256withECDSA/SHA384withECDSA; + -keystoreFile : keystore file, if signature mode is localSign, required fields, JKS or P12 format; + -keystorePwd : keystore password, optional fields; + -outFile : output the signed Provision Profile file, required fields; + + 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" + + 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; + + EXAMPLE: + verify-app -inFile "D:\OH\app1-signed.hap" -outcertchain "outCertchain.cer" -outProfile "outprofile.p7b" + +COMMANDS: + generate-keypair : generate key pair + generate-csr : generate certificate signing request + generate-cert : generate certificate in full, large and complete, any certificate can be generated + generate-ca : generate root/subject CA certificate, if the key does not exist, generate the key together + generate-app-cert : generate application debug/release certificate + generate-profile-cert : generate application debug/release certificate + sign-profile : Provision Profile file signature + verify-profile : Provision Profile file verification + sign-app : application package signature + verify-app : application package file verification \ No newline at end of file diff --git a/hapsigntool/hap_sign_tool/src/main/resources/log4j2.xml b/hapsigntool/hap_sign_tool/src/main/resources/log4j2.xml new file mode 100644 index 0000000000000000000000000000000000000000..87353ad62d4d5643600a51f02ed79c64693a455e --- /dev/null +++ b/hapsigntool/hap_sign_tool/src/main/resources/log4j2.xml @@ -0,0 +1,49 @@ + + + + + + + ../logs + ../logs + ../logs + + %d{yyyy-MM-dd HH:mm:ss.SSS} [%t-%L] %-5level %logger{36} - %msg%n + %d{MM-dd HH:mm:ss} %-5level - %msg%n + + + + + + + + + + + + + + + + + + + + \ No newline at end of file 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 new file mode 100644 index 0000000000000000000000000000000000000000..3ac4917618be646251a1b95c144a60ee200f1a16 --- /dev/null +++ b/hapsigntool/hap_sign_tool/src/test/java/com/ohos/hapsigntoolcmd/CmdUnitTest.java @@ -0,0 +1,747 @@ +/* + * Copyright (c) 2021-2022 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.hapsigntoolcmd; + +import com.ohos.hapsigntool.HapSignTool; +import com.ohos.hapsigntool.key.KeyPairTools; +import com.ohos.hapsigntool.utils.FileUtils; +import org.junit.jupiter.api.MethodOrderer; +import org.junit.jupiter.api.Order; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestMethodOrder; +import org.junit.platform.commons.logging.Logger; +import org.junit.platform.commons.logging.LoggerFactory; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +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; + +/** + * CmdUnitTest. + * + * @since 2021/12/28 + */ +@TestMethodOrder(MethodOrderer.OrderAnnotation.class) +public class CmdUnitTest { + + /** + * Command line parameter appCaCertFile. + */ + public static final String CMD_SUB_CA_CERT_FILE = "-subCaCertFile"; + /** + * Command line parameter outFile. + */ + public static final String CMD_OUT_FILE = "-outFile"; + /** + * Command line parameter basicConstraints. + */ + public static final String CMD_BASIC_CONSTRAINTS = "-basicConstraints"; + /** + * Command line parameter basicConstraintsCa. + */ + public static final String CMD_BASIC_CONSTRAINTS_CA = "-basicConstraintsCa"; + /** + * Command line parameter basicConstraintsCritical. + */ + public static final String CMD_BASIC_CONSTRAINTS_CRITICAL = "-basicConstraintsCritical"; + /** + * Command line parameter basicConstraintsPathLen. + */ + public static final String CMD_BASIC_CONSTRAINTS_PATH_LEN = "-basicConstraintsPathLen"; + /** + * Command line parameter caCertFile. + */ + public static final String CMD_ROOT_CA_CERT_FILE = "-rootCaCertFile"; + /** + * Command line parameter outForm. + */ + public static final String CMD_OUT_FORM = "-outForm"; + /** + * Command line parameter cert. + */ + public static final String CMD_CERT_CHAIN = "certChain"; + /** + * Command line parameter digitalSignature. + */ + public static final String CMD_DIGITAL_SIGNATURE = "digitalSignature"; + /** + * Command line parameter codeSignature and emailProtection. + */ + public static final String CMD_CODE_AND_EMAIL = "codeSignature,emailProtection"; + /** + * Command line parameter mode. + */ + public static final String CMD_MODE = "-mode"; + /** + * Command line parameter keystoreFile. + */ + public static final String CMD_KEY_STORE_FILE = "-keystoreFile"; + /** + * Command line parameter keystorePwd. + */ + public static final String CMD_KEY_STORE_RIGHTS = "-keystorePwd"; + /** + * Command line parameter keyAlg. + */ + public static final String CMD_KEY_ALG = "-keyAlg"; + /** + * Command line parameter keyAlias. + */ + public static final String CMD_KEY_ALIAS = "-keyAlias"; + /** + * Command line parameter keyPwd. + */ + public static final String CMD_KEY_RIGHTS = "-keyPwd"; + /** + * Command line parameter keySize. + */ + public static final String CMD_KEY_SIZE = "-keySize"; + /** + * Command line parameter keyUsage. + */ + public static final String CMD_KEY_USAGE = "-keyUsage"; + /** + * Command line parameter keyUsageCritical. + */ + public static final String CMD_KEY_USAGE_CRITICAL = "-keyUsageCritical"; + /** + * Command line parameter extKeyUsage. + */ + public static final String CMD_EXT_KEY_USAGE = "-extKeyUsage"; + /** + * Command line parameter extKeyUsageCritical. + */ + public static final String CMD_EXT_KEY_USAGE_CRITICAL = "-extKeyUsageCritical"; + /** + * Command line parameter profileCertFile. + */ + public static final String CMD_PROFILE_CERT_FILE = "-profileCertFile"; + /** + * Command line parameter subject. + */ + public static final String CMD_SUBJECT = "-subject"; + /** + * Command line parameter signAlg. + */ + public static final String CMD_SIGN_ALG = "-signAlg"; + /** + * Command line parameter inFile. + */ + public static final String CMD_IN_FILE = "-inFile"; + /** + * Command line parameter issuer. + */ + public static final String CMD_ISSUER = "-issuer"; + /** + * Command line parameter issuerKeyAlias. + */ + public static final String CMD_ISSUER_KEY_ALIAS = "-issuerKeyAlias"; + /** + * Command line parameter issuerKeyPwd. + */ + public static final String CMD_ISSUER_KEY_RIGHTS = "-issuerKeyPwd"; + /** + * Command line parameter validity. + */ + public static final String CMD_VALIDITY = "-validity"; + /** + * Command line parameter false. + */ + public static final String CMD_FALSE = "false"; + /** + * Command line parameter true. + */ + public static final String CMD_TRUE = "true"; + /** + * Command line parameter basicConstraintsPathLen is 0. + */ + public static final String CMD_BC_PATH_LEN_0 = "0"; + /** + * Command line parameter password is 123456. + */ + public static final String CMD_RIGHTS_123456 = "123456"; + /** + * Command line parameter RSA is 2048. + */ + public static final String CMD_RSA_2048 = "2048"; + /** + * Command line parameter validity is 365. + */ + public static final String CMD_VALIDITY_365 = "365"; + /** + * Command line parameter json file is UnsgnedDebugProfileTemplate. + */ + public static final String CMD_JSON_FILE = "UnsgnedDebugProfileTemplate.json"; + /** + * Command line parameter localSign. + */ + public static final String CMD_LOCAL_SIGN = "localSign"; + /** + * Command line parameter SHA256withRSA. + */ + public static final String CMD_SHA_256_WITH_RSA = "SHA256withRSA"; + /** + * Command line parameter cer file is test_app-debug-cert. + */ + public static final String CMD_APP_DEBUG_CERT_PATH = "test_app-debug-cert.pem"; + /** + * Command line parameter cer file is test_app-release-cert. + */ + public static final String CMD_APP_RELEASE_CERT_PATH = "test_app-release-cert.pem"; + /** + * Command line parameter cer file is test_cert. + */ + public static final String CMD_CERT_PATH = "test_cert.cer"; + /** + * Command line parameter csr file is test_csr. + */ + public static final String CMD_CSR_PATH = "test_csr.csr"; + /** + * Command line parameter jks file is test_app_csr. + */ + public static final String CMD_KEY_APP_STORE_PATH = "test_app_keypair.jks"; + /** + * Command line parameter jks file is test_profile_csr. + */ + public static final String CMD_KEY_PROFILE_STORE_PATH = "test_profile_keypair.jks"; + /** + * Command line parameter cer file is test_root_app_ca. + */ + public static final String CMD_ROOT_APP_CA_PATH = "test_root_app_ca.cer"; + /** + * Command line parameter cer file is test_root_profile_ca. + */ + public static final String CMD_ROOT_PROFILE_CA_PATH = "test_root_profile_ca.cer"; + /** + * Command line parameter cer file is test_sub_app_ca. + */ + public static final String CMD_SUB_APP_CA_PATH = "test_sub_app_ca.cer"; + /** + * Command line parameter cer file is test_sub_profile_ca. + */ + public static final String CMD_SUB_PROFILE_CA_PATH = "test_sub_profile_ca.cer"; + /** + * Command line parameter p7b file is test_sign_profile. + */ + public static final String CMD_SIGN_PROFILE_PATH = "test_sign_profile.p7b"; + /** + * Command line parameter cer file is test_profile-debug-cert. + */ + public static final String CMD_PROFILE_DEBUG_CERT_PATH = "test_profile-debug-cert.pem"; + /** + * Command line parameter cer file is test_profile-release-cert. + */ + public static final String CMD_PROFILE_RELEASE_CERT_PATH = "test_profile-release-cert.pem"; + /** + * Command line parameter cer file is test_verify_profile. + */ + public static final String CMD_VERIFY_PROFILE_RESULT_PATH = "test_verify_profile_result.json"; + /** + * Command line parameter oh-profile-key-v1. + */ + public static final String CMD_OH_PROFILE_KEY_V1 = "oh-profile-key-v1"; + /** + * Command line parameter oh-app1-key-v1. + */ + public static final String CMD_OH_APP1_KEY_V1 = "oh-app1-key-v1"; + /** + * Command line parameter oh-root-ca-key-v1. + */ + public static final String CMD_OH_ROOT_CA_KEY_V1 = "oh-root-ca-key-v1"; + /** + * Command line parameter oh-sub-app-ca-key-v1. + */ + public static final String CMD_OH_SUB_APP_CA_KEY_V1 = "oh-sub-app-ca-key-v1"; + /** + * Command line parameter oh-sub-profile-ca-key-v1. + */ + public static final String CMD_OH_SUB_PROFILE_CA_KEY_V1 = "oh-sub-profile-ca-key-v1"; + /** + * Command line parameter CN=ROOT CA. + */ + public static final String CMD_ROOT_CA = "C=CN,O=OpenHarmony,OU=OpenHarmony Community,CN=ROOT CA"; + /** + * Command line parameter CN=App1 Release. + */ + public static final String CMD_APP1_RELEASE = "C=CN,O=OpenHarmony,OU=OpenHarmony Community,CN=App1 Release"; + /** + * Command line parameter CN=Provision Profile Release. + */ + public static final String CMD_PROFILE_RELEASE = "C=CN,O=OpenHarmony," + + "OU=OpenHarmony Community,CN=Provision Profile Release"; + /** + * Command line parameter CN=Provision Profile Signature Service CA. + */ + public static final String CMD_PROFILE_CA = "C=CN,O=OpenHarmony,OU=OpenHarmony Community," + + "CN=Provision Profile Signature Service CA"; + /** + * Command line parameter CN=Application Signature Service CA. + */ + public static final String CMD_APP_CA = "C=CN," + + "O=OpenHarmony,OU=OpenHarmony Community,CN=Application Signature Service CA"; + + /** + * Add log info. + */ + private final Logger logger = LoggerFactory.getLogger(CmdUnitTest.class); + @Order(1) + @Test + public void testCmdKeypair() throws IOException { + + try { + deleteFile(CMD_KEY_APP_STORE_PATH); + deleteFile(CMD_KEY_PROFILE_STORE_PATH); + boolean result = HapSignTool.processCmd(new String[]{CmdUtil.Method.GENERATE_KEYPAIR}); + assertFalse(result); + assertFalse(FileUtils.isFileExist(CMD_KEY_APP_STORE_PATH)); + assertFalse(FileUtils.isFileExist(CMD_KEY_PROFILE_STORE_PATH)); + } catch (Exception exception) { + logger.info(exception, () -> exception.getMessage()); + } + + deleteFile(CMD_KEY_APP_STORE_PATH); + boolean result = HapSignTool.processCmd(new String[]{ + CmdUtil.Method.GENERATE_KEYPAIR, + CMD_KEY_ALIAS, CMD_OH_APP1_KEY_V1, + CMD_KEY_RIGHTS, CMD_RIGHTS_123456, + CMD_KEY_ALG, KeyPairTools.RSA, + CMD_KEY_SIZE, CMD_RSA_2048, + CMD_KEY_STORE_FILE, CMD_KEY_APP_STORE_PATH, + CMD_KEY_STORE_RIGHTS, CMD_RIGHTS_123456}); + assertTrue(result); + assertTrue(FileUtils.isFileExist(CMD_KEY_APP_STORE_PATH)); + deleteFile(CMD_KEY_PROFILE_STORE_PATH); + result = HapSignTool.processCmd(new String[]{ + CmdUtil.Method.GENERATE_KEYPAIR, + CMD_KEY_ALIAS, CMD_OH_PROFILE_KEY_V1, + CMD_KEY_RIGHTS, CMD_RIGHTS_123456, + CMD_KEY_ALG, KeyPairTools.RSA, + CMD_KEY_SIZE, CMD_RSA_2048, + CMD_KEY_STORE_FILE, CMD_KEY_PROFILE_STORE_PATH, + CMD_KEY_STORE_RIGHTS, CMD_RIGHTS_123456}); + assertTrue(result); + assertTrue(FileUtils.isFileExist(CMD_KEY_PROFILE_STORE_PATH)); + } + + /** + * Csr test case. + * + * @throws IOException Error + */ + @Order(2) + @Test + public void testCmdCsr() throws IOException { + + try { + deleteFile(CMD_CSR_PATH); + boolean result = HapSignTool.processCmd(new String[]{CmdUtil.Method.GENERATE_CSR}); + assertFalse(result); + assertFalse(FileUtils.isFileExist(CMD_CSR_PATH)); + } catch (Exception exception) { + logger.info(exception, () -> exception.getMessage()); + } + + deleteFile(CMD_CSR_PATH); + boolean result = HapSignTool.processCmd(new String[]{ + CmdUtil.Method.GENERATE_CSR, + CMD_KEY_ALIAS, CMD_OH_APP1_KEY_V1, + CMD_KEY_RIGHTS, CMD_RIGHTS_123456, + CMD_SUBJECT, CMD_APP1_RELEASE, + CMD_SIGN_ALG, CMD_SHA_256_WITH_RSA, + CMD_KEY_STORE_FILE, CMD_KEY_APP_STORE_PATH, + CMD_KEY_STORE_RIGHTS, CMD_RIGHTS_123456, + CMD_OUT_FILE, CMD_CSR_PATH}); + assertTrue(result); + assertTrue(FileUtils.isFileExist(CMD_CSR_PATH)); + } + + /** + * Cert test case + * + * @throws IOException Error + */ + @Order(3) + @Test + public void testCmdCert() throws IOException { + + try { + deleteFile(CMD_CERT_PATH); + boolean result = HapSignTool.processCmd(new String[]{CmdUtil.Method.GENERATE_CERT}); + assertFalse(result); + assertFalse(FileUtils.isFileExist(CMD_CERT_PATH)); + } catch (Exception exception) { + logger.info(exception, () -> exception.getMessage()); + } + + deleteFile(CMD_CERT_PATH); + boolean result = HapSignTool.processCmd(new String[]{ + CmdUtil.Method.GENERATE_CERT, + CMD_KEY_ALIAS, CMD_OH_APP1_KEY_V1, + CMD_KEY_RIGHTS, CMD_RIGHTS_123456, + CMD_ISSUER, CMD_APP_CA, + CMD_SIGN_ALG, CMD_SHA_256_WITH_RSA, + CMD_KEY_STORE_FILE, CMD_KEY_APP_STORE_PATH, + CMD_KEY_STORE_RIGHTS, CMD_RIGHTS_123456, + CMD_OUT_FILE, CMD_CERT_PATH, + CMD_ISSUER_KEY_ALIAS, CMD_OH_APP1_KEY_V1, + CMD_ISSUER_KEY_RIGHTS, CMD_RIGHTS_123456, + CMD_SUBJECT, CMD_APP1_RELEASE, + CMD_VALIDITY, CMD_VALIDITY_365, + CMD_KEY_USAGE, CMD_DIGITAL_SIGNATURE, + CMD_KEY_USAGE_CRITICAL, CMD_FALSE, + CMD_EXT_KEY_USAGE, CMD_CODE_AND_EMAIL, + CMD_EXT_KEY_USAGE_CRITICAL, CMD_TRUE, + CMD_BASIC_CONSTRAINTS, CMD_FALSE, + CMD_BASIC_CONSTRAINTS_CRITICAL, CMD_TRUE, + CMD_BASIC_CONSTRAINTS_CA, CMD_FALSE, + CMD_BASIC_CONSTRAINTS_PATH_LEN, CMD_BC_PATH_LEN_0}); + assertTrue(result); + assertTrue(FileUtils.isFileExist(CMD_CERT_PATH)); + } + + /** + * Ca test case. + * + * @throws IOException Error + */ + @Order(4) + @Test + public void testCmdCa() throws IOException { + try { + deleteFile(CMD_ROOT_APP_CA_PATH); + deleteFile(CMD_ROOT_PROFILE_CA_PATH); + deleteFile(CMD_SUB_APP_CA_PATH); + deleteFile(CMD_SUB_PROFILE_CA_PATH); + boolean result = HapSignTool.processCmd(new String[]{CmdUtil.Method.GENERATE_CA}); + assertFalse(result); + assertFalse(FileUtils.isFileExist(CMD_ROOT_APP_CA_PATH)); + assertFalse(FileUtils.isFileExist(CMD_ROOT_PROFILE_CA_PATH)); + assertFalse(FileUtils.isFileExist(CMD_SUB_APP_CA_PATH)); + assertFalse(FileUtils.isFileExist(CMD_SUB_PROFILE_CA_PATH)); + } catch (Exception exception) { + logger.info(exception, () -> exception.getMessage()); + } + deleteFile(CMD_ROOT_APP_CA_PATH); + boolean result = generateAppRootCa(); + assertTrue(result); + assertTrue(FileUtils.isFileExist(CMD_ROOT_APP_CA_PATH)); + deleteFile(CMD_ROOT_PROFILE_CA_PATH); + result = generateProfileRootCa(); + assertTrue(result); + assertTrue(FileUtils.isFileExist(CMD_ROOT_PROFILE_CA_PATH)); + deleteFile(CMD_SUB_APP_CA_PATH); + result = generateAppSubCa(); + assertTrue(result); + assertTrue(FileUtils.isFileExist(CMD_SUB_APP_CA_PATH)); + deleteFile(CMD_SUB_PROFILE_CA_PATH); + result = generateProfileSubCa(); + assertTrue(result); + assertTrue(FileUtils.isFileExist(CMD_SUB_PROFILE_CA_PATH)); + } + + /** + * App cert test case. + * + * @throws IOException Error + */ + @Order(5) + @Test + public void testCmdAppCert() throws IOException { + try { + deleteFile(CMD_APP_DEBUG_CERT_PATH); + deleteFile(CMD_APP_RELEASE_CERT_PATH); + boolean result = HapSignTool.processCmd(new String[]{CmdUtil.Method.GENERATE_APP_CERT}); + assertFalse(result); + assertFalse(FileUtils.isFileExist(CMD_APP_DEBUG_CERT_PATH)); + assertFalse(FileUtils.isFileExist(CMD_APP_RELEASE_CERT_PATH)); + } catch (Exception exception) { + logger.info(exception, () -> exception.getMessage()); + } + deleteFile(CMD_APP_DEBUG_CERT_PATH); + boolean result = HapSignTool.processCmd(new String[]{ + CmdUtil.Method.GENERATE_APP_CERT, + CMD_KEY_ALIAS, CMD_OH_APP1_KEY_V1, + CMD_KEY_RIGHTS, CMD_RIGHTS_123456, + CMD_ISSUER, CMD_APP_CA, + CMD_KEY_STORE_FILE, CMD_KEY_APP_STORE_PATH, + CMD_KEY_STORE_RIGHTS, CMD_RIGHTS_123456, + CMD_OUT_FILE, CMD_APP_DEBUG_CERT_PATH, + CMD_ISSUER_KEY_ALIAS, CMD_OH_SUB_APP_CA_KEY_V1, + CMD_ISSUER_KEY_RIGHTS, CMD_RIGHTS_123456, + CMD_SUBJECT, CMD_APP1_RELEASE, + CMD_VALIDITY, CMD_VALIDITY_365, + CMD_OUT_FORM, CMD_CERT_CHAIN, + CMD_ROOT_CA_CERT_FILE, CMD_ROOT_APP_CA_PATH, + CMD_SUB_CA_CERT_FILE, CMD_SUB_APP_CA_PATH, + CMD_SIGN_ALG, CMD_SHA_256_WITH_RSA}); + assertTrue(result); + assertTrue(FileUtils.isFileExist(CMD_APP_DEBUG_CERT_PATH)); + deleteFile(CMD_APP_RELEASE_CERT_PATH); + result = HapSignTool.processCmd(new String[]{ + CmdUtil.Method.GENERATE_APP_CERT, + CMD_KEY_ALIAS, CMD_OH_APP1_KEY_V1, + CMD_KEY_RIGHTS, CMD_RIGHTS_123456, + CMD_ISSUER, CMD_APP_CA, + CMD_KEY_STORE_FILE, CMD_KEY_APP_STORE_PATH, + CMD_KEY_STORE_RIGHTS, CMD_RIGHTS_123456, + CMD_OUT_FILE, CMD_APP_RELEASE_CERT_PATH, + CMD_ISSUER_KEY_ALIAS, CMD_OH_SUB_APP_CA_KEY_V1, + CMD_ISSUER_KEY_RIGHTS, CMD_RIGHTS_123456, + CMD_SUBJECT, CMD_APP1_RELEASE, + CMD_VALIDITY, CMD_VALIDITY_365, + CMD_OUT_FORM, CMD_CERT_CHAIN, + CMD_ROOT_CA_CERT_FILE, CMD_ROOT_APP_CA_PATH, + CMD_SUB_CA_CERT_FILE, CMD_SUB_APP_CA_PATH, + CMD_SIGN_ALG, CMD_SHA_256_WITH_RSA}); + assertTrue(result); + assertTrue(FileUtils.isFileExist(CMD_APP_RELEASE_CERT_PATH)); + } + + /** + * Profile cert test case + * + * @throws IOException Error + */ + @Order(6) + @Test + public void testCmdProfileCert() throws IOException { + try { + deleteFile(CMD_PROFILE_DEBUG_CERT_PATH); + deleteFile(CMD_PROFILE_RELEASE_CERT_PATH); + boolean result = HapSignTool.processCmd(new String[]{CmdUtil.Method.GENERATE_PROFILE_CERT}); + assertFalse(result); + assertFalse(FileUtils.isFileExist(CMD_PROFILE_DEBUG_CERT_PATH)); + assertFalse(FileUtils.isFileExist(CMD_PROFILE_RELEASE_CERT_PATH)); + } catch (Exception exception) { + logger.info(exception, () -> exception.getMessage()); + } + deleteFile(CMD_PROFILE_DEBUG_CERT_PATH); + boolean result = HapSignTool.processCmd(new String[]{ + CmdUtil.Method.GENERATE_PROFILE_CERT, + CMD_KEY_ALIAS, CMD_OH_PROFILE_KEY_V1, + CMD_KEY_RIGHTS, CMD_RIGHTS_123456, + CMD_ISSUER, CMD_PROFILE_CA, + CMD_KEY_STORE_FILE, CMD_KEY_PROFILE_STORE_PATH, + CMD_KEY_STORE_RIGHTS, CMD_RIGHTS_123456, + CMD_OUT_FILE, CMD_PROFILE_DEBUG_CERT_PATH, + CMD_ISSUER_KEY_ALIAS, CMD_OH_SUB_PROFILE_CA_KEY_V1, + CMD_ISSUER_KEY_RIGHTS, CMD_RIGHTS_123456, + CMD_SUBJECT, CMD_PROFILE_RELEASE, + CMD_VALIDITY, CMD_VALIDITY_365, + CMD_OUT_FORM, CMD_CERT_CHAIN, + CMD_ROOT_CA_CERT_FILE, CMD_ROOT_PROFILE_CA_PATH, + CMD_SUB_CA_CERT_FILE, CMD_SUB_PROFILE_CA_PATH, + CMD_SIGN_ALG, CMD_SHA_256_WITH_RSA}); + assertTrue(result); + assertTrue(FileUtils.isFileExist(CMD_PROFILE_DEBUG_CERT_PATH)); + deleteFile(CMD_PROFILE_RELEASE_CERT_PATH); + result = HapSignTool.processCmd(new String[]{ + CmdUtil.Method.GENERATE_PROFILE_CERT, + CMD_KEY_ALIAS, CMD_OH_PROFILE_KEY_V1, + CMD_KEY_RIGHTS, CMD_RIGHTS_123456, + CMD_ISSUER, CMD_PROFILE_CA, + CMD_KEY_STORE_FILE, CMD_KEY_PROFILE_STORE_PATH, + CMD_KEY_STORE_RIGHTS, CMD_RIGHTS_123456, + CMD_OUT_FILE, CMD_PROFILE_RELEASE_CERT_PATH, + CMD_ISSUER_KEY_ALIAS, CMD_OH_SUB_PROFILE_CA_KEY_V1, + CMD_ISSUER_KEY_RIGHTS, CMD_RIGHTS_123456, + CMD_SUBJECT, CMD_PROFILE_RELEASE, + CMD_VALIDITY, CMD_VALIDITY_365, + CMD_OUT_FORM, CMD_CERT_CHAIN, + CMD_ROOT_CA_CERT_FILE, CMD_ROOT_PROFILE_CA_PATH, + CMD_SUB_CA_CERT_FILE, CMD_SUB_PROFILE_CA_PATH, + CMD_SIGN_ALG, CMD_SHA_256_WITH_RSA}); + assertTrue(result); + assertTrue(FileUtils.isFileExist(CMD_PROFILE_RELEASE_CERT_PATH)); + } + + /** + * Sign profile test case. + * + * @throws IOException error + */ + @Order(7) + @Test + public void testCmdSignProfile() throws IOException { + + try { + deleteFile(CMD_SIGN_PROFILE_PATH); + boolean result = HapSignTool.processCmd(new String[]{CmdUtil.Method.SIGN_PROFILE}); + assertFalse(result); + assertFalse(FileUtils.isFileExist(CMD_SIGN_PROFILE_PATH)); + } catch (Exception exception) { + logger.info(exception, () -> exception.getMessage()); + } + + deleteFile(CMD_SIGN_PROFILE_PATH); + loadFile(CMD_JSON_FILE); + boolean result = HapSignTool.processCmd(new String[]{ + CmdUtil.Method.SIGN_PROFILE, + CMD_MODE, CMD_LOCAL_SIGN, + CMD_KEY_ALIAS, CMD_OH_PROFILE_KEY_V1, + CMD_KEY_RIGHTS, CMD_RIGHTS_123456, + CMD_PROFILE_CERT_FILE, CMD_PROFILE_RELEASE_CERT_PATH, + CMD_IN_FILE, CMD_JSON_FILE, + CMD_SIGN_ALG, CMD_SHA_256_WITH_RSA, + CMD_KEY_STORE_FILE, CMD_KEY_PROFILE_STORE_PATH, + CMD_KEY_STORE_RIGHTS, CMD_RIGHTS_123456, + CMD_OUT_FILE, CMD_SIGN_PROFILE_PATH}); + assertTrue(result); + assertTrue(FileUtils.isFileExist(CMD_SIGN_PROFILE_PATH)); + } + + /** + * Verify profile test case. + */ + @Order(8) + @Test + public void testVerifyProfile() { + try { + boolean result = HapSignTool.processCmd(new String[]{CmdUtil.Method.VERIFY_PROFILE}); + assertFalse(result); + } catch (Exception exception) { + logger.info(exception, () -> exception.getMessage()); + } + + boolean result = HapSignTool.processCmd(new String[]{ + CmdUtil.Method.VERIFY_PROFILE, + CMD_IN_FILE, CMD_SIGN_PROFILE_PATH, + CMD_OUT_FILE, CMD_VERIFY_PROFILE_RESULT_PATH}); + assertTrue(result); + } + + /** + * Sign hap test case. + */ + @Order(9) + @Test + public void testCmdSignApp() { + try { + boolean result = HapSignTool.processCmd(new String[]{CmdUtil.Method.SIGN_APP}); + assertFalse(result); + } catch (Exception exception) { + logger.info(exception, () -> exception.getMessage()); + } + } + + /** + * Verify signed app test case. + */ + @Order(10) + @Test + public void testCmdVerifyApp() { + try { + boolean result = HapSignTool.processCmd(new String[]{CmdUtil.Method.VERIFY_APP}); + assertFalse(result); + } catch (Exception exception) { + logger.info(exception, () -> exception.getMessage()); + } + } + + private boolean generateAppRootCa() { + boolean result = HapSignTool.processCmd(new String[]{ + CmdUtil.Method.GENERATE_CA, + CMD_KEY_ALIAS, CMD_OH_ROOT_CA_KEY_V1, + CMD_KEY_RIGHTS, CMD_RIGHTS_123456, + CMD_KEY_ALG, KeyPairTools.RSA, + CMD_KEY_SIZE, CMD_RSA_2048, + CMD_KEY_STORE_FILE, CMD_KEY_APP_STORE_PATH, + CMD_KEY_STORE_RIGHTS, CMD_RIGHTS_123456, + CMD_OUT_FILE, CMD_ROOT_APP_CA_PATH, + CMD_SUBJECT, CMD_ROOT_CA, + CMD_VALIDITY, CMD_VALIDITY_365, + CMD_SIGN_ALG, CMD_SHA_256_WITH_RSA, + CMD_BASIC_CONSTRAINTS_PATH_LEN, CMD_BC_PATH_LEN_0}); + return result; + } + + private boolean generateProfileRootCa() { + boolean result = HapSignTool.processCmd(new String[]{ + CmdUtil.Method.GENERATE_CA, + CMD_KEY_ALIAS, CMD_OH_ROOT_CA_KEY_V1, + CMD_KEY_RIGHTS, CMD_RIGHTS_123456, + CMD_KEY_ALG, KeyPairTools.RSA, + CMD_KEY_SIZE, CMD_RSA_2048, + CMD_KEY_STORE_FILE, CMD_KEY_PROFILE_STORE_PATH, + CMD_KEY_STORE_RIGHTS, CMD_RIGHTS_123456, + CMD_OUT_FILE, CMD_ROOT_PROFILE_CA_PATH, + CMD_SUBJECT, CMD_ROOT_CA, + CMD_VALIDITY, CMD_VALIDITY_365, + CMD_SIGN_ALG, CMD_SHA_256_WITH_RSA, + CMD_BASIC_CONSTRAINTS_PATH_LEN, CMD_BC_PATH_LEN_0}); + return result; + } + + private boolean generateAppSubCa() { + boolean result = HapSignTool.processCmd(new String[]{ + CmdUtil.Method.GENERATE_CA, + CMD_KEY_ALIAS, CMD_OH_SUB_APP_CA_KEY_V1, + CMD_KEY_RIGHTS, CMD_RIGHTS_123456, + CMD_ISSUER, CMD_ROOT_CA, + CMD_KEY_ALG, KeyPairTools.RSA, + CMD_KEY_SIZE, CMD_RSA_2048, + CMD_KEY_STORE_FILE, CMD_KEY_APP_STORE_PATH, + CMD_KEY_STORE_RIGHTS, CMD_RIGHTS_123456, + CMD_OUT_FILE, CMD_SUB_APP_CA_PATH, + CMD_ISSUER_KEY_ALIAS, CMD_OH_ROOT_CA_KEY_V1, + CMD_ISSUER_KEY_RIGHTS, CMD_RIGHTS_123456, + CMD_SUBJECT, CMD_APP_CA, + CMD_VALIDITY, CMD_VALIDITY_365, + CMD_SIGN_ALG, CMD_SHA_256_WITH_RSA, + CMD_BASIC_CONSTRAINTS_PATH_LEN, CMD_BC_PATH_LEN_0}); + return result; + } + + private boolean generateProfileSubCa() { + boolean result = HapSignTool.processCmd(new String[]{ + CmdUtil.Method.GENERATE_CA, + CMD_KEY_ALIAS, CMD_OH_SUB_PROFILE_CA_KEY_V1, + CMD_KEY_RIGHTS, CMD_RIGHTS_123456, + CMD_ISSUER, CMD_ROOT_CA, + CMD_KEY_ALG, KeyPairTools.RSA, + CMD_KEY_SIZE, CMD_RSA_2048, + CMD_KEY_STORE_FILE, CMD_KEY_PROFILE_STORE_PATH, + CMD_KEY_STORE_RIGHTS, CMD_RIGHTS_123456, + CMD_OUT_FILE, CMD_SUB_PROFILE_CA_PATH, + CMD_ISSUER_KEY_ALIAS, CMD_OH_ROOT_CA_KEY_V1, + CMD_ISSUER_KEY_RIGHTS, CMD_RIGHTS_123456, + CMD_SUBJECT, CMD_PROFILE_CA, + CMD_VALIDITY, CMD_VALIDITY_365, + CMD_SIGN_ALG, CMD_SHA_256_WITH_RSA, + CMD_BASIC_CONSTRAINTS_PATH_LEN, CMD_BC_PATH_LEN_0}); + return result; + } + + private void loadFile(String filePath) throws IOException { + ClassLoader classLoader = CmdUnitTest.class.getClassLoader(); + InputStream fileInputStream = classLoader.getResourceAsStream(filePath); + byte[] fileData = FileUtils.read(fileInputStream); + FileUtils.write(fileData, new File(filePath)); + } + + private void deleteFile(String filePath) throws IOException { + if (FileUtils.isFileExist(filePath)) { + Path path = Paths.get(filePath); + Files.delete(path); + } + } +} diff --git a/hapsigntool/hap_sign_tool/src/test/resources/UnsgnedDebugProfileTemplate.json b/hapsigntool/hap_sign_tool/src/test/resources/UnsgnedDebugProfileTemplate.json new file mode 100644 index 0000000000000000000000000000000000000000..72a6eab68e6ec090ca922812cce024818a1a6750 --- /dev/null +++ b/hapsigntool/hap_sign_tool/src/test/resources/UnsgnedDebugProfileTemplate.json @@ -0,0 +1,29 @@ +{ + "version-name": "1.0.0", + "version-code": 1, + "uuid": "fe686e1b-3770-4824-a938-961b140a7c98", + "validity": { + "not-before": 1610519532, + "not-after": 1705127532 + }, + "type": "debug", + "bundle-info": { + "developer-id": "OpenHarmony", + "development-certificate": "-----BEGIN CERTIFICATE-----\nMIICMzCCAbegAwIBAgIEaOC/zDAMBggqhkjOPQQDAwUAMGMxCzAJBgNVBAYTAkNO\nMRQwEgYDVQQKEwtPcGVuSGFybW9ueTEZMBcGA1UECxMQT3Blbkhhcm1vbnkgVGVh\nbTEjMCEGA1UEAxMaT3Blbkhhcm1vbnkgQXBwbGljYXRpb24gQ0EwHhcNMjEwMjAy\nMTIxOTMxWhcNNDkxMjMxMTIxOTMxWjBoMQswCQYDVQQGEwJDTjEUMBIGA1UEChML\nT3Blbkhhcm1vbnkxGTAXBgNVBAsTEE9wZW5IYXJtb255IFRlYW0xKDAmBgNVBAMT\nH09wZW5IYXJtb255IEFwcGxpY2F0aW9uIFJlbGVhc2UwWTATBgcqhkjOPQIBBggq\nhkjOPQMBBwNCAATbYOCQQpW5fdkYHN45v0X3AHax12jPBdEDosFRIZ1eXmxOYzSG\nJwMfsHhUU90E8lI0TXYZnNmgM1sovubeQqATo1IwUDAfBgNVHSMEGDAWgBTbhrci\nFtULoUu33SV7ufEFfaItRzAOBgNVHQ8BAf8EBAMCB4AwHQYDVR0OBBYEFPtxruhl\ncRBQsJdwcZqLu9oNUVgaMAwGCCqGSM49BAMDBQADaAAwZQIxAJta0PQ2p4DIu/ps\nLMdLCDgQ5UH1l0B4PGhBlMgdi2zf8nk9spazEQI/0XNwpft8QAIwHSuA2WelVi/o\nzAlF08DnbJrOOtOnQq5wHOPlDYB4OtUzOYJk9scotrEnJxJzGsh/\n-----END CERTIFICATE-----\n", + "bundle-name": "com.OpenHarmony.app.test", + "app-feature": "hos_system_app" + }, + "permissions": { + "restricted-permissions": [ + "" + ] + }, + "debug-info": { + "device-ids": [ + "69C7505BE341BDA5948C3C0CB44ABCD530296054159EFE0BD16A16CD0129CC42", + "7EED06506FCE6325EB2E2FAA019458B856AB10493A6718C7679A73F958732865" + ], + "device-id-type": "udid" + }, + "issuer": "pki_internal" +} \ No newline at end of file diff --git a/hapsigntool/hap_sign_tool_lib/build.gradle b/hapsigntool/hap_sign_tool_lib/build.gradle new file mode 100644 index 0000000000000000000000000000000000000000..b5f7c0ce085f52045a7a1ccdb4c0ba5ea95dff9e --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/build.gradle @@ -0,0 +1,24 @@ +plugins { + id 'java' +} + +group 'com.ohos' +version '1.0-SNAPSHOT' + + +repositories { + mavenCentral() +} + +dependencies { + 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.0' + implementation group: 'org.apache.logging.log4j', name: 'log4j-core', version: '2.17.0' + implementation 'org.bouncycastle:bcpkix-jdk15on:1.69' + implementation 'com.google.code.gson:gson:2.8.6' +} + +test { + useJUnitPlatform() +} \ No newline at end of file diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/api/LocalizationAdapter.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/api/LocalizationAdapter.java new file mode 100644 index 0000000000000000000000000000000000000000..a1556a23e56ef7991d7c09c5182ce1fa5e81e9d5 --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/api/LocalizationAdapter.java @@ -0,0 +1,380 @@ +/* + * Copyright (c) 2021-2022 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.api; + +import com.ohos.hapsigntool.api.model.Options; +import com.ohos.hapsigntool.error.CustomException; +import com.ohos.hapsigntool.error.ERROR; +import com.ohos.hapsigntool.key.KeyPairTools; +import com.ohos.hapsigntool.keystore.KeyStoreHelper; +import com.ohos.hapsigntool.utils.CertUtils; +import com.ohos.hapsigntool.utils.FileUtils; +import com.ohos.hapsigntool.utils.StringUtils; +import com.ohos.hapsigntool.utils.ValidateUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.bouncycastle.asn1.x500.X500Name; +import org.bouncycastle.asn1.x509.KeyPurposeId; +import org.bouncycastle.asn1.x509.KeyUsage; + +import java.io.File; +import java.io.IOException; +import java.security.KeyPair; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; +import java.util.Arrays; +import java.util.List; + +/** + * Localization adapter. + * + * @since 2021/12/28 + */ +public class LocalizationAdapter { + /** + * check cert chain size + */ + private static final int CERT_CHAIN_SIZE = 3; + /** + * Logger + */ + private final Logger logger = LogManager.getLogger(LocalizationAdapter.class); + /** + * Params + */ + private final Options options; + /** + * Operation of keystore + */ + private KeyStoreHelper keyStoreHelper; + + /** + * Constructor of LocalizationAdapter. + * + * @param options options + */ + public LocalizationAdapter(Options options) { + this.options = options; + } + + /** + * Get options. + * + * @return options + */ + public Options getOptions() { + return options; + } + + private void initKeyStore() { + // Avoid duplicated initialization + if (keyStoreHelper != null) { + return; + } + String keyStore = options.getString(Options.KEY_STORE_FILE, ""); + keyStoreHelper = new KeyStoreHelper(keyStore, options.getChars(Options.KEY_STORE_RIGHTS)); + } + + /** + * Get KeyPair through key alias and password. + * + * @param autoCreate autoCreate + * @return keyPair + */ + public KeyPair getAliasKey(boolean autoCreate) { + return getKeyPair(options.getString(Options.KEY_ALIAS), + options.getChars(Options.KEY_RIGHTS), autoCreate); + } + + /** + * getIssuerAliasKey. + * + * @return param of issuerKeyAlias + */ + public KeyPair getIssuerAliasKey() { + return getKeyPair(options.getString(Options.ISSUER_KEY_ALIAS), + options.getChars(Options.ISSUER_KEY_RIGHTS), false); + } + + /** + * Keystore has alias or not. + * + * @param alias alias + * @return true or false + */ + public boolean hasAlias(String alias) { + if (keyStoreHelper == null) { + initKeyStore(); + } + return keyStoreHelper.hasAlias(alias); + } + + /** + * Error if not exist. + * + * @param alias alias + */ + public void errorIfNotExist(String alias) { + if (keyStoreHelper == null) { + initKeyStore(); + } + keyStoreHelper.errorIfNotExist(alias); + } + + /** + * Error on exist. + * + * @param alias alias + */ + public void errorOnExist(String alias) { + if (keyStoreHelper == null) { + initKeyStore(); + } + keyStoreHelper.errorOnExist(alias); + } + + private KeyPair getKeyPair(String alias, char[] keyPwd, boolean autoCreate) { + if (keyStoreHelper == null) { + initKeyStore(); + } + ValidateUtils.throwIfNotMatches(!StringUtils.isEmpty(alias), ERROR.ACCESS_ERROR, "Alias could not be empty"); + KeyPair keyPair = null; + if (keyStoreHelper.hasAlias(alias)) { + keyPair = keyStoreHelper.loadKeyPair(alias, keyPwd); + } else { + if (autoCreate) { + options.required(Options.KEY_ALG, Options.KEY_SIZE); + keyPair = KeyPairTools.generateKeyPair(options.getString(Options.KEY_ALG), + options.getInt(Options.KEY_SIZE)); + keyStoreHelper.store(alias, keyPwd, keyPair, null); + } + } + ValidateUtils.throwIfNotMatches(keyPair != null, ERROR.NOT_SUPPORT_ERROR, + String.format("%s: '%s' is not exist in %s", Options.KEY_ALIAS, alias, + options.getString(Options.KEY_STORE_FILE))); + return keyPair; + } + + /** + * getProfileCert. + * + * @return profile cert + */ + public List getSignCertChain() { + String certPath = options.getString(Options.PROFILE_CERT_FILE); + if (StringUtils.isEmpty(certPath)) { + certPath = options.getString(Options.APP_CERT_FILE); + } + List certificates = getCertsFromFile(certPath, Options.PROFILE_CERT_FILE); + ValidateUtils.throwIfNotMatches(certificates.size() == CERT_CHAIN_SIZE, ERROR.NOT_SUPPORT_ERROR, + String.format("Profile cert '%s' must a cert chain", certPath)); + return certificates; + } + + /** + * getSubCaCertFile. + * + * @return sub ca cert + */ + public X509Certificate getSubCaCertFile() { + String certPath = options.getString(Options.SUB_CA_CERT_FILE); + return getCertsFromFile(certPath, Options.SUB_CA_CERT_FILE).get(0); + } + + /** + * getCaCertFile. + * + * @return root ca cert + */ + public X509Certificate getCaCertFile() { + String certPath = options.getString(Options.CA_CERT_FILE); + return getCertsFromFile(certPath, Options.CA_CERT_FILE).get(0); + } + + /** + * isOutFormChain. + * + * @return is out form chain + */ + public boolean isOutFormChain() { + String outForm = options.getString(Options.OUT_FORM, "cert"); + return outForm.equals("certChain"); + } + + /** + * Get certificates from file. + * + * @param certPath certPath + * @param logTitle logTitle + * @return certificates + */ + public List getCertsFromFile(String certPath, String logTitle) { + ValidateUtils.throwIfNotMatches(!StringUtils.isEmpty(certPath), ERROR.NOT_SUPPORT_ERROR, + String.format("Params '%s' is not exist", logTitle)); + + File certFile = new File(certPath); + ValidateUtils.throwIfNotMatches(certFile.exists(), ERROR.FILE_NOT_FOUND, + String.format("%s: '%s' is not exist", logTitle, certPath)); + List certificates = null; + try { + certificates = CertUtils.generateCertificates(FileUtils.readFile(certFile)); + } catch (IOException | CertificateException exception) { + logger.debug(exception.getMessage(), exception); + CustomException.throwException(ERROR.ACCESS_ERROR, exception.getMessage()); + } + ValidateUtils.throwIfNotMatches(certificates != null && certificates.size() > 0, ERROR.READ_FILE_ERROR, + String.format("Read fail from %s, bot found certificates", certPath)); + return certificates; + } + + /** + * getSignAlg. + * + * @return sign alg + */ + public String getSignAlg() { + return options.getString(Options.SIGN_ALG); + } + + /** + * isKeyUsageCritical. + * + * @return isKeyUsageCritical + */ + public boolean isKeyUsageCritical() { + return options.getBoolean(Options.KEY_USAGE_CRITICAL, true); + } + + /** + * isExtKeyUsageCritical. + * + * @return isExtKeyUsageCritical + */ + public boolean isExtKeyUsageCritical() { + return options.getBoolean(Options.EXT_KEY_USAGE_CRITICAL, true); + } + + /** + * isBasicConstraintsCa. + * + * @return isBasicConstraintsCa + */ + public boolean isBasicConstraintsCa() { + return options.getBoolean(Options.BASIC_CONSTRAINTS_CA, false); + } + + /** + * isBasicConstraintsCritical + * + * @return isBasicConstraintsCritical + */ + public boolean isBasicConstraintsCritical() { + return options.getBoolean(Options.BASIC_CONSTRAINTS_CRITICAL, false); + } + + /** + * getBasicConstraintsPathLen. + * + * @return BasicConstraintsPathLen + */ + public int getBasicConstraintsPathLen() { + return options.getInt(Options.BASIC_CONSTRAINTS_PATH_LEN); + } + + /** + * getExtKeyUsage. + * + * @return KeyPurposeId[] of ExtKeyUsage + */ + public KeyPurposeId[] getExtKeyUsage() { + return CertUtils.parseExtKeyUsage(options.getString(Options.EXT_KEY_USAGE)); + } + + /** + * getKeyUsage. + * + * @return KeyUsage + */ + public KeyUsage getKeyUsage() { + return new KeyUsage(CertUtils.parseKeyUsage(options.getString(Options.KEY_USAGE))); + } + + /** + * getSubject. + * + * @return Subject + */ + public X500Name getSubject() { + String subject = options.getString(Options.SUBJECT); + return CertUtils.buildDN(subject); + } + + /** + * getIssuer. + * @return Issuer + */ + public X500Name getIssuer() { + String issuer = options.getString(Options.ISSUER, options.getString(Options.SUBJECT)); + return CertUtils.buildDN(issuer); + } + + /** + * getOutFile. + * + * @return OutFile + */ + public String getOutFile() { + return options.getString(Options.OUT_FILE); + } + + /** + * getInFile. + * + * @return InFile + */ + public String getInFile() { + String file = options.getString(Options.IN_FILE); + ValidateUtils.throwIfNotMatches(new File(file).exists(), ERROR.FILE_NOT_FOUND, + String.format("Required %s: '%s' not exist", Options.IN_FILE, file)); + return file; + } + + /** + * isRemoteSigner. + * + * @return isRemoteSigner + */ + public boolean isRemoteSigner() { + String mode = options.getString(Options.MODE, "localSign"); + return "remoteSign".equalsIgnoreCase(mode); + } + + /** + * Reset pwd to keep security + */ + public void releasePwd() { + resetChars(options.getChars(Options.KEY_STORE_RIGHTS)); + resetChars(options.getChars(Options.KEY_RIGHTS)); + resetChars(options.getChars(Options.ISSUER_KEY_RIGHTS)); + } + + private void resetChars(char[] chars) { + if (chars == null) { + return; + } + Arrays.fill(chars, (char) 0); + } +} diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/api/ServiceApi.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/api/ServiceApi.java new file mode 100644 index 0000000000000000000000000000000000000000..61402ef6806e48edddd08bf26f929fd354872fd2 --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/api/ServiceApi.java @@ -0,0 +1,107 @@ +/* + * Copyright (c) 2021-2022 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.api; + +import com.ohos.hapsigntool.api.model.Options; + +/** + * Public api of core lib to deal with signature. + * + * @since 2021/12/28 + */ +public interface ServiceApi { + + /** + * Generate keyStore. + * + * @param options options + * @return Generate or not + */ + boolean generateKeyStore(Options options); + + /** + * Generate csr. + * + * @param options options + * @return Generate or not + */ + boolean generateCsr(Options options); + + /** + * Generate cert. + * + * @param options options + * @return Generate or not + */ + boolean generateCert(Options options); + + /** + * Generate CA. + * + * @param options options + * @return Generate or not + */ + boolean generateCA(Options options); + + /** + * Generate app cert. + * + * @param options options + * @return Generate or not + */ + boolean generateAppCert(Options options); + + /** + * Generate profile cert. + * + * @param options options + * @return Generate or not + */ + boolean generateProfileCert(Options options); + + /** + * Sign for profile. + * + * @param options options + * @return Sign or not + */ + boolean signProfile(Options options); + + /** + * Verify profile. + * + * @param options options + * @return Verify or not + */ + boolean verifyProfile(Options options); + + /** + * Sign for hap. + * + * @param options options + * @return Sign or not + */ + boolean signHap(Options options); + + /** + * Verify hap. + * + * @param options options + * @return Verify or not + */ + boolean verifyHap(Options options); + +} 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 new file mode 100644 index 0000000000000000000000000000000000000000..191c401e11ea5683008b6f9bcf1c092dc500c14d --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/api/SignToolServiceImpl.java @@ -0,0 +1,346 @@ +/* + * Copyright (c) 2021-2022 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.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.hap.provider.LocalJKSSignProvider; +import com.ohos.hapsigntool.hap.provider.RemoteSignProvider; +import com.ohos.hapsigntool.hap.provider.SignProvider; +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.StringUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.bouncycastle.jce.provider.BouncyCastleProvider; + +import java.io.File; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.security.KeyPair; +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; + +/** + * Main entry of lib. + * + * @since 2021/12/28 + */ +public class SignToolServiceImpl implements ServiceApi { + + static { + Security.addProvider(new BouncyCastleProvider()); + } + + /** + * Logger. + */ + private final Logger logger = LogManager.getLogger(ServiceApi.class); + + /** + * map of input params for signing hap. + */ + protected Map inputParams = new HashMap(); + + /** + * Generate keyStore. + * + * @param options options + * @return Generate or not + */ + @Override + public boolean generateKeyStore(Options options) { + LocalizationAdapter adapter = new LocalizationAdapter(options); + adapter.errorOnExist(options.getString(Options.KEY_ALIAS)); + KeyPair keyPair = adapter.getAliasKey(true); + adapter.releasePwd(); + return keyPair != null; + } + + /** + * Generate csr. + * + * @param options options + * @return Generate or not + */ + @Override + public boolean generateCsr(Options options) { + LocalizationAdapter adapter = new LocalizationAdapter(options); + KeyPair keyPair = adapter.getAliasKey(false); + adapter.releasePwd(); + byte[] csr = CertTools.generateCsr(keyPair, adapter.getSignAlg(), adapter.getSubject()); + if (csr == null) { + return false; + } + String csrContent = CertUtils.toCsrTemplate(csr); + outputString(csrContent, adapter.getOutFile()); + return true; + } + + /** + * Generate cert. + * + * @param options options + * @return Generate or not + */ + @Override + public boolean generateCert(Options options) { + LocalizationAdapter adapter = new LocalizationAdapter(options); + adapter.errorIfNotExist(adapter.getOptions().getString(Options.KEY_ALIAS)); + String issuerAlias = adapter.getOptions().getString(Options.ISSUER_KEY_ALIAS); + adapter.errorIfNotExist(issuerAlias); + + KeyPair subjectKeyPair = adapter.getAliasKey(false); + KeyPair rootKeyPair = adapter.getIssuerAliasKey(); + adapter.releasePwd(); + byte[] csr = CertTools.generateCsr(subjectKeyPair, adapter.getSignAlg(), adapter.getSubject()); + + X509Certificate cert = CertTools.generateCert(rootKeyPair, csr, adapter); + return outputCert(cert, adapter.getOutFile()); + + } + + /** + * Generate CA. + * + * @param options options + * @return Generate or not + */ + @Override + public boolean generateCA(Options options) { + LocalizationAdapter adapter = new LocalizationAdapter(options); + boolean genRootCA = StringUtils.isEmpty(options.getString(Options.ISSUER_KEY_ALIAS)); + KeyPair subKey = adapter.getAliasKey(true); + KeyPair rootKey; + if (genRootCA) { + rootKey = subKey; + } else { + rootKey = adapter.getIssuerAliasKey(); + } + adapter.releasePwd(); + + byte[] csr = CertTools.generateCsr(subKey, adapter.getSignAlg(), adapter.getSubject()); + X509Certificate cert; + if (genRootCA) { + cert = CertTools.generateRootCaCert(rootKey, csr, adapter); + } else { + cert = CertTools.generateSubCert(rootKey, csr, adapter); + } + return outputCert(cert, adapter.getOutFile()); + } + + /** + * Generate app cert. + * + * @param options options + * @return Generate or not + */ + @Override + public boolean generateAppCert(Options options) { + return generateProfileCert(options); + } + + /** + * Generate profile cert. + * + * @param options options + * @return Generate or not + */ + @Override + public boolean generateProfileCert(Options options) { + LocalizationAdapter adapter = new LocalizationAdapter(options); + KeyPair keyPair = adapter.getAliasKey(false); + KeyPair issueKeyPair = adapter.getIssuerAliasKey(); + adapter.releasePwd(); + + byte[] csr = CertTools.generateCsr(keyPair, adapter.getSignAlg(), adapter.getSubject()); + X509Certificate cert = CertTools.generateEndCert(issueKeyPair, csr, adapter); + 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()); + } + } + + /** + * Sign for profile. + * + * @param options options + * @return Sign or not + */ + @Override + public boolean signProfile(Options options) { + boolean result; + try { + LocalizationAdapter adapter = new LocalizationAdapter(options); + byte[] provisionContent = ProfileSignTool.getProvisionContent(new File(adapter.getInFile())); + byte[] p7b = ProfileSignTool.generateP7b(adapter, provisionContent); + FileUtils.write(p7b, new File(adapter.getOutFile())); + result = true; + } catch (IOException exception) { + logger.debug(exception.getMessage(), exception); + logger.error(exception.getMessage()); + result = false; + } + return result; + } + + /** + * Verify profile. + * + * @param options options + * @return Verify or not + */ + @Override + public boolean verifyProfile(Options options) { + boolean result; + 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) { + 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; + } + return result; + } + + /** + * Sign for hap. + * + * @param options options + * @return Sign or not + */ + @Override + public boolean signHap(Options options) { + String mode = options.getString(Options.MODE); + //sign online or locally + SignProvider signProvider; + if ("localSign".equalsIgnoreCase(mode)) { + signProvider = new LocalJKSSignProvider(); + } else if ("remoteSign".equalsIgnoreCase(mode)){ + signProvider = new RemoteSignProvider(); + } else { + logger.info("Resign mode. But not implement yet"); + return false; + } + + //The type of file is bin or hap + String inFile = options.getString(Options.IN_FILE); + if (inFile.endsWith(".bin")) { + return signProvider.signBin(options); + } else { + return signProvider.sign(options); + } + } + + @Override + public boolean verifyHap(Options options) { + VerifyHap hapVerify = new VerifyHap(); + return hapVerify.verify(options); + } + + /** + * Output string, save into file or print. + * + * @param content String Content + * @param file file + */ + public void outputString(String content, String file) { + if (StringUtils.isEmpty(file)) { + logger.info(content); + } else { + try { + FileUtils.write(content.getBytes(StandardCharsets.UTF_8), new File(file)); + } catch (IOException exception) { + logger.debug(exception.getMessage(), exception); + CustomException.throwException(ERROR.WRITE_FILE_ERROR, exception.getMessage()); + } + } + } + + /** + * Output certificate. + * + * @param certificate certificate + * @param file file + * @return Whether to output certificate + */ + public boolean outputCert(X509Certificate certificate, String file) { + try { + if (StringUtils.isEmpty(file)) { + logger.info(CertUtils.generateCertificateInCer(certificate)); + } else { + FileUtils.write(CertUtils.generateCertificateInCer(certificate).getBytes(StandardCharsets.UTF_8), + new File(file)); + } + return true; + } catch (CertificateException | IOException exception) { + logger.debug(exception.getMessage(), exception); + CustomException.throwException(ERROR.WRITE_FILE_ERROR, exception.getMessage()); + return false; + } + } + + /** + * Output certificate in certificates list. + * + * @param certificates certificates + * @param file file + * @return Whether to output certificate in certificates list + */ + public boolean outputCertChain(List certificates, String file) { + try { + StringBuilder stringBuilder = new StringBuilder(); + for (X509Certificate cert : certificates) { + stringBuilder.append(CertUtils.generateCertificateInCer(cert)); + } + if (StringUtils.isEmpty(file)) { + logger.info(stringBuilder.toString()); + } else { + FileUtils.write(stringBuilder.toString().getBytes(StandardCharsets.UTF_8), new File(file)); + } + return true; + } catch (CertificateException | IOException exception) { + logger.debug(exception.getMessage(), exception); + CustomException.throwException(ERROR.WRITE_FILE_ERROR, exception.getMessage()); + return false; + } + } +} diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/api/model/Options.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/api/model/Options.java new file mode 100644 index 0000000000000000000000000000000000000000..42bbe70ceccaf7eef54d6da382ac5e9448c5a94f --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/api/model/Options.java @@ -0,0 +1,321 @@ +/* + * Copyright (c) 2021-2022 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.api.model; + +import com.ohos.hapsigntool.api.ServiceApi; +import com.ohos.hapsigntool.error.CustomException; +import com.ohos.hapsigntool.error.ERROR; +import com.ohos.hapsigntool.utils.StringUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.util.HashMap; + +/** + * Options parameter class. + * + * @since 2021/12/28 + */ +public class Options extends HashMap { + /** + * Serial version UID. + */ + private static final long serialVersionUID = 1L; + /** + * Empty char. + */ + private static final char[] NO_CHAR = {}; + /** + * Logger. + */ + private final Logger logger = LogManager.getLogger(ServiceApi.class); + /** + * App cert file parameter name. + */ + public static final String APP_CERT_FILE = "appCertFile"; + /** + * BasicConstraints parameter name. + */ + public static final String BASIC_CONSTRAINTS = "basicConstraints"; + /** + * BasicConstraintsCa parameter name. + */ + public static final String BASIC_CONSTRAINTS_CA = "basicConstraintsCa"; + /** + * BasicConstraintsCritical parameter name. + */ + public static final String BASIC_CONSTRAINTS_CRITICAL = "basicConstraintsCritical"; + /** + * BasicConstraintsPathLen parameter name. + */ + public static final String BASIC_CONSTRAINTS_PATH_LEN = "basicConstraintsPathLen"; + /** + * End file type of cert. values in: cert / certChain + */ + public static final String OUT_FORM = "outForm"; + /** + * Ca cert file parameter name. + */ + public static final String CA_CERT_FILE = "rootCaCertFile"; + /** + * Sub cert for sign + */ + public static final String SUB_CA_CERT_FILE = "subCaCertFile"; + + /** + * Ext cfg file parameter name. + */ + public static final String EXT_CFG_FILE = "extCfgFile"; + /** + * Ext key usage parameter name. + */ + public static final String EXT_KEY_USAGE = "extKeyUsage"; + /** + * Ext key usage critical parameter name. + */ + public static final String EXT_KEY_USAGE_CRITICAL = "extKeyUsageCritical"; + /** + * In file parameter name. + */ + public static final String IN_FILE = "inFile"; + /** + * Issuer parameter name. + */ + public static final String ISSUER = "issuer"; + /** + * Issuer key alias parameter name. + */ + public static final String ISSUER_KEY_ALIAS = "issuerKeyAlias"; + /** + * Issuer key right parameter name. + */ + public static final String ISSUER_KEY_RIGHTS = "issuerKeyPwd"; + /** + * Key alg parameter name. + */ + public static final String KEY_ALG = "keyAlg"; + /** + * Key alias parameter name. + */ + public static final String KEY_ALIAS = "keyAlias"; + /** + * Key right parameter name. + */ + public static final String KEY_RIGHTS = "keyPwd"; + /** + * Key size parameter name. + */ + public static final String KEY_SIZE = "keySize"; + /** + * Keystore file parameter name. + */ + public static final String KEY_STORE_FILE = "keystoreFile"; + /** + * Keystore right parameter name. + */ + public static final String KEY_STORE_RIGHTS = "keystorePwd"; + /** + * Key usage parameter name. + */ + public static final String KEY_USAGE = "keyUsage"; + /** + * Key usage critical parameter name. + */ + public static final String KEY_USAGE_CRITICAL = "keyUsageCritical"; + /** + * Mode parameter name. + */ + public static final String MODE = "mode"; + /** + * Out file parameter name. + */ + public static final String OUT_FILE = "outFile"; + + /** + * Out file parameter name. + */ + public static final String OUT_CERT_CHAIN = "outCertChain"; + + /** + * Out file parameter name. + */ + public static final String OUT_PROFILE = "outProfile"; + + /** + * Profile cert file parameter name. + */ + public static final String PROFILE_CERT_FILE = "profileCertFile"; + /** + * Profile file parameter name. + */ + public static final String PROFILE_FILE = "profileFile"; + /** + * Sign alg parameter name. + */ + public static final String SIGN_ALG = "signAlg"; + /** + * Subject parameter name. + */ + public static final String SUBJECT = "subject"; + /** + * Trusted app source file parameter name. + */ + public static final String TRUSTED_APP_SOURCE_FILE = "trustedAppSourceFile"; + /** + * Trusted root ca file parameter name. + */ + public static final String TRUSTED_ROOT_CA_FILE = "trustedRootCaFile"; + /** + * Validity parameter name. + */ + public static final String VALIDITY = "validity"; + + /** + * All usages included in the extended key usage. + */ + public static final String EXT_KEY_USAGE_SCOPE = "clientAuthentication,serverAuthentication,codeSignature," + + "emailProtection,smartCardLogin,timestamp,ocspSignature"; + /** + * Key usage includes all usages. + */ + public static final String KEY_USAGE_SCOPE = "digitalSignature,nonRepudiation,keyEncipherment,dataEncipherment," + + "keyAgreement,certificateSignature,crlSignature,encipherOnly,decipherOnly"; + + /** + * Out form includes all forms. + */ + public static final String OUT_FORM_SCOPE = "cert,certChain"; + + /** + * Check required key, throw exception + * + * @param keys keys to check + */ + public void required(String... keys) { + for (String key : keys) { + if (!StringUtils.isEmpty(key) && !this.containsKey(key)) { + CustomException.throwException(ERROR.COMMAND_ERROR, String.format("Params '%s' is required", key)); + } + } + } + + /** + * Get char value of key. + * + * @param key key + * @return value of key + */ + public char[] getChars(String key) { + Object value = this.get(key); + if (value instanceof char[]) { + return (char[]) value; + } + return NO_CHAR; + } + + /** + * Get string value of key. + * + * @param key key + * @param defValue defValue + * @return string value of key + */ + public String getString(String key, String defValue) { + Object value = this.get(key); + if (!(value instanceof String)) { + return defValue; + } + return (String) value; + } + + /** + * Get string value of key or def value. + * + * @param key key + * @return string value of key + */ + public String getString(String key) { + return getString(key, ""); + } + + /** + * Get boolean value of key. + * + * @param key key + * @param defValue defValue + * @return boolean value of key + */ + public boolean getBoolean(String key, boolean defValue) { + Object value = this.get(key); + boolean result = defValue; + if (value instanceof Boolean) { + result = (boolean) value; + } + if (value instanceof String) { + if ("true".equalsIgnoreCase((String) value)) { + result = true; + } + if ("false".equalsIgnoreCase((String) value)) { + result = false; + } + } + return result; + } + + /** + * Get boolean value of key or def value. + * + * @param key key + * @return boolean value of key + */ + public boolean getBoolean(String key) { + return getBoolean(key, false); + } + + /** + * Get int value of key. + * + * @param key key + * @param defValue defValue + * @return Value of key + */ + public int getInt(String key, int defValue) { + Object value = this.get(key); + if (value instanceof Integer) { + return (int) value; + } + if (value instanceof String) { + try { + defValue = Integer.parseInt((String) value); + } catch (NumberFormatException exception) { + logger.debug(exception.getMessage(), exception); + return defValue; + } + } + return defValue; + } + + /** + * Get int value of key or def value. + * + * @param key key + * @return Value of key + */ + public int getInt(String key) { + return getInt(key, 0); + } +} + diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/cert/CertBuilder.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/cert/CertBuilder.java new file mode 100644 index 0000000000000000000000000000000000000000..2a67c8850296d12ed6feeb5af7b253a755b4269d --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/cert/CertBuilder.java @@ -0,0 +1,202 @@ +/* + * Copyright (c) 2021-2022 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.cert; + +import com.ohos.hapsigntool.api.ServiceApi; +import com.ohos.hapsigntool.error.CustomException; +import com.ohos.hapsigntool.error.ERROR; +import com.ohos.hapsigntool.utils.CertUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.bouncycastle.asn1.x500.X500Name; +import org.bouncycastle.asn1.x509.BasicConstraints; +import org.bouncycastle.asn1.x509.ExtendedKeyUsage; +import org.bouncycastle.asn1.x509.Extension; +import org.bouncycastle.asn1.x509.KeyPurposeId; +import org.bouncycastle.asn1.x509.KeyUsage; +import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo; +import org.bouncycastle.cert.CertIOException; +import org.bouncycastle.cert.X509v3CertificateBuilder; +import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter; +import org.bouncycastle.cert.jcajce.JcaX509ExtensionUtils; +import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.bouncycastle.operator.ContentSigner; +import org.bouncycastle.pkcs.PKCS10CertificationRequest; + +import java.io.IOException; +import java.security.InvalidKeyException; +import java.security.KeyPair; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +import java.security.SignatureException; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.util.Date; + +/** + * Builder pattern to build certification. + * + * @since 2021/12/28 + */ +public class CertBuilder { + /** + * Logger. + */ + private final Logger logger = LogManager.getLogger(ServiceApi.class); + /** + * issuer keyPair. + */ + private final KeyPair keyPair; + /** + * CertificateBuilder. + */ + private final X509v3CertificateBuilder x509v3CertificateBuilder; + + /** + * CertBuilder. + * + * @param keyPair keyPair + * @param issuer issuer + * @param csr csr + * @param certExpire certExpire + */ + public CertBuilder(KeyPair keyPair, X500Name issuer, byte[] csr, long certExpire) { + this.keyPair = keyPair; + LocalDateTime notBefore = LocalDateTime.now(); + LocalDateTime notAfter = notBefore.plusDays(certExpire); + + PKCS10CertificationRequest request = null; + try { + request = new PKCS10CertificationRequest(csr); + } catch (IOException exception) { + logger.debug(exception.getMessage(), exception); + CustomException.throwException(ERROR.NOT_SUPPORT_ERROR, exception.getMessage()); + } + x509v3CertificateBuilder = new X509v3CertificateBuilder( + issuer, CertUtils.randomSerial(), Date.from(notBefore.atZone(ZoneId.systemDefault()).toInstant()), + Date.from(notAfter.atZone(ZoneId.systemDefault()).toInstant()), + request.getSubject(), request.getSubjectPublicKeyInfo()); + try { + JcaX509ExtensionUtils extUtils = new JcaX509ExtensionUtils(); + x509v3CertificateBuilder.addExtension(Extension.subjectKeyIdentifier, false, + extUtils.createSubjectKeyIdentifier(request.getSubjectPublicKeyInfo())); + } catch (NoSuchAlgorithmException | CertIOException exception) { + logger.debug(exception.getMessage(), exception); + CustomException.throwException(ERROR.NOT_SUPPORT_ERROR, exception.getMessage()); + } + } + + /** + * Add authorityKeyIdentifier for certificate builder. + * + * @param certLevel certLevel + * @return CertBuilder + */ + public CertBuilder withAuthorityKeyIdentifier(CertLevel certLevel) { + try { + JcaX509ExtensionUtils extUtils = new JcaX509ExtensionUtils(); + if (certLevel == CertLevel.SUB_CA) { + x509v3CertificateBuilder.addExtension(Extension.authorityKeyIdentifier, false, + extUtils.createAuthorityKeyIdentifier(SubjectPublicKeyInfo + .getInstance(keyPair.getPublic().getEncoded()))); + } + } catch (NoSuchAlgorithmException | CertIOException exception) { + logger.debug(exception.getMessage(), exception); + CustomException.throwException(ERROR.NOT_SUPPORT_ERROR, exception.getMessage()); + } + return this; + } + + /** + * Add basicConstraints for certificate builder. + * + * @param certLevel certLevel + * @param basicConstraintsCritical basicConstraintsCritical + * @param basicConstraintsCa basicConstraintsCa + * @param basicConstraintsPathLen basicConstraintsPathLen + * @return CertBuilder + * @throws CertIOException CertIOException + */ + public CertBuilder withBasicConstraints(CertLevel certLevel, boolean basicConstraintsCritical, + boolean basicConstraintsCa, Integer basicConstraintsPathLen) + throws CertIOException { + BasicConstraints basicConstraints; + if (certLevel == CertLevel.END_ENTITY) { + basicConstraints = new BasicConstraints(basicConstraintsCritical); + } else { + if (basicConstraintsPathLen == null) { + basicConstraints = new BasicConstraints(basicConstraintsCa); + } else { + basicConstraints = new BasicConstraints(basicConstraintsPathLen); + } + } + x509v3CertificateBuilder.addExtension(Extension.basicConstraints, basicConstraintsCritical, basicConstraints); + return this; + } + + /** + * Add keyUsages for certificate builder. + * + * @param keyUsage keyUsage + * @param keyUsageCritical keyUsageCritical + * @return CertBuilder + * @throws CertIOException CertIOException + */ + public CertBuilder withKeyUsages(KeyUsage keyUsage, boolean keyUsageCritical) throws CertIOException { + x509v3CertificateBuilder.addExtension(Extension.keyUsage, keyUsageCritical, keyUsage); + return this; + } + + /** + * Add extKeyUsages for certificate builder. + * + * @param extKeyUsages extKeyUsages + * @param extKeyUsageCritical extKeyUsageCritical + * @return CertBuilder + * @throws CertIOException CertIOException + */ + public CertBuilder withExtKeyUsages(KeyPurposeId[] extKeyUsages, boolean extKeyUsageCritical) + throws CertIOException { + if (extKeyUsages != null) { + ExtendedKeyUsage extendedKeyUsage = new ExtendedKeyUsage(extKeyUsages); + x509v3CertificateBuilder.addExtension(Extension.extendedKeyUsage, extKeyUsageCritical, extendedKeyUsage); + } + return this; + } + + /** + * build X509Certificate. + * + * @param signAlgorithm signAlgorithm to sign + * @return X509Certificate + */ + public X509Certificate build(String signAlgorithm) { + ContentSigner contentSigner = CertTools.createFixedContentSigner(keyPair.getPrivate(), signAlgorithm); + X509Certificate cert = null; + try { + cert = new JcaX509CertificateConverter().setProvider(BouncyCastleProvider.PROVIDER_NAME) + .getCertificate(x509v3CertificateBuilder.build(contentSigner)); + cert.verify(keyPair.getPublic()); + } catch (CertificateException | NoSuchAlgorithmException | SignatureException + | InvalidKeyException | NoSuchProviderException exception) { + logger.debug(exception.getMessage(), exception); + CustomException.throwException(ERROR.NOT_SUPPORT_ERROR, exception.getMessage()); + } + return cert; + } +} diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/cert/CertLevel.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/cert/CertLevel.java new file mode 100644 index 0000000000000000000000000000000000000000..3cba8560de00121a00ffc7bb58e116c51caabcd9 --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/cert/CertLevel.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2021-2022 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.cert; + +/** + * Define cert level: root, sub, end. + * + * @since 2021/12/28 + */ +public enum CertLevel { + /** + * RootCA. + */ + ROOT_CA, + /** + * SubCA. + */ + SUB_CA, + /** + * EndEntity. + */ + END_ENTITY +} diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/cert/CertTools.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/cert/CertTools.java new file mode 100644 index 0000000000000000000000000000000000000000..eb38088e894184d14fcec9b7cecbefa77e08e734 --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/cert/CertTools.java @@ -0,0 +1,233 @@ +/* + * Copyright (c) 2021-2022 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.cert; + +import com.ohos.hapsigntool.api.LocalizationAdapter; +import com.ohos.hapsigntool.api.ServiceApi; +import com.ohos.hapsigntool.api.model.Options; +import com.ohos.hapsigntool.error.CustomException; +import com.ohos.hapsigntool.error.ERROR; +import com.ohos.hapsigntool.utils.ValidateUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.bouncycastle.asn1.x500.X500Name; +import org.bouncycastle.asn1.x509.KeyPurposeId; +import org.bouncycastle.asn1.x509.KeyUsage; +import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.bouncycastle.operator.ContentSigner; +import org.bouncycastle.operator.OperatorCreationException; +import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder; +import org.bouncycastle.pkcs.PKCS10CertificationRequest; +import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequestBuilder; + +import java.io.IOException; +import java.security.KeyPair; +import java.security.PrivateKey; +import java.security.cert.X509Certificate; +import java.security.interfaces.ECPrivateKey; +import java.security.interfaces.RSAPrivateKey; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * CertTools. + * + * @since 2021/12/28 + */ +public final class CertTools { + /** + * Ten years days. + */ + private static final int TEN_YEAR_DAY = 3650; + /** + * Three years dats. + */ + private static final int THREE_YEAR_DAY = 1095; + /** + * Empty csr array. + */ + private static final byte[] NO_CSR = {}; + /** + * ECC. + */ + private static final String ECC = "ECDSA"; + /** + * Compile String. + */ + private static final Pattern SIGN_ALGORITHM_PATTERN = Pattern.compile("^SHA([0-9]{3})with([A-Z]{1,5})$"); + /** + * Logger. + */ + private static final Logger LOGGER = LogManager.getLogger(ServiceApi.class); + + private CertTools() { + } + + /** + * Generate root ca certificate. + * + * @param keyPair keyPair + * @param csr csr + * @param adapter adapter + * @return X509Certificate + */ + public static X509Certificate generateRootCaCert(KeyPair keyPair, byte[] csr, LocalizationAdapter adapter) { + try { + return new CertBuilder(keyPair, adapter.getIssuer(), csr, + adapter.getOptions().getInt(Options.VALIDITY, TEN_YEAR_DAY)) + .withAuthorityKeyIdentifier(CertLevel.ROOT_CA) + .withBasicConstraints(CertLevel.ROOT_CA, true, true, + adapter.getBasicConstraintsPathLen()) + .withKeyUsages(new KeyUsage(KeyUsage.keyCertSign | KeyUsage.cRLSign), true) + .withExtKeyUsages(null, false) + .build(adapter.getSignAlg()); + } catch (IOException exception) { + LOGGER.debug(exception.getMessage(), exception); + CustomException.throwException(ERROR.NOT_SUPPORT_ERROR, exception.getMessage()); + } + return null; + } + + /** + * Generate sub ca certificate. + * + * @param keyPair keyPair + * @param csr csr + * @param adapter parameter + * @return X509Certificate + */ + public static X509Certificate generateSubCert(KeyPair keyPair, byte[] csr, LocalizationAdapter adapter) { + try { + return new CertBuilder(keyPair, adapter.getIssuer(), csr, + adapter.getOptions().getInt(Options.VALIDITY, TEN_YEAR_DAY)) + .withAuthorityKeyIdentifier(CertLevel.SUB_CA) + .withBasicConstraints(CertLevel.SUB_CA, true, true, + adapter.getBasicConstraintsPathLen()) + .withKeyUsages(new KeyUsage(KeyUsage.keyCertSign | KeyUsage.cRLSign), true) + .build(adapter.getSignAlg()); + } catch (IOException exception) { + LOGGER.debug(exception.getMessage(), exception); + CustomException.throwException(ERROR.NOT_SUPPORT_ERROR, exception.getMessage()); + } + return null; + } + + /** + * Generate certificate. + * + * @param keyPair keyPair + * @param csr csr + * @param adapter parameter + * @return X509Certificate + */ + public static X509Certificate generateCert(KeyPair keyPair, byte[] csr, LocalizationAdapter adapter) { + try { + return new CertBuilder(keyPair, adapter.getIssuer(), csr, + adapter.getOptions().getInt(Options.VALIDITY, THREE_YEAR_DAY)) + //Need CertLevel + .withAuthorityKeyIdentifier(CertLevel.ROOT_CA) + .withBasicConstraints(CertLevel.ROOT_CA, + adapter.isBasicConstraintsCritical(), + adapter.isBasicConstraintsCa(), + adapter.getBasicConstraintsPathLen()) + .withKeyUsages(adapter.getKeyUsage(), adapter.isKeyUsageCritical()) + .withExtKeyUsages(adapter.getExtKeyUsage(), adapter.isExtKeyUsageCritical()) + .build(adapter.getSignAlg()); + } catch (IOException exception) { + LOGGER.debug(exception.getMessage(), exception); + CustomException.throwException(ERROR.NOT_SUPPORT_ERROR, exception.getMessage()); + } + return null; + } + + /** + * Generate app certificate. + * + * @param keyPair keyPair + * @param csr csr + * @param adapter adapter + * @return X509Certificate + */ + public static X509Certificate generateEndCert(KeyPair keyPair, byte[] csr, LocalizationAdapter adapter) { + try { + return new CertBuilder(keyPair, adapter.getIssuer(), csr, + adapter.getOptions().getInt(Options.VALIDITY, THREE_YEAR_DAY)) + .withBasicConstraints(CertLevel.END_ENTITY, false, false, + null) + .withKeyUsages(new KeyUsage(KeyUsage.digitalSignature), true) + .withExtKeyUsages(new KeyPurposeId[]{KeyPurposeId.id_kp_codeSigning}, false) + .build(adapter.getSignAlg()); + } catch (IOException exception) { + LOGGER.debug(exception.getMessage(), exception); + CustomException.throwException(ERROR.NOT_SUPPORT_ERROR, exception.getMessage()); + } + return null; + } + + /** + * generateCsr. + * + * @param keyPair Applier keypair + * @param signAlgorithm sign algorithm + * @param subject Applier subject + * @return csr bytes + */ + public static byte[] generateCsr(KeyPair keyPair, String signAlgorithm, X500Name subject) { + JcaPKCS10CertificationRequestBuilder csrBuilder = new JcaPKCS10CertificationRequestBuilder(subject, + keyPair.getPublic()); + PKCS10CertificationRequest csr = csrBuilder.build(createFixedContentSigner(keyPair.getPrivate(), + signAlgorithm)); + try { + return csr.getEncoded(); + } catch (IOException exception) { + LOGGER.debug(exception.getMessage(), exception); + CustomException.throwException(ERROR.NOT_SUPPORT_ERROR, "Not support " + subject); + return NO_CSR; + } + } + + /** + * Auto fix algorithm according key type and create content signer. + * + * @param privateKey Sign key + * @param signAlgorithm Sign algorithm + * @return ContentSigner + */ + public static ContentSigner createFixedContentSigner(PrivateKey privateKey, String signAlgorithm) { + Matcher matcher = SIGN_ALGORITHM_PATTERN.matcher(signAlgorithm); + ValidateUtils.throwIfNotMatches(matcher.matches(), ERROR.NOT_SUPPORT_ERROR, "Not Support " + signAlgorithm); + // Auto fix signAlgorithm error + if (privateKey instanceof ECPrivateKey && signAlgorithm.contains("RSA")) { + signAlgorithm = signAlgorithm.replace("RSA", ECC); + } else { + if (privateKey instanceof RSAPrivateKey && signAlgorithm.contains(ECC)) { + signAlgorithm = signAlgorithm.replace(ECC, "RSA"); + } + } + + JcaContentSignerBuilder jcaContentSignerBuilder = new JcaContentSignerBuilder(signAlgorithm); + jcaContentSignerBuilder.setProvider(BouncyCastleProvider.PROVIDER_NAME); + try { + return jcaContentSignerBuilder.build(privateKey); + } catch (OperatorCreationException exception) { + LOGGER.debug(exception.getMessage(), exception); + CustomException.throwException(ERROR.NOT_SUPPORT_ERROR, "Not support " + signAlgorithm); + } + return null; + } + + +} diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/error/CustomException.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/error/CustomException.java new file mode 100644 index 0000000000000000000000000000000000000000..f928e9ef2aa7cea78f66b6a15501d3ffdc0b7bf3 --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/error/CustomException.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2021-2022 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; + +/** + * Runtime exception for programs. + * + * @since 2021/12/28 + */ +public class CustomException extends RuntimeException { + + /** + * Create custom exception with params. + * + * @param error Error enum to throw + * @param message Error msg to throw + */ + CustomException(ERROR error, String message) { + super(String.format("%s, code: %d. Details: %s", error.toString(), error.getErrorCode(), message)); + } + + /** + * Throw custom exception with params. + * + * @param error Error enum to throw + * @param message Error msg to throw + */ + public static void throwException(ERROR error, String message) { + throw new CustomException(error, message); + } + +} 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 new file mode 100644 index 0000000000000000000000000000000000000000..005bcffc76e6a30e4f3397b15d4f9a3831b0ff54 --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/error/ERROR.java @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2021-2022 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; + +/** + * Errors. + * + * @since 2021/12/28 + */ +public enum ERROR { + /** + * Enum constant COMMAND_ERROR. + */ + COMMAND_ERROR(101), + /** + * Enum constant FILE_NOT_FOUND. + */ + FILE_NOT_FOUND(102), + /** + * Enum constant WRITE_FILE_ERROR. + */ + WRITE_FILE_ERROR(103), + /** + * Enum constant READ_FILE_ERROR. + */ + READ_FILE_ERROR(104), + /** + * Enum constant NOT_SUPPORT_ERROR. + */ + NOT_SUPPORT_ERROR(105), + /** + * Enum constant NETWORK_ERROR. + */ + NETWORK_ERROR(106), + /** + * Enum constant SIGN_ERROR. + */ + SIGN_ERROR(107), + /** + * Enum constant VERIFY_ERROR. + */ + VERIFY_ERROR(108), + /** + * Enum constant ACCESS_ERROR. + */ + ACCESS_ERROR(109); + + /** + * Field errorCode. + */ + private final int errorCode; + + /** + * getErrorCode. + * + * @return Integer code + */ + public int getErrorCode() { + return this.errorCode; + } + + ERROR(int code) { + this.errorCode = code; + } + +} diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/config/SignerConfig.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/config/SignerConfig.java new file mode 100644 index 0000000000000000000000000000000000000000..9be334efb0cbf28d704360a3d36718efad409612 --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/config/SignerConfig.java @@ -0,0 +1,147 @@ +/* + * Copyright (c) 2021 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.config; + +import com.ohos.hapsigntool.api.LocalizationAdapter; +import com.ohos.hapsigntool.api.model.Options; +import com.ohos.hapsigntool.hap.sign.SignatureAlgorithm; +import com.ohos.hapsigntool.signer.ISigner; +import com.ohos.hapsigntool.signer.SignerFactory; + +import java.security.cert.X509CRL; +import java.security.cert.X509Certificate; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Sign config super class + * + * @since 2021-12-13 + */ +public class SignerConfig { + /** + * params inputted by users + */ + private Options options; + + /** + * certificate chain used for sign hap + */ + private List certificates; + + /** + * certificate revocation list return from server + */ + private List x509CRLs; + + /** + * Signature Algorithms used for sign hap + */ + private List signatureAlgorithms; + + /** + * parameters for sign hap + */ + private Map signParamMap = new HashMap(); + + /** + * Get options. + * + * @return options + */ + public Options getOptions() { + return options; + } + + /** + * set options. + */ + public void setOptions(Options options) { + this.options = options; + } + + /** + * Get certificates. + * + * @return certificates + */ + public List getCertificates() { + return certificates; + } + + /** + * set certificate + */ + public void setCertificates(List certificates) { + this.certificates = certificates; + } + + /** + * get crl + * + * @return crl list + */ + public List getX509CRLs() { + return x509CRLs; + } + + /** + * set crl + */ + public void setX509CRLs(List crls) { + this.x509CRLs = crls; + } + + /** + * get signature algorithm + * + * @return signature algorithm + */ + public List getSignatureAlgorithms() { + return signatureAlgorithms; + } + + /** + * set signature algorithm + */ + public void setSignatureAlgorithms(List signatureAlgorithms) { + this.signatureAlgorithms = signatureAlgorithms; + } + + /** + * get param map + * + * @return param map + */ + public Map getSignParamMap() { + return this.signParamMap; + } + + /** + * set param map + */ + public void fillParameters(Map params) { + this.signParamMap = params; + } + + /** + * get signer + */ + public ISigner getSigner() { + return new SignerFactory().getSigner(new LocalizationAdapter(options)); + } +} 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 new file mode 100644 index 0000000000000000000000000000000000000000..5757cae4f0b3209f6a052a4edb0fd57cce4d5af9 --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/entity/HwBlockHead.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2021 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; + +/** + * define class of hap signature sub-block head + */ +public class HwBlockHead { + private static final int BLOCK_LEN = 8; //current block length + + public static int getBlockLen() { + return BLOCK_LEN; + } + + /** + * get serialization of HwBlockHead + * + * @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[] getBlockHead(char type, char tag, short length, int offset) { + return new byte[] { + (byte) (type), + (byte) (tag), + (byte) ((length >> 8) & 0xff), + (byte) (length & 0xff), + (byte) ((offset >> 24) & 0xff), + (byte) ((offset >> 16) & 0xff), + (byte) ((offset >> 8) & 0xff), + (byte) (offset & 0xff) + }; + } +} \ 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 new file mode 100644 index 0000000000000000000000000000000000000000..2a271f730037086929b680ef62fead2928ede056 --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/entity/HwSignHead.java @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2021 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.ByteArrayUtils; + +import java.io.IOException; + +/** + * define class of hap signature block head + * + * @since 2021-12-13 + */ +public class HwSignHead { + /** + * length of sign head + */ + 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 + private static final int NUM_OF_BLOCK = 2; // number of sub-block + private char[] reserve = new char[4]; + + /** + * get serialization of HwSignHead + * + * @param subBlockSize the total size of all sub-blocks + * @return Byte array after serialization of HwSignHead + */ + public byte[] getSignHead(int subBlockSize) { + int size = subBlockSize; // total size of sub-block + byte[] signHead = new byte[SIGN_HEAD_LEN]; + int start = 0; + try { + start = ByteArrayUtils.insertCharToByteArray(signHead, start, MAGIC); + if (start < 0) { + throw new IOException(); + } + start = ByteArrayUtils.insertCharToByteArray(signHead, start, VERSION); + if (start < 0) { + throw new IOException(); + } + start = ByteArrayUtils.insertIntToByteArray(signHead, start, size); + if (start < 0) { + throw new IOException(); + } + start = ByteArrayUtils.insertIntToByteArray(signHead, start, NUM_OF_BLOCK); + if (start < 0) { + throw new IOException(); + } + start = ByteArrayUtils.insertCharToByteArray(signHead, start, reserve); + if (start < 0) { + throw new IOException(); + } + } catch (IOException e) { + return null; + } + return signHead; + } +} diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/entity/Pair.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/entity/Pair.java new file mode 100644 index 0000000000000000000000000000000000000000..701d3527039b153562f62776997015c3035b7fbc --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/entity/Pair.java @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2021 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; + +/** + * Pair of two elements + * + * @since 2021-12-13 + */ +public final class Pair { + private final A mFirst; + + private final B mSecond; + + private Pair(A first, B second) { + mFirst = first; + mSecond = second; + } + + /** + * create a pair with key of type A and value of type B + * + * @param first key of pair + * @param second value of pair + * @param type of key + * @param type of value + * @return a pair with key of type A and value of type B + */ + public static Pair create(A first, B second) { + return new Pair(first, second); + } + + /** + * get key of pair + * + * @return key of pair + */ + public A getFirst() { + return mFirst; + } + + /** + * get value of pair + * + * @return value of pair + */ + public B getSecond() { + return mSecond; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((mFirst == null) ? 0 : mFirst.hashCode()); + result = prime * result + ((mSecond == null) ? 0 : mSecond.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if ((obj == null) || (getClass() != obj.getClass()) || (!(obj instanceof Pair))) { + return false; + } + Pair other = (Pair) obj; + return compare(mFirst, other.mFirst) && compare(mSecond, other.mSecond); + } + + private boolean compare(C value1, C value2) { + if (value1 == null) { + return value2 == null; + } + return value1.equals(value2); + } +} diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/entity/SignContentInfo.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/entity/SignContentInfo.java new file mode 100644 index 0000000000000000000000000000000000000000..2c607796e7daebac5022455c876ce5e0c6d1b316 --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/entity/SignContentInfo.java @@ -0,0 +1,143 @@ +/* + * Copyright (c) 2021 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.ByteArrayUtils; + +import java.io.IOException; +import java.util.ArrayList; + +/** + * Define class of content used to sign + * + * @since 2021-12-13 + */ +class SignContentHash { + /** + * the signature sub-block type + */ + protected char type; + + /** + * the signature sub-block tag + */ + protected char tag; + + /** + * the algorithm ID of digest + */ + protected short algId; + + /** + * the data length of hash value + */ + protected int length; + + /** + * the data of hash value + */ + protected byte[] hash; + + /** + * the length of content + */ + protected int contentHashLen; + + SignContentHash(char type, char tag, short algId, int length, byte[] hash) { + this.type = type; + this.tag = tag; + this.algId = algId; + this.length = length; + this.hash = hash; + this.contentHashLen = 8 + this.hash.length; + } +} + +/** + * Define class of ContentInfo of PKCS7 + * + * @since 2021-12-13 + */ +public class SignContentInfo { + // content version is 1.0.0.0 + private char[] version = "1000".toCharArray(); + private short size = 8; + private short numOfBlocks = 0; + private ArrayList hashData = new ArrayList(); + + /** + * input data to contentInfo + * + * @param type the signature sub-block type + * @param tag the signature sub-block tag + * @param algId the algorithm ID of digest + * @param length the data length of hash value + * @param hash the data of hash value + */ + public void addContentHashData(char type, char tag, short algId, int length, byte[] hash) { + SignContentHash signInfo = new SignContentHash(type, tag, algId, length, hash); + this.addHashData(signInfo); + } + + private void addHashData(SignContentHash signInfo) { + if (hashData != null) { + hashData.add(signInfo); + numOfBlocks++; + size += signInfo.contentHashLen; + } + } + + /** + * Serialization of contentInfo + * + * @return Byte array of contentInfo after Serialization + */ + public byte[] getByteContent() { + byte[] ret = new byte[this.size]; + byte[] errorOutput = null; + int index = 0; + try { + index = ByteArrayUtils.insertCharToByteArray(ret, index, version); + if (index < 0) { + throw new IOException(); + } + index = ByteArrayUtils.insertShortToByteArray(ret, ret.length, index, size); + if (index < 0) { + throw new IOException(); + } + index = ByteArrayUtils.insertShortToByteArray(ret, ret.length, index, numOfBlocks); + if (index < 0) { + throw new IOException(); + } + for (int i = 0; i < hashData.size(); i++) { + SignContentHash tmp = hashData.get(i); + ret[index] = (byte) tmp.type; + index++; + ret[index] = (byte) tmp.tag; + index++; + index = ByteArrayUtils.insertShortToByteArray(ret, ret.length, index, tmp.algId); + index = ByteArrayUtils.insertIntToByteArray(ret, index, tmp.length); + index = ByteArrayUtils.insertByteToByteArray(ret, ret.length, index, tmp.hash, tmp.hash.length); + if (index < 0) { + throw new IOException(); + } + } + } catch (IOException e) { + return errorOutput; + } + return ret; + } +} diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/entity/SignatureBlockTags.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/entity/SignatureBlockTags.java new file mode 100644 index 0000000000000000000000000000000000000000..de0e4699d37f8a3ff337e622383468fa028d83b0 --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/entity/SignatureBlockTags.java @@ -0,0 +1,132 @@ +/* + * Copyright (c) 2021 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; + +/** + * Define class of hap signature block tags + * + * @since 2021-12-13 + */ +public class SignatureBlockTags { + /** + * Default value of signature block tags + */ + public static final char DEFAULT = 0; + + /** + * The number of times get digest of whole package + */ + public static final char HASH_ALL = 1; + + /** + * Ii is root node tag of Merkle tree. + * This tag mean the digest of Merkle tree and the data size of each tree node is 1M bytes. + */ + public static final char HASH_ROOT_1M = 0x80; + + /** + * Ii is root node tag of Merkle tree. + * This tag mean the digest of Merkle tree and the data size of each tree node is 512K bytes. + */ + public static final char HASH_ROOT_512K = 0x81; + + /** + * Ii is root node tag of Merkle tree. + * This tag mean the digest of Merkle tree and the data size of each tree node is 256K bytes. + */ + public static final char HASH_ROOT_256K = 0x82; + + /** + * Ii is root node tag of Merkle tree. + * This tag mean the digest of Merkle tree and the data size of each tree node is 128K bytes. + */ + public static final char HASH_ROOT_128K = 0x83; + + /** + * Ii is root node tag of Merkle tree. + * This tag mean the digest of Merkle tree and the data size of each tree node is 64K bytes. + */ + public static final char HASH_ROOT_64K = 0x84; + + /** + * Ii is root node tag of Merkle tree. + * This tag mean the digest of Merkle tree and the data size of each tree node is 32K bytes. + */ + public static final char HASH_ROOT_32K = 0x85; + + /** + * Ii is root node tag of Merkle tree. + * This tag mean the digest of Merkle tree and the data size of each tree node is 16K bytes. + */ + public static final char HASH_ROOT_16K = 0x86; + + /** + * Ii is root node tag of Merkle tree. + * This tag mean the digest of Merkle tree and the data size of each tree node is 8K bytes. + */ + public static final char HASH_ROOT_8K = 0x87; + + /** + * Ii is root node tag of Merkle tree. + * This tag mean the digest of Merkle tree and the data size of each tree node is 4K bytes. + */ + public static final char HASH_ROOT_4K = 0x88; + + /** + * The digest of block is 1M bytes. + */ + public static final char HASH_BLOCK_1M = 0x90; + + /** + * The digest of block is 512K bytes. + */ + public static final char HASH_BLOCK_512K = 0x91; + + /** + * The digest of block is 256k bytes. + */ + public static final char HASH_BLOCK_256K = 0x92; + + /** + * The digest of block is 128k bytes. + */ + public static final char HASH_BLOCK_128K = 0x93; + + /** + * The digest of block is 64k bytes. + */ + public static final char HASH_BLOCK_64K = 0x94; + + /** + * The digest of block is 32k bytes. + */ + public static final char HASH_BLOCK_32K = 0x95; + + /** + * The digest of block is 16k bytes. + */ + public static final char HASH_BLOCK_16K = 0x96; + + /** + * The digest of block is 8k bytes. + */ + public static final char HASH_BLOCK_8K = 0x97; + + /** + * The digest of block is 4k bytes. + */ + public static final char HASH_BLOCK_4K = 0x98; +} \ No newline at end of file diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/entity/SignatureBlockTypes.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/entity/SignatureBlockTypes.java new file mode 100644 index 0000000000000000000000000000000000000000..b0d01c8266fe1be32247d6de899248804f66e9da --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/entity/SignatureBlockTypes.java @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2021 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; + +/** + * Define class of hap signature block types + */ +public class SignatureBlockTypes { + /** + * type-value of hap signature block + */ + public static final char SIGNATURE_BLOCK = 0; + + /** + * type-value of unsigned profile + */ + public static final char PROFILE_NOSIGNED_BLOCK = 1; + + /** + * type-value of signed profile + */ + public static final char PROFILE_SIGNED_BLOCK = 2; + + /** + * type-value of key rotation block + */ + public static final char KEY_ROTATION_BLOCK = 3; +} \ No newline at end of file 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 new file mode 100644 index 0000000000000000000000000000000000000000..00e8c7fe7b9668ed49e01495cfa0bfc30bd69f41 --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/entity/SigningBlock.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2021 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; + +/** + * Signature block with TLV format + * + * @since 2021-12-13 + */ +public class SigningBlock { + private int type; + private int length; + private byte[] value; + + public SigningBlock(int type, byte[] value) { + super(); + this.type = type; + this.length = value.length; + this.value = value; + } + + public int getType() { + return type; + } + + public int getLength() { + return length; + } + + public byte[] getValue() { + return value; + } +} \ No newline at end of file diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/exception/HapFormatException.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/exception/HapFormatException.java new file mode 100644 index 0000000000000000000000000000000000000000..2b3a36486469b83c058c40ff5a7d91edf78bbe23 --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/exception/HapFormatException.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2021 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.exception; + +import com.ohos.hapsigntool.utils.ParamConstants; + +/** + * Exception Hap file format exception + * + * @since 2021/12/20 + */ +public class HapFormatException extends SignatureException { + private static final long serialVersionUID = -8095203467304334741L; + + public HapFormatException(String message) { + super(ParamConstants.HAP_FORMAT_ERROR, message); + } + + public HapFormatException(String message, Throwable cause) { + super(ParamConstants.HAP_FORMAT_ERROR, message, cause); + } + + public HapFormatException(int errorCode, String message) { + super(errorCode, message); + } + + public HapFormatException(int errorCode, String message, Throwable cause) { + super(errorCode, message, cause); + } +} diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/exception/InvalidParamsException.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/exception/InvalidParamsException.java new file mode 100644 index 0000000000000000000000000000000000000000..62d4b01d44583e20534b09cd936d6928af37d790 --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/exception/InvalidParamsException.java @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2021 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.exception; + +/** + * Exception occurs when the input parameters are invalid + * + * @since 2021/12/20 + */ +public class InvalidParamsException extends Exception { + public InvalidParamsException(String message) { + super(message); + } + + public InvalidParamsException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/exception/MissingParamsException.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/exception/MissingParamsException.java new file mode 100644 index 0000000000000000000000000000000000000000..c587e8397544ff069443cf548c29d0758f0f952e --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/exception/MissingParamsException.java @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2021 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.exception; + +/** + * Exception occurs when the required parameters aren't entered. + * + * @since 2021/12/20 + */ +public class MissingParamsException extends Exception { + public MissingParamsException(String message) { + super(message); + } + + public MissingParamsException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/exception/ProfileException.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/exception/ProfileException.java new file mode 100644 index 0000000000000000000000000000000000000000..c6e80b9a1a7cda1abc922522ba789027907d9289 --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/exception/ProfileException.java @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2021 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.exception; + +/** + * Exception occurs when profile is invalid + * + * @since 2021/12/20 + */ +public class ProfileException extends Exception { + public ProfileException(String message) { + super(message); + } + + public ProfileException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/exception/SignatureException.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/exception/SignatureException.java new file mode 100644 index 0000000000000000000000000000000000000000..c7d5c5a864493b4b492575b7a466a807908e8b15 --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/exception/SignatureException.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2021 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.exception; + +/** + * Signature exception + * + * @since 2021/12/20 + */ +public class SignatureException extends Exception { + private static final long serialVersionUID = 1L; + private int errorCode; + + public SignatureException(String message) { + super(message); + } + + public SignatureException(int errorCode, String message) { + super(message); + this.errorCode = errorCode; + } + + public SignatureException(int errorCode, String message, Throwable cause) { + super(message, cause); + this.errorCode = errorCode; + } + + public SignatureException(String message, Throwable cause) { + super(message, cause); + } + + @Override + public String getMessage() { + StringBuilder sb = new StringBuilder(); + sb.append("{"); + sb.append("errorcode:"); + sb.append(this.errorCode); + sb.append(","); + sb.append("message:"); + sb.append(super.getMessage()); + sb.append("}"); + return sb.toString(); + } +} diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/exception/SignatureNotFoundException.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/exception/SignatureNotFoundException.java new file mode 100644 index 0000000000000000000000000000000000000000..43ede87e357a51df88f977a2838a605ededb5b32 --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/exception/SignatureNotFoundException.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2021 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.exception; + +/** + * Exception to find signature in the signed hap file + * + * @since 2021/12/20 + */ +public class SignatureNotFoundException extends Exception { + private static final long serialVersionUID = 1L; + + public SignatureNotFoundException(String message) { + super(message); + } + + public SignatureNotFoundException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/exception/VerifyCertificateChainException.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/exception/VerifyCertificateChainException.java new file mode 100644 index 0000000000000000000000000000000000000000..2590446ee3b19b73a9bbd2bc9556a4e8372c2a03 --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/exception/VerifyCertificateChainException.java @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2021 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.exception; + +/** + * Exception occurs when verify certificate chains + * + * @since 2021/12/20 + */ +public class VerifyCertificateChainException extends Exception { + public VerifyCertificateChainException(String message) { + super(message); + } + + public VerifyCertificateChainException(String message, Throwable cause) { + super(message, cause); + } +} 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 new file mode 100644 index 0000000000000000000000000000000000000000..fc766bea54fe5c202e45b0f34f552421b452a84d --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/provider/LocalJKSSignProvider.java @@ -0,0 +1,155 @@ +/* + * Copyright (c) 2021 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.provider; + +import com.ohos.hapsigntool.api.model.Options; +import com.ohos.hapsigntool.hap.exception.InvalidParamsException; +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 com.ohos.hapsigntool.utils.StringUtils; +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.security.GeneralSecurityException; +import java.security.cert.CRL; +import java.security.cert.CertificateFactory; +import java.security.cert.X509CRL; +import java.util.Set; + +/** + * Local keystore sign provider + * + * @since 2021/12/22 + */ +public class LocalJKSSignProvider extends SignProvider { + private static final Logger LOGGER = LogManager.getLogger(LocalJKSSignProvider.class); + + @Override + public X509CRL getCrl() { + X509CRL crl = null; + String crlPath = signParams.get(ParamConstants.PARAM_BASIC_CRL); + if (crlPath == null || "".equals(crlPath)) { + return crl; + } + try (FileInputStream input = new FileInputStream(new File(crlPath));) { + CertificateFactory cf = CertificateFactory.getInstance("X.509"); + CRL baseCrl = cf.generateCRL(input); + if (!(baseCrl instanceof X509CRL)) { + LOGGER.error("crl is not X509CRL"); + return crl; + } + crl = (X509CRL) baseCrl; + } catch (IOException e) { + LOGGER.error("read CRL File has IOException!"); + crl = null; + } catch (GeneralSecurityException e) { + LOGGER.error("Generate x509 CRL failed!"); + crl = null; + } + return crl; + } + + /** + * check keystore + * + * @throws MissingParamsException Exception occurs when the keystore file is not entered. + */ + private void checkKeystore() throws MissingParamsException { + if (!signParams.containsKey(ParamConstants.PARAM_LOCAL_JKS_KEYSTORE)) { + throw new MissingParamsException("Missing parameter: " + ParamConstants.PARAM_LOCAL_JKS_KEYSTORE); + } + } + + /** + * check keystore password + * + * @throws MissingParamsException Exception occurs when the keystore password is not right. + */ + private void checkKeystorePassword() throws MissingParamsException { + if (!signParams.containsKey(ParamConstants.PARAM_LOCAL_JKS_KEYSTORE_CODE)) { + throw new MissingParamsException("Missing parameter: " + ParamConstants.PARAM_LOCAL_JKS_KEYSTORE_CODE); + } + } + + /** + * check jks alias password + * + * @throws MissingParamsException Exception occurs when the key alias password is not right. + */ + private void checkJKSAliasPassword() throws MissingParamsException { + if (!signParams.containsKey(ParamConstants.PARAM_LOCAL_JKS_KEYALIAS_CODE)) { + throw new MissingParamsException("Missing parameter: " + ParamConstants.PARAM_LOCAL_JKS_KEYALIAS_CODE); + } + } + + /** + * check public cert + * + * @throws MissingParamsException Exception occurs when the key alias password is not right. + * @throws InvalidParamsException Exception occurs when the key alias password is invalid. + */ + private void checkPublicKeyPath() throws MissingParamsException, InvalidParamsException { + if (!signParams.containsKey(ParamConstants.PARAM_LOCAL_PUBLIC_CERT)) { + throw new MissingParamsException("Missing parameter: " + ParamConstants.PARAM_LOCAL_PUBLIC_CERT); + } + + String publicCertsFile = signParams.get(ParamConstants.PARAM_LOCAL_PUBLIC_CERT); + if (StringUtils.isEmpty(publicCertsFile)) { + throw new MissingParamsException("empty-parameter : " + ParamConstants.PARAM_LOCAL_PUBLIC_CERT); + } + + File publicKeyFile = new File(publicCertsFile); + try { + FileUtils.isValidFile(publicKeyFile); + } catch (IOException e) { + LOGGER.error("file is invalid: " + publicCertsFile + System.lineSeparator(), e); + throw new InvalidParamsException("Invalid file: " + publicCertsFile); + } + } + + @Override + 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_PUBLIC_CERT + }; + + Set paramSet = ParamProcessUtil.initParamField(paramFileds); + + for (String paramKey : options.keySet()) { + if (paramSet.contains(paramKey)) { + if (paramKey.endsWith("Pwd")) { + signParams.put(paramKey, new String(options.getChars(paramKey))); + } else { + signParams.put(paramKey, options.getString(paramKey)); + } + } + } + + checkKeystore(); + checkKeystorePassword(); + checkJKSAliasPassword(); + checkPublicKeyPath(); + } +} diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/provider/RemoteSignProvider.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/provider/RemoteSignProvider.java new file mode 100644 index 0000000000000000000000000000000000000000..3b20e0a6af2734266581402c37b0ec5c62fae204 --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/provider/RemoteSignProvider.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2021 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.provider; + +import com.ohos.hapsigntool.api.model.Options; +import com.ohos.hapsigntool.hap.exception.InvalidParamsException; +import com.ohos.hapsigntool.hap.exception.MissingParamsException; + +import java.security.cert.X509Certificate; + +public class RemoteSignProvider extends SignProvider { + @Override + public void checkParams(Options options) throws MissingParamsException, InvalidParamsException { + super.checkParams(options); + //add remote params check here + } + + @Override + protected boolean checkInputCertMatchWithProfile(X509Certificate inputCert, X509Certificate certInProfile) { + return inputCert == null ? false : inputCert.equals(certInProfile); + } +} \ No newline at end of file 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 new file mode 100644 index 0000000000000000000000000000000000000000..0ba76f10c33f96e649a446a38a4bdbd2b61c82cb --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/provider/SignProvider.java @@ -0,0 +1,642 @@ +/* + * Copyright (c) 2021 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.provider; + +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.hap.config.SignerConfig; +import com.ohos.hapsigntool.hap.entity.SigningBlock; +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.sign.SignBin; +import com.ohos.hapsigntool.hap.sign.SignHapV2; +import com.ohos.hapsigntool.hap.sign.SignatureAlgorithm; +import com.ohos.hapsigntool.hap.verify.VerifyUtils; +import com.ohos.hapsigntool.utils.CertificateUtils; +import com.ohos.hapsigntool.utils.DigestUtils; +import com.ohos.hapsigntool.utils.EscapeCharacter; +import com.ohos.hapsigntool.utils.HapUtils; +import com.ohos.hapsigntool.utils.ParamConstants; +import com.ohos.hapsigntool.utils.ParamProcessUtil; +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.ZipDataInput; +import com.ohos.hapsigntool.zip.ZipDataOutput; +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 java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.RandomAccessFile; +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.StandardCopyOption; +import java.security.InvalidKeyException; +import java.security.Security; +import java.security.cert.CertificateException; +import java.security.cert.X509CRL; +import java.security.cert.X509Certificate; +import java.util.ArrayList; +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.jar.JarFile; +import java.util.jar.JarOutputStream; +import java.util.regex.Pattern; + +/** + * Sign provider super class + * + * @since 2021-12-14 + */ +public abstract class SignProvider { + private static final Logger LOGGER = LogManager.getLogger(SignProvider.class); + private static final List VALID_SIGN_ALG_NAME = new ArrayList(); + private static final List PARAMETERS_NEED_ESCAPE = new ArrayList(); + private static final Pattern DOMAIN_NUM_PATTER = Pattern.compile("^[a-zA-Z]+[0-9]+$"); + private static final long TIMESTAMP = 1230768000000L; + /** + * list of hap signature optional blocks + */ + protected List optionalBlocks = new ArrayList(); + + /** + * parameters only used in signing + */ + protected Map signParams = new HashMap(); + + /** + * Read data of optional blocks from file user inputted. + * + * @throws InvalidParamsException Exception occurs when the input is invalid. + */ + protected void loadOptionalBlocks() throws InvalidParamsException { + String property = signParams.get(ParamConstants.PARAM_BASIC_PROPERTY); + loadOptionalBlock(property, HapUtils.HAP_PROPERTY_BLOCK_ID); + + String profile = signParams.get(ParamConstants.PARAM_BASIC_PROFILE); + loadOptionalBlock(profile, HapUtils.HAP_PROFILE_BLOCK_ID); + + String proofOfRotation = signParams.get(ParamConstants.PARAM_BASIC_PROOF); + loadOptionalBlock(proofOfRotation, HapUtils.HAP_PROOF_OF_ROTATION_BLOCK_ID); + } + + private void loadOptionalBlock(String file, int type) throws InvalidParamsException { + if (!checkStringIsNotNullAndEmity(file)) { + return; + } + if (!checkFile(file)) { + LOGGER.error("check file failed"); + throw new InvalidParamsException("Invalid file: " + file + ", filetype: " + type); + } + try { + byte[] optionalBlockBytes = HapUtils.readFileToByte(file); + if (optionalBlockBytes == null || optionalBlockBytes.length <= 0) { + LOGGER.warn("Optional block is null!"); + return; + } + optionalBlocks.add(new SigningBlock(type, optionalBlockBytes)); + } catch (IOException e) { + LOGGER.error("read file error", e); + throw new InvalidParamsException("Invalid file: " + file + " is not readable. filetype: " + type); + } + } + + /** + * check if the input path is a file + * @param filePath input file path + * @return true, if path is a file and can be read + */ + private boolean checkFile(String filePath) { + if (!(checkStringIsNotNullAndEmity(filePath))) { + LOGGER.error("fileName is null"); + return false; + } + File file = new File(filePath); + if (!file.canRead() || !file.isFile()) { + LOGGER.error(filePath + " not exist or can not read!"); + return false; + } + return true; + } + + private boolean checkStringIsNotNullAndEmity(String str) { + return !(str == null || "".equals(str)); + } + + /** + * Get certificate chain used to sign. + * + * @return list of x509 certificates. + */ + private List getPublicCerts() { + String publicCertsFile = signParams.get(ParamConstants.PARAM_LOCAL_PUBLIC_CERT); + if (StringUtils.isEmpty(publicCertsFile)) { + return Collections.emptyList(); + } + return getCertificateChainFromFile(publicCertsFile); + } + + /** + * get certificate revocation list used to sign + * + * @return certificate revocation list + */ + public X509CRL getCrl() { + return null; + } + + /** + * Create SignerConfig by certificate chain and certificate revocation list. + * + * @param certificates certificate chain + * @param crl certificate revocation list + * @return Object of SignerConfig + * @throws InvalidKeyException on error when the key is invalid. + */ + public SignerConfig createV2SignerConfigs(List certificates, X509CRL crl, Options options) + throws InvalidKeyException { + SignerConfig signerConfig = new SignerConfig(); + signerConfig.fillParameters(this.signParams); + signerConfig.setCertificates(certificates); + signerConfig.setOptions(options); + + List signatureAlgorithms = new ArrayList(); + signatureAlgorithms.add( + ParamProcessUtil.getSignatureAlgorithm(this.signParams.get(ParamConstants.PARAM_BASIC_SIGANTURE_ALG))); + signerConfig.setSignatureAlgorithms(signatureAlgorithms); + + if (crl != null) { + signerConfig.setX509CRLs(Collections.singletonList(crl)); + } + return signerConfig; + } + + /** + * sign bin file + * + * @param options parameters used to sign bin file + * @return true, if sign successfully. + */ + public boolean signBin(Options options) { + Security.addProvider(new BouncyCastleProvider()); + List publicCert = null; + SignerConfig signerConfig; + try { + // 1. check the parameters + checkParams(options); + + // 2. load optionalBlocks + loadOptionalBlocks(); + + // 3. get x509 verify certificate + publicCert = getPublicCerts(); + + checkProfileValid(publicCert); + + // 4. Get x509 CRL + X509CRL crl = getCrl(); + + // 5. Create signer configs, which contains public cert and crl info. + signerConfig = createV2SignerConfigs(publicCert, crl, options); + } catch (InvalidKeyException | InvalidParamsException | MissingParamsException | ProfileException e) { + LOGGER.error("create v2 signer configs failed.", e); + printErrorLog(e); + return false; + } + + /* 6. make signed file into output file. */ + if (!SignBin.sign(signerConfig, signParams)) { + LOGGER.error("hapsigntoolv2: error: Sign bin internal failed."); + return false; + } + LOGGER.info("Sign success"); + return true; + } + + /** + * sign hap file + * + * @param options parameters used to sign hap file + * @return true, if sign successfully + */ + public boolean sign(Options options) { + Security.addProvider(new BouncyCastleProvider()); + List publicCerts = null; + File output = null; + File tmpOutput = null; + boolean ret = false; + boolean pathOverlap = false; + try { + // 1. check the parameters + checkParams(options); + + // 2. get x509 verify certificate + publicCerts = getPublicCerts(); + + // 3. load optionalBlocks + loadOptionalBlocks(); + + checkProfileValid(publicCerts); + + X509CRL crl = getCrl(); + File input = new File(signParams.get(ParamConstants.PARAM_BASIC_INPUT_FILE)); + output = new File(signParams.get(ParamConstants.PARAM_BASIC_OUTPUT_FILE)); + if (input.getCanonicalPath().equals(output.getCanonicalPath())) { + tmpOutput = File.createTempFile("signedHap", ".hap"); + tmpOutput.deleteOnExit(); + pathOverlap = true; + } else { + tmpOutput = output; + } + // copy file and Alignment + int alignment = Integer.parseInt(signParams.get(ParamConstants.PARAM_BASIC_ALIGNMENT)); + copyFileAndAlignment(input, tmpOutput, alignment); + // generate sign block and output signedHap + try (RandomAccessFile outputHap = new RandomAccessFile(tmpOutput, "rw")) { + ZipDataInput outputHapIn = new RandomAccessFileZipDataInput(outputHap); + ZipFileInfo zipInfo = ZipUtils.findZipInfo(outputHapIn); + long centralDirectoryOffset = zipInfo.getCentralDirectoryOffset(); + ZipDataInput beforeCentralDir = outputHapIn.slice(0, centralDirectoryOffset); + ByteBuffer centralDirBuffer = + outputHapIn.createByteBuffer(centralDirectoryOffset, zipInfo.getCentralDirectorySize()); + ZipDataInput centralDirectory = new ByteBufferZipDataInput(centralDirBuffer); + + ByteBuffer eocdBuffer = zipInfo.getEocd(); + ZipDataInput eocd = new ByteBufferZipDataInput(eocdBuffer); + + SignerConfig signerConfig = createV2SignerConfigs(publicCerts, crl, options); + ZipDataInput[] contents = {beforeCentralDir, centralDirectory, eocd}; + byte[] signingBlock = SignHapV2.sign(contents, signerConfig, optionalBlocks); + long newCentralDirectoryOffset = centralDirectoryOffset + signingBlock.length; + ZipUtils.setCentralDirectoryOffset(eocdBuffer, newCentralDirectoryOffset); + LOGGER.info("Generate signing block success, begin write it to output file"); + + outputSignedFile(outputHap, centralDirectoryOffset, signingBlock, centralDirectory, eocdBuffer); + ret = true; + } + } catch (IOException | InvalidKeyException | SignatureException | MissingParamsException + | InvalidParamsException | ProfileException e) { + printErrorLog(e); + ret = false; + } + + return doAfterSign(ret, pathOverlap, tmpOutput, output); + } + + private void outputSignedFile(RandomAccessFile outputHap, long centralDirectoryOffset, + byte[] signingBlock, ZipDataInput centralDirectory, ByteBuffer eocdBuffer) throws IOException { + ZipDataOutput outputHapOut = new RandomAccessFileZipDataOutput(outputHap, centralDirectoryOffset); + outputHapOut.write(signingBlock, 0, signingBlock.length); + centralDirectory.copyTo(0, centralDirectory.size(), outputHapOut); + outputHapOut.write(eocdBuffer); + } + + private boolean doAfterSign(boolean isSuccess, boolean pathOverlap, File tmpOutput, File output) { + boolean ret = isSuccess; + if (ret && pathOverlap) { + try { + Files.move(tmpOutput.toPath(), output.toPath(), StandardCopyOption.REPLACE_EXISTING); + } catch (IOException e) { + printErrorLog(e); + ret = false; + } + } + if ((!ret) && (!pathOverlap) && (output != null)) { + output.deleteOnExit(); + } + + if (ret) { + LOGGER.info("Sign Hap success!"); + } + return ret; + } + + private void printErrorLog(Exception e) { + if (e != null) { + LOGGER.error("hapsigntoolv2: error: {}", e.getMessage(), e); + } + } + + 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(9); + List entryNames = SignHapV2.getEntryNamesFromHap(inputJar); + SignHapV2.copyFiles(entryNames, inputJar, outputJar, timestamp, alignment); + } + } + + /** + * check private key path + * + * @throws MissingParamsException Exception occurs when private key alias is not entered. + */ + public void checkPrivateKeyPath() throws MissingParamsException { + if (!signParams.containsKey(ParamConstants.PARAM_BASIC_PRIVATE_KEY)) { + throw new MissingParamsException("Missing parameter: " + ParamConstants.PARAM_BASIC_PRIVATE_KEY); + } + } + + /** + * check input hap file + * + * @throws MissingParamsException Exception occurs when unsigned file is not entered. + */ + protected void checkInputFile() throws MissingParamsException { + if (!signParams.containsKey(ParamConstants.PARAM_BASIC_INPUT_FILE)) { + throw new MissingParamsException("Missing parameter: " + ParamConstants.PARAM_BASIC_INPUT_FILE); + } + } + + static { + VALID_SIGN_ALG_NAME.add(ParamConstants.HAP_SIG_ALGORITHM_SHA256_ECDSA); + VALID_SIGN_ALG_NAME.add(ParamConstants.HAP_SIG_ALGORITHM_SHA384_ECDSA); + VALID_SIGN_ALG_NAME.add(ParamConstants.HAP_SIG_ALGORITHM_SHA512_ECDSA); + VALID_SIGN_ALG_NAME.add(ParamConstants.HAP_SIG_ALGORITHM_SHA256_RSA_PSS); + VALID_SIGN_ALG_NAME.add(ParamConstants.HAP_SIG_ALGORITHM_SHA384_RSA_PSS); + VALID_SIGN_ALG_NAME.add(ParamConstants.HAP_SIG_ALGORITHM_SHA512_RSA_PSS); + 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); + } + + /** + * check signature algorithm + * + * @throws MissingParamsException Exception occurs when the name of sign algorithm is not entered. + * @throws InvalidParamsException Exception occurs when the inputted sign algorithm is invalid. + */ + private void checkSignatureAlg() throws MissingParamsException, InvalidParamsException { + if (!signParams.containsKey(ParamConstants.PARAM_BASIC_SIGANTURE_ALG)) { + LOGGER.error("Missing parameter : " + ParamConstants.PARAM_BASIC_SIGANTURE_ALG); + throw new MissingParamsException("Missing parameter: " + ParamConstants.PARAM_BASIC_SIGANTURE_ALG); + } + + String signAlg = signParams.get( ParamConstants.PARAM_BASIC_SIGANTURE_ALG).trim(); + for (String validAlg : VALID_SIGN_ALG_NAME) { + if (validAlg.equalsIgnoreCase(signAlg)) { + return; + } + } + LOGGER.error("Unsupported signature algorithm :" + signAlg); + throw new InvalidParamsException("Invalid parameter: Sign Alg"); + } + + /** + * check output hap file + * + * @throws MissingParamsException Exception occurs when the output file path is not entered. + */ + protected void checkOutputFile() throws MissingParamsException { + if (!signParams.containsKey(ParamConstants.PARAM_BASIC_OUTPUT_FILE)) { + throw new MissingParamsException("Missing parameter: " + ParamConstants.PARAM_BASIC_OUTPUT_FILE); + } + } + + /** + * check profile + * + * @throws MissingParamsException Exception occurs when the profile is not entered. + */ + private void checkProfile() throws MissingParamsException { + if (!signParams.containsKey(ParamConstants.PARAM_BASIC_PROFILE)) { + throw new MissingParamsException("Missing parameter: " + ParamConstants.PARAM_BASIC_PROFILE); + } + } + + /** + * check alignment + */ + protected void checkSignAlignment() { + if (!signParams.containsKey(ParamConstants.PARAM_BASIC_ALIGNMENT)) { + signParams.put(ParamConstants.PARAM_BASIC_ALIGNMENT, ParamConstants.ALIGNMENT); + } + } + + /** + * Get CN value of developer certificate from profile. + * + * @param buildInfoObject json obect of buildInfo in profile. + * @return Object of development-certificate. + */ + private X509Certificate getDevelopmentCertificate(JsonObject buildInfoObject) { + final String developmentCertElememt = "development-certificate"; + String developmentCertificate = buildInfoObject.get(developmentCertElememt).getAsString(); + return DigestUtils.decodeBase64ToX509Certifate(developmentCertificate); + } + + /** + * Get CN value of release certificate from profile. + * + * @param buildInfoObject json obect of buildInfo in profile. + * @return Object of distribution-certificate. + */ + private X509Certificate getReleaseCertificate(JsonObject buildInfoObject) { + final String distributeCertElememt = "distribution-certificate"; + String distributeCertificate = buildInfoObject.get(distributeCertElememt).getAsString(); + return DigestUtils.decodeBase64ToX509Certifate(distributeCertificate); + } + + private String getCertificateCN(X509Certificate cert) { + if (cert == null) { + return ""; + } + String valueOfDN = cert.getSubjectDN().toString(); + + valueOfDN = valueOfDN.replaceAll("\"", ""); + + String[] arrayDN = valueOfDN.split(","); + for (String element : arrayDN) { + if (element.trim().startsWith("CN=")) { + return element.split("=")[1]; + } + } + return ""; + } + + private byte[] findProfileFromOptionalBlocks() { + byte[] profile = new byte[0]; + for (SigningBlock optionalBlock : optionalBlocks) { + if (optionalBlock.getType() == HapUtils.HAP_PROFILE_BLOCK_ID) { + profile = optionalBlock.getValue(); + } + } + return profile; + } + + /** + * Check profile is valid. A valid profile must include type and + * certificate which has a non-empty value of DN. + * + * @param inputCerts certificates inputted by user. + * @throws ProfileException Exception occurs when profile is invalid. + */ + private void checkProfileValid(List inputCerts) throws ProfileException { + try { + byte[] profile = findProfileFromOptionalBlocks(); + String content; + CMSSignedData cmsSignedData = new CMSSignedData(profile); + boolean verifyResult = VerifyUtils.verifyCmsSignedData(cmsSignedData); + if (!verifyResult) { + 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!"); + } + content = new String((byte[]) contentObj, StandardCharsets.UTF_8); + JsonElement parser = JsonParser.parseString(content); + JsonObject profileJson = parser.getAsJsonObject(); + checkProfileInfo(profileJson, inputCerts); + } catch (CMSException e) { + throw new ProfileException("Verify profile pkcs7 failed! Profile is invalid.", e); + } catch (JsonParseException e) { + throw new ProfileException("Invalid parameter: profile content is not a JSON.", e); + } + } + + private void checkProfileInfo(JsonObject profileJson, List inputCerts) throws ProfileException { + String profileTypeKey = "type"; + String profileType = profileJson.get(profileTypeKey).getAsString(); + if (profileType == null || profileType.length() == 0) { + throw new ProfileException("Get profile type error!"); + } + String buildInfoMember = "bundle-info"; + JsonObject buildInfoObject = profileJson.getAsJsonObject(buildInfoMember); + X509Certificate certInProfile; + if (profileType.equalsIgnoreCase("release")) { + certInProfile = getReleaseCertificate(buildInfoObject); + } else if (profileType.equalsIgnoreCase("debug")) { + certInProfile = getDevelopmentCertificate(buildInfoObject); + } else { + throw new ProfileException("Unsupported profile type!"); + } + if (!inputCerts.isEmpty()) { + if (!checkInputCertMatchWithProfile(inputCerts.get(0), certInProfile)) { + throw new ProfileException("input certificates do not match with profile!"); + } + } + String cn = getCertificateCN(certInProfile); + LOGGER.info("certificate in profile: {}", cn); + if (cn.isEmpty()) { + throw new ProfileException("Common name of certificate is empty!"); + } + } + + /** + * check whether certificate inputted by user is matched with the certificate in profile. + * + * @param inputCert certificates inputted by user. + * @param certInProfile the certificate in profile. + * @return true, if it is match. + */ + protected boolean checkInputCertMatchWithProfile(X509Certificate inputCert, X509Certificate certInProfile) { + return true; + } + + /** + * Check input parameters is valid. And put valid parameters into signParams. + * + * @param options parameters inputted by user. + * @throws MissingParamsException Exception occurs when the required parameters are not entered. + * @throws InvalidParamsException Exception occurs when the required parameters are invalid. + */ + 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 + }; + Set paramSet = ParamProcessUtil.initParamField(paramFileds); + + for (String paramKey : options.keySet()) { + if (paramSet.contains(paramKey)) { + signParams.put(paramKey, getParamValue(paramKey, options.getString(paramKey))); + } + } + + checkPrivateKeyPath(); + checkSignatureAlg(); + checkInputFile(); + checkOutputFile(); + checkProfile(); + checkSignAlignment(); + } + + static { + PARAMETERS_NEED_ESCAPE.add(ParamConstants.PARAM_REMOTE_CODE); + PARAMETERS_NEED_ESCAPE.add(ParamConstants.PARAM_LOCAL_JKS_KEYSTORE_CODE); + PARAMETERS_NEED_ESCAPE.add(ParamConstants.PARAM_LOCAL_JKS_KEYALIAS_CODE); + } + + /** + * Get parameters from inputted strings. This function unescape some escaped parameters and return it. + * + * @param paramName the name of parameter + * @param paramValue the value of parameter + * @return parameter value in the correct form. + */ + protected String getParamValue(String paramName, String paramValue) { + for ( String name : PARAMETERS_NEED_ESCAPE) { + if (name.equals(paramName)) { + return EscapeCharacter.unescape(paramValue); + } + } + return paramValue; + } + + private List getCertificateChainFromFile(String certChianFile) { + try { + return CertificateUtils.getCertListFromFile(certChianFile); + } catch (CertificateException e) { + LOGGER.error("File content is not certificates! " + e.getMessage()); + } catch (IOException e) { + LOGGER.error("Certificate file exception: " + e.getMessage()); + } catch (VerifyCertificateChainException e) { + LOGGER.error(e.getMessage()); + } + return Collections.emptyList(); + } +} diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/sign/BcPkcs7Generator.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/sign/BcPkcs7Generator.java new file mode 100644 index 0000000000000000000000000000000000000000..86b23dd926e1a28f65574c08155c4a1bdf76421f --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/sign/BcPkcs7Generator.java @@ -0,0 +1,267 @@ +/* + * Copyright (c) 2021 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.hap.config.SignerConfig; +import com.ohos.hapsigntool.hap.entity.Pair; +import com.ohos.hapsigntool.hap.exception.SignatureException; +import com.ohos.hapsigntool.hap.verify.VerifyUtils; + +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.ASN1ObjectIdentifier; +import org.bouncycastle.asn1.ASN1Set; +import org.bouncycastle.asn1.BERSet; +import org.bouncycastle.asn1.DEROctetString; +import org.bouncycastle.asn1.DERSet; +import org.bouncycastle.asn1.cms.Attribute; +import org.bouncycastle.asn1.cms.AttributeTable; +import org.bouncycastle.asn1.cms.Time; +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.asn1.x509.AlgorithmIdentifier; +import org.bouncycastle.cert.jcajce.JcaX509CRLHolder; +import org.bouncycastle.cert.jcajce.JcaX509CertificateHolder; +import org.bouncycastle.cms.CMSException; +import org.bouncycastle.cms.CMSSignedData; +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.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.PublicKey; +import java.security.Signature; +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.Hashtable; +import java.util.List; + +/** + * BC implementation + * + * @since 2021/12/21 + */ +public class BcPkcs7Generator implements Pkcs7Generator { + private static final Logger LOGGER = LogManager.getLogger(BcPkcs7Generator.class); + private static final SignatureAlgorithmIdentifierFinder SIGN_ALG_FINDER = + new DefaultSignatureAlgorithmIdentifierFinder(); + private static final DigestAlgorithmIdentifierFinder DIGEST_ALG_FINDER = + new DefaultDigestAlgorithmIdentifierFinder(); + + @Override + public byte[] generateSignedData(byte[] content, SignerConfig signerConfig) throws SignatureException { + if (content == null) { + throw new SignatureException("unsigned data is null"); + } + ASN1EncodableVector signerInfoLst = new ASN1EncodableVector(); + ASN1EncodableVector algorithmIdLst = new ASN1EncodableVector(); + for (SignatureAlgorithm signatureAlgorithm : signerConfig.getSignatureAlgorithms()) { + try { + SignerInfo signerInfo = getSignerInfo(signatureAlgorithm, content, signerConfig); + if (signerInfo != null) { + algorithmIdLst.add(signerInfo.getDigestAlgorithm()); + signerInfoLst.add(signerInfo); + } + LOGGER.info("Add sign data in sign info list success."); + } catch (NoSuchAlgorithmException e) { + throw new SignatureException( + "Invalid algorithm: " + signatureAlgorithm.getContentDigestAlgorithm().name(), e); + } catch (IOException e) { + throw new SignatureException("sign IOException" + e.getMessage(), e); + } + } + return packagePKCS7(signerConfig, new DERSet(signerInfoLst), new DERSet(algorithmIdLst), content); + } + + private byte[] packagePKCS7( + SignerConfig signerConfig, + ASN1Set signerInfoLst, + ASN1Set algorithmIdLst, + byte[] unsignedHapDigest) + throws SignatureException { + ContentInfo contentInfo = new ContentInfo(PKCSObjectIdentifiers.data, new DEROctetString(unsignedHapDigest)); + ASN1Set certs = null; + ASN1Set crls = null; + byte[] signBlock; + try { + if (checkListNotNullOrEmty(signerConfig.getCertificates())) { + certs = createBerSetFromCerts(signerConfig.getCertificates()); + } + if (checkListNotNullOrEmty(signerConfig.getX509CRLs())) { + crls = createBerSetFromCrls(signerConfig.getX509CRLs()); + } + SignedData signedData = new SignedData( + new ASN1Integer(1), algorithmIdLst, contentInfo, certs, crls, signerInfoLst); + ContentInfo pkcs7 = new ContentInfo(PKCSObjectIdentifiers.signedData, signedData); + signBlock = pkcs7.getEncoded(ASN1Encoding.DER); + } catch (CertificateEncodingException | CRLException | IOException e) { + throw new SignatureException("Packaging PKCS cms data failed!", e); + } + boolean verifyResult = false; + try { + CMSSignedData cmsSignedData = new CMSSignedData(signBlock); + verifyResult = VerifyUtils.verifyCmsSignedData(cmsSignedData); + } catch (CMSException e) { + throw new SignatureException("PKCS cms data verify failed", e); + } + if (!verifyResult) { + throw new SignatureException("PKCS cms data did not verify"); + } + return signBlock; + } + + private SignerInfo getSignerInfo( + SignatureAlgorithm signatureAlgorithm,byte[] unsignedHapDigest, SignerConfig signerConfig) + throws SignatureException, IOException, NoSuchAlgorithmException { + Pair signatureParams = signatureAlgorithm.getSignatureAlgAndParams(); + ContentDigestAlgorithm contentDigestAlg = signatureAlgorithm.getContentDigestAlgorithm(); + String jcaSignatureAlg = signatureParams.getFirst(); + MessageDigest md = MessageDigest.getInstance(contentDigestAlg.name()); + byte[] digest = md.digest(unsignedHapDigest); + ASN1Set authed = generatePKCS9Attributes(digest); + + //Get sign data content from sign server + byte[] signatureBytes = signerConfig.getSigner().getSignature( + authed.getEncoded(), jcaSignatureAlg, signatureParams.getSecond()); + if (signatureBytes == null) { + throw new SignatureException("Generate signature bytes error"); + } + if (signerConfig.getCertificates().isEmpty()) { + throw new SignatureException("No certificates configured for signer"); + } + + Pair obj = signatureAlgorithm.getSignatureAlgAndParams(); + Pair signAlgPair = Pair.create(obj.getFirst(), obj.getSecond()); + if (!verifySignatureFromServer(signerConfig, signatureBytes, signAlgPair, authed)) { + throw new SignatureException("Signature did not verify"); + } + return createSignerInfo(signerConfig, signatureAlgorithm, authed, signatureBytes); + } + + private SignerInfo createSignerInfo( + SignerConfig signerConfig, + SignatureAlgorithm signatureAlgorithm, + ASN1Set authed, + byte[] signedHapDigest) + throws SignatureException { + String digestAlgorithm = signatureAlgorithm.getContentDigestAlgorithm().getDigestAlgorithm(); + String signAlg = signatureAlgorithm.getSignatureAlgAndParams().getFirst(); + try { + JcaX509CertificateHolder certificateHolder = + new JcaX509CertificateHolder(signerConfig.getCertificates().get(0)); + AlgorithmIdentifier digestAlgId = DIGEST_ALG_FINDER.find(digestAlgorithm); + AlgorithmIdentifier signAlgId = SIGN_ALG_FINDER.find(signAlg); + IssuerAndSerialNumber issuerAndSerialNumber = + new IssuerAndSerialNumber(certificateHolder.getIssuer(), certificateHolder.getSerialNumber()); + return new SignerInfo(new ASN1Integer(1), issuerAndSerialNumber, digestAlgId, + authed, signAlgId, new DEROctetString(signedHapDigest), null); + } catch (CertificateEncodingException e) { + throw new SignatureException("Generate signer info error", e); + } + } + + private ASN1Set generatePKCS9Attributes(byte[] digest) { + Hashtable tab = new Hashtable<>(); + Attribute signTime = new Attribute(PKCSObjectIdentifiers.pkcs_9_at_signingTime, + new DERSet(new Time(new Date()))); + Attribute contentType = new Attribute(PKCSObjectIdentifiers.pkcs_9_at_contentType, + new DERSet(PKCSObjectIdentifiers.data)); + Attribute digestAtt = new Attribute(PKCSObjectIdentifiers.pkcs_9_at_messageDigest, + new DERSet(new DEROctetString(digest))); + tab.put(signTime.getAttrType(), signTime); + tab.put(contentType.getAttrType(), contentType); + tab.put(digestAtt.getAttrType(), digestAtt); + return new DERSet(new AttributeTable(tab).toASN1EncodableVector()); + } + + private ASN1Set createBerSetFromCrls(List crls) throws CRLException { + if (crls == null || crls.size() == 0) { + return null; + } + ASN1EncodableVector vector = new ASN1EncodableVector(); + for (X509CRL crl : crls) { + vector.add(new JcaX509CRLHolder(crl).toASN1Structure()); + } + return new BERSet(vector); + } + + private ASN1Set createBerSetFromCerts(List certs) throws CertificateEncodingException { + if (certs == null || certs.size() == 0) { + return null; + } + ASN1EncodableVector vector = new ASN1EncodableVector(); + + for (X509Certificate cert : certs) { + vector.add(new JcaX509CertificateHolder(cert).toASN1Structure()); + } + return new BERSet(vector); + } + + private boolean checkListNotNullOrEmty(List lists) { + return (lists != null) && (lists.size() > 0); + } + + private boolean verifySignatureFromServer( + SignerConfig signerConfig, + byte[] signatureBytes, + Pair signAlgPair, + ASN1Set authed) + throws SignatureException { + try { + PublicKey publicKey = signerConfig.getCertificates().get(0).getPublicKey(); + Signature signature = Signature.getInstance(signAlgPair.getFirst()); + signature.initVerify(publicKey); + if (signAlgPair.getSecond() != null) { + signature.setParameter(signAlgPair.getSecond()); + } + signature.update(authed.getEncoded()); + if (signatureBytes == null) { + LOGGER.error("signatureBytes is null"); + throw new SignatureException("Signature did not verify"); + } + if (!signature.verify(signatureBytes)) { + throw new SignatureException("Signature did not verify"); + } + return true; + } catch (InvalidKeyException | java.security.SignatureException e) { + LOGGER.error("Failed to verify generated signature using public key from certificate", e); + } catch (NoSuchAlgorithmException e) { + LOGGER.error("Failed to verify generated " + signAlgPair.getFirst() + + " signature using public key from certificate", e); + } catch (InvalidAlgorithmParameterException e) { + LOGGER.error("Failed to verify generated " + signAlgPair.getSecond() + + " signature using public key from certificate", e); + } catch (IOException e) { + LOGGER.error("PKCS9 Attributes encode failed.", e); + } + return false; + } +} diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/sign/ContentDigestAlgorithm.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/sign/ContentDigestAlgorithm.java new file mode 100644 index 0000000000000000000000000000000000000000..ae0366b281f36e46c39654f12de5e37d284f4610 --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/sign/ContentDigestAlgorithm.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2021 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; + +/** + * Content digest algorithm + * + * @since 2021-12-13 + */ +public enum ContentDigestAlgorithm { + SHA256("SHA-256", 256 / 8), + SHA384("SHA-384", 384 / 8), + SHA512("SHA-512", 512 / 8); + + private String digestAlgorithm; + + private int digestOutputByteSize; + + ContentDigestAlgorithm(String digestAlgorithm, int digestOutputByteSize) { + this.digestAlgorithm = digestAlgorithm; + this.digestOutputByteSize = digestOutputByteSize; + } + + public String getDigestAlgorithm() { + return digestAlgorithm; + } + + public int getDigestOutputByteSize() { + return digestOutputByteSize; + } +} diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/sign/Pkcs7Generator.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/sign/Pkcs7Generator.java new file mode 100644 index 0000000000000000000000000000000000000000..ef027e01d8ff2c31014d73edcf44f0aec0d3975c --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/sign/Pkcs7Generator.java @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2021 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.hap.config.SignerConfig; +import com.ohos.hapsigntool.hap.exception.SignatureException; + +/** + * interface of PKCS#7 signed data generator + * + * @since 2021/12/21 + */ +@FunctionalInterface +public interface Pkcs7Generator { + Pkcs7Generator BC = new BcPkcs7Generator(); + + /** + * Generate PKCS#7 signed data with the specific content and signer config. + * + * @param content PKCS7 data, content of unsigned file digest. + * @param signerConfig configurations of signer. + * @return PKCS7 signed data. + * @throws SignatureException if an error occurs when generating PKCS#7 block. + */ + byte[] generateSignedData(byte[] content, SignerConfig signerConfig) throws SignatureException; +} 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 new file mode 100644 index 0000000000000000000000000000000000000000..d81be304aedccf58c20ac2572d512182a0dc752c --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/sign/SignBin.java @@ -0,0 +1,202 @@ +/* + * Copyright (c) 2021 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.hap.config.SignerConfig; +import com.ohos.hapsigntool.hap.entity.HwBlockHead; +import com.ohos.hapsigntool.hap.entity.HwSignHead; +import com.ohos.hapsigntool.hap.entity.SignContentInfo; +import com.ohos.hapsigntool.hap.entity.SignatureBlockTags; +import com.ohos.hapsigntool.hap.entity.SignatureBlockTypes; +import com.ohos.hapsigntool.hap.exception.SignatureException; +import com.ohos.hapsigntool.utils.FileUtils; +import com.ohos.hapsigntool.utils.HashUtils; +import com.ohos.hapsigntool.utils.ParamConstants; +import com.ohos.hapsigntool.utils.ParamProcessUtil; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.io.DataOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.Map; +/** + * Lite OS bin file Signature signer. + * + * @since 2021/12/21 + */ +public class SignBin { + private static final Logger LOGGER = LogManager.getLogger(SignBin.class); + + /** + * The function of sign bin file. + * + * @param signerConfig The config of sign bin file. + * @param signParams The input parameters of sign bin + * @return true, if sign successfully. + */ + public static boolean sign(SignerConfig signerConfig, Map signParams) { + boolean result = false; + /* 1. Make block head, write to output file. */ + 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); + if (!writeBlockDataToFile(inputFile, outputFile, profileFile)) { + LOGGER.error("The block head data made failed."); + ParamProcessUtil.delDir(new File(outputFile)); + return false; + } + LOGGER.info("The block head data made success."); + + /* 2. Make sign data, add write to output file */ + String signAlg = signParams.get(ParamConstants.PARAM_BASIC_SIGANTURE_ALG); + if (!writeSignDataToOutputFile(signerConfig, outputFile, signAlg)) { + LOGGER.error("The sign data made failed."); + ParamProcessUtil.delDir(new File(outputFile)); + return false; + } + LOGGER.info("The data signed success."); + + /* 3. Make sign data, add write to output file */ + if (!writeSignHeadDataToOutputFile(inputFile, outputFile)) { + LOGGER.error("The sign head data made failed."); + ParamProcessUtil.delDir(new File(outputFile)); + } else { + result = true; + } + return result; + } + + private static boolean writeBlockDataToFile( + String inputFile, String outputFile, String profileFile) { + try { + long binFileLen = FileUtils.getFileLen(inputFile); + long profileDataLen = FileUtils.getFileLen(profileFile); + if (!checkBinAndProfileLengthIsValid(binFileLen, profileDataLen)) { + LOGGER.error("file length is invalid, binFileLen: " + binFileLen + " profileDataLen: " + profileDataLen); + throw new IOException(); + } + + long offset = binFileLen + HwBlockHead.getBlockLen() + HwBlockHead.getBlockLen(); + if (isLongOverflowInteger(offset)) { + LOGGER.error("The profile block head offset is overflow interger range, offset: " + offset); + throw new IOException(); + } + char isSigned = SignatureBlockTypes.PROFILE_SIGNED_BLOCK; + byte[] proBlockByte = + HwBlockHead.getBlockHead(isSigned, SignatureBlockTags.DEFAULT, (short) profileDataLen, (int) offset); + + offset += profileDataLen; + if (isLongOverflowInteger(offset)) { + LOGGER.error("The sign block head offset is overflow integer range, offset: " + offset); + throw new IOException(); + } + byte[] signBlockByte = HwBlockHead.getBlockHead( + SignatureBlockTypes.SIGNATURE_BLOCK, SignatureBlockTags.DEFAULT, (short) 0, (int) offset); + + return writeSignedBin(inputFile, proBlockByte, signBlockByte, profileFile, outputFile); + } catch (IOException e) { + LOGGER.error("writeBlockDataToFile failed.", e); + return false; + } + } + + private static boolean writeSignedBin(String inputFile, byte[] proBlockByte, byte[] signBlockByte, + String profileFile, 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 infomation of input file: " + inputFile + + " to outputFile: " + outputFile); + throw new IOException(); + } + + // 2. write profile block head to the output file. + if (!FileUtils.writeByteToDos(proBlockByte, dataOutputStream)) { + LOGGER.error("Failed to write proBlockByte to output file: " + outputFile); + throw new IOException(); + } + + // 3. write sign block head to the output file. + if (!FileUtils.writeByteToDos(signBlockByte, dataOutputStream)) { + LOGGER.error("Failed to write binBlockByte to output file: " + outputFile); + throw new IOException(); + } + + // 4. write profile src file to the output file. + if (!FileUtils.writeFileToDos(profileFile, dataOutputStream)) { + LOGGER.error("Failed to write profile file: " + profileFile); + throw new IOException(); + } + } catch (IOException e) { + LOGGER.error("writeSignedBin failed.", e); + return false; + } + return true; + } + + private static boolean checkBinAndProfileLengthIsValid(long binFileLen, long profileDataLen) { + return (binFileLen != -1) && (profileDataLen != -1) && (!isLongOverflowShort(profileDataLen)); + } + + private static boolean writeSignHeadDataToOutputFile(String inputFile, String outputFile) { + long size = FileUtils.getFileLen(outputFile) - FileUtils.getFileLen(inputFile) + HwSignHead.SIGN_HEAD_LEN; + if (isLongOverflowInteger(size)) { + LOGGER.error("File size is Overflow integer range."); + return false; + } + HwSignHead signHeadData = new HwSignHead(); + byte[] signHeadByte = signHeadData.getSignHead((int) size); + if (signHeadByte == null) { + LOGGER.error("Failed to get sign head data."); + return false; + } + return FileUtils.writeByteToOutFile(signHeadByte, outputFile); + } + + private static boolean writeSignDataToOutputFile(SignerConfig signerConfig, String outputFile, String signAlg) { + String alg = ParamProcessUtil.getSignatureAlgorithm(signAlg).getContentDigestAlgorithm().getDigestAlgorithm(); + byte[] data = HashUtils.getFileDigest(outputFile, alg); + if (data == null) { + LOGGER.error("getFileDigest failed."); + return false; + } + byte[] outputChunk = null; + SignContentInfo contentInfo = null; + try { + contentInfo = new SignContentInfo(); + contentInfo.addContentHashData( + (char) 0, SignatureBlockTags.HASH_ROOT_4K, (short) HashUtils.getHashAlgsId(alg), data.length, data); + byte[] dig = contentInfo.getByteContent(); + outputChunk = Pkcs7Generator.BC.generateSignedData(dig, signerConfig); + return FileUtils.writeByteToOutFile(outputChunk, outputFile); + } catch (SignatureException e) { + LOGGER.error("Sign hap Lite failed.", e); + } + return false; + } + + 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/SignHapV2.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/sign/SignHapV2.java new file mode 100644 index 0000000000000000000000000000000000000000..90a2ebc60aaee35c0baabf3d3060349a7a15d635 --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/sign/SignHapV2.java @@ -0,0 +1,340 @@ +/* + * Copyright (c) 2021 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.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.SignatureException; +import com.ohos.hapsigntool.utils.HapUtils; +import com.ohos.hapsigntool.zip.ZipDataInput; + +import java.io.IOException; +import java.io.InputStream; +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; +import java.util.Map; +import java.util.Set; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; +import java.util.jar.JarOutputStream; + +/** + * Hap Signature Scheme v2 signer + * + * @since 2021/12/21 + */ +public abstract class SignHapV2 { + private static final int HAP_SIGN_SCHEME_VERSION = 2; + private static final int STORED_ENTRY_SO_ALIGNMENT = 4096; + + private SignHapV2() {} + + /** + * 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. + */ + public static void copyFiles(List entryNames, JarFile in, + JarOutputStream out, long timestamp, int defaultAlignment) throws IOException { + Collections.sort(entryNames); + long offset = 4L; + for (String name : entryNames) { + JarEntry inEntry = in.getJarEntry(name); + if (inEntry.getMethod() != JarEntry.STORED) { + continue; + } + + offset += JarFile.LOCHDR; + + JarEntry outEntry = new JarEntry(inEntry); + outEntry.setTime(timestamp); + + outEntry.setComment(null); + outEntry.setExtra(null); + + offset += outEntry.getName().length(); + + int alignment = getStoredEntryDataAlignment(name, defaultAlignment); + if (alignment > 0 && (offset % alignment != 0)) { + int needed = alignment - (int) (offset % alignment); + outEntry.setExtra(new byte[needed]); + offset += needed; + } + + out.putNextEntry(outEntry); + byte[] buffer = new byte[4096]; + try (InputStream data = in.getInputStream(inEntry)) { + int num; + while ((num = data.read(buffer)) > 0) { + out.write(buffer, 0, num); + offset += num; + } + out.flush(); + } + } + + copyFilesExceptStoredFile(entryNames, in, out, timestamp); + } + + private static void copyFilesExceptStoredFile(List entryNames, JarFile in, + JarOutputStream out, long timestamp) throws IOException { + byte[] buffer = new byte[4096]; + + for (String name : entryNames) { + JarEntry inEntry = in.getJarEntry(name); + if (inEntry.getMethod() == JarEntry.STORED) { + continue; + } + + JarEntry outEntry = new JarEntry(name); + 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(); + } + } + } + + /** + * If store entry is end with '.so', use 4096-alignment, otherwise, use default-alignment. + * + * @param entryName name of entry + * @param defaultAlignment default value of alignment. + * @return value of alignment. + */ + private static int getStoredEntryDataAlignment(String entryName, int defaultAlignment) { + if (defaultAlignment <= 0) { + return 0; + } + if (entryName.endsWith(".so")) { + return STORED_ENTRY_SO_ALIGNMENT; + } + return defaultAlignment; + } + + private static byte[] getHapSigningBlock( + Set contentDigestAlgorithms, + List optionalBlocks, + SignerConfig signerConfig, + ZipDataInput[] hapData) + throws SignatureException { + /** + * Compute digests of Hap contents + * Sign the digests and wrap the signature and signer info into the Hap Signing Block + */ + byte[] hapSignatureBytes = null; + try { + Map contentDigests = + HapUtils.computeDigests(contentDigestAlgorithms, hapData, optionalBlocks); + hapSignatureBytes = generateHapSigningBlock(signerConfig, contentDigests, optionalBlocks); + } catch (DigestException | IOException e) { + throw new SignatureException("Failed to compute digests of HAP", e); + } + return hapSignatureBytes; + } + + private static byte[] generateHapSigningBlock( + SignerConfig signerConfig, + Map contentDigests, + List optionalBlocks) + throws SignatureException { + byte[] hapSignatureSchemeV2Block = generateHapSignatureSchemeV2Block(signerConfig, contentDigests); + return generateHapSigningBlock(hapSignatureSchemeV2Block, optionalBlocks); + } + + private static byte[] generateHapSigningBlock(byte[] hapSignatureSchemeBlock, List optionalBlocks) { + // 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 + long optionalBlockSize = 0L; + for (SigningBlock optionalBlock : optionalBlocks) { + optionalBlockSize += optionalBlock.getLength(); + } + + long resultSize = + ((4 + 4 + 4) * (optionalBlocks.size() + 1)) + + optionalBlockSize // optional pair + + hapSignatureSchemeBlock.length // App signing pairs + + 4 // block count + + 8 // size + + 16 // magic + + 4; // verision + if (resultSize > Integer.MAX_VALUE) { + throw new IllegalArgumentException("HapSigningBlock out of range : " + resultSize); + } + ByteBuffer result = ByteBuffer.allocate((int) resultSize); + result.order(ByteOrder.LITTLE_ENDIAN); + + Map typeAndOffsetMap = new HashMap(); + int currentOffset = ((4 + 4 + 4) * (optionalBlocks.size() + 1)); + int currentOffsetInBlockValue = 0; + int blockValueSizes = (int) (optionalBlockSize + hapSignatureSchemeBlock.length); + byte[] blockValues = new byte[blockValueSizes]; + + for (SigningBlock optionalBlock : optionalBlocks) { + System.arraycopy( + optionalBlock.getValue(), 0, blockValues, currentOffsetInBlockValue, optionalBlock.getLength()); + typeAndOffsetMap.put(optionalBlock.getType(), currentOffset); + currentOffset += optionalBlock.getLength(); + currentOffsetInBlockValue += optionalBlock.getLength(); + } + + System.arraycopy( + 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 + } + 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); + 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()); // magic + result.putInt(HAP_SIGN_SCHEME_VERSION); // version + return result.array(); + } + + private static byte[] generateHapSignatureSchemeV2Block( + SignerConfig signerConfig, Map contentDigests) throws SignatureException { + byte[] signerBlock = null; + try { + signerBlock = generateSignerBlock(signerConfig, contentDigests); + } catch (SignatureException e) { + throw new SignatureException("generate SignerBlock failed", e); + } + return signerBlock; + } + + private static byte[] generateSignerBlock( + SignerConfig signerConfig, Map contentDigests) throws SignatureException { + String mode = signerConfig.getOptions().getString(Options.MODE); + if (!("remoteSign".equalsIgnoreCase(mode))) { + if (signerConfig.getCertificates().isEmpty()) { + throw new SignatureException("No certificates configured for signer"); + } + } + + List> digests = + new ArrayList>(signerConfig.getSignatureAlgorithms().size()); + for (SignatureAlgorithm signatureAlgorithm : signerConfig.getSignatureAlgorithms()) { + ContentDigestAlgorithm contentDigestAlgorithm = signatureAlgorithm.getContentDigestAlgorithm(); + byte[] contentDigest = contentDigests.get(contentDigestAlgorithm); + if (contentDigest == null) { + throw new SignatureException( + contentDigestAlgorithm.getDigestAlgorithm() + + " content digest for " + + signatureAlgorithm.getSignatureAlgAndParams().getFirst() + + " not computed"); + } + digests.add(Pair.create(signatureAlgorithm.getId(), contentDigest)); + } + byte[] unsignedHapDigest = HapUtils.encodeListOfPairsToByteArray(digests); + return Pkcs7Generator.BC.generateSignedData(unsignedHapDigest, signerConfig); + } + + /** + * Signs the provided Hap using Hap Signature Scheme v2 and returns the + * signed block as an array of ByteBuffer + * + * @param contents Hap content before ZIP CD + * @param signerConfig signer config + * @param optionalBlocks optional blocks + * @return signed block + * @throws SignatureException if an error occurs when sign hap file. + */ + public static byte[] sign(ZipDataInput[] contents, SignerConfig signerConfig, List optionalBlocks) + throws SignatureException { + Set contentDigestAlgorithms = new HashSet(); + for (SignatureAlgorithm signatureAlgorithm : signerConfig.getSignatureAlgorithms()) { + contentDigestAlgorithms.add(signatureAlgorithm.getContentDigestAlgorithm()); + } + return getHapSigningBlock(contentDigestAlgorithms, optionalBlocks, signerConfig, contents); + } +} diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/sign/SignatureAlgorithm.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/sign/SignatureAlgorithm.java new file mode 100644 index 0000000000000000000000000000000000000000..5d26abe807f223d712129ceae5d825ae523ee441 --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/sign/SignatureAlgorithm.java @@ -0,0 +1,111 @@ +/* + * Copyright (c) 2021 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.hap.entity.Pair; + +import java.security.spec.AlgorithmParameterSpec; +import java.security.spec.MGF1ParameterSpec; +import java.security.spec.PSSParameterSpec; + +/** + * Signature algorithm + * + * @since 2021-12-13 + */ +public enum SignatureAlgorithm { + RSA_PSS_WITH_SHA256( + 0x101, + "RSA", + ContentDigestAlgorithm.SHA256, + Pair.create( + "SHA256withRSAANDMGF1", + new PSSParameterSpec("SHA-256", "MGF1", MGF1ParameterSpec.SHA256, 256 / 8, 1))), + RSA_PSS_WITH_SHA384( + 0x102, + "RSA", + ContentDigestAlgorithm.SHA384, + Pair.create( + "SHA384withRSAANDMGF1", + new PSSParameterSpec("SHA-384", "MGF1", MGF1ParameterSpec.SHA384, 384 / 8, 1))), + RSA_PSS_WITH_SHA512( + 0x103, + "RSA", + ContentDigestAlgorithm.SHA512, + Pair.create( + "SHA512withRSAANDMGF1", + new PSSParameterSpec("SHA-512", "MGF1", MGF1ParameterSpec.SHA512, 512 / 8, 1))), + RSA_PKCS1_V1_5_WITH_SHA256(0x104, "RSA", ContentDigestAlgorithm.SHA256, Pair.create("SHA256withRSA", null)), + RSA_PKCS1_V1_5_WITH_SHA384(0x105, "RSA", ContentDigestAlgorithm.SHA256, Pair.create("SHA384withRSA", null)), + RSA_PKCS1_V1_5_WITH_SHA512(0x106, "RSA", ContentDigestAlgorithm.SHA512, Pair.create("SHA512withRSA", null)), + ECDSA_WITH_SHA256(0x201, "EC", ContentDigestAlgorithm.SHA256, Pair.create("SHA256withECDSA", null)), + ECDSA_WITH_SHA384(0x202, "EC", ContentDigestAlgorithm.SHA384, Pair.create("SHA384withECDSA", null)), + ECDSA_WITH_SHA512(0x203, "EC", ContentDigestAlgorithm.SHA512, Pair.create("SHA512withECDSA", null)), + DSA_WITH_SHA256(0x301, "DSA", ContentDigestAlgorithm.SHA256, Pair.create("SHA256withDSA", null)), + DSA_WITH_SHA384(0x302, "DSA", ContentDigestAlgorithm.SHA384, Pair.create("SHA384withDSA", null)), + DSA_WITH_SHA512(0x303, "DSA", ContentDigestAlgorithm.SHA512, Pair.create("SHA512withDSA", null)); + + private int id; + + private String keyAlgorithm; + + private ContentDigestAlgorithm contentDigestAlgorithm; + + private Pair signatureAlgAndParams; + + SignatureAlgorithm( + int id, + String keyAlgorithm, + ContentDigestAlgorithm contentDigestAlgorithm, + Pair signatureAlgAndParams) { + this.id = id; + this.keyAlgorithm = keyAlgorithm; + this.contentDigestAlgorithm = contentDigestAlgorithm; + this.signatureAlgAndParams = signatureAlgAndParams; + } + + public int getId() { + return id; + } + + public String getKeyAlgorithm() { + return keyAlgorithm; + } + + public ContentDigestAlgorithm getContentDigestAlgorithm() { + return contentDigestAlgorithm; + } + + public Pair getSignatureAlgAndParams() { + return signatureAlgAndParams; + } + + /** + * Find value of SignatureAlgorithm according to ID + * + * @param id ID of SignatureAlgorithm object + * @return SignatureAlgorithm object + */ + public static SignatureAlgorithm findById(int id) { + SignatureAlgorithm ret = null; + for (SignatureAlgorithm alg : SignatureAlgorithm.values()) { + if (id == alg.getId()) { + return alg; + } + } + return ret; + } +} diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/verify/HapVerifyV2.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/verify/HapVerifyV2.java new file mode 100644 index 0000000000000000000000000000000000000000..6e5e87022c3876530a836d33c4e19bd972154523 --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/verify/HapVerifyV2.java @@ -0,0 +1,413 @@ +/* + * Copyright (c) 2021 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.hap.entity.SigningBlock; +import com.ohos.hapsigntool.hap.sign.ContentDigestAlgorithm; +import com.ohos.hapsigntool.hap.sign.SignatureAlgorithm; +import com.ohos.hapsigntool.utils.DigestUtils; +import com.ohos.hapsigntool.utils.HapUtils; +import com.ohos.hapsigntool.zip.ZipDataInput; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.bouncycastle.cert.X509CRLHolder; +import org.bouncycastle.cert.X509CertificateHolder; +import org.bouncycastle.cert.jcajce.JcaX509CRLConverter; +import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter; +import org.bouncycastle.cms.CMSException; +import org.bouncycastle.cms.CMSSignedData; +import org.bouncycastle.cms.SignerInformation; +import org.bouncycastle.cms.SignerInformationStore; +import org.bouncycastle.util.Store; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.security.DigestException; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +import java.security.PublicKey; +import java.security.SignatureException; +import java.security.cert.CRLException; +import java.security.cert.CertificateEncodingException; +import java.security.cert.CertificateException; +import java.security.cert.X509CRL; +import java.security.cert.X509CRLEntry; +import java.security.cert.X509Certificate; +import java.security.interfaces.DSAKey; +import java.security.interfaces.DSAParams; +import java.security.interfaces.ECKey; +import java.security.interfaces.RSAKey; +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; + +/** + * Class used to verify hap-file with signature v2 + * + * @since 2021/12/22 + */ +public class HapVerifyV2 { + private static final Logger LOGGER = LogManager.getLogger(HapVerifyV2.class); + + private ZipDataInput beforeApkSigningBlock; + + private ByteBuffer signatureSchemeBlock; + + private ZipDataInput centralDirectoryBlock; + + private ZipDataInput eocd; + + private List optionalBlocks; + + private Map digestMap = new HashMap(); + + private JcaX509CertificateConverter certificateConverter = new JcaX509CertificateConverter(); + + private JcaX509CRLConverter crlConverter = new JcaX509CRLConverter(); + + private boolean printCert; + + public HapVerifyV2( + ZipDataInput beforeApkSigningBlock, + ByteBuffer signatureSchemeBlock, + ZipDataInput centralDirectoryBlock, + ZipDataInput eocd, + List optionalBlocks) { + this.beforeApkSigningBlock = beforeApkSigningBlock; + this.signatureSchemeBlock = signatureSchemeBlock; + this.centralDirectoryBlock = centralDirectoryBlock; + this.eocd = eocd; + this.optionalBlocks = optionalBlocks; + } + + /** + * Verify hap signature v2. + * + * @return verify result. + */ + public VerifyResult verify() { + return parserSigner(signatureSchemeBlock); + } + + public void setPrintCert(boolean printCert) { + this.printCert = printCert; + } + + private boolean checkCRL(X509CRL crl, List certificates) { + boolean ret = false; + for (X509Certificate cert : certificates) { + if (!crl.getIssuerDN().getName().equals(cert.getIssuerDN().getName())) { + continue; + } + X509CRLEntry entry = crl.getRevokedCertificate(cert); + if (entry != null) { + LOGGER.info("cert(subject DN = {}) is revoked by crl (IssuerDN = {})", + cert.getSubjectDN().getName(), crl.getIssuerDN().getName()); + ret = false; + break; + } + ret = true; + } + return ret; + } + + private boolean verifyCRL(X509CRL crl, X509Certificate cert, List certificates) + throws SignatureException { + try { + crl.verify(cert.getPublicKey()); + return checkCRL(crl, certificates); + } catch (NoSuchAlgorithmException + | InvalidKeyException + | SignatureException + | CRLException + | NoSuchProviderException e) { + throw new SignatureException("crl verify failed.", e); + } + } + + private boolean verifyCRL(X509CRL crl, List certificates) throws SignatureException { + boolean revoked = true; + for (X509Certificate cert : certificates) { + if (!crl.getIssuerDN().getName().equals(cert.getSubjectDN().getName())) { + continue; + } + if (!verifyCRL(crl, cert, certificates)) { + revoked = false; + } + } + return revoked; + } + + private void verifyCRLs(List crls, List certificates) throws VerifyHapException { + if (crls == null) { + return; + } + boolean revoked = true; + try { + for (X509CRL crl : crls) { + if (!verifyCRL(crl, certificates)) { + revoked = false; + } + } + } catch (SignatureException e) { + throw new VerifyHapException("Verify CRL error!", e); + } + if (!revoked) { + throw new VerifyHapException("Certificate is revoked!"); + } + } + + private CMSSignedData verifyCmsSignedData(byte[] signingBlock) throws VerifyHapException { + try { + CMSSignedData cmsSignedData = new CMSSignedData(signingBlock); + boolean verifyResult = VerifyUtils.verifyCmsSignedData(cmsSignedData); + if (!verifyResult) { + throw new VerifyHapException("Verify PKCS7 cms data failed!"); + } + return cmsSignedData; + } catch (CMSException e) { + throw new VerifyHapException("Verify PKCS7 cms data error!", e); + } + } + + private VerifyResult parserSigner(ByteBuffer signer) { + byte[] signingBlock = new byte[signer.remaining()]; + signer.get(signingBlock); + try { + CMSSignedData cmsSignedData = verifyCmsSignedData(signingBlock); + List certificates = getCertChain(cmsSignedData); + List crlList = getCrlList(cmsSignedData); + verifyCRLs(crlList, certificates); + checkContentDigest(cmsSignedData); + List signerInfos = getSignerInformations(cmsSignedData); + VerifyResult result = new VerifyResult(true, VerifyResult.RET_SUCCESS, "Verify success"); + result.setCrls(crlList); + result.setCertificates(certificates); + result.setCertificateHolderStore(cmsSignedData.getCertificates()); + result.setSignerInfos(signerInfos); + result.setOptionalBlocks(optionalBlocks); + return result; + } catch (VerifyHapException e) { + LOGGER.error("Verify Hap error!", e); + return new VerifyResult(false, VerifyResult.RET_UNKNOWN_ERROR, e.getMessage()); + } + } + + private List getSignerInformations(CMSSignedData cmsSignedData) throws VerifyHapException { + SignerInformationStore signerInfos = cmsSignedData.getSignerInfos(); + int size = signerInfos.size(); + if (size <= 0) { + throw new VerifyHapException("PKCS7 cms data has no signer info, size: " + size); + } + Collection signers = signerInfos.getSigners(); + return new ArrayList<>(signers); + } + + private void checkContentDigest(CMSSignedData cmsSignedData) throws VerifyHapException { + Object content = cmsSignedData.getSignedContent().getContent(); + byte[] contentBytes = null; + if (content instanceof byte[]) { + contentBytes = (byte[]) content; + } else { + throw new VerifyHapException("PKCS cms content is not a byte array!"); + } + try { + boolean checkResult = parserContentinfo(contentBytes); + if (!checkResult) { + throw new VerifyHapException("Hap content digest check failed."); + } + } catch (DigestException | SignatureException | IOException e) { + throw new VerifyHapException("Check Hap content digest error!", e); + } + } + + private List getCertChain(CMSSignedData cmsSignedData) throws VerifyHapException { + Store certificates = cmsSignedData.getCertificates(); + try { + List certificateList = certStoreToCertList(certificates); + if (certificateList == null || certificateList.size() == 0) { + throw new VerifyHapException("Certificate chain is empty!"); + } + if (printCert) { + for (int i = 0; i < certificateList.size(); i++) { + LOGGER.info("+++++++++++++++++++++++++++certificate #{} +++++++++++++++++++++++++++++++", i); + printCert(certificateList.get(i)); + } + } + return certificateList; + } catch (CertificateException e) { + throw new VerifyHapException("Get certificate chain error!", e); + } + } + + private List getCrlList(CMSSignedData cmsSignedData) throws VerifyHapException { + Store crLs = cmsSignedData.getCRLs(); + if (crLs == null) { + return Collections.emptyList(); + } + Collection matches = crLs.getMatches(null); + if (matches == null || !matches.iterator().hasNext()) { + return Collections.emptyList(); + } + Iterator iterator = matches.iterator(); + List crlList = new ArrayList<>(); + try { + while (iterator.hasNext()) { + X509CRLHolder crlHolder = iterator.next(); + crlList.add(crlConverter.getCRL(crlHolder)); + } + } catch (CRLException e) { + throw new VerifyHapException("Get CRL error!", e); + } + return crlList; + } + + private List certStoreToCertList(Store certificates) + throws CertificateException { + if (certificates == null) { + return Collections.emptyList(); + } + Collection matches = certificates.getMatches(null); + if (matches == null || !matches.iterator().hasNext()) { + return Collections.emptyList(); + } + List certificateList = new ArrayList<>(); + Iterator iterator = matches.iterator(); + while (iterator.hasNext()) { + X509CertificateHolder next = iterator.next(); + certificateList.add(certificateConverter.getCertificate(next)); + } + return certificateList; + } + + 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) { + /** + * contentinfo format: + * int: version + * int: block number + * digest blocks: + * each digest block format: + * int: length of sizeof(digestblock) - 4 + * int: Algorithm ID + * int: length of digest + * byte[]: digest + */ + int signBlockVersion = digestDatas.getInt(); + int signBlockCount = digestDatas.getInt(); + LOGGER.info("version is: {}, number of block is: {}", signBlockVersion, signBlockCount); + int digestBlockLen = digestDatas.getInt(); + int signatureAlgId = digestDatas.getInt(); + int digestDatalen = digestDatas.getInt(); + if (digestBlockLen != digestDatalen + 8) { + throw new SignatureException("digestBlockLen: " + digestBlockLen + ", digestDatalen: " + digestDatalen); + } + ByteBuffer degestBuffer = HapUtils.sliceBuffer(digestDatas, digestDatalen); + byte[] degisetData = new byte[degestBuffer.remaining()]; + degestBuffer.get(degisetData); + SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.findById(signatureAlgId); + if (signatureAlgorithm == null) { + throw new SignatureException("Unsupported SignatureAlgorithm ID : " + signatureAlgId); + } + digestMap.put(signatureAlgorithm.getContentDigestAlgorithm(), degisetData); + } + + Set keySet = digestMap.keySet(); + Map actualDigestMap = HapUtils.computeDigests( + keySet, new ZipDataInput[]{beforeApkSigningBlock, centralDirectoryBlock, eocd}, optionalBlocks); + + 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; + LOGGER.error( + "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()); + } + return result; + } + + private void printCert(X509Certificate cert) throws CertificateEncodingException { + byte[] encodedCert = cert.getEncoded(); + + LOGGER.info("Subject: {}", cert.getSubjectX500Principal()); + LOGGER.info("Issuer: {}", cert.getIssuerX500Principal()); + LOGGER.info("SerialNumber: {}", cert.getSerialNumber().toString(16)); + LOGGER.info("Validity: {} ~ {}", formatDateTime(cert.getNotBefore()), formatDateTime(cert.getNotAfter())); + LOGGER.info("SHA256: {}", HapUtils.toHex(DigestUtils.sha256Digest(encodedCert), ":")); + LOGGER.info("Signature Algorithm: {}", cert.getSigAlgName()); + PublicKey publicKey = cert.getPublicKey(); + LOGGER.info("Key: {}, key length: {} bits", publicKey.getAlgorithm(), getKeySize(publicKey)); + LOGGER.info("Cert Version: V{}", cert.getVersion()); + } + + private int getKeySize(PublicKey publicKey) { + int result = -1; + if (publicKey instanceof RSAKey) { + result = ((RSAKey) publicKey).getModulus().bitLength(); + } + if (publicKey instanceof ECKey) { + result = ((ECKey) publicKey).getParams().getOrder().bitLength(); + } + if (publicKey instanceof DSAKey) { + DSAParams dsaParams = ((DSAKey) publicKey).getParams(); + if (dsaParams != null) { + result = dsaParams.getP().bitLength(); + } + } + return result; + } + + private String formatDateTime(Date date) { + if (date != null) { + DateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + return format.format(date); + } + return ""; + } + + private static class VerifyHapException extends Exception { + VerifyHapException(String message) { + super(message); + } + + VerifyHapException(String message, Throwable cause) { + super(message, cause); + } + } +} diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/verify/VerifyAndParseProvision.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/verify/VerifyAndParseProvision.java new file mode 100644 index 0000000000000000000000000000000000000000..583de6e8e99ea015e37814cbb4dbada3b1fb1004 --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/verify/VerifyAndParseProvision.java @@ -0,0 +1,120 @@ +/* + * Copyright (c) 2021 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.utils.FileUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.bouncycastle.cms.CMSException; +import org.bouncycastle.cms.CMSSignedData; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.security.SignatureException; + +/** + * verify signatures of provision and output unsigned provision + * + * @since 2021/12/21 + */ +public class VerifyAndParseProvision { + private static final Logger LOGGER = LogManager.getLogger(VerifyAndParseProvision.class); + + /** + * this function verify signatures of provision and output unsigned provision + * + * @param signedProvisionPath the path of the inputted signedProvision file + * @param unsignedProvisionPath the path which the caller wants to output unsignedProvision file + * @return true indicates verify and parse provision file successfully, + * false indicates something wrong is happened. + */ + public boolean verifyAndParseProvision(String signedProvisionPath, String unsignedProvisionPath) { + File signedProvisionFile = new File(signedProvisionPath); + try { + if (!checkProvisionFile(signedProvisionFile)) { + String errorMsg = "Check input provision file false!"; + LOGGER.error(errorMsg); + throw new IOException(); + } + byte[] signedProvisionData = getSignedProvisionData(signedProvisionFile); + if (signedProvisionData.length == 0) { + LOGGER.error("read provision file failed"); + throw new IOException(); + } + byte[] unsignedProvisionData = getUnsignedProvisionData(signedProvisionData); + if (unsignedProvisionData == null) { + LOGGER.error("get unsigned provision failed"); + throw new IOException(); + } + return outputUnsignedProvisionToFile(unsignedProvisionData, unsignedProvisionPath); + } catch (IOException e) { + return false; + } + } + + private boolean checkProvisionFile(File signedProvisionFile) { + try { + if (!signedProvisionFile.canRead()) { + LOGGER.error(signedProvisionFile.getCanonicalPath() + " does not exist or can not read!"); + throw new IOException(); + } + if (!signedProvisionFile.isFile()) { + LOGGER.error(signedProvisionFile.getCanonicalPath() + " is not a file!"); + throw new IOException(); + } + } catch (IOException e) { + LOGGER.error("getCanonicalPath failed", e); + return false; + } + return true; + } + + private byte[] getSignedProvisionData(File signedProvisionFile) { + byte[] signedProvisionData = new byte[0]; + try { + signedProvisionData = FileUtils.readFileToByteArray(signedProvisionFile); + } catch (IOException e) { + LOGGER.error("readFileToByteArray failed.", e); + } + return signedProvisionData; + } + + private byte[] getUnsignedProvisionData(byte[] signedProvisionData) { + byte[] unsignedProvisionData = null; + try { + CMSSignedData cmsSignedData = new CMSSignedData(signedProvisionData); + if (!VerifyUtils.verifyCmsSignedData(cmsSignedData)) { + throw new SignatureException("PKCS7 cms data verify faild!"); + } + unsignedProvisionData = (byte[]) cmsSignedData.getSignedContent().getContent(); + } catch (SignatureException | CMSException e) { + LOGGER.error("get unsigned provision data failed.", e); + } + return unsignedProvisionData; + } + + private boolean outputUnsignedProvisionToFile(byte[] unsignedProvisionData, String unsignedProvisionPath) { + boolean ret = false; + try (FileOutputStream unsignedProvisionOutputStream = new FileOutputStream(new File(unsignedProvisionPath))) { + unsignedProvisionOutputStream.write(unsignedProvisionData); + ret = true; + } catch (IOException e) { + LOGGER.error("output unsigned provision to file failed.", e); + } + return ret; + } +} 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 new file mode 100644 index 0000000000000000000000000000000000000000..7f43fde0633350162bd2282053d448bab8b83477 --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/verify/VerifyHap.java @@ -0,0 +1,323 @@ +/* + * Copyright (c) 2021 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.hap.entity.Pair; +import com.ohos.hapsigntool.hap.entity.SigningBlock; +import com.ohos.hapsigntool.hap.exception.HapFormatException; +import com.ohos.hapsigntool.hap.exception.SignatureNotFoundException; +import com.ohos.hapsigntool.utils.FileUtils; +import com.ohos.hapsigntool.utils.HapUtils; +import com.ohos.hapsigntool.utils.ParamConstants; +import com.ohos.hapsigntool.utils.ParamProcessUtil; +import com.ohos.hapsigntool.utils.StringUtils; +import com.ohos.hapsigntool.zip.ByteBufferZipDataInput; +import com.ohos.hapsigntool.zip.RandomAccessFileZipDataInput; +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.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.security.Security; +import java.security.cert.X509Certificate; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Class of verify hap. + * + * @2021/12/23 + */ +public class VerifyHap { + private static final Logger LOGGER = LogManager.getLogger(VerifyHap.class); + private static final int ZIP_HEAD_OF_SIGNING_BLOCK_LENGTH = 32; + 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; + + public VerifyHap() { + this(true); + } + + public VerifyHap(boolean printCert) { + this.printCert = printCert; + } + + static { + Security.addProvider(new BouncyCastleProvider()); + } + + /** + * Check whether parameters are valid + * + * @param options input parameters used to verify hap. + * @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 hap file. + * + * @param options input parameters used to verify hap. + * @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); + String outputCertPath = options.getString(ParamConstants.PARAM_VERIFY_CERTCHAIN_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 hap false!"); + throw new IOException(); + } + verifyResult = verifyHap(filePath); + if (!verifyResult.getResult()) { + LOGGER.error("verify: {}", verifyResult.getMessage()); + throw new IOException(); + } + + 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); + String outputProofFile = options.getString(ParamConstants.PARAM_VERIFY_PROOF_FILE); + String outputPropertyFile = options.getString(ParamConstants.PARAM_VERIFY_PROPERTY_FILE); + try { + outputOptionalBlocks(outputProfileFile, outputProofFile, outputPropertyFile, 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, String outputProofFile, String outputPropertyFile, + VerifyResult verifyResult) throws IOException { + List optionalBlocks = verifyResult.getOptionalBlocks(); + if (optionalBlocks != null && optionalBlocks.size() > 0) { + for (SigningBlock optionalBlock : optionalBlocks) { + int type = optionalBlock.getType(); + switch (type) { + case HapUtils.HAP_PROFILE_BLOCK_ID: + writeOptionalBytesToFile(optionalBlock.getValue(), outputProfileFile); + break; + case HapUtils.HAP_PROOF_OF_ROTATION_BLOCK_ID: + writeOptionalBytesToFile(optionalBlock.getValue(), outputProofFile); + break; + case HapUtils.HAP_PROPERTY_BLOCK_ID: + writeOptionalBytesToFile(optionalBlock.getValue(), outputPropertyFile); + break; + default: + throw new IOException("Unsupported Block Id: 0x" + Long.toHexString(type)); + } + } + } + } + + private void writeOptionalBytesToFile(byte[] data, String outputFile) throws IOException { + if (outputFile == null || outputFile.isEmpty()) { + return; + } + try (OutputStream out = new FileOutputStream(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 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.getResult()) { + 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. + * + * @param hapFilePath path of hap file. + * @return true, if verify successfully. + */ + public VerifyResult verifyHap(String hapFilePath) { + VerifyResult result; + try (RandomAccessFile fle = new RandomAccessFile(hapFilePath, "r")) { + ZipDataInput hapFile = new RandomAccessFileZipDataInput(fle); + ZipFileInfo zipInfo = ZipUtils.findZipInfo(hapFile); + long eocdOffset = zipInfo.getEocdOffset(); + if (ZipUtils.checkZip64EoCDLocatorIsPresent(hapFile, eocdOffset)) { + String errorMsg = "ZIP64 format not supported!"; + LOGGER.error(errorMsg); + return new VerifyResult(false, VerifyResult.RET_UNSUPPORTED_FORMAT_ERROR, errorMsg); + } + Pair hapSigningBlockAndOffsetInFile = HapUtils.findHapSigningBlock(hapFile, zipInfo); + ByteBuffer signingBlock = hapSigningBlockAndOffsetInFile.getSecond(); + signingBlock.order(ByteOrder.LITTLE_ENDIAN); + Pair> blockPair = getHapSignatureSchemeBlockAndOptionalBlocks(signingBlock); + ByteBuffer signatureSchemeBlock = blockPair.getFirst(); + List optionalBlocks = blockPair.getSecond(); + Collections.reverse(optionalBlocks); + long signingBlockOffset = hapSigningBlockAndOffsetInFile.getFirst(); + 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); + HapVerifyV2 verifyEngine = new HapVerifyV2(beforeHapSigningBlock, signatureSchemeBlock, + centralDirectoryBlock, eocdBlock, optionalBlocks); + verifyEngine.setPrintCert(printCert); + result = verifyEngine.verify(); + } catch (IOException e) { + LOGGER.error("Verify Hap has IO error!", e); + result = new VerifyResult(false, VerifyResult.RET_IO_ERROR, e.getMessage()); + } catch (SignatureNotFoundException e) { + LOGGER.error("Verify Hap failed, signature not found.", e); + result = new VerifyResult(false, VerifyResult.RET_SIGNATURE_NOT_FOUND_ERROR, e.getMessage()); + } catch (HapFormatException e) { + LOGGER.error("Verify Hap failed, unsupported format hap.", e); + result = new VerifyResult(false, VerifyResult.RET_UNSUPPORTED_FORMAT_ERROR, e.getMessage()); + } + return result; + } + + private Pair> getHapSignatureSchemeBlockAndOptionalBlocks(ByteBuffer hapSigningBlock) + throws SignatureNotFoundException { + try { + ByteBuffer header = HapUtils.reverseSliceBuffer( + 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); + + byte[] signatureValueBytes = new byte[value.capacity()]; + value.get(signatureValueBytes, 0, signatureValueBytes.length); + signatureValueBytes = Arrays.reverse(signatureValueBytes); + header.position(ZIP_HEAD_OF_SIGNING_BLOCK_COUNT_OFFSET_REVERSE); // position to the block count offset + int blockCount = header.getInt(); + int current = value.capacity() - ZIP_HEAD_OF_SUBSIGNING_BLOCK_LENGTH * blockCount; + value.position(current); + + int blockType = -1; + int blockLength = -1; + int blockOffset = -1; + ByteBuffer hapSigningPkcs7Block = null; + List optionalBlocks = new ArrayList(); + for (int i = 0; i < blockCount; i++) { + blockOffset = value.getInt(); + 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); + } + if (HapUtils.getHapSignatureOptionalBlockIds().contains(blockType)) { + byte[] blockValue = Arrays.copyOfRange(signatureValueBytes, blockOffset, blockOffset + blockLength); + optionalBlocks.add(new SigningBlock(blockType, blockValue)); + } + if (blockType == HapUtils.HAP_SIGNATURE_SCHEME_V1_BLOCK_ID) { + byte[] result = Arrays.copyOfRange(signatureValueBytes, blockOffset, blockOffset + blockLength); + hapSigningPkcs7Block = ByteBuffer.wrap(result); + } + } + return Pair.create(hapSigningPkcs7Block, optionalBlocks); + } finally { + hapSigningBlock.clear(); + } + } +} \ No newline at end of file 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 new file mode 100644 index 0000000000000000000000000000000000000000..640cbad4cb192e2991793a15baf1de23de531166 --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/verify/VerifyResult.java @@ -0,0 +1,179 @@ +/* + * Copyright (c) 2021 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.hap.entity.SigningBlock; +import org.bouncycastle.cert.X509CertificateHolder; +import org.bouncycastle.cms.SignerInformation; +import org.bouncycastle.util.Store; + +import java.security.cert.X509CRL; +import java.security.cert.X509Certificate; +import java.util.List; + +/** + * Indicate result of verify hap. + * + * @since 2021/12/22 + */ +public class VerifyResult { + /** + * Return code of verify success. + */ + public static final int RET_SUCCESS = 10000; + + /** + * Return code of unknown error. + */ + public static final int RET_UNKNOWN_ERROR = 10001; + + /** + * Return code of IO error. + */ + public static final int RET_IO_ERROR = 10002; + + /** + * Return code of file format error. + */ + public static final int RET_UNSUPPORTED_FORMAT_ERROR = 10003; + + /** + * Return code of file not found error. + */ + public static final int RET_FILE_NOT_FOUND_ERROR = 10004; + + /** + * Return code of encoding certificates errors. + */ + public static final int RET_CERTIFICATE_ENCODING_ERROR = 10005; + + /** + * Return code of unsupported algorithm error. + */ + public static final int RET_UNSUPPORTED_ALGORITHM_ERROR = 10006; + + /** + * Return code of digest error. + */ + public static final int RET_DIGEST_ERROR = 10007; + + /** + * Return code of signatures not found error. + */ + public static final int RET_SIGNATURE_NOT_FOUND_ERROR = 10008; + + /** + * Return code of signatures verify error. + */ + public static final int RET_SIGNATURE_ERROR = 10009; + + /** + * Return code of certificate revoked error. + */ + public static final int RET_CERTIFICATE_REVOKED = 10010; + + /** + * Return code of certificate revoked error. + */ + public static final int RET_CRL_ERROR = 10011; + + private boolean result; + private int code; + private String message; + + private List certificates; + + private List crls; + + private List signerInfos; + + private List optionalBlocks; + + private Store certificateHolderStore; + + public VerifyResult() { + } + + public VerifyResult(boolean result, int code, String message) { + this.result = result; + this.code = code; + this.message = message; + } + + public boolean getResult() { + return result; + } + + public void setResult(boolean result) { + this.result = result; + } + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public List getCertificates() { + return certificates; + } + + public void setCertificates(List certificates) { + this.certificates = certificates; + } + + public List getCrls() { + return crls; + } + + public void setCrls(List crls) { + this.crls = crls; + } + + public List getSignerInfos() { + return signerInfos; + } + + public void setSignerInfos(List signerInfos) { + this.signerInfos = signerInfos; + } + + public List getOptionalBlocks() { + return optionalBlocks; + } + + public void setOptionalBlocks(List optionalBlocks) { + this.optionalBlocks = optionalBlocks; + } + + public Store getCertificateHolderStore() { + return certificateHolderStore; + } + + public void setCertificateHolderStore(Store certificateHolderStore) { + this.certificateHolderStore = certificateHolderStore; + } +} diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/verify/VerifyUtils.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/verify/VerifyUtils.java new file mode 100644 index 0000000000000000000000000000000000000000..939efab86d5318835eabe6d6499d062641564dbf --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/verify/VerifyUtils.java @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2021 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 org.bouncycastle.cert.X509CertificateHolder; +import org.bouncycastle.cms.CMSException; +import org.bouncycastle.cms.CMSSignedData; +import org.bouncycastle.cms.jcajce.JcaSimpleSignerInfoVerifierBuilder; +import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.bouncycastle.operator.OperatorCreationException; +import org.bouncycastle.util.Store; + +import java.security.Provider; +import java.security.Security; +import java.security.cert.CertificateException; +import java.util.Collection; + +/** + * Utils for verify CMS signed data + * + * @since 2021/12/20 + */ +public class VerifyUtils { + static { + Provider bc = Security.getProvider("BC"); + if (bc == null) { + Security.addProvider(new BouncyCastleProvider()); + } + } + + /** + * Verify cms signed data + * + * @param cmsSignedData cms signed data + * @return true, if verify success + * @throws CMSException if error occurs + */ + public static boolean verifyCmsSignedData(CMSSignedData cmsSignedData) throws CMSException { + Store certs = cmsSignedData.getCertificates(); + boolean verifyResult = cmsSignedData.verifySignatures(sid -> { + try { + Collection collection = certs.getMatches(sid); + if ((collection == null) || (collection.size() != 1)) { + throw new OperatorCreationException( + "No matched cert or more than one matched certs: " + collection); + } + X509CertificateHolder cert = collection.iterator().next(); + return new JcaSimpleSignerInfoVerifierBuilder().setProvider("BC").build(cert); + } catch (CertificateException e) { + throw new OperatorCreationException("Failed to verify BC signatures: " + e.getMessage(), e); + } + }); + return verifyResult; + } +} diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/key/KeyPairTools.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/key/KeyPairTools.java new file mode 100644 index 0000000000000000000000000000000000000000..285795a24df97274be569989478d5a41d0fa3ee4 --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/key/KeyPairTools.java @@ -0,0 +1,138 @@ +/* + * Copyright (c) 2021-2022 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.key; + +import com.ohos.hapsigntool.api.ServiceApi; +import com.ohos.hapsigntool.error.CustomException; +import com.ohos.hapsigntool.error.ERROR; +import com.ohos.hapsigntool.utils.ValidateUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.bouncycastle.util.encoders.Base64; + +import java.security.Key; +import java.security.KeyFactory; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.NoSuchAlgorithmException; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.X509EncodedKeySpec; + +/** + * Key pair relation Class, to create new key pairs. + * + * @since 2021/12/28 + */ +public final class KeyPairTools { + private KeyPairTools() { + } + + /** + * Field RSA. + */ + public static final String RSA = "RSA"; + /** + * Field EC. + */ + public static final String ECC = "EC"; + /** + * Field ECC. + */ + public static final String ECC_INPUT = "ECC"; + /** + * Field RSA_2048. + */ + public static final int RSA_2048 = 2048; + /** + * Field RSA_3072. + */ + public static final int RSA_3072 = 3072; + /** + * Field RSA_4096. + */ + public static final int RSA_4096 = 4096; + /** + * Field NIST_P_256. + */ + public static final int NIST_P_256 = 256; + /** + * Field NIST_P_384. + */ + public static final int NIST_P_384 = 384; + /** + * Logger. + */ + private static final Logger LOGGER = LogManager.getLogger(ServiceApi.class); + + + /** + * @param algorithm RSA/ECC + * @param keySize RSA_2048/3072/4096 NIST_P_256/384 + * @return Generated keypair + */ + public static KeyPair generateKeyPair(String algorithm, int keySize) { + if (algorithm.equalsIgnoreCase(ECC_INPUT)) { + algorithm = ECC; + } + if (algorithm.equalsIgnoreCase(RSA)) { + ValidateUtils.throwIfNotMatches((keySize == RSA_2048 || keySize == RSA_3072 || keySize == RSA_4096), + ERROR.NOT_SUPPORT_ERROR, "Algorithm 'RSA' not support size: " + keySize); + } else if (algorithm.equalsIgnoreCase(ECC)) { + ValidateUtils.throwIfNotMatches((keySize == NIST_P_256 || keySize == NIST_P_384), + ERROR.NOT_SUPPORT_ERROR, "Algorithm 'ECC' not support size: " + keySize); + } else { + CustomException.throwException(ERROR.NOT_SUPPORT_ERROR, "Not support algorithm: " + algorithm); + } + + try { + KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(algorithm); + keyPairGenerator.initialize(keySize); + return keyPairGenerator.generateKeyPair(); + } catch (NoSuchAlgorithmException e) { + LOGGER.debug(e.getMessage(), e); + CustomException.throwException(ERROR.NOT_SUPPORT_ERROR, e.getMessage()); + return null; + } + } + + /** + * Convert key to String + * + * @param key input parameter and key can not be null. + * @return return key.getEncoded() in Base64 format. + */ + public static String key2String(Key key) { + return Base64.toBase64String(key.getEncoded()); + } + + /** + * Convert string back to key + * + * @param algorithm input parameter and algorithm can not be null. + * @param keyString input parameter and keyString can not be null. + * @return return x509EncodedKeySpec. + */ + public static Key string2Key(String algorithm, String keyString) { + X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(Base64.decode(keyString)); + try { + return KeyFactory.getInstance(algorithm).generatePublic(x509EncodedKeySpec); + } catch (InvalidKeySpecException | NoSuchAlgorithmException exception) { + LOGGER.debug(exception.getMessage(), exception); + CustomException.throwException(ERROR.ACCESS_ERROR, exception.getMessage()); + return null; + } + } +} diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/keystore/KeyStoreHelper.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/keystore/KeyStoreHelper.java new file mode 100644 index 0000000000000000000000000000000000000000..c9f49a4ce201123225bfede74ace77d802813cf5 --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/keystore/KeyStoreHelper.java @@ -0,0 +1,334 @@ +/* + * Copyright (c) 2021-2022 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.keystore; + +import com.ohos.hapsigntool.cert.CertTools; +import com.ohos.hapsigntool.error.CustomException; +import com.ohos.hapsigntool.error.ERROR; +import com.ohos.hapsigntool.utils.CertUtils; +import com.ohos.hapsigntool.utils.FileUtils; +import com.ohos.hapsigntool.utils.StringUtils; +import com.ohos.hapsigntool.utils.ValidateUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.bouncycastle.asn1.x500.X500NameBuilder; +import org.bouncycastle.asn1.x500.style.BCStyle; +import org.bouncycastle.cert.X509v3CertificateBuilder; +import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter; +import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder; +import org.bouncycastle.operator.ContentSigner; + +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.security.KeyPair; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.security.UnrecoverableKeyException; +import java.security.cert.Certificate; +import java.security.cert.CertificateException; +import java.security.cert.CertificateExpiredException; +import java.security.cert.CertificateNotYetValidException; +import java.security.cert.X509Certificate; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + + +/** + * Read and save Keypair and certificate. + * + * @since 2021/12/28 + */ +public class KeyStoreHelper { + /** + * Field KEYSTORE_TYPE_PKCS12. + */ + private static final String KEYSTORE_TYPE_PKCS12 = "pkcs12"; + /** + * Field KEYSTORE_TYPE_JKS. + */ + private static final String KEYSTORE_TYPE_JKS = "jks"; + /** + * Field FILE_TYPE_JKS. + */ + private static final String FILE_TYPE_JKS = "jks"; + /** + * Field FILE_TYPE_PKCS12. + */ + private static final String FILE_TYPE_PKCS12 = "p12"; + /** + * Field number 100. + */ + private static final int NUM_ONE_HUNDRED = 100; + /** + * Use LogManager to show log instead of use "System.out.print" to show log. + */ + private final Logger logger = LogManager.getLogger(KeyStoreHelper.class); + /** + * Field keyStorePath. + */ + private final String keyStorePath; + /** + * Field keyStorePwd. + */ + private final char[] keyStorePwd; + /** + * Field keyStore. + */ + private final KeyStore keyStore; + + /** + * Helper to load and save pair. + * + * @param keyStorePath File path + * @param storePwd passwd of key store + */ + public KeyStoreHelper(String keyStorePath, char[] storePwd) { + ValidateUtils.throwIfMatches(StringUtils.isEmpty(keyStorePath), ERROR.COMMAND_ERROR, + "Missed params: 'keyStorePath'"); + if (storePwd == null) { + storePwd = new char[0]; + } + this.keyStorePwd = storePwd; + this.keyStorePath = keyStorePath; + this.keyStore = createKeyStoreAccordingFileType(keyStorePath); + try { + if (FileUtils.isFileExist(keyStorePath)) { + logger.info(keyStorePath + " is exist. Try to load it with given passwd"); + FileInputStream fis = new FileInputStream(keyStorePath); + keyStore.load(fis, storePwd); + FileUtils.close(fis); + } else { + keyStore.load(null, null); + } + } catch (Exception exception) { + logger.debug(exception.getMessage(), exception); + CustomException.throwException(ERROR.ACCESS_ERROR, exception.getMessage()); + } + } + + /** + * Throw exception if alias exist in keystore. + * + * @param alias alias of key + */ + public void errorOnExist(String alias) { + ValidateUtils.throwIfMatches(this.hasAlias(alias), ERROR.ACCESS_ERROR, + String.format("Could not overwrite! Already exist '%s' in %s", alias, this.keyStorePath)); + } + + /** + * Throw exception if alias no exist in keystore. + * + * @param alias alias of key + */ + public void errorIfNotExist(String alias) { + ValidateUtils.throwIfNotMatches(this.hasAlias(alias), ERROR.FILE_NOT_FOUND, + String.format("Not exist '%s' in %s", alias, this.keyStorePath)); + } + + /** + * Check if keystore contain the alias. + * + * @param alias key alias + * @return result + */ + public boolean hasAlias(String alias) { + try { + return keyStore.containsAlias(alias); + } catch (KeyStoreException exception) { + logger.debug(exception.getMessage(), exception); + CustomException.throwException(ERROR.ACCESS_ERROR, exception.getMessage()); + return false; + } + } + + /** + * Load keypair form keystore. + * + * @param alias alias + * @param certPwd key pwd + * @return Keypair + */ + public KeyPair loadKeyPair(String alias, char[] certPwd) { + List certificates = loadCertificates(alias); + PrivateKey privateKey = loadPrivateKey(alias, certPwd); + return new KeyPair(certificates.get(0).getPublicKey(), privateKey); + } + + /** + * Get private key from give key store + * + * @param alias Cert alias + * @param certPwd Cert pwd + * @return private key + */ + public PrivateKey loadPrivateKey(String alias, char[] certPwd) { + if (certPwd == null) { + certPwd = new char[0]; + } + try { + return (PrivateKey) keyStore.getKey(alias, certPwd); + } catch (KeyStoreException | NoSuchAlgorithmException exception) { + logger.debug(exception.getMessage(), exception); + CustomException.throwException(ERROR.ACCESS_ERROR, exception.getMessage()); + } catch (UnrecoverableKeyException exception) { + logger.debug(exception.getMessage(), exception); + CustomException.throwException(ERROR.ACCESS_ERROR, "Password error of '" + alias + "'"); + } + return null; + } + + /** + * Validate the cert and save into cert list. + * + * @param certificates Result list to save + * @param certificate Cert to validate + */ + private void putValidCert(List certificates, Certificate certificate) { + if (!(certificate instanceof X509Certificate)) { + return; + } + X509Certificate cert = (X509Certificate) certificate; + try { + cert.checkValidity(); + certificates.add(cert); + } catch (CertificateExpiredException | CertificateNotYetValidException exception) { + logger.debug(exception.getMessage(), exception); + } + } + + /** + * Get certificates from keystore. + * + * @param alias cert alias + * @return certificates of alias + */ + public List loadCertificates(String alias) { + ValidateUtils.throwIfNotMatches(this.hasAlias(alias), ERROR.NOT_SUPPORT_ERROR, + String.format("Not found '%s' in %s", alias, this.keyStorePath)); + + List certificates = new ArrayList<>(); + try { + if (keyStore.isKeyEntry(alias)) { + Certificate[] certs = keyStore.getCertificateChain(alias); + for (Certificate certificate : certs) { + putValidCert(certificates, certificate); + } + } else { + if (keyStore.isCertificateEntry(alias)) { + Certificate cert = keyStore.getCertificate(alias); + putValidCert(certificates, cert); + } + } + } catch (KeyStoreException exception) { + logger.debug(exception.getMessage(), exception); + CustomException.throwException(ERROR.NOT_SUPPORT_ERROR, exception.getMessage()); + } + + ValidateUtils.throwIfNotMatches(certificates.size() > 0, ERROR.ACCESS_ERROR, + "No usable cert found in " + this.keyStorePath); + return certificates; + } + + /** + * Create simple keystore file. + * Exist if file exist. + * + * @param alias cert alias + * @param keyPwd password of String format + * @param keyPair keypair to save + * @param certs will create one if null + * @return Boolean value. + */ + public boolean store(String alias, char[] keyPwd, KeyPair keyPair, X509Certificate[] certs) { + errorOnExist(alias); + if (keyPwd == null) { + keyPwd = new char[0]; + } + if (certs == null) { + X509Certificate certificate = createKeyOnly(keyPair, alias); + certs = new X509Certificate[]{certificate}; + } + try { + keyStore.setKeyEntry(alias, keyPair.getPrivate(), keyPwd, certs); + FileOutputStream fos = new FileOutputStream(keyStorePath); + keyStore.store(fos, keyStorePwd); + fos.flush(); + fos.close(); + } catch (Exception exception) { + logger.debug(exception.getMessage(), exception); + CustomException.throwException(ERROR.WRITE_FILE_ERROR, exception.getMessage()); + return false; + } + return true; + } + + private X509Certificate createKeyOnly(KeyPair keyPair, String alias) { + ContentSigner contentSigner = CertTools.createFixedContentSigner(keyPair.getPrivate(), "SHA256withRSA"); + + X500NameBuilder nameBuilder = new X500NameBuilder(BCStyle.INSTANCE); + nameBuilder.addRDN(BCStyle.CN, alias); + + LocalDateTime notBefore = LocalDateTime.now(); + LocalDateTime notAfter = notBefore.plusYears(NUM_ONE_HUNDRED); + + X509v3CertificateBuilder certificateBuilder = new JcaX509v3CertificateBuilder( + nameBuilder.build(), + CertUtils.randomSerial(), + Date.from(notBefore.atZone(ZoneId.systemDefault()).toInstant()), + Date.from(notAfter.atZone(ZoneId.systemDefault()).toInstant()), + nameBuilder.build(), + keyPair.getPublic() + ); + try { + return new JcaX509CertificateConverter().setProvider("BC") + .getCertificate(certificateBuilder.build(contentSigner)); + } catch (CertificateException exception) { + logger.debug(exception.getMessage(), exception); + CustomException.throwException(ERROR.NOT_SUPPORT_ERROR, exception.getMessage()); + return null; + } + } + + /** + * Auto create jks/pkcs12 keystore according file type + * + * @param keyFile keystore file path + * @return keystore instance + */ + private KeyStore createKeyStoreAccordingFileType(String keyFile) { + KeyStore typeKeyStore = null; + String type = FileUtils.getSuffix(keyFile); + try { + if (type.equalsIgnoreCase(FILE_TYPE_PKCS12)) { + typeKeyStore = KeyStore.getInstance(KEYSTORE_TYPE_PKCS12); + } else if (type.equalsIgnoreCase(FILE_TYPE_JKS)) { + typeKeyStore = KeyStore.getInstance(KEYSTORE_TYPE_JKS); + } else { + typeKeyStore = KeyStore.getInstance(KeyStore.getDefaultType()); + } + } catch (KeyStoreException exception) { + logger.debug(exception.getMessage(), exception); + CustomException.throwException(ERROR.NOT_SUPPORT_ERROR, exception.getMessage()); + } + return typeKeyStore; + } +} 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 new file mode 100644 index 0000000000000000000000000000000000000000..0b896afb468bdcb6b940d06963df056d8d268c2d --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/profile/IProvisionVerifier.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2021-2022 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.profile; + +import com.ohos.hapsigntool.profile.model.VerificationResult; + +/** + * IProvisionVerifier. + * + * @since 2021/12/28 + */ +@FunctionalInterface +public interface IProvisionVerifier { + + /** + * verify p7b content. + * + * @param p7b signed p7b content + * @return result + */ + VerificationResult verify(byte[] p7b); +} 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 new file mode 100644 index 0000000000000000000000000000000000000000..a646be9a0e9d7911aa2e3bc9fba6dc9277d533cc --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/profile/ProfileSignTool.java @@ -0,0 +1,194 @@ +/* + * Copyright (c) 2021-2022 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.profile; + +import com.ohos.hapsigntool.api.LocalizationAdapter; +import com.ohos.hapsigntool.error.CustomException; +import com.ohos.hapsigntool.error.ERROR; +import com.ohos.hapsigntool.profile.model.Provision; +import com.ohos.hapsigntool.signer.ISigner; +import com.ohos.hapsigntool.signer.SignerFactory; +import com.ohos.hapsigntool.utils.FileUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.bouncycastle.asn1.ASN1EncodableVector; +import org.bouncycastle.asn1.ASN1Set; +import org.bouncycastle.asn1.BERSet; +import org.bouncycastle.asn1.DEROctetString; +import org.bouncycastle.asn1.DERSet; +import org.bouncycastle.asn1.cms.Attribute; +import org.bouncycastle.asn1.cms.CMSObjectIdentifiers; +import org.bouncycastle.asn1.cms.ContentInfo; +import org.bouncycastle.asn1.cms.IssuerAndSerialNumber; +import org.bouncycastle.asn1.cms.SignedData; +import org.bouncycastle.asn1.cms.SignerIdentifier; +import org.bouncycastle.asn1.cms.SignerInfo; +import org.bouncycastle.asn1.cms.Time; +import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers; +import org.bouncycastle.asn1.x509.AlgorithmIdentifier; +import org.bouncycastle.cert.jcajce.JcaX509CRLHolder; +import org.bouncycastle.cert.jcajce.JcaX509CertificateHolder; +import org.bouncycastle.operator.DefaultDigestAlgorithmIdentifierFinder; +import org.bouncycastle.operator.DefaultSignatureAlgorithmIdentifierFinder; +import org.bouncycastle.operator.DigestCalculator; +import org.bouncycastle.operator.DigestCalculatorProvider; +import org.bouncycastle.operator.OperatorCreationException; +import org.bouncycastle.operator.jcajce.JcaDigestCalculatorProviderBuilder; + +import java.io.File; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.security.cert.CRLException; +import java.security.cert.CertificateEncodingException; +import java.security.cert.X509CRL; +import java.security.cert.X509Certificate; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.util.Date; +import java.util.List; + +/** + * To sign and verify profile. + * + * @since 2021/12/28 + */ +public final class ProfileSignTool { + /** + * Empty byte array. + */ + private static final byte[] NO_BYTE = {}; + /** + * logger + */ + private static final Logger LOGGER = LogManager.getLogger(ProfileSignTool.class); + + private ProfileSignTool() { + } + + /** + * generateP7b. + * + * @param adapter local adapter with params + * @param content content to sign + * @return signed content + */ + public static byte[] generateP7b(LocalizationAdapter adapter, byte[] content) { + ISigner signer = new SignerFactory().getSigner(adapter); + return signProfile(content, signer, adapter.getSignAlg()); + } + + /** + * Get provision content. + * + * @param input input provision profile + * @return file data + */ + public static byte[] getProvisionContent(File input) throws IOException { + byte[] bytes = FileUtils.readFile(input); + Provision provision = FileUtils.GSON.fromJson(new String(bytes, StandardCharsets.UTF_8), Provision.class); + Provision.enforceValid(provision); + return FileUtils.GSON.toJson(provision).getBytes(StandardCharsets.UTF_8); + } + + /** + * signProfile. + * + * @param content content to sign + * @param signer signer + * @param sigAlg sign algorithm + * @return signed data + */ + public static byte[] signProfile(byte[] content, ISigner signer, String sigAlg) { + try { + AlgorithmIdentifier sigAlgId = (new DefaultSignatureAlgorithmIdentifierFinder()).find(sigAlg); + ASN1EncodableVector digestAlgIds = new ASN1EncodableVector(); + AlgorithmIdentifier digestAlgId = (new DefaultDigestAlgorithmIdentifierFinder()).find(sigAlgId); + digestAlgIds.add(digestAlgId); + byte[] digest = getContentDigest(content, digestAlgId); + ASN1Set signedAttr = generatePKCS9Attributes(digest); + byte[] signature = signer.getSignature(signedAttr.getEncoded("DER"), sigAlg, null); + SignerIdentifier signerIdentifier = generateSignerIdentifier(signer.getCertificates().get(0)); + SignerInfo signerInfo = new SignerInfo(signerIdentifier, digestAlgId, signedAttr, sigAlgId, + new DEROctetString(signature), null); + ASN1EncodableVector signerInfos = new ASN1EncodableVector(); + signerInfos.add(signerInfo); + ASN1Set certList = createBerSetFromCerts(signer.getCertificates()); + List crls = signer.getCrls(); + ASN1Set crlList = createBerSetFromCrls(crls); + ContentInfo encryptInfo = new ContentInfo(CMSObjectIdentifiers.data, new DEROctetString(content)); + SignedData sd = new SignedData(new DERSet(digestAlgIds), encryptInfo, certList, crlList, + new DERSet(signerInfos)); + ContentInfo contentInfo = new ContentInfo(CMSObjectIdentifiers.signedData, sd); + return contentInfo.getEncoded("DER"); + } catch (Exception e) { + LOGGER.debug(e.getMessage(), e); + CustomException.throwException(ERROR.SIGN_ERROR, e.getMessage()); + } + return NO_BYTE; + } + + private static SignerIdentifier generateSignerIdentifier(X509Certificate certificate) + throws CertificateEncodingException { + return new SignerIdentifier(new IssuerAndSerialNumber( + (new JcaX509CertificateHolder(certificate)).toASN1Structure())); + } + + private static ASN1Set generatePKCS9Attributes(byte[] digest) { + ASN1EncodableVector vector = new ASN1EncodableVector(); + Attribute signTime = new Attribute(PKCSObjectIdentifiers.pkcs_9_at_signingTime, + new DERSet(new Time(Date.from(LocalDateTime.now().atZone(ZoneId.systemDefault()).toInstant())))); + Attribute contentType = new Attribute(PKCSObjectIdentifiers.pkcs_9_at_contentType, + new DERSet(PKCSObjectIdentifiers.data)); + Attribute digestAtt = new Attribute(PKCSObjectIdentifiers.pkcs_9_at_messageDigest, + new DERSet(new DEROctetString(digest))); + vector.add(signTime); + vector.add(contentType); + vector.add(digestAtt); + return new DERSet(vector); + } + + private static byte[] getContentDigest(byte[] content, AlgorithmIdentifier digestAlgorithmIdentifier) + throws OperatorCreationException, IOException { + DigestCalculatorProvider digestCalculatorProvider = (new JcaDigestCalculatorProviderBuilder()).build(); + DigestCalculator digestCalculator = digestCalculatorProvider.get(digestAlgorithmIdentifier); + digestCalculator.getOutputStream().write(content); + return digestCalculator.getDigest(); + } + + private static ASN1Set createBerSetFromCrls(List crls) throws CRLException { + if (crls != null && crls.size() != 0) { + ASN1EncodableVector vector = new ASN1EncodableVector(crls.size()); + for (X509CRL crl : crls) { + vector.add((new JcaX509CRLHolder(crl)).toASN1Structure()); + } + return new BERSet(vector); + } else { + return null; + } + } + + private static ASN1Set createBerSetFromCerts(List certs) throws CertificateEncodingException { + if (certs != null && certs.size() != 0) { + ASN1EncodableVector vector = new ASN1EncodableVector(certs.size()); + for (X509Certificate cert : certs) { + vector.add((new JcaX509CertificateHolder(cert)).toASN1Structure()); + } + return new BERSet(vector); + } else { + return null; + } + } +} 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 new file mode 100644 index 0000000000000000000000000000000000000000..f67e2886a1f8fe4a398eeb6b303f250f69d51473 --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/profile/VerifyHelper.java @@ -0,0 +1,111 @@ +/* + * Copyright (c) 2021-2022 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.profile; + +import com.ohos.hapsigntool.error.CustomException; +import com.ohos.hapsigntool.error.ERROR; +import com.ohos.hapsigntool.profile.model.Provision; +import com.ohos.hapsigntool.profile.model.VerificationResult; +import com.ohos.hapsigntool.utils.FileUtils; +import com.ohos.hapsigntool.utils.ValidateUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.bouncycastle.cert.X509CertificateHolder; +import org.bouncycastle.cms.CMSException; +import org.bouncycastle.cms.CMSSignedData; +import org.bouncycastle.cms.SignerId; +import org.bouncycastle.cms.SignerInformationVerifier; +import org.bouncycastle.cms.jcajce.JcaSimpleSignerInfoVerifierBuilder; +import org.bouncycastle.util.Store; + +import java.nio.charset.StandardCharsets; +import java.security.cert.CertificateException; +import java.util.Collection; + +/** + * Signed provision profile verifier. + * + * @since 2021/12/28 + */ +public class VerifyHelper implements IProvisionVerifier { + + /** + * LOGGER. + */ + private static final Logger LOGGER = LogManager.getLogger(VerifyHelper.class); + + /** + * Signed provision profile verifier. + */ + public VerifyHelper() { + // Empty constructor + } + + /** + * verify p7b content. + * + * @param p7b signed p7b content + * @return result + */ + @Override + public VerificationResult verify(byte[] p7b) { + VerificationResult result = new VerificationResult(); + + try { + CMSSignedData cmsSignedData = this.verifyPkcs(p7b); + result.setContent(FileUtils.GSON.fromJson(new String((byte[]) (cmsSignedData + .getSignedContent().getContent()), StandardCharsets.UTF_8), Provision.class)); + result.setMessage("OK"); + result.setVerifiedPassed(true); + return result; + } catch (CustomException exception) { + LOGGER.debug(exception.getMessage(), exception); + result.setMessage("Failed to verify provision" + exception.getMessage()); + result.setVerifiedPassed(false); + return result; + } + } + + CMSSignedData verifyPkcs(byte[] p7b) { + CMSSignedData cmsSignedData = null; + try { + cmsSignedData = new CMSSignedData(p7b); + Store store = cmsSignedData.getCertificates(); + cmsSignedData.verifySignatures((SignerId sid) -> { + Collection collection = + (Collection) store.getMatches(sid); + ValidateUtils.throwIfNotMatches(collection != null && collection.size() == 1, ERROR.VERIFY_ERROR, + "No matched cert or more than one matched certs: " + collection); + X509CertificateHolder cert = collection.iterator().next(); + SignerInformationVerifier signInfoVerifier = null; + try { + signInfoVerifier = (new JcaSimpleSignerInfoVerifierBuilder()).setProvider("BC").build(cert); + } catch (CertificateException exception) { + LOGGER.debug(exception.getMessage(), exception); + CustomException.throwException(ERROR.VERIFY_ERROR, "Failed to verify BC signatures: " + + exception.getMessage()); + } + return signInfoVerifier; + }); + return cmsSignedData; + } catch (CMSException exception) { + LOGGER.debug(exception.getMessage(), exception); + CustomException.throwException(ERROR.VERIFY_ERROR, "Failed to verify BC signatures: " + + exception.getMessage()); + } + return cmsSignedData; + } +} diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/profile/model/Acls.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/profile/model/Acls.java new file mode 100644 index 0000000000000000000000000000000000000000..0a2f3af748c4def51b7386aa4bb775f831953910 --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/profile/model/Acls.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2021-2022 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.profile.model; + +import com.google.gson.annotations.SerializedName; + +import java.util.List; + +/** + * Sub dto of Provision. + * + * @since 2021/12/28 + */ +public class Acls { + /** + * Field allowed-acls. + */ + @SerializedName("allowed-acls") + private List allowedAcls; + + /** + * Sub dto of Provision. + */ + public Acls() { + //Empty constructor of Acls. + } + + public List getAllowedAcls() { + return allowedAcls; + } + + public void setAllowedAcls(List allowedAcls) { + this.allowedAcls = allowedAcls; + } +} diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/profile/model/BundleInfo.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/profile/model/BundleInfo.java new file mode 100644 index 0000000000000000000000000000000000000000..234aeb8fa8a868052237e2cb48ba6fcb0acb757f --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/profile/model/BundleInfo.java @@ -0,0 +1,135 @@ +/* + * Copyright (c) 2021-2022 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.profile.model; + +import com.google.gson.annotations.SerializedName; +import com.ohos.hapsigntool.error.ERROR; +import com.ohos.hapsigntool.utils.ValidateUtils; + +/** + * Sub dto of Provision. + * + * @since 2021/12/28 + */ +public class BundleInfo { + /** + * Field developer-id. + */ + @SerializedName("developer-id") + private String developerId; + /** + * Field development-certificate. + */ + @SerializedName("development-certificate") + private String developmentCertificate; + /** + * Field distribution-certificate. + */ + @SerializedName("distribution-certificate") + private String distributionCertificate; + /** + * Field bundle-name. + */ + @SerializedName("bundle-name") + private String bundleName; + /** + * Field apl. + */ + @SerializedName("apl") + private String apl; + /** + * Field app-feature. + */ + @SerializedName("app-feature") + private String appFeature; + + /** + * Sub dto of Provision. + */ + public BundleInfo() { + //Empty constructor of BundleInfo. + } + + public String getDeveloperId() { + return developerId; + } + + public void setDeveloperId(String developerId) { + this.developerId = developerId; + } + + public String getDevelopmentCertificate() { + return developmentCertificate; + } + + public void setDevelopmentCertificate(String developmentCertificate) { + this.developmentCertificate = developmentCertificate; + } + + public String getDistributionCertificate() { + return distributionCertificate; + } + + public void setDistributionCertificate(String distributionCertificate) { + this.distributionCertificate = distributionCertificate; + } + + public String getBundleName() { + return bundleName; + } + + public void setBundleName(String bundleName) { + this.bundleName = bundleName; + } + + public String getApl() { + return apl; + } + + public void setApl(String apl) { + this.apl = apl; + } + + public String getAppFeature() { + return appFeature; + } + + public void setAppFeature(String appFeature) { + this.appFeature = appFeature; + } + + /** + * Enforce valid. + * + * @param buildType build type + */ + public void enforceValid(String buildType) { + ValidateUtils.throwIfMatches(this.developerId == null, ERROR.SIGN_ERROR, + "Require developerId in bundleInfo!"); + if (Provision.isBuildTypeRelease(buildType)) { + ValidateUtils.throwIfMatches(this.distributionCertificate == null, + ERROR.SIGN_ERROR, "Require cert in bundleInfo!"); + } else { + ValidateUtils.throwIfMatches(this.developmentCertificate == null, + ERROR.SIGN_ERROR, "Require cert in bundleInfo!"); + } + ValidateUtils.throwIfMatches(this.bundleName == null, ERROR.SIGN_ERROR, + "Require bundleName in bundleInfo!"); + ValidateUtils.throwIfMatches(this.appFeature == null || Provision.isAppDistTypeValid(this.appFeature), + ERROR.SIGN_ERROR, "Require appFeature be hos_system_app or hos_normal_app,curr is :" + + this.appFeature + " in bundleInfo!"); + } +} diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/profile/model/DebugInfo.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/profile/model/DebugInfo.java new file mode 100644 index 0000000000000000000000000000000000000000..886c5ae3fe9a7d13c0bd58c946bf9fb7a89eca61 --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/profile/model/DebugInfo.java @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2021-2022 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.profile.model; + +import com.google.gson.annotations.SerializedName; +import com.ohos.hapsigntool.error.ERROR; +import com.ohos.hapsigntool.utils.ValidateUtils; + +import java.util.List; + +/** + * Sub dto of Provision. + * + * @since 2021/12/28 + */ +public class DebugInfo { + + /** + * Max number of debug device. + */ + private static final int MAX_DEBUG_DEVICE_NUM = 100; + + /** + * Field device-id-type. + */ + @SerializedName("device-id-type") + private String deviceIdType; + /** + * Field device-ids. + */ + @SerializedName("device-ids") + private List deviceIds; + + /** + * Sub dto of Provision. + */ + public DebugInfo() { + //Empty constructor of DebugInfo. + } + + public String getDeviceIdType() { + return deviceIdType; + } + + public void setDeviceIdType(String deviceIdType) { + this.deviceIdType = deviceIdType; + } + + public List getDeviceIds() { + return deviceIds; + } + + public void setDeviceIds(List deviceIds) { + this.deviceIds = deviceIds; + } + + /** + * Enforce valid. + */ + public void enforceValid() { + if (this.deviceIds != null) { + ValidateUtils.throwIfMatches(this.deviceIds.size() > MAX_DEBUG_DEVICE_NUM, ERROR.SIGN_ERROR, + "Support at most: 100 devices!"); + ValidateUtils.throwIfMatches(!this.isDeviceIdTypeValid(), ERROR.SIGN_ERROR, + "Device id type must be sn or udid, current is " + this.deviceIdType); + } + + } + + private boolean isDeviceIdTypeValid() { + return "sn".equals(this.deviceIdType) || "udid".equals(this.deviceIdType); + } +} diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/profile/model/Permissions.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/profile/model/Permissions.java new file mode 100644 index 0000000000000000000000000000000000000000..0b59b5e4d561cf44699a7e4145aa72fca99ac202 --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/profile/model/Permissions.java @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2021-2022 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.profile.model; + +import com.google.gson.annotations.SerializedName; + +import java.util.List; + +/** + * Sub dto of Provision. + * + * @since 2021/12/28 + */ +public class Permissions { + /** + * Field restricted-permissions. + */ + @SerializedName("restricted-permissions") + private List restrictedPermissions; + /** + * Field restricted-capabilities. + */ + @SerializedName("restricted-capabilities") + private List restrictedCapabilities; + + /** + * Sub dto of Provision. + */ + public Permissions() { + //Empty constructor of Permissions. + } + + public List getRestrictedPermissions() { + return restrictedPermissions; + } + + public void setRestrictedPermissions(List restrictedPermissions) { + this.restrictedPermissions = restrictedPermissions; + } + + public List getRestrictedCapabilities() { + return restrictedCapabilities; + } + + public void setRestrictedCapabilities(List restrictedCapabilities) { + this.restrictedCapabilities = restrictedCapabilities; + } +} + diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/profile/model/Provision.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/profile/model/Provision.java new file mode 100644 index 0000000000000000000000000000000000000000..9a9eb97284fbc6fa076b6e33f815d59e94551dc7 --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/profile/model/Provision.java @@ -0,0 +1,306 @@ +/* + * Copyright (c) 2021-2022 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.profile.model; + +import com.google.gson.annotations.SerializedName; +import com.ohos.hapsigntool.error.ERROR; +import com.ohos.hapsigntool.utils.ValidateUtils; + +/** + * Json object of provision profile. + * + * @since 2021/12/28 + */ +public class Provision { + /** + * Field DEBUG. + */ + public static final String DEBUG = "debug"; + /** + * Field RELEASE. + */ + public static final String RELEASE = "release"; + /** + * Field HOS_SYSTEM_APP. + */ + public static final String HOS_SYSTEM_APP = "hos_system_app"; + /** + * Field HOS_NORMAL_APP. + */ + public static final String HOS_NORMAL_APP = "hos_normal_app"; + /** + * Field NORMAL. + */ + public static final String NORMAL = "normal"; + /** + * Field SYSTEM_BASIC. + */ + public static final String SYSTEM_BASIC = "system_basic"; + /** + * Field SYSTEM_CORE. + */ + public static final String SYSTEM_CORE = "system_core"; + /** + * Field APP_GALLERY. + */ + public static final String APP_GALLERY = "app_gallery"; + /** + * Field ENTERPRISE. + */ + public static final String ENTERPRISE = "enterprise"; + /** + * Field OS_INTEGRATION. + */ + public static final String OS_INTEGRATION = "os_integration"; + /** + * Number 100. + */ + public static final int NUM_ONE_HUNDRED = 100; + + /** + * Field version-code. + */ + @SerializedName("version-code") + private Integer versionCode; + /** + * Field version-name. + */ + @SerializedName("version-name") + private String versionName; + /** + * Field uuid. + */ + @SerializedName("uuid") + private String uuid; + /** + * Field type. + */ + @SerializedName("type") + private String type; + /** + * Field app-distribution-type. + */ + @SerializedName("app-distribution-type") + private String appDistributionType; + /** + * Field validity. + */ + @SerializedName("validity") + private Validity validity; + /** + * Field bundle-info. + */ + @SerializedName("bundle-info") + private BundleInfo bundleInfo; + /** + * Field acls. + */ + @SerializedName("acls") + private Acls acls; + /** + * Field permissions. + */ + @SerializedName("permissions") + private Permissions permissions; + /** + * Field debug-info. + */ + @SerializedName("debug-info") + private DebugInfo debuginfo; + /** + * Field issuer. + */ + @SerializedName("issuer") + private String issuer; + + /** + * Dto for provision profile. + */ + public Provision() { + //Empty constructor of Provision. + } + + public static boolean isBuildTypeValid(String buildType) { + return DEBUG.equals(buildType) || RELEASE.equals(buildType); + } + + public static boolean isBuildTypeRelease(String buildType) { + return RELEASE.equals(buildType); + } + + /** + * Check if dist type in scope. + * + * @param appDistType Input type + * @return Is type in scope + */ + public static boolean isAppDistTypeValid(String appDistType) { + return APP_GALLERY.equals(appDistType) + || ENTERPRISE.equals(appDistType) + || OS_INTEGRATION.equals(appDistType); + } + + /** + * Enforce valid. + * + * @param provision provision + */ + public static void enforceValid(Provision provision) { + ValidateUtils.throwIfMatches(provision == null, ERROR.SIGN_ERROR, + "Require provision not empty!"); + ValidateUtils.throwIfMatches(provision.versionName == null, ERROR.SIGN_ERROR, + "Require provision version name!"); + ValidateUtils.throwIfMatches(provision.versionCode == 0, ERROR.SIGN_ERROR, + "Require provision version code!"); + ValidateUtils.throwIfMatches(provision.uuid == null, ERROR.SIGN_ERROR, + "Require provision uuid!"); + ValidateUtils.throwIfMatches(provision.type == null || !isBuildTypeValid(provision.type), + ERROR.SIGN_ERROR, "Require build type must be debug or release, current is :" + provision.type); + + ValidateUtils.throwIfMatches(isBuildTypeRelease(provision.type) + && (provision.appDistributionType == null + || !isAppDistTypeValid(provision.appDistributionType)), ERROR.SIGN_ERROR, + "Require app distribution type must be one of app_gallery, " + + "enterprise or os_integration, current is " + provision.appDistributionType); + ValidateUtils.throwIfMatches(provision.bundleInfo == null, ERROR.SIGN_ERROR, + "Require bundleInfo in provision!"); + provision.bundleInfo.enforceValid(provision.type); + ValidateUtils.throwIfMatches(provision.validity == null, ERROR.SIGN_ERROR, + "Require validity in provision!"); + provision.validity.enforceValid(); + if (provision.debuginfo != null) { + provision.debuginfo.enforceValid(); + } + ValidateUtils.throwIfMatches(provision.issuer == null, ERROR.SIGN_ERROR, + "Require issuer in provision!"); + } + + public Integer getVersionCode() { + return versionCode; + } + + public void setVersionCode(Integer versionCode) { + this.versionCode = versionCode; + } + + public String getVersionName() { + return versionName; + } + + public void setVersionName(String versionName) { + this.versionName = versionName; + } + + public String getUuid() { + return uuid; + } + + public void setUuid(String uuid) { + this.uuid = uuid; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public String getAppDistributionType() { + return appDistributionType; + } + + public void setAppDistributionType(String appDistributionType) { + this.appDistributionType = appDistributionType; + } + + public Validity getValidity() { + return validity; + } + + public void setValidity(Validity validity) { + this.validity = validity; + } + + public BundleInfo getBundleInfo() { + return bundleInfo; + } + + public void setBundleInfo(BundleInfo bundleInfo) { + this.bundleInfo = bundleInfo; + } + + public Acls getAcls() { + return acls; + } + + public void setAcls(Acls acls) { + this.acls = acls; + } + + public Permissions getPermissions() { + return permissions; + } + + public void setPermissions(Permissions permissions) { + this.permissions = permissions; + } + + public DebugInfo getDebuginfo() { + return debuginfo; + } + + public void setDebuginfo(DebugInfo debuginfo) { + this.debuginfo = debuginfo; + } + + public String getIssuer() { + return issuer; + } + + public void setIssuer(String issuer) { + this.issuer = issuer; + } + + @Override + public String toString() { + return "\n" + "version-code:" + versionCode + "\n" + + "version-name:" + versionCode + "\n" + + "uuid:" + uuid + "\n" + + "type:" + type + "\n" + + "app-distribution-type:" + appDistributionType + "\n" + + "validity:\n" + + "\t not-before:" + getValidity().getNotBefore() + "\n" + + "\t not-after:" + getValidity().getNotAfter() + "\n" + + "bundle-info\n" + + "\t developer-id:" + getBundleInfo().getDeveloperId() + "\n" + + "\t development-certificate:" + getBundleInfo().getDevelopmentCertificate() + "\n" + + "\t distribution-certificate:" + getBundleInfo().getDistributionCertificate() + "\n" + + "\t bundle-name:" + getBundleInfo().getBundleName() + "\n" + + "\t apl:" + getBundleInfo().getApl() + "\n" + + "\t app-feature:" + getBundleInfo().getAppFeature() + "\n" + + "acls:\n" + + "\t allowed-acls:" + getAcls().getAllowedAcls() + "\n" + + "permissions:\n" + + "\t restricted-permissions:" + getPermissions().getRestrictedPermissions() + "\n" + + "\t restricted-capabilities:" + getPermissions().getRestrictedCapabilities() + "\n" + + "debug-info\n" + + "\t device-id-type:" + getDebuginfo().getDeviceIdType() + "\n" + + "\t device-ids:" + getDebuginfo().getDeviceIds() + "\n" + + "issuer:" + getIssuer(); + } +} diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/profile/model/Validity.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/profile/model/Validity.java new file mode 100644 index 0000000000000000000000000000000000000000..441a8a1ace2c50f77a32699ba260e8a5974b4445 --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/profile/model/Validity.java @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2021-2022 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.profile.model; + +import com.google.gson.annotations.SerializedName; +import com.ohos.hapsigntool.error.ERROR; +import com.ohos.hapsigntool.utils.ValidateUtils; + +/** + * Sub dto of Provision. + * + * @since 2021/12/28 + */ +public class Validity { + /** + * Field not-before. + */ + @SerializedName("not-before") + private Long notBefore; + /** + * Field not-after. + */ + @SerializedName("not-after") + private Long notAfter; + + /** + * Sub dto of Provision. + */ + public Validity() { + //Empty constructor of Validity. + } + + /** + * Validate attribute. + */ + public void enforceValid() { + ValidateUtils.throwIfMatches(this.notBefore == 0L, ERROR.SIGN_ERROR, "Require notBefore in validity!"); + ValidateUtils.throwIfMatches(this.notAfter == 0L, ERROR.SIGN_ERROR, "Require notAfter in validity!"); + ValidateUtils.throwIfMatches(this.notBefore >= this.notAfter, ERROR.SIGN_ERROR, + "Require notBefore less than notAfter in validity!"); + } + + public Long getNotBefore() { + return notBefore; + } + + public void setNotBefore(Long notBefore) { + this.notBefore = notBefore; + } + + public Long getNotAfter() { + return notAfter; + } + + public void setNotAfter(Long notAfter) { + this.notAfter = notAfter; + } +} diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/profile/model/VerificationResult.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/profile/model/VerificationResult.java new file mode 100644 index 0000000000000000000000000000000000000000..50439159f33ff92c1874ae11be18f5c0a57ac785 --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/profile/model/VerificationResult.java @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2021-2022 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.profile.model; + +/** + * VerificationResult. + * + * @since 2021/12/28 + */ +public class VerificationResult { + /** + * Field verifiedPassed. + */ + private boolean verifiedPassed; + /** + * Field message. + */ + private String message; + /** + * Field content. + */ + private Provision content; + + public boolean isVerifiedPassed() { + return verifiedPassed; + } + + public void setVerifiedPassed(boolean verifiedPassed) { + this.verifiedPassed = verifiedPassed; + } + + public String getMessage() { + return this.message; + } + + public void setMessage(String string) { + this.message = string; + } + + public Provision getContent() { + return content; + } + + public void setContent(Provision provision) { + this.content = provision; + } + + /** + * Empty constructor without value initial. + */ + public VerificationResult() { + //Empty constructor of VerificationResult. + } +} diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/signer/ISigner.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/signer/ISigner.java new file mode 100644 index 0000000000000000000000000000000000000000..ebd1eeb19db54d5072c9e40814e5f2e396c287d3 --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/signer/ISigner.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2021-2022 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.signer; + +import java.security.cert.X509CRL; +import java.security.cert.X509Certificate; +import java.security.spec.AlgorithmParameterSpec; +import java.util.List; + +/** + * ISigner. + * + * @since 2021/12/28 + */ +public interface ISigner { + + /** + * Sign function, implement by local or remote. + * + * @param data Data to sign + * @param signAlg Sign algorithm + * @param parameterSpec Preset params + * @return Data signed + */ + byte[] getSignature(byte[] data, String signAlg, AlgorithmParameterSpec parameterSpec); + + /** + * getCrls. + * + * @return crls + */ + List getCrls(); + + /** + * getCertificates. + * + * @return Certificates + */ + List getCertificates(); + +} diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/signer/LocalSigner.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/signer/LocalSigner.java new file mode 100644 index 0000000000000000000000000000000000000000..72bd87a1c20fa30e457e48dd34009ca847a5427b --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/signer/LocalSigner.java @@ -0,0 +1,106 @@ +/* + * Copyright (c) 2021-2022 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.signer; + +import com.ohos.hapsigntool.error.CustomException; +import com.ohos.hapsigntool.error.ERROR; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.security.PrivateKey; +import java.security.Signature; +import java.security.cert.X509CRL; +import java.security.cert.X509Certificate; +import java.security.spec.AlgorithmParameterSpec; +import java.util.ArrayList; +import java.util.List; + +/** + * LocalSigner. + * + * @since 2021/12/28 + */ +public class LocalSigner implements ISigner { + /** + * logger of LocalSigner + */ + private final Logger logger = LogManager.getLogger(LocalSigner.class); + /** + * PrivateKey. + */ + private final PrivateKey privateKey; + /** + * X509Certificate. + */ + private final List certificates; + + /** + * Create local signer. + * + * @param privateKey Private key to sign + * @param certificates Cert chain to sign + */ + public LocalSigner(PrivateKey privateKey, List certificates) { + this.privateKey = privateKey; + this.certificates = certificates; + } + + /** + * Signed by local singer. + * + * @param data Data to sign + * @param signAlg Sign algorithm + * @param parameterSpec Preset params + * @return Data signed + */ + @Override + public byte[] getSignature(byte[] data, String signAlg, AlgorithmParameterSpec parameterSpec) { + byte[] signData = null; + try { + Signature signature = Signature.getInstance(signAlg); + signature.initSign(privateKey); + if (parameterSpec != null) { + signature.setParameter(parameterSpec); + } + signature.update(data); + signData = signature.sign(); + } catch (Exception exception) { + logger.debug(exception.getMessage(), exception); + CustomException.throwException(ERROR.SIGN_ERROR, exception.getMessage()); + } + return signData; + } + + /** + * getCrls. + * + * @return crls + */ + @Override + public List getCrls() { + return new ArrayList<>(); + } + + /** + * getCertificates. + * + * @return Certificates + */ + @Override + public List getCertificates() { + return certificates; + } +} diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/signer/RemoteSigner.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/signer/RemoteSigner.java new file mode 100644 index 0000000000000000000000000000000000000000..0cd39afc79a04eb3266e74b1d8d2cde94127a495 --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/signer/RemoteSigner.java @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2021-2022 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.signer; + +import java.security.cert.X509CRL; +import java.security.cert.X509Certificate; +import java.security.spec.AlgorithmParameterSpec; +import java.util.List; +import java.util.Map; + +/** + * RemoteSigner. + * + * @since 2021/12/28 + */ +public class RemoteSigner implements ISigner { + + /** + * UnsupportedOperationException message. + */ + private static final String ERROR = "Not implement yet"; + + /** + * Empty remote signer. + * + * @param params Preset map configurations + */ + public RemoteSigner(Map params) { + } + + /** + * Signed by remote signer. + * + * @param data Data to sign + * @param signAlg Sign algorithm + * @param parameterSpec Preset params + * @return Data signed + */ + @Override + public byte[] getSignature(byte[] data, String signAlg, AlgorithmParameterSpec parameterSpec) { + throw new UnsupportedOperationException(ERROR); + } + + /** + * getCrls. + * + * @return crls + */ + @Override + public List getCrls() { + throw new UnsupportedOperationException(ERROR); + } + + /** + * getCertificates. + * + * @return Certificates + */ + @Override + public List getCertificates() { + throw new UnsupportedOperationException(ERROR); + } +} diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/signer/SignerFactory.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/signer/SignerFactory.java new file mode 100644 index 0000000000000000000000000000000000000000..b978e222ffe479a01883ee92fe0961a701c547b7 --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/signer/SignerFactory.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2021-2022 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.signer; + +import com.ohos.hapsigntool.api.LocalizationAdapter; + +import java.security.KeyPair; + +/** + * Factory pattern to create signer. + * + * @since 2021/12/28 + */ +public class SignerFactory { + + /** + * Create a signer. + * + * @param adapter Params adapter + * @return Local signer or remote signer + */ + public ISigner getSigner(LocalizationAdapter adapter) { + if (adapter.isRemoteSigner()) { + return new RemoteSigner(adapter.getOptions()); + } else { + KeyPair keyPair = adapter.getAliasKey(false); + adapter.releasePwd(); + return new LocalSigner(keyPair.getPrivate(), adapter.getSignCertChain()); + } + } +} diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/utils/ByteArrayUtils.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/utils/ByteArrayUtils.java new file mode 100644 index 0000000000000000000000000000000000000000..29e77909a326a0d63fa68cf069e70c97e295be40 --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/utils/ByteArrayUtils.java @@ -0,0 +1,104 @@ +/* + * Copyright (c) 2021 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.utils; + +/** + * utils functions about byte arrays + * + * @since 2021-12-13 + */ +public class ByteArrayUtils { + /** + * Insert int value to byte array + * + * @param desByte destination byte array + * @param index position of inserting + * @param num value is inserted + * @return end of position of inserting, if successfully + */ + public static int insertIntToByteArray(byte[] desByte, int index, int num) { + if (index + Integer.BYTES > desByte.length) { + return -1; + } + int pos = index; + desByte[pos] = (byte) ((num >> 24) & 0xff); + pos++; + desByte[pos] = (byte) ((num >> 16) & 0xff); + pos++; + desByte[pos] = (byte) ((num >> 8) & 0xff); + pos++; + desByte[pos] = (byte) (num & 0xff); + pos++; + return pos; + } + + /** + *Insert short value to byte array + * + * @param desByte destination byte array + * @param desByteLen length of destination byte array + * @param index position of inserting + * @param num value is inserted + * @return end of position of inserting, if successfully + */ + public static int insertShortToByteArray(byte[] desByte, int desByteLen, int index, short num) { + if (index + 2 > desByteLen) { + return -1; + } + int pos = index; + desByte[pos] = (byte) ((num >> 8) & 0xff); + pos++; + desByte[pos] = (byte) (num & 0xff); + pos++; + return pos; + } + + /** + * Insert byte array to byte array + * + * @param des destination byte array + * @param desByteLen length of destination byte array + * @param start position of inserting + * @param src byte array is inserted + * @param srcLen length of byte array is inserted + * @return end of position of inserting, if successfully + */ + public static int insertByteToByteArray(byte[] des, int desByteLen, int start, byte[] src, int srcLen) { + if (src.length < srcLen) { + return -1; + } + System.arraycopy(src, 0, des, start, srcLen); + return start + srcLen; + } + + /** + * Insert char array to byte array + * + * @param des destination byte array + * @param start position of inserting + * @param src char array is inserted + * @return end of position of inserting, if successfully + */ + public static int insertCharToByteArray(byte[] des, int start, char[] src) { + if (start > des.length - src.length) { + return -1; + } + for (int i = 0; i < src.length; i++) { + des[i + start] = (byte) src[i]; + } + return start + src.length; + } +} \ No newline at end of file diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/utils/CertUtils.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/utils/CertUtils.java new file mode 100644 index 0000000000000000000000000000000000000000..23ffd73291de01147f770ad3e6b19626a29502ca --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/utils/CertUtils.java @@ -0,0 +1,276 @@ +/* + * Copyright (c) 2021-2022 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.utils; + +import com.google.gson.Gson; +import com.ohos.hapsigntool.error.CustomException; +import com.ohos.hapsigntool.error.ERROR; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.bouncycastle.asn1.ASN1ObjectIdentifier; +import org.bouncycastle.asn1.x500.X500Name; +import org.bouncycastle.asn1.x500.X500NameBuilder; +import org.bouncycastle.asn1.x500.style.BCStyle; +import org.bouncycastle.asn1.x509.KeyPurposeId; +import org.bouncycastle.asn1.x509.KeyUsage; +import org.bouncycastle.util.io.pem.PemObject; +import org.bouncycastle.util.io.pem.PemWriter; + +import javax.security.auth.x500.X500Principal; +import java.io.ByteArrayInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStreamWriter; +import java.math.BigInteger; +import java.nio.charset.StandardCharsets; +import java.security.SecureRandom; +import java.security.cert.CRLException; +import java.security.cert.CertificateEncodingException; +import java.security.cert.CertificateException; +import java.security.cert.CertificateFactory; +import java.security.cert.X509CRL; +import java.security.cert.X509Certificate; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Cert Usage Util. + * + * @since 2021/12/28 + */ +public final class CertUtils { + /** + * Logger. + */ + private static final Logger LOGGER = LogManager.getLogger(CertUtils.class); + + /** + * Max length to print certificate string. + */ + private static final int MAX_LINE_LENGTH = 65; + /** + * Length of serial security random number. + */ + private static final int RANDOM_SERIAL_LENGTH = 32; + /** + * number constant. + */ + private static final int SECOND_INDEX = 2; + + private CertUtils() { + // Empty constructor + } + + /** + * Parse string to key usage. + * + * @param keyUsageStr Key usage string + * @return Key usage + */ + public static int parseKeyUsage(String keyUsageStr) { + int keyUsage = 0; + if (keyUsageStr.contains("digitalSignature")) { + keyUsage |= KeyUsage.digitalSignature; + } + if (keyUsageStr.contains("nonRepudiation")) { + keyUsage |= KeyUsage.nonRepudiation; + } + if (keyUsageStr.contains("keyEncipherment")) { + keyUsage |= KeyUsage.keyEncipherment; + } + if (keyUsageStr.contains("dataEncipherment")) { + keyUsage |= KeyUsage.dataEncipherment; + } + if (keyUsageStr.contains("keyAgreement")) { + keyUsage |= KeyUsage.keyAgreement; + } + if (keyUsageStr.contains("certificateSignature")) { + keyUsage |= KeyUsage.keyCertSign; + } + if (keyUsageStr.contains("crlSignature")) { + keyUsage |= KeyUsage.cRLSign; + } + if (keyUsageStr.contains("encipherOnly")) { + keyUsage |= KeyUsage.encipherOnly; + } + if (keyUsageStr.contains("decipherOnly")) { + keyUsage |= KeyUsage.decipherOnly; + } + return keyUsage; + } + + /** + * Parse string to KeyPurposeId[] + * + * @param extKeyUsageStr ext key usage string + * @return KeyPurposeId[] + */ + public static KeyPurposeId[] parseExtKeyUsage(String extKeyUsageStr) { + ArrayList ids = new ArrayList<>(); + if (extKeyUsageStr.contains("clientAuthentication")) { + ids.add(KeyPurposeId.id_kp_clientAuth); + } + if (extKeyUsageStr.contains("serverAuthentication")) { + ids.add(KeyPurposeId.id_kp_serverAuth); + } + if (extKeyUsageStr.contains("codeSignature")) { + ids.add(KeyPurposeId.id_kp_codeSigning); + } + if (extKeyUsageStr.contains("emailProtection")) { + ids.add(KeyPurposeId.id_kp_emailProtection); + } + if (extKeyUsageStr.contains("smartCardLogin")) { + ids.add(KeyPurposeId.id_kp_smartcardlogon); + } + if (extKeyUsageStr.contains("timestamp")) { + ids.add(KeyPurposeId.id_kp_timeStamping); + } + if (extKeyUsageStr.contains("ocspSignature")) { + ids.add(KeyPurposeId.id_kp_OCSPSigning); + } + return ids.toArray(new KeyPurposeId[]{}); + } + + public static X500Name buildDN(String nameString) { + ValidateUtils.throwIfNotMatches(!StringUtils.isEmpty(nameString), ERROR.COMMAND_ERROR, ""); + + String gsonStr = nameString.replace(",", "\",\""); + gsonStr = "{\"" + gsonStr.replace("=", "\":\"") + "\"}"; + + X500NameBuilder builder = new X500NameBuilder(); + HashMap map = FileUtils.GSON.fromJson(gsonStr, HashMap.class); + + BCStyle x500NameStyle = (BCStyle) BCStyle.INSTANCE; + for (Map.Entry entry : map.entrySet()) { + if (StringUtils.isEmpty(entry.getKey()) || StringUtils.isEmpty(entry.getValue())) { + continue; + } + try { + ASN1ObjectIdentifier oid = x500NameStyle.attrNameToOID(entry.getKey().trim()); + builder.addRDN(oid, entry.getValue()); + } catch (IllegalArgumentException | IndexOutOfBoundsException exception) { + LOGGER.debug(exception.getMessage(), exception); + CustomException.throwException(ERROR.COMMAND_ERROR, + String.format("Error params near: %s. Reason: %s", nameString, exception.getMessage())); + } + } + return builder.build(); + } + + /** + * Generate crl. + * + * @param crl crl + * @return X509CRL + */ + public static X509CRL generateCrl(byte[] crl) { + try { + CertificateFactory factory = CertificateFactory.getInstance("X.509"); + return (X509CRL) factory.generateCRL(new ByteArrayInputStream(crl)); + } catch (CertificateException | CRLException exception) { + LOGGER.debug(exception.getMessage(), exception); + CustomException.throwException(ERROR.NOT_SUPPORT_ERROR, exception.getMessage()); + } + return null; + } + + /** + * Convert byte to CSR String. + * + * @param csr bytes of CSR + * @return String + */ + public static String toCsrTemplate(byte[] csr) { + return "-----BEGIN NEW CERTIFICATE REQUEST-----\n" + + java.util.Base64.getMimeEncoder(MAX_LINE_LENGTH, "\n".getBytes(StandardCharsets.UTF_8)) + .encodeToString(csr) + + "\n-----END NEW CERTIFICATE REQUEST-----\n"; + } + + /** + * Encoding cert to String. + * + * @param certificate Cert to convert to string + * @return Cert templated string + * @throws CertificateEncodingException Failed encoding + */ + public static String generateCertificateInCer(X509Certificate certificate) + throws CertificateEncodingException { + return "-----BEGIN CERTIFICATE-----\n" + + java.util.Base64.getMimeEncoder(MAX_LINE_LENGTH, "\n".getBytes(StandardCharsets.UTF_8)) + .encodeToString(certificate.getEncoded()) + + "\n" + "-----END CERTIFICATE-----" + "\n"; + } + + /** + * Random serial. + * + * @return Random big integer + */ + public static BigInteger randomSerial() { + return new BigInteger(RANDOM_SERIAL_LENGTH, new SecureRandom()); + } + + /** + * save2Pem. + * + * @param certificates certificates to save + * @param filePath filePath to save + */ + public static void save2Pem(List certificates, String filePath) { + try (PemWriter pemWriter = new PemWriter(new OutputStreamWriter(new FileOutputStream(filePath)))) { + for (X509Certificate certificate : certificates) { + PemObject object = new PemObject("certificate", certificate.getEncoded()); + pemWriter.writeObject(object); + } + } catch (CertificateEncodingException exception) { + LOGGER.debug(exception.getMessage(), exception); + CustomException.throwException(ERROR.NOT_SUPPORT_ERROR, exception.getMessage()); + } catch (IOException exception) { + LOGGER.debug(exception.getMessage(), exception); + CustomException.throwException(ERROR.WRITE_FILE_ERROR, exception.getMessage()); + } + } + + /** + * Convert byte to cert. + * + * @param cert Byte from cert file + * @return Certs + * @throws CertificateException Convert failed + */ + public static List generateCertificates(byte[] cert) throws CertificateException { + CertificateFactory factory = CertificateFactory.getInstance("X.509"); + List certificates = + (List) factory.generateCertificates(new ByteArrayInputStream(cert)); + sortCertificateChain(certificates); + return certificates; + } + + private static void sortCertificateChain(List certificates) { + if (certificates != null && certificates.size() > 1) { + int size = certificates.size(); + X500Principal lastSubjectX500Principal = (certificates.get(size - 1)).getSubjectX500Principal(); + X500Principal beforeIssuerX500Principal = (certificates.get(size - SECOND_INDEX)).getIssuerX500Principal(); + if (!lastSubjectX500Principal.equals(beforeIssuerX500Principal)) { + Collections.reverse(certificates); + } + } + } +} diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/utils/CertificateUtils.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/utils/CertificateUtils.java new file mode 100644 index 0000000000000000000000000000000000000000..2143fd39e6b8b132cc53ba52d329ba2e590321ec --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/utils/CertificateUtils.java @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2021 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.utils; + +import com.ohos.hapsigntool.hap.exception.VerifyCertificateChainException; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +import java.security.SignatureException; +import java.security.cert.CertificateException; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +import javax.security.auth.x500.X500Principal; + +/** + * Utils of certificate processing. + */ +public class CertificateUtils { + private static void verifyCertChain(List certs) throws VerifyCertificateChainException { + if (certs.size() <= 1) { + return; + } + for(int i = 1; i < certs.size(); i++) { + try { + certs.get(i - 1).verify(certs.get(i).getPublicKey()); + } catch (CertificateException | NoSuchAlgorithmException | InvalidKeyException + | NoSuchProviderException | SignatureException e) { + throw new VerifyCertificateChainException("verify certificate chain failed! " + e.getMessage()); + } + } + } + + /** + * Get certificates chains from input file. + * + * @param certsFile input file + * @return list of certificate chain + * @throws IOException file is not exist + * @throws CertificateException data in file is not certificate + * @throws VerifyCertificateChainException certificates in file are not certificate chain + */ + public static List getCertListFromFile(String certsFile) throws IOException, CertificateException, + VerifyCertificateChainException { + try (FileInputStream fileInputStream = FileUtils.openInputStream(new File(certsFile))) { + CertificateFactory cf = CertificateFactory.getInstance("X.509"); + Collection certificates = + (Collection) cf.generateCertificates(fileInputStream); + if (certificates != null && certificates.size() > 0) { + List certs = new ArrayList(certificates); + sortCertificateChain(certs); + verifyCertChain(certs); + return certs; + } + } + return Collections.emptyList(); + } + + /** + * If the last certificate subject is not equal to previous certificate issuer, + * the certificate-chain need be reversed. + * + * @param certificates input certificate-chain + */ + private static void sortCertificateChain(List certificates) { + if (certificates != null && certificates.size() > 1) { + int size = certificates.size(); + X500Principal lastSubjectX500Principal = certificates.get(size - 1).getSubjectX500Principal(); + X500Principal beforeIssuerX500Principal = certificates.get(size - 2).getIssuerX500Principal(); + if (!lastSubjectX500Principal.equals(beforeIssuerX500Principal)) { + Collections.reverse(certificates); + } + } + } +} diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/utils/DigestUtils.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/utils/DigestUtils.java new file mode 100644 index 0000000000000000000000000000000000000000..917bcb67b8d4f7bc6d504ab60571c1ef64422f4c --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/utils/DigestUtils.java @@ -0,0 +1,150 @@ +/* + * Copyright (c) 2021 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.utils; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.io.ByteArrayInputStream; +import java.io.UnsupportedEncodingException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.cert.CRL; +import java.security.cert.CRLException; +import java.security.cert.Certificate; +import java.security.cert.CertificateException; +import java.security.cert.CertificateFactory; +import java.security.cert.X509CRL; +import java.security.cert.X509Certificate; +import java.util.Base64; + +/** + * Digest util class + * + * @since 2021-12-13 + */ +public class DigestUtils { + private static final Logger LOGGER = LogManager.getLogger(DigestUtils.class); + + /** + * digest the inputContent with SHA-256 + * + * @param inputContentArray input Content Array + * @return the result of digest with SHA-256 + */ + public static byte[] sha256Digest(byte[] inputContentArray) { + byte[] sha2Array = null; + try { + MessageDigest md = MessageDigest.getInstance("SHA-256"); + md.update(inputContentArray); + sha2Array = md.digest(); + } catch (NoSuchAlgorithmException e) { + LOGGER.error("don't has SHA-256 Algorithm"); + } + return sha2Array; + } + + /** + * digest the inputContent with SHA-384 + * + * @param inputContentArray input Content Array + * @return the result of digest with SHA-384 + */ + public static byte[] sha384Digest(byte[] inputContentArray) { + byte[] sha2Array = null; + try { + MessageDigest md = MessageDigest.getInstance("SHA-384"); + md.update(inputContentArray); + sha2Array = md.digest(); + } catch (NoSuchAlgorithmException e) { + LOGGER.error("don't has SHA-384 Algorithm"); + } + return sha2Array; + } + + /** + * digest the inputContent with SHA-512 + * + * @param inputContentArray input Content Array + * @return the result of digest with SHA-512 + */ + public static byte[] sha512Digest(byte[] inputContentArray) { + byte[] sha2Array = null; + try { + MessageDigest md = MessageDigest.getInstance("SHA-512"); + md.update(inputContentArray); + sha2Array = md.digest(); + } catch (NoSuchAlgorithmException e) { + LOGGER.error("don't has SHA-512 Algorithm"); + } + return sha2Array; + } + + /** + * Transform a string of certificate with base64 encoded to an object of X509Certificate. + * + * @param encodeString string of certificate with base64 encoded + * @return object of X509Certificate + */ + public static X509Certificate decodeBase64ToX509Certifate(String encodeString) { + String header = "-----BEGIN CERTIFICATE-----\n"; + String tail = "-----END CERTIFICATE-----\n"; + byte[] certificateDatas = null; + X509Certificate x509Certificate = null; + try { + CertificateFactory cf = CertificateFactory.getInstance("X.509"); + if (encodeString.startsWith(header) && encodeString.endsWith(tail)) { + certificateDatas = encodeString.getBytes("UTF-8"); + } else { + certificateDatas = Base64.getUrlDecoder().decode(encodeString); + } + + Certificate obj = cf.generateCertificate(new ByteArrayInputStream(certificateDatas)); + if (!(obj instanceof X509Certificate)) { + LOGGER.error("generateCertificate is not x509"); + return x509Certificate; + } + x509Certificate = (X509Certificate)obj; + } catch (UnsupportedEncodingException | CertificateException e) { + LOGGER.error("Decode Base64 certificate failed!", e); + } + return x509Certificate; + } + + /** + * Get an object of x509CRL from a string of certificate with base64 encoded + * + * @param encodeString string of certificate with base64 encoded + * @return an object of x509CRL + */ + public static X509CRL decodeBase64ToX509CRL(String encodeString) { + byte[] certificateDatas = Base64.getUrlDecoder().decode(encodeString); + + X509CRL x509CRL = null; + try { + CertificateFactory cf = CertificateFactory.getInstance("X.509"); + CRL obj = cf.generateCRL(new ByteArrayInputStream(certificateDatas)); + if (!(obj instanceof X509CRL)) { + LOGGER.error("generateCRL is not x509"); + return x509CRL; + } + x509CRL = (X509CRL)obj; + } catch (CertificateException | CRLException e) { + LOGGER.error("Decode Base64 crl failed!"); + } + return x509CRL; + } +} diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/utils/EscapeCharacter.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/utils/EscapeCharacter.java new file mode 100644 index 0000000000000000000000000000000000000000..12f7a4ec3e2a87a9269a4ba7b66b8b2521342cb0 --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/utils/EscapeCharacter.java @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2021 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.utils; + +/** + * Provider function of escape character. + * + * @since 2021/12/21 + */ +public class EscapeCharacter { + /** + * Phase string which is escaped + * @param src escaped string + * @return string after unescape. + */ + public static String unescape(String src) { + StringBuffer tmp = new StringBuffer(); + tmp.ensureCapacity(src.length()); + int lastPos = 0; + int pos = 0; + while (lastPos < src.length()) { + pos = src.indexOf('%', lastPos); + if (pos == lastPos) { + if (src.charAt(pos + 1) == 'u') { + char ch = (char) Integer.parseInt(src.substring(pos + 2, pos + 6), 16); + tmp.append(ch); + lastPos = pos + 6; + } else { + char ch = (char) Integer.parseInt(src.substring(pos + 1, pos + 3), 16); + tmp.append(ch); + lastPos = pos + 3; + } + } else if (pos == -1) { + tmp.append(src.substring(lastPos)); + lastPos = src.length(); + } else { + tmp.append(src.substring(lastPos, pos)); + lastPos = pos; + } + } + return tmp.toString(); + } +} 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 new file mode 100644 index 0000000000000000000000000000000000000000..5d48d526ebeef2511ae2f5e9579440ce75289330 --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/utils/FileUtils.java @@ -0,0 +1,357 @@ +/* + * Copyright (c) 2021-2022 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.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; + +import java.io.BufferedWriter; +import java.io.ByteArrayOutputStream; +import java.io.Closeable; +import java.io.DataOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.nio.charset.Charset; + +/** + * Common file operation. + * + * @since 2021/12/28 + */ +public final class FileUtils { + + /** + * LOGGER. + */ + private static final Logger LOGGER = LogManager.getLogger(FileUtils.class); + /** + * 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; + /** + * File end + */ + public static final int FILE_END = -1; + /** + * Expected split string length + */ + public static final int SPLIT_LENGTH = 2; + + private static final byte[] EMPTY_BYTE_ARRAY = new byte[0]; + + private FileUtils() { + } + + /** + * Close closeable quietly. + * + * @param closeable closeable + */ + public static void close(Closeable closeable) { + if (closeable != null) { + try { + closeable.close(); + } catch (IOException exception) { + LOGGER.debug(exception.getMessage(), exception); + } + } + } + + /** + * Read byte from input file. + * + * @param file Which file to read + * @return byte content + * @throws IOException Read failed + */ + public static byte[] readFile(File file) throws IOException { + return read(new FileInputStream(file)); + } + + /** + * Read byte from input stream. + * + * @param input Input stream + * @return File content + * @throws IOException Read failed + */ + public static byte[] read(InputStream input) throws IOException { + try (ByteArrayOutputStream output = new ByteArrayOutputStream()) { + byte[] buffer = new byte[FILE_BUFFER_BLOCK]; + int read; + while ((read = input.read(buffer)) != FILE_END) { + output.write(buffer, 0, read); + } + return output.toByteArray(); + } finally { + close(input); + } + } + + /** + * Out put content to file. + * + * @param content Which content to out put + * @param output File to write + * @throws IOException Write failed + */ + public static void write(byte[] content, File output) throws IOException { + try (FileOutputStream out = new FileOutputStream(output)) { + for (byte b : content) { + out.write(b); + } + } + } + + /** + * Check file exist or not. + * + * @param filePath File path + * @return Is file exist + */ + public static boolean isFileExist(String filePath) { + return new File(filePath).exists(); + } + + /** + * Throw runtime exception if not allowed file type. + * + * @param filePath file path + * @param types Such as "txt" "json" "mp3" + */ + public static void validFileType(String filePath, String... types) { + String suffix = getSuffix(filePath); + ValidateUtils.throwIfNotMatches(!StringUtils.isEmpty(suffix), + ERROR.NOT_SUPPORT_ERROR, "Not support file: " + filePath); + boolean isMatches = false; + for (String type : types) { + if (StringUtils.isEmpty(type)) { + continue; + } + if (type.equalsIgnoreCase(suffix)) { + isMatches = true; + break; + } + } + ValidateUtils.throwIfNotMatches(isMatches, ERROR.NOT_SUPPORT_ERROR, "Not support file: " + filePath); + } + + /** + * Get suffix of file. + * + * @param filePath file path + * @return file suffix. Such as "txt" "json" "p12" + */ + public static String getSuffix(String filePath) { + if (StringUtils.isEmpty(filePath)) { + return ""; + } + File file = new File(filePath); + String fileName = file.getName(); + String[] temps = fileName.split("\\."); + if (temps.length < SPLIT_LENGTH) { + return ""; + } + return temps[temps.length - 1]; + } + + /** + * Write data in file to output stream + * + * @param file input file path. + * @param dos output stream. + * @return true, if write successfully. + */ + public static boolean writeFileToDos(String file, DataOutputStream dos) { + if (dos == null) { + return false; + } + File src = new File(file); + try (FileInputStream fileStream = new FileInputStream(src)) { + int temp; + byte[] buf = new byte[4096]; + while ((temp = fileStream.read(buf)) > 0) { + dos.write(buf, 0, temp); + } + 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; + } + + /** + * Write byte array to output stream + * + * @param data byte array + * @param dos output stream + * @return true, if write successfully. + */ + public static boolean writeByteToDos(byte[] data, DataOutputStream dos) { + try { + dos.write(data); + } catch (IOException e) { + LOGGER.error("Faile to write data to output stream."); + return false; + } + return true; + } + + /** + * Get length of file. + * + * @param filePath input file path. + * @return long value of file length. + */ + public static long getFileLen(String filePath) { + File file = new File(filePath); + if (file.exists() && file.isFile()) { + return file.length(); + } + return -1; + } + + /** + * Write byte array data to output file. + * + * @param signHeadByte byte array data. + * @param outFile output file path. + * @return true, if write successfully. + */ + public static boolean writeByteToOutFile(byte[] signHeadByte, String outFile) { + try (OutputStream ops = new FileOutputStream(outFile, true)) { + ops.write(signHeadByte, 0, signHeadByte.length); + ops.flush(); + return true; + } 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; + } + + /** + * Check input file is valid. + * + * @param file input file. + * @throws IOException file is a directory or can't be read. + */ + public static void isValidFile(File file) throws IOException { + if (!file.exists()) { + throw new FileNotFoundException("File '" + file + "' does not exist"); + } + + if (file.isDirectory()) { + throw new IOException("File '" + file + "' exists but is a directory"); + } + + if (!file.canRead()) { + throw new IOException("File '" + file + "' cannot be read"); + } + } + + /** + * 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. + */ + public static FileInputStream openInputStream(File file) throws IOException { + isValidFile(file); + return new FileInputStream(file); + } + + private static byte[] toByteArray(final InputStream input, final int size) throws IOException { + if (size < 0) { + throw new IllegalArgumentException("Size must be equal or greater than zero: " + size); + } + + if (size == 0) { + return EMPTY_BYTE_ARRAY; + } + + final byte[] data = new byte[size]; + int offset = 0; + int read; + + while (offset < size && (read = input.read(data, offset, size - offset)) != FILE_END) { + offset += read; + } + + if (offset != size) { + throw new IOException("Unexpected read size. current: " + offset + ", expected: " + size); + } + + return data; + } + + /** + * Read file to byte array. + * + * @param file input file. + * @return byte array of file-content. + * @throws IOException fill exception + */ + public static byte[] readFileToByteArray(File file) throws IOException { + try (InputStream in = openInputStream(file)) { + final long fileLength = file.length(); + if (fileLength > Integer.MAX_VALUE) { + throw new IllegalArgumentException("Size cannot be greater than Integer max value: " + fileLength); + } + return toByteArray(in, (int) fileLength); + } + } + + + + + + /** + * Write a string to file + * + * @param source String will be written. + * @param filePath path to write file. + * @param charset a charset + * @throws IOException write error. + */ + public static void writeStringToFile(String source, String filePath, Charset charset) throws IOException { + try (BufferedWriter writer = new BufferedWriter(new OutputStreamWriter( + new FileOutputStream(filePath), charset))) { + writer.write(source); + writer.flush(); + } + } +} 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 new file mode 100644 index 0000000000000000000000000000000000000000..dc7fb4a64192c37680f35a9ffb156b9285403316 --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/utils/HapUtils.java @@ -0,0 +1,445 @@ +/* + * Copyright (c) 2021 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.utils; + +import com.ohos.hapsigntool.hap.entity.Pair; +import com.ohos.hapsigntool.hap.entity.SigningBlock; +import com.ohos.hapsigntool.hap.exception.SignatureNotFoundException; +import com.ohos.hapsigntool.hap.sign.ContentDigestAlgorithm; +import com.ohos.hapsigntool.zip.MessageDigestZipDataOutput; +import com.ohos.hapsigntool.zip.ZipDataInput; +import com.ohos.hapsigntool.zip.ZipDataOutput; +import com.ohos.hapsigntool.zip.ZipFileInfo; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.bouncycastle.util.Arrays; + +import java.io.ByteArrayOutputStream; +import java.io.FileInputStream; +import java.io.IOException; +import java.nio.BufferUnderflowException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.security.DigestException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * Hap util, parse hap, find signature block. + * + * @since 2021/12/20 + */ +public class HapUtils { + private static final Logger LOGGER = LogManager.getLogger(HapUtils.class); + + /** + * ID of hap signature blocks of version 1 + */ + public static final int HAP_SIGNATURE_SCHEME_V1_BLOCK_ID = 0x20000000; + + /** + * ID of hap proof of rotation block + */ + public static final int HAP_PROOF_OF_ROTATION_BLOCK_ID = 0x20000001; + + /** + * ID of profile block + */ + public static final int HAP_PROFILE_BLOCK_ID = 0x20000002; + + /** + * ID of property block + */ + public static final int HAP_PROPERTY_BLOCK_ID = 0x20000003; + + /** + * The size of data block used to get digest + */ + public static final int CONTENT_DIGESTED_CHUNK_MAX_SIZE_BYTES = 1024 * 1024; + + /** + * The set of IDs of optional blocks in hap signature block. + */ + private static final Set HAP_SIGNATURE_OPTIONAL_BLOCK_IDS = + new HashSet() { + private static final long SERIAL_VERSION_UID = -7972389022577288313L; + + { + add(HAP_PROOF_OF_ROTATION_BLOCK_ID); + add(HAP_PROFILE_BLOCK_ID); + add(HAP_PROPERTY_BLOCK_ID); + } + }; + + /** + * Magic word of hap signature block/ + */ + private static final byte[] HAP_SIGNING_BLOCK_MAGIC = + new byte[] {0x48, 0x41, 0x50, 0x20, 0x53, 0x69, 0x67, 0x20, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x20, 0x34, 0x32}; + + /** + * The value of lower 8 bytes of magic word + */ + public static final long HAP_SIG_BLOCK_MAGIC_LO = 0x2067695320504148L; + + /** + * The value of higher 8 bytes of magic word + */ + public static final long HAP_SIG_BLOCK_MAGIC_HI = 0x3234206b636f6c42L; + + /** + * Size of hap signature block header + */ + public static final int HAP_SIG_BLOCK_HEADER_SIZE = 32; + + /** + * The min size of hap signature block + */ + public static final int HAP_SIG_BLOCK_MIN_SIZE = HAP_SIG_BLOCK_HEADER_SIZE; + + 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; + + /** + * Get HAP_SIGNATURE_OPTIONAL_BLOCK_IDS + * + * @return HAP_SIGNATURE_OPTIONAL_BLOCK_IDS + */ + public static Set getHapSignatureOptionalBlockIds() { + return HAP_SIGNATURE_OPTIONAL_BLOCK_IDS; + } + + /** + * Get HAP_SIGNING_BLOCK_MAGIC + * + * @return HAP_SIGNING_BLOCK_MAGIC + */ + public static byte[] getHapSigningBlockMagic() { + return HAP_SIGNING_BLOCK_MAGIC; + } + + /** + * Read data from hap file. + * + * @param file input file path. + * @return true, if read successfully. + * @throws IOException on error. + */ + public static byte[] readFileToByte(String file) throws IOException { + try (FileInputStream in = new FileInputStream(file); + ByteArrayOutputStream out = new ByteArrayOutputStream(in.available());) { + byte[] buf = new byte[4096]; + int len = 0; + while ((len = in.read(buf)) != -1) { + out.write(buf, 0, len); + } + return out.toByteArray(); + } + } + + 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); + } + return chunkCount; + } + + /** + * compute digests of contents + * + * @param digestAlgorithms algorithm of digest + * @param zipData content used to get digest + * @param optionalBlocks list of optional blocks used to get digest + * @return digests + * @throws DigestException digest error + * @throws IOException if an IO error occurs when compute hap file digest + */ + public static Map computeDigests( + Set digestAlgorithms, ZipDataInput[] zipData, List optionalBlocks) + throws DigestException, IOException { + long chunkCountLong = getChunkCount(zipData); + if (chunkCountLong > Integer.MAX_VALUE) { + throw new DigestException("Input too long: " + chunkCountLong + " chunks"); + } + int chunkCount = (int) chunkCountLong; + ContentDigestAlgorithm[] contentDigestAlgorithms = digestAlgorithms.toArray( + new ContentDigestAlgorithm[digestAlgorithms.size()]); + MessageDigest[] messageDigests = new MessageDigest[contentDigestAlgorithms.length]; + int[] digestOutputSizes = new int[contentDigestAlgorithms.length]; + byte[][] digestOfChunks = new byte[contentDigestAlgorithms.length][]; + initComputeItem(chunkCount, contentDigestAlgorithms, messageDigests, digestOutputSizes, digestOfChunks); + int chunkIndex = 0; + byte[] chunkContentPrefix = new byte[DIGEST_PRIFIX_LENGTH]; + chunkContentPrefix[0] = ZIP_SECOND_LEVEL_CHUNK_PREFIX; + byte[] buf = new byte[CONTENT_DIGESTED_CHUNK_MAX_SIZE_BYTES]; + ZipDataOutput digests = new MessageDigestZipDataOutput(messageDigests); + for (ZipDataInput content : zipData) { + long offset = 0L; + long remaining = content.size(); + while (remaining > 0) { + int chunkSize = (int) Math.min(buf.length, remaining); + setUInt32ToByteArrayWithLittleEngian(chunkSize, chunkContentPrefix, 1); + for (int i = 0; i < contentDigestAlgorithms.length; i++) { + messageDigests[i].update(chunkContentPrefix); + } + try { + content.copyTo(offset, chunkSize, digests); + } catch (IOException e) { + throw new IOException("Failed to read chunk #" + chunkIndex, e); + } + + getDigests(contentDigestAlgorithms, digestOutputSizes, messageDigests, digestOfChunks, chunkIndex); + offset += chunkSize; + remaining -= chunkSize; + chunkIndex++; + } + } + return getContentDigestAlgorithmMap(optionalBlocks, contentDigestAlgorithms, messageDigests, digestOfChunks); + } + + private static void getDigests(ContentDigestAlgorithm[] contentDigestAlgorithms, int[] digestOutputSizes, + MessageDigest[] messageDigests, byte[][] digestOfChunks, int chunkIndex) throws DigestException { + 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); + if (actualDigestSizeBytes != expectedDigestSizeBytes) { + throw new DigestException("Unexpected output size of " + messageDigests[i].getAlgorithm() + + " digest: " + actualDigestSizeBytes); + } + } + } + + private static void initComputeItem(int chunkCount, ContentDigestAlgorithm[] contentDigestAlgorithms, + MessageDigest[] messageDigests, int[] digestOutputSizes, + byte[][] digestOfChunks) throws DigestException { + try { + for (int i = 0; i < contentDigestAlgorithms.length; i++) { + int digestOutputSizeBytes = contentDigestAlgorithms[i].getDigestOutputByteSize(); + byte[] concatenationOfChunkCountAndChunkDigests = + new byte[DIGEST_PRIFIX_LENGTH + chunkCount * digestOutputSizeBytes]; + concatenationOfChunkCountAndChunkDigests[0] = ZIP_FIRST_LEVEL_CHUNK_PREFIX; + setUInt32ToByteArrayWithLittleEngian(chunkCount, concatenationOfChunkCountAndChunkDigests, 1); + digestOfChunks[i] = concatenationOfChunkCountAndChunkDigests; + messageDigests[i] = MessageDigest.getInstance(contentDigestAlgorithms[i].getDigestAlgorithm()); + digestOutputSizes[i] = contentDigestAlgorithms[i].getDigestOutputByteSize(); + } + } catch (NoSuchAlgorithmException e) { + throw new DigestException("Digest algorithm not supported", e); + } + } + + private static Map getContentDigestAlgorithmMap(List optionalBlocks, + 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]); + for (SigningBlock signingBlock : optionalBlocks) { + messageDigests[i].update(signingBlock.getValue()); + } + result.put(contentDigestAlgorithms[i], messageDigests[i].digest()); + } + return result; + } + + private static void setUInt32ToByteArrayWithLittleEngian(int value, byte[] result, int offset) { + for (int i = 0; i < 4; i++) { + result[offset + i] = (byte) ((value >> (8 * i)) & 0xff); + } + } + + private static final char[] HEX_CHAR_ARRAY = "0123456789ABCDEF".toCharArray(); + + /** + * Slice buffer to target size. + * + * @param source input data buffer + * @param targetSize target buffer's size + * @return target buffer of target size + */ + public static ByteBuffer sliceBuffer(ByteBuffer source, int targetSize) { + int limit = source.limit(); + int position = source.position(); + int targetLimit = position + targetSize; + if ((targetLimit < position) || (targetLimit > limit)) { + LOGGER.error("targetSize: " + targetSize); + throw new BufferUnderflowException(); + } + try { + source.limit(targetLimit); + ByteBuffer target = source.slice(); + target.order(source.order()); + return target; + } finally { + source.position(targetLimit); + source.limit(limit); + } + } + + private static ByteBuffer sliceBuffer(ByteBuffer source, int startPos, int endPos) { + int capacity = source.capacity(); + if (startPos < 0 || endPos < startPos || endPos > capacity) { + throw new IllegalArgumentException( + "startPos: " + startPos + ", endPos: " + endPos + ", capacity: " + capacity); + } + int limit = source.limit(); + int position = source.position(); + try { + source.position(0); + source.limit(endPos); + source.position(startPos); + ByteBuffer target = source.slice(); + target.order(source.order()); + return target; + } finally { + source.limit(limit); + source.position(position); + } + } + + /** + * Slice buffer from startPos to endPos, and then reverse it. + * + * @param hapSigningBlock input buffer used to slice. + * @param startPos start position of slice buffer. + * @param endPos end position of slice buffer. + * @return new buffer. + */ + public static ByteBuffer reverseSliceBuffer(ByteBuffer hapSigningBlock, int startPos, int endPos) { + ByteBuffer header = HapUtils.sliceBuffer(hapSigningBlock, startPos, endPos); + byte[] signatureBlockBytes = new byte[header.capacity()]; + header.get(signatureBlockBytes, 0, signatureBlockBytes.length); + return ByteBuffer.wrap(Arrays.reverse(signatureBlockBytes)); + } + + /** + * Check whether buffer is little endian. + * + * @param buffer ByteBuffer used to check + */ + public static void checkBufferLittleEndian(ByteBuffer buffer) { + if (buffer.order() == ByteOrder.LITTLE_ENDIAN) { + return; + } + throw new IllegalArgumentException("ByteBuffer is not little endian"); + } + + /** + * TLV encode list of pairs + * + * @param pairList input list of pairs + * @return byte array after encoding + */ + public static byte[] encodeListOfPairsToByteArray(List> pairList) { + int encodeSize = 0; + encodeSize += 4 + 4; + for (Pair pair : pairList) { + encodeSize += 12 + pair.getSecond().length; + } + ByteBuffer encodeBytes = ByteBuffer.allocate(encodeSize); + encodeBytes.order(ByteOrder.LITTLE_ENDIAN); + encodeBytes.putInt(2); // version + encodeBytes.putInt(1); // block number + for (Pair pair : pairList) { + byte[] second = pair.getSecond(); + encodeBytes.putInt(8 + second.length); + encodeBytes.putInt(pair.getFirst()); + encodeBytes.putInt(second.length); + encodeBytes.put(second); + } + return encodeBytes.array(); + } + + /** + * Translate value to Hex string. + * + * @param value input byte array. + * @param separator symbol insert between two bytes. + * @return a hex-values string. + */ + public static String toHex(byte[] value, String separator) { + StringBuilder sb = new StringBuilder(value.length * 2); + String useSeparator = separator == null ? "" : separator; + int len = value.length; + for (int i = 0; i < len; i++) { + int hi = (value[i] & 0xff) >>> 4; + int lo = value[i] & 0x0f; + sb.append(HEX_CHAR_ARRAY[hi]).append(HEX_CHAR_ARRAY[lo]); + if (i != len - 1) { + sb.append(useSeparator); + } + } + return sb.toString(); + } + + /** + * find signing block from hap file + * + * @param hap ZipDataInput object of zip file + * @param zipInfo ZipFileInfo object of hap file + * @return pair of offset of signing block and data of signing block + * @throws SignatureNotFoundException No signing block is found + * @throws IOException file operation error + */ + public static Pair findHapSigningBlock(ZipDataInput hap, ZipFileInfo zipInfo) + throws SignatureNotFoundException, IOException { + long centralDirectoryStartOffset = zipInfo.getCentralDirectoryOffset(); + long centralDirectorySize = zipInfo.getCentralDirectorySize(); + long eocdOffset = zipInfo.getEocdOffset(); + long centralDirectoryEndOffset = centralDirectoryStartOffset + centralDirectorySize; + if (eocdOffset != centralDirectoryEndOffset) { + throw new SignatureNotFoundException("ZIP Central Directory is not immediately followed by End of Central" + + "Directory. CD end: " + centralDirectoryEndOffset + ", EoCD start: " + eocdOffset); + } + if (centralDirectoryStartOffset < HAP_SIG_BLOCK_MIN_SIZE) { + throw new SignatureNotFoundException("Hap too small for Hap Signing Block. ZIP Central Directory offset: " + + centralDirectoryStartOffset); + } + long hapSigningBlockHeaderOffset = centralDirectoryStartOffset - HAP_SIG_BLOCK_HEADER_SIZE; + ByteBuffer hapSigningBlockHeader = hap.createByteBuffer(hapSigningBlockHeaderOffset, HAP_SIG_BLOCK_HEADER_SIZE); + hapSigningBlockHeader.order(ByteOrder.LITTLE_ENDIAN); + int blockCount = hapSigningBlockHeader.getInt(); + long hapSigBlockSize = hapSigningBlockHeader.getLong(); + long hapSignBlockMagicLo = hapSigningBlockHeader.getLong(); + long hapSignBlockMagicHi = hapSigningBlockHeader.getLong(); + int version = hapSigningBlockHeader.getInt(); + if ((hapSignBlockMagicLo != HAP_SIG_BLOCK_MAGIC_LO) + || (hapSignBlockMagicHi != HAP_SIG_BLOCK_MAGIC_HI)) { + throw new SignatureNotFoundException("No Hap Signing Block before ZIP Central Directory"); + } + if ((hapSigBlockSize < HAP_SIG_BLOCK_HEADER_SIZE) || (hapSigBlockSize > Integer.MAX_VALUE - 8)) { + throw new SignatureNotFoundException("Hap Signing Block size out of range: " + hapSigBlockSize); + } + int totalSize = (int) hapSigBlockSize; + long hapSigningBlockOffset = centralDirectoryStartOffset - totalSize; + 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 Pair.create(hapSigningBlockOffset, hapSigningBlockByteBuffer); + } +} \ No newline at end of file diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/utils/HashUtils.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/utils/HashUtils.java new file mode 100644 index 0000000000000000000000000000000000000000..2a2d5253bb20cd5e57c7694594500ca49b55b7f2 --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/utils/HashUtils.java @@ -0,0 +1,177 @@ +/* + * Copyright (c) 2021 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.utils; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.HashMap; + +/** + * The utils function used to get hash value. + * + * @since 2021/12/21 + */ +public class HashUtils { + private static final Logger LOGGER = LogManager.getLogger(HashUtils.class); + private static final int HASH_LEN = 4096; + private MessageDigest md; + + /** + * Get algorithm id of algorithm name. + * + * @param algMethod algorithm name + * @return algorithm ID + */ + public static int getHashAlgsId(String algMethod) { + int result = HashAlgs.USE_NONE; + if ("SHA-224".equals(algMethod)) { + result = HashAlgs.USE_SHA224; + } + if ("SHA-256".equals(algMethod)) { + result = HashAlgs.USE_SHA256; + } + if ("SHA-384".equals(algMethod)) { + result = HashAlgs.USE_SHA384; + } + if ("SHA-512".equals(algMethod)){ + result = HashAlgs.USE_SHA512; + } + return result; + } + + private static MessageDigest getMessageDigest(String algMethod) { + MessageDigest md = null; + try { + md = MessageDigest.getInstance(algMethod); + } catch (NoSuchAlgorithmException e) { + LOGGER.error("no such algorithm", e); + } + return md; + } + + private static byte[] getByteDigest(byte[] str, int count, String algMethod) { + MessageDigest md = getMessageDigest(algMethod); + md.update(str, 0, count); + return md.digest(); + } + + /** + * get digest used data in file. + * + * @param filePath input file path. + * @param algName algorithm name. + * @return byte array of digest. + */ + public static byte[] getFileDigest(String filePath, String algName) { + byte[] digest = null; + MessageDigest md = getMessageDigest(algName); + try (InputStream input = new FileInputStream(new File(filePath))) { + if (md == null) { + throw new NoSuchAlgorithmException(); + } + byte[] fileDate = new byte[HASH_LEN]; + int num = 0; + int byteCount; + HashMap hashList = new HashMap(); + while ((byteCount = input.read(fileDate)) > 0) { + byte[] dig = getByteDigest(fileDate, byteCount, algName); + hashList.put(num, dig); + num++; + } + if (hashList.isEmpty()) { + LOGGER.error("hashList is empty"); + return digest; + } + for (int i = 0; i < hashList.size(); i++) { + md.update(hashList.get(i)); + } + return md.digest(); + } catch (FileNotFoundException e) { + LOGGER.error("getFileDigest File Not Found failed."); + } catch (IOException e) { + LOGGER.error("getFileDigest IOException failed.", e); + } catch (NoSuchAlgorithmException e) { + LOGGER.error("MessageDigest is null", e); + } + return digest; + } + + /** + * Algorithm of Hash. + * + * @since 2021/12/21 + */ + class HashAlgs { + /** + * None + */ + public static final int USE_NONE = 0; + + /** + * The MD2 message digest. + */ + public static final int USE_MD2 = 1; + + /** + * The MD4 message digest. + */ + public static final int USE_MD4 = 2; + + /** + * The MD5 message digest. + */ + public static final int USE_MD5 = 3; + + /** + * The SSH-1 message digest. + */ + public static final int USE_SHA1 = 4; + + /** + * The SSH-224 message digest. + */ + public static final int USE_SHA224 = 5; + + /** + * The SSH-256 message digest. + */ + public static final int USE_SHA256 = 6; + + /** + * The SSH-384 message digest. + */ + public static final int USE_SHA384 = 7; + + /** + * The SSH-512 message digest. + */ + public static final int USE_SHA512 = 8; + + /** + * The RIPEMD-160 message digest. + */ + public static final int USE_RIPEMD160 = 9; + } +} + 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 new file mode 100644 index 0000000000000000000000000000000000000000..5f0e87eaa189e019d7b2c1fd6a502c9cc718e764 --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/utils/ParamConstants.java @@ -0,0 +1,246 @@ +/* + * Copyright (c) 2021 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.utils; + +/** + *Define const parameters + * + * @since 2021-12-13 + */ +public class ParamConstants { + /** + * error code of hap format error. + */ + public static final int HAP_FORMAT_ERROR = 20001; + + /** + * error code of hap parse error. + */ + public static final int HAP_PARSE_ERROR = 20002; + + /** + * error code of hap signatures error. + */ + public static final int HAP_SIGNATURE_ERROR = 20003; + + /** + * error code of hap signature block not found error. + */ + public static final int HAP_SIGNATURE_NOT_FOUND_ERROR = 20004; + + /** + * Algorithm name of sha-256. + */ + public static final String HAP_SIG_SCHEME_V256_DIGEST_ALGORITHM = "SHA-256"; + + /** + * Algorithm name of sha-384. + */ + public static final String HAP_SIG_SCHEME_V384_DIGEST_ALGORITHM = "SHA-384"; + + /** + * Algorithm name of sha-512. + */ + public static final String HAP_SIG_SCHEME_V512_DIGEST_ALGORITHM = "SHA-512"; + + /** + * Signature algorithm name of SHA256withECDSA. + */ + public static final String HAP_SIG_ALGORITHM_SHA256_ECDSA = "SHA256withECDSA"; + + /** + * Signature algorithm name of SHA384withECDSA. + */ + public static final String HAP_SIG_ALGORITHM_SHA384_ECDSA = "SHA384withECDSA"; + + /** + * Signature algorithm name of SHA512withECDSA. + */ + public static final String HAP_SIG_ALGORITHM_SHA512_ECDSA = "SHA512withECDSA"; + + /** + * Signature algorithm name of SHA256withRSA. + */ + public static final String HAP_SIG_ALGORITHM_SHA256_RSA = "SHA256withRSA"; + + /** + * Signature algorithm name of SHA384withRSA. + */ + public static final String HAP_SIG_ALGORITHM_SHA384_RSA = "SHA384withRSA"; + + /** + * Signature algorithm name of SHA256withRSA/PSS. + */ + public static final String HAP_SIG_ALGORITHM_SHA256_RSA_PSS = "SHA256withRSA/PSS"; + + /** + * Signature algorithm name of SHA384withRSA/PSS. + */ + public static final String HAP_SIG_ALGORITHM_SHA384_RSA_PSS = "SHA384withRSA/PSS"; + + /** + * Signature algorithm name of SHA512withRSA/PSS. + */ + public static final String HAP_SIG_ALGORITHM_SHA512_RSA_PSS = "SHA512withRSA/PSS"; + + /** + * Signature algorithm name of SHA256withRSAANDMGF1. + */ + public static final String HAP_SIG_ALGORITHM_SHA256_RSA_MGF1 = "SHA256withRSAANDMGF1"; + + /** + * Signature algorithm name of SHA384withRSAANDMGF1. + */ + public static final String HAP_SIG_ALGORITHM_SHA384_RSA_MGF1 = "SHA384withRSAANDMGF1"; + + /** + * Signature algorithm name of SHA512withRSAANDMGF1. + */ + public static final String HAP_SIG_ALGORITHM_SHA512_RSA_MGF1 = "SHA512withRSAANDMGF1"; + + /** + * Default value of zip-file align + */ + public static final String ALIGNMENT = "4"; + + /** + * Signature mode + */ + public static final String PARAM_SIGN_MODE = "mode"; + + /** + * Certificate revocation list + */ + public static final String PARAM_BASIC_CRL = "crl"; + + /** + * Hap-file's property, stored developer info + */ + public static final String PARAM_BASIC_PROPERTY = "property"; + + /** + * Hap-file's capability profile + */ + public static final String PARAM_BASIC_PROFILE = "profileFile"; + + /** + * Hap-file's proof-of-rotation + */ + public static final String PARAM_BASIC_PROOF = "proof"; + + /** + * Alignment + */ + public static final String PARAM_BASIC_ALIGNMENT = "a"; + + /** + * Private key used in signature + */ + public static final String PARAM_BASIC_PRIVATE_KEY = "keyAlias"; + + /** + * Unsigned file + */ + public static final String PARAM_BASIC_INPUT_FILE = "inFile"; + + /** + * Signed file + */ + public static final String PARAM_BASIC_OUTPUT_FILE = "outFile"; + + /** + * Algorithm name of signature + */ + public static final String PARAM_BASIC_SIGANTURE_ALG = "signAlg"; + + /** + * Url of signature server + */ + public static final String PARAM_REMOTE_SERVER = "signServer"; + + /** + * username used in remote sign mode + */ + public static final String PARAM_REMOTE_USERNAME = "username"; + + /** + * password used in remote sign mode + */ + public static final String PARAM_REMOTE_CODE = "password"; + + /** + * Local keystore path + */ + public static final String PARAM_LOCAL_JKS_KEYSTORE = "keystoreFile"; + + /** + * The password of keystore + */ + public static final String PARAM_LOCAL_JKS_KEYSTORE_CODE = "keystorePwd"; + + /** + * The key alias password + */ + public static final String PARAM_LOCAL_JKS_KEYALIAS_CODE = "keyPwd"; + + /** + * The certificate file path + */ + public static final String PARAM_LOCAL_PUBLIC_CERT = "appCertFile"; + + /** + * The path used to output certificate-chain + */ + public static final String PARAM_VERIFY_CERTCHAIN_FILE = "outCertChain"; + + /** + * The path used to output profile + */ + public static final String PARAM_VERIFY_PROFILE_FILE = "outProfile"; + + /** + * The path used to output proof-rotation file + */ + public static final String PARAM_VERIFY_PROOF_FILE = "outproof"; + + /** + * The path used to output property file + */ + public static final String PARAM_VERIFY_PROPERTY_FILE = "outproperty"; + + /** + * The config params of resign hap + */ + public static final String PARAM_RESIGN_CONFIG_FILE = "resignconfig"; + + /** + * Enumerated value of whether a profile is signed + */ + public enum ProfileSignFlag { + UNSIGNED_PROFILE("0"), + SIGNED_PROFILE("1"); + + private String signFlag; + + ProfileSignFlag(String signFlag) { + this.signFlag = signFlag; + } + + public String getSignFlag() { + return signFlag; + } + } +} \ No newline at end of file diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/utils/ParamProcessUtil.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/utils/ParamProcessUtil.java new file mode 100644 index 0000000000000000000000000000000000000000..8e87688490e17fdc86e5065dfbd61652a137398c --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/utils/ParamProcessUtil.java @@ -0,0 +1,98 @@ +/* + * Copyright (c) 2021 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.utils; + +import com.ohos.hapsigntool.hap.sign.SignatureAlgorithm; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + +/** + * Utils functions for process parameters. + * + * @since 2021/12/21 + */ +public class ParamProcessUtil { + private static final Logger LOGGER = LogManager.getLogger(ParamProcessUtil.class); + /** + * Use string array to init string set + * + * @param paramFileds input string array + * @return string set. + */ + public static Set initParamField(String[] paramFileds) { + return new HashSet(Arrays.asList(paramFileds)); + } + + /** + * Delete all files Recursively + * + * @param file file path. + */ + public static void delDir(File file) { + if (file.isDirectory()) { + File[] zFiles = file.listFiles(); + if (zFiles != null) { + for (File file2 : zFiles) { + delDir(file2); + } + } + } + try { + Files.delete(file.toPath()); + } catch (IOException e) { + LOGGER.warn("delete files failed!"); + } + } + + /** + * Get SignatureAlgorithm value by algorithm name. + * + * @param signatureAlgorithm algorithm name. + * @return SignatureAlgorithm value + */ + public static SignatureAlgorithm getSignatureAlgorithm(String signatureAlgorithm) { + SignatureAlgorithm result; + if (ParamConstants.HAP_SIG_ALGORITHM_SHA256_ECDSA.equalsIgnoreCase(signatureAlgorithm)) { + result = SignatureAlgorithm.ECDSA_WITH_SHA256; + } else if (ParamConstants.HAP_SIG_ALGORITHM_SHA384_ECDSA.equalsIgnoreCase(signatureAlgorithm)) { + result = SignatureAlgorithm.ECDSA_WITH_SHA384; + } else if (ParamConstants.HAP_SIG_ALGORITHM_SHA512_ECDSA.equalsIgnoreCase(signatureAlgorithm)) { + result = SignatureAlgorithm.ECDSA_WITH_SHA512; + } else if (ParamConstants.HAP_SIG_ALGORITHM_SHA256_RSA_PSS.equalsIgnoreCase(signatureAlgorithm)) { + result = SignatureAlgorithm.RSA_PSS_WITH_SHA256; + } else if (ParamConstants.HAP_SIG_ALGORITHM_SHA384_RSA_PSS.equalsIgnoreCase(signatureAlgorithm)) { + result = SignatureAlgorithm.RSA_PSS_WITH_SHA384; + } else if (ParamConstants.HAP_SIG_ALGORITHM_SHA512_RSA_PSS.equalsIgnoreCase(signatureAlgorithm)) { + result = SignatureAlgorithm.RSA_PSS_WITH_SHA512; + } else if (ParamConstants.HAP_SIG_ALGORITHM_SHA256_RSA_MGF1.equalsIgnoreCase(signatureAlgorithm)) { + result = SignatureAlgorithm.RSA_PSS_WITH_SHA256; + } else if (ParamConstants.HAP_SIG_ALGORITHM_SHA384_RSA_MGF1.equalsIgnoreCase(signatureAlgorithm)) { + result = SignatureAlgorithm.RSA_PSS_WITH_SHA384; + } else if (ParamConstants.HAP_SIG_ALGORITHM_SHA512_RSA_MGF1.equalsIgnoreCase(signatureAlgorithm)) { + result = SignatureAlgorithm.RSA_PSS_WITH_SHA512; + } else { + throw new IllegalArgumentException("Unsupported signature algorithm: " + signatureAlgorithm); + } + return result; + } +} diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/utils/StringUtils.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/utils/StringUtils.java new file mode 100644 index 0000000000000000000000000000000000000000..202bade044532022625a4dc31ca6cce0dedc0df0 --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/utils/StringUtils.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2021-2022 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.utils; + +/** + * StringUtils. + * + * @since 2021/12/28 + */ +public final class StringUtils { + private StringUtils() { + } + + /** + * Check string is empty. + * + * @param cs input string + * @return true, if cs is empty + */ + public static boolean isEmpty(final CharSequence cs) { + return cs == null || cs.length() == 0; + } +} diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/utils/ValidateUtils.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/utils/ValidateUtils.java new file mode 100644 index 0000000000000000000000000000000000000000..761b3c23ba8a1fa18320ccc100c38d9f6aade450 --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/utils/ValidateUtils.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2021-2022 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.utils; + +import com.ohos.hapsigntool.error.CustomException; +import com.ohos.hapsigntool.error.ERROR; + +/** + * ValidateUtils. + * + * @since 2021/12/28 + */ +public final class ValidateUtils { + private ValidateUtils() { + } + + /** + * Throw exception if not true. + * + * @param isMatch Want a true value + * @param error Error enum to throw + * @param errorMsg Error msg to throw + */ + public static void throwIfNotMatches(boolean isMatch, ERROR error, String errorMsg) throws CustomException { + if (!isMatch) { + CustomException.throwException(error, errorMsg); + } + } + + /** + * Throw exception if true. + * + * @param isMatch Want a false value + * @param error Error enum to throw + * @param errorMsg Error msg to throw + */ + public static void throwIfMatches(boolean isMatch, ERROR error, String errorMsg) throws CustomException { + throwIfNotMatches(!isMatch, error, errorMsg); + } +} diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/zip/ByteBufferZipDataInput.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/zip/ByteBufferZipDataInput.java new file mode 100644 index 0000000000000000000000000000000000000000..f3df89933a87f8c32f035a9078165328a5889f90 --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/zip/ByteBufferZipDataInput.java @@ -0,0 +1,103 @@ +/* + * Copyright (c) 2021 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.io.IOException; +import java.nio.ByteBuffer; + +/** + * ByteBuffer implementation of ZipDataInput + * + * @since 2021/12/20 + */ +public class ByteBufferZipDataInput implements ZipDataInput { + private final ByteBuffer buffer; + private final int sourceSize; + + public ByteBufferZipDataInput(ByteBuffer buffer) { + this(buffer, true); + } + + private ByteBufferZipDataInput(ByteBuffer buffer, boolean needSlice) { + this.buffer = needSlice ? buffer.slice() : buffer; + this.sourceSize = this.buffer.remaining(); + } + + @Override + public long size() { + return sourceSize; + } + + @Override + public void copyTo(long offset, long size, ZipDataOutput output) throws IOException { + if (size < 0 || size > sourceSize) { + throw new IndexOutOfBoundsException("size: " + size + ", source size: " + sourceSize); + } + output.write(createByteBuffer(offset, (int) size)); + } + + @Override + public void copyTo(long offset, int size, ByteBuffer buffer) throws IOException { + buffer.put(createByteBuffer(offset, size)); + } + + @Override + public ByteBuffer createByteBuffer(long offset, int size) { + checkChunkValid(offset, size); + int position = (int) offset; + int limit = position + size; + synchronized (buffer) { + buffer.position(0); + buffer.limit(limit); + buffer.position(position); + return buffer.slice(); + } + } + + @Override + public ZipDataInput slice(long offset, long size) { + if (offset == 0 && size == sourceSize) { + return this; + } + if (size < 0 || size > sourceSize) { + throw new IndexOutOfBoundsException("size: " + size + ", source size: " + sourceSize); + } + ByteBuffer byteBuffer = createByteBuffer(offset, (int) size); + return new ByteBufferZipDataInput(byteBuffer, false); + } + + private void checkChunkValid(long offset, long size) { + if (offset < 0) { + throw new IndexOutOfBoundsException("offset: " + offset); + } + if (size < 0) { + throw new IndexOutOfBoundsException("sourceSize: " + size); + } + if (offset > sourceSize) { + throw new IndexOutOfBoundsException( + "offset (" + offset + ") > source sourceSize (" + sourceSize + ")"); + } + long endOffset = offset + size; + if (endOffset < offset) { + throw new IndexOutOfBoundsException( + "offset (" + offset + ") + sourceSize (" + size + ") overflow"); + } + if (endOffset > sourceSize) { + throw new IndexOutOfBoundsException( + "offset (" + offset + ") + sourceSize (" + size + ") > source sourceSize (" + sourceSize + ")"); + } + } +} \ No newline at end of file diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/zip/MessageDigestZipDataOutput.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/zip/MessageDigestZipDataOutput.java new file mode 100644 index 0000000000000000000000000000000000000000..55632873aa92365070230a53b7e2d83a9c0964bc --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/zip/MessageDigestZipDataOutput.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2021 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.io.IOException; +import java.nio.ByteBuffer; +import java.security.MessageDigest; + +/** + * MessageDigest implementation of ZipDataOutput + * + * @since 2021/12/20 + */ +public class MessageDigestZipDataOutput implements ZipDataOutput { + private final MessageDigest[] messageDigests; + + public MessageDigestZipDataOutput(MessageDigest[] digests) { + messageDigests = digests; + } + + @Override + public void write(byte[] buffer, int offset, int length) throws IOException { + for (MessageDigest messageDigest : messageDigests) { + messageDigest.update(buffer, offset, length); + } + } + + @Override + public void write(ByteBuffer buffer) throws IOException { + int position = buffer.position(); + for (MessageDigest messageDigest : messageDigests) { + buffer.position(position); + messageDigest.update(buffer); + } + } +} diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/zip/RandomAccessFileZipDataInput.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/zip/RandomAccessFileZipDataInput.java new file mode 100644 index 0000000000000000000000000000000000000000..11c7130948ecc70b4fdb0715584f00537c9e99b4 --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/zip/RandomAccessFileZipDataInput.java @@ -0,0 +1,160 @@ +/* + * Copyright (c) 2021 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.io.IOException; +import java.io.RandomAccessFile; +import java.nio.BufferOverflowException; +import java.nio.ByteBuffer; +import java.nio.channels.FileChannel; + +/** + * RandomAccessFile implementation of ZipDataInput + * + * @since 2021/12/20 + */ +public class RandomAccessFileZipDataInput implements ZipDataInput { + private static final int MAX_READ_BLOCK_SIZE = 1024 * 1024; + private static final byte[] READ_BUFFER = new byte[MAX_READ_BLOCK_SIZE]; + private final RandomAccessFile file; + private final FileChannel fileChannel; + private final long startIndex; + private final long size; + + public RandomAccessFileZipDataInput(RandomAccessFile file) { + this.file = file; + this.fileChannel = file.getChannel(); + this.startIndex = 0; + this.size = -1; + } + + public RandomAccessFileZipDataInput(RandomAccessFile file, long offset, long size) { + if (offset < 0) { + throw new IndexOutOfBoundsException("offset: " + offset); + } + if (size < 0) { + throw new IndexOutOfBoundsException("size: " + size); + } + this.file = file; + this.fileChannel = file.getChannel(); + this.startIndex = offset; + this.size = size; + } + + @Override + public long size() { + if (size == -1) { + try { + return file.length(); + } catch (IOException e) { + return 0; + } + } + return size; + } + + @Override + public void copyTo(long offset, long size, ZipDataOutput output) throws IOException { + long srcSize = size(); + checkBoundValid(offset, size, srcSize); + if (size == 0) { + return; + } + long offsetInFile = startIndex + offset; + long remaining = size; + int blockSize; + while (remaining > 0) { + blockSize = (int) Math.min(remaining, READ_BUFFER.length); + synchronized (file) { + file.seek(offsetInFile); + file.readFully(READ_BUFFER, 0, blockSize); + output.write(READ_BUFFER, 0, blockSize); + } + offsetInFile += blockSize; + remaining -= blockSize; + } + } + + @Override + public void copyTo(long offset, int size, ByteBuffer buffer) throws IOException { + long srcSize = size(); + checkBoundValid(offset, size, srcSize); + if (size == 0) { + return; + } + if (size > buffer.remaining()) { + throw new BufferOverflowException(); + } + long offsetInFile = startIndex + offset; + int remaining = size; + int originalLimit = buffer.limit(); + try { + buffer.limit(buffer.position() + size); + int readSize; + while (remaining > 0) { + synchronized (file) { + fileChannel.position(offsetInFile); + readSize = fileChannel.read(buffer); + } + offsetInFile += readSize; + remaining -= readSize; + } + } finally { + buffer.limit(originalLimit); + } + } + + @Override + public ByteBuffer createByteBuffer(long offset, int size) throws IOException { + if (size < 0) { + throw new IndexOutOfBoundsException("size: " + size); + } + ByteBuffer buffer = ByteBuffer.allocate(size); + copyTo(offset, size, buffer); + buffer.flip(); + return buffer; + } + + @Override + public ZipDataInput slice(long offset, long size) { + long srcSize = size(); + checkBoundValid(offset, size, srcSize); + if (offset == 0 && size == srcSize) { + return this; + } + return new RandomAccessFileZipDataInput(file, offset + startIndex, size); + } + + private void checkBoundValid(long offset, long size, long sourceSize) { + if (offset < 0) { + throw new IndexOutOfBoundsException("offset: " + offset); + } + if (size < 0) { + throw new IndexOutOfBoundsException("size: " + size); + } + if (offset > sourceSize) { + throw new IndexOutOfBoundsException("offset (" + offset + ") > sourceSize (" + sourceSize + ")"); + } + long endOffset = offset + size; + if (endOffset < offset) { + throw new IndexOutOfBoundsException("offset (" + offset + ") + size (" + size + ") overflow"); + } + if (endOffset > sourceSize) { + throw new IndexOutOfBoundsException("offset (" + offset + ") + size (" + size + + ") > sourceSize (" + sourceSize + ")"); + } + } +} diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/zip/RandomAccessFileZipDataOutput.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/zip/RandomAccessFileZipDataOutput.java new file mode 100644 index 0000000000000000000000000000000000000000..ff7d345d8f324b1c567946d47dfe0fddd7a4add2 --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/zip/RandomAccessFileZipDataOutput.java @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2021 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.io.IOException; +import java.io.RandomAccessFile; +import java.nio.ByteBuffer; +import java.nio.channels.FileChannel; + +/** + * RandomAccessFile implementation of ZipDataOutput + * + * @since 2021/12/20 + */ +public class RandomAccessFileZipDataOutput implements ZipDataOutput { + private final RandomAccessFile file; + private final FileChannel fileChannel; + private long position; + + public RandomAccessFileZipDataOutput(RandomAccessFile file) { + this(file, 0); + } + + public RandomAccessFileZipDataOutput(RandomAccessFile file, long startPosition) { + if (file == null) { + throw new NullPointerException("file is null"); + } + if (startPosition < 0) { + throw new IllegalArgumentException("Invalid start position: " + startPosition); + } + this.file = file; + this.fileChannel = file.getChannel(); + this.position = startPosition; + } + + @Override + public void write(byte[] buffer, int offset, int length) throws IOException { + if (offset < 0) { + throw new IndexOutOfBoundsException("offset: " + offset); + } + if (offset > buffer.length) { + throw new IndexOutOfBoundsException("offset (" + offset + ") > buffer length(" + buffer.length + ")"); + } + if (length == 0) { + return; + } + synchronized (file) { + file.seek(position); + file.write(buffer, offset, length); + position += length; + } + } + + @Override + public void write(ByteBuffer buffer) throws IOException { + int length = buffer.remaining(); + if (length == 0) { + return; + } + synchronized (file) { + file.seek(position); + while (buffer.hasRemaining()) { + fileChannel.write(buffer); + } + position += length; + } + } +} diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/zip/ZipDataInput.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/zip/ZipDataInput.java new file mode 100644 index 0000000000000000000000000000000000000000..e992fc063176824c22f9b18a951b29adb7d220bf --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/zip/ZipDataInput.java @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2021 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.io.IOException; +import java.nio.ByteBuffer; + +/** + * ZipDataInput interface + * + * @since 2021/12/20 + */ +public interface ZipDataInput { + /** + * return how many bytes contained in this data input + * + * @return this data input size + */ + long size(); + + /** + * Copy the specified data block into the destination ZipDataOutput + * + * @param offset offset index at the ZipDataInput + * @param size size of the data block + * @param output the destination ZipDataOutput + * @throws IOException when IO error occurred + */ + void copyTo(long offset, long size, ZipDataOutput output) throws IOException; + + /** + * Copy the specified data block into the destination ZipDataOutput + * + * @param offset offset index at the ZipDataInput + * @param size size of the data block + * @param buffer the destination ZipDataOutput + * @throws IOException when IO error occurred + */ + void copyTo(long offset, int size, ByteBuffer buffer) throws IOException; + + /** + * Create a ByteBuffer which contain the specified data block from this ZipDataInput + * + * @param offset offset index at the ZipDataInput + * @param size size of the data block + * @return a ByteBuffer + * @throws IOException when IO error occurred + */ + ByteBuffer createByteBuffer(long offset, int size) throws IOException; + + /** + * Create a new ZipDataInput whose content is shared by this ZipDataInput + * @param offset offset index at the ZipDataInput + * @param size size of the data block + * @return new ZipDataInput + */ + ZipDataInput slice(long offset, long size); +} diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/zip/ZipDataOutput.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/zip/ZipDataOutput.java new file mode 100644 index 0000000000000000000000000000000000000000..829c68626cef0b0f6d2ea5f028e5e0cd3fd38c48 --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/zip/ZipDataOutput.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2021 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.io.IOException; +import java.nio.ByteBuffer; + +/** + * ZipDataOutput interface + * + * @since 2021/12/20 + */ +public interface ZipDataOutput { + /** + * Write data into this ZipDataOutput + * @param buffer data bytes + * @param offset offset index of data bytes + * @param length the number of bytes to use, starting at offset index + * @throws IOException when IO error occurred + */ + void write(byte[] buffer, int offset, int length) throws IOException; + + /** + * Write all remaining data in the ByteBuffer into this ZipDataOutput + * + * @param buffer data bytes + * @throws IOException when IO error occurred + */ + void write(ByteBuffer buffer) throws IOException; +} diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/zip/ZipFileInfo.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/zip/ZipFileInfo.java new file mode 100644 index 0000000000000000000000000000000000000000..33e9d60ffd958c61b02863fdecea63bc6f49e563 --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/zip/ZipFileInfo.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2021 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; + +/** + * Information of ZIP file + * + * @since 2021/12/20 + */ +public class ZipFileInfo { + private final long centralDirectoryOffset; + private final int centralDirectorySize; + private final int centralDirectoryEntryCount; + private final long eocdOffset; + private final ByteBuffer eocd; + + public ZipFileInfo(long centralDirectoryOffset, int centralDirectorySize, int centralDirectoryEntryCount, + long eocdOffset, ByteBuffer eocd) { + this.centralDirectoryOffset = centralDirectoryOffset; + this.centralDirectorySize = centralDirectorySize; + this.centralDirectoryEntryCount = centralDirectoryEntryCount; + this.eocdOffset = eocdOffset; + this.eocd = eocd; + } + + public long getCentralDirectoryOffset() { + return centralDirectoryOffset; + } + + public int getCentralDirectorySize() { + return centralDirectorySize; + } + + public int getCentralDirectoryEntryCount() { + return centralDirectoryEntryCount; + } + + public long getEocdOffset() { + return eocdOffset; + } + + public ByteBuffer getEocd() { + return eocd; + } +} \ No newline at end of file diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/zip/ZipUtils.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/zip/ZipUtils.java new file mode 100644 index 0000000000000000000000000000000000000000..6e669b5c46688233a29181694d63a43e43d18d29 --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/zip/ZipUtils.java @@ -0,0 +1,240 @@ +/* + * Copyright (c) 2021 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.hap.entity.Pair; +import com.ohos.hapsigntool.hap.exception.HapFormatException; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +/** + * Utils functions of zip-files. + * + * @since 2021/12/22 + */ +public class ZipUtils { + private static final int ZIP_EOCD_SEGMENT_MIN_SIZE = 22; + + private static final int ZIP_EOCD_SEGMENT_FLAG = 0x06054b50; + + private static final int ZIP_CENTRAL_DIR_COUNT_OFFSET_IN_EOCD = 10; + + private static final int ZIP_CENTRAL_DIR_SIZE_OFFSET_IN_EOCD = 12; + + private static final int ZIP_CENTRAL_DIR_OFFSET_IN_EOCD = 16; + + private static final int ZIP_EOCD_COMMENT_LENGTH_OFFSET = 20; + + private static final int ZIP64_EOCD_LOCATOR_SIZE = 20; + + private static final int ZIP64_EOCD_LOCATOR_SIG = 0x07064b50; + + private static final int UINT16_MAX_VALUE = 0xffff; + + private static final long UINT32_MAX_VALUE = 0xffffffffL; + + /** + * This function find Eocd by searching Eocd flag from input buffer(searchBuffer) and + * making sure the comment length is equal to the expected value + * + * @param searchBuffer data buffer used to search EOCD. + * @return offset from buffer start point. + */ + public static int findEocdInSearchBuffer(ByteBuffer searchBuffer) { + checkBufferIsLittleEndian(searchBuffer); + /* + * Eocd format: + * 4-bytes: End of central directory flag + * 2-bytes: Number of this disk + * 2-bytes: Number of the disk with the start of 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 + * 4-bytes: Size of central directory + * 4-bytes: offset of central directory in zip file + * 2-bytes: ZIP file comment length, the value n is in the range of [0, 65535] + * n-bytes: ZIP Comment block data + */ + int searchBufferSize = searchBuffer.capacity(); + if (searchBufferSize < ZIP_EOCD_SEGMENT_MIN_SIZE) { + return -1; + } + + int currentOffset = searchBufferSize - ZIP_EOCD_SEGMENT_MIN_SIZE; + while (currentOffset >= 0) { + if (searchBuffer.getInt(currentOffset) == ZIP_EOCD_SEGMENT_FLAG) { + int commentLength = getUInt16FromBuffer(searchBuffer, currentOffset + ZIP_EOCD_COMMENT_LENGTH_OFFSET); + int expectedCommentLength = searchBufferSize - ZIP_EOCD_SEGMENT_MIN_SIZE - currentOffset; + if (commentLength == expectedCommentLength) { + return currentOffset; + } + } + currentOffset--; + } + return -1; + } + + /** + * Check whether the zip is zip64 by finding ZIP64 End of Central Directory Locator. + * ZIP64 End of Central Directory Locator immediately precedes the ZIP End of Central Directory. + * + * @param zip object of RandomAccessFile for zip-file. + * @param zipEocdOffset offset of the ZIP EOCD in the file. + * @return true, if ZIP64 End of Central Directory Locator is present. + * @throws IOException read file error. + */ + public static boolean checkZip64EoCDLocatorIsPresent(ZipDataInput zip, long zipEocdOffset) throws IOException { + long locatorPos = zipEocdOffset - ZIP64_EOCD_LOCATOR_SIZE; + if (locatorPos < 0) { + return false; + } + ByteBuffer byteBuffer = zip.createByteBuffer(locatorPos, 4); + byteBuffer.order(ByteOrder.LITTLE_ENDIAN); + return byteBuffer.getInt() == ZIP64_EOCD_LOCATOR_SIG; + } + + /** + * Get offset value of Central Directory from End of Central Directory Record. + * + * @param eocd buffer of End of Central Directory Record + * @return offset value of Central Directory. + */ + public static long getCentralDirectoryOffset(ByteBuffer eocd) { + checkBufferIsLittleEndian(eocd); + return getUInt32FromBuffer(eocd, eocd.position() + ZIP_CENTRAL_DIR_OFFSET_IN_EOCD); + } + + /** + * set offset value of Central Directory to End of Central Directory Record. + * + * @param eocd buffer of End of Central Directory Record. + * @param offset offset value of Central Directory. + */ + public static void setCentralDirectoryOffset(ByteBuffer eocd, long offset) { + checkBufferIsLittleEndian(eocd); + setUInt32ToBuffer(eocd, eocd.position() + ZIP_CENTRAL_DIR_OFFSET_IN_EOCD, offset); + } + + /** + * Get size of Central Directory from End of Central Directory Record. + * + * @param eocd buffer of End of Central Directory Record. + * @return size of Central Directory. + */ + public static long getCentralDirectorySize(ByteBuffer eocd) { + checkBufferIsLittleEndian(eocd); + return getUInt32FromBuffer(eocd, eocd.position() + ZIP_CENTRAL_DIR_SIZE_OFFSET_IN_EOCD); + } + + /** + * Get total count of Central Directory from End of Central Directory Record. + * + * @param eocd buffer of End of Central Directory Record. + * @return size of Central Directory. + */ + public static int getCentralDirectoryCount(ByteBuffer eocd) { + checkBufferIsLittleEndian(eocd); + return getUInt16FromBuffer(eocd, eocd.position() + ZIP_CENTRAL_DIR_COUNT_OFFSET_IN_EOCD); + } + + private static void checkBufferIsLittleEndian(ByteBuffer buffer) { + if (buffer.order() == ByteOrder.LITTLE_ENDIAN) { + return; + } + throw new IllegalArgumentException("ByteBuffer is not little endian"); + } + + static int getUInt16FromBuffer(ByteBuffer buffer, int offset) { + return buffer.getShort(offset) & 0xffff; + } + + static long getUInt32FromBuffer(ByteBuffer buffer, int offset) { + return buffer.getInt(offset) & UINT32_MAX_VALUE; + } + + private static void setUInt32ToBuffer(ByteBuffer buffer, int offset, long value) { + if ((value < 0) || (value > UINT32_MAX_VALUE)) { + throw new IllegalArgumentException("uint32 value of out range: " + value); + } + buffer.putInt(buffer.position() + offset, (int) value); + } + + /** + * Find the key information for parsing the zip file. + * + * @param in zip file + * @return the key information for parsing the zip file. + * @throws IOException file operation error + * @throws HapFormatException hap file format error + */ + public static ZipFileInfo findZipInfo(ZipDataInput in) throws IOException, HapFormatException { + Pair eocdOffsetAndBuffer = findEocdInHap(in); + if (eocdOffsetAndBuffer == null) { + throw new HapFormatException("ZIP End of Central Directory not found"); + } + long eocdOffset = eocdOffsetAndBuffer.getFirst(); + ByteBuffer eocdBuffer = eocdOffsetAndBuffer.getSecond().order(ByteOrder.LITTLE_ENDIAN); + long centralDirectoryStartOffset = ZipUtils.getCentralDirectoryOffset(eocdBuffer); + if (centralDirectoryStartOffset > eocdOffset) { + throw new HapFormatException("ZIP Central Directory start offset(" + centralDirectoryStartOffset + + ") larger than ZIP End of Central Directory offset(" + eocdOffset + ")"); + } + long centralDirectorySizeLong = ZipUtils.getCentralDirectorySize(eocdBuffer); + if (centralDirectorySizeLong > Integer.MAX_VALUE) { + throw new HapFormatException("ZIP Central Directory out of range: " + centralDirectorySizeLong); + } + int centralDirectorySize = (int) centralDirectorySizeLong; + long centralDirectoryEndOffset = centralDirectoryStartOffset + centralDirectorySizeLong; + if (centralDirectoryEndOffset != eocdOffset) { + throw new HapFormatException("ZIP Central Directory end offset(" + centralDirectoryEndOffset + ") " + + " different from ZIP End of Central Directory offset(" + eocdOffset + ")"); + } + int centralDirectoryCount = ZipUtils.getCentralDirectoryCount(eocdBuffer); + return new ZipFileInfo(centralDirectoryStartOffset, centralDirectorySize, centralDirectoryCount, eocdOffset, + eocdBuffer); + } + + private static Pair findEocdInHap(ZipDataInput in) throws IOException { + Pair eocdInHap = findEocdInHap(in, 0); + if (eocdInHap != null) { + return eocdInHap; + } + return findEocdInHap(in, UINT16_MAX_VALUE); + } + + private static Pair findEocdInHap(ZipDataInput zip, int maxCommentSize) throws IOException { + if ((maxCommentSize < 0) || (maxCommentSize > UINT16_MAX_VALUE)) { + throw new IllegalArgumentException("maxCommentSize: " + maxCommentSize); + } + long fileSize = zip.size(); + if (fileSize < ZIP_EOCD_SEGMENT_MIN_SIZE) { + throw new IllegalArgumentException("file length " + fileSize + " is too smaller"); + } + int finalMaxCommentSize = (int) Math.min(maxCommentSize, fileSize - ZIP_EOCD_SEGMENT_MIN_SIZE); + int searchBufferSize = finalMaxCommentSize + ZIP_EOCD_SEGMENT_MIN_SIZE; + long bufferOffsetInFile = fileSize - searchBufferSize; + ByteBuffer searchEocdBuffer = zip.createByteBuffer(bufferOffsetInFile, searchBufferSize); + searchEocdBuffer.order(ByteOrder.LITTLE_ENDIAN); + int eocdOffsetInSearchBuffer = findEocdInSearchBuffer(searchEocdBuffer); + if (eocdOffsetInSearchBuffer == -1) { + return null; + } + searchEocdBuffer.position(eocdOffsetInSearchBuffer); + ByteBuffer eocdBuffer = searchEocdBuffer.slice().order(ByteOrder.LITTLE_ENDIAN); + return Pair.create(bufferOffsetInFile + eocdOffsetInSearchBuffer, eocdBuffer); + } +} \ No newline at end of file diff --git a/hapsigntool/hap_sign_tool_lib/src/test/java/com/ohos/hapsigntool/CertTest.java b/hapsigntool/hap_sign_tool_lib/src/test/java/com/ohos/hapsigntool/CertTest.java new file mode 100644 index 0000000000000000000000000000000000000000..df6db55d82171293c3c9125f3330381a39933285 --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/test/java/com/ohos/hapsigntool/CertTest.java @@ -0,0 +1,221 @@ +/* + * Copyright (c) 2021-2022 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; + +import com.ohos.hapsigntool.api.LocalizationAdapter; +import com.ohos.hapsigntool.api.model.Options; +import com.ohos.hapsigntool.cert.CertTools; +import com.ohos.hapsigntool.key.KeyPairTools; +import com.ohos.hapsigntool.utils.CertUtils; +import org.bouncycastle.asn1.x500.X500Name; +import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.junit.jupiter.api.MethodOrderer; +import org.junit.jupiter.api.Order; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestMethodOrder; +import org.junit.platform.commons.logging.Logger; +import org.junit.platform.commons.logging.LoggerFactory; + +import java.security.InvalidKeyException; +import java.security.KeyPair; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +import java.security.Security; +import java.security.SignatureException; +import java.security.cert.CertificateException; +import java.security.cert.CertificateExpiredException; +import java.security.cert.CertificateNotYetValidException; +import java.security.cert.X509Certificate; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * Test various certificate generation status. + * + * @since 2021/12/28 + */ +@TestMethodOrder(MethodOrderer.OrderAnnotation.class) +public class CertTest { + /** + * The certificate is valid for 365 days. + */ + public static final int VALIDITY_365 = 365; + /** + * Params SHA384withRSA. + */ + public static final String SHA_384_WITH_RSA = "SHA384withRSA"; + /** + * Params CN=Application Signature Service CA. + */ + public static final String APP_CA = "C=CN,O=OpenHarmony,OU=OpenHarmony Community," + + "CN=Application Signature Service CA"; + /** + * Params CN=App1 Release. + */ + public static final String APP1_RELEASE = "C=CN,O=OpenHarmony,OU=OpenHarmony Community,CN=App1 Release"; + + static { + Security.addProvider(new BouncyCastleProvider()); + } + + /** + * Add log info. + */ + private final Logger logger = LoggerFactory.getLogger(CertTest.class); + /** + * Generate keystore file. + */ + private final KeyPair keyPair = KeyPairTools.generateKeyPair(KeyPairTools.RSA, KeyPairTools.RSA_2048); + + @Order(1) + @Test + public void testRootCaCert() { + String subject = "C=CN,O=OpenHarmony,OU=OpenHarmony Community,CN= Root CA"; + X500Name subName = CertUtils.buildDN(subject); + byte[] csr = generateCsrParameters(subName); + + Options options = new Options(); + options.put(Options.ISSUER, subject); + options.put(Options.SIGN_ALG, SHA_384_WITH_RSA); + options.put(Options.VALIDITY, VALIDITY_365); + options.put(Options.BASIC_CONSTRAINTS_PATH_LEN, 0); + X509Certificate rootCaCert = CertTools.generateRootCaCert(keyPair, csr, new LocalizationAdapter(options)); + + try { + rootCaCert.verify(keyPair.getPublic()); + } catch (CertificateException | NoSuchAlgorithmException | InvalidKeyException + | NoSuchProviderException | SignatureException exception) { + logger.error(exception, () -> exception.getMessage()); + } + assertNotNull(rootCaCert); + try { + rootCaCert.checkValidity(); + } catch (CertificateExpiredException | CertificateNotYetValidException exception) { + logger.error(exception, () -> exception.getMessage()); + } + assertEquals(subName.toString(), rootCaCert.getSubjectDN().toString()); + assertTrue(SHA_384_WITH_RSA.equalsIgnoreCase(rootCaCert.getSigAlgName())); + + try { + options.put(Options.ISSUER, null); + options.put(Options.SIGN_ALG, ""); + rootCaCert = CertTools.generateRootCaCert(keyPair, csr, new LocalizationAdapter(options)); + assertNull(rootCaCert); + } catch (Exception exception) { + logger.error(exception, () -> exception.getMessage()); + } + } + + @Order(2) + @Test + public void testSubCaCert() { + + X500Name subName = CertUtils.buildDN(APP1_RELEASE); + byte[] csr = generateCsrParameters(subName); + Options options = new Options(); + options.put(Options.SUBJECT, APP1_RELEASE); + options.put(Options.ISSUER, APP_CA); + options.put(Options.SIGN_ALG, SHA_384_WITH_RSA); + options.put(Options.VALIDITY, VALIDITY_365); + options.put(Options.BASIC_CONSTRAINTS_PATH_LEN, 0); + X509Certificate subCaCert = CertTools.generateSubCert(keyPair, csr, new LocalizationAdapter(options)); + try { + subCaCert.verify(keyPair.getPublic()); + } catch (CertificateException | NoSuchAlgorithmException | InvalidKeyException + | NoSuchProviderException | SignatureException exception) { + logger.info(exception, () -> exception.getMessage()); + } + assertNotNull(subCaCert); + try { + subCaCert.checkValidity(); + } catch (CertificateExpiredException | CertificateNotYetValidException exception) { + logger.info(exception, () -> exception.getMessage()); + } + assertEquals(subName.toString(), subCaCert.getSubjectDN().toString()); + assertTrue(SHA_384_WITH_RSA.equalsIgnoreCase(subCaCert.getSigAlgName())); + + try { + options.put(Options.ISSUER, null); + options.put(Options.SIGN_ALG, ""); + subCaCert = CertTools.generateSubCert(keyPair, csr, new LocalizationAdapter(options)); + assertNull(subCaCert); + } catch (Exception exception) { + logger.info(exception, () -> exception.getMessage()); + } + } + + @Order(3) + @Test + public void testAppCert() { + X500Name subName = CertUtils.buildDN(APP1_RELEASE); + byte[] csr = generateCsrParameters(subName); + Options options = new Options(); + options.put(Options.SUBJECT, APP1_RELEASE); + options.put(Options.ISSUER, APP_CA); + options.put(Options.SIGN_ALG, SHA_384_WITH_RSA); + options.put(Options.VALIDITY, VALIDITY_365); + X509Certificate appCert = CertTools.generateEndCert(keyPair, csr, new LocalizationAdapter(options)); + try { + appCert.verify(keyPair.getPublic()); + } catch (CertificateException | NoSuchAlgorithmException | InvalidKeyException + | NoSuchProviderException | SignatureException exception) { + logger.info(exception, () -> exception.getMessage()); + } + assertNotNull(appCert); + try { + appCert.checkValidity(); + } catch (CertificateExpiredException | CertificateNotYetValidException exception) { + logger.info(exception, () -> exception.getMessage()); + } + assertEquals(subName.toString(), appCert.getSubjectDN().toString()); + assertTrue(SHA_384_WITH_RSA.equalsIgnoreCase(appCert.getSigAlgName())); + + try { + options.put(Options.ISSUER, null); + options.put(Options.SIGN_ALG, ""); + appCert = CertTools.generateEndCert(keyPair, csr, new LocalizationAdapter(options)); + assertNull(appCert); + } catch (Exception exception) { + logger.info(exception, () -> exception.getMessage()); + } + } + + @Order(4) + @Test + public void testCsrTemplate() { + X500Name name = new X500Name(APP1_RELEASE); + byte[] csr = generateCsrParameters(name); + String csrTemplate = CertUtils.toCsrTemplate(csr); + assertNotNull(csrTemplate); + assertTrue(csrTemplate.startsWith("-----BEGIN NEW CERTIFICATE REQUEST-----\n")); + assertTrue(csrTemplate.endsWith("\n-----END NEW CERTIFICATE REQUEST-----\n")); + + try { + csrTemplate = CertUtils.toCsrTemplate(null); + assertNull(csrTemplate); + } catch (Exception exception) { + logger.info(exception, () -> exception.getMessage()); + } + } + + private byte[] generateCsrParameters(X500Name name) { + byte[] csr = CertTools.generateCsr(keyPair, SHA_384_WITH_RSA, name); + return csr; + } +} diff --git a/hapsigntool/hap_sign_tool_lib/src/test/java/com/ohos/hapsigntool/KeyPairTest.java b/hapsigntool/hap_sign_tool_lib/src/test/java/com/ohos/hapsigntool/KeyPairTest.java new file mode 100644 index 0000000000000000000000000000000000000000..cfe21fa39127f77998fed7902040ee622d4c9003 --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/test/java/com/ohos/hapsigntool/KeyPairTest.java @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2021-2022 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; + +import com.ohos.hapsigntool.key.KeyPairTools; +import org.junit.jupiter.api.Test; +import org.junit.platform.commons.logging.Logger; +import org.junit.platform.commons.logging.LoggerFactory; + +import java.security.KeyPair; +import java.security.interfaces.ECPrivateKey; +import java.security.interfaces.ECPublicKey; +import java.security.interfaces.RSAPrivateKey; +import java.security.interfaces.RSAPublicKey; + +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * KeyPairTest. + * + * @since 2021/12/28 + */ +public class KeyPairTest { + /** + * Add log info. + */ + private Logger logger = LoggerFactory.getLogger(ProfileTest.class); + + @Test + public void testKeyPair() { + KeyPair kRsa2048 = KeyPairTools.generateKeyPair(KeyPairTools.RSA, KeyPairTools.RSA_2048); + assertNotNull(kRsa2048); + assertTrue(kRsa2048.getPrivate() instanceof RSAPrivateKey); + assertTrue(kRsa2048.getPublic() instanceof RSAPublicKey); + + KeyPair kRsa3072 = KeyPairTools.generateKeyPair(KeyPairTools.RSA, KeyPairTools.RSA_3072); + assertNotNull(kRsa3072); + assertTrue(kRsa3072.getPrivate() instanceof RSAPrivateKey); + assertTrue(kRsa3072.getPublic() instanceof RSAPublicKey); + + KeyPair kRsa4096 = KeyPairTools.generateKeyPair(KeyPairTools.RSA, KeyPairTools.RSA_4096); + assertNotNull(kRsa4096); + assertTrue(kRsa4096.getPrivate() instanceof RSAPrivateKey); + assertTrue(kRsa4096.getPublic() instanceof RSAPublicKey); + + KeyPair kEcc256 = KeyPairTools.generateKeyPair(KeyPairTools.ECC, KeyPairTools.NIST_P_256); + assertNotNull(kEcc256); + assertTrue(kEcc256.getPrivate() instanceof ECPrivateKey); + assertTrue(kEcc256.getPublic() instanceof ECPublicKey); + + KeyPair kEcc384 = KeyPairTools.generateKeyPair(KeyPairTools.ECC, KeyPairTools.NIST_P_384); + assertNotNull(kEcc384); + assertTrue(kEcc384.getPrivate() instanceof ECPrivateKey); + assertTrue(kEcc384.getPublic() instanceof ECPublicKey); + + try { + KeyPair keyPairRsa = KeyPairTools.generateKeyPair(KeyPairTools.RSA, KeyPairTools.NIST_P_256); + assertNull(keyPairRsa); + } catch (Exception exception) { + logger.info(exception, () -> exception.getMessage()); + } + try { + KeyPair keyPairEcc = KeyPairTools.generateKeyPair(KeyPairTools.ECC, KeyPairTools.RSA_3072); + assertNull(keyPairEcc); + } catch (Exception exception) { + logger.info(exception, () -> exception.getMessage()); + } + } +} diff --git a/hapsigntool/hap_sign_tool_lib/src/test/java/com/ohos/hapsigntool/KeyStoreTest.java b/hapsigntool/hap_sign_tool_lib/src/test/java/com/ohos/hapsigntool/KeyStoreTest.java new file mode 100644 index 0000000000000000000000000000000000000000..fed66e7b79a99b133047a49caa44c035a68867ef --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/test/java/com/ohos/hapsigntool/KeyStoreTest.java @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2021-2022 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; + +import com.ohos.hapsigntool.key.KeyPairTools; +import com.ohos.hapsigntool.keystore.KeyStoreHelper; +import com.ohos.hapsigntool.utils.FileUtils; +import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.security.KeyPair; +import java.security.Security; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * KeyStoreTest. + * + * @since 2021/12/28 + */ +public class KeyStoreTest { + static { + Security.addProvider(new BouncyCastleProvider()); + } + + @Test + public void testKeyStore() throws IOException { + String keyStorePath = "test_keypair.jks"; + String pwd = "123456"; + String keyAlias = "oh-app1-key-v1"; + if (FileUtils.isFileExist(keyStorePath)) { + Path path = Paths.get(keyStorePath); + Files.delete(path); + } + KeyPair keyPair = KeyPairTools.generateKeyPair(KeyPairTools.RSA, KeyPairTools.RSA_2048); + KeyStoreHelper keyStoreHelper = new KeyStoreHelper(keyStorePath, pwd.toCharArray()); + keyStoreHelper.store(keyAlias, pwd.toCharArray(), keyPair, null); + + KeyStoreHelper keyStore = new KeyStoreHelper(keyStorePath, pwd.toCharArray()); + KeyPair keyPairLoad = keyStore.loadKeyPair(keyAlias, pwd.toCharArray()); + + assertEquals(keyPair.getPrivate(), keyPairLoad.getPrivate()); + assertEquals(keyPair.getPublic(), keyPairLoad.getPublic()); + } +} 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 new file mode 100644 index 0000000000000000000000000000000000000000..fffaa6b5371ac3a86f083af5a4eed5a70b10f8b1 --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/test/java/com/ohos/hapsigntool/ProfileTest.java @@ -0,0 +1,168 @@ +/* + * Copyright (c) 2021-2022 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; + +import com.ohos.hapsigntool.api.LocalizationAdapter; +import com.ohos.hapsigntool.api.model.Options; +import com.ohos.hapsigntool.key.KeyPairTools; +import com.ohos.hapsigntool.keystore.KeyStoreHelper; +import com.ohos.hapsigntool.profile.ProfileSignTool; +import com.ohos.hapsigntool.profile.VerifyHelper; +import com.ohos.hapsigntool.profile.model.VerificationResult; +import com.ohos.hapsigntool.utils.FileUtils; +import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.junit.jupiter.api.Test; +import org.junit.platform.commons.logging.Logger; +import org.junit.platform.commons.logging.LoggerFactory; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.security.KeyPair; +import java.security.Security; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * ProfileTest. + * + * @since 2021/12/28 + */ +public class ProfileTest { + /** + * Add log info. + */ + private final Logger logger = LoggerFactory.getLogger(ProfileTest.class); + /** + * Output the signed ProvisionProfile file in p7b format. + */ + private static final String OUT_PATH = "test_sign_profile.p7b"; + /** + * Keystore file in JKS or P12 format. + */ + private static final String KEY_STORE_PATH = "test_keypair.jks"; + /** + * Key alias. + */ + private static final String KEY_ALIAS = "oh-app1-key-v1"; + /** + * Key pwd and keystore pwd. + */ + private static final String PWD = "123456"; + /** + * Input original ProvisionProfile file. + */ + private static final String IN_FILE_PATH = "UnsgnedDebugProfileTemplate.json"; + /** + * Profile signing certificate. + */ + private static final String CERT_PATH = "test_profile_cert.cer"; + /** + * Mode is localSign. + */ + private static final String LOCAL_SIGN = "localSign"; + /** + * Mode is remoteSign. + */ + private static final String REMOTE_SIGN = "remoteSign"; + /** + * Params SHA256withRSA. + */ + public static final String SHA_256_WITH_RSA = "SHA256withRSA"; + + static { + Security.addProvider(new BouncyCastleProvider()); + } + + @Test + public void testProfile() throws IOException { + try { + Options options = new Options(); + LocalizationAdapter adapter = new LocalizationAdapter(options); + byte[] provisionContent = ProfileSignTool.getProvisionContent(new File(adapter.getInFile())); + byte[] p7b = ProfileSignTool.generateP7b(adapter, provisionContent); + FileUtils.write(p7b, new File(adapter.getOutFile())); + assertFalse(FileUtils.isFileExist(OUT_PATH)); + } catch (Exception exception) { + logger.info(exception, () -> exception.getMessage()); + } + loadFile(IN_FILE_PATH); + loadFile(CERT_PATH); + deleteFile(OUT_PATH); + deleteFile(KEY_STORE_PATH); + KeyPair keyPair = KeyPairTools.generateKeyPair(KeyPairTools.RSA, KeyPairTools.RSA_2048); + KeyStoreHelper keyStoreHelper = new KeyStoreHelper(KEY_STORE_PATH, PWD.toCharArray()); + keyStoreHelper.store(KEY_ALIAS, PWD.toCharArray(), keyPair, null); + Options options = new Options(); + putParams(options); + LocalizationAdapter adapter = new LocalizationAdapter(options); + byte[] provisionContent = ProfileSignTool.getProvisionContent(new File(adapter.getInFile())); + byte[] p7b = ProfileSignTool.generateP7b(adapter, provisionContent); + FileUtils.write(p7b, new File(adapter.getOutFile())); + assertTrue(FileUtils.isFileExist(OUT_PATH)); + VerifyHelper verifyHelper = new VerifyHelper(); + VerificationResult verificationResult = verifyHelper.verify(p7b); + assertTrue(verificationResult.isVerifiedPassed()); + try { + options.put(Options.MODE, REMOTE_SIGN); + adapter = new LocalizationAdapter(options); + provisionContent = ProfileSignTool.getProvisionContent(new File(adapter.getInFile())); + p7b = ProfileSignTool.generateP7b(adapter, provisionContent); + FileUtils.write(p7b, new File(adapter.getOutFile())); + assertTrue(FileUtils.isFileExist(OUT_PATH)); + } catch (Exception exception) { + logger.info(exception, () -> exception.getMessage()); + } + try { + verificationResult = verifyHelper.verify(null); + assertFalse(verificationResult.isVerifiedPassed()); + } catch (Exception exception) { + logger.info(exception, () -> exception.getMessage()); + } + } + + private void loadFile(String filePath) throws IOException { + ClassLoader classLoader = ProfileTest.class.getClassLoader(); + InputStream fileInputStream = classLoader.getResourceAsStream(filePath); + if (fileInputStream != null) { + byte[] fileData = FileUtils.read(fileInputStream); + FileUtils.write(fileData, new File(filePath)); + } + } + + private void putParams(Options options) { + options.put(Options.KEY_ALIAS, KEY_ALIAS); + options.put(Options.KEY_RIGHTS, PWD.toCharArray()); + options.put(Options.MODE, LOCAL_SIGN); + options.put(Options.PROFILE_CERT_FILE, CERT_PATH); + options.put(Options.IN_FILE, IN_FILE_PATH); + options.put(Options.SIGN_ALG, SHA_256_WITH_RSA); + options.put(Options.KEY_STORE_FILE, KEY_STORE_PATH); + options.put(Options.KEY_STORE_RIGHTS, PWD.toCharArray()); + options.put(Options.OUT_FILE, OUT_PATH); + } + + private void deleteFile(String filePath) throws IOException { + if (FileUtils.isFileExist(filePath)) { + Path path = Paths.get(filePath); + Files.delete(path); + } + } +} diff --git a/hapsigntool/hap_sign_tool_lib/src/test/resources/UnsgnedDebugProfileTemplate.json b/hapsigntool/hap_sign_tool_lib/src/test/resources/UnsgnedDebugProfileTemplate.json new file mode 100644 index 0000000000000000000000000000000000000000..72a6eab68e6ec090ca922812cce024818a1a6750 --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/test/resources/UnsgnedDebugProfileTemplate.json @@ -0,0 +1,29 @@ +{ + "version-name": "1.0.0", + "version-code": 1, + "uuid": "fe686e1b-3770-4824-a938-961b140a7c98", + "validity": { + "not-before": 1610519532, + "not-after": 1705127532 + }, + "type": "debug", + "bundle-info": { + "developer-id": "OpenHarmony", + "development-certificate": "-----BEGIN CERTIFICATE-----\nMIICMzCCAbegAwIBAgIEaOC/zDAMBggqhkjOPQQDAwUAMGMxCzAJBgNVBAYTAkNO\nMRQwEgYDVQQKEwtPcGVuSGFybW9ueTEZMBcGA1UECxMQT3Blbkhhcm1vbnkgVGVh\nbTEjMCEGA1UEAxMaT3Blbkhhcm1vbnkgQXBwbGljYXRpb24gQ0EwHhcNMjEwMjAy\nMTIxOTMxWhcNNDkxMjMxMTIxOTMxWjBoMQswCQYDVQQGEwJDTjEUMBIGA1UEChML\nT3Blbkhhcm1vbnkxGTAXBgNVBAsTEE9wZW5IYXJtb255IFRlYW0xKDAmBgNVBAMT\nH09wZW5IYXJtb255IEFwcGxpY2F0aW9uIFJlbGVhc2UwWTATBgcqhkjOPQIBBggq\nhkjOPQMBBwNCAATbYOCQQpW5fdkYHN45v0X3AHax12jPBdEDosFRIZ1eXmxOYzSG\nJwMfsHhUU90E8lI0TXYZnNmgM1sovubeQqATo1IwUDAfBgNVHSMEGDAWgBTbhrci\nFtULoUu33SV7ufEFfaItRzAOBgNVHQ8BAf8EBAMCB4AwHQYDVR0OBBYEFPtxruhl\ncRBQsJdwcZqLu9oNUVgaMAwGCCqGSM49BAMDBQADaAAwZQIxAJta0PQ2p4DIu/ps\nLMdLCDgQ5UH1l0B4PGhBlMgdi2zf8nk9spazEQI/0XNwpft8QAIwHSuA2WelVi/o\nzAlF08DnbJrOOtOnQq5wHOPlDYB4OtUzOYJk9scotrEnJxJzGsh/\n-----END CERTIFICATE-----\n", + "bundle-name": "com.OpenHarmony.app.test", + "app-feature": "hos_system_app" + }, + "permissions": { + "restricted-permissions": [ + "" + ] + }, + "debug-info": { + "device-ids": [ + "69C7505BE341BDA5948C3C0CB44ABCD530296054159EFE0BD16A16CD0129CC42", + "7EED06506FCE6325EB2E2FAA019458B856AB10493A6718C7679A73F958732865" + ], + "device-id-type": "udid" + }, + "issuer": "pki_internal" +} \ No newline at end of file diff --git a/hapsigntool/hap_sign_tool_lib/src/test/resources/app-sign-srv-ca.cer b/hapsigntool/hap_sign_tool_lib/src/test/resources/app-sign-srv-ca.cer new file mode 100644 index 0000000000000000000000000000000000000000..63c8efbf8f1bfae1d19194b0647ff9b8888ae8bb --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/test/resources/app-sign-srv-ca.cer @@ -0,0 +1,22 @@ +-----BEGIN CERTIFICATE----- +MIIDqTCCApGgAwIBAgIFAJJiSpAwDQYJKoZIhvcNAQEMBQAwVTELMAkGA1UEBhMC +Q04xHjAcBgNVBAsMFU9wZW5IYXJtb255IENvbW11bml0eTEQMA4GA1UEAwwHUm9v +dCBDQTEUMBIGA1UECgwLT3Blbkhhcm1vbnkwHhcNMjIwMTA0MTIwMTU3WhcNMjMw +MTA0MTIwMTU3WjBvMQswCQYDVQQGEwJDTjEeMBwGA1UECwwVT3Blbkhhcm1vbnkg +Q29tbXVuaXR5MSowKAYDVQQDDCEgQXBwbGljYXRpb24gU2lnbmF0dXJlIFNlcnZp +Y2UgQ0ExFDASBgNVBAoMC09wZW5IYXJtb255MIIBIjANBgkqhkiG9w0BAQEFAAOC +AQ8AMIIBCgKCAQEAiCMs9n9z5GOAs/rt/sLX0oVY5dhLc9MutYgxXfnoLJsLH/Ex +9IoyH+HIAowsaoRg41s/6up8OIp01FYEAQ5/T/hVbNMjnXfgy6AZMIWU2LZrv5Bs +phEF08tfuy8Rfs3OAlFlvjKjyTDPW/pv6hgTTGOFdd4jw+D5YRsLfF50030X2wPg +BU0IHwcxoDWSrXYsXhBoFTR9rgv/fu38NSY9IL5tKM1BZDfoq6pkML0PalD5+EUo +8f/jl2RotGqZXfRsJHS15Lt6kIeHZ1LL/uIxVEYiX526vO6l9D0po6cah2P+VaCy +33QPs0bGDybC1GskmfYOpbKVUhAZSTAFHvWYWwIDAQABo2YwZDAdBgNVHQ4EFgQU +f9mJlQV0AmcywRSuaT+s9K3AnQgwHwYDVR0jBBgwFoAUYH4Ah5XEx7InOmRu+PiY +XVE6C3EwEgYDVR0TAQH/BAgwBgEB/wIBADAOBgNVHQ8BAf8EBAMCAQYwDQYJKoZI +hvcNAQEMBQADggEBAFVdwa+aASKc3kVVgYSttYaC3pCw9Hw+93F2lPTndlOZdVwn +xJpdZU7E/FDkAZglE5SLjThCqBvyY0Ylzcvo8JKFXZVF6syZSxiQDayFmW/yUIfP +40BgPVka7JhznrPY1WQFsva+RKUxZafsXaP6+nnw6pvIbtTUaLIUJi2+KX/nl87d +gje5Muok/aQ9ALPJu4m3rbbnrsNm1VzszywygPnOAK8vlWwtxhLZeBK8SaRVhzbE +DkX3NTGhebFDZiOeExmzA5O3GvDkeLG7qaKzxVYkpoU3Mt8b6CUOJlW4NCp5odRt +HpUB2504/5zS5ciM7LYejJ4Q1ysmSmx8VRmDs94= +-----END CERTIFICATE----- diff --git a/hapsigntool/hap_sign_tool_lib/src/test/resources/root-ca.cer b/hapsigntool/hap_sign_tool_lib/src/test/resources/root-ca.cer new file mode 100644 index 0000000000000000000000000000000000000000..f46bde79a144d4cd32e4a6c731c33694bcbd2991 --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/test/resources/root-ca.cer @@ -0,0 +1,21 @@ +-----BEGIN CERTIFICATE----- +MIIDbTCCAlWgAwIBAgIEBNIkszANBgkqhkiG9w0BAQwFADBVMQswCQYDVQQGEwJD +TjEeMBwGA1UECwwVT3Blbkhhcm1vbnkgQ29tbXVuaXR5MRAwDgYDVQQDDAdSb290 +IENBMRQwEgYDVQQKDAtPcGVuSGFybW9ueTAeFw0yMjAxMDQxMjAxMjNaFw0yMzAx +MDQxMjAxMjNaMFUxCzAJBgNVBAYTAkNOMR4wHAYDVQQLDBVPcGVuSGFybW9ueSBD +b21tdW5pdHkxEDAOBgNVBAMMB1Jvb3QgQ0ExFDASBgNVBAoMC09wZW5IYXJtb255 +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqBa7pDeoRhPFqKGb5Ycc +mZpTnqFiFY7RFjGv+uot2pTX+72qp7v49kDcY5wEquo7p1TzoVOtttlWLzqR8elb ++w62bSxxCpgziN66Z/YoiigxcA5qoapOOd6cGrB24n16y4HnS9hY20RSAYHor7Ow +T7ZL6wZXud/n+L8BSTEnXS7bKYZ6o3FXVlBYcOInlVYYBwyie035DYNpvsZROppY +N3IpG/4/MKgZb3ydOo4Z8nnyt7yVvhC0FivLfXUfn/7mi9Ava1u1gldZFlpxaIdT +YNgmLcYq3RBFh/EV/Hnatn6r8022w1UrKNNt/NDiQXSHqvzxb7D1JVgb1XLAsHXQ +wwIDAQABo0UwQzAdBgNVHQ4EFgQUYH4Ah5XEx7InOmRu+PiYXVE6C3EwEgYDVR0T +AQH/BAgwBgEB/wIBADAOBgNVHQ8BAf8EBAMCAQYwDQYJKoZIhvcNAQEMBQADggEB +AGmk+8GwSqOOt7qf8gjc2ewRRePjUh3HEeUCyE80mElj5CicHWt64jl/tEX6nFJJ +4jk0nygdmO9W3hCbBiyhDQUwIWInlMg9rUTL/DM7a2oUBcZSnSK4tTiWAYz5ZDmO +YWR6M8tcuftwENbeDa/OYiMp/jtOet+9z9JNHYietSDR9/H+qOAISSwhLP/1Y08n +F9orqCu91WbSf/o7CLwpC1GSJNeXE06704a2vrCbbe1Uw9hA2BaVaq4J+fjLhaLI +4UtE9edYQWdwnReyfWJw061wkCGLKmh+t8ZP2gxKBwrc1lXN+0oeJWR9J2zdWVPM +qhSTxnmY7iZj+bzgAJXDPPA= +-----END CERTIFICATE----- diff --git a/hapsigntool/hap_sign_tool_lib/src/test/resources/test_profile_cert.cer b/hapsigntool/hap_sign_tool_lib/src/test/resources/test_profile_cert.cer new file mode 100644 index 0000000000000000000000000000000000000000..a7322f93272c5dacc77ad9a9cc221e091b69f66e --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/test/resources/test_profile_cert.cer @@ -0,0 +1,65 @@ +-----BEGIN CERTIFICATE----- +MIIDbTCCAlWgAwIBAgIEBNIkszANBgkqhkiG9w0BAQwFADBVMQswCQYDVQQGEwJD +TjEeMBwGA1UECwwVT3Blbkhhcm1vbnkgQ29tbXVuaXR5MRAwDgYDVQQDDAdSb290 +IENBMRQwEgYDVQQKDAtPcGVuSGFybW9ueTAeFw0yMjAxMDQxMjAxMjNaFw0yMzAx +MDQxMjAxMjNaMFUxCzAJBgNVBAYTAkNOMR4wHAYDVQQLDBVPcGVuSGFybW9ueSBD +b21tdW5pdHkxEDAOBgNVBAMMB1Jvb3QgQ0ExFDASBgNVBAoMC09wZW5IYXJtb255 +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqBa7pDeoRhPFqKGb5Ycc +mZpTnqFiFY7RFjGv+uot2pTX+72qp7v49kDcY5wEquo7p1TzoVOtttlWLzqR8elb ++w62bSxxCpgziN66Z/YoiigxcA5qoapOOd6cGrB24n16y4HnS9hY20RSAYHor7Ow +T7ZL6wZXud/n+L8BSTEnXS7bKYZ6o3FXVlBYcOInlVYYBwyie035DYNpvsZROppY +N3IpG/4/MKgZb3ydOo4Z8nnyt7yVvhC0FivLfXUfn/7mi9Ava1u1gldZFlpxaIdT +YNgmLcYq3RBFh/EV/Hnatn6r8022w1UrKNNt/NDiQXSHqvzxb7D1JVgb1XLAsHXQ +wwIDAQABo0UwQzAdBgNVHQ4EFgQUYH4Ah5XEx7InOmRu+PiYXVE6C3EwEgYDVR0T +AQH/BAgwBgEB/wIBADAOBgNVHQ8BAf8EBAMCAQYwDQYJKoZIhvcNAQEMBQADggEB +AGmk+8GwSqOOt7qf8gjc2ewRRePjUh3HEeUCyE80mElj5CicHWt64jl/tEX6nFJJ +4jk0nygdmO9W3hCbBiyhDQUwIWInlMg9rUTL/DM7a2oUBcZSnSK4tTiWAYz5ZDmO +YWR6M8tcuftwENbeDa/OYiMp/jtOet+9z9JNHYietSDR9/H+qOAISSwhLP/1Y08n +F9orqCu91WbSf/o7CLwpC1GSJNeXE06704a2vrCbbe1Uw9hA2BaVaq4J+fjLhaLI +4UtE9edYQWdwnReyfWJw061wkCGLKmh+t8ZP2gxKBwrc1lXN+0oeJWR9J2zdWVPM +qhSTxnmY7iZj+bzgAJXDPPA= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIDqTCCApGgAwIBAgIFAJJiSpAwDQYJKoZIhvcNAQEMBQAwVTELMAkGA1UEBhMC +Q04xHjAcBgNVBAsMFU9wZW5IYXJtb255IENvbW11bml0eTEQMA4GA1UEAwwHUm9v +dCBDQTEUMBIGA1UECgwLT3Blbkhhcm1vbnkwHhcNMjIwMTA0MTIwMTU3WhcNMjMw +MTA0MTIwMTU3WjBvMQswCQYDVQQGEwJDTjEeMBwGA1UECwwVT3Blbkhhcm1vbnkg +Q29tbXVuaXR5MSowKAYDVQQDDCEgQXBwbGljYXRpb24gU2lnbmF0dXJlIFNlcnZp +Y2UgQ0ExFDASBgNVBAoMC09wZW5IYXJtb255MIIBIjANBgkqhkiG9w0BAQEFAAOC +AQ8AMIIBCgKCAQEAiCMs9n9z5GOAs/rt/sLX0oVY5dhLc9MutYgxXfnoLJsLH/Ex +9IoyH+HIAowsaoRg41s/6up8OIp01FYEAQ5/T/hVbNMjnXfgy6AZMIWU2LZrv5Bs +phEF08tfuy8Rfs3OAlFlvjKjyTDPW/pv6hgTTGOFdd4jw+D5YRsLfF50030X2wPg +BU0IHwcxoDWSrXYsXhBoFTR9rgv/fu38NSY9IL5tKM1BZDfoq6pkML0PalD5+EUo +8f/jl2RotGqZXfRsJHS15Lt6kIeHZ1LL/uIxVEYiX526vO6l9D0po6cah2P+VaCy +33QPs0bGDybC1GskmfYOpbKVUhAZSTAFHvWYWwIDAQABo2YwZDAdBgNVHQ4EFgQU +f9mJlQV0AmcywRSuaT+s9K3AnQgwHwYDVR0jBBgwFoAUYH4Ah5XEx7InOmRu+PiY +XVE6C3EwEgYDVR0TAQH/BAgwBgEB/wIBADAOBgNVHQ8BAf8EBAMCAQYwDQYJKoZI +hvcNAQEMBQADggEBAFVdwa+aASKc3kVVgYSttYaC3pCw9Hw+93F2lPTndlOZdVwn +xJpdZU7E/FDkAZglE5SLjThCqBvyY0Ylzcvo8JKFXZVF6syZSxiQDayFmW/yUIfP +40BgPVka7JhznrPY1WQFsva+RKUxZafsXaP6+nnw6pvIbtTUaLIUJi2+KX/nl87d +gje5Muok/aQ9ALPJu4m3rbbnrsNm1VzszywygPnOAK8vlWwtxhLZeBK8SaRVhzbE +DkX3NTGhebFDZiOeExmzA5O3GvDkeLG7qaKzxVYkpoU3Mt8b6CUOJlW4NCp5odRt +HpUB2504/5zS5ciM7LYejJ4Q1ysmSmx8VRmDs94= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIDmDCCAoCgAwIBAgIFANEUD+8wDQYJKoZIhvcNAQELBQAwbjELMAkGA1UEBhMC +Q04xHjAcBgNVBAsMFU9wZW5IYXJtb255IENvbW11bml0eTEpMCcGA1UEAwwgQXBw +bGljYXRpb24gU2lnbmF0dXJlIFNlcnZpY2UgQ0ExFDASBgNVBAoMC09wZW5IYXJt +b255MB4XDTIyMDEwNDEyMDM1OFoXDTIzMDEwNDEyMDM1OFowWjELMAkGA1UEBhMC +Q04xHjAcBgNVBAsMFU9wZW5IYXJtb255IENvbW11bml0eTEVMBMGA1UEAwwMQXBw +MSBSZWxlYXNlMRQwEgYDVQQKDAtPcGVuSGFybW9ueTCCASIwDQYJKoZIhvcNAQEB +BQADggEPADCCAQoCggEBAJcB00Tgz1XUYG6508q2sskmxMz2ISJY1KSlFhaTgiBa +0NEWM/vtslgb0R+3YRvQe6x8CfXkNwQtwbooxtHZxnlGB2wezq48aNmPLxQj+pij +iw/OVV5HFrbRAnszGsGBZL71BjV30ntZ8uwLq+iTFtyG4SoRgMWm632t/U90ttD8 +0CITBFThF5DLnRLN4yR/FysfqggrSygj1USrkCJfYVBKIBz1oo0r5r1bXmTMQBtA +ZYv6ONPgZWSHwWpB0ZAQI/v2sBUhVufLJdzj0ckVLKznkHThpIGJ289IrM8a7C4b +hTIiXqdk2OWS4L28aHXCdpm1ugHctdmAPjP1MYjOYE8CAwEAAaNRME8wHQYDVR0O +BBYEFF9my7g4SO70qSORHAckxIHf7nbsMAkGA1UdEwQCMAAwDgYDVR0PAQH/BAQD +AgeAMBMGA1UdJQQMMAoGCCsGAQUFBwMDMA0GCSqGSIb3DQEBCwUAA4IBAQBg970m +z07sOfZadyQV5bpfcpRrRL9872+f37A5tZIR6LOIIKg9457LrWmzb+ZiOgOEx+8j +2GpprWRMmxMZirFc6Jb88HcbBktiZXhvennHlXB+ZLp0ijWF3mU0KjTePtayd11H +pO9B8TIljbXoXagJQL0TIqHrq12hCMaDjt5BzB2ODtd4QCi5cY66A+L4nIQdomvj +LqIWVxBQ+hUHEVFcdFy3YLVKS7d6lP2TliGZ9ztrYh4Nv4BIfMhda91lOAmAy8I2 +leJTVGnQS4H1en4TWBG4UzGQ67M+nMyzRP3coSeP9twUzuoQOVAmOfeMvZMYdKHQ +0k6rWiZZdMPPyZQU +-----END CERTIFICATE----- diff --git a/hapsigntool/settings.gradle b/hapsigntool/settings.gradle new file mode 100644 index 0000000000000000000000000000000000000000..b680be6140320f32f5665541d7001149389dc069 --- /dev/null +++ b/hapsigntool/settings.gradle @@ -0,0 +1,4 @@ +rootProject.name = 'hapsigntool' +include 'hap_sign_tool_lib' +include 'hap_sign_tool' + diff --git a/tools/auto_test.py b/tools/auto_test.py new file mode 100644 index 0000000000000000000000000000000000000000..590c8ab315786b9b7fe5744ea7db1400ca72651a --- /dev/null +++ b/tools/auto_test.py @@ -0,0 +1,436 @@ +############################################## +# Copyright (c) 2021-2022 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. +############################################## + +import os.path +import random +import sys +import time +from subprocess import Popen +from subprocess import PIPE + + +def print_help(): + content = "\n" \ + "Usage: signtool.jar -scope -n <--random>\n" \ + " signtool.jar : Main progress jar file\n" \ + " component: \n" \ + " --random: random test, default false" \ + "\n" \ + "Example: \n" \ + " signtool.jar \n" \ + " signtool.jar -scope all -n 1000\n" \ + " signtool.jar -scope generate-profile-cert\n" \ + " signtool.jar -n 50 --random\n" \ + "\n" + + print(content) + pass + + +def random_pwd(): + min_pwd = 100000 + max_pwd = 999999 + return random.randint(min_pwd, max_pwd), random.randint(min_pwd, max_pwd) + + +keystorePwd, keyPwd = random_pwd() + + +random_scope = { + 'generate-keypair': { + 'required': { + 'keyAlias': 'oh-app1-key-v1', + 'keyAlg': ["RSA", "ECC"], + 'keySize': ["2048", "3072", "4096", "NIST-P-256", "NIST-P-384"], + 'keystoreFile': ['ohtest.jks', 'ohtest.p12'] + }, + 'others': { + 'keyPwd': '123456', + 'keystorePwd': '123456' + } + }, + 'generate-csr': { + 'required': { + 'keyAlias': 'oh-app1-key-v1', + 'signAlg': ["SHA256withRSA", "SHA384withRSA", "SHA256withECDSA", "SHA384withECDSA"], + 'subject': "C=CN,O=OpenHarmony,OU=OpenHarmony Community,CN=App1 Release", + 'keystoreFile': ['ohtest.jks', 'ohtest.p12'], + 'outFile': 'oh-app1-key-v1.csr' + }, + 'others': { + 'keyPwd': '123456', + 'keystorePwd': '132456' + } + }, + 'generate-ca': { + 'required': { + 'keyAlias': ['oh-ca-key-v1', "oh-app-sign-srv-ca-key-v1"], + 'signAlg': ["SHA256withRSA", "SHA384withRSA", "SHA256withECDSA", "SHA384withECDSA"], + 'keyAlg': ['RSA', 'ECC'], + 'keySize': ["2048", "3072", "4096", "NIST-P-256", "NIST-P-384"], + 'subject': ["C=CN,O=OpenHarmony,OU=OpenHarmony Community,CN=App1 Release", + "C=CN,O=OpenHarmony,OU=OpenHarmony Community,CN=Root CA"], + 'keystoreFile': ['ohtest.jks', 'ohtest.p12'], + 'outFile': 'app-sign-srv-ca.cer' + }, + 'others': { + 'keyPwd': '123456', + 'keystorePwd': '132456', + 'issuer': 'C=CN,O=OpenHarmony,OU=OpenHarmony Community,CN=Application Signature Service CA', + 'issuerKeyAlias': 'oh-app-sign-srv-ca-key-v1', + 'issuerKeyPwd': '123456', + 'validity': '365', + 'basicConstraintsPathLen': '2' + } + }, + 'generate-cert': { + 'required': { + 'keyAlias': ['oh-sub-key-v1', 'oh-ca-key-v1'], + 'signAlg': ["SHA256withRSA", "SHA384withRSA", "SHA256withECDSA", "SHA384withECDSA"], + 'subject': "C=CN,O=OpenHarmony,OU=OpenHarmony Community,CN=App1 Release", + 'issuer': 'C=CN,O=OpenHarmony,OU=OpenHarmony Community,CN=Application Signature Service CA', + 'issuerKeyAlias': 'oh-ca-key-v1', + 'extKeyUsage': 'codeSignature', + 'keyUsage': ['digitalSignature,nonRepudiation,keyEncipherment', + 'dataEncipherment,keyAgreement, certificateSignature, crlSignature', + 'encipherOnly, encipherOnly'], + 'keystoreFile': ['ohtest.jks', 'ohtest.p12'], + 'outFile': 'app1.cer' + }, + 'others': { + 'extKeyUsage': ['serverAuthentication', 'clientAuthentication', 'emailProtection'], + 'extKeyUsageCritical': ['false', 'true'], + 'keyUsageCritical': ['false', 'true'], + 'issuerKeyPwd': '123456', + 'keyPwd': '123456', + 'validity': '365', + 'keystorePwd': '123456' + } + } +} + +simple_scope = { + 'generate-keypair': [ + 'generate-keypair -keyAlias "oh-app1-key-v1" -keyPwd 123456 -keyAlg ECC -keySize NIST-P-256 ' + '-keystoreFile "./test1/ohtest.jks" -keystorePwd 123456', + + 'generate-keypair -keyAlias "oh-profile1-key-v1" -keyPwd 123456 -keyAlg ECC -keySize NIST-P-384 ' + '-keystoreFile "./test1/ohtest.jks" -keystorePwd 123456', + + 'generate-keypair -keyAlias "oh-app2-key-v1" -keyPwd 123456 -keyAlg RSA -keySize 2048 ' + '-keystoreFile "./test2/ohtest.p12" -keystorePwd 123456', + + 'generate-keypair -keyAlias "oh-profile2-key-v1" -keyPwd 123456 -keyAlg RSA -keySize 4096 ' + '-keystoreFile "./test2/ohtest.p12" -keystorePwd 123456' + ], + 'generate-csr': [ + 'generate-csr -keyAlias "oh-app1-key-v1" -keyPwd 123456 -subject ' + '"C=CN,O=OpenHarmony,OU=OpenHarmony Community,CN=App1 Release" -signAlg SHA256withECDSA ' + '-keystoreFile "./test1/ohtest.jks" -keystorePwd 123456 -outFile "./test1/oh-app1-key-v1.csr"', + + 'generate-csr -keyAlias "oh-profile2-key-v1" -keyPwd 123456 -subject ' + '"C=CN,O=OpenHarmony,OU=OpenHarmony Community,CN=App1 Release" -signAlg SHA256withRSA ' + '-keystoreFile "./test2/ohtest.p12" -keystorePwd 123456 -outFile "./test2/oh-profile2-key-v1.csr"' + ], + 'generate-ca': [ + # Root CA in ohtest.jks + 'generate-ca -keyAlias "oh-root-ca-key-v1" -subject "C=CN,O=OpenHarmony,OU=OpenHarmony Community,CN=Root CA" ' + '-validity 365 -signAlg SHA384withECDSA -keystoreFile "./test1/ohtest.jks" -keystorePwd 123456 ' + '-outFile "./test1/root-ca1.cer" -keyAlg ECC -keySize NIST-P-256', + # Sub app cert in ohtest.jks + 'generate-ca -keyAlias "oh-app-sign-srv-ca-key-v1" -issuer "C=CN,O=OpenHarmony,OU=OpenHarmony Community,' + 'CN=Root CA" -issuerKeyAlias "oh-root-ca-key-v1" -subject "C=CN,O=OpenHarmony,OU=OpenHarmony Community,' + 'CN= Application Signature Service CA" -validity 365 -signAlg SHA384withECDSA ' + '-keystoreFile "./test1/ohtest.jks" -keystorePwd 123456 -outFile "./test1/app-sign-srv-ca1.cer" ' + '-keyAlg ECC -keySize NIST-P-256', + # Sub profile cert in ohtest.jks + 'generate-ca -keyAlias "oh-profile-sign-srv-ca-key-v1" -issuer "C=CN,O=OpenHarmony,OU=OpenHarmony Community,' + 'CN=Root CA" -issuerKeyAlias "oh-root-ca-key-v1" -subject "C=CN,O=OpenHarmony,OU=OpenHarmony Community,' + 'CN= Profile Signature Service CA" -validity 365 -signAlg SHA384withECDSA -keystoreFile "./test1/ohtest.jks" ' + '-keystorePwd 123456 -outFile "./test1/profile-sign-srv-ca1.cer" -keyAlg ECC -keySize NIST-P-384', + + # Root CA in ohtest.p12 + 'generate-ca -keyAlias "oh-root-ca2-key-v1" -subject "C=CN,O=OpenHarmony,OU=OpenHarmony Community,CN=Root CA" ' + '-validity 365 -signAlg SHA384withRSA -keystoreFile "./test2/ohtest.p12" -keystorePwd 123456 ' + '-outFile "./test2/root-ca2.cer" -keyAlg RSA -keySize 2048', + # Sub app cert in ohtest.p12 + 'generate-ca -keyAlias "oh-app-sign-srv-ca2-key-v1" -issuer "C=CN,O=OpenHarmony,OU=OpenHarmony Community,' + 'CN=Root CA" -issuerKeyAlias "oh-root-ca2-key-v1" -subject "C=CN,O=OpenHarmony,OU=OpenHarmony Community,' + 'CN= Application Signature Service CA" -validity 365 -signAlg SHA384withRSA ' + '-keystoreFile "./test2/ohtest.p12" -keystorePwd 123456 -outFile "./test2/app-sign-srv-ca2.cer" ' + '-keyAlg RSA -keySize 2048', + # Sub profile cert in ohtest.p12 + 'generate-ca -keyAlias "oh-profile-sign-srv-ca2-key-v1" -issuer "C=CN,O=OpenHarmony,OU=OpenHarmony Community,' + 'CN=Root CA" -issuerKeyAlias "oh-root-ca2-key-v1" -subject "C=CN,O=OpenHarmony,OU=OpenHarmony Community,' + 'CN= Profile Signature Service CA" -validity 365 -signAlg SHA384withRSA -keystoreFile "./test2/ohtest.p12" ' + '-keystorePwd 123456 -outFile "./test2/profile-sign-srv-ca2.cer" -keyAlg RSA -keySize 2048' + ], + 'generate-cert': [ + # Self-Definition cert - Root CA + 'generate-cert -keyAlias "oh-app1-key-v1" -issuer "C=CN,O=OpenHarmony,OU=OpenHarmony Community,CN=Root CA" ' + '-issuerKeyAlias "oh-app1-key-v1" -issuerKeyPwd 123456 ' + '-subject "C=CN,O=OpenHarmony,OU=OpenHarmony Community,CN=Root CA" -validity 365 ' + '-keyUsage "certificateSignature, crlSignature" -signAlg SHA256withECDSA ' + '-keystoreFile "./test1/ohtest.jks" -keystorePwd 123456 -outFile "./test1/single-root.cer" -keyPwd 123456', + # Self-definition sign cert - app cert + 'generate-cert -keyAlias "oh-app1-key-v1" -issuer "C=CN,O=OpenHarmony,OU=OpenHarmony Community,' + 'CN=Application Signature Service CA" -issuerKeyAlias "oh-app-sign-srv-ca-key-v1" ' + '-subject "C=CN,O=OpenHarmony,OU=OpenHarmony Community,CN=App1 Release" -validity 365 ' + '-keyUsage digitalSignature -extKeyUsage codeSignature -signAlg SHA256withECDSA ' + '-keystoreFile "./test1/ohtest.jks" -keystorePwd 123456 -outFile "./test1/single-app1.cer" -keyPwd 123456' + ], + 'generate-app-cert': [ + # App sign cert via ohtest.jks + 'generate-app-cert -keyAlias "oh-app1-key-v1" -issuer "C=CN,O=OpenHarmony,OU=OpenHarmony Community,' + 'CN=Application Signature Service CA" -issuerKeyAlias "oh-app-sign-srv-ca-key-v1" -subject ' + '"C=CN,O=OpenHarmony,OU=OpenHarmony Community,CN=App1 Release" -validity 365 -signAlg SHA256withECDSA ' + '-keystoreFile "./test1/ohtest.jks" -keystorePwd 123456 -outFile "./test1/app1.cer" -keyPwd 123456', + # App sign cert chain via ohtest.jks + 'generate-app-cert -keyAlias "oh-app1-key-v1" -issuer "C=CN,O=OpenHarmony,OU=OpenHarmony Community,' + 'CN=Application Signature Service CA" -issuerKeyAlias "oh-app-sign-srv-ca-key-v1" -subject ' + '"C=CN,O=OpenHarmony,OU=OpenHarmony Community,CN=App1 Release" -validity 365 -signAlg SHA256withECDSA ' + '-keystoreFile "./test1/ohtest.jks" -keystorePwd 123456 -outFile "./test1/app-release1.pem" ' + '-subCaCertFile ./test1/app-sign-srv-ca1.cer -outForm certChain ' + '-rootCaCertFile ./test1/root-ca1.cer -keyPwd 123456', + # App sign cert via ohtest.p12 + 'generate-app-cert -keyAlias "oh-app2-key-v1" -issuer "C=CN,O=OpenHarmony,OU=OpenHarmony Community,' + 'CN=Application Signature Service CA" -issuerKeyAlias "oh-app-sign-srv-ca2-key-v1" -subject ' + '"C=CN,O=OpenHarmony,OU=OpenHarmony Community,CN=App1 Release" -validity 365 -signAlg SHA256withECDSA ' + '-keystoreFile "./test2/ohtest.p12" -keystorePwd 123456 -outFile "./test2/app2.cer" -keyPwd 123456', + # App sign cert chain via ohtest.p12 + 'generate-app-cert -keyAlias "oh-app2-key-v1" -issuer "C=CN,O=OpenHarmony,OU=OpenHarmony Community,' + 'CN=Application Signature Service CA" -issuerKeyAlias "oh-app-sign-srv-ca2-key-v1" -subject ' + '"C=CN,O=OpenHarmony,OU=OpenHarmony Community,CN=App1 Release" -validity 365 -signAlg SHA256withECDSA ' + '-keystoreFile "./test2/ohtest.p12" -keystorePwd 123456 -outFile "./test2/app-release2.pem" ' + '-subCaCertFile ./test2/app-sign-srv-ca2.cer -outForm certChain ' + '-rootCaCertFile ./test2/root-ca2.cer -keyPwd 123456' + ], + 'generate-profile-cert': [ + # Profile sign cert via ohtest.jks + 'generate-profile-cert -keyAlias "oh-profile1-key-v1" -issuer "C=CN,O=OpenHarmony,OU=OpenHarmony Community,' + 'CN=Profile Signature Service CA" -issuerKeyAlias "oh-profile-sign-srv-ca-key-v1" ' + '-subject "C=CN,O=OpenHarmony,OU=OpenHarmony Community,CN=Profile1 Release" ' + '-validity 365 -signAlg SHA256withECDSA -keystoreFile "./test1/ohtest.jks" ' + '-keystorePwd 123456 -outFile "./test1/profile1.cer" -keyPwd 123456', + # Profile sign cert chain via ohtest.jks + 'generate-profile-cert -keyAlias "oh-profile1-key-v1" -issuer "C=CN,O=OpenHarmony,OU=OpenHarmony Community,' + 'CN=Profile Signature Service CA" -issuerKeyAlias "oh-profile-sign-srv-ca-key-v1" ' + '-subject "C=CN,O=OpenHarmony,OU=OpenHarmony Community,CN=Profile1 Release" -validity 365 ' + '-signAlg SHA256withECDSA -keystoreFile "./test1/ohtest.jks" ' + '-keystorePwd 123456 -outFile "./test1/profile-release1.pem" ' + '-subCaCertFile "./test1/profile-sign-srv-ca1.cer" -outForm certChain ' + '-rootCaCertFile "./test1/root-ca1.cer" -keyPwd 123456', + # Profile sign cert via ohtest.p12 + 'generate-profile-cert -keyAlias "oh-app2-key-v1" -issuer "C=CN,O=OpenHarmony,OU=OpenHarmony Community,' + 'CN=Profile Signature Service CA" -issuerKeyAlias "oh-profile-sign-srv-ca2-key-v1" ' + '-subject "C=CN,O=OpenHarmony,OU=OpenHarmony Community,CN=Profile2 Release" ' + '-validity 365 -signAlg SHA256withECDSA -keystoreFile "./test2/ohtest.p12" ' + '-keystorePwd 123456 -outFile "./test2/profile2.cer" -keyPwd 123456', + # Profile sign cert chain via ohtest.p12 + 'generate-profile-cert -keyAlias "oh-app2-key-v1" -issuer "C=CN,O=OpenHarmony,OU=OpenHarmony Community,' + 'CN=Profile Signature Service CA" -issuerKeyAlias "oh-profile-sign-srv-ca2-key-v1" ' + '-subject "C=CN,O=OpenHarmony,OU=OpenHarmony Community,CN=Profile2 Release" -validity 365 ' + '-signAlg SHA256withECDSA -keystoreFile "./test2/ohtest.p12" ' + '-keystorePwd 123456 -outFile "./test2/profile-release2.pem" -subCaCertFile "./test2/profile-sign-srv-ca2.cer" ' + '-outForm certChain -rootCaCertFile "./test2/root-ca2.cer" -keyPwd 123456' + ], + 'sign-profile': [ + 'sign-profile -mode localSign -keyAlias "oh-app1-key-v1" -profileCertFile "./test1/profile-release1.pem" ' + '-inFile "profile.json" -signAlg SHA256withECDSA -keystoreFile "./test1/ohtest.jks" -keystorePwd 123456 ' + '-outFile "./test1/app1-profile1.p7b" -keyPwd 123456', + 'sign-profile -mode localSign -keyAlias "oh-app2-key-v1" -profileCertFile "./test2/profile-release2.pem" ' + '-inFile "profile.json" -signAlg SHA256withRSA -keystoreFile "./test2/ohtest.p12" -keystorePwd 123456 ' + '-outFile "./test2/app1-profile2.p7b" -keyPwd 123456' + ], + 'verify-profile': [ + 'verify-profile -inFile "./test1/app1-profile1.p7b"', + 'verify-profile -inFile "./test2/app1-profile2.p7b"' + ] +} + +test_result = {} + + +def run_target(case, cmd): + if not test_result.get(case, None): + test_result[case] = {'times': 0, 'total_cost': 0, 'success': 0, 'fail': 0} + + case_result = test_result.get(case) + case_result['times'] = case_result['times'] + 1 + start = time.time() + + command = Popen(cmd, stdout=PIPE, stderr=PIPE, stdin=PIPE, shell=True) + + out = command.stdout.readlines() + with open("log.txt", mode='a+') as f: + if len(out) > 0: + f.writelines(cmd + "\r\n") + for line in out: + f.writelines(str(line.strip()) + "\r\n") + + success = True + error = command.stderr.readlines() + with open("error.txt", mode='a+') as f: + if len(error) > 0: + f.writelines(cmd + "\r\n") + + for line in error: + success = False + f.writelines(str(line.strip()) + "\r\n") + + command.wait() + end = time.time() + case_result['total_cost'] = case_result['total_cost'] + (end - start) + + if success: + case_result['success'] = case_result['success'] + 1 + else: + case_result['fail'] = case_result['fail'] + 1 + return success + + +def run_simple_case(case, jar_file): + test_case = simple_scope.get(case, None) + if not test_case: + print("Not found test case: {}".format(case)) + exit(0) + + for k in test_case: + cmd = 'java -jar {} {}'.format(jar_file, k) + print("== Run command: {}".format(cmd)) + result = run_target(case, cmd) + print("== Done command: {}".format(result)) + + +def random_str(): + strs = "abcdefghjiklmnopqstuvwxyzABCDEFGHIJKLMNOPQRS TUVWXYZ1234567890~!@#ls%^&*()_+,./<>?;':" + result = '' + for i in range(random.randint(1, 30)): + result = result + random.choice(strs) + return result + + +def run_random_case(case, jar_file): + test_case = random_scope.get(case, None) + if not test_case: + print("Not found test case: {}".format(case)) + exit(0) + + cmd = 'java -jar {} {}'.format(jar_file, case) + for k, v in test_case.get('required').items(): + r = random.choice(['none', 'choice', 'choice', 'random']) + if r == 'choice': + cmd = cmd + ' -{} "{}" '.format(k, random.choice(v) if isinstance(v, list) else v) + elif r == 'random': + cmd = cmd + ' -{} "{}" '.format(k, random_str()) + + for k, v in test_case.get('others').items(): + r = random.choice(['none', 'choice', 'choice', 'random']) + if r == 'choice': + cmd = cmd + ' -{} "{}" '.format(k, random.choice(v) if isinstance(v, list) else v) + elif r == 'random': + cmd = cmd + ' -{} "{}" '.format(k, random_str()) + + print("== Run command: {}".format(cmd)) + result = run_target(case, cmd) + print("== Done command: {}".format(result)) + + +def run_all_case(case, jar_file): + test_case = random_scope.get(case, None) + if not test_case: + print("Not found test case: {}".format(case)) + exit(0) + + cmd = 'java -jar {} {}'.format(jar_file, case) + for ak, av in test_case.get('required').items(): + cmd = cmd + ' -{} "{}" '.format(ak, random.choice(av) if isinstance(av, list) else av) + + print("== Run command: {}".format(cmd)) + result = run_target(case, cmd) + print("== Done command: {}".format(result)) + + +def prepare_env(): + test_dirs = ['test1', 'test2'] + for test_dir in test_dirs: + if not os.path.exists(test_dir): + os.mkdir(test_dir) + + for key_file in ['ohtest.jks', 'ohtest.p12']: + target_file = os.path.join(test_dir, key_file) + if os.path.exists(target_file): + os.remove(target_file) + + +def process_cmd(args): + run_round = 1 + run_scope = 'simple' + is_random = False + + if len(args) <= 1 or ('.jar' not in args[1]) or '--help' == args[1] or '-h' == args[1]: + print_help() + exit(0) + + jar_file = args[1] + if not os.path.exists(jar_file): + print("Jar file '{}' not found".format(jar_file)) + exit(0) + + if len(args) >= 3: + try: + for i in range(2, len(args), 1): + if args[i] == '-n': + run_round = int(args[i + 1]) + elif args[i] == '-scope': + run_scope = args[i + 1] + elif args[i] == '--random': + is_random = True + except IndexError: + print_help() + exit(0) + + print('=== Start testing ===') + print('Scope: {}. Round: {}. Random: {}'.format(run_scope, run_round, is_random)) + + if os.path.exists('log.txt'): + os.remove('log.txt') + if os.path.exists('error.txt'): + os.remove('error.txt') + + for i in range(run_round): + if run_scope == 'all': + for r_scope, _ in random_scope.items(): + run_all_case(r_scope, jar_file) + elif is_random: + for r_scope, _ in random_scope.items(): + run_random_case(r_scope, jar_file) + elif run_scope == 'simple': + prepare_env() + for s_scope, _ in simple_scope.items(): + run_simple_case(s_scope, jar_file) + else: + run_simple_case(run_scope, jar_file) + + +if __name__ == '__main__': + process_cmd(sys.argv) + print("All test done") + print("========================") + for rk, rv in test_result.items(): + print("Case {}, run times: {}, avg cost: {}s, total success: {}, total fail: {}".format(rk, rv['times'], round( + rv['total_cost'] / rv['times'], 2), rv['success'], rv['fail'])) + print("========================") + print("See log.txt / error.txt") diff --git a/tools/profile.json b/tools/profile.json new file mode 100644 index 0000000000000000000000000000000000000000..d4f3bbf71e34cd8f7ed1bcdb7837c3083ccc948f --- /dev/null +++ b/tools/profile.json @@ -0,0 +1,29 @@ +{ + "version-name": "1.0.0", + "version-code": 1, + "uuid": "fe686e1b-3770-4824-a938-961b140a7c98", + "validity": { + "not-before": 1610519532, + "not-after": 1705127532 + }, + "type": "debug", + "bundle-info": { + "developer-id": "OpenHarmony", + "development-certificate": "-----BEGIN CERTIFICATE-----\nMIICMzCCAbegAwIBAgIEaOC/zDAMBggqhkjOPQQDAwUAMGMxCzAJBgNVBAYTAkNO\nMRQwEgYDVQQKEwtPcGVuSGFybW9ueTEZMBcGA1UECxMQT3Blbkhhcm1vbnkgVGVh\nbTEjMCEGA1UEAxMaT3Blbkhhcm1vbnkgQXBwbGljYXRpb24gQ0EwHhcNMjEwMjAy\nMTIxOTMxWhcNNDkxMjMxMTIxOTMxWjBoMQswCQYDVQQGEwJDTjEUMBIGA1UEChML\nT3Blbkhhcm1vbnkxGTAXBgNVBAsTEE9wZW5IYXJtb255IFRlYW0xKDAmBgNVBAMT\nH09wZW5IYXJtb255IEFwcGxpY2F0aW9uIFJlbGVhc2UwWTATBgcqhkjOPQIBBggq\nhkjOPQMBBwNCAATbYOCQQpW5fdkYHN45v0X3AHax12jPBdEDosFRIZ1eXmxOYzSG\nJwMfsHhUU90E8lI0TXYZnNmgM1sovubeQqATo1IwUDAfBgNVHSMEGDAWgBTbhrci\nFtULoUu33SV7ufEFfaItRzAOBgNVHQ8BAf8EBAMCB4AwHQYDVR0OBBYEFPtxruhl\ncRBQsJdwcZqLu9oNUVgaMAwGCCqGSM49BAMDBQADaAAwZQIxAJta0PQ2p4DIu/ps\nLMdLCDgQ5UH1l0B4PGhBlMgdi2zf8nk9spazEQI/0XNwpft8QAIwHSuA2WelVi/o\nzAlF08DnbJrOOtOnQq5wHOPlDYB4OtUzOYJk9scotrEnJxJzGsh/\n-----END CERTIFICATE-----\n", + "bundle-name": "com.OpenHarmony.app.test", + "app-feature": "hos_system_app" + }, + "permissions": { + "restricted-permissions": [ + "" + ] + }, + "debug-info": { + "device-ids": [ + "69C7505BE341BDA5948C3C0CB44ABCD530296054159EFE0BD16A16CD0129CC42", + "7EED06506FCE6325EB2E2FAA019458B856AB10493A6718C7679A73F958732865" + ], + "device-id-type": "udid" + }, + "issuer": "pki_internal" +}