• 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.error.ERROR;
28 import com.ohos.hapsigntool.hap.config.SignerConfig;
29 import com.ohos.hapsigntool.hap.entity.SigningBlock;
30 import com.ohos.hapsigntool.error.HapFormatException;
31 import com.ohos.hapsigntool.error.InvalidParamsException;
32 import com.ohos.hapsigntool.error.MissingParamsException;
33 import com.ohos.hapsigntool.error.ProfileException;
34 import com.ohos.hapsigntool.error.SignatureException;
35 import com.ohos.hapsigntool.error.VerifyCertificateChainException;
36 import com.ohos.hapsigntool.hap.sign.SignBin;
37 import com.ohos.hapsigntool.hap.sign.SignElf;
38 import com.ohos.hapsigntool.hap.sign.SignHap;
39 import com.ohos.hapsigntool.entity.SignatureAlgorithm;
40 import com.ohos.hapsigntool.hap.verify.VerifyUtils;
41 import com.ohos.hapsigntool.utils.CertificateUtils;
42 import com.ohos.hapsigntool.utils.DigestUtils;
43 import com.ohos.hapsigntool.utils.EscapeCharacter;
44 import com.ohos.hapsigntool.utils.FileUtils;
45 import com.ohos.hapsigntool.hap.utils.HapUtils;
46 import com.ohos.hapsigntool.entity.ParamConstants;
47 import com.ohos.hapsigntool.utils.ParamProcessUtil;
48 import com.ohos.hapsigntool.utils.StringUtils;
49 import com.ohos.hapsigntool.zip.ByteBufferZipDataInput;
50 import com.ohos.hapsigntool.zip.RandomAccessFileZipDataInput;
51 import com.ohos.hapsigntool.zip.RandomAccessFileZipDataOutput;
52 import com.ohos.hapsigntool.zip.Zip;
53 import com.ohos.hapsigntool.zip.ZipDataInput;
54 import com.ohos.hapsigntool.zip.ZipDataOutput;
55 import com.ohos.hapsigntool.zip.ZipFileInfo;
56 import com.ohos.hapsigntool.zip.ZipUtils;
57 
58 import org.apache.logging.log4j.LogManager;
59 import org.apache.logging.log4j.Logger;
60 import org.bouncycastle.asn1.x500.RDN;
61 import org.bouncycastle.asn1.x500.X500Name;
62 import org.bouncycastle.asn1.x500.style.BCStyle;
63 import org.bouncycastle.cms.CMSException;
64 import org.bouncycastle.cms.CMSSignedData;
65 import org.bouncycastle.jce.provider.BouncyCastleProvider;
66 
67 import java.io.File;
68 import java.io.IOException;
69 import java.io.RandomAccessFile;
70 import java.nio.ByteBuffer;
71 import java.nio.ByteOrder;
72 import java.nio.charset.StandardCharsets;
73 import java.nio.file.Files;
74 import java.nio.file.StandardCopyOption;
75 import java.security.InvalidKeyException;
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 Logger LOGGER = LogManager.getLogger(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("Invalid file: " + file + ", filetype: " + type);
154         }
155         try {
156             byte[] optionalBlockBytes = HapUtils.readFileToByte(file);
157             if (optionalBlockBytes == null || optionalBlockBytes.length <= 0) {
158                 LOGGER.warn("Optional block is null!");
159                 return;
160             }
161             optionalBlocks.add(new SigningBlock(type, optionalBlockBytes));
162         } catch (IOException e) {
163             LOGGER.error("read file error", e);
164             throw new InvalidParamsException("Invalid file: " + file + " is not readable. filetype: " + type);
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      * @throws InvalidKeyException on error when the key is invalid.
221      */
createSignerConfigs(List<X509Certificate> certificates, Optional<X509CRL> crl, Options options)222     public SignerConfig createSignerConfigs(List<X509Certificate> certificates, Optional<X509CRL> crl, Options options)
223             throws InvalidKeyException {
224         SignerConfig signerConfig = new SignerConfig();
225         signerConfig.fillParameters(this.signParams);
226         signerConfig.setCertificates(certificates);
227         signerConfig.setOptions(options);
228 
229         List<SignatureAlgorithm> signatureAlgorithms = new ArrayList<SignatureAlgorithm>();
230         signatureAlgorithms.add(
231                 ParamProcessUtil.getSignatureAlgorithm(this.signParams.get(ParamConstants.PARAM_BASIC_SIGANTURE_ALG)));
232         signerConfig.setSignatureAlgorithms(signatureAlgorithms);
233 
234         if (!crl.equals(Optional.empty())) {
235             signerConfig.setX509CRLs(Collections.singletonList(crl.get()));
236         }
237         return signerConfig;
238     }
239 
240     /**
241      * sign bin file
242      *
243      * @param options parameters used to sign bin file
244      * @return true, if sign successfully.
245      */
signBin(Options options)246     public boolean signBin(Options options) {
247         List<X509Certificate> publicCert = null;
248         SignerConfig signerConfig;
249         try {
250             publicCert = getX509Certificates(options);
251 
252             // Get x509 CRL
253             Optional<X509CRL> crl = getCrl();
254 
255             // Create signer configs, which contains public cert and crl info.
256             signerConfig = createSignerConfigs(publicCert, crl, options);
257         } catch (InvalidKeyException | InvalidParamsException | MissingParamsException | ProfileException e) {
258             LOGGER.error("create signer configs failed.", e);
259             printErrorLogWithoutStack(e);
260             return false;
261         }
262 
263         /* 6. make signed file into output file. */
264         if (!SignBin.sign(signerConfig, signParams)) {
265             LOGGER.error("hap-sign-tool: error: Sign bin internal failed.");
266             return false;
267         }
268         LOGGER.info("Sign success");
269         return true;
270     }
271 
272     /**
273      * sign elf file
274      *
275      * @param options parameters used to sign elf file
276      * @return true, if sign successfully.
277      */
signElf(Options options)278     public boolean signElf(Options options) {
279         List<X509Certificate> publicCert = null;
280         SignerConfig signerConfig = null;
281         try {
282             publicCert = getX509Certificates(options);
283 
284             // Get x509 CRL
285             Optional<X509CRL> crl = getCrl();
286 
287             // Create signer configs, which contains public cert and crl info.
288             signerConfig = createSignerConfigs(publicCert, crl, options);
289         } catch (InvalidKeyException | InvalidParamsException | MissingParamsException | ProfileException e) {
290             LOGGER.error("create signer configs failed.", e);
291             printErrorLogWithoutStack(e);
292             return false;
293         }
294 
295         if (ParamConstants.ProfileSignFlag.DISABLE_SIGN_CODE.getSignFlag().equals(
296                 signParams.get(ParamConstants.PARAM_BASIC_PROFILE_SIGNED))) {
297             LOGGER.error("hap-sign-tool: error: Sign elf can not use unsigned profile.");
298             return false;
299         }
300 
301         if (profileContent != null) {
302             signParams.put(ParamConstants.PARAM_PROFILE_JSON_CONTENT, profileContent);
303         }
304         /* 6. make signed file into output file. */
305         if (!SignElf.sign(signerConfig, signParams)) {
306             LOGGER.error("hap-sign-tool: error: Sign elf internal failed.");
307             return false;
308         }
309         LOGGER.info("Sign success");
310         return true;
311     }
312 
313     /**
314      * sign hap file
315      *
316      * @param options parameters used to sign hap file
317      * @return true, if sign successfully
318      */
sign(Options options)319     public boolean sign(Options options) {
320         List<X509Certificate> publicCerts = null;
321         File output = null;
322         File tmpOutput = null;
323         boolean isRet = false;
324         boolean isPathOverlap = false;
325         try {
326             publicCerts = getX509Certificates(options);
327             checkCompatibleVersion();
328             File input = new File(signParams.get(ParamConstants.PARAM_BASIC_INPUT_FILE));
329             output = new File(signParams.get(ParamConstants.PARAM_BASIC_OUTPUT_FILE));
330             String suffix = getFileSuffix(input);
331             if (input.getCanonicalPath().equals(output.getCanonicalPath())) {
332                 tmpOutput = File.createTempFile("signedHap", "." + suffix);
333                 isPathOverlap = true;
334             } else {
335                 tmpOutput = output;
336             }
337             // copy file and Alignment
338             int alignment = Integer.parseInt(signParams.get(ParamConstants.PARAM_BASIC_ALIGNMENT));
339             Zip zip = copyFileAndAlignment(input, tmpOutput, alignment);
340             // generate sign block and output signedHap
341             try (RandomAccessFile outputHap = new RandomAccessFile(tmpOutput, "rw")) {
342                 ZipDataInput outputHapIn = new RandomAccessFileZipDataInput(outputHap);
343                 ZipFileInfo zipInfo = ZipUtils.findZipInfo(outputHapIn);
344                 long centralDirectoryOffset = zipInfo.getCentralDirectoryOffset();
345                 ZipDataInput beforeCentralDir = outputHapIn.slice(0, centralDirectoryOffset);
346                 ByteBuffer centralDirBuffer =
347                         outputHapIn.createByteBuffer(centralDirectoryOffset, zipInfo.getCentralDirectorySize());
348                 ZipDataInput centralDirectory = new ByteBufferZipDataInput(centralDirBuffer);
349                 ByteBuffer eocdBuffer = zipInfo.getEocd();
350                 ZipDataInput eocd = new ByteBufferZipDataInput(eocdBuffer);
351 
352                 Optional<X509CRL> crl = getCrl();
353                 SignerConfig signerConfig = createSignerConfigs(publicCerts, crl, options);
354                 signerConfig.setCompatibleVersion(Integer.parseInt(
355                         signParams.get(ParamConstants.PARAM_BASIC_COMPATIBLE_VERSION)));
356                 ZipDataInput[] contents = {beforeCentralDir, centralDirectory, eocd};
357                 appendCodeSignBlock(signerConfig, tmpOutput, suffix, centralDirectoryOffset, zip);
358                 byte[] signingBlock = SignHap.sign(contents, signerConfig, optionalBlocks);
359                 long newCentralDirectoryOffset = centralDirectoryOffset + signingBlock.length;
360                 ZipUtils.setCentralDirectoryOffset(eocdBuffer, newCentralDirectoryOffset);
361                 LOGGER.info("Generate signing block success, begin write it to output file");
362 
363                 outputSignedFile(outputHap, centralDirectoryOffset, signingBlock, centralDirectory, eocdBuffer);
364                 isRet = true;
365             }
366         } catch (FsVerityDigestException | InvalidKeyException | HapFormatException | MissingParamsException
367 |InvalidParamsException |ProfileException |NumberFormatException |CustomException |IOException |CodeSignException e) {
368             printErrorLogWithoutStack(e);
369         } catch (SignatureException e) {
370             printErrorLog(e);
371         }
372         return doAfterSign(isRet, isPathOverlap, tmpOutput, output);
373     }
374 
375     /**
376      * append code signBlock
377      *
378      * @param signerConfig signerConfig
379      * @param tmpOutput temp output file
380      * @param suffix suffix
381      * @param centralDirectoryOffset central directory offset
382      * @param zip zip
383      * @throws FsVerityDigestException FsVerity digest on error
384      * @throws CodeSignException code sign on error
385      * @throws IOException IO error
386      * @throws HapFormatException hap format on error
387      * @throws ProfileException profile of app is invalid
388      */
appendCodeSignBlock(SignerConfig signerConfig, File tmpOutput, String suffix, long centralDirectoryOffset, Zip zip)389     private void appendCodeSignBlock(SignerConfig signerConfig, File tmpOutput, String suffix,
390         long centralDirectoryOffset, Zip zip)
391             throws FsVerityDigestException, CodeSignException, IOException, HapFormatException, ProfileException {
392         if (signParams.get(ParamConstants.PARAM_SIGN_CODE)
393             .equals(ParamConstants.SignCodeFlag.ENABLE_SIGN_CODE.getSignCodeFlag())) {
394             if (!StringUtils.containsIgnoreCase(CodeSigning.SUPPORT_FILE_FORM, suffix)) {
395                 LOGGER.info("no need to sign code for :" + suffix);
396                 return;
397             }
398             // 4 means hap format occupy 4 byte storage location,2 means optional blocks reserve 2 storage location
399             long codeSignOffset = centralDirectoryOffset + ((4 + 4 + 4) * (optionalBlocks.size() + 2) + (4 + 4 + 4));
400             // create CodeSigning Object
401             CodeSigning codeSigning = new CodeSigning(signerConfig);
402             byte[] codeSignArray = codeSigning.getCodeSignBlock(tmpOutput, codeSignOffset, suffix, profileContent, zip);
403             ByteBuffer result = ByteBuffer.allocate(codeSignArray.length + (4 + 4 + 4));
404             result.order(ByteOrder.LITTLE_ENDIAN);
405             result.putInt(HapUtils.HAP_CODE_SIGN_BLOCK_ID); // type
406             result.putInt(codeSignArray.length); // length
407             result.putInt((int) codeSignOffset); // offset
408             result.put(codeSignArray);
409             SigningBlock propertyBlock = new SigningBlock(HapUtils.HAP_PROPERTY_BLOCK_ID, result.array());
410             optionalBlocks.add(0, propertyBlock);
411         }
412     }
413 
414     /**
415      * obtain file name suffix
416      *
417      * @param output output file
418      * @return suffix
419      * @throws HapFormatException hap format error
420      */
getFileSuffix(File output)421     private String getFileSuffix(File output) throws HapFormatException {
422         String[] fileNameArray = output.getName().split("\\.");
423         if (fileNameArray.length < ParamConstants.FILE_NAME_MIN_LENGTH) {
424             throw new HapFormatException("hap format error :" + output);
425         }
426         return fileNameArray[fileNameArray.length - 1];
427     }
428 
429     /**
430      * Load certificate chain from input parameters
431      *
432      * @param options parameters used to sign hap file
433      * @return list of type x509certificate
434      * @throws MissingParamsException Exception occurs when the required parameters are not entered.
435      * @throws InvalidParamsException Exception occurs when the required parameters are invalid.
436      * @throws ProfileException Exception occurs when profile is invalid.
437      */
getX509Certificates(Options options)438     private List<X509Certificate> getX509Certificates(Options options) throws MissingParamsException,
439             InvalidParamsException, ProfileException {
440         List<X509Certificate> publicCerts;
441         // 1. check the parameters
442         checkParams(options);
443         // 2. get x509 verify certificate
444         publicCerts = getPublicCerts();
445         // 3. load optionalBlocks
446         loadOptionalBlocks();
447         if ("elf".equals(options.getString(ParamConstants.PARAM_IN_FORM))
448                 && StringUtils.isEmpty(options.getString(ParamConstants.PARAM_BASIC_PROFILE))) {
449             return publicCerts;
450         }
451         checkProfileValid(publicCerts);
452         return publicCerts;
453     }
454 
outputSignedFile(RandomAccessFile outputHap, long centralDirectoryOffset, byte[] signingBlock, ZipDataInput centralDirectory, ByteBuffer eocdBuffer)455     private void outputSignedFile(RandomAccessFile outputHap, long centralDirectoryOffset,
456         byte[] signingBlock, ZipDataInput centralDirectory, ByteBuffer eocdBuffer) throws IOException {
457         ZipDataOutput outputHapOut = new RandomAccessFileZipDataOutput(outputHap, centralDirectoryOffset);
458         outputHapOut.write(signingBlock, 0, signingBlock.length);
459         centralDirectory.copyTo(0, centralDirectory.size(), outputHapOut);
460         outputHapOut.write(eocdBuffer);
461     }
462 
doAfterSign(boolean isSuccess, boolean isPathOverlap, File tmpOutput, File output)463     private boolean doAfterSign(boolean isSuccess, boolean isPathOverlap, File tmpOutput, File output) {
464         boolean isRet = isSuccess;
465         if (isRet && isPathOverlap) {
466             try {
467                 Files.move(tmpOutput.toPath(), output.toPath(), StandardCopyOption.REPLACE_EXISTING);
468             } catch (IOException e) {
469                 printErrorLog(e);
470                 isRet = false;
471             }
472         }
473 
474         if (isRet) {
475             LOGGER.info("Sign Hap success!");
476         } else {
477             FileUtils.deleteFile(tmpOutput);
478         }
479         return isRet;
480     }
481 
printErrorLog(Exception exception)482     private void printErrorLog(Exception exception) {
483         if (exception != null) {
484             LOGGER.error("hap-sign-tool: error: {}", exception.getMessage(), exception);
485         }
486     }
487 
printErrorLogWithoutStack(Exception exception)488     private void printErrorLogWithoutStack(Exception exception) {
489         if (exception != null) {
490             LOGGER.error("hap-sign-tool: error: {}", exception.getMessage());
491         }
492     }
493 
494     /**
495      * Copy file and alignment
496      *
497      * @param input file input
498      * @param tmpOutput file tmpOutput
499      * @param alignment alignment
500      * @return zip zip
501      * @throws IOException io error
502      * @throws HapFormatException hap format error
503      */
copyFileAndAlignment(File input, File tmpOutput, int alignment)504     private Zip copyFileAndAlignment(File input, File tmpOutput, int alignment)
505             throws IOException, HapFormatException {
506         Zip zip = new Zip(input);
507         zip.alignment(alignment);
508         zip.removeSignBlock();
509         long start = System.currentTimeMillis();
510         zip.toFile(tmpOutput.getCanonicalPath());
511         long end = System.currentTimeMillis();
512         LOGGER.debug("zip to file use {} ms", end - start);
513         return zip;
514     }
515 
516     /**
517      * check signature algorithm
518      *
519      * @throws InvalidParamsException Exception occurs when the inputted sign algorithm is invalid.
520      */
checkSignatureAlg()521     private void checkSignatureAlg() throws InvalidParamsException {
522         String signAlg = signParams.get(ParamConstants.PARAM_BASIC_SIGANTURE_ALG).trim();
523         for (String validAlg : VALID_SIGN_ALG_NAME) {
524             if (validAlg.equalsIgnoreCase(signAlg)) {
525                 return;
526             }
527         }
528         LOGGER.error("Unsupported signature algorithm :" + signAlg);
529         throw new InvalidParamsException("Invalid parameter: Sign Alg");
530     }
531 
532     /**
533      * check alignment
534      */
checkSignAlignment()535     protected void checkSignAlignment() {
536         if (!signParams.containsKey(ParamConstants.PARAM_BASIC_ALIGNMENT)) {
537             signParams.put(ParamConstants.PARAM_BASIC_ALIGNMENT, ParamConstants.ALIGNMENT);
538         }
539     }
540 
541     /**
542      * Get CN value of developer certificate from profile.
543      *
544      * @param buildInfoObject json obect of buildInfo in profile.
545      * @return Object of development-certificate.
546      */
getDevelopmentCertificate(JsonObject buildInfoObject)547     private X509Certificate getDevelopmentCertificate(JsonObject buildInfoObject) {
548         final String developmentCertElememt = "development-certificate";
549         String developmentCertificate = buildInfoObject.get(developmentCertElememt).getAsString();
550         return DigestUtils.decodeBase64ToX509Certifate(developmentCertificate);
551     }
552 
553     /**
554      * Get CN value of release certificate from profile.
555      *
556      * @param buildInfoObject json obect of buildInfo in profile.
557      * @return Object of distribution-certificate.
558      */
getReleaseCertificate(JsonObject buildInfoObject)559     private X509Certificate getReleaseCertificate(JsonObject buildInfoObject) {
560         final String distributeCertElememt = "distribution-certificate";
561         String distributeCertificate = buildInfoObject.get(distributeCertElememt).getAsString();
562         return DigestUtils.decodeBase64ToX509Certifate(distributeCertificate);
563     }
564 
getCertificateCN(X509Certificate cert)565     private String getCertificateCN(X509Certificate cert) {
566         if (cert == null) {
567             return "";
568         }
569         String nameStr = cert.getSubjectX500Principal().getName();
570         X500Name name = new X500Name(nameStr);
571         RDN[] commonName = name.getRDNs(BCStyle.CN);
572         if (commonName.length <= 0) {
573             CustomException.throwException(ERROR.CERTIFICATE_ERROR, "subject without common name");
574         }
575         return commonName[0].getFirst().getValue().toString();
576     }
577 
findProfileFromOptionalBlocks()578     private byte[] findProfileFromOptionalBlocks() {
579         byte[] profile = new byte[0];
580         for (SigningBlock optionalBlock : optionalBlocks) {
581             if (optionalBlock.getType() == HapUtils.HAP_PROFILE_BLOCK_ID) {
582                 profile = optionalBlock.getValue();
583             }
584         }
585         return profile;
586     }
587 
588     /**
589      * Check profile is valid. A valid profile must include type and
590      * certificate which has a non-empty value of DN.
591      *
592      * @param inputCerts certificates inputted by user.
593      * @throws ProfileException Exception occurs when profile is invalid.
594      */
checkProfileValid(List<X509Certificate> inputCerts)595     private void checkProfileValid(List<X509Certificate> inputCerts) throws ProfileException {
596         try {
597             byte[] profile = findProfileFromOptionalBlocks();
598             boolean isProfileWithoutSign = ParamConstants.ProfileSignFlag.DISABLE_SIGN_CODE.getSignFlag().equals(
599                     signParams.get(ParamConstants.PARAM_BASIC_PROFILE_SIGNED));
600             if (!isProfileWithoutSign) {
601                 CMSSignedData cmsSignedData = new CMSSignedData(profile);
602                 boolean isVerify = VerifyUtils.verifyCmsSignedData(cmsSignedData);
603                 if (!isVerify) {
604                     throw new ProfileException("Verify profile pkcs7 failed! Profile is invalid.");
605                 }
606                 Object contentObj = cmsSignedData.getSignedContent().getContent();
607                 if (!(contentObj instanceof byte[])) {
608                     throw new ProfileException("Check profile failed, signed profile content is not byte array!");
609                 }
610                 profileContent = new String((byte[]) contentObj, StandardCharsets.UTF_8);
611             } else {
612                 profileContent = new String(profile, StandardCharsets.UTF_8);
613             }
614             JsonElement parser = JsonParser.parseString(profileContent);
615             JsonObject profileJson = parser.getAsJsonObject();
616             checkProfileInfo(profileJson, inputCerts);
617         } catch (CMSException e) {
618             throw new ProfileException("Verify profile pkcs7 failed! Profile is invalid.", e);
619         } catch (JsonParseException e) {
620             throw new ProfileException("Invalid parameter: profile content is not a JSON.", e);
621         }
622     }
623 
checkProfileInfo(JsonObject profileJson, List<X509Certificate> inputCerts)624     private void checkProfileInfo(JsonObject profileJson, List<X509Certificate> inputCerts) throws ProfileException {
625         String profileTypeKey = "type";
626         String profileType = profileJson.get(profileTypeKey).getAsString();
627         if (profileType == null || profileType.length() == 0) {
628             throw new ProfileException("Get profile type error!");
629         }
630         String buildInfoMember = "bundle-info";
631         JsonObject buildInfoObject = profileJson.getAsJsonObject(buildInfoMember);
632         X509Certificate certInProfile;
633         if (profileType.equalsIgnoreCase("release")) {
634             certInProfile = getReleaseCertificate(buildInfoObject);
635         } else if (profileType.equalsIgnoreCase("debug")) {
636             certInProfile = getDevelopmentCertificate(buildInfoObject);
637         } else {
638             throw new ProfileException("Unsupported profile type!");
639         }
640         if (!inputCerts.isEmpty() && !checkInputCertMatchWithProfile(inputCerts.get(0), certInProfile)) {
641             throw new ProfileException("input certificates do not match with profile!");
642         }
643         String cn = getCertificateCN(certInProfile);
644         LOGGER.info("certificate in profile: {}", cn);
645         if (cn.isEmpty()) {
646             throw new ProfileException("Common name of certificate is empty!");
647         }
648     }
649 
650     /**
651      * check whether certificate inputted by user is matched with the certificate in profile.
652      *
653      * @param inputCert certificates inputted by user.
654      * @param certInProfile the certificate in profile.
655      * @return true, if it is match.
656      */
checkInputCertMatchWithProfile(X509Certificate inputCert, X509Certificate certInProfile)657     protected boolean checkInputCertMatchWithProfile(X509Certificate inputCert, X509Certificate certInProfile) {
658         return true;
659     }
660 
661     /**
662      * Check input parameters is valid. And put valid parameters into signParams.
663      *
664      * @param options parameters inputted by user.
665      * @throws MissingParamsException Exception occurs when the required parameters are not entered.
666      * @throws InvalidParamsException Exception occurs when the required parameters are invalid.
667      */
checkParams(Options options)668     public void checkParams(Options options) throws MissingParamsException, InvalidParamsException {
669         String[] paramFileds = {
670                 ParamConstants.PARAM_BASIC_ALIGNMENT,
671                 ParamConstants.PARAM_BASIC_SIGANTURE_ALG,
672                 ParamConstants.PARAM_BASIC_INPUT_FILE,
673                 ParamConstants.PARAM_BASIC_OUTPUT_FILE,
674                 ParamConstants.PARAM_BASIC_PRIVATE_KEY,
675                 ParamConstants.PARAM_BASIC_PROFILE,
676                 ParamConstants.PARAM_BASIC_PROOF,
677                 ParamConstants.PARAM_BASIC_PROPERTY,
678                 ParamConstants.PARAM_REMOTE_SERVER,
679                 ParamConstants.PARAM_BASIC_PROFILE_SIGNED,
680                 ParamConstants.PARAM_LOCAL_PUBLIC_CERT,
681                 ParamConstants.PARAM_BASIC_COMPATIBLE_VERSION,
682                 ParamConstants.PARAM_SIGN_CODE,
683                 ParamConstants.PARAM_IN_FORM
684         };
685         Set<String> paramSet = ParamProcessUtil.initParamField(paramFileds);
686 
687         for (String paramKey : options.keySet()) {
688             if (paramSet.contains(paramKey)) {
689                 signParams.put(paramKey, getParamValue(paramKey, options.getString(paramKey)));
690             }
691         }
692         if (!signParams.containsKey(ParamConstants.PARAM_BASIC_PROFILE_SIGNED)) {
693             signParams.put(ParamConstants.PARAM_BASIC_PROFILE_SIGNED, "1");
694         }
695         if (StringUtils.isEmpty(signParams.get(ParamConstants.PARAM_BASIC_PROFILE_SIGNED))) {
696             signParams.put(ParamConstants.PARAM_BASIC_PROFILE_SIGNED, "1");
697         }
698         checkSignCode();
699         checkSignatureAlg();
700         checkSignAlignment();
701     }
702 
703     /**
704      * Check code sign, if param do not have code sign default "1".
705      *
706      * @throws InvalidParamsException invalid param
707      */
checkSignCode()708     protected void checkSignCode() throws InvalidParamsException {
709         if (!signParams.containsKey(ParamConstants.PARAM_SIGN_CODE)) {
710             signParams.put(ParamConstants.PARAM_SIGN_CODE,
711                     ParamConstants.SignCodeFlag.ENABLE_SIGN_CODE.getSignCodeFlag());
712             return;
713         }
714         String codeSign = signParams.get(ParamConstants.PARAM_SIGN_CODE);
715         if (!codeSign.equals(ParamConstants.SignCodeFlag.ENABLE_SIGN_CODE.getSignCodeFlag())
716                 && !codeSign.equals(ParamConstants.SignCodeFlag.DISABLE_SIGN_CODE.getSignCodeFlag())) {
717             throw new InvalidParamsException("Invalid parameter: " + ParamConstants.PARAM_SIGN_CODE);
718         }
719     }
720 
721     /**
722      * Check compatible version, if param do not have compatible version default 9.
723      *
724      * @throws InvalidParamsException invalid param
725      * @throws MissingParamsException missing param
726      */
checkCompatibleVersion()727     protected void checkCompatibleVersion() throws InvalidParamsException, MissingParamsException {
728         if (!signParams.containsKey(ParamConstants.PARAM_BASIC_COMPATIBLE_VERSION)) {
729             signParams.put(ParamConstants.PARAM_BASIC_COMPATIBLE_VERSION, "9");
730             return;
731         }
732         String compatibleApiVersionVal = signParams.get(ParamConstants.PARAM_BASIC_COMPATIBLE_VERSION);
733         try {
734             int compatibleApiVersion = Integer.parseInt(compatibleApiVersionVal);
735         } catch (NumberFormatException e) {
736             throw new InvalidParamsException("Invalid parameter: " + ParamConstants.PARAM_BASIC_COMPATIBLE_VERSION);
737         }
738     }
739 
740     /**
741      * Get parameters from inputted strings. This function unescape some escaped parameters and return it.
742      *
743      * @param paramName the name of parameter
744      * @param paramValue the value of parameter
745      * @return parameter value in the correct form.
746      */
getParamValue(String paramName, String paramValue)747     protected String getParamValue(String paramName, String paramValue) {
748         for (String name : PARAMETERS_NEED_ESCAPE) {
749             if (name.equals(paramName)) {
750                 return EscapeCharacter.unescape(paramValue);
751             }
752         }
753         return paramValue;
754     }
755 
getCertificateChainFromFile(String certChianFile)756     private List<X509Certificate> getCertificateChainFromFile(String certChianFile) {
757         try {
758             return CertificateUtils.getCertListFromFile(certChianFile);
759         } catch (CertificateException e) {
760             LOGGER.error("File content is not certificates! " + e.getMessage());
761         } catch (IOException e) {
762             LOGGER.error("Certificate file exception: " + e.getMessage());
763         } catch (VerifyCertificateChainException e) {
764             LOGGER.error(e.getMessage());
765         }
766         return Collections.emptyList();
767     }
768 }
769