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