When inserting a smart card to a reader the certificates will be read in to the personal store, my question is simple how I then explain for WCF that it should use a specific (of these) certificate in runtime?
My WCF service is selfhosted and are communicating with the client over TCP.
I do already have a communication that uses certificate but these certificates is stated in the config files(one for service and one for client).
Now I need to switch the certificate on the client side before communicating.
You should try to find a specific set of attributes that is unique to the certificate you would like to use.
For example we use filtering like this:
/// <summary>
/// Get the certificate we need for authentication
/// </summary>
/// <returns>the certificate</returns>
/// <exception cref="InvalidOperationException">when no certificate is available</exception>
public static X509Certificate2 GetCertificate()
{
// open private certificate store of the user
X509Store store = new X509Store(StoreName.My);
store.Open(OpenFlags.ReadOnly);
// get the collection of certificates which we need
X509Certificate2Collection certColl = store.Certificates
.Find(X509FindType.FindByExtension, new X509KeyUsageExtension().Oid.Value, false)
.Find(X509FindType.FindByKeyUsage, X509KeyUsageFlags.NonRepudiation, false);
store.Close();
// if more certificates match the criteria, let the user choose one
if (certColl.Count > 1)
{
certColl = X509Certificate2UI.SelectFromCollection(
certColl, "Choose certificate", "We were unable to find the correct certificate. Please choose one from the list.",
X509SelectionFlag.SingleSelection);
}
// if no certificate is available, fail
if (certColl.Count < 1)
{
throw new InvalidOperationException("No certificates selected");
}
else
{
return certColl[0];
}
}
Check the attributes of the certificates you need and try to create your own Find criteria. Of course the use could have additional certificates in his store which match the criteria, this case a dialog window pops up and asks the user to do this himself.
Of course once the user chooses the correct certificate you can store the CN of the certificate in the application configuration file so next time he won't have to do these steps again.
Related
How to setup Gatling to use client certificates per virtual user?
I am preparing Gatling simulations for API that is protected by client certificates and currently I only managed to set using one client certificate for all simulations in Gatling configuration
ssl {
keyStore {
#type = ""
file = "C:/client_certificates/00001.pfx"
password = "password"
#algorithm = ""
}
}
However, I would like to have different client certificates for different virtual users (client certificates can be selected randomly from the list of available client certificates or circularly). Galing documentation mentions keymanagerfactory and perUserKeyManagerFactory function, but without explicit examples of how to use it.
The keys of the config provided in the question are referenced in io.gatling.core.ConfigKeys.ssl.keyStore. The values are read and passed to Ssl.newKeyManagerFactory to create a KeyManagerFactory.
perUserKeyManagerFactory takes in a function Long => KeyManagerFactory that constructs the KeyManagerFactory from the virtual user's ID.
Knowing that we can write the following:
import io.gatling.commons.util.Ssl
...
.perUserKeyManagerFactory { userId =>
val fileName = f"C:/client_certificates/$userId%05d.pfx"
Ssl.newKeyManagerFactory(None, fileName, "password", None)
}
With the f interpolator, we can easily pad the userId with zeros to have 5 digits.
To select the files circularly, we can write ${userId % totalNumber} instead of $userId.
Summary
Use CNG or Pkcs11Interop or any other alternatives to login to an HSM, search for a privatekey then pass it on to a 3rd party application for use.
The key cannot be extracted from the HSM or stored in memory.
a 3rd Party application needs to make use of a private key that is stored on a Hardware Security Module (HSM).
I have looked into two methods CNG and Pkcs11Interop.
The code needs to accomplish the following:
1-Authenticate and establish a session with the HSM
2-Search for the key
3-Pass the private key to the 3rd party using RSACryptoServiceProvider or other methods.
Important: The key cannot be accessed extracted from the HSM or access directly (by design for security purposes).
Below are the two samples I've put together for both CNG and PKCS11Interop
The Problem:
1-CNG I am struggling to authenticate (if that's possible)
2-PKCS11Interop I've been able to login, search for the key but struggling to make use of the key.
Happy to use either of the methods, and I welcome any assistance, alternative solutions or advice.
CNG Code:
This code works when authentication is disabled on HSM
Q. Is there a way to authenticate using a password , open a session prior to using the key?
CngProvider provider = new CngProvider("CNGProvider");
const string KeyName = "somekey";
key = CngKey.Open(KeyName, provider);
Console.WriteLine("found the key!");
var cngRsa = new RSACng(key);
var privateSshKey = new SshPrivateKey(cngRsa);
PKCS11Interop, I managed to authenticate, search for the key and assign it to a handle..
Q. How do i go about passing the private key onto a standard .Net Framework type AsymmetricAlgorithm? while keeping in mind it not exportable?
can it be passed to RSACryptoServiceProvider? and then onto AsymmetricAlgorithm?
using (IPkcs11Library pkcs11Library = Settings.Factories.Pkcs11LibraryFactory.LoadPkcs11Library(Settings.Factories, Settings.Pkcs11LibraryPath, Settings.AppType))
{
ISlot slot = Helpers.GetUsableSlot(pkcs11Library);
using (ISession session = slot.OpenSession(SessionType.ReadWrite))
{
//search for key
try
{
const string keyname = "somekey";
// Login as normal user
session.Login(CKU.CKU_USER, Settings.NormalUserPin);
IObjectHandle publicKeyHandle = Helpers.CreateDataObject(session);
IObjectHandle privateKeyHandle = Helpers.CreateDataObject(session);
// Prepare attribute template that defines search criteria
List<IObjectAttribute> privateKeyAttributes = new List<IObjectAttribute>();
privateKeyAttributes.Add(session.Factories.ObjectAttributeFactory.Create(CKA.CKA_CLASS, CKO.CKO_PRIVATE_KEY));
privateKeyAttributes.Add(session.Factories.ObjectAttributeFactory.Create(CKA.CKA_KEY_TYPE, CKK.CKK_RSA));
privateKeyAttributes.Add(session.Factories.ObjectAttributeFactory.Create(CKA.CKA_LABEL, keyname));
List<IObjectHandle> foundPrivateKeys = session.FindAllObjects(privateKeyAttributes);
if (foundPrivateKeys == null || foundPrivateKeys.Count != 1)
throw new Exception("Unable to find private key");
// Use found object handles
privateKeyHandle = foundPrivateKeys[0];
session.FindObjectsFinal();
// How do i go about using the privatekey handle here?
session.DestroyObject(privateKeyHandle);
session.Logout();
}
catch (Exception ex)
{
Console.WriteLine("Crypto error: " + ex.Message);
}
Console.WriteLine("done!");
System.Console.Write("[Hit Enter to Continue]");
System.Console.ReadLine();
}
}
With an HSM, by design, you cannot "Pass the private key to a 3rd party app".
You also cannot pass the key handle between processes (although this might work in some implementations - a key handle should be PKCS11 session specific).
Your 3rd party app needs to offload cryptographic operations to the HSM by using a configurable cryptography library like OpenSSL (or similar) or if it is using CNG it should allow you to configure the provider.
Q. Is there a way to authenticate using a password , open a session prior to using the key?
A.: For an app that uses CNG, you should use the CNG Key Storage Provider (KSP) from the HSM Vendor after you have configured it.
The HSM Vendor KSP will then prompt for the password or, if you configured the provider (using a utility or configuration file from the HSM vendor) to store the password/pin, it will just work.
eHSM sample code using NCryptNative:
SafeNCryptProviderHandle handle;
NCryptOpenStorageProvider(handle, "eHSM Key Storage Provider",0);
...
Q. How do i go about passing the private key onto a standard .Net Framework type AsymmetricAlgorithm? while keeping in mind it not exportable? can it be passed to RSACryptoServiceProvider? and then onto AsymmetricAlgorithm?
A.: No, you cannot pass key material and you cannot pass the raw PKCS11 handle to the CNG Framework. You either have to use the PKCS11Interop functions to perform all cryptographic operations OR you have to do everything in CNG (correctly configured as above).
To directly use the PKCS11 interface you continue calling PKCS11 functions with the key handle, ie.
// How do i go about using the privatekey handle here?
// something like this
session.SignInit(mechanism, privateKeyHandle);
session.Sign(data, signature));
I am using iTextSharp 5.5.13 in order to sign and validate PDF files. Few days ago I ended up with exception in OcspVerifier.Verify which stated "certificate expired on 20181225...GMT+00:00"
All PDF files are signed with embeded OCSP response and CLR. Validation was done using signing date and it used to work fine until the ocsp signers certificate expired.
Despite the fact that all certificates were valid at the time of signing, the OcspVerifier.Verify started to throw exception which states "cert expired...".
if (pkcs7.Ocsp != null)
ocsps.Add(pkcs7.Ocsp);
PdfOcspVerifier ocspVerifier = new PdfOcspVerifier(null, ocsps);
ocspVerifier.OnlineCheckingAllowed = false;
List<VerificationOK> verification = ocspVerifier.Verify(signCert, issuerCert, signDate.ToUniversalTime());
EXCEPTION: "certificate expired on 20181225...GMT+00:00"
It looks like bug to me? Is there any reason why OCSP signer certificate is not verified against signing date?
Class OscpVerifier - original, with line which validate cert against current date:
virtual public void IsValidResponse(BasicOcspResp ocspResp, X509Certificate issuerCert)
{
....
//check if lifetime of certificate is ok
responderCert.CheckValidity();
}
Modified version of OscpVerifier.cs:
// old definition with old functionality
virtual public void IsValidResponse(BasicOcspResp ocspResp, X509Certificate issuerCert)
{
IsValidResponse(ocspResp, issuerCert, DateTime.UtcNow);
}
// with signDate parameter:
virtual public void IsValidResponse(BasicOcspResp ocspResp, X509Certificate issuerCert, DateTime signDate)
{
...
//check if lifetime of certificate is ok
responderCert.CheckValidity(signDate);
//responderCert.CheckValidity();
}
with corresponding method call change fom:
virtual public bool Verify(BasicOcspResp ocspResp, X509Certificate signCert, X509Certificate issuerCert, DateTime signDate)
{
...
... {
// check if the OCSP response was genuine
IsValidResponse(ocspResp, issuerCert);
return true;
}
...
}
to:
virtual public bool Verify(BasicOcspResp ocspResp, X509Certificate signCert, X509Certificate issuerCert, DateTime signDate)
{
...
... {
// check if the OCSP response was genuine
IsValidResponse(ocspResp, issuerCert, signDate);
return true;
}
...
}
I included this variation of the OscpVerifier class directly in the project and it validates now old signatures as expected.
However, I am not sure if I hit the bug or there is reason why those signatures should be considered as not valid?
OCSP signer certificate must be validated at current time except if the OCSP response has been protected with a time stamp, so itext is working properly.
Summarizing, an OCSP response contains (See RFC6960 ):
target certificate and validity interfal
time when the response was generated
A digital signature of a CA trusted responder, and its certificate
The criteria for accepting an OCSP response is established in the RFC, but it does not clarify your question, because it does not establish how to validate the response over time.
3.2. Signed Response Acceptance Requirements
Prior to accepting a signed response for a particular certificate as
valid, OCSP clients SHALL confirm that:
The certificate identified in a received response corresponds to the certificate that was identified in the corresponding request;
The signature on the response is valid;
The identity of the signer matches the intended recipient of the request;
The signer is currently authorized to provide a response for the certificate in question;
The time at which the status being indicated is known to be correct (thisUpdate) is sufficiently recent;
When available, the time at or before which newer information will be available about the status of the certificate (nextUpdate) is greater than the current time.
However, it does indicate that the digital signature must be valid (2). In general, for a signature to be considered valid, it must comply with:
Cryptographic integrity
Validity of the certificate (expiration and revocation)
An OCSP response with an expired certificate will not meet the second criterion and therefore should be rejected. To extend the period in which a signature is valid, a time stamp must be added on the content. The PAdES standard specifies how
There is also an ETSI guide with the details on how to perform the verification of a PAdES and CAdES signatures, but unfortunately I have not found the link now
I need to register companies on my site for an electronic procurement system. Up to now these were local companies I could meet physically and give credentials to, but now they can be companies based anywhere in the world.
The solution is to have an online registration process whereby they submit a third party certificate. So say Verisign says they are 'Company X' so I register them as Company X and issue them credentials.
How can I implement this on my site? Do I simply give them a field in the registration form where they upload their certificate file? Do I then manually check these certificates in my back office? How does one check this manually? Is there a way to automate this process?
Once they have an account, should I simply request the credentials I issue them with to log in, or can all future logins request the same certificate file? In these a particular format for certificates I can request or should I allow a number of common formats that different certificate vendors provide?
Thanks in advance.
Being able to provide a certificate does unfortunately not prove anything. A certificate is completely public, and anyone can get a hold of the SSL certificate for any website. The certificate contains a public key. Proving ownership of the corresponding private key is what's required.
This is possible to do, but it requires that your users are technical enough to know how to run scripts and/or OpenSSL terminal commands so that they can sign something with their private key. Having the users upload their private key is of course a big no-no, as it means you can now act as the user, and that would require an enormous amount of trust in you to discard the private key after you've verified it.
From a technical perspective, you can do the verification by creating some kind of challenge, for example a random string, and have the user encrypt this string with their private key. If you decrypt this string with the public key in the certificate, and get the original string back, then you know that they have possession of the corresponding private key.
Here's a self-contained Ruby script that demonstrates this, with comments indicating which part of it is run on your side, and which part is run on their side.
require "openssl"
## This happens on the client side. They generate a private key and a certificate.
## This particular certificate is not signed by a CA - it is assumed that a CA
## signature check is already done elsewhere on the user cert.
user_keypair = OpenSSL::PKey::RSA.new(2048)
user_cert = OpenSSL::X509::Certificate.new
user_cert.not_before = Time.now
user_cert.subject = OpenSSL::X509::Name.new([
["C", "NO"],
["ST", "Oslo"],
["L", "Oslo"],
["CN", "August Lilleaas"]
])
user_cert.issuer = user_cert.subject
user_cert.not_after = Time.now + 1000000000 # 40 or so years
user_cert.public_key = user_keypair.public_key
user_cert.sign(user_keypair, OpenSSL::Digest::SHA256.new)
File.open("/tmp/user-cert.crt", "w+") do |f|
f.write user_cert.to_pem
end
## This happens on your side - generate a random phrase, and agree on a digest algorithm
random_phrase = "A small brown fox"
digest = OpenSSL::Digest::SHA256.new
## The client signs (encrypts a cheksum) the random phrase
signature = user_keypair.sign(digest, random_phrase)
## On your side, verify the signature using the user's certificate.
your_user_cert = OpenSSL::X509::Certificate.new(File.new("/tmp/user-cert.crt"))
puts your_user_cert.public_key.verify(digest, signature, random_phrase + "altered")
# => falase
puts your_user_cert.public_key.verify(digest, signature, random_phrase)
# => true
## On your side - attempting to verify with another public key/keypair fails
malicious_keypair = OpenSSL::PKey::RSA.new(2048)
puts malicious_keypair.public_key.verify(digest, signature, random_phrase)
Note that this script does not take into account the CA verification step - you also obviously want to verify that the user's certificate is verified by a CA, such as Verisign that you mentioned, because anyone can issue a certificate and hold a private key for foo.com - it's the CA signature of the certificate that provides authenticity guarantees.
After a brutal struggle with WCF Security, I think I'm at the final stage now and can see the light.
I've got a Client certificate installed on my server, and is now, as advised, in the Trusted People folder of the certificate store.
However, when I try and read the certificate application -> service, I get this error:
Cannot find the X.509 certificate using the following search criteria: StoreName 'My', StoreLocation 'CurrentUser', FindType
'FindBySubjectName', FindValue 'Forename Surname'.
With the "Forename Surname" being the "Issued to" part of my certificate. In all tutorials I have seen, this is just one word; is this the problem? I received my certificate from my CA with these two words, with a space.
Anyone ever come across this, is there something I'm blatantly doing wrong?
Update, cert can be seen here:
Update:
It gets even more strange:
I installed Visual Studio on my web server, and used the following code to pick up the cert by Thumbprint:
var store = new X509Store(StoreName.TrustedPeople, StoreLocation.CurrentUser);
store.Open(OpenFlags.ReadOnly);
var certs = store.Certificates.Find(X509FindType.FindByThumbprint, "71995159BFF803D25BFB691DEF7AF625D4EE6DFB", false);
This actually RETURNS a valid result. When I put this information into the web.config of my service/client though, I still get the error.
I think..You installed certificate at location Trusted People and searching at store name my
var store = new X509Store(StoreName.TrustedPeople, StoreLocation.CurrentUser);
store.Open(OpenFlags.ReadOnly);
var certs = store.Certificates.Find(X509FindType.FindBySubjectDistinguishedName, certificateSubject, false);
Also there are two search terms FindBySubjectName or FindBySubjectDistinguishedName, the later is more relevant with keywords and first one will find anything with search keywords.
So basically you need to look for Subject and if you use above code then your search string would be .."CN=urs.microsoft.com, O=DO_NOT_TRUST, OU=Created by http://fiddler2.com"
https://i.stack.imgur.com/QtYvV.png
private X509Certificate2 GetCertificateFromStore()
{
var store = new X509Store(StoreName.My, StoreLocation.LocalMachine);
store.Open(OpenFlags.ReadOnly);
var certCollection = store.Certificates;
var currentCerts = certCollection.Find(X509FindType.FindBySubjectDistinguishedName, "CN=sf.sandbox.mapshc.com", false);
return currentCerts.Count == 0 ? null : currentCerts[0];
}