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:
committed by
Dmitry Podgorny
parent
cf2f7147d9
commit
18c95fa7bd
46
src/auth.c
46
src/auth.c
@@ -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.");
|
||||
|
||||
@@ -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 */
|
||||
|
||||
72
src/conn.c
72
src/conn.c
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user