using System; using System.Collections.Generic; using System.Text; using DotNetOpenId.RelyingParty; using System.Globalization; namespace DotNetOpenId { /// /// Represents some unique value that can help prevent replay attacks. /// /// /// When persisting nonce instances, only the and /// properties are significant. Nonces never need to be deserialized. /// public class Nonce { const uint UniqueFragmentLength = 8; /// /// These are the characters that may be chosen from when forming a random nonce, /// per the OpenID 2.0 Authentication spec section 10.1. /// /// /// The following characters are allowed in the spec, but because they can cause validation /// failures with ASP.NET query validation (XSS-detection) they are deliberately left out of /// the set of characters we choose from: < & /// const string allowedCharacters = @"!""#$%'()*+,-./0123456789:;=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~"; // This array of formats is not yet a complete list. static readonly string[] PermissibleDateTimeFormats = { "yyyy-MM-ddTHH:mm:ssZ" }; internal Nonce() : this(DateTime.UtcNow, generateUniqueFragment(), false) { } /// /// Deserializes a nonce from a string passed to us. /// /// /// A nonce in the format described by the OpenID Authentication 2.0 /// spec section 10.1. Specifically, it should be in the format: /// 2005-05-15T17:11:51ZUNIQUE /// /// internal Nonce(string code, bool remoteServerOrigin) { if (string.IsNullOrEmpty(code)) throw new ArgumentNullException("code"); Code = code; int indexOfDateEnd = code.IndexOf("Z", StringComparison.Ordinal); if (indexOfDateEnd < 0) throw new FormatException(Strings.InvalidNonce); CreationDate = DateTime.Parse(code.Substring(0, indexOfDateEnd + 1), CultureInfo.InvariantCulture); UniqueFragment = code.Substring(indexOfDateEnd + 1); this.remoteServerOrigin = remoteServerOrigin; } internal Nonce(DateTime creation, string uniqueFragment, bool remoteServerOrigin) { Code = creation.ToUniversalTime().ToString(PermissibleDateTimeFormats[0], CultureInfo.InvariantCulture) + uniqueFragment; CreationDate = creation.ToUniversalTime(); UniqueFragment = uniqueFragment; this.remoteServerOrigin = remoteServerOrigin; } /// /// The string form of the nonce that can be transmitted with an authentication /// request or response. /// public string Code { get; internal set; } /// /// The UTC date/time this nonce was generated. /// internal DateTime CreationDate { get; set; } internal string UniqueFragment { get; set; } bool remoteServerOrigin; TimeSpan maximumLifetime { get { return remoteServerOrigin ? Protocol.MaximumUserAgentAuthenticationTime + Protocol.MaximumAllowableTimeSkew : Protocol.MaximumUserAgentAuthenticationTime; } } internal TimeSpan Age { get { return DateTime.UtcNow - CreationDate.ToUniversalTime(); } } /// /// Gets whether this nonce is so old it no longer needs to be stored. /// public bool IsExpired { get { return Age > maximumLifetime; } } /// /// Gets the UTC date beyond which this nonce is no longer valid, so storing /// a nonce for replay attack protection is only necessary until this time. /// public DateTime ExpirationDate { get { return CreationDate + maximumLifetime; } } static Random generator = new Random(); static string generateUniqueFragment() { char[] nonce = new char[UniqueFragmentLength]; for (int i = 0; i < nonce.Length; i++) { nonce[i] = allowedCharacters[generator.Next(allowedCharacters.Length)]; } return new string(nonce); } internal void Consume(INonceStore store) { if (IsExpired) throw new OpenIdException(string.Format(CultureInfo.CurrentCulture, Strings.ExpiredNonce, ExpirationDate, DateTime.UtcNow)); // We could store unused nonces and remove them as they are used, or // we could store used nonces and check that they do not previously exist. // To protect against DoS attacks, it's cheaper to store fully-used ones // than half-used ones because it costs the user agent more to get that far. // Replay detection if (!store.TryStoreNonce(this)) { // We've used this nonce before! Replay attack! throw new OpenIdException(Strings.ReplayAttackDetected); } store.ClearExpiredNonces(); } /// /// Tests equality of two objects. /// public override bool Equals(object obj) { Nonce other = obj as Nonce; if (other == null) return false; return Code == other.Code; } /// /// Gets the hash code. /// public override int GetHashCode() { return Code.GetHashCode(); } /// /// Returns the string representation of the . /// This is the property. /// public override string ToString() { return Code; } } }