PushSharp's notification to Apple generate temp RSA files on server - c#

I'm having a problem with my WCF push notification service.
Every time it sends a push notification to apple, a new RSA is generated inside the "C:\ProgramData\Microsoft\Crypto\RSA\MachineKeys" folder.
I think that the problem causing this might be related to the Apple p12 Certificate.
Why does that happen? Is there any way to prevent the generating of those RSA?
I'm using PushSharp library and my code looks like this:
pushBroker = new PushBroker();
pushBroker.RegisterAppleService(
new PushSharp.Apple.ApplePushChannelSettings(false, appleCert, p12Pass, false)
, null);

The temporary file is where Windows has put the RSA private key that it loaded out of the appleCert PFX blob.
If the file appears, then disappears (since you called it a "temp" file), then everything's working as expected. If it appears and stays forever then PushSharp is loading the certificate with PersistKeySet, which says that the private key shouldn't be deleted.
I don't know if https://github.com/has-taiar/PushSharp.Web/blob/master/PushSharp.Apple/ApplePushChannelSettings.cs is the real source of ApplePushChannelSettings, but it'll do. Looking at that source, they seem to be using PersistKeySet, making the file permanent.
You have two practical approaches:
1) Load the certificate into an X509Store as a one-time operation (using PersistKeySet on load because you need to for store persistence). The file becomes permanent, but gets used for forever, so it's cool.
2) Load the certificate yourself from the PFX and (ideally) Dispose the certificate when you're done. I don't know if you have any sort of on-shutdown notification that you can more-or-less reliably use, but calling cert.Dispose() (or, on older Framework versions, cert.Close()) is more reliable than waiting on the GC and Finalizer to do the cleanup for you.
If you do have a good shutdown event:
// Note that I'm not specifying any X509KeyStorageFlags here.
// You don't want it PersistKeySet (that's the whole point) and you don't
// need it to be exportable (unless you get exceptions without Exportable, in which case
// PushSharp is doing something naughty).
private X509Certificate _cert = new X509Certificate2(appleCert, p12CerPass);
...
_broker.RegisterAppleService(new PushSharp.Apple.ApplePushChannelSettings(_cert, false));
...
// In some OnShutdown type location
_cert.Dispose();
If not:
// Still don't specify any load flags. Especially not PersistKeySet.
var cert = new X509Certificate2(appleCert, p12CerPass);
_broker.RegisterAppleService(new PushSharp.Apple.ApplePushChannelSettings(cert, false));
No matter what you'll still end up with a permanently leaked file every time your process abnormally terminates, since the file-deletion code won't get a chance to kick in.
A solution exists to completely avoid creating the temporary file, and that is to use the EphemeralKeySet flag which was added to .NET Core 2.0 (currently in preview). It's not yet available in .NET Framework.
// Keep the private key in memory, never let it touch the hard drive.
var cert = new X509Certificate2(appleCert, p12CerPass, X509KeyStorageFlags.EphemeralKeySet);
_broker.RegisterAppleService(new PushSharp.Apple.ApplePushChannelSettings(cert, false));
But I don't know if PushSharp is available for .NET Core.

I had the same problem and i found the solution
to prevent generate key every time you send new push notification,
the code should be like this. and it work perfect for me
var cert = X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.Exportable;
var certificate1 = new X509Certificate2(appleCert, p12CerPass,cert);
_broker.RegisterAppleService(new PushSharp.Apple.ApplePushChannelSettings(certificate1, false));

Related

Grant user permission to the private key

