Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/Sign.Core/Tools/VsixSignTool/XmlSignatureBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// 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.Globalization;
using System.Security.Cryptography.X509Certificates;
using System.Security.Cryptography.Xml;
using System.Xml;
Expand Down Expand Up @@ -226,7 +227,7 @@ public void SetFileManifest(OpcSignatureManifest manifest)
var signatureTimeFormatElement = _document.CreateElement("Format", OpcKnownUris.XmlDigitalSignature.AbsoluteUri);
var signatureTimeValueElement = _document.CreateElement("Value", OpcKnownUris.XmlDigitalSignature.AbsoluteUri);
signatureTimeFormatElement.InnerText = "YYYY-MM-DDThh:mm:ss.sTZD";
signatureTimeValueElement.InnerText = _signingContext.ContextCreationTime.ToString("yyyy-MM-ddTHH:mm:ss.fzzz");
signatureTimeValueElement.InnerText = _signingContext.ContextCreationTime.ToString("yyyy-MM-ddTHH:mm:ss.fzzz", CultureInfo.InvariantCulture);

signatureTimeElement.AppendChild(signatureTimeFormatElement);
signatureTimeElement.AppendChild(signatureTimeValueElement);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@
// 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.Globalization;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Xml;
using Sign.Core.Timestamp;
using Xunit.Abstractions;

Expand Down Expand Up @@ -220,6 +222,73 @@ public void ShouldRemoveSignature()
}
}

[Fact]
public void ShouldUseInvariantCultureForContextCreationTime()
{
CultureInfo originalCulture = CultureInfo.CurrentCulture;

try
{
// This test only works if the current culture is one of a set of cultures that includes en-DK that
// that repro the original bug. However, because tests should not rely on a specific culture being
// installed, we'll create a custom culture just for this test.
var customCulture = (CultureInfo)CultureInfo.InvariantCulture.Clone();

customCulture.DateTimeFormat.TimeSeparator = ".";

CultureInfo.CurrentCulture = customCulture;

using (OpcPackage package = ShadowCopyPackage(
SamplePackage,
out string? path,
OpcPackageFileMode.ReadWrite))
{
OpcPackageSignatureBuilder signerBuilder = package.CreateSignatureBuilder();

signerBuilder.EnqueueNamedPreset<VSIXSignatureBuilderPreset>();

using (X509Certificate2 certificate = _pfxFilesFixture.GetPfx(
keySizeInBits: 3072,
HashAlgorithmName.SHA384))
using (RSA? rsaPrivateKey = certificate.GetRSAPrivateKey())
{
OpcSignature signature = signerBuilder.Sign(
new SignConfigurationSet(
publicCertificate: certificate,
signatureDigestAlgorithm: HashAlgorithmName.SHA384,
fileDigestAlgorithm: HashAlgorithmName.SHA384,
signingKey: rsaPrivateKey!));

using (Stream stream = signature.Part!.Open())
{
XmlDocument document = new();

document.Load(stream);

XmlNode? signatureTimeValueElement = document.GetElementsByTagName("Value")[0];

Assert.NotNull(signatureTimeValueElement);

const string expectedFormat = "yyyy-MM-ddTHH:mm:ss.fzzz";

bool isValidFormat = DateTimeOffset.TryParseExact(
signatureTimeValueElement.InnerText,
expectedFormat,
CultureInfo.InvariantCulture,
DateTimeStyles.None,
out DateTimeOffset parsedDateTime);

Assert.True(isValidFormat, $"The date time string '{signatureTimeValueElement.InnerText}' does not match the expected format '{expectedFormat}'.");
}
}
}
}
finally
{
CultureInfo.CurrentCulture = originalCulture;
}
}

public static IEnumerable<object[]> RsaTimestampTheories
{
get
Expand All @@ -230,9 +299,9 @@ public static IEnumerable<object[]> RsaTimestampTheories

private OpcPackage ShadowCopyPackage(string packagePath, out string path, OpcPackageFileMode mode = OpcPackageFileMode.Read)
{
var temp = Path.GetTempFileName();
string temp = Path.GetTempFileName();
_shadowFiles.Add(temp);
File.Copy(packagePath, temp, true);
File.Copy(packagePath, temp, overwrite: true);
path = temp;
return OpcPackage.Open(temp, mode);
}
Expand Down