从.NET Core中的PEM文件创建X509Certificate2 [英] Create X509Certificate2 from PEM file in .NET Core

查看:104
本文介绍了从.NET Core中的PEM文件创建X509Certificate2的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想基于PEM文件创建X509Certificate2对象。问题是设置X509Certificate2的PrivateKey属性。我在.NET Core
上阅读了 X509Certificate2.CreateFromCertFile()

  var rsa = new RSACryptoServiceProvider(); 

rsa.ImportCspBlob(pvk);

其中 pvk 是字节数组私钥(从GetBytesFromPEM中读取,如此处所示,如何从PEM获取私钥文件?)来设置私钥,但是随后我得到了


Internal.Cryptography.CryptoThrowHelper + WindowsCryptographicException与消息提供程序的版本错误。


如何根据PEM文件中的私钥正确设置X509Certificate2的私钥?



如果我查看创建X509Certificate2 ,他们使用

  RSACryptoServiceProvider prov = Crypto.DecodeRsaPrivateKey(keyBuffer); 
certificate.PrivateKey = prov;

这似乎是一种简洁的方法,但这在.Net Core中不起作用。 。

解决方案

如果您刚刚从私钥文件的Base64编码中提取了字节,则您拥有PKCS#1, PKCS#8或加密的PKCS#8私钥blob(取决于它说的是 BEGIN RSA PRIVATE KEY, BEGIN PRIVATE KEY还是 BEGIN ENCRYPTED PRIVATE KEY)。 ImportCspBlob 想要一种自定义数据格式,这就是为什么要抱怨的原因。



在不使用BouncyCastle的情况下使用c#进行数字签名说明了前进的方式。最简单/最有条理的方法是只使用证书和密钥来创建PFX,然后让 X509Certificate2 构造函数来完成它。



如果您直接采用直接加载密钥对象的方法,那么将私钥与证书配对的方法是使用新的 CopyWithPrivateKey 扩展方法。这将返回一个新的 X509Certificate2 实例,该实例知道私钥。



PrivateKey 设置器已从.NET Core中删除,因为它在Windows上具有很多副作用,很难在Linux和macOS上复制,特别是如果您从X509Store实例中检索证书。 / p>




此代码是对严格的BER规则过于严格和过度接受的组合,但这应该读取有效编码的PKCS#8

 私有静态只读字节[] s_derIntegerZero = {0x02,0x01,0x00};文件,除非它们包含属性。 

私有静态只读字节[] s_rsaAlgorithmId =
{
0x30、0x0D,
0x06、0x09、0x2A,0x86、0x48、0x86、0xF7、0x0D,0x01 ,0x01、0x01,
0x05、0x00,
};

私有静态整数ReadLength(byte [] data,ref int offset)
{
byte lengthOrLengthLength = data [offset ++];

if(lengthOrLengthLength< 0x80)
{
return lengthOrLengthLength;
}

int lengthLength = lengthOrLengthLength& 0x7F;
int length = 0;

for(int i = 0; i< lengthLength; i ++)
{
if(length> ushort.MaxValue)
{
throw new InvalidOperationException(这似乎太大了。);
}

的长度<< = 8;
length | = data [offset ++];
}

返回长度;
}

私有静态字节[] ReadUnsignedInteger(byte []数据,ref int偏移量,int targetSize = 0)
{
if(data [offset ++]! = 0x02)
{
抛出new InvalidOperationException( Invalid encoding);
}

int length = ReadLength(data,ref offset);

//编码规则说0被编码为一个字节值0x00。
//由于我们期望无符号,因此如果设置了高位则抛出。
if(length< 1 || data [offset]> = 0x80)
{
throw new InvalidOperationException( Invalid encoding);
}

byte [] ret;

if(length == 1)
{
ret = new byte [length];
ret [0] = data [offset ++];
回程;
}

if(data [offset] == 0)
{
offset ++;
长度-;
}

if(targetSize!= 0)
{
if(length> targetSize)
{
throw new InvalidOperationException(错误的关键参数);
}

ret =新字节[targetSize];
}
else
{
ret = new byte [length];
}

Buffer.BlockCopy(data,offset,ret,ret.Length-length,length);
偏移量+ =长度;
回程;
}

private static void EatFullPayloadTag(byte [] data,ref int offset,byte tagValue)
{
if(data [offset ++]!= tagValue)
{
抛出new InvalidOperationException( Invalid encoding);
}

int length = ReadLength(data,ref offset);

if(data.Length-offset!= length)
{
抛出new InvalidOperationException(数据不精确地代表一个值);
}
}

