I am developing windows service in C# which invokes webservice methods. I must use SSL to connect to webservice. I have recieved from publisher p12 file with certificate. The file is password protected. To use Import method to use this certificate. Everything is working fine, but I do not like this method - I have password harcoded in my app. When publisher changes certificate I must rewrite code(changing the password to new one). Is there any way not to harcode password to .p12 file or use other option(.cer file)?
What you could do is something like this:
Install the SSL certificate into your local machine certificate store (using the Microsoft Management Console "MMC")
Extract the certificates thumbprint (e.g. "748681ca3646ccc7c4facb7360a0e3baa0894cb5")
Use a function which fetches you the certificate from the local certificate store for the given thumbprint.
Provide the SSL certificate when calling your web service.
private static X509Certificate2 GetCertificateByThumbprint(string certificateThumbPrint, StoreLocation certificateStoreLocation) {
X509Certificate2 certificate = null;
X509Store certificateStore = new X509Store(certificateStoreLocation);
certificateStore.Open(OpenFlags.ReadOnly);
X509Certificate2Collection certCollection = certificateStore.Certificates;
foreach (X509Certificate2 cert in certCollection)
{
if (cert.Thumbprint != null && cert.Thumbprint.Equals(certificateThumbPrint, StringComparison.OrdinalIgnoreCase))
{
certificate = cert;
break;
}
}
if (certificate == null)
{
Log.ErrorFormat(CultureInfo.InvariantCulture, "Certificate with thumbprint {0} not found", certificateThumbPrint);
}
return certificate;
}
public string GetServiceResponse() {
string WebSvcEndpointConfigurationName = "WebServiceEndpoint";
Uri webSvcEndpointAddress = new Uri("http://www.example.com/YourWebService.svc");
string webSvcCertificateThumbPrint = "748681ca3646ccc7c4facb7360a0e3baa0894cb5";
string webSvcResponse = null;
SomeWebServiceClient webServiceClient = null;
try
{
webServiceClient = new SomeWebServiceClient(WebSvcEndpointConfigurationName, new EndpointAddress(webSvcEndpointAddress));
webServiceClient.ClientCredentials.ClientCertificate.Certificate = GetCertificateByThumbprint(webSvcCertificateThumbPrint, StoreLocation.LocalMachine);
webSvcResponse = webServiceClient.GetServiceResponse();
}
catch (Exception ex)
{
}
finally
{
if (webServiceClient != null)
{
webServiceClient.Close();
}
}
return webSvcResponse;
}
PKCS#12 file is provided to you as it is a natural way to transport certificates together with private keys. You can use one of the following:
convert it to format you like and store the way you like
convert it to passwordless PFX
import it to computer's certificate storage and use it this way
But all those methods (together with keeping a hardcoded password) provide no real protection to the private key and thus are not usable if you distribute the application to outside of your organization.
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 wrote a simple c# console app that just tries to read the private key of a x509 cert,
The code is:
var store = new X509Store(StoreName.My, StoreLocation.LocalMachine);
store.Open(OpenFlags.ReadOnly);
X509Certificate2Collection cers = store.Certificates.Find(X509FindType.FindBySubjectName, "cert", false);
if (cers.Count > 0)
{
X509Certificate2 cer = cers[0];
try
{
if (cer.HasPrivateKey)
{
Console.WriteLine("Private key:");
Console.WriteLine(cer.GetRSAPrivateKey().ToXmlString(false));
}
}
catch (CryptographicException)
{
string currentUser = System.Security.Principal.WindowsIdentity.GetCurrent().Name;
Console.WriteLine("This cert has a private key and this user, " + currentUser + ", was unable to access it.");
}
};
store.Close();
If I run as admin, it work fine.
Otherwise, I get a read permission error.
I used the mmc to give Full Access to my account and even tried giving full access to Everybody (for testing) but same result. The cert does not seem to honor the permissions set.
Am I missing a step?
Any help would be appreciated.
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 two x509Certificates installed in my Personal certificate store and wish to retrieve the certificates by Application Policy.
I use the following code to achieve this:
public X509Certificate2 LocateCertificate(Oid oid)
{
var store = new X509Store(Store.My, StoreLocation.CurrentUser);
store.Open(OpenFlags.ReadOnly);
try
{
var certificates = store.Certificates.Find(X509FindType.FindByApplicationPolicy, oid.Value, true);
if(certificates.Count != 1)
{
throw new CryptographicException(string.Format("Expected one certificate, found {0}", certificates.Count);
}
return certificates[0];
}
finally
{
store.Close();
}
}
When both installed X509Certificates have different Extended key Usage values the above method successfully retrieve the correct certificate when provided with a valid OID. However, if one certificate does not have its Extended Key Usage property set it is also returned by the query along with the correct certificate. I want to guard against returning certificates which have:
An incorrect Extended Key Usage value set.
No Extended Key Usage value set.
Any help would be appriciated.
I'm writing a Windows service that needs several certificates in the certificate store in order to connect to a third party web service.
On my installer I call a small application (C#) that creates a user to run the service as.
It works fine.
I now need to install about 10 certificates (don't ask!) into the users certificate store, but can't find any succinct programmatic way to do so.
Any hints? Or am I going to have to use COM interop...
Turns out you first need to impersonate the user.
Using the very nice library described in A small C# Class for impersonating a User, you can do the following:
using (new Impersonator("username", "", "password"))
{
try
{
X509Store serviceRuntimeUserCertificateStore = new X509Store(StoreName.My);
string baseDir = AppDomain.CurrentDomain.BaseDirectory;
string certPath = Path.Combine(baseDir, certificateFolder);
string certificateFile = "c:\\file.cert";
string certificatePassword = "somePassword";
string certificateLocation = certPath + "\\" + certificateFile;
InstallCertificate(certificateLocation, certificatePassword);
}
catch (Exception ex)
{
Console.WriteLine(ex);
}
}
private static void InstallCertificate(string certificatePath, string certificatePassword)
{
try
{
var serviceRuntimeUserCertificateStore = new X509Store(StoreName.My);
serviceRuntimeUserCertificateStore.Open(OpenFlags.ReadWrite);
X509Certificate2 cert;
try
{
cert = new X509Certificate2(certificatePath, certificatePassword);
}
catch(Exception ex)
{
Console.WriteLine("Failed to load certificate " + certificatePath);
throw new DataException("Certificate appeared to load successfully but also seems to be null.", ex);
}
serviceRuntimeUserCertificateStore.Add(cert);
serviceRuntimeUserCertificateStore.Close();
}
catch(Exception)
{
Console.WriteLine("Failed to install {0}. Check the certificate index entry and verify the certificate file exists.", certificatePath);
}
}
Please add your own exception handling. If you're adding multiple certificates keep the X509Store open for the duration for efficiency.