Search code examples
c#.netcryptographybouncycastlecsr

How to add Subject Alternative Name (SAN) To a CSR Using BouncyCastle C#


I'm trying to generate a Certificate Signing Request (CSR) using the BouncyCastle library in C#. My goal is to include Subject alternative names in the CSR, but I'm encountering an error related to the format of extensions. Specifically, I'm trying to pass a Dictionary<DerObjectIdentifier, X509Extension> as extensions to the Pkcs10CertificationRequest constructor, but it expects an Asn1Set.

When decoding the CSR, it should look like this:

enter image description here

CSR detailed information:

Certificate Request:
    Data:
        Version: 1 (0x0)
        Subject: C=SA, OU=amman Branchch, O=haya yag 3, CN=127.0.0.1
        Subject Public Key Info:
            Public Key Algorithm: id-ecPublicKey
                Public-Key: (256 bit)
                pub:
                    04:db:8a:b6:27:ff:2b:ff:3a:c1:e1:16:53:0f:bc:
                    57:11:c4:8d:e7:b6:e9:35:8b:0b:62:94:d6:2e:57:
                    6f:bd:e6:49:c3:02:c8:5b:e4:e1:6c:21:80:87:a3:
                    f9:0b:d3:42:af:c4:bb:38:fa:cf:ab:d6:0f:2f:a9:
                    48:29:a2:4f:17
                ASN1 OID: secp256k1
        Attributes:
            Requested Extensions:
                1.3.6.1.4.1.311.20.2: 
                    ..TSTZATCA-Code-Signing
                X509v3 Subject Alternative Name: 
                    DirName:/SN=1-haya|2-234|3-354/UID=310175397400003/title=1100/registeredAddress=Zatca 3/businessCategory=Food Business3
    Signature Algorithm: ecdsa-with-SHA256
    Signature Value:
        30:44:02:20:2a:eb:3b:b9:8a:e9:57:ba:30:d6:fe:25:22:05:
        0d:ff:80:17:6f:23:59:f2:1a:dc:7c:3d:69:71:94:f7:f2:67:
        02:20:2b:26:95:2c:1f:18:13:93:c9:0f:7b:83:c9:b0:84:db:
        21:ac:92:c8:7b:f3:7e:9b:6a:10:c7:c3:8f:b3:fb:6b


(Decoded using the following version of OpenSSL: OpenSSL 3.1.1 30 May 2023)

