using System.Buffers.Binary; using System.Security.Cryptography; namespace SwPackagePromote; /// /// Independent decrypt-side implementation of the Offline Rollout Package container format /// documented in SwBLL.OfflineRollout.RolloutPackageCrypto — byte-identical duplicate of /// SwOfflineApply.OfflinePackageDecryptor (same file, same reasoning: this tool must build and /// run with zero dependency on any live GB5 project). The format itself is the shared contract: /// [1 byte] FormatVersion (=1) /// [4 bytes] BE length of the RSA-OAEP-SHA256-encrypted AES key (N) /// [N bytes] RSA-encrypted AES-256 key /// [12 bytes] AES-GCM nonce /// [16 bytes] AES-GCM tag /// [remaining bytes] AES-GCM ciphertext /// public static class OfflinePackageDecryptor { public static byte[] Decrypt(byte[] encryptedPackage, string privateKeyPem) { using var input = new MemoryStream(encryptedPackage); using var reader = new BinaryReader(input); var formatVersion = reader.ReadByte(); if (formatVersion != 1) throw new NotSupportedException($"Offline Rollout Package format version {formatVersion} is not supported by this tool."); var encryptedAesKeyLength = BinaryPrimitives.ReverseEndianness(reader.ReadInt32()); var encryptedAesKey = reader.ReadBytes(encryptedAesKeyLength); var nonce = reader.ReadBytes(12); var tag = reader.ReadBytes(16); var ciphertext = reader.ReadBytes((int)(input.Length - input.Position)); using var rsa = RSA.Create(); rsa.ImportFromPem(privateKeyPem); var aesKey = rsa.Decrypt(encryptedAesKey, RSAEncryptionPadding.OaepSHA256); var plaintext = new byte[ciphertext.Length]; using (var aes = new AesGcm(aesKey, 16)) aes.Decrypt(nonce, ciphertext, tag, plaintext); return plaintext; } }