using System; using System.IO; using System.Net; using System.Reflection; using System.Security.Cryptography; using Xunit; namespace TeslaConsole.DiffTests { /// /// Proves the source-built secure-config library (SecureConfig/Tesla.SecureConfig, /// emitting assembly TeslaSecureConfiguration) is behaviourally identical to the /// original vendored TeslaSecureConfiguration.dll for the parts that define the /// over-the-wire provisioning format: the OFB keystream cipher and the /// BasicConfigResponse byte layout. Byte-identity against the original binary /// transitively guarantees compatibility with the pod-side counterpart (the /// Launcher's hand-written OFBDuplexStream), which was built against that binary. /// /// The original is loaded out-of-band with Assembly.LoadFile (it shares identity /// with the source build); construction logic is identical reflection over both. /// public class SecureConfigCompatTests { private static readonly Assembly SourceCfg = typeof(Tesla.BasicConfigResponse).Assembly; private static readonly Assembly VendoredCfg = LoadVendored(); private static Assembly LoadVendored() { string path = Path.Combine(AssemblyPaths.RepoRoot, "lib", "TeslaSecureConfiguration.dll"); Assert.True(File.Exists(path), "Vendored baseline not found: " + path); return Assembly.LoadFile(path); } // Fixed, deterministic inputs so both assemblies see identical material. private static readonly byte[] Key = BuildBytes(32, i => (byte)(i * 7 + 1)); private static readonly byte[] Iv = BuildBytes(16, i => (byte)(i * 13 + 5)); private static readonly byte[] Plaintext = System.Text.Encoding.UTF8.GetBytes("The quick brown pod jumps over the lazy console. 0123456789!"); [Fact] public void Baseline_And_Source_Are_Distinct_Assemblies_With_Same_Identity() { Assert.NotEqual(SourceCfg.Location, VendoredCfg.Location); Assert.Equal(VendoredCfg.GetName().Name, SourceCfg.GetName().Name); Assert.Equal(VendoredCfg.GetName().Version, SourceCfg.GetName().Version); } [Fact] public void OFBCryptoStream_Produces_Identical_Ciphertext() { byte[] fromVendored = Encrypt(VendoredCfg); byte[] fromSource = Encrypt(SourceCfg); Assert.Equal(fromVendored, fromSource); // Sanity: it actually encrypted (output differs from plaintext). Assert.NotEqual(Plaintext, fromSource); } [Fact] public void OFBCryptoStream_RoundTrips() { byte[] cipher = Encrypt(SourceCfg); byte[] plain = Decrypt(SourceCfg, cipher); Assert.Equal(Plaintext, plain); } [Fact] public void BasicConfigResponse_ToBytes_Is_Identical() { Assert.Equal(ConfigToBytes(VendoredCfg), ConfigToBytes(SourceCfg)); } [Fact] public void BasicConfigResponse_RoundTrips_Across_Implementations() { // Bytes produced by the vendored impl must parse correctly in the source impl. byte[] bytes = ConfigToBytes(VendoredCfg); object parsed = Activator.CreateInstance( SourceCfg.GetType("Tesla.BasicConfigResponse", true), bytes); Assert.Equal("192.168.1.50", Prop(parsed, "Address").ToString()); Assert.Equal("255.255.255.0", Prop(parsed, "Mask").ToString()); Assert.Equal("192.168.1.1", Prop(parsed, "Gateway").ToString()); Assert.Equal("8.8.8.8", Prop(parsed, "Dns").ToString()); Assert.Equal("POD-01", Prop(parsed, "HostName")); } // ── helpers ────────────────────────────────────────────────────────── private static byte[] Encrypt(Assembly asm) { using var aes = Rijndael.Create(); aes.Mode = CipherMode.ECB; aes.Key = Key; var ms = new MemoryStream(); var ofb = NewOfb(asm, ms, aes.CreateEncryptor(), CryptoStreamMode.Write, Iv); ofb.Write(Plaintext, 0, Plaintext.Length); ofb.Flush(); return ms.ToArray(); } private static byte[] Decrypt(Assembly asm, byte[] cipher) { using var aes = Rijndael.Create(); aes.Mode = CipherMode.ECB; aes.Key = Key; var ms = new MemoryStream(cipher); var ofb = NewOfb(asm, ms, aes.CreateEncryptor(), CryptoStreamMode.Read, Iv); var outBuf = new byte[Plaintext.Length]; int off = 0; while (off < outBuf.Length) { int n = ofb.Read(outBuf, off, outBuf.Length - off); if (n == 0) break; off += n; } return outBuf; } private static Stream NewOfb(Assembly asm, Stream inner, ICryptoTransform t, CryptoStreamMode mode, byte[] iv) // OFBCryptoStream keeps the IV array by reference and reuses it as its // ping-pong keystream scratch buffer, so it MUST get its own copy — the // real callers always pass a freshly generated IV. => (Stream)Activator.CreateInstance( asm.GetType("System.Security.Cryptography.OFBCryptoStream", true), inner, t, mode, (byte[])iv.Clone()); private static byte[] ConfigToBytes(Assembly asm) { Type t = asm.GetType("Tesla.BasicConfigResponse", true); object cfg = Activator.CreateInstance(t, IPAddress.Parse("192.168.1.50"), IPAddress.Parse("255.255.255.0"), IPAddress.Parse("192.168.1.1"), IPAddress.Parse("8.8.8.8"), "POD-01"); return (byte[])t.GetMethod("ToBytes").Invoke(cfg, null); } private static object Prop(object o, string name) => o.GetType().GetProperty(name).GetValue(o, null); private static byte[] BuildBytes(int n, Func f) { var b = new byte[n]; for (int i = 0; i < n; i++) b[i] = f(i); return b; } } }