SecurityNegotiationException - Retrieving token from web service - c#

I'm attempting to move from WIF 3.5 and onto WIF 4.5. However, the conversion is proving to be more difficult than I anticipated. The questions will correspond to the comments in the code.
Full error message:
System.Web.Services.Protocols.SoapException: 'System.Web.Services.Protocols.SoapException: Authentication Failed --->
System.ServiceModel.Security.SecurityNegotiationException: Secure
channel cannot be opened because security negotiation with the remote
endpoint has failed. This may be due to absent or incorrectly
specified EndpointIdentity in the EndpointAddress used to create the
channel. Please verify the EndpointIdentity specified or implied by
the EndpointAddress correctly identifies the remote endpoint.
Secure channel cannot be opened because security negotiation with the
remote endpoint has failed. This may be due to absent or incorrectly
specified EndpointIdentity in the EndpointAddress used to create the
channel. Please verify the EndpointIdentity specified or implied by
the EndpointAddress correctly identifies the remote endpoint.
#1. Which username/password combination is needed and which is not?
#2. This is where SecurityNegotiationException is thrown. What am I missing exactly?
So, am I way off or is it something simple I'm missing? Do I need to entirely rewrite how the WSTrustChannelFactory is being created?
Code:
public string GetToken(string url, string domain, string realm, string username, string password)
{
string rp = realm;
string token = "";
WSTrustChannelFactory trustChannelFactory = new WSTrustChannelFactory
(
new WSHttpBinding(SecurityMode.TransportWithMessageCredential),
new EndpointAddress(new Uri(url))
);
trustChannelFactory.TrustVersion = TrustVersion.WSTrust13;
trustChannelFactory.Credentials.Windows.ClientCredential.Domain = domain;
trustChannelFactory.Credentials.Windows.ClientCredential.UserName = username; // #1; not sure which pair is needed?
trustChannelFactory.Credentials.Windows.ClientCredential.Password = password;
trustChannelFactory.Credentials.UserName.Password = password;
trustChannelFactory.Credentials.UserName.UserName = username;
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
try
{
RequestSecurityToken rst = new RequestSecurityToken(RequestTypes.Issue, KeyTypes.Bearer);
rst.AppliesTo = new EndpointReference(rp);
rst.TokenType = SecurityTokenTypes.Saml;
WSTrustChannel channel = (WSTrustChannel)trustChannelFactory.CreateChannel();
GenericXmlSecurityToken token = channel.Issue(rst) as GenericXmlSecurityToken; // #2; Exception thrown here
token = token.TokenXml.OuterXml;
}
catch (SecurityNegotiationException e)
{
LogError("Authentication Failed", e);
}
catch (TimeoutException e)
{
LogError("Unable to authenticate", e);
}
catch (CommunicationException e)
{
LogError("Communication exception", e);
}
catch (Exception e)
{
LogError("Unknown exception", e);
}
return token;
}

You need to use a SecurityTokenHandlerCollection
public SecurityToken GetToken(string url, string realm, string username, string password)
{
string rp = realm;
WS2007HttpBinding binding = new WS2007HttpBinding(SecurityMode.TransportWithMessageCredential, false);
binding.Security.Message.ClientCredentialType = MessageCredentialType.UserName;
binding.Security.Message.EstablishSecurityContext = false;
EndpointAddress endpoint = new EndpointAddress(url);
WSTrustChannelFactory factory = new WSTrustChannelFactory(binding, endpoint);
factory.TrustVersion = TrustVersion.WSTrust13;
factory.Credentials.UserName.UserName = username;
factory.Credentials.UserName.Password = password;
WSTrustChannel channel = (WSTrustChannel) factory.CreateChannel();
RequestSecurityToken rst = new RequestSecurityToken
{
RequestType = RequestTypes.Issue,
KeyType = KeyTypes.Bearer,
AppliesTo = new EndpointReference(rp),
TokenType = "http://docs.oasis-open.org/wss/oasis-wss-saml-token-profile-1.1#SAMLV2.0"
};
GenericXmlSecurityToken genericXmlSecurityToken = (GenericXmlSecurityToken) channel.Issue(rst, out RequestSecurityTokenResponse rstr);
SecurityTokenHandlerCollection tokenHandlers = new SecurityTokenHandlerCollection(
new SecurityTokenHandler[]
{
new SamlSecurityTokenHandler(),
new Saml2SecurityTokenHandler()
}
);
tokenHandlers.Configuration.AudienceRestriction = new AudienceRestriction();
tokenHandlers.Configuration.AudienceRestriction.AllowedAudienceUris.Add(new Uri(rp));
TrustedIssuerNameRegistry trustedIssuerNameRegistry = new TrustedIssuerNameRegistry();
tokenHandlers.Configuration.IssuerNameRegistry = trustedIssuerNameRegistry;
SecurityToken token =
tokenHandlers.ReadToken(
new XmlTextReader(new StringReader(genericXmlSecurityToken.TokenXml.OuterXml)));
return token;
}
public class TrustedIssuerNameRegistry : IssuerNameRegistry
{
public override string GetIssuerName(SecurityToken securityToken)
{
return "Trusted Issuer";
}
}

