/// <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); }
/// <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); }
// 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; }
/// <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); }
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; }
/// <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); }
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); }