Consuming Teamcity REST API using WCF - wcf

I use following code to create a channel:
var basicHttpBinding = new BasicHttpBinding(BasicHttpSecurityMode.TransportCredentialOnly);
basicHttpBinding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Basic;
basicHttpBinding.Security.Transport.Realm = "TeamCity";
var channelFactory = ChannelFactory<IMyInterface>(basicHttpBinding);
channelFactory.EndPint.Address = new EndPoint("http://localhost/httpAuth/app/rest/version");
channelFactory.Credentials.Username.Username = 'username';
channelFactory.Credentials.Username.Password = 'password';
channelFactory.Open();
var channel = channel.CreateChannel();
var verInfo = channel.GetVersion();
but when I run this code, CLR raise "The remote server returned an unexpected response:(405 Method not allowed)"
how can I do basic http authentication with wcf?

Related

Migrating a SOAP client from to .NET5 that uses WSE and Microsoft.Web.Services2

I am migrating a .NetFramework application that access a DMS system over SOAP
The working implementation involves some generated code where it was necessary to change the base class to Microsoft.Web.Services2.WebServiceClientProtocol in order for the security headers to be correctly built.
public partial class MyService: Microsoft.Web.Services2.WebServicesClientProtocol
The following code successfully calls the WsSearchDmsDocument
var token = new UsernameToken(DmsUsername, DmsPassword, PasswordOption.SendHashed);
var client = new MyService() {Url = ReinsUrl};
SoapContext requestContext = client.RequestSoapContext;
requestContext.Security.Timestamp.TtlInSeconds = 60;
requestContext.Security.Tokens.Add(token);
var myRequest = new Request();
var response = client.WsSearchDmsDocument(request);
Which sends the username/password security header looking like and returns the expected response
<wsse:UsernameToken wsu:Id='UsernameToken-238be95be3bf445fb8534666a7a8693c'>
<wsse:Username>***login***</wsse:Username>
<wsse:Password Type='http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-usernametoken-profile-1.0#PasswordDigest'>***Base64 (SHA-1 (nonce + created + password) )***</wsse:Password>
<wsse:Nonce EncodingType='http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soapmessage-security-1.0#Base64Binary'>***Base64 nonce***</wsse:Nonce>
<wsu:Created>2019-09-06T12:09:15.604Z</wsu:Created>
</wsse:UsernameToken>
In .Net5
I modified the MyService class in the following way
internal partial class MyService: System.ServiceModel.ClientBase<MyReinsServices>, MyReinsServices
Then I try to call the service
var token = new UsernameToken(DmsUsername, DmsPassword, PasswordOption.SendHashed);
MyService client = new MyService(ReinsServicesClient.EndpointConfiguration.ReinsServicesSoap11, ReinsUrl);
UserNamePasswordClientCredential credential = client.ClientCredentials.UserName;
credential.UserName = DmsUsername;
credential.Password = DmsPassword;
var myRequest = new Request();
var response = client.WsSearchDmsDocument(request);
But this fails with
com.sun.xml.wss.XWSSecurityException:
Message does not conform to configured policy [ AuthenticationTokenPolicy(S) ]:
No Security Header found;
nested exception is com.sun.xml.wss.XWSSecurityException:
com.sun.xml.wss.XWSSecurityException:
Message does not conform to configured policy [ AuthenticationTokenPolicy(S) ]:
No Security Header found
This is a similar error to what I was getting in the .Net Framework version before I used Microsoft.Web.Services2.WebServicesClientProtocol
I think I am very close to a solution but no matter what client.ClientCredentials I take it does not satisfy the security header needed by this particular SOAP service
EDIT
I am able to use SoapUI to call this service. I have to set in WS-Security setting a username and password with PasswordDigest and adding this as a Basic Auth to the outgoing WSS. I then copy the resulting SOAP Envelope into Soap.txt and try to send this via .NET5 in the following code
string soap = File.ReadAllText("soup.txt");
XmlDocument document = new XmlDocument();
document.LoadXml(soap); //loading soap message as string
XmlNamespaceManager manager = new XmlNamespaceManager(document.NameTable);
manager.AddNamespace("reins", "http://scor.com/dms-reins-webservices/schemas/2.0/reins");
manager.AddNamespace("wsse", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd");
manager.AddNamespace("soapenv", "http://schemas.xmlsoap.org/soap/envelope/");
manager.AddNamespace("wsu", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd");
//Build the token
RNGCryptoServiceProvider Generator = new RNGCryptoServiceProvider();
var _nonce = new byte[16];
Generator.GetBytes(_nonce);
string nonce = Convert.ToBase64String(_nonce);
var created = DateTime.Now.ToString("yyyy-MM-ddTHH:mm:ssZ");
string payLoad = nonce + created + DmsPassword;
byte[] payLoadBytes = System.Text.Encoding.UTF8.GetBytes(payLoad);
SHA1 sha = new SHA1CryptoServiceProvider();
var tokenBytes = sha.ComputeHash(payLoadBytes);
string token = Convert.ToBase64String(tokenBytes);
document.SelectSingleNode("//soapenv:Envelope/soapenv:Header/wsse:Security/wsse:UsernameToken/wsu:Created", manager).InnerText = created;
document.SelectSingleNode("//soapenv:Envelope/soapenv:Header/wsse:Security/wsse:UsernameToken/wsse:Password", manager).InnerText = token;
document.SelectSingleNode("//soapenv:Envelope/soapenv:Header/wsse:Security/wsse:UsernameToken/wsse:Nonce", manager).InnerText = nonce;
var httpClient = new HttpClient();
httpClient.DefaultRequestVersion = HttpVersion.Version11;
HttpResponseMessage response;
var soapMessage = new HttpRequestMessage
{
Method = HttpMethod.Post,
RequestUri = new Uri(url),
Content = new StringContent(s, Encoding.UTF8, MediaTypeNames.Text.Xml),
};
soapMessage.Headers.Add("Accept", "text/xml");
soapMessage.Headers.Add("Accept-Encoding", "gzip,deflate");
soapMessage.Headers.Add("SOAPAction", "");
soapMessage.Headers.Add("Connection", "Keep-Alive");
soapMessage.Headers.Add("User-Agent", "Apache-HttpClient/4.5.5 (Java/12.0.1)");
response = httpClient.Send(soapMessage);
When I make the request via SoapUi I get a StatusCode 200 and the data I want. When I use the above code that should perform the exact same operation I get an StatusCode 500 Internal Server error.
Any suggestions how to solve either the first part of this or to get this hack to work using .NET5 or .NET6 would be welcome

Configuration for ClientCredentials ServiceCertificate authentication not applied or used

I am building a .NET Core 3.1 application where I am trying to call a WCF Service over HTTPS and temporarily disabling SSL authentication for the server certificate.
There is a clearly documented way to achieve this. Namely, by setting the ServiceCertificate.SslCertificateAuthentication property on the ChannelFactory class.
Below is code for setting up het Binding, endpoint and ClientCredentials.
var endpointAddress = new EndpointAddress("https://*.com");
var binding = new BasicHttpsBinding();
binding.Security.Mode = BasicHttpsSecurityMode.Transport;
binding.Security.Transport = new HttpTransportSecurity()
{
ClientCredentialType = HttpClientCredentialType.None
};
var factory = new ChannelFactory<IService>(binding, endpointAddress);
factory.Credentials.ServiceCertificate.SslCertificateAuthentication = new X509ServiceCertificateAuthentication()
{
CertificateValidationMode = X509CertificateValidationMode.None,
RevocationMode = X509RevocationMode.NoCheck
};
factory.Credentials.ServiceCertificate.Authentication.CertificateValidationMode = X509CertificateValidationMode.None;
factory.Credentials.ServiceCertificate.Authentication.RevocationMode = X509RevocationMode.NoCheck;
var client = factory.CreateChannel();
client.Call();
However, when I run this code I receive the exception chain:
Could not establish trust relationship for the SSL/TLS secure channel
with authority 'domain'
The SSL connection could not be established,
see inner exception.
Authentication failed, see inner exception. The
message received was unexpected or badly formatted.
I would expect the WCF client to have skipped SSL authentication.
I also tried to use a custom certificate validator, by extending the X509CertificateValidator and configuring this in the following way:
factory.Credentials.ServiceCertificate.SslCertificateAuthentication = new X509ServiceCertificateAuthentication()
{
CertificateValidationMode = X509CertificateValidationMode.Custom,
CustomCertificateValidator = new CustomCertificateValidator();
};
factory.Credentials.ServiceCertificate.Authentication.CertificateValidationMode = X509CertificateValidationMode.Custom;
factory.Credentials.ServiceCertificate.Authentication.CustomCertificateValidator = new CustomCertificateValidator();
As you might expect as this point, I receive the same exceptions as before. Even worse though, my CustomCertificate.Validate(..) method was not being called at all.
WCF seems to provide an API which allows for quite a bit of control but no matter what I try, my policies/configurations do not seem to by honoured in any way.
What might be going on here?
The below code will work when requiring the SSL authentication in a DotCore project.
Uri uri = new Uri("https://vabqia969vm:21011");
BasicHttpsBinding binding = new BasicHttpsBinding();
binding.Security.Mode = BasicHttpsSecurityMode.Transport;
binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.None;
ChannelFactory<IService> factory = new ChannelFactory<IService>(binding, new EndpointAddress(uri));
factory.Credentials.ServiceCertificate.SslCertificateAuthentication = new System.ServiceModel.Security.X509ServiceCertificateAuthentication()
{
CertificateValidationMode = System.ServiceModel.Security.X509CertificateValidationMode.None,
RevocationMode = System.Security.Cryptography.X509Certificates.X509RevocationMode.NoCheck
};
//these two lines will not work.
//factory.Credentials.ServiceCertificate.Authentication.CertificateValidationMode = System.ServiceModel.Security.X509CertificateValidationMode.None;
//factory.Credentials.ServiceCertificate.Authentication.RevocationMode = System.Security.Cryptography.X509Certificates.X509RevocationMode.NoCheck;
var client = factory.CreateChannel();
var result = client.TestAsync();
Console.WriteLine(result.Result);
On my side, it works perfectly. I think there is something wrong with the server-side. As you know, we should ensure that the binding type between the client-side and the server-side is consistent. What are the details on the server-side?

getting "Could not establish trust relationship for the SSL/TLS secure channel with authority" when running fiddler

I have a WCF client application that sends a https request to a third-party webservice. everythings works well. I receive the correct answer.
I have installed fiddler and trusted its rootcertificate to capture and encode the https traffic. But now my requests recieve the error "Could not establish trust relationship for the SSL/TLS secure..."
I have searched a lot on this error on the web. But each time the solution is that I need to trust the fiddler root certificate. Since I have done this from het start, it looks like this is not the solution for my problem.
Here is a snippet out of my code that sends the request:
AsymmetricSecurityBindingElement securityBindingElement = new AsymmetricSecurityBindingElement();
securityBindingElement.InitiatorTokenParameters = new X509SecurityTokenParameters { InclusionMode = SecurityTokenInclusionMode.Never };
securityBindingElement.RecipientTokenParameters = new X509securityTokenParameters();
securityBindingElement.EnableUnsecuredResponse = true;
securityBindingElement.EndpointSupportingTokenParameters.Signed.Add(new X509SecurityTokenParameters());
CustomBinding binding = new CustomBinding(securityBindingElement, new TextMessageEncodingBindingElement(MessageVersion.Soap11, Encoding.UTF8), new HttpsTransportBindingElement());
EndpointAddress endpoint = new EndpointAddress(new Uri(URL), new X509CertificateEndpointIdentity(serverCertificate));
ChannelFactory<type> channelFactory = new ChannelFactory<type>(binding, endpoint);
channelFactory.Credentials.ClientCertificate.Certificate = clientX509Certificaat;
channelFactory.Credentials.ServiceCertificate.Authentication.RevocationMode = X509RevocationMode.NoCheck;
channelFactory.Endpoint.Contract.ProtectionLevel = ProtectionLevel.Sign;
type client = channelFactory.CreateChannel();
client.request();
Somebody knows what is what is going wrong here?
I changed my code to:
AsymmetricSecurityBindingElement securityBindingElement = new AsymmetricSecurityBindingElement();
securityBindingElement.InitiatorTokenParameters = new X509SecurityTokenParameters { InclusionMode = SecurityTokenInclusionMode.Never };
securityBindingElement.RecipientTokenParameters = new X509securityTokenParameters();
securityBindingElement.EnableUnsecuredResponse = true;
securityBindingElement.EndpointSupportingTokenParameters.Signed.Add(new X509SecurityTokenParameters());
CustomBinding binding = new CustomBinding(securityBindingElement, new TextMessageEncodingBindingElement(MessageVersion.Soap11, Encoding.UTF8), new HttpsTransportBindingElement());
EndpointAddress endpoint = new EndpointAddress(new Uri(URL));
ChannelFactory<type> channelFactory = new ChannelFactory<type>(binding, endpoint);
channelFactory.Credentials.ClientCertificate.Certificate = clientX509Certificaat;
channelFactory.Credentials.ServiceCertificate.DefaultCertificate = serverX509Certificaat;
channelFactory.Endpoint.Contract.ProtectionLevel = ProtectionLevel.Sign;
type client = channelFactory.CreateChannel();
client.request();
It is not clear for me why I do not have problems anymore. Anyone?

WCF Security exception when calling from Windows Service

I have some code which consumes a WCF service. The service is protected by basic authentication, so on creating the client, I'm using the following code:
BasicHttpBinding httpBinding = new BasicHttpBinding();
httpBinding.Security.Mode = BasicHttpSecurityMode.TransportCredentialOnly;
httpBinding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Basic;
httpBinding.Security.Transport.ProxyCredentialType = HttpProxyCredentialType.None;
httpBinding.Security.Transport.Realm = service_realm;
EndpointAddress address = new EndpointAddress(service_address);
Service.ServiceClient client = new Service.ServiceClient(httpBinding, address);
client.ClientCredentials.UserName.UserName = service_username;
client.ClientCredentials.UserName.Password = service_password;
Works fine when I run the code from a console app. But when I run the same code from a windows service, a MessageSecurityException is being thrown telling me that my request was unauthorized. For some reason it seems to be using the current Windows account for authentication, because my own account does have access to the service. But I don't want it to, I want it to use the stored credentials. What am I missing here?
WCF basicHttpBinding does not support plaintext credentials; the reason is because the moment you want pass credentials around on a transport binding, WCF requires the underlying transport to be a secure transport, such as SSL.
In order for your code to work, you would then need to use service via https or using certificates or encryption.
Seems to be fixed using this config:
_httpBinding = new BasicHttpBinding();
_httpBinding.Security.Mode = BasicHttpSecurityMode.TransportCredentialOnly;
_httpBinding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Basic;
_httpBinding.Security.Transport.ProxyCredentialType = HttpProxyCredentialType.None;
_httpBinding.Security.Message.ClientCredentialType = BasicHttpMessageCredentialType.UserName;
_httpBinding.Security.Message.AlgorithmSuite = SecurityAlgorithmSuite.Default;
_httpBinding.AllowCookies = false;
_httpBinding.BypassProxyOnLocal = false;
_httpBinding.HostNameComparisonMode = HostNameComparisonMode.StrongWildcard;
_httpBinding.MessageEncoding = WSMessageEncoding.Text;
_httpBinding.TextEncoding = Encoding.UTF8;
_httpBinding.TransferMode = TransferMode.Buffered;
_httpBinding.UseDefaultWebProxy = false;
Service.ServiceClient client = new Service.ServiceClient(_httpBinding, _address);
client.ClientCredentials.UserName.UserName = service_username;
client.ClientCredentials.UserName.Password = service_password;

WCF : Configuring message security programmatically

I'm coding an Azure WCF Service Bus service, which is to be configured programmatically to have message security using certificates:
ServiceBusEnvironment.SystemConnectivity.Mode = ConnectivityMode.Tcp;
// create the service URI based on the service namespace
Uri address = ServiceBusEnvironment.CreateServiceUri("sb", ConfigurationManager.AppSettings["serviceNamespace"], "TestService");
// create the credentials object for the endpoint
TransportClientEndpointBehavior sharedSecretServiceBusCredential = new TransportClientEndpointBehavior();
sharedSecretServiceBusCredential.TokenProvider = TokenProvider.CreateSharedSecretTokenProvider(ConfigurationManager.AppSettings["issuerName"], ConfigurationManager.AppSettings["issuerSecret"]);
//Create and bind the serviceEndpoint
ContractDescription contractDescription = ContractDescription.GetContract(typeof(ITestContract), typeof(TestServiceImpl));
ServiceEndpoint serviceEndPoint = new ServiceEndpoint(contractDescription);
serviceEndPoint.Address = new EndpointAddress(address);
var NetTcpRelayBinding = new NetTcpRelayBinding(EndToEndSecurityMode.TransportWithMessageCredential, RelayClientAuthenticationType.RelayAccessToken);
NetTcpRelayBinding.Security.Message.ClientCredentialType = MessageCredentialType.Certificate; //The serivice will check the TrustedPeople store for the client
serviceEndPoint.Binding = NetTcpRelayBinding;
serviceEndPoint.Behaviors.Add(sharedSecretServiceBusCredential);
Host = new ServiceHost(typeof(TestServiceImpl), address);
//Add a service certificate
Host.Credentials.ClientCertificate.Authentication.CertificateValidationMode = X509CertificateValidationMode.PeerTrust;
Host.Credentials.ServiceCertificate.SetCertificate(StoreLocation.LocalMachine,StoreName.My,X509FindType.FindByThumbprint,"E86870F0118CE39D771A49B9337C28444F3C7348");
// create the service host reading the configuration
Host.Description.Endpoints.Add(serviceEndPoint);
I can get this service up and running, however, any client )with just the ServiceBus SharedSecret, clientCredentials NOT set to use any cert) is able to call my service without any errors.
Is the above code sufficient to indicate that certificates (and only certificates base authorization) should be used for message security ?
Any good articles on configuring WCF message security programmatically ?
Turns out that lack of sleep was the culprit; I was running an older version of the service. Clients without any certificates do error out (with System.ServiceModel.ProtocolException was unhandled Message=Error while reading message framing format at position 1 of stream (state: Start).
A properly coded up client for this is :
ServiceBusEnvironment.SystemConnectivity.Mode = ConnectivityMode.Tcp;
string serviceNamespace = "valid-namespace";
string issuerName = "owner";
string issuerSecret = "validSecret";
// create the service URI based on the service namespace
Uri serviceUri = ServiceBusEnvironment.CreateServiceUri("sb", serviceNamespace, "valid-namespace");
// create the credentials object for the endpoint
TransportClientEndpointBehavior sharedSecretServiceBusCredential = new TransportClientEndpointBehavior();
sharedSecretServiceBusCredential.CredentialType = TransportClientCredentialType.SharedSecret;
sharedSecretServiceBusCredential.Credentials.SharedSecret.IssuerName = issuerName;
sharedSecretServiceBusCredential.Credentials.SharedSecret.IssuerSecret = issuerSecret;
ChannelFactory<ITestChannel> channelFactory = new ChannelFactory<ITestChannel>();
channelFactory.Endpoint.Address = new EndpointAddress(serviceUri);
var NTRB = new NetTcpRelayBinding();
NTRB.Security.Mode = EndToEndSecurityMode.TransportWithMessageCredential;
NTRB.Security.Message.ClientCredentialType = MessageCredentialType.Certificate;
channelFactory.Endpoint.Binding = NTRB;
channelFactory.Endpoint.Contract.ContractType = typeof(ITestChannel);
// apply the Service Bus credentials
channelFactory.Endpoint.Behaviors.Add(sharedSecretServiceBusCredential);
//Question : Why doesn't use of the following line effect Service-Validation ? I can successfully call the service from a machine where the server's certificate does NOT exist in the trusted-people store
//channelFactory.Credentials.ServiceCertificate.Authentication.CertificateValidationMode = System.ServiceModel.Security.X509CertificateValidationMode.PeerTrust;
channelFactory.Credentials.ClientCertificate.SetCertificate(StoreLocation.LocalMachine, StoreName.My, X509FindType.FindByThumbprint, "valid-thubmprint");
// create and open the client channel
ITestChannel channel = channelFactory.CreateChannel();
Console.WriteLine(channel.ServiceMethod());
Console.ReadKey();
channel.Close();
channelFactory.Close();
Still have the problem of the ServiceCertificate always being assumed valid, even when PeerTrust is used for channelFactory.Credentials.ServiceCertificate.Authentication.CertificateValidationMode and the service certificate isn't in the TrustedPeople store.
Anyone with ideas on why this happens ?