Skip to content

Commit

Permalink
add support for a password callback
Browse files Browse the repository at this point in the history
In order to be able to load password-protected key files a password
callback was added.

This also adds support for PKCS#12 containers instead of certificate+key.

Signed-off-by: Steffen Jaeckel <jaeckel-floss@eyet-services.de>
  • Loading branch information
sjaeckel committed Feb 3, 2022
1 parent ceef0c3 commit b5c33a9
Show file tree
Hide file tree
Showing 11 changed files with 383 additions and 57 deletions.
55 changes: 52 additions & 3 deletions examples/complex.c
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include <strophe.h>

Expand Down Expand Up @@ -80,6 +81,52 @@ static int certfail_handler(const xmpp_tlscert_t *cert,
return read_char[0] == 'y' || read_char[0] == 'Y';
}

static int
password_callback(char *pw, size_t pw_max, const char *fname, void *userdata)
{
(void)userdata;
/* when using an encrypted keyfile, we get asked multiple times for the
* password ... cache it until we're connected
*/
static struct {
char pass[256];
unsigned char hash[XMPP_SHA1_DIGEST_SIZE];
size_t passlen, fnamelen;
unsigned count;
} password_cache;
int ret = -1;
unsigned char hash[XMPP_SHA1_DIGEST_SIZE];

size_t fname_len = strlen(fname);
xmpp_sha1_digest((void *)fname, fname_len, hash);
if (fname_len && fname_len == password_cache.fnamelen &&
memcmp(hash, password_cache.hash, sizeof(hash)) == 0) {
if (password_cache.passlen && --password_cache.count) {
memcpy(pw, password_cache.pass, password_cache.passlen + 1);
ret = password_cache.passlen;
if (!password_cache.count)
memset(&password_cache, 0, sizeof(password_cache));
return ret;
}
}
printf("Trying to unlock %s\n", fname);
char *pass = getpass("Please enter password: ");
if (!pass)
return -1;
size_t passlen = strlen(pass);
if (passlen < pw_max) {
memcpy(pw, pass, passlen + 1);
ret = passlen;
memcpy(password_cache.pass, pass, passlen + 1);
password_cache.passlen = passlen;
password_cache.count = 6;
memcpy(password_cache.hash, hash, sizeof(hash));
password_cache.fnamelen = fname_len;
}
memset(pass, 0, passlen);
return ret;
}