installing my WCF service I also install certificate with private key.
Since I will be running service as a different user, that user needs access to the private key. I extensively read other stackoverflow questions and they all suggest permission on private key file in file system needs to adjusted.
I do this by,
private static void AddUserPermissions(X509Certificate2 certificate, NTAccount user, StoreLocation storeLocation)
{
RSACryptoServiceProvider rsaProvider = (RSACryptoServiceProvider)certificate.PrivateKey;
// Find file
string keyPath = FindKeyLocation(rsaProvider.CspKeyContainerInfo.UniqueKeyContainerName, storeLocation);
FileInfo keyFileInfo = new FileInfo(keyPath);
// Create new FileSecurity
FileSecurity keyFileSecurity = keyFileInfo.GetAccessControl();
keyFileSecurity.AddAccessRule(new FileSystemAccessRule(user, FileSystemRights.Read, AccessControlType.Allow));
// Apply file security to the file
keyFileInfo.SetAccessControl(keyFileSecurity);
}
When I run my program and inspect the private key file I can see, for example "Network Service" has been added to the permissions list.
Great, that's working, but when WCF tries to use private key, it cannot access it.
Looking at certlm, certificate -> All Tasks -> Manage Private Keys..
I can see that my user is not on the list. Adding my user through GUI solves the issue, but I need to do it in code!!
Crypto Service Provider (Microsoft RSA SChannel Cryptographic Provider)
The keys are located in C:\ProgramData\Application Data\Microsoft\Crypto\RSA\MachineKeys and setting a normal file permission here is reflected in certlm.msc.
Crypto Next Generation (Microsoft Key Storage Provider)
The keys are located in C:\ProgramData\Application Data\Microsoft\Crypto\Keys and setting a normal file permission here is reflected in certlm.msc.
Summary
Ensure you modify the permissions on the right file in the right location.
Anyone who gets this far looking for a solution for ECDSA certificate keys, I found a way!
string keyUniqueName = (certificate.GetECDsaPrivateKey() as ECDsaCng)?.Key.UniqueName
?? (certificate.GetRSAPrivateKey() as RSACng)?.Key.UniqueName
?? throw new NotSupportedException("No ECDSA or RSA key found");
In the example code from the question, the FindKeyLocation gets passed rsaProvider.CspKeyContainerInfo.UniqueKeyContainerName, instead you would pass keyUniqueName as taken from my example.
One more good note, the accepted answer from Daniel Fisher lennybacon correctly documents where keys are stored based on where they were generated/installed. But you should use Environment.SpecialFolder.CommonApplicationData or Environment.SpecialFolder.ApplicationData for key file paths.
Also, I found my keys in c:\ProgramData\Microsoft\Crypto\, not c:\ProgramData\Application Data\Microsoft\Crypto\.

Gaining access to a MemoryMappedFile from low-integrity process

I'm trying to create a MemoryMappedFile on a medium-integrity process, then open the same file on a low-integrity child process and use this shared memory for IPC. There's no real disk file (using MemoryMappedFile.CreateNew).
My problem is that the low-integrity process cannot open the shared memory, throwing this: "System.UnauthorizedAccessException: Access to the path is denied.". I'm not surprised that this is the case, given that I want write access from the low-integrity process, but how do you grant it access?
Here's my code:
Medium integrity process:
MemoryMappedFileSecurity security = new MemoryMappedFileSecurity();
var file = MemoryMappedFile.CreateNew("test", 4096, MemoryMappedFileAccess.ReadWrite, MemoryMappedFileOptions.None, security, HandleInheritability.Inheritable);
var view = file.CreateViewAccessor();
view.Write(0, true);
Low integrity process:
try
{
MemoryMappedFile file = MemoryMappedFile.OpenExisting("test", MemoryMappedFileRights.ReadWrite);
var view = file.CreateViewAccessor();
var v = view.ReadBoolean(0);
Log.Info("MAPPED: " + v);
}
catch (Exception e)
{
Log.Info("Error: " + e);
}
Works fine if both processes work in medium integrity. After reading this, I tried setting the SDDL string on the medium integrity process like this:
security.SetSecurityDescriptorSddlForm("S:(ML;;NW;;;LW)");
But that gives me another exception, this time when the memory mapped file is created: "System.IO.IOException: A required privilege is not held by the client.". Not really sure this is the right way to do it anyway, I'm not really clear on how the Win32/C++ examples translates to C#...
Anyone know anything more about this?
Okay, got a working solution. There were two problems:
Passing an empty MemoryMappedFileSecurity object to MemoryMappedFile.CreateNew() made the mapped memory inaccessible even to the same process. That explained my error in my comment ("System.UnauthorizedAccessException: Access to the path is denied").
I couldn't actually get security.SetSecurityDescriptorSddlForm to work (and even though google reveals several other attempts at this, none of them worked for me). Instead, I used this solution: https://stackoverflow.com/a/14424623/5105846. As far as I can tell, it does the same thing, but using PInvoke instead. So I just called InterProcessSecurity.SetLowIntegrityLevel(file.SafeMemoryMappedFileHandle), and it made it accessible from the low-integrity child process. Success!
Not the perfect solution, but a working one is all I need for now. Thanks Harry for your help!

Visual C# - Checking installed certificate - Does nothing if it doesnt exist

