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
This commit is contained in:
Suciu Mircea Adrian
2022-10-26 08:40:27 +02:00
committed by GitHub
parent 8c7da18657
commit 3e273facbe
3 changed files with 113 additions and 10 deletions
@@ -327,13 +327,13 @@ namespace Opc.Ua.Security.Certificates
crlWriter.WriteEncodedValue((ReadOnlySpan<byte>)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();
}
}
/// <summary>
/// Write either a UTC time or a Generalized time depending if DataTime is before or after 2050.
/// </summary>
/// <param name="writer">The writer to write to.</param>
/// <param name="dateTime">The date time to write.</param>
private static void WriteTime(AsnWriter writer, DateTime dateTime)
{
if (dateTime.Year < 2050)
{
writer.WriteUtcTime(dateTime);
}
else
{
writer.WriteGeneralizedTime(dateTime);
}
}
#endregion
#region Private Fields
@@ -220,7 +220,7 @@ namespace Opc.Ua.Security.Certificates
}
/// <summary>
/// Decode the Tbs of the CRL.
/// Decode the Tbs of the CRL.
/// </summary>
/// <param name="tbs">The raw TbsCertList of the CRL.</param>
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
}
}
/// <summary>
/// Read the time, UTC or local time
/// </summary>
/// <param name="asnReader"></param>
/// <param name="optional"></param>
/// <returns>The DateTime representing the tag</returns>
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.");
}
}
/// <summary>
/// Decode if RawData is yet undecoded.
/// </summary>
@@ -211,6 +211,68 @@ namespace Opc.Ua.Security.Certificates.Tests
Assert.True(x509Crl.VerifySignature(issuerPubKey, true));
}
}
/// <summary>
/// Validate a CRL Builder and decoder pass on using utc and generalized times.
/// </summary>
[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