From 28008a746a31abb7909dd86cb0cd413ac8943b0b Mon Sep 17 00:00:00 2001 From: jmpoep Date: Thu, 7 Dec 2023 16:51:07 +0800 Subject: first commit --- utils/ipn_tool/App.config | 6 + utils/ipn_tool/CheckSsvTest.cs | 170 +++++++++++++++++ utils/ipn_tool/Program.cs | 301 ++++++++++++++++++++++++++++++ utils/ipn_tool/Properties/AssemblyInfo.cs | 35 ++++ utils/ipn_tool/Watermarks.cs | 111 +++++++++++ utils/ipn_tool/ipn_tool.csproj | 63 +++++++ utils/ipn_tool/ipn_tool.sln | 22 +++ 7 files changed, 708 insertions(+) create mode 100644 utils/ipn_tool/App.config create mode 100644 utils/ipn_tool/CheckSsvTest.cs create mode 100644 utils/ipn_tool/Program.cs create mode 100644 utils/ipn_tool/Properties/AssemblyInfo.cs create mode 100644 utils/ipn_tool/Watermarks.cs create mode 100644 utils/ipn_tool/ipn_tool.csproj create mode 100644 utils/ipn_tool/ipn_tool.sln (limited to 'utils/ipn_tool') diff --git a/utils/ipn_tool/App.config b/utils/ipn_tool/App.config new file mode 100644 index 0000000..0a564f8 --- /dev/null +++ b/utils/ipn_tool/App.config @@ -0,0 +1,6 @@ + + + + + + diff --git a/utils/ipn_tool/CheckSsvTest.cs b/utils/ipn_tool/CheckSsvTest.cs new file mode 100644 index 0000000..e5a708d --- /dev/null +++ b/utils/ipn_tool/CheckSsvTest.cs @@ -0,0 +1,170 @@ +using System; +using System.IO; +using System.Linq; +using System.Text.RegularExpressions; + +namespace ipn_tool +{ + internal static class CheckSsvTest + { + /* GOOD sequence +Taggant Test Application + +Taggant Library version 1 + + + - correct PE file + - taggant is found + - taggant object created + - taggant is correct + - taggant does not contain timestamp + - file protected by packer with 1 id, version .+ + - hashmap covers following regions: +\d.+ any lines + - hashmap is valid + - full file hash is valid + - full file hash covers first \d+ bytes + - SPV Certificate + 30 82 05 5b 30 82 03 43 a0 03 02 01 02 02 10 25 many lines + - User Certificate + 30 82 04 04 30 82 02 ec a0 03 02 01 02 02 10 05 many lines + etc +*/ + internal static int Result(string logFile, string pemFile) + { + var expectedLines = new[] + { + /*00*/ new Regex("^Taggant Test Application$"), + /*01*/ null, + /*02*/ new Regex("^Taggant Library version 1$"), + /*03*/ null, + /*04*/ new Regex(@"^.+"), + /*05*/ new Regex(@"^ - correct PE file$"), + /*06*/ new Regex(@"^ - taggant is found$"), + /*07*/ new Regex(@"^ - taggant object created$"), + /*08*/ new Regex(@"^ - taggant is correct$"), + /*09*/ new Regex(@"^ - taggant does not contain timestamp$"), + /*10*/ new Regex(@"^ - file protected by packer with 1 id, version .+$"), + /*11*/ new Regex(@"^ - hashmap covers following regions:$"), + /*12*/ new Regex(@"^\d.+$"), //any lines + /*13*/ new Regex(@"^ - hashmap is valid$"), + /*14*/ new Regex(@"^ - full file hash is valid$"), + /*15*/ new Regex(@"^ - full file hash covers first \d+ bytes$"), + /*16*/ new Regex(@"^ - SPV Certificate$"), + /*17*/ null, + /*18*/ new Regex(@"^ - User Certificate"), + /*19*/ null + }; + try + { + var stateIndex = 0; + var lineNo = 0; + var spv = new MemoryStream(); + var usr = new MemoryStream(); + var curFile = "unknown"; + foreach (var line in File.ReadAllLines(logFile)) + { + if (stateIndex == 4) + curFile = line; + var re = expectedLines[stateIndex]; + var needCheck = true; + switch (stateIndex) + { + case 13: + needCheck = false; + if (re.IsMatch(line)) + ++stateIndex; + else if (!expectedLines[stateIndex - 1].IsMatch(line)) + throw new InvalidDataException(string.Format("Regex '{0}' or '{1}' match was expected at line #{2} but got '{3}'", re, expectedLines[stateIndex - 1], lineNo, line)); + break; + case 17: + if (!AppendToMemoryStream(line, spv)) + { + stateIndex = 18; + re = expectedLines[stateIndex]; + } + else + { + needCheck = false; + } + break; + case 19: + if (!AppendToMemoryStream(line, usr)) + { + if (!CompareCertificates(pemFile, curFile, spv, usr)) + return 1; + spv = new MemoryStream(); + usr = new MemoryStream(); + stateIndex = 4; + re = expectedLines[stateIndex]; + } + else + { + needCheck = false; + } + break; + } + if(needCheck) + { + if (re == null && line != string.Empty) + throw new InvalidDataException(string.Format("Empty line #{0} was expected but got '{1}'", lineNo, line)); + if (re != null && !re.IsMatch(line)) + throw new InvalidDataException(string.Format("Regex '{0}' match was expected at line #{1} but got '{2}'", re, lineNo, line)); + ++stateIndex; + } + ++lineNo; + } + return CompareCertificates(pemFile, curFile, spv, usr) ? 0 : 1; + } + catch (Exception ex) + { + Console.Error.WriteLine(ex); + return 1; + } + } + + private static bool CompareCertificates(string pemFile, string binaryName, MemoryStream spv, MemoryStream usr) + { + var pemContents = File.ReadAllText(pemFile); + var m = Regex.Match(pemContents, + @"^-----BEGIN CERTIFICATE-----[\s]*(?([^-]+))[\s]*-----END CERTIFICATE-----[\s]*-----BEGIN CERTIFICATE-----[\s]*(?([^-]+))[\s]*-----END CERTIFICATE-----[\s]*-----BEGIN RSA PRIVATE KEY-----[\s]*(?([^-]+))[\s]*-----END RSA PRIVATE KEY-----[\s]*$", + RegexOptions.Multiline); + if (!m.Success) + throw new InvalidDataException("Cannot parse " + pemFile); + var expectedSpv = Convert.FromBase64String(m.Groups["spv"].Value); + var expectedUsr = Convert.FromBase64String(m.Groups["usr"].Value); + //TODO: check private key if need + return CompareBa("SPV", binaryName, expectedSpv, spv.ToArray()) && CompareBa("USER", binaryName, expectedUsr, usr.ToArray()); + } + + private static bool CompareBa(string partName, string binaryName, byte[] p1, byte[] p2) + { + if (p1.Length == p2.Length && p1.Length > 0) + { + for(var i = 0; i < p1.Length; i++) + if (p1[i] != p2[i]) + throw new InvalidDataException(string.Format("taggant.pem {0} did not match to file {1} signature at position {2}.", partName, binaryName, i)); + return true; + } + throw new InvalidDataException(string.Format("taggant.pem {0} did not match to file {1} signature", partName, binaryName)); + } + + // 30 82 05 5b 30 82 03 43 a0 03 02 01 02 02 10 25 - typical line + private static bool AppendToMemoryStream(string line, Stream spv) + { + if (!Regex.IsMatch(line, @"^[\s0-9A-F]+$", RegexOptions.IgnoreCase)) + return false; + var chunk = StringToByteArray(line.Replace(" ", "")); + spv.Write(chunk, 0, chunk.Length); + return true; + } + + private static byte[] StringToByteArray(string hex) + { + return Enumerable.Range(0, hex.Length) + .Where(x => x % 2 == 0) + .Select(x => Convert.ToByte(hex.Substring(x, 2), 16)) + .ToArray(); + } + } +} \ No newline at end of file diff --git a/utils/ipn_tool/Program.cs b/utils/ipn_tool/Program.cs new file mode 100644 index 0000000..b86dbdb --- /dev/null +++ b/utils/ipn_tool/Program.cs @@ -0,0 +1,301 @@ +using System; +using System.Data; +using System.Data.SqlClient; +using System.Globalization; +using System.IO; +using System.Text; +using System.Xml; + +namespace ipn_tool +{ + static class Program + { + static int Main(string[] args) + { + switch (args.Length) + { + case 2: + if (args[0] == "-export_wm") + return ExportWatermarks(args[1]); + break; + case 3: + switch (args[0]) + { + case "-export_bl": + return MergeBlacklist(args[1], args[2]); + case "-export_tasks": + return ExportTasks(null, args[1], args[2]); + case "-check_ssvtest": + return CheckSsvTest.Result(args[1], args[2]); + } + break; + case 4: + if (args[0] == "-export_task") + return ExportTasks(args[1], args[2], args[3]); + if (args[0] == "-check_wm") + return Watermarks.CheckIfPresent(args[1], args[2], args[3]); + break; + case 5: + if (args[0] == "-register_result") + return RegisterResult(args[1], args[2], args[3], args[4]); + break; + } + Console.WriteLine("IPN database tool. USAGE:"); + Console.WriteLine("a) Export watermarks: ipn_tool -export_wm "); + Console.WriteLine("b) Merge project with blacklist: ipn_tool -export_bl "); + Console.WriteLine("c) Prepare for all actual end-user builds: ipn_tool -export_tasks "); + Console.WriteLine("d) Prepare for actual end-user build: ipn_tool -export_task "); + Console.WriteLine("e) Register build result: ipn_tool -register_result "); + Console.WriteLine("f) Parse and check ssvtest.log: ipn_tool -check_ssvtest "); + Console.WriteLine("g) Check if watermark present: ipn_tool -check_wm "); + return 1; + } + + private static int RegisterResult(string licenseId, string bambooBuildUrl, string success, string version) + { + int ret; + using (var con = IpnConn()) + { + using (var cmd = new SqlCommand("dbo.RegisterBuildTaskResult", con)) + { + cmd.CommandType = CommandType.StoredProcedure; + cmd.CommandTimeout = 600; + cmd.Parameters.AddWithValue("@licenseID", int.Parse(licenseId)); + cmd.Parameters.AddWithValue("@bambooBuildUrl", bambooBuildUrl); + cmd.Parameters.AddWithValue("@success", bool.Parse(success)); + cmd.Parameters.AddWithValue("@version", version); + + con.Open(); + ret = (int)cmd.ExecuteScalar(); + Console.WriteLine("RegisterResult (0 - OK): {0}.", ret); + } + } + return ret; + } + + private enum Platform + { + Windows, + Linux, + Mac + } + + private static Platform RunningPlatform() + { + switch (Environment.OSVersion.Platform) + { + case PlatformID.Unix: + // Well, there are chances MacOSX is reported as Unix instead of MacOSX. + // Instead of platform check, we'll do a feature checks (Mac specific root folders) + if (Directory.Exists("/Applications") + & Directory.Exists("/System") + & Directory.Exists("/Users") + & Directory.Exists("/Volumes")) + return Platform.Mac; + else + return Platform.Linux; + + case PlatformID.MacOSX: + return Platform.Mac; + + default: + return Platform.Windows; + } + } + + private static int ExportTasks(string licenseId, string rootPath, string version) + { + var ret = 1; + if (!rootPath.EndsWith(Path.DirectorySeparatorChar + @"licenses")) + { + Console.WriteLine(@"RootPath '{0}' check failed (should ends with {1}licenses)", rootPath, Path.DirectorySeparatorChar); + } + else using (var con = IpnConn()) + { + using (var cmd = new SqlCommand("dbo.ExportBuildTaskInfo", con)) + { + cmd.CommandType = CommandType.StoredProcedure; + cmd.CommandTimeout = 600; + cmd.Parameters.AddWithValue("@licenseID", licenseId == null ? DBNull.Value : (object)int.Parse(licenseId)); + cmd.Parameters.AddWithValue("@filterOutVersion", version); + cmd.Parameters.AddWithValue("@filterByOperatingSystem", RunningPlatform().ToString()); + + con.Open(); + var cnt = 0; + using (var r = cmd.ExecuteReader()) + { + if (licenseId == null) + { + Directory.Delete(rootPath, true); + } + while (r.Read()) + { + ++cnt; + var lId = r.GetInt32(0); + var task = r.GetString(1); + var key = r.GetString(2); + var taggant = r.GetString(3); + + Directory.CreateDirectory(Path.Combine(rootPath, lId.ToString(CultureInfo.InvariantCulture))); + var taskIniName = Path.Combine(rootPath, lId.ToString(), "task.ini"); + using (var taskIni = new StreamWriter(new FileStream(taskIniName, FileMode.OpenOrCreate, FileAccess.Write), new UTF8Encoding(false))) + { + var doc = new XmlDocument(); + doc.LoadXml(task); + // ReSharper disable once PossibleNullReferenceException + foreach (XmlAttribute attr in doc.DocumentElement.Attributes) + { + taskIni.WriteLine("{0}={1}", attr.Name, attr.Value); + } + } + var keyName = Path.Combine(rootPath, lId.ToString(), "VMProtect.key"); + File.WriteAllText(keyName, key); + var tagName = Path.Combine(rootPath, lId.ToString(), "taggant.pem"); + File.WriteAllText(tagName, + String.Join("\r\n", taggant.Split(new[] {'\r', '\n'}, StringSplitOptions.RemoveEmptyEntries))); + } + + Console.WriteLine("ExportTasks: {0} item(s) exported.", cnt); + if (cnt > 0) + ret = 0; + } + } + } + return ret; + } + + private static int MergeBlacklist(string filenameIn, string filenameOut) + { + var ret = 1; + using (var con = IpnConn()) + { + using (var cmd = new SqlCommand("dbo.ExportBlacklist", con)) + { + cmd.CommandType = CommandType.StoredProcedure; + cmd.CommandTimeout = 600; + con.Open(); + var xml = new XmlDocument(); + xml.Load(filenameIn); + var lm = xml.SelectSingleNode("Document/LicenseManager"); + if (lm != null) + { + lm.InnerXml = ""; + var cnt = 0; + using (var r = cmd.ExecuteReader()) + { + while (r.Read()) + { + XmlNode license = xml.CreateElement("License"); + XmlAttribute adate = xml.CreateAttribute("Date"); + adate.Value = r.GetDateTime(1).ToString("yyyy-MM-dd"); + + XmlAttribute aname = xml.CreateAttribute("CustomerName"); + aname.Value = r.GetString(2); + + XmlAttribute aemail = xml.CreateAttribute("CustomerEmail"); + aemail.Value = r.GetString(3); + + XmlAttribute akey = xml.CreateAttribute("SerialNumber"); + akey.Value = r.GetString(4); + + if (license.Attributes != null) + { + license.Attributes.Append(adate); + license.Attributes.Append(aname); + license.Attributes.Append(aemail); + license.Attributes.Append(akey); + XmlAttribute ablocked = xml.CreateAttribute("Blocked"); + ablocked.Value = "1"; + license.Attributes.Append(ablocked); + } + lm.AppendChild(license); + + cnt++; + } + } + xml.Save(filenameOut); + Console.WriteLine("MergeBlacklist: {0} item(s) exported.", cnt); + if (cnt > 0) + ret = 0; + } + } + } + return ret; + } + + private static int ExportWatermarks(string filenameOut) + { + var ret = 1; + using (var con = IpnConn()) + { + using (var cmd = new SqlCommand("dbo.ExportWatermarks", con)) + { + cmd.CommandType = CommandType.StoredProcedure; + cmd.CommandTimeout = 600; + con.Open(); + var xml = new XmlDocument(); + xml.InsertBefore(xml.CreateXmlDeclaration("1.0",null,null), xml.DocumentElement); + XmlNode rootNode = xml.CreateElement("Document"); + xml.AppendChild(rootNode); + XmlNode wms = xml.CreateElement("Watermarks"); + rootNode.AppendChild(wms); + + int cnt = 0; + using (var r = cmd.ExecuteReader()) + { + while (r.Read()) + { + XmlNode wm = xml.CreateElement("Watermark"); + XmlAttribute aid = xml.CreateAttribute("Id"); + aid.Value = cnt.ToString(CultureInfo.InvariantCulture); + + XmlAttribute aname = xml.CreateAttribute("Name"); + aname.Value = r.GetString(0); + + XmlAttribute areadablename = xml.CreateAttribute("ReadableName"); + areadablename.Value = r.GetString(1); + + XmlAttribute aemail = xml.CreateAttribute("EMail"); + aemail.Value = r.GetString(2); + + wm.InnerText = r.GetString(3); + + if (wm.Attributes != null) + { + wm.Attributes.Append(aid); + wm.Attributes.Append(aname); + wm.Attributes.Append(areadablename); + wm.Attributes.Append(aemail); + if (r.GetInt32(4) == 0) + { + XmlAttribute aenabled = xml.CreateAttribute("Enabled"); + aenabled.Value = "0"; + wm.Attributes.Append(aenabled); + } + } + wms.AppendChild(wm); + + cnt++; + } + } + XmlAttribute acnt = xml.CreateAttribute("Id"); + acnt.Value = cnt.ToString(CultureInfo.InvariantCulture); + if (wms.Attributes != null) + wms.Attributes.Append(acnt); + + xml.Save(filenameOut); + Console.WriteLine("ExportWatermarks: {0} item(s) exported.", cnt); + if (cnt > 0) + ret = 0; + } + } + return ret; + } + + private static SqlConnection IpnConn() + { + return new SqlConnection("server=scb-serv;database=ipn;user id=ipn_reader;password=rAqiEiGBOh39;Connection Timeout=300"); + } + + } +} diff --git a/utils/ipn_tool/Properties/AssemblyInfo.cs b/utils/ipn_tool/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..0c49f83 --- /dev/null +++ b/utils/ipn_tool/Properties/AssemblyInfo.cs @@ -0,0 +1,35 @@ +using System.Reflection; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("ipn_tool")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("ipn_tool")] +[assembly: AssemblyCopyright("Copyright © 2015")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("a555f928-8e1a-4b21-b346-ad777bddebab")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/utils/ipn_tool/Watermarks.cs b/utils/ipn_tool/Watermarks.cs new file mode 100644 index 0000000..3ed47f5 --- /dev/null +++ b/utils/ipn_tool/Watermarks.cs @@ -0,0 +1,111 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text.RegularExpressions; +using System.Xml; + +namespace ipn_tool +{ + internal static class Watermarks + { + internal static int CheckIfPresent(string xmlDb, string id, string binFile) + { + try + { + var wm = LoadWm(xmlDb, id); + return FindWm(wm, binFile) ? 0 : 1; + } + catch (Exception ex) + { + Console.Error.WriteLine(ex); + return 1; + } + } + + private static string LoadWm(string xmlDb, string id) + { + var s = new FileStream(xmlDb, FileMode.Open, FileAccess.Read); + using (var x = new XmlTextReader(s)) + { + while (x.Read()) + { + if (x.NodeType == XmlNodeType.Element && x.Name == "Watermark" && x.GetAttribute("Name") == id) + { + if(x.Read() && x.NodeType == XmlNodeType.Text && !string.IsNullOrEmpty(x.Value)) + return x.Value; + } + } + } + throw new KeyNotFoundException("WM.id=" + id); + } + + private static bool FindWm(string wm, string binFile) + { + var wmna = WmStringToNibbleArray(wm.ToUpper()); + var filesToCheck = new List(); + if (Directory.Exists(binFile)) + { + filesToCheck.AddRange(Directory.EnumerateFiles(binFile)); + } + else + { + filesToCheck.Add(binFile); + } + return filesToCheck.All(s => CheckFile(s, wmna)); + } + + private static bool CheckFile(string binFile, int[] wmna) + { + var filena = new List(wmna.Length + 1); + using (var s = new FileStream(binFile, FileMode.Open, FileAccess.Read)) + { + while (true) + { + var nextByte = s.ReadByte(); + if (nextByte == -1) + { + Console.Error.WriteLine("Watermark was not found in {0}", binFile); + return false; + } + filena.Add(nextByte >> 4); + filena.Add(nextByte & 15); + while (filena.Count >= wmna.Length) + { + if (MatchWm(wmna, filena)) + { + Console.WriteLine("Watermark in {0}: FOUND", binFile); + return true; + } + filena.RemoveAt(0); + } + } + } + } + + private static bool MatchWm(IEnumerable wmna, IList filena) + { + return !wmna.Where((t, i) => t != -1 && t != filena[i]).Any(); + } + + private static int[] WmStringToNibbleArray(string wm) + { + if(!Regex.IsMatch(wm, @"[A-Z0-9\?]+")) + throw new ArgumentException("Watermark contains bad symbols: " + wm, "wm"); + + var ret = new int[wm.Length]; + var idx = 0; + foreach (var ch in wm) + { + if (ch == '?') + ret[idx++] = -1; + else if (ch <= '9') + ret[idx++] = ch - '0'; + else + ret[idx++] = ch - 'A' + 10; + } + return ret; + } + + } +} \ No newline at end of file diff --git a/utils/ipn_tool/ipn_tool.csproj b/utils/ipn_tool/ipn_tool.csproj new file mode 100644 index 0000000..379456e --- /dev/null +++ b/utils/ipn_tool/ipn_tool.csproj @@ -0,0 +1,63 @@ + + + + + Debug + AnyCPU + {4ADE08C2-D14E-4FE6-BF66-95EE29C953C6} + Exe + Properties + ipn_tool + ipn_tool + v4.8 + 512 + + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + false + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + false + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/utils/ipn_tool/ipn_tool.sln b/utils/ipn_tool/ipn_tool.sln new file mode 100644 index 0000000..2b43a4d --- /dev/null +++ b/utils/ipn_tool/ipn_tool.sln @@ -0,0 +1,22 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 14 +VisualStudioVersion = 14.0.25123.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ipn_tool", "ipn_tool.csproj", "{4ADE08C2-D14E-4FE6-BF66-95EE29C953C6}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {4ADE08C2-D14E-4FE6-BF66-95EE29C953C6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4ADE08C2-D14E-4FE6-BF66-95EE29C953C6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4ADE08C2-D14E-4FE6-BF66-95EE29C953C6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4ADE08C2-D14E-4FE6-BF66-95EE29C953C6}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal -- cgit v1.2.3