diff --git a/Vagrantfile b/Vagrantfile index bd6edf641086a2e01df66150ab6486707c4cbe07..d57c36bf9840e508e37467813e35fea9ab967407 100644 --- a/Vagrantfile +++ b/Vagrantfile @@ -28,7 +28,13 @@ Vagrant.configure("2") do |config| # Mount local directories on VMs if vm_config[:name] == "wireguard-client1" + node.vm.box = "generic/alpine318" node.vm.synced_folder "./wireguard-client1", "/etc/wireguard" + node.vm.synced_folder "./linux", "/linux" + node.vm.provider "virtualbox" do |vb| + vb.memory = "4096" + vb.cpus = 8 + end end if vm_config[:name] == "wireguard-client2" node.vm.synced_folder "./wireguard-client2", "/etc/wireguard" @@ -92,17 +98,14 @@ Vagrant.configure("2") do |config| echo "Hello from #{vm_config[:name]} with IP: #{vm_config[:ip]}!" # Update repository - sudo apt-get -y update + sudo apk update - # Install wireguard along with - sudo apt-get -y install wireguard + # Install wireguard along with + sudo apk add wireguard-tools-wg-quick iptables # Some customizations - sudo apt-get -y install vim + sudo apk add vim sed -ri 's/^( *)#alias (.*)/\\1alias \\2/' /home/vagrant/.bashrc - - # Install resolvconf for Wireguard to work - sudo apt-get -y install resolvconf SHELL end diff --git a/linux/APKBUILD b/linux/APKBUILD new file mode 100644 index 0000000000000000000000000000000000000000..416625d5e46a1d64e14216b42090734ac05746eb --- /dev/null +++ b/linux/APKBUILD @@ -0,0 +1,353 @@ +# Maintainer: Natanael Copa <ncopa@alpinelinux.org> + +_flavor=lts +pkgname=linux-$_flavor +pkgver=6.1.77 +case $pkgver in + *.*.*) _kernver=${pkgver%.*};; + *.*) _kernver=$pkgver;; +esac +pkgrel=0 +pkgdesc="Linux lts kernel" +url="https://www.kernel.org" +depends="initramfs-generator" +_depends_dev="perl gmp-dev mpc1-dev mpfr-dev elfutils-dev bash flex bison zstd" +makedepends="$_depends_dev sed installkernel bc linux-headers linux-firmware-any openssl-dev>3 mawk + diffutils findutils zstd pahole>=1.25 python3" +options="!strip" +_config=${config:-config-lts.${CARCH}} +source="https://cdn.kernel.org/pub/linux/kernel/v${pkgver%%.*}.x/linux-$_kernver.tar.xz + 0001-powerpc-config-defang-gcc-check-for-stack-protector-.patch + 0001-x86-Compress-vmlinux-with-zstd-19-instead-of-22.patch + 0001-kexec-add-kexec_load_disabled-boot-option.patch + awk.patch + wireguard-leak.patch + ppc-notext.patch + 0001-tty-Move-sysctl-setup-into-core-tty-logic.patch + 0002-tty-Allow-TIOCSTI-to-be-disabled.patch + 0003-tty-Move-TIOCSTI-toggle-variable-before-kerndoc.patch + + virt.aarch64.config + virt.armv7.config + virt.ppc64le.config + virt.x86.config + virt.x86_64.config + " +# subpackages="$pkgname-dev:_dev:$CBUILD_ARCH" +for _i in $source; do + case $_i in + *.$CARCH.config) + _f=${_i%."$CARCH".config} + _flavors="$_flavors $_f" + if [ "linux-$_f" != "$pkgname" ]; then + subpackages="$subpackages linux-$_f::$CBUILD_ARCH linux-$_f-dev:_dev:$CBUILD_ARCH" + fi + ;; + esac +done +builddir="$srcdir"/linux-$_kernver + +if [ "${pkgver%.0}" = "$pkgver" ]; then + source="$source + https://cdn.kernel.org/pub/linux/kernel/v${pkgver%%.*}.x/patch-$pkgver.xz" +fi +arch="all !armhf !riscv64" +license="GPL-2.0-only" + +# secfixes: +# 5.10.4-r0: +# - CVE-2020-29568 +# - CVE-2020-29569 +# 5.15.74-r0: +# - CVE-2022-41674 +# - CVE-2022-42719 +# - CVE-2022-42720 +# - CVE-2022-42721 +# - CVE-2022-42722 +# 6.1.27-r3: +# - CVE-2023-32233 +# 6.1.74-r0: +# - CVE-2023-46838 + +prepare() { + if [ "$_kernver" != "$pkgver" ]; then + msg "Applying patch-$pkgver.xz" + unxz -c < "$srcdir"/patch-$pkgver.xz | patch -p1 -N + fi + + default_prepare + + # remove localversion from patch if any + rm -f localversion* +} + +_kernelarch() { + local arch="$1" + case "$arch" in + aarch64*) arch="arm64" ;; + arm*) arch="arm" ;; + mips*) arch="mips" ;; + ppc*) arch="powerpc" ;; + s390*) arch="s390" ;; + esac + echo "$arch" +} + +_prepareconfig() { + local _flavor="$1" + local _arch="$2" + local _config=$_flavor.$_arch.config + local _builddir="$srcdir"/build-$_flavor.$_arch + mkdir -p "$_builddir" + echo "-$pkgrel-$_flavor" > "$_builddir"/localversion-alpine + + cp "$srcdir"/$_config "$_builddir"/.config + msg "Configuring $_flavor kernel ($_arch)" + make -C "$srcdir"/linux-$_kernver \ + O="$_builddir" \ + ARCH="$(_kernelarch $_arch)" \ + olddefconfig + + if grep "CONFIG_MODULE_SIG=y" "$_builddir"/.config >/dev/null; then + if [ -f "$KERNEL_SIGNING_KEY" ]; then + sed -i -e "s:^CONFIG_MODULE_SIG_KEY=.*:CONFIG_MODULE_SIG_KEY=\"$KERNEL_SIGNING_KEY\":" \ + "$_builddir"/.config + msg "Using $KERNEL_SIGNING_KEY to sign $_flavor kernel ($_arch) modules" + else + warning "KERNEL_SIGNING_KEY was not set. A signing key will be generated, but 3rd" + warning "party modules can not be signed" + fi + fi +} + +listconfigs() { + for i in $source; do + case "$i" in + *.config) echo $i;; + esac + done +} + +prepareconfigs() { + for _config in $(listconfigs); do + local _flavor=${_config%%.*} + local _arch=${_config%.config} + _arch=${_arch#*.} + local _builddir="$srcdir"/build-$_flavor.$_arch + _prepareconfig "$_flavor" "$_arch" + done +} + +# this is supposed to be run before version is bumped so we can compare +# what new kernel config knobs are introduced +prepareupdate() { + clean && fetch && unpack && prepare && deps + prepareconfigs + rm -r "$srcdir"/linux-$_kernver +} + +updateconfigs() { + if ! [ -d "$srcdir"/linux-$_kernver ]; then + deps && fetch && unpack && prepare + fi + for _config in ${CONFIGS:-$(listconfigs)}; do + msg "updating $_config" + local _flavor=${_config%%.*} + local _arch=${_config%.config} + _arch=${_arch#*.} + local _builddir="$srcdir"/build-$_flavor.$_arch + mkdir -p "$_builddir" + echo "-$pkgrel-$_flavor" > "$_builddir"/localversion-alpine + local actions="listnewconfig oldconfig" + if ! [ -f "$_builddir"/.config ]; then + cp "$srcdir"/$_config "$_builddir"/.config + actions="olddefconfig" + fi + env | grep ^CONFIG_ >> "$_builddir"/.config || true + make -j1 -C "$srcdir"/linux-$_kernver \ + O="$_builddir" \ + ARCH="$(_kernelarch $_arch)" \ + $actions savedefconfig + + cp "$_builddir"/defconfig "$startdir"/$_config + done +} + +build() { + unset LDFLAGS + # for some reason these sometimes leak into the kernel build, + # -Werror=format-security breaks some stuff + unset CFLAGS CPPFLAGS CXXFLAGS + export KBUILD_BUILD_TIMESTAMP="$(date -Ru${SOURCE_DATE_EPOCH:+d @$SOURCE_DATE_EPOCH})" + for i in $_flavors; do + _prepareconfig "$i" "$CARCH" + done + for i in $_flavors; do + msg "Building $i kernel" + cd "$srcdir"/build-$i.$CARCH + + # set org in cert for modules signing + # https://www.kernel.org/doc/html/v6.1/admin-guide/module-signing.html#generating-signing-keys + mkdir -p certs + sed -e 's/#O = Unspecified company/O = alpinelinux.org/' \ + "$srcdir"/linux-$_kernver/certs/default_x509.genkey \ + > certs/x509.genkey + + make ARCH="$(_kernelarch $CARCH)" \ + CC="${CC:-gcc}" \ + AWK="${AWK:-mawk}" \ + KBUILD_BUILD_VERSION="$((pkgrel + 1 ))-Alpine" + done +} + +_package() { + local _buildflavor="$1" _outdir="$2" + export KBUILD_BUILD_TIMESTAMP="$(date -Ru${SOURCE_DATE_EPOCH:+d @$SOURCE_DATE_EPOCH})" + + cd "$srcdir"/build-$_buildflavor.$CARCH + local _abi_release="$(make -s kernelrelease)" + # modules_install seems to regenerate a defect Modules.symvers on s390x. Work + # around it by backing it up and restore it after modules_install + cp Module.symvers Module.symvers.backup + + mkdir -p "$_outdir"/boot "$_outdir"/lib/modules + + local _install + case "$CARCH" in + arm*|aarch64) _install="zinstall dtbs_install";; + *) _install=install;; + esac + + make modules_install $_install \ + ARCH="$(_kernelarch $CARCH)" \ + INSTALL_MOD_PATH="$_outdir" \ + INSTALL_MOD_STRIP=1 \ + INSTALL_PATH="$_outdir"/boot \ + INSTALL_DTBS_PATH="$_outdir/boot/dtbs-$_buildflavor" + + cp Module.symvers.backup Module.symvers + + rm -f "$_outdir"/lib/modules/"$_abi_release"/build \ + "$_outdir"/lib/modules/"$_abi_release"/source + rm -rf "$_outdir"/lib/firmware + + install -D -m644 include/config/kernel.release \ + "$_outdir"/usr/share/kernel/$_buildflavor/kernel.release +} + +# main flavor installs in $pkgdir +package() { + depends="$depends linux-firmware-any" + + _package lts "$pkgdir" + + # copy files for linux-lts-doc sub package + mkdir -p "$pkgdir"/usr/share/doc + cp -r "$srcdir"/linux-"$_kernver"/Documentation \ + "$pkgdir"/usr/share/doc/linux-doc-"$pkgver"/ + # remove files that aren't part of the documentation itself + for nondoc in \ + .gitignore conf.py docutils.conf \ + dontdiff Kconfig Makefile + do + rm "$pkgdir"/usr/share/doc/linux-doc-"$pkgver"/"$nondoc" + done + # create /usr/share/doc/linux-doc symlink + cd "$pkgdir"/usr/share/doc; ln -s linux-doc-"$pkgver" linux-doc +} + +# subflavors install in $subpkgdir +virt() { + _package virt "$subpkgdir" +} + +_dev() { + local _flavor=$(echo $subpkgname | sed -E 's/(^linux-|-dev$)//g') + local _builddir="$srcdir"/build-$_flavor.$CARCH + local _abi_release="$(make -C "$_builddir" -s kernelrelease)" + # copy the only the parts that we really need for build 3rd party + # kernel modules and install those as /usr/src/linux-headers, + # simlar to what ubuntu does + # + # this way you dont need to install the 300-400 kernel sources to + # build a tiny kernel module + # + pkgdesc="Headers and script for third party modules for $_flavor kernel" + depends="$_depends_dev" + local dir="$subpkgdir"/usr/src/linux-headers-"$_abi_release" + export KBUILD_BUILD_TIMESTAMP="$(date -Ru${SOURCE_DATE_EPOCH:+d @$SOURCE_DATE_EPOCH})" + + # first we import config, run prepare to set up for building + # external modules, and create the scripts + mkdir -p "$dir" + cp -a "$_builddir"/.config "$_builddir"/localversion-alpine \ + "$dir"/ + + install -D -t "$dir"/certs "$_builddir"/certs/signing_key.x509 || : + + make -C "$srcdir"/linux-$_kernver \ + O="$dir" \ + ARCH="$(_kernelarch $CARCH)" \ + AWK="${AWK:-mawk}" \ + prepare modules_prepare scripts + + # remove the stuff that points to real sources. we want 3rd party + # modules to believe this is the sources + rm "$dir"/Makefile "$dir"/source + + # copy the needed stuff from real sources + # + # this is taken from ubuntu kernel build script + # http://kernel.ubuntu.com/git/ubuntu/ubuntu-zesty.git/tree/debian/rules.d/3-binary-indep.mk + cd "$srcdir"/linux-$_kernver + find . -path './include/*' -prune \ + -o -path './scripts/*' -prune -o -type f \ + \( -name 'Makefile*' -o -name 'Kconfig*' -o -name 'Kbuild*' -o \ + -name '*.sh' -o -name '*.pl' -o -name '*.lds' -o -name 'Platform' \) \ + -print | cpio -pdm "$dir" + + cp -a scripts include "$dir" + + find "arch/$_karch" -name include -type d -print | while IFS='' read -r folder; do + find "$folder" -type f + done | sort -u | cpio -pdm "$dir" + + install -Dm644 "$srcdir"/build-$_flavor.$CARCH/Module.symvers \ + "$dir"/Module.symvers + + # remove unneeded things + msg "Removing documentation..." + rm -r "$dir"/Documentation + find "$dir" -type f -name '*.o' -printf 'Removing %P\n' -delete + local _karch="$(_kernelarch $CARCH | sed 's/x86_64/x86/')" + msg "Removing unneeded arch headers..." + for i in "$dir"/arch/*; do + if [ "${i##*/}" != "$_karch" ]; then + echo " ${i##*/}" + rm -r "$i" + fi + done + + mkdir -p "$subpkgdir"/lib/modules/"$_abi_release" + ln -sf /usr/src/linux-headers-"$_abi_release" \ + "$subpkgdir"/lib/modules/"$_abi_release"/build +} + +sha512sums=" +6ed2a73c2699d0810e54753715635736fc370288ad5ce95c594f2379959b0e418665cd71bc512a0273fe226fe90074d8b10d14c209080a6466498417a4fdda68 linux-6.1.tar.xz +a6c826e8c3884550fbe1dee3771b201b3395b073369f7fbfb5dc4aa5501e848b0bbc3a5ad80d932a6a50cbe0cfd579c75662f0c43ef79a38c1c6848db395c4c8 0001-powerpc-config-defang-gcc-check-for-stack-protector-.patch +55f732c01e10948b283eb1cc77ce3f1169428251075f8f5dd3100d8fda1f08b6b3850969b84b42a52468d4f7c7ec7b49d01f0dff3c20c30f2e9ac0c07071b9d3 0001-x86-Compress-vmlinux-with-zstd-19-instead-of-22.patch +5464cf4b2e251972091cc9d5138291a94298737e98ec24829829203fc879593b440a7cd1ca08e6d01debaab2887976cb5d38fae2fbe5149cef1735d5d73bb086 0001-kexec-add-kexec_load_disabled-boot-option.patch +15fca2343b57dcab247c5ad86175ae638bace41a803984170a4aca4f3ec70edf6399ce5c6de82ae0dfabd3430a7a7567941718806b6d5d56515ecc7d55b2a800 awk.patch +a1bc0109ad4b429e5189eaa3fd96891ec5769bea4cc5c6e86ce523c20ad7641fd5f937e1405680a8276c3e4e1deec3c5ae62361675decc9b2a9ff5b6f75111e0 wireguard-leak.patch +c74488940244ba032e741c370767467cfab93b89077b5dfccfed39b658f381e0995527e6c61de53b1c112b04ba647bfccf00f2e05c0637d3c703680a508821cc ppc-notext.patch +f8582538a8482656138a0ae56b67a6d3cec09ff8d82a3f790ffa3fa7c37d0ae24a04f01f0ae1aec09838f86490907995c2568be7841a3821f0e64704f60153e5 0001-tty-Move-sysctl-setup-into-core-tty-logic.patch +0bfd2c138e997f25f821cd518263ad515fc303bbde37bcdb7dd651d30316c77652fec6fedb195102b8476b92b1e7269c143dad5df6431db49c4318375ad2e802 0002-tty-Allow-TIOCSTI-to-be-disabled.patch +8b245ec672a56dae7ce82f488880f6f3c0b65776177db663a29f26de752bf2cefe6b677e2cedbebd85e98d6f6e3d66b9f198a0d7a6042a20191b5ca8f4a749e7 0003-tty-Move-TIOCSTI-toggle-variable-before-kerndoc.patch +f03a4ed754fc0bede7a4279fbe4fdc47b5945e336e41fce57f7130bf7e52995a08e33e1a881586cc5e260bf68ff5de3ed1aa1318b77ac9e50ba6895fe0d3be9d virt.aarch64.config +d4323576ffeb7605346673f3f1bfe97bc8da8d00afc272ae6017ba2b5d6e4170004fddec155c993a85f66a3d0e9abeb7da95bbae1c1c9c6cc55235db7bc6d2d3 virt.armv7.config +f16ba6d9cb8d0e5b5527c6e4df468951237ee6d9c5af432b3a25b72c167bfb83d5941c54788ca9d6610a9cc203da4aa4ebb06e20e7a1d72263b12bd0996a8197 virt.ppc64le.config +f24432150e2bf3565a04be5034cfb7e6ae91012ca8cfe97c844055db6b637a8e35cc7273cc29c3168421e8636f0160c622e85e4ba858acc4276f9de434bbadff virt.x86.config +d90e3fed0584accae7289c392d57c656aa2c03529ab1e27f6dc06a4a2e6047daf6f4319a911982a2d587c6f506d018b65c4f52e7a3b4870f2af351d4300c3f4a virt.x86_64.config +83cbff3cefebf4c7629f8dc58fcbacd1cd2bc73b277850376b6c63ff09c6036e9d464c0b84c0975e5a1fea8929fe9882f19843fec1e803517544411c4ff662ac patch-6.1.77.xz +" diff --git a/linux/noise.c b/linux/noise.c new file mode 100644 index 0000000000000000000000000000000000000000..1f9b1e0bc027e7710b4ca37891538f9dc521b5af --- /dev/null +++ b/linux/noise.c @@ -0,0 +1,929 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2015-2019 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved. + */ + +#include "noise.h" +#include "device.h" +#include "peer.h" +#include "messages.h" +#include "queueing.h" +#include "peerlookup.h" + +#include <linux/rcupdate.h> +#include <linux/slab.h> +#include <linux/bitmap.h> +#include <linux/scatterlist.h> +#include <linux/highmem.h> +#include <crypto/algapi.h> + +/* This implements Noise_IKpsk2: + * + * <- s + * ****** + * -> e, es, s, ss, {t} + * <- e, ee, se, psk, {} + */ + +void printHex(const char *varName, const u8 *ptr, size_t size) { + printk("%s = \'", varName); + for (size_t i = 0; i < size; i++) { + printk(KERN_CONT "%.2x", ptr[i]); + } + printk(KERN_CONT "\'"); +} + +static const u8 handshake_name[37] = "Noise_IKpsk2_25519_ChaChaPoly_BLAKE2s"; +static const u8 identifier_name[34] = "WireGuard v1 zx2c4 Jason@zx2c4.com"; +static u8 handshake_init_hash[NOISE_HASH_LEN] __ro_after_init; +static u8 handshake_init_chaining_key[NOISE_HASH_LEN] __ro_after_init; +static atomic64_t keypair_counter = ATOMIC64_INIT(0); + +void __init wg_noise_init(void) +{ + struct blake2s_state blake; + + blake2s(handshake_init_chaining_key, handshake_name, NULL, + NOISE_HASH_LEN, sizeof(handshake_name), 0); + blake2s_init(&blake, NOISE_HASH_LEN); + blake2s_update(&blake, handshake_init_chaining_key, NOISE_HASH_LEN); + blake2s_update(&blake, identifier_name, sizeof(identifier_name)); + blake2s_final(&blake, handshake_init_hash); +} + +/* Must hold peer->handshake.static_identity->lock */ +void wg_noise_precompute_static_static(struct wg_peer *peer) +{ + down_write(&peer->handshake.lock); + if (!peer->handshake.static_identity->has_identity || + !curve25519(peer->handshake.precomputed_static_static, + peer->handshake.static_identity->static_private, + peer->handshake.remote_static)) + memset(peer->handshake.precomputed_static_static, 0, + NOISE_PUBLIC_KEY_LEN); + up_write(&peer->handshake.lock); +} + +void wg_noise_handshake_init(struct noise_handshake *handshake, + struct noise_static_identity *static_identity, + const u8 peer_public_key[NOISE_PUBLIC_KEY_LEN], + const u8 peer_preshared_key[NOISE_SYMMETRIC_KEY_LEN], + struct wg_peer *peer) +{ + memset(handshake, 0, sizeof(*handshake)); + init_rwsem(&handshake->lock); + handshake->entry.type = INDEX_HASHTABLE_HANDSHAKE; + handshake->entry.peer = peer; + memcpy(handshake->remote_static, peer_public_key, NOISE_PUBLIC_KEY_LEN); + if (peer_preshared_key) + memcpy(handshake->preshared_key, peer_preshared_key, + NOISE_SYMMETRIC_KEY_LEN); + handshake->static_identity = static_identity; + handshake->state = HANDSHAKE_ZEROED; + wg_noise_precompute_static_static(peer); +} + +static void handshake_zero(struct noise_handshake *handshake) +{ + memset(&handshake->ephemeral_private, 0, NOISE_PUBLIC_KEY_LEN); + memset(&handshake->remote_ephemeral, 0, NOISE_PUBLIC_KEY_LEN); + memset(&handshake->hash, 0, NOISE_HASH_LEN); + memset(&handshake->chaining_key, 0, NOISE_HASH_LEN); + handshake->remote_index = 0; + handshake->state = HANDSHAKE_ZEROED; +} + +void wg_noise_handshake_clear(struct noise_handshake *handshake) +{ + down_write(&handshake->lock); + wg_index_hashtable_remove( + handshake->entry.peer->device->index_hashtable, + &handshake->entry); + handshake_zero(handshake); + up_write(&handshake->lock); +} + +static struct noise_keypair *keypair_create(struct wg_peer *peer) +{ + struct noise_keypair *keypair = kzalloc(sizeof(*keypair), GFP_KERNEL); + + if (unlikely(!keypair)) + return NULL; + spin_lock_init(&keypair->receiving_counter.lock); + keypair->internal_id = atomic64_inc_return(&keypair_counter); + keypair->entry.type = INDEX_HASHTABLE_KEYPAIR; + keypair->entry.peer = peer; + kref_init(&keypair->refcount); + return keypair; +} + +static void keypair_free_rcu(struct rcu_head *rcu) +{ + kfree_sensitive(container_of(rcu, struct noise_keypair, rcu)); +} + +static void keypair_free_kref(struct kref *kref) +{ + struct noise_keypair *keypair = + container_of(kref, struct noise_keypair, refcount); + + net_dbg_ratelimited("%s: Keypair %llu destroyed for peer %llu\n", + keypair->entry.peer->device->dev->name, + keypair->internal_id, + keypair->entry.peer->internal_id); + wg_index_hashtable_remove(keypair->entry.peer->device->index_hashtable, + &keypair->entry); + call_rcu(&keypair->rcu, keypair_free_rcu); +} + +void wg_noise_keypair_put(struct noise_keypair *keypair, bool unreference_now) +{ + if (unlikely(!keypair)) + return; + if (unlikely(unreference_now)) + wg_index_hashtable_remove( + keypair->entry.peer->device->index_hashtable, + &keypair->entry); + kref_put(&keypair->refcount, keypair_free_kref); +} + +struct noise_keypair *wg_noise_keypair_get(struct noise_keypair *keypair) +{ + RCU_LOCKDEP_WARN(!rcu_read_lock_bh_held(), + "Taking noise keypair reference without holding the RCU BH read lock"); + if (unlikely(!keypair || !kref_get_unless_zero(&keypair->refcount))) + return NULL; + return keypair; +} + +void wg_noise_keypairs_clear(struct noise_keypairs *keypairs) +{ + struct noise_keypair *old; + + spin_lock_bh(&keypairs->keypair_update_lock); + + /* We zero the next_keypair before zeroing the others, so that + * wg_noise_received_with_keypair returns early before subsequent ones + * are zeroed. + */ + old = rcu_dereference_protected(keypairs->next_keypair, + lockdep_is_held(&keypairs->keypair_update_lock)); + RCU_INIT_POINTER(keypairs->next_keypair, NULL); + wg_noise_keypair_put(old, true); + + old = rcu_dereference_protected(keypairs->previous_keypair, + lockdep_is_held(&keypairs->keypair_update_lock)); + RCU_INIT_POINTER(keypairs->previous_keypair, NULL); + wg_noise_keypair_put(old, true); + + old = rcu_dereference_protected(keypairs->current_keypair, + lockdep_is_held(&keypairs->keypair_update_lock)); + RCU_INIT_POINTER(keypairs->current_keypair, NULL); + wg_noise_keypair_put(old, true); + + spin_unlock_bh(&keypairs->keypair_update_lock); +} + +void wg_noise_expire_current_peer_keypairs(struct wg_peer *peer) +{ + struct noise_keypair *keypair; + + wg_noise_handshake_clear(&peer->handshake); + wg_noise_reset_last_sent_handshake(&peer->last_sent_handshake); + + spin_lock_bh(&peer->keypairs.keypair_update_lock); + keypair = rcu_dereference_protected(peer->keypairs.next_keypair, + lockdep_is_held(&peer->keypairs.keypair_update_lock)); + if (keypair) + keypair->sending.is_valid = false; + keypair = rcu_dereference_protected(peer->keypairs.current_keypair, + lockdep_is_held(&peer->keypairs.keypair_update_lock)); + if (keypair) + keypair->sending.is_valid = false; + spin_unlock_bh(&peer->keypairs.keypair_update_lock); +} + +static void add_new_keypair(struct noise_keypairs *keypairs, + struct noise_keypair *new_keypair) +{ + struct noise_keypair *previous_keypair, *next_keypair, *current_keypair; + + spin_lock_bh(&keypairs->keypair_update_lock); + previous_keypair = rcu_dereference_protected(keypairs->previous_keypair, + lockdep_is_held(&keypairs->keypair_update_lock)); + next_keypair = rcu_dereference_protected(keypairs->next_keypair, + lockdep_is_held(&keypairs->keypair_update_lock)); + current_keypair = rcu_dereference_protected(keypairs->current_keypair, + lockdep_is_held(&keypairs->keypair_update_lock)); + if (new_keypair->i_am_the_initiator) { + /* If we're the initiator, it means we've sent a handshake, and + * received a confirmation response, which means this new + * keypair can now be used. + */ + if (next_keypair) { + /* If there already was a next keypair pending, we + * demote it to be the previous keypair, and free the + * existing current. Note that this means KCI can result + * in this transition. It would perhaps be more sound to + * always just get rid of the unused next keypair + * instead of putting it in the previous slot, but this + * might be a bit less robust. Something to think about + * for the future. + */ + RCU_INIT_POINTER(keypairs->next_keypair, NULL); + rcu_assign_pointer(keypairs->previous_keypair, + next_keypair); + wg_noise_keypair_put(current_keypair, true); + } else /* If there wasn't an existing next keypair, we replace + * the previous with the current one. + */ + rcu_assign_pointer(keypairs->previous_keypair, + current_keypair); + /* At this point we can get rid of the old previous keypair, and + * set up the new keypair. + */ + wg_noise_keypair_put(previous_keypair, true); + rcu_assign_pointer(keypairs->current_keypair, new_keypair); + } else { + /* If we're the responder, it means we can't use the new keypair + * until we receive confirmation via the first data packet, so + * we get rid of the existing previous one, the possibly + * existing next one, and slide in the new next one. + */ + rcu_assign_pointer(keypairs->next_keypair, new_keypair); + wg_noise_keypair_put(next_keypair, true); + RCU_INIT_POINTER(keypairs->previous_keypair, NULL); + wg_noise_keypair_put(previous_keypair, true); + } + spin_unlock_bh(&keypairs->keypair_update_lock); +} + +bool wg_noise_received_with_keypair(struct noise_keypairs *keypairs, + struct noise_keypair *received_keypair) +{ + struct noise_keypair *old_keypair; + bool key_is_new; + + /* We first check without taking the spinlock. */ + key_is_new = received_keypair == + rcu_access_pointer(keypairs->next_keypair); + if (likely(!key_is_new)) + return false; + + spin_lock_bh(&keypairs->keypair_update_lock); + /* After locking, we double check that things didn't change from + * beneath us. + */ + if (unlikely(received_keypair != + rcu_dereference_protected(keypairs->next_keypair, + lockdep_is_held(&keypairs->keypair_update_lock)))) { + spin_unlock_bh(&keypairs->keypair_update_lock); + return false; + } + + /* When we've finally received the confirmation, we slide the next + * into the current, the current into the previous, and get rid of + * the old previous. + */ + old_keypair = rcu_dereference_protected(keypairs->previous_keypair, + lockdep_is_held(&keypairs->keypair_update_lock)); + rcu_assign_pointer(keypairs->previous_keypair, + rcu_dereference_protected(keypairs->current_keypair, + lockdep_is_held(&keypairs->keypair_update_lock))); + wg_noise_keypair_put(old_keypair, true); + rcu_assign_pointer(keypairs->current_keypair, received_keypair); + RCU_INIT_POINTER(keypairs->next_keypair, NULL); + + spin_unlock_bh(&keypairs->keypair_update_lock); + return true; +} + +/* Must hold static_identity->lock */ +void wg_noise_set_static_identity_private_key( + struct noise_static_identity *static_identity, + const u8 private_key[NOISE_PUBLIC_KEY_LEN]) +{ + memcpy(static_identity->static_private, private_key, + NOISE_PUBLIC_KEY_LEN); + curve25519_clamp_secret(static_identity->static_private); + static_identity->has_identity = curve25519_generate_public( + static_identity->static_public, private_key); +} + +static void hmac(u8 *out, const u8 *in, const u8 *key, const size_t inlen, const size_t keylen) +{ + struct blake2s_state state; + u8 x_key[BLAKE2S_BLOCK_SIZE] __aligned(__alignof__(u32)) = { 0 }; + u8 i_hash[BLAKE2S_HASH_SIZE] __aligned(__alignof__(u32)); + int i; + + // printHex("[HMAC] out", out, BLAKE2S_HASH_SIZE); + // printHex("[HMAC] in", in, inlen); + // printHex("[HMAC] key", key, keylen); + + if (keylen > BLAKE2S_BLOCK_SIZE) { + blake2s_init(&state, BLAKE2S_HASH_SIZE); + blake2s_update(&state, key, keylen); + blake2s_final(&state, x_key); + } else + memcpy(x_key, key, keylen); + + for (i = 0; i < BLAKE2S_BLOCK_SIZE; ++i) + x_key[i] ^= 0x36; + + blake2s_init(&state, BLAKE2S_HASH_SIZE); + blake2s_update(&state, x_key, BLAKE2S_BLOCK_SIZE); + blake2s_update(&state, in, inlen); + blake2s_final(&state, i_hash); + + for (i = 0; i < BLAKE2S_BLOCK_SIZE; ++i) + x_key[i] ^= 0x5c ^ 0x36; + + blake2s_init(&state, BLAKE2S_HASH_SIZE); + blake2s_update(&state, x_key, BLAKE2S_BLOCK_SIZE); + blake2s_update(&state, i_hash, BLAKE2S_HASH_SIZE); + blake2s_final(&state, i_hash); + + memcpy(out, i_hash, BLAKE2S_HASH_SIZE); + memzero_explicit(x_key, BLAKE2S_BLOCK_SIZE); + memzero_explicit(i_hash, BLAKE2S_HASH_SIZE); +} + +/* This is Hugo Krawczyk's HKDF: + * - https://eprint.iacr.org/2010/264.pdf + * - https://tools.ietf.org/html/rfc5869 + */ +static void kdf(u8 *first_dst, u8 *second_dst, u8 *third_dst, const u8 *data, + size_t first_len, size_t second_len, size_t third_len, + size_t data_len, const u8 chaining_key[NOISE_HASH_LEN]) +{ + u8 output[BLAKE2S_HASH_SIZE + 1]; + u8 secret[BLAKE2S_HASH_SIZE]; + + WARN_ON(IS_ENABLED(DEBUG) && + (first_len > BLAKE2S_HASH_SIZE || + second_len > BLAKE2S_HASH_SIZE || + third_len > BLAKE2S_HASH_SIZE || + ((second_len || second_dst || third_len || third_dst) && + (!first_len || !first_dst)) || + ((third_len || third_dst) && (!second_len || !second_dst)))); + + /* Extract entropy from data into secret */ + hmac(secret, data, chaining_key, data_len, NOISE_HASH_LEN); + //printHex("[KDF] secret", secret, BLAKE2S_HASH_SIZE); + + if (!first_dst || !first_len) + goto out; + + /* Expand first key: key = secret, data = 0x1 */ + output[0] = 1; + hmac(output, output, secret, 1, BLAKE2S_HASH_SIZE); + //printHex("[KDF] output", output, BLAKE2S_HASH_SIZE+1); + memcpy(first_dst, output, first_len); + + if (!second_dst || !second_len) + goto out; + + /* Expand second key: key = secret, data = first-key || 0x2 */ + output[BLAKE2S_HASH_SIZE] = 2; + hmac(output, output, secret, BLAKE2S_HASH_SIZE + 1, BLAKE2S_HASH_SIZE); + //printHex("[KDF] output2", output, BLAKE2S_HASH_SIZE+1); + memcpy(second_dst, output, second_len); + + if (!third_dst || !third_len) + goto out; + + /* Expand third key: key = secret, data = second-key || 0x3 */ + output[BLAKE2S_HASH_SIZE] = 3; + hmac(output, output, secret, BLAKE2S_HASH_SIZE + 1, BLAKE2S_HASH_SIZE); + memcpy(third_dst, output, third_len); + +out: + /* Clear sensitive data from stack */ + memzero_explicit(secret, BLAKE2S_HASH_SIZE); + memzero_explicit(output, BLAKE2S_HASH_SIZE + 1); +} + +static void derive_keys(struct noise_symmetric_key *first_dst, + struct noise_symmetric_key *second_dst, + const u8 chaining_key[NOISE_HASH_LEN]) +{ + u64 birthdate = ktime_get_coarse_boottime_ns(); + const u8 absurd_value = 0x55; + kdf(first_dst->key, second_dst->key, NULL, &absurd_value, + NOISE_SYMMETRIC_KEY_LEN, NOISE_SYMMETRIC_KEY_LEN, 0, 1, + chaining_key); + first_dst->birthdate = second_dst->birthdate = birthdate; + first_dst->is_valid = second_dst->is_valid = true; +} + +static bool __must_check mix_dh(u8 chaining_key[NOISE_HASH_LEN], + u8 key[NOISE_SYMMETRIC_KEY_LEN], + const u8 private[NOISE_PUBLIC_KEY_LEN], + const u8 public[NOISE_PUBLIC_KEY_LEN]) +{ + u8 dh_calculation[NOISE_PUBLIC_KEY_LEN]; + + if (unlikely(!curve25519(dh_calculation, private, public))) + return false; + // printHex("dh_calculation", dh_calculation, NOISE_PUBLIC_KEY_LEN); + // printHex("private", private, NOISE_PUBLIC_KEY_LEN); + // printHex("public", public, NOISE_PUBLIC_KEY_LEN); + kdf(chaining_key, key, NULL, dh_calculation, NOISE_HASH_LEN, + NOISE_SYMMETRIC_KEY_LEN, 0, NOISE_PUBLIC_KEY_LEN, chaining_key); + memzero_explicit(dh_calculation, NOISE_PUBLIC_KEY_LEN); + return true; +} + +static bool __must_check mix_precomputed_dh(u8 chaining_key[NOISE_HASH_LEN], + u8 key[NOISE_SYMMETRIC_KEY_LEN], + const u8 precomputed[NOISE_PUBLIC_KEY_LEN]) +{ + static u8 zero_point[NOISE_PUBLIC_KEY_LEN]; + if (unlikely(!crypto_memneq(precomputed, zero_point, NOISE_PUBLIC_KEY_LEN))) + return false; + printHex("precomputed", precomputed, NOISE_PUBLIC_KEY_LEN); + // printHex("private", private, NOISE_PUBLIC_KEY_LEN); + // printHex("public", public, NOISE_PUBLIC_KEY_LEN); + kdf(chaining_key, key, NULL, precomputed, NOISE_HASH_LEN, + NOISE_SYMMETRIC_KEY_LEN, 0, NOISE_PUBLIC_KEY_LEN, + chaining_key); + return true; +} + +static void mix_hash(u8 hash[NOISE_HASH_LEN], const u8 *src, size_t src_len) +{ + struct blake2s_state blake; + + blake2s_init(&blake, NOISE_HASH_LEN); + blake2s_update(&blake, hash, NOISE_HASH_LEN); + blake2s_update(&blake, src, src_len); + blake2s_final(&blake, hash); +} + +static void mix_psk(u8 chaining_key[NOISE_HASH_LEN], u8 hash[NOISE_HASH_LEN], + u8 key[NOISE_SYMMETRIC_KEY_LEN], + const u8 psk[NOISE_SYMMETRIC_KEY_LEN]) +{ + u8 temp_hash[NOISE_HASH_LEN]; + + kdf(chaining_key, temp_hash, key, psk, NOISE_HASH_LEN, NOISE_HASH_LEN, + NOISE_SYMMETRIC_KEY_LEN, NOISE_SYMMETRIC_KEY_LEN, chaining_key); + mix_hash(hash, temp_hash, NOISE_HASH_LEN); + memzero_explicit(temp_hash, NOISE_HASH_LEN); +} + +static void handshake_init(u8 chaining_key[NOISE_HASH_LEN], + u8 hash[NOISE_HASH_LEN], + const u8 remote_static[NOISE_PUBLIC_KEY_LEN]) +{ + memcpy(hash, handshake_init_hash, NOISE_HASH_LEN); + // printHex("Hi", handshake_init_hash, NOISE_HASH_LEN); + memcpy(chaining_key, handshake_init_chaining_key, NOISE_HASH_LEN); + mix_hash(hash, remote_static, NOISE_PUBLIC_KEY_LEN); +} + +static void message_encrypt(u8 *dst_ciphertext, const u8 *src_plaintext, + size_t src_len, u8 key[NOISE_SYMMETRIC_KEY_LEN], + u8 hash[NOISE_HASH_LEN]) +{ + chacha20poly1305_encrypt(dst_ciphertext, src_plaintext, src_len, hash, + NOISE_HASH_LEN, + 0 /* Always zero for Noise_IK */, key); + // printHex("dst_ciphertext", dst_ciphertext, noise_encrypted_len(src_len)); + mix_hash(hash, dst_ciphertext, noise_encrypted_len(src_len)); +} + +static bool message_decrypt(u8 *dst_plaintext, const u8 *src_ciphertext, + size_t src_len, u8 key[NOISE_SYMMETRIC_KEY_LEN], + u8 hash[NOISE_HASH_LEN]) +{ + if (!chacha20poly1305_decrypt(dst_plaintext, src_ciphertext, src_len, + hash, NOISE_HASH_LEN, + 0 /* Always zero for Noise_IK */, key)) + return false; + mix_hash(hash, src_ciphertext, src_len); + return true; +} + +static void message_ephemeral(u8 ephemeral_dst[NOISE_PUBLIC_KEY_LEN], + const u8 ephemeral_src[NOISE_PUBLIC_KEY_LEN], + u8 chaining_key[NOISE_HASH_LEN], + u8 hash[NOISE_HASH_LEN]) +{ + if (ephemeral_dst != ephemeral_src) + memcpy(ephemeral_dst, ephemeral_src, NOISE_PUBLIC_KEY_LEN); + mix_hash(hash, ephemeral_src, NOISE_PUBLIC_KEY_LEN); + kdf(chaining_key, NULL, NULL, ephemeral_src, NOISE_HASH_LEN, 0, 0, + NOISE_PUBLIC_KEY_LEN, chaining_key); +} + +static void tai64n_now(u8 output[NOISE_TIMESTAMP_LEN]) +{ + struct timespec64 now; + + ktime_get_real_ts64(&now); + + /* In order to prevent some sort of infoleak from precise timers, we + * round down the nanoseconds part to the closest rounded-down power of + * two to the maximum initiations per second allowed anyway by the + * implementation. + */ + now.tv_nsec = ALIGN_DOWN(now.tv_nsec, + rounddown_pow_of_two(NSEC_PER_SEC / INITIATIONS_PER_SECOND)); + + /* https://cr.yp.to/libtai/tai64.html */ + *(__be64 *)output = cpu_to_be64(0x400000000000000aULL + now.tv_sec); + *(__be32 *)(output + sizeof(__be64)) = cpu_to_be32(now.tv_nsec); +} + +bool +wg_noise_handshake_create_initiation(struct message_handshake_initiation *dst, + struct noise_handshake *handshake) +{ + u8 timestamp[NOISE_TIMESTAMP_LEN]; + u8 key[NOISE_SYMMETRIC_KEY_LEN]; + bool ret = false; + + /* We need to wait for crng _before_ taking any locks, since + * curve25519_generate_secret uses get_random_bytes_wait. + */ + wait_for_random_bytes(); + + down_read(&handshake->static_identity->lock); + down_write(&handshake->lock); + + if (unlikely(!handshake->static_identity->has_identity)) + goto out; + + dst->header.type = cpu_to_le32(MESSAGE_HANDSHAKE_INITIATION); + + handshake_init(handshake->chaining_key, handshake->hash, + handshake->remote_static); + // printHex("C_i", handshake->chaining_key, NOISE_HASH_LEN); + // printHex("H_i", handshake->hash, NOISE_HASH_LEN); + + /* e */ + curve25519_generate_secret(handshake->ephemeral_private); + if (!curve25519_generate_public(dst->unencrypted_ephemeral, + handshake->ephemeral_private)) + goto out; + printHex("E^priv_i", handshake->ephemeral_private, NOISE_PUBLIC_KEY_LEN); + // printHex("E^pub_i", dst->unencrypted_ephemeral, NOISE_PUBLIC_KEY_LEN); + + message_ephemeral(dst->unencrypted_ephemeral, + dst->unencrypted_ephemeral, handshake->chaining_key, + handshake->hash); + // printHex("dst->unencrypted_ephemeral", dst->unencrypted_ephemeral, NOISE_PUBLIC_KEY_LEN); + // printHex("handshake->chaining_key", handshake->chaining_key, NOISE_HASH_LEN); + // printHex("handshake->hash", handshake->hash, NOISE_HASH_LEN); + + /* es */ + if (!mix_dh(handshake->chaining_key, key, handshake->ephemeral_private, + handshake->remote_static)) + goto out; + // printHex("key", key, NOISE_SYMMETRIC_KEY_LEN); + // printHex("handshake->chaining_key", handshake->chaining_key, NOISE_HASH_LEN); + + /* s */ + message_encrypt(dst->encrypted_static, + handshake->static_identity->static_public, + NOISE_PUBLIC_KEY_LEN, key, handshake->hash); + // printHex("dst->encrypted_static", dst->encrypted_static, (size_t)(noise_encrypted_len(dst->encrypted_static))); + // printHex("handshake->hash", handshake->hash, NOISE_HASH_LEN); + + /* ss */ + if (!mix_precomputed_dh(handshake->chaining_key, key, + handshake->precomputed_static_static)) + goto out; + // printHex("key", key, NOISE_SYMMETRIC_KEY_LEN); + // printHex("handshake->chaining_key", handshake->chaining_key, NOISE_HASH_LEN); + + /* {t} */ + tai64n_now(timestamp); + message_encrypt(dst->encrypted_timestamp, timestamp, + NOISE_TIMESTAMP_LEN, key, handshake->hash); + // printHex("timestamp", timestamp, NOISE_TIMESTAMP_LEN); + // printHex("dst->encrypted_timestamp", dst->encrypted_timestamp, NOISE_TIMESTAMP_LEN); + // printHex("handshake->hash", handshake->hash, NOISE_HASH_LEN); + + dst->sender_index = wg_index_hashtable_insert( + handshake->entry.peer->device->index_hashtable, + &handshake->entry); + + handshake->state = HANDSHAKE_CREATED_INITIATION; + ret = true; + +out: + up_write(&handshake->lock); + up_read(&handshake->static_identity->lock); + memzero_explicit(key, NOISE_SYMMETRIC_KEY_LEN); + return ret; +} + +struct wg_peer * +wg_noise_handshake_consume_initiation(struct message_handshake_initiation *src, + struct wg_device *wg) +{ + struct wg_peer *peer = NULL, *ret_peer = NULL; + struct noise_handshake *handshake; + bool replay_attack, flood_attack; + u8 key[NOISE_SYMMETRIC_KEY_LEN]; + u8 chaining_key[NOISE_HASH_LEN]; + u8 hash[NOISE_HASH_LEN]; + u8 s[NOISE_PUBLIC_KEY_LEN]; + u8 e[NOISE_PUBLIC_KEY_LEN]; + u8 t[NOISE_TIMESTAMP_LEN]; + u64 initiation_consumption; + + down_read(&wg->static_identity.lock); + if (unlikely(!wg->static_identity.has_identity)) + goto out; + + handshake_init(chaining_key, hash, wg->static_identity.static_public); + // printHex("C_i", handshake->chaining_key, NOISE_HASH_LEN); + // printHex("H_i", handshake->hash, NOISE_HASH_LEN); + + /* e */ + message_ephemeral(e, src->unencrypted_ephemeral, chaining_key, hash); + // printHex("dst->unencrypted_ephemeral", src->unencrypted_ephemeral, NOISE_PUBLIC_KEY_LEN); + // printHex("chaining_key", chaining_key, NOISE_HASH_LEN); + // printHex("hash", hash, NOISE_HASH_LEN); + + /* es */ + if (!mix_dh(chaining_key, key, wg->static_identity.static_private, e)) + goto out; + // printHex("key", key, NOISE_SYMMETRIC_KEY_LEN); + // printHex("chaining_key", chaining_key, NOISE_HASH_LEN); + // printHex("E", e, NOISE_PUBLIC_KEY_LEN); + // printHex("wg->static_identity.static_private", wg->static_identity.static_private, NOISE_PUBLIC_KEY_LEN); + + /* s */ + if (!message_decrypt(s, src->encrypted_static, + sizeof(src->encrypted_static), key, hash)) + goto out; + // printHex("hash", hash, NOISE_HASH_LEN); + + /* Lookup which peer we're actually talking to */ + peer = wg_pubkey_hashtable_lookup(wg->peer_hashtable, s); + if (!peer) + goto out; + handshake = &peer->handshake; + + /* ss */ + if (!mix_precomputed_dh(chaining_key, key, + handshake->precomputed_static_static)) + goto out; + + /* {t} */ + if (!message_decrypt(t, src->encrypted_timestamp, + sizeof(src->encrypted_timestamp), key, hash)) + goto out; + + down_read(&handshake->lock); + replay_attack = memcmp(t, handshake->latest_timestamp, + NOISE_TIMESTAMP_LEN) <= 0; + flood_attack = (s64)handshake->last_initiation_consumption + + NSEC_PER_SEC / INITIATIONS_PER_SECOND > + (s64)ktime_get_coarse_boottime_ns(); + up_read(&handshake->lock); + if (replay_attack || flood_attack) + goto out; + + /* Success! Copy everything to peer */ + down_write(&handshake->lock); + memcpy(handshake->remote_ephemeral, e, NOISE_PUBLIC_KEY_LEN); + if (memcmp(t, handshake->latest_timestamp, NOISE_TIMESTAMP_LEN) > 0) + memcpy(handshake->latest_timestamp, t, NOISE_TIMESTAMP_LEN); + memcpy(handshake->hash, hash, NOISE_HASH_LEN); + memcpy(handshake->chaining_key, chaining_key, NOISE_HASH_LEN); + handshake->remote_index = src->sender_index; + initiation_consumption = ktime_get_coarse_boottime_ns(); + if ((s64)(handshake->last_initiation_consumption - initiation_consumption) < 0) + handshake->last_initiation_consumption = initiation_consumption; + handshake->state = HANDSHAKE_CONSUMED_INITIATION; + up_write(&handshake->lock); + ret_peer = peer; + +out: + memzero_explicit(key, NOISE_SYMMETRIC_KEY_LEN); + memzero_explicit(hash, NOISE_HASH_LEN); + memzero_explicit(chaining_key, NOISE_HASH_LEN); + up_read(&wg->static_identity.lock); + if (!ret_peer) + wg_peer_put(peer); + return ret_peer; +} + +bool wg_noise_handshake_create_response(struct message_handshake_response *dst, + struct noise_handshake *handshake) +{ + u8 key[NOISE_SYMMETRIC_KEY_LEN]; + bool ret = false; + + /* We need to wait for crng _before_ taking any locks, since + * curve25519_generate_secret uses get_random_bytes_wait. + */ + wait_for_random_bytes(); + + down_read(&handshake->static_identity->lock); + down_write(&handshake->lock); + + if (handshake->state != HANDSHAKE_CONSUMED_INITIATION) + goto out; + + dst->header.type = cpu_to_le32(MESSAGE_HANDSHAKE_RESPONSE); + dst->receiver_index = handshake->remote_index; + + /* e */ + curve25519_generate_secret(handshake->ephemeral_private); + if (!curve25519_generate_public(dst->unencrypted_ephemeral, + handshake->ephemeral_private)) + goto out; + + message_ephemeral(dst->unencrypted_ephemeral, + dst->unencrypted_ephemeral, handshake->chaining_key, + handshake->hash); + + /* ee */ + if (!mix_dh(handshake->chaining_key, NULL, handshake->ephemeral_private, + handshake->remote_ephemeral)) + goto out; + + /* se */ + if (!mix_dh(handshake->chaining_key, NULL, handshake->ephemeral_private, + handshake->remote_static)) + goto out; + + /* psk */ + mix_psk(handshake->chaining_key, handshake->hash, key, + handshake->preshared_key); + + /* {} */ + message_encrypt(dst->encrypted_nothing, NULL, 0, key, handshake->hash); + + dst->sender_index = wg_index_hashtable_insert( + handshake->entry.peer->device->index_hashtable, + &handshake->entry); + + handshake->state = HANDSHAKE_CREATED_RESPONSE; + ret = true; + +out: + up_write(&handshake->lock); + up_read(&handshake->static_identity->lock); + memzero_explicit(key, NOISE_SYMMETRIC_KEY_LEN); + return ret; +} + +struct wg_peer * +wg_noise_handshake_consume_response(struct message_handshake_response *src, + struct wg_device *wg) +{ + enum noise_handshake_state state = HANDSHAKE_ZEROED; + struct wg_peer *peer = NULL, *ret_peer = NULL; + struct noise_handshake *handshake; + u8 key[NOISE_SYMMETRIC_KEY_LEN]; + u8 hash[NOISE_HASH_LEN]; + u8 chaining_key[NOISE_HASH_LEN]; + u8 e[NOISE_PUBLIC_KEY_LEN]; + u8 ephemeral_private[NOISE_PUBLIC_KEY_LEN]; + u8 static_private[NOISE_PUBLIC_KEY_LEN]; + u8 preshared_key[NOISE_SYMMETRIC_KEY_LEN]; + + down_read(&wg->static_identity.lock); + + if (unlikely(!wg->static_identity.has_identity)) + goto out; + + handshake = (struct noise_handshake *)wg_index_hashtable_lookup( + wg->index_hashtable, INDEX_HASHTABLE_HANDSHAKE, + src->receiver_index, &peer); + if (unlikely(!handshake)) + goto out; + + down_read(&handshake->lock); + state = handshake->state; + memcpy(hash, handshake->hash, NOISE_HASH_LEN); + memcpy(chaining_key, handshake->chaining_key, NOISE_HASH_LEN); + memcpy(ephemeral_private, handshake->ephemeral_private, + NOISE_PUBLIC_KEY_LEN); + memcpy(preshared_key, handshake->preshared_key, + NOISE_SYMMETRIC_KEY_LEN); + up_read(&handshake->lock); + + if (state != HANDSHAKE_CREATED_INITIATION) + goto fail; + + /* e */ + message_ephemeral(e, src->unencrypted_ephemeral, chaining_key, hash); + // printHex("src->unencrypted_ephemeral", src->unencrypted_ephemeral, NOISE_PUBLIC_KEY_LEN); + // printHex("chaining_key", chaining_key, NOISE_HASH_LEN); + // printHex("hash", hash, NOISE_HASH_LEN); + + /* ee */ + if (!mix_dh(chaining_key, NULL, ephemeral_private, e)) + goto fail; + // printHex("ephemeral_private", ephemeral_private, NOISE_PUBLIC_KEY_LEN); + // printHex("e", e, NOISE_PUBLIC_KEY_LEN); + // printHex("chaining_key", chaining_key, NOISE_HASH_LEN); + + /* se */ + if (!mix_dh(chaining_key, NULL, wg->static_identity.static_private, e)) + goto fail; + // printHex("wg->static_identity.static_private", wg->static_identity.static_private, NOISE_PUBLIC_KEY_LEN); + // printHex("e", e, NOISE_PUBLIC_KEY_LEN); + // printHex("chaining_key", chaining_key, NOISE_HASH_LEN); + + /* psk */ + mix_psk(chaining_key, hash, key, preshared_key); + // printHex("chaining_key", chaining_key, NOISE_HASH_LEN); + // printHex("key", key, NOISE_SYMMETRIC_KEY_LEN); + // printHex("hash", hash, NOISE_HASH_LEN); + + /* {} */ + if (!message_decrypt(NULL, src->encrypted_nothing, + sizeof(src->encrypted_nothing), key, hash)) + goto fail; + + /* Success! Copy everything to peer */ + down_write(&handshake->lock); + /* It's important to check that the state is still the same, while we + * have an exclusive lock. + */ + if (handshake->state != state) { + up_write(&handshake->lock); + goto fail; + } + memcpy(handshake->remote_ephemeral, e, NOISE_PUBLIC_KEY_LEN); + memcpy(handshake->hash, hash, NOISE_HASH_LEN); + memcpy(handshake->chaining_key, chaining_key, NOISE_HASH_LEN); + handshake->remote_index = src->sender_index; + handshake->state = HANDSHAKE_CONSUMED_RESPONSE; + up_write(&handshake->lock); + ret_peer = peer; + goto out; + +fail: + wg_peer_put(peer); +out: + memzero_explicit(key, NOISE_SYMMETRIC_KEY_LEN); + memzero_explicit(hash, NOISE_HASH_LEN); + memzero_explicit(chaining_key, NOISE_HASH_LEN); + memzero_explicit(ephemeral_private, NOISE_PUBLIC_KEY_LEN); + memzero_explicit(static_private, NOISE_PUBLIC_KEY_LEN); + memzero_explicit(preshared_key, NOISE_SYMMETRIC_KEY_LEN); + up_read(&wg->static_identity.lock); + return ret_peer; +} + +bool wg_noise_handshake_begin_session(struct noise_handshake *handshake, + struct noise_keypairs *keypairs) +{ + struct noise_keypair *new_keypair; + bool ret = false; + + down_write(&handshake->lock); + if (handshake->state != HANDSHAKE_CREATED_RESPONSE && + handshake->state != HANDSHAKE_CONSUMED_RESPONSE) + goto out; + + new_keypair = keypair_create(handshake->entry.peer); + if (!new_keypair) + goto out; + new_keypair->i_am_the_initiator = handshake->state == + HANDSHAKE_CONSUMED_RESPONSE; + new_keypair->remote_index = handshake->remote_index; + + printk("Before derive"); + if (new_keypair->i_am_the_initiator) + derive_keys(&new_keypair->sending, &new_keypair->receiving, + handshake->chaining_key); + else + derive_keys(&new_keypair->receiving, &new_keypair->sending, + handshake->chaining_key); + + printHex("new_keypair->sending", new_keypair->sending.key, NOISE_SYMMETRIC_KEY_LEN); + printHex("new_keypair->receiving", new_keypair->receiving.key, NOISE_SYMMETRIC_KEY_LEN); + + handshake_zero(handshake); + rcu_read_lock_bh(); + if (likely(!READ_ONCE(container_of(handshake, struct wg_peer, + handshake)->is_dead))) { + add_new_keypair(keypairs, new_keypair); + net_dbg_ratelimited("%s: Keypair %llu created for peer %llu\n", + handshake->entry.peer->device->dev->name, + new_keypair->internal_id, + handshake->entry.peer->internal_id); + ret = wg_index_hashtable_replace( + handshake->entry.peer->device->index_hashtable, + &handshake->entry, &new_keypair->entry); + } else { + kfree_sensitive(new_keypair); + } + rcu_read_unlock_bh(); + +out: + up_write(&handshake->lock); + return ret; +} diff --git a/linux/noise.c.original b/linux/noise.c.original new file mode 100644 index 0000000000000000000000000000000000000000..202a33af5a721f2216ad0815e7b63a28d2bf5888 --- /dev/null +++ b/linux/noise.c.original @@ -0,0 +1,861 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2015-2019 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved. + */ + +#include "noise.h" +#include "device.h" +#include "peer.h" +#include "messages.h" +#include "queueing.h" +#include "peerlookup.h" + +#include <linux/rcupdate.h> +#include <linux/slab.h> +#include <linux/bitmap.h> +#include <linux/scatterlist.h> +#include <linux/highmem.h> +#include <crypto/utils.h> + +/* This implements Noise_IKpsk2: + * + * <- s + * ****** + * -> e, es, s, ss, {t} + * <- e, ee, se, psk, {} + */ + +static const u8 handshake_name[37] = "Noise_IKpsk2_25519_ChaChaPoly_BLAKE2s"; +static const u8 identifier_name[34] = "WireGuard v1 zx2c4 Jason@zx2c4.com"; +static u8 handshake_init_hash[NOISE_HASH_LEN] __ro_after_init; +static u8 handshake_init_chaining_key[NOISE_HASH_LEN] __ro_after_init; +static atomic64_t keypair_counter = ATOMIC64_INIT(0); + +void __init wg_noise_init(void) +{ + struct blake2s_state blake; + + blake2s(handshake_init_chaining_key, handshake_name, NULL, + NOISE_HASH_LEN, sizeof(handshake_name), 0); + blake2s_init(&blake, NOISE_HASH_LEN); + blake2s_update(&blake, handshake_init_chaining_key, NOISE_HASH_LEN); + blake2s_update(&blake, identifier_name, sizeof(identifier_name)); + blake2s_final(&blake, handshake_init_hash); +} + +/* Must hold peer->handshake.static_identity->lock */ +void wg_noise_precompute_static_static(struct wg_peer *peer) +{ + down_write(&peer->handshake.lock); + if (!peer->handshake.static_identity->has_identity || + !curve25519(peer->handshake.precomputed_static_static, + peer->handshake.static_identity->static_private, + peer->handshake.remote_static)) + memset(peer->handshake.precomputed_static_static, 0, + NOISE_PUBLIC_KEY_LEN); + up_write(&peer->handshake.lock); +} + +void wg_noise_handshake_init(struct noise_handshake *handshake, + struct noise_static_identity *static_identity, + const u8 peer_public_key[NOISE_PUBLIC_KEY_LEN], + const u8 peer_preshared_key[NOISE_SYMMETRIC_KEY_LEN], + struct wg_peer *peer) +{ + memset(handshake, 0, sizeof(*handshake)); + init_rwsem(&handshake->lock); + handshake->entry.type = INDEX_HASHTABLE_HANDSHAKE; + handshake->entry.peer = peer; + memcpy(handshake->remote_static, peer_public_key, NOISE_PUBLIC_KEY_LEN); + if (peer_preshared_key) + memcpy(handshake->preshared_key, peer_preshared_key, + NOISE_SYMMETRIC_KEY_LEN); + handshake->static_identity = static_identity; + handshake->state = HANDSHAKE_ZEROED; + wg_noise_precompute_static_static(peer); +} + +static void handshake_zero(struct noise_handshake *handshake) +{ + memset(&handshake->ephemeral_private, 0, NOISE_PUBLIC_KEY_LEN); + memset(&handshake->remote_ephemeral, 0, NOISE_PUBLIC_KEY_LEN); + memset(&handshake->hash, 0, NOISE_HASH_LEN); + memset(&handshake->chaining_key, 0, NOISE_HASH_LEN); + handshake->remote_index = 0; + handshake->state = HANDSHAKE_ZEROED; +} + +void wg_noise_handshake_clear(struct noise_handshake *handshake) +{ + down_write(&handshake->lock); + wg_index_hashtable_remove( + handshake->entry.peer->device->index_hashtable, + &handshake->entry); + handshake_zero(handshake); + up_write(&handshake->lock); +} + +static struct noise_keypair *keypair_create(struct wg_peer *peer) +{ + struct noise_keypair *keypair = kzalloc(sizeof(*keypair), GFP_KERNEL); + + if (unlikely(!keypair)) + return NULL; + spin_lock_init(&keypair->receiving_counter.lock); + keypair->internal_id = atomic64_inc_return(&keypair_counter); + keypair->entry.type = INDEX_HASHTABLE_KEYPAIR; + keypair->entry.peer = peer; + kref_init(&keypair->refcount); + return keypair; +} + +static void keypair_free_rcu(struct rcu_head *rcu) +{ + kfree_sensitive(container_of(rcu, struct noise_keypair, rcu)); +} + +static void keypair_free_kref(struct kref *kref) +{ + struct noise_keypair *keypair = + container_of(kref, struct noise_keypair, refcount); + + net_dbg_ratelimited("%s: Keypair %llu destroyed for peer %llu\n", + keypair->entry.peer->device->dev->name, + keypair->internal_id, + keypair->entry.peer->internal_id); + wg_index_hashtable_remove(keypair->entry.peer->device->index_hashtable, + &keypair->entry); + call_rcu(&keypair->rcu, keypair_free_rcu); +} + +void wg_noise_keypair_put(struct noise_keypair *keypair, bool unreference_now) +{ + if (unlikely(!keypair)) + return; + if (unlikely(unreference_now)) + wg_index_hashtable_remove( + keypair->entry.peer->device->index_hashtable, + &keypair->entry); + kref_put(&keypair->refcount, keypair_free_kref); +} + +struct noise_keypair *wg_noise_keypair_get(struct noise_keypair *keypair) +{ + RCU_LOCKDEP_WARN(!rcu_read_lock_bh_held(), + "Taking noise keypair reference without holding the RCU BH read lock"); + if (unlikely(!keypair || !kref_get_unless_zero(&keypair->refcount))) + return NULL; + return keypair; +} + +void wg_noise_keypairs_clear(struct noise_keypairs *keypairs) +{ + struct noise_keypair *old; + + spin_lock_bh(&keypairs->keypair_update_lock); + + /* We zero the next_keypair before zeroing the others, so that + * wg_noise_received_with_keypair returns early before subsequent ones + * are zeroed. + */ + old = rcu_dereference_protected(keypairs->next_keypair, + lockdep_is_held(&keypairs->keypair_update_lock)); + RCU_INIT_POINTER(keypairs->next_keypair, NULL); + wg_noise_keypair_put(old, true); + + old = rcu_dereference_protected(keypairs->previous_keypair, + lockdep_is_held(&keypairs->keypair_update_lock)); + RCU_INIT_POINTER(keypairs->previous_keypair, NULL); + wg_noise_keypair_put(old, true); + + old = rcu_dereference_protected(keypairs->current_keypair, + lockdep_is_held(&keypairs->keypair_update_lock)); + RCU_INIT_POINTER(keypairs->current_keypair, NULL); + wg_noise_keypair_put(old, true); + + spin_unlock_bh(&keypairs->keypair_update_lock); +} + +void wg_noise_expire_current_peer_keypairs(struct wg_peer *peer) +{ + struct noise_keypair *keypair; + + wg_noise_handshake_clear(&peer->handshake); + wg_noise_reset_last_sent_handshake(&peer->last_sent_handshake); + + spin_lock_bh(&peer->keypairs.keypair_update_lock); + keypair = rcu_dereference_protected(peer->keypairs.next_keypair, + lockdep_is_held(&peer->keypairs.keypair_update_lock)); + if (keypair) + keypair->sending.is_valid = false; + keypair = rcu_dereference_protected(peer->keypairs.current_keypair, + lockdep_is_held(&peer->keypairs.keypair_update_lock)); + if (keypair) + keypair->sending.is_valid = false; + spin_unlock_bh(&peer->keypairs.keypair_update_lock); +} + +static void add_new_keypair(struct noise_keypairs *keypairs, + struct noise_keypair *new_keypair) +{ + struct noise_keypair *previous_keypair, *next_keypair, *current_keypair; + + spin_lock_bh(&keypairs->keypair_update_lock); + previous_keypair = rcu_dereference_protected(keypairs->previous_keypair, + lockdep_is_held(&keypairs->keypair_update_lock)); + next_keypair = rcu_dereference_protected(keypairs->next_keypair, + lockdep_is_held(&keypairs->keypair_update_lock)); + current_keypair = rcu_dereference_protected(keypairs->current_keypair, + lockdep_is_held(&keypairs->keypair_update_lock)); + if (new_keypair->i_am_the_initiator) { + /* If we're the initiator, it means we've sent a handshake, and + * received a confirmation response, which means this new + * keypair can now be used. + */ + if (next_keypair) { + /* If there already was a next keypair pending, we + * demote it to be the previous keypair, and free the + * existing current. Note that this means KCI can result + * in this transition. It would perhaps be more sound to + * always just get rid of the unused next keypair + * instead of putting it in the previous slot, but this + * might be a bit less robust. Something to think about + * for the future. + */ + RCU_INIT_POINTER(keypairs->next_keypair, NULL); + rcu_assign_pointer(keypairs->previous_keypair, + next_keypair); + wg_noise_keypair_put(current_keypair, true); + } else /* If there wasn't an existing next keypair, we replace + * the previous with the current one. + */ + rcu_assign_pointer(keypairs->previous_keypair, + current_keypair); + /* At this point we can get rid of the old previous keypair, and + * set up the new keypair. + */ + wg_noise_keypair_put(previous_keypair, true); + rcu_assign_pointer(keypairs->current_keypair, new_keypair); + } else { + /* If we're the responder, it means we can't use the new keypair + * until we receive confirmation via the first data packet, so + * we get rid of the existing previous one, the possibly + * existing next one, and slide in the new next one. + */ + rcu_assign_pointer(keypairs->next_keypair, new_keypair); + wg_noise_keypair_put(next_keypair, true); + RCU_INIT_POINTER(keypairs->previous_keypair, NULL); + wg_noise_keypair_put(previous_keypair, true); + } + spin_unlock_bh(&keypairs->keypair_update_lock); +} + +bool wg_noise_received_with_keypair(struct noise_keypairs *keypairs, + struct noise_keypair *received_keypair) +{ + struct noise_keypair *old_keypair; + bool key_is_new; + + /* We first check without taking the spinlock. */ + key_is_new = received_keypair == + rcu_access_pointer(keypairs->next_keypair); + if (likely(!key_is_new)) + return false; + + spin_lock_bh(&keypairs->keypair_update_lock); + /* After locking, we double check that things didn't change from + * beneath us. + */ + if (unlikely(received_keypair != + rcu_dereference_protected(keypairs->next_keypair, + lockdep_is_held(&keypairs->keypair_update_lock)))) { + spin_unlock_bh(&keypairs->keypair_update_lock); + return false; + } + + /* When we've finally received the confirmation, we slide the next + * into the current, the current into the previous, and get rid of + * the old previous. + */ + old_keypair = rcu_dereference_protected(keypairs->previous_keypair, + lockdep_is_held(&keypairs->keypair_update_lock)); + rcu_assign_pointer(keypairs->previous_keypair, + rcu_dereference_protected(keypairs->current_keypair, + lockdep_is_held(&keypairs->keypair_update_lock))); + wg_noise_keypair_put(old_keypair, true); + rcu_assign_pointer(keypairs->current_keypair, received_keypair); + RCU_INIT_POINTER(keypairs->next_keypair, NULL); + + spin_unlock_bh(&keypairs->keypair_update_lock); + return true; +} + +/* Must hold static_identity->lock */ +void wg_noise_set_static_identity_private_key( + struct noise_static_identity *static_identity, + const u8 private_key[NOISE_PUBLIC_KEY_LEN]) +{ + memcpy(static_identity->static_private, private_key, + NOISE_PUBLIC_KEY_LEN); + curve25519_clamp_secret(static_identity->static_private); + static_identity->has_identity = curve25519_generate_public( + static_identity->static_public, private_key); +} + +static void hmac(u8 *out, const u8 *in, const u8 *key, const size_t inlen, const size_t keylen) +{ + struct blake2s_state state; + u8 x_key[BLAKE2S_BLOCK_SIZE] __aligned(__alignof__(u32)) = { 0 }; + u8 i_hash[BLAKE2S_HASH_SIZE] __aligned(__alignof__(u32)); + int i; + + if (keylen > BLAKE2S_BLOCK_SIZE) { + blake2s_init(&state, BLAKE2S_HASH_SIZE); + blake2s_update(&state, key, keylen); + blake2s_final(&state, x_key); + } else + memcpy(x_key, key, keylen); + + for (i = 0; i < BLAKE2S_BLOCK_SIZE; ++i) + x_key[i] ^= 0x36; + + blake2s_init(&state, BLAKE2S_HASH_SIZE); + blake2s_update(&state, x_key, BLAKE2S_BLOCK_SIZE); + blake2s_update(&state, in, inlen); + blake2s_final(&state, i_hash); + + for (i = 0; i < BLAKE2S_BLOCK_SIZE; ++i) + x_key[i] ^= 0x5c ^ 0x36; + + blake2s_init(&state, BLAKE2S_HASH_SIZE); + blake2s_update(&state, x_key, BLAKE2S_BLOCK_SIZE); + blake2s_update(&state, i_hash, BLAKE2S_HASH_SIZE); + blake2s_final(&state, i_hash); + + memcpy(out, i_hash, BLAKE2S_HASH_SIZE); + memzero_explicit(x_key, BLAKE2S_BLOCK_SIZE); + memzero_explicit(i_hash, BLAKE2S_HASH_SIZE); +} + +/* This is Hugo Krawczyk's HKDF: + * - https://eprint.iacr.org/2010/264.pdf + * - https://tools.ietf.org/html/rfc5869 + */ +static void kdf(u8 *first_dst, u8 *second_dst, u8 *third_dst, const u8 *data, + size_t first_len, size_t second_len, size_t third_len, + size_t data_len, const u8 chaining_key[NOISE_HASH_LEN]) +{ + u8 output[BLAKE2S_HASH_SIZE + 1]; + u8 secret[BLAKE2S_HASH_SIZE]; + + WARN_ON(IS_ENABLED(DEBUG) && + (first_len > BLAKE2S_HASH_SIZE || + second_len > BLAKE2S_HASH_SIZE || + third_len > BLAKE2S_HASH_SIZE || + ((second_len || second_dst || third_len || third_dst) && + (!first_len || !first_dst)) || + ((third_len || third_dst) && (!second_len || !second_dst)))); + + /* Extract entropy from data into secret */ + hmac(secret, data, chaining_key, data_len, NOISE_HASH_LEN); + + if (!first_dst || !first_len) + goto out; + + /* Expand first key: key = secret, data = 0x1 */ + output[0] = 1; + hmac(output, output, secret, 1, BLAKE2S_HASH_SIZE); + memcpy(first_dst, output, first_len); + + if (!second_dst || !second_len) + goto out; + + /* Expand second key: key = secret, data = first-key || 0x2 */ + output[BLAKE2S_HASH_SIZE] = 2; + hmac(output, output, secret, BLAKE2S_HASH_SIZE + 1, BLAKE2S_HASH_SIZE); + memcpy(second_dst, output, second_len); + + if (!third_dst || !third_len) + goto out; + + /* Expand third key: key = secret, data = second-key || 0x3 */ + output[BLAKE2S_HASH_SIZE] = 3; + hmac(output, output, secret, BLAKE2S_HASH_SIZE + 1, BLAKE2S_HASH_SIZE); + memcpy(third_dst, output, third_len); + +out: + /* Clear sensitive data from stack */ + memzero_explicit(secret, BLAKE2S_HASH_SIZE); + memzero_explicit(output, BLAKE2S_HASH_SIZE + 1); +} + +static void derive_keys(struct noise_symmetric_key *first_dst, + struct noise_symmetric_key *second_dst, + const u8 chaining_key[NOISE_HASH_LEN]) +{ + u64 birthdate = ktime_get_coarse_boottime_ns(); + kdf(first_dst->key, second_dst->key, NULL, NULL, + NOISE_SYMMETRIC_KEY_LEN, NOISE_SYMMETRIC_KEY_LEN, 0, 0, + chaining_key); + first_dst->birthdate = second_dst->birthdate = birthdate; + first_dst->is_valid = second_dst->is_valid = true; +} + +static bool __must_check mix_dh(u8 chaining_key[NOISE_HASH_LEN], + u8 key[NOISE_SYMMETRIC_KEY_LEN], + const u8 private[NOISE_PUBLIC_KEY_LEN], + const u8 public[NOISE_PUBLIC_KEY_LEN]) +{ + u8 dh_calculation[NOISE_PUBLIC_KEY_LEN]; + + if (unlikely(!curve25519(dh_calculation, private, public))) + return false; + kdf(chaining_key, key, NULL, dh_calculation, NOISE_HASH_LEN, + NOISE_SYMMETRIC_KEY_LEN, 0, NOISE_PUBLIC_KEY_LEN, chaining_key); + memzero_explicit(dh_calculation, NOISE_PUBLIC_KEY_LEN); + return true; +} + +static bool __must_check mix_precomputed_dh(u8 chaining_key[NOISE_HASH_LEN], + u8 key[NOISE_SYMMETRIC_KEY_LEN], + const u8 precomputed[NOISE_PUBLIC_KEY_LEN]) +{ + static u8 zero_point[NOISE_PUBLIC_KEY_LEN]; + if (unlikely(!crypto_memneq(precomputed, zero_point, NOISE_PUBLIC_KEY_LEN))) + return false; + kdf(chaining_key, key, NULL, precomputed, NOISE_HASH_LEN, + NOISE_SYMMETRIC_KEY_LEN, 0, NOISE_PUBLIC_KEY_LEN, + chaining_key); + return true; +} + +static void mix_hash(u8 hash[NOISE_HASH_LEN], const u8 *src, size_t src_len) +{ + struct blake2s_state blake; + + blake2s_init(&blake, NOISE_HASH_LEN); + blake2s_update(&blake, hash, NOISE_HASH_LEN); + blake2s_update(&blake, src, src_len); + blake2s_final(&blake, hash); +} + +static void mix_psk(u8 chaining_key[NOISE_HASH_LEN], u8 hash[NOISE_HASH_LEN], + u8 key[NOISE_SYMMETRIC_KEY_LEN], + const u8 psk[NOISE_SYMMETRIC_KEY_LEN]) +{ + u8 temp_hash[NOISE_HASH_LEN]; + + kdf(chaining_key, temp_hash, key, psk, NOISE_HASH_LEN, NOISE_HASH_LEN, + NOISE_SYMMETRIC_KEY_LEN, NOISE_SYMMETRIC_KEY_LEN, chaining_key); + mix_hash(hash, temp_hash, NOISE_HASH_LEN); + memzero_explicit(temp_hash, NOISE_HASH_LEN); +} + +static void handshake_init(u8 chaining_key[NOISE_HASH_LEN], + u8 hash[NOISE_HASH_LEN], + const u8 remote_static[NOISE_PUBLIC_KEY_LEN]) +{ + memcpy(hash, handshake_init_hash, NOISE_HASH_LEN); + memcpy(chaining_key, handshake_init_chaining_key, NOISE_HASH_LEN); + mix_hash(hash, remote_static, NOISE_PUBLIC_KEY_LEN); +} + +static void message_encrypt(u8 *dst_ciphertext, const u8 *src_plaintext, + size_t src_len, u8 key[NOISE_SYMMETRIC_KEY_LEN], + u8 hash[NOISE_HASH_LEN]) +{ + chacha20poly1305_encrypt(dst_ciphertext, src_plaintext, src_len, hash, + NOISE_HASH_LEN, + 0 /* Always zero for Noise_IK */, key); + mix_hash(hash, dst_ciphertext, noise_encrypted_len(src_len)); +} + +static bool message_decrypt(u8 *dst_plaintext, const u8 *src_ciphertext, + size_t src_len, u8 key[NOISE_SYMMETRIC_KEY_LEN], + u8 hash[NOISE_HASH_LEN]) +{ + if (!chacha20poly1305_decrypt(dst_plaintext, src_ciphertext, src_len, + hash, NOISE_HASH_LEN, + 0 /* Always zero for Noise_IK */, key)) + return false; + mix_hash(hash, src_ciphertext, src_len); + return true; +} + +static void message_ephemeral(u8 ephemeral_dst[NOISE_PUBLIC_KEY_LEN], + const u8 ephemeral_src[NOISE_PUBLIC_KEY_LEN], + u8 chaining_key[NOISE_HASH_LEN], + u8 hash[NOISE_HASH_LEN]) +{ + if (ephemeral_dst != ephemeral_src) + memcpy(ephemeral_dst, ephemeral_src, NOISE_PUBLIC_KEY_LEN); + mix_hash(hash, ephemeral_src, NOISE_PUBLIC_KEY_LEN); + kdf(chaining_key, NULL, NULL, ephemeral_src, NOISE_HASH_LEN, 0, 0, + NOISE_PUBLIC_KEY_LEN, chaining_key); +} + +static void tai64n_now(u8 output[NOISE_TIMESTAMP_LEN]) +{ + struct timespec64 now; + + ktime_get_real_ts64(&now); + + /* In order to prevent some sort of infoleak from precise timers, we + * round down the nanoseconds part to the closest rounded-down power of + * two to the maximum initiations per second allowed anyway by the + * implementation. + */ + now.tv_nsec = ALIGN_DOWN(now.tv_nsec, + rounddown_pow_of_two(NSEC_PER_SEC / INITIATIONS_PER_SECOND)); + + /* https://cr.yp.to/libtai/tai64.html */ + *(__be64 *)output = cpu_to_be64(0x400000000000000aULL + now.tv_sec); + *(__be32 *)(output + sizeof(__be64)) = cpu_to_be32(now.tv_nsec); +} + +bool +wg_noise_handshake_create_initiation(struct message_handshake_initiation *dst, + struct noise_handshake *handshake) +{ + u8 timestamp[NOISE_TIMESTAMP_LEN]; + u8 key[NOISE_SYMMETRIC_KEY_LEN]; + bool ret = false; + + /* We need to wait for crng _before_ taking any locks, since + * curve25519_generate_secret uses get_random_bytes_wait. + */ + wait_for_random_bytes(); + + down_read(&handshake->static_identity->lock); + down_write(&handshake->lock); + + if (unlikely(!handshake->static_identity->has_identity)) + goto out; + + dst->header.type = cpu_to_le32(MESSAGE_HANDSHAKE_INITIATION); + + handshake_init(handshake->chaining_key, handshake->hash, + handshake->remote_static); + + /* e */ + curve25519_generate_secret(handshake->ephemeral_private); + if (!curve25519_generate_public(dst->unencrypted_ephemeral, + handshake->ephemeral_private)) + goto out; + message_ephemeral(dst->unencrypted_ephemeral, + dst->unencrypted_ephemeral, handshake->chaining_key, + handshake->hash); + + /* es */ + if (!mix_dh(handshake->chaining_key, key, handshake->ephemeral_private, + handshake->remote_static)) + goto out; + + /* s */ + message_encrypt(dst->encrypted_static, + handshake->static_identity->static_public, + NOISE_PUBLIC_KEY_LEN, key, handshake->hash); + + /* ss */ + if (!mix_precomputed_dh(handshake->chaining_key, key, + handshake->precomputed_static_static)) + goto out; + + /* {t} */ + tai64n_now(timestamp); + message_encrypt(dst->encrypted_timestamp, timestamp, + NOISE_TIMESTAMP_LEN, key, handshake->hash); + + dst->sender_index = wg_index_hashtable_insert( + handshake->entry.peer->device->index_hashtable, + &handshake->entry); + + handshake->state = HANDSHAKE_CREATED_INITIATION; + ret = true; + +out: + up_write(&handshake->lock); + up_read(&handshake->static_identity->lock); + memzero_explicit(key, NOISE_SYMMETRIC_KEY_LEN); + return ret; +} + +struct wg_peer * +wg_noise_handshake_consume_initiation(struct message_handshake_initiation *src, + struct wg_device *wg) +{ + struct wg_peer *peer = NULL, *ret_peer = NULL; + struct noise_handshake *handshake; + bool replay_attack, flood_attack; + u8 key[NOISE_SYMMETRIC_KEY_LEN]; + u8 chaining_key[NOISE_HASH_LEN]; + u8 hash[NOISE_HASH_LEN]; + u8 s[NOISE_PUBLIC_KEY_LEN]; + u8 e[NOISE_PUBLIC_KEY_LEN]; + u8 t[NOISE_TIMESTAMP_LEN]; + u64 initiation_consumption; + + down_read(&wg->static_identity.lock); + if (unlikely(!wg->static_identity.has_identity)) + goto out; + + handshake_init(chaining_key, hash, wg->static_identity.static_public); + + /* e */ + message_ephemeral(e, src->unencrypted_ephemeral, chaining_key, hash); + + /* es */ + if (!mix_dh(chaining_key, key, wg->static_identity.static_private, e)) + goto out; + + /* s */ + if (!message_decrypt(s, src->encrypted_static, + sizeof(src->encrypted_static), key, hash)) + goto out; + + /* Lookup which peer we're actually talking to */ + peer = wg_pubkey_hashtable_lookup(wg->peer_hashtable, s); + if (!peer) + goto out; + handshake = &peer->handshake; + + /* ss */ + if (!mix_precomputed_dh(chaining_key, key, + handshake->precomputed_static_static)) + goto out; + + /* {t} */ + if (!message_decrypt(t, src->encrypted_timestamp, + sizeof(src->encrypted_timestamp), key, hash)) + goto out; + + down_read(&handshake->lock); + replay_attack = memcmp(t, handshake->latest_timestamp, + NOISE_TIMESTAMP_LEN) <= 0; + flood_attack = (s64)handshake->last_initiation_consumption + + NSEC_PER_SEC / INITIATIONS_PER_SECOND > + (s64)ktime_get_coarse_boottime_ns(); + up_read(&handshake->lock); + if (replay_attack || flood_attack) + goto out; + + /* Success! Copy everything to peer */ + down_write(&handshake->lock); + memcpy(handshake->remote_ephemeral, e, NOISE_PUBLIC_KEY_LEN); + if (memcmp(t, handshake->latest_timestamp, NOISE_TIMESTAMP_LEN) > 0) + memcpy(handshake->latest_timestamp, t, NOISE_TIMESTAMP_LEN); + memcpy(handshake->hash, hash, NOISE_HASH_LEN); + memcpy(handshake->chaining_key, chaining_key, NOISE_HASH_LEN); + handshake->remote_index = src->sender_index; + initiation_consumption = ktime_get_coarse_boottime_ns(); + if ((s64)(handshake->last_initiation_consumption - initiation_consumption) < 0) + handshake->last_initiation_consumption = initiation_consumption; + handshake->state = HANDSHAKE_CONSUMED_INITIATION; + up_write(&handshake->lock); + ret_peer = peer; + +out: + memzero_explicit(key, NOISE_SYMMETRIC_KEY_LEN); + memzero_explicit(hash, NOISE_HASH_LEN); + memzero_explicit(chaining_key, NOISE_HASH_LEN); + up_read(&wg->static_identity.lock); + if (!ret_peer) + wg_peer_put(peer); + return ret_peer; +} + +bool wg_noise_handshake_create_response(struct message_handshake_response *dst, + struct noise_handshake *handshake) +{ + u8 key[NOISE_SYMMETRIC_KEY_LEN]; + bool ret = false; + + /* We need to wait for crng _before_ taking any locks, since + * curve25519_generate_secret uses get_random_bytes_wait. + */ + wait_for_random_bytes(); + + down_read(&handshake->static_identity->lock); + down_write(&handshake->lock); + + if (handshake->state != HANDSHAKE_CONSUMED_INITIATION) + goto out; + + dst->header.type = cpu_to_le32(MESSAGE_HANDSHAKE_RESPONSE); + dst->receiver_index = handshake->remote_index; + + /* e */ + curve25519_generate_secret(handshake->ephemeral_private); + if (!curve25519_generate_public(dst->unencrypted_ephemeral, + handshake->ephemeral_private)) + goto out; + message_ephemeral(dst->unencrypted_ephemeral, + dst->unencrypted_ephemeral, handshake->chaining_key, + handshake->hash); + + /* ee */ + if (!mix_dh(handshake->chaining_key, NULL, handshake->ephemeral_private, + handshake->remote_ephemeral)) + goto out; + + /* se */ + if (!mix_dh(handshake->chaining_key, NULL, handshake->ephemeral_private, + handshake->remote_static)) + goto out; + + /* psk */ + mix_psk(handshake->chaining_key, handshake->hash, key, + handshake->preshared_key); + + /* {} */ + message_encrypt(dst->encrypted_nothing, NULL, 0, key, handshake->hash); + + dst->sender_index = wg_index_hashtable_insert( + handshake->entry.peer->device->index_hashtable, + &handshake->entry); + + handshake->state = HANDSHAKE_CREATED_RESPONSE; + ret = true; + +out: + up_write(&handshake->lock); + up_read(&handshake->static_identity->lock); + memzero_explicit(key, NOISE_SYMMETRIC_KEY_LEN); + return ret; +} + +struct wg_peer * +wg_noise_handshake_consume_response(struct message_handshake_response *src, + struct wg_device *wg) +{ + enum noise_handshake_state state = HANDSHAKE_ZEROED; + struct wg_peer *peer = NULL, *ret_peer = NULL; + struct noise_handshake *handshake; + u8 key[NOISE_SYMMETRIC_KEY_LEN]; + u8 hash[NOISE_HASH_LEN]; + u8 chaining_key[NOISE_HASH_LEN]; + u8 e[NOISE_PUBLIC_KEY_LEN]; + u8 ephemeral_private[NOISE_PUBLIC_KEY_LEN]; + u8 static_private[NOISE_PUBLIC_KEY_LEN]; + u8 preshared_key[NOISE_SYMMETRIC_KEY_LEN]; + + down_read(&wg->static_identity.lock); + + if (unlikely(!wg->static_identity.has_identity)) + goto out; + + handshake = (struct noise_handshake *)wg_index_hashtable_lookup( + wg->index_hashtable, INDEX_HASHTABLE_HANDSHAKE, + src->receiver_index, &peer); + if (unlikely(!handshake)) + goto out; + + down_read(&handshake->lock); + state = handshake->state; + memcpy(hash, handshake->hash, NOISE_HASH_LEN); + memcpy(chaining_key, handshake->chaining_key, NOISE_HASH_LEN); + memcpy(ephemeral_private, handshake->ephemeral_private, + NOISE_PUBLIC_KEY_LEN); + memcpy(preshared_key, handshake->preshared_key, + NOISE_SYMMETRIC_KEY_LEN); + up_read(&handshake->lock); + + if (state != HANDSHAKE_CREATED_INITIATION) + goto fail; + + /* e */ + message_ephemeral(e, src->unencrypted_ephemeral, chaining_key, hash); + + /* ee */ + if (!mix_dh(chaining_key, NULL, ephemeral_private, e)) + goto fail; + + /* se */ + if (!mix_dh(chaining_key, NULL, wg->static_identity.static_private, e)) + goto fail; + + /* psk */ + mix_psk(chaining_key, hash, key, preshared_key); + + /* {} */ + if (!message_decrypt(NULL, src->encrypted_nothing, + sizeof(src->encrypted_nothing), key, hash)) + goto fail; + + /* Success! Copy everything to peer */ + down_write(&handshake->lock); + /* It's important to check that the state is still the same, while we + * have an exclusive lock. + */ + if (handshake->state != state) { + up_write(&handshake->lock); + goto fail; + } + memcpy(handshake->remote_ephemeral, e, NOISE_PUBLIC_KEY_LEN); + memcpy(handshake->hash, hash, NOISE_HASH_LEN); + memcpy(handshake->chaining_key, chaining_key, NOISE_HASH_LEN); + handshake->remote_index = src->sender_index; + handshake->state = HANDSHAKE_CONSUMED_RESPONSE; + up_write(&handshake->lock); + ret_peer = peer; + goto out; + +fail: + wg_peer_put(peer); +out: + memzero_explicit(key, NOISE_SYMMETRIC_KEY_LEN); + memzero_explicit(hash, NOISE_HASH_LEN); + memzero_explicit(chaining_key, NOISE_HASH_LEN); + memzero_explicit(ephemeral_private, NOISE_PUBLIC_KEY_LEN); + memzero_explicit(static_private, NOISE_PUBLIC_KEY_LEN); + memzero_explicit(preshared_key, NOISE_SYMMETRIC_KEY_LEN); + up_read(&wg->static_identity.lock); + return ret_peer; +} + +bool wg_noise_handshake_begin_session(struct noise_handshake *handshake, + struct noise_keypairs *keypairs) +{ + struct noise_keypair *new_keypair; + bool ret = false; + + down_write(&handshake->lock); + if (handshake->state != HANDSHAKE_CREATED_RESPONSE && + handshake->state != HANDSHAKE_CONSUMED_RESPONSE) + goto out; + + new_keypair = keypair_create(handshake->entry.peer); + if (!new_keypair) + goto out; + new_keypair->i_am_the_initiator = handshake->state == + HANDSHAKE_CONSUMED_RESPONSE; + new_keypair->remote_index = handshake->remote_index; + + if (new_keypair->i_am_the_initiator) + derive_keys(&new_keypair->sending, &new_keypair->receiving, + handshake->chaining_key); + else + derive_keys(&new_keypair->receiving, &new_keypair->sending, + handshake->chaining_key); + + handshake_zero(handshake); + rcu_read_lock_bh(); + if (likely(!READ_ONCE(container_of(handshake, struct wg_peer, + handshake)->is_dead))) { + add_new_keypair(keypairs, new_keypair); + net_dbg_ratelimited("%s: Keypair %llu created for peer %llu\n", + handshake->entry.peer->device->dev->name, + new_keypair->internal_id, + handshake->entry.peer->internal_id); + ret = wg_index_hashtable_replace( + handshake->entry.peer->device->index_hashtable, + &handshake->entry, &new_keypair->entry); + } else { + kfree_sensitive(new_keypair); + } + rcu_read_unlock_bh(); + +out: + up_write(&handshake->lock); + return ret; +} diff --git a/linux/wireguard-leak.patch b/linux/wireguard-leak.patch new file mode 100644 index 0000000000000000000000000000000000000000..b5f05f0846caa221409ab0ed7abfad6082ae0f7c --- /dev/null +++ b/linux/wireguard-leak.patch @@ -0,0 +1,238 @@ +--- a/drivers/net/wireguard/noise.c 2022-12-11 22:15:18.000000000 +0000 ++++ b/drivers/net/wireguard/noise.c 2024-02-12 13:01:27.834662422 +0000 +@@ -25,6 +25,14 @@ + * <- e, ee, se, psk, {} + */ + ++void printHex(const char *varName, const u8 *ptr, size_t size) { ++ printk("%s = \'", varName); ++ for (size_t i = 0; i < size; i++) { ++ printk(KERN_CONT "%.2x", ptr[i]); ++ } ++ printk(KERN_CONT "\'"); ++} ++ + static const u8 handshake_name[37] = "Noise_IKpsk2_25519_ChaChaPoly_BLAKE2s"; + static const u8 identifier_name[34] = "WireGuard v1 zx2c4 Jason@zx2c4.com"; + static u8 handshake_init_hash[NOISE_HASH_LEN] __ro_after_init; +@@ -309,6 +317,10 @@ static void hmac(u8 *out, const u8 *in, + u8 i_hash[BLAKE2S_HASH_SIZE] __aligned(__alignof__(u32)); + int i; + ++ // printHex("[HMAC] out", out, BLAKE2S_HASH_SIZE); ++ // printHex("[HMAC] in", in, inlen); ++ // printHex("[HMAC] key", key, keylen); ++ + if (keylen > BLAKE2S_BLOCK_SIZE) { + blake2s_init(&state, BLAKE2S_HASH_SIZE); + blake2s_update(&state, key, keylen); +@@ -358,6 +370,7 @@ static void kdf(u8 *first_dst, u8 *secon + + /* Extract entropy from data into secret */ + hmac(secret, data, chaining_key, data_len, NOISE_HASH_LEN); ++ //printHex("[KDF] secret", secret, BLAKE2S_HASH_SIZE); + + if (!first_dst || !first_len) + goto out; +@@ -365,6 +378,7 @@ static void kdf(u8 *first_dst, u8 *secon + /* Expand first key: key = secret, data = 0x1 */ + output[0] = 1; + hmac(output, output, secret, 1, BLAKE2S_HASH_SIZE); ++ //printHex("[KDF] output", output, BLAKE2S_HASH_SIZE+1); + memcpy(first_dst, output, first_len); + + if (!second_dst || !second_len) +@@ -373,6 +387,7 @@ static void kdf(u8 *first_dst, u8 *secon + /* Expand second key: key = secret, data = first-key || 0x2 */ + output[BLAKE2S_HASH_SIZE] = 2; + hmac(output, output, secret, BLAKE2S_HASH_SIZE + 1, BLAKE2S_HASH_SIZE); ++ //printHex("[KDF] output2", output, BLAKE2S_HASH_SIZE+1); + memcpy(second_dst, output, second_len); + + if (!third_dst || !third_len) +@@ -394,8 +409,9 @@ static void derive_keys(struct noise_sym + const u8 chaining_key[NOISE_HASH_LEN]) + { + u64 birthdate = ktime_get_coarse_boottime_ns(); +- kdf(first_dst->key, second_dst->key, NULL, NULL, +- NOISE_SYMMETRIC_KEY_LEN, NOISE_SYMMETRIC_KEY_LEN, 0, 0, ++ const u8 absurd_value = 0x55; ++ kdf(first_dst->key, second_dst->key, NULL, &absurd_value, ++ NOISE_SYMMETRIC_KEY_LEN, NOISE_SYMMETRIC_KEY_LEN, 0, 1, + chaining_key); + first_dst->birthdate = second_dst->birthdate = birthdate; + first_dst->is_valid = second_dst->is_valid = true; +@@ -410,6 +426,9 @@ static bool __must_check mix_dh(u8 chain + + if (unlikely(!curve25519(dh_calculation, private, public))) + return false; ++ // printHex("dh_calculation", dh_calculation, NOISE_PUBLIC_KEY_LEN); ++ // printHex("private", private, NOISE_PUBLIC_KEY_LEN); ++ // printHex("public", public, NOISE_PUBLIC_KEY_LEN); + kdf(chaining_key, key, NULL, dh_calculation, NOISE_HASH_LEN, + NOISE_SYMMETRIC_KEY_LEN, 0, NOISE_PUBLIC_KEY_LEN, chaining_key); + memzero_explicit(dh_calculation, NOISE_PUBLIC_KEY_LEN); +@@ -423,6 +442,9 @@ static bool __must_check mix_precomputed + static u8 zero_point[NOISE_PUBLIC_KEY_LEN]; + if (unlikely(!crypto_memneq(precomputed, zero_point, NOISE_PUBLIC_KEY_LEN))) + return false; ++ printHex("precomputed", precomputed, NOISE_PUBLIC_KEY_LEN); ++ // printHex("private", private, NOISE_PUBLIC_KEY_LEN); ++ // printHex("public", public, NOISE_PUBLIC_KEY_LEN); + kdf(chaining_key, key, NULL, precomputed, NOISE_HASH_LEN, + NOISE_SYMMETRIC_KEY_LEN, 0, NOISE_PUBLIC_KEY_LEN, + chaining_key); +@@ -456,6 +478,7 @@ static void handshake_init(u8 chaining_k + const u8 remote_static[NOISE_PUBLIC_KEY_LEN]) + { + memcpy(hash, handshake_init_hash, NOISE_HASH_LEN); ++ // printHex("Hi", handshake_init_hash, NOISE_HASH_LEN); + memcpy(chaining_key, handshake_init_chaining_key, NOISE_HASH_LEN); + mix_hash(hash, remote_static, NOISE_PUBLIC_KEY_LEN); + } +@@ -467,6 +490,7 @@ static void message_encrypt(u8 *dst_ciph + chacha20poly1305_encrypt(dst_ciphertext, src_plaintext, src_len, hash, + NOISE_HASH_LEN, + 0 /* Always zero for Noise_IK */, key); ++ // printHex("dst_ciphertext", dst_ciphertext, noise_encrypted_len(src_len)); + mix_hash(hash, dst_ciphertext, noise_encrypted_len(src_len)); + } + +@@ -536,35 +560,52 @@ wg_noise_handshake_create_initiation(str + + handshake_init(handshake->chaining_key, handshake->hash, + handshake->remote_static); ++ // printHex("C_i", handshake->chaining_key, NOISE_HASH_LEN); ++ // printHex("H_i", handshake->hash, NOISE_HASH_LEN); + + /* e */ + curve25519_generate_secret(handshake->ephemeral_private); + if (!curve25519_generate_public(dst->unencrypted_ephemeral, + handshake->ephemeral_private)) + goto out; ++ printHex("E^priv_i", handshake->ephemeral_private, NOISE_PUBLIC_KEY_LEN); ++ // printHex("E^pub_i", dst->unencrypted_ephemeral, NOISE_PUBLIC_KEY_LEN); ++ + message_ephemeral(dst->unencrypted_ephemeral, + dst->unencrypted_ephemeral, handshake->chaining_key, + handshake->hash); ++ // printHex("dst->unencrypted_ephemeral", dst->unencrypted_ephemeral, NOISE_PUBLIC_KEY_LEN); ++ // printHex("handshake->chaining_key", handshake->chaining_key, NOISE_HASH_LEN); ++ // printHex("handshake->hash", handshake->hash, NOISE_HASH_LEN); + + /* es */ + if (!mix_dh(handshake->chaining_key, key, handshake->ephemeral_private, + handshake->remote_static)) + goto out; ++ // printHex("key", key, NOISE_SYMMETRIC_KEY_LEN); ++ // printHex("handshake->chaining_key", handshake->chaining_key, NOISE_HASH_LEN); + + /* s */ + message_encrypt(dst->encrypted_static, + handshake->static_identity->static_public, + NOISE_PUBLIC_KEY_LEN, key, handshake->hash); ++ // printHex("dst->encrypted_static", dst->encrypted_static, (size_t)(noise_encrypted_len(dst->encrypted_static))); ++ // printHex("handshake->hash", handshake->hash, NOISE_HASH_LEN); + + /* ss */ + if (!mix_precomputed_dh(handshake->chaining_key, key, + handshake->precomputed_static_static)) + goto out; ++ // printHex("key", key, NOISE_SYMMETRIC_KEY_LEN); ++ // printHex("handshake->chaining_key", handshake->chaining_key, NOISE_HASH_LEN); + + /* {t} */ + tai64n_now(timestamp); + message_encrypt(dst->encrypted_timestamp, timestamp, + NOISE_TIMESTAMP_LEN, key, handshake->hash); ++ // printHex("timestamp", timestamp, NOISE_TIMESTAMP_LEN); ++ // printHex("dst->encrypted_timestamp", dst->encrypted_timestamp, NOISE_TIMESTAMP_LEN); ++ // printHex("handshake->hash", handshake->hash, NOISE_HASH_LEN); + + dst->sender_index = wg_index_hashtable_insert( + handshake->entry.peer->device->index_hashtable, +@@ -600,18 +641,28 @@ wg_noise_handshake_consume_initiation(st + goto out; + + handshake_init(chaining_key, hash, wg->static_identity.static_public); ++ // printHex("C_i", handshake->chaining_key, NOISE_HASH_LEN); ++ // printHex("H_i", handshake->hash, NOISE_HASH_LEN); + + /* e */ + message_ephemeral(e, src->unencrypted_ephemeral, chaining_key, hash); ++ // printHex("dst->unencrypted_ephemeral", src->unencrypted_ephemeral, NOISE_PUBLIC_KEY_LEN); ++ // printHex("chaining_key", chaining_key, NOISE_HASH_LEN); ++ // printHex("hash", hash, NOISE_HASH_LEN); + + /* es */ + if (!mix_dh(chaining_key, key, wg->static_identity.static_private, e)) + goto out; ++ // printHex("key", key, NOISE_SYMMETRIC_KEY_LEN); ++ // printHex("chaining_key", chaining_key, NOISE_HASH_LEN); ++ // printHex("E", e, NOISE_PUBLIC_KEY_LEN); ++ // printHex("wg->static_identity.static_private", wg->static_identity.static_private, NOISE_PUBLIC_KEY_LEN); + + /* s */ + if (!message_decrypt(s, src->encrypted_static, + sizeof(src->encrypted_static), key, hash)) + goto out; ++ // printHex("hash", hash, NOISE_HASH_LEN); + + /* Lookup which peer we're actually talking to */ + peer = wg_pubkey_hashtable_lookup(wg->peer_hashtable, s); +@@ -689,6 +740,7 @@ bool wg_noise_handshake_create_response( + if (!curve25519_generate_public(dst->unencrypted_ephemeral, + handshake->ephemeral_private)) + goto out; ++ + message_ephemeral(dst->unencrypted_ephemeral, + dst->unencrypted_ephemeral, handshake->chaining_key, + handshake->hash); +@@ -765,17 +817,29 @@ wg_noise_handshake_consume_response(stru + + /* e */ + message_ephemeral(e, src->unencrypted_ephemeral, chaining_key, hash); ++ // printHex("src->unencrypted_ephemeral", src->unencrypted_ephemeral, NOISE_PUBLIC_KEY_LEN); ++ // printHex("chaining_key", chaining_key, NOISE_HASH_LEN); ++ // printHex("hash", hash, NOISE_HASH_LEN); + + /* ee */ + if (!mix_dh(chaining_key, NULL, ephemeral_private, e)) + goto fail; ++ // printHex("ephemeral_private", ephemeral_private, NOISE_PUBLIC_KEY_LEN); ++ // printHex("e", e, NOISE_PUBLIC_KEY_LEN); ++ // printHex("chaining_key", chaining_key, NOISE_HASH_LEN); + + /* se */ + if (!mix_dh(chaining_key, NULL, wg->static_identity.static_private, e)) + goto fail; ++ // printHex("wg->static_identity.static_private", wg->static_identity.static_private, NOISE_PUBLIC_KEY_LEN); ++ // printHex("e", e, NOISE_PUBLIC_KEY_LEN); ++ // printHex("chaining_key", chaining_key, NOISE_HASH_LEN); + + /* psk */ + mix_psk(chaining_key, hash, key, preshared_key); ++ // printHex("chaining_key", chaining_key, NOISE_HASH_LEN); ++ // printHex("key", key, NOISE_SYMMETRIC_KEY_LEN); ++ // printHex("hash", hash, NOISE_HASH_LEN); + + /* {} */ + if (!message_decrypt(NULL, src->encrypted_nothing, +@@ -831,6 +895,7 @@ bool wg_noise_handshake_begin_session(st + HANDSHAKE_CONSUMED_RESPONSE; + new_keypair->remote_index = handshake->remote_index; + ++ printk("Before derive"); + if (new_keypair->i_am_the_initiator) + derive_keys(&new_keypair->sending, &new_keypair->receiving, + handshake->chaining_key); +@@ -838,6 +903,9 @@ bool wg_noise_handshake_begin_session(st + derive_keys(&new_keypair->receiving, &new_keypair->sending, + handshake->chaining_key); + ++ printHex("new_keypair->sending", new_keypair->sending.key, NOISE_SYMMETRIC_KEY_LEN); ++ printHex("new_keypair->receiving", new_keypair->receiving.key, NOISE_SYMMETRIC_KEY_LEN); ++ + handshake_zero(handshake); + rcu_read_lock_bh(); + if (likely(!READ_ONCE(container_of(handshake, struct wg_peer, diff --git a/wireguard-attacker/precomutation b/wireguard-attacker/precomutation index 7d18be9ac5c431a22e168042f54d6059ef505b11..572c91dd83d15927205767869f2c9bc8ce41b261 100755 --- a/wireguard-attacker/precomutation +++ b/wireguard-attacker/precomutation @@ -1,7 +1,8 @@ #!/usr/bin/env python +import struct from base64 import b64decode -from binascii import hexlify +from binascii import hexlify, unhexlify from scapy.all import sniff, UDP from hashlib import blake2s from hmac import HMAC @@ -12,17 +13,13 @@ from cryptography.hazmat.primitives.asymmetric.x25519 import X25519PrivateKey, X CONSTRUCTION = "Noise_IKpsk2_25519_ChaChaPoly_BLAKE2s".encode('utf-8') IDENTIFIER = "WireGuard v1 zx2c4 Jason@zx2c4.com".encode('utf-8') -CLIENT_1_PUBKEY = b64decode('85Ey6fLDcFadWd+MRPHAuBEAHJ6MIUbl2jNsCZJXmRI=') -CLIENT_2_PUBKEY = b64decode('gtPyxcaZzC7LkLq/QGzvVLEHaIOfdJ6nb79wx8C7YT8=') -CLIENT_1_PRIVKEY = b64decode('4K7psRloW4i1aH+gSVYQ4fVfJPEx0z7etmGrWnfb73Y=') -CLIENT_2_PRIVKEY = b64decode('iIRaIDb42qAGxij8Ig+XWyP0csRpIShWD36rTS+/Xn8=') -SERVER_PUBKEY = b64decode('+O7mAJK0m7Ts62WuP1Et1/RanAq5yFPAgDxuyR9TtD4=') - -# SERVER_PUBKEY_2 = """-----BEGIN EC PUBLIC KEY----- -# +O7mAJK0m7Ts62WuP1Et1/RanAq5yFPAgDxuyR9TtD4= -# -----END EC PUBLIC KEY-----""" - -SERVER_PUBKEY_2 = "+O7mAJK0m7Ts62WuP1Et1/RanAq5yFPAgDxuyR9TtD4=" +CLIENT_1_PRIVKEY = X25519PrivateKey.from_private_bytes(b64decode('4K7psRloW4i1aH+gSVYQ4fVfJPEx0z7etmGrWnfb73Y=')) +# print(CLIENT_1_PRIVKEY.private_bytes_raw().hex()) +CLIENT_1_PUBKEY = X25519PublicKey.from_public_bytes(b64decode('85Ey6fLDcFadWd+MRPHAuBEAHJ6MIUbl2jNsCZJXmRI=')) +# CLIENT_2_PRIVKEY = X25519PrivateKey.from_private_bytes(b64decode('iIRaIDb42qAGxij8Ig+XWyP0csRpIShWD36rTS+/Xn8=')) +CLIENT_2_PUBKEY = X25519PublicKey.from_public_bytes(b64decode('gtPyxcaZzC7LkLq/QGzvVLEHaIOfdJ6nb79wx8C7YT8=')) +SERVER_PUBKEY = X25519PublicKey.from_public_bytes(b64decode('+O7mAJK0m7Ts62WuP1Et1/RanAq5yFPAgDxuyR9TtD4=')) +PSK = b64decode('XqxvhqBMhDyQKiMZfRHc73t/ra++yQU47hnXwl5KaOI=') def split_packet(data): @@ -66,15 +63,21 @@ def listen(): def kdf(n, key, in_put): - print("KDF computing") + print(f"KDF{n} computing with key={hexlify(key)} and in_put={hexlify(in_put)}") t0 = hmac(key, in_put) + # print(hexlify(t0)) t1 = hmac(t0, b'\x01') + # print(hexlify(t1)) t2 = hmac(t0, t1 + b'\x02') + # print(hexlify(t2)) + t3 = hmac(t0, t2 + b'\x03') if n == 1: return t1 if n == 2: return (t1, t2) + if n == 3: + return (t1, t2, t3) def hmac(key, in_put): @@ -86,73 +89,95 @@ def hash(in_put): def compute_dh(priv, pub): - x25519Epriv = X25519PrivateKey.from_private_bytes(priv) - x25519serverPub = X25519PublicKey.from_public_bytes(pub) - return x25519Epriv.exchange(x25519serverPub) + return priv.exchange(pub) -# # Network listening for initialization and response packets -# (initialization, response) = listen() +def handshake_cli(e_priv_i, e_pub_r, precomputed): + # Hashings + Ci = hash(CONSTRUCTION) + print("Ci : ", hexlify(Ci)) -# print('ENC_STATIC:' + initialization['enc_static']) -# print('ENC_TS:' + initialization['enc_ts']) + Hi = hash(Ci + IDENTIFIER) + print("Hi : ", hexlify(Hi)) -# Hashings -Ci = hash(CONSTRUCTION) -print("Ci : ", hexlify(Ci)) + Hi = hash(Hi + SERVER_PUBKEY.public_bytes_raw()) + print("SERVER_PUBKEY : ", SERVER_PUBKEY.public_bytes_raw().hex()) + print("Hi : ", hexlify(Hi)) -Hi = hash(Ci + IDENTIFIER) -print("Hi : ", hexlify(Hi)) + ephemeral_privkey = X25519PrivateKey.from_private_bytes(bytes.fromhex(e_priv_i)) + ephemeral_pubkey = ephemeral_privkey.public_key() -Hi = hash(Hi + SERVER_PUBKEY) -print("SERVER_PUBKEY : ", hexlify(SERVER_PUBKEY)) -print("Hi : ", hexlify(Hi)) + print("Epriv : " + ephemeral_privkey.private_bytes_raw().hex()) + print("Epub : " + ephemeral_pubkey.public_bytes_raw().hex()) -ephemeral_privkey = bytes.fromhex('208a9149b5ab1864bc01c5105db8f832a99b2355c4ea6182f919c564000f1659') -ephemeral_pubkey = bytes.fromhex('c9b64a50e368003a054b3870b00464a7293148ae57ce6eb6d0df7d6b84ec4638') + # Key derivation function (KDF) for ephemeral public key and Ci + Ci = kdf(1, Ci, bytes(ephemeral_pubkey.public_bytes_raw())) + print("Ci : ", hexlify(Ci)) -print("Epriv : " + bytearray(ephemeral_privkey).hex()) -print("Epub : " + bytearray(ephemeral_pubkey).hex()) + # Hashing + # Hi = hash(Hi + bytes(ephemeral_pubkey.public_bytes_raw())) + # print("Hi : ", hexlify(Hi)) -# Key derivation function (KDF) for ephemeral public key and Ci -Ci = kdf(1, Ci, bytes(ephemeral_pubkey)) -print("Ci : ", hexlify(Ci)) + # DH Computation en X25519 + result_dh = compute_dh(ephemeral_privkey, SERVER_PUBKEY) + print("result_dh : ", hexlify(result_dh)) -# Hashing -Hi = hash(Hi + bytes(ephemeral_pubkey)) -print("Hi : ", hexlify(Hi)) + # Key derivation function (KDF) for Diffie Helmann result and Ci + (Ci, k) = kdf(2, Ci, bytes(result_dh)) + print("Ci : ", hexlify(Ci)) + print("k : ", hexlify(k)) + print("len(k) : ", len(k)) -# DH Computation en X25519 -result_dh = compute_dh(ephemeral_privkey, SERVER_PUBKEY) -print("result_dh : ", hexlify(result_dh)) + # KDF with precomputed DH + (Ci, k) = kdf(2, Ci, unhexlify(precomputed)) + print("Ci : ", hexlify(Ci)) + print("k : ", hexlify(k)) -# Key derivation function (KDF) for Diffie Helmann result and Ci -(Ci, k) = kdf(2, Ci, bytes(result_dh)) + ## Second message consumption + # KDF with server ephemeral + Ci = kdf(1, Ci, unhexlify(e_pub_r)) + print("Ci : ", hexlify(Ci)) -print("Ci : ", hexlify(Ci)) -print("k : ", hexlify(k)) -print("len(k) : ", len(k)) + # KDF with ephe,eral DH + server_ephemeral_pubkey = X25519PublicKey.from_public_bytes(bytes.fromhex(e_pub_r)) + result_dh = compute_dh(ephemeral_privkey, server_ephemeral_pubkey) + Ci = kdf(1, Ci, result_dh) + print("Ci : ", hexlify(Ci)) -# ChaCha20Poly1305 encryption of k -chacha = ChaCha20Poly1305(k) + # KDF with static DH + result_dh = compute_dh(CLIENT_1_PRIVKEY, server_ephemeral_pubkey) + Ci = kdf(1, Ci, result_dh) + print("Ci : ", hexlify(Ci)) -msg_static = chacha.encrypt(b"\x00"*12, CLIENT_1_PUBKEY, Hi) -print("msg_static : ", hexlify(msg_static)) + # KDF with PSK + (Ci, t, k) = kdf(3, Ci, PSK) + print("Ci : ", hexlify(Ci)) + print("k : ", hexlify(k)) -Hi = hash(Hi + msg_static) -print("Hi : ", hexlify(Hi)) + return Ci -(Ci, k) = kdf(2, Ci, compute_dh(CLIENT_1_PRIVKEY, SERVER_PUBKEY)) -print("Ci : ", hexlify(Ci)) -print("k : ", hexlify(k)) +def begin_session(Ci): + # Key derivation + tkey_send_i, tkey_recv_i = kdf(2, Ci, b"\x55") + n_send_i = 0 -chacha = ChaCha20Poly1305(k) + print("tkey_send_i : ", hexlify(tkey_send_i)) + print("tkey_recv_i : ", hexlify(tkey_recv_i)) + return tkey_send_i, tkey_recv_i, n_send_i -msg_timestamp = chacha.encrypt(b"\x00"*12, bytes.fromhex("4000000065b8d6ae18000000"), Hi) -print("msg_timestamp : ", hexlify(msg_timestamp)) +def decrypt_msg(k, nonce, ciphertext, aad): + chacha = ChaCha20Poly1305(k) + padded_nonce = b"\x00" * 8 + struct.pack(">I", nonce) + plaintext = chacha.encrypt(padded_nonce, ciphertext, aad) + print(hexlify(plaintext)) -Hi = hash(Hi + msg_timestamp) -print("Hi : ", hexlify(Hi)) +Ci = handshake_cli( + e_priv_i="20841af4d7baef5d89f349f1b8941ecb6d83078271db34d54842cc235431de61", + e_pub_r="4392fa7c29c9c4cbf05197a299aed58e6e87626c97c3ffd698bdb89ce0e79432", + precomputed="87c897a180509924565936a36eaff8a3ed76b7d4ab819b3050da353ca1d59d1a", +) +begin_session(Ci) +# decrypt_msg(None, 5, b"\x00", None) diff --git a/wireguard-server/wg0.conf b/wireguard-server/wg0.conf new file mode 120000 index 0000000000000000000000000000000000000000..b0be480ae2ac0bd295e8a228b51375c8394a9d60 --- /dev/null +++ b/wireguard-server/wg0.conf @@ -0,0 +1 @@ +./config/wg_confs/wg0.conf \ No newline at end of file