From 11b4d6a7afeb6f3be906a1a0ddf132df3f255b4a Mon Sep 17 00:00:00 2001
From: Peter Dettman
+ * The main difference between these and UTC time is a 4 digit year.
+ *
+ * One second resolution date+time on UTC timezone (Z)
+ * with 4 digit year (valid from 0001 to 9999).
+ *
+ * Timestamp format is: yyyymmddHHMMSS'Z'
+ *
+ *
+ * 11.7.1 The encoding shall terminate with a "Z",
+ * as described in the ITU-T Rec. X.680 | ISO/IEC 8824-1 clause on
+ * GeneralizedTime.
+ *
+ * 11.7.2 The seconds element shall always be present.
+ *
+ * 11.7.3 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.
+ */
+ public class Asn1GeneralizedTime
+ : Asn1Object
+ {
+ internal class Meta : Asn1UniversalType
+ {
+ internal static readonly Asn1UniversalType Instance = new Meta();
+
+ private Meta() : base(typeof(Asn1GeneralizedTime), Asn1Tags.GeneralizedTime) { }
+
+ internal override Asn1Object FromImplicitPrimitive(DerOctetString octetString)
+ {
+ return CreatePrimitive(octetString.GetOctets());
+ }
+ }
+
+ public static Asn1GeneralizedTime GetInstance(object obj)
+ {
+ if (obj == null)
+ return null;
+
+ if (obj is Asn1GeneralizedTime asn1GeneralizedTime)
+ return asn1GeneralizedTime;
+
+ if (obj is IAsn1Convertible asn1Convertible)
+ {
+ Asn1Object asn1Object = asn1Convertible.ToAsn1Object();
+ if (asn1Object is Asn1GeneralizedTime converted)
+ return converted;
+ }
+ else if (obj is byte[] bytes)
+ {
+ try
+ {
+ return (Asn1GeneralizedTime)Meta.Instance.FromByteArray(bytes);
+ }
+ catch (IOException e)
+ {
+ throw new ArgumentException("failed to construct generalized time from byte[]: " + e.Message);
+ }
+ }
+
+ throw new ArgumentException("illegal object in GetInstance: " + Platform.GetTypeName(obj), nameof(obj));
+ }
+
+ public static Asn1GeneralizedTime GetInstance(Asn1TaggedObject taggedObject, bool declaredExplicit)
+ {
+ return (Asn1GeneralizedTime)Meta.Instance.GetContextInstance(taggedObject, declaredExplicit);
+ }
+
+ internal readonly byte[] m_contents;
+
+ public Asn1GeneralizedTime(string time)
+ {
+ m_contents = Strings.ToByteArray(time);
+
+ try
+ {
+ ToDateTime();
+ }
+ catch (FormatException e)
+ {
+ throw new ArgumentException("invalid date string: " + e.Message);
+ }
+ }
+
+ public Asn1GeneralizedTime(DateTime time)
+ {
+ 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";
+ }
+
+ return stime + CalculateGmtOffset(stime);
+ }
+
+ 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);
+ }
+
+ private string CalculateGmtFormatString(string d)
+ {
+ 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 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);
+ }
+
+ protected bool HasMinutes()
+ {
+ return IsDigit(10) && IsDigit(11);
+ }
+
+ private bool IsDigit(int pos)
+ {
+ return m_contents.Length > pos && m_contents[pos] >= '0' && m_contents[pos] <= '9';
+ }
+
+ 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);
+ }
+
+ 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);
+ }
+
+ protected override bool Asn1Equals(Asn1Object asn1Object)
+ {
+ if (!(asn1Object is Asn1GeneralizedTime that))
+ return false;
+
+ return Arrays.AreEqual(m_contents, that.m_contents);
+ }
+
+ protected override int Asn1GetHashCode()
+ {
+ return Arrays.GetHashCode(m_contents);
+ }
+
+ internal static Asn1GeneralizedTime CreatePrimitive(byte[] contents)
+ {
+ return new Asn1GeneralizedTime(contents);
+ }
+
+ internal byte[] GetDerTime()
+ {
+ if (m_contents[m_contents.Length - 1] != 'Z')
+ {
+ return m_contents; // TODO: is there a better way?
+ }
+
+ if (!HasMinutes())
+ {
+ byte[] derTime = new byte[m_contents.Length + 4];
+
+ 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())
+ {
+ byte[] derTime = new byte[m_contents.Length + 2];
+
+ Array.Copy(m_contents, 0, derTime, 0, m_contents.Length - 1);
+ Array.Copy(Strings.ToByteArray("00Z"), 0, derTime, m_contents.Length - 1, 3);
+
+ return derTime;
+ }
+ else if (HasFractionalSeconds())
+ {
+ int ind = m_contents.Length - 2;
+ while (ind > 0 && m_contents[ind] == '0')
+ {
+ ind--;
+ }
+
+ 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
+ {
+ byte[] derTime = new byte[ind + 2];
+
+ Array.Copy(m_contents, 0, derTime, 0, ind + 1);
+ derTime[ind + 1] = (byte)'Z';
+
+ return derTime;
+ }
+ }
+ else
+ {
+ return m_contents;
+ }
+ }
+
+ private static string FString(int count)
+ {
+ StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < count; ++i)
+ {
+ sb.Append('f');
+ }
+ return sb.ToString();
+ }
+
+ private static DateTime ParseDateString(string s, string format, bool makeUniversal)
+ {
+ DateTimeStyles dateTimeStyles = DateTimeStyles.None;
+ if (Platform.EndsWith(format, "Z"))
+ {
+ dateTimeStyles |= DateTimeStyles.AdjustToUniversal;
+ dateTimeStyles |= DateTimeStyles.AssumeUniversal;
+ }
+
+ DateTime dt = DateTime.ParseExact(s, format, DateTimeFormatInfo.InvariantInfo, dateTimeStyles);
+
+ return makeUniversal ? dt.ToUniversalTime() : dt;
+ }
+ }
+}
diff --git a/crypto/src/asn1/Asn1InputStream.cs b/crypto/src/asn1/Asn1InputStream.cs
index aa91cdf62..e7caee9bd 100644
--- a/crypto/src/asn1/Asn1InputStream.cs
+++ b/crypto/src/asn1/Asn1InputStream.cs
@@ -426,7 +426,7 @@ namespace Org.BouncyCastle.Asn1
case Asn1Tags.BitString:
return DerBitString.CreatePrimitive(bytes);
case Asn1Tags.GeneralizedTime:
- return DerGeneralizedTime.CreatePrimitive(bytes);
+ return Asn1GeneralizedTime.CreatePrimitive(bytes);
case Asn1Tags.GeneralString:
return DerGeneralString.CreatePrimitive(bytes);
case Asn1Tags.GraphicString:
diff --git a/crypto/src/asn1/Asn1UniversalTypes.cs b/crypto/src/asn1/Asn1UniversalTypes.cs
index 214918bcd..d188988f6 100644
--- a/crypto/src/asn1/Asn1UniversalTypes.cs
+++ b/crypto/src/asn1/Asn1UniversalTypes.cs
@@ -51,7 +51,7 @@ namespace Org.BouncyCastle.Asn1
case Asn1Tags.UtcTime: // [UNIVERSAL 23] IMPLICIT VisibleString (restricted values)
return DerUtcTime.Meta.Instance;
case Asn1Tags.GeneralizedTime: // [UNIVERSAL 24] IMPLICIT VisibleString (restricted values)
- return DerGeneralizedTime.Meta.Instance;
+ return Asn1GeneralizedTime.Meta.Instance;
case Asn1Tags.GraphicString: // [UNIVERSAL 25] IMPLICIT OCTET STRING (encode as if)
return DerGraphicString.Meta.Instance;
case Asn1Tags.VisibleString: // [UNIVERSAL 26] IMPLICIT OCTET STRING (encode as if)
diff --git a/crypto/src/asn1/DerGeneralizedTime.cs b/crypto/src/asn1/DerGeneralizedTime.cs
index 898a3d585..0386ecb02 100644
--- a/crypto/src/asn1/DerGeneralizedTime.cs
+++ b/crypto/src/asn1/DerGeneralizedTime.cs
@@ -1,367 +1,33 @@
using System;
-using System.Globalization;
-using System.IO;
-using System.Text;
-
-using Org.BouncyCastle.Utilities;
namespace Org.BouncyCastle.Asn1
{
- /**
- * Generalized time object.
- */
public class DerGeneralizedTime
- : Asn1Object
+ : Asn1GeneralizedTime
{
- internal class Meta : Asn1UniversalType
- {
- internal static readonly Asn1UniversalType Instance = new Meta();
-
- private Meta() : base(typeof(DerGeneralizedTime), Asn1Tags.GeneralizedTime) {}
-
- internal override Asn1Object FromImplicitPrimitive(DerOctetString octetString)
- {
- return CreatePrimitive(octetString.GetOctets());
- }
- }
-
- /**
- * return a generalized time from the passed in object
- *
- * @exception ArgumentException if the object cannot be converted.
- */
- public static DerGeneralizedTime GetInstance(object obj)
- {
- if (obj == null || obj is DerGeneralizedTime)
- {
- return (DerGeneralizedTime)obj;
- }
- else if (obj is IAsn1Convertible)
- {
- Asn1Object asn1Object = ((IAsn1Convertible)obj).ToAsn1Object();
- if (asn1Object is DerGeneralizedTime)
- return (DerGeneralizedTime)asn1Object;
- }
- else if (obj is byte[])
- {
- try
- {
- return (DerGeneralizedTime)Meta.Instance.FromByteArray((byte[])obj);
- }
- catch (IOException e)
- {
- throw new ArgumentException("failed to construct generalized time from byte[]: " + e.Message);
- }
- }
-
- throw new ArgumentException("illegal object in GetInstance: " + Platform.GetTypeName(obj), "obj");
- }
-
- /**
- * return a generalized Time object 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 DerGeneralizedTime GetInstance(Asn1TaggedObject taggedObject, bool declaredExplicit)
+ public DerGeneralizedTime(byte[] time)
+ : base(time)
{
- return (DerGeneralizedTime)Meta.Instance.GetContextInstance(taggedObject, declaredExplicit);
}
- private readonly string time;
-
- /**
- * The correct format for this is YYYYMMDDHHMMSS[.f]Z, or without the Z
- * for local time, or Z+-HHMM on the end, for difference between local
- * time and UTC time. The fractional second amount f must consist of at
- * least one number with trailing zeroes removed.
- *
- * @param time the time string.
- * @exception ArgumentException if string is an illegal format.
- */
- public DerGeneralizedTime(
- string time)
- {
- this.time = time;
-
- try
- {
- ToDateTime();
- }
- catch (FormatException e)
- {
- throw new ArgumentException("invalid date string: " + e.Message);
- }
- }
-
- /**
- * base constructor from a local time object
- */
public DerGeneralizedTime(DateTime time)
+ : base(time)
{
- this.time = time.ToUniversalTime().ToString(@"yyyyMMddHHmmss\Z");
- }
-
- internal DerGeneralizedTime(
- byte[] bytes)
- {
- //
- // explicitly convert to characters
- //
- this.time = Strings.FromAsciiByteArray(bytes);
}
- /**
- * Return the time.
- * @return The time string as it appeared in the encoded object.
- */
- public string TimeString
+ public DerGeneralizedTime(string time)
+ : base(time)
{
- get { return time; }
- }
-
- /**
- * return the time - always in the form of
- * YYYYMMDDhhmmssGMT(+hh:mm|-hh:mm).
- *
- * Normally in a certificate we would expect "Z" rather than "GMT",
- * however adding the "GMT" means we can just use:
- * X.690
+ * This is what is called "restricted string",
+ * and it uses ASCII characters to encode digits and supplemental data.
+ *
+ * 11: Restrictions on BER employed by both CER and DER
+ * 11.7 GeneralizedTime
+ *
- * dateF = new SimpleDateFormat("yyyyMMddHHmmssz");
- *
- * To read in the time and Get a date which is compatible with our local
- * time zone.