I need to create a self-signed certificate (for local encryption - it's not used to secure communications), using C#.
I've seen some implementations that use P/Invoke with Crypt32.dll, but they are complicated and it's hard to update the parameters - and I would also like to avoid P/Invoke if at all possible.
I don't need something that is cross platform - running only on Windows is good enough for me.
Ideally, the result would be an X509Certificate2 object that I can use to insert into the Windows certificate store or export to a PFX file.
Since .NET 4.7.2 you can create self-signed certs using System.Security.Cryptography.X509Certificates.CertificateRequest.
For example:
using System;
using System.IO;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
public class CertificateUtil
{
static void MakeCert()
{
var ecdsa = ECDsa.Create(); // generate asymmetric key pair
var req = new CertificateRequest("cn=foobar", ecdsa, HashAlgorithmName.SHA256);
var cert = req.CreateSelfSigned(DateTimeOffset.Now, DateTimeOffset.Now.AddYears(5));
// Create PFX (PKCS #12) with private key
File.WriteAllBytes("c:\\temp\\mycert.pfx", cert.Export(X509ContentType.Pfx, "P#55w0rd"));
// Create Base 64 encoded CER (public key only)
File.WriteAllText("c:\\temp\\mycert.cer",
"-----BEGIN CERTIFICATE-----\r\n"
+ Convert.ToBase64String(cert.Export(X509ContentType.Cert), Base64FormattingOptions.InsertLineBreaks)
+ "\r\n-----END CERTIFICATE-----");
}
}
This implementation uses the CX509CertificateRequestCertificate COM object (and friends - MSDN doc) from certenroll.dll to create a self signed certificate request and sign it.
The example below is pretty straight forward (if you ignore the bits of COM stuff that goes on here) and there are a few parts of the code that are really optional (such as EKU) which are none-the-less useful and easy to adapt to your use.
public static X509Certificate2 CreateSelfSignedCertificate(string subjectName)
{
// create DN for subject and issuer
var dn = new CX500DistinguishedName();
dn.Encode("CN=" + subjectName, X500NameFlags.XCN_CERT_NAME_STR_NONE);
// create a new private key for the certificate
CX509PrivateKey privateKey = new CX509PrivateKey();
privateKey.ProviderName = "Microsoft Base Cryptographic Provider v1.0";
privateKey.MachineContext = true;
privateKey.Length = 2048;
privateKey.KeySpec = X509KeySpec.XCN_AT_SIGNATURE; // use is not limited
privateKey.ExportPolicy = X509PrivateKeyExportFlags.XCN_NCRYPT_ALLOW_PLAINTEXT_EXPORT_FLAG;
privateKey.Create();
// Use the stronger SHA512 hashing algorithm
var hashobj = new CObjectId();
hashobj.InitializeFromAlgorithmName(ObjectIdGroupId.XCN_CRYPT_HASH_ALG_OID_GROUP_ID,
ObjectIdPublicKeyFlags.XCN_CRYPT_OID_INFO_PUBKEY_ANY,
AlgorithmFlags.AlgorithmFlagsNone, "SHA512");
// add extended key usage if you want - look at MSDN for a list of possible OIDs
var oid = new CObjectId();
oid.InitializeFromValue("1.3.6.1.5.5.7.3.1"); // SSL server
var oidlist = new CObjectIds();
oidlist.Add(oid);
var eku = new CX509ExtensionEnhancedKeyUsage();
eku.InitializeEncode(oidlist);
// Create the self signing request
var cert = new CX509CertificateRequestCertificate();
cert.InitializeFromPrivateKey(X509CertificateEnrollmentContext.ContextMachine, privateKey, "");
cert.Subject = dn;
cert.Issuer = dn; // the issuer and the subject are the same
cert.NotBefore = DateTime.Now;
// this cert expires immediately. Change to whatever makes sense for you
cert.NotAfter = DateTime.Now;
cert.X509Extensions.Add((CX509Extension)eku); // add the EKU
cert.HashAlgorithm = hashobj; // Specify the hashing algorithm
cert.Encode(); // encode the certificate
// Do the final enrollment process
var enroll = new CX509Enrollment();
enroll.InitializeFromRequest(cert); // load the certificate
enroll.CertificateFriendlyName = subjectName; // Optional: add a friendly name
string csr = enroll.CreateRequest(); // Output the request in base64
// and install it back as the response
enroll.InstallResponse(InstallResponseRestrictionFlags.AllowUntrustedCertificate,
csr, EncodingType.XCN_CRYPT_STRING_BASE64, ""); // no password
// output a base64 encoded PKCS#12 so we can import it back to the .Net security classes
var base64encoded = enroll.CreatePFX("", // no password, this is for internal consumption
PFXExportOptions.PFXExportChainWithRoot);
// instantiate the target class with the PKCS#12 data (and the empty password)
return new System.Security.Cryptography.X509Certificates.X509Certificate2(
System.Convert.FromBase64String(base64encoded), "",
// mark the private key as exportable (this is usually what you want to do)
System.Security.Cryptography.X509Certificates.X509KeyStorageFlags.Exportable
);
}
The result can be added to a certificate store using X509Store or exported using the X509Certificate2 methods.
For a fully managed and not tied to Microsoft's platform, and if you're OK with Mono's licensing, then you can look at X509CertificateBuilder from Mono.Security. Mono.Security is standalone from Mono, in that it doesn't need the rest of Mono to run and can be used in any compliant .Net environment (e.g. Microsoft's implementation).
Another option is to use the CLR Security extensions library from CodePlex, which implements a helper function to generate self-signed X.509 certificates:
X509Certificate2 cert = CngKey.CreateSelfSignedCertificate(subjectName);
You can also look at the implementation of that function (in CngKeyExtensionMethods.cs) to see how to create the self-signed certificate explicitly in managed code.
You can use the free PluralSight.Crypto library to simplify programmatic creation of self-signed X.509 certificates:
using (CryptContext ctx = new CryptContext())
{
ctx.Open();
X509Certificate2 cert = ctx.CreateSelfSignedCertificate(
new SelfSignedCertProperties
{
IsPrivateKeyExportable = true,
KeyBitLength = 4096,
Name = new X500DistinguishedName("cn=localhost"),
ValidFrom = DateTime.Today.AddDays(-1),
ValidTo = DateTime.Today.AddYears(1),
});
X509Certificate2UI.DisplayCertificate(cert);
}
PluralSight.Crypto requires .NET 3.5 or later.
If it helps anyone else, I needed to generate a test certificate in PEM format (so needed crt and key files), using the answer from Duncan Smart, I produced the following...
public static void MakeCert(string certFilename, string keyFilename)
{
const string CRT_HEADER = "-----BEGIN CERTIFICATE-----\n";
const string CRT_FOOTER = "\n-----END CERTIFICATE-----";
const string KEY_HEADER = "-----BEGIN RSA PRIVATE KEY-----\n";
const string KEY_FOOTER = "\n-----END RSA PRIVATE KEY-----";
using var rsa = RSA.Create();
var certRequest = new CertificateRequest("cn=test", rsa, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
// We're just going to create a temporary certificate, that won't be valid for long
var certificate = certRequest.CreateSelfSigned(DateTimeOffset.Now, DateTimeOffset.Now.AddDays(1));
// export the private key
var privateKey = Convert.ToBase64String(rsa.ExportRSAPrivateKey(), Base64FormattingOptions.InsertLineBreaks);
File.WriteAllText(keyFilename, KEY_HEADER + privateKey + KEY_FOOTER);
// Export the certificate
var exportData = certificate.Export(X509ContentType.Cert);
var crt = Convert.ToBase64String(exportData, Base64FormattingOptions.InsertLineBreaks);
File.WriteAllText(certFilename, CRT_HEADER + crt + CRT_FOOTER);
}
Extending 0909EMs answer with SubjectAlternativeNames based on code found here: Understanding self-signed certificates in c#
public static void MakeCert(string certFilename, string keyFilename)
{
const string CRT_HEADER = "-----BEGIN CERTIFICATE-----\n";
const string CRT_FOOTER = "\n-----END CERTIFICATE-----";
const string KEY_HEADER = "-----BEGIN RSA PRIVATE KEY-----\n";
const string KEY_FOOTER = "\n-----END RSA PRIVATE KEY-----";
using var rsa = RSA.Create();
var certRequest = new CertificateRequest("cn=test", rsa, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
// Adding SubjectAlternativeNames (SAN)
var subjectAlternativeNames = new SubjectAlternativeNameBuilder();
subjectAlternativeNames .AddDnsName("test");
certRequest.CertificateExtensions.Add(subjectAlternativeNames.Build());
// We're just going to create a temporary certificate, that won't be valid for long
var certificate = certRequest.CreateSelfSigned(DateTimeOffset.Now, DateTimeOffset.Now.AddDays(1));
// export the private key
var privateKey = Convert.ToBase64String(rsa.ExportRSAPrivateKey(), Base64FormattingOptions.InsertLineBreaks);
File.WriteAllText(keyFilename, KEY_HEADER + privateKey + KEY_FOOTER);
// Export the certificate
var exportData = certificate.Export(X509ContentType.Cert);
var crt = Convert.ToBase64String(exportData, Base64FormattingOptions.InsertLineBreaks);
File.WriteAllText(certFilename, CRT_HEADER + crt + CRT_FOOTER);
}
And for definition of the usage of a key using X509KeyUsageExtension look here https://stackoverflow.com/a/48210587/226278
This is the Powershell version on how to create a certificate. You can use it by executing the command.
Check https://technet.microsoft.com/itpro/powershell/windows/pkiclient/new-selfsignedcertificate
Edit: forgot to say that after you create the certificate, you can use the Windows "manage computer certificates" program, to export the certificate to .CER or other type.
Related
I want to ask a qusetion according to my code, My code is as bellow:
in AuthenticateAsServer I get "The server mode SSL must use a certificate with the associated private key" error cause privatekey is not in my certificate and also privatekey is not extractable from the HSM, would you please guid me what is the solution here?
static void ProcessClient(TcpClient client)
{
SslStream sslClientStream = new SslStream(client.GetStream(), true, AllowAnyServerCertificate, null, EncryptionPolicy.RequireEncryption);
try
{
X509Certificate2 _HsmserverCertificate = null;
string pkcs11LibraryPath = "C:\\Program Files (x86)\\nCipher\\nfast\\toolkits\\pkcs11\\cknfast-64.dll";
Pkcs11InteropFactories factories = new Pkcs11InteropFactories();
using (IPkcs11Library pkcs11Library = factories.Pkcs11LibraryFactory.LoadPkcs11Library(factories, pkcs11LibraryPath, AppType.MultiThreaded))
{
ISlot slot = HelpersMethods.GetUsableSlot(pkcs11Library);
using (Net.Pkcs11Interop.HighLevelAPI.ISession session = slot.OpenSession(SessionType.ReadWrite))
{
session.Login(CKU.CKU_USER, #"1234");
var certificate = ReadCertificates(slot, session)[0];
_HsmserverCertificate = new X509Certificate2(certificate.CkaValue);
session.Logout();
}
}
sslClientStream.ReadTimeout = glb_intReciveTimeOut;
sslClientStream.WriteTimeout = glb_intSendTimeOut;
sslClientStream.AuthenticateAsServer(_HsmserverCertificate,
clientCertificateRequired: false,
SslProtocols.Tls12,
checkCertificateRevocation: true);
}
}
Unwritten rule in .NET world: If you want to use an instance X509Certificate2 class in SSL connection then you cannot create it manually but you need to acquire it from X509Store.
X509Store class provides access to all certificates propagated into windows certificate store. Take a look at your device documentation for more details on how to propagate your device certificates into windows certificate store. Sometimes it is also referred to as CAPI, CSP, CNG, KSP etc. If you are unfamiliar with those terms then your best bet is to contact device vendor support.
I've written an ASP.NET Core 2.0 website in C# and have Facebook authentication enabled, so it requires HTTPS. I'm using the native Kestrel web server to host the site and have a listener set to take the PFX certificate per MS' documentation. I can't seem to find a way for Kestrel to recognize the private key after recall from Key Vault. I know it's present, as I wrote two debug statements that indicate it is, in fact present.
This is the function that I'm using to retrieve the secret, which is working.
public static async Task<X509Certificate2> GetKeyVaultCert()
{
X509Certificate2 pfx;
try
{
var kvClient = new KeyVaultClient(new KeyVaultClient.AuthenticationCallback(GetToken));
var secret = await kvClient
.GetSecretAsync("https://macscampvault.vault.azure.net/secrets/letsencrypt").ConfigureAwait(false);
byte[] bytes;
if(secret.ContentType == "application/x-pkcs12")
bytes = Convert.FromBase64String(secret.Value);
else
{
bytes = new byte[0];
Console.WriteLine("secret is not PFX!!");
throw new ArgumentException("This is not a PFX string!!");
}
var password = new SecureString();
var coll = new X509Certificate2Collection();
coll.Import(bytes, null, X509KeyStorageFlags.Exportable);
pfx = coll[0];
// File output added in case I end up needing to write cert to container
// File.WriteAllBytes(Directory.GetCurrentDirectory().ToString() + "/Macs.pfx", bytes);
Console.WriteLine(pfx.HasPrivateKey);
Console.WriteLine(pfx.GetRSAPrivateKey());
}
catch (Exception ex)
{
Console.WriteLine($"There was a problem during the key vault operation\n{ex.Message}");
throw;
}
return pfx;
}
The debug statements after the assignment call pfx = coll[0]; tell me that this private key exists, but when I try to connect to the website using lynx https://localhost I receive the following exception:
System.NotSupportedException: The server mode SSL must use a certificate with the associated private key.
So, how do I use the private key? Here's a gist to the file in question.
I already was helped by How to serialize and deserialize a PFX certificate in Azure Key Vault? but after following it, I got to this state.
In your gist you have the following code:
var keyVaultCert = GetKeyVaultCert().Result ??
throw new ArgumentNullException("GetKeyVaultCert().Result");
pfx = new X509Certificate2(keyVaultCert.RawData);
The second line there removes the private key, because the RawData property just returns the DER encoded X.509 object.
keyVaultCert is already an X509Certificate2 with a private key, you probably want to just use it.
pfx = GetKeyVaultCert().Result ?? throw etc;
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
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);
}
}
}
}
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)
I am trying to use nShield from Thales to generate pair of asymmetric keys on it.
I have found the following example on msdn:
CspParameters csp = new CspParameters(1, "eToken Base Cryptographic Provider");
csp.Flags = CspProviderFlags.UseDefaultKeyContainer;
try
{
RSACryptoServiceProvider rsa = new RSACryptoServiceProvider(csp);
key = rsa.ToXmlString(true);
}
catch(Exception ex )
{
string s = ex.Message;
}
I can use KeySafe to succesfully connect and generate key-pairs on the HSM.
The code above throws the following exception:
System.Security.Cryptography.CryptographicException
"Invalid Signature." System.Security.Cryptography.CryptographicException
I have the feeling that I am not setting the correct second parameter in the CspParameters constructor. This is what it says in the example:
// The 1st parameter comes from HKEY_LOCAL_MACHINE\Software\Microsoft\Cryptography\Defaults\Provider Types.
// The 2nd parameter comes from HKEY_LOCAL_MACHINE\Software\Microsoft\Cryptography\Defaults\Provider.
I don't see any nCipher or nShield or Thales or anything like that there.
Edit:
Working test:
RSACryptoServiceProvider rsa = new RSACryptoServiceProvider(csp);
byte[] data = Encoding.ASCII.GetBytes("string");
byte[] enc = rsa.Encrypt(data, false);
String dec = Encoding.ASCII.GetString(rsa.Decrypt(enc, false));
key = rsa.ToXmlString(true);
You need to run nCipher CSP install wizard which is located under Start > All Programs > nCipher in order to register nCipher CSP in your operating system. After that mentioned registry entries will be available and you will be able to read exact CSP name from them.
How can I ensure to I am accesing the Certificates from my SmartCard and not form my personal certificate store in c#?
and How can I make my RSACryptoProvider to utilize my smart card certificate private key?
thanks
Wally
Sometimes, especially if you are not using default key container name on the smart card (recommended by Microsoft), certificates are not copied to local certificate store. The solution is to use crypto api to access the key with KP_CERTIFICATE, construct certificate from the retrieved data, and assign it a new RSACryptoServiceProvider constructed using your own key container name.
The pseudo C# code follows:
int reti = CryptoApi.CryptGetUserKey(_hprovider, keytype, ref userKey);
if (reti)
{
reti =CryptoApi.CryptGetKeyParam(_userKey, KP_CERTIFICATE, ref pbdata, ref pwddatalen, 0);
}
if (reti || pwddatalen>0)
{
byte[] data = new byte[pwddatalen];
ret = CryptoApi.CryptGetKeyParam(_userKey, KP_CERTIFICATE, data, ref pwddatalen, 0);
if (ret)
{
X509Certificate2 c = new X509Certificate2(data);
X509Store store = new X509Store(StoreName.My, StoreLocation.CurrentUser);
store.Open(OpenFlags.ReadOnly);
X509Certificate2Collection col = store.Certificates.Find(X509FindType.FindByThumbprint, c.Thumbprint, validonly);
store.Close();
if (col.Count != 1)
{
//not found in store - CSP didn't copy it
c.PrivateKey = PrivateKey(keytype);
return c;
}
else
{
return col[0];
}
}
}
private RSACryptoServiceProvider PrivateKey (KeyType keytype)
{
CspParameters csparms = new CspParameters();
csparms.KeyContainerName = _containerName;
csparms.ProviderName = _provider;
csparms.ProviderType = 1;
csparms.Flags = CspProviderFlags.UseMachineKeyStore | CspProviderFlags.UseExistingKey;
csparms.KeyNumber = (int)keytype;
return new RSACryptoServiceProvider(csparms);
}
You will need to go through your Cryptographic Service Provider (CSP) for your smartcard. On Windows (2000, XP, and Vista) any time you insert your smartcard into a smartcard reader all the certificates on it are propogated to your personal certificate store. Your private key stays on your smart card. What that means is if you use your certificate (for example to digitally sign an e-mail) then you are prompted to insert your smart card. If your smart card requires a PIN you will be asked to input it. The reason for this is that there is one location for applications to look for user certificates, your personal certificate store, so applications don't have to be rewritten just to handle certificates on smartcards.