diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..261eeb9e9f8b2b4b0d119366dda99c6fd7d35c64 --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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. 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..4919362ab3754e5194c77c853b303888504bd383 --- /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.5' + 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..c063a9e07df5456796018b6790e937c2c57372af --- /dev/null +++ b/hapsigntool/hap_sign_tool/src/main/java/com/ohos/hapsigntool/HapSignTool.java @@ -0,0 +1,285 @@ +/* + * 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; + +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"; + + private HapSignTool() { + } + + public static void main(String[] args) { + try { + processCmd(args); + } catch (CustomException exception) { + LOGGER.debug(exception.getMessage(), exception); + LOGGER.error(exception.getMessage()); + } + } + + 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.KEY_ALIAS, Options.CA_CERT_FILE, Options.APP_CERT_FILE, + Options.PROFILE_FILE, Options.IN_FILE, Options.SIGN_ALG, Options.OUT_FILE); + String mode = params.getString(Options.MODE); + if (!LOCAL_SIGN.equalsIgnoreCase(mode) + && !"remoteSign".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); + } + + String profileFile = params.getString(Options.PROFILE_FILE); + FileUtils.validFileType(profileFile, "p7b"); + String infile = params.getString(Options.IN_FILE); + FileUtils.validFileType(infile, "hap", "bin"); + 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.KEY_ALIAS, + Options.PROFILE_CERT_FILE, Options.IN_FILE, Options.SIGN_ALG, Options.OUT_FILE); + String mode = params.getString(Options.MODE); + if (!LOCAL_SIGN.equalsIgnoreCase(mode) && !"remoteSign".equalsIgnoreCase(mode)) { + CustomException.throwException(ERROR.COMMAND_ERROR, "mode params is incorrect"); + } + if (LOCAL_SIGN.equalsIgnoreCase(mode)) { + params.required(Options.KEY_STORE_FILE); + } + + String signAlg = params.getString(Options.SIGN_ALG); + CmdUtil.judgeSignAlgType(signAlg); + FileUtils.validFileType(params.getString(Options.KEY_STORE_FILE), "p12", "jks"); + 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.TRUSTED_ROOT_CA_FILE, + Options.TRUSTED_APP_SOURCE_FILE); + FileUtils.validFileType(params.getString(Options.IN_FILE), "hap", "bin"); + FileUtils.validFileType(params.getString(Options.TRUSTED_ROOT_CA_FILE), "json"); + FileUtils.validFileType(params.getString(Options.TRUSTED_APP_SOURCE_FILE), "json"); + + 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"); + + return api.verifyProfile(params); + } + + public static void version() { + LOGGER.info(VERSION); + } + + 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..1ffad5de0e1229e71e001f9ded47f8911ff82e91 --- /dev/null +++ b/hapsigntool/hap_sign_tool/src/main/java/com/ohos/hapsigntoolcmd/CmdUtil.java @@ -0,0 +1,191 @@ +/* + * 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; + +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() { + } + + 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("-") || 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 (key.toLowerCase().endsWith("pwd")) { + params.getOptions().put(key, value.toCharArray()); + result = true; + } else { + params.getOptions().put(key, value); + result = true; + } + return result; + } + + public static void judgeAlgType(String alg) { + if (!"RSA".equalsIgnoreCase(alg) && !"ECC".equalsIgnoreCase(alg)) { + CustomException.throwException(ERROR.COMMAND_ERROR, + "KeyAlg params is incorrect"); + } + } + + 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)); + } + } + } + + 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"); + } + } + + public static void verifyType(String inputType, String[] supportTypes) { + String[] types = inputType.split(","); + List supportList = Arrays.asList(supportTypes); + 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"); + } + } + } + + 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..de7301c3b007d3c93a8e1fe5a59aff79363cb981 --- /dev/null +++ b/hapsigntool/hap_sign_tool/src/main/java/com/ohos/hapsigntoolcmd/HelpDocument.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.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; + +public final class HelpDocument { + private HelpDocument() { + } + + 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..bbe4a932dd14dac5d5d5c8abc6ad51930fd2c429 --- /dev/null +++ b/hapsigntool/hap_sign_tool/src/main/java/com/ohos/hapsigntoolcmd/Params.java @@ -0,0 +1,68 @@ +/* + * 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; + +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..da1da749e13b57a06649707e2c7acb6cffd6e2c6 --- /dev/null +++ b/hapsigntool/hap_sign_tool/src/main/resources/help.txt @@ -0,0 +1,163 @@ +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 123456 -keyAlg ECC -keySize NIST-P-256 -keystoreFile "D:\OH\ohtest.jks" -keystorePwd 123456 + + 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 123456 -subject "C=CN,O=OpenHarmony,OU=OpenHarmony Community,CN=App1 Release" -signAlg SHA256withECDSA –keystoreFile "D:\OH\ohtest.jks" -keystorePwd 123456 –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-app-sign-key-v1" -issuer "C=CN,O=OpenHarmony,OU=OpenHarmony Community,CN=Application Signature Service CA" -issuerKeyAlias "oh-app1-key-v1" -issuerKeyPwd 123456 -subject "C=CN,O=OpenHarmony,OU=OpenHarmony Community,CN=App1 Release" -validity 365 -keyUsage digitalSignature -extKeyUsage codeSignature -signAlg SHA256withECDSA -keystoreFile "D:\OH\ohtest.jks" -keystorePwd 123456 –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\ohtest.jks" -keystorePwd 123456 -outFile "D:\OH\root-ca.cer" -keyAlg RSA -keySize 2048 + generate-ca -keyAlias "oh-app-sign-srv-ca-key-v1" -keyAlg RSA -keySize 2048 -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= Application Signature Service CA" -validity 365 -signAlg SHA384withECDSA -keystoreFile "D:\OH\ohtest.jks" -keystorePwd 123456 -outFile "D:\OH\app-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-app-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 "D:\OH\ohtest.jks" -keystorePwd 123456 -outFile "D:\OH\app1.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-release-key-v1" -issuer "C=CN,O=OpenHarmony,OU=OpenHarmony Community,CN=Provision Profile Signature Service CA" -issuerKeyAlias "oh-app1-key-v1" -issuerKeyPwd 123456 -subject "C=CN,O=OpenHarmony,OU=OpenHarmony Community,CN=Provision Profile Release" -validity 365 -signAlg SHA256withECDSA -keystoreFile "D:\OH\ohtest.jks" -keystorePwd 123456 -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-release-v1" -profileCertFile "D:\OH\profile-release.cer" -inFile "D:\OH\app1-profile-release.json" -signAlg SHA256withECDSA -keystoreFile "D:\OH\ohtest.jks" -keystorePwd 123456 -outFile "D:\OH\app1-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\app1-profile-release.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\app1.cer" -profileFile "D:\OH\app1-profile-release.p7b" -inFile "D:\OH\app1-unsigned.hap" -signAlg SHA256withECDSA -keystoreFile "D:\OH\ohtest.jks" -keystorePwd 123456 -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..4263d29473f9562378f3dd21e8b467ab7f35ee06 --- /dev/null +++ b/hapsigntool/hap_sign_tool/src/test/java/com/ohos/hapsigntoolcmd/CmdUnitTest.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.hapsigntoolcmd; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +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 com.ohos.hapsigntool.HapSignTool; +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; + +@TestMethodOrder(MethodOrderer.OrderAnnotation.class) +public class CmdUnitTest { + /** + * Add log info. + */ + private Logger logger = LoggerFactory.getLogger(CmdUnitTest.class); + @Order(1) + @Test + public void testCmdKeypair() throws IOException { + + try { + deleteFile(Constants.CMD_KEY_STORE_PATH); + boolean result = HapSignTool.processCmd(new String[]{CmdUtil.Method.GENERATE_KEYPAIR}); + assertFalse(result); + assertFalse(FileUtils.isFileExist(Constants.CMD_KEY_STORE_PATH)); + } catch (Exception exception) { + logger.info(exception, () -> exception.getMessage()); + } + + deleteFile(Constants.CMD_KEY_STORE_PATH); + boolean result = HapSignTool.processCmd(new String[]{ + CmdUtil.Method.GENERATE_KEYPAIR, + Constants.CMD_KEY_ALIAS, Constants.CMD_OH_APP1_KEY_V1, + Constants.CMD_KEY_PWD, Constants.CMD_PWD_123456, + Constants.CMD_KEY_ALG, KeyPairTools.RSA, + Constants.CMD_KEY_SIZE, Constants.CMD_RSA_2048, + Constants.CMD_KEY_STORE_FILE, Constants.CMD_KEY_STORE_PATH, + Constants.CMD_KEY_STORE_PWD, Constants.CMD_PWD_123456}); + assertTrue(result); + assertTrue(FileUtils.isFileExist(Constants.CMD_KEY_STORE_PATH)); + result = HapSignTool.processCmd(new String[]{ + CmdUtil.Method.GENERATE_KEYPAIR, + Constants.CMD_KEY_ALIAS, Constants.CMD_OH_APP_CA_V1, + Constants.CMD_KEY_PWD, Constants.CMD_PWD_123456, + Constants.CMD_KEY_ALG, KeyPairTools.RSA, + Constants.CMD_KEY_SIZE, Constants.CMD_RSA_2048, + Constants.CMD_KEY_STORE_FILE, Constants.CMD_KEY_STORE_PATH, + Constants.CMD_KEY_STORE_PWD, Constants.CMD_PWD_123456}); + assertTrue(result); + assertTrue(FileUtils.isFileExist(Constants.CMD_KEY_STORE_PATH)); + } + + @Order(2) + @Test + public void testCmdCsr() throws IOException { + + try { + deleteFile(Constants.CMD_CSR_PATH); + boolean result = HapSignTool.processCmd(new String[]{CmdUtil.Method.GENERATE_CSR}); + assertFalse(result); + assertFalse(FileUtils.isFileExist(Constants.CMD_CSR_PATH)); + } catch (Exception exception) { + logger.info(exception, () -> exception.getMessage()); + } + + deleteFile(Constants.CMD_CSR_PATH); + boolean result = HapSignTool.processCmd(new String[]{ + CmdUtil.Method.GENERATE_CSR, + Constants.CMD_KEY_ALIAS, Constants.CMD_OH_APP1_KEY_V1, + Constants.CMD_KEY_PWD, Constants.CMD_PWD_123456, + Constants.CMD_SUBJECT, Constants.CMD_APP1_RELEASE, + Constants.CMD_SIGN_ALG, Constants.CMD_SHA_256_WITH_RSA, + Constants.CMD_KEY_STORE_FILE, Constants.CMD_KEY_STORE_PATH, + Constants.CMD_KEY_STORE_PWD, Constants.CMD_PWD_123456, + Constants.CMD_OUT_FILE, Constants.CMD_CSR_PATH}); + assertTrue(result); + assertTrue(FileUtils.isFileExist(Constants.CMD_CSR_PATH)); + } + + @Order(3) + @Test + public void testCmdCert() throws IOException { + + try { + deleteFile(Constants.CMD_CERT_PATH); + boolean result = HapSignTool.processCmd(new String[]{CmdUtil.Method.GENERATE_CERT}); + assertFalse(result); + assertFalse(FileUtils.isFileExist(Constants.CMD_CERT_PATH)); + } catch (Exception exception) { + logger.info(exception, () -> exception.getMessage()); + } + + deleteFile(Constants.CMD_CERT_PATH); + boolean result = HapSignTool.processCmd(new String[]{ + CmdUtil.Method.GENERATE_CERT, + Constants.CMD_KEY_ALIAS, Constants.CMD_OH_APP1_KEY_V1, + Constants.CMD_KEY_PWD, Constants.CMD_PWD_123456, + Constants.CMD_ISSUER, Constants.CMD_APP_CA, + Constants.CMD_SIGN_ALG, Constants.CMD_SHA_256_WITH_RSA, + Constants.CMD_KEY_STORE_FILE, Constants.CMD_KEY_STORE_PATH, + Constants.CMD_KEY_STORE_PWD, Constants.CMD_PWD_123456, + Constants.CMD_OUT_FILE, Constants.CMD_CERT_PATH, + Constants.CMD_ISSUER_KEY_ALIAS, Constants.CMD_OH_APP1_KEY_V1, + Constants.CMD_ISSUER_KEY_PWD, Constants.CMD_PWD_123456, + Constants.CMD_SUBJECT, Constants.CMD_APP1_RELEASE, + Constants.CMD_VALIDITY, Constants.CMD_VALIDITY_365, + Constants.CMD_KEY_USAGE, Constants.CMD_DIGITAL_SIGNATURE, + Constants.CMD_KEY_USAGE_CRITICAL, Constants.CMD_FALSE, + Constants.CMD_EXT_KEY_USAGE, Constants.CMD_CODE_AND_EMAIL, + Constants.CMD_EXT_KEY_USAGE_CRITICAL, Constants.CMD_TRUE, + Constants.CMD_BASIC_CONSTRAINTS, Constants.CMD_FALSE, + Constants.CMD_BASIC_CONSTRAINTS_CRITICAL, Constants.CMD_TRUE, + Constants.CMD_BASIC_CONSTRAINTS_CA, Constants.CMD_FALSE, + Constants.CMD_BASIC_CONSTRAINTS_PATH_LEN, Constants.CMD_BC_PATH_LEN_0}); + assertTrue(result); + assertTrue(FileUtils.isFileExist(Constants.CMD_CERT_PATH)); + } + + @Order(4) + @Test + public void testCmdCa() throws IOException { + try { + deleteFile(Constants.CMD_ROOT_CA_PATH); + deleteFile(Constants.CMD_SUB_CA_PATH); + boolean result = HapSignTool.processCmd(new String[]{CmdUtil.Method.GENERATE_CA}); + assertFalse(result); + assertFalse(FileUtils.isFileExist(Constants.CMD_ROOT_CA_PATH)); + assertFalse(FileUtils.isFileExist(Constants.CMD_SUB_CA_PATH)); + } catch (Exception exception) { + logger.info(exception, () -> exception.getMessage()); + } + deleteFile(Constants.CMD_ROOT_CA_PATH); + boolean result1 = HapSignTool.processCmd(new String[]{ + CmdUtil.Method.GENERATE_CA, + Constants.CMD_KEY_ALIAS, Constants.CMD_OH_APP_CA_V1, + Constants.CMD_KEY_PWD, Constants.CMD_PWD_123456, + Constants.CMD_KEY_ALG, KeyPairTools.RSA, + Constants.CMD_KEY_SIZE, Constants.CMD_RSA_2048, + Constants.CMD_KEY_STORE_FILE, Constants.CMD_KEY_STORE_PATH, + Constants.CMD_KEY_STORE_PWD, Constants.CMD_PWD_123456, + Constants.CMD_OUT_FILE, Constants.CMD_ROOT_CA_PATH, + Constants.CMD_SUBJECT, Constants.CMD_APP_CA, + Constants.CMD_VALIDITY, Constants.CMD_VALIDITY_365, + Constants.CMD_SIGN_ALG, Constants.CMD_SHA_256_WITH_RSA, + Constants.CMD_BASIC_CONSTRAINTS_PATH_LEN, Constants.CMD_BC_PATH_LEN_0}); + assertTrue(result1); + assertTrue(FileUtils.isFileExist(Constants.CMD_ROOT_CA_PATH)); + deleteFile(Constants.CMD_SUB_CA_PATH); + boolean result2 = HapSignTool.processCmd(new String[]{ + CmdUtil.Method.GENERATE_CA, + Constants.CMD_KEY_ALIAS, Constants.CMD_OH_APP1_KEY_V1, + Constants.CMD_KEY_PWD, Constants.CMD_PWD_123456, + Constants.CMD_ISSUER, Constants.CMD_APP_CA, + Constants.CMD_KEY_ALG, KeyPairTools.RSA, + Constants.CMD_KEY_SIZE, Constants.CMD_RSA_2048, + Constants.CMD_KEY_STORE_FILE, Constants.CMD_KEY_STORE_PATH, + Constants.CMD_KEY_STORE_PWD, Constants.CMD_PWD_123456, + Constants.CMD_OUT_FILE, Constants.CMD_SUB_CA_PATH, + Constants.CMD_ISSUER_KEY_ALIAS, Constants.CMD_OH_APP_CA_V1, + Constants.CMD_ISSUER_KEY_PWD, Constants.CMD_PWD_123456, + Constants.CMD_SUBJECT, Constants.CMD_APP_CA, + Constants.CMD_VALIDITY, Constants.CMD_VALIDITY_365, + Constants.CMD_SIGN_ALG, Constants.CMD_SHA_256_WITH_RSA, + Constants.CMD_BASIC_CONSTRAINTS_PATH_LEN, Constants.CMD_BC_PATH_LEN_0}); + assertTrue(result2); + assertTrue(FileUtils.isFileExist(Constants.CMD_SUB_CA_PATH)); + } + + @Order(5) + @Test + public void testCmdAppCert() throws IOException { + + try { + deleteFile(Constants.CMD_APP_CERT_PATH); + boolean result = HapSignTool.processCmd(new String[]{CmdUtil.Method.GENERATE_APP_CERT}); + assertFalse(result); + assertFalse(FileUtils.isFileExist(Constants.CMD_APP_CERT_PATH)); + } catch (Exception exception) { + logger.info(exception, () -> exception.getMessage()); + } + deleteFile(Constants.CMD_APP_CERT_PATH); + boolean result = HapSignTool.processCmd(new String[]{ + CmdUtil.Method.GENERATE_APP_CERT, + Constants.CMD_KEY_ALIAS, Constants.CMD_OH_APP1_KEY_V1, + Constants.CMD_KEY_PWD, Constants.CMD_PWD_123456, + Constants.CMD_ISSUER, Constants.CMD_APP_CA, + Constants.CMD_KEY_STORE_FILE, Constants.CMD_KEY_STORE_PATH, + Constants.CMD_KEY_STORE_PWD, Constants.CMD_PWD_123456, + Constants.CMD_OUT_FILE, Constants.CMD_APP_CERT_PATH, + Constants.CMD_ISSUER_KEY_ALIAS, Constants.CMD_OH_APP_CA_V1, + Constants.CMD_ISSUER_KEY_PWD, Constants.CMD_PWD_123456, + Constants.CMD_SUBJECT, Constants.CMD_APP1_RELEASE, + Constants.CMD_VALIDITY, Constants.CMD_VALIDITY_365, + Constants.CMD_OUT_FORM, Constants.CMD_CERT_CHAIN, + Constants.CMD_ROOT_CA_CERT_FILE, Constants.CMD_ROOT_CA_PATH, + Constants.CMD_SUB_CA_CERT_FILE, Constants.CMD_SUB_CA_PATH, + Constants.CMD_SIGN_ALG, Constants.CMD_SHA_256_WITH_RSA}); + assertTrue(result); + assertTrue(FileUtils.isFileExist(Constants.CMD_APP_CERT_PATH)); + } + + @Order(6) + @Test + public void testCmdProfileCert() throws IOException { + try { + deleteFile(Constants.CMD_PROFILE_CERT_PATH); + boolean result = HapSignTool.processCmd(new String[]{CmdUtil.Method.GENERATE_PROFILE_CERT}); + assertFalse(result); + assertFalse(FileUtils.isFileExist(Constants.CMD_PROFILE_CERT_PATH)); + } catch (Exception exception) { + logger.info(exception, () -> exception.getMessage()); + } + + deleteFile(Constants.CMD_PROFILE_CERT_PATH); + boolean result = HapSignTool.processCmd(new String[]{ + CmdUtil.Method.GENERATE_PROFILE_CERT, + Constants.CMD_KEY_ALIAS, Constants.CMD_OH_APP1_KEY_V1, + Constants.CMD_KEY_PWD, Constants.CMD_PWD_123456, + Constants.CMD_ISSUER, Constants.CMD_PP_CA, + Constants.CMD_KEY_STORE_FILE, Constants.CMD_KEY_STORE_PATH, + Constants.CMD_KEY_STORE_PWD, Constants.CMD_PWD_123456, + Constants.CMD_OUT_FILE, Constants.CMD_PROFILE_CERT_PATH, + Constants.CMD_ISSUER_KEY_ALIAS, Constants.CMD_OH_APP_CA_V1, + Constants.CMD_ISSUER_KEY_PWD, Constants.CMD_PWD_123456, + Constants.CMD_SUBJECT, Constants.CMD_PP_RELEASE, + Constants.CMD_VALIDITY, Constants.CMD_VALIDITY_365, + Constants.CMD_OUT_FORM, Constants.CMD_CERT_CHAIN, + Constants.CMD_ROOT_CA_CERT_FILE, Constants.CMD_ROOT_CA_PATH, + Constants.CMD_SUB_CA_CERT_FILE, Constants.CMD_SUB_CA_PATH, + Constants.CMD_SIGN_ALG, Constants.CMD_SHA_256_WITH_RSA}); + assertTrue(result); + assertTrue(FileUtils.isFileExist(Constants.CMD_PROFILE_CERT_PATH)); + } + + @Order(7) + @Test + public void testCmdSignProfile() throws IOException { + + try { + deleteFile(Constants.CMD_SIGN_PROFILE_PATH); + boolean result = HapSignTool.processCmd(new String[]{CmdUtil.Method.SIGN_PROFILE}); + assertFalse(result); + assertFalse(FileUtils.isFileExist(Constants.CMD_SIGN_PROFILE_PATH)); + } catch (Exception exception) { + logger.info(exception, () -> exception.getMessage()); + } + + deleteFile(Constants.CMD_SIGN_PROFILE_PATH); + loadFile(Constants.CMD_JSON_FILE); + boolean result = HapSignTool.processCmd(new String[]{ + CmdUtil.Method.SIGN_PROFILE, + Constants.CMD_MODE, Constants.CMD_LOCAL_SIGN, + Constants.CMD_KEY_ALIAS, Constants.CMD_OH_APP1_KEY_V1, + Constants.CMD_KEY_PWD, Constants.CMD_PWD_123456, + Constants.CMD_PROFILE_CERT_FILE, Constants.CMD_PROFILE_CERT_PATH, + Constants.CMD_IN_FILE, Constants.CMD_JSON_FILE, + Constants.CMD_SIGN_ALG, Constants.CMD_SHA_256_WITH_RSA, + Constants.CMD_KEY_STORE_FILE, Constants.CMD_KEY_STORE_PATH, + Constants.CMD_KEY_STORE_PWD, Constants.CMD_PWD_123456, + Constants.CMD_OUT_FILE, Constants.CMD_SIGN_PROFILE_PATH}); + assertTrue(result); + assertTrue(FileUtils.isFileExist(Constants.CMD_SIGN_PROFILE_PATH)); + } + + @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, + Constants.CMD_IN_FILE, Constants.CMD_SIGN_PROFILE_PATH, + Constants.CMD_OUT_FILE, Constants.CMD_VERIFY_PROFILE_PATH}); + assertTrue(result); + } + + @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()); + } + } + + @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 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/java/com/ohos/hapsigntoolcmd/Constants.java b/hapsigntool/hap_sign_tool/src/test/java/com/ohos/hapsigntoolcmd/Constants.java new file mode 100644 index 0000000000000000000000000000000000000000..9ab9d23bc204364cef9507a688dc719179b44211 --- /dev/null +++ b/hapsigntool/hap_sign_tool/src/test/java/com/ohos/hapsigntoolcmd/Constants.java @@ -0,0 +1,245 @@ +/* + * 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; + +public final class Constants { + private Constants() { + } + + /** + * 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_PWD = "-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_PWD = "-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 profileCaCertFile. + */ + public static final String CMD_PROFILE_CA_CERT_FILE = "-profileCaCertFile"; + /** + * 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_PWD = "-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_PWD_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-cert. + */ + public static final String CMD_APP_CERT_PATH = "test_app-cert.cer"; + /** + * 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_csr. + */ + public static final String CMD_KEY_STORE_PATH = "test_keypair.jks"; + /** + * Command line parameter cer file is test_root_ca. + */ + public static final String CMD_ROOT_CA_PATH = "test_root_ca.cer"; + /** + * Command line parameter cer file is test_sub_ca. + */ + public static final String CMD_SUB_CA_PATH = "test_sub_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-cert. + */ + public static final String CMD_PROFILE_CERT_PATH = "test_profile-cert.cer"; + /** + * Command line parameter cer file is test_verify_profile. + */ + public static final String CMD_VERIFY_PROFILE_PATH = "test_verify_profile.cer"; + /** + * Command line parameter oh-app-ca-v1. + */ + public static final String CMD_OH_APP_CA_V1 = "oh-app-ca-v1"; + /** + * Command line parameter oh-app1-key-v1. + */ + public static final String CMD_OH_APP1_KEY_V1 = "oh-app1-key-v1"; + /** + * 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_PP_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_PP_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"; +} 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..0b93a54575cc47e631ae05e2e10712ea791ac80c --- /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.5' +} + +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..6472901b1803f8d633a5baeaafa5304179d7742f --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/api/LocalizationAdapter.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.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. + */ +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 Options options; + /** + * Operation of keystore + */ + private KeyStoreHelper keyStoreHelper; + + /** + * Constructor of LocalizationAdapter. + * + * @param options options + */ + public LocalizationAdapter(Options options) { + this.options = options; + initKeyStore(); + } + + public Options getOptions() { + return options; + } + + public void setOptions(Options options) { + this.options = options; + } + + private void initKeyStore() { + keyStoreHelper = new KeyStoreHelper(options.getString(Options.KEY_STORE_FILE, ""), + options.getChars(Options.KEY_STORE_PWD)); + } + + /** + * 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_PWD), autoCreate); + } + + public KeyPair getIssuerAliasKey() { + return getKeyPair(options.getString(Options.ISSUER_KEY_ALIAS), + options.getChars(Options.ISSUER_KEY_PWD), false); + } + + /** + * Keystore has alias or not. + * + * @param alias alias + * @return true or false + */ + public boolean hasAlias(String alias) { + return keyStoreHelper.hasAlias(alias); + } + + /** + * Error if not exist. + * + * @param alias alias + */ + public void errorIfNotExist(String alias) { + keyStoreHelper.errorIfNotExist(alias); + } + + /** + * Error on exist. + * + * @param alias alias + */ + public void errorOnExist(String alias) { + keyStoreHelper.errorOnExist(alias); + } + + private KeyPair getKeyPair(String alias, char[] keyPwd, boolean autoCreate) { + 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; + } + + public List getAppCert() { + String certPath = options.getString(Options.APP_CERT_FILE); + return getCertsFromFile(certPath, Options.APP_CERT_FILE); + } + + + public List getProfileCert() { + String certPath = options.getString(Options.PROFILE_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; + } + + public X509Certificate getSubCaCertFile() { + String certPath = options.getString(Options.SUB_CA_CERT_FILE); + return getCertsFromFile(certPath, Options.SUB_CA_CERT_FILE).get(0); + } + + public X509Certificate getCaCertFile() { + String certPath = options.getString(Options.CA_CERT_FILE); + return getCertsFromFile(certPath, Options.CA_CERT_FILE).get(0); + } + + 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; + } + + public String getSignAlg() { + return options.getString(Options.SIGN_ALG); + } + + public boolean isKeyUsageCritical() { + return options.getBoolean(Options.KEY_USAGE_CRITICAL, true); + } + + public boolean isExtKeyUsageCritical() { + return options.getBoolean(Options.EXT_KEY_USAGE_CRITICAL, true); + } + + public boolean isBasicConstraintsCa() { + return options.getBoolean(Options.BASIC_CONSTRAINTS_CA, false); + } + + public boolean isBasicConstraintsCritical() { + return options.getBoolean(Options.BASIC_CONSTRAINTS_CRITICAL, false); + } + + public int getBasicConstraintsPathLen() { + return options.getInt(Options.BASIC_CONSTRAINTS_PATH_LEN); + } + + public KeyPurposeId[] getExtKeyUsage() { + return CertUtils.parseExtKeyUsage(options.getString(Options.EXT_KEY_USAGE)); + } + + public KeyUsage getKeyUsage() { + return new KeyUsage(CertUtils.parseKeyUsage(options.getString(Options.KEY_USAGE))); + } + + public X500Name getSubject() { + String subject = options.getString(Options.SUBJECT); + return CertUtils.buildDN(subject); + } + + public X500Name getIssuer() { + String issuer = options.getString(Options.ISSUER, options.getString(Options.SUBJECT)); + return CertUtils.buildDN(issuer); + } + + public String getOutFile() { + return options.getString(Options.OUT_FILE); + } + + 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; + } + + 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_PWD)); + resetChars(options.getChars(Options.KEY_PWD)); + resetChars(options.getChars(Options.ISSUER_KEY_PWD)); + } + + 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..9a5c9279c094b45998e9ff75aa94e78466e1d60f --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/api/ServiceApi.java @@ -0,0 +1,105 @@ +/* + * 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. + */ +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..30bf48f7e94cf4b41a2912be4b9db794ab037fef --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/api/SignToolServiceImpl.java @@ -0,0 +1,263 @@ +/* + * 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.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.List; + +/** + * Main entry of lib + */ +public class SignToolServiceImpl implements ServiceApi { + + static { + Security.addProvider(new BouncyCastleProvider()); + } + + /** + * Logger. + */ + private final Logger logger = LogManager.getLogger(ServiceApi.class); + + @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; + } + + @Override + public boolean generateCsr(Options options) { + LocalizationAdapter adapter = new LocalizationAdapter(options); + KeyPair keyPair = adapter.getAliasKey(false); + adapter.releasePwd(); + if (keyPair == null) { + return false; + } + byte[] csr = CertTools.generateCsr(keyPair, adapter.getSignAlg(), adapter.getSubject()); + if (csr == null) { + return false; + } + String csrContent = CertUtils.toCsrTemplate(csr); + return outputCsr(csrContent, adapter.getOutFile()); + } + + /** + * Common Cert generator + */ + @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()); + + } + + @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()); + } + + @Override + public boolean generateAppCert(Options options) { + return generateProfileCert(options); + } + + @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()); + } + } + + @Override + public boolean signProfile(Options options) { + 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())); + } catch (IOException exception) { + logger.debug(exception.getMessage(), exception); + logger.error(exception.getMessage()); + return false; + } + return true; + } + + @Override + public boolean verifyProfile(Options options) { + try { + LocalizationAdapter adapter = new LocalizationAdapter(options); + VerifyHelper verifyHelper = new VerifyHelper(); + byte[] p7b = FileUtils.readFile(new File(adapter.getInFile())); + VerificationResult verificationResult = verifyHelper.verify(p7b); + logger.info(FileUtils.GSON_PRETTY_PRINT.toJson(verificationResult)); + return verificationResult.isVerifiedPassed(); + } catch (IOException exception) { + logger.debug(exception.getMessage(), exception); + logger.error(exception.getMessage()); + return false; + } + } + + @Override + public boolean signHap(Options options) { + logger.info("Start signing hap. But not implement yet"); + return false; + } + + @Override + public boolean verifyHap(Options options) { + logger.info("Start verifying hap. But not implement yet"); + return false; + } + + /** + * Output csr. + * + * @param csrContent csrContent + * @param file file + * @return Whether or not to output csr + */ + public boolean outputCsr(String csrContent, String file) { + if (StringUtils.isEmpty(file)) { + logger.info(csrContent); + return true; + } + try { + FileUtils.write(csrContent.getBytes(StandardCharsets.UTF_8), new File(file)); + return true; + } catch (IOException exception) { + logger.debug(exception.getMessage(), exception); + CustomException.throwException(ERROR.WRITE_FILE_ERROR, exception.getMessage()); + return false; + } + } + + /** + * Output certificate. + * + * @param certificate certificate + * @param file file + * @return Whether or not 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 or not 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..54221f585cd20994a0538d36867797e75f587013 --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/api/model/Options.java @@ -0,0 +1,309 @@ +/* + * 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. + */ +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 pwd parameter name. + */ + public static final String ISSUER_KEY_PWD = "issuerKeyPwd"; + /** + * Key alg parameter name. + */ + public static final String KEY_ALG = "keyAlg"; + /** + * Key alias parameter name. + */ + public static final String KEY_ALIAS = "keyAlias"; + /** + * Key pwd parameter name. + */ + public static final String KEY_PWD = "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 pwd parameter name. + */ + public static final String KEY_STORE_PWD = "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"; + /** + * 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 = new String[]{"digitalSignature", "nonRepudiation", "keyEncipherment", + "dataEncipherment", "keyAgreement", "certificateSignature", "crlSignature", + "encipherOnly", "decipherOnly"}; + + /** + * Out form includes all forms. + */ + public static final String[] OUT_FORM_SCOPE = new String[]{"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 (StringUtils.isEmpty(value) || !(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..ae186469051797dba18b59593cd1de3a4b0b4502 --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/cert/CertBuilder.java @@ -0,0 +1,197 @@ +/* + * 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; + +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..fb190f5f7e50d146b1e7a0b26f2fd80f57849cf7 --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/cert/CertLevel.java @@ -0,0 +1,34 @@ +/* + * 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 + */ +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..e8ab20a23e4361b94f9edc3f12bcc301913dbc43 --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/cert/CertTools.java @@ -0,0 +1,220 @@ +/* + * 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; + +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; + } + + 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..3355b32a9975137d6d0ef03906828e4c27b43c11 --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/error/CustomException.java @@ -0,0 +1,43 @@ +/* + * 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. + */ +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..945085610bceab56cb16317e8c70f82e65bf9384 --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/error/ERROR.java @@ -0,0 +1,69 @@ +/* + * 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; + +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; + + 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/key/KeyPairTools.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/key/KeyPairTools.java new file mode 100644 index 0000000000000000000000000000000000000000..334b479e87e53eff915e4c839d3d1d5877f184e7 --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/key/KeyPairTools.java @@ -0,0 +1,136 @@ +/* + * 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 + */ +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..26026cb599341c93ef229c672844dad1cc88b3c0 --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/keystore/KeyStoreHelper.java @@ -0,0 +1,300 @@ +/* + * 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.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 + */ +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; + + public KeyStoreHelper(String keyStorePath, char[] storePwd) { + 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()); + } + } + + 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)); + } + + public void errorIfNotExist(String alias) { + ValidateUtils.throwIfNotMatches(this.hasAlias(alias), ERROR.FILE_NOT_FOUND, + String.format("Not exist '%s' in %s", alias, this.keyStorePath)); + } + + 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; + } + } + + 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..fda1a23ad5d0376df629159a77efa50b32b7b3cb --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/profile/IProvisionVerifier.java @@ -0,0 +1,23 @@ +/* + * 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; + +@FunctionalInterface +public interface IProvisionVerifier { + 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..f2b89559bb6db8c68ce20d2a5739ada8223daf18 --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/profile/ProfileSignTool.java @@ -0,0 +1,177 @@ +/* + * 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 + */ +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() { + } + + 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); + } + + 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); + } + + public 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..0e07c351cb6e24c9bd6af189216c0ac8b8c01afd --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/profile/VerifyHelper.java @@ -0,0 +1,103 @@ +/* + * 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. + */ +public class VerifyHelper implements IProvisionVerifier { + + /** + * LOGGER. + */ + private static final Logger LOGGER = LogManager.getLogger(VerifyHelper.class); + + /** + * Signed provision profile verifier. + */ + public VerifyHelper() { + // Empty constructor + } + + @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..fc2ba7448d6e2a2999f7fe0213e47682d245aff2 --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/profile/model/Acls.java @@ -0,0 +1,46 @@ +/* + * 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. + */ +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..0930c2d04ecee3d3b543fe3730fb437f2d8eb34c --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/profile/model/BundleInfo.java @@ -0,0 +1,128 @@ +/* + * 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. + */ +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!"); + 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..90154909239e44039e5f29998801dcf5c7a5882b --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/profile/model/DebugInfo.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.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. + */ +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..51ceecdeff006b9db50d52ec7eb5d74a8ba7ac24 --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/profile/model/Permissions.java @@ -0,0 +1,60 @@ +/* + * 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. + */ +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..8f3a0880c43ee70d807da2be38b61d7501dd2cfa --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/profile/model/Provision.java @@ -0,0 +1,304 @@ +/* + * 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. + */ +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(provision.type == null, 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!"); + 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..f89c3d08dd4701f49fc3dd0ea86378dd2283fda2 --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/profile/model/Validity.java @@ -0,0 +1,57 @@ +/* + * 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; + +/** + * Sub dto of Provision. + */ +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. + } + + 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..c5a27a9e815e5038a3d4379e7593211c964eb7ea --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/profile/model/VerificationResult.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; + +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..aa7a28be08459f9aa0651bff097c54a199780297 --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/signer/ISigner.java @@ -0,0 +1,39 @@ +/* + * 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; + +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); + + List getCrls(); + + 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..cb82cabc5fae266f7039729e9ad571d40e9cd7fe --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/signer/LocalSigner.java @@ -0,0 +1,80 @@ +/* + * 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; + +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; + } + + @Override + public byte[] getSignature(byte[] data, String signAlg, AlgorithmParameterSpec parameterSpec) { + byte[] signData = null; + try { + Signature signature = Signature.getInstance(signAlg); + signature.initSign(privateKey); + signature.update(data); + signData = signature.sign(); + } catch (Exception exception) { + logger.debug(exception.getMessage(), exception); + CustomException.throwException(ERROR.SIGN_ERROR, exception.getMessage()); + } + return signData; + } + + @Override + public List getCrls() { + return new ArrayList<>(); + } + + @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..b3ed09c6c4dc9889c1d1823d7b21d88cbca24565 --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/signer/RemoteSigner.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.signer; + +import java.security.cert.X509CRL; +import java.security.cert.X509Certificate; +import java.security.spec.AlgorithmParameterSpec; +import java.util.List; +import java.util.Map; + +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) { + } + + @Override + public byte[] getSignature(byte[] data, String signAlg, AlgorithmParameterSpec parameterSpec) { + throw new UnsupportedOperationException(ERROR); + } + + @Override + public List getCrls() { + throw new UnsupportedOperationException(ERROR); + } + + @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..f751b6b0aa19406d70ee6d37fde9a30f8dfb10d4 --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/signer/SignerFactory.java @@ -0,0 +1,42 @@ +/* + * 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. + */ +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.getProfileCert()); + } + } +} 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..8404bf71dfa43358a69e3556af4e60f2af0d78c0 --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/utils/CertUtils.java @@ -0,0 +1,265 @@ +/* + * 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. + */ +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, ""); + + nameString = nameString.replace(",", "\",\""); + nameString = "{\"" + nameString.replace("=", "\":\"") + "\"}"; + + X500NameBuilder builder = new X500NameBuilder(); + HashMap map = new Gson().fromJson(nameString, HashMap.class); + + BCStyle x500NameStyle = (BCStyle) BCStyle.INSTANCE; + for (Map.Entry entry : map.entrySet()) { + ASN1ObjectIdentifier oid = x500NameStyle.attrNameToOID(entry.getKey()); + builder.addRDN(oid, entry.getValue()); + } + 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/FileUtils.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/utils/FileUtils.java new file mode 100644 index 0000000000000000000000000000000000000000..c0799c87e4994d8af0282539c487d2b9cdb14acf --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/utils/FileUtils.java @@ -0,0 +1,178 @@ +/* + * 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.ByteArrayOutputStream; +import java.io.Closeable; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; + + +/** + * Common file operation + */ +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 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]; + } +} 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..964f08ddf81eac512bffd90b83e47a7402185f6f --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/utils/StringUtils.java @@ -0,0 +1,25 @@ +/* + * 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; + +public final class StringUtils { + private StringUtils() { + } + + public static boolean isEmpty(Object obj) { + return obj == null || "".equals(obj); + } +} 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..9838ceabdfedca00df69e1bda244d91aade5352b --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/utils/ValidateUtils.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.utils; + +import com.ohos.hapsigntool.error.CustomException; +import com.ohos.hapsigntool.error.ERROR; + +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/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..d4fde0c8cf5666fe225b8169670a2012c4bcdc30 --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/test/java/com/ohos/hapsigntool/CertTest.java @@ -0,0 +1,219 @@ +/* + * 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. + */ +@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..7e67800b545a8967f07bfc80478e473874f1d034 --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/test/java/com/ohos/hapsigntool/KeyPairTest.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; + +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; + +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..66eb77a846475c3d46e2c79826a21c5db8222ea7 --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/test/java/com/ohos/hapsigntool/KeyStoreTest.java @@ -0,0 +1,57 @@ +/* + * 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; + +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..bbc05c5f2917cdd7cdd01520ced26fa395a4e7ab --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/test/java/com/ohos/hapsigntool/ProfileTest.java @@ -0,0 +1,164 @@ +/* + * 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; + + +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_PWD, 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_PWD, 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' +