4

I have an x509 certificate with a public and private key that is stored on a safenet usb token.

I have some data I want to sign. I need to use the public key of the certificate to verify the signature.

Ultimate code doing the signing with my own self signed certificate:

RSACryptoServiceProvider rsa1 = (RSACryptoServiceProvider)useCertificate.PrivateKey;
byte[] digitalSignature = rsa1.SignHash(hash, CryptoConfig.MapNameToOID("SHA1"));

And the code to verify using the public key of the certificate:

RSACryptoServiceProvider rsa = (RSACryptoServiceProvider)useCertificate.PublicKey.Key;
Verified = rsa.VerifyHash(hash, CryptoConfig.MapNameToOID("SHA1"), digitalSignature);

With the self signed certificate this works fine. The signature I get back is 256
Bytes.

With the token using this code to obtain the signature and then verify it, I get only 128 Byte signature and the verify fails:

CspParameters csp = new CspParameters(1, "SafeNet RSA CSP");
csp.Flags = CspProviderFlags.UseDefaultKeyContainer;
csp.KeyNumber = (int)KeyNumber.Signature;
RSACryptoServiceProvider rsa1 = new RSACryptoServiceProvider(csp);

Verify code same as above.

I note that the certificate I want to use is the default in the token. Why am I only getting a 128 Byte signature back instead of 256? I suspect that is why it won't verify.

Do I need some other parameters and settings in my csp?

Thanks

* Update based on comments *

It's clear that I am using 1024 bits when I specify the csp.keyNumber = (int)KeyNumber.Signature - but this is the only way the token actually returns anything. Even though the token key size is 2048 bits and the key specification is AT_KEYEXCHANGE. When I use the exchange keynumber which I think is actually correct, then when I try to compute a signature I am prompted to login, but then I get an exception "The parameter is invalid". So I need one of 2 things as far as I can see:

1 - how to use the public key to verify the signature using 1024 bits (without the token - we need to verify on a machine without the token).

or

2 - how to set whatever is incorrect so that we can get passed the exception -- which I think is the better idea.

Does anyone have any advice on what I can do about this exception or what might be causing it?

Full exception details below:

HResult = -2147024809 Message = The parameter is incorrect. Stack Trace

at System.Security.Cryptography.CryptographicException.ThrowCryptographicException(Int32 hr) at System.Security.Cryptography.Utils.SignValue(SafeKeyHandle hKey, Int32 keyNumber, Int32 calgKey, Int32 calgHash, Byte[] hash, Int32 cbHash, ObjectHandleOnStack retSignature) at System.Security.Cryptography.Utils.SignValue(SafeKeyHandle hKey, Int32 keyNumber, Int32 calgKey, Int32 calgHash, Byte[] hash) at System.Security.Cryptography.RSACryptoServiceProvider.SignHash(Byte[] rgbHash, Int32 calgHash) at System.Security.Cryptography.RSACryptoServiceProvider.SignHash(Byte[] rgbHash, String str) at TE.Program.Main(String[] args) in z:\Work\compusolve\enctest\TE\TE\Program.cs:line 77

user2709214
  • 185
  • 2
  • 12
  • 1
    Unless data is lost, a 128 byte signature is for a 1024 bit key instead of a 2048 bit one; i.e. wrong key pair. – Maarten Bodewes Mar 30 '17 at 22:44
  • This helps I think. I know my key for verification is a 2048 bit key. So I'll dig into how to get the token to use the proper keypair. I'll report back here. – user2709214 Mar 31 '17 at 00:29
  • I changed the code for the signature to this: `RSACryptoServiceProvider rsa1 = new RSACryptoServiceProvider(2048, csp);` But it did not change the result - I still only get 128 bytes back. This should have required the use of a 2048 bit key. – user2709214 Mar 31 '17 at 10:24
  • No matter what I specify when creating the RsaCryptoServiceProvider the internal keysize is 1024. If I change the csp.KeyNumber to `(Int)KeyNumber.Exchange` instead of `Signature` then I get the 2048 keysize that I want, however, when I ask the device to compute the signature I get an exception "The Parameter is invalid". The KeySize on the device is 2048 bits. – user2709214 Mar 31 '17 at 10:57
  • Are you sure you can use the key for signature generation in the first place? Because AT_KEYEXCHANGE is not signature generation. – Maarten Bodewes Mar 31 '17 at 14:32
  • AT_KEYEXCHANGE can be uses for signature generation but is not limited to it. – user2709214 Mar 31 '17 at 15:32

2 Answers2

2

The answer to this is two fold. If you are using one of these devices, I found that in the registry under HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Cryptography\Defaults\Provider

There are 3 different providers. Each with identical settings for type and even image - the dll used. But selecting a different one, in my case Datakey RSP CSP, provided the 256 byte signature based on the 2048 bit key. You also have to ensure that the certificate you are using is the default certificate in the token. In my case there were two different certificates. I was verifying using one, but signing using another.

