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