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.exception.VerifyCodeSignException; 19 import com.ohos.hapsigntool.entity.Pair; 20 21 import java.nio.ByteBuffer; 22 import java.nio.ByteOrder; 23 import java.nio.charset.StandardCharsets; 24 import java.util.ArrayList; 25 import java.util.Arrays; 26 import java.util.List; 27 import java.util.Locale; 28 29 /** 30 * SoInfoSegment consists of a header part: 31 * <p> 32 * u32 magic: magic number 33 * <p> 34 * u32 length: byte size of SoInfoSegment 35 * <p> 36 * u32 section num: the amount of file being signed 37 * <p> 38 * Followed by an area containing the offset and size of each file being signed with its signed info: 39 * <p> 40 * u32 file name offset: position of file name based on the start of SoInfoSegment 41 * <p> 42 * u32 file name size : byte size of file name string 43 * <p> 44 * u32 sign info offset : position of signed info based on the start of SoInfoSegment 45 * <p> 46 * u32 sign size: byte size of signed info 47 * <p> 48 * Ends with the file name and signed info content: 49 * <p> 50 * file name List : file name of each signed file 51 * <p> 52 * sign info List : signed info of each file 53 * <p> 54 * 55 * @since 2023/09/08 56 */ 57 public class NativeLibInfoSegment { 58 private static final int MAGIC_LENGTH_SECNUM_BYTES = 12; 59 60 private static final int SIGNED_FILE_POS_SIZE = 16; 61 62 // lower 4 bytes of the MD5 result of string "native lib info segment" (0ED2 E720) 63 private static final int MAGIC_NUM = (0x0ED2 << 16) + 0xE720; 64 65 private static final int ALIGNMENT_FOR_SIGNINFO = 4; 66 67 private int magic; 68 69 private int segmentSize; 70 71 private int sectionNum; 72 73 private List<Pair<String, SignInfo>> soInfoList = new ArrayList<>(); 74 75 private List<SignedFilePos> signedFilePosList; 76 77 private List<String> fileNameList; 78 79 private List<SignInfo> signInfoList; 80 81 private byte[] zeroPadding; 82 83 private int fileNameListBlockSize; 84 85 private int signInfoListBlockSize; 86 87 /** 88 * Constructor for SoInfoSegment 89 * 90 * @param builder Builder 91 */ NativeLibInfoSegment(Builder builder)92 private NativeLibInfoSegment(Builder builder) { 93 this.magic = builder.magic; 94 this.segmentSize = builder.segmentSize; 95 this.sectionNum = builder.sectionNum; 96 this.signedFilePosList = builder.signedFilePosList; 97 this.fileNameList = builder.fileNameList; 98 this.signInfoList = builder.signInfoList; 99 this.zeroPadding = builder.zeroPadding; 100 } 101 102 /** 103 * set soInfoList, generate fileNameList and soInfoList 104 * 105 * @param soInfoList list of file and its signed info 106 */ setSoInfoList(List<Pair<String, SignInfo>> soInfoList)107 public void setSoInfoList(List<Pair<String, SignInfo>> soInfoList) { 108 this.soInfoList = soInfoList; 109 // Once map is set, update length, sectionNum as well 110 this.sectionNum = soInfoList.size(); 111 // generate file name list and sign info list 112 generateList(); 113 } 114 getSectionNum()115 public int getSectionNum() { 116 return sectionNum; 117 } 118 getFileNameList()119 public List<String> getFileNameList() { 120 return fileNameList; 121 } 122 getSignInfoList()123 public List<SignInfo> getSignInfoList() { 124 return signInfoList; 125 } 126 127 // generate List based on current so generateList()128 private void generateList() { 129 // empty all before generate list 130 this.fileNameList.clear(); 131 this.signInfoList.clear(); 132 this.signedFilePosList.clear(); 133 int fileNameOffset = 0; 134 int signInfoOffset = 0; 135 for (Pair<String, SignInfo> soInfo : soInfoList) { 136 String fileName = soInfo.getFirst(); 137 SignInfo signInfo = soInfo.getSecond(); 138 int fileNameSizeInBytes = fileName.getBytes(StandardCharsets.UTF_8).length; 139 int signInfoSizeInBytes = signInfo.toByteArray().length; 140 this.fileNameList.add(fileName); 141 this.signInfoList.add(signInfo); 142 this.signedFilePosList.add( 143 new SignedFilePos(fileNameOffset, fileNameSizeInBytes, signInfoOffset, signInfoSizeInBytes)); 144 // increase fileNameOffset and signInfoOffset 145 fileNameOffset += fileNameSizeInBytes; 146 signInfoOffset += signInfoSizeInBytes; 147 } 148 this.fileNameListBlockSize = fileNameOffset; 149 this.signInfoListBlockSize = signInfoOffset; 150 // alignment for signInfo 151 this.zeroPadding = new byte[(ALIGNMENT_FOR_SIGNINFO - this.fileNameListBlockSize % ALIGNMENT_FOR_SIGNINFO) 152 % ALIGNMENT_FOR_SIGNINFO]; 153 // after fileNameList and signInfoList is generated, update segment size 154 this.segmentSize = this.size(); 155 // adjust file name and sign info offset base on segment start 156 int fileNameOffsetBase = MAGIC_LENGTH_SECNUM_BYTES + signedFilePosList.size() * SIGNED_FILE_POS_SIZE; 157 int signInfoOffsetBase = fileNameOffsetBase + this.fileNameListBlockSize; 158 for (SignedFilePos pos : this.signedFilePosList) { 159 pos.increaseFileNameOffset(fileNameOffsetBase); 160 pos.increaseSignInfoOffset(signInfoOffsetBase + this.zeroPadding.length); 161 } 162 } 163 164 /** 165 * Returns byte size of SoInfoSegment 166 * 167 * @return byte size of SoInfoSegment 168 */ size()169 public int size() { 170 int blockSize = MAGIC_LENGTH_SECNUM_BYTES; 171 blockSize += signedFilePosList.size() * SIGNED_FILE_POS_SIZE; 172 blockSize += this.fileNameListBlockSize + this.zeroPadding.length + this.signInfoListBlockSize; 173 return blockSize; 174 } 175 176 /** 177 * Converts SoInfoSegment to a newly created byte array 178 * 179 * @return Byte array representation of SoInfoSegment 180 */ toByteArray()181 public byte[] toByteArray() { 182 ByteBuffer bf = ByteBuffer.allocate(this.size()).order(ByteOrder.LITTLE_ENDIAN); 183 bf.putInt(magic); 184 bf.putInt(segmentSize); 185 bf.putInt(sectionNum); 186 for (SignedFilePos offsetAndSize : this.signedFilePosList) { 187 bf.putInt(offsetAndSize.getFileNameOffset()); 188 bf.putInt(offsetAndSize.getFileNameSize()); 189 bf.putInt(offsetAndSize.getSignInfoOffset()); 190 bf.putInt(offsetAndSize.getSignInfoSize()); 191 } 192 for (String fileName : fileNameList) { 193 bf.put(fileName.getBytes(StandardCharsets.UTF_8)); 194 } 195 bf.put(this.zeroPadding); 196 for (SignInfo signInfo : signInfoList) { 197 bf.put(signInfo.toByteArray()); 198 } 199 return bf.array(); 200 } 201 202 /** 203 * Init the SoInfoSegment by a byte array 204 * 205 * @param bytes Byte array representation of a SoInfoSegment object 206 * @return a newly created NativeLibInfoSegment object 207 * @throws VerifyCodeSignException parsing result invalid 208 */ fromByteArray(byte[] bytes)209 public static NativeLibInfoSegment fromByteArray(byte[] bytes) throws VerifyCodeSignException { 210 ByteBuffer bf = ByteBuffer.allocate(bytes.length).order(ByteOrder.LITTLE_ENDIAN); 211 bf.put(bytes); 212 bf.rewind(); 213 int inMagic = bf.getInt(); 214 if (inMagic != MAGIC_NUM) { 215 throw new VerifyCodeSignException("Invalid magic number of NativeLibInfoSegment"); 216 } 217 int inSegmentSize = bf.getInt(); 218 if (inSegmentSize < 0) { 219 throw new VerifyCodeSignException("Invalid segmentSize of NativeLibInfoSegment"); 220 } 221 int inSectionNum = bf.getInt(); 222 if (inSectionNum < 0) { 223 throw new VerifyCodeSignException("Invalid sectionNum of NativeLibInfoSegment"); 224 } 225 List<SignedFilePos> inSignedFilePosList = new ArrayList<>(); 226 for (int i = 0; i < inSectionNum; i++) { 227 byte[] entry = new byte[SIGNED_FILE_POS_SIZE]; 228 bf.get(entry); 229 inSignedFilePosList.add(SignedFilePos.fromByteArray(entry)); 230 } 231 // parse file name list 232 List<String> inFileNameList = new ArrayList<>(); 233 int fileNameListSize = 0; 234 for (SignedFilePos pos : inSignedFilePosList) { 235 byte[] fileNameBuffer = new byte[pos.getFileNameSize()]; 236 fileNameListSize += pos.getFileNameSize(); 237 bf.position(pos.getFileNameOffset()); 238 bf.get(fileNameBuffer); 239 inFileNameList.add(new String(fileNameBuffer, StandardCharsets.UTF_8)); 240 } 241 // parse zeroPadding 242 byte[] inZeroPadding = new byte[(ALIGNMENT_FOR_SIGNINFO - fileNameListSize % ALIGNMENT_FOR_SIGNINFO) 243 % ALIGNMENT_FOR_SIGNINFO]; 244 bf.get(inZeroPadding); 245 // parse sign info list 246 List<SignInfo> inSignInfoList = new ArrayList<>(); 247 for (SignedFilePos pos : inSignedFilePosList) { 248 if (pos.getSignInfoOffset() % ALIGNMENT_FOR_SIGNINFO != 0) { 249 throw new VerifyCodeSignException("SignInfo not aligned in NativeLibInfoSegment"); 250 } 251 byte[] signInfoBuffer = new byte[pos.getSignInfoSize()]; 252 bf.position(pos.getSignInfoOffset()); 253 bf.get(signInfoBuffer); 254 inSignInfoList.add(SignInfo.fromByteArray(signInfoBuffer)); 255 } 256 return new Builder().setMagic(inMagic).setSegmentSize(inSegmentSize).setSectionNum(inSectionNum) 257 .setSignedFilePosList(inSignedFilePosList).setFileNameList(inFileNameList) 258 .setSignInfoList(inSignInfoList).setZeroPadding(inZeroPadding).build(); 259 } 260 261 /** 262 * Return a string representation of the object 263 * 264 * @return string representation of the object 265 */ toString()266 public String toString() { 267 return String.format(Locale.ROOT, 268 "SoInfoSegment: magic[%d], length[%d], secNum[%d], signedFileEntryList[%s], fileNameList[%s], " 269 + "zeroPadding[%s], signInfoList[%s]", this.magic, this.segmentSize, this.sectionNum, 270 Arrays.toString(this.signedFilePosList.toArray()), Arrays.toString(this.fileNameList.toArray()), 271 Arrays.toString(this.zeroPadding), Arrays.toString(this.signInfoList.toArray())); 272 } 273 274 /** 275 * Builder of NativeLibInfoSegment class 276 */ 277 public static class Builder { 278 private int magic = MAGIC_NUM; 279 280 private int segmentSize; 281 282 private int sectionNum; 283 284 private List<SignedFilePos> signedFilePosList = new ArrayList<>(); 285 286 private List<String> fileNameList = new ArrayList<>(); 287 288 private List<SignInfo> signInfoList = new ArrayList<>(); 289 290 private byte[] zeroPadding = new byte[0]; 291 setMagic(int magic)292 public Builder setMagic(int magic) { 293 this.magic = magic; 294 return this; 295 } 296 setSegmentSize(int segmentSize)297 public Builder setSegmentSize(int segmentSize) { 298 this.segmentSize = segmentSize; 299 return this; 300 } 301 setSectionNum(int sectionNum)302 public Builder setSectionNum(int sectionNum) { 303 this.sectionNum = sectionNum; 304 return this; 305 } 306 setSignedFilePosList(List<SignedFilePos> signedFilePosList)307 public Builder setSignedFilePosList(List<SignedFilePos> signedFilePosList) { 308 this.signedFilePosList = signedFilePosList; 309 return this; 310 } 311 setFileNameList(List<String> fileNameList)312 public Builder setFileNameList(List<String> fileNameList) { 313 this.fileNameList = fileNameList; 314 return this; 315 } 316 setSignInfoList(List<SignInfo> signInfoList)317 public Builder setSignInfoList(List<SignInfo> signInfoList) { 318 this.signInfoList = signInfoList; 319 return this; 320 } 321 setZeroPadding(byte[] zeroPadding)322 public Builder setZeroPadding(byte[] zeroPadding) { 323 this.zeroPadding = zeroPadding; 324 return this; 325 } 326 327 /** 328 * Create a NativeLibInfoSegment object 329 * 330 * @return a NativeLibInfoSegment object 331 */ build()332 public NativeLibInfoSegment build() { 333 return new NativeLibInfoSegment(this); 334 } 335 } 336 } 337