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

XFEExtension.NetCore.ServerInteractive

[DLL] Server interaction extension, including user identity verification and querying in conjunction with AutoConfig

公开
关注 0 Fork 0 Star 0
UTF-8
using System.Net;
using System.Net.Sockets;
using System.Net.WebSockets;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Text.Json;
using XFEExtension.NetCore.CyberComm;
using XFEExtension.NetCore.ServerInteractive.Models;
using Xunit;
using XFEExtension.NetCore.ServerInteractive.Models.UserModels;
using XFEExtension.NetCore.ServerInteractive.Utilities.Extensions;
using XFEExtension.NetCore.ServerInteractive.Utilities.Helpers;
using XFEExtension.NetCore.ServerInteractive.Utilities.Requester;
using XFEExtension.NetCore.ServerInteractive.Utilities.Server;

namespace XFEExtension.NetCore.ServerInteractive.Test;

public sealed class CyberCommIntegrationTests
{
    [Fact]
    public async Task HttpServer_ProcessesPostAndStopsCleanly()
    {
        var port = GetFreePort();
        await using var server = CreateServer(port);
        server.HttpRequestHandler = async (context, _) =>
        {
            await context.Response.WriteTextAsync(context.RequestBody, HttpStatusCode.OK, "application/json");
            context.Response.Complete();
        };

        await server.StartAsync();
        using var client = new HttpClient();
        using var response = await client.PostAsync($"http://127.0.0.1:{port}/echo", new StringContent("{\"ok\":true}", Encoding.UTF8, "application/json"));
        Assert.Equal(HttpStatusCode.OK, response.StatusCode);
        Assert.Equal("{\"ok\":true}", await response.Content.ReadAsStringAsync());
        await server.StopAsync();
        Assert.Equal(CyberCommServerState.Stopped, server.State);
    }

    [Fact]
    public async Task HttpServer_RejectsConflictingContentLength()
    {
        var port = GetFreePort();
        await using var server = CreateServer(port);
        await server.StartAsync();

        using var client = new TcpClient();
        await client.ConnectAsync(IPAddress.Loopback, port);
        await using var stream = client.GetStream();
        var request = $"POST / HTTP/1.1\r\nHost: 127.0.0.1:{port}\r\nContent-Length: 1\r\nContent-Length: 2\r\nConnection: close\r\n\r\nx";
        await stream.WriteAsync(Encoding.ASCII.GetBytes(request));
        var buffer = new byte[1024];
        var count = await stream.ReadAsync(buffer);
        Assert.StartsWith("HTTP/1.1 400", Encoding.ASCII.GetString(buffer, 0, count));
    }

    [Fact]
    public async Task HttpServer_ReadsChunkedRequestBody()
    {
        var port = GetFreePort();
        await using var server = CreateServer(port);
        server.HttpRequestHandler = async (context, _) =>
        {
            await context.Response.WriteTextAsync(context.RequestBody);
            context.Response.Complete();
        };
        await server.StartAsync();

        using var client = new TcpClient();
        await client.ConnectAsync(IPAddress.Loopback, port);
        await using var stream = client.GetStream();
        var request = $"POST /chunk HTTP/1.1\r\nHost: 127.0.0.1:{port}\r\nTransfer-Encoding: chunked\r\nConnection: close\r\n\r\n5\r\nhello\r\n0\r\n\r\n";
        await stream.WriteAsync(Encoding.ASCII.GetBytes(request));
        using var output = new MemoryStream();
        var buffer = new byte[1024];
        int count;
        while ((count = await stream.ReadAsync(buffer)) > 0) await output.WriteAsync(buffer.AsMemory(0, count));
        Assert.EndsWith("hello", Encoding.UTF8.GetString(output.ToArray()));
    }

