如何使用带有ECDSA签名的Bouncy Castle延迟签署证书申请?

时间:2017-09-12 17:50:37

标签: c# bouncycastle csr ecdsa pkcs#10

我正在尝试使用C#中的Bouncy Castle使用ECDSA签名算法实现证书请求(CSR)的延迟签名。到目前为止,我已经设法用RSA而不是ECDSA实现了这一点。我使用Bouncy Castle的Pkcs10CertificationRequestDelaySigned类。

我的测试代码片段在验证签名时失败(完整代码可在下面找到):

        [TestMethod]
        public void ValidCsrWithoutPassword_Ecdsa_SignatureIsAppended()
        {
            DelayCsrProvider sut = CreateSut();

            const string signAlgorithm = "ECDSA";
            var keys = new Keys(signAlgorithm);

            // Create CSR
            var signatureAlgorithm = "SHA256withECDSA";
            byte[] octetData = CreateCsr(keys.SignKeyPair, signatureAlgorithm);

            // Append password to CSR
            byte[] csrWithPass = sut.AppendPassword(octetData, "some-text-1");

            // Calculate HASH
            var hashAlgorithm = CmsSignedGenerator.DigestSha256;
            byte[] hash = sut.BuildHash(csrWithPass, hashAlgorithm);

            // Sign using HASH
            byte[] signature = Sign(hash, signAlgorithm, hashAlgorithm, keys.SignKeyPair.Private);

            // Add signature to CSR
            byte[] csrSigned = sut.AppendSignature(csrWithPass, signature);

            // Just verify the signature matches CSR's public key + data,
            // public key should match the private key
            // this is where it fails

            Verify(csrSigned);
        }

情景是:  有两个实体(程序在不同的机器上运行)。 一个具有私钥和公钥 - 称为Signer,另一个具有附加信息(如密码),用于扩展具有该信息的证书请求但不能访问私钥 - 称之为DelayCsrProvider。

