mirror of
https://github.com/jart/cosmopolitan.git
synced 2025-01-31 11:37:35 +00:00
504 lines
14 KiB
C
504 lines
14 KiB
C
/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:4;tab-width:8;coding:utf-8 -*-│
|
|
│ vi: set et ft=c ts=4 sts=4 sw=4 fenc=utf-8 :vi │
|
|
╞══════════════════════════════════════════════════════════════════════════════╡
|
|
│ Copyright 2021 Justine Alexandra Roberts Tunney │
|
|
│ │
|
|
│ Copying of this file is authorized only if (1) you are Justine Tunney, │
|
|
│ or (2) you make absolutely no changes to your copy. │
|
|
│ │
|
|
│ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL │
|
|
│ WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED │
|
|
│ WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE │
|
|
│ AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL │
|
|
│ DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR │
|
|
│ PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER │
|
|
│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │
|
|
│ PERFORMANCE OF THIS SOFTWARE. │
|
|
╚─────────────────────────────────────────────────────────────────────────────*/
|
|
#define PY_SSIZE_T_CLEAN
|
|
#include "libc/assert.h"
|
|
#include "libc/calls/calls.h"
|
|
#include "libc/errno.h"
|
|
#include "libc/macros.h"
|
|
#include "libc/mem/gc.h"
|
|
#include "libc/str/str.h"
|
|
#include "net/https/https.h"
|
|
#include "third_party/mbedtls/ctr_drbg.h"
|
|
#include "third_party/mbedtls/debug.h"
|
|
#include "third_party/mbedtls/error.h"
|
|
#include "third_party/mbedtls/ssl.h"
|
|
#include "third_party/python/Include/abstract.h"
|
|
#include "third_party/python/Include/import.h"
|
|
#include "third_party/python/Include/longobject.h"
|
|
#include "third_party/python/Include/modsupport.h"
|
|
#include "third_party/python/Include/object.h"
|
|
#include "third_party/python/Include/objimpl.h"
|
|
#include "third_party/python/Include/pyerrors.h"
|
|
#include "third_party/python/Include/pymacro.h"
|
|
#include "third_party/python/Include/structmember.h"
|
|
#include "third_party/python/Include/yoink.h"
|
|
|
|
/**
|
|
* @fileoverview Enough TLS support for HttpsClient so far.
|
|
*/
|
|
|
|
PYTHON_PROVIDE("tls");
|
|
|
|
#if 0
|
|
#define LOG(...) kprintf(__VA_ARGS__)
|
|
#else
|
|
#define LOG(...) (void)0
|
|
#endif
|
|
|
|
struct Tls {
|
|
PyObject_HEAD
|
|
int fd;
|
|
PyObject *todo;
|
|
mbedtls_ssl_config conf;
|
|
mbedtls_ssl_context ssl;
|
|
mbedtls_ctr_drbg_context rng;
|
|
};
|
|
|
|
static PyObject *TlsError;
|
|
static PyTypeObject tls_type;
|
|
|
|
static PyObject *
|
|
SetTlsError(int rc)
|
|
{
|
|
char b[128];
|
|
mbedtls_strerror(rc, b, sizeof(b));
|
|
PyErr_SetString(TlsError, b);
|
|
return NULL;
|
|
}
|
|
|
|
static int
|
|
TlsSend(void *c, const unsigned char *p, size_t n)
|
|
{
|
|
int rc;
|
|
struct Tls *self = c;
|
|
for (;;) {
|
|
rc = write(self->fd, p, n);
|
|
if (rc != -1) {
|
|
return rc;
|
|
} else if (errno == EINTR) {
|
|
if (PyErr_CheckSignals()) {
|
|
return -1;
|
|
}
|
|
} else {
|
|
PyErr_SetFromErrno(PyExc_OSError);
|
|
return -1;
|
|
}
|
|
}
|
|
}
|
|
|
|
static int
|
|
TlsRecv(void *c, unsigned char *p, size_t n, uint32_t t)
|
|
{
|
|
int rc;
|
|
struct Tls *self = c;
|
|
for (;;) {
|
|
rc = read(self->fd, p, n);
|
|
if (rc != -1) {
|
|
return rc;
|
|
} else if (errno == EINTR) {
|
|
if (PyErr_CheckSignals()) {
|
|
return -1;
|
|
}
|
|
} else {
|
|
PyErr_SetFromErrno(PyExc_OSError);
|
|
return -1;
|
|
}
|
|
}
|
|
}
|
|
|
|
static struct Tls *
|
|
tls_new(int fd, const char *host, PyObject *todo)
|
|
{
|
|
struct Tls *self;
|
|
LOG("TLS.new\n");
|
|
if ((self = PyObject_New(struct Tls, &tls_type))) {
|
|
self->fd = fd;
|
|
self->todo = todo;
|
|
Py_INCREF(todo);
|
|
InitializeRng(&self->rng);
|
|
mbedtls_ssl_init(&self->ssl);
|
|
mbedtls_ssl_config_init(&self->conf);
|
|
mbedtls_ssl_config_defaults(&self->conf,
|
|
MBEDTLS_SSL_IS_CLIENT,
|
|
MBEDTLS_SSL_TRANSPORT_STREAM,
|
|
MBEDTLS_SSL_PRESET_DEFAULT);
|
|
mbedtls_ssl_conf_rng(&self->conf, mbedtls_ctr_drbg_random, &self->rng);
|
|
mbedtls_ssl_conf_ca_chain(&self->conf, GetSslRoots(), 0);
|
|
/* mbedtls_ssl_conf_dbg(&self->conf, TlsDebug, 0); */
|
|
/* mbedtls_debug_threshold = 5; */
|
|
if (host && *host) {
|
|
mbedtls_ssl_conf_authmode(&self->conf, MBEDTLS_SSL_VERIFY_REQUIRED);
|
|
mbedtls_ssl_set_hostname(&self->ssl, host);
|
|
} else {
|
|
mbedtls_ssl_conf_authmode(&self->conf, MBEDTLS_SSL_VERIFY_NONE);
|
|
}
|
|
mbedtls_ssl_set_bio(&self->ssl, self, TlsSend, 0, TlsRecv);
|
|
mbedtls_ssl_setup(&self->ssl, &self->conf);
|
|
self->conf.disable_compression = true;
|
|
}
|
|
return self;
|
|
}
|
|
|
|
static void
|
|
tls_dealloc(struct Tls *self)
|
|
{
|
|
LOG("TLS.dealloc\n");
|
|
if (self->fd != -1) {
|
|
LOG("TLS CLOSING\n");
|
|
close(self->fd);
|
|
}
|
|
Py_DECREF(self->todo);
|
|
if (PyErr_Occurred()) {
|
|
PyErr_Clear();
|
|
}
|
|
mbedtls_ssl_free(&self->ssl);
|
|
mbedtls_ctr_drbg_free(&self->rng);
|
|
mbedtls_ssl_config_free(&self->conf);
|
|
PyObject_Del(self);
|
|
}
|
|
|
|
PyDoc_STRVAR(tls_send__doc__, "\
|
|
send($self, bytes, /)\n\
|
|
--\n\n\
|
|
Sends bytes as ciphertext to file descriptor, returning number\n\
|
|
of bytes from buffer transmitted, or raising OSError/TlsError.");
|
|
|
|
static PyObject *
|
|
tls_send(struct Tls *self, PyObject *args)
|
|
{
|
|
int rc;
|
|
PyObject *res;
|
|
Py_buffer data;
|
|
LOG("TLS.send\n");
|
|
if (!PyArg_ParseTuple(args, "y*:send", &data)) return 0;
|
|
rc = mbedtls_ssl_write(&self->ssl, data.buf, data.len);
|
|
if (rc != -1) {
|
|
if (rc >= 0) {
|
|
res = PyLong_FromLong(rc);
|
|
} else {
|
|
SetTlsError(rc);
|
|
LOG("TLS CLOSING\n");
|
|
close(self->fd);
|
|
self->fd = -1;
|
|
res = 0;
|
|
}
|
|
} else {
|
|
res = 0;
|
|
}
|
|
PyBuffer_Release(&data);
|
|
return res;
|
|
}
|
|
|
|
PyDoc_STRVAR(tls_sendall__doc__, "\
|
|
sendall($self, bytes, /)\n\
|
|
--\n\n\
|
|
Sends all bytes to file descriptor as ciphertext, returning number\n\
|
|
of bytes from buffer transmitted, or raising OSError / TlsError.");
|
|
|
|
static PyObject *
|
|
tls_sendall(struct Tls *self, PyObject *args)
|
|
{
|
|
LOG("TLS.sendall\n");
|
|
int rc;
|
|
Py_ssize_t i;
|
|
PyObject *res;
|
|
Py_buffer data;
|
|
if (!PyArg_ParseTuple(args, "y*:sendall", &data)) return 0;
|
|
for (i = 0;;) {
|
|
rc = mbedtls_ssl_write(&self->ssl, (char *)data.buf + i, data.len - i);
|
|
if (rc > 0) {
|
|
if ((i += rc) == data.len) {
|
|
res = Py_None;
|
|
Py_INCREF(res);
|
|
break;
|
|
}
|
|
} else {
|
|
if (rc != -1) {
|
|
SetTlsError(rc);
|
|
LOG("TLS CLOSING\n");
|
|
close(self->fd);
|
|
self->fd = -1;
|
|
}
|
|
res = 0;
|
|
break;
|
|
}
|
|
}
|
|
PyBuffer_Release(&data);
|
|
return res;
|
|
}
|
|
|
|
PyDoc_STRVAR(tls_recv__doc__, "\
|
|
recv($self, nbytes, /)\n\
|
|
--\n\n\
|
|
Receives deciphered bytes from file descriptor, returning bytes\n\
|
|
or raising OSError / TlsError.");
|
|
|
|
static PyObject *
|
|
tls_recv(struct Tls *self, PyObject *args)
|
|
{
|
|
LOG("TLS.recv\n");
|
|
int rc;
|
|
Py_ssize_t n;
|
|
PyObject *res, *buf;
|
|
if (!PyArg_ParseTuple(args, "n:recv", &n)) return 0;
|
|
if (n < 0) {
|
|
PyErr_SetString(PyExc_ValueError,
|
|
"negative buffersize in recv");
|
|
return NULL;
|
|
}
|
|
if (!(buf = PyBytes_FromStringAndSize(0, n))) return 0;
|
|
rc = mbedtls_ssl_read(&self->ssl, PyBytes_AS_STRING(buf), n);
|
|
if (rc != -1) {
|
|
if (rc >= 0) {
|
|
if (rc != n) {
|
|
_PyBytes_Resize(&buf, rc);
|
|
}
|
|
res = buf;
|
|
} else {
|
|
Py_DECREF(buf);
|
|
SetTlsError(rc);
|
|
LOG("TLS CLOSING\n");
|
|
close(self->fd);
|
|
self->fd = -1;
|
|
res = 0;
|
|
}
|
|
} else {
|
|
Py_DECREF(buf);
|
|
res = 0;
|
|
}
|
|
return res;
|
|
}
|
|
|
|
PyDoc_STRVAR(tls_recv_into__doc__, "\
|
|
recv_into($self, buf, /)\n\
|
|
--\n\n\
|
|
Reads into an existing buffer...");
|
|
|
|
static PyObject *
|
|
tls_recv_into(struct Tls *self, PyObject *args)
|
|
{
|
|
LOG("TLS.recv_into\n");
|
|
int rc;
|
|
PyObject *res;
|
|
Py_buffer buf;
|
|
if (!PyArg_ParseTuple(args, "w*:recv_into", &buf)) return 0;
|
|
rc = mbedtls_ssl_read(&self->ssl, buf.buf, buf.len);
|
|
if (rc != -1) {
|
|
if (rc >= 0) {
|
|
res = PyLong_FromLong(rc);
|
|
LOG("got %d\n", rc);
|
|
} else {
|
|
SetTlsError(rc);
|
|
LOG("TLS CLOSING\n");
|
|
close(self->fd);
|
|
self->fd = -1;
|
|
res = 0;
|
|
}
|
|
} else {
|
|
res = 0;
|
|
}
|
|
PyBuffer_Release(&buf);
|
|
return res;
|
|
}
|
|
|
|
PyDoc_STRVAR(tls_fileno__doc__, "\
|
|
fileno($self, /)\n\
|
|
--\n\n\
|
|
Returns file descriptor passed to constructor.");
|
|
|
|
static PyObject *
|
|
tls_fileno(struct Tls *self, PyObject *unused)
|
|
{
|
|
LOG("TLS.fileno\n");
|
|
return PyLong_FromLong(self->fd);
|
|
}
|
|
|
|
PyDoc_STRVAR(tls_shutdown__doc__, "\
|
|
shutdown($self, how, /)\n\
|
|
--\n\n\
|
|
Does nothing currently.");
|
|
|
|
static PyObject *
|
|
tls_shutdown(struct Tls *self, PyObject *unused)
|
|
{
|
|
LOG("TLS.shutdown\n");
|
|
Py_RETURN_NONE;
|
|
}
|
|
|
|
PyDoc_STRVAR(tls_close__doc__, "\
|
|
close($self, /)\n\
|
|
--\n\n\
|
|
Closes SSL connection and file descriptor.");
|
|
|
|
static PyObject *
|
|
tls_close(struct Tls *self, PyObject *unused)
|
|
{
|
|
int rc, fd;
|
|
/* TODO(jart): do nothing until we can figure out how to own fd */
|
|
Py_RETURN_NONE;
|
|
fd = self->fd;
|
|
if (fd != -1) {
|
|
self->fd = -1;
|
|
rc = mbedtls_ssl_close_notify(&self->ssl);
|
|
if (rc) {
|
|
LOG("TLS CLOSING\n");
|
|
close(fd);
|
|
if (rc != -1) {
|
|
SetTlsError(rc);
|
|
}
|
|
return 0;
|
|
}
|
|
LOG("TLS CLOSING\n");
|
|
rc = close(fd);
|
|
if (rc == -1) {
|
|
return PyErr_SetFromErrno(PyExc_OSError);
|
|
}
|
|
}
|
|
Py_RETURN_NONE;
|
|
}
|
|
|
|
PyDoc_STRVAR(tls_handshake__doc__, "\
|
|
handshake($self, /)\n\
|
|
--\n\n\
|
|
Handshakes SSL connection and file descriptor.");
|
|
|
|
static PyObject *
|
|
tls_handshake(struct Tls *self, PyObject *unused)
|
|
{
|
|
int rc;
|
|
LOG("TLS.handshake\n");
|
|
rc = mbedtls_ssl_handshake(&self->ssl);
|
|
if (rc) {
|
|
LOG("TLS CLOSING\n");
|
|
close(self->fd);
|
|
self->fd = -1;
|
|
if (rc != -1) {
|
|
if (rc == MBEDTLS_ERR_X509_CERT_VERIFY_FAILED) {
|
|
PyErr_SetString(TlsError, gc(DescribeSslVerifyFailure(self->ssl.session_negotiate->verify_result)));
|
|
} else {
|
|
SetTlsError(rc);
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
Py_RETURN_NONE;
|
|
}
|
|
|
|
static PyMethodDef tls_methods[] = {
|
|
{"handshake", (PyCFunction)tls_handshake, METH_NOARGS, tls_handshake__doc__},
|
|
{"recv", (PyCFunction)tls_recv, METH_VARARGS, tls_recv__doc__},
|
|
{"recv_into", (PyCFunction)tls_recv_into, METH_VARARGS, tls_recv_into__doc__},
|
|
{"send", (PyCFunction)tls_send, METH_VARARGS, tls_send__doc__},
|
|
{"sendall", (PyCFunction)tls_sendall, METH_VARARGS, tls_sendall__doc__},
|
|
{"fileno", (PyCFunction)tls_fileno, METH_NOARGS, tls_fileno__doc__},
|
|
{"shutdown", (PyCFunction)tls_shutdown, METH_VARARGS, tls_shutdown__doc__},
|
|
{"close", (PyCFunction)tls_close, METH_NOARGS, tls_close__doc__},
|
|
{0}
|
|
};
|
|
|
|
static PyObject *
|
|
tls_repr(struct Tls *self)
|
|
{
|
|
return PyUnicode_FromFormat("<TLS object @ %p>", self);
|
|
}
|
|
|
|
PyDoc_STRVAR(tls_doc,
|
|
"An MbedTLS object.");
|
|
|
|
static PyTypeObject tls_type = {
|
|
PyVarObject_HEAD_INIT(NULL, 0)
|
|
/*tp_name*/ "tls.TLS",
|
|
/*tp_basicsize*/ sizeof(struct Tls),
|
|
/*tp_itemsize*/ 0,
|
|
/*tp_dealloc*/ (destructor)tls_dealloc,
|
|
/*tp_print*/ 0,
|
|
/*tp_getattr*/ 0,
|
|
/*tp_setattr*/ 0,
|
|
/*tp_reserved*/ 0,
|
|
/*tp_repr*/ (reprfunc)tls_repr,
|
|
/*tp_as_number*/ 0,
|
|
/*tp_as_sequence*/ 0,
|
|
/*tp_as_mapping*/ 0,
|
|
/*tp_hash*/ 0,
|
|
/*tp_call*/ 0,
|
|
/*tp_str*/ 0,
|
|
/*tp_getattro*/ 0,
|
|
/*tp_setattro*/ 0,
|
|
/*tp_as_buffer*/ 0,
|
|
/*tp_flags*/ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
|
|
/*tp_doc*/ tls_doc,
|
|
/*tp_traverse*/ 0,
|
|
/*tp_clear*/ 0,
|
|
/*tp_richcompare*/ 0,
|
|
/*tp_weaklistoffset*/ 0,
|
|
/*tp_iter*/ 0,
|
|
/*tp_iternext*/ 0,
|
|
/*tp_methods*/ tls_methods,
|
|
/*tp_members*/ 0,
|
|
/*tp_getset*/ 0,
|
|
/*tp_base*/ 0,
|
|
/*tp_dict*/ 0,
|
|
/*tp_descr_get*/ 0,
|
|
/*tp_descr_set*/ 0,
|
|
/*tp_dictoffset*/ 0,
|
|
};
|
|
|
|
PyDoc_STRVAR(newclient__doc__,
|
|
"newclient($module, fd, host)\n\
|
|
--\n\n\
|
|
Creates TLS client.");
|
|
|
|
static PyObject *
|
|
newclient(PyObject *self, PyObject *args)
|
|
{
|
|
int fd;
|
|
PyObject *todo;
|
|
struct Tls *tls;
|
|
const char *host;
|
|
if (!PyArg_ParseTuple(args, "isO:newclient", &fd, &host, &todo)) return 0;
|
|
tls = tls_new(fd, host, todo);
|
|
return (PyObject *)tls;
|
|
}
|
|
|
|
static struct PyMethodDef mbedtls_functions[] = {
|
|
{"newclient", (PyCFunction)newclient, METH_VARARGS, newclient__doc__},
|
|
{0}
|
|
};
|
|
|
|
static struct PyModuleDef mbedtls_module = {
|
|
PyModuleDef_HEAD_INIT,
|
|
"tls",
|
|
NULL,
|
|
-1,
|
|
mbedtls_functions
|
|
};
|
|
|
|
PyMODINIT_FUNC
|
|
PyInit_tls(void)
|
|
{
|
|
PyObject *m;
|
|
Py_TYPE(&tls_type) = &PyType_Type;
|
|
if (PyType_Ready(&tls_type) < 0) return 0;
|
|
if (!(m = PyModule_Create(&mbedtls_module))) return 0;
|
|
Py_INCREF((PyObject *)&tls_type);
|
|
PyModule_AddObject(m, "TLS", (PyObject *)&tls_type);
|
|
TlsError = PyErr_NewException("tls.TlsError", NULL, NULL);
|
|
Py_INCREF(TlsError);
|
|
PyModule_AddObject(m, "TlsError", TlsError);
|
|
return m;
|
|
}
|
|
|
|
#ifdef __aarch64__
|
|
_Section(".rodata.pytab.1 //")
|
|
#else
|
|
_Section(".rodata.pytab.1")
|
|
#endif
|
|
const struct _inittab _PyImport_Inittab_tls = {
|
|
"tls",
|
|
PyInit_tls,
|
|
};
|