• 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.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