• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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