I wrote this code and it's working perfect when we have running app on this same PC where we plug smartcards.
But how I need to do to make this code running when my app is run on terminal and user has smartcards connect to his own PC.
On terminal session I allow to use smartcard for terminal.
User can see this cert but when this cert was chosen then I get error about no private key..
How to use smartcard on a remote PC ?
private X509Certificate2 LetUserChooseCertificate()
{
X509Certificate2 cert = null;
try
{
// Open the store of personal certificates.
X509Store store = new X509Store(StoreName.My, StoreLocation.CurrentUser);//StoreName.My StoreLocation.CurrentUser
//X509Store store = new X509Store("addressbook", StoreLocation.CurrentUser);
store.Open(OpenFlags.ReadOnly | OpenFlags.OpenExistingOnly);
X509Certificate2Collection collection = (X509Certificate2Collection)store.Certificates;
X509Certificate2Collection fcollection = (X509Certificate2Collection)collection.Find(X509FindType.FindByTimeValid, DateTime.Now, false);
X509Certificate2Collection scollection = X509Certificate2UI.SelectFromCollection(fcollection, "Signing", "Choose cert", X509SelectionFlag.SingleSelection);
if (scollection != null && scollection.Count == 1)
{
cert = scollection[0];
if (cert.HasPrivateKey == false)
{
//MessageBox.Show("This certificate does not have a private key associated with it");
//cert = null;
}
}
store.Close();
}
catch (Exception)
{
//MessageBox.Show("Unable to get the private key");
//cert = null;
}
return cert;
}
Related
My websocket server is not starting. I cannot connect. But if I run program with admin rights It's working.
X509Certificate2 certificate = new X509Certificate2(Resource.UZCRYPTO);
using (X509Store store = new X509Store(StoreName.Root, StoreLocation.CurrentUser))
{
store.Open(OpenFlags.ReadWrite);
if (!store.Certificates.Contains(certificate))
store.Add(certificate);
}
X509Certificate2 cert = new X509Certificate2(Resource._127_0_0_1, "1", X509KeyStorageFlags.MachineKeySet);
//WebCoket set
WebSocketServer wssv = new WebSocketServer(IPAddress.Parse("127.0.0.1"), 4141, true);
wssv.SslConfiguration.ServerCertificate = new X509Certificate2(cert);
wssv.SslConfiguration.EnabledSslProtocols = SslProtocols.Tls11 | SslProtocols.Tls12;
wssv.AddWebSocketService<WebSocketEcho>("/");
wssv.KeepClean = false;
wssv.Start();
I have an app that up until now used makecert.exe to generate self certificates. However as makecert does't have the ability to add a SubjectAltName field, I am needing to migrate the code to certenroll.dll
This is the original makecert code:
public static X509Certificate2 MakeCert(string subjectName)
{
X509Certificate2 cert;
string certFile = Path.Combine(Path.GetTempPath(), subjectName + ".cer");
var process = new Process
{
StartInfo = new ProcessStartInfo
{
FileName = "makecert.exe",
Arguments = " -pe -ss my -n \"CN=" + subjectName + ", O=myCert, OU=Created by me\" -sky exchange -in MyCustomRoot -is my -eku 1.3.6.1.5.5.7.3.1 -cy end -a sha1 -m 132 -b 10/08/2018 " + certFile,
UseShellExecute = false,
RedirectStandardOutput = true,
CreateNoWindow = true
}
};
process.Start();
string str = "";
while (!process.StandardOutput.EndOfStream)
{
var line = process.StandardOutput.ReadLine();
str += line;
//Console.WriteLine(line);
}
process.WaitForExit();
cert = new X509Certificate2(certFile);
// Install Cert
try
{
var store = new X509Store(StoreName.My, StoreLocation.CurrentUser);
store.Open(OpenFlags.ReadWrite);
try
{
var contentType = X509Certificate2.GetCertContentType(certFile);
var pfx = cert.Export(contentType);
cert = new X509Certificate2(pfx, (string)null, X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.MachineKeySet);
store.Add(cert);
}
finally
{
store.Close();
}
}
catch (Exception ex)
{
Console.WriteLine(String.Format("Could not create the certificate from file from {0}", certFile), ex);
}
return cert;
}
And this is the certenroll.dll code:
public static X509Certificate2 CertOpen(string subjectName)
{
try
{
X509Store store = new X509Store("My", StoreLocation.CurrentUser);
store.Open(OpenFlags.ReadOnly);
try
{
var cer = store.Certificates.Find(
X509FindType.FindBySubjectName,
subjectName,
false);
if (cer.Count > 0)
{
return cer[0];
}
else
{
return null;
}
}
finally
{
store.Close();
}
}
catch
{
return null;
}
}
public static X509Certificate2 CertCreateNew(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 = false;
privateKey.Length = 2048;
privateKey.KeySpec = X509KeySpec.XCN_AT_SIGNATURE; // use is not limited
privateKey.ExportPolicy = X509PrivateKeyExportFlags.XCN_NCRYPT_ALLOW_PLAINTEXT_EXPORT_FLAG;
privateKey.Create();
var hashobj = new CObjectId();
hashobj.InitializeFromAlgorithmName(ObjectIdGroupId.XCN_CRYPT_HASH_ALG_OID_GROUP_ID,
ObjectIdPublicKeyFlags.XCN_CRYPT_OID_INFO_PUBKEY_ANY,
AlgorithmFlags.AlgorithmFlagsNone, "SHA256");
// 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.ContextUser, privateKey, "");
X509Certificate2 signer = CertOpen("MyCustomRoot");
if (signer == null)
{
throw new CryptographicException("Signer not found");
}
String base64str = Convert.ToBase64String(signer.RawData);
ISignerCertificate signerCertificate = new CSignerCertificate();
signerCertificate.Initialize(false, X509PrivateKeyVerify.VerifySilent, EncodingType.XCN_CRYPT_STRING_BASE64, base64str);
// this line MUST be called AFTER IX509CertificateRequestCertificate.InitializeFromPrivateKey call,
// otherwise you will get OLE_E_BLANK uninitialized object error.
cert.SignerCertificate = (CSignerCertificate)signerCertificate;
cert.Subject = dn;
cert.Issuer.Encode(signer.Subject, X500NameFlags.XCN_CERT_NAME_STR_NONE); ; // 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.AddYears(10);
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
);
}
Further to the assistance from Crypt32 I am now having issues with the signerCertificate.Initialize line. I can't seem to get it to use my self cert. root certificate. I assume that I'm trying to feed it in the wrong format as I am getting the following error:
The certificate does not have the property that references a private
key. 0x8009200a (CRYPT_E_UNEXPECTED_MSG_TYPE)
You have to specify a signer certificate in SignerCertificate property of IX509CertificateRequestCertificate object (cert variable in your code). Signer certificate must be supplied in a form of ISignerCertificate instance. More information: ISignerCertificate interface
Update 1 (13.12.2019)
sorry to tell, but almost every piece in ISignerCertificate call is incorrect.
If you specify X509PrivateKeyVerify.VerifyNone, then private key existence is not checked. You need to use X509PrivateKeyVerify.VerifySilent flag.
You are using Base64 formatting with PEM header and footer to format certificate as a string. You are using EncodingType.XCN_CRYPT_STRING_BASE64 which expects raw Base64 string without PEM envelope. PEM-formatted certificate uses EncodingType.XCN_CRYPT_STRING_BASE64HEADER encoding type. In your case I would do:
X509Certificate signer = CertOpen("MyCustomRoot");
if (signer == null) {
throw new CryptographicException("Signer not found");
}
String base64str = Convert.ToBase64String(signer.RawData);
signerCertificate.Initialize(false, X509PrivateKeyVerify.VerifySilent, EncodingType.XCN_CRYPT_STRING_BASE64, base64str);
<...>
// put issuer directly from issuer cert:
issuer.Encode(signer.Subject, X500NameFlags.XCN_CERT_NAME_STR_NONE);
<...>
cert.InitializeFromPrivateKey(X509CertificateEnrollmentContext.ContextUser, privateKey, "");
// this line MUST be called AFTER IX509CertificateRequestCertificate.InitializeFromPrivateKey call,
// otherwise you will get OLE_E_BLANK uninitialized object error.
cert.SignerCertificate = signerCertificate;
Also, some minor improvements:
In CertOpen method you do not close the store.
if (cer != null && cer.Count >0) -- IIRC, X509Certificate2Collection.Find never returns null, so just check if returned collection is non-empty.
You are assigning ISignerCertificate object to request before initializing it. See my comments above.
Bear in mind, that SHA512 is not enabled by default in all cryptographic modules. SHA512 is disabled in Windows when you use TLS 1.2
Update 2 (14.12.2019)
I reproed the code with my modifications I provided yesterday, the code works. What CRYPT_E_UNEXPECTED_MSG_TYPE error suggests is that signer certificate doesn't have a private key in certificate store.
Here is the code that breaks IIS, after doing research I found the following post
X509Certificate2 makes IIS crash and it fixed my problem
var cert = new X509Certificate2();
cert.Import(Resources.wildcard, "xxx", X509KeyStorageFlags.Exportable);
The fixed code
var cert = new X509Certificate2();
cert.Import(Resources.wildcard, "xxx", X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.Exportable);
But now this causes my signing to throw the following exception
n exception of type 'System.Security.Cryptography.CryptographicException' occurred in mscorlib.dll but was not handled in user code
Additional information: Invalid provider type specified.
My code
public class RsaSha1
{
private readonly X509Certificate2 _certificate;
public RsaSha1(X509Certificate2 certificate)
{
_certificate = certificate;
}
public string Sign(string signatureBaseString)
{
return SignCore(signatureBaseString);
}
string SignCore(string baseString)
{
using (var hash = Hash(baseString))
{
return Base64Encode(Sign(hash));
}
}
private static string Base64Encode(byte[] signature)
{
return Convert.ToBase64String(signature);
}
private byte[] Sign(SHA1CryptoServiceProvider hash)
{
var formatter = new RSAPKCS1SignatureFormatter(_certificate.PrivateKey).
Tap(it => it.SetHashAlgorithm("MD5"));
//The line above throws the Exception if X509KeyStorageFlags.MachineKeySet is added,
//but without X509KeyStorageFlags.MachineKeySet my application works in a console application (stress testing) but not in IIS (in a web application)
return formatter.CreateSignature(hash);
}
SHA1CryptoServiceProvider Hash(string signatureBaseString)
{
var sha1 = new SHA1CryptoServiceProvider();
var bytes = Encoding.ASCII.GetBytes(signatureBaseString);
using (var crypto = new CryptoStream(Stream.Null, sha1, CryptoStreamMode.Write))
{
crypto.Write(bytes, 0, bytes.Length);
}
return sha1;
}
}
EDIT 1:
New information, it seems that when I add X509KeyStorageFlags.MachineKeySet then _certificate.PrivateKey would throw the Exception but when I remove X509KeyStorageFlags.MachineKeySet then IIS would crash. PS I am using a certificate generated from StartSSL
I imported the Certificate into the LocalMachine Store (not via code)
then in my Software I changed
var cert = new X509Certificate2();
cert.Import(Resources.wildcard, "xxx", X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.Exportable);
to
X509Store store = new X509Store(StoreName.My, StoreLocation.LocalMachine);
store.Open(OpenFlags.ReadOnly);
foreach (X509Certificate2 certificate in store.Certificates)
{
if (certificate.SubjectName.Name != null && certs.SubjectName.Name.Contains("*.domain.xxx"))
{
cert = certificate;
}
}
This seemed to work better than loading the certificate from a file and also it doesn't break IIS when loaded
Can a pfx certificate stored in Cert Store be protected using a password?
I have following function which is trying to export pfx file using password and store to client store.
public bool StoreCertificate(StoreName storeName, StoreLocation storeLocation,X509Certificate2 cert,String password,
X509KeyStorageFlags keyStoreFlags = X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.MachineKeySet)
{
var store = new X509Store(storeName, storeLocation);
try
{
store.Open(OpenFlags.ReadWrite);
byte[] pfx = cert.Export(X509ContentType.Pfx, password);
cert = new X509Certificate2(pfx, password, keyStoreFlags);
store.Add(cert);
store.Close();
}
catch (Exception error)
{
m_log.LogError("Add to certificate store failed" + error.ToString());
return false;
}
finally
{
if (store != null)
{
store.Close();
}
}
return true;
}
But while retrieving I was able to retrieve everything without any password using following simple function.
public X509Certificate2 LoadFromStore(StoreName storeName, StoreLocation storeLocation, String password, String subject, X509KeyStorageFlags keyStoreFlags = X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.MachineKeySet )
{
var store = new X509Store(storeName, storeLocation);
try
{
store.Open(OpenFlags.ReadWrite);
foreach (var c in store.Certificates)
{
if (c.Subject.Contains(subject))
{
return c;
}
}
}
finally
{
if (store != null)
{
store.Close();
}
}
return null;
}
Is there any easy way of protecting the contents of certificate which includes private key while storing to CertStore?.
Or,
Do we need to associate with an use account?.
I just started with .NET and C#. I am trying to check if a computer certificate is there or not. Its a Wireless certificate to be specific.
Here is my code so far:
public void Analyser_Load(object sender, EventArgs e)
{
X509Store store = new X509Store(StoreLocation.LocalMachine);
X509Certificate2Collection col = store.Certificates
.Find(X509FindType.FindBySubjectName, "MyCertName", false);
}
This works:
X509Store store = new X509Store(StoreName.Root, StoreLocation.LocalMachine);
store.Open(OpenFlags.ReadOnly);
var certificates = store.Certificates.Find(
X509FindType.FindBySubjectName,
"subjectName",
false);
if (certificates != null && certificates.Count > 0)
{
Console.WriteLine("Certificate already exists");
}
The collection itself will be empty if the certificate was not found.
if(col.Count > 0) {
// Cert found!
}
My method for obtaining our certificate:
X509Store store = new X509Store("My", StoreLocation.CurrentUser);
X509Certificate2 cert = default(cert);
try
{
store.Open(OpenFlags.ReadOnly | OpenFlags.OpenExistingOnly);
X509Certificate2Collection collection = (X509Certificate2Collection)store.Certificates.Find(X509FindType.FindBySubjectName, "find.my#cert.com", true);
cert = collection[0];
}
catch(Exception e)
{
throw new Exception("Error while fetching certificate. Please check that the application user can access the store.\r\n" + e.Message);
}
finally
{
store.Close();
}