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