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 */ compareSignatureAlgorithm(SignatureAlgorithm alg1, SignatureAlgorithm alg2)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 */ compareContentDigestAlgorithm( ContentDigestAlgorithm alg1, ContentDigestAlgorithm alg2)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 */ verifyIntegrity( DataSource beforeApkSigningBlock, DataSource centralDir, ByteBuffer eocd, Set<ContentDigestAlgorithm> contentDigestAlgorithms, Result result)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 findApkSignatureSchemeBlock( ByteBuffer apkSigningBlock, int blockId, Result result)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 checkByteOrderLittleEndian(ByteBuffer buffer)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 */ sliceFromTo(ByteBuffer source, int start, int end)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 */ getByteBuffer(ByteBuffer source, int size)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 getLengthPrefixedSlice(ByteBuffer source)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 readLengthPrefixedByteArray(ByteBuffer buf)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 toHex(byte[] value)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 computeContentDigests( Set<ContentDigestAlgorithm> digestAlgorithms, DataSource beforeCentralDir, DataSource centralDir, DataSource eocd)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 computeOneMbChunkContentDigests( Set<ContentDigestAlgorithm> digestAlgorithms, DataSource[] contents, Map<ContentDigestAlgorithm, byte[]> outputContentDigests)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 computeApkVerityDigest(DataSource beforeCentralDir, DataSource centralDir, DataSource eocd, Map<ContentDigestAlgorithm, byte[]> outputContentDigests)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 getChunkCount(long inputSize, int chunkSize)526 private static final long getChunkCount(long inputSize, int chunkSize) { 527 return (inputSize + chunkSize - 1) / chunkSize; 528 } 529 setUnsignedInt32LittleEndian(int value, byte[] result, int offset)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 encodePublicKey(PublicKey publicKey)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 encodeCertificates(List<X509Certificate> certificates)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 encodeAsLengthPrefixedElement(byte[] bytes)573 public static byte[] encodeAsLengthPrefixedElement(byte[] bytes) { 574 byte[][] adapterBytes = new byte[1][]; 575 adapterBytes[0] = bytes; 576 return encodeAsSequenceOfLengthPrefixedElements(adapterBytes); 577 } 578 encodeAsSequenceOfLengthPrefixedElements(List<byte[]> sequence)579 public static byte[] encodeAsSequenceOfLengthPrefixedElements(List<byte[]> sequence) { 580 return encodeAsSequenceOfLengthPrefixedElements( 581 sequence.toArray(new byte[sequence.size()][])); 582 } 583 encodeAsSequenceOfLengthPrefixedElements(byte[][] sequence)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 encodeAsSequenceOfLengthPrefixedPairsOfIntAndLengthPrefixedBytes( List<Pair<Integer, byte[]>> sequence)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 */ findSignature( DataSource apk, ApkUtils.ZipSections zipSections, int blockId, Result result)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 */ generateApkSigningBlockPadding( DataSource beforeCentralDir, boolean apkSigningBlockPaddingSupported)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 copyWithModifiedCDOffset( DataSource beforeCentralDir, DataSource eocd)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 generateApkSigningBlock( List<Pair<byte[], Integer>> apkSignatureSchemeBlockPairs)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[]>> computeContentDigests( DataSource beforeCentralDir, DataSource centralDir, DataSource eocd, List<SignerConfig> signerConfigs)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 */ getSignaturesToVerify( List<SupportedSignature> signatures, int minSdkVersion, int maxSdkVersion)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 NoSupportedSignaturesException(String message)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 SignatureNotFoundException(String message)882 public SignatureNotFoundException(String message) { 883 super(message); 884 } 885 SignatureNotFoundException(String message, Throwable cause)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 */ generateSignaturesOverData( SignerConfig signerConfig, byte[] data)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 Result(int signatureSchemeVersion)983 public Result(int signatureSchemeVersion) { 984 this.signatureSchemeVersion = signatureSchemeVersion; 985 } 986 containsErrors()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 addError(ApkVerifier.Issue msg, Object... parameters)1001 public void addError(ApkVerifier.Issue msg, Object... parameters) { 1002 mErrors.add(new ApkVerifier.IssueWithParams(msg, parameters)); 1003 } 1004 addWarning(ApkVerifier.Issue msg, Object... parameters)1005 public void addWarning(ApkVerifier.Issue msg, Object... parameters) { 1006 mWarnings.add(new ApkVerifier.IssueWithParams(msg, parameters)); 1007 } 1008 getErrors()1009 public List<ApkVerifier.IssueWithParams> getErrors() { 1010 return mErrors; 1011 } 1012 getWarnings()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 addError(ApkVerifier.Issue msg, Object... parameters)1033 public void addError(ApkVerifier.Issue msg, Object... parameters) { 1034 mErrors.add(new ApkVerifier.IssueWithParams(msg, parameters)); 1035 } 1036 addWarning(ApkVerifier.Issue msg, Object... parameters)1037 public void addWarning(ApkVerifier.Issue msg, Object... parameters) { 1038 mWarnings.add(new ApkVerifier.IssueWithParams(msg, parameters)); 1039 } 1040 containsErrors()1041 public boolean containsErrors() { 1042 return !mErrors.isEmpty(); 1043 } 1044 getErrors()1045 public List<ApkVerifier.IssueWithParams> getErrors() { 1046 return mErrors; 1047 } 1048 getWarnings()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 ContentDigest(int signatureAlgorithmId, byte[] value)1057 public ContentDigest(int signatureAlgorithmId, byte[] value) { 1058 mSignatureAlgorithmId = signatureAlgorithmId; 1059 mValue = value; 1060 } 1061 getSignatureAlgorithmId()1062 public int getSignatureAlgorithmId() { 1063 return mSignatureAlgorithmId; 1064 } 1065 getValue()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 Signature(int algorithmId, byte[] value)1075 public Signature(int algorithmId, byte[] value) { 1076 mAlgorithmId = algorithmId; 1077 mValue = value; 1078 } 1079 getAlgorithmId()1080 public int getAlgorithmId() { 1081 return mAlgorithmId; 1082 } 1083 getValue()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 AdditionalAttribute(int id, byte[] value)1093 public AdditionalAttribute(int id, byte[] value) { 1094 mId = id; 1095 mValue = value.clone(); 1096 } 1097 getId()1098 public int getId() { 1099 return mId; 1100 } 1101 getValue()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 SupportedSignature(SignatureAlgorithm algorithm, byte[] signature)1113 public SupportedSignature(SignatureAlgorithm algorithm, byte[] signature) { 1114 this.algorithm = algorithm; 1115 this.signature = signature; 1116 } 1117 } 1118 } 1119