/// <summary> /// Write the provided <see cref="DateTimeOffset"/> as a GeneralizedTime with a specified /// UNIVERSAL 24, optionally excluding the fractional seconds. /// </summary> /// <param name="tag">The tagto write.</param> /// <param name="value">The value to write.</param> /// <param name="omitFractionalSeconds"> /// <c>true</c> to treat the fractional seconds in <paramref name="value"/> as 0 even if /// a non-zero value is present. /// </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="ObjectDisposedException">The writer has been Disposed.</exception> /// <seealso cref="WriteGeneralizedTime(System.Security.Cryptography.Asn1.Asn1Tag,System.DateTimeOffset,bool)"/> public void WriteGeneralizedTime(Asn1Tag tag, DateTimeOffset value, bool omitFractionalSeconds = true) { CheckUniversalTag(tag, UniversalTagNumber.GeneralizedTime); // Clear the constructed flag, if present. this.WriteGeneralizedTimeCore(tag.AsPrimitive(), value, omitFractionalSeconds); }
/// <summary> /// Write the provided <see cref="DateTimeOffset"/> as a UTCTime with a specified tag, /// accepting the two-digit year as valid in context. /// </summary> /// <param name="tag">The tag to write.</param> /// <param name="value">The value to write.</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="ObjectDisposedException">The writer has been Disposed.</exception> /// <seealso cref="WriteUtcTime(Asn1Tag,DateTimeOffset,int)"/> /// <seealso cref="System.Globalization.Calendar.TwoDigitYearMax"/> public void WriteUtcTime(Asn1Tag tag, DateTimeOffset value) { CheckUniversalTag(tag, UniversalTagNumber.UtcTime); // Clear the constructed flag, if present. this.WriteUtcTimeCore(tag.AsPrimitive(), 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.Cryptography_Asn_UnusedBitCountRange); } CheckDisposed(); // T-REC-X.690-201508 sec 8.6.2.3 if (bitString.Length == 0 && unusedBitCount != 0) { throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding); } // If 3 bits are "unused" then build a mask for them to check for 0. // 1 << 3 => 0b0000_1000 // subtract 1 => 0b000_0111 int mask = (1 << unusedBitCount) - 1; byte lastByte = bitString.IsEmpty ? (byte)0 : bitString[bitString.Length - 1]; if ((lastByte & mask) != 0) { // 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. // TODO: Probably warrants a distinct message. throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding); } 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; }
/// <summary> /// Write a non-[<see cref="FlagsAttribute"/>] enum value as an Enumerated with /// tag UNIVERSAL 10. /// </summary> /// <param name="tag">The tag to write.</param> /// <param name="enumValue">The boxed enumeration value to write.</param> /// <exception cref="ArgumentNullException"> /// <paramref name="enumValue"/> is <c>null</c> /// </exception> /// <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="enumValue"/> is not a boxed enum value --OR-- /// the unboxed type of <paramref name="enumValue"/> is declared [<see cref="FlagsAttribute"/>] /// </exception> /// <exception cref="ObjectDisposedException">The writer has been Disposed.</exception> /// <seealso cref="WriteEnumeratedValue(System.Security.Cryptography.Asn1.Asn1Tag,object)"/> /// <seealso cref="WriteEnumeratedValue{T}(T)"/> public void WriteEnumeratedValue(Asn1Tag tag, object enumValue) { if (enumValue == null) { throw new ArgumentNullException(nameof(enumValue)); } this.WriteEnumeratedValue(tag.AsPrimitive(), enumValue.GetType(), enumValue); }
// 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.Resource("Cryptography_Asn_UnusedBitCountRange")); } this.CheckDisposed(); // T-REC-X.690-201508 sec 8.6.2.3 if (bitString.Length == 0 && unusedBitCount != 0) { throw new CryptographicException(SR.Resource("Cryptography_Der_Invalid_Encoding")); } 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)) { // TODO: Probably warrants a distinct message. throw new CryptographicException(SR.Resource("Cryptography_Der_Invalid_Encoding")); } if (this.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) { this.WriteConstructedCerBitString(tag, bitString, unusedBitCount); return; } } // Clear the constructed flag, if present. this.WriteTag(tag.AsPrimitive()); // The unused bits byte requires +1. this.WriteLength(bitString.Length + 1); this._buffer[this._offset] = (byte)unusedBitCount; this._offset++; bitString.CopyTo(this._buffer.AsSpan(this._offset)); this._offset += bitString.Length; }
// T-REC-X.690-201508 sec 8.23 private void WriteCharacterStringCore(Asn1Tag tag, Text.Encoding encoding, ReadOnlySpan <char> str) { int size = -1; // T-REC-X.690-201508 sec 9.2 if (RuleSet == AsnEncodingRules.CER) { // TODO: Split this for netstandard vs netcoreapp for span?. unsafe { fixed(char *strPtr = &MemoryMarshal.GetReference(str)) { size = encoding.GetByteCount(strPtr, str.Length); // If it exceeds the primitive segment size, use the constructed encoding. if (size > AsnReader.MaxCERSegmentSize) { WriteConstructedCerCharacterString(tag, encoding, str, size); return; } } } } // TODO: Split this for netstandard vs netcoreapp for span?. unsafe { fixed(char *strPtr = &MemoryMarshal.GetReference(str)) { if (size < 0) { size = encoding.GetByteCount(strPtr, str.Length); } // Clear the constructed tag, if present. WriteTag(tag.AsPrimitive()); WriteLength(size); Span <byte> dest = _buffer.AsSpan(_offset, size); fixed(byte *destPtr = &MemoryMarshal.GetReference(dest)) { int written = encoding.GetBytes(strPtr, str.Length, destPtr, dest.Length); 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 UTCTime with a specified tag, /// provided the year is in the allowed range. /// </summary> /// <param name="tag">The tag to write.</param> /// <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> /// <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> /// <exception cref="ObjectDisposedException">The writer has been Disposed.</exception> /// <seealso cref="WriteUtcTime(System.Security.Cryptography.Asn1.Asn1Tag,System.DateTimeOffset,int)"/> /// <seealso cref="System.Globalization.Calendar.TwoDigitYearMax"/> public void WriteUtcTime(Asn1Tag tag, DateTimeOffset value, int twoDigitYearMax) { CheckUniversalTag(tag, UniversalTagNumber.UtcTime); value = value.ToUniversalTime(); if (value.Year > twoDigitYearMax || value.Year <= twoDigitYearMax - 100) { throw new ArgumentOutOfRangeException(nameof(value)); } this.WriteUtcTimeCore(tag.AsPrimitive(), value); }
// T-REC-X.690-201508 sec 8.7 private void WriteOctetStringCore(Asn1Tag tag, ReadOnlySpan <byte> octetString) { if (this.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) { this.WriteConstructedCerOctetString(tag, octetString); return; } } // Clear the constructed flag, if present. this.WriteTag(tag.AsPrimitive()); this.WriteLength(octetString.Length); octetString.CopyTo(this._buffer.AsSpan(this._offset)); this._offset += octetString.Length; }
// T-REC-X.690-201508 sec 8.6 private void WriteBitStringCore <TState>( Asn1Tag tag, int byteLength, TState state, SpanAction <byte, TState> action, int unusedBitCount = 0) { if (byteLength == 0) { WriteBitStringCore(tag, ReadOnlySpan <byte> .Empty, unusedBitCount); return; } // T-REC-X.690-201508 sec 8.6.2.2 if (unusedBitCount < 0 || unusedBitCount > 7) { throw new ArgumentOutOfRangeException( nameof(unusedBitCount), unusedBitCount, SR.Resource("Cryptography_Asn_UnusedBitCountRange")); } CheckDisposed(); int savedOffset = _offset; Span <byte> scratchSpace; byte[] ensureNoExtraCopy = null; int expectedSize = 0; // 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) bool segmentedWrite = RuleSet == AsnEncodingRules.CER && byteLength >= AsnReader.MaxCERSegmentSize; if (segmentedWrite) { // Rather than call the callback multiple times, grow the buffer to allow // for enough space for the final output, then return a buffer where the last segment // is in the correct place. (Data will shift backwards to the right spot while writing // other segments). expectedSize = DetermineCerBitStringTotalLength(tag, byteLength); EnsureWriteCapacity(expectedSize); int overhead = expectedSize - byteLength; // Start writing where the last content byte is in the correct place, which is // after all of the overhead, but ending before the two byte end-of-contents marker. int scratchStart = overhead - 2; ensureNoExtraCopy = _buffer; scratchSpace = _buffer.AsSpan(scratchStart, byteLength); // Don't let gapped-writes be unpredictable. scratchSpace.Clear(); } else { WriteTag(tag.AsPrimitive()); // The unused bits byte requires +1. WriteLength(byteLength + 1); _buffer[_offset] = (byte)unusedBitCount; _offset++; scratchSpace = _buffer.AsSpan(_offset, byteLength); } action(scratchSpace, state); // 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(scratchSpace[byteLength - 1], unusedBitCount)) { // Since we are restoring _offset we won't clear this on a grow or Dispose, // so clear it now. _offset = savedOffset; scratchSpace.Clear(); // TODO: Probably warrants a distinct message. throw new CryptographicException(SR.Resource("Cryptography_Der_Invalid_Encoding")); } if (segmentedWrite) { WriteConstructedCerBitString(tag, scratchSpace, unusedBitCount); Debug.Assert(_offset - savedOffset == expectedSize, $"expected size was {expectedSize}, actual was {_offset - savedOffset}"); Debug.Assert(_buffer == ensureNoExtraCopy, $"_buffer was replaced during while writing a bit string via callback"); } else { _offset += byteLength; } }
/// <summary> /// Write a Boolean value with a specified tag. /// </summary> /// <param name="tag">The tag to write.</param> /// <param name="value">The value to write.</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(Asn1Tag tag, bool value) { CheckUniversalTag(tag, UniversalTagNumber.Boolean); WriteBooleanCore(tag.AsPrimitive(), value); }
/// <summary> /// Write NULL with a specified tag. /// </summary> /// <param name="tag">The tag to write.</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="ObjectDisposedException">The writer has been Disposed.</exception> public void WriteNull(Asn1Tag tag) { CheckUniversalTag(tag, UniversalTagNumber.Null); this.WriteNullCore(tag.AsPrimitive()); }
/// <summary> /// Write a non-[<see cref="FlagsAttribute"/>] enum value as an Enumerated with /// tag UNIVERSAL 10. /// </summary> /// <param name="tag">The tag to write.</param> /// <param name="enumValue">The boxed enumeration value to write.</param> /// <exception cref="ArgumentNullException"> /// <paramref name="enumValue"/> is <c>null</c> /// </exception> /// <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-- /// <typeparamref name="TEnum"/> is not an enum --OR-- /// <typeparamref name="TEnum"/> is declared [<see cref="FlagsAttribute"/>] /// </exception> /// <exception cref="ObjectDisposedException">The writer has been Disposed.</exception> /// <seealso cref="WriteEnumeratedValue(Asn1Tag,object)"/> /// <seealso cref="WriteEnumeratedValue{T}(T)"/> public void WriteEnumeratedValue <TEnum>(Asn1Tag tag, TEnum enumValue) where TEnum : struct { this.WriteEnumeratedValue(tag.AsPrimitive(), typeof(TEnum), enumValue); }
/// <summary> /// Write an Object Identifier with a specified tag. /// </summary> /// <param name="tag">The tag to write.</param> /// <param name="oidValue">The object identifier to write.</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="CryptographicException"> /// <paramref name="oidValue"/> is not a valid dotted decimal /// object identifier /// </exception> /// <exception cref="ObjectDisposedException">The writer has been Disposed.</exception> public void WriteObjectIdentifier(Asn1Tag tag, ReadOnlySpan <char> oidValue) { CheckUniversalTag(tag, UniversalTagNumber.ObjectIdentifier); WriteObjectIdentifierCore(tag.AsPrimitive(), oidValue); }
/// <summary> /// Write an Integer value with a specified tag. /// </summary> /// <param name="tag">The tag to write.</param> /// <param name="value">The value to write.</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="ObjectDisposedException">The writer has been Disposed.</exception> public void WriteInteger(Asn1Tag tag, ulong value) { CheckUniversalTag(tag, UniversalTagNumber.Integer); this.WriteNonNegativeIntegerCore(tag.AsPrimitive(), value); }
/// <summary> /// Write an Integer value with a specified tag. /// </summary> /// <param name="tag">The tag to write.</param> /// <param name="value">The integer value to write, in unsigned big-endian byte order.</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="CryptographicException"> /// the 9 most sigificant bits are all unset /// </exception> /// <exception cref="ObjectDisposedException">The writer has been Disposed.</exception> public void WriteIntegerUnsigned(Asn1Tag tag, ReadOnlySpan <byte> value) { CheckUniversalTag(tag, UniversalTagNumber.Integer); this.WriteIntegerUnsignedCore(tag.AsPrimitive(), value); }
/// <summary> /// Write an Integer value with a specified tag. /// </summary> /// <param name="tag">The tag to write.</param> /// <param name="value">The value to write.</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="ObjectDisposedException">The writer has been Disposed.</exception> public void WriteInteger(Asn1Tag tag, BigInteger value) { CheckUniversalTag(tag, UniversalTagNumber.Integer); WriteIntegerCore(tag.AsPrimitive(), value); }