From 8609418378fd15633ee6fdbf1628a387b70605bb Mon Sep 17 00:00:00 2001 From: Guflly <145608489+Guflly@users.noreply.github.com> Date: Mon, 27 Jul 2026 14:41:55 -0700 Subject: [PATCH] Reject fragment delimiters in OPC part names --- src/Sign.Core/Tools/VsixSignTool/OpcPart.cs | 7 ++- .../VSIXSignTool/OpcPackageSigningTests.cs | 51 +++++++++++++++++++ 2 files changed, 57 insertions(+), 1 deletion(-) diff --git a/src/Sign.Core/Tools/VsixSignTool/OpcPart.cs b/src/Sign.Core/Tools/VsixSignTool/OpcPart.cs index fe29ae8e..724085a4 100644 --- a/src/Sign.Core/Tools/VsixSignTool/OpcPart.cs +++ b/src/Sign.Core/Tools/VsixSignTool/OpcPart.cs @@ -18,6 +18,11 @@ internal sealed class OpcPart : IEquatable internal OpcPart(OpcPackage package, string path, ZipArchiveEntry entry, OpcPackageFileMode mode) { + if (path.Contains('#')) + { + throw new InvalidDataException($"Package part name '{path}' contains the unsupported '#' character."); + } + Uri = new Uri(OpcPackage.BasePackageUri, path); Package = package; _path = path; @@ -111,4 +116,4 @@ private OpcRelationships ConstructRelationships() /// public override int GetHashCode() => Uri.GetHashCode(); } -} \ No newline at end of file +} diff --git a/test/Sign.Core.Test/Tools/VSIXSignTool/OpcPackageSigningTests.cs b/test/Sign.Core.Test/Tools/VSIXSignTool/OpcPackageSigningTests.cs index 350b0a92..42100f21 100644 --- a/test/Sign.Core.Test/Tools/VSIXSignTool/OpcPackageSigningTests.cs +++ b/test/Sign.Core.Test/Tools/VSIXSignTool/OpcPackageSigningTests.cs @@ -2,10 +2,12 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE.txt file in the project root for more information. +using System.IO.Compression; using System.Globalization; using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; using System.Xml; +using Microsoft.Extensions.Logging; using Sign.Core.Timestamp; using Xunit.Abstractions; @@ -70,6 +72,40 @@ public static IEnumerable RsaSigningTheories } } + [Theory] + [InlineData("ab#c.txt")] + [InlineData("folder/ab#c.txt")] + public async Task ShouldRejectPartNameContainingFragmentDelimiter(string entryName) + { + string path = CreatePackageWithEntry(entryName); + VsixSignTool signTool = new(Moq.Mock.Of>()); + + using (X509Certificate2 certificate = _pfxFilesFixture.GetPfx( + keySizeInBits: 2048, + HashAlgorithmName.SHA256)) + using (RSA? rsaPrivateKey = certificate.GetRSAPrivateKey()) + { + SignConfigurationSet configuration = new( + publicCertificate: certificate, + signatureDigestAlgorithm: HashAlgorithmName.SHA256, + fileDigestAlgorithm: HashAlgorithmName.SHA256, + signingKey: rsaPrivateKey!); + SignOptions options = new( + fileHashAlgorithm: HashAlgorithmName.SHA256, + timestampService: null!); + + InvalidDataException exception = await Assert.ThrowsAsync( + () => signTool.SignAsync(new FileInfo(path), configuration, options)); + + Assert.Contains(entryName, exception.Message, StringComparison.Ordinal); + } + + using (OpcPackage package = OpcPackage.Open(path)) + { + Assert.Empty(package.GetSignatures()); + } + } + [Theory] [MemberData(nameof(RsaTimestampTheories))] public async Task ShouldTimestampFileWithRsa(int keySizeInBits, HashAlgorithmName hashAlgorithmName, HashAlgorithmName timestampDigestAlgorithm) @@ -274,6 +310,21 @@ public static IEnumerable RsaTimestampTheories } } + private string CreatePackageWithEntry(string entryName) + { + string path = Path.GetTempFileName(); + _shadowFiles.Add(path); + File.Copy(SamplePackage, path, overwrite: true); + + using (ZipArchive archive = ZipFile.Open(path, ZipArchiveMode.Update)) + using (StreamWriter writer = new(archive.CreateEntry(entryName).Open())) + { + writer.Write("test"); + } + + return path; + } + private OpcPackage ShadowCopyPackage(string packagePath, out string path, OpcPackageFileMode mode = OpcPackageFileMode.Read) { string temp = Path.GetTempFileName();