XFE Git
XFE Studio Git
Git 首页 全局搜索
XFE 主站 文档 NuGet

XFESystem

【Rust】XFE系统,基于Rust和汇编

公开
关注 0 Fork 0 Star 0
UTF-8
using System.Runtime.InteropServices;
using System.Security.Cryptography;
using System.Text.Json;
using Org.BouncyCastle.Crypto.Prng;
using Org.BouncyCastle.Security;
using Entropy = XFESystem.NativeEntropy.NativeEntropy;

if (args.Length != 2 || args[0] is not ("production" or "failure" or "missing-export")) throw new ArgumentException("AdapterChecks <production|failure|missing-export> <native dll>");
string mode = args[0], libraryPath = Path.GetFullPath(args[1]);
nint library = NativeLibrary.Load(libraryPath);
NativeLibrary.SetDllImportResolver(typeof(Entropy).Assembly, (name, _, _) => name == "xfe_pal" ? library : 0);
var checks = new List<object>(); bool passed = false; string? failure = null;
void Check(string name, bool condition)
{ checks.Add(new { Name = name, Passed = condition }); if (!condition) throw new InvalidOperationException(name); }
void Throws<T>(string name, Action action) where T : Exception
{
    bool thrown = false;
    try { action(); } catch (T) { thrown = true; }
    Check(name, thrown);
}
bool HasCryptoFailure(Exception error)
{
    for (Exception? current = error; current is not null; current = current.InnerException)
        if (current is CryptographicException && current.Message.Contains("native hardware entropy source failed", StringComparison.Ordinal)) return true;
    return false;
}
void BouncyCastleFailsClosed()
{
    bool rejected = false;
    try { new SecureRandom().NextBytes(new byte[32]); }
    catch (Exception error) { rejected = HasCryptoFailure(error); }
    Check("PatchedBouncyCastleDefaultSeedingRejectsUnavailableHardware", rejected);
}

