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.datastructure; 17 18 import com.ohos.hapsigntool.codesigning.sign.CodeSigning; 19 20 import java.nio.ByteBuffer; 21 import java.nio.ByteOrder; 22 import java.util.ArrayList; 23 import java.util.Arrays; 24 import java.util.HashMap; 25 import java.util.List; 26 import java.util.Locale; 27 import java.util.Map; 28 29 /** 30 * code sign block is a chunk of bytes attached to hap package or file. 31 * It consists of two headers: 32 * 1) code sign block header 33 * 2) segment header 34 * three segments: 35 * 1) fs-verity info segment 36 * 2) hap info segment 37 * 3) so info segment 38 * one zero padding area in order to align merkle tree raw bytes 39 * 1) zero padding 40 * and one area storing merkle tree bytes: 41 * 1) merkle tree raw bytes 42 * <p> 43 * After signing a hap, call toByteArray() method to generate a block of bytes. 44 * 45 * @since 2023/09/08 46 */ 47 public class CodeSignBlock { 48 /** 49 * page size in bytes 50 */ 51 public static final long PAGE_SIZE_4K = 4096L; 52 53 /** 54 * Segment header count, including fs-verity info, hap info, so info segment 55 */ 56 public static final int SEGMENT_HEADER_COUNT = 3; 57 58 private CodeSignBlockHeader codeSignBlockHeader; 59 60 private final List<SegmentHeader> segmentHeaderList; 61 62 private FsVerityInfoSegment fsVerityInfoSegment; 63 64 private HapInfoSegment hapInfoSegment; 65 66 private NativeLibInfoSegment nativeLibInfoSegment; 67 68 private byte[] zeroPadding; 69 70 private final Map<String, byte[]> merkleTreeMap; 71 CodeSignBlock()72 public CodeSignBlock() { 73 this.codeSignBlockHeader = new CodeSignBlockHeader.Builder().build(); 74 this.segmentHeaderList = new ArrayList<>(); 75 this.fsVerityInfoSegment = new FsVerityInfoSegment(); 76 this.hapInfoSegment = new HapInfoSegment(); 77 this.nativeLibInfoSegment = new NativeLibInfoSegment.Builder().build(); 78 this.merkleTreeMap = new HashMap<>(); 79 } 80 81 /** 82 * Add one merkle tree into merkleTreeMap 83 * 84 * @param key file name 85 * @param merkleTree merkle tree raw bytes 86 */ addOneMerkleTree(String key, byte[] merkleTree)87 public void addOneMerkleTree(String key, byte[] merkleTree) { 88 if (merkleTree == null) { 89 this.merkleTreeMap.put(key, new byte[0]); 90 } else { 91 this.merkleTreeMap.put(key, merkleTree); 92 } 93 } 94 95 /** 96 * Get one merkle tree from merkleTreeMap by file name 97 * 98 * @param key file name 99 * @return merkle tree bytes 100 */ getOneMerkleTreeByFileName(String key)101 public byte[] getOneMerkleTreeByFileName(String key) { 102 return this.merkleTreeMap.get(key); 103 } 104 105 /** 106 * set code sign block flag 107 */ setCodeSignBlockFlag()108 public void setCodeSignBlockFlag() { 109 int flags = CodeSignBlockHeader.FLAG_MERKLE_TREE_INLINED; 110 if (this.nativeLibInfoSegment.getSectionNum() != 0) { 111 flags += CodeSignBlockHeader.FLAG_NATIVE_LIB_INCLUDED; 112 } 113 this.codeSignBlockHeader.setFlags(flags); 114 } 115 116 /** 117 * set segmentNum defined in code sign block header, equals length of segmentHeaderList 118 */ setSegmentNum()119 public void setSegmentNum() { 120 this.codeSignBlockHeader.setSegmentNum(segmentHeaderList.size()); 121 } 122 123 /** 124 * add one segment to segmentHeaderList 125 * 126 * @param sh segment header 127 */ addToSegmentList(SegmentHeader sh)128 public void addToSegmentList(SegmentHeader sh) { 129 this.segmentHeaderList.add(sh); 130 } 131 getSegmentHeaderList()132 public List<SegmentHeader> getSegmentHeaderList() { 133 return segmentHeaderList; 134 } 135 136 /** 137 * set segment header list 138 */ setSegmentHeaders()139 public void setSegmentHeaders() { 140 // fs-verity info segment 141 segmentHeaderList.add(new SegmentHeader(SegmentHeader.CSB_FSVERITY_INFO_SEG, this.fsVerityInfoSegment.size())); 142 // hap info segment 143 segmentHeaderList.add(new SegmentHeader(SegmentHeader.CSB_HAP_META_SEG, this.hapInfoSegment.size())); 144 // native lib info segment 145 segmentHeaderList.add( 146 new SegmentHeader(SegmentHeader.CSB_NATIVE_LIB_INFO_SEG, this.nativeLibInfoSegment.size())); 147 } 148 getCodeSignBlockHeader()149 public CodeSignBlockHeader getCodeSignBlockHeader() { 150 return codeSignBlockHeader; 151 } 152 setCodeSignBlockHeader(CodeSignBlockHeader csbHeader)153 public void setCodeSignBlockHeader(CodeSignBlockHeader csbHeader) { 154 this.codeSignBlockHeader = csbHeader; 155 } 156 setFsVerityInfoSegment(FsVerityInfoSegment fsVeritySeg)157 public void setFsVerityInfoSegment(FsVerityInfoSegment fsVeritySeg) { 158 this.fsVerityInfoSegment = fsVeritySeg; 159 } 160 getFsVerityInfoSegment()161 public FsVerityInfoSegment getFsVerityInfoSegment() { 162 return fsVerityInfoSegment; 163 } 164 getHapInfoSegment()165 public HapInfoSegment getHapInfoSegment() { 166 return hapInfoSegment; 167 } 168 setHapInfoSegment(HapInfoSegment hapSeg)169 public void setHapInfoSegment(HapInfoSegment hapSeg) { 170 this.hapInfoSegment = hapSeg; 171 } 172 getSoInfoSegment()173 public NativeLibInfoSegment getSoInfoSegment() { 174 return nativeLibInfoSegment; 175 } 176 setSoInfoSegment(NativeLibInfoSegment soSeg)177 public void setSoInfoSegment(NativeLibInfoSegment soSeg) { 178 this.nativeLibInfoSegment = soSeg; 179 } 180 181 /** 182 * Convert code sign block object to a newly created byte array 183 * 184 * @return Byte array representation of a CodeSignBlock object 185 */ toByteArray()186 public byte[] toByteArray() { 187 ByteBuffer bf = ByteBuffer.allocate(this.codeSignBlockHeader.getBlockSize()).order(ByteOrder.LITTLE_ENDIAN); 188 bf.put(this.codeSignBlockHeader.toByteArray()); 189 for (SegmentHeader sh : this.segmentHeaderList) { 190 bf.put(sh.toByteArray()); 191 } 192 bf.put(this.zeroPadding); 193 // Hap merkle tree 194 if (this.hapInfoSegment.getSignInfo().getExtensionByType(MerkleTreeExtension.MERKLE_TREE_INLINED) != null) { 195 bf.put(merkleTreeMap.get("Hap")); 196 } 197 bf.put(this.fsVerityInfoSegment.toByteArray()); 198 bf.put(this.hapInfoSegment.toByteArray()); 199 bf.put(this.nativeLibInfoSegment.toByteArray()); 200 return bf.array(); 201 } 202 203 /** 204 * SegmentOffset is the position of each segment defined in segmentHeaderList, 205 * based on the start position of code sign block 206 */ computeSegmentOffset()207 public void computeSegmentOffset() { 208 // 1) the first segment is placed after merkle tree 209 int segmentOffset = CodeSignBlockHeader.size() 210 + this.segmentHeaderList.size() * SegmentHeader.SEGMENT_HEADER_LENGTH + this.zeroPadding.length 211 + this.getOneMerkleTreeByFileName(CodeSigning.HAP_SIGNATURE_ENTRY_NAME).length; 212 for (SegmentHeader sh : segmentHeaderList) { 213 sh.setSegmentOffset(segmentOffset); 214 segmentOffset += sh.getSegmentSize(); 215 } 216 } 217 218 /** 219 * Compute the offset to store merkle tree raw bytes based on file start 220 * 221 * @param codeSignBlockOffset offset to store code sign block based on file start 222 * @return offset to store merkle tree based on the file start, it includes the codeSignBlockOffset 223 */ computeMerkleTreeOffset(long codeSignBlockOffset)224 public long computeMerkleTreeOffset(long codeSignBlockOffset) { 225 long sizeWithoutMerkleTree = CodeSignBlockHeader.size() 226 + SEGMENT_HEADER_COUNT * SegmentHeader.SEGMENT_HEADER_LENGTH; 227 // add code sign block offset while computing align position for merkle tree 228 long residual = (codeSignBlockOffset + sizeWithoutMerkleTree) % PAGE_SIZE_4K; 229 if (residual == 0) { 230 this.zeroPadding = new byte[0]; 231 } else { 232 this.zeroPadding = new byte[(int) (PAGE_SIZE_4K - residual)]; 233 } 234 return codeSignBlockOffset + sizeWithoutMerkleTree + zeroPadding.length; 235 } 236 237 /** 238 * Convert CodeSignBlock to bytes 239 * 240 * @param fsvTreeOffset merkle tree offset 241 * @return byte array representing the code sign block 242 */ generateCodeSignBlockByte(long fsvTreeOffset)243 public byte[] generateCodeSignBlockByte(long fsvTreeOffset) { 244 // 1) compute overall block size without merkle tree 245 long csbSize = CodeSignBlockHeader.size() 246 + (long) this.segmentHeaderList.size() * SegmentHeader.SEGMENT_HEADER_LENGTH + this.zeroPadding.length 247 + this.getOneMerkleTreeByFileName(CodeSigning.HAP_SIGNATURE_ENTRY_NAME).length 248 + this.fsVerityInfoSegment.size() + this.hapInfoSegment.size() + this.nativeLibInfoSegment.size(); 249 Extension ext = this.hapInfoSegment.getSignInfo().getExtensionByType(MerkleTreeExtension.MERKLE_TREE_INLINED); 250 if (ext instanceof MerkleTreeExtension) { 251 MerkleTreeExtension merkleTreeExtension = (MerkleTreeExtension) ext; 252 merkleTreeExtension.setMerkleTreeOffset(fsvTreeOffset); 253 } 254 this.codeSignBlockHeader.setBlockSize(csbSize); 255 // 2) generate byte array of complete code sign block 256 return toByteArray(); 257 } 258 259 /** 260 * Return a string representation of the object 261 * 262 * @return string representation of the object 263 */ toString()264 public String toString() { 265 return String.format(Locale.ROOT, 266 "CodeSignBlockHeader[%s], SegmentHeaders[%s], FsVeritySeg[%s], HapInfoSeg[%s], SoInfoSeg[%s]", 267 this.codeSignBlockHeader, Arrays.toString(this.segmentHeaderList.toArray()), this.fsVerityInfoSegment, 268 this.hapInfoSegment, this.nativeLibInfoSegment); 269 } 270 } 271