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