try
{
    Check("AdapterHasNoBouncyCastleDependency", !typeof(Entropy).Assembly.GetReferencedAssemblies().Any(name => name.Name!.Contains("BouncyCastle", StringComparison.Ordinal)));
    Check("PrivateBouncyCastleReferencesNativeAdapter", typeof(SecureRandom).Assembly.GetReferencedAssemblies().Any(name => name.Name == "XFESystem.NativeEntropy"));
    Entropy.Fill(Span<byte>.Empty); Check("EmptySpanDoesNotRequireHardware", true);
    byte[] tooLarge = Enumerable.Repeat((byte)0x55, Entropy.MaximumRequestBytes + 1).ToArray();
    Throws<ArgumentOutOfRangeException>("OneMiBBoundRejectedBeforeNativeAccess", () => Entropy.Fill(tooLarge));
    Check("OversizedBufferRemainsUntouched", tooLarge.All(value => value == 0x55));
    using var generator = Entropy.Create();
    Check("CreateUsesOnlyTheExplicitHardwareSubclass", generator.GetType().Name.Contains("HardwareRandomNumberGenerator", StringComparison.Ordinal));
    Throws<ArgumentNullException>("NullGeneratorRejected", () => Entropy.GetBytes(null!, new byte[4]));
    Throws<ArgumentNullException>("NullArrayRejected", () => Entropy.GetBytes(generator, (byte[])null!));
    Throws<ArgumentOutOfRangeException>("InvalidSliceRejected", () => Entropy.GetBytes(generator, new byte[4], 3, 2));
    using var foreign = new ForeignRandom();
    Throws<CryptographicException>("ExplicitForeignProviderRejected", () => Entropy.GetBytes(foreign, new byte[32]));
    Check("ForeignProviderNeverRan", foreign.Calls == 0);
    Entropy.Dispose(generator); Entropy.Dispose(generator);
    Throws<ObjectDisposedException>("DisposedStaticArrayRejected", () => Entropy.GetBytes(generator, new byte[4]));
    Throws<ObjectDisposedException>("DisposedStaticSliceRejected", () => Entropy.GetBytes(generator, new byte[8], 2, 4));
    Throws<ObjectDisposedException>("DisposedStaticSpanRejected", () => Entropy.GetBytes(generator, new byte[8].AsSpan(2, 4)));
    Throws<ObjectDisposedException>("DisposedInstanceRejected", () => generator.GetBytes(new byte[4]));

    if (mode == "production")
    {
        byte[] first = new byte[32], second = new byte[32]; Entropy.Fill(first); Entropy.Fill(second);
        Check("ActualProductionCpuBytesAreNonconstant", first.Distinct().Count() > 1 && second.Distinct().Count() > 1);
        Check("SeparateActualHardwareCallsDiffer", !first.SequenceEqual(second));
        using var actual = Entropy.Create(); byte[] data = Enumerable.Repeat((byte)0x55, 80).ToArray();
        Entropy.GetBytes(actual, data, 16, 32);
        Check("OffsetPrefixAndSuffixUntouched", data.Take(16).All(value => value == 0x55) && data.Skip(48).All(value => value == 0x55));
        Check("OffsetSliceUsesActualHardware", data.Skip(16).Take(32).Distinct().Count() > 1);
        Entropy.GetBytes(actual, data.AsSpan(8, 24)); Check("StaticSpanUsesActualHardware", data.Skip(8).Take(24).Distinct().Count() > 1);
        actual.GetBytes(data); Check("InstanceArrayUsesActualHardware", data.Distinct().Count() > 1);
        actual.GetBytes(data.AsSpan(8, 24)); Check("InstanceSpanUsesActualHardware", data.Skip(8).Take(24).Distinct().Count() > 1);
        actual.GetBytes(data, 8, 24); Check("InstanceSliceUsesActualHardware", data.Skip(8).Take(24).Distinct().Count() > 1);
        var random = new SecureRandom(); random.NextBytes(first);
        Check("PatchedBouncyCastleDefaultSecureRandomSeeding", first.Distinct().Count() > 1);
        var api = new CryptoApiRandomGenerator(); api.NextBytes(first); api.Dispose();
        Check("PatchedBouncyCastleCryptoApiRandomGenerator", first.Distinct().Count() > 1);
        byte[] seed = new CryptoApiEntropySourceProvider().Get(256).GetEntropy();
        Check("PatchedBouncyCastleCryptoApiEntropySourceProvider", seed.Length == 32 && seed.Distinct().Count() > 1);
        SecureRandom.GetInstance("SHA256PRNG", true).NextBytes(second);
        Check("PatchedBouncyCastleAutoSeededSha256Prng", second.Distinct().Count() > 1);
    }
    else
    {
        byte[] data = Enumerable.Repeat((byte)0x55, 64).ToArray();
        if (mode == "missing-export")
            Throws<EntryPointNotFoundException>("MissingNativeEntryIsRefused", () => Entropy.Fill(data.AsSpan(8, 32)));
        else
            Throws<CryptographicException>("EveryNonSuccessNativeResultIsRefused", () => Entropy.Fill(data.AsSpan(8, 32)));
        Check("FailureClearsWholeDestinationSpan", data.Skip(8).Take(32).All(value => value == 0));
        Check("FailureDoesNotTouchOutsideDestinationSpan", data.Take(8).Concat(data.Skip(40)).All(value => value == 0x55));
        if (mode == "failure") BouncyCastleFailsClosed();
    }
    passed = true;
}
catch (Exception error) { failure = error.ToString(); }
Console.WriteLine(JsonSerializer.Serialize(new { Kind = "NativeEntropyAdapterHostChecks", Passed = passed, Mode = mode,
    HardwareEntropyProof = mode == "production", FailureOnlyTestProvider = mode == "failure", GuestExecutionVerified = false,
    GuestTlsVerified = false, EntropyQualityCertified = false, NativeLibrary = libraryPath, Checks = checks, Failure = failure }));
return passed ? 0 : 1;

sealed class ForeignRandom : RandomNumberGenerator
{
    public int Calls { get; private set; }
    public override void GetBytes(byte[] data) { Calls++; throw new InvalidOperationException("A foreign source must never run."); }
}