I need to access a certificate from my Azure Function.
I followed the steps outlined in Runtime error loading certificate in Azure Functions but it didn't work out.
private static X509Certificate2 GetCertificate(string thumbprint, TraceWriter log)
{
X509Store store = new X509Store(StoreName.My, StoreLocation.CurrentUser);
try
{
store.Open(OpenFlags.ReadOnly);
log.Info("Enumerating certificates");
foreach (var cert in store.Certificates) {
log.Info(cert.Subject);
}
var col = store.Certificates.Find(X509FindType.FindByThumbprint, thumbprint, false);
if (col == null || col.Count == 0)
{
return null;
}
return col[0];
}
finally
{
store.Close();
}
}
Two certificates where uploaded to the Azure Function and the setting WEBSITE_LOAD_CERTIFICATES was added as well and set to either * or to the thumpbrint of the required certificate, but to no avail.
The GetCertificate method should print a list of all certificates in the store, but the store is empty.
Any clues on how to solve this?
UPDATE:
Client certificates are now supported in the Consumption plan.
Client certificates are not yet supported in our Consumption plan, only in App Service plan. This is tracked by an issue in our repo here. We're working on it - please follow that issue for status. Thanks.
Related
I have an SSL certificate installed on my domain and I wanted to use it for signing with IdentityServer 4. Now I found that there is a method which would let me do that:
services.AddIdentityServer().AddSigningCredentials(certificate);
However, I cannot figure out how to actually get my certificate and pass it to the identity server.
I have tried the following:
var cert = X509Certificate.CreateFromCertFile(fileName);
services.AddIdentityServer().AddSigningCredentials(certificate);
The error that I get is it cannot convert from
'System.Security.Cryptography.X509Certificates.X509Certificate' to 'Microsoft.IdentityModel.Tokens.SIgningCredential'
Now I don't understand why it is complaining about signing credentials when one of the overrides for the method is the certificate.
I ended up resolving it like this. I'm using a shared server where I am hosting this and I could not find the file name for the certificate or the path to get it. So I ended up just opening the store and finding it that way. Not very efficient, but it will do the trick until I move it to a dedicated server and have more control.
X509Store store = new X509Store(StoreName.My, StoreLocation.LocalMachine);
store.Open(OpenFlags.ReadOnly);
X509Certificate2 cert = null;
foreach (X509Certificate2 certificate in store.Certificates)
{
if (!string.IsNullOrWhiteSpace(certificate?.SubjectName?.Name) && certificate.SubjectName.Name.StartsWith("CN=*.mysite.com"))
{
cert = certificate;
break;
}
}
Maybe it can't convert due to issue in permissions or loading the certificate as a stream.
In my case using IdentityServer3 the below code works:
/// <summary>
/// Load the certificate that sign the Id or Jw token
/// </summary>
/// <returns></returns>
private static X509Certificate2 LoadCertificate()
{
string baseDirectory = AppDomain.CurrentDomain.BaseDirectory;
return new X509Certificate2(
Path.Combine(AppDomain.CurrentDomain.BaseDirectory, ConfigMngr.GetAppSettingsValue<string>("IdSrv:SigningCertificatePath")), ConfigMngr.GetAppSettingsValue<string>("IdSrv:SigningCertificatePassword"));
}
Then in owin's startup file, I am passing it as below:
SigningCertificate = LoadCertificate(),
I know in Idsrv4 it's a different implementation than the code I posted but it should be the same abstraction, for example, you loading X509Certificate but it's deprecated so make sure to use the correct overloading to load the certificate as stream and make sure to return the correct type.
Also, This code is testable with IdSrv4:
var fileName = Path.Combine(env.WebRootPath, "FileName" );
if (!File.Exists(fileName))
{
throw new FileNotFoundException("No Signing Certificate!");
}
var cert = new X509Certificate2(fileName, "Pass" );
services.AddIdentityServer().AddSigningCredential(cert)
So instead of using
X509Certificate.CreateFromCertFile(fileName);
You can construct a new X509Certificate2 certificate like this:
var cert = new X509Certificate2(fileName, "Pass" );
And pass it to the owin middleware:
services.AddIdentityServer().AddSigningCredential(cert)
I have generated a certificate using powershell using this suggestion found at Stackoverflow:
New-SelfSignedCertificate -Subject "CN=Test Code Signing" -Type CodeSigningCert -KeySpec "Signature" -KeyUsage "DigitalSignature" -FriendlyName "Test Code Signing" -NotAfter (get-date).AddYears(5)
I have copied and pasted this certificate into Trusted Root Certification Authorities.
My NET Core WebAPI Program.cs is set as follows:
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>()
.UseKestrel(options=> {
options.Listen(IPAddress.Loopback, 5000); // http:localhost:5000
options.Listen(IPAddress.Any, 80); // http:*:80
options.Listen(IPAddress.Loopback, 443, listenOptions =>
{
//how to use a certificate store here?
//listenOptions.UseHttps("certificate.pfx", "password");
//listenOptions.UseHttps(StoreName.My, "Test Code Signing", allowInvalid: true);
listenOptions.UseHttps(StoreName.My, "localhost", allowInvalid: true);
});
});
Neither localhost or Test Code Signing worked in this code since they cannot be found. Maybe I am missing something. Tried to follow this MSDN documentation with no luck.
For now the certificate shown on Google Chrome is different from the ones I have in Personal and Trusted Root Certification authorities:
How to setup Kestrel in order to pick a self signed certificate that is trusted by browsers and avoiding blocking messages such as NET::ERR_CERT_AUTHORITY_INVALID?
The UseHttps overload you are using doesn't allow you to specific the store location so it defaults to StoreLocation.CurrentUser. You need to do call a method which retrieves the certificate from the store and passes it to the UseHttps method. There's an MSDN article that gives more detail that I've included at the bottom but here's an example (you need to replace 'your common name here' with the certificate common name):
static void Main(string[] args)
{
var host = new WebHostBuilder()
.UseKestrel(options =>
{
options.Listen(IPAddress.Any, 443, listenOptions =>
{
listenOptions.UseHttps(GetHttpsCertificateFromStore());
listenOptions.NoDelay = true;
});
})
.Build();
}
private static X509Certificate2 GetHttpsCertificateFromStore()
{
using (var store = new X509Store(StoreName.My, StoreLocation.LocalMachine))
{
store.Open(OpenFlags.ReadOnly);
var certCollection = store.Certificates;
var currentCerts = certCollection.Find(X509FindType.FindBySubjectDistinguishedName, "CN=[your common name here]", false);
if (currentCerts.Count == 0)
{
throw new Exception("Https certificate is not found.");
}
return currentCerts[0];
}
}
https://learn.microsoft.com/bs-latn-ba/azure/service-fabric/service-fabric-tutorial-dotnet-app-enable-https-endpoint
This method worked for me :
private static X509Certificate2 LoadCertificate(string p_storeName, string p_storeLocation, string p_Host, string p_FilePath, string p_Password, IHostingEnvironment environment)
{
if (p_storeName != "" && p_storeLocation != "")
{
using (var store = new X509Store(p_storeName, Enum.Parse<StoreLocation>(p_storeLocation)))
{
store.Open(OpenFlags.ReadOnly);
bool validOnly = false;
if (environment.IsDevelopment() == true) { validOnly = false; }
else
{
if (p_Host == "localhost") { validOnly = false; }
else { validOnly = true; }
}
var certificate = store.Certificates.Find(
X509FindType.FindBySubjectName,p_Host,validOnly: validOnly);
if (certificate.Count == 0)
{
throw new InvalidOperationException($"Certificate not found for {p_Host}.");
}
return certificate[0];
}
}
if (p_FilePath != "" && p_Password != "")
{
return new X509Certificate2(p_FilePath, p_Password);
}
throw new InvalidOperationException("No valid certificate configuration found for the current endpoint.");
}
I think you should (first) create a right trusted local certificate, (second) associate it with your ip and port, and (third) you should use 'localhost' (the 'CN' for which the certificate was created) in your browser instead the loopback ip.
The following commands in PowerShell (run as admin) will create a root certificate and its associated trusted certificate:
1.- We create a new root trusted cert:
$rootCert = New-SelfSignedCertificate -Subject 'CN=TestRootCA,O=TestRootCA,OU=TestRootCA' -KeyExportPolicy Exportable -KeyUsage CertSign,CRLSign,DigitalSignature -KeyLength 2048 -KeyUsageProperty All -KeyAlgorithm 'RSA' -HashAlgorithm 'SHA256' -Provider 'Microsoft Enhanced RSA and AES Cryptographic Provider'
2.- We create the cert from the root trusted cert chain:
New-SelfSignedCertificate -DnsName "localhost" -CertStoreLocation "cert:\LocalMachine\My" -Signer $rootCert -TextExtension #("2.5.29.37={text}1.3.6.1.5.5.7.3.1") -Provider "Microsoft Strong Cryptographic Provider" -HashAlgorithm "SHA256" -NotAfter (Get-Date).AddYears(10)
3.- We copy the thumbprint returned by the last command
4.- (If neccesary) We remove the last association ip/port/cert:
netsh http delete sslcert ipport=0.0.0.0:443
5.- We associate the new certificate with any ip and port 443 (the appid value does not matter, is any valid guid):
netsh http add sslcert ipport=0.0.0.0:443 appid='{214124cd-d05b-4309-9af9-9caa44b2b74a}' certhash=here_the_copied_thumbprint
6.- Now, you must drag and drop the TestRootCA from Personal/Certificates folder to Trusted Root Certification Authorities/Certificates.
These commands also resolve the error ERR_CERT_WEAK_SIGNATURE_ALGORITHM returned later by Google Chrome because the certificate is created with SHA1 instead of SHA256.
I am using the below method to get certificate from the x509 store
private X509Certificate GetCert(string certThumbPrint)
{
var certStore = new X509Store(StoreName.My, StoreLocation.LocalMachine);
certStore.Open(OpenFlags.ReadOnly);
var certCollection = certStore.Certificates.Find(X509FindType.FindByThumbprint, certThumbPrint.Trim(), false);
certStore.Close();
// Check to see if our certificate was added to the collection. If no, throw an error, if yes, create a certificate using it.
if (0 == certCollection.Count || certCollection.Count == null)
{
throw new ApiException(string.Format("Error: Admin Service Client : No certificate found containing thumbprint {0}",certThumbPrint), ApiLogCategory.Configuration, ErrorCodes.AdminServiceClient);
}
var cert = new X509Certificate(certCollection[0].RawData, string.Empty, X509KeyStorageFlags.MachineKeySet);
return cert;
}
I was getting error so I tried debugging and found that the application is unable to open the X509 store. What can I do in this case.
It looks like a permission related issue.
Is your certificate installed in personal store?
if so, Right click the certificate => All tasks => Manage private key => Add IIS AppPool\AppPoolName and grant it Full control
if you're running app pool under a different account than application pool identity then that that user.
I have uploaded Certificates thorough azure new portal while i am not getting these certificates back here is my code
var store = new X509Store(StoreName.My, StoreLocation.CurrentUser);
store.Open(OpenFlags.ReadOnly);
X509Certificate2Collection certificates = store.Certificates;
try
{
}
finally
{
store.Close();
}
return certificates;
These are the certificates which I always get
enter image description here
Also i have follow this article https://azure.microsoft.com/en-us/blog/using-certificates-in-azure-websites-applications/
Anybody know the reason why i am not getting all the certificates and why i am getting these certificates ?? please help
instead of geeting all certificates i just received these 4, and in the local enviorment i get all certificates which are install on my machine
From my test, the article in your reply help us to use Certificates in Azure web app. However we only could query the certificates with the following conditions:
1) the certificate has been uploaded to Azure web app
2) setting WEBSITE_LOAD_CERTIFICATES in Azure portal with its value set to the certificate thumbprint
It is different with your test on your local machine because Azure web app run in sandbox. For more information about Azure web app sandbox, please refer to this article.
Well, I'm using this function I had found somewhere and is working fine. If you have uploaded all the certificates properly, can you please try running this piece of code. I know it looks the same but you can't really tell.
private X509Certificate2 GetStoreCertificate(string thumbprint)
{
List<StoreLocation> locations = new List<StoreLocation> { StoreLocation.CurrentUser, StoreLocation.LocalMachine };
foreach (var location in locations)
{
Console.WriteLine("location: " + location.ToString());
X509Store store = new X509Store("My", location);
try
{
Console.WriteLine("Try, store.Open...");
store.Open(OpenFlags.ReadOnly | OpenFlags.OpenExistingOnly);
Console.WriteLine("store.Opened..." + store.Certificates.Count.ToString());
foreach (X509Certificate2 cert in store.Certificates)
{
Console.WriteLine("X509Certificate2 Thumbprint : " + cert.Thumbprint);
}
foreach (X509Certificate cert in store.Certificates)
{
Console.WriteLine("X509Certificate Thumbprint : " + cert.Issuer);
}
X509Certificate2Collection certificates = store.Certificates.Find(X509FindType.FindByThumbprint, thumbprint, false);
Console.WriteLine("Finding certificate.." + certificates.Count.ToString());
if (certificates.Count == 1)
{
Console.WriteLine("Atleast one found!!!");
return certificates[0];
}
}
finally
{
store.Close();
}
}
throw new ArgumentException(string.Format("A Certificate with Thumbprint '{0}' could not be located.", thumbprint));
}
I think I have read every single thing on the internet about this (bold statement I know) but I can't work it out...
I have a very simple webpage that gets the status VMs on Azure, which works fine on my machine. I created a Cert on my local machine with makecert and debug runs fine.
After deploying it to another server on IIS all I get is 403 errors.
Things I tried:
Exporting Cert from my dev machine with private key and importing onto the test server
Creating new Cert with makecert (edit: recreated the cert on the server I want to deploy to) (according to this link from MSN), upload to Azure, update code to search for new thumbprint, redeploy and admire the same error msg..
Both times I changed the app pool identity to a user account that is log-on-able (and reverted)
Tried with cert as both localmachine and current user, with user updated in the app pool
I changed my get cert code to more resemble an answer from a similar question, but finding the cert doesn't appear to be the issue.. if I remove the cert created on the server, I get a different error.
var store = new X509Store(StoreName.My, StoreLocation.CurrentUser);
store.Open(OpenFlags.OpenExistingOnly | OpenFlags.ReadOnly);
var certificate = store.Certificates.Cast<X509Certificate2>().SingleOrDefault(c => string.Equals(c.Thumbprint, thumbprint, StringComparison.OrdinalIgnoreCase)); // please replace CertificateThumbprint with original Thumbprint
return certificate;
Ref: how to connect to azure (management) rest api via C# in IIS
Code to create HttpClient:
WebRequestHandler handler = new WebRequestHandler();
String CertThumbprint = _certthumbprint;
X509Certificate2 managementCert = FindX509Certificate(CertThumbprint);
if (managementCert != null)
{
handler.ClientCertificates.Add(managementCert);
HttpClient httpClient = new HttpClient(handler);
httpClient.DefaultRequestHeaders.Add("x-ms-version", "2014-05-01");
httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/xml"));
return httpClient;
}
Retrieve VMs Code:
String uri = String.Format("https://management.core.windows.net/{0}/services/hostedservices/{1}/deploymentslots/{2}", _subscriptionid, ServiceName, "Production");
XDocument vms = new XDocument();
vms.Add(new XElement("VirtualMachines"));
ApplyNamespace(vms.Root, ns);
try
{
HttpClient http = GetHttpClient();
Stream responseStream = await http.GetStreamAsync(uri);
if (responseStream != null)
{
XDocument xml = XDocument.Load(responseStream);
var roles = xml.Root.Descendants(ns + "RoleInstance");
foreach (XElement r in roles)
{
XElement svcNamee1 = new XElement("ServiceName", ServiceName);
ApplyNamespace(svcNamee1, ns);
r.Add(svcNamee1);
vms.Root.Add(r);
}
}
}
This code is currently about 95% copy and paste from here
The resolution for me in this case was to create a new Publishsettings file via powershell and import that on the server via powershell. Then use the thumbprint from that in code. Making a cert on the server and uploading to Azure still doesn't work for whatever reason...