| | | 1 | | using System.Security.Cryptography; |
| | | 2 | | using System.Text; |
| | | 3 | | |
| | | 4 | | namespace Orchestrator.Commands.Utility.Snapshots; |
| | | 5 | | |
| | | 6 | | /// <summary> |
| | | 7 | | /// Provides AES-256-GCM encryption for test fixtures. |
| | | 8 | | /// This is a copy of the logic from KicktippIntegration.Tests to avoid a dependency. |
| | | 9 | | /// </summary> |
| | | 10 | | public static class SnapshotEncryptor |
| | | 11 | | { |
| | | 12 | | private const int NonceSize = 12; // 96 bits for GCM |
| | | 13 | | private const int TagSize = 16; // 128 bits for GCM |
| | | 14 | | |
| | | 15 | | /// <summary> |
| | | 16 | | /// Generates a new random AES-256 encryption key. |
| | | 17 | | /// </summary> |
| | | 18 | | /// <returns>Base64-encoded 256-bit key.</returns> |
| | | 19 | | public static string GenerateKey() |
| | | 20 | | { |
| | 0 | 21 | | var keyBytes = RandomNumberGenerator.GetBytes(32); |
| | 0 | 22 | | return Convert.ToBase64String(keyBytes); |
| | | 23 | | } |
| | | 24 | | |
| | | 25 | | /// <summary> |
| | | 26 | | /// Encrypts plaintext content using AES-256-GCM. |
| | | 27 | | /// </summary> |
| | | 28 | | /// <param name="plaintext">The content to encrypt.</param> |
| | | 29 | | /// <param name="base64Key">Base64-encoded 256-bit key.</param> |
| | | 30 | | /// <returns>Base64-encoded encrypted data (nonce + ciphertext + tag).</returns> |
| | | 31 | | public static string Encrypt(string plaintext, string base64Key) |
| | | 32 | | { |
| | 0 | 33 | | var key = Convert.FromBase64String(base64Key); |
| | 0 | 34 | | ValidateKey(key); |
| | | 35 | | |
| | 0 | 36 | | var plaintextBytes = Encoding.UTF8.GetBytes(plaintext); |
| | 0 | 37 | | var nonce = RandomNumberGenerator.GetBytes(NonceSize); |
| | 0 | 38 | | var ciphertext = new byte[plaintextBytes.Length]; |
| | 0 | 39 | | var tag = new byte[TagSize]; |
| | | 40 | | |
| | 0 | 41 | | using var aes = new AesGcm(key, TagSize); |
| | 0 | 42 | | aes.Encrypt(nonce, plaintextBytes, ciphertext, tag); |
| | | 43 | | |
| | | 44 | | // Combine: nonce (12) + ciphertext (variable) + tag (16) |
| | 0 | 45 | | var result = new byte[NonceSize + ciphertext.Length + TagSize]; |
| | 0 | 46 | | Buffer.BlockCopy(nonce, 0, result, 0, NonceSize); |
| | 0 | 47 | | Buffer.BlockCopy(ciphertext, 0, result, NonceSize, ciphertext.Length); |
| | 0 | 48 | | Buffer.BlockCopy(tag, 0, result, NonceSize + ciphertext.Length, TagSize); |
| | | 49 | | |
| | 0 | 50 | | return Convert.ToBase64String(result); |
| | 0 | 51 | | } |
| | | 52 | | |
| | | 53 | | private static void ValidateKey(byte[] key) |
| | | 54 | | { |
| | 0 | 55 | | if (key.Length != 32) |
| | | 56 | | { |
| | 0 | 57 | | throw new ArgumentException($"Key must be 256 bits (32 bytes). Got {key.Length} bytes."); |
| | | 58 | | } |
| | 0 | 59 | | } |
| | | 60 | | } |