diff --git a/docs/configuration.md b/docs/configuration.md index 4a4adc7..0c02ca9 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -316,6 +316,34 @@ Also not on `SparkOptions`: - **HttpClient settings** (the SSP and the block explorer). Drive these via `IHttpClientFactory` named clients in your DI container. +## Custom operator TLS + +Operators are reached through Grpc.Net.Client with the platform's default +handler and trust store. To talk to operators whose certificates the trust +store doesn't know — a private CA, or a self-hosted regtest network with +self-signed certificates — pass your own handler: + +```csharp +var handler = new SocketsHttpHandler +{ + EnableMultipleHttp2Connections = true, + SslOptions = new SslClientAuthenticationOptions + { + CertificateChainPolicy = new X509ChainPolicy + { + TrustMode = X509ChainTrustMode.CustomRootTrust, + CustomTrustStore = { X509Certificate2.CreateFromPemFile("operators-ca.pem") }, + }, + }, +}; + +await using var spark = new SparkConnection(options, http, loggerFactory, handler); +``` + +The handler is shared by every operator channel and is not disposed with the +connection. Prefer a custom root over a raw `RemoteCertificateValidationCallback`; +if you pin self-signed leaf certificates, compare the exact certificate bytes. + ## See also - [`getting-started.md`](getting-started.md) — minimum setup. diff --git a/src/NSpark/PublicAPI.Unshipped.txt b/src/NSpark/PublicAPI.Unshipped.txt index 3e52f67..3d3a82b 100644 --- a/src/NSpark/PublicAPI.Unshipped.txt +++ b/src/NSpark/PublicAPI.Unshipped.txt @@ -720,6 +720,7 @@ NSpark.SparkConnection.Dispose() -> void NSpark.SparkConnection.DisposeAsync() -> System.Threading.Tasks.ValueTask NSpark.SparkConnection.Options.get -> NSpark.SparkOptions! NSpark.SparkConnection.SparkConnection(Microsoft.Extensions.Options.IOptions! options, System.Net.Http.HttpClient! httpClient) -> void +NSpark.SparkConnection.SparkConnection(Microsoft.Extensions.Options.IOptions! options, System.Net.Http.HttpClient! httpClient, Microsoft.Extensions.Logging.ILoggerFactory! loggerFactory, System.Net.Http.HttpMessageHandler? operatorHandler) -> void NSpark.SparkConnection.SparkConnection(Microsoft.Extensions.Options.IOptions! options, System.Net.Http.HttpClient! httpClient, Microsoft.Extensions.Logging.ILoggerFactory! loggerFactory) -> void NSpark.SparkNetwork NSpark.SparkNetwork.Mainnet = 0 -> NSpark.SparkNetwork diff --git a/src/NSpark/SparkConnection.cs b/src/NSpark/SparkConnection.cs index 81d3d7a..2b8ac1e 100644 --- a/src/NSpark/SparkConnection.cs +++ b/src/NSpark/SparkConnection.cs @@ -53,10 +53,17 @@ public SparkConnection( } /// - /// Construct a client whose operator channels send over - /// instead of the network, when it is set — the seam unit tests use to stand in for operators. + /// Construct a client whose operator gRPC channels send over + /// when it is set, for example a that trusts a private CA or + /// pins the certificates of self-hosted (regtest) operators. null uses the default handler. /// - internal SparkConnection( + /// + /// The handler is shared by every operator channel, so it must support HTTP/2 + /// ( is recommended). It is not + /// disposed with the connection: the caller owns it. Mainnet still requires https:// + /// operator addresses. + /// + public SparkConnection( IOptions options, HttpClient httpClient, ILoggerFactory loggerFactory,