Skip to content
ecdsa.com

Decode a certificate · .NET

How to decode an X.509 certificate in C# (.NET)

To decode an X.509 certificate in C#, load it with X509CertificateLoader (.NET 9 and later) and read the X509Certificate2 properties — subject, issuer, validity, serial, extensions and the typed public key. This recipe prints the usual fields of an ECDSA certificate, its SHA-256 fingerprint, and validates a chain against a custom root with X509Chain.

Tested with .NET 10.0.13 (SDK 10.0.402) (Linux, OpenSSL 3.0.13 backend) and OpenSSL 3.0.13 (test CA and leaf certificate).

decode.cs — fields, SANs, key usage, key and fingerprint
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
// .NET 9+: X509CertificateLoader replaces the obsolete X509Certificate2 constructors
using X509Certificate2 cert = X509CertificateLoader.LoadCertificateFromFile("cert.pem"); // PEM or DER
Console.WriteLine($"subject: {cert.Subject}");
Console.WriteLine($"common name: {cert.GetNameInfo(X509NameType.SimpleName, forIssuer: false)}");
Console.WriteLine($"issuer: {cert.Issuer}");
Console.WriteLine($"serial: {cert.SerialNumber}");
Console.WriteLine($"valid: {cert.NotBefore.ToUniversalTime():O} -> {cert.NotAfter.ToUniversalTime():O}");
Console.WriteLine($"signed with: {cert.SignatureAlgorithm.FriendlyName}"); // the issuer's algorithm
foreach (var san in cert.Extensions.OfType<X509SubjectAlternativeNameExtension>())
{
foreach (string dns in san.EnumerateDnsNames()) Console.WriteLine($"DNS name: {dns}");
}
foreach (var usage in cert.Extensions.OfType<X509KeyUsageExtension>())
{
Console.WriteLine($"key usage: {usage.KeyUsages}");
}
using ECDsa? key = cert.GetECDsaPublicKey(); // null for RSA or Ed25519 certificates
if (key is not null)
{
Console.WriteLine($"key: ECDSA {key.KeySize}-bit, curve {key.ExportParameters(false).Curve.Oid.Value}");
}
Console.WriteLine($"SHA-256 fingerprint: {cert.GetCertHashString(HashAlgorithmName.SHA256)}");
chain.cs — validate against your own root instead of the system store
// Validate against your own trust anchor instead of the machine's root store
using X509Certificate2 root = X509CertificateLoader.LoadCertificateFromFile("ca.pem");
using X509Certificate2 leaf = X509CertificateLoader.LoadCertificateFromFile("leaf.pem");
using var chain = new X509Chain();
chain.ChainPolicy.TrustMode = X509ChainTrustMode.CustomRootTrust;
chain.ChainPolicy.CustomTrustStore.Add(root);
chain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck; // a test CA publishes no CRL or OCSP
bool trusted = chain.Build(leaf);
Console.WriteLine($"chain trusted: {trusted}, {chain.ChainElements.Count} certificates");
foreach (X509ChainStatus status in chain.ChainStatus) Console.WriteLine($"status: {status.Status}");

How it works

  1. X509CertificateLoader.LoadCertificateFromFile accepts PEM and DER. For PKCS#12 files it has a separate LoadPkcs12FromFile, so a certificate path can never accidentally load a private key.
  2. Subject and Issuer are display strings; GetNameInfo(X509NameType.SimpleName, false) extracts the common name without string parsing.
  3. Extensions come typed: X509SubjectAlternativeNameExtension.EnumerateDnsNames() (.NET 7+) lists SAN DNS names, X509KeyUsageExtension.KeyUsages is a flags enum.
  4. GetECDsaPublicKey() returns an ECDsa ready for VerifyData, or null when the certificate holds another key type.
  5. X509Chain with CustomRootTrust builds and validates the path to the root you supply; ChainStatus explains a failure (UntrustedRoot, NotTimeValid, PartialChain …).

Gotchas

  • NotBefore and NotAfter are DateTime values in local time (Kind == Local). Convert with ToUniversalTime() before printing or comparing with UTC timestamps, as above.
  • new X509Certificate2(path) and new X509Certificate2(bytes) are obsolete since .NET 9 (warning SYSLIB0057): they silently accept PKCS#12 and other formats. X509CertificateLoader loads exactly one format per method.
  • SignatureAlgorithm describes the issuer's signature over this certificate (sha256ECDSA), not what the certificate's key can do.
  • On Linux, X509Chain fetches missing intermediates and revocation data over the network unless told otherwise. In tests and air-gapped hosts set RevocationMode = NoCheck deliberately, never in production validation.

Related recipes