This is probably a very basic error, but i am pulling my hair trying to understand why this is happening.
I have a check against the store certificates if it contains a certain certificate with a name. If it doesnt, then update a label.text.
It does the check just fine and it finds it, but no matter what i do it doesnt handle if it isnt there. Its not doing anything. No text beeing displayed. I have also tried a simple else without the (!mCert) but still no go.
// Certificate controls
X509Store store = new X509Store("My", StoreLocation.LocalMachine);
store.Open(OpenFlags.ReadOnly);
foreach (X509Certificate2 mCert in store.Certificates)
{
if (mCert.Issuer.Contains("Cert-Name"))
{
label3.Text = "Found certificate";
}
else if (!mCert.Issuer.Contains("Cert-Name"))
{
label3.Text = "Didnt find the certificate";
}
}
So the else if statement isnt doing anything. Even if i just put an else instead there it isnt updating the label3.text.
If the store is empty and cant find any certificates the foreach will never run, thats why the if statements never get processed.
Adding this before the foreach will solve it
if (store.Certificates.Count==0)
"My" store of local machine is very likely to be empty (unless you install things such as IIS, where the installer generates test certificates).
Thus, switch to other stores, such as using StoreName enumeration,
https://msdn.microsoft.com/en-us/library/system.security.cryptography.x509certificates.storename(v=vs.110).aspx
You can always open MMC to see stores and certificates,
https://msdn.microsoft.com/en-us/library/ms788967(v=vs.110).aspx

Getting back variables after a program crashes

I am trying to find a way to get back my previous variable's value so that I can resume my application to it's previous running state before it crashed when I MANUALLY relaunch it. I am currently using a 'config' file that is saved in the project folder. Is there a better way to do this?
Some small parts of my code that I want to save.
if (EventID == WIA.EventID.wiaEventItemCreated)
{
if (d != null)
{
foreach (Property p in d.Properties)
{
if (p.Name.Equals("Pictures Taken"))
Console.WriteLine("Taken");
}
wiaImageFile = (WIA.ImageFile)(d.Items[d.Items.Count].Transfer(FormatID.wiaFormatJPEG));
wiaImageFile.SaveFile(Properties.Settings.Default.FolderNameRaw + "\\" + imageCount + ".jpg");
imageCount++;//I want to save this count so that I can continue the sequence even after the application crashes
Pluck.Default.PhotoExistsInDirectory = true;
FacebookControls fbc = new FacebookControls();
if(Properties.Settings.Default.UseFB == true)
fbc.UploadPhotos();
}
}
A config file is a good answer in general. Your other options are usually the registry or the database, but I would argue that a config file is a safer option.
The thing about persisting this information is that it may cause an error again, and if so you'll want to be able to discard it easily. A file (of course stored in user settings space) is perhaps the way to do that. If need be you can instruct the user to delete the file. It's a more complicated fix for a user to access the registry or the database.
Also, you should wrap up your state in an appropriate object, and build initialization logic that initializes the state object and has mechanism for not loading for the config file.
I use config files. I also have a global exception handler that catches any exceptions and offers the chance to save any files (including those that the user is working on) before the app closes.
I would also agree with C Ross that you may persist the data that caused the app to fail. Another option that will not get you right back is to persist the settings at regular intervals using a timer or background process. I use this with several backups a bit like the system restore feature in windows.
You can handle UnhandledException, Application_ThreadException and Application.ApplicationExit Event, and try saving your data there:
http://www.switchonthecode.com/tutorials/csharp-tutorial-dealing-with-unhandled-exceptions
As #C. Ross said, user config file is a good choice.
Of course, first you'll have to preserve your application's state in some object during runtime.

.NET virus scanning API

