Use a Client Certificate from IIS - iis-7

I have an IIS website that needs to retrieve data from a 3rd party web service. The website is an ISAPI dll writren in Delphi XE 10.2.
The web service has created a self signed client certificate for us to use. I have imported the certificate into the Local Machine store. The Root certificate is in the Trusted Root CA.
The web service returns 403 Access Denied.
I have create a stand alone client outside of IIS. The client works as expected.
I have added code to explicitly add the certificate from the file system. Again the test code works but the ISAPI dll fails to connect.
Here is the code I use to load the certificate from the file system.
Data: Pointer);
var
HTTPRStore: IClientCertInfo;
hStore: HCERTSTORE;
CertContext: PCCERT_CONTEXT;
begin
HTTPReqResp.InvokeOptions:=[soPickFirstClientCertificate];
HTTPRStore:=HTTPReqResp as IClientCertInfo;
try
hStore := GetCertStore('C:\Certs\ClientDev.pfx', 'xxxx');
if Assigned(hStore) then
begin
CertContext :=
CertFindCertificateInStore(
hStore,
X509_ASN_ENCODING or PKCS_7_ASN_ENCODING,
0,
CERT_FIND_SUBJECT_STR,
PChar('ClientDev'),
nil
);
HTTPRStore.SetCertStore(hStore);
HTTPRStore.SetCertName(GetCertInfo(CertContext));
HTTPRStore.SetCertIssuer(GetCertInfo(CertContext, CERT_NAME_ISSUER_FLAG));
HTTPRStore.SetCertSerialNumber(GetCertSerialNumber(#CertContext.pCertInfo.SerialNumber));
TFile.AppendAllText('C:\inetpub\wwwroot\logs\test.log', HTTPRStore.GetCertName + #13#10);
CertFreeCertificateContext(CertContext);
end
else
raise Exception.Create('Cert store not open');
finally
HTTPRStore := nil;
end;
end;
Does anyone know why IIS does not send the client certificate?

Related

How to setup https when developing localy with webpack and hosting on Azure in Docker container running ASP.NET Core

I am hosting on Azure and have it configured to only allow https. The backend is running ASP .NET Core in a Linux container. The webserver (Kestrel) is running without https enabled. I've configured Azure TLS/SSL settings to force https, so when users connect from the public internet, they have to use https. I have a cert that is signed by a cert authority and it's configured in the Azure App Service -> TLS/SSL -> Bindings settings.
However in my local development environment I've been running webpack using http. So when I test I connect to localhost:8080 and this is redirected to localhost:8085 by webpack. localhost:8085 is the port Kestrel is listening on. I've decided I want to develop locally using https so that my environment mimics the production environment closely. To this I've started the webpack-dev-server with the --https command line option, and ammended my redirects in my webpack.config.js
For example:
'/api/*': {
target: 'https://localhost:' + (process.env.SERVER_PROXY_PORT || "8085"),
changeOrigin: true,
secure: false
},
This redirects https requests to port 8085.
I've created a self-signed cert for use by Kestrel when developing locally. I modified my code to use this certificate as shown below:
let configure_host (settings_file : string) (builder : IWebHostBuilder) =
//turns out if you pass an anonymous function to a function that expects an Action<...> or
//Func<...> the type inference will work out the inner types....so you don't need to specify them.
builder.ConfigureAppConfiguration((fun ctx config_builder ->
config_builder
.SetBasePath(Directory.GetCurrentDirectory())
.AddEnvironmentVariables()
.AddJsonFile(settings_file, false, true)
.Build() |> ignore))
.ConfigureKestrel(fun ctx opt ->
eprintfn "JWTIssuer = %A" ctx.Configuration.["JWTIssuer"]
eprintfn "CertificateFilename = %A" ctx.Configuration.["CertificateFilename"]
let certificate_file = (ctx.Configuration.["CertificateFilename"])
let certificate_password = (ctx.Configuration.["CertificatePassword"])
let certificate = new X509Certificate2(certificate_file, certificate_password)
opt.AddServerHeader <- false
opt.Listen(IPAddress.Loopback, 8085, (fun opt -> opt.UseHttps(certificate) |> ignore)))
.UseUrls("https://localhost:8085") |> ignore
builder
This all works, and I can connect to webpack locally and it redirects the request to the webserver using https. The browser complains that the cert is insecure because it's self-signed but that was expected.
My question is how should this be setup in the production environment. I don't want to be running the container on azure with the certificates I created locally embeded in the image. In my production environment should I be configuring Kestrel, as I have done with the localhost code, to use the cert in loaded into Azure (as mentioned in the 1st paragraph)? Or is simply binding it to the domain using the portal and forcing https via the Web UI enough?
On Azure, If you have the PFX certificate you can choose to upload the certificate:
see this image
However, this certificate needs to come from a trusted certificate authority.
If the URL is a subdomain, you can choose a Free App Service Managed Certificate.
After, that all you need to do is enable https only in the portal.
If its a naked domain and you really need the certificate to be free, you can get a certificate from sslforfree.com. sslforfree will give you the .cer file and the private key you will need to generate a pfx.

AspNet.Security.OpenIdConnect.Server (ASP.NET vNext) Authority Configuration in Mixed http/https Environments

I am using Visual Studio 2015 Enterprise and ASP.NET vNext Beta8 to build an endpoint that both issues and consumes JWT tokens as described in detail here. As explained in that article the endpoint uses AspNet.Security.OpenIdConnect.Server (AKA OIDC) to do the heavy lifting.
While standing this prototype up in our internal development environment we have encountered a problem using it with a load balancer. In particular, we think it has to do with the "Authority" setting on app.UseJwtBearerAuthentication and our peculiar mix of http/https. With our load balanced environment, any attempt to call a REST method using the token yields this exception:
WebException: The remote name could not be resolved: 'devapi.contoso.com.well-known'
HttpRequestException: An error occurred while sending the request.
IOException: IDX10804: Unable to retrieve document from: 'https://devapi.contoso.com.well-known/openid-configuration'.
Consider the following steps to reproduce (this is for prototyping and should not be considered production worthy):
We created a beta8 prototype using OIDC as described here.
We deployed the project to 2 identically configured IIS 8.5 servers running on Server 2012 R2. The IIS servers host a beta8 site called "API" with bindings to port 80 and 443 for the host name "devapi.contoso.com" (sanitized for purposes of this post) on all available IP addresses.
Both IIS servers have a host entry that point to themselves:
127.0.0.1 devapi.contoso.com
Our network admin has bound a * certificate (*.contoso.com) with our Kemp load balancer and configured the DNS entry for https://devapi.contoso.com to resolve to the load balancer.
Now this is important, the load balancer has also been configured to proxy https traffic to the IIS servers using http (not, repeat, not on https). It has been explained to me that this is standard operating procedure for our company because they only have to install the certificate in one place. We're not sure why our network admin bound 443 in IIS since it, in theory, never receives any traffic on this port.
We make a secure post via https to https://devapi.contoso.com/authorize/v1 to fetch a token, which works fine (the details of how to make this post are here ):
{
"sub": "todo",
"iss": "https://devapi.contoso.com/",
"aud": "https://devapi.contoso.com/",
"exp": 1446158373,
"nbf": 1446154773
}
We then use this token in another secure get via https to https://devapi.contoso.com/values/v1/5.
OpenIdConnect.OpenIdConnectConfigurationRetriever throws the exception:
WebException: The remote name could not be resolved: 'devapi.contoso.com.well-known'
HttpRequestException: An error occurred while sending the request.
IOException: IDX10804: Unable to retrieve document from: 'https://devapi.contoso.com.well-known/openid-configuration'.
We think this is happening because OIDC is attempting to consult the host specified in "options.Authority", which we set at startup time to https://devapi.contoso.com/. Further we speculate that because our environment has been configured to translate https traffic to non https traffic between the load balancer and IIS something is going wrong when the framework tries to resolve https://devapi.contoso.com/. We have tried many configuration changes including even pointing the authority to non-secure http://devapi.contoso.com to no avail.
Any assistance in helping us understand this problem would be greatly appreciated.
#Pinpoint was right. This exception was caused by the OIDC configuration code path that allows IdentityModel to initiate non-HTTPS calls. In particular the code sample we were using was sensitive to missing trailing slash in the authority URI. Here is a code fragment that uses the Uri class to combine paths in a reliable way, regardless of whether the Authority URI has a trailing slash:
public void Configure(IApplicationBuilder app, IOptions<AppSettings> appSettings)
{
.
.
.
// Add a new middleware validating access tokens issued by the OIDC server.
app.UseJwtBearerAuthentication
(
options =>
{
options.AuthenticationScheme = JwtBearerDefaults.AuthenticationScheme ;
options.AutomaticAuthentication = false ;
options.Authority = new Uri(appSettings.Value.AuthAuthority).ToString() ;
options.Audience = new Uri(appSettings.Value.AuthAuthority).ToString() ;
// Allow IdentityModel to use HTTP
options.ConfigurationManager =
new ConfigurationManager<OpenIdConnectConfiguration>
(
metadataAddress : new Uri(new Uri(options.Authority), ".well-known/openid-configuration").ToString(),
configRetriever : new OpenIdConnectConfigurationRetriever() ,
docRetriever : new HttpDocumentRetriever { RequireHttps = false }
);
}
);
.
.
.
}
In this example we're pulling in the Authority URI from config.json via "appSettings.Value.AuthAuthority" and then sanitizing/combining it using the Uri class.

The remote server returned an error: (401) Unauthorized. Using CSOM in ASP.NET

I'm tried to pull some SharePoint 2013 list data I created which works fine when running locally on my machine and when run locally one the server. I'm user the same credentials when running both locally and locally on the server. The issue is when I publish and navigate to my ASP.NET app on the server I get the "The remote server returned an error: (401) Unauthorized." Error...
I've looked at a bunch of the posts on stackoverflow and some other articles on the web
This points out that the context seems to be using IUSR:
http://blogs.msdn.com/b/sridhara/archive/2014/02/06/sharepoint-2013-csom-call-from-web-part-fails-with-401-for-all-users.aspx
This one mentions to try setting the default network credentials:
https://sharepoint.stackexchange.com/questions/10364/http-401-unauthorized-using-the-managed-client-object-model
I've tried using the fixes mentioned in the article as well as trying to force the context to use DefaultNetworkCredentials but no luck. I would like for the app to use the credentials of the logged in user and not the machine...
Here is the code I'm using:
SP.ClientContext context = new SP.ClientContext("MySPDevInstance");
context.Credentials = CredentialCache.DefaultNetworkCredentials;
Entity entity = context.Web.GetEntity(collectionNamespace, collectionName);
LobSystem lobSystem = entity.GetLobSystem();
LobSystemInstanceCollection lobSystemInstanceCollection = lobSystem.GetLobSystemInstances();
context.Load(lobSystemInstanceCollection);
context.ExecuteQuery();
LobSystemInstance lobSystemInstance = lobSystemInstanceCollection[0];
FilterCollection filterCollection = entity.GetFilters(filter);
filterCollection.SetFilterValue("LimitFilter", 0, 1000);
EntityInstanceCollection items = entity.FindFiltered(filterCollection, filter, lobSystemInstance);
The server is running IIS 6.0
Any advice would be much appreciated!
Thank you
I presume your ASP.NET web site is using Windows Integrated (NTLM) authentication. A user authenticated this way cannot authenticate to a second location from the server side (the web server.) You are experiencing what is known as the "double-hop" (1) limitation of NTLM. You must use a dedicated account on the server side, or if you really do want to use the logged-in user's identity, you must use an authentication scheme that permits delegation, such as Kerberos.
If you really need the user's identity to access SharePoint data and you cannot change the authentication scheme, then the best way to do this is to use the JavaScript CSOM. This means the user is authenticating directly to the SharePoint server (a single hop, not double) and your ASP.NET site serves the page containing this script to the user.
(1) http://blogs.msdn.com/b/knowledgecast/archive/2007/01/31/the-double-hop-problem.aspx
Use Default Credentials worked for me:
HttpWebRequest httpWebRequest = (HttpWebRequest)WebRequest.Create(url);
httpWebRequest.UseDefaultCredentials = true;
Setup the crendentials by code:
SP.ClientContext context = new SP.ClientContext("MySPDevInstance");
context.Credentials = new NetworkCredential("username", "password");
You should put this at the configuration file to change it without publishing or recompiling the application.
Just to add one more setting that I encountered. If the account is restricted to access only certain servers than add the client machine to that account as well. For example if a web application is hosted on Server A and trying to connect to SharePoint 2010 on Server B with account ABC then make sure that account has access to Server A in Active Directory. Normally the AD account doesn't have restrictions to connect to machines but in my case the account was restricted to only certain machines. I added my web application hosted server to the account and it worked.

Can an X509Certificate be fetched from the certificates store by ASP.NET without file-system permissions?

Where should a certificate that appears in the Trusted Root Certification Authorities node in certmgr.msc be kept so that an IIS web app can obtain it and sign a SAML Assertion with it? Is there a way to extract the certificate from the certificates "hive" directly, that does not require file-system permissions? Or is it necessary to export the certificate to a folder to which the IIS7 built-in user has access permissions?
The X509Certificate2.Import() method's first parameter is fileName.
If I export the Certificate and put the file in my Visual Studio 2012 Project folders hierarchy and provide a fully qualified path to the Import() method, the cert import succeeds, but only if the application is running in Visual Studio's built-in server, not if it's running in the Local IIS Web Server.
I've tried using the Friendly Name with X509KeyStorageFlags.MachineKeySet but that did not work.
EDIT: This works when using the built-in Visual Studio server but not the LOCAL IIS7 Server in Windows 7:
certStore = New X509Store(StoreLocation.CurrentUser)
certStore.Open(OpenFlags.ReadOnly)
Dim thumbprint As String
thumbprint = ConfigurationManager.AppSettings("thumb").ToString
certCollection = certStore.Certificates.Find(X509FindType.FindByThumbprint, thumbprint, False)
certStore.Close()
cert = certCollection(0)
so I need to find out how to give the Default App Pool access to this certificate in Windows 7.
You don't "import", just create an instance. Formally, you open a key store and loop through certificates. And yes, you don't need any filesystem permission, however, to access the private key, your application pool identity has to have permission to the key, you set the permission in the certificate snapin of the mmc console.
Edit: the code to access the certificate would be something like:
var store = new X509Store( name, location );
store.Open( OpenFlags.ReadOnly );
foreach ( var cert in store.Certificates )
... loop and match, by thumbprint, friendly name or whatever else

Certificate validation failed

Actors
-Asp.net site - Client
-Wcf services - Server
Both applications runs on IIS-7.
I want to make integration test between the two applications. The client access the Server through 'https'.
I have created a certificate and assigned it to the server. I also added the certificate to the 'Trusted Root Certification Authorities' to be considered a valid certificate. When I 'hit' the server's services through my browser (IE, chrome...) the certificate appears to be valid. But when my client application tries to access the server then I get the following error:
Could not establish trust relationship for the SSL/TLS secure channel with authority **** --->
The remote certificate is invalid according to the validation procedure.
Is there any way to skip the validation procedure or to make the certificate valid for my client application?
Just to know:
1. I cannot purchase a certificate because I will only use it for testing purposes.
2. I cannot make any changes on any of the application's code (server-client)
I finally managed to figured it out.
The problem was a previous (expired) certificate with the same name that was already added to the 'Trusted Root Certification Authorities'. Every time I was installing my new certificate through the 'Certificate Import Wizard' (or through MMC) the wizard informed me that it was successfully added. However, it was keeping the instance of the previous certificate without overwriting it.
Modify the validation callback to always return true:
ServicePointManager.ServerCertificateValidationCallback += (sender, certificate, chain, policyErrors) => true;
Or does that violate the 'no changes to code' condition?
How did you install the certificate into your trusted root store?
If you went through a browser to do it, most likely you only added it to the current user. Try adding it through the MMC snap-in for the Local Computer Account instead; this is where we install our self-signed IIS Express certificates and WCF seems happy with them.

Resources