We decided to continue to use WIF 3.5 for now and will have an entire rewrite for WIF 4.5 instead of trying to do something that isn't possible.
There was simply too much change and not enough documentation to "shoehorn" our existing code from WIF 3.4 to WIF 4.5

Related

.NET Core 2.1 Apple Push Notifications

I have to send push notifications to specific iOS devices with my .Net Core WebAPI that will be executed on a Windows 2008 Server R2. The server itself should not be the problem because it is working with a node.js library. But I want it to work with an WepAPI in ASP .Net Core 2.1 which is self hosted with the inbuilt Kestrel Server. Maybe you've got an idea how to solve this problem.
My Code:
// This will encode the jason web token apns needs for the authorization
// get the base64 private key of the .p8 file from apple
string p8File = System.IO.File.ReadAllText(Settings.Apn.PrivateKey);
p8File = p8File.Replace("-----BEGIN PRIVATE KEY-----", string.Empty);
p8File = p8File.Replace("-----END PRIVATE KEY-----", string.Empty);
p8File = p8File.Replace(" ", string.Empty);
byte[] keyData = Convert.FromBase64String(p8File);
ECDsa key = new ECDsaCng(CngKey.Import(keyData, CngKeyBlobFormat.Pkcs8PrivateBlob));
ECDsaSecurityKey securityKey = new ECDsaSecurityKey(key) { KeyId = Settings.Apn.KeyId };
SigningCredentials credentials = new SigningCredentials(securityKey, "ES256");
SecurityTokenDescriptor descriptor =
new SecurityTokenDescriptor
{
IssuedAt = DateTime.Now,
Issuer = Settings.Apn.TeamId,
SigningCredentials = credentials
};
JwtSecurityTokenHandler jwtHandler = new JwtSecurityTokenHandler();
string encodedToken = jwtHandler.CreateEncodedJwt(descriptor);
this.log?.LogInformation($"Created JWT: {encodedToken}");
// The hostname is: https://api.development.push.apple.com:443
HttpClient client = new HttpClient { BaseAddress = new Uri(Settings.Apn.Hostname) };
client.DefaultRequestHeaders.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
this.log?.LogInformation("Initialized new HttpClient.");
// payload content for the apns
JObject payloadData = new JObject
{
new JProperty("alert", data.Message),
new JProperty("badge", 2),
new JProperty("sound", "default")
};
JObject payload = new JObject
{
new JProperty("aps", payloadData)
};
this.log?.LogInformation($"Setup payload: {payload}");
// HttpRequestMessage that should be send
HttpRequestMessage request = new HttpRequestMessage(
HttpMethod.Post,
$"{Settings.Apn.Hostname}/3/device/{data.DeviceId}")
{
Content = new StringContent(JsonConvert.SerializeObject(payload), Encoding.UTF8, "application/json")
};
this.log?.LogInformation("Setup HttpRequestMessage.");
// Setup the header
request.Headers.Add("Authorization", $"Bearer {encodedToken}");
request.Headers.Add("apns-id", Guid.NewGuid().ToString());
request.Headers.Add("apns-expiration", DateTime.Now.AddDays(1).ToString(CultureInfo.InvariantCulture));
request.Headers.Add("apns-priority", "10");
request.Headers.Add("apns-topic", "de.gefasoft-engineering.FabChat");
// Debug logging
this.log.LogDebug(request.ToString());
this.log.LogDebug(await request.Content.ReadAsStringAsync());
this.log.LogDebug(request.RequestUri.Host + request.RequestUri.Port);
// Send request
var result = await client.SendAsync(request);
this.log?.LogInformation("Sent request.");
this.log?.LogInformation(await result.Content.ReadAsStringAsync());
I always get following Exception thrown:
System.Net.Http.HttpRequestException: The SSL connection could not be
established, see inner exception. --->
System.Security.Authentication.AuthenticationException: Authentication
failed, see inner exception. --->
System.ComponentModel.Win32Exception: The message received was
unexpected or badly formatted --- End of inner exception stack
trace ---
Use CorePush lib
It's very lightweight. I use it across all my projects to send Firebase Android/WebPush and Apple iOS push notifications. Useful links:
NuGet package
Documentation
The interface is very simple and minimalistic:
Send APN message:
var apn = new ApnSender(settings, httpClient);
await apn.SendAsync(notification, deviceToken);
It can also send Android FCM message if needed:
var fcm = new FcmSender(settings, httpClient);
await fcm.SendAsync(deviceToken, notification);
can you try adding version information to your request after the apns-topic line as below? It ran to completion and I got a "bad device token" error for the first time after adding the following line.
request.Version = new Version(2, 0);
System.Net.ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12 | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls;
I saw the version setting command at the post below.
How to implement apple token based push notifications (using p8 file) in C#?
I've already commented on the answer from #civilator. But I think, that some people read over it, so I'm posting it again.
This is the code that worked for me. Sorry for the late answer!
private readonly string hostname = "gateway.sandbox.push.apple.com";
private readonly int port = 2195;
public async Task<RestResult<JObject>> SendPushNotification(string deviceToken, string message)
{
this.log?.LogInformation("Trying to send push notification.");
X509Certificate2Collection certificatesCollection;
// Setup and read the certificate
// NOTE: You should get the certificate from your apple developer account.
try
{
string certificatePath = Settings.Apn.Certificate;
X509Certificate2 clientCertificate = new X509Certificate2(
File.ReadAllBytes(certificatePath),
Settings.Apn.Password);
certificatesCollection = new X509Certificate2Collection(clientCertificate);
this.log?.LogInformation("Setup certificates.");
}
catch (Exception e)
{
this.log?.LogError(e.ToString());
return new RestResult<JObject> { Result = "exception", Message = "Failed to setup certificates." };
}
// Setup a tcp connection to the apns
TcpClient client = new TcpClient(AddressFamily.InterNetwork);
this.log?.LogInformation("Created new TcpClient.");
try
{
IPHostEntry host = Dns.GetHostEntry(this.hostname);
await client.ConnectAsync(host.AddressList[0], this.port);
this.log?.LogInformation($"Opened connection to {this.hostname}:{this.port}.");
}
catch (Exception e)
{
this.log?.LogError("Failed to open tcp connection to the apns.");
this.log?.LogError(e.ToString());
}
// Validate the Certificate you get from the APN (for more information read the documentation:
// https://developer.apple.com/library/archive/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/APNSOverview.html#//apple_ref/doc/uid/TP40008194-CH8-SW1).
SslStream sslStream = new SslStream(
client.GetStream(),
false,
new RemoteCertificateValidationCallback(this.ValidateServerCertificate),
null);
try
{
await sslStream.AuthenticateAsClientAsync(this.hostname, certificatesCollection, SslProtocols.Tls, false);
MemoryStream memoryStream = new MemoryStream();
BinaryWriter writer = new BinaryWriter(memoryStream);
writer.Write((byte)0);
writer.Write((byte)0);
writer.Write((byte)32);
writer.Write(HexStringToByteArray(deviceToken.ToUpper()));
// Creating an payload object to send key values to the apns
JObject aps = new JObject
{
new JProperty("alert", message),
new JProperty("badge", 0),
new JProperty("sound", "default")
};
JObject payload = new JObject
{
new JProperty("aps", aps)
};
string payloadString = JsonConvert.SerializeObject(payload);
writer.Write((byte)0);
writer.Write((byte)payloadString.Length);
byte[] b1 = System.Text.Encoding.UTF8.GetBytes(payloadString);
writer.Write(b1);
writer.Flush();
byte[] array = memoryStream.ToArray();
sslStream.Write(array);
sslStream.Flush();
client.Dispose();
}
catch (AuthenticationException ex)
{
this.log?.LogError(ex.ToString());
client.Dispose();
return new RestResult<JObject> { Result = "exception", Message = "Authentication Exception." };
}
catch (Exception e)
{
this.log?.LogError(e.ToString());
client.Dispose();
return new RestResult<JObject> { Result = "exception", Message = "Exception was thrown." };
}
this.log?.LogInformation("Notification sent.");
return new RestResult<JObject> { Result = "success", Message = "Notification sent. Check your device." };
}
#region Helper methods
private static byte[] HexStringToByteArray(string hex)
{
return Enumerable.Range(0, hex.Length)
.Where(x => x % 2 == 0)
.Select(x => Convert.ToByte(hex.Substring(x, 2), 16))
.ToArray();
}
// The following method is invoked by the RemoteCertificateValidationDelegate.
private bool ValidateServerCertificate(
object sender,
X509Certificate certificate,
X509Chain chain,
SslPolicyErrors sslPolicyErrors)
{
if (sslPolicyErrors == SslPolicyErrors.None)
{
this.log?.LogInformation("Server Certificate validated.");
return true;
}
this.log?.LogError($"Server Certificate error: {sslPolicyErrors}");
// Do not allow this client to communicate with unauthenticated servers.
return false;
}
#endregion

