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.fsverity; 17 18 import com.ohos.hapsigntool.codesigning.exception.CodeSignErrMsg; 19 import com.ohos.hapsigntool.codesigning.exception.FsVerityDigestException; 20 import com.ohos.hapsigntool.codesigning.exception.VerifyCodeSignException; 21 import com.ohos.hapsigntool.codesigning.utils.NumberUtils; 22 23 import java.nio.ByteBuffer; 24 import java.nio.ByteOrder; 25 26 /** 27 * Format of FsVerity descriptor 28 * uint8 version 29 * uint8 hashAlgorithm 30 * uint8 log2BlockSize 31 * uint8 saltSize 32 * uint32 signSize 33 * le64 dataSize 34 * uint8[64] rootHash 35 * uint8[32] salt 36 * uint32 flags 37 * uint8[4] 0 38 * uint64 treeOffset 39 * uint8[127] 0 40 * uint8 csVersion 41 * 42 * @since 2023/06/05 43 */ 44 public class FsVerityDescriptor { 45 /** 46 * fs-verity version, must be 1 47 */ 48 public static final byte VERSION = 1; 49 50 /** 51 * page size in bytes 52 */ 53 public static final int PAGE_SIZE_4K = 4096; 54 55 /** 56 * Indicating merkle tree offset is set in fs-verity descriptor 57 */ 58 public static final int FLAG_STORE_MERKLE_TREE_OFFSET = 0x1; 59 60 /** 61 * Indicating fs-verity descriptor type 62 */ 63 public static final int FS_VERITY_DESCRIPTOR_TYPE = 0x1; 64 65 /** 66 * code sign version 67 */ 68 public static final byte CODE_SIGN_VERSION = 0x1; 69 70 /** 71 * code sign version 72 */ 73 public static final byte CODE_SIGN_VERSION_V2 = 0x2; 74 75 /** 76 * FsVerity descriptor size 77 */ 78 public static final int DESCRIPTOR_SIZE = 256; 79 80 /** 81 * root hash size 82 */ 83 public static final int ROOT_HASH_FILED_SIZE = 64; 84 85 /** 86 * salt size 87 */ 88 public static final int SALT_SIZE = 32; 89 90 /** 91 * reserved size 92 */ 93 public static final int RESERVED_SIZE_AFTER_TREE_OFFSET = 119; 94 95 private byte version; 96 97 private long fileSize; 98 99 private byte hashAlgorithm; 100 101 private byte log2BlockSize; 102 103 private byte saltSize; 104 105 private int signSize; 106 107 private byte[] salt; 108 109 private byte[] rawRootHash; 110 111 private int flags; 112 113 private int bitMapSize; 114 115 private long merkleTreeOffset; 116 117 private long bitMapOffset; 118 119 private byte csVersion; 120 FsVerityDescriptor(Builder builder)121 private FsVerityDescriptor(Builder builder) { 122 this.version = builder.version; 123 this.fileSize = builder.fileSize; 124 this.hashAlgorithm = builder.hashAlgorithm; 125 this.log2BlockSize = builder.log2BlockSize; 126 this.saltSize = builder.saltSize; 127 this.signSize = builder.signSize; 128 this.salt = builder.salt; 129 this.rawRootHash = builder.rawRootHash; 130 this.flags = builder.flags; 131 this.merkleTreeOffset = builder.merkleTreeOffset; 132 this.csVersion = builder.csVersion; 133 } 134 getFileSize()135 public long getFileSize() { 136 return fileSize; 137 } 138 getMerkleTreeOffset()139 public long getMerkleTreeOffset() { 140 return merkleTreeOffset; 141 } 142 getSignSize()143 public int getSignSize() { 144 return signSize; 145 } 146 147 /** 148 * Init the FsVerityDescriptor by a byte array 149 * 150 * @param bytes Byte array representation of a FsVerityDescriptor object 151 * @return a newly created FsVerityDescriptor object 152 * @throws VerifyCodeSignException parse result invalid 153 */ fromByteArray(byte[] bytes)154 public static FsVerityDescriptor fromByteArray(byte[] bytes) throws VerifyCodeSignException { 155 ByteBuffer bf = ByteBuffer.allocate(bytes.length).order(ByteOrder.LITTLE_ENDIAN); 156 bf.put(bytes); 157 // after put, rewind is mandatory before get 158 bf.rewind(); 159 FsVerityDescriptor.Builder builder = new FsVerityDescriptor.Builder(); 160 byte inFsVersion = bf.get(); 161 if (inFsVersion != FsVerityDescriptor.VERSION) { 162 throw new VerifyCodeSignException("Invalid fs-verify descriptor version of ElfSignBlock"); 163 } 164 byte inFsHashAlgorithm = bf.get(); 165 byte inLog2BlockSize = bf.get(); 166 builder.setVersion(inFsVersion).setHashAlgorithm(inFsHashAlgorithm).setLog2BlockSize(inLog2BlockSize); 167 byte inSaltSize = bf.get(); 168 int inSignSize = bf.getInt(); 169 long inDataSize = bf.getLong(); 170 byte[] inRootHash = new byte[FsVerityDescriptor.ROOT_HASH_FILED_SIZE]; 171 bf.get(inRootHash); 172 builder.setSaltSize(inSaltSize).setSignSize(inSignSize).setFileSize(inDataSize).setRawRootHash(inRootHash); 173 byte[] inSalt = new byte[FsVerityDescriptor.SALT_SIZE]; 174 bf.get(inSalt); 175 int inFlags = bf.getInt(); 176 bf.getInt(); 177 long inTreeOffset = bf.getLong(); 178 if (!NumberUtils.isMultiple4K(inTreeOffset)) { 179 throw new VerifyCodeSignException("Invalid merkle tree offset of ElfSignBlock"); 180 } 181 bf.get(new byte[FsVerityDescriptor.RESERVED_SIZE_AFTER_TREE_OFFSET]); 182 byte inCsVersion = bf.get(); 183 builder.setSalt(inSalt).setFlags(inFlags).setMerkleTreeOffset(inTreeOffset).setCsVersion(inCsVersion); 184 return builder.build(); 185 } 186 187 /** 188 * Get FsVerity descriptor bytes 189 * 190 * @return bytes of descriptor 191 * @throws FsVerityDigestException if error 192 */ toByteArray()193 public byte[] toByteArray() throws FsVerityDigestException { 194 ByteBuffer buffer = ByteBuffer.allocate(DESCRIPTOR_SIZE).order(ByteOrder.LITTLE_ENDIAN); 195 buffer.put(VERSION); 196 buffer.put(hashAlgorithm); 197 buffer.put(log2BlockSize); 198 if (this.saltSize > SALT_SIZE) { 199 throw new FsVerityDigestException(CodeSignErrMsg.CODE_SIGN_INTERNAL_ERROR.toString("Salt is too long")); 200 } 201 buffer.put(this.saltSize); 202 buffer.putInt(signSize); 203 buffer.putLong(fileSize); 204 writeBytesWithSize(buffer, rawRootHash, ROOT_HASH_FILED_SIZE); 205 writeBytesWithSize(buffer, salt, SALT_SIZE); 206 buffer.putInt(flags); 207 buffer.putInt(0); 208 buffer.putLong(merkleTreeOffset); 209 buffer.putLong(0); 210 writeBytesWithSize(buffer, null, RESERVED_SIZE_AFTER_TREE_OFFSET); 211 buffer.put(csVersion); 212 return buffer.array(); 213 } 214 215 /** 216 * Get bytes for generate digest, first byte is CODE_SIGN_VERSION, sign size is 0, last 128 bytes is 0 217 * 218 * @return bytes of descriptor 219 * @throws FsVerityDigestException if error 220 */ getDiscByte()221 public byte[] getDiscByte() throws FsVerityDigestException { 222 ByteBuffer buffer = ByteBuffer.allocate(DESCRIPTOR_SIZE).order(ByteOrder.LITTLE_ENDIAN); 223 buffer.put(CODE_SIGN_VERSION); 224 buffer.put(hashAlgorithm); 225 buffer.put(log2BlockSize); 226 if (this.saltSize > SALT_SIZE) { 227 throw new FsVerityDigestException(CodeSignErrMsg.CODE_SIGN_INTERNAL_ERROR.toString("Salt is too long")); 228 } 229 buffer.put(this.saltSize); 230 buffer.putInt(0); 231 buffer.putLong(fileSize); 232 writeBytesWithSize(buffer, rawRootHash, ROOT_HASH_FILED_SIZE); 233 writeBytesWithSize(buffer, salt, SALT_SIZE); 234 buffer.putInt(flags); 235 buffer.putInt(0); 236 buffer.putLong(merkleTreeOffset); 237 return buffer.array(); 238 } 239 240 /** 241 * Get bytes for generate digest, cs_version 2 242 * 243 * @param mapOffset bit map data offset at file 244 * @param mapSize bit map size 245 * @param unitSize bit map unit size corresponding to each page 246 * @return bytes of descriptor 247 * @throws FsVerityDigestException if error 248 */ getDiscByteCsv2(long mapOffset, long mapSize, byte unitSize)249 public byte[] getDiscByteCsv2(long mapOffset, long mapSize, byte unitSize) throws FsVerityDigestException { 250 ByteBuffer buffer = ByteBuffer.allocate(DESCRIPTOR_SIZE).order(ByteOrder.LITTLE_ENDIAN); 251 buffer.put(VERSION); 252 buffer.put(hashAlgorithm); 253 buffer.put(log2BlockSize); 254 if (this.saltSize > SALT_SIZE) { 255 throw new FsVerityDigestException(CodeSignErrMsg.CODE_SIGN_INTERNAL_ERROR.toString("Salt is too long")); 256 } 257 buffer.put(this.saltSize); 258 buffer.putInt(0); 259 buffer.putLong(fileSize); 260 writeBytesWithSize(buffer, rawRootHash, ROOT_HASH_FILED_SIZE); 261 writeBytesWithSize(buffer, salt, SALT_SIZE); 262 buffer.putInt((unitSize << 1 | flags)); 263 buffer.putInt((int) mapSize); 264 buffer.putLong(merkleTreeOffset); 265 buffer.putLong(mapOffset); 266 writeBytesWithSize(buffer, null, RESERVED_SIZE_AFTER_TREE_OFFSET); 267 buffer.put(CODE_SIGN_VERSION_V2); 268 return buffer.array(); 269 } 270 271 /** 272 * Write bytes to ByteBuffer with specific size 273 * 274 * @param buffer target buffer 275 * @param src bytes to write 276 * @param size size of written bytes, fill 0 if src bytes is long enough 277 */ writeBytesWithSize(ByteBuffer buffer, byte[] src, int size)278 private void writeBytesWithSize(ByteBuffer buffer, byte[] src, int size) { 279 int pos = buffer.position(); 280 if (src != null) { 281 if (src.length > size) { 282 buffer.put(src, 0, size); 283 } else { 284 buffer.put(src); 285 } 286 } 287 buffer.position(pos + size); 288 } 289 290 /** 291 * Builder of FsVerityDescriptor class 292 */ 293 public static class Builder { 294 private byte version = VERSION; 295 296 private long fileSize; 297 298 private byte hashAlgorithm; 299 300 private byte log2BlockSize; 301 302 private byte saltSize; 303 304 private int signSize; 305 306 private byte[] salt; 307 308 private byte[] rawRootHash; 309 310 private int flags; 311 312 private int bitMapSize; 313 314 private long merkleTreeOffset; 315 316 private long bitMapOffset; 317 318 private byte csVersion; 319 setVersion(byte version)320 public Builder setVersion(byte version) { 321 this.version = version; 322 return this; 323 } 324 setFileSize(long fileSize)325 public Builder setFileSize(long fileSize) { 326 this.fileSize = fileSize; 327 return this; 328 } 329 setHashAlgorithm(byte hashAlgorithm)330 public Builder setHashAlgorithm(byte hashAlgorithm) { 331 this.hashAlgorithm = hashAlgorithm; 332 return this; 333 } 334 setLog2BlockSize(byte log2BlockSize)335 public Builder setLog2BlockSize(byte log2BlockSize) { 336 this.log2BlockSize = log2BlockSize; 337 return this; 338 } 339 setSignSize(int signSize)340 public Builder setSignSize(int signSize) { 341 this.signSize = signSize; 342 return this; 343 } 344 setSaltSize(byte saltSize)345 public Builder setSaltSize(byte saltSize) { 346 this.saltSize = saltSize; 347 return this; 348 } 349 setSalt(byte[] salt)350 public Builder setSalt(byte[] salt) { 351 this.salt = salt; 352 return this; 353 } 354 setRawRootHash(byte[] rawRootHash)355 public Builder setRawRootHash(byte[] rawRootHash) { 356 this.rawRootHash = rawRootHash; 357 return this; 358 } 359 setFlags(int flags)360 public Builder setFlags(int flags) { 361 this.flags = flags; 362 return this; 363 } 364 setMerkleTreeOffset(long merkleTreeOffset)365 public Builder setMerkleTreeOffset(long merkleTreeOffset) { 366 this.merkleTreeOffset = merkleTreeOffset; 367 return this; 368 } 369 setCsVersion(byte csVersion)370 public Builder setCsVersion(byte csVersion) { 371 this.csVersion = csVersion; 372 return this; 373 } 374 375 /** 376 * Create a FsVerityDescriptor object 377 * 378 * @return a FsVerityDescriptor object 379 */ build()380 public FsVerityDescriptor build() { 381 return new FsVerityDescriptor(this); 382 } 383 } 384 } 385