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

[Breaking change]: X509Certificate2, X509Certificate constructors and X509Certificate2Collection.Import are obsolete for binary and file content #41662

Closed
1 of 3 tasks
vcsjones opened this issue Jul 5, 2024 · 25 comments · Fixed by #42018
Assignees
Labels
breaking-change Indicates a .NET Core breaking change doc-idea Indicates issues that are suggestions for new topics [org][type][category] in-pr This issue will be closed (fixed) by an active pull request. Pri1 High priority, do before Pri2 and Pri3 📌 seQUESTered Identifies that an issue has been imported into Quest. source incompatible Source code may encounter a breaking change in behavior when targeting the new version.

Comments

@vcsjones
Copy link
Member

vcsjones commented Jul 5, 2024

Description

The constructors on X509Certificate and X509Certificate2 that accept content as a byte[], ReadOnlySpan<byte>, or a string file path have been marked obsolete.

The Import method and all overloads on X509Certificate2Collection have also been marked obsolete.

Version

.NET 9 Preview 7

Previous behavior

Developers could use those APIs without an obsolete warning.

New behavior

Affected APIs will receive an obsolete compilation warning with SYSLIB0057.

Type of breaking change

  • Binary incompatible: Existing binaries may encounter a breaking change in behavior, such as failure to load or execute, and if so, require recompilation.
  • Source incompatible: When recompiled using the new SDK or component or to target the new runtime, existing source code may require source changes to compile successfully.
  • Behavioral change: Existing binaries may behave differently at run time.

Reason for change

The affected APIs supported loading certificates in multiple formats. For example, new X509Certificate2(data) would load a certificate from a byte[] called data. This data could be one of any supported format, including X.509, PKCS7, or PKCS12/PFX.

While this was easy to use, it created issues where user-supplied data is passed with a different format than intended. This may allow loading PKCS12 where only X.509 content was intended to be loaded, or create interoperability issues from handling the data in different ways.

Recommended action

Developers should use a different API to load certificate content, depending on the intended content type.

A new class called X509CertificateLoader can be used to load X.509 or PKCS12 content.

  • If you are loading X.509 content, X509CertificateLoader.LoadCertificate and X509CertificateLoader.LoadCertificateFromFile can be used.
  • If you are loading PKCS12 content, X509CertificateLoader.LoadPkcs12, X509CertificateLoader.LoadPkcs12FromFile, X509CertificateLoader.LoadPkcs12Collection, and X509CertificateLoader.LoadPkcs12CollectionFromFile can be used.
  • If you are loading PKCS7 content, SignedCms from the System.Security.Cryptography.Pkcs package can be used to inspect certificates in PKCS7 content.

If you are uncertain about the content type you are loading, X509Certificate2.GetCertContentType can be used to determine the content type and call the appropriate API.

You may also suppress the obsoletion using #pragma warning disable SYSLIB0057 and #pragma warning restore SYSLIB0057 around the affected code to continue using the legacy certificate loading APIs.

The Microsoft.Bcl.Cryptography package supplies X509CertificateLoader for .NET Framework and .NET Standard.

Feature area

Cryptography

Affected APIs

