@Test public void testMultithreadedVersionMatchesSinglethreaded() throws Exception { Map<ContentDigestAlgorithm, byte[]> outputContentDigests = new EnumMap<>(ContentDigestAlgorithm.class); Map<ContentDigestAlgorithm, byte[]> outputContentDigestsMultithreaded = new EnumMap<>(ContentDigestAlgorithm.class); ApkSigningBlockUtils.computeOneMbChunkContentDigests( RunnablesExecutor.SINGLE_THREADED, algos, dataSource, outputContentDigests); ApkSigningBlockUtils.computeOneMbChunkContentDigests( (RunnablesProvider provider) -> { ForkJoinPool forkJoinPool = ForkJoinPool.commonPool(); int jobCount = forkJoinPool.getParallelism(); List<Future<?>> jobs = new ArrayList<>(jobCount); for (int i = 0; i < jobCount; i++) { jobs.add(forkJoinPool.submit(provider.createRunnable())); } try { for (Future<?> future : jobs) { future.get(); } } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new RuntimeException(e); } catch (ExecutionException e) { throw new RuntimeException(e); } }, algos, dataSource, outputContentDigestsMultithreaded); assertEqualDigests(outputContentDigestsMultithreaded, outputContentDigests); }
static void computeOneMbChunkContentDigests( Set<ContentDigestAlgorithm> digestAlgorithms, DataSource[] contents, Map<ContentDigestAlgorithm, byte[]> outputContentDigests) throws IOException, NoSuchAlgorithmException, DigestException { long chunkCountLong = 0; for (DataSource input : contents) { chunkCountLong += getChunkCount(input.size(), CONTENT_DIGESTED_CHUNK_MAX_SIZE_BYTES); } if (chunkCountLong > Integer.MAX_VALUE) { throw new DigestException("Input too long: " + chunkCountLong + " chunks"); } int chunkCount = (int) chunkCountLong; ContentDigestAlgorithm[] digestAlgorithmsArray = digestAlgorithms.toArray(new ContentDigestAlgorithm[digestAlgorithms.size()]); MessageDigest[] mds = new MessageDigest[digestAlgorithmsArray.length]; byte[][] digestsOfChunks = new byte[digestAlgorithmsArray.length][]; int[] digestOutputSizes = new int[digestAlgorithmsArray.length]; for (int i = 0; i < digestAlgorithmsArray.length; i++) { ContentDigestAlgorithm digestAlgorithm = digestAlgorithmsArray[i]; int digestOutputSizeBytes = digestAlgorithm.getChunkDigestOutputSizeBytes(); digestOutputSizes[i] = digestOutputSizeBytes; byte[] concatenationOfChunkCountAndChunkDigests = new byte[5 + chunkCount * digestOutputSizeBytes]; concatenationOfChunkCountAndChunkDigests[0] = 0x5a; setUnsignedInt32LittleEndian( chunkCount, concatenationOfChunkCountAndChunkDigests, 1); digestsOfChunks[i] = concatenationOfChunkCountAndChunkDigests; String jcaAlgorithm = digestAlgorithm.getJcaMessageDigestAlgorithm(); mds[i] = MessageDigest.getInstance(jcaAlgorithm); } DataSink mdSink = DataSinks.asDataSink(mds); byte[] chunkContentPrefix = new byte[5]; chunkContentPrefix[0] = (byte) 0xa5; int chunkIndex = 0; for (DataSource input : contents) { long inputOffset = 0; long inputRemaining = input.size(); while (inputRemaining > 0) { int chunkSize = (int) Math.min(inputRemaining, CONTENT_DIGESTED_CHUNK_MAX_SIZE_BYTES); setUnsignedInt32LittleEndian(chunkSize, chunkContentPrefix, 1); for (int i = 0; i < mds.length; i++) { mds[i].update(chunkContentPrefix); } try { input.feed(inputOffset, chunkSize, mdSink); } catch (IOException e) { throw new IOException("Failed to read chunk #" + chunkIndex, e); } for (int i = 0; i < digestAlgorithmsArray.length; i++) { MessageDigest md = mds[i]; byte[] concatenationOfChunkCountAndChunkDigests = digestsOfChunks[i]; int expectedDigestSizeBytes = digestOutputSizes[i]; int actualDigestSizeBytes = md.digest( concatenationOfChunkCountAndChunkDigests, 5 + chunkIndex * expectedDigestSizeBytes, expectedDigestSizeBytes); if (actualDigestSizeBytes != expectedDigestSizeBytes) { throw new RuntimeException( "Unexpected output size of " + md.getAlgorithm() + " digest: " + actualDigestSizeBytes); } } inputOffset += chunkSize; inputRemaining -= chunkSize; chunkIndex++; } } for (int i = 0; i < digestAlgorithmsArray.length; i++) { ContentDigestAlgorithm digestAlgorithm = digestAlgorithmsArray[i]; byte[] concatenationOfChunkCountAndChunkDigests = digestsOfChunks[i]; MessageDigest md = mds[i]; byte[] digest = md.digest(concatenationOfChunkCountAndChunkDigests); outputContentDigests.put(digestAlgorithm, digest); } }
ApkSigningBlockUtils { static void computeOneMbChunkContentDigests( Set<ContentDigestAlgorithm> digestAlgorithms, DataSource[] contents, Map<ContentDigestAlgorithm, byte[]> outputContentDigests) throws IOException, NoSuchAlgorithmException, DigestException { long chunkCountLong = 0; for (DataSource input : contents) { chunkCountLong += getChunkCount(input.size(), CONTENT_DIGESTED_CHUNK_MAX_SIZE_BYTES); } if (chunkCountLong > Integer.MAX_VALUE) { throw new DigestException("Input too long: " + chunkCountLong + " chunks"); } int chunkCount = (int) chunkCountLong; ContentDigestAlgorithm[] digestAlgorithmsArray = digestAlgorithms.toArray(new ContentDigestAlgorithm[digestAlgorithms.size()]); MessageDigest[] mds = new MessageDigest[digestAlgorithmsArray.length]; byte[][] digestsOfChunks = new byte[digestAlgorithmsArray.length][]; int[] digestOutputSizes = new int[digestAlgorithmsArray.length]; for (int i = 0; i < digestAlgorithmsArray.length; i++) { ContentDigestAlgorithm digestAlgorithm = digestAlgorithmsArray[i]; int digestOutputSizeBytes = digestAlgorithm.getChunkDigestOutputSizeBytes(); digestOutputSizes[i] = digestOutputSizeBytes; byte[] concatenationOfChunkCountAndChunkDigests = new byte[5 + chunkCount * digestOutputSizeBytes]; concatenationOfChunkCountAndChunkDigests[0] = 0x5a; setUnsignedInt32LittleEndian( chunkCount, concatenationOfChunkCountAndChunkDigests, 1); digestsOfChunks[i] = concatenationOfChunkCountAndChunkDigests; String jcaAlgorithm = digestAlgorithm.getJcaMessageDigestAlgorithm(); mds[i] = MessageDigest.getInstance(jcaAlgorithm); } DataSink mdSink = DataSinks.asDataSink(mds); byte[] chunkContentPrefix = new byte[5]; chunkContentPrefix[0] = (byte) 0xa5; int chunkIndex = 0; for (DataSource input : contents) { long inputOffset = 0; long inputRemaining = input.size(); while (inputRemaining > 0) { int chunkSize = (int) Math.min(inputRemaining, CONTENT_DIGESTED_CHUNK_MAX_SIZE_BYTES); setUnsignedInt32LittleEndian(chunkSize, chunkContentPrefix, 1); for (int i = 0; i < mds.length; i++) { mds[i].update(chunkContentPrefix); } try { input.feed(inputOffset, chunkSize, mdSink); } catch (IOException e) { throw new IOException("Failed to read chunk #" + chunkIndex, e); } for (int i = 0; i < digestAlgorithmsArray.length; i++) { MessageDigest md = mds[i]; byte[] concatenationOfChunkCountAndChunkDigests = digestsOfChunks[i]; int expectedDigestSizeBytes = digestOutputSizes[i]; int actualDigestSizeBytes = md.digest( concatenationOfChunkCountAndChunkDigests, 5 + chunkIndex * expectedDigestSizeBytes, expectedDigestSizeBytes); if (actualDigestSizeBytes != expectedDigestSizeBytes) { throw new RuntimeException( "Unexpected output size of " + md.getAlgorithm() + " digest: " + actualDigestSizeBytes); } } inputOffset += chunkSize; inputRemaining -= chunkSize; chunkIndex++; } } for (int i = 0; i < digestAlgorithmsArray.length; i++) { ContentDigestAlgorithm digestAlgorithm = digestAlgorithmsArray[i]; byte[] concatenationOfChunkCountAndChunkDigests = digestsOfChunks[i]; MessageDigest md = mds[i]; byte[] digest = md.digest(concatenationOfChunkCountAndChunkDigests); outputContentDigests.put(digestAlgorithm, digest); } } }
ApkSigningBlockUtils { static void computeOneMbChunkContentDigests( Set<ContentDigestAlgorithm> digestAlgorithms, DataSource[] contents, Map<ContentDigestAlgorithm, byte[]> outputContentDigests) throws IOException, NoSuchAlgorithmException, DigestException { long chunkCountLong = 0; for (DataSource input : contents) { chunkCountLong += getChunkCount(input.size(), CONTENT_DIGESTED_CHUNK_MAX_SIZE_BYTES); } if (chunkCountLong > Integer.MAX_VALUE) { throw new DigestException("Input too long: " + chunkCountLong + " chunks"); } int chunkCount = (int) chunkCountLong; ContentDigestAlgorithm[] digestAlgorithmsArray = digestAlgorithms.toArray(new ContentDigestAlgorithm[digestAlgorithms.size()]); MessageDigest[] mds = new MessageDigest[digestAlgorithmsArray.length]; byte[][] digestsOfChunks = new byte[digestAlgorithmsArray.length][]; int[] digestOutputSizes = new int[digestAlgorithmsArray.length]; for (int i = 0; i < digestAlgorithmsArray.length; i++) { ContentDigestAlgorithm digestAlgorithm = digestAlgorithmsArray[i]; int digestOutputSizeBytes = digestAlgorithm.getChunkDigestOutputSizeBytes(); digestOutputSizes[i] = digestOutputSizeBytes; byte[] concatenationOfChunkCountAndChunkDigests = new byte[5 + chunkCount * digestOutputSizeBytes]; concatenationOfChunkCountAndChunkDigests[0] = 0x5a; setUnsignedInt32LittleEndian( chunkCount, concatenationOfChunkCountAndChunkDigests, 1); digestsOfChunks[i] = concatenationOfChunkCountAndChunkDigests; String jcaAlgorithm = digestAlgorithm.getJcaMessageDigestAlgorithm(); mds[i] = MessageDigest.getInstance(jcaAlgorithm); } DataSink mdSink = DataSinks.asDataSink(mds); byte[] chunkContentPrefix = new byte[5]; chunkContentPrefix[0] = (byte) 0xa5; int chunkIndex = 0; for (DataSource input : contents) { long inputOffset = 0; long inputRemaining = input.size(); while (inputRemaining > 0) { int chunkSize = (int) Math.min(inputRemaining, CONTENT_DIGESTED_CHUNK_MAX_SIZE_BYTES); setUnsignedInt32LittleEndian(chunkSize, chunkContentPrefix, 1); for (int i = 0; i < mds.length; i++) { mds[i].update(chunkContentPrefix); } try { input.feed(inputOffset, chunkSize, mdSink); } catch (IOException e) { throw new IOException("Failed to read chunk #" + chunkIndex, e); } for (int i = 0; i < digestAlgorithmsArray.length; i++) { MessageDigest md = mds[i]; byte[] concatenationOfChunkCountAndChunkDigests = digestsOfChunks[i]; int expectedDigestSizeBytes = digestOutputSizes[i]; int actualDigestSizeBytes = md.digest( concatenationOfChunkCountAndChunkDigests, 5 + chunkIndex * expectedDigestSizeBytes, expectedDigestSizeBytes); if (actualDigestSizeBytes != expectedDigestSizeBytes) { throw new RuntimeException( "Unexpected output size of " + md.getAlgorithm() + " digest: " + actualDigestSizeBytes); } } inputOffset += chunkSize; inputRemaining -= chunkSize; chunkIndex++; } } for (int i = 0; i < digestAlgorithmsArray.length; i++) { ContentDigestAlgorithm digestAlgorithm = digestAlgorithmsArray[i]; byte[] concatenationOfChunkCountAndChunkDigests = digestsOfChunks[i]; MessageDigest md = mds[i]; byte[] digest = md.digest(concatenationOfChunkCountAndChunkDigests); outputContentDigests.put(digestAlgorithm, digest); } }  }
ApkSigningBlockUtils { static void computeOneMbChunkContentDigests( Set<ContentDigestAlgorithm> digestAlgorithms, DataSource[] contents, Map<ContentDigestAlgorithm, byte[]> outputContentDigests) throws IOException, NoSuchAlgorithmException, DigestException { long chunkCountLong = 0; for (DataSource input : contents) { chunkCountLong += getChunkCount(input.size(), CONTENT_DIGESTED_CHUNK_MAX_SIZE_BYTES); } if (chunkCountLong > Integer.MAX_VALUE) { throw new DigestException("Input too long: " + chunkCountLong + " chunks"); } int chunkCount = (int) chunkCountLong; ContentDigestAlgorithm[] digestAlgorithmsArray = digestAlgorithms.toArray(new ContentDigestAlgorithm[digestAlgorithms.size()]); MessageDigest[] mds = new MessageDigest[digestAlgorithmsArray.length]; byte[][] digestsOfChunks = new byte[digestAlgorithmsArray.length][]; int[] digestOutputSizes = new int[digestAlgorithmsArray.length]; for (int i = 0; i < digestAlgorithmsArray.length; i++) { ContentDigestAlgorithm digestAlgorithm = digestAlgorithmsArray[i]; int digestOutputSizeBytes = digestAlgorithm.getChunkDigestOutputSizeBytes(); digestOutputSizes[i] = digestOutputSizeBytes; byte[] concatenationOfChunkCountAndChunkDigests = new byte[5 + chunkCount * digestOutputSizeBytes]; concatenationOfChunkCountAndChunkDigests[0] = 0x5a; setUnsignedInt32LittleEndian( chunkCount, concatenationOfChunkCountAndChunkDigests, 1); digestsOfChunks[i] = concatenationOfChunkCountAndChunkDigests; String jcaAlgorithm = digestAlgorithm.getJcaMessageDigestAlgorithm(); mds[i] = MessageDigest.getInstance(jcaAlgorithm); } DataSink mdSink = DataSinks.asDataSink(mds); byte[] chunkContentPrefix = new byte[5]; chunkContentPrefix[0] = (byte) 0xa5; int chunkIndex = 0; for (DataSource input : contents) { long inputOffset = 0; long inputRemaining = input.size(); while (inputRemaining > 0) { int chunkSize = (int) Math.min(inputRemaining, CONTENT_DIGESTED_CHUNK_MAX_SIZE_BYTES); setUnsignedInt32LittleEndian(chunkSize, chunkContentPrefix, 1); for (int i = 0; i < mds.length; i++) { mds[i].update(chunkContentPrefix); } try { input.feed(inputOffset, chunkSize, mdSink); } catch (IOException e) { throw new IOException("Failed to read chunk #" + chunkIndex, e); } for (int i = 0; i < digestAlgorithmsArray.length; i++) { MessageDigest md = mds[i]; byte[] concatenationOfChunkCountAndChunkDigests = digestsOfChunks[i]; int expectedDigestSizeBytes = digestOutputSizes[i]; int actualDigestSizeBytes = md.digest( concatenationOfChunkCountAndChunkDigests, 5 + chunkIndex * expectedDigestSizeBytes, expectedDigestSizeBytes); if (actualDigestSizeBytes != expectedDigestSizeBytes) { throw new RuntimeException( "Unexpected output size of " + md.getAlgorithm() + " digest: " + actualDigestSizeBytes); } } inputOffset += chunkSize; inputRemaining -= chunkSize; chunkIndex++; } } for (int i = 0; i < digestAlgorithmsArray.length; i++) { ContentDigestAlgorithm digestAlgorithm = digestAlgorithmsArray[i]; byte[] concatenationOfChunkCountAndChunkDigests = digestsOfChunks[i]; MessageDigest md = mds[i]; byte[] digest = md.digest(concatenationOfChunkCountAndChunkDigests); outputContentDigests.put(digestAlgorithm, digest); } }  static int compareSignatureAlgorithm(SignatureAlgorithm alg1, SignatureAlgorithm alg2); static void verifyIntegrity(
            RunnablesExecutor executor,
            DataSource beforeApkSigningBlock,
            DataSource centralDir,
            ByteBuffer eocd,
            Set<ContentDigestAlgorithm> contentDigestAlgorithms,
            Result result); static ByteBuffer findApkSignatureSchemeBlock(
            ByteBuffer apkSigningBlock,
            int blockId,
            Result result); static void checkByteOrderLittleEndian(ByteBuffer buffer); static ByteBuffer getLengthPrefixedSlice(ByteBuffer source); static byte[] readLengthPrefixedByteArray(ByteBuffer buf); static String toHex(byte[] value); static Map<ContentDigestAlgorithm, byte[]> computeContentDigests(
            RunnablesExecutor executor,
            Set<ContentDigestAlgorithm> digestAlgorithms,
            DataSource beforeCentralDir,
            DataSource centralDir,
            DataSource eocd); static byte[] encodePublicKey(PublicKey publicKey); static List<byte[]> encodeCertificates(List<X509Certificate> certificates); static byte[] encodeAsLengthPrefixedElement(byte[] bytes); static byte[] encodeAsSequenceOfLengthPrefixedElements(List<byte[]> sequence); static byte[] encodeAsSequenceOfLengthPrefixedElements(byte[][] sequence); static byte[] encodeAsSequenceOfLengthPrefixedPairsOfIntAndLengthPrefixedBytes(
            List<Pair<Integer, byte[]>> sequence); static SignatureInfo findSignature(
            DataSource apk, ApkUtils.ZipSections zipSections, int blockId, Result result); static Pair<DataSource, Integer> generateApkSigningBlockPadding(
            DataSource beforeCentralDir,
            boolean apkSigningBlockPaddingSupported); static DataSource copyWithModifiedCDOffset(
            DataSource beforeCentralDir, DataSource eocd); static byte[] generateApkSigningBlock(
            List<Pair<byte[], Integer>> apkSignatureSchemeBlockPairs); static Pair<List<SignerConfig>, Map<ContentDigestAlgorithm, byte[]>> computeContentDigests(
                    RunnablesExecutor executor,
                    DataSource beforeCentralDir,
                    DataSource centralDir,
                    DataSource eocd,
                    List<SignerConfig> signerConfigs); static List<SupportedSignature> getSignaturesToVerify(
            List<SupportedSignature> signatures, int minSdkVersion, int maxSdkVersion); static List<Pair<Integer, byte[]>> generateSignaturesOverData(
            SignerConfig signerConfig, byte[] data); }
