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