コード例 #1
0
ファイル: SecurityToken.cs プロジェクト: Khrove/LowPay
        /// <summary>
        /// Converts the token to its string representation, including packing, compressing, encrypting,
        /// and digitally signing the token's contents and encoding to a web-safe Base64 variant.
        /// </summary>
        public override string ToString()
        {
            SecurityTokenFlags Flags = SecurityTokenFlags.None;

            // Pack the key/value pairs in the dictionary into a single byte array, each
            // value being encoded in UTF-8 (full Unicode support, with efficient storage
            // for US-ASCII data).  Note that each string is limited to 65535 chars, to
            // save the extra 4 bytes per pair for encoding the extra zeros for a 32-bit
            // value, considering that any value that large is excessive for the intended
            // application.
            List <byte> InnerPayload = new List <byte>();

            foreach (string S in Data.Keys.OrderBy(K => K).SelectMany(K => new string[] { K, Data[K] }))
            {
                byte[] StrBytes = Encoding.UTF8.GetBytes(S);
                if (StrBytes.Length > UInt16.MaxValue)
                {
                    throw new SecurityTokenException("Each key/value in a " + typeof(SecurityToken).Name + " cannot "
                                                     + "exceed " + UInt16.MaxValue.ToString() + " bytes in UTF-8 representation.");
                }
                InnerPayload.AddRange(BitConverter.GetBytes((UInt16)StrBytes.Length).EndianFlip());
                InnerPayload.AddRange(StrBytes);
            }

            // If an expiration date is set and the value is valid, set the expiration flag and
            // encode an expiration date at the end of the payload.  Expiration date is encoded
            // as a 32-bit unsigned Unix date with second precision, for simplicity.
            if ((Expires < DateTime.MaxValue) && (Expires >= Epoch))
            {
                Flags |= SecurityTokenFlags.ExpireDate;
                InnerPayload.AddRange(BitConverter.GetBytes((UInt32)(Expires - Epoch).TotalSeconds).EndianFlip());
            }

            // Automatically attempt to compress the payload.  If the payload successfully compresses to a
            // smaller size than the original, set the compression flag and use the compressed payload.
            // This may make the encoded token at least look a little smaller, though compression ratios
            // will be unimpressive on data this small.  Note that compression is done before adding the
            // validation HMAC, as (1) HMAC data doesn't compress well, and (2) we want to be able to validate
            // the compressed payload before attempting to engage the decompressor on the other side, as piping
            // untrusted data into a decompressor may be a security or DoS risk.
            List <byte> PackedPayload = InnerPayload.DeflateCompress(Ionic.Zlib.CompressionLevel.BestCompression).ToList();

            if (PackedPayload.Count < InnerPayload.Count)
            {
                InnerPayload = PackedPayload;
                Flags       |= SecurityTokenFlags.Deflated;
            }

            // Encrypt the inner payload with the same key as used for signing.  Try to use CTS, if the data
            // is long enough, as it doesn't add any overhead, but fall back on CBC w/ PKCS7 padding for very
            // short messages.
            SymmetricAlgorithm Alg = CreateCipher(Key);

            if (InnerPayload.Count >= (Alg.BlockSize / 8))
            {
                InnerPayload = Alg.CTSEncrypt(InnerPayload.ToArray()).ToList();
                Flags       |= SecurityTokenFlags.EncryptedCTS;
            }
            else
            {
                InnerPayload = Alg.CBCEncrypt(InnerPayload.ToArray()).ToList();
                Flags       |= SecurityTokenFlags.EncryptedCBC;
            }

            // Reverse the encrypted content and re-encrypt it, to cause any plaintext change to propagate
            // to every bit of output.  This should theoretically provide at least a little hardening against
            // a chosen-plaintext attack.  We can always use CTS mode here, as the content at this point is
            // guaranteed to be long enough for CTS mode (a previous CBC encryption would have expanded it).
            InnerPayload.Reverse();
            InnerPayload = Alg.CTSEncrypt(InnerPayload.ToArray()).ToList();

            // Concatenate the message flags, content, and validation HMAC and return the result encoded
            // in a format appropriate for use in a URL QueryString.
            List <byte> Final = new List <byte>();

            Final.Add((byte)Flags);
            Final.AddRange(InnerPayload);
            Final.AddRange(Final.ToArray().GetHMACSHA1Bytes(Key));
            return(Final.ToArray().Base64WebSafeEncode());
        }