为了测试目的,我的单元测试使用BouncyCastle作为.NET核心生成一个带有自定义扩展的测试证书。
生成函数
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");
}
}期望values.Region为长度为2的"r1“,但”u0004\u0002r1“的长度为4,与"\u0004\u0002r”(索引0)不同。
因此,BouncyCastle为扩展值添加了两个额外的字节\u0004\u0002 (传输结束,开始文本)。
我将证书保存到一个文件中,并通过certutil -dump -v test.pfx将其转储。

我做错了什么?这是证书的产生吗?还是我是如何解读这些价值观的?所有扩展值都是这样编码的吗?我只期望字符串字节。我在说明书里找不到什么东西。
发布于 2020-07-17 12:07:11
我做错什么了?
你的扩展是错误的。
certificateGenerator.AddExtension(new DerObjectIdentifier(fakeOid), false, Encoding.ASCII.GetBytes(region));最后一个参数不正确。它必须包含任何有效的ASN.1类型。由于参数值是无效的ASN.1类型,BC假定它只是一个随机/任意的八进制字符串,并隐式地将原始值编码为ASN.1 OCTET_STRING类型。如果它应该是文本字符串,那么使用任何适合您的要求和char集的适用的ASN.1字符串类型。
您必须更新您的阅读器,以期望您选择的ASN.1字符串类型编码,然后从ASN.1字符串类型解码字符串值。
https://stackoverflow.com/questions/62952657
复制相似问题