extmod/modussl: Improve exception error messages.
This commit adds human readable error messages when mbedtls or axtls raise an exception. Currently often just an EIO error is raised so the user is lost and can't tell whether it's a cert error, buffer overrun, connecting to a non-ssl port, etc. The axtls and mbedtls error raising in the ussl module is modified to raise: OSError(-err_num, "error string") For axtls a small error table of strings is added and used for the second argument of the OSErrer. For mbedtls the code uses mbedtls' built-in strerror function, and if there is an out of memory condition it just produces OSError(-err_num). Producing the error string for mbedtls is conditional on them being included in the mbedtls build, via MBEDTLS_ERROR_C.
This commit is contained in:
parent
c7f7c0214c
commit
9aa214077e
|
@ -29,6 +29,7 @@
|
||||||
|
|
||||||
#include "py/runtime.h"
|
#include "py/runtime.h"
|
||||||
#include "py/stream.h"
|
#include "py/stream.h"
|
||||||
|
#include "py/objstr.h"
|
||||||
|
|
||||||
#if MICROPY_PY_USSL && MICROPY_SSL_AXTLS
|
#if MICROPY_PY_USSL && MICROPY_SSL_AXTLS
|
||||||
|
|
||||||
|
@ -54,6 +55,56 @@ struct ssl_args {
|
||||||
|
|
||||||
STATIC const mp_obj_type_t ussl_socket_type;
|
STATIC const mp_obj_type_t ussl_socket_type;
|
||||||
|
|
||||||
|
// Table of errors
|
||||||
|
struct ssl_errs {
|
||||||
|
int16_t errnum;
|
||||||
|
const char *errstr;
|
||||||
|
};
|
||||||
|
STATIC const struct ssl_errs ssl_error_tab[] = {
|
||||||
|
{ SSL_NOT_OK, "NOT_OK" },
|
||||||
|
{ SSL_ERROR_DEAD, "DEAD" },
|
||||||
|
{ SSL_CLOSE_NOTIFY, "CLOSE_NOTIFY" },
|
||||||
|
{ SSL_EAGAIN, "EAGAIN" },
|
||||||
|
{ SSL_ERROR_CONN_LOST, "CONN_LOST" },
|
||||||
|
{ SSL_ERROR_RECORD_OVERFLOW, "RECORD_OVERFLOW" },
|
||||||
|
{ SSL_ERROR_SOCK_SETUP_FAILURE, "SOCK_SETUP_FAILURE" },
|
||||||
|
{ SSL_ERROR_INVALID_HANDSHAKE, "INVALID_HANDSHAKE" },
|
||||||
|
{ SSL_ERROR_INVALID_PROT_MSG, "INVALID_PROT_MSG" },
|
||||||
|
{ SSL_ERROR_INVALID_HMAC, "INVALID_HMAC" },
|
||||||
|
{ SSL_ERROR_INVALID_VERSION, "INVALID_VERSION" },
|
||||||
|
{ SSL_ERROR_UNSUPPORTED_EXTENSION, "UNSUPPORTED_EXTENSION" },
|
||||||
|
{ SSL_ERROR_INVALID_SESSION, "INVALID_SESSION" },
|
||||||
|
{ SSL_ERROR_NO_CIPHER, "NO_CIPHER" },
|
||||||
|
{ SSL_ERROR_INVALID_CERT_HASH_ALG, "INVALID_CERT_HASH_ALG" },
|
||||||
|
{ SSL_ERROR_BAD_CERTIFICATE, "BAD_CERTIFICATE" },
|
||||||
|
{ SSL_ERROR_INVALID_KEY, "INVALID_KEY" },
|
||||||
|
{ SSL_ERROR_FINISHED_INVALID, "FINISHED_INVALID" },
|
||||||
|
{ SSL_ERROR_NO_CERT_DEFINED, "NO_CERT_DEFINED" },
|
||||||
|
{ SSL_ERROR_NO_CLIENT_RENOG, "NO_CLIENT_RENOG" },
|
||||||
|
{ SSL_ERROR_NOT_SUPPORTED, "NOT_SUPPORTED" },
|
||||||
|
};
|
||||||
|
|
||||||
|
STATIC NORETURN void ussl_raise_error(int err) {
|
||||||
|
for (size_t i = 0; i < MP_ARRAY_SIZE(ssl_error_tab); i++) {
|
||||||
|
if (ssl_error_tab[i].errnum == err) {
|
||||||
|
// construct string object
|
||||||
|
mp_obj_str_t *o_str = m_new_obj_maybe(mp_obj_str_t);
|
||||||
|
if (o_str == NULL) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
o_str->base.type = &mp_type_str;
|
||||||
|
o_str->data = (const byte *)ssl_error_tab[i].errstr;
|
||||||
|
o_str->len = strlen((char *)o_str->data);
|
||||||
|
o_str->hash = qstr_compute_hash(o_str->data, o_str->len);
|
||||||
|
// raise
|
||||||
|
mp_obj_t args[2] = { MP_OBJ_NEW_SMALL_INT(err), MP_OBJ_FROM_PTR(o_str)};
|
||||||
|
nlr_raise(mp_obj_exception_make_new(&mp_type_OSError, 2, 0, args));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mp_raise_OSError(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
STATIC mp_obj_ssl_socket_t *ussl_socket_new(mp_obj_t sock, struct ssl_args *args) {
|
STATIC mp_obj_ssl_socket_t *ussl_socket_new(mp_obj_t sock, struct ssl_args *args) {
|
||||||
#if MICROPY_PY_USSL_FINALISER
|
#if MICROPY_PY_USSL_FINALISER
|
||||||
mp_obj_ssl_socket_t *o = m_new_obj_with_finaliser(mp_obj_ssl_socket_t);
|
mp_obj_ssl_socket_t *o = m_new_obj_with_finaliser(mp_obj_ssl_socket_t);
|
||||||
|
@ -107,9 +158,7 @@ STATIC mp_obj_ssl_socket_t *ussl_socket_new(mp_obj_t sock, struct ssl_args *args
|
||||||
int res = ssl_handshake_status(o->ssl_sock);
|
int res = ssl_handshake_status(o->ssl_sock);
|
||||||
|
|
||||||
if (res != SSL_OK) {
|
if (res != SSL_OK) {
|
||||||
printf("ssl_handshake_status: %d\n", res);
|
ussl_raise_error(res);
|
||||||
ssl_display_error(res);
|
|
||||||
mp_raise_OSError(MP_EIO);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -34,6 +34,7 @@
|
||||||
|
|
||||||
#include "py/runtime.h"
|
#include "py/runtime.h"
|
||||||
#include "py/stream.h"
|
#include "py/stream.h"
|
||||||
|
#include "py/objstr.h"
|
||||||
|
|
||||||
// mbedtls_time_t
|
// mbedtls_time_t
|
||||||
#include "mbedtls/platform.h"
|
#include "mbedtls/platform.h"
|
||||||
|
@ -43,6 +44,7 @@
|
||||||
#include "mbedtls/entropy.h"
|
#include "mbedtls/entropy.h"
|
||||||
#include "mbedtls/ctr_drbg.h"
|
#include "mbedtls/ctr_drbg.h"
|
||||||
#include "mbedtls/debug.h"
|
#include "mbedtls/debug.h"
|
||||||
|
#include "mbedtls/error.h"
|
||||||
|
|
||||||
typedef struct _mp_obj_ssl_socket_t {
|
typedef struct _mp_obj_ssl_socket_t {
|
||||||
mp_obj_base_t base;
|
mp_obj_base_t base;
|
||||||
|
@ -74,6 +76,42 @@ STATIC void mbedtls_debug(void *ctx, int level, const char *file, int line, cons
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
STATIC NORETURN void mbedtls_raise_error(int err) {
|
||||||
|
#if defined(MBEDTLS_ERROR_C)
|
||||||
|
// Including mbedtls_strerror takes about 16KB on the esp32 due to all the strings.
|
||||||
|
// MBEDTLS_ERROR_C is the define used by mbedtls to conditionally include mbedtls_strerror.
|
||||||
|
// It is set/unset in the MBEDTLS_CONFIG_FILE which is defined in the Makefile.
|
||||||
|
// "small" negative integer error codes come from underlying stream/sockets, not mbedtls
|
||||||
|
if (err < 0 && err > -256) {
|
||||||
|
mp_raise_OSError(-err);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to allocate memory for the message
|
||||||
|
#define ERR_STR_MAX 100 // mbedtls_strerror truncates if it doesn't fit
|
||||||
|
mp_obj_str_t *o_str = m_new_obj_maybe(mp_obj_str_t);
|
||||||
|
byte *o_str_buf = m_new_maybe(byte, ERR_STR_MAX);
|
||||||
|
if (o_str == NULL || o_str_buf == NULL) {
|
||||||
|
mp_raise_OSError(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
// print the error message into the allocated buffer
|
||||||
|
mbedtls_strerror(err, (char *)o_str_buf, ERR_STR_MAX);
|
||||||
|
size_t len = strnlen((char *)o_str_buf, ERR_STR_MAX);
|
||||||
|
|
||||||
|
// Put the exception object together
|
||||||
|
o_str->base.type = &mp_type_str;
|
||||||
|
o_str->data = o_str_buf;
|
||||||
|
o_str->len = len;
|
||||||
|
o_str->hash = qstr_compute_hash(o_str->data, o_str->len);
|
||||||
|
// raise
|
||||||
|
mp_obj_t args[2] = { MP_OBJ_NEW_SMALL_INT(err), MP_OBJ_FROM_PTR(o_str)};
|
||||||
|
nlr_raise(mp_obj_exception_make_new(&mp_type_OSError, 2, 0, args));
|
||||||
|
#else
|
||||||
|
// mbedtls is compiled without error strings so we simply return the err number
|
||||||
|
mp_raise_OSError(err); // typ. err is negative
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
STATIC int _mbedtls_ssl_send(void *ctx, const byte *buf, size_t len) {
|
STATIC int _mbedtls_ssl_send(void *ctx, const byte *buf, size_t len) {
|
||||||
mp_obj_t sock = *(mp_obj_t *)ctx;
|
mp_obj_t sock = *(mp_obj_t *)ctx;
|
||||||
|
|
||||||
|
@ -85,7 +123,7 @@ STATIC int _mbedtls_ssl_send(void *ctx, const byte *buf, size_t len) {
|
||||||
if (mp_is_nonblocking_error(err)) {
|
if (mp_is_nonblocking_error(err)) {
|
||||||
return MBEDTLS_ERR_SSL_WANT_WRITE;
|
return MBEDTLS_ERR_SSL_WANT_WRITE;
|
||||||
}
|
}
|
||||||
return -err;
|
return -err; // convert an MP_ERRNO to something mbedtls passes through as error
|
||||||
} else {
|
} else {
|
||||||
return out_sz;
|
return out_sz;
|
||||||
}
|
}
|
||||||
|
@ -197,7 +235,6 @@ STATIC mp_obj_ssl_socket_t *socket_new(mp_obj_t sock, struct ssl_args *args) {
|
||||||
if (args->do_handshake.u_bool) {
|
if (args->do_handshake.u_bool) {
|
||||||
while ((ret = mbedtls_ssl_handshake(&o->ssl)) != 0) {
|
while ((ret = mbedtls_ssl_handshake(&o->ssl)) != 0) {
|
||||||
if (ret != MBEDTLS_ERR_SSL_WANT_READ && ret != MBEDTLS_ERR_SSL_WANT_WRITE) {
|
if (ret != MBEDTLS_ERR_SSL_WANT_READ && ret != MBEDTLS_ERR_SSL_WANT_WRITE) {
|
||||||
printf("mbedtls_ssl_handshake error: -%x\n", -ret);
|
|
||||||
goto cleanup;
|
goto cleanup;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -221,7 +258,7 @@ cleanup:
|
||||||
} else if (ret == MBEDTLS_ERR_X509_BAD_INPUT_DATA) {
|
} else if (ret == MBEDTLS_ERR_X509_BAD_INPUT_DATA) {
|
||||||
mp_raise_ValueError(MP_ERROR_TEXT("invalid cert"));
|
mp_raise_ValueError(MP_ERROR_TEXT("invalid cert"));
|
||||||
} else {
|
} else {
|
||||||
mp_raise_OSError(MP_EIO);
|
mbedtls_raise_error(ret);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -9,7 +9,7 @@ except ImportError:
|
||||||
|
|
||||||
# create in client mode
|
# create in client mode
|
||||||
try:
|
try:
|
||||||
ss = ssl.wrap_socket(io.BytesIO())
|
ss = ssl.wrap_socket(io.BytesIO(), server_hostname="test.example.com")
|
||||||
except OSError as er:
|
except OSError as er:
|
||||||
print("wrap_socket:", repr(er))
|
print("wrap_socket:", repr(er))
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
ssl_handshake_status: -256
|
wrap_socket: OSError(-256, 'CONN_LOST')
|
||||||
wrap_socket: OSError(5,)
|
|
||||||
<_SSLSocket
|
<_SSLSocket
|
||||||
4
|
4
|
||||||
b''
|
b''
|
||||||
|
|
|
@ -0,0 +1,44 @@
|
||||||
|
# test that modtls produces a numerical error message when out of heap
|
||||||
|
|
||||||
|
try:
|
||||||
|
import usocket as socket, ussl as ssl, sys
|
||||||
|
except:
|
||||||
|
import socket, ssl, sys
|
||||||
|
try:
|
||||||
|
from micropython import alloc_emergency_exception_buf, heap_lock, heap_unlock
|
||||||
|
except:
|
||||||
|
print("SKIP")
|
||||||
|
raise SystemExit
|
||||||
|
|
||||||
|
|
||||||
|
# test with heap locked to see it switch to number-only error message
|
||||||
|
def test(addr):
|
||||||
|
alloc_emergency_exception_buf(256)
|
||||||
|
s = socket.socket()
|
||||||
|
s.connect(addr)
|
||||||
|
try:
|
||||||
|
s.setblocking(False)
|
||||||
|
s = ssl.wrap_socket(s, do_handshake=False)
|
||||||
|
heap_lock()
|
||||||
|
print("heap is locked")
|
||||||
|
while True:
|
||||||
|
ret = s.write("foo")
|
||||||
|
if ret:
|
||||||
|
break
|
||||||
|
heap_unlock()
|
||||||
|
print("wrap: no exception")
|
||||||
|
except OSError as e:
|
||||||
|
heap_unlock()
|
||||||
|
# mbedtls produces "-29184"
|
||||||
|
# axtls produces "RECORD_OVERFLOW"
|
||||||
|
ok = "-29184" in str(e) or "RECORD_OVERFLOW" in str(e)
|
||||||
|
print("wrap:", ok)
|
||||||
|
if not ok:
|
||||||
|
print("got exception:", e)
|
||||||
|
s.close()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
# connect to plain HTTP port, oops!
|
||||||
|
addr = socket.getaddrinfo("micropython.org", 80)[0][-1]
|
||||||
|
test(addr)
|
|
@ -0,0 +1,2 @@
|
||||||
|
heap is locked
|
||||||
|
wrap: True
|
|
@ -0,0 +1,33 @@
|
||||||
|
# test that modtls produces a text error message
|
||||||
|
|
||||||
|
try:
|
||||||
|
import usocket as socket, ussl as ssl, sys
|
||||||
|
except:
|
||||||
|
import socket, ssl, sys
|
||||||
|
|
||||||
|
|
||||||
|
def test(addr):
|
||||||
|
s = socket.socket()
|
||||||
|
s.connect(addr)
|
||||||
|
try:
|
||||||
|
s = ssl.wrap_socket(s)
|
||||||
|
print("wrap: no exception")
|
||||||
|
except OSError as e:
|
||||||
|
# mbedtls produces "mbedtls -0x7200: SSL - An invalid SSL record was received"
|
||||||
|
# axtls produces "RECORD_OVERFLOW"
|
||||||
|
# CPython produces "[SSL: WRONG_VERSION_NUMBER] wrong version number (_ssl.c:1108)"
|
||||||
|
ok = (
|
||||||
|
"invalid SSL record" in str(e)
|
||||||
|
or "RECORD_OVERFLOW" in str(e)
|
||||||
|
or "wrong version" in str(e)
|
||||||
|
)
|
||||||
|
print("wrap:", ok)
|
||||||
|
if not ok:
|
||||||
|
print("got exception:", e)
|
||||||
|
s.close()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
# connect to plain HTTP port, oops!
|
||||||
|
addr = socket.getaddrinfo("micropython.org", 80)[0][-1]
|
||||||
|
test(addr)
|
Loading…
Reference in New Issue