using System; using System.Collections; using System.Collections.Specialized; using System.Text; using System.Security.Cryptography; using System.Collections.Generic; using IProviderAssociationStore = DotNetOpenId.IAssociationStore; using System.Diagnostics; namespace DotNetOpenId.Provider { /// /// Signs things. /// internal class Signatory { /// /// The duration any association and secret key the Provider generates will be good for. /// static readonly TimeSpan smartAssociationLifetime = TimeSpan.FromDays(14); /// /// The duration a secret key used for signing dumb client requests will be good for. /// static readonly TimeSpan dumbSecretLifetime = TimeSpan.FromMinutes(5); /// /// The store for shared secrets. /// IProviderAssociationStore store; public Signatory(IProviderAssociationStore store) { if (store == null) throw new ArgumentNullException("store"); this.store = store; } public void Sign(EncodableResponse response) { Association assoc; string assoc_handle = response.PreferredAssociationHandle; if (!string.IsNullOrEmpty(assoc_handle)) { assoc = GetAssociation(assoc_handle, AssociationRelyingPartyType.Smart); if (assoc == null) { Logger.WarnFormat("No associaton found with assoc_handle {0}. Setting invalidate_handle and creating new Association.", assoc_handle); response.Fields[response.Protocol.openidnp.invalidate_handle] = assoc_handle; assoc = CreateAssociation(AssociationRelyingPartyType.Dumb, null); } } else { assoc = this.CreateAssociation(AssociationRelyingPartyType.Dumb, null); Logger.Debug("No assoc_handle supplied. Creating new association."); } response.Fields[response.Protocol.openidnp.assoc_handle] = assoc.Handle; response.Signed.Add(response.Protocol.openidnp.assoc_handle); response.Fields[response.Protocol.openidnp.signed] = String.Join(",", response.Signed.ToArray()); response.Fields[response.Protocol.openidnp.sig] = Convert.ToBase64String(assoc.Sign(response.Fields, response.Signed, string.Empty)); } public virtual bool Verify(string assoc_handle, string signature, IDictionary signed_pairs, IList signedKeyOrder) { Association assoc = GetAssociation(assoc_handle, AssociationRelyingPartyType.Dumb); if (assoc == null) { Logger.ErrorFormat("Signature verification failed. No association with handle {0} found ", assoc_handle); return false; } string expected_sig = Convert.ToBase64String(assoc.Sign(signed_pairs, signedKeyOrder)); if (signature != expected_sig) { Logger.ErrorFormat("Expected signature is '{0}'. Actual signature is '{1}' ", expected_sig, signature); } return expected_sig.Equals(signature, StringComparison.Ordinal); } public virtual Association CreateAssociation(AssociationRelyingPartyType associationType, OpenIdProvider provider) { if (provider == null && associationType == AssociationRelyingPartyType.Smart) throw new ArgumentNullException("provider", "For Smart associations, the provider must be given."); string assoc_type; Protocol associationProtocol; if (associationType == AssociationRelyingPartyType.Dumb) { // We'll just use the best association available. associationProtocol = Protocol.Default; assoc_type = associationProtocol.Args.SignatureAlgorithm.Best; } else { associationProtocol = provider.Protocol; assoc_type = Util.GetRequiredArg(provider.Query, provider.Protocol.openid.assoc_type); Debug.Assert(Array.IndexOf(provider.Protocol.Args.SignatureAlgorithm.All, assoc_type) >= 0, "This should have been checked by our caller."); } int secretLength = HmacShaAssociation.GetSecretLength(associationProtocol, assoc_type); RNGCryptoServiceProvider generator = new RNGCryptoServiceProvider(); byte[] secret = new byte[secretLength]; byte[] uniq_bytes = new byte[4]; string uniq; string handle; HmacShaAssociation assoc; generator.GetBytes(secret); generator.GetBytes(uniq_bytes); uniq = Convert.ToBase64String(uniq_bytes); double seconds = DateTime.UtcNow.Subtract(Association.UnixEpoch).TotalSeconds; handle = "{{" + assoc_type + "}{" + seconds + "}{" + uniq + "}"; TimeSpan lifeSpan = associationType == AssociationRelyingPartyType.Dumb ? dumbSecretLifetime : smartAssociationLifetime; assoc = HmacShaAssociation.Create(secretLength, handle, secret, lifeSpan); store.StoreAssociation(associationType, assoc); return assoc; } public virtual Association GetAssociation(string assoc_handle, AssociationRelyingPartyType associationType) { if (assoc_handle == null) throw new ArgumentNullException("assoc_handle"); Association assoc = store.GetAssociation(associationType, assoc_handle); if (assoc == null || assoc.IsExpired) { Logger.ErrorFormat("Association {0} expired or not in store.", assoc_handle); store.RemoveAssociation(associationType, assoc_handle); assoc = null; } return assoc; } public virtual void Invalidate(string assoc_handle, AssociationRelyingPartyType associationType) { Logger.DebugFormat("Invalidating association '{0}'.", assoc_handle); store.RemoveAssociation(associationType, assoc_handle); } } }