/// <summary> /// Parses a string containing a token and returns the token it contains, /// if that token is valid. /// </summary> /// <param name="key">The shared secret key with which the token was originally signed.</param> /// <param name="token">The original token string representation.</param> public static SecurityToken FromString(string key, string token) { // Decode the incoming string and do a basic sanity check. byte[] RawData = token.Base64WebSafeDecode(); if (RawData.Length < (HMAC_SIZE + 1)) { throw new SecurityTokenInvalidException("String is too small to be a valid " + typeof(SecurityToken).Name); } // Recompute the HMAC using the provided key (as the key is a secret and // cannot be encoded into the token itself), and make sure it matches. If // not, then the token is corrupt and cannot be trusted. byte[] RawHMAC = RawData.Skip(RawData.Length - HMAC_SIZE).ToArray(); RawData = RawData.Take(RawData.Length - HMAC_SIZE).ToArray(); if (RawData.GetHMACSHA1Bytes(key).Base64Encode() != RawHMAC.Base64Encode()) { throw new SecurityTokenInvalidException("HMAC validation failed."); } // Extract the flags from the payload. SecurityTokenFlags Flags = (SecurityTokenFlags)RawData.First(); RawData = RawData.Skip(1).ToArray(); // If data was encrypted, decrypt it. SymmetricAlgorithm Alg = CreateCipher(key); if ((Flags & SecurityTokenFlags.EncryptedCTS) != 0) { RawData = Alg.CTSDecrypt(Alg.CTSDecrypt(RawData).Reverse().ToArray()); } else if ((Flags & SecurityTokenFlags.EncryptedCBC) != 0) { RawData = Alg.CBCDecrypt(Alg.CTSDecrypt(RawData).Reverse().ToArray()); } // If the data was originally deflated, decompress it. if ((Flags & SecurityTokenFlags.Deflated) != 0) { RawData = RawData.DeflateDecompress().ToArray(); } // If the data contains an expiration date, then decode the expiration date // and make sure the token has not expired. DateTime Exp = DateTime.MaxValue; if ((Flags & SecurityTokenFlags.ExpireDate) != 0) { Exp = Epoch.AddSeconds(BitConverter.ToUInt32(RawData.Skip(RawData.Length - sizeof(UInt32)).EndianFlip().ToArray(), 0)); if (Exp < DateTime.UtcNow) { throw new SecurityTokenExpiredException("Token has expired."); } RawData = RawData.Take(RawData.Length - sizeof(UInt32)).ToArray(); } // The remaining data is the key/value pair data, packed as an even number of // strings, each prefixed with a big-endian uint16 length specifier, followed // by that many bytes of UTF-8-encoded string data. After unpacking strings, // rebuild the original dictionary. Queue <string> Values = new Queue <string>(); while (RawData.Length > 0) { ushort StrLen = BitConverter.ToUInt16(RawData.Take(2).EndianFlip().ToArray(), 0); Values.Enqueue(Encoding.UTF8.GetString(RawData.Skip(2).Take(StrLen).ToArray())); RawData = RawData.Skip(StrLen + 2).ToArray(); } Dictionary <string, string> NewData = new Dictionary <string, string>(); while (Values.Count > 0) { NewData[Values.Dequeue()] = Values.Dequeue(); } // Return a security token containing the original expiration, key, and // payload data. Note that if any of the checks above fails (payload validation, // matching key, expiration) then an exception will be thrown instead of // returning any token. return(new SecurityToken { Expires = Exp, Key = key, Data = NewData }); }