    [Fact]
    public async Task WebSocketClientAndServer_EchoAndDisconnectWithoutReconnect()
    {
        var port = GetFreePort();
        await using var server = CreateServer(port);
        server.WebSocketMessageHandler = async (args, _) =>
        {
            if (args.MessageType == BackMessageType.Text) await args.ReplyMessage(args.TextMessage!);
        };
        await server.StartAsync();

        await using var client = new CyberCommClient(new CyberCommClientOptions
        {
            ServerUri = new Uri($"ws://127.0.0.1:{port}/ws"),
            Reconnect = new() { Enabled = false }
        });
        var received = new TaskCompletionSource<string>(TaskCreationOptions.RunContinuationsAsynchronously);
        client.MessageHandler = (args, _) =>
        {
            if (args.MessageType == BackMessageType.Text) received.TrySetResult(args.TextMessage!);
            return ValueTask.CompletedTask;
        };
        await client.ConnectAsync();
        Assert.True((await client.SendTextAsync("echo")).IsSuccess);
        Assert.Equal("echo", await received.Task.WaitAsync(TimeSpan.FromSeconds(5)));
        await Task.WhenAll(Enumerable.Range(0, 8).Select(_ => client.DisconnectAsync()));
        Assert.Equal(CyberCommClientState.Disconnected, client.State);
        await Task.Delay(200);
        Assert.False(client.IsConnected);
    }

    [Fact]
    public async Task WebSocketClient_AbnormalDisconnectCreatesANewSocketForReconnect()
    {
        var port = GetFreePort();
        await using var server = CreateServer(port);
        server.WebSocketMessageHandler = (args, _) =>
        {
            if (args.TextMessage == "drop") args.CurrentWebSocket.Abort();
            return ValueTask.CompletedTask;
        };
        await server.StartAsync();

        await using var client = new CyberCommClient(new CyberCommClientOptions
        {
            ServerUri = new Uri($"ws://127.0.0.1:{port}/ws"),
            Reconnect = new()
            {
                Enabled = true,
                MaxAttempts = 10,
                InitialDelay = TimeSpan.FromMilliseconds(20),
                MaxDelay = TimeSpan.FromMilliseconds(100),
                JitterRatio = 0
            }
        });
        var connectionCount = 0;
        var reconnected = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
        client.ConnectedHandler = _ =>
        {
            if (Interlocked.Increment(ref connectionCount) == 2) reconnected.TrySetResult();
            return ValueTask.CompletedTask;
        };

        await client.ConnectAsync();
        var firstSocket = client.ClientWebSocket;
        Assert.True((await client.SendTextAsync("drop")).IsSuccess);
        await reconnected.Task.WaitAsync(TimeSpan.FromSeconds(5));
        Assert.NotSame(firstSocket, client.ClientWebSocket);
        Assert.Equal(CyberCommClientState.Connected, client.State);
    }

    [Fact]
    public async Task HttpsAndWss_UseTheSameBclTlsTransport()
    {
        var port = GetFreePort();
        using var certificate = CreateCertificate();
        await using var server = new CyberCommServer(new CyberCommServerOptions
        {
            ListenUrls = [$"https://127.0.0.1:{port}/"],
            Tls = new() { Certificate = certificate }
        });
        server.HttpRequestHandler = async (context, _) =>
        {
            await context.Response.WriteTextAsync("secure");
            context.Response.Complete();
        };
        server.ErrorHandler = Console.WriteLine;
        server.WebSocketMessageHandler = async (args, _) => await args.ReplyMessage(args.TextMessage!);
        await server.StartAsync();

        using var handler = new HttpClientHandler { ServerCertificateCustomValidationCallback = static (_, _, _, _) => true };
        using var httpClient = new HttpClient(handler);
        Assert.Equal("secure", await httpClient.GetStringAsync($"https://127.0.0.1:{port}/health"));

        await using var webSocketClient = new CyberCommClient(new CyberCommClientOptions
        {
            ServerUri = new Uri($"wss://127.0.0.1:{port}/ws"),
            Reconnect = new() { Enabled = false },
            RemoteCertificateValidationCallback = static (_, _, _, _) => true
        });
        var received = new TaskCompletionSource<string>(TaskCreationOptions.RunContinuationsAsynchronously);
        webSocketClient.MessageHandler = (args, _) =>
        {
            if (args.TextMessage is not null) received.TrySetResult(args.TextMessage);
            return ValueTask.CompletedTask;
        };
        await webSocketClient.ConnectAsync();
        Assert.True((await webSocketClient.SendTextAsync("wss")).IsSuccess);
        Assert.Equal("wss", await received.Task.WaitAsync(TimeSpan.FromSeconds(5)));
    }