Affected Doc IDs:

  • M:System.Security.Cryptography.X509Certificates.X509Certificate2.#ctor(System.Byte[])
  • M:System.Security.Cryptography.X509Certificates.X509Certificate2.#ctor(System.ReadOnlySpan{System.Byte})
  • M:System.Security.Cryptography.X509Certificates.X509Certificate2.#ctor(System.String)
  • M:System.Security.Cryptography.X509Certificates.X509Certificate2.#ctor(System.Byte[],System.Security.SecureString)
  • M:System.Security.Cryptography.X509Certificates.X509Certificate2.#ctor(System.Byte[],System.String)
  • M:System.Security.Cryptography.X509Certificates.X509Certificate2.#ctor(System.String,System.Security.SecureString)
  • M:System.Security.Cryptography.X509Certificates.X509Certificate2.#ctor(System.String,System.String)
  • M:System.Security.Cryptography.X509Certificates.X509Certificate2.#ctor(System.Byte[],System.Security.SecureString,System.Security.Cryptography.X509Certificates.X509KeyStorageFlags)
  • M:System.Security.Cryptography.X509Certificates.X509Certificate2.#ctor(System.Byte[],System.String,System.Security.Cryptography.X509Certificates.X509KeyStorageFlags)
  • M:System.Security.Cryptography.X509Certificates.X509Certificate2.#ctor(System.ReadOnlySpan{System.Byte},System.ReadOnlySpan{System.Char},System.Security.Cryptography.X509Certificates.X509KeyStorageFlags)
  • M:System.Security.Cryptography.X509Certificates.X509Certificate2.#ctor(System.String,System.ReadOnlySpan{System.Char},System.Security.Cryptography.X509Certificates.X509KeyStorageFlags)
  • M:System.Security.Cryptography.X509Certificates.X509Certificate2.#ctor(System.String,System.Security.SecureString,System.Security.Cryptography.X509Certificates.X509KeyStorageFlags)
  • M:System.Security.Cryptography.X509Certificates.X509Certificate2.#ctor(System.String,System.String,System.Security.Cryptography.X509Certificates.X509KeyStorageFlags)

  • M:System.Security.Cryptography.X509Certificates.X509Certificate.#ctor(System.Byte[])
  • M:System.Security.Cryptography.X509Certificates.X509Certificate.#ctor(System.String)
  • M:System.Security.Cryptography.X509Certificates.X509Certificate.#ctor(System.Byte[],System.Security.SecureString)
  • M:System.Security.Cryptography.X509Certificates.X509Certificate.#ctor(System.Byte[],System.String)
  • M:System.Security.Cryptography.X509Certificates.X509Certificate.#ctor(System.String,System.String,System.Security.Cryptography.X509Certificates.X509KeyStorageFlags)
  • M:System.Security.Cryptography.X509Certificates.X509Certificate.#ctor(System.String,System.Security.SecureString)
  • M:System.Security.Cryptography.X509Certificates.X509Certificate.#ctor(System.String,System.String)
  • M:System.Security.Cryptography.X509Certificates.X509Certificate.#ctor(System.Byte[],System.Security.SecureString,System.Security.Cryptography.X509Certificates.X509KeyStorageFlags)
  • M:System.Security.Cryptography.X509Certificates.X509Certificate.#ctor(System.Byte[],System.String,System.Security.Cryptography.X509Certificates.X509KeyStorageFlags)
  • M:System.Security.Cryptography.X509Certificates.X509Certificate.#ctor(System.String,System.Security.SecureString,System.Security.Cryptography.X509Certificates.X509KeyStorageFlags)
  • M:System.Security.Cryptography.X509Certificates.X509Certificate.#ctor(System.String,System.String,System.Security.Cryptography.X509Certificates.X509KeyStorageFlags)

  • M:System.Security.Cryptography.X509Certificates.X509Certificate2Collection.Import(System.Byte[])
  • M:System.Security.Cryptography.X509Certificates.X509Certificate2Collection.Import(System.ReadOnlySpan{System.Byte})
  • M:System.Security.Cryptography.X509Certificates.X509Certificate2Collection.Import(System.ReadOnlySpan{System.Byte})
  • M:System.Security.Cryptography.X509Certificates.X509Certificate2Collection.Import(System.Byte[],System.String,System.Security.Cryptography.X509Certificates.X509KeyStorageFlags)
  • M:System.Security.Cryptography.X509Certificates.X509Certificate2Collection.Import(System.ReadOnlySpan{System.Byte},System.ReadOnlySpan{System.Char},System.Security.Cryptography.X509Certificates.X509KeyStorageFlags)
  • M:System.Security.Cryptography.X509Certificates.X509Certificate2Collection.Import(System.ReadOnlySpan{System.Byte},System.String,System.Security.Cryptography.X509Certificates.X509KeyStorageFlags)
  • M:System.Security.Cryptography.X509Certificates.X509Certificate2Collection.Import(System.String,System.ReadOnlySpan{System.Char},System.Security.Cryptography.X509Certificates.X509KeyStorageFlags)
  • M:System.Security.Cryptography.X509Certificates.X509Certificate2Collection.Import(System.String,System.String,System.Security.Cryptography.X509Certificates.X509KeyStorageFlags)

  • "M:System.Security.Cryptography.X509Certificates.X509Certificate.CreateFromSignedFile(System.String)

