1
//-----------------------------------------------------------------------
2 // <copyright file="AssociationHandshakeTests.cs" company="Andrew Arnott">
3 // Copyright (c) Andrew Arnott. All rights reserved.
5 //-----------------------------------------------------------------------
7 namespace DotNetOpenAuth
.Test
.OpenId
{
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
;
17 public class AssociationHandshakeTests
: OpenIdTestBase
{
19 public override void SetUp() {
24 public void AssociateUnencrypted() {
25 this.ParameterizedAssociationTest(new Uri("https://host"));
29 public void AssociateDiffieHellmanOverHttp() {
30 this.ParameterizedAssociationTest(new Uri("http://host"));
34 /// Verifies that the Provider can do Diffie-Hellman over HTTPS.
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.
41 public void AssociateDiffieHellmanOverHttps() {
42 Protocol protocol
= Protocol
.V20
;
43 OpenIdCoordinator coordinator
= new OpenIdCoordinator(
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
);
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.
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(
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
));
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
);
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
);
107 /// Verifies that the OP rejects an associate request
108 /// when the HMAC and DH bit lengths do not match.
111 public void OPRejectsMismatchingAssociationAndSessionTypes() {
112 Protocol protocol
= Protocol
.V20
;
113 OpenIdCoordinator coordinator
= new OpenIdCoordinator(
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
);
131 /// Verifies that the RP quietly rejects an OP that suggests an unknown association type.
134 public void RPRejectsUnrecognizedAssociationType() {
135 Protocol protocol
= Protocol
.V20
;
136 OpenIdCoordinator coordinator
= new OpenIdCoordinator(
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.");
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
);
155 /// Verifies that the RP quietly rejects an OP that suggests an no encryption over an HTTP channel.
158 public void RPRejectsUnencryptedSuggestion() {
159 Protocol protocol
= Protocol
.V20
;
160 OpenIdCoordinator coordinator
= new OpenIdCoordinator(
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.");
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
);
179 /// Verifies that the RP rejects an associate renegotiate request
180 /// when the HMAC and DH bit lengths do not match.
183 public void RPRejectsMismatchingAssociationAndSessionBitLengths() {
184 Protocol protocol
= Protocol
.V20
;
185 OpenIdCoordinator coordinator
= new OpenIdCoordinator(
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.");
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
);
204 /// Verifies that the RP cannot get caught in an infinite loop if a bad OP
205 /// keeps sending it association retry messages.
208 public void RPOnlyRenegotiatesOnce() {
209 Protocol protocol
= Protocol
.V20
;
210 OpenIdCoordinator coordinator
= new OpenIdCoordinator(
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.");
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
);
238 /// Verifies security settings limit RP's acceptance of OP's counter-suggestion
241 public void AssociateRenegotiateLimitedByRPSecuritySettings() {
242 Protocol protocol
= Protocol
.V20
;
243 OpenIdCoordinator coordinator
= new OpenIdCoordinator(
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.");
250 op
.SecuritySettings
.MaximumHashBitLength
= 160;
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.
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
);
269 /// Runs a parameterized association flow test using all supported OpenID versions.
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
);
281 /// Runs a parameterized association flow test.
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.
287 /// <param name="expectedAssociationType">
288 /// The value of the openid.assoc_type parameter expected,
289 /// or null if a failure is anticipated.
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(
302 rp
.SecuritySettings
= this.RelyingPartySecuritySettings
;
303 rpAssociation
= rp
.AssociationManager
.GetOrCreateAssociation(opDescription
);
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
;
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
);
329 var associationManagerAccessor
= AssociationManager_Accessor
.AttachShadow(coordinator
.RelyingParty
.AssociationManager
);
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.");
348 Assert
.IsInstanceOfType(associateSuccessfulResponse
, typeof(AssociateUnencryptedResponse
));
349 var unencryptedResponse
= (AssociateUnencryptedResponse
)associateSuccessfulResponse
;
352 Assert
.IsNull(associationManagerAccessor
.associationStore
.GetAssociation(opDescription
.Endpoint
));
353 Assert
.IsNull(coordinator
.Provider
.AssociationStore
.GetAssociation(AssociationRelyingPartyType
.Smart
));