public void MissingSignedParameter() {
			var cryptoStore = new MemoryCryptoKeyStore();
			byte[] associationSecret = Convert.FromBase64String("rsSwv1zPWfjPRQU80hciu8FPDC+GONAMJQ/AvSo1a2M=");
			string handle = "{634477555066085461}{TTYcIg==}{32}";
			cryptoStore.StoreKey(ProviderAssociationKeyStorage.PrivateAssociationBucket, handle, new CryptoKey(associationSecret, DateTime.UtcNow.AddDays(1)));

			var signer = new ProviderSigningBindingElement(new ProviderAssociationKeyStorage(cryptoStore), new ProviderSecuritySettings());
			var testChannel = new TestChannel(new OpenIdProviderMessageFactory());
			signer.Channel = testChannel;

			var buggyRPMessage = new Dictionary<string, string>() {
				{ "openid.assoc_handle", "{634477555066085461}{TTYcIg==}{32}" },
				{ "openid.claimed_id", "https://openid.stackexchange.com/user/f5e91123-e5b4-43c5-871f-5f276c75d31a" },
				{ "openid.identity", "https://openid.stackexchange.com/user/f5e91123-e5b4-43c5-871f-5f276c75d31a" },
				{ "openid.mode", "check_authentication" },
				{ "openid.op_endpoint", "https://openid.stackexchange.com/openid/provider" },
				{ "openid.response_nonce", "2011-08-01T00:32:10Zvdyt3efw" },
				{ "openid.return_to", "http://openid-consumer.appspot.com/finish?session_id=1543025&janrain_nonce=2011-08-01T00%3A32%3A09ZIPGz7D" },
				{ "openid.sig", "b0Rll6Kt1KKBWWBEg/qBvW3sQYtmhOUmpI0/UREBVZ0=" },
				{ "openid.signed", "claimed_id,identity,assoc_handle,op_endpoint,return_to,response_nonce,ns.sreg,sreg.email,sreg.fullname" },
				{ "openid.sreg.email", "*****@*****.**" },
				{ "openid.sreg.fullname", "Kevin K Montrose" },
			};
			var message = (CheckAuthenticationRequest)testChannel.Receive(buggyRPMessage, new MessageReceivingEndpoint(OPUri, HttpDeliveryMethods.PostRequest));
			var originalResponse = new IndirectSignedResponse(message, signer.Channel);
			signer.ProcessIncomingMessage(originalResponse);
		}
		protected static Mock<IAuthorizationServer> CreateAuthorizationServerMock() {
			var authHostMock = new Mock<IAuthorizationServer>();
			var cryptoStore = new MemoryCryptoKeyStore();
			authHostMock.Setup(m => m.GetClient(ClientId)).Returns(ClientDescription);
			authHostMock.SetupGet(m => m.CryptoKeyStore).Returns(cryptoStore);
			authHostMock.Setup(m => m.IsAuthorizationValid(It.Is<IAuthorizationDescription>(d => d.ClientIdentifier == ClientId && d.User == ResourceOwnerUsername))).Returns(true);
			return authHostMock;
		}
		protected static Mock<IAuthorizationServer> CreateAuthorizationServerMock() {
			var authHostMock = new Mock<IAuthorizationServer>();
			var cryptoStore = new MemoryCryptoKeyStore();
			authHostMock.Setup(m => m.GetClient(ClientId)).Returns(ClientDescription);
			authHostMock.SetupGet(m => m.CryptoKeyStore).Returns(cryptoStore);
			authHostMock.Setup(
				m =>
				m.IsAuthorizationValid(
					It.Is<IAuthorizationDescription>(
						d =>
						d.ClientIdentifier == ClientId && d.User == ResourceOwnerUsername &&
						MessagingUtilities.AreEquivalent(d.Scope, TestScopes)))).Returns(true);
			authHostMock.Setup(m => m.IsResourceOwnerCredentialValid(ResourceOwnerUsername, ResourceOwnerPassword)).Returns(true);
			authHostMock.Setup(m => m.GetAccessTokenParameters(It.IsAny<IAccessTokenRequest>())).Returns(new AccessTokenParameters());
			return authHostMock;
		}
		protected static Mock<IAuthorizationServerHost> CreateAuthorizationServerMock() {
			var authHostMock = new Mock<IAuthorizationServerHost>();
			var cryptoStore = new MemoryCryptoKeyStore();
			authHostMock.Setup(m => m.GetClient(ClientId)).Returns(ClientDescription);
			authHostMock.SetupGet(m => m.CryptoKeyStore).Returns(cryptoStore);
			authHostMock.Setup(
				m =>
				m.IsAuthorizationValid(
					It.Is<IAuthorizationDescription>(
						d =>
						d.ClientIdentifier == ClientId && d.User == ResourceOwnerUsername &&
						MessagingUtilities.AreEquivalent(d.Scope, TestScopes)))).Returns(true);
			string canonicalUserName = ResourceOwnerUsername;
			authHostMock.Setup(m => m.TryAuthorizeResourceOwnerCredentialGrant(ResourceOwnerUsername, ResourceOwnerPassword, It.IsAny<IAccessTokenRequest>(), out canonicalUserName)).Returns(true);
			authHostMock.Setup(m => m.CreateAccessToken(It.IsAny<IAccessTokenRequest>())).Returns(new AccessTokenResult(new AuthorizationServerAccessToken()));
			return authHostMock;
		}
		/// <summary>
		/// Simulates an extension request and response.
		/// </summary>
		/// <param name="protocol">The protocol to use in the roundtripping.</param>
		/// <param name="requests">The extensions to add to the request message.</param>
		/// <param name="responses">The extensions to add to the response message.</param>
		/// <remarks>
		/// This method relies on the extension objects' Equals methods to verify
		/// accurate transport.  The Equals methods should be verified by separate tests.
		/// </remarks>
		internal static void Roundtrip(
			Protocol protocol,
			IEnumerable<IOpenIdMessageExtension> requests,
			IEnumerable<IOpenIdMessageExtension> responses) {
			var securitySettings = new ProviderSecuritySettings();
			var cryptoKeyStore = new MemoryCryptoKeyStore();
			var associationStore = new ProviderAssociationHandleEncoder(cryptoKeyStore);
			Association association = HmacShaAssociationProvider.Create(protocol, protocol.Args.SignatureAlgorithm.Best, AssociationRelyingPartyType.Smart, associationStore, securitySettings);
			var coordinator = new OpenIdCoordinator(
				rp => {
					RegisterExtension(rp.Channel, Mocks.MockOpenIdExtension.Factory);
					var requestBase = new CheckIdRequest(protocol.Version, OpenIdTestBase.OPUri, AuthenticationRequestMode.Immediate);
					OpenIdTestBase.StoreAssociation(rp, OpenIdTestBase.OPUri, association);
					requestBase.AssociationHandle = association.Handle;
					requestBase.ClaimedIdentifier = "http://claimedid";
					requestBase.LocalIdentifier = "http://localid";
					requestBase.ReturnTo = OpenIdTestBase.RPUri;

					foreach (IOpenIdMessageExtension extension in requests) {
						requestBase.Extensions.Add(extension);
					}

					rp.Channel.Respond(requestBase);
					var response = rp.Channel.ReadFromRequest<PositiveAssertionResponse>();

					var receivedResponses = response.Extensions.Cast<IOpenIdMessageExtension>();
					CollectionAssert<IOpenIdMessageExtension>.AreEquivalentByEquality(responses.ToArray(), receivedResponses.ToArray());
				},
				op => {
					RegisterExtension(op.Channel, Mocks.MockOpenIdExtension.Factory);
					var key = cryptoKeyStore.GetCurrentKey(ProviderAssociationHandleEncoder.AssociationHandleEncodingSecretBucket, TimeSpan.FromSeconds(1));
					op.CryptoKeyStore.StoreKey(ProviderAssociationHandleEncoder.AssociationHandleEncodingSecretBucket, key.Key, key.Value);
					var request = op.Channel.ReadFromRequest<CheckIdRequest>();
					var response = new PositiveAssertionResponse(request);
					var receivedRequests = request.Extensions.Cast<IOpenIdMessageExtension>();
					CollectionAssert<IOpenIdMessageExtension>.AreEquivalentByEquality(requests.ToArray(), receivedRequests.ToArray());

					foreach (var extensionResponse in responses) {
						response.Extensions.Add(extensionResponse);
					}

					op.Channel.Respond(response);
				});
			coordinator.Run();
		}
		protected static Mock<IAuthorizationServerHost> CreateAuthorizationServerMock() {
			var authHostMock = new Mock<IAuthorizationServerHost>();
			var cryptoStore = new MemoryCryptoKeyStore();
			authHostMock.Setup(m => m.GetClient(ClientId)).Returns(ClientDescription);
			authHostMock.SetupGet(m => m.CryptoKeyStore).Returns(cryptoStore);
			authHostMock.Setup(
				m =>
				m.IsAuthorizationValid(
					It.Is<IAuthorizationDescription>(
						d =>
						d.ClientIdentifier == ClientId && d.User == ResourceOwnerUsername &&
						MessagingUtilities.AreEquivalent(d.Scope, TestScopes)))).Returns(true);
			authHostMock
				.Setup(m => m.CheckAuthorizeResourceOwnerCredentialGrant(ResourceOwnerUsername, ResourceOwnerPassword, It.IsAny<IAccessTokenRequest>()))
				.Returns<string, string, IAccessTokenRequest>((p1, p2, p3) => new AutomatedUserAuthorizationCheckResponse(p3, true, ResourceOwnerUsername));
			authHostMock.Setup(m => m.CreateAccessToken(It.IsAny<IAccessTokenRequest>())).Returns(new AccessTokenResult(new AuthorizationServerAccessToken() { AccessTokenSigningKey = AsymmetricKey }));
			return authHostMock;
		}
		public void SignaturesMatchKnownGood() {
			Protocol protocol = Protocol.V20;
			var settings = new ProviderSecuritySettings();
			var cryptoStore = new MemoryCryptoKeyStore();
			byte[] associationSecret = Convert.FromBase64String("rsSwv1zPWfjPRQU80hciu8FPDC+GONAMJQ/AvSo1a2M=");
			string handle = "mock";
			cryptoStore.StoreKey(ProviderAssociationKeyStorage.SharedAssociationBucket, handle, new CryptoKey(associationSecret, DateTime.UtcNow.AddDays(1)));

			var store = new ProviderAssociationKeyStorage(cryptoStore);
			SigningBindingElement signer = new ProviderSigningBindingElement(store, settings);
			signer.Channel = new TestChannel(this.MessageDescriptions);

			IndirectSignedResponse message = new IndirectSignedResponse(protocol.Version, new Uri("http://rp"));
			ITamperResistantOpenIdMessage signedMessage = message;
			message.ProviderEndpoint = new Uri("http://provider");
			signedMessage.UtcCreationDate = DateTime.Parse("1/1/2009");
			signedMessage.AssociationHandle = handle;
			Assert.IsNotNull(signer.ProcessOutgoingMessage(message));
			Assert.AreEqual("o9+uN7qTaUS9v0otbHTuNAtbkpBm14+es9QnNo6IHD4=", signedMessage.Signature);
		}
		/// <summary>
		/// Simulates an extension request and response.
		/// </summary>
		/// <param name="protocol">The protocol to use in the roundtripping.</param>
		/// <param name="requests">The extensions to add to the request message.</param>
		/// <param name="responses">The extensions to add to the response message.</param>
		/// <remarks>
		/// This method relies on the extension objects' Equals methods to verify
		/// accurate transport.  The Equals methods should be verified by separate tests.
		/// </remarks>
		internal async Task RoundtripAsync(
			Protocol protocol, IEnumerable<IOpenIdMessageExtension> requests, IEnumerable<IOpenIdMessageExtension> responses) {
			var securitySettings = new ProviderSecuritySettings();
			var cryptoKeyStore = new MemoryCryptoKeyStore();
			var associationStore = new ProviderAssociationHandleEncoder(cryptoKeyStore);
			Association association = HmacShaAssociationProvider.Create(
				protocol,
				protocol.Args.SignatureAlgorithm.Best,
				AssociationRelyingPartyType.Smart,
				associationStore,
				securitySettings);

			this.HandleProvider(
				async (op, req) => {
					ExtensionTestUtilities.RegisterExtension(op.Channel, Mocks.MockOpenIdExtension.Factory);
					var key = cryptoKeyStore.GetCurrentKey(
						ProviderAssociationHandleEncoder.AssociationHandleEncodingSecretBucket, TimeSpan.FromSeconds(1));
					op.CryptoKeyStore.StoreKey(
						ProviderAssociationHandleEncoder.AssociationHandleEncodingSecretBucket, key.Key, key.Value);
					var request = await op.Channel.ReadFromRequestAsync<CheckIdRequest>(req, CancellationToken.None);
					var response = new PositiveAssertionResponse(request);
					var receivedRequests = request.Extensions.Cast<IOpenIdMessageExtension>();
					CollectionAssert<IOpenIdMessageExtension>.AreEquivalentByEquality(requests.ToArray(), receivedRequests.ToArray());

					foreach (var extensionResponse in responses) {
						response.Extensions.Add(extensionResponse);
					}

					return await op.Channel.PrepareResponseAsync(response);
				});

			{
				var rp = this.CreateRelyingParty();
				ExtensionTestUtilities.RegisterExtension(rp.Channel, Mocks.MockOpenIdExtension.Factory);
				var requestBase = new CheckIdRequest(protocol.Version, OpenIdTestBase.OPUri, AuthenticationRequestMode.Immediate);
				OpenIdTestBase.StoreAssociation(rp, OpenIdTestBase.OPUri, association);
				requestBase.AssociationHandle = association.Handle;
				requestBase.ClaimedIdentifier = "http://claimedid";
				requestBase.LocalIdentifier = "http://localid";
				requestBase.ReturnTo = OpenIdTestBase.RPUri;

				foreach (IOpenIdMessageExtension extension in requests) {
					requestBase.Extensions.Add(extension);
				}

				var redirectingRequest = await rp.Channel.PrepareResponseAsync(requestBase);
				Uri redirectingResponseUri;
				this.HostFactories.AllowAutoRedirects = false;
				using (var httpClient = rp.Channel.HostFactories.CreateHttpClient()) {
					using (var redirectingResponse = await httpClient.GetAsync(redirectingRequest.Headers.Location)) {
						Assert.AreEqual(HttpStatusCode.Found, redirectingResponse.StatusCode);
						redirectingResponseUri = redirectingResponse.Headers.Location;
					}
				}

				var response =
					await
					rp.Channel.ReadFromRequestAsync<PositiveAssertionResponse>(
						new HttpRequestMessage(HttpMethod.Get, redirectingResponseUri), CancellationToken.None);
				var receivedResponses = response.Extensions.Cast<IOpenIdMessageExtension>();
				CollectionAssert<IOpenIdMessageExtension>.AreEquivalentByEquality(responses.ToArray(), receivedResponses.ToArray());
			}
		}
		private void ParameterizedAuthenticationTest(Protocol protocol, bool statelessRP, bool sharedAssociation, bool positive, bool immediate, bool tamper) {
			Contract.Requires<ArgumentException>(!statelessRP || !sharedAssociation, "The RP cannot be stateless while sharing an association with the OP.");
			Contract.Requires<ArgumentException>(positive || !tamper, "Cannot tamper with a negative response.");
			var securitySettings = new ProviderSecuritySettings();
			var cryptoKeyStore = new MemoryCryptoKeyStore();
			var associationStore = new ProviderAssociationHandleEncoder(cryptoKeyStore);
			Association association = sharedAssociation ? HmacShaAssociation.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);

					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.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);
							}
						} 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);
							}
						}
					} 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);
							}
						} else {
							Assert.IsNull(response.UserSetupUrl);
						}
					}
				},
				op => {
					if (association != null) {
						var key = cryptoKeyStore.GetCurrentKey(ProviderAssociationHandleEncoder.AssociationHandleEncodingSecretBucket, TimeSpan.FromSeconds(1));
						op.CryptoKeyStore.StoreKey(ProviderAssociationHandleEncoder.AssociationHandleEncodingSecretBucket, key.Key, key.Value);
					}

					var request = op.Channel.ReadFromRequest<CheckIdRequest>();
					Assert.IsNotNull(request);
					IProtocolMessage response;
					if (positive) {
						response = new PositiveAssertionResponse(request);
					} 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);
						}
					}
				});
			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();
		}
		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;
			int opStep = 0;
			HandleProvider(
				async (op, req) => {
					if (association != null) {
						var key = cryptoKeyStore.GetCurrentKey(
							ProviderAssociationHandleEncoder.AssociationHandleEncodingSecretBucket, TimeSpan.FromSeconds(1));
						op.CryptoKeyStore.StoreKey(
							ProviderAssociationHandleEncoder.AssociationHandleEncodingSecretBucket, key.Key, key.Value);
					}

					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);
							}

							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);
							}

							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);
								}
							}

							throw Assumes.NotReachable();
						default:
							throw Assumes.NotReachable();
					}
				});

			{
				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 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 {
						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);
						}
					}
				} 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);
					}
				}
			}
		}