• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (c) 2023-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.codesigning.sign;
17 
18 import com.ohos.hapsigntool.codesigning.datastructure.CodeSignBlock;
19 import com.ohos.hapsigntool.codesigning.datastructure.CodeSignBlockHeader;
20 import com.ohos.hapsigntool.codesigning.datastructure.ElfSignBlock;
21 import com.ohos.hapsigntool.codesigning.datastructure.Extension;
22 import com.ohos.hapsigntool.codesigning.datastructure.FsVerityInfoSegment;
23 import com.ohos.hapsigntool.codesigning.datastructure.HapInfoSegment;
24 import com.ohos.hapsigntool.codesigning.datastructure.MerkleTreeExtension;
25 import com.ohos.hapsigntool.codesigning.datastructure.NativeLibInfoSegment;
26 import com.ohos.hapsigntool.codesigning.datastructure.SegmentHeader;
27 import com.ohos.hapsigntool.codesigning.exception.FsVerityDigestException;
28 import com.ohos.hapsigntool.codesigning.exception.VerifyCodeSignException;
29 import com.ohos.hapsigntool.codesigning.fsverity.FsVerityGenerator;
30 import com.ohos.hapsigntool.codesigning.utils.CmsUtils;
31 import com.ohos.hapsigntool.codesigning.utils.HapUtils;
32 import com.ohos.hapsigntool.entity.Pair;
33 import com.ohos.hapsigntool.error.ProfileException;
34 import com.ohos.hapsigntool.utils.StringUtils;
35 
36 import org.apache.logging.log4j.LogManager;
37 import org.apache.logging.log4j.Logger;
38 import org.bouncycastle.asn1.ASN1ObjectIdentifier;
39 import org.bouncycastle.asn1.cms.Attribute;
40 import org.bouncycastle.asn1.cms.AttributeTable;
41 import org.bouncycastle.cms.CMSException;
42 import org.bouncycastle.cms.CMSSignedData;
43 import org.bouncycastle.cms.SignerInformation;
44 
45 import java.io.File;
46 import java.io.FileInputStream;
47 import java.io.IOException;
48 import java.io.InputStream;
49 import java.util.Arrays;
50 import java.util.Collection;
51 import java.util.Locale;
52 import java.util.jar.JarEntry;
53 import java.util.jar.JarFile;
54 
55 /**
56  * Verify code signature given a file with code sign block
57  *
58  * @since 2023/09/08
59  */
60 public class VerifyCodeSignature {
61     private static final Logger LOGGER = LogManager.getLogger(VerifyCodeSignature.class);
62 
checkOwnerID(byte[] signature, String profileOwnerID, String profileType)63     private static void checkOwnerID(byte[] signature, String profileOwnerID, String profileType)
64         throws CMSException, VerifyCodeSignException {
65         String ownerID = profileOwnerID;
66         // if profileType is debug, check the app-id in signature, should be null or DEBUG_LIB_ID
67         if ("debug".equals(profileType)) {
68             ownerID = "DEBUG_LIB_ID";
69         }
70 
71         CMSSignedData cmsSignedData = new CMSSignedData(signature);
72         Collection<SignerInformation> signers = cmsSignedData.getSignerInfos().getSigners();
73         Collection<String> results = null;
74         for (SignerInformation signer : signers) {
75             AttributeTable attrTable = signer.getSignedAttributes();
76             Attribute attr = attrTable.get(new ASN1ObjectIdentifier(BcSignedDataGenerator.SIGNER_OID));
77             // if app-id is null, if profileType is debug, it's ok.
78             if (attr == null) {
79                 if ("debug".equals(profileType)) {
80                     continue;
81                 }
82                 if (ownerID == null) {
83                     continue;
84                 } else {
85                     throw new VerifyCodeSignException("app-identifier is not in the signature");
86                 }
87             }
88             if (ownerID == null) {
89                 throw new VerifyCodeSignException("app-identifier in profile is null, but is not null in signature");
90             }
91             // if app-id in signature exists, it should be equal to the app-id in profile.
92             String resultOwnerID = attr.getAttrValues().getObjectAt(0).toString();
93             if (!ownerID.equals(resultOwnerID)) {
94                 throw new VerifyCodeSignException("app-identifier in signature is invalid");
95             }
96         }
97     }
98 
99     /**
100      * Verify a signed elf's signature
101      *
102      * @param file       signed elf file
103      * @param offset     start position of code sign block based on the start of the elf file
104      * @param length     byte size of code sign block
105      * @param fileFormat elf
106      * @param profileContent profile json string
107      * @return true if signature verify succeed and false otherwise
108      * @throws IOException             If an input or output exception occurred
109      * @throws VerifyCodeSignException parsing result invalid
110      * @throws FsVerityDigestException if fs-verity digest generation failed
111      * @throws CMSException            if signature verify failed
112      * @throws ProfileException        if verify profile failed
113      */
verifyElf(File file, long offset, long length, String fileFormat, String profileContent)114     public static boolean verifyElf(File file, long offset, long length, String fileFormat, String profileContent)
115             throws IOException, VerifyCodeSignException, FsVerityDigestException, CMSException, ProfileException {
116         if (!CodeSigning.SUPPORT_BIN_FILE_FORM.equalsIgnoreCase(fileFormat)) {
117             LOGGER.info("Not elf file, skip code signing verify");
118             return true;
119         }
120         // 1) parse sign block to ElfCodeSignBlock object
121         ElfSignBlock elfSignBlock;
122         try (FileInputStream signedElf = new FileInputStream(file)) {
123             byte[] codeSignBlockBytes = new byte[(int) length];
124             signedElf.skip(offset);
125             signedElf.read(codeSignBlockBytes);
126             elfSignBlock = ElfSignBlock.fromByteArray(codeSignBlockBytes);
127         }
128         // 2) verify file data
129         try (FileInputStream signedElf = new FileInputStream(file)) {
130             int paddingSize = ElfSignBlock.computeMerkleTreePaddingLength(offset);
131             byte[] merkleTreeWithPadding = elfSignBlock.getMerkleTreeWithPadding();
132             byte[] merkleTree = Arrays.copyOfRange(merkleTreeWithPadding, paddingSize, merkleTreeWithPadding.length);
133             verifySingleFile(signedElf, elfSignBlock.getDataSize(), elfSignBlock.getSignature(),
134                 elfSignBlock.getTreeOffset(), merkleTree);
135         }
136         if (profileContent != null) {
137             Pair<String, String> pairResult = HapUtils.parseAppIdentifier(profileContent);
138             checkOwnerID(elfSignBlock.getSignature(), pairResult.getFirst(), pairResult.getSecond());
139         }
140         return true;
141     }
142 
143     /**
144      * Verify a signed hap's signature
145      *
146      * @param file       signed hap file
147      * @param offset     start position of code sign block based on the start of the hap file
148      * @param length     byte size of code sign block
149      * @param fileFormat hap or hsp, etc.
150      * @param profileContent profile of the hap
151      * @return true if signature verify succeed and false otherwise
152      * @throws IOException             If an input or output exception occurred
153      * @throws VerifyCodeSignException parsing result invalid
154      * @throws FsVerityDigestException if fs-verity digest generation failed
155      * @throws CMSException            if signature verify failed
156      * @throws ProfileException        profile of the hap failed
157      */
verifyHap(File file, long offset, long length, String fileFormat, String profileContent)158     public static boolean verifyHap(File file, long offset, long length, String fileFormat, String profileContent)
159             throws IOException, VerifyCodeSignException, FsVerityDigestException, CMSException, ProfileException {
160         if (!StringUtils.containsIgnoreCase(CodeSigning.SUPPORT_FILE_FORM, fileFormat)) {
161             LOGGER.info("Not hap or hsp file, skip code signing verify");
162             return true;
163         }
164         Pair<String, String> pairResult = HapUtils.parseAppIdentifier(profileContent);
165 
166         CodeSignBlock csb = generateCodeSignBlock(file, offset, length);
167         // 2) verify hap
168         try (FileInputStream hap = new FileInputStream(file)) {
169             long dataSize = csb.getHapInfoSegment().getSignInfo().getDataSize();
170             byte[] signature = csb.getHapInfoSegment().getSignInfo().getSignature();
171             Extension extension = csb.getHapInfoSegment()
172                 .getSignInfo()
173                 .getExtensionByType(MerkleTreeExtension.MERKLE_TREE_INLINED);
174             MerkleTreeExtension mte = new MerkleTreeExtension(0, 0, null);
175             if (extension instanceof MerkleTreeExtension) {
176                 mte = (MerkleTreeExtension) extension;
177             }
178             // temporary: merkle tree offset set to zero, change to merkleTreeOffset
179             verifySingleFile(hap, dataSize, signature, mte.getMerkleTreeOffset(),
180                 csb.getOneMerkleTreeByFileName(CodeSigning.HAP_SIGNATURE_ENTRY_NAME));
181             checkOwnerID(signature, pairResult.getFirst(), pairResult.getSecond());
182         }
183         // 3) verify native libs
184         try (JarFile inputJar = new JarFile(file, false)) {
185             for (int i = 0; i < csb.getSoInfoSegment().getSectionNum(); i++) {
186                 String entryName = csb.getSoInfoSegment().getFileNameList().get(i);
187                 byte[] entrySig = csb.getSoInfoSegment().getSignInfoList().get(i).getSignature();
188                 JarEntry entry = inputJar.getJarEntry(entryName);
189                 if (entry.getSize() != csb.getSoInfoSegment().getSignInfoList().get(i).getDataSize()) {
190                     throw new VerifyCodeSignException(
191                         String.format(Locale.ROOT, "Invalid dataSize of native lib %s", entryName));
192                 }
193                 InputStream entryInputStream = inputJar.getInputStream(entry);
194                 // temporary merkleTreeOffset 0
195                 verifySingleFile(entryInputStream, entry.getSize(), entrySig, 0, null);
196                 checkOwnerID(entrySig, pairResult.getFirst(), pairResult.getSecond());
197             }
198         }
199         return true;
200     }
201 
generateCodeSignBlock(File file, long offset, long length)202     private static CodeSignBlock generateCodeSignBlock(File file, long offset, long length)
203         throws IOException, VerifyCodeSignException {
204         CodeSignBlock csb = new CodeSignBlock();
205         // 1) parse sign block to CodeSignBlock object
206         try (FileInputStream signedHap = new FileInputStream(file)) {
207             int fileReadOffset = 0;
208             // 0) skip data part, but fileReadOffset remains at start(0)
209             signedHap.skip(offset);
210             // 1) parse codeSignBlockHeader
211             byte[] codeSignBlockHeaderByteArray = new byte[CodeSignBlockHeader.size()];
212             fileReadOffset += signedHap.read(codeSignBlockHeaderByteArray);
213             csb.setCodeSignBlockHeader(CodeSignBlockHeader.fromByteArray(codeSignBlockHeaderByteArray));
214             if (csb.getCodeSignBlockHeader().getBlockSize() != length) {
215                 throw new VerifyCodeSignException("Invalid code Sign block size of setCodeSignBlockHeader");
216             }
217             // 2) parse segment headers
218             for (int i = 0; i < csb.getCodeSignBlockHeader().getSegmentNum(); i++) {
219                 byte[] segmentHeaderByteArray = new byte[SegmentHeader.SEGMENT_HEADER_LENGTH];
220                 fileReadOffset += signedHap.read(segmentHeaderByteArray);
221                 csb.addToSegmentList(SegmentHeader.fromByteArray(segmentHeaderByteArray));
222             }
223             // compute merkle tree offset by alignment, based on file start
224             long computedTreeOffset = getAlignmentAddr(CodeSignBlock.PAGE_SIZE_4K, fileReadOffset + offset);
225             // skip zero padding before merkle tree, adds zero padding length to fileReadOffset
226             fileReadOffset += signedHap.skip(computedTreeOffset - offset - fileReadOffset);
227             parseMerkleTree(csb, fileReadOffset, signedHap, computedTreeOffset);
228         }
229         return csb;
230     }
231 
parseMerkleTree(CodeSignBlock csb, int readOffset, FileInputStream signedHap, long computedTreeOffset)232     private static void parseMerkleTree(CodeSignBlock csb, int readOffset, FileInputStream signedHap,
233         long computedTreeOffset) throws VerifyCodeSignException, IOException {
234         // check segment offset and segment size
235         byte[] merkleTreeBytes = new byte[0];
236         int fileReadOffset = readOffset;
237         for (SegmentHeader segmentHeader : csb.getSegmentHeaderList()) {
238             if (fileReadOffset > segmentHeader.getSegmentOffset()) {
239                 throw new VerifyCodeSignException("Invaild offset of merkle tree and segment header");
240             }
241             // get merkle tree bytes
242             if (fileReadOffset < segmentHeader.getSegmentOffset()) {
243                 merkleTreeBytes = new byte[segmentHeader.getSegmentOffset() - fileReadOffset];
244                 fileReadOffset += signedHap.read(merkleTreeBytes);
245             }
246             byte[] sh = new byte[segmentHeader.getSegmentSize()];
247             fileReadOffset += signedHap.read(sh);
248             if (segmentHeader.getType() == SegmentHeader.CSB_FSVERITY_INFO_SEG) {
249                 // 3) parse fs-verity info segment
250                 csb.setFsVerityInfoSegment(FsVerityInfoSegment.fromByteArray(sh));
251             } else if (segmentHeader.getType() == SegmentHeader.CSB_HAP_META_SEG) {
252                 // 4) parse hap info segment
253                 csb.setHapInfoSegment(HapInfoSegment.fromByteArray(sh));
254             } else if (segmentHeader.getType() == SegmentHeader.CSB_NATIVE_LIB_INFO_SEG) {
255                 // 5) parse so info segment
256                 csb.setSoInfoSegment(NativeLibInfoSegment.fromByteArray(sh));
257             }
258         }
259         if (fileReadOffset != csb.getCodeSignBlockHeader().getBlockSize()) {
260             throw new VerifyCodeSignException("Invalid blockSize of getCodeSignBlockHeader");
261         }
262         // parse merkle tree
263         Extension extension = csb.getHapInfoSegment()
264             .getSignInfo()
265             .getExtensionByType(MerkleTreeExtension.MERKLE_TREE_INLINED);
266         if (extension == null) {
267             throw new VerifyCodeSignException("Missing merkleTreeExtension in verifycation");
268         }
269         if (extension instanceof MerkleTreeExtension) {
270             MerkleTreeExtension mte = (MerkleTreeExtension) extension;
271             if (computedTreeOffset != mte.getMerkleTreeOffset()) {
272                 throw new VerifyCodeSignException("Invalid merkle tree offset");
273             }
274             if (merkleTreeBytes.length != mte.getMerkleTreeSize()) {
275                 throw new VerifyCodeSignException("Invalid merkle tree size");
276             }
277             csb.addOneMerkleTree(CodeSigning.HAP_SIGNATURE_ENTRY_NAME, merkleTreeBytes);
278         }
279     }
280 
getAlignmentAddr(long alignment, long input)281     private static long getAlignmentAddr(long alignment, long input) {
282         long residual = input % alignment;
283         if (residual == 0) {
284             return input;
285         } else {
286             return input + (alignment - residual);
287         }
288     }
289 
290     /**
291      * Verifies the signature of a given file with its signature in pkcs#7 format
292      *
293      * @param input             file being verified in InputStream representation
294      * @param length            size of signed data in the file
295      * @param signature         byte array of signature in pkcs#7 format
296      * @param merkleTreeOffset  merkle tree offset based on file start
297      * @param inMerkleTreeBytes merkle tree raw bytes
298      * @throws FsVerityDigestException if fs-verity digest generation failed
299      * @throws CMSException            if signature verify failed
300      * @throws VerifyCodeSignException parsing code sign block error
301      */
verifySingleFile(InputStream input, long length, byte[] signature, long merkleTreeOffset, byte[] inMerkleTreeBytes)302     public static void verifySingleFile(InputStream input, long length, byte[] signature, long merkleTreeOffset,
303         byte[] inMerkleTreeBytes) throws FsVerityDigestException, CMSException, VerifyCodeSignException {
304         Pair<byte[], byte[]> pairResult = generateFsVerityDigest(input, length, merkleTreeOffset);
305         byte[] generatedMerkleTreeBytes = pairResult.getSecond();
306         if (generatedMerkleTreeBytes == null) {
307             generatedMerkleTreeBytes = new byte[0];
308         }
309         // For native libs, inMerkleTreeBytes is null, skip check here
310         if ((inMerkleTreeBytes != null) && !Arrays.equals(inMerkleTreeBytes, generatedMerkleTreeBytes)) {
311             throw new VerifyCodeSignException("verify merkle tree bytes failed");
312         }
313         CmsUtils.verifySignDataWithUnsignedDataDigest(pairResult.getFirst(), signature);
314     }
315 
generateFsVerityDigest(InputStream inputStream, long size, long merkleTreeOffset)316     private static Pair<byte[], byte[]> generateFsVerityDigest(InputStream inputStream, long size,
317         long merkleTreeOffset) throws FsVerityDigestException {
318         FsVerityGenerator fsVerityGenerator = new FsVerityGenerator();
319         fsVerityGenerator.generateFsVerityDigest(inputStream, size, merkleTreeOffset);
320         return Pair.create(fsVerityGenerator.getFsVerityDigest(), fsVerityGenerator.getTreeBytes());
321     }
322 }
323