Complete source code for a test client is below:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Security.Cryptography.X509Certificates;
using System.Security.Cryptography;

namespace TE
{
class Program
{
    static void Main(string[] args)
    {
        try
        {

            // these variables should be changed to math your installation

            // find CSP's in this windows registry key:  HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Cryptography\Defaults\Provider
            string TokenCSPName = "Datakey RSA CSP";
            string TokenCertificateName = "ACME Inc";
            string NonTokenCertificateName = "SelfSigned";
            string certLocation = "Token"; // change to something else to use self signed "Token" for token
            // the certificate on the token should be installed into the local users certificate store
            // tokens will not store or export the private key, only the public key


            // find the certificate we want to use - there's no recovery if the certificate is not found

            X509Store store = new X509Store(StoreName.My, StoreLocation.CurrentUser);
            store.Open(OpenFlags.OpenExistingOnly);
            X509Certificate2Collection certificates = store.Certificates;
            X509Certificate2 certificate = new X509Certificate2();
            X509Certificate2 useCertificate = new X509Certificate2();
            if (certLocation == "Token")
            {
                for (int i = 0; i < certificates.Count; i++)
                {
                    certificate = certificates[i];
                    string subj = certificate.Subject;
                    List<X509KeyUsageExtension> extensions = certificate.Extensions.OfType<X509KeyUsageExtension>().ToList();
                    if (certificate.GetNameInfo(X509NameType.SimpleName, false).ToString() == TokenCertificateName)
                    {
                        for (int j = 0; j < extensions.Count; j++)
                        {
                            if ((extensions[j].KeyUsages & X509KeyUsageFlags.DigitalSignature) == X509KeyUsageFlags.DigitalSignature)
                            {
                                useCertificate = certificate;
                                j = extensions.Count + 1;
                            }

                        }

                    }
                }
            } else
            {
                for (int i = 0; i < certificates.Count; i++)
                {
                    certificate = certificates[i];
                    string subj = certificate.Subject;
                    List<X509KeyUsageExtension> extensions = certificate.Extensions.OfType<X509KeyUsageExtension>().ToList();
                    if (certificate.GetNameInfo(X509NameType.SimpleName, false).ToString() == NonTokenCertificateName)
                        useCertificate = certificate;
                }

            }
            CspParameters csp = new CspParameters(1, TokenCSPName);

            csp.Flags = CspProviderFlags.UseDefaultKeyContainer;
            csp.KeyNumber = (int)KeyNumber.Exchange;
            RSACryptoServiceProvider rsa1 = new RSACryptoServiceProvider(csp);
            string SignatureString = "Data that is to be signed";
            byte[] plainTextBytes = Encoding.ASCII.GetBytes(SignatureString);
            bool Verified = false;
            using (SHA1CryptoServiceProvider shaM = new SHA1CryptoServiceProvider())
            {
                // hash the data to be signed - you can use signData and avoid the hashing if you like

                byte[] hash = shaM.ComputeHash(plainTextBytes);
                // sign the hash
                byte[] digitalSignature = rsa1.SignHash(hash, CryptoConfig.MapNameToOID("SHA1"));
                // check your signature size here - if not 256 bytes then you may not be using the proper
                // crypto provider

                // Verify the signature with the hash

                RSACryptoServiceProvider rsa = (RSACryptoServiceProvider)useCertificate.PublicKey.Key;
                Verified = rsa.VerifyHash(hash, CryptoConfig.MapNameToOID("SHA1"), digitalSignature);
                if (Verified)
                {
                    Console.WriteLine("Signature Verified");
                }
                else
                {
                    Console.WriteLine("Signature Failed Verification");
                }
            }
        }
        catch (Exception e)
        {
            Console.WriteLine(e.Message);
        }
    }
  }
}
user2709214
  • 185
  • 2
  • 12
0

I have to challenge your assertion that it's actually the default key container key (you may have caused that one to be created the first time your code ran, since you didn't assert the UseExistingKey flag).

Assuming the certificate is in your cert store, run certutil -user -silent store my and find the certificate entry and check the Key Container value:

================ Certificate 11 ================
Serial Number: 0123456789abcdeffedcba9876543210
Issuer: CN=Intermediate Certificate Authority
 NotBefore: 10/21/2016 7:26 AM
 NotAfter: 10/21/2017 7:26 AM
Subject: CN=bartonjs
Non-root Certificate
Template:
Cert Hash(sha1): 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 10 11 12 13 14
  Key Container = le-Smartcard-987abcdf-6332-43-16531
  Provider = Microsoft Base Smart Card Crypto Provider

If you copy/paste whatever value that is and use it as the key container name your signatures will probably start being the correct size.

(If your certificate is in the machine store instead of the user store, omit the -user option)

bartonjs
  • 30,352
  • 2
  • 71
  • 111
  • The key container for the private key is on the token not the local store. I did ensure that it was the default certificate as well - there are two on the usb token. – user2709214 Mar 31 '17 at 17:03