I'm completely stuck with ONVIF authentication. I think I've tried everything or at least almost everything and I don't find enough information on the Internet. I have created the stub client using svcutil, my code to do the authentication is (one of them because I have tried a lot of things):
string uri = "http://140.0.22.39/onvif/services";
EndpointAddress serviceAddressPrueba = new EndpointAddress(uri);
HttpTransportBindingElement httpBinding = new HttpTransportBindingElement();
httpBinding.AuthenticationScheme = AuthenticationSchemes.Digest;
var messegeElement = new TextMessageEncodingBindingElement();
messegeElement.MessageVersion = MessageVersion.CreateVersion(EnvelopeVersion.Soap12, AddressingVersion.None);
CustomBinding bindprueba = new CustomBinding(messegeElement, httpBinding);
DeviceClient clientprueba = new DeviceClient(bindprueba, serviceAddressPrueba);
string passwordDigestBase64;
//HERE I PUT THE CODE TO ENCRYPT THE PASSWORD.
PasswordDigestBehavior behavior1 = new PasswordDigestBehavior("root",passwordDigestBase64);
clientprueba.Endpoint.Behaviors.Add(behavior1);
string d1;
string d2;
string d3;
string d4;
clientprueba.GetDeviceInformation(out d1, out d2, out d3, out d4);
After this there is the following error:
{"The remote server returned an unexpected response: (400) Bad Request."}
I will be very, very grateful if you please could help me with any information to solve this.
Try this way:
ServicePointManager.Expect100Continue = false;
var endPointAddress = new EndpointAddress("http://" + cameraAddress + "/onvif/device_service");
var httpTransportBinding = new HttpTransportBindingElement { AuthenticationScheme = AuthenticationSchemes.Digest };
var textMessageEncodingBinding = new TextMessageEncodingBindingElement { MessageVersion = MessageVersion.CreateVersion(EnvelopeVersion.Soap12, AddressingVersion.None) };
var customBinding = new CustomBinding(textMessageEncodingBinding, httpTransportBinding);
var passwordDigestBehavior = new PasswordDigestBehavior(adminName, adminPassword);
var deviceClient = new DeviceClient(customBinding, endPointAddress);
deviceClient.Endpoint.Behaviors.Add(passwordDigestBehavior);
Notice that it is important to set ServicePointManager.Expect100Continue to false.
A couple of things could cause this:
You've set a root password via web browser, thus locking the ONVIF user. Log in to the camera and add an ONVIF user (There's a special page for that)
Your password digest includes only the password, where it should include a concatenation of a random nonce, the creation time, and the password.
Your local clock is not synchronized with the camera's clock. call getSystemDateAndTime to read the remote clock and record the time differences between you.
These were the 3 out of the 4 major things that slowed me down (the 4th one was importing the wsdl, but it looks like you got it already)
Related
Edit:
After struggling for a long time to figure this out, I came across a potential solution. As of today (2021-10-19), the latest stable version of System.ServiceModel.*** packages is 4.8.1, but there are release candidates for 4.9.0 which seem to solve exactly the problem I'm having here.
I checked the .NET WCF GitHub source and found this release candidate (version 4.9.0-rc1.21431.2) which has exactly what I'm looking for. They've updated the HttpTransportBindingElement to include a Proxy property. Obviously it is not stable release yet, but it still gets the job done. With that I was able to solve the original problem using something that looks like this:
using (var myWsdlClient = new MyWsdlGeneratedClient())
{
var binding = myWsdlClient.Endpoint.Binding as BasicHttpBinding;
var customBinding = new CustomBinding(binding);
var htbe = customBinding.Elements.Find<HttpTransportBindingElement>();
htbe.AuthenticationScheme = AuthenticationSchemes.Basic;
htbe.ProxyAuthenticationScheme = AuthenticationSchemes.Basic;
htbe.UseDefaultWebProxy = false;
htbe.BypassProxyOnLocal = false;
htbe.Proxy = new WebProxy
{
Address = new Uri("http://myproxyaddress.com:8080"),
/* Proxy creds */
Credentials = new NetworkCredential("MyProxyUserName", "MyProxyPassword"),
BypassProxyOnLocal = false
};
myWsdlClient.Endpoint.Binding = customBinding;
/* Client creds */
myWsdlClient.ClientCredentials.UserName.UserName = "MyClientUserName";
myWsdlClient.ClientCredentials.UserName.Password = "MyClientPassword";
/* Send request */
myWsdlClient.Endpoint.Address = new EndpointAddress("https://myclientaddress.com");
myWsdlClient.doSomeAction(actionRequest); // <-- IT WORKS!!!
}
Original question:
I'm trying to send a WCF service request through a web proxy, and I'm receiving the error "Remote Server returned an error: (407) Proxy Authentication Required". I've already generated the proxy classes with a WSDL, set up the bindings/endpoints etc. in my app.config (it is a BasicHttpBinding). The problem is: both the client and the proxy require Basic authentication, and I can only seem be able to set the client credentials, not the proxy.
Things I've already tried:
I saw online you could try to pass credentials in the URL of the proxy itself. So I did this programatically for the ProxyAddress property on the binding, like so:
using (var myWsdlClient = new MyWsdlGeneratedClient())
{
var binding = myWsdlClient.Endpoint.Binding as BasicHttpBinding;
/* Client creds */
binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Basic;
myWsdlClient.ClientCredentials.UserName.UserName = "MyClientUserName";
myWsdlClient.ClientCredentials.UserName.Password = "MyClientPassword";
/* Proxy creds */
binding.Security.Transport.ProxyCredentialType = HttpProxyCredentialType.Basic;
binding.UseDefaultWebProxy = false;
binding.BypassProxyOnLocal = false;
binding.ProxyAddress = new Uri("http://MyProxyUserName:MyProxyPassword#myproxyaddress.com:8080");
/* Send request */
myWsdlClient.Endpoint.Address = new EndpointAddress("https://myclientaddress.com");
myWsdlClient.doSomeAction(actionRequest); // <-- error is thrown here, inner exception is 407 HTTP response
}
I also tried with default web proxy (it sorta worked). Again, I set it programatically like so:
using (var myWsdlClient = new MyWsdlGeneratedClient())
{
var binding = myWsdlClient.Endpoint.Binding as BasicHttpBinding;
/* Client creds */
binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Basic;
myWsdlClient.ClientCredentials.UserName.UserName = "MyClientUserName";
myWsdlClient.ClientCredentials.UserName.Password = "MyClientPassword";
/* Proxy creds */
binding.Security.Transport.ProxyCredentialType = HttpProxyCredentialType.Basic;
binding.UseDefaultWebProxy = true;
binding.BypassProxyOnLocal = false;
var defaultProxyBefore = WebRequest.DefaultWebProxy;
var newProxy = new WebProxy
{
Address = new Uri("http://myproxyaddress.com:8080"),
Credentials = new NetworkCredential("MyProxyUserName", "MyProxyPassword"),
BypassProxyOnLocal = false
};
WebRequest.DefaultWebProxy = newProxy;
/* Send request */
myWsdlClient.Endpoint.Address = new EndpointAddress("https://myclientaddress.com");
try
{
myWsdlClient.doSomeAction(actionRequest);
}
finally
{
WebRequest.DefaultWebProxy = defaultProxyBefore;
}
}
The good thing about this second approach is that it actually worked! However, it is not enough for the requirements of my project. The application I am developing is sending loads of requests per second on different threads, some of which are going through the default proxy. I don't want all those unrelated requests to go through my "new" proxy, they should continue to go through the default.
So to summarize, I need a way of setting the proxy per-request, while also being able to set Basic authentication for both the client and the proxy. I'm not very experienced with WCF and I have just stumbled along the concept of "Custom bindings", which seems promising, but I still haven't found if it can do what I need. Any help on this is incredibly appreciated!
Welcome to Stack Overflow. Thanks for your detailed question.
The "proper" solution is to use an HTTPS proxy (not an HTTP proxy).
If this isn't feasible, you can set the Binding's security mode to BasicHttpSecurityMode.TransportCredentialOnly. (Because Basic Authentication isn't encrypted, I don't recommend doing this in a Production application.)
Below is an example based on your original post. Let me know if it works for you.
using (var myWsdlClient = new MyWsdlGeneratedClient())
{
var binding = myWsdlClient.Endpoint.Binding as BasicHttpBinding;
binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Basic;
/* Client creds */
myWsdlClient.ClientCredentials.UserName.UserName = "MyClientUserName";
myWsdlClient.ClientCredentials.UserName.Password = "MyClientPassword";
/* Disable HTTPS requirement */
binding.Security.Mode = BasicHttpSecurityMode.TransportCredentialOnly;
/* Proxy creds */
/*
* Since the credentials for the Proxy are in the URL,
* set the proxy credential type to None (the default value).
* Otherwise, WCF may attempt using myWsdlClient.ClientCredentials to
* authenticate with the Proxy.
*/
binding.Security.Transport.ProxyCredentialType = HttpProxyCredentialType.None;
/* Note: UseDefaultWebProxy is true by default. */
binding.UseDefaultWebProxy = false;
binding.BypassProxyOnLocal = false;
/* Ensure your Proxy Server supports passing credentials in the URL. */
binding.ProxyAddress = new Uri("http://MyProxyUserName:MyProxyPassword#myproxyaddress.com:8080");
/* Send request */
myWsdlClient.Endpoint.Address = new EndpointAddress("https://myclientaddress.com");
myWsdlClient.doSomeAction(actionRequest);
}
I have a web service feature that I have been using for many years. Today they sent me a new certificate, a .cer file, which I inserted instead of the old one but I get this error:
(Translated with Google, sorry)
MessageSecurityException
Failed to complete identity check for outgoing message.
The expected DNS identity of the remote endpoint was 'pddasl-coll.rmmg.rsr.rupar.puglia.it'
but the remote endpoint provided a DNS claim 'pdd-virtasl.rmmg.rsr.rupar.puglia.it'.
If this is a legitimate remote endpoint, you can fix the problem by specifying
explicitly the DNS identity 'pdd-virtasl.rmmg.rsr.rupar.puglia.it'
as the Identity property of EndpointAddress when creating the channel proxy.
I asked the owner of the web service and they told me that I have to make sure to ignore the error, but I don't know how. I tried to insert in the app.config: enableUnsecuredResponse = "true" but it didn't work.
This is the method for connecting to the web service:
public static CVPClient Connect()
{
CVPClient oConsist = null;
string cEndPoint = "https://pddasl-coll.rmmg.rsr.rupar.puglia.it:8181/aslba/CVPService";
ServicePointManager.ServerCertificateValidationCallback = Leo.CertificateHandler;
datiOperatore DataOp = Leo.OperatorData();//unimportant parameters
datiApplicativo DataApp = Leo.AppData();//unimportant parameters
var b = new CustomBinding();
var sec = new AsymmetricSecurityBindingElement(
new X509SecurityTokenParameters(X509KeyIdentifierClauseType.Any, SecurityTokenInclusionMode.Never),
new X509SecurityTokenParameters(X509KeyIdentifierClauseType.Any, SecurityTokenInclusionMode.AlwaysToRecipient));
sec.MessageSecurityVersion = MessageSecurityVersion.WSSecurity10WSTrust13WSSecureConversation13WSSecurityPolicy12BasicSecurityProfile10;
sec.SecurityHeaderLayout = SecurityHeaderLayout.Strict;
sec.IncludeTimestamp = true;
sec.SetKeyDerivation(false);
sec.KeyEntropyMode = System.ServiceModel.Security.SecurityKeyEntropyMode.ServerEntropy;
sec.EnableUnsecuredResponse = true;
b.Elements.Add(sec);
b.Elements.Add(new TextMessageEncodingBindingElement(MessageVersion.Soap11, Encoding.UTF8));
b.Elements.Add(new HttpsTransportBindingElement());
EndpointAddress ea = new EndpointAddress(cEndPoint);
oConsist = new CVPClient(b, ea);
X509Certificate2 certSigned = Leo.GetSignedCert();//this returns my private certificate, not the one they replaced me
string cPin = "123456";
System.Security.SecureString SecurePIN = new System.Security.SecureString();
foreach (char ch in cPin)
{ SecurePIN.AppendChar(ch); }
var rsa = (RSACryptoServiceProvider)certSigned.PrivateKey;
string ContinerName = rsa.CspKeyContainerInfo.KeyContainerName;
string CspName = rsa.CspKeyContainerInfo.ProviderName;
int CspType = rsa.CspKeyContainerInfo.ProviderType;
CspParameters csp = new CspParameters(CspType, CspName, ContinerName, new System.Security.AccessControl.CryptoKeySecurity(), SecurePIN);
RSACryptoServiceProvider CSP = new RSACryptoServiceProvider(csp);
X509Certificate2 certUnsigned = Leo.GetUnSignedCertificate();//Here I read the new certificate
oConsist.ClientCredentials.ClientCertificate.Certificate = certSigned;
oConsist.ClientCredentials.ServiceCertificate.DefaultCertificate = certUnsigned;
oConsist.Open();
return oConsist;
}
As you can see also here I have inserted sec.EnableUnsecuredResponse = true; The connection is successful but, as I said before, I get the error when I call the web service.
How can I solve the problem?
UPDATE:
Since the error tells me to explicitly assign the DNS identity 'pdd-virtasl.rmmg.rsr.rupar.puglia.it'
as the Identity property of EndpointAddress, I replaced this line:
EndpointAddress ea = new EndpointAddress(cEndPoint);
with this:
DnsEndpointIdentity identity = new DnsEndpointIdentity(cEndPoint);
EndpointAddress ea = new EndpointAddress(new Uri(cEndPoint), identity, new AddressHeaderCollection());
but it doesn't work, I get the same error
The correct solution is this:
DnsEndpointIdentity identity = new DnsEndpointIdentity("pdd-virtasl.rmmg.rsr.rupar.puglia.it");
EndpointAddress ea = new EndpointAddress(new Uri(cEndPoint), identity, new AddressHeaderCollection());
You have to use the web service endpoint but specify the endopint contained in the certificate in the identity.
I am new to Web Services in general, and we are using .Net Framework 4.5.2, anyway I am trying to consume a web service that requires a certificate and a password.
I added the certificate gained from the providers in the project properties --> Resources --> file --> add, I also tried to use the SetCertificate() function but It seems to be a little complicated for me so I stick with loading the certificate from the properties as mentioned, however I already set all the binding setting as wanted, but somehow I am missing something, Here is my code:
string clientUrl = "some wsdl URL goes here";
BasicHttpsBinding binding = new BasicHttpsBinding
{
MaxReceivedMessageSize = Int32.MaxValue,
MaxBufferSize = Int32.MaxValue,
SendTimeout = new TimeSpan(0, 15, 0),
MessageEncoding = WSMessageEncoding.Text,
Security = {
Mode = BasicHttpsSecurityMode.Transport,
Transport = {
ClientCredentialType = HttpClientCredentialType.Certificate
}
}
};
ClientsClient testClient = new ClientsClient(binding, new EndpointAddress(new Uri(clientUrl)));
testClient.ClientCredentials.ClientCertificate.Certificate = LoadCertification();
private X509Certificate2 LoadCertification()
{
byte[] bytes = Properties.Resources.publicCert;
return new X509Certificate2(bytes, "password");
}
Note 1: The certificate extenstion is '.p12', It may be a list of certifications, if that is the case!, is it possible to pass them all?.
In the code I presented I am always getting The exception:
System.ServiceModel.ProtocolException: The 'Security' header from the namespace 'Some Http url goes here' not was understood by the recipient of the message. The message was not processed. The error usually indicates that the sender of the message has enabled a communication protocol that cannot be processed by the recipient. Verify that the client binding configuration is consistent with the service binding.
I tried to test the web service with "SOAP UI" and it worked, which made me sure that I am doing something wrong with the code, So I appreaciate any possible help that explains how to associate the certifcate in the code in the right way!.
EDIT:
in the .p12 file there are 3 certifications, which I tried to add also like this:
X509Certificate2Collection coll = LoadCertification();
int count = 0;
foreach (X509Certificate2 cert in coll)
{
testClient.ClientCredentials.ClientCertificate.Certificate = cert;
count++;// this variable is just to check the number of certificates
}
And I modified the loadCertification() method to look like this:
private X509Certificate2Collection LoadCertification()
{ string certPath = "C:/Users/ISA/Desktop/Progetti/Certificato e password/name.p12";
X509Certificate2Collection coll = new X509Certificate2Collection();
coll.Import(certPath , "password", X509KeyStorageFlags.DefaultKeySet);
return coll;
}
I coded a WCF Service using HttpTransportBindingElement in conjunction with IIS on port 80.
The code works fine as long as no proxy is used. But if a customer has a http-proxy the communication between WCF-Client and Server does not work in this case by occuring following error:
'There was no endpoint listening at ... that could accept the message. This is often caused by an incorrect address or SOAP action.'
It is essential to use settings by code ONLY!
here is my code approach for that issue but i stuck on it:
bool SendClientRequest(Action<ICustomerService> channel)
{
string proxy ="my.proxy.domain:8080";
string user = "user1";
string password="secret";
// maybe i do not need this 3 lines!
WebProxy webproxy = new WebProxy(proxy, true);
webproxy.Credentials = new NetworkCredential(user, password);
WebRequest.DefaultWebProxy = webproxy;
CustomBinding customBinding = new CustomBinding();
customBinding.Elements.Add(new HttpTransportBindingElement()
{
AuthenticationSchemes.None : AuthenticationSchemes.Basic,
ProxyAddress = string.IsNullOrEmpty(proxy) ? null : new Uri(proxy),
UseDefaultWebProxy = false,
BypassProxyOnLocal = true,
TransferMode = TransferMode.Streamed,
MaxReceivedMessageSize = 84087406592,
MaxBufferPoolSize = 0x1000000,
MaxBufferSize = 0x1000000
});
using (ChannelFactory<ICustomerService> factory = new
ChannelFactory<ICustomerService>(customBinding ))
{
IClientChannel contextChannel = null;
string url = "http://my.domain.de/Distribution/eService.svc",
EndpointAddress ep = new EndpointAddress(url);
ICustomerService clientChannel = factory.CreateChannel(ep);
contextChannel = clientChannel as IClientChannel;
contextChannel.OperationTimeout = TimeSpan.FromMinutes(rcvTimeout );
channel(clientChannel); // <- here i get the exception!
return true;
}
}
I tried several solution approaches but nothing seems to be specific like mine.
I think you have a few options, some of which I'll detail below.
First you could set UseDefaultWebProxy to true. This would then mean that proxy information is retrieved automatically from system proxy settings, configurable in Internet Explorer (Internet Options > Connections > LAN settings > Proxy server). This may be appropriate if you don't need to specify credentials for proxy use.
Another approach that's worked for me is to use the ProxyAuthenticationScheme property within your HttpTransportBindingElement() object. This property is only available on the CustomBinding class and allows an authentication scheme to be specified that will be used to authenticate against a proxy. In conjunction with this, the proxy server must be set against property ProxyAddress. Last but not least, the credentials to use against the proxy should be set according to the authentication scheme used, so for example, using AuthenticationSchemes.Ntlm would mean setting the UserName and Password properties on ChannelFactory.ClientCredentials.Windows.ClientCredential or perhaps ChannelFactory.ClientCredentials.HttpDigest.ClientCredential
With the second approach, be sure to note the difference between holding credentials in the ChannelFactory for use with the remote service versus credentials used for the proxy server. I've highlighted these in the code example below for clarity:
// Example service call using a CustomBinding that is configured for client
// authentication based on a user name and password sent as part of the message.
var binding = new CustomBinding();
TransportSecurityBindingElement securityBindingElement = SecurityBindingElement.CreateUserNameOverTransportBindingElement();
var secureTransport = new HttpsTransportBindingElement();
secureTransport.UseDefaultWebProxy = false;
secureTransport.ProxyAddress = new Uri("http://some-proxy");
secureTransport.ProxyAuthenticationScheme = AuthenticationSchemes.Ntlm;
binding.Elements.Add(securityBindingElement);
binding.Elements.Add(secureTransport);
var endpointAddress = new EndpointAddress("https://some-service");
var factory = new ChannelFactory<IService>(binding, endpointAddress);
// Credentials for authentication against the remote service
factory.Credentials.UserName.UserName = "serviceUser";
factory.Credentials.UserName.Password = "abc";
// Credentials for authentication against the proxy server
factory.Credentials.Windows.ClientCredential.UserName = "domain\user";
factory.Credentials.Windows.ClientCredential.Password = "xyz";
var client = factory.CreateChannel();
client.CallMethod();
I have the following code for adding a new Envelope with a local file:
EnvelopeDefinition envelope = new EnvelopeDefinition
{
Status = "sent"
};
// Get the contract file
Byte[] bytes = File.ReadAllBytes("[Local Filename]");
// Add a document to the envelope
DocuSign.eSign.Model.Document doc = new DocuSign.eSign.Model.Document();
doc.DocumentBase64 = System.Convert.ToBase64String(bytes);
doc.Name = contract.FileName;
doc.DocumentId = "1";
envelope.Documents = new List<DocuSign.eSign.Model.Document>();
envelope.Documents.Add(doc);
// Add a recipient to sign the documeent
Signer signer = new Signer();
signer.Email = recipientEmail;
signer.Name = recipientName;
signer.RecipientId = "1";
// Create a |SignHere| tab somewhere on the document for the recipient to sign
signer.Tabs = new Tabs();
signer.Tabs.SignHereTabs = new List<SignHere>();
SignHere signHere = new SignHere();
signHere.DocumentId = "1";
signHere.PageNumber = "1";
signHere.RecipientId = "1";
signHere.XPosition = "100";
signHere.YPosition = "150";
signer.Tabs.SignHereTabs.Add(signHere);
envelope.Recipients = new Recipients();
envelope.Recipients.Signers = new List<Signer>();
envelope.Recipients.Signers.Add(signer);
DocuSignAuthentication Creds = new DocuSignAuthentication
{
Username = "[My Username]",
Password = "[My Password]",
IntegratorKey = "[My Integration Key]"
};
ApiClient apiClient = new ApiClient("https://demo.docusign.net/restapi/v2/accounts/XXXXXXX");
string authHeader = JsonConvert.SerializeObject(Creds);
DocuSign.eSign.Client.Configuration cfg = new DocuSign.eSign.Client.Configuration(apiClient);
cfg.AddDefaultHeader("X-DocuSign-Authentication", authHeader);
EnvelopesApi envelopeApi = new EnvelopesApi(cfg);
EnvelopeSummary response = envelopeApi.CreateEnvelope("XXXXXXX", envelope);
The server returns a very little information in the error:
[ApiException: Error calling CreateEnvelope: ]
DocuSign.eSign.Api.EnvelopesApi.CreateEnvelopeWithHttpInfo(String accountId, EnvelopeDefinition envelopeDefinition, CreateEnvelopeOptions options) in
Y:\dev\SDKs\csharp\sdk\src\main\csharp\DocuSign\eSign\Api\EnvelopesApi.cs:2606
DocuSign.eSign.Api.EnvelopesApi.CreateEnvelope(String accountId, EnvelopeDefinition envelopeDefinition, CreateEnvelopeOptions options) in Y:\dev\SDKs\csharp\sdk\src\main\csharp\DocuSign\eSign\Api\EnvelopesApi.cs:2532
[Rest of the stack trace relates to our code]
If we try to use fiddler to capture any error messages from the server, the error becomes:
Could not establish trust relationship for the SSL/TLS secure channel
Is there anyway to get a more information about what's wrong with our request? Or is there an un-encrypted developer endpoint available to work through these kinds of issues? Any help you can spare would be greatly appreciated.
The error "Could not establish trust relationship for the SSL/TLS secure channel" means that the HTTPRequest call from the client (which can be your server, but is a client in the sense that it made the HTTPRequest) to the server (DocuSign in this case) could not use TLS over SSL (https:// address) to ensure that there's a certificate that allows to encrypt the information in the HTTPRequest and HttpResponse. This can happen for a variety of reasons including not having the current version of TLS on the client as well as having the wrong certificate or other issues with the network. I would recommend trying to make this call from a different server, potentially outside your corporate network to isolate the problem. Also, check the version of TLS to ensure it's at least 1.1