Squashed 'third_party/rawrtc/re/' content from commit f3163ce8b
Change-Id: I6a235e6ac0f03269d951026f9d195da05c40fdab
git-subtree-dir: third_party/rawrtc/re
git-subtree-split: f3163ce8b526a13b35ef71ce4dd6f43585064d8a
diff --git a/src/tls/openssl/tls.c b/src/tls/openssl/tls.c
new file mode 100644
index 0000000..d84e65f
--- /dev/null
+++ b/src/tls/openssl/tls.c
@@ -0,0 +1,904 @@
+/**
+ * @file openssl/tls.c TLS backend using OpenSSL
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <string.h>
+#include <openssl/ssl.h>
+#include <openssl/err.h>
+#include <openssl/rsa.h>
+#include <openssl/bn.h>
+#include <openssl/evp.h>
+#include <openssl/x509.h>
+#include <re_types.h>
+#include <re_fmt.h>
+#include <re_mem.h>
+#include <re_mbuf.h>
+#include <re_main.h>
+#include <re_sa.h>
+#include <re_net.h>
+#include <re_srtp.h>
+#include <re_sys.h>
+#include <re_tcp.h>
+#include <re_tls.h>
+#include "tls.h"
+
+
+/* also defined by wincrypt.h */
+#ifdef WIN32
+#undef X509_NAME
+#endif
+
+
+#define DEBUG_MODULE "tls"
+#define DEBUG_LEVEL 5
+#include <re_dbg.h>
+
+
+/* NOTE: shadow struct defined in tls_*.c */
+struct tls_conn {
+ SSL *ssl;
+};
+
+
+static void destructor(void *data)
+{
+ struct tls *tls = data;
+
+ if (tls->ctx)
+ SSL_CTX_free(tls->ctx);
+
+ if (tls->cert)
+ X509_free(tls->cert);
+
+ mem_deref(tls->pass);
+}
+
+
+/*The password code is not thread safe*/
+static int password_cb(char *buf, int size, int rwflag, void *userdata)
+{
+ struct tls *tls = userdata;
+
+ (void)rwflag;
+
+ DEBUG_NOTICE("password callback\n");
+
+ if (size < (int)strlen(tls->pass)+1)
+ return 0;
+
+ strncpy(buf, tls->pass, size);
+
+ return (int)strlen(tls->pass);
+}
+
+
+static int keytype2int(enum tls_keytype type)
+{
+ switch (type) {
+ case TLS_KEYTYPE_EC:
+ return EVP_PKEY_EC;
+ case TLS_KEYTYPE_RSA:
+ return EVP_PKEY_RSA;
+ default:
+ return EVP_PKEY_NONE;
+ }
+}
+
+
+/**
+ * Allocate a new TLS context
+ *
+ * @param tlsp Pointer to allocated TLS context
+ * @param method TLS method
+ * @param keyfile Optional private key file
+ * @param pwd Optional password
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int tls_alloc(struct tls **tlsp, enum tls_method method, const char *keyfile,
+ const char *pwd)
+{
+ struct tls *tls;
+ int r, err;
+
+ if (!tlsp)
+ return EINVAL;
+
+ tls = mem_zalloc(sizeof(*tls), destructor);
+ if (!tls)
+ return ENOMEM;
+
+ switch (method) {
+
+ case TLS_METHOD_SSLV23:
+ tls->ctx = SSL_CTX_new(SSLv23_method());
+ break;
+
+#ifdef USE_OPENSSL_DTLS
+ case TLS_METHOD_DTLSV1:
+#if OPENSSL_VERSION_NUMBER >= 0x10100000L && \
+ !defined(LIBRESSL_VERSION_NUMBER)
+
+ tls->ctx = SSL_CTX_new(DTLS_method());
+#else
+ tls->ctx = SSL_CTX_new(DTLSv1_method());
+#endif
+ break;
+
+#ifdef SSL_OP_NO_DTLSv1_2
+ /* DTLS v1.2 is available in OpenSSL 1.0.2 and later */
+
+ case TLS_METHOD_DTLS:
+ tls->ctx = SSL_CTX_new(DTLS_method());
+ break;
+
+ case TLS_METHOD_DTLSV1_2:
+#if OPENSSL_VERSION_NUMBER >= 0x10100000L && \
+ !defined(LIBRESSL_VERSION_NUMBER)
+
+ tls->ctx = SSL_CTX_new(DTLS_method());
+#else
+ tls->ctx = SSL_CTX_new(DTLSv1_2_method());
+#endif
+ break;
+#endif
+
+#endif
+
+ default:
+ DEBUG_WARNING("tls method %d not supported\n", method);
+ err = ENOSYS;
+ goto out;
+ }
+
+ if (!tls->ctx) {
+ ERR_clear_error();
+ err = ENOMEM;
+ goto out;
+ }
+
+#if (OPENSSL_VERSION_NUMBER < 0x00905100L)
+ SSL_CTX_set_verify_depth(tls->ctx, 1);
+#endif
+
+ /* Load our keys and certificates */
+ if (keyfile) {
+ if (pwd) {
+ err = str_dup(&tls->pass, pwd);
+ if (err)
+ goto out;
+
+ SSL_CTX_set_default_passwd_cb(tls->ctx, password_cb);
+ SSL_CTX_set_default_passwd_cb_userdata(tls->ctx, tls);
+ }
+
+ r = SSL_CTX_use_certificate_chain_file(tls->ctx, keyfile);
+ if (r <= 0) {
+ DEBUG_WARNING("Can't read certificate file: %s (%d)\n",
+ keyfile, r);
+ ERR_clear_error();
+ err = EINVAL;
+ goto out;
+ }
+
+ r = SSL_CTX_use_PrivateKey_file(tls->ctx, keyfile,
+ SSL_FILETYPE_PEM);
+ if (r <= 0) {
+ DEBUG_WARNING("Can't read key file: %s (%d)\n",
+ keyfile, r);
+ ERR_clear_error();
+ err = EINVAL;
+ goto out;
+ }
+ }
+
+ err = 0;
+ out:
+ if (err)
+ mem_deref(tls);
+ else
+ *tlsp = tls;
+
+ return err;
+}
+
+
+/**
+ * Set default locations for trusted CA certificates
+ *
+ * @param tls TLS Context
+ * @param cafile PEM file with CA certificates
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int tls_add_ca(struct tls *tls, const char *cafile)
+{
+ if (!tls || !cafile)
+ return EINVAL;
+
+ /* Load the CAs we trust */
+ if (!(SSL_CTX_load_verify_locations(tls->ctx, cafile, NULL))) {
+ DEBUG_WARNING("Can't read CA file: %s\n", cafile);
+ ERR_clear_error();
+ return EINVAL;
+ }
+
+ return 0;
+}
+
+
+/**
+ * Generate and set selfsigned certificate on TLS context
+ *
+ * @param tls TLS Context
+ * @param cn Common Name
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int tls_set_selfsigned(struct tls *tls, const char *cn)
+{
+ X509_NAME *subj = NULL;
+ EVP_PKEY *key = NULL;
+ X509 *cert = NULL;
+ BIGNUM *bn = NULL;
+ RSA *rsa = NULL;
+ int r, err = ENOMEM;
+
+ if (!tls || !cn)
+ return EINVAL;
+
+ rsa = RSA_new();
+ if (!rsa)
+ goto out;
+
+ bn = BN_new();
+ if (!bn)
+ goto out;
+
+ BN_set_word(bn, RSA_F4);
+ if (!RSA_generate_key_ex(rsa, 1024, bn, NULL))
+ goto out;
+
+ key = EVP_PKEY_new();
+ if (!key)
+ goto out;
+
+ if (!EVP_PKEY_set1_RSA(key, rsa))
+ goto out;
+
+ cert = X509_new();
+ if (!cert)
+ goto out;
+
+ if (!X509_set_version(cert, 2))
+ goto out;
+
+ if (!ASN1_INTEGER_set(X509_get_serialNumber(cert), rand_u32()))
+ goto out;
+
+ subj = X509_NAME_new();
+ if (!subj)
+ goto out;
+
+ if (!X509_NAME_add_entry_by_txt(subj, "CN", MBSTRING_ASC,
+ (unsigned char *)cn,
+ (int)strlen(cn), -1, 0))
+ goto out;
+
+ if (!X509_set_issuer_name(cert, subj) ||
+ !X509_set_subject_name(cert, subj))
+ goto out;
+
+ if (!X509_gmtime_adj(X509_get_notBefore(cert), -3600*24*365) ||
+ !X509_gmtime_adj(X509_get_notAfter(cert), 3600*24*365*10))
+ goto out;
+
+ if (!X509_set_pubkey(cert, key))
+ goto out;
+
+ if (!X509_sign(cert, key, EVP_sha1()))
+ goto out;
+
+ r = SSL_CTX_use_certificate(tls->ctx, cert);
+ if (r != 1)
+ goto out;
+
+ r = SSL_CTX_use_PrivateKey(tls->ctx, key);
+ if (r != 1)
+ goto out;
+
+ if (tls->cert)
+ X509_free(tls->cert);
+
+ tls->cert = cert;
+ cert = NULL;
+
+ err = 0;
+
+ out:
+ if (subj)
+ X509_NAME_free(subj);
+
+ if (cert)
+ X509_free(cert);
+
+ if (key)
+ EVP_PKEY_free(key);
+
+ if (rsa)
+ RSA_free(rsa);
+
+ if (bn)
+ BN_free(bn);
+
+ if (err)
+ ERR_clear_error();
+
+ return err;
+}
+
+
+/**
+ * Set the certificate and private key on a TLS context
+ *
+ * @param tls TLS Context
+ * @param cert Certificate in PEM format
+ * @param len_cert Length of certificate PEM string
+ * @param key Private key in PEM format, will be read from cert if NULL
+ * @param len_key Length of private key PEM string
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int tls_set_certificate_pem(struct tls *tls, const char *cert, size_t len_cert,
+ const char *key, size_t len_key)
+{
+ BIO *bio = NULL, *kbio = NULL;
+ X509 *x509 = NULL;
+ EVP_PKEY *pkey = NULL;
+ int r, err = ENOMEM;
+
+ if (!tls || !cert || !len_cert || (key && !len_key))
+ return EINVAL;
+
+ if (!key) {
+ key = cert;
+ len_key = len_cert;
+ }
+
+ bio = BIO_new_mem_buf((char *)cert, (int)len_cert);
+ kbio = BIO_new_mem_buf((char *)key, (int)len_key);
+ if (!bio || !kbio)
+ goto out;
+
+ x509 = PEM_read_bio_X509(bio, NULL, 0, NULL);
+ pkey = PEM_read_bio_PrivateKey(kbio, NULL, 0, NULL);
+ if (!x509 || !pkey)
+ goto out;
+
+ r = SSL_CTX_use_certificate(tls->ctx, x509);
+ if (r != 1)
+ goto out;
+
+ r = SSL_CTX_use_PrivateKey(tls->ctx, pkey);
+ if (r != 1) {
+ DEBUG_WARNING("set_certificate_pem: use_PrivateKey failed\n");
+ goto out;
+ }
+
+ if (tls->cert)
+ X509_free(tls->cert);
+
+ tls->cert = x509;
+ x509 = NULL;
+
+ err = 0;
+
+ out:
+ if (x509)
+ X509_free(x509);
+ if (pkey)
+ EVP_PKEY_free(pkey);
+ if (bio)
+ BIO_free(bio);
+ if (kbio)
+ BIO_free(kbio);
+ if (err)
+ ERR_clear_error();
+
+ return err;
+}
+
+
+/**
+ * Set the certificate and private key on a TLS context
+ *
+ * @param tls TLS Context
+ * @param keytype Private key type
+ * @param cert Certificate in DER format
+ * @param len_cert Length of certificate DER bytes
+ * @param key Private key in DER format, will be read from cert if NULL
+ * @param len_key Length of private key DER bytes
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int tls_set_certificate_der(struct tls *tls, enum tls_keytype keytype,
+ const uint8_t *cert, size_t len_cert,
+ const uint8_t *key, size_t len_key)
+{
+ const uint8_t *buf_cert;
+ X509 *x509 = NULL;
+ EVP_PKEY *pkey = NULL;
+ int r, type, err = ENOMEM;
+
+ if (!tls || !cert || !len_cert || (key && !len_key))
+ return EINVAL;
+
+ type = keytype2int(keytype);
+ if (type == EVP_PKEY_NONE)
+ return EINVAL;
+
+ buf_cert = cert;
+
+ x509 = d2i_X509(NULL, &buf_cert, len_cert);
+ if (!x509)
+ goto out;
+
+ if (!key) {
+ key = buf_cert;
+ len_key = len_cert - (buf_cert - cert);
+ }
+
+ pkey = d2i_PrivateKey(type, NULL, &key, len_key);
+ if (!pkey)
+ goto out;
+
+ r = SSL_CTX_use_certificate(tls->ctx, x509);
+ if (r != 1)
+ goto out;
+
+ r = SSL_CTX_use_PrivateKey(tls->ctx, pkey);
+ if (r != 1) {
+ DEBUG_WARNING("set_certificate_der: use_PrivateKey failed\n");
+ goto out;
+ }
+
+ if (tls->cert)
+ X509_free(tls->cert);
+
+ tls->cert = x509;
+ x509 = NULL;
+
+ err = 0;
+
+ out:
+ if (x509)
+ X509_free(x509);
+ if (pkey)
+ EVP_PKEY_free(pkey);
+ if (err)
+ ERR_clear_error();
+
+ return err;
+}
+
+
+/**
+ * Set the certificate and private key on a TLS context
+ *
+ * @param tls TLS Context
+ * @param pem Certificate and private key in PEM format
+ * @param len Length of PEM string
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int tls_set_certificate(struct tls *tls, const char *pem, size_t len)
+{
+ return tls_set_certificate_pem(tls, pem, len, NULL, 0);
+}
+
+
+static int verify_handler(int ok, X509_STORE_CTX *ctx)
+{
+ (void)ok;
+ (void)ctx;
+
+ return 1; /* We trust the certificate from peer */
+}
+
+
+/**
+ * Set TLS server context to request certificate from client
+ *
+ * @param tls TLS Context
+ */
+void tls_set_verify_client(struct tls *tls)
+{
+ if (!tls)
+ return;
+
+ SSL_CTX_set_verify_depth(tls->ctx, 0);
+ SSL_CTX_set_verify(tls->ctx, SSL_VERIFY_PEER | SSL_VERIFY_CLIENT_ONCE,
+ verify_handler);
+}
+
+
+/**
+ * Set SRTP suites on TLS context
+ *
+ * @param tls TLS Context
+ * @param suites Secure-RTP Profiles
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int tls_set_srtp(struct tls *tls, const char *suites)
+{
+#ifdef USE_OPENSSL_SRTP
+ if (!tls || !suites)
+ return EINVAL;
+
+ if (0 != SSL_CTX_set_tlsext_use_srtp(tls->ctx, suites)) {
+ ERR_clear_error();
+ return ENOSYS;
+ }
+
+ return 0;
+#else
+ (void)tls;
+ (void)suites;
+
+ return ENOSYS;
+#endif
+}
+
+
+static int cert_fingerprint(X509 *cert, enum tls_fingerprint type,
+ uint8_t *md, size_t size)
+{
+ unsigned int len = (unsigned int)size;
+ int n;
+
+ switch (type) {
+
+ case TLS_FINGERPRINT_SHA1:
+ if (size < 20)
+ return EOVERFLOW;
+
+ n = X509_digest(cert, EVP_sha1(), md, &len);
+ break;
+
+ case TLS_FINGERPRINT_SHA256:
+ if (size < 32)
+ return EOVERFLOW;
+
+ n = X509_digest(cert, EVP_sha256(), md, &len);
+ break;
+
+ default:
+ return ENOSYS;
+ }
+
+ if (n != 1) {
+ ERR_clear_error();
+ return ENOENT;
+ }
+
+ return 0;
+}
+
+
+/**
+ * Get fingerprint of local certificate
+ *
+ * @param tls TLS Context
+ * @param type Digest type
+ * @param md Buffer for fingerprint digest
+ * @param size Buffer size
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int tls_fingerprint(const struct tls *tls, enum tls_fingerprint type,
+ uint8_t *md, size_t size)
+{
+ if (!tls || !tls->cert || !md)
+ return EINVAL;
+
+ return cert_fingerprint(tls->cert, type, md, size);
+}
+
+
+/**
+ * Get fingerprint of peer certificate of a TLS connection
+ *
+ * @param tc TLS Connection
+ * @param type Digest type
+ * @param md Buffer for fingerprint digest
+ * @param size Buffer size
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int tls_peer_fingerprint(const struct tls_conn *tc, enum tls_fingerprint type,
+ uint8_t *md, size_t size)
+{
+ X509 *cert;
+ int err;
+
+ if (!tc || !md)
+ return EINVAL;
+
+ cert = SSL_get_peer_certificate(tc->ssl);
+ if (!cert)
+ return ENOENT;
+
+ err = cert_fingerprint(cert, type, md, size);
+
+ X509_free(cert);
+
+ return err;
+}
+
+
+/**
+ * Get common name of peer certificate of a TLS connection
+ *
+ * @param tc TLS Connection
+ * @param cn Returned common name
+ * @param size Size of common name
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int tls_peer_common_name(const struct tls_conn *tc, char *cn, size_t size)
+{
+ X509 *cert;
+ int n;
+
+ if (!tc || !cn || !size)
+ return EINVAL;
+
+ cert = SSL_get_peer_certificate(tc->ssl);
+ if (!cert)
+ return ENOENT;
+
+ n = X509_NAME_get_text_by_NID(X509_get_subject_name(cert),
+ NID_commonName, cn, (int)size);
+
+ X509_free(cert);
+
+ if (n < 0) {
+ ERR_clear_error();
+ return ENOENT;
+ }
+
+ return 0;
+}
+
+
+/**
+ * Verify peer certificate of a TLS connection
+ *
+ * @param tc TLS Connection
+ *
+ * @return 0 if verified, otherwise errorcode
+ */
+int tls_peer_verify(const struct tls_conn *tc)
+{
+ if (!tc)
+ return EINVAL;
+
+ if (SSL_get_verify_result(tc->ssl) != X509_V_OK)
+ return EAUTH;
+
+ return 0;
+}
+
+
+/**
+ * Get SRTP suite and keying material of a TLS connection
+ *
+ * @param tc TLS Connection
+ * @param suite Returned SRTP suite
+ * @param cli_key Client key
+ * @param cli_key_size Client key size
+ * @param srv_key Server key
+ * @param srv_key_size Server key size
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int tls_srtp_keyinfo(const struct tls_conn *tc, enum srtp_suite *suite,
+ uint8_t *cli_key, size_t cli_key_size,
+ uint8_t *srv_key, size_t srv_key_size)
+{
+#ifdef USE_OPENSSL_SRTP
+ static const char *label = "EXTRACTOR-dtls_srtp";
+ size_t key_size, salt_size, size;
+ SRTP_PROTECTION_PROFILE *sel;
+ uint8_t keymat[256], *p;
+
+ if (!tc || !suite || !cli_key || !srv_key)
+ return EINVAL;
+
+ sel = SSL_get_selected_srtp_profile(tc->ssl);
+ if (!sel)
+ return ENOENT;
+
+ switch (sel->id) {
+
+ case SRTP_AES128_CM_SHA1_80:
+ *suite = SRTP_AES_CM_128_HMAC_SHA1_80;
+ key_size = 16;
+ salt_size = 14;
+ break;
+
+ case SRTP_AES128_CM_SHA1_32:
+ *suite = SRTP_AES_CM_128_HMAC_SHA1_32;
+ key_size = 16;
+ salt_size = 14;
+ break;
+
+#ifdef SRTP_AEAD_AES_128_GCM
+ case SRTP_AEAD_AES_128_GCM:
+ *suite = SRTP_AES_128_GCM;
+ key_size = 16;
+ salt_size = 12;
+ break;
+#endif
+
+#ifdef SRTP_AEAD_AES_256_GCM
+ case SRTP_AEAD_AES_256_GCM:
+ *suite = SRTP_AES_256_GCM;
+ key_size = 32;
+ salt_size = 12;
+ break;
+#endif
+
+ default:
+ return ENOSYS;
+ }
+
+ size = key_size + salt_size;
+
+ if (cli_key_size < size || srv_key_size < size)
+ return EOVERFLOW;
+
+ if (sizeof(keymat) < 2*size)
+ return EOVERFLOW;
+
+ if (1 != SSL_export_keying_material(tc->ssl, keymat, 2*size, label,
+ strlen(label), NULL, 0, 0)) {
+ ERR_clear_error();
+ return ENOENT;
+ }
+
+ p = keymat;
+
+ memcpy(cli_key, p, key_size); p += key_size;
+ memcpy(srv_key, p, key_size); p += key_size;
+ memcpy(cli_key + key_size, p, salt_size); p += salt_size;
+ memcpy(srv_key + key_size, p, salt_size);
+
+ return 0;
+#else
+ (void)tc;
+ (void)suite;
+ (void)cli_key;
+ (void)cli_key_size;
+ (void)srv_key;
+ (void)srv_key_size;
+
+ return ENOSYS;
+#endif
+}
+
+
+/**
+ * Get cipher name of a TLS connection
+ *
+ * @param tc TLS Connection
+ *
+ * @return name of cipher actually used.
+ */
+const char *tls_cipher_name(const struct tls_conn *tc)
+{
+ if (!tc)
+ return NULL;
+
+ return SSL_get_cipher_name(tc->ssl);
+}
+
+
+/**
+ * Set the ciphers to use for this TLS context
+ *
+ * @param tls TLS Context
+ * @param cipherv Vector of cipher names, in order of priority
+ * @param count Number of cipher names in the vector
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int tls_set_ciphers(struct tls *tls, const char *cipherv[], size_t count)
+{
+ struct mbuf *mb;
+ int r, err;
+ size_t i;
+
+ if (!tls || !cipherv || !count)
+ return EINVAL;
+
+ mb = mbuf_alloc(32 * count);
+ if (!mb)
+ return ENOMEM;
+
+ for (i=0; i<count; i++) {
+
+ err = mbuf_printf(mb, "%s%s", i>0 ? ":" : "", cipherv[i]);
+ if (err)
+ goto out;
+ }
+
+ err = mbuf_write_u8(mb, '\0');
+ if (err)
+ goto out;
+
+ r = SSL_CTX_set_cipher_list(tls->ctx, (char *)mb->buf);
+ if (r <= 0) {
+ ERR_clear_error();
+ err = EPROTO;
+ goto out;
+ }
+
+ out:
+ mem_deref(mb);
+
+ return err;
+}
+
+
+/**
+ * Set the server name on a TLS Connection, using TLS SNI extension.
+ *
+ * @param tc TLS Connection
+ * @param servername Server name
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int tls_set_servername(struct tls_conn *tc, const char *servername)
+{
+ if (!tc || !servername)
+ return EINVAL;
+
+ if (1 != SSL_set_tlsext_host_name(tc->ssl, servername)) {
+ DEBUG_WARNING("tls: SSL_set_tlsext_host_name error\n");
+ ERR_clear_error();
+ return EPROTO;
+ }
+
+ return 0;
+}
+
+
+static int print_error(const char *str, size_t len, void *unused)
+{
+ (void)unused;
+ DEBUG_WARNING("%b", str, len);
+
+ return 1;
+}
+
+
+void tls_flush_error(void)
+{
+ ERR_print_errors_cb(print_error, NULL);
+}
+
+
+/**
+ * Get the backend-specific (OpenSSL) context
+ *
+ * @param tls Generic TLS Context
+ *
+ * @return OpenSSL context
+ */
+struct ssl_ctx_st *tls_openssl_context(const struct tls *tls)
+{
+ return tls ? tls->ctx : NULL;
+}
diff --git a/src/tls/openssl/tls.h b/src/tls/openssl/tls.h
new file mode 100644
index 0000000..2c621d5
--- /dev/null
+++ b/src/tls/openssl/tls.h
@@ -0,0 +1,35 @@
+/**
+ * @file openssl/tls.h TLS backend using OpenSSL (Internal API)
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+
+
+/*
+ * Mapping of feature macros
+ */
+
+#if OPENSSL_VERSION_NUMBER >= 0x10100000L
+#define TLS_BIO_OPAQUE 1
+#endif
+
+#if defined (LIBRESSL_VERSION_NUMBER)
+#undef TLS_BIO_OPAQUE
+#endif
+
+
+#if OPENSSL_VERSION_NUMBER >= 0x10100000L && \
+ !defined(LIBRESSL_VERSION_NUMBER)
+#define SSL_state SSL_get_state
+#define SSL_ST_OK TLS_ST_OK
+#endif
+
+
+struct tls {
+ SSL_CTX *ctx;
+ X509 *cert;
+ char *pass; /* password for private key */
+};
+
+
+void tls_flush_error(void);
diff --git a/src/tls/openssl/tls_tcp.c b/src/tls/openssl/tls_tcp.c
new file mode 100644
index 0000000..ef3601e
--- /dev/null
+++ b/src/tls/openssl/tls_tcp.c
@@ -0,0 +1,437 @@
+/**
+ * @file openssl/tls_tcp.c TLS/TCP backend using OpenSSL
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+
+#include <openssl/ssl.h>
+#include <openssl/err.h>
+#include <re_types.h>
+#include <re_fmt.h>
+#include <re_mem.h>
+#include <re_mbuf.h>
+#include <re_main.h>
+#include <re_sa.h>
+#include <re_net.h>
+#include <re_srtp.h>
+#include <re_tcp.h>
+#include <re_tls.h>
+#include "tls.h"
+
+
+#define DEBUG_MODULE "tls"
+#define DEBUG_LEVEL 5
+#include <re_dbg.h>
+
+
+/* NOTE: shadow struct defined in tls_*.c */
+struct tls_conn {
+ SSL *ssl;
+#ifdef TLS_BIO_OPAQUE
+ BIO_METHOD *biomet;
+#endif
+ BIO *sbio_out;
+ BIO *sbio_in;
+ struct tcp_helper *th;
+ struct tcp_conn *tcp;
+ bool active;
+ bool up;
+};
+
+
+static void destructor(void *arg)
+{
+ struct tls_conn *tc = arg;
+
+ if (tc->ssl) {
+ int r = SSL_shutdown(tc->ssl);
+ if (r <= 0)
+ ERR_clear_error();
+
+ SSL_free(tc->ssl);
+ }
+
+#ifdef TLS_BIO_OPAQUE
+ if (tc->biomet)
+ BIO_meth_free(tc->biomet);
+#endif
+
+ mem_deref(tc->th);
+ mem_deref(tc->tcp);
+}
+
+
+static int bio_create(BIO *b)
+{
+#ifdef TLS_BIO_OPAQUE
+ BIO_set_init(b, 1);
+ BIO_set_data(b, NULL);
+ BIO_set_flags(b, 0);
+#else
+ b->init = 1;
+ b->num = 0;
+ b->ptr = NULL;
+ b->flags = 0;
+#endif
+
+ return 1;
+}
+
+
+static int bio_destroy(BIO *b)
+{
+ if (!b)
+ return 0;
+
+#ifdef TLS_BIO_OPAQUE
+ BIO_set_init(b, 0);
+ BIO_set_data(b, NULL);
+ BIO_set_flags(b, 0);
+#else
+ b->ptr = NULL;
+ b->init = 0;
+ b->flags = 0;
+#endif
+
+ return 1;
+}
+
+
+static int bio_write(BIO *b, const char *buf, int len)
+{
+#ifdef TLS_BIO_OPAQUE
+ struct tls_conn *tc = BIO_get_data(b);
+#else
+ struct tls_conn *tc = b->ptr;
+#endif
+ struct mbuf mb;
+ int err;
+
+ mb.buf = (void *)buf;
+ mb.pos = 0;
+ mb.end = mb.size = len;
+
+ err = tcp_send_helper(tc->tcp, &mb, tc->th);
+ if (err)
+ return -1;
+
+ return len;
+}
+
+
+static long bio_ctrl(BIO *b, int cmd, long num, void *ptr)
+{
+ (void)b;
+ (void)num;
+ (void)ptr;
+
+ if (cmd == BIO_CTRL_FLUSH) {
+ /* The OpenSSL library needs this */
+ return 1;
+ }
+
+ return 0;
+}
+
+
+#ifdef TLS_BIO_OPAQUE
+
+static BIO_METHOD *bio_method_tcp(void)
+{
+ BIO_METHOD *method;
+
+ method = BIO_meth_new(BIO_TYPE_SOURCE_SINK, "tcp_send");
+ if (!method) {
+ DEBUG_WARNING("alloc: BIO_meth_new() failed\n");
+ ERR_clear_error();
+ return NULL;
+ }
+
+ BIO_meth_set_write(method, bio_write);
+ BIO_meth_set_ctrl(method, bio_ctrl);
+ BIO_meth_set_create(method, bio_create);
+ BIO_meth_set_destroy(method, bio_destroy);
+
+ return method;
+}
+
+#else
+
+static struct bio_method_st bio_tcp_send = {
+ BIO_TYPE_SOURCE_SINK,
+ "tcp_send",
+ bio_write,
+ 0,
+ 0,
+ 0,
+ bio_ctrl,
+ bio_create,
+ bio_destroy,
+ 0
+};
+
+#endif
+
+
+static int tls_connect(struct tls_conn *tc)
+{
+ int err = 0, r;
+
+ ERR_clear_error();
+
+ r = SSL_connect(tc->ssl);
+ if (r <= 0) {
+ const int ssl_err = SSL_get_error(tc->ssl, r);
+
+ ERR_clear_error();
+
+ switch (ssl_err) {
+
+ case SSL_ERROR_WANT_READ:
+ break;
+
+ default:
+ DEBUG_WARNING("connect: error (r=%d, ssl_err=%d)\n",
+ r, ssl_err);
+ err = EPROTO;
+ break;
+ }
+ }
+
+ return err;
+}
+
+
+static int tls_accept(struct tls_conn *tc)
+{
+ int err = 0, r;
+
+ ERR_clear_error();
+
+ r = SSL_accept(tc->ssl);
+ if (r <= 0) {
+ const int ssl_err = SSL_get_error(tc->ssl, r);
+
+ ERR_clear_error();
+
+ switch (ssl_err) {
+
+ case SSL_ERROR_WANT_READ:
+ break;
+
+ default:
+ DEBUG_WARNING("accept error: (r=%d, ssl_err=%d)\n",
+ r, ssl_err);
+ err = EPROTO;
+ break;
+ }
+ }
+
+ return err;
+}
+
+
+static bool estab_handler(int *err, bool active, void *arg)
+{
+ struct tls_conn *tc = arg;
+
+ DEBUG_INFO("tcp established (active=%u)\n", active);
+
+ if (!active)
+ return true;
+
+ tc->active = true;
+ *err = tls_connect(tc);
+
+ return true;
+}
+
+
+static bool recv_handler(int *err, struct mbuf *mb, bool *estab, void *arg)
+{
+ struct tls_conn *tc = arg;
+ int r;
+
+ /* feed SSL data to the BIO */
+ r = BIO_write(tc->sbio_in, mbuf_buf(mb), (int)mbuf_get_left(mb));
+ if (r <= 0) {
+ DEBUG_WARNING("recv: BIO_write %d\n", r);
+ ERR_clear_error();
+ *err = ENOMEM;
+ return true;
+ }
+
+ if (SSL_state(tc->ssl) != SSL_ST_OK) {
+
+ if (tc->up) {
+ *err = EPROTO;
+ return true;
+ }
+
+ if (tc->active) {
+ *err = tls_connect(tc);
+ }
+ else {
+ *err = tls_accept(tc);
+ }
+
+ DEBUG_INFO("state=0x%04x\n", SSL_state(tc->ssl));
+
+ /* TLS connection is established */
+ if (SSL_state(tc->ssl) != SSL_ST_OK)
+ return true;
+
+ *estab = true;
+ tc->up = true;
+ }
+
+ mbuf_set_pos(mb, 0);
+
+ for (;;) {
+ int n;
+
+ if (mbuf_get_space(mb) < 4096) {
+ *err = mbuf_resize(mb, mb->size + 8192);
+ if (*err)
+ return true;
+ }
+
+ ERR_clear_error();
+
+ n = SSL_read(tc->ssl, mbuf_buf(mb), (int)mbuf_get_space(mb));
+ if (n <= 0) {
+ const int ssl_err = SSL_get_error(tc->ssl, n);
+
+ ERR_clear_error();
+
+ switch (ssl_err) {
+
+ case SSL_ERROR_WANT_READ:
+ break;
+
+ case SSL_ERROR_ZERO_RETURN:
+ *err = ECONNRESET;
+ return true;
+
+ default:
+ *err = EPROTO;
+ return true;
+ }
+
+ break;
+ }
+
+ mb->pos += n;
+ }
+
+ mbuf_set_end(mb, mb->pos);
+ mbuf_set_pos(mb, 0);
+
+ return false;
+}
+
+
+static bool send_handler(int *err, struct mbuf *mb, void *arg)
+{
+ struct tls_conn *tc = arg;
+ int r;
+
+ ERR_clear_error();
+
+ r = SSL_write(tc->ssl, mbuf_buf(mb), (int)mbuf_get_left(mb));
+ if (r <= 0) {
+ DEBUG_WARNING("SSL_write: %d\n", SSL_get_error(tc->ssl, r));
+ ERR_clear_error();
+ *err = EPROTO;
+ }
+
+ return true;
+}
+
+
+/**
+ * Start TLS on a TCP-connection
+ *
+ * @param ptc Pointer to allocated TLS connectioon
+ * @param tls TLS Context
+ * @param tcp TCP Connection
+ * @param layer Protocol stack layer
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int tls_start_tcp(struct tls_conn **ptc, struct tls *tls, struct tcp_conn *tcp,
+ int layer)
+{
+ struct tls_conn *tc;
+ int err;
+
+ if (!ptc || !tls || !tcp)
+ return EINVAL;
+
+ tc = mem_zalloc(sizeof(*tc), destructor);
+ if (!tc)
+ return ENOMEM;
+
+ err = tcp_register_helper(&tc->th, tcp, layer, estab_handler,
+ send_handler, recv_handler, tc);
+ if (err)
+ goto out;
+
+ tc->tcp = mem_ref(tcp);
+
+#ifdef TLS_BIO_OPAQUE
+ tc->biomet = bio_method_tcp();
+ if (!tc->biomet) {
+ err = ENOMEM;
+ goto out;
+ }
+#endif
+
+ err = ENOMEM;
+
+ /* Connect the SSL socket */
+ tc->ssl = SSL_new(tls->ctx);
+ if (!tc->ssl) {
+ DEBUG_WARNING("alloc: SSL_new() failed (ctx=%p)\n", tls->ctx);
+ ERR_clear_error();
+ goto out;
+ }
+
+ tc->sbio_in = BIO_new(BIO_s_mem());
+ if (!tc->sbio_in) {
+ DEBUG_WARNING("alloc: BIO_new() failed\n");
+ ERR_clear_error();
+ goto out;
+ }
+
+
+#ifdef TLS_BIO_OPAQUE
+ tc->sbio_out = BIO_new(tc->biomet);
+#else
+ tc->sbio_out = BIO_new(&bio_tcp_send);
+#endif
+ if (!tc->sbio_out) {
+ DEBUG_WARNING("alloc: BIO_new_socket() failed\n");
+ ERR_clear_error();
+ BIO_free(tc->sbio_in);
+ goto out;
+ }
+
+#ifdef TLS_BIO_OPAQUE
+ BIO_set_data(tc->sbio_out, tc);
+#else
+ tc->sbio_out->ptr = tc;
+#endif
+
+ SSL_set_bio(tc->ssl, tc->sbio_in, tc->sbio_out);
+
+ err = 0;
+
+ out:
+ if (err)
+ mem_deref(tc);
+ else
+ *ptc = tc;
+
+ return err;
+}
diff --git a/src/tls/openssl/tls_udp.c b/src/tls/openssl/tls_udp.c
new file mode 100644
index 0000000..4ec81a3
--- /dev/null
+++ b/src/tls/openssl/tls_udp.c
@@ -0,0 +1,883 @@
+/**
+ * @file openssl/tls_udp.c DTLS backend using OpenSSL
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+
+#include <sys/time.h>
+#include <openssl/ssl.h>
+#include <openssl/err.h>
+#include <re_types.h>
+#include <re_fmt.h>
+#include <re_mem.h>
+#include <re_mbuf.h>
+#include <re_list.h>
+#include <re_hash.h>
+#include <re_sa.h>
+#include <re_srtp.h>
+#include <re_udp.h>
+#include <re_tmr.h>
+#include <re_tls.h>
+#include "tls.h"
+
+
+#define DEBUG_MODULE "dtls"
+#define DEBUG_LEVEL 5
+#include <re_dbg.h>
+
+
+enum {
+ MTU_DEFAULT = 1400,
+ MTU_FALLBACK = 548,
+};
+
+
+struct dtls_sock {
+ struct sa peer;
+ struct udp_helper *uh;
+ struct udp_sock *us;
+ struct hash *ht;
+ struct mbuf *mb;
+ dtls_conn_h *connh;
+ void *arg;
+ size_t mtu;
+};
+
+
+/* NOTE: shadow struct defined in tls_*.c */
+struct tls_conn {
+ SSL *ssl; /* inheritance */
+#ifdef TLS_BIO_OPAQUE
+ BIO_METHOD *biomet;
+#endif
+ BIO *sbio_out;
+ BIO *sbio_in;
+ struct tmr tmr;
+ struct sa peer;
+ struct le he;
+ struct dtls_sock *sock;
+ dtls_estab_h *estabh;
+ dtls_recv_h *recvh;
+ dtls_close_h *closeh;
+ void *arg;
+ bool active;
+ bool up;
+};
+
+
+static int bio_create(BIO *b)
+{
+#ifdef TLS_BIO_OPAQUE
+ BIO_set_init(b, 1);
+ BIO_set_data(b, NULL);
+ BIO_set_flags(b, 0);
+#else
+ b->init = 1;
+ b->num = 0;
+ b->ptr = NULL;
+ b->flags = 0;
+#endif
+
+ return 1;
+}
+
+
+static int bio_destroy(BIO *b)
+{
+ if (!b)
+ return 0;
+
+#ifdef TLS_BIO_OPAQUE
+ BIO_set_init(b, 0);
+ BIO_set_data(b, NULL);
+ BIO_set_flags(b, 0);
+#else
+ b->ptr = NULL;
+ b->init = 0;
+ b->flags = 0;
+#endif
+
+ return 1;
+}
+
+
+static int bio_write(BIO *b, const char *buf, int len)
+{
+#ifdef TLS_BIO_OPAQUE
+ struct tls_conn *tc = BIO_get_data(b);
+#else
+ struct tls_conn *tc = b->ptr;
+#endif
+ struct mbuf *mb;
+ enum {SPACE = 4};
+ int err;
+
+ mb = mbuf_alloc(SPACE + len);
+ if (!mb)
+ return -1;
+
+ mb->pos = SPACE;
+ (void)mbuf_write_mem(mb, (void *)buf, len);
+ mb->pos = SPACE;
+
+ err = udp_send_helper(tc->sock->us, &tc->peer, mb, tc->sock->uh);
+
+ mem_deref(mb);
+
+ return err ? -1 : len;
+}
+
+
+static long bio_ctrl(BIO *b, int cmd, long num, void *ptr)
+{
+#ifdef TLS_BIO_OPAQUE
+ struct tls_conn *tc = BIO_get_data(b);
+#else
+ struct tls_conn *tc = b->ptr;
+#endif
+ (void)num;
+ (void)ptr;
+
+ switch (cmd) {
+
+ case BIO_CTRL_FLUSH:
+ /* The OpenSSL library needs this */
+ return 1;
+
+#if defined (BIO_CTRL_DGRAM_QUERY_MTU)
+ case BIO_CTRL_DGRAM_QUERY_MTU:
+ return tc ? tc->sock->mtu : MTU_DEFAULT;
+#endif
+
+#if defined (BIO_CTRL_DGRAM_GET_FALLBACK_MTU)
+ case BIO_CTRL_DGRAM_GET_FALLBACK_MTU:
+ return MTU_FALLBACK;
+#endif
+ }
+
+ return 0;
+}
+
+
+#ifdef TLS_BIO_OPAQUE
+
+static BIO_METHOD *bio_method_udp(void)
+{
+ BIO_METHOD *method;
+
+ method = BIO_meth_new(BIO_TYPE_SOURCE_SINK, "udp_send");
+ if (!method) {
+ DEBUG_WARNING("alloc: BIO_meth_new() failed\n");
+ ERR_clear_error();
+ return NULL;
+ }
+
+ BIO_meth_set_write(method, bio_write);
+ BIO_meth_set_ctrl(method, bio_ctrl);
+ BIO_meth_set_create(method, bio_create);
+ BIO_meth_set_destroy(method, bio_destroy);
+
+ return method;
+}
+
+#else
+
+static struct bio_method_st bio_udp_send = {
+ BIO_TYPE_SOURCE_SINK,
+ "udp_send",
+ bio_write,
+ 0,
+ 0,
+ 0,
+ bio_ctrl,
+ bio_create,
+ bio_destroy,
+ 0
+};
+
+#endif
+
+
+static void tls_close(struct tls_conn *tc)
+{
+ int r;
+
+ if (!tc->ssl)
+ return;
+
+ r = SSL_shutdown(tc->ssl);
+ if (r <= 0)
+ ERR_clear_error();
+
+ SSL_free(tc->ssl);
+ tc->ssl = NULL;
+}
+
+
+static void conn_destructor(void *arg)
+{
+ struct tls_conn *tc = arg;
+
+ hash_unlink(&tc->he);
+ tmr_cancel(&tc->tmr);
+ tls_close(tc);
+
+#ifdef TLS_BIO_OPAQUE
+ if (tc->biomet)
+ BIO_meth_free(tc->biomet);
+#endif
+
+ mem_deref(tc->sock);
+}
+
+
+static void conn_close(struct tls_conn *tc, int err)
+{
+ tmr_cancel(&tc->tmr);
+ tls_close(tc);
+ tc->up = false;
+
+ if (tc->closeh)
+ tc->closeh(err, tc->arg);
+}
+
+
+#if defined (DTLS_CTRL_HANDLE_TIMEOUT) && defined(DTLS_CTRL_GET_TIMEOUT)
+
+static void check_timer(struct tls_conn *tc);
+
+
+static void timeout(void *arg)
+{
+ struct tls_conn *tc = arg;
+
+ DEBUG_INFO("timeout\n");
+
+ if (0 <= DTLSv1_handle_timeout(tc->ssl)) {
+
+ check_timer(tc);
+ }
+ else {
+ ERR_clear_error();
+ conn_close(tc, ETIMEDOUT);
+ }
+}
+
+
+static void check_timer(struct tls_conn *tc)
+{
+ struct timeval tv = {0, 0};
+
+ if (1 == DTLSv1_get_timeout(tc->ssl, &tv)) {
+
+ tmr_start(&tc->tmr, tv.tv_sec * 1000 + tv.tv_usec / 1000,
+ timeout, tc);
+ }
+ else {
+ tmr_cancel(&tc->tmr);
+ }
+}
+
+#else
+
+static void check_timer(struct tls_conn *tc)
+{
+ (void)tc;
+}
+
+#endif
+
+
+static int tls_connect(struct tls_conn *tc)
+{
+ int r;
+
+ ERR_clear_error();
+
+ r = SSL_connect(tc->ssl);
+ if (r <= 0) {
+ const int ssl_err = SSL_get_error(tc->ssl, r);
+
+ tls_flush_error();
+
+ switch (ssl_err) {
+
+ case SSL_ERROR_WANT_READ:
+ break;
+
+ default:
+ DEBUG_WARNING("connect error: %i\n", ssl_err);
+ return EPROTO;
+ }
+ }
+
+ check_timer(tc);
+
+ return 0;
+}
+
+
+static int tls_accept(struct tls_conn *tc)
+{
+ int r;
+
+ ERR_clear_error();
+
+ r = SSL_accept(tc->ssl);
+ if (r <= 0) {
+ const int ssl_err = SSL_get_error(tc->ssl, r);
+
+ tls_flush_error();
+
+ switch (ssl_err) {
+
+ case SSL_ERROR_WANT_READ:
+ break;
+
+ default:
+ DEBUG_WARNING("accept error: %i\n", ssl_err);
+ return EPROTO;
+ }
+ }
+
+ check_timer(tc);
+
+ return 0;
+}
+
+
+static void conn_recv(struct tls_conn *tc, struct mbuf *mb)
+{
+ int err, r;
+
+ if (!tc->ssl)
+ return;
+
+ /* feed SSL data to the BIO */
+ r = BIO_write(tc->sbio_in, mbuf_buf(mb), (int)mbuf_get_left(mb));
+ if (r <= 0) {
+ DEBUG_WARNING("receive bio write error: %i\n", r);
+ ERR_clear_error();
+ conn_close(tc, ENOMEM);
+ return;
+ }
+
+ if (SSL_state(tc->ssl) != SSL_ST_OK) {
+
+ if (tc->up) {
+ conn_close(tc, EPROTO);
+ return;
+ }
+
+ if (tc->active) {
+ err = tls_connect(tc);
+ }
+ else {
+ err = tls_accept(tc);
+ }
+
+ if (err) {
+ conn_close(tc, err);
+ return;
+ }
+
+ DEBUG_INFO("%s: state=0x%04x\n",
+ tc->active ? "client" : "server",
+ SSL_state(tc->ssl));
+
+ /* TLS connection is established */
+ if (SSL_state(tc->ssl) != SSL_ST_OK)
+ return;
+
+ tc->up = true;
+
+ if (tc->estabh) {
+ uint32_t nrefs;
+
+ mem_ref(tc);
+
+ tc->estabh(tc->arg);
+
+ nrefs = mem_nrefs(tc);
+ mem_deref(tc);
+
+ /* check if connection was deref'd from handler */
+ if (nrefs == 1)
+ return;
+ }
+ }
+
+ mbuf_set_pos(mb, 0);
+
+ for (;;) {
+ int n;
+
+ if (mbuf_get_space(mb) < 4096) {
+ err = mbuf_resize(mb, mb->size + 8192);
+ if (err) {
+ conn_close(tc, err);
+ return;
+ }
+ }
+
+ ERR_clear_error();
+
+ n = SSL_read(tc->ssl, mbuf_buf(mb), (int)mbuf_get_space(mb));
+ if (n <= 0) {
+ const int ssl_err = SSL_get_error(tc->ssl, n);
+
+ ERR_clear_error();
+
+ switch (ssl_err) {
+
+ case SSL_ERROR_WANT_READ:
+ break;
+
+ case SSL_ERROR_ZERO_RETURN:
+ conn_close(tc, ECONNRESET);
+ return;
+
+ default:
+ DEBUG_WARNING("read error: %i\n", ssl_err);
+ conn_close(tc, EPROTO);
+ return;
+ }
+
+ break;
+ }
+
+ mb->pos += n;
+ }
+
+ mbuf_set_end(mb, mb->pos);
+ mbuf_set_pos(mb, 0);
+
+ if (tc->recvh && mbuf_get_left(mb) > 0)
+ tc->recvh(mb, tc->arg);
+
+ return;
+}
+
+
+static int conn_alloc(struct tls_conn **ptc, struct tls *tls,
+ struct dtls_sock *sock, const struct sa *peer,
+ dtls_estab_h *estabh, dtls_recv_h *recvh,
+ dtls_close_h *closeh, void *arg)
+{
+ struct tls_conn *tc;
+ int err = 0;
+
+ tc = mem_zalloc(sizeof(*tc), conn_destructor);
+ if (!tc)
+ return ENOMEM;
+
+ hash_append(sock->ht, sa_hash(peer, SA_ALL), &tc->he, tc);
+
+ tc->sock = mem_ref(sock);
+ tc->peer = *peer;
+ tc->estabh = estabh;
+ tc->recvh = recvh;
+ tc->closeh = closeh;
+ tc->arg = arg;
+
+#ifdef TLS_BIO_OPAQUE
+ tc->biomet = bio_method_udp();
+ if (!tc->biomet) {
+ err = ENOMEM;
+ goto out;
+ }
+#endif
+
+ /* Connect the SSL socket */
+ tc->ssl = SSL_new(tls->ctx);
+ if (!tc->ssl) {
+ DEBUG_WARNING("ssl new failed: %i\n",
+ ERR_GET_REASON(ERR_get_error()));
+ ERR_clear_error();
+ err = ENOMEM;
+ goto out;
+ }
+
+ tc->sbio_in = BIO_new(BIO_s_mem());
+ if (!tc->sbio_in) {
+ ERR_clear_error();
+ err = ENOMEM;
+ goto out;
+ }
+
+#ifdef TLS_BIO_OPAQUE
+ tc->sbio_out = BIO_new(tc->biomet);
+#else
+ tc->sbio_out = BIO_new(&bio_udp_send);
+#endif
+ if (!tc->sbio_out) {
+ ERR_clear_error();
+ BIO_free(tc->sbio_in);
+ err = ENOMEM;
+ goto out;
+ }
+
+#ifdef TLS_BIO_OPAQUE
+ BIO_set_data(tc->sbio_out, tc);
+#else
+ tc->sbio_out->ptr = tc;
+#endif
+
+ SSL_set_bio(tc->ssl, tc->sbio_in, tc->sbio_out);
+
+ SSL_set_read_ahead(tc->ssl, 1);
+
+ out:
+ if (err)
+ mem_deref(tc);
+ else
+ *ptc = tc;
+
+ return err;
+}
+
+
+/**
+ * DTLS Connect
+ *
+ * @param ptc Pointer to allocated DTLS connection
+ * @param tls TLS Context
+ * @param sock DTLS Socket
+ * @param peer Peer address
+ * @param estabh Establish handler
+ * @param recvh Receive handler
+ * @param closeh Close handler
+ * @param arg Handler argument
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int dtls_connect(struct tls_conn **ptc, struct tls *tls,
+ struct dtls_sock *sock, const struct sa *peer,
+ dtls_estab_h *estabh, dtls_recv_h *recvh,
+ dtls_close_h *closeh, void *arg)
+{
+ struct tls_conn *tc;
+ int err;
+
+ if (!ptc || !tls || !sock || !peer)
+ return EINVAL;
+
+ err = conn_alloc(&tc, tls, sock, peer, estabh, recvh, closeh, arg);
+ if (err)
+ return err;
+
+ tc->active = true;
+
+ err = tls_connect(tc);
+ if (err)
+ goto out;
+
+ out:
+ if (err)
+ mem_deref(tc);
+ else
+ *ptc = tc;
+
+ return err;
+}
+
+
+/**
+ * DTLS Accept
+ *
+ * @param ptc Pointer to allocated DTLS connection
+ * @param tls TLS Context
+ * @param sock DTLS Socket
+ * @param estabh Establish handler
+ * @param recvh Receive handler
+ * @param closeh Close handler
+ * @param arg Handler argument
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int dtls_accept(struct tls_conn **ptc, struct tls *tls,
+ struct dtls_sock *sock,
+ dtls_estab_h *estabh, dtls_recv_h *recvh,
+ dtls_close_h *closeh, void *arg)
+{
+ struct tls_conn *tc;
+ int err, r;
+
+ if (!ptc || !tls || !sock || !sock->mb)
+ return EINVAL;
+
+ err = conn_alloc(&tc, tls, sock, &sock->peer, estabh, recvh, closeh,
+ arg);
+ if (err)
+ return err;
+
+ tc->active = false;
+
+ r = BIO_write(tc->sbio_in, mbuf_buf(sock->mb),
+ (int)mbuf_get_left(sock->mb));
+ if (r <= 0) {
+ DEBUG_WARNING("accept bio write error: %i\n", r);
+ ERR_clear_error();
+ err = ENOMEM;
+ goto out;
+ }
+
+ err = tls_accept(tc);
+ if (err)
+ goto out;
+
+ sock->mb = mem_deref(sock->mb);
+
+ out:
+ if (err)
+ mem_deref(tc);
+ else
+ *ptc = tc;
+
+ return err;
+}
+
+
+/**
+ * Send data on a DTLS connection
+ *
+ * @param tc DTLS connection
+ * @param mb Buffer to send
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int dtls_send(struct tls_conn *tc, struct mbuf *mb)
+{
+ int r;
+
+ if (!tc || !mb)
+ return EINVAL;
+
+ if (!tc->up || !tc->ssl)
+ return ENOTCONN;
+
+ ERR_clear_error();
+
+ r = SSL_write(tc->ssl, mbuf_buf(mb), (int)mbuf_get_left(mb));
+ if (r <= 0) {
+ DEBUG_WARNING("write error: %i\n", SSL_get_error(tc->ssl, r));
+ ERR_clear_error();
+ return EPROTO;
+ }
+
+ return 0;
+}
+
+
+/**
+ * Set handlers on a DTLS Connection
+ *
+ * @param tc DTLS Connection
+ * @param estabh DTLS Connection Established handler
+ * @param recvh DTLS Connection Receive data handler
+ * @param closeh DTLS Connection Close handler
+ * @param arg Handler argument
+ */
+void dtls_set_handlers(struct tls_conn *tc, dtls_estab_h *estabh,
+ dtls_recv_h *recvh, dtls_close_h *closeh, void *arg)
+{
+ if (!tc)
+ return;
+
+ tc->estabh = estabh;
+ tc->recvh = recvh;
+ tc->closeh = closeh;
+ tc->arg = arg;
+}
+
+
+/**
+ * Get the remote peer of a DTLS Connection
+ *
+ * @param tc DTLS Connection
+ *
+ * @return Remote peer
+ */
+const struct sa *dtls_peer(const struct tls_conn *tc)
+{
+ return tc ? &tc->peer : NULL;
+}
+
+
+/**
+ * Set the remote peer of a DTLS Connection
+ *
+ * @param tc DTLS Connection
+ * @param peer Peer address
+ */
+void dtls_set_peer(struct tls_conn *tc, const struct sa *peer)
+{
+ if (!tc || !peer)
+ return;
+
+ hash_unlink(&tc->he);
+ hash_append(tc->sock->ht, sa_hash(peer, SA_ALL), &tc->he, tc);
+
+ tc->peer = *peer;
+}
+
+
+static void sock_destructor(void *arg)
+{
+ struct dtls_sock *sock = arg;
+
+ hash_clear(sock->ht);
+ mem_deref(sock->uh);
+ mem_deref(sock->us);
+ mem_deref(sock->ht);
+ mem_deref(sock->mb);
+}
+
+
+static bool cmp_handler(struct le *le, void *arg)
+{
+ struct tls_conn *tc = le->data;
+
+ return sa_cmp(&tc->peer, arg, SA_ALL);
+}
+
+
+static struct tls_conn *conn_lookup(struct dtls_sock *sock,
+ const struct sa *peer)
+{
+ return list_ledata(hash_lookup(sock->ht, sa_hash(peer, SA_ALL),
+ cmp_handler, (void *)peer));
+}
+
+
+static bool recv_handler(struct sa *src, struct mbuf *mb, void *arg)
+{
+ struct dtls_sock *sock = arg;
+ struct tls_conn *tc;
+ uint8_t b;
+
+ if (mbuf_get_left(mb) < 1)
+ return false;
+
+ b = mb->buf[mb->pos];
+ if (b < 20 || b > 63)
+ return false;
+
+ tc = conn_lookup(sock, src);
+ if (tc) {
+ conn_recv(tc, mb);
+ return true;
+ }
+
+ if (sock->connh) {
+
+ mem_deref(sock->mb);
+ sock->mb = mem_ref(mb);
+ sock->peer = *src;
+
+ sock->connh(src, sock->arg);
+ }
+
+ return true;
+}
+
+
+/**
+ * Create DTLS Socket
+ *
+ * @param sockp Pointer to returned DTLS Socket
+ * @param laddr Local listen address (optional)
+ * @param us External UDP socket (optional)
+ * @param htsize Connection hash table size
+ * @param layer UDP protocol layer
+ * @param connh Connect handler
+ * @param arg Handler argument
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int dtls_listen(struct dtls_sock **sockp, const struct sa *laddr,
+ struct udp_sock *us, uint32_t htsize, int layer,
+ dtls_conn_h *connh, void *arg)
+{
+ struct dtls_sock *sock;
+ int err;
+
+ if (!sockp)
+ return EINVAL;
+
+ sock = mem_zalloc(sizeof(*sock), sock_destructor);
+ if (!sock)
+ return ENOMEM;
+
+ if (us) {
+ sock->us = mem_ref(us);
+ }
+ else {
+ err = udp_listen(&sock->us, laddr, NULL, NULL);
+ if (err)
+ goto out;
+ }
+
+ err = udp_register_helper(&sock->uh, sock->us, layer,
+ NULL, recv_handler, sock);
+ if (err)
+ goto out;
+
+ err = hash_alloc(&sock->ht, hash_valid_size(htsize));
+ if (err)
+ goto out;
+
+ sock->mtu = MTU_DEFAULT;
+ sock->connh = connh;
+ sock->arg = arg;
+
+ out:
+ if (err)
+ mem_deref(sock);
+ else
+ *sockp = sock;
+
+ return err;
+}
+
+
+/**
+ * Get the underlying UDP socket of a DTLS Socket
+ *
+ * @param sock DTLS Socket
+ *
+ * @return UDP Socket
+ */
+struct udp_sock *dtls_udp_sock(struct dtls_sock *sock)
+{
+ return sock ? sock->us : NULL;
+}
+
+
+/**
+ * Set MTU on a DTLS Socket
+ *
+ * @param sock DTLS Socket
+ * @param mtu MTU value
+ */
+void dtls_set_mtu(struct dtls_sock *sock, size_t mtu)
+{
+ if (!sock)
+ return;
+
+ sock->mtu = mtu;
+}
+
+
+void dtls_recv_packet(struct dtls_sock *sock, const struct sa *src,
+ struct mbuf *mb)
+{
+ struct sa addr;
+
+ if (!sock || !src || !mb)
+ return;
+
+ addr = *src;
+
+ recv_handler(&addr, mb, sock);
+}