diff options
Diffstat (limited to 'crypto/tlssession.c')
-rw-r--r-- | crypto/tlssession.c | 186 |
1 files changed, 132 insertions, 54 deletions
diff --git a/crypto/tlssession.c b/crypto/tlssession.c index 1e98f44..6d8f8df 100644 --- a/crypto/tlssession.c +++ b/crypto/tlssession.c @@ -44,6 +44,13 @@ struct QCryptoTLSSession { QCryptoTLSSessionReadFunc readFunc; void *opaque; char *peername; + + /* + * Allow concurrent reads and writes, so track + * errors separately + */ + Error *rerr; + Error *werr; }; @@ -54,6 +61,9 @@ qcrypto_tls_session_free(QCryptoTLSSession *session) return; } + error_free(session->rerr); + error_free(session->werr); + gnutls_deinit(session->handle); g_free(session->hostname); g_free(session->peername); @@ -67,13 +77,26 @@ static ssize_t qcrypto_tls_session_push(void *opaque, const void *buf, size_t len) { QCryptoTLSSession *session = opaque; + ssize_t ret; if (!session->writeFunc) { errno = EIO; return -1; }; - return session->writeFunc(buf, len, session->opaque); + error_free(session->werr); + session->werr = NULL; + + ret = session->writeFunc(buf, len, session->opaque, &session->werr); + if (ret == QCRYPTO_TLS_SESSION_ERR_BLOCK) { + errno = EAGAIN; + return -1; + } else if (ret < 0) { + errno = EIO; + return -1; + } else { + return ret; + } } @@ -81,13 +104,26 @@ static ssize_t qcrypto_tls_session_pull(void *opaque, void *buf, size_t len) { QCryptoTLSSession *session = opaque; + ssize_t ret; if (!session->readFunc) { errno = EIO; return -1; }; - return session->readFunc(buf, len, session->opaque); + error_free(session->rerr); + session->rerr = NULL; + + ret = session->readFunc(buf, len, session->opaque, &session->rerr); + if (ret == QCRYPTO_TLS_SESSION_ERR_BLOCK) { + errno = EAGAIN; + return -1; + } else if (ret < 0) { + errno = EIO; + return -1; + } else { + return ret; + } } #define TLS_PRIORITY_ADDITIONAL_ANON "+ANON-DH" @@ -441,23 +477,25 @@ qcrypto_tls_session_set_callbacks(QCryptoTLSSession *session, ssize_t qcrypto_tls_session_write(QCryptoTLSSession *session, const char *buf, - size_t len) + size_t len, + Error **errp) { ssize_t ret = gnutls_record_send(session->handle, buf, len); if (ret < 0) { - switch (ret) { - case GNUTLS_E_AGAIN: - errno = EAGAIN; - break; - case GNUTLS_E_INTERRUPTED: - errno = EINTR; - break; - default: - errno = EIO; - break; + if (ret == GNUTLS_E_AGAIN) { + return QCRYPTO_TLS_SESSION_ERR_BLOCK; + } else { + if (session->werr) { + error_propagate(errp, session->werr); + session->werr = NULL; + } else { + error_setg(errp, + "Cannot write to TLS channel: %s", + gnutls_strerror(ret)); + } + return -1; } - ret = -1; } return ret; @@ -467,26 +505,29 @@ qcrypto_tls_session_write(QCryptoTLSSession *session, ssize_t qcrypto_tls_session_read(QCryptoTLSSession *session, char *buf, - size_t len) + size_t len, + bool gracefulTermination, + Error **errp) { ssize_t ret = gnutls_record_recv(session->handle, buf, len); if (ret < 0) { - switch (ret) { - case GNUTLS_E_AGAIN: - errno = EAGAIN; - break; - case GNUTLS_E_INTERRUPTED: - errno = EINTR; - break; - case GNUTLS_E_PREMATURE_TERMINATION: - errno = ECONNABORTED; - break; - default: - errno = EIO; - break; + if (ret == GNUTLS_E_AGAIN) { + return QCRYPTO_TLS_SESSION_ERR_BLOCK; + } else if ((ret == GNUTLS_E_PREMATURE_TERMINATION) && + gracefulTermination){ + return 0; + } else { + if (session->rerr) { + error_propagate(errp, session->rerr); + session->rerr = NULL; + } else { + error_setg(errp, + "Cannot read from TLS channel: %s", + gnutls_strerror(ret)); + } + return -1; } - ret = -1; } return ret; @@ -505,35 +546,69 @@ qcrypto_tls_session_handshake(QCryptoTLSSession *session, Error **errp) { int ret = gnutls_handshake(session->handle); - if (ret == 0) { + if (!ret) { session->handshakeComplete = true; + return QCRYPTO_TLS_HANDSHAKE_COMPLETE; + } + + if (ret == GNUTLS_E_INTERRUPTED || ret == GNUTLS_E_AGAIN) { + int direction = gnutls_record_get_direction(session->handle); + return direction ? QCRYPTO_TLS_HANDSHAKE_SENDING : + QCRYPTO_TLS_HANDSHAKE_RECVING; + } + + if (session->rerr || session->werr) { + error_setg(errp, "TLS handshake failed: %s: %s", + gnutls_strerror(ret), + error_get_pretty(session->rerr ? + session->rerr : session->werr)); } else { - if (ret == GNUTLS_E_INTERRUPTED || - ret == GNUTLS_E_AGAIN) { - ret = 1; - } else { - error_setg(errp, "TLS handshake failed: %s", - gnutls_strerror(ret)); - ret = -1; - } + error_setg(errp, "TLS handshake failed: %s", + gnutls_strerror(ret)); } - return ret; + error_free(session->rerr); + error_free(session->werr); + session->rerr = session->werr = NULL; + + return -1; } -QCryptoTLSSessionHandshakeStatus -qcrypto_tls_session_get_handshake_status(QCryptoTLSSession *session) +int +qcrypto_tls_session_bye(QCryptoTLSSession *session, Error **errp) { - if (session->handshakeComplete) { - return QCRYPTO_TLS_HANDSHAKE_COMPLETE; - } else if (gnutls_record_get_direction(session->handle) == 0) { - return QCRYPTO_TLS_HANDSHAKE_RECVING; + int ret; + + if (!session->handshakeComplete) { + return 0; + } + + ret = gnutls_bye(session->handle, GNUTLS_SHUT_WR); + + if (!ret) { + return QCRYPTO_TLS_BYE_COMPLETE; + } + + if (ret == GNUTLS_E_INTERRUPTED || ret == GNUTLS_E_AGAIN) { + int direction = gnutls_record_get_direction(session->handle); + return direction ? QCRYPTO_TLS_BYE_SENDING : QCRYPTO_TLS_BYE_RECVING; + } + + if (session->rerr || session->werr) { + error_setg(errp, "TLS termination failed: %s: %s", gnutls_strerror(ret), + error_get_pretty(session->rerr ? + session->rerr : session->werr)); } else { - return QCRYPTO_TLS_HANDSHAKE_SENDING; + error_setg(errp, "TLS termination failed: %s", gnutls_strerror(ret)); } -} + error_free(session->rerr); + error_free(session->werr); + session->rerr = session->werr = NULL; + + return -1; +} int qcrypto_tls_session_get_key_size(QCryptoTLSSession *session, @@ -605,9 +680,10 @@ qcrypto_tls_session_set_callbacks( ssize_t qcrypto_tls_session_write(QCryptoTLSSession *sess, const char *buf, - size_t len) + size_t len, + Error **errp) { - errno = -EIO; + error_setg(errp, "TLS requires GNUTLS support"); return -1; } @@ -615,9 +691,11 @@ qcrypto_tls_session_write(QCryptoTLSSession *sess, ssize_t qcrypto_tls_session_read(QCryptoTLSSession *sess, char *buf, - size_t len) + size_t len, + bool gracefulTermination, + Error **errp) { - errno = -EIO; + error_setg(errp, "TLS requires GNUTLS support"); return -1; } @@ -638,10 +716,10 @@ qcrypto_tls_session_handshake(QCryptoTLSSession *sess, } -QCryptoTLSSessionHandshakeStatus -qcrypto_tls_session_get_handshake_status(QCryptoTLSSession *sess) +int +qcrypto_tls_session_bye(QCryptoTLSSession *session, Error **errp) { - return QCRYPTO_TLS_HANDSHAKE_COMPLETE; + return QCRYPTO_TLS_BYE_COMPLETE; } |