179 lines
6.7 KiB
C#
179 lines
6.7 KiB
C#
using System;
|
|
using System.Diagnostics;
|
|
using System.IO;
|
|
using System.Runtime.InteropServices;
|
|
using System.Security.Cryptography;
|
|
using System.Security.Cryptography.X509Certificates;
|
|
|
|
namespace Lskj.AgentBridge
|
|
{
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
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);
|
|
}
|
|
}
|