I have problems with configuring my asp.net web api service to authenticate requests by client certificates
I do the steps describing in Pro ASP.NET Web Api Security:
I create certificates using makecert.exe
makecert.exe -r -n "CN=MobileTradeDataGateway" -pe -sv MobileTradeDataGateway.pvk -a sha256 -cy authority MobileTradeDataGateway.cer and makecert.exe -iv MobileTradeDataGateway.pvk -ic MobileTradeDataGateway.cer -n "CN=DataGateway1" -pe -sv DataGateway1.pvk -a sha256 -sky exchange DataGateway1.cer -eku 1.3.6.1.5.5.7.3.2
I install MobileTradeDataGateway certificate in server Trusted Root Certification Authorities and in client too. Install DataGateway1 in client personal authority.
Configure site to accept certificates and enable. Enable anonymous authentication.
Create DelegatingHandler and add it to messagehandlers collection in mvc to check certificates.
Call web api method
var certStore = new X509Store(StoreLocation.CurrentUser);
certStore.Open(OpenFlags.ReadOnly);
var collection = certStore.Certificates.Find(X509FindType.FindByIssuerName, "MobileTradeDataGateway", true);
var cert = collection[0];
certStore.Close();
var messageHandler = new WebRequestHandler();
messageHandler.ClientCertificates.Add(cert);
var client = new HttpClient(messageHandler) { BaseAddress = new Uri("...") };
var res = client.GetAsync("/api/orderuploader?number=5").Result;
.
Everything works fine in my local machine and network where my machine is server.
But when I deploy it to azure cloud service I get null
var cert = request.GetClientCertificate(); // here is null
in my custom delegating handler
Off course I enable IIS to accept certificates and correctelly put certificates in Trusted Root Certification Authorities
Any ideas?
Did you also tried getting your certificate by "Thumbprint".
here is a sample code that tries to read a certificate from certificate store.
private X509Certificate2 FindCertificate()
{
X509Store certificateStore = new X509Store(StoreName.My, StoreLocation.CurrentUser);
certificateStore.Open(OpenFlags.ReadOnly);
X509Certificate2Collection certificates = certificateStore.Certificates;
X509Certificate2Collection matchingCertificates = certificates.Find(X509FindType.FindByThumbprint, "CertThumbprint", false);
if (matchingCertificates != null && matchingCertificates.Count > 0)
{
return matchingCertificates[0];
}
throw new ArgumentException("Unable to find a matching certificate in the certificate store. Please modify the search criteria.");
}
this link has more information on how you can read certificate from a web /worker role
Here is the code of my delegatinghandler from web api
public class X509ClientCertificateHandler : DelegatingHandler
{
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
if (request.RequestUri.Scheme != Uri.UriSchemeHttps)
{
WebApiEventSource.Log.InvalidHttpsScheme();
return request.CreateResponse(HttpStatusCode.Forbidden);
}
var cert = request.GetClientCertificate(); // here is null!!!
if (cert == null)
{
WebApiEventSource.Log.FailureAuthenticate("certificate is abcent", "", "");
return request.CreateResponse(HttpStatusCode.Unauthorized);
}
var chain =new X509Chain {ChainPolicy = {RevocationMode = X509RevocationMode.NoCheck}};
if (chain.Build(cert) && cert.Issuer.Equals("CN=MobileTradeDataGateway"))
{
var claims = new List<Claim>
{
new Claim(ClaimTypes.Name, cert.Subject.Substring(3))
};
var principal = new ClaimsPrincipal(new[] {new ClaimsIdentity(claims, "X509")});
Thread.CurrentPrincipal = principal;
if (HttpContext.Current != null)
HttpContext.Current.User = principal;
WebApiEventSource.Log.SuccessAuthenticate(cert.SubjectName.Name);
return await base.SendAsync(request, cancellationToken);
}
WebApiEventSource.Log.FailureAuthenticate("certificate is incorrect", cert.IssuerName.Name, cert.SubjectName.Name);
return request.CreateResponse(HttpStatusCode.Unauthorized);
}
}
I think you have missed to upload the certificate to Azure Portal. Please make sure you upload .cer or .pfx certificate to Azure Portal. Let me know if you need help on how to upload etc.
Related
my authorization server and resource servers are different , i have third party token issuer is below.
https://XXXXXXXXXXX/v2/token
i am able to get access token from authorization server, but i am unable to get success to call my resource server API.
my resource server configuration
public void Configuration(IAppBuilder app)
{
// For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=316888
var issuer = "https://XXXXXXXXXX/v2/token";
var audience = "05XXXXXXX29";
var secret = TextEncodings.Base64Url.Decode("43558e250ab87XXXXXXXXXXXXXf57549c58fca1");
app.UseJwtBearerAuthentication(
new JwtBearerAuthenticationOptions
{
AuthenticationMode = AuthenticationMode.Active,
AllowedAudiences = new[] { audience },
IssuerSecurityTokenProviders = new IIssuerSecurityTokenProvider[]
{
new SymmetricKeyIssuerSecurityTokenProvider(issuer, secret)
}
});
}
The access_token is a JWT token, which is self-contained and verifiable. You resource server should just verify it. There are many 3rd party JWT library for JWT verification, see https://openid.net/developers/jwt/
The following code below is used to authenticate users in ADFS 2016 and to request an Access Token for the resource defined in cp.APIBaseURL:
public partial class Startup
{
public void ConfigureAuth(IAppBuilder app)
{
var cp = UnityConfig.Container.Resolve<IConfigurationProvider>();
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
ClientId = cp.ClientId,
MetadataAddress = cp.MetadataAddress,
RedirectUri = cp.RedirectUri,
PostLogoutRedirectUri = cp.PostLogoutRedirectUri,
ResponseType = "code id_token",
Scope = "openid",
Notifications = new OpenIdConnectAuthenticationNotifications()
{
AuthorizationCodeReceived = OnAuthorizationCodeReceived
}
});
private async Task OnAuthorizationCodeReceived(AuthorizationCodeReceivedNotification context)
{
var cp = UnityConfig.Container.Resolve<IConfigurationProvider>();
AuthenticationContext ac = new AuthenticationContext(
configurationProvider.Authority, false,
new InMemoryTokenCache(context.AuthenticationTicket.Identity.Name));
AuthenticationResult ar = await ac.AcquireTokenByAuthorizationCodeAsync(
context.Code, new Uri(cp.RedirectUri),
new ClientCredential(cp.ClientId, cp.ClientSecretKey),
cp.APIBaseURL);
}
I would like to know how to change the code to request a 2nd Access Token for a different API (having a different audience)?
Can I also specify different scopes for the 2nd Access Token I need?
You can use result = await ac.AcquireTokenSilentAsync(resource, clientId); to request the access token for different resouces. Refer here for more details.
Can I also specify different scopes for the 2nd Access Token I need?
No,for v1(adal) Azure AD apps, scopes must be statically configured in the Azure Portal under the API permissions, configured permissions.
I have installed ssl certificate and but when I use certCollection.Find by Extension but it's returning null.
private X509Certificate2 GetCertificateFromStore()
{
X509Certificate2 x509Certificate2;
var aspNetCoreEnvironment = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT");
if (string.Equals(aspNetCoreEnvironment, "Development", StringComparison.OrdinalIgnoreCase))
{
const string aspNetHttpsOid = "1.3.1.1.3.1.1.84.1.1";
const string cnName = "CN=localhost";
using (var store = new X509Store(StoreName.My, StoreLocation.LocalMachine))
{
store.Open(OpenFlags.ReadOnly);
var certCollection = store.Certificates;
var currentCerts = certCollection.Find(X509FindType.FindByExtension, aspNetHttpsOid, true);
currentCerts = currentCerts.Find(X509FindType.FindByIssuerDistinguishedName, cnName, true);
x509Certificate2 = currentCerts.Count == 0 ? null : currentCerts[0];
}
}
}
Note: I have created a self-signed development certificate with below commands.
PS C:\program files\microsoft sdks\service fabric\clustersetup\secure> .\CertSetup.ps1 -Install -CertSubjectName CN=mytestcert
after installing i can see certificate is created with added into Trusted root.
Am I making some mistake in creating self signed certificate. I am not sure.
You have stored the certificate in StoreLocation.CurrentUser but you code is looking in StoreLocation.LocalMachine.
Also, don't forget to the add proper ACL's.
Exported Qlik Sense certificate using QMC (client.pfx, root.cer, server.pfx).
Imported certificates into IIS web server using MMC. Server and client certificates to store Personal->Certificates, root to store Trusted Root Certification Authorities.
Requested QRS API from ASP.NET controller using QlikClient certificate from store (code below). Tried various user IDs and directories, including INTERNAL/sa_repository, but in all cases got an error "An error occurred while sending the request. The client certificate credentials were not recognized".
Endpoint for test : https://server:4242/qrs/about
I've searched the web but I haven't managed to find what I'm doing wrong, what credentials I should provide.
On the other hand, as I converted exported certificates to separate .key/.crt files (using https://www.markbrilman.nl/2011/08/howto-convert-a-pfx-to-a-seperate-key-crt-file/) and used them in the Postman from web server, it worked without any problem, actually with any UserId in header (i guess it's ignored in that case).
ASP.NET controller:
public X509Certificate2 LoadQlikCertificate()
{
X509Certificate2 certificate = null;
try
{
// Open certification store (MMC)
X509Store store = new X509Store(StoreName.My, StoreLocation.LocalMachine);
store.Open(OpenFlags.ReadOnly);
// Get certiface based on the friendly name
certificate = store.Certificates.Cast<X509Certificate2>().FirstOrDefault(c => c.FriendlyName == "QlikClient");
// Logging for debugging purposes
if (certificate != null)
{
logger.Log(LogLevel.Warning, $"Certificate: {certificate.FriendlyName} {certificate.GetSerialNumberString()}");
}
else
{
logger.Log(LogLevel.Warning, $"Certificate: No certificate");
}
// Close certification store
store.Close();
// Return certificate
return certificate;
}
catch (Exception e)
{
...
}
}
/* Get Qlik API response
***********************/
[HttpGet("getqlikapi")]
public IActionResult GetQlikAPI()
{
// Get Qlik certificate
var certificate = this.LoadQlikCertificate();
try
{
ServicePointManager.ServerCertificateValidationCallback = delegate { return true; };
// Set server name
string server = "server";
// HARDCODED USER AND DIRECTORY FOR TESTING
string userID = "sa_repository"; // tried also other user ids
string userDirectory = "INTERNAL";
// Set Xrfkey header to prevent cross-site request forgery
string xrfkey = "abcdefg123456789";
// Create URL to REST endpoint
string url = $"https://{server}:4242/qrs/about?xrfkey={xrfkey}";
// The JSON object containing the UserId and UserDirectory
string body = $"{{ 'UserId': '{userID}', 'UserDirectory': '{userDirectory}', 'Attributes': [] }}";
// Encode the json object and get the bytes
byte[] bodyBytes = Encoding.UTF8.GetBytes(body);
// Create the HTTP Request
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
// Add the method to authentication the user
request.ClientCertificates.Add(certificate);
// POST request will be used
request.Method = "POST";
// The request will accept responses in JSON format
request.Accept = "application/json";
// A header is added to validate that this request contains a valid cross-site scripting key (the same key as the one used in the url)
request.Headers.Add("X-Qlik-Xrfkey", xrfkey);
request.ContentType = "application/json";
request.ContentLength = bodyBytes.Length;
Stream requestStream = request.GetRequestStream();
requestStream.Write(bodyBytes, 0, bodyBytes.Length);
requestStream.Close();
// Make the web request and get response
HttpWebResponse response = (HttpWebResponse)request.GetResponse();
Stream stream = response.GetResponseStream();
// Return string in response
//return new OkObjectResult(stream != null ? new StreamReader(stream).ReadToEnd() : string.Empty);
return new OkObjectResult("test");
}
catch (Exception e)
{
...
}
}
I ran into this issue on a system we are building.
The problem was that the user did not have rights to the certificate.
Open certificate manager (Start > Manage Computer Certificates)
Find the required certificate.
Right-click cert > All Tasks > Manage Private Keys > Add > [Select the appropriate user]
Note: Manage User Certificates does not have the Manage Private Keys option.
I am building a Web Api (using ASP.NET Web API), that connects via Secure WebSockets to an endpoint that our client exposed (wss://client-domain:4747/app/engineData). They gave me their certificates all in .pem format (root.pem and client.pem), and a private key (client_key.pem).
In order to get this done I did the following:
1) Converted client.pem and client_key.pem to a single .pfx file (used this here: Convert a CERT/PEM certificate to a PFX certificate)
2) I used the library System.Net.WebSockets, and wrote the following code:
private void InitWebSockesClient()
{
client = new ClientWebSocket();
client.Options.SetRequestHeader(HEADER_KEY, HEADER_VALUE); //Some headers I need
AddCertificatesSecurity();
}
private void AddCertificatesSecurity()
{
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls
| SecurityProtocolType.Tls11
| SecurityProtocolType.Tls12;
// I KNOW THIS SHOULDNT BE USED ON PROD, had to use it to make it
// work locally.
ServicePointManager.ServerCertificateValidationCallback = delegate { return true; };
X509Certificate2 x509 = new X509Certificate2();
// this is the pfx I converted from client.pem and client_key
byte[] rawData = ReadFile(certificatesPath + #"\cert.pfx");
x509.Import(rawData, "123456", X509KeyStorageFlags.UserKeySet);
X509Certificate2Collection certificateCollection = new X509Certificate2Collection(x509);
client.Options.ClientCertificates = certificateCollection;
}
And when I want to connect I call:
public async Task<bool> Connect()
{
Uri uriToConnect = new Uri(URL);
await client.ConnectAsync(uriToConnect, CancellationToken.None);
return client.State == WebSocketState.Open;
}
This works fine locally. But whenever I deploy my Web Api on Azure (App Service) and make an HTTP request to it, it throws:
System.Net.WebSockets.WebSocketException - Unable to connect to the remote server.
And the inner exception:
System.Net.WebException - The request was aborted: Could not create SSL/TLS secure channel.
I enabled WebSockets on the AppService instance.
If I delete the line that always return true for the certificate validation, it doesn't work even locally, and the message says something like:
The remote certificate is invalid according to the validation procedure.
So definitely I got something wrong with the certificates, those three .pem files are being used right now in a similar [![enter image description here][1]][1]app in a node.js and work fine, the WSS connection is established properly. I don't really know what usage give to each one, so I am kind of lost here.
These are the cipher suites of the domain I want to connect: https://i.stack.imgur.com/ZFbo3.png
Inspired by Tom's comment, I finally made it work by just adding the certificate to the Web App in Azure App Service, instead of trying to use it from the filesystem. First I uploaded the .pfx file in the SSL Certificates section in Azure. Then, in the App settings, I added a setting called WEBSITE_LOAD_CERTIFICATES, with the thumbprint of the certificate I wanted (the .pfx).
After that, I modified my code to do work like this:
private void InitWebSockesClient()
{
client = new ClientWebSocket();
client.Options.SetRequestHeader(HEADER_KEY, HEADER_VALUE); //Some headers I need
AddCertificateToWebSocketsClient();
}
private void AddCertificateToWebSocketsClient()
{
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls11
| SecurityProtocolType.Tls12;
// this should really validate the cert
ServicePointManager.ServerCertificateValidationCallback = delegate { return true; };
// reading cert from store
X509Store certStore = new X509Store(StoreName.My, StoreLocation.CurrentUser);
certStore.Open(OpenFlags.ReadOnly);
X509Certificate2Collection certCollection =
certStore.Certificates.Find(X509FindType.FindByThumbprint,
CERTIFICATES_THUMBPRINT,
false);
if (certCollection.Count > 0)
{
client.Options.ClientCertificates = certCollection;
}
else
{
// handle error
}
certStore.Close();
}
Where CERTIFICATES_THUMBPRINT is a string (thumbsprint of your certificate, the one you saw on Azure).
In case you want to make it work locally, you just need to install the certificate on your computer, as otherwise it won't obviously find it on the store.
Reference for all this in Azure docs: https://learn.microsoft.com/en-us/azure/app-service/app-service-web-ssl-cert-load.