From patchwork Mon Feb 27 12:50:21 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Arne Schwabe X-Patchwork-Id: 3093 Return-Path: Delivered-To: patchwork@openvpn.net Received: by 2002:a05:7300:2310:b0:9f:bfa4:120f with SMTP id r16csp1132728dye; Mon, 27 Feb 2023 04:51:29 -0800 (PST) X-Google-Smtp-Source: AK7set+RnXYU4NX4PjJKMAINUBn7mMrZaKXfCXVwhDvIZWXVjM7x/RHYy1cByOIQf3Kqvj09x5/2 X-Received: by 2002:a05:6a20:3d17:b0:cc:75b8:7cba with SMTP id y23-20020a056a203d1700b000cc75b87cbamr13568094pzi.43.1677502289194; Mon, 27 Feb 2023 04:51:29 -0800 (PST) ARC-Seal: i=1; a=rsa-sha256; t=1677502289; cv=none; d=google.com; s=arc-20160816; b=VBlIY1CR4X2Bk/PoNDjg5E/hnJxrPNPgBRTx2MbQxecwuxcTU9Dvnxkvw8/HmPxbmH W+xwNrZNRSp04sWZsz8Fl2dmzzBKaIdrGXZfIGeZbW7xcep7OaiqaGNthCCebA1ozN8k udfRSo9402wydGT/+4L4Jtb5nJ0wNBT6m5aiAY0Afk/HprX5S1uoA5vuPXR+DmP45cUS jGDQZ2DYDZHDj+rQm+RS041Z6ZryHrpQBGjiWWCrbAZJwsDrYaZH8ZGAmPTcu+7GJ4mX AyAok89BRFZ/rEePvI2226HXncH2UKrVipR2ZrfvhyoIko13O+khm5ExV/QXHstUy9jX F2GQ== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=errors-to:content-transfer-encoding:list-subscribe:list-help :list-post:list-archive:list-unsubscribe:list-id:precedence:subject :mime-version:message-id:date:to:from:dkim-signature:dkim-signature; bh=Hd/2FtLVK9aGS6vtAr7ekQXQzT96TeUFhhdDDwEnbps=; b=LBdGbosSfsZTvfsKJ3fvCH+TxnFVrHiGuOhxrLjQnHhFJjk9lD7nzNNdGSGZdKA+lA KZSDn90x6QlmluiPUcvB4AWnipRs5a1EbY8D5Ga6otpnbU9S6G3jxLZ6aJohESrwXDhP W8jbyx+2SzOoC2d4ZJf2JhVVY9hd/au8+vwrj89PPZkJwAVGB/usAgsYztK2LKdBpgIa KiH+fitQleXva/NQJYm0LJFvbJcPJ0a1pCrWeOqWPil7RuHs0ZtBVEeJAueyMQ15VE0o RHatlTV+HzEsuCQCjsddVFnmHXMhpWv5kVHClcu64JVJ6vRZEdbCdFs7QtD/PnjxyQAW bHxQ== ARC-Authentication-Results: i=1; mx.google.com; dkim=neutral (body hash did not verify) header.i=@sourceforge.net header.s=x header.b=TGKBPCYS; dkim=neutral (body hash did not verify) header.i=@sf.net header.s=x header.b=dGsA+LSm; spf=pass (google.com: domain of openvpn-devel-bounces@lists.sourceforge.net designates 216.105.38.7 as permitted sender) smtp.mailfrom=openvpn-devel-bounces@lists.sourceforge.net Received: from lists.sourceforge.net (lists.sourceforge.net. [216.105.38.7]) by mx.google.com with ESMTPS id e7-20020a63e007000000b004fba312c234si7432868pgh.401.2023.02.27.04.51.28 (version=TLS1_2 cipher=ECDHE-ECDSA-AES128-GCM-SHA256 bits=128/128); Mon, 27 Feb 2023 04:51:29 -0800 (PST) Received-SPF: pass (google.com: domain of openvpn-devel-bounces@lists.sourceforge.net designates 216.105.38.7 as permitted sender) client-ip=216.105.38.7; Authentication-Results: mx.google.com; dkim=neutral (body hash did not verify) header.i=@sourceforge.net header.s=x header.b=TGKBPCYS; dkim=neutral (body hash did not verify) header.i=@sf.net header.s=x header.b=dGsA+LSm; spf=pass (google.com: domain of openvpn-devel-bounces@lists.sourceforge.net designates 216.105.38.7 as permitted sender) smtp.mailfrom=openvpn-devel-bounces@lists.sourceforge.net Received: from [127.0.0.1] (helo=sfs-ml-4.v29.lw.sourceforge.com) by sfs-ml-4.v29.lw.sourceforge.com with esmtp (Exim 4.95) (envelope-from ) id 1pWcxj-00010K-HZ; Mon, 27 Feb 2023 12:50:39 +0000 Received: from [172.30.20.202] (helo=mx.sourceforge.net) by sfs-ml-4.v29.lw.sourceforge.com with esmtps (TLS1.2) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.95) (envelope-from ) id 1pWcxh-00010D-C4 for openvpn-devel@lists.sourceforge.net; Mon, 27 Feb 2023 12:50:37 +0000 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=sourceforge.net; s=x; h=Content-Transfer-Encoding:MIME-Version:Message-Id: Date:Subject:To:From:Sender:Reply-To:Cc:Content-Type:Content-ID: Content-Description:Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc :Resent-Message-ID:In-Reply-To:References:List-Id:List-Help:List-Unsubscribe: List-Subscribe:List-Post:List-Owner:List-Archive; bh=c+7XKW5PRt32fmScs/RnL9/XBj3HZAN9ljNv7vcOtNQ=; b=TGKBPCYSbNLg5Pr/1xF/56qaIN 7vgsk5+duy6Hx70SyRGDM9P9WzL5pdmfNMZ1qwTtk332bmhDHfi95XNd1mstJ2J8ikm95sX+SPTtb tmBF3OegegHqFM3GPyX0/y7z1EWahOkMxBNQqGdFcKFIPjp9dRT49QwDCSPzD+sAnGUs=; DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=sf.net; s=x ; h=Content-Transfer-Encoding:MIME-Version:Message-Id:Date:Subject:To:From: Sender:Reply-To:Cc:Content-Type:Content-ID:Content-Description:Resent-Date: Resent-From:Resent-Sender:Resent-To:Resent-Cc:Resent-Message-ID:In-Reply-To: References:List-Id:List-Help:List-Unsubscribe:List-Subscribe:List-Post: List-Owner:List-Archive; bh=c+7XKW5PRt32fmScs/RnL9/XBj3HZAN9ljNv7vcOtNQ=; b=d GsA+LSmkqOg0cQKisc4qpXM/QWSwdDw9d+9om5d/D4+O57bkKZk7rEeP+XkNjIgUsYVhu6AJTAqw0 oIll41Y7Dzdl2AGj1YlGzVJqJ3KHvodA/E2QWBatCv9JC58YwGxTBAPc2ahyOczfDINZMsUX4vTol sdGjcyMDqYYMKF+0=; Received: from mail.blinkt.de ([192.26.174.232]) by sfi-mx-1.v28.lw.sourceforge.com with esmtps (TLS1.2:ECDHE-RSA-AES256-GCM-SHA384:256) (Exim 4.95) id 1pWcxg-003KH1-Hs for openvpn-devel@lists.sourceforge.net; Mon, 27 Feb 2023 12:50:37 +0000 Received: from kamera.blinkt.de ([2001:638:502:390:20c:29ff:fec8:535c]) by mail.blinkt.de with smtp (Exim 4.95 (FreeBSD)) (envelope-from ) id 1pWcxT-000IK8-SP for openvpn-devel@lists.sourceforge.net; Mon, 27 Feb 2023 13:50:23 +0100 Received: (nullmailer pid 2561425 invoked by uid 10006); Mon, 27 Feb 2023 12:50:23 -0000 From: Arne Schwabe To: openvpn-devel@lists.sourceforge.net Date: Mon, 27 Feb 2023 13:50:21 +0100 Message-Id: <20230227125023.2561379-1-arne@rfc2549.org> X-Mailer: git-send-email 2.25.1 MIME-Version: 1.0 X-Spam-Score: 0.3 (/) X-Spam-Report: Spam detection software, running on the system "util-spamd-1.v13.lw.sourceforge.com", has NOT identified this incoming email as spam. The original message has been attached to this so you can view it or label similar future email. If you have any questions, see the administrator of that system for details. Content preview: OpenSSL only supports SIPHASH with OpenSSL 3.1 and newer. The source code of siphash is quite small and has very liberal CC0 license, so include it instead of pulling an extra library for it. Change-Id: I1292894fe7f537049a97bee97af4419e5e854a00 Signed-off-by: Arne Schwabe --- src/openvpn/siphash.c | 212 ++++++++++++++++++++++++++++++++++++++++++ src/openvpn/siphash.h | 3 [...] Content analysis details: (0.3 points, 6.0 required) pts rule name description ---- ---------------------- -------------------------------------------------- 0.2 HEADER_FROM_DIFFERENT_DOMAINS From and EnvelopeFrom 2nd level mail domains are different 0.0 SPF_HELO_NONE SPF: HELO does not publish an SPF Record 0.0 SPF_NONE SPF: sender does not publish an SPF Record X-Headers-End: 1pWcxg-003KH1-Hs Subject: [Openvpn-devel] [PATCH 1/3] Add siphash reference implementation X-BeenThere: openvpn-devel@lists.sourceforge.net X-Mailman-Version: 2.1.21 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: openvpn-devel-bounces@lists.sourceforge.net X-getmail-retrieved-from-mailbox: Inbox X-GMAIL-THRID: =?utf-8?q?1758988640437590326?= X-GMAIL-MSGID: =?utf-8?q?1758988640437590326?= OpenSSL only supports SIPHASH with OpenSSL 3.1 and newer. The source code of siphash is quite small and has very liberal CC0 license, so include it instead of pulling an extra library for it. Change-Id: I1292894fe7f537049a97bee97af4419e5e854a00 Signed-off-by: Arne Schwabe --- src/openvpn/siphash.c | 212 ++++++++++++++++++++++++++++++++++++++++++ src/openvpn/siphash.h | 31 ++++++ 2 files changed, 243 insertions(+) create mode 100644 src/openvpn/siphash.c create mode 100644 src/openvpn/siphash.h diff --git a/src/openvpn/siphash.c b/src/openvpn/siphash.c new file mode 100644 index 000000000..b8a7bbc11 --- /dev/null +++ b/src/openvpn/siphash.c @@ -0,0 +1,212 @@ +/* + * SipHash reference C implementation + * + * Copyright (c) 2012-2022 Jean-Philippe Aumasson + * + * Copyright (c) 2012-2014 Daniel J. Bernstein + * + * To the extent possible under law, the author(s) have dedicated all copyright + * and related and neighboring rights to this software to the public domain + * worldwide. This software is distributed without any warranty. + * + * You should have received a copy of the CC0 Public Domain Dedication along + * with + * this software. If not, see + * . + */ + +#include "siphash.h" +#include +#include +#include + +/* default: SipHash-2-4 */ +#ifndef cROUNDS +#define cROUNDS 2 +#endif +#ifndef dROUNDS +#define dROUNDS 4 +#endif + +#define ROTL(x, b) (uint64_t)(((x) << (b)) | ((x) >> (64 - (b)))) + +#define U32TO8_LE(p, v) \ + (p)[0] = (uint8_t)((v)); \ + (p)[1] = (uint8_t)((v) >> 8); \ + (p)[2] = (uint8_t)((v) >> 16); \ + (p)[3] = (uint8_t)((v) >> 24); + +#define U64TO8_LE(p, v) \ + U32TO8_LE((p), (uint32_t)((v))); \ + U32TO8_LE((p) + 4, (uint32_t)((v) >> 32)); + +#define U8TO64_LE(p) \ + (((uint64_t)((p)[0])) | ((uint64_t)((p)[1]) << 8) \ + |((uint64_t)((p)[2]) << 16) | ((uint64_t)((p)[3]) << 24) \ + |((uint64_t)((p)[4]) << 32) | ((uint64_t)((p)[5]) << 40) \ + |((uint64_t)((p)[6]) << 48) | ((uint64_t)((p)[7]) << 56)) + +#define SIPROUND \ + do { \ + v0 += v1; \ + v1 = ROTL(v1, 13); \ + v1 ^= v0; \ + v0 = ROTL(v0, 32); \ + v2 += v3; \ + v3 = ROTL(v3, 16); \ + v3 ^= v2; \ + v0 += v3; \ + v3 = ROTL(v3, 21); \ + v3 ^= v0; \ + v2 += v1; \ + v1 = ROTL(v1, 17); \ + v1 ^= v2; \ + v2 = ROTL(v2, 32); \ + } while (0) + +#ifdef DEBUG_SIPHASH +#include + +#define TRACE \ + do { \ + printf("(%3zu) v0 %016" PRIx64 "\n", inlen, v0); \ + printf("(%3zu) v1 %016" PRIx64 "\n", inlen, v1); \ + printf("(%3zu) v2 %016" PRIx64 "\n", inlen, v2); \ + printf("(%3zu) v3 %016" PRIx64 "\n", inlen, v3); \ + } while (0) +#else /* ifdef DEBUG_SIPHASH */ +#define TRACE +#endif + +/* + * Computes a SipHash value + * in: pointer to input data (read-only) + * inlen: input data length in bytes (any size_t value) + * k: pointer to the key data (read-only), must be 16 bytes + * out: pointer to output data (write-only), outlen bytes must be allocated + * outlen: length of the output in bytes, must be 8 or 16 + */ +int +siphash(const void *in, const size_t inlen, const void *k, uint8_t *out, + const size_t outlen) +{ + + const unsigned char *ni = (const unsigned char *)in; + const unsigned char *kk = (const unsigned char *)k; + + assert((outlen == 8) || (outlen == 16)); + uint64_t v0 = UINT64_C(0x736f6d6570736575); + uint64_t v1 = UINT64_C(0x646f72616e646f6d); + uint64_t v2 = UINT64_C(0x6c7967656e657261); + uint64_t v3 = UINT64_C(0x7465646279746573); + uint64_t k0 = U8TO64_LE(kk); + uint64_t k1 = U8TO64_LE(kk + 8); + uint64_t m; + int i; + const unsigned char *end = ni + inlen - (inlen % sizeof(uint64_t)); + const int left = inlen & 7; + uint64_t b = ((uint64_t)inlen) << 56; + v3 ^= k1; + v2 ^= k0; + v1 ^= k1; + v0 ^= k0; + + if (outlen == 16) + { + v1 ^= 0xee; + } + + for (; ni != end; ni += 8) + { + m = U8TO64_LE(ni); + v3 ^= m; + + TRACE; + for (i = 0; i < cROUNDS; ++i) + { + SIPROUND; + } + + v0 ^= m; + } + + switch (left) + { + case 7: + b |= ((uint64_t)ni[6]) << 48; + + /* FALLTHRU */ + case 6: + b |= ((uint64_t)ni[5]) << 40; + + /* FALLTHRU */ + case 5: + b |= ((uint64_t)ni[4]) << 32; + + /* FALLTHRU */ + case 4: + b |= ((uint64_t)ni[3]) << 24; + + /* FALLTHRU */ + case 3: + b |= ((uint64_t)ni[2]) << 16; + + /* FALLTHRU */ + case 2: + b |= ((uint64_t)ni[1]) << 8; + + /* FALLTHRU */ + case 1: + b |= ((uint64_t)ni[0]); + break; + + case 0: + break; + } + + v3 ^= b; + + TRACE; + for (i = 0; i < cROUNDS; ++i) + { + SIPROUND; + } + + v0 ^= b; + + if (outlen == 16) + { + v2 ^= 0xee; + } + else + { + v2 ^= 0xff; + } + + TRACE; + for (i = 0; i < dROUNDS; ++i) + { + SIPROUND; + } + + b = v0 ^ v1 ^ v2 ^ v3; + U64TO8_LE(out, b); + + if (outlen == 8) + { + return 0; + } + + v1 ^= 0xdd; + + TRACE; + for (i = 0; i < dROUNDS; ++i) + { + SIPROUND; + } + + b = v0 ^ v1 ^ v2 ^ v3; + U64TO8_LE(out + 8, b); + + return 0; +} diff --git a/src/openvpn/siphash.h b/src/openvpn/siphash.h new file mode 100644 index 000000000..d26ee36ec --- /dev/null +++ b/src/openvpn/siphash.h @@ -0,0 +1,31 @@ +/* + * SipHash reference C implementation + * + * Copyright (c) 2012-2021 Jean-Philippe Aumasson + * + * Copyright (c) 2012-2014 Daniel J. Bernstein + * + * To the extent possible under law, the author(s) have dedicated all copyright + * and related and neighboring rights to this software to the public domain + * worldwide. This software is distributed without any warranty. + * + * You should have received a copy of the CC0 Public Domain Dedication along + * with + * this software. If not, see + * . + */ + +#ifndef SIPHASH_H +#define SIPHASH_H + +#include +#include + +int siphash(const void *in, size_t inlen, const void *k, uint8_t *out, + size_t outlen); + +/* siphash always uses 128-bit keys */ +#define SIPHASH_KEY_SIZE 16 +#define SIPHASH_HASH_SIZE 16 + +#endif From patchwork Mon Feb 27 12:50:22 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Arne Schwabe X-Patchwork-Id: 3092 Return-Path: Delivered-To: patchwork@openvpn.net Received: by 2002:a05:7300:2310:b0:9f:bfa4:120f with SMTP id r16csp1132471dye; Mon, 27 Feb 2023 04:51:04 -0800 (PST) X-Google-Smtp-Source: AK7set9k/JrneMq2MxwXolGEq0eWS9GB92gh7ELefxs+FGm+3L1FUtX05TxUKirbOHfbgMElhchk X-Received: by 2002:a17:903:2289:b0:19b:33c0:409e with SMTP id b9-20020a170903228900b0019b33c0409emr31398714plh.50.1677502264587; Mon, 27 Feb 2023 04:51:04 -0800 (PST) ARC-Seal: i=1; a=rsa-sha256; t=1677502264; cv=none; d=google.com; s=arc-20160816; b=wH98nXY+xuHydOJIX+5lwdz6EqWbl8T73WJIi7r9588FW/2tlHsorPkvAXVKeEm6YX 2B3GIAKVsqzx7iqbjTvX8hhyy18MEPBJKYRp9Fcx3yoL+1j2sxUuRgHifA4nqMYiA5/I /GhQ+v6P7mzaQoPKbQQSVnAzm8mz8oKNm6mPSrxRSsW2m/xsnJgphEUyLPd6LlwASOKx I46UkNdDu3D2aV5vHqfO8vHIarRafKcmmTuM+DX22Tmw6rQ1TAY2KzoFdQpA1UfjGqCe 1UCro+Sf/U+c62l0wHERm+Wt404IwkJZgsuV1lw8LCExOrXPsPg17ZcCmR1GPIWfxxeL /nng== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=errors-to:content-transfer-encoding:list-subscribe:list-help :list-post:list-archive:list-unsubscribe:list-id:precedence:subject :mime-version:references:in-reply-to:message-id:date:to:from :dkim-signature:dkim-signature; bh=xhdzrxVfET8VPOlsTVMCFnBV3M7zzr2VO4HtMYZDCcM=; b=JMGaX5JUvK5KLiJechuSArHp3SEFYOQ9IWP0jMxhHs09rfHelCG48RqR1CDDyEsRqu xeNh1thpFDxOpOCMevqQfsnggLwrDYDXJ6j+9BV+T8o8dpCGvZqT+X946wEorCZk+FNv yxLJ3/tqsCzN36FWZyp1bRN9Y0F/xlBtRc3CB+fxJ6rKPttQUit7EzrDJCqbGbk4yI5s 2DdAUnTj76QhkFNVWJxeqJ6DJnGXYbx8+WfjNLNBP/7za7RYobP86CSLTAKTvDYhXrET 0lr82xYQ2lSDIlaqQUC49OiOet5B2XliwNFxFzV0U/ALwQO387DoJGIpBJoKszrDOw50 EB8Q== ARC-Authentication-Results: i=1; mx.google.com; dkim=neutral (body hash did not verify) header.i=@sourceforge.net header.s=x header.b=mcmktLlG; dkim=neutral (body hash did not verify) header.i=@sf.net header.s=x header.b=HdoJX2AY; spf=pass (google.com: domain of openvpn-devel-bounces@lists.sourceforge.net designates 216.105.38.7 as permitted sender) smtp.mailfrom=openvpn-devel-bounces@lists.sourceforge.net Received: from lists.sourceforge.net (lists.sourceforge.net. [216.105.38.7]) by mx.google.com with ESMTPS id x62-20020a638641000000b00502fd2d3a95si7455909pgd.540.2023.02.27.04.51.04 (version=TLS1_2 cipher=ECDHE-ECDSA-AES128-GCM-SHA256 bits=128/128); Mon, 27 Feb 2023 04:51:04 -0800 (PST) Received-SPF: pass (google.com: domain of openvpn-devel-bounces@lists.sourceforge.net designates 216.105.38.7 as permitted sender) client-ip=216.105.38.7; Authentication-Results: mx.google.com; dkim=neutral (body hash did not verify) header.i=@sourceforge.net header.s=x header.b=mcmktLlG; dkim=neutral (body hash did not verify) header.i=@sf.net header.s=x header.b=HdoJX2AY; spf=pass (google.com: domain of openvpn-devel-bounces@lists.sourceforge.net designates 216.105.38.7 as permitted sender) smtp.mailfrom=openvpn-devel-bounces@lists.sourceforge.net Received: from [127.0.0.1] (helo=sfs-ml-2.v29.lw.sourceforge.com) by sfs-ml-2.v29.lw.sourceforge.com with esmtp (Exim 4.95) (envelope-from ) id 1pWcxk-0007wX-FQ; Mon, 27 Feb 2023 12:50:39 +0000 Received: from [172.30.20.202] (helo=mx.sourceforge.net) by sfs-ml-2.v29.lw.sourceforge.com with esmtps (TLS1.2) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.95) (envelope-from ) id 1pWcxj-0007wP-Nd for openvpn-devel@lists.sourceforge.net; Mon, 27 Feb 2023 12:50:39 +0000 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=sourceforge.net; s=x; h=Content-Transfer-Encoding:MIME-Version:References: In-Reply-To:Message-Id:Date:Subject:To:From:Sender:Reply-To:Cc:Content-Type: Content-ID:Content-Description:Resent-Date:Resent-From:Resent-Sender: Resent-To:Resent-Cc:Resent-Message-ID:List-Id:List-Help:List-Unsubscribe: List-Subscribe:List-Post:List-Owner:List-Archive; bh=kLPAt/mM9VGQgHQLQytZUuwHRU/65kU/RXvkyrCDSWw=; b=mcmktLlGSpaLM4HqprZU6cVyru h9leJm3TVQDgUVH8ePXEiO7sRjZVOmNEXEvWiyLaL6Yzp8PQEjqb9S1FIEWeW/GqgMSIGf4i+JPJI tnszak3Yv+1q/51P85qd9b0+aOYvNJSwX53x1wcax9Xd5A9XiWhAp2kIroG9+X4K/ebI=; DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=sf.net; s=x ; h=Content-Transfer-Encoding:MIME-Version:References:In-Reply-To:Message-Id: Date:Subject:To:From:Sender:Reply-To:Cc:Content-Type:Content-ID: Content-Description:Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc :Resent-Message-ID:List-Id:List-Help:List-Unsubscribe:List-Subscribe: List-Post:List-Owner:List-Archive; bh=kLPAt/mM9VGQgHQLQytZUuwHRU/65kU/RXvkyrCDSWw=; b=HdoJX2AYiS3nbf85Y6uyBZb4h7 I35QkHuHFn0+oEinH5fcwagLJyMbOalYgf6PcHRDYY4USgzudB9/vGtVsdsc8eDJsGZCdq94Vvn0K +ScU17hSX21RQKdOfJfn7k8e+7AzAQ2Ip1j3aMxfjZX3OS6hUm2DF54xqmtVAFM6L5k0=; Received: from mail.blinkt.de ([192.26.174.232]) by sfi-mx-1.v28.lw.sourceforge.com with esmtps (TLS1.2:ECDHE-RSA-AES256-GCM-SHA384:256) (Exim 4.95) id 1pWcxg-003KH2-IN for openvpn-devel@lists.sourceforge.net; Mon, 27 Feb 2023 12:50:39 +0000 Received: from kamera.blinkt.de ([2001:638:502:390:20c:29ff:fec8:535c]) by mail.blinkt.de with smtp (Exim 4.95 (FreeBSD)) (envelope-from ) id 1pWcxT-000IKA-T0 for openvpn-devel@lists.sourceforge.net; Mon, 27 Feb 2023 13:50:23 +0100 Received: (nullmailer pid 2561428 invoked by uid 10006); Mon, 27 Feb 2023 12:50:23 -0000 From: Arne Schwabe To: openvpn-devel@lists.sourceforge.net Date: Mon, 27 Feb 2023 13:50:22 +0100 Message-Id: <20230227125023.2561379-2-arne@rfc2549.org> X-Mailer: git-send-email 2.25.1 In-Reply-To: <20230227125023.2561379-1-arne@rfc2549.org> References: <20230227125023.2561379-1-arne@rfc2549.org> MIME-Version: 1.0 X-Spam-Score: 0.3 (/) X-Spam-Report: Spam detection software, running on the system "util-spamd-2.v13.lw.sourceforge.com", has NOT identified this incoming email as spam. The original message has been attached to this so you can view it or label similar future email. If you have any questions, see the administrator of that system for details. Content preview: When an OpenVPN server is used/tried to be usedc in a reflection attack the protection with the simple --connect-freq-initial also block legimitate client from other networks that are not attacked by [...] Content analysis details: (0.3 points, 6.0 required) pts rule name description ---- ---------------------- -------------------------------------------------- 0.0 SPF_HELO_NONE SPF: HELO does not publish an SPF Record 0.0 SPF_NONE SPF: sender does not publish an SPF Record 0.2 HEADER_FROM_DIFFERENT_DOMAINS From and EnvelopeFrom 2nd level mail domains are different X-Headers-End: 1pWcxg-003KH2-IN Subject: [Openvpn-devel] [PATCH 2/3] Implement initial packet reflection protection using bloom filter X-BeenThere: openvpn-devel@lists.sourceforge.net X-Mailman-Version: 2.1.21 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: openvpn-devel-bounces@lists.sourceforge.net X-getmail-retrieved-from-mailbox: Inbox X-GMAIL-THRID: =?utf-8?q?1758988614743896800?= X-GMAIL-MSGID: =?utf-8?q?1758988614743896800?= When an OpenVPN server is used/tried to be usedc in a reflection attack the protection with the simple --connect-freq-initial also block legimitate client from other networks that are not attacked by a reflection attack. To allow a server to still reply to these clients, we need to make the counts rather more detailed and count per subnet or IP address. On the other hand when we keep all this state, we eliminate the advantage of having a stateless cookie based initial packet handshake. As compromoise we use a bloom filter to store the information. This data structure is probabilistic and can have false positive and more packets being dropped but since it is a constant size and the size of this map is small enough for non-embedded systems (tests were done with a 2MB bloom filter), it is a good compromise. The code is split into the bloom filter implementation and the actual logic implementing tracking the subnets, so the bloom filter should be relatively easily be exchangable by another data structure. As hash funtion SIPHASH has been chosen since it was designed for this kind of application. Change-Id: I0a9274cab7fefce3b13c05052fb9a072e0bfa6b9 Signed-off-by: Arne Schwabe --- .github/workflows/build.yaml | 21 +- doc/man-sections/server-options.rst | 71 +++++ src/openvpn/Makefile.am | 2 + src/openvpn/bloom.c | 253 +++++++++++++++++ src/openvpn/bloom.h | 97 +++++++ src/openvpn/mudp.c | 5 +- src/openvpn/multi.c | 3 +- src/openvpn/openvpn.vcxproj | 3 + src/openvpn/options.c | 93 ++++++ src/openvpn/options.h | 3 + src/openvpn/reflect_filter.c | 361 +++++++++++++++++++++++- src/openvpn/reflect_filter.h | 54 +++- tests/unit_tests/openvpn/Makefile.am | 18 +- tests/unit_tests/openvpn/test_reflect.c | 323 +++++++++++++++++++++ 14 files changed, 1284 insertions(+), 23 deletions(-) create mode 100644 src/openvpn/bloom.c create mode 100644 src/openvpn/bloom.h create mode 100644 tests/unit_tests/openvpn/test_reflect.c diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index a3ca7a2ea..13009d3dc 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -234,36 +234,39 @@ jobs: - name: List unittests directory run: "dir unittests" - - name: Run argvunit test + - name: Run argv unit test run: ./unittests/argv_testdriver.exe - - name: Run auth_tokenunit test + - name: Run auth_token unit test run: ./unittests/auth_token_testdriver.exe - - name: Run bufferunit test + - name: Run buffer unit test run: ./unittests/buffer_testdriver.exe - name: Run cryptoapi unit test run: ./unittests/cryptoapi_testdriver.exe - - name: Run cryptounit test + - name: Run crypto unit test run: ./unittests/crypto_testdriver.exe - - name: Run miscunit test + - name: Run misc unit test run: ./unittests/misc_testdriver.exe - - name: Run ncpunit test + - name: Run ncp unit test run: ./unittests/ncp_testdriver.exe - - name: Run packet idunit test + - name: Run packet id unit test run: ./unittests/packet_id_testdriver.exe - - name: Run pktunit test + - name: Run pkt unit test run: ./unittests/pkt_testdriver.exe - - name: Run providerunit test + - name: Run provider unit test run: ./unittests/provider_testdriver.exe + - name: Run reflect unit test + run: ./unittests/reflect_testdriver.exe + ubuntu: strategy: fail-fast: false diff --git a/doc/man-sections/server-options.rst b/doc/man-sections/server-options.rst index 6b9ad21b8..320ec0068 100644 --- a/doc/man-sections/server-options.rst +++ b/doc/man-sections/server-options.rst @@ -208,6 +208,77 @@ fast hardware. SSL/TLS authentication must be used in this mode. will not be counted against the limit. The default is to allow 100 initial connection per 10s. + +--connect-freq-initial-bloom-size args + + Valid syntax: + :: + + connect-freq-initial-bloom-size size [numhashes] + + Configures the size of the bloom filter used for the initial connection + packet responses. See `--connect-freq-initial-bloom-limit` for a full + description of the feature. + + Bloom filters are probabilistic by their nature and the larger the size + of the bloom filter is, the more accurate they are. This option configures + the number of buckets in the bloom filter. Each bucket takes up 4 bits. + E.g. the default of 8336608 (8 * 2^20) when the option is not set + will require 2MB of memory. The numhashes will control the number of hash + functions that are used for the bloom filter. The default is 7. + +--connect-freq-initial-bloom-limit arg + + Valid syntax: + :: + + connect-freq-initial-bloom-limit inet netmask limit + connect-freq-initial-bloom-limit inet6 netmask limit + + This option implements a rate limiting function for initial packet like + ``--connect-freq-initial`` but takes the source of the initial packet into + account. This function shares the the reset after a time period and + enabling this function will not disable the overall limit of + ``--conect-freq-initial``. + + To avoid resource exhaustion on the OpenVPN server side from reflection + attacks and also trying to avoid blocking legitimate clients when an + attacker tries to use an OpenVPN server, the limits are not applied globally + but rather a per netmask granularity. Each line of this command configures + a separate granularity. As example, the following configuration, + + :: + + connect-freq-initial 1000 60 + connect-freq-initial-bloom-limit inet 24 50 + connect-freq-initial-bloom-limit inet 8 100 + connect-freq-initial-bloom-limit inet6 56 50 + connect-freq-initial-bloom-limit inet6 32 100 + + will limit the number of (unanswered) replies per /24 network to 50 + packets. After 50 packets from the same /24, the server will + drop any further packets. Similarly, if a /16 network receives more than + 100 connection requests, the server will drop further packets. + If the count of all packets exceeds 1000, further packets will be + completely dropped regardless of their origin. + + The implementation uses a counting bloom filter to store information of how + many packets the server has seen per subnet. A bloom filter is a + probabilistic data structure can yield false positives. In this case, the + server will drop packets even though the limit + for that particular subnet is not yet reached. Since we are using a + counting bloom filter, a reply from a client that triggers a removal of an + entry can also lead to a false negative. However, this is limited in the + sense a reply can at most trigger one false negative. + + Every limit configured with this option will put more more entries into + the bloomfilter since per limit (per family), one entry is put into the + bloom filter. So a large number of entries requires a larger bloom filter + to avoid too many false positives. + + IPv6 mapped IPv4 addresses (::ffff:0:0/96) are treated as IPv4 addresses + for these limits. + --duplicate-cn Allow multiple clients with the same common name to concurrently connect. In the absence of this option, OpenVPN will disconnect a client diff --git a/src/openvpn/Makefile.am b/src/openvpn/Makefile.am index a8e44528c..c1de7b723 100644 --- a/src/openvpn/Makefile.am +++ b/src/openvpn/Makefile.am @@ -43,6 +43,7 @@ openvpn_SOURCES = \ auth_token.c auth_token.h \ base64.c base64.h \ basic.h \ + bloom.c bloom.h \ buffer.c buffer.h \ circ_list.h \ clinat.c clinat.h \ @@ -122,6 +123,7 @@ openvpn_SOURCES = \ session_id.c session_id.h \ shaper.c shaper.h \ sig.c sig.h \ + siphash.c siphash.h \ socket.c socket.h \ socks.c socks.h \ ssl.c ssl.h ssl_backend.h \ diff --git a/src/openvpn/bloom.c b/src/openvpn/bloom.c new file mode 100644 index 000000000..9382bf264 --- /dev/null +++ b/src/openvpn/bloom.c @@ -0,0 +1,253 @@ +/* + * OpenVPN -- An application to securely tunnel IP networks + * over a single TCP/UDP port, with support for SSL/TLS-based + * session authentication and key exchange, + * packet encryption, packet authentication, and + * packet compression. + * + * Copyright (C) 2022 OpenVPN Inc + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/* This file implements a specialised counting bloom filter implementation + * used in OpenVPN to mitigate it being used in reflection attacks. The bloom + * implementation should be general enough to be used in other contexts + * however */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#elif defined(_MSC_VER) +#include "config-msvc.h" +#endif + +#include "syshead.h" + + +#include +#include +#include +#include +#include +#include "siphash.h" +#include "crypto.h" + +#include "bloom.h" + + +static size_t +calc_ceil_log2(size_t num) +{ + /* Figure out how many bits we have in our hash by doing + * ceil(log2(bf->size) */ + + size_t numbits = 0; + while (num >>= 1) + { + numbits++; + } + return numbits; +} + +/** + * Calculate number of bytes needed for each hash function in the bloom filter + * + */ +static inline size_t +calculate_num_bytes_hashfun(size_t size) +{ + size_t hash_bits = calc_ceil_log2(size); + /* round up to the nearest byte size */ + size_t hash_bytes = (hash_bits + 7) /8; + return hash_bytes; +} + +/** + * A bloom filter uses a number of hashes to perform its functionality. + * + * Instead of using the same number of siphash function we split the bytes of + * the siphash to the different hashes. + * + * We could also optimise this further by splitting at the bit level but that + * crates a lot of extra code for bit shifting, etc. so we waste some bits + * + * @param bf + * @param num_hashes + * @return + */ +size_t +calculate_num_sip_hash_hashes(struct bloom_filter *bf) +{ + size_t hash_bytes = calculate_num_bytes_hashfun(bf->size); + size_t total_bytes = bf->num_hashes * hash_bytes; + + /* Round up to next SIPHASH_HASH_SIZE */ + size_t num_siphash = (total_bytes + SIPHASH_HASH_SIZE -1) / SIPHASH_HASH_SIZE; + + return num_siphash; +} + +/** + * Calculates the number of bytes we need for storing a bloom filter of size + * size. We add + 1 to avoid rounding problems and too small allocation */ +static inline +size_t +bloom_get_filter_byte_count(size_t size) +{ + static_assert(sizeof(bloom_counter_t) * 8 % BLOOM_FILTER_BITS_COUNT == 0, + "bloom_counter_t must be a multiple of BLOOM_FILTER_BIT_COUNT"); + + return size * sizeof(bloom_counter_t)/BLOOM_FILTER_BITS_COUNT + 1; +} + + +static inline +size_t +bloom_get_filter_bit_offset(size_t bucket) +{ + return (bucket * BLOOM_FILTER_BITS_COUNT) % sizeof(bloom_counter_t); +} + +static inline +size_t +bloom_get_filter_array_index(size_t bucket) +{ + return (bucket * BLOOM_FILTER_BITS_COUNT) / sizeof(bloom_counter_t); +} + +static inline bloom_counter_t +bloom_get_filter_get_counter(struct bloom_filter *bf, size_t bucket) +{ + static_assert((BLOOM_FILTER_BITS_MASK + 1) == (1 << BLOOM_FILTER_BITS_COUNT), + "BLOOM_FILTER_BITMASK and BLOOM_FILTER_BIT_COUNT are inconsistent"); + + bloom_counter_t counter = bf->buckets[bloom_get_filter_array_index(bucket)]; + size_t bitoffset = bloom_get_filter_bit_offset(bucket); + + return (counter >> bitoffset) & BLOOM_FILTER_BITS_MASK; +} + +static inline void +bloom_set_filter_counter(struct bloom_filter *bf, size_t bucket, bloom_counter_t value) +{ + bloom_counter_t data = bf->buckets[bloom_get_filter_array_index(bucket)]; + size_t bitoffset = bloom_get_filter_bit_offset(bucket); + + data = data & ~(BLOOM_FILTER_BITS_MASK << bitoffset); + + data = data | ((value & BLOOM_FILTER_BITS_MASK) << bitoffset); + + bf->buckets[bloom_get_filter_array_index(bucket)] = data; +} + +/** + * Creates a new bloom filter structure + * @param size the number of buckets. + * @return the newly created bloom filter structure + */ +struct bloom_filter * +bloom_create(size_t size, size_t num_hashes, struct gc_arena *gc) +{ + size_t bloomfilter_bytes = bloom_get_filter_byte_count(size); + struct bloom_filter *bf = gc_malloc(sizeof(struct bloom_filter) + bloomfilter_bytes, + false, gc); + bf->size = size; + bf->num_hashes = num_hashes; + + bf->hash_bytes = calculate_num_bytes_hashfun(size); + bf->num_siphash = calculate_num_sip_hash_hashes(bf); + + ALLOC_ARRAY_GC(bf->siphash_keys, struct siphash_key, bf->num_siphash, gc); + + bloom_clear(bf); + return bf; +} + +/** + * Clear the bloom filter, making it empty again as if it were freshly created + * @param bf the bloom structure to clear + */ +void +bloom_clear(struct bloom_filter *bf) +{ + memset(bf->buckets, 0, bloom_get_filter_byte_count(bf->size)); + + /* We randomise the bloom filter keys on every clear of the bloom filter + * to avoid scenarios where an attacker might learn specific pattern + * that could exploit false positives in the bloom filter */ + for (size_t i = 0; i < bf->num_siphash; i++) + { + prng_bytes(bf->siphash_keys[i].key, SIPHASH_KEY_SIZE); + } +} + + +static bloom_counter_t +bloom_add_test(struct bloom_filter *bf, const uint8_t *item, size_t len, bloom_counter_t inc) +{ + uint8_t result[SIPHASH_HASH_SIZE]; + size_t j = 0; + size_t idx = 0; + bloom_counter_t ret = bloom_counter_max; + + for (size_t i = 0; i < bf->num_hashes; i++) + { + size_t bucket = 0; + for (int k = 0; k < bf->hash_bytes; k++) + { + if (idx == 0) + { + /* We have no longer unused bytes in result, generate the next hash */ + siphash(item, len, bf->siphash_keys[j++].key, result, SIPHASH_HASH_SIZE); + } + + bucket = bucket << 8; + bucket |= result[idx]; + + idx = (idx + 1) % SIPHASH_HASH_SIZE; + } + + bucket = bucket % bf->size; + bloom_counter_t value = bloom_get_filter_get_counter(bf, bucket); + + ret = min_bloom_counter(ret, value); + + if (inc) + { + value = min_bloom_counter(bloom_counter_max, value + 1); + bloom_set_filter_counter(bf, bucket, value); + } + } + return ret; +} + + +bloom_counter_t +bloom_add(struct bloom_filter *bf, const uint8_t *item, size_t len) +{ + return bloom_add_test(bf, item, len, 1); +} + +bloom_counter_t +bloom_remove(struct bloom_filter *bf, const uint8_t *item, size_t len) +{ + return bloom_add_test(bf, item, len, -1); +} + + +bloom_counter_t +bloom_test(struct bloom_filter *bf, const uint8_t *item, size_t len) +{ + return bloom_add_test(bf, item, len, 0); +} diff --git a/src/openvpn/bloom.h b/src/openvpn/bloom.h new file mode 100644 index 000000000..e18026181 --- /dev/null +++ b/src/openvpn/bloom.h @@ -0,0 +1,97 @@ +/* + * OpenVPN -- An application to securely tunnel IP networks + * over a single TCP/UDP port, with support for SSL/TLS-based + * session authentication and key exchange, + * packet encryption, packet authentication, and + * packet compression. + * + * Copyright (C) 2022 OpenVPN Inc + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef BLOOM_H +#define BLOOM_H + +#include +#include "siphash.h" +#include "buffer.h" + +/* This is the type we use for the buckets. This is split into small buckets + * with BLOOM_FILTER_BIT_COUNT size */ +typedef uint32_t bloom_counter_t; +#define BLOOM_FILTER_BITS_COUNT 2 +#define BLOOM_FILTER_BITS_MASK 0x03 +#define bloom_counter_max 0x03 + + +static inline bloom_counter_t +min_bloom_counter(bloom_counter_t x, bloom_counter_t y) +{ + if (x < y) + { + return x; + } + else + { + return y; + } +} + +struct siphash_key { + uint8_t key[SIPHASH_KEY_SIZE]; +}; + +struct bloom_filter { + /** Size of the bloom filter in entries, ie total bits/bits per counter */ + size_t size; + + /** Number of bytes used by each hash function: + * + * log2(size * 8) bits rounded up to the next byte + * + * This is a cached value since log2 is surprisingly slow + * (5% of total time of if we do not cache it) */ + size_t hash_bytes; + + /** number of hashes we use to determine the bit positions */ + size_t num_hashes; + /** number of siphash function needed to calculate. This can be + * calculated from the other members of the struct but we store it + * in the struct for fast access */ + size_t num_siphash; + + /** keys for the siphash functions */ + struct siphash_key *siphash_keys; + + /** the actual buckets that hold the data */ + bloom_counter_t buckets[]; +}; + + +struct bloom_filter * +bloom_create(size_t size, size_t num_hashes, struct gc_arena *gc); + +bloom_counter_t +bloom_test(struct bloom_filter *bf, const uint8_t *item, size_t len); + +bloom_counter_t +bloom_add(struct bloom_filter *bf, const uint8_t *item, size_t len); + +bloom_counter_t +bloom_remove(struct bloom_filter *bf, const uint8_t *item, size_t len); + +void +bloom_clear(struct bloom_filter *bf); +#endif /* ifndef BLOOM_H */ diff --git a/src/openvpn/mudp.c b/src/openvpn/mudp.c index 8698aefc8..b4053a84f 100644 --- a/src/openvpn/mudp.c +++ b/src/openvpn/mudp.c @@ -86,7 +86,7 @@ do_pre_decrypt_check(struct multi_context *m, { /* Check if we are still below our limit for sending out * responses */ - if (!reflect_filter_rate_limit_check(m->initial_rate_limiter)) + if (!reflect_filter_check(m->initial_rate_limiter, from)) { return false; } @@ -256,7 +256,8 @@ multi_get_create_instance_udp(struct multi_context *m, bool *floated) { /* a successful three-way handshake only counts against * connect-freq but not against connect-freq-initial */ - reflect_filter_rate_limit_decrease(m->initial_rate_limiter); + reflect_filter_rate_limit_decrease(m->initial_rate_limiter, + &m->top.c2.from.dest); mi = multi_create_instance(m, &real); if (mi) diff --git a/src/openvpn/multi.c b/src/openvpn/multi.c index f25590168..ef8828a8b 100644 --- a/src/openvpn/multi.c +++ b/src/openvpn/multi.c @@ -370,7 +370,8 @@ multi_init(struct multi_context *m, struct context *t, bool tcp_mode) m->new_connection_limiter = frequency_limit_init(t->options.cf_max, t->options.cf_per); m->initial_rate_limiter = initial_rate_limit_init(t->options.cf_initial_max, - t->options.cf_initial_per); + t->options.cf_initial_per, + &t->options.initial_cf_bloom_config); /* * Allocate broadcast/multicast buffer list diff --git a/src/openvpn/openvpn.vcxproj b/src/openvpn/openvpn.vcxproj index 97baf678c..2d12f8309 100644 --- a/src/openvpn/openvpn.vcxproj +++ b/src/openvpn/openvpn.vcxproj @@ -266,6 +266,7 @@ + @@ -330,6 +331,7 @@ + @@ -427,6 +429,7 @@ + diff --git a/src/openvpn/options.c b/src/openvpn/options.c index 9105449c7..041e3961f 100644 --- a/src/openvpn/options.c +++ b/src/openvpn/options.c @@ -3453,6 +3453,22 @@ options_postprocess_mutate_invariant(struct options *options) #endif } +static void +check_filter_tier_under_global_limit(struct filter_tier *ft, size_t limit, + const char *family) +{ + while (ft) + { + if (ft->limit < limit) + { + msg(M_WARN, "Note: --connect-freq-initial-bloom-limit %s %d %d " + "has a larger limit than connect-freq-limit-initial " + "limit (%zu).", family, ft->netmask, ft->limit, limit); + } + ft = ft->next; + } +} + static void options_postprocess_verify(const struct options *o) { @@ -3477,6 +3493,18 @@ options_postprocess_verify(const struct options *o) "channel offload: packets are always sent to the VPN " "interface and then routed based on the system routing table"); } + + if ((bool)(o->initial_cf_bloom_config.inet_tiers) + +(bool)(o->initial_cf_bloom_config.inet6_tiers) == 1) + { + msg(M_FATAL, "connect-freq-initial-bloom-limit must be provided for " + "both inet and inet6"); + } + check_filter_tier_under_global_limit(o->initial_cf_bloom_config.inet_tiers, + o->cf_initial_max, "inet"); + check_filter_tier_under_global_limit(o->initial_cf_bloom_config.inet6_tiers, + o->cf_initial_max, "inet6"); + } /** @@ -7473,6 +7501,71 @@ add_option(struct options *options, options->cf_initial_max = cf_max; options->cf_initial_per = cf_per; } + else if (streq(p[0], "connect-freq-initial-bloom-size") && p[1] && !p[3]) + { + VERIFY_PERMISSION(OPT_P_GENERAL); + + int cf_bloom_size = atoi(p[1]); + + if (cf_bloom_size <= 0) + { + msg(msglevel, "--connect-freq-initial-bloom-size size must be > 0"); + goto err; + } + options->initial_cf_bloom_config.size = cf_bloom_size; + if (p[2]) + { + int num_hash = atoi(p[1]); + if (num_hash <= 0) + { + msg(msglevel, "--connect-freq-initial-bloom-size number of hash " + "functions must be > 0"); + goto err; + } + options->initial_cf_bloom_config.num_hashes = num_hash; + } + } + else if (streq(p[0], "connect-freq-initial-bloom-limit") && p[1] && p[2] + && p[3] && !p[4]) + { + int netmask = atoi(p[2]); + int limit = atoi(p[3]); + + if (limit <= 0) + { + msg(msglevel, "Limit parameter to %s must be > 0", p[0]); + goto err; + } + + if (streq(p[1], "inet")) + { + if (netmask < 0 || netmask > 32) + { + msg(msglevel, "Netmask parameter (%s) for IPv4 for %s must be " + "between 0 and 32", p[2], p[0]); + goto err; + } + reflect_add_filter_tier(&options->initial_cf_bloom_config, + &options->gc, false, netmask, limit); + } + else if (streq(p[1], "inet6")) + { + if (netmask < 0 || netmask > 128) + { + msg(msglevel, "Netmask parameter (%s) for IPv6 for %s must be " + "between 0 and 128", p[2], p[0]); + goto err; + } + reflect_add_filter_tier(&options->initial_cf_bloom_config, + &options->gc, true, netmask, limit); + } + else + { + msg(msglevel, "Unknown parameter %s for %s. Must be inet or inet6", + p[1], p[0]); + goto err; + } + } else if (streq(p[0], "max-clients") && p[1] && !p[2]) { int max_clients; diff --git a/src/openvpn/options.h b/src/openvpn/options.h index 7df717f73..b078cfded 100644 --- a/src/openvpn/options.h +++ b/src/openvpn/options.h @@ -43,6 +43,7 @@ #include "clinat.h" #include "crypto_backend.h" #include "dns.h" +#include "reflect_filter.h" /* @@ -520,6 +521,8 @@ struct options int cf_initial_max; int cf_initial_per; + struct bloom_filter_conf initial_cf_bloom_config; + int max_clients; int max_routes_per_client; int stale_routes_check_interval; diff --git a/src/openvpn/reflect_filter.c b/src/openvpn/reflect_filter.c index cfe69a634..35ab2880c 100644 --- a/src/openvpn/reflect_filter.c +++ b/src/openvpn/reflect_filter.c @@ -40,8 +40,7 @@ #include "crypto.h" #include "reflect_filter.h" - -bool +static bool reflect_filter_rate_limit_check(struct initial_packet_rate_limit *irl) { if (now > irl->last_period_reset + irl->period_length) @@ -74,34 +73,384 @@ reflect_filter_rate_limit_check(struct initial_packet_rate_limit *irl) return !over_limit; } +static void +reset_filter_tier(struct initial_packet_rate_limit *irl, + struct filter_tier *tier, const char *prefix) +{ + for (; tier != NULL; tier = tier->next) + { + if (tier->dropped > 0) + { + msg(D_TLS_DEBUG_LOW, "Dropped %zu initial handshake packets due to " + "--connect-freq-initial-bloom-limit %s %d %d", + tier->dropped, prefix, tier->netmask, tier->limit); + tier->dropped = 0; + } + } +} + +static void +bloom_filter_check_reset(struct initial_packet_rate_limit *irl) +{ + if (now > irl->last_period_reset + irl->period_length) + { + reset_filter_tier(irl, irl->bloom_conf.inet_tiers, "inet"); + reset_filter_tier(irl, irl->bloom_conf.inet6_tiers, "inet6"); + + bloom_clear(irl->bf); + } +} + + +/** + * structure used as lookup key for the bloom structure. We used the + * netmask as part of the structure to avoid the look for the first + * IP of a subnet and the subnet to be same key. + */ +struct bloom_filter_key { + union { + struct in_addr in; + struct in6_addr in6; + }; + int netmask; + /* we keep the count in the key instead of in the bloom filter table as + * can then keep the counter in the bloom filter itself small (2 bits) + * and bloom filter usage is the same for 20000 request from the same IP + * (20k entries with different count but same IP) and from 20000 random ips + * (20k entries with count 1 but different IP) */ + int count; +}; + +static inline struct bloom_filter_key +filter_mask_inet6(struct openvpn_sockaddr *addr, int netmask) +{ + struct bloom_filter_key ret = { 0 }; + ret.in6 = addr->addr.in6.sin6_addr; + + int bits_to_clear = 128 - netmask; + int bytes_to_clear = bits_to_clear /8; + bits_to_clear = bits_to_clear % 8; + + memset(&ret.in6.s6_addr[15 - bytes_to_clear], 0x00, bytes_to_clear); + + ret.in6.s6_addr[15 - bytes_to_clear - 1] &= (0xff << bits_to_clear); + + ret.netmask = netmask; + + return ret; +} + +static inline struct bloom_filter_key +filter_mask_inet(struct openvpn_sockaddr *addr, int netmask) +{ + struct bloom_filter_key ret = { 0 }; + ret.in = addr->addr.in4.sin_addr; + ret.in.s_addr &= htonl(0xffffffff << (32 - netmask)); + ret.netmask = netmask; + return ret; +} + +/* We use one function and an action argument to avoid repeating + * the code to iterate to the tiers and the creating the lookup + * keys */ +enum bloom_filter_action +{ + REFLECT_CHECK, + REFLECT_INCREASE, + REFLECT_DECREASE, +}; + +static int +reflect_lookup_bf_key(struct bloom_filter *bf, struct bloom_filter_key *key, int limit) +{ + /* we do a lookup for 1,2,3, and 4 and the limit first + * and after that do regular binary search. This is meant to optimise + * the common case where just a small number of requests are coming from + * each IP */ + key->count = limit; + if (bloom_test(bf, (const uint8_t *) key, sizeof(struct bloom_filter_key)) > 0) + { + return limit; + } + + key->count = 4; + if (bloom_test(bf, (const uint8_t *) key, sizeof(struct bloom_filter_key)) == 0) + { + /* The value for 4 has not been found, so the real value might be 1, 2, or 3. */ + for (int i = 3; i > 0; i--) + { + key->count = i; + if (bloom_test(bf, (const uint8_t *) key, sizeof(struct bloom_filter_key)) > 0) + { + return i; + } + } + + /* 4 was no in the map and 1-3 are also not there, so assume the key is not in the map */ + return 0; + } + + int low = 3; + int high = limit; + + while (low < high) + { + + key->count = (high + low + 1)/2; + bloom_counter_t count = bloom_test(bf, (const uint8_t *) key, sizeof(struct bloom_filter_key)); + if (count > 0) + { + low = key->count; + } + else + { + high = key->count - 1; + } + } + + if (low == 4) + { + /* we reached the lower end of our binary search have not found the + * key and we know that 4 is in the map */ + return 4; + } + else + { + return low; + } +} + +/** + * Convert a mapped IPv6 mapped IPv4 address (::ffff:0:0/96) to an + * equivalent IPv4 adress. + * + * @note: This function only converts the IP itself and ignores other + * parts of the \c from structure like port or protocol. + */ +static struct openvpn_sockaddr +convert_mapped_inet_sockaddr(struct openvpn_sockaddr *from) +{ + struct openvpn_sockaddr from_mapped = { 0 }; + /* we ignore the fields like port that the key in the bloom filter + * ignores too. This makes this function non-generic */ + + from_mapped.addr.in4.sin_family = AF_INET; + + memcpy(&from_mapped.addr.in4.sin_addr.s_addr, + &from->addr.in6.sin6_addr.s6_addr[12], + sizeof(from_mapped.addr.in4.sin_addr.s_addr)); + + return from_mapped; +} + +static bool +bloom_filter_action(struct initial_packet_rate_limit *irl, + struct openvpn_sockaddr *from, + enum bloom_filter_action action) +{ + bool found = false; + struct filter_tier *tier = NULL; + + struct openvpn_sockaddr from_mapped; + + if (from->addr.sa.sa_family == AF_INET6 + && IN6_IS_ADDR_V4MAPPED(&from->addr.in6.sin6_addr)) + { + from_mapped = convert_mapped_inet_sockaddr(from); + from = &from_mapped; + } + + if (from->addr.sa.sa_family == AF_INET) + { + tier = irl->bloom_conf.inet_tiers; + + } + else if (from->addr.sa.sa_family == AF_INET6) + { + tier = irl->bloom_conf.inet6_tiers; + } + + while (tier) + { + struct filter_tier *next_tier = tier->next; + struct bloom_filter_key key; + + if (from->addr.sa.sa_family == AF_INET6) + { + key = filter_mask_inet6(from, tier->netmask); + } + else + { + key = filter_mask_inet(from, tier->netmask); + } + + /* fetch the current count of the key in the bloom filter */ + int result = reflect_lookup_bf_key(irl->bf, &key, tier->limit); + struct gc_arena gc = gc_new(); + gc_free(&gc); + + switch (action) + { + + case REFLECT_CHECK: + if (result >= tier->limit) + { + found = true; + tier->dropped++; + if (tier->dropped == 1) + { + msg(M_WARN, "Note: --connect-freq-initial-bloom-limit " + "limit for netmask /%d exceeded. Expect additional " + "initial packet drops for the next %d seconds", + tier->netmask, + (int)(irl->last_period_reset + irl->period_length - now)); + } + } + break; + + case REFLECT_INCREASE: + ASSERT(result < tier->limit); + key.count = result + 1; + bloom_add(irl->bf, (const uint8_t *) &key, sizeof(key)); + break; + + case REFLECT_DECREASE: + key.count = result - 1; + bloom_remove(irl->bf, (const uint8_t *) &key, sizeof(key)); + break; + } + + tier = next_tier; + + } + if (!found && action == REFLECT_CHECK) + { + /* We only want to increase the counters if the IP is not already + * in the set. */ + bloom_filter_action(irl, from, REFLECT_INCREASE); + } + return found; +} + +static bool +bloom_filter_check(struct initial_packet_rate_limit *irl, + struct openvpn_sockaddr *from) +{ + if (now > irl->last_period_reset + irl->period_length) + { + + bloom_filter_check_reset(irl); + bloom_clear(irl->bf); + } + + return bloom_filter_action(irl, from, REFLECT_CHECK); +} + + +bool +reflect_filter_check(struct initial_packet_rate_limit *irl, + struct openvpn_sockaddr *from) +{ + /* We are doing the bloom filter check first so packets that are already + * rejected by the bloom filter do not count against the limit of the + * simple rate limiter */ + if (irl->bf && bloom_filter_check(irl, from)) + { + return false; + } + + if (!reflect_filter_rate_limit_check(irl)) + { + return false; + } + + return true; +} + + void -reflect_filter_rate_limit_decrease(struct initial_packet_rate_limit *irl) +reflect_filter_rate_limit_decrease(struct initial_packet_rate_limit *irl, struct openvpn_sockaddr *from) { + if (irl->bf && bloom_filter_action(irl, from, REFLECT_CHECK)) + { + /* Only remove if it is actually present. This might be a packet + * coming from an early period or be relayed */ + bloom_filter_action(irl, from, REFLECT_DECREASE); + } + if (irl->curr_period_counter > 0) { irl->curr_period_counter--; } } +void +reflect_add_filter_tier(struct bloom_filter_conf *bfconf, struct gc_arena *gc, + bool ipv6, int netmask, int limit) +{ + struct filter_tier *ftnew = gc_malloc(sizeof(struct filter_tier), true, gc); + + ftnew->netmask = netmask; + ftnew->limit = limit; + + if (ipv6) + { + ftnew->next = bfconf->inet6_tiers; + bfconf->inet6_tiers = ftnew; + } + else + { + ftnew->next = bfconf->inet_tiers; + bfconf->inet_tiers = ftnew; + } +} + +void +init_bloom_filter(struct initial_packet_rate_limit *irl) +{ + if (!irl->bloom_conf.size) + { + /* the default allocates 2MB for bloom filter entries */ + irl->bloom_conf.size = 1024ul * 1024 * 8; + } + if (!irl->bloom_conf.num_hashes) + { + irl->bloom_conf.num_hashes = 7; + } + + irl->bf = bloom_create(irl->bloom_conf.size, irl->bloom_conf.num_hashes, + &irl->gc); + bloom_clear(irl->bf); +} struct initial_packet_rate_limit * -initial_rate_limit_init(int max_per_period, int period_length) +initial_rate_limit_init(int max_per_period, int period_length, + struct bloom_filter_conf *bconf) { - struct initial_packet_rate_limit *irl; + struct initial_packet_rate_limit *irl = NULL; + ALLOC_OBJ_CLEAR(irl, struct initial_packet_rate_limit); - ALLOC_OBJ(irl, struct initial_packet_rate_limit); + irl->gc = gc_new(); irl->max_per_period = max_per_period; irl->period_length = period_length; irl->curr_period_counter = 0; irl->last_period_reset = 0; + if (bconf) + { + irl->bloom_conf = *bconf; + init_bloom_filter(irl); + } + return irl; } void initial_rate_limit_free(struct initial_packet_rate_limit *irl) { + gc_free(&irl->gc); free(irl); + irl = NULL; } diff --git a/src/openvpn/reflect_filter.h b/src/openvpn/reflect_filter.h index b708d4fd1..e94de587e 100644 --- a/src/openvpn/reflect_filter.h +++ b/src/openvpn/reflect_filter.h @@ -24,6 +24,28 @@ #define REFLECT_FILTER_H #include +#include "socket.h" +#include "bloom.h" + +struct filter_tier { + struct filter_tier *next; + + int limit; + int netmask; + + /** The number of packets we dropped since we went over this limit */ + size_t dropped; +}; + +struct bloom_filter_conf { + struct filter_tier *inet_tiers; + struct filter_tier *inet6_tiers; + + /* Configuration of the bloom filter */ + size_t num_hashes; + size_t size; +}; + /** struct that handles all the rate limiting logic for initial * responses */ @@ -35,7 +57,7 @@ struct initial_packet_rate_limit { int period_length; /** Number of packets in the current period. We use int64_t here - * to avoid any potiential issues with overflow */ + * to avoid any potential issues with overflow */ int64_t curr_period_counter; /* Last time we reset our timer */ @@ -44,15 +66,28 @@ struct initial_packet_rate_limit { /* we want to warn once per period that packets are being started to * be dropped */ bool warning_displayed; + + struct bloom_filter_conf bloom_conf; + struct bloom_filter *bf; + + /* gc_arena used for the various allocations by this struct */ + struct gc_arena gc; }; +/** + * Adds a bloom filter tier to the bloom filter config. + */ +void +reflect_add_filter_tier(struct bloom_filter_conf *bfconf, struct gc_arena *gc, + bool ipv6, int netmask, int limit); /** * checks if the connection is still allowed to connect under the rate * limit. This also increases the internal counter at the same time */ bool -reflect_filter_rate_limit_check(struct initial_packet_rate_limit *irl); +reflect_filter_check(struct initial_packet_rate_limit *irl, + struct openvpn_sockaddr *from); /** * decreases the counter of initial packets seen, so connections that @@ -60,16 +95,27 @@ reflect_filter_rate_limit_check(struct initial_packet_rate_limit *irl); * the counter of initial connection attempts */ void -reflect_filter_rate_limit_decrease(struct initial_packet_rate_limit *irl); +reflect_filter_rate_limit_decrease(struct initial_packet_rate_limit *irl, + struct openvpn_sockaddr *from); /** * allocate and initialize the initial-packet rate limiter structure + * + * Note: this function does not copy bconf's contents. */ struct initial_packet_rate_limit * -initial_rate_limit_init(int max_per_period, int period_length); +initial_rate_limit_init(int max_per_period, int period_length, + struct bloom_filter_conf *bconf); /** * free the initial-packet rate limiter structure */ void initial_rate_limit_free(struct initial_packet_rate_limit *irl); + +/** + * Initialises the bloom filter with the configuration values of + * irl->bloom_conf + */ +void +init_bloom_filter(struct initial_packet_rate_limit *irl); #endif /* ifndef REFLECT_FILTER_H */ diff --git a/tests/unit_tests/openvpn/Makefile.am b/tests/unit_tests/openvpn/Makefile.am index ee0a3d8aa..b8ba1d4db 100644 --- a/tests/unit_tests/openvpn/Makefile.am +++ b/tests/unit_tests/openvpn/Makefile.am @@ -7,7 +7,7 @@ test_binaries += argv_testdriver buffer_testdriver endif test_binaries += crypto_testdriver packet_id_testdriver auth_token_testdriver ncp_testdriver misc_testdriver \ - pkt_testdriver + pkt_testdriver reflect_testdriver if HAVE_LD_WRAP_SUPPORT if !WIN32 test_binaries += tls_crypt_testdriver @@ -99,6 +99,22 @@ pkt_testdriver_SOURCES = test_pkt.c mock_msg.c mock_msg.h \ $(openvpn_srcdir)/win32-util.c \ $(openvpn_srcdir)/tls_crypt.c +reflect_testdriver_CFLAGS = @TEST_CFLAGS@ \ + -I$(openvpn_includedir) -I$(compat_srcdir) -I$(openvpn_srcdir) +reflect_testdriver_LDFLAGS = @TEST_LDFLAGS@ +reflect_testdriver_SOURCES = test_reflect.c mock_msg.c mock_msg.h \ + $(openvpn_srcdir)/reflect_filter.c \ + $(openvpn_srcdir)/bloom.c \ + $(openvpn_srcdir)/buffer.c \ + $(openvpn_srcdir)/crypto.c \ + $(openvpn_srcdir)/crypto_mbedtls.c \ + $(openvpn_srcdir)/crypto_openssl.c \ + $(openvpn_srcdir)/otime.c \ + $(openvpn_srcdir)/packet_id.c \ + $(openvpn_srcdir)/platform.c \ + $(openvpn_srcdir)/siphash.c \ + $(openvpn_srcdir)/win32-util.c + if !WIN32 tls_crypt_testdriver_CFLAGS = @TEST_CFLAGS@ \ -I$(openvpn_includedir) -I$(compat_srcdir) -I$(openvpn_srcdir) diff --git a/tests/unit_tests/openvpn/test_reflect.c b/tests/unit_tests/openvpn/test_reflect.c new file mode 100644 index 000000000..624c6b20b --- /dev/null +++ b/tests/unit_tests/openvpn/test_reflect.c @@ -0,0 +1,323 @@ +/* + * OpenVPN -- An application to securely tunnel IP networks + * over a single UDP port, with support for SSL/TLS-based + * session authentication and key exchange, + * packet encryption, packet authentication, and + * packet compression. + * + * Copyright (C) 2016-2021 Fox Crypto B.V. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program (see the file COPYING included with this + * distribution); if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#elif defined(_MSC_VER) +#include "config-msvc.h" +#endif + +#include "syshead.h" + +#include "bloom.h" +#include "buffer.h" +#include "reflect_filter.h" + +#include +#include +#include + +#include + + + +static void +test_bloom(void **state) +{ + static const int present_mod = 77; + + struct gc_arena gc = gc_new(); + /* Use a bloom filter with 1M entries (256kB) for the unit test */ + struct bloom_filter *bf = bloom_create(1024ul*1024, 8, &gc); + + for (int32_t i = 0; i < 20000; i += present_mod) + { + bloom_add(bf, (const uint8_t *) &i, sizeof(i)); + } + + /* all these should be positive, for the small unit test we do not expect + * false positive */ + for (int32_t i = 0; i < 20000; i++) + { + int present = (i % present_mod == 0 ) ? 1 : 0; + /* cast to bool to only get 1 and 0 for present/not present and not */ + assert_int_equal((bool) bloom_test(bf, (const uint8_t *) &i, sizeof(i)), present); + } + gc_free(&gc); +} + +static void +test_reflect_ddos(void **state) +{ + /* This tests if the bloom filter implementation does actually work with + * the goal of dropping packets to a reflected /24 while still allowing + * other clients */ + + /* Disable the normal fallback that puts a hard cap on the reflection filter */ + struct initial_packet_rate_limit *irl = initial_rate_limit_init(INT_MAX, 300, NULL); + + reflect_add_filter_tier(&irl->bloom_conf, &irl->gc, false, 24, 5); + reflect_add_filter_tier(&irl->bloom_conf, &irl->gc, false, 8, 20); + init_bloom_filter(irl); + + int num_legimate_reject = 0; + int num_legimate_accepted = 0; + + int num_ddos_rejected = 0; + int num_ddos_accepted = 0; + + + /* /24 net addresses in host byte order */ + in_addr_t net_attack[4]; + for (int i = 0; i < 3; i++) + { + net_attack[i] = random() % 0xff; + } + + /* The 4th network is close enough to the 3rd to fall in the same /16 */ + /* XOR the 3rd byte to achieve this */ + net_attack[3] = net_attack[2] ^ 00002300; + + /* Assume 200000 packets, including roughly 200 legitimate packets, + * the unit test works also with 20 millions packet but takes too long*/ + static const int total_packets = 200 * 1000; + + for (int i = 0; i < total_packets; i++) + { + struct openvpn_sockaddr from = { 0 }; + from.addr.in4.sin_family = AF_INET; + from.addr.in4.sin_addr.s_addr = random(); + from.addr.in4.sin_port = random(); + + if (i % 1023 == 0) + { + /* roughly 200 legitimate clients with random addresses */ + from.addr.in4.sin_addr.s_addr = random(); + + bool allowed = reflect_filter_check(irl, &from); + if (allowed) + { + num_legimate_accepted++; + } + else + { + num_legimate_reject++; + } + + } + else + { + /* We attack the 4 networks at random */ + from.addr.in4.sin_addr.s_addr = net_attack[random() % 4] + (random() % 256); + + bool allowed = reflect_filter_check(irl, &from); + if (allowed) + { + num_ddos_accepted++; + } + else + { + num_ddos_rejected++; + } + } + } + + assert_int_equal(num_legimate_reject + num_legimate_accepted + num_ddos_accepted + num_ddos_rejected, total_packets); + + /* We assume that most legitimate made it through but a few were unfortunate to be in an attacked network */ + assert_in_range(num_legimate_reject, 0, 10); + + /* We disabled total number of packets, so we expect all /8 to have their + * 20 packets, which is 5120. */ + assert_in_range(num_ddos_accepted, 0, 256 * 20); + + initial_rate_limit_free(irl); +} + + +static void +test_bloom_minimal(void **state) +{ + struct gc_arena gc = gc_new(); + struct bloom_filter *bf = bloom_create(2048, 3, &gc); + + int item = 0xbabe; + + bloom_add(bf, (const uint8_t *) &item, sizeof(item)); + assert_int_equal(bloom_test(bf, (const uint8_t *) &item, sizeof(item)), 1); + + item = 0xf00f; + assert_int_equal(bloom_test(bf, (const uint8_t *) &item, sizeof(item)), 0); + + bloom_free(bf); + gc_free(&gc); +} + +static void +test_reflect_reflect_bloom_simple(void **state) +{ + struct initial_packet_rate_limit *irl = initial_rate_limit_init(INT_MAX, 300, NULL); + + reflect_add_filter_tier(&irl->bloom_conf, &irl->gc, true, 32, 50); + reflect_add_filter_tier(&irl->bloom_conf, &irl->gc, true, 56, 200); + init_bloom_filter(irl); + + struct openvpn_sockaddr from = { 0 }; + from.addr.in6.sin6_family = AF_INET6; + from.addr.in6.sin6_port = random(); + from.addr.in6.sin6_addr.s6_addr[15] = 1; /* ::1 */ + + /* There are 50 attempts that should work until one fails */ + for (int i = 0; i < 50; i++) + { + assert_true(reflect_filter_check(irl, &from)); + } + + /* 2002::1 */ + struct openvpn_sockaddr from2 = from; + from2.addr.in6.sin6_addr.s6_addr[0] = 0x7; + from2.addr.in6.sin6_addr.s6_addr[1] = 0x7; + + assert_true(reflect_filter_check(irl, &from2)); + + /* Any more attempts from ::1 should fail */ + assert_false(reflect_filter_check(irl, &from)); + + initial_rate_limit_free(irl); +} + +static void +test_reflect_bloom_netmask_masking(void **state) +{ + struct initial_packet_rate_limit *irl = initial_rate_limit_init(INT_MAX, 300, NULL); + + reflect_add_filter_tier(&irl->bloom_conf, &irl->gc, true, 45, 10); + init_bloom_filter(irl); + + struct openvpn_sockaddr from = { 0 }; + from.addr.in6.sin6_family = AF_INET6; + from.addr.in6.sin6_port = random(); + for (int i = 0; i < 15; i++) + { + from.addr.in6.sin6_addr.s6_addr[i] = random(); + } + + for (int i = 0; i < 10; i++) + { + /* /45 means that if we leave the leave first 8 bytes and 5 bits + * untouched, it is still the same subnet */ + for (int j = 8; j<15; j++) + { + from.addr.in6.sin6_addr.s6_addr[j] = random(); + from.addr.in6.sin6_addr.s6_addr[7] ^= random() & 0x7; + } + assert_true(reflect_filter_check(irl, &from)); + + } + + /* testing the last IP again should give us a negative result */ + assert_false(reflect_filter_check(irl, &from)); + + initial_rate_limit_free(irl); +} + + +static void +test_reflect_reflect_bloom_mapped(void **state) +{ + struct initial_packet_rate_limit *irl = initial_rate_limit_init(INT_MAX, 300, NULL); + + reflect_add_filter_tier(&irl->bloom_conf, &irl->gc, false, 24, 50); + reflect_add_filter_tier(&irl->bloom_conf, &irl->gc, false, 8, 80); + init_bloom_filter(irl); + + /* Our OpenVPN server will not receive IPv4 as well as IPv4 mapped + * addresses in the same process but for the unit test it is convient + * to see if they actually mapped to the same entries */ + + struct openvpn_sockaddr mapped_v4 = { 0 }; + mapped_v4.addr.in6.sin6_family = AF_INET6; + mapped_v4.addr.in6.sin6_port = random(); + /* ::ffff:192.168.0.99 */ + mapped_v4.addr.in6.sin6_addr.s6_addr[10] = 0xff; + mapped_v4.addr.in6.sin6_addr.s6_addr[11] = 0xff; + mapped_v4.addr.in6.sin6_addr.s6_addr[12] = 192; + mapped_v4.addr.in6.sin6_addr.s6_addr[13] = 168; + mapped_v4.addr.in6.sin6_addr.s6_addr[14] = 0; + mapped_v4.addr.in6.sin6_addr.s6_addr[15] = 99; + + assert_true(IN6_IS_ADDR_V4MAPPED(&mapped_v4.addr.in6.sin6_addr)); + + /* Not the same address but in the same /8 */ + struct openvpn_sockaddr v4addr = {0 }; + v4addr.addr.in4.sin_family = AF_INET; + v4addr.addr.in4.sin_port = random(); + /* 192.168.123.244 */ + v4addr.addr.in4.sin_addr.s_addr = htonl(0xc0a87bf4); + + /* check that we run into the 50 limit with our mapped address */ + for (int i = 0; i < 50; i++) + { + assert_true(reflect_filter_check(irl, &mapped_v4)); + } + assert_false(reflect_filter_check(irl, &mapped_v4)); + + + /* Check that the non-mapped IPv4 address uses the same /8 subnet limit */ + for (int i = 0; i < 30; i++) + { + assert_true(reflect_filter_check(irl, &v4addr)); + } + assert_false(reflect_filter_check(irl, &v4addr)); + + initial_rate_limit_free(irl); +} + + +static void +test_bloom_access_functions(void **state) +{ + static_assert(BLOOM_FILTER_BITS_COUNT == 2, "unit test not in sync"); + static_assert(BLOOM_FILTER_BITS_MASK == 0x3, "unit test not in sync"); +} + + +int +main(void) +{ + const struct CMUnitTest tests[] = { + cmocka_unit_test(test_bloom_access_functions), + cmocka_unit_test(test_bloom), + cmocka_unit_test(test_bloom_minimal), + cmocka_unit_test(test_reflect_reflect_bloom_simple), + cmocka_unit_test(test_reflect_reflect_bloom_mapped), + cmocka_unit_test(test_reflect_bloom_netmask_masking), + cmocka_unit_test(test_reflect_ddos), + }; + + + int ret = cmocka_run_group_tests_name("crypto tests", tests, NULL, NULL); + + return ret; +} From patchwork Mon Feb 27 12:50:23 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Arne Schwabe X-Patchwork-Id: 3094 Return-Path: Delivered-To: patchwork@openvpn.net Received: by 2002:a05:7300:2310:b0:9f:bfa4:120f with SMTP id r16csp1132731dye; Mon, 27 Feb 2023 04:51:29 -0800 (PST) X-Google-Smtp-Source: AK7set+P53kiFRmndPAMEqupNXprcC6EhSF5jreGTxxuUXfDjCcEwyu0Zyilt0Wq9EyllL77/C5H X-Received: by 2002:aa7:9d83:0:b0:5a8:bd67:156d with SMTP id f3-20020aa79d83000000b005a8bd67156dmr19536399pfq.6.1677502289406; Mon, 27 Feb 2023 04:51:29 -0800 (PST) ARC-Seal: i=1; a=rsa-sha256; t=1677502289; cv=none; d=google.com; s=arc-20160816; b=Ku3Pb6Y+MU+ny/6K5gLgPmX0ge4HvLW4jzUvojF+sMSpJ2A4qopwUak3zzetKLoIfg igksjYEPC6bdDv06YMSOSUxGr/clRMuo/1fiu56o6ZtDkaRuZlubrzSaTOHPjaW5RSca RedgUt3EzgA2lWtkMEE4rkj+8NyK3Bifqr3drMzU8hVxI1gIHeabpWI8+/teYSKucxUQ zpb7jBGiNcehJdoqX1oZdEDKM0eL8XahS1vOnuT00oOT4lDlbYyaK4h8Pz6NY4wXRLTV hscmFIE51QfZRVQLmcsAM4SEDqoQKIUtCNNe8uKlSChWv5BWEcX1WGpp/lOvgx5Cbk1X ExDw== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=errors-to:content-transfer-encoding:list-subscribe:list-help :list-post:list-archive:list-unsubscribe:list-id:precedence:subject :mime-version:references:in-reply-to:message-id:date:to:from :dkim-signature:dkim-signature; bh=xVQOO9ZEb0Nj3GmgtTc3GAnczOE+f/Y19YI/MR1IiXI=; b=j3igtt0Cz8N+aNvGBLNSNCluvIWWnkfFJSkwePyFX50ibJLCNkR52SBhDkvYGN068K G6k8r7pWhc3yafSUGW7ltddpvKUQ5cBmXNMVS8K84GMODp6OSepmeovPPJTUEP5koAzE CJsIu2ln5EOre+Rv+bp88Q/ohtuA36lgqJyyLWHgLobFeqpK5UoNk0txf71orY+p0p8k IeoODZU8ByjgCxk2tcka08AIUZzibNv7UJMnBV/da7Bwk776gObNmyMdDPqbi5DHaBPk Mtlzqye6l1WpdqZXQzD4FpnLMUvhSI23gPWu2J0HPU32ZedT6Y48qlJPDSVz9AVWZLfs LI4g== ARC-Authentication-Results: i=1; mx.google.com; dkim=neutral (body hash did not verify) header.i=@sourceforge.net header.s=x header.b=LRTzbFU2; dkim=neutral (body hash did not verify) header.i=@sf.net header.s=x header.b=fpfm+yYX; spf=pass (google.com: domain of openvpn-devel-bounces@lists.sourceforge.net designates 216.105.38.7 as permitted sender) smtp.mailfrom=openvpn-devel-bounces@lists.sourceforge.net Received: from lists.sourceforge.net (lists.sourceforge.net. [216.105.38.7]) by mx.google.com with ESMTPS id u1-20020a056a00098100b00593c99a9c87si7312803pfg.348.2023.02.27.04.51.29 (version=TLS1_2 cipher=ECDHE-ECDSA-AES128-GCM-SHA256 bits=128/128); Mon, 27 Feb 2023 04:51:29 -0800 (PST) Received-SPF: pass (google.com: domain of openvpn-devel-bounces@lists.sourceforge.net designates 216.105.38.7 as permitted sender) client-ip=216.105.38.7; Authentication-Results: mx.google.com; dkim=neutral (body hash did not verify) header.i=@sourceforge.net header.s=x header.b=LRTzbFU2; dkim=neutral (body hash did not verify) header.i=@sf.net header.s=x header.b=fpfm+yYX; spf=pass (google.com: domain of openvpn-devel-bounces@lists.sourceforge.net designates 216.105.38.7 as permitted sender) smtp.mailfrom=openvpn-devel-bounces@lists.sourceforge.net Received: from [127.0.0.1] (helo=sfs-ml-2.v29.lw.sourceforge.com) by sfs-ml-2.v29.lw.sourceforge.com with esmtp (Exim 4.95) (envelope-from ) id 1pWcxi-0007wJ-1Z; Mon, 27 Feb 2023 12:50:37 +0000 Received: from [172.30.20.202] (helo=mx.sourceforge.net) by sfs-ml-2.v29.lw.sourceforge.com with esmtps (TLS1.2) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.95) (envelope-from ) id 1pWcxf-0007wD-JT for openvpn-devel@lists.sourceforge.net; Mon, 27 Feb 2023 12:50:34 +0000 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=sourceforge.net; s=x; h=Content-Transfer-Encoding:MIME-Version:References: In-Reply-To:Message-Id:Date:Subject:To:From:Sender:Reply-To:Cc:Content-Type: Content-ID:Content-Description:Resent-Date:Resent-From:Resent-Sender: Resent-To:Resent-Cc:Resent-Message-ID:List-Id:List-Help:List-Unsubscribe: List-Subscribe:List-Post:List-Owner:List-Archive; bh=cBwuNJ8GbSLSNpROw+c6cfPT0HR/rDHTFRhRUj8AOF0=; b=LRTzbFU2HtFqL6tS+juUlZu3sP z3GYSsCpDDaZfysMaYVUjBTG9Kb/t091VsNJ6cm9aQH7I28MpTopaJOUZpkaGvlXfPFArYj2kyWek WpBOAtOASzLSTaaT0TK5uKeUsOaW7YJFwmYsdgpyVH3a+xwa3sihY+kddV5p+ej7Z5UE=; DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=sf.net; s=x ; h=Content-Transfer-Encoding:MIME-Version:References:In-Reply-To:Message-Id: Date:Subject:To:From:Sender:Reply-To:Cc:Content-Type:Content-ID: Content-Description:Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc :Resent-Message-ID:List-Id:List-Help:List-Unsubscribe:List-Subscribe: List-Post:List-Owner:List-Archive; bh=cBwuNJ8GbSLSNpROw+c6cfPT0HR/rDHTFRhRUj8AOF0=; b=fpfm+yYXJ+DsHDklFS1FEy6bM+ ROwhailPTezi9eEYAg+Aw7yh+xLwC/f6rDd1lspmRxmTAR/VjP+ubJs0hPrxTLCfdvmFyUd/dJ1AA PmglUbzrQgAQUGrGOUmT9WTUnJ+gcmAQ1Utd3eNqfVga57KoJB6B07udgi3C87gmOiBw=; Received: from mail.blinkt.de ([192.26.174.232]) by sfi-mx-2.v28.lw.sourceforge.com with esmtps (TLS1.2:ECDHE-RSA-AES256-GCM-SHA384:256) (Exim 4.95) id 1pWcxc-00079x-8X for openvpn-devel@lists.sourceforge.net; Mon, 27 Feb 2023 12:50:34 +0000 Received: from kamera.blinkt.de ([2001:638:502:390:20c:29ff:fec8:535c]) by mail.blinkt.de with smtp (Exim 4.95 (FreeBSD)) (envelope-from ) id 1pWcxT-000IKC-Ti for openvpn-devel@lists.sourceforge.net; Mon, 27 Feb 2023 13:50:23 +0100 Received: (nullmailer pid 2561431 invoked by uid 10006); Mon, 27 Feb 2023 12:50:23 -0000 From: Arne Schwabe To: openvpn-devel@lists.sourceforge.net Date: Mon, 27 Feb 2023 13:50:23 +0100 Message-Id: <20230227125023.2561379-3-arne@rfc2549.org> X-Mailer: git-send-email 2.25.1 In-Reply-To: <20230227125023.2561379-1-arne@rfc2549.org> References: <20230227125023.2561379-1-arne@rfc2549.org> MIME-Version: 1.0 X-Spam-Score: 0.3 (/) X-Spam-Report: Spam detection software, running on the system "util-spamd-2.v13.lw.sourceforge.com", has NOT identified this incoming email as spam. The original message has been attached to this so you can view it or label similar future email. If you have any questions, see the administrator of that system for details. Content preview: OpenSSL library is significantly faster than the reference implementation (almost 2x). Prefer using this when available. The API for using the SIPHASH MAC is different enough from using normal HMAC or [...] Content analysis details: (0.3 points, 6.0 required) pts rule name description ---- ---------------------- -------------------------------------------------- 0.0 SPF_HELO_NONE SPF: HELO does not publish an SPF Record 0.0 SPF_NONE SPF: sender does not publish an SPF Record 0.2 HEADER_FROM_DIFFERENT_DOMAINS From and EnvelopeFrom 2nd level mail domains are different X-Headers-End: 1pWcxc-00079x-8X Subject: [Openvpn-devel] [PATCH 3/3] Prefer OpenSSL's SIPHASH implementation when available X-BeenThere: openvpn-devel@lists.sourceforge.net X-Mailman-Version: 2.1.21 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: openvpn-devel-bounces@lists.sourceforge.net X-getmail-retrieved-from-mailbox: Inbox X-GMAIL-THRID: =?utf-8?q?1758988640477801080?= X-GMAIL-MSGID: =?utf-8?q?1758988640477801080?= OpenSSL library is significantly faster than the reference implementation (almost 2x). Prefer using this when available. The API for using the SIPHASH MAC is different enough from using normal HMAC or Digest that we already implement that combining them into one API does not make sense. Change-Id: I09aa27caa1a3aab0d1be6118b26d54a1c1bf7aa0 Signed-off-by: Arne Schwabe --- src/openvpn/Makefile.am | 1 + src/openvpn/bloom.c | 14 ++- src/openvpn/bloom.h | 6 + src/openvpn/openvpn.vcxproj | 1 + src/openvpn/reflect_filter.c | 1 + src/openvpn/siphash.c | 4 +- src/openvpn/siphash.h | 48 +++++++- src/openvpn/siphash_openssl.c | 149 ++++++++++++++++++++++++ tests/unit_tests/openvpn/Makefile.am | 1 + tests/unit_tests/openvpn/test_reflect.c | 36 ++++++ 10 files changed, 253 insertions(+), 8 deletions(-) create mode 100644 src/openvpn/siphash_openssl.c diff --git a/src/openvpn/Makefile.am b/src/openvpn/Makefile.am index c1de7b723..2e9ba389c 100644 --- a/src/openvpn/Makefile.am +++ b/src/openvpn/Makefile.am @@ -124,6 +124,7 @@ openvpn_SOURCES = \ shaper.c shaper.h \ sig.c sig.h \ siphash.c siphash.h \ + siphash_openssl.c \ socket.c socket.h \ socks.c socks.h \ ssl.c ssl.h ssl_backend.h \ diff --git a/src/openvpn/bloom.c b/src/openvpn/bloom.c index 9382bf264..6225e0318 100644 --- a/src/openvpn/bloom.c +++ b/src/openvpn/bloom.c @@ -169,11 +169,20 @@ bloom_create(size_t size, size_t num_hashes, struct gc_arena *gc) bf->num_siphash = calculate_num_sip_hash_hashes(bf); ALLOC_ARRAY_GC(bf->siphash_keys, struct siphash_key, bf->num_siphash, gc); - + + bf->siphash_ctx = siphash_cryptolib_init(); + bloom_clear(bf); return bf; } +void +bloom_free(struct bloom_filter *bf) +{ + siphash_cryptolib_uninit(bf->siphash_ctx); +} + + /** * Clear the bloom filter, making it empty again as if it were freshly created * @param bf the bloom structure to clear @@ -209,7 +218,8 @@ bloom_add_test(struct bloom_filter *bf, const uint8_t *item, size_t len, bloom_c if (idx == 0) { /* We have no longer unused bytes in result, generate the next hash */ - siphash(item, len, bf->siphash_keys[j++].key, result, SIPHASH_HASH_SIZE); + siphash(bf->siphash_ctx, item, len, bf->siphash_keys[j++].key, + result, SIPHASH_HASH_SIZE); } bucket = bucket << 8; diff --git a/src/openvpn/bloom.h b/src/openvpn/bloom.h index e18026181..b35d784da 100644 --- a/src/openvpn/bloom.h +++ b/src/openvpn/bloom.h @@ -75,6 +75,9 @@ struct bloom_filter { /** keys for the siphash functions */ struct siphash_key *siphash_keys; + /** (opaque) context for the siphash implementation */ + void *siphash_ctx; + /** the actual buckets that hold the data */ bloom_counter_t buckets[]; }; @@ -83,6 +86,9 @@ struct bloom_filter { struct bloom_filter * bloom_create(size_t size, size_t num_hashes, struct gc_arena *gc); +void +bloom_free(struct bloom_filter *bf); + bloom_counter_t bloom_test(struct bloom_filter *bf, const uint8_t *item, size_t len); diff --git a/src/openvpn/openvpn.vcxproj b/src/openvpn/openvpn.vcxproj index 2d12f8309..c6bd544d2 100644 --- a/src/openvpn/openvpn.vcxproj +++ b/src/openvpn/openvpn.vcxproj @@ -332,6 +332,7 @@ + diff --git a/src/openvpn/reflect_filter.c b/src/openvpn/reflect_filter.c index 35ab2880c..0d3a742a5 100644 --- a/src/openvpn/reflect_filter.c +++ b/src/openvpn/reflect_filter.c @@ -450,6 +450,7 @@ initial_rate_limit_init(int max_per_period, int period_length, void initial_rate_limit_free(struct initial_packet_rate_limit *irl) { + bloom_free(irl->bf); gc_free(&irl->gc); free(irl); irl = NULL; diff --git a/src/openvpn/siphash.c b/src/openvpn/siphash.c index b8a7bbc11..0c995f964 100644 --- a/src/openvpn/siphash.c +++ b/src/openvpn/siphash.c @@ -87,8 +87,8 @@ * outlen: length of the output in bytes, must be 8 or 16 */ int -siphash(const void *in, const size_t inlen, const void *k, uint8_t *out, - const size_t outlen) +siphash_reference(const void *in, const size_t inlen, const void *k, + uint8_t *out, const size_t outlen) { const unsigned char *ni = (const unsigned char *)in; diff --git a/src/openvpn/siphash.h b/src/openvpn/siphash.h index d26ee36ec..14414d5b4 100644 --- a/src/openvpn/siphash.h +++ b/src/openvpn/siphash.h @@ -20,12 +20,52 @@ #include #include - -int siphash(const void *in, size_t inlen, const void *k, uint8_t *out, - size_t outlen); +#include /* siphash always uses 128-bit keys */ #define SIPHASH_KEY_SIZE 16 #define SIPHASH_HASH_SIZE 16 -#endif + +/* Prototypes for an implementation of SIPHASH in a crypto library */ + +/** + * Calculates SIPHASH using the crypto library function. + */ +int +siphash_cryptolib(void *sip_context, const void *in, size_t inlen, + const void *k, uint8_t *out, size_t outlen); + +/** + * Calculates SIPHASH using the reference implementation + */ +int +siphash_reference(const void *in, size_t inlen, const void *k, + uint8_t *out, size_t outlen); + +void * +siphash_cryptolib_init(void); + +void +siphash_cryptolib_uninit(void *sip_context); + +bool +siphash_cryptolib_available(void *sip_context); + +static inline +int +siphash(void *ctx, const void *in, size_t inlen, const void *k, + uint8_t *out, size_t outlen) +{ + if (siphash_cryptolib_available(ctx) && false) + { + return siphash_cryptolib(ctx, in, inlen, k, out, outlen); + } + else + { + return siphash_reference(in, inlen, k, out, outlen); + } + +} + +#endif /* ifndef SIPHASH_H */ diff --git a/src/openvpn/siphash_openssl.c b/src/openvpn/siphash_openssl.c new file mode 100644 index 000000000..d29f3b6a8 --- /dev/null +++ b/src/openvpn/siphash_openssl.c @@ -0,0 +1,149 @@ +/* + * OpenVPN -- An application to securely tunnel IP networks + * over a single TCP/UDP port, with support for SSL/TLS-based + * session authentication and key exchange, + * packet encryption, packet authentication, and + * packet compression. + * + * Copyright (C) 2023 OpenVPN Inc + * Copyright (C) 2023 Arne Schwabe + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#elif defined(_MSC_VER) +#include "config-msvc.h" +#endif + +#include "syshead.h" + +#include "siphash.h" +#include "buffer.h" + + +#ifdef ENABLE_CRYPTO_OPENSSL +#include +#endif + +#if defined(ENABLE_CRYPTO_OPENSSL) && OPENSSL_VERSION_NUMBER >= 0x30000000L + +#include +#include "crypto_openssl.h" + +struct siphash_context +{ + EVP_MAC *mac; + EVP_MAC_CTX *ctx; + size_t size; + OSSL_PARAM params[3]; +}; + +/* + * Computes a SipHash value + * in: pointer to input data (read-only) + * inlen: input data length in bytes (any size_t value) + * k: pointer to the key data (read-only), must be 16 bytes + * out: pointer to output data (write-only), outlen bytes must be allocated + * outlen: length of the output in bytes, must be 8 or 16 + */ +int +siphash_cryptolib(void *sip_context, const void *in, const size_t inlen, + const void *k, uint8_t *out, const size_t outlen) +{ + struct siphash_context *sip = sip_context; + + + sip->params[1] = OSSL_PARAM_construct_octet_string("key", (void *)k, + SIPHASH_KEY_SIZE); + if (!EVP_MAC_init(sip->ctx, NULL, 0, sip->params)) + { + crypto_msg(M_FATAL, "EVP_MAC_init failed"); + } + EVP_MAC_update(sip->ctx, in, inlen); + + size_t outl = 0; + EVP_MAC_final(sip->ctx, out, &outl, outlen); + return 0; +} + +void * +siphash_cryptolib_init() +{ + struct siphash_context *sip; + ALLOC_OBJ(sip, struct siphash_context); + + sip->mac = EVP_MAC_fetch(NULL, "SIPHASH", NULL); + if (!sip->mac) + { + /* Our OpenSSL library does not support SIPHASH */ + return sip; + } + sip->ctx = EVP_MAC_CTX_new(sip->mac); + + /* OpenSSL will truly hold a pointer to an int in that parameter */ + sip->size = SIPHASH_HASH_SIZE; + sip->params[0] = OSSL_PARAM_construct_size_t("size", &sip->size); + /* params[1] will hold the key that changes which each invocation */ + sip->params[2] = OSSL_PARAM_construct_end(); + return sip; +} + +bool +siphash_cryptolib_available(void *sip_context) +{ + struct siphash_context *sip = sip_context; + + return (bool)(sip->mac); +} + +void +siphash_cryptolib_uninit(void *sip_context) +{ + struct siphash_context *sip = sip_context; + EVP_MAC_CTX_free(sip->ctx); + EVP_MAC_free(sip->mac); + free(sip_context); +} + +#else /* if defined(ENABLE_CRYPTO_OPENSSL) && OPENSSL_VERSION_NUMBER >= 0x30000000L */ +/* for now, we only have one implementation of SIPHASH in a libray, so put the + * dummy functions also here */ +int +siphash_cryptolib(void *sip_context, const void *in, const size_t inlen, + const void *k, uint8_t *out, const size_t outlen) +{ + return -1; +} + +bool +siphash_cryptolib_available(void *sip_context) +{ + return false; +} + +void * +siphash_cryptolib_init(void) +{ + return NULL; +} + +void +siphash_cryptolib_uninit(void *sip_context) +{ +} + + +#endif /* if defined(ENABLE_CRYPTO_OPENSSL) && OPENSSL_VERSION_NUMBER >= 0x30000000L */ diff --git a/tests/unit_tests/openvpn/Makefile.am b/tests/unit_tests/openvpn/Makefile.am index b8ba1d4db..c6f026561 100644 --- a/tests/unit_tests/openvpn/Makefile.am +++ b/tests/unit_tests/openvpn/Makefile.am @@ -113,6 +113,7 @@ reflect_testdriver_SOURCES = test_reflect.c mock_msg.c mock_msg.h \ $(openvpn_srcdir)/packet_id.c \ $(openvpn_srcdir)/platform.c \ $(openvpn_srcdir)/siphash.c \ + $(openvpn_srcdir)/siphash_openssl.c \ $(openvpn_srcdir)/win32-util.c if !WIN32 diff --git a/tests/unit_tests/openvpn/test_reflect.c b/tests/unit_tests/openvpn/test_reflect.c index 624c6b20b..0ce111d95 100644 --- a/tests/unit_tests/openvpn/test_reflect.c +++ b/tests/unit_tests/openvpn/test_reflect.c @@ -40,8 +40,43 @@ #include +static void +test_siphash(void **state) +{ + const char *message = "Look behind you, a Three-Headed Monkey!"; + + uint8_t out[SIPHASH_HASH_SIZE]; + const uint8_t key[SIPHASH_KEY_SIZE] = { 0x11, 0x22, 0x33, 0x44, 0x55, 0x66 }; + + siphash_reference(message, strlen(message), key, out, SIPHASH_HASH_SIZE); + + const uint8_t expected_out[SIPHASH_HASH_SIZE] = + { 0x3e, 0xea, 0x95, 0xb2, 0x6d, 0x5c, 0x4e, 0xfa, + 0x20, 0x47, 0x65, 0x7e, 0xdd, 0xcd, 0x62, 0x51}; + assert_memory_equal(out, expected_out, SIPHASH_HASH_SIZE); + + struct gc_arena gc = gc_new(); + void *sipctx = siphash_cryptolib_init(); + uint8_t out2[SIPHASH_HASH_SIZE]; + + if (siphash_cryptolib_available(sipctx)) + { + siphash_cryptolib(sipctx, message, strlen(message), key, out2, + SIPHASH_HASH_SIZE); + assert_memory_equal(out, out2, SIPHASH_HASH_SIZE); + + /* check that calling the function twice is safe */ + siphash_cryptolib(sipctx, message, strlen(message), key, out2, + SIPHASH_HASH_SIZE); + assert_memory_equal(out, out2, SIPHASH_HASH_SIZE); + } + + siphash_cryptolib_uninit(sipctx); + gc_free(&gc); +} + static void test_bloom(void **state) { @@ -307,6 +342,7 @@ int main(void) { const struct CMUnitTest tests[] = { + cmocka_unit_test(test_siphash), cmocka_unit_test(test_bloom_access_functions), cmocka_unit_test(test_bloom), cmocka_unit_test(test_bloom_minimal),