using System.Security.Cryptography; using System.Text.Json; using System.Text.Json.Serialization; using ZB.MOM.WW.ScadaBridge.Commons.Types.Transport; namespace ZB.MOM.WW.ScadaBridge.Transport.Encryption; /// /// T-005: computes the AES-GCM Associated Authenticated Data (AAD) for a bundle's /// encrypted payload. AAD is the SHA-256 of the manifest after normalising the two /// derivative fields (ContentHash, Encryption) to known sentinel /// values — those depend on the ciphertext and the IV, so they cannot themselves /// be authenticated, but every OTHER manifest field (SourceEnvironment, /// ExportedBy, ScadaBridgeVersion, Summary, Contents, /// CreatedAtUtc, …) participates in the GCM tag. /// /// Threading this byte array through AesGcm.Encrypt / AesGcm.Decrypt /// makes the Step-4 "type the source environment name to confirm" gate /// tamper-evident: a flipped SourceEnvironment on a stolen bundle yields /// an AuthenticationTagMismatchException on decrypt instead of producing /// a valid plaintext with a forged origin label. /// /// public static class BundleManifestAad { /// /// JSON options matching BundleSerializer.JsonOptions so the AAD bytes /// are stable across the encrypt + decrypt side. /// private static readonly JsonSerializerOptions JsonOptions = new() { WriteIndented = false, DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, Converters = { new JsonStringEnumConverter() }, }; /// /// Computes the AAD bytes for the supplied manifest. The two derivative /// fields are normalised to fixed sentinels so the AAD is independent of the /// ciphertext / IV that will eventually populate them in the on-disk /// manifest. /// /// The manifest whose non-derivative fields should be authenticated. /// SHA-256 digest of the canonicalised manifest bytes. public static byte[] Compute(BundleManifest manifest) { ArgumentNullException.ThrowIfNull(manifest); var canonical = manifest with { ContentHash = string.Empty, Encryption = null, }; var canonicalBytes = JsonSerializer.SerializeToUtf8Bytes(canonical, JsonOptions); return SHA256.HashData(canonicalBytes); } }