如何使用 .Net framework 4.7 在 GRPC 服务器中使用 windows 存储证书 (X509Certificate2)?

How to use windows store certificate (X509Certificate2) in GRPC server using .Net framework 4.7?

我正在从 Windows 10 个证书存储中获取证书。我从 windows 证书商店获得了 X509Certificate2。现在,如何在 SslServerCredentials 中使用此 X509Certificate2。 我知道我可以按如下方式使用 SSL:

var serverKey = File.ReadAllText("C:/repos/TestCert/server-key.pem");
var keyPair = new KeyCertificatePair(serverCert, serverKey);
var caCert = File.ReadAllText("C:/repos/TestCert/client-cert.pem");
var servCred = new SslServerCredentials(new List<KeyCertificatePair>() { keyPair }, caCert, true);

但我没有将上述所有证书放在单独的位置。我只想使用 windows 证书库中已有的证书。我从 windows 商店得到了 X509Certificate2 class 对象。我如何将此 X509Certificate2SslServerCredentials class.

一起使用

为了使其正常工作,您的证书必须在添加到商店时将其私钥标记为可导出,否则会出现错误。几年前我一起破解了这个可能针对.NET Framework 4.7(但可能是 4.8) - 证书导出代码完全来自其他 Stack Overflow 答案(可能包括评论中链接的答案)

public static class CertificateExporter
{
    public static string ExportX509CertificateAsPEM(this X509Certificate2 certificate)
    {
        StringBuilder builder = new StringBuilder();

        builder.AppendLine("-----BEGIN CERTIFICATE-----");
        
        AppendByteArrayAsPEM(builder, certificate.Export(X509ContentType.Cert));
        builder.AppendLine("-----END CERTIFICATE-----");

        return builder.ToString();
    }
    
    private static void AppendByteArrayAsPEM(StringBuilder builder, byte[] input)
    {
        var base64 = Convert.ToBase64String(input);

        for (int i = 0; i < base64.Length; i += 64)
        {
            var line = base64.Substring(i, Math.Min(64, base64.Length - i));
            builder.AppendLine(line);
        }
    }

    public static string ExportX509PrivateRSAKey(this X509Certificate2 cert)
    {
        var csp = (RSACryptoServiceProvider)cert.PrivateKey;

        if (csp.PublicOnly)
            throw new ArgumentException("CSP does not contain a private key", "csp");

        var parameters = csp.ExportParameters(true);
        using (var stream = new MemoryStream())
        {
            var writer = new BinaryWriter(stream);
            writer.Write((byte)0x30); // SEQUENCE
            using (var innerStream = new MemoryStream())
            {
                var innerWriter = new BinaryWriter(innerStream);
                EncodeIntegerBigEndian(innerWriter, new byte[] { 0x00 }); // Version
                EncodeIntegerBigEndian(innerWriter, parameters.Modulus);
                EncodeIntegerBigEndian(innerWriter, parameters.Exponent);
                EncodeIntegerBigEndian(innerWriter, parameters.D);
                EncodeIntegerBigEndian(innerWriter, parameters.P);
                EncodeIntegerBigEndian(innerWriter, parameters.Q);
                EncodeIntegerBigEndian(innerWriter, parameters.DP);
                EncodeIntegerBigEndian(innerWriter, parameters.DQ);
                EncodeIntegerBigEndian(innerWriter, parameters.InverseQ);
                var length = (int)innerStream.Length;
                EncodeLength(writer, length);
                writer.Write(innerStream.GetBuffer(), 0, length);
            }

            var outputStream = new StringWriter();

            var base64 = Convert.ToBase64String(stream.GetBuffer(), 0, (int)stream.Length).ToCharArray();
            outputStream.WriteLine("-----BEGIN RSA PRIVATE KEY-----");
            // Output as Base64 with lines chopped at 64 characters
            for (var i = 0; i < base64.Length; i += 64)
            {
                outputStream.WriteLine(base64, i, Math.Min(64, base64.Length - i));
            }

            outputStream.WriteLine("-----END RSA PRIVATE KEY-----");

            return outputStream.ToString();
        }
    }

    private static void EncodeLength(BinaryWriter stream, int length)
    {
        if (length < 0) throw new ArgumentOutOfRangeException("length", "Length must be non-negative");
        if (length < 0x80)
        {
            // Short form
            stream.Write((byte)length);
        }
        else
        {
            // Long form
            var temp = length;
            var bytesRequired = 0;
            while (temp > 0)
            {
                temp >>= 8;
                bytesRequired++;
            }

            stream.Write((byte)(bytesRequired | 0x80));
            for (var i = bytesRequired - 1; i >= 0; i--)
            {
                stream.Write((byte)(length >> (8 * i) & 0xff));
            }
        }
    }

    private static void EncodeIntegerBigEndian(BinaryWriter stream, byte[] value, bool forceUnsigned = true)
    {
        stream.Write((byte)0x02); // INTEGER
        var prefixZeros = 0;
        for (var i = 0; i < value.Length; i++)
        {
            if (value[i] != 0) break;
            prefixZeros++;
        }

        if (value.Length - prefixZeros == 0)
        {
            EncodeLength(stream, 1);
            stream.Write((byte)0);
        }
        else
        {
            if (forceUnsigned && value[prefixZeros] > 0x7f)
            {
                // Add a prefix zero to force unsigned if the MSB is 1
                EncodeLength(stream, value.Length - prefixZeros + 1);
                stream.Write((byte)0);
            }
            else
            {
                EncodeLength(stream, value.Length - prefixZeros);
            }

            for (var i = prefixZeros; i < value.Length; i++)
            {
                stream.Write(value[i]);
            }
        }
    }
}

然后创建凭据:

public class SslCredentialReader
{

    public SslServerCredentials CreateSslServerCredentials(string subject)
    {
        var certificate = GetServerCertificate(subject);
        var keyPair = new KeyCertificatePair(certificate.ExportX509CertificateAsPEM(),
            certificate.ExportX509PrivateRSAKey());
        return new SslServerCredentials(new[] {keyPair});
    }
    
    public X509Certificate2 GetServerCertificate(string subject)
    {
        var store = new X509Store(StoreName.My, StoreLocation.LocalMachine);
        store.Open(OpenFlags.ReadOnly);
        var certificates = store.Certificates.Find(X509FindType.FindBySubjectName, subject, true);
        store.Close();
        
        if (certificates.Count == 0)
            throw new ArgumentException($"No certificate matching the subject name {subject} could be found");
        
        return certificates[0];
    }
}

我可以使用这段代码,但它位于一个旧分支上并且未经测试,希望它足以让你到达那里。