diff --git a/docs/rc_protocol.rst b/docs/rc_protocol.rst index 7638aaed2..2a558dc9e 100644 --- a/docs/rc_protocol.rst +++ b/docs/rc_protocol.rst @@ -47,24 +47,25 @@ Encrypted communication .. versionadded:: 0.26.0 -When using the :opt:`remote_control_password` option communication to the terminal is -encrypted to keep the password secure. A public key is used from the -:envvar:`KITTY_PUBLIC_KEY` environment variable. Currently, only one encryption -protocol is supported. The protocol number is present in -:envvar:`KITTY_PUBLIC_KEY` as ``1``. The key data in this environment variable is -`Base-85 `__ encoded. -The algorithm used is `Elliptic Curve Diffie Helman +When using the :opt:`remote_control_password` option communication to the +terminal is encrypted to keep the password secure. A public key is used from +the :envvar:`KITTY_PUBLIC_KEY` environment variable. Currently, only one +encryption protocol is supported. The protocol number is present in +:envvar:`KITTY_PUBLIC_KEY` as ``1``. The key data in this environment variable +is `Base-85 `__ encoded. The +algorithm used is `Elliptic Curve Diffie Helman `__ with the -`X25519 curve `__. A -time based nonce is used to minimise replay attacks. The original JSON command has -the fields: ``password`` and ``timestamp`` added. The timestamp is the number -of nanoseconds since the epoch, excluding leap seconds. Commands with a -timestamp more than 5 minutes from the current time are rejected. The command is then -encrypted using AES-256-GCM in authenticated encryption mode, with a symmetric key that -is derived from the ECDH key-pair by running the shared secret through SHA-256 hashing, once. -An IV of 96 bits of CSPRNG data is used. The tag for authenticated encryption **must** be 128 bits long. -The tag **must** authenticate only the value of the ``encrypted`` field. A new -command is created and transmitted that contains the fields: +`X25519 curve `__. A time based nonce +is used to minimise replay attacks. The original JSON command has the fields: +``password`` and ``timestamp`` added. The timestamp is the number of +nanoseconds since the epoch, excluding leap seconds. Commands with a timestamp +more than 5 minutes from the current time are rejected. The command is then +encrypted using AES-256-GCM in authenticated encryption mode, with a symmetric +key that is derived from the ECDH key-pair by running the shared secret through +SHA-256 hashing, once. An IV of at least 96 bits of CSPRNG data is used. The +tag for authenticated encryption **must** be at least 128 bits long. The tag +**must** authenticate only the value of the ``encrypted`` field. A new command +is created and transmitted that contains the fields: .. code-block:: json diff --git a/kitty/crypto.c b/kitty/crypto.c index c24499515..cf4ccd7e1 100644 --- a/kitty/crypto.c +++ b/kitty/crypto.c @@ -371,15 +371,19 @@ new_aes256gcmdecrypt(PyTypeObject *type, PyObject *args, PyObject *kwds UNUSED) if (!PyArg_ParseTuple(args, "O!y#y#", &Secret_Type, &key, &iv, &iv_len, &tag, &tag_len)) return NULL; const EVP_CIPHER *cipher = EVP_get_cipherbynid(NID_aes_256_gcm); if (key->secret_len != (size_t)EVP_CIPHER_key_length(cipher)) { PyErr_Format(PyExc_ValueError, "The key for AES 256 GCM must be %d bytes long", EVP_CIPHER_key_length(cipher)); return NULL; } - if (iv_len != EVP_CIPHER_iv_length(cipher)) { PyErr_Format(PyExc_ValueError, "The iv for AES 256 GCM must be %d bytes long", EVP_CIPHER_iv_length(cipher)); return NULL; } + if (iv_len < EVP_CIPHER_iv_length(cipher)) { PyErr_Format(PyExc_ValueError, "The iv for AES 256 GCM must be at least %d bytes long", EVP_CIPHER_iv_length(cipher)); return NULL; } AES256GCMDecrypt *self = (AES256GCMDecrypt *)type->tp_alloc(type, 0); if (!self) return NULL; if (!(self->ctx = EVP_CIPHER_CTX_new())) { Py_CLEAR(self); return set_error_from_openssl("Failed to allocate decryption context"); } + if (iv_len > EVP_CIPHER_iv_length(cipher)) { + if (!EVP_CIPHER_CTX_ctrl(self->ctx, EVP_CTRL_GCM_SET_IVLEN, iv_len, NULL)) { Py_CLEAR(self); return set_error_from_openssl("Failed to set the IV length"); } + } if (1 != EVP_DecryptInit_ex(self->ctx, cipher, NULL, key->secret, iv)) { Py_CLEAR(self); return set_error_from_openssl("Failed to initialize encryption context"); } // Ensure tag length is 16 because the OpenSSL verification routines will happily pass even if you set a truncated tag. - if (tag_len != cipher_ctx_tag_length(self->ctx)) { PyErr_Format(PyExc_ValueError, "Tag length for AES 256 GCM must be %d", cipher_ctx_tag_length(self->ctx)); return NULL; } + if (tag_len < cipher_ctx_tag_length(self->ctx)) { PyErr_Format(PyExc_ValueError, "Tag length for AES 256 GCM must be at least %d", cipher_ctx_tag_length(self->ctx)); return NULL; } if (!EVP_CIPHER_CTX_ctrl(self->ctx, EVP_CTRL_AEAD_SET_TAG, tag_len, tag)) { Py_CLEAR(self); return set_error_from_openssl("Failed to set the tag"); } + return (PyObject*)self; }