• 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.entity.Options;
19 import com.ohos.hapsigntool.codesigning.exception.FsVerityDigestException;
20 import com.ohos.hapsigntool.codesigning.exception.VerifyCodeSignException;
21 import com.ohos.hapsigntool.codesigning.sign.VerifyCodeSignature;
22 import com.ohos.hapsigntool.hap.entity.ElfBlockData;
23 import com.ohos.hapsigntool.hap.entity.HwBlockHead;
24 import com.ohos.hapsigntool.hap.entity.HwSignHead;
25 import com.ohos.hapsigntool.hap.entity.SignatureBlockTypes;
26 import com.ohos.hapsigntool.hap.entity.SigningBlock;
27 import com.ohos.hapsigntool.error.ProfileException;
28 import com.ohos.hapsigntool.hap.sign.SignElf;
29 import com.ohos.hapsigntool.utils.FileUtils;
30 import com.ohos.hapsigntool.entity.ParamConstants;
31 import com.ohos.hapsigntool.utils.StringUtils;
32 
33 import org.apache.logging.log4j.LogManager;
34 import org.apache.logging.log4j.Logger;
35 import org.bouncycastle.cms.CMSException;
36 import org.bouncycastle.cms.CMSSignedData;
37 import org.bouncycastle.jce.provider.BouncyCastleProvider;
38 import org.bouncycastle.openssl.jcajce.JcaPEMWriter;
39 
40 import java.io.File;
41 import java.io.FileWriter;
42 import java.io.IOException;
43 import java.io.OutputStream;
44 import java.nio.ByteBuffer;
45 import java.nio.ByteOrder;
46 import java.nio.charset.StandardCharsets;
47 import java.nio.file.Files;
48 import java.nio.file.Paths;
49 import java.security.Security;
50 import java.security.cert.X509Certificate;
51 import java.util.HashMap;
52 import java.util.List;
53 import java.util.Map;
54 
55 /**
56  * Class of verify ELF.
57  *
58  * @since 2023/11/23
59  */
60 public class VerifyElf {
61     private static final Logger LOGGER = LogManager.getLogger(VerifyElf.class);
62 
63     static {
Security.addProvider(new BouncyCastleProvider())64         Security.addProvider(new BouncyCastleProvider());
65     }
66 
getProfileContent(byte[] profile)67     private static String getProfileContent(byte[] profile) throws ProfileException {
68         try {
69             CMSSignedData cmsSignedData = new CMSSignedData(profile);
70             if (!VerifyUtils.verifyCmsSignedData(cmsSignedData)) {
71                 throw new ProfileException("Verify profile pkcs7 failed! Profile is invalid");
72             }
73             Object contentObj = cmsSignedData.getSignedContent().getContent();
74             if (!(contentObj instanceof byte[])) {
75                 throw new ProfileException("Check profile failed, signed profile content is not byte array!");
76             }
77             return new String((byte[]) contentObj, StandardCharsets.UTF_8);
78         } catch (CMSException e) {
79             return new String(profile, StandardCharsets.UTF_8);
80         }
81     }
82 
83 
84     /**
85      * Check whether parameters are valid
86      *
87      * @param options input parameters used to verify ELF.
88      * @return true, if all parameters are valid.
89      */
checkParams(Options options)90     public boolean checkParams(Options options) {
91         if (!options.containsKey(ParamConstants.PARAM_VERIFY_CERTCHAIN_FILE)) {
92             LOGGER.error("Missing parameter: {}", ParamConstants.PARAM_VERIFY_CERTCHAIN_FILE);
93             return false;
94         }
95         if (!options.containsKey(ParamConstants.PARAM_VERIFY_PROFILE_FILE)) {
96             LOGGER.error("Missing parameter: {}", ParamConstants.PARAM_VERIFY_PROFILE_FILE);
97             return false;
98         }
99         if (!options.containsKey(ParamConstants.PARAM_VERIFY_PROOF_FILE)) {
100             LOGGER.warn("Missing parameter: {}", ParamConstants.PARAM_VERIFY_PROOF_FILE);
101         }
102         return true;
103     }
104 
105     /**
106      * verify elf file.
107      *
108      * @param options input parameters used to verify elf.
109      * @return true, if verify successfully.
110      */
verify(Options options)111     public boolean verify(Options options) {
112         VerifyResult verifyResult;
113         try {
114             if (!checkParams(options)) {
115                 LOGGER.error("Check params failed!");
116                 throw new IOException();
117             }
118             String filePath = options.getString(ParamConstants.PARAM_BASIC_INPUT_FILE);
119             if (StringUtils.isEmpty(filePath)) {
120                 LOGGER.error("Not found verify file path!");
121                 throw new IOException();
122             }
123             File signedFile = new File(filePath);
124             if (!checkSignFile(signedFile)) {
125                 LOGGER.error("Check input signature ELF false!");
126                 throw new IOException();
127             }
128             verifyResult = verifyElf(filePath);
129             if (!verifyResult.isVerified()) {
130                 LOGGER.error("verify: {}", verifyResult.getMessage());
131                 throw new IOException();
132             }
133             String outputCertPath = options.getString(ParamConstants.PARAM_VERIFY_CERTCHAIN_FILE);
134             if (verifyResult.getCertificates() != null) {
135                 writeCertificate(outputCertPath, verifyResult.getCertificates());
136             }
137         } catch (IOException e) {
138             LOGGER.error("Write certificate chain error", e);
139             return false;
140         }
141 
142         String outputProfileFile = options.getString(ParamConstants.PARAM_VERIFY_PROFILE_FILE);
143         try {
144             outputOptionalBlocks(outputProfileFile, verifyResult);
145         } catch (IOException e) {
146             LOGGER.error("Output optional blocks error", e);
147             return false;
148         }
149 
150         LOGGER.info("verify: {}", verifyResult.getMessage());
151         return true;
152     }
153 
writeCertificate(String destFile, List<X509Certificate> certificates)154     private void writeCertificate(String destFile, List<X509Certificate> certificates) throws IOException {
155         try (JcaPEMWriter writer = new JcaPEMWriter(new FileWriter(destFile))) {
156             for (final X509Certificate cert : certificates) {
157                 writer.write(cert.getSubjectDN().toString() + System.lineSeparator());
158                 writer.writeObject(cert);
159             }
160             LOGGER.info("Write certificate chain success!");
161         }
162     }
163 
outputOptionalBlocks(String outputProfileFile, VerifyResult verifyResult)164     private void outputOptionalBlocks(String outputProfileFile, VerifyResult verifyResult) throws IOException {
165         byte[] profile = verifyResult.getProfile();
166         if (profile != null) {
167             writeOptionalBytesToFile(profile, outputProfileFile);
168         }
169     }
170 
writeOptionalBytesToFile(byte[] data, String outputFile)171     private void writeOptionalBytesToFile(byte[] data, String outputFile) throws IOException {
172         if (outputFile == null || outputFile.isEmpty()) {
173             return;
174         }
175         try (OutputStream out = Files.newOutputStream(Paths.get(outputFile))) {
176             out.write(data);
177             out.flush();
178         }
179     }
180 
checkSignFile(File signedFile)181     private boolean checkSignFile(File signedFile) {
182         try {
183             FileUtils.isValidFile(signedFile);
184         } catch (IOException e) {
185             LOGGER.error("signedFile is invalid.", e);
186             return false;
187         }
188         return true;
189     }
190 
191     /**
192      * Verify elf file.
193      *
194      * @param binFile path of elf file.
195      * @return true, if verify successfully.
196      */
verifyElf(String binFile)197     public VerifyResult verifyElf(String binFile) {
198         VerifyResult result = new VerifyResult(true, VerifyResult.RET_SUCCESS, "verify signature success");
199         File bin = new File(binFile);
200         try {
201             byte[] bytes = FileUtils.readFile(bin);
202             ElfBlockData elfSignBlockData = getElfSignBlockData(bytes);
203             String profileJson;
204             byte[] profileByte;
205             Map<Character, SigningBlock> signBlock = getSignBlock(bytes, elfSignBlockData);
206             if (signBlock.containsKey(SignatureBlockTypes.PROFILE_NOSIGNED_BLOCK)) {
207                 profileByte = signBlock.get(SignatureBlockTypes.PROFILE_NOSIGNED_BLOCK).getValue();
208                 profileJson = new String(profileByte, StandardCharsets.UTF_8);
209                 result.setProfile(profileByte);
210                 LOGGER.warn("profile is not signed");
211             } else if (signBlock.containsKey(SignatureBlockTypes.PROFILE_SIGNED_BLOCK)) {
212                 // verify signed profile
213                 SigningBlock profileSign = signBlock.get(SignatureBlockTypes.PROFILE_SIGNED_BLOCK);
214                 profileByte = profileSign.getValue();
215                 profileJson = getProfileContent(profileByte);
216                 result = new HapVerify().verifyElfProfile(profileSign.getValue());
217                 result.setProfile(profileByte);
218                 LOGGER.info("verify profile success");
219             } else {
220                 LOGGER.warn("can not found profile sign block");
221                 profileJson = null;
222             }
223 
224             if (signBlock.containsKey(SignElf.CODESIGN_BLOCK_TYPE)) {
225                 // verify codesign
226                 SigningBlock codesign = signBlock.get(SignElf.CODESIGN_BLOCK_TYPE);
227                 if (!VerifyCodeSignature.verifyElf(bin, codesign.getOffset(), codesign.getLength(),
228                         "elf", profileJson)) {
229                     String errMsg = "Verify codesign error!";
230                     result = new VerifyResult(false, VerifyResult.RET_IO_ERROR, errMsg);
231                 }
232                 LOGGER.info("verify codesign success");
233             } else {
234                 LOGGER.warn("can not found code sign block");
235             }
236         } catch (IOException e) {
237             LOGGER.error("Verify file has IO error!", e);
238             result = new VerifyResult(false, VerifyResult.RET_IO_ERROR, e.getMessage());
239         } catch (FsVerityDigestException | VerifyCodeSignException e) {
240             LOGGER.error("Verify codesign error!", e);
241             result = new VerifyResult(false, VerifyResult.RET_IO_ERROR, e.getMessage());
242         } catch (CMSException | ProfileException e) {
243             LOGGER.error("Verify profile error!", e);
244             result = new VerifyResult(false, VerifyResult.RET_IO_ERROR, e.getMessage());
245         }
246         return result;
247     }
248 
getElfSignBlockData(byte[] bytes)249     private ElfBlockData getElfSignBlockData(byte[] bytes) throws IOException {
250         int offset = bytes.length - HwSignHead.SIGN_HEAD_LEN;
251         byte[] magicByte = readByteArrayOffset(bytes, offset, HwSignHead.ELF_MAGIC.length);
252         offset += HwSignHead.ELF_MAGIC.length;
253         byte[] versionByte = readByteArrayOffset(bytes, offset, HwSignHead.VERSION.length);
254         offset += HwSignHead.VERSION.length;
255         for (int i = 0; i < HwSignHead.ELF_MAGIC.length; i++) {
256             if (HwSignHead.ELF_MAGIC[i] != magicByte[i]) {
257                 throw new IOException("elf magic verify failed");
258             }
259         }
260         for (int i = 0; i < HwSignHead.VERSION.length; i++) {
261             if (HwSignHead.VERSION[i] != versionByte[i]) {
262                 throw new IOException("elf sign version verify failed");
263             }
264         }
265         int intByteLength = 4;
266         byte[] blockSizeByte = readByteArrayOffset(bytes, offset, intByteLength);
267         offset += intByteLength;
268         byte[] blockNumByte = readByteArrayOffset(bytes, offset, intByteLength);
269         ByteBuffer blockNumBf = ByteBuffer.wrap(blockNumByte).order(ByteOrder.LITTLE_ENDIAN);
270         int blockNum = blockNumBf.getInt();
271 
272         ByteBuffer blockSizeBf = ByteBuffer.wrap(blockSizeByte).order(ByteOrder.LITTLE_ENDIAN);
273         int blockSize = blockSizeBf.getInt();
274 
275         int blockStart = bytes.length - HwSignHead.SIGN_HEAD_LEN - blockSize;
276         return new ElfBlockData(blockNum, blockStart);
277     }
278 
getSignBlock(byte[] bytes, ElfBlockData elfBlockData)279     private Map<Character, SigningBlock> getSignBlock(byte[] bytes, ElfBlockData elfBlockData) throws ProfileException {
280         int offset = elfBlockData.getBlockStart();
281 
282         Map<Character, SigningBlock> blockMap = new HashMap<>();
283         for (int i = 0; i < elfBlockData.getBlockNum(); i++) {
284             byte[] blockByte = readByteArrayOffset(bytes, offset, HwBlockHead.ELF_BLOCK_LEN);
285             ByteBuffer blockBuffer = ByteBuffer.wrap(blockByte).order(ByteOrder.LITTLE_ENDIAN);
286             char type = blockBuffer.getChar();
287             char tag = blockBuffer.getChar();
288             int length = blockBuffer.getInt();
289             int blockOffset = blockBuffer.getInt();
290             byte[] value = readByteArrayOffset(bytes, elfBlockData.getBlockStart() + blockOffset, length);
291             blockMap.put(type, new SigningBlock(type, value, elfBlockData.getBlockStart() + blockOffset));
292             offset += HwBlockHead.ELF_BLOCK_LEN;
293         }
294         return blockMap;
295     }
296 
readByteArrayOffset(byte[] bytes, int offset, int length)297     private byte[] readByteArrayOffset(byte[] bytes, int offset, int length) {
298         byte[] output = new byte[length];
299         System.arraycopy(bytes, offset, output, 0, length);
300         return output;
301     }
302 }