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.get(fileNameBuffer); 238 inFileNameList.add(new String(fileNameBuffer, StandardCharsets.UTF_8)); 239 } 240 // parse zeroPadding 241 byte[] inZeroPadding = new byte[(ALIGNMENT_FOR_SIGNINFO - fileNameListSize % ALIGNMENT_FOR_SIGNINFO) 242 % ALIGNMENT_FOR_SIGNINFO]; 243 bf.get(inZeroPadding); 244 // parse sign info list 245 List<SignInfo> inSignInfoList = new ArrayList<>(); 246 for (SignedFilePos pos : inSignedFilePosList) { 247 if (pos.getSignInfoOffset() % ALIGNMENT_FOR_SIGNINFO != 0) { 248 throw new VerifyCodeSignException("SignInfo not aligned in NativeLibInfoSegment"); 249 } 250 byte[] signInfoBuffer = new byte[pos.getSignInfoSize()]; 251 bf.get(signInfoBuffer); 252 inSignInfoList.add(SignInfo.fromByteArray(signInfoBuffer)); 253 } 254 return new Builder().setMagic(inMagic).setSegmentSize(inSegmentSize).setSectionNum(inSectionNum) 255 .setSignedFilePosList(inSignedFilePosList).setFileNameList(inFileNameList) 256 .setSignInfoList(inSignInfoList).setZeroPadding(inZeroPadding).build(); 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 "SoInfoSegment: magic[%d], length[%d], secNum[%d], signedFileEntryList[%s], fileNameList[%s], " 267 + "zeroPadding[%s], signInfoList[%s]", this.magic, this.segmentSize, this.sectionNum, 268 Arrays.toString(this.signedFilePosList.toArray()), Arrays.toString(this.fileNameList.toArray()), 269 Arrays.toString(this.zeroPadding), Arrays.toString(this.signInfoList.toArray())); 270 } 271 272 /** 273 * Builder of NativeLibInfoSegment class 274 */ 275 public static class Builder { 276 private int magic = MAGIC_NUM; 277 278 private int segmentSize; 279 280 private int sectionNum; 281 282 private List<SignedFilePos> signedFilePosList = new ArrayList<>(); 283 284 private List<String> fileNameList = new ArrayList<>(); 285 286 private List<SignInfo> signInfoList = new ArrayList<>(); 287 288 private byte[] zeroPadding = new byte[0]; 289 setMagic(int magic)290 public Builder setMagic(int magic) { 291 this.magic = magic; 292 return this; 293 } 294 setSegmentSize(int segmentSize)295 public Builder setSegmentSize(int segmentSize) { 296 this.segmentSize = segmentSize; 297 return this; 298 } 299 setSectionNum(int sectionNum)300 public Builder setSectionNum(int sectionNum) { 301 this.sectionNum = sectionNum; 302 return this; 303 } 304 setSignedFilePosList(List<SignedFilePos> signedFilePosList)305 public Builder setSignedFilePosList(List<SignedFilePos> signedFilePosList) { 306 this.signedFilePosList = signedFilePosList; 307 return this; 308 } 309 setFileNameList(List<String> fileNameList)310 public Builder setFileNameList(List<String> fileNameList) { 311 this.fileNameList = fileNameList; 312 return this; 313 } 314 setSignInfoList(List<SignInfo> signInfoList)315 public Builder setSignInfoList(List<SignInfo> signInfoList) { 316 this.signInfoList = signInfoList; 317 return this; 318 } 319 setZeroPadding(byte[] zeroPadding)320 public Builder setZeroPadding(byte[] zeroPadding) { 321 this.zeroPadding = zeroPadding; 322 return this; 323 } 324 325 /** 326 * Create a NativeLibInfoSegment object 327 * 328 * @return a NativeLibInfoSegment object 329 */ build()330 public NativeLibInfoSegment build() { 331 return new NativeLibInfoSegment(this); 332 } 333 } 334 } 335