File: Signing\DerEncoding\DerGeneralizedTime.cs
Web Access
Project: src\src\nuget-client\src\NuGet.Core\NuGet.Packaging\NuGet.Packaging.csproj (NuGet.Packaging)
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using System.Globalization;
using System.Security.Cryptography;

namespace NuGet.Packaging.Signing.DerEncoding
{
    /// <remarks>This is public only to facilitate testing.</remarks>
    public sealed class DerGeneralizedTime
    {
        public DateTime DateTime { get; }

        private DerGeneralizedTime(DateTime datetime)
        {
            DateTime = datetime;
        }

        public static DerGeneralizedTime Read(string decodedTime)
        {
            // YYYYMMDDhhmmssZ
            var minimumValidLength = 15;

            if (string.IsNullOrEmpty(decodedTime) || decodedTime.Length < minimumValidLength)
            {
                throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding);
            }

            if (decodedTime[decodedTime.Length - 1] != 'Z')
            {
                throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding);
            }

#if NETCOREAPP
            var decimalIndex = decodedTime.IndexOf('.', StringComparison.Ordinal);
#else
            var decimalIndex = decodedTime.IndexOf('.');
#endif
            var stringToParse = decodedTime;
            string format;

            if (decimalIndex == -1)
            {
                format = "yyyyMMddHHmmssZ";
            }
            else
            {
                // 20180214095812.3456789Z
                //                ^-----^
                var fractionalSecondDigits = decodedTime.Length - 2 - decimalIndex;

                // Disallow decimal with no following digits
                if (fractionalSecondDigits < 1)
                {
                    throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding);
                }

                // Disallow trailing zero
                if (decodedTime[decodedTime.Length - 2] == '0')
                {
                    throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding);
                }

                if (fractionalSecondDigits > 7)
                {
                    // DateTime is precise to 1 ten-millionth of a second or 7 digits after the decimal.
                    // If decodedTime precision is greater than this, ignore the trailing digits.
                    // The length of the maximum format string minus the trailing "Z" is 22 characters:
                    //
                    //     20180214095812.3456789Z
                    //     ^--------------------^
                    stringToParse = $"{decodedTime.Substring(0, 22)}Z";
                    fractionalSecondDigits = 7;
                }

                format = $"yyyyMMddHHmmss.{new string('F', fractionalSecondDigits)}Z";
            }

            DateTime datetime;

            if (DateTime.TryParseExact(
                stringToParse,
                format,
                CultureInfo.InvariantCulture,
                DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal,
                out datetime))
            {
                return new DerGeneralizedTime(datetime);
            }

            throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding);
        }
    }
}