/* * Copyright (C) 2010-2015 Free Software Foundation, Inc. * * Author: Nikos Mavrogiannopoulos * * This file is part of GnuTLS. * * The GnuTLS is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * as published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see * */ #include #include #include "gnutls_int.h" #include "errors.h" #include #include #include #include #include #ifdef _WIN32 # include # include # if defined(__MINGW32__) && !defined(__MINGW64__) && __MINGW32_MAJOR_VERSION <= 3 && __MINGW32_MINOR_VERSION <= 20 typedef PCCRL_CONTEXT WINAPI(*CertEnumCRLsInStoreFunc) (HCERTSTORE hCertStore, PCCRL_CONTEXT pPrevCrlContext); static CertEnumCRLsInStoreFunc pCertEnumCRLsInStore; static HMODULE Crypt32_dll; # else # define pCertEnumCRLsInStore CertEnumCRLsInStore # endif #else /* !_WIN32 */ # include # ifdef HAVE_PTHREAD_LOCKS # include # endif # if defined(HAVE_GETPWUID_R) # include # endif #endif /* System specific function wrappers. */ #ifdef _WIN32 /* Do not use the gnulib functions for sending and receiving data. * Using them makes gnutls only working with gnulib applications. */ #undef send #undef recv #undef select int system_errno(gnutls_transport_ptr p) { int tmperr = WSAGetLastError(); int ret = 0; switch (tmperr) { case WSAEWOULDBLOCK: ret = EAGAIN; break; case NO_ERROR: ret = 0; break; case WSAEINTR: ret = EINTR; break; case WSAEMSGSIZE: ret = EMSGSIZE; break; default: ret = EIO; break; } WSASetLastError(tmperr); return ret; } ssize_t system_write(gnutls_transport_ptr ptr, const void *data, size_t data_size) { return send(GNUTLS_POINTER_TO_INT(ptr), data, data_size, 0); } #else /* POSIX */ int system_errno(gnutls_transport_ptr_t ptr) { #if defined(_AIX) || defined(AIX) if (errno == 0) errno = EAGAIN; #endif return errno; } #ifdef MSG_NOSIGNAL ssize_t system_writev_nosignal(gnutls_transport_ptr_t ptr, const giovec_t * iovec, int iovec_cnt) { struct msghdr hdr; memset(&hdr, 0, sizeof(hdr)); hdr.msg_iov = (struct iovec *)iovec; hdr.msg_iovlen = iovec_cnt; return sendmsg(GNUTLS_POINTER_TO_INT(ptr), &hdr, MSG_NOSIGNAL); } #endif ssize_t system_writev(gnutls_transport_ptr_t ptr, const giovec_t * iovec, int iovec_cnt) { struct msghdr hdr; memset(&hdr, 0, sizeof(hdr)); hdr.msg_iov = (struct iovec *)iovec; hdr.msg_iovlen = iovec_cnt; return sendmsg(GNUTLS_POINTER_TO_INT(ptr), &hdr, 0); } #endif ssize_t system_read(gnutls_transport_ptr_t ptr, void *data, size_t data_size) { return recv(GNUTLS_POINTER_TO_INT(ptr), data, data_size, 0); } /** * gnutls_system_recv_timeout: * @ptr: A gnutls_transport_ptr_t pointer * @ms: The number of milliseconds to wait. * * Wait for data to be received from the provided socket (@ptr) within a * timeout period in milliseconds, using select() on the provided @ptr. * * This function is provided as a helper for constructing custom * callbacks for gnutls_transport_set_pull_timeout_function(), * which can be used if you rely on socket file descriptors. * * Returns -1 on error, 0 on timeout, positive value if data are available for reading. * * Since: 3.4.0 **/ int gnutls_system_recv_timeout(gnutls_transport_ptr_t ptr, unsigned int ms) { int ret; int fd = GNUTLS_POINTER_TO_INT(ptr); #ifndef _WIN32 int timeo; struct pollfd pfd; pfd.fd = fd; pfd.events = POLLIN; pfd.revents = 0; if (ms == GNUTLS_INDEFINITE_TIMEOUT) timeo = -1; else timeo = ms; ret = poll(&pfd, 1, timeo); #else fd_set rfds; struct timeval _tv, *tv = NULL; FD_ZERO(&rfds); FD_SET(fd, &rfds); if (ms != GNUTLS_INDEFINITE_TIMEOUT) { _tv.tv_sec = ms/1000; _tv.tv_usec = (ms % 1000) * 1000; tv = &_tv; } ret = select(fd + 1, &rfds, NULL, NULL, tv); #endif if (ret <= 0) return ret; return ret; } /* Thread stuff */ #ifdef HAVE_WIN32_LOCKS static int gnutls_system_mutex_init(void **priv) { CRITICAL_SECTION *lock = malloc(sizeof(CRITICAL_SECTION)); if (lock == NULL) return GNUTLS_E_MEMORY_ERROR; InitializeCriticalSection(lock); *priv = lock; return 0; } static int gnutls_system_mutex_deinit(void **priv) { DeleteCriticalSection((CRITICAL_SECTION *) * priv); free(*priv); return 0; } static int gnutls_system_mutex_lock(void **priv) { EnterCriticalSection((CRITICAL_SECTION *) * priv); return 0; } static int gnutls_system_mutex_unlock(void **priv) { LeaveCriticalSection((CRITICAL_SECTION *) * priv); return 0; } #endif /* WIN32_LOCKS */ #ifdef HAVE_PTHREAD_LOCKS static int gnutls_system_mutex_init(void **priv) { pthread_mutex_t *lock = malloc(sizeof(pthread_mutex_t)); int ret; if (lock == NULL) return GNUTLS_E_MEMORY_ERROR; ret = pthread_mutex_init(lock, NULL); if (ret) { free(lock); gnutls_assert(); return GNUTLS_E_LOCKING_ERROR; } *priv = lock; return 0; } static int gnutls_system_mutex_deinit(void **priv) { pthread_mutex_destroy((pthread_mutex_t *) * priv); free(*priv); return 0; } static int gnutls_system_mutex_lock(void **priv) { if (pthread_mutex_lock((pthread_mutex_t *) * priv)) { gnutls_assert(); return GNUTLS_E_LOCKING_ERROR; } return 0; } static int gnutls_system_mutex_unlock(void **priv) { if (pthread_mutex_unlock((pthread_mutex_t *) * priv)) { gnutls_assert(); return GNUTLS_E_LOCKING_ERROR; } return 0; } #endif /* PTHREAD_LOCKS */ #ifdef HAVE_NO_LOCKS static int gnutls_system_mutex_init(void **priv) { return 0; } static int gnutls_system_mutex_deinit(void **priv) { return 0; } static int gnutls_system_mutex_lock(void **priv) { return 0; } static int gnutls_system_mutex_unlock(void **priv) { return 0; } #endif /* NO_LOCKS */ gnutls_time_func gnutls_time = time; mutex_init_func gnutls_mutex_init = gnutls_system_mutex_init; mutex_deinit_func gnutls_mutex_deinit = gnutls_system_mutex_deinit; mutex_lock_func gnutls_mutex_lock = gnutls_system_mutex_lock; mutex_unlock_func gnutls_mutex_unlock = gnutls_system_mutex_unlock; int gnutls_system_global_init(void) { #ifdef _WIN32 #if defined(__MINGW32__) && !defined(__MINGW64__) && __MINGW32_MAJOR_VERSION <= 3 && __MINGW32_MINOR_VERSION <= 20 HMODULE crypto; crypto = LoadLibraryA("Crypt32.dll"); if (crypto == NULL) return GNUTLS_E_CRYPTO_INIT_FAILED; pCertEnumCRLsInStore = (CertEnumCRLsInStoreFunc) GetProcAddress(crypto, "CertEnumCRLsInStore"); if (pCertEnumCRLsInStore == NULL) { FreeLibrary(crypto); return GNUTLS_E_CRYPTO_INIT_FAILED; } Crypt32_dll = crypto; #endif #endif return 0; } void gnutls_system_global_deinit(void) { #ifdef _WIN32 #if defined(__MINGW32__) && !defined(__MINGW64__) && __MINGW32_MAJOR_VERSION <= 3 && __MINGW32_MINOR_VERSION <= 20 FreeLibrary(Crypt32_dll); #endif #endif } #define CONFIG_PATH ".gnutls" /* Returns a path to store user-specific configuration * data. */ int _gnutls_find_config_path(char *path, size_t max_size) { const char *home_dir = getenv("HOME"); if (home_dir != NULL && home_dir[0] != 0) { snprintf(path, max_size, "%s/" CONFIG_PATH, home_dir); return 0; } #ifdef _WIN32 if (home_dir == NULL || home_dir[0] == '\0') { const char *home_drive = getenv("HOMEDRIVE"); const char *home_path = getenv("HOMEPATH"); if (home_drive != NULL && home_path != NULL) { snprintf(path, max_size, "%s%s\\" CONFIG_PATH, home_drive, home_path); } else { path[0] = 0; } } #elif defined(HAVE_GETPWUID_R) if (home_dir == NULL || home_dir[0] == '\0') { struct passwd *pwd; struct passwd _pwd; int ret; char tmp[512]; ret = getpwuid_r(getuid(), &_pwd, tmp, sizeof(tmp), &pwd); if (ret == 0 && pwd != NULL) { snprintf(path, max_size, "%s/" CONFIG_PATH, pwd->pw_dir); } else { path[0] = 0; } } #else if (home_dir == NULL || home_dir[0] == '\0') { path[0] = 0; } #endif return 0; } #if defined(DEFAULT_TRUST_STORE_FILE) || (defined(DEFAULT_TRUST_STORE_PKCS11) && defined(ENABLE_PKCS11)) static int add_system_trust(gnutls_x509_trust_list_t list, unsigned int tl_flags, unsigned int tl_vflags) { int ret, r = 0; const char *crl_file = #ifdef DEFAULT_CRL_FILE DEFAULT_CRL_FILE; #else NULL; #endif #if defined(ENABLE_PKCS11) && defined(DEFAULT_TRUST_STORE_PKCS11) ret = gnutls_x509_trust_list_add_trust_file(list, DEFAULT_TRUST_STORE_PKCS11, crl_file, GNUTLS_X509_FMT_DER, tl_flags, tl_vflags); if (ret > 0) r += ret; #endif #ifdef DEFAULT_TRUST_STORE_FILE ret = gnutls_x509_trust_list_add_trust_file(list, DEFAULT_TRUST_STORE_FILE, crl_file, GNUTLS_X509_FMT_PEM, tl_flags, tl_vflags); if (ret > 0) r += ret; #endif #ifdef DEFAULT_BLACKLIST_FILE ret = gnutls_x509_trust_list_remove_trust_file(list, DEFAULT_BLACKLIST_FILE, GNUTLS_X509_FMT_PEM); if (ret < 0) { _gnutls_debug_log("Could not load blacklist file '%s'\n", DEFAULT_BLACKLIST_FILE); } #endif return r; } #elif defined(_WIN32) static int add_system_trust(gnutls_x509_trust_list_t list, unsigned int tl_flags, unsigned int tl_vflags) { unsigned int i; int r = 0; for (i = 0; i < 2; i++) { HCERTSTORE store; const CERT_CONTEXT *cert; const CRL_CONTEXT *crl; gnutls_datum_t data; if (i == 0) store = CertOpenSystemStore(0, "ROOT"); else store = CertOpenSystemStore(0, "CA"); if (store == NULL) return GNUTLS_E_FILE_ERROR; cert = CertEnumCertificatesInStore(store, NULL); crl = pCertEnumCRLsInStore(store, NULL); while (cert != NULL) { if (cert->dwCertEncodingType == X509_ASN_ENCODING) { data.data = cert->pbCertEncoded; data.size = cert->cbCertEncoded; if (gnutls_x509_trust_list_add_trust_mem (list, &data, NULL, GNUTLS_X509_FMT_DER, tl_flags, tl_vflags) > 0) r++; } cert = CertEnumCertificatesInStore(store, cert); } while (crl != NULL) { if (crl->dwCertEncodingType == X509_ASN_ENCODING) { data.data = crl->pbCrlEncoded; data.size = crl->cbCrlEncoded; gnutls_x509_trust_list_add_trust_mem(list, NULL, &data, GNUTLS_X509_FMT_DER, tl_flags, tl_vflags); } crl = pCertEnumCRLsInStore(store, crl); } CertCloseStore(store, 0); } #ifdef DEFAULT_BLACKLIST_FILE ret = gnutls_x509_trust_list_remove_trust_file(list, DEFAULT_BLACKLIST_FILE, GNUTLS_X509_FMT_PEM); if (ret < 0) { _gnutls_debug_log("Could not load blacklist file '%s'\n", DEFAULT_BLACKLIST_FILE); } #endif return r; } #elif defined(ANDROID) || defined(__ANDROID__) || defined(DEFAULT_TRUST_STORE_DIR) # include # include # if defined(ANDROID) || defined(__ANDROID__) # define DEFAULT_TRUST_STORE_DIR "/system/etc/security/cacerts/" static int load_revoked_certs(gnutls_x509_trust_list_t list, unsigned type) { DIR *dirp; struct dirent *d; int ret; int r = 0; char path[GNUTLS_PATH_MAX]; dirp = opendir("/data/misc/keychain/cacerts-removed/"); if (dirp != NULL) { do { d = readdir(dirp); if (d != NULL && d->d_type == DT_REG) { snprintf(path, sizeof(path), "/data/misc/keychain/cacerts-removed/%s", d->d_name); ret = gnutls_x509_trust_list_remove_trust_file (list, path, type); if (ret >= 0) r += ret; } } while (d != NULL); closedir(dirp); } return r; } # endif /* This works on android 4.x */ static int add_system_trust(gnutls_x509_trust_list_t list, unsigned int tl_flags, unsigned int tl_vflags) { int r = 0, ret; ret = gnutls_x509_trust_list_add_trust_dir(list, DEFAULT_TRUST_STORE_DIR, NULL, GNUTLS_X509_FMT_PEM, tl_flags, tl_vflags); if (ret >= 0) r += ret; # if defined(ANDROID) || defined(__ANDROID__) ret = load_revoked_certs(list, GNUTLS_X509_FMT_DER); if (ret >= 0) r -= ret; ret = gnutls_x509_trust_list_add_trust_dir(list, "/data/misc/keychain/cacerts-added/", NULL, GNUTLS_X509_FMT_DER, tl_flags, tl_vflags); if (ret >= 0) r += ret; # endif return r; } #else #define add_system_trust(x,y,z) GNUTLS_E_UNIMPLEMENTED_FEATURE #endif /** * gnutls_x509_trust_list_add_system_trust: * @list: The structure of the list * @tl_flags: GNUTLS_TL_* * @tl_vflags: gnutls_certificate_verify_flags if flags specifies GNUTLS_TL_VERIFY_CRL * * This function adds the system's default trusted certificate * authorities to the trusted list. Note that on unsupported systems * this function returns %GNUTLS_E_UNIMPLEMENTED_FEATURE. * * This function implies the flag %GNUTLS_TL_NO_DUPLICATES. * * Returns: The number of added elements or a negative error code on error. * * Since: 3.1 **/ int gnutls_x509_trust_list_add_system_trust(gnutls_x509_trust_list_t list, unsigned int tl_flags, unsigned int tl_vflags) { return add_system_trust(list, tl_flags|GNUTLS_TL_NO_DUPLICATES, tl_vflags); } #if defined(_WIN32) #include int _gnutls_ucs2_to_utf8(const void *data, size_t size, gnutls_datum_t * output, unsigned be) { int ret; unsigned i; int len = 0, src_len; char *dst = NULL; char *src = NULL; static unsigned flags = 0; static int checked = 0; if (checked == 0) { /* Not all windows versions support MB_ERR_INVALID_CHARS */ ret = WideCharToMultiByte(CP_UTF8, MB_ERR_INVALID_CHARS, L"hello", -1, NULL, 0, NULL, NULL); if (ret > 0) flags = MB_ERR_INVALID_CHARS; checked = 1; } if (((uint8_t *) data)[size] == 0 && ((uint8_t *) data)[size+1] == 0) { size -= 2; } src_len = wcslen(data); src = gnutls_malloc(size+2); if (src == NULL) return gnutls_assert_val(GNUTLS_E_MEMORY_ERROR); /* convert to LE */ if (be) { for (i = 0; i < size; i += 2) { src[i] = ((uint8_t *) data)[1 + i]; src[1 + i] = ((uint8_t *) data)[i]; } } else { memcpy(src, data, size); } src[size] = 0; src[size+1] = 0; ret = WideCharToMultiByte(CP_UTF8, flags, (void *) src, src_len, NULL, 0, NULL, NULL); if (ret == 0) { _gnutls_debug_log("WideCharToMultiByte: %d\n", (int)GetLastError()); ret = gnutls_assert_val(GNUTLS_E_PARSING_ERROR); goto fail; } len = ret + 1; dst = gnutls_malloc(len); if (dst == NULL) { ret = gnutls_assert_val(GNUTLS_E_MEMORY_ERROR); goto fail; } dst[0] = 0; ret = WideCharToMultiByte(CP_UTF8, flags, (void *) src, src_len, dst, len-1, NULL, NULL); if (ret == 0) { ret = gnutls_assert_val(GNUTLS_E_PARSING_ERROR); goto fail; } dst[len - 1] = 0; output->data = dst; output->size = ret; ret = 0; goto cleanup; fail: gnutls_free(dst); cleanup: gnutls_free(src); return ret; } #elif defined(HAVE_ICONV) || defined(HAVE_LIBICONV) #include int _gnutls_ucs2_to_utf8(const void *data, size_t size, gnutls_datum_t * output, unsigned be) { iconv_t conv; int ret; size_t orig, dstlen = size * 2; char *src = (void *) data; char *dst = NULL, *pdst; if (size == 0) return gnutls_assert_val(GNUTLS_E_INVALID_REQUEST); if (be) { conv = iconv_open("UTF-8", "UTF-16BE"); } else { conv = iconv_open("UTF-8", "UTF-16LE"); } if (conv == (iconv_t) - 1) return gnutls_assert_val(GNUTLS_E_MEMORY_ERROR); /* Note that dstlen has enough size for every possible input characters. * (remember the in UTF-16 the characters in data are at most size/2, * and we allocate 4 bytes per character). */ pdst = dst = gnutls_malloc(dstlen + 1); if (dst == NULL) { ret = gnutls_assert_val(GNUTLS_E_MEMORY_ERROR); goto fail; } orig = dstlen; ret = iconv(conv, &src, &size, &pdst, &dstlen); if (ret == -1) { ret = gnutls_assert_val(GNUTLS_E_PARSING_ERROR); goto fail; } output->data = (void *) dst; output->size = orig - dstlen; output->data[output->size] = 0; ret = 0; goto cleanup; fail: gnutls_free(dst); cleanup: iconv_close(conv); return ret; } #else /* Can convert only english (ASCII) */ int _gnutls_ucs2_to_utf8(const void *data, size_t size, gnutls_datum_t * output, unsigned be) { unsigned int i, j; char *dst; const char *src = data; if (size == 0 || size % 2 != 0) return gnutls_assert_val(GNUTLS_E_INVALID_REQUEST); dst = gnutls_malloc(size + 1); if (dst == NULL) return gnutls_assert_val(GNUTLS_E_MEMORY_ERROR); for (i = j = 0; i < size; i += 2, j++) { if (src[i] != 0 || !c_isascii(src[i + 1])) return gnutls_assert_val(GNUTLS_E_PARSING_ERROR); if (be) dst[j] = src[i + 1]; else dst[j] = src[i]; } output->data = (void *) dst; output->size = j; output->data[output->size] = 0; return 0; } #endif