diff options
Diffstat (limited to 'src/DotNetOpenAuth.Test/OpenId/AuthenticationTests.cs')
-rw-r--r-- | src/DotNetOpenAuth.Test/OpenId/AuthenticationTests.cs | 365 |
1 files changed, 220 insertions, 145 deletions
diff --git a/src/DotNetOpenAuth.Test/OpenId/AuthenticationTests.cs b/src/DotNetOpenAuth.Test/OpenId/AuthenticationTests.cs index 6129ee7..871eb78 100644 --- a/src/DotNetOpenAuth.Test/OpenId/AuthenticationTests.cs +++ b/src/DotNetOpenAuth.Test/OpenId/AuthenticationTests.cs @@ -6,7 +6,11 @@ namespace DotNetOpenAuth.Test.OpenId { using System; - using System.Diagnostics.Contracts; + using System.Net; + using System.Net.Http; + using System.Threading; + using System.Threading.Tasks; + using DotNetOpenAuth.Messaging; using DotNetOpenAuth.Messaging.Bindings; using DotNetOpenAuth.OpenId; @@ -15,6 +19,7 @@ namespace DotNetOpenAuth.Test.OpenId { using DotNetOpenAuth.OpenId.RelyingParty; using DotNetOpenAuth.Test.Mocks; using NUnit.Framework; + using Validation; [TestFixture] public class AuthenticationTests : OpenIdTestBase { @@ -24,76 +29,101 @@ namespace DotNetOpenAuth.Test.OpenId { } [Test] - public void SharedAssociationPositive() { - this.ParameterizedAuthenticationTest(true, true, false); + public async Task SharedAssociationPositive() { + await this.ParameterizedAuthenticationTestAsync(true, true, false); } /// <summary> /// Verifies that a shared association protects against tampering. /// </summary> [Test] - public void SharedAssociationTampered() { - this.ParameterizedAuthenticationTest(true, true, true); + public async Task SharedAssociationTampered() { + await this.ParameterizedAuthenticationTestAsync(true, true, true); } [Test] - public void SharedAssociationNegative() { - this.ParameterizedAuthenticationTest(true, false, false); + public async Task SharedAssociationNegative() { + await this.ParameterizedAuthenticationTestAsync(true, false, false); } [Test] - public void PrivateAssociationPositive() { - this.ParameterizedAuthenticationTest(false, true, false); + public async Task PrivateAssociationPositive() { + await this.ParameterizedAuthenticationTestAsync(false, true, false); } /// <summary> /// Verifies that a private association protects against tampering. /// </summary> [Test] - public void PrivateAssociationTampered() { - this.ParameterizedAuthenticationTest(false, true, true); + public async Task PrivateAssociationTampered() { + await this.ParameterizedAuthenticationTestAsync(false, true, true); } [Test] - public void NoAssociationNegative() { - this.ParameterizedAuthenticationTest(false, false, false); + public async Task NoAssociationNegative() { + await this.ParameterizedAuthenticationTestAsync(false, false, false); } [Test] - public void UnsolicitedAssertion() { - this.MockResponder.RegisterMockRPDiscovery(); - OpenIdCoordinator coordinator = new OpenIdCoordinator( - rp => { - rp.Channel.WebRequestHandler = this.MockResponder.MockWebRequestHandler; - IAuthenticationResponse response = rp.GetResponse(); + public async Task UnsolicitedAssertion() { + var opStore = new MemoryCryptoKeyAndNonceStore(); + Handle(RPUri).By( + async req => { + var rp = new OpenIdRelyingParty(new MemoryCryptoKeyAndNonceStore(), this.HostFactories); + IAuthenticationResponse response = await rp.GetResponseAsync(req); + Assert.That(response, Is.Not.Null); 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 + return new HttpResponseMessage(); + }); + Handle(OPUri).By( + async (req, ct) => { + var op = new OpenIdProvider(opStore, this.HostFactories); + return await this.AutoProviderActionAsync(op, req, ct); }); - coordinator.Run(); + this.RegisterMockRPDiscovery(ssl: false); + + { + var op = new OpenIdProvider(opStore, this.HostFactories); + Identifier id = GetMockIdentifier(ProtocolVersion.V20); + var assertion = await op.PrepareUnsolicitedAssertionAsync(OPUri, RPRealmUri, id, OPLocalIdentifiers[0]); + + using (var httpClient = this.HostFactories.CreateHttpClient()) { + using (var response = await httpClient.GetAsync(assertion.Headers.Location)) { + response.EnsureSuccessStatusCode(); + } + } + } } [Test] - public void UnsolicitedAssertionRejected() { - this.MockResponder.RegisterMockRPDiscovery(); - OpenIdCoordinator coordinator = new OpenIdCoordinator( - rp => { - rp.Channel.WebRequestHandler = this.MockResponder.MockWebRequestHandler; + public async Task UnsolicitedAssertionRejected() { + var opStore = new MemoryCryptoKeyAndNonceStore(); + Handle(RPUri).By( + async req => { + var rp = new OpenIdRelyingParty(new MemoryCryptoKeyAndNonceStore(), this.HostFactories); rp.SecuritySettings.RejectUnsolicitedAssertions = true; - IAuthenticationResponse response = rp.GetResponse(); + IAuthenticationResponse response = await rp.GetResponseAsync(req); + Assert.That(response, Is.Not.Null); 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 + return new HttpResponseMessage(); + }); + Handle(OPUri).By( + async req => { + var op = new OpenIdProvider(opStore, this.HostFactories); + return await this.AutoProviderActionAsync(op, req, CancellationToken.None); }); - coordinator.Run(); + this.RegisterMockRPDiscovery(ssl: false); + + { + var op = new OpenIdProvider(opStore, this.HostFactories); + Identifier id = GetMockIdentifier(ProtocolVersion.V20); + var assertion = await op.PrepareUnsolicitedAssertionAsync(OPUri, RPRealmUri, id, OPLocalIdentifiers[0]); + using (var httpClient = this.HostFactories.CreateHttpClient()) { + using (var response = await httpClient.GetAsync(assertion.Headers.Location)) { + response.EnsureSuccessStatusCode(); + } + } + } } /// <summary> @@ -101,25 +131,37 @@ namespace DotNetOpenAuth.Test.OpenId { /// when the appropriate security setting is set. /// </summary> [Test] - public void UnsolicitedDelegatingIdentifierRejection() { - this.MockResponder.RegisterMockRPDiscovery(); - OpenIdCoordinator coordinator = new OpenIdCoordinator( - rp => { - rp.Channel.WebRequestHandler = this.MockResponder.MockWebRequestHandler; + public async Task UnsolicitedDelegatingIdentifierRejection() { + var opStore = new MemoryCryptoKeyAndNonceStore(); + Handle(RPUri).By( + async req => { + var rp = this.CreateRelyingParty(); rp.SecuritySettings.RejectDelegatingIdentifiers = true; - IAuthenticationResponse response = rp.GetResponse(); + IAuthenticationResponse response = await rp.GetResponseAsync(req); + Assert.That(response, Is.Not.Null); 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 + return new HttpResponseMessage(); }); - coordinator.Run(); + Handle(OPUri).By( + async req => { + var op = new OpenIdProvider(opStore, this.HostFactories); + return await this.AutoProviderActionAsync(op, req, CancellationToken.None); + }); + this.RegisterMockRPDiscovery(ssl: false); + + { + var op = new OpenIdProvider(opStore, this.HostFactories); + Identifier id = GetMockIdentifier(ProtocolVersion.V20, false, true); + var assertion = await op.PrepareUnsolicitedAssertionAsync(OPUri, RPRealmUri, id, OPLocalIdentifiers[0]); + using (var httpClient = this.HostFactories.CreateHttpClient()) { + using (var response = await httpClient.GetAsync(assertion.Headers.Location)) { + response.EnsureSuccessStatusCode(); + } + } + } } - private void ParameterizedAuthenticationTest(bool sharedAssociation, bool positive, bool tamper) { + private async Task ParameterizedAuthenticationTestAsync(bool sharedAssociation, bool positive, bool tamper) { foreach (Protocol protocol in Protocol.AllPracticalVersions) { foreach (bool statelessRP in new[] { false, true }) { if (sharedAssociation && statelessRP) { @@ -129,121 +171,154 @@ namespace DotNetOpenAuth.Test.OpenId { 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); + await this.ParameterizedAuthenticationTestAsync(protocol, statelessRP, sharedAssociation, positive, immediate, tamper); } } } } - private void ParameterizedAuthenticationTest(Protocol protocol, bool statelessRP, bool sharedAssociation, bool positive, bool immediate, bool tamper) { - Requires.True(!statelessRP || !sharedAssociation, null, "The RP cannot be stateless while sharing an association with the OP."); - Requires.True(positive || !tamper, null, "Cannot tamper with a negative response."); + private async Task ParameterizedAuthenticationTestAsync(Protocol protocol, bool statelessRP, bool sharedAssociation, bool positive, bool immediate, bool tamper) { + Requires.That(!statelessRP || !sharedAssociation, null, "The RP cannot be stateless while sharing an association with the OP."); + Requires.That(positive || !tamper, null, "Cannot tamper with a negative response."); var securitySettings = new ProviderSecuritySettings(); var cryptoKeyStore = new MemoryCryptoKeyStore(); var associationStore = new ProviderAssociationHandleEncoder(cryptoKeyStore); Association association = sharedAssociation ? HmacShaAssociationProvider.Create(protocol, protocol.Args.SignatureAlgorithm.Best, AssociationRelyingPartyType.Smart, associationStore, securitySettings) : null; - var coordinator = new OpenIdCoordinator( - rp => { - var request = new CheckIdRequest(protocol.Version, OPUri, immediate ? AuthenticationRequestMode.Immediate : AuthenticationRequestMode.Setup); - + int opStep = 0; + HandleProvider( + async (op, req) => { if (association != null) { - StoreAssociation(rp, OPUri, association); - request.AssociationHandle = association.Handle; + var key = cryptoKeyStore.GetCurrentKey( + ProviderAssociationHandleEncoder.AssociationHandleEncodingSecretBucket, TimeSpan.FromSeconds(1)); + op.CryptoKeyStore.StoreKey( + ProviderAssociationHandleEncoder.AssociationHandleEncodingSecretBucket, key.Key, key.Value); } - request.ClaimedIdentifier = "http://claimedid"; - request.LocalIdentifier = "http://localid"; - request.ReturnTo = RPUri; - request.Realm = RPUri; - rp.Channel.Respond(request); - if (positive) { - if (tamper) { - try { - rp.Channel.ReadFromRequest<PositiveAssertionResponse>(); - 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); + switch (++opStep) { + case 1: + var request = await op.Channel.ReadFromRequestAsync<CheckIdRequest>(req, CancellationToken.None); + Assert.IsNotNull(request); + IProtocolMessage response; + if (positive) { + response = new PositiveAssertionResponse(request); + } else { + response = await NegativeAssertionResponse.CreateAsync(request, CancellationToken.None, op.Channel); } - } else { - var response = rp.Channel.ReadFromRequest<PositiveAssertionResponse>(); - 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); + + return await op.Channel.PrepareResponseAsync(response); + case 2: + if (positive && (statelessRP || !sharedAssociation)) { + var checkauthRequest = + await op.Channel.ReadFromRequestAsync<CheckAuthenticationRequest>(req, CancellationToken.None); + var checkauthResponse = new CheckAuthenticationResponse(checkauthRequest.Version, checkauthRequest); + checkauthResponse.IsValid = checkauthRequest.IsValid; + return await op.Channel.PrepareResponseAsync(checkauthResponse); } - } - } else { - var response = rp.Channel.ReadFromRequest<NegativeAssertionResponse>(); - Assert.IsNotNull(response); - if (immediate) { - // Only 1.1 was required to include user_setup_url - if (protocol.Version.Major < 2) { - Assert.IsNotNull(response.UserSetupUrl); + + throw Assumes.NotReachable(); + case 3: + if (positive && (statelessRP || !sharedAssociation)) { + if (!tamper) { + // Respond to the replay attack. + var checkauthRequest = + await op.Channel.ReadFromRequestAsync<CheckAuthenticationRequest>(req, CancellationToken.None); + var checkauthResponse = new CheckAuthenticationResponse(checkauthRequest.Version, checkauthRequest); + checkauthResponse.IsValid = checkauthRequest.IsValid; + return await op.Channel.PrepareResponseAsync(checkauthResponse); + } } - } else { - Assert.IsNull(response.UserSetupUrl); - } + + throw Assumes.NotReachable(); + default: + throw Assumes.NotReachable(); } - }, - op => { - if (association != null) { - var key = cryptoKeyStore.GetCurrentKey(ProviderAssociationHandleEncoder.AssociationHandleEncodingSecretBucket, TimeSpan.FromSeconds(1)); - op.CryptoKeyStore.StoreKey(ProviderAssociationHandleEncoder.AssociationHandleEncodingSecretBucket, key.Key, key.Value); + }); + + { + var rp = this.CreateRelyingParty(statelessRP); + if (tamper) { + rp.Channel.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"; + } + }; + } + + 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; + var redirectRequest = await rp.Channel.PrepareResponseAsync(request); + Uri redirectResponse; + this.HostFactories.AllowAutoRedirects = false; + using (var httpClient = rp.Channel.HostFactories.CreateHttpClient()) { + using (var response = await httpClient.GetAsync(redirectRequest.Headers.Location)) { + Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.Redirect)); + redirectResponse = response.Headers.Location; } + } - var request = op.Channel.ReadFromRequest<CheckIdRequest>(); - Assert.IsNotNull(request); - IProtocolMessage response; - if (positive) { - response = new PositiveAssertionResponse(request); + var assertionMessage = new HttpRequestMessage(HttpMethod.Get, redirectResponse); + if (positive) { + if (tamper) { + try { + await rp.Channel.ReadFromRequestAsync<PositiveAssertionResponse>(assertionMessage, CancellationToken.None); + 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 { - response = new NegativeAssertionResponse(request, op.Channel); - } - op.Channel.Respond(response); - - if (positive && (statelessRP || !sharedAssociation)) { - var checkauthRequest = op.Channel.ReadFromRequest<CheckAuthenticationRequest>(); - var checkauthResponse = new CheckAuthenticationResponse(checkauthRequest.Version, checkauthRequest); - checkauthResponse.IsValid = checkauthRequest.IsValid; - op.Channel.Respond(checkauthResponse); - - if (!tamper) { - // Respond to the replay attack. - checkauthRequest = op.Channel.ReadFromRequest<CheckAuthenticationRequest>(); - checkauthResponse = new CheckAuthenticationResponse(checkauthRequest.Version, checkauthRequest); - checkauthResponse.IsValid = checkauthRequest.IsValid; - op.Channel.Respond(checkauthResponse); + var response = + await rp.Channel.ReadFromRequestAsync<PositiveAssertionResponse>(assertionMessage, CancellationToken.None); + 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 { + await rp.Channel.ReadFromRequestAsync<PositiveAssertionResponse>(assertionMessage, CancellationToken.None); + 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); } } - }); - 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"; + } else { + var response = + await rp.Channel.ReadFromRequestAsync<NegativeAssertionResponse>(assertionMessage, CancellationToken.None); + 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); } - }; - } - if (statelessRP) { - coordinator.RelyingParty = new OpenIdRelyingParty(null); + } } - - coordinator.Run(); } } } |