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