How do you parse the Subject Alternate Names from an X509Certificate2?

To get the "Subject Alternative Name" from a certificate:

X509Certificate2 cert = /* your code here */;

Console.WriteLine("UpnName : {0}{1}", cert.GetNameInfo(X509NameType.UpnName, false), Environment.NewLine);

Use the Format method of the extension for a printable version.

X509Certificate2 cert = /* your code here */;

foreach (X509Extension extension in cert.Extensions)
{
    // Create an AsnEncodedData object using the extensions information.
    AsnEncodedData asndata = new AsnEncodedData(extension.Oid, extension.RawData);
    Console.WriteLine("Extension type: {0}", extension.Oid.FriendlyName);
    Console.WriteLine("Oid value: {0}",asndata.Oid.Value);
    Console.WriteLine("Raw data length: {0} {1}", asndata.RawData.Length, Environment.NewLine);
    Console.WriteLine(asndata.Format(true));
}

Here's a solution that does not require parsing the text returned by AsnEncodedData.Format() (but requires .NET 5):

using System.Formats.Asn1;

...

public static List<string> GetAlternativeDnsNames(X509Certificate2 cert)
{
    const string SAN_OID = "2.5.29.17";

    var extension = cert.Extensions[SAN_OID];
    if (extension is null)
    {
        return new List<string>();
    }

    // Tag value "2" is defined by:
    //
    //    dNSName                         [2]     IA5String,
    //
    // in: https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.6
    var dnsNameTag = new Asn1Tag(TagClass.ContextSpecific, tagValue: 2, isConstructed: false);

    var asnReader = new AsnReader(extension.RawData, AsnEncodingRules.BER);
    var sequenceReader = asnReader.ReadSequence(Asn1Tag.Sequence);

    var resultList = new List<string>();

    while (sequenceReader.HasData)
    {
        var tag = sequenceReader.PeekTag();
        if (tag != dnsNameTag)
        {
            sequenceReader.ReadEncodedValue();
            continue;
        }

        var dnsName = sequenceReader.ReadCharacterString(UniversalTagNumber.IA5String, dnsNameTag);
        resultList.Add(dnsName);
    }

    return resultList;
}

Based on the answer from Minh, here is a self-contained static function that should return them all

    public static IEnumerable<string> ParseSujectAlternativeNames(X509Certificate2 cert)
    {
        Regex sanRex = new Regex(@"^DNS Name=(.*)", RegexOptions.Compiled | RegexOptions.CultureInvariant);

        var sanList = from X509Extension ext in cert.Extensions
                      where ext.Oid.FriendlyName.Equals("Subject Alternative Name", StringComparison.Ordinal)
                      let data = new AsnEncodedData(ext.Oid, ext.RawData)
                      let text = data.Format(true)
                      from line in text.Split(new char[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries)
                      let match = sanRex.Match(line)
                      where match.Success && match.Groups.Count > 0 && !string.IsNullOrEmpty(match.Groups[1].Value)
                      select match.Groups[1].Value;

        return sanList;
    }