Refactored test helpers to remove TestSupport and move its functionality into OpenIdT...
[dotnetoauth.git] / src / DotNetOpenAuth.Test / OpenId / AssociationHandshakeTests.cs
blob36f07d1ddf12a840e00a66408777cee64390b4ae
1 //-----------------------------------------------------------------------
2 // <copyright file="AssociationHandshakeTests.cs" company="Andrew Arnott">
3 // Copyright (c) Andrew Arnott. All rights reserved.
4 // </copyright>
5 //-----------------------------------------------------------------------
7 namespace DotNetOpenAuth.Test.OpenId {
8 using System;
9 using DotNetOpenAuth.Messaging;
10 using DotNetOpenAuth.OpenId;
11 using DotNetOpenAuth.OpenId.Messages;
12 using DotNetOpenAuth.OpenId.Provider;
13 using DotNetOpenAuth.OpenId.RelyingParty;
14 using Microsoft.VisualStudio.TestTools.UnitTesting;
16 [TestClass]
17 public class AssociationHandshakeTests : OpenIdTestBase {
18 [TestInitialize]
19 public override void SetUp() {
20 base.SetUp();
23 [TestMethod]
24 public void AssociateUnencrypted() {
25 this.ParameterizedAssociationTest(new Uri("https://host"));
28 [TestMethod]
29 public void AssociateDiffieHellmanOverHttp() {
30 this.ParameterizedAssociationTest(new Uri("http://host"));
33 /// <summary>
34 /// Verifies that the Provider can do Diffie-Hellman over HTTPS.
35 /// </summary>
36 /// <remarks>
37 /// Some OPs out there flatly refuse to do this, and the spec doesn't forbid
38 /// putting the two together, so we verify that DNOI can handle it.
39 /// </remarks>
40 [TestMethod]
41 public void AssociateDiffieHellmanOverHttps() {
42 Protocol protocol = Protocol.V20;
43 OpenIdCoordinator coordinator = new OpenIdCoordinator(
44 rp => {
45 // We have to formulate the associate request manually,
46 // since the DNOI RP won't voluntarily use DH on HTTPS.
47 AssociateDiffieHellmanRequest request = new AssociateDiffieHellmanRequest(protocol.Version, new Uri("https://Provider"));
48 request.AssociationType = protocol.Args.SignatureAlgorithm.HMAC_SHA256;
49 request.SessionType = protocol.Args.SessionType.DH_SHA256;
50 request.InitializeRequest();
51 var response = rp.Channel.Request<AssociateSuccessfulResponse>(request);
52 Assert.IsNotNull(response);
53 Assert.AreEqual(request.AssociationType, response.AssociationType);
54 Assert.AreEqual(request.SessionType, response.SessionType);
56 AutoProvider);
57 coordinator.Run();
60 /// <summary>
61 /// Verifies that the RP and OP can renegotiate an association type if the RP's
62 /// initial request for an association is for a type the OP doesn't support.
63 /// </summary>
64 [TestMethod]
65 public void AssociateRenegotiateBitLength() {
66 Protocol protocol = Protocol.V20;
68 // The strategy is to make a simple request of the RP to establish an association,
69 // and to more carefully observe the Provider-side of things to make sure that both
70 // the OP and RP are behaving as expected.
71 OpenIdCoordinator coordinator = new OpenIdCoordinator(
72 rp => {
73 var opDescription = new ProviderEndpointDescription(OPUri, protocol.Version);
74 Association association = rp.AssociationManager.GetOrCreateAssociation(opDescription);
75 Assert.IsNotNull(association, "Association failed to be created.");
76 Assert.AreEqual(protocol.Args.SignatureAlgorithm.HMAC_SHA1, association.GetAssociationType(protocol));
78 op => {
79 op.SecuritySettings.MaximumHashBitLength = 160; // Force OP to reject HMAC-SHA256
81 // Receive initial request for an HMAC-SHA256 association.
82 AutoResponsiveRequest req = (AutoResponsiveRequest) op.GetRequest();
83 AutoResponsiveRequest_Accessor reqAccessor = AutoResponsiveRequest_Accessor.AttachShadow(req);
84 AssociateRequest associateRequest = (AssociateRequest)reqAccessor.RequestMessage;
85 Assert.AreEqual(protocol.Args.SignatureAlgorithm.HMAC_SHA256, associateRequest.AssociationType);
87 // Ensure that the response is a suggestion that the RP try again with HMAC-SHA1
88 AssociateUnsuccessfulResponse renegotiateResponse = (AssociateUnsuccessfulResponse)reqAccessor.ResponseMessage;
89 Assert.AreEqual(protocol.Args.SignatureAlgorithm.HMAC_SHA1, renegotiateResponse.AssociationType);
90 req.Response.Send();
92 // Receive second attempt request for an HMAC-SHA1 association.
93 req = (AutoResponsiveRequest)op.GetRequest();
94 reqAccessor = AutoResponsiveRequest_Accessor.AttachShadow(req);
95 associateRequest = (AssociateRequest)reqAccessor.RequestMessage;
96 Assert.AreEqual(protocol.Args.SignatureAlgorithm.HMAC_SHA1, associateRequest.AssociationType);
98 // Ensure that the response is a success response.
99 AssociateSuccessfulResponse successResponse = (AssociateSuccessfulResponse)reqAccessor.ResponseMessage;
100 Assert.AreEqual(protocol.Args.SignatureAlgorithm.HMAC_SHA1, successResponse.AssociationType);
101 req.Response.Send();
103 coordinator.Run();
106 /// <summary>
107 /// Verifies that the OP rejects an associate request
108 /// when the HMAC and DH bit lengths do not match.
109 /// </summary>
110 [TestMethod]
111 public void OPRejectsMismatchingAssociationAndSessionTypes() {
112 Protocol protocol = Protocol.V20;
113 OpenIdCoordinator coordinator = new OpenIdCoordinator(
114 rp => {
115 // We have to formulate the associate request manually,
116 // since the DNOI RP won't voluntarily mismatch the association and session types.
117 AssociateDiffieHellmanRequest request = new AssociateDiffieHellmanRequest(protocol.Version, new Uri("https://Provider"));
118 request.AssociationType = protocol.Args.SignatureAlgorithm.HMAC_SHA256;
119 request.SessionType = protocol.Args.SessionType.DH_SHA1;
120 request.InitializeRequest();
121 var response = rp.Channel.Request<AssociateUnsuccessfulResponse>(request);
122 Assert.IsNotNull(response);
123 Assert.AreEqual(protocol.Args.SignatureAlgorithm.HMAC_SHA1, response.AssociationType);
124 Assert.AreEqual(protocol.Args.SessionType.DH_SHA1, response.SessionType);
126 AutoProvider);
127 coordinator.Run();
130 /// <summary>
131 /// Verifies that the RP quietly rejects an OP that suggests an unknown association type.
132 /// </summary>
133 [TestMethod]
134 public void RPRejectsUnrecognizedAssociationType() {
135 Protocol protocol = Protocol.V20;
136 OpenIdCoordinator coordinator = new OpenIdCoordinator(
137 rp => {
138 var association = rp.AssociationManager.GetOrCreateAssociation(new ProviderEndpointDescription(OPUri, protocol.Version));
139 Assert.IsNull(association, "The RP should quietly give up when the OP misbehaves.");
141 op => {
142 // Receive initial request.
143 var request = op.Channel.ReadFromRequest<AssociateRequest>();
145 // Send a response that suggests a foreign association type.
146 AssociateUnsuccessfulResponse renegotiateResponse = new AssociateUnsuccessfulResponse(request);
147 renegotiateResponse.AssociationType = "HMAC-UNKNOWN";
148 renegotiateResponse.SessionType = "DH-UNKNOWN";
149 op.Channel.Send(renegotiateResponse);
151 coordinator.Run();
154 /// <summary>
155 /// Verifies that the RP quietly rejects an OP that suggests an no encryption over an HTTP channel.
156 /// </summary>
157 [TestMethod]
158 public void RPRejectsUnencryptedSuggestion() {
159 Protocol protocol = Protocol.V20;
160 OpenIdCoordinator coordinator = new OpenIdCoordinator(
161 rp => {
162 var association = rp.AssociationManager.GetOrCreateAssociation(new ProviderEndpointDescription(OPUri, protocol.Version));
163 Assert.IsNull(association, "The RP should quietly give up when the OP misbehaves.");
165 op => {
166 // Receive initial request.
167 var request = op.Channel.ReadFromRequest<AssociateRequest>();
169 // Send a response that suggests a no encryption.
170 AssociateUnsuccessfulResponse renegotiateResponse = new AssociateUnsuccessfulResponse(request);
171 renegotiateResponse.AssociationType = protocol.Args.SignatureAlgorithm.HMAC_SHA1;
172 renegotiateResponse.SessionType = protocol.Args.SessionType.NoEncryption;
173 op.Channel.Send(renegotiateResponse);
175 coordinator.Run();
178 /// <summary>
179 /// Verifies that the RP rejects an associate renegotiate request
180 /// when the HMAC and DH bit lengths do not match.
181 /// </summary>
182 [TestMethod]
183 public void RPRejectsMismatchingAssociationAndSessionBitLengths() {
184 Protocol protocol = Protocol.V20;
185 OpenIdCoordinator coordinator = new OpenIdCoordinator(
186 rp => {
187 var association = rp.AssociationManager.GetOrCreateAssociation(new ProviderEndpointDescription(OPUri, protocol.Version));
188 Assert.IsNull(association, "The RP should quietly give up when the OP misbehaves.");
190 op => {
191 // Receive initial request.
192 var request = op.Channel.ReadFromRequest<AssociateRequest>();
194 // Send a mismatched response
195 AssociateUnsuccessfulResponse renegotiateResponse = new AssociateUnsuccessfulResponse(request);
196 renegotiateResponse.AssociationType = protocol.Args.SignatureAlgorithm.HMAC_SHA1;
197 renegotiateResponse.SessionType = protocol.Args.SessionType.DH_SHA256;
198 op.Channel.Send(renegotiateResponse);
200 coordinator.Run();
203 /// <summary>
204 /// Verifies that the RP cannot get caught in an infinite loop if a bad OP
205 /// keeps sending it association retry messages.
206 /// </summary>
207 [TestMethod]
208 public void RPOnlyRenegotiatesOnce() {
209 Protocol protocol = Protocol.V20;
210 OpenIdCoordinator coordinator = new OpenIdCoordinator(
211 rp => {
212 var association = rp.AssociationManager.GetOrCreateAssociation(new ProviderEndpointDescription(OPUri, protocol.Version));
213 Assert.IsNull(association, "The RP should quietly give up when the OP misbehaves.");
215 op => {
216 // Receive initial request.
217 var request = op.Channel.ReadFromRequest<AssociateRequest>();
219 // Send a renegotiate response
220 AssociateUnsuccessfulResponse renegotiateResponse = new AssociateUnsuccessfulResponse(request);
221 renegotiateResponse.AssociationType = protocol.Args.SignatureAlgorithm.HMAC_SHA1;
222 renegotiateResponse.SessionType = protocol.Args.SessionType.DH_SHA1;
223 op.Channel.Send(renegotiateResponse);
225 // Receive second-try
226 request = op.Channel.ReadFromRequest<AssociateRequest>();
228 // Send ANOTHER renegotiate response, at which point the DNOI RP should give up.
229 renegotiateResponse = new AssociateUnsuccessfulResponse(request);
230 renegotiateResponse.AssociationType = protocol.Args.SignatureAlgorithm.HMAC_SHA256;
231 renegotiateResponse.SessionType = protocol.Args.SessionType.DH_SHA256;
232 op.Channel.Send(renegotiateResponse);
234 coordinator.Run();
237 /// <summary>
238 /// Verifies security settings limit RP's acceptance of OP's counter-suggestion
239 /// </summary>
240 [TestMethod]
241 public void AssociateRenegotiateLimitedByRPSecuritySettings() {
242 Protocol protocol = Protocol.V20;
243 OpenIdCoordinator coordinator = new OpenIdCoordinator(
244 rp => {
245 rp.SecuritySettings.MinimumHashBitLength = 256;
246 var association = rp.AssociationManager.GetOrCreateAssociation(new ProviderEndpointDescription(OPUri, protocol.Version));
247 Assert.IsNull(association, "No association should have been created when RP and OP could not agree on association strength.");
249 op => {
250 op.SecuritySettings.MaximumHashBitLength = 160;
251 AutoProvider(op);
253 coordinator.Run();
256 /// <summary>
257 /// Verifies that the RP can recover from an invalid or non-existent
258 /// response from the OP, for example in the HTTP timeout case.
259 /// </summary>
260 [TestMethod]
261 public void AssociateQuietlyFailsAfterHttpError() {
262 this.MockResponder.RegisterMockNotFound(OPUri);
263 var rp = this.CreateRelyingParty();
264 var association = rp.AssociationManager.GetOrCreateAssociation(new ProviderEndpointDescription(OPUri, Protocol.V20.Version));
265 Assert.IsNull(association);
268 /// <summary>
269 /// Runs a parameterized association flow test using all supported OpenID versions.
270 /// </summary>
271 /// <param name="opEndpoint">The OP endpoint to simulate using.</param>
272 private void ParameterizedAssociationTest(Uri opEndpoint) {
273 foreach (Protocol protocol in Protocol.AllPracticalVersions) {
274 var endpoint = new ProviderEndpointDescription(opEndpoint, protocol.Version);
275 var associationType = protocol.Version.Major < 2 ? protocol.Args.SignatureAlgorithm.HMAC_SHA1 : protocol.Args.SignatureAlgorithm.HMAC_SHA256;
276 this.ParameterizedAssociationTest(endpoint, associationType);
280 /// <summary>
281 /// Runs a parameterized association flow test.
282 /// </summary>
283 /// <param name="opDescription">
284 /// The description of the Provider that the relying party uses to formulate the request.
285 /// The specific host is not used, but the scheme is significant.
286 /// </param>
287 /// <param name="expectedAssociationType">
288 /// The value of the openid.assoc_type parameter expected,
289 /// or null if a failure is anticipated.
290 /// </param>
291 private void ParameterizedAssociationTest(
292 ProviderEndpointDescription opDescription,
293 string expectedAssociationType) {
294 Protocol protocol = Protocol.Lookup(opDescription.ProtocolVersion);
295 bool expectSuccess = expectedAssociationType != null;
296 bool expectDiffieHellman = !opDescription.Endpoint.IsTransportSecure();
297 Association rpAssociation = null, opAssociation;
298 AssociateSuccessfulResponse associateSuccessfulResponse = null;
299 AssociateUnsuccessfulResponse associateUnsuccessfulResponse = null;
300 OpenIdCoordinator coordinator = new OpenIdCoordinator(
301 rp => {
302 rp.SecuritySettings = this.RelyingPartySecuritySettings;
303 rpAssociation = rp.AssociationManager.GetOrCreateAssociation(opDescription);
305 op => {
306 op.SecuritySettings = this.ProviderSecuritySettings;
307 IRequest req = op.GetRequest();
308 Assert.IsNotNull(req, "Expected incoming request but did not receive it.");
309 Assert.IsTrue(req.IsResponseReady);
310 UserAgentResponse resp = req.Response;
311 resp.Send();
313 coordinator.IncomingMessageFilter = message => {
314 Assert.AreSame(opDescription.ProtocolVersion, message.Version, "The message was recognized as version {0} but was expected to be {1}.", message.Version, opDescription.ProtocolVersion);
315 var associateSuccess = message as AssociateSuccessfulResponse;
316 var associateFailed = message as AssociateUnsuccessfulResponse;
317 if (associateSuccess != null) {
318 associateSuccessfulResponse = associateSuccess;
320 if (associateFailed != null) {
321 associateUnsuccessfulResponse = associateFailed;
324 coordinator.OutgoingMessageFilter = message => {
325 Assert.AreSame(opDescription.ProtocolVersion, message.Version, "The message was for version {0} but was expected to be for {1}.", message.Version, opDescription.ProtocolVersion);
327 coordinator.Run();
329 var associationManagerAccessor = AssociationManager_Accessor.AttachShadow(coordinator.RelyingParty.AssociationManager);
331 if (expectSuccess) {
332 Assert.IsNotNull(rpAssociation);
333 Assert.AreSame(rpAssociation, associationManagerAccessor.associationStore.GetAssociation(opDescription.Endpoint, rpAssociation.Handle));
334 opAssociation = coordinator.Provider.AssociationStore.GetAssociation(AssociationRelyingPartyType.Smart, rpAssociation.Handle);
335 Assert.IsNotNull(opAssociation, "The Provider should have stored the association.");
337 Assert.AreEqual(opAssociation.Handle, rpAssociation.Handle);
338 Assert.AreEqual(expectedAssociationType, rpAssociation.GetAssociationType(protocol));
339 Assert.AreEqual(expectedAssociationType, opAssociation.GetAssociationType(protocol));
340 Assert.IsTrue(Math.Abs(opAssociation.SecondsTillExpiration - rpAssociation.SecondsTillExpiration) < 60);
341 Assert.IsTrue(MessagingUtilities.AreEquivalent(opAssociation.SecretKey, rpAssociation.SecretKey));
343 if (expectDiffieHellman) {
344 Assert.IsInstanceOfType(associateSuccessfulResponse, typeof(AssociateDiffieHellmanResponse));
345 var diffieHellmanResponse = (AssociateDiffieHellmanResponse)associateSuccessfulResponse;
346 Assert.IsFalse(MessagingUtilities.AreEquivalent(diffieHellmanResponse.EncodedMacKey, rpAssociation.SecretKey), "Key should have been encrypted.");
347 } else {
348 Assert.IsInstanceOfType(associateSuccessfulResponse, typeof(AssociateUnencryptedResponse));
349 var unencryptedResponse = (AssociateUnencryptedResponse)associateSuccessfulResponse;
351 } else {
352 Assert.IsNull(associationManagerAccessor.associationStore.GetAssociation(opDescription.Endpoint));
353 Assert.IsNull(coordinator.Provider.AssociationStore.GetAssociation(AssociationRelyingPartyType.Smart));