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