I'm having a problem with a custom certificate validator when using TLS 1.2
I have set up a custom validator by inheriting from X509CertificateValidator and implementing the Validate() function.
However, for some reason Validate() function never gets called, and my client gets the error:
The caller was not authenticated by the service
The inner exception:
The request for security token could not be satisfied because authentication failed.
This works fine with TLS 1.0 (with that enabled, I can set a breakpoint in Validate() and it gets hit, but disabled, it doesn't.)
As advised by other questions, I have tried adding this both in the client and the server:
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
...and this in the client's config file:
<AppContextSwitchOverrides value="Switch.System.ServiceModel.DisableUsingServicePointManagerSecurityProtocols=false;Switch.System.Net.DontEnableSchUseStrongCrypto=false;;Switch.System.Net.DontEnableSystemDefaultTlsVersions=false" />
... and this in the server's web.config file:
<add key="AppContext.SetSwitch:Switch.System.Net.DontEnableSchUseStrongCrypto" value="false" />
<add key="AppContext.SetSwitch:Switch.System.Net.DontEnableSystemDefaultTlsVersions" value="false" />
Here is the code that creates the custom validator:
protected override void ApplyConfiguration() // Overrides ServiceHost.ApplyConfiguration()
{
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
base.ApplyConfiguration();
var binding = new MyAppHttpBinding(); // Custom object inheriting from CustomBinding - See below...
AddServiceEndpoint(typeof(IMyService), binding);
Credentials.ClientCertificate.Authentication.CertificateValidationMode = X509CertificateValidationMode.Custom;
var configuration = WebConfigurationManager.OpenWebConfiguration("~");
var clientCertificate = configuration.GetCertificate("MyApp.ClientCertificate");
var serviceCertificate = configuration.GetCertificate("MyApp.ServerCertificate");
Credentials.ClientCertificate.Authentication.CustomCertificateValidator = new ThumbprintCertificateValidator(new[] { clientCertificate });
Credentials.ServiceCertificate.Certificate = serviceCertificate;
}
... and the Validate() function...
(Doesn't appear to hit this with just TLS 1.2)
public void Validate(string thumbprint)
{
var valid = Thumbprints
.Contains(thumbprint);
if (!valid)
{
throw new SecurityTokenValidationException("Certificate thumbprint does not match any in certificate store.");
}
}
/// <summary>
/// Validates the certificate's thumbprint with those specified.
/// </summary>
public override void Validate(X509Certificate2 certificate)
{
var thumbprint = certificate.Thumbprint;
Validate(thumbprint);
}
Here is the initialisation code for MyAppHttpBinding called from its constructor
var sslNegotiationBindingElement = SecurityBindingElement.CreateSslNegotiationBindingElement(true);
sslNegotiationBindingElement.MessageSecurityVersion = MessageSecurityVersion.WSSecurity11WSTrustFebruary2005WSSecureConversationFebruary2005WSSecurityPolicy11BasicSecurityProfile10;
var secureConversationBindingElement = SecurityBindingElement.CreateSecureConversationBindingElement(sslNegotiationBindingElement);
Elements.Add(new TransactionFlowBindingElement());
Elements.Add(secureConversationBindingElement);
Elements.Add(new TextMessageEncodingBindingElement());
Elements.Add(new HttpTransportBindingElement());
To use TLS, see the documentation Transport Layer Security (TLS) best practice with the .NET Framework.
1.For TLS 1.2, target .NET Framework 4.7 or higher on your application and .NET Framework 4.7.1 or higher on your WCF application.
In the example below, replace 4.7.2 with whatever version of the framework you are currently using >= 4.7
<system.web>
<compilation targetFramework="4.7.2"></compilation>
<httpRuntime targetFramework="4.7.2" />
</system.web>
2.It is recommended not to specify the TLS version. Configure your code to let the OS decide the TLS version.
3.One more symbol (;) is written in the screenshot code, and the configuration of the client and server should be as consistent as possible.
https://learn.microsoft.com/en-us/dotnet/framework/configure-apps/file-schema/runtime/appcontextswitchoverrides-element
Related
I'm bulding a web api with owin and JWT, when I execute the project it shows me an exception. This is a method that consume the JWT, I'm having problems with as:AudienceId and as:AudienceSecret, it tells me that AudienceSecret is null. What can be the issue?
private void ConfigureOAuthTokenConsumption(IAppBuilder app)
{
var issuer = "http://localhost:59822";
string audienceId = ConfigurationManager.AppSettings["as:AudienceId"];
byte[] audienceSecret = TextEncodings.Base64Url.Decode(ConfigurationManager.AppSettings["as:AudienceSecret"]);
// Api controllers with an [Authorize] attribute will be validated with JWT
app.UseJwtBearerAuthentication(
new JwtBearerAuthenticationOptions
{
AuthenticationMode = AuthenticationMode.Active,
AllowedAudiences = new[] { audienceId },
IssuerSecurityTokenProviders = new IIssuerSecurityTokenProvider[]
{
new SymmetricKeyIssuerSecurityTokenProvider(issuer, audienceSecret)
}
});
}
This statement is looking for the appSettings key as:AudienceSecret in web.config file.
byte[] audienceSecret = TextEncodings.Base64Url.Decode(ConfigurationManager.AppSettings["as:AudienceSecret"]);
Since the result audienceSecret is null. The statement is not finding the key as:AudienceSecret. The most reasonable explanation is the key is missing from web.config. You need to add the key to web.config like this.Something like this
<appSettings>
<add key="as:AudienceId" value="414e1927a3884f68abc79f7283837fd1" />
<add key="as:AudienceSecret" value="qMCdFDQuF23RV1Y-1Gq9L3cF3VmuFwVbam4fMTdAfpo" />
</appSettings>
You may want to use different values cause it's ... a secret.
I've had quite the challenge in handling the following scenario.
I want to use the Unity DI Framework to create a new channel for my service when needed.
The service is secured with Federated security.
The service is not called from within a service hosted within IIS, but called from within a self-hosted WCF service.
My current issue is occurring in step 3 above - how do I bootstrap the token and yet then satisfy the above 2 requirements also?? I'll outline the solutions to each step though as they're non-trivial and will hopefully assist someone else with this issue.
Solving issue 1:
The following code-snippet will create a new instance of the channel anytime one is required.
container.RegisterType<IMyWcfService>(new PerResolveLifetimeManager(),
new InjectionFactory(
x => new ChannelFactoryWithChannelFactoryOperations<IMyWcfService>("*")
.CreateChannel()));
Here's some examples of people doing this better than I:
http://unity.codeplex.com/discussions/211736
https://gist.github.com/tracker1/5675161
You can also use alternative lifetime managers as well e.g. the TransientLifetimeManager would work well here.
Solving Issue 2:
Now the real difficulty begins - how do I include a security token with the InjectionFactory?
Clearly I'm going to want to use CreatChannelWithIssuedToken, but I'm going to need to grab the bootstrap token to do so. This is fairly well documented around the net, e.g. here:
http://www.cloudidentity.com/blog/2012/11/30/using-the-bootstrapcontext-property-in-net-4-5-2/
Some important things to note: make sure that you have a serviceBehaviors section in the config that specifies useIdentityConfiguration="true" otherwise your system.identityModel section will be ignored e.g.
<serviceBehaviors>
<behavior name="">
<serviceCredentials useIdentityConfiguration="true" />
</behavior>
</serviceBehaviors>
And then your system.identityModel section should also feature:
<system.identityModel>
<identityConfiguration>
<securityTokenHandlers>
<securityTokenHandlerConfiguration saveBootstrapContext="true" />
</securityTokenHandlers>
</identityConfiguration>
</system.identityModel>
Then, provided that you've made a request to set this correctly on the session (see my other question: AJAX call against REST endpoint secured with Thinktecture's IdentityServer STS it will be available in the session whenever you access the bootstrap context security token like so:
var bootstrapContext = ((ClaimsIdentity) Thread.CurrentPrincipal.Identity).BootstrapContext
as BootstrapContext;
SecurityToken securityToken = bootstrapContext.SecurityToken;
You can then either change your InjectionFactory to look like this:
container.RegisterType<IControllerConfigurationService>(new PerResolveLifetimeManager(),
new InjectionFactory(
x =>
{
var bootstrapContext = ((ClaimsIdentity)
Thread.CurrentPrincipal.Identity).BootstrapContext as BootstrapContext;
var securityToken = bootstrapContext.SecurityToken;
return new ChannelFactoryWithChannelFactoryOperations<IMyWcfService>("*")
.CreateChannelWithIssuedToken(securityToken);
}));
Or perhaps better yet, create a class that inherits from ChannelFactory and add a method that pulls that combines the above logic into a single CreateChannelWithIssuedTokenUsingBootstrapContext method:
public class ChannelFactoryWithChannelFactoryOperations<T> : ChannelFactory<T>
{
protected ChannelFactoryWithChannelFactoryOperations(Type channelType) : base(channelType)
{
}
public ChannelFactoryWithChannelFactoryOperations()
{
}
public ChannelFactoryWithChannelFactoryOperations(string endpointConfigurationName)
: base(endpointConfigurationName)
{
}
public ChannelFactoryWithChannelFactoryOperations(string endpointConfigurationName,
EndpointAddress remoteAddress) : base(endpointConfigurationName, remoteAddress)
{
}
public ChannelFactoryWithChannelFactoryOperations(Binding binding) : base(binding)
{
}
public ChannelFactoryWithChannelFactoryOperations(Binding binding, string remoteAddress)
: base(binding, remoteAddress)
{
}
public ChannelFactoryWithChannelFactoryOperations(Binding binding, EndpointAddress remoteAddress)
: base(binding, remoteAddress)
{
}
public ChannelFactoryWithChannelFactoryOperations(ServiceEndpoint endpoint) : base(endpoint)
{
}
public T CreateChannelWithIssuedTokenUsingBootstrapContext()
{
var bootstrapContext =
((ClaimsIdentity) Thread.CurrentPrincipal.Identity).BootstrapContext as BootstrapContext;
SecurityToken securityToken = bootstrapContext.SecurityToken;
return CreateChannelWithIssuedToken(securityToken);
}
}
This allows you to then just call this:
container.RegisterType<IMyWcfService>(new PerResolveLifetimeManager(),
new InjectionFactory(
x => new ChannelFactoryWithChannelFactoryOperations<IMyWcfService>("*")
.CreateChannelWithIssuedTokenUsingBootstrapContext()));
Solving issue 3:
Adding upon the complexity of the above two issues, I'm now trying the same thing outside of IIS in my own Self-Hosted WCF service. This same service is completely stateless, so here's where my next dilemma occurs:
Bootstrapping of the security token still occurs, but it isn't occurring on the correct Thread. Unity seems to run its InjectionFactory in a separate Thread to the actual service call execution.
i.e. when the InjectionFactory delegate above executes, the CurrentPrincipal is an unauthorized GenericPrincipal. This is different from what we had in issue 2 above - where it is an authorized ClaimsPrincipal - I believe this is all set up by the IIS session (please do feel free to correct if I'm incorrect).
Funnily enough, if we then replace the above with
container.RegisterType<IMyWcfService>(new PerResolveLifetimeManager(),
new InjectionFactory(
x => new ChannelFactoryWithChannelFactoryOperations<IMyWcfService>("*")
.CreateChannel()));
i.e. now just inject an unsecured Channel object, we can then observe that in our self-hosted WCF service, where we actually try to interact with the channel, the Thread.CurrentPrincipal is the authenticated ClaimsPrincipal with the SecurityToken correctly Bootstrapped on the principal.
So the problem can be summarized as the following:
because the InjectionFactory delegate executes on a thread on which no Authentication/Authorization has yet taken place, the SecurityToken is not actually available to pass to the creation of the channel.
Does anybody have any suggestions on how I might solve this issue? Have I already painted myself into a corner with this particular combination of self-hosted WCF and unity?
Thanks,
Clint
I've found a way to do this, though I'd love a better suggestion. The SessionSecurityTokenHandler ValidateToken method always executes on the same Thread as the InjectionFactory delegate, so we can set that Thread's CurrentPrincipal in the ValidateToken method of a CustomSessionSecurityTokenHandler like so:
public class CustomSessionSecurityTokenHandler: SessionSecurityTokenHandler
{
public override ReadOnlyCollection<ClaimsIdentity> ValidateToken(SecurityToken token)
{
var claimsIdentities = base.ValidateToken(token);
Thread.CurrentPrincipal = new ClaimsPrincipal(claimsIdentities);
return claimsIdentities;
}
}
And then the system.identityModel configuration needs to be modified to include the custom securityTokenHandler:
<securityTokenHandlers>
<remove type="System.IdentityModel.Tokens.SessionSecurityTokenHandler, System.IdentityModel, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"/>
<add type="MyAssembly.CustomSessionSecurityTokenHandler, MyAssembly" />
</securityTokenHandlers>
Having done that, attempts to access the security token from the Bootstrap context then succeed:
container.RegisterType<IControllerConfigurationService>(new PerResolveLifetimeManager(),
new InjectionFactory(
x =>
{
var bootstrapContext = ((ClaimsIdentity)
Thread.CurrentPrincipal.Identity).BootstrapContext as BootstrapContext;
var securityToken = bootstrapContext.SecurityToken;
return new ChannelFactoryWithChannelFactoryOperations<IMyWcfService>("*")
.CreateChannelWithIssuedToken(securityToken);
}));
Please feel free to add any alternative suggestions! :)
Thanks,
Clint
I'm trying to add to a MVC4 webapi project the simple membership provider authentication mechanism found in a MVC 4 web application project, for a hybrid application serving its pages with a rich JS content, which uses AJAX calls to webapi actions to perform its tasks. I need the app users to authenticate before they can work with the apps provided in these pages, so I think I'll be fine with the forms authentication. I thus need to add it to the existing WebApi project and let my authorized-only actions return a 302 (redirect user to login page) rather than a 401.
Anyway, I'm missing something because as soon as I try to use a WebSecurity method I get the following exception:
System.InvalidOperationException was caught
Message=To call this method, the "Membership.Provider" property must be an instance of "ExtendedMembershipProvider".
Source=WebMatrix.WebData
Could anyone suggest a fix? Here are the steps I took for adding authorization:
1) Web.config: add to system.web:
<authentication mode="Forms">
<forms loginUrl="~/Account/Login" timeout="2880" />
</authentication>
Add to appsettings (the 2nd entry is for replacing 401 with 302):
<add key="enableSimpleMembership" value="true"/>
<add key="webapi:EnableSuppressRedirect" value="false" />
Also remove profile, membership and rolemanager sections from the original template (they are not intended to be used with simple membership).
2) add NuGet packages for OpenAuth (DotNetOpenAuth Core, DotNetOpenAuth ext for ASP.NET, DotNetOpenAuth 1.0(a) consumer, DotNetOpenAuth 1.0(a), DotNetOpenAuth OpenID Core, DotNetOpenAuth OpenID Relying Party).
3) add InitializeSimpleMembership.cs to Filters (the code is pretty standard, see below).
4) copy from an MVC web app project the models in AccountModels.cs, all the views in Views/Account, and the AccountController.cs.
The InitializeSimpleMembership code is here:
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
public sealed class InitializeSimpleMembershipAttribute : ActionFilterAttribute
{
private static SimpleMembershipInitializer _initializer;
private static object _initializerLock = new object();
private static bool _isInitialized;
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
LazyInitializer.EnsureInitialized(ref _initializer, ref _isInitialized, ref _initializerLock);
}
private class SimpleMembershipInitializer
{
private static void SeedData()
{
// seed data: users and roles
if (!WebSecurity.UserExists("TheAdminGuyName"))
WebSecurity.CreateUserAndAccount("TheAdminGuyName", "password");
if (!Roles.RoleExists("administrator")) Roles.CreateRole("administrator");
if (!Roles.IsUserInRole("TheAdminGuyName", "administrator"))
Roles.AddUserToRole("TheAdminGuyName", "administrator");
}
public SimpleMembershipInitializer()
{
Database.SetInitializer<UsersContext>(null);
try
{
using (var context = new UsersContext())
{
if (!context.Database.Exists())
{
((IObjectContextAdapter)context).ObjectContext.CreateDatabase();
}
}
WebSecurity.InitializeDatabaseConnection("DefaultConnection", "User", "UserId", "UserName", autoCreateTables: true);
SeedData();
}
catch (Exception ex)
{
throw new InvalidOperationException("The ASP.NET Simple Membership database could not be initialized. For more information, please see http://go.microsoft.com/fwlink/?LinkId=256588", ex);
}
}
}
}
This might be relevant, as it mentions your error specifically:
http://weblogs.asp.net/jgalloway/archive/2012/08/29/simplemembership-membership-providers-universal-providers-and-the-new-asp-net-4-5-web-forms-and-asp-net-mvc-4-templates.aspx
Situation
I have a client library that uses the Windows Azure AppFabric Service Bus NetTcpRelayBinding to connect to the endpoint. The client library is hosted by an application, that is not necessarily a .NET-application. Anyway, app.config is out of the question, which means everything should be configured in code.
Machine.config is one option, but it's better if it can be avoided. A local custom proxy or a front-end server could be another option, but I'd like to explore this option first, before changing architecture that radically.
There are no problems with system bindings, but I haven't figured out or found a solution how to add the following configuration to a ChannelFactory in code:
<extensions>
<bindingElementExtensions>
<add name="tcpRelayTransport" type="Microsoft.ServiceBus.Configuration.TcpRelayTransportElement, Microsoft.ServiceBus, Version=1.6.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
</bindingElementExtensions>
<bindingExtensions>
<add name="netTcpRelayBinding" type="Microsoft.ServiceBus.Configuration.NetTcpRelayBindingCollectionElement, Microsoft.ServiceBus, Version=1.6.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
</bindingExtensions>
</extensions>
Here is the solution that has now been tested. Hopefully it makes clear, what the problem really was and maybe it helps somebody else with a similar problem. Credits to co-worker, who had the time to investigate the issue.
The solution was to edit dynamically the configuration and add the reference to the required Binding Element Extensions (without app.config in any layer or without changing machine.config in client machines):
var service = ServiceModelSectionGroup.GetSectionGroup(ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None));
var tcpRelayTransportExtension = new ExtensionElement("tcpRelayTransport", "Microsoft.ServiceBus.Configuration.TcpRelayTransportElement, Microsoft.ServiceBus, Version=1.6.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35");
var netTcpRelayTransportExtension = new ExtensionElement("netTcpRelayBinding", "Microsoft.ServiceBus.Configuration.NetTcpRelayBindingCollectionElement, Microsoft.ServiceBus, Version=1.6.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35");
if (service.Extensions.BindingElementExtensions.ContainsKey(tcpRelayTransportExtension.Name) == false)
{
service.Extensions.BindingElementExtensions.Add(tcpRelayTransportExtension);
}
if (service.Extensions.BindingElementExtensions.ContainsKey(netTcpRelayTransportExtension.Name) == false)
{
service.Extensions.BindingElementExtensions.Add(netTcpRelayTransportExtension);
}
Thanks to everyone who tried to help, though!
You can add the binding element extension via code by writing a custom service host factory. A similar question has been answered here
Based on your type of WCF service you just need to inherit the appropriate servicehostfactory class when writing a custom service host factory. For example as shown below:
How to build a custom service host
If building a REST WCF Service use WebServiceHostFactory
If building a WCF service use ServiceHostFactory
A sample for this without config is available here: http://code.msdn.microsoft.com/windowsazure/Relayed-Messaging-Windows-0d2cede3
Following is the code snippet:
ChannelFactory<IEchoChannel> channelFactory = null;
IEchoChannel channel = null;
try
{
//Create a Behavior for the Credentials
TransportClientEndpointBehavior sharedSecretServiceBusCredential = new TransportClientEndpointBehavior();
sharedSecretServiceBusCredential.TokenProvider = TokenProvider.CreateSharedSecretTokenProvider(issuerName, issuerSecret);
//Create a Channel Factory
channelFactory = new ChannelFactory<IEchoChannel>(new NetTcpRelayBinding(), new EndpointAddress(serviceAddress));
channelFactory.Endpoint.Behaviors.Add(sharedSecretServiceBusCredential);
LogMessage("Opening channel to: {0}", serviceAddress);
channel = channelFactory.CreateChannel();
channel.Open();
LogMessage("Sending: {0}", echoTextBox.Text);
string echoedText = channel.Echo(echoTextBox.Text);
LogMessage("Received: {0}", echoedText);
echoTextBox.Text = string.Empty;
LogMessage("Closing channel");
channel.Close();
LogMessage("Closing factory");
channelFactory.Close();
}
catch (Exception ex)
{
LogMessage("Error sending: {0}<br/>{1}", ex.Message, ex.StackTrace.Replace("\n", "<br/>"));
// Close the channel and factory properly
if (channel != null)
{
CloseCommunicationObject(channel);
}
if (channelFactory != null)
{
CloseCommunicationObject(channelFactory);
}
}
I get the following error when i try to consume my wcf service
Could not find endpoint element with name 'http://localhost:8080/Provider/basic' and contract 'Provider.IProvider' in the ServiceModel client configuration section
I can however connect to my base address through the WCF Test Client on http://localhost:8080/Provider
I've tried updating my service reference and that didn't work, Anyone know what is wrong with my setup
public ServiceHost ProviderServiceHost { get; set; }
private void StartProvider()
{
if (ProviderServiceHost != null)
Abort();
ProviderServiceHost = new ServiceHost(typeof(Provider), new Uri("http://localhost:8080/Provider"));
var binding = new BasicHttpBinding
{
Name = "basicBinding",
HostNameComparisonMode = HostNameComparisonMode.WeakWildcard,
Security = { Mode = BasicHttpSecurityMode.None }
};
var metadataBehavior = ProviderServiceHost.Description.Behaviors.Find<ServiceMetadataBehavior>();
if (metadataBehavior == null)
{
metadataBehavior = new ServiceMetadataBehavior { HttpGetEnabled = true };
ProviderServiceHost.Description.Behaviors.Add(metadataBehavior);
}
ProviderServiceHost.AddServiceEndpoint(typeof(IProvider), binding, "http://localhost:8080/Provider/basic");
ProviderServiceHost.Open();
}
My client is connecting like this
private static ProviderClient _proxy = new ProviderClient(http://localhost:8080/Provider/basic);
If i don't put in an address then i get this exception
Message "Could not find default endpoint element that references contract 'Provider.IProvider' in the ServiceModel client configuration section. This might be because no configuration file was found for your application, or because no endpoint element matching this contract could be found in the client element." string
Are you using client side configuration? As it appears you're using the ClientBase proxy I expect you've used 'Add Service Reference...' and are just the default configuration file.
The overload that you're using is new ServiceClient(string endpointConfigurationName) - the string value represents a Name, not an Address. If you check your configuration file you should see a client section has been added:
<client>
<endpoint address="http://localhost:8080/Provider/basic"
binding="basicHttpBinding"
bindingConfiguration="basicBinding_IProvider"
contract="ServiceReference1.ITest"
name="basicBinding_IProvider" />
</client>
So if you change your Client constructor to use this Name property it should work better for you.
private static ProviderClient _proxy =
new ProviderClient("basicBinding_IProvider");
On the other hand if you are not using a configuration file and want to specify the address in code, you can use a different ClientBase constructor:
using System.ServiceModel;
// ...
_client = new ProviderClient(new BasicHttpBinding(),
new EndpointAddress("http://localhost:8080/Provider/basic"));
There are a number of different overloads in the ClientBase class (your ProviderClient) which allow you to specify various properties.