如何使用BouncyCastle X509V3CertificateGenerator设置upnName(用户主体名称)

时间:2017-05-31 16:38:59

标签: c# unit-testing certificate bouncycastle x509

我正在为应用程序中的预先存在/正常运行的方法编写一些单元测试,该应用程序通过

从给定证书收集用户主体名称
var upnName = currentUserCert.GetNameInfo(X509NameType.UpnName, false);

这在运行时有效,但到目前为止,我还无法从BouncyCastle X509V3CertificateGenerator生成的证书中检索此值。虽然我可以在大部分时间内完成,但我无法生成包含 upnName 的证书。我只是不确定如何设置用户主体,以便在测试时可以通过 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的建议,将以下内容添加到设置名称

的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方法点亮并返回预期值,证书上是否还有其他必须发生的事情?

如何通过BouncyCastle X509V3CertificateGenerator创建证书,以便我可以检索 upnName 以进行测试?

3 个答案:

答案 0 :(得分:1)

根据this page,UPN应该是SubjectAlternativeName(SAN)扩展的一部分。

  

主题备选名称=其他名称:主要名称=(UPN)。例如:   UPN = user1@name.com   UPN OtherName OID是:“1.3.6.1.4.1.311.20.2.3”   UPN OtherName值:必须是ASN1编码的UTF8字符串

可以找到使用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结构正确 - 顺序 - >(oid,utf8string)。最好的方法是使用ASN.1 editor解析现有证书,以检查扩展的ASN.1结构。

答案 1 :(得分:1)

Microsoft提供Guidelines for enabling smart card logon with third-party certification authorities。在该文档中,枚举了证书的特定格式要求:

  
      
  • CRL分发点(CDP)位置(CRL为   证书撤销清单)必须填写,在线和   可用。例如:
      [1] CRL分发点
      分发点
      名称:
      全名:网址= http://server1.name.com/CertEnroll/caname.crl

  •   
  • 密钥用法 =数字签名

  •   
  • 基本约束 [主题类型=最终实体,路径长度约束=无(可选)

  •   
  • 增强型密钥用法 =

         
        
    • 客户端验证(1.3.6.1.5.5.7.3.2)
        (仅在使用证书时才需要客户端身份验证OID)   用于SSL身份验证。)
    •   
    • 智能卡登录(1.3.6.1.4.1.311.20.2.2)
    •   
  •   
  • 主题备用名称 =其他名称:主要名称=(UPN)。例如:
      UPN = user1@name.com
      UPN OtherName OID是:" 1.3.6.1.4.1.311.20.2.3"
      UPN OtherName值:必须是ASN1编码的UTF8字符串

  •   
  • 主题 =用户的专有名称。此字段是必填字段,但此字段的填充是可选的。

  •   

考虑到格式要求和证书生成代码(包括您基于pepo's answer添加的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));
}

执行此操作应创建一个可用作X509Certificate2 GetNameInfo(X509NameType.UpnName, false);实例的证书。

答案 2 :(得分:1)

以下内容适用于我并生成了具有“使用者备用名称”属性的证书,该证书的格式与Microsoft证书服务在用于用户/客户端身份验证时生成的证书的格式相同。也就是说,在Windows中查看.cer文件的“使用者备用名称”属性时,您会看到:

    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中。根据{{​​3}}的建议,我使用pepo查看现有证书。它围绕这些值有一堆特定于上下文的节点。因此,我对此进行了研究ASN.1 Editor,这使我进入了DerTaggedObject。从那里开始,我刚开始尝试一些代码,直到我生成的证书的输出与Microsoft证书服务创建的证书的输出相匹配。

另请注意,主题备用名称的扩展名未标记为严重AddExtension方法的第二个参数为false)。这再次与Microsoft证书服务生成的内容相匹配。