forked from Manuel/meeting-assistant
151 lines
5.0 KiB
C#
151 lines
5.0 KiB
C#
#:property PublishAot=false
|
|
#:property UseAppHost=false
|
|
#:property AssemblyName=WineSdkTrust
|
|
|
|
using System.Runtime.InteropServices;
|
|
using System.Security.Cryptography;
|
|
using System.Security.Cryptography.X509Certificates;
|
|
|
|
if (args.Length != 3 || (args[0] != "--validate" && args[0] != "--import"))
|
|
{
|
|
Console.Error.WriteLine("Usage: WineSdkTrust <--validate|--import> <codesignctl.pem> <timestampctl.pem>");
|
|
return 2;
|
|
}
|
|
|
|
var import = args[0] == "--import";
|
|
if (import && (!OperatingSystem.IsWindows() || !IsWine()))
|
|
{
|
|
Console.Error.WriteLine("REFUSED: --import requires Windows under Wine (ntdll.dll!wine_get_version). No certificate store was opened.");
|
|
return 2;
|
|
}
|
|
|
|
Console.WriteLine($"OS: {RuntimeInformation.OSDescription}");
|
|
Console.WriteLine($"Runtime: {RuntimeInformation.FrameworkDescription}");
|
|
Console.WriteLine($"Mode: {args[0]}");
|
|
var certificates = new X509Certificate2Collection();
|
|
try
|
|
{
|
|
foreach (var path in args.Skip(1))
|
|
{
|
|
if (!File.Exists(path) || new FileInfo(path).Length == 0)
|
|
{
|
|
throw new InvalidDataException($"The SDK certificate bundle is missing or empty: {path}");
|
|
}
|
|
|
|
var bundle = new X509Certificate2Collection();
|
|
try
|
|
{
|
|
bundle.ImportFromPemFile(path);
|
|
if (bundle.Count == 0)
|
|
{
|
|
throw new InvalidDataException($"The SDK bundle contains no PEM certificates: {path}");
|
|
}
|
|
|
|
var legacyRoots = 0;
|
|
foreach (var certificate in bundle)
|
|
{
|
|
if (certificate.HasPrivateKey)
|
|
{
|
|
throw new InvalidDataException($"The SDK bundle must contain public certificates only: {certificate.Thumbprint}");
|
|
}
|
|
|
|
var constraints = certificate.Extensions.OfType<X509BasicConstraintsExtension>().SingleOrDefault();
|
|
if (constraints is { CertificateAuthority: false })
|
|
{
|
|
throw new InvalidDataException($"The SDK bundle contains a non-CA certificate: {certificate.Thumbprint}");
|
|
}
|
|
if (constraints is null)
|
|
{
|
|
// Microsoft also ships historical roots without the BasicConstraints extension.
|
|
if (!certificate.SubjectName.RawData.AsSpan().SequenceEqual(certificate.IssuerName.RawData))
|
|
{
|
|
throw new InvalidDataException($"A certificate without CA constraints is not self-issued: {certificate.Thumbprint}");
|
|
}
|
|
legacyRoots++;
|
|
}
|
|
}
|
|
|
|
Console.WriteLine($"Bundle: {Path.GetFullPath(path)}");
|
|
Console.WriteLine($" SHA256: {Convert.ToHexString(SHA256.HashData(File.ReadAllBytes(path)))}");
|
|
Console.WriteLine($" Certificates: {bundle.Count}; historical self-issued roots without BasicConstraints: {legacyRoots}");
|
|
certificates.AddRange(bundle);
|
|
bundle.Clear();
|
|
}
|
|
finally
|
|
{
|
|
foreach (var certificate in bundle)
|
|
{
|
|
certificate.Dispose();
|
|
}
|
|
}
|
|
}
|
|
|
|
var thumbprints = certificates.Select(certificate => certificate.Thumbprint).ToHashSet(StringComparer.OrdinalIgnoreCase);
|
|
Console.WriteLine($"Unique SDK certificate thumbprints: {thumbprints.Count}");
|
|
if (!import)
|
|
{
|
|
Console.WriteLine("PASS: both SDK bundles validated; no certificate store was opened.");
|
|
return 0;
|
|
}
|
|
|
|
using (var store = new X509Store(StoreName.Root, StoreLocation.CurrentUser))
|
|
{
|
|
store.Open(OpenFlags.ReadWrite);
|
|
store.AddRange(certificates);
|
|
}
|
|
|
|
using (var store = new X509Store(StoreName.Root, StoreLocation.CurrentUser))
|
|
{
|
|
store.Open(OpenFlags.ReadOnly);
|
|
var installed = store.Certificates;
|
|
try
|
|
{
|
|
var installedThumbprints = installed.Select(certificate => certificate.Thumbprint).ToHashSet(StringComparer.OrdinalIgnoreCase);
|
|
var missing = thumbprints.Except(installedThumbprints).ToArray();
|
|
if (missing.Length != 0)
|
|
{
|
|
throw new CryptographicException($"SDK certificates missing after import: {string.Join(", ", missing)}");
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
foreach (var certificate in installed)
|
|
{
|
|
certificate.Dispose();
|
|
}
|
|
}
|
|
}
|
|
|
|
Console.WriteLine($"PASS: all {thumbprints.Count} SDK certificate thumbprints verified in CurrentUser Root.");
|
|
return 0;
|
|
}
|
|
catch (Exception exception)
|
|
{
|
|
Console.Error.WriteLine($"FAIL: {exception.GetType().Name}: {exception.Message}");
|
|
return 1;
|
|
}
|
|
finally
|
|
{
|
|
foreach (var certificate in certificates)
|
|
{
|
|
certificate.Dispose();
|
|
}
|
|
}
|
|
|
|
static bool IsWine()
|
|
{
|
|
if (!NativeLibrary.TryLoad("ntdll.dll", out var library))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
try
|
|
{
|
|
return NativeLibrary.TryGetExport(library, "wine_get_version", out _);
|
|
}
|
|
finally
|
|
{
|
|
NativeLibrary.Free(library);
|
|
}
|
|
}
|