How to stop Windows CryptoAPI to add private key to public-only cert upon decryption
<p>I'm trying to test S/MIME decryption but CryptDecryptMessage function in decryption mode keeps working even if the certificate in Personal store is public-only (while private key is needed for decryption). Magically, the certificate turns into private+public certificate after this function is called.</p>

<p>Initially, I had full certificate in my Personal store and everything was working fine. Then, I had to test what would happen if someone tried to decrypt having just a public-key cert. So I exported public key, removed cert from the store and imported the public key alone. But I can't do my testing that because Windows somehow adds the private key back to the cert. Perhaps, there is some internal cache of private keys or something like that which is activated when CryptoAPI needs the private key. I would like to disable this feature if possible or somehow tell the system that I don't want to use it with my CryptoAPI calls.</p>

<p>The same occurs with Outlook (as it internally uses the same CryptoAPI functions).</p>

<p>I now added the excerpt from sources (cannot create the fully self-contained sample as it turns out to be too complex) but the decryption part should be OK. I double checked that the private key appears in the certificate at the very moment when CryptDecryptMessage is executed, not before or after.</p>

<pre><code>// stores is 1 entry (MY), got with CertOpenStore
// contentBytes is our data to decrypt
IntPtr[] storeHandles = new IntPtr[stores.Length];
for (int i = 0; i &lt; stores.Length; i++)
{
    if (stores[i] != null)
    {
        storeHandles[i] = stores[i].Handle;
    }
}
byte[] contentBytes = data;
IntPtr pDecryptPara = IntPtr.Zero;
IntPtr pbEncryptedBlob = IntPtr.Zero;
uint cbEncryptedBlob = (uint)contentBytes.Length;
IntPtr pbDecrypted = IntPtr.Zero;
uint cbDecrypted = 0;
CRYPT_DECRYPT_MESSAGE_PARA decryptPara = new CRYPT_DECRYPT_MESSAGE_PARA();
decryptPara.cbSize = (uint)Marshal.SizeOf(decryptPara.GetType());
decryptPara.dwMsgAndCertEncodingType = PKCS_7_ASN_ENCODING | X509_ASN_ENCODING;
decryptPara.cCertStore = (uint)storeHandles.Length;

GCHandle storeHandlesHandle = GCHandle.Alloc(storeHandles, GCHandleType.Pinned);
decryptPara.rghCertStore = (IntPtr) storeHandlesHandle.AddrOfPinnedObject();

IntPtr pXchgCert = IntPtr.Zero;

pDecryptPara = Marshal.AllocHGlobal((int)decryptPara.cbSize);
Marshal.StructureToPtr(decryptPara, pDecryptPara, true);

pbEncryptedBlob = Marshal.AllocHGlobal((int)cbEncryptedBlob);
Marshal.Copy(contentBytes, 0, pbEncryptedBlob, (int)cbEncryptedBlob);
// At this point the cert in MY storage does not have private key
int ret = CryptoApiFuncs.CryptDecryptMessage(pDecryptPara, pbEncryptedBlob, cbEncryptedBlob, pbDecrypted, ref cbDecrypted, ref pXchgCert);
// Now the cert has the private key
</code></pre>