Enabled and added several more associate renegotiate tests.
[dotnetoauth.git] / src / DotNetOpenAuth.Test / OpenId / AssociationHandshakeTests.cs
blob509bf9af619c10e194ad052d62b20990e62cb283
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 Microsoft.VisualStudio.TestTools.UnitTesting;
15 [TestClass]
16 public class AssociationHandshakeTests : OpenIdTestBase {
17 [TestInitialize]
18 public override void SetUp() {
19 base.SetUp();
22 [TestMethod]
23 public void AssociateUnencrypted() {
24 this.ParameterizedAssociationTest(new Uri("https://host"));
27 [TestMethod]
28 public void AssociateDiffieHellmanOverHttp() {
29 this.ParameterizedAssociationTest(new Uri("http://host"));
32 /// <summary>
33 /// Verifies that the Provider can do Diffie-Hellman over HTTPS.
34 /// </summary>
35 /// <remarks>
36 /// Some OPs out there flatly refuse to do this, and the spec doesn't forbid
37 /// putting the two together, so we verify that DNOI can handle it.
38 /// </remarks>
39 [TestMethod]
40 public void AssociateDiffieHellmanOverHttps() {
41 Protocol protocol = Protocol.V20;
42 OpenIdCoordinator coordinator = new OpenIdCoordinator(
43 rp => {
44 // We have to formulate the associate request manually,
45 // since the DNOI RP won't voluntarily use DH on HTTPS.
46 AssociateDiffieHellmanRequest request = new AssociateDiffieHellmanRequest(protocol.Version, new Uri("https://Provider"));
47 request.AssociationType = protocol.Args.SignatureAlgorithm.HMAC_SHA256;
48 request.SessionType = protocol.Args.SessionType.DH_SHA256;
49 request.InitializeRequest();
50 var response = rp.Channel.Request<AssociateSuccessfulResponse>(request);
51 Assert.IsNotNull(response);
52 Assert.AreEqual(request.AssociationType, response.AssociationType);
53 Assert.AreEqual(request.SessionType, response.SessionType);
55 TestSupport.AutoProvider);
56 coordinator.Run();
59 /// <summary>
60 /// Verifies that the RP and OP can renegotiate an association type if the RP's
61 /// initial request for an association is for a type the OP doesn't support.
62 /// </summary>
63 [TestMethod]
64 public void AssociateRenegotiateBitLength() {
65 Protocol protocol = Protocol.V20;
67 // The strategy is to make a simple request of the RP to establish an association,
68 // and to more carefully observe the Provider-side of things to make sure that both
69 // the OP and RP are behaving as expected.
70 OpenIdCoordinator coordinator = new OpenIdCoordinator(
71 rp => {
72 var opDescription = new ProviderEndpointDescription(ProviderUri, protocol.Version);
73 Association association = rp.GetOrCreateAssociation(opDescription);
74 Assert.AreEqual(protocol.Args.SignatureAlgorithm.HMAC_SHA1, association.GetAssociationType(protocol));
76 op => {
77 op.SecuritySettings.MaximumHashBitLength = 160; // Force OP to reject HMAC-SHA256
79 // Receive initial request for an HMAC-SHA256 association.
80 AutoResponsiveRequest req = (AutoResponsiveRequest) op.GetRequest();
81 AutoResponsiveRequest_Accessor reqAccessor = AutoResponsiveRequest_Accessor.AttachShadow(req);
82 AssociateRequest associateRequest = (AssociateRequest)reqAccessor.RequestMessage;
83 Assert.AreEqual(protocol.Args.SignatureAlgorithm.HMAC_SHA256, associateRequest.AssociationType);
85 // Ensure that the response is a suggestion that the RP try again with HMAC-SHA1
86 AssociateUnsuccessfulResponse renegotiateResponse = (AssociateUnsuccessfulResponse)reqAccessor.ResponseMessage;
87 Assert.AreEqual(protocol.Args.SignatureAlgorithm.HMAC_SHA1, renegotiateResponse.AssociationType);
88 req.Response.Send();
90 // Receive second attempt request for an HMAC-SHA1 association.
91 req = (AutoResponsiveRequest)op.GetRequest();
92 reqAccessor = AutoResponsiveRequest_Accessor.AttachShadow(req);
93 associateRequest = (AssociateRequest)reqAccessor.RequestMessage;
94 Assert.AreEqual(protocol.Args.SignatureAlgorithm.HMAC_SHA1, associateRequest.AssociationType);
96 // Ensure that the response is a success response.
97 AssociateSuccessfulResponse successResponse = (AssociateSuccessfulResponse)reqAccessor.ResponseMessage;
98 Assert.AreEqual(protocol.Args.SignatureAlgorithm.HMAC_SHA1, successResponse.AssociationType);
99 req.Response.Send();
101 coordinator.Run();
104 /// <summary>
105 /// Verifies that the OP rejects an associate request
106 /// when the HMAC and DH bit lengths do not match.
107 /// </summary>
108 [TestMethod]
109 public void OPRejectsMismatchingAssociationAndSessionTypes() {
110 Protocol protocol = Protocol.V20;
111 OpenIdCoordinator coordinator = new OpenIdCoordinator(
112 rp => {
113 // We have to formulate the associate request manually,
114 // since the DNOI RP won't voluntarily mismatch the association and session types.
115 AssociateDiffieHellmanRequest request = new AssociateDiffieHellmanRequest(protocol.Version, new Uri("https://Provider"));
116 request.AssociationType = protocol.Args.SignatureAlgorithm.HMAC_SHA256;
117 request.SessionType = protocol.Args.SessionType.DH_SHA1;
118 request.InitializeRequest();
119 var response = rp.Channel.Request<AssociateUnsuccessfulResponse>(request);
120 Assert.IsNotNull(response);
121 Assert.AreEqual(protocol.Args.SignatureAlgorithm.HMAC_SHA1, response.AssociationType);
122 Assert.AreEqual(protocol.Args.SessionType.DH_SHA1, response.SessionType);
124 TestSupport.AutoProvider);
125 coordinator.Run();
128 /// <summary>
129 /// Verifies that the RP quietly rejects an OP that suggests an unknown association type.
130 /// </summary>
131 [TestMethod]
132 public void RPRejectsUnrecognizedAssociationType() {
133 Protocol protocol = Protocol.V20;
134 OpenIdCoordinator coordinator = new OpenIdCoordinator(
135 rp => {
136 var association = rp.GetOrCreateAssociation(new ProviderEndpointDescription(ProviderUri, protocol.Version));
137 Assert.IsNull(association, "The RP should quietly give up when the OP misbehaves.");
139 op => {
140 // Receive initial request.
141 var request = op.Channel.ReadFromRequest<AssociateRequest>();
143 // Send a response that suggests a foreign association type.
144 AssociateUnsuccessfulResponse renegotiateResponse = new AssociateUnsuccessfulResponse(request);
145 renegotiateResponse.AssociationType = "HMAC-UNKNOWN";
146 renegotiateResponse.SessionType = "DH-UNKNOWN";
147 op.Channel.Send(renegotiateResponse).Send();
149 coordinator.Run();
152 /// <summary>
153 /// Verifies that the RP quietly rejects an OP that suggests an no encryption over an HTTP channel.
154 /// </summary>
155 [TestMethod]
156 public void RPRejectsUnencryptedSuggestion() {
157 Protocol protocol = Protocol.V20;
158 OpenIdCoordinator coordinator = new OpenIdCoordinator(
159 rp => {
160 var association = rp.GetOrCreateAssociation(new ProviderEndpointDescription(ProviderUri, protocol.Version));
161 Assert.IsNull(association, "The RP should quietly give up when the OP misbehaves.");
163 op => {
164 // Receive initial request.
165 var request = op.Channel.ReadFromRequest<AssociateRequest>();
167 // Send a response that suggests a no encryption.
168 AssociateUnsuccessfulResponse renegotiateResponse = new AssociateUnsuccessfulResponse(request);
169 renegotiateResponse.AssociationType = protocol.Args.SignatureAlgorithm.HMAC_SHA1;
170 renegotiateResponse.SessionType = protocol.Args.SessionType.NoEncryption;
171 op.Channel.Send(renegotiateResponse).Send();
173 coordinator.Run();
176 /// <summary>
177 /// Verifies that the RP rejects an associate renegotiate request
178 /// when the HMAC and DH bit lengths do not match.
179 /// </summary>
180 [TestMethod]
181 public void RPRejectsMismatchingAssociationAndSessionBitLengths() {
182 Protocol protocol = Protocol.V20;
183 OpenIdCoordinator coordinator = new OpenIdCoordinator(
184 rp => {
185 var association = rp.GetOrCreateAssociation(new ProviderEndpointDescription(ProviderUri, protocol.Version));
186 Assert.IsNull(association, "The RP should quietly give up when the OP misbehaves.");
188 op => {
189 // Receive initial request.
190 var request = op.Channel.ReadFromRequest<AssociateRequest>();
192 // Send a mismatched response
193 AssociateUnsuccessfulResponse renegotiateResponse = new AssociateUnsuccessfulResponse(request);
194 renegotiateResponse.AssociationType = protocol.Args.SignatureAlgorithm.HMAC_SHA1;
195 renegotiateResponse.SessionType = protocol.Args.SessionType.DH_SHA256;
196 op.Channel.Send(renegotiateResponse).Send();
198 coordinator.Run();
201 /// <summary>
202 /// Verifies that the RP cannot get caught in an infinite loop if a bad OP
203 /// keeps sending it association retry messages.
204 /// </summary>
205 [TestMethod]
206 public void RPOnlyRenegotiatesOnce() {
207 Protocol protocol = Protocol.V20;
208 OpenIdCoordinator coordinator = new OpenIdCoordinator(
209 rp => {
210 var association = rp.GetOrCreateAssociation(new ProviderEndpointDescription(ProviderUri, protocol.Version));
211 Assert.IsNull(association, "The RP should quietly give up when the OP misbehaves.");
213 op => {
214 // Receive initial request.
215 var request = op.Channel.ReadFromRequest<AssociateRequest>();
217 // Send a renegotiate response
218 AssociateUnsuccessfulResponse renegotiateResponse = new AssociateUnsuccessfulResponse(request);
219 renegotiateResponse.AssociationType = protocol.Args.SignatureAlgorithm.HMAC_SHA1;
220 renegotiateResponse.SessionType = protocol.Args.SessionType.DH_SHA1;
221 op.Channel.Send(renegotiateResponse).Send();
223 // Receive second-try
224 request = op.Channel.ReadFromRequest<AssociateRequest>();
226 // Send ANOTHER renegotiate response, at which point the DNOI RP should give up.
227 renegotiateResponse = new AssociateUnsuccessfulResponse(request);
228 renegotiateResponse.AssociationType = protocol.Args.SignatureAlgorithm.HMAC_SHA256;
229 renegotiateResponse.SessionType = protocol.Args.SessionType.DH_SHA256;
230 op.Channel.Send(renegotiateResponse).Send();
232 coordinator.Run();
235 /// <summary>
236 /// Verifies security settings limit RP's initial associate request
237 /// </summary>
238 [TestMethod]
239 public void AssociateRequestDeterminedBySecuritySettings() {
240 // TODO: Code here
241 Assert.Inconclusive();
244 /// <summary>
245 /// Verifies security settings limit RP's acceptance of OP's counter-suggestion
246 /// </summary>
247 [TestMethod]
248 public void AssociateRenegotiateLimitedByRPSecuritySettings() {
249 // TODO: Code here
250 Assert.Inconclusive();
253 /// <summary>
254 /// Verifies security settings limit OP's set of acceptable association types.
255 /// </summary>
256 [TestMethod]
257 public void AssociateLimitedByOPSecuritySettings() {
258 // TODO: Code here
259 Assert.Inconclusive();
262 /// <summary>
263 /// Verifies that the RP can recover from an invalid or non-existent
264 /// response from the OP, for example in the HTTP timeout case.
265 /// </summary>
266 [TestMethod]
267 public void AssociateContinueAfterHttpError() {
268 // TODO: Code here
269 Assert.Inconclusive();
272 /// <summary>
273 /// Runs a parameterized association flow test using all supported OpenID versions.
274 /// </summary>
275 /// <param name="opEndpoint">The OP endpoint to simulate using.</param>
276 private void ParameterizedAssociationTest(Uri opEndpoint) {
277 foreach (Protocol protocol in Protocol.AllPracticalVersions) {
278 var endpoint = new ProviderEndpointDescription(opEndpoint, protocol.Version);
279 var associationType = protocol.Version.Major < 2 ? protocol.Args.SignatureAlgorithm.HMAC_SHA1 : protocol.Args.SignatureAlgorithm.HMAC_SHA256;
280 this.ParameterizedAssociationTest(endpoint, associationType);
284 /// <summary>
285 /// Runs a parameterized association flow test.
286 /// </summary>
287 /// <param name="opDescription">
288 /// The description of the Provider that the relying party uses to formulate the request.
289 /// The specific host is not used, but the scheme is significant.
290 /// </param>
291 /// <param name="expectedAssociationType">
292 /// The value of the openid.assoc_type parameter expected,
293 /// or null if a failure is anticipated.
294 /// </param>
295 private void ParameterizedAssociationTest(
296 ProviderEndpointDescription opDescription,
297 string expectedAssociationType) {
298 Protocol protocol = Protocol.Lookup(opDescription.ProtocolVersion);
299 bool expectSuccess = expectedAssociationType != null;
300 bool expectDiffieHellman = !opDescription.Endpoint.IsTransportSecure();
301 Association rpAssociation = null, opAssociation;
302 AssociateSuccessfulResponse associateSuccessfulResponse = null;
303 AssociateUnsuccessfulResponse associateUnsuccessfulResponse = null;
304 OpenIdCoordinator coordinator = new OpenIdCoordinator(
305 rp => {
306 rp.SecuritySettings = this.RelyingPartySecuritySettings;
307 rpAssociation = rp.GetOrCreateAssociation(opDescription);
309 op => {
310 op.SecuritySettings = this.ProviderSecuritySettings;
311 IRequest req = op.GetRequest();
312 Assert.IsTrue(req.IsResponseReady);
313 UserAgentResponse resp = req.Response;
314 resp.Send();
316 coordinator.IncomingMessageFilter = message => {
317 Assert.AreSame(opDescription.ProtocolVersion, message.Version, "The message was recognized as version {0} but was expected to be {1}.", message.Version, opDescription.ProtocolVersion);
318 var associateSuccess = message as AssociateSuccessfulResponse;
319 var associateFailed = message as AssociateUnsuccessfulResponse;
320 if (associateSuccess != null) {
321 associateSuccessfulResponse = associateSuccess;
323 if (associateFailed != null) {
324 associateUnsuccessfulResponse = associateFailed;
327 coordinator.OutgoingMessageFilter = message => {
328 Assert.AreSame(opDescription.ProtocolVersion, message.Version, "The message was for version {0} but was expected to be for {1}.", message.Version, opDescription.ProtocolVersion);
330 coordinator.Run();
332 if (expectSuccess) {
333 Assert.IsNotNull(rpAssociation);
334 Assert.AreSame(rpAssociation, coordinator.RelyingParty.AssociationStore.GetAssociation(opDescription.Endpoint, rpAssociation.Handle));
335 opAssociation = coordinator.Provider.AssociationStore.GetAssociation(AssociationRelyingPartyType.Smart, rpAssociation.Handle);
336 Assert.IsNotNull(opAssociation, "The Provider should have stored the association.");
338 Assert.AreEqual(opAssociation.Handle, rpAssociation.Handle);
339 Assert.AreEqual(expectedAssociationType, rpAssociation.GetAssociationType(protocol));
340 Assert.AreEqual(expectedAssociationType, opAssociation.GetAssociationType(protocol));
341 Assert.IsTrue(Math.Abs(opAssociation.SecondsTillExpiration - rpAssociation.SecondsTillExpiration) < 60);
342 Assert.IsTrue(MessagingUtilities.AreEquivalent(opAssociation.SecretKey, rpAssociation.SecretKey));
344 if (expectDiffieHellman) {
345 Assert.IsInstanceOfType(associateSuccessfulResponse, typeof(AssociateDiffieHellmanResponse));
346 var diffieHellmanResponse = (AssociateDiffieHellmanResponse)associateSuccessfulResponse;
347 Assert.IsFalse(MessagingUtilities.AreEquivalent(diffieHellmanResponse.EncodedMacKey, rpAssociation.SecretKey), "Key should have been encrypted.");
348 } else {
349 Assert.IsInstanceOfType(associateSuccessfulResponse, typeof(AssociateUnencryptedResponse));
350 var unencryptedResponse = (AssociateUnencryptedResponse)associateSuccessfulResponse;
352 } else {
353 Assert.IsNull(coordinator.RelyingParty.AssociationStore.GetAssociation(opDescription.Endpoint));
354 Assert.IsNull(coordinator.Provider.AssociationStore.GetAssociation(AssociationRelyingPartyType.Smart));