diff options
Diffstat (limited to 'crypto/src/asn1/Asn1GeneralizedTime.cs')
-rw-r--r-- | crypto/src/asn1/Asn1GeneralizedTime.cs | 439 |
1 files changed, 439 insertions, 0 deletions
diff --git a/crypto/src/asn1/Asn1GeneralizedTime.cs b/crypto/src/asn1/Asn1GeneralizedTime.cs new file mode 100644 index 000000000..e844c8ca2 --- /dev/null +++ b/crypto/src/asn1/Asn1GeneralizedTime.cs @@ -0,0 +1,439 @@ +using System; +using System.Globalization; +using System.IO; +using System.Text; + +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> + * <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. + */ + 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; + } + } +} |