diff --git a/crypto/src/asn1/Asn1UtcTime.cs b/crypto/src/asn1/Asn1UtcTime.cs
index 05de430c4..478b5c485 100644
--- a/crypto/src/asn1/Asn1UtcTime.cs
+++ b/crypto/src/asn1/Asn1UtcTime.cs
@@ -1,14 +1,13 @@
using System;
using System.Globalization;
using System.IO;
+using System.Text;
using Org.BouncyCastle.Utilities;
namespace Org.BouncyCastle.Asn1
{
- /**
- * UTC time object.
- */
+ /// <summary>UTCTime ASN.1 type</summary>
public class Asn1UtcTime
: Asn1Object
{
@@ -55,210 +54,206 @@ namespace Org.BouncyCastle.Asn1
}
}
- throw new ArgumentException("illegal object in GetInstance: " + Platform.GetTypeName(obj));
+ throw new ArgumentException("illegal object in GetInstance: " + Platform.GetTypeName(obj), nameof(obj));
}
- /**
- * return a UTC Time from a tagged object.
- *
- * @param taggedObject the tagged object holding the object we want
- * @param declaredExplicit true if the object is meant to be explicitly tagged false otherwise.
- * @exception ArgumentException if the tagged object cannot be converted.
- */
public static Asn1UtcTime GetInstance(Asn1TaggedObject taggedObject, bool declaredExplicit)
{
return (Asn1UtcTime)Meta.Instance.GetContextInstance(taggedObject, declaredExplicit);
}
- private readonly string time;
+ private readonly string m_timeString;
+ private readonly DateTime m_dateTime;
+ private readonly bool m_dateTimeLocked;
+ private readonly int m_twoDigitYearMax;
- /**
- * The correct format for this is YYMMDDHHMMSSZ (it used to be that seconds were
- * never encoded. When you're creating one of these objects from scratch, that's
- * what you want to use, otherwise we'll try to deal with whatever Gets read from
- * the input stream... (this is why the input format is different from the GetTime()
- * method output).
- * <p>
- * @param time the time string.</p>
- */
- public Asn1UtcTime(string time)
- {
- if (time == null)
- throw new ArgumentNullException("time");
+ public Asn1UtcTime(string timeString)
+ {
+ if (timeString == null)
+ throw new ArgumentNullException(nameof(timeString));
- this.time = time;
+ m_timeString = timeString;
try
{
- ToDateTime();
- }
- catch (FormatException e)
+ m_dateTime = FromString(timeString, out m_twoDigitYearMax);
+ m_dateTimeLocked = false;
+ }
+ catch (FormatException e)
{
throw new ArgumentException("invalid date string: " + e.Message);
}
- }
+ }
- /**
- * base constructor from a DateTime object
- */
- public Asn1UtcTime(DateTime time)
- {
- this.time = time.ToUniversalTime().ToString("yyMMddHHmmss", CultureInfo.InvariantCulture) + "Z";
+ [Obsolete("Use `Asn1UtcTime(DateTime, int)' instead")]
+ public Asn1UtcTime(DateTime dateTime)
+ {
+ DateTime utc = dateTime.ToUniversalTime();
+ dateTime = new DateTime(utc.Year, utc.Month, utc.Day, utc.Hour, utc.Minute, utc.Second, DateTimeKind.Utc);
+
+ m_dateTime = dateTime;
+ m_dateTimeLocked = true;
+ m_timeString = ToStringCanonical(dateTime, out m_twoDigitYearMax);
}
- internal Asn1UtcTime(byte[] contents)
+ public Asn1UtcTime(DateTime dateTime, int twoDigitYearMax)
{
- //
- // explicitly convert to characters
- //
- this.time = Strings.FromAsciiByteArray(contents);
+ DateTime utc = dateTime.ToUniversalTime();
+ dateTime = new DateTime(utc.Year, utc.Month, utc.Day, utc.Hour, utc.Minute, utc.Second, DateTimeKind.Utc);
+
+ Validate(dateTime, twoDigitYearMax);
+
+ m_dateTime = dateTime;
+ m_dateTimeLocked = true;
+ m_timeString = ToStringCanonical(dateTime);
+ m_twoDigitYearMax = twoDigitYearMax;
}
- /**
- * return the time as a date based on whatever a 2 digit year will return. For
- * standardised processing use ToAdjustedDateTime().
- *
- * @return the resulting date
- * @exception ParseException if the date string cannot be parsed.
- */
- public DateTime ToDateTime()
+ internal Asn1UtcTime(byte[] contents)
+ // NOTE: Non-ASCII characters will produce '?' characters, which will fail DateTime parsing
+ : this(Encoding.ASCII.GetString(contents))
{
- return ParseDateString(TimeString, @"yyMMddHHmmss'GMT'zzz");
}
- /**
- * return the time as an adjusted date
- * in the range of 1950 - 2049.
- *
- * @return a date in the range of 1950 to 2049.
- * @exception ParseException if the date string cannot be parsed.
- */
- public DateTime ToAdjustedDateTime()
+ public string TimeString => m_timeString;
+
+ public DateTime ToDateTime()
{
- return ParseDateString(AdjustedTimeString, @"yyyyMMddHHmmss'GMT'zzz");
+ return m_dateTime;
}
- private DateTime ParseDateString(string dateStr, string formatStr)
- {
- DateTime dt = DateTime.ParseExact(
- dateStr,
- formatStr,
- DateTimeFormatInfo.InvariantInfo);
+ public DateTime ToDateTime(int twoDigitYearMax)
+ {
+ if (InRange(m_dateTime, twoDigitYearMax))
+ return m_dateTime;
- return dt.ToUniversalTime();
- }
+ if (m_dateTimeLocked)
+ throw new InvalidOperationException();
- /**
- * return the time - always in the form of
- * YYMMDDhhmmssGMT(+hh:mm|-hh:mm).
- * <p>
- * Normally in a certificate we would expect "Z" rather than "GMT",
- * however adding the "GMT" means we can just use:
- * <pre>
- * dateF = new SimpleDateFormat("yyMMddHHmmssz");
- * </pre>
- * To read in the time and Get a date which is compatible with our local
- * time zone.</p>
- * <p>
- * <b>Note:</b> In some cases, due to the local date processing, this
- * may lead to unexpected results. If you want to stick the normal
- * convention of 1950 to 2049 use the GetAdjustedTime() method.</p>
- */
- public string TimeString
+ int twoDigitYear = m_dateTime.Year % 100;
+ int twoDigitYearCutoff = twoDigitYearMax % 100;
+
+ int diff = twoDigitYear - twoDigitYearCutoff;
+ int newYear = twoDigitYearMax + diff;
+ if (diff > 0)
+ {
+ newYear -= 100;
+ }
+
+ return m_dateTime.AddYears(newYear - m_dateTime.Year);
+ }
+
+ public DateTime ToDateTime(Calendar calendar)
{
- get
- {
- //
- // standardise the format.
- //
- if (time.IndexOf('-') < 0 && time.IndexOf('+') < 0)
- {
- if (time.Length == 11)
- {
- return time.Substring(0, 10) + "00GMT+00:00";
- }
- else
- {
- return time.Substring(0, 12) + "GMT+00:00";
- }
- }
- else
- {
- int index = time.IndexOf('-');
- if (index < 0)
- {
- index = time.IndexOf('+');
- }
- string d = time;
-
- if (index == time.Length - 3)
- {
- d += "00";
- }
-
- if (index == 10)
- {
- return d.Substring(0, 10) + "00GMT" + d.Substring(10, 3) + ":" + d.Substring(13, 2);
- }
- else
- {
- return d.Substring(0, 12) + "GMT" + d.Substring(12, 3) + ":" + d.Substring(15, 2);
- }
- }
- }
+ return ToDateTime(calendar.TwoDigitYearMax);
}
- /// <summary>
- /// Return a time string as an adjusted date with a 4 digit year.
- /// This goes in the range of 1950 - 2049.
- /// </summary>
- public string AdjustedTimeString
- {
- get
- {
- string d = TimeString;
- string c = d[0] < '5' ? "20" : "19";
+ /// <summary>Return an adjusted date in the range of 1950 - 2049.</summary>
+ [Obsolete("Use 'ToDateTime(2049)' instead")]
+ public DateTime ToAdjustedDateTime()
+ {
+ return ToDateTime(2049);
+ }
- return c + d;
- }
- }
+ public int TwoDigitYearMax => m_twoDigitYearMax;
- internal byte[] GetOctets()
+ internal byte[] GetContents(int encoding)
{
- return Strings.ToAsciiByteArray(time);
+ if (encoding == Asn1OutputStream.EncodingDer && m_timeString.Length != 13)
+ {
+ string canonical = ToStringCanonical(m_dateTime);
+ return Encoding.ASCII.GetBytes(canonical);
+ }
+
+ return Encoding.ASCII.GetBytes(m_timeString);
}
internal override IAsn1Encoding GetEncoding(int encoding)
{
- return new PrimitiveEncoding(Asn1Tags.Universal, Asn1Tags.UtcTime, GetOctets());
+ return new PrimitiveEncoding(Asn1Tags.Universal, Asn1Tags.UtcTime, GetContents(encoding));
}
internal override IAsn1Encoding GetEncodingImplicit(int encoding, int tagClass, int tagNo)
{
- return new PrimitiveEncoding(tagClass, tagNo, GetOctets());
+ return new PrimitiveEncoding(tagClass, tagNo, GetContents(encoding));
}
protected override bool Asn1Equals(Asn1Object asn1Object)
- {
+ {
if (!(asn1Object is Asn1UtcTime that))
return false;
- return this.time == that.time;
+ // TODO Performance
+ return Arrays.AreEqual(
+ this.GetContents(Asn1OutputStream.EncodingDer),
+ that.GetContents(Asn1OutputStream.EncodingDer));
}
- protected override int Asn1GetHashCode()
- {
- return time.GetHashCode();
+ protected override int Asn1GetHashCode()
+ {
+ // TODO Performance
+ return Arrays.GetHashCode(
+ this.GetContents(Asn1OutputStream.EncodingDer));
}
- public override string ToString()
- {
- return time;
- }
+ public override string ToString()
+ {
+ return m_timeString;
+ }
internal static Asn1UtcTime CreatePrimitive(byte[] contents)
{
return new Asn1UtcTime(contents);
}
+
+ private static DateTime FromString(string s, out int twoDigitYearMax)
+ {
+ var provider = DateTimeFormatInfo.InvariantInfo;
+ twoDigitYearMax = provider.Calendar.TwoDigitYearMax;
+
+ switch (s.Length)
+ {
+ case 11:
+ return DateTime.ParseExact(s, @"yyMMddHHmm\Z", provider,
+ DateTimeStyles.AdjustToUniversal | DateTimeStyles.AssumeUniversal);
+ case 13:
+ return DateTime.ParseExact(s, @"yyMMddHHmmss\Z", provider,
+ DateTimeStyles.AdjustToUniversal | DateTimeStyles.AssumeUniversal);
+ case 15:
+ return DateTime.ParseExact(s, @"yyMMddHHmmzzz", provider,
+ DateTimeStyles.AdjustToUniversal);
+ case 17:
+ return DateTime.ParseExact(s, @"yyMMddHHmmsszzz", provider,
+ DateTimeStyles.AdjustToUniversal);
+ default:
+ throw new FormatException();
+ }
+ }
+
+ private static bool InRange(DateTime dateTime, int twoDigitYearMax)
+ {
+ return (uint)(twoDigitYearMax - dateTime.Year) < 100;
+ }
+
+ private static string ToStringCanonical(DateTime dateTime, out int twoDigitYearMax)
+ {
+ var provider = DateTimeFormatInfo.InvariantInfo;
+ twoDigitYearMax = provider.Calendar.TwoDigitYearMax;
+
+ Validate(dateTime, twoDigitYearMax);
+
+ return dateTime.ToString(@"yyMMddHHmmss\Z", provider);
+ }
+
+ private static string ToStringCanonical(DateTime dateTime)
+ {
+ return dateTime.ToString(@"yyMMddHHmmss\Z", DateTimeFormatInfo.InvariantInfo);
+ }
+
+ private static void Validate(DateTime dateTime, int twoDigitYearMax)
+ {
+ if (!InRange(dateTime, twoDigitYearMax))
+ throw new ArgumentOutOfRangeException(nameof(dateTime));
+ }
}
}
|