• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 com.android.apksig.internal.apk.v4;
18 
19 import java.io.EOFException;
20 import java.io.IOException;
21 import java.io.InputStream;
22 import java.io.OutputStream;
23 import java.nio.ByteBuffer;
24 import java.nio.ByteOrder;
25 import java.util.ArrayList;
26 import java.util.Arrays;
27 
28 public class V4Signature {
29     public static final int CURRENT_VERSION = 2;
30 
31     public static final int HASHING_ALGORITHM_SHA256 = 1;
32     public static final byte LOG2_BLOCK_SIZE_4096_BYTES = 12;
33 
34     public static final int MAX_SIGNING_INFOS_SIZE = 7168;
35 
36     public static class HashingInfo {
37         public final int hashAlgorithm; // only 1 == SHA256 supported
38         public final byte log2BlockSize; // only 12 (block size 4096) supported now
39         public final byte[] salt; // used exactly as in fs-verity, 32 bytes max
40         public final byte[] rawRootHash; // salted digest of the first Merkle tree page
41 
HashingInfo(int hashAlgorithm, byte log2BlockSize, byte[] salt, byte[] rawRootHash)42         HashingInfo(int hashAlgorithm, byte log2BlockSize, byte[] salt, byte[] rawRootHash) {
43             this.hashAlgorithm = hashAlgorithm;
44             this.log2BlockSize = log2BlockSize;
45             this.salt = salt;
46             this.rawRootHash = rawRootHash;
47         }
48 
fromByteArray(byte[] bytes)49         static HashingInfo fromByteArray(byte[] bytes) throws IOException {
50             ByteBuffer buffer = ByteBuffer.wrap(bytes).order(ByteOrder.LITTLE_ENDIAN);
51             final int hashAlgorithm = buffer.getInt();
52             final byte log2BlockSize = buffer.get();
53             byte[] salt = readBytes(buffer);
54             byte[] rawRootHash = readBytes(buffer);
55             return new HashingInfo(hashAlgorithm, log2BlockSize, salt, rawRootHash);
56         }
57 
toByteArray()58         byte[] toByteArray() {
59             final int size = 4/*hashAlgorithm*/ + 1/*log2BlockSize*/ + bytesSize(this.salt)
60                     + bytesSize(this.rawRootHash);
61             ByteBuffer buffer = ByteBuffer.allocate(size).order(ByteOrder.LITTLE_ENDIAN);
62             buffer.putInt(this.hashAlgorithm);
63             buffer.put(this.log2BlockSize);
64             writeBytes(buffer, this.salt);
65             writeBytes(buffer, this.rawRootHash);
66             return buffer.array();
67         }
68     }
69 
70     public static class SigningInfo {
71         public final byte[] apkDigest;  // used to match with the corresponding APK
72         public final byte[] certificate; // ASN.1 DER form
73         public final byte[] additionalData; // a free-form binary data blob
74         public final byte[] publicKey; // ASN.1 DER, must match the certificate
75         public final int signatureAlgorithmId; // see the APK v2 doc for the list
76         public final byte[] signature;
77 
SigningInfo(byte[] apkDigest, byte[] certificate, byte[] additionalData, byte[] publicKey, int signatureAlgorithmId, byte[] signature)78         SigningInfo(byte[] apkDigest, byte[] certificate, byte[] additionalData,
79                 byte[] publicKey, int signatureAlgorithmId, byte[] signature) {
80             this.apkDigest = apkDigest;
81             this.certificate = certificate;
82             this.additionalData = additionalData;
83             this.publicKey = publicKey;
84             this.signatureAlgorithmId = signatureAlgorithmId;
85             this.signature = signature;
86         }
87 
fromByteArray(byte[] bytes)88         static SigningInfo fromByteArray(byte[] bytes) throws IOException {
89             return fromByteBuffer(ByteBuffer.wrap(bytes).order(ByteOrder.LITTLE_ENDIAN));
90         }
91 
fromByteBuffer(ByteBuffer buffer)92         static SigningInfo fromByteBuffer(ByteBuffer buffer) throws IOException {
93             byte[] apkDigest = readBytes(buffer);
94             byte[] certificate = readBytes(buffer);
95             byte[] additionalData = readBytes(buffer);
96             byte[] publicKey = readBytes(buffer);
97             int signatureAlgorithmId = buffer.getInt();
98             byte[] signature = readBytes(buffer);
99             return new SigningInfo(apkDigest, certificate, additionalData, publicKey,
100                     signatureAlgorithmId, signature);
101         }
102 
toByteArray()103         byte[] toByteArray() {
104             final int size = bytesSize(this.apkDigest) + bytesSize(this.certificate) + bytesSize(
105                     this.additionalData) + bytesSize(this.publicKey) + 4/*signatureAlgorithmId*/
106                     + bytesSize(this.signature);
107             ByteBuffer buffer = ByteBuffer.allocate(size).order(ByteOrder.LITTLE_ENDIAN);
108             writeBytes(buffer, this.apkDigest);
109             writeBytes(buffer, this.certificate);
110             writeBytes(buffer, this.additionalData);
111             writeBytes(buffer, this.publicKey);
112             buffer.putInt(this.signatureAlgorithmId);
113             writeBytes(buffer, this.signature);
114             return buffer.array();
115         }
116     }
117 
118     public static class SigningInfoBlock {
119         public final int blockId;
120         public final byte[] signingInfo;
121 
SigningInfoBlock(int blockId, byte[] signingInfo)122         public SigningInfoBlock(int blockId, byte[] signingInfo) {
123             this.blockId = blockId;
124             this.signingInfo = signingInfo;
125         }
126 
fromByteBuffer(ByteBuffer buffer)127         static SigningInfoBlock fromByteBuffer(ByteBuffer buffer) throws IOException {
128             int blockId = buffer.getInt();
129             byte[] signingInfo = readBytes(buffer);
130             return new SigningInfoBlock(blockId, signingInfo);
131         }
132 
toByteArray()133         byte[] toByteArray() {
134             final int size = 4/*blockId*/ + bytesSize(this.signingInfo);
135             ByteBuffer buffer = ByteBuffer.allocate(size).order(ByteOrder.LITTLE_ENDIAN);
136             buffer.putInt(this.blockId);
137             writeBytes(buffer, this.signingInfo);
138             return buffer.array();
139         }
140     }
141 
142     public static class SigningInfos {
143         public final SigningInfo signingInfo;
144         public final SigningInfoBlock[] signingInfoBlocks;
145 
SigningInfos(SigningInfo signingInfo)146         public SigningInfos(SigningInfo signingInfo) {
147             this.signingInfo = signingInfo;
148             this.signingInfoBlocks = new SigningInfoBlock[0];
149         }
150 
SigningInfos(SigningInfo signingInfo, SigningInfoBlock... signingInfoBlocks)151         public SigningInfos(SigningInfo signingInfo, SigningInfoBlock... signingInfoBlocks) {
152             this.signingInfo = signingInfo;
153             this.signingInfoBlocks = signingInfoBlocks;
154         }
155 
fromByteArray(byte[] bytes)156         public static SigningInfos fromByteArray(byte[] bytes) throws IOException {
157             ByteBuffer buffer = ByteBuffer.wrap(bytes).order(ByteOrder.LITTLE_ENDIAN);
158             SigningInfo signingInfo = SigningInfo.fromByteBuffer(buffer);
159             if (!buffer.hasRemaining()) {
160                 return new SigningInfos(signingInfo);
161             }
162             ArrayList<SigningInfoBlock> signingInfoBlocks = new ArrayList<>(1);
163             while (buffer.hasRemaining()) {
164                 signingInfoBlocks.add(SigningInfoBlock.fromByteBuffer(buffer));
165             }
166             return new SigningInfos(signingInfo,
167                     signingInfoBlocks.toArray(new SigningInfoBlock[signingInfoBlocks.size()]));
168         }
169 
toByteArray()170         byte[] toByteArray() {
171             byte[][] arrays = new byte[1 + this.signingInfoBlocks.length][];
172             arrays[0] = this.signingInfo.toByteArray();
173             int size = arrays[0].length;
174             for (int i = 0, isize = this.signingInfoBlocks.length; i < isize; ++i) {
175                 arrays[i + 1] = this.signingInfoBlocks[i].toByteArray();
176                 size += arrays[i + 1].length;
177             }
178             if (size > MAX_SIGNING_INFOS_SIZE) {
179                 throw new IllegalArgumentException(
180                         "Combined SigningInfos length exceeded limit of 7K: " + size);
181             }
182 
183             // Combine all arrays into one.
184             byte[] result = Arrays.copyOf(arrays[0], size);
185             int offset = arrays[0].length;
186             for (int i = 0, isize = this.signingInfoBlocks.length; i < isize; ++i) {
187                 System.arraycopy(arrays[i + 1], 0, result, offset, arrays[i + 1].length);
188                 offset += arrays[i + 1].length;
189             }
190             return result;
191         }
192     }
193 
194     // Always 2 for now.
195     public final int version;
196     public final byte[] hashingInfo;
197     // Can contain either SigningInfo or SigningInfo + one or multiple SigningInfoBlock.
198     // Passed as-is to the kernel. Can be retrieved later.
199     public final byte[] signingInfos;
200 
V4Signature(int version, byte[] hashingInfo, byte[] signingInfos)201     V4Signature(int version, byte[] hashingInfo, byte[] signingInfos) {
202         this.version = version;
203         this.hashingInfo = hashingInfo;
204         this.signingInfos = signingInfos;
205     }
206 
readFrom(InputStream stream)207     static V4Signature readFrom(InputStream stream) throws IOException {
208         final int version = readIntLE(stream);
209         if (version != CURRENT_VERSION) {
210             throw new IOException("Invalid signature version.");
211         }
212         final byte[] hashingInfo = readBytes(stream);
213         final byte[] signingInfo = readBytes(stream);
214         return new V4Signature(version, hashingInfo, signingInfo);
215     }
216 
writeTo(OutputStream stream)217     public void writeTo(OutputStream stream) throws IOException {
218         writeIntLE(stream, this.version);
219         writeBytes(stream, this.hashingInfo);
220         writeBytes(stream, this.signingInfos);
221     }
222 
getSignedData(long fileSize, HashingInfo hashingInfo, SigningInfo signingInfo)223     static byte[] getSignedData(long fileSize, HashingInfo hashingInfo, SigningInfo signingInfo) {
224         final int size =
225                 4/*size*/ + 8/*fileSize*/ + 4/*hash_algorithm*/ + 1/*log2_blocksize*/ + bytesSize(
226                         hashingInfo.salt) + bytesSize(hashingInfo.rawRootHash) + bytesSize(
227                         signingInfo.apkDigest) + bytesSize(signingInfo.certificate) + bytesSize(
228                         signingInfo.additionalData);
229         ByteBuffer buffer = ByteBuffer.allocate(size).order(ByteOrder.LITTLE_ENDIAN);
230         buffer.putInt(size);
231         buffer.putLong(fileSize);
232         buffer.putInt(hashingInfo.hashAlgorithm);
233         buffer.put(hashingInfo.log2BlockSize);
234         writeBytes(buffer, hashingInfo.salt);
235         writeBytes(buffer, hashingInfo.rawRootHash);
236         writeBytes(buffer, signingInfo.apkDigest);
237         writeBytes(buffer, signingInfo.certificate);
238         writeBytes(buffer, signingInfo.additionalData);
239         return buffer.array();
240     }
241 
242     // Utility methods.
bytesSize(byte[] bytes)243     static int bytesSize(byte[] bytes) {
244         return 4/*length*/ + (bytes == null ? 0 : bytes.length);
245     }
246 
readFully(InputStream stream, byte[] buffer)247     static void readFully(InputStream stream, byte[] buffer) throws IOException {
248         int len = buffer.length;
249         int n = 0;
250         while (n < len) {
251             int count = stream.read(buffer, n, len - n);
252             if (count < 0) {
253                 throw new EOFException();
254             }
255             n += count;
256         }
257     }
258 
readIntLE(InputStream stream)259     static int readIntLE(InputStream stream) throws IOException {
260         final byte[] buffer = new byte[4];
261         readFully(stream, buffer);
262         return ByteBuffer.wrap(buffer).order(ByteOrder.LITTLE_ENDIAN).getInt();
263     }
264 
writeIntLE(OutputStream stream, int v)265     static void writeIntLE(OutputStream stream, int v) throws IOException {
266         final byte[] buffer = ByteBuffer.wrap(new byte[4]).order(ByteOrder.LITTLE_ENDIAN).putInt(v).array();
267         stream.write(buffer);
268     }
269 
readBytes(InputStream stream)270     static byte[] readBytes(InputStream stream) throws IOException {
271         try {
272             final int size = readIntLE(stream);
273             final byte[] bytes = new byte[size];
274             readFully(stream, bytes);
275             return bytes;
276         } catch (EOFException ignored) {
277             return null;
278         }
279     }
280 
readBytes(ByteBuffer buffer)281     static byte[] readBytes(ByteBuffer buffer) throws IOException {
282         if (buffer.remaining() < 4) {
283             throw new EOFException();
284         }
285         final int size = buffer.getInt();
286         if (buffer.remaining() < size) {
287             throw new EOFException();
288         }
289         final byte[] bytes = new byte[size];
290         buffer.get(bytes);
291         return bytes;
292     }
293 
writeBytes(OutputStream stream, byte[] bytes)294     static void writeBytes(OutputStream stream, byte[] bytes) throws IOException {
295         if (bytes == null) {
296             writeIntLE(stream, 0);
297             return;
298         }
299         writeIntLE(stream, bytes.length);
300         stream.write(bytes);
301     }
302 
writeBytes(ByteBuffer buffer, byte[] bytes)303     static void writeBytes(ByteBuffer buffer, byte[] bytes) {
304         if (bytes == null) {
305             buffer.putInt(0);
306             return;
307         }
308         buffer.putInt(bytes.length);
309         buffer.put(bytes);
310     }
311 }
312