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 Microsoft
.VisualStudio
.TestTools
.UnitTesting
;
16 public class AssociationHandshakeTests
: OpenIdTestBase
{
18 public override void SetUp() {
23 public void AssociateUnencrypted() {
24 this.ParameterizedAssociationTest(new Uri("https://host"));
28 public void AssociateDiffieHellmanOverHttp() {
29 this.ParameterizedAssociationTest(new Uri("http://host"));
33 /// Verifies that the Provider can do Diffie-Hellman over HTTPS.
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.
40 public void AssociateDiffieHellmanOverHttps() {
41 Protocol protocol
= Protocol
.V20
;
42 OpenIdCoordinator coordinator
= new OpenIdCoordinator(
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
);
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.
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(
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
));
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
);
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
);
105 /// Verifies that the OP rejects an associate request
106 /// when the HMAC and DH bit lengths do not match.
109 public void OPRejectsMismatchingAssociationAndSessionTypes() {
110 Protocol protocol
= Protocol
.V20
;
111 OpenIdCoordinator coordinator
= new OpenIdCoordinator(
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
);
129 /// Verifies that the RP quietly rejects an OP that suggests an unknown association type.
132 public void RPRejectsUnrecognizedAssociationType() {
133 Protocol protocol
= Protocol
.V20
;
134 OpenIdCoordinator coordinator
= new OpenIdCoordinator(
136 var association
= rp
.GetOrCreateAssociation(new ProviderEndpointDescription(ProviderUri
, protocol
.Version
));
137 Assert
.IsNull(association
, "The RP should quietly give up when the OP misbehaves.");
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();
153 /// Verifies that the RP quietly rejects an OP that suggests an no encryption over an HTTP channel.
156 public void RPRejectsUnencryptedSuggestion() {
157 Protocol protocol
= Protocol
.V20
;
158 OpenIdCoordinator coordinator
= new OpenIdCoordinator(
160 var association
= rp
.GetOrCreateAssociation(new ProviderEndpointDescription(ProviderUri
, protocol
.Version
));
161 Assert
.IsNull(association
, "The RP should quietly give up when the OP misbehaves.");
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();
177 /// Verifies that the RP rejects an associate renegotiate request
178 /// when the HMAC and DH bit lengths do not match.
181 public void RPRejectsMismatchingAssociationAndSessionBitLengths() {
182 Protocol protocol
= Protocol
.V20
;
183 OpenIdCoordinator coordinator
= new OpenIdCoordinator(
185 var association
= rp
.GetOrCreateAssociation(new ProviderEndpointDescription(ProviderUri
, protocol
.Version
));
186 Assert
.IsNull(association
, "The RP should quietly give up when the OP misbehaves.");
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();
202 /// Verifies that the RP cannot get caught in an infinite loop if a bad OP
203 /// keeps sending it association retry messages.
206 public void RPOnlyRenegotiatesOnce() {
207 Protocol protocol
= Protocol
.V20
;
208 OpenIdCoordinator coordinator
= new OpenIdCoordinator(
210 var association
= rp
.GetOrCreateAssociation(new ProviderEndpointDescription(ProviderUri
, protocol
.Version
));
211 Assert
.IsNull(association
, "The RP should quietly give up when the OP misbehaves.");
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();
236 /// Verifies security settings limit RP's initial associate request
239 public void AssociateRequestDeterminedBySecuritySettings() {
241 Assert
.Inconclusive();
245 /// Verifies security settings limit RP's acceptance of OP's counter-suggestion
248 public void AssociateRenegotiateLimitedByRPSecuritySettings() {
250 Assert
.Inconclusive();
254 /// Verifies security settings limit OP's set of acceptable association types.
257 public void AssociateLimitedByOPSecuritySettings() {
259 Assert
.Inconclusive();
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.
267 public void AssociateContinueAfterHttpError() {
269 Assert
.Inconclusive();
273 /// Runs a parameterized association flow test using all supported OpenID versions.
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
);
285 /// Runs a parameterized association flow test.
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.
291 /// <param name="expectedAssociationType">
292 /// The value of the openid.assoc_type parameter expected,
293 /// or null if a failure is anticipated.
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(
306 rp
.SecuritySettings
= this.RelyingPartySecuritySettings
;
307 rpAssociation
= rp
.GetOrCreateAssociation(opDescription
);
310 op
.SecuritySettings
= this.ProviderSecuritySettings
;
311 IRequest req
= op
.GetRequest();
312 Assert
.IsTrue(req
.IsResponseReady
);
313 UserAgentResponse resp
= req
.Response
;
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
);
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.");
349 Assert
.IsInstanceOfType(associateSuccessfulResponse
, typeof(AssociateUnencryptedResponse
));
350 var unencryptedResponse
= (AssociateUnencryptedResponse
)associateSuccessfulResponse
;
353 Assert
.IsNull(coordinator
.RelyingParty
.AssociationStore
.GetAssociation(opDescription
.Endpoint
));
354 Assert
.IsNull(coordinator
.Provider
.AssociationStore
.GetAssociation(AssociationRelyingPartyType
.Smart
));