Authentication in ADFS with Web Service

I have developed the test application in which I have added .asmx file for ADFS authentication, after authentication, it would redirect to my original already developed web application. the common code is shown below
var stsEndpoint = ConfigurationManager.AppSettings["EndPoint"];
var relayPartyUri = ConfigurationManager.AppSettings["RelayPartyUri"];
EndpointAddress epoint = new EndpointAddress(stsEndpoint);
using (var factory = new WSTrustChannelFactory(new UserNameWSTrustBinding(SecurityMode.TransportWithMessageCredential), epoint) { TrustVersion = TrustVersion.WSTrust13 })
{
if (factory.Credentials != null)
{
factory.Credentials.UserName.UserName = #"Domain\" + userName;
factory.Credentials.UserName.Password = password;
}
var rst = new RequestSecurityToken
{
RequestType = WSTrust13Constants.RequestTypes.Issue,
AppliesTo = new EndpointReference(relayPartyUri),
KeyType = WSTrust13Constants.KeyTypes.Bearer
};
var channel = factory.CreateChannel();
var token = channel.Issue(rst);
var genericToken = token as GenericXmlSecurityToken;
...
}
...
The line var token = channel.Issue(rst); throws error
Error is ID3082: The request scope is not valid or is unsupported
Also, I am not sure which URI I should use for RelyingPartyURI
the service URL which I am using for ADFS authentication. or
the URL of my already developed web application
Please help.
relayPartyUri is the unique identifier for your app.