Associated WorkItem - 292111

@vcsjones vcsjones added doc-idea Indicates issues that are suggestions for new topics [org][type][category] breaking-change Indicates a .NET Core breaking change Pri1 High priority, do before Pri2 and Pri3 labels Jul 5, 2024
@dotnet-bot dotnet-bot added ⌚ Not Triaged Not triaged source incompatible Source code may encounter a breaking change in behavior when targeting the new version. labels Jul 5, 2024
@vcsjones vcsjones changed the title [Breaking change]: X509Certificate2, X509Certificate constructors and X509Certificate2Collection.Import are obsolete [Breaking change]: X509Certificate2, X509Certificate constructors and X509Certificate2Collection.Import are obsolete for binary and file content Jul 5, 2024
@vcsjones
Copy link
Member Author

vcsjones commented Jul 5, 2024

The content and messaging of this should get sign-off from @bartonjs first.

@bartonjs
Copy link
Member

bartonjs commented Jul 5, 2024

I tweaked the message a little bit, LGTM.

@akoeplinger
Copy link
Member

@bartonjs reading dotnet/runtime#91763 it looks like the Authenticode pieces were cut, is the intention for those to keep using X509Certificate2 ctor and suppress the warning?

Here's code from the SDK where we use this and I had to suppress:
https://github.com/dotnet/sdk/blob/70479edb89ef1820051e951c4977f5fdb153f8e1/src/Cli/dotnet/Installer/Windows/Security/Signature.cs#L32-L35

@akoeplinger
Copy link
Member

Also there's quite a lot of code that uses the approach shown in https://stackoverflow.com/questions/72096812/loading-x509certificate2-from-pem-file-results-in-no-credentials-are-available/72101855#72101855 to export+import a cert to workaround some Windows issue, is that contuing to work with export+X509CertificateLoader.LoadCertificate ?

@bartonjs
Copy link
Member

bartonjs commented Jul 8, 2024

reading dotnet/runtime#91763 it looks like the Authenticode pieces were cut, is the intention for those to keep using X509Certificate2 ctor and suppress the warning?

More or less. For anyone still using that ctor, but expecting a certain content type, the recommendation is to do something like

if (X509Certificate2.GetCertContentType(input) != X509ContentType.TheOneIExpect)
{
    throw new CryptographicException();
}

#pragma warning disable SYSLIB0057
X509Certificate2 cert = new(input);
#pragma warning restore

@bartonjs
Copy link
Member

bartonjs commented Jul 8, 2024

export+import a cert to workaround some Windows issue, is that contuing to work with export+X509CertificateLoader.LoadCertificate

Yeah, just now it's X509CertificateLoader.LoadPkcs12(cert.Export(X509ContentType.Pkcs12, null))

@akoeplinger
Copy link
Member

akoeplinger commented Jul 8, 2024

Ok. It's a bit sad that we have to suppress the warning for the Authenticode case but so be it :)
Probably good to mention this in the doc somewhere.

@ScotMac
Copy link

ScotMac commented Jul 11, 2024

export+import a cert to workaround some Windows issue, is that contuing to work with export+X509CertificateLoader.LoadCertificate

Yeah, just now it's X509CertificateLoader.LoadPkcs12(cert.Export(X509ContentType.Pkcs12, null))

@bartonjs HI Jeremy, i believe that question you quoted was in reference to PEM files. How do we get an x509Certificate2Collection for a PEM file with multiple certs/keys that has been read in and is now in a byte[]?

Thanks.

@bartonjs
Copy link
Member

bartonjs commented Jul 12, 2024

@ScotMac If you actually mean PEM when you say PEM, then that's X509Certificate2Collection.ImportFromPem, which is not being marked as obsolete.

If you meant PFX when you said PEM, that's X509CertificateLoader.LoadPkcs12Collection.

