Skip to content

Commit 4db78f0

Browse files
fix: add custom server certificates (#276)
1 parent 10aac80 commit 4db78f0

File tree

9 files changed

+113
-27
lines changed

9 files changed

+113
-27
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
- Fixed bug: public key presented not for certificate signature.
12
- Fixed: YdbDataReader does not throw YdbException when CloseAsync is called for UPDATE/INSERT statements with no
23
result.
34

examples/src/YC/CmdOptions.cs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
using CommandLine;
2+
3+
namespace YcCloud;
4+
5+
internal class CmdOptions
6+
{
7+
[Option('h', "host", Required = true, HelpText = "Database host")]
8+
public string Host { get; set; } = null!;
9+
10+
[Option('d', "database", Required = true, HelpText = "Database name")]
11+
public string Database { get; set; } = null!;
12+
13+
[Option("saFilePath", Required = true, HelpText = "Sa Key")]
14+
public string SaFilePath { get; set; } = null!;
15+
}

examples/src/YC/Program.cs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
using CommandLine;
2+
using Microsoft.Extensions.Logging;
3+
using YcCloud;
4+
using Ydb.Sdk.Ado;
5+
using Ydb.Sdk.Yc;
6+
7+
await Parser.Default.ParseArguments<CmdOptions>(args).WithParsedAsync(async cmd =>
8+
{
9+
var loggerFactory = LoggerFactory.Create(builder => builder.AddConsole().SetMinimumLevel(LogLevel.Information));
10+
11+
var saProvider = new ServiceAccountProvider(saFilePath: cmd.SaFilePath, loggerFactory: loggerFactory);
12+
await saProvider.Initialize();
13+
14+
var builder = new YdbConnectionStringBuilder
15+
{
16+
UseTls = true,
17+
Host = cmd.Host,
18+
Port = 2135,
19+
Database = cmd.Database,
20+
CredentialsProvider = saProvider,
21+
LoggerFactory = loggerFactory,
22+
ServerCertificates = YcCerts.GetYcServerCertificates()
23+
};
24+
25+
await using var ydbConnection = new YdbConnection(builder);
26+
await ydbConnection.OpenAsync();
27+
28+
Console.WriteLine(await new YdbCommand(ydbConnection) { CommandText = "SELECT 'Hello Dedicated YDB!'u" }
29+
.ExecuteScalarAsync());
30+
});

examples/src/YC/YC.csproj

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<OutputType>Exe</OutputType>
5+
<TargetFramework>net8.0</TargetFramework>
6+
<ImplicitUsings>enable</ImplicitUsings>
7+
<Nullable>enable</Nullable>
8+
<RootNamespace>YcCloud</RootNamespace>
9+
</PropertyGroup>
10+
11+
<ItemGroup>
12+
<PackageReference Include="CommandLineParser" Version="2.8.0" />
13+
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="5.0.0" />
14+
<PackageReference Include="Microsoft.Extensions.Logging" Version="5.0.0" />
15+
<PackageReference Include="Ydb.Protos" Version="1.1.1" />
16+
<PackageReference Include="Ydb.Sdk.Yc.Auth" Version="0.1.0" />
17+
</ItemGroup>
18+
<ItemGroup>
19+
<ProjectReference Include="..\..\..\src\Ydb.Sdk\src\Ydb.Sdk.csproj" />
20+
</ItemGroup>
21+
</Project>

examples/src/YdbExamples.sln

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AdoNet", "AdoNet\AdoNet.csp
1313
EndProject
1414
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DapperExample", "DapperExample\DapperExample.csproj", "{AC8F1B10-31EB-4A29-BF35-7F49B06E1FA8}"
1515
EndProject
16+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "YC", "YC\YC.csproj", "{753E4F33-CB08-47B9-864F-4CC037B278C4}"
17+
EndProject
1618
Global
1719
GlobalSection(SolutionConfigurationPlatforms) = preSolution
1820
Debug|Any CPU = Debug|Any CPU
@@ -39,6 +41,10 @@ Global
3941
{AC8F1B10-31EB-4A29-BF35-7F49B06E1FA8}.Debug|Any CPU.Build.0 = Debug|Any CPU
4042
{AC8F1B10-31EB-4A29-BF35-7F49B06E1FA8}.Release|Any CPU.ActiveCfg = Release|Any CPU
4143
{AC8F1B10-31EB-4A29-BF35-7F49B06E1FA8}.Release|Any CPU.Build.0 = Release|Any CPU
44+
{753E4F33-CB08-47B9-864F-4CC037B278C4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
45+
{753E4F33-CB08-47B9-864F-4CC037B278C4}.Debug|Any CPU.Build.0 = Debug|Any CPU
46+
{753E4F33-CB08-47B9-864F-4CC037B278C4}.Release|Any CPU.ActiveCfg = Release|Any CPU
47+
{753E4F33-CB08-47B9-864F-4CC037B278C4}.Release|Any CPU.Build.0 = Release|Any CPU
4248
EndGlobalSection
4349
GlobalSection(SolutionProperties) = preSolution
4450
HideSolutionNode = FALSE

src/Ydb.Sdk/src/Ado/YdbConnectionStringBuilder.cs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -137,9 +137,11 @@ public string? RootCertificate
137137

138138
private string? _rootCertificate;
139139

140-
public ILoggerFactory? LoggerFactory { get; set; }
140+
public ILoggerFactory? LoggerFactory { get; init; }
141141

142-
public ICredentialsProvider? CredentialsProvider { get; set; }
142+
public ICredentialsProvider? CredentialsProvider { get; init; }
143+
144+
public X509Certificate2Collection? ServerCertificates { get; init; }
143145

144146
private void SaveValue(string propertyName, object? value)
145147
{
@@ -189,7 +191,8 @@ internal Task<Driver> BuildDriver()
189191
endpoint: Endpoint,
190192
database: Database,
191193
credentials: credentialsProvider,
192-
customServerCertificate: cert
194+
customServerCertificate: cert,
195+
customServerCertificates: ServerCertificates
193196
), LoggerFactory);
194197
}
195198