私有静态无效EatMatch(byte [] data,ref int offset,byte [] toMatch)
{
if(data。长度-偏移量> toMatch.Length)
{
if(data.Skip(offset).Take(toMatch.Length).SequenceEqual(toMatch))
{
偏移量+ = toMatch.Length;
的回报;
}
}

抛出新的InvalidOperationException( Bad data。);
}

私有静态RSA DecodeRSAPkcs8(byte [] pkcs8Bytes)
{
int offset = 0;

// PrivateKeyInfo序列
EatFullPayloadTag(pkcs8Bytes,ref offset,0x30);
// PKCS#8 PrivateKeyInfo.version == 0
EatMatch(pkcs8Bytes,ref offset,s_derIntegerZero);
// rsaEncryption AlgorithmIdentifier值
EatMatch(pkcs8Bytes,ref offset,s_rsaAlgorithmId);
// PrivateKeyInfo.privateKey OCTET STRING
EatFullPayloadTag(pkcs8Bytes,ref offset,0x04);
// RSAPrivateKey序列
EatFullPayloadTag(pkcs8Bytes,ref offset,0x30);
// RSAPrivateKey.version == 0
EatMatch(pkcs8Bytes,ref offset,s_derIntegerZero);

RSAParameters rsaParameters = new RSAParameters();
rsaParameters.Modulus = ReadUnsignedInteger(pkcs8Bytes,参考偏移量);
rsaParameters.Exponent = ReadUnsignedInteger(pkcs8Bytes,参考偏移量);
rsaParameters.D = ReadUnsignedInteger(pkcs8Bytes,ref offset,rsaParameters.Modulus.Length);
int halfModulus =(rsaParameters.Modulus.Length + 1)/ 2;
rsaParameters.P = ReadUnsignedInteger(pkcs8Bytes,ref offset,halfModulus);
rsaParameters.Q = ReadUnsignedInteger(pkcs8Bytes,ref offset,halfModulus);
rsaParameters.DP = ReadUnsignedInteger(pkcs8Bytes,ref offset,halfModulus);
rsaParameters.DQ = ReadUnsignedInteger(pkcs8Bytes,ref offset,halfModulus);
rsaParameters.InverseQ = ReadUnsignedInteger(pkcs8Bytes,ref offset,halfModulus);

if(offset!= pkcs8Bytes.Length)
{
throw new InvalidOperationException(东西没加起来);
}

RSA rsa = RSA.Create();
rsa.ImportParameters(rsaParameters);
return rsa;
}


I want to create a X509Certificate2 object based on a PEM file. The problem is setting the PrivateKey property of X509Certificate2. I read X509Certificate2.CreateFromCertFile() on .NET Core and then used

var rsa = new RSACryptoServiceProvider();

rsa.ImportCspBlob(pvk);

Where pvk is the byte array of the private key (read from GetBytesFromPEM as shown here how to get private key from PEM file?), to set the private key, but then I get an

Internal.Cryptography.CryptoThrowHelper+WindowsCryptographicException with message Bad Version of provider.

How can I properly set the PrivateKey of the X509Certificate2 based on the private key in the PEM file?

If I look at Creating the X509Certificate2, they use

 RSACryptoServiceProvider prov = Crypto.DecodeRsaPrivateKey(keyBuffer);
 certificate.PrivateKey = prov;

which seems like a neat way to do this, but this does not work in .Net Core...

解决方案

If you've just extracted the bytes from the Base64 encoding of the private key file you have a PKCS#1, PKCS#8, or encrypted PKCS#8 private key blob (depending on if it said "BEGIN RSA PRIVATE KEY", "BEGIN PRIVATE KEY" or "BEGIN ENCRYPTED PRIVATE KEY"). ImportCspBlob wants a custom format for the data, and that's why it's complaining.

Digital signature in c# without using BouncyCastle has an explanation of ways forward. The easiest / most formulaic is to just make a PFX with the cert and key, and let the X509Certificate2 constructor do its thing.

If you go the route of loading the key object directly then the way you would mate a private key with the certificate is to use one of the new CopyWithPrivateKey extension methods. This returns a new instance of X509Certificate2 which knows about the private key.

The PrivateKey setter was "removed" from .NET Core because it has a lot of side effects on Windows that are hard to replicate on Linux and macOS, particularly if you retrieved the certificate out of an instance of X509Store.


This code is a combination of overly strict and overly accepting for real BER rules, but this should read validly encoded PKCS#8 files unless they included attributes.

private static readonly byte[] s_derIntegerZero = { 0x02, 0x01, 0x00 };

private static readonly byte[] s_rsaAlgorithmId =
{
    0x30, 0x0D,
    0x06, 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x01,
    0x05, 0x00,
};

