add support for client authentication via certificates

The SASL EXTERNAL method is implemented to make this possible.

Signed-off-by: Steffen Jaeckel <jaeckel-floss@eyet-services.de>
This commit is contained in:
Steffen Jaeckel
2021-03-03 13:57:09 +01:00
committed by Dmitry Podgorny
parent cf2f7147d9
commit 18c95fa7bd
6 changed files with 301 additions and 3 deletions

View File

@@ -242,6 +242,9 @@ _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->sasl_support |= SASL_MASK_EXTERNAL;
else if (strcasecmp(text, "DIGEST-MD5") == 0)
conn->sasl_support |= SASL_MASK_DIGESTMD5;
else if (strcasecmp(text, "SCRAM-SHA-1") == 0)
@@ -651,6 +654,49 @@ static void _auth(xmpp_conn_t *conn)
/* SASL ANONYMOUS was tried, unset flag */
conn->sasl_support &= ~SASL_MASK_ANONYMOUS;
} else if (conn->sasl_support & SASL_MASK_EXTERNAL) {
/* more crap here */
auth = _make_sasl_auth(conn, "EXTERNAL");
if (!auth) {
disconnect_mem_error(conn);
return;
}
authdata = xmpp_stanza_new(conn->ctx);
if (!authdata) {
xmpp_stanza_release(auth);
disconnect_mem_error(conn);
return;
}
str = tls_id_on_xmppaddr(conn, 0);
if (!str || (tls_id_on_xmppaddr_num(conn) == 1 &&
strcmp(str, conn->jid) == 0)) {
xmpp_stanza_set_text(authdata, "=");
} else {
xmpp_free(conn->ctx, str);
str = xmpp_base64_encode(conn->ctx, (void *)conn->jid,
strlen(conn->jid));
if (!str) {
xmpp_stanza_release(authdata);
xmpp_stanza_release(auth);
disconnect_mem_error(conn);
return;
}
xmpp_stanza_set_text(authdata, str);
}
xmpp_free(conn->ctx, str);
xmpp_stanza_add_child(auth, authdata);
xmpp_stanza_release(authdata);
handler_add(conn, _handle_sasl_result, XMPP_NS_SASL, NULL, NULL,
"EXTERNAL");
xmpp_send(conn, auth);
xmpp_stanza_release(auth);
/* SASL EXTERNAL was tried, unset flag */
conn->sasl_support &= ~SASL_MASK_EXTERNAL;
} else if (anonjid) {
xmpp_error(conn->ctx, "auth",
"No node in JID, and SASL ANONYMOUS unsupported.");

View File

@@ -131,6 +131,7 @@ struct _xmpp_send_queue_t {
#define SASL_MASK_SCRAMSHA1 (1 << 3)
#define SASL_MASK_SCRAMSHA256 (1 << 4)
#define SASL_MASK_SCRAMSHA512 (1 << 5)
#define SASL_MASK_EXTERNAL (1 << 6)
#define SASL_MASK_SCRAM \
(SASL_MASK_SCRAMSHA1 | SASL_MASK_SCRAMSHA256 | SASL_MASK_SCRAMSHA512)
@@ -164,6 +165,8 @@ struct _xmpp_conn_t {
int tls_mandatory;
int tls_legacy_ssl;
int tls_trust;
char *tls_client_cert;
char *tls_client_key;
int tls_failed; /* set when tls fails, so we don't try again */
int sasl_support; /* if true, field is a bitfield of supported
mechanisms */

View File

@@ -143,6 +143,8 @@ xmpp_conn_t *xmpp_conn_new(xmpp_ctx_t *ctx)
conn->tls_legacy_ssl = 0;
conn->tls_trust = 0;
conn->tls_failed = 0;
conn->tls_client_cert = NULL;
conn->tls_client_key = NULL;
conn->sasl_support = 0;
conn->auth_legacy_enabled = 0;
conn->secured = 0;
@@ -338,6 +340,10 @@ int xmpp_conn_release(xmpp_conn_t *conn)
xmpp_free(ctx, conn->pass);
if (conn->lang)
xmpp_free(ctx, conn->lang);
if (conn->tls_client_cert)
xmpp_free(ctx, conn->tls_client_cert);
if (conn->tls_client_key)
xmpp_free(ctx, conn->tls_client_key);
xmpp_free(ctx, conn);
released = 1;
}
@@ -394,6 +400,58 @@ void xmpp_conn_set_jid(xmpp_conn_t *conn, const char *jid)
conn->jid = xmpp_strdup(conn->ctx, jid);
}
/** 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.
*
* @param conn a Strophe connection object
* @param cert path to a certificate file
* @param key path to a private key file
*
* @ingroup Connections
*/
void xmpp_conn_set_client_cert(xmpp_conn_t *const conn,
const char *const cert,
const char *const key)
{
xmpp_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 = xmpp_strdup(conn->ctx, cert);
if (conn->tls_client_key)
xmpp_free(conn->ctx, conn->tls_client_key);
conn->tls_client_key = xmpp_strdup(conn->ctx, key);
}
/** Get the number of xmppAddr entries in the client certificate.
*
* @param conn a Strophe connection object
*
* @return the number of xmppAddr entries in the client certificate
*
* @ingroup Connections
*/
unsigned int xmpp_conn_cert_xmppaddr_num(xmpp_conn_t *const conn)
{
return tls_id_on_xmppaddr_num(conn);
}
/** Get a specific xmppAddr entry.
*
* @param conn a Strophe connection object
* @param n the index of the entry, starting at 0
*
* @return a string containing the xmppAddr or NULL if n is out of range
*
* @ingroup Connections
*/
char *xmpp_conn_cert_xmppaddr(xmpp_conn_t *const conn, unsigned int n)
{
return tls_id_on_xmppaddr(conn, n);
}
/** Get the password used for authentication of a connection.
*
* @param conn a Strophe connection object
@@ -470,6 +528,20 @@ int xmpp_connect_client(xmpp_conn_t *conn,
int found = XMPP_DOMAIN_NOT_FOUND;
int rc;
if (!conn->jid && conn->tls_client_cert) {
if (tls_id_on_xmppaddr_num(conn) != 1) {
xmpp_debug(conn->ctx, "xmpp",
"Client certificate contains multiple or no xmppAddr "
"and no JID was given to be used.");
return XMPP_EINVOP;
}
conn->jid = tls_id_on_xmppaddr(conn, 0);
if (!conn->jid)
return XMPP_EMEM;
xmpp_debug(conn->ctx, "xmpp", "Use jid %s from id-on-xmppAddr.",
conn->jid);
}
domain = xmpp_jid_domain(conn->ctx, conn->jid);
if (!domain)
return XMPP_EMEM;

View File

@@ -27,6 +27,9 @@ void tls_shutdown(void);
tls_t *tls_new(xmpp_conn_t *conn);
void tls_free(tls_t *tls);
char *tls_id_on_xmppaddr(xmpp_conn_t *conn, unsigned int n);
unsigned int tls_id_on_xmppaddr_num(xmpp_conn_t *conn);
int tls_set_credentials(tls_t *tls, const char *cafilename);
int tls_start(tls_t *tls);

View File

@@ -47,11 +47,19 @@
#endif
#endif
#if OPENSSL_VERSION_NUMBER < 0x10100000L
static const unsigned char *ASN1_STRING_get0_data(ASN1_STRING *asn1)
{
return ASN1_STRING_data(asn1);
}
#endif
struct _tls {
xmpp_ctx_t *ctx;
sock_t sock;
SSL_CTX *ssl_ctx;
SSL *ssl;
X509 *client_cert;
int lasterror;
};
@@ -66,6 +74,10 @@ static const char *_tls_error_str(int error, const char **tbl, size_t tbl_size);
static void _tls_set_error(tls_t *tls, int error);
static void _tls_log_error(xmpp_ctx_t *ctx);
static void _tls_dump_cert_info(tls_t *tls);
static X509 *_tls_cert_read(xmpp_conn_t *conn);
static int _tls_xaddr_nid(void);
static int _tls_name_to_xmppaddr(GENERAL_NAME *name, char **res);
static GENERAL_NAMES *_tls_cert_get_names(xmpp_conn_t *conn);
#define TLS_ERROR_STR(error, table) \
_tls_error_str(error, table, ARRAY_SIZE(table))
@@ -189,6 +201,8 @@ void tls_initialize(void)
#else
OPENSSL_init_ssl(OPENSSL_INIT_LOAD_SSL_STRINGS, NULL);
#endif
/* init xmppAddr OID */
_tls_xaddr_nid();
}
void tls_shutdown(void)
@@ -199,6 +213,7 @@ void tls_shutdown(void)
* memory rather than cause random crashes of the main program.
*/
#if OPENSSL_VERSION_NUMBER < 0x10100000L
OBJ_cleanup();
ERR_free_strings();
EVP_cleanup();
CRYPTO_cleanup_all_ex_data();
@@ -218,6 +233,55 @@ int tls_error(tls_t *tls)
return tls->lasterror;
}
/** Search through the SubjectAlternativeNames and return the next
* id-on-xmppAddr element starting from `n`.
*/
char *tls_id_on_xmppaddr(xmpp_conn_t *conn, unsigned int n)
{
char *ret = NULL;
int i, j;
GENERAL_NAMES *names = _tls_cert_get_names(conn);
if (!names)
return NULL;
int num_names = sk_GENERAL_NAME_num(names);
for (i = j = 0; i < num_names; ++i) {
char *res;
GENERAL_NAME *name = sk_GENERAL_NAME_value(names, i);
if (name == NULL)
break;
if (_tls_name_to_xmppaddr(name, &res))
continue;
if (j == (int)n) {
xmpp_debug(conn->ctx, "tls", "extracted jid %s from id-on-xmppAddr",
res);
ret = xmpp_strdup(conn->ctx, res);
OPENSSL_free(res);
break;
}
j++;
OPENSSL_free(res);
}
GENERAL_NAMES_free(names);
return ret;
}
unsigned int tls_id_on_xmppaddr_num(xmpp_conn_t *conn)
{
unsigned int ret = 0;
GENERAL_NAMES *names = _tls_cert_get_names(conn);
if (!names)
return 0;
int j, num_names = sk_GENERAL_NAME_num(names);
for (j = 0; j < num_names; ++j) {
GENERAL_NAME *name = sk_GENERAL_NAME_value(names, j);
if (_tls_name_to_xmppaddr(name, NULL))
continue;
ret++;
}
GENERAL_NAMES_free(names);
return ret;
}
tls_t *tls_new(xmpp_conn_t *conn)
{
tls_t *tls = xmpp_alloc(conn->ctx, sizeof(*tls));
@@ -249,7 +313,23 @@ tls_t *tls_new(xmpp_conn_t *conn)
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_client_cert_cb(tls->ssl_ctx, NULL);
if (conn->tls_client_cert && conn->tls_client_key) {
tls->client_cert = _tls_cert_read(conn);
if (!tls->client_cert) {
xmpp_error(tls->ctx, "tls",
"could not read client certificate");
goto err_free_ctx;
}
SSL_CTX_use_certificate_file(tls->ssl_ctx, conn->tls_client_cert,
SSL_FILETYPE_PEM);
SSL_CTX_use_PrivateKey_file(tls->ssl_ctx, conn->tls_client_key,
SSL_FILETYPE_PEM);
} else {
/* 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_mode(tls->ssl_ctx, SSL_MODE_ENABLE_PARTIAL_WRITE);
ret = SSL_CTX_set_default_verify_paths(tls->ssl_ctx);
@@ -261,12 +341,12 @@ tls_t *tls_new(xmpp_conn_t *conn)
*/
xmpp_error(tls->ctx, "tls",
"SSL_CTX_set_default_verify_paths() failed");
goto err_free_ctx;
goto err_free_cert;
}
tls->ssl = SSL_new(tls->ssl_ctx);
if (tls->ssl == NULL)
goto err_free_ctx;
goto err_free_cert;
#if OPENSSL_VERSION_NUMBER >= 0x0908060L && !defined(OPENSSL_NO_TLSEXT)
/* Enable SNI. */
@@ -301,6 +381,8 @@ tls_t *tls_new(xmpp_conn_t *conn)
err_free_ssl:
SSL_free(tls->ssl);
err_free_cert:
X509_free(tls->client_cert);
err_free_ctx:
SSL_CTX_free(tls->ssl_ctx);
err:
@@ -312,6 +394,7 @@ err:
void tls_free(tls_t *tls)
{
SSL_free(tls->ssl);
X509_free(tls->client_cert);
SSL_CTX_free(tls->ssl_ctx);
xmpp_free(tls->ctx, tls);
}
@@ -509,3 +592,89 @@ static void _tls_dump_cert_info(tls_t *tls)
X509_free(cert);
}
}
static X509 *_tls_cert_read(xmpp_conn_t *conn)
{
if (conn->tls && conn->tls->client_cert)
return conn->tls->client_cert;
BIO *f = BIO_new_file(conn->tls_client_cert, "r");
if (!f) {
xmpp_debug(conn->ctx, "tls", "f == NULL");
return NULL;
}
X509 *c = PEM_read_bio_X509(f, NULL, NULL, NULL);
BIO_free(f);
if (!c) {
unsigned long error;
while ((error = ERR_get_error()) != 0) {
xmpp_debug(conn->ctx, "tls", "c == NULL: %s",
ERR_error_string(error, NULL));
}
}
return c;
}
static int _tls_xaddr_nid(void)
{
static int xaddr_nid = NID_undef;
if (xaddr_nid == NID_undef) {
xaddr_nid = OBJ_create("1.3.6.1.5.5.7.8.5", "id-on-xmppAddr",
"XmppAddr Identifier");
}
return xaddr_nid;
}
static GENERAL_NAMES *_tls_cert_get_names(xmpp_conn_t *conn)
{
X509 *client_cert;
GENERAL_NAMES *names = NULL;
client_cert = _tls_cert_read(conn);
if (!client_cert)
return NULL;
int san = X509_get_ext_by_NID(client_cert, NID_subject_alt_name, 0);
X509_EXTENSION *san_ext = X509_get_ext(client_cert, san);
if (!san_ext)
goto OUT;
ASN1_OCTET_STRING *data = X509_EXTENSION_get_data(san_ext);
if (!data)
goto OUT;
const unsigned char *d = ASN1_STRING_get0_data(data);
if (!d)
goto OUT;
names = d2i_GENERAL_NAMES(NULL, &d, ASN1_STRING_length(data));
OUT:
if (!conn->tls || !conn->tls->client_cert)
X509_free(client_cert);
return names;
}
/** Convert GENERAL_NAME* to a string
*
* This checks whether the GENERAL_NAME* that is given has the
* correct id-on-xmppAddr set and then optionally converts this
* form ASN.1 to a string/char*.
*
* When `res` pointer is set to NULL this method doesn't allocate
* the result but only checks whether it is in the correct format.
*
* @param name Pointer to the GENERAL_NAME that shall be converted
* @param res Result-pointer (optional, can be NULL)
*
* @return classic Unix style - 0=success, 1=error
*/
static int _tls_name_to_xmppaddr(GENERAL_NAME *name, char **res)
{
ASN1_OBJECT *oid;
ASN1_TYPE *val;
if (!name || name->type != GEN_OTHERNAME)
return 1;
if (GENERAL_NAME_get0_otherName(name, &oid, &val) == 0)
return 1;
if (OBJ_obj2nid(oid) != _tls_xaddr_nid() || !val)
return 1;
if (!res)
return 0;
if (ASN1_STRING_to_UTF8((unsigned char **)res, val->value.asn1_string) < 0)
return 1;
return 0;
}

View File

@@ -234,6 +234,11 @@ int xmpp_conn_set_flags(xmpp_conn_t *conn, long flags);
const char *xmpp_conn_get_jid(const xmpp_conn_t *conn);
const char *xmpp_conn_get_bound_jid(const xmpp_conn_t *conn);
void xmpp_conn_set_jid(xmpp_conn_t *conn, const char *jid);
void xmpp_conn_set_client_cert(xmpp_conn_t *conn,
const char *cert,
const char *key);
unsigned int xmpp_conn_cert_xmppaddr_num(xmpp_conn_t *conn);
char *xmpp_conn_cert_xmppaddr(xmpp_conn_t *conn, unsigned int n);
const char *xmpp_conn_get_pass(const xmpp_conn_t *conn);
void xmpp_conn_set_pass(xmpp_conn_t *conn, const char *pass);
xmpp_ctx_t *xmpp_conn_get_context(xmpp_conn_t *conn);