using System; using System.Diagnostics; using System.IO; using System.Runtime.InteropServices; using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; namespace Lskj.AgentBridge { /// /// Binds a named-pipe UAT request to the actual Windows client process. /// The customer-signed authorization pins the complete executable SHA-256; /// therefore an Authenticode mutation, certificate transplant or unsigned /// replacement necessarily changes the approved hash. The embedded signer /// thumbprint is checked as a second independent identity dimension. /// public sealed class WindowsBridgeClientProcessIdentityVerifier : IBridgeClientProcessIdentityVerifier { private const uint TokenQuery = 0x0008; private const int TokenElevationClass = 20; public bool Verify( int processId, WorkflowUatExecutableIdentity expectedIdentity) { if (Environment.OSVersion.Platform != PlatformID.Win32NT || processId <= 0 || expectedIdentity == null || string.IsNullOrWhiteSpace(expectedIdentity.FileName) || !Lskj.CommandKernel.CommandInputFingerprint.IsValid( expectedIdentity.Sha256) || WindowsTrustedPeopleSignatureVerifier.NormalizeThumbprint( expectedIdentity.SignerThumbprint) == null) return false; try { using (Process process = Process.GetProcessById(processId)) { if (process.HasExited || process.MainModule == null) return false; string path = Path.GetFullPath(process.MainModule.FileName); if (!string.Equals( Path.GetFileName(path), expectedIdentity.FileName, StringComparison.OrdinalIgnoreCase)) return false; FileInfo file = new FileInfo(path); if (!file.Exists || file.Length <= 0 || file.Length > 128L * 1024L * 1024L || (file.Attributes & (FileAttributes.Directory | FileAttributes.Device | FileAttributes.ReparsePoint)) != 0 || !NoReparseDirectoryChain(file.Directory)) return false; string actualHash; using (FileStream stream = new FileStream( file.FullName, FileMode.Open, FileAccess.Read, FileShare.Read)) using (SHA256 sha = SHA256.Create()) actualHash = Hex(sha.ComputeHash(stream)); if (!WorkflowUatAuthorizationVerifier.FixedEquals( actualHash, expectedIdentity.Sha256)) return false; string actualSigner = SignerThumbprint(file.FullName); if (!string.Equals( actualSigner, WindowsTrustedPeopleSignatureVerifier.NormalizeThumbprint( expectedIdentity.SignerThumbprint), StringComparison.Ordinal)) return false; return !expectedIdentity.RequiresElevation || IsElevated(process.Handle); } } catch { return false; } } private static bool NoReparseDirectoryChain(DirectoryInfo directory) { try { DirectoryInfo current = directory; while (current != null) { if (!current.Exists || (current.Attributes & FileAttributes.ReparsePoint) != 0) return false; current = current.Parent; } return true; } catch { return false; } } private static string SignerThumbprint(string path) { X509Certificate certificate = null; X509Certificate2 certificate2 = null; try { certificate = X509Certificate.CreateFromSignedFile(path); certificate2 = new X509Certificate2(certificate); return WindowsTrustedPeopleSignatureVerifier.NormalizeThumbprint( certificate2.Thumbprint); } catch { return null; } finally { if (certificate2 != null) certificate2.Reset(); } } private static bool IsElevated(IntPtr processHandle) { IntPtr token = IntPtr.Zero; try { if (processHandle == IntPtr.Zero || !OpenProcessToken(processHandle, TokenQuery, out token) || token == IntPtr.Zero) return false; TokenElevation elevation; int returned; int size = Marshal.SizeOf(typeof(TokenElevation)); return GetTokenInformation( token, TokenElevationClass, out elevation, size, out returned) && returned == size && elevation.TokenIsElevated != 0; } catch { return false; } finally { if (token != IntPtr.Zero) CloseHandle(token); } } private static string Hex(byte[] bytes) { return BitConverter.ToString(bytes).Replace("-", string.Empty) .ToLowerInvariant(); } [StructLayout(LayoutKind.Sequential)] private struct TokenElevation { public int TokenIsElevated; } [DllImport("advapi32.dll", SetLastError = true)] private static extern bool OpenProcessToken( IntPtr processHandle, uint desiredAccess, out IntPtr tokenHandle); [DllImport("advapi32.dll", SetLastError = true)] private static extern bool GetTokenInformation( IntPtr tokenHandle, int tokenInformationClass, out TokenElevation tokenInformation, int tokenInformationLength, out int returnLength); [DllImport("kernel32.dll", SetLastError = true)] private static extern bool CloseHandle(IntPtr handle); } }