Пример #1
0
        /// <summary>
        ///   Write the provided value as a UTCTime with a specified tag,
        ///   accepting the two-digit year as valid in context.
        /// </summary>
        /// <param name="value">The value to write.</param>
        /// <param name="tag">The tag to write, or <see langword="null"/> for the default tag (Universal 23).</param>
        /// <exception cref="ArgumentException">
        ///   <paramref name="tag"/>.<see cref="Asn1Tag.TagClass"/> is
        ///   <see cref="TagClass.Universal"/>, but
        ///   <paramref name="tag"/>.<see cref="Asn1Tag.TagValue"/> is not correct for
        ///   the method.
        /// </exception>
        /// <seealso cref="WriteUtcTime(DateTimeOffset,int,Nullable{Asn1Tag})"/>
        /// <seealso cref="System.Globalization.Calendar.TwoDigitYearMax"/>
        public void WriteUtcTime(DateTimeOffset value, Asn1Tag?tag = null)
        {
            CheckUniversalTag(tag, UniversalTagNumber.UtcTime);

            // Clear the constructed flag, if present.
            WriteUtcTimeCore(tag?.AsPrimitive() ?? Asn1Tag.UtcTime, value);
        }
        // T-REC-X.690-201508 sec 8.23
        private void WriteCharacterStringCore(Asn1Tag tag, Text.Encoding encoding, ReadOnlySpan <char> str)
        {
            int size = encoding.GetByteCount(str);

            // T-REC-X.690-201508 sec 9.2
            if (RuleSet == AsnEncodingRules.CER)
            {
                // If it exceeds the primitive segment size, use the constructed encoding.
                if (size > AsnReader.MaxCERSegmentSize)
                {
                    WriteConstructedCerCharacterString(tag, encoding, str, size);
                    return;
                }
            }

            // Clear the constructed tag, if present.
            WriteTag(tag.AsPrimitive());
            WriteLength(size);
            Span <byte> dest = _buffer.AsSpan(_offset, size);

            int written = encoding.GetBytes(str, dest);

            if (written != size)
            {
                Debug.Fail(
                    $"Encoding produced different answer for GetByteCount ({size}) and GetBytes ({written})");
                throw new InvalidOperationException();
            }

            _offset += size;
        }
        /// <summary>
        ///   Write the provided <see cref="DateTimeOffset"/> as a GeneralizedTime with a specified
        ///   UNIVERSAL 24, optionally excluding the fractional seconds.
        /// </summary>
        /// <param name="value">The value to write.</param>
        /// <param name="omitFractionalSeconds">
        ///   <see langword="true"/> to treat the fractional seconds in <paramref name="value"/> as 0 even if
        ///   a non-zero value is present.
        /// </param>
        /// <param name="tag">The tag to write, or <see langword="null"/> for the default tag (Universal 24).</param>
        /// <exception cref="ArgumentException">
        ///   <paramref name="tag"/>.<see cref="Asn1Tag.TagClass"/> is
        ///   <see cref="TagClass.Universal"/>, but
        ///   <paramref name="tag"/>.<see cref="Asn1Tag.TagValue"/> is not correct for
        ///   the method.
        /// </exception>
        public void WriteGeneralizedTime(
            DateTimeOffset value,
            bool omitFractionalSeconds = false,
            Asn1Tag?tag = null)
        {
            CheckUniversalTag(tag, UniversalTagNumber.GeneralizedTime);

            // Clear the constructed flag, if present.
            WriteGeneralizedTimeCore(
                tag?.AsPrimitive() ?? Asn1Tag.GeneralizedTime,
                value,
                omitFractionalSeconds);
        }
