Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

new X509Certificate2(byte[]) should return the signing cert for PKCS#7 on Unix #15073

Open
Tracked by #64488
bartonjs opened this issue Aug 21, 2015 · 23 comments
Open
Tracked by #64488
Labels
area-System.Security bug disabled-test The test is disabled in source code against the issue
Milestone

Comments

@bartonjs
Copy link
Member

The Windows code supports reading a PKCS#7 signed or PKCS#7 signed-and-enveloped structure; but in single certificate mode it doesn't return certs[0], it returns the certificate which signed the structure.

My test files (produced by Windows (certmgr.msc and X509Certificate2Collection::Export)) aren't signed, so Windows emits an exception (new CryptographicException(ErrorCode.CRYPT_E_SIGNER_NOT_FOUND)).

Without a sample to see what's going on here, it's hard to make compatible behavior. So, for now, the Unix implementation will just throw, even if it could have worked.

@bartonjs bartonjs self-assigned this Aug 21, 2015
@bartonjs bartonjs removed their assignment Nov 9, 2016
@danmoseley
Copy link
Member

Marking Future explicitly because we have decided thisi s not necessary for 2.0.

@bartonjs
Copy link
Member Author

Moving to Future, it's just getting a bit late to plumb this down (and a lot of tests need to be defined to ensure that we're behaving the same way as in our Windows black-box implementation... zero signers, multiple signers, igner-cert-not-included, hybridized values, etc)

The workaround for caller code which knows it's getting a PKCS7 is to just load it into the SignedCms class. This is the naive approach (only works for one signer, when the signer cert is present)

SignedCms cms = new SignedCms();
cms.Decode(bytes);
SignerInfoCollection signers = cms.SignerInfos;

if (signers.Count != 1)
{
    throw new CryptographicException("Wrong number of signers.");
}

return signers[0].Certificate ?? throw new CryptograpicException("Signer cert was not present");

@filipnavara
Copy link
Member

filipnavara commented Jun 22, 2018

I was playing with decoding Authenticode, which is also supposed to be supported. Turns out most of the plumbing code is already present in CoreFX (no error handling, just PoC):

		var peReader = new PEReader(new MemoryStream(smallspcexe));
		// TODO: Check for non-PE files
		var certDirectory = peReader.PEHeaders.PEHeader.CertificateTableDirectory;
		// TODO: Check for no certificate table
		var certData = peReader.GetEntireImage().GetContent(certDirectory.RelativeVirtualAddress, certDirectory.Size);
		// TODO: Handle different certificate types (X.509, PKCS#7) and read the length (ie. multiple certificates)
		var certData2 = new byte[certData.Length - 8];
		certData.CopyTo(8, certData2, 0, certData2.Length);
		var certType = X509Certificate2.GetCertContentType(certData2);
		var x509 = new X509Certificate2(certData2);

and it boils down to reading PKCS#7 certificates. The last constructor fails with an exception.

@kygagner
Copy link

kygagner commented Jul 2, 2018

We need to be able to verify Authenticode signatures on managed DLLs in Linux for licensing enforcement.

@Kalyxt
Copy link

Kalyxt commented Jun 11, 2019

Hello, my project based on linux relying on reading cert from PKCS#7 signed file, which is not working atm. Is this going to be fixed in upcoming .NET Core versions (3.0) or what is the situation around this? I would greatly appreciate any info about this issue and its future.

@secana
Copy link

secana commented Jun 11, 2019

@Kalyxt Maybe https://github.com/secana/PeNet can do what you want (I'm the author). The PeNet library is able to extract the cert from signed files on Windows, Linux and Mac.

@Kalyxt
Copy link

Kalyxt commented Jun 11, 2019

@Kalyxt Maybe https://github.com/secana/PeNet can do what you want (I'm the author). The PeNet library is able to extract the cert from signed files on Windows, Linux and Mac.

Thanks! This is exactly what I was looking for. I've tested it on win and linux-arm.

