560 lines
9.9 KiB
C
560 lines
9.9 KiB
C
/**
|
|
* @file openssl/tls.c TLS backend using OpenSSL
|
|
*
|
|
* Copyright (C) 2010 Creytiv.com
|
|
*/
|
|
#include <string.h>
|
|
#define OPENSSL_NO_KRB5 1
|
|
#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_sys.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;
|
|
};
|
|
|
|
|
|
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);
|
|
}
|
|
|
|
|
|
/**
|
|
* 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:
|
|
tls->ctx = SSL_CTX_new(DTLSv1_method());
|
|
break;
|
|
#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
|
|
|
|
if (method == TLS_METHOD_DTLSV1) {
|
|
SSL_CTX_set_read_ahead(tls->ctx, 1);
|
|
}
|
|
|
|
/* 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 capath Path to CA certificates
|
|
*
|
|
* @return 0 if success, otherwise errorcode
|
|
*/
|
|
int tls_add_ca(struct tls *tls, const char *capath)
|
|
{
|
|
if (!tls || !capath)
|
|
return EINVAL;
|
|
|
|
/* Load the CAs we trust */
|
|
if (!(SSL_CTX_load_verify_locations(tls->ctx, capath, 0))) {
|
|
DEBUG_WARNING("Can't read CA list: %s\n", capath);
|
|
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;
|
|
RSA *rsa = NULL;
|
|
int r, err = ENOMEM;
|
|
|
|
if (!tls || !cn)
|
|
return EINVAL;
|
|
|
|
rsa = RSA_generate_key(1024, RSA_F4, NULL, NULL);
|
|
if (!rsa)
|
|
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 (err)
|
|
ERR_clear_error();
|
|
|
|
return err;
|
|
}
|
|
|
|
|
|
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;
|
|
|
|
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
|
|
}
|