Пример #4
0
        /// <summary>
        ///   Write the provided value as a UTCTime with a specified tag,
        ///   provided the year is in the allowed range.
        /// </summary>
        /// <param name="value">The value to write.</param>
        /// <param name="twoDigitYearMax">
        ///   The maximum valid year for <paramref name="value"/>, after conversion to UTC.
        ///   For the X.509 Time.utcTime range of 1950-2049, pass <c>2049</c>.
        /// </param>
        /// <param name="tag">The tag to write, or <see langword="null"/> for the default tag (Universal 23).</param>
        /// <exception cref="ArgumentException">
        ///   <paramref name="tag"/>.<see cref="Asn1Tag.TagClass"/> is
        ///   <see cref="TagClass.Universal"/>, but
        ///   <paramref name="tag"/>.<see cref="Asn1Tag.TagValue"/> is not correct for
        ///   the method.
        /// </exception>
        /// <exception cref="ArgumentOutOfRangeException">
        ///   <paramref name="value"/>.<see cref="DateTimeOffset.Year"/> (after conversion to UTC)
        ///   is not in the range
        ///   (<paramref name="twoDigitYearMax"/> - 100, <paramref name="twoDigitYearMax"/>].
        /// </exception>
        /// <seealso cref="WriteUtcTime(DateTimeOffset,Nullable{Asn1Tag})"/>
        /// <seealso cref="System.Globalization.Calendar.TwoDigitYearMax"/>
        public void WriteUtcTime(DateTimeOffset value, int twoDigitYearMax, Asn1Tag?tag = null)
        {
            CheckUniversalTag(tag, UniversalTagNumber.UtcTime);

            value = value.ToUniversalTime();

            if (value.Year > twoDigitYearMax || value.Year <= twoDigitYearMax - 100)
            {
                throw new ArgumentOutOfRangeException(nameof(value));
            }

            WriteUtcTimeCore(tag?.AsPrimitive() ?? Asn1Tag.UtcTime, value);
        }
Пример #5
0
        // T-REC-X.690-201508 sec 8.6
        private void WriteBitStringCore(Asn1Tag tag, ReadOnlySpan <byte> bitString, int unusedBitCount)
        {
            // T-REC-X.690-201508 sec 8.6.2.2
            if (unusedBitCount < 0 || unusedBitCount > 7)
            {
                throw new ArgumentOutOfRangeException(
                          nameof(unusedBitCount),
                          unusedBitCount,
                          SR.Argument_UnusedBitCountRange);
            }

            // T-REC-X.690-201508 sec 8.6.2.3
            if (bitString.Length == 0 && unusedBitCount != 0)
            {
                throw new ArgumentException(SR.Argument_UnusedBitCountMustBeZero, nameof(unusedBitCount));
            }

            byte lastByte = bitString.IsEmpty ? (byte)0 : bitString[bitString.Length - 1];

            // T-REC-X.690-201508 sec 11.2
            //
            // This could be ignored for BER, but since DER is more common and
            // it likely suggests a program error on the caller, leave it enabled for
            // BER for now.
            if (!CheckValidLastByte(lastByte, unusedBitCount))
            {
                throw new ArgumentException(SR.Argument_UnusedBitWasSet, nameof(unusedBitCount));
            }

            if (RuleSet == AsnEncodingRules.CER)
            {
                // T-REC-X.690-201508 sec 9.2
                //
                // If it's not within a primitive segment, use the constructed encoding.
                // (>= instead of > because of the unused bit count byte)
                if (bitString.Length >= AsnReader.MaxCERSegmentSize)
                {
                    WriteConstructedCerBitString(tag, bitString, unusedBitCount);
                    return;
                }
            }

            // Clear the constructed flag, if present.
            WriteTag(tag.AsPrimitive());
            // The unused bits byte requires +1.
            WriteLength(bitString.Length + 1);
            _buffer[_offset] = (byte)unusedBitCount;
            _offset++;
            bitString.CopyTo(_buffer.AsSpan(_offset));
            _offset += bitString.Length;
        }
        // T-REC-X.690-201508 sec 8.7
        private void WriteOctetStringCore(Asn1Tag tag, ReadOnlySpan <byte> octetString)
        {
            if (RuleSet == AsnEncodingRules.CER)
            {
                // If it's bigger than a primitive segment, use the constructed encoding
                // T-REC-X.690-201508 sec 9.2
                if (octetString.Length > AsnReader.MaxCERSegmentSize)
                {
                    WriteConstructedCerOctetString(tag, octetString);
                    return;
                }
            }

            // Clear the constructed flag, if present.
            WriteTag(tag.AsPrimitive());
            WriteLength(octetString.Length);
            octetString.CopyTo(_buffer.AsSpan(_offset));
            _offset += octetString.Length;
        }