@bartonjs
Copy link
Member Author

Is this going to be fixed in upcoming .NET Core versions (3.0)

No, there's not a lot of time left in the 3.0 release and I'm already over committed. (Though PRs are welcome if someone wants to contribute a fix...)

@secana
Copy link

secana commented Dec 20, 2019

Any updates on the issue regarding the version it will most likely be fixed in? It's not fixed in 3.1 and was already an issue in 2.0.
If someone points me to a starting point, I could try to implement it myself and do PR. Never worked with the dotnet repo, so any hints are welcome.

@bartonjs
Copy link
Member Author

I'll go ahead and mark it as 5 (aka "next version"). If you want to give it a try,

if (single)
{
// In single mode for a PKCS#7 signed or signed-and-enveloped file we're supposed to return
// the certificate which signed the PKCS#7 file.
//
// X509Certificate2Collection::Export(X509ContentType.Pkcs7) claims to be a signed PKCS#7,
// but doesn't emit a signature block. So this is hard to test.
//
// TODO(2910): Figure out how to extract the signing certificate, when it's present.
throw new CryptographicException(SR.Cryptography_X509_PKCS7_NoSigner);
}
is where things need to get smarter. Largely what's needed is a test suite. e.g. what happens on Windows with

  • SignedCMS, no signers
  • SignedCMS, multiple signers
  • SignedCMS, non-zero signers with countersigners
  • SignedCMS, signer, no embedded certificate, certificate present in CU\My store
    • If this is different from the next case it needs to be [OuterLoop] or just a manual test.
  • SignedCMS, signer, no embedded certificate, certificate not present in CU\My store

If you just want the functionality but aren't too keen on playing with pointers, that's fine... it'll still get taken care of this release.

@msftgits msftgits transferred this issue from dotnet/corefx Jan 31, 2020
@msftgits msftgits added this to the 5.0 milestone Jan 31, 2020
@maryamariyan maryamariyan added the untriaged New issue has not been triaged by the area owner label Feb 23, 2020
@bartonjs bartonjs removed untriaged New issue has not been triaged by the area owner help wanted [up-for-grabs] Good issue for external contributors labels Feb 25, 2020
@secana
Copy link

secana commented Apr 25, 2020

TODO(2910): Figure out how to extract the signing certificate, when it's present.
Getting the signing certificate on Linux from a PE file (taken from PeNet) is pretty straight forward.

Get the serial number of the signing certificate:

private string? GetSigningSerialNumber()
        {
            var asn1 = _contentInfo?.Content;
            if (asn1 is null) return null;
            var x = (Asn1Integer)asn1.Nodes[0].Nodes[4].Nodes[0].Nodes[1].Nodes[1]; // ASN.1 Path to signer serial number: /1/0/4/0/1/1
            return x.Value.ToHexString().Substring(2).ToUpper();
        }

Get the signing certificate from the PE file with the SignerSerialNumber from above.

private X509Certificate2 GetSigningCertificateNonWindows(PeFile peFile)
        {
            var collection = new X509Certificate2Collection();
            collection.Import(peFile.WinCertificate?.BCertificate.ToArray());
            return collection.Cast<X509Certificate2>().FirstOrDefault(cert =>
                string.Equals(cert.SerialNumber, SignerSerialNumber, StringComparison.CurrentCultureIgnoreCase));
        }

