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