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