But I'm not sure on how to get access to the Asn1 of the PE file in the PkcsFormarReader.cs @bartonjs linked, so I'm stuck with any implementation attempt before I even get started :(

@bartonjs
Copy link
Member Author

bartonjs commented Apr 25, 2020

@secana There are two different issues.

  • Get the signature from a PKCS#7 file
  • Get the signature from an Authenticode file
    • It looks like years ago I bucketed those problems into this one, but as you saw, understanding how to find the signature in various file formats is problematic. This part wasn't in my set of expectations for .NET 5, since I had mis-remembered it as a separate issue.
    • But this depends on the first part.

@jeffhandley jeffhandley modified the milestones: 5.0.0, 6.0.0 Aug 14, 2020
0xced added a commit to 0xced/PeNet that referenced this issue Oct 4, 2020
On macOS, X509Certificate2Collection.Import() throws the following exception when importing PKCS#7 data:
> HResult = -25257  
Message = Unknown format in import.  
Source = System.Security.Cryptography.X509Certificates  
StackTrace = at Internal.Cryptography.Pal.X509Pal.AppleX509Pal.GetCertContentType(Byte[] rawData)  
   at System.Security.Cryptography.X509Certificates.X509Certificate2.GetCertContentType(Byte[] rawData)  
   at Internal.Cryptography.Pal.StorePal.FromBlob(Byte[] rawData, SafePasswordHandle password, X509KeyStorageFlags keyStorageFlags)  
   at System.Security.Cryptography.X509Certificates.X509Certificate2Collection.Import(Byte[] rawData, String password, X509KeyStorageFlags keyStorageFlags)  
   at System.Security.Cryptography.X509Certificates.X509Certificate2Collection.Import(Byte[] rawData)  
   at PeNet.Header.Authenticode.AuthenticodeInfo.GetSigningCertificateNonWindows(Byte[] pkcs7)
   
Using `SignedCms.Decode` instead of `X509Certificate2Collection.Import` as [suggested by Jeremy Barton][1] works on macOS thus making all the unit tests pass.

[1]: dotnet/runtime#15073 (comment)
0xced added a commit to 0xced/PeNet that referenced this issue Oct 4, 2020
On macOS, X509Certificate2Collection.Import() throws the following exception when importing PKCS#7 data:
> HResult = -25257  
Message = Unknown format in import.  
Source = System.Security.Cryptography.X509Certificates  
StackTrace = at Internal.Cryptography.Pal.X509Pal.AppleX509Pal.GetCertContentType(Byte[] rawData)  
   at System.Security.Cryptography.X509Certificates.X509Certificate2.GetCertContentType(Byte[] rawData)  
   at Internal.Cryptography.Pal.StorePal.FromBlob(Byte[] rawData, SafePasswordHandle password, X509KeyStorageFlags keyStorageFlags)  
   at System.Security.Cryptography.X509Certificates.X509Certificate2Collection.Import(Byte[] rawData, String password, X509KeyStorageFlags keyStorageFlags)  
   at System.Security.Cryptography.X509Certificates.X509Certificate2Collection.Import(Byte[] rawData)  
   at PeNet.Header.Authenticode.AuthenticodeInfo.GetSigningCertificateNonWindows(Byte[] pkcs7)
   
Using `SignedCms.Decode` instead of `X509Certificate2Collection.Import` as [suggested by Jeremy Barton][1] works on macOS thus making all the unit tests pass.

[1]: dotnet/runtime#15073 (comment)
@bartonjs bartonjs modified the milestones: 6.0.0, 7.0.0 Jul 21, 2021
@vcsjones
Copy link
Member

I think I can pick this one up to get X509Certificate(byte[] contents) handling PKCS#7/CMS data.

but in single certificate mode it doesn't return certs[0], it returns the certificate which signed the structure.

@bartonjs Do you know off the top of your head if it is actually validating anything? Maybe it isn't going so far as to validate a certificate chain or anything, but I wonder what happens if the EncapsulatedContentInfo has been tampered with. The testing scenarios for this are going to be complex regardless, so I suppose I will find out.

If we actually need to validate something, we can't add a reference to S.S.C.Pkcs due to circular references.

@bartonjs
Copy link
Member Author

Do you know off the top of your head if it is actually validating anything?

I'm like 98% sure that it's just a structural decode and checking if it's a signed CMS for Windows to report that it's CERT_QUERY_CONTENT_PKCS7_SIGNED. Once it's one of those, we just ask for the signerinfo, don't see any calls to verification: https://github.com/dotnet/runtime/blob/main/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/CertificatePal.Windows.Import.cs#L109-L141 (The hCertStore there represents the optional embedded certificates, nothing magical and dynamic with CU\My lookup or anything like that)

@jeffhandley jeffhandley modified the milestones: 7.0.0, Future Jul 9, 2022
@NGame1
Copy link

NGame1 commented Jul 13, 2022

@jeffhandley
OMG! After about 7 years of opening this issue. Again is it postponed?
No way!

@et1975
Copy link

et1975 commented Jan 26, 2023

@jeffhandley This issue was in the 7.0.0 bucket that's now marked as completed. Is the fix in .NET 7?

@vcsjones
Copy link
Member

This issue was in the 7.0.0 bucket that's now marked as completed. Is the fix in .NET 7?

No. This is currently marked "Future" and still open. This did not get complete for 7.0.

@bryandam
Copy link

bryandam commented Aug 11, 2023

I've been hitting this issue (I think), reading up on it, and am wondering if someone can confirm that I'm understanding the implications correctly.
Does this imply that there is currently no cross-platform way in .NET to sign files or verify file signatures? In this case, files signed in Windows can't be verified on Unix/Linux?

@vcsjones
Copy link
Member

Does this imply that there is currently no cross-platform way in .NET to sign files or verify file signatures? In this case, files signed in Windows can't be verified on Unix/Linux?

If you mean a file signed with Windows Authenticode, then correct - .NET does not provide an API to verify Authenticode signatures.

But new X509Certificate2(byte[]) on Windows does not do that either. It returns the signing cert, or the first cert-looking thing it can find in the file, but it does not actually check the signature.

@bryandam
Copy link

Thanks. Part of what I was struggling to determine was if all code signing in Windows was Authenticode or not as a lot of info just refers to 'digital signature'. In any case, I think it's safe to say that Window's signtool.exe is Authenticode.

Quite right, getting the cert from the file isn't validating the cert, that was a subsequent step I was taking. I just wanted to be sure that my actual goal of validating the file wasn't possible some other way that didn't necessitate actually having the cert object itself.

@vcsjones vcsjones removed their assignment Sep 5, 2023
@wstaelens
Copy link

what is the status of this? as we think we experience invalid exports of PKCS#7 in .net 7 and seeing this makes me believe things are not fully implemented yet?

@vcsjones
Copy link
Member

we think we experience invalid exports of PKCS#7 in .net 7

This issue is specifically about importing a PKCS#7 blob from X509Certificate{2} and it has not been implemented.

@wstaelens do you have a code snippet of what is not working for you?

@wstaelens
Copy link

wstaelens commented Nov 28, 2023

@vcsjones We tried several things. Maybe missing something, not sure how to get it working.

  static void ExportTo(X509Certificate2 cert, string location, string baseFileName, string pwd)
    {
      // Create PFX (PKCS) with private key
      File.WriteAllBytes(Path.Combine(location, baseFileName + ".pfx"), cert.Export(X509ContentType.Pfx, pwd));
      var collection = new X509Certificate2Collection(cert);
      File.WriteAllBytes(Path.Combine(location, baseFileName + ".pk7"), collection.Export(X509ContentType.Pkcs7));
 
      // Create Base 64 encoded CER (public key only)
      File.WriteAllText(Path.Combine(location, baseFileName + ".cer"),
          "-----BEGIN CERTIFICATE-----\r\n"
          + Convert.ToBase64String(cert.Export(X509ContentType.Cert), Base64FormattingOptions.InsertLineBreaks)
          + "\r\n-----END CERTIFICATE-----");
    }

If we do cert.export(pkcs7 this does not work, doesn't seem to be supported?

If we put it in a collection and export it the file is about 30% smaller and when importing it it isn't valid (can't debug it is being loaded into an external device).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-System.Security bug disabled-test The test is disabled in source code against the issue
Projects
None yet
Development

No branches or pull requests