diff --git a/.github/workflows/installer.build.yml b/.github/workflows/installer.build.yml index a08c08fa..a6acf427 100644 --- a/.github/workflows/installer.build.yml +++ b/.github/workflows/installer.build.yml @@ -22,7 +22,9 @@ jobs: JFROG_ACCESS_TOKEN: ${{ secrets.JFROG_ACCESS_TOKEN }} gh_ci_key: ${{ secrets.GH_CI_KEY }} ZITI_DEBUG: "yes_please" - + OPENZITI_P12_PASS: ${{ secrets.OPENZITI_P12_PASS }} + OPENZITI_P12: ${{ secrets.OPENZITI_P12 }} + steps: - uses: actions/checkout@master - uses: microsoft/setup-msbuild@v1 @@ -39,7 +41,9 @@ jobs: run: | echo "${env:SIGNING_CERT}" > WinSign.b64 echo "${env:ADV_INST_KEY}" > ADV_INST_KEY.key + echo "${env:OPENZITI_P12}" > OPENZITI_P12.b64 C:\Windows\System32\certutil.exe -decode WinSign.b64 Installer/WinSign.p12 + C:\Windows\System32\certutil.exe -decode OPENZITI_P12.b64 Installer/openziti.p12 - name: Trying to run choco install. run: Set-ExecutionPolicy Bypass -Scope Process -Force; [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072; iex ((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1')) - name: "Install Choco needs: advanced-installer wixtoolset zip ninja" diff --git a/.gitignore b/.gitignore index 9312a38f..cfec3e2b 100644 --- a/.gitignore +++ b/.gitignore @@ -268,7 +268,7 @@ service/libuv.dll Installer/build/ Installer/ziti-tunnel-service.zip ziti-tunnel-service.zip -WinSign.p12 +WinSign.* *.p12 *.pfx *.pem diff --git a/DesktopEdge/MainWindow.xaml.cs b/DesktopEdge/MainWindow.xaml.cs index 3e9bcdca..a727ff65 100644 --- a/DesktopEdge/MainWindow.xaml.cs +++ b/DesktopEdge/MainWindow.xaml.cs @@ -1439,7 +1439,8 @@ namespace ZitiDesktopEdge { public bool CanExecute(object parameter) { return true; } - +#pragma warning disable CS0067 //The event 'ActionCommand.CanExecuteChanged' is never used public event EventHandler CanExecuteChanged; +#pragma warning restore CS0067 //The event 'ActionCommand.CanExecuteChanged' is never used } } diff --git a/DesktopEdge/Properties/AssemblyInfo.cs b/DesktopEdge/Properties/AssemblyInfo.cs index 69c6b337..a9be57de 100644 --- a/DesktopEdge/Properties/AssemblyInfo.cs +++ b/DesktopEdge/Properties/AssemblyInfo.cs @@ -50,6 +50,6 @@ using System.Windows; // // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: -// [assembly: AssemblyVersion("1.9.6.0")] -[assembly: AssemblyVersion("1.9.6.0")] -[assembly: AssemblyFileVersion("1.9.6.0")] +// [assembly: AssemblyVersion("1.9.8.0")] +[assembly: AssemblyVersion("1.9.8.0")] +[assembly: AssemblyFileVersion("1.9.8.0")] diff --git a/Installer/ZitiDesktopEdge.aip b/Installer/ZitiDesktopEdge.aip index 891569b9..7bbedc5c 100644 --- a/Installer/ZitiDesktopEdge.aip +++ b/Installer/ZitiDesktopEdge.aip @@ -23,10 +23,10 @@ - + - + diff --git a/Installer/build.ps1 b/Installer/build.ps1 index 67c8559e..e0e51091 100644 --- a/Installer/build.ps1 +++ b/Installer/build.ps1 @@ -70,5 +70,11 @@ if($gituser -eq "ziti-ci") { } else { echo "detected user [${gituser}] which is not ziti-ci - skipping installer commit" } -(Get-FileHash "${scriptPath}\Output\Ziti Desktop Edge Client-${installerVersion}.exe").Hash > "${scriptPath}\Output\Ziti Desktop Edge Client-${installerVersion}.exe.sha256" + +$exeAbsPath="${scriptPath}\Output\Ziti Desktop Edge Client-${installerVersion}.exe" + +echo "adding additional signature to executable with openziti.org signing certificate" +signtool sign /f "${scriptPath}\openziti.p12" /p "${env:OPENZITI_P12_PASS}" /tr http://ts.ssl.com /fd sha512 /td sha512 /as "${exeAbsPath}" + +(Get-FileHash "${exeAbsPath}").Hash > "${scriptPath}\Output\Ziti Desktop Edge Client-${installerVersion}.exe.sha256" echo "========================== build.ps1 competed ==========================" diff --git a/ZitiDesktopEdge.Client/ServiceClient/AbstractClient.cs b/ZitiDesktopEdge.Client/ServiceClient/AbstractClient.cs index 4b90a28e..c5d96dca 100644 --- a/ZitiDesktopEdge.Client/ServiceClient/AbstractClient.cs +++ b/ZitiDesktopEdge.Client/ServiceClient/AbstractClient.cs @@ -165,10 +165,10 @@ namespace ZitiDesktopEdge.ServiceClient { } else { //ClientDisconnected(null); } - } catch (Exception e) { + } catch (Exception) { try { ReconnectFailureEvent("reconnect failure"); - } catch (Exception ex){ + } catch (Exception){ // don't care - just catch it and continue... it's a timeout... } var now = DateTime.Now; diff --git a/ZitiDesktopEdge.sln b/ZitiDesktopEdge.sln index 8167e22a..70b973ed 100644 --- a/ZitiDesktopEdge.sln +++ b/ZitiDesktopEdge.sln @@ -6,7 +6,10 @@ MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{D66B1C6A-ADFE-437E-B037-C4C849A6CA97}" ProjectSection(SolutionItems) = preProject .editorconfig = .editorconfig + Installer\build.ps1 = Installer\build.ps1 + .github\workflows\installer.build.yml = .github\workflows\installer.build.yml README.md = README.md + Installer\reg.bat = Installer\reg.bat EndProjectSection EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ZitiDesktopEdge", "DesktopEdge\ZitiDesktopEdge.csproj", "{FA4291EA-A97E-4854-959F-91EE7B330B37}" diff --git a/ZitiUpdateService/App.config b/ZitiUpdateService/App.config index df91ae79..e2c58379 100644 --- a/ZitiUpdateService/App.config +++ b/ZitiUpdateService/App.config @@ -1,4 +1,4 @@ - + - + - - - + + + - + - + - + - - + + diff --git a/ZitiUpdateService/Properties/AssemblyInfo.cs b/ZitiUpdateService/Properties/AssemblyInfo.cs index 03a5efb4..54e00a6d 100644 --- a/ZitiUpdateService/Properties/AssemblyInfo.cs +++ b/ZitiUpdateService/Properties/AssemblyInfo.cs @@ -30,6 +30,6 @@ using System.Runtime.InteropServices; // // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: -// [assembly: AssemblyVersion("1.9.6.0")] -[assembly: AssemblyVersion("1.9.6.0")] -[assembly: AssemblyFileVersion("1.9.6.0")] +// [assembly: AssemblyVersion("1.9.8.0")] +[assembly: AssemblyVersion("1.9.8.0")] +[assembly: AssemblyFileVersion("1.9.8.0")] diff --git a/ZitiUpdateService/UpdateService.cs b/ZitiUpdateService/UpdateService.cs index bfd6e615..debe9e45 100644 --- a/ZitiUpdateService/UpdateService.cs +++ b/ZitiUpdateService/UpdateService.cs @@ -19,6 +19,7 @@ using NLog; using Newtonsoft.Json; using System.Net; using DnsClient; +using ZitiUpdateService.Checkers.PeFile; namespace ZitiUpdateService { public partial class UpdateService : ServiceBase { @@ -33,12 +34,9 @@ namespace ZitiUpdateService { public bool useGithubCheck { get; private set; } - private static string[] expected_subject = new string[] { @"CN=""NetFoundry, Inc."", O=""NetFoundry, Inc."", L=Herndon, S=Virginia, C=US", @"CN=NetFoundry Inc., O=NetFoundry Inc., L=Herndon, S=Virginia, C=US" }; - private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); private Timer _updateTimer = new Timer(); - private Timer openzitiRemovalTimer = new Timer(); private bool inUpdateCheck = false; private string exeLocation = null; @@ -54,7 +52,7 @@ namespace ZitiUpdateService { IPCServer svr = new IPCServer(); Task ipcServer = null; Task eventServer = null; - checkers.UpdateCheck check = null; + Checkers.UpdateCheck check = null; private Timer dnsProbeTimer = new Timer(); private IPAddress dnsIpAddress = null; @@ -574,9 +572,9 @@ namespace ZitiUpdateService { updateUrl = "https://api.github.com/repos/openziti/desktop-edge-win-beta/releases/latest"; } if (useGithubCheck) { - check = new checkers.GithubCheck(updateUrl); + check = new Checkers.GithubCheck(updateUrl); } else { - check = new checkers.FilesystemCheck(1); + check = new Checkers.FilesystemCheck(1); } } @@ -605,10 +603,10 @@ namespace ZitiUpdateService { Logger.Warn("Still in update check. This is abnormal. Please report if you see this warning"); return; } - inUpdateCheck = true; //simple semaphone + inUpdateCheck = true; //simple semaphore try { Logger.Debug("checking for update"); - + int avail = check.IsUpdateAvailable(assemblyVersion); if (avail >= 0) { Logger.Debug("update check complete. no update available"); @@ -626,8 +624,8 @@ namespace ZitiUpdateService { string fileDestination = Path.Combine(updateFolder, filename); if (check.AlreadyDownloaded(updateFolder, filename)) { - Logger.Info("package has already been downloaded to {0} - moving to install phase", Path.Combine(updateFolder, filename)); - } else { + Logger.Info("package has already been downloaded to {0} - moving to install phase", fileDestination); + } else { Logger.Info("copying update package begins"); check.CopyUpdatePackage(updateFolder, filename); Logger.Info("copying update package complete"); @@ -640,23 +638,8 @@ namespace ZitiUpdateService { return; } Logger.Debug("downloaded file hash was correct. update can continue."); - - // check digital signature - var signer = X509Certificate.CreateFromSignedFile(fileDestination); - /* keep these commented out lines - just in case we need all the certs from the file use this - var coll = new X509Certificate2Collection(); - coll.Import(filePath); - */ - - var subject = signer.Subject; - if (!expected_subject.Contains(subject)) { - Logger.Error("the file downloaded uses a subject that is unknown! the installation will not proceed. [subject:{0}]", subject); - inUpdateCheck = false; - return; - - } else { - Logger.Info("the file downloaded uses a known subject. installation and can proceed. [subject:{0}]", subject); - } + + new SignedFileValidator(fileDestination).Verify(); StopZiti(); StopUI().Wait(); diff --git a/ZitiUpdateService/ZitiUpdateService.csproj b/ZitiUpdateService/ZitiUpdateService.csproj index cbb3bf6a..78d8eab1 100644 --- a/ZitiUpdateService/ZitiUpdateService.csproj +++ b/ZitiUpdateService/ZitiUpdateService.csproj @@ -13,6 +13,7 @@ true true false + publish\ true Disk @@ -28,7 +29,6 @@ false true true - x64 @@ -98,9 +98,26 @@ ..\packages\Microsoft.PowerShell.5.ReferenceAssemblies.1.1.0\lib\net4\System.Management.Automation.dll + + ..\packages\System.Memory.4.5.4\lib\net461\System.Memory.dll + + + + ..\packages\System.Numerics.Vectors.4.5.0\lib\net46\System.Numerics.Vectors.dll + + + ..\packages\System.Runtime.CompilerServices.Unsafe.5.0.0\lib\net45\System.Runtime.CompilerServices.Unsafe.dll + + + + ..\packages\System.Security.Cryptography.Pkcs.5.0.1\lib\net461\System.Security.Cryptography.Pkcs.dll + + + ..\packages\System.ValueTuple.4.5.0\lib\net47\System.ValueTuple.dll + @@ -113,6 +130,10 @@ + + + + Component @@ -150,6 +171,7 @@ + @@ -170,5 +192,6 @@ ZitiDesktopEdge.Client + \ No newline at end of file diff --git a/ZitiUpdateService/checkers/FilesystemCheck.cs b/ZitiUpdateService/checkers/FilesystemCheck.cs index 6d0d456e..ef56ad32 100644 --- a/ZitiUpdateService/checkers/FilesystemCheck.cs +++ b/ZitiUpdateService/checkers/FilesystemCheck.cs @@ -1,13 +1,9 @@ using System; using System.IO; -using System.Net; - -using System.Security.Cryptography; using NLog; -using Newtonsoft.Json.Linq; -namespace ZitiUpdateService.checkers { +namespace ZitiUpdateService.Checkers { internal class FilesystemCheck : UpdateCheck { private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); string dest = null; diff --git a/ZitiUpdateService/checkers/GithubCheck.cs b/ZitiUpdateService/checkers/GithubCheck.cs index eeca9452..37512d30 100644 --- a/ZitiUpdateService/checkers/GithubCheck.cs +++ b/ZitiUpdateService/checkers/GithubCheck.cs @@ -7,8 +7,9 @@ using NLog; using Newtonsoft.Json.Linq; using ZitiDesktopEdge.Utility; +using ZitiUpdateService.Checkers.PeFile; -namespace ZitiUpdateService.checkers { +namespace ZitiUpdateService.Checkers { internal class GithubCheck : UpdateCheck { private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); @@ -97,7 +98,7 @@ namespace ZitiUpdateService.checkers { File.Delete(sha256dest); return computed.ToLower().Trim() == hash.ToLower().Trim(); } - } + } override public Version GetNextVersion() { return nextVersion; diff --git a/ZitiUpdateService/checkers/PeFile/SignedFileStructs.cs b/ZitiUpdateService/checkers/PeFile/SignedFileStructs.cs new file mode 100644 index 00000000..ce1d2cc9 --- /dev/null +++ b/ZitiUpdateService/checkers/PeFile/SignedFileStructs.cs @@ -0,0 +1,539 @@ +using System; +using System.Runtime.InteropServices; + +namespace ZitiUpdateService.Checkers.PeFile { + + //defined by http://www.pinvoke.net/default.aspx/Structures/IMAGE_FILE_HEADER.html + [StructLayout(LayoutKind.Sequential)] + public struct IMAGE_FILE_HEADER { + public UInt16 Machine; + public UInt16 NumberOfSections; + public UInt32 TimeDateStamp; + public UInt32 PointerToSymbolTable; + public UInt32 NumberOfSymbols; + public UInt16 SizeOfOptionalHeader; + public UInt16 Characteristics; + } + + //see http://www.pinvoke.net/default.aspx/Structures/IMAGE_OPTIONAL_HEADER32.html + //defined by http://www.pinvoke.net/default.aspx/Structures/IMAGE_OPTIONAL_HEADER32.html + [StructLayout(LayoutKind.Sequential)] + public struct IMAGE_DATA_DIRECTORY { + public UInt32 VirtualAddress; + public UInt32 Size; + } + + public enum MachineType : ushort { + Native = 0, + I386 = 0x014c, + Itanium = 0x0200, + x64 = 0x8664 + } + + public sealed class MagicType { + public const Int16 IMAGE_NT_OPTIONAL_HDR32_MAGIC = 0x10b; + public const Int16 IMAGE_NT_OPTIONAL_HDR64_MAGIC = 0x20b; + } + + public enum SubSystemType : ushort { + IMAGE_SUBSYSTEM_UNKNOWN = 0, + IMAGE_SUBSYSTEM_NATIVE = 1, + IMAGE_SUBSYSTEM_WINDOWS_GUI = 2, + IMAGE_SUBSYSTEM_WINDOWS_CUI = 3, + IMAGE_SUBSYSTEM_POSIX_CUI = 7, + IMAGE_SUBSYSTEM_WINDOWS_CE_GUI = 9, + IMAGE_SUBSYSTEM_EFI_APPLICATION = 10, + IMAGE_SUBSYSTEM_EFI_BOOT_SERVICE_DRIVER = 11, + IMAGE_SUBSYSTEM_EFI_RUNTIME_DRIVER = 12, + IMAGE_SUBSYSTEM_EFI_ROM = 13, + IMAGE_SUBSYSTEM_XBOX = 14 + + } + + public enum DllCharacteristicsType : ushort { + RES_0 = 0x0001, + RES_1 = 0x0002, + RES_2 = 0x0004, + RES_3 = 0x0008, + IMAGE_DLL_CHARACTERISTICS_DYNAMIC_BASE = 0x0040, + IMAGE_DLL_CHARACTERISTICS_FORCE_INTEGRITY = 0x0080, + IMAGE_DLL_CHARACTERISTICS_NX_COMPAT = 0x0100, + IMAGE_DLLCHARACTERISTICS_NO_ISOLATION = 0x0200, + IMAGE_DLLCHARACTERISTICS_NO_SEH = 0x0400, + IMAGE_DLLCHARACTERISTICS_NO_BIND = 0x0800, + RES_4 = 0x1000, + IMAGE_DLLCHARACTERISTICS_WDM_DRIVER = 0x2000, + IMAGE_DLLCHARACTERISTICS_TERMINAL_SERVER_AWARE = 0x8000 + } + + [StructLayout(LayoutKind.Explicit)] + public struct IMAGE_OPTIONAL_HEADER32 { + [FieldOffset(0)] public ushort Magic; + + [FieldOffset(2)] public byte MajorLinkerVersion; + + [FieldOffset(3)] public byte MinorLinkerVersion; + + [FieldOffset(4)] public uint SizeOfCode; + + [FieldOffset(8)] public uint SizeOfInitializedData; + + [FieldOffset(12)] public uint SizeOfUninitializedData; + + [FieldOffset(16)] public uint AddressOfEntryPoint; + + [FieldOffset(20)] public uint BaseOfCode; + + // PE32 contains this additional field + [FieldOffset(24)] public uint BaseOfData; + + [FieldOffset(28)] public uint ImageBase; + + [FieldOffset(32)] public uint SectionAlignment; + + [FieldOffset(36)] public uint FileAlignment; + + [FieldOffset(40)] public ushort MajorOperatingSystemVersion; + + [FieldOffset(42)] public ushort MinorOperatingSystemVersion; + + [FieldOffset(44)] public ushort MajorImageVersion; + + [FieldOffset(46)] public ushort MinorImageVersion; + + [FieldOffset(48)] public ushort MajorSubsystemVersion; + + [FieldOffset(50)] public ushort MinorSubsystemVersion; + + [FieldOffset(52)] public uint Win32VersionValue; + + [FieldOffset(56)] public uint SizeOfImage; + + [FieldOffset(60)] public uint SizeOfHeaders; + + [FieldOffset(64)] public uint CheckSum; + + [FieldOffset(68)] public SubSystemType Subsystem; + + [FieldOffset(70)] public DllCharacteristicsType DllCharacteristics; + + [FieldOffset(72)] public uint SizeOfStackReserve; + + [FieldOffset(76)] public uint SizeOfStackCommit; + + [FieldOffset(80)] public uint SizeOfHeapReserve; + + [FieldOffset(84)] public uint SizeOfHeapCommit; + + [FieldOffset(88)] public uint LoaderFlags; + + [FieldOffset(92)] public uint NumberOfRvaAndSizes; + + [FieldOffset(96)] public IMAGE_DATA_DIRECTORY ExportTable; + + [FieldOffset(104)] public IMAGE_DATA_DIRECTORY ImportTable; + + [FieldOffset(112)] public IMAGE_DATA_DIRECTORY ResourceTable; + + [FieldOffset(120)] public IMAGE_DATA_DIRECTORY ExceptionTable; + + [FieldOffset(128)] public IMAGE_DATA_DIRECTORY CertificateTable; + + [FieldOffset(136)] public IMAGE_DATA_DIRECTORY BaseRelocationTable; + + [FieldOffset(144)] public IMAGE_DATA_DIRECTORY Debug; + + [FieldOffset(152)] public IMAGE_DATA_DIRECTORY Architecture; + + [FieldOffset(160)] public IMAGE_DATA_DIRECTORY GlobalPtr; + + [FieldOffset(168)] public IMAGE_DATA_DIRECTORY TLSTable; + + [FieldOffset(176)] public IMAGE_DATA_DIRECTORY LoadConfigTable; + + [FieldOffset(184)] public IMAGE_DATA_DIRECTORY BoundImport; + + [FieldOffset(192)] public IMAGE_DATA_DIRECTORY IAT; + + [FieldOffset(200)] public IMAGE_DATA_DIRECTORY DelayImportDescriptor; + + [FieldOffset(208)] public IMAGE_DATA_DIRECTORY CLRRuntimeHeader; + + [FieldOffset(216)] public IMAGE_DATA_DIRECTORY Reserved; + } + + [StructLayout(LayoutKind.Explicit)] + public struct IMAGE_OPTIONAL_HEADER64 { + [FieldOffset(0)] public ushort Magic; + + [FieldOffset(2)] public byte MajorLinkerVersion; + + [FieldOffset(3)] public byte MinorLinkerVersion; + + [FieldOffset(4)] public uint SizeOfCode; + + [FieldOffset(8)] public uint SizeOfInitializedData; + + [FieldOffset(12)] public uint SizeOfUninitializedData; + + [FieldOffset(16)] public uint AddressOfEntryPoint; + + [FieldOffset(20)] public uint BaseOfCode; + + [FieldOffset(24)] public ulong ImageBase; + + [FieldOffset(32)] public uint SectionAlignment; + + [FieldOffset(36)] public uint FileAlignment; + + [FieldOffset(40)] public ushort MajorOperatingSystemVersion; + + [FieldOffset(42)] public ushort MinorOperatingSystemVersion; + + [FieldOffset(44)] public ushort MajorImageVersion; + + [FieldOffset(46)] public ushort MinorImageVersion; + + [FieldOffset(48)] public ushort MajorSubsystemVersion; + + [FieldOffset(50)] public ushort MinorSubsystemVersion; + + [FieldOffset(52)] public uint Win32VersionValue; + + [FieldOffset(56)] public uint SizeOfImage; + + [FieldOffset(60)] public uint SizeOfHeaders; + + [FieldOffset(64)] public uint CheckSum; + + [FieldOffset(68)] public SubSystemType Subsystem; + + [FieldOffset(70)] public DllCharacteristicsType DllCharacteristics; + + [FieldOffset(72)] public ulong SizeOfStackReserve; + + [FieldOffset(80)] public ulong SizeOfStackCommit; + + [FieldOffset(88)] public ulong SizeOfHeapReserve; + + [FieldOffset(96)] public ulong SizeOfHeapCommit; + + [FieldOffset(104)] public uint LoaderFlags; + + [FieldOffset(108)] public uint NumberOfRvaAndSizes; + + [FieldOffset(112)] public IMAGE_DATA_DIRECTORY ExportTable; + + [FieldOffset(120)] public IMAGE_DATA_DIRECTORY ImportTable; + + [FieldOffset(128)] public IMAGE_DATA_DIRECTORY ResourceTable; + + [FieldOffset(136)] public IMAGE_DATA_DIRECTORY ExceptionTable; + + [FieldOffset(144)] public IMAGE_DATA_DIRECTORY CertificateTable; + + [FieldOffset(152)] public IMAGE_DATA_DIRECTORY BaseRelocationTable; + + [FieldOffset(160)] public IMAGE_DATA_DIRECTORY Debug; + + [FieldOffset(168)] public IMAGE_DATA_DIRECTORY Architecture; + + [FieldOffset(176)] public IMAGE_DATA_DIRECTORY GlobalPtr; + + [FieldOffset(184)] public IMAGE_DATA_DIRECTORY TLSTable; + + [FieldOffset(192)] public IMAGE_DATA_DIRECTORY LoadConfigTable; + + [FieldOffset(200)] public IMAGE_DATA_DIRECTORY BoundImport; + + [FieldOffset(208)] public IMAGE_DATA_DIRECTORY IAT; + + [FieldOffset(216)] public IMAGE_DATA_DIRECTORY DelayImportDescriptor; + + [FieldOffset(224)] public IMAGE_DATA_DIRECTORY CLRRuntimeHeader; + + [FieldOffset(232)] public IMAGE_DATA_DIRECTORY Reserved; + } + + + [StructLayout(LayoutKind.Explicit)] + public struct IMAGE_SECTION_HEADER { + //FROM: http://www.pinvoke.net/default.aspx/Structures/IMAGE_SECTION_HEADER.html + [FieldOffset(0)] [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)] + public char[] Name; + + [FieldOffset(8)] public UInt32 VirtualSize; + + [FieldOffset(12)] public UInt32 VirtualAddress; + + [FieldOffset(16)] public UInt32 SizeOfRawData; + + [FieldOffset(20)] public UInt32 PointerToRawData; + + [FieldOffset(24)] public UInt32 PointerToRelocations; + + [FieldOffset(28)] public UInt32 PointerToLinenumbers; + + [FieldOffset(32)] public UInt16 NumberOfRelocations; + + [FieldOffset(34)] public UInt16 NumberOfLinenumbers; + + [FieldOffset(36)] public DataSectionFlags Characteristics; + + public string Section { + get { return new string(Name); } + } + } + + [Flags] + public enum DataSectionFlags : uint { + //FROM: http://www.pinvoke.net/default.aspx/Structures/IMAGE_SECTION_HEADER.html + /// + /// Reserved for future use. + /// + TypeReg = 0x00000000, + + /// + /// Reserved for future use. + /// + TypeDsect = 0x00000001, + + /// + /// Reserved for future use. + /// + TypeNoLoad = 0x00000002, + + /// + /// Reserved for future use. + /// + TypeGroup = 0x00000004, + + /// + /// The section should not be padded to the next boundary. This flag is obsolete and is replaced by IMAGE_SCN_ALIGN_1BYTES. This is valid only for object files. + /// + TypeNoPadded = 0x00000008, + + /// + /// Reserved for future use. + /// + TypeCopy = 0x00000010, + + /// + /// The section contains executable code. + /// + ContentCode = 0x00000020, + + /// + /// The section contains initialized data. + /// + ContentInitializedData = 0x00000040, + + /// + /// The section contains uninitialized data. + /// + ContentUninitializedData = 0x00000080, + + /// + /// Reserved for future use. + /// + LinkOther = 0x00000100, + + /// + /// The section contains comments or other information. The .drectve section has this type. This is valid for object files only. + /// + LinkInfo = 0x00000200, + + /// + /// Reserved for future use. + /// + TypeOver = 0x00000400, + + /// + /// The section will not become part of the image. This is valid only for object files. + /// + LinkRemove = 0x00000800, + + /// + /// The section contains COMDAT data. For more information, see section 5.5.6, COMDAT Sections (Object Only). This is valid only for object files. + /// + LinkComDat = 0x00001000, + + /// + /// Reset speculative exceptions handling bits in the TLB entries for this section. + /// + NoDeferSpecExceptions = 0x00004000, + + /// + /// The section contains data referenced through the global pointer (GP). + /// + RelativeGP = 0x00008000, + + /// + /// Reserved for future use. + /// + MemPurgeable = 0x00020000, + + /// + /// Reserved for future use. + /// + Memory16Bit = 0x00020000, + + /// + /// Reserved for future use. + /// + MemoryLocked = 0x00040000, + + /// + /// Reserved for future use. + /// + MemoryPreload = 0x00080000, + + /// + /// Align data on a 1-byte boundary. Valid only for object files. + /// + Align1Bytes = 0x00100000, + + /// + /// Align data on a 2-byte boundary. Valid only for object files. + /// + Align2Bytes = 0x00200000, + + /// + /// Align data on a 4-byte boundary. Valid only for object files. + /// + Align4Bytes = 0x00300000, + + /// + /// Align data on an 8-byte boundary. Valid only for object files. + /// + Align8Bytes = 0x00400000, + + /// + /// Align data on a 16-byte boundary. Valid only for object files. + /// + Align16Bytes = 0x00500000, + + /// + /// Align data on a 32-byte boundary. Valid only for object files. + /// + Align32Bytes = 0x00600000, + + /// + /// Align data on a 64-byte boundary. Valid only for object files. + /// + Align64Bytes = 0x00700000, + + /// + /// Align data on a 128-byte boundary. Valid only for object files. + /// + Align128Bytes = 0x00800000, + + /// + /// Align data on a 256-byte boundary. Valid only for object files. + /// + Align256Bytes = 0x00900000, + + /// + /// Align data on a 512-byte boundary. Valid only for object files. + /// + Align512Bytes = 0x00A00000, + + /// + /// Align data on a 1024-byte boundary. Valid only for object files. + /// + Align1024Bytes = 0x00B00000, + + /// + /// Align data on a 2048-byte boundary. Valid only for object files. + /// + Align2048Bytes = 0x00C00000, + + /// + /// Align data on a 4096-byte boundary. Valid only for object files. + /// + Align4096Bytes = 0x00D00000, + + /// + /// Align data on an 8192-byte boundary. Valid only for object files. + /// + Align8192Bytes = 0x00E00000, + + /// + /// The section contains extended relocations. + /// + LinkExtendedRelocationOverflow = 0x01000000, + + /// + /// The section can be discarded as needed. + /// + MemoryDiscardable = 0x02000000, + + /// + /// The section cannot be cached. + /// + MemoryNotCached = 0x04000000, + + /// + /// The section is not pageable. + /// + MemoryNotPaged = 0x08000000, + + /// + /// The section can be shared in memory. + /// + MemoryShared = 0x10000000, + + /// + /// The section can be executed as code. + /// + MemoryExecute = 0x20000000, + + /// + /// The section can be read. + /// + MemoryRead = 0x40000000, + + /// + /// The section can be written to. + /// + MemoryWrite = 0x80000000 + } + + + public class ImageOptionalHeaderWrapper { + public IMAGE_OPTIONAL_HEADER32 h32; + public IMAGE_OPTIONAL_HEADER64 h64; + public bool IsPe; + + + public uint CertificateTableVirtualAddress { + get { + if (IsPe) { + return h32.CertificateTable.VirtualAddress; + } else { + return h64.CertificateTable.VirtualAddress; + } + } + } + + public uint CertificateTableSize { + get { + if (IsPe) { + return h32.CertificateTable.Size; + } else { + return h64.CertificateTable.Size; + } + } + } + + public uint SizeOfHeaders { + get { + if (IsPe) { + return h32.SizeOfHeaders; + } else { + return h64.SizeOfHeaders; + } + } + } + } +} \ No newline at end of file diff --git a/ZitiUpdateService/checkers/PeFile/SignedFileValidator.cs b/ZitiUpdateService/checkers/PeFile/SignedFileValidator.cs new file mode 100644 index 00000000..cb4ac576 --- /dev/null +++ b/ZitiUpdateService/checkers/PeFile/SignedFileValidator.cs @@ -0,0 +1,607 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Net.Http; +using System.Runtime.InteropServices; +using System.Security.Cryptography; +using System.Security.Cryptography.Pkcs; +using System.Security.Cryptography.X509Certificates; +using System.Text; +using System.Threading.Tasks; +using NLog; +using VerifyingFiles.PInvoke; + +namespace ZitiUpdateService.Checkers.PeFile { + public class SignedFileValidator { + private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); + + public const int LengthOfChecksum = 4; //the checksum is 4 bytes + public const int LengthCertificateTable = 8; //the Certificate Table is 8 bytes + + public HashPositions ImportantHashPositions { get; private set; } + public string FilePath { get; private set; } + public PeType Type { get; private set; } + public IMAGE_FILE_HEADER CoffHeader { get; private set; } + public IMAGE_OPTIONAL_HEADER32 StandardFieldsPe32 { get; private set; } + public IMAGE_OPTIONAL_HEADER64 StandardFieldsPe32Plus { get; private set; } + + private readonly X509Certificate2 expectedRootCa = null; + private readonly X509Store empty = new X509Store(); + private X509Store SystemCAs = new X509Store(); + private const string oldKnownThumbprint = "39636E9F5E80308DE370C914CE8112876ECF4E0C"; + + public SignedFileValidator(string pathToFile) { + X509Store store = new X509Store("MY", StoreLocation.CurrentUser); + store.Open(OpenFlags.ReadOnly | OpenFlags.OpenExistingOnly); + SystemCAs.Open(OpenFlags.ReadWrite); + SystemCAs.AddRange(store.Certificates); + + expectedRootCa = certFromResource("ZitiUpdateService.checkers.PeFile.openziti.rootCA.rsa.pem.txt"); + FilePath = pathToFile; + ImportantHashPositions = new HashPositions(); + + parse(); + if (ImportantHashPositions.reorderNeeded) { + ImportantHashPositions.SectionTableHeaders.Sort((x, y) => x.PointerToRawData.CompareTo(y.PointerToRawData)); + } + } + + private X509Certificate2 certFromResource(string resourceName) { + var assembly = this.GetType().Assembly; + using (Stream stream = assembly.GetManifestResourceStream(resourceName)) + using (BinaryReader reader = new BinaryReader(stream)) { + byte[] bytes = new byte[stream.Length]; + stream.Read(bytes, 0, bytes.Length); + return new X509Certificate2(bytes); + } + } + + // ReSharper disable once InconsistentNaming + private void parse() { + using (FileStream stream = new FileStream(FilePath, System.IO.FileMode.Open, System.IO.FileAccess.Read)) + using (BinaryReader reader = new BinaryReader(stream)) { + + //seek to 0x3c to get right to the PE header location... + stream.Seek(0x3c, SeekOrigin.Begin); + uint startCoffHeader = BitConverter.ToUInt32(reader.ReadBytes(4), 0); + ImportantHashPositions.SetStartCoffHeader(startCoffHeader); + stream.Seek(startCoffHeader, SeekOrigin.Begin); + + //parse COFF Header (28 or 24 bytes): https://docs.microsoft.com/en-us/windows/win32/debug/pe-format#coff-file-header-object-and-image + byte[] coffHeaderBytes = reader.ReadBytes(24); + CoffHeader = StructHelper.FromBytes(coffHeaderBytes); + + byte[] magicNumberBytes = reader.ReadBytes(2); + int magicNumber = BitConverter.ToInt16(magicNumberBytes, 0); + Type = magicNumber == 0x10b ? PeType.Pe32 : PeType.Pe32Plus; + stream.Seek(-2, SeekOrigin.Current); //skip back to before the magic number so the struct below is created properly + + //parse Optional Header Standard and Windows-Specific fields (66 or 88 bytes) + //see: https://docs.microsoft.com/en-us/windows/win32/debug/pe-format#optional-header-standard-fields-image-only + if (Type == PeType.Pe32) { + byte[] headerBytes = reader.ReadBytes(Marshal.SizeOf(typeof(IMAGE_OPTIONAL_HEADER32))); + StandardFieldsPe32 = StructHelper.FromBytes(headerBytes); + } else { + byte[] headerBytes = reader.ReadBytes(Marshal.SizeOf(typeof(IMAGE_OPTIONAL_HEADER64))); + StandardFieldsPe32Plus = StructHelper.FromBytes(headerBytes); + } + + ImageOptionalHeaderWrapper wrapper = new ImageOptionalHeaderWrapper() { + h32 = StandardFieldsPe32, + h64 = StandardFieldsPe32Plus, + IsPe = Type == PeType.Pe32 + }; + ImportantHashPositions.SetCertificateDetails(Type, wrapper.SizeOfHeaders, wrapper.CertificateTableVirtualAddress, wrapper.CertificateTableSize); + + for (int i = 0; i < CoffHeader.NumberOfSections; i++) { + byte[] nextSection = reader.ReadBytes(Marshal.SizeOf(typeof(IMAGE_SECTION_HEADER))); //should be 40 bytes... + IMAGE_SECTION_HEADER sectionTableHeader = StructHelper.FromBytes(nextSection); + ImportantHashPositions.AddSectionTableHeader(sectionTableHeader); + } + } + } + + private void addCertToList(SignerInfo si, List list) { + + X509Certificate2 cert = si.Certificate; + if (list.Find(x => x.Thumbprint == cert.Thumbprint) == null) { + list.Add(cert); + } else { + Logger.Debug("Certificate with Thumbprint {0} already in list. skipping.", cert.Thumbprint); + } + } + + public List ExtractVerifiedSignatureCertificates() { + List list = new List(); + using (FileStream stream = new FileStream(FilePath, FileMode.Open, FileAccess.Read)) + using (BinaryReader reader = new BinaryReader(stream)) { + //verify all the embedded signatures + stream.Seek(this.ImportantHashPositions.CertificateTableStart, SeekOrigin.Begin); + int readCertLen = reader.ReadInt32(); + int readRevision = reader.ReadInt16(); + int readCertType = reader.ReadInt16(); + byte[] pkcs7 = reader.ReadBytes(readCertLen); + + SignedCms cms = new SignedCms(); + cms.Decode(pkcs7); + cms.CheckHash(); + + //shout out to Scott MG: https://social.msdn.microsoft.com/Forums/windowsdesktop/en-US/655f5c27-b049-4275-a8b3-cc1c0be2b4f2/retrieve-certificate-info-for-dualsigned-sha1sha256 + //for indicating that the unsigned attributes contain the other SignerInfos + foreach (var cmsSi in cms.SignerInfos) { + cmsSi.CheckSignature(true); + if (VerifyTrust(SystemCAs, expectedRootCa, cmsSi.Certificate).Result) { + VerifyFileHash(cms, cmsSi); + addCertToList(cmsSi, list); + } + if (cmsSi.UnsignedAttributes.Count > 0) { + foreach (var unsignedAttr in cmsSi.UnsignedAttributes) { + foreach (AsnEncodedData asn in unsignedAttr.Values) { + SignedCms innerCms = new SignedCms(); + innerCms.Decode(asn.RawData); + innerCms.CheckHash(); + if (innerCms.SignerInfos.Count > 0) { + SignerInfo innerSignerInfo = innerCms.SignerInfos[0]; + try { + innerSignerInfo.CheckSignature(false); + } + catch (CryptographicException) { + if (innerSignerInfo.Certificate.Thumbprint == oldKnownThumbprint) { + //special handling for the known 'old' NetFoundry signing certificate, now expired... + //TODO: remove this code after 2021 + //just allow this one error + Logger.Warn("Ignoring timestamp validity issue for the existing code signing certificate. Subject: {0}, Thumbprint: {1}", innerSignerInfo.Certificate.Subject, innerSignerInfo.Certificate.Thumbprint); + } else { + if (IsCertificateOpenZitiVerifies(innerSignerInfo.Certificate)) { + Logger.Debug("Signature could not be verified. This is expected for the certificates issued by openziti/netfoundry."); + } else { + throw; + } + } + } + + if (VerifyTrust(SystemCAs, expectedRootCa, innerSignerInfo.Certificate).Result) { + VerifyFileHash(innerCms, innerSignerInfo); + addCertToList(innerSignerInfo, list); + } + } + } + } + } + } + } + + return list; + } + + public bool IsCertificateOpenZitiVerifies(X509Certificate2 cert) { + bool yesOrNo = cert.Subject.ToLower().Contains("netfoundry") || cert.Subject.ToLower().Contains("openziti"); + if (yesOrNo) { + Logger.Debug("Certificate does need verification: {0}", cert.Subject); + } else { + Logger.Debug("Certificate does not need verification: {0}", cert.Subject); + } + return yesOrNo; + } + + public void Verify() { + Logger.Debug("extracting certificates from file: {0}", FilePath); + List list = ExtractVerifiedSignatureCertificates(); + Logger.Info("Certificates extracted: {0}", list.Count); + foreach (X509Certificate2 cert in list) { + Logger.Info("Checking certificate: [{0}] {1}", cert.Thumbprint, cert); + if (IsCertificateOpenZitiVerifies(cert)) { + //verify this certificate was issued from the known CA + try { + Logger.Info("Verifying trust of certificate: [{0}] {1}", cert, cert.Thumbprint); + VerifyTrust(empty, expectedRootCa, cert, true).Wait(); + Logger.Info("Certificate [{0}] {1} was verified signed by [{2}] {3}", cert.Thumbprint, cert.Subject, expectedRootCa.Thumbprint, expectedRootCa.Subject); + return; //yes! + } + catch { + //empty on purpose. keep checking... + } + } + } + + throw new CryptographicException("Executable not signed by an appropriate certificate"); + } + + public void VerifyFileHash(SignedCms cms, SignerInfo signerInfo) { + if (!IsCertificateOpenZitiVerifies(signerInfo.Certificate)) { + return; + } + + //calculate the hash using the first signed info + var hashAlg = IncrementalHash.CreateHash(new HashAlgorithmName(signerInfo.DigestAlgorithm.FriendlyName.ToUpper())); + byte[] calculatedHash = CalculatePeHashStreaming(hashAlg); + string hash = BitConverter.ToString(calculatedHash).Replace("-", ""); + + string content = BitConverter.ToString(cms.ContentInfo.Content).Replace("-", ""); + if (!content.Contains(hash)) { + throw new CryptographicException("The expected hash and the actual hash did not match!"); + } + } + + public async static Task VerifyTrust(X509Store cas, X509Certificate2 trustedRootCertificateAuthority, X509Certificate2 certificate) { + return await VerifyTrust(cas, trustedRootCertificateAuthority, certificate, false); + } + + public async static Task VerifyTrust(X509Store cas, X509Certificate2 trustedRootCertificateAuthority, X509Certificate2 certificate, bool verifyTrustedRoot) { + X509Chain chain = new X509Chain(); + chain.ChainPolicy.ExtraStore.Add(trustedRootCertificateAuthority); + chain.ChainPolicy.ExtraStore.AddRange(cas.Certificates); + chain.ChainPolicy.VerificationFlags = X509VerificationFlags.NoFlag; + chain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck; + chain.Build(new X509Certificate2(certificate)); + List exceptions = new List(); + foreach (X509ChainStatus status in chain.ChainStatus) { + if (status.Status == X509ChainStatusFlags.NoError || status.Status == X509ChainStatusFlags.UntrustedRoot) { + //X509ChainStatusFlags.UntrustedRoot simply means it was not found in the computer/user's trust store.... that's fine... + } else { + if (status.Status == X509ChainStatusFlags.NotTimeValid && certificate.Thumbprint == oldKnownThumbprint) { + //X509ChainStatusFlags.NotTimeValid means the certificate has expired - we're allowing this for now (summer 2021) to allow older clients to update + Logger.Warn("Executable is signed using the old signing certificate. Allowing this certificate to report as expired"); + } else { + exceptions.Add(new CryptographicException("Could not verify trust: " + status.StatusInformation)); + } + } + } + + //final check - make sure the last certificate matches the expected thumbprint + if (verifyTrustedRoot && chain.ChainElements[chain.ChainElements.Count - 1].Certificate.Thumbprint != trustedRootCertificateAuthority.Thumbprint) { + exceptions.Add(new Exception("Could not verify trust. The expected thumbprint was not found!")); + } + + foreach (var e in certificate.Extensions) { + if (e.Oid.Value == "2.5.29.31") { //2.5.29.31 == CRL Distribution Points + try { + CrlDistributionPointParser t = new CrlDistributionPointParser(e.RawData); + + if (t != null) { + foreach (string url in t.urls) { + try { + //good - things are going the way we want... keep going + //fetch the CRL from the url provided and parse it... + byte[] crlBytes = await new HttpClient().GetByteArrayAsync(url); + + if (crlBytes != null && crlBytes.Length > 0) { + //fetch the crl + Win32Crypto.CrlInfo info = Win32Crypto.FromBlob(crlBytes); + if (info.RevokedSerialNumbers.Contains(certificate.SerialNumber)) { + exceptions.Add(new CryptographicException("Serial number " + certificate.SerialNumber + " has been revoked.")); + } + } else { + exceptions.Add(new CryptographicException("Could not retrieve revocation list from " + url + "- cannot verify trust")); + } + } + catch (Exception innerException) { + exceptions.Add(new CryptographicException("crl at " + url + " could not be used", innerException)); + } + } + } + } + catch (Exception ex) { + exceptions.Add(ex); + } + } + } + + if (exceptions.Count > 0) { + throw new AggregateException(exceptions); + } + + return true; + } + + public byte[] CalculatePeHashStreaming(IncrementalHash alg) { + using (FileStream stream = new FileStream(FilePath, System.IO.FileMode.Open, System.IO.FileAccess.Read)) + using (BinaryReader reader = new BinaryReader(stream)) { + stream.Seek(ImportantHashPositions.Chunk1.Start, SeekOrigin.Begin); + alg.AppendData(reader.ReadBytes(ImportantHashPositions.Chunk1.Length)); + + stream.Seek(ImportantHashPositions.Chunk2.Start, SeekOrigin.Begin); + alg.AppendData(reader.ReadBytes(ImportantHashPositions.Chunk2.Length)); + + stream.Seek(ImportantHashPositions.Chunk3.Start, SeekOrigin.Begin); + alg.AppendData(reader.ReadBytes(ImportantHashPositions.Chunk3.Length)); + + int blockSize = 8192; + int read = 0; + byte[] buf = new byte[blockSize]; + + foreach (IMAGE_SECTION_HEADER h in ImportantHashPositions.SectionTableHeaders) { + stream.Seek(h.PointerToRawData, SeekOrigin.Begin); + if (h.SizeOfRawData <= blockSize) { + //just read the full block as one + alg.AppendData(reader.ReadBytes((int)h.SizeOfRawData)); + } else { + long pos = stream.Position; + long end = h.PointerToRawData + h.SizeOfRawData - blockSize; + //process the data into blocks + while (pos < end) { + read = stream.Read(buf, 0, blockSize); + alg.AppendData(buf, 0, read); + pos += blockSize; + } + + int remaining = (int)(end - pos + blockSize); + if (remaining > 0) { + read = stream.Read(buf, 0, remaining); + alg.AppendData(buf, 0, read); + } + } + } + + /* + 14. Create a value called FILE_SIZE, which is not part of the signature. Set this value to the + image’s file size, acquired from the underlying file system. If FILE_SIZE is greater than + SUM_OF_BYTES_HASHED, the file contains extra data that must be added to the hash. This data + begins at the SUM_OF_BYTES_HASHED file offset, and its length is: + (File Size) – ((Size of AttributeCertificateTable) + SUM_OF_BYTES_HASHED) + Note: The size of Attribute Certificate Table is specified in the second ULONG value in the + Certificate Table entry (32 bit: offset 132, 64 bit: offset 148) in Optional Header Data + Directories. + */ + long FILE_SIZE = new FileInfo(FilePath).Length; + + long leftoverBytesToHash = FILE_SIZE - ImportantHashPositions.SumOfBytesHashed - ImportantHashPositions.CertificateTableSize; + + if (leftoverBytesToHash > 0) { + //HashBlock(alg, reader, SUM_OF_BYTES_HASHED + certTableSize, FILE_SIZE); + stream.Seek(ImportantHashPositions.SumOfBytesHashed, SeekOrigin.Begin); + byte[] leftoverBytes = reader.ReadBytes((int)leftoverBytesToHash); + alg.AppendData(leftoverBytes); + } else { + //no more bytes to hash... + } + //alg.TransformFinalBlock(new byte[0], 0, 0); + + byte[] result = alg.GetHashAndReset(); + + return result; + } + } + + public class HashPositions { + internal HashPositions() { + Chunk1 = new Range(); + Chunk2 = new Range(); + Chunk3 = new Range(); + SectionTableHeaders = new List(); + } + /* + * These functions are all based from the document linked to from the bottom of: + * https://docs.microsoft.com/en-us/windows/win32/debug/pe-format#optional-header-standard-fields-image-only + * + * Link to the document: + * https://download.microsoft.com/download/9/c/5/9c5b2167-8017-4bae-9fde-d599bac8184a/Authenticode_PE.docx + * + * Relevant section copied from that document: + * To calculate the hash value: + 1. Load the image header into memory. + 2. Initialize a hash algorithm context. + 3. Hash the image header from its base to immediately before the start of the + checksum address, as specified in Optional Header Windows-Specific Fields. + 4. Skip over the checksum, which is a 4-byte field. + 5. Hash everything from the end of the checksum field to immediately before the start of + the Certificate Table entry, as specified in Optional Header Data Directories. + 6. Get the Attribute Certificate Table address and size from the Certificate Table entry. + For details, see section 5.7 of the PE/COFF specification. + 7. Exclude the Certificate Table entry from the calculation and hash everything from the + end of the Certificate Table entry to the end of image header, including Section Table + (headers).The Certificate Table entry is 8 bytes long, as specified in Optional Header + Data Directories. + 8. Create a counter called SUM_OF_BYTES_HASHED, which is not part of the signature. Set + this counter to the SizeOfHeaders field, as specified in Optional Header Windows-Specific + Field. + 9. Build a temporary table of pointers to all of the section headers in the image. The + NumberOfSections field of COFF File Header indicates how big the table should be. Do not + include any section headers in the table whose SizeOfRawData field is zero. + 10. Using the PointerToRawData field (offset 20) in the referenced SectionHeader structure as + a key, arrange the table's elements in ascending order. In other words, sort the section + headers in ascending order according to the disk-file offset of the sections. + 11. Walk through the sorted table, load the corresponding section into memory, and hash the entire + section. Use the SizeOfRawData field in the SectionHeader structure to determine the amount + of data to hash. + 12. Add the section’s SizeOfRawData value to SUM_OF_BYTES_HASHED. + 13. Repeat steps 11 and 12 for all of the sections in the sorted table. + 14. Create a value called FILE_SIZE, which is not part of the signature. Set this value to the + image’s file size, acquired from the underlying file system. If FILE_SIZE is greater than + SUM_OF_BYTES_HASHED, the file contains extra data that must be added to the hash. This data + begins at the SUM_OF_BYTES_HASHED file offset, and its length is: + (File Size) – ((Size of AttributeCertificateTable) + SUM_OF_BYTES_HASHED) + Note: The size of Attribute Certificate Table is specified in the second ULONG value in the + Certificate Table entry (32 bit: offset 132, 64 bit: offset 148) in Optional Header Data + Directories. + 15. Finalize the hash algorithm context. + Note: This procedure uses offset values from the PE/COFF specification, version 8.1 . + For authoritative offset values, refer to the most recent version of the PE/COFF specification. + */ + + public List SectionTableHeaders { get; private set; } + + /// + /// Chunk1 Start will set the position in the file which the hashing of the file should begin from + /// This is always the start of the file and thus is hardcoded to 0 and takes no parameters + /// + /// Chunk1 End is defined to be: "immediately before the start of the checksum address" + /// + /// Relevant step: + /// 3. Hash the image header from its base to immediately before the start of the + /// checksum address, as specified in Optional Header Windows-Specific Fields. + /// + public Range Chunk1 { get; private set; } + + /// + /// Chunk2 Start is defined to be: "end of the checksum field" + /// Chunk2 End is defined to be: "immediately before the start of the Certificate Table entry" + /// + /// Relevant step: + /// 5. Hash everything from the end of the checksum field to immediately before the start of + /// the Certificate Table entry, as specified in Optional Header Data Directories. + /// + public Range Chunk2 { get; private set; } + + /// + /// Chunk3 Start is defined to be: "the end of the Certificate Table entry" + /// Chunk3 End is defined to be: "the end of image header" + /// When testing/developing this solution - this was found to be the position specified by + /// the SizeOfHeaders field + /// + /// Relevant step: + /// 7. Exclude the Certificate Table entry from the calculation and hash everything from the + /// end of the Certificate Table entry to the end of image header, including Section Table + /// (headers).The Certificate Table entry is 8 bytes long, as specified in Optional Header + /// Data Directories. + /// + public Range Chunk3 { get; private set; } + + public uint SizeOfHeaders { get; private set; } + public uint SumOfBytesHashed { get; private set; } + public long CertificateTableStart { get; private set; } + public long CertificateTableSize { get; private set; } + + internal bool reorderNeeded = false; + private uint lastAddress = 0; + public void AddSectionTableHeader(IMAGE_SECTION_HEADER sectionTableHeader) { + + if (sectionTableHeader.SizeOfRawData > 0) { + SectionTableHeaders.Add(sectionTableHeader); + } + + if (lastAddress < sectionTableHeader.PointerToRawData) { + //good - it's in order... + } else { + reorderNeeded = true; + } + lastAddress = sectionTableHeader.PointerToRawData; + SumOfBytesHashed += sectionTableHeader.SizeOfRawData; + } + + public void SetStartCoffHeader(uint peHeaderStart) { + Chunk1.Start = 0; + uint checksumStart = peHeaderStart + 88; //88 is the size of the COFF Header (24) + Standard Headers + Windows-Specific Fields up to checksum (offset 64) + Chunk1.End = checksumStart; + Chunk2.Start = checksumStart + LengthOfChecksum; + + //and the four tables before the Certificate Table + //(ExportTable, ImportTable, ResourceTable, ExceptionTable) + uint certHeaderTableOffset = checksumStart + 64; + + Chunk2.End = certHeaderTableOffset; + Chunk3.Start = certHeaderTableOffset + LengthCertificateTable; + } + + public void SetCertificateDetails(PeType type, uint sizeOfHeaders, uint certificateTableStart, + uint certificateTableSize) { + if (type == PeType.Pe32Plus) { + uint winSpecificExtraBytes = 16; //need to account for the extra bytes in the Windows-Specific Fields (each field has 4 extra bytes) + Chunk2.End += winSpecificExtraBytes; + Chunk3.Start += winSpecificExtraBytes; + } + + Chunk3.End = sizeOfHeaders; + SizeOfHeaders = sizeOfHeaders; + SumOfBytesHashed = sizeOfHeaders; + CertificateTableStart = certificateTableStart; + CertificateTableSize = certificateTableSize; + } + } + + public class Range { + public long Start { get; internal set; } + public long End { get; internal set; } + + public int Length => (int)(End - Start); + } + } + + public enum PeType { + Pe32, + Pe32Plus + } + + internal class CrlDistributionPointParser { + internal int offset; + internal int length; + internal List tags = new List(); + private CrlDistributionPointParser parent = null; + private AsnTagType type = AsnTagType.NONE; + internal List urls = new List(); + private int consumedBytes = 0; + + internal CrlDistributionPointParser(byte[] asn1Data) : this(new MemoryStream(asn1Data)) { + + } + + internal CrlDistributionPointParser(MemoryStream asn1Data) : this(null, asn1Data/*new BinaryReader(asn1Data)*/, 0) { + + } + + internal CrlDistributionPointParser(CrlDistributionPointParser parent, MemoryStream memoryStream, /*BinaryReader asn1Reader,*/ int offset) { + BinaryReader asn1Reader = new BinaryReader(memoryStream); + this.offset = offset; + type = readTag(asn1Reader); + length = readTagLen(asn1Reader); + this.parent = parent; + int headerBytes = consumedBytes; + while (this.consumedBytes < length + headerBytes) { + switch (type) { + case AsnTagType.SEQUENCE: + case AsnTagType.ARRAY_ELEMENT: + var t = new CrlDistributionPointParser(this, memoryStream, (int) memoryStream.Position); + consumedBytes += t.consumedBytes; + tags.Add(t); + this.urls.AddRange(t.urls); + break; + case AsnTagType.STRING: + consumedBytes += length; + urls.Add(System.Text.Encoding.UTF8.GetString(asn1Reader.ReadBytes(length))); + break; + } + } + } + + internal AsnTagType readTag(BinaryReader asn1Data) { + byte tagByte = asn1Data.ReadByte(); + if (tagByte != 0x30 && tagByte != 0xA0 && tagByte != 0x86) { + throw new CryptographicException("Could not parse CRL Distribution Points. First byte not asn SEQUENCE|ARRAY|STRING marker"); + } + + consumedBytes++; + if (tagByte == 0x30) return AsnTagType.SEQUENCE; + if (tagByte == 0xA0) return AsnTagType.ARRAY_ELEMENT; + if (tagByte == 0x86) return AsnTagType.STRING; + return AsnTagType.NONE; + } + + internal int readTagLen(BinaryReader asn1Data) { + int tagLen = asn1Data.ReadByte(); + consumedBytes++; + if (tagLen > 128) { + //means there's more bytes to read that control the overall length of the next section + int moreBytes = tagLen - 128; + byte[] lenBytes = new byte[4]; + byte[] d = asn1Data.ReadBytes(moreBytes); + + int pos = 0; + for (int i = d.Length - 1; i >= 0; i--) { + lenBytes[pos++] = d[i]; + } + + tagLen = BitConverter.ToInt32(lenBytes, 0); + consumedBytes += moreBytes; + } + + return tagLen; + } + } + + internal enum AsnTagType { + SEQUENCE = 0x30, + ARRAY_ELEMENT = 0xA0, + STRING = 0x86, + NONE = 0, + } +} diff --git a/ZitiUpdateService/checkers/PeFile/StructHelper.cs b/ZitiUpdateService/checkers/PeFile/StructHelper.cs new file mode 100644 index 00000000..60670764 --- /dev/null +++ b/ZitiUpdateService/checkers/PeFile/StructHelper.cs @@ -0,0 +1,15 @@ +using System.Runtime.InteropServices; + +namespace ZitiUpdateService.Checkers.PeFile { + public static class StructHelper { + + public static T FromBytes(byte[] bytes) { + // Pin the managed memory while, copy it out the data, then unpin it + GCHandle handle = GCHandle.Alloc(bytes, GCHandleType.Pinned); + T theStructure = (T)Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof(T)); + handle.Free(); + + return theStructure; + } + } +} diff --git a/ZitiUpdateService/checkers/PeFile/Win32Crypto.cs b/ZitiUpdateService/checkers/PeFile/Win32Crypto.cs new file mode 100644 index 00000000..b84dd21e --- /dev/null +++ b/ZitiUpdateService/checkers/PeFile/Win32Crypto.cs @@ -0,0 +1,249 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Runtime.InteropServices; +using System.Text; + +namespace VerifyingFiles.PInvoke { + public static class Win32Crypto { + //based on https://docs.microsoft.com/en-us/archive/blogs/alejacma/how-to-get-information-from-a-crl-net + + #region APIs + [DllImport("CRYPT32.DLL", EntryPoint = "CryptQueryObject", CharSet = CharSet.Auto, SetLastError = true)] + public static extern Boolean CryptQueryObject( + Int32 dwObjectType, + [MarshalAs(UnmanagedType.LPWStr)] String pvObject, + Int32 dwExpectedContentTypeFlags, + Int32 dwExpectedFormatTypeFlags, + Int32 dwFlags, + IntPtr pdwMsgAndCertEncodingType, + IntPtr pdwContentType, + IntPtr pdwFormatType, + IntPtr phCertStore, + IntPtr phMsg, + ref IntPtr ppvContext + ); + [DllImport("CRYPT32.DLL", EntryPoint = "CryptQueryObject", CharSet = CharSet.Auto, SetLastError = true)] + public static extern Boolean CryptQueryObject2( + Int32 dwObjectType, + IntPtr pvObject, + Int32 dwExpectedContentTypeFlags, + Int32 dwExpectedFormatTypeFlags, + Int32 dwFlags, + IntPtr pdwMsgAndCertEncodingType, + IntPtr pdwContentType, + IntPtr pdwFormatType, + ref IntPtr phCertStore, + IntPtr phMsg, + ref IntPtr ppvContext + ); + + [DllImport("CRYPT32.DLL", EntryPoint = "CertFreeCRLContext", SetLastError = true)] + public static extern Boolean CertFreeCRLContext( + IntPtr pCrlContext + ); + + [DllImport("CRYPT32.DLL", EntryPoint = "CertNameToStr", CharSet = CharSet.Auto, SetLastError = true)] + public static extern Int32 CertNameToStr( + Int32 dwCertEncodingType, + ref CRYPTOAPI_BLOB pName, + Int32 dwStrType, + StringBuilder psz, + Int32 csz + ); + + [DllImport("CRYPT32.DLL", EntryPoint = "CertFindExtension", CharSet = CharSet.Auto, SetLastError = true)] + public static extern IntPtr CertFindExtension( + [MarshalAs(UnmanagedType.LPStr)] String pszObjId, + Int32 cExtensions, + IntPtr rgExtensions + ); + + [DllImport("CRYPT32.DLL", EntryPoint = "CryptFormatObject", CharSet = CharSet.Auto, SetLastError = true)] + public static extern Boolean CryptFormatObject( + Int32 dwCertEncodingType, + Int32 dwFormatType, + Int32 dwFormatStrType, + IntPtr pFormatStruct, + [MarshalAs(UnmanagedType.LPStr)] String lpszStructType, + IntPtr pbEncoded, + Int32 cbEncoded, + StringBuilder pbFormat, + ref Int32 pcbFormat + ); + + #endregion APIs + + #region Structs + + [StructLayout(LayoutKind.Sequential)] + public struct CRL_CONTEXT { + public Int32 dwCertEncodingType; + public IntPtr pbCrlEncoded; + public Int32 cbCrlEncoded; + public IntPtr pCrlInfo; + public IntPtr hCertStore; + } + + [StructLayout(LayoutKind.Sequential)] + public struct CRL_INFO { + public Int32 dwVersion; + public CRYPT_ALGORITHM_IDENTIFIER SignatureAlgorithm; + public CRYPTOAPI_BLOB Issuer; + public FILETIME ThisUpdate; + public FILETIME NextUpdate; + public Int32 cCRLEntry; + public IntPtr rgCRLEntry; + public Int32 cExtension; + public IntPtr rgExtension; + } + + [StructLayout(LayoutKind.Sequential)] + public struct CRYPT_ALGORITHM_IDENTIFIER { + [MarshalAs(UnmanagedType.LPStr)] public String pszObjId; + public CRYPTOAPI_BLOB Parameters; + } + + [StructLayout(LayoutKind.Sequential)] + public struct CRYPTOAPI_BLOB { + public Int32 cbData; + public IntPtr pbData; + } + + [StructLayout(LayoutKind.Sequential)] + public struct FILETIME { + public Int32 dwLowDateTime; + public Int32 dwHighDateTime; + } + + [StructLayout(LayoutKind.Sequential)] + public struct CRL_ENTRY { + public CRYPTOAPI_BLOB SerialNumber; + public FILETIME RevocationDate; + public Int32 cExtension; + public IntPtr rgExtension; + } + + [StructLayout(LayoutKind.Sequential)] + public struct CERT_EXTENSION { + [MarshalAs(UnmanagedType.LPStr)] public String pszObjId; + public Boolean fCritical; + public CRYPTOAPI_BLOB Value; + } + + #endregion Structs + + #region Consts + + public const Int32 CERT_QUERY_OBJECT_BLOB = 0x00000002; + public const Int32 CERT_QUERY_CONTENT_CRL = 3; + public const Int32 CERT_QUERY_CONTENT_FLAG_CRL = 1 << CERT_QUERY_CONTENT_CRL; + public const Int32 CERT_QUERY_FORMAT_BINARY = 1; + public const Int32 CERT_QUERY_FORMAT_BASE64_ENCODED = 2; + public const Int32 CERT_QUERY_FORMAT_ASN_ASCII_HEX_ENCODED = 3; + public const Int32 CERT_QUERY_FORMAT_FLAG_BINARY = 1 << CERT_QUERY_FORMAT_BINARY; + public const Int32 CERT_QUERY_FORMAT_FLAG_BASE64_ENCODED = 1 << CERT_QUERY_FORMAT_BASE64_ENCODED; + public const Int32 CERT_QUERY_FORMAT_FLAG_ASN_ASCII_HEX_ENCODED = 1 << CERT_QUERY_FORMAT_ASN_ASCII_HEX_ENCODED; + public const Int32 CERT_QUERY_FORMAT_FLAG_ALL = CERT_QUERY_FORMAT_FLAG_BINARY | CERT_QUERY_FORMAT_FLAG_BASE64_ENCODED | CERT_QUERY_FORMAT_FLAG_ASN_ASCII_HEX_ENCODED; + + public const Int32 X509_ASN_ENCODING = 0x00000001; + public const Int32 PKCS_7_ASN_ENCODING = 0x00010000; + + public const Int32 X509_NAME = 7; + + public const Int32 CERT_SIMPLE_NAME_STR = 1; + public const Int32 CERT_OID_NAME_STR = 2; + public const Int32 CERT_X500_NAME_STR = 3; + + public const String szOID_CRL_REASON_CODE = "2.5.29.21"; + + #endregion + + private static List GetRevokedSerialNumbers(CRL_INFO stCrlInfo) { + List rtn = new List(); + var rgCrlEntry = stCrlInfo.rgCRLEntry; + + for (var i = 0; i < stCrlInfo.cCRLEntry; i++) { + var serial = string.Empty; + var stCrlEntry = (CRL_ENTRY)Marshal.PtrToStructure(rgCrlEntry, typeof(CRL_ENTRY)); + + IntPtr pByte = stCrlEntry.SerialNumber.pbData; + for (var j = 0; j < stCrlEntry.SerialNumber.cbData; j++) { + Byte bByte = Marshal.ReadByte(pByte); + serial = bByte.ToString("X").PadLeft(2, '0') + serial; + pByte = pByte + Marshal.SizeOf(typeof(byte)); + + } + rtn.Add(serial); + rgCrlEntry = rgCrlEntry + Marshal.SizeOf(typeof(CRL_ENTRY)); + } + + return rtn; + } + + public static DateTime FileTimeToDateTime(FILETIME fileTime) { + DateTime dateTime; + IntPtr int64Ptr = Marshal.AllocHGlobal(sizeof(Int64)); + try { + Marshal.StructureToPtr(fileTime, int64Ptr, true); + dateTime = DateTime.FromFileTime(Marshal.ReadInt64(int64Ptr)); + } finally { + Marshal.FreeHGlobal(int64Ptr); + } + + return dateTime; + } + + public static CrlInfo FromBlob(byte[] CrlDERBytes) { + + var phCertStore = IntPtr.Zero; + var pvContext = IntPtr.Zero; + var hCrlData = new GCHandle(); + var hCryptBlob = new GCHandle(); + try { + hCrlData = GCHandle.Alloc(CrlDERBytes, GCHandleType.Pinned); + CRYPTOAPI_BLOB stCryptBlob; + stCryptBlob.cbData = CrlDERBytes.Length; + stCryptBlob.pbData = hCrlData.AddrOfPinnedObject(); + hCryptBlob = GCHandle.Alloc(stCryptBlob, GCHandleType.Pinned); + + if (!CryptQueryObject2( + CERT_QUERY_OBJECT_BLOB, + hCryptBlob.AddrOfPinnedObject(), + CERT_QUERY_CONTENT_FLAG_CRL, + CERT_QUERY_FORMAT_FLAG_BINARY, + 0, + IntPtr.Zero, + IntPtr.Zero, + IntPtr.Zero, + ref phCertStore, + IntPtr.Zero, + ref pvContext + )) { + throw new Win32Exception(Marshal.GetLastWin32Error(), "CRL is Corrupted."); + } + var stCrlContext = (CRL_CONTEXT)Marshal.PtrToStructure(pvContext, typeof(CRL_CONTEXT)); + var stCrlInfo = (CRL_INFO)Marshal.PtrToStructure(stCrlContext.pCrlInfo, typeof(CRL_INFO)); + + CrlInfo info = new CrlInfo(); + info.validTo = FileTimeToDateTime(stCrlInfo.NextUpdate); + info.validFrom = FileTimeToDateTime(stCrlInfo.ThisUpdate); + info.RevokedSerialNumbers = GetRevokedSerialNumbers(stCrlInfo); + + return info; + } finally { + if (hCrlData.IsAllocated) hCrlData.Free(); + if (hCryptBlob.IsAllocated) hCryptBlob.Free(); + if (!pvContext.Equals(IntPtr.Zero)) { + CertFreeCRLContext(pvContext); + } + } + } + + public struct CrlInfo { + public DateTime validTo; + public DateTime validFrom; + public List RevokedSerialNumbers; + } + } +} diff --git a/ZitiUpdateService/checkers/PeFile/openziti.rootCA.rsa.pem.txt b/ZitiUpdateService/checkers/PeFile/openziti.rootCA.rsa.pem.txt new file mode 100644 index 00000000..87464af1 --- /dev/null +++ b/ZitiUpdateService/checkers/PeFile/openziti.rootCA.rsa.pem.txt @@ -0,0 +1,33 @@ +-----BEGIN CERTIFICATE----- +MIIFozCCA4ugAwIBAgIUdhfTn8Eu4pU39MIPpoerTLqbLRkwDQYJKoZIhvcNAQEN +BQAwYTEYMBYGA1UEAwwPUm9vdCBTaWduaW5nIENBMRkwFwYDVQQKDBBvcGVueml0 +aS5vcmcgSW5jMRAwDgYDVQQLDAdhZHYtZGV2MQswCQYDVQQGEwJVUzELMAkGA1UE +CAwCTkMwHhcNMjEwNjAyMTcyNDMzWhcNNDEwNjE3MTcyNDMzWjBhMRgwFgYDVQQD +DA9Sb290IFNpZ25pbmcgQ0ExGTAXBgNVBAoMEG9wZW56aXRpLm9yZyBJbmMxEDAO +BgNVBAsMB2Fkdi1kZXYxCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJOQzCCAiIwDQYJ +KoZIhvcNAQEBBQADggIPADCCAgoCggIBALcLp/jh0zYqDQw6Gxzoxnib08QLwBo7 +AHLdJSOxdFy3K0a7xSEZjkhJgWQWw/c/HArw5PiHS4jdsh7o8mSJGLqBj+s/Gj4M +tM4k3A5jJHcgAbDKPAaAZAXQ2aEDp8eA65S1mRqNnYHbmTm3Q3t3U7dqI1lISzjz +k5jBQnXS3Qtlj2SyMXRYPpPJIQlXgZXdvLmL7wG1FpmCFBSEEdOVMIhEr5hJ/5rc +HpTRSueLPpdJUSv3B3uuPdo5rI/pJJ6gAJUtmMw/G8CAlMwXlUbSLt5IvNriyjqT +pisTjkQR0Vs8pvAN0CKfOqwoPWDwuJkump+LRTTQ0PLaNCnlU96mpoX9D7yhQAGP +C5h0N0jPV2MpnEFNVPaGE43Wj1zejVP1S5Ep3i/Xu/aSUGmIK5MQ95z/J927LBKe +AOyQDdQC0WkCBpCzpe0G9c+M0u7lYUHWXExWB22nQT2rM3aG5ZdOJRu42PXFNUr4 +bgkZq56PiCCgwDENOlln6/h8wD2a2o5n638BCwW3Qq4w8u2e7dKIAjNroQ053iyw +ZmSIwImf9N+0631d8LDoVLllry+geP1bSlI1UCYcVjJWGSDwEmK9h+kwUsMX+yl7 +TAzgrnAkBS+C62sDvGHRjhp82IDklqsBQV7SsRG5ZqHAipbIa0NfCURsqmQ6iyNR +a3/9oiE9ARdHAgMBAAGjUzBRMB0GA1UdDgQWBBS0v5OYBy16UEw7k7nMLg7Y3Fs7 +uDAfBgNVHSMEGDAWgBS0v5OYBy16UEw7k7nMLg7Y3Fs7uDAPBgNVHRMBAf8EBTAD +AQH/MA0GCSqGSIb3DQEBDQUAA4ICAQBPtyCaKyk+WhXI1Gu83h6ePKuevwwnBygr +xOEJM7b0kAxtcgK46B9wPgvLuUw1znmDzAuAqU52k00q1xLGZ+N5JNhlzzNWW0n2 +8Kj0gFLmu3tO7XXjQfQPKiZp/iiwOG9XPVVDTF2HVqkOsvdGYD2XH+wI4v/4m3wa +xTvliEz9NCei7/NOhuTbJvbvcSmqAvs3+gH55bzBvvr+Hx/krx8HMcBikx92S5nF +NKBkHIUQVqGoCHxGY/uM0xou5797faVEzLkaDle+oisMiy061pYI0bb9CRdHAU0r +UoemoY3UOKlyK57wdXKRizDxmEyND2zxPYrktZu0SybjZmaFOCHLLp1WrcTlcp3C +7Lsuj9Vzui2mjJuQzSTEyd1R04AT3xwb9slN+iHuN1dAGexVsENahW2q9ZAjEtbu +E6B+gOrSJ7WaL8R7AAz/E3tfU2bYm99JoGCPjI2/kP7udw7EqvoKYfJO1gbRo45z +37xJmDrxb2SJFEtn5mqPO63HjGXGiC6xI52Olmv4Csk3WOseO3j9/SwazDAMqZSh +dY0BN0DADO8kiohyF+Cq7nPR/tX17DpQiWFgJ4xoYHRFYCyb6QZ5jOzMhrZJa7cM +v7BIbBcUe1vcKJ+gD6dXMT77KakdkRSfuiuEAeh6GmjPC130ni1stn+chw0pymax +ZSpzkClEkQ== +-----END CERTIFICATE----- \ No newline at end of file diff --git a/ZitiUpdateService/checkers/UpdateCheckers.cs b/ZitiUpdateService/checkers/UpdateCheckers.cs index 582a15d8..374f1113 100644 --- a/ZitiUpdateService/checkers/UpdateCheckers.cs +++ b/ZitiUpdateService/checkers/UpdateCheckers.cs @@ -3,7 +3,7 @@ using System.IO; using NLog; -namespace ZitiUpdateService.checkers { +namespace ZitiUpdateService.Checkers { abstract class UpdateCheck { public abstract int IsUpdateAvailable(Version current); diff --git a/ZitiUpdateService/packages.config b/ZitiUpdateService/packages.config index 2acb7a14..4e54d782 100644 --- a/ZitiUpdateService/packages.config +++ b/ZitiUpdateService/packages.config @@ -5,4 +5,9 @@ - + + + + + + \ No newline at end of file diff --git a/release-notes.md b/release-notes.md index 611c4a40..0a509425 100644 --- a/release-notes.md +++ b/release-notes.md @@ -1,3 +1,17 @@ +# Release 1.9.7 + +## What's New +* none + +## Other changes: +* none + +## Bugs fixed: +* none + +## Dependency Updates +* tsdk is updated to v0.15.7, c-sdk 0.23.3 + # Release 1.9.6 ## What's New diff --git a/service/build.bat b/service/build.bat index e1b6b27a..3c390e0c 100644 --- a/service/build.bat +++ b/service/build.bat @@ -14,7 +14,7 @@ REM See the License for the specific language governing permissions and REM limitations under the License. REM SET REPO_URL=https://github.com/openziti/ziti-tunnel-sdk-c.git -SET ZITI_TUNNEL_REPO_BRANCH=v0.15.6 +SET ZITI_TUNNEL_REPO_BRANCH=v0.15.7 REM override the c sdk used in the build - leave blank for the same as specified in the tunneler sdk SET ZITI_SDK_C_BRANCH= REM the number of TCP connections the tunneler sdk can have at any one time diff --git a/service/ziti-tunnel/version.go b/service/ziti-tunnel/version.go index 5305d2a9..2307c033 100644 --- a/service/ziti-tunnel/version.go +++ b/service/ziti-tunnel/version.go @@ -20,9 +20,9 @@ package main const ( - Version = "1.9.6" - Revision = "ed9a214d5c33" + Version = "1.9.8" + Revision = "a763f2acac36" Branch = "main" BuildUser = "" - BuildDate = "2021-05-31 14:36:25" + BuildDate = "2021-06-07 18:26:10" ) diff --git a/version b/version index 7bc1c404..66beabb5 100644 --- a/version +++ b/version @@ -1 +1 @@ -1.9.6 +1.9.8