Excel Add-in, connecting to a web service through both http and https protocol - authentication

I'm making changes to a Excel Add-in that reads data from a server through a web service interface. Previously the servers where the data is read from didn't support https protocol. In the future they will, but some servers for the same purpose may not be changed to support https for a long time.
So, I would like to maintain flexible logging into the server: I would like that the user doesn't need to make choice between protocols. The user could be totally unaware of the protocol and needs only to give credentials and server name. However, the default protocol would be https. If the server, where data is read, doesn't support that, then http would be used.
The current implementation for logging in is approximately like this:
public partial class AuthenticationService : System.Web.Services.Protocols.SoapHttpClientProtocol {
public AuthenticationService() {
this.Url = global::ExcelAddInExtension.Base.Properties.Settings.Default.ExcelAddInExtension_Base_ServiceReference_AuthenticationService;
if ((this.IsLocalFileSystemWebService(this.Url) == true)) {
this.UseDefaultCredentials = true;
this.useDefaultCredentialsSetExplicitly = false;
}
else {
this.useDefaultCredentialsSetExplicitly = true;
}
}
public new string Url {
get {
return base.Url;
}
set {
if ((((this.IsLocalFileSystemWebService(base.Url) == true)
&& (this.useDefaultCredentialsSetExplicitly == false))
&& (this.IsLocalFileSystemWebService(value) == false))) {
base.UseDefaultCredentials = false;
}
base.Url = value;
}
}
[System.Web.Services.Protocols.SoapDocumentMethodAttribute("http://asp.net/ApplicationServices/v200/AuthenticationService/Login", RequestNamespace="http://asp.net/ApplicationServices/v200", ResponseNamespace="http://asp.net/ApplicationServices/v200", Use=System.Web.Services.Description.SoapBindingUse.Literal, ParameterStyle=System.Web.Services.Protocols.SoapParameterStyle.Wrapped)]
public void Login([System.Xml.Serialization.XmlElementAttribute(IsNullable=true)] string username, [System.Xml.Serialization.XmlElementAttribute(IsNullable=true)] string password, [System.Xml.Serialization.XmlElementAttribute(IsNullable=true)] string customCredential, bool isPersistent, [System.Xml.Serialization.XmlIgnoreAttribute()] bool isPersistentSpecified, out bool LoginResult, [System.Xml.Serialization.XmlIgnoreAttribute()] out bool LoginResultSpecified) {
object[] results = this.Invoke("Login", new object[] {
username,
password,
customCredential,
isPersistent,
isPersistentSpecified});
LoginResult = ((bool)(results[0]));
LoginResultSpecified = ((bool)(results[1]));
}
}
The actual login is done like this:
using (var authenticationService = new ServiceReference.AuthenticationService())
{
authenticationService.Url = "https://server/AuthenticationService.svc";
authenticationService.CookieContainer = new CookieContainer();
bool loginResult;
bool loginResultSpecified;
try
{
authenticationService.Login(userName, password, "", true, true, out loginResult, out loginResultSpecified);
}
catch (Exception ex)
{
Debug.Write(string.Format("Authentication failed, Error message: {0}, \r\nerror inner exception \r\n{1}, \r\nerror stack trace \r\n{2}.", ex.Message, ex.InnerException, ex.StackTrace)));
loginResult = false;
}
}
So, when trying to login to a server that doesn't support https, I get error
System.Net.Sockets.SocketException (0x80004005): No connection could be made because the target machine actively refused it server_ip_number:443.
This is understandable.
My approach was to retry login using http -protocol in the catch block.
authenticationService.Url = "http://server/AuthenticationService.svc";
authenticationService.Login(userName, password, "", true, true, out loginResult, out loginResultSpecified);
Unfortunately I get the same error
System.Net.Sockets.SocketException (0x80004005): No connection could be made because the target machine actively refused it server_ip_number:443.
Seems like the authenticationService object keeps the port number after the first attempt somewhere.
How to refresh or reinitialize that? Or, what kind of approach do you suggest?
The login itself works regardless of the protocol (to a server that supports https or if I try logging in first through http to a server that doesn't support https).
EDIT:
I'm almost ashamed about my question, and specially not understanding that the error message I got second time was actually from the first exception.
Anyway, I solved my problem. Solution below as an answer.

So, I solved my problem by checking first if the server I'm contacting from Excel Add-In is capable of using https-protocol. Then, according to the result, I used either http or https -protocol in the actual authentication.
The solution is originally provided here: How can I check if a server has ssl enable or not
private bool DetectSslSupport(string url)
{
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
request.Timeout = 5000;
try
{
using (HttpWebResponse response = (HttpWebResponse)request.GetResponse())
{
//some sites like stackoverflow will perform a service side redirect to the http site before the browser/request can throw an errror.
return response.ResponseUri.Scheme == "https";
}
}
catch (WebException webex)//"The underlying connection was closed: Could not establish trust relationship for the SSL/TLS secure channel."}
{
return false;
}
}

Related

What is the difference between public and private HTTPS connections?

I have a Net 5 Blazor server app published on a productive server and used a valid certificate for https requests, which works fine. Newly I added a HubConnection class to support SignalR notifications between web pages. But if I call the web page through an public URL like https://crm.example.com/call, I get the following error, although the same page works fine if I call it through an internal URL like https://10.12.0.151/call:
Unable to read data from the transport connection: An existing connection was forcibly closed by the remote host..
I don't know why it happens, what is the difference between a public and private HTTPS connection? For public connection it uses a valid public certificate and actually works fine if I deactivate the SignalR notification.
Because of the application working just fine if I call the page with an internal URL, seems that all prerequisite for using SignalR are included such as installing WebSocket-Protocol feature on the server and so on.
The following snipet shows the part of code:
try
{
string sHubUrl = NavManager.BaseUri;
sHubUrl = sHubUrl.TrimEnd('/') + "/call";
LogBuilder.LogInfo($"URL in Call.NotificationInit: " + sHubUrl);
hubConnection = new HubConnectionBuilder()
.WithUrl(sHubUrl, options => {
options.UseDefaultCredentials = true;
options.HttpMessageHandlerFactory = (msg) =>
{
if (msg is HttpClientHandler clientHandler)
{
System.Net.ServicePointManager.SecurityProtocol |= System.Net.SecurityProtocolType.Tls11 | System.Net.SecurityProtocolType.Tls12;
// bypass SSL certificate
clientHandler.ServerCertificateCustomValidationCallback +=
(sender, certificate, chain, sslPolicyErrors) => { return true; };
}
return msg;
};
})
.WithAutomaticReconnect()
.Build();
hubConnection.On<string, string>("NewMessage", ReceivedNotification);
await hubConnection.StartAsync();
}
catch (Exception ex)
{
LogBuilder.LogExecption("Exception at Call.NotificationInit");
}
What else should I do? Can anyone help me to solve this problem?

"No connection could be made because the target machine actively refused it." error in Blazor project

I'm teaching myself Blazor and have run into this conundrum, where I get this error:
{"No connection could be made because the target machine actively refused it."}
The uri I call is this one:
api/employee
However, when use the full uri, such as this:
https://localhost:44376/api/employee
I get no errors at all.
Even though this is just a practice project, I'd still prefer to use a relative uri without a port number, but am not sure how to make it work.
Here's the method where I am making these calls:
public async Task<IEnumerable<Employee>> GetAllEmployees()
{
bool isEmployeeListEmpty = true; ;
IEnumerable<Employee> employeeList = null;
try
{
//string uriEmployeeList = $"https://localhost:44376/api/employee";
string uriEmployeeList = $"api/employee";
var employees = await JsonSerializer.DeserializeAsync<IEnumerable<Employee>>
(await _httpClient.GetStreamAsync(uriEmployeeList), new JsonSerializerOptions() { PropertyNameCaseInsensitive = true });
if (employees != null)
{
isEmployeeListEmpty = false;
employeeList = await JsonSerializer.DeserializeAsync<IEnumerable<Employee>>
(await _httpClient.GetStreamAsync(uriEmployeeList), new JsonSerializerOptions() { PropertyNameCaseInsensitive = true });
}
else
{
isEmployeeListEmpty = true;
}
}
catch (Exception ex)
{
Console.WriteLine("An exception occurred inside the GetAllEmployees() method of the EmployeeDataService class");
Console.WriteLine(ex.Message);
}
if (!isEmployeeListEmpty)
return employeeList;
else
return null;
}
I am doing this all on one machine with IIS Express.
UPDATE
Thank you all for your help and suggestions. It turned out that I had the ssl port defined as 44376 in the lauchSettings.json, while I had the base addresses in Startup.cs file set as https://localhost:44340/ for all three HttpClient objects I use. I changed the port in each of the base addresses to 44376 to match the 44376 port I have set up in the launchSettings.json file, and everything now works with the relative/abbreviated uri strings.
Please see if api and web project are set under "Multiple startup" and both are ACTION : "start" are not (Right click on Solution > Set Startup Projects), its seems like api project is not started and refuse the connection and getting similar error while accessing the api controller.

SSL connectivity to Redis with StackExchange.Redis

I am having a very weird issue with StackExchange.Redis to connect with Redis.
I have enabled SSL on Redis database and I am not able to connect from client to Redis server with SSL certificate with below code.
static RedisConnectionFactory()
{
try
{
string connectionString = "rediscluster:13184";
var options = ConfigurationOptions.Parse(connectionString);
options.Password = "PASSWORD";
options.AllowAdmin = true;
options.AbortOnConnectFail = false;
options.Ssl = true;
options.SslHost = "HOSTNAME";
var certificate = GetCertificateFromThubprint();
options.CertificateSelection += delegate
{
return certificate;
};
Connection = new Lazy<ConnectionMultiplexer>(
() => ConnectionMultiplexer.Connect(options)
);
}
catch (Exception ex)
{
throw new Exception("Unable to connect to Cache Server " + ex);
}
}
public static ConnectionMultiplexer GetConnection() => Connection.Value;
public static IEnumerable<RedisKey> GetCacheKeys()
{
return GetConnection().GetServer("rediscluster", 13184).Keys();
}
// Find certificate based on Thumbprint
private static X509Certificate2 GetCertificateFromThubprint()
{
// Find certificate from "certificate store" based on thumbprint and return
StoreName CertStoreName = StoreName.Root;
string PFXThumbPrint = "NUMBER";
X509Store certLocalMachineStore = new X509Store(CertStoreName, StoreLocation.LocalMachine);
certLocalMachineStore.Open(OpenFlags.ReadOnly);
X509Certificate2Collection certLocalMachineCollection = certLocalMachineStore.Certificates.Find(
X509FindType.FindByThumbprint, PFXThumbPrint, true);
certLocalMachineStore.Close();
return certLocalMachineCollection[0];
}
However, If I create a console application and connect to Redis with above code then I am able to connect, but If I used same code from my web application to connect to redis then I am not able to connect.
Not sure if I am missing something.
Also, I went through "mgravell" post
In that post he has configured "CertificateValidation" method, In my scenario I want Redis to validate SSL certificate. so I have not implementation validation. And implemented "CertificateSelection" method to provide client certificate.
You can try to validate the cert using CertificateValidation. I tried the following code and it worked for me:
options.CertificateValidation += ValidateServerCertificate;
...
public static bool ValidateServerCertificate(
object sender,
X509Certificate certificate,
X509Chain chain,
SslPolicyErrors sslPolicyErrors)
{
if (sslPolicyErrors == SslPolicyErrors.None)
return true;
Console.WriteLine("Certificate error: {0}", sslPolicyErrors);
return false;
}
In cases like this where you are using a client certificate and it works in a console app but does not work for some other application (you don't say but I guess from an IIS hosted web app), it almost always has to do with whether the account has permission to access the private key.
The console app runs with your account which probably has access to the private key.
To give an account access
open the Local Computer certificate store
find your client certificate
right click and choose "All tasks -> Manage Provate Keys..."
click "Add..." and add the account.
Note: if your adding an IIS App Pool account the format is:
IIS APPPOOL<my app pool name>
Location should be the local machine and not a domain.
I was able to ssl the Redis server I had started on a VM with the following codes.
add stackexchange.redis visual studio
try
{
ConfigurationOptions configurationOptions = new ConfigurationOptions
{
KeepAlive = 0,
AllowAdmin = true,
EndPoints = { { "SERVER IP ADDRESS", 6379 }, { "127.0.0.1", 6379 } },
ConnectTimeout = 5000,
ConnectRetry = 5,
SyncTimeout = 5000,
AbortOnConnectFail = false,
};
configurationOptions.CertificateSelection += delegate
{
var cert = new X509Certificate2("PFX FILE PATH", "");
return cert;
};
ConnectionMultiplexer connection =
ConnectionMultiplexer.Connect(configurationOptions);
IDatabase databaseCache = connection.GetDatabase();
//set value
databaseCache.StringSet("KEYNAME", "KEYVALUE");
//get Value
label_show_value.Text = databaseCache.StringGet("KEYNAME").ToString();
}
catch (Exception e1)
{
}

Xamarin Portable Class Library Gets Proxy Access Denied on iPhone Simulator

I've run into a bit of an issue with the iPhone simulator when trying to access a WCF REST service.
I've asked the question on the Xamarin forums, but no joy.
Some context:
I have a PCL for a Xamarin cross platform project, in VS 2012.
I use the Portable Microsoft HttpClient package and the Json.NET package.
I have a pretty simple WCF REST service sitting in the background.
When testing
I can access the service fine from a browser on the dev machine.
I can access it fine using a console application going via the PCL.
I can access it fine via the app, from a real android device on the WiFi network of
the same corporate network.
I can access it fine from Safari on the build Mac.
I can access it fine from Safari on the iPhone simulator on the build Mac.
The issue is, as soon as I try to access the service via the app on the iPhone simulator, I get a 407, Proxy Access Denied error.
Here is the code I'm using to set up the connection:
private static HttpRequestMessage PrepareRequestMessage(HttpMethod method, string baseUri,
string queryParameters, out HttpClient httpClient, string bodyContent)
{
var finalUri = new Uri(baseUri + queryParameters);
var handler = new HttpClientHandler();
httpClient = new HttpClient(handler);
var requestMessage = new HttpRequestMessage(method, finalUri);
if (handler.SupportsTransferEncodingChunked())
{
requestMessage.Headers.TransferEncodingChunked = true;
}
if (method == HttpMethod.Post || method == HttpMethod.Put)
{
requestMessage.Content =
new StringContent(bodyContent, Encoding.UTF8, "application/json");
}
return requestMessage;
}
That code gives me the 407 error.
I have tried setting the proxy by using various combinations of SupportsProxy and SupportsUseProxy. (Both returning false from the simulator.)
I've tried forcing the proxy settings regardless. I've tried setting the credentials on the handler itself. I've tried playing with the UseDefaultCredentials and UseProxy flags. I've also tried setting the IfModifiedSince value in the message header. I've tried using the PortableRest package as well.
All of that only seemed to make things worse. Where I was initially getting the 407 error, the call to httpClient.GetAsync would just immediately return null.
I am at a bit of a loss here, so any help would be greatly appreciated.
PS. For completeness, the rest of the surrounding code that makes the call: (please forgive crappy exception handling, I'm still playing around with the errors)
public static async Task<T> SendRESTMessage<T>(HttpMethod method, string baseUri,
string queryParameters, T contentObject)
{
HttpClient httpClient;
var payload = string.Empty;
if (contentObject != null)
{
payload = JsonConvert.SerializeObject(contentObject);
}
var requestMessage =
PrepareRequestMessage(method, baseUri, queryParameters, out httpClient, payload);
HttpResponseMessage responseMessage = null;
try
{
if (method == HttpMethod.Get)
{
responseMessage = await httpClient.GetAsync(requestMessage.RequestUri);
}
else
{
responseMessage = await httpClient.SendAsync(requestMessage);
}
}
catch (HttpRequestException exc)
{
var innerException = exc.InnerException as WebException;
if (innerException != null)
{
throw new Exception("Unable to connect to remote server.");
}
}
return await HandleResponse<T>(responseMessage);
}
private static async Task<T> HandleResponse<T>(HttpResponseMessage responseMessage)
{
if (responseMessage != null)
{
if (!responseMessage.IsSuccessStatusCode)
{
throw new Exception("Request was unsuccessful");
}
var jsonString = await responseMessage.Content.ReadAsStringAsync();
var responseObject = JsonConvert.DeserializeObject<T>(jsonString);
return responseObject;
}
return default(T);
}
This was my attempt at implementing IWebProxy quick and dirty, which I think could have made things worse:
public class MyProxy : IWebProxy
{
private System.Net.ICredentials creds;
public ICredentials Credentials
{
get
{
return creds;
}
set
{
creds = value;
}
}
public Uri GetProxy(Uri destination)
{
return new Uri("proxy addy here");
}
public bool IsBypassed(Uri host)
{
return true;
}
}
Thanks again for taking the time to read my question.
So I finally got it working.
Turns out it was something really stupid, but being new to iOS mobile dev and the fact that the service worked via Safari on the simulator threw me for a loop.
I read that the simulator uses the proxy settings as defined on the Mac. So I went to the network settings and added the service address to the proxy bypass list.
Works like a charm now.
If anybody feels there is a better way to do this, please add your opinions.

Has anyone ever got WS-Trust to work in JBoss 7?

I've literally tried everything under the sun to get token based WS-Trust Web Services to work, to no avail. I can obtain a token from an STS, but the life of me, I can not figure out how make the WS server secure and accessible from the outside using a token.
So what I would love to know, is if anyone has ever got this to work on JBoss 7. I'm not interested in "this and that on jboss should give you some information". Been there done that - doesn't work. Have YOU been able to get it to work?
I looked at picketlink to secure web services using SAML but it appears to be exposing the SAML authentication using a JAAS security context. So instead I just wrote a custom handler using the picketlink API to secure the WS. The handler essentially does the same thing (i.e. saml assertion expiration and digital signature validation check) as the SAMLTokenCertValidatingCommonLoginModule available in picketlink jars but passes the SAML attributes into WS message context instead of passing it along as a JAAS security context.
Find below the code snippet.
See org.picketlink.identity.federation.bindings.jboss.auth.SAMLTokenCertValidatingCommonLoginModule
class of the picketlink-jbas-common source for implementation of methods getX509Certificate, validateCertPath used in the custom handler.
public class CustomSAML2Handler<C extends LogicalMessageContext> implements SOAPHandler {
protected boolean handleInbound(MessageContext msgContext) {
logger.info("Handling Inbound Message");
String assertionNS = JBossSAMLURIConstants.ASSERTION_NSURI.get();
SOAPMessageContext ctx = (SOAPMessageContext) msgContext;
SOAPMessage soapMessage = ctx.getMessage();
if (soapMessage == null)
throw logger.nullValueError("SOAP Message");
// retrieve the assertion
Document document = soapMessage.getSOAPPart();
Element soapHeader = Util.findOrCreateSoapHeader(document.getDocumentElement());
Element assertion = Util.findElement(soapHeader, new QName(assertionNS, "Assertion"));
if (assertion != null) {
AssertionType assertionType = null;
try {
assertionType = SAMLUtil.fromElement(assertion);
if (AssertionUtil.hasExpired(assertionType))
throw new RuntimeException(logger.samlAssertionExpiredError());
} catch (Exception e) {
logger.samlAssertionPasingFailed(e);
}
SamlCredential credential = new SamlCredential(assertion);
if (logger.isTraceEnabled()) {
logger.trace("Assertion included in SOAP payload: " + credential.getAssertionAsString());
}
try {
validateSAMLCredential(credential, assertionType);
ctx.put("roles",AssertionUtil.getRoles(assertionType, null));
ctx.setScope("roles", MessageContext.Scope.APPLICATION);
} catch (Exception e) {
logger.error("Error: " + e);
throw new RuntimeException(e);
}
} else {
logger.trace("We did not find any assertion");
}
return true;
}
private void validateSAMLCredential(SamlCredential credential, AssertionType assertion) throws LoginException, ConfigurationException, CertificateExpiredException, CertificateNotYetValidException {
// initialize xmlsec
org.apache.xml.security.Init.init();
X509Certificate cert = getX509Certificate(credential);
// public certificate validation
validateCertPath(cert);
// check time validity of the certificate
cert.checkValidity();
boolean sigValid = false;
try {
sigValid = AssertionUtil.isSignatureValid(credential.getAssertionAsElement(), cert.getPublicKey());
} catch (ProcessingException e) {
logger.processingError(e);
}
if (!sigValid) {
throw logger.authSAMLInvalidSignatureError();
}
if (AssertionUtil.hasExpired(assertion)) {
throw logger.authSAMLAssertionExpiredError();
}
}
}