bcgit / bc-csharp

BouncyCastle.NET Cryptography Library (Mirror)
https://www.bouncycastle.org/csharp
MIT License
1.68k stars 558 forks source link

More control over how BER-encoding works #88

Open qmfrederik opened 7 years ago

qmfrederik commented 7 years ago

This is a continuation of the discussion in #66.

I use Bouncy Castle to digitally sign iOS applications. I've noticed that validation of iOS apps fails (crashes) on the device if the signature contains BER-encoded sequences where i) a length which exceeds 0x1000 and ii) they are encoded using fixed-length encoding.

I can't control iOS; hence I need to be able to control how sequences are encoded and be able to use variable-length encoding.

Currently, the BouncyCastle exposes no method (that I'm aware of) that enables me to control how objects are encoded.

Hence, the gist of this issue is that I'd like to control that.

I've been able to workaround this by enabling subclassing of the DerObjectStream class and implementing a subclass as shown below; and PR #66 was submitted to enable that scenario.

    /// <summary>
    /// Implements a <c>DerOutputStream</c> which uses the encoding format
    /// iOS expects: indefinite-length tags for objects which exceed as size of
    /// 0x1000.
    /// </summary>
    public class iOSDerOutputStream : DerOutputStream
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="iOSDerOutputStream"/> class.
        /// </summary>
        /// <param name="stream">
        /// The underlying stream to which to write the data.
        /// </param>
        public iOSDerOutputStream(Stream stream)
            : base(stream)
        {
        }

        /// <summary>
        /// Gets or sets a value indicating whether the stream should always
        /// use indefinite-length objects. The default is <see langword="false"/>.
        /// </summary>
        public bool ForceIndefinite
        {
            get;
            set;
        }

        /// <inheritdoc/>
        public override void WriteEncoded(
            int tag,
            byte[] bytes)
        {
            this.WriteByte((byte)tag);
            this.WriteLengthStart(bytes.Length);
            this.Write(bytes, 0, bytes.Length);
            this.WriteLengthEnd(bytes.Length);
        }

        /// <inheritdoc/>
        public override void WriteEncoded(int tag, byte[] bytes, int offset, int length)
        {
            this.WriteByte((byte)tag);
            this.WriteLengthStart(length);
            this.Write(bytes, offset, length);
            this.WriteLengthEnd(length);
        }

        /// <summary>
        /// Writes the leading length bytes for a der object.
        /// </summary>
        /// <param name="length">
        /// The length of the object.
        /// </param>
        private void WriteLengthStart(int length)
        {
            if (length >= 0x1000 || this.ForceIndefinite)
            {
                this.WriteByte(0x80);
            }
            else if (length > 127)
            {
                int size = 1;
                uint val = (uint)length;

                while ((val >>= 8) != 0)
                {
                    size++;
                }

                this.WriteByte((byte)(size | 0x80));

                for (int i = (size - 1) * 8; i >= 0; i -= 8)
                {
                    this.WriteByte((byte)(length >> i));
                }
            }
            else
            {
                this.WriteByte((byte)length);
            }
        }

        /// <summary>
        /// Writes the trailing length bytes of an object. Trailing length bytes
        /// are only written out when the object is serialized as an indefinite-length
        /// object.
        /// </summary>
        /// <param name="length">
        /// The length of an object.
        /// </param>
        private void WriteLengthEnd(
            int length)
        {
            if (length >= 0x1000 || this.ForceIndefinite)
            {
                this.WriteByte(0x00);
                this.WriteByte(0x00);
            }
        }
    }
qmfrederik commented 7 years ago

@peterdettman I hope this gives you a bit more background for PR #66. The subclass I've described above works for me (from a fork of BouncyCastle with #66 applied); if there's a better way to achieve this happy to find out.

jimsch commented 7 years ago

Reading the title, this makes no sense. DER by definition does not support indefinite length encoding. Do do that you need to use BER.

qmfrederik commented 7 years ago

@jimsch Thanks for the feedback, updated the title and description to hopefully be more precise.

Gist is that I'd like to control how objects are BER-encoded. In this case, I want to deviate from DER encoding in a very specify way for compatibility reasons.

jimsch commented 6 years ago

A BER octet string is composed of DER encoded octet strings as children. From my reading it appears that there may be a limit in some circumstances of 1000 bytes for the DER encoded sequence. However if you create the BER octet string from a sequence then that limitation does not exist. This only seems to be used in GenerateOctets, but I don't know where that is being used. I don't generally use BouncyCastle for this purpose. Instead I use my own libraries.

nmoinvaz commented 6 years ago

@jimsch - I found my issue; I was using BER object wrapped in DER sequence or other DER wrapper, and it did not behave the way I thought it should have. When I changed everything on up to use BER it started working. Thanks!