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 }