Is this why a WCF SSL Secure Channel is faulting?

I'm supporting a project where we recently needed to apply a series of upgrades to a newer version of the .Net Framework. This has largely succeeded but for one final component that's been around for a very long time.
Our client uses InfoPath templates to populate information for other users to consume. Everything the templates need comes from a WCF web service we host. We set the web service call up with the following code.
private WSHttpBinding CreateBinding()
{
var wsHttpBinding = new WSHttpBinding();
wsHttpBinding.CloseTimeout = TimeSpan.FromMinutes(10);
wsHttpBinding.OpenTimeout = TimeSpan.FromMinutes(10);
wsHttpBinding.ReceiveTimeout = TimeSpan.FromMinutes(10);
wsHttpBinding.SendTimeout = TimeSpan.FromMinutes(10);
wsHttpBinding.BypassProxyOnLocal = false;
wsHttpBinding.TransactionFlow = false;
wsHttpBinding.HostNameComparisonMode = HostNameComparisonMode.StrongWildcard;
wsHttpBinding.MaxBufferPoolSize = 524288;
wsHttpBinding.MaxReceivedMessageSize = 2147483647;
wsHttpBinding.MessageEncoding = WSMessageEncoding.Text;
wsHttpBinding.TextEncoding = Encoding.UTF8;
wsHttpBinding.UseDefaultWebProxy = true;
wsHttpBinding.AllowCookies = false;
wsHttpBinding.ReaderQuotas.MaxDepth = 32;
wsHttpBinding.ReaderQuotas.MaxStringContentLength = 2147483647;
wsHttpBinding.ReaderQuotas.MaxArrayLength = 16384;
wsHttpBinding.ReaderQuotas.MaxBytesPerRead = 4096;
wsHttpBinding.ReaderQuotas.MaxNameTableCharCount = 16384;
wsHttpBinding.ReliableSession.Ordered = true;
wsHttpBinding.ReliableSession.InactivityTimeout = TimeSpan.FromMinutes(10);
wsHttpBinding.ReliableSession.Enabled = false;
wsHttpBinding.Security.Mode = SecurityMode.TransportWithMessageCredential;
wsHttpBinding.Security.Transport.ClientCredentialType = HttpClientCredentialType.None;
wsHttpBinding.Security.Transport.ProxyCredentialType = HttpProxyCredentialType.None;
wsHttpBinding.Security.Transport.Realm = string.Empty;
wsHttpBinding.Security.Message.ClientCredentialType = MessageCredentialType.UserName;
wsHttpBinding.Security.Message.NegotiateServiceCredential = false;
wsHttpBinding.Security.Message.AlgorithmSuite = SecurityAlgorithmSuite.Basic256;
return wsHttpBinding;
}
private EndpointAddress CreateEndPoint()
{
X509Store store = new X509Store(StoreName.My, StoreLocation.CurrentUser);
store.Open(OpenFlags.ReadOnly);
X509Certificate2 certificate = store.Certificates.Find(X509FindType.FindBySubjectName, "*.wildcard.address.foo", false)[0];
store.Close();
EndpointIdentity identity = EndpointIdentity.CreateX509CertificateIdentity(certificate);
string address = getWcfServiceUrl();
AddressHeader header = AddressHeader.CreateAddressHeader(address);
List<AddressHeader> headerList = new List<AddressHeader> { header };
Uri uri = new Uri(address);
var endpointAddress = new EndpointAddress(uri, identity, headerList.ToArray());
return endpointAddress;
}
}
This works fine and if we're testing it out, calls can be made successfully for all other intents and purposes. Except for one.
In one case we need to get information from a 3rd party resource. In that situation, our web service makes a separate call out to this 3rd party at an HTTPS address (passed in to the url parameter here:
private string requestURL(string url)
{
string toReturn = null;
Stream stream = null;
try
{
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
request.Method = httpMethod;
stream = ((HttpWebResponse)request.GetResponse()).GetResponseStream();
StreamReader reader = new StreamReader(stream);
toReturn = reader.ReadToEnd();
}
catch(Exception e)
{
throw new Exception("Error with that service please try again: " + e.Message, e);
}
finally
{
if(stream != null)
{
stream.Close();
}
}
return toReturn;
}
In this case, the following error is returned:
The request was aborted: Could not create SSL/TLS secure channel.
My suspicion is that we're setting up a very specific set of constraints around the SSL connection between our local client (i.e. InfoPath) and the web service but the call from that web service to the 3rd party is not set up with any constraints beyond simply calling over HTTPS.
What should I be looking out for in trying to fix this issue?
WCF IMHO is particular about configuration at both ends and asks for things like transport credential specifically in the back and forth. I suspect you have no control of how the security is managed at the third party and can't change it, but your generic method to call all web services won't work because the configuration doesn't match.

active client - get token from resource partner adfs using idp token

I am trying to write console application with the following scenario:
client first requests a token from an identity provider, and then uses this token to request a new token from a Resource STS
Using the following link: http://leastprivilege.com/2010/10/28/wif-adfs-2-and-wcfpart-6-chaining-multiple-token-services/
I managed get the token from Idp but didn't managed getting the token from Resource STS.
This is my code:
string RPRealm = "https://service.contoso.com/";
string RSTSRealm = "http://fsweb.contoso.com/adfs/services/trust";
string IdPstsEndpoint = "https://IdpAdfs.domain.com/adfs/services/trust/13/kerberosmixed";
string RSTSEndpoint = "https://fsweb.contoso.com/adfs/services/trust/13/IssuedTokenMixedSymmetricBasic256";
private static SecurityToken GetIdPToken(string rstsRealm, string IdPstsEndpoint)
{
using (var factory = new WSTrustChannelFactory(
new KerberosWSTrustBinding(SecurityMode.TransportWithMessageCredential),
new EndpointAddress(new Uri(IdPstsEndpoint))))
{
WSTrustChannel channel = null;
factory.TrustVersion = TrustVersion.WSTrust13;
try
{
var rst = new RequestSecurityToken
{
RequestType = WSTrust13Constants.RequestTypes.Issue,
AppliesTo = new EndpointAddress(rstsRealm),
KeyType = WSTrust13Constants.KeyTypes.Bearer,
};
channel = (WSTrustChannel)factory.CreateChannel();
RequestSecurityTokenResponse rstr;
SecurityToken token = channel.Issue(rst, out rstr);
return token;
}
finally
{
if (channel != null)
{
channel.Abort();
}
factory.Abort();
}
}
}
private static SecurityToken GetRSTSToken(SecurityToken IdPToken, string RSTSEndpoint, string RPRealm)
{
var binding = new WS2007FederationHttpBinding();
binding.Security.Message.IssuedKeyType = SecurityKeyType.BearerKey;
binding.Security.Message.EstablishSecurityContext = false;
binding.Security.Mode = WSFederationHttpSecurityMode.TransportWithMessageCredential;
using (var factory = new WSTrustChannelFactory(
binding,
new EndpointAddress(new Uri(RSTSEndpoint))))
{
var rst = new RequestSecurityToken
{
RequestType = WSTrust13Constants.RequestTypes.Issue,
AppliesTo = new EndpointAddress(RPRealm),
KeyType = WSTrust13Constants.KeyTypes.Bearer,
};
factory.Credentials.ServiceCertificate.Authentication.CertificateValidationMode = X509CertificateValidationMode.None;
factory.TrustVersion = TrustVersion.WSTrust13;
factory.Credentials.SupportInteractive = false;
factory.ConfigureChannelFactory();
var channel = factory.CreateChannelWithIssuedToken(IdPToken);
RequestSecurityTokenResponse rstr;
SecurityToken token = channel.Issue(rst, out rstr);
return token;
}
}
I get this error:
The content type text/html of the response message does not match the content type of the binding (application/soap+xml; charset=utf-8)
what is worng with my code?
Thanks in advance
ADFS does not support bearer tokens on its federation endoints. In other words, on your first hop you need to specify a KeyTypes.Symmetric on the RST.

Getting SAML token from ADFS using windows credentials

I am creating a windows 8 client app in c#.This app will use odata service of SAP. For authentication I need SAML token issued by ADFS. Is there any method to get SAML token from ADFS using windows credentials?
You can get the SAML token using the below code.
var factory = new WSTrustChannelFactory(new Microsoft.IdentityModel.Protocols.WSTrust.Bindings.UserNameWSTrustBinding(SecurityMode.TransportWithMessageCredential), adfsEndpoint);
factory.Credentials.UserName.UserName = "username";
factory.Credentials.UserName.Password = "********";
factory.Credentials.ServiceCertificate.Authentication.CertificateValidationMode = X509CertificateValidationMode.None;
factory.TrustVersion = TrustVersion.WSTrust13;
WSTrustChannel channel = null;
try
{
var rst = new RequestSecurityToken
{
RequestType = WSTrust13Constants.RequestTypes.Issue,
AppliesTo = new EndpointAddress("https://yourserviceendpoint.com/"),
KeyType = KeyTypes.Bearer,
};
channel = (WSTrustChannel)factory.CreateChannel();
return channel.Issue(rst);
}
catch (Exception e)
{
return null;
}

Categories