mirror of
https://github.com/nzbget/nzbget.git
synced 2026-01-19 11:27:46 -05:00
1561 lines
36 KiB
C++
1561 lines
36 KiB
C++
/*
|
|
* This file is part of nzbget
|
|
*
|
|
* Based on "tls.c" from project "mpop" by Martin Lambers
|
|
* Original source code available on http://sourceforge.net/projects/mpop/
|
|
*
|
|
* Copyright (C) 2000, 2003, 2004, 2005, 2006, 2007
|
|
* Martin Lambers <marlam@marlam.de>
|
|
*
|
|
* Copyright (C) 2008 Andrei Prygounkov <hugbug@users.sourceforge.net>
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
|
*
|
|
* $Revision$
|
|
* $Date$
|
|
*
|
|
*/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
# include "config.h"
|
|
#endif
|
|
|
|
#ifndef DISABLE_TLS
|
|
|
|
#define SKIP_DEFAULT_WINDOWS_HEADERS
|
|
#ifdef WIN32
|
|
#include "win32.h"
|
|
#endif
|
|
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <cstdio>
|
|
#ifdef WIN32
|
|
#include <winsock2.h>
|
|
#include <ws2tcpip.h>
|
|
#else
|
|
#include <strings.h>
|
|
#endif
|
|
#include <ctype.h>
|
|
#include <stdarg.h>
|
|
#include <limits.h>
|
|
#include <time.h>
|
|
#include <errno.h>
|
|
#include <list>
|
|
|
|
#include "nzbget.h"
|
|
|
|
#ifdef HAVE_LIBGNUTLS
|
|
#include <gnutls/gnutls.h>
|
|
#include <gnutls/x509.h>
|
|
#include <gcrypt.h>
|
|
#endif /* HAVE_LIBGNUTLS */
|
|
#ifdef HAVE_OPENSSL
|
|
#include <openssl/ssl.h>
|
|
#include <openssl/x509.h>
|
|
#include <openssl/x509v3.h>
|
|
#include <openssl/err.h>
|
|
#include <openssl/rand.h>
|
|
#include <openssl/evp.h>
|
|
#endif /* HAVE_OPENSSL */
|
|
|
|
#ifdef HAVE_LIBIDN
|
|
# include <idna.h>
|
|
#endif
|
|
|
|
#include "TLS.h"
|
|
#include "Thread.h"
|
|
|
|
/**
|
|
* Substitutes for xasprintf, xmalloc, xstrdup and _() to remove dependencies from gnulib
|
|
*/
|
|
#define xmalloc (char*)malloc
|
|
#define xstrdup strdup
|
|
#define _(a) (a)
|
|
|
|
char* xasprintf(const char* msg, ...)
|
|
{
|
|
char* szResult = (char*)malloc(2048); // should be enough for all messages
|
|
|
|
va_list ap;
|
|
va_start(ap, msg);
|
|
vsnprintf(szResult, 2048, msg, ap);
|
|
szResult[2048-1] = '\0';
|
|
va_end(ap);
|
|
|
|
return szResult;
|
|
}
|
|
/* End Substitutes */
|
|
|
|
|
|
#ifdef HAVE_LIBGNUTLS
|
|
|
|
/**
|
|
* Mutexes for gcryptlib
|
|
*/
|
|
|
|
typedef std::list<Mutex*> Mutexes;
|
|
Mutexes* g_pGCryptLibMutexes;
|
|
|
|
static int gcry_mutex_init(void **priv)
|
|
{
|
|
Mutex* pMutex = new Mutex();
|
|
g_pGCryptLibMutexes->push_back(pMutex);
|
|
*priv = pMutex;
|
|
return 0;
|
|
}
|
|
|
|
static int gcry_mutex_destroy(void **lock)
|
|
{
|
|
Mutex* pMutex = ((Mutex*)*lock);
|
|
g_pGCryptLibMutexes->remove(pMutex);
|
|
delete pMutex;
|
|
return 0;
|
|
}
|
|
|
|
static int gcry_mutex_lock(void **lock)
|
|
{
|
|
((Mutex*)*lock)->Lock();
|
|
return 0;
|
|
}
|
|
|
|
static int gcry_mutex_unlock(void **lock)
|
|
{
|
|
((Mutex*)*lock)->Unlock();
|
|
return 0;
|
|
}
|
|
|
|
static struct gcry_thread_cbs gcry_threads_Mutex =
|
|
{ GCRY_THREAD_OPTION_USER, NULL,
|
|
gcry_mutex_init, gcry_mutex_destroy,
|
|
gcry_mutex_lock, gcry_mutex_unlock,
|
|
NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL };
|
|
|
|
#endif /* HAVE_LIBGNUTLS */
|
|
|
|
/*
|
|
* tls_clear()
|
|
*
|
|
* see tls.h
|
|
*/
|
|
|
|
void tls_clear(tls_t *tls)
|
|
{
|
|
tls->have_trust_file = 0;
|
|
tls->is_active = 0;
|
|
}
|
|
|
|
|
|
/*
|
|
* seed_prng()
|
|
*
|
|
* Seeds the OpenSSL random number generator.
|
|
* Used error codes: TLS_ESEED
|
|
*/
|
|
|
|
#ifdef HAVE_OPENSSL
|
|
int seed_prng(char **errstr)
|
|
{
|
|
char randfile[512];
|
|
time_t t;
|
|
int prn;
|
|
int system_prn_max = 1024;
|
|
|
|
/* Most systems have /dev/random or other sources of random numbers that
|
|
* OpenSSL can use to seed itself.
|
|
* The only system I know of where we must seed the PRNG is DOS.
|
|
*/
|
|
if (!RAND_status())
|
|
{
|
|
if (!RAND_file_name(randfile, 512))
|
|
{
|
|
*errstr = xasprintf(_("no environment variables RANDFILE or HOME, "
|
|
"or filename of rand file too long"));
|
|
return TLS_ESEED;
|
|
}
|
|
if (RAND_load_file(randfile, -1) < 1)
|
|
{
|
|
*errstr = xasprintf(_("%s: input error"), randfile);
|
|
return TLS_ESEED;
|
|
}
|
|
/* Seed in time. I can't think of other "random" things on DOS
|
|
* systems. */
|
|
if ((t = time(NULL)) < 0)
|
|
{
|
|
*errstr = xasprintf(_("cannot get system time: %s"),
|
|
strerror(errno));
|
|
return TLS_ESEED;
|
|
}
|
|
RAND_seed((unsigned char *)&t, sizeof(time_t));
|
|
/* If the RANDFILE + time is not enough, we fall back to the insecure
|
|
* and stupid method of seeding OpenSSLs PRNG with the systems PRNG. */
|
|
if (!RAND_status())
|
|
{
|
|
srand((unsigned int)(t % UINT_MAX));
|
|
while (!RAND_status() && system_prn_max > 0)
|
|
{
|
|
prn = rand();
|
|
RAND_seed(&prn, sizeof(int));
|
|
system_prn_max--;
|
|
}
|
|
}
|
|
/* Are we happy now? */
|
|
if (!RAND_status())
|
|
{
|
|
*errstr = xasprintf(_("random file + time + pseudo randomness is "
|
|
"not enough, giving up"));
|
|
return TLS_ESEED;
|
|
}
|
|
/* Save a rand file for later usage. We ignore errors here as we can't
|
|
* do anything about them. */
|
|
(void)RAND_write_file(randfile);
|
|
}
|
|
return TLS_EOK;
|
|
}
|
|
#endif /* HAVE_OPENSSL */
|
|
|
|
|
|
/*
|
|
* tls_lib_init()
|
|
*
|
|
* see tls.h
|
|
*/
|
|
|
|
int tls_lib_init(char **errstr)
|
|
{
|
|
#ifdef HAVE_LIBGNUTLS
|
|
int error_code;
|
|
|
|
g_pGCryptLibMutexes = new Mutexes();
|
|
if ((error_code = gcry_control(GCRYCTL_SET_THREAD_CBS, &gcry_threads_Mutex)) != 0)
|
|
{
|
|
*errstr = xasprintf("%s", "Could not initialize libcrypt");
|
|
return TLS_ELIBFAILED;
|
|
}
|
|
|
|
if ((error_code = gnutls_global_init()) != 0)
|
|
{
|
|
*errstr = xasprintf("%s", gnutls_strerror(error_code));
|
|
return TLS_ELIBFAILED;
|
|
}
|
|
|
|
return TLS_EOK;
|
|
#endif /* HAVE_LIBGNUTLS */
|
|
|
|
#ifdef HAVE_OPENSSL
|
|
int e;
|
|
|
|
SSL_load_error_strings();
|
|
SSL_library_init();
|
|
if ((e = seed_prng(errstr)) != TLS_EOK)
|
|
{
|
|
return e;
|
|
}
|
|
|
|
return TLS_EOK;
|
|
#endif /* HAVE_OPENSSL */
|
|
}
|
|
|
|
|
|
/*
|
|
* tls_is_active()
|
|
*
|
|
* see tls.h
|
|
*/
|
|
|
|
int tls_is_active(tls_t *tls)
|
|
{
|
|
return tls->is_active;
|
|
}
|
|
|
|
|
|
/*
|
|
* tls_cert_info_new()
|
|
*/
|
|
|
|
tls_cert_info_t *tls_cert_info_new(void)
|
|
{
|
|
tls_cert_info_t *tci;
|
|
int i;
|
|
|
|
tci = (tls_cert_info_t*)xmalloc(sizeof(tls_cert_info_t));
|
|
for (i = 0; i < 6; i++)
|
|
{
|
|
tci->owner_info[i] = NULL;
|
|
tci->issuer_info[i] = NULL;
|
|
}
|
|
|
|
return tci;
|
|
}
|
|
|
|
|
|
/*
|
|
* tls_cert_info_free()
|
|
*/
|
|
|
|
void tls_cert_info_free(tls_cert_info_t *tci)
|
|
{
|
|
int i;
|
|
|
|
if (tci)
|
|
{
|
|
for (i = 0; i < 6; i++)
|
|
{
|
|
free(tci->owner_info[i]);
|
|
free(tci->issuer_info[i]);
|
|
}
|
|
free(tci);
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* asn1time_to_time_t() [OpenSSL only]
|
|
*
|
|
* Convert a ASN1 time string ([YY]YYMMDDhhmm[ss](Z)) into a time_t.
|
|
* The flag 'is_utc' indicates whether the string is in UTC or GENERALIZED
|
|
* format. GENERALIZED means a 4 digit year.
|
|
* In case of invalid strings or over-/underflows, 1 is returned, and the value
|
|
* of 't' is undefined. On success, 0 is returned.
|
|
*
|
|
* This code uses many ideas from GnuTLS code (lib/x509/common.c).
|
|
* The transformation of struct tm to time_t is based on code from Russ Allbery
|
|
* (rra@stanford.edu), who wrote a mktime_utc function and placed it under
|
|
* public domain.
|
|
*/
|
|
|
|
#ifdef HAVE_OPENSSL
|
|
int is_leap(int year)
|
|
{
|
|
return (((year) % 4) == 0 && (((year) % 100) != 0 || ((year) % 400) == 0));
|
|
}
|
|
|
|
int asn1time_to_time_t(const char *asn1time, int is_utc, time_t *t)
|
|
{
|
|
size_t len;
|
|
int i;
|
|
size_t j;
|
|
const char *p;
|
|
char xx[3];
|
|
char xxxx[5];
|
|
const int monthdays[] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
|
|
struct tm tm;
|
|
|
|
len = strlen(asn1time);
|
|
if ((is_utc && len < 10) || (!is_utc && len < 12))
|
|
{
|
|
goto error_exit;
|
|
}
|
|
for (j = 0; j < len - 1; j++)
|
|
{
|
|
if (!isdigit((unsigned char)asn1time[j]))
|
|
{
|
|
goto error_exit;
|
|
}
|
|
}
|
|
|
|
xx[2] = '\0';
|
|
xxxx[4] = '\0';
|
|
p = asn1time;
|
|
if (is_utc)
|
|
{
|
|
strncpy(xx, p, 2);
|
|
tm.tm_year = atoi(xx);
|
|
tm.tm_year += (tm.tm_year > 49) ? 1900 : 2000;
|
|
p += 2;
|
|
}
|
|
else
|
|
{
|
|
strncpy(xxxx, p, 4);
|
|
tm.tm_year = atoi(xxxx);
|
|
p += 4;
|
|
}
|
|
strncpy(xx, p, 2);
|
|
tm.tm_mon = atoi(xx) - 1;
|
|
p += 2;
|
|
strncpy(xx, p, 2);
|
|
tm.tm_mday = atoi(xx);
|
|
p += 2;
|
|
strncpy(xx, p, 2);
|
|
tm.tm_hour = atoi(xx);
|
|
p += 2;
|
|
strncpy(xx, p, 2);
|
|
tm.tm_min = atoi(xx);
|
|
p += 2;
|
|
if (isdigit((unsigned char)(*p)))
|
|
{
|
|
strncpy(xx, p, 2);
|
|
tm.tm_sec = atoi(xx);
|
|
}
|
|
else
|
|
{
|
|
tm.tm_sec = 0;
|
|
}
|
|
|
|
/* basic check for 32 bit time_t overflows. */
|
|
if (sizeof(time_t) <= 4 && tm.tm_year >= 2038)
|
|
{
|
|
goto error_exit;
|
|
}
|
|
if (tm.tm_year < 1970 || tm.tm_mon < 0 || tm.tm_mon > 11)
|
|
{
|
|
goto error_exit;
|
|
}
|
|
*t = 0;
|
|
for (i = 1970; i < tm.tm_year; i++)
|
|
{
|
|
*t += 365 + (is_leap(i) ? 1 : 0);
|
|
}
|
|
for (i = 0; i < tm.tm_mon; i++)
|
|
{
|
|
*t += monthdays[i];
|
|
}
|
|
if (tm.tm_mon > 1 && is_leap(tm.tm_year))
|
|
{
|
|
*t += 1;
|
|
}
|
|
*t = 24 * (*t + tm.tm_mday - 1) + tm.tm_hour;
|
|
*t = 60 * (*t) + tm.tm_min;
|
|
*t = 60 * (*t) + tm.tm_sec;
|
|
|
|
return 0;
|
|
|
|
error_exit:
|
|
return 1;
|
|
}
|
|
#endif /* HAVE_OPENSSL */
|
|
|
|
|
|
/*
|
|
* tls_cert_info_get()
|
|
*
|
|
* see tls.h
|
|
*/
|
|
|
|
int tls_cert_info_get(tls_t *tls, tls_cert_info_t *tci, char **errstr)
|
|
{
|
|
#ifdef HAVE_LIBGNUTLS
|
|
const gnutls_datum_t *cert_list;
|
|
unsigned int cert_list_size;
|
|
gnutls_x509_crt_t cert;
|
|
size_t size;
|
|
const char *oid[6] = { GNUTLS_OID_X520_COMMON_NAME,
|
|
GNUTLS_OID_X520_ORGANIZATION_NAME,
|
|
GNUTLS_OID_X520_ORGANIZATIONAL_UNIT_NAME,
|
|
GNUTLS_OID_X520_LOCALITY_NAME,
|
|
GNUTLS_OID_X520_STATE_OR_PROVINCE_NAME,
|
|
GNUTLS_OID_X520_COUNTRY_NAME };
|
|
int i;
|
|
int e;
|
|
char *p;
|
|
const char *errmsg;
|
|
|
|
errmsg = _("cannot get TLS certificate info");
|
|
if (!(cert_list =
|
|
gnutls_certificate_get_peers(tls->session, &cert_list_size))
|
|
|| cert_list_size == 0)
|
|
{
|
|
*errstr = xasprintf(_("%s: no certificate was found"), errmsg);
|
|
return TLS_ECERT;
|
|
}
|
|
if (gnutls_x509_crt_init(&cert) != 0)
|
|
{
|
|
*errstr = xasprintf(_("%s: cannot initialize certificate structure"),
|
|
errmsg);
|
|
return TLS_ECERT;
|
|
}
|
|
if (gnutls_x509_crt_import(cert, &cert_list[0], GNUTLS_X509_FMT_DER) != 0)
|
|
{
|
|
*errstr = xasprintf(_("%s: error parsing certificate"), errmsg);
|
|
gnutls_x509_crt_deinit(cert);
|
|
return TLS_ECERT;
|
|
}
|
|
|
|
/* certificate information */
|
|
size = 20;
|
|
if (gnutls_x509_crt_get_fingerprint(cert, GNUTLS_DIG_SHA,
|
|
tci->sha1_fingerprint, &size) != 0)
|
|
{
|
|
*errstr = xasprintf(_("%s: error getting SHA1 fingerprint"), errmsg);
|
|
gnutls_x509_crt_deinit(cert);
|
|
return TLS_ECERT;
|
|
}
|
|
size = 16;
|
|
if (gnutls_x509_crt_get_fingerprint(cert, GNUTLS_DIG_MD5,
|
|
tci->md5_fingerprint, &size) != 0)
|
|
{
|
|
*errstr = xasprintf(_("%s: error getting MD5 fingerprint"), errmsg);
|
|
gnutls_x509_crt_deinit(cert);
|
|
return TLS_ECERT;
|
|
}
|
|
if ((tci->activation_time = gnutls_x509_crt_get_activation_time(cert)) < 0)
|
|
{
|
|
*errstr = xasprintf(_("%s: cannot get activation time"), errmsg);
|
|
gnutls_x509_crt_deinit(cert);
|
|
return TLS_ECERT;
|
|
}
|
|
if ((tci->expiration_time = gnutls_x509_crt_get_expiration_time(cert)) < 0)
|
|
{
|
|
*errstr = xasprintf(_("%s: cannot get expiration time"), errmsg);
|
|
gnutls_x509_crt_deinit(cert);
|
|
return TLS_ECERT;
|
|
}
|
|
|
|
/* owner information */
|
|
for (i = 0; i < 6; i++)
|
|
{
|
|
size = 0;
|
|
e = gnutls_x509_crt_get_dn_by_oid(cert, oid[i], 0, 0, NULL, &size);
|
|
if (e == GNUTLS_E_SHORT_MEMORY_BUFFER)
|
|
{
|
|
p = xmalloc(size);
|
|
e = gnutls_x509_crt_get_dn_by_oid(cert, oid[i], 0, 0, p, &size);
|
|
if (e == 0)
|
|
{
|
|
tci->owner_info[i] = p;
|
|
}
|
|
else
|
|
{
|
|
free(p);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* issuer information */
|
|
for (i = 0; i < 6; i++)
|
|
{
|
|
size = 0;
|
|
e = gnutls_x509_crt_get_issuer_dn_by_oid(
|
|
cert, oid[i], 0, 0, NULL, &size);
|
|
if (e == GNUTLS_E_SHORT_MEMORY_BUFFER)
|
|
{
|
|
p = xmalloc(size);
|
|
e = gnutls_x509_crt_get_issuer_dn_by_oid(
|
|
cert, oid[i], 0, 0, p, &size);
|
|
if (e == 0)
|
|
{
|
|
tci->issuer_info[i] = p;
|
|
}
|
|
else
|
|
{
|
|
free(p);
|
|
}
|
|
}
|
|
}
|
|
|
|
gnutls_x509_crt_deinit(cert);
|
|
return TLS_EOK;
|
|
#endif /* HAVE_LIBGNUTLS */
|
|
|
|
#ifdef HAVE_OPENSSL
|
|
X509 *x509cert;
|
|
X509_NAME *x509_subject;
|
|
X509_NAME *x509_issuer;
|
|
ASN1_TIME *asn1time;
|
|
int nid[6] = { NID_commonName,
|
|
NID_organizationName,
|
|
NID_organizationalUnitName,
|
|
NID_localityName,
|
|
NID_stateOrProvinceName,
|
|
NID_countryName };
|
|
int size;
|
|
unsigned int usize;
|
|
char *p;
|
|
int i;
|
|
const char *errmsg;
|
|
|
|
errmsg = _("cannot get TLS certificate info");
|
|
if (!(x509cert = SSL_get_peer_certificate(tls->ssl)))
|
|
{
|
|
*errstr = xasprintf(_("%s: no certificate was found"), errmsg);
|
|
return TLS_ECERT;
|
|
}
|
|
if (!(x509_subject = X509_get_subject_name(x509cert)))
|
|
{
|
|
*errstr = xasprintf(_("%s: cannot get certificate subject"), errmsg);
|
|
X509_free(x509cert);
|
|
return TLS_ECERT;
|
|
}
|
|
if (!(x509_issuer = X509_get_issuer_name(x509cert)))
|
|
{
|
|
*errstr = xasprintf(_("%s: cannot get certificate issuer"), errmsg);
|
|
X509_free(x509cert);
|
|
return TLS_ECERT;
|
|
}
|
|
|
|
/* certificate information */
|
|
usize = 20;
|
|
if (!X509_digest(x509cert, EVP_sha1(), tci->sha1_fingerprint, &usize))
|
|
{
|
|
*errstr = xasprintf(_("%s: error getting SHA1 fingerprint"), errmsg);
|
|
return TLS_ECERT;
|
|
}
|
|
usize = 16;
|
|
if (!X509_digest(x509cert, EVP_md5(), tci->md5_fingerprint, &usize))
|
|
{
|
|
*errstr = xasprintf(_("%s: error getting MD5 fingerprint"), errmsg);
|
|
return TLS_ECERT;
|
|
}
|
|
asn1time = X509_get_notBefore(x509cert);
|
|
if (asn1time_to_time_t((char *)asn1time->data,
|
|
(asn1time->type != V_ASN1_GENERALIZEDTIME),
|
|
&(tci->activation_time)) != 0)
|
|
{
|
|
*errstr = xasprintf(_("%s: cannot get activation time"), errmsg);
|
|
X509_free(x509cert);
|
|
tls_cert_info_free(tci);
|
|
return TLS_ECERT;
|
|
}
|
|
asn1time = X509_get_notAfter(x509cert);
|
|
if (asn1time_to_time_t((char *)asn1time->data,
|
|
(asn1time->type != V_ASN1_GENERALIZEDTIME),
|
|
&(tci->expiration_time)) != 0)
|
|
{
|
|
*errstr = xasprintf(_("%s: cannot get expiration time"), errmsg);
|
|
X509_free(x509cert);
|
|
tls_cert_info_free(tci);
|
|
return TLS_ECERT;
|
|
}
|
|
|
|
/* owner information */
|
|
for (i = 0; i < 6; i++)
|
|
{
|
|
size = X509_NAME_get_text_by_NID(x509_subject, nid[i], NULL, 0);
|
|
size++;
|
|
p = xmalloc((size_t)size);
|
|
if (X509_NAME_get_text_by_NID(x509_subject, nid[i], p, size) != -1)
|
|
{
|
|
tci->owner_info[i] = p;
|
|
}
|
|
else
|
|
{
|
|
free(p);
|
|
}
|
|
}
|
|
|
|
/* issuer information */
|
|
for (i = 0; i < 6; i++)
|
|
{
|
|
size = X509_NAME_get_text_by_NID(x509_issuer, nid[i], NULL, 0);
|
|
size++;
|
|
p = xmalloc((size_t)size);
|
|
if (X509_NAME_get_text_by_NID(x509_issuer, nid[i], p, size) != -1)
|
|
{
|
|
tci->issuer_info[i] = p;
|
|
}
|
|
else
|
|
{
|
|
free(p);
|
|
}
|
|
}
|
|
|
|
X509_free(x509cert);
|
|
return TLS_EOK;
|
|
#endif /* HAVE_OPENSSL */
|
|
}
|
|
|
|
|
|
/*
|
|
* [OpenSSL only] hostname_match()
|
|
*
|
|
* Compares the hostname with the name in the certificate. The certificate name
|
|
* may include a wildcard as the leftmost domain component (its first two
|
|
* characters are "*." in this case).
|
|
*
|
|
* Returns 1 if the names match, 0 otherwise.
|
|
*
|
|
* This is the same form of matching that gnutls_x509_crt_check_hostname() from
|
|
* GnuTLS 1.2.0 uses.
|
|
* It conforms to RFC2595 (Using TLS with IMAP, POP3 and ACAP), section 2.4.
|
|
* RFC2818 (HTTP over TLS), section 3.1 says that `f*.com matches foo.com'. This
|
|
* function does not allow that.
|
|
* RFC3207 (SMTP Service Extension for Secure SMTP over Transport Layer
|
|
* Security), section 4.1 says nothing more than `A SMTP client would probably
|
|
* only want to authenticate an SMTP server whose server certificate has a
|
|
* domain name that is the domain name that the client thought it was connecting
|
|
* to'.
|
|
*/
|
|
|
|
#ifdef HAVE_OPENSSL
|
|
int hostname_match(const char *hostname, const char *certname)
|
|
{
|
|
const char *cmp1, *cmp2;
|
|
|
|
if (strncmp(certname, "*.", 2) == 0)
|
|
{
|
|
cmp1 = certname + 2;
|
|
cmp2 = strchr(hostname, '.');
|
|
if (!cmp2)
|
|
{
|
|
return 0;
|
|
}
|
|
else
|
|
{
|
|
cmp2++;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
cmp1 = certname;
|
|
cmp2 = hostname;
|
|
}
|
|
|
|
if (*cmp1 == '\0' || *cmp2 == '\0')
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
if (strcasecmp(cmp1, cmp2) != 0)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
#endif /* HAVE_OPENSSL */
|
|
|
|
|
|
/*
|
|
* tls_check_cert()
|
|
*
|
|
* If the 'verify' flag is set, perform a real verification of the peer's
|
|
* certificate. If this succeeds, the connection can be considered secure.
|
|
* If the 'verify' flag is not set, perform only a few sanity checks of the
|
|
* peer's certificate. You cannot trust the connection when this succeeds.
|
|
* Used error codes: TLS_ECERT
|
|
*/
|
|
|
|
int tls_check_cert(tls_t *tls, const char *hostname, int verify, char **errstr)
|
|
{
|
|
#ifdef HAVE_LIBGNUTLS
|
|
int error_code;
|
|
const char *error_msg;
|
|
unsigned int status;
|
|
const gnutls_datum_t *cert_list;
|
|
unsigned int cert_list_size;
|
|
unsigned int i;
|
|
gnutls_x509_crt_t cert;
|
|
time_t t1, t2;
|
|
#ifdef HAVE_LIBIDN
|
|
char *hostname_ascii;
|
|
#endif
|
|
|
|
if (verify)
|
|
{
|
|
error_msg = _("TLS certificate verification failed");
|
|
}
|
|
else
|
|
{
|
|
error_msg = _("TLS certificate check failed");
|
|
}
|
|
|
|
/* If 'verify' is true, this function uses the trusted CAs in the
|
|
* credentials structure. So you must have installed one or more CA
|
|
* certificates. */
|
|
gnutls_certificate_set_verify_flags(tls->cred,
|
|
GNUTLS_VERIFY_ALLOW_X509_V1_CA_CRT);
|
|
if ((error_code = gnutls_certificate_verify_peers2(tls->session,
|
|
&status)) != 0)
|
|
{
|
|
*errstr = xasprintf("%s: %s", error_msg, gnutls_strerror(error_code));
|
|
return TLS_ECERT;
|
|
}
|
|
if (verify)
|
|
{
|
|
if (status & GNUTLS_CERT_SIGNER_NOT_FOUND)
|
|
{
|
|
*errstr = xasprintf(
|
|
_("%s: the certificate hasn't got a known issuer"),
|
|
error_msg);
|
|
return TLS_ECERT;
|
|
}
|
|
if (status & GNUTLS_CERT_INVALID)
|
|
{
|
|
*errstr = xasprintf(_("%s: the certificate is not trusted"),
|
|
error_msg);
|
|
return TLS_ECERT;
|
|
}
|
|
}
|
|
if (status & GNUTLS_CERT_REVOKED)
|
|
{
|
|
*errstr = xasprintf(_("%s: the certificate has been revoked"),
|
|
error_msg);
|
|
return TLS_ECERT;
|
|
}
|
|
if (gnutls_certificate_type_get(tls->session) != GNUTLS_CRT_X509)
|
|
{
|
|
*errstr = xasprintf(_("%s: the certificate type is not X509"),
|
|
error_msg);
|
|
return TLS_ECERT;
|
|
}
|
|
if (!(cert_list = gnutls_certificate_get_peers(
|
|
tls->session, &cert_list_size)))
|
|
{
|
|
*errstr = xasprintf(_("%s: no certificate was found"), error_msg);
|
|
return TLS_ECERT;
|
|
}
|
|
/* Needed to check times: */
|
|
if ((t1 = time(NULL)) < 0)
|
|
{
|
|
*errstr = xasprintf("%s: cannot get system time: %s", error_msg,
|
|
strerror(errno));
|
|
return TLS_ECERT;
|
|
}
|
|
/* Check the certificate chain. All certificates in the chain must have
|
|
* valid activation/expiration times. The first certificate in the chain is
|
|
* the host's certificate; it must match the hostname. */
|
|
for (i = 0; i < cert_list_size; i++)
|
|
{
|
|
if (gnutls_x509_crt_init(&cert) < 0)
|
|
{
|
|
*errstr = xasprintf(
|
|
_("%s: cannot initialize certificate structure"),
|
|
error_msg);
|
|
return TLS_ECERT;
|
|
}
|
|
if (gnutls_x509_crt_import(cert, &cert_list[i], GNUTLS_X509_FMT_DER)
|
|
< 0)
|
|
{
|
|
*errstr = xasprintf(_("%s: error parsing certificate %u of %u"),
|
|
error_msg, i + 1, cert_list_size);
|
|
return TLS_ECERT;
|
|
}
|
|
/* Check hostname */
|
|
if (i == 0)
|
|
{
|
|
#ifdef HAVE_LIBIDN
|
|
if (idna_to_ascii_lz(hostname, &hostname_ascii, 0) == IDNA_SUCCESS)
|
|
{
|
|
if (!gnutls_x509_crt_check_hostname(cert, hostname_ascii))
|
|
{
|
|
*errstr = xasprintf(_("%s: the certificate owner does not "
|
|
"match hostname %s"), error_msg, hostname);
|
|
free(hostname_ascii);
|
|
return TLS_ECERT;
|
|
}
|
|
free(hostname_ascii);
|
|
}
|
|
else
|
|
#endif
|
|
if (!gnutls_x509_crt_check_hostname(cert, hostname))
|
|
{
|
|
*errstr = xasprintf(_("%s: the certificate owner does not "
|
|
"match hostname %s"), error_msg, hostname);
|
|
return TLS_ECERT;
|
|
}
|
|
}
|
|
/* Check certificate times */
|
|
if ((t2 = gnutls_x509_crt_get_activation_time(cert)) < 0)
|
|
{
|
|
*errstr = xasprintf(_("%s: cannot get activation time for "
|
|
"certificate %u of %u"),
|
|
error_msg, i + 1, cert_list_size);
|
|
return TLS_ECERT;
|
|
}
|
|
if (t2 > t1)
|
|
{
|
|
*errstr = xasprintf(
|
|
_("%s: certificate %u of %u is not yet activated"),
|
|
error_msg, i + 1, cert_list_size);
|
|
return TLS_ECERT;
|
|
}
|
|
if ((t2 = gnutls_x509_crt_get_expiration_time(cert)) < 0)
|
|
{
|
|
*errstr = xasprintf(_("%s: cannot get expiration time for "
|
|
"certificate %u of %u"),
|
|
error_msg, i + 1, cert_list_size);
|
|
return TLS_ECERT;
|
|
}
|
|
if (t2 < t1)
|
|
{
|
|
*errstr = xasprintf(_("%s: certificate %u of %u has expired"),
|
|
error_msg, i + 1, cert_list_size);
|
|
return TLS_ECERT;
|
|
}
|
|
gnutls_x509_crt_deinit(cert);
|
|
}
|
|
|
|
return TLS_EOK;
|
|
#endif /* HAVE_LIBGNUTLS */
|
|
|
|
#ifdef HAVE_OPENSSL
|
|
X509 *x509cert;
|
|
long status;
|
|
const char *error_msg;
|
|
int i;
|
|
/* hostname in ASCII format: */
|
|
char *hostname_ascii;
|
|
/* needed to get the common name: */
|
|
X509_NAME *x509_subject;
|
|
char *buf;
|
|
int bufsize;
|
|
/* needed to get the DNS subjectAltNames: */
|
|
STACK *subj_alt_names;
|
|
int subj_alt_names_count;
|
|
GENERAL_NAME *subj_alt_name;
|
|
/* did we find a name matching hostname? */
|
|
int match_found;
|
|
|
|
|
|
if (verify)
|
|
{
|
|
error_msg = _("TLS certificate verification failed");
|
|
}
|
|
else
|
|
{
|
|
error_msg = _("TLS certificate check failed");
|
|
}
|
|
|
|
/* Get certificate */
|
|
if (!(x509cert = SSL_get_peer_certificate(tls->ssl)))
|
|
{
|
|
*errstr = xasprintf(_("%s: no certificate was sent"), error_msg);
|
|
return TLS_ECERT;
|
|
}
|
|
|
|
/* Get result of OpenSSL's default verify function */
|
|
if ((status = SSL_get_verify_result(tls->ssl)) != X509_V_OK)
|
|
{
|
|
if (verify || (status != X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY
|
|
&& status != X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT
|
|
&& status != X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN))
|
|
{
|
|
*errstr = xasprintf("%s: %s", error_msg,
|
|
X509_verify_cert_error_string(status));
|
|
X509_free(x509cert);
|
|
return TLS_ECERT;
|
|
}
|
|
}
|
|
|
|
/* Check if 'hostname' matches the one of the subjectAltName extensions of
|
|
* type DNS or the Common Name (CN). */
|
|
|
|
#ifdef HAVE_LIBIDN
|
|
if (idna_to_ascii_lz(hostname, &hostname_ascii, 0) != IDNA_SUCCESS)
|
|
{
|
|
hostname_ascii = xstrdup(hostname);
|
|
}
|
|
#else
|
|
hostname_ascii = xstrdup(hostname);
|
|
#endif
|
|
|
|
/* Try the DNS subjectAltNames. */
|
|
match_found = 0;
|
|
if ((subj_alt_names =
|
|
(STACK*)X509_get_ext_d2i(x509cert, NID_subject_alt_name, NULL, NULL)))
|
|
{
|
|
subj_alt_names_count = sk_GENERAL_NAME_num(subj_alt_names);
|
|
for (i = 0; i < subj_alt_names_count; i++)
|
|
{
|
|
subj_alt_name = sk_GENERAL_NAME_value(subj_alt_names, i);
|
|
if (subj_alt_name->type == GEN_DNS)
|
|
{
|
|
if ((match_found = hostname_match(hostname_ascii,
|
|
(char *)(subj_alt_name->d.ia5->data))))
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (!match_found)
|
|
{
|
|
/* Try the common name */
|
|
if (!(x509_subject = X509_get_subject_name(x509cert)))
|
|
{
|
|
*errstr = xasprintf(_("%s: cannot get certificate subject"),
|
|
error_msg);
|
|
X509_free(x509cert);
|
|
return TLS_ECERT;
|
|
}
|
|
bufsize = X509_NAME_get_text_by_NID(x509_subject, NID_commonName,
|
|
NULL, 0);
|
|
bufsize++;
|
|
buf = xmalloc((size_t)bufsize);
|
|
if (X509_NAME_get_text_by_NID(x509_subject, NID_commonName,
|
|
buf, bufsize) == -1)
|
|
{
|
|
*errstr = xasprintf(_("%s: cannot get certificate common name"),
|
|
error_msg);
|
|
X509_free(x509cert);
|
|
free(buf);
|
|
return TLS_ECERT;
|
|
}
|
|
match_found = hostname_match(hostname_ascii, buf);
|
|
free(buf);
|
|
}
|
|
X509_free(x509cert);
|
|
free(hostname_ascii);
|
|
|
|
if (!match_found)
|
|
{
|
|
*errstr = xasprintf(
|
|
_("%s: the certificate owner does not match hostname %s"),
|
|
error_msg, hostname);
|
|
return TLS_ECERT;
|
|
}
|
|
|
|
return TLS_EOK;
|
|
#endif /* HAVE_OPENSSL */
|
|
}
|
|
|
|
|
|
/*
|
|
* tls_init()
|
|
*
|
|
* see tls.h
|
|
*/
|
|
|
|
int tls_init(tls_t *tls, const char *key_file, const char *cert_file,
|
|
const char *trust_file, int force_sslv3, char **errstr)
|
|
{
|
|
#ifdef HAVE_LIBGNUTLS
|
|
int error_code;
|
|
|
|
if ((error_code = gnutls_init(&tls->session, GNUTLS_CLIENT)) != 0)
|
|
{
|
|
*errstr = xasprintf(_("cannot initialize TLS Session: %s"),
|
|
gnutls_strerror(error_code));
|
|
return TLS_ELIBFAILED;
|
|
}
|
|
if ((error_code = gnutls_set_default_priority(tls->session)) != 0)
|
|
{
|
|
*errstr = xasprintf(_("cannot set priorities on TLS Session: %s"),
|
|
gnutls_strerror(error_code));
|
|
gnutls_deinit(tls->session);
|
|
return TLS_ELIBFAILED;
|
|
}
|
|
if (force_sslv3)
|
|
{
|
|
const int force_sslv3_proto_prio[2] = { GNUTLS_SSL3, 0 };
|
|
if ((error_code = gnutls_protocol_set_priority(tls->session,
|
|
force_sslv3_proto_prio)) != 0)
|
|
{
|
|
*errstr = xasprintf(_("cannot force SSLv3: %s"),
|
|
gnutls_strerror(error_code));
|
|
gnutls_deinit(tls->session);
|
|
return TLS_ELIBFAILED;
|
|
}
|
|
}
|
|
if ((error_code = gnutls_certificate_allocate_credentials(&tls->cred)) < 0)
|
|
{
|
|
*errstr = xasprintf(
|
|
_("cannot allocate certificate for TLS Session: %s"),
|
|
gnutls_strerror(error_code));
|
|
gnutls_deinit(tls->session);
|
|
return TLS_ELIBFAILED;
|
|
}
|
|
if (key_file && cert_file)
|
|
{
|
|
if ((error_code = gnutls_certificate_set_x509_key_file(tls->cred,
|
|
cert_file, key_file, GNUTLS_X509_FMT_PEM)) < 0)
|
|
{
|
|
*errstr = xasprintf(_("cannot set X509 key file %s and/or "
|
|
"X509 cert file %s for TLS Session: %s"),
|
|
key_file, cert_file, gnutls_strerror(error_code));
|
|
gnutls_deinit(tls->session);
|
|
gnutls_certificate_free_credentials(tls->cred);
|
|
return TLS_EFILE;
|
|
}
|
|
}
|
|
if (trust_file)
|
|
{
|
|
if ((error_code = gnutls_certificate_set_x509_trust_file(
|
|
tls->cred, trust_file, GNUTLS_X509_FMT_PEM)) <= 0)
|
|
{
|
|
*errstr = xasprintf(
|
|
_("cannot set X509 trust file %s for TLS Session: %s"),
|
|
trust_file, gnutls_strerror(error_code));
|
|
gnutls_deinit(tls->session);
|
|
gnutls_certificate_free_credentials(tls->cred);
|
|
return TLS_EFILE;
|
|
}
|
|
tls->have_trust_file = 1;
|
|
}
|
|
if ((error_code = gnutls_credentials_set(tls->session,
|
|
GNUTLS_CRD_CERTIFICATE, tls->cred)) < 0)
|
|
{
|
|
*errstr = xasprintf(_("cannot set credentials for TLS Session: %s"),
|
|
gnutls_strerror(error_code));
|
|
gnutls_deinit(tls->session);
|
|
gnutls_certificate_free_credentials(tls->cred);
|
|
return TLS_ELIBFAILED;
|
|
}
|
|
return TLS_EOK;
|
|
|
|
#endif /* HAVE_LIBGNUTLS */
|
|
|
|
#ifdef HAVE_OPENSSL
|
|
|
|
SSL_METHOD *ssl_method = NULL;
|
|
|
|
ssl_method = force_sslv3 ? SSLv3_client_method() : SSLv23_client_method();
|
|
if (!ssl_method)
|
|
{
|
|
*errstr = xasprintf(_("cannot set TLS method"));
|
|
return TLS_ELIBFAILED;
|
|
}
|
|
if (!(tls->ssl_ctx = SSL_CTX_new(ssl_method)))
|
|
{
|
|
*errstr = xasprintf(_("cannot create TLS context: %s"),
|
|
ERR_error_string(ERR_get_error(), NULL));
|
|
return TLS_ELIBFAILED;
|
|
}
|
|
/* SSLv2 has known flaws. Disable it. */
|
|
(void)SSL_CTX_set_options(tls->ssl_ctx, SSL_OP_NO_SSLv2);
|
|
if (key_file && cert_file)
|
|
{
|
|
if (SSL_CTX_use_PrivateKey_file(
|
|
tls->ssl_ctx, key_file, SSL_FILETYPE_PEM) != 1)
|
|
{
|
|
*errstr = xasprintf(_("cannot load key file %s: %s"),
|
|
key_file, ERR_error_string(ERR_get_error(), NULL));
|
|
SSL_CTX_free(tls->ssl_ctx);
|
|
tls->ssl_ctx = NULL;
|
|
return TLS_EFILE;
|
|
}
|
|
if (SSL_CTX_use_certificate_chain_file(tls->ssl_ctx, cert_file) != 1)
|
|
{
|
|
*errstr = xasprintf(_("cannot load certificate file %s: %s"),
|
|
cert_file, ERR_error_string(ERR_get_error(), NULL));
|
|
SSL_CTX_free(tls->ssl_ctx);
|
|
tls->ssl_ctx = NULL;
|
|
return TLS_EFILE;
|
|
}
|
|
}
|
|
if (trust_file)
|
|
{
|
|
if (SSL_CTX_load_verify_locations(tls->ssl_ctx, trust_file, NULL) != 1)
|
|
{
|
|
*errstr = xasprintf(_("cannot load trust file %s: %s"),
|
|
trust_file, ERR_error_string(ERR_get_error(), NULL));
|
|
SSL_CTX_free(tls->ssl_ctx);
|
|
tls->ssl_ctx = NULL;
|
|
return TLS_EFILE;
|
|
}
|
|
tls->have_trust_file = 1;
|
|
}
|
|
if (!(tls->ssl = SSL_new(tls->ssl_ctx)))
|
|
{
|
|
*errstr = xasprintf(_("cannot create a TLS structure: %s"),
|
|
ERR_error_string(ERR_get_error(), NULL));
|
|
SSL_CTX_free(tls->ssl_ctx);
|
|
tls->ssl_ctx = NULL;
|
|
return TLS_ELIBFAILED;
|
|
}
|
|
return TLS_EOK;
|
|
|
|
#endif /* HAVE_OPENSSL */
|
|
}
|
|
|
|
|
|
/*
|
|
* openssl_io_error()
|
|
*
|
|
* Used only internally by the OpenSSL code.
|
|
*
|
|
* Construct an error line according to 'error_code' (which originates from an
|
|
* SSL_read(), SSL_write() or SSL_connect() operation) and 'error_code2' (which
|
|
* originates from an SSL_get_error() call with 'error_code' as its argument).
|
|
* The line will read: "error_string: error_reason". 'error_string' is given by
|
|
* the calling function, this function finds out 'error_reason'.
|
|
* The resulting string will be returned in an allocated string.
|
|
* OpenSSL error strings are max 120 characters long according to
|
|
* ERR_error_string(3).
|
|
*/
|
|
|
|
#ifdef HAVE_OPENSSL
|
|
char *openssl_io_error(int error_code, int error_code2,
|
|
const char *error_string)
|
|
{
|
|
unsigned long error_code3;
|
|
const char *error_reason;
|
|
|
|
switch (error_code2)
|
|
{
|
|
case SSL_ERROR_SYSCALL:
|
|
error_code3 = ERR_get_error();
|
|
if (error_code3 == 0)
|
|
{
|
|
if (error_code == 0)
|
|
{
|
|
error_reason = _("a protocol violating EOF occured");
|
|
}
|
|
else if (error_code == -1)
|
|
{
|
|
error_reason = strerror(errno);
|
|
}
|
|
else
|
|
{
|
|
error_reason = _("unknown error");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
error_reason = ERR_error_string(error_code3, NULL);
|
|
}
|
|
break;
|
|
|
|
case SSL_ERROR_ZERO_RETURN:
|
|
error_reason = _("the connection was closed unexpectedly");
|
|
break;
|
|
|
|
case SSL_ERROR_SSL:
|
|
error_reason = ERR_error_string(ERR_get_error(), NULL);
|
|
break;
|
|
|
|
case SSL_ERROR_WANT_READ:
|
|
case SSL_ERROR_WANT_WRITE:
|
|
error_reason = _("the operation timed out");
|
|
break;
|
|
|
|
default:
|
|
/* probably SSL_ERROR_NONE */
|
|
error_reason = _("unknown error");
|
|
break;
|
|
}
|
|
return xasprintf("%s: %s", error_string, error_reason);
|
|
}
|
|
#endif /* HAVE_OPENSSL */
|
|
|
|
|
|
/*
|
|
* tls_start()
|
|
*
|
|
* see tls.h
|
|
*/
|
|
|
|
int tls_start(tls_t *tls, int fd, const char *hostname, int no_certcheck,
|
|
tls_cert_info_t *tci, char **errstr)
|
|
{
|
|
#ifdef HAVE_LIBGNUTLS
|
|
int error_code;
|
|
|
|
gnutls_transport_set_ptr(tls->session, (gnutls_transport_ptr_t)(size_t)fd);
|
|
if ((error_code = gnutls_handshake(tls->session)) < 0)
|
|
{
|
|
if (error_code == GNUTLS_E_INTERRUPTED)
|
|
{
|
|
*errstr = xasprintf(_("operation aborted"));
|
|
}
|
|
else if (error_code == GNUTLS_E_AGAIN)
|
|
{
|
|
/* This error message makes more sense than what
|
|
* gnutls_strerror() would return. */
|
|
*errstr = xasprintf(_("TLS handshake failed: %s"),
|
|
_("the operation timed out"));
|
|
}
|
|
else
|
|
{
|
|
*errstr = xasprintf(_("TLS handshake failed: %s"),
|
|
gnutls_strerror(error_code));
|
|
}
|
|
gnutls_deinit(tls->session);
|
|
gnutls_certificate_free_credentials(tls->cred);
|
|
return TLS_EHANDSHAKE;
|
|
}
|
|
if (tci)
|
|
{
|
|
if ((error_code = tls_cert_info_get(tls, tci, errstr)) != TLS_EOK)
|
|
{
|
|
gnutls_deinit(tls->session);
|
|
gnutls_certificate_free_credentials(tls->cred);
|
|
return error_code;
|
|
}
|
|
}
|
|
if (!no_certcheck)
|
|
{
|
|
if ((error_code = tls_check_cert(tls, hostname,
|
|
tls->have_trust_file, errstr)) != TLS_EOK)
|
|
{
|
|
gnutls_deinit(tls->session);
|
|
gnutls_certificate_free_credentials(tls->cred);
|
|
return error_code;
|
|
}
|
|
}
|
|
tls->is_active = 1;
|
|
return TLS_EOK;
|
|
#endif /* HAVE_LIBGNUTLS */
|
|
|
|
#ifdef HAVE_OPENSSL
|
|
int error_code;
|
|
|
|
if (!SSL_set_fd(tls->ssl, fd))
|
|
{
|
|
*errstr = xasprintf(_("cannot set the file descriptor for TLS: %s"),
|
|
ERR_error_string(ERR_get_error(), NULL));
|
|
SSL_free(tls->ssl);
|
|
SSL_CTX_free(tls->ssl_ctx);
|
|
return TLS_ELIBFAILED;
|
|
}
|
|
if ((error_code = SSL_connect(tls->ssl)) < 1)
|
|
{
|
|
if (errno == EINTR
|
|
&& (SSL_get_error(tls->ssl, error_code) == SSL_ERROR_WANT_READ
|
|
|| SSL_get_error(tls->ssl, error_code)
|
|
== SSL_ERROR_WANT_WRITE))
|
|
{
|
|
*errstr = xasprintf(_("operation aborted"));
|
|
}
|
|
else
|
|
{
|
|
*errstr = openssl_io_error(error_code,
|
|
SSL_get_error(tls->ssl, error_code),
|
|
_("TLS handshake failed"));
|
|
}
|
|
SSL_free(tls->ssl);
|
|
SSL_CTX_free(tls->ssl_ctx);
|
|
return TLS_EIO;
|
|
}
|
|
if (tci)
|
|
{
|
|
if ((error_code = tls_cert_info_get(tls, tci, errstr)) != TLS_EOK)
|
|
{
|
|
SSL_free(tls->ssl);
|
|
SSL_CTX_free(tls->ssl_ctx);
|
|
return error_code;
|
|
}
|
|
}
|
|
if (!no_certcheck)
|
|
{
|
|
if ((error_code = tls_check_cert(tls, hostname, tls->have_trust_file,
|
|
errstr)) != TLS_EOK)
|
|
{
|
|
SSL_free(tls->ssl);
|
|
SSL_CTX_free(tls->ssl_ctx);
|
|
return error_code;
|
|
}
|
|
}
|
|
tls->is_active = 1;
|
|
return TLS_EOK;
|
|
#endif /* HAVE_OPENSSL */
|
|
}
|
|
|
|
|
|
/*
|
|
* tls_getbuf()
|
|
*
|
|
* see tls.h
|
|
*/
|
|
|
|
int tls_getbuf(tls_t *tls, char* s, size_t len, size_t* readlen, char **errstr)
|
|
{
|
|
#ifdef HAVE_LIBGNUTLS
|
|
*readlen = 0;
|
|
ssize_t ret;
|
|
|
|
ret = gnutls_record_recv(tls->session, s, len);
|
|
if (ret >= 0)
|
|
{
|
|
*readlen = ret;
|
|
return TLS_EOK;
|
|
}
|
|
else
|
|
{
|
|
if (ret == GNUTLS_E_INTERRUPTED)
|
|
{
|
|
*errstr = xasprintf(_("operation aborted"));
|
|
}
|
|
else if (ret == GNUTLS_E_AGAIN)
|
|
{
|
|
/* This error message makes more sense than what
|
|
* gnutls_strerror() would return. */
|
|
*errstr = xasprintf(_("cannot read from TLS connection: %s"),
|
|
_("the operation timed out"));
|
|
}
|
|
else
|
|
{
|
|
*errstr = xasprintf(_("cannot read from TLS connection: %s"),
|
|
gnutls_strerror((int)ret));
|
|
}
|
|
return TLS_EIO;
|
|
}
|
|
|
|
#endif /* HAVE_LIBGNUTLS */
|
|
|
|
#ifdef HAVE_OPENSSL
|
|
*readlen = 0;
|
|
|
|
int error_code;
|
|
int error_code2;
|
|
|
|
int ret = SSL_read(tls->ssl, s, len);
|
|
|
|
if ((error_code = ret) < 1)
|
|
{
|
|
if ((error_code2 = SSL_get_error(tls->ssl, error_code))
|
|
== SSL_ERROR_NONE)
|
|
{
|
|
return TLS_EOK;
|
|
}
|
|
else
|
|
{
|
|
if (errno == EINTR &&
|
|
(SSL_get_error(tls->ssl, error_code) == SSL_ERROR_WANT_READ
|
|
|| SSL_get_error(tls->ssl, error_code)
|
|
== SSL_ERROR_WANT_WRITE))
|
|
{
|
|
*errstr = xasprintf(_("operation aborted"));
|
|
}
|
|
else
|
|
{
|
|
*errstr = openssl_io_error(error_code, error_code2,
|
|
_("cannot read from TLS connection"));
|
|
}
|
|
return TLS_EIO;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
*readlen = ret;
|
|
return TLS_EOK;
|
|
}
|
|
|
|
#endif /* HAVE_OPENSSL */
|
|
}
|
|
|
|
|
|
/*
|
|
* tls_putbuf()
|
|
*
|
|
* see tls.h
|
|
*/
|
|
|
|
int tls_putbuf(tls_t *tls, const char *s, size_t len, char **errstr)
|
|
{
|
|
#ifdef HAVE_LIBGNUTLS
|
|
|
|
ssize_t ret;
|
|
|
|
if (len < 1)
|
|
{
|
|
/* nothing to be done */
|
|
return TLS_EOK;
|
|
}
|
|
|
|
if ((ret = gnutls_record_send(tls->session, s, len)) < 0)
|
|
{
|
|
if (ret == GNUTLS_E_INTERRUPTED)
|
|
{
|
|
*errstr = xasprintf(_("operation aborted"));
|
|
}
|
|
else if (ret == GNUTLS_E_AGAIN)
|
|
{
|
|
/* This error message makes more sense than what
|
|
* gnutls_strerror() would return. */
|
|
*errstr = xasprintf(_("cannot write to TLS connection: %s"),
|
|
_("the operation timed out"));
|
|
}
|
|
else
|
|
{
|
|
*errstr = xasprintf(_("cannot write to TLS connection: %s"),
|
|
gnutls_strerror((int)ret));
|
|
}
|
|
return TLS_EIO;
|
|
}
|
|
else if ((size_t)ret == len)
|
|
{
|
|
return TLS_EOK;
|
|
}
|
|
else /* 0 <= error_code < len */
|
|
{
|
|
*errstr = xasprintf(_("cannot write to TLS connection: %s"),
|
|
_("unknown error"));
|
|
return TLS_EIO;
|
|
}
|
|
|
|
#endif /* HAVE_LIBGNUTLS */
|
|
|
|
#ifdef HAVE_OPENSSL
|
|
|
|
int error_code;
|
|
|
|
if (len < 1)
|
|
{
|
|
/* nothing to be done */
|
|
return TLS_EOK;
|
|
}
|
|
|
|
if ((error_code = SSL_write(tls->ssl, s, (int)len)) != (int)len)
|
|
{
|
|
if (errno == EINTR
|
|
&& ((SSL_get_error(tls->ssl, error_code) == SSL_ERROR_WANT_READ
|
|
|| SSL_get_error(tls->ssl, error_code)
|
|
== SSL_ERROR_WANT_WRITE)))
|
|
{
|
|
*errstr = xasprintf(_("operation aborted"));
|
|
}
|
|
else
|
|
{
|
|
*errstr = openssl_io_error(error_code,
|
|
SSL_get_error(tls->ssl, error_code),
|
|
_("cannot write to TLS connection"));
|
|
}
|
|
return TLS_EIO;
|
|
}
|
|
|
|
return TLS_EOK;
|
|
|
|
#endif /* HAVE_OPENSSL */
|
|
}
|
|
|
|
|
|
/*
|
|
* tls_close()
|
|
*
|
|
* see tls.h
|
|
*/
|
|
|
|
void tls_close(tls_t *tls)
|
|
{
|
|
if (tls->is_active)
|
|
{
|
|
#ifdef HAVE_LIBGNUTLS
|
|
gnutls_bye(tls->session, GNUTLS_SHUT_WR);
|
|
gnutls_deinit(tls->session);
|
|
gnutls_certificate_free_credentials(tls->cred);
|
|
#endif /* HAVE_LIBGNUTLS */
|
|
#ifdef HAVE_OPENSSL
|
|
SSL_shutdown(tls->ssl);
|
|
SSL_free(tls->ssl);
|
|
SSL_CTX_free(tls->ssl_ctx);
|
|
#endif /* HAVE_OPENSSL */
|
|
}
|
|
tls_clear(tls);
|
|
}
|
|
|
|
|
|
/*
|
|
* tls_lib_deinit()
|
|
*
|
|
* see tls.h
|
|
*/
|
|
|
|
void tls_lib_deinit(void)
|
|
{
|
|
#ifdef HAVE_LIBGNUTLS
|
|
gnutls_global_deinit();
|
|
|
|
// fixing memory leak in gcryptlib
|
|
for (Mutexes::iterator it = g_pGCryptLibMutexes->begin(); it != g_pGCryptLibMutexes->end(); it++)
|
|
{
|
|
delete *it;
|
|
}
|
|
delete g_pGCryptLibMutexes;
|
|
#endif /* HAVE_LIBGNUTLS */
|
|
}
|
|
|
|
#endif
|