如何使用 BouncyCastle X509V3CertificateGenerator 设置 upnName(用户主体名称)
How can the upnName (user principal name) be set with BouncyCastle X509V3CertificateGenerator
我正在为通过
从给定证书收集用户主体名称的应用程序中预先存在/运行的方法编写一些单元测试
var upnName = currentUserCert.GetNameInfo(X509NameType.UpnName, false);
这在 运行 时间有效,但是,到目前为止,我无法从 BouncyCastle X509V3CertificateGenerator 生成的证书中检索此值。虽然我可以完成大部分工作,但我无法生成包含 upnName 的证书。我只是不确定如何设置 user principal 以便在测试时可以通过 GetNameInfo 检索它。
测试证书本身就是这样生成的
private X509Certificate2 GenerateCert(KeyPurposeID keyPurposeId, string subjectPrefix = "OID.1.2.3.4=", string validCertOriginFragment = "OU=SOME ORG")
{
const int keyStrength = 2048;
const string subject = "98876543210123";
// Generating Random Numbers
var randomGenerator = new CryptoApiRandomGenerator();
var random = new SecureRandom(randomGenerator);
var kpgen = new RsaKeyPairGenerator();
kpgen.Init(new KeyGenerationParameters(new SecureRandom(new CryptoApiRandomGenerator()), keyStrength));
var certificateGenerator = new X509V3CertificateGenerator();
var certName = new X509Name($"{subjectPrefix}{subject} + CN=JAMES BOND (Affiliate), {validCertOriginFragment}, O=SOME ORG, C=US");
var issuer = new X509Name("OU=CERT AUTH CA, OU=Certification Authorities, O=MYCERTAUTH, C=US");
var serialNo = BigInteger.ProbablePrime(120, new Random());
certificateGenerator.SetSerialNumber(serialNo);
certificateGenerator.SetSubjectDN(certName);
certificateGenerator.SetIssuerDN(issuer);
certificateGenerator.SetNotAfter(DateTime.Now.AddYears(50));
certificateGenerator.SetNotBefore(DateTime.Now);
// TODO setup upn name
var keyGenerationParameters = new KeyGenerationParameters(random, keyStrength);
var keyPairGenerator = new RsaKeyPairGenerator();
keyPairGenerator.Init(keyGenerationParameters);
var subjectKeyPair = keyPairGenerator.GenerateKeyPair();
certificateGenerator.SetPublicKey(subjectKeyPair.Public);
if (keyPurposeId != null)
{
certificateGenerator.AddExtension(
X509Extensions.ExtendedKeyUsage.Id,
false,
new ExtendedKeyUsage(keyPurposeId));
}
// Generating the Certificate
var issuerKeyPair = subjectKeyPair;
ISignatureFactory signatureFactory = new Asn1SignatureFactory("SHA256WITHRSA", issuerKeyPair.Private, random);
// selfsign certificate
var certificate = certificateGenerator.Generate(signatureFactory);
var x509 = new X509Certificate2(certificate.GetEncoded());
return x509;
}
根据@peop 的建议,将以下内容添加到设置 upn 名称的 TODO 中
certificateGenerator.AddExtension("2.5.29.17", true,
new GeneralNames(
new GeneralName(GeneralName.OtherName,
new DerSequence(new DerObjectIdentifier("1.3.6.1.4.1.311.20.2.3"), new DerUtf8String($"{subject}@SOMEWHERE.COM")))
));
让我更接近了,我认为,在调查证书时我有一个很好的额外扩展,但是预期的信息仍然没有通过 GetNameInfo 方法公开。为了使神奇的 Microsoft GetNameInfo 方法亮起并 return 预期值,证书是否必须发生更多事情?
如何通过 BouncyCastle X509V3CertificateGenerator 创建证书以便我可以检索 upnName 以进行测试?
根据 this page UPN 应该是 SubjectAlternativeName (SAN) 扩展的一部分。
Subject Alternative Name = Other Name: Principal Name= (UPN). For example:
UPN = user1@name.com
The UPN OtherName OID is : "1.3.6.1.4.1.311.20.2.3"
The UPN OtherName value: Must be ASN1-encoded UTF8 string
可以找到使用 X509V3CertificateGenerator 生成 SAN 扩展的示例 here。
// example of adding email address to SAN
certGen.AddExtension("2.5.29.17", true,
new GeneralNames(new GeneralName(GeneralName.Rfc822Name, "test@test.test")));
非常接近,但我们需要 GeneralName.OtherName
,它定义为 here。
所以我的伪代码(用记事本编写)应该是这样的:
certificateGenerator.AddExtension("2.5.29.17", true,
new GeneralNames(
new GeneralName(GeneralName.OtherName,
new DerSequence(new DerObjectIdentifier("1.3.6.1.4.1.311.20.2.3"),
new DerUtf8String(upnName)))
));
希望我的 ASN.1 结构正确 - Sequence->(oid, utf8string)。最好的方法是解析现有证书,即 ASN.1 editor 以检查扩展的 ASN.1 结构。
Microsoft 提供 Guidelines for enabling smart card logon with third-party certification authorities。在该文档中列举了证书的特定格式要求:
The CRL Distribution Point (CDP) location (where CRL is the
Certification Revocation List) must be populated, online, and
available. For example:
[1]CRL Distribution Point
Distribution Point
Name:
Full Name: URL=http://server1.name.com/CertEnroll/caname.crl
Key Usage = Digital Signature
Basic Constraints [Subject Type=End Entity, Path Length Constraint=None (Optional)
Enhanced Key Usage =
- Client Authentication (1.3.6.1.5.5.7.3.2)
(The client authentication OID) is only required if a certificate is used
for SSL authentication.)
- Smart Card Logon (1.3.6.1.4.1.311.20.2.2)
Subject Alternative Name = Other Name: Principal Name= (UPN). For example:
UPN = user1@name.com
The UPN OtherName OID is : "1.3.6.1.4.1.311.20.2.3"
The UPN OtherName value: Must be ASN1-encoded UTF8 string
Subject = Distinguished name of user. This field is a mandatory extension, but the population of this field is optional.
鉴于格式要求和您的证书生成代码(包括您根据 添加的 UPN),您需要修改向证书添加扩展的部分:
if (keyPurposeId != null)
{
certificateGenerator.AddExtension(
X509Extensions.ExtendedKeyUsage.Id,
false,
new ExtendedKeyUsage(keyPurposeId));
// new extension not present in your question's code
certificateGenerator.AddExtension(
"1.3.6.1.5.5.7.3.2",
false,
new ExtendedKeyUsage(KeyPurposeID.IdKPClientAuth));
}
这样做应该会创建一个证书,可以用作 GetNameInfo(X509NameType.UpnName, false);
.
的 X509Certificate2
实例
以下对我有用,并生成了一个带有主题备用名称属性的证书,该属性与用于 user/client 身份验证的 Microsoft 证书服务生成的证书格式相同。也就是说,在 Windows 中查看 .cer 文件的 Subject Alternative Name 属性时,您会看到:
Other Name:
Principal Name=john.doe@contoso.com
RFC822 Name=john.doe@contoso.com
certificateGenerator.AddExtension(X509Extensions.SubjectAlternativeName.Id, false, new DerSequence(
new GeneralName(GeneralName.OtherName,
new DerSequence(new DerObjectIdentifier("1.3.6.1.4.1.311.20.2.3"),
new DerTaggedObject(0, new DerUtf8String("john.doe@contoso.com")))),
new GeneralName(GeneralName.Rfc822Name, "john.doe@costoso.com")));
关键是将 UPN 字符串包装在 DerTaggedObject
中。正如 pepo, I used the ASN.1 Editor to view an existing certificate. It had a bunch of Context Specific nodes around these values. So doing research on that, I found Tutorial for ASN1 DER Primitive Encoder 所建议的那样,这使我进入了 DerTaggedObject
。从那里开始,我开始尝试一些代码,直到我生成的证书的输出与 Microsoft 证书服务创建的证书的输出相匹配。
另请注意,Subject Alternative Name 的扩展未标记为 critical(AddExtension
方法的第二个参数是 false
)。这再次匹配 Microsoft 证书服务生成的内容。
我正在为通过
从给定证书收集用户主体名称的应用程序中预先存在/运行的方法编写一些单元测试var upnName = currentUserCert.GetNameInfo(X509NameType.UpnName, false);
这在 运行 时间有效,但是,到目前为止,我无法从 BouncyCastle X509V3CertificateGenerator 生成的证书中检索此值。虽然我可以完成大部分工作,但我无法生成包含 upnName 的证书。我只是不确定如何设置 user principal 以便在测试时可以通过 GetNameInfo 检索它。
测试证书本身就是这样生成的
private X509Certificate2 GenerateCert(KeyPurposeID keyPurposeId, string subjectPrefix = "OID.1.2.3.4=", string validCertOriginFragment = "OU=SOME ORG")
{
const int keyStrength = 2048;
const string subject = "98876543210123";
// Generating Random Numbers
var randomGenerator = new CryptoApiRandomGenerator();
var random = new SecureRandom(randomGenerator);
var kpgen = new RsaKeyPairGenerator();
kpgen.Init(new KeyGenerationParameters(new SecureRandom(new CryptoApiRandomGenerator()), keyStrength));
var certificateGenerator = new X509V3CertificateGenerator();
var certName = new X509Name($"{subjectPrefix}{subject} + CN=JAMES BOND (Affiliate), {validCertOriginFragment}, O=SOME ORG, C=US");
var issuer = new X509Name("OU=CERT AUTH CA, OU=Certification Authorities, O=MYCERTAUTH, C=US");
var serialNo = BigInteger.ProbablePrime(120, new Random());
certificateGenerator.SetSerialNumber(serialNo);
certificateGenerator.SetSubjectDN(certName);
certificateGenerator.SetIssuerDN(issuer);
certificateGenerator.SetNotAfter(DateTime.Now.AddYears(50));
certificateGenerator.SetNotBefore(DateTime.Now);
// TODO setup upn name
var keyGenerationParameters = new KeyGenerationParameters(random, keyStrength);
var keyPairGenerator = new RsaKeyPairGenerator();
keyPairGenerator.Init(keyGenerationParameters);
var subjectKeyPair = keyPairGenerator.GenerateKeyPair();
certificateGenerator.SetPublicKey(subjectKeyPair.Public);
if (keyPurposeId != null)
{
certificateGenerator.AddExtension(
X509Extensions.ExtendedKeyUsage.Id,
false,
new ExtendedKeyUsage(keyPurposeId));
}
// Generating the Certificate
var issuerKeyPair = subjectKeyPair;
ISignatureFactory signatureFactory = new Asn1SignatureFactory("SHA256WITHRSA", issuerKeyPair.Private, random);
// selfsign certificate
var certificate = certificateGenerator.Generate(signatureFactory);
var x509 = new X509Certificate2(certificate.GetEncoded());
return x509;
}
根据@peop 的建议,将以下内容添加到设置 upn 名称的 TODO 中
certificateGenerator.AddExtension("2.5.29.17", true,
new GeneralNames(
new GeneralName(GeneralName.OtherName,
new DerSequence(new DerObjectIdentifier("1.3.6.1.4.1.311.20.2.3"), new DerUtf8String($"{subject}@SOMEWHERE.COM")))
));
让我更接近了,我认为,在调查证书时我有一个很好的额外扩展,但是预期的信息仍然没有通过 GetNameInfo 方法公开。为了使神奇的 Microsoft GetNameInfo 方法亮起并 return 预期值,证书是否必须发生更多事情?
如何通过 BouncyCastle X509V3CertificateGenerator 创建证书以便我可以检索 upnName 以进行测试?
根据 this page UPN 应该是 SubjectAlternativeName (SAN) 扩展的一部分。
Subject Alternative Name = Other Name: Principal Name= (UPN). For example: UPN = user1@name.com The UPN OtherName OID is : "1.3.6.1.4.1.311.20.2.3" The UPN OtherName value: Must be ASN1-encoded UTF8 string
可以找到使用 X509V3CertificateGenerator 生成 SAN 扩展的示例 here。
// example of adding email address to SAN
certGen.AddExtension("2.5.29.17", true,
new GeneralNames(new GeneralName(GeneralName.Rfc822Name, "test@test.test")));
非常接近,但我们需要 GeneralName.OtherName
,它定义为 here。
所以我的伪代码(用记事本编写)应该是这样的:
certificateGenerator.AddExtension("2.5.29.17", true,
new GeneralNames(
new GeneralName(GeneralName.OtherName,
new DerSequence(new DerObjectIdentifier("1.3.6.1.4.1.311.20.2.3"),
new DerUtf8String(upnName)))
));
希望我的 ASN.1 结构正确 - Sequence->(oid, utf8string)。最好的方法是解析现有证书,即 ASN.1 editor 以检查扩展的 ASN.1 结构。
Microsoft 提供 Guidelines for enabling smart card logon with third-party certification authorities。在该文档中列举了证书的特定格式要求:
The CRL Distribution Point (CDP) location (where CRL is the Certification Revocation List) must be populated, online, and available. For example:
[1]CRL Distribution Point
Distribution Point
Name:
Full Name: URL=http://server1.name.com/CertEnroll/caname.crl
Key Usage = Digital Signature
Basic Constraints [Subject Type=End Entity, Path Length Constraint=None (Optional)
Enhanced Key Usage =
- Client Authentication (1.3.6.1.5.5.7.3.2)
(The client authentication OID) is only required if a certificate is used for SSL authentication.)- Smart Card Logon (1.3.6.1.4.1.311.20.2.2)
Subject Alternative Name = Other Name: Principal Name= (UPN). For example:
UPN = user1@name.com
The UPN OtherName OID is : "1.3.6.1.4.1.311.20.2.3"
The UPN OtherName value: Must be ASN1-encoded UTF8 stringSubject = Distinguished name of user. This field is a mandatory extension, but the population of this field is optional.
鉴于格式要求和您的证书生成代码(包括您根据
if (keyPurposeId != null)
{
certificateGenerator.AddExtension(
X509Extensions.ExtendedKeyUsage.Id,
false,
new ExtendedKeyUsage(keyPurposeId));
// new extension not present in your question's code
certificateGenerator.AddExtension(
"1.3.6.1.5.5.7.3.2",
false,
new ExtendedKeyUsage(KeyPurposeID.IdKPClientAuth));
}
这样做应该会创建一个证书,可以用作 GetNameInfo(X509NameType.UpnName, false);
.
X509Certificate2
实例
以下对我有用,并生成了一个带有主题备用名称属性的证书,该属性与用于 user/client 身份验证的 Microsoft 证书服务生成的证书格式相同。也就是说,在 Windows 中查看 .cer 文件的 Subject Alternative Name 属性时,您会看到:
Other Name: Principal Name=john.doe@contoso.com RFC822 Name=john.doe@contoso.com
certificateGenerator.AddExtension(X509Extensions.SubjectAlternativeName.Id, false, new DerSequence(
new GeneralName(GeneralName.OtherName,
new DerSequence(new DerObjectIdentifier("1.3.6.1.4.1.311.20.2.3"),
new DerTaggedObject(0, new DerUtf8String("john.doe@contoso.com")))),
new GeneralName(GeneralName.Rfc822Name, "john.doe@costoso.com")));
关键是将 UPN 字符串包装在 DerTaggedObject
中。正如 pepo, I used the ASN.1 Editor to view an existing certificate. It had a bunch of Context Specific nodes around these values. So doing research on that, I found Tutorial for ASN1 DER Primitive Encoder 所建议的那样,这使我进入了 DerTaggedObject
。从那里开始,我开始尝试一些代码,直到我生成的证书的输出与 Microsoft 证书服务创建的证书的输出相匹配。
另请注意,Subject Alternative Name 的扩展未标记为 critical(AddExtension
方法的第二个参数是 false
)。这再次匹配 Microsoft 证书服务生成的内容。