diff options
author | Peter Dettman <peter.dettman@bouncycastle.org> | 2022-11-08 13:18:17 +0700 |
---|---|---|
committer | Peter Dettman <peter.dettman@bouncycastle.org> | 2022-11-08 13:18:17 +0700 |
commit | 879bb29bb0058a0723326de6edebb201e4cbc0b8 (patch) | |
tree | 089e7769c2342a99dfa7549c57290522f72c451b | |
parent | removed unused SecureRandom (diff) | |
download | BouncyCastle.NET-ed25519-879bb29bb0058a0723326de6edebb201e4cbc0b8.tar.xz |
Overhaul GeneralizedTime classes
-rw-r--r-- | crypto/src/asn1/Asn1GeneralizedTime.cs | 422 | ||||
-rw-r--r-- | crypto/src/asn1/Asn1UtcTime.cs | 18 | ||||
-rw-r--r-- | crypto/src/asn1/DerGeneralizedTime.cs | 17 | ||||
-rw-r--r-- | crypto/src/asn1/cms/Time.cs | 5 | ||||
-rw-r--r-- | crypto/src/asn1/util/Asn1Dump.cs | 2 | ||||
-rw-r--r-- | crypto/src/asn1/x509/Time.cs | 5 | ||||
-rw-r--r-- | crypto/src/tsp/TimeStampTokenGenerator.cs | 61 | ||||
-rw-r--r-- | crypto/src/tsp/TimeStampTokenInfo.cs | 21 | ||||
-rw-r--r-- | crypto/src/util/Platform.cs | 15 | ||||
-rw-r--r-- | crypto/src/util/date/DateTimeUtilities.cs | 28 | ||||
-rw-r--r-- | crypto/test/src/asn1/test/GeneralizedTimeTest.cs | 131 | ||||
-rw-r--r-- | crypto/test/src/tsp/test/NewTspTest.cs | 10 | ||||
-rw-r--r-- | crypto/test/src/tsp/test/TimeStampTokenInfoTest.cs | 21 |
13 files changed, 238 insertions, 518 deletions
diff --git a/crypto/src/asn1/Asn1GeneralizedTime.cs b/crypto/src/asn1/Asn1GeneralizedTime.cs index 139384c1a..beabbdfa5 100644 --- a/crypto/src/asn1/Asn1GeneralizedTime.cs +++ b/crypto/src/asn1/Asn1GeneralizedTime.cs @@ -7,35 +7,7 @@ using Org.BouncyCastle.Utilities; namespace Org.BouncyCastle.Asn1 { - /** - * Base class representing the ASN.1 GeneralizedTime type. - * <p> - * The main difference between these and UTC time is a 4 digit year. - * </p><p> - * One second resolution date+time on UTC timezone (Z) - * with 4 digit year (valid from 0001 to 9999). - * </p><p> - * Timestamp format is: yyyymmddHHMMSS'Z' - * </p><p> - * <h2>X.690</h2> - * This is what is called "restricted string", - * and it uses ASCII characters to encode digits and supplemental data. - * - * <h3>11: Restrictions on BER employed by both CER and DER</h3> - * <h4>11.7 GeneralizedTime </h4> - * </p><p> - * <b>11.7.1</b> The encoding shall terminate with a "Z", - * as described in the ITU-T Rec. X.680 | ISO/IEC 8824-1 clause on - * GeneralizedTime. - * </p><p> - * <b>11.7.2</b> The seconds element shall always be present. - * </p><p> - * <b>11.7.3</b> The fractional-seconds elements, if present, - * shall omit all trailing zeros; if the elements correspond to 0, - * they shall be wholly omitted, and the decimal point element also - * shall be omitted. - * </p> - */ + /// <summary>GeneralizedTime ASN.1 type</summary> public class Asn1GeneralizedTime : Asn1Object { @@ -85,15 +57,18 @@ namespace Org.BouncyCastle.Asn1 return (Asn1GeneralizedTime)Meta.Instance.GetContextInstance(taggedObject, declaredExplicit); } - internal readonly byte[] m_contents; + private readonly string m_timeString; + private readonly bool m_timeStringCanonical; + private readonly DateTime m_dateTime; - public Asn1GeneralizedTime(string time) + public Asn1GeneralizedTime(string timeString) { - m_contents = Strings.ToByteArray(time); + m_timeString = timeString ?? throw new ArgumentNullException(nameof(timeString)); + m_timeStringCanonical = false; // TODO Dynamic check? try { - ToDateTime(); + m_dateTime = FromString(timeString); } catch (FormatException e) { @@ -101,238 +76,45 @@ namespace Org.BouncyCastle.Asn1 } } - public Asn1GeneralizedTime(DateTime time) + public Asn1GeneralizedTime(DateTime dateTime) { - DateTime utc = time.ToUniversalTime(); - var formatStr = @"yyyyMMddHHmmss\Z"; - var formatProvider = DateTimeFormatInfo.InvariantInfo; - string utcString = utc.ToString(formatStr, formatProvider); - m_contents = Strings.ToByteArray(utcString); - } - - // TODO Custom locale constructor? - //public Asn1GeneralizedTime(DateTime time, Locale locale) - //{ - // SimpleDateFormat dateF = new SimpleDateFormat("yyyyMMddHHmmss\Z", locale); - - // dateF.setTimeZone(new SimpleTimeZone(0, "Z")); - - // this.contents = Strings.toByteArray(dateF.format(time)); - //} - - internal Asn1GeneralizedTime(byte[] bytes) - { - if (bytes == null) - throw new ArgumentNullException(nameof(bytes)); - if (bytes.Length < 4) - throw new ArgumentException("GeneralizedTime string too short", nameof(bytes)); - - m_contents = bytes; - - if (!(IsDigit(0) && IsDigit(1) && IsDigit(2) && IsDigit(3))) - throw new ArgumentException("illegal characters in GeneralizedTime string", nameof(bytes)); - } - - public string TimeString => Strings.FromByteArray(m_contents); - - public string GetTime() - { - string stime = Strings.FromByteArray(m_contents); - - // - // standardise the format. - // - if (stime[stime.Length - 1] == 'Z') - return stime.Substring(0, stime.Length - 1) + "GMT+00:00"; - - int signPos = stime.Length - 6; - char sign = stime[signPos]; - if ((sign == '-' || sign == '+') && stime.IndexOf("GMT") == signPos - 3) - { - // already a GMT string! - return stime; - } - - signPos = stime.Length - 5; - sign = stime[signPos]; - if (sign == '-' || sign == '+') - { - return stime.Substring(0, signPos) - + "GMT" - + stime.Substring(signPos, 3) - + ":" - + stime.Substring(signPos + 3); - } - - signPos = stime.Length - 3; - sign = stime[signPos]; - if (sign == '-' || sign == '+') - { - return stime.Substring(0, signPos) - + "GMT" - + stime.Substring(signPos) - + ":00"; - } + dateTime = dateTime.ToUniversalTime(); - return stime + CalculateGmtOffset(stime); + m_dateTime = dateTime; + m_timeString = ToStringCanonical(dateTime); + m_timeStringCanonical = true; } - private string CalculateGmtOffset(string stime) - { - TimeZoneInfo timeZone = TimeZoneInfo.Local; - TimeSpan offset = timeZone.BaseUtcOffset; - - string sign = "+"; - if (offset.CompareTo(TimeSpan.Zero) < 0) - { - sign = "-"; - offset = offset.Duration(); - } - - int hours = offset.Hours; - int minutes = offset.Minutes; - - try - { - if (timeZone.SupportsDaylightSavingTime) - { - string d = stime + "GMT" + sign + Convert(hours) + ":" + Convert(minutes); - string formatStr = CalculateGmtFormatString(d); - - DateTime dateTime = ParseDateString(d, formatStr, makeUniversal: true); - - if (timeZone.IsDaylightSavingTime(dateTime)) - { - hours += sign.Equals("+") ? 1 : -1; - } - } - } - catch (Exception) - { - // we'll do our best and ignore daylight savings - } - - return "GMT" + sign + Convert(hours) + ":" + Convert(minutes); - } + // TODO TimeZoneInfo or other locale-specific constructors? - private string CalculateGmtFormatString(string d) + internal Asn1GeneralizedTime(byte[] contents) + : this(Encoding.ASCII.GetString(contents)) { - if (HasFractionalSeconds()) - { - int fCount = Platform.IndexOf(d, "GMT") - 1 - d.IndexOf('.'); - return @"yyyyMMddHHmmss." + FString(fCount) + @"'GMT'zzz"; - } - - if (HasSeconds()) - return @"yyyyMMddHHmmss'GMT'zzz"; - - if (HasMinutes()) - return @"yyyyMMddHHmm'GMT'zzz"; - - return @"yyyyMMddHH'GMT'zzz"; } - private string Convert(int time) - { - if (time < 10) - return "0" + time; - - return time.ToString(); - } + public string TimeString => m_timeString; public DateTime ToDateTime() { - string formatStr; - string stime = Strings.FromByteArray(m_contents); - string d = stime; - bool makeUniversal = false; - - if (Platform.EndsWith(stime, "Z")) - { - if (HasFractionalSeconds()) - { - int fCount = d.Length - d.IndexOf('.') - 2; - formatStr = @"yyyyMMddHHmmss." + FString(fCount) + @"\Z"; - } - else if (HasSeconds()) - { - formatStr = @"yyyyMMddHHmmss\Z"; - } - else if (HasMinutes()) - { - formatStr = @"yyyyMMddHHmm\Z"; - } - else - { - formatStr = @"yyyyMMddHH\Z"; - } - } - else if (stime.IndexOf('-') > 0 || stime.IndexOf('+') > 0) - { - d = GetTime(); - formatStr = CalculateGmtFormatString(d); - makeUniversal = true; - } - else - { - if (HasFractionalSeconds()) - { - int fCount = d.Length - 1 - d.IndexOf('.'); - formatStr = @"yyyyMMddHHmmss." + FString(fCount); - } - else if (HasSeconds()) - { - formatStr = @"yyyyMMddHHmmss"; - } - else if (HasMinutes()) - { - formatStr = @"yyyyMMddHHmm"; - } - else - { - formatStr = @"yyyyMMddHH"; - } - } - - // TODO Epoch adjustment? - //return DateUtil.epochAdjust(dateF.parse(d)); - return ParseDateString(d, formatStr, makeUniversal); - } - - protected bool HasFractionalSeconds() - { - return m_contents.Length > 14 && m_contents[14] == '.'; - } - - protected bool HasSeconds() - { - return IsDigit(12) && IsDigit(13); + return m_dateTime; } - protected bool HasMinutes() + internal byte[] GetContents(int encoding) { - return IsDigit(10) && IsDigit(11); - } + if (encoding == Asn1OutputStream.EncodingDer && !m_timeStringCanonical) + return Encoding.ASCII.GetBytes(ToStringCanonical(m_dateTime)); - private bool IsDigit(int pos) - { - return m_contents.Length > pos && m_contents[pos] >= '0' && m_contents[pos] <= '9'; + return Encoding.ASCII.GetBytes(m_timeString); } internal override IAsn1Encoding GetEncoding(int encoding) { - if (Asn1OutputStream.EncodingDer == encoding) - return new PrimitiveEncoding(Asn1Tags.Universal, Asn1Tags.GeneralizedTime, GetDerTime()); - - return new PrimitiveEncoding(Asn1Tags.Universal, Asn1Tags.GeneralizedTime, m_contents); + return new PrimitiveEncoding(Asn1Tags.Universal, Asn1Tags.GeneralizedTime, GetContents(encoding)); } internal override IAsn1Encoding GetEncodingImplicit(int encoding, int tagClass, int tagNo) { - if (Asn1OutputStream.EncodingDer == encoding) - return new PrimitiveEncoding(tagClass, tagNo, GetDerTime()); - - return new PrimitiveEncoding(tagClass, tagNo, m_contents); + return new PrimitiveEncoding(tagClass, tagNo, GetContents(encoding)); } protected override bool Asn1Equals(Asn1Object asn1Object) @@ -340,12 +122,17 @@ namespace Org.BouncyCastle.Asn1 if (!(asn1Object is Asn1GeneralizedTime that)) return false; - return Arrays.AreEqual(m_contents, that.m_contents); + // TODO Performance + return Arrays.AreEqual( + this.GetContents(Asn1OutputStream.EncodingDer), + that.GetContents(Asn1OutputStream.EncodingDer)); } protected override int Asn1GetHashCode() { - return Arrays.GetHashCode(m_contents); + // TODO Performance + return Arrays.GetHashCode( + this.GetContents(Asn1OutputStream.EncodingDer)); } internal static Asn1GeneralizedTime CreatePrimitive(byte[] contents) @@ -353,86 +140,123 @@ namespace Org.BouncyCastle.Asn1 return new Asn1GeneralizedTime(contents); } - internal byte[] GetDerTime() + private static DateTime FromString(string s) { - if (m_contents[m_contents.Length - 1] != 'Z') - { - return m_contents; // TODO: is there a better way? - } + if (s.Length < 10) + throw new FormatException(); - if (!HasMinutes()) - { - byte[] derTime = new byte[m_contents.Length + 4]; + s = s.Replace(',', '.'); - Array.Copy(m_contents, 0, derTime, 0, m_contents.Length - 1); - Array.Copy(Strings.ToByteArray("0000Z"), 0, derTime, m_contents.Length - 1, 5); - - return derTime; - } - else if (!HasSeconds()) + if (Platform.EndsWith(s, "Z")) { - byte[] derTime = new byte[m_contents.Length + 2]; + switch (s.Length) + { + case 11: return ParseUtc(s, @"yyyyMMddHH\Z"); + case 13: return ParseUtc(s, @"yyyyMMddHHmm\Z"); + case 15: return ParseUtc(s, @"yyyyMMddHHmmss\Z"); + case 17: return ParseUtc(s, @"yyyyMMddHHmmss.f\Z"); + case 18: return ParseUtc(s, @"yyyyMMddHHmmss.ff\Z"); + case 19: return ParseUtc(s, @"yyyyMMddHHmmss.fff\Z"); + case 20: return ParseUtc(s, @"yyyyMMddHHmmss.ffff\Z"); + case 21: return ParseUtc(s, @"yyyyMMddHHmmss.fffff\Z"); + case 22: return ParseUtc(s, @"yyyyMMddHHmmss.ffffff\Z"); + case 23: return ParseUtc(s, @"yyyyMMddHHmmss.fffffff\Z"); + default: + throw new FormatException(); + } + } - Array.Copy(m_contents, 0, derTime, 0, m_contents.Length - 1); - Array.Copy(Strings.ToByteArray("00Z"), 0, derTime, m_contents.Length - 1, 3); + int signIndex = IndexOfSign(s, System.Math.Max(10, s.Length - 5)); - return derTime; - } - else if (HasFractionalSeconds()) + if (signIndex < 0) { - int ind = m_contents.Length - 2; - while (ind > 0 && m_contents[ind] == '0') + switch (s.Length) { - ind--; + case 10: return ParseLocal(s, @"yyyyMMddHH"); + case 12: return ParseLocal(s, @"yyyyMMddHHmm"); + case 14: return ParseLocal(s, @"yyyyMMddHHmmss"); + case 16: return ParseLocal(s, @"yyyyMMddHHmmss.f"); + case 17: return ParseLocal(s, @"yyyyMMddHHmmss.ff"); + case 18: return ParseLocal(s, @"yyyyMMddHHmmss.fff"); + case 19: return ParseLocal(s, @"yyyyMMddHHmmss.ffff"); + case 20: return ParseLocal(s, @"yyyyMMddHHmmss.fffff"); + case 21: return ParseLocal(s, @"yyyyMMddHHmmss.ffffff"); + case 22: return ParseLocal(s, @"yyyyMMddHHmmss.fffffff"); + default: + throw new FormatException(); } + } - if (m_contents[ind] == '.') - { - byte[] derTime = new byte[ind + 1]; - - Array.Copy(m_contents, 0, derTime, 0, ind); - derTime[ind] = (byte)'Z'; - - return derTime; - } - else + if (signIndex == s.Length - 5) + { + switch (s.Length) { - byte[] derTime = new byte[ind + 2]; - - Array.Copy(m_contents, 0, derTime, 0, ind + 1); - derTime[ind + 1] = (byte)'Z'; - - return derTime; + case 15: return ParseTimeZone(s, @"yyyyMMddHHzzz"); + case 17: return ParseTimeZone(s, @"yyyyMMddHHmmzzz"); + case 19: return ParseTimeZone(s, @"yyyyMMddHHmmsszzz"); + case 21: return ParseTimeZone(s, @"yyyyMMddHHmmss.fzzz"); + case 22: return ParseTimeZone(s, @"yyyyMMddHHmmss.ffzzz"); + case 23: return ParseTimeZone(s, @"yyyyMMddHHmmss.fffzzz"); + case 24: return ParseTimeZone(s, @"yyyyMMddHHmmss.ffffzzz"); + case 25: return ParseTimeZone(s, @"yyyyMMddHHmmss.fffffzzz"); + case 26: return ParseTimeZone(s, @"yyyyMMddHHmmss.ffffffzzz"); + case 27: return ParseTimeZone(s, @"yyyyMMddHHmmss.fffffffzzz"); + default: + throw new FormatException(); } } - else + + if (signIndex == s.Length - 3) { - return m_contents; + switch (s.Length) + { + case 13: return ParseTimeZone(s, @"yyyyMMddHHzz"); + case 15: return ParseTimeZone(s, @"yyyyMMddHHmmzz"); + case 17: return ParseTimeZone(s, @"yyyyMMddHHmmsszz"); + case 19: return ParseTimeZone(s, @"yyyyMMddHHmmss.fzz"); + case 20: return ParseTimeZone(s, @"yyyyMMddHHmmss.ffzz"); + case 21: return ParseTimeZone(s, @"yyyyMMddHHmmss.fffzz"); + case 22: return ParseTimeZone(s, @"yyyyMMddHHmmss.ffffzz"); + case 23: return ParseTimeZone(s, @"yyyyMMddHHmmss.fffffzz"); + case 24: return ParseTimeZone(s, @"yyyyMMddHHmmss.ffffffzz"); + case 25: return ParseTimeZone(s, @"yyyyMMddHHmmss.fffffffzz"); + default: + throw new FormatException(); + } } + + throw new FormatException(); } - private static string FString(int count) + private static int IndexOfSign(string s, int startIndex) { - StringBuilder sb = new StringBuilder(); - for (int i = 0; i < count; ++i) + int index = Platform.IndexOf(s, '+', startIndex); + if (index < 0) { - sb.Append('f'); + index = Platform.IndexOf(s, '-', startIndex); } - return sb.ToString(); + return index; } - private static DateTime ParseDateString(string s, string format, bool makeUniversal) + private static DateTime ParseLocal(string s, string format) { - DateTimeStyles dateTimeStyles = DateTimeStyles.None; - if (Platform.EndsWith(format, "Z")) - { - dateTimeStyles |= DateTimeStyles.AdjustToUniversal; - dateTimeStyles |= DateTimeStyles.AssumeUniversal; - } + return DateTime.ParseExact(s, format, DateTimeFormatInfo.InvariantInfo, DateTimeStyles.AssumeLocal); + } - DateTime dt = DateTime.ParseExact(s, format, DateTimeFormatInfo.InvariantInfo, dateTimeStyles); + private static DateTime ParseTimeZone(string s, string format) + { + return DateTime.ParseExact(s, format, DateTimeFormatInfo.InvariantInfo, DateTimeStyles.AdjustToUniversal); + } + + private static DateTime ParseUtc(string s, string format) + { + return DateTime.ParseExact(s, format, DateTimeFormatInfo.InvariantInfo, + DateTimeStyles.AdjustToUniversal | DateTimeStyles.AssumeUniversal); + } - return makeUniversal ? dt.ToUniversalTime() : dt; + private static string ToStringCanonical(DateTime dateTime) + { + return dateTime.ToUniversalTime().ToString(@"yyyyMMddHHmmss.FFFFFFFK", DateTimeFormatInfo.InvariantInfo); } } } diff --git a/crypto/src/asn1/Asn1UtcTime.cs b/crypto/src/asn1/Asn1UtcTime.cs index 478b5c485..bd06a3258 100644 --- a/crypto/src/asn1/Asn1UtcTime.cs +++ b/crypto/src/asn1/Asn1UtcTime.cs @@ -4,6 +4,7 @@ using System.IO; using System.Text; using Org.BouncyCastle.Utilities; +using Org.BouncyCastle.Utilities.Date; namespace Org.BouncyCastle.Asn1 { @@ -69,10 +70,7 @@ namespace Org.BouncyCastle.Asn1 public Asn1UtcTime(string timeString) { - if (timeString == null) - throw new ArgumentNullException(nameof(timeString)); - - m_timeString = timeString; + m_timeString = timeString ?? throw new ArgumentNullException(nameof(timeString)); try { @@ -88,8 +86,7 @@ namespace Org.BouncyCastle.Asn1 [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); + dateTime = DateTimeUtilities.WithPrecisionSecond(dateTime.ToUniversalTime()); m_dateTime = dateTime; m_dateTimeLocked = true; @@ -98,8 +95,7 @@ namespace Org.BouncyCastle.Asn1 public Asn1UtcTime(DateTime dateTime, int twoDigitYearMax) { - DateTime utc = dateTime.ToUniversalTime(); - dateTime = new DateTime(utc.Year, utc.Month, utc.Day, utc.Hour, utc.Minute, utc.Second, DateTimeKind.Utc); + dateTime = DateTimeUtilities.WithPrecisionSecond(dateTime.ToUniversalTime()); Validate(dateTime, twoDigitYearMax); @@ -220,11 +216,9 @@ namespace Org.BouncyCastle.Asn1 return DateTime.ParseExact(s, @"yyMMddHHmmss\Z", provider, DateTimeStyles.AdjustToUniversal | DateTimeStyles.AssumeUniversal); case 15: - return DateTime.ParseExact(s, @"yyMMddHHmmzzz", provider, - DateTimeStyles.AdjustToUniversal); + return DateTime.ParseExact(s, @"yyMMddHHmmzzz", provider, DateTimeStyles.AdjustToUniversal); case 17: - return DateTime.ParseExact(s, @"yyMMddHHmmsszzz", provider, - DateTimeStyles.AdjustToUniversal); + return DateTime.ParseExact(s, @"yyMMddHHmmsszzz", provider, DateTimeStyles.AdjustToUniversal); default: throw new FormatException(); } diff --git a/crypto/src/asn1/DerGeneralizedTime.cs b/crypto/src/asn1/DerGeneralizedTime.cs index 0386ecb02..3d814de30 100644 --- a/crypto/src/asn1/DerGeneralizedTime.cs +++ b/crypto/src/asn1/DerGeneralizedTime.cs @@ -5,29 +5,30 @@ namespace Org.BouncyCastle.Asn1 public class DerGeneralizedTime : Asn1GeneralizedTime { - public DerGeneralizedTime(byte[] time) - : base(time) + public DerGeneralizedTime(string timeString) + : base(timeString) { } - public DerGeneralizedTime(DateTime time) - : base(time) + public DerGeneralizedTime(DateTime dateTime) + : base(dateTime) { } - public DerGeneralizedTime(string time) - : base(time) + internal DerGeneralizedTime(byte[] contents) + : base(contents) { } internal override IAsn1Encoding GetEncoding(int encoding) { - return new PrimitiveEncoding(Asn1Tags.Universal, Asn1Tags.GeneralizedTime, GetDerTime()); + return new PrimitiveEncoding(Asn1Tags.Universal, Asn1Tags.GeneralizedTime, + GetContents(Asn1OutputStream.EncodingDer)); } internal override IAsn1Encoding GetEncodingImplicit(int encoding, int tagClass, int tagNo) { - return new PrimitiveEncoding(tagClass, tagNo, GetDerTime()); + return new PrimitiveEncoding(tagClass, tagNo, GetContents(Asn1OutputStream.EncodingDer)); } } } diff --git a/crypto/src/asn1/cms/Time.cs b/crypto/src/asn1/cms/Time.cs index f054e3809..7b6b49c30 100644 --- a/crypto/src/asn1/cms/Time.cs +++ b/crypto/src/asn1/cms/Time.cs @@ -101,7 +101,10 @@ namespace Org.BouncyCastle.Asn1.Cms if (m_timeObject is Asn1UtcTime utcTime) return utcTime.ToDateTime(2049).ToString(@"yyyyMMddHHmmssK", DateTimeFormatInfo.InvariantInfo); - return ((Asn1GeneralizedTime)m_timeObject).GetTime(); + if (m_timeObject is Asn1GeneralizedTime generalizedTime) + return generalizedTime.ToDateTime().ToString(@"yyyyMMddHHmmss.FFFFFFFK", DateTimeFormatInfo.InvariantInfo); + + throw new InvalidOperationException(); } } } diff --git a/crypto/src/asn1/util/Asn1Dump.cs b/crypto/src/asn1/util/Asn1Dump.cs index 6c005f512..3a19f1276 100644 --- a/crypto/src/asn1/util/Asn1Dump.cs +++ b/crypto/src/asn1/util/Asn1Dump.cs @@ -214,7 +214,7 @@ namespace Org.BouncyCastle.Asn1.Utilities else if (obj is Asn1GeneralizedTime generalizedTime) { buf.Append(indent); - buf.AppendLine("GeneralizedTime(" + generalizedTime.GetTime() + ")"); + buf.AppendLine("GeneralizedTime(" + generalizedTime.TimeString + ")"); } else if (obj is DerEnumerated en) { diff --git a/crypto/src/asn1/x509/Time.cs b/crypto/src/asn1/x509/Time.cs index 7f2d43315..e03055f6d 100644 --- a/crypto/src/asn1/x509/Time.cs +++ b/crypto/src/asn1/x509/Time.cs @@ -102,7 +102,10 @@ namespace Org.BouncyCastle.Asn1.X509 if (m_timeObject is Asn1UtcTime utcTime) return utcTime.ToDateTime(2049).ToString(@"yyyyMMddHHmmssK", DateTimeFormatInfo.InvariantInfo); - return ((Asn1GeneralizedTime)m_timeObject).GetTime(); + if (m_timeObject is Asn1GeneralizedTime generalizedTime) + return generalizedTime.ToDateTime().ToString(@"yyyyMMddHHmmss.FFFFFFFK", DateTimeFormatInfo.InvariantInfo); + + throw new InvalidOperationException(); } } } diff --git a/crypto/src/tsp/TimeStampTokenGenerator.cs b/crypto/src/tsp/TimeStampTokenGenerator.cs index 9e6a21f9c..930463ca3 100644 --- a/crypto/src/tsp/TimeStampTokenGenerator.cs +++ b/crypto/src/tsp/TimeStampTokenGenerator.cs @@ -16,6 +16,7 @@ using Org.BouncyCastle.Crypto.Operators; using Org.BouncyCastle.Math; using Org.BouncyCastle.Utilities; using Org.BouncyCastle.Utilities.Collections; +using Org.BouncyCastle.Utilities.Date; using Org.BouncyCastle.X509; namespace Org.BouncyCastle.Tsp @@ -334,15 +335,7 @@ namespace Org.BouncyCastle.Tsp respExtensions = extGen.Generate(); } - Asn1GeneralizedTime timeStampTime; - if (resolution == Resolution.R_SECONDS) - { - timeStampTime = new Asn1GeneralizedTime(genTime); - } - else - { - timeStampTime = CreateGeneralizedTime(genTime); - } + var timeStampTime = new Asn1GeneralizedTime(WithResolution(genTime, resolution)); TstInfo tstInfo = new TstInfo(tsaPolicy, messageImprint, new DerInteger(serialNumber), timeStampTime, accuracy, @@ -385,53 +378,21 @@ namespace Org.BouncyCastle.Tsp //} } - private Asn1GeneralizedTime CreateGeneralizedTime(DateTime genTime) + private static DateTime WithResolution(DateTime dateTime, Resolution resolution) { - string format = "yyyyMMddHHmmss.fff"; - - StringBuilder sBuild = new StringBuilder(genTime.ToString(format)); - int dotIndex = sBuild.ToString().IndexOf("."); - - if (dotIndex <0) - { - sBuild.Append("Z"); - return new Asn1GeneralizedTime(sBuild.ToString()); - } - - switch(resolution) + switch (resolution) { + case Resolution.R_SECONDS: + return DateTimeUtilities.WithPrecisionSecond(dateTime); case Resolution.R_TENTHS_OF_SECONDS: - if (sBuild.Length > dotIndex + 2) - { - sBuild.Remove(dotIndex + 2, sBuild.Length-(dotIndex+2)); - } - break; + return DateTimeUtilities.WithPrecisionDecisecond(dateTime); case Resolution.R_HUNDREDTHS_OF_SECONDS: - if (sBuild.Length > dotIndex + 3) - { - sBuild.Remove(dotIndex + 3, sBuild.Length-(dotIndex+3)); - } - break; - - - case Resolution.R_SECONDS: + return DateTimeUtilities.WithPrecisionCentisecond(dateTime); case Resolution.R_MILLISECONDS: - // do nothing. - break; - } - - while (sBuild[sBuild.Length - 1] == '0') - { - sBuild.Remove(sBuild.Length - 1,1); - } - - if (sBuild.Length - 1 == dotIndex) - { - sBuild.Remove(sBuild.Length - 1, 1); + return DateTimeUtilities.WithPrecisionMillisecond(dateTime); + default: + throw new InvalidOperationException(); } - - sBuild.Append("Z"); - return new Asn1GeneralizedTime(sBuild.ToString()); } private class TableGen diff --git a/crypto/src/tsp/TimeStampTokenInfo.cs b/crypto/src/tsp/TimeStampTokenInfo.cs index cdef826bc..b9f0e3195 100644 --- a/crypto/src/tsp/TimeStampTokenInfo.cs +++ b/crypto/src/tsp/TimeStampTokenInfo.cs @@ -8,10 +8,27 @@ namespace Org.BouncyCastle.Tsp { public class TimeStampTokenInfo { - private TstInfo tstInfo; + private static TstInfo ParseTstInfo(byte[] tstInfoEncoding) + { + try + { + return TstInfo.GetInstance(tstInfoEncoding); + } + catch (Exception e) + { + throw new TspException("unable to parse TstInfo encoding: " + e.Message); + } + } + + private TstInfo tstInfo; private DateTime genTime; - public TimeStampTokenInfo( + public TimeStampTokenInfo(byte[] tstInfoEncoding) + : this(ParseTstInfo(tstInfoEncoding)) + { + } + + public TimeStampTokenInfo( TstInfo tstInfo) { this.tstInfo = tstInfo; diff --git a/crypto/src/util/Platform.cs b/crypto/src/util/Platform.cs index 118c29918..78ff6cee4 100644 --- a/crypto/src/util/Platform.cs +++ b/crypto/src/util/Platform.cs @@ -31,11 +31,26 @@ namespace Org.BouncyCastle.Utilities d.Dispose(); } + internal static int IndexOf(string source, char value) + { + return InvariantCompareInfo.IndexOf(source, value, CompareOptions.Ordinal); + } + internal static int IndexOf(string source, string value) { return InvariantCompareInfo.IndexOf(source, value, CompareOptions.Ordinal); } + internal static int IndexOf(string source, char value, int startIndex) + { + return InvariantCompareInfo.IndexOf(source, value, startIndex, CompareOptions.Ordinal); + } + + internal static int IndexOf(string source, string value, int startIndex) + { + return InvariantCompareInfo.IndexOf(source, value, startIndex, CompareOptions.Ordinal); + } + internal static int LastIndexOf(string source, string value) { return InvariantCompareInfo.LastIndexOf(source, value, CompareOptions.Ordinal); diff --git a/crypto/src/util/date/DateTimeUtilities.cs b/crypto/src/util/date/DateTimeUtilities.cs index 3660e29c2..c46cde8db 100644 --- a/crypto/src/util/date/DateTimeUtilities.cs +++ b/crypto/src/util/date/DateTimeUtilities.cs @@ -53,5 +53,31 @@ namespace Org.BouncyCastle.Utilities.Date { return DateTimeToUnixMs(DateTime.UtcNow); } - } + + public static DateTime WithPrecisionCentisecond(DateTime dateTime) + { + int millisecond = dateTime.Millisecond - (dateTime.Millisecond % 10); + return new DateTime(dateTime.Year, dateTime.Month, dateTime.Day, + dateTime.Hour, dateTime.Minute, dateTime.Second, millisecond, dateTime.Kind); + } + + public static DateTime WithPrecisionDecisecond(DateTime dateTime) + { + int millisecond = dateTime.Millisecond - (dateTime.Millisecond % 100); + return new DateTime(dateTime.Year, dateTime.Month, dateTime.Day, + dateTime.Hour, dateTime.Minute, dateTime.Second, millisecond, dateTime.Kind); + } + + public static DateTime WithPrecisionMillisecond(DateTime dateTime) + { + return new DateTime(dateTime.Year, dateTime.Month, dateTime.Day, + dateTime.Hour, dateTime.Minute, dateTime.Second, dateTime.Millisecond, dateTime.Kind); + } + + public static DateTime WithPrecisionSecond(DateTime dateTime) + { + return new DateTime(dateTime.Year, dateTime.Month, dateTime.Day, + dateTime.Hour, dateTime.Minute, dateTime.Second, dateTime.Kind); + } + } } diff --git a/crypto/test/src/asn1/test/GeneralizedTimeTest.cs b/crypto/test/src/asn1/test/GeneralizedTimeTest.cs index 1cc1d8433..266ca2753 100644 --- a/crypto/test/src/asn1/test/GeneralizedTimeTest.cs +++ b/crypto/test/src/asn1/test/GeneralizedTimeTest.cs @@ -40,56 +40,6 @@ namespace Org.BouncyCastle.Asn1.Tests "20020122122220.0001+1000" }; - private static readonly string[] output = - { - "20020122122220", - "20020122122220GMT+00:00", - "20020122122220GMT-10:00", - "20020122122220GMT+00:00", - "20020122122220.1", - "20020122122220.1GMT+00:00", - "20020122122220.1GMT-10:00", - "20020122122220.1GMT+00:00", - "20020122122220.01", - "20020122122220.01GMT+00:00", - "20020122122220.01GMT-10:00", - "20020122122220.01GMT+00:00", - "20020122122220.001", - "20020122122220.001GMT+00:00", - "20020122122220.001GMT-10:00", - "20020122122220.001GMT+00:00", - "20020122122220.0001", - "20020122122220.0001GMT+00:00", - "20020122122220.0001GMT-10:00", - "20020122122220.0001GMT+00:00", - "20020122122220.0001GMT+10:00" - }; - - private static readonly string[] zOutput = - { - "20020122122220Z", - "20020122122220Z", - "20020122222220Z", - "20020122122220Z", - "20020122122220Z", - "20020122122220Z", - "20020122222220Z", - "20020122122220Z", - "20020122122220Z", - "20020122122220Z", - "20020122222220Z", - "20020122122220Z", - "20020122122220Z", - "20020122122220Z", - "20020122222220Z", - "20020122122220Z", - "20020122122220Z", - "20020122122220Z", - "20020122222220Z", - "20020122122220Z", - "20020122022220Z" - }; - private static readonly string[] mzOutput = { "20020122122220.000Z", @@ -161,38 +111,6 @@ namespace Org.BouncyCastle.Asn1.Tests { for (int i = 0; i != input.Length; i++) { - string ii = input[i], oi = output[i]; - - Asn1GeneralizedTime t = new Asn1GeneralizedTime(ii); - DateTime dt = t.ToDateTime(); - string st = t.GetTime(); - - if (oi.IndexOf('G') > 0) // don't check local time the same way - { - if (!st.Equals(oi)) - { - Fail("failed GMT conversion test " + i); - } - - string dts = dt.ToString(@"yyyyMMddHHmmss\Z"); - string zi = zOutput[i]; - if (!dts.Equals(zi)) - { - Fail("failed date conversion test " + i); - } - } - else - { - string offset = CalculateGmtOffset(dt); - if (!st.Equals(oi + offset)) - { - Fail("failed conversion test " + i); - } - } - } - - for (int i = 0; i != input.Length; i++) - { Asn1GeneralizedTime t = new Asn1GeneralizedTime(input[i]); if (!t.ToDateTime().ToString(@"yyyyMMddHHmmss.fff\Z").Equals(mzOutput[i])) @@ -209,7 +127,7 @@ namespace Org.BouncyCastle.Asn1.Tests if (!AreEqual(t.GetEncoded(), new Asn1GeneralizedTime(derMzOutput[i]).GetEncoded())) { - Fail("der encoding wrong"); + Fail("DER encoding wrong"); } } @@ -219,7 +137,7 @@ namespace Org.BouncyCastle.Asn1.Tests if (!AreEqual(t.GetEncoded(), new Asn1GeneralizedTime(derTruncOutput[i]).GetEncoded())) { - Fail("trunc der encoding wrong"); + Fail("trunc DER encoding wrong"); } } @@ -239,20 +157,14 @@ namespace Org.BouncyCastle.Asn1.Tests IsTrue(Arrays.AreEqual(Hex.Decode("180f32303232303830393132313530305a"), der.GetEncoded(Asn1Encodable.Der))); } - { - // check an actual GMT string comes back untampered - Asn1GeneralizedTime time = new Asn1GeneralizedTime("20190704031318GMT+00:00"); - - IsTrue("20190704031318GMT+00:00".Equals(time.GetTime())); - } - try { - new DerGeneralizedTime(new byte[0]); + new DerGeneralizedTime(string.Empty); + Fail("Expected exception"); } catch (ArgumentException e) { - IsTrue(e.Message.StartsWith("GeneralizedTime string too short")); + IsTrue(e.Message.StartsWith("invalid date string")); } /* @@ -277,39 +189,6 @@ namespace Org.BouncyCastle.Asn1.Tests } } - private string CalculateGmtOffset(DateTime date) - { - TimeZoneInfo timeZone = TimeZoneInfo.Local; - TimeSpan offset = timeZone.BaseUtcOffset; - - char sign = '+'; - if (offset.CompareTo(TimeSpan.Zero) < 0) - { - sign = '-'; - offset = offset.Duration(); - } - - int hours = offset.Hours; - int minutes = offset.Minutes; - - if (timeZone.SupportsDaylightSavingTime && timeZone.IsDaylightSavingTime(date)) - { - hours += sign.Equals("+") ? 1 : -1; - } - - return "GMT" + sign + Convert(hours) + ":" + Convert(minutes); - } - - private string Convert(int time) - { - if (time < 10) - { - return "0" + time; - } - - return time.ToString(); - } - [Test] public void TestFunction() { diff --git a/crypto/test/src/tsp/test/NewTspTest.cs b/crypto/test/src/tsp/test/NewTspTest.cs index c98e5d191..69bec7958 100644 --- a/crypto/test/src/tsp/test/NewTspTest.cs +++ b/crypto/test/src/tsp/test/NewTspTest.cs @@ -787,12 +787,12 @@ namespace Org.BouncyCastle.Tsp.Tests } private void resolutionTest(AsymmetricKeyParameter privateKey, X509Certificate cert, - IStore<X509Certificate> certs, Resolution resoution, string timeString) + IStore<X509Certificate> certs, Resolution resolution, string timeString) { TimeStampTokenGenerator tsTokenGen = new TimeStampTokenGenerator( - privateKey, cert, TspAlgorithms.Sha1, "1.2"); + privateKey, cert, TspAlgorithms.Sha1, "1.2"); - tsTokenGen.Resolution = resoution; + tsTokenGen.Resolution = resolution; tsTokenGen.SetCertificates(certs); TimeStampRequestGenerator reqGen = new TimeStampRequestGenerator(); @@ -813,14 +813,14 @@ namespace Org.BouncyCastle.Tsp.Tests tsToken = tsResp.TimeStampToken; Assert.AreEqual("19700101000009Z", tsToken.TimeStampInfo.TstInfo.GenTime.TimeString); - if ((int)resoution > (int)Resolution.R_HUNDREDTHS_OF_SECONDS) + if ((int)resolution > (int)Resolution.R_HUNDREDTHS_OF_SECONDS) { tsResp = tsRespGen.Generate(request, new BigInteger("23"), UnixEpoch.AddMilliseconds(9990)); tsToken = tsResp.TimeStampToken; Assert.AreEqual("19700101000009.99Z", tsToken.TimeStampInfo.TstInfo.GenTime.TimeString); } - if ((int)resoution > (int)Resolution.R_TENTHS_OF_SECONDS) + if ((int)resolution > (int)Resolution.R_TENTHS_OF_SECONDS) { tsResp = tsRespGen.Generate(request, new BigInteger("23"), UnixEpoch.AddMilliseconds(9900)); tsToken = tsResp.TimeStampToken; diff --git a/crypto/test/src/tsp/test/TimeStampTokenInfoTest.cs b/crypto/test/src/tsp/test/TimeStampTokenInfoTest.cs index a1a7355e5..03f611beb 100644 --- a/crypto/test/src/tsp/test/TimeStampTokenInfoTest.cs +++ b/crypto/test/src/tsp/test/TimeStampTokenInfoTest.cs @@ -1,7 +1,5 @@ using NUnit.Framework; -using Org.BouncyCastle.Asn1; -using Org.BouncyCastle.Asn1.Tsp; using Org.BouncyCastle.Math; using Org.BouncyCastle.Utilities; using Org.BouncyCastle.Utilities.Date; @@ -31,7 +29,7 @@ namespace Org.BouncyCastle.Tsp.Tests [Test] public void TestTstInfo1() { - TimeStampTokenInfo tstInfo = getTimeStampTokenInfo(tstInfo1); + TimeStampTokenInfo tstInfo = CreateTimeStampTokenInfo(tstInfo1); // // verify @@ -60,7 +58,7 @@ namespace Org.BouncyCastle.Tsp.Tests [Test] public void TestTstInfo2() { - TimeStampTokenInfo tstInfo = getTimeStampTokenInfo(tstInfo2); + TimeStampTokenInfo tstInfo = CreateTimeStampTokenInfo(tstInfo2); // // verify @@ -81,7 +79,9 @@ namespace Org.BouncyCastle.Tsp.Tests Assert.AreEqual(tstInfo.Nonce, BigInteger.ValueOf(100)); - Assert.IsTrue(Arrays.AreEqual(Hex.Decode("ffffffffffffffffffffffffffffffffffffffff"), tstInfo.GetMessageImprintDigest())); + Assert.IsTrue(Arrays.AreEqual( + Hex.Decode("ffffffffffffffffffffffffffffffffffffffff"), + tstInfo.GetMessageImprintDigest())); Assert.IsTrue(Arrays.AreEqual(tstInfo2, tstInfo.GetEncoded())); } @@ -89,7 +89,7 @@ namespace Org.BouncyCastle.Tsp.Tests [Test] public void TestTstInfo3() { - TimeStampTokenInfo tstInfo = getTimeStampTokenInfo(tstInfo3); + TimeStampTokenInfo tstInfo = CreateTimeStampTokenInfo(tstInfo3); // // verify @@ -122,7 +122,7 @@ namespace Org.BouncyCastle.Tsp.Tests { try { - getTimeStampTokenInfo(tstInfoDudDate); + CreateTimeStampTokenInfo(tstInfoDudDate); Assert.Fail("dud date not detected."); } @@ -132,12 +132,9 @@ namespace Org.BouncyCastle.Tsp.Tests } } - private TimeStampTokenInfo getTimeStampTokenInfo( - byte[] tstInfo) + private TimeStampTokenInfo CreateTimeStampTokenInfo(byte[] tstInfo) { - return new TimeStampTokenInfo( - TstInfo.GetInstance( - Asn1Object.FromByteArray(tstInfo))); + return new TimeStampTokenInfo(tstInfo); } } } |