@bartonjs
Copy link
Member

Oh, I slightly lied. The Collection ImportFromPem won't import private keys. There's no API for loading multiple certificates and multiple private keys from PEM. Only PFX supports multiple private keys.

@ScotMac
Copy link

ScotMac commented Jul 12, 2024

@bartonjs Thanks Jeremy.

Yes, it appears that API will only read in public key certs. Is there no .NET API for parsing PEM data that contains at least one private key, and possibly multiple public key certs? Thus providing a way to get the key/certs necessary to estabish an mTLS connection (including the validation of the server cert), without using MCS (cert stores).

@ScotMac
Copy link

ScotMac commented Jul 12, 2024

And yes, i meant PEM.

But thanks for that p12 load function, since we are currently using x509Certificate2Collection.Import() for p12, which appears to be obsoleted by this notice.

@gewarren gewarren added the 🗺️ reQUEST Triggers an issue to be imported into Quest. label Aug 1, 2024
@dotnet-bot dotnet-bot removed the ⌚ Not Triaged Not triaged label Aug 1, 2024
@sequestor sequestor bot added 📌 seQUESTered Identifies that an issue has been imported into Quest. and removed 🗺️ reQUEST Triggers an issue to be imported into Quest. labels Aug 1, 2024
@gewarren gewarren moved this from 🔖 Ready to 🏗 In progress in dotnet/docs August 2024 Sprint Aug 2, 2024
@gewarren gewarren moved this from 🏗 In progress to 👀 In review in dotnet/docs August 2024 Sprint Aug 2, 2024
@dotnet-policy-service dotnet-policy-service bot added the in-pr This issue will be closed (fixed) by an active pull request. label Aug 2, 2024
@github-project-automation github-project-automation bot moved this from 👀 In review to ✅ Done in dotnet/docs August 2024 Sprint Aug 6, 2024
@ScotMac
Copy link

ScotMac commented Oct 18, 2024

@bartonjs Hi Jeremy, it appears that the replacement functions you suggested, LoadPkcs12 and LoadPkcs12Collection, are part of the nuget package Microsoft.Bcl.Cryptography. Are there any plans to make them "built-in" to .NET?

Also, Microsoft.Bcl.Cryptography original purpose was to add support in older .NET versions that didn't have the newer cryptography support. That is not the case here, where this is instead Core functionality for .NET that was obsoleted.

@vcsjones
Copy link
Member Author

@ScotMac

Are there any plans to make them "built-in" to .NET?

They are built in to .NET 9. You don't need the package if you are targeting .NET 9+. Microsoft.Bcl.Cryptography is only needed if you want the API down level, which might make multi-targeting easier.

@sailro
Copy link

sailro commented Nov 13, 2024

What is the recommended way of replacing the now obsolete X509Certificate.CreateFromSignedFile (able to extract a certificate from a signed dll/exe file).

Because X509CertificateLoader.LoadCertificateFromFile is doing a complete different stuff => loads a single certificate in pem/dem encoding.

@bdovaz
Copy link

bdovaz commented Nov 13, 2024

What is the recommended way of replacing the now obsolete X509Certificate.CreateFromSignedFile (able to extract a certificate from a signed dll/exe file).

Because X509CertificateLoader.LoadCertificateFromFile is doing a complete different stuff => loads a single certificate in pem/dem encoding.

Same problem here...

@vcsjones
Copy link
Member Author

For extracting "Authenticode" things, the new cert loader does not currently handle that. You should

  1. Use GetCertContentType to check the payload is actually authenticode.
  2. Suppress the obsoletion and use the constructor.

This comment from the preview 7 release notes shows an example: https://github.com/dotnet/core/blob/main/release-notes/9.0/preview/preview7/libraries.md#changes-to-x509-certificate-loading

But, essentially:

X509ContentType actualType = X509Certificate2.GetCertContentType(data);

if (actualType != X509ContentType.Authenticode)
{
    throw new CryptographicException();
}

#pragma warning disable SYSLIB0057
using X509Certificate2 cert = new X509Certificate2(data);
#pragma warning restore SYSLIB0057

// use cert

