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