Пример #7
0
        /// <summary>
        ///   Write an Object Identifier with a specified tag.
        /// </summary>
        /// <param name="oidValue">The object identifier to write.</param>
        /// <param name="tag">The tag to write, or <see langword="null"/> for the default tag (Universal 6).</param>
        /// <exception cref="ArgumentException">
        ///   <paramref name="tag"/>.<see cref="Asn1Tag.TagClass"/> is
        ///   <see cref="TagClass.Universal"/>, but
        ///   <paramref name="tag"/>.<see cref="Asn1Tag.TagValue"/> is not correct for
        ///   the method.
        ///
        ///   -or-
        ///
        ///   <paramref name="oidValue"/> is not a valid dotted decimal
        ///   object identifier.
        /// </exception>
        public void WriteObjectIdentifier(ReadOnlySpan <char> oidValue, Asn1Tag?tag = null)
        {
            CheckUniversalTag(tag, UniversalTagNumber.ObjectIdentifier);

            WriteObjectIdentifierCore(tag?.AsPrimitive() ?? Asn1Tag.ObjectIdentifier, oidValue);
        }
Пример #8
0
        private void PopTag(Asn1Tag tag, UniversalTagNumber tagType, bool sortContents = false)
        {
            if (_nestingStack == null || _nestingStack.Count == 0)
            {
                throw new InvalidOperationException(SR.AsnWriter_PopWrongTag);
            }

            (Asn1Tag stackTag, int lenOffset, UniversalTagNumber stackTagType) = _nestingStack.Peek();

            Debug.Assert(tag.IsConstructed);
            if (stackTag != tag || stackTagType != tagType)
            {
                throw new InvalidOperationException(SR.AsnWriter_PopWrongTag);
            }

            _nestingStack.Pop();

            if (sortContents)
            {
                Debug.Assert(tagType == UniversalTagNumber.SetOf);
                SortContents(_buffer, lenOffset + 1, _offset);
            }

            // BER could use the indefinite encoding that CER does.
            // But since the definite encoding form is easier to read (doesn't require a contextual
            // parser to find the end-of-contents marker) some ASN.1 readers (including the previous
            // incarnation of AsnReader) may choose not to support it.
            //
            // So, BER will use the DER rules here, in the interest of broader compatibility.

            // T-REC-X.690-201508 sec 9.1 (constructed CER => indefinite length)
            // T-REC-X.690-201508 sec 8.1.3.6
            if (RuleSet == AsnEncodingRules.CER && tagType != UniversalTagNumber.OctetString)
            {
                WriteEndOfContents();
                return;
            }

            int containedLength = _offset - 1 - lenOffset;

            Debug.Assert(containedLength >= 0);

            int start = lenOffset + 1;

            // T-REC-X.690-201508 sec 9.2
            // T-REC-X.690-201508 sec 10.2
            if (tagType == UniversalTagNumber.OctetString)
            {
                if (RuleSet != AsnEncodingRules.CER || containedLength <= AsnDecoder.MaxCERSegmentSize)
                {
                    // Need to replace the tag with the primitive tag.
                    // Since the P/C bit doesn't affect the length, overwrite the tag.
                    int tagLen = tag.CalculateEncodedSize();
                    tag.AsPrimitive().Encode(_buffer.AsSpan(lenOffset - tagLen, tagLen));
                    // Continue with the regular flow.
                }
                else
                {
                    int fullSegments = Math.DivRem(
                        containedLength,
                        AsnDecoder.MaxCERSegmentSize,
                        out int lastSegmentSize);

                    int requiredPadding =
                        // Each full segment has a header of 048203E8
                        4 * fullSegments +
                        // The last one is 04 plus the encoded length.
                        2 + GetEncodedLengthSubsequentByteCount(lastSegmentSize);

                    // Shift the data forward so we can use right-source-overlapped
                    // copy in the existing method.
                    // Also, ensure the space for the end-of-contents marker.
                    EnsureWriteCapacity(requiredPadding + 2);
                    ReadOnlySpan <byte> src  = _buffer.AsSpan(start, containedLength);
                    Span <byte>         dest = _buffer.AsSpan(start + requiredPadding, containedLength);
                    src.CopyTo(dest);

                    int expectedEnd = start + containedLength + requiredPadding + 2;
                    _offset = lenOffset - tag.CalculateEncodedSize();
                    WriteConstructedCerOctetString(tag, dest);
                    Debug.Assert(_offset == expectedEnd);
                    return;
                }
            }

            int shiftSize = GetEncodedLengthSubsequentByteCount(containedLength);

            // Best case, length fits in the compact byte
            if (shiftSize == 0)
            {
                _buffer[lenOffset] = (byte)containedLength;
                return;
            }

            // We're currently at the end, so ensure we have room for N more bytes.
            EnsureWriteCapacity(shiftSize);

            // Buffer.BlockCopy correctly does forward-overlapped, so use it.
            Buffer.BlockCopy(_buffer, start, _buffer, start + shiftSize, containedLength);

            int tmp = _offset;

            _offset = lenOffset;
            WriteLength(containedLength);
            Debug.Assert(_offset - lenOffset - 1 == shiftSize);
            _offset = tmp + shiftSize;
        }
