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