CSR ASN.1 information:

  0 459: SEQUENCE {
  4 370:   SEQUENCE {
  8   1:     INTEGER 0
 11  79:     SEQUENCE {
 13  11:       SET {
 15   9:         SEQUENCE {
 17   3:           OBJECT IDENTIFIER countryName (2 5 4 6)
 22   2:           PrintableString 'SA'
       :           }
       :         }
 26  23:       SET {
 28  21:         SEQUENCE {
 30   3:           OBJECT IDENTIFIER organizationalUnitName (2 5 4 11)
 35  14:           UTF8String 'amman Branchch'
       :           }
       :         }
 51  19:       SET {
 53  17:         SEQUENCE {
 55   3:           OBJECT IDENTIFIER organizationName (2 5 4 10)
 60  10:           UTF8String 'haya yag 3'
       :           }
       :         }
 72  18:       SET {
 74  16:         SEQUENCE {
 76   3:           OBJECT IDENTIFIER commonName (2 5 4 3)
 81   9:           UTF8String '127.0.0.1'
       :           }
       :         }
       :       }
 92  86:     SEQUENCE {
 94  16:       SEQUENCE {
 96   7:         OBJECT IDENTIFIER ecPublicKey (1 2 840 10045 2 1)
105   5:         OBJECT IDENTIFIER secp256k1 (1 3 132 0 10)
       :         }
112  66:       BIT STRING
       :         04 DB 8A B6 27 FF 2B FF 3A C1 E1 16 53 0F BC 57
       :         11 C4 8D E7 B6 E9 35 8B 0B 62 94 D6 2E 57 6F BD
       :         E6 49 C3 02 C8 5B E4 E1 6C 21 80 87 A3 F9 0B D3
       :         42 AF C4 BB 38 FA CF AB D6 0F 2F A9 48 29 A2 4F
       :         17
       :       }
180 195:     [0] {
183 192:       SEQUENCE {
186   9:         OBJECT IDENTIFIER extensionRequest (1 2 840 113549 1 9 14)
197 178:         SET {
200 175:           SEQUENCE {
203  36:             SEQUENCE {
205   9:               OBJECT IDENTIFIER
       :                 enrollCerttypeExtension (1 3 6 1 4 1 311 20 2)
216  23:               OCTET STRING
       :                 13 15 54 53 54 5A 41 54 43 41 2D 43 6F 64 65 2D
       :                 53 69 67 6E 69 6E 67
       :               }
241 134:             SEQUENCE {
244   3:               OBJECT IDENTIFIER subjectAltName (2 5 29 17)
249 127:               OCTET STRING
       :                 30 7D A4 7B 30 79 31 1B 30 19 06 03 55 04 04 0C
       :                 12 31 2D 68 61 79 61 7C 32 2D 32 33 34 7C 33 2D
       :                 33 35 34 31 1F 30 1D 06 0A 09 92 26 89 93 F2 2C
       :                 64 01 01 0C 0F 33 31 30 31 37 35 33 39 37 34 30
       :                 30 30 30 33 31 0D 30 0B 06 03 55 04 0C 0C 04 31
       :                 31 30 30 31 10 30 0E 06 03 55 04 1A 0C 07 5A 61
       :                 74 63 61 20 33 31 18 30 16 06 03 55 04 0F 0C 0F
       :                 46 6F 6F 64 20 42 75 73 73 69 6E 65 73 73 33
       :               }
       :             }
       :           }
       :         }
       :       }
       :     }
378  10:   SEQUENCE {
380   8:     OBJECT IDENTIFIER ecdsaWithSHA256 (1 2 840 10045 4 3 2)
       :     }
390  71:   BIT STRING
       :     30 44 02 20 2A EB 3B B9 8A E9 57 BA 30 D6 FE 25
       :     22 05 0D FF 80 17 6F 23 59 F2 1A DC 7C 3D 69 71
       :     94 F7 F2 67 02 20 2B 26 95 2C 1F 18 13 93 C9 0F
       :     7B 83 C9 B0 84 DB 21 AC 92 C8 7B F3 7E 9B 6A 10
       :     C7 C3 8F B3 FB 6B
       :   }

You can use this CSR as a reference:

Website to Decode

-----BEGIN CERTIFICATE REQUEST-----
MIIByzCCAXICAQAwTzELMAkGA1UEBhMCU0ExFzAVBgNVBAsMDmFtbWFuIEJyYW5j
aGNoMRMwEQYDVQQKDApoYXlhIHlhZyAzMRIwEAYDVQQDDAkxMjcuMC4wLjEwVjAQ
BgcqhkjOPQIBBgUrgQQACgNCAATbirYn/yv/OsHhFlMPvFcRxI3ntuk1iwtilNYu
V2+95knDAshb5OFsIYCHo/kL00KvxLs4+s+r1g8vqUgpok8XoIHDMIHABgkqhkiG
9w0BCQ4xgbIwga8wJAYJKwYBBAGCNxQCBBcTFVRTVFpBVENBLUNvZGUtU2lnbmlu
ZzCBhgYDVR0RBH8wfaR7MHkxGzAZBgNVBAQMEjEtaGF5YXwyLTIzNHwzLTM1NDEf
MB0GCgmSJomT8ixkAQEMDzMxMDE3NTM5NzQwMDAwMzENMAsGA1UEDAwEMTEwMDEQ
MA4GA1UEGgwHWmF0Y2EgMzEYMBYGA1UEDwwPRm9vZCBCdXNzaW5lc3MzMAoGCCqG
SM49BAMCA0cAMEQCICrrO7mK6Ve6MNb+JSIFDf+AF28jWfIa3Hw9aXGU9/JnAiAr
JpUsHxgTk8kPe4PJsITbIaySyHvzfptqEMfDj7P7aw==
-----END CERTIFICATE REQUEST-----

