From d4404288d34dbeeb77e548f058c4297343b84c3b Mon Sep 17 00:00:00 2001 From: Christian Heimes Date: Thu, 11 Aug 2016 19:55:44 +0200 Subject: [PATCH] AF_ALG kernel crypto support for socket module --- Doc/library/socket.rst | 31 ++++++- Lib/test/test_socket.py | 56 ++++++++++++ Modules/socketmodule.c | 221 +++++++++++++++++++++++++++++++++++++++++++++++- configure | 49 ++++++++--- configure.ac | 13 +++ pyconfig.h.in | 3 + 6 files changed, 356 insertions(+), 17 deletions(-) diff --git a/Doc/library/socket.rst b/Doc/library/socket.rst index d0a3c5edab6a7384185fb7d4ed78611939d511a1..e963c7d9b90dfb6557c24454ed1e508db2ce2344 100644 --- a/Doc/library/socket.rst +++ b/Doc/library/socket.rst @@ -131,6 +131,20 @@ created. Socket addresses are represented as follows: string format. (ex. ``b'12:23:34:45:56:67'``) This protocol is not supported under FreeBSD. +- :const:`AF_ALG` is a Linux-only socket based interface to Kernel + cryptography. An algorithm socket is configured with a tuple of two to four + elements ``(type, name [, feat [, mask]])``, where: + + - *type* is the algorithm type as string, e.g. ``hash``, ``skcipher`` + or ``rng``. + + - *name* is the algorithm name and operation mode as string, e.g. + ``sha256``, ``hmac(sha256)``, ``cbc(aes)`` or ``drbg_nopr_ctr_aes256``. + + - *feat* and *mask* are unsigned int 32 values. + + .. versionadded:: 3.6 + - Certain other address families (:const:`AF_PACKET`, :const:`AF_CAN`) support specific representations. @@ -328,7 +342,6 @@ Constants .. versionadded:: 3.3 - .. data:: SIO_RCVALL SIO_KEEPALIVE_VALS SIO_LOOPBACK_FAST_PATH @@ -346,6 +359,17 @@ Constants TIPC related constants, matching the ones exported by the C socket API. See the TIPC documentation for more information. +.. data:: AF_ALG + SO_ALG + ALG_* + RDS_* + + Constants for Linux Kernel cryptography. + + Availability: Linux >= 2.6.38. + + .. versionadded:: 3.6 + .. data:: AF_LINK Availability: BSD, OSX. @@ -858,6 +882,11 @@ to sockets. an exception, the method now retries the system call instead of raising an :exc:`InterruptedError` exception (see :pep:`475` for the rationale). +.. method:: socket.algset(op[, iv[, assoclen[, flags]]]) + + Set mode, IV, AEAD assoc length and flags for :const:`AF_ALG` socket. + + .. versionadded:: 3.6 .. method:: socket.bind(address) diff --git a/Lib/test/test_socket.py b/Lib/test/test_socket.py index 7fce13e175d1655650934084b0ba05d55bc88606..59c795f5792bf7b6aba5799c1f6b460832e3494b 100644 --- a/Lib/test/test_socket.py +++ b/Lib/test/test_socket.py @@ -21,6 +21,7 @@ import pickle import struct import random import string +from binascii import hexlify, unhexlify try: import multiprocessing except ImportError: @@ -5325,6 +5326,59 @@ class SendfileUsingSendfileTest(SendfileUsingSendTest): def meth_from_sock(self, sock): return getattr(sock, "_sendfile_use_sendfile") +@unittest.skipUnless(hasattr(socket, "AF_ALG"), 'AF_ALG required') +class LinuxKernelCryptoAPI(unittest.TestCase): + # tests for AF_ALG + def create_alg(self, typ, name): + sock = socket.socket(socket.AF_ALG, socket.SOCK_SEQPACKET, 0) + sock.bind((typ, name)) + return sock + + def test_sha256(self): + expected = b"ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad" + with self.create_alg('hash', 'sha256') as algo: + op, _ = algo.accept() + try: + op.send(b"abc") + self.assertEqual(hexlify(op.recv(512)), expected) + finally: + op.close() + + op, _ = algo.accept() + try: + op.send(b'a', socket.MSG_MORE) + op.send(b'b', socket.MSG_MORE) + op.send(b'c', socket.MSG_MORE) + op.send(b'') + self.assertEqual(hexlify(op.recv(512)), expected) + finally: + op.close() + + def test_hmac_sha1(self): + expected = b"effcdf6ae5eb2fa2d27416d5f184df9c259a7c79" + with self.create_alg('hash', 'hmac(sha1)') as algo: + algo.setsockopt(socket.SOL_ALG, socket.ALG_SET_KEY, b"Jefe") + op, _ = algo.accept() + try: + op.sendall(b"what do ya want for nothing?") + self.assertEqual(hexlify(op.recv(512)), expected) + finally: + op.close() + + def test_aes_cbc(self): + key = unhexlify('06a9214036b8a15b512e03d534120006') + iv = unhexlify('3dafba429d9eb430b422da802c9fac41') + msg = b"Single block msg" + ciphertext = unhexlify('e353779c1079aeb82708942dbe77181a') + with self.create_alg('skcipher', 'cbc(aes)') as algo: + algo.setsockopt(socket.SOL_ALG, socket.ALG_SET_KEY, key) + op, _ = algo.accept() + try: + op.algset(op=socket.ALG_OP_ENCRYPT, iv=iv) + op.sendall(msg) + self.assertEqual(op.recv(16), ciphertext) + finally: + op.close() def test_main(): tests = [GeneralModuleTests, BasicTCPTest, TCPCloserTest, TCPTimeoutTest, @@ -5352,6 +5406,7 @@ def test_main(): tests.extend([TIPCTest, TIPCThreadableTest]) tests.extend([BasicCANTest, CANTest]) tests.extend([BasicRDSTest, RDSTest]) + tests.append(LinuxKernelCryptoAPI) tests.extend([ CmsgMacroTests, SendmsgUDPTest, @@ -5381,6 +5436,7 @@ def test_main(): SendfileUsingSendfileTest, ]) + tests = [LinuxKernelCryptoAPI] thread_info = support.threading_setup() support.run_unittest(*tests) support.threading_cleanup(*thread_info) diff --git a/Modules/socketmodule.c b/Modules/socketmodule.c index d21509e9ebe5e62d758c36fee9f57677e3b9addd..8126917c1b0d91c04462d7205eb1b8cf2965d561 100644 --- a/Modules/socketmodule.c +++ b/Modules/socketmodule.c @@ -286,6 +286,16 @@ http://cvsweb.netbsd.org/bsdweb.cgi/src/lib/libc/net/getaddrinfo.c.diff?r1=1.82& #include #endif +#ifdef HAVE_SOCKADDR_ALG +#include +#ifndef AF_ALG +#define AF_ALG 38 +#endif +#ifndef SOL_ALG +#define SOL_ALG 279 +#endif +#endif + /* Generic socket object definitions and includes */ #define PySocket_BUILDING_SOCKET #include "socketmodule.h" @@ -1375,6 +1385,22 @@ makesockaddr(SOCKET_T sockfd, struct sockaddr *addr, size_t addrlen, int proto) } #endif +#ifdef HAVE_SOCKADDR_ALG + case AF_ALG: + { + struct sockaddr_alg *a = (struct sockaddr_alg *)addr; + return Py_BuildValue("s#s#HH", + a->salg_type, + strnlen((const char*)a->salg_type, + sizeof(a->salg_type)), + a->salg_name, + strnlen((const char*)a->salg_name, + sizeof(a->salg_name)), + a->salg_feat, + a->salg_mask); + } +#endif + /* More cases here... */ default: @@ -1940,6 +1966,35 @@ getsockaddrarg(PySocketSockObject *s, PyObject *args, return 0; } #endif +#ifdef HAVE_SOCKADDR_ALG + case AF_ALG: + { + struct sockaddr_alg *sa; + char *type; + char *name; + sa = (struct sockaddr_alg *)addr_ret; + + memset(sa, 0, sizeof(*sa)); + sa->salg_family = AF_ALG; + + if (!PyArg_ParseTuple(args, "ss|HH:getsockaddrarg", + &type, &name, &sa->salg_feat, &sa->salg_mask)) + return 0; + if (strlen(type) > sizeof(sa->salg_type)) { + PyErr_SetString(PyExc_ValueError, "AF_ALG type too long."); + return 0; + } + strncpy((char *)sa->salg_type, type, sizeof(sa->salg_type)); + if (strlen(name) > sizeof(sa->salg_name)) { + PyErr_SetString(PyExc_ValueError, "AF_ALG name too long."); + return 0; + } + strncpy((char *)sa->salg_name, name, sizeof(sa->salg_name)); + + *len_ret = sizeof(*sa); + return 1; + } +#endif /* More cases here... */ @@ -2061,6 +2116,13 @@ getsockaddrlen(PySocketSockObject *s, socklen_t *len_ret) return 0; } #endif +#ifdef HAVE_SOCKADDR_ALG + case AF_ALG: + { + *len_ret = sizeof (struct sockaddr_alg); + return 1; + } +#endif /* More cases here... */ @@ -2220,10 +2282,21 @@ static int sock_accept_impl(PySocketSockObject *s, void *data) { struct sock_accept *ctx = data; + struct sockaddr *addr = SAS2SA(ctx->addrbuf); + socklen_t *addrlen = ctx->addrlen; +#ifdef HAVE_SOCKADDR_ALG + /* AF_ALG does not support accept() with addr and raises + * ECONNABORTED instead. */ + if (s->sock_family == AF_ALG) { + addr = NULL; + addrlen = NULL; + *ctx->addrlen = 0; + } +#endif #if defined(HAVE_ACCEPT4) && defined(SOCK_CLOEXEC) if (accept4_works != 0) { - ctx->result = accept4(s->sock_fd, SAS2SA(ctx->addrbuf), ctx->addrlen, + ctx->result = accept4(s->sock_fd, addr, addrlen, SOCK_CLOEXEC); if (ctx->result == INVALID_SOCKET && accept4_works == -1) { /* On Linux older than 2.6.28, accept4() fails with ENOSYS */ @@ -2231,9 +2304,9 @@ sock_accept_impl(PySocketSockObject *s, void *data) } } if (accept4_works == 0) - ctx->result = accept(s->sock_fd, SAS2SA(ctx->addrbuf), ctx->addrlen); + ctx->result = accept(s->sock_fd, addr, addrlen); #else - ctx->result = accept(s->sock_fd, SAS2SA(ctx->addrbuf), ctx->addrlen); + ctx->result = accept(s->sock_fd, addr, addrlen); #endif #ifdef MS_WINDOWS @@ -3995,6 +4068,125 @@ the message. The return value is the number of bytes of non-ancillary\n\ data sent."); #endif /* CMSG_LEN */ +#ifdef HAVE_SOCKADDR_ALG +static PyObject* +sock_algset(PySocketSockObject *self, PyObject *args, PyObject *kwds) +{ + PyObject *retval = NULL; + Py_buffer iv = {NULL, NULL}; + int type = -1; + int assoclen = -1; + unsigned int *uiptr; + int flags = 0; + struct msghdr msg; + struct cmsghdr *header = NULL; + struct af_alg_iv *alg_iv = NULL; + struct sock_sendmsg ctx; + Py_ssize_t controllen; + void *controlbuf = NULL; + static char *keywords[] = {"op", "iv", "assoclen", "flags", 0}; + + if (self->sock_family != AF_ALG) { + PyErr_SetString(PyExc_OSError, + "algset is only supported for AF_ALG"); + return NULL; + } + + if (!PyArg_ParseTupleAndKeywords(args, kwds, + "|$iy*ii:socket", keywords, + &type, &iv, &assoclen, &flags)) + return NULL; + + if (type < 0) { + PyErr_SetString(PyExc_TypeError, + "Required argument 'op' (pos 1) not found"); + goto finally; + } + + memset(&msg, 0, sizeof(msg)); + + controllen = CMSG_SPACE(4); + if (iv.buf != NULL) { + controllen += CMSG_SPACE(sizeof(*alg_iv) + iv.len); + } + if (assoclen >= 0) { + controllen += CMSG_SPACE(4); + } + + controlbuf = PyMem_Malloc(controllen); + if (controlbuf == NULL) { + return PyErr_NoMemory(); + } + memset(controlbuf, 0, controllen); + + msg.msg_controllen = controllen; + msg.msg_control = controlbuf; + + /* set operation to encrypt or decrypt */ + header = CMSG_FIRSTHDR(&msg); + if (header == NULL) { + PyErr_SetString(PyExc_RuntimeError, + "unexpected NULL result from CMSG_FIRSTHDR"); + goto finally; + } + header->cmsg_level = SOL_ALG; + header->cmsg_type = ALG_SET_OP; + header->cmsg_len = CMSG_LEN(4); + uiptr = (void*)CMSG_DATA(header); + *uiptr = (unsigned int)type; + + /* set initialization vector */ + if (iv.buf != NULL) { + header = CMSG_NXTHDR(&msg, header); + if (header == NULL) { + PyErr_SetString(PyExc_RuntimeError, + "unexpected NULL result from CMSG_NXTHDR(iv)"); + goto finally; + } + header->cmsg_level = SOL_ALG; + header->cmsg_type = ALG_SET_IV; + header->cmsg_len = CMSG_SPACE(sizeof(*alg_iv) + iv.len); + alg_iv = (void*)CMSG_DATA(header); + alg_iv->ivlen = iv.len; + memcpy(alg_iv->iv, iv.buf, iv.len); + } + + /* set length of associated data for AEAD */ + if (assoclen >= 0) { + header = CMSG_NXTHDR(&msg, header); + if (header == NULL) { + PyErr_SetString(PyExc_RuntimeError, + "unexpected NULL result from CMSG_NXTHDR(assoc)"); + goto finally; + } + header->cmsg_level = SOL_ALG; + header->cmsg_type = ALG_SET_AEAD_ASSOCLEN; + header->cmsg_len = CMSG_LEN(4); + uiptr = (void*)CMSG_DATA(header); + *uiptr = (unsigned int)assoclen; + } + + ctx.msg = &msg; + ctx.flags = flags; + if (sock_call(self, 1, sock_sendmsg_impl, &ctx) < 0) + goto finally; + + retval = PyLong_FromSsize_t(ctx.result); + + finally: + if (iv.buf != NULL) + PyBuffer_Release(&iv); + if (controlbuf != NULL) + PyMem_Free(controlbuf); + return retval; +} + +PyDoc_STRVAR(algset_doc, +"algset(op[, iv[, assoclen[, flags]]])\n\ +\n\ +Set operation mode, IV and length of associated data for an AF_ALG\n\ +operation socket."); +#endif /* s.shutdown(how) method */ @@ -4174,6 +4366,10 @@ static PyMethodDef sock_methods[] = { {"sendmsg", (PyCFunction)sock_sendmsg, METH_VARARGS, sendmsg_doc}, #endif +#ifdef HAVE_SOCKADDR_ALG + {"algset", (PyCFunction)sock_algset, METH_VARARGS | METH_KEYWORDS, + algset_doc}, +#endif {NULL, NULL} /* sentinel */ }; @@ -6277,6 +6473,9 @@ PyInit__socket(void) /* Reserved for Werner's ATM */ PyModule_AddIntMacro(m, AF_AAL5); #endif +#ifdef HAVE_SOCKADDR_ALG + PyModule_AddIntMacro(m, AF_ALG); /* Linux crypto */ +#endif #ifdef AF_X25 /* Reserved for X.25 project */ PyModule_AddIntMacro(m, AF_X25); @@ -6491,6 +6690,19 @@ PyInit__socket(void) PyModule_AddIntMacro(m, TIPC_TOP_SRV); #endif +#ifdef HAVE_SOCKADDR_ALG +/* Socket options */ + PyModule_AddIntMacro(m, ALG_SET_KEY); + PyModule_AddIntMacro(m, ALG_SET_IV); + PyModule_AddIntMacro(m, ALG_SET_OP); + PyModule_AddIntMacro(m, ALG_SET_AEAD_ASSOCLEN); + PyModule_AddIntMacro(m, ALG_SET_AEAD_AUTHSIZE); + +/* Operations */ + PyModule_AddIntMacro(m, ALG_OP_DECRYPT); + PyModule_AddIntMacro(m, ALG_OP_ENCRYPT); +#endif + /* Socket types */ PyModule_AddIntMacro(m, SOCK_STREAM); PyModule_AddIntMacro(m, SOCK_DGRAM); @@ -6749,6 +6961,9 @@ PyInit__socket(void) #ifdef SOL_RDS PyModule_AddIntMacro(m, SOL_RDS); #endif +#ifdef HAVE_SOCKADDR_ALG + PyModule_AddIntMacro(m, SOL_ALG); +#endif #ifdef RDS_CANCEL_SENT_TO PyModule_AddIntMacro(m, RDS_CANCEL_SENT_TO); #endif diff --git a/configure b/configure index e06fce424accdef06056ce8a06f52b6ef4b6d9e6..8e763dec1da9d2765959ef14fefd7a8fce7d8a7f 100755 --- a/configure +++ b/configure @@ -775,7 +775,6 @@ infodir docdir oldincludedir includedir -runstatedir localstatedir sharedstatedir sysconfdir @@ -886,7 +885,6 @@ datadir='${datarootdir}' sysconfdir='${prefix}/etc' sharedstatedir='${prefix}/com' localstatedir='${prefix}/var' -runstatedir='${localstatedir}/run' includedir='${prefix}/include' oldincludedir='/usr/include' docdir='${datarootdir}/doc/${PACKAGE_TARNAME}' @@ -1139,15 +1137,6 @@ do | -silent | --silent | --silen | --sile | --sil) silent=yes ;; - -runstatedir | --runstatedir | --runstatedi | --runstated \ - | --runstate | --runstat | --runsta | --runst | --runs \ - | --run | --ru | --r) - ac_prev=runstatedir ;; - -runstatedir=* | --runstatedir=* | --runstatedi=* | --runstated=* \ - | --runstate=* | --runstat=* | --runsta=* | --runst=* | --runs=* \ - | --run=* | --ru=* | --r=*) - runstatedir=$ac_optarg ;; - -sbindir | --sbindir | --sbindi | --sbind | --sbin | --sbi | --sb) ac_prev=sbindir ;; -sbindir=* | --sbindir=* | --sbindi=* | --sbind=* | --sbin=* \ @@ -1285,7 +1274,7 @@ fi for ac_var in exec_prefix prefix bindir sbindir libexecdir datarootdir \ datadir sysconfdir sharedstatedir localstatedir includedir \ oldincludedir docdir infodir htmldir dvidir pdfdir psdir \ - libdir localedir mandir runstatedir + libdir localedir mandir do eval ac_val=\$$ac_var # Remove trailing slashes. @@ -1438,7 +1427,6 @@ Fine tuning of the installation directories: --sysconfdir=DIR read-only single-machine data [PREFIX/etc] --sharedstatedir=DIR modifiable architecture-independent data [PREFIX/com] --localstatedir=DIR modifiable single-machine data [PREFIX/var] - --runstatedir=DIR modifiable per-process data [LOCALSTATEDIR/run] --libdir=DIR object code libraries [EPREFIX/lib] --includedir=DIR C header files [PREFIX/include] --oldincludedir=DIR C header files for non-gcc [/usr/include] @@ -13036,6 +13024,41 @@ $as_echo "#define HAVE_SOCKADDR_STORAGE 1" >>confdefs.h fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for sockaddr_alg" >&5 +$as_echo_n "checking for sockaddr_alg... " >&6; } +if ${ac_cv_struct_sockaddr_alg+:} false; then : + $as_echo_n "(cached) " >&6 +else + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +# include +# include +# include +int +main () +{ +struct sockaddr_alg s + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + ac_cv_struct_sockaddr_alg=yes +else + ac_cv_struct_sockaddr_alg=no +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext +fi + +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_struct_sockaddr_alg" >&5 +$as_echo "$ac_cv_struct_sockaddr_alg" >&6; } +if test $ac_cv_struct_sockaddr_alg = yes; then + +$as_echo "#define HAVE_SOCKADDR_ALG 1" >>confdefs.h + +fi + # checks for compiler characteristics { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether char is unsigned" >&5 diff --git a/configure.ac b/configure.ac index a83fe51e9e079de168d522f1e39699fa1859ce45..751bfb9347e9061b8576ed37bda927dc46cecda0 100644 --- a/configure.ac +++ b/configure.ac @@ -3836,6 +3836,19 @@ if test $ac_cv_struct_sockaddr_storage = yes; then AC_DEFINE(HAVE_SOCKADDR_STORAGE, 1, [struct sockaddr_storage (sys/socket.h)]) fi +AC_MSG_CHECKING(for sockaddr_alg) +AC_CACHE_VAL(ac_cv_struct_sockaddr_alg, +AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ +# include +# include +# include ]], [[struct sockaddr_alg s]])], + [ac_cv_struct_sockaddr_alg=yes], + [ac_cv_struct_sockaddr_alg=no])) +AC_MSG_RESULT($ac_cv_struct_sockaddr_alg) +if test $ac_cv_struct_sockaddr_alg = yes; then + AC_DEFINE(HAVE_SOCKADDR_ALG, 1, [struct sockaddr_alg (linux/if_alg.h)]) +fi + # checks for compiler characteristics AC_C_CHAR_UNSIGNED diff --git a/pyconfig.h.in b/pyconfig.h.in index dce5cfdab370252f7ae751601f826a2dbab05fcb..e6f8e857da55ffef9ebbdcd8ad3506ea2d11c8a1 100644 --- a/pyconfig.h.in +++ b/pyconfig.h.in @@ -892,6 +892,9 @@ /* Define to 1 if you have the `snprintf' function. */ #undef HAVE_SNPRINTF +/* struct sockaddr_alg (linux/if_alg.h) */ +#undef HAVE_SOCKADDR_ALG + /* Define if sockaddr has sa_len member */ #undef HAVE_SOCKADDR_SA_LEN -- 2.7.4