I'm building a web application in which I need to scan the user-uploaded files for viruses.
Does anyone with experience in building something like this can provide information on how to get this up and running? I'm guessing antivirus software packages have APIs to access their functionality programatically, but it seems it's not easy to get a hand on the details.
FYI, the application is written in C#.
Important note before use:
Be aware of TOS agreement. You give them full access to everything: "When you upload or otherwise submit content, you give VirusTotal (and those we work with) a worldwide, royalty free, irrevocable and transferable licence to use, edit, host, store, reproduce, modify, create derivative works, communicate, publish, publicly perform, publicly display and distribute such content."
Instead of using a local Antivirus program (and thus binding your program to that particular Antivirus product and requesting your customers to install that Antivirus product) you could use the services of VirusTotal.com
This site provides a free service in which your file is given as input to numerous antivirus products and you receive back a detailed report with the evidences resulting from the scanning process. In this way your solution is no more binded to a particular Antivirus product (albeit you are binded to Internet availability)
The site provides also an Application Programming Interface that allows a programmatically approach to its scanning engine.
Here a VirusTotal.NET a library for this API
Here the comprensive documentation about their API
Here the documentation with examples in Python of their interface
And because no answer is complete without code, this is taken directly from the sample client shipped with the VirusTotal.NET library
static void Main(string[] args)
{
VirusTotal virusTotal = new VirusTotal(ConfigurationManager.AppSettings["ApiKey"]);
//Use HTTPS instead of HTTP
virusTotal.UseTLS = true;
//Create the EICAR test virus. See http://www.eicar.org/86-0-Intended-use.html
FileInfo fileInfo = new FileInfo("EICAR.txt");
File.WriteAllText(fileInfo.FullName, #"X5O!P%#AP[4\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*");
//Check if the file has been scanned before.
FileReport fileReport = virusTotal.GetFileReport(fileInfo);
bool hasFileBeenScannedBefore = fileReport.ResponseCode == ReportResponseCode.Present;
Console.WriteLine("File has been scanned before: " + (hasFileBeenScannedBefore ? "Yes" : "No"));
//If the file has been scanned before, the results are embedded inside the report.
if (hasFileBeenScannedBefore)
{
PrintScan(fileReport);
}
else
{
ScanResult fileResult = virusTotal.ScanFile(fileInfo);
PrintScan(fileResult);
}
... continue with testing a web site ....
}
DISCLAIMER
I am in no way involved with them. I am writing this answer just because it seems to be a good update for these 4 years old answers.
You can use IAttachmentExecute API.
Windows OS provide the common API to calling the anti virus software which is installed (Of course, the anti virus software required support the API).
But, the API to calling the anti virus software provide only COM Interface style, not supported IDispatch.
So, calling this API is too difficult from any .NET language and script language.
Download this library from here Anti Virus Scanner for .NET or add reference your VS project from "NuGet" AntiVirusScanner
For example bellow code scan a file :
var scanner = new AntiVirus.Scanner();
var result = scanner.ScanAndClean(#"c:\some\file\path.txt");
Console.WriteLine(result); // console output is "VirusNotFound".
I would probably just make a system call to run an independent process to do the scan. There are a number of command-line AV engines out there from various vendors.
Take a look at the Microsoft Antivirus API. It makes use of COM, which should be easy enough to interface with from .NET. It refers specifically to Internet Explorer and Microsoft Office, but I don't see why you wouldn't be able to use to to on-demand scan any file.
All modern scanners that run on Windows should understand this API.
Various Virus scanners do have API's. One I have integrated with is Sophos. I am pretty sure Norton has an API also while McAfee doesn't (it used to). What virus software do you want to use? You may want to check out Metascan as it will allow integration with many different scanners, but there is an annual license cost. :-P
I also had this requirement. I used clamAv anti virus which provides on-demand scanning by sending the file to their tcp listening port. You can use nClam nuget package to send files to clamav.
var clam = new ClamClient("localhost", 3310);
var scanResult = clam.ScanFileOnServerAsync("C:\\test.txt"); //any file you would like!
switch (scanResult.Result.Result)
{
case ClamScanResults.Clean:
Console.WriteLine("The file is clean!");
break;
case ClamScanResults.VirusDetected:
Console.WriteLine("Virus Found!");
Console.WriteLine("Virus name: {0}", scanResult.Result.InfectedFiles[0].FileName);
break;
case ClamScanResults.Error:
Console.WriteLine("Woah an error occured! Error: {0}", scanResult.Result.RawResult);
break;
}
A simple and detailed example is shown here. Note:- The synchronous scan method is not available in the latest nuget. You have to code like I done above
For testing a virus you can use the below string in a txt file
X5O!P%#AP[4\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*
Shameless plug but you might want to check out https://scanii.com, it's basically malware/virus detection as a (REST) service. Oh also, make sure you read and understand virustotal's API terms (https://www.virustotal.com/en/documentation/public-api/) - they are very clear about not allowing commercial usage.
I would recommend using this approach:
using System;
using System.Diagnostics;
using Cloudmersive.APIClient.NET.VirusScan.Api;
using Cloudmersive.APIClient.NET.VirusScan.Client;
using Cloudmersive.APIClient.NET.VirusScan.Model;
namespace Example
{
public class ScanFileAdvancedExample
{
public void main()
{
// Configure API key authorization: Apikey
Configuration.Default.AddApiKey("Apikey", "YOUR_API_KEY");
var apiInstance = new ScanApi();
var inputFile = new System.IO.FileStream("C:\\temp\\inputfile", System.IO.FileMode.Open); // System.IO.Stream | Input file to perform the operation on.
var allowExecutables = true; // bool? | Set to false to block executable files (program code) from being allowed in the input file. Default is false (recommended). (optional)
var allowInvalidFiles = true; // bool? | Set to false to block invalid files, such as a PDF file that is not really a valid PDF file, or a Word Document that is not a valid Word Document. Default is false (recommended). (optional)
var allowScripts = true; // bool? | Set to false to block script files, such as a PHP files, Pythong scripts, and other malicious content or security threats that can be embedded in the file. Set to true to allow these file types. Default is false (recommended). (optional)
var allowPasswordProtectedFiles = true; // bool? | Set to false to block password protected and encrypted files, such as encrypted zip and rar files, and other files that seek to circumvent scanning through passwords. Set to true to allow these file types. Default is false (recommended). (optional)
var restrictFileTypes = restrictFileTypes_example; // string | Specify a restricted set of file formats to allow as clean as a comma-separated list of file formats, such as .pdf,.docx,.png would allow only PDF, PNG and Word document files. All files must pass content verification against this list of file formats, if they do not, then the result will be returned as CleanResult=false. Set restrictFileTypes parameter to null or empty string to disable; default is disabled. (optional)
try
{
// Advanced Scan a file for viruses
VirusScanAdvancedResult result = apiInstance.ScanFileAdvanced(inputFile, allowExecutables, allowInvalidFiles, allowScripts, allowPasswordProtectedFiles, restrictFileTypes);
Debug.WriteLine(result);
}
catch (Exception e)
{
Debug.Print("Exception when calling ScanApi.ScanFileAdvanced: " + e.Message );
}
}
}
}
Note that this way you can even control whether you filter out non-virus threat payloads such as executables, scripts, encrypted/password-protected files, etc.
This approach has a free tier and can also validate the contents of the files that you upload.
We tried two options:
clamav-daemon installed on a tiny linux container + "nClam" .NET library to interact with it. Works fine, but Clam AV misses a lot (a lot!) of viruses, especially dangerous macros hidden in MS Office files. Also ClamAV virus database has to be kept in memory at all times, which uses around 3.5GB of memory, which requires a rather expensive cloud virtual machine.
Ended up using Windows Defender via MpCmdRun.exe CLI api. See answer here
You can try to use DevDragon.io.
It is a web service with an API and .NET client DevDragon.Antivirus.Client you can get from NuGet. Scans are sub 200ms for 1MB file.
More documentation here:
https://github.com/Dev-Dragon/Antivirus-Client
Disclosure: I work for them.
From my experience you can use COM for interfacing with some anti-virus software. But what I would suggest is a bit easier, just parse scan results after scanning. All you need to do is to start the scanner process and point it to file/folder you want to scan, store scan results into file or redirect stdout to your application and parse results.
//Scan
string start = Console.ReadLine();
System.Diagnostics.Process scanprocess = new System.Diagnostics.Process();
sp.StartInfo.WorkingDirectory = #"<location of your antivirus>";
sp.StartInfo.UseShellExecute = false;
sp.StartInfo.FileName = "cmd.exe";
sp.StartInfo.Arguments = #"/c antivirusscanx.exe /scan="+filePath;
sp.StartInfo.CreateNoWindow = true;
sp.StartInfo.RedirectStandardInput = true;
sp.StartInfo.RedirectStandardError = true; sp.Start();
string output = sp.StandardOutput.ReadToEnd();
//Scan results
System.Diagnostics.Process pr = new System.Diagnostics.Process();
pr.StartInfo.FileName = "cmd.exe";
pr.StartInfo.Arguments = #"/c echo %ERRORLEVEL%";
pr.StartInfo.RedirectStandardInput = true;
pr.StartInfo.RedirectStandardError = true; pr.Start();
output = processresult.StandardOutput.ReadToEnd();
pr.Close();

Categories

Resources