private async Task <string> HashResourcePack(string url, IProgress <float> downloadProgress, Server server) { string result = ""; if (string.IsNullOrEmpty(url)) { return(result); } //ensure tmp directory new DirectoryInfo(Path.Combine(_application.AppPath, "tmp")).Create(); FileInfo resourcePackFile = new FileInfo( Path.Combine(_application.AppPath, "tmp", Guid.NewGuid().ToString() .Replace("-", "") + ".zip")); //Download the resource pack var client = new HttpClient(); HttpResponseMessage response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Head, url)); if (response.Headers.GetValues("ContentType").All(h => h != "application/zip")) { await _console.WriteError(server, "Failed to generate resource-pack hash: No zip at URL\nResuming with no hash..."); return(result); } await _download.DownloadFileAsync(url, resourcePackFile.FullName, downloadProgress, CancellationToken.None); //Calculate sha-1 await using (FileStream fs = resourcePackFile.OpenRead()) { await using var bs = new BufferedStream(fs); using (SHA1 sha1 = SHA1.Create()) { byte[] hash = await sha1.ComputeHashAsync(bs); StringBuilder formatted = new StringBuilder(2 * hash.Length); foreach (var b in hash) { formatted.Append($"{b:X2}"); } result = formatted.ToString(); } } resourcePackFile.Delete(); return(result); }
/// <summary> /// Signs the archive with the given PEM certificate and private key. /// </summary> /// <param name="path">Path to the APK to sign</param> /// <param name="pemData">PEM of the certificate and private key</param> public async Task SignApk(string path, string pemData) { //await using Stream manifestFile = apkArchive.CreateAndOpenEntry("META-INF/MANIFEST.MF"); await using Stream manifestFile = new MemoryStream(); //await using Stream signaturesFile = apkArchive.CreateAndOpenEntry("META-INF/BS.SF"); await using Stream sigFileBody = new MemoryStream(); await using (StreamWriter manifestWriter = OpenStreamWriter(manifestFile)) { await manifestWriter.WriteLineAsync("Manifest-Version: 1.0"); await manifestWriter.WriteLineAsync("Created-By: QuestPatcher"); await manifestWriter.WriteLineAsync(); } // Temporarily open the archive in order to calculate these hashes // This is done because opening all of the entries will cause them all to be recompressed if using ZipArchiveMode.Update, thus causing a long dispose time using (ZipArchive apkArchive = ZipFile.OpenRead(path)) { foreach (ZipArchiveEntry entry in apkArchive.Entries.Where(entry => !entry.FullName.StartsWith("META-INF"))) // Skip other signature related files { await WriteEntryHash(entry, manifestFile, sigFileBody); } } using (ZipArchive apkArchive = ZipFile.Open(path, ZipArchiveMode.Update)) { // Delete existing signature related files foreach (ZipArchiveEntry entry in apkArchive.Entries.Where(entry => entry.FullName.StartsWith("META-INF")).ToList()) { entry.Delete(); } await using Stream signaturesFile = apkArchive.CreateAndOpenEntry("META-INF/BS.SF"); await using Stream rsaFile = apkArchive.CreateAndOpenEntry("META-INF/BS.RSA"); await using Stream manifestStream = apkArchive.CreateAndOpenEntry("META-INF/MANIFEST.MF"); // Find the hash of the manifest manifestFile.Position = 0; byte[] manifestHash = await Sha.ComputeHashAsync(manifestFile); // Finally, copy it to the output file manifestFile.Position = 0; await manifestFile.CopyToAsync(manifestStream); // Write the signature information await using (StreamWriter signatureWriter = OpenStreamWriter(signaturesFile)) { await signatureWriter.WriteLineAsync("Signature-Version: 1.0"); await signatureWriter.WriteLineAsync($"SHA1-Digest-Manifest: {Convert.ToBase64String(manifestHash)}"); await signatureWriter.WriteLineAsync("Created-By: QuestPatcher"); await signatureWriter.WriteLineAsync(); } // Copy the body of signatures for each file into the signature file sigFileBody.Position = 0; await sigFileBody.CopyToAsync(signaturesFile); signaturesFile.Position = 0; // Get the bytes in the signature file for signing await using MemoryStream sigFileMs = new(); await signaturesFile.CopyToAsync(sigFileMs); // Sign the signature file, and save the signature byte[] keyFile = GetSignature(sigFileMs.ToArray(), pemData); await rsaFile.WriteAsync(keyFile); } }