From b4709429ed88563982412a5a027b92143c37e268 Mon Sep 17 00:00:00 2001 From: Nakidai Date: Thu, 31 Jul 2025 17:12:27 +0300 Subject: Add files --- Makefile | 13 +++ common.h | 74 ++++++++++++++ config.h | 10 ++ fatvpn.c | 215 ++++++++++++++++++++++++++++++++++++++++ fatvpnd.c | 228 +++++++++++++++++++++++++++++++++++++++++++ fvpn_launch.sh | 41 ++++++++ fvpn_stop.sh | 13 +++ fvpnd_launch.sh | 17 ++++ fvpnd_stop.sh | 9 ++ shorttypes.h | 36 +++++++ sponge-bob.h | 296 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 11 files changed, 952 insertions(+) create mode 100644 Makefile create mode 100644 common.h create mode 100644 config.h create mode 100644 fatvpn.c create mode 100644 fatvpnd.c create mode 100755 fvpn_launch.sh create mode 100755 fvpn_stop.sh create mode 100755 fvpnd_launch.sh create mode 100755 fvpnd_stop.sh create mode 100644 shorttypes.h create mode 100644 sponge-bob.h diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..e760d65 --- /dev/null +++ b/Makefile @@ -0,0 +1,13 @@ + +all: fatvpn fatvpnd + +fatvpn: fatvpn.c config.h common.h shorttypes.h sponge-bob.h + echo -e "\n" + $(CC) $(CFLAGS) $(CPPFLAGS) $(LDFLAGS) -o $@ $< $(LDLIBS) + +fatvpnd: fatvpnd.c config.h common.h shorttypes.h sponge-bob.h + echo -e "\n" + $(CC) $(CFLAGS) $(CPPFLAGS) $(LDFLAGS) -o $@ $< $(LDLIBS) + +clean: + rm -f fatvpn fatvpnd diff --git a/common.h b/common.h new file mode 100644 index 0000000..9bb5912 --- /dev/null +++ b/common.h @@ -0,0 +1,74 @@ +/* common (not config, need code patch after change) */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "shorttypes.h" +#include "sponge-bob.h" + +#define ERRDIE(a, b) if ((a) == -1) perror((b)), exit(1) + +#define NONCE_SZ 16 +#define HMAC_SZ 16 + +u8 key[32]; +u32 prng_state[14]; + + +static void send_pkt +(s32 skt, void *buf, u16 sz, struct sockaddr_storage *addr){ + ssize_t res; + u8 *nonce, *tag; + + /* nonce, tag */ + nonce = (u8*)buf + sz; + tag = nonce + NONCE_SZ; + duplex257_prng_rand16(prng_state, nonce); + + /* encrypt */ + duplex257_ae_encrypt(key, nonce, tag, buf, sz); + sz += NONCE_SZ + HMAC_SZ; + + /* send */ + res = sendto(skt, buf, sz, 0,(struct sockaddr*)addr, sizeof(*addr)); + ERRDIE(res, "sendto"); + return; +} + +static s32 recv_pkt +(s32 skt, void *buf, u16 sz, struct sockaddr_storage *addr){ + socklen_t skl; + s32 res; + u8 *nonce, *rtag; + u8 tag[HMAC_SZ]; + + /* recv */ + skl = sizeof(*addr); + res = recvfrom(skt, buf, sz, 0, (struct sockaddr*)addr, &skl); + ERRDIE(res, "recvfrom"); + + /* nonce, recieved tag */ + rtag = buf + res - HMAC_SZ; + nonce = rtag - NONCE_SZ; + + /* decrypt */ + res -= NONCE_SZ + HMAC_SZ; + duplex257_ae_decrypt(key, nonce, tag, buf, res); + if (0 != memcmp(tag, rtag, HMAC_SZ)) return -1; + + return res; +} + diff --git a/config.h b/config.h new file mode 100644 index 0000000..0c43c86 --- /dev/null +++ b/config.h @@ -0,0 +1,10 @@ +/* common config */ + +#define PASSWORD_BUF_SZ 512 /* 256 russian utf8 chars */ + +/* do not forget to set MTU on tap: MAX_PKT_SZ - HMAC_SZ - NONCE_SZ - 14 */ +#define MAX_PKT_SZ 1440 + +/* hardcode the password here if you don't want the program to ask for it + * when it starts. */ +char password[PASSWORD_BUF_SZ]; diff --git a/fatvpn.c b/fatvpn.c new file mode 100644 index 0000000..3a03067 --- /dev/null +++ b/fatvpn.c @@ -0,0 +1,215 @@ +/* fatvpn client by eeeee + * Public Domain */ + +#include "config.h" +#include "common.h" + +#include + +#define LOGFILE "/tmp/fatvpn.log" +#define TIMEOUT_P 10 /* ping timeout in seconds */ + +int main(int argc, char *argv[]){ + u8 *pkt; + s32 tmp, res, tap, fdmax, skt; + u32 lptime; + struct sockaddr_in *s4; + struct sockaddr_in6 srv, sender; + struct addrinfo *srvi; + struct timespec t; + struct timeval slp; + struct termios attr; + struct ifreq ifr; + fd_set rset; + + /* arguments */ + if (argc != 4){ + fprintf(stderr, "Using:\n\t" + "%s
\n\n" + "Example:\n\t" + "%s fvpn0 169.254.123.123 2222\n\t" + "%s tap0 vpn.example.org 5555\n\n", + argv[0], argv[0], argv[0]); + exit(1); + } + + if (strlen(argv[1]) + 1 > IFNAMSIZ) + fputs("tap name is too long\n", stderr), exit(1); + + tmp = 0; + do { /* dns, ip/port str to num */ + res = getaddrinfo(argv[2], argv[3], NULL, &srvi); + tmp++; + usleep(10000); /* 10 ms */ + } while (res == EAI_AGAIN && tmp < 5); + if (res){ + fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(res)); + exit(1); + } + + /* server address */ + switch (srvi->ai_family){ + case AF_INET: + s4 = (struct sockaddr_in*) srvi->ai_addr; + srv.sin6_family = AF_INET6; + srv.sin6_port = s4->sin_port; + memset(&srv.sin6_addr.s6_addr, 0, 16); + srv.sin6_addr.s6_addr[10] = 0xFF; + srv.sin6_addr.s6_addr[11] = 0xFF; + memcpy(&srv.sin6_addr.s6_addr[12], &s4->sin_addr.s_addr, 4); + break; + case AF_INET6: + memcpy(&srv, srvi->ai_addr, sizeof(srv)); + break; + default: + fprintf(stderr, "unsupported addrfamily: %s\n", argv[2]); + exit(1); + } + + if (password[0] == 0){ + /* TODO: check std streams redirected to file */ + /* get term attrs */ + res = tcgetattr(STDOUT_FILENO, &attr); + ERRDIE(res, "tcgetattr"); + + /* echo off */ + attr.c_lflag &= ~ECHO; + res = tcsetattr(STDOUT_FILENO, TCSANOW, &attr); + ERRDIE(res, "tcsetattr"); + + /* get password */ + fputs("Password: ", stdout), fflush(stdout); + pkt = fgets(password, PASSWORD_BUF_SZ, stdin); + fputs("\n", stdout); + if (pkt == NULL) + fputs("can't read password\n", stderr), exit(1); + + /* echo on */ + attr.c_lflag |= ECHO; + res = tcsetattr(STDOUT_FILENO, TCSANOW, &attr); + ERRDIE(res, "tcsetattr"); + } + + /* password without '\r\n' */ + res = 0; + while (password[res] != '\0' + && password[res] != '\r' + && password[res] != '\n') res++; + + /* hash password */ + sponge256_hash32(key, password, res); + memset(password, 0, PASSWORD_BUF_SZ); + + /* feed prng with key and current time */ + duplex257_prng_feed(prng_state, ((u32*)key)[0]); + duplex257_prng_feed(prng_state, ((u32*)key)[1]); + duplex257_prng_feed(prng_state, ((u32*)key)[2]); + duplex257_prng_feed(prng_state, ((u32*)key)[3]); + duplex257_prng_feed(prng_state, ((u32*)key)[4]); + duplex257_prng_feed(prng_state, ((u32*)key)[5]); + duplex257_prng_feed(prng_state, ((u32*)key)[6]); + duplex257_prng_feed(prng_state, ((u32*)key)[7]); + clock_gettime(CLOCK_MONOTONIC, &t); + duplex257_prng_feed(prng_state, (u32)t.tv_sec); + duplex257_prng_feed(prng_state, (u32)t.tv_nsec); + + lptime = (u32)t.tv_sec; + + /* daemonize: redirect std streams */ + res = open("/dev/null", O_RDONLY); ERRDIE(res, "open"); + close(STDIN_FILENO), dup(res), close(res); + res = open(LOGFILE, O_CREAT | O_WRONLY, S_IRUSR | S_IWUSR); + ERRDIE(res, "open"); + close(STDOUT_FILENO); + close(STDERR_FILENO); + dup(res), dup(res), close(res); + + /* daemonize: fork 1 */ + res = fork(); ERRDIE(res, "fork 1"); + if (res != 0) exit(0); + + /* daemonize: become session leader */ + setsid(); + + /* daemonize: fork 2 */ + res = fork(); ERRDIE(res, "fork 2"); + if (res != 0) exit(0); /* kill session leader */ + + /* daemonized! */ + + /* cur pkt buf */ + pkt = malloc(MAX_PKT_SZ); + if (pkt == NULL) fputs("pkt = malloc() failed\n",stderr), exit(1); + + /* socket, setsockopt */ + skt = socket(PF_INET6, SOCK_DGRAM, 0); ERRDIE(skt, "socket"); + res = 1; + res = setsockopt(skt, SOL_SOCKET, SO_REUSEADDR, &res, sizeof(res)); + ERRDIE(res, "SO_REUSEADDR"); + + /* tap */ + tap = open("/dev/net/tun", O_RDWR); + ERRDIE(tap, "/dev/net/tun"); + + memset(&ifr, 0, sizeof(ifr)); + ifr.ifr_flags = IFF_TAP | IFF_NO_PI; + strncpy(ifr.ifr_name, argv[1], IFNAMSIZ); + res = ioctl(tap, TUNSETIFF, &ifr); + ERRDIE(res, "TUNSETIFF"); + + fdmax = (tap > skt) ? tap+1 : skt+1; + +inf_loop: + /* select */ + FD_ZERO(&rset); + FD_SET(skt, &rset); + FD_SET(tap, &rset); + + slp.tv_sec = 0; + slp.tv_usec = 50000; + select(fdmax, &rset, NULL, NULL, &slp); + + clock_gettime(CLOCK_MONOTONIC, &t); + if (TIMEOUT_P <= (u32)t.tv_sec - lptime){ + /* ping for NAT keepalive */ + send_pkt(skt, pkt, 14, (struct sockaddr_storage*)&srv); + lptime = t.tv_sec; + } + + /* read packet from tap */ + if (FD_ISSET(tap, &rset)){ + res = read(tap, pkt, MAX_PKT_SZ); + ERRDIE(res, "read(tap)"); + + /* TODO: check res+N > MAX_PKT_SZ */ + send_pkt(skt, pkt, res, (struct sockaddr_storage*)&srv); + } + + /* recv packet from server */ + if (0 == FD_ISSET(skt, &rset)) goto inf_loop; + + res = recv_pkt(skt, pkt, MAX_PKT_SZ, + (struct sockaddr_storage*)&sender); + if (res < 14) goto inf_loop; /* ignore too small packets */ + + /* compare port and ip */ + if (srv.sin6_port != sender.sin6_port) goto inf_loop; + tmp = memcmp(srv.sin6_addr.s6_addr, sender.sin6_addr.s6_addr, 16); + if (tmp != 0) goto inf_loop; + + /* packet recv time for entropy and last packet time */ + clock_gettime(CLOCK_MONOTONIC, &t); + duplex257_prng_feed(prng_state, t.tv_nsec); + lptime = t.tv_sec; + + tmp = write(tap, pkt, res); + if (tmp < res) + fprintf(stderr, "tmp < res, tmp=%d, res=%d\n", tmp, res), + exit(1); + + goto inf_loop; + + return 0; +} + + diff --git a/fatvpnd.c b/fatvpnd.c new file mode 100644 index 0000000..767f305 --- /dev/null +++ b/fatvpnd.c @@ -0,0 +1,228 @@ +/* fatvpn daemon by eeeee + * Public Domain */ + +#include "config.h" +#include "common.h" + +#define LOGFILE "/tmp/fatvpnd.log" +#define MAX_PEERS 16 +#define TIMEOUT_D 180 /* timeout (no packets) in seconds for disconnect */ + +struct { + struct sockaddr_in6 a; + u32 lptime; /* time of last packet */ + u8 mac[6]; /* internal mac */ +} peer[MAX_PEERS]; + +u32 ccnt; + +int main(int argc, char *argv[]){ + u8 *pkt; + s32 res, tap, fdmax, skt, i, psz; + struct sockaddr_in6 c; + struct timespec t; + struct termios attr; + struct ifreq ifr; + fd_set rset; + + /* arguments */ + if (argc != 3 && argc != 4){ + fprintf(stderr, "Using:\n\t" + "%s [address] \n\n" + "Example:\n\t" + "%s tap0 2222\n\t%s tap0 127.0.0.1 2222\n\n", + argv[0], argv[0], argv[0]); + exit(1); + } + + memset(&c, 0, sizeof(c)); + c.sin6_family = AF_INET6; + c.sin6_port = htons((u16)atoi(argv[argc-1])); + if (argc == 4){ + res = inet_pton(AF_INET6, argv[2], c.sin6_addr.s6_addr); + if (res != 1){ + res = inet_pton(AF_INET, argv[2], &psz); + if (res != 1){ + fprintf(stderr, "wrong addr: %s\n",argv[2]); + exit(1); + } + c.sin6_addr.s6_addr[10] = 0xFF; + c.sin6_addr.s6_addr[11] = 0xFF; + memcpy(&c.sin6_addr.s6_addr[12], &psz, 4); + } + } + + if (strlen(argv[1]) + 1 > IFNAMSIZ) + fputs("tap name is too long\n", stderr), exit(1); + + if (password[0] == 0){ + /* TODO: test std streams redirected to file */ + /* get term attrs */ + res = tcgetattr(STDOUT_FILENO, &attr); + ERRDIE(res, "tcgetattr"); + + /* echo off */ + attr.c_lflag &= ~ECHO; + res = tcsetattr(STDOUT_FILENO, TCSANOW, &attr); + ERRDIE(res, "tcsetattr"); + + /* get password */ + fputs("Password: ", stdout), fflush(stdout); + pkt = fgets(password, PASSWORD_BUF_SZ, stdin); + fputs("\n", stdout); + if (pkt == NULL) + fputs("can't read password\n", stderr), exit(1); + + /* echo on */ + attr.c_lflag |= ECHO; + res = tcsetattr(STDOUT_FILENO, TCSANOW, &attr); + ERRDIE(res, "tcsetattr"); + } + + /* password without '\r\n' */ + res = 0; + while (password[res] != '\0' + && password[res] != '\r' + && password[res] != '\n') res++; + + /* hash password */ + sponge256_hash32(key, password, res); + memset(password, 0, PASSWORD_BUF_SZ); + + /* feed prng with key and current time */ + duplex257_prng_feed(prng_state, ((u32*)key)[0]); + duplex257_prng_feed(prng_state, ((u32*)key)[1]); + duplex257_prng_feed(prng_state, ((u32*)key)[2]); + duplex257_prng_feed(prng_state, ((u32*)key)[3]); + duplex257_prng_feed(prng_state, ((u32*)key)[4]); + duplex257_prng_feed(prng_state, ((u32*)key)[5]); + duplex257_prng_feed(prng_state, ((u32*)key)[6]); + duplex257_prng_feed(prng_state, ((u32*)key)[7]); + clock_gettime(CLOCK_MONOTONIC, &t); + duplex257_prng_feed(prng_state, (u32)t.tv_sec); + duplex257_prng_feed(prng_state, (u32)t.tv_nsec); + + /* daemonize: redirect std streams */ + res = open("/dev/null", O_RDONLY); ERRDIE(res, "open(/dev/null)"); + close(STDIN_FILENO), dup(res), close(res); + res = open(LOGFILE, O_CREAT|O_WRONLY|O_TRUNC, S_IRUSR | S_IWUSR); + ERRDIE(res, "open(logfile)"); + close(STDOUT_FILENO); + close(STDERR_FILENO); + dup(res), dup(res), close(res); + + /* daemonize: fork 1 */ + res = fork(); ERRDIE(res, "fork 1"); + if (res != 0) exit(0); + + /* daemonize: become session leader */ + setsid(); + + /* daemonize: fork 2 */ + res = fork(); ERRDIE(res, "fork 2"); + if (res != 0) exit(0); /* kill session leader */ + + /* daemonized! */ + + /* cur pkt buf */ + pkt = malloc(MAX_PKT_SZ); + if (pkt == NULL) fputs("pkt = malloc() failed\n",stderr), exit(1); + + /* socket, setsockopt */ + skt = socket(PF_INET6, SOCK_DGRAM, 0); ERRDIE(skt, "socket"); + res = 1; + res = setsockopt(skt, SOL_SOCKET, SO_REUSEADDR, &res, sizeof(res)); + ERRDIE(res, "SO_REUSEADDR"); + + /* bind */ + res = bind(skt, (struct sockaddr*) &c, sizeof(c)); + ERRDIE(res, "bind"); + + /* tap */ + tap = open("/dev/net/tun", O_RDWR); + ERRDIE(tap, "open(/dev/net/tun)"); + + memset(&ifr, 0, sizeof(ifr)); + ifr.ifr_flags = IFF_TAP | IFF_NO_PI; + strncpy(ifr.ifr_name, argv[1], IFNAMSIZ); + res = ioctl(tap, TUNSETIFF, &ifr); + ERRDIE(res, "TUNSETIFF"); + + fdmax = (tap > skt) ? tap+1 : skt+1; + +inf_loop: + /* "disconnect" peers with timeout */ + clock_gettime(CLOCK_MONOTONIC, &t); + for (i=0; i < ccnt; i++){ + if (t.tv_sec - peer[i].lptime < TIMEOUT_D) continue; + if (i != ccnt-1) + memcpy(&peer[i], &peer[ccnt-1], sizeof(peer[0])); + + ccnt--, i--; + memset(&peer[ccnt], 0, sizeof(peer[0])); + } + + /* select */ + FD_ZERO(&rset); + FD_SET(skt, &rset); + FD_SET(tap, &rset); + + select(fdmax, &rset, NULL, NULL, NULL); + + /* read packet from tap */ + if (FD_ISSET(tap, &rset)){ + psz = read(tap, pkt, MAX_PKT_SZ); + ERRDIE(psz, "read(tap)"); + + for (i=0; i < ccnt; i++){ + if (0 == memcmp(peer[i].mac, pkt, 6)){ + send_pkt(skt, pkt, psz, + (struct sockaddr_storage*) &peer[i].a); + break; + } + } + } + + /* recv packet from client */ + if (0 == FD_ISSET(skt, &rset)) goto inf_loop; + + psz = recv_pkt(skt, pkt, MAX_PKT_SZ, (struct sockaddr_storage*) &c); + + for (i=0; i < ccnt; i++) + if (c.sin6_port == peer[i].a.sin6_port && + 0 == memcmp(c.sin6_addr.s6_addr, + peer[i].a.sin6_addr.s6_addr, 16)) break; + + if (i == ccnt){ + /* client not connected */ + if (psz <= 14) /* ethernet header len == 14 */ + goto inf_loop; + + if (ccnt == MAX_PEERS) goto inf_loop; + + /* add peer to array */ + memcpy(&peer[i].a, &c, sizeof(c)); + memcpy(&peer[i].mac, &pkt[6], 6); + ccnt++; + } + + /* client connected */ + clock_gettime(CLOCK_MONOTONIC, &t); + duplex257_prng_feed(prng_state, t.tv_nsec); + peer[i].lptime = t.tv_sec; + + /* if connected peer send small packet, it's a ping packet for NAT + * keepalive, all i need to do is update lptime */ + if (psz <= 14) goto inf_loop; + + i = write(tap, pkt, psz); + if (i == -1) perror("write(tap)"); + if (i < psz) + fprintf(stderr, "i < psz, i=%d, psz=%d\n", i, psz), exit(1); + + goto inf_loop; + + return 0; +} + + diff --git a/fvpn_launch.sh b/fvpn_launch.sh new file mode 100755 index 0000000..3b158de --- /dev/null +++ b/fvpn_launch.sh @@ -0,0 +1,41 @@ +#!/bin/sh + +set -e + +TAP=fvpn0 +INTERNAL_IP=10.43.43.2/24 +INTERNAL_SRV_IP=10.43.43.1 +SERVER=vpn.example.org +PORT=12345 +MAX_PKT_SZ=1440 +MTU=$(( $MAX_PKT_SZ - 46 )) + +mac=$(awk 'BEGIN{ + srand(); + printf("ae:ae:ae:%02x:%02x:%02x", + rand()*256, rand()*256, rand()*256); + }') + +ip tuntap add $TAP mode tap user root +ip addr add $INTERNAL_IP dev $TAP +ip link set $TAP mtu $MTU +ip link set $TAP address $mac +ip link set $TAP up + +./fatvpn $TAP $SERVER $PORT + +# route ALL traffic to vpn +# do not forget to configure your server: +# 1) ip forwarding on +# 2) masquarade +# 3) firewall forward rules configured/disabled +if [ "$1" = "all" ]; then + srv_ip=$(dig +short $SERVER | head -1) + gw=$(ip route get $srv_ip | head -1 | awk '{print $3}') + ip route add $srv_ip/32 via $gw + ip route add 0.0.0.0/1 via $INTERNAL_SRV_IP + ip route add 128.0.0.0/1 via $INTERNAL_SRV_IP +fi + +ping -c3 $INTERNAL_SRV_IP + diff --git a/fvpn_stop.sh b/fvpn_stop.sh new file mode 100755 index 0000000..5149fba --- /dev/null +++ b/fvpn_stop.sh @@ -0,0 +1,13 @@ +#!/bin/sh + +TAP=fvpn0 +SERVER=vpn.example.org + +ip route del 0.0.0.0/1 +ip route del 128.0.0.0/1 +ip route del "$(dig +short $SERVER | head -1)/32" + +killall fatvpn +ip link set $TAP down +ip tuntap del $TAP mode tap + diff --git a/fvpnd_launch.sh b/fvpnd_launch.sh new file mode 100755 index 0000000..042121b --- /dev/null +++ b/fvpnd_launch.sh @@ -0,0 +1,17 @@ +#!/bin/sh + +TAP=fvpn0 +INTERNAL_IP=10.43.43.1/24 +IP=0.0.0.0 +PORT=12345 +MAX_PKT_SZ=1440 +MTU=$(( $MAX_PKT_SZ - 46 )) + +ip tuntap add $TAP mode tap +ip addr add $INTERNAL_IP dev $TAP +ip link set $TAP mtu $MTU +ip link set $TAP up + +./fatvpnd $TAP $IP $PORT + + diff --git a/fvpnd_stop.sh b/fvpnd_stop.sh new file mode 100755 index 0000000..bed0acc --- /dev/null +++ b/fvpnd_stop.sh @@ -0,0 +1,9 @@ +#!/bin/sh + +TAP=fvpn0 + +killall fatvpnd +ip link set $TAP down +ip tuntap del $TAP mode tap + + diff --git a/shorttypes.h b/shorttypes.h new file mode 100644 index 0000000..7b2df58 --- /dev/null +++ b/shorttypes.h @@ -0,0 +1,36 @@ +#ifndef SHORTTYPES_H +#define SHORTTYPES_H +#include + +#if CHAR_BIT != 8 + #error "CHAR_BIT != 8" +#endif + +#if USHRT_MAX != 65535 + #error "USHRT_MAX != 65535" +#endif + +#if UINT_MAX != 4294967295U + #error "UINT_MAX != 4294967295U" +#endif + +typedef signed char s8; +typedef signed short s16; +typedef signed int s32; +typedef unsigned char u8; +typedef unsigned short u16; +typedef unsigned int u32; + +#ifdef ULLONG_MAX + #if ULLONG_MAX == 18446744073709551615ULL + typedef signed long long s64; + typedef unsigned long long u64; + #else + #error "ULLONG_MAX != 18446744073709551615ULL" + #endif +#else + #error "no long long type" +#endif + + +#endif /* SHORTTYPES_H */ diff --git a/sponge-bob.h b/sponge-bob.h new file mode 100644 index 0000000..0f7db6b --- /dev/null +++ b/sponge-bob.h @@ -0,0 +1,296 @@ +/* implementation of sponge based crypto by eeeee + * Public Domain + * + * Cryptographic sponge functions: + * https://keccak.team/files/CSF-0.1.pdf + * https://web.archive.org/web/20250620013520/https://keccak.team/files/CSF-0.1.pdf + * + * Sponge functions require permutation function. I use gimli: + * https://gimli.cr.yp.to/ + * https://gimli.cr.yp.to/gimli-20170627.pdf + * https://web.archive.org/web/20250421113716/https://gimli.cr.yp.to/gimli-20170627.pdf + */ +#ifndef SPONGE_BOB_H +#define SPONGE_BOB_H + +#include +#include +#include "shorttypes.h" + +#define ROTL32(x, n) (((x)<<(n)) | ((x)>>(32-(n)))) + +static void gimli(void *s){ + u32 round, column; + u32 x, y, z; + u32 *state = s; + + for (round = 24; round != 0; --round){ + for (column = 0; column < 4; ++column){ + x = ROTL32(state[ column], 24); + y = ROTL32(state[4 + column], 9); + z = state[8 + column]; + + state[8 + column] = x ^ (z << 1) ^ ((y&z) << 2); + state[4 + column] = y ^ x ^ ((x|z) << 1); + state[ column] = z ^ y ^ ((x&y) << 3); + } + + if ((round & 3) == 0) { // small swap: pattern s...s...s... + x = state[0]; + state[0] = state[1]; + state[1] = x; + x = state[2]; + state[2] = state[3]; + state[3] = x; + } + + if ((round & 3) == 2) { // big swap: ..S...S...S. etc. + x = state[0]; + state[0] = state[2]; + state[2] = x; + x = state[1]; + state[1] = state[3]; + state[3] = x; + } + + if ((round & 3) == 0) { // add constant: c...c...c... etc. + state[0] ^= (0x9e377900 | round); + } + } + + return; +} + +/* gimli, endian-agnostic version */ +static void gimli_ea(void *s){ + u32 *state = s; + + state[ 0] = ntohl(state[ 0]); + state[ 1] = ntohl(state[ 1]); + state[ 2] = ntohl(state[ 2]); + state[ 3] = ntohl(state[ 3]); + state[ 4] = ntohl(state[ 4]); + state[ 5] = ntohl(state[ 5]); + state[ 6] = ntohl(state[ 6]); + state[ 7] = ntohl(state[ 7]); + state[ 8] = ntohl(state[ 8]); + state[ 9] = ntohl(state[ 9]); + state[10] = ntohl(state[10]); + state[11] = ntohl(state[11]); + + gimli(s); + + state[ 0] = htonl(state[ 0]); + state[ 1] = htonl(state[ 1]); + state[ 2] = htonl(state[ 2]); + state[ 3] = htonl(state[ 3]); + state[ 4] = htonl(state[ 4]); + state[ 5] = htonl(state[ 5]); + state[ 6] = htonl(state[ 6]); + state[ 7] = htonl(state[ 7]); + state[ 8] = htonl(state[ 8]); + state[ 9] = htonl(state[ 9]); + state[10] = htonl(state[10]); + state[11] = htonl(state[11]); + + return; +} + +#ifndef FPERMUTE +#define FPERMUTE gimli_ea +#endif + +#define MEMXOR16(dst, src) \ + ((u32*)dst)[0] ^= ((u32*)src)[0]; \ + ((u32*)dst)[1] ^= ((u32*)src)[1]; \ + ((u32*)dst)[2] ^= ((u32*)src)[2]; \ + ((u32*)dst)[3] ^= ((u32*)src)[3] + +#define MEMXOR32(dst, src) \ + ((u32*)dst)[0] ^= ((u32*)src)[0]; \ + ((u32*)dst)[1] ^= ((u32*)src)[1]; \ + ((u32*)dst)[2] ^= ((u32*)src)[2]; \ + ((u32*)dst)[3] ^= ((u32*)src)[3]; \ + ((u32*)dst)[4] ^= ((u32*)src)[4]; \ + ((u32*)dst)[5] ^= ((u32*)src)[5]; \ + ((u32*)dst)[6] ^= ((u32*)src)[6]; \ + ((u32*)dst)[7] ^= ((u32*)src)[7] + + +/* reseedable prng + * state[12]: input buffer index + * state[13]: output buffer index + */ +static void duplex257_prng_feed(u32 state[14], u32 food){ + state[13] = 0; + state[state[12]] ^= food; + state[12]++; + if (state[12] == 8){ + ((u8*)state)[32] ^= 0x80; + state[12] = 0; + FPERMUTE(state); + } + return; +} + +static u32 duplex257_prng_rand(u32 state[14]){ + u32 res; + if (state[12]){ + ((u8*)state)[state[12]] ^= 0x80; + FPERMUTE(state); + state[12] = 0; + state[13] = 1; + return state[0]; + } + + res = state[state[13]]; + state[13]++; + if (state[13] == 8){ + state[13] = 0; + ((u8*)state)[0] ^= 0x80; + FPERMUTE(state); + } + + return res; +} + +static void duplex257_prng_rand16(u32 state[14], u8 dst[16]){ + if (state[12]){ + ((u8*)state)[state[12]] ^= 0x80; + FPERMUTE(state); + state[12] = 0; + state[13] = 4; + memcpy(dst, state, 16); + return; + } + + if (state[13] == 0){ + state[13] = 4; + memcpy(dst, state, 16); + } else if (state[13] < 5){ + state[13] = 0; + memcpy(dst, &state[4], 16); + ((u8*)state)[0] ^= 0x80; + FPERMUTE(state); + } else { + ((u8*)state)[0] ^= 0x80; + FPERMUTE(state); + state[13] = 4; + memcpy(dst, state, 16); + } + return; +} + +/* duplex257 AE */ +static void duplex257_ae_encrypt + (const u8 key[32], const u8 nonce[16], u8 tag[16], u8 *src_dst, u32 sz){ + static u8 state[48]; + u32 i, n; + + /* duplex_mute(key) */ + memcpy(state, key, 32); + state[32] = 0x80; + FPERMUTE(state); + + /* duplex_mute(nonce) */ + MEMXOR16(state, nonce); + state[16] ^= 0x80; + FPERMUTE(state); + + /* 32 bytes steps */ + n = sz >> 5; + while (n){ + /* duplex(src_dst), encrypt */ + MEMXOR32(src_dst, state); + memcpy(state, src_dst, 32); + state[32] ^= 0x80; + FPERMUTE(state); + src_dst += 32; + n--; + } + + /* 1 byte steps */ + n = sz & 0x1Fu; + /* duplex(src_dst), encrypt */ + for (i = 0; i < n; i++) src_dst[i] ^= state[i]; + memcpy(state, src_dst, n); + state[n] ^= 0x80; + FPERMUTE(state); + + memcpy(tag, state, 16); + memset(state, 0, sizeof(state)); + return; +} + +static void duplex257_ae_decrypt + (const u8 key[32], const u8 nonce[16], u8 tag[16], u8 *src_dst, u32 sz){ + static u8 state[48]; + u32 i, n; + + /* duplex_mute(key) */ + memcpy(state, key, 32); + state[32] = 0x80; + FPERMUTE(state); + + /* duplex_mute(nonce) */ + MEMXOR16(state, nonce); + state[16] ^= 0x80; + FPERMUTE(state); + + /* 32 bytes steps */ + n = sz >> 5; + while (n){ + /* duplex(src_dst), encrypt */ + MEMXOR32(src_dst, state); + MEMXOR32(state, src_dst); + state[32] ^= 0x80; + FPERMUTE(state); + src_dst += 32; + n--; + } + + /* 1 byte steps */ + n = sz & 0x1Fu; + /* duplex(src_dst), encrypt */ + for (i = 0; i < n; i++){ + src_dst[i] ^= state[i]; + state[i] ^= src_dst[i]; + } + state[n] ^= 0x80; + FPERMUTE(state); + + memcpy(tag, state, 16); + memset(state, 0, sizeof(state)); + return; +} + +/* sponge */ +static void sponge256_absorb(u8 state[48], const u8 *src, u32 sz){ + u32 n, i; + + n = sz >> 5; + while (n){ /* 32 byte steps */ + MEMXOR32(state, src); + FPERMUTE(state); + src += 32; + n--; + } + + /* 1 byte steps */ + n = sz & 0x1Fu; + for (i=0; i < n; i++) state[i] ^= src[i]; + state[i] ^= 0x80; + FPERMUTE(state); + return; +} + +/* sponge hash */ +static void sponge256_hash32(u8 dst[32], u8 *src, u32 sz){ + static u8 state[48]; + sponge256_absorb(state, src, sz); + memcpy(dst, state, 32); + memset(state, 0, sizeof(state)); + return; +} + +#endif /* SPONGE_BOB_H */ -- cgit 1.4.1