417 lines
12 KiB
C
417 lines
12 KiB
C
#define PY_SSIZE_T_CLEAN
|
|
|
|
#include <Python.h>
|
|
#include <openssl/err.h>
|
|
#include <openssl/evp.h>
|
|
|
|
#define MODULE_NAME "aioquic._crypto"
|
|
|
|
#define AEAD_KEY_LENGTH_MAX 32
|
|
#define AEAD_NONCE_LENGTH 12
|
|
#define AEAD_TAG_LENGTH 16
|
|
|
|
#define PACKET_LENGTH_MAX 1500
|
|
#define PACKET_NUMBER_LENGTH_MAX 4
|
|
#define SAMPLE_LENGTH 16
|
|
|
|
#define CHECK_RESULT(expr) \
|
|
if (!(expr)) { \
|
|
ERR_clear_error(); \
|
|
PyErr_SetString(CryptoError, "OpenSSL call failed"); \
|
|
return NULL; \
|
|
}
|
|
|
|
#define CHECK_RESULT_CTOR(expr) \
|
|
if (!(expr)) { \
|
|
ERR_clear_error(); \
|
|
PyErr_SetString(CryptoError, "OpenSSL call failed"); \
|
|
return -1; \
|
|
}
|
|
|
|
static PyObject *CryptoError;
|
|
|
|
/* AEAD */
|
|
|
|
typedef struct {
|
|
PyObject_HEAD
|
|
EVP_CIPHER_CTX *decrypt_ctx;
|
|
EVP_CIPHER_CTX *encrypt_ctx;
|
|
unsigned char buffer[PACKET_LENGTH_MAX];
|
|
unsigned char key[AEAD_KEY_LENGTH_MAX];
|
|
unsigned char iv[AEAD_NONCE_LENGTH];
|
|
unsigned char nonce[AEAD_NONCE_LENGTH];
|
|
} AEADObject;
|
|
|
|
static PyObject *AEADType;
|
|
|
|
static EVP_CIPHER_CTX *
|
|
create_ctx(const EVP_CIPHER *cipher, int key_length, int operation)
|
|
{
|
|
EVP_CIPHER_CTX *ctx;
|
|
int res;
|
|
|
|
ctx = EVP_CIPHER_CTX_new();
|
|
CHECK_RESULT(ctx != 0);
|
|
|
|
res = EVP_CipherInit_ex(ctx, cipher, NULL, NULL, NULL, operation);
|
|
CHECK_RESULT(res != 0);
|
|
|
|
res = EVP_CIPHER_CTX_set_key_length(ctx, key_length);
|
|
CHECK_RESULT(res != 0);
|
|
|
|
res = EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_CCM_SET_IVLEN, AEAD_NONCE_LENGTH, NULL);
|
|
CHECK_RESULT(res != 0);
|
|
|
|
return ctx;
|
|
}
|
|
|
|
static int
|
|
AEAD_init(AEADObject *self, PyObject *args, PyObject *kwargs)
|
|
{
|
|
const char *cipher_name;
|
|
const unsigned char *key, *iv;
|
|
Py_ssize_t cipher_name_len, key_len, iv_len;
|
|
|
|
if (!PyArg_ParseTuple(args, "y#y#y#", &cipher_name, &cipher_name_len, &key, &key_len, &iv, &iv_len))
|
|
return -1;
|
|
|
|
const EVP_CIPHER *evp_cipher = EVP_get_cipherbyname(cipher_name);
|
|
if (evp_cipher == 0) {
|
|
PyErr_Format(CryptoError, "Invalid cipher name: %s", cipher_name);
|
|
return -1;
|
|
}
|
|
if (key_len > AEAD_KEY_LENGTH_MAX) {
|
|
PyErr_SetString(CryptoError, "Invalid key length");
|
|
return -1;
|
|
}
|
|
if (iv_len > AEAD_NONCE_LENGTH) {
|
|
PyErr_SetString(CryptoError, "Invalid iv length");
|
|
return -1;
|
|
}
|
|
|
|
memcpy(self->key, key, key_len);
|
|
memcpy(self->iv, iv, iv_len);
|
|
|
|
self->decrypt_ctx = create_ctx(evp_cipher, key_len, 0);
|
|
CHECK_RESULT_CTOR(self->decrypt_ctx != 0);
|
|
|
|
self->encrypt_ctx = create_ctx(evp_cipher, key_len, 1);
|
|
CHECK_RESULT_CTOR(self->encrypt_ctx != 0);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void
|
|
AEAD_dealloc(AEADObject *self)
|
|
{
|
|
EVP_CIPHER_CTX_free(self->decrypt_ctx);
|
|
EVP_CIPHER_CTX_free(self->encrypt_ctx);
|
|
PyTypeObject *tp = Py_TYPE(self);
|
|
freefunc free = PyType_GetSlot(tp, Py_tp_free);
|
|
free(self);
|
|
Py_DECREF(tp);
|
|
}
|
|
|
|
static PyObject*
|
|
AEAD_decrypt(AEADObject *self, PyObject *args)
|
|
{
|
|
const unsigned char *data, *associated;
|
|
Py_ssize_t data_len, associated_len;
|
|
int outlen, outlen2, res;
|
|
uint64_t pn;
|
|
|
|
if (!PyArg_ParseTuple(args, "y#y#K", &data, &data_len, &associated, &associated_len, &pn))
|
|
return NULL;
|
|
|
|
if (data_len < AEAD_TAG_LENGTH || data_len > PACKET_LENGTH_MAX) {
|
|
PyErr_SetString(CryptoError, "Invalid payload length");
|
|
return NULL;
|
|
}
|
|
|
|
memcpy(self->nonce, self->iv, AEAD_NONCE_LENGTH);
|
|
for (int i = 0; i < 8; ++i) {
|
|
self->nonce[AEAD_NONCE_LENGTH - 1 - i] ^= (uint8_t)(pn >> 8 * i);
|
|
}
|
|
|
|
res = EVP_CIPHER_CTX_ctrl(self->decrypt_ctx, EVP_CTRL_CCM_SET_TAG, AEAD_TAG_LENGTH, (void*)(data + (data_len - AEAD_TAG_LENGTH)));
|
|
CHECK_RESULT(res != 0);
|
|
|
|
res = EVP_CipherInit_ex(self->decrypt_ctx, NULL, NULL, self->key, self->nonce, 0);
|
|
CHECK_RESULT(res != 0);
|
|
|
|
res = EVP_CipherUpdate(self->decrypt_ctx, NULL, &outlen, associated, associated_len);
|
|
CHECK_RESULT(res != 0);
|
|
|
|
res = EVP_CipherUpdate(self->decrypt_ctx, self->buffer, &outlen, data, data_len - AEAD_TAG_LENGTH);
|
|
CHECK_RESULT(res != 0);
|
|
|
|
res = EVP_CipherFinal_ex(self->decrypt_ctx, NULL, &outlen2);
|
|
if (res == 0) {
|
|
PyErr_SetString(CryptoError, "Payload decryption failed");
|
|
return NULL;
|
|
}
|
|
|
|
return PyBytes_FromStringAndSize((const char*)self->buffer, outlen);
|
|
}
|
|
|
|
static PyObject*
|
|
AEAD_encrypt(AEADObject *self, PyObject *args)
|
|
{
|
|
const unsigned char *data, *associated;
|
|
Py_ssize_t data_len, associated_len;
|
|
int outlen, outlen2, res;
|
|
uint64_t pn;
|
|
|
|
if (!PyArg_ParseTuple(args, "y#y#K", &data, &data_len, &associated, &associated_len, &pn))
|
|
return NULL;
|
|
|
|
if (data_len > PACKET_LENGTH_MAX) {
|
|
PyErr_SetString(CryptoError, "Invalid payload length");
|
|
return NULL;
|
|
}
|
|
|
|
memcpy(self->nonce, self->iv, AEAD_NONCE_LENGTH);
|
|
for (int i = 0; i < 8; ++i) {
|
|
self->nonce[AEAD_NONCE_LENGTH - 1 - i] ^= (uint8_t)(pn >> 8 * i);
|
|
}
|
|
|
|
res = EVP_CipherInit_ex(self->encrypt_ctx, NULL, NULL, self->key, self->nonce, 1);
|
|
CHECK_RESULT(res != 0);
|
|
|
|
res = EVP_CipherUpdate(self->encrypt_ctx, NULL, &outlen, associated, associated_len);
|
|
CHECK_RESULT(res != 0);
|
|
|
|
res = EVP_CipherUpdate(self->encrypt_ctx, self->buffer, &outlen, data, data_len);
|
|
CHECK_RESULT(res != 0);
|
|
|
|
res = EVP_CipherFinal_ex(self->encrypt_ctx, NULL, &outlen2);
|
|
CHECK_RESULT(res != 0 && outlen2 == 0);
|
|
|
|
res = EVP_CIPHER_CTX_ctrl(self->encrypt_ctx, EVP_CTRL_CCM_GET_TAG, AEAD_TAG_LENGTH, self->buffer + outlen);
|
|
CHECK_RESULT(res != 0);
|
|
|
|
return PyBytes_FromStringAndSize((const char*)self->buffer, outlen + AEAD_TAG_LENGTH);
|
|
}
|
|
|
|
static PyMethodDef AEAD_methods[] = {
|
|
{"decrypt", (PyCFunction)AEAD_decrypt, METH_VARARGS, ""},
|
|
{"encrypt", (PyCFunction)AEAD_encrypt, METH_VARARGS, ""},
|
|
|
|
{NULL}
|
|
};
|
|
|
|
static PyType_Slot AEADType_slots[] = {
|
|
{Py_tp_dealloc, AEAD_dealloc},
|
|
{Py_tp_methods, AEAD_methods},
|
|
{Py_tp_doc, "AEAD objects"},
|
|
{Py_tp_init, AEAD_init},
|
|
{0, 0},
|
|
};
|
|
|
|
static PyType_Spec AEADType_spec = {
|
|
MODULE_NAME ".AEADType",
|
|
sizeof(AEADObject),
|
|
0,
|
|
Py_TPFLAGS_DEFAULT,
|
|
AEADType_slots
|
|
};
|
|
|
|
/* HeaderProtection */
|
|
|
|
typedef struct {
|
|
PyObject_HEAD
|
|
EVP_CIPHER_CTX *ctx;
|
|
int is_chacha20;
|
|
unsigned char buffer[PACKET_LENGTH_MAX];
|
|
unsigned char mask[31];
|
|
unsigned char zero[5];
|
|
} HeaderProtectionObject;
|
|
|
|
static PyObject *HeaderProtectionType;
|
|
|
|
static int
|
|
HeaderProtection_init(HeaderProtectionObject *self, PyObject *args, PyObject *kwargs)
|
|
{
|
|
const char *cipher_name;
|
|
const unsigned char *key;
|
|
Py_ssize_t cipher_name_len, key_len;
|
|
int res;
|
|
|
|
if (!PyArg_ParseTuple(args, "y#y#", &cipher_name, &cipher_name_len, &key, &key_len))
|
|
return -1;
|
|
|
|
const EVP_CIPHER *evp_cipher = EVP_get_cipherbyname(cipher_name);
|
|
if (evp_cipher == 0) {
|
|
PyErr_Format(CryptoError, "Invalid cipher name: %s", cipher_name);
|
|
return -1;
|
|
}
|
|
|
|
memset(self->mask, 0, sizeof(self->mask));
|
|
memset(self->zero, 0, sizeof(self->zero));
|
|
self->is_chacha20 = cipher_name_len == 8 && memcmp(cipher_name, "chacha20", 8) == 0;
|
|
|
|
self->ctx = EVP_CIPHER_CTX_new();
|
|
CHECK_RESULT_CTOR(self->ctx != 0);
|
|
|
|
res = EVP_CipherInit_ex(self->ctx, evp_cipher, NULL, NULL, NULL, 1);
|
|
CHECK_RESULT_CTOR(res != 0);
|
|
|
|
res = EVP_CIPHER_CTX_set_key_length(self->ctx, key_len);
|
|
CHECK_RESULT_CTOR(res != 0);
|
|
|
|
res = EVP_CipherInit_ex(self->ctx, NULL, NULL, key, NULL, 1);
|
|
CHECK_RESULT_CTOR(res != 0);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void
|
|
HeaderProtection_dealloc(HeaderProtectionObject *self)
|
|
{
|
|
EVP_CIPHER_CTX_free(self->ctx);
|
|
PyTypeObject *tp = Py_TYPE(self);
|
|
freefunc free = PyType_GetSlot(tp, Py_tp_free);
|
|
free(self);
|
|
Py_DECREF(tp);
|
|
}
|
|
|
|
static int HeaderProtection_mask(HeaderProtectionObject *self, const unsigned char* sample)
|
|
{
|
|
int outlen;
|
|
if (self->is_chacha20) {
|
|
return EVP_CipherInit_ex(self->ctx, NULL, NULL, NULL, sample, 1) &&
|
|
EVP_CipherUpdate(self->ctx, self->mask, &outlen, self->zero, sizeof(self->zero));
|
|
} else {
|
|
return EVP_CipherUpdate(self->ctx, self->mask, &outlen, sample, SAMPLE_LENGTH);
|
|
}
|
|
}
|
|
|
|
static PyObject*
|
|
HeaderProtection_apply(HeaderProtectionObject *self, PyObject *args)
|
|
{
|
|
const unsigned char *header, *payload;
|
|
Py_ssize_t header_len, payload_len;
|
|
int res;
|
|
|
|
if (!PyArg_ParseTuple(args, "y#y#", &header, &header_len, &payload, &payload_len))
|
|
return NULL;
|
|
|
|
int pn_length = (header[0] & 0x03) + 1;
|
|
int pn_offset = header_len - pn_length;
|
|
|
|
res = HeaderProtection_mask(self, payload + PACKET_NUMBER_LENGTH_MAX - pn_length);
|
|
CHECK_RESULT(res != 0);
|
|
|
|
memcpy(self->buffer, header, header_len);
|
|
memcpy(self->buffer + header_len, payload, payload_len);
|
|
|
|
if (self->buffer[0] & 0x80) {
|
|
self->buffer[0] ^= self->mask[0] & 0x0F;
|
|
} else {
|
|
self->buffer[0] ^= self->mask[0] & 0x1F;
|
|
}
|
|
|
|
for (int i = 0; i < pn_length; ++i) {
|
|
self->buffer[pn_offset + i] ^= self->mask[1 + i];
|
|
}
|
|
|
|
return PyBytes_FromStringAndSize((const char*)self->buffer, header_len + payload_len);
|
|
}
|
|
|
|
static PyObject*
|
|
HeaderProtection_remove(HeaderProtectionObject *self, PyObject *args)
|
|
{
|
|
const unsigned char *packet;
|
|
Py_ssize_t packet_len;
|
|
int pn_offset, res;
|
|
|
|
if (!PyArg_ParseTuple(args, "y#I", &packet, &packet_len, &pn_offset))
|
|
return NULL;
|
|
|
|
res = HeaderProtection_mask(self, packet + pn_offset + PACKET_NUMBER_LENGTH_MAX);
|
|
CHECK_RESULT(res != 0);
|
|
|
|
memcpy(self->buffer, packet, pn_offset + PACKET_NUMBER_LENGTH_MAX);
|
|
|
|
if (self->buffer[0] & 0x80) {
|
|
self->buffer[0] ^= self->mask[0] & 0x0F;
|
|
} else {
|
|
self->buffer[0] ^= self->mask[0] & 0x1F;
|
|
}
|
|
|
|
int pn_length = (self->buffer[0] & 0x03) + 1;
|
|
uint32_t pn_truncated = 0;
|
|
for (int i = 0; i < pn_length; ++i) {
|
|
self->buffer[pn_offset + i] ^= self->mask[1 + i];
|
|
pn_truncated = self->buffer[pn_offset + i] | (pn_truncated << 8);
|
|
}
|
|
|
|
return Py_BuildValue("y#i", self->buffer, pn_offset + pn_length, pn_truncated);
|
|
}
|
|
|
|
static PyMethodDef HeaderProtection_methods[] = {
|
|
{"apply", (PyCFunction)HeaderProtection_apply, METH_VARARGS, ""},
|
|
{"remove", (PyCFunction)HeaderProtection_remove, METH_VARARGS, ""},
|
|
{NULL}
|
|
};
|
|
|
|
static PyType_Slot HeaderProtectionType_slots[] = {
|
|
{Py_tp_dealloc, HeaderProtection_dealloc},
|
|
{Py_tp_methods, HeaderProtection_methods},
|
|
{Py_tp_doc, "HeaderProtection objects"},
|
|
{Py_tp_init, HeaderProtection_init},
|
|
{0, 0},
|
|
};
|
|
|
|
static PyType_Spec HeaderProtectionType_spec = {
|
|
MODULE_NAME ".HeaderProtectionType",
|
|
sizeof(HeaderProtectionObject),
|
|
0,
|
|
Py_TPFLAGS_DEFAULT,
|
|
HeaderProtectionType_slots
|
|
};
|
|
|
|
static struct PyModuleDef moduledef = {
|
|
PyModuleDef_HEAD_INIT,
|
|
MODULE_NAME, /* m_name */
|
|
"Cryptography utilities.", /* m_doc */
|
|
-1, /* m_size */
|
|
NULL, /* m_methods */
|
|
NULL, /* m_reload */
|
|
NULL, /* m_traverse */
|
|
NULL, /* m_clear */
|
|
NULL, /* m_free */
|
|
};
|
|
|
|
PyMODINIT_FUNC
|
|
PyInit__crypto(void)
|
|
{
|
|
PyObject* m;
|
|
|
|
m = PyModule_Create(&moduledef);
|
|
if (m == NULL)
|
|
return NULL;
|
|
|
|
CryptoError = PyErr_NewException(MODULE_NAME ".CryptoError", PyExc_ValueError, NULL);
|
|
Py_INCREF(CryptoError);
|
|
PyModule_AddObject(m, "CryptoError", CryptoError);
|
|
|
|
AEADType = PyType_FromSpec(&AEADType_spec);
|
|
if (AEADType == NULL)
|
|
return NULL;
|
|
PyModule_AddObject(m, "AEAD", AEADType);
|
|
|
|
HeaderProtectionType = PyType_FromSpec(&HeaderProtectionType_spec);
|
|
if (HeaderProtectionType == NULL)
|
|
return NULL;
|
|
PyModule_AddObject(m, "HeaderProtection", HeaderProtectionType);
|
|
|
|
// ensure required ciphers are initialised
|
|
EVP_add_cipher(EVP_aes_128_ecb());
|
|
EVP_add_cipher(EVP_aes_128_gcm());
|
|
EVP_add_cipher(EVP_aes_256_ecb());
|
|
EVP_add_cipher(EVP_aes_256_gcm());
|
|
|
|
return m;
|
|
}
|