From 06aacf7b75f630d622fd75e047d0165134486024 Mon Sep 17 00:00:00 2001 From: KarlaCarvajal Date: Tue, 8 Oct 2024 09:11:09 -0500 Subject: [PATCH] (sdk-dotnet): fix ca cert server validation in dotnet client (#1056) Create validation method to trust CA server certificate in dotnet client --- .gitignore | 1 + .../Examples/BasicExample/BasicExample.csproj | 2 +- .../LHInputVariablesTest.cs | 3 +- .../Utils/CertificatesHandlerTest.cs | 40 ------------------- sdk-dotnet/LittleHorse.Sdk/LHConfig.cs | 26 +++++++++--- .../Utils/CertificatesHandler.cs | 31 ++------------ 6 files changed, 27 insertions(+), 76 deletions(-) delete mode 100644 sdk-dotnet/LittleHorse.Sdk.Tests/Utils/CertificatesHandlerTest.cs diff --git a/.gitignore b/.gitignore index 900d44ccd..6f3f97632 100644 --- a/.gitignore +++ b/.gitignore @@ -32,6 +32,7 @@ lhctl/lhctl # Others local-dev/certs/ build/ +.config # Python __pycache__ diff --git a/sdk-dotnet/Examples/BasicExample/BasicExample.csproj b/sdk-dotnet/Examples/BasicExample/BasicExample.csproj index 654bb87e7..1c58361d1 100644 --- a/sdk-dotnet/Examples/BasicExample/BasicExample.csproj +++ b/sdk-dotnet/Examples/BasicExample/BasicExample.csproj @@ -2,7 +2,7 @@ Exe - net6.0 + net8.0 enable enable diff --git a/sdk-dotnet/LittleHorse.Sdk.Tests/LHInputVariablesTest.cs b/sdk-dotnet/LittleHorse.Sdk.Tests/LHInputVariablesTest.cs index 84355d525..b24d57dfc 100644 --- a/sdk-dotnet/LittleHorse.Sdk.Tests/LHInputVariablesTest.cs +++ b/sdk-dotnet/LittleHorse.Sdk.Tests/LHInputVariablesTest.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.IO; using Xunit; namespace LittleHorse.Sdk.Tests.Internal @@ -169,7 +168,7 @@ public void LHConfigVariables_WithSomeLHOptionsCommentedInFile_ShouldReturnSetOp Assert.Equal(int.Parse(keyValueLHConfigs["LHC_API_PORT"]), inputVariables.LHC_API_PORT); Assert.Equal(DefaultLHConfigVariables.LHC_API_PROTOCOL, inputVariables.LHC_API_PROTOCOL); Assert.Null(inputVariables.LHC_CA_CERT); - Assert.Equal(string.Empty, inputVariables.LHW_TASK_WORKER_VERSION); + Assert.Equal(DefaultLHConfigVariables.LHW_TASK_WORKER_VERSION, inputVariables.LHW_TASK_WORKER_VERSION); } [Fact] diff --git a/sdk-dotnet/LittleHorse.Sdk.Tests/Utils/CertificatesHandlerTest.cs b/sdk-dotnet/LittleHorse.Sdk.Tests/Utils/CertificatesHandlerTest.cs deleted file mode 100644 index 40668e140..000000000 --- a/sdk-dotnet/LittleHorse.Sdk.Tests/Utils/CertificatesHandlerTest.cs +++ /dev/null @@ -1,40 +0,0 @@ -using System; -using System.IO; -using LittleHorse.Sdk.Utils; -using Xunit; - -namespace LittleHorse.Sdk.Tests.Utils; - -public class CertificatesHandlerTest -{ - [Fact] - public void CertManager_WithCaCertificateTrusted_ShouldReturnHttpHandler() - { - const string caFilename = "trusted_ca.crt"; - string caCertPath = Path.Combine(Directory.GetCurrentDirectory(), "Resources", caFilename); - - var currentHttpHandler = CertificatesHandler.GetHttpHandlerFrom(caCertPath); - - Assert.NotNull(currentHttpHandler); - } - - [Fact] - public void CertManager_WithoutCaCertificate_ShouldThrowFileNotFoundException() - { - const string caCertPath = "file_not_found.crt"; - var exception = Assert.Throws(() => CertificatesHandler.GetHttpHandlerFrom(caCertPath)); - - Assert.Contains($"Certificate file {caCertPath} does not exist.", exception.Message); - } - - [Fact] - public void CertManager_WithEmptyCaCertificate_ShouldThrowException() - { - const string caFilename = "empty_ca.crt"; - string caCertPath = Path.Combine(Directory.GetCurrentDirectory(), "Resources", caFilename); - - var exception = Assert.Throws(() => CertificatesHandler.GetHttpHandlerFrom(caCertPath)); - - Assert.Contains($"Certificate file {caCertPath} has corrupted data.", exception.Message); - } -} \ No newline at end of file diff --git a/sdk-dotnet/LittleHorse.Sdk/LHConfig.cs b/sdk-dotnet/LittleHorse.Sdk/LHConfig.cs index 819ecd965..ba01fdf5a 100644 --- a/sdk-dotnet/LittleHorse.Sdk/LHConfig.cs +++ b/sdk-dotnet/LittleHorse.Sdk/LHConfig.cs @@ -5,6 +5,9 @@ using LittleHorse.Sdk.Internal; using LittleHorse.Sdk.Utils; using Microsoft.Extensions.Logging; +using System.Net.Http; +using System.Security.Cryptography.X509Certificates; +using System.Net.Security; using static LittleHorse.Common.Proto.LittleHorse; namespace LittleHorse.Sdk { @@ -136,11 +139,7 @@ private GrpcChannel CreateChannel(string host, int port) { var httpHandler = new HttpClientHandler(); var address = $"{BootstrapProtocol}://{host}:{port}"; - - if (_inputVariables.LHC_CA_CERT != null) - { - httpHandler = CertificatesHandler.GetHttpHandlerFrom(_inputVariables.LHC_CA_CERT); - } + httpHandler.ServerCertificateCustomValidationCallback = ServerCertificateCustomValidation; if (_inputVariables.LHC_CLIENT_CERT != null && _inputVariables.LHC_CLIENT_KEY != null) { @@ -150,7 +149,7 @@ private GrpcChannel CreateChannel(string host, int port) httpHandler.ClientCertificates.Add(cert); } - + if (IsOAuth) { return CreateGrpcChannelWithOauthCredentials(address, httpHandler); @@ -162,6 +161,21 @@ private GrpcChannel CreateChannel(string host, int port) }); } + private bool ServerCertificateCustomValidation(HttpRequestMessage requestMessage, X509Certificate2? certificate, X509Chain? certChain, SslPolicyErrors sslErrors) + { + var pathCaCert = _inputVariables.LHC_CA_CERT; + if (pathCaCert != null) + { + var caCert = new X509Certificate2(File.ReadAllBytes(pathCaCert)); + + certChain!.ChainPolicy.TrustMode = X509ChainTrustMode.CustomRootTrust; + certChain.ChainPolicy.CustomTrustStore.Add(caCert); + } + + var certChainBuilder = certificate != null && certChain != null && certChain.Build(certificate); + return certChainBuilder; + } + private GrpcChannel CreateGrpcChannelWithOauthCredentials(string address, HttpClientHandler httpHandler) { InitializeOAuth(); diff --git a/sdk-dotnet/LittleHorse.Sdk/Utils/CertificatesHandler.cs b/sdk-dotnet/LittleHorse.Sdk/Utils/CertificatesHandler.cs index 0e4f673f1..18a328e39 100644 --- a/sdk-dotnet/LittleHorse.Sdk/Utils/CertificatesHandler.cs +++ b/sdk-dotnet/LittleHorse.Sdk/Utils/CertificatesHandler.cs @@ -1,39 +1,16 @@ +using System.Net.Security; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +using System.Security.Authentication; using System.Security.Cryptography.X509Certificates; +using LittleHorse.Sdk.Internal; +using Microsoft.Extensions.Logging; [assembly: InternalsVisibleTo("LittleHorse.Sdk.Tests")] namespace LittleHorse.Sdk.Utils { internal class CertificatesHandler { - internal static HttpClientHandler GetHttpHandlerFrom(string pathCaCert) - { - try - { - var caCert = new X509Certificate2(File.ReadAllBytes(pathCaCert)); - var handler = new HttpClientHandler(); - - handler.ServerCertificateCustomValidationCallback = - (httpRequestMessage, cert, certChain, sslPolicyErrors) => - { - return certChain!.ChainElements.Any(element => - element.Certificate.Thumbprint == caCert.Thumbprint); - }; - - return handler; - } - catch (System.Security.Cryptography.CryptographicException) - { - throw new Exception($"Certificate file {pathCaCert} has corrupted data."); - } - catch (Exception ex) - { - throw new FileNotFoundException($"Certificate file {pathCaCert} does not exist.", - ex.Message); - } - } - internal static X509Certificate2 GetX509CertificateFrom(string pathPrivateCert, string pathRequestedCert) { string certificatePem = File.ReadAllText(pathRequestedCert);