Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

extmod/modssl_mbedtls: Implement SSLSession support. #12780

Open
wants to merge 5 commits into
base: master
Choose a base branch
from

Conversation

DvdGiessen
Copy link
Sponsor Contributor

@DvdGiessen DvdGiessen commented Oct 23, 2023

This implements support for the SSLSession class, introduced in CPython in 3.6 (see #2415). It allows saving session data from an active TLS client-side connection and then creating a new connection re-using this session data. Benefits include a faster handshake and reduced data usage for short connections.

It adds the SSLSession class, the session= parameter for the SSLContext.wrap_socket() method, and the session attribute for an SSLSocket object.

Additionally, I've added a non-standard part: The SSLSession.serialize() function that converts the session to a bytes object (also available via the buffer protocol, so perhaps exposing this function is redundant); so that it can be stored by the user, and a constructor for the SSLSession object that accepts a bytes-like object to reconstruct the session object (CPython doesn't allow direct construction). This allows storing the session somewhere and use it after a deep sleep or reboot.

The second commit adds server-side support for TLS tickets in the Unix port, so that we can meaningfully test the session resumption in tests. The third commit adds a test which tests session resumption using the SSLSession object, checking that the resumption worked by checking that a resuming consumes less data.

micropython/micropython-lib#829 is a companion MR that implements support in the ssl module wrapper. It is required for the tests to pass.

A small example test, using a wrapper class around the TCP socket so we can count how many bytes of data we're sending/receiving:

from io import IOBase
import socket
import ssl
import time

import network
wlan = network.WLAN(network.STA_IF)
wlan.active(True)
wlan.connect('my-ssid', 'my-password')
while wlan.ifconfig()[0] == '0.0.0.0':
    time.sleep(0.1)

class AccountingStream(IOBase):
    def __init__(self, stream):
        self.stream = stream
        self.bytes_read = 0
        self.bytes_written = 0
        for attr in dir(stream):
            if not hasattr(self, attr):
                value = getattr(stream, attr)
                if callable(value):
                    setattr(self, attr, value)
    def read(self, size=None):
        result = self.stream.read() if size is None else self.stream.read(size)
        self.bytes_read += len(result)
        return result
    def readinto(self, buf, nbytes=None):
        result = self.stream.readinto(buf) if nbytes is None else self.stream.readinto(buf, nbytes)
        self.bytes_read += result
        return result
    def write(self, buf):
        self.bytes_written += len(buf)
        return self.stream.write(buf)

def connect_and_count(host, port, session=None):
    addr = socket.getaddrinfo(host, port)[0][-1]
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.settimeout(10)
    sock.connect(addr)
    sock_accounting = AccountingStream(sock)
    sock_tls = ssl.wrap_socket(sock_accounting, cert_reqs=ssl.CERT_NONE, server_hostname=host, session=session)
    sock_tls.write('HEAD / HTTP/1.1\r\nHost: {}\r\n\r\n'.format(host).encode())
    print('Response:', sock_tls.readline())
    print('Bytes read and written:', sock_accounting.bytes_read, sock_accounting.bytes_written)
    session = sock_tls.session
    sock.close()
    return session

host, port = 'tls-v1-2.badssl.com', 1012
socket.getaddrinfo(host, port)

print('Clean start:')
t = time.ticks_ms()
session = connect_and_count(host, port, None)
print('Time (ms):', time.ticks_diff(time.ticks_ms(), t))

print('\nReusing SSLSession:')
t = time.ticks_ms()
session = connect_and_count(host, port, session)
print('Time (ms):', time.ticks_diff(time.ticks_ms(), t))

print('\nUsing serialized and parsed SSLSession:')
t = time.ticks_ms()
session = connect_and_count(host, port, ssl.SSLSession(bytes(session)))
print('Time (ms):', time.ticks_diff(time.ticks_ms(), t))
Clean start:
Response: b'HTTP/1.1 200 OK\r\n'
Bytes read and written: 4947 453
Time (ms): 1829

Reusing SSLSession:
Response: b'HTTP/1.1 200 OK\r\n'
Bytes read and written: 438 602
Time (ms): 919

Using serialized and parsed SSLSession:
Response: b'HTTP/1.1 200 OK\r\n'
Bytes read and written: 438 602
Time (ms): 926

@github-actions
Copy link

github-actions bot commented Oct 23, 2023

Code size report:

   bare-arm:    +0 +0.000% 
minimal x86:    +0 +0.000% 
   unix x64: +12128 +1.464% standard[incl +528(data)]
      stm32:    +0 +0.000% PYBV10
     mimxrt:    +0 +0.000% TEENSY40
        rp2:    +0 +0.000% RPI_PICO
       samd:    +0 +0.000% ADAFRUIT_ITSYBITSY_M4_EXPRESS

@codecov
Copy link

codecov bot commented Oct 23, 2023

Codecov Report

Attention: Patch coverage is 90.90909% with 5 lines in your changes are missing coverage. Please review.

Project coverage is 98.37%. Comparing base (8a8c65f) to head (a7c1dc6).

Files Patch % Lines
extmod/modtls_mbedtls.c 90.90% 5 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master   #12780      +/-   ##
==========================================
- Coverage   98.39%   98.37%   -0.02%     
==========================================
  Files         161      161              
  Lines       21204    21257      +53     
==========================================
+ Hits        20864    20912      +48     
- Misses        340      345       +5     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@projectgus
Copy link
Contributor

This is an automated heads-up that we've just merged a Pull Request
that removes the STATIC macro from MicroPython's C API.

See #13763

A search suggests this PR might apply the STATIC macro to some C code. If it
does, then next time you rebase the PR (or merge from master) then you should
please replace all the STATIC keywords with static.

Although this is an automated message, feel free to @-reply to me directly if
you have any questions about this.

@DvdGiessen
Copy link
Sponsor Contributor Author

DvdGiessen commented Mar 19, 2024

Updated on latest master branch, added server-side support for TLS tickets to the Unix port, and added a test that checks (a) that SSLSession works and (b) that session resumption actually results in decreased data usage.

I've been using various versions of this patch for almost a year now to resume HTTPS connections without any trouble (though that might just be because I didn't try with many different configurations).

Marked as ready for review.

EDIT: And re-pushed because I forgot to add the documentation commit.

@DvdGiessen DvdGiessen force-pushed the mbedtls_sslsession branch 3 times, most recently from caeb380 to feae3a7 Compare March 20, 2024 12:57
Signed-off-by: Daniël van de Giessen <daniel@dvdgiessen.nl>
Signed-off-by: Daniël van de Giessen <daniel@dvdgiessen.nl>
Signed-off-by: Daniël van de Giessen <daniel@dvdgiessen.nl>
Signed-off-by: Daniël van de Giessen <daniel@dvdgiessen.nl>
Signed-off-by: Daniël van de Giessen <daniel@dvdgiessen.nl>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants