Skip to content

Commit

Permalink
Disable TLS certificate validation (#5675)
Browse files Browse the repository at this point in the history
  • Loading branch information
Nigusu-Allehu committed Apr 1, 2024
1 parent 1ac36bf commit a7a4025
Show file tree
Hide file tree
Showing 6 changed files with 575 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Security;
using System.Security.Cryptography.X509Certificates;
using System.Threading;
using System.Threading.Tasks;
using NuGet.Configuration;
Expand All @@ -17,6 +19,11 @@ public class HttpHandlerResourceV3Provider : ResourceProvider
{
private readonly IProxyCache _proxyCache;

#if NETSTANDARD2_0
internal static Func<HttpRequestMessage, X509Certificate2, X509Chain, SslPolicyErrors, bool> DangerousAcceptAnyServerCertificateValidator =
(message, certificate, chain, policyErrors) => true;
#endif

public HttpHandlerResourceV3Provider()
: this(ProxyCache.Instance)
{
Expand Down Expand Up @@ -56,6 +63,18 @@ private HttpHandlerResourceV3 CreateResource(PackageSource packageSource)
AutomaticDecompression = (DecompressionMethods.GZip | DecompressionMethods.Deflate),
};

#if NETSTANDARD2_0
if (packageSource.DisableTLSCertificateValidation)
{
clientHandler.ServerCertificateCustomValidationCallback = DangerousAcceptAnyServerCertificateValidator;
}
#else
if (packageSource.DisableTLSCertificateValidation)
{
clientHandler.ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator;
}
#endif

#if IS_DESKTOP
if (packageSource.MaxHttpRequestsPerSource > 0)
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System.IO;
using System.Threading.Tasks;
using NuGet.Packaging;
using NuGet.Test.Utility;
using NuGet.XPlat.FuncTest;
using Test.Utility;
using Xunit;

namespace Dotnet.Integration.Test
{
[Collection(DotnetIntegrationCollection.Name)]
public class DotnetRestoreTLSCertificateValidationTests
{
private readonly DotnetIntegrationTestFixture _msbuildFixture;

public DotnetRestoreTLSCertificateValidationTests(DotnetIntegrationTestFixture fixture)
{
_msbuildFixture = fixture;
}

[PlatformFact(Platform.Windows)]
public async Task DotnetRestore_withTLSCertificateValidationDisabled_DoesnotThrowException()
{
// Arrange
using var pathContext = _msbuildFixture.CreateSimpleTestPathContext();
var packageA100 = new SimpleTestPackageContext("A", "1.0.0");
await SimpleTestPackageUtility.CreateFolderFeedV3Async(
pathContext.PackageSource,
PackageSaveMode.Defaultv3,
packageA100);
var projectA = XPlatTestUtils.CreateProject("ProjectA", pathContext, packageA100, "net472");
var workingDirectory = Path.Combine(pathContext.SolutionRoot, projectA.ProjectName);
SelfSignedCertificateMockServer tcpListenerServer = new SelfSignedCertificateMockServer(pathContext.PackageSource);
var serverTask = tcpListenerServer.StartServerAsync();
var configFile = @$"<?xml version=""1.0"" encoding=""utf-8""?>
<configuration>
<packageSources>
<add key=""source1"" value=""{tcpListenerServer.URI}v3/index.json"" disableTLSCertificateValidation=""true""/>
</packageSources>
</configuration>
";

// Act & Assert
File.WriteAllText(Path.Combine(workingDirectory, "NuGet.Config"), configFile);
_msbuildFixture.RunDotnetExpectSuccess(workingDirectory, $"restore {projectA.ProjectName}.csproj --configfile ./NuGet.config");
tcpListenerServer.StopServer();
}

[PlatformFact(Platform.Windows)]
public async Task DotnetRestore_withTLSCertificateValidationEnabled_ThrowException()
{
// Arrange
using var pathContext = _msbuildFixture.CreateSimpleTestPathContext();
var packageB100 = new SimpleTestPackageContext("myPackg", "1.0.0");
await SimpleTestPackageUtility.CreateFolderFeedV3Async(
pathContext.PackageSource,
PackageSaveMode.Defaultv3,
packageB100);
var projectB = XPlatTestUtils.CreateProject("ProjectB", pathContext, packageB100, "net472");
var workingDirectory = Path.Combine(pathContext.SolutionRoot, projectB.ProjectName);
SelfSignedCertificateMockServer tcpListenerServer = new SelfSignedCertificateMockServer(pathContext.PackageSource);
var serverTask = tcpListenerServer.StartServerAsync();
var configFile = @$"<?xml version=""1.0"" encoding=""utf-8""?>
<configuration>
<packageSources>
<add key=""source1"" value=""{tcpListenerServer.URI}v3/index.json""/>
</packageSources>
</configuration>
";
File.WriteAllText(Path.Combine(workingDirectory, "NuGet.Config"), configFile);

// Act & Assert
var _result = _msbuildFixture.RunDotnetExpectFailure(workingDirectory, $"restore {projectB.ProjectName}.csproj --configfile ./NuGet.config");
Assert.Contains("SSL connection could not be established", _result.AllOutput);
tcpListenerServer.StopServer();
}

[PlatformFact(Platform.Windows)]
public async Task DotnetRestore_withAnotherSourceTLSCertificateValidationDisbaled_ThrowException()
{
// Arrange
using var pathContext = _msbuildFixture.CreateSimpleTestPathContext();
var packageB100 = new SimpleTestPackageContext("myPackg", "1.0.0");
await SimpleTestPackageUtility.CreateFolderFeedV3Async(
pathContext.PackageSource,
PackageSaveMode.Defaultv3,
packageB100);
var projectB = XPlatTestUtils.CreateProject("ProjectB", pathContext, packageB100, "net472");
var workingDirectory = Path.Combine(pathContext.SolutionRoot, projectB.ProjectName);
SelfSignedCertificateMockServer tcpListenerServer1 = new SelfSignedCertificateMockServer(pathContext.PackageSource);
SelfSignedCertificateMockServer tcpListenerServer2 = new SelfSignedCertificateMockServer(pathContext.PackageSource);
var serverTask = tcpListenerServer1.StartServerAsync();
var serverTask2 = tcpListenerServer2.StartServerAsync();
var configFile = @$"<?xml version=""1.0"" encoding=""utf-8""?>
<configuration>
<packageSources>
<add key=""source1"" value=""{tcpListenerServer1.URI}v3/index.json""/>
<add key=""source2"" value=""{tcpListenerServer2.URI}v3/index.json"" disableTLSCertificateValidation=""true""/>
</packageSources>
</configuration>
";
File.WriteAllText(Path.Combine(workingDirectory, "NuGet.Config"), configFile);

// Act & Assert
var _result = _msbuildFixture.RunDotnetExpectFailure(workingDirectory, $"restore {projectB.ProjectName}.csproj --configfile ./NuGet.config");
Assert.Contains("SSL connection could not be established", _result.AllOutput);
tcpListenerServer1.StopServer();
tcpListenerServer2.StopServer();
}

[PlatformFact(Platform.Windows)]
public async Task DotnetRestore_withAnotherSourceTLSCertificateValidationEnabled_DoesNotThrowException()
{
// Arrange
using var pathContext = _msbuildFixture.CreateSimpleTestPathContext();
var packageB100 = new SimpleTestPackageContext("myPackg", "1.0.0");
await SimpleTestPackageUtility.CreateFolderFeedV3Async(
pathContext.PackageSource,
PackageSaveMode.Defaultv3,
packageB100);
var projectB = XPlatTestUtils.CreateProject("ProjectB", pathContext, packageB100, "net472");
var workingDirectory = Path.Combine(pathContext.SolutionRoot, projectB.ProjectName);
SelfSignedCertificateMockServer tcpListenerServer1 = new SelfSignedCertificateMockServer(pathContext.PackageSource);
SelfSignedCertificateMockServer tcpListenerServer2 = new SelfSignedCertificateMockServer(pathContext.PackageSource);
var serverTask = tcpListenerServer1.StartServerAsync();
var serverTask2 = tcpListenerServer2.StartServerAsync();
var configFile = @$"<?xml version=""1.0"" encoding=""utf-8""?>
<configuration>
<packageSources>
<add key=""source1"" value=""{tcpListenerServer1.URI}v3/index.json""/>
<add key=""source2"" value=""{tcpListenerServer2.URI}v3/index.json"" disableTLSCertificateValidation=""true""/>
</packageSources>
</configuration>
";
File.WriteAllText(Path.Combine(workingDirectory, "NuGet.Config"), configFile);

// Act & Assert
var _result = _msbuildFixture.RunDotnetExpectSuccess(workingDirectory, $"restore {projectB.ProjectName}.csproj --configfile ./NuGet.config --source {tcpListenerServer2.URI}v3/index.json");
tcpListenerServer1.StopServer();
tcpListenerServer2.StopServer();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,17 @@
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Security;
using System.Security.Authentication;
using System.Threading;
using System.Threading.Tasks;
using FluentAssertions;
using Moq;
using NuGet.Configuration;
using NuGet.Protocol.Core.Types;
using NuGet.Test.Server;
using NuGet.Test.Utility;
using Org.BouncyCastle.Asn1.X509;
using Xunit;

namespace NuGet.Protocol.Tests
Expand Down Expand Up @@ -124,5 +128,135 @@ static IEnumerable<DelegatingHandler> GetDelegatingHandlers(HttpMessageHandler h
}
}
}

[Fact]
public async Task TryCreate_WhenCertificateValidationIsDisabled_HttpClientHandlerServerCertificateCustomValidationCallbackShouldNotBeNull()
{
// Arrange
Mock<IProxyCache> proxyCache = new();
proxyCache.Setup(pc => pc.GetProxy(It.IsAny<Uri>())).Returns((IWebProxy)null);
PackageSource packageSource = new(_testPackageSourceURL, "source")
{
DisableTLSCertificateValidation = true
};
SourceRepository sourceRepository = new(packageSource, Array.Empty<INuGetResourceProvider>());
HttpHandlerResourceV3Provider target = new(proxyCache.Object);

// Act
var result = await target.TryCreate(sourceRepository, CancellationToken.None);
HttpHandlerResourceV3 resource = (HttpHandlerResourceV3)result.Item2;
HttpClientHandler clientHandler = resource.ClientHandler;

// Assert
clientHandler.ServerCertificateCustomValidationCallback.Should().NotBeNull();
}

[Fact]
public async Task Invoke_WhenCertificateValidationIsDisabled_HttpClientHandlerServerCertificateCustomValidationCallbackReturnsTrue()
{
// Arrange
Mock<IProxyCache> proxyCache = new();
proxyCache.Setup(pc => pc.GetProxy(It.IsAny<Uri>())).Returns((IWebProxy)null);
PackageSource packageSource = new(_testPackageSourceURL, "source")
{
DisableTLSCertificateValidation = true
};
SourceRepository sourceRepository = new(packageSource, Array.Empty<INuGetResourceProvider>());
HttpHandlerResourceV3Provider target = new(proxyCache.Object);
var result = await target.TryCreate(sourceRepository, CancellationToken.None);
HttpHandlerResourceV3 resource = (HttpHandlerResourceV3)result.Item2;
HttpClientHandler clientHandler = resource.ClientHandler;

// Act
var callbackResult = clientHandler.ServerCertificateCustomValidationCallback.Invoke(null, null, null, SslPolicyErrors.RemoteCertificateChainErrors
& SslPolicyErrors.RemoteCertificateNameMismatch
& SslPolicyErrors.RemoteCertificateNotAvailable
& SslPolicyErrors.None);

// Assert
callbackResult.Should().BeTrue();
}

[Fact]
public async Task TryCreate_WhenCertificateValidationIsEnabled_HttpClientHandlerServerCertificateCustomValidationCallbackShouldBeNull()
{
// Arrange
Mock<IProxyCache> proxyCache = new();
proxyCache.Setup(pc => pc.GetProxy(It.IsAny<Uri>())).Returns((IWebProxy)null);
PackageSource packageSource = new(_testPackageSourceURL, "source")
{
DisableTLSCertificateValidation = false
};
SourceRepository sourceRepository = new(packageSource, Array.Empty<INuGetResourceProvider>());
HttpHandlerResourceV3Provider target = new(proxyCache.Object);

// Act
var result = await target.TryCreate(sourceRepository, CancellationToken.None);
HttpHandlerResourceV3 resource = (HttpHandlerResourceV3)result.Item2;
HttpClientHandler clientHandler = resource.ClientHandler;

// Assert
clientHandler.ServerCertificateCustomValidationCallback.Should().BeNull();
}

[Fact]
public async Task GetAsync_InvalidCertificateWithValidationEnabled_ClientHandlerThrowsAnException()
{
// Arrange
TcpListenerServer server = new()
{
Mode = TestServerMode.InvalidTLSCertificate
};

await server.ExecuteAsync(async uri =>
{
Mock<IProxyCache> proxyCache = new();
proxyCache.Setup(pc => pc.GetProxy(It.IsAny<Uri>())).Returns((IWebProxy)null);
PackageSource packageSource = new(uri, "source");
SourceRepository sourceRepository = new(packageSource, Array.Empty<INuGetResourceProvider>());
HttpHandlerResourceV3Provider target = new(proxyCache.Object);
var result = await target.TryCreate(sourceRepository, CancellationToken.None);
HttpHandlerResourceV3 resource = (HttpHandlerResourceV3)result.Item2;
HttpClientHandler clientHandler = resource.ClientHandler;
var client = new HttpClient(clientHandler);

// Act & Assert
var exception = await Assert.ThrowsAsync<HttpRequestException>(async () => await client.GetAsync(uri));
return 0;
});
}

[Fact]
public async Task GetAsync_InvalidCertificateWithValidationDisabled_ClientHandlerDoesNotThrowAnException()
{
// Arrange
TcpListenerServer server = new()
{
Mode = TestServerMode.InvalidTLSCertificate
};

await server.ExecuteAsync(async uri =>
{
Mock<IProxyCache> proxyCache = new();
proxyCache.Setup(pc => pc.GetProxy(It.IsAny<Uri>())).Returns((IWebProxy)null);
PackageSource packageSource = new(uri, "source")
{
DisableTLSCertificateValidation = true
};
SourceRepository sourceRepository = new(packageSource, Array.Empty<INuGetResourceProvider>());
HttpHandlerResourceV3Provider target = new(proxyCache.Object);
var result = await target.TryCreate(sourceRepository, CancellationToken.None);
HttpHandlerResourceV3 resource = (HttpHandlerResourceV3)result.Item2;
HttpClientHandler clientHandler = resource.ClientHandler;
var client = new HttpClient(clientHandler);

// Act
var response = await client.GetAsync(uri);

// Assert
Assert.True(response.IsSuccessStatusCode);
return 0;
});
}
}
}
Loading

0 comments on commit a7a4025

Please sign in to comment.