From 3e273facbef5f3a3d88506d62fbeff22fc89070b Mon Sep 17 00:00:00 2001 From: Suciu Mircea Adrian Date: Wed, 26 Oct 2022 09:40:27 +0300 Subject: [PATCH] Encode and decode CRL time values as UTC or GeneralizedTime depending on the value of the year. (#1959) - Follow RFC to encode time/date >=2050 as generalized time - Fix bug that encoded generalized time was not properly decoded --- .../X509Crl/CrlBuilder.cs | 21 ++++++- .../X509Crl/X509Crl.cs | 40 +++++++++--- .../CRLTests.cs | 62 +++++++++++++++++++ 3 files changed, 113 insertions(+), 10 deletions(-) diff --git a/Libraries/Opc.Ua.Security.Certificates/X509Crl/CrlBuilder.cs b/Libraries/Opc.Ua.Security.Certificates/X509Crl/CrlBuilder.cs index 70b97d5b5e..a38331b1df 100644 --- a/Libraries/Opc.Ua.Security.Certificates/X509Crl/CrlBuilder.cs +++ b/Libraries/Opc.Ua.Security.Certificates/X509Crl/CrlBuilder.cs @@ -327,13 +327,13 @@ namespace Opc.Ua.Security.Certificates crlWriter.WriteEncodedValue((ReadOnlySpan)IssuerName.RawData); // this update - crlWriter.WriteUtcTime(this.ThisUpdate); + WriteTime(crlWriter, ThisUpdate); if (NextUpdate != DateTime.MinValue && NextUpdate > ThisUpdate) { // next update - crlWriter.WriteUtcTime(NextUpdate); + WriteTime(crlWriter, NextUpdate); } // sequence to start the revoked certificates. @@ -384,6 +384,23 @@ namespace Opc.Ua.Security.Certificates return crlWriter.Encode(); } } + + /// + /// Write either a UTC time or a Generalized time depending if DataTime is before or after 2050. + /// + /// The writer to write to. + /// The date time to write. + private static void WriteTime(AsnWriter writer, DateTime dateTime) + { + if (dateTime.Year < 2050) + { + writer.WriteUtcTime(dateTime); + } + else + { + writer.WriteGeneralizedTime(dateTime); + } + } #endregion #region Private Fields diff --git a/Libraries/Opc.Ua.Security.Certificates/X509Crl/X509Crl.cs b/Libraries/Opc.Ua.Security.Certificates/X509Crl/X509Crl.cs index 3dfc557260..e1c8e6b576 100644 --- a/Libraries/Opc.Ua.Security.Certificates/X509Crl/X509Crl.cs +++ b/Libraries/Opc.Ua.Security.Certificates/X509Crl/X509Crl.cs @@ -220,7 +220,7 @@ namespace Opc.Ua.Security.Certificates } /// - /// Decode the Tbs of the CRL. + /// Decode the Tbs of the CRL. /// /// The raw TbsCertList of the CRL. internal void DecodeCrl(byte[] tbs) @@ -262,15 +262,11 @@ namespace Opc.Ua.Security.Certificates m_issuerName = new X500DistinguishedName(seqReader.ReadEncodedValue().ToArray()); // thisUpdate - m_thisUpdate = seqReader.ReadUtcTime().UtcDateTime; + m_thisUpdate = ReadTime(seqReader, optional: false); // nextUpdate is OPTIONAL - var utcTag = new Asn1Tag(UniversalTagNumber.UtcTime); - peekTag = seqReader.PeekTag(); - if (peekTag == utcTag) - { - m_nextUpdate = seqReader.ReadUtcTime().UtcDateTime; - } + m_nextUpdate = ReadTime(seqReader, optional: true); + var seqTag = new Asn1Tag(UniversalTagNumber.Sequence, true); peekTag = seqReader.PeekTag(); @@ -331,6 +327,34 @@ namespace Opc.Ua.Security.Certificates } } + /// + /// Read the time, UTC or local time + /// + /// + /// + /// The DateTime representing the tag + private DateTime ReadTime(AsnReader asnReader, bool optional) + { + // determine if the time is UTC or GeneralizedTime time + var timeTag = asnReader.PeekTag(); + if (timeTag.TagValue == Asn1Tag.UtcTime.TagValue) + { + return asnReader.ReadUtcTime().UtcDateTime; + } + else if (timeTag.TagValue == Asn1Tag.GeneralizedTime.TagValue) + { + return asnReader.ReadGeneralizedTime().LocalDateTime; + } + else if (optional) + { + return DateTime.MinValue; + } + else + { + throw new AsnContentException("The CRL contains an invalid time tag."); + } + } + /// /// Decode if RawData is yet undecoded. /// diff --git a/Tests/Opc.Ua.Security.Certificates.Tests/CRLTests.cs b/Tests/Opc.Ua.Security.Certificates.Tests/CRLTests.cs index 05ac40164f..07b2c7499e 100644 --- a/Tests/Opc.Ua.Security.Certificates.Tests/CRLTests.cs +++ b/Tests/Opc.Ua.Security.Certificates.Tests/CRLTests.cs @@ -211,6 +211,68 @@ namespace Opc.Ua.Security.Certificates.Tests Assert.True(x509Crl.VerifySignature(issuerPubKey, true)); } } + + /// + /// Validate a CRL Builder and decoder pass on using utc and generalized times. + /// + [Test] + public void CrlUtcAndGeneralizedTimeTest() + { + // Generate a CRL with dates over 2050 + var dname = new X500DistinguishedName("CN=Test, O=OPC Foundation"); + var hash = HashAlgorithmName.SHA256; + var crlBuilder = CrlBuilder.Create(dname, hash) + .SetThisUpdate(DateTime.Today.AddYears(30)) + .SetNextUpdate(DateTime.Today.AddYears(30).AddDays(1)); + byte[] serial = new byte[] { 4, 5, 6, 7 }; + var revokedarray = new RevokedCertificate(serial); + crlBuilder.RevokedCertificates.Add(revokedarray); + string serstring = "45678910"; + var revokedstring = new RevokedCertificate(serstring); + crlBuilder.RevokedCertificates.Add(revokedstring); + crlBuilder.CrlExtensions.Add(X509Extensions.BuildCRLNumber(123)); + var crlEncoded = crlBuilder.Encode(); + Assert.NotNull(crlEncoded); + var x509Crl = new X509CRL(); + x509Crl.DecodeCrl(crlEncoded); + Assert.NotNull(x509Crl); + Assert.NotNull(x509Crl.CrlExtensions); + Assert.NotNull(x509Crl.RevokedCertificates); + Assert.AreEqual(dname.RawData, x509Crl.IssuerName.RawData); + Assert.AreEqual(crlBuilder.ThisUpdate, x509Crl.ThisUpdate); + Assert.AreEqual(crlBuilder.NextUpdate, x509Crl.NextUpdate); + Assert.AreEqual(2, x509Crl.RevokedCertificates.Count); + Assert.AreEqual(serial, x509Crl.RevokedCertificates[0].UserCertificate); + Assert.AreEqual(serstring, x509Crl.RevokedCertificates[1].SerialNumber); + Assert.AreEqual(1, x509Crl.CrlExtensions.Count); + Assert.AreEqual(hash, x509Crl.HashAlgorithmName); + + // Generate a CRL with dates up-to 2050 + DateTime dateToEncode = DateTime.UtcNow.Date; + crlBuilder = CrlBuilder.Create(dname, hash) + .SetThisUpdate(dateToEncode) + .SetNextUpdate(dateToEncode.AddDays(1)); + revokedarray = new RevokedCertificate(serial); + crlBuilder.RevokedCertificates.Add(revokedarray); + revokedstring = new RevokedCertificate(serstring); + crlBuilder.RevokedCertificates.Add(revokedstring); + crlBuilder.CrlExtensions.Add(X509Extensions.BuildCRLNumber(123)); + crlEncoded = crlBuilder.Encode(); + Assert.NotNull(crlEncoded); + x509Crl = new X509CRL(); + x509Crl.DecodeCrl(crlEncoded); + Assert.NotNull(x509Crl); + Assert.NotNull(x509Crl.CrlExtensions); + Assert.NotNull(x509Crl.RevokedCertificates); + Assert.AreEqual(dname.RawData, x509Crl.IssuerName.RawData); + Assert.AreEqual(crlBuilder.ThisUpdate, x509Crl.ThisUpdate); + Assert.AreEqual(crlBuilder.NextUpdate, x509Crl.NextUpdate); + Assert.AreEqual(2, x509Crl.RevokedCertificates.Count); + Assert.AreEqual(serial, x509Crl.RevokedCertificates[0].UserCertificate); + Assert.AreEqual(serstring, x509Crl.RevokedCertificates[1].SerialNumber); + Assert.AreEqual(1, x509Crl.CrlExtensions.Count); + Assert.AreEqual(hash, x509Crl.HashAlgorithmName); + } #endregion #region Private Methods