    [Fact]
    public async Task TlsOptions_LoadExternalPfxAndPemFiles()
    {
        var testDirectory = Path.Combine(Path.GetTempPath(), $"cybercomm-tls-{Guid.NewGuid():N}");
        Directory.CreateDirectory(testDirectory);
        try
        {
            using var certificate = CreateCertificate();
            const string password = "test-only-password";
            var pfxPath = Path.Combine(testDirectory, "server.pfx");
            var certificatePath = Path.Combine(testDirectory, "server.crt.pem");
            var privateKeyPath = Path.Combine(testDirectory, "server.key.pem");
            await File.WriteAllBytesAsync(pfxPath, certificate.Export(X509ContentType.Pkcs12, password));
            await File.WriteAllTextAsync(certificatePath, certificate.ExportCertificatePem());
            using (var privateKey = certificate.GetRSAPrivateKey())
                await File.WriteAllTextAsync(privateKeyPath, privateKey!.ExportPkcs8PrivateKeyPem());

            await AssertTlsConfigurationStartsAsync(new CyberCommTlsOptions
            {
                PfxPath = pfxPath,
                PfxPassword = password
            });
            await AssertTlsConfigurationStartsAsync(new CyberCommTlsOptions
            {
                CertificatePemPath = certificatePath,
                PrivateKeyPemPath = privateKeyPath
            });
        }
        finally
        {
            Directory.Delete(testDirectory, true);
        }
    }

    [Fact]
    public async Task Lifecycle_ConcurrentCallsShareOneTransition()
    {
        var port = GetFreePort();
        await using var server = CreateServer(port);
        await Task.WhenAll(Enumerable.Range(0, 8).Select(_ => server.StartAsync()));
        Assert.Equal(CyberCommServerState.Running, server.State);
        await Task.WhenAll(Enumerable.Range(0, 8).Select(_ => server.StopAsync()));
        Assert.Equal(CyberCommServerState.Stopped, server.State);
    }

    [Fact]
    public async Task StopAsync_WaitsForActiveRequestButNotForKeepAliveIdleTimeout()
    {
        var port = GetFreePort();
        await using var server = new CyberCommServer(new CyberCommServerOptions
        {
            ListenUrls = [$"http://127.0.0.1:{port}/"],
            Limits = new()
            {
                HandlerTimeout = TimeSpan.FromSeconds(5),
                IdleTimeout = TimeSpan.FromSeconds(5),
                CloseTimeout = TimeSpan.FromSeconds(2)
            }
        });
        var started = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
        var release = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
        server.HttpRequestHandler = async (context, cancellationToken) =>
        {
            started.TrySetResult();
            await release.Task.WaitAsync(cancellationToken);
            await context.Response.WriteTextAsync("finished");
            context.Response.Complete();
        };
        await server.StartAsync();

        using var client = new HttpClient();
        var request = client.GetAsync($"http://127.0.0.1:{port}/work");
        await started.Task.WaitAsync(TimeSpan.FromSeconds(2));
        var stop = server.StopAsync();
        await Task.Delay(50);
        Assert.False(stop.IsCompleted);
        release.TrySetResult();

        using var response = await request;
        Assert.Equal("finished", await response.Content.ReadAsStringAsync());
        await stop.WaitAsync(TimeSpan.FromSeconds(1));
        Assert.Equal(CyberCommServerState.Stopped, server.State);
    }

