/// <summary> /// Creates the association at the provider side after the association request has been received. /// </summary> /// <param name="request">The association request.</param> /// <param name="associationStore">The OpenID Provider's association store or handle encoder.</param> /// <param name="securitySettings">The security settings of the Provider.</param> /// <returns> /// The newly created association. /// </returns> /// <remarks> /// The response message is updated to include the details of the created association by this method, /// but the resulting association is <i>not</i> added to the association store and must be done by the caller. /// </remarks> public Association CreateAssociationAtProvider(AssociateRequest request, IProviderAssociationStore associationStore, ProviderSecuritySettings securitySettings) { var diffieHellmanRequest = request as AssociateDiffieHellmanRequest; ErrorUtilities.VerifyInternal(diffieHellmanRequest != null, "Expected a DH request type."); this.SessionType = this.SessionType ?? request.SessionType; // Go ahead and create the association first, complete with its secret that we're about to share. Association association = HmacShaAssociationProvider.Create(this.Protocol, this.AssociationType, AssociationRelyingPartyType.Smart, associationStore, securitySettings); // We now need to securely communicate the secret to the relying party using Diffie-Hellman. // We do this by performing a DH algorithm on the secret and setting a couple of properties // that will be transmitted to the Relying Party. The RP will perform an inverse operation // using its part of a DH secret in order to decrypt the shared secret we just invented // above when we created the association. using (DiffieHellman dh = new DiffieHellmanManaged( diffieHellmanRequest.DiffieHellmanModulus ?? AssociateDiffieHellmanRequest.DefaultMod, diffieHellmanRequest.DiffieHellmanGen ?? AssociateDiffieHellmanRequest.DefaultGen, AssociateDiffieHellmanRequest.DefaultX)) { HashAlgorithm hasher = DiffieHellmanUtilities.Lookup(this.Protocol, this.SessionType); this.DiffieHellmanServerPublic = DiffieHellmanUtilities.EnsurePositive(dh.CreateKeyExchange()); this.EncodedMacKey = DiffieHellmanUtilities.SHAHashXorSecret(hasher, dh, diffieHellmanRequest.DiffieHellmanConsumerPublic, association.SecretKey); } return(association); }
/// <summary> /// Called to create the Association based on a request previously given by the Relying Party. /// </summary> /// <param name="request">The prior request for an association.</param> /// <param name="associationStore">The Provider's association store.</param> /// <param name="securitySettings">The security settings of the Provider.</param> /// <returns> /// The created association. /// </returns> /// <remarks> /// <para>The caller will update this message's /// <see cref="AssociateSuccessfulResponse.ExpiresIn"/> and /// <see cref="AssociateSuccessfulResponse.AssociationHandle"/> /// properties based on the <see cref="Association"/> returned by this method, but any other /// association type specific properties must be set by this method.</para> /// <para>The response message is updated to include the details of the created association by this method, /// but the resulting association is <i>not</i> added to the association store and must be done by the caller.</para> /// </remarks> public Association CreateAssociationAtProvider(AssociateRequest request, IProviderAssociationStore associationStore, ProviderSecuritySettings securitySettings) { Association association = HmacShaAssociationProvider.Create(Protocol, this.AssociationType, AssociationRelyingPartyType.Smart, associationStore, securitySettings); this.MacKey = association.SecretKey; return(association); }
/// <summary> /// Gets a private Provider association used for signing messages in "dumb" mode. /// </summary> /// <returns>An existing or newly created association.</returns> protected override Association GetDumbAssociationForSigning() { // If no assoc_handle was given or it was invalid, the only thing // left to do is sign a message using a 'dumb' mode association. Protocol protocol = Protocol.Default; Association association = HmacShaAssociationProvider.Create(protocol, protocol.Args.SignatureAlgorithm.HMAC_SHA256, AssociationRelyingPartyType.Dumb, this.opAssociations, this.opSecuritySettings); return(association); }
/// <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(); }
private void ParameterizedCheckIdTest(Protocol protocol, string assocType) { Association assoc = HmacShaAssociationProvider.Create( protocol, assocType, AssociationRelyingPartyType.Smart, this.provider.AssociationStore, this.provider.SecuritySettings); var checkidRequest = this.CreateCheckIdRequest(true); MeasurePerformance( async delegate { var request = (IAuthenticationRequest)await this.provider.GetRequestAsync(checkidRequest); request.IsAuthenticated = true; var response = await this.provider.PrepareResponseAsync(request); Assert.IsInstanceOf <PositiveAssertionResponse>(((HttpResponseMessageWithOriginal)response).OriginalMessage); }, maximumAllowedUnitTime: 6.8e4f); }
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); } } } }
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."); 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); 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(); }
/// <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()); } }