move password cache into libstrophe
The cache is stored per connection object and is cleared on * entry of wrong password * release of connection object * successful connection It can be configured that libstrophe retries the password entry in case the user entered a wrong password. Signed-off-by: Steffen Jaeckel <jaeckel-floss@eyet-services.de>
This commit is contained in:
@@ -1,7 +1,11 @@
|
|||||||
0.12.0
|
0.12.0
|
||||||
- Fix potential infinite loop in resolver (#200)
|
- Fix potential infinite loop in resolver (#200)
|
||||||
- Add code coverage support
|
- Add code coverage support
|
||||||
|
- Add support for password-protected TLS key & PKCS#12/PFX files
|
||||||
- New API:
|
- New API:
|
||||||
|
- xmpp_conn_get_keyfile()
|
||||||
|
- xmpp_conn_set_password_callback()
|
||||||
|
- xmpp_conn_set_password_retries()
|
||||||
- xmpp_stanza_get_child_by_path()
|
- xmpp_stanza_get_child_by_path()
|
||||||
- xmpp_conn_set_sockopt_callback()
|
- xmpp_conn_set_sockopt_callback()
|
||||||
- xmpp_sockopt_cb_keepalive()
|
- xmpp_sockopt_cb_keepalive()
|
||||||
|
|||||||
@@ -95,47 +95,22 @@ static int certfail_handler(const xmpp_tlscert_t *cert,
|
|||||||
}
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
password_callback(char *pw, size_t pw_max, const char *fname, void *userdata)
|
password_callback(char *pw, size_t pw_max, xmpp_conn_t *conn, void *userdata)
|
||||||
{
|
{
|
||||||
(void)userdata;
|
(void)userdata;
|
||||||
/* when using an encrypted keyfile, we get asked multiple times for the
|
printf("Trying to unlock %s\n", xmpp_conn_get_keyfile(conn));
|
||||||
* 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: ");
|
char *pass = getpass("Please enter password: ");
|
||||||
if (!pass)
|
if (!pass)
|
||||||
return -1;
|
return -1;
|
||||||
size_t passlen = strlen(pass);
|
size_t passlen = strlen(pass);
|
||||||
if (passlen < pw_max) {
|
int ret;
|
||||||
memcpy(pw, pass, passlen + 1);
|
if (passlen >= pw_max) {
|
||||||
ret = passlen;
|
ret = -1;
|
||||||
memcpy(password_cache.pass, pass, passlen + 1);
|
goto out;
|
||||||
password_cache.passlen = passlen;
|
|
||||||
password_cache.count = 6;
|
|
||||||
memcpy(password_cache.hash, hash, sizeof(hash));
|
|
||||||
password_cache.fnamelen = fname_len;
|
|
||||||
}
|
}
|
||||||
|
ret = passlen + 1;
|
||||||
|
memcpy(pw, pass, ret);
|
||||||
|
out:
|
||||||
memset(pass, 0, passlen);
|
memset(pass, 0, passlen);
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
@@ -327,7 +302,9 @@ int main(int argc, char **argv)
|
|||||||
xmpp_conn_set_sockopt_callback(conn, sockopt_cb);
|
xmpp_conn_set_sockopt_callback(conn, sockopt_cb);
|
||||||
|
|
||||||
/* ask for a password if key is protected */
|
/* ask for a password if key is protected */
|
||||||
xmpp_conn_set_password_callback(conn, password_callback, conn);
|
xmpp_conn_set_password_callback(conn, password_callback, NULL);
|
||||||
|
/* try at max 3 times in case the user enters the password wrong */
|
||||||
|
xmpp_conn_set_password_retries(conn, 3);
|
||||||
/* setup authentication information */
|
/* setup authentication information */
|
||||||
if (key) {
|
if (key) {
|
||||||
xmpp_conn_set_client_cert(conn, cert, key);
|
xmpp_conn_set_client_cert(conn, cert, key);
|
||||||
|
|||||||
@@ -825,6 +825,7 @@ static void _auth(xmpp_conn_t *conn)
|
|||||||
|
|
||||||
static void _auth_success(xmpp_conn_t *conn)
|
static void _auth_success(xmpp_conn_t *conn)
|
||||||
{
|
{
|
||||||
|
tls_clear_password_cache(conn);
|
||||||
conn->authenticated = 1;
|
conn->authenticated = 1;
|
||||||
/* call connection handler */
|
/* call connection handler */
|
||||||
conn->conn_handler(conn, XMPP_CONN_CONNECT, 0, NULL, conn->userdata);
|
conn->conn_handler(conn, XMPP_CONN_CONNECT, 0, NULL, conn->userdata);
|
||||||
|
|||||||
@@ -194,6 +194,12 @@ struct _xmpp_conn_t {
|
|||||||
xmpp_certfail_handler certfail_handler;
|
xmpp_certfail_handler certfail_handler;
|
||||||
xmpp_password_callback password_callback;
|
xmpp_password_callback password_callback;
|
||||||
void *password_callback_userdata;
|
void *password_callback_userdata;
|
||||||
|
struct {
|
||||||
|
char pass[1024];
|
||||||
|
unsigned char fname_hash[XMPP_SHA1_DIGEST_SIZE];
|
||||||
|
size_t passlen, fnamelen;
|
||||||
|
} password_cache;
|
||||||
|
unsigned int password_retries;
|
||||||
|
|
||||||
/* if server returns <bind/> or <session/> we must do them */
|
/* if server returns <bind/> or <session/> we must do them */
|
||||||
int bind_required;
|
int bind_required;
|
||||||
|
|||||||
37
src/conn.c
37
src/conn.c
@@ -184,6 +184,8 @@ xmpp_conn_t *xmpp_conn_new(xmpp_ctx_t *ctx)
|
|||||||
conn->certfail_handler = NULL;
|
conn->certfail_handler = NULL;
|
||||||
conn->password_callback = NULL;
|
conn->password_callback = NULL;
|
||||||
conn->password_callback_userdata = NULL;
|
conn->password_callback_userdata = NULL;
|
||||||
|
tls_clear_password_cache(conn);
|
||||||
|
conn->password_retries = 1;
|
||||||
|
|
||||||
conn->bind_required = 0;
|
conn->bind_required = 0;
|
||||||
conn->session_required = 0;
|
conn->session_required = 0;
|
||||||
@@ -384,6 +386,7 @@ int xmpp_conn_release(xmpp_conn_t *conn)
|
|||||||
strophe_free(ctx, conn->tls_cafile);
|
strophe_free(ctx, conn->tls_cafile);
|
||||||
if (conn->tls_capath)
|
if (conn->tls_capath)
|
||||||
strophe_free(ctx, conn->tls_capath);
|
strophe_free(ctx, conn->tls_capath);
|
||||||
|
tls_clear_password_cache(conn);
|
||||||
strophe_free(ctx, conn);
|
strophe_free(ctx, conn);
|
||||||
released = 1;
|
released = 1;
|
||||||
}
|
}
|
||||||
@@ -511,6 +514,40 @@ void xmpp_conn_set_password_callback(xmpp_conn_t *conn,
|
|||||||
conn->password_callback_userdata = userdata;
|
conn->password_callback_userdata = userdata;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Set the number of retry attempts to decrypt a private key file.
|
||||||
|
*
|
||||||
|
* In case the user enters the password manually it can be useful to
|
||||||
|
* directly retry if the decryption of the key file failed.
|
||||||
|
*
|
||||||
|
* @param conn a Strophe connection object
|
||||||
|
* @param retries The number of retries that should be tried
|
||||||
|
*
|
||||||
|
* @ingroup TLS
|
||||||
|
*/
|
||||||
|
void xmpp_conn_set_password_retries(xmpp_conn_t *conn, unsigned int retries)
|
||||||
|
{
|
||||||
|
if (retries == 0)
|
||||||
|
conn->password_retries = 1;
|
||||||
|
else
|
||||||
|
conn->password_retries = retries;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Retrieve the path of the key file that shall be unlocked.
|
||||||
|
*
|
||||||
|
* This makes usually sense to be called from the
|
||||||
|
* \ref xmpp_password_callback .
|
||||||
|
*
|
||||||
|
* @param conn a Strophe connection object
|
||||||
|
*
|
||||||
|
* @return a String of the path to the key file
|
||||||
|
*
|
||||||
|
* @ingroup TLS
|
||||||
|
*/
|
||||||
|
const char *xmpp_conn_get_keyfile(const xmpp_conn_t *conn)
|
||||||
|
{
|
||||||
|
return conn->tls_client_key;
|
||||||
|
}
|
||||||
|
|
||||||
/** Set the Client Certificate and Private Key or PKCS#12 encoded file that
|
/** 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 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.
|
* will be discarded. This should not be used after a connection is created.
|
||||||
|
|||||||
37
src/tls.c
37
src/tls.c
@@ -211,3 +211,40 @@ int tlscert_add_dnsname(xmpp_tlscert_t *cert, const char *dnsname)
|
|||||||
strophe_strdup(cert->ctx, dnsname);
|
strophe_strdup(cert->ctx, dnsname);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int tls_caching_password_callback(char *pw, size_t pw_max, xmpp_conn_t *conn)
|
||||||
|
{
|
||||||
|
int ret;
|
||||||
|
unsigned char hash[XMPP_SHA1_DIGEST_SIZE];
|
||||||
|
|
||||||
|
const char *fname = conn->tls_client_key;
|
||||||
|
size_t fname_len = strlen(fname);
|
||||||
|
xmpp_sha1_digest((void *)fname, fname_len, hash);
|
||||||
|
if (fname_len && fname_len == conn->password_cache.fnamelen &&
|
||||||
|
memcmp(hash, conn->password_cache.fname_hash, sizeof(hash)) == 0) {
|
||||||
|
if (conn->password_cache.passlen) {
|
||||||
|
memcpy(pw, conn->password_cache.pass,
|
||||||
|
conn->password_cache.passlen + 1);
|
||||||
|
return conn->password_cache.passlen;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
size_t max_len = pw_max == 256 ? pw_max : sizeof(conn->password_cache.pass);
|
||||||
|
ret = conn->password_callback(conn->password_cache.pass, max_len, conn,
|
||||||
|
conn->password_callback_userdata);
|
||||||
|
|
||||||
|
if (ret < 0 || ret >= (ssize_t)max_len) {
|
||||||
|
memset(conn->password_cache.pass, 0, sizeof(conn->password_cache.pass));
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
conn->password_cache.pass[ret] = '\0';
|
||||||
|
memcpy(pw, conn->password_cache.pass, ret + 1);
|
||||||
|
conn->password_cache.passlen = ret;
|
||||||
|
conn->password_cache.fnamelen = fname_len;
|
||||||
|
memcpy(conn->password_cache.fname_hash, hash, sizeof(hash));
|
||||||
|
return conn->password_cache.passlen;
|
||||||
|
}
|
||||||
|
|
||||||
|
void tls_clear_password_cache(xmpp_conn_t *conn)
|
||||||
|
{
|
||||||
|
memset(&conn->password_cache, 0, sizeof(conn->password_cache));
|
||||||
|
}
|
||||||
|
|||||||
@@ -62,4 +62,7 @@ int tls_is_recoverable(int error);
|
|||||||
xmpp_tlscert_t *tlscert_new(xmpp_ctx_t *ctx);
|
xmpp_tlscert_t *tlscert_new(xmpp_ctx_t *ctx);
|
||||||
int tlscert_add_dnsname(xmpp_tlscert_t *cert, const char *dnsname);
|
int tlscert_add_dnsname(xmpp_tlscert_t *cert, const char *dnsname);
|
||||||
|
|
||||||
|
int tls_caching_password_callback(char *pw, size_t pw_max, xmpp_conn_t *conn);
|
||||||
|
void tls_clear_password_cache(xmpp_conn_t *conn);
|
||||||
|
|
||||||
#endif /* __LIBSTROPHE_TLS_H__ */
|
#endif /* __LIBSTROPHE_TLS_H__ */
|
||||||
|
|||||||
108
src/tls_gnutls.c
108
src/tls_gnutls.c
@@ -62,12 +62,7 @@ static int _tls_password_callback(void *userdata,
|
|||||||
UNUSED(token_url);
|
UNUSED(token_url);
|
||||||
UNUSED(token_label);
|
UNUSED(token_label);
|
||||||
UNUSED(flags);
|
UNUSED(flags);
|
||||||
if (!conn->password_callback) {
|
int ret = tls_caching_password_callback(pin, pin_max, conn);
|
||||||
strophe_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;
|
return ret > 0 ? 0 : GNUTLS_E_PKCS11_PIN_ERROR;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -93,10 +88,15 @@ error_out:
|
|||||||
static gnutls_x509_crt_t _tls_load_cert_p12(xmpp_conn_t *conn)
|
static gnutls_x509_crt_t _tls_load_cert_p12(xmpp_conn_t *conn)
|
||||||
{
|
{
|
||||||
gnutls_pkcs12_t p12;
|
gnutls_pkcs12_t p12;
|
||||||
gnutls_x509_crt_t *cert;
|
gnutls_x509_crt_t *cert = NULL;
|
||||||
gnutls_datum_t data;
|
gnutls_datum_t data;
|
||||||
gnutls_x509_privkey_t key;
|
gnutls_x509_privkey_t key;
|
||||||
unsigned int cert_num = 0;
|
unsigned int cert_num = 0, retries = 0;
|
||||||
|
int err = -1;
|
||||||
|
if (!conn->password_callback) {
|
||||||
|
strophe_error(conn->ctx, "tls", "No password callback set");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
if (gnutls_pkcs12_init(&p12) < 0)
|
if (gnutls_pkcs12_init(&p12) < 0)
|
||||||
return NULL;
|
return NULL;
|
||||||
if (gnutls_load_file(conn->tls_client_key, &data) < 0)
|
if (gnutls_load_file(conn->tls_client_key, &data) < 0)
|
||||||
@@ -104,18 +104,30 @@ static gnutls_x509_crt_t _tls_load_cert_p12(xmpp_conn_t *conn)
|
|||||||
if (gnutls_pkcs12_import(p12, &data, GNUTLS_X509_FMT_DER, 0) < 0)
|
if (gnutls_pkcs12_import(p12, &data, GNUTLS_X509_FMT_DER, 0) < 0)
|
||||||
goto error_out2;
|
goto error_out2;
|
||||||
|
|
||||||
char pass[GNUTLS_PKCS11_MAX_PIN_LEN];
|
while (retries++ < conn->password_retries) {
|
||||||
pass[0] = '\0';
|
char pass[GNUTLS_PKCS11_MAX_PIN_LEN];
|
||||||
int passlen =
|
int passlen =
|
||||||
_tls_password_callback(conn, 0, NULL, NULL, 0, pass, sizeof(pass));
|
_tls_password_callback(conn, 0, NULL, NULL, 0, pass, sizeof(pass));
|
||||||
if (passlen < 0)
|
if (passlen < 0)
|
||||||
pass[0] = '\0';
|
continue;
|
||||||
|
|
||||||
int res = gnutls_pkcs12_simple_parse(p12, pass, &key, &cert, &cert_num,
|
err = gnutls_pkcs12_simple_parse(p12, pass, &key, &cert, &cert_num,
|
||||||
NULL, NULL, NULL, 0);
|
NULL, NULL, NULL, 0);
|
||||||
|
memset(pass, 0, sizeof(pass));
|
||||||
|
if (err == 0)
|
||||||
|
break;
|
||||||
|
tls_clear_password_cache(conn);
|
||||||
|
if (err != GNUTLS_E_DECRYPTION_FAILED &&
|
||||||
|
err != GNUTLS_E_MAC_VERIFY_FAILED) {
|
||||||
|
strophe_error(conn->ctx, "tls", "could not read P12 file");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
strophe_debug(conn->ctx, "tls", "wrong password?");
|
||||||
|
}
|
||||||
|
|
||||||
gnutls_pkcs12_deinit(p12);
|
gnutls_pkcs12_deinit(p12);
|
||||||
gnutls_free(data.data);
|
gnutls_free(data.data);
|
||||||
if (res < 0)
|
if (err < 0)
|
||||||
goto error_out;
|
goto error_out;
|
||||||
gnutls_x509_privkey_deinit(key);
|
gnutls_x509_privkey_deinit(key);
|
||||||
if (cert_num > 1) {
|
if (cert_num > 1) {
|
||||||
@@ -129,6 +141,7 @@ static gnutls_x509_crt_t _tls_load_cert_p12(xmpp_conn_t *conn)
|
|||||||
error_out2:
|
error_out2:
|
||||||
gnutls_free(data.data);
|
gnutls_free(data.data);
|
||||||
error_out:
|
error_out:
|
||||||
|
tls_clear_password_cache(conn);
|
||||||
if (cert) {
|
if (cert) {
|
||||||
for (unsigned int n = 0; n < cert_num; ++n) {
|
for (unsigned int n = 0; n < cert_num; ++n) {
|
||||||
gnutls_x509_crt_deinit(cert[n]);
|
gnutls_x509_crt_deinit(cert[n]);
|
||||||
@@ -407,31 +420,55 @@ tls_t *tls_new(xmpp_conn_t *conn)
|
|||||||
gnutls_certificate_allocate_credentials(&tls->cred);
|
gnutls_certificate_allocate_credentials(&tls->cred);
|
||||||
tls_set_credentials(tls, NULL);
|
tls_set_credentials(tls, NULL);
|
||||||
|
|
||||||
gnutls_certificate_set_pin_function(tls->cred, _tls_password_callback,
|
if (conn->password_callback)
|
||||||
conn);
|
gnutls_certificate_set_pin_function(tls->cred,
|
||||||
|
_tls_password_callback, conn);
|
||||||
|
|
||||||
if (conn->tls_client_cert && conn->tls_client_key) {
|
if (conn->tls_client_cert && conn->tls_client_key) {
|
||||||
|
unsigned int retries = 0;
|
||||||
tls->client_cert = _tls_load_cert(conn);
|
tls->client_cert = _tls_load_cert(conn);
|
||||||
if (!tls->client_cert) {
|
if (!tls->client_cert) {
|
||||||
strophe_error(tls->ctx, "tls",
|
strophe_error(tls->ctx, "tls",
|
||||||
"could not read client certificate");
|
"could not read client certificate");
|
||||||
gnutls_certificate_free_credentials(tls->cred);
|
goto error_out;
|
||||||
gnutls_deinit(tls->session);
|
}
|
||||||
strophe_free(tls->ctx, tls);
|
while (retries++ < conn->password_retries) {
|
||||||
return NULL;
|
int err = gnutls_certificate_set_x509_key_file(
|
||||||
|
tls->cred, conn->tls_client_cert, conn->tls_client_key,
|
||||||
|
GNUTLS_X509_FMT_PEM);
|
||||||
|
if (err == 0)
|
||||||
|
break;
|
||||||
|
tls_clear_password_cache(conn);
|
||||||
|
if (err != GNUTLS_E_DECRYPTION_FAILED) {
|
||||||
|
strophe_error(tls->ctx, "tls",
|
||||||
|
"could not read private key");
|
||||||
|
goto error_out;
|
||||||
|
}
|
||||||
|
strophe_debug(tls->ctx, "tls", "wrong password?");
|
||||||
}
|
}
|
||||||
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) {
|
} else if (conn->tls_client_key) {
|
||||||
char pass[GNUTLS_PKCS11_MAX_PIN_LEN];
|
unsigned int retries = 0;
|
||||||
pass[0] = '\0';
|
|
||||||
int passlen = _tls_password_callback(conn, 0, NULL, NULL, 0, pass,
|
while (retries++ < conn->password_retries) {
|
||||||
sizeof(pass));
|
char pass[GNUTLS_PKCS11_MAX_PIN_LEN];
|
||||||
if (passlen < 0)
|
|
||||||
pass[0] = '\0';
|
pass[0] = '\0';
|
||||||
gnutls_certificate_set_x509_simple_pkcs12_file(
|
int passlen = _tls_password_callback(conn, 0, NULL, NULL, 0,
|
||||||
tls->cred, conn->tls_client_key, GNUTLS_X509_FMT_DER, pass);
|
pass, sizeof(pass));
|
||||||
|
if (passlen < 0)
|
||||||
|
continue;
|
||||||
|
int err = gnutls_certificate_set_x509_simple_pkcs12_file(
|
||||||
|
tls->cred, conn->tls_client_key, GNUTLS_X509_FMT_DER, pass);
|
||||||
|
memset(pass, 0, sizeof(pass));
|
||||||
|
if (err == 0)
|
||||||
|
break;
|
||||||
|
tls_clear_password_cache(conn);
|
||||||
|
if (err != GNUTLS_E_DECRYPTION_FAILED &&
|
||||||
|
err != GNUTLS_E_MAC_VERIFY_FAILED) {
|
||||||
|
strophe_error(tls->ctx, "tls", "could not read P12 file");
|
||||||
|
goto error_out;
|
||||||
|
}
|
||||||
|
strophe_debug(tls->ctx, "tls", "wrong password?");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
gnutls_certificate_set_verify_function(tls->cred, _tls_verify);
|
gnutls_certificate_set_verify_function(tls->cred, _tls_verify);
|
||||||
@@ -444,6 +481,13 @@ tls_t *tls_new(xmpp_conn_t *conn)
|
|||||||
}
|
}
|
||||||
|
|
||||||
return tls;
|
return tls;
|
||||||
|
error_out:
|
||||||
|
if (tls->client_cert)
|
||||||
|
gnutls_x509_crt_deinit(tls->client_cert);
|
||||||
|
gnutls_certificate_free_credentials(tls->cred);
|
||||||
|
gnutls_deinit(tls->session);
|
||||||
|
strophe_free(tls->ctx, tls);
|
||||||
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
void tls_free(tls_t *tls)
|
void tls_free(tls_t *tls)
|
||||||
|
|||||||
@@ -541,14 +541,8 @@ static int _tls_verify(int preverify_ok, X509_STORE_CTX *x509_ctx)
|
|||||||
|
|
||||||
static int _tls_password_callback(char *buf, int size, int rwflag, void *u)
|
static int _tls_password_callback(char *buf, int size, int rwflag, void *u)
|
||||||
{
|
{
|
||||||
xmpp_conn_t *conn = u;
|
|
||||||
UNUSED(rwflag);
|
UNUSED(rwflag);
|
||||||
if (!conn->password_callback) {
|
return tls_caching_password_callback(buf, size, u);
|
||||||
strophe_error(conn->ctx, "tls", "No password callback set");
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
return conn->password_callback(buf, (size_t)size, conn->tls_client_key,
|
|
||||||
conn->password_callback_userdata);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
tls_t *tls_new(xmpp_conn_t *conn)
|
tls_t *tls_new(xmpp_conn_t *conn)
|
||||||
@@ -580,10 +574,14 @@ tls_t *tls_new(xmpp_conn_t *conn)
|
|||||||
SSL_CTX_set_options(tls->ssl_ctx, SSL_OP_NO_SSLv2); /* DROWN */
|
SSL_CTX_set_options(tls->ssl_ctx, SSL_OP_NO_SSLv2); /* DROWN */
|
||||||
SSL_CTX_set_options(tls->ssl_ctx, SSL_OP_NO_SSLv3); /* POODLE */
|
SSL_CTX_set_options(tls->ssl_ctx, SSL_OP_NO_SSLv3); /* POODLE */
|
||||||
SSL_CTX_set_options(tls->ssl_ctx, SSL_OP_NO_TLSv1); /* BEAST */
|
SSL_CTX_set_options(tls->ssl_ctx, SSL_OP_NO_TLSv1); /* BEAST */
|
||||||
SSL_CTX_set_default_passwd_cb(tls->ssl_ctx, _tls_password_callback);
|
|
||||||
SSL_CTX_set_default_passwd_cb_userdata(tls->ssl_ctx, conn);
|
if (conn->password_callback) {
|
||||||
|
SSL_CTX_set_default_passwd_cb(tls->ssl_ctx, _tls_password_callback);
|
||||||
|
SSL_CTX_set_default_passwd_cb_userdata(tls->ssl_ctx, conn);
|
||||||
|
}
|
||||||
|
|
||||||
if (conn->tls_client_cert && conn->tls_client_key) {
|
if (conn->tls_client_cert && conn->tls_client_key) {
|
||||||
|
unsigned int retries = 0;
|
||||||
tls->client_cert = _tls_cert_read(conn);
|
tls->client_cert = _tls_cert_read(conn);
|
||||||
if (!tls->client_cert) {
|
if (!tls->client_cert) {
|
||||||
strophe_error(tls->ctx, "tls",
|
strophe_error(tls->ctx, "tls",
|
||||||
@@ -593,8 +591,25 @@ tls_t *tls_new(xmpp_conn_t *conn)
|
|||||||
|
|
||||||
SSL_CTX_use_certificate_file(tls->ssl_ctx, conn->tls_client_cert,
|
SSL_CTX_use_certificate_file(tls->ssl_ctx, conn->tls_client_cert,
|
||||||
SSL_FILETYPE_PEM);
|
SSL_FILETYPE_PEM);
|
||||||
SSL_CTX_use_PrivateKey_file(tls->ssl_ctx, conn->tls_client_key,
|
while (retries++ < conn->password_retries) {
|
||||||
SSL_FILETYPE_PEM);
|
if (SSL_CTX_use_PrivateKey_file(
|
||||||
|
tls->ssl_ctx, conn->tls_client_key, SSL_FILETYPE_PEM)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
tls_clear_password_cache(conn);
|
||||||
|
unsigned long err = ERR_peek_error();
|
||||||
|
if ((ERR_GET_LIB(err) == ERR_LIB_EVP &&
|
||||||
|
ERR_GET_REASON(err) == EVP_R_BAD_DECRYPT) ||
|
||||||
|
(ERR_GET_LIB(err) == ERR_LIB_PEM &&
|
||||||
|
ERR_GET_REASON(err) == PEM_R_BAD_DECRYPT)) {
|
||||||
|
strophe_debug(tls->ctx, "tls", "wrong password?");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
strophe_error(tls->ctx, "tls",
|
||||||
|
"could not use private key %d %d",
|
||||||
|
ERR_GET_LIB(err), ERR_GET_REASON(err));
|
||||||
|
goto err_free_ctx;
|
||||||
|
}
|
||||||
} else if (conn->tls_client_key) {
|
} else if (conn->tls_client_key) {
|
||||||
EVP_PKEY *pkey = NULL;
|
EVP_PKEY *pkey = NULL;
|
||||||
STACK_OF(X509) *ca = NULL;
|
STACK_OF(X509) *ca = NULL;
|
||||||
@@ -876,7 +891,7 @@ static void _tls_log_error(xmpp_ctx_t *ctx)
|
|||||||
do {
|
do {
|
||||||
e = ERR_get_error();
|
e = ERR_get_error();
|
||||||
if (e != 0) {
|
if (e != 0) {
|
||||||
strophe_debug(ctx, "tls", "error:%08uX:%s:%s:%s", e,
|
strophe_debug(ctx, "tls", "error:%08X:%s:%s:%s", e,
|
||||||
ERR_lib_error_string(e), ERR_func_error_string(e),
|
ERR_lib_error_string(e), ERR_func_error_string(e),
|
||||||
ERR_reason_error_string(e));
|
ERR_reason_error_string(e));
|
||||||
}
|
}
|
||||||
@@ -928,43 +943,66 @@ _tls_cert_read_p12(xmpp_conn_t *conn, EVP_PKEY **pkey, STACK_OF(X509) * *ca)
|
|||||||
{
|
{
|
||||||
if (conn->tls && conn->tls->client_cert && !pkey && !ca)
|
if (conn->tls && conn->tls->client_cert && !pkey && !ca)
|
||||||
return conn->tls->client_cert;
|
return conn->tls->client_cert;
|
||||||
|
X509 *cert = NULL;
|
||||||
|
PKCS12 *p12 = NULL;
|
||||||
BIO *f = BIO_new_file(conn->tls_client_key, "r");
|
BIO *f = BIO_new_file(conn->tls_client_key, "r");
|
||||||
if (!f) {
|
if (!f) {
|
||||||
strophe_debug(conn->ctx, "tls", "f == NULL");
|
strophe_debug(conn->ctx, "tls", "f == NULL");
|
||||||
goto error_out;
|
goto error_out;
|
||||||
}
|
}
|
||||||
PKCS12 *p12 = d2i_PKCS12_bio(f, NULL);
|
p12 = d2i_PKCS12_bio(f, NULL);
|
||||||
BIO_free(f);
|
BIO_free(f);
|
||||||
if (!p12) {
|
if (!p12) {
|
||||||
strophe_debug(conn->ctx, "tls", "Could not read p12 file");
|
strophe_debug(conn->ctx, "tls", "Could not read p12 file");
|
||||||
goto error_out;
|
goto error_out;
|
||||||
}
|
}
|
||||||
|
|
||||||
char pass[PEM_BUFSIZE];
|
unsigned int retries = 0;
|
||||||
int passlen = _tls_password_callback(pass, sizeof(pass), 0, conn);
|
|
||||||
if (passlen <= 0)
|
|
||||||
pass[0] = '\0';
|
|
||||||
|
|
||||||
/* For some reason `PKCS12_parse()` fails without a `EVP_PKEY`
|
pem_password_cb *cb = PEM_def_callback;
|
||||||
* so if the user doesn't want it, use a local one and free it
|
void *userdata = NULL;
|
||||||
* again directly after parsing.
|
if (conn->password_callback) {
|
||||||
*/
|
cb = _tls_password_callback;
|
||||||
EVP_PKEY *pkey_;
|
userdata = conn;
|
||||||
if (!pkey)
|
}
|
||||||
pkey = &pkey_;
|
|
||||||
X509 *cert = NULL;
|
while (retries++ < conn->password_retries) {
|
||||||
int parse_ok = PKCS12_parse(p12, pass, pkey, &cert, ca);
|
char pass[PEM_BUFSIZE + 1];
|
||||||
if (pkey == &pkey_ && pkey_)
|
int passlen = cb(pass, PEM_BUFSIZE, 0, userdata);
|
||||||
EVP_PKEY_free(pkey_);
|
if (passlen < 0 || passlen > PEM_BUFSIZE)
|
||||||
PKCS12_free(p12);
|
goto error_out;
|
||||||
if (!parse_ok) {
|
|
||||||
|
/* For some reason `PKCS12_parse()` fails without a `EVP_PKEY`
|
||||||
|
* so if the user doesn't want it, use a local one and free it
|
||||||
|
* again directly after parsing.
|
||||||
|
*/
|
||||||
|
EVP_PKEY *pkey_;
|
||||||
|
if (!pkey)
|
||||||
|
pkey = &pkey_;
|
||||||
|
int parse_ok = PKCS12_parse(p12, pass, pkey, &cert, ca);
|
||||||
|
if (pkey == &pkey_ && pkey_)
|
||||||
|
EVP_PKEY_free(pkey_);
|
||||||
|
if (parse_ok) {
|
||||||
|
goto success;
|
||||||
|
}
|
||||||
|
cert = NULL;
|
||||||
|
tls_clear_password_cache(conn);
|
||||||
|
int err = ERR_peek_last_error();
|
||||||
|
if (ERR_GET_LIB(err) == ERR_LIB_PKCS12 &&
|
||||||
|
ERR_GET_REASON(err) == PKCS12_R_MAC_VERIFY_FAILURE) {
|
||||||
|
strophe_debug(conn->ctx, "tls",
|
||||||
|
"Entered password is most likely wrong!");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
strophe_debug(conn->ctx, "tls", "Could not parse PKCS#12");
|
strophe_debug(conn->ctx, "tls", "Could not parse PKCS#12");
|
||||||
goto error_out;
|
goto error_out;
|
||||||
}
|
}
|
||||||
return cert;
|
|
||||||
error_out:
|
error_out:
|
||||||
_tls_log_error(conn->ctx);
|
_tls_log_error(conn->ctx);
|
||||||
return NULL;
|
success:
|
||||||
|
if (p12)
|
||||||
|
PKCS12_free(p12);
|
||||||
|
return cert;
|
||||||
}
|
}
|
||||||
|
|
||||||
static X509 *_tls_cert_read(xmpp_conn_t *conn)
|
static X509 *_tls_cert_read(xmpp_conn_t *conn)
|
||||||
|
|||||||
19
strophe.h
19
strophe.h
@@ -283,23 +283,30 @@ typedef int (*xmpp_certfail_handler)(const xmpp_tlscert_t *cert,
|
|||||||
* include/openssl/pem.h: #define PEM_BUFSIZE 1024
|
* include/openssl/pem.h: #define PEM_BUFSIZE 1024
|
||||||
* ```
|
* ```
|
||||||
*
|
*
|
||||||
* The usable length for GnuTLS is 255 as it expects a NULL-terminated string.
|
* We expect the buffer to be NULL-terminated, therefore the usable lengths
|
||||||
|
* are:
|
||||||
*
|
*
|
||||||
* The usable length for OpenSSL is 1024.
|
* * 255 for GnuTLS
|
||||||
|
* * 1023 for OpenSSL
|
||||||
|
*
|
||||||
|
* Useful API's inside this callback are e.g.
|
||||||
|
*
|
||||||
|
* \ref xmpp_conn_get_keyfile
|
||||||
*
|
*
|
||||||
*
|
*
|
||||||
* @param pw The buffer where the password shall be stored.
|
* @param pw The buffer where the password shall be stored.
|
||||||
* @param pw_max The maximum length of the password.
|
* @param pw_max The maximum length of the password.
|
||||||
* @param fname The name of the file that is going to be unlocked.
|
* @param conn The Strophe connection object this callback originates from.
|
||||||
* @param userdata The userdata pointer as supplied when setting this callback.
|
* @param userdata The userdata pointer as supplied when setting this callback.
|
||||||
*
|
*
|
||||||
* @return -1 on error, else the number of bytes written to `pw`
|
* @return -1 on error, else the number of bytes written to `pw` w/o
|
||||||
|
* terminating NUL byte
|
||||||
*
|
*
|
||||||
* @ingroup TLS
|
* @ingroup TLS
|
||||||
*/
|
*/
|
||||||
typedef int (*xmpp_password_callback)(char *pw,
|
typedef int (*xmpp_password_callback)(char *pw,
|
||||||
size_t pw_max,
|
size_t pw_max,
|
||||||
const char *fname,
|
xmpp_conn_t *conn,
|
||||||
void *userdata);
|
void *userdata);
|
||||||
|
|
||||||
/** The function which will be called when Strophe creates a new socket.
|
/** The function which will be called when Strophe creates a new socket.
|
||||||
@@ -355,6 +362,8 @@ xmpp_tlscert_t *xmpp_conn_get_peer_cert(xmpp_conn_t *const conn);
|
|||||||
void xmpp_conn_set_password_callback(xmpp_conn_t *conn,
|
void xmpp_conn_set_password_callback(xmpp_conn_t *conn,
|
||||||
xmpp_password_callback cb,
|
xmpp_password_callback cb,
|
||||||
void *userdata);
|
void *userdata);
|
||||||
|
void xmpp_conn_set_password_retries(xmpp_conn_t *conn, unsigned int retries);
|
||||||
|
const char *xmpp_conn_get_keyfile(const xmpp_conn_t *conn);
|
||||||
void xmpp_conn_set_client_cert(xmpp_conn_t *conn,
|
void xmpp_conn_set_client_cert(xmpp_conn_t *conn,
|
||||||
const char *cert,
|
const char *cert,
|
||||||
const char *key);
|
const char *key);
|
||||||
|
|||||||
@@ -19,11 +19,11 @@
|
|||||||
#include "test.h"
|
#include "test.h"
|
||||||
|
|
||||||
static int
|
static int
|
||||||
password_callback(char *pw, size_t pw_max, const char *fname, void *userdata)
|
password_callback(char *pw, size_t pw_max, xmpp_conn_t *conn, void *userdata)
|
||||||
{
|
{
|
||||||
(void)pw_max;
|
(void)pw_max;
|
||||||
(void)userdata;
|
(void)userdata;
|
||||||
(void)fname;
|
(void)conn;
|
||||||
memcpy(pw, "abc123", 7);
|
memcpy(pw, "abc123", 7);
|
||||||
return 6;
|
return 6;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user