using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Security.Cryptography;
namespace Janrain.OpenId.Consumer {
///
/// A state-containing bit of non-confidential data that is sent to the
/// user agent as part of the return_to URL so we can read from it later.
///
class Token {
static readonly TimeSpan maximumLifetime = TimeSpan.FromMinutes(5);
public static readonly string TokenKey = "token";
public Uri IdentityUrl;
public Uri ServerId;
public Uri ServerUrl;
public Token(ServiceEndpoint serviceEndpoint)
: this(serviceEndpoint.IdentityUrl, serviceEndpoint.ServerId, serviceEndpoint.ServerUrl) {
}
Token(Uri identityUrl, Uri serverId, Uri serverUrl) {
IdentityUrl = identityUrl;
ServerId = serverId;
ServerUrl = serverUrl;
}
delegate void DataWriter(string data, bool writeSeparator);
///
/// Serializes this instance as a string that can be
/// included as part of a return_to variable in a querystring.
/// This string is cryptographically signed to protect against tampering.
///
public string Serialize(byte[] signingSecretKey) {
string timestamp = DateTime.UtcNow.ToFileTimeUtc().ToString();
using (MemoryStream ms = new MemoryStream())
using (HashAlgorithm sha1 = new HMACSHA1(signingSecretKey))
using (CryptoStream sha1Stream = new CryptoStream(ms, sha1, CryptoStreamMode.Write)) {
DataWriter writeData = delegate(string value, bool writeSeparator) {
byte[] buffer = Encoding.ASCII.GetBytes(value);
sha1Stream.Write(buffer, 0, buffer.Length);
if (writeSeparator)
sha1Stream.WriteByte(0);
};
writeData(timestamp, true);
writeData(IdentityUrl.AbsoluteUri, true);
writeData(ServerId.AbsoluteUri, true);
writeData(ServerUrl.AbsoluteUri, false);
sha1Stream.Flush();
sha1Stream.FlushFinalBlock();
byte[] hash = sha1.Hash;
byte[] data = new byte[sha1.HashSize / 8 + ms.Length];
Buffer.BlockCopy(hash, 0, data, 0, hash.Length);
Buffer.BlockCopy(ms.ToArray(), 0, data, hash.Length, (int)ms.Length);
return CryptUtil.ToBase64String(data);
}
}
public static Token Deserialize(string token, byte[] signingSecretKey) {
byte[] tok = Convert.FromBase64String(token);
if (tok.Length < 20)
throw new FailureException(null, "Failed while reading token.");
byte[] sig = new byte[20];
Buffer.BlockCopy(tok, 0, sig, 0, 20);
HMACSHA1 hmac = new HMACSHA1(signingSecretKey);
byte[] newSig = hmac.ComputeHash(tok, 20, tok.Length - 20);
for (int i = 0; i < sig.Length; i++)
if (sig[i] != newSig[i])
throw new FailureException(null, "Token failed signature verification.");
List items = new List();
int prev = 20;
int idx;
while ((idx = Array.IndexOf(tok, 0, prev)) > -1) {
items.Add(Encoding.ASCII.GetString(tok, prev, idx - prev));
prev = idx + 1;
}
if (prev < tok.Length)
items.Add(Encoding.ASCII.GetString(tok, prev, tok.Length - prev));
//# Check if timestamp has expired
DateTime ts = DateTime.FromFileTimeUtc(Convert.ToInt64(items[0]));
ts += maximumLifetime;
if (ts < DateTime.UtcNow)
throw new FailureException(null, "Token has expired.");
items.RemoveAt(0);
return new Token(new Uri(items[0]), new Uri(items[1]), new Uri(items[2]));
}
}
}