• 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 
26 public class V4Signature {
27     public static final int CURRENT_VERSION = 2;
28 
29     public static final int HASHING_ALGORITHM_SHA256 = 1;
30     public static final byte LOG2_BLOCK_SIZE_4096_BYTES = 12;
31 
32     public static class HashingInfo {
33         public final int hashAlgorithm; // only 1 == SHA256 supported
34         public final byte log2BlockSize; // only 12 (block size 4096) supported now
35         public final byte[] salt; // used exactly as in fs-verity, 32 bytes max
36         public final byte[] rawRootHash; // salted digest of the first Merkle tree page
37 
HashingInfo(int hashAlgorithm, byte log2BlockSize, byte[] salt, byte[] rawRootHash)38         HashingInfo(int hashAlgorithm, byte log2BlockSize, byte[] salt, byte[] rawRootHash) {
39             this.hashAlgorithm = hashAlgorithm;
40             this.log2BlockSize = log2BlockSize;
41             this.salt = salt;
42             this.rawRootHash = rawRootHash;
43         }
44 
fromByteArray(byte[] bytes)45         static HashingInfo fromByteArray(byte[] bytes) throws IOException {
46             ByteBuffer buffer = ByteBuffer.wrap(bytes).order(ByteOrder.LITTLE_ENDIAN);
47             final int hashAlgorithm = buffer.getInt();
48             final byte log2BlockSize = buffer.get();
49             byte[] salt = readBytes(buffer);
50             byte[] rawRootHash = readBytes(buffer);
51             return new HashingInfo(hashAlgorithm, log2BlockSize, salt, rawRootHash);
52         }
53 
toByteArray()54         byte[] toByteArray() {
55             final int size = 4/*hashAlgorithm*/ + 1/*log2BlockSize*/ + bytesSize(this.salt)
56                     + bytesSize(this.rawRootHash);
57             ByteBuffer buffer = ByteBuffer.allocate(size).order(ByteOrder.LITTLE_ENDIAN);
58             buffer.putInt(this.hashAlgorithm);
59             buffer.put(this.log2BlockSize);
60             writeBytes(buffer, this.salt);
61             writeBytes(buffer, this.rawRootHash);
62             return buffer.array();
63         }
64     }
65 
66     public static class SigningInfo {
67         public final byte[] apkDigest;  // used to match with the corresponding APK
68         public final byte[] certificate; // ASN.1 DER form
69         public final byte[] additionalData; // a free-form binary data blob
70         public final byte[] publicKey; // ASN.1 DER, must match the certificate
71         public final int signatureAlgorithmId; // see the APK v2 doc for the list
72         public final byte[] signature;
73 
SigningInfo(byte[] apkDigest, byte[] certificate, byte[] additionalData, byte[] publicKey, int signatureAlgorithmId, byte[] signature)74         SigningInfo(byte[] apkDigest, byte[] certificate, byte[] additionalData,
75                 byte[] publicKey, int signatureAlgorithmId, byte[] signature) {
76             this.apkDigest = apkDigest;
77             this.certificate = certificate;
78             this.additionalData = additionalData;
79             this.publicKey = publicKey;
80             this.signatureAlgorithmId = signatureAlgorithmId;
81             this.signature = signature;
82         }
83 
fromByteArray(byte[] bytes)84         static SigningInfo fromByteArray(byte[] bytes) throws IOException {
85             ByteBuffer buffer = ByteBuffer.wrap(bytes).order(ByteOrder.LITTLE_ENDIAN);
86             byte[] apkDigest = readBytes(buffer);
87             byte[] certificate = readBytes(buffer);
88             byte[] additionalData = readBytes(buffer);
89             byte[] publicKey = readBytes(buffer);
90             int signatureAlgorithmId = buffer.getInt();
91             byte[] signature = readBytes(buffer);
92             return new SigningInfo(apkDigest, certificate, additionalData, publicKey,
93                     signatureAlgorithmId, signature);
94         }
95 
toByteArray()96         byte[] toByteArray() {
97             final int size = bytesSize(this.apkDigest) + bytesSize(this.certificate) + bytesSize(
98                     this.additionalData) + bytesSize(this.publicKey) + 4/*signatureAlgorithmId*/
99                     + bytesSize(this.signature);
100             ByteBuffer buffer = ByteBuffer.allocate(size).order(ByteOrder.LITTLE_ENDIAN);
101             writeBytes(buffer, this.apkDigest);
102             writeBytes(buffer, this.certificate);
103             writeBytes(buffer, this.additionalData);
104             writeBytes(buffer, this.publicKey);
105             buffer.putInt(this.signatureAlgorithmId);
106             writeBytes(buffer, this.signature);
107             return buffer.array();
108         }
109     }
110 
111     public final int version; // Always 2 for now.
112     public final byte[] hashingInfo;
113     public final byte[] signingInfo; // Passed as-is to the kernel. Can be retrieved later.
114 
V4Signature(int version, byte[] hashingInfo, byte[] signingInfo)115     V4Signature(int version, byte[] hashingInfo, byte[] signingInfo) {
116         this.version = version;
117         this.hashingInfo = hashingInfo;
118         this.signingInfo = signingInfo;
119     }
120 
readFrom(InputStream stream)121     static V4Signature readFrom(InputStream stream) throws IOException {
122         final int version = readIntLE(stream);
123         if (version != CURRENT_VERSION) {
124             throw new IOException("Invalid signature version.");
125         }
126         final byte[] hashingInfo = readBytes(stream);
127         final byte[] signingInfo = readBytes(stream);
128         return new V4Signature(version, hashingInfo, signingInfo);
129     }
130 
writeTo(OutputStream stream)131     public void writeTo(OutputStream stream) throws IOException {
132         writeIntLE(stream, this.version);
133         writeBytes(stream, this.hashingInfo);
134         writeBytes(stream, this.signingInfo);
135     }
136 
getSignedData(long fileSize, HashingInfo hashingInfo, SigningInfo signingInfo)137     static byte[] getSignedData(long fileSize, HashingInfo hashingInfo, SigningInfo signingInfo) {
138         final int size =
139                 4/*size*/ + 8/*fileSize*/ + 4/*hash_algorithm*/ + 1/*log2_blocksize*/ + bytesSize(
140                         hashingInfo.salt) + bytesSize(hashingInfo.rawRootHash) + bytesSize(
141                         signingInfo.apkDigest) + bytesSize(signingInfo.certificate) + bytesSize(
142                         signingInfo.additionalData);
143         ByteBuffer buffer = ByteBuffer.allocate(size).order(ByteOrder.LITTLE_ENDIAN);
144         buffer.putInt(size);
145         buffer.putLong(fileSize);
146         buffer.putInt(hashingInfo.hashAlgorithm);
147         buffer.put(hashingInfo.log2BlockSize);
148         writeBytes(buffer, hashingInfo.salt);
149         writeBytes(buffer, hashingInfo.rawRootHash);
150         writeBytes(buffer, signingInfo.apkDigest);
151         writeBytes(buffer, signingInfo.certificate);
152         writeBytes(buffer, signingInfo.additionalData);
153         return buffer.array();
154     }
155 
156     // Utility methods.
bytesSize(byte[] bytes)157     static int bytesSize(byte[] bytes) {
158         return 4/*length*/ + (bytes == null ? 0 : bytes.length);
159     }
160 
readFully(InputStream stream, byte[] buffer)161     static void readFully(InputStream stream, byte[] buffer) throws IOException {
162         int len = buffer.length;
163         int n = 0;
164         while (n < len) {
165             int count = stream.read(buffer, n, len - n);
166             if (count < 0) {
167                 throw new EOFException();
168             }
169             n += count;
170         }
171     }
172 
readIntLE(InputStream stream)173     static int readIntLE(InputStream stream) throws IOException {
174         final byte[] buffer = new byte[4];
175         readFully(stream, buffer);
176         return ByteBuffer.wrap(buffer).order(ByteOrder.LITTLE_ENDIAN).getInt();
177     }
178 
writeIntLE(OutputStream stream, int v)179     static void writeIntLE(OutputStream stream, int v) throws IOException {
180         final byte[] buffer = ByteBuffer.wrap(new byte[4]).order(ByteOrder.LITTLE_ENDIAN).putInt(v).array();
181         stream.write(buffer);
182     }
183 
readBytes(InputStream stream)184     static byte[] readBytes(InputStream stream) throws IOException {
185         try {
186             final int size = readIntLE(stream);
187             final byte[] bytes = new byte[size];
188             readFully(stream, bytes);
189             return bytes;
190         } catch (EOFException ignored) {
191             return null;
192         }
193     }
194 
readBytes(ByteBuffer buffer)195     static byte[] readBytes(ByteBuffer buffer) throws IOException {
196         if (buffer.remaining() < 4) {
197             throw new EOFException();
198         }
199         final int size = buffer.getInt();
200         if (buffer.remaining() < size) {
201             throw new EOFException();
202         }
203         final byte[] bytes = new byte[size];
204         buffer.get(bytes);
205         return bytes;
206     }
207 
writeBytes(OutputStream stream, byte[] bytes)208     static void writeBytes(OutputStream stream, byte[] bytes) throws IOException {
209         if (bytes == null) {
210             writeIntLE(stream, 0);
211             return;
212         }
213         writeIntLE(stream, bytes.length);
214         stream.write(bytes);
215     }
216 
writeBytes(ByteBuffer buffer, byte[] bytes)217     static void writeBytes(ByteBuffer buffer, byte[] bytes) {
218         if (bytes == null) {
219             buffer.putInt(0);
220             return;
221         }
222         buffer.putInt(bytes.length);
223         buffer.put(bytes);
224     }
225 }
226