Home | History | Annotate | Download | only in apk
      1 /*
      2  * Copyright (C) 2018 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;
     18 
     19 import com.android.apksig.SigningCertificateLineage;
     20 import com.android.apksig.ApkVerifier;
     21 import com.android.apksig.apk.ApkFormatException;
     22 import com.android.apksig.apk.ApkSigningBlockNotFoundException;
     23 import com.android.apksig.apk.ApkUtils;
     24 import com.android.apksig.internal.util.ByteBufferDataSource;
     25 import com.android.apksig.internal.util.ChainedDataSource;
     26 import com.android.apksig.internal.util.Pair;
     27 import com.android.apksig.internal.util.VerityTreeBuilder;
     28 import com.android.apksig.internal.zip.ZipUtils;
     29 import com.android.apksig.util.DataSink;
     30 import com.android.apksig.util.DataSinks;
     31 import com.android.apksig.util.DataSource;
     32 import com.android.apksig.util.DataSources;
     33 
     34 import java.io.IOException;
     35 import java.nio.BufferUnderflowException;
     36 import java.nio.ByteBuffer;
     37 import java.nio.ByteOrder;
     38 import java.security.DigestException;
     39 import java.security.InvalidAlgorithmParameterException;
     40 import java.security.InvalidKeyException;
     41 import java.security.KeyFactory;
     42 import java.security.MessageDigest;
     43 import java.security.NoSuchAlgorithmException;
     44 import java.security.PrivateKey;
     45 import java.security.PublicKey;
     46 import java.security.Signature;
     47 import java.security.SignatureException;
     48 import java.security.cert.CertificateEncodingException;
     49 import java.security.cert.X509Certificate;
     50 import java.security.spec.AlgorithmParameterSpec;
     51 import java.security.spec.InvalidKeySpecException;
     52 import java.security.spec.X509EncodedKeySpec;
     53 import java.util.ArrayList;
     54 import java.util.Arrays;
     55 import java.util.HashMap;
     56 import java.util.HashSet;
     57 import java.util.List;
     58 import java.util.Map;
     59 import java.util.Set;
     60 import java.util.stream.Collectors;
     61 
     62 public class ApkSigningBlockUtils {
     63 
     64     private static final char[] HEX_DIGITS = "01234567890abcdef".toCharArray();
     65     private static final int CONTENT_DIGESTED_CHUNK_MAX_SIZE_BYTES = 1024 * 1024;
     66     public static final int ANDROID_COMMON_PAGE_ALIGNMENT_BYTES = 4096;
     67     public static final byte[] APK_SIGNING_BLOCK_MAGIC =
     68           new byte[] {
     69               0x41, 0x50, 0x4b, 0x20, 0x53, 0x69, 0x67, 0x20,
     70               0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x20, 0x34, 0x32,
     71           };
     72     private static final int VERITY_PADDING_BLOCK_ID = 0x42726577;
     73 
     74     public static final int VERSION_JAR_SIGNATURE_SCHEME = 1;
     75     public static final int VERSION_APK_SIGNATURE_SCHEME_V2 = 2;
     76     public static final int VERSION_APK_SIGNATURE_SCHEME_V3 = 3;
     77 
     78 
     79     /**
     80      * Returns positive number if {@code alg1} is preferred over {@code alg2}, {@code -1} if
     81      * {@code alg2} is preferred over {@code alg1}, and {@code 0} if there is no preference.
     82      */
     83     public static int compareSignatureAlgorithm(SignatureAlgorithm alg1, SignatureAlgorithm alg2) {
     84         ContentDigestAlgorithm digestAlg1 = alg1.getContentDigestAlgorithm();
     85         ContentDigestAlgorithm digestAlg2 = alg2.getContentDigestAlgorithm();
     86         return compareContentDigestAlgorithm(digestAlg1, digestAlg2);
     87     }
     88 
     89     /**
     90      * Returns a positive number if {@code alg1} is preferred over {@code alg2}, a negative number
     91      * if {@code alg2} is preferred over {@code alg1}, or {@code 0} if there is no preference.
     92      */
     93     private static int compareContentDigestAlgorithm(
     94             ContentDigestAlgorithm alg1,
     95             ContentDigestAlgorithm alg2) {
     96         switch (alg1) {
     97             case CHUNKED_SHA256:
     98                 switch (alg2) {
     99                     case CHUNKED_SHA256:
    100                         return 0;
    101                     case CHUNKED_SHA512:
    102                     case VERITY_CHUNKED_SHA256:
    103                         return -1;
    104                     default:
    105                         throw new IllegalArgumentException("Unknown alg2: " + alg2);
    106                 }
    107             case CHUNKED_SHA512:
    108                 switch (alg2) {
    109                     case CHUNKED_SHA256:
    110                     case VERITY_CHUNKED_SHA256:
    111                         return 1;
    112                     case CHUNKED_SHA512:
    113                         return 0;
    114                     default:
    115                         throw new IllegalArgumentException("Unknown alg2: " + alg2);
    116                 }
    117             case VERITY_CHUNKED_SHA256:
    118                 switch (alg2) {
    119                     case CHUNKED_SHA256:
    120                         return 1;
    121                     case VERITY_CHUNKED_SHA256:
    122                         return 0;
    123                     case CHUNKED_SHA512:
    124                         return -1;
    125                     default:
    126                         throw new IllegalArgumentException("Unknown alg2: " + alg2);
    127                 }
    128             default:
    129                 throw new IllegalArgumentException("Unknown alg1: " + alg1);
    130         }
    131     }
    132 
    133 
    134 
    135     /**
    136      * Verifies integrity of the APK outside of the APK Signing Block by computing digests of the
    137      * APK and comparing them against the digests listed in APK Signing Block. The expected digests
    138      * are taken from {@code SignerInfos} of the provided {@code result}.
    139      *
    140      * <p>This method adds one or more errors to the {@code result} if a verification error is
    141      * expected to be encountered on Android. No errors are added to the {@code result} if the APK's
    142      * integrity is expected to verify on Android for each algorithm in
    143      * {@code contentDigestAlgorithms}.
    144      *
    145      * <p>The reason this method is currently not parameterized by a
    146      * {@code [minSdkVersion, maxSdkVersion]} range is that up until now content digest algorithms
    147      * exhibit the same behavior on all Android platform versions.
    148      */
    149     public static void verifyIntegrity(
    150             DataSource beforeApkSigningBlock,
    151             DataSource centralDir,
    152             ByteBuffer eocd,
    153             Set<ContentDigestAlgorithm> contentDigestAlgorithms,
    154             Result result) throws IOException, NoSuchAlgorithmException {
    155         if (contentDigestAlgorithms.isEmpty()) {
    156             // This should never occur because this method is invoked once at least one signature
    157             // is verified, meaning at least one content digest is known.
    158             throw new RuntimeException("No content digests found");
    159         }
    160 
    161         // For the purposes of verifying integrity, ZIP End of Central Directory (EoCD) must be
    162         // treated as though its Central Directory offset points to the start of APK Signing Block.
    163         // We thus modify the EoCD accordingly.
    164         ByteBuffer modifiedEocd = ByteBuffer.allocate(eocd.remaining());
    165         int eocdSavedPos = eocd.position();
    166         modifiedEocd.order(ByteOrder.LITTLE_ENDIAN);
    167         modifiedEocd.put(eocd);
    168         modifiedEocd.flip();
    169 
    170         // restore eocd to position prior to modification in case it is to be used elsewhere
    171         eocd.position(eocdSavedPos);
    172         ZipUtils.setZipEocdCentralDirectoryOffset(modifiedEocd, beforeApkSigningBlock.size());
    173         Map<ContentDigestAlgorithm, byte[]> actualContentDigests;
    174         try {
    175             actualContentDigests =
    176                     computeContentDigests(
    177                             contentDigestAlgorithms,
    178                             beforeApkSigningBlock,
    179                             centralDir,
    180                             new ByteBufferDataSource(modifiedEocd));
    181         } catch (DigestException e) {
    182             throw new RuntimeException("Failed to compute content digests", e);
    183         }
    184         if (!contentDigestAlgorithms.equals(actualContentDigests.keySet())) {
    185             throw new RuntimeException(
    186                     "Mismatch between sets of requested and computed content digests"
    187                             + " . Requested: " + contentDigestAlgorithms
    188                             + ", computed: " + actualContentDigests.keySet());
    189         }
    190 
    191         // Compare digests computed over the rest of APK against the corresponding expected digests
    192         // in signer blocks.
    193         for (Result.SignerInfo signerInfo : result.signers) {
    194             for (Result.SignerInfo.ContentDigest expected : signerInfo.contentDigests) {
    195                 SignatureAlgorithm signatureAlgorithm =
    196                         SignatureAlgorithm.findById(expected.getSignatureAlgorithmId());
    197                 if (signatureAlgorithm == null) {
    198                     continue;
    199                 }
    200                 ContentDigestAlgorithm contentDigestAlgorithm =
    201                         signatureAlgorithm.getContentDigestAlgorithm();
    202                 byte[] expectedDigest = expected.getValue();
    203                 byte[] actualDigest = actualContentDigests.get(contentDigestAlgorithm);
    204                 if (!Arrays.equals(expectedDigest, actualDigest)) {
    205                     if (result.signatureSchemeVersion == VERSION_APK_SIGNATURE_SCHEME_V2) {
    206                         signerInfo.addError(
    207                                 ApkVerifier.Issue.V2_SIG_APK_DIGEST_DID_NOT_VERIFY,
    208                                 contentDigestAlgorithm,
    209                                 toHex(expectedDigest),
    210                                 toHex(actualDigest));
    211                     } else if (result.signatureSchemeVersion == VERSION_APK_SIGNATURE_SCHEME_V3) {
    212                         signerInfo.addError(
    213                                 ApkVerifier.Issue.V3_SIG_APK_DIGEST_DID_NOT_VERIFY,
    214                                 contentDigestAlgorithm,
    215                                 toHex(expectedDigest),
    216                                 toHex(actualDigest));
    217                     }
    218                     continue;
    219                 }
    220                 signerInfo.verifiedContentDigests.put(contentDigestAlgorithm, actualDigest);
    221             }
    222         }
    223     }
    224 
    225     public static ByteBuffer findApkSignatureSchemeBlock(
    226             ByteBuffer apkSigningBlock,
    227             int blockId,
    228             Result result) throws SignatureNotFoundException {
    229         checkByteOrderLittleEndian(apkSigningBlock);
    230         // FORMAT:
    231         // OFFSET       DATA TYPE  DESCRIPTION
    232         // * @+0  bytes uint64:    size in bytes (excluding this field)
    233         // * @+8  bytes pairs
    234         // * @-24 bytes uint64:    size in bytes (same as the one above)
    235         // * @-16 bytes uint128:   magic
    236         ByteBuffer pairs = sliceFromTo(apkSigningBlock, 8, apkSigningBlock.capacity() - 24);
    237 
    238         int entryCount = 0;
    239         while (pairs.hasRemaining()) {
    240             entryCount++;
    241             if (pairs.remaining() < 8) {
    242                 throw new SignatureNotFoundException(
    243                         "Insufficient data to read size of APK Signing Block entry #" + entryCount);
    244             }
    245             long lenLong = pairs.getLong();
    246             if ((lenLong < 4) || (lenLong > Integer.MAX_VALUE)) {
    247                 throw new SignatureNotFoundException(
    248                         "APK Signing Block entry #" + entryCount
    249                                 + " size out of range: " + lenLong);
    250             }
    251             int len = (int) lenLong;
    252             int nextEntryPos = pairs.position() + len;
    253             if (len > pairs.remaining()) {
    254                 throw new SignatureNotFoundException(
    255                         "APK Signing Block entry #" + entryCount + " size out of range: " + len
    256                                 + ", available: " + pairs.remaining());
    257             }
    258             int id = pairs.getInt();
    259             if (id == blockId) {
    260                 return getByteBuffer(pairs, len - 4);
    261             }
    262             pairs.position(nextEntryPos);
    263         }
    264 
    265         throw new SignatureNotFoundException(
    266                 "No APK Signature Scheme block in APK Signing Block with ID: " + blockId);
    267     }
    268 
    269     public static void checkByteOrderLittleEndian(ByteBuffer buffer) {
    270         if (buffer.order() != ByteOrder.LITTLE_ENDIAN) {
    271             throw new IllegalArgumentException("ByteBuffer byte order must be little endian");
    272         }
    273     }
    274 
    275     /**
    276      * Returns new byte buffer whose content is a shared subsequence of this buffer's content
    277      * between the specified start (inclusive) and end (exclusive) positions. As opposed to
    278      * {@link ByteBuffer#slice()}, the returned buffer's byte order is the same as the source
    279      * buffer's byte order.
    280      */
    281     private static ByteBuffer sliceFromTo(ByteBuffer source, int start, int end) {
    282         if (start < 0) {
    283             throw new IllegalArgumentException("start: " + start);
    284         }
    285         if (end < start) {
    286             throw new IllegalArgumentException("end < start: " + end + " < " + start);
    287         }
    288         int capacity = source.capacity();
    289         if (end > source.capacity()) {
    290             throw new IllegalArgumentException("end > capacity: " + end + " > " + capacity);
    291         }
    292         int originalLimit = source.limit();
    293         int originalPosition = source.position();
    294         try {
    295             source.position(0);
    296             source.limit(end);
    297             source.position(start);
    298             ByteBuffer result = source.slice();
    299             result.order(source.order());
    300             return result;
    301         } finally {
    302             source.position(0);
    303             source.limit(originalLimit);
    304             source.position(originalPosition);
    305         }
    306     }
    307 
    308     /**
    309      * Relative <em>get</em> method for reading {@code size} number of bytes from the current
    310      * position of this buffer.
    311      *
    312      * <p>This method reads the next {@code size} bytes at this buffer's current position,
    313      * returning them as a {@code ByteBuffer} with start set to 0, limit and capacity set to
    314      * {@code size}, byte order set to this buffer's byte order; and then increments the position by
    315      * {@code size}.
    316      */
    317     private static ByteBuffer getByteBuffer(ByteBuffer source, int size) {
    318         if (size < 0) {
    319             throw new IllegalArgumentException("size: " + size);
    320         }
    321         int originalLimit = source.limit();
    322         int position = source.position();
    323         int limit = position + size;
    324         if ((limit < position) || (limit > originalLimit)) {
    325             throw new BufferUnderflowException();
    326         }
    327         source.limit(limit);
    328         try {
    329             ByteBuffer result = source.slice();
    330             result.order(source.order());
    331             source.position(limit);
    332             return result;
    333         } finally {
    334             source.limit(originalLimit);
    335         }
    336     }
    337 
    338     public static ByteBuffer getLengthPrefixedSlice(ByteBuffer source) throws ApkFormatException {
    339         if (source.remaining() < 4) {
    340             throw new ApkFormatException(
    341                     "Remaining buffer too short to contain length of length-prefixed field"
    342                             + ". Remaining: " + source.remaining());
    343         }
    344         int len = source.getInt();
    345         if (len < 0) {
    346             throw new IllegalArgumentException("Negative length");
    347         } else if (len > source.remaining()) {
    348             throw new ApkFormatException(
    349                     "Length-prefixed field longer than remaining buffer"
    350                             + ". Field length: " + len + ", remaining: " + source.remaining());
    351         }
    352         return getByteBuffer(source, len);
    353     }
    354 
    355     public static byte[] readLengthPrefixedByteArray(ByteBuffer buf) throws ApkFormatException {
    356         int len = buf.getInt();
    357         if (len < 0) {
    358             throw new ApkFormatException("Negative length");
    359         } else if (len > buf.remaining()) {
    360             throw new ApkFormatException(
    361                     "Underflow while reading length-prefixed value. Length: " + len
    362                             + ", available: " + buf.remaining());
    363         }
    364         byte[] result = new byte[len];
    365         buf.get(result);
    366         return result;
    367     }
    368 
    369     public static String toHex(byte[] value) {
    370         StringBuilder sb = new StringBuilder(value.length * 2);
    371         int len = value.length;
    372         for (int i = 0; i < len; i++) {
    373             int hi = (value[i] & 0xff) >>> 4;
    374             int lo = value[i] & 0x0f;
    375             sb.append(HEX_DIGITS[hi]).append(HEX_DIGITS[lo]);
    376         }
    377         return sb.toString();
    378     }
    379 
    380     public static Map<ContentDigestAlgorithm, byte[]> computeContentDigests(
    381             Set<ContentDigestAlgorithm> digestAlgorithms,
    382             DataSource beforeCentralDir,
    383             DataSource centralDir,
    384             DataSource eocd) throws IOException, NoSuchAlgorithmException, DigestException {
    385         Map<ContentDigestAlgorithm, byte[]> contentDigests = new HashMap<>();
    386         Set<ContentDigestAlgorithm> oneMbChunkBasedAlgorithm = digestAlgorithms.stream()
    387                 .filter(a -> a == ContentDigestAlgorithm.CHUNKED_SHA256 ||
    388                              a == ContentDigestAlgorithm.CHUNKED_SHA512)
    389                 .collect(Collectors.toSet());
    390         computeOneMbChunkContentDigests(oneMbChunkBasedAlgorithm,
    391                 new DataSource[] { beforeCentralDir, centralDir, eocd },
    392                 contentDigests);
    393 
    394         if (digestAlgorithms.contains(ContentDigestAlgorithm.VERITY_CHUNKED_SHA256)) {
    395             computeApkVerityDigest(beforeCentralDir, centralDir, eocd, contentDigests);
    396         }
    397         return contentDigests;
    398     }
    399 
    400     private static void computeOneMbChunkContentDigests(
    401             Set<ContentDigestAlgorithm> digestAlgorithms,
    402             DataSource[] contents,
    403             Map<ContentDigestAlgorithm, byte[]> outputContentDigests)
    404             throws IOException, NoSuchAlgorithmException, DigestException {
    405         // For each digest algorithm the result is computed as follows:
    406         // 1. Each segment of contents is split into consecutive chunks of 1 MB in size.
    407         //    The final chunk will be shorter iff the length of segment is not a multiple of 1 MB.
    408         //    No chunks are produced for empty (zero length) segments.
    409         // 2. The digest of each chunk is computed over the concatenation of byte 0xa5, the chunk's
    410         //    length in bytes (uint32 little-endian) and the chunk's contents.
    411         // 3. The output digest is computed over the concatenation of the byte 0x5a, the number of
    412         //    chunks (uint32 little-endian) and the concatenation of digests of chunks of all
    413         //    segments in-order.
    414 
    415         long chunkCountLong = 0;
    416         for (DataSource input : contents) {
    417             chunkCountLong +=
    418                     getChunkCount(input.size(), CONTENT_DIGESTED_CHUNK_MAX_SIZE_BYTES);
    419         }
    420         if (chunkCountLong > Integer.MAX_VALUE) {
    421             throw new DigestException("Input too long: " + chunkCountLong + " chunks");
    422         }
    423         int chunkCount = (int) chunkCountLong;
    424 
    425         ContentDigestAlgorithm[] digestAlgorithmsArray =
    426                 digestAlgorithms.toArray(new ContentDigestAlgorithm[digestAlgorithms.size()]);
    427         MessageDigest[] mds = new MessageDigest[digestAlgorithmsArray.length];
    428         byte[][] digestsOfChunks = new byte[digestAlgorithmsArray.length][];
    429         int[] digestOutputSizes = new int[digestAlgorithmsArray.length];
    430         for (int i = 0; i < digestAlgorithmsArray.length; i++) {
    431             ContentDigestAlgorithm digestAlgorithm = digestAlgorithmsArray[i];
    432             int digestOutputSizeBytes = digestAlgorithm.getChunkDigestOutputSizeBytes();
    433             digestOutputSizes[i] = digestOutputSizeBytes;
    434             byte[] concatenationOfChunkCountAndChunkDigests =
    435                     new byte[5 + chunkCount * digestOutputSizeBytes];
    436             concatenationOfChunkCountAndChunkDigests[0] = 0x5a;
    437             setUnsignedInt32LittleEndian(
    438                     chunkCount, concatenationOfChunkCountAndChunkDigests, 1);
    439             digestsOfChunks[i] = concatenationOfChunkCountAndChunkDigests;
    440             String jcaAlgorithm = digestAlgorithm.getJcaMessageDigestAlgorithm();
    441             mds[i] = MessageDigest.getInstance(jcaAlgorithm);
    442         }
    443 
    444         DataSink mdSink = DataSinks.asDataSink(mds);
    445         byte[] chunkContentPrefix = new byte[5];
    446         chunkContentPrefix[0] = (byte) 0xa5;
    447         int chunkIndex = 0;
    448         // Optimization opportunity: digests of chunks can be computed in parallel. However,
    449         // determining the number of computations to be performed in parallel is non-trivial. This
    450         // depends on a wide range of factors, such as data source type (e.g., in-memory or fetched
    451         // from file), CPU/memory/disk cache bandwidth and latency, interconnect architecture of CPU
    452         // cores, load on the system from other threads of execution and other processes, size of
    453         // input.
    454         // For now, we compute these digests sequentially and thus have the luxury of improving
    455         // performance by writing the digest of each chunk into a pre-allocated buffer at exactly
    456         // the right position. This avoids unnecessary allocations, copying, and enables the final
    457         // digest to be more efficient because it's presented with all of its input in one go.
    458         for (DataSource input : contents) {
    459             long inputOffset = 0;
    460             long inputRemaining = input.size();
    461             while (inputRemaining > 0) {
    462                 int chunkSize =
    463                         (int) Math.min(inputRemaining, CONTENT_DIGESTED_CHUNK_MAX_SIZE_BYTES);
    464                 setUnsignedInt32LittleEndian(chunkSize, chunkContentPrefix, 1);
    465                 for (int i = 0; i < mds.length; i++) {
    466                     mds[i].update(chunkContentPrefix);
    467                 }
    468                 try {
    469                     input.feed(inputOffset, chunkSize, mdSink);
    470                 } catch (IOException e) {
    471                     throw new IOException("Failed to read chunk #" + chunkIndex, e);
    472                 }
    473                 for (int i = 0; i < digestAlgorithmsArray.length; i++) {
    474                     MessageDigest md = mds[i];
    475                     byte[] concatenationOfChunkCountAndChunkDigests = digestsOfChunks[i];
    476                     int expectedDigestSizeBytes = digestOutputSizes[i];
    477                     int actualDigestSizeBytes =
    478                             md.digest(
    479                                     concatenationOfChunkCountAndChunkDigests,
    480                                     5 + chunkIndex * expectedDigestSizeBytes,
    481                                     expectedDigestSizeBytes);
    482                     if (actualDigestSizeBytes != expectedDigestSizeBytes) {
    483                         throw new RuntimeException(
    484                                 "Unexpected output size of " + md.getAlgorithm()
    485                                         + " digest: " + actualDigestSizeBytes);
    486                     }
    487                 }
    488                 inputOffset += chunkSize;
    489                 inputRemaining -= chunkSize;
    490                 chunkIndex++;
    491             }
    492         }
    493 
    494         for (int i = 0; i < digestAlgorithmsArray.length; i++) {
    495             ContentDigestAlgorithm digestAlgorithm = digestAlgorithmsArray[i];
    496             byte[] concatenationOfChunkCountAndChunkDigests = digestsOfChunks[i];
    497             MessageDigest md = mds[i];
    498             byte[] digest = md.digest(concatenationOfChunkCountAndChunkDigests);
    499             outputContentDigests.put(digestAlgorithm, digest);
    500         }
    501     }
    502 
    503     private static void computeApkVerityDigest(DataSource beforeCentralDir, DataSource centralDir,
    504             DataSource eocd, Map<ContentDigestAlgorithm, byte[]> outputContentDigests)
    505             throws IOException, NoSuchAlgorithmException {
    506         // FORMAT:
    507         // OFFSET       DATA TYPE  DESCRIPTION
    508         // * @+0  bytes uint8[32]  Merkle tree root hash of SHA-256
    509         // * @+32 bytes int64      Length of source data
    510         int backBufferSize =
    511                 ContentDigestAlgorithm.VERITY_CHUNKED_SHA256.getChunkDigestOutputSizeBytes() +
    512                 Long.SIZE / Byte.SIZE;
    513         ByteBuffer encoded = ByteBuffer.allocate(backBufferSize);
    514         encoded.order(ByteOrder.LITTLE_ENDIAN);
    515 
    516         // Use 0s as salt for now.  This also needs to be consistent in the fsverify header for
    517         // kernel to use.
    518         VerityTreeBuilder builder = new VerityTreeBuilder(new byte[8]);
    519         byte[] rootHash = builder.generateVerityTreeRootHash(beforeCentralDir, centralDir, eocd);
    520         encoded.put(rootHash);
    521         encoded.putLong(beforeCentralDir.size() + centralDir.size() + eocd.size());
    522 
    523         outputContentDigests.put(ContentDigestAlgorithm.VERITY_CHUNKED_SHA256, encoded.array());
    524     }
    525 
    526     private static final long getChunkCount(long inputSize, int chunkSize) {
    527         return (inputSize + chunkSize - 1) / chunkSize;
    528     }
    529 
    530     private static void setUnsignedInt32LittleEndian(int value, byte[] result, int offset) {
    531         result[offset] = (byte) (value & 0xff);
    532         result[offset + 1] = (byte) ((value >> 8) & 0xff);
    533         result[offset + 2] = (byte) ((value >> 16) & 0xff);
    534         result[offset + 3] = (byte) ((value >> 24) & 0xff);
    535     }
    536 
    537     public static byte[] encodePublicKey(PublicKey publicKey)
    538             throws InvalidKeyException, NoSuchAlgorithmException {
    539         byte[] encodedPublicKey = null;
    540         if ("X.509".equals(publicKey.getFormat())) {
    541             encodedPublicKey = publicKey.getEncoded();
    542         }
    543         if (encodedPublicKey == null) {
    544             try {
    545                 encodedPublicKey =
    546                         KeyFactory.getInstance(publicKey.getAlgorithm())
    547                                 .getKeySpec(publicKey, X509EncodedKeySpec.class)
    548                                 .getEncoded();
    549             } catch (InvalidKeySpecException e) {
    550                 throw new InvalidKeyException(
    551                         "Failed to obtain X.509 encoded form of public key " + publicKey
    552                                 + " of class " + publicKey.getClass().getName(),
    553                         e);
    554             }
    555         }
    556         if ((encodedPublicKey == null) || (encodedPublicKey.length == 0)) {
    557             throw new InvalidKeyException(
    558                     "Failed to obtain X.509 encoded form of public key " + publicKey
    559                             + " of class " + publicKey.getClass().getName());
    560         }
    561         return encodedPublicKey;
    562     }
    563 
    564     public static List<byte[]> encodeCertificates(List<X509Certificate> certificates)
    565             throws CertificateEncodingException {
    566         List<byte[]> result = new ArrayList<>(certificates.size());
    567         for (X509Certificate certificate : certificates) {
    568             result.add(certificate.getEncoded());
    569         }
    570         return result;
    571     }
    572 
    573     public static byte[] encodeAsLengthPrefixedElement(byte[] bytes) {
    574         byte[][] adapterBytes = new byte[1][];
    575         adapterBytes[0] = bytes;
    576         return encodeAsSequenceOfLengthPrefixedElements(adapterBytes);
    577     }
    578 
    579     public static byte[] encodeAsSequenceOfLengthPrefixedElements(List<byte[]> sequence) {
    580         return encodeAsSequenceOfLengthPrefixedElements(
    581                 sequence.toArray(new byte[sequence.size()][]));
    582     }
    583 
    584     public static byte[] encodeAsSequenceOfLengthPrefixedElements(byte[][] sequence) {
    585         int payloadSize = 0;
    586         for (byte[] element : sequence) {
    587             payloadSize += 4 + element.length;
    588         }
    589         ByteBuffer result = ByteBuffer.allocate(payloadSize);
    590         result.order(ByteOrder.LITTLE_ENDIAN);
    591         for (byte[] element : sequence) {
    592             result.putInt(element.length);
    593             result.put(element);
    594         }
    595         return result.array();
    596       }
    597 
    598     public static byte[] encodeAsSequenceOfLengthPrefixedPairsOfIntAndLengthPrefixedBytes(
    599             List<Pair<Integer, byte[]>> sequence) {
    600           int resultSize = 0;
    601           for (Pair<Integer, byte[]> element : sequence) {
    602               resultSize += 12 + element.getSecond().length;
    603           }
    604           ByteBuffer result = ByteBuffer.allocate(resultSize);
    605           result.order(ByteOrder.LITTLE_ENDIAN);
    606           for (Pair<Integer, byte[]> element : sequence) {
    607               byte[] second = element.getSecond();
    608               result.putInt(8 + second.length);
    609               result.putInt(element.getFirst());
    610               result.putInt(second.length);
    611               result.put(second);
    612           }
    613           return result.array();
    614       }
    615 
    616     /**
    617      * Returns the APK Signature Scheme block contained in the provided APK file for the given ID
    618      * and the additional information relevant for verifying the block against the file.
    619      *
    620      * @param blockId the ID value in the APK Signing Block's sequence of ID-value pairs
    621      *                identifying the appropriate block to find, e.g. the APK Signature Scheme v2
    622      *                block ID.
    623      *
    624      * @throws SignatureNotFoundException if the APK is not signed using given APK Signature Scheme
    625      * @throws IOException if an I/O error occurs while reading the APK
    626      */
    627     public static SignatureInfo findSignature(
    628             DataSource apk, ApkUtils.ZipSections zipSections, int blockId, Result result)
    629                     throws IOException, SignatureNotFoundException {
    630         // Find the APK Signing Block.
    631         DataSource apkSigningBlock;
    632         long apkSigningBlockOffset;
    633         try {
    634             ApkUtils.ApkSigningBlock apkSigningBlockInfo =
    635                     ApkUtils.findApkSigningBlock(apk, zipSections);
    636             apkSigningBlockOffset = apkSigningBlockInfo.getStartOffset();
    637             apkSigningBlock = apkSigningBlockInfo.getContents();
    638         } catch (ApkSigningBlockNotFoundException e) {
    639             throw new SignatureNotFoundException(e.getMessage(), e);
    640         }
    641         ByteBuffer apkSigningBlockBuf =
    642                 apkSigningBlock.getByteBuffer(0, (int) apkSigningBlock.size());
    643         apkSigningBlockBuf.order(ByteOrder.LITTLE_ENDIAN);
    644 
    645         // Find the APK Signature Scheme Block inside the APK Signing Block.
    646         ByteBuffer apkSignatureSchemeBlock =
    647                 findApkSignatureSchemeBlock(apkSigningBlockBuf, blockId, result);
    648         return new SignatureInfo(
    649                 apkSignatureSchemeBlock,
    650                 apkSigningBlockOffset,
    651                 zipSections.getZipCentralDirectoryOffset(),
    652                 zipSections.getZipEndOfCentralDirectoryOffset(),
    653                 zipSections.getZipEndOfCentralDirectory());
    654     }
    655 
    656     /**
    657      * Generates a new DataSource representing the APK contents before the Central Directory with
    658      * padding, if padding is requested.  If the existing data entries before the Central Directory
    659      * are already aligned, or no padding is requested, the original DataSource is used.  This
    660      * padding is used to allow for verity-based APK verification.
    661      *
    662      * @return {@code Pair} containing the potentially new {@code DataSource} and the amount of
    663      *         padding used.
    664      */
    665     public static Pair<DataSource, Integer> generateApkSigningBlockPadding(
    666             DataSource beforeCentralDir,
    667             boolean apkSigningBlockPaddingSupported) {
    668 
    669         // Ensure APK Signing Block starts from page boundary.
    670         int padSizeBeforeSigningBlock = 0;
    671         if (apkSigningBlockPaddingSupported &&
    672                 (beforeCentralDir.size() % ANDROID_COMMON_PAGE_ALIGNMENT_BYTES != 0)) {
    673             padSizeBeforeSigningBlock = (int) (
    674                     ANDROID_COMMON_PAGE_ALIGNMENT_BYTES -
    675                             beforeCentralDir.size() % ANDROID_COMMON_PAGE_ALIGNMENT_BYTES);
    676             beforeCentralDir = new ChainedDataSource(
    677                     beforeCentralDir,
    678                     DataSources.asDataSource(
    679                             ByteBuffer.allocate(padSizeBeforeSigningBlock)));
    680         }
    681         return Pair.of(beforeCentralDir, padSizeBeforeSigningBlock);
    682     }
    683 
    684     public static DataSource copyWithModifiedCDOffset(
    685             DataSource beforeCentralDir, DataSource eocd) throws IOException {
    686 
    687         // Ensure that, when digesting, ZIP End of Central Directory record's Central Directory
    688         // offset field is treated as pointing to the offset at which the APK Signing Block will
    689         // start.
    690         long centralDirOffsetForDigesting = beforeCentralDir.size();
    691         ByteBuffer eocdBuf = ByteBuffer.allocate((int) eocd.size());
    692         eocdBuf.order(ByteOrder.LITTLE_ENDIAN);
    693         eocd.copyTo(0, (int) eocd.size(), eocdBuf);
    694         eocdBuf.flip();
    695         ZipUtils.setZipEocdCentralDirectoryOffset(eocdBuf, centralDirOffsetForDigesting);
    696         return DataSources.asDataSource(eocdBuf);
    697     }
    698 
    699     public static byte[] generateApkSigningBlock(
    700             List<Pair<byte[], Integer>> apkSignatureSchemeBlockPairs) {
    701         // FORMAT:
    702         // uint64:  size (excluding this field)
    703         // repeated ID-value pairs:
    704         //     uint64:           size (excluding this field)
    705         //     uint32:           ID
    706         //     (size - 4) bytes: value
    707         // (extra dummy ID-value for padding to make block size a multiple of 4096 bytes)
    708         // uint64:  size (same as the one above)
    709         // uint128: magic
    710 
    711         int blocksSize = 0;
    712         for (Pair<byte[], Integer> schemeBlockPair : apkSignatureSchemeBlockPairs) {
    713             blocksSize += 8 + 4 + schemeBlockPair.getFirst().length; // size + id + value
    714         }
    715 
    716         int resultSize =
    717                 8 // size
    718                 + blocksSize
    719                 + 8 // size
    720                 + 16 // magic
    721                 ;
    722         ByteBuffer paddingPair = null;
    723         if (resultSize % ANDROID_COMMON_PAGE_ALIGNMENT_BYTES != 0) {
    724             int padding = ANDROID_COMMON_PAGE_ALIGNMENT_BYTES -
    725                     (resultSize % ANDROID_COMMON_PAGE_ALIGNMENT_BYTES);
    726             if (padding < 12) {  // minimum size of an ID-value pair
    727                 padding += ANDROID_COMMON_PAGE_ALIGNMENT_BYTES;
    728             }
    729             paddingPair = ByteBuffer.allocate(padding).order(ByteOrder.LITTLE_ENDIAN);
    730             paddingPair.putLong(padding - 8);
    731             paddingPair.putInt(VERITY_PADDING_BLOCK_ID);
    732             paddingPair.rewind();
    733             resultSize += padding;
    734         }
    735 
    736         ByteBuffer result = ByteBuffer.allocate(resultSize);
    737         result.order(ByteOrder.LITTLE_ENDIAN);
    738         long blockSizeFieldValue = resultSize - 8L;
    739         result.putLong(blockSizeFieldValue);
    740 
    741 
    742         for (Pair<byte[], Integer> schemeBlockPair : apkSignatureSchemeBlockPairs) {
    743             byte[] apkSignatureSchemeBlock = schemeBlockPair.getFirst();
    744             int apkSignatureSchemeId = schemeBlockPair.getSecond();
    745             long pairSizeFieldValue = 4L + apkSignatureSchemeBlock.length;
    746             result.putLong(pairSizeFieldValue);
    747             result.putInt(apkSignatureSchemeId);
    748             result.put(apkSignatureSchemeBlock);
    749         }
    750 
    751         if (paddingPair != null) {
    752             result.put(paddingPair);
    753         }
    754 
    755         result.putLong(blockSizeFieldValue);
    756         result.put(APK_SIGNING_BLOCK_MAGIC);
    757 
    758         return result.array();
    759     }
    760 
    761     /**
    762      * Computes the digests of the given APK components according to the algorithms specified in the
    763      * given SignerConfigs.
    764      *
    765      * @param signerConfigs signer configurations, one for each signer At least one signer config
    766      *        must be provided.
    767      *
    768      * @throws IOException if an I/O error occurs
    769      * @throws NoSuchAlgorithmException if a required cryptographic algorithm implementation is
    770      *         missing
    771      * @throws SignatureException if an error occurs when computing digests of generating
    772      *         signatures
    773      */
    774     public static Pair<List<SignerConfig>, Map<ContentDigestAlgorithm, byte[]>>
    775             computeContentDigests(
    776                     DataSource beforeCentralDir,
    777                     DataSource centralDir,
    778                     DataSource eocd,
    779                     List<SignerConfig> signerConfigs)
    780                             throws IOException, NoSuchAlgorithmException, SignatureException {
    781         if (signerConfigs.isEmpty()) {
    782             throw new IllegalArgumentException(
    783                     "No signer configs provided. At least one is required");
    784         }
    785 
    786         // Figure out which digest(s) to use for APK contents.
    787         Set<ContentDigestAlgorithm> contentDigestAlgorithms = new HashSet<>(1);
    788         for (SignerConfig signerConfig : signerConfigs) {
    789             for (SignatureAlgorithm signatureAlgorithm : signerConfig.signatureAlgorithms) {
    790                 contentDigestAlgorithms.add(signatureAlgorithm.getContentDigestAlgorithm());
    791             }
    792         }
    793 
    794         // Compute digests of APK contents.
    795         Map<ContentDigestAlgorithm, byte[]> contentDigests; // digest algorithm ID -> digest
    796         try {
    797             contentDigests =
    798                     computeContentDigests(
    799                             contentDigestAlgorithms,
    800                             beforeCentralDir,
    801                             centralDir,
    802                             eocd);
    803         } catch (IOException e) {
    804             throw new IOException("Failed to read APK being signed", e);
    805         } catch (DigestException e) {
    806             throw new SignatureException("Failed to compute digests of APK", e);
    807         }
    808 
    809         // Sign the digests and wrap the signatures and signer info into an APK Signing Block.
    810         return Pair.of(signerConfigs, contentDigests);
    811     }
    812 
    813     /**
    814      * Returns the subset of signatures which are expected to be verified by at least one Android
    815      * platform version in the {@code [minSdkVersion, maxSdkVersion]} range. The returned result is
    816      * guaranteed to contain at least one signature.
    817      *
    818      * <p>Each Android platform version typically verifies exactly one signature from the provided
    819      * {@code signatures} set. This method returns the set of these signatures collected over all
    820      * requested platform versions. As a result, the result may contain more than one signature.
    821      *
    822      * @throws NoSupportedSignaturesException if no supported signatures were
    823      *         found for an Android platform version in the range.
    824      */
    825     public static List<SupportedSignature> getSignaturesToVerify(
    826             List<SupportedSignature> signatures, int minSdkVersion, int maxSdkVersion)
    827             throws NoSupportedSignaturesException {
    828         // Pick the signature with the strongest algorithm at all required SDK versions, to mimic
    829         // Android's behavior on those versions.
    830         //
    831         // Here we assume that, once introduced, a signature algorithm continues to be supported in
    832         // all future Android versions. We also assume that the better-than relationship between
    833         // algorithms is exactly the same on all Android platform versions (except that older
    834         // platforms might support fewer algorithms). If these assumption are no longer true, the
    835         // logic here will need to change accordingly.
    836         Map<Integer, SupportedSignature> bestSigAlgorithmOnSdkVersion = new HashMap<>();
    837         int minProvidedSignaturesVersion = Integer.MAX_VALUE;
    838         for (SupportedSignature sig : signatures) {
    839             SignatureAlgorithm sigAlgorithm = sig.algorithm;
    840             int sigMinSdkVersion = sigAlgorithm.getMinSdkVersion();
    841             if (sigMinSdkVersion > maxSdkVersion) {
    842                 continue;
    843             }
    844             if (sigMinSdkVersion < minProvidedSignaturesVersion) {
    845                 minProvidedSignaturesVersion = sigMinSdkVersion;
    846             }
    847 
    848             SupportedSignature candidate = bestSigAlgorithmOnSdkVersion.get(sigMinSdkVersion);
    849             if ((candidate == null)
    850                     || (compareSignatureAlgorithm(
    851                             sigAlgorithm, candidate.algorithm) > 0)) {
    852                 bestSigAlgorithmOnSdkVersion.put(sigMinSdkVersion, sig);
    853             }
    854         }
    855 
    856         // Must have some supported signature algorithms for minSdkVersion.
    857         if (minSdkVersion < minProvidedSignaturesVersion) {
    858             throw new NoSupportedSignaturesException(
    859                     "Minimum provided signature version " + minProvidedSignaturesVersion +
    860                     " < minSdkVersion " + minSdkVersion);
    861         }
    862         if (bestSigAlgorithmOnSdkVersion.isEmpty()) {
    863             throw new NoSupportedSignaturesException("No supported signature");
    864         }
    865         return bestSigAlgorithmOnSdkVersion.values().stream()
    866                 .sorted((sig1, sig2) -> Integer.compare(
    867                         sig1.algorithm.getId(), sig2.algorithm.getId()))
    868                 .collect(Collectors.toList());
    869     }
    870 
    871     public static class NoSupportedSignaturesException extends Exception {
    872         private static final long serialVersionUID = 1L;
    873 
    874         public NoSupportedSignaturesException(String message) {
    875             super(message);
    876         }
    877     }
    878 
    879     public static class SignatureNotFoundException extends Exception {
    880         private static final long serialVersionUID = 1L;
    881 
    882         public SignatureNotFoundException(String message) {
    883             super(message);
    884         }
    885 
    886         public SignatureNotFoundException(String message, Throwable cause) {
    887             super(message, cause);
    888         }
    889     }
    890 
    891     /**
    892      * uses the SignatureAlgorithms in the provided signerConfig to sign the provided data
    893      *
    894      * @return list of signature algorithm IDs and their corresponding signatures over the data.
    895      */
    896     public static List<Pair<Integer, byte[]>> generateSignaturesOverData(
    897             SignerConfig signerConfig, byte[] data)
    898                     throws InvalidKeyException, NoSuchAlgorithmException, SignatureException {
    899         List<Pair<Integer, byte[]>> signatures =
    900                 new ArrayList<>(signerConfig.signatureAlgorithms.size());
    901         PublicKey publicKey = signerConfig.certificates.get(0).getPublicKey();
    902         for (SignatureAlgorithm signatureAlgorithm : signerConfig.signatureAlgorithms) {
    903             Pair<String, ? extends AlgorithmParameterSpec> sigAlgAndParams =
    904                     signatureAlgorithm.getJcaSignatureAlgorithmAndParams();
    905             String jcaSignatureAlgorithm = sigAlgAndParams.getFirst();
    906             AlgorithmParameterSpec jcaSignatureAlgorithmParams = sigAlgAndParams.getSecond();
    907             byte[] signatureBytes;
    908             try {
    909                 Signature signature = Signature.getInstance(jcaSignatureAlgorithm);
    910                 signature.initSign(signerConfig.privateKey);
    911                 if (jcaSignatureAlgorithmParams != null) {
    912                     signature.setParameter(jcaSignatureAlgorithmParams);
    913                 }
    914                 signature.update(data);
    915                 signatureBytes = signature.sign();
    916             } catch (InvalidKeyException e) {
    917                 throw new InvalidKeyException("Failed to sign using " + jcaSignatureAlgorithm, e);
    918             } catch (InvalidAlgorithmParameterException | SignatureException e) {
    919                 throw new SignatureException("Failed to sign using " + jcaSignatureAlgorithm, e);
    920             }
    921 
    922             try {
    923                 Signature signature = Signature.getInstance(jcaSignatureAlgorithm);
    924                 signature.initVerify(publicKey);
    925                 if (jcaSignatureAlgorithmParams != null) {
    926                     signature.setParameter(jcaSignatureAlgorithmParams);
    927                 }
    928                 signature.update(data);
    929                 if (!signature.verify(signatureBytes)) {
    930                     throw new SignatureException("Failed to verify generated "
    931                             + jcaSignatureAlgorithm
    932                             + " signature using public key from certificate");
    933                 }
    934             } catch (InvalidKeyException e) {
    935                 throw new InvalidKeyException(
    936                         "Failed to verify generated " + jcaSignatureAlgorithm + " signature using"
    937                                 + " public key from certificate", e);
    938             } catch (InvalidAlgorithmParameterException | SignatureException e) {
    939                 throw new SignatureException(
    940                         "Failed to verify generated " + jcaSignatureAlgorithm + " signature using"
    941                                 + " public key from certificate", e);
    942             }
    943 
    944             signatures.add(Pair.of(signatureAlgorithm.getId(), signatureBytes));
    945         }
    946         return signatures;
    947     }
    948 
    949     /**
    950      * Signer configuration.
    951      */
    952     public static class SignerConfig {
    953         /** Private key. */
    954         public PrivateKey privateKey;
    955 
    956         /**
    957          * Certificates, with the first certificate containing the public key corresponding to
    958          * {@link #privateKey}.
    959          */
    960         public List<X509Certificate> certificates;
    961 
    962         /**
    963          * List of signature algorithms with which to sign.
    964          */
    965         public List<SignatureAlgorithm> signatureAlgorithms;
    966 
    967         public int minSdkVersion;
    968         public int maxSdkVersion;
    969         public SigningCertificateLineage mSigningCertificateLineage;
    970     }
    971 
    972     public static class Result {
    973         public final int signatureSchemeVersion;
    974 
    975         /** Whether the APK's APK Signature Scheme signature verifies. */
    976         public boolean verified;
    977 
    978         public final List<SignerInfo> signers = new ArrayList<>();
    979         public SigningCertificateLineage signingCertificateLineage = null;
    980         private final List<ApkVerifier.IssueWithParams> mWarnings = new ArrayList<>();
    981         private final List<ApkVerifier.IssueWithParams> mErrors = new ArrayList<>();
    982 
    983         public Result(int signatureSchemeVersion) {
    984             this.signatureSchemeVersion = signatureSchemeVersion;
    985         }
    986 
    987         public boolean containsErrors() {
    988             if (!mErrors.isEmpty()) {
    989                 return true;
    990             }
    991             if (!signers.isEmpty()) {
    992                 for (SignerInfo signer : signers) {
    993                     if (signer.containsErrors()) {
    994                         return true;
    995                     }
    996                 }
    997             }
    998             return false;
    999         }
   1000 
   1001         public void addError(ApkVerifier.Issue msg, Object... parameters) {
   1002             mErrors.add(new ApkVerifier.IssueWithParams(msg, parameters));
   1003         }
   1004 
   1005         public void addWarning(ApkVerifier.Issue msg, Object... parameters) {
   1006             mWarnings.add(new ApkVerifier.IssueWithParams(msg, parameters));
   1007         }
   1008 
   1009         public List<ApkVerifier.IssueWithParams> getErrors() {
   1010             return mErrors;
   1011         }
   1012 
   1013         public List<ApkVerifier.IssueWithParams> getWarnings() {
   1014             return mWarnings;
   1015         }
   1016 
   1017         public static class SignerInfo {
   1018             public int index;
   1019             public List<X509Certificate> certs = new ArrayList<>();
   1020             public List<ContentDigest> contentDigests = new ArrayList<>();
   1021             public Map<ContentDigestAlgorithm, byte[]> verifiedContentDigests = new HashMap<>();
   1022             public List<Signature> signatures = new ArrayList<>();
   1023             public Map<SignatureAlgorithm, byte[]> verifiedSignatures = new HashMap<>();
   1024             public List<AdditionalAttribute> additionalAttributes = new ArrayList<>();
   1025             public byte[] signedData;
   1026             public int minSdkVersion;
   1027             public int maxSdkVersion;
   1028             public SigningCertificateLineage signingCertificateLineage;
   1029 
   1030             private final List<ApkVerifier.IssueWithParams> mWarnings = new ArrayList<>();
   1031             private final List<ApkVerifier.IssueWithParams> mErrors = new ArrayList<>();
   1032 
   1033             public void addError(ApkVerifier.Issue msg, Object... parameters) {
   1034                 mErrors.add(new ApkVerifier.IssueWithParams(msg, parameters));
   1035             }
   1036 
   1037             public void addWarning(ApkVerifier.Issue msg, Object... parameters) {
   1038                 mWarnings.add(new ApkVerifier.IssueWithParams(msg, parameters));
   1039             }
   1040 
   1041             public boolean containsErrors() {
   1042                 return !mErrors.isEmpty();
   1043             }
   1044 
   1045             public List<ApkVerifier.IssueWithParams> getErrors() {
   1046                 return mErrors;
   1047             }
   1048 
   1049             public List<ApkVerifier.IssueWithParams> getWarnings() {
   1050                 return mWarnings;
   1051             }
   1052 
   1053             public static class ContentDigest {
   1054                 private final int mSignatureAlgorithmId;
   1055                 private final byte[] mValue;
   1056 
   1057                 public ContentDigest(int signatureAlgorithmId, byte[] value) {
   1058                     mSignatureAlgorithmId  = signatureAlgorithmId;
   1059                     mValue = value;
   1060                 }
   1061 
   1062                 public int getSignatureAlgorithmId() {
   1063                     return mSignatureAlgorithmId;
   1064                 }
   1065 
   1066                 public byte[] getValue() {
   1067                     return mValue;
   1068                 }
   1069             }
   1070 
   1071             public static class Signature {
   1072                 private final int mAlgorithmId;
   1073                 private final byte[] mValue;
   1074 
   1075                 public Signature(int algorithmId, byte[] value) {
   1076                     mAlgorithmId  = algorithmId;
   1077                     mValue = value;
   1078                 }
   1079 
   1080                 public int getAlgorithmId() {
   1081                     return mAlgorithmId;
   1082                 }
   1083 
   1084                 public byte[] getValue() {
   1085                     return mValue;
   1086                 }
   1087             }
   1088 
   1089             public static class AdditionalAttribute {
   1090                 private final int mId;
   1091                 private final byte[] mValue;
   1092 
   1093                 public AdditionalAttribute(int id, byte[] value) {
   1094                     mId  = id;
   1095                     mValue = value.clone();
   1096                 }
   1097 
   1098                 public int getId() {
   1099                     return mId;
   1100                 }
   1101 
   1102                 public byte[] getValue() {
   1103                     return mValue.clone();
   1104                 }
   1105             }
   1106         }
   1107     }
   1108 
   1109     public static class SupportedSignature {
   1110         public final SignatureAlgorithm algorithm;
   1111         public final byte[] signature;
   1112 
   1113         public SupportedSignature(SignatureAlgorithm algorithm, byte[] signature) {
   1114             this.algorithm = algorithm;
   1115             this.signature = signature;
   1116         }
   1117     }
   1118 }
   1119