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:
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:
-----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.
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:
enrollCerttypeExtension
and the subjectAltName
sequences.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:
in accordance with the extensions ASN.1 portion of the posted CSR as can be verified here with the lapo.it parser.