Here's the relevant code:

public static string GeneratePkcs10ECDSA(string commonName, string organizationUnitName, string organizationName, string country, string sanDirName)
{
    var csr = "";
    try
    {
        // Create ECDSA key pair generator
        var ecKeyPairGenerator = new ECKeyPairGenerator();
        var genParam = new ECKeyGenerationParameters(SecObjectIdentifiers.SecP256k1, new SecureRandom());
        ecKeyPairGenerator.Init(genParam);

        AsymmetricCipherKeyPair pair = ecKeyPairGenerator.GenerateKeyPair();
        AsymmetricCipherKeyPair ecKeyPair = ecKeyPairGenerator.GenerateKeyPair();
        // Subject Name
        var subjectAttrs = new List<DerObjectIdentifier>();
        var subjectValues = new List<string>();

        subjectAttrs.Add(X509Name.C);
        subjectValues.Add(country);

        subjectAttrs.Add(X509Name.OU);
        subjectValues.Add(organizationUnitName);

        subjectAttrs.Add(X509Name.O);
        subjectValues.Add(organizationName);

        subjectAttrs.Add(X509Name.CN);
        subjectValues.Add(commonName);


        var subject = new X509Name(subjectAttrs.ToArray(), subjectValues.ToArray());

        // Subject Alternative Names
        var sanAttrs = new List<DerObjectIdentifier>();
        var sanValues = new List<string>();

        sanAttrs.Add(X509Name.BusinessCategory);
        sanValues.Add("Examp");

        sanAttrs.Add(X509Name.PostalAddress);
        sanValues.Add("Examp 1");

        sanAttrs.Add(X509Name.T);
        sanValues.Add("1100");

        sanAttrs.Add(X509Name.UID);
        sanValues.Add("31047539761234");
        
        sanAttrs.Add(X509Name.SerialNumber);
        sanValues.Add("1-qwer|2-322|3-123");

        var san = new X509Name(sanAttrs.ToArray(), sanValues.ToArray());

        string certificateTemplateName = "1.3.6.1.4.1.311.20.2";
        DerObjectIdentifier certificateTemplateExtensionOid = new DerObjectIdentifier(certificateTemplateName);
        DerSequence certificateTemplateExtension = new DerSequence(new DerObjectIdentifier(certificateTemplateName), new DerPrintableString("ZATCA-Code-Signing"));

        var extensions = new Dictionary<DerObjectIdentifier, X509Extension>()
        {
            {
                X509Extensions.BasicConstraints,
                new X509Extension(true, new DerOctetString(new BasicConstraints(false)))
            },
            {
                X509Extensions.KeyUsage,
                new X509Extension(true, new DerOctetString(new KeyUsage(KeyUsage.DigitalSignature | KeyUsage.KeyEncipherment | KeyUsage.NonRepudiation)))
            },
            {
                X509Extensions.SubjectAlternativeName,
                new X509Extension(false, new DerOctetString(new GeneralName(GeneralName.DirectoryName, san)))
            },
            {
                certificateTemplateExtensionOid,
                new X509Extension(false, new DerOctetString(certificateTemplateExtension))
            },
        };

        // Convert extensions to Asn1Set

        var extensionList = new List<Asn1Encodable>();

        foreach (var extension in extensions)
        {
            extensionList.Add(extension.Value.GetParsedValue());
        }

        // Convert the list of extensions to an Asn1Set
        var extensionsSet = new DerSet(extensionList.ToArray());

        var pkcs10CertificationRequest = new Pkcs10CertificationRequest(
            "SHA256withECDSA",
            subject,
            ecKeyPair.Public,
            extensionsSet,
            ecKeyPair.Private);
        csr = Convert.ToBase64String(pkcs10CertificationRequest.GetEncoded());

        return csr;
    }
    catch (Exception ex)
    {
        // Handle errors as needed
        throw new Exception(ex.Message);
    }
}