序列是:

  1. Signer在没有密码的情况下创建CSR并签署数据,将其发送到PKC#10格式DER编码的DelayCsrProvider。

  2. DelayCsrProvider使用收到的CSR中的所有信息创建新的CSR,并添加包含密码的附加属性。 现在我们必须签署这个新的CSR,但我们没有私钥。相反,我们计算数据的哈希值(SHA-256)并将摘要发送给签名者。

  3. 签名者收到哈希并签名哈希,将签名发送回DelayCsrProvider。

  4. DelayCsrProvider将收到的签名插入CSR,从而创建一个具有有效签名的完整CSR。

  5. 我创建了DelayCsrProvider类和单元测试,它正在完成上述所有步骤。 RSA的一个单元测试工作正常,在验证签名时,ECDSA的另一个单元测试失败。

    此处还可以做些什么来解决ECDSA哈希签名?

    请参阅下面的代码的主要部分或从GIT下载整个示例:https://github.com/DmitriNymi/Certificate-Enrollment.git

    注意:

    .Net Framework 4.6.2 充气城堡nuget BouncyCastle.Crypto.dll FileVersion = 1.8.15362.1

    这是在Assert ValidCsrWithoutPassword_Ecdsa_SignatureIsAppended()中失败的测试的完整代码示例:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using Org.BouncyCastle.Asn1;
    using Org.BouncyCastle.Asn1.Pkcs;
    using Org.BouncyCastle.Crypto;
    using Org.BouncyCastle.Pkcs;
    using Org.BouncyCastle.Security;
    
    namespace NEnrollment.Services.DelaySigning
    {
    public class DelayCsrProvider
    {
        /// <summary>
        /// append password to CSR: csrWithPassword = (csr, password)
        /// </summary>
        /// <param name="csr"></param>
        /// <param name="password"></param>
        /// <returns>CSR that  contains password</returns>
        public byte[] AppendPassword(byte[] csr, string password)
        {
            if (csr == null) throw new ArgumentNullException(nameof(csr));
            if (string.IsNullOrEmpty(password)) throw new ArgumentNullException(nameof(password));
    
            var originalCsr = new Pkcs10CertificationRequest(csr);
    
            CertificationRequestInfo cri = originalCsr.GetCertificationRequestInfo();
    
            DerSet attributesSet = AddPasswordAttribute(password, cri.Attributes);
    
            AsymmetricKeyParameter publicKey = PublicKeyFactory.CreateKey(cri.SubjectPublicKeyInfo);
    
            string signatureAlgorithm = originalCsr.SignatureAlgorithm.Algorithm.Id;
    
            // build new CSR from original + password attribute
            var csrWithPassword =
                new Pkcs10CertificationRequestDelaySigned(signatureAlgorithm, cri.Subject, publicKey, attributesSet);
    
            // this signing key is not used for signing but here only to suppress exception thrown in ctor
            csrWithPassword.SignRequest(new byte[] { });
    
            var csrWithPasswordBytes = csrWithPassword.GetDerEncoded();
    
            return csrWithPasswordBytes;
        }
    
        private DerSet AddPasswordAttribute(string password, Asn1Set attributes)
        {
            if (attributes == null) attributes = new DerSet();
    
            List<AttributePkcs> attributesPkcs = attributes
                .OfType<DerSequence>()
                .Select(AttributePkcs.GetInstance)
                .ToList();
    
            bool hasPassword = attributesPkcs.Any(x => x.AttrType.Equals(PkcsObjectIdentifiers.Pkcs9AtChallengePassword));
            if (hasPassword) throw new Exception("Cannot append password, already has password attribute in CSR.");
    
            AttributePkcs passwordAttribute = ChallengePasswordAttribute(password);
    
            attributesPkcs.Add(passwordAttribute);
    
            // ReSharper disable once CoVariantArrayConversion
            DerSet attributesSet = new DerSet(attributesPkcs.ToArray());
            return attributesSet;
        }
    
        private AttributePkcs ChallengePasswordAttribute(string password)
        {
            if (password == null) return null;
    
            Asn1EncodableVector attributeValues = new Asn1EncodableVector { new DerPrintableString(password) };
    
            return new AttributePkcs(PkcsObjectIdentifiers.Pkcs9AtChallengePassword, new DerSet(attributeValues));
        }
    
        /// <summary>
        /// Calculates hash (digest) of the given CSR using the specified hash algorithm OID
        /// </summary>
        /// <param name="csr">CSR without password</param>
        /// <param name="algorithm">digest algorithm OID, for example for SHA256 use: "2.16.840.1.101.3.4.2.1"</param>
        /// <returns>Hash of csr</returns>
        public byte[] BuildHash(byte[] csr, string algorithm)
        {
            var originalCsr = new Pkcs10CertificationRequestDelaySigned(csr);
    
            // parse CSR to Org.BouncyCastle.Pkcs.Pkcs10CertificationRequestDelaySigned
            //  requires CSR to have:
            // 1. Subject
            //      a. X509Name
            //      b. subject public key
            //      c. attributes
            //          c1. password - should be empty
            //          c2. extensions - should contain ... doesn't matter - don't touch
            // 2. SignatureAlgorithmId - keep as it is defined by user request
            // 3. SignBits of user for the given CSR
    
            // hash = function(csrWithPassword without signature/signature algorithm)
            // for some hash algorithms Hash may depend on a random number, 
            // thus giving different Hash every time it is calculated even for the same Data, PrivateKey
    
            byte[] dataToSign = originalCsr.GetDataToSign();
    
            //byte[] digest = DigestUtilities.CalculateDigest(CmsSignedGenerator.DigestSha256, dataToSign);
            byte[] digest = DigestUtilities.CalculateDigest(algorithm, dataToSign);
    
            return digest;
        }
    
        /// <summary>
        /// Creates new csr from given CSR + signature
        /// </summary>
        /// <param name="csr">CSR to be used for appending signature</param>
        /// <param name="signature">signature to be appended to CSR</param>
        /// <returns>new CSR with signature appended inside</returns>
        public byte[] AppendSignature(byte[] csr, byte[] signature)
        {
            if (csr == null) throw new ArgumentNullException(nameof(csr));
    
            var originalCsr = new Pkcs10CertificationRequestDelaySigned(csr);
    
            originalCsr.SignRequest(signature);
    
            byte[] csrBytes = originalCsr.GetDerEncoded();
    
            return csrBytes;
        }
    }
    }
    

    这是签署哈希并调用DelayCsrProvider的测试代码。 使用ECDSA进行签名时测试失败,请参阅测试方法:ValidCsrWithoutPassword_Ecdsa_SignatureIsAppended

        using System;
        using System.Collections.Generic;
        using System.IO;
        using System.Linq;
        using Microsoft.VisualStudio.TestTools.UnitTesting;
        using NEnrollment.Services.DelaySigning;
        using Org.BouncyCastle.Asn1;
        using Org.BouncyCastle.Asn1.Sec;
        using Org.BouncyCastle.Asn1.X509;
        using Org.BouncyCastle.Cms;
        using Org.BouncyCastle.Crypto;
        using Org.BouncyCastle.Crypto.Operators;
        using Org.BouncyCastle.Crypto.Parameters;
        using Org.BouncyCastle.Math;
        using Org.BouncyCastle.Pkcs;
        using Org.BouncyCastle.Security;
    
        namespace NEnrollment.Tests
        {
        [TestClass]
        public class DelayCsrProviderTest
        {
            private readonly bool _enableWritingToFile = false;
    
            DelayCsrProvider CreateSut()
            {
                return new DelayCsrProvider();
            }
    
            [TestMethod]
            public void ValidCsrWithoutPassword_Rsa_SignatureIsAppended()
            {
                var sut = CreateSut();
    
                const string signAlgorithm = "RSA";
                var keys = new Keys(signAlgorithm);
    
                // Create CSR
                var signatureAlgorithm = "SHA256withRSA";
                byte[] octetData = CreateCsr(keys.SignKeyPair, signatureAlgorithm);
                ByteArrayToFile(@"Rsa\csrWithoutPass.csr", octetData);
    
                // Append password to CSR
                byte[] csrWithPass = sut.AppendPassword(octetData, "some-text-1");
                ByteArrayToFile(@"Rsa\csrWithPass.csr", csrWithPass);
    
                // Calculate HASH
                var hashAlgorithm = CmsSignedGenerator.DigestSha256;
                byte[] hash = sut.BuildHash(csrWithPass, hashAlgorithm);
    
                // Sign using HASH
                byte[] signature = Sign(hash, signAlgorithm, hashAlgorithm, keys.SignKeyPair.Private);
    
                // Add signature to CSR
                byte[] csrSigned = sut.AppendSignature(csrWithPass, signature);
                ByteArrayToFile(@"Rsa\csrSigned.csr", csrSigned);
    
                // Just verify the signature matches CSR's public key + data,
                // public key should match the private key
                Verify(csrSigned);
                Verify2(csrSigned);
            }
    
            [TestMethod]
            public void ValidCsrWithoutPassword_Ecdsa_SignatureIsAppended()
            {
                var sut = CreateSut();
    
                const string signAlgorithm = "ECDSA";
                var keys = new Keys(signAlgorithm);
    
                // Create CSR
                var signatureAlgorithm = "SHA256withECDSA";
                byte[] octetData = CreateCsr(keys.SignKeyPair, signatureAlgorithm);
                ByteArrayToFile(@"Ecdsa\csrWithoutPass.csr", octetData);
                Verify(octetData);
    
                // Append password to CSR
                byte[] csrWithPass = sut.AppendPassword(octetData, "some-text-1");
                ByteArrayToFile(@"Ecdsa\csrWithPass.csr", csrWithPass);
    
                // Calculate HASH
                var hashAlgorithm = CmsSignedGenerator.DigestSha256;
                byte[] hash = sut.BuildHash(csrWithPass, hashAlgorithm);
    
                // Sign using HASH
                byte[] signature = Sign(hash, signAlgorithm, hashAlgorithm, keys.SignKeyPair.Private);
    
                // Add signature to CSR
                byte[] csrSigned = sut.AppendSignature(csrWithPass, signature);
                ByteArrayToFile(@"Ecdsa\csrSigned.csr", csrSigned);
    
                // Just verify the signature matches CSR's public key + data,
                // public key should match the private key
    
                //Verify2(csrSigned);
                Verify(csrSigned);
            }
    
            private byte[] CreateCsr(AsymmetricCipherKeyPair signingKeyPair, string signatureAlgorithm)
            {
                var key = signingKeyPair;
    
                Dictionary<DerObjectIdentifier, string> values = CreateSubjectValues("my common name");
    
                var subject = new X509Name(values.Keys.Reverse().ToList(), values);
    
                DerSet attributes = null;
    
                var signatureFactory = new Asn1SignatureFactory(signatureAlgorithm, key.Private);
    
                var pkcs10Csr = new Pkcs10CertificationRequest(
                    signatureFactory,
                    subject,
                    key.Public,
                    attributes,
                    key.Private);
    
                byte[] derEncoded = pkcs10Csr.GetDerEncoded();
    
                //string stringEncoded = Convert.ToBase64String(derEncoded);
                //return stringEncoded;
                return derEncoded;
            }
    
            private Dictionary<DerObjectIdentifier, string> CreateSubjectValues(string commonName)
            {
                var values = new Dictionary<DerObjectIdentifier, string>
                {
                    {X509Name.CN, commonName}, //domain name inside the quotes
                    /*
                    {X509Name.CN, csrSubject.CommonName}, //domain name inside the quotes
                    {X509Name.OU, csrSubject.OrganizationalUnit},
                    {X509Name.O, csrSubject.Organization}, //Organisation's Legal name inside the quotes
                    {X509Name.L, csrSubject.City},
                    {X509Name.ST, csrSubject.Country},
                    {X509Name.C, csrSubject.State},
                    */
                };
    
                // remove empty values
                var emptyKeys = values.Keys.Where(key => string.IsNullOrEmpty(values[key])).ToList();
    
                emptyKeys.ForEach(key => values.Remove(key));
    
                return values;
            }
    
            /// <summary>
            /// Calculate signature using signer algorithm for the defined has algorithm
            /// </summary>
            /// <param name="hash"></param>
            /// <param name="signerAlgorithm"></param>
            /// <param name="hashAlgorithmOid">
            /// hash Algorithm Oid, for example:
            /// "2.16.840.1.101.3.4.2.1"
            /// </param>
            /// <param name="privateSigningKey">private key for signing</param>
            /// <returns></returns>
            public static byte[] Sign(byte[] hash, string signerAlgorithm, string hashAlgorithmOid, AsymmetricKeyParameter privateSigningKey)
            {
    
                var digestAlgorithm = new AlgorithmIdentifier(new DerObjectIdentifier(hashAlgorithmOid), DerNull.Instance);
                var dInfo = new DigestInfo(digestAlgorithm, hash);
                byte[] digest = dInfo.GetDerEncoded();
    
                ISigner signer = SignerUtilities.GetSigner(signerAlgorithm);
                signer.Init(true, privateSigningKey);
                signer.BlockUpdate(digest, 0, digest.Length);
                byte[] signature = signer.GenerateSignature();
                return signature;
    
    /*  // Another way of signing
                if (signerAlgorithm == "RSA")
                {
                    // convert private key from BouncyCastle to System.Security :
                    RSA key = DotNetUtilities.ToRSA((RsaPrivateCrtKeyParameters)privateSigningKey);
                    using (var cryptoServiceProvider = new RSACryptoServiceProvider())
                    {
                        cryptoServiceProvider.ImportParameters(key.ExportParameters(true));
    
                        //
                        // Hash and sign the data. Pass a new instance of SHA1CryptoServiceProvider
                        // to specify the use of SHA1 for hashing.
                        byte[] signedData = cryptoServiceProvider.SignHash(hash, hashAlgorithmOid);
                        return signedData;
                    }
                }
    
                if (signerAlgorithm == "ECDSA")
                {
                    // convert private key from BouncyCastle to System.Security :
                    var bcKeyInfo = PrivateKeyInfoFactory.CreatePrivateKeyInfo(privateSigningKey);
                    var pkcs8Blob = bcKeyInfo.GetDerEncoded();
                    var key = CngKey.Import(pkcs8Blob, CngKeyBlobFormat.Pkcs8PrivateBlob);
    
                    using (ECDsaCng cryptoServiceProvider = new ECDsaCng(key))
                    {
                        cryptoServiceProvider.HashAlgorithm = CngAlgorithm.Sha256; //, hashAlgorithmOid);
    
                        byte[] signature = cryptoServiceProvider.SignHash(hash);
                        return signature;
                    }
                }
    
                throw new NotImplementedException(signerAlgorithm);
    */
            }
    
            /// <summary>
            /// Verify signature using self verification of Pkcs10CertificationRequest
            /// </summary>
            /// <param name="csrSigned"></param>
            private void Verify(byte[] csrSigned)
            {
                Assert.IsNotNull(csrSigned);
    
                var csr = new Pkcs10CertificationRequest(csrSigned);
    
                bool isValid = csr.Verify();
    
                Assert.IsTrue(isValid, "Verification failed");
            }
    
            /// <summary>
            /// Verify signature using specified signer
            /// </summary>
            /// <param name="csrSigned"></param>
            private void Verify2(byte[] csrSigned)
            {
                var csr = new Pkcs10CertificationRequestDelaySigned(csrSigned);
                var sigBytes = csr.Signature.GetBytes();//.GetDerEncoded();
                var data = csr.GetDataToSign();
                AsymmetricKeyParameter publicSigningKey = csr.GetPublicKey();
                var signerAlgorithm = csr.SignatureAlgorithm.Algorithm.Id;
    
                var s = SignerUtilities.GetSigner(signerAlgorithm);
                s.Init(false, publicSigningKey);
                s.BlockUpdate(data, 0, data.Length);
                bool isValidSignature = s.VerifySignature(sigBytes);
    
                Assert.IsTrue(isValidSignature, "ECDSA verification failed");
            }
    
            private void ByteArrayToFile(string fileName, byte[] byteArray)
            {
                if (!_enableWritingToFile) return;
    
                try
                {
                    fileName = @"C:\temp\delayCsrTest\" + fileName;
                    new FileInfo(fileName).Directory?.Create();
                    File.WriteAllBytes(fileName, byteArray);
                }
                catch (Exception ex)
                {
                    Console.WriteLine("Exception caught in process: {0}", ex);
                    throw;
                }
            }
        }
    
        /// <summary>
        /// Helper that stores private and public key-pair as required for signing and verification of signature
        /// </summary>
        class Keys
        {
            private static readonly SecureRandom Rand;
    
            private readonly string _keyAlgorithm;
    
            private readonly KeyGenerationParameters _keyGenerationParameters;
    
            private readonly IAsymmetricCipherKeyPairGenerator _keyPairGenerator;
    
            private AsymmetricCipherKeyPair _signKeyPair;
            public AsymmetricCipherKeyPair SignKeyPair => _signKeyPair ?? (_signKeyPair = MakeKeyPair());
    
            static Keys()
            {
                try
                {
                    Rand = new SecureRandom();
                }
                catch (Exception ex)
                {
                    throw new Exception(ex.ToString());
                }
            }
    
            public Keys(string keyAlgorithm)
            {
                _keyAlgorithm = keyAlgorithm;
                _keyGenerationParameters = CreateKeyGenerationParameters();
                _keyPairGenerator = CreateKeyPairGenerator();
            }
    
            private KeyGenerationParameters CreateKeyGenerationParameters()
            {
                SecureRandom random = Rand;
                //SecureRandom random = SecureRandom.GetInstance("SHA256PRNG");
    
                if (_keyAlgorithm == "RSA")
                {
                    return new RsaKeyGenerationParameters(BigInteger.ValueOf(65537), random, 2048, 25);
                }
    
                if (_keyAlgorithm == "ECDSA")
                {
                    return new ECKeyGenerationParameters(SecObjectIdentifiers.SecP256r1, random);
                }
    
                throw new NotSupportedException(_keyAlgorithm);
            }
    
            private IAsymmetricCipherKeyPairGenerator CreateKeyPairGenerator()
            {
                var keyPairGenerator = GeneratorUtilities.GetKeyPairGenerator(_keyAlgorithm);
                keyPairGenerator.Init(_keyGenerationParameters);
    
                return keyPairGenerator;
            }
    
            public AsymmetricCipherKeyPair MakeKeyPair()
            {
                return _keyPairGenerator.GenerateKeyPair();
            }
        }
        }
    

