8000 extmod/modussl: Improve exception error messages. · micropython/micropython@9aa2140 · GitHub
[go: up one dir, main page]

Skip to content

Commit 9aa2140

Browse files
tvedpgeorge
authored andcommitted
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.
1 parent c7f7c02 commit 9aa2140

File tree

7 files changed

+173
-9
lines changed

7 files changed

+173
-9
lines changed

extmod/modussl_axtls.c

Lines changed: 52 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929

3030
#include "py/runtime.h"
3131
#include "py/stream.h"
32+
#include "py/objstr.h"
3233

3334
#if MICROPY_PY_USSL && MICROPY_SSL_AXTLS
3435

@@ -54,6 +55,56 @@ struct ssl_args {
5455

5556
STATIC const mp_obj_type_t ussl_socket_type;
5657

58+
// Table of errors
59+
struct ssl_errs {
60+
int16_t errnum;
61+
const char *errstr;
62+
};
63+
STATIC const struct ssl_errs ssl_error_tab[] = {
64+
{ SSL_NOT_OK, "NOT_OK" },
65+
{ SSL_ERROR_DEAD, "DEAD" },
66+
{ SSL_CLOSE_NOTIFY, "CLOSE_NOTIFY" },
67+
{ SSL_EAGAIN, "EAGAIN" },
68+
{ SSL_ERROR_CONN_LOST, "CONN_LOST" },
69+
{ SSL_ERROR_RECORD_OVERFLOW, "RECORD_OVERFLOW" },
70+
{ SSL_ERROR_SOCK_SETUP_FAILURE, "SOCK_SETUP_FAILURE" },
71+
{ SSL_ERROR_INVALID_HANDSHAKE, "INVALID_HANDSHAKE" },
72+
{ SSL_ERROR_INVALID_PROT_MSG, "INVALID_PROT_MSG" },
73+
{ SSL_ERROR_INVALID_HMAC, "INVALID_HMAC" },
74+
{ SSL_ERROR_INVALID_VERSION, "INVALID_VERSION" },
75+
{ SSL_ERROR_UNSUPPORTED_EXTENSION, "UNSUPPORTED_EXTENSION" },
76+
{ SSL_ERROR_INVALID_SESSION, "INVALID_SESSION" },
77+
{ SSL_ERROR_NO_CIPHER, "NO_CIPHER" },
78+
{ SSL_ERROR_INVALID_CERT_HASH_ALG, "INVALID_CERT_HASH_ALG" },
79+
{ SSL_ERROR_BAD_CERTIFICATE, "BAD_CERTIFICATE" },
80+
{ SSL_ERROR_INVALID_KEY, "INVALID_KEY" },
81+
{ SSL_ERROR_FINISHED_INVALID, "FINISHED_INVALID" },
82+
{ SSL_ERROR_NO_CERT_DEFINED, "NO_CERT_DEFINED" },
83+
{ SSL_ERROR_NO_CLIENT_RENOG, "NO_CLIENT_RENOG" },
84+
{ SSL_ERROR_NOT_SUPPORTED, "NOT_SUPPORTED" },
85+
};
86+
87+
STATIC NORETURN void ussl_raise_error(int err) {
88+
for (size_t i = 0; i < MP_ARRAY_SIZE(ssl_error_tab); i++) {
89+
if (ssl_error_tab[i].errnum == err) {
90+
// construct string object
91+
mp_obj_str_t *o_str = m_new_obj_maybe(mp_obj_str_t);
92+
if (o_str == NULL) {
93+
break;
94+
}
95+
o_str->base.type = &mp_type_str;
96+
o_str->data = (const byte *)ssl_error_tab[i].errstr;
97+
o_str->len = strlen((char *)o_str->data);
98+
o_str->hash = qstr_compute_hash(o_str->data, o_str->len);
99+
// raise
100+
mp_obj_t args[2] = { MP_OBJ_NEW_SMALL_INT(err), MP_OBJ_FROM_PTR(o_str)};
101+
nlr_raise(mp_obj_exception_make_new(&mp_type_OSError, 2, 0, args));
102+
}
103+
}
104+
mp_raise_OSError(err);
105+
}
106+
107+
57108
STATIC mp_obj_ssl_socket_t *ussl_socket_new(mp_obj_t sock, struct ssl_args *args) {
58109
#if MICROPY_PY_USSL_FINALISER
59110
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
107158
int res = ssl_handshake_status(o->ssl_sock);
108159

109160
if (res != SSL_OK) {
110-
printf("ssl_handshake_status: %d\n", res);
111-
ssl_display_error(res);
112-
mp_raise_OSError(MP_EIO);
161+
ussl_raise_error(res);
113162
}
114163
}
115164

extmod/modussl_mbedtls.c

Lines changed: 40 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434

3535
#include "py/runtime.h"
3636
#include "py/stream.h"
37+
#include "py/objstr.h"
3738

3839
// mbedtls_time_t
3940
#include "mbedtls/platform.h"
@@ -43,6 +44,7 @@
4344
#include "mbedtls/entropy.h"
4445
#include "mbedtls/ctr_drbg.h"
4546
#include "mbedtls/debug.h"
47+
#include "mbedtls/error.h"
4648

4749
typedef struct _mp_obj_ssl_socket_t {
4850
mp_obj_base_t base;
@@ -74,6 +76,42 @@ STATIC void mbedtls_debug(void *ctx, int level, const char *file, int line, cons
7476
}
7577
#endif
7678

79+
STATIC NORETURN void mbedtls_raise_error(int err) {
80+
#if defined(MBEDTLS_ERROR_C)
81+
// Including mbedtls_strerror takes about 16KB on the esp32 due to all the strings.
82+
// MBEDTLS_ERROR_C is the define used by mbedtls to conditionally include mbedtls_strerror.
83+
// It is set/unset in the MBEDTLS_CONFIG_FILE which is defined in the Makefile.
84+
// "small" negative integer error codes come from underlying stream/sockets, not mbedtls
85+
if (err < 0 && err > -256) {
86+
mp_raise_OSError(-err);
87+
}
88+
89+
// Try to allocate memory for the message
90+
#define ERR_STR_MAX 100 // mbedtls_strerror truncates if it doesn't fit
91+
mp_obj_str_t *o_str = m_new_obj_maybe(mp_obj_str_t);
92+
byte *o_str_buf = m_new_maybe(byte, ERR_STR_MAX);
93+
if (o_str == NULL || o_str_buf == NULL) {
94+
mp_raise_OSError(err);
95+
}
96+
97+
// print the error message into the allocated buffer
98+
mbedtls_strerror(err, (char *)o_str_buf, ERR_STR_MAX);
99+
size_t len = strnlen((char *)o_str_buf, ERR_STR_MAX);
100+
101+
// Put the exception object together
102+
o_str->base.type = &mp_type_str;
103+
o_str->data = o_str_buf;
104+
o_str->len = len;
105+
o_str->hash = qstr_compute_hash(o_str->data, o_str->len);
106+
// raise
107+
mp_obj_t args[2] = { MP_OBJ_NEW_SMALL_INT(err), MP_OBJ_FROM_PTR(o_str)};
108+
nlr_raise(mp_obj_exception_make_new(&mp_type_OSError, 2, 0, args));
109+
#else
110+
// mbedtls is compiled without error strings so we simply return the err number
111+
mp_raise_OSError(err); // typ. err is negative
112+
#endif
113+
}
114+
77115
STATIC int _mbedtls_ssl_send(void *ctx, const byte *buf, size_t len) {
78116
mp_obj_t sock = *(mp_obj_t *)ctx;
79117

@@ -85,7 +123,7 @@ STATIC int _mbedtls_ssl_send(void *ctx, const byte *buf, size_t len) {
85123
if (mp_is_nonblocking_error(err)) {
86124
return MBEDTLS_ERR_SSL_WANT_WRITE;
87125
}
88-
return -err;
126+
return -err; // convert an MP_ERRNO to something mbedtls passes through as error
89127
} else {
90128
return out_sz;
91129
}
@@ -197,7 +235,6 @@ STATIC mp_obj_ssl_socket_t *socket_new(mp_obj_t sock, struct ssl_args *args) {
197235
if (args->do_handshake.u_bool) {
198236
while ((ret = mbedtls_ssl_handshake(&o->ssl)) != 0) {
199237
if (ret != MBEDTLS_ERR_SSL_WANT_READ && ret != MBEDTLS_ERR_SSL_WANT_WRITE) {
200-
printf("mbedtls_ssl_handshake error: -%x\n", -ret);
201238
goto cleanup;
202239
}
203240
}
@@ -221,7 +258,7 @@ STATIC mp_obj_ssl_socket_t *socket_new(mp_obj_t sock, struct ssl_args *args) {
221258
} else if (ret == MBEDTLS_ERR_X509_BAD_INPUT_DATA) {
222259
mp_raise_ValueError(MP_ERROR_TEXT("invalid cert"));
223260
} else {
224-
mp_raise_OSError(MP_EIO);
261+
mbedtls_raise_error(ret);
225262
}
226263
}
227264

tests/extmod/ussl_basic.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99

1010
# create in client mode
1111
try:
12-
ss = ssl.wrap_socket(io.BytesIO())
12+
ss = ssl.wrap_socket(io.BytesIO(), server_hostname="test.example.com")
1313
except OSError as er:
1414
print("wrap_socket:", repr(er))
1515

tests/extmod/ussl_basic.py.exp

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
ssl_handshake_status: -256
2-
wrap_socket: OSError(5,)
1+
wrap_socket: OSError(-256, 'CONN_LOST')
32
<_SSLSocket
43
4
54
b''

tests/net_inet/tls_num_errors.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
# test that modtls produces a numerical error message when out of heap
2+
3+
try:
4+
import usocket as socket, ussl as ssl, sys
5+
except:
6+
import socket, ssl, sys
7+
try:
8+
from micropython import alloc_emergency_exception_buf, heap_lock, heap_unlock
9+
except:
10+
print("SKIP")
11+
raise SystemExit
12+
13+
14+
# test with heap locked to see it switch to number-only error message
15+
def test(addr):
16+
alloc_emergency_exception_buf(256)
17+
s = socket.socket()
18+
s.connect(addr)
19+
try:
20+
s.setblocking(False)
21+
s = ssl.wrap_socket(s, do_handshake=False)
22+
heap_lock()
23+
print("heap is locked")
24+
while True:
25+
ret = s.write("foo")
26+
if ret:
27+
break
28+
heap_unlock()
29+
print("wrap: no exception")
30+
except OSError as e:
31+
heap_unlock()
32+
# mbedtls produces "-29184"
33+
# axtls produces "RECORD_OVERFLOW"
34+
ok = "-29184" in str(e) or "RECORD_OVERFLOW" in str(e)
35+
print("wrap:", ok)
36+
if not ok:
37+
print("got exception:", e)
38+
s.close()
39+
40+
41+
if __name__ == "__main__":
42+
# connect to plain HTTP port, oops!
43+
addr = socket.getaddrinfo("micropython.org", 80)[0][-1]
44+
test(addr)

tests/net_inet/tls_num_errors.py.exp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
heap is locked
2+
wrap: True

tests/net_inet/tls_text_errors.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# test that modtls produces a text error message
2+
3+
try:
4+
import usocket as socket, ussl as ssl, sys
5+
except:
6+
import socket, ssl, sys
7+
8+
9+
def test(addr):
10+
s = socket.socket()
11+
s.connect(addr)
12+
try:
13+
s = ssl.wrap_socket(s)
14+
print("wrap: no exception")
15+
except OSError as e:
16+
# mbedtls produces "mbedtls -0x7200: SSL - An invalid SSL record was received"
17+
# axtls produces "RECORD_OVERFLOW"
18+
# CPython produces "[SSL: WRONG_VERSION_NUMBER] wrong version number (_ssl.c:1108)"
19+
ok = (
20+
"invalid SSL record" in str(e)
21+
or "RECORD_OVERFLOW" in str(e)
22+
or "wrong version" in str(e)
23+
)
24+
print("wrap:", ok)
25+
if not ok:
26+
print("got exception:", e)
27+
s.close()
28+
29+
30+
if __name__ == "__main__":
31+
# connect to plain HTTP port, oops!
32+
addr = socket.getaddrinfo("micropython.org", 80)[0][-1]
33+
test(addr)

0 commit comments

Comments
 (0)
0