I get this error:

System.Exception: 'Unknown object in factory:
Org.BouncyCastle.Asn1.DerBitString (Parameter 'obj')'

Since I don't have any prior cryptographic experience, I'm not sure if I'm approaching this correctly. I've been trying to find a solution to this issue, but I'm not sure how to, Any guidance or examples would be greatly appreciated.

Thank you for your help.


Solution

  • The following code generates a CSR identical to the one posted (except for the keys, which are newly generated, and consequently the signature):

    ...
    // Create ECDSA key pair generator
    var ecKeyPairGenerator = new ECKeyPairGenerator();
    var genParam = new ECKeyGenerationParameters(SecObjectIdentifiers.SecP256k1, new SecureRandom());
    ecKeyPairGenerator.Init(genParam);
    AsymmetricCipherKeyPair ecKeyPair = ecKeyPairGenerator.GenerateKeyPair();
    
    // Subject Name
    var subjectAttrs = new List<DerObjectIdentifier>() { X509Name.C, X509Name.OU, X509Name.O, X509Name.CN };
    var subjectValues = new List<string>() { country, organizationUnitName, organizationName, commonName };
    var subject = new X509Name(subjectAttrs.ToArray(), subjectValues.ToArray());
    
    // SAN
    var sanAttrs = new List<DerObjectIdentifier>() { X509Name.Surname, X509Name.UID, X509Name.T, new DerObjectIdentifier("2.5.4.26"), X509Name.BusinessCategory};
    var sanValues = new List<string>() { "1-haya|2-234|3-354", "310175397400003", "1100", "Zatca 3", "Food Business3" };
    var san = new X509Name(sanAttrs.ToArray(), sanValues.ToArray());
    
    // Extensions
    var extensionsDictionary = new Dictionary<DerObjectIdentifier, X509Extension>()
    {
        {
            new DerObjectIdentifier("1.3.6.1.4.1.311.20.2"),
            new X509Extension(false, new DerOctetString(new DerPrintableString("TSTZATCA-Code-Signing")))
        },
        {
            X509Extensions.SubjectAlternativeName,
            new X509Extension(false, new DerOctetString(new DerSequence(new DerTaggedObject(4, san))))
        },
    };
    var extensions = new X509Extensions(extensionsDictionary); 
    var attribute = new AttributePkcs(PkcsObjectIdentifiers.Pkcs9AtExtensionRequest, new DerSet(extensions));
    var extensionsSet = new DerSet(attribute);
    
    // Create CSR using keys, subject, extensions 
    var pkcs10CertificationRequest = new Pkcs10CertificationRequest(
        "SHA256withECDSA",
        subject,
        ecKeyPair.Public,
        extensionsSet,
        ecKeyPair.Private);
    var csr = Convert.ToBase64String(pkcs10CertificationRequest.GetEncoded());
    ...
    

    This code is based on your code. The main difference to the original code is in the Extensions section:

    • The two dictionary elements create the enrollCerttypeExtension and the subjectAltName sequences.
    • The code new DerOctetString(new DerSequence(new DerTaggedObject(4, san))) of the second dictionary element creates the OCTET STRING - SEQUENCE - [4] - SEQUENCE structure with the SAN elements.

    For an overview of ASN.1 see e.g. here.


    If the generated CSR is loaded in an ASN.1 parser (e.g. https://lapo.it/asn1js) then the following ASN.1 is displayed for the extensions-part:

    enter image description here

    in accordance with the extensions ASN.1 portion of the posted CSR as can be verified here with the lapo.it parser.