1 个答案:

答案 0 :(得分:1)

我发现问题与计算签名的方式有关。由于某种原因,它不适用于ECDSA,因此这里的解决方案适用于ECDSA和RSA(使用SHA-256哈希)。 简而言之,在计算签名时使用&#34; NONEwithECDSA&#34;或&#34; NONEwithRSA&#34;而且RSA要求在ECDSA中添加DigestInfo而不是bair签名(我还在想为什么?)。

 [TestMethod]
 public void TestDelaySigning()
 {
            const string hashAlgorithm = "SHA256";
            const string signAlgorithm = "ECDSA"; // or "RSA" 
            bool isRsa =false; // or true for RSA

           // Create CSR
            var signatureAlgorithm = hashAlgorithm + "with" + signAlgorithm; // "SHA256withECDSA" or "SHA256withRSA"
            byte[] octetData = CreateCsr(keys.SignKeyPair, signatureAlgorithm);
            byte[] csrWithPass = AppendPassword(octetData, "some-password");

            byte[] hash = BuildHash(csrWithPass, hashAlgorithm);

            // Sign the hash
            string singingAlgorithm2 = "NONEwith" + signAlgorithm;
            byte[] signature = Sign(hash, singingAlgorithm2, hashAlgorithm, keys.SignKeyPair.Private, isRsa );

            byte[] csrSigned = AppendSignature(csrWithPass, signature);

            Verify(csrSigned);
 }

        public byte[] BuildHash(byte[] csr, string algorithm)
        {
            var originalCsr = new Pkcs10CertificationRequestDelaySigned(csr);
            byte[] dataToSign = originalCsr.GetDataToSign();
            byte[] digest = DigestUtilities.CalculateDigest(algorithm, dataToSign);
            return digest;
        }

        public static byte[] Sign(byte[] hash, string signerAlgorithm, string hashAlgorithm, 
                                                     AsymmetricKeyParameter privateSigningKey, bool isRsa)
        {
            if (isRsa)
        {
            var hashAlgorithmOid = DigestUtilities.GetObjectIdentifier(hashAlgorithm).Id;

            var digestAlgorithm = new AlgorithmIdentifier(new DerObjectIdentifier(hashAlgorithmOid), DerNull.Instance);
            var dInfo = new DigestInfo(digestAlgorithm, hash);
            byte[] digest = dInfo.GetDerEncoded();
            hash = digest;
        }

        ISigner signer = SignerUtilities.GetSigner(signerAlgorithm);
        signer.Init(true, privateSigningKey);
        signer.BlockUpdate(hash, 0, hash.Length);
        byte[] signature = signer.GenerateSignature();
        return signature;
      }
 }