diff --git a/docs/build.rst b/docs/build.rst index 57f8b081d..d9b8c60f6 100644 --- a/docs/build.rst +++ b/docs/build.rst @@ -35,6 +35,7 @@ Run-time dependencies: * ``libpng`` * ``liblcms2`` * ``librsync`` +* ``openssl`` * ``freetype`` (not needed on macOS) * ``fontconfig`` (not needed on macOS) * ``libcanberra`` (not needed on macOS) diff --git a/kitty/crypto.c b/kitty/crypto.c new file mode 100644 index 000000000..f0a4f236a --- /dev/null +++ b/kitty/crypto.c @@ -0,0 +1,98 @@ +/* + * crypto.c + * Copyright (C) 2022 Kovid Goyal + * + * Distributed under terms of the GPL3 license. + */ + +#include "data-types.h" + +#include +#include +#include +#include +#include + +#define EC_KEY_CAPSULE_NAME "EC-key-capsule" + +static PyObject* +set_error_from_openssl(const char *prefix) { + BIO *bio = BIO_new(BIO_s_mem()); + ERR_print_errors(bio); + char *buf = NULL; + size_t len = BIO_get_mem_data(bio, &buf); + PyObject *msg = PyUnicode_FromStringAndSize(buf, len); + if (msg) PyErr_Format(PyExc_ValueError, "%s: %U", prefix, msg); + BIO_free(bio); + Py_CLEAR(msg); + return NULL; +} + +static void +destroy_ec_key_capsule(PyObject *cap) { + EVP_PKEY *key = PyCapsule_GetPointer(cap, EC_KEY_CAPSULE_NAME); + if (key) EVP_PKEY_free(key); +} + +static PyObject* +elliptic_curve_key_create(PyObject *self UNUSED, PyObject *args) { + const char *curve_name = "X25519"; + if (!PyArg_ParseTuple(args, "|s", &curve_name)) return NULL; + int nid = NID_X25519; + if (strcmp(curve_name, "X25519") != 0) { PyErr_Format(PyExc_KeyError, "Unknown curve: %s", curve_name); return NULL; } + EVP_PKEY *key = NULL; + EVP_PKEY_CTX *pctx = NULL; +#define cleanup() { if (key) EVP_PKEY_free(key); key = NULL; if (pctx) EVP_PKEY_CTX_free(pctx); pctx = NULL; } +#define ssl_error(text) { cleanup(); return set_error_from_openssl(text); } + + if (NULL == (pctx = EVP_PKEY_CTX_new_id(nid, NULL))) ssl_error("Failed to create context for key generation"); + if(1 != EVP_PKEY_keygen_init(pctx)) ssl_error("Failed to initialize keygen context"); + if (1 != EVP_PKEY_keygen(pctx, &key)) ssl_error("Failed to generate key"); + + PyObject *ans = PyCapsule_New(key, EC_KEY_CAPSULE_NAME, destroy_ec_key_capsule); + if (ans) key = NULL; + cleanup(); + return ans; +#undef cleanup +#undef ssl_error +} + +static PyObject* +elliptic_curve_key_public(PyObject *self UNUSED, PyObject *key_capsule) { + if (!PyCapsule_IsValid(key_capsule, EC_KEY_CAPSULE_NAME)) { PyErr_SetString(PyExc_TypeError, "Not a valid elliptic curve key capsule"); return NULL; } + EVP_PKEY *pkey = PyCapsule_GetPointer(key_capsule, EC_KEY_CAPSULE_NAME); + /* PEM_write_PUBKEY(stdout, pkey); */ + size_t len = 0; + if (1 != EVP_PKEY_get_raw_public_key(pkey, NULL, &len)) return set_error_from_openssl("Could not get public key from EVP_KEY"); + PyObject *ans = PyBytes_FromStringAndSize(NULL, len); + if (!ans) return NULL; + if (1 != EVP_PKEY_get_raw_public_key(pkey, (unsigned char*)PyBytes_AS_STRING(ans), &len)) return set_error_from_openssl("Could not get public key from EVP_KEY"); + return ans; +} + +static PyObject* +elliptic_curve_key_private(PyObject *self UNUSED, PyObject *key_capsule) { + if (!PyCapsule_IsValid(key_capsule, EC_KEY_CAPSULE_NAME)) { PyErr_SetString(PyExc_TypeError, "Not a valid elliptic curve key capsule"); return NULL; } + EVP_PKEY *pkey = PyCapsule_GetPointer(key_capsule, EC_KEY_CAPSULE_NAME); + size_t len = 0; + if (1 != EVP_PKEY_get_raw_private_key(pkey, NULL, &len)) return set_error_from_openssl("Could not get public key from EVP_KEY"); + PyObject *ans = PyBytes_FromStringAndSize(NULL, len); + if (!ans) return NULL; + if (1 != EVP_PKEY_get_raw_private_key(pkey, (unsigned char*)PyBytes_AS_STRING(ans), &len)) return set_error_from_openssl("Could not get public key from EVP_KEY"); + return ans; +} + + +static PyMethodDef module_methods[] = { + METHODB(elliptic_curve_key_create, METH_VARARGS), + METHODB(elliptic_curve_key_public, METH_O), + METHODB(elliptic_curve_key_private, METH_O), + {NULL, NULL, 0, NULL} /* Sentinel */ +}; + + +bool +init_crypto_library(PyObject *module) { + if (PyModule_AddFunctions(module, module_methods) != 0) return false; + return true; +} diff --git a/kitty/data-types.c b/kitty/data-types.c index 202aafaaa..e615c4005 100644 --- a/kitty/data-types.c +++ b/kitty/data-types.c @@ -257,6 +257,7 @@ extern int init_Line(PyObject *); extern int init_ColorProfile(PyObject *); extern int init_Screen(PyObject *); extern bool init_fontconfig_library(PyObject*); +extern bool init_crypto_library(PyObject*); extern bool init_desktop(PyObject*); extern bool init_fonts(PyObject*); extern bool init_glfw(PyObject *m); @@ -339,6 +340,7 @@ PyInit_fast_data_types(void) { if (!init_fonts(m)) return NULL; if (!init_utmp(m)) return NULL; if (!init_loop_utils(m)) return NULL; + if (!init_crypto_library(m)) return NULL; CellAttrs a; #define s(name, attr) { a.val = 0; a.attr = 1; PyModule_AddIntConstant(m, #name, shift_to_first_set_bit(a)); } diff --git a/setup.py b/setup.py index 4f0a2d4cf..b5df2f66e 100755 --- a/setup.py +++ b/setup.py @@ -412,6 +412,7 @@ def kitty_env() -> Env: at_least_version('harfbuzz', 1, 5) cflags.extend(pkg_config('libpng', '--cflags-only-I')) cflags.extend(pkg_config('lcms2', '--cflags-only-I')) + cflags.extend(pkg_config('libcrypto', '--cflags-only-I')) if is_macos: platform_libs = [ '-framework', 'Carbon', '-framework', 'CoreText', '-framework', 'CoreGraphics', @@ -436,7 +437,8 @@ def kitty_env() -> Env: gl_libs = ['-framework', 'OpenGL'] if is_macos else pkg_config('gl', '--libs') libpng = pkg_config('libpng', '--libs') lcms2 = pkg_config('lcms2', '--libs') - ans.ldpaths += pylib + platform_libs + gl_libs + libpng + lcms2 + libcrypto = pkg_config('libcrypto', '--libs') + ans.ldpaths += pylib + platform_libs + gl_libs + libpng + lcms2 + libcrypto if is_macos: ans.ldpaths.extend('-framework Cocoa'.split()) elif not is_openbsd: