//----------------------------------------------------------------------- // // Copyright (c) Andrew Arnott. All rights reserved. // //----------------------------------------------------------------------- namespace DotNetOpenAuth.Test.OpenId { using System; using System.Diagnostics.Contracts; using DotNetOpenAuth.Messaging; using DotNetOpenAuth.Messaging.Bindings; using DotNetOpenAuth.OpenId; using DotNetOpenAuth.OpenId.Messages; using DotNetOpenAuth.OpenId.Provider; using DotNetOpenAuth.OpenId.RelyingParty; using DotNetOpenAuth.Test.Mocks; using NUnit.Framework; [TestFixture] public class AuthenticationTests : OpenIdTestBase { [SetUp] public override void SetUp() { base.SetUp(); } [TestCase] public void SharedAssociationPositive() { this.ParameterizedAuthenticationTest(true, true, false); } /// /// Verifies that a shared association protects against tampering. /// [TestCase] public void SharedAssociationTampered() { this.ParameterizedAuthenticationTest(true, true, true); } [TestCase] public void SharedAssociationNegative() { this.ParameterizedAuthenticationTest(true, false, false); } [TestCase] public void PrivateAssociationPositive() { this.ParameterizedAuthenticationTest(false, true, false); } /// /// Verifies that a private association protects against tampering. /// [TestCase] public void PrivateAssociationTampered() { this.ParameterizedAuthenticationTest(false, true, true); } [TestCase] public void NoAssociationNegative() { this.ParameterizedAuthenticationTest(false, false, false); } [TestCase] public void UnsolicitedAssertion() { this.MockResponder.RegisterMockRPDiscovery(); OpenIdCoordinator coordinator = new OpenIdCoordinator( rp => { rp.Channel.WebRequestHandler = this.MockResponder.MockWebRequestHandler; IAuthenticationResponse response = rp.GetResponse(); Assert.AreEqual(AuthenticationStatus.Authenticated, response.Status); }, op => { op.Channel.WebRequestHandler = this.MockResponder.MockWebRequestHandler; Identifier id = GetMockIdentifier(ProtocolVersion.V20); op.SendUnsolicitedAssertion(OPUri, RPRealmUri, id, OPLocalIdentifiers[0]); AutoProvider(op); // handle check_auth }); coordinator.Run(); } [TestCase] public void UnsolicitedAssertionRejected() { this.MockResponder.RegisterMockRPDiscovery(); OpenIdCoordinator coordinator = new OpenIdCoordinator( rp => { rp.Channel.WebRequestHandler = this.MockResponder.MockWebRequestHandler; rp.SecuritySettings.RejectUnsolicitedAssertions = true; IAuthenticationResponse response = rp.GetResponse(); Assert.AreEqual(AuthenticationStatus.Failed, response.Status); }, op => { op.Channel.WebRequestHandler = this.MockResponder.MockWebRequestHandler; Identifier id = GetMockIdentifier(ProtocolVersion.V20); op.SendUnsolicitedAssertion(OPUri, RPRealmUri, id, OPLocalIdentifiers[0]); AutoProvider(op); // handle check_auth }); coordinator.Run(); } /// /// Verifies that delegating identifiers are rejected in unsolicited assertions /// when the appropriate security setting is set. /// [TestCase] public void UnsolicitedDelegatingIdentifierRejection() { this.MockResponder.RegisterMockRPDiscovery(); OpenIdCoordinator coordinator = new OpenIdCoordinator( rp => { rp.Channel.WebRequestHandler = this.MockResponder.MockWebRequestHandler; rp.SecuritySettings.RejectDelegatingIdentifiers = true; IAuthenticationResponse response = rp.GetResponse(); Assert.AreEqual(AuthenticationStatus.Failed, response.Status); }, op => { op.Channel.WebRequestHandler = this.MockResponder.MockWebRequestHandler; Identifier id = GetMockIdentifier(ProtocolVersion.V20, false, true); op.SendUnsolicitedAssertion(OPUri, RPRealmUri, id, OPLocalIdentifiers[0]); AutoProvider(op); // handle check_auth }); coordinator.Run(); } private void ParameterizedAuthenticationTest(bool sharedAssociation, bool positive, bool tamper) { foreach (Protocol protocol in Protocol.AllPracticalVersions) { foreach (bool statelessRP in new[] { false, true }) { if (sharedAssociation && statelessRP) { // Skip the invalid combination scenario. continue; } foreach (bool immediate in new[] { false, true }) { TestLogger.InfoFormat("Beginning authentication test scenario. OpenID: {0}, Shared: {1}, positive: {2}, tamper: {3}, stateless: {4}, immediate: {5}", protocol.Version, sharedAssociation, positive, tamper, statelessRP, immediate); this.ParameterizedAuthenticationTest(protocol, statelessRP, sharedAssociation, positive, immediate, tamper); } } } } private void ParameterizedAuthenticationTest(Protocol protocol, bool statelessRP, bool sharedAssociation, bool positive, bool immediate, bool tamper) { Contract.Requires(!statelessRP || !sharedAssociation, "The RP cannot be stateless while sharing an association with the OP."); Contract.Requires(positive || !tamper, "Cannot tamper with a negative response."); ProviderSecuritySettings securitySettings = new ProviderSecuritySettings(); Association association = sharedAssociation ? HmacShaAssociation.Create(protocol, protocol.Args.SignatureAlgorithm.Best, AssociationRelyingPartyType.Smart, securitySettings) : null; var coordinator = new OpenIdCoordinator( rp => { var request = new CheckIdRequest(protocol.Version, OPUri, immediate ? AuthenticationRequestMode.Immediate : AuthenticationRequestMode.Setup); if (association != null) { StoreAssociation(rp, OPUri, association); request.AssociationHandle = association.Handle; } request.ClaimedIdentifier = "http://claimedid"; request.LocalIdentifier = "http://localid"; request.ReturnTo = RPUri; request.Realm = RPUri; rp.Channel.Send(request); if (positive) { if (tamper) { try { rp.Channel.ReadFromRequest(); Assert.Fail("Expected exception {0} not thrown.", typeof(InvalidSignatureException).Name); } catch (InvalidSignatureException) { TestLogger.InfoFormat("Caught expected {0} exception after tampering with signed data.", typeof(InvalidSignatureException).Name); } } else { var response = rp.Channel.ReadFromRequest(); Assert.IsNotNull(response); Assert.AreEqual(request.ClaimedIdentifier, response.ClaimedIdentifier); Assert.AreEqual(request.LocalIdentifier, response.LocalIdentifier); Assert.AreEqual(request.ReturnTo, response.ReturnTo); // Attempt to replay the message and verify that it fails. // Because in various scenarios and protocol versions different components // notice the replay, we can get one of two exceptions thrown. // When the OP notices the replay we get a generic InvalidSignatureException. // When the RP notices the replay we get a specific ReplayMessageException. try { CoordinatingChannel channel = (CoordinatingChannel)rp.Channel; channel.Replay(response); Assert.Fail("Expected ProtocolException was not thrown."); } catch (ProtocolException ex) { Assert.IsTrue(ex is ReplayedMessageException || ex is InvalidSignatureException, "A {0} exception was thrown instead of the expected {1} or {2}.", ex.GetType(), typeof(ReplayedMessageException).Name, typeof(InvalidSignatureException).Name); } } } else { var response = rp.Channel.ReadFromRequest(); Assert.IsNotNull(response); if (immediate) { // Only 1.1 was required to include user_setup_url if (protocol.Version.Major < 2) { Assert.IsNotNull(response.UserSetupUrl); } } else { Assert.IsNull(response.UserSetupUrl); } } }, op => { if (association != null) { op.AssociationStore.StoreAssociation(AssociationRelyingPartyType.Smart, association); } var request = op.Channel.ReadFromRequest(); Assert.IsNotNull(request); IProtocolMessage response; if (positive) { response = new PositiveAssertionResponse(request); } else { response = new NegativeAssertionResponse(request, op.Channel); } op.Channel.Send(response); if (positive && (statelessRP || !sharedAssociation)) { var checkauthRequest = op.Channel.ReadFromRequest(); var checkauthResponse = new CheckAuthenticationResponse(checkauthRequest.Version, checkauthRequest); checkauthResponse.IsValid = checkauthRequest.IsValid; op.Channel.Send(checkauthResponse); if (!tamper) { // Respond to the replay attack. checkauthRequest = op.Channel.ReadFromRequest(); checkauthResponse = new CheckAuthenticationResponse(checkauthRequest.Version, checkauthRequest); checkauthResponse.IsValid = checkauthRequest.IsValid; op.Channel.Send(checkauthResponse); } } }); if (tamper) { coordinator.IncomingMessageFilter = message => { var assertion = message as PositiveAssertionResponse; if (assertion != null) { // Alter the Local Identifier between the Provider and the Relying Party. // If the signature binding element does its job, this should cause the RP // to throw. assertion.LocalIdentifier = "http://victim"; } }; } if (statelessRP) { coordinator.RelyingParty = new OpenIdRelyingParty(null); } coordinator.Run(); } } }