1 /* 2 * Copyright (C) 2020 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.os.incremental; 18 19 import android.annotation.NonNull; 20 import android.annotation.Nullable; 21 import android.os.ParcelFileDescriptor; 22 23 import java.io.ByteArrayInputStream; 24 import java.io.ByteArrayOutputStream; 25 import java.io.EOFException; 26 import java.io.IOException; 27 import java.io.InputStream; 28 import java.io.OutputStream; 29 import java.nio.ByteBuffer; 30 import java.nio.ByteOrder; 31 import java.util.ArrayList; 32 33 /** 34 * V4 signature fields. 35 * Keep in sync with APKSig authoritative copy. 36 * @hide 37 */ 38 public class V4Signature { 39 public static final String EXT = ".idsig"; 40 public static final int SUPPORTED_VERSION = 2; 41 42 public static final int HASHING_ALGORITHM_SHA256 = 1; 43 public static final byte LOG2_BLOCK_SIZE_4096_BYTES = 12; 44 45 public static final int INCFS_MAX_SIGNATURE_SIZE = 8096; // incrementalfs.h 46 47 /** 48 * IncFS hashing data. 49 */ 50 public static class HashingInfo { 51 public final int hashAlgorithm; // only 1 == SHA256 supported 52 public final byte log2BlockSize; // only 12 (block size 4096) supported now 53 @Nullable public final byte[] salt; // used exactly as in fs-verity, 32 bytes max 54 @Nullable public final byte[] rawRootHash; // salted digest of the first Merkle tree page 55 HashingInfo(int hashAlgorithm, byte log2BlockSize, byte[] salt, byte[] rawRootHash)56 HashingInfo(int hashAlgorithm, byte log2BlockSize, byte[] salt, byte[] rawRootHash) { 57 this.hashAlgorithm = hashAlgorithm; 58 this.log2BlockSize = log2BlockSize; 59 this.salt = salt; 60 this.rawRootHash = rawRootHash; 61 } 62 63 /** 64 * Constructs HashingInfo from byte array. 65 */ 66 @NonNull fromByteArray(@onNull byte[] bytes)67 public static HashingInfo fromByteArray(@NonNull byte[] bytes) throws IOException { 68 ByteBuffer buffer = ByteBuffer.wrap(bytes).order(ByteOrder.LITTLE_ENDIAN); 69 final int hashAlgorithm = buffer.getInt(); 70 final byte log2BlockSize = buffer.get(); 71 byte[] salt = readBytes(buffer); 72 byte[] rawRootHash = readBytes(buffer); 73 return new HashingInfo(hashAlgorithm, log2BlockSize, salt, rawRootHash); 74 } 75 } 76 77 /** 78 * Signature data. 79 */ 80 public static class SigningInfo { 81 public final byte[] apkDigest; // used to match with the corresponding APK 82 public final byte[] certificate; // ASN.1 DER form 83 public final byte[] additionalData; // a free-form binary data blob 84 public final byte[] publicKey; // ASN.1 DER, must match the certificate 85 public final int signatureAlgorithmId; // see the APK v2 doc for the list 86 public final byte[] signature; 87 SigningInfo(byte[] apkDigest, byte[] certificate, byte[] additionalData, byte[] publicKey, int signatureAlgorithmId, byte[] signature)88 SigningInfo(byte[] apkDigest, byte[] certificate, byte[] additionalData, 89 byte[] publicKey, int signatureAlgorithmId, byte[] signature) { 90 this.apkDigest = apkDigest; 91 this.certificate = certificate; 92 this.additionalData = additionalData; 93 this.publicKey = publicKey; 94 this.signatureAlgorithmId = signatureAlgorithmId; 95 this.signature = signature; 96 } 97 98 /** 99 * Constructs SigningInfo from byte array. 100 */ fromByteArray(byte[] bytes)101 public static SigningInfo fromByteArray(byte[] bytes) throws IOException { 102 return fromByteBuffer(ByteBuffer.wrap(bytes).order(ByteOrder.LITTLE_ENDIAN)); 103 } 104 105 /** 106 * Constructs SigningInfo from byte buffer. 107 */ fromByteBuffer(ByteBuffer buffer)108 public static SigningInfo fromByteBuffer(ByteBuffer buffer) throws IOException { 109 byte[] apkDigest = readBytes(buffer); 110 byte[] certificate = readBytes(buffer); 111 byte[] additionalData = readBytes(buffer); 112 byte[] publicKey = readBytes(buffer); 113 int signatureAlgorithmId = buffer.getInt(); 114 byte[] signature = readBytes(buffer); 115 return new SigningInfo(apkDigest, certificate, additionalData, publicKey, 116 signatureAlgorithmId, signature); 117 } 118 } 119 120 /** 121 * Optional signature data block with ID. 122 */ 123 public static class SigningInfoBlock { 124 public final int blockId; 125 public final byte[] signingInfo; 126 SigningInfoBlock(int blockId, byte[] signingInfo)127 public SigningInfoBlock(int blockId, byte[] signingInfo) { 128 this.blockId = blockId; 129 this.signingInfo = signingInfo; 130 } 131 fromByteBuffer(ByteBuffer buffer)132 static SigningInfoBlock fromByteBuffer(ByteBuffer buffer) throws IOException { 133 int blockId = buffer.getInt(); 134 byte[] signingInfo = readBytes(buffer); 135 return new SigningInfoBlock(blockId, signingInfo); 136 } 137 } 138 139 /** 140 * V4 signature data. 141 */ 142 public static class SigningInfos { 143 // Default signature. 144 public final SigningInfo signingInfo; 145 // Additional signatures corresponding to extended V2/V3/V31 blocks. 146 public final SigningInfoBlock[] signingInfoBlocks; 147 SigningInfos(SigningInfo signingInfo)148 public SigningInfos(SigningInfo signingInfo) { 149 this.signingInfo = signingInfo; 150 this.signingInfoBlocks = new SigningInfoBlock[0]; 151 } 152 SigningInfos(SigningInfo signingInfo, SigningInfoBlock... signingInfoBlocks)153 public SigningInfos(SigningInfo signingInfo, SigningInfoBlock... signingInfoBlocks) { 154 this.signingInfo = signingInfo; 155 this.signingInfoBlocks = signingInfoBlocks; 156 } 157 158 /** 159 * Constructs SigningInfos from byte array. 160 */ fromByteArray(byte[] bytes)161 public static SigningInfos fromByteArray(byte[] bytes) throws IOException { 162 ByteBuffer buffer = ByteBuffer.wrap(bytes).order(ByteOrder.LITTLE_ENDIAN); 163 SigningInfo signingInfo = SigningInfo.fromByteBuffer(buffer); 164 if (!buffer.hasRemaining()) { 165 return new SigningInfos(signingInfo); 166 } 167 ArrayList<SigningInfoBlock> signingInfoBlocks = new ArrayList<>(1); 168 while (buffer.hasRemaining()) { 169 signingInfoBlocks.add(SigningInfoBlock.fromByteBuffer(buffer)); 170 } 171 return new SigningInfos(signingInfo, 172 signingInfoBlocks.toArray(new SigningInfoBlock[signingInfoBlocks.size()])); 173 } 174 } 175 176 public final int version; // Always 2 for now. 177 /** 178 * Raw byte array containing the IncFS hashing data. 179 * @see HashingInfo#fromByteArray(byte[]) 180 */ 181 @Nullable public final byte[] hashingInfo; 182 183 /** 184 * Raw byte array containing V4 signatures. 185 * <p>Passed as-is to the kernel. Can be retrieved later. 186 * @see SigningInfos#fromByteArray(byte[]) 187 */ 188 @Nullable public final byte[] signingInfos; 189 190 /** 191 * Construct a V4Signature from .idsig file. 192 */ readFrom(ParcelFileDescriptor pfd)193 public static V4Signature readFrom(ParcelFileDescriptor pfd) throws IOException { 194 try (InputStream stream = new ParcelFileDescriptor.AutoCloseInputStream(pfd.dup())) { 195 return readFrom(stream); 196 } 197 } 198 199 /** 200 * Construct a V4Signature from a byte array. 201 */ 202 @NonNull readFrom(@onNull byte[] bytes)203 public static V4Signature readFrom(@NonNull byte[] bytes) throws IOException { 204 try (InputStream stream = new ByteArrayInputStream(bytes)) { 205 return readFrom(stream); 206 } 207 } 208 209 /** 210 * Store the V4Signature to a byte-array. 211 */ toByteArray()212 public byte[] toByteArray() { 213 try (ByteArrayOutputStream stream = new ByteArrayOutputStream()) { 214 this.writeTo(stream); 215 return stream.toByteArray(); 216 } catch (IOException e) { 217 return null; 218 } 219 } 220 221 /** 222 * Combines necessary data to a signed data blob. 223 * The blob can be validated against signingInfo.signature. 224 * 225 * @param fileSize - size of the signed file (APK) 226 */ getSignedData(long fileSize, HashingInfo hashingInfo, SigningInfo signingInfo)227 public static byte[] getSignedData(long fileSize, HashingInfo hashingInfo, 228 SigningInfo signingInfo) { 229 final int size = 230 4/*size*/ + 8/*fileSize*/ + 4/*hash_algorithm*/ + 1/*log2_blocksize*/ + bytesSize( 231 hashingInfo.salt) + bytesSize(hashingInfo.rawRootHash) + bytesSize( 232 signingInfo.apkDigest) + bytesSize(signingInfo.certificate) + bytesSize( 233 signingInfo.additionalData); 234 ByteBuffer buffer = ByteBuffer.allocate(size).order(ByteOrder.LITTLE_ENDIAN); 235 buffer.putInt(size); 236 buffer.putLong(fileSize); 237 buffer.putInt(hashingInfo.hashAlgorithm); 238 buffer.put(hashingInfo.log2BlockSize); 239 writeBytes(buffer, hashingInfo.salt); 240 writeBytes(buffer, hashingInfo.rawRootHash); 241 writeBytes(buffer, signingInfo.apkDigest); 242 writeBytes(buffer, signingInfo.certificate); 243 writeBytes(buffer, signingInfo.additionalData); 244 return buffer.array(); 245 } 246 isVersionSupported()247 public boolean isVersionSupported() { 248 return this.version == SUPPORTED_VERSION; 249 } 250 V4Signature(int version, @Nullable byte[] hashingInfo, @Nullable byte[] signingInfos)251 private V4Signature(int version, @Nullable byte[] hashingInfo, @Nullable byte[] signingInfos) { 252 this.version = version; 253 this.hashingInfo = hashingInfo; 254 this.signingInfos = signingInfos; 255 } 256 257 /** 258 * Constructs a V4Signature from an InputStream. 259 */ readFrom(InputStream stream)260 public static V4Signature readFrom(InputStream stream) throws IOException { 261 final int version = readIntLE(stream); 262 int maxSize = INCFS_MAX_SIGNATURE_SIZE; 263 final byte[] hashingInfo = readBytes(stream, maxSize); 264 if (hashingInfo != null) { 265 maxSize -= hashingInfo.length; 266 } 267 final byte[] signingInfo = readBytes(stream, maxSize); 268 return new V4Signature(version, hashingInfo, signingInfo); 269 } 270 writeTo(OutputStream stream)271 private void writeTo(OutputStream stream) throws IOException { 272 writeIntLE(stream, this.version); 273 writeBytes(stream, this.hashingInfo); 274 writeBytes(stream, this.signingInfos); 275 } 276 277 // Utility methods. bytesSize(byte[] bytes)278 private static int bytesSize(byte[] bytes) { 279 return 4/*length*/ + (bytes == null ? 0 : bytes.length); 280 } 281 readFully(InputStream stream, byte[] buffer)282 private static void readFully(InputStream stream, byte[] buffer) throws IOException { 283 int len = buffer.length; 284 int n = 0; 285 while (n < len) { 286 int count = stream.read(buffer, n, len - n); 287 if (count < 0) { 288 throw new EOFException(); 289 } 290 n += count; 291 } 292 } 293 readIntLE(InputStream stream)294 private static int readIntLE(InputStream stream) throws IOException { 295 final byte[] buffer = new byte[4]; 296 readFully(stream, buffer); 297 return ByteBuffer.wrap(buffer).order(ByteOrder.LITTLE_ENDIAN).getInt(); 298 } 299 writeIntLE(OutputStream stream, int v)300 private static void writeIntLE(OutputStream stream, int v) throws IOException { 301 final byte[] buffer = ByteBuffer.wrap(new byte[4]).order(ByteOrder.LITTLE_ENDIAN).putInt( 302 v).array(); 303 stream.write(buffer); 304 } 305 readBytes(InputStream stream, int maxSize)306 private static byte[] readBytes(InputStream stream, int maxSize) throws IOException { 307 try { 308 final int size = readIntLE(stream); 309 if (size > maxSize) { 310 throw new IOException( 311 "Signature is too long. Max allowed is " + INCFS_MAX_SIGNATURE_SIZE); 312 } 313 final byte[] bytes = new byte[size]; 314 readFully(stream, bytes); 315 return bytes; 316 } catch (EOFException ignored) { 317 return null; 318 } 319 } 320 readBytes(ByteBuffer buffer)321 private static byte[] readBytes(ByteBuffer buffer) throws IOException { 322 if (buffer.remaining() < 4) { 323 throw new EOFException(); 324 } 325 final int size = buffer.getInt(); 326 if (buffer.remaining() < size) { 327 throw new EOFException(); 328 } 329 final byte[] bytes = new byte[size]; 330 buffer.get(bytes); 331 return bytes; 332 } 333 writeBytes(OutputStream stream, byte[] bytes)334 private static void writeBytes(OutputStream stream, byte[] bytes) throws IOException { 335 if (bytes == null) { 336 writeIntLE(stream, 0); 337 return; 338 } 339 writeIntLE(stream, bytes.length); 340 stream.write(bytes); 341 } 342 writeBytes(ByteBuffer buffer, byte[] bytes)343 private static void writeBytes(ByteBuffer buffer, byte[] bytes) { 344 if (bytes == null) { 345 buffer.putInt(0); 346 return; 347 } 348 buffer.putInt(bytes.length); 349 buffer.put(bytes); 350 } 351 } 352