using System.Buffers.Binary; using System.Security.Cryptography; namespace SwBLL.OfflineRollout; // ============================================================ // RolloutPackageCrypto — see IRolloutPackageCrypto. // // Container format, byte-for-byte identical to // FrameworkBLL.Promotion.PromotionPackageCrypto (deliberate — one format across both directions // this platform moves encrypted content, not two independently-maintained schemes): // [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 // // The recipient's private key never touches this deployment — only the public half (registered // via SW.MSWCLIENTDATABASEKEY) is ever seen here. Decrypt happens exclusively at the on-prem/BYOC // install, in the standalone apply tool (Phase 4), which has its own independent implementation // of the read/decrypt half — this class is deliberately encrypt-only. // ============================================================ public class RolloutPackageCrypto : IRolloutPackageCrypto { private const byte FormatVersion = 1; private const int AesKeySizeBytes = 32; // AES-256 private const int NonceSizeBytes = 12; // AES-GCM standard nonce size private const int TagSizeBytes = 16; // AES-GCM standard tag size public Task EncryptPackageAsync(byte[] plaintext, string recipientPublicKeyPem, CancellationToken ct) { if (plaintext is null || plaintext.Length == 0) throw new ArgumentException("Plaintext package content is required.", nameof(plaintext)); if (string.IsNullOrWhiteSpace(recipientPublicKeyPem)) throw new ArgumentException("RecipientPublicKeyPem is required.", nameof(recipientPublicKeyPem)); using var rsa = RSA.Create(); rsa.ImportFromPem(recipientPublicKeyPem); var aesKey = RandomNumberGenerator.GetBytes(AesKeySizeBytes); var nonce = RandomNumberGenerator.GetBytes(NonceSizeBytes); var ciphertext = new byte[plaintext.Length]; var tag = new byte[TagSizeBytes]; using (var aes = new AesGcm(aesKey, TagSizeBytes)) aes.Encrypt(nonce, plaintext, ciphertext, tag); var encryptedAesKey = rsa.Encrypt(aesKey, RSAEncryptionPadding.OaepSHA256); using var output = new MemoryStream(); using (var writer = new BinaryWriter(output)) { writer.Write(FormatVersion); writer.Write(BinaryPrimitives.ReverseEndianness(encryptedAesKey.Length)); writer.Write(encryptedAesKey); writer.Write(nonce); writer.Write(tag); writer.Write(ciphertext); } return Task.FromResult(output.ToArray()); } }