Can a .NET assembly be signed using an X509Certificate? - c#

I believe I have a foundational understanding of the X509Certificate and have created certificates and private keys for my Development environment. Getting more acquainted now with the X509Certificate Class and other relevant permissions also.
So in the process, I decided to test a certificate after having installed it on my system. Then using the following code, I attempted to validate the check process for certification authentication:
const string x509Cert = #"\PathToMyCertificate\LMC.cer";
var cert = new X509Certificate(x509Cert);
var pmc = new PublisherMembershipCondition(cert);
if(pmc.Check(Assembly.GetCallingAssembly().Evidence))
{
Console.WriteLine("Assembly belongs to publisher");
}
Of course as expected, the inner block of code doesn't execute. So then I figured to simply sign my assembly using the certificate key, but "simply" wasn't as simple as I'd anticipated.
I used the following code in effort of assigning the certificate to my applications Evidence:
var publisher = new Publisher(X509Certificate.CreateFromCertFile(x509Cert));
var evidence = Assembly.GetCallingAssembly().Evidence;
evidence.AddHost(publisher);
// Create an identity permission based on publisher evidence.
var x509Permission = (PublisherIdentityPermission)publisher.CreateIdentityPermission(evidence);
x509Permission.Demand();
But this didn't seem to work either. Next, I checked the properties of my project to see if there was any way to sign it there using the X509Certificate key but nothing. The only option I see that comes close is to sign with Click Once manifests; but the "Select from file" option is looking for a .pfx extension. So I think maybe this method only works to support certificates generated by Click-Once?
Per BOL, "If you have a key file or certificate in another format, store it in the Windows certificate store and select the certificate is described in the previous procedure." I installed my X509Certificate in the Trusted Root Certificate Authorities store. Wouldn't that be a Windows certificate store? Because nothing shows up in the Certificate Store window.
Searching online resources didn't yield much either unless I am using the wrong combination of keywords. Now I could create another X509Certificate and key ensuring that the extension of the key is .pfx but I wanted to make certain that I am on the right course of resolve before spinning my wheels for nothing and I don't believe that would be the answer.
So, can a .NET assembly be signed using an X509Certificate? If so, what documentation is available to assist in performing this task?

Publisher* classes are associated with Authenticode(tm).
Look for the command-line tools:
* signcode (or signtool)
* chktrust
for how you can sign any .exe, .dll (and .cab, .ocx) using a valid code-signing certificate. A google search on Authenticode or "code-signing certificate" can also be helpful.

The question is what you want to do. There exist .NET signing (using RSA keypair) used for strong-naming the assemblies, and there exists Authenticode which lets you sign any file in PE format including assemblies in DLL files. Note, that Authenticode is not .NET-specific and knows nothing about .NET. It signs PE structure.
As said by poupou, for Authenticode signing (using X.509 certificates suitable for Code Signing) you can use SignTool.exe tool. .NET will verify the signature when it loads the assembly, but in some cases such verification can take extra seconds (if the OS performs CRL and OCSP checking of certificates in the chain), slowing down assembly loading.
So you need to choose the right tool and define what you want to achieve (signing is a method, not a goal).

Related

.net standard x509 operating system differences