private static int ReadLength(byte[] data, ref int offset)
{
    byte lengthOrLengthLength = data[offset++];

    if (lengthOrLengthLength < 0x80)
    {
        return lengthOrLengthLength;
    }

    int lengthLength = lengthOrLengthLength & 0x7F;
    int length = 0;

    for (int i = 0; i < lengthLength; i++)
    {
        if (length > ushort.MaxValue)
        {
            throw new InvalidOperationException("This seems way too big.");
        }

        length <<= 8;
        length |= data[offset++];
    }

    return length;
}

private static byte[] ReadUnsignedInteger(byte[] data, ref int offset, int targetSize = 0)
{
    if (data[offset++] != 0x02)
    {
        throw new InvalidOperationException("Invalid encoding");
    }

    int length = ReadLength(data, ref offset);

    // Encoding rules say 0 is encoded as the one byte value 0x00.
    // Since we expect unsigned, throw if the high bit is set.
    if (length < 1 || data[offset] >= 0x80)
    {
        throw new InvalidOperationException("Invalid encoding");
    }

    byte[] ret;

    if (length == 1)
    {
        ret = new byte[length];
        ret[0] = data[offset++];
        return ret;
    }

    if (data[offset] == 0)
    {
        offset++;
        length--;
    }

    if (targetSize != 0)
    {
        if (length > targetSize)
        {
            throw new InvalidOperationException("Bad key parameters");
        }

        ret = new byte[targetSize];
    }
    else
    {
        ret = new byte[length];
    }

    Buffer.BlockCopy(data, offset, ret, ret.Length - length, length);
    offset += length;
    return ret;
}

private static void EatFullPayloadTag(byte[] data, ref int offset, byte tagValue)
{
    if (data[offset++] != tagValue)
    {
        throw new InvalidOperationException("Invalid encoding");
    }

    int length = ReadLength(data, ref offset);

    if (data.Length - offset != length)
    {
        throw new InvalidOperationException("Data does not represent precisely one value");
    }
}

private static void EatMatch(byte[] data, ref int offset, byte[] toMatch)
{
    if (data.Length - offset > toMatch.Length)
    {
        if (data.Skip(offset).Take(toMatch.Length).SequenceEqual(toMatch))
        {
            offset += toMatch.Length;
            return;
        }
    }

    throw new InvalidOperationException("Bad data.");
}

private static RSA DecodeRSAPkcs8(byte[] pkcs8Bytes)
{
    int offset = 0;

    // PrivateKeyInfo SEQUENCE
    EatFullPayloadTag(pkcs8Bytes, ref offset, 0x30);
    // PKCS#8 PrivateKeyInfo.version == 0
    EatMatch(pkcs8Bytes, ref offset, s_derIntegerZero);
    // rsaEncryption AlgorithmIdentifier value
    EatMatch(pkcs8Bytes, ref offset, s_rsaAlgorithmId);
    // PrivateKeyInfo.privateKey OCTET STRING
    EatFullPayloadTag(pkcs8Bytes, ref offset, 0x04);
    // RSAPrivateKey SEQUENCE
    EatFullPayloadTag(pkcs8Bytes, ref offset, 0x30);
    // RSAPrivateKey.version == 0
    EatMatch(pkcs8Bytes, ref offset, s_derIntegerZero);

    RSAParameters rsaParameters = new RSAParameters();
    rsaParameters.Modulus = ReadUnsignedInteger(pkcs8Bytes, ref offset);
    rsaParameters.Exponent = ReadUnsignedInteger(pkcs8Bytes, ref offset);
    rsaParameters.D = ReadUnsignedInteger(pkcs8Bytes, ref offset, rsaParameters.Modulus.Length);
    int halfModulus = (rsaParameters.Modulus.Length + 1) / 2;
    rsaParameters.P = ReadUnsignedInteger(pkcs8Bytes, ref offset, halfModulus);
    rsaParameters.Q = ReadUnsignedInteger(pkcs8Bytes, ref offset, halfModulus);
    rsaParameters.DP = ReadUnsignedInteger(pkcs8Bytes, ref offset, halfModulus);
    rsaParameters.DQ = ReadUnsignedInteger(pkcs8Bytes, ref offset, halfModulus);
    rsaParameters.InverseQ = ReadUnsignedInteger(pkcs8Bytes, ref offset, halfModulus);

    if (offset != pkcs8Bytes.Length)
    {
        throw new InvalidOperationException("Something didn't add up");
    }

    RSA rsa = RSA.Create();
    rsa.ImportParameters(rsaParameters);
    return rsa;
}

这篇关于从.NET Core中的PEM文件创建X509Certificate2的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