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.
Related
I'm hoping someone can help with my OpenSSL(1.1.1k) issue I'm having when trying to validate a certificate on the client against a server. The certificate contents are specified in text and not in a .PEM file on the client side. Some sample code is here:
bool setCert(SSL_CTX* ctx, LPCSTR cert)
{
//cert is the text of the certificate e.g. -----BEGIN CERTIFICATE----- blah -----END CERTIFICATE-----
bool bOk = true;
if (!cert.empty()) // no cert on disk
{
BIO *bioCert = BIO_new_mem_buf((void*)cert, -1);
X509* pCert = PEM_read_bio_X509(bioCert, NULL, 0, NULL);
if (SSL_CTX_use_certificate(ctx, pCert) != 1)
{
//log errror, "Invalid certificate"
bOk = false;
}
BIO_free_all(bioCert);
X509_free(pCert);
}
SSL_CTX_set_options(ctx, SSL_OP_NO_SSLv2|SSL_OP_NO_SSLv3); // other options not used atm SSL_OP_NO_TLSv1 | SSL_OP_NO_TLSv1_1 | SSL_OP_NO_TLSv1_2
SSL_CTX_set_session_cache_mode(ctx, SSL_SESS_CACHE_OFF);
return bOk;
}
After this method returns, I create an SSL object based on the context, with SSL_new(ctx).
I then call SSL_Connect(ssl)
The handshake occurs and is accepted even if I pass in an invalid certificate.
My assumption here is that it's doing this because I do not set the verify method like:
SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, NULL);
My understanding is that I cannot use SSL_VERIFY_PEER unless there is a cert / .PEM file on disk on the client side to validate against.
How can I validate the handshake when keeping the certificate contents in memory?
If I change the code to use a PEM file, call SSL_CTX_use_certificate_file instead of SSL_CTX_use_certificate, it works. In that case I did set SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, NULL);
Any help here is greatly appreciated. Thanks
SSL_CTX_use_certificate is used for setting up the certificate which should be used locally to authenticate against the peer. It needs to be accompanied with a private key, setup with SSL_CTX_use_PrivateKey. But authenticating against a peer, i.e. using a client certificate, is not what you intend to do in your client.
Instead you want to setup a certificate for use as a trusted CA when validating the server certificate. The correct function for this would be X509_STORE_add_cert instead:
X509_STORE_add_cert(SSL_CTX_get_cert_store(ctx), pCert);
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.
At the moment, I am investigating the possibilities to use ZeroMQ with Curve to secure traffic between my publishers and subscribers.
I have successfully implemented a pub sub system which is using CZMQ.
At the moment, my publisher is encrypting the data he wants to send with his private key and subscribers can decrypt this data using the public key of the publisher. This is more 'authenticating' data than 'encrypting' data. Because when there is a Man In The Middle he can still decrypt all the data because the public key of the publisher is public.
I'm coding in C with the latest version of ZeroMQ and CZMQ.
My publisher.c
zctx_t* zmq_context = zctx_new();
zauth_t* auth = zauth_new (zmq_context);
zauth_set_verbose (auth, true);
zauth_configure_curve (auth, "*", CURVE_ALLOW_ANY);
zcert_t* pub_cert = zcert_load("cert.key"); // private key of publisher
void* socket = zsocket_new(zmq_context, ZMQ_PUB);
zcert_apply(pub_cert, socket);
zsocket_set_curve_server(socket, true);
//start publishing from here
My subscriber.c
zctx_t* zmq_context = zctx_new();
zcert_t* sub_cert = zcert_new();
zcert_t* pub_cert = zcert_load("cert.pub"); // public key of publisher
char* pub_key = zcert_public_txt(pub_cert);
void* zmq_s = zsocket_new(zmq_context, ZMQ_SUB);
zcert_apply(sub_cert, zmq_s);
zsocket_set_curve_serverkey(zmq_s, pub_key);
//start subscribing to topics and receiving messages from here
From this point, the publisher is encrypting all the data with his private key and the subscribing is decrypting all the data with the public key of the publisher. I would like to swap this system.
So, I would like to encrypt all the data with the public key of the publisher and decrypt all the data with the private key of the publisher.
I have tested it and changed the zcert_load("cert.key") to zcert_load("cert.pub") in my publisher.c.
I also changed this code in my subscriber.c:
zcert_t* pub_cert = zcert_load("cert.pub"); // public key of publisher
char* pub_key = zcert_public_txt(pub_cert);
to this code:
zcert_t* pub_cert = zcert_load("cert.key"); // private key of publisher
char* pub_key = zcert_secret_txt(pub_cert);
When I run my publisher and subscriber with these code changes, the publisher is constantly giving me the message: CURVE I: cannot open client HELLO -- wrong server key?
My question: Is it possibile to use a public key for encrypting data (publisher socket) and the private key for decrypting data (subscriber socket) with the architecture of ZeroMQ and CZMQ ?
Many thanks in advance,
Roy
I think you're misunderstanding the ZeroMQ CURVE mechanism. There are two great articles written about it by Pieter Hintjens, one is theoretical, where you can find this:
Clients and servers have long-term permanent keys, and for each connection, they create and securely exchange short-term transient keys. Each key is a public/secret keypair, following the elliptic curve security model.
To start a secure connection the client needs the server permanent public key. It then generates a transient key pair and sends a HELLO command to the server that contains its short term public key. The HELLO command is worthless to an attacker; it doesn't identify the client.
The server, when it gets a HELLO, generates its own short term key pair (one connection uses four keys in total), and encodes this new private key in a "cookie", which it sends back to the client as a WELCOME command. It also sends its short term public key, encrypted so only the client can read it. It then discards this short term key pair.
At this stage, the server hasn't stored any state for the client. It has generated a keypair, sent that back to the client in a way only the client can read, and thrown it away.
The client comes back with an INITIATE command that provides the server with its cookie back, and the client permanent public key, encrypted as a "vouch" so only the server can read it. As far as the client is concerned, the server is now authenticated, so it can also send back metadata in the command.
The server reads the INITIATE and can now authenticate the client permanent public key. It also unpacks the cookie and gets its short term key pair for the connection. As far as the server is now concerned, the client is now authenticated, so the server can send its metadata safely. Both sides can then send messages and commands.
So the keys you generate here are not used directly to encrypt data, they are only used to authenticate parties. The handshake process between client and server produces real encryption (and authentication as in MAC) keys that are used to encrypt the data. So if the only concern you have is MITM, you're already protected.
But you can also be concerned about rogue clients, your current scheme allows anyone to connect. This is where the second article by Pieter Hintjens can help you with "The Ironhouse Pattern". The critical part is to disable CURVE_ALLOW_ANY and tell server where to look for client public certificates:
// Tell authenticator to use the certificate store in .curve
zauth_configure_curve (auth, "*", ".curve");
Then you generate client key on the client (once! not calling zcert_new() for every connection), transfer its public part to the ".curve" directory of your server and load (on the client) that key instead of calling zcert_new().
If you want to make life a bit easier (not needing to transfer public keys from client to server for each client), you can create a "golden" client key pair, get its public part into the ".curve" store and then (via a secure channel, of course) copy the secret key on every client that needs to connect to your publisher.
I want to get multiple connections with OpenSSL.
Should I create new SSL_CTX context for every new connection or accept all connections with one context?
Should I do additional actions with memory or something with start/stop connection, except
close(_socket); //socket which accept the connection
SSL_shutdown(_ssl); //_ssl — SSL connection
SSL_free (_ssl);
Should I create new SSL_CTX context for every new connection or accept all connections with one context?
It depends on the number of server names and distinct certificates.
If you have one server name and one certificate, then use one default context.
If you have multiple server names and one certificate, then use one default context.
If you have multiple server names and multiple certificate, then see below on the SNI or servername callback and swapping in a context.
If your server listens for foo.com and bar.com with distinct certifcates, then you will need three contexts. One default context is for non-SNI clients, one context is for foo.com, and one context is for bar.com.
Effectively, the only thing that will likely change between the two sites is the certificate that's served. So you listen with the default context. If the client provided a servername via SNI, then you swap-in one of the other two contexts in the servername callback and SSL_set_SSL_CTX. Here's how it would look:
static int ServerNameCallback(SSL *ssl, int *ad, void *arg)
{
UNUSED(ad);
UNUSED(arg);
ASSERT(ssl);
if (ssl == NULL)
return SSL_TLSEXT_ERR_NOACK;
const char* servername = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name);
ASSERT(servername && servername[0]);
if (!servername || servername[0] == '\0')
return SSL_TLSEXT_ERR_NOACK;
/* Need a certificate and context for this domain */
SSL_CTX* ctx = GetServerContext(servername);
ASSERT(ctx != NULL);
if (ctx == NULL)
return SSL_TLSEXT_ERR_NOACK;
/* We should not be peeking into the object like this... */
ASSERT(ctx != ssl->ctx);
/* Useless return value */
SSL_CTX* v = SSL_set_SSL_CTX(ssl, ctx);
return SSL_TLSEXT_ERR_OK;
}
GetServerContext just provides the context for foo.com or bar.com. It creates them once, and then reuses the same ones.
For the default context, you set the servername callback with SSL_CTX_set_tlsext_servername_callback. There's no need to set it for non-default contexts.
SSL_CTX_set_tlsext_servername_callback(ctx, ServerNameCallback);
Contexts are referenced counted, so you can reuse them.
Should I do additional actions with memory or something with
start/stop connection, except
close(_socket); //socket which accept the connection
SSL_shutdown(_ssl); //_ssl — SSL connection
SSL_free (_ssl);
Here, you should seek a treatment of the subject. For example, you should not call close before SSL_shutdown. And the first call to SSL_shutdown may fail, so you need to know what to do next.
For a treatment of the subject, see Eric Rescorla's tutorials: An Introduction to OpenSSL Programming, Part I of II and An Introduction to OpenSSL Programming, Part II of II. Or get the book: Network Security with OpenSSL.
I am trying to use OpenSSL in my application, and achieve a secure connection.
At first I tried:
create ssl struct
create socketbio for the tcp socket
create a sslbio
set socketbio to SSL strcut
SSL_accept(ssl)
BIO_push(ssl, socketbio)
This cause handshake to happen successfully, but application data wasn't properly decrypted.
Then I tweaked a little, and relaced 6 with
(new) BIO_ctrl(sslbio, SET_SSL, ssl)
and things worked fine.
I Wanted to know, what's wrong with previous approach, and what's causing the new apprach work?
It's hard to answer the question without knowing why you think BIO_push is all you need to do. At any rate, you shouldn't call BIO_ctrl directly. You should use the high-level wrapper BIO_set_ssl defined in bio.h:
#define BIO_set_ssl(b,ssl,c) BIO_ctrl(b,BIO_C_SET_SSL,c,(char *)ssl)
This macro sets the ssl member of the BIO object as you can see in bio_ssl.c:
case BIO_C_SET_SSL:
if (ssl != NULL)
ssl_free(b);
b->shutdown=(int)num;
ssl=(SSL *)ptr;
((BIO_SSL *)b->ptr)->ssl=ssl;
bio=SSL_get_rbio(ssl);
if (bio != NULL)
{
if (b->next_bio != NULL)
BIO_push(bio,b->next_bio);
b->next_bio=bio;
CRYPTO_add(&bio->references,1,CRYPTO_LOCK_BIO);
}
b->init=1;
break;
The important step in this function is not the BIO_push, but rather is where it sets the ssl pointer in the BIO_SSL object to your active SSL context, i.e., ((BIO_SSL *)b->ptr)->ssl=ssl;.