ApkSigningBlockUtils { static void computeOneMbChunkContentDigests( Set<ContentDigestAlgorithm> digestAlgorithms, DataSource[] contents, Map<ContentDigestAlgorithm, byte[]> outputContentDigests) throws IOException, NoSuchAlgorithmException, DigestException { long chunkCountLong = 0; for (DataSource input : contents) { chunkCountLong += getChunkCount(input.size(), CONTENT_DIGESTED_CHUNK_MAX_SIZE_BYTES); } if (chunkCountLong > Integer.MAX_VALUE) { throw new DigestException("Input too long: " + chunkCountLong + " chunks"); } int chunkCount = (int) chunkCountLong; ContentDigestAlgorithm[] digestAlgorithmsArray = digestAlgorithms.toArray(new ContentDigestAlgorithm[digestAlgorithms.size()]); MessageDigest[] mds = new MessageDigest[digestAlgorithmsArray.length]; byte[][] digestsOfChunks = new byte[digestAlgorithmsArray.length][]; int[] digestOutputSizes = new int[digestAlgorithmsArray.length]; for (int i = 0; i < digestAlgorithmsArray.length; i++) { ContentDigestAlgorithm digestAlgorithm = digestAlgorithmsArray[i]; int digestOutputSizeBytes = digestAlgorithm.getChunkDigestOutputSizeBytes(); digestOutputSizes[i] = digestOutputSizeBytes; byte[] concatenationOfChunkCountAndChunkDigests = new byte[5 + chunkCount * digestOutputSizeBytes]; concatenationOfChunkCountAndChunkDigests[0] = 0x5a; setUnsignedInt32LittleEndian( chunkCount, concatenationOfChunkCountAndChunkDigests, 1); digestsOfChunks[i] = concatenationOfChunkCountAndChunkDigests; String jcaAlgorithm = digestAlgorithm.getJcaMessageDigestAlgorithm(); mds[i] = MessageDigest.getInstance(jcaAlgorithm); } DataSink mdSink = DataSinks.asDataSink(mds); byte[] chunkContentPrefix = new byte[5]; chunkContentPrefix[0] = (byte) 0xa5; int chunkIndex = 0; for (DataSource input : contents) { long inputOffset = 0; long inputRemaining = input.size(); while (inputRemaining > 0) { int chunkSize = (int) Math.min(inputRemaining, CONTENT_DIGESTED_CHUNK_MAX_SIZE_BYTES); setUnsignedInt32LittleEndian(chunkSize, chunkContentPrefix, 1); for (int i = 0; i < mds.length; i++) { mds[i].update(chunkContentPrefix); } try { input.feed(inputOffset, chunkSize, mdSink); } catch (IOException e) { throw new IOException("Failed to read chunk #" + chunkIndex, e); } for (int i = 0; i < digestAlgorithmsArray.length; i++) { MessageDigest md = mds[i]; byte[] concatenationOfChunkCountAndChunkDigests = digestsOfChunks[i]; int expectedDigestSizeBytes = digestOutputSizes[i]; int actualDigestSizeBytes = md.digest( concatenationOfChunkCountAndChunkDigests, 5 + chunkIndex * expectedDigestSizeBytes, expectedDigestSizeBytes); if (actualDigestSizeBytes != expectedDigestSizeBytes) { throw new RuntimeException( "Unexpected output size of " + md.getAlgorithm() + " digest: " + actualDigestSizeBytes); } } inputOffset += chunkSize; inputRemaining -= chunkSize; chunkIndex++; } } for (int i = 0; i < digestAlgorithmsArray.length; i++) { ContentDigestAlgorithm digestAlgorithm = digestAlgorithmsArray[i]; byte[] concatenationOfChunkCountAndChunkDigests = digestsOfChunks[i]; MessageDigest md = mds[i]; byte[] digest = md.digest(concatenationOfChunkCountAndChunkDigests); outputContentDigests.put(digestAlgorithm, digest); } }  static int compareSignatureAlgorithm(SignatureAlgorithm alg1, SignatureAlgorithm alg2); static void verifyIntegrity(
            RunnablesExecutor executor,
            DataSource beforeApkSigningBlock,
            DataSource centralDir,
            ByteBuffer eocd,
            Set<ContentDigestAlgorithm> contentDigestAlgorithms,
            Result result); static ByteBuffer findApkSignatureSchemeBlock(
            ByteBuffer apkSigningBlock,
            int blockId,
            Result result); static void checkByteOrderLittleEndian(ByteBuffer buffer); static ByteBuffer getLengthPrefixedSlice(ByteBuffer source); static byte[] readLengthPrefixedByteArray(ByteBuffer buf); static String toHex(byte[] value); static Map<ContentDigestAlgorithm, byte[]> computeContentDigests(
            RunnablesExecutor executor,
            Set<ContentDigestAlgorithm> digestAlgorithms,
            DataSource beforeCentralDir,
            DataSource centralDir,
            DataSource eocd); static byte[] encodePublicKey(PublicKey publicKey); static List<byte[]> encodeCertificates(List<X509Certificate> certificates); static byte[] encodeAsLengthPrefixedElement(byte[] bytes); static byte[] encodeAsSequenceOfLengthPrefixedElements(List<byte[]> sequence); static byte[] encodeAsSequenceOfLengthPrefixedElements(byte[][] sequence); static byte[] encodeAsSequenceOfLengthPrefixedPairsOfIntAndLengthPrefixedBytes(
            List<Pair<Integer, byte[]>> sequence); static SignatureInfo findSignature(
            DataSource apk, ApkUtils.ZipSections zipSections, int blockId, Result result); static Pair<DataSource, Integer> generateApkSigningBlockPadding(
            DataSource beforeCentralDir,
            boolean apkSigningBlockPaddingSupported); static DataSource copyWithModifiedCDOffset(
            DataSource beforeCentralDir, DataSource eocd); static byte[] generateApkSigningBlock(
            List<Pair<byte[], Integer>> apkSignatureSchemeBlockPairs); static Pair<List<SignerConfig>, Map<ContentDigestAlgorithm, byte[]>> computeContentDigests(
                    RunnablesExecutor executor,
                    DataSource beforeCentralDir,
                    DataSource centralDir,
                    DataSource eocd,
                    List<SignerConfig> signerConfigs); static List<SupportedSignature> getSignaturesToVerify(
            List<SupportedSignature> signatures, int minSdkVersion, int maxSdkVersion); static List<Pair<Integer, byte[]>> generateSignaturesOverData(
            SignerConfig signerConfig, byte[] data); static final int ANDROID_COMMON_PAGE_ALIGNMENT_BYTES; static final byte[] APK_SIGNING_BLOCK_MAGIC; static final int VERSION_JAR_SIGNATURE_SCHEME; static final int VERSION_APK_SIGNATURE_SCHEME_V2; static final int VERSION_APK_SIGNATURE_SCHEME_V3; }