add support for a password callback
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>
This commit is contained in:
@@ -114,7 +114,9 @@ EXTRA_DIST = \
|
|||||||
jni/Application.mk \
|
jni/Application.mk \
|
||||||
src/tls_schannel.c \
|
src/tls_schannel.c \
|
||||||
tests/cert.pem \
|
tests/cert.pem \
|
||||||
|
tests/cert.pfx \
|
||||||
tests/key.pem \
|
tests/key.pem \
|
||||||
|
tests/key_encrypted.pem \
|
||||||
tests/res_query_dump.c
|
tests/res_query_dump.c
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|||||||
@@ -12,6 +12,7 @@
|
|||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
#include <winsock2.h>
|
#include <winsock2.h>
|
||||||
@@ -93,6 +94,52 @@ static int certfail_handler(const xmpp_tlscert_t *cert,
|
|||||||
return read_char[0] == 'y' || read_char[0] == 'Y';
|
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 int sockopt_cb(xmpp_conn_t *conn, void *socket)
|
static int sockopt_cb(xmpp_conn_t *conn, void *socket)
|
||||||
{
|
{
|
||||||
int timeout = KA_TIMEOUT;
|
int timeout = KA_TIMEOUT;
|
||||||
@@ -181,7 +228,7 @@ static void usage(int exit_code)
|
|||||||
" --jid <jid> The JID to use to authenticate.\n"
|
" --jid <jid> The JID to use to authenticate.\n"
|
||||||
" --pass <pass> The password of the JID.\n"
|
" --pass <pass> The password of the JID.\n"
|
||||||
" --tls-cert <cert> Path to client certificate.\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 "
|
" --capath <path> Path to an additional CA trust store "
|
||||||
"(directory).\n"
|
"(directory).\n"
|
||||||
" --cafile <path> Path to an additional CA trust store "
|
" --cafile <path> Path to an additional CA trust store "
|
||||||
@@ -247,7 +294,7 @@ int main(int argc, char **argv)
|
|||||||
else
|
else
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if ((!jid && (!cert || !key)) || (argc - i) > 2) {
|
if ((!jid && !key) || (argc - i) > 2) {
|
||||||
usage(1);
|
usage(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -279,8 +326,10 @@ int main(int argc, char **argv)
|
|||||||
if (tcp_keepalive)
|
if (tcp_keepalive)
|
||||||
xmpp_conn_set_sockopt_callback(conn, sockopt_cb);
|
xmpp_conn_set_sockopt_callback(conn, sockopt_cb);
|
||||||
|
|
||||||
|
/* ask for a password if key is protected */
|
||||||
|
xmpp_conn_set_password_callback(conn, password_callback, conn);
|
||||||
/* setup authentication information */
|
/* setup authentication information */
|
||||||
if (cert && key) {
|
if (key) {
|
||||||
xmpp_conn_set_client_cert(conn, cert, key);
|
xmpp_conn_set_client_cert(conn, cert, key);
|
||||||
}
|
}
|
||||||
if (jid)
|
if (jid)
|
||||||
|
|||||||
@@ -242,7 +242,7 @@ _handle_features(xmpp_conn_t *conn, xmpp_stanza_t *stanza, void *userdata)
|
|||||||
if (strcasecmp(text, "PLAIN") == 0)
|
if (strcasecmp(text, "PLAIN") == 0)
|
||||||
conn->sasl_support |= SASL_MASK_PLAIN;
|
conn->sasl_support |= SASL_MASK_PLAIN;
|
||||||
else if (strcasecmp(text, "EXTERNAL") == 0 &&
|
else if (strcasecmp(text, "EXTERNAL") == 0 &&
|
||||||
conn->tls_client_cert)
|
(conn->tls_client_cert || conn->tls_client_key))
|
||||||
conn->sasl_support |= SASL_MASK_EXTERNAL;
|
conn->sasl_support |= SASL_MASK_EXTERNAL;
|
||||||
else if (strcasecmp(text, "DIGEST-MD5") == 0)
|
else if (strcasecmp(text, "DIGEST-MD5") == 0)
|
||||||
conn->sasl_support |= SASL_MASK_DIGESTMD5;
|
conn->sasl_support |= SASL_MASK_DIGESTMD5;
|
||||||
|
|||||||
@@ -192,6 +192,8 @@ struct _xmpp_conn_t {
|
|||||||
int auth_legacy_enabled;
|
int auth_legacy_enabled;
|
||||||
int secured; /* set when stream is secured with TLS */
|
int secured; /* set when stream is secured with TLS */
|
||||||
xmpp_certfail_handler certfail_handler;
|
xmpp_certfail_handler certfail_handler;
|
||||||
|
xmpp_password_callback password_callback;
|
||||||
|
void *password_callback_userdata;
|
||||||
|
|
||||||
/* 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;
|
||||||
|
|||||||
44
src/conn.c
44
src/conn.c
@@ -182,6 +182,8 @@ xmpp_conn_t *xmpp_conn_new(xmpp_ctx_t *ctx)
|
|||||||
conn->auth_legacy_enabled = 0;
|
conn->auth_legacy_enabled = 0;
|
||||||
conn->secured = 0;
|
conn->secured = 0;
|
||||||
conn->certfail_handler = NULL;
|
conn->certfail_handler = NULL;
|
||||||
|
conn->password_callback = NULL;
|
||||||
|
conn->password_callback_userdata = NULL;
|
||||||
|
|
||||||
conn->bind_required = 0;
|
conn->bind_required = 0;
|
||||||
conn->session_required = 0;
|
conn->session_required = 0;
|
||||||
@@ -492,15 +494,38 @@ xmpp_tlscert_t *xmpp_conn_get_peer_cert(xmpp_conn_t *const conn)
|
|||||||
return tls_peer_cert(conn);
|
return tls_peer_cert(conn);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Set the Client Certificate and Private Key that will be bound to the
|
/** Set the Callback function which will be called when the TLS stack can't
|
||||||
* connection. If any of the both was previously set, it will be discarded.
|
* decrypt a password protected key file.
|
||||||
* This should not be used after a connection is created. The function will
|
*
|
||||||
* make a copy of the strings passed in.
|
* @param conn a Strophe connection object
|
||||||
* Currently only non-encrypted Private Keys are supported.
|
* @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 conn a Strophe connection object
|
||||||
* @param cert path to a certificate file
|
* @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
|
* @ingroup TLS
|
||||||
*/
|
*/
|
||||||
@@ -511,7 +536,10 @@ void xmpp_conn_set_client_cert(xmpp_conn_t *const conn,
|
|||||||
strophe_debug(conn->ctx, "conn", "set client cert %s %s", cert, key);
|
strophe_debug(conn->ctx, "conn", "set client cert %s %s", cert, key);
|
||||||
if (conn->tls_client_cert)
|
if (conn->tls_client_cert)
|
||||||
strophe_free(conn->ctx, conn->tls_client_cert);
|
strophe_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)
|
if (conn->tls_client_key)
|
||||||
strophe_free(conn->ctx, conn->tls_client_key);
|
strophe_free(conn->ctx, conn->tls_client_key);
|
||||||
conn->tls_client_key = strophe_strdup(conn->ctx, key);
|
conn->tls_client_key = strophe_strdup(conn->ctx, key);
|
||||||
@@ -620,7 +648,7 @@ int xmpp_connect_client(xmpp_conn_t *conn,
|
|||||||
int found = XMPP_DOMAIN_NOT_FOUND;
|
int found = XMPP_DOMAIN_NOT_FOUND;
|
||||||
int rc;
|
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) {
|
if (tls_id_on_xmppaddr_num(conn) != 1) {
|
||||||
strophe_debug(conn->ctx, "xmpp",
|
strophe_debug(conn->ctx, "xmpp",
|
||||||
"Client certificate contains multiple or no xmppAddr "
|
"Client certificate contains multiple or no xmppAddr "
|
||||||
|
|||||||
103
src/tls_gnutls.c
103
src/tls_gnutls.c
@@ -17,6 +17,8 @@
|
|||||||
#include <gnutls/gnutls.h>
|
#include <gnutls/gnutls.h>
|
||||||
#include <gnutls/x509.h>
|
#include <gnutls/x509.h>
|
||||||
#include <gnutls/x509-ext.h>
|
#include <gnutls/x509-ext.h>
|
||||||
|
#include <gnutls/pkcs11.h>
|
||||||
|
#include <gnutls/pkcs12.h>
|
||||||
|
|
||||||
#include "common.h"
|
#include "common.h"
|
||||||
#include "tls.h"
|
#include "tls.h"
|
||||||
@@ -47,10 +49,30 @@ void tls_shutdown(void)
|
|||||||
gnutls_global_deinit();
|
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) {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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_x509_crt_t cert;
|
||||||
gnutls_datum_t data;
|
gnutls_datum_t data;
|
||||||
int res;
|
int res;
|
||||||
@@ -68,6 +90,64 @@ error_out:
|
|||||||
return NULL;
|
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) {
|
||||||
|
strophe_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)
|
static void _tls_free_cert(xmpp_conn_t *conn, gnutls_x509_crt_t cert)
|
||||||
{
|
{
|
||||||
if (conn->tls && conn->tls->client_cert == cert)
|
if (conn->tls && conn->tls->client_cert == cert)
|
||||||
@@ -269,12 +349,15 @@ static int _tls_verify(gnutls_session_t session)
|
|||||||
|
|
||||||
/* Return early if the Certificate is trusted
|
/* Return early if the Certificate is trusted
|
||||||
* OR if we trust all Certificates */
|
* 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;
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
if (!tls->conn->certfail_handler) {
|
if (!tls->conn->certfail_handler) {
|
||||||
strophe_error(tls->ctx, "tls",
|
strophe_error(tls->ctx, "tls",
|
||||||
"No certfail handler set, canceling connection attempt");
|
"No certfail handler set, canceling connection attempt");
|
||||||
|
gnutls_free(out.data);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -324,6 +407,9 @@ 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,
|
||||||
|
conn);
|
||||||
|
|
||||||
if (conn->tls_client_cert && conn->tls_client_key) {
|
if (conn->tls_client_cert && conn->tls_client_key) {
|
||||||
tls->client_cert = _tls_load_cert(conn);
|
tls->client_cert = _tls_load_cert(conn);
|
||||||
if (!tls->client_cert) {
|
if (!tls->client_cert) {
|
||||||
@@ -337,6 +423,15 @@ tls_t *tls_new(xmpp_conn_t *conn)
|
|||||||
gnutls_certificate_set_x509_key_file(
|
gnutls_certificate_set_x509_key_file(
|
||||||
tls->cred, conn->tls_client_cert, conn->tls_client_key,
|
tls->cred, conn->tls_client_cert, conn->tls_client_key,
|
||||||
GNUTLS_X509_FMT_PEM);
|
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);
|
gnutls_certificate_set_verify_function(tls->cred, _tls_verify);
|
||||||
|
|||||||
@@ -26,6 +26,7 @@
|
|||||||
#include <openssl/err.h>
|
#include <openssl/err.h>
|
||||||
#include <openssl/opensslv.h>
|
#include <openssl/opensslv.h>
|
||||||
#include <openssl/x509v3.h>
|
#include <openssl/x509v3.h>
|
||||||
|
#include <openssl/pkcs12.h>
|
||||||
|
|
||||||
#include "common.h"
|
#include "common.h"
|
||||||
#include "tls.h"
|
#include "tls.h"
|
||||||
@@ -54,6 +55,30 @@ static const unsigned char *ASN1_STRING_get0_data(ASN1_STRING *asn1)
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#if OPENSSL_VERSION_NUMBER < 0x10100000L || defined LIBRESSL_VERSION_NUMBER
|
||||||
|
static int SSL_CTX_use_cert_and_key(SSL_CTX *ctx,
|
||||||
|
X509 *x509,
|
||||||
|
EVP_PKEY *privatekey,
|
||||||
|
STACK_OF(X509) * chain,
|
||||||
|
int override)
|
||||||
|
{
|
||||||
|
UNUSED(override);
|
||||||
|
if (!ctx)
|
||||||
|
return 0;
|
||||||
|
if (x509 && !SSL_CTX_use_certificate(ctx, x509))
|
||||||
|
return 0;
|
||||||
|
if (privatekey && !SSL_CTX_use_PrivateKey(ctx, privatekey))
|
||||||
|
return 0;
|
||||||
|
#ifdef SSL_CTX_set1_chain
|
||||||
|
if (chain && !SSL_CTX_set1_chain(ctx, chain))
|
||||||
|
return 0;
|
||||||
|
#else
|
||||||
|
UNUSED(chain);
|
||||||
|
#endif
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
#if OPENSSL_VERSION_NUMBER < 0x10000000L
|
#if OPENSSL_VERSION_NUMBER < 0x10000000L
|
||||||
static int GENERAL_NAME_get0_otherName(const GENERAL_NAME *gen,
|
static int GENERAL_NAME_get0_otherName(const GENERAL_NAME *gen,
|
||||||
ASN1_OBJECT **poid,
|
ASN1_OBJECT **poid,
|
||||||
@@ -90,6 +115,8 @@ static void _tls_set_error(tls_t *tls, int error);
|
|||||||
static void _tls_log_error(xmpp_ctx_t *ctx);
|
static void _tls_log_error(xmpp_ctx_t *ctx);
|
||||||
static void _tls_dump_cert_info(tls_t *tls);
|
static void _tls_dump_cert_info(tls_t *tls);
|
||||||
static X509 *_tls_cert_read(xmpp_conn_t *conn);
|
static X509 *_tls_cert_read(xmpp_conn_t *conn);
|
||||||
|
static X509 *
|
||||||
|
_tls_cert_read_p12(xmpp_conn_t *conn, EVP_PKEY **pkey, STACK_OF(X509) * *ca);
|
||||||
static int _tls_xaddr_nid(void);
|
static int _tls_xaddr_nid(void);
|
||||||
static int _tls_xmppaddr_to_string(GENERAL_NAME *name, char **res);
|
static int _tls_xmppaddr_to_string(GENERAL_NAME *name, char **res);
|
||||||
static int _tls_dnsname_to_string(GENERAL_NAME *name, char **res);
|
static int _tls_dnsname_to_string(GENERAL_NAME *name, char **res);
|
||||||
@@ -260,8 +287,10 @@ char *tls_id_on_xmppaddr(xmpp_conn_t *conn, unsigned int n)
|
|||||||
char *ret = NULL;
|
char *ret = NULL;
|
||||||
int i, j;
|
int i, j;
|
||||||
GENERAL_NAMES *names = _tls_conn_get_names(conn);
|
GENERAL_NAMES *names = _tls_conn_get_names(conn);
|
||||||
if (!names)
|
if (!names) {
|
||||||
|
_tls_log_error(conn->ctx);
|
||||||
return NULL;
|
return NULL;
|
||||||
|
}
|
||||||
int num_names = sk_GENERAL_NAME_num(names);
|
int num_names = sk_GENERAL_NAME_num(names);
|
||||||
for (i = j = 0; i < num_names; ++i) {
|
for (i = j = 0; i < num_names; ++i) {
|
||||||
char *res;
|
char *res;
|
||||||
@@ -288,8 +317,10 @@ unsigned int tls_id_on_xmppaddr_num(xmpp_conn_t *conn)
|
|||||||
{
|
{
|
||||||
unsigned int ret = 0;
|
unsigned int ret = 0;
|
||||||
GENERAL_NAMES *names = _tls_conn_get_names(conn);
|
GENERAL_NAMES *names = _tls_conn_get_names(conn);
|
||||||
if (!names)
|
if (!names) {
|
||||||
|
_tls_log_error(conn->ctx);
|
||||||
return 0;
|
return 0;
|
||||||
|
}
|
||||||
int j, num_names = sk_GENERAL_NAME_num(names);
|
int j, num_names = sk_GENERAL_NAME_num(names);
|
||||||
for (j = 0; j < num_names; ++j) {
|
for (j = 0; j < num_names; ++j) {
|
||||||
GENERAL_NAME *name = sk_GENERAL_NAME_value(names, j);
|
GENERAL_NAME *name = sk_GENERAL_NAME_value(names, j);
|
||||||
@@ -355,18 +386,16 @@ _get_fingerprint(const xmpp_ctx_t *ctx, X509 *err_cert, xmpp_cert_element_t el)
|
|||||||
static char *
|
static char *
|
||||||
_get_alg(const xmpp_ctx_t *ctx, X509 *err_cert, xmpp_cert_element_t el)
|
_get_alg(const xmpp_ctx_t *ctx, X509 *err_cert, xmpp_cert_element_t el)
|
||||||
{
|
{
|
||||||
int rc, alg_nid = NID_undef;
|
int alg_nid = NID_undef;
|
||||||
|
|
||||||
switch (el) {
|
switch (el) {
|
||||||
case XMPP_CERT_KEYALG: {
|
case XMPP_CERT_KEYALG: {
|
||||||
#if OPENSSL_VERSION_NUMBER < 0x10100000L
|
#if OPENSSL_VERSION_NUMBER < 0x10100000L
|
||||||
UNUSED(rc);
|
|
||||||
alg_nid = OBJ_obj2nid(err_cert->cert_info->key->algor->algorithm);
|
alg_nid = OBJ_obj2nid(err_cert->cert_info->key->algor->algorithm);
|
||||||
#else
|
#else
|
||||||
X509_PUBKEY *pubkey = X509_get_X509_PUBKEY(err_cert);
|
X509_PUBKEY *pubkey = X509_get_X509_PUBKEY(err_cert);
|
||||||
ASN1_OBJECT *ppkalg = NULL;
|
ASN1_OBJECT *ppkalg = NULL;
|
||||||
rc = X509_PUBKEY_get0_param(&ppkalg, NULL, NULL, NULL, pubkey);
|
if (X509_PUBKEY_get0_param(&ppkalg, NULL, NULL, NULL, pubkey)) {
|
||||||
if (rc) {
|
|
||||||
alg_nid = OBJ_obj2nid(ppkalg);
|
alg_nid = OBJ_obj2nid(ppkalg);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
@@ -510,6 +539,18 @@ static int _tls_verify(int preverify_ok, X509_STORE_CTX *x509_ctx)
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int _tls_password_callback(char *buf, int size, int rwflag, void *u)
|
||||||
|
{
|
||||||
|
xmpp_conn_t *conn = u;
|
||||||
|
UNUSED(rwflag);
|
||||||
|
if (!conn->password_callback) {
|
||||||
|
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)
|
||||||
{
|
{
|
||||||
tls_t *tls = strophe_alloc(conn->ctx, sizeof(*tls));
|
tls_t *tls = strophe_alloc(conn->ctx, sizeof(*tls));
|
||||||
@@ -539,6 +580,8 @@ 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->tls_client_cert && conn->tls_client_key) {
|
if (conn->tls_client_cert && conn->tls_client_key) {
|
||||||
tls->client_cert = _tls_cert_read(conn);
|
tls->client_cert = _tls_cert_read(conn);
|
||||||
@@ -552,6 +595,21 @@ tls_t *tls_new(xmpp_conn_t *conn)
|
|||||||
SSL_FILETYPE_PEM);
|
SSL_FILETYPE_PEM);
|
||||||
SSL_CTX_use_PrivateKey_file(tls->ssl_ctx, conn->tls_client_key,
|
SSL_CTX_use_PrivateKey_file(tls->ssl_ctx, conn->tls_client_key,
|
||||||
SSL_FILETYPE_PEM);
|
SSL_FILETYPE_PEM);
|
||||||
|
} else if (conn->tls_client_key) {
|
||||||
|
EVP_PKEY *pkey = NULL;
|
||||||
|
STACK_OF(X509) *ca = NULL;
|
||||||
|
X509 *cert = _tls_cert_read_p12(conn, &pkey, &ca);
|
||||||
|
if (!cert) {
|
||||||
|
goto err_free_ctx;
|
||||||
|
}
|
||||||
|
|
||||||
|
SSL_CTX_use_cert_and_key(tls->ssl_ctx, cert, pkey, ca, 1);
|
||||||
|
|
||||||
|
if (pkey)
|
||||||
|
EVP_PKEY_free(pkey);
|
||||||
|
if (ca)
|
||||||
|
sk_X509_pop_free(ca, X509_free);
|
||||||
|
tls->client_cert = cert;
|
||||||
} else {
|
} else {
|
||||||
/* If the server asks for a client certificate, don't send one. */
|
/* If the server asks for a client certificate, don't send one. */
|
||||||
SSL_CTX_set_client_cert_cb(tls->ssl_ctx, NULL);
|
SSL_CTX_set_client_cert_cb(tls->ssl_ctx, NULL);
|
||||||
@@ -848,7 +906,7 @@ static void _tls_dump_cert_info(tls_t *tls)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static X509 *_tls_cert_read(xmpp_conn_t *conn)
|
static X509 *_tls_cert_read_x509(xmpp_conn_t *conn)
|
||||||
{
|
{
|
||||||
if (conn->tls && conn->tls->client_cert)
|
if (conn->tls && conn->tls->client_cert)
|
||||||
return conn->tls->client_cert;
|
return conn->tls->client_cert;
|
||||||
@@ -865,6 +923,60 @@ static X509 *_tls_cert_read(xmpp_conn_t *conn)
|
|||||||
return c;
|
return c;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static X509 *
|
||||||
|
_tls_cert_read_p12(xmpp_conn_t *conn, EVP_PKEY **pkey, STACK_OF(X509) * *ca)
|
||||||
|
{
|
||||||
|
if (conn->tls && conn->tls->client_cert && !pkey && !ca)
|
||||||
|
return conn->tls->client_cert;
|
||||||
|
BIO *f = BIO_new_file(conn->tls_client_key, "r");
|
||||||
|
if (!f) {
|
||||||
|
strophe_debug(conn->ctx, "tls", "f == NULL");
|
||||||
|
goto error_out;
|
||||||
|
}
|
||||||
|
PKCS12 *p12 = d2i_PKCS12_bio(f, NULL);
|
||||||
|
BIO_free(f);
|
||||||
|
if (!p12) {
|
||||||
|
strophe_debug(conn->ctx, "tls", "Could not read p12 file");
|
||||||
|
goto error_out;
|
||||||
|
}
|
||||||
|
|
||||||
|
char pass[PEM_BUFSIZE];
|
||||||
|
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`
|
||||||
|
* 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_;
|
||||||
|
X509 *cert = NULL;
|
||||||
|
int parse_ok = PKCS12_parse(p12, pass, pkey, &cert, ca);
|
||||||
|
if (pkey == &pkey_ && pkey_)
|
||||||
|
EVP_PKEY_free(pkey_);
|
||||||
|
PKCS12_free(p12);
|
||||||
|
if (!parse_ok) {
|
||||||
|
strophe_debug(conn->ctx, "tls", "Could not parse PKCS#12");
|
||||||
|
goto error_out;
|
||||||
|
}
|
||||||
|
return cert;
|
||||||
|
error_out:
|
||||||
|
_tls_log_error(conn->ctx);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static X509 *_tls_cert_read(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_cert_read_p12(conn, NULL, NULL);
|
||||||
|
}
|
||||||
|
return _tls_cert_read_x509(conn);
|
||||||
|
}
|
||||||
|
|
||||||
static int _tls_xaddr_nid(void)
|
static int _tls_xaddr_nid(void)
|
||||||
{
|
{
|
||||||
static int xaddr_nid = NID_undef;
|
static int xaddr_nid = NID_undef;
|
||||||
|
|||||||
36
strophe.h
36
strophe.h
@@ -269,6 +269,39 @@ typedef void (*xmpp_conn_handler)(xmpp_conn_t *conn,
|
|||||||
typedef int (*xmpp_certfail_handler)(const xmpp_tlscert_t *cert,
|
typedef int (*xmpp_certfail_handler)(const xmpp_tlscert_t *cert,
|
||||||
const char *const errormsg);
|
const char *const errormsg);
|
||||||
|
|
||||||
|
/** The Handler function which will be called when the TLS stack can't
|
||||||
|
* decrypt a password protected key file.
|
||||||
|
*
|
||||||
|
* When this callback is called it shall write a NULL-terminated
|
||||||
|
* string of maximum length `pw_max - 1` to `pw`.
|
||||||
|
*
|
||||||
|
* This is currently only supported for GnuTLS and OpenSSL.
|
||||||
|
*
|
||||||
|
* On 2022-02-02 the following maximum lengths are valid:
|
||||||
|
* ```
|
||||||
|
* include/gnutls/pkcs11.h: #define GNUTLS_PKCS11_MAX_PIN_LEN 256
|
||||||
|
* include/openssl/pem.h: #define PEM_BUFSIZE 1024
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* The usable length for GnuTLS is 255 as it expects a NULL-terminated string.
|
||||||
|
*
|
||||||
|
* The usable length for OpenSSL is 1024.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @param pw The buffer where the password shall be stored.
|
||||||
|
* @param pw_max The maximum length of the password.
|
||||||
|
* @param fname The name of the file that is going to be unlocked.
|
||||||
|
* @param userdata The userdata pointer as supplied when setting this callback.
|
||||||
|
*
|
||||||
|
* @return -1 on error, else the number of bytes written to `pw`
|
||||||
|
*
|
||||||
|
* @ingroup TLS
|
||||||
|
*/
|
||||||
|
typedef int (*xmpp_password_callback)(char *pw,
|
||||||
|
size_t pw_max,
|
||||||
|
const char *fname,
|
||||||
|
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.
|
||||||
*
|
*
|
||||||
* The `sock` argument is a pointer that is dependent on the architecture
|
* The `sock` argument is a pointer that is dependent on the architecture
|
||||||
@@ -319,6 +352,9 @@ void xmpp_conn_set_capath(xmpp_conn_t *const conn, const char *path);
|
|||||||
void xmpp_conn_set_certfail_handler(xmpp_conn_t *const conn,
|
void xmpp_conn_set_certfail_handler(xmpp_conn_t *const conn,
|
||||||
xmpp_certfail_handler hndl);
|
xmpp_certfail_handler hndl);
|
||||||
xmpp_tlscert_t *xmpp_conn_get_peer_cert(xmpp_conn_t *const conn);
|
xmpp_tlscert_t *xmpp_conn_get_peer_cert(xmpp_conn_t *const conn);
|
||||||
|
void xmpp_conn_set_password_callback(xmpp_conn_t *conn,
|
||||||
|
xmpp_password_callback cb,
|
||||||
|
void *userdata);
|
||||||
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);
|
||||||
|
|||||||
BIN
tests/cert.pfx
Normal file
BIN
tests/cert.pfx
Normal file
Binary file not shown.
8
tests/key_encrypted.pem
Normal file
8
tests/key_encrypted.pem
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
-----BEGIN EC PRIVATE KEY-----
|
||||||
|
Proc-Type: 4,ENCRYPTED
|
||||||
|
DEK-Info: AES-256-CBC,30B7F2152A39184AEEF290ED2B1DBC0E
|
||||||
|
|
||||||
|
IojJAF1hGHL9qnu5MCzl5l1PzsnxLj38JjLBLPXaNekiAbag5H9Tzr7y5nfyviPM
|
||||||
|
hi6syrkXgLOBF+mZWJouOxCNx7+t6eWVfsDZcnzsw6HrTN/2xwkYP/3lYfzlnt6q
|
||||||
|
/AKYQIUUPc/31J6xg3kGRQ==
|
||||||
|
-----END EC PRIVATE KEY-----
|
||||||
22
tests/test.h
22
tests/test.h
@@ -34,17 +34,17 @@
|
|||||||
#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]))
|
#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]))
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#define COMPARE(v1, v2) \
|
#define COMPARE(v1, v2) \
|
||||||
do { \
|
do { \
|
||||||
const char *__v1 = v1; \
|
const char *__v1 = v1; \
|
||||||
const char *__v2 = v2; \
|
const char *__v2 = v2; \
|
||||||
if (strcmp(__v1, __v2) != 0) { \
|
if (!__v1 || !__v2 || strcmp(__v1, __v2) != 0) { \
|
||||||
printf("Error: %s\n" \
|
printf("Error: %s\n" \
|
||||||
"Expected: %s\n" \
|
"Expected: %s\n" \
|
||||||
"Got: %s\n", \
|
"Got: %s\n", \
|
||||||
#v1 " != " #v2, __v1, __v2); \
|
#v1 " != " #v2, __v1, __v2); \
|
||||||
exit(1); \
|
exit(1); \
|
||||||
} \
|
} \
|
||||||
} while (0)
|
} while (0)
|
||||||
|
|
||||||
#define COMPARE_BUF(v1, len1, v2, len2) \
|
#define COMPARE_BUF(v1, len1, v2, len2) \
|
||||||
|
|||||||
@@ -18,50 +18,71 @@
|
|||||||
|
|
||||||
#include "test.h"
|
#include "test.h"
|
||||||
|
|
||||||
|
static int
|
||||||
|
password_callback(char *pw, size_t pw_max, const char *fname, void *userdata)
|
||||||
|
{
|
||||||
|
(void)pw_max;
|
||||||
|
(void)userdata;
|
||||||
|
(void)fname;
|
||||||
|
memcpy(pw, "abc123", 7);
|
||||||
|
return 6;
|
||||||
|
}
|
||||||
|
|
||||||
int main()
|
int main()
|
||||||
{
|
{
|
||||||
xmpp_ctx_t *ctx;
|
xmpp_ctx_t *ctx;
|
||||||
xmpp_conn_t *conn;
|
xmpp_conn_t *conn;
|
||||||
xmpp_log_t *log;
|
xmpp_log_t *log;
|
||||||
|
char *client_cert[][2] = {
|
||||||
|
{"tests/cert.pem", "tests/key.pem"},
|
||||||
|
{"tests/cert.pem", "tests/key_encrypted.pem"},
|
||||||
|
{NULL, "tests/cert.pfx"},
|
||||||
|
};
|
||||||
|
|
||||||
char xmppaddr_num[] = "0";
|
char xmppaddr_num[] = "0";
|
||||||
unsigned int n;
|
unsigned int m, n;
|
||||||
|
|
||||||
xmpp_initialize();
|
xmpp_initialize();
|
||||||
log = xmpp_get_default_logger(XMPP_LEVEL_DEBUG);
|
log = xmpp_get_default_logger(XMPP_LEVEL_DEBUG);
|
||||||
ctx = xmpp_ctx_new(NULL, log);
|
ctx = xmpp_ctx_new(NULL, log);
|
||||||
conn = xmpp_conn_new(ctx);
|
|
||||||
|
|
||||||
xmpp_conn_set_client_cert(conn, "tests/cert.pem", "tests/key.pem");
|
for (m = 0; m < sizeof(client_cert) / sizeof(client_cert[0]); ++m) {
|
||||||
|
conn = xmpp_conn_new(ctx);
|
||||||
|
|
||||||
xmppaddr_num[0] = xmppaddr_num[0] + xmpp_conn_cert_xmppaddr_num(conn);
|
xmpp_conn_set_password_callback(conn, password_callback, NULL);
|
||||||
|
|
||||||
COMPARE("2", xmppaddr_num);
|
xmpp_conn_set_client_cert(conn, client_cert[m][0], client_cert[m][1]);
|
||||||
|
|
||||||
for (n = 0; n < 3; ++n) {
|
xmppaddr_num[0] = '0' + xmpp_conn_cert_xmppaddr_num(conn);
|
||||||
char *r = xmpp_conn_cert_xmppaddr(conn, n);
|
|
||||||
switch (n) {
|
COMPARE("2", xmppaddr_num);
|
||||||
case 0:
|
|
||||||
COMPARE("very.long.username@so.the.asn1.length.is.a.valid.ascii."
|
for (n = 0; n < 3; ++n) {
|
||||||
|
char *r = xmpp_conn_cert_xmppaddr(conn, n);
|
||||||
|
switch (n) {
|
||||||
|
case 0:
|
||||||
|
COMPARE(
|
||||||
|
"very.long.username@so.the.asn1.length.is.a.valid.ascii."
|
||||||
"character",
|
"character",
|
||||||
r);
|
r);
|
||||||
break;
|
break;
|
||||||
case 1:
|
case 1:
|
||||||
COMPARE("second@xmpp.jid", r);
|
COMPARE("second@xmpp.jid", r);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
if (r != NULL) {
|
if (r != NULL) {
|
||||||
printf("\nThere shall only be two id-on-xmppAddr SANs!\nFound "
|
printf("\nThere shall only be two id-on-xmppAddr SANs!\n"
|
||||||
"another one: %s\n",
|
"Found another one: %s\n",
|
||||||
r);
|
r);
|
||||||
exit(1);
|
exit(1);
|
||||||
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
break;
|
free(r);
|
||||||
}
|
}
|
||||||
free(r);
|
xmpp_conn_release(conn);
|
||||||
}
|
}
|
||||||
|
|
||||||
xmpp_conn_release(conn);
|
|
||||||
xmpp_ctx_free(ctx);
|
xmpp_ctx_free(ctx);
|
||||||
xmpp_shutdown();
|
xmpp_shutdown();
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user