You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用BouncyCastle.NET生成含非ASCII字符SAN的X509证书断言失败

解决BouncyCastle.NET处理IDN域名时特殊字符丢失的问题

这个问题的核心在于X.509证书的DNS主题备用名称(SAN)要求使用IA5String编码,而IA5String仅支持ASCII字符——直接传入带非ASCII的国际化域名(IDN)时,BouncyCastle会自动把非ASCII字符替换成?,这就是你断言失败的原因。

为什么会出现这个问题?

当你调用new GeneralName(GeneralName.DnsName, fqdn)时,BouncyCastle默认会把传入的字符串当作IA5String处理。IA5String的字符范围仅限ASCII,任何超出范围的字符都会被替换为?,所以你从证书里读回的字符串才会和原域名不一致。你之前尝试用DerIA5String(fqdn, true)显式验证失败,也是因为原域名包含非ASCII字符,违反了IA5String的编码规则。

正确的处理方式(符合标准)

根据RFC 5280和IDN规范,国际化域名需要先转换为Punycode(ASCII兼容编码,ACE),再存入证书的DNS SAN中。.NET提供了System.Globalization.IdnMapping类来处理这种转换,具体步骤如下:

  1. 将原IDN域名转换为Punycode
  2. 使用转换后的Punycode创建GeneralName
  3. 验证时,把证书中读取到的Punycode转换回Unicode域名再做断言

修改后的代码示例:

using System.Globalization;

[TestCase("myDevice.abc.example.com")]
[TestCase("myDevice.äöü.example.com")] // 西欧字符
[TestCase("myDevice.ařa.example.com")] // 东欧字符
[TestCase("mydevice.aデa.example.com")] // 片假名
[Test]
public void IdnTest(string fqdn) {
    #region Preparation
    ECKeyPairGenerator kpgen = new ECKeyPairGenerator();
    kpgen.Init(new KeyGenerationParameters(new SecureRandom(new CryptoApiRandomGenerator()), Constants.SelectedRootKeySize));
    var caKeyPair = kpgen.GenerateKeyPair();
    var certKeyPair = kpgen.GenerateKeyPair();

    X509V3CertificateGenerator certGenerator = new X509V3CertificateGenerator();
    certGenerator.SetSerialNumber(Org.BouncyCastle.Math.BigInteger.ProbablePrime(120, new Random()));
    IList oids = new ArrayList() { X509Name.OU };
    IList values = new ArrayList() { "Test" };
    certGenerator.SetIssuerDN(new X509Name(oids, values));
    certGenerator.SetNotBefore(DateTime.Now.Date);
    certGenerator.SetNotAfter(DateTime.Now + TimeSpan.FromDays(365));
    certGenerator.SetPublicKey(certKeyPair.Public);

    // 关键:将IDN域名转换为Punycode
    var idnMapping = new IdnMapping();
    string punycodeFqdn = idnMapping.GetAscii(fqdn);
    var dnsName = new GeneralName(GeneralName.DnsName, punycodeFqdn);
    
    GeneralNames subjectAltName = new GeneralNames(dnsName);
    certGenerator.AddExtension(X509Extensions.SubjectAlternativeName, true, subjectAltName);

    ISignatureFactory signatureFactory = new Asn1SignatureFactory("sha256WithECDSA", caKeyPair.Private, new SecureRandom(new CryptoApiRandomGenerator()));
    Org.BouncyCastle.X509.X509Certificate x509Certificate = certGenerator.Generate(signatureFactory);
    #endregion

    #region Verification
    var san = x509Certificate.GetSubjectAlternativeNames() as ArrayList;
    Assert.AreEqual(1, san.Count);
    var generalName = san[0] as System.Collections.ArrayList;
    Assert.AreEqual(GeneralName.DnsName, generalName[0]);
    
    // 验证时将Punycode转换回Unicode域名
    string actualPunycode = generalName[1].ToString();
    string actualFqdn = idnMapping.GetUnicode(actualPunycode);
    Assert.AreEqual(fqdn, actualFqdn);
    #endregion
}

额外说明

如果你尝试直接用UTF8编码存储域名(比如用DerUtf8String构造GeneralName),虽然技术上可行,但不符合X.509标准,会导致很多TLS客户端(比如浏览器、操作系统)无法正确识别证书中的域名,因此强烈不建议这么做。

内容的提问来源于stack exchange,提问作者pniederh

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.15 04:30:17