src/Ydb.Sdk/src/DriverConfig.cs

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,21 +8,31 @@ public class DriverConfig
88
public string Endpoint { get; }
99
public string Database { get; }
1010
public ICredentialsProvider Credentials { get; }
11-
public X509Certificate? CustomServerCertificate { get; }
1211

12+
internal X509Certificate2Collection CustomServerCertificates { get; } = new();
1313
internal TimeSpan EndpointDiscoveryInterval = TimeSpan.FromMinutes(1);
1414
internal TimeSpan EndpointDiscoveryTimeout = TimeSpan.FromSeconds(10);
1515

1616
public DriverConfig(
1717
string endpoint,
1818
string database,
1919
ICredentialsProvider? credentials = null,
20-
X509Certificate? customServerCertificate = null)
20+
X509Certificate? customServerCertificate = null,
21+
X509Certificate2Collection? customServerCertificates = null)
2122
{
2223
Endpoint = FormatEndpoint(endpoint);
2324
Database = database;
2425
Credentials = credentials ?? new AnonymousProvider();
25-
CustomServerCertificate = customServerCertificate;
26+
27+
if (customServerCertificate != null)
28+
{
29+
CustomServerCertificates.Add(new X509Certificate2(customServerCertificate));
30+
}
31+
32+
if (customServerCertificates != null)
33+
{
34+
CustomServerCertificates.AddRange(customServerCertificates);
35+
}
2636
}
2737

2838
private static string FormatEndpoint(string endpoint)

src/Ydb.Sdk/src/Pool/ChannelPool.cs

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
using Grpc.Core;
66
using Grpc.Net.Client;
77
using Microsoft.Extensions.Logging;
8-
using Org.BouncyCastle.Security;
98

109
namespace Ydb.Sdk.Pool;
1110