    [Fact]
    public async Task ServerInteractive_SharesOneCyberCommListenerAcrossBasePaths()
    {
        var port = GetFreePort();
        var listenUrl = $"http://0.0.0.0:{port}/";
        var firstCore = XFEServerCoreBuilder.CreateBuilder()
            .AddConnectService()
            .Build(options =>
            {
                options.ServerCoreName = "first";
                options.MainEntryPoint = "first";
                options.AcceptGet = true;
                options.BindIP(listenUrl);
            });
        var secondCore = XFEServerCoreBuilder.CreateBuilder()
            .AddConnectService()
            .Build(options =>
            {
                options.ServerCoreName = "second";
                options.MainEntryPoint = "second";
                options.AcceptGet = true;
                options.BindIP(listenUrl);
            });
        await using var server = XFEServerBuilder.CreateBuilder()
            .UseXFEServer()
            .AddServerCore(firstCore)
            .AddServerCore(secondCore)
            .Build();
        await server.StartAsync();

        using var client = new HttpClient();
        using var firstResponse = await client.GetAsync($"http://127.0.0.1:{port}/first/check_connect");
        using var secondResponse = await client.GetAsync($"http://127.0.0.1:{port}/second/check_connect");
        using var missingResponse = await client.GetAsync($"http://127.0.0.1:{port}/missing/check_connect");
        Assert.Equal(HttpStatusCode.OK, firstResponse.StatusCode);
        Assert.Equal(HttpStatusCode.OK, secondResponse.StatusCode);
        Assert.Equal(HttpStatusCode.NotFound, missingResponse.StatusCode);
    }

    [Fact]
    public async Task ServerInteractive_RoutesDirectIpDnsAndEdgeOneRequestsByOriginPort()
    {
        var originPort = GetFreePort();
        var core = XFEServerCoreBuilder.CreateBuilder()
            .AddConnectService()
            .Build(options =>
            {
                options.ServerCoreName = "edge-one-origin";
                options.MainEntryPoint = "api";
                options.AcceptGet = true;
                options.BindIP($"http://*:{originPort}/");
            });
        await using var server = XFEServerBuilder.CreateBuilder()
            .AddXFEExceptionProcess()
            .AddXFECoreProcessor()
            .AddServerCore(core)
            .Build();
        await server.StartAsync();

        var directIpResponse = await SendRawAsync(originPort,
            "GET /api/check_connect HTTP/1.1\r\n" +
            $"Host: 127.0.0.1:{originPort}\r\n" +
            "Connection: close\r\n\r\n");
        var directDnsResponse = await SendRawAsync(originPort,
            "GET /api/check_connect HTTP/1.1\r\n" +
            $"Host: toolbox.api.xfe.studio:{originPort}\r\n" +
            "Connection: close\r\n\r\n");
        var edgeOneResponse = await SendRawAsync(originPort,
            "GET /api/check_connect HTTP/1.1\r\n" +
            "Host: toolbox.api.xfe.studio\r\n" +
            "X-Forwarded-Proto: http\r\n" +
            "Connection: close\r\n\r\n");

        Assert.StartsWith("HTTP/1.1 200", directIpResponse);
        Assert.StartsWith("HTTP/1.1 200", directDnsResponse);
        Assert.StartsWith("HTTP/1.1 200", edgeOneResponse);
    }

    [Fact]
    public async Task TableRequester_AcceptsCreatedAndUsesPageSize()
    {
        var port = GetFreePort();
        await using var server = CreateServer(port);
        var getRequestBody = new TaskCompletionSource<string>(TaskCreationOptions.RunContinuationsAsynchronously);
        server.HttpRequestHandler = async (context, _) =>
        {
            if (context.RequestUri.AbsolutePath.EndsWith("/add/userFaceInfo", StringComparison.Ordinal))
                await context.Response.WriteTextAsync("{\"id\":\"1\",\"version\":1}", HttpStatusCode.Created, "application/json");
            else
            {
                getRequestBody.TrySetResult(context.RequestBody);
                await context.Response.WriteTextAsync("{\"totalCount\":0,\"lastPage\":0,\"dataList\":[]}", HttpStatusCode.OK, "application/json");
            }
            context.Response.Complete();
        };
        await server.StartAsync();
        var requester = new TableRequester { RequestAddress = $"http://127.0.0.1:{port}/api/" };

        Assert.True(await requester.Add(new UserFaceInfo { Id = "1", Version = 1 }));
        _ = await requester.Get<UserFaceInfo>(2, 1);
        var body = await getRequestBody.Task.WaitAsync(TimeSpan.FromSeconds(2));
        Assert.Contains("\"pageSize\":2", body, StringComparison.Ordinal);
        Assert.DoesNotContain("pageCount", body, StringComparison.Ordinal);
    }

