Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions src/vmime/net/imap/IMAPConnection.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -364,6 +364,13 @@ void IMAPConnection::authenticateSASL() {
shared_ptr <security::sasl::SASLContext> saslContext =
security::sasl::SASLContext::create();

#if VMIME_HAVE_TLS_SUPPORT
// Allow using mechanisms with channel binding ("-PLUS" variants)
if (m_secured) {
saslContext->setChannelBindingData(dynamicCast <tls::TLSSocket>(m_socket));
}
#endif // VMIME_HAVE_TLS_SUPPORT

for (unsigned int i = 0 ; i < saslMechs.size() ; ++i) {

try {
Expand Down
7 changes: 7 additions & 0 deletions src/vmime/net/pop3/POP3Connection.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -439,6 +439,13 @@ void POP3Connection::authenticateSASL() {
shared_ptr <security::sasl::SASLContext> saslContext =
security::sasl::SASLContext::create();

#if VMIME_HAVE_TLS_SUPPORT
// Allow using mechanisms with channel binding ("-PLUS" variants)
if (m_secured) {
saslContext->setChannelBindingData(dynamicCast <tls::TLSSocket>(m_socket));
}
#endif // VMIME_HAVE_TLS_SUPPORT

for (unsigned int i = 0 ; i < saslMechs.size() ; ++i) {

try {
Expand Down
7 changes: 7 additions & 0 deletions src/vmime/net/smtp/SMTPConnection.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -390,6 +390,13 @@ void SMTPConnection::authenticateSASL() {
shared_ptr <security::sasl::SASLContext> saslContext =
security::sasl::SASLContext::create();

#if VMIME_HAVE_TLS_SUPPORT
// Allow using mechanisms with channel binding ("-PLUS" variants)
if (m_secured) {
saslContext->setChannelBindingData(dynamicCast <tls::TLSSocket>(m_socket));
}
#endif // VMIME_HAVE_TLS_SUPPORT

for (unsigned int i = 0 ; i < saslMechs.size() ; ++i) {

try {
Expand Down
21 changes: 21 additions & 0 deletions src/vmime/net/tls/TLSSocket.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,27 @@ class VMIME_EXPORT TLSSocket : public socket {
*/
virtual shared_ptr <security::cert::certificateChain> getPeerCertificates() = 0;

/** Channel binding types (see RFC 5056).
*/
enum ChannelBindingType {
CHANNEL_BINDING_TLS_UNIQUE, /**< "tls-unique" (RFC 5929), not defined for TLS 1.3. */
CHANNEL_BINDING_TLS_EXPORTER /**< "tls-exporter" (RFC 9266), defined for TLS 1.3,
or TLS 1.2 with Extended Master Secret. */
};

/** Return the channel binding data for this connection, which can
* be used to bind an authentication to the TLS channel (for example,
* by the SASL "-PLUS" mechanisms).
*
* @param type channel binding type
* @param data will receive the channel binding data
* @return true if channel binding data of the specified type is
* available, or false otherwise (eg. if the handshake has not been
* performed yet, or if the type is not defined for the negotiated
* TLS version)
*/
virtual bool getChannelBindingData(const ChannelBindingType type, byteArray& data) = 0;

/** Test whether the specified host is an IPv4 or IPv6 address
* rather than a host name. IP addresses must not be sent in
* the Server Name Indication (SNI) extension (RFC 6066).
Expand Down
47 changes: 47 additions & 0 deletions src/vmime/net/tls/gnutls/TLSSocket_GnuTLS.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -520,6 +520,53 @@ shared_ptr <security::cert::certificateChain> TLSSocket_GnuTLS::getPeerCertifica
}


bool TLSSocket_GnuTLS::getChannelBindingData(const ChannelBindingType type, byteArray& data) {

if (!m_connected) {
return false;
}

gnutls_channel_binding_t gnutlsType;

switch (type) {

case CHANNEL_BINDING_TLS_UNIQUE:

#if defined(GNUTLS_VERSION_NUMBER) && GNUTLS_VERSION_NUMBER >= 0x030603
// "tls-unique" is not defined for TLS 1.3 (RFC 9266)
if (gnutls_protocol_get_version(*m_session->m_gnutlsSession) == GNUTLS_TLS1_3) {
return false;
}
#endif // GNUTLS_VERSION_NUMBER >= 0x030603

gnutlsType = GNUTLS_CB_TLS_UNIQUE;
break;

#if defined(GNUTLS_VERSION_NUMBER) && GNUTLS_VERSION_NUMBER >= 0x030702
case CHANNEL_BINDING_TLS_EXPORTER:

gnutlsType = GNUTLS_CB_TLS_EXPORTER;
break;
#endif // GNUTLS_VERSION_NUMBER >= 0x030702

default:

return false;
}

gnutls_datum_t cb;

if (gnutls_session_channel_binding(*m_session->m_gnutlsSession, gnutlsType, &cb) != GNUTLS_E_SUCCESS) {
return false;
}

data.assign(cb.data, cb.data + cb.size);
gnutls_free(cb.data);

return !data.empty();
}


// Following is a workaround for C++ exceptions to pass correctly between
// C and C++ calls.
//
Expand Down
2 changes: 2 additions & 0 deletions src/vmime/net/tls/gnutls/TLSSocket_GnuTLS.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ class TLSSocket_GnuTLS : public TLSSocket {

shared_ptr <security::cert::certificateChain> getPeerCertificates();

bool getChannelBindingData(const ChannelBindingType type, byteArray& data);

// Implementation of 'socket'
void connect(const string& address, const port_t port);
void disconnect();
Expand Down
71 changes: 71 additions & 0 deletions src/vmime/net/tls/openssl/TLSSocket_OpenSSL.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -514,6 +514,77 @@ shared_ptr <security::cert::certificateChain> TLSSocket_OpenSSL::getPeerCertific
}


bool TLSSocket_OpenSSL::getChannelBindingData(const ChannelBindingType type, byteArray& data) {

if (!m_connected || !m_ssl) {
return false;
}

#ifdef TLS1_3_VERSION
const bool isTLS13 = (SSL_version(m_ssl) >= TLS1_3_VERSION);
#else
const bool isTLS13 = false;
#endif

switch (type) {

case CHANNEL_BINDING_TLS_UNIQUE: {

// "tls-unique" is not defined for TLS 1.3 (RFC 9266)
if (isTLS13) {
return false;
}

// First Finished message of the handshake (RFC 5929): the one we sent
// for a full handshake, or the server's one for a resumed session
unsigned char finished[EVP_MAX_MD_SIZE];

const size_t len = SSL_session_reused(m_ssl)
? SSL_get_peer_finished(m_ssl, finished, sizeof(finished))
: SSL_get_finished(m_ssl, finished, sizeof(finished));

if (len == 0 || len > sizeof(finished)) {
return false;
}

data.assign(finished, finished + len);
return true;
}

case CHANNEL_BINDING_TLS_EXPORTER: {

// "tls-exporter" is only defined for TLS 1.3, or TLS 1.2 with
// Extended Master Secret (RFC 9266)
if (!isTLS13) {
#ifdef SSL_get_extms_support
if (SSL_get_extms_support(m_ssl) != 1) {
return false;
}
#else
return false;
#endif // SSL_get_extms_support
}

static const char label[] = "EXPORTER-Channel-Binding";
static const unsigned char context[] = "";
unsigned char exporter[32];

if (SSL_export_keying_material(
m_ssl, exporter, sizeof(exporter),
label, sizeof(label) - 1, context, 0, /* use_context */ 1) != 1) {

return false;
}

data.assign(exporter, exporter + sizeof(exporter));
return true;
}
}

return false;
}


void TLSSocket_OpenSSL::internalThrow() {

if (!!m_ex) {
Expand Down
2 changes: 2 additions & 0 deletions src/vmime/net/tls/openssl/TLSSocket_OpenSSL.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@ class TLSSocket_OpenSSL : public TLSSocket {

shared_ptr <security::cert::certificateChain> getPeerCertificates();

bool getChannelBindingData(const ChannelBindingType type, byteArray& data);

// Implementation of 'socket'
void connect(const string& address, const port_t port);
void disconnect();
Expand Down
74 changes: 74 additions & 0 deletions src/vmime/security/sasl/SASLContext.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,11 @@
#include "vmime/utility/outputStreamStringAdapter.hpp"
#include "vmime/utility/inputStreamStringAdapter.hpp"
#include "vmime/utility/inputStreamByteBufferAdapter.hpp"
#include "vmime/utility/stringUtils.hpp"

#if VMIME_HAVE_TLS_SUPPORT
# include "vmime/net/tls/TLSSocket.hpp"
#endif // VMIME_HAVE_TLS_SUPPORT


namespace vmime {
Expand Down Expand Up @@ -83,12 +88,81 @@ shared_ptr <SASLSession> SASLContext::createSession(

shared_ptr <SASLMechanism> SASLContext::createMechanism(const string& name) {

// Mechanisms with channel binding would always fail without channel binding data
if (isChannelBindingMechanism(name) && !hasChannelBindingData()) {
throw exceptions::no_such_mechanism(name);
}

return SASLMechanismFactory::getInstance()->create(
dynamicCast <SASLContext>(shared_from_this()), name
);
}


bool SASLContext::setChannelBindingData(const string& type, const byteArray& data) {

const bool supported =
type == "tls-unique"
#if defined(GSASL_VERSION_NUMBER) && GSASL_VERSION_NUMBER >= 0x020100
|| type == "tls-exporter"
#endif
;

if (!supported || data.empty()) {
return false;
}

m_channelBindingType = type;
m_channelBindingData = data;

return true;
}


#if VMIME_HAVE_TLS_SUPPORT

void SASLContext::setChannelBindingData(const shared_ptr <net::tls::TLSSocket>& sok) {

if (!sok) {
return;
}

// Prefer "tls-unique", which is the default for TLS 1.2 and earlier (RFC 5802);
// it is not defined for TLS 1.3, for which "tls-exporter" is used (RFC 9266)
byteArray data;

if (sok->getChannelBindingData(net::tls::TLSSocket::CHANNEL_BINDING_TLS_UNIQUE, data)
&& setChannelBindingData("tls-unique", data)) {

return;
}

if (sok->getChannelBindingData(net::tls::TLSSocket::CHANNEL_BINDING_TLS_EXPORTER, data)) {
setChannelBindingData("tls-exporter", data);
}
}

#endif // VMIME_HAVE_TLS_SUPPORT


bool SASLContext::hasChannelBindingData() const {

return !m_channelBindingData.empty();
}


// static
bool SASLContext::isChannelBindingMechanism(const string& name) {

static const string suffix = "-PLUS";

return name.length() > suffix.length()
&& utility::stringUtils::isStringEqualNoCase(
string(name.end() - suffix.length(), name.end()), suffix
);
}


shared_ptr <SASLMechanism> SASLContext::suggestMechanism(
const std::vector <shared_ptr <SASLMechanism> >& mechs
) {
Expand Down
54 changes: 54 additions & 0 deletions src/vmime/security/sasl/SASLContext.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,19 @@


namespace vmime {


#if VMIME_HAVE_TLS_SUPPORT
namespace net {
namespace tls {

class TLSSocket;

} // tls
} // net
#endif // VMIME_HAVE_TLS_SUPPORT


namespace security {
namespace sasl {

Expand Down Expand Up @@ -93,6 +106,40 @@ class VMIME_EXPORT SASLContext : public object, public enable_shared_from_this <
const std::vector <shared_ptr <SASLMechanism> >& mechs
);

/** Set the channel binding data of the connection on which
* authentication takes place (see RFC 5056). This is required
* to use the mechanisms which support channel binding (the
* "-PLUS" variants): they are not available otherwise.
*
* @param type channel binding type, as registered by IANA
* ("tls-unique" or "tls-exporter")
* @param data channel binding data
* @return true if the channel binding type is supported, or
* false otherwise (in this case, the data is ignored)
*/
bool setChannelBindingData(const string& type, const byteArray& data);

#if VMIME_HAVE_TLS_SUPPORT

/** Set the channel binding data from the TLS socket on which
* authentication takes place. "tls-unique" is used if it is
* available (TLS 1.2 and earlier), otherwise "tls-exporter"
* is used (TLS 1.3).
*
* @param sok TLS socket
*/
void setChannelBindingData(const shared_ptr <net::tls::TLSSocket>& sok);

#endif // VMIME_HAVE_TLS_SUPPORT

/** Return whether channel binding data has been set for
* this context.
*
* @return true if channel binding data is available,
* false otherwise
*/
bool hasChannelBindingData() const;

/** Helper function for decoding Base64-encoded challenge.
*
* @param input input buffer
Expand All @@ -117,7 +164,14 @@ class VMIME_EXPORT SASLContext : public object, public enable_shared_from_this <

static const string getErrorMessage(const string& fname, const int code);

/** Return whether the specified mechanism uses channel binding,
* ie. if it is a "-PLUS" variant. */
static bool isChannelBindingMechanism(const string& name);

Gsasl* m_gsaslContext;

string m_channelBindingType;
byteArray m_channelBindingData;
};


Expand Down
Loading
Loading