|
| 1 | +import uselect |
| 2 | +import ussl |
| 3 | +import io |
| 4 | +from micropython import const |
| 5 | + |
| 6 | +_MP_STREAM_POLL_RD = const(0x0001) |
| 7 | +_MP_STREAM_POLL_WR = const(0x0004) |
| 8 | +_MP_STREAM_POLL_NVAL = const(0x0020) |
| 9 | +_MP_STREAM_POLL = const(3) |
| 10 | +_MP_STREAM_CLOSE = const(4) |
| 11 | + |
| 12 | + |
| 13 | +# Generated with `openssl req -x509 -newkey rsa:2048 -nodes -keyout key.pem -out cert.pem -sha256 -days 36500 -subj '/CN=localhost'` |
| 14 | +certificate = b'0\x82\x03\x0b0\x82\x01\xf3\xa0\x03\x02\x01\x02\x02\x14.\xab\x80\xd7\x8fL\xb5\x83T\xee|;O`\xd2\xc7>\xd1\xf4\x940\r\x06\t*\x86H\x86\xf7\r\x01\x01\x0b\x05\x000\x141\x120\x10\x06\x03U\x04\x03\x0c\tlocalhost0 \x17\r221215172840Z\x18\x0f21221121172840Z0\x141\x120\x10\x06\x03U\x04\x03\x0c\tlocalhost0\x82\x01"0\r\x06\t*\x86H\x86\xf7\r\x01\x01\x01\x05\x00\x03\x82\x01\x0f\x000\x82\x01\n\x02\x82\x01\x01\x00\x8dK\xde\x87:UFX\xf4\xa2\x89Om\xc6}ju\x7f\xa9)\x00\xdf\x9f\xbd\x8aM\x17\x1a\xbf\xfaD?\xbc\xcc6\xabG\x946\xe1\xa6\xc8\x1d\x8bh\x1bV\x0c\xc5\xbd\x11[!*\x0f\xcc\x82\x8a\xee\x96\xf0\xa3\xf0F\xe0\xa3\xe7%\x17??:\x11\x82W\x1f\x16\x8aw\x01\n\x02\xd63\x8a\xceW\xd9!\x15;\xe9L\x1cx\xa9M\xaeR\'\xd1\x95\xbd\x8a0N\xf2\xdbc\x0c\xc5\xd0\x0e\xc7<x(\xf4BJ\xd4CGh\xe5\xc7\xd6\x07]\xae\xeb\x88@\xcfp\xf8%_\xb6\xcfCr\x05l(?\x13\xfc\xb2\xe68\xb5\xf8\xd2(\x97a\x07pV#\x82J\x06\xa1x\xbd)\x89\\\x1e\xcb ;\x8e\xe6\x17\x92\xd2\x03.\xce\x90\xa5^\xfa\xcac\xd65\xe7\x0bR\xac\xff\x96:Dss\x1fiSu\x1a\xe8!+\x13\x8c\xc5\xa0\x8d\x18_{\xcf\xc9\xafa\xde\x02\xc70\x8eMm\xcb\xfa*\x08\xa3\x84\xe7\x1d\xd3-\xe8@\x13\xb3\x7f\xb6#\xcf\xeb\xec\x0c\x04\x98%\xea\x17\xf6*{\x02\x03\x01\x00\x01\xa3S0Q0\x1d\x06\x03U\x1d\x0e\x04\x16\x04\x142;\xee<\xcd\xbf9\xbc\xdc\xb8OP\xcd\x88}K+\xca_#0\x1f\x06\x03U\x1d#\x04\x180\x16\x80\x142;\xee<\xcd\xbf9\xbc\xdc\xb8OP\xcd\x88}K+\xca_#0\x0f\x06\x03U\x1d\x13\x01\x01\xff\x04\x050\x03\x01\x01\xff0\r\x06\t*\x86H\x86\xf7\r\x01\x01\x0b\x05\x00\x03\x82\x01\x01\x00p\x9c\x87\x85\xac\xb6\xa3C5\xff\xaf\'+\xa1\x8b]\x87(\xe1\xee"j\xbc\xe8\x15\xd1f7\xe9\xd4d\xff\x0c\x97\x10H\xc5\x88\xa1g\x15\x94\x1e/m\x93\x17\x84@@\xcd\xa9\x7f\xd9:\x02F\x86\x10\xa06\xe3[\xaf\xff\xa3\xa2\x90\xcb\xcf(\xa8\xd3\xb0\x07p^\xf7\x8a\xe6\xf2\'5\xd6\xe7\xac\x82S\xafZ)7\x81\xc2\xa1 \'\x1ft\xe0\x1b\\Y \xd5\xce\xefH&\xfc\x06\xed%\xc0\n\xb1\xfe\xa8\xc8k\xb8\xdaK\xad\xd3Z\x9d>\x88\xf1\xaa\xb8\xda\xe5\xcd\xd7\xe7\x95\xa0\xf4Q\xc2\xfb\xd7\xcd\x89\x8f1\x0e\x16\xa6\xf9#\x9c$\xaa#\xa8\xaa\x8f=\x8b\xf4\xce\x9c\xf0|\xca\xea%\x02\xd3\x13B\xf4\xfa\xdas\xc8\x15mL\x84\x82\xef\xb0\xe8e\xbc7\x10;\xd5V\xee\'\x1f\xf1d\xc5\xc8\xdf\xd5\xe5\x90\xb2)\x1c\xab;\xe4Jr\xb4B:\xd1\xedD\x8a\x8e\xb9%P\xecuM:(\x82eg\xce8U\x15\xeew\xe8-E\xa8\xa9#\xeb\xb0D\xe7\x99\xbb\xbe\t\xbd\xe6\xcd' |
| 15 | +key = b'0\x82\x04\xbd\x02\x01\x000\r\x06\t*\x86H\x86\xf7\r\x01\x01\x01\x05\x00\x04\x82\x04\xa70\x82\x04\xa3\x02\x01\x00\x02\x82\x01\x01\x00\x8dK\xde\x87:UFX\xf4\xa2\x89Om\xc6}ju\x7f\xa9)\x00\xdf\x9f\xbd\x8aM\x17\x1a\xbf\xfaD?\xbc\xcc6\xabG\x946\xe1\xa6\xc8\x1d\x8bh\x1bV\x0c\xc5\xbd\x11[!*\x0f\xcc\x82\x8a\xee\x96\xf0\xa3\xf0F\xe0\xa3\xe7%\x17??:\x11\x82W\x1f\x16\x8aw\x01\n\x02\xd63\x8a\xceW\xd9!\x15;\xe9L\x1cx\xa9M\xaeR\'\xd1\x95\xbd\x8a0N\xf2\xdbc\x0c\xc5\xd0\x0e\xc7<x(\xf4BJ\xd4CGh\xe5\xc7\xd6\x07]\xae\xeb\x88@\xcfp\xf8%_\xb6\xcfCr\x05l(?\x13\xfc\xb2\xe68\xb5\xf8\xd2(\x97a\x07pV#\x82J\x06\xa1x\xbd)\x89\\\x1e\xcb ;\x8e\xe6\x17\x92\xd2\x03.\xce\x90\xa5^\xfa\xcac\xd65\xe7\x0bR\xac\xff\x96:Dss\x1fiSu\x1a\xe8!+\x13\x8c\xc5\xa0\x8d\x18_{\xcf\xc9\xafa\xde\x02\xc70\x8eMm\xcb\xfa*\x08\xa3\x84\xe7\x1d\xd3-\xe8@\x13\xb3\x7f\xb6#\xcf\xeb\xec\x0c\x04\x98%\xea\x17\xf6*{\x02\x03\x01\x00\x01\x02\x82\x01\x00\x13)HF\xaff\xf4\x10\xc90\'\x8d\xd1u\xba\xf9Np\xdf\x08S\xf9\x7f\x88\xad22!k\nm\xe9\xf4\xd8\xfe\xca\xf0|\x8d\x8a\xc1Z\xdf\x02\xd2=\xee\xc7\xf1\xa2\xe7\xb1#\x88\xb86\xc47B\x0e\xcf\x18tS\xb0P\x19\xdf\x02M\x8d\xed\xfa\x84\xe1\xfa\xb1\xccZB\x16P\xf7*k6\xfb[\xd3,5\x818T\x9e\x1a[\x91\xf9\xcb3\x95jlL\x8b\xd0Lg\x1c\xca\xd0\xa1\xc8l\x0fa\x82ja\ns\x0bB\x92\xf2\xac8\x04\xe5oj\xfc\xa3\x91\xa9\x85\xfd\xbcl8\x1e@\x1a\xc5:\xb7\'\xdbc\xfe\xc0XV(`\xec\x91\x95X\xcd\x91\xf4\xa3w\xf3qt\xc1HJN\xebQ\xdex\xa4\x16\xf0\xae2o\x92+\x0b\xa7\x12\x80qnaR\xb2\t\xdf\x93\xf90N<:\x02\x88K\xe2\x92Z1\xd9\x12?.d\x1e\xd44l\xbe:\xa1m\x89\xda\xb7\x84\xc6\x8d\x1a\xc7\x8b"\x9d\x85\xb3\xe6c\xf5\xd0\x9fox\xe5\xfb\xed\x159\xe5\xa35\x87\x00Z3\xefX\xf9\x02\x81\x81\x00\xb8\x94\x10\xf7`\xba\xf3\xb1\xee7\xc3F\xf2\xf3\x83l\xf8\xde\x9f\x15\x03#\xc4\xaf!Eu\xab\xf7xch\xa2t\xb7@>^5\x7f\xa5\x16\x86\x94r\xdf\x06)G\xc8\xfe\xf9\xa0X\xff\x19\xf5\x197c\xd3\xac\x8f*Y\x18G \xab\x96\xbf\xd7"\x04\xce\x0f\x81=\xbb\x1b\xaaf\x1e2\x96-\x9a\x93\xe5\x14\xa5a\x12\xb4\xe0\xed\xec\x8f\xca\xad\x9d\xeb\x8dJ=\x8aT]\x15\\\x98"h\x18\xc2\x81\xce\xae~%\xc4\x0e\xbb\xc7i\xf0Lc\x02\x81\x81\x00\xc3\xf8aP\xccM\xdb\xf7f\xe4]0M\x14\xeb!\xf4\xf6/\x8e\\\xca\xdf\xdci\x11\xe2|\xe4\x17\xc9\xb3\x89\x96\x0b\x05\x82u\xbaH&\xb1\x98-\xc2-\xc6\xaa\xbc/@k \xddd0DT\xc2K\xed\x15\r\\\x00\xc0S\xc3Y\xc1\xb1\xb1bnov\xf1\xc1\xe1\x8d\xc1\x07\xe2/\xcfd\x15\x98,N\x14\xbe\x1f\x0b\x95\x8a\x112\xa1O\x0c\xd1p\x14\xe6\xf1\x0e1`w\xcdqz\xb6\xfdt\x07J\x1bq\x88\x11\x12\xa6{\x1c\t\t\x02\x81\x80\x04\xc35H\xdc\xc3\x16$\xa2+\xe8*\xfd{\xd1\tO\xc6\x96\xbe9\r\x846\xac\x9a\x196-\xb5z\x83)\xa5\xefP\x86\x0br?\xef\xe7\x8b\xe3j\xaf\\~V.\xd6}dh\xc7tI\x01\xb4\x8f+\xd8\x08\xfd\xa3\xbc\xdf\xa0\xf5,w\x98\xbat\xfbH%"\x8d\xa0b\x1e.kI\xba\xb5\x81Yh\xd5\xf7\x92>\xfe\x8a\xc5\x0e\xd4\xc0\xf4\x11.\xdd\x1a\x87f3\xdc\xa8=\xf8\xadL\xee?\xe10Yj\xea\x0b#G:\xf1\x8bW\x02\x81\x80+b\x98\xc0\xd7\x8aA1\x83\x80\xf94\x91L\x19F:B*\x83\x1c\xfd\xf9\x13\x85\xdbd\xc5\xfb\x85\\\xad7\xbf\x95\x0f\x123\xd8\x1a\xd3\x1e,/\xad6\x8f.\x0b]v\xa8\x80\xed"\x9a \xf6\x96\xd1RZ\x7f\xcb\xa7\x8a\xec\xc0i\xe5\x9c\xdeE\x89gy\xf0\xc9\xd8\x92\x96r\x95[\xbaQQ\n\x90|t\xd1&t]\x15\xe4\xfa\xcd\x85\x7f\xb3\xfaYVKu\xb5\xee\xc2w$1c\xc3\xb6\xe5J=\xcb#\xb1\x8b\xecy\x82\xdai\x02\x81\x81\x00\x9f\xf2~7\\uZQ<$\xa2\xc8k\x94@\xc5\xcbt\x16\xf9\x8c\xf61Uj\xdf\xd2!\xa5\r\xb9Z\x8e\x1fJ\xd2\x96Z?\x9a\x07\x00\xeb\xf7\xc2\xe5\x08\xc9\x95\xb2\xb2\x8e\x8d\t\xc2Q\x16h\xc1\xd8p\x88\'\xa3gTO\x91\xb9\x00 }e`\xca2\x90\xcc5<\x19ws\x8c\x7fo\x13r\xc8\x89\x96j\n6\xbb\xe2\x891\xec)\xa6\x84\xf3\xcbwH\x10\xaeX\xa5\xf4\xd0\xa8\xca\xb0l\x1b\xfd\xd7\xec\xcd<\xb7\xaf\xcfw\xa2$' |
| 16 | + |
| 17 | + |
| 18 | +class _Pipe(io.IOBase): |
| 19 | + def __init__(self): |
| 20 | + self._other = None |
| 21 | + self.block_reads = False |
| 22 | + self.block_writes = False |
| 23 | + |
| 24 | + self.write_buffers = [] |
| 25 | + self.last_poll_arg = None |
| 26 | + |
| 27 | + def readinto(self, buf): |
| 28 | + if self.block_reads or len(self._other.write_buffers) == 0: |
| 29 | + return None |
| 30 | + |
| 31 | + read_buf = self._other.write_buffers[0] |
| 32 | + l = min(len(buf), len(read_buf)) |
| 33 | + buf[:l] = read_buf[:l] |
| 34 | + if l == len(read_buf): |
| 35 | + self._other.write_buffers.pop(0) |
| 36 | + else: |
| 37 | + self._other.write_buffers[0] = read_buf[l:] |
| 38 | + return l |
| 39 | + |
| 40 | + def write(self, buf): |
| 41 | + if self.block_writes: |
| 42 | + return None |
| 43 | + |
| 44 | + self.write_buffers.append(memoryview(bytes(buf))) |
| 45 | + return len(buf) |
| 46 | + |
| 47 | + def ioctl(self, request, arg): |
| 48 | + if request == _MP_STREAM_POLL: |
| 49 | + self.last_poll_arg = arg |
| 50 | + ret = 0 |
| 51 | + if arg & _MP_STREAM_POLL_RD: |
| 52 | + if not self.block_reads and self._other.write_buffers: |
| 53 | + ret |= _MP_STREAM_POLL_RD |
| 54 | + if arg & _MP_STREAM_POLL_WR: |
| 55 | + if not self.block_writes: |
| 56 | + ret |= _MP_STREAM_POLL_WR |
| 57 | + return ret |
| 58 | + |
| 59 | + elif request == _MP_STREAM_CLOSE: |
| 60 | + return 0 |
| 61 | + |
| 62 | + raise NotImplementedError() |
| 63 | + |
| 64 | + @classmethod |
| 65 | + def new_pair(cls): |
| 66 | + p1 = cls() |
| 67 | + p2 = cls() |
| 68 | + p1._other = p2 |
| 69 | + p2._other = p1 |
| 70 | + return p1, p2 |
| 71 | + |
| 72 | + |
| 73 | +def assert_poll(s, i, arg, expected_arg, expected_ret): |
| 74 | + ret = s.ioctl(_MP_STREAM_POLL, arg) |
| 75 | + assert i.last_poll_arg == expected_arg |
| 76 | + i.last_poll_arg = None |
| 77 | + assert ret == expected_ret |
| 78 | + |
| 79 | + |
| 80 | +def assert_raises(cb, *args, **kwargs): |
| 81 | + try: |
| 82 | + cb(*args, **kwargs) |
| 83 | + raise AssertionError("should have raised") |
| 84 | + except Exception as exc: |
| 85 | + pass |
| 86 | + |
| 87 | + |
| 88 | +client_io, server_io = _Pipe.new_pair() |
| 89 | + |
| 90 | +client_io.block_reads = True |
| 91 | +client_io.block_writes = True |
| 92 | +client_sock = ussl.wrap_socket(client_io, do_handshake=False) |
| 93 | + |
| 94 | +server_sock = ussl.wrap_socket(server_io, key=key, cert=certificate, server_side=True, do_handshake=False) |
| 95 | + |
| 96 | +# Do a test read, at this point the TLS handshake wants to write, |
| 97 | +# so it returns None: |
| 98 | +assert client_sock.read(128) is None |
| 99 | + |
| 100 | +# Polling for either read or write actually check if the underlying socket can write: |
| 101 | +assert_poll(client_sock, client_io, _MP_STREAM_POLL_RD, _MP_STREAM_POLL_WR, 0) |
| 102 | +assert_poll(client_sock, client_io, _MP_STREAM_POLL_WR, _MP_STREAM_POLL_WR, 0) |
| 103 | + |
| 104 | +# Mark the socket as writable, and do another test read: |
| 105 | +client_io.block_writes = False |
| 106 | +assert client_sock.read(128) is None |
| 107 | + |
| 108 | +# The client wrote the CLIENT_HELLO message |
| 109 | +assert len(client_io.write_buffers) == 1 |
| 110 | + |
| 111 | +# At this point the TLS handshake wants to read, but we don't know that yet: |
| 112 | +assert_poll(client_sock, client_io, _MP_STREAM_POLL_RD, _MP_STREAM_POLL_RD, 0) |
| 113 | +assert_poll(client_sock, client_io, _MP_STREAM_POLL_WR, _MP_STREAM_POLL_WR, _MP_STREAM_POLL_WR) |
| 114 | + |
| 115 | +# Do a test write |
| 116 | +client_sock.write(b"foo") |
| 117 | + |
| 118 | +# Now we know that we want to read: |
| 119 | +assert_poll(client_sock, client_io, _MP_STREAM_POLL_RD, _MP_STREAM_POLL_RD, 0) |
| 120 | +assert_poll(client_sock, client_io, _MP_STREAM_POLL_WR, _MP_STREAM_POLL_RD, 0) |
| 121 | + |
| 122 | +# Unblock reads and nudge the two sockets: |
| 123 | +client_io.block_reads = False |
| 124 | +while server_io.write_buffers or client_io.write_buffers: |
| 125 | + if server_io.write_buffers: |
| 126 | + assert client_sock.read(128) is None |
| 127 | + if client_io.write_buffers: |
| 128 | + assert server_sock.read(128) is None |
| 129 | + |
| 130 | +# At this point, the handshake is done, try writing data: |
| 131 | +client_sock.write(b"foo") |
| 132 | +assert server_sock.read(3) == b"foo" |
| 133 | + |
| 134 | +# Test reading partial data: |
| 135 | +client_sock.write(b"foobar") |
| 136 | +assert server_sock.read(3) == b"foo" |
| 137 | +server_io.block_reads = True |
| 138 | +assert_poll(server_sock, server_io, _MP_STREAM_POLL_RD, None, _MP_STREAM_POLL_RD) # Did not go to the socket, just consumed buffered data |
| 139 | +assert server_sock.read(3) == b"bar" |
| 140 | + |
| 141 | + |
| 142 | +# Polling on a closed socket errors out: |
| 143 | +client_io, _ = _Pipe.new_pair() |
| 144 | +client_sock = ussl.wrap_socket(client_io, do_handshake=False) |
| 145 | +client_sock.close() |
| 146 | +assert_poll(client_sock, client_io, _MP_STREAM_POLL_RD, None, _MP_STREAM_POLL_NVAL) # Did not go to the socket |
| 147 | + |
| 148 | + |
| 149 | +# Errors propagates to poll: |
| 150 | +client_io, server_io = _Pipe.new_pair() |
| 151 | +client_sock = ussl.wrap_socket(client_io, do_handshake=False) |
| 152 | + |
| 153 | +# The server returns garbage: |
| 154 | +server_io.write(b"fooba") # Needs to be exactly 5 bytes |
| 155 | + |
| 156 | +assert_poll(client_sock, client_io, _MP_STREAM_POLL_RD, _MP_STREAM_POLL_RD, _MP_STREAM_POLL_RD) |
| 157 | +assert_raises(client_sock.read, 128) |
| 158 | +assert_poll(client_sock, client_io, _MP_STREAM_POLL_RD, None, _MP_STREAM_POLL_NVAL) # Did not go to the socket |
0 commit comments