• 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.hap.sign;
17 
18 import com.ohos.hapsigntool.codesigning.exception.CodeSignException;
19 import com.ohos.hapsigntool.codesigning.exception.FsVerityDigestException;
20 import com.ohos.hapsigntool.codesigning.sign.CodeSigning;
21 import com.ohos.hapsigntool.hap.config.SignerConfig;
22 import com.ohos.hapsigntool.hap.entity.BlockHead;
23 import com.ohos.hapsigntool.hap.entity.SignHead;
24 import com.ohos.hapsigntool.hap.entity.SignBlockData;
25 import com.ohos.hapsigntool.hap.entity.SignatureBlockTags;
26 import com.ohos.hapsigntool.hap.entity.SignatureBlockTypes;
27 import com.ohos.hapsigntool.error.HapFormatException;
28 import com.ohos.hapsigntool.error.ProfileException;
29 import com.ohos.hapsigntool.utils.FileUtils;
30 import com.ohos.hapsigntool.entity.ParamConstants;
31 import com.ohos.hapsigntool.utils.LogUtils;
32 import com.ohos.hapsigntool.utils.ParamProcessUtil;
33 import com.ohos.hapsigntool.utils.StringUtils;
34 
35 import java.io.DataOutputStream;
36 import java.io.File;
37 import java.io.FileInputStream;
38 import java.io.FileOutputStream;
39 import java.io.IOException;
40 import java.util.ArrayList;
41 import java.util.Date;
42 import java.util.List;
43 import java.util.Map;
44 
45 /**
46  * elf file Signature signer.
47  *
48  * @since 2023/11/21
49  */
50 public class SignElf {
51     /**
52      * codesign sign block type
53      */
54     public static final char CODESIGN_BLOCK_TYPE = 3;
55 
56     private static final LogUtils LOGGER = new LogUtils(SignElf.class);
57 
58     private static final String CODESIGN_OFF = "0";
59 
60     private static int blockNum = 0;
61 
62     private static final int PAGE_SIZE = 4096;
63 
64     private static final int FILE_BUFFER_BLOCK = 16384;
65 
66     /**
67      * Constructor of Method
68      */
SignElf()69     private SignElf() {
70     }
71 
72     /**
73      * Sign the elf file.
74      *
75      * @param signerConfig Config of the elf file to be signed.
76      * @param signParams The input parameters of sign elf.
77      * @return true if sign successfully; false otherwise.
78      */
sign(SignerConfig signerConfig, Map<String, String> signParams)79     public static boolean sign(SignerConfig signerConfig, Map<String, String> signParams) {
80         boolean isSuccess = false;
81         /* 1. Make block head, write to output file. */
82         String inputFile = signParams.get(ParamConstants.PARAM_BASIC_INPUT_FILE);
83         String tmpFile = alignFileBy4kBytes(inputFile);
84         if (tmpFile == null) {
85             LOGGER.error("copy input File failed");
86             return isSuccess;
87         }
88         String outputFile = signParams.get(ParamConstants.PARAM_BASIC_OUTPUT_FILE);
89         String profileSigned = signParams.get(ParamConstants.PARAM_BASIC_PROFILE_SIGNED);
90         if (!writeBlockDataToFile(signerConfig, tmpFile, outputFile, profileSigned, signParams)) {
91             LOGGER.error("The block head data made failed.`");
92             ParamProcessUtil.delDir(new File(outputFile));
93             return isSuccess;
94         }
95         LOGGER.info("The block head data made success.");
96 
97         /* 2. Make sign data, and write to output file */
98         if (!writeSignHeadDataToOutputFile(tmpFile, outputFile, blockNum)) {
99             LOGGER.error("The sign head data made failed.");
100             ParamProcessUtil.delDir(new File(outputFile));
101         } else {
102             isSuccess = true;
103         }
104         return isSuccess;
105     }
106 
alignFileBy4kBytes(String inputFile)107     private static String alignFileBy4kBytes(String inputFile) {
108         String tmp = "tmpFile" + new Date().getTime();
109         File tmpFile = new File(tmp);
110         try {
111             tmpFile.createNewFile();
112         } catch (IOException e) {
113             LOGGER.error("create tmp file Failed");
114             return null;
115         }
116         try (FileOutputStream output = new FileOutputStream(tmpFile);
117              FileInputStream input = new FileInputStream(inputFile)) {
118             byte[] buffer = new byte[FILE_BUFFER_BLOCK];
119             int read;
120             while ((read = input.read(buffer)) != FileUtils.FILE_END) {
121                 output.write(buffer, 0, read);
122             }
123 
124             long addLength = PAGE_SIZE - (tmpFile.length() % PAGE_SIZE);
125             if (isLongOverflowInteger(addLength)) {
126                 LOGGER.error("File alignment error");
127                 return null;
128             }
129             byte[] bytes = new byte[(int) addLength];
130             java.util.Arrays.fill(bytes, (byte) 0);
131             FileUtils.writeByteToOutFile(bytes, tmp);
132         } catch (IOException e) {
133             LOGGER.error("copy inFile Failed");
134             return null;
135         }
136         return tmp;
137     }
138 
writeBlockDataToFile(SignerConfig signerConfig, String inputFile, String outputFile, String profileSigned, Map<String, String> signParams)139     private static boolean writeBlockDataToFile(SignerConfig signerConfig,
140         String inputFile, String outputFile, String profileSigned, Map<String, String> signParams) {
141         try {
142             String profileFile = signParams.get(ParamConstants.PARAM_BASIC_PROFILE);
143 
144             List<SignBlockData> signDataList = new ArrayList<>();
145 
146             long binFileLen = FileUtils.getFileLen(inputFile);
147             if (binFileLen == -1) {
148                 LOGGER.error("file length is invalid, elf file len: " + binFileLen);
149                 throw new IOException();
150             }
151             // 1. generate sign data
152             if (!StringUtils.isEmpty(signParams.get(ParamConstants.PARAM_BASIC_PROFILE))) {
153                 signDataList.add(generateProfileSignByte(profileFile, profileSigned));
154             }
155             blockNum = signDataList.size() + 1; // other sign block num + codesign block 1
156             SignBlockData codeSign = generateCodeSignByte(signerConfig, signParams, inputFile, blockNum, binFileLen);
157             if (codeSign != null) {
158                 signDataList.add(0, codeSign);
159             }
160             blockNum = signDataList.size();
161             // 2. use sign data generate offset and sign block head
162             generateSignBlockHead(signDataList);
163 
164             return writeSignedElf(inputFile, signDataList, outputFile);
165         } catch (IOException e) {
166             LOGGER.error("writeBlockDataToFile failed.", e);
167             return false;
168         } catch (FsVerityDigestException | CodeSignException | HapFormatException | ProfileException e) {
169             LOGGER.error("codesign failed.", e);
170             return false;
171         }
172     }
173 
writeSignedElf(String inputFile, List<SignBlockData> signBlockList, String outputFile)174     private static boolean writeSignedElf(String inputFile, List<SignBlockData> signBlockList, String outputFile) {
175         try (FileOutputStream fileOutputStream = new FileOutputStream(outputFile);
176              DataOutputStream dataOutputStream = new DataOutputStream(fileOutputStream)) {
177             // 1. write the input file to the output file.
178             if (!FileUtils.writeFileToDos(inputFile, dataOutputStream)) {
179                 LOGGER.error("Failed to write information of input file: " + inputFile
180                         + " to outputFile: " + outputFile);
181                 throw new IOException();
182             }
183 
184             // 2. write block head to the output file.
185             for (SignBlockData signBlockData : signBlockList) {
186                 if (!FileUtils.writeByteToDos(signBlockData.getBlockHead(), dataOutputStream)) {
187                     LOGGER.error("Failed to write Block Head to output file: " + outputFile);
188                     throw new IOException();
189                 }
190             }
191 
192             // 3. write block data to the output file.
193             for (SignBlockData signBlockData : signBlockList) {
194                 boolean isSuccess;
195                 if (signBlockData.isByte()) {
196                     isSuccess = FileUtils.writeByteToDos(signBlockData.getSignData(), dataOutputStream);
197                 } else {
198                     isSuccess = FileUtils.writeFileToDos(signBlockData.getSignFile(), dataOutputStream);
199                 }
200 
201                 if (!isSuccess) {
202                     LOGGER.error("Failed to write Block Data to output file: " + outputFile);
203                     throw new IOException();
204                 }
205             }
206         } catch (IOException e) {
207             LOGGER.error("writeSignedElf failed.", e);
208             return false;
209         }
210         return true;
211     }
212 
generateSignBlockHead(List<SignBlockData> signDataList)213     private static void generateSignBlockHead(List<SignBlockData> signDataList)
214             throws IOException {
215         long offset = (long) BlockHead.getElfBlockLen() * signDataList.size();
216 
217         for (int i = 0; i < signDataList.size(); i++) {
218             SignBlockData signBlockData = signDataList.get(i);
219 
220             signBlockData.setBlockHead(BlockHead.getBlockHeadLittleEndian(signBlockData.getType(),
221                     SignatureBlockTags.DEFAULT, (int) signBlockData.getLen(), (int) offset));
222             offset += signBlockData.getLen();
223             if (isLongOverflowInteger(offset)) {
224                 LOGGER.error("The sign block " + i + "offset is overflow integer, offset: " + offset);
225                 throw new IOException();
226             }
227         }
228     }
229 
generateProfileSignByte(String profileFile, String profileSigned)230     private static SignBlockData generateProfileSignByte(String profileFile, String profileSigned) throws IOException {
231         long profileDataLen = FileUtils.getFileLen(profileFile);
232 
233         if (profileDataLen == -1 || isLongOverflowShort(profileDataLen)) {
234             LOGGER.error("file length is invalid, profileDataLen: " + profileDataLen);
235             throw new IOException();
236         }
237 
238         char isSigned = SignatureBlockTypes.getProfileBlockTypes(profileSigned);
239         return new SignBlockData(profileFile, isSigned);
240     }
241 
generateCodeSignByte(SignerConfig signerConfig, Map<String, String> signParams, String inputFile, int blockNum, long binFileLen)242     private static SignBlockData generateCodeSignByte(SignerConfig signerConfig, Map<String, String> signParams,
243         String inputFile, int blockNum, long binFileLen) throws IOException,
244             FsVerityDigestException, CodeSignException, HapFormatException, ProfileException {
245         if (CODESIGN_OFF.equals(signParams.get(ParamConstants.PARAM_SIGN_CODE))) {
246             return null;
247         }
248         CodeSigning codeSigning = new CodeSigning(signerConfig);
249         long offset = binFileLen + (long) BlockHead.getElfBlockLen() * blockNum;
250         String profileContent = signParams.get(ParamConstants.PARAM_PROFILE_JSON_CONTENT);
251         byte[] codesignData = codeSigning.getElfCodeSignBlock(new File(inputFile), offset,
252                 signParams.get(ParamConstants.PARAM_IN_FORM), profileContent);
253         return new SignBlockData(codesignData, CODESIGN_BLOCK_TYPE);
254     }
255 
writeSignHeadDataToOutputFile(String inputFile, String outputFile, int blockNum)256     private static boolean writeSignHeadDataToOutputFile(String inputFile, String outputFile, int blockNum) {
257         long size = FileUtils.getFileLen(outputFile) - FileUtils.getFileLen(inputFile);
258         if (isLongOverflowInteger(size)) {
259             LOGGER.error("File size is overflow integer range.");
260             return false;
261         }
262         SignHead signHeadData = new SignHead();
263         byte[] signHeadByte = signHeadData.getSignHeadLittleEndian((int) size, blockNum);
264         if (signHeadByte == null) {
265             LOGGER.error("Failed to get sign head data.");
266             return false;
267         }
268         return FileUtils.writeByteToOutFile(signHeadByte, outputFile);
269     }
270 
isLongOverflowInteger(long num)271     private static boolean isLongOverflowInteger(long num) {
272         return (num - (num & 0xffffffffL)) != 0;
273     }
274 
isLongOverflowShort(long num)275     private static boolean isLongOverflowShort(long num) {
276         return (num - (num & 0xffffL)) != 0;
277     }
278 }
279