I have a DLL which creates a signature based off a JSON. The signing is used with X509 self signed certificate with a public \ private keys (.crt and .pfx files), the DLL is using the private key (pfx file) to sign the data.
This DLL is used twice: once in my actual project, which runs on a linux container, and once in my local tests project, which runs on my windows machine.
There is a second DLL uses the public key to verify the signature (crt file).
Here's the thing: the signature created in my test project matches the signature created in my verifier project, both are windows machines. The project on the linux machine signs differently.
What is going on here?? I have several directions:
Different newlines between linux and windows: I have changed the signing DLL so that it takes the JSON string, deserializes into Newtonsoft.JSON object, serializes again with flag Formatting.None, and then deserializes for the final time. This did not help.
Different invisible characters \ encoding: my code is calling UTF8.GetBytes internally on the string, so don't think it's that.
Additionaly, I took the literall byte array from the linux machine into the windows machine, and got different signatures.
Which leads me to think, is the library System.Security.Cryptography.Xml(5.0.0) behaves differently between the OSs? this page says yes, but does not say how:
Cryptographic operations in .NET Core and .NET 5 are done by operating system (OS) libraries. This dependency has advantages
Any ideas as to why this is happening, and maybe a solution?
My signer code:
...
JToken data = JToken.Parse("{}") //obviously a different string
var certificate = new X509Certificate2(...);
var rsaCSP = new RSACryptoServiceProvider();
rsaCSP.FromXmlString(certificate.PrivateKey.ToXmlString(true));
var signatureBytes = rsaCSP.SignData(Encoding.UTF8.GetBytes(data.ToString(), CryptoConfig.MapNameToIOD(...));

Using C#, how to programmatically determine if a certificate in a Windows certificate store has been disabled

I'm currently working on refining communications between mutually authenticated client/server applications using HTTPS. I am currently building validation logic into a C# client to help identify configuration issues when a TLS connection fails. In verifying the connection, I validate that the root CA certificate presented by the server is installed on the client, in the appropriate store, and is valid. I'm using X509Store to pull the X509Certificate2, and validating it using X509Chain.
My issue is that the certificate will report as valid even if the certificate has been disabled via MMC. So the TLS connection will fail, despite the chain reporting as valid.
It's an unlikely case, but one I'd like to handle by reporting something like "Could not connect because root CA is disabled."
Could anyone point me in the direction of a .NET or Win32 call that could be made to determine the value of "Certificate Purposes" for a certificate? Or to read the "Certificate Status" for a cert?
I read through MSDN's listing of what's in the System.Security.Cryptography namespace, and started looking into CryptoAPI and CNG, but didn't find anything so far.
Thanks!
That dialog does not "disable" a certificate it disables it "for all purposes". What this means is it counts as having an empty Enhanced Key Usage extension for purposes of EKU validation.
Normally a root certificate (or an intermediate CA certificate) will not have an EKU extension, so if you do a chain build with any ApplicationPolicy value it will be a match. Once you set it to Disable for all purposes you'll get a chain error X509ChainStatusFlags.NotValidForUsage.
If you want to build something for validating TLS you'd check either the Server Authentication or Client Authentication EKUs (depending on what you're checking):
// Server EKU or Client EKU
Oid eku = forServer ? new Oid("1.3.6.1.5.5.7.3.1") : new Oid("1.3.6.1.5.5.7.3.2");
// Test if it's valid for that purpose
chain.ChainPolicy.ApplicationPolicy.Add(eku);
If you want to "Disable" a root CA altogether, add a copy of the certificate to the Disallowed store (called "Untrusted Certificates" in the UI). That will result in a chain build producing X509ChainStatusFlags.ExplicitDistrust.

C# How to get .dll or .exe files digital signer certificate info even the certificate expired?

