@@ -572,6 +572,8 @@
src/openvpn/shaper.h
src/openvpn/sig.c
src/openvpn/sig.h
+ src/openvpn/siphash.h
+ src/openvpn/siphash_reference.c
src/openvpn/socket.c
src/openvpn/socket.h
src/openvpn/socket_util.c
@@ -784,6 +786,7 @@
)
target_sources(test_crypto PRIVATE
+ tests/unit_tests/openvpn/siphash_openssl.c
src/openvpn/crypto_mbedtls.c
src/openvpn/crypto_openssl.c
src/openvpn/crypto_epoch.c
@@ -792,6 +795,7 @@
src/openvpn/packet_id.c
src/openvpn/mtu.c
src/openvpn/mss.c
+ src/openvpn/siphash_reference.c
)
target_sources(test_ssl PRIVATE
@@ -128,6 +128,7 @@
session_id.c session_id.h \
shaper.c shaper.h \
sig.c sig.h \
+ siphash_reference.c siphash.h \
socket.c socket.h \
socket_util.c socket_util.h \
socks.c socks.h \
@@ -18,12 +18,26 @@
#ifndef SIPHASH_H
#define SIPHASH_H
-#include <inttypes.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdbool.h>
/* siphash always uses 128-bit keys */
#define SIPHASH_KEY_SIZE 16
-int siphash(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);
-#endif
+
+static inline int
+siphash(const void *in, size_t inlen, const void *k,
+ uint8_t *out, size_t outlen)
+{
+ return siphash_reference(in, inlen, k, out, outlen);
+}
+
+#endif /* ifndef SIPHASH_H */
\ No newline at end of file
@@ -100,8 +100,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;
const unsigned char *kk = (const unsigned char *)k;
@@ -73,6 +73,7 @@
crypto_testdriver_LDFLAGS = @TEST_LDFLAGS@
crypto_testdriver_SOURCES = test_crypto.c \
mock_msg.c mock_msg.h test_common.h \
+ siphash_openssl.c siphash_openssl.h \
$(top_srcdir)/src/openvpn/buffer.c \
$(top_srcdir)/src/openvpn/crypto.c \
$(top_srcdir)/src/openvpn/crypto_mbedtls.c \
@@ -84,7 +85,8 @@
$(top_srcdir)/src/openvpn/platform.c \
$(top_srcdir)/src/openvpn/mtu.c \
$(top_srcdir)/src/openvpn/win32-util.c \
- $(top_srcdir)/src/openvpn/mss.c
+ $(top_srcdir)/src/openvpn/mss.c \
+ $(top_srcdir)/src/openvpn/siphash_reference.c
dhcp_testdriver_CFLAGS = -I$(top_srcdir)/src/openvpn -I$(top_srcdir)/src/compat @TEST_CFLAGS@ -DDHCP_UNIT_TEST
dhcp_testdriver_LDFLAGS = @TEST_LDFLAGS@ -L$(top_srcdir)/src/openvpn
new file mode 100644
@@ -0,0 +1,144 @@
+/*
+ * 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) 2002-2026 OpenVPN Inc <sales@openvpn.net>
+ * Copyright (C) 2026 Arne Schwabe <arne@rfc2549.org>
+ *
+ * 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, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "siphash.h"
+
+#ifdef ENABLE_CRYPTO_OPENSSL
+#include <openssl/opensslv.h>
+#endif
+
+/* OpenSSL siphash is currently 2-3 times slower than the reference
+ * implementation, so we only use it for unit testing that our implementation
+ * and OpenSSL agree */
+#if defined(ENABLE_CRYPTO_OPENSSL) && OPENSSL_VERSION_NUMBER >= 0x30000000L
+#include <openssl/evp.h>
+#include "crypto_openssl.h"
+#include "crypto_backend.h"
+#include "buffer.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_openssl(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_openssl_init(size_t hash_size)
+{
+ 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 = 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_openssl_available(void *sip_context)
+{
+ struct siphash_context *sip = sip_context;
+
+ return (bool)(sip->mac);
+}
+
+void
+siphash_openssl_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
+/* Do avoid a lot more ifdefs in the test we put dummy functions here */
+int
+siphash_openssl(void *sip_context, const void *in, const size_t inlen,
+ const void *k, uint8_t *out, const size_t outlen)
+{
+ return -1;
+}
+
+bool
+siphash_openssl_available(void *sip_context)
+{
+ return false;
+}
+
+void *
+siphash_openssl_init(size_t hash_size)
+{
+ return NULL;
+}
+
+void
+siphash_openssl_uninit(void *sip_context)
+{
+}
+
+
+#endif /* if defined(ENABLE_CRYPTO_OPENSSL) && OPENSSL_VERSION_NUMBER >= 0x30000000L */
new file mode 100644
@@ -0,0 +1,64 @@
+/*
+ * 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) 2002-2026 OpenVPN Inc <sales@openvpn.net>
+ *
+ * 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, see <https://www.gnu.org/licenses/>.
+ */
+#ifndef SIPHASH_OPENSSL_H
+#define SIPHASH_OPENSSL_H
+
+#include <stdint.h>
+#include <stdio.h>
+#include <stdbool.h>
+
+
+/* Prototypes for an implementation of SIPHASH in a crypto library */
+/**
+ *
+ * @param hash_size the size of the output hash size
+ * @return initialised context for siphash
+ */
+void *
+siphash_openssl_init(size_t hash_size);
+
+
+/**
+ * Calculates SIPHASH using the crypto library function.
+ */
+int
+siphash_openssl(void *sip_context, const void *in, size_t inlen,
+ const void *k, uint8_t *out, size_t outlen);
+
+/**
+ * Free the siphash context used for the crypto library
+ * @param sip_context
+ */
+void
+siphash_openssl_uninit(void *sip_context);
+
+/**
+ * Returns if the crypto library is available (and should be used)
+ *
+ * This returns if there is a crypto library version of Siphash24 is
+ * available and should be used (OpenSSL 3/4 version is quite slow, so
+ * we prefer the reference implementation)
+ *
+ */
+bool
+siphash_openssl_available(void *sip_context);
+#endif /* ifndef SIPHASH_OPENSSL_H */
\ No newline at end of file
@@ -31,12 +31,15 @@
#include <stdarg.h>
#include <string.h>
#include <setjmp.h>
+#include <inttypes.h>
#include <cmocka.h>
#include "crypto.h"
#include "crypto_epoch.h"
#include "options.h"
#include "ssl_backend.h"
+#include "siphash.h"
+#include "siphash_openssl.h"
#include "mss.h"
#include "test_common.h"
@@ -923,6 +926,69 @@
assert_memory_equal(key_parameters.hmac, exp_impl_iv, sizeof(exp_impl_iv));
}
+/* Use a define here since some c compilers don't like array initialisation
+ * with an integer */
+#define UT_SIPHASH_HASH_SIZE 16
+
+static const char *ut_message = "Look behind you, a Three-Headed Monkey!";
+static const uint8_t ut_key[SIPHASH_KEY_SIZE] = { 0x11, 0x22, 0x33, 0x44, 0x55, 0x66 };
+const uint8_t expected_hash[UT_SIPHASH_HASH_SIZE] = { 0x3e, 0xea, 0x95, 0xb2, 0x6d, 0x5c, 0x4e, 0xfa,
+ 0x20, 0x47, 0x65, 0x7e, 0xdd, 0xcd, 0x62, 0x51 };
+
+static void
+test_siphash(void **state)
+{
+ uint8_t out[UT_SIPHASH_HASH_SIZE] = { 0 };
+ siphash_reference(ut_message, strlen(ut_message), ut_key, out, UT_SIPHASH_HASH_SIZE);
+ assert_memory_equal(out, expected_hash, UT_SIPHASH_HASH_SIZE);
+}
+
+static void
+test_siphash_openssl(void **state)
+{
+ void *sipctx = siphash_openssl_init(UT_SIPHASH_HASH_SIZE);
+
+ if (!siphash_openssl_available(sipctx))
+ {
+ siphash_openssl_uninit(sipctx);
+ skip();
+ }
+
+ uint8_t out[UT_SIPHASH_HASH_SIZE] = { 0 };
+
+ siphash_openssl(sipctx, ut_message, strlen(ut_message), ut_key, out,
+ UT_SIPHASH_HASH_SIZE);
+ assert_memory_equal(out, expected_hash, UT_SIPHASH_HASH_SIZE);
+
+ /* check that calling the function twice is safe */
+ siphash_openssl(sipctx, ut_message, strlen(ut_message), ut_key, out,
+ UT_SIPHASH_HASH_SIZE);
+ assert_memory_equal(out, expected_hash, UT_SIPHASH_HASH_SIZE);
+
+ /* Test a few random strings and ensure that our implementation behave the
+ * same */
+ for (int i = 0; i < 1000; i++)
+ {
+ size_t len = random() % 1000u;
+ uint8_t buf[1024] = { 0 };
+ uint8_t key[SIPHASH_KEY_SIZE] = { 0 };
+
+ assert_true(rand_bytes(buf, (int)len));
+ assert_true(rand_bytes(key, sizeof(key)));
+
+
+ siphash_openssl(sipctx, buf, len, key, out, UT_SIPHASH_HASH_SIZE);
+
+ uint8_t outref[UT_SIPHASH_HASH_SIZE] = { 0 };
+ siphash_reference(buf, len, key, outref, UT_SIPHASH_HASH_SIZE);
+
+ assert_memory_equal(out, outref, UT_SIPHASH_HASH_SIZE);
+ }
+
+ siphash_openssl_uninit(sipctx);
+}
+
+
int
main(void)
{
@@ -960,7 +1026,9 @@
cmocka_unit_test_prestate_setup_teardown(crypto_test_epoch_edge,
crypto_test_epoch_setup,
crypto_test_epoch_teardown, &prestate_num13),
- cmocka_unit_test(epoch_test_derive_data_key)
+ cmocka_unit_test(epoch_test_derive_data_key),
+ cmocka_unit_test(test_siphash),
+ cmocka_unit_test(test_siphash_openssl)
};
return cmocka_run_group_tests_name("crypto tests", tests, NULL, NULL);