    [Fact]
    public async Task Client_InitialFailureWithoutReconnectBecomesFaulted()
    {
        var port = GetFreePort();
        await using var client = new CyberCommClient(new CyberCommClientOptions
        {
            ServerUri = new Uri($"ws://127.0.0.1:{port}/"),
            Reconnect = new() { Enabled = false }
        });
        await Assert.ThrowsAsync<XFEExtension.NetCore.Exceptions.XFECyberCommException>(() => client.ConnectAsync());
        Assert.Equal(CyberCommClientState.Faulted, client.State);
    }

    [Fact]
    public async Task HttpServer_RejectsUnsupportedMethodWith405()
    {
        var port = GetFreePort();
        await using var server = CreateServer(port);
        await server.StartAsync();
        var response = await SendRawAsync(port, $"TRACE / HTTP/1.1\r\nHost: 127.0.0.1:{port}\r\nConnection: close\r\n\r\n");
        Assert.StartsWith("HTTP/1.1 405", response);
        Assert.Contains("Allow:", response);
    }

    [Fact]
    public async Task HttpServer_HandlerWithoutResponseGetsDeterministic404()
    {
        var port = GetFreePort();
        await using var server = CreateServer(port);
        server.HttpRequestHandler = static (_, _) => ValueTask.CompletedTask;
        await server.StartAsync();

        using var client = new HttpClient();
        using var response = await client.GetAsync($"http://127.0.0.1:{port}/missing");
        Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
    }

    [Fact]
    public async Task HttpServer_RejectsOversizedBodyBeforeReadingIt()
    {
        var port = GetFreePort();
        await using var server = new CyberCommServer(new CyberCommServerOptions
        {
            ListenUrls = [$"http://127.0.0.1:{port}/"],
            Limits = new() { MaxHttpBodyBytes = 10, MaxHttpChunkBytes = 10 }
        });
        await server.StartAsync();

        var response = await SendRawAsync(port,
            $"POST / HTTP/1.1\r\nHost: 127.0.0.1:{port}\r\nContent-Length: 11\r\nConnection: close\r\n\r\n");
        Assert.StartsWith("HTTP/1.1 413", response);
    }

    [Fact]
    public async Task Server_InvalidOptionsBecomeFaultedInsteadOfStayingStarting()
    {
        await using var server = new CyberCommServer(new CyberCommServerOptions());
        await Assert.ThrowsAsync<InvalidOperationException>(() => server.StartAsync());
        Assert.Equal(CyberCommServerState.Faulted, server.State);
    }

    [Fact]
    public async Task WebSocket_UnassembledModeDoesNotDropFirstFragment()
    {
        var port = GetFreePort();
        await using var server = new CyberCommServer(new CyberCommServerOptions
        {
            ListenUrls = [$"http://127.0.0.1:{port}/"],
            AssembleWebSocketMessages = false
        });
        var fragments = new List<string>();
        var completed = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
        server.WebSocketMessageHandler = (args, _) =>
        {
            fragments.Add(args.TextMessage ?? string.Empty);
            if (args.EndOfMessage) completed.TrySetResult();
            return ValueTask.CompletedTask;
        };
        await server.StartAsync();
        using var client = new ClientWebSocket();
        await client.ConnectAsync(new Uri($"ws://127.0.0.1:{port}/ws"), CancellationToken.None);
        await client.SendAsync("first-"u8.ToArray(), WebSocketMessageType.Text, false, CancellationToken.None);
        await client.SendAsync("second"u8.ToArray(), WebSocketMessageType.Text, true, CancellationToken.None);
        await completed.Task.WaitAsync(TimeSpan.FromSeconds(5));
        Assert.Equal(new[] { "first-", "second" }, fragments);
    }

    [Theory]
    [InlineData("v1/*", "v1/test", true)]
    [InlineData("v1/*", "v1/test/more", false)]
    [InlineData("v1/*", "v1", false)]
    [InlineData("v1/**", "v1", true)]
    [InlineData("v1/**", "v1/test/more", true)]
    public void WildcardRoute_HasBoundedSemantics(string pattern, string route, bool expected)
        => Assert.Equal(expected, RouteMatchHelper.MatchWildcardRoute(pattern, route));

