//-----------------------------------------------------------------------
//
// Copyright (c) Andrew Arnott. All rights reserved.
//
//-----------------------------------------------------------------------
namespace DotNetOpenAuth.Test.OpenId {
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using DotNetOpenAuth.Messaging;
using DotNetOpenAuth.Messaging.Bindings;
using DotNetOpenAuth.OpenId;
using DotNetOpenAuth.OpenId.ChannelElements;
using DotNetOpenAuth.OpenId.Messages;
using DotNetOpenAuth.OpenId.RelyingParty;
using DotNetOpenAuth.Test.Mocks;
using Microsoft.VisualStudio.TestTools.UnitTesting;
[TestClass]
public class AuthenticationTests : OpenIdTestBase {
[TestInitialize]
public override void SetUp() {
base.SetUp();
}
[TestMethod]
public void SharedAssociationPositive() {
this.ParameterizedPositiveAuthenticationTest(true, true, false);
}
///
/// Verifies that a shared association protects against tampering.
///
[TestMethod]
public void SharedAssociationTampered() {
this.ParameterizedPositiveAuthenticationTest(true, true, true);
}
[TestMethod]
public void SharedAssociationNegative() {
this.ParameterizedPositiveAuthenticationTest(true, false, false);
}
[TestMethod]
public void PrivateAssociationPositive() {
this.ParameterizedPositiveAuthenticationTest(false, true, false);
}
///
/// Verifies that a private association protects against tampering.
///
[TestMethod]
public void PrivateAssociationTampered() {
this.ParameterizedPositiveAuthenticationTest(false, true, true);
}
[TestMethod]
public void NoAssociationNegative() {
this.ParameterizedPositiveAuthenticationTest(false, false, false);
}
private void ParameterizedPositiveAuthenticationTest(bool sharedAssociation, bool positive, bool tamper) {
foreach (Protocol protocol in Protocol.AllPracticalVersions) {
this.ParameterizedPositiveAuthenticationTest(protocol, sharedAssociation, positive, tamper);
}
}
private void ParameterizedPositiveAuthenticationTest(Protocol protocol, bool sharedAssociation, bool positive, bool tamper) {
ErrorUtilities.VerifyArgument(positive || !tamper, "Cannot tamper with a negative response.");
Uri userSetupUrl = protocol.Version.Major < 2 ? new Uri("http://usersetupurl") : null;
Association association = sharedAssociation ? HmacShaAssociation.Create(protocol, protocol.Args.SignatureAlgorithm.Best, AssociationRelyingPartyType.Smart) : null;
var coordinator = new OpenIdCoordinator(
rp => {
var request = new CheckIdRequest(protocol.Version, ProviderUri, AuthenticationRequestMode.Immediate);
if (association != null) {
rp.AssociationStore.StoreAssociation(ProviderUri, association);
request.AssociationHandle = association.Handle;
}
request.ClaimedIdentifier = "http://claimedid";
request.LocalIdentifier = "http://localid";
request.ReturnTo = RPUri;
rp.Channel.Send(request).Send();
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.
Type expectedExceptionType = sharedAssociation || protocol.Version.Major < 2 ? typeof(ReplayedMessageException) : typeof(InvalidSignatureException);
try {
CoordinatingChannel channel = (CoordinatingChannel)rp.Channel;
channel.Replay(response);
Assert.Fail("Expected exception {0} was not thrown.", expectedExceptionType.Name);
} catch (ProtocolException ex) {
Assert.IsInstanceOfType(ex, expectedExceptionType);
}
}
} else {
var response = rp.Channel.ReadFromRequest();
Assert.IsNotNull(response);
Assert.AreEqual(userSetupUrl, 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) { UserSetupUrl = userSetupUrl };
}
op.Channel.Send(response).Send();
if (positive && !sharedAssociation) {
var checkauthRequest = op.Channel.ReadFromRequest();
var checkauthResponse = new CheckAuthenticationResponse(checkauthRequest);
checkauthResponse.IsValid = checkauthRequest.IsValid;
op.Channel.Send(checkauthResponse).Send();
if (!tamper) {
// Respond to the replay attack.
checkauthRequest = op.Channel.ReadFromRequest();
checkauthResponse = new CheckAuthenticationResponse(checkauthRequest);
checkauthResponse.IsValid = checkauthRequest.IsValid;
op.Channel.Send(checkauthResponse).Send();
}
}
});
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";
}
};
}
coordinator.Run();
}
}
}