diff --git a/Makefile.am b/Makefile.am index 761df66..0b7a060 100644 --- a/Makefile.am +++ b/Makefile.am @@ -114,7 +114,9 @@ EXTRA_DIST = \ jni/Application.mk \ src/tls_schannel.c \ tests/cert.pem \ + tests/cert.pfx \ tests/key.pem \ + tests/key_encrypted.pem \ tests/res_query_dump.c ## Examples diff --git a/examples/complex.c b/examples/complex.c index 00305ee..67c5d01 100644 --- a/examples/complex.c +++ b/examples/complex.c @@ -12,6 +12,7 @@ #include #include #include +#include #ifdef _WIN32 #include @@ -93,6 +94,52 @@ static int certfail_handler(const xmpp_tlscert_t *cert, return read_char[0] == 'y' || read_char[0] == 'Y'; } +static int +password_callback(char *pw, size_t pw_max, const char *fname, void *userdata) +{ + (void)userdata; + /* when using an encrypted keyfile, we get asked multiple times for the + * password ... cache it until we're connected + */ + static struct { + char pass[256]; + unsigned char hash[XMPP_SHA1_DIGEST_SIZE]; + size_t passlen, fnamelen; + unsigned count; + } password_cache; + int ret = -1; + unsigned char hash[XMPP_SHA1_DIGEST_SIZE]; + + size_t fname_len = strlen(fname); + xmpp_sha1_digest((void *)fname, fname_len, hash); + if (fname_len && fname_len == password_cache.fnamelen && + memcmp(hash, password_cache.hash, sizeof(hash)) == 0) { + if (password_cache.passlen && --password_cache.count) { + memcpy(pw, password_cache.pass, password_cache.passlen + 1); + ret = password_cache.passlen; + if (!password_cache.count) + memset(&password_cache, 0, sizeof(password_cache)); + return ret; + } + } + printf("Trying to unlock %s\n", fname); + char *pass = getpass("Please enter password: "); + if (!pass) + return -1; + size_t passlen = strlen(pass); + if (passlen < pw_max) { + memcpy(pw, pass, passlen + 1); + ret = passlen; + memcpy(password_cache.pass, pass, passlen + 1); + password_cache.passlen = passlen; + password_cache.count = 6; + memcpy(password_cache.hash, hash, sizeof(hash)); + password_cache.fnamelen = fname_len; + } + memset(pass, 0, passlen); + return ret; +} + static int sockopt_cb(xmpp_conn_t *conn, void *socket) { int timeout = KA_TIMEOUT; @@ -181,7 +228,7 @@ static void usage(int exit_code) " --jid The JID to use to authenticate.\n" " --pass The password of the JID.\n" " --tls-cert Path to client certificate.\n" - " --tls-key Path to private key.\n\n" + " --tls-key Path to private key or P12 file.\n\n" " --capath Path to an additional CA trust store " "(directory).\n" " --cafile Path to an additional CA trust store " @@ -247,7 +294,7 @@ int main(int argc, char **argv) else break; } - if ((!jid && (!cert || !key)) || (argc - i) > 2) { + if ((!jid && !key) || (argc - i) > 2) { usage(1); } @@ -279,8 +326,10 @@ int main(int argc, char **argv) if (tcp_keepalive) 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 */ - if (cert && key) { + if (key) { xmpp_conn_set_client_cert(conn, cert, key); } if (jid) diff --git a/src/auth.c b/src/auth.c index eaaf3cc..24a6291 100644 --- a/src/auth.c +++ b/src/auth.c @@ -242,7 +242,7 @@ _handle_features(xmpp_conn_t *conn, xmpp_stanza_t *stanza, void *userdata) if (strcasecmp(text, "PLAIN") == 0) conn->sasl_support |= SASL_MASK_PLAIN; else if (strcasecmp(text, "EXTERNAL") == 0 && - conn->tls_client_cert) + (conn->tls_client_cert || conn->tls_client_key)) conn->sasl_support |= SASL_MASK_EXTERNAL; else if (strcasecmp(text, "DIGEST-MD5") == 0) conn->sasl_support |= SASL_MASK_DIGESTMD5; diff --git a/src/common.h b/src/common.h index 1b60b1b..964fb5f 100644 --- a/src/common.h +++ b/src/common.h @@ -192,6 +192,8 @@ struct _xmpp_conn_t { int auth_legacy_enabled; int secured; /* set when stream is secured with TLS */ xmpp_certfail_handler certfail_handler; + xmpp_password_callback password_callback; + void *password_callback_userdata; /* if server returns or we must do them */ int bind_required; diff --git a/src/conn.c b/src/conn.c index b9e1ffe..261c3cb 100644 --- a/src/conn.c +++ b/src/conn.c @@ -182,6 +182,8 @@ xmpp_conn_t *xmpp_conn_new(xmpp_ctx_t *ctx) conn->auth_legacy_enabled = 0; conn->secured = 0; conn->certfail_handler = NULL; + conn->password_callback = NULL; + conn->password_callback_userdata = NULL; conn->bind_required = 0; conn->session_required = 0; @@ -492,15 +494,38 @@ xmpp_tlscert_t *xmpp_conn_get_peer_cert(xmpp_conn_t *const conn) return tls_peer_cert(conn); } -/** Set the Client Certificate and Private Key that will be bound to the - * connection. If any of the both was previously set, it will be discarded. - * This should not be used after a connection is created. The function will - * make a copy of the strings passed in. - * Currently only non-encrypted Private Keys are supported. +/** Set the Callback function which will be called when the TLS stack can't + * decrypt a password protected key file. + * + * @param conn a Strophe connection object + * @param cb The callback function that shall be called + * @param userdata An opaque data pointer that will be passed to the callback + * + * @ingroup TLS + */ +void xmpp_conn_set_password_callback(xmpp_conn_t *conn, + xmpp_password_callback cb, + void *userdata) +{ + conn->password_callback = cb; + conn->password_callback_userdata = userdata; +} + +/** Set the Client Certificate and Private Key or PKCS#12 encoded file that + * will be bound to the connection. If any of them was previously set, it + * will be discarded. This should not be used after a connection is created. + * The function will make a copy of the strings passed in. + * + * In case the Private Key is encrypted, a callback must be set via + * \ref xmpp_conn_set_password_callback so the TLS stack can retrieve the + * password. + * + * In case one wants to use a PKCS#12 encoded file, it must be passed via + * the `key` parameter and `cert` must be NULL. * * @param conn a Strophe connection object * @param cert path to a certificate file - * @param key path to a private key file + * @param key path to a private key file or a P12 file * * @ingroup TLS */ @@ -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); if (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) strophe_free(conn->ctx, conn->tls_client_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 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) { strophe_debug(conn->ctx, "xmpp", "Client certificate contains multiple or no xmppAddr " diff --git a/src/tls_gnutls.c b/src/tls_gnutls.c index b56810e..7c506f5 100644 --- a/src/tls_gnutls.c +++ b/src/tls_gnutls.c @@ -17,6 +17,8 @@ #include #include #include +#include +#include #include "common.h" #include "tls.h" @@ -47,10 +49,30 @@ void tls_shutdown(void) gnutls_global_deinit(); } -static gnutls_x509_crt_t _tls_load_cert(xmpp_conn_t *conn) +static int _tls_password_callback(void *userdata, + int attempt, + const char *token_url, + const char *token_label, + unsigned int flags, + char *pin, + size_t pin_max) +{ + xmpp_conn_t *conn = userdata; + UNUSED(attempt); + UNUSED(token_url); + UNUSED(token_label); + UNUSED(flags); + if (!conn->password_callback) { + 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_datum_t data; int res; @@ -68,6 +90,64 @@ error_out: 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) { 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 * OR if we trust all Certificates */ - if (status == 0 || tls->conn->tls_trust) + if (status == 0 || tls->conn->tls_trust) { + gnutls_free(out.data); return 0; + } if (!tls->conn->certfail_handler) { strophe_error(tls->ctx, "tls", "No certfail handler set, canceling connection attempt"); + gnutls_free(out.data); return -1; } @@ -324,6 +407,9 @@ tls_t *tls_new(xmpp_conn_t *conn) gnutls_certificate_allocate_credentials(&tls->cred); tls_set_credentials(tls, NULL); + gnutls_certificate_set_pin_function(tls->cred, _tls_password_callback, + conn); + if (conn->tls_client_cert && conn->tls_client_key) { tls->client_cert = _tls_load_cert(conn); if (!tls->client_cert) { @@ -337,6 +423,15 @@ tls_t *tls_new(xmpp_conn_t *conn) gnutls_certificate_set_x509_key_file( tls->cred, conn->tls_client_cert, conn->tls_client_key, GNUTLS_X509_FMT_PEM); + } else if (conn->tls_client_key) { + char pass[GNUTLS_PKCS11_MAX_PIN_LEN]; + pass[0] = '\0'; + int passlen = _tls_password_callback(conn, 0, NULL, NULL, 0, pass, + sizeof(pass)); + if (passlen < 0) + pass[0] = '\0'; + gnutls_certificate_set_x509_simple_pkcs12_file( + tls->cred, conn->tls_client_key, GNUTLS_X509_FMT_DER, pass); } gnutls_certificate_set_verify_function(tls->cred, _tls_verify); diff --git a/src/tls_openssl.c b/src/tls_openssl.c index ab962b6..4dde48b 100644 --- a/src/tls_openssl.c +++ b/src/tls_openssl.c @@ -26,6 +26,7 @@ #include #include #include +#include #include "common.h" #include "tls.h" @@ -54,6 +55,30 @@ static const unsigned char *ASN1_STRING_get0_data(ASN1_STRING *asn1) } #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 static int GENERAL_NAME_get0_otherName(const GENERAL_NAME *gen, 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_dump_cert_info(tls_t *tls); 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_xmppaddr_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; int i, j; GENERAL_NAMES *names = _tls_conn_get_names(conn); - if (!names) + if (!names) { + _tls_log_error(conn->ctx); return NULL; + } int num_names = sk_GENERAL_NAME_num(names); for (i = j = 0; i < num_names; ++i) { char *res; @@ -288,8 +317,10 @@ unsigned int tls_id_on_xmppaddr_num(xmpp_conn_t *conn) { unsigned int ret = 0; GENERAL_NAMES *names = _tls_conn_get_names(conn); - if (!names) + if (!names) { + _tls_log_error(conn->ctx); 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); @@ -355,18 +386,16 @@ _get_fingerprint(const xmpp_ctx_t *ctx, X509 *err_cert, xmpp_cert_element_t el) static char * _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) { case XMPP_CERT_KEYALG: { #if OPENSSL_VERSION_NUMBER < 0x10100000L - UNUSED(rc); alg_nid = OBJ_obj2nid(err_cert->cert_info->key->algor->algorithm); #else X509_PUBKEY *pubkey = X509_get_X509_PUBKEY(err_cert); ASN1_OBJECT *ppkalg = NULL; - rc = X509_PUBKEY_get0_param(&ppkalg, NULL, NULL, NULL, pubkey); - if (rc) { + if (X509_PUBKEY_get0_param(&ppkalg, NULL, NULL, NULL, pubkey)) { alg_nid = OBJ_obj2nid(ppkalg); } #endif @@ -510,6 +539,18 @@ static int _tls_verify(int preverify_ok, X509_STORE_CTX *x509_ctx) 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 = 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_SSLv3); /* POODLE */ 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) { tls->client_cert = _tls_cert_read(conn); @@ -552,6 +595,21 @@ tls_t *tls_new(xmpp_conn_t *conn) SSL_FILETYPE_PEM); SSL_CTX_use_PrivateKey_file(tls->ssl_ctx, conn->tls_client_key, 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 { /* If the server asks for a client certificate, don't send one. */ 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) return conn->tls->client_cert; @@ -865,6 +923,60 @@ static X509 *_tls_cert_read(xmpp_conn_t *conn) 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 xaddr_nid = NID_undef; diff --git a/strophe.h b/strophe.h index 7a5fe1c..69bd3b4 100644 --- a/strophe.h +++ b/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, 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 `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, xmpp_certfail_handler hndl); 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, const char *cert, const char *key); diff --git a/tests/cert.pfx b/tests/cert.pfx new file mode 100644 index 0000000..6d55fd8 Binary files /dev/null and b/tests/cert.pfx differ diff --git a/tests/key_encrypted.pem b/tests/key_encrypted.pem new file mode 100644 index 0000000..665185d --- /dev/null +++ b/tests/key_encrypted.pem @@ -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----- diff --git a/tests/test.h b/tests/test.h index 6166d5b..cd883a2 100644 --- a/tests/test.h +++ b/tests/test.h @@ -34,17 +34,17 @@ #define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0])) #endif -#define COMPARE(v1, v2) \ - do { \ - const char *__v1 = v1; \ - const char *__v2 = v2; \ - if (strcmp(__v1, __v2) != 0) { \ - printf("Error: %s\n" \ - "Expected: %s\n" \ - "Got: %s\n", \ - #v1 " != " #v2, __v1, __v2); \ - exit(1); \ - } \ +#define COMPARE(v1, v2) \ + do { \ + const char *__v1 = v1; \ + const char *__v2 = v2; \ + if (!__v1 || !__v2 || strcmp(__v1, __v2) != 0) { \ + printf("Error: %s\n" \ + "Expected: %s\n" \ + "Got: %s\n", \ + #v1 " != " #v2, __v1, __v2); \ + exit(1); \ + } \ } while (0) #define COMPARE_BUF(v1, len1, v2, len2) \ diff --git a/tests/test_xmppaddr.c b/tests/test_xmppaddr.c index 39539f9..b346f89 100644 --- a/tests/test_xmppaddr.c +++ b/tests/test_xmppaddr.c @@ -18,50 +18,71 @@ #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() { xmpp_ctx_t *ctx; xmpp_conn_t *conn; 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"; - unsigned int n; + unsigned int m, n; xmpp_initialize(); log = xmpp_get_default_logger(XMPP_LEVEL_DEBUG); 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) { - char *r = xmpp_conn_cert_xmppaddr(conn, n); - switch (n) { - case 0: - COMPARE("very.long.username@so.the.asn1.length.is.a.valid.ascii." + xmppaddr_num[0] = '0' + xmpp_conn_cert_xmppaddr_num(conn); + + COMPARE("2", xmppaddr_num); + + 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", r); - break; - case 1: - COMPARE("second@xmpp.jid", r); - break; - default: - if (r != NULL) { - printf("\nThere shall only be two id-on-xmppAddr SANs!\nFound " - "another one: %s\n", - r); - exit(1); + break; + case 1: + COMPARE("second@xmpp.jid", r); + break; + default: + if (r != NULL) { + printf("\nThere shall only be two id-on-xmppAddr SANs!\n" + "Found another one: %s\n", + r); + exit(1); + } + break; } - break; + free(r); } - free(r); + xmpp_conn_release(conn); } - xmpp_conn_release(conn); xmpp_ctx_free(ctx); xmpp_shutdown();