// T-REC-X.690-201508 sec 8.12 // The writer claims SetOf, and not Set, so as to avoid the field // ordering clause of T-REC-X.690-201508 sec 9.3 private Scope PushSetOfCore(Asn1Tag tag) { Debug.Assert(tag.IsConstructed); return(PushTag(tag, UniversalTagNumber.SetOf)); }
// T-REC-X.690-201508 sec 8.9, 8.10 private void PopSequenceCore(Asn1Tag tag) { Debug.Assert(tag.IsConstructed); PopTag(tag, UniversalTagNumber.Sequence); }
internal Asn1Tag PeekTag() { return(Asn1Tag.Decode(_span, out _)); }
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; }
private static int ProcessConstructedBitString( ReadOnlySpan <byte> source, AsnEncodingRules ruleSet, Span <byte> destination, BitStringCopyAction?copyAction, bool isIndefinite, out int lastUnusedBitCount, out int bytesRead) { lastUnusedBitCount = 0; bytesRead = 0; int lastSegmentLength = MaxCERSegmentSize; ReadOnlySpan <byte> cur = source; Stack <(int Offset, int Length, bool Indefinite, int BytesRead)>?readerStack = null; int totalLength = 0; Asn1Tag tag = Asn1Tag.ConstructedBitString; Span <byte> curDest = destination; while (true) { while (!cur.IsEmpty) { tag = ReadTagAndLength(cur, ruleSet, out int?length, out int headerLength); if (tag == Asn1Tag.PrimitiveBitString) { if (lastUnusedBitCount != 0) { // T-REC-X.690-201508 sec 8.6.4, only the last segment may have // a number of bits not a multiple of 8. throw new AsnContentException(); } if (ruleSet == AsnEncodingRules.CER && lastSegmentLength != MaxCERSegmentSize) { // T-REC-X.690-201508 sec 9.2 throw new AsnContentException(SR.ContentException_InvalidUnderCer_TryBerOrDer); } Debug.Assert(length != null); ReadOnlySpan <byte> encodedValue = Slice(cur, headerLength, length.Value); ParsePrimitiveBitStringContents( encodedValue, ruleSet, out lastUnusedBitCount, out ReadOnlySpan <byte> contents, out byte normalizedLastByte); int localLen = headerLength + encodedValue.Length; cur = cur.Slice(localLen); bytesRead += localLen; totalLength += contents.Length; lastSegmentLength = encodedValue.Length; if (copyAction != null) { copyAction(contents, normalizedLastByte, curDest); curDest = curDest.Slice(contents.Length); } } else if (tag == Asn1Tag.EndOfContents && isIndefinite) { ValidateEndOfContents(tag, length, headerLength); bytesRead += headerLength; if (readerStack?.Count > 0) { (int topOffset, int topLength, bool wasIndefinite, int pushedBytesRead) = readerStack.Pop(); ReadOnlySpan <byte> topSpan = source.Slice(topOffset, topLength); cur = topSpan.Slice(bytesRead); bytesRead += pushedBytesRead; isIndefinite = wasIndefinite; } else { // We have matched the EndOfContents that brought us here. break; } } else if (tag == Asn1Tag.ConstructedBitString) { if (ruleSet == AsnEncodingRules.CER) { // T-REC-X.690-201508 sec 9.2 throw new AsnContentException(SR.ContentException_InvalidUnderCerOrDer_TryBer); } readerStack ??= new Stack <(int, int, bool, int)>(); if (!source.Overlaps(cur, out int curOffset)) { Debug.Fail("Non-overlapping data encountered..."); throw new AsnContentException(); } readerStack.Push((curOffset, cur.Length, isIndefinite, bytesRead)); cur = Slice(cur, headerLength, length); bytesRead = headerLength; isIndefinite = (length == null); } else { // T-REC-X.690-201508 sec 8.6.4.1 (in particular, Note 2) throw new AsnContentException(); } } if (isIndefinite && tag != Asn1Tag.EndOfContents) { throw new AsnContentException(); } if (readerStack?.Count > 0) { (int topOffset, int topLength, bool wasIndefinite, int pushedBytesRead) = readerStack.Pop(); ReadOnlySpan <byte> tmpSpan = source.Slice(topOffset, topLength); cur = tmpSpan.Slice(bytesRead); isIndefinite = wasIndefinite; bytesRead += pushedBytesRead; } else { return(totalLength); } } }
private static int CopyConstructedOctetString( ReadOnlySpan <byte> source, AsnEncodingRules ruleSet, Span <byte> destination, bool write, bool isIndefinite, out int bytesRead) { bytesRead = 0; int lastSegmentLength = MaxCERSegmentSize; ReadOnlySpan <byte> cur = source; Stack <(int Offset, int Length, bool IsIndefinite, int BytesRead)>?readerStack = null; int totalLength = 0; Asn1Tag tag = Asn1Tag.ConstructedBitString; Span <byte> curDest = destination; while (true) { while (!cur.IsEmpty) { tag = ReadTagAndLength(cur, ruleSet, out int?length, out int headerLength); if (tag == Asn1Tag.PrimitiveOctetString) { if (ruleSet == AsnEncodingRules.CER && lastSegmentLength != MaxCERSegmentSize) { // T-REC-X.690-201508 sec 9.2 throw new AsnContentException(SR.ContentException_InvalidUnderCerOrDer_TryBer); } Debug.Assert(length != null); // The call to Slice here sanity checks the data bounds, length.Value is not // reliable unless this call has succeeded. ReadOnlySpan <byte> contents = Slice(cur, headerLength, length.Value); int localLen = headerLength + contents.Length; cur = cur.Slice(localLen); bytesRead += localLen; totalLength += contents.Length; lastSegmentLength = contents.Length; if (ruleSet == AsnEncodingRules.CER && lastSegmentLength > MaxCERSegmentSize) { // T-REC-X.690-201508 sec 9.2 throw new AsnContentException(SR.ContentException_InvalidUnderCerOrDer_TryBer); } if (write) { contents.CopyTo(curDest); curDest = curDest.Slice(contents.Length); } } else if (tag == Asn1Tag.EndOfContents && isIndefinite) { ValidateEndOfContents(tag, length, headerLength); bytesRead += headerLength; if (readerStack?.Count > 0) { (int topOffset, int topLength, bool wasIndefinite, int pushedBytesRead) = readerStack.Pop(); ReadOnlySpan <byte> topSpan = source.Slice(topOffset, topLength); cur = topSpan.Slice(bytesRead); bytesRead += pushedBytesRead; isIndefinite = wasIndefinite; } else { // We have matched the EndOfContents that brought us here. break; } } else if (tag == Asn1Tag.ConstructedOctetString) { if (ruleSet == AsnEncodingRules.CER) { // T-REC-X.690-201508 sec 9.2 throw new AsnContentException(SR.ContentException_InvalidUnderCerOrDer_TryBer); } if (readerStack == null) { readerStack = new Stack <(int, int, bool, int)>(); } if (!source.Overlaps(cur, out int curOffset)) { Debug.Fail("Non-overlapping data encountered..."); throw new AsnContentException(); } readerStack.Push((curOffset, cur.Length, isIndefinite, bytesRead)); cur = Slice(cur, headerLength, length); bytesRead = headerLength; isIndefinite = (length == null); } else { // T-REC-X.690-201508 sec 8.6.4.1 (in particular, Note 2) throw new AsnContentException(); } } if (isIndefinite && tag != Asn1Tag.EndOfContents) { throw new AsnContentException(); } if (readerStack?.Count > 0) { (int topOffset, int topLength, bool wasIndefinite, int pushedBytesRead) = readerStack.Pop(); ReadOnlySpan <byte> topSpan = source.Slice(topOffset, topLength); cur = topSpan.Slice(bytesRead); isIndefinite = wasIndefinite; bytesRead += pushedBytesRead; } else { return(totalLength); } } }
// T-REC-X.690-201508 sec 8.3 private void WriteNonNegativeIntegerCore(Asn1Tag tag, ulong value) { int valueLength; // 0x80 needs two bytes: 0x00 0x80 if (value < 0x80) { valueLength = 1; } else if (value < 0x8000) { valueLength = 2; } else if (value < 0x800000) { valueLength = 3; } else if (value < 0x80000000) { valueLength = 4; } else if (value < 0x80_00000000) { valueLength = 5; } else if (value < 0x8000_00000000) { valueLength = 6; } else if (value < 0x800000_00000000) { valueLength = 7; } else if (value < 0x80000000_00000000) { valueLength = 8; } else { valueLength = 9; } // Clear the constructed bit, if it was set. Debug.Assert(!tag.IsConstructed); WriteTag(tag); WriteLength(valueLength); ulong remaining = value; int idx = _offset + valueLength - 1; do { _buffer[idx] = (byte)remaining; remaining >>= 8; idx--; } while (idx >= _offset); #if DEBUG if (valueLength > 1) { // T-REC-X.690-201508 sec 8.3.2 // Cannot start with 9 bits of 0 (or 9 bits of 1, but that's not this method). Debug.Assert(_buffer[_offset] != 0 || _buffer[_offset + 1] > 0x7F); } #endif _offset += valueLength; }
// T-REC-X.690-201508 sec 8.3 private void WriteIntegerCore(Asn1Tag tag, long value) { if (value >= 0) { WriteNonNegativeIntegerCore(tag, (ulong)value); return; } int valueLength; if (value >= sbyte.MinValue) { valueLength = 1; } else if (value >= short.MinValue) { valueLength = 2; } else if (value >= unchecked ((long)0xFFFFFFFF_FF800000)) { valueLength = 3; } else if (value >= int.MinValue) { valueLength = 4; } else if (value >= unchecked ((long)0xFFFFFF80_00000000)) { valueLength = 5; } else if (value >= unchecked ((long)0xFFFF8000_00000000)) { valueLength = 6; } else if (value >= unchecked ((long)0xFF800000_00000000)) { valueLength = 7; } else { valueLength = 8; } Debug.Assert(!tag.IsConstructed); WriteTag(tag); WriteLength(valueLength); long remaining = value; int idx = _offset + valueLength - 1; do { _buffer[idx] = (byte)remaining; remaining >>= 8; idx--; } while (idx >= _offset); #if DEBUG if (valueLength > 1) { // T-REC-X.690-201508 sec 8.3.2 // Cannot start with 9 bits of 1 (or 9 bits of 0, but that's not this method). Debug.Assert(_buffer[_offset] != 0xFF || _buffer[_offset + 1] < 0x80); } #endif _offset += valueLength; }