diff options
Diffstat (limited to 'crypto/src/asn1/Asn1UtcTime.cs')
-rw-r--r-- | crypto/src/asn1/Asn1UtcTime.cs | 295 |
1 files changed, 145 insertions, 150 deletions
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)); + } } } |