    [Fact]
    public void PasswordCredential_VerifiesMigratesAndPublicDtoOmitsSecrets()
    {
        var credential = PasswordHasher.Hash("correct horse battery staple", 100_000);
        Assert.True(PasswordHasher.Verify("correct horse battery staple", credential));
        Assert.False(PasswordHasher.Verify("wrong", credential));
        Assert.False(PasswordHasher.Verify("anything", new PasswordCredential
        {
            Iterations = 600_000,
            Salt = Convert.ToBase64String(new byte[16]),
            Hash = string.Empty
        }));

        var user = new User { UserName = "demo", Password = "legacy" };
        Assert.Equal(UserOperateResult.Success, UserHelper.GetUser("demo", "legacy", [user], out _, out var upgraded));
        Assert.True(upgraded);
        Assert.Empty(user.Password);
        Assert.NotNull(user.PasswordCredential);

        var json = JsonSerializer.Serialize(UserPublicDto.From(user));
        Assert.DoesNotContain("legacy", json, StringComparison.Ordinal);
        Assert.DoesNotContain("PasswordCredential", json, StringComparison.Ordinal);
    }

    private static CyberCommServer CreateServer(int port) => new(new CyberCommServerOptions
    {
        ListenUrls = [$"http://127.0.0.1:{port}/"],
        Limits = new()
        {
            HandlerTimeout = TimeSpan.FromSeconds(5),
            ReceiveTimeout = TimeSpan.FromSeconds(5),
            IdleTimeout = TimeSpan.FromSeconds(5)
        }
    });

    private static int GetFreePort()
    {
        var listener = new TcpListener(IPAddress.Loopback, 0);
        listener.Start();
        var port = ((IPEndPoint)listener.LocalEndpoint).Port;
        listener.Stop();
        return port;
    }

    private static async Task<string> SendRawAsync(int port, string request)
    {
        using var client = new TcpClient();
        await client.ConnectAsync(IPAddress.Loopback, port);
        await using var stream = client.GetStream();
        await stream.WriteAsync(Encoding.ASCII.GetBytes(request));
        using var output = new MemoryStream();
        var buffer = new byte[1024];
        int count;
        while ((count = await stream.ReadAsync(buffer)) > 0)
            await output.WriteAsync(buffer.AsMemory(0, count));
        return Encoding.ASCII.GetString(output.ToArray());
    }

    private static async Task AssertTlsConfigurationStartsAsync(CyberCommTlsOptions tlsOptions)
    {
        var port = GetFreePort();
        await using var server = new CyberCommServer(new CyberCommServerOptions
        {
            ListenUrls = [$"https://127.0.0.1:{port}/"],
            Tls = tlsOptions
        });
        server.HttpRequestHandler = async (context, _) =>
        {
            await context.Response.WriteTextAsync("secure");
            context.Response.Complete();
        };
        await server.StartAsync();

        using var handler = new HttpClientHandler { ServerCertificateCustomValidationCallback = static (_, _, _, _) => true };
        using var client = new HttpClient(handler);
        Assert.Equal("secure", await client.GetStringAsync($"https://127.0.0.1:{port}/"));
    }

    private static X509Certificate2 CreateCertificate()
    {
        using var rsa = RSA.Create(2048);
        var request = new CertificateRequest("CN=localhost", rsa, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
        request.CertificateExtensions.Add(new X509BasicConstraintsExtension(false, false, 0, false));
        request.CertificateExtensions.Add(new X509KeyUsageExtension(X509KeyUsageFlags.DigitalSignature | X509KeyUsageFlags.KeyEncipherment, false));
        request.CertificateExtensions.Add(new X509EnhancedKeyUsageExtension(new OidCollection { new("1.3.6.1.5.5.7.3.1") }, false));
        var names = new SubjectAlternativeNameBuilder();
        names.AddDnsName("localhost");
        names.AddIpAddress(IPAddress.Loopback);
        request.CertificateExtensions.Add(names.Build());
        using var ephemeral = request.CreateSelfSigned(DateTimeOffset.UtcNow.AddMinutes(-1), DateTimeOffset.UtcNow.AddDays(1));
        return X509CertificateLoader.LoadPkcs12(ephemeral.Export(X509ContentType.Pkcs12), null,
            X509KeyStorageFlags.Exportable | X509KeyStorageFlags.PersistKeySet);
    }
}