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.
Related
I am trying to consume a WSDL file from a third party in .net web API but it's retrieving.
Third-party sent me a Certificate that needs to be installed on the machine that wants to send a request and I already installed it.
In SoapUI it succeeded, with these configurations:
I can't mimic the configuration in SoupUI in .net.
This is what I have done:
var binding = GetBinding();
var endpointAddress = GetEndpointAddress("Endpoint URL");
if (endpointAddress != null)
{
ChannelFactory<IVitalEventsChannel> factory = null;
IVitalEventsChannel serviceProxy = null;
try
{
factory = new ChannelFactory<IVitalEventsChannel>(binding, endpointAddress);
factory.Credentials.UserName.UserName = "Username";
factory.Credentials.UserName.Password = "Passowrd";
serviceProxy = factory.CreateChannel();
gePersonalRequest gePersonalReq = new gePersonalRequest("National number");
serviceProxy.Open();
var remoteAddress = serviceProxy.RemoteAddress;
var state = serviceProxy.State;
var result = await serviceProxy.gePersonalAsync(gePersonalReq);
factory.Close();
((ICommunicationObject)serviceProxy).Close();
}
catch (Exception ex)
{
//await client.CloseAsync();
factory.Close();
((ICommunicationObject)serviceProxy).Close();
return BadRequest(new { error = ex.Message });
}
}
Binding part looks like this:
private BasicHttpsBinding GetBinding()
{
var httpsBinding = new BasicHttpsBinding(BasicHttpsSecurityMode.TransportWithMessageCredential);
httpsBinding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Basic;
//var httpsBinding = new BasicHttpBinding(BasicHttpSecurityMode.TransportWithMessageCredential);
WSHttpBinding b = new WSHttpBinding(SecurityMode.TransportWithMessageCredential);
b.Security.Message.ClientCredentialType = MessageCredentialType.UserName;
httpsBinding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Basic;
httpsBinding.Security.Transport.ProxyCredentialType = HttpProxyCredentialType.Basic;
httpsBinding.Security.Mode = BasicHttpsSecurityMode.TransportWithMessageCredential;
httpsBinding.MaxBufferSize = int.MaxValue;
httpsBinding.ReaderQuotas = XmlDictionaryReaderQuotas.Max;
httpsBinding.MaxReceivedMessageSize = int.MaxValue;
httpsBinding.AllowCookies = true;
return httpsBinding;
}
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
Does anyone know the best way to implement accessing a REST service which has a Client Certificate in Xamarin targeting Android?. I'm using .NET Standard 2.0 project for the shared code.
I've tried WebRequestHandler to add the certificate, but mono does not seem to support this and the application won't run. I have also tried HttpClientHandler.
Code snippet below :
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
X509Certificate2 certificate = App.CertificateFile;
var handler = new WebRequestHandler
{
ClientCertificateOptions = ClientCertificateOption.Manual
};
handler.ClientCertificates.Add(certificate);
var client = new HttpClient(handler);
There is a similar question posted here Using custom SSL client certificates System.Net.HttpClient on Mono
but was a while ago so hoping things have improved. My code works correctly from a .Standard 2.0 project if called via a console app, but the same code does not work on the device.
You can try AndroidClientHandler and Programatically using androidClientHandler:
AndroidClientHandler clientHandler = new AndroidClientHandler();
Java.Security.Cert.X509Certificate cert = null;
try
{
CertificateFactory factory = CertificateFactory.GetInstance("X.509");
using (var stream = Application.Context.Assets.Open("MyCert.pfx"))
{
cert = (Java.Security.Cert.X509Certificate)factory.GenerateCertificate(stream);
}
} catch (Exception e)
{
System.Console.WriteLine(e.Message);
}
if (clientHandler.TrustedCerts != null)
{
clientHandler.TrustedCerts.Add(cert);
}
else
{
clientHandler.TrustedCerts = new List<Certificate>();
clientHandler.TrustedCerts.Add(cert);
}
HttpClient client = new HttpClient(clientHandler);
Update:
If the up codes doesn't work, you can try the Android Native implementation, which leverage the same thing as AndroidClientHandler, but are more flexible for use:
var keyStore = KeyStore.GetInstance("PKCS12");
string clientCertPassword = "password_of_certificate";
using (var stream = Application.Context.Assets.Open("cert.pfx"))
{
keyStore.Load(stream, clientCertPassword.ToCharArray());
}
KeyManagerFactory kmf = KeyManagerFactory.GetInstance("x509");
kmf.Init(keyStore, clientCertPassword.ToCharArray());
IKeyManager[] keyManagers = kmf.GetKeyManagers();
SSLContext sslContext = SSLContext.GetInstance("TLS");
sslContext.Init(keyManagers, null, null);
String result = null;
HttpURLConnection urlConnection = null;
HttpStatus lastResponseCode;
try
{
URL requestedUrl = new URL("https://10.106.92.42:444");
urlConnection = (HttpURLConnection)requestedUrl.OpenConnection();
if (urlConnection is HttpsURLConnection) {
((HttpsURLConnection)urlConnection).SSLSocketFactory = sslContext.SocketFactory;
}
urlConnection.RequestMethod = "GET";
urlConnection.ConnectTimeout = 1500;
urlConnection.ReadTimeout = 1500;
lastResponseCode = urlConnection.ResponseCode;
result = ReadFully(urlConnection.InputStream);
string lastContentType = urlConnection.ContentType;
}
catch (Exception ex)
{
result = ex.ToString();
}
finally
{
if (urlConnection != null)
{
urlConnection.Disconnect();
}
}
Wanting to communicate with a SOAP webservice, I had C# classes created by SvcUtil.exe from the wsdl file.
When sending the Request below to a secure server (HTTPS with BASIC auth) I receive a System.ServiceModel.Security.MessageSecurityException and when checking the HTTP request by having traffic go though a Burp proxy I see that no BASIC auth information is passed. Is anything missing for the SOAP request in the C# code or what could be the problem that the BASIC auth does not work?
var binding = new WSHttpBinding();
binding.MessageEncoding = WSMessageEncoding.Mtom;
binding.Security.Mode = SecurityMode.Transport;
binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Basic;
binding.Name = "BasicAuthSecured";
SearchServicePortClient searchClient = new SearchServicePortClient(binding, new EndpointAddress("https://myUrl:Port/myService"));
searchClient.ClientCredentials.UserName.UserName = "username";
searchClient.ClientCredentials.UserName.Password = "pw";
query soap = new query();
//...
queryResponse response = searchClient.query(soap);
Thanks in advance
This is another approach, don't know if it is the best though
using (var client = _clientFactory.GetClient())
{
var credentials = Utils.EncodeTo64("user123:password");
client.ChannelFactory.CreateChannel();
using (OperationContextScope scope = new OperationContextScope(client.InnerChannel))
{
var httpRequestProperty = new HttpRequestMessageProperty();
httpRequestProperty.Headers[HttpRequestHeader.Authorization] = "Basic " + credentials;
OperationContext.Current.OutgoingMessageProperties[HttpRequestMessageProperty.Name] = httpRequestProperty;
//operation
client.Send(request);
}
}
Try to use TransportWithMessageCredential:
binding.Security.Mode = SecurityMode.TransportWithMessageCredential;
I am working on a Android/C# project. What I need to be able to do is have a WCF soap service which can either run on Windows or Linux (Mono).
It's working fine on Windows and I can access the WCF Service on Mono from the WCF Test Client provided in Visual Studio and it works fine but when accessing android using KSOAP2 I get the error HTTP Request Failed, HTTP status: 415
Below is how the soap service is started
string methodInfo = classDetails + MethodInfo.GetCurrentMethod().Name;
try
{
if (Environment.GetEnvironmentVariable("MONO_STRICT_MS_COMPLIANT") != "yes")
{
Environment.SetEnvironmentVariable("MONO_STRICT_MS_COMPLIANT", "yes");
}
if (String.IsNullOrEmpty(soapServerUrl))
{
string message = "Not starting Soap Server: URL or Port number is not set in config file";
library.logging(methodInfo, message);
library.setAlarm(message, CommonTasks.AlarmStatus.Medium, methodInfo);
return;
}
Console.WriteLine("Soap Server URL: {0}", soapServerUrl);
baseAddress = new Uri(soapServerUrl);
host = new ServiceHost(soapHandlerType, baseAddress);
BasicHttpBinding basicHttpBinding = new BasicHttpBinding();
//basicHttpBinding.Namespace = "http://tempuri.org/";
var meta = new ServiceMetadataBehavior()
{
HttpGetEnabled = true,
HttpGetUrl = new Uri("", UriKind.Relative),
//HttpGetBinding = basicHttpBinding,
};
//meta.MetadataExporter.PolicyVersion = PolicyVersion.Policy15;
host.Description.Behaviors.Add(meta);
host.AddServiceEndpoint(soapManagerInterface, basicHttpBinding, soapServerUrl);
host.AddServiceEndpoint(typeof(IMetadataExchange), MetadataExchangeBindings.CreateMexHttpBinding(), "mex");
var debugBehaviour = new ServiceDebugBehavior()
{
HttpHelpPageEnabled = true,
HttpHelpPageUrl = new Uri("", UriKind.Relative),
IncludeExceptionDetailInFaults = true,
//HttpHelpPageBinding = basicHttpBinding,
};
host.Description.Behaviors.Remove(typeof(ServiceDebugBehavior));
host.Description.Behaviors.Add(debugBehaviour);
host.Opened += new EventHandler(host_Opened);
host.Faulted += new EventHandler(host_Faulted);
host.Closed += new EventHandler(host_Closed);
host.UnknownMessageReceived += new EventHandler<UnknownMessageReceivedEventArgs>(host_UnknownMessageReceived);
host.Open();
}
The soapServerURL is http://192.168.1.74:8000/CritiMon.
Below is how I am trying to call the soap service from android using KSOAP2.
final String soapAction = "http://tempuri.org/ISoapInterface/testSoapFunction";
final String namespace = "http://tempuri.org/";
final String methodName = "testSoapFunction";
final String url = "http://192.168.1.74:8000/CritiMon?wsdl";
String resultData = "";
new Thread(new Runnable() {
#Override
public void run() {
SoapSerializationEnvelope envelope = null;
try
{
//String resultData = "";
SoapObject request = new SoapObject(namespace, methodName);
//request.addProperty("firstName", "Chris");
//request.addProperty("lastName", "Board");
envelope = new SoapSerializationEnvelope(SoapEnvelope.VER11);
envelope.dotNet = true;
envelope.setOutputSoapObject(request);
HttpTransportSE http = new HttpTransportSE(url);
http.call(soapAction, envelope);
SoapPrimitive result = (SoapPrimitive)envelope.getResponse();
String resultData = result.toString();
Log.d("Soap Result", resultData);
}
catch (Exception ex)
{
Log.e("Soap Error 2", ex.getMessage());
}
I have no idea what I can do to make this work on Mono with Android.
Firstly, you want to catch the actual SOAP request on the wire. You can do this using Fiddler or SoapUI - they both act as proxies through which local requests are passed, allowing you to inspect the actual XML request for anomalies. You might discover something obvious by doing this, or you can at least update your question with more information.
Following that, and without any further information, I can only offer a memorable experience with talking to WCF services from non-.NET applications:
WCF specifies the XML request that it expects, but it actually requires object properties to be in a specific order. This can be a declared order in the datacontract, or it can be an implicit alphabetical order. Either way, if you don't provide object properties in the specified order you will be stung and things won't work.