summaryrefslogtreecommitdiffstats
path: root/src/DotNetOpenId/Provider/Signatory.cs
blob: eaf3d219520c1bfcb6552b74f71e70be707eaffc (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
using System;
using System.Collections;
using System.Collections.Specialized;
using System.Text;
using System.Security.Cryptography;
using System.Collections.Generic;
using IProviderAssociationStore = DotNetOpenId.IAssociationStore<DotNetOpenId.AssociationRelyingPartyType>;
using System.Diagnostics;

namespace DotNetOpenId.Provider {
	/// <summary>
	/// Signs things.
	/// </summary>
	internal class Signatory {
		/// <summary>
		/// The duration any association and secret key the Provider generates will be good for.
		/// </summary>
		static readonly TimeSpan smartAssociationLifetime = TimeSpan.FromDays(14);
		/// <summary>
		/// The duration a secret key used for signing dumb client requests will be good for.
		/// </summary>
		static readonly TimeSpan dumbSecretLifetime = TimeSpan.FromMinutes(5);

		/// <summary>
		/// The store for shared secrets.
		/// </summary>
		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<string, string> signed_pairs, IList<string> 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);
		}
	}
}