/// <summary>
        /// Returns the contents of the APK's <code>AndroidManifest.xml</code> or <code>null</code> if this entry
        /// is not present in the APK.
        /// </summary>
        /// <param name="cdRecords"></param>
        /// <param name="lhfSection"></param>
        /// <returns></returns>

        private static byte[] GetAndroidManifestFromApk(List <CentralDirectoryRecord> cdRecords, DataSource lhfSection)
        {
            var androidManifestCdRecord = cdRecords.FirstOrDefault(r => r.Name == ApkUtils.AndroidManifestZipEntryName);

            if (androidManifestCdRecord == null)
            {
                return(null);
            }
            return(LocalFileRecord.GetUncompressedData(lhfSection, androidManifestCdRecord, lhfSection.Length));
        }
        private static int GetInputJarEntryDataAlignmentMultiple(LocalFileRecord entry)
        {
            if (entry.IsDataCompressed)
            {
                // Compressed entries don't need to be aligned
                return(1);
            }
            // Attempt to obtain the alignment multiple from the entry's extra field.
            var extra = entry.Extra;

            if (extra.Length > 0)
            {
                // FORMAT: sequence of fields. Each field consists of:
                //   * uint16 ID
                //   * uint16 size
                //   * 'size' bytes: payload
                using (var reader = new BinaryReader(new MemoryStream(extra)))
                {
                    while ((reader.BaseStream.Remaining()) >= 4)
                    {
                        var headerId = reader.ReadInt16();
                        int dataSize = reader.ReadUInt16();
                        if (dataSize > (reader.BaseStream.Remaining()))
                        {
                            // Malformed field -- insufficient input remaining
                            break;
                        }
                        if (headerId != AlignmentZipExtraDataFieldHeaderId)
                        {
                            // Skip this field
                            reader.BaseStream.Position += dataSize;
                            continue;
                        }
                        // This is APK alignment field.
                        // FORMAT:
                        //  * uint16 alignment multiple (in bytes)
                        //  * remaining bytes -- padding to achieve alignment of data which starts after
                        //    the extra field
                        if (dataSize < 2)
                        {
                            // Malformed
                            break;
                        }

                        return(reader.ReadUInt16());
                    }
                }
            }
            // Fall back to filename-based defaults
            return((entry.Name.EndsWith(".so")) ? 4096 : 4);
        }
 private static void FulfillInspectInputJarEntryRequest(
     DataSource lfhSection,
     LocalFileRecord localFileRecord,
     DefaultApkSignerEngine.IInspectJarEntryRequest inspectEntryRequest)
 {
     try
     {
         localFileRecord.OutputUncompressedData(lfhSection, inspectEntryRequest.DataSink);
     }
     catch (ZipFormatException e)
     {
         throw new ApkFormatException("Malformed ZIP entry: " + localFileRecord.Name, e);
     }
     inspectEntryRequest.Done();
 }
        private static long OutputInputJarEntryLfhRecordPreservingDataAlignment(
            DataSource inputLfhSection,
            LocalFileRecord inputRecord,
            Stream outputLfhSection,
            long outputOffset)
        {
            var inputOffset = inputRecord.StartOffsetInArchive;

            if (inputOffset == outputOffset)
            {
                // This record's data will be aligned same as in the input APK.
                return(inputRecord.OutputRecord(inputLfhSection, outputLfhSection));
            }
            var dataAlignmentMultiple = GetInputJarEntryDataAlignmentMultiple(inputRecord);

            if ((dataAlignmentMultiple <= 1) ||
                ((inputOffset % dataAlignmentMultiple)
                 == (outputOffset % dataAlignmentMultiple)))
            {
                // This record's data will be aligned same as in the input APK.
                return(inputRecord.OutputRecord(inputLfhSection, outputLfhSection));
            }
            var inputDataStartOffset = inputOffset + inputRecord.DataStartOffset;

            if ((inputDataStartOffset % dataAlignmentMultiple) != 0)
            {
                // This record's data is not aligned in the input APK. No need to align it in the
                // output.
                return(inputRecord.OutputRecord(inputLfhSection, outputLfhSection));
            }
            // This record's data needs to be re-aligned in the output. This is achieved using the
            // record's extra field.
            var aligningExtra =
                CreateExtraFieldToAlignData(
                    inputRecord.Extra,
                    outputOffset + inputRecord.ExtraFieldStartOffsetInsideRecord,
                    dataAlignmentMultiple);

            return(inputRecord.OutputRecordWithModifiedExtra(inputLfhSection, aligningExtra, outputLfhSection));
        }
        /// <summary>
        /// Signs the input APK and outputs the resulting signed APK. The input APK is not modified.
        /// </summary>
        public void Sign()
        {
            // Step 1. Find input APK's main ZIP sections
            using (var inputApk = new DataSource(File.OpenRead(_inputFile)))
                using (var outputApk = new FileStream(_outputFile, FileMode.Create, FileAccess.ReadWrite))
                {
                    ApkUtils.ZipSections inputZipSections;
                    try
                    {
                        inputZipSections = ApkUtils.FindZipSections(inputApk);
                    }
                    catch (ZipFormatException e)
                    {
                        throw new ApkFormatException("Malformed APK: not a ZIP archive", e);
                    }

                    long       inputApkSigningBlockOffset = -1;
                    DataSource inputApkSigningBlock       = null;

                    var apkSigningBlockAndOffset = V2SchemeVerifier.FindApkSigningBlock(inputApk, inputZipSections);
                    if (apkSigningBlockAndOffset != null)
                    {
                        inputApkSigningBlock       = apkSigningBlockAndOffset.Item1;
                        inputApkSigningBlockOffset = apkSigningBlockAndOffset.Item2;
                    }

                    var inputApkLfhSection = inputApk.Slice(0,
                                                            (inputApkSigningBlockOffset != -1)
                            ? inputApkSigningBlockOffset
                            : inputZipSections.CentralDirectoryOffset);

                    // Step 2. Parse the input APK's ZIP Central Directory
                    var inputCd        = GetZipCentralDirectory(inputApk, inputZipSections);
                    var inputCdRecords = ParseZipCentralDirectory(inputCd, inputZipSections);

                    // Step 3. Obtain a signer engine instance
                    // Construct a signer engine from the provided parameters

                    // Need to extract minSdkVersion from the APK's AndroidManifest.xml
                    var minSdkVersion = GetMinSdkVersionFromApk(inputCdRecords, inputApkLfhSection);

                    var signerEngine = new DefaultApkSignerEngine(_certificate, minSdkVersion, V1SigningEnabled, V2SigningEnabled, DigestAlgorithm);
                    // Step 4. Provide the signer engine with the input APK's APK Signing Block (if any)
                    if (inputApkSigningBlock != null)
                    {
                        signerEngine.InputApkSigningBlock(inputApkSigningBlock);
                    }


                    // Step 5. Iterate over input APK's entries and output the Local File Header + data of those
                    // entries which need to be output. Entries are iterated in the order in which their Local
                    // File Header records are stored in the file. This is to achieve better data locality in
                    // case Central Directory entries are in the wrong order.
                    var inputCdRecordsSortedByLfhOffset = new List <CentralDirectoryRecord>(inputCdRecords);
                    inputCdRecordsSortedByLfhOffset.Sort(CentralDirectoryRecord.BY_LOCAL_FILE_HEADER_OFFSET_COMPARATOR);
                    var  lastModifiedDateForNewEntries = -1;
                    var  lastModifiedTimeForNewEntries = -1;
                    long inputOffset           = 0;
                    long outputOffset          = 0;
                    var  outputCdRecordsByName = new Dictionary <string, CentralDirectoryRecord>(inputCdRecords.Count);
                    foreach (var inputCdRecord in inputCdRecordsSortedByLfhOffset)
                    {
                        var  entryName         = inputCdRecord.Name;
                        var  entryInstructions = signerEngine.InputJarEntry(entryName);
                        bool shouldOutput;
                        switch (entryInstructions.OutputPolicy)
                        {
                        case DefaultApkSignerEngine.OutputPolicy.Output:
                            shouldOutput = true;
                            break;

                        case DefaultApkSignerEngine.OutputPolicy.OutputByEngine:
                        case DefaultApkSignerEngine.OutputPolicy.Skip:
                            shouldOutput = false;
                            break;

                        default:
                            throw new ArgumentOutOfRangeException(
                                      "Unknown output policy: " + entryInstructions.OutputPolicy);
                        }
                        var inputLocalFileHeaderStartOffset = inputCdRecord.LocalFileHeaderOffset;
                        if (inputLocalFileHeaderStartOffset > inputOffset)
                        {
                            // Unprocessed data in input starting at inputOffset and ending and the start of
                            // this record's LFH. We output this data verbatim because this signer is supposed
                            // to preserve as much of input as possible.
                            var chunkSize = inputLocalFileHeaderStartOffset - inputOffset;
                            inputApkLfhSection.Feed(inputOffset, chunkSize, outputApk);
                            outputOffset += chunkSize;
                            inputOffset   = inputLocalFileHeaderStartOffset;
                        }
                        LocalFileRecord inputLocalFileRecord;
                        try
                        {
                            inputLocalFileRecord =
                                LocalFileRecord.GetRecord(
                                    inputApkLfhSection, inputCdRecord, inputApkLfhSection.Length);
                        }
                        catch (ZipFormatException e)
                        {
                            throw new ApkFormatException("Malformed ZIP entry: " + inputCdRecord.Name, e);
                        }
                        inputOffset += inputLocalFileRecord.Size;
                        var inspectEntryRequest =
                            entryInstructions.InspectJarEntryRequest;
                        if (inspectEntryRequest != null)
                        {
                            FulfillInspectInputJarEntryRequest(
                                inputApkLfhSection, inputLocalFileRecord, inspectEntryRequest);
                        }
                        if (shouldOutput)
                        {
                            // Find the max value of last modified, to be used for new entries added by the
                            // signer.
                            var lastModifiedDate = inputCdRecord.LastModificationDate;
                            var lastModifiedTime = inputCdRecord.LastModificationTime;
                            if ((lastModifiedDateForNewEntries == -1) ||
                                (lastModifiedDate > lastModifiedDateForNewEntries) ||
                                ((lastModifiedDate == lastModifiedDateForNewEntries) &&
                                 (lastModifiedTime > lastModifiedTimeForNewEntries)))
                            {
                                lastModifiedDateForNewEntries = lastModifiedDate;
                                lastModifiedTimeForNewEntries = lastModifiedTime;
                            }
                            inspectEntryRequest = signerEngine.OutputJarEntry(entryName);
                            if (inspectEntryRequest != null)
                            {
                                FulfillInspectInputJarEntryRequest(
                                    inputApkLfhSection, inputLocalFileRecord, inspectEntryRequest);
                            }
                            // Output entry's Local File Header + data
                            var outputLocalFileHeaderOffset = outputOffset;
                            var outputLocalFileRecordSize   =
                                OutputInputJarEntryLfhRecordPreservingDataAlignment(
                                    inputApkLfhSection,
                                    inputLocalFileRecord,
                                    outputApk,
                                    outputLocalFileHeaderOffset);
                            outputOffset += outputLocalFileRecordSize;
                            // Enqueue entry's Central Directory record for output
                            CentralDirectoryRecord outputCdRecord;
                            if (outputLocalFileHeaderOffset == inputLocalFileRecord.StartOffsetInArchive)
                            {
                                outputCdRecord = inputCdRecord;
                            }
                            else
                            {
                                outputCdRecord =
                                    inputCdRecord.CreateWithModifiedLocalFileHeaderOffset(
                                        outputLocalFileHeaderOffset);
                            }
                            outputCdRecordsByName.Add(entryName, outputCdRecord);
                        }
                    }
                    var inputLfhSectionSize = inputApkLfhSection.Length;
                    if (inputOffset < inputLfhSectionSize)
                    {
                        // Unprocessed data in input starting at inputOffset and ending and the end of the input
                        // APK's LFH section. We output this data verbatim because this signer is supposed
                        // to preserve as much of input as possible.
                        var chunkSize = inputLfhSectionSize - inputOffset;
                        inputApkLfhSection.Feed(inputOffset, chunkSize, outputApk);
                        outputOffset += chunkSize;
                        inputOffset   = inputLfhSectionSize;
                    }

                    // Step 6. Sort output APK's Central Directory records in the order in which they should
                    // appear in the output
                    var outputCdRecords = new List <CentralDirectoryRecord>(inputCdRecords.Count + 10);
                    foreach (var inputCdRecord in inputCdRecords)
                    {
                        var entryName = inputCdRecord.Name;
                        if (outputCdRecordsByName.TryGetValue(entryName, out var outputCdRecord))
                        {
                            outputCdRecords.Add(outputCdRecord);
                        }
                    }

                    // Step 7. Generate and output JAR signatures, if necessary. This may output more Local File
                    // Header + data entries and add to the list of output Central Directory records.
                    var outputJarSignatureRequest = signerEngine.OutputJarEntries();
                    if (outputJarSignatureRequest != null)
                    {
                        if (lastModifiedDateForNewEntries == -1)
                        {
                            lastModifiedDateForNewEntries = 0x3a21; // Jan 1 2009 (DOS)
                            lastModifiedTimeForNewEntries = 0;
                        }
                        foreach (var entry in outputJarSignatureRequest.AdditionalJarEntries)
                        {
                            var entryName        = entry.Name;
                            var uncompressedData = entry.Data;

                            var deflateResult         = ZipUtils.Deflate(uncompressedData);
                            var compressedData        = deflateResult.Item1;
                            var uncompressedDataCrc32 = deflateResult.Item2;

                            var inspectEntryRequest = signerEngine.OutputJarEntry(entryName);
                            if (inspectEntryRequest != null)
                            {
                                inspectEntryRequest.DataSink.Write(uncompressedData, 0, uncompressedData.Length);
                                inspectEntryRequest.Done();
                            }
                            var localFileHeaderOffset = outputOffset;
                            outputOffset +=
                                LocalFileRecord.OutputRecordWithDeflateCompressedData(
                                    entryName,
                                    lastModifiedTimeForNewEntries,
                                    lastModifiedDateForNewEntries,
                                    compressedData,
                                    uncompressedDataCrc32,
                                    uncompressedData.Length,
                                    outputApk);
                            outputCdRecords.Add(
                                CentralDirectoryRecord.CreateWithDeflateCompressedData(
                                    entryName,
                                    lastModifiedTimeForNewEntries,
                                    lastModifiedDateForNewEntries,
                                    uncompressedDataCrc32,
                                    compressedData.Length,
                                    uncompressedData.Length,
                                    localFileHeaderOffset));
                        }
                        outputJarSignatureRequest.Done();
                    }

                    // Step 8. Construct output ZIP Central Directory in an in-memory buffer
                    long outputCentralDirSizeBytes = 0;
                    foreach (var record in outputCdRecords)
                    {
                        outputCentralDirSizeBytes += record.Size;
                    }

                    var outputCentralDir = new MemoryStream((int)outputCentralDirSizeBytes);
                    foreach (var record in outputCdRecords)
                    {
                        record.CopyTo(outputCentralDir);
                    }
                    var outputCentralDirStartOffset = outputOffset;
                    var outputCentralDirRecordCount = outputCdRecords.Count;

                    // Step 9. Construct output ZIP End of Central Directory record in an in-memory buffer
                    var outputEocdBytes = EocdRecord.CreateWithModifiedCentralDirectoryInfo(
                        inputZipSections.EndOfCentralDirectory,
                        outputCentralDirRecordCount,
                        outputCentralDir.Length,
                        outputCentralDirStartOffset);
                    var outputEocd = new MemoryStream(outputEocdBytes, true);

                    // Step 10. Generate and output APK Signature Scheme v2 signatures, if necessary. This may
                    // insert an APK Signing Block just before the output's ZIP Central Directory
                    var outputApkSigingBlockRequest =
                        signerEngine.OutputZipSections(
                            outputApk,
                            outputCentralDir,
                            outputEocd);

                    outputApk.Position = outputCentralDirStartOffset;
                    if (outputApkSigingBlockRequest != null)
                    {
                        var outputApkSigningBlock = outputApkSigingBlockRequest.ApkSigningBlock;
                        outputApk.Write(outputApkSigningBlock, 0, outputApkSigningBlock.Length);
                        ZipUtils.SetZipEocdCentralDirectoryOffset(
                            outputEocd, outputCentralDirStartOffset + outputApkSigningBlock.Length);
                        outputApkSigingBlockRequest.Done();
                    }

                    // Step 11. Output ZIP Central Directory and ZIP End of Central Directory
                    outputCentralDir.Position = 0;
                    outputCentralDir.CopyTo(outputApk);

                    outputApk.Write(outputEocdBytes, 0, outputEocdBytes.Length);
                    signerEngine.OutputDone();
                }
        }