From 1a94f0748bdc4b922c8896dfafc2fdbca3478c95 Mon Sep 17 00:00:00 2001 From: Nigusu Yenework Date: Fri, 17 Apr 2026 12:52:13 -0700 Subject: [PATCH 1/2] Migrate RepositorySignature --- .../Converters/FingerprintsStjConverter.cs | 42 +++++++++++++++++++ .../Model/RepositoryCertificateInfo.cs | 19 ++++++--- .../Model/RepositorySignatureModel.cs | 16 +++++++ .../NuGet.Protocol/NuGet.Protocol.csproj | 1 + .../RepositorySignatureResourceProvider.cs | 10 ++++- .../Resources/RepositorySignatureResource.cs | 22 ++++++++++ .../NuGet.Protocol/Utility/JsonContext.cs | 3 +- 7 files changed, 104 insertions(+), 9 deletions(-) create mode 100644 src/NuGet.Core/NuGet.Protocol/Converters/FingerprintsStjConverter.cs create mode 100644 src/NuGet.Core/NuGet.Protocol/Model/RepositorySignatureModel.cs diff --git a/src/NuGet.Core/NuGet.Protocol/Converters/FingerprintsStjConverter.cs b/src/NuGet.Core/NuGet.Protocol/Converters/FingerprintsStjConverter.cs new file mode 100644 index 00000000000..67a624634c1 --- /dev/null +++ b/src/NuGet.Core/NuGet.Protocol/Converters/FingerprintsStjConverter.cs @@ -0,0 +1,42 @@ +// 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; +using System.Collections.Generic; +using System.Text.Json; +using System.Text.Json.Serialization; +using NuGet.Packaging.Core; + +namespace NuGet.Protocol.Converters +{ + internal sealed class FingerprintsStjConverter : JsonConverter + { + public override Fingerprints Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + if (reader.TokenType != JsonTokenType.StartObject) + { + throw new JsonException(); + } + + var dict = new Dictionary(); + while (reader.Read() && reader.TokenType == JsonTokenType.PropertyName) + { + string key = reader.GetString()!; + reader.Read(); + dict[key] = reader.GetString() ?? string.Empty; + } + + return new Fingerprints(dict); + } + + public override void Write(Utf8JsonWriter writer, Fingerprints value, JsonSerializerOptions options) + { + writer.WriteStartObject(); + foreach (KeyValuePair kvp in value) + { + writer.WriteString(kvp.Key, kvp.Value); + } + writer.WriteEndObject(); + } + } +} diff --git a/src/NuGet.Core/NuGet.Protocol/Model/RepositoryCertificateInfo.cs b/src/NuGet.Core/NuGet.Protocol/Model/RepositoryCertificateInfo.cs index a6025f90b4c..ddfde63bca3 100644 --- a/src/NuGet.Core/NuGet.Protocol/Model/RepositoryCertificateInfo.cs +++ b/src/NuGet.Core/NuGet.Protocol/Model/RepositoryCertificateInfo.cs @@ -4,27 +4,34 @@ using System; using Newtonsoft.Json; using NuGet.Packaging.Core; +using System.Text.Json.Serialization; namespace NuGet.Protocol { public class RepositoryCertificateInfo : IRepositoryCertificateInfo { [JsonProperty(PropertyName = JsonProperties.Fingerprints, Required = Required.Always)] - public Fingerprints Fingerprints { get; private set; } = null!; + [JsonPropertyName(JsonProperties.Fingerprints)] + public Fingerprints Fingerprints { get; internal init; } = null!; [JsonProperty(PropertyName = JsonProperties.Subject, Required = Required.Always)] - public string Subject { get; private set; } = null!; + [JsonPropertyName(JsonProperties.Subject)] + public string Subject { get; internal init; } = null!; [JsonProperty(PropertyName = JsonProperties.Issuer, Required = Required.Always)] - public string Issuer { get; private set; } = null!; + [JsonPropertyName(JsonProperties.Issuer)] + public string Issuer { get; internal init; } = null!; [JsonProperty(PropertyName = JsonProperties.NotBefore, Required = Required.Always)] - public DateTimeOffset NotBefore { get; private set; } + [JsonPropertyName(JsonProperties.NotBefore)] + public DateTimeOffset NotBefore { get; internal init; } [JsonProperty(PropertyName = JsonProperties.NotAfter, Required = Required.Always)] - public DateTimeOffset NotAfter { get; private set; } + [JsonPropertyName(JsonProperties.NotAfter)] + public DateTimeOffset NotAfter { get; internal init; } [JsonProperty(PropertyName = JsonProperties.ContentUrl, Required = Required.Always)] - public string ContentUrl { get; private set; } = null!; + [JsonPropertyName(JsonProperties.ContentUrl)] + public string ContentUrl { get; internal init; } = null!; } } diff --git a/src/NuGet.Core/NuGet.Protocol/Model/RepositorySignatureModel.cs b/src/NuGet.Core/NuGet.Protocol/Model/RepositorySignatureModel.cs new file mode 100644 index 00000000000..409bf1ff948 --- /dev/null +++ b/src/NuGet.Core/NuGet.Protocol/Model/RepositorySignatureModel.cs @@ -0,0 +1,16 @@ +// 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.Text.Json.Serialization; + +namespace NuGet.Protocol.Model +{ + internal sealed class RepositorySignatureModel + { + [JsonPropertyName(JsonProperties.AllRepositorySigned)] + public bool? AllRepositorySigned { get; set; } + + [JsonPropertyName(JsonProperties.SigningCertificates)] + public RepositoryCertificateInfo[]? SigningCertificates { get; set; } + } +} diff --git a/src/NuGet.Core/NuGet.Protocol/NuGet.Protocol.csproj b/src/NuGet.Core/NuGet.Protocol/NuGet.Protocol.csproj index 5ed856305a9..4e873de17c2 100644 --- a/src/NuGet.Core/NuGet.Protocol/NuGet.Protocol.csproj +++ b/src/NuGet.Core/NuGet.Protocol/NuGet.Protocol.csproj @@ -37,6 +37,7 @@ + diff --git a/src/NuGet.Core/NuGet.Protocol/Providers/RepositorySignatureResourceProvider.cs b/src/NuGet.Core/NuGet.Protocol/Providers/RepositorySignatureResourceProvider.cs index 719c4aae806..6c2f0829230 100644 --- a/src/NuGet.Core/NuGet.Protocol/Providers/RepositorySignatureResourceProvider.cs +++ b/src/NuGet.Core/NuGet.Protocol/Providers/RepositorySignatureResourceProvider.cs @@ -6,10 +6,13 @@ using System; using System.Globalization; using System.Linq; +using System.Text.Json; using System.Threading; using System.Threading.Tasks; using NuGet.Common; using NuGet.Protocol.Core.Types; +using NuGet.Protocol.Model; +using NuGet.Protocol.Utility; namespace NuGet.Protocol { @@ -80,9 +83,12 @@ private async Task GetRepositorySignatureResourceAs }, async httpSourceResult => { - var json = await httpSourceResult.Stream.AsJObjectAsync(token); + RepositorySignatureModel model = await JsonSerializer.DeserializeAsync( + httpSourceResult.Stream, + JsonContext.Default.RepositorySignatureModel, + token); - return new RepositorySignatureResource(json, source); + return new RepositorySignatureResource(model, source); }, log, token); diff --git a/src/NuGet.Core/NuGet.Protocol/Resources/RepositorySignatureResource.cs b/src/NuGet.Core/NuGet.Protocol/Resources/RepositorySignatureResource.cs index 85740ba0666..0cbe6142187 100644 --- a/src/NuGet.Core/NuGet.Protocol/Resources/RepositorySignatureResource.cs +++ b/src/NuGet.Core/NuGet.Protocol/Resources/RepositorySignatureResource.cs @@ -11,6 +11,7 @@ using NuGet.Packaging; using NuGet.Packaging.Core; using NuGet.Protocol.Core.Types; +using NuGet.Protocol.Model; namespace NuGet.Protocol { @@ -44,6 +45,27 @@ public RepositorySignatureResource(JObject repoSignInformationContent, SourceRep Source = source.PackageSource.Source; } + internal RepositorySignatureResource(RepositorySignatureModel model, SourceRepository source) + { + AllRepositorySigned = model.AllRepositorySigned ?? + throw new FatalProtocolException(string.Format(CultureInfo.CurrentCulture, Strings.Log_FailedToParseRepoSignInfor, JsonProperties.AllRepositorySigned, source.PackageSource.Source)); + + RepositoryCertificateInfo[] certs = model.SigningCertificates ?? + throw new FatalProtocolException(string.Format(CultureInfo.CurrentCulture, Strings.Log_FailedToParseRepoSignInfor, JsonProperties.SigningCertificates, source.PackageSource.Source)); + + foreach (RepositoryCertificateInfo cert in certs) + { + if (!Uri.TryCreate(cert.ContentUrl, UriKind.Absolute, out Uri contentUrl) + || !string.Equals(contentUrl.Scheme, "https", StringComparison.OrdinalIgnoreCase)) + { + throw new FatalProtocolException(Strings.RepositoryContentUrlMustBeHttps); + } + } + + RepositoryCertificateInfos = certs; + Source = source.PackageSource.Source; + } + // Test only. public RepositorySignatureResource(bool allRepositorySigned, IEnumerable repositoryCertInfos) { diff --git a/src/NuGet.Core/NuGet.Protocol/Utility/JsonContext.cs b/src/NuGet.Core/NuGet.Protocol/Utility/JsonContext.cs index 2a181357a86..4155f3ba1d6 100644 --- a/src/NuGet.Core/NuGet.Protocol/Utility/JsonContext.cs +++ b/src/NuGet.Core/NuGet.Protocol/Utility/JsonContext.cs @@ -12,11 +12,12 @@ namespace NuGet.Protocol.Utility [JsonSourceGenerationOptions(PropertyNameCaseInsensitive = true, PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase, GenerationMode = JsonSourceGenerationMode.Metadata, - Converters = [typeof(VersionRangeStjConverter)])] + Converters = [typeof(VersionRangeStjConverter), typeof(FingerprintsStjConverter)])] #pragma warning restore CS3016 // Arrays as attribute arguments is not CLS-compliant [JsonSerializable(typeof(HttpFileSystemBasedFindPackageByIdResource.FlatContainerVersionList))] [JsonSerializable(typeof(IReadOnlyList), TypeInfoPropertyName = "VulnerabilityIndex")] [JsonSerializable(typeof(CaseInsensitiveDictionary>), TypeInfoPropertyName = "VulnerabilityPage")] + [JsonSerializable(typeof(RepositorySignatureModel))] internal partial class JsonContext : JsonSerializerContext { } From de38336f02a7561268eb31a7ed85703f465d22cd Mon Sep 17 00:00:00 2001 From: Nigusu Yenework Date: Fri, 17 Apr 2026 13:08:14 -0700 Subject: [PATCH 2/2] constractor --- .../Model/RepositoryCertificateInfo.cs | 31 +++++++++++++++---- 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/src/NuGet.Core/NuGet.Protocol/Model/RepositoryCertificateInfo.cs b/src/NuGet.Core/NuGet.Protocol/Model/RepositoryCertificateInfo.cs index ddfde63bca3..fee1b68bb4b 100644 --- a/src/NuGet.Core/NuGet.Protocol/Model/RepositoryCertificateInfo.cs +++ b/src/NuGet.Core/NuGet.Protocol/Model/RepositoryCertificateInfo.cs @@ -12,26 +12,45 @@ public class RepositoryCertificateInfo : IRepositoryCertificateInfo { [JsonProperty(PropertyName = JsonProperties.Fingerprints, Required = Required.Always)] [JsonPropertyName(JsonProperties.Fingerprints)] - public Fingerprints Fingerprints { get; internal init; } = null!; + public Fingerprints Fingerprints { get; private set; } = null!; [JsonProperty(PropertyName = JsonProperties.Subject, Required = Required.Always)] [JsonPropertyName(JsonProperties.Subject)] - public string Subject { get; internal init; } = null!; + public string Subject { get; private set; } = null!; [JsonProperty(PropertyName = JsonProperties.Issuer, Required = Required.Always)] [JsonPropertyName(JsonProperties.Issuer)] - public string Issuer { get; internal init; } = null!; + public string Issuer { get; private set; } = null!; [JsonProperty(PropertyName = JsonProperties.NotBefore, Required = Required.Always)] [JsonPropertyName(JsonProperties.NotBefore)] - public DateTimeOffset NotBefore { get; internal init; } + public DateTimeOffset NotBefore { get; private set; } [JsonProperty(PropertyName = JsonProperties.NotAfter, Required = Required.Always)] [JsonPropertyName(JsonProperties.NotAfter)] - public DateTimeOffset NotAfter { get; internal init; } + public DateTimeOffset NotAfter { get; private set; } [JsonProperty(PropertyName = JsonProperties.ContentUrl, Required = Required.Always)] [JsonPropertyName(JsonProperties.ContentUrl)] - public string ContentUrl { get; internal init; } = null!; + public string ContentUrl { get; private set; } = null!; + + public RepositoryCertificateInfo() { } + + [System.Text.Json.Serialization.JsonConstructor] + internal RepositoryCertificateInfo( + Fingerprints fingerprints, + string subject, + string issuer, + DateTimeOffset notBefore, + DateTimeOffset notAfter, + string contentUrl) + { + Fingerprints = fingerprints; + Subject = subject; + Issuer = issuer; + NotBefore = notBefore; + NotAfter = notAfter; + ContentUrl = contentUrl; + } } }