From cfd7632298f2ed192698350dbc7f71d975267089 Mon Sep 17 00:00:00 2001 From: AdmiringWorm Date: Mon, 21 Jul 2025 16:12:40 +0200 Subject: [PATCH] (#3600) Handle non-ASCII passwords with legacy encoding fallback Add a method to convert passwords to NetworkCredential using codepage 1252 encoding to support non-ASCII characters in credentials. This addresses issues with encoding mismatches in .NET 4.8.1 that cause incorrect password handling. Update credential retrieval to use this conversion method, falling back to the original password on retry attempts to maintain compatibility. Add tests verifying correct username and legacy-encoded password handling for non-ASCII characters in explicit and implicit credential scenarios. --- .../ChocolateyNugetCredentialProviderSpecs.cs | 63 +++++++++++++++++++ .../ChocolateyNugetCredentialProvider.cs | 56 ++++++++++++++--- 2 files changed, 112 insertions(+), 7 deletions(-) diff --git a/src/chocolatey.tests/infrastructure.app/nuget/ChocolateyNugetCredentialProviderSpecs.cs b/src/chocolatey.tests/infrastructure.app/nuget/ChocolateyNugetCredentialProviderSpecs.cs index 180d805dd..5c4073ab3 100644 --- a/src/chocolatey.tests/infrastructure.app/nuget/ChocolateyNugetCredentialProviderSpecs.cs +++ b/src/chocolatey.tests/infrastructure.app/nuget/ChocolateyNugetCredentialProviderSpecs.cs @@ -182,6 +182,69 @@ public void Should_Provide_The_Correct_Password() } } + public class When_A_Password_Using_Non_ASCII_Characters_With_Explicit_Username_and_Password : ChocolateyNugetCredentialProviderSpecsBase + { + public override void Context() + { + base.Context(); + Configuration.Sources = Configuration.ExplicitSources = TargetSourceUrl; + Configuration.SourceCommand.Username = "user"; + Configuration.SourceCommand.Password = "tøtally_sæcure_påssword!!!"; + } + + [Fact] + public void Should_Find_The_Saved_Source_And_Return_The_Credential() + { + Result.Should().NotBeNull(); + } + + [Fact] + public void Should_Provide_The_Correct_Username() + { + Result.UserName.Should().Be(Username); + } + + [Fact] + public void Should_Provide_Password_Using_1252_Codepage() + { + // The following looks odd, but this is a workaround to + // allow passwords using non-ascii characters to be used + // against sources. + Result.Password.Should().Be("tøtally_sæcure_pÃ¥ssword!!!"); + } + } + + public class When_A_Password_Using_Non_ASCII_Characters_With_Implicit_Usernam_And_Password : ChocolateyNugetCredentialProviderSpecsBase + { + public override void Context() + { + base.Context(); + Configuration.Sources = Configuration.ExplicitSources = TargetSourceName; + Configuration.MachineSources[1].EncryptedPassword = NugetEncryptionUtility.EncryptString("tøtally_sæcure_påssword!!!"); + } + + [Fact] + public void Should_Find_The_Saved_Source_And_Return_The_Credential() + { + Result.Should().NotBeNull(); + } + + [Fact] + public void Should_Provide_The_Correct_Username() + { + Result.UserName.Should().Be(Username); + } + + [Fact] + public void Should_Provide_Password_Using_1252_Codepage() + { + // The following looks odd, but this is a workaround to + // allow passwords using non-ascii characters to be used + // against sources. + Result.Password.Should().Be("tøtally_sæcure_pÃ¥ssword!!!"); + } + } + public class Looks_Up_Source_Url_When_Name_And_Credentials_Is_Provided : ChocolateyNugetCredentialProviderSpecsBase { public override void Context() diff --git a/src/chocolatey/infrastructure.app/nuget/ChocolateyNugetCredentialProvider.cs b/src/chocolatey/infrastructure.app/nuget/ChocolateyNugetCredentialProvider.cs index 16ec76de2..012b7edfd 100644 --- a/src/chocolatey/infrastructure.app/nuget/ChocolateyNugetCredentialProvider.cs +++ b/src/chocolatey/infrastructure.app/nuget/ChocolateyNugetCredentialProvider.cs @@ -17,6 +17,7 @@ using System; using System.Linq; using System.Net; +using System.Text; using System.Text.RegularExpressions; using chocolatey.infrastructure.commandline; using chocolatey.infrastructure.app.configuration; @@ -81,7 +82,7 @@ public Task GetAsync(Uri uri, IWebProxy proxy, CredentialReq { this.Log().Debug("Using passed in credentials"); - return Task.FromResult(new CredentialResponse(new NetworkCredential(_config.SourceCommand.Username, _config.SourceCommand.Password))); + return Task.FromResult(new CredentialResponse(ToNetworkCredentials(_config.SourceCommand.Username, _config.SourceCommand.Password, isRetry))); } } @@ -156,7 +157,9 @@ public Task GetAsync(Uri uri, IWebProxy proxy, CredentialReq this.Log().Debug("Using saved credentials"); } - return Task.FromResult(new CredentialResponse(new NetworkCredential(source.Username, NugetEncryptionUtility.DecryptString(source.EncryptedPassword)))); + var credentials = ToNetworkCredentials(source.Username, NugetEncryptionUtility.DecryptString(source.EncryptedPassword), isRetry); + + return Task.FromResult(new CredentialResponse(credentials)); } #pragma warning disable IDE0060 // unused method parameter @@ -186,15 +189,54 @@ public ICredentials GetUserCredentials(Uri uri, IWebProxy proxy, CredentialReque return CredentialCache.DefaultNetworkCredentials; } - var credentials = new NetworkCredential - { - UserName = username, - Password = password - }; + var credentials = ToNetworkCredentials(username, password, isRetry: false); return credentials; } + private static NetworkCredential ToNetworkCredentials(string username, string password, bool isRetry) + { + if (isRetry) + { + // If we have already attempted using the fallback legacy + // way to set the string, then we will just return the network + // credentials as is, and assume that the server expects the + // encoding used to be the same as the bug in .NET 4.8.1 that + // causes incorrect encodings to be used. + + return new NetworkCredential(username, password); + } + + var utf8Bytes = Encoding.UTF8.GetBytes(password); + try + { + var legacyEncoding = GetEncoding(1252); + + var legacyPassword = legacyEncoding.GetString(utf8Bytes); + + return new NetworkCredential(username, legacyPassword); + } + catch + { + return new NetworkCredential(username, password); + } + } + + private static Encoding GetEncoding(int codepage) + { + try + { + return Encoding.GetEncoding(codepage); + } + catch + { + // If the code page specified isn't available, + // then let us fall back to the default encoding + // used on the system. + return Encoding.Default; + } + } + #pragma warning disable IDE0022, IDE1006 [Obsolete("This overload is deprecated and will be removed in v3.")] public ICredentials get_credentials_from_user(Uri uri, IWebProxy proxy, CredentialRequestType credentialType)