I am trying to call a WCF service (netmsmq endpoint) which has been secured against ADFS. I need to open a channel with an issued token that resides in the application context.
The code below is my atempt to do that, however, I get the following exception:
The signing token System.IdentityModel.Tokens.SamlSecurityToken has no
keys. The security token is used in a context that requires it to
perform cryptographic operations, but the token contains no
cryptographic keys. Either the token type does not support
cryptographic operations, or the particular token instance does not
contain cryptographic keys. Check your configuration to ensure that
cryptographically disabled token types (for example,
UserNameSecurityToken) are not specified in a context that requires
cryptographic operations (for example, an endorsing supporting token).
I am aware this error message is to do with BearerKey tokens, however, the netMsmqBinding doesn't expose the property binding.Security.Message.IssuedKeyType = SecurityKeyType.BearerKey;
What to do? Is it even possible to call a netmsmq service with an issued token? It seems so.
public string CallServiceQueue()
{
try
{
var binding = new NetMsmqBinding(NetMsmqSecurityMode.Message);
binding.Security.Message.ClientCredentialType = MessageCredentialType.IssuedToken;
var ep = new EndpointAddress("net.msmq://localhost/private/service/helloqueue.svc");
var factory = new ChannelFactory<IHelloQueue>(binding, ep);
factory.Credentials.SupportInteractive = false;
factory.Credentials.UseIdentityConfiguration = true;
factory.Credentials.ServiceCertificate.SetDefaultCertificate(StoreLocation.LocalMachine, StoreName.My, X509FindType.FindBySerialNumber, "74 0A FE 19 E9 F0 53 9C 46 D9 F2 D6 56 A7 0C E8");
var context = (BootstrapContext)((ClaimsIdentity)Thread.CurrentPrincipal.Identity).BootstrapContext;
var channel = factory.CreateChannelWithIssuedToken(context.SecurityToken);
channel.SayHello();
((IServiceChannel)channel).Close();
return "Your message has been sent.";
}
catch (SecurityException)
{
return "Access denied.";
}
catch (Exception ex)
{
return ex.Message;
}
}
Related
I attempt the following:
A WCF client calls a STS and gets SAML assertion
The client calls a service using the SAML assertion
Now I have implemented the scenario above as three LinqPad scripts: client.linq, sts.linq (self hosted WCF service) and service.linq (self hosted WCF service). They can all be found at https://github.com/codeape2/WCF_STS
I need some help getting this to work.
Using the following code in client.linq, I am able to call my STS and get a SAML assertion:
SecurityToken GetToken()
{
var binding = new BasicHttpBinding();
var factory = new WSTrustChannelFactory(binding, stsAddress);
factory.TrustVersion = TrustVersion.WSTrustFeb2005;
var rst = new RequestSecurityToken
{
RequestType = RequestTypes.Issue,
KeyType = KeyTypes.Symmetric,
AppliesTo = new EndpointReference(serviceAddress)
};
return factory.CreateChannel().Issue(rst);
}
Next step, I use the following code to (attempt to) call my service with the SAML assertion included:
var binding = new WSFederationHttpBinding(WSFederationHttpSecurityMode.Message);
binding.Security.Message.EstablishSecurityContext = false;
var factory = new ChannelFactory<ICrossGatewayQueryITI38>(
binding,
new EndpointAddress(new Uri(serviceAddress), new DnsEndpointIdentity("LocalSTS"))
);
factory.Credentials.SupportInteractive = false;
factory.Credentials.ServiceCertificate.Authentication.CertificateValidationMode =
X509CertificateValidationMode.None;
var proxy = factory.CreateChannelWithIssuedToken(token);
var response = proxy.CrossGatewayQuery(
Message.CreateMessage(MessageVersion.Soap12WSAddressing10, "urn:ihe:iti:2007:CrossGatewayQuery", "Hello world")
);
What happens next I totally do not understand. I have fiddler running when I run the script, and here's what I see:
The first request to /STS (as expected)
The proxy.CrossGatewayQuery results in three calls to /Service:
2.1. A SOAP call with action http://schemas.xmlsoap.org/ws/2005/02/trust/RST/Issue
2.2. A SOAP call with action http://schemas.xmlsoap.org/ws/2005/02/trust/RSTR/Issue
2.3. A final SOAP call with action urn:ihe:iti:2007:CrossGatewayQuery. Using Fiddler, I notice that the SOAP security header includes the SAML assertion from step one.
The final call results in a SOAP fault back from the service: At least one security token in the message could not be validated. The saved Fiddler request/response log is here: https://drive.google.com/file/d/0B-UZlLvBjjB2S050TXRhVEo2Vmc/view?usp=sharing
If anyone could enlighten me regarding the following, I would be very grateful:
Why does the WCF client send the RST/Issue and RSTS/Issue requests to the /Service (steps 2.1 and 2.2 above) ?
How can I configure the pieces to do what I want, i.e. send one request to the STS and then one request to the service, passing the SAML assertion I got from the STS.
The first problem was with re negotiating of service credentials.
This change took care of that:
binding.Security.Message.NegotiateServiceCredential = false
Then the service had to enable WIF configuration:
host.Credentials.UseIdentityConfiguration = true;
host.Credentials.IdentityConfiguration = CreateIdentityConfig();
IdentityConfiguration CreateIdentityConfig()
{
IdentityConfiguration identityConfig = new IdentityConfiguration(false);
//AUDIENCE URI
//the token we receive contains this value, so if do not match we fail
identityConfig.AudienceRestriction.AllowedAudienceUris.Add(new Uri($"http://{Environment.MachineName}:8000/Service"));
//ISSUER NAME REGISTRY explicit the thumbprint of the accepted certificates, if the token coming in is not signed with any of these certificates then is considered invalid
var issuerNameRegistry = new ConfigurationBasedIssuerNameRegistry();
issuerNameRegistry.AddTrustedIssuer("81 5b 06 b2 7f 5b 26 30 47 3b 8a b9 56 bb 9f 9f 8c 36 20 76", "signing certificate sts"); //STS signing certificate thumbprint
identityConfig.IssuerNameRegistry = issuerNameRegistry;
identityConfig.CertificateValidationMode = System.ServiceModel.Security.X509CertificateValidationMode.None;
return identityConfig;
}
There were other changes too, the github repo has updated code that works in the master branch.
Thanks to MS support who walked me through figuring this out.
I'm trying to implement OAuth security for a WCF SOAP service. I could find samples online which talks about OAUTH and REST service. Is there any best approach to use OAuth with WCF SOAP service. If it is possible to secure WCF SOAP usig OAUth, I also would like to know whether I could use claims based authorization in this case.
The short answer is a simple yes, you can do this. I tried to find an "official" way to do this and I was not successful, mostly because OAuth is not really designed for this scenario, more on that later. First though how to actually do it. One way to do it would be to provide a custom ServiceAuthorizationManager and inside of it do something like this
public class OAuthAuthorizationManager : ServiceAuthorizationManager
{
protected override bool CheckAccessCore(OperationContext operationContext)
{
// Extract the action URI from the OperationContext. Match this against the claims
// in the AuthorizationContext.
string action = operationContext.RequestContext.RequestMessage.Headers.Action;
try
{
//get the message
var message = operationContext.RequestContext.RequestMessage;
//get the http headers
var httpHeaders = ((System.ServiceModel.Channels.HttpRequestMessageProperty)message.Properties.Values.ElementAt(message.Properties.Keys.ToList().IndexOf("httpRequest"))).Headers;
//get authorization header
var authHeader = httpHeaders.GetValues("Authorization");
if (authHeader != null)
{
var parts = authHeader[0].Split(' ');
if (parts[0] == "Bearer")
{
var tokenClaims = ValidateJwt(parts[1]);
foreach (System.Security.Claims.Claim c in tokenClaims.Where(c => c.Type == "http://www.contoso.com/claims/allowedoperation"))
{
var authorized = true;
//other claims authorization logic etc....
if(authorized)
{
return true;
}
}
}
}
return false;
}
catch (Exception)
{
throw;
}
}
private static IEnumerable<System.Security.Claims.Claim> ValidateJwt(string jwt)
{
var handler = new JwtSecurityTokenHandler();
var validationParameters = new TokenValidationParameters()
{
ValidAudience = "urn://your.audience",
IssuerSigningKey = new InMemorySymmetricSecurityKey(Convert.FromBase64String("base64encoded symmetric key")),
ValidIssuer = "urn://your.issuer",
CertificateValidator = X509CertificateValidator.None,
RequireExpirationTime = true
};
try
{
SecurityToken validatedToken;
var principal = handler.ValidateToken(jwt, validationParameters, out validatedToken);
return principal.Claims;
}
catch (Exception e)
{
return new List<System.Security.Claims.Claim>();
}
}
}
be sure to also set the web.config to use this custom class using the serviceAuthorizationElement
This example requires the System.IdentityModel.Tokens.Jwt nuget package as well, your tokens might be in another format though and in that case you would need to just replace that logic in the example. Also, note that this example is assuming you will be passing our token on the Authorization header in the http request, the OAuth 2.0 Authorization Framework: Bearer Token Usage documentation also specifies that both form encoded body parameters and URI query paramters may be used as well. The form encoded body parameter method is probably entirely incompatible with SOAP services but I see no reason you could not adapt this code to also look at the query parameter method if needed.
What this code does is for every single request to your service the CheckAccessCore method will fire, inside it attempts to extract and validate the JWT oauth token then you can use the extracted principle and associated claims to authorize or deny authorization to the request.
All of this said, I think the best approach would be to not use OAuth at all, the above works but it is a hack to how WCF SOAP services are meant to be secured. OAuth is also not meant to authenticate the user, so you will need to do that in some other way prior to passing the bearer token obtained from authentication on to your service. If you absolutely must use OAuth you can use the above to get you started, there may be better ways but it is not easy by any measure to make it work and be readable. If you have not looked into WS-Security you should do that and familiarize yourself with the abundance of information and possibilities that exist for securing a soap based service most of which have numerous examples to go on here.
We have an issue where our web app calls to CRM via Microsoft.Xrm.Sdk OriganizationServiceProxy are failing to authenticate. The issue appears to be environment specific i.e. the calls work on our DEV web server but fail when the app is promoted to our System Test environment. The code that fails is as follows:
using (var serviceProxy = this.serviceFactory.Impersonate(userProvider.PrincipalUserName).ServiceProxy)
{
var countResult = serviceProxy.RetrieveMultiple(new FetchExpression(query));
int? count = 0;
var entity = countResult.Entities.FirstOrDefault();
if (entity != null)
{
count = (int?)((AliasedValue)entity["activity_count"]).Value;
}
return count.Value;
}
The error that appears in our logs is:
System.ServiceModel.Security.SecurityNegotiationException: The caller was not authenticated by the service. ---> System.ServiceModel.FaultException: The request for security token could not be satisfied because authentication failed.
at System.ServiceModel.Security.SecurityUtils.ThrowIfNegotiationFault(Message message, EndpointAddress target)
at System.ServiceModel.Security.SspiNegotiationTokenProvider.GetNextOutgoingMessageBody(Message incomingMessage, SspiNegotiationTokenProviderState sspiState)
--- End of inner exception stack trace ---
I have double checked the apppool identity of the IIS site and CRM settings. Is there anything obvious here that we may have missed?
I found the connection to CRM Online was taking the longest time so I create one instance to pass round of the OrganizationServiceProxy with explicit credentials that I can easily switch between environments.
IServiceManagement<IOrganizationService> management = ServiceConfigurationFactory.CreateManagement<IOrganizationService>(new Uri(CrmUrl));
ClientCredentials credentials = new ClientCredentials();
credentials.UserName.UserName = CrmUserName;
credentials.UserName.Password = CrmPassword;
AuthenticationCredentials authCredentials = management.Authenticate(new AuthenticationCredentials { ClientCredentials = credentials });
SecurityTokenResponse securityTokenResponse = authCredentials.SecurityTokenResponse;
OrganizationServiceProxy orgProxy = new OrganizationServiceProxy(management, securityTokenResponse);
orgProxy.EnableProxyTypes();
_xrmService = new XrmServiceContext(orgProxy)
I'm developing a simple JAX - RS server and I need to implement Authentication and Authorization. I have found in other posts that the standard way is to use the standard Java EE web servlet container security with role-mapping in conjunction with OAuth, Form-based Authentication or Basic Authentication.
My problem is my company has a custom Authentication Provider accessible by an EJB client. The Authentication provider takes username and password in input and return a UserObject with a series of rights associated with it. If the rights list contain the one associated with my REST server, then the user is also authorized to ask for resources, otherwise a CustomAuthorizationException is thrown. In fact this is a 'one-shot Authorization', so i could enforce it with additional servlet container standard authorization.
This is an example:
String username = //acquire username;
String password = // acquire password;
UserObject user = null;
try {
user = authenticationClient.login(username, password);
if(user == null){
//erro message and forward to login page
}
verifyAuthorization(user);
} catch (CustomAuthorizationException e1) {
//manage exception
} catch (CustomAuthenticationException e2) {
//manage exception
}
How can I integrate this with JAX - RS standards?
Can anyone point me to a suitable WCF Extension Point for hooking into the WCF Pipeline to extract credentials for UserNamePasswordValidator from the headers of an incoming HTTP REST Request?
Yes I know about all the funky stunts with Http Handlers etc. you can pull to somehow get Basic/Digest Auth working but since the client I'm working on will be strictly Javascript based I've opted for a simple model where the credentials are passed using two custom headers over an SSL pipe.
Update: I've managed to improve on this by using the approach described here. While this does not solves the problem described in my question, it gets rid of having to authenticate in a authorization policy since authentication is now handled by a custom AuthenticationManager, bypassing the UsernamePasswordValidator alltogether.
For the time being I've solved the problem by combining Authentication and Authorization in a custom Authorization Policy. I'd still rather find a way to hook into the normal UserNamePasswordValidator authentication scheme because an Authorization Policy is supposed to to Authorization not Authentication.
internal class RESTAuthorizationPolicy : IAuthorizationPolicy
{
public RESTAuthorizationPolicy()
{
Id = Guid.NewGuid().ToString();
Issuer = ClaimSet.System;
}
public bool Evaluate(EvaluationContext evaluationContext, ref object state)
{
const String HttpRequestKey = "httpRequest";
const String UsernameHeaderKey = "x-ms-credentials-username";
const String PasswordHeaderKey = "x-ms-credentials-password";
const String IdentitiesKey = "Identities";
const String PrincipalKey = "Principal";
// Check if the properties of the context has the identities list
if (evaluationContext.Properties.Count > 0 ||
evaluationContext.Properties.ContainsKey(IdentitiesKey) ||
!OperationContext.Current.IncomingMessageProperties.ContainsKey(HttpRequestKey))
return false;
// get http request
var httpRequest = (HttpRequestMessageProperty)OperationContext.Current.IncomingMessageProperties[HttpRequestKey];
// extract credentials
var username = httpRequest.Headers[UsernameHeaderKey];
var password = httpRequest.Headers[PasswordHeaderKey];
// verify credentials complete
if (string.IsNullOrEmpty(username) || string.IsNullOrEmpty(password))
return false;
// Get or create the identities list
if (!evaluationContext.Properties.ContainsKey(IdentitiesKey))
evaluationContext.Properties[IdentitiesKey] = new List<IIdentity>();
var identities = (List<IIdentity>) evaluationContext.Properties[IdentitiesKey];
// lookup user
using (var con = ServiceLocator.Current.GetInstance<IDbConnection>())
{
using (var userDao = ServiceLocator.Current.GetDao<IUserDao>(con))
{
var user = userDao.GetUserByUsernamePassword(username, password);
...