I am trying to generate digital signature using "Microsoft RSA SChannel Cryptographic Provider". After acquiring the handle to container, I am generating a signature using CryptGenKey(). But this function returns FALSE.
The dwError for CryptGenKey() returns 80090008.
The same works for any other provider type. Also, when I am trying to create key exchange pair for the same provider, it is working fine. What am I doing wrong?
#include <Windows.h>
#include <wincrypt.h>
int main()
{
HCRYPTPROV phProv = 0;
LPTSTR pszContainer = NULL;
DWORD dwFlags = 0;
bool flag;
DWORD_PTR dwError;
HCRYPTKEY phKey;
flag = CryptAcquireContext(&phProv, pszContainer,
MS_DEF_RSA_SCHANNEL_PROV, PROV_RSA_SCHANNEL, dwFlags);
if (!flag)
{
flag = CryptAcquireContext(&phProv, pszContainer,
MS_DEF_RSA_SCHANNEL_PROV, PROV_RSA_SCHANNEL, CRYPT_NEWKEYSET);
}
dwError = GetLastError();
flag = CryptGenKey(phProv, AT_SIGNATURE, CRYPT_EXPORTABLE, &phKey);
dwError = GetLastError();
flag = CryptGetUserKey(phProv, AT_SIGNATURE, &phKey);
dwError = GetLastError();
return 0;
}
Thanks.
The SChannel provider doesn't support AT_SIGNATURE RSA keys, only AT_EXCHANGE. It's mainly a holdover from early TLS (back when it was still SSL) when keys were exchanged using RSA encryption instead of agreed upon using Diffie-Hellman Key Agreement signed with RSA signature... and then, presumably "well, everybody knows how the SChannel provider behaves, why change it?". (The closest I see to that being written down is https://msdn.microsoft.com/en-us/library/windows/desktop/aa387690(v=vs.85).aspx, which shows CALG_RSA_KEYX and doesn't talk about CALG_RSA_SIGN.)
In CAPI an AT_EXCHANGE key can do both encryption and signing, and an AT_SIGNATURE key can only do signing.
In general the Windows Cryptography team discourages new code written with CAPI (I don't have written evidence othen than what I just wrote; mainly this has come from being in meetings with them). CNG has a much more developer-friendly API, and is more powerful. CNG shipped back in in Windows Vista, and so it's available in every supported version of Windows. CAPI doesn't serve the purpose of "well, it has more coverage" anymore, it's just "the old, crufty, legacy API" (unless you're writing code for out-of-support OSes, like XP).
If you are using CAPI, I don't know why you'd want to use the SChannel provider. PROV_RSA_AES via MS_ENH_RSA_AES_PROV is the most functional RSA that CAPI has (SHA-2 based PKCS signatures). But it's out of date compared to the software provider in CNG (PSS signature, OAEP with SHA-2, and supports public exponent values larger than 2^32 (okay, that's not a common need, but it's something CNG fixed)).
Related
I am currently working on a small sample program using Crypto Next Generation (Windows Crypto API) to generate a key, store it in the TPM on my computer, encrypt some data and then retrieve it and decrypt the data.
My choice of RSA encryption is because it is the only algorithm my TPM supports.
I understand I can access the TPM as a provider using:
// Open handle to TPM
if (FAILED(secStatus = NCryptOpenStorageProvider(
&hProv,
MS_PLATFORM_CRYPTO_PROVIDER,
0)))
{
wprintf(L"**** Error 0x%x returned by NCryptOpenStorageProvider\n", secStatus);
goto Cleanup;
}
And that I can generate the key (which documentation states should save this in my provider):
// Create a persistent key
if (FAILED(secStatus = NCryptCreatePersistedKey(
hProv,
&hKey,
NCRYPT_RSA_ALGORITHM,
L"RSAKey0",
0,
0)))
{
wprintf(L"**** Error 0x%x returned by NCryptCreatePersistedKey\n", secStatus);
goto Cleanup;
}
(and then set length, finalize, etc)
And it appears my data is encrypted by running:
// Encrypt Data
if (!NT_SUCCESS(status = NCryptEncrypt(
hKey, // hKey
InputData, // pbInput
InputDataSize, // cbInput
NULL, // pPaddingInfo
encryptedBuffer, // pbOutput
encryptedBufferSize, // cbOutput
&encryptedBufferSize, // pcbResult
NCRYPT_PAD_PKCS1_FLAG))) // dwFlags
{
wprintf(L"**** Failed to encrypt data. Error 0x%x returned by NCryptEncrypt\n", status);
goto Cleanup;
}
This appears to work alright with no errors and the data looks encrypted.
(I fear I may be misunderstanding the function usage here with RSA encryption and generating a persistent key as opposed to a key pair, but because I am not looking to need to share a public key, I assume this should work)
But, when trying to retrieve the key using:
// Get key from TPM
if (FAILED(secStatus = NCryptOpenKey(
hProv,
&hKey,
L"RSAKey0",
0,
0)))
{
wprintf(L"**** Error 0x%x returned by NCryptOpenKey\n", secStatus);
goto Cleanup;
}
I receive an error of NTE_BAD_KEYSET. Which indicates the key was not found.
Potentially, the only function I see that I may be missing is NCryptExportKey, but if I understand it right that exports the key to a blob of memory and not to the TPM (which should have been saved upon CreatePersistedKey).
Am I missing a step to ensure the key is stored in my TPM?
Also, I am using NCryptDeleteKey as cleanup of my encryption function, but the documentation states that this just frees the key handle and not the actual stored key. How do you delete a key from the TPM after storing it there?
NCryptCreatePersistedKey needs to be followed up by a call to NCryptFinalizeKey() or it is never actually stored to the TPM. That's where the actual magic happens. For instance, if you are not elevated / admin it will fail with E_ACCESS here.
NCryptDeleteKey does delete the key from your TPM as well as cleaning up the handle.
This was discovered through experimentation with enum and listing keys.
I am attempting to retrieve the subject alternative name from my client certificate. By running this command, I can see the SAN:
openssl x509 -noout -text -in certname.cert.pem
...
X509v3 Subject Alternative Name:
IP Address:10.10.10.10
In a C file, I am trying to retrieve the client SAN, so that I can validate it with the server IP. Here is my attempt:
cert = X509_STORE_CTX_get_current_cert(x509Ctx);
int i;
int san_names_nb = -1;
STACK_OF(GENERAL_NAME) *san_names = NULL;
// Try to extract the names within the SAN extension from the certificate
san_names = (GENERAL_NAME*)X509_get_ext_d2i((X509 *) cert, NID_subject_alt_name, NULL, NULL);
if (san_names == NULL)
{
return Error;
}
Right now, my code is returning the error because san_names is NULL. Any guidance would be much appreciated. Thank you!
The OpenSSL command itself gives the SAN as Null
X509v3 Subject Alternative Name: **<BLANK>**
IP Address:10.10.10.10
Can you just open the certificate and see if it contains the SAN. If not you will have to ask the team to add the SAN and create a new certificate again.
You are misusing X509_get_ext_d2i(). Per the OpenSSL documentation for X509_get_ext_d2i() (bolding mine):
If idx is NULL then only one occurrence of an extension is permissible otherwise the first extension after index *idx is returned and *idx updated to the location of the extension.
Depending on the version of OpenSSL you're using, the behavior might be slightly different. The above is the documented behavior for OpenSSL 1.1.0.
Since you are passing NULL as idx, if there's more than one SAN on the cert you'll get NULL from X509_get_ext_d2i().
You can get the OpenSSL error code with ERR_get_error().
I have a certificate in PEM format. Let say I have a certificate that I copied from google.com.
So, the chain is that
Google Trust Services-GlobalSign Root CA-R2
->Google Internet Authority G3
-->*.google.com
Suppose that I have certificate *.google.com and I want a C program to verify this certificate with my local trust store in Linux. Let say /etc/ssl/certs.
I need to it offline without connecting to the server. What should I do?
The overall OpenSSL documentation around this topic is rather limited and has broken links all over the place, so my approach might not the only one or best one. As far as I can see, verifying a certificate (chain) is done with the following steps, unrolled in reverse order because I think that gives a better understanding. For the resulting code, see the end of this answer. All code has error checking omitted for the sake of brevity. Also, the loading of Certificate Revocation Lists (CRLs) is not explained, I think that is beyond the scope of your question.
The actual verification function
The functionality to verify a certificate (chain) is provided by the OpenSSL function X509_verify_cert(). A return value of 1 indicates successful verification, 0 indicates no success. As you can see in the documentation, the function only requires one parameter of the type X509_STORE_CTX, which is a structure holding the "context" (a rather vague and overused term in OpenSSL, IMO) of the collection of X509 certificates involved.
Setting up the certificate store context
The certificate store context contains information about trusted certificates, untrusted intermediate certificates and the certificate to be verified. It is constructed and initialized as follows:
store_ctx = X509_STORE_CTX_new();
X509_STORE_CTX_init(store_ctx, store, cert, intermediates)
The store parameter will be used to contain information about the trusted certificates, the cert parameter contains the certificate to be verified and the and intermediates parameter is a stack of untrusted intermediate certificates.
The store parameter
The X509_STORE type is able a contain a set of X509 certificates and for the purpose of verifying a certificate needs to be provided with information about trusted certificates. Since you indicated that you have trusted certificates in /etc/ssl/certs, this can be done as follows:
store = X509_STORE_new();
lookup = X509_STORE_add_lookup(store, X509_LOOKUP_hash_dir());
X509_LOOKUP_add_dir(lookup, "/etc/ssl/certs", X509_FILETYPE_PEM);
This assumes that your local trust store is set up properly
The cert parameter`
This parameter contains the actual certificate to be verified. It can be loaded from a file in several ways, one approach is as follows:
bio_in = BIO_new_file(certFileName, "r");
result = PEM_read_bio_X509(bio_in, NULL, NULL, NULL);
BIO_free(bio_in);
The intermediates parameter
OpenSSL provides a stack API to handle collections of objects. The intermediates parameter is a stack of X509 objects that contains the intermediate certificates between your certificate to be tested and your trusted certificate. In pseudo code, it can be filled as follows:
intermediates = sk_X509_new_null();
for (filename in certFilenames) do {
icert = readCert(filename);
sk_X509_push(intermediates, icert);
}
This concludes the explanation, this should give you all you need to verify the chain.
**About the certificate at the end of the downloaded chain
The certificate at the end of the downloaded chain is typically contained in your local trust store. Some experiments show that you can actually feed it into the verify function as if it is an untrusted intermediate or you can omit it. Both seemed to end in a properly verified chain.
A code example
Finally :-)
#include <openssl/x509.h>
#include <openssl/x509_vfy.h>
#include <openssl/pem.h>
const char *trustedCertsPath = "/etc/ssl/certs";
int main(
int argc,
char **argv)
{
X509 *cert = NULL;
X509 *icert = NULL;
STACK_OF(X509) *intermediates = NULL;
X509_STORE *store = NULL;
X509_LOOKUP *lookup = NULL;
X509_STORE_CTX *store_ctx = NULL;
BIO *bio_in = NULL;
int currentArg = 1;
int result = 0;
store = X509_STORE_new();
lookup = X509_STORE_add_lookup(store, X509_LOOKUP_hash_dir());
X509_LOOKUP_add_dir(lookup, trustedCertsPath, X509_FILETYPE_PEM);
/* Certificate to be checked */
bio_in = BIO_new_file(argv[currentArg++], "r");
cert = PEM_read_bio_X509(bio_in, NULL, NULL, NULL);
BIO_free(bio_in);
/* Stack of untrusted intermediate certificates */
intermediates = sk_X509_new_null();
while (currentArg < argc) {
bio_in = BIO_new_file(argv[currentArg++], "r");
icert = PEM_read_bio_X509(bio_in, NULL, NULL, NULL);
BIO_free(bio_in);
sk_X509_push(intermediates, icert);
}
store_ctx = X509_STORE_CTX_new();
X509_STORE_CTX_init(store_ctx, store, cert, intermediates);
result = X509_verify_cert(store_ctx);
printf("Result from X509_verify_cert is %d\n", result);
sk_X509_pop_free(intermediates, X509_free);
X509_STORE_CTX_cleanup(store_ctx);
X509_STORE_CTX_free(store_ctx);
X509_STORE_free(store);
}
You can build and run it as follows (where the .pem arguments are the names of the files containing your certificate and intermediates in PEM format:
$ gcc main.c $(pkg-config openssl --libs) -o verify -Wall
$ ./verify \*.google.com.pem Google\ Internet\ Authority\ G3.pem
Result from X509_verify_cert is 1
I'm using libssh2 to make a networking program more secure.
I'd like my program to authenticate in as similar way to the OpenSSH client ssh(1) as possible. The OpenSSH client will only ask for passphrases for keys that are actually accepted by the server.
As I understand from this link, an ssh client sends a request to use a public key, and if that is accepted, it can unlock the private key using the passphrase.
libssh2 provides a function libssh2_userauth_publickey_fromfile which takes the private and public key file names and a passphrase. Using this function is very straight forward, but it means I have to obtain the passphrase for private keys even if the public key wouldn't have been accepted by the server in the first place. This is clearly a problem for users that have a lot of different keys (my program currently iterates through key files in the ~/.ssh directory).
I have tried reading the man pages for libssh2 functions, and most of them appear too brief for me to understand without a more detailed knowledge of the ssh protocol. In fact some of them haven't even been written yet.
Can anyone tell me how to only prompt for passphrases for keys that are actually accepted by an ssh server using libssh2?
After RTFM and doing some testing, I discovered that libssh2_userauth_publickey_fromfile will return a different error code depending on whether the key wasn't accepted by the server, or the passphrase was incorrect.
So, here is a pretty inefficient solution (because it calls libssh2_userauth_publickey_fromfile and hence all the key exchange parts of the protocol at least twice).
int nAttempts = 3; // number of attempts the user gets at entering the passphrase
// Try authenticating with an empty passphrase
int err = libssh2_userauth_publickey_fromfile(session, user, pub, priv,"");
if (err == 0)
{
fprintf(stderr, "You shouldn't use keys with an empty passphrase!\n");
}
else if (err == LIBSSH2_ERROR_PUBLICKEY_UNVERIFIED)
{
// if we get here it means the public key was initially accepted
// but the private key has a non-empty passphrase
char p[BUFSIZ];
for (int i = 0; i < nAttempts; ++i)
{
get_passphrase(p); // assume this gets the passphrase
err = libssh2_userauth_publickey_fromfile(session, user, pub, priv,p);
if (err != LIBSSH2_ERROR_PUBLICKEY_UNVERIFIED) break;
}
}
if (err != 0)
{
fprintf(stderr, "Authentication using key %s failed!\n", priv);
}
For completeness, the get_passphrase function uses the solution to this question to prompt the user for a passphrase.
I'm using CryptEncryptMessage to generate a PKCS#7 enveloped message. I'm using szOID_NIST_AES256_CBC as the encryption algorithm.
The generated message appears to be valid but is the RSAES-OAEP for the Key Transport Algorithm which has limited support in the wild (Thunderbird, OpenSSL SMIME Module among many others don't support it).
I'll like for CAPI to revert to the older RSAencryption for key transport.
Is there any possible way to do that, I could revert to the low level messaging functions if there is a way rather than to use CryptEncryptMessage but I can't find a way to do that even using the low level functions.
Code:
CRYPT_ENCRYPT_MESSAGE_PARA EncryptMessageParams;
EncryptMessageParams.cbSize = sizeof(CMSG_ENVELOPED_ENCODE_INFO);
EncryptMessageParams.dwMsgEncodingType = PKCS_7_ASN_ENCODING;
EncryptMessageParams.ContentEncryptionAlgorithm.pszObjId = szOID_NIST_AES256_CBC;
EncryptMessageParams.ContentEncryptionAlgorithm.Parameters.cbData = 0;
EncryptMessageParams.ContentEncryptionAlgorithm.Parameters.pbData = 0;
EncryptMessageParams.hCryptProv = NULL;
EncryptMessageParams.pvEncryptionAuxInfo = NULL;
EncryptMessageParams.dwFlags = 0;
EncryptMessageParams.dwInnerContentType = 0;
BYTE pbEncryptedBlob[640000];
DWORD pcbEncryptedBlob = 640000;
BOOL retval = CryptEncryptMessage(&EncryptMessageParams, cRecipientCert, pRecipCertContextArray, pbMsgText, dwMsgTextSize, pbEncryptedBlob, &pcbEncryptedBlob);
The Key Transport Algorithm is a bit tricky to handle, and it may not serve its purpose (I see you noted that you'd like CAPI to support RSAencryption; trust me, I would too). It looks like you've alaready detected the bulk of your problem - The generated message appears is valid, but your method makes it necessary to use CryptEncryptMessage, which won't work well/at all in the long run.
Step 1 - Examine the Code
CRYPT_ENCRYPT_MESSAGE_PARA EncryptMessageParams;
EncryptMessageParams.cbSize = sizeof(CMSG_ENVELOPED_ENCODE_INFO);
EncryptMessageParams.dwMsgEncodingType = PKCS_7_ASN_ENCODING;
EncryptMessageParams.ContentEncryptionAlgorithm.pszObjId = szOID_NIST_AES256_CBC;
EncryptMessageParams.ContentEncryptionAlgorithm.Parameters.cbData = 0;
EncryptMessageParams.ContentEncryptionAlgorithm.Parameters.pbData = 0;
EncryptMessageParams.hCryptProv = NULL;
EncryptMessageParams.pvEncryptionAuxInfo = NULL;
EncryptMessageParams.dwFlags = 0;
EncryptMessageParams.dwInnerContentType = 0;
BYTE pbEncryptedBlob[640000];
DWORD pcbEncryptedBlob = 640000;
BOOL retval = CryptEncryptMessage(&EncryptMessageParams, cRecipientCert, pRecipCertContextArray, pbMsgText, dwMsgTextSize, pbEncryptedBlob, &pcbEncryptedBlob);
Pretty basic, isn't it? Although efficient, it's not really getting the problem done. If you look at this:
EncryptMessageParams.dwFlags = 0;
EncryptMessageParams.dwInnerContentType = 0;
you will see that it is pre-defined, but used only in the definition of retval. However, I could definitely see this as a micro-optimization, and not really useful if we're going to re-write the code. However, I've outlined the basic steps blow to integrate this without a total re-do of the code (so you can keep on using the same parameters):
Step 2 - Editing the Parameters
As #owlstead mentioned in his comments, the Crypto API is not very user-friendly. However, you've done a great job with limited resources. What you'll wanna add is a Cryptographic Enumeration Provider to help narrow down the keys. Make sure you have either Microsoft Base Cryptographic Provider version 1.0 or Microsoft Enhanced Cryptographic Provider version 1.0 to use these efficiently. Otherwise, you'll need to add in the function like so:
DWORD cbName;
DWORD dwType;
DWORD dwIndex;
CHAR *pszName = NULL;
(regular crypt calls here)
This is mainly used to prevent the NTE_BAD_FLAGS error, although technically you could avoid this with a more low-level declaration. If you wanted, you could also create a whole new hash (although this is only necessary if the above implementation won't scale to the necessary factor of time/speed):
DWORD dwBufferLen = strlen((char *)pbBuffer)+1*(0+5);
HCRYPTHASH hHash;
HCRYPTKEY hKey;
HCRYPTKEY hPubKey;
BYTE *pbKeyBlob;
BYTE *pbSignature;
DWORD dwSigLen;
DWORD dwBlobLen;
(use hash as normal w/ crypt calls and the pbKeyBlobs/Signatures)
Make sure to vaildate this snippet before moving on. You can do so easily like so:
if(CryptAcquireContext(&hProv, NULL, NULL, PROV_RSA_FULL, 0)) {
printf("CSP context acquired.\n");
}
If you're documenting or releasing, might want to add a void MyHandleError(char *s) to catch the error so someone who edits but fails can catch it quickly.
By the way, the first time you run it you'll have to create a new set because there's no default. A nice one-liner that can be popped into an if is below:
CryptAcquireContext(&hCryptProv, NULL, NULL, PROV_RSA_FULL, CRYPT_NEWKEYSET)
Remember that syncing server resources will not be as efficient as doing the re-work I suggested in the first step. This is what I will be explaining below:
Step 3 - Recode and Relaunch
As a programmer, re-coding might seem like a waste of time, but it can definitely help you out in the long run. Remember that you'll still have to code in the custom params when encoding/syncing; I'm not going to hand-feed you all the code like a baby. It should be well sufficient to show you the basic outlines.
I'm definitely assuming that you're trying to handle to the current user's key container within a particular CSP; otherwise, I don't really see the use of this. If not, you can do some basic edits to suit your needs.
Remember, we're going to bypass CryptEncryptMessage by using CryptReleaseContext, which directly releases the handle acquired by the CryptAcquireContext function. Microsoft's standard on the CAC is below:
BOOL WINAPI CryptAcquireContext(
_Out_ HCRYPTPROV *phProv,
_In_ LPCTSTR pszContainer,
_In_ LPCTSTR pszProvider,
_In_ DWORD dwProvType,
_In_ DWORD dwFlags
);
Note that Microsoft's scolding you if you're using a user interface:
If the CSP must display the UI to operate, the call fails and the NTE_SILENT_CONTEXT error code is set as the last error. In addition, if calls are made to CryptGenKey with the CRYPT_USER_PROTECTED flag with a context that has been acquired with the CRYPT_SILENT flag, the calls fail and the CSP sets NTE_SILENT_CONTEXT.
This is mainly server code, and the ERROR_BUSY will definitely be displayed to new users when there are multiple connections, especially those with a high latency. Above 300ms will just cause a NTE_BAD_KEYSET_PARAM or similar to be called, due to the timeout without even a proper error being received. (Transmission problems, anyone with me?)
Unless you're concerned about multiple DLL's (which this doesn't support due to NTE_PROVIDER_DLL_FAIL errors), the basic set up to grab crypt services clientside would be as below (copied directly from Microsoft's examples):
if (GetLastError() == NTE_BAD_KEYSET)
{
if(CryptAcquireContext(
&hCryptProv,
UserName,
NULL,
PROV_RSA_FULL,
CRYPT_NEWKEYSET))
{
printf("A new key container has been created.\n");
}
else
{
printf("Could not create a new key container.\n");
exit(1);
}
}
else
{
printf("A cryptographic service handle could not be "
"acquired.\n");
exit(1);
}
However simple this may seem, you definitely don't want to get stuck passing this on to the key exchange algorithm (or whatever else you have handling this). Unless you're using symmetric session keys (Diffie-Hellman/KEA), the exchange keypair can be used to encrypt session keys so that they can be safely stored and exchanged with other users.
Someone named John Howard has written a nice Hyper-V Remote Management Configuration Utility (HVRemote) which is a large compilation of the techniques discussed here. In addition to using the basic crypts and keypairs, they can be used to permit ANONYMOUS LOGON remote DCOM access (cscript hvremote.wsf, to be specific). You can see many of the functions and techniques in his latest crypts (you'll have to narrow the query) on his blog:
http://blogs.technet.com/b/jhoward/
If you need any more help with the basics, just leave a comment or request a private chat.
Conclusion
Although it's pretty simple once you realize the basic server-side methods for hashing and how the client grabs the "crypts", you'll be questioning why you even tried the encryption during transmits. However, without the crypting clientside, encrypts would definitely be the only secure way to transmit what was already hashed.
Although you might argue that the packets could be decrypted and hashed off the salts, consider that both in-outgoing would have to be processed and stored in the correct timing and order necessary to re-hash clientside.