static void usage(int exit_code)
{
fprintf(stderr,
Expand All @@ -88,7 +135,7 @@ static void usage(int exit_code)
" --jid <jid> The JID to use to authenticate.\n"
" --pass <pass> The password of the JID.\n"
" --tls-cert <cert> Path to client certificate.\n"
" --tls-key <key> Path to private key.\n\n"
" --tls-key <key> Path to private key or P12 file.\n\n"
" --capath <path> Path to an additional CA trust store "
"(directory).\n"
" --cafile <path> Path to an additional CA trust store "
Expand Down Expand Up @@ -154,7 +201,7 @@ int main(int argc, char **argv)
else
break;
}
if ((!jid && (!cert || !key)) || (argc - i) > 2) {
if ((!jid && !key) || (argc - i) > 2) {
usage(1);
}

Expand Down Expand Up @@ -186,8 +233,10 @@ int main(int argc, char **argv)
if (tcp_keepalive)
xmpp_conn_set_keepalive(conn, KA_TIMEOUT, KA_INTERVAL);

/* ask for a password if key is protected */
xmpp_conn_set_password_callback(conn, password_callback, conn);
/* setup authentication information */
if (cert && key) {
if (key) {
xmpp_conn_set_client_cert(conn, cert, key);
}
if (jid)
Expand Down
2 changes: 1 addition & 1 deletion src/auth.c
Original file line number Diff line number Diff line change
Expand Up @@ -242,7 +242,7 @@ _handle_features(xmpp_conn_t *conn, xmpp_stanza_t *stanza, void *userdata)
if (strcasecmp(text, "PLAIN") == 0)
conn->sasl_support |= SASL_MASK_PLAIN;
else if (strcasecmp(text, "EXTERNAL") == 0 &&
conn->tls_client_cert)
(conn->tls_client_cert || conn->tls_client_key))
conn->sasl_support |= SASL_MASK_EXTERNAL;
else if (strcasecmp(text, "DIGEST-MD5") == 0)
conn->sasl_support |= SASL_MASK_DIGESTMD5;
Expand Down
2 changes: 2 additions & 0 deletions src/common.h
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,8 @@ struct _xmpp_conn_t {
int auth_legacy_enabled;
int secured; /* set when stream is secured with TLS */
xmpp_certfail_handler certfail_handler;
xmpp_password_callback password_callback;
void *password_callback_userdata;

/* if server returns <bind/> or <session/> we must do them */
int bind_required;
Expand Down
44 changes: 36 additions & 8 deletions src/conn.c
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,8 @@ xmpp_conn_t *xmpp_conn_new(xmpp_ctx_t *ctx)
conn->auth_legacy_enabled = 0;
conn->secured = 0;
conn->certfail_handler = NULL;
conn->password_callback = NULL;
conn->password_callback_userdata = NULL;

conn->bind_required = 0;
conn->session_required = 0;
Expand Down Expand Up @@ -465,15 +467,38 @@ xmpp_tlscert_t *xmpp_conn_get_peer_cert(xmpp_conn_t *const conn)
return tls_peer_cert(conn);
}

/** Set the Client Certificate and Private Key that will be bound to the
* connection. If any of the both was previously set, it will be discarded.
* This should not be used after a connection is created. The function will
* make a copy of the strings passed in.
* Currently only non-encrypted Private Keys are supported.
/** Set the Callback function which will be called when the TLS stack can't
* decrypt a password protected key file.
*
* @param conn a Strophe connection object
* @param cb The callback function that shall be called
* @param userdata An opaque data pointer that will be passed to the callback
*
* @ingroup TLS
*/
void xmpp_conn_set_password_callback(xmpp_conn_t *conn,
xmpp_password_callback cb,
void *userdata)
{
conn->password_callback = cb;
conn->password_callback_userdata = userdata;
}

/** Set the Client Certificate and Private Key or PKCS#12 encoded file that
* will be bound to the connection. If any of them was previously set, it
* will be discarded. This should not be used after a connection is created.
* The function will make a copy of the strings passed in.
*
* In case the Private Key is encrypted, a callback must be set via
* \ref xmpp_conn_set_password_callback so the TLS stack can retrieve the
* password.
*
* In case one wants to use a PKCS#12 encoded file, it must be passed via
* the `key` parameter and `cert` must be NULL.
*
* @param conn a Strophe connection object
* @param cert path to a certificate file
* @param key path to a private key file
* @param key path to a private key file or a P12 file
*
* @ingroup TLS
*/
Expand All @@ -484,7 +509,10 @@ void xmpp_conn_set_client_cert(xmpp_conn_t *const conn,
log_debug(conn->ctx, "conn", "set client cert %s %s", cert, key);
if (conn->tls_client_cert)
xmpp_free(conn->ctx, conn->tls_client_cert);
conn->tls_client_cert = strophe_strdup(conn->ctx, cert);
if (cert)
conn->tls_client_cert = strophe_strdup(conn->ctx, cert);
else
conn->tls_client_cert = NULL;
if (conn->tls_client_key)
xmpp_free(conn->ctx, conn->tls_client_key);
conn->tls_client_key = strophe_strdup(conn->ctx, key);
Expand Down Expand Up @@ -593,7 +621,7 @@ int xmpp_connect_client(xmpp_conn_t *conn,
int found = XMPP_DOMAIN_NOT_FOUND;
int rc;

if (!conn->jid && conn->tls_client_cert) {
if (!conn->jid && (conn->tls_client_cert || conn->tls_client_key)) {
if (tls_id_on_xmppaddr_num(conn) != 1) {
log_debug(conn->ctx, "xmpp",
"Client certificate contains multiple or no xmppAddr "
Expand Down
102 changes: 98 additions & 4 deletions src/tls_gnutls.c
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
#include <gnutls/gnutls.h>
#include <gnutls/x509.h>
#include <gnutls/x509-ext.h>
#include <gnutls/pkcs11.h>
#include <gnutls/pkcs12.h>

#include "common.h"
#include "tls.h"
Expand Down Expand Up @@ -47,10 +49,30 @@ void tls_shutdown(void)
gnutls_global_deinit();
}

static gnutls_x509_crt_t _tls_load_cert(xmpp_conn_t *conn)
static int _tls_password_callback(void *userdata,
int attempt,
const char *token_url,
const char *token_label,
unsigned int flags,
char *pin,
size_t pin_max)
{
xmpp_conn_t *conn = userdata;
UNUSED(attempt);
UNUSED(token_url);
UNUSED(token_label);
UNUSED(flags);
if (!conn->password_callback) {
log_error(conn->ctx, "tls", "No password callback set");
return -1;
}
int ret = conn->password_callback(pin, pin_max, conn->tls_client_key,
conn->password_callback_userdata);
return ret > 0 ? 0 : GNUTLS_E_PKCS11_PIN_ERROR;
}

static gnutls_x509_crt_t _tls_load_cert_x509(xmpp_conn_t *conn)
{
if (conn->tls && conn->tls->client_cert)
return conn->tls->client_cert;
gnutls_x509_crt_t cert;
gnutls_datum_t data;
int res;
Expand All @@ -68,6 +90,63 @@ static gnutls_x509_crt_t _tls_load_cert(xmpp_conn_t *conn)
return NULL;
}

static gnutls_x509_crt_t _tls_load_cert_p12(xmpp_conn_t *conn)
{
gnutls_pkcs12_t p12;
gnutls_x509_crt_t *cert;
gnutls_datum_t data;
gnutls_x509_privkey_t key;
unsigned int cert_num = 0;
if (gnutls_pkcs12_init(&p12) < 0)
return NULL;
if (gnutls_load_file(conn->tls_client_key, &data) < 0)
goto error_out;
if (gnutls_pkcs12_import(p12, &data, GNUTLS_X509_FMT_DER, 0) < 0)
goto error_out2;

char pass[GNUTLS_PKCS11_MAX_PIN_LEN];
pass[0] = '\0';
int passlen =
_tls_password_callback(conn, 0, NULL, NULL, 0, pass, sizeof(pass));
if (passlen < 0)
pass[0] = '\0';

int res = gnutls_pkcs12_simple_parse(p12, pass, &key, &cert, &cert_num,
NULL, NULL, NULL, 0);
gnutls_pkcs12_deinit(p12);
gnutls_free(data.data);
if (res < 0)
goto error_out;
gnutls_x509_privkey_deinit(key);
if (cert_num > 1) {
log_error(conn->ctx, "tls", "Can't handle stack of %u certs", cert_num);
goto error_out;
}
gnutls_x509_crt_t ret = *cert;
gnutls_free(cert);
return ret;
error_out2:
gnutls_free(data.data);
error_out:
if (cert) {
for (unsigned int n = 0; n < cert_num; ++n) {
gnutls_x509_crt_deinit(cert[n]);
}
gnutls_free(cert);
}
return NULL;
}

static gnutls_x509_crt_t _tls_load_cert(xmpp_conn_t *conn)
{
if (conn->tls && conn->tls->client_cert)
return conn->tls->client_cert;
if (!conn->tls_client_cert && conn->tls_client_key) {
return _tls_load_cert_p12(conn);
}
return _tls_load_cert_x509(conn);
}

static void _tls_free_cert(xmpp_conn_t *conn, gnutls_x509_crt_t cert)
{
if (conn->tls && conn->tls->client_cert == cert)
Expand Down Expand Up @@ -268,12 +347,15 @@ static int _tls_verify(gnutls_session_t session)

/* Return early if the Certificate is trusted
* OR if we trust all Certificates */
if (status == 0 || tls->conn->tls_trust)
if (status == 0 || tls->conn->tls_trust) {
gnutls_free(out.data);
return 0;
}

if (!tls->conn->certfail_handler) {
log_error(tls->ctx, "tls",
"No certfail handler set, canceling connection attempt");
gnutls_free(out.data);
return -1;
}

Expand Down Expand Up @@ -323,6 +405,9 @@ tls_t *tls_new(xmpp_conn_t *conn)
gnutls_certificate_allocate_credentials(&tls->cred);
tls_set_credentials(tls, NULL);

gnutls_certificate_set_pin_function(tls->cred, _tls_password_callback,
conn);

if (conn->tls_client_cert && conn->tls_client_key) {
tls->client_cert = _tls_load_cert(conn);
if (!tls->client_cert) {
Expand All @@ -335,6 +420,15 @@ tls_t *tls_new(xmpp_conn_t *conn)
gnutls_certificate_set_x509_key_file(
tls->cred, conn->tls_client_cert, conn->tls_client_key,
GNUTLS_X509_FMT_PEM);
} else if (conn->tls_client_key) {
char pass[GNUTLS_PKCS11_MAX_PIN_LEN];
pass[0] = '\0';
int passlen = _tls_password_callback(conn, 0, NULL, NULL, 0, pass,
sizeof(pass));
if (passlen < 0)
pass[0] = '\0';
gnutls_certificate_set_x509_simple_pkcs12_file(
tls->cred, conn->tls_client_key, GNUTLS_X509_FMT_DER, pass);
}

gnutls_certificate_set_verify_function(tls->cred, _tls_verify);
Expand Down
Loading

0 comments on commit b5c33a9

Please sign in to comment.