• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (c) 2021-2023 Huawei Device Co., Ltd.
3  * Licensed under the Apache License, Version 2.0 (the "License");
4  * you may not use this file except in compliance with the License.
5  * You may obtain a copy of the License at
6  *
7  *     http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software
10  * distributed under the License is distributed on an "AS IS" BASIS,
11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  * See the License for the specific language governing permissions and
13  * limitations under the License.
14  */
15 
16 package com.ohos.hapsigntool.hap.provider;
17 
18 import com.google.gson.JsonElement;
19 import com.google.gson.JsonObject;
20 import com.google.gson.JsonParseException;
21 import com.google.gson.JsonParser;
22 import com.ohos.hapsigntool.entity.Options;
23 import com.ohos.hapsigntool.codesigning.exception.CodeSignException;
24 import com.ohos.hapsigntool.codesigning.exception.FsVerityDigestException;
25 import com.ohos.hapsigntool.codesigning.sign.CodeSigning;
26 import com.ohos.hapsigntool.error.CustomException;
27 import com.ohos.hapsigntool.hap.config.SignerConfig;
28 import com.ohos.hapsigntool.hap.entity.SigningBlock;
29 import com.ohos.hapsigntool.error.HapFormatException;
30 import com.ohos.hapsigntool.error.InvalidParamsException;
31 import com.ohos.hapsigntool.error.MissingParamsException;
32 import com.ohos.hapsigntool.error.ProfileException;
33 import com.ohos.hapsigntool.error.SignatureException;
34 import com.ohos.hapsigntool.error.VerifyCertificateChainException;
35 import com.ohos.hapsigntool.hap.sign.SignBin;
36 import com.ohos.hapsigntool.hap.sign.SignElf;
37 import com.ohos.hapsigntool.hap.sign.SignHap;
38 import com.ohos.hapsigntool.entity.SignatureAlgorithm;
39 import com.ohos.hapsigntool.hap.verify.VerifyUtils;
40 import com.ohos.hapsigntool.utils.CertificateUtils;
41 import com.ohos.hapsigntool.utils.DigestUtils;
42 import com.ohos.hapsigntool.utils.EscapeCharacter;
43 import com.ohos.hapsigntool.utils.FileUtils;
44 import com.ohos.hapsigntool.hap.utils.HapUtils;
45 import com.ohos.hapsigntool.entity.ParamConstants;
46 import com.ohos.hapsigntool.utils.ParamProcessUtil;
47 import com.ohos.hapsigntool.utils.StringUtils;
48 import com.ohos.hapsigntool.zip.ByteBufferZipDataInput;
49 import com.ohos.hapsigntool.zip.RandomAccessFileZipDataInput;
50 import com.ohos.hapsigntool.zip.RandomAccessFileZipDataOutput;
51 import com.ohos.hapsigntool.zip.Zip;
52 import com.ohos.hapsigntool.zip.ZipDataInput;
53 import com.ohos.hapsigntool.zip.ZipDataOutput;
54 import com.ohos.hapsigntool.zip.ZipFileInfo;
55 import com.ohos.hapsigntool.zip.ZipUtils;
56 
57 import org.apache.logging.log4j.LogManager;
58 import org.apache.logging.log4j.Logger;
59 import org.bouncycastle.cms.CMSException;
60 import org.bouncycastle.cms.CMSSignedData;
61 import org.bouncycastle.jce.provider.BouncyCastleProvider;
62 
63 import java.io.File;
64 import java.io.IOException;
65 import java.io.RandomAccessFile;
66 import java.nio.ByteBuffer;
67 import java.nio.ByteOrder;
68 import java.nio.charset.StandardCharsets;
69 import java.nio.file.Files;
70 import java.nio.file.StandardCopyOption;
71 import java.security.InvalidKeyException;
72 import java.security.Security;
73 import java.security.cert.CertificateException;
74 import java.security.cert.X509CRL;
75 import java.security.cert.X509Certificate;
76 import java.util.ArrayList;
77 import java.util.Collections;
78 import java.util.HashMap;
79 import java.util.List;
80 import java.util.Map;
81 import java.util.Optional;
82 import java.util.Set;
83 
84 /**
85  * Sign provider super class
86  *
87  * @since 2021-12-14
88  */
89 public abstract class SignProvider {
90     private static final Logger LOGGER = LogManager.getLogger(SignProvider.class);
91     private static final List<String> VALID_SIGN_ALG_NAME = new ArrayList<String>();
92     private static final List<String> PARAMETERS_NEED_ESCAPE = new ArrayList<String>();
93     private static final long TIMESTAMP = 1230768000000L;
94     private static final int COMPRESSION_MODE = 9;
95 
96     static {
97         VALID_SIGN_ALG_NAME.add(ParamConstants.HAP_SIG_ALGORITHM_SHA256_ECDSA);
98         VALID_SIGN_ALG_NAME.add(ParamConstants.HAP_SIG_ALGORITHM_SHA384_ECDSA);
99         VALID_SIGN_ALG_NAME.add(ParamConstants.HAP_SIG_ALGORITHM_SHA512_ECDSA);
100         VALID_SIGN_ALG_NAME.add(ParamConstants.HAP_SIG_ALGORITHM_SHA256_RSA_PSS);
101         VALID_SIGN_ALG_NAME.add(ParamConstants.HAP_SIG_ALGORITHM_SHA384_RSA_PSS);
102         VALID_SIGN_ALG_NAME.add(ParamConstants.HAP_SIG_ALGORITHM_SHA512_RSA_PSS);
103         VALID_SIGN_ALG_NAME.add(ParamConstants.HAP_SIG_ALGORITHM_SHA256_RSA_MGF1);
104         VALID_SIGN_ALG_NAME.add(ParamConstants.HAP_SIG_ALGORITHM_SHA384_RSA_MGF1);
105         VALID_SIGN_ALG_NAME.add(ParamConstants.HAP_SIG_ALGORITHM_SHA512_RSA_MGF1);
Security.addProvider(new BouncyCastleProvider())106         Security.addProvider(new BouncyCastleProvider());
107     }
108 
109     static {
110         PARAMETERS_NEED_ESCAPE.add(ParamConstants.PARAM_REMOTE_CODE);
111         PARAMETERS_NEED_ESCAPE.add(ParamConstants.PARAM_LOCAL_JKS_KEYSTORE_CODE);
112         PARAMETERS_NEED_ESCAPE.add(ParamConstants.PARAM_LOCAL_JKS_KEYALIAS_CODE);
113     }
114 
115     /**
116      * list of hap signature optional blocks
117      */
118     protected List<SigningBlock> optionalBlocks = new ArrayList<SigningBlock>();
119 
120     /**
121      * parameters only used in signing
122      */
123     protected Map<String, String> signParams = new HashMap<String, String>();
124 
125     private String profileContent;
126 
127     /**
128      * Read data of optional blocks from file user inputted.
129      *
130      * @throws InvalidParamsException Exception occurs when the input is invalid.
131      */
loadOptionalBlocks()132     protected void loadOptionalBlocks() throws InvalidParamsException {
133         String property = signParams.get(ParamConstants.PARAM_BASIC_PROPERTY);
134         loadOptionalBlock(property, HapUtils.HAP_PROPERTY_BLOCK_ID);
135 
136         String profile = signParams.get(ParamConstants.PARAM_BASIC_PROFILE);
137         loadOptionalBlock(profile, HapUtils.HAP_PROFILE_BLOCK_ID);
138 
139         String proofOfRotation = signParams.get(ParamConstants.PARAM_BASIC_PROOF);
140         loadOptionalBlock(proofOfRotation, HapUtils.HAP_PROOF_OF_ROTATION_BLOCK_ID);
141     }
142 
loadOptionalBlock(String file, int type)143     private void loadOptionalBlock(String file, int type) throws InvalidParamsException {
144         if (!checkStringIsNotNullAndEmity(file)) {
145             return;
146         }
147         if (!checkFile(file)) {
148             LOGGER.error("check file failed");
149             throw new InvalidParamsException("Invalid file: " + file + ", filetype: " + type);
150         }
151         try {
152             byte[] optionalBlockBytes = HapUtils.readFileToByte(file);
153             if (optionalBlockBytes == null || optionalBlockBytes.length <= 0) {
154                 LOGGER.warn("Optional block is null!");
155                 return;
156             }
157             optionalBlocks.add(new SigningBlock(type, optionalBlockBytes));
158         } catch (IOException e) {
159             LOGGER.error("read file error", e);
160             throw new InvalidParamsException("Invalid file: " + file + " is not readable. filetype: " + type);
161         }
162     }
163 
164     /**
165      * check if the input path is a file
166      *
167      * @param filePath input file path
168      * @return true, if path is a file and can be read
169      */
checkFile(String filePath)170     private boolean checkFile(String filePath) {
171         if (!(checkStringIsNotNullAndEmity(filePath))) {
172             LOGGER.error("fileName is null");
173             return false;
174         }
175         File file = new File(filePath);
176         if (!file.canRead() || !file.isFile()) {
177             LOGGER.error(filePath + " not exist or can not read!");
178             return false;
179         }
180         return true;
181     }
182 
checkStringIsNotNullAndEmity(String str)183     private boolean checkStringIsNotNullAndEmity(String str) {
184         return !(str == null || "".equals(str));
185     }
186 
187     /**
188      * Get certificate chain used to sign.
189      *
190      * @return list of x509 certificates.
191      */
getPublicCerts()192     private List<X509Certificate> getPublicCerts() {
193         String publicCertsFile = signParams.get(ParamConstants.PARAM_LOCAL_PUBLIC_CERT);
194         if (StringUtils.isEmpty(publicCertsFile)) {
195             return Collections.emptyList();
196         }
197         return getCertificateChainFromFile(publicCertsFile);
198     }
199 
200     /**
201      * get certificate revocation list used to sign
202      *
203      * @return certificate revocation list
204      */
getCrl()205     public Optional<X509CRL> getCrl() {
206         return Optional.empty();
207     }
208 
209     /**
210      * Create SignerConfig by certificate chain and certificate revocation list.
211      *
212      * @param certificates certificate chain
213      * @param crl certificate revocation list
214      * @param options options
215      * @return Object of SignerConfig
216      * @throws InvalidKeyException on error when the key is invalid.
217      */
createSignerConfigs(List<X509Certificate> certificates, Optional<X509CRL> crl, Options options)218     public SignerConfig createSignerConfigs(List<X509Certificate> certificates, Optional<X509CRL> crl, Options options)
219             throws InvalidKeyException {
220         SignerConfig signerConfig = new SignerConfig();
221         signerConfig.fillParameters(this.signParams);
222         signerConfig.setCertificates(certificates);
223         signerConfig.setOptions(options);
224 
225         List<SignatureAlgorithm> signatureAlgorithms = new ArrayList<SignatureAlgorithm>();
226         signatureAlgorithms.add(
227                 ParamProcessUtil.getSignatureAlgorithm(this.signParams.get(ParamConstants.PARAM_BASIC_SIGANTURE_ALG)));
228         signerConfig.setSignatureAlgorithms(signatureAlgorithms);
229 
230         if (!crl.equals(Optional.empty())) {
231             signerConfig.setX509CRLs(Collections.singletonList(crl.get()));
232         }
233         return signerConfig;
234     }
235 
236     /**
237      * sign bin file
238      *
239      * @param options parameters used to sign bin file
240      * @return true, if sign successfully.
241      */
signBin(Options options)242     public boolean signBin(Options options) {
243         List<X509Certificate> publicCert = null;
244         SignerConfig signerConfig;
245         try {
246             publicCert = getX509Certificates(options);
247 
248             // Get x509 CRL
249             Optional<X509CRL> crl = getCrl();
250 
251             // Create signer configs, which contains public cert and crl info.
252             signerConfig = createSignerConfigs(publicCert, crl, options);
253         } catch (InvalidKeyException | InvalidParamsException | MissingParamsException | ProfileException e) {
254             LOGGER.error("create signer configs failed.", e);
255             printErrorLogWithoutStack(e);
256             return false;
257         }
258 
259         /* 6. make signed file into output file. */
260         if (!SignBin.sign(signerConfig, signParams)) {
261             LOGGER.error("hap-sign-tool: error: Sign bin internal failed.");
262             return false;
263         }
264         LOGGER.info("Sign success");
265         return true;
266     }
267 
268     /**
269      * sign elf file
270      *
271      * @param options parameters used to sign elf file
272      * @return true, if sign successfully.
273      */
signElf(Options options)274     public boolean signElf(Options options) {
275         List<X509Certificate> publicCert = null;
276         SignerConfig signerConfig = null;
277         try {
278             publicCert = getX509Certificates(options);
279 
280             // Get x509 CRL
281             Optional<X509CRL> crl = getCrl();
282 
283             // Create signer configs, which contains public cert and crl info.
284             signerConfig = createSignerConfigs(publicCert, crl, options);
285         } catch (InvalidKeyException | InvalidParamsException | MissingParamsException | ProfileException e) {
286             LOGGER.error("create signer configs failed.", e);
287             printErrorLogWithoutStack(e);
288             return false;
289         }
290 
291         if (ParamConstants.ProfileSignFlag.DISABLE_SIGN_CODE.getSignFlag().equals(
292                 signParams.get(ParamConstants.PARAM_BASIC_PROFILE_SIGNED))) {
293             LOGGER.error("hap-sign-tool: error: Sign elf can not use unsigned profile.");
294             return false;
295         }
296 
297         if (profileContent != null) {
298             signParams.put(ParamConstants.PARAM_PROFILE_JSON_CONTENT, profileContent);
299         }
300         /* 6. make signed file into output file. */
301         if (!SignElf.sign(signerConfig, signParams)) {
302             LOGGER.error("hap-sign-tool: error: Sign elf internal failed.");
303             return false;
304         }
305         LOGGER.info("Sign success");
306         return true;
307     }
308 
309     /**
310      * sign hap file
311      *
312      * @param options parameters used to sign hap file
313      * @return true, if sign successfully
314      */
sign(Options options)315     public boolean sign(Options options) {
316         List<X509Certificate> publicCerts = null;
317         File output = null;
318         File tmpOutput = null;
319         boolean isRet = false;
320         boolean isPathOverlap = false;
321         try {
322             publicCerts = getX509Certificates(options);
323             checkCompatibleVersion();
324             File input = new File(signParams.get(ParamConstants.PARAM_BASIC_INPUT_FILE));
325             output = new File(signParams.get(ParamConstants.PARAM_BASIC_OUTPUT_FILE));
326             String suffix = getFileSuffix(input);
327             if (input.getCanonicalPath().equals(output.getCanonicalPath())) {
328                 tmpOutput = File.createTempFile("signedHap", "." + suffix);
329                 isPathOverlap = true;
330             } else {
331                 tmpOutput = output;
332             }
333             // copy file and Alignment
334             int alignment = Integer.parseInt(signParams.get(ParamConstants.PARAM_BASIC_ALIGNMENT));
335             Zip zip = copyFileAndAlignment(input, tmpOutput, alignment);
336             // generate sign block and output signedHap
337             try (RandomAccessFile outputHap = new RandomAccessFile(tmpOutput, "rw")) {
338                 ZipDataInput outputHapIn = new RandomAccessFileZipDataInput(outputHap);
339                 ZipFileInfo zipInfo = ZipUtils.findZipInfo(outputHapIn);
340                 long centralDirectoryOffset = zipInfo.getCentralDirectoryOffset();
341                 ZipDataInput beforeCentralDir = outputHapIn.slice(0, centralDirectoryOffset);
342                 ByteBuffer centralDirBuffer =
343                         outputHapIn.createByteBuffer(centralDirectoryOffset, zipInfo.getCentralDirectorySize());
344                 ZipDataInput centralDirectory = new ByteBufferZipDataInput(centralDirBuffer);
345                 ByteBuffer eocdBuffer = zipInfo.getEocd();
346                 ZipDataInput eocd = new ByteBufferZipDataInput(eocdBuffer);
347 
348                 Optional<X509CRL> crl = getCrl();
349                 SignerConfig signerConfig = createSignerConfigs(publicCerts, crl, options);
350                 signerConfig.setCompatibleVersion(Integer.parseInt(
351                         signParams.get(ParamConstants.PARAM_BASIC_COMPATIBLE_VERSION)));
352                 ZipDataInput[] contents = {beforeCentralDir, centralDirectory, eocd};
353                 appendCodeSignBlock(signerConfig, tmpOutput, suffix, centralDirectoryOffset, zip);
354                 byte[] signingBlock = SignHap.sign(contents, signerConfig, optionalBlocks);
355                 long newCentralDirectoryOffset = centralDirectoryOffset + signingBlock.length;
356                 ZipUtils.setCentralDirectoryOffset(eocdBuffer, newCentralDirectoryOffset);
357                 LOGGER.info("Generate signing block success, begin write it to output file");
358 
359                 outputSignedFile(outputHap, centralDirectoryOffset, signingBlock, centralDirectory, eocdBuffer);
360                 isRet = true;
361             }
362         } catch (FsVerityDigestException | InvalidKeyException | HapFormatException | MissingParamsException
363 |InvalidParamsException |ProfileException |NumberFormatException |CustomException |IOException |CodeSignException e) {
364             printErrorLogWithoutStack(e);
365         } catch (SignatureException e) {
366             printErrorLog(e);
367         }
368         return doAfterSign(isRet, isPathOverlap, tmpOutput, output);
369     }
370 
371     /**
372      * append code signBlock
373      *
374      * @param signerConfig signerConfig
375      * @param tmpOutput temp output file
376      * @param suffix suffix
377      * @param centralDirectoryOffset central directory offset
378      * @param zip zip
379      * @throws FsVerityDigestException FsVerity digest on error
380      * @throws CodeSignException code sign on error
381      * @throws IOException IO error
382      * @throws HapFormatException hap format on error
383      * @throws ProfileException profile of app is invalid
384      */
appendCodeSignBlock(SignerConfig signerConfig, File tmpOutput, String suffix, long centralDirectoryOffset, Zip zip)385     private void appendCodeSignBlock(SignerConfig signerConfig, File tmpOutput, String suffix,
386         long centralDirectoryOffset, Zip zip)
387             throws FsVerityDigestException, CodeSignException, IOException, HapFormatException, ProfileException {
388         if (signParams.get(ParamConstants.PARAM_SIGN_CODE)
389             .equals(ParamConstants.SignCodeFlag.ENABLE_SIGN_CODE.getSignCodeFlag())) {
390             if (!StringUtils.containsIgnoreCase(CodeSigning.SUPPORT_FILE_FORM, suffix)) {
391                 LOGGER.info("no need to sign code for :" + suffix);
392                 return;
393             }
394             // 4 means hap format occupy 4 byte storage location,2 means optional blocks reserve 2 storage location
395             long codeSignOffset = centralDirectoryOffset + ((4 + 4 + 4) * (optionalBlocks.size() + 2) + (4 + 4 + 4));
396             // create CodeSigning Object
397             CodeSigning codeSigning = new CodeSigning(signerConfig);
398             byte[] codeSignArray = codeSigning.getCodeSignBlock(tmpOutput, codeSignOffset, suffix, profileContent, zip);
399             ByteBuffer result = ByteBuffer.allocate(codeSignArray.length + (4 + 4 + 4));
400             result.order(ByteOrder.LITTLE_ENDIAN);
401             result.putInt(HapUtils.HAP_CODE_SIGN_BLOCK_ID); // type
402             result.putInt(codeSignArray.length); // length
403             result.putInt((int) codeSignOffset); // offset
404             result.put(codeSignArray);
405             SigningBlock propertyBlock = new SigningBlock(HapUtils.HAP_PROPERTY_BLOCK_ID, result.array());
406             optionalBlocks.add(0, propertyBlock);
407         }
408     }
409 
410     /**
411      * obtain file name suffix
412      *
413      * @param output output file
414      * @return suffix
415      * @throws HapFormatException hap format error
416      */
getFileSuffix(File output)417     private String getFileSuffix(File output) throws HapFormatException {
418         String[] fileNameArray = output.getName().split("\\.");
419         if (fileNameArray.length < ParamConstants.FILE_NAME_MIN_LENGTH) {
420             throw new HapFormatException("hap format error :" + output);
421         }
422         return fileNameArray[fileNameArray.length - 1];
423     }
424 
425     /**
426      * Load certificate chain from input parameters
427      *
428      * @param options parameters used to sign hap file
429      * @return list of type x509certificate
430      * @throws MissingParamsException Exception occurs when the required parameters are not entered.
431      * @throws InvalidParamsException Exception occurs when the required parameters are invalid.
432      * @throws ProfileException Exception occurs when profile is invalid.
433      */
getX509Certificates(Options options)434     private List<X509Certificate> getX509Certificates(Options options) throws MissingParamsException,
435             InvalidParamsException, ProfileException {
436         List<X509Certificate> publicCerts;
437         // 1. check the parameters
438         checkParams(options);
439         // 2. get x509 verify certificate
440         publicCerts = getPublicCerts();
441         // 3. load optionalBlocks
442         loadOptionalBlocks();
443         if ("elf".equals(options.getString(ParamConstants.PARAM_IN_FORM))
444                 && StringUtils.isEmpty(options.getString(ParamConstants.PARAM_BASIC_PROFILE))) {
445             return publicCerts;
446         }
447         checkProfileValid(publicCerts);
448         return publicCerts;
449     }
450 
outputSignedFile(RandomAccessFile outputHap, long centralDirectoryOffset, byte[] signingBlock, ZipDataInput centralDirectory, ByteBuffer eocdBuffer)451     private void outputSignedFile(RandomAccessFile outputHap, long centralDirectoryOffset,
452         byte[] signingBlock, ZipDataInput centralDirectory, ByteBuffer eocdBuffer) throws IOException {
453         ZipDataOutput outputHapOut = new RandomAccessFileZipDataOutput(outputHap, centralDirectoryOffset);
454         outputHapOut.write(signingBlock, 0, signingBlock.length);
455         centralDirectory.copyTo(0, centralDirectory.size(), outputHapOut);
456         outputHapOut.write(eocdBuffer);
457     }
458 
doAfterSign(boolean isSuccess, boolean isPathOverlap, File tmpOutput, File output)459     private boolean doAfterSign(boolean isSuccess, boolean isPathOverlap, File tmpOutput, File output) {
460         boolean isRet = isSuccess;
461         if (isRet && isPathOverlap) {
462             try {
463                 Files.move(tmpOutput.toPath(), output.toPath(), StandardCopyOption.REPLACE_EXISTING);
464             } catch (IOException e) {
465                 printErrorLog(e);
466                 isRet = false;
467             }
468         }
469 
470         if (isRet) {
471             LOGGER.info("Sign Hap success!");
472         } else {
473             FileUtils.deleteFile(tmpOutput);
474         }
475         return isRet;
476     }
477 
printErrorLog(Exception exception)478     private void printErrorLog(Exception exception) {
479         if (exception != null) {
480             LOGGER.error("hap-sign-tool: error: {}", exception.getMessage(), exception);
481         }
482     }
483 
printErrorLogWithoutStack(Exception exception)484     private void printErrorLogWithoutStack(Exception exception) {
485         if (exception != null) {
486             LOGGER.error("hap-sign-tool: error: {}", exception.getMessage());
487         }
488     }
489 
490     /**
491      * Copy file and alignment
492      *
493      * @param input file input
494      * @param tmpOutput file tmpOutput
495      * @param alignment alignment
496      * @return zip zip
497      * @throws IOException io error
498      * @throws HapFormatException hap format error
499      */
copyFileAndAlignment(File input, File tmpOutput, int alignment)500     private Zip copyFileAndAlignment(File input, File tmpOutput, int alignment)
501             throws IOException, HapFormatException {
502         Zip zip = new Zip(input);
503         zip.alignment(alignment);
504         zip.removeSignBlock();
505         long start = System.currentTimeMillis();
506         zip.toFile(tmpOutput.getCanonicalPath());
507         long end = System.currentTimeMillis();
508         LOGGER.debug("zip to file use {} ms", end - start);
509         return zip;
510     }
511 
512     /**
513      * check signature algorithm
514      *
515      * @throws InvalidParamsException Exception occurs when the inputted sign algorithm is invalid.
516      */
checkSignatureAlg()517     private void checkSignatureAlg() throws InvalidParamsException {
518         String signAlg = signParams.get(ParamConstants.PARAM_BASIC_SIGANTURE_ALG).trim();
519         for (String validAlg : VALID_SIGN_ALG_NAME) {
520             if (validAlg.equalsIgnoreCase(signAlg)) {
521                 return;
522             }
523         }
524         LOGGER.error("Unsupported signature algorithm :" + signAlg);
525         throw new InvalidParamsException("Invalid parameter: Sign Alg");
526     }
527 
528     /**
529      * check alignment
530      */
checkSignAlignment()531     protected void checkSignAlignment() {
532         if (!signParams.containsKey(ParamConstants.PARAM_BASIC_ALIGNMENT)) {
533             signParams.put(ParamConstants.PARAM_BASIC_ALIGNMENT, ParamConstants.ALIGNMENT);
534         }
535     }
536 
537     /**
538      * Get CN value of developer certificate from profile.
539      *
540      * @param buildInfoObject json obect of buildInfo in profile.
541      * @return Object of development-certificate.
542      */
getDevelopmentCertificate(JsonObject buildInfoObject)543     private X509Certificate getDevelopmentCertificate(JsonObject buildInfoObject) {
544         final String developmentCertElememt = "development-certificate";
545         String developmentCertificate = buildInfoObject.get(developmentCertElememt).getAsString();
546         return DigestUtils.decodeBase64ToX509Certifate(developmentCertificate);
547     }
548 
549     /**
550      * Get CN value of release certificate from profile.
551      *
552      * @param buildInfoObject json obect of buildInfo in profile.
553      * @return Object of distribution-certificate.
554      */
getReleaseCertificate(JsonObject buildInfoObject)555     private X509Certificate getReleaseCertificate(JsonObject buildInfoObject) {
556         final String distributeCertElememt = "distribution-certificate";
557         String distributeCertificate = buildInfoObject.get(distributeCertElememt).getAsString();
558         return DigestUtils.decodeBase64ToX509Certifate(distributeCertificate);
559     }
560 
getCertificateCN(X509Certificate cert)561     private String getCertificateCN(X509Certificate cert) {
562         if (cert == null) {
563             return "";
564         }
565         String valueOfDN = cert.getSubjectDN().toString();
566         valueOfDN = valueOfDN.replace("\"", "");
567         String[] arrayDN = valueOfDN.split(",");
568         for (String element : arrayDN) {
569             if (element.trim().startsWith("CN=")) {
570                 String[] tempArray = element.split("=");
571                 if (tempArray.length == 2) {
572                     return tempArray[1];
573                 }
574                 return "";
575             }
576         }
577         return "";
578     }
579 
findProfileFromOptionalBlocks()580     private byte[] findProfileFromOptionalBlocks() {
581         byte[] profile = new byte[0];
582         for (SigningBlock optionalBlock : optionalBlocks) {
583             if (optionalBlock.getType() == HapUtils.HAP_PROFILE_BLOCK_ID) {
584                 profile = optionalBlock.getValue();
585             }
586         }
587         return profile;
588     }
589 
590     /**
591      * Check profile is valid. A valid profile must include type and
592      * certificate which has a non-empty value of DN.
593      *
594      * @param inputCerts certificates inputted by user.
595      * @throws ProfileException Exception occurs when profile is invalid.
596      */
checkProfileValid(List<X509Certificate> inputCerts)597     private void checkProfileValid(List<X509Certificate> inputCerts) throws ProfileException {
598         try {
599             byte[] profile = findProfileFromOptionalBlocks();
600             boolean isProfileWithoutSign = ParamConstants.ProfileSignFlag.DISABLE_SIGN_CODE.getSignFlag().equals(
601                     signParams.get(ParamConstants.PARAM_BASIC_PROFILE_SIGNED));
602             if (!isProfileWithoutSign) {
603                 CMSSignedData cmsSignedData = new CMSSignedData(profile);
604                 boolean isVerify = VerifyUtils.verifyCmsSignedData(cmsSignedData);
605                 if (!isVerify) {
606                     throw new ProfileException("Verify profile pkcs7 failed! Profile is invalid.");
607                 }
608                 Object contentObj = cmsSignedData.getSignedContent().getContent();
609                 if (!(contentObj instanceof byte[])) {
610                     throw new ProfileException("Check profile failed, signed profile content is not byte array!");
611                 }
612                 profileContent = new String((byte[]) contentObj, StandardCharsets.UTF_8);
613             } else {
614                 profileContent = new String(profile, StandardCharsets.UTF_8);
615             }
616             JsonElement parser = JsonParser.parseString(profileContent);
617             JsonObject profileJson = parser.getAsJsonObject();
618             checkProfileInfo(profileJson, inputCerts);
619         } catch (CMSException e) {
620             throw new ProfileException("Verify profile pkcs7 failed! Profile is invalid.", e);
621         } catch (JsonParseException e) {
622             throw new ProfileException("Invalid parameter: profile content is not a JSON.", e);
623         }
624     }
625 
checkProfileInfo(JsonObject profileJson, List<X509Certificate> inputCerts)626     private void checkProfileInfo(JsonObject profileJson, List<X509Certificate> inputCerts) throws ProfileException {
627         String profileTypeKey = "type";
628         String profileType = profileJson.get(profileTypeKey).getAsString();
629         if (profileType == null || profileType.length() == 0) {
630             throw new ProfileException("Get profile type error!");
631         }
632         String buildInfoMember = "bundle-info";
633         JsonObject buildInfoObject = profileJson.getAsJsonObject(buildInfoMember);
634         X509Certificate certInProfile;
635         if (profileType.equalsIgnoreCase("release")) {
636             certInProfile = getReleaseCertificate(buildInfoObject);
637         } else if (profileType.equalsIgnoreCase("debug")) {
638             certInProfile = getDevelopmentCertificate(buildInfoObject);
639         } else {
640             throw new ProfileException("Unsupported profile type!");
641         }
642         if (!inputCerts.isEmpty() && !checkInputCertMatchWithProfile(inputCerts.get(0), certInProfile)) {
643             throw new ProfileException("input certificates do not match with profile!");
644         }
645         String cn = getCertificateCN(certInProfile);
646         LOGGER.info("certificate in profile: {}", cn);
647         if (cn.isEmpty()) {
648             throw new ProfileException("Common name of certificate is empty!");
649         }
650     }
651 
652     /**
653      * check whether certificate inputted by user is matched with the certificate in profile.
654      *
655      * @param inputCert certificates inputted by user.
656      * @param certInProfile the certificate in profile.
657      * @return true, if it is match.
658      */
checkInputCertMatchWithProfile(X509Certificate inputCert, X509Certificate certInProfile)659     protected boolean checkInputCertMatchWithProfile(X509Certificate inputCert, X509Certificate certInProfile) {
660         return true;
661     }
662 
663     /**
664      * Check input parameters is valid. And put valid parameters into signParams.
665      *
666      * @param options parameters inputted by user.
667      * @throws MissingParamsException Exception occurs when the required parameters are not entered.
668      * @throws InvalidParamsException Exception occurs when the required parameters are invalid.
669      */
checkParams(Options options)670     public void checkParams(Options options) throws MissingParamsException, InvalidParamsException {
671         String[] paramFileds = {
672                 ParamConstants.PARAM_BASIC_ALIGNMENT,
673                 ParamConstants.PARAM_BASIC_SIGANTURE_ALG,
674                 ParamConstants.PARAM_BASIC_INPUT_FILE,
675                 ParamConstants.PARAM_BASIC_OUTPUT_FILE,
676                 ParamConstants.PARAM_BASIC_PRIVATE_KEY,
677                 ParamConstants.PARAM_BASIC_PROFILE,
678                 ParamConstants.PARAM_BASIC_PROOF,
679                 ParamConstants.PARAM_BASIC_PROPERTY,
680                 ParamConstants.PARAM_REMOTE_SERVER,
681                 ParamConstants.PARAM_BASIC_PROFILE_SIGNED,
682                 ParamConstants.PARAM_LOCAL_PUBLIC_CERT,
683                 ParamConstants.PARAM_BASIC_COMPATIBLE_VERSION,
684                 ParamConstants.PARAM_SIGN_CODE,
685                 ParamConstants.PARAM_IN_FORM
686         };
687         Set<String> paramSet = ParamProcessUtil.initParamField(paramFileds);
688 
689         for (String paramKey : options.keySet()) {
690             if (paramSet.contains(paramKey)) {
691                 signParams.put(paramKey, getParamValue(paramKey, options.getString(paramKey)));
692             }
693         }
694         if (!signParams.containsKey(ParamConstants.PARAM_BASIC_PROFILE_SIGNED)) {
695             signParams.put(ParamConstants.PARAM_BASIC_PROFILE_SIGNED, "1");
696         }
697         checkSignCode();
698         checkSignatureAlg();
699         checkSignAlignment();
700     }
701 
702     /**
703      * Check code sign, if param do not have code sign default "1".
704      *
705      * @throws InvalidParamsException invalid param
706      */
checkSignCode()707     protected void checkSignCode() throws InvalidParamsException {
708         if (!signParams.containsKey(ParamConstants.PARAM_SIGN_CODE)) {
709             signParams.put(ParamConstants.PARAM_SIGN_CODE,
710                     ParamConstants.SignCodeFlag.ENABLE_SIGN_CODE.getSignCodeFlag());
711             return;
712         }
713         String codeSign = signParams.get(ParamConstants.PARAM_SIGN_CODE);
714         if (!codeSign.equals(ParamConstants.SignCodeFlag.ENABLE_SIGN_CODE.getSignCodeFlag())
715                 && !codeSign.equals(ParamConstants.SignCodeFlag.DISABLE_SIGN_CODE.getSignCodeFlag())) {
716             throw new InvalidParamsException("Invalid parameter: " + ParamConstants.PARAM_SIGN_CODE);
717         }
718     }
719 
720     /**
721      * Check compatible version, if param do not have compatible version default 9.
722      *
723      * @throws InvalidParamsException invalid param
724      * @throws MissingParamsException missing param
725      */
checkCompatibleVersion()726     protected void checkCompatibleVersion() throws InvalidParamsException, MissingParamsException {
727         if (!signParams.containsKey(ParamConstants.PARAM_BASIC_COMPATIBLE_VERSION)) {
728             signParams.put(ParamConstants.PARAM_BASIC_COMPATIBLE_VERSION, "9");
729             return;
730         }
731         String compatibleApiVersionVal = signParams.get(ParamConstants.PARAM_BASIC_COMPATIBLE_VERSION);
732         try {
733             int compatibleApiVersion = Integer.parseInt(compatibleApiVersionVal);
734         } catch (NumberFormatException e) {
735             throw new InvalidParamsException("Invalid parameter: " + ParamConstants.PARAM_BASIC_COMPATIBLE_VERSION);
736         }
737     }
738 
739     /**
740      * Get parameters from inputted strings. This function unescape some escaped parameters and return it.
741      *
742      * @param paramName the name of parameter
743      * @param paramValue the value of parameter
744      * @return parameter value in the correct form.
745      */
getParamValue(String paramName, String paramValue)746     protected String getParamValue(String paramName, String paramValue) {
747         for (String name : PARAMETERS_NEED_ESCAPE) {
748             if (name.equals(paramName)) {
749                 return EscapeCharacter.unescape(paramValue);
750             }
751         }
752         return paramValue;
753     }
754 
getCertificateChainFromFile(String certChianFile)755     private List<X509Certificate> getCertificateChainFromFile(String certChianFile) {
756         try {
757             return CertificateUtils.getCertListFromFile(certChianFile);
758         } catch (CertificateException e) {
759             LOGGER.error("File content is not certificates! " + e.getMessage());
760         } catch (IOException e) {
761             LOGGER.error("Certificate file exception: " + e.getMessage());
762         } catch (VerifyCertificateChainException e) {
763             LOGGER.error(e.getMessage());
764         }
765         return Collections.emptyList();
766     }
767 }
768