@@ -79,14 +78,14 @@ public interface IChannelFactory<out T> where T : ChannelBase, IDisposable
7978
internal class GrpcChannelFactory : IChannelFactory<GrpcChannel>
8079
{
8180
private readonly ILoggerFactory _loggerFactory;
82-
private readonly X509Certificate? _x509Certificate;
8381
private readonly ILogger<GrpcChannelFactory> _logger;
82+
private readonly X509Certificate2Collection _x509Certificate2Collection;
8483

8584
internal GrpcChannelFactory(ILoggerFactory loggerFactory, DriverConfig config)
8685
{
8786
_loggerFactory = loggerFactory;
88-
_x509Certificate = config.CustomServerCertificate;
8987
_logger = loggerFactory.CreateLogger<GrpcChannelFactory>();
88+
_x509Certificate2Collection = config.CustomServerCertificates;
9089
}
9190

9291
public GrpcChannel CreateChannel(string endpoint)
@@ -98,47 +97,48 @@ public GrpcChannel CreateChannel(string endpoint)
9897
LoggerFactory = _loggerFactory
9998
};
10099

101-
if (_x509Certificate == null)
100+
var httpHandler = new SocketsHttpHandler();
101+
102+
// https://github.com/grpc/grpc-dotnet/issues/2312#issuecomment-1790661801
103+
httpHandler.Properties["__GrpcLoadBalancingDisabled"] = true;
104+
105+
channelOptions.HttpHandler = httpHandler;
106+
channelOptions.DisposeHttpClient = true;
107+
108+
if (_x509Certificate2Collection.Count == 0)
102109
{
103110
return GrpcChannel.ForAddress(endpoint, channelOptions);
104111
}
105112

106-
var httpHandler = new SocketsHttpHandler();
107-
108-
var customCertificate = DotNetUtilities.FromX509Certificate(_x509Certificate);
109-
110-
httpHandler.SslOptions.RemoteCertificateValidationCallback =
111-
(_, certificate, _, sslPolicyErrors) =>
113+
httpHandler.SslOptions.RemoteCertificateValidationCallback +=
114+
(_, certificate, chain, sslPolicyErrors) =>
112115
{
113116
if (sslPolicyErrors == SslPolicyErrors.None)
114117
{
115118
return true;
116119
}
117120

118-
if (certificate is null)
121+
if (certificate is null || chain is null)
119122
{
120123
return false;
121124
}
122125

123126
try
124127
{
125-
var cert = DotNetUtilities.FromX509Certificate(certificate);
126-
cert.Verify(customCertificate.GetPublicKey());
128+
chain.ChainPolicy.VerificationFlags = X509VerificationFlags.AllowUnknownCertificateAuthority;
129+
chain.ChainPolicy.ExtraStore.AddRange(_x509Certificate2Collection);
130+
131+
return chain.Build(new X509Certificate2(certificate)) && chain.ChainElements.Any(chainElement =>
132+
_x509Certificate2Collection.Any(trustedCert =>
133+
chainElement.Certificate.Thumbprint == trustedCert.Thumbprint));
127134
}
128135
catch (Exception e)
129136
{
130137
_logger.LogError(e, "Failed to verify remote certificate!");
131138

132139
return false;
133140
}
134-
135-
return true;
136141
};
137-
// https://github.com/grpc/grpc-dotnet/issues/2312#issuecomment-1790661801
138-
httpHandler.Properties["__GrpcLoadBalancingDisabled"] = true;
139-
140-
channelOptions.HttpHandler = httpHandler;
141-
channelOptions.DisposeHttpClient = true;
142142

143143
return GrpcChannel.ForAddress(endpoint, channelOptions);
144144
}

src/Ydb.Sdk/src/Transport/AuthGrpcChannelDriver.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ ILoggerFactory loggerFactory
1717
new DriverConfig(
1818
endpoint: driverConfig.Endpoint,
1919
database: driverConfig.Database,
20-
customServerCertificate: driverConfig.CustomServerCertificate
20+
customServerCertificates: driverConfig.CustomServerCertificates
2121
), loggerFactory, loggerFactory.CreateLogger<AuthGrpcChannelDriver>())
2222
{
2323
_channel = grpcChannelFactory.CreateChannel(Config.Endpoint);

0 commit comments

Comments
 (0)