From fd6bc55db6e801b0adfae09889c75fa104f68664 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 3 Aug 2022 22:37:35 +0530 Subject: [PATCH] Start work on implementing public key crypto Will come in handy for various things in the future, so lets just setup the API now. No new dependencies are needed since Python already depends on OpenSSL. --- docs/build.rst | 1 + kitty/crypto.c | 98 ++++++++++++++++++++++++++++++++++++++++++++++ kitty/data-types.c | 2 + setup.py | 4 +- 4 files changed, 104 insertions(+), 1 deletion(-) create mode 100644 kitty/crypto.c 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: