public SvrNativeInterface() { InitAndroidInterface(); AppUtils = new AppUtils(this); ApkUtils = new ApkUtils(this); BatteryUtils = new BatteryUtils(this); WifiUtils = new WifiUtils(this); VolumeUtils = new VolumeUtils(this); BrightnessUtils = new BrightnessUtils(this); DateTimeUtils = new DateTimeUtils(this); WFDUtils = new WFDUtils(this); FotaUtils = new FotaUtils(this); BluetoothUtils = new BluetoothUtils(this); DeviceUtils = new DeviceUtils(this); }
/// <summary> /// Returns the minimum Android version (API Level) supported by the provided APK. This is based /// the <code>android:minSdkVersion</code>code> attributes of the APK's <code>AndroidManifest.xml</code>. /// </summary> /// <param name="cdRecords"></param> /// <param name="lhfSection"></param> /// <returns></returns> private static int GetMinSdkVersionFromApk(List <CentralDirectoryRecord> cdRecords, DataSource lhfSection) { var androidManifest = GetAndroidManifestFromApk(cdRecords, lhfSection); if (androidManifest == null) { return(DefaultMinSdkVersion); } try { return(ApkUtils.GetMinSdkVersionFromBinaryAndroidManifest(androidManifest)); } catch { return(DefaultMinSdkVersion); } }
/// <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(); } }