Disclaimer: I am 2 days into reading about Certificates/RSA Algorithms and Encrypt/Decrypt.
I am trying to do a small app that communicates with Windows Key Store ( Certificate Store ) and where I should be able to read certificates/add certificates.
I have created a method to add a certificate.
public void AddKey()
{
CngKey cngKey;
CngKeyCreationParameters cng = new CngKeyCreationParameters
{
KeyUsage = CngKeyUsages.AllUsages
};
if (!CngKey.Exists(KEY_NAME))
{
cngKey = CngKey.Create(CngAlgorithm.Rsa, KEY_NAME, cng);
}
else
{
cngKey = CngKey.Open(KEY_NAME);
}
RSACng rsaKey = new RSACng(cngKey)
{
KeySize = 2048
};
byte[] rsaPrvKeyExport = rsaKey.Key.Export(CngKeyBlobFormat.GenericPrivateBlob);
byte[] rsaPubKeyExport = rsaKey.Key.Export(CngKeyBlobFormat.GenericPublicBlob);
CngKey cngPrv = CngKey.Import(rsaPrvKeyExport, CngKeyBlobFormat.GenericPrivateBlob);
CngKey cngPub = CngKey.Import(rsaPubKeyExport, CngKeyBlobFormat.GenericPublicBlob);
//var signed = Sign512(Constants.STRING_TO_ENCODE.ToByteArray(), rsaPrvKeyExport);
string exportPrivateKey = Convert.ToBase64String(rsaKey.ExportPkcs8PrivateKey());
string pemString = $"{Constants.RSA_KEY_HEADER}\n{exportPrivateKey}\n{Constants.RSA_KEY_FOOTER}";
X509Store store = new X509Store(StoreName.My, StoreLocation.LocalMachine);
store.Open(OpenFlags.ReadWrite);
var certificate = BuildSelfSignedServerCertificate(rsaKey);
var thumbprint = certificate.Thumbprint;
store.Certificates.Add(certificate);
store.Close();
}
I know not all lines in this code are needed, but I am in the learning process.
So what I do here is create a cngKey
Open the store.
Create a Certificate from my cngKey
Add the certificate to the store
Close the store.
I generate a certificate from a cngKey using this code
private X509Certificate2 BuildSelfSignedServerCertificate(RSA key)
{
X500DistinguishedName distinguishedName = new X500DistinguishedName($"CN={Constants.CERTIFICATE_NAME}");
var request = new CertificateRequest(distinguishedName, key, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
request.CertificateExtensions.Add(
new X509KeyUsageExtension(X509KeyUsageFlags.DataEncipherment | X509KeyUsageFlags.KeyEncipherment | X509KeyUsageFlags.DigitalSignature, false));
request.CertificateExtensions.Add(
new X509EnhancedKeyUsageExtension(
new OidCollection { new Oid("1.3.6.1.5.5.7.3.1") }, false));
//request.CertificateExtensions.Add(sanBuilder.Build());
var certificate = request.CreateSelfSigned(new DateTimeOffset(DateTime.UtcNow.AddDays(-1)), new DateTimeOffset(DateTime.UtcNow.AddDays(3650)));
certificate.FriendlyName = Constants.CERTIFICATE_NAME;
return new X509Certificate2(certificate.Export(X509ContentType.Pfx, "WeNeedASaf3rPassword"), "WeNeedASaf3rPassword", X509KeyStorageFlags.MachineKeySet);
}
This gives no error, but when I open the certificate store I can't find it
I also tried to retrive it programmaticaly
using this bit of code
X509Store store = new X509Store(StoreName.My, StoreLocation.LocalMachine);
store.Open(OpenFlags.ReadOnly);
var certificates = store.Certificates;
but my certificate is not here.
What am I missing, I am sure I am doing something wrong, but being new to this, I have no idea what.
Online I could not find a complete example of what I tried to achieve here.
As #dimitar.bogdanov pointed out in comments, you are not adding the certificate to the store:
store.Certificates.Add(certificate);
here you are adding the certificate only to disconnected collection. Any changes in this collection object will not reflect actual store state. Instead, you have to use X509Store.Add method to update actual store.
I'm trying to make a soap service.
To send request I need to use X509 Certificate. I used X509Certificate2 class for that purpose.
The problem is that its keys are System.Security.Cryptography.RSACryptoServiceProvider class that accepts only "http://www.w3.org/2000/09/xmldsig#rsa-sha1" signature algorithm.
In our case sha256 signature algorithm is needed.
Have you got any idea how can I deal with that?
Code looks like this:
SoapService.GetOrderStatusRequest request = new SoapService.GetOrderStatusRequest()
{
orderId = Int32.Parse(txtID.Text),
requestHeader = new SoapService.RequestHeader()
{
institutionId = 123,
requestId = "aeacbff8-ba6d-4a01-8e76-0b4384c24721",
system = "Test"
}
};
var cert = new X509Certificate2(AppDomain.CurrentDomain.BaseDirectory + "//cert.p12", "Passs123");
client.ChannelFactory.Credentials.ClientCertificate.Certificate = cert;
client.ClientCredentials.ClientCertificate.Certificate = cert;
SoapService.GetOrderStatusResponse response = client.getOrderStatus(request);
txtResult.Text = response.order.name;
Edit1.
I found out this is not problem with X509Certificate2 configuration, but with binding configuration... with message security to be exac.
Unfortunately I still have some errors :/
I have a web service which need the body signing on all the requests. so i wonder what is the best practice in order to do that? I'm using this method in order to sign the string but i guess its not the best way. because my private key never disposes:
using (var provider = new RSACryptoServiceProvider())
{
provider.ImportParameters(ProjectValues.PrivateKey.ExportParameters(true));
encryptByteList = provider.SignData(content, CryptoConfig.MapNameToOID("SHA256"));
}
I already read the certificate and private key once when the project loaded for first time. Like this:
Certificate = new X509Certificate2("my pfx path", "***", X509KeyStorageFlags.Exportable);
PrivateKey = (RSACryptoServiceProvider)Certificate.PrivateKey;
Then I found this method:
var cert = new X509Certificate2("my pfx path", "***", X509KeyStorageFlags.Exportable);
using (var rsa = cert.GetRSAPrivateKey())
{
encryptByteList = rsa.SignData(content, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
}
but this one works if i read certificate from file on every requests. obviously its not a efficient way
I'm having trouble with adding a new SSL certificate to my webService request.
var client = new RestClient(tokenUrl);
string certif = String.Format("{0}/client.cer", CertifPath);
string key = String.Format("{0}/client.key", CertifPath);
if (File.Exists(certif) && File.Exists(key))
{
X509Certificate2 cert = new X509Certificate2(certif, key);
X509CertificateCollection collection1 = new X509CertificateCollection();
collection1.Add(cert);
client.ClientCertificates = collection1;
}
I'm getting as a response : 400 no required ssl certificate was sent nginx !!!!.
In Addition : When i use PostMan Or SoapUI .I a must add a third secret key(passphrase) to be able to get response. ex :Add certificate via postman
My Question is How can i add this third parameter(secret key) in my request c# ?.
There is another way to implement certificate to my request ???
Can you use that neat little piece of code that let's you do exactly that :
byte[] certBuffer = Helpers.GetBytesFromPEM(publicCert, PemStringType.Certificate);
byte[] keyBuffer = Helpers.GetBytesFromPEM(privateKey, PemStringType.RsaPrivateKey);
X509Certificate2 certificate = new X509Certificate2(certBuffer, password);
RSACryptoServiceProvider prov = Crypto.DecodeRsaPrivateKey(keyBuffer);
certificate.PrivateKey = prov;
Solution :
I wasted a lot of time to search for how can include three informations (Certificat.cer,certif.key and the passphrase) in one Rest call.
The solution was easy:
the certificate Object is very flexible : i can encapsulate the
three information in one certificate called (.pfx) using OpenSSL.
you can install OpenSSL via : http://slproweb.com/download/Win64OpenSSL_Light-1_1_0g.exe
After this commande :
openssl pkcs12 -export -out certificate.pfx -inkey client.key -in client.cer.
A new file is generated : certificate.pfx.
So Now I can easly include my new certificate without any ERRORS :) .
I have a self-hosted WCF server running as a Windows service under the Local System account. I am trying to create a self-signed certificate programmatically in c# for use with a net.tcp endpoint using Message level security.
I am using the following code which is very closely based on the accepted answer in How to create a self-signed certificate using C#? with some small changes trying to solve my problem.
public static X509Certificate2 CreateSelfSignedCertificate(string subjectName, TimeSpan expirationLength)
{
// create DN for subject and issuer
var dn = new CX500DistinguishedName();
dn.Encode("CN=" + subjectName, X500NameFlags.XCN_CERT_NAME_STR_NONE);
CX509PrivateKey privateKey = new CX509PrivateKey();
privateKey.ProviderName = "Microsoft Strong Cryptographic Provider";
privateKey.Length = 1024;
privateKey.KeySpec = X509KeySpec.XCN_AT_KEYEXCHANGE;
privateKey.KeyUsage = X509PrivateKeyUsageFlags.XCN_NCRYPT_ALLOW_DECRYPT_FLAG | X509PrivateKeyUsageFlags.XCN_NCRYPT_ALLOW_KEY_AGREEMENT_FLAG;
privateKey.MachineContext = true;
privateKey.ExportPolicy = X509PrivateKeyExportFlags.XCN_NCRYPT_ALLOW_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, "SHA1");
// 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.Date;
// this cert expires immediately. Change to whatever makes sense for you
cert.NotAfter = cert.NotBefore + expirationLength;
//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)
// mark private key to go into the Machine store instead of the current users store
X509KeyStorageFlags.Exportable | X509KeyStorageFlags.MachineKeySet
);
}
And I store it with this code:
X509Store store = new X509Store(storeName, StoreLocation.LocalMachine);
store.Open(OpenFlags.ReadWrite);
store.Add(newCert);
store.Close();
This creates the certificate and puts it in the LocalMachine certificate store. The problem is that when I try to start the WCF service I get the following exception:
It is likely that certificate 'CN=myCertificate' may not have a private key that is capable of key exchange or the process may not have access rights for the private key. Please see inner exception for detail.
Inner exception: Keyset does not exist
The output of the FindPrivateKey sample (http://msdn.microsoft.com/en-us/library/aa717039%28v=vs.100%29.aspx) for my certificate is:
Private key directory:
C:\ProgramData\Microsoft\Crypto\RSA\MachineKeys
Private key file name:
f0d47c7826b8ef5148b6d412f1c40024_4a8a026f-58e4-40f7-b779-3ae9b6aae1a7
I can see this 1.43KB file in explorer. If I look at the properties|Security I see SYSTEM and Administrators both with Full control.
In researching this error I have seen many answers about the private key missing or incorrect permissions. I can't see what the problem is.
The really strange thing is that if I use the mmc Certificate plugin, go to the certificate and choose All Tasks|Manage Private Keys... I see the same security settings. After viewing this even if I just bring up the dialog and hit the Cancel button the certificate now works correctly in WCF. I can simply restart the service and everything runs perfectly.
If I create a certificate using MakeCert it works just fine from the start. I don't know what it does differently.
One other piece of information that may not be relevant is that the certificate not only gets put in the My store where I told it to get put, but it also gets put in the "Intermediate Certification Authorities" store. I don't know why or if it matters.
So...any ideas what I am doing wrong?
UPDATE: Well, this is not just a WCF issue. I essentially get the same problem when I try to use the certificate to bind to an endpoint with http.sys using HttpSetServiceConfiguration. The method returns 1312 - "A specified logon session does not exist. It may already have been terminated". This is actually not the real error. I saw in the Security Event log an Audit Failure that say this:
Cryptographic Parameters:
Provider Name: Microsoft Software Key Storage Provider
Algorithm Name: Not Available.
Key Name: {A23712D0-9A7B-4377-89DB-B1B39E3DA8B5}
Key Type: Machine key.
Cryptographic Operation:
Operation: Open Key.
Return Code: 0x80090011
0x80090011 is Object was not found. So this appears to be the same problem. Again, after I open the Manage Private Keys dialog for the certificate this works perfectly also.
I am still looking for the cause of the problem.
UPDATE #2: I was able to get this working using the accepted answer below. Interestingly, this code now seems to put the certificate in the Machine store without calling the X509Store code. I still call the code because I am not sure and it does not hurt anything. Here is the final code that I am using to create the certificate.
static public X509Certificate2 CreateSelfSignedCertificate(string subjectName, TimeSpan expirationLength)
{
// create DN for subject and issuer
var dn = new CX500DistinguishedName();
dn.Encode("CN=" + subjectName, X500NameFlags.XCN_CERT_NAME_STR_NONE);
CX509PrivateKey privateKey = new CX509PrivateKey();
privateKey.ProviderName = "Microsoft Strong Cryptographic Provider";
privateKey.Length = 2048;
privateKey.KeySpec = X509KeySpec.XCN_AT_KEYEXCHANGE;
privateKey.KeyUsage = X509PrivateKeyUsageFlags.XCN_NCRYPT_ALLOW_DECRYPT_FLAG | X509PrivateKeyUsageFlags.XCN_NCRYPT_ALLOW_KEY_AGREEMENT_FLAG;
privateKey.MachineContext = true;
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");
// 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.Date;
// this cert expires immediately. Change to whatever makes sense for you
cert.NotAfter = cert.NotBefore + expirationLength;
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)
// mark private key to go into the Machine store instead of the current users store
X509KeyStorageFlags.Exportable | X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.PersistKeySet
);
}
I had the same issue using the equivalent code in PowerShell. It appears that sometime the private key just disappears. I used Process Monitor and you can see the key file being deleted.
The way I solved this was to add X509KeyStorageFlags.PersistKeySet to the X509Certificate2 constructor.
I could not make this work, but I found an alternate solution. (Update December 2014: I have now gotten it to work using the accepted answer.)
I was able to use the PluralSight.Crypto library to achieve what I need. I had to modify the source code slightly to get the private key to store in the LocalMachine store. The changes I made were to the file CryptContext.cs. I changed the CreateSelfSignedCertificate method. Following is a snippet of code including the change that I made. In essence, I set the Flags member of the CryptKeyProviderInformation structure to set it to 0x20 (CRYPT_MACHINE_KEYSET) if the CryptContext object contains this value in its Flags.
byte[] asnName = properties.Name.RawData;
GCHandle asnNameHandle = GCHandle.Alloc(asnName, GCHandleType.Pinned);
int flags = 0; // New code
if ((this.Flags & 0x20) == 0x20) // New code
flags = 0x20; // New code
var kpi = new Win32Native.CryptKeyProviderInformation
{
ContainerName = this.ContainerName,
KeySpec = (int)KeyType.Exchange,
ProviderType = 1, // default RSA Full provider
Flags = flags // New code
};
Then I use the function in my own code like this:
using (Pluralsight.Crypto.CryptContext ctx = new Pluralsight.Crypto.CryptContext()) {
ctx.Flags = 0x8 | 0x20;
ctx.Open();
X509Certificate2 cert = ctx.CreateSelfSignedCertificate(
new Pluralsight.Crypto.SelfSignedCertProperties
{
IsPrivateKeyExportable = true,
KeyBitLength = 4096,
Name = new X500DistinguishedName("CN=" + subjectName),
ValidFrom = DateTime.Today,
ValidTo = DateTime.Today + expirationLength,
});
return cert;
}
Notice that I set the Flags for the CryptContext object to be 0x8 | 0x20 (CRYPT_NEWKEYSET | CRYPT_MACHINE_KEYSET).
I wish I could figure out what was wrong with my original solution. But I need something to work and in my testing this solution does what I need. I hope it helps someone else along the way.
You can also use the CLR Security library on CodePlex (https://clrsecurity.codeplex.com/). Here is sample code which creates a self signed certificate, and tests it with SSLStream.
var machineName = Environment.MachineName;
var keyCreationParameters = new CngKeyCreationParameters();
keyCreationParameters.KeyUsage = CngKeyUsages.AllUsages;
keyCreationParameters.KeyCreationOptions = CngKeyCreationOptions.OverwriteExistingKey;
keyCreationParameters.Parameters.Add(new CngProperty("Length", BitConverter.GetBytes(4096), CngPropertyOptions.None));
var cngKey = CngKey.Create(CngAlgorithm2.Rsa, "Test", keyCreationParameters);
var x500DistinguishedName = new X500DistinguishedName("CN=" + machineName);
x500DistinguishedName.Oid.Value = "1.3.6.1.5.5.7.3.1";
var certificateCreationParameters = new X509CertificateCreationParameters(x500DistinguishedName);
certificateCreationParameters.SignatureAlgorithm = X509CertificateSignatureAlgorithm.RsaSha512;
certificateCreationParameters.TakeOwnershipOfKey = true;
certificateCreationParameters.CertificateCreationOptions = X509CertificateCreationOptions.None;
certificateCreationParameters.EndTime = new DateTime(9999, 12,31, 23, 59, 59, 999, DateTimeKind.Utc);
var certificate = cngKey.CreateSelfSignedCertificate(certificateCreationParameters);
var certificateStore = new X509Store(StoreName.Root, StoreLocation.CurrentUser);
certificateStore.Open(OpenFlags.ReadWrite);
certificateStore.Add(certificate);
certificateStore.Close();
var tcpListener = TcpListener.Create(6666);
tcpListener.Start();
var client = new TcpClient("localhost", 6666);
var acceptedClient = tcpListener.AcceptTcpClient();
var acceptedClinetSslStream = new SslStream(
acceptedClient.GetStream(), false);
var serverAuthTask = acceptedClinetSslStream.AuthenticateAsServerAsync(certificate,
false, SslProtocols.Tls, true);
SslStream clientSslStream = new SslStream(
client.GetStream(),
false,
delegate(object o, X509Certificate x509Certificate, X509Chain chain, SslPolicyErrors errors)
{
if (errors == SslPolicyErrors.None)
return true;
Console.WriteLine("Certificate error: {0}", errors);
// Do not allow this client to communicate with unauthenticated servers.
return false;
},
null);
var clientAuthTask = clientSslStream.AuthenticateAsClientAsync(machineName);
Task.WaitAll(serverAuthTask, clientAuthTask);