@bdovaz
Copy link

bdovaz commented Nov 14, 2024

@vcsjones how is it possible that there isn't an alternative that doesn't involve making use of the deprecated API and adding a supress? 💩

@ScotMac
Copy link

ScotMac commented Nov 14, 2024

What is the recommended way of replacing the now obsolete X509Certificate.CreateFromSignedFile (able to extract a certificate from a signed dll/exe file).

Because X509CertificateLoader.LoadCertificateFromFile is doing a complete different stuff => loads a single certificate in pem/dem encoding.

Yes, exactly what we need. I way to load all the certificates in a particular cert store file, both for PEM and P12.

@bartonjs
Copy link
Member

how is it possible that there isn't an alternative that doesn't involve making use of the deprecated API and adding a suppress?

Mostly because we thought almost no one was using that particular aspect of the API. That has proven incorrect. (However, no one has yet asked for any of the other formats that we dropped). It also turns out the feature is wrong "less good than it could be". Authenticode isn't only a single signer (some/many/most/all Microsoft-signed libraries are signed twice, once with Windows XP compatibility, and once with more modern choices), but the API only returns one (and for the Microsoft-signed ones, it's the older one, probably not the one you want).

@bartonjs
Copy link
Member

I way to load all the certificates in a particular cert store file, both for PEM and P12.

You'll have to test the file yourself and then route it either to X509CertificateLoader.LoadPkcs12Collection or X509Certificate2Collection.ImportFromPem. Hint: A PFX will always start with the byte 0x30, and a PEM sequence never will (assuming you're not trying to do a hybrid PEM/binary thing).

I don't forsee us adding any more "content-sniffing" loaders, at least not in the cryptography space.

@Frostchi
Copy link

Frostchi commented Nov 15, 2024

What namespace is X509CertificateLoader supposed to be in? I can't find it anywhere...

EDIT: Found it Microsoft.Bcl.Cryptography

@ScotMac
Copy link

ScotMac commented Nov 15, 2024

@bartonjs Sorry, if i wasn't clear. I meant separate files. ie, an API for loading multiple certs in a p12 file into a X509Certificate2Collection, and a separate API for loading multiple certs in a PEM file into a x509Certificate2collection.

@ScotMac
Copy link

ScotMac commented Nov 15, 2024

OK, so for a PEM file, Jeremy's suggestion was X509Certificate2Collection.ImportFromPem(ReadOnlySpan). However, the problem with that API is that it will only load the public certs in the file (hence the fact that it doesn't take a password). From the doc page for it:

"PEM-encoded items with a CERTIFICATE PEM label will be imported. PEM items with other labels will be ignored."

However, it does give us half the work needed: the public key certs.

So then, can we use the following to get the private key (password protected) cert:

X509Certificate2.CreateFromEncryptedPemFile()

So, then a combination of the above two methods might solve my problem, for PEM encoded files, giving me the public certs and the private key cert.

@bdovaz
Copy link

bdovaz commented Nov 18, 2024

how is it possible that there isn't an alternative that doesn't involve making use of the deprecated API and adding a suppress?

Mostly because we thought almost no one was using that particular aspect of the API. That has proven incorrect. (However, no one has yet asked for any of the other formats that we dropped). It also turns out the feature is wrong "less good than it could be". Authenticode isn't only a single signer (some/many/most/all Microsoft-signed libraries are signed twice, once with Windows XP compatibility, and once with more modern choices), but the API only returns one (and for the Microsoft-signed ones, it's the older one, probably not the one you want).

This means that we are going to have this warning supress until dotnet 10 is released? Assuming that this case will be taken into account in 10 of course ....

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
breaking-change Indicates a .NET Core breaking change doc-idea Indicates issues that are suggestions for new topics [org][type][category] in-pr This issue will be closed (fixed) by an active pull request. Pri1 High priority, do before Pri2 and Pri3 📌 seQUESTered Identifies that an issue has been imported into Quest. source incompatible Source code may encounter a breaking change in behavior when targeting the new version.
Projects
No open projects
Status: ✅ Done
Development

Successfully merging a pull request may close this issue.

9 participants