I'm using C# check some exe or dll file digital certificate.
Assembly asm = Assembly.LoadFrom(assembly);
Module module = asm.GetModules().First();
System.Security.Cryptography.X509Certificates.X509Certificate certificate = module.GetSignerCertificate();
// it will null if the certificate time expired
if (null != certificate)
{
if (certificate.Subject.Contains("Company Name"))
Console.Write("success");
else
Console.Write("invalid");
}
else
{
Console.Write("failed");
}
But now problem is : If the certificate time is expired, or because modify local time to after certificate time the code module.GetSignerCertificate() always get null.
Because I just want check the assembly has digital certificate and company name, not check the time, So Is there any way can check it use other way or not let it null?
This code does work well in my case, including expired certificate case.
using System.Security.Cryptography.X509Certificates;
...
// "assembly" is a string of full path for .exe or .dll.
var cert = X509Certificate.CreateFromSignedFile(assembly);
The module is incorrectly signed. A properly signed module includes the timestamp when the module was signed. The validation rule dictates that the certificate should had been valid at the moment of signing (this, at the timestamp). Read this answer here https://stackoverflow.com/a/3428386/105929 for more details.
If this is your own module, then the best action is to modify your release sign-off to include a timestamp in the signature. Read Time Stamping Authenticode Signatures to understand how to do it (time stamping involves a URL service provided by your certificate CA). Also read Everything you need to know about Authenticode Code Signing.
As to your question: if you look at module.cs reference code, you'll see that the implementation is native and not much you can do about it to modify it:
[System.Security.SecurityCritical] // auto-generated
[ResourceExposure(ResourceScope.None)]
[DllImport(JitHelpers.QCall, CharSet = CharSet.Unicode)]
[SuppressUnmanagedCodeSecurity]
static private extern void GetSignerCertificate(RuntimeModule module, ObjectHandleOnStack retData);
You could instead use the signtool.exe verify command, afaik it has options to display all certificates, including expired ones on modules lacking a proper timestamp.
The module is correct. Because before it expired, it can use very well. not get null. after it expired, or I change my system time. it will get null
This can easily be proven to be incorrect. Find an old correctly signed assembly on your machine, one distributed by a serious vendor that knows what is doing. Eg. I found this on my machine: c:\Program Files (x86)\Microsoft SQL Server\100\Setup Bootstrap\Release\x64\Microsoft.AnalysisServices.DLL. Look at the Digital Signature properties:
Do note the signature timestamp (20110922) and the certificate expiration (20120521).
Write a small app to get the cert using CLR API:
static void Main(string[] args)
{
Assembly asm = Assembly.LoadFile(#"c:\Program Files (x86)\Microsoft SQL Server\100\Setup Bootstrap\Release\x64\Microsoft.AnalysisServices.DLL");
Module m = asm.GetModules()[0];
var cert = m.GetSignerCertificate();
Console.WriteLine("{0}", cert);
}
Is the certificate found? Yes. Is the certificate expired? Yes. Is the module properly signed, using a timestmap? Yes.
QED

How do I access X.509 certificates stored in a service account?

I'm trying to digitally sign a PDF document using Syncfusion PDF 10.4, like so:
PdfLoadedDocument document = new PdfLoadedDocument(inputStream);
PdfCertificate certificate = PdfCertificate.FindBySubject(certificateStoreType, certificateSubjectName);
PdfSignature signature = new PdfSignature(document, document.Pages[0], certificate, "Signatur");
signature.Bounds = new RectangleF(new PointF(5, 5), new SizeF(100, 100));
This works great for my local user account after installing a suitable certificate using MMC (adding the Certificates snap-in for My user account and storing it in Personal), but not for a service (choosing Service account this time, and picking my service). Running the same code results in no suitable certificate being found, i.e. certificate is null. Furthermore, PdfCertificate.GetCertificates() throws an AccessViolationException, which I assume is a bug on Syncfusion's end.
I can, however, reproduce the same problem without Syncfusion code:
var store = new System.Security.Cryptography.X509Certificates.X509Store("My");
store.Open(System.Security.Cryptography.X509Certificates.OpenFlags.ReadOnly);
foreach (var item in store.Certificates)
{
…
}
Run as my own user, the certificate shows up (as do all the others shown in MMC under Personal), but if I debug the service (by running it, then invoking System.Diagnostics.Debugger.Launch()), I only get a "CN=LOCAL SERVICE" certificate, which doesn't show up in MMC at all.
I'm assuming that I need to A) tell it to open the correct certificate store, or B) change something about the way the service is installed or run, such as giving it a different identity, enabling UserInteraction, etc. Currently, it runs using LocalService and with UserInteraction disabled.
From what I remember, Windows machine accounts (like LocalService) use the machine certificate store. This means that in your code, you have to access the store with StoreLocation.LocalMachine.
var store =
new System.Security.Cryptography.X509Certificates.X509Store(StoreLocation.LocalMachine);
Note that if you decide to run the service under specific identity, you should rather first login as the identity, then import the certificate to the Personal store and then, use StoreLocation.CurrentUser.
The answer appears to be that .NET doesn't support accessing service account certificate stores without P/Invoke or the like:
I don't think that any of the .NET APIs allow access to the Services Certificate store.
However, you can install the certificate into the CurrentUser store of the account that the service runs under.
I've changed the service to run under its own user (which doesn't need admin rights), ran mmc.exe as that user using runas, and imported the certificate to that user's personal store.
I ran into this problem, and to solve it had to allow the "Local Service" account to access the "Local Computer" certificate store using the tool "WinHttpCertCfg"
It is described in detail here:
https://support.microsoft.com/en-us/help/901183/how-to-call-a-web-service-by-using-a-client-certificate-for-authentication-in-an-asp-net-web-application

X509 Certificate with Subject UID

I am loading a certificate from string like this:
public static void Test()
{
byte[] arrayCertificate;
arrayCertificate = Convert.FromBase64String("CERT_STRING");
X509Certificate2 clientCertificateFromXml = new X509Certificate2(arrayCertificate);
Console.Write(clientCertificateFromXml);
Console.ReadKey();
}
But this certificate doesn't have a "Subject Unique Identifier"
Take a look at this:
http://en.wikipedia.org/wiki/X.509 (The part of Structure of a certificate)
And I want to know how can I read that value from my .NET code (I looked that I can get SerialNumber, Thumbprints and others but there is no Subject UID anywhere).
Also, I will really appreciate If anyone can share an openssl command to include this UID for the certificate :-) (pfx one)
And I want to know how can I read that value from my .NET code
IIRC this is not exposed in the .NET BCL, either from X509Certificate or the newer (better but still incomplete) X509Certificate2.
But you can use Mono.Security assembly (or just the code you want from it), from the Mono project. It's open source, MIT.X11 licensed and it includes it's own X509Certificate.
This version expose just about everything in X.509 certificates, including a SubjectUniqueIdentifier property.
I will really appreciate If anyone can share an openssl command to include this UID for the certificate
I do not recall for openssl... but you can use the X509CertificateBuilder from Mono.Security to create your own certificates. See Mono's makecert tool source code for an example.
Disclaimer: I wrote the code :-)

Categories

Resources