Rejecting all client certificates in IIS - ssl

Just as this person, I've been struggling a bit with browsers caching SSL sessions. In short, if a client certificate is selected, there is no way to clear the state programmatically, except in IE using document.execCommand("ClearAuthenticationCache").
One of the answers mentions that making a request to "a URL on the same hostname that requires a client certificate but rejects all certificates" it would force the browser to clear the SSL session. How can I set up such an endpoint in IIS? Because I presume I need more than just a simple endpoint returning http status 403 or similar.

The SSL negotiation happens before the endpoint request is sent, so there is no way of "rejecting a certificate" based on the endpoint (you can perhaps force renegotiation, but I'm not sure IIS supports it).
But you can maybe set up the same hostname and a different port and disable client certificates there. Since the hostname matches (being the same...), I'd expect the browser to try them, and fail.

Short Answer: delete sslcert [ipport=]IP Address:port ref
If you want to script/automate it in code, you could do it in C# in two steps below, you would need to adapt the code to suit your needs
1. Get your certs
using (var store = new X509Store(StoreName.My, StoreLocation.LocalMachine))
{
store.Open(OpenFlags.ReadOnly | OpenFlags.OpenExistingOnly);
// Get /Display a list of all the certificates
foreach (var x in store.Certificates)
{
// *** TODO
// add it to a drop down
// SomeDropDownListControl_IISCert.Items.Add(new SomeDropDownListControl_IISCert(x.FriendlyName, x.SerialNumber));
//or delete it, see Below
}
}
2. Build the command and pass the cert and delete it with the Shell Command
StringBuilder str = new StringBuilder();
ProcessStartInfo psi = new ProcessStartInfo() {CreateNoWindow = true, UseShellExecute = false, RedirectStandardOutput = true};
psi.FileName = "netsh";
psi.Arguments = $"http show sslcert ipport=0.0.0.0:{port}";
Process procShow = Process.Start(psi);
while (procShow != null && !procShow.StandardOutput.EndOfStream)
{
str.Append(procShow.StandardOutput.ReadLine());
}
Log.Warn(str.ToString);
// delete IPV4.
psi.Arguments = $"http delete sslcert ipport=0.0.0.0:{port}";
Process procDel = Process.Start(psi);
//exitCode = procDel.ExitCode;

Related

Google OAuth 2.0 for desktop apps for Windows without Admin privileges

I've heard about Google's plan of modernizing OAuth interactions described here: https://developers.googleblog.com/2016/08/modernizing-oauth-interactions-in-native-apps.html
Then I was looking at the sample desktop application for Windows found here: https://github.com/googlesamples/oauth-apps-for-windows/tree/master/OAuthDesktopApp.
It's pretty simple and it was working, but once I started Visual Studio without elevated privileges (as a non-admin), I experienced that the HttpListener was not able to start because of the following error: "Access Denied".
It turned out that starting an HttpListener at the loopback address (127.0.0.1) is not possible without admin rights. However trying localhost instead of 127.0.0.1 lead to success.
I found that there is a specific command that allows HttpListener to start at the given address (and port):
netsh http add urlacl url=http://+:80/MyUri user=DOMAIN\user
But it also can be only executed with admin rights, so it's not an option.
Still localhost seems to be the best shot but OAuth 2.0 for Mobile & Desktop Apps states the following regarding this section:
See the redirect_uri parameter definition for more information about the loopback IP address. It is also possible to use localhost in place of the loopback IP, but this may cause issues with client firewalls. Most, but not all, firewalls allow loopback communication.
This is why I'm a bit suspicious to use localhost. So I'm wondering what is the recommended way of Google in this case, as I'm not intending to run our application as administrator just for this reason.
Any ideas?
You can use TcpListener for instance instead of HttpListener. It does not need elevation to listen.
The following is a modified excerpt of this sample:
https://github.com/googlesamples/oauth-apps-for-windows/tree/master/OAuthDesktopApp
// Generates state and PKCE values.
string state = randomDataBase64url(32);
string code_verifier = randomDataBase64url(32);
string code_challenge = base64urlencodeNoPadding(sha256(code_verifier));
const string code_challenge_method = "S256";
// Creates a redirect URI using an available port on the loopback address.
var listener = new TcpListener(IPAddress.Loopback, 0);
listener.Start();
string redirectURI = string.Format("http://{0}:{1}/", IPAddress.Loopback, ((IPEndPoint)listener.LocalEndpoint).Port);
output("redirect URI: " + redirectURI);
// Creates the OAuth 2.0 authorization request.
string authorizationRequest = string.Format("{0}?response_type=code&scope=openid%20profile&redirect_uri={1}&client_id={2}&state={3}&code_challenge={4}&code_challenge_method={5}",
authorizationEndpoint,
System.Uri.EscapeDataString(redirectURI),
clientID,
state,
code_challenge,
code_challenge_method);
// Opens request in the browser.
System.Diagnostics.Process.Start(authorizationRequest);
// Waits for the OAuth authorization response.
var client = await listener.AcceptTcpClientAsync();
// Read response.
var response = ReadString(client);
// Brings this app back to the foreground.
this.Activate();
// Sends an HTTP response to the browser.
WriteStringAsync(client, "<html><head><meta http-equiv='refresh' content='10;url=https://google.com'></head><body>Please close this window and return to the app.</body></html>").ContinueWith(t =>
{
client.Dispose();
listener.Stop();
Console.WriteLine("HTTP server stopped.");
});
// TODO: Check the response here to get the authorization code and verify the code challenge
The read and write methods being:
private string ReadString(TcpClient client)
{
var readBuffer = new byte[client.ReceiveBufferSize];
string fullServerReply = null;
using (var inStream = new MemoryStream())
{
var stream = client.GetStream();
while (stream.DataAvailable)
{
var numberOfBytesRead = stream.Read(readBuffer, 0, readBuffer.Length);
if (numberOfBytesRead <= 0)
break;
inStream.Write(readBuffer, 0, numberOfBytesRead);
}
fullServerReply = Encoding.UTF8.GetString(inStream.ToArray());
}
return fullServerReply;
}
private Task WriteStringAsync(TcpClient client, string str)
{
return Task.Run(() =>
{
using (var writer = new StreamWriter(client.GetStream(), Encoding.UTF8))
{
writer.Write("HTTP/1.0 200 OK");
writer.Write(Environment.NewLine);
writer.Write("Content-Type: text/html; charset=UTF-8");
writer.Write(Environment.NewLine);
writer.Write("Content-Length: " + str.Length);
writer.Write(Environment.NewLine);
writer.Write(Environment.NewLine);
writer.Write(str);
}
});
}
By default there is a URL pattern http://+:80/Temporary_Listen_Addresses/ which is allowed for all users (\Everyone)
You can use this as a prefix for your listener. More generally (to avoid collisions with other listeners) you should generate a URL under Temporary_Listen_Addresses (e.g. using a GUID) and use that as your listener prefix.
Unfortunately, a sysadmin can use netsh http to delete this entry or to restrict its usage to only certain users. Also, this does not appear to support listening for an HTTPS request as there is no corresponding ACL entry for port 443.
An admin can list all these permitted URL patterns using netsh http show urlacl as a command.

The request was aborted: Could not create SSL/TLS secure channel. (RestSharp, SSL Client Certificates)

I have following code which is calling an API using basic authentication and SSL client certificate but its throwing exception and giving me following error.
"The request was aborted: Could not create SSL/TLS secure channel."
I tried to find a solution on Google but failed to find any solution. Can anyone help me out on this. Thanks.
// Variables
string basicAuthenticationUserName = "username";
string basicAuthenticationPassword = "password";
string clientCertificateFilePath = "Path-To-Certificate-File";
string clientCertificatePassword = "certificate-password";
string url = "https://" + basicAuthenticationUserName + ":" + basicAuthenticationPassword + "#apiserverurl/apimethod";
// Creating RestSharp Request Object
var request = new RestRequest(Method.POST)
{
RequestFormat = DataFormat.Json,
OnBeforeDeserialization = resp =>
{
resp.ContentType = "application/json";
}
};
// Adding Headers
request.AddHeader("Content-Length", "0");
request.AddHeader("Accept", "application/x-null-message");
// Importing Certificates
var certificates = new X509Certificate();
certificates.Import(clientCertificateFilePath, clientCertificatePassword, X509KeyStorageFlags.PersistKeySet);
// Creating RestSharp Client Object
var client = new RestClient
{
BaseUrl = new Uri(url),
ClientCertificates = new X509CertificateCollection { certificates },
Authenticator = new HttpBasicAuthenticator(managingLou, basicAuthenticationPassword)
};
// Executing Request
var response = client.Execute<T>(request);
I have faced the similar issue. Let me mention the steps here for your help.
After the installation of windows service, I went through the following steps to fix the issue:
Go To Start > Run and type Services.msc
Select your service > Right click and choose Properties
Select the 2nd tab "Log On"
Select the radio button "This account"
Enter the username and password of currently log in user. (Make sure Its the same user who has installed the service)
Apply the changes
Start the service

System.ServiceModel.AddressAlreadyInUseException: HTTP could not register URL

I am having a WCF issue when trying to add service references in an application I am making. The URL that is being used is
http://+:8000/HelpDeskDataServices/CallDataService/because it conflicts with an existing registration on the machine.
I have checked netsh and there is nothing on 8000, and I have been trying to add that URL to netsh but do not think I am getting the syntax right (trying it this way):
netsh http add urlacl url=http://+:8000/HelpDeskDataServices
I have three service references that are all going to be using the 8000 port if I can get it to work. Any suggestions?
here is a piece of code from my project:
// http://antscode.blogspot.com/2011/02/running-clickonce-application-as.html
// http://msdn.microsoft.com/en-us/library/ms733768.aspx
// http://stackoverflow.com/questions/2521950/wcf-selfhosted-service-installer-class-and-netsh
var everyone = new SecurityIdentifier("S-1-1-0").Translate(typeof(NTAccount)).ToString();
var args = string.Format("http add urlacl url=http://+:{0}/ user=\\{1}", port, everyone);
var pi = new ProcessStartInfo("netsh", args);
pi.UseShellExecute = true;
pi.CreateNoWindow = true;
pi.WindowStyle = ProcessWindowStyle.Hidden;
pi.Verb = "runas";
var p = Process.Start(pi);
basically I register it for everyone - may be this will fix your issue?
or trailing slash? but most likely you already have something using this port. Try another port - and if there is no error that would mean you did not look for existing registration good enough...

Subsquent HttpWebRequest with NetworkCredential set honors recently changed domain password

We gather a windows domain username and password and persist that in a cookie through a web front end. Those credentials along with a request for specific data are on an ongoing basis ultimately passed through a WCF call hosted in windows service that itself redirects that as a new REST call to another server setup under IIS with windows auth configured. This is done using HttpWebRequest...
var url = baseServerUrl + "/" + apiCall.Url;
var request = (HttpWebRequest)WebRequest.Create(url);
request.Timeout = 1000000;
request.Method = apiCall.HttpMethod;
bool credentialsSet = false;
if (!string.IsNullOrEmpty(apiCall.Identity))
{
// split into Domain\User\Password
var parts = apiCall.Identity.Split('\\');
if (parts.Length == 3)
{
request.Credentials = new NetworkCredential(parts[1], parts[2], parts[0]);
credentialsSet = true;
}
}
// Then more code submitting the request ...
So if I store in the cookie domain password A, make a request through the above (which all works fine), but then change my windows domain password to B what we are finding for subsequent requests is the above code (again running as a windows service) will happily authenticate just fine when receiving and setting A on the NetworkCredential (receiving A because the cookie is still hanging around with the old password). If we restart the service immediately any further attempts to use the HttpWebRequest and NetworkCredential with password A generate a 401. Why are the old domain passwords honored until the restart? How do you stop that behavior?

accesing a https site using firefox addon

I am using a self signed ssl certificate to set up a https site and using the request package to access the contents on this site. However the program seems to get stuck and it is not printing the contents of the site. Is there any way to overcome this issue.
Warning: This should only be used for debugging. Automatically adding an override for a wrong SSL certificate compromises the entire connection - if you do that then you can just skip using SSL in the first place. When you release this extension for other people you should use a valid certificate.
You probably want to add a certificate override manually. That's something you would use nsICertOverrideService.rememberValidityOverride() for (chrome authority required). The only problem is getting the certificate that you want to add an override for. But trying to contact the server and calling nsIRecentBadCertsService.getRecentBadCert() then should do. Something like this:
var Request = require("request").Request;
var host = "example.com";
var port = "443";
Request({
url: "https://" + host + ":" + port + "/foo",
onComplete: function(response)
{
var status = null;
try
{
status = response.status;
} catch(e) {}
if (!status)
{
// There was a connection error, probably a bad certificate
var {Cc, Ci} = require("chrome");
var badCerts = Cc["#mozilla.org/security/recentbadcerts;1"]
.getService(Ci.nsIRecentBadCertsService);
var status = badCerts.getRecentBadCert(host + ":" + port);
if (status)
{
var overrideService = Cc["#mozilla.org/security/certoverride;1"]
.getService(Ci.nsICertOverrideService);
overrideService.rememberValidityOverride(host, port, status.serverCert,
Ci.nsICertOverrideService.ERROR_UNTRUSTED, false);
// Override added, now you should re-do the request
...
}
}
}
});
Note: This code hasn't been tested, in particular I'm not sure whether detecting connection errors will really work by checking response.status (my guess is that it should throw if there was a connection error but the documentation doesn't say anything).