Пример #9
0
        /// <summary>
        ///   Write an Integer value with a specified tag.
        /// </summary>
        /// <param name="value">The integer value to write, in signed big-endian byte order.</param>
        /// <param name="tag">The tag to write, or <see langword="null"/> for the default tag (Universal 2).</param>
        /// <exception cref="ArgumentException">
        ///   <paramref name="tag"/>.<see cref="Asn1Tag.TagClass"/> is
        ///   <see cref="TagClass.Universal"/>, but
        ///   <paramref name="tag"/>.<see cref="Asn1Tag.TagValue"/> is not correct for
        ///   the method.
        /// </exception>
        /// <exception cref="ArgumentException">
        ///   the 9 most significant bits are all set.
        ///
        ///   -or-
        ///
        ///   the 9 most significant bits are all unset.
        /// </exception>
        public void WriteInteger(ReadOnlySpan <byte> value, Asn1Tag?tag = null)
        {
            CheckUniversalTag(tag, UniversalTagNumber.Integer);

            WriteIntegerCore(tag?.AsPrimitive() ?? Asn1Tag.Integer, value);
        }
Пример #10
0
        public void WriteInteger(ulong value, Asn1Tag?tag = null)
        {
            CheckUniversalTag(tag, UniversalTagNumber.Integer);

            WriteNonNegativeIntegerCore(tag?.AsPrimitive() ?? Asn1Tag.Integer, value);
        }
        /// <summary>
        ///   Write a Boolean value with a specified tag.
        /// </summary>
        /// <param name="value">The value to write.</param>
        /// <param name="tag">The tag to write, or <see langword="null"/> for the default tag (Universal 1).</param>
        /// <exception cref="ArgumentException">
        ///   <paramref name="tag"/>.<see cref="Asn1Tag.TagClass"/> is
        ///   <see cref="TagClass.Universal"/>, but
        ///   <paramref name="tag"/>.<see cref="Asn1Tag.TagValue"/> is not correct for
        ///   the method
        /// </exception>
        public void WriteBoolean(bool value, Asn1Tag?tag = null)
        {
            CheckUniversalTag(tag, UniversalTagNumber.Boolean);

            WriteBooleanCore(tag?.AsPrimitive() ?? Asn1Tag.Boolean, value);
        }
        /// <summary>
        ///   Write NULL with a specified tag.
        /// </summary>
        /// <param name="tag">The tag to write, or <see langword="null"/> for the default tag (Universal 5).</param>
        /// <exception cref="ArgumentException">
        ///   <paramref name="tag"/>.<see cref="Asn1Tag.TagClass"/> is
        ///   <see cref="TagClass.Universal"/>, but
        ///   <paramref name="tag"/>.<see cref="Asn1Tag.TagValue"/> is not correct for
        ///   the method.
        /// </exception>
        public void WriteNull(Asn1Tag?tag = null)
        {
            CheckUniversalTag(tag, UniversalTagNumber.Null);

            WriteNullCore(tag?.AsPrimitive() ?? Asn1Tag.Null);
        }