【问题标题】:Certificate extension value contains 2 extra bytes (\u0004\u0002) octet encoding证书扩展值包含 2 个额外字节 (\u0004\u0002) 八位字节编码
【发布时间】:2020-07-17 11:03:13
【问题描述】:

出于测试目的,我的 unittest 使用 BouncyCastle for .NET core 生成带有自定义扩展的测试证书。

生成函数

static internal class CertificateGenerator
{
    public static X509Certificate2 GenerateCertificate(string region)
    {
        var randomGenerator = new CryptoApiRandomGenerator();
        var random = new SecureRandom(randomGenerator);
        var certificateGenerator = new X509V3CertificateGenerator();
        var serialNumber =
            BigIntegers.CreateRandomInRange(
                BigInteger.One, BigInteger.ValueOf(Int64.MaxValue), random);
        certificateGenerator.SetSerialNumber(serialNumber);
        const string signatureAlgorithm = "SHA1WithRSA";
        certificateGenerator.SetSignatureAlgorithm(signatureAlgorithm);
        var subjectDN = new X509Name("CN=FOOBAR");
        var issuerDN = subjectDN;
        certificateGenerator.SetIssuerDN(issuerDN);
        certificateGenerator.SetSubjectDN(subjectDN);
        var notBefore = DateTime.UtcNow.Date.AddHours(-24);
        var notAfter = notBefore.AddYears(1000);
        certificateGenerator.SetNotBefore(notBefore);
        certificateGenerator.SetNotAfter(notAfter);
      
        var fakeOid = "1.3.6.1.1.5.6.100.345434.345";
        if (region != null)
        {
            certificateGenerator.AddExtension(new DerObjectIdentifier(fakeOid), false, Encoding.ASCII.GetBytes(region));
        }

        const int strength = 4096;
        var keyGenerationParameters = new KeyGenerationParameters(random, strength);
        var keyPairGenerator = new RsaKeyPairGenerator();
        keyPairGenerator.Init(keyGenerationParameters);
        var subjectKeyPair = keyPairGenerator.GenerateKeyPair();

        certificateGenerator.SetPublicKey(subjectKeyPair.Public);

        var issuerKeyPair = subjectKeyPair;
        var certificate = certificateGenerator.Generate(issuerKeyPair.Private, random);

        var store = new Pkcs12Store();
        string friendlyName = certificate.SubjectDN.ToString();
        var certificateEntry = new X509CertificateEntry(certificate);
        store.SetCertificateEntry(friendlyName, certificateEntry);

        store.SetKeyEntry(friendlyName, new AsymmetricKeyEntry(subjectKeyPair.Private), new[] { certificateEntry });

        string password = "password";
        var stream = new MemoryStream();
        store.Save(stream, password.ToCharArray(), random);


        byte[] pfx = Pkcs12Utilities.ConvertToDefiniteLength(stream.ToArray(), password.ToCharArray());

        var convertedCertificate =
            new X509Certificate2(
                pfx, password,
                X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.Exportable);

        return convertedCertificate;
    }
}

阅读器

public class CertificateExtensionReader
{
    private readonly ILogger logger;

    public CertificateExtensionReader(ILogger logger)
    {
        this.logger = logger;
    }

    public CertificateExtensionValues ReadExtensionValues(byte[] certificate)
    {
        var x509Certificate2 = new X509Certificate2(certificate);
        var region = GetCustomExtensionValue(x509Certificate2.Extensions, new Oid("1.3.6.1.1.5.6.100.345434.345"));

        return new CertificateExtensionValues { Region = region };
    }

    private string GetCustomExtensionValue(X509ExtensionCollection x509Extensions, Oid oId)
    {
        var extension = x509Extensions[oId.Value];

        if(extension == null)
            throw new CertificateExtensionValidationException($"The client certificate does not contain the expected extensions '{oId.FriendlyName}' with OID {oId.Value}.");

        if (extension.RawData == null)
            throw new CertificateExtensionValidationException($"Device client certificate does not a value for the '{oId.FriendlyName}' extension with OID {oId.Value}");

        var customExtensionValue = Encoding.UTF8.GetString(extension.RawData).Trim();
        logger.LogInformation($"Custom Extension value for the '{oId.FriendlyName}' extension with OID {oId.Value}: '{customExtensionValue}'");
        return customExtensionValue;
    }
}

public class CertificateExtensionValues
{
    public string Region { get; set; }
}

测试

[TestFixture]
public class CertificateExtensionReaderFixture
{
    private ILogger logger = new NullLogger<CertificateExtensionReaderFixture>();
    private CertificateExtensionReader reader;

    [SetUp]
    public void Setup()
    {
        reader = new CertificateExtensionReader(logger);
    }

    [Test]
    public void ShouldReadExtensionValues()
    {
        var certificate = CertificateGenerator.GenerateCertificate("r1").Export(X509ContentType.Pfx);

        var values = reader.ReadExtensionValues(certificate);

        values.Region.Should().Be("r1");
    }
}

预期值。区域为“r1”,长度为 2,但“\u0004\u0002r1”长度为 4,与“\u0004\u0002r”(索引 0)附近不同。

因此,BouncyCastle 为扩展值添加了两个额外的字节 \u0004\u0002(传输结束,文本开始)。

我将证书保存到一个文件并通过certutil -dump -v test.pfx转储它

我做错了什么?是证书的生成吗?或者是我如何读取这些值?所有扩展值都是这样编码的吗?我只期待字符串字节。我在规范中找不到东西。

【问题讨论】:

    标签: c# .net-core x509certificate bouncycastle x509


    【解决方案1】:

    我做错了什么?

    您创建的扩展程序错误。

    certificateGenerator.AddExtension(new DerObjectIdentifier(fakeOid), false, Encoding.ASCII.GetBytes(region));
    

    最后一个参数不正确。它必须包含任何有效的 ASN.1 类型。由于参数值是无效的 ASN.1 类型,BC 假设它只是一个随机/任意八位字节字符串,并将原始值隐式编码为 ASN.1 OCTET_STRING 类型。如果它应该是文本字符串,则使用任何适合您要求和字符集的适用 ASN.1 字符串类型。

    并且您必须更新您的阅读器以期待您选择用于编码的 ASN.1 字符串类型,然后从 ASN.1 字符串类型解码字符串值。

    【讨论】:

    • 谢谢。我现在使用new DerUtf8String(region) 添加值。您知道如何仅使用 .net core 正确阅读吗?
    • .NET Core 没有 ASN 数据解析器。对于简单的场景(当字符串长度小于 128 个字符时),您可以安全地跳过前两个字节,其余字节将表示编码的 UTF-8 字符串。
    • 是的,这就是我现在正在做的事情。但我觉得有点脏 :D 再次感谢。
    • .NET 团队在 .NET 5 中宣布了一些 ASN 解析器功能,但这是一条漫长的道路。如果你现在真的需要它,你可以使用 3rd 方库。
    猜你喜欢
    • 2013-11-24
    • 2011-06-05
    • 1970-01-01
    • 2012-01-13
    • 1970-01-01
    • 2019-04-13
    • 2016-06-07
    • 2011-06-15
    • 1970-01-01
    相关资源
    最近更新 更多