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