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.verify; 17 18 import com.ohos.hapsigntool.hap.entity.SigningBlock; 19 import com.ohos.hapsigntool.entity.ContentDigestAlgorithm; 20 import com.ohos.hapsigntool.entity.SignatureAlgorithm; 21 import com.ohos.hapsigntool.utils.DigestUtils; 22 import com.ohos.hapsigntool.hap.utils.HapUtils; 23 import com.ohos.hapsigntool.zip.ZipDataInput; 24 25 import org.apache.logging.log4j.LogManager; 26 import org.apache.logging.log4j.Logger; 27 import org.bouncycastle.cert.X509CRLHolder; 28 import org.bouncycastle.cert.X509CertificateHolder; 29 import org.bouncycastle.cert.jcajce.JcaX509CRLConverter; 30 import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter; 31 import org.bouncycastle.cms.CMSException; 32 import org.bouncycastle.cms.CMSSignedData; 33 import org.bouncycastle.cms.SignerInformation; 34 import org.bouncycastle.cms.SignerInformationStore; 35 import org.bouncycastle.util.Store; 36 37 import java.io.IOException; 38 import java.nio.ByteBuffer; 39 import java.nio.ByteOrder; 40 import java.security.DigestException; 41 import java.security.InvalidKeyException; 42 import java.security.NoSuchAlgorithmException; 43 import java.security.NoSuchProviderException; 44 import java.security.PublicKey; 45 import java.security.SignatureException; 46 import java.security.cert.CRLException; 47 import java.security.cert.CertificateEncodingException; 48 import java.security.cert.CertificateException; 49 import java.security.cert.X509CRL; 50 import java.security.cert.X509CRLEntry; 51 import java.security.cert.X509Certificate; 52 import java.security.interfaces.DSAKey; 53 import java.security.interfaces.DSAParams; 54 import java.security.interfaces.ECKey; 55 import java.security.interfaces.RSAKey; 56 import java.text.DateFormat; 57 import java.text.SimpleDateFormat; 58 import java.util.ArrayList; 59 import java.util.Arrays; 60 import java.util.Collection; 61 import java.util.Collections; 62 import java.util.Date; 63 import java.util.HashMap; 64 import java.util.Iterator; 65 import java.util.List; 66 import java.util.Map; 67 import java.util.Map.Entry; 68 import java.util.Set; 69 70 /** 71 * Class used to verify hap-file with signature 72 * 73 * @since 2021/12/22 74 */ 75 public class HapVerify { 76 private static final Logger LOGGER = LogManager.getLogger(HapVerify.class); 77 78 private ZipDataInput beforeApkSigningBlock; 79 80 private ByteBuffer signatureSchemeBlock; 81 82 private ZipDataInput centralDirectoryBlock; 83 84 private ZipDataInput eocd; 85 86 private List<SigningBlock> optionalBlocks; 87 88 private Map<ContentDigestAlgorithm, byte[]> digestMap = new HashMap<ContentDigestAlgorithm, byte[]>(); 89 90 private JcaX509CertificateConverter certificateConverter = new JcaX509CertificateConverter(); 91 92 private JcaX509CRLConverter crlConverter = new JcaX509CRLConverter(); 93 94 private boolean isPrintCert; 95 96 /** 97 * Init Zip HapVerify 98 * 99 * @param beforeApkSigningBlock beforeApkSigningBlock 100 * @param signatureSchemeBlock signatureSchemeBlock 101 * @param centralDirectoryBlock centralDirectoryBlock 102 * @param eocd eocd 103 * @param optionalBlocks optionalBlocks 104 */ HapVerify( ZipDataInput beforeApkSigningBlock, ByteBuffer signatureSchemeBlock, ZipDataInput centralDirectoryBlock, ZipDataInput eocd, List<SigningBlock> optionalBlocks)105 public HapVerify( 106 ZipDataInput beforeApkSigningBlock, 107 ByteBuffer signatureSchemeBlock, 108 ZipDataInput centralDirectoryBlock, 109 ZipDataInput eocd, 110 List<SigningBlock> optionalBlocks) { 111 this.beforeApkSigningBlock = beforeApkSigningBlock; 112 this.signatureSchemeBlock = signatureSchemeBlock; 113 this.centralDirectoryBlock = centralDirectoryBlock; 114 this.eocd = eocd; 115 this.optionalBlocks = optionalBlocks; 116 } 117 118 /** 119 * init HapVerify 120 */ HapVerify()121 public HapVerify() { 122 } 123 124 /** 125 * Verify hap signature. 126 * 127 * @return verify result. 128 */ verify()129 public VerifyResult verify() { 130 return parserSigner(signatureSchemeBlock); 131 } 132 133 /** 134 * Verify elf signature. 135 * 136 * @param profile profile byte 137 * @return verify result. 138 */ verifyElfProfile(byte[] profile)139 public VerifyResult verifyElfProfile(byte[] profile) { 140 return parserSigner(ByteBuffer.wrap(profile), false); 141 } 142 setIsPrintCert(boolean isPrintCert)143 public void setIsPrintCert(boolean isPrintCert) { 144 this.isPrintCert = isPrintCert; 145 } 146 checkCRL(X509CRL crl, List<X509Certificate> certificates)147 private boolean checkCRL(X509CRL crl, List<X509Certificate> certificates) { 148 boolean isRet = false; 149 for (X509Certificate cert : certificates) { 150 if (!crl.getIssuerDN().getName().equals(cert.getIssuerDN().getName())) { 151 continue; 152 } 153 X509CRLEntry entry = crl.getRevokedCertificate(cert); 154 if (entry != null) { 155 LOGGER.info("cert(subject DN = {}) is revoked by crl (IssuerDN = {})", 156 cert.getSubjectDN().getName(), crl.getIssuerDN().getName()); 157 isRet = false; 158 break; 159 } 160 isRet = true; 161 } 162 return isRet; 163 } 164 verifyCRL(X509CRL crl, X509Certificate cert, List<X509Certificate> certificates)165 private boolean verifyCRL(X509CRL crl, X509Certificate cert, List<X509Certificate> certificates) 166 throws SignatureException { 167 try { 168 crl.verify(cert.getPublicKey()); 169 return checkCRL(crl, certificates); 170 } catch (NoSuchAlgorithmException 171 | InvalidKeyException 172 | SignatureException 173 | CRLException 174 | NoSuchProviderException e) { 175 throw new SignatureException("crl verify failed.", e); 176 } 177 } 178 verifyCRL(X509CRL crl, List<X509Certificate> certificates)179 private boolean verifyCRL(X509CRL crl, List<X509Certificate> certificates) throws SignatureException { 180 boolean isRevoked = true; 181 for (X509Certificate cert : certificates) { 182 if (!crl.getIssuerDN().getName().equals(cert.getSubjectDN().getName())) { 183 continue; 184 } 185 if (!verifyCRL(crl, cert, certificates)) { 186 isRevoked = false; 187 } 188 } 189 return isRevoked; 190 } 191 verifyCRLs(List<X509CRL> crls, List<X509Certificate> certificates)192 private void verifyCRLs(List<X509CRL> crls, List<X509Certificate> certificates) throws VerifyHapException { 193 if (crls == null) { 194 return; 195 } 196 boolean isRevoked = true; 197 try { 198 for (X509CRL crl : crls) { 199 if (!verifyCRL(crl, certificates)) { 200 isRevoked = false; 201 } 202 } 203 } catch (SignatureException e) { 204 throw new VerifyHapException("Verify CRL error!", e); 205 } 206 if (!isRevoked) { 207 throw new VerifyHapException("Certificate is revoked!"); 208 } 209 } 210 verifyCmsSignedData(byte[] signingBlock)211 private CMSSignedData verifyCmsSignedData(byte[] signingBlock) throws VerifyHapException { 212 try { 213 CMSSignedData cmsSignedData = new CMSSignedData(signingBlock); 214 boolean isVerifyResult = VerifyUtils.verifyCmsSignedData(cmsSignedData); 215 if (!isVerifyResult) { 216 throw new VerifyHapException("Verify PKCS7 cms data failed!"); 217 } 218 return cmsSignedData; 219 } catch (CMSException e) { 220 throw new VerifyHapException("Verify PKCS7 cms data error!", e); 221 } 222 } 223 parserSigner(ByteBuffer signer)224 private VerifyResult parserSigner(ByteBuffer signer) { 225 return parserSigner(signer, true); 226 } 227 parserSigner(ByteBuffer signer, boolean verifyContent)228 private VerifyResult parserSigner(ByteBuffer signer, boolean verifyContent) { 229 byte[] signingBlock = new byte[signer.remaining()]; 230 signer.get(signingBlock); 231 try { 232 CMSSignedData cmsSignedData = verifyCmsSignedData(signingBlock); 233 List<X509Certificate> certificates = getCertChain(cmsSignedData); 234 List<X509CRL> crlList = getCrlList(cmsSignedData); 235 verifyCRLs(crlList, certificates); 236 if (verifyContent) { 237 checkContentDigest(cmsSignedData); 238 } 239 List<SignerInformation> signerInfos = getSignerInformations(cmsSignedData); 240 VerifyResult result = new VerifyResult(true, VerifyResult.RET_SUCCESS, "Verify success"); 241 result.setCrls(crlList); 242 result.setCertificates(certificates); 243 result.setCertificateHolderStore(cmsSignedData.getCertificates()); 244 result.setSignerInfos(signerInfos); 245 result.setOptionalBlocks(optionalBlocks); 246 return result; 247 } catch (VerifyHapException e) { 248 LOGGER.error("Verify profile error!", e); 249 return new VerifyResult(false, VerifyResult.RET_UNKNOWN_ERROR, e.getMessage()); 250 } 251 } 252 getSignerInformations(CMSSignedData cmsSignedData)253 private List<SignerInformation> getSignerInformations(CMSSignedData cmsSignedData) throws VerifyHapException { 254 SignerInformationStore signerInfos = cmsSignedData.getSignerInfos(); 255 int size = signerInfos.size(); 256 if (size <= 0) { 257 throw new VerifyHapException("PKCS7 cms data has no signer info, size: " + size); 258 } 259 Collection<SignerInformation> signers = signerInfos.getSigners(); 260 return new ArrayList<>(signers); 261 } 262 checkContentDigest(CMSSignedData cmsSignedData)263 private void checkContentDigest(CMSSignedData cmsSignedData) throws VerifyHapException { 264 Object content = cmsSignedData.getSignedContent().getContent(); 265 byte[] contentBytes = null; 266 if (content instanceof byte[]) { 267 contentBytes = (byte[]) content; 268 } else { 269 throw new VerifyHapException("PKCS cms content is not a byte array!"); 270 } 271 try { 272 boolean isCheckResult = parserContentinfo(contentBytes); 273 if (!isCheckResult) { 274 throw new VerifyHapException("Hap content digest check failed."); 275 } 276 } catch (DigestException | SignatureException | IOException e) { 277 throw new VerifyHapException("Check Hap content digest error!", e); 278 } 279 } 280 getCertChain(CMSSignedData cmsSignedData)281 private List<X509Certificate> getCertChain(CMSSignedData cmsSignedData) throws VerifyHapException { 282 Store<X509CertificateHolder> certificates = cmsSignedData.getCertificates(); 283 try { 284 List<X509Certificate> certificateList = certStoreToCertList(certificates); 285 if (certificateList == null || certificateList.size() == 0) { 286 throw new VerifyHapException("Certificate chain is empty!"); 287 } 288 if (isPrintCert) { 289 for (int i = 0; i < certificateList.size(); i++) { 290 LOGGER.info("+++++++++++++++++++++++++++certificate #{} +++++++++++++++++++++++++++++++", i); 291 printCert(certificateList.get(i)); 292 } 293 } 294 return certificateList; 295 } catch (CertificateException e) { 296 throw new VerifyHapException("Get certificate chain error!", e); 297 } 298 } 299 getCrlList(CMSSignedData cmsSignedData)300 private List<X509CRL> getCrlList(CMSSignedData cmsSignedData) throws VerifyHapException { 301 Store<X509CRLHolder> crLs = cmsSignedData.getCRLs(); 302 if (crLs == null) { 303 return Collections.emptyList(); 304 } 305 Collection<X509CRLHolder> matches = crLs.getMatches(null); 306 if (matches == null || !matches.iterator().hasNext()) { 307 return Collections.emptyList(); 308 } 309 Iterator<X509CRLHolder> iterator = matches.iterator(); 310 List<X509CRL> crlList = new ArrayList<>(); 311 try { 312 while (iterator.hasNext()) { 313 X509CRLHolder crlHolder = iterator.next(); 314 crlList.add(crlConverter.getCRL(crlHolder)); 315 } 316 } catch (CRLException e) { 317 throw new VerifyHapException("Get CRL error!", e); 318 } 319 return crlList; 320 } 321 certStoreToCertList(Store<X509CertificateHolder> certificates)322 private List<X509Certificate> certStoreToCertList(Store<X509CertificateHolder> certificates) 323 throws CertificateException { 324 if (certificates == null) { 325 return Collections.emptyList(); 326 } 327 Collection<X509CertificateHolder> matches = certificates.getMatches(null); 328 if (matches == null || !matches.iterator().hasNext()) { 329 return Collections.emptyList(); 330 } 331 List<X509Certificate> certificateList = new ArrayList<>(); 332 Iterator<X509CertificateHolder> iterator = matches.iterator(); 333 while (iterator.hasNext()) { 334 X509CertificateHolder next = iterator.next(); 335 certificateList.add(certificateConverter.getCertificate(next)); 336 } 337 return certificateList; 338 } 339 parserContentinfo(byte[] data)340 private boolean parserContentinfo(byte[] data) 341 throws DigestException, SignatureException, IOException { 342 ByteBuffer digestDatas = ByteBuffer.wrap(data).order(ByteOrder.LITTLE_ENDIAN); 343 while (digestDatas.remaining() > 4) { 344 /** 345 * contentinfo format: 346 * int: version 347 * int: block number 348 * digest blocks: 349 * each digest block format: 350 * int: length of sizeof(digestblock) - 4 351 * int: Algorithm ID 352 * int: length of digest 353 * byte[]: digest 354 */ 355 int signBlockVersion = digestDatas.getInt(); 356 int signBlockCount = digestDatas.getInt(); 357 LOGGER.info("version is: {}, number of block is: {}", signBlockVersion, signBlockCount); 358 int digestBlockLen = digestDatas.getInt(); 359 int signatureAlgId = digestDatas.getInt(); 360 int digestDatalen = digestDatas.getInt(); 361 if (digestBlockLen != digestDatalen + 8) { 362 throw new SignatureException("digestBlockLen: " + digestBlockLen + ", digestDatalen: " + digestDatalen); 363 } 364 ByteBuffer degestBuffer = HapUtils.sliceBuffer(digestDatas, digestDatalen); 365 byte[] degisetData = new byte[degestBuffer.remaining()]; 366 degestBuffer.get(degisetData); 367 SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.findById(signatureAlgId); 368 if (signatureAlgorithm == null) { 369 throw new SignatureException("Unsupported SignatureAlgorithm ID : " + signatureAlgId); 370 } 371 digestMap.put(signatureAlgorithm.getContentDigestAlgorithm(), degisetData); 372 } 373 374 Set<ContentDigestAlgorithm> keySet = digestMap.keySet(); 375 Map<ContentDigestAlgorithm, byte[]> actualDigestMap = HapUtils.computeDigests( 376 keySet, new ZipDataInput[]{beforeApkSigningBlock, centralDirectoryBlock, eocd}, optionalBlocks); 377 boolean isResult = true; 378 for (Entry<ContentDigestAlgorithm, byte[]> entry : digestMap.entrySet()) { 379 ContentDigestAlgorithm digestAlg = entry.getKey(); 380 byte[] exceptDigest = entry.getValue(); 381 byte[] actualDigest = actualDigestMap.get(digestAlg); 382 if (!Arrays.equals(actualDigest, exceptDigest)) { 383 isResult = false; 384 LOGGER.error( 385 "degist data do not match! DigestAlgorithm: {}, actualDigest: <{}> VS exceptDigest : <{}>", 386 digestAlg.getDigestAlgorithm(), 387 HapUtils.toHex(actualDigest, ""), 388 HapUtils.toHex(exceptDigest, "")); 389 } 390 LOGGER.info("Digest verify result: {}, DigestAlgorithm: {}", isResult, digestAlg.getDigestAlgorithm()); 391 } 392 return isResult; 393 } 394 printCert(X509Certificate cert)395 private void printCert(X509Certificate cert) throws CertificateEncodingException { 396 byte[] encodedCert = cert.getEncoded(); 397 398 LOGGER.info("Subject: {}", cert.getSubjectX500Principal()); 399 LOGGER.info("Issuer: {}", cert.getIssuerX500Principal()); 400 LOGGER.info("SerialNumber: {}", cert.getSerialNumber().toString(16)); 401 LOGGER.info("Validity: {} ~ {}", formatDateTime(cert.getNotBefore()), formatDateTime(cert.getNotAfter())); 402 LOGGER.info("SHA256: {}", HapUtils.toHex(DigestUtils.sha256Digest(encodedCert), ":")); 403 LOGGER.info("Signature Algorithm: {}", cert.getSigAlgName()); 404 PublicKey publicKey = cert.getPublicKey(); 405 LOGGER.info("Key: {}, key length: {} bits", publicKey.getAlgorithm(), getKeySize(publicKey)); 406 LOGGER.info("Cert Version: V{}", cert.getVersion()); 407 } 408 getKeySize(PublicKey publicKey)409 private int getKeySize(PublicKey publicKey) { 410 int result = -1; 411 if (publicKey instanceof RSAKey) { 412 result = ((RSAKey) publicKey).getModulus().bitLength(); 413 } 414 if (publicKey instanceof ECKey) { 415 result = ((ECKey) publicKey).getParams().getOrder().bitLength(); 416 } 417 if (publicKey instanceof DSAKey) { 418 DSAParams dsaParams = ((DSAKey) publicKey).getParams(); 419 if (dsaParams != null) { 420 result = dsaParams.getP().bitLength(); 421 } 422 } 423 return result; 424 } 425 formatDateTime(Date date)426 private String formatDateTime(Date date) { 427 if (date != null) { 428 DateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); 429 return format.format(date); 430 } 431 return ""; 432 } 433 434 private static class VerifyHapException extends Exception { VerifyHapException(String message)435 VerifyHapException(String message) { 436 super(message); 437 } 438 VerifyHapException(String message, Throwable cause)439 VerifyHapException(String message, Throwable cause) { 440 super(message, cause); 441 } 442 } 443 } 444