feat: add ERP agent pet bridge and startup guide
This commit is contained in:
@@ -0,0 +1,178 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user