mirror of
https://github.com/shadowsocks/shadowsocks-libev.git
synced 2026-10-28 10:44:43 +08:00
Merge pull request #3049 from shadowsocks/feature/aead-2022
Add Shadowsocks 2022 (SIP022) ciphers and ss:// URL support
This commit is contained in:
43 files changed
+4647
-108
No files matched your search
+7
-2
@@ -13,10 +13,15 @@ include(GNUInstallDirs)
|
||||
# Compilation database for clang-tidy and other tooling
|
||||
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
||||
|
||||
# Compiler flags matching autotools
|
||||
# C11 with GNU extensions. _GNU_SOURCE stays for the glibc-specific
|
||||
# interfaces used by the Linux-only modules (ss-redir, connmark/nftables).
|
||||
set(CMAKE_C_STANDARD 11)
|
||||
set(CMAKE_C_STANDARD_REQUIRED ON)
|
||||
set(CMAKE_C_EXTENSIONS ON)
|
||||
|
||||
# Note: -Werror is applied per-target in src/CMakeLists.txt to avoid
|
||||
# breaking bundled submodules (libcork, libipset, libbloom)
|
||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -std=gnu99 -D_GNU_SOURCE")
|
||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -D_GNU_SOURCE")
|
||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -g -O2 -Wall -Wno-deprecated-declarations -fno-strict-aliasing")
|
||||
|
||||
option(ENABLE_SANITIZERS "Build with AddressSanitizer and UndefinedBehaviorSanitizer" OFF)
|
||||
|
||||
@@ -344,7 +344,11 @@ you may refer to the man pages of the applications, respectively.
|
||||
|
||||
-k <password> Password of your remote server.
|
||||
|
||||
-m <encrypt_method> Encrypt method: rc4-md5,
|
||||
-m <encrypt_method> Encrypt method:
|
||||
2022-blake3-aes-128-gcm,
|
||||
2022-blake3-aes-256-gcm,
|
||||
2022-blake3-chacha20-poly1305,
|
||||
rc4-md5,
|
||||
aes-128-gcm, aes-192-gcm, aes-256-gcm,
|
||||
aes-128-cfb, aes-192-cfb, aes-256-cfb,
|
||||
aes-128-ctr, aes-192-ctr, aes-256-ctr,
|
||||
@@ -355,6 +359,21 @@ you may refer to the man pages of the applications, respectively.
|
||||
salsa20, chacha20 and chacha20-ietf.
|
||||
The default cipher is chacha20-ietf-poly1305.
|
||||
|
||||
The 2022-blake3-* ciphers implement
|
||||
Shadowsocks 2022 (SIP022) and are the
|
||||
recommended choice. They take a
|
||||
base64-encoded pre-shared key of exactly
|
||||
the cipher's key size via -k, not a
|
||||
password: generate one with
|
||||
`openssl rand -base64 32` (or 16 for
|
||||
2022-blake3-aes-128-gcm).
|
||||
|
||||
[--server-url <ss_url>] Take the server address, port, cipher,
|
||||
password and SIP003 plugin from a single
|
||||
ss:// URL (SIP002 or the legacy form).
|
||||
ss-local only. Options given later on the
|
||||
command line override the URL's values.
|
||||
|
||||
[-a <user>] Run as another user.
|
||||
|
||||
[-f <pid_file>] The file path to store pid.
|
||||
|
||||
+84
-34
@@ -5,54 +5,79 @@
|
||||
# MBEDTLS_INCLUDE_DIRS
|
||||
# MBEDTLS_CRYPTO_LIBRARY
|
||||
# MBEDTLS_TLS_LIBRARY
|
||||
#
|
||||
# The headers and the libraries MUST come from the same installation. Mixing
|
||||
# them is not a build-time error but a memory-corruption bug at runtime:
|
||||
# mbedTLS structs such as mbedtls_cipher_context_t changed size between 3.x
|
||||
# and 4.x, so a context allocated with sizeof() from one version and
|
||||
# initialized by the other overruns its heap block. Homebrew makes this easy
|
||||
# to hit, since mbedtls (4.x) and the keg-only mbedtls@3 can both be present
|
||||
# and CMAKE_PREFIX_PATH outranks HINTS in find_library's search order.
|
||||
|
||||
include(CheckCSourceCompiles)
|
||||
|
||||
# mbedtls@3 is keg-only on Homebrew; also check versioned opt paths
|
||||
find_path(MBEDTLS_INCLUDE_DIR
|
||||
NAMES mbedtls/cipher.h
|
||||
HINTS
|
||||
/opt/homebrew/opt/mbedtls@3/include
|
||||
/usr/local/opt/mbedtls@3/include
|
||||
/opt/homebrew/opt/mbedtls/include
|
||||
/usr/local/opt/mbedtls/include
|
||||
/opt/homebrew/include
|
||||
/usr/local/include
|
||||
/usr/include
|
||||
# This codebase targets the mbedTLS 3.x API, so prefer a 3.x installation.
|
||||
set(_MBEDTLS_PREFIX_HINTS
|
||||
/opt/homebrew/opt/mbedtls@3
|
||||
/usr/local/opt/mbedtls@3
|
||||
/opt/homebrew/opt/mbedtls
|
||||
/usr/local/opt/mbedtls
|
||||
/opt/homebrew
|
||||
/usr/local
|
||||
/usr
|
||||
)
|
||||
|
||||
find_library(MBEDTLS_CRYPTO_LIBRARY
|
||||
NAMES mbedcrypto
|
||||
HINTS
|
||||
/opt/homebrew/opt/mbedtls@3/lib
|
||||
/usr/local/opt/mbedtls@3/lib
|
||||
/opt/homebrew/opt/mbedtls/lib
|
||||
/usr/local/opt/mbedtls/lib
|
||||
/opt/homebrew/lib
|
||||
/usr/local/lib
|
||||
/usr/lib
|
||||
)
|
||||
# Locate a prefix that provides both the headers and libmbedcrypto, so the
|
||||
# two can never be drawn from different installations.
|
||||
foreach(_prefix IN LISTS _MBEDTLS_PREFIX_HINTS)
|
||||
if(EXISTS "${_prefix}/include/mbedtls/cipher.h")
|
||||
find_library(_MBEDTLS_CRYPTO_IN_PREFIX
|
||||
NAMES mbedcrypto
|
||||
PATHS "${_prefix}/lib"
|
||||
NO_DEFAULT_PATH
|
||||
)
|
||||
if(_MBEDTLS_CRYPTO_IN_PREFIX)
|
||||
set(_MBEDTLS_ROOT "${_prefix}")
|
||||
break()
|
||||
endif()
|
||||
unset(_MBEDTLS_CRYPTO_IN_PREFIX CACHE)
|
||||
endif()
|
||||
endforeach()
|
||||
|
||||
find_library(MBEDTLS_TLS_LIBRARY
|
||||
NAMES mbedtls
|
||||
HINTS
|
||||
/opt/homebrew/opt/mbedtls@3/lib
|
||||
/usr/local/opt/mbedtls@3/lib
|
||||
/opt/homebrew/opt/mbedtls/lib
|
||||
/usr/local/opt/mbedtls/lib
|
||||
/opt/homebrew/lib
|
||||
/usr/local/lib
|
||||
/usr/lib
|
||||
)
|
||||
if(_MBEDTLS_ROOT)
|
||||
# Pin every component to the prefix chosen above.
|
||||
find_path(MBEDTLS_INCLUDE_DIR
|
||||
NAMES mbedtls/cipher.h
|
||||
PATHS "${_MBEDTLS_ROOT}/include"
|
||||
NO_DEFAULT_PATH
|
||||
)
|
||||
find_library(MBEDTLS_CRYPTO_LIBRARY
|
||||
NAMES mbedcrypto
|
||||
PATHS "${_MBEDTLS_ROOT}/lib"
|
||||
NO_DEFAULT_PATH
|
||||
)
|
||||
find_library(MBEDTLS_TLS_LIBRARY
|
||||
NAMES mbedtls
|
||||
PATHS "${_MBEDTLS_ROOT}/lib"
|
||||
NO_DEFAULT_PATH
|
||||
)
|
||||
else()
|
||||
# No single prefix provided both; fall back to a plain search.
|
||||
find_path(MBEDTLS_INCLUDE_DIR NAMES mbedtls/cipher.h)
|
||||
find_library(MBEDTLS_CRYPTO_LIBRARY NAMES mbedcrypto)
|
||||
find_library(MBEDTLS_TLS_LIBRARY NAMES mbedtls)
|
||||
endif()
|
||||
|
||||
unset(_MBEDTLS_CRYPTO_IN_PREFIX CACHE)
|
||||
|
||||
if(MBEDTLS_INCLUDE_DIR AND MBEDTLS_CRYPTO_LIBRARY)
|
||||
set(MBEDTLS_FOUND TRUE)
|
||||
set(MBEDTLS_INCLUDE_DIRS ${MBEDTLS_INCLUDE_DIR})
|
||||
|
||||
# Check for required CFB mode support
|
||||
set(CMAKE_REQUIRED_INCLUDES ${MBEDTLS_INCLUDE_DIR})
|
||||
set(CMAKE_REQUIRED_LIBRARIES ${MBEDTLS_CRYPTO_LIBRARY})
|
||||
|
||||
# Check for required CFB mode support
|
||||
check_c_source_compiles("
|
||||
#include <mbedtls/cipher.h>
|
||||
#if !defined(MBEDTLS_CIPHER_MODE_CFB)
|
||||
@@ -78,6 +103,31 @@ if(MBEDTLS_INCLUDE_DIR AND MBEDTLS_CRYPTO_LIBRARY)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# Verify the headers and the runtime library agree on the version. A
|
||||
# mismatch silently corrupts the heap, so refuse to configure instead.
|
||||
if(NOT CMAKE_CROSSCOMPILING AND NOT DEFINED MBEDTLS_VERSION_CONSISTENT)
|
||||
try_run(_mbedtls_run_result _mbedtls_compile_result
|
||||
${CMAKE_BINARY_DIR}/CMakeFiles/mbedtls_version_check
|
||||
${CMAKE_CURRENT_LIST_DIR}/mbedtls_version_check.c
|
||||
CMAKE_FLAGS
|
||||
"-DINCLUDE_DIRECTORIES=${MBEDTLS_INCLUDE_DIR}"
|
||||
"-DLINK_LIBRARIES=${MBEDTLS_CRYPTO_LIBRARY}"
|
||||
RUN_OUTPUT_VARIABLE _mbedtls_run_output
|
||||
)
|
||||
if(_mbedtls_compile_result AND _mbedtls_run_result EQUAL 0)
|
||||
set(MBEDTLS_VERSION_CONSISTENT TRUE CACHE INTERNAL "")
|
||||
elseif(_mbedtls_compile_result)
|
||||
message(FATAL_ERROR
|
||||
"mbedTLS headers and library are from different installations:\n"
|
||||
" ${_mbedtls_run_output}\n"
|
||||
" headers: ${MBEDTLS_INCLUDE_DIR}\n"
|
||||
" library: ${MBEDTLS_CRYPTO_LIBRARY}\n"
|
||||
"Mixing them corrupts memory at runtime. Point both at the same "
|
||||
"mbedTLS installation, e.g. "
|
||||
"-DCMAKE_PREFIX_PATH=/opt/homebrew/opt/mbedtls@3")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
unset(CMAKE_REQUIRED_INCLUDES)
|
||||
unset(CMAKE_REQUIRED_LIBRARIES)
|
||||
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Configure-time check that the mbedTLS headers and the linked library are
|
||||
* the same version. A mismatch is not a link error, but struct layouts
|
||||
* differ across major versions, so contexts sized with sizeof() from the
|
||||
* headers are overrun by library code that expects the other layout.
|
||||
*
|
||||
* Exits 0 when the compile-time and runtime versions agree.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <mbedtls/version.h>
|
||||
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
char runtime[32] = { 0 };
|
||||
|
||||
mbedtls_version_get_string(runtime);
|
||||
|
||||
if (strcmp(runtime, MBEDTLS_VERSION_STRING) != 0) {
|
||||
printf("headers say %s, library reports %s",
|
||||
MBEDTLS_VERSION_STRING, runtime);
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -56,8 +56,10 @@ Not available in manager mode.
|
||||
-m <encrypt_method>::
|
||||
Set the cipher.
|
||||
+
|
||||
*Shadowsocks-libev* accepts 19 different ciphers:
|
||||
*Shadowsocks-libev* accepts 22 different ciphers:
|
||||
+
|
||||
2022-blake3-aes-128-gcm, 2022-blake3-aes-256-gcm,
|
||||
2022-blake3-chacha20-poly1305,
|
||||
aes-128-gcm, aes-192-gcm, aes-256-gcm,
|
||||
rc4-md5, aes-128-cfb, aes-192-cfb, aes-256-cfb,
|
||||
aes-128-ctr, aes-192-ctr, aes-256-ctr, bf-cfb,
|
||||
@@ -67,6 +69,13 @@ salsa20, chacha20 and chacha20-ietf.
|
||||
+
|
||||
The default cipher is 'chacha20-ietf-poly1305'.
|
||||
+
|
||||
The '2022-blake3-*' ciphers implement Shadowsocks 2022 (SIP022) and are
|
||||
the recommended choice: they provide full replay protection and
|
||||
session-based UDP relay. They require a base64-encoded pre-shared key of
|
||||
exactly the cipher's key size (16 bytes for 2022-blake3-aes-128-gcm, 32
|
||||
bytes for the others) given with *-k*; passwords are not accepted and are
|
||||
never stretched into a key. Generate one with `openssl rand -base64 32`.
|
||||
+
|
||||
If built with PolarSSL or custom OpenSSL libraries, some of
|
||||
these ciphers may not work.
|
||||
|
||||
|
||||
+19
-1
@@ -17,6 +17,7 @@ SYNOPSIS
|
||||
[--mtu <MTU>] [--no-delay]
|
||||
[--plugin <plugin_name>] [--plugin-opts <plugin_options>]
|
||||
[--password <password>] [--key <key_in_base64>]
|
||||
[--server-url <ss_url>]
|
||||
|
||||
DESCRIPTION
|
||||
-----------
|
||||
@@ -48,11 +49,21 @@ Set the password. The server and the client should use the same password.
|
||||
--key <key_in_base64>::
|
||||
Set the key directly. The key should be encoded with URL-safe Base64.
|
||||
|
||||
--server-url <ss_url>::
|
||||
Take the server address, port, cipher, password and any SIP003 plugin
|
||||
from a single 'ss://' URL, as produced by most clients and by
|
||||
*shadowsocks-rust*'s `ssurl`. Both the SIP002 form
|
||||
('ss://base64(method:password)@host:port/?plugin=...#tag') and the older
|
||||
'ss://base64(method:password@host:port)' form are accepted. Options given
|
||||
later on the command line override the values taken from the URL.
|
||||
|
||||
-m <encrypt_method>::
|
||||
Set the cipher.
|
||||
+
|
||||
*Shadowsocks-libev* accepts 19 different ciphers:
|
||||
*Shadowsocks-libev* accepts 22 different ciphers:
|
||||
+
|
||||
2022-blake3-aes-128-gcm, 2022-blake3-aes-256-gcm,
|
||||
2022-blake3-chacha20-poly1305,
|
||||
aes-128-gcm, aes-192-gcm, aes-256-gcm,
|
||||
rc4-md5, aes-128-cfb, aes-192-cfb, aes-256-cfb,
|
||||
aes-128-ctr, aes-192-ctr, aes-256-ctr, bf-cfb,
|
||||
@@ -62,6 +73,13 @@ salsa20, chacha20 and chacha20-ietf.
|
||||
+
|
||||
The default cipher is 'chacha20-ietf-poly1305'.
|
||||
+
|
||||
The '2022-blake3-*' ciphers implement Shadowsocks 2022 (SIP022) and are
|
||||
the recommended choice: they provide full replay protection and
|
||||
session-based UDP relay. They require a base64-encoded pre-shared key of
|
||||
exactly the cipher's key size (16 bytes for 2022-blake3-aes-128-gcm, 32
|
||||
bytes for the others) given with *-k*; passwords are not accepted and are
|
||||
never stretched into a key. Generate one with `openssl rand -base64 32`.
|
||||
+
|
||||
If built with PolarSSL or custom OpenSSL libraries, some of
|
||||
these ciphers may not work.
|
||||
|
||||
|
||||
+10
-1
@@ -43,8 +43,10 @@ Set the password. The server and the client should use the same password.
|
||||
-m <encrypt_method>::
|
||||
Set the cipher.
|
||||
+
|
||||
*Shadowsocks-libev* accepts 19 different ciphers:
|
||||
*Shadowsocks-libev* accepts 22 different ciphers:
|
||||
+
|
||||
2022-blake3-aes-128-gcm, 2022-blake3-aes-256-gcm,
|
||||
2022-blake3-chacha20-poly1305,
|
||||
aes-128-gcm, aes-192-gcm, aes-256-gcm,
|
||||
rc4-md5, aes-128-cfb, aes-192-cfb, aes-256-cfb,
|
||||
aes-128-ctr, aes-192-ctr, aes-256-ctr, bf-cfb,
|
||||
@@ -54,6 +56,13 @@ salsa20, chacha20 and chacha20-ietf.
|
||||
+
|
||||
The default cipher is 'chacha20-ietf-poly1305'.
|
||||
+
|
||||
The '2022-blake3-*' ciphers implement Shadowsocks 2022 (SIP022) and are
|
||||
the recommended choice: they provide full replay protection and
|
||||
session-based UDP relay. They require a base64-encoded pre-shared key of
|
||||
exactly the cipher's key size (16 bytes for 2022-blake3-aes-128-gcm, 32
|
||||
bytes for the others) given with *-k*; passwords are not accepted and are
|
||||
never stretched into a key. Generate one with `openssl rand -base64 32`.
|
||||
+
|
||||
If built with PolarSSL or custom OpenSSL libraries, some of
|
||||
these ciphers may not work.
|
||||
|
||||
|
||||
+10
-1
@@ -50,8 +50,10 @@ Set the key directly. The key should be encoded with URL-safe Base64.
|
||||
-m <encrypt_method>::
|
||||
Set the cipher.
|
||||
+
|
||||
*Shadowsocks-libev* accepts 19 different ciphers:
|
||||
*Shadowsocks-libev* accepts 22 different ciphers:
|
||||
+
|
||||
2022-blake3-aes-128-gcm, 2022-blake3-aes-256-gcm,
|
||||
2022-blake3-chacha20-poly1305,
|
||||
aes-128-gcm, aes-192-gcm, aes-256-gcm,
|
||||
rc4-md5, aes-128-cfb, aes-192-cfb, aes-256-cfb,
|
||||
aes-128-ctr, aes-192-ctr, aes-256-ctr, bf-cfb,
|
||||
@@ -61,6 +63,13 @@ salsa20, chacha20 and chacha20-ietf.
|
||||
+
|
||||
The default cipher is 'chacha20-ietf-poly1305'.
|
||||
+
|
||||
The '2022-blake3-*' ciphers implement Shadowsocks 2022 (SIP022) and are
|
||||
the recommended choice: they provide full replay protection and
|
||||
session-based UDP relay. They require a base64-encoded pre-shared key of
|
||||
exactly the cipher's key size (16 bytes for 2022-blake3-aes-128-gcm, 32
|
||||
bytes for the others) given with *-k*; passwords are not accepted and are
|
||||
never stretched into a key. Generate one with `openssl rand -base64 32`.
|
||||
+
|
||||
If built with PolarSSL or custom OpenSSL libraries, some of
|
||||
these ciphers may not work.
|
||||
|
||||
|
||||
+10
-1
@@ -48,8 +48,10 @@ Set the key directly. The key should be encoded with URL-safe Base64.
|
||||
-m <encrypt_method>::
|
||||
Set the cipher.
|
||||
+
|
||||
*Shadowsocks-libev* accepts 19 different ciphers:
|
||||
*Shadowsocks-libev* accepts 22 different ciphers:
|
||||
+
|
||||
2022-blake3-aes-128-gcm, 2022-blake3-aes-256-gcm,
|
||||
2022-blake3-chacha20-poly1305,
|
||||
aes-128-gcm, aes-192-gcm, aes-256-gcm,
|
||||
rc4-md5, aes-128-cfb, aes-192-cfb, aes-256-cfb,
|
||||
aes-128-ctr, aes-192-ctr, aes-256-ctr, bf-cfb,
|
||||
@@ -57,6 +59,13 @@ camellia-128-cfb, camellia-192-cfb, camellia-256-cfb,
|
||||
chacha20-ietf-poly1305, xchacha20-ietf-poly1305,
|
||||
salsa20, chacha20 and chacha20-ietf.
|
||||
+
|
||||
The '2022-blake3-*' ciphers implement Shadowsocks 2022 (SIP022) and are
|
||||
the recommended choice: they provide full replay protection and
|
||||
session-based UDP relay. They require a base64-encoded pre-shared key of
|
||||
exactly the cipher's key size (16 bytes for 2022-blake3-aes-128-gcm, 32
|
||||
bytes for the others) given with *-k*; passwords are not accepted and are
|
||||
never stretched into a key. Generate one with `openssl rand -base64 32`.
|
||||
+
|
||||
If built with PolarSSL or custom OpenSSL libraries, some of
|
||||
these ciphers may not work.
|
||||
|
||||
|
||||
+10
-1
@@ -50,8 +50,10 @@ Set the key directly. The key should be encoded with URL-safe Base64.
|
||||
-m <encrypt_method>::
|
||||
Set the cipher.
|
||||
+
|
||||
*Shadowsocks-libev* accepts 19 different ciphers:
|
||||
*Shadowsocks-libev* accepts 22 different ciphers:
|
||||
+
|
||||
2022-blake3-aes-128-gcm, 2022-blake3-aes-256-gcm,
|
||||
2022-blake3-chacha20-poly1305,
|
||||
aes-128-gcm, aes-192-gcm, aes-256-gcm,
|
||||
rc4-md5, aes-128-cfb, aes-192-cfb, aes-256-cfb,
|
||||
aes-128-ctr, aes-192-ctr, aes-256-ctr, bf-cfb,
|
||||
@@ -61,6 +63,13 @@ salsa20, chacha20 and chacha20-ietf.
|
||||
+
|
||||
The default cipher is 'chacha20-ietf-poly1305'.
|
||||
+
|
||||
The '2022-blake3-*' ciphers implement Shadowsocks 2022 (SIP022) and are
|
||||
the recommended choice: they provide full replay protection and
|
||||
session-based UDP relay. They require a base64-encoded pre-shared key of
|
||||
exactly the cipher's key size (16 bytes for 2022-blake3-aes-128-gcm, 32
|
||||
bytes for the others) given with *-k*; passwords are not accepted and are
|
||||
never stretched into a key. Generate one with `openssl rand -base64 32`.
|
||||
+
|
||||
If built with PolarSSL or custom OpenSSL libraries, some of
|
||||
these ciphers may not work.
|
||||
|
||||
|
||||
@@ -16,10 +16,29 @@ set(SS_ACL_SOURCE
|
||||
set(SS_CRYPTO_SOURCE
|
||||
crypto.c
|
||||
aead.c
|
||||
aead2022.c
|
||||
aead2022_udp.c
|
||||
stream.c
|
||||
base64.c
|
||||
)
|
||||
|
||||
# BLAKE3 (vendored) provides the key derivation for the 2022 ciphers. Only
|
||||
# the portable backend is built: the SIMD ones need per-file ISA flags and
|
||||
# extra assembly, which is not worth it for key derivation.
|
||||
add_library(blake3 STATIC
|
||||
blake3/blake3.c
|
||||
blake3/blake3_dispatch.c
|
||||
blake3/blake3_portable.c
|
||||
)
|
||||
target_compile_definitions(blake3 PUBLIC
|
||||
BLAKE3_NO_AVX512 BLAKE3_NO_AVX2 BLAKE3_NO_SSE41 BLAKE3_NO_SSE2
|
||||
BLAKE3_USE_NEON=0
|
||||
)
|
||||
target_include_directories(blake3 PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
# vendored upstream code is not held to the project's -Werror budget
|
||||
target_compile_options(blake3 PRIVATE -Wno-error -Wno-unused-function)
|
||||
set_target_properties(blake3 PROPERTIES POSITION_INDEPENDENT_CODE ON)
|
||||
|
||||
set(SS_PLUGIN_SOURCE
|
||||
plugin.c
|
||||
)
|
||||
@@ -40,6 +59,7 @@ set(LIBSHADOWSOCKS_LIBEV_SOURCE
|
||||
udprelay.c
|
||||
cache.c
|
||||
local.c
|
||||
ssurl.c
|
||||
${SS_CRYPTO_SOURCE}
|
||||
${SS_PLUGIN_SOURCE}
|
||||
${SS_ACL_SOURCE}
|
||||
@@ -111,6 +131,7 @@ find_library(LIBPCRE2 libpcre2-8.a)
|
||||
# Dependencies we need for static and shared
|
||||
list(APPEND DEPS
|
||||
m
|
||||
blake3
|
||||
bloom
|
||||
${LIBEV}
|
||||
${LIBUDNS}
|
||||
@@ -138,6 +159,7 @@ find_library(LIBPCRE2_SHARED pcre2-8)
|
||||
if (WITH_EMBEDDED_SRC)
|
||||
list(APPEND DEPS_SHARED
|
||||
m
|
||||
blake3
|
||||
bloom
|
||||
cork
|
||||
ipset
|
||||
@@ -154,6 +176,7 @@ find_library(LIBCORK_SHARED cork)
|
||||
find_library(LIBCORKIPSET_SHARED corkipset)
|
||||
list(APPEND DEPS_SHARED
|
||||
m
|
||||
blake3
|
||||
${LIBBLOOM_SHARED}
|
||||
${LIBCORK_SHARED}
|
||||
${LIBCORKIPSET_SHARED}
|
||||
|
||||
+75
-32
@@ -37,23 +37,11 @@
|
||||
|
||||
#include "ppbloom.h"
|
||||
#include "aead.h"
|
||||
#include "aead_internal.h"
|
||||
#include "utils.h"
|
||||
#include "winsock.h"
|
||||
|
||||
#define NONE (-1)
|
||||
#define AES128GCM 0
|
||||
#define AES192GCM 1
|
||||
#define AES256GCM 2
|
||||
/*
|
||||
* methods above requires gcm context
|
||||
* methods below doesn't require it,
|
||||
* then we need to fake one
|
||||
*/
|
||||
#define CHACHA20POLY1305IETF 3
|
||||
|
||||
#ifdef FS_HAVE_XCHACHA20IETF
|
||||
#define XCHACHA20POLY1305IETF 4
|
||||
#endif
|
||||
|
||||
#define CHUNK_SIZE_LEN 2
|
||||
#define CHUNK_SIZE_MASK 0x3FFF
|
||||
@@ -108,7 +96,10 @@ const char *supported_aead_ciphers[AEAD_CIPHER_NUM] = {
|
||||
"aes-128-gcm",
|
||||
"aes-192-gcm",
|
||||
"aes-256-gcm",
|
||||
"2022-blake3-aes-128-gcm",
|
||||
"2022-blake3-aes-256-gcm",
|
||||
"chacha20-ietf-poly1305",
|
||||
"2022-blake3-chacha20-poly1305",
|
||||
#ifdef FS_HAVE_XCHACHA20IETF
|
||||
"xchacha20-ietf-poly1305"
|
||||
#endif
|
||||
@@ -121,6 +112,9 @@ static const char *supported_aead_ciphers_mbedtls[AEAD_CIPHER_NUM] = {
|
||||
"AES-128-GCM",
|
||||
"AES-192-GCM",
|
||||
"AES-256-GCM",
|
||||
"AES-128-GCM",
|
||||
"AES-256-GCM",
|
||||
CIPHER_UNSUPPORTED,
|
||||
CIPHER_UNSUPPORTED,
|
||||
#ifdef FS_HAVE_XCHACHA20IETF
|
||||
CIPHER_UNSUPPORTED
|
||||
@@ -128,27 +122,33 @@ static const char *supported_aead_ciphers_mbedtls[AEAD_CIPHER_NUM] = {
|
||||
};
|
||||
|
||||
static const int supported_aead_ciphers_nonce_size[AEAD_CIPHER_NUM] = {
|
||||
12, 12, 12, 12,
|
||||
12, 12, 12, 12, 12, 12, 12,
|
||||
#ifdef FS_HAVE_XCHACHA20IETF
|
||||
24
|
||||
#endif
|
||||
};
|
||||
|
||||
static const int supported_aead_ciphers_key_size[AEAD_CIPHER_NUM] = {
|
||||
16, 24, 32, 32,
|
||||
16, 24, 32, 16, 32, 32, 32,
|
||||
#ifdef FS_HAVE_XCHACHA20IETF
|
||||
32
|
||||
#endif
|
||||
};
|
||||
|
||||
static const int supported_aead_ciphers_tag_size[AEAD_CIPHER_NUM] = {
|
||||
16, 16, 16, 16,
|
||||
16, 16, 16, 16, 16, 16, 16,
|
||||
#ifdef FS_HAVE_XCHACHA20IETF
|
||||
16
|
||||
#endif
|
||||
};
|
||||
|
||||
static int
|
||||
int
|
||||
aead_is_2022(const cipher_t *cipher)
|
||||
{
|
||||
return cipher != NULL && is_aead_2022(cipher->method);
|
||||
}
|
||||
|
||||
int
|
||||
aead_cipher_encrypt(cipher_ctx_t *cipher_ctx,
|
||||
uint8_t *c,
|
||||
size_t *clen,
|
||||
@@ -177,6 +177,8 @@ aead_cipher_encrypt(cipher_ctx_t *cipher_ctx,
|
||||
// Otherwise, just use the mbedTLS one with crappy AES-NI.
|
||||
case AES192GCM:
|
||||
case AES128GCM:
|
||||
case AES128GCM2022:
|
||||
case AES256GCM2022:
|
||||
#if MBEDTLS_VERSION_NUMBER < 0x03000000
|
||||
err = mbedtls_cipher_auth_encrypt(cipher_ctx->evp, n, nlen, ad, adlen,
|
||||
m, mlen, c, clen, c + mlen, tlen);
|
||||
@@ -187,6 +189,7 @@ aead_cipher_encrypt(cipher_ctx_t *cipher_ctx,
|
||||
#endif
|
||||
break;
|
||||
case CHACHA20POLY1305IETF:
|
||||
case CHACHA20POLY1305IETF2022:
|
||||
err = crypto_aead_chacha20poly1305_ietf_encrypt(c, &long_clen, m, mlen,
|
||||
ad, adlen, NULL, n, k);
|
||||
*clen = (size_t)long_clen;
|
||||
@@ -205,7 +208,7 @@ aead_cipher_encrypt(cipher_ctx_t *cipher_ctx,
|
||||
return err;
|
||||
}
|
||||
|
||||
static int
|
||||
int
|
||||
aead_cipher_decrypt(cipher_ctx_t *cipher_ctx,
|
||||
uint8_t *p, size_t *plen,
|
||||
uint8_t *m, size_t mlen,
|
||||
@@ -230,6 +233,8 @@ aead_cipher_decrypt(cipher_ctx_t *cipher_ctx,
|
||||
// Otherwise, just use the mbedTLS one with crappy AES-NI.
|
||||
case AES192GCM:
|
||||
case AES128GCM:
|
||||
case AES128GCM2022:
|
||||
case AES256GCM2022:
|
||||
#if MBEDTLS_VERSION_NUMBER < 0x03000000
|
||||
err = mbedtls_cipher_auth_decrypt(cipher_ctx->evp, n, nlen, ad, adlen,
|
||||
m, mlen - tlen, p, plen, m + mlen - tlen, tlen);
|
||||
@@ -239,6 +244,7 @@ aead_cipher_decrypt(cipher_ctx_t *cipher_ctx,
|
||||
#endif
|
||||
break;
|
||||
case CHACHA20POLY1305IETF:
|
||||
case CHACHA20POLY1305IETF2022:
|
||||
err = crypto_aead_chacha20poly1305_ietf_decrypt(p, &long_plen, NULL, m, mlen,
|
||||
ad, adlen, n, k);
|
||||
*plen = (size_t)long_plen; // it's safe to cast 64bit to 32bit length here
|
||||
@@ -290,21 +296,27 @@ aead_get_cipher_type(int method)
|
||||
return mbedtls_cipher_info_from_string(mbedtlsname);
|
||||
}
|
||||
|
||||
static void
|
||||
void
|
||||
aead_cipher_ctx_set_key(cipher_ctx_t *cipher_ctx, int enc)
|
||||
{
|
||||
const digest_type_t *md = mbedtls_md_info_from_string("SHA1");
|
||||
if (md == NULL) {
|
||||
FATAL("SHA1 Digest not found in crypto library");
|
||||
}
|
||||
if (is_aead_2022(cipher_ctx->cipher->method)) {
|
||||
ss2022_derive_subkey(cipher_ctx->cipher->key, cipher_ctx->cipher->key_len,
|
||||
cipher_ctx->salt, cipher_ctx->cipher->key_len,
|
||||
cipher_ctx->skey, cipher_ctx->cipher->key_len);
|
||||
} else {
|
||||
const digest_type_t *md = mbedtls_md_info_from_string("SHA1");
|
||||
if (md == NULL) {
|
||||
FATAL("SHA1 Digest not found in crypto library");
|
||||
}
|
||||
|
||||
int err = crypto_hkdf(md,
|
||||
cipher_ctx->salt, cipher_ctx->cipher->key_len,
|
||||
cipher_ctx->cipher->key, cipher_ctx->cipher->key_len,
|
||||
(uint8_t *)SUBKEY_INFO, strlen(SUBKEY_INFO),
|
||||
cipher_ctx->skey, cipher_ctx->cipher->key_len);
|
||||
if (err) {
|
||||
FATAL("Unable to generate subkey");
|
||||
int err = crypto_hkdf(md,
|
||||
cipher_ctx->salt, cipher_ctx->cipher->key_len,
|
||||
cipher_ctx->cipher->key, cipher_ctx->cipher->key_len,
|
||||
(uint8_t *)SUBKEY_INFO, strlen(SUBKEY_INFO),
|
||||
cipher_ctx->skey, cipher_ctx->cipher->key_len);
|
||||
if (err) {
|
||||
FATAL("Unable to generate subkey");
|
||||
}
|
||||
}
|
||||
|
||||
memset(cipher_ctx->nonce, 0, cipher_ctx->cipher->nonce_len);
|
||||
@@ -374,6 +386,7 @@ aead_ctx_init(cipher_t *cipher, cipher_ctx_t *cipher_ctx, int enc)
|
||||
{
|
||||
sodium_memzero(cipher_ctx, sizeof(cipher_ctx_t));
|
||||
cipher_ctx->cipher = cipher;
|
||||
cipher_ctx->role = aead_get_role();
|
||||
|
||||
aead_cipher_ctx_init(cipher_ctx, cipher->method, enc);
|
||||
|
||||
@@ -407,6 +420,11 @@ aead_ctx_release(cipher_ctx_t *cipher_ctx)
|
||||
int
|
||||
aead_encrypt_all(buffer_t *plaintext, cipher_t *cipher, size_t capacity)
|
||||
{
|
||||
if (is_aead_2022(cipher->method)) {
|
||||
LOGE("AEAD-2022 UDP requires the session interface");
|
||||
return CRYPTO_ERROR;
|
||||
}
|
||||
|
||||
cipher_ctx_t cipher_ctx;
|
||||
aead_ctx_init(cipher, &cipher_ctx, 1);
|
||||
|
||||
@@ -448,6 +466,11 @@ aead_encrypt_all(buffer_t *plaintext, cipher_t *cipher, size_t capacity)
|
||||
int
|
||||
aead_decrypt_all(buffer_t *ciphertext, cipher_t *cipher, size_t capacity)
|
||||
{
|
||||
if (is_aead_2022(cipher->method)) {
|
||||
LOGE("AEAD-2022 UDP requires the session interface");
|
||||
return CRYPTO_ERROR;
|
||||
}
|
||||
|
||||
size_t salt_len = cipher->key_len;
|
||||
size_t tag_len = cipher->tag_len;
|
||||
int err = CRYPTO_OK;
|
||||
@@ -541,6 +564,9 @@ aead_encrypt(buffer_t *plaintext, cipher_ctx_t *cipher_ctx, size_t capacity)
|
||||
if (cipher_ctx == NULL)
|
||||
return CRYPTO_ERROR;
|
||||
|
||||
if (is_aead_2022(cipher_ctx->cipher->method))
|
||||
return ss2022_tcp_encrypt(plaintext, cipher_ctx, capacity);
|
||||
|
||||
if (plaintext->len == 0) {
|
||||
return CRYPTO_OK;
|
||||
}
|
||||
@@ -635,6 +661,9 @@ aead_decrypt(buffer_t *ciphertext, cipher_ctx_t *cipher_ctx, size_t capacity)
|
||||
int err = CRYPTO_OK;
|
||||
cipher_t *cipher = cipher_ctx->cipher;
|
||||
|
||||
if (is_aead_2022(cipher->method))
|
||||
return ss2022_tcp_decrypt(ciphertext, cipher_ctx, capacity);
|
||||
|
||||
size_t salt_len = cipher->key_len;
|
||||
|
||||
if (cipher_ctx->chunk == NULL) {
|
||||
@@ -741,12 +770,26 @@ aead_key_init(int method, const char *pass, const char *key)
|
||||
FATAL("Cannot initialize cipher");
|
||||
}
|
||||
|
||||
if (key != NULL)
|
||||
if (is_aead_2022(method)) {
|
||||
/*
|
||||
* SIP022 requires a cryptographically-secure fixed-length key,
|
||||
* provided in base64. EVP_BytesToKey-style password derivation
|
||||
* MUST NOT be used, so the password (or key) field must hold the
|
||||
* base64 PSK itself.
|
||||
*/
|
||||
const char *psk = (key != NULL) ? key : pass;
|
||||
if (psk == NULL) {
|
||||
FATAL("AEAD-2022 ciphers require a base64 pre-shared key");
|
||||
}
|
||||
cipher->key_len = crypto_parse_psk(psk, cipher->key,
|
||||
supported_aead_ciphers_key_size[method]);
|
||||
} else if (key != NULL) {
|
||||
cipher->key_len = crypto_parse_key(key, cipher->key,
|
||||
supported_aead_ciphers_key_size[method]);
|
||||
else
|
||||
} else {
|
||||
cipher->key_len = crypto_derive_key(pass, cipher->key,
|
||||
supported_aead_ciphers_key_size[method]);
|
||||
}
|
||||
|
||||
if (cipher->key_len == 0) {
|
||||
FATAL("Cannot generate key and nonce");
|
||||
|
||||
+10
-2
@@ -28,9 +28,9 @@
|
||||
// currently, XCHACHA20POLY1305IETF is not released yet
|
||||
// XCHACHA20POLY1305 is removed in upstream
|
||||
#ifdef FS_HAVE_XCHACHA20IETF
|
||||
#define AEAD_CIPHER_NUM 5
|
||||
#define AEAD_CIPHER_NUM 8
|
||||
#else
|
||||
#define AEAD_CIPHER_NUM 4
|
||||
#define AEAD_CIPHER_NUM 7
|
||||
#endif
|
||||
|
||||
int aead_encrypt_all(buffer_t *, cipher_t *, size_t);
|
||||
@@ -44,4 +44,12 @@ void aead_ctx_release(cipher_ctx_t *);
|
||||
|
||||
cipher_t *aead_init(const char *pass, const char *key, const char *method);
|
||||
|
||||
/* AEAD-2022 (SIP022) */
|
||||
int aead_is_2022(const cipher_t *cipher);
|
||||
void aead_set_role(int role); /* override compile-time default; used by tests */
|
||||
|
||||
int aead_2022_encrypt_udp(buffer_t *, cipher_t *, size_t, void **);
|
||||
int aead_2022_decrypt_udp(buffer_t *, cipher_t *, size_t, void **);
|
||||
void aead_2022_udp_session_release(void *);
|
||||
|
||||
#endif // _AEAD_H
|
||||
+643
@@ -0,0 +1,643 @@
|
||||
/*
|
||||
* aead2022.c - Shadowsocks 2022 Edition (SIP022) AEAD ciphers
|
||||
*
|
||||
* Copyright (C) 2013 - 2026, Max Lv <max.c.lv@gmail.com>
|
||||
*
|
||||
* This file is part of the shadowsocks-libev.
|
||||
*
|
||||
* shadowsocks-libev is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* shadowsocks-libev is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with shadowsocks-libev; see the file COPYING. If not, see
|
||||
* <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* Spec: https://github.com/Shadowsocks-NET/shadowsocks-specs/blob/main/2022-1-shadowsocks-2022-edition.md
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#include <time.h>
|
||||
|
||||
#include <sodium.h>
|
||||
|
||||
#include "aead.h"
|
||||
#include "aead_internal.h"
|
||||
#include "cache.h"
|
||||
#include "utils.h"
|
||||
#include "blake3/blake3.h"
|
||||
|
||||
/* SIP022 constants */
|
||||
#define SS2022_HDR_TYPE_CLIENT 0
|
||||
#define SS2022_HDR_TYPE_SERVER 1
|
||||
|
||||
/* TCP decrypt state machine (cipher_ctx_t.hdr_stage) */
|
||||
#define SS2022_STAGE_FIXED_HDR 0
|
||||
#define SS2022_STAGE_VAR_HDR 1 /* request: variable header; response: first payload */
|
||||
#define SS2022_STAGE_STREAM 2
|
||||
|
||||
/* request fixed-length header: type(1) + timestamp(8) + length(2) */
|
||||
#define SS2022_REQ_FIXED_LEN 11
|
||||
/* response fixed-length header: type(1) + timestamp(8) + request salt + length(2) */
|
||||
#define SS2022_RESP_FIXED_LEN(salt_len) (11 + (salt_len))
|
||||
#define SS2022_FIXED_BUF_LEN (11 + MAX_KEY_LENGTH)
|
||||
|
||||
#define SS2022_CHUNK_LEN_SIZE 2
|
||||
#define SS2022_CHUNK_MASK 0xFFFF
|
||||
#define SS2022_TAG_LEN 16
|
||||
#define SS2022_SALT_POOL_CAP 65536
|
||||
|
||||
/*
|
||||
* Role handling. AEAD-2022 is asymmetric: the client encrypts request
|
||||
* streams and decrypts response streams, the server does the opposite.
|
||||
* The default follows the module this file is compiled into; unit tests
|
||||
* override it with aead_set_role().
|
||||
*/
|
||||
#ifdef MODULE_REMOTE
|
||||
static int aead_role = CRYPTO_ROLE_SERVER;
|
||||
#else
|
||||
static int aead_role = CRYPTO_ROLE_CLIENT;
|
||||
#endif
|
||||
|
||||
void
|
||||
aead_set_role(int role)
|
||||
{
|
||||
aead_role = role;
|
||||
}
|
||||
|
||||
int
|
||||
aead_get_role(void)
|
||||
{
|
||||
return aead_role;
|
||||
}
|
||||
|
||||
/*
|
||||
* session_subkey := blake3::derive_key(context, key || salt)
|
||||
* salt is the per-stream random salt for TCP, or the session ID for UDP.
|
||||
*/
|
||||
void
|
||||
ss2022_derive_subkey(const uint8_t *key, size_t key_len,
|
||||
const uint8_t *salt, size_t salt_len,
|
||||
uint8_t *subkey, size_t subkey_len)
|
||||
{
|
||||
uint8_t material[2 * MAX_KEY_LENGTH];
|
||||
blake3_hasher hasher;
|
||||
|
||||
memcpy(material, key, key_len);
|
||||
memcpy(material + key_len, salt, salt_len);
|
||||
|
||||
blake3_hasher_init_derive_key(&hasher, SS2022_SUBKEY_CTX);
|
||||
blake3_hasher_update(&hasher, material, key_len + salt_len);
|
||||
blake3_hasher_finalize(&hasher, subkey, subkey_len);
|
||||
|
||||
sodium_memzero(material, sizeof(material));
|
||||
}
|
||||
|
||||
/*
|
||||
* Server-side salt pool. SIP022 requires exact salt matching with 60s of
|
||||
* retention and forbids bloom filters (false positives are not allowed),
|
||||
* so ppbloom cannot be reused here.
|
||||
*/
|
||||
static struct cache *ss2022_salt_pool = NULL;
|
||||
|
||||
static int
|
||||
ss2022_salt_check_and_add(const uint8_t *salt, size_t salt_len)
|
||||
{
|
||||
if (ss2022_salt_pool == NULL) {
|
||||
if (cache_create(&ss2022_salt_pool, SS2022_SALT_POOL_CAP, NULL) != 0)
|
||||
return CRYPTO_ERROR;
|
||||
}
|
||||
|
||||
cache_clear(ss2022_salt_pool, SS2022_SALT_RETENTION);
|
||||
|
||||
if (cache_key_exist(ss2022_salt_pool, (char *)salt, salt_len)) {
|
||||
LOGE("crypto: AEAD-2022: repeat salt detected");
|
||||
return CRYPTO_ERROR;
|
||||
}
|
||||
|
||||
cache_insert(ss2022_salt_pool, (char *)salt, salt_len, NULL);
|
||||
return CRYPTO_OK;
|
||||
}
|
||||
|
||||
static int
|
||||
ss2022_check_timestamp(uint64_t ts)
|
||||
{
|
||||
uint64_t now = (uint64_t)time(NULL);
|
||||
uint64_t diff = now > ts ? now - ts : ts - now;
|
||||
return diff <= SS2022_TIME_WINDOW ? CRYPTO_OK : CRYPTO_ERROR;
|
||||
}
|
||||
|
||||
static void
|
||||
store16_be(uint8_t *p, uint16_t v)
|
||||
{
|
||||
p[0] = (uint8_t)(v >> 8);
|
||||
p[1] = (uint8_t)v;
|
||||
}
|
||||
|
||||
static void
|
||||
store64_be(uint8_t *p, uint64_t v)
|
||||
{
|
||||
for (int i = 7; i >= 0; i--) {
|
||||
p[i] = (uint8_t)(v & 0xff);
|
||||
v >>= 8;
|
||||
}
|
||||
}
|
||||
|
||||
static uint64_t
|
||||
load64_be(const uint8_t *p)
|
||||
{
|
||||
uint64_t v = 0;
|
||||
for (int i = 0; i < 8; i++)
|
||||
v = (v << 8) | p[i];
|
||||
return v;
|
||||
}
|
||||
|
||||
/*
|
||||
* Length of the SOCKS5 address (ATYP + address + port) at the head of buf,
|
||||
* or 0 if it is malformed or truncated.
|
||||
*/
|
||||
static size_t
|
||||
ss2022_socks_addr_len(const uint8_t *buf, size_t len)
|
||||
{
|
||||
if (len < 1)
|
||||
return 0;
|
||||
|
||||
size_t addr_len;
|
||||
switch (buf[0] & ADDRTYPE_MASK) {
|
||||
case 1: /* IPv4 */
|
||||
addr_len = 1 + 4;
|
||||
break;
|
||||
case 4: /* IPv6 */
|
||||
addr_len = 1 + 16;
|
||||
break;
|
||||
case 3: /* domain */
|
||||
if (len < 2)
|
||||
return 0;
|
||||
addr_len = 2 + buf[1];
|
||||
break;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
|
||||
return (len >= addr_len + 2) ? addr_len + 2 : 0;
|
||||
}
|
||||
|
||||
/* Encrypt one chunk at c, then advance the counting nonce. */
|
||||
static int
|
||||
ss2022_seal(cipher_ctx_t *ctx, uint8_t *c, const uint8_t *p, size_t plen)
|
||||
{
|
||||
size_t clen = plen + SS2022_TAG_LEN;
|
||||
int err = aead_cipher_encrypt(ctx, c, &clen, (uint8_t *)p, plen,
|
||||
NULL, 0, ctx->nonce, ctx->skey);
|
||||
if (err)
|
||||
return -1;
|
||||
sodium_increment(ctx->nonce, ctx->cipher->nonce_len);
|
||||
return (int)clen;
|
||||
}
|
||||
|
||||
/* Decrypt one chunk (mlen includes the tag), then advance the nonce. */
|
||||
static int
|
||||
ss2022_open(cipher_ctx_t *ctx, uint8_t *p, const uint8_t *c, size_t mlen)
|
||||
{
|
||||
size_t plen = mlen - SS2022_TAG_LEN;
|
||||
int err = aead_cipher_decrypt(ctx, p, &plen, (uint8_t *)c, mlen,
|
||||
NULL, 0, ctx->nonce, ctx->skey);
|
||||
if (err)
|
||||
return -1;
|
||||
sodium_increment(ctx->nonce, ctx->cipher->nonce_len);
|
||||
return (int)plen;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ TCP */
|
||||
|
||||
/*
|
||||
* Build the request variable-length header body:
|
||||
* ATYP || address || port || padding length || padding || initial payload
|
||||
* The caller's first plaintext holds the SOCKS address, optionally followed
|
||||
* by initial payload bytes. SIP022 requires padding when there is no initial
|
||||
* payload, so random padding is added in that case.
|
||||
*/
|
||||
static int
|
||||
ss2022_build_req_varhdr(const uint8_t *first, size_t first_len,
|
||||
uint8_t **out, size_t *out_len)
|
||||
{
|
||||
size_t addr_len = ss2022_socks_addr_len(first, first_len);
|
||||
if (addr_len == 0) {
|
||||
LOGE("AEAD-2022: malformed SOCKS address in request header");
|
||||
return CRYPTO_ERROR;
|
||||
}
|
||||
|
||||
size_t payload_len = first_len - addr_len;
|
||||
uint16_t pad_len = 0;
|
||||
|
||||
if (payload_len == 0) {
|
||||
uint16_t r;
|
||||
rand_bytes(&r, sizeof(r));
|
||||
pad_len = (uint16_t)(r % SS2022_MAX_PADDING) + 1;
|
||||
}
|
||||
|
||||
size_t total = addr_len + 2 + pad_len + payload_len;
|
||||
uint8_t *buf = ss_malloc(total);
|
||||
|
||||
memcpy(buf, first, addr_len);
|
||||
store16_be(buf + addr_len, pad_len);
|
||||
if (pad_len > 0)
|
||||
rand_bytes(buf + addr_len + 2, pad_len);
|
||||
if (payload_len > 0)
|
||||
memcpy(buf + addr_len + 2 + pad_len, first + addr_len, payload_len);
|
||||
|
||||
*out = buf;
|
||||
*out_len = total;
|
||||
return CRYPTO_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
* Emit salt and header chunks for the first write of a stream. The salt and
|
||||
* headers are written into one buffer so the caller sends them in a single
|
||||
* write, as SIP022 requires.
|
||||
*/
|
||||
static int
|
||||
ss2022_emit_header(cipher_ctx_t *ctx, buffer_t *out, size_t *out_len,
|
||||
const uint8_t *first, size_t first_len, size_t capacity)
|
||||
{
|
||||
size_t salt_len = ctx->cipher->key_len;
|
||||
uint64_t now = (uint64_t)time(NULL);
|
||||
uint8_t fixed[SS2022_FIXED_BUF_LEN];
|
||||
size_t fixed_len;
|
||||
uint8_t *body = NULL;
|
||||
size_t body_len = 0;
|
||||
int body_owned = 0;
|
||||
int rc = CRYPTO_ERROR;
|
||||
|
||||
if (ctx->role == CRYPTO_ROLE_CLIENT) {
|
||||
if (ss2022_build_req_varhdr(first, first_len, &body, &body_len)
|
||||
!= CRYPTO_OK)
|
||||
return CRYPTO_ERROR;
|
||||
body_owned = 1;
|
||||
|
||||
if (body_len > SS2022_CHUNK_MASK) {
|
||||
LOGE("AEAD-2022: request header too large");
|
||||
goto done;
|
||||
}
|
||||
|
||||
fixed_len = SS2022_REQ_FIXED_LEN;
|
||||
fixed[0] = SS2022_HDR_TYPE_CLIENT;
|
||||
store64_be(fixed + 1, now);
|
||||
store16_be(fixed + 9, (uint16_t)body_len);
|
||||
} else {
|
||||
/*
|
||||
* The response fixed header doubles as the first length chunk and
|
||||
* echoes the request salt, which lives in the paired decrypt ctx.
|
||||
*/
|
||||
cipher_ctx_t *peer = ctx->peer;
|
||||
if (peer == NULL || !peer->init) {
|
||||
LOGE("AEAD-2022: response header needs the request salt");
|
||||
return CRYPTO_ERROR;
|
||||
}
|
||||
if (first_len > SS2022_CHUNK_MASK) {
|
||||
LOGE("AEAD-2022: response payload too large");
|
||||
return CRYPTO_ERROR;
|
||||
}
|
||||
|
||||
body = (uint8_t *)first;
|
||||
body_len = first_len;
|
||||
|
||||
fixed_len = SS2022_RESP_FIXED_LEN(salt_len);
|
||||
fixed[0] = SS2022_HDR_TYPE_SERVER;
|
||||
store64_be(fixed + 1, now);
|
||||
memcpy(fixed + 9, peer->salt, salt_len);
|
||||
store16_be(fixed + 9 + salt_len, (uint16_t)body_len);
|
||||
}
|
||||
|
||||
brealloc(out, salt_len + fixed_len + SS2022_TAG_LEN
|
||||
+ body_len + SS2022_TAG_LEN, capacity);
|
||||
|
||||
memcpy(out->data, ctx->salt, salt_len);
|
||||
size_t o = salt_len;
|
||||
|
||||
int n = ss2022_seal(ctx, (uint8_t *)out->data + o, fixed, fixed_len);
|
||||
if (n < 0)
|
||||
goto done;
|
||||
o += n;
|
||||
|
||||
n = ss2022_seal(ctx, (uint8_t *)out->data + o, body, body_len);
|
||||
if (n < 0)
|
||||
goto done;
|
||||
o += n;
|
||||
|
||||
*out_len = o;
|
||||
rc = CRYPTO_OK;
|
||||
|
||||
done:
|
||||
if (body_owned)
|
||||
ss_free(body);
|
||||
return rc;
|
||||
}
|
||||
|
||||
int
|
||||
ss2022_tcp_encrypt(buffer_t *plaintext, cipher_ctx_t *ctx, size_t capacity)
|
||||
{
|
||||
static buffer_t tmp = { 0, 0, 0, NULL };
|
||||
|
||||
if (plaintext->len == 0)
|
||||
return CRYPTO_OK;
|
||||
|
||||
if (!ctx->init) {
|
||||
aead_cipher_ctx_set_key(ctx, 1);
|
||||
ctx->init = 1;
|
||||
|
||||
size_t out_len = 0;
|
||||
int err = ss2022_emit_header(ctx, &tmp, &out_len,
|
||||
(uint8_t *)plaintext->data,
|
||||
plaintext->len, capacity);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
tmp.len = out_len;
|
||||
bswap_data(plaintext, &tmp);
|
||||
plaintext->len = out_len;
|
||||
return CRYPTO_OK;
|
||||
}
|
||||
|
||||
/* Steady state: length chunk followed by payload chunk. */
|
||||
size_t remaining = plaintext->len;
|
||||
uint8_t *src = (uint8_t *)plaintext->data;
|
||||
size_t total_out = 0;
|
||||
|
||||
size_t nchunks = (plaintext->len + SS2022_CHUNK_MASK - 1) / SS2022_CHUNK_MASK;
|
||||
brealloc(&tmp, plaintext->len
|
||||
+ nchunks * (SS2022_CHUNK_LEN_SIZE + 2 * SS2022_TAG_LEN),
|
||||
capacity);
|
||||
|
||||
while (remaining > 0) {
|
||||
uint16_t plen = remaining > SS2022_CHUNK_MASK
|
||||
? SS2022_CHUNK_MASK : (uint16_t)remaining;
|
||||
|
||||
uint8_t len_buf[SS2022_CHUNK_LEN_SIZE];
|
||||
store16_be(len_buf, plen);
|
||||
|
||||
int n = ss2022_seal(ctx, (uint8_t *)tmp.data + total_out,
|
||||
len_buf, SS2022_CHUNK_LEN_SIZE);
|
||||
if (n < 0)
|
||||
return CRYPTO_ERROR;
|
||||
total_out += n;
|
||||
|
||||
n = ss2022_seal(ctx, (uint8_t *)tmp.data + total_out, src, plen);
|
||||
if (n < 0)
|
||||
return CRYPTO_ERROR;
|
||||
total_out += n;
|
||||
|
||||
src += plen;
|
||||
remaining -= plen;
|
||||
}
|
||||
|
||||
tmp.len = total_out;
|
||||
bswap_data(plaintext, &tmp);
|
||||
plaintext->len = total_out;
|
||||
return CRYPTO_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
* Parse a decrypted request variable-length header and emit the SOCKS
|
||||
* address followed by any initial payload, which is the layout the
|
||||
* ss-server request path already understands.
|
||||
*/
|
||||
static int
|
||||
ss2022_parse_req_varhdr(const uint8_t *h, size_t len, buffer_t *out,
|
||||
size_t capacity)
|
||||
{
|
||||
size_t socks_len = ss2022_socks_addr_len(h, len);
|
||||
if (socks_len == 0 || len < socks_len + 2)
|
||||
return CRYPTO_ERROR;
|
||||
|
||||
uint16_t pad_len = load16_be(h + socks_len);
|
||||
size_t pad_ofst = socks_len + 2;
|
||||
|
||||
if (pad_len > SS2022_MAX_PADDING || pad_ofst + pad_len > len)
|
||||
return CRYPTO_ERROR;
|
||||
|
||||
size_t payload_ofst = pad_ofst + pad_len;
|
||||
size_t payload_len = len - payload_ofst;
|
||||
|
||||
/* SIP022: a request with neither payload nor padding must be rejected. */
|
||||
if (payload_len == 0 && pad_len == 0)
|
||||
return CRYPTO_ERROR;
|
||||
|
||||
brealloc(out, socks_len + payload_len, capacity);
|
||||
memcpy(out->data, h, socks_len);
|
||||
if (payload_len > 0)
|
||||
memcpy(out->data + socks_len, h + payload_ofst, payload_len);
|
||||
out->len = socks_len + payload_len;
|
||||
return CRYPTO_OK;
|
||||
}
|
||||
|
||||
/* Append n bytes of freshly decrypted plaintext to the output buffer. */
|
||||
static int
|
||||
ss2022_drain_chunks(cipher_ctx_t *ctx, buffer_t *chunk, buffer_t *out,
|
||||
size_t capacity)
|
||||
{
|
||||
for (;;) {
|
||||
if (ctx->next_plen == 0) {
|
||||
/* Need a length chunk first. */
|
||||
if (chunk->len < SS2022_CHUNK_LEN_SIZE + SS2022_TAG_LEN)
|
||||
return CRYPTO_OK;
|
||||
|
||||
uint8_t len_buf[SS2022_CHUNK_LEN_SIZE];
|
||||
int n = ss2022_open(ctx, len_buf,
|
||||
(uint8_t *)chunk->data + chunk->idx,
|
||||
SS2022_CHUNK_LEN_SIZE + SS2022_TAG_LEN);
|
||||
if (n < 0) {
|
||||
LOGE("AEAD-2022: length chunk authentication failed");
|
||||
return CRYPTO_ERROR;
|
||||
}
|
||||
|
||||
uint16_t plen = load16_be(len_buf);
|
||||
if (plen == 0) {
|
||||
LOGE("AEAD-2022: zero-length payload chunk");
|
||||
return CRYPTO_ERROR;
|
||||
}
|
||||
|
||||
chunk->idx += SS2022_CHUNK_LEN_SIZE + SS2022_TAG_LEN;
|
||||
chunk->len -= SS2022_CHUNK_LEN_SIZE + SS2022_TAG_LEN;
|
||||
ctx->next_plen = plen;
|
||||
}
|
||||
|
||||
/*
|
||||
* The length chunk's nonce has already been consumed, so the pending
|
||||
* length is kept in ctx->next_plen until the payload arrives.
|
||||
*/
|
||||
size_t need = (size_t)ctx->next_plen + SS2022_TAG_LEN;
|
||||
if (chunk->len < need)
|
||||
return CRYPTO_OK;
|
||||
|
||||
brealloc(out, out->len + ctx->next_plen, capacity);
|
||||
int n = ss2022_open(ctx, (uint8_t *)out->data + out->len,
|
||||
(uint8_t *)chunk->data + chunk->idx, need);
|
||||
if (n < 0) {
|
||||
LOGE("AEAD-2022: payload chunk authentication failed");
|
||||
return CRYPTO_ERROR;
|
||||
}
|
||||
|
||||
chunk->idx += need;
|
||||
chunk->len -= need;
|
||||
out->len += n;
|
||||
ctx->next_plen = 0;
|
||||
}
|
||||
}
|
||||
|
||||
int
|
||||
ss2022_tcp_decrypt(buffer_t *ciphertext, cipher_ctx_t *ctx, size_t capacity)
|
||||
{
|
||||
static buffer_t out = { 0, 0, 0, NULL };
|
||||
|
||||
size_t salt_len = ctx->cipher->key_len;
|
||||
int is_client = (ctx->role == CRYPTO_ROLE_CLIENT);
|
||||
|
||||
if (ctx->chunk == NULL) {
|
||||
ctx->chunk = (buffer_t *)ss_malloc(sizeof(buffer_t));
|
||||
memset(ctx->chunk, 0, sizeof(buffer_t));
|
||||
balloc(ctx->chunk, capacity);
|
||||
}
|
||||
|
||||
buffer_t *chunk = ctx->chunk;
|
||||
|
||||
/* Accumulate the newly received ciphertext. */
|
||||
if (chunk->idx > 0) {
|
||||
if (chunk->len > 0)
|
||||
memmove(chunk->data, chunk->data + chunk->idx, chunk->len);
|
||||
chunk->idx = 0;
|
||||
}
|
||||
brealloc(chunk, chunk->len + ciphertext->len, capacity);
|
||||
memcpy(chunk->data + chunk->len, ciphertext->data, ciphertext->len);
|
||||
chunk->len += ciphertext->len;
|
||||
|
||||
out.len = 0;
|
||||
|
||||
/* Stage 0: salt and fixed-length header, consumed as one unit. */
|
||||
if (!ctx->init) {
|
||||
size_t fixed_len = is_client ? SS2022_RESP_FIXED_LEN(salt_len)
|
||||
: SS2022_REQ_FIXED_LEN;
|
||||
size_t need = salt_len + fixed_len + SS2022_TAG_LEN;
|
||||
|
||||
if (chunk->len < need) {
|
||||
ciphertext->len = 0;
|
||||
return CRYPTO_NEED_MORE;
|
||||
}
|
||||
|
||||
memcpy(ctx->salt, chunk->data + chunk->idx, salt_len);
|
||||
|
||||
if (!is_client
|
||||
&& ss2022_salt_check_and_add(ctx->salt, salt_len) != CRYPTO_OK)
|
||||
return CRYPTO_ERROR;
|
||||
|
||||
aead_cipher_ctx_set_key(ctx, 0);
|
||||
|
||||
uint8_t fixed[SS2022_FIXED_BUF_LEN];
|
||||
int n = ss2022_open(ctx, fixed,
|
||||
(uint8_t *)chunk->data + chunk->idx + salt_len,
|
||||
fixed_len + SS2022_TAG_LEN);
|
||||
if (n < 0) {
|
||||
LOGE("AEAD-2022: fixed header authentication failed");
|
||||
return CRYPTO_ERROR;
|
||||
}
|
||||
|
||||
uint8_t want_type = is_client ? SS2022_HDR_TYPE_SERVER
|
||||
: SS2022_HDR_TYPE_CLIENT;
|
||||
if (fixed[0] != want_type) {
|
||||
LOGE("AEAD-2022: unexpected header type %u", fixed[0]);
|
||||
return CRYPTO_ERROR;
|
||||
}
|
||||
if (ss2022_check_timestamp(load64_be(fixed + 1)) != CRYPTO_OK) {
|
||||
LOGE("AEAD-2022: header timestamp out of window");
|
||||
return CRYPTO_ERROR;
|
||||
}
|
||||
|
||||
if (is_client) {
|
||||
cipher_ctx_t *peer = ctx->peer;
|
||||
if (peer == NULL || memcmp(fixed + 9, peer->salt, salt_len) != 0) {
|
||||
LOGE("AEAD-2022: response does not echo the request salt");
|
||||
return CRYPTO_ERROR;
|
||||
}
|
||||
ctx->next_plen = load16_be(fixed + 9 + salt_len);
|
||||
} else {
|
||||
ctx->hdr_varlen = load16_be(fixed + 9);
|
||||
}
|
||||
|
||||
chunk->idx += need;
|
||||
chunk->len -= need;
|
||||
ctx->init = 1;
|
||||
ctx->hdr_stage = SS2022_STAGE_VAR_HDR;
|
||||
}
|
||||
|
||||
/*
|
||||
* Stage 1: the request's variable-length header, or the response's first
|
||||
* payload chunk whose length came from the fixed header.
|
||||
*/
|
||||
if (ctx->hdr_stage == SS2022_STAGE_VAR_HDR) {
|
||||
uint16_t want = is_client ? ctx->next_plen : ctx->hdr_varlen;
|
||||
size_t need = (size_t)want + SS2022_TAG_LEN;
|
||||
|
||||
if (want == 0) {
|
||||
LOGE("AEAD-2022: empty header chunk");
|
||||
return CRYPTO_ERROR;
|
||||
}
|
||||
if (chunk->len < need) {
|
||||
ciphertext->len = 0;
|
||||
return CRYPTO_NEED_MORE;
|
||||
}
|
||||
|
||||
uint8_t *body = ss_malloc(want);
|
||||
int n = ss2022_open(ctx, body,
|
||||
(uint8_t *)chunk->data + chunk->idx, need);
|
||||
if (n < 0) {
|
||||
ss_free(body);
|
||||
LOGE("AEAD-2022: header chunk authentication failed");
|
||||
return CRYPTO_ERROR;
|
||||
}
|
||||
chunk->idx += need;
|
||||
chunk->len -= need;
|
||||
|
||||
int rc = CRYPTO_OK;
|
||||
if (is_client) {
|
||||
brealloc(&out, n, capacity);
|
||||
memcpy(out.data, body, n);
|
||||
out.len = n;
|
||||
} else {
|
||||
rc = ss2022_parse_req_varhdr(body, n, &out, capacity);
|
||||
if (rc != CRYPTO_OK)
|
||||
LOGE("AEAD-2022: malformed request header");
|
||||
}
|
||||
ss_free(body);
|
||||
if (rc != CRYPTO_OK)
|
||||
return rc;
|
||||
|
||||
ctx->next_plen = 0;
|
||||
ctx->hdr_stage = SS2022_STAGE_STREAM;
|
||||
}
|
||||
|
||||
/* Stage 2: length chunk and payload chunk, repeated. */
|
||||
if (ss2022_drain_chunks(ctx, chunk, &out, capacity) != CRYPTO_OK)
|
||||
return CRYPTO_ERROR;
|
||||
|
||||
if (chunk->len == 0)
|
||||
chunk->idx = 0;
|
||||
|
||||
if (out.len == 0) {
|
||||
ciphertext->len = 0;
|
||||
return CRYPTO_NEED_MORE;
|
||||
}
|
||||
|
||||
brealloc(ciphertext, out.len, capacity);
|
||||
memcpy(ciphertext->data, out.data, out.len);
|
||||
ciphertext->len = out.len;
|
||||
return CRYPTO_OK;
|
||||
}
|
||||
@@ -0,0 +1,595 @@
|
||||
/*
|
||||
* aead2022_udp.c - Shadowsocks 2022 Edition (SIP022) session-based UDP
|
||||
*
|
||||
* Copyright (C) 2013 - 2026, Max Lv <max.c.lv@gmail.com>
|
||||
*
|
||||
* This file is part of the shadowsocks-libev.
|
||||
*
|
||||
* shadowsocks-libev is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* shadowsocks-libev is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with shadowsocks-libev; see the file COPYING. If not, see
|
||||
* <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* A UDP packet is:
|
||||
*
|
||||
* AES methods: [AES-ECB(psk, session ID || packet ID)] [AEAD body]
|
||||
* ChaCha: [24B random nonce] [XChaCha20-Poly1305(psk) body]
|
||||
*
|
||||
* The body carries a main header (type, timestamp, optional client session
|
||||
* ID, padding, SOCKS address) followed by the payload.
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#include <time.h>
|
||||
|
||||
#include <sodium.h>
|
||||
#include <mbedtls/aes.h>
|
||||
|
||||
#include "aead.h"
|
||||
#include "aead_internal.h"
|
||||
#include "cache.h"
|
||||
#include "utils.h"
|
||||
|
||||
#define SS2022_UDP_TYPE_CLIENT 0
|
||||
#define SS2022_UDP_TYPE_SERVER 1
|
||||
#define SS2022_UDP_SEP_HDR_LEN 16
|
||||
#define SS2022_UDP_TAG_LEN 16
|
||||
#define SS2022_UDP_XNONCE_LEN 24
|
||||
/* type(1) + timestamp(8) + padding length(2) */
|
||||
#define SS2022_UDP_MIN_HDR 11
|
||||
/* sliding window width for replay protection */
|
||||
#define SS2022_WINDOW_BITS 256
|
||||
|
||||
/*
|
||||
* Per-conversation UDP session state. The client keeps one of these per
|
||||
* source address; the server keeps one per client session ID. It holds our
|
||||
* own outgoing session (ID, packet counter) and the replay window of the
|
||||
* peer session we are receiving from.
|
||||
*/
|
||||
typedef struct ss2022_udp_session {
|
||||
cipher_t *cipher;
|
||||
|
||||
/* outgoing */
|
||||
uint8_t tx_session_id[SS2022_SESSION_ID_LEN];
|
||||
uint64_t tx_packet_id;
|
||||
uint8_t tx_subkey[MAX_KEY_LENGTH];
|
||||
int tx_ready;
|
||||
|
||||
/* incoming: last accepted peer session and its sliding window */
|
||||
uint8_t rx_session_id[SS2022_SESSION_ID_LEN];
|
||||
uint8_t rx_subkey[MAX_KEY_LENGTH];
|
||||
int rx_ready;
|
||||
uint64_t rx_highest;
|
||||
uint8_t rx_window[SS2022_WINDOW_BITS / 8];
|
||||
|
||||
/* peer's client session ID, echoed by the server in its replies */
|
||||
uint8_t peer_session_id[SS2022_SESSION_ID_LEN];
|
||||
int peer_session_known;
|
||||
|
||||
/*
|
||||
* Server sessions are owned by the session table (they are looked up by
|
||||
* client session ID, which is only known after decryption); client
|
||||
* sessions are owned by the caller's slot.
|
||||
*/
|
||||
int table_owned;
|
||||
} ss2022_udp_session_t;
|
||||
|
||||
/*
|
||||
* Server-side session table, keyed by the 8-byte client session ID. SIP022
|
||||
* requires servers to route by session ID rather than source address, so
|
||||
* that sessions survive client network changes.
|
||||
*/
|
||||
#define SS2022_SESSION_TABLE_CAP 4096
|
||||
#define SS2022_SESSION_TTL 600
|
||||
|
||||
static struct cache *ss2022_sessions = NULL;
|
||||
|
||||
static void
|
||||
session_free_cb(void *key, void *element)
|
||||
{
|
||||
(void)key;
|
||||
if (element != NULL) {
|
||||
sodium_memzero(element, sizeof(ss2022_udp_session_t));
|
||||
ss_free(element);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
store16_be(uint8_t *p, uint16_t v)
|
||||
{
|
||||
p[0] = (uint8_t)(v >> 8);
|
||||
p[1] = (uint8_t)v;
|
||||
}
|
||||
|
||||
static void
|
||||
store64_be(uint8_t *p, uint64_t v)
|
||||
{
|
||||
for (int i = 7; i >= 0; i--) {
|
||||
p[i] = (uint8_t)(v & 0xff);
|
||||
v >>= 8;
|
||||
}
|
||||
}
|
||||
|
||||
static uint64_t
|
||||
load64_be(const uint8_t *p)
|
||||
{
|
||||
uint64_t v = 0;
|
||||
for (int i = 0; i < 8; i++)
|
||||
v = (v << 8) | p[i];
|
||||
return v;
|
||||
}
|
||||
|
||||
static int
|
||||
ss2022_check_timestamp(uint64_t ts)
|
||||
{
|
||||
uint64_t now = (uint64_t)time(NULL);
|
||||
uint64_t diff = now > ts ? now - ts : ts - now;
|
||||
return diff <= SS2022_TIME_WINDOW ? CRYPTO_OK : CRYPTO_ERROR;
|
||||
}
|
||||
|
||||
/*
|
||||
* Sliding window replay filter (WireGuard style). Only called after the
|
||||
* packet has been authenticated and its header validated.
|
||||
*/
|
||||
static int
|
||||
window_check(const ss2022_udp_session_t *s, uint64_t id)
|
||||
{
|
||||
if (id > s->rx_highest)
|
||||
return CRYPTO_OK;
|
||||
if (s->rx_highest - id >= SS2022_WINDOW_BITS)
|
||||
return CRYPTO_ERROR;
|
||||
|
||||
uint64_t bit = id % SS2022_WINDOW_BITS;
|
||||
return (s->rx_window[bit / 8] & (1u << (bit % 8)))
|
||||
? CRYPTO_ERROR : CRYPTO_OK;
|
||||
}
|
||||
|
||||
static void
|
||||
window_update(ss2022_udp_session_t *s, uint64_t id)
|
||||
{
|
||||
if (id > s->rx_highest) {
|
||||
uint64_t shift = id - s->rx_highest;
|
||||
if (shift >= SS2022_WINDOW_BITS) {
|
||||
memset(s->rx_window, 0, sizeof(s->rx_window));
|
||||
} else {
|
||||
for (uint64_t i = s->rx_highest + 1; i <= id; i++) {
|
||||
uint64_t b = i % SS2022_WINDOW_BITS;
|
||||
s->rx_window[b / 8] &= (uint8_t) ~(1u << (b % 8));
|
||||
}
|
||||
}
|
||||
s->rx_highest = id;
|
||||
}
|
||||
|
||||
uint64_t bit = id % SS2022_WINDOW_BITS;
|
||||
s->rx_window[bit / 8] |= (uint8_t)(1u << (bit % 8));
|
||||
}
|
||||
|
||||
static ss2022_udp_session_t *
|
||||
session_new(cipher_t *cipher)
|
||||
{
|
||||
ss2022_udp_session_t *s = ss_malloc(sizeof(ss2022_udp_session_t));
|
||||
memset(s, 0, sizeof(ss2022_udp_session_t));
|
||||
s->cipher = cipher;
|
||||
return s;
|
||||
}
|
||||
|
||||
static ss2022_udp_session_t *
|
||||
session_get(void **slot, cipher_t *cipher)
|
||||
{
|
||||
ss2022_udp_session_t *s = (ss2022_udp_session_t *)*slot;
|
||||
if (s != NULL)
|
||||
return s;
|
||||
|
||||
s = session_new(cipher);
|
||||
*slot = s;
|
||||
return s;
|
||||
}
|
||||
|
||||
/* Look up (or create) the server-side session for a client session ID. */
|
||||
static ss2022_udp_session_t *
|
||||
session_by_id(const uint8_t *session_id, cipher_t *cipher)
|
||||
{
|
||||
if (ss2022_sessions == NULL
|
||||
&& cache_create(&ss2022_sessions, SS2022_SESSION_TABLE_CAP,
|
||||
session_free_cb) != 0)
|
||||
return NULL;
|
||||
|
||||
ss2022_udp_session_t *s = NULL;
|
||||
cache_lookup(ss2022_sessions, (char *)session_id, SS2022_SESSION_ID_LEN,
|
||||
(void *)&s);
|
||||
if (s != NULL)
|
||||
return s;
|
||||
|
||||
cache_clear(ss2022_sessions, SS2022_SESSION_TTL);
|
||||
|
||||
s = session_new(cipher);
|
||||
s->table_owned = 1;
|
||||
cache_insert(ss2022_sessions, (char *)session_id, SS2022_SESSION_ID_LEN, s);
|
||||
return s;
|
||||
}
|
||||
|
||||
void
|
||||
aead_2022_udp_session_release(void *slot)
|
||||
{
|
||||
ss2022_udp_session_t *s = (ss2022_udp_session_t *)slot;
|
||||
if (s == NULL || s->table_owned)
|
||||
return; /* table-owned sessions are freed when the table evicts them */
|
||||
sodium_memzero(s, sizeof(ss2022_udp_session_t));
|
||||
ss_free(s);
|
||||
}
|
||||
|
||||
static int
|
||||
is_chacha(const cipher_t *cipher)
|
||||
{
|
||||
return cipher->method == CHACHA20POLY1305IETF2022;
|
||||
}
|
||||
|
||||
/* AES-ECB over the 16-byte separate header, keyed directly with the PSK. */
|
||||
static int
|
||||
sep_hdr_crypt(const cipher_t *cipher, uint8_t *dst, const uint8_t *src, int enc)
|
||||
{
|
||||
mbedtls_aes_context aes;
|
||||
int err;
|
||||
|
||||
mbedtls_aes_init(&aes);
|
||||
if (enc)
|
||||
err = mbedtls_aes_setkey_enc(&aes, cipher->key,
|
||||
(unsigned int)cipher->key_len * 8);
|
||||
else
|
||||
err = mbedtls_aes_setkey_dec(&aes, cipher->key,
|
||||
(unsigned int)cipher->key_len * 8);
|
||||
if (err == 0)
|
||||
err = mbedtls_aes_crypt_ecb(&aes,
|
||||
enc ? MBEDTLS_AES_ENCRYPT
|
||||
: MBEDTLS_AES_DECRYPT,
|
||||
src, dst);
|
||||
mbedtls_aes_free(&aes);
|
||||
return err == 0 ? CRYPTO_OK : CRYPTO_ERROR;
|
||||
}
|
||||
|
||||
/*
|
||||
* AEAD over the packet body. For AES methods the session subkey and a nonce
|
||||
* taken from the separate header are used; for ChaCha the PSK is used
|
||||
* directly with a random 24-byte nonce.
|
||||
*/
|
||||
static int
|
||||
body_seal(const cipher_t *cipher, const uint8_t *key,
|
||||
uint8_t *c, size_t *clen, const uint8_t *m, size_t mlen,
|
||||
const uint8_t *nonce)
|
||||
{
|
||||
unsigned long long out_len = 0;
|
||||
int err;
|
||||
|
||||
if (is_chacha(cipher)) {
|
||||
err = crypto_aead_xchacha20poly1305_ietf_encrypt(
|
||||
c, &out_len, m, mlen, NULL, 0, NULL, nonce, key);
|
||||
*clen = (size_t)out_len;
|
||||
return err == 0 ? CRYPTO_OK : CRYPTO_ERROR;
|
||||
}
|
||||
|
||||
cipher_t tmp_cipher = *cipher;
|
||||
cipher_ctx_t tmp_ctx;
|
||||
memset(&tmp_ctx, 0, sizeof(tmp_ctx));
|
||||
tmp_ctx.cipher = &tmp_cipher;
|
||||
|
||||
/* Reuse the shared one-shot helper with an mbedTLS GCM context. */
|
||||
tmp_ctx.evp = ss_malloc(sizeof(cipher_evp_t));
|
||||
memset(tmp_ctx.evp, 0, sizeof(cipher_evp_t));
|
||||
mbedtls_cipher_init(tmp_ctx.evp);
|
||||
const cipher_kt_t *kt = mbedtls_cipher_info_from_string(
|
||||
cipher->key_len == 16 ? "AES-128-GCM" : "AES-256-GCM");
|
||||
if (kt == NULL || mbedtls_cipher_setup(tmp_ctx.evp, kt) != 0
|
||||
|| mbedtls_cipher_setkey(tmp_ctx.evp, key,
|
||||
(int)cipher->key_len * 8, MBEDTLS_ENCRYPT) != 0) {
|
||||
mbedtls_cipher_free(tmp_ctx.evp);
|
||||
ss_free(tmp_ctx.evp);
|
||||
return CRYPTO_ERROR;
|
||||
}
|
||||
|
||||
*clen = mlen + SS2022_UDP_TAG_LEN;
|
||||
err = aead_cipher_encrypt(&tmp_ctx, c, clen, (uint8_t *)m, mlen,
|
||||
NULL, 0, (uint8_t *)nonce, (uint8_t *)key);
|
||||
|
||||
mbedtls_cipher_free(tmp_ctx.evp);
|
||||
ss_free(tmp_ctx.evp);
|
||||
return err == 0 ? CRYPTO_OK : CRYPTO_ERROR;
|
||||
}
|
||||
|
||||
static int
|
||||
body_open(const cipher_t *cipher, const uint8_t *key,
|
||||
uint8_t *p, size_t *plen, const uint8_t *c, size_t clen,
|
||||
const uint8_t *nonce)
|
||||
{
|
||||
unsigned long long out_len = 0;
|
||||
int err;
|
||||
|
||||
if (is_chacha(cipher)) {
|
||||
err = crypto_aead_xchacha20poly1305_ietf_decrypt(
|
||||
p, &out_len, NULL, c, clen, NULL, 0, nonce, key);
|
||||
*plen = (size_t)out_len;
|
||||
return err == 0 ? CRYPTO_OK : CRYPTO_ERROR;
|
||||
}
|
||||
|
||||
cipher_t tmp_cipher = *cipher;
|
||||
cipher_ctx_t tmp_ctx;
|
||||
memset(&tmp_ctx, 0, sizeof(tmp_ctx));
|
||||
tmp_ctx.cipher = &tmp_cipher;
|
||||
|
||||
tmp_ctx.evp = ss_malloc(sizeof(cipher_evp_t));
|
||||
memset(tmp_ctx.evp, 0, sizeof(cipher_evp_t));
|
||||
mbedtls_cipher_init(tmp_ctx.evp);
|
||||
const cipher_kt_t *kt = mbedtls_cipher_info_from_string(
|
||||
cipher->key_len == 16 ? "AES-128-GCM" : "AES-256-GCM");
|
||||
if (kt == NULL || mbedtls_cipher_setup(tmp_ctx.evp, kt) != 0
|
||||
|| mbedtls_cipher_setkey(tmp_ctx.evp, key,
|
||||
(int)cipher->key_len * 8, MBEDTLS_DECRYPT) != 0) {
|
||||
mbedtls_cipher_free(tmp_ctx.evp);
|
||||
ss_free(tmp_ctx.evp);
|
||||
return CRYPTO_ERROR;
|
||||
}
|
||||
|
||||
*plen = clen - SS2022_UDP_TAG_LEN;
|
||||
err = aead_cipher_decrypt(&tmp_ctx, p, plen, (uint8_t *)c, clen,
|
||||
NULL, 0, (uint8_t *)nonce, (uint8_t *)key);
|
||||
|
||||
mbedtls_cipher_free(tmp_ctx.evp);
|
||||
ss_free(tmp_ctx.evp);
|
||||
return err == 0 ? CRYPTO_OK : CRYPTO_ERROR;
|
||||
}
|
||||
|
||||
/*
|
||||
* Encrypt one UDP packet. The plaintext is a SOCKS address followed by the
|
||||
* payload, matching what udprelay hands to the legacy encrypt_all.
|
||||
*/
|
||||
int
|
||||
aead_2022_encrypt_udp(buffer_t *plaintext, cipher_t *cipher, size_t capacity,
|
||||
void **slot)
|
||||
{
|
||||
static buffer_t tmp = { 0, 0, 0, NULL };
|
||||
|
||||
ss2022_udp_session_t *s = session_get(slot, cipher);
|
||||
int is_server = (aead_get_role() == CRYPTO_ROLE_SERVER);
|
||||
size_t salt_len = cipher->key_len;
|
||||
|
||||
if (!s->tx_ready) {
|
||||
rand_bytes(s->tx_session_id, SS2022_SESSION_ID_LEN);
|
||||
s->tx_packet_id = 0;
|
||||
ss2022_derive_subkey(cipher->key, salt_len,
|
||||
s->tx_session_id, SS2022_SESSION_ID_LEN,
|
||||
s->tx_subkey, salt_len);
|
||||
s->tx_ready = 1;
|
||||
}
|
||||
|
||||
/* Build the main header followed by the payload. */
|
||||
size_t extra = is_server ? SS2022_SESSION_ID_LEN : 0;
|
||||
size_t hdr_len = SS2022_UDP_MIN_HDR + extra;
|
||||
size_t body_len = hdr_len + plaintext->len;
|
||||
|
||||
uint8_t *body = ss_malloc(body_len);
|
||||
size_t o = 0;
|
||||
|
||||
body[o++] = is_server ? SS2022_UDP_TYPE_SERVER : SS2022_UDP_TYPE_CLIENT;
|
||||
store64_be(body + o, (uint64_t)time(NULL));
|
||||
o += 8;
|
||||
|
||||
if (is_server) {
|
||||
if (!s->peer_session_known) {
|
||||
ss_free(body);
|
||||
LOGE("AEAD-2022: no client session to reply to");
|
||||
return CRYPTO_ERROR;
|
||||
}
|
||||
memcpy(body + o, s->peer_session_id, SS2022_SESSION_ID_LEN);
|
||||
o += SS2022_SESSION_ID_LEN;
|
||||
}
|
||||
|
||||
store16_be(body + o, 0); /* padding length */
|
||||
o += 2;
|
||||
|
||||
memcpy(body + o, plaintext->data, plaintext->len);
|
||||
|
||||
int rc = CRYPTO_ERROR;
|
||||
|
||||
if (is_chacha(cipher)) {
|
||||
/*
|
||||
* nonce || XChaCha20-Poly1305(psk, session ID || packet ID ||
|
||||
* main header || payload)
|
||||
*/
|
||||
size_t merged_len = SS2022_UDP_SEP_HDR_LEN + body_len;
|
||||
uint8_t *merged = ss_malloc(merged_len);
|
||||
memcpy(merged, s->tx_session_id, SS2022_SESSION_ID_LEN);
|
||||
store64_be(merged + SS2022_SESSION_ID_LEN, s->tx_packet_id);
|
||||
memcpy(merged + SS2022_UDP_SEP_HDR_LEN, body, body_len);
|
||||
|
||||
brealloc(&tmp, SS2022_UDP_XNONCE_LEN + merged_len + SS2022_UDP_TAG_LEN,
|
||||
capacity);
|
||||
rand_bytes(tmp.data, SS2022_UDP_XNONCE_LEN);
|
||||
|
||||
size_t clen = 0;
|
||||
rc = body_seal(cipher, cipher->key,
|
||||
(uint8_t *)tmp.data + SS2022_UDP_XNONCE_LEN, &clen,
|
||||
merged, merged_len, (uint8_t *)tmp.data);
|
||||
sodium_memzero(merged, merged_len);
|
||||
ss_free(merged);
|
||||
|
||||
if (rc == CRYPTO_OK)
|
||||
tmp.len = SS2022_UDP_XNONCE_LEN + clen;
|
||||
} else {
|
||||
uint8_t sep[SS2022_UDP_SEP_HDR_LEN];
|
||||
memcpy(sep, s->tx_session_id, SS2022_SESSION_ID_LEN);
|
||||
store64_be(sep + SS2022_SESSION_ID_LEN, s->tx_packet_id);
|
||||
|
||||
brealloc(&tmp, SS2022_UDP_SEP_HDR_LEN + body_len + SS2022_UDP_TAG_LEN,
|
||||
capacity);
|
||||
|
||||
rc = sep_hdr_crypt(cipher, (uint8_t *)tmp.data, sep, 1);
|
||||
if (rc == CRYPTO_OK) {
|
||||
size_t clen = 0;
|
||||
/* nonce is separate_header[4..16] of the plaintext header */
|
||||
rc = body_seal(cipher, s->tx_subkey,
|
||||
(uint8_t *)tmp.data + SS2022_UDP_SEP_HDR_LEN, &clen,
|
||||
body, body_len, sep + 4);
|
||||
if (rc == CRYPTO_OK)
|
||||
tmp.len = SS2022_UDP_SEP_HDR_LEN + clen;
|
||||
}
|
||||
}
|
||||
|
||||
sodium_memzero(body, body_len);
|
||||
ss_free(body);
|
||||
|
||||
if (rc != CRYPTO_OK)
|
||||
return CRYPTO_ERROR;
|
||||
|
||||
s->tx_packet_id++;
|
||||
|
||||
bswap_data(plaintext, &tmp);
|
||||
plaintext->len = tmp.len;
|
||||
return CRYPTO_OK;
|
||||
}
|
||||
|
||||
int
|
||||
aead_2022_decrypt_udp(buffer_t *ciphertext, cipher_t *cipher, size_t capacity,
|
||||
void **slot)
|
||||
{
|
||||
static buffer_t tmp = { 0, 0, 0, NULL };
|
||||
|
||||
int is_server = (aead_get_role() == CRYPTO_ROLE_SERVER);
|
||||
size_t salt_len = cipher->key_len;
|
||||
|
||||
uint8_t session_id[SS2022_SESSION_ID_LEN];
|
||||
uint64_t packet_id;
|
||||
size_t plen = 0;
|
||||
|
||||
if (is_chacha(cipher)) {
|
||||
if (ciphertext->len <= SS2022_UDP_XNONCE_LEN + SS2022_UDP_SEP_HDR_LEN
|
||||
+ SS2022_UDP_TAG_LEN)
|
||||
return CRYPTO_ERROR;
|
||||
|
||||
size_t clen = ciphertext->len - SS2022_UDP_XNONCE_LEN;
|
||||
brealloc(&tmp, clen, capacity);
|
||||
|
||||
if (body_open(cipher, cipher->key, (uint8_t *)tmp.data, &plen,
|
||||
(uint8_t *)ciphertext->data + SS2022_UDP_XNONCE_LEN, clen,
|
||||
(uint8_t *)ciphertext->data) != CRYPTO_OK)
|
||||
return CRYPTO_ERROR;
|
||||
|
||||
if (plen < SS2022_UDP_SEP_HDR_LEN)
|
||||
return CRYPTO_ERROR;
|
||||
|
||||
memcpy(session_id, tmp.data, SS2022_SESSION_ID_LEN);
|
||||
packet_id = load64_be((uint8_t *)tmp.data + SS2022_SESSION_ID_LEN);
|
||||
|
||||
/* strip the merged session ID and packet ID */
|
||||
plen -= SS2022_UDP_SEP_HDR_LEN;
|
||||
memmove(tmp.data, tmp.data + SS2022_UDP_SEP_HDR_LEN, plen);
|
||||
} else {
|
||||
if (ciphertext->len <= SS2022_UDP_SEP_HDR_LEN + SS2022_UDP_TAG_LEN)
|
||||
return CRYPTO_ERROR;
|
||||
|
||||
uint8_t sep[SS2022_UDP_SEP_HDR_LEN];
|
||||
if (sep_hdr_crypt(cipher, sep, (uint8_t *)ciphertext->data, 0)
|
||||
!= CRYPTO_OK)
|
||||
return CRYPTO_ERROR;
|
||||
|
||||
memcpy(session_id, sep, SS2022_SESSION_ID_LEN);
|
||||
packet_id = load64_be(sep + SS2022_SESSION_ID_LEN);
|
||||
|
||||
uint8_t subkey[MAX_KEY_LENGTH];
|
||||
ss2022_derive_subkey(cipher->key, salt_len,
|
||||
session_id, SS2022_SESSION_ID_LEN,
|
||||
subkey, salt_len);
|
||||
|
||||
size_t clen = ciphertext->len - SS2022_UDP_SEP_HDR_LEN;
|
||||
brealloc(&tmp, clen, capacity);
|
||||
|
||||
int rc = body_open(cipher, subkey, (uint8_t *)tmp.data, &plen,
|
||||
(uint8_t *)ciphertext->data + SS2022_UDP_SEP_HDR_LEN,
|
||||
clen, sep + 4);
|
||||
sodium_memzero(subkey, sizeof(subkey));
|
||||
if (rc != CRYPTO_OK)
|
||||
return CRYPTO_ERROR;
|
||||
}
|
||||
|
||||
/*
|
||||
* Resolve the session. The server routes by client session ID, which is
|
||||
* only known now; the client keeps one session per source address, held
|
||||
* in the caller's slot.
|
||||
*/
|
||||
ss2022_udp_session_t *s;
|
||||
if (is_server) {
|
||||
s = session_by_id(session_id, cipher);
|
||||
if (s == NULL)
|
||||
return CRYPTO_ERROR;
|
||||
*slot = s; /* the reply path encrypts with this same session */
|
||||
} else {
|
||||
s = session_get(slot, cipher);
|
||||
}
|
||||
|
||||
/* Main header validation */
|
||||
size_t extra = is_server ? 0 : SS2022_SESSION_ID_LEN;
|
||||
size_t hdr_len = SS2022_UDP_MIN_HDR + extra;
|
||||
if (plen < hdr_len)
|
||||
return CRYPTO_ERROR;
|
||||
|
||||
uint8_t *body = (uint8_t *)tmp.data;
|
||||
uint8_t want_type = is_server ? SS2022_UDP_TYPE_CLIENT
|
||||
: SS2022_UDP_TYPE_SERVER;
|
||||
if (body[0] != want_type)
|
||||
return CRYPTO_ERROR;
|
||||
|
||||
if (ss2022_check_timestamp(load64_be(body + 1)) != CRYPTO_OK)
|
||||
return CRYPTO_ERROR;
|
||||
|
||||
size_t o = 9;
|
||||
if (!is_server) {
|
||||
/* the server echoes our client session ID; verify it is ours */
|
||||
if (!s->tx_ready
|
||||
|| memcmp(body + o, s->tx_session_id, SS2022_SESSION_ID_LEN) != 0)
|
||||
return CRYPTO_ERROR;
|
||||
o += SS2022_SESSION_ID_LEN;
|
||||
}
|
||||
|
||||
uint16_t pad_len = load16_be(body + o);
|
||||
o += 2;
|
||||
if (o + pad_len > plen)
|
||||
return CRYPTO_ERROR;
|
||||
o += pad_len;
|
||||
|
||||
/* Replay protection: new session resets the window. */
|
||||
if (!s->rx_ready
|
||||
|| memcmp(s->rx_session_id, session_id, SS2022_SESSION_ID_LEN) != 0) {
|
||||
memcpy(s->rx_session_id, session_id, SS2022_SESSION_ID_LEN);
|
||||
memset(s->rx_window, 0, sizeof(s->rx_window));
|
||||
s->rx_highest = 0;
|
||||
s->rx_ready = 1;
|
||||
/* packet ID 0 is legitimate for a fresh session */
|
||||
if (packet_id > 0)
|
||||
s->rx_highest = packet_id - 1;
|
||||
} else if (window_check(s, packet_id) != CRYPTO_OK) {
|
||||
LOGE("AEAD-2022: UDP replay detected");
|
||||
return CRYPTO_ERROR;
|
||||
}
|
||||
window_update(s, packet_id);
|
||||
|
||||
if (is_server) {
|
||||
memcpy(s->peer_session_id, session_id, SS2022_SESSION_ID_LEN);
|
||||
s->peer_session_known = 1;
|
||||
}
|
||||
|
||||
size_t payload_len = plen - o;
|
||||
brealloc(ciphertext, payload_len ? payload_len : 1, capacity);
|
||||
memcpy(ciphertext->data, body + o, payload_len);
|
||||
ciphertext->len = payload_len;
|
||||
return CRYPTO_OK;
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
* aead_internal.h - Internal interface shared by aead.c and aead2022.c
|
||||
*
|
||||
* Copyright (C) 2013 - 2026, Max Lv <max.c.lv@gmail.com>
|
||||
*
|
||||
* This file is part of the shadowsocks-libev.
|
||||
*
|
||||
* shadowsocks-libev is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* shadowsocks-libev is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with shadowsocks-libev; see the file COPYING. If not, see
|
||||
* <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef _AEAD_INTERNAL_H
|
||||
#define _AEAD_INTERNAL_H
|
||||
|
||||
#include "crypto.h"
|
||||
|
||||
/* AEAD method identifiers (indexes into the supported_aead_ciphers tables) */
|
||||
#define AES128GCM 0
|
||||
#define AES192GCM 1
|
||||
#define AES256GCM 2
|
||||
#define AES128GCM2022 3
|
||||
#define AES256GCM2022 4
|
||||
/*
|
||||
* methods above require a gcm context
|
||||
* methods below don't require it,
|
||||
* then we need to fake one
|
||||
*/
|
||||
#define CHACHA20POLY1305IETF 5
|
||||
#define CHACHA20POLY1305IETF2022 6
|
||||
|
||||
#ifdef FS_HAVE_XCHACHA20IETF
|
||||
#define XCHACHA20POLY1305IETF 7
|
||||
#endif
|
||||
|
||||
static inline int
|
||||
is_aead_2022(int method)
|
||||
{
|
||||
return method == AES128GCM2022 || method == AES256GCM2022
|
||||
|| method == CHACHA20POLY1305IETF2022;
|
||||
}
|
||||
|
||||
/* Low-level one-shot AEAD operations implemented in aead.c */
|
||||
int aead_cipher_encrypt(cipher_ctx_t *cipher_ctx,
|
||||
uint8_t *c, size_t *clen,
|
||||
uint8_t *m, size_t mlen,
|
||||
uint8_t *ad, size_t adlen,
|
||||
uint8_t *n, uint8_t *k);
|
||||
int aead_cipher_decrypt(cipher_ctx_t *cipher_ctx,
|
||||
uint8_t *p, size_t *plen,
|
||||
uint8_t *m, size_t mlen,
|
||||
uint8_t *ad, size_t adlen,
|
||||
uint8_t *n, uint8_t *k);
|
||||
void aead_cipher_ctx_set_key(cipher_ctx_t *cipher_ctx, int enc);
|
||||
|
||||
/* AEAD-2022 pieces implemented in aead2022.c */
|
||||
int aead_get_role(void);
|
||||
void ss2022_derive_subkey(const uint8_t *key, size_t key_len,
|
||||
const uint8_t *salt, size_t salt_len,
|
||||
uint8_t *subkey, size_t subkey_len);
|
||||
int ss2022_tcp_encrypt(buffer_t *plaintext, cipher_ctx_t *cipher_ctx,
|
||||
size_t capacity);
|
||||
int ss2022_tcp_decrypt(buffer_t *ciphertext, cipher_ctx_t *cipher_ctx,
|
||||
size_t capacity);
|
||||
|
||||
#endif // _AEAD_INTERNAL_H
|
||||
+7
-1
@@ -34,9 +34,15 @@
|
||||
#include "base64.h"
|
||||
|
||||
/* ---------------- private code */
|
||||
/*
|
||||
* Decoding table indexed by (c - 43). It accepts both the standard alphabet
|
||||
* ('+' and '/') and the URL-safe one ('-' and '_'), since keys are quoted
|
||||
* from tools using either: `openssl rand -base64` emits standard base64,
|
||||
* while shadowsocks configs have traditionally used the URL-safe form.
|
||||
*/
|
||||
static const uint8_t map2[] =
|
||||
{
|
||||
0xff, 0xff, 0x3e, 0xff, 0xff, 0x34, 0x35, 0x36,
|
||||
0x3e, 0xff, 0x3e, 0xff, 0x3f, 0x34, 0x35, 0x36,
|
||||
0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x01,
|
||||
0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09,
|
||||
|
||||
@@ -0,0 +1,121 @@
|
||||
Creative Commons Legal Code
|
||||
|
||||
CC0 1.0 Universal
|
||||
|
||||
CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE
|
||||
LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN
|
||||
ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS
|
||||
INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES
|
||||
REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS
|
||||
PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM
|
||||
THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED
|
||||
HEREUNDER.
|
||||
|
||||
Statement of Purpose
|
||||
|
||||
The laws of most jurisdictions throughout the world automatically confer
|
||||
exclusive Copyright and Related Rights (defined below) upon the creator
|
||||
and subsequent owner(s) (each and all, an "owner") of an original work of
|
||||
authorship and/or a database (each, a "Work").
|
||||
|
||||
Certain owners wish to permanently relinquish those rights to a Work for
|
||||
the purpose of contributing to a commons of creative, cultural and
|
||||
scientific works ("Commons") that the public can reliably and without fear
|
||||
of later claims of infringement build upon, modify, incorporate in other
|
||||
works, reuse and redistribute as freely as possible in any form whatsoever
|
||||
and for any purposes, including without limitation commercial purposes.
|
||||
These owners may contribute to the Commons to promote the ideal of a free
|
||||
culture and the further production of creative, cultural and scientific
|
||||
works, or to gain reputation or greater distribution for their Work in
|
||||
part through the use and efforts of others.
|
||||
|
||||
For these and/or other purposes and motivations, and without any
|
||||
expectation of additional consideration or compensation, the person
|
||||
associating CC0 with a Work (the "Affirmer"), to the extent that he or she
|
||||
is an owner of Copyright and Related Rights in the Work, voluntarily
|
||||
elects to apply CC0 to the Work and publicly distribute the Work under its
|
||||
terms, with knowledge of his or her Copyright and Related Rights in the
|
||||
Work and the meaning and intended legal effect of CC0 on those rights.
|
||||
|
||||
1. Copyright and Related Rights. A Work made available under CC0 may be
|
||||
protected by copyright and related or neighboring rights ("Copyright and
|
||||
Related Rights"). Copyright and Related Rights include, but are not
|
||||
limited to, the following:
|
||||
|
||||
i. the right to reproduce, adapt, distribute, perform, display,
|
||||
communicate, and translate a Work;
|
||||
ii. moral rights retained by the original author(s) and/or performer(s);
|
||||
iii. publicity and privacy rights pertaining to a person's image or
|
||||
likeness depicted in a Work;
|
||||
iv. rights protecting against unfair competition in regards to a Work,
|
||||
subject to the limitations in paragraph 4(a), below;
|
||||
v. rights protecting the extraction, dissemination, use and reuse of data
|
||||
in a Work;
|
||||
vi. database rights (such as those arising under Directive 96/9/EC of the
|
||||
European Parliament and of the Council of 11 March 1996 on the legal
|
||||
protection of databases, and under any national implementation
|
||||
thereof, including any amended or successor version of such
|
||||
directive); and
|
||||
vii. other similar, equivalent or corresponding rights throughout the
|
||||
world based on applicable law or treaty, and any national
|
||||
implementations thereof.
|
||||
|
||||
2. Waiver. To the greatest extent permitted by, but not in contravention
|
||||
of, applicable law, Affirmer hereby overtly, fully, permanently,
|
||||
irrevocably and unconditionally waives, abandons, and surrenders all of
|
||||
Affirmer's Copyright and Related Rights and associated claims and causes
|
||||
of action, whether now known or unknown (including existing as well as
|
||||
future claims and causes of action), in the Work (i) in all territories
|
||||
worldwide, (ii) for the maximum duration provided by applicable law or
|
||||
treaty (including future time extensions), (iii) in any current or future
|
||||
medium and for any number of copies, and (iv) for any purpose whatsoever,
|
||||
including without limitation commercial, advertising or promotional
|
||||
purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each
|
||||
member of the public at large and to the detriment of Affirmer's heirs and
|
||||
successors, fully intending that such Waiver shall not be subject to
|
||||
revocation, rescission, cancellation, termination, or any other legal or
|
||||
equitable action to disrupt the quiet enjoyment of the Work by the public
|
||||
as contemplated by Affirmer's express Statement of Purpose.
|
||||
|
||||
3. Public License Fallback. Should any part of the Waiver for any reason
|
||||
be judged legally invalid or ineffective under applicable law, then the
|
||||
Waiver shall be preserved to the maximum extent permitted taking into
|
||||
account Affirmer's express Statement of Purpose. In addition, to the
|
||||
extent the Waiver is so judged Affirmer hereby grants to each affected
|
||||
person a royalty-free, non transferable, non sublicensable, non exclusive,
|
||||
irrevocable and unconditional license to exercise Affirmer's Copyright and
|
||||
Related Rights in the Work (i) in all territories worldwide, (ii) for the
|
||||
maximum duration provided by applicable law or treaty (including future
|
||||
time extensions), (iii) in any current or future medium and for any number
|
||||
of copies, and (iv) for any purpose whatsoever, including without
|
||||
limitation commercial, advertising or promotional purposes (the
|
||||
"License"). The License shall be deemed effective as of the date CC0 was
|
||||
applied by Affirmer to the Work. Should any part of the License for any
|
||||
reason be judged legally invalid or ineffective under applicable law, such
|
||||
partial invalidity or ineffectiveness shall not invalidate the remainder
|
||||
of the License, and in such case Affirmer hereby affirms that he or she
|
||||
will not (i) exercise any of his or her remaining Copyright and Related
|
||||
Rights in the Work or (ii) assert any associated claims and causes of
|
||||
action with respect to the Work, in either case contrary to Affirmer's
|
||||
express Statement of Purpose.
|
||||
|
||||
4. Limitations and Disclaimers.
|
||||
|
||||
a. No trademark or patent rights held by Affirmer are waived, abandoned,
|
||||
surrendered, licensed or otherwise affected by this document.
|
||||
b. Affirmer offers the Work as-is and makes no representations or
|
||||
warranties of any kind concerning the Work, express, implied,
|
||||
statutory or otherwise, including without limitation warranties of
|
||||
title, merchantability, fitness for a particular purpose, non
|
||||
infringement, or the absence of latent or other defects, accuracy, or
|
||||
the present or absence of errors, whether or not discoverable, all to
|
||||
the greatest extent permissible under applicable law.
|
||||
c. Affirmer disclaims responsibility for clearing rights of other persons
|
||||
that may apply to the Work or any use thereof, including without
|
||||
limitation any person's Copyright and Related Rights in the Work.
|
||||
Further, Affirmer disclaims responsibility for obtaining any necessary
|
||||
consents, permissions or other rights required for any use of the
|
||||
Work.
|
||||
d. Affirmer understands and acknowledges that Creative Commons is not a
|
||||
party to this document and has no duty or obligation with respect to
|
||||
this CC0 or use of the Work.
|
||||
@@ -0,0 +1,623 @@
|
||||
#include <assert.h>
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "blake3.h"
|
||||
#include "blake3_impl.h"
|
||||
|
||||
const char *blake3_version(void) { return BLAKE3_VERSION_STRING; }
|
||||
|
||||
INLINE void chunk_state_init(blake3_chunk_state *self, const uint32_t key[8],
|
||||
uint8_t flags) {
|
||||
memcpy(self->cv, key, BLAKE3_KEY_LEN);
|
||||
self->chunk_counter = 0;
|
||||
memset(self->buf, 0, BLAKE3_BLOCK_LEN);
|
||||
self->buf_len = 0;
|
||||
self->blocks_compressed = 0;
|
||||
self->flags = flags;
|
||||
}
|
||||
|
||||
INLINE void chunk_state_reset(blake3_chunk_state *self, const uint32_t key[8],
|
||||
uint64_t chunk_counter) {
|
||||
memcpy(self->cv, key, BLAKE3_KEY_LEN);
|
||||
self->chunk_counter = chunk_counter;
|
||||
self->blocks_compressed = 0;
|
||||
memset(self->buf, 0, BLAKE3_BLOCK_LEN);
|
||||
self->buf_len = 0;
|
||||
}
|
||||
|
||||
INLINE size_t chunk_state_len(const blake3_chunk_state *self) {
|
||||
return (BLAKE3_BLOCK_LEN * (size_t)self->blocks_compressed) +
|
||||
((size_t)self->buf_len);
|
||||
}
|
||||
|
||||
INLINE size_t chunk_state_fill_buf(blake3_chunk_state *self,
|
||||
const uint8_t *input, size_t input_len) {
|
||||
size_t take = BLAKE3_BLOCK_LEN - ((size_t)self->buf_len);
|
||||
if (take > input_len) {
|
||||
take = input_len;
|
||||
}
|
||||
uint8_t *dest = self->buf + ((size_t)self->buf_len);
|
||||
memcpy(dest, input, take);
|
||||
self->buf_len += (uint8_t)take;
|
||||
return take;
|
||||
}
|
||||
|
||||
INLINE uint8_t chunk_state_maybe_start_flag(const blake3_chunk_state *self) {
|
||||
if (self->blocks_compressed == 0) {
|
||||
return CHUNK_START;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
uint32_t input_cv[8];
|
||||
uint64_t counter;
|
||||
uint8_t block[BLAKE3_BLOCK_LEN];
|
||||
uint8_t block_len;
|
||||
uint8_t flags;
|
||||
} output_t;
|
||||
|
||||
INLINE output_t make_output(const uint32_t input_cv[8],
|
||||
const uint8_t block[BLAKE3_BLOCK_LEN],
|
||||
uint8_t block_len, uint64_t counter,
|
||||
uint8_t flags) {
|
||||
output_t ret;
|
||||
memcpy(ret.input_cv, input_cv, 32);
|
||||
memcpy(ret.block, block, BLAKE3_BLOCK_LEN);
|
||||
ret.block_len = block_len;
|
||||
ret.counter = counter;
|
||||
ret.flags = flags;
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Chaining values within a given chunk (specifically the compress_in_place
|
||||
// interface) are represented as words. This avoids unnecessary bytes<->words
|
||||
// conversion overhead in the portable implementation. However, the hash_many
|
||||
// interface handles both user input and parent node blocks, so it accepts
|
||||
// bytes. For that reason, chaining values in the CV stack are represented as
|
||||
// bytes.
|
||||
INLINE void output_chaining_value(const output_t *self, uint8_t cv[32]) {
|
||||
uint32_t cv_words[8];
|
||||
memcpy(cv_words, self->input_cv, 32);
|
||||
blake3_compress_in_place(cv_words, self->block, self->block_len,
|
||||
self->counter, self->flags);
|
||||
store_cv_words(cv, cv_words);
|
||||
}
|
||||
|
||||
INLINE void output_root_bytes(const output_t *self, uint64_t seek, uint8_t *out,
|
||||
size_t out_len) {
|
||||
if (out_len == 0) {
|
||||
return;
|
||||
}
|
||||
uint64_t output_block_counter = seek / 64;
|
||||
size_t offset_within_block = seek % 64;
|
||||
uint8_t wide_buf[64];
|
||||
if(offset_within_block) {
|
||||
blake3_compress_xof(self->input_cv, self->block, self->block_len, output_block_counter, self->flags | ROOT, wide_buf);
|
||||
const size_t available_bytes = 64 - offset_within_block;
|
||||
const size_t bytes = out_len > available_bytes ? available_bytes : out_len;
|
||||
memcpy(out, wide_buf + offset_within_block, bytes);
|
||||
out += bytes;
|
||||
out_len -= bytes;
|
||||
output_block_counter += 1;
|
||||
}
|
||||
if(out_len / 64) {
|
||||
blake3_xof_many(self->input_cv, self->block, self->block_len, output_block_counter, self->flags | ROOT, out, out_len / 64);
|
||||
}
|
||||
output_block_counter += out_len / 64;
|
||||
out += out_len & -64;
|
||||
out_len -= out_len & -64;
|
||||
if(out_len) {
|
||||
blake3_compress_xof(self->input_cv, self->block, self->block_len, output_block_counter, self->flags | ROOT, wide_buf);
|
||||
memcpy(out, wide_buf, out_len);
|
||||
}
|
||||
}
|
||||
|
||||
INLINE void chunk_state_update(blake3_chunk_state *self, const uint8_t *input,
|
||||
size_t input_len) {
|
||||
if (self->buf_len > 0) {
|
||||
size_t take = chunk_state_fill_buf(self, input, input_len);
|
||||
input += take;
|
||||
input_len -= take;
|
||||
if (input_len > 0) {
|
||||
blake3_compress_in_place(
|
||||
self->cv, self->buf, BLAKE3_BLOCK_LEN, self->chunk_counter,
|
||||
self->flags | chunk_state_maybe_start_flag(self));
|
||||
self->blocks_compressed += 1;
|
||||
self->buf_len = 0;
|
||||
memset(self->buf, 0, BLAKE3_BLOCK_LEN);
|
||||
}
|
||||
}
|
||||
|
||||
while (input_len > BLAKE3_BLOCK_LEN) {
|
||||
blake3_compress_in_place(self->cv, input, BLAKE3_BLOCK_LEN,
|
||||
self->chunk_counter,
|
||||
self->flags | chunk_state_maybe_start_flag(self));
|
||||
self->blocks_compressed += 1;
|
||||
input += BLAKE3_BLOCK_LEN;
|
||||
input_len -= BLAKE3_BLOCK_LEN;
|
||||
}
|
||||
|
||||
chunk_state_fill_buf(self, input, input_len);
|
||||
}
|
||||
|
||||
INLINE output_t chunk_state_output(const blake3_chunk_state *self) {
|
||||
uint8_t block_flags =
|
||||
self->flags | chunk_state_maybe_start_flag(self) | CHUNK_END;
|
||||
return make_output(self->cv, self->buf, self->buf_len, self->chunk_counter,
|
||||
block_flags);
|
||||
}
|
||||
|
||||
INLINE output_t parent_output(const uint8_t block[BLAKE3_BLOCK_LEN],
|
||||
const uint32_t key[8], uint8_t flags) {
|
||||
return make_output(key, block, BLAKE3_BLOCK_LEN, 0, flags | PARENT);
|
||||
}
|
||||
|
||||
// Given some input larger than one chunk, return the number of bytes that
|
||||
// should go in the left subtree. This is the largest power-of-2 number of
|
||||
// chunks that leaves at least 1 byte for the right subtree.
|
||||
INLINE size_t left_len(size_t content_len) {
|
||||
// Subtract 1 to reserve at least one byte for the right side. content_len
|
||||
// should always be greater than BLAKE3_CHUNK_LEN.
|
||||
size_t full_chunks = (content_len - 1) / BLAKE3_CHUNK_LEN;
|
||||
return round_down_to_power_of_2(full_chunks) * BLAKE3_CHUNK_LEN;
|
||||
}
|
||||
|
||||
// Use SIMD parallelism to hash up to MAX_SIMD_DEGREE chunks at the same time
|
||||
// on a single thread. Write out the chunk chaining values and return the
|
||||
// number of chunks hashed. These chunks are never the root and never empty;
|
||||
// those cases use a different codepath.
|
||||
INLINE size_t compress_chunks_parallel(const uint8_t *input, size_t input_len,
|
||||
const uint32_t key[8],
|
||||
uint64_t chunk_counter, uint8_t flags,
|
||||
uint8_t *out) {
|
||||
#if defined(BLAKE3_TESTING)
|
||||
assert(0 < input_len);
|
||||
assert(input_len <= MAX_SIMD_DEGREE * BLAKE3_CHUNK_LEN);
|
||||
#endif
|
||||
|
||||
const uint8_t *chunks_array[MAX_SIMD_DEGREE];
|
||||
size_t input_position = 0;
|
||||
size_t chunks_array_len = 0;
|
||||
while (input_len - input_position >= BLAKE3_CHUNK_LEN) {
|
||||
chunks_array[chunks_array_len] = &input[input_position];
|
||||
input_position += BLAKE3_CHUNK_LEN;
|
||||
chunks_array_len += 1;
|
||||
}
|
||||
|
||||
blake3_hash_many(chunks_array, chunks_array_len,
|
||||
BLAKE3_CHUNK_LEN / BLAKE3_BLOCK_LEN, key, chunk_counter,
|
||||
true, flags, CHUNK_START, CHUNK_END, out);
|
||||
|
||||
// Hash the remaining partial chunk, if there is one. Note that the empty
|
||||
// chunk (meaning the empty message) is a different codepath.
|
||||
if (input_len > input_position) {
|
||||
uint64_t counter = chunk_counter + (uint64_t)chunks_array_len;
|
||||
blake3_chunk_state chunk_state;
|
||||
chunk_state_init(&chunk_state, key, flags);
|
||||
chunk_state.chunk_counter = counter;
|
||||
chunk_state_update(&chunk_state, &input[input_position],
|
||||
input_len - input_position);
|
||||
output_t output = chunk_state_output(&chunk_state);
|
||||
output_chaining_value(&output, &out[chunks_array_len * BLAKE3_OUT_LEN]);
|
||||
return chunks_array_len + 1;
|
||||
} else {
|
||||
return chunks_array_len;
|
||||
}
|
||||
}
|
||||
|
||||
// Use SIMD parallelism to hash up to MAX_SIMD_DEGREE parents at the same time
|
||||
// on a single thread. Write out the parent chaining values and return the
|
||||
// number of parents hashed. (If there's an odd input chaining value left over,
|
||||
// return it as an additional output.) These parents are never the root and
|
||||
// never empty; those cases use a different codepath.
|
||||
INLINE size_t compress_parents_parallel(const uint8_t *child_chaining_values,
|
||||
size_t num_chaining_values,
|
||||
const uint32_t key[8], uint8_t flags,
|
||||
uint8_t *out) {
|
||||
#if defined(BLAKE3_TESTING)
|
||||
assert(2 <= num_chaining_values);
|
||||
assert(num_chaining_values <= 2 * MAX_SIMD_DEGREE_OR_2);
|
||||
#endif
|
||||
|
||||
const uint8_t *parents_array[MAX_SIMD_DEGREE_OR_2];
|
||||
size_t parents_array_len = 0;
|
||||
while (num_chaining_values - (2 * parents_array_len) >= 2) {
|
||||
parents_array[parents_array_len] =
|
||||
&child_chaining_values[2 * parents_array_len * BLAKE3_OUT_LEN];
|
||||
parents_array_len += 1;
|
||||
}
|
||||
|
||||
blake3_hash_many(parents_array, parents_array_len, 1, key,
|
||||
0, // Parents always use counter 0.
|
||||
false, flags | PARENT,
|
||||
0, // Parents have no start flags.
|
||||
0, // Parents have no end flags.
|
||||
out);
|
||||
|
||||
// If there's an odd child left over, it becomes an output.
|
||||
if (num_chaining_values > 2 * parents_array_len) {
|
||||
memcpy(&out[parents_array_len * BLAKE3_OUT_LEN],
|
||||
&child_chaining_values[2 * parents_array_len * BLAKE3_OUT_LEN],
|
||||
BLAKE3_OUT_LEN);
|
||||
return parents_array_len + 1;
|
||||
} else {
|
||||
return parents_array_len;
|
||||
}
|
||||
}
|
||||
|
||||
// The wide helper function returns (writes out) an array of chaining values
|
||||
// and returns the length of that array. The number of chaining values returned
|
||||
// is the dynamically detected SIMD degree, at most MAX_SIMD_DEGREE. Or fewer,
|
||||
// if the input is shorter than that many chunks. The reason for maintaining a
|
||||
// wide array of chaining values going back up the tree, is to allow the
|
||||
// implementation to hash as many parents in parallel as possible.
|
||||
//
|
||||
// As a special case when the SIMD degree is 1, this function will still return
|
||||
// at least 2 outputs. This guarantees that this function doesn't perform the
|
||||
// root compression. (If it did, it would use the wrong flags, and also we
|
||||
// wouldn't be able to implement extendable output.) Note that this function is
|
||||
// not used when the whole input is only 1 chunk long; that's a different
|
||||
// codepath.
|
||||
//
|
||||
// Why not just have the caller split the input on the first update(), instead
|
||||
// of implementing this special rule? Because we don't want to limit SIMD or
|
||||
// multi-threading parallelism for that update().
|
||||
static size_t blake3_compress_subtree_wide(const uint8_t *input,
|
||||
size_t input_len,
|
||||
const uint32_t key[8],
|
||||
uint64_t chunk_counter,
|
||||
uint8_t flags, uint8_t *out) {
|
||||
// Note that the single chunk case does *not* bump the SIMD degree up to 2
|
||||
// when it is 1. If this implementation adds multi-threading in the future,
|
||||
// this gives us the option of multi-threading even the 2-chunk case, which
|
||||
// can help performance on smaller platforms.
|
||||
if (input_len <= blake3_simd_degree() * BLAKE3_CHUNK_LEN) {
|
||||
return compress_chunks_parallel(input, input_len, key, chunk_counter, flags,
|
||||
out);
|
||||
}
|
||||
|
||||
// With more than simd_degree chunks, we need to recurse. Start by dividing
|
||||
// the input into left and right subtrees. (Note that this is only optimal
|
||||
// as long as the SIMD degree is a power of 2. If we ever get a SIMD degree
|
||||
// of 3 or something, we'll need a more complicated strategy.)
|
||||
size_t left_input_len = left_len(input_len);
|
||||
size_t right_input_len = input_len - left_input_len;
|
||||
const uint8_t *right_input = &input[left_input_len];
|
||||
uint64_t right_chunk_counter =
|
||||
chunk_counter + (uint64_t)(left_input_len / BLAKE3_CHUNK_LEN);
|
||||
|
||||
// Make space for the child outputs. Here we use MAX_SIMD_DEGREE_OR_2 to
|
||||
// account for the special case of returning 2 outputs when the SIMD degree
|
||||
// is 1.
|
||||
uint8_t cv_array[2 * MAX_SIMD_DEGREE_OR_2 * BLAKE3_OUT_LEN];
|
||||
size_t degree = blake3_simd_degree();
|
||||
if (left_input_len > BLAKE3_CHUNK_LEN && degree == 1) {
|
||||
// The special case: We always use a degree of at least two, to make
|
||||
// sure there are two outputs. Except, as noted above, at the chunk
|
||||
// level, where we allow degree=1. (Note that the 1-chunk-input case is
|
||||
// a different codepath.)
|
||||
degree = 2;
|
||||
}
|
||||
uint8_t *right_cvs = &cv_array[degree * BLAKE3_OUT_LEN];
|
||||
|
||||
// Recurse! If this implementation adds multi-threading support in the
|
||||
// future, this is where it will go.
|
||||
size_t left_n = blake3_compress_subtree_wide(input, left_input_len, key,
|
||||
chunk_counter, flags, cv_array);
|
||||
size_t right_n = blake3_compress_subtree_wide(
|
||||
right_input, right_input_len, key, right_chunk_counter, flags, right_cvs);
|
||||
|
||||
// The special case again. If simd_degree=1, then we'll have left_n=1 and
|
||||
// right_n=1. Rather than compressing them into a single output, return
|
||||
// them directly, to make sure we always have at least two outputs.
|
||||
if (left_n == 1) {
|
||||
memcpy(out, cv_array, 2 * BLAKE3_OUT_LEN);
|
||||
return 2;
|
||||
}
|
||||
|
||||
// Otherwise, do one layer of parent node compression.
|
||||
size_t num_chaining_values = left_n + right_n;
|
||||
return compress_parents_parallel(cv_array, num_chaining_values, key, flags,
|
||||
out);
|
||||
}
|
||||
|
||||
// Hash a subtree with compress_subtree_wide(), and then condense the resulting
|
||||
// list of chaining values down to a single parent node. Don't compress that
|
||||
// last parent node, however. Instead, return its message bytes (the
|
||||
// concatenated chaining values of its children). This is necessary when the
|
||||
// first call to update() supplies a complete subtree, because the topmost
|
||||
// parent node of that subtree could end up being the root. It's also necessary
|
||||
// for extended output in the general case.
|
||||
//
|
||||
// As with compress_subtree_wide(), this function is not used on inputs of 1
|
||||
// chunk or less. That's a different codepath.
|
||||
INLINE void compress_subtree_to_parent_node(
|
||||
const uint8_t *input, size_t input_len, const uint32_t key[8],
|
||||
uint64_t chunk_counter, uint8_t flags, uint8_t out[2 * BLAKE3_OUT_LEN]) {
|
||||
#if defined(BLAKE3_TESTING)
|
||||
assert(input_len > BLAKE3_CHUNK_LEN);
|
||||
#endif
|
||||
|
||||
uint8_t cv_array[MAX_SIMD_DEGREE_OR_2 * BLAKE3_OUT_LEN];
|
||||
size_t num_cvs = blake3_compress_subtree_wide(input, input_len, key,
|
||||
chunk_counter, flags, cv_array);
|
||||
assert(num_cvs <= MAX_SIMD_DEGREE_OR_2);
|
||||
// The following loop never executes when MAX_SIMD_DEGREE_OR_2 is 2, because
|
||||
// as we just asserted, num_cvs will always be <=2 in that case. But GCC
|
||||
// (particularly GCC 8.5) can't tell that it never executes, and if NDEBUG is
|
||||
// set then it emits incorrect warnings here. We tried a few different
|
||||
// hacks to silence these, but in the end our hacks just produced different
|
||||
// warnings (see https://github.com/BLAKE3-team/BLAKE3/pull/380). Out of
|
||||
// desperation, we ifdef out this entire loop when we know it's not needed.
|
||||
#if MAX_SIMD_DEGREE_OR_2 > 2
|
||||
// If MAX_SIMD_DEGREE_OR_2 is greater than 2 and there's enough input,
|
||||
// compress_subtree_wide() returns more than 2 chaining values. Condense
|
||||
// them into 2 by forming parent nodes repeatedly.
|
||||
uint8_t out_array[MAX_SIMD_DEGREE_OR_2 * BLAKE3_OUT_LEN / 2];
|
||||
while (num_cvs > 2) {
|
||||
num_cvs =
|
||||
compress_parents_parallel(cv_array, num_cvs, key, flags, out_array);
|
||||
memcpy(cv_array, out_array, num_cvs * BLAKE3_OUT_LEN);
|
||||
}
|
||||
#endif
|
||||
memcpy(out, cv_array, 2 * BLAKE3_OUT_LEN);
|
||||
}
|
||||
|
||||
INLINE void hasher_init_base(blake3_hasher *self, const uint32_t key[8],
|
||||
uint8_t flags) {
|
||||
memcpy(self->key, key, BLAKE3_KEY_LEN);
|
||||
chunk_state_init(&self->chunk, key, flags);
|
||||
self->cv_stack_len = 0;
|
||||
}
|
||||
|
||||
void blake3_hasher_init(blake3_hasher *self) { hasher_init_base(self, IV, 0); }
|
||||
|
||||
void blake3_hasher_init_keyed(blake3_hasher *self,
|
||||
const uint8_t key[BLAKE3_KEY_LEN]) {
|
||||
uint32_t key_words[8];
|
||||
load_key_words(key, key_words);
|
||||
hasher_init_base(self, key_words, KEYED_HASH);
|
||||
}
|
||||
|
||||
void blake3_hasher_init_derive_key_raw(blake3_hasher *self, const void *context,
|
||||
size_t context_len) {
|
||||
blake3_hasher context_hasher;
|
||||
hasher_init_base(&context_hasher, IV, DERIVE_KEY_CONTEXT);
|
||||
blake3_hasher_update(&context_hasher, context, context_len);
|
||||
uint8_t context_key[BLAKE3_KEY_LEN];
|
||||
blake3_hasher_finalize(&context_hasher, context_key, BLAKE3_KEY_LEN);
|
||||
uint32_t context_key_words[8];
|
||||
load_key_words(context_key, context_key_words);
|
||||
hasher_init_base(self, context_key_words, DERIVE_KEY_MATERIAL);
|
||||
}
|
||||
|
||||
void blake3_hasher_init_derive_key(blake3_hasher *self, const char *context) {
|
||||
blake3_hasher_init_derive_key_raw(self, context, strlen(context));
|
||||
}
|
||||
|
||||
// As described in hasher_push_cv() below, we do "lazy merging", delaying
|
||||
// merges until right before the next CV is about to be added. This is
|
||||
// different from the reference implementation. Another difference is that we
|
||||
// aren't always merging 1 chunk at a time. Instead, each CV might represent
|
||||
// any power-of-two number of chunks, as long as the smaller-above-larger stack
|
||||
// order is maintained. Instead of the "count the trailing 0-bits" algorithm
|
||||
// described in the spec, we use a "count the total number of 1-bits" variant
|
||||
// that doesn't require us to retain the subtree size of the CV on top of the
|
||||
// stack. The principle is the same: each CV that should remain in the stack is
|
||||
// represented by a 1-bit in the total number of chunks (or bytes) so far.
|
||||
INLINE void hasher_merge_cv_stack(blake3_hasher *self, uint64_t total_len) {
|
||||
size_t post_merge_stack_len = (size_t)popcnt(total_len);
|
||||
while (self->cv_stack_len > post_merge_stack_len) {
|
||||
uint8_t *parent_node =
|
||||
&self->cv_stack[(self->cv_stack_len - 2) * BLAKE3_OUT_LEN];
|
||||
output_t output = parent_output(parent_node, self->key, self->chunk.flags);
|
||||
output_chaining_value(&output, parent_node);
|
||||
self->cv_stack_len -= 1;
|
||||
}
|
||||
}
|
||||
|
||||
// In reference_impl.rs, we merge the new CV with existing CVs from the stack
|
||||
// before pushing it. We can do that because we know more input is coming, so
|
||||
// we know none of the merges are root.
|
||||
//
|
||||
// This setting is different. We want to feed as much input as possible to
|
||||
// compress_subtree_wide(), without setting aside anything for the chunk_state.
|
||||
// If the user gives us 64 KiB, we want to parallelize over all 64 KiB at once
|
||||
// as a single subtree, if at all possible.
|
||||
//
|
||||
// This leads to two problems:
|
||||
// 1) This 64 KiB input might be the only call that ever gets made to update.
|
||||
// In this case, the root node of the 64 KiB subtree would be the root node
|
||||
// of the whole tree, and it would need to be ROOT finalized. We can't
|
||||
// compress it until we know.
|
||||
// 2) This 64 KiB input might complete a larger tree, whose root node is
|
||||
// similarly going to be the root of the whole tree. For example, maybe
|
||||
// we have 196 KiB (that is, 128 + 64) hashed so far. We can't compress the
|
||||
// node at the root of the 256 KiB subtree until we know how to finalize it.
|
||||
//
|
||||
// The second problem is solved with "lazy merging". That is, when we're about
|
||||
// to add a CV to the stack, we don't merge it with anything first, as the
|
||||
// reference impl does. Instead we do merges using the *previous* CV that was
|
||||
// added, which is sitting on top of the stack, and we put the new CV
|
||||
// (unmerged) on top of the stack afterwards. This guarantees that we never
|
||||
// merge the root node until finalize().
|
||||
//
|
||||
// Solving the first problem requires an additional tool,
|
||||
// compress_subtree_to_parent_node(). That function always returns the top
|
||||
// *two* chaining values of the subtree it's compressing. We then do lazy
|
||||
// merging with each of them separately, so that the second CV will always
|
||||
// remain unmerged. (That also helps us support extendable output when we're
|
||||
// hashing an input all-at-once.)
|
||||
INLINE void hasher_push_cv(blake3_hasher *self, uint8_t new_cv[BLAKE3_OUT_LEN],
|
||||
uint64_t chunk_counter) {
|
||||
hasher_merge_cv_stack(self, chunk_counter);
|
||||
memcpy(&self->cv_stack[self->cv_stack_len * BLAKE3_OUT_LEN], new_cv,
|
||||
BLAKE3_OUT_LEN);
|
||||
self->cv_stack_len += 1;
|
||||
}
|
||||
|
||||
void blake3_hasher_update(blake3_hasher *self, const void *input,
|
||||
size_t input_len) {
|
||||
// Explicitly checking for zero avoids causing UB by passing a null pointer
|
||||
// to memcpy. This comes up in practice with things like:
|
||||
// std::vector<uint8_t> v;
|
||||
// blake3_hasher_update(&hasher, v.data(), v.size());
|
||||
if (input_len == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const uint8_t *input_bytes = (const uint8_t *)input;
|
||||
|
||||
// If we have some partial chunk bytes in the internal chunk_state, we need
|
||||
// to finish that chunk first.
|
||||
if (chunk_state_len(&self->chunk) > 0) {
|
||||
size_t take = BLAKE3_CHUNK_LEN - chunk_state_len(&self->chunk);
|
||||
if (take > input_len) {
|
||||
take = input_len;
|
||||
}
|
||||
chunk_state_update(&self->chunk, input_bytes, take);
|
||||
input_bytes += take;
|
||||
input_len -= take;
|
||||
// If we've filled the current chunk and there's more coming, finalize this
|
||||
// chunk and proceed. In this case we know it's not the root.
|
||||
if (input_len > 0) {
|
||||
output_t output = chunk_state_output(&self->chunk);
|
||||
uint8_t chunk_cv[32];
|
||||
output_chaining_value(&output, chunk_cv);
|
||||
hasher_push_cv(self, chunk_cv, self->chunk.chunk_counter);
|
||||
chunk_state_reset(&self->chunk, self->key, self->chunk.chunk_counter + 1);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Now the chunk_state is clear, and we have more input. If there's more than
|
||||
// a single chunk (so, definitely not the root chunk), hash the largest whole
|
||||
// subtree we can, with the full benefits of SIMD (and maybe in the future,
|
||||
// multi-threading) parallelism. Two restrictions:
|
||||
// - The subtree has to be a power-of-2 number of chunks. Only subtrees along
|
||||
// the right edge can be incomplete, and we don't know where the right edge
|
||||
// is going to be until we get to finalize().
|
||||
// - The subtree must evenly divide the total number of chunks up until this
|
||||
// point (if total is not 0). If the current incomplete subtree is only
|
||||
// waiting for 1 more chunk, we can't hash a subtree of 4 chunks. We have
|
||||
// to complete the current subtree first.
|
||||
// Because we might need to break up the input to form powers of 2, or to
|
||||
// evenly divide what we already have, this part runs in a loop.
|
||||
while (input_len > BLAKE3_CHUNK_LEN) {
|
||||
size_t subtree_len = round_down_to_power_of_2(input_len);
|
||||
uint64_t count_so_far = self->chunk.chunk_counter * BLAKE3_CHUNK_LEN;
|
||||
// Shrink the subtree_len until it evenly divides the count so far. We know
|
||||
// that subtree_len itself is a power of 2, so we can use a bitmasking
|
||||
// trick instead of an actual remainder operation. (Note that if the caller
|
||||
// consistently passes power-of-2 inputs of the same size, as is hopefully
|
||||
// typical, this loop condition will always fail, and subtree_len will
|
||||
// always be the full length of the input.)
|
||||
//
|
||||
// An aside: We don't have to shrink subtree_len quite this much. For
|
||||
// example, if count_so_far is 1, we could pass 2 chunks to
|
||||
// compress_subtree_to_parent_node. Since we'll get 2 CVs back, we'll still
|
||||
// get the right answer in the end, and we might get to use 2-way SIMD
|
||||
// parallelism. The problem with this optimization, is that it gets us
|
||||
// stuck always hashing 2 chunks. The total number of chunks will remain
|
||||
// odd, and we'll never graduate to higher degrees of parallelism. See
|
||||
// https://github.com/BLAKE3-team/BLAKE3/issues/69.
|
||||
while ((((uint64_t)(subtree_len - 1)) & count_so_far) != 0) {
|
||||
subtree_len /= 2;
|
||||
}
|
||||
// The shrunken subtree_len might now be 1 chunk long. If so, hash that one
|
||||
// chunk by itself. Otherwise, compress the subtree into a pair of CVs.
|
||||
uint64_t subtree_chunks = subtree_len / BLAKE3_CHUNK_LEN;
|
||||
if (subtree_len <= BLAKE3_CHUNK_LEN) {
|
||||
blake3_chunk_state chunk_state;
|
||||
chunk_state_init(&chunk_state, self->key, self->chunk.flags);
|
||||
chunk_state.chunk_counter = self->chunk.chunk_counter;
|
||||
chunk_state_update(&chunk_state, input_bytes, subtree_len);
|
||||
output_t output = chunk_state_output(&chunk_state);
|
||||
uint8_t cv[BLAKE3_OUT_LEN];
|
||||
output_chaining_value(&output, cv);
|
||||
hasher_push_cv(self, cv, chunk_state.chunk_counter);
|
||||
} else {
|
||||
// This is the high-performance happy path, though getting here depends
|
||||
// on the caller giving us a long enough input.
|
||||
uint8_t cv_pair[2 * BLAKE3_OUT_LEN];
|
||||
compress_subtree_to_parent_node(input_bytes, subtree_len, self->key,
|
||||
self->chunk.chunk_counter,
|
||||
self->chunk.flags, cv_pair);
|
||||
hasher_push_cv(self, cv_pair, self->chunk.chunk_counter);
|
||||
hasher_push_cv(self, &cv_pair[BLAKE3_OUT_LEN],
|
||||
self->chunk.chunk_counter + (subtree_chunks / 2));
|
||||
}
|
||||
self->chunk.chunk_counter += subtree_chunks;
|
||||
input_bytes += subtree_len;
|
||||
input_len -= subtree_len;
|
||||
}
|
||||
|
||||
// If there's any remaining input less than a full chunk, add it to the chunk
|
||||
// state. In that case, also do a final merge loop to make sure the subtree
|
||||
// stack doesn't contain any unmerged pairs. The remaining input means we
|
||||
// know these merges are non-root. This merge loop isn't strictly necessary
|
||||
// here, because hasher_push_chunk_cv already does its own merge loop, but it
|
||||
// simplifies blake3_hasher_finalize below.
|
||||
if (input_len > 0) {
|
||||
chunk_state_update(&self->chunk, input_bytes, input_len);
|
||||
hasher_merge_cv_stack(self, self->chunk.chunk_counter);
|
||||
}
|
||||
}
|
||||
|
||||
void blake3_hasher_finalize(const blake3_hasher *self, uint8_t *out,
|
||||
size_t out_len) {
|
||||
blake3_hasher_finalize_seek(self, 0, out, out_len);
|
||||
}
|
||||
|
||||
void blake3_hasher_finalize_seek(const blake3_hasher *self, uint64_t seek,
|
||||
uint8_t *out, size_t out_len) {
|
||||
// Explicitly checking for zero avoids causing UB by passing a null pointer
|
||||
// to memcpy. This comes up in practice with things like:
|
||||
// std::vector<uint8_t> v;
|
||||
// blake3_hasher_finalize(&hasher, v.data(), v.size());
|
||||
if (out_len == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If the subtree stack is empty, then the current chunk is the root.
|
||||
if (self->cv_stack_len == 0) {
|
||||
output_t output = chunk_state_output(&self->chunk);
|
||||
output_root_bytes(&output, seek, out, out_len);
|
||||
return;
|
||||
}
|
||||
// If there are any bytes in the chunk state, finalize that chunk and do a
|
||||
// roll-up merge between that chunk hash and every subtree in the stack. In
|
||||
// this case, the extra merge loop at the end of blake3_hasher_update
|
||||
// guarantees that none of the subtrees in the stack need to be merged with
|
||||
// each other first. Otherwise, if there are no bytes in the chunk state,
|
||||
// then the top of the stack is a chunk hash, and we start the merge from
|
||||
// that.
|
||||
output_t output;
|
||||
size_t cvs_remaining;
|
||||
if (chunk_state_len(&self->chunk) > 0) {
|
||||
cvs_remaining = self->cv_stack_len;
|
||||
output = chunk_state_output(&self->chunk);
|
||||
} else {
|
||||
// There are always at least 2 CVs in the stack in this case.
|
||||
cvs_remaining = self->cv_stack_len - 2;
|
||||
output = parent_output(&self->cv_stack[cvs_remaining * 32], self->key,
|
||||
self->chunk.flags);
|
||||
}
|
||||
while (cvs_remaining > 0) {
|
||||
cvs_remaining -= 1;
|
||||
uint8_t parent_block[BLAKE3_BLOCK_LEN];
|
||||
memcpy(parent_block, &self->cv_stack[cvs_remaining * 32], 32);
|
||||
output_chaining_value(&output, &parent_block[32]);
|
||||
output = parent_output(parent_block, self->key, self->chunk.flags);
|
||||
}
|
||||
output_root_bytes(&output, seek, out, out_len);
|
||||
}
|
||||
|
||||
void blake3_hasher_reset(blake3_hasher *self) {
|
||||
chunk_state_reset(&self->chunk, self->key, 0);
|
||||
self->cv_stack_len = 0;
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
#ifndef BLAKE3_H
|
||||
#define BLAKE3_H
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#if !defined(BLAKE3_API)
|
||||
# if defined(_WIN32) || defined(__CYGWIN__)
|
||||
# if defined(BLAKE3_DLL)
|
||||
# if defined(BLAKE3_DLL_EXPORTS)
|
||||
# define BLAKE3_API __declspec(dllexport)
|
||||
# else
|
||||
# define BLAKE3_API __declspec(dllimport)
|
||||
# endif
|
||||
# define BLAKE3_PRIVATE
|
||||
# else
|
||||
# define BLAKE3_API
|
||||
# define BLAKE3_PRIVATE
|
||||
# endif
|
||||
# elif __GNUC__ >= 4
|
||||
# define BLAKE3_API __attribute__((visibility("default")))
|
||||
# define BLAKE3_PRIVATE __attribute__((visibility("hidden")))
|
||||
# else
|
||||
# define BLAKE3_API
|
||||
# define BLAKE3_PRIVATE
|
||||
# endif
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define BLAKE3_VERSION_STRING "1.5.5"
|
||||
#define BLAKE3_KEY_LEN 32
|
||||
#define BLAKE3_OUT_LEN 32
|
||||
#define BLAKE3_BLOCK_LEN 64
|
||||
#define BLAKE3_CHUNK_LEN 1024
|
||||
#define BLAKE3_MAX_DEPTH 54
|
||||
|
||||
// This struct is a private implementation detail. It has to be here because
|
||||
// it's part of blake3_hasher below.
|
||||
typedef struct {
|
||||
uint32_t cv[8];
|
||||
uint64_t chunk_counter;
|
||||
uint8_t buf[BLAKE3_BLOCK_LEN];
|
||||
uint8_t buf_len;
|
||||
uint8_t blocks_compressed;
|
||||
uint8_t flags;
|
||||
} blake3_chunk_state;
|
||||
|
||||
typedef struct {
|
||||
uint32_t key[8];
|
||||
blake3_chunk_state chunk;
|
||||
uint8_t cv_stack_len;
|
||||
// The stack size is MAX_DEPTH + 1 because we do lazy merging. For example,
|
||||
// with 7 chunks, we have 3 entries in the stack. Adding an 8th chunk
|
||||
// requires a 4th entry, rather than merging everything down to 1, because we
|
||||
// don't know whether more input is coming. This is different from how the
|
||||
// reference implementation does things.
|
||||
uint8_t cv_stack[(BLAKE3_MAX_DEPTH + 1) * BLAKE3_OUT_LEN];
|
||||
} blake3_hasher;
|
||||
|
||||
BLAKE3_API const char *blake3_version(void);
|
||||
BLAKE3_API void blake3_hasher_init(blake3_hasher *self);
|
||||
BLAKE3_API void blake3_hasher_init_keyed(blake3_hasher *self,
|
||||
const uint8_t key[BLAKE3_KEY_LEN]);
|
||||
BLAKE3_API void blake3_hasher_init_derive_key(blake3_hasher *self, const char *context);
|
||||
BLAKE3_API void blake3_hasher_init_derive_key_raw(blake3_hasher *self, const void *context,
|
||||
size_t context_len);
|
||||
BLAKE3_API void blake3_hasher_update(blake3_hasher *self, const void *input,
|
||||
size_t input_len);
|
||||
BLAKE3_API void blake3_hasher_finalize(const blake3_hasher *self, uint8_t *out,
|
||||
size_t out_len);
|
||||
BLAKE3_API void blake3_hasher_finalize_seek(const blake3_hasher *self, uint64_t seek,
|
||||
uint8_t *out, size_t out_len);
|
||||
BLAKE3_API void blake3_hasher_reset(blake3_hasher *self);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* BLAKE3_H */
|
||||
@@ -0,0 +1,331 @@
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "blake3_impl.h"
|
||||
|
||||
#if defined(_MSC_VER)
|
||||
#include <Windows.h>
|
||||
#endif
|
||||
|
||||
#if defined(IS_X86)
|
||||
#if defined(_MSC_VER)
|
||||
#include <intrin.h>
|
||||
#elif defined(__GNUC__)
|
||||
#include <immintrin.h>
|
||||
#else
|
||||
#undef IS_X86 /* Unimplemented! */
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if !defined(BLAKE3_ATOMICS)
|
||||
#if defined(__has_include)
|
||||
#if __has_include(<stdatomic.h>) && !defined(_MSC_VER)
|
||||
#define BLAKE3_ATOMICS 1
|
||||
#else
|
||||
#define BLAKE3_ATOMICS 0
|
||||
#endif /* __has_include(<stdatomic.h>) && !defined(_MSC_VER) */
|
||||
#else
|
||||
#define BLAKE3_ATOMICS 0
|
||||
#endif /* defined(__has_include) */
|
||||
#endif /* BLAKE3_ATOMICS */
|
||||
|
||||
#if BLAKE3_ATOMICS
|
||||
#define ATOMIC_INT _Atomic int
|
||||
#define ATOMIC_LOAD(x) x
|
||||
#define ATOMIC_STORE(x, y) x = y
|
||||
#elif defined(_MSC_VER)
|
||||
#define ATOMIC_INT LONG
|
||||
#define ATOMIC_LOAD(x) InterlockedOr(&x, 0)
|
||||
#define ATOMIC_STORE(x, y) InterlockedExchange(&x, y)
|
||||
#else
|
||||
#define ATOMIC_INT int
|
||||
#define ATOMIC_LOAD(x) x
|
||||
#define ATOMIC_STORE(x, y) x = y
|
||||
#endif
|
||||
|
||||
#define MAYBE_UNUSED(x) (void)((x))
|
||||
|
||||
#if defined(IS_X86)
|
||||
static uint64_t xgetbv(void) {
|
||||
#if defined(_MSC_VER)
|
||||
return _xgetbv(0);
|
||||
#else
|
||||
uint32_t eax = 0, edx = 0;
|
||||
__asm__ __volatile__("xgetbv\n" : "=a"(eax), "=d"(edx) : "c"(0));
|
||||
return ((uint64_t)edx << 32) | eax;
|
||||
#endif
|
||||
}
|
||||
|
||||
static void cpuid(uint32_t out[4], uint32_t id) {
|
||||
#if defined(_MSC_VER)
|
||||
__cpuid((int *)out, id);
|
||||
#elif defined(__i386__) || defined(_M_IX86)
|
||||
__asm__ __volatile__("movl %%ebx, %1\n"
|
||||
"cpuid\n"
|
||||
"xchgl %1, %%ebx\n"
|
||||
: "=a"(out[0]), "=r"(out[1]), "=c"(out[2]), "=d"(out[3])
|
||||
: "a"(id));
|
||||
#else
|
||||
__asm__ __volatile__("cpuid\n"
|
||||
: "=a"(out[0]), "=b"(out[1]), "=c"(out[2]), "=d"(out[3])
|
||||
: "a"(id));
|
||||
#endif
|
||||
}
|
||||
|
||||
static void cpuidex(uint32_t out[4], uint32_t id, uint32_t sid) {
|
||||
#if defined(_MSC_VER)
|
||||
__cpuidex((int *)out, id, sid);
|
||||
#elif defined(__i386__) || defined(_M_IX86)
|
||||
__asm__ __volatile__("movl %%ebx, %1\n"
|
||||
"cpuid\n"
|
||||
"xchgl %1, %%ebx\n"
|
||||
: "=a"(out[0]), "=r"(out[1]), "=c"(out[2]), "=d"(out[3])
|
||||
: "a"(id), "c"(sid));
|
||||
#else
|
||||
__asm__ __volatile__("cpuid\n"
|
||||
: "=a"(out[0]), "=b"(out[1]), "=c"(out[2]), "=d"(out[3])
|
||||
: "a"(id), "c"(sid));
|
||||
#endif
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
enum cpu_feature {
|
||||
SSE2 = 1 << 0,
|
||||
SSSE3 = 1 << 1,
|
||||
SSE41 = 1 << 2,
|
||||
AVX = 1 << 3,
|
||||
AVX2 = 1 << 4,
|
||||
AVX512F = 1 << 5,
|
||||
AVX512VL = 1 << 6,
|
||||
/* ... */
|
||||
UNDEFINED = 1 << 30
|
||||
};
|
||||
|
||||
#if !defined(BLAKE3_TESTING)
|
||||
static /* Allow the variable to be controlled manually for testing */
|
||||
#endif
|
||||
ATOMIC_INT g_cpu_features = UNDEFINED;
|
||||
|
||||
#if !defined(BLAKE3_TESTING)
|
||||
static
|
||||
#endif
|
||||
enum cpu_feature
|
||||
get_cpu_features(void) {
|
||||
|
||||
/* If TSAN detects a data race here, try compiling with -DBLAKE3_ATOMICS=1 */
|
||||
enum cpu_feature features = ATOMIC_LOAD(g_cpu_features);
|
||||
if (features != UNDEFINED) {
|
||||
return features;
|
||||
} else {
|
||||
#if defined(IS_X86)
|
||||
uint32_t regs[4] = {0};
|
||||
uint32_t *eax = ®s[0], *ebx = ®s[1], *ecx = ®s[2], *edx = ®s[3];
|
||||
(void)edx;
|
||||
features = 0;
|
||||
cpuid(regs, 0);
|
||||
const int max_id = *eax;
|
||||
cpuid(regs, 1);
|
||||
#if defined(__amd64__) || defined(_M_X64)
|
||||
features |= SSE2;
|
||||
#else
|
||||
if (*edx & (1UL << 26))
|
||||
features |= SSE2;
|
||||
#endif
|
||||
if (*ecx & (1UL << 9))
|
||||
features |= SSSE3;
|
||||
if (*ecx & (1UL << 19))
|
||||
features |= SSE41;
|
||||
|
||||
if (*ecx & (1UL << 27)) { // OSXSAVE
|
||||
const uint64_t mask = xgetbv();
|
||||
if ((mask & 6) == 6) { // SSE and AVX states
|
||||
if (*ecx & (1UL << 28))
|
||||
features |= AVX;
|
||||
if (max_id >= 7) {
|
||||
cpuidex(regs, 7, 0);
|
||||
if (*ebx & (1UL << 5))
|
||||
features |= AVX2;
|
||||
if ((mask & 224) == 224) { // Opmask, ZMM_Hi256, Hi16_Zmm
|
||||
if (*ebx & (1UL << 31))
|
||||
features |= AVX512VL;
|
||||
if (*ebx & (1UL << 16))
|
||||
features |= AVX512F;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
ATOMIC_STORE(g_cpu_features, features);
|
||||
return features;
|
||||
#else
|
||||
/* How to detect NEON? */
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
void blake3_compress_in_place(uint32_t cv[8],
|
||||
const uint8_t block[BLAKE3_BLOCK_LEN],
|
||||
uint8_t block_len, uint64_t counter,
|
||||
uint8_t flags) {
|
||||
#if defined(IS_X86)
|
||||
const enum cpu_feature features = get_cpu_features();
|
||||
MAYBE_UNUSED(features);
|
||||
#if !defined(BLAKE3_NO_AVX512)
|
||||
if (features & AVX512VL) {
|
||||
blake3_compress_in_place_avx512(cv, block, block_len, counter, flags);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
#if !defined(BLAKE3_NO_SSE41)
|
||||
if (features & SSE41) {
|
||||
blake3_compress_in_place_sse41(cv, block, block_len, counter, flags);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
#if !defined(BLAKE3_NO_SSE2)
|
||||
if (features & SSE2) {
|
||||
blake3_compress_in_place_sse2(cv, block, block_len, counter, flags);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
blake3_compress_in_place_portable(cv, block, block_len, counter, flags);
|
||||
}
|
||||
|
||||
void blake3_compress_xof(const uint32_t cv[8],
|
||||
const uint8_t block[BLAKE3_BLOCK_LEN],
|
||||
uint8_t block_len, uint64_t counter, uint8_t flags,
|
||||
uint8_t out[64]) {
|
||||
#if defined(IS_X86)
|
||||
const enum cpu_feature features = get_cpu_features();
|
||||
MAYBE_UNUSED(features);
|
||||
#if !defined(BLAKE3_NO_AVX512)
|
||||
if (features & AVX512VL) {
|
||||
blake3_compress_xof_avx512(cv, block, block_len, counter, flags, out);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
#if !defined(BLAKE3_NO_SSE41)
|
||||
if (features & SSE41) {
|
||||
blake3_compress_xof_sse41(cv, block, block_len, counter, flags, out);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
#if !defined(BLAKE3_NO_SSE2)
|
||||
if (features & SSE2) {
|
||||
blake3_compress_xof_sse2(cv, block, block_len, counter, flags, out);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
blake3_compress_xof_portable(cv, block, block_len, counter, flags, out);
|
||||
}
|
||||
|
||||
|
||||
void blake3_xof_many(const uint32_t cv[8],
|
||||
const uint8_t block[BLAKE3_BLOCK_LEN],
|
||||
uint8_t block_len, uint64_t counter, uint8_t flags,
|
||||
uint8_t out[64], size_t outblocks) {
|
||||
if (outblocks == 0) {
|
||||
// The current assembly implementation always outputs at least 1 block.
|
||||
return;
|
||||
}
|
||||
#if defined(IS_X86)
|
||||
const enum cpu_feature features = get_cpu_features();
|
||||
#if !defined(_WIN32) && !defined(BLAKE3_NO_AVX512)
|
||||
if (features & AVX512VL) {
|
||||
blake3_xof_many_avx512(cv, block, block_len, counter, flags, out, outblocks);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
for(size_t i = 0; i < outblocks; ++i) {
|
||||
blake3_compress_xof(cv, block, block_len, counter + i, flags, out + 64*i);
|
||||
}
|
||||
}
|
||||
|
||||
void blake3_hash_many(const uint8_t *const *inputs, size_t num_inputs,
|
||||
size_t blocks, const uint32_t key[8], uint64_t counter,
|
||||
bool increment_counter, uint8_t flags,
|
||||
uint8_t flags_start, uint8_t flags_end, uint8_t *out) {
|
||||
#if defined(IS_X86)
|
||||
const enum cpu_feature features = get_cpu_features();
|
||||
MAYBE_UNUSED(features);
|
||||
#if !defined(BLAKE3_NO_AVX512)
|
||||
if ((features & (AVX512F|AVX512VL)) == (AVX512F|AVX512VL)) {
|
||||
blake3_hash_many_avx512(inputs, num_inputs, blocks, key, counter,
|
||||
increment_counter, flags, flags_start, flags_end,
|
||||
out);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
#if !defined(BLAKE3_NO_AVX2)
|
||||
if (features & AVX2) {
|
||||
blake3_hash_many_avx2(inputs, num_inputs, blocks, key, counter,
|
||||
increment_counter, flags, flags_start, flags_end,
|
||||
out);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
#if !defined(BLAKE3_NO_SSE41)
|
||||
if (features & SSE41) {
|
||||
blake3_hash_many_sse41(inputs, num_inputs, blocks, key, counter,
|
||||
increment_counter, flags, flags_start, flags_end,
|
||||
out);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
#if !defined(BLAKE3_NO_SSE2)
|
||||
if (features & SSE2) {
|
||||
blake3_hash_many_sse2(inputs, num_inputs, blocks, key, counter,
|
||||
increment_counter, flags, flags_start, flags_end,
|
||||
out);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if BLAKE3_USE_NEON == 1
|
||||
blake3_hash_many_neon(inputs, num_inputs, blocks, key, counter,
|
||||
increment_counter, flags, flags_start, flags_end, out);
|
||||
return;
|
||||
#endif
|
||||
|
||||
blake3_hash_many_portable(inputs, num_inputs, blocks, key, counter,
|
||||
increment_counter, flags, flags_start, flags_end,
|
||||
out);
|
||||
}
|
||||
|
||||
// The dynamically detected SIMD degree of the current platform.
|
||||
size_t blake3_simd_degree(void) {
|
||||
#if defined(IS_X86)
|
||||
const enum cpu_feature features = get_cpu_features();
|
||||
MAYBE_UNUSED(features);
|
||||
#if !defined(BLAKE3_NO_AVX512)
|
||||
if ((features & (AVX512F|AVX512VL)) == (AVX512F|AVX512VL)) {
|
||||
return 16;
|
||||
}
|
||||
#endif
|
||||
#if !defined(BLAKE3_NO_AVX2)
|
||||
if (features & AVX2) {
|
||||
return 8;
|
||||
}
|
||||
#endif
|
||||
#if !defined(BLAKE3_NO_SSE41)
|
||||
if (features & SSE41) {
|
||||
return 4;
|
||||
}
|
||||
#endif
|
||||
#if !defined(BLAKE3_NO_SSE2)
|
||||
if (features & SSE2) {
|
||||
return 4;
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
#if BLAKE3_USE_NEON == 1
|
||||
return 4;
|
||||
#endif
|
||||
return 1;
|
||||
}
|
||||
@@ -0,0 +1,304 @@
|
||||
#ifndef BLAKE3_IMPL_H
|
||||
#define BLAKE3_IMPL_H
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "blake3.h"
|
||||
|
||||
// internal flags
|
||||
enum blake3_flags {
|
||||
CHUNK_START = 1 << 0,
|
||||
CHUNK_END = 1 << 1,
|
||||
PARENT = 1 << 2,
|
||||
ROOT = 1 << 3,
|
||||
KEYED_HASH = 1 << 4,
|
||||
DERIVE_KEY_CONTEXT = 1 << 5,
|
||||
DERIVE_KEY_MATERIAL = 1 << 6,
|
||||
};
|
||||
|
||||
// This C implementation tries to support recent versions of GCC, Clang, and
|
||||
// MSVC.
|
||||
#if defined(_MSC_VER)
|
||||
#define INLINE static __forceinline
|
||||
#else
|
||||
#define INLINE static inline __attribute__((always_inline))
|
||||
#endif
|
||||
|
||||
#if (defined(__x86_64__) || defined(_M_X64)) && !defined(_M_ARM64EC)
|
||||
#define IS_X86
|
||||
#define IS_X86_64
|
||||
#endif
|
||||
|
||||
#if defined(__i386__) || defined(_M_IX86)
|
||||
#define IS_X86
|
||||
#define IS_X86_32
|
||||
#endif
|
||||
|
||||
#if defined(__aarch64__) || defined(_M_ARM64) || defined(_M_ARM64EC)
|
||||
#define IS_AARCH64
|
||||
#endif
|
||||
|
||||
#if defined(IS_X86)
|
||||
#if defined(_MSC_VER)
|
||||
#include <intrin.h>
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if !defined(BLAKE3_USE_NEON)
|
||||
// If BLAKE3_USE_NEON not manually set, autodetect based on AArch64ness
|
||||
#if defined(IS_AARCH64)
|
||||
#if defined(__ARM_BIG_ENDIAN)
|
||||
#define BLAKE3_USE_NEON 0
|
||||
#else
|
||||
#define BLAKE3_USE_NEON 1
|
||||
#endif
|
||||
#else
|
||||
#define BLAKE3_USE_NEON 0
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if defined(IS_X86)
|
||||
#define MAX_SIMD_DEGREE 16
|
||||
#elif BLAKE3_USE_NEON == 1
|
||||
#define MAX_SIMD_DEGREE 4
|
||||
#else
|
||||
#define MAX_SIMD_DEGREE 1
|
||||
#endif
|
||||
|
||||
// There are some places where we want a static size that's equal to the
|
||||
// MAX_SIMD_DEGREE, but also at least 2.
|
||||
#define MAX_SIMD_DEGREE_OR_2 (MAX_SIMD_DEGREE > 2 ? MAX_SIMD_DEGREE : 2)
|
||||
|
||||
static const uint32_t IV[8] = {0x6A09E667UL, 0xBB67AE85UL, 0x3C6EF372UL,
|
||||
0xA54FF53AUL, 0x510E527FUL, 0x9B05688CUL,
|
||||
0x1F83D9ABUL, 0x5BE0CD19UL};
|
||||
|
||||
static const uint8_t MSG_SCHEDULE[7][16] = {
|
||||
{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15},
|
||||
{2, 6, 3, 10, 7, 0, 4, 13, 1, 11, 12, 5, 9, 14, 15, 8},
|
||||
{3, 4, 10, 12, 13, 2, 7, 14, 6, 5, 9, 0, 11, 15, 8, 1},
|
||||
{10, 7, 12, 9, 14, 3, 13, 15, 4, 0, 11, 2, 5, 8, 1, 6},
|
||||
{12, 13, 9, 11, 15, 10, 14, 8, 7, 2, 5, 3, 0, 1, 6, 4},
|
||||
{9, 14, 11, 5, 8, 12, 15, 1, 13, 3, 0, 10, 2, 6, 4, 7},
|
||||
{11, 15, 5, 0, 1, 9, 8, 6, 14, 10, 2, 12, 3, 4, 7, 13},
|
||||
};
|
||||
|
||||
/* Find index of the highest set bit */
|
||||
/* x is assumed to be nonzero. */
|
||||
static unsigned int highest_one(uint64_t x) {
|
||||
#if defined(__GNUC__) || defined(__clang__)
|
||||
return 63 ^ (unsigned int)__builtin_clzll(x);
|
||||
#elif defined(_MSC_VER) && defined(IS_X86_64)
|
||||
unsigned long index;
|
||||
_BitScanReverse64(&index, x);
|
||||
return index;
|
||||
#elif defined(_MSC_VER) && defined(IS_X86_32)
|
||||
if(x >> 32) {
|
||||
unsigned long index;
|
||||
_BitScanReverse(&index, (unsigned long)(x >> 32));
|
||||
return 32 + index;
|
||||
} else {
|
||||
unsigned long index;
|
||||
_BitScanReverse(&index, (unsigned long)x);
|
||||
return index;
|
||||
}
|
||||
#else
|
||||
unsigned int c = 0;
|
||||
if(x & 0xffffffff00000000ULL) { x >>= 32; c += 32; }
|
||||
if(x & 0x00000000ffff0000ULL) { x >>= 16; c += 16; }
|
||||
if(x & 0x000000000000ff00ULL) { x >>= 8; c += 8; }
|
||||
if(x & 0x00000000000000f0ULL) { x >>= 4; c += 4; }
|
||||
if(x & 0x000000000000000cULL) { x >>= 2; c += 2; }
|
||||
if(x & 0x0000000000000002ULL) { c += 1; }
|
||||
return c;
|
||||
#endif
|
||||
}
|
||||
|
||||
// Count the number of 1 bits.
|
||||
INLINE unsigned int popcnt(uint64_t x) {
|
||||
#if defined(__GNUC__) || defined(__clang__)
|
||||
return (unsigned int)__builtin_popcountll(x);
|
||||
#else
|
||||
unsigned int count = 0;
|
||||
while (x != 0) {
|
||||
count += 1;
|
||||
x &= x - 1;
|
||||
}
|
||||
return count;
|
||||
#endif
|
||||
}
|
||||
|
||||
// Largest power of two less than or equal to x. As a special case, returns 1
|
||||
// when x is 0.
|
||||
INLINE uint64_t round_down_to_power_of_2(uint64_t x) {
|
||||
return 1ULL << highest_one(x | 1);
|
||||
}
|
||||
|
||||
INLINE uint32_t counter_low(uint64_t counter) { return (uint32_t)counter; }
|
||||
|
||||
INLINE uint32_t counter_high(uint64_t counter) {
|
||||
return (uint32_t)(counter >> 32);
|
||||
}
|
||||
|
||||
INLINE uint32_t load32(const void *src) {
|
||||
const uint8_t *p = (const uint8_t *)src;
|
||||
return ((uint32_t)(p[0]) << 0) | ((uint32_t)(p[1]) << 8) |
|
||||
((uint32_t)(p[2]) << 16) | ((uint32_t)(p[3]) << 24);
|
||||
}
|
||||
|
||||
INLINE void load_key_words(const uint8_t key[BLAKE3_KEY_LEN],
|
||||
uint32_t key_words[8]) {
|
||||
key_words[0] = load32(&key[0 * 4]);
|
||||
key_words[1] = load32(&key[1 * 4]);
|
||||
key_words[2] = load32(&key[2 * 4]);
|
||||
key_words[3] = load32(&key[3 * 4]);
|
||||
key_words[4] = load32(&key[4 * 4]);
|
||||
key_words[5] = load32(&key[5 * 4]);
|
||||
key_words[6] = load32(&key[6 * 4]);
|
||||
key_words[7] = load32(&key[7 * 4]);
|
||||
}
|
||||
|
||||
INLINE void load_block_words(const uint8_t block[BLAKE3_BLOCK_LEN],
|
||||
uint32_t block_words[16]) {
|
||||
for (size_t i = 0; i < 16; i++) {
|
||||
block_words[i] = load32(&block[i * 4]);
|
||||
}
|
||||
}
|
||||
|
||||
INLINE void store32(void *dst, uint32_t w) {
|
||||
uint8_t *p = (uint8_t *)dst;
|
||||
p[0] = (uint8_t)(w >> 0);
|
||||
p[1] = (uint8_t)(w >> 8);
|
||||
p[2] = (uint8_t)(w >> 16);
|
||||
p[3] = (uint8_t)(w >> 24);
|
||||
}
|
||||
|
||||
INLINE void store_cv_words(uint8_t bytes_out[32], uint32_t cv_words[8]) {
|
||||
store32(&bytes_out[0 * 4], cv_words[0]);
|
||||
store32(&bytes_out[1 * 4], cv_words[1]);
|
||||
store32(&bytes_out[2 * 4], cv_words[2]);
|
||||
store32(&bytes_out[3 * 4], cv_words[3]);
|
||||
store32(&bytes_out[4 * 4], cv_words[4]);
|
||||
store32(&bytes_out[5 * 4], cv_words[5]);
|
||||
store32(&bytes_out[6 * 4], cv_words[6]);
|
||||
store32(&bytes_out[7 * 4], cv_words[7]);
|
||||
}
|
||||
|
||||
void blake3_compress_in_place(uint32_t cv[8],
|
||||
const uint8_t block[BLAKE3_BLOCK_LEN],
|
||||
uint8_t block_len, uint64_t counter,
|
||||
uint8_t flags);
|
||||
|
||||
void blake3_compress_xof(const uint32_t cv[8],
|
||||
const uint8_t block[BLAKE3_BLOCK_LEN],
|
||||
uint8_t block_len, uint64_t counter, uint8_t flags,
|
||||
uint8_t out[64]);
|
||||
|
||||
void blake3_xof_many(const uint32_t cv[8],
|
||||
const uint8_t block[BLAKE3_BLOCK_LEN],
|
||||
uint8_t block_len, uint64_t counter, uint8_t flags,
|
||||
uint8_t out[64], size_t outblocks);
|
||||
|
||||
void blake3_hash_many(const uint8_t *const *inputs, size_t num_inputs,
|
||||
size_t blocks, const uint32_t key[8], uint64_t counter,
|
||||
bool increment_counter, uint8_t flags,
|
||||
uint8_t flags_start, uint8_t flags_end, uint8_t *out);
|
||||
|
||||
size_t blake3_simd_degree(void);
|
||||
|
||||
|
||||
// Declarations for implementation-specific functions.
|
||||
void blake3_compress_in_place_portable(uint32_t cv[8],
|
||||
const uint8_t block[BLAKE3_BLOCK_LEN],
|
||||
uint8_t block_len, uint64_t counter,
|
||||
uint8_t flags);
|
||||
|
||||
void blake3_compress_xof_portable(const uint32_t cv[8],
|
||||
const uint8_t block[BLAKE3_BLOCK_LEN],
|
||||
uint8_t block_len, uint64_t counter,
|
||||
uint8_t flags, uint8_t out[64]);
|
||||
|
||||
void blake3_hash_many_portable(const uint8_t *const *inputs, size_t num_inputs,
|
||||
size_t blocks, const uint32_t key[8],
|
||||
uint64_t counter, bool increment_counter,
|
||||
uint8_t flags, uint8_t flags_start,
|
||||
uint8_t flags_end, uint8_t *out);
|
||||
|
||||
#if defined(IS_X86)
|
||||
#if !defined(BLAKE3_NO_SSE2)
|
||||
void blake3_compress_in_place_sse2(uint32_t cv[8],
|
||||
const uint8_t block[BLAKE3_BLOCK_LEN],
|
||||
uint8_t block_len, uint64_t counter,
|
||||
uint8_t flags);
|
||||
void blake3_compress_xof_sse2(const uint32_t cv[8],
|
||||
const uint8_t block[BLAKE3_BLOCK_LEN],
|
||||
uint8_t block_len, uint64_t counter,
|
||||
uint8_t flags, uint8_t out[64]);
|
||||
void blake3_hash_many_sse2(const uint8_t *const *inputs, size_t num_inputs,
|
||||
size_t blocks, const uint32_t key[8],
|
||||
uint64_t counter, bool increment_counter,
|
||||
uint8_t flags, uint8_t flags_start,
|
||||
uint8_t flags_end, uint8_t *out);
|
||||
#endif
|
||||
#if !defined(BLAKE3_NO_SSE41)
|
||||
void blake3_compress_in_place_sse41(uint32_t cv[8],
|
||||
const uint8_t block[BLAKE3_BLOCK_LEN],
|
||||
uint8_t block_len, uint64_t counter,
|
||||
uint8_t flags);
|
||||
void blake3_compress_xof_sse41(const uint32_t cv[8],
|
||||
const uint8_t block[BLAKE3_BLOCK_LEN],
|
||||
uint8_t block_len, uint64_t counter,
|
||||
uint8_t flags, uint8_t out[64]);
|
||||
void blake3_hash_many_sse41(const uint8_t *const *inputs, size_t num_inputs,
|
||||
size_t blocks, const uint32_t key[8],
|
||||
uint64_t counter, bool increment_counter,
|
||||
uint8_t flags, uint8_t flags_start,
|
||||
uint8_t flags_end, uint8_t *out);
|
||||
#endif
|
||||
#if !defined(BLAKE3_NO_AVX2)
|
||||
void blake3_hash_many_avx2(const uint8_t *const *inputs, size_t num_inputs,
|
||||
size_t blocks, const uint32_t key[8],
|
||||
uint64_t counter, bool increment_counter,
|
||||
uint8_t flags, uint8_t flags_start,
|
||||
uint8_t flags_end, uint8_t *out);
|
||||
#endif
|
||||
#if !defined(BLAKE3_NO_AVX512)
|
||||
void blake3_compress_in_place_avx512(uint32_t cv[8],
|
||||
const uint8_t block[BLAKE3_BLOCK_LEN],
|
||||
uint8_t block_len, uint64_t counter,
|
||||
uint8_t flags);
|
||||
|
||||
void blake3_compress_xof_avx512(const uint32_t cv[8],
|
||||
const uint8_t block[BLAKE3_BLOCK_LEN],
|
||||
uint8_t block_len, uint64_t counter,
|
||||
uint8_t flags, uint8_t out[64]);
|
||||
|
||||
void blake3_hash_many_avx512(const uint8_t *const *inputs, size_t num_inputs,
|
||||
size_t blocks, const uint32_t key[8],
|
||||
uint64_t counter, bool increment_counter,
|
||||
uint8_t flags, uint8_t flags_start,
|
||||
uint8_t flags_end, uint8_t *out);
|
||||
|
||||
#if !defined(_WIN32)
|
||||
void blake3_xof_many_avx512(const uint32_t cv[8],
|
||||
const uint8_t block[BLAKE3_BLOCK_LEN],
|
||||
uint8_t block_len, uint64_t counter, uint8_t flags,
|
||||
uint8_t* out, size_t outblocks);
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if BLAKE3_USE_NEON == 1
|
||||
void blake3_hash_many_neon(const uint8_t *const *inputs, size_t num_inputs,
|
||||
size_t blocks, const uint32_t key[8],
|
||||
uint64_t counter, bool increment_counter,
|
||||
uint8_t flags, uint8_t flags_start,
|
||||
uint8_t flags_end, uint8_t *out);
|
||||
#endif
|
||||
|
||||
|
||||
#endif /* BLAKE3_IMPL_H */
|
||||
@@ -0,0 +1,160 @@
|
||||
#include "blake3_impl.h"
|
||||
#include <string.h>
|
||||
|
||||
INLINE uint32_t rotr32(uint32_t w, uint32_t c) {
|
||||
return (w >> c) | (w << (32 - c));
|
||||
}
|
||||
|
||||
INLINE void g(uint32_t *state, size_t a, size_t b, size_t c, size_t d,
|
||||
uint32_t x, uint32_t y) {
|
||||
state[a] = state[a] + state[b] + x;
|
||||
state[d] = rotr32(state[d] ^ state[a], 16);
|
||||
state[c] = state[c] + state[d];
|
||||
state[b] = rotr32(state[b] ^ state[c], 12);
|
||||
state[a] = state[a] + state[b] + y;
|
||||
state[d] = rotr32(state[d] ^ state[a], 8);
|
||||
state[c] = state[c] + state[d];
|
||||
state[b] = rotr32(state[b] ^ state[c], 7);
|
||||
}
|
||||
|
||||
INLINE void round_fn(uint32_t state[16], const uint32_t *msg, size_t round) {
|
||||
// Select the message schedule based on the round.
|
||||
const uint8_t *schedule = MSG_SCHEDULE[round];
|
||||
|
||||
// Mix the columns.
|
||||
g(state, 0, 4, 8, 12, msg[schedule[0]], msg[schedule[1]]);
|
||||
g(state, 1, 5, 9, 13, msg[schedule[2]], msg[schedule[3]]);
|
||||
g(state, 2, 6, 10, 14, msg[schedule[4]], msg[schedule[5]]);
|
||||
g(state, 3, 7, 11, 15, msg[schedule[6]], msg[schedule[7]]);
|
||||
|
||||
// Mix the rows.
|
||||
g(state, 0, 5, 10, 15, msg[schedule[8]], msg[schedule[9]]);
|
||||
g(state, 1, 6, 11, 12, msg[schedule[10]], msg[schedule[11]]);
|
||||
g(state, 2, 7, 8, 13, msg[schedule[12]], msg[schedule[13]]);
|
||||
g(state, 3, 4, 9, 14, msg[schedule[14]], msg[schedule[15]]);
|
||||
}
|
||||
|
||||
INLINE void compress_pre(uint32_t state[16], const uint32_t cv[8],
|
||||
const uint8_t block[BLAKE3_BLOCK_LEN],
|
||||
uint8_t block_len, uint64_t counter, uint8_t flags) {
|
||||
uint32_t block_words[16];
|
||||
block_words[0] = load32(block + 4 * 0);
|
||||
block_words[1] = load32(block + 4 * 1);
|
||||
block_words[2] = load32(block + 4 * 2);
|
||||
block_words[3] = load32(block + 4 * 3);
|
||||
block_words[4] = load32(block + 4 * 4);
|
||||
block_words[5] = load32(block + 4 * 5);
|
||||
block_words[6] = load32(block + 4 * 6);
|
||||
block_words[7] = load32(block + 4 * 7);
|
||||
block_words[8] = load32(block + 4 * 8);
|
||||
block_words[9] = load32(block + 4 * 9);
|
||||
block_words[10] = load32(block + 4 * 10);
|
||||
block_words[11] = load32(block + 4 * 11);
|
||||
block_words[12] = load32(block + 4 * 12);
|
||||
block_words[13] = load32(block + 4 * 13);
|
||||
block_words[14] = load32(block + 4 * 14);
|
||||
block_words[15] = load32(block + 4 * 15);
|
||||
|
||||
state[0] = cv[0];
|
||||
state[1] = cv[1];
|
||||
state[2] = cv[2];
|
||||
state[3] = cv[3];
|
||||
state[4] = cv[4];
|
||||
state[5] = cv[5];
|
||||
state[6] = cv[6];
|
||||
state[7] = cv[7];
|
||||
state[8] = IV[0];
|
||||
state[9] = IV[1];
|
||||
state[10] = IV[2];
|
||||
state[11] = IV[3];
|
||||
state[12] = counter_low(counter);
|
||||
state[13] = counter_high(counter);
|
||||
state[14] = (uint32_t)block_len;
|
||||
state[15] = (uint32_t)flags;
|
||||
|
||||
round_fn(state, &block_words[0], 0);
|
||||
round_fn(state, &block_words[0], 1);
|
||||
round_fn(state, &block_words[0], 2);
|
||||
round_fn(state, &block_words[0], 3);
|
||||
round_fn(state, &block_words[0], 4);
|
||||
round_fn(state, &block_words[0], 5);
|
||||
round_fn(state, &block_words[0], 6);
|
||||
}
|
||||
|
||||
void blake3_compress_in_place_portable(uint32_t cv[8],
|
||||
const uint8_t block[BLAKE3_BLOCK_LEN],
|
||||
uint8_t block_len, uint64_t counter,
|
||||
uint8_t flags) {
|
||||
uint32_t state[16];
|
||||
compress_pre(state, cv, block, block_len, counter, flags);
|
||||
cv[0] = state[0] ^ state[8];
|
||||
cv[1] = state[1] ^ state[9];
|
||||
cv[2] = state[2] ^ state[10];
|
||||
cv[3] = state[3] ^ state[11];
|
||||
cv[4] = state[4] ^ state[12];
|
||||
cv[5] = state[5] ^ state[13];
|
||||
cv[6] = state[6] ^ state[14];
|
||||
cv[7] = state[7] ^ state[15];
|
||||
}
|
||||
|
||||
void blake3_compress_xof_portable(const uint32_t cv[8],
|
||||
const uint8_t block[BLAKE3_BLOCK_LEN],
|
||||
uint8_t block_len, uint64_t counter,
|
||||
uint8_t flags, uint8_t out[64]) {
|
||||
uint32_t state[16];
|
||||
compress_pre(state, cv, block, block_len, counter, flags);
|
||||
|
||||
store32(&out[0 * 4], state[0] ^ state[8]);
|
||||
store32(&out[1 * 4], state[1] ^ state[9]);
|
||||
store32(&out[2 * 4], state[2] ^ state[10]);
|
||||
store32(&out[3 * 4], state[3] ^ state[11]);
|
||||
store32(&out[4 * 4], state[4] ^ state[12]);
|
||||
store32(&out[5 * 4], state[5] ^ state[13]);
|
||||
store32(&out[6 * 4], state[6] ^ state[14]);
|
||||
store32(&out[7 * 4], state[7] ^ state[15]);
|
||||
store32(&out[8 * 4], state[8] ^ cv[0]);
|
||||
store32(&out[9 * 4], state[9] ^ cv[1]);
|
||||
store32(&out[10 * 4], state[10] ^ cv[2]);
|
||||
store32(&out[11 * 4], state[11] ^ cv[3]);
|
||||
store32(&out[12 * 4], state[12] ^ cv[4]);
|
||||
store32(&out[13 * 4], state[13] ^ cv[5]);
|
||||
store32(&out[14 * 4], state[14] ^ cv[6]);
|
||||
store32(&out[15 * 4], state[15] ^ cv[7]);
|
||||
}
|
||||
|
||||
INLINE void hash_one_portable(const uint8_t *input, size_t blocks,
|
||||
const uint32_t key[8], uint64_t counter,
|
||||
uint8_t flags, uint8_t flags_start,
|
||||
uint8_t flags_end, uint8_t out[BLAKE3_OUT_LEN]) {
|
||||
uint32_t cv[8];
|
||||
memcpy(cv, key, BLAKE3_KEY_LEN);
|
||||
uint8_t block_flags = flags | flags_start;
|
||||
while (blocks > 0) {
|
||||
if (blocks == 1) {
|
||||
block_flags |= flags_end;
|
||||
}
|
||||
blake3_compress_in_place_portable(cv, input, BLAKE3_BLOCK_LEN, counter,
|
||||
block_flags);
|
||||
input = &input[BLAKE3_BLOCK_LEN];
|
||||
blocks -= 1;
|
||||
block_flags = flags;
|
||||
}
|
||||
store_cv_words(out, cv);
|
||||
}
|
||||
|
||||
void blake3_hash_many_portable(const uint8_t *const *inputs, size_t num_inputs,
|
||||
size_t blocks, const uint32_t key[8],
|
||||
uint64_t counter, bool increment_counter,
|
||||
uint8_t flags, uint8_t flags_start,
|
||||
uint8_t flags_end, uint8_t *out) {
|
||||
while (num_inputs > 0) {
|
||||
hash_one_portable(inputs[0], blocks, key, counter, flags, flags_start,
|
||||
flags_end, out);
|
||||
if (increment_counter) {
|
||||
counter += 1;
|
||||
}
|
||||
inputs += 1;
|
||||
num_inputs -= 1;
|
||||
out = &out[BLAKE3_OUT_LEN];
|
||||
}
|
||||
}
|
||||
+2
-1
@@ -75,7 +75,8 @@ enum {
|
||||
GETOPT_VAL_TCP_INCOMING_RCVBUF,
|
||||
GETOPT_VAL_TCP_OUTGOING_SNDBUF,
|
||||
GETOPT_VAL_TCP_OUTGOING_RCVBUF,
|
||||
GETOPT_VAL_NFTABLES_SETS
|
||||
GETOPT_VAL_NFTABLES_SETS,
|
||||
GETOPT_VAL_SERVER_URL
|
||||
};
|
||||
|
||||
#endif // _COMMON_H
|
||||
+68
-13
@@ -192,6 +192,11 @@ crypto_init(const char *password, const char *key, const char *method)
|
||||
.decrypt = &aead_decrypt,
|
||||
.ctx_init = &aead_ctx_init,
|
||||
.ctx_release = &aead_ctx_release,
|
||||
/* AEAD-2022 replaces the stateless UDP path with sessions */
|
||||
.encrypt_udp = aead_is_2022(cipher) ? &aead_2022_encrypt_udp : NULL,
|
||||
.decrypt_udp = aead_is_2022(cipher) ? &aead_2022_decrypt_udp : NULL,
|
||||
.udp_session_release = aead_is_2022(cipher)
|
||||
? &aead_2022_udp_session_release : NULL,
|
||||
};
|
||||
memcpy(crypto, &tmp, sizeof(crypto_t));
|
||||
return crypto;
|
||||
@@ -354,15 +359,40 @@ crypto_hkdf_expand(const mbedtls_md_info_t *md, const unsigned char *prk,
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Report an unusable key and exit, after showing a freshly generated one of
|
||||
* the right size so the user has something to paste back.
|
||||
*/
|
||||
static void
|
||||
crypto_key_fatal(const char *what, uint8_t *key, size_t key_len)
|
||||
{
|
||||
char encoded[BASE64_SIZE(MAX_KEY_LENGTH)];
|
||||
|
||||
rand_bytes(key, key_len);
|
||||
base64_encode(encoded, sizeof(encoded), key, key_len);
|
||||
LOGE("Invalid %s for your chosen cipher!", what);
|
||||
LOGE("It requires a " SIZE_FMT "-byte key encoded with Base64", key_len);
|
||||
LOGE("Generating a new random key: %s", encoded);
|
||||
FATAL("Please use the key above or input a valid key");
|
||||
}
|
||||
|
||||
int
|
||||
crypto_parse_key(const char *base64, uint8_t *key, size_t key_len)
|
||||
{
|
||||
size_t base64_len = strlen(base64);
|
||||
int out_len = BASE64_SIZE(base64_len);
|
||||
uint8_t out[out_len];
|
||||
/*
|
||||
* Decode into a fixed buffer rather than one sized from the input: the
|
||||
* key comes from the command line or config file, and a VLA sized by
|
||||
* its length lets a long string overflow the stack. Only the first
|
||||
* key_len bytes are ever used, and base64_decode never writes past the
|
||||
* size it is given.
|
||||
*/
|
||||
uint8_t out[MAX_KEY_LENGTH];
|
||||
|
||||
out_len = base64_decode(out, base64, out_len);
|
||||
if (out_len > 0 && out_len >= key_len) {
|
||||
if (key_len > sizeof(out))
|
||||
FATAL("Key length exceeds the maximum supported size");
|
||||
|
||||
int out_len = base64_decode(out, base64, (int)key_len);
|
||||
if (out_len > 0 && (size_t)out_len >= key_len) {
|
||||
memcpy(key, out, key_len);
|
||||
#ifdef SS_DEBUG
|
||||
dump("KEY", (char *)key, key_len);
|
||||
@@ -370,14 +400,39 @@ crypto_parse_key(const char *base64, uint8_t *key, size_t key_len)
|
||||
return key_len;
|
||||
}
|
||||
|
||||
out_len = BASE64_SIZE(key_len);
|
||||
char out_key[out_len];
|
||||
rand_bytes(key, key_len);
|
||||
base64_encode(out_key, out_len, key, key_len);
|
||||
LOGE("Invalid key for your chosen cipher!");
|
||||
LOGE("It requires a " SIZE_FMT "-byte key encoded with URL-safe Base64", key_len);
|
||||
LOGE("Generating a new random key: %s", out_key);
|
||||
FATAL("Please use the key above or input a valid key");
|
||||
crypto_key_fatal("key", key, key_len);
|
||||
return key_len;
|
||||
}
|
||||
|
||||
/*
|
||||
* SIP022 requires a base64-encoded pre-shared key of exactly the cipher's
|
||||
* key size. Unlike crypto_parse_key there is no password fallback: a wrong
|
||||
* size is a hard error, since deriving a key from a password is forbidden.
|
||||
*/
|
||||
int
|
||||
crypto_parse_psk(const char *base64, uint8_t *key, size_t key_len)
|
||||
{
|
||||
/*
|
||||
* One byte of slack lets an over-long key be told apart from an exact
|
||||
* one: base64_decode stops at the size it is given, so decoding into
|
||||
* key_len + 1 bytes yields key_len only for a key of exactly that size.
|
||||
*/
|
||||
uint8_t out[MAX_KEY_LENGTH + 1];
|
||||
|
||||
if (key_len >= sizeof(out))
|
||||
FATAL("Key length exceeds the maximum supported size");
|
||||
|
||||
int out_len = base64_decode(out, base64, (int)key_len + 1);
|
||||
|
||||
if (out_len != (int)key_len) {
|
||||
LOGE("AEAD-2022 requires exactly a " SIZE_FMT "-byte key in base64", key_len);
|
||||
crypto_key_fatal("pre-shared key", key, key_len);
|
||||
}
|
||||
|
||||
memcpy(key, out, key_len);
|
||||
#ifdef SS_DEBUG
|
||||
dump("PSK", (char *)key, key_len);
|
||||
#endif
|
||||
return key_len;
|
||||
}
|
||||
|
||||
|
||||
+38
-1
@@ -71,6 +71,16 @@ typedef mbedtls_md_info_t digest_type_t;
|
||||
#define SUBKEY_INFO "ss-subkey"
|
||||
#define IV_INFO "ss-iv"
|
||||
|
||||
/* SIP022 AEAD-2022 (Shadowsocks 2022 Edition) */
|
||||
#define SS2022_SUBKEY_CTX "shadowsocks 2022 session subkey"
|
||||
#define SS2022_TIME_WINDOW 30
|
||||
#define SS2022_SALT_RETENTION 60
|
||||
#define SS2022_MAX_PADDING 900
|
||||
#define SS2022_SESSION_ID_LEN 8
|
||||
|
||||
#define CRYPTO_ROLE_CLIENT 0
|
||||
#define CRYPTO_ROLE_SERVER 1
|
||||
|
||||
#ifndef BF_NUM_ENTRIES_FOR_SERVER
|
||||
#define BF_NUM_ENTRIES_FOR_SERVER 1e6
|
||||
#endif
|
||||
@@ -103,7 +113,7 @@ typedef struct {
|
||||
uint8_t key[MAX_KEY_LENGTH];
|
||||
} cipher_t;
|
||||
|
||||
typedef struct {
|
||||
typedef struct cipher_ctx {
|
||||
uint32_t init;
|
||||
uint64_t counter;
|
||||
cipher_evp_t *evp;
|
||||
@@ -113,6 +123,13 @@ typedef struct {
|
||||
uint8_t salt[MAX_KEY_LENGTH];
|
||||
uint8_t skey[MAX_KEY_LENGTH];
|
||||
uint8_t nonce[MAX_NONCE_LENGTH];
|
||||
|
||||
/* AEAD-2022 state */
|
||||
struct cipher_ctx *peer; /* paired enc/dec ctx of the same connection */
|
||||
uint8_t role; /* CRYPTO_ROLE_CLIENT or CRYPTO_ROLE_SERVER */
|
||||
uint8_t hdr_stage; /* TCP decrypt header state machine stage */
|
||||
uint16_t hdr_varlen; /* pending request variable-length header size */
|
||||
uint16_t next_plen; /* pending response first payload chunk size */
|
||||
} cipher_ctx_t;
|
||||
|
||||
typedef struct crypto {
|
||||
@@ -125,8 +142,27 @@ typedef struct crypto {
|
||||
|
||||
void(*const ctx_init) (cipher_t *, cipher_ctx_t *, int);
|
||||
void(*const ctx_release) (cipher_ctx_t *);
|
||||
|
||||
/*
|
||||
* AEAD-2022 session-based UDP. NULL for legacy ciphers; when set,
|
||||
* udprelay must use these instead of encrypt_all/decrypt_all, passing
|
||||
* a per-conversation session slot. The slot is released with
|
||||
* udp_session_release.
|
||||
*/
|
||||
int(*const encrypt_udp) (buffer_t *, cipher_t *, size_t, void **);
|
||||
int(*const decrypt_udp) (buffer_t *, cipher_t *, size_t, void **);
|
||||
void(*const udp_session_release) (void *);
|
||||
} crypto_t;
|
||||
|
||||
/* Pair the encrypt/decrypt contexts of one connection (needed by AEAD-2022
|
||||
* to echo and verify the request salt in the response header). */
|
||||
static inline void
|
||||
cipher_ctx_pair(cipher_ctx_t *a, cipher_ctx_t *b)
|
||||
{
|
||||
a->peer = b;
|
||||
b->peer = a;
|
||||
}
|
||||
|
||||
int balloc(buffer_t *, size_t);
|
||||
int brealloc(buffer_t *, size_t, size_t);
|
||||
int bprepend(buffer_t *, buffer_t *, size_t);
|
||||
@@ -150,6 +186,7 @@ unsigned char *crypto_md5(const unsigned char *, size_t, unsigned char *);
|
||||
|
||||
int crypto_derive_key(const char *, uint8_t *, size_t);
|
||||
int crypto_parse_key(const char *, uint8_t *, size_t);
|
||||
int crypto_parse_psk(const char *, uint8_t *, size_t);
|
||||
int crypto_hkdf(const mbedtls_md_info_t *md, const unsigned char *salt,
|
||||
int salt_len, const unsigned char *ikm, int ikm_len,
|
||||
const unsigned char *info, int info_len, unsigned char *okm,
|
||||
|
||||
+35
-5
@@ -52,6 +52,7 @@
|
||||
#include <libcork/core.h>
|
||||
|
||||
#include "netutils.h"
|
||||
#include "ssurl.h"
|
||||
#include "utils.h"
|
||||
#include "socks5.h"
|
||||
#include "acl.h"
|
||||
@@ -396,7 +397,7 @@ server_handshake(EV_P_ ev_io *w, buffer_t *buf)
|
||||
LOGI("inet_ntop(AF_INET): %s", strerror(errno));
|
||||
ip[0] = '\0';
|
||||
}
|
||||
sprintf(port, "%d", p);
|
||||
snprintf(port, sizeof(port), "%d", p);
|
||||
}
|
||||
} else if (atyp == SOCKS5_ATYP_DOMAIN) {
|
||||
if (buf->len < request_len + 1) {
|
||||
@@ -414,7 +415,7 @@ server_handshake(EV_P_ ev_io *w, buffer_t *buf)
|
||||
uint16_t p = load16_be(buf->data + request_len + 1 + name_len);
|
||||
memcpy(host, buf->data + request_len + 1, name_len);
|
||||
host[name_len] = '\0';
|
||||
sprintf(port, "%d", p);
|
||||
snprintf(port, sizeof(port), "%d", p);
|
||||
}
|
||||
} else if (atyp == SOCKS5_ATYP_IPV6) {
|
||||
size_t in6_addr_len = sizeof(struct in6_addr);
|
||||
@@ -431,7 +432,7 @@ server_handshake(EV_P_ ev_io *w, buffer_t *buf)
|
||||
LOGI("inet_ntop(AF_INET6): %s", strerror(errno));
|
||||
ip[0] = '\0';
|
||||
}
|
||||
sprintf(port, "%d", p);
|
||||
snprintf(port, sizeof(port), "%d", p);
|
||||
}
|
||||
} else {
|
||||
LOGE("unsupported addrtype: %d", request->atyp);
|
||||
@@ -1217,6 +1218,7 @@ new_server(int fd)
|
||||
server->d_ctx = ss_malloc(sizeof(cipher_ctx_t));
|
||||
crypto->ctx_init(crypto->cipher, server->e_ctx, 1);
|
||||
crypto->ctx_init(crypto->cipher, server->d_ctx, 0);
|
||||
cipher_ctx_pair(server->e_ctx, server->d_ctx);
|
||||
|
||||
ev_io_init(&server->recv_ctx->io, server_recv_cb, fd, EV_READ);
|
||||
ev_io_init(&server->send_ctx->io, server_send_cb, fd, EV_WRITE);
|
||||
@@ -1454,6 +1456,9 @@ main(int argc, char **argv)
|
||||
ss_addr_t remote_addr[MAX_REMOTE_NUM];
|
||||
char *remote_port = NULL;
|
||||
|
||||
/* Lives until exit; its strings are handed to the config above. */
|
||||
ss_url_t server_url = { 0 };
|
||||
|
||||
memset(remote_addr, 0, sizeof(ss_addr_t) * MAX_REMOTE_NUM);
|
||||
|
||||
static struct option long_options[] = {
|
||||
@@ -1471,6 +1476,7 @@ main(int argc, char **argv)
|
||||
{ "plugin-opts", required_argument, NULL, GETOPT_VAL_PLUGIN_OPTS },
|
||||
{ "password", required_argument, NULL, GETOPT_VAL_PASSWORD },
|
||||
{ "key", required_argument, NULL, GETOPT_VAL_KEY },
|
||||
{ "server-url", required_argument, NULL, GETOPT_VAL_SERVER_URL },
|
||||
{ "help", no_argument, NULL, GETOPT_VAL_HELP },
|
||||
{ NULL, 0, NULL, 0 }
|
||||
};
|
||||
@@ -1518,6 +1524,30 @@ main(int argc, char **argv)
|
||||
case GETOPT_VAL_KEY:
|
||||
key = optarg;
|
||||
break;
|
||||
case GETOPT_VAL_SERVER_URL:
|
||||
/*
|
||||
* An ss:// URI carries the server, credentials and plugin in one
|
||||
* argument. Options given later on the command line still win,
|
||||
* since they are applied as getopt reaches them.
|
||||
*/
|
||||
if (ss_url_parse(optarg, &server_url) != 0) {
|
||||
FATAL("invalid ss:// server URL");
|
||||
}
|
||||
if (remote_num < MAX_REMOTE_NUM) {
|
||||
remote_addr[remote_num].host = server_url.host;
|
||||
remote_addr[remote_num].port = NULL;
|
||||
remote_num++;
|
||||
}
|
||||
remote_port = server_url.port;
|
||||
method = server_url.method;
|
||||
password = server_url.password;
|
||||
if (server_url.plugin != NULL)
|
||||
plugin = server_url.plugin;
|
||||
if (server_url.plugin_opts != NULL)
|
||||
plugin_opts = server_url.plugin_opts;
|
||||
if (server_url.tag != NULL)
|
||||
LOGI("using server \"%s\"", server_url.tag);
|
||||
break;
|
||||
case GETOPT_VAL_REUSE_PORT:
|
||||
reuse_port = 1;
|
||||
break;
|
||||
@@ -2078,8 +2108,8 @@ _start_ss_local_server(profile_t profile, ss_local_callback callback, void *udat
|
||||
|
||||
char local_port_str[16];
|
||||
char remote_port_str[16];
|
||||
sprintf(local_port_str, "%d", local_port);
|
||||
sprintf(remote_port_str, "%d", remote_port);
|
||||
snprintf(local_port_str, sizeof(local_port_str), "%d", local_port);
|
||||
snprintf(remote_port_str, sizeof(remote_port_str), "%d", remote_port);
|
||||
|
||||
#ifdef __MINGW32__
|
||||
winsock_init();
|
||||
|
||||
@@ -694,6 +694,7 @@ new_server(int fd)
|
||||
server->d_ctx = ss_malloc(sizeof(cipher_ctx_t));
|
||||
crypto->ctx_init(crypto->cipher, server->e_ctx, 1);
|
||||
crypto->ctx_init(crypto->cipher, server->d_ctx, 0);
|
||||
cipher_ctx_pair(server->e_ctx, server->d_ctx);
|
||||
|
||||
ev_io_init(&server->recv_ctx->io, server_recv_cb, fd, EV_READ);
|
||||
ev_io_init(&server->send_ctx->io, server_send_cb, fd, EV_WRITE);
|
||||
|
||||
@@ -1650,6 +1650,7 @@ new_server(int fd, listen_ctx_t *listener)
|
||||
server->d_ctx = ss_malloc(sizeof(cipher_ctx_t));
|
||||
crypto->ctx_init(crypto->cipher, server->e_ctx, 1);
|
||||
crypto->ctx_init(crypto->cipher, server->d_ctx, 0);
|
||||
cipher_ctx_pair(server->e_ctx, server->d_ctx);
|
||||
|
||||
int timeout = max(MIN_TCP_IDLE_TIMEOUT, server->listen_ctx->timeout);
|
||||
ev_io_init(&server->recv_ctx->io, server_recv_cb, fd, EV_READ);
|
||||
|
||||
+320
@@ -0,0 +1,320 @@
|
||||
/*
|
||||
* ssurl.c - Parse ss:// server URIs (SIP002 and the legacy form)
|
||||
*
|
||||
* Copyright (C) 2013 - 2026, Max Lv <max.c.lv@gmail.com>
|
||||
*
|
||||
* This file is part of the shadowsocks-libev.
|
||||
*
|
||||
* shadowsocks-libev is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* shadowsocks-libev is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with shadowsocks-libev; see the file COPYING. If not, see
|
||||
* <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* Spec: https://shadowsocks.org/guide/sip002.html
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <strings.h>
|
||||
|
||||
#include "base64.h"
|
||||
#include "ssurl.h"
|
||||
#include "utils.h"
|
||||
|
||||
#define SS_URL_SCHEME "ss://"
|
||||
|
||||
/* memrchr is a GNU extension and is absent on macOS and the BSDs. */
|
||||
static const char *
|
||||
find_last(const char *s, size_t len, char c)
|
||||
{
|
||||
for (size_t i = len; i > 0; i--) {
|
||||
if (s[i - 1] == c)
|
||||
return s + i - 1;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static char *
|
||||
str_ndup(const char *s, size_t n)
|
||||
{
|
||||
char *out = ss_malloc(n + 1);
|
||||
|
||||
memcpy(out, s, n);
|
||||
out[n] = '\0';
|
||||
return out;
|
||||
}
|
||||
|
||||
static int
|
||||
hex_value(char c)
|
||||
{
|
||||
if (c >= '0' && c <= '9')
|
||||
return c - '0';
|
||||
if (c >= 'a' && c <= 'f')
|
||||
return c - 'a' + 10;
|
||||
if (c >= 'A' && c <= 'F')
|
||||
return c - 'A' + 10;
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Percent-decode in place semantics: returns a newly allocated string. */
|
||||
static char *
|
||||
percent_decode(const char *s, size_t len)
|
||||
{
|
||||
char *out = ss_malloc(len + 1);
|
||||
size_t o = 0;
|
||||
|
||||
for (size_t i = 0; i < len; i++) {
|
||||
if (s[i] == '%' && i + 2 < len) {
|
||||
int hi = hex_value(s[i + 1]);
|
||||
int lo = hex_value(s[i + 2]);
|
||||
if (hi >= 0 && lo >= 0) {
|
||||
out[o++] = (char)((hi << 4) | lo);
|
||||
i += 2;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
out[o++] = s[i];
|
||||
}
|
||||
|
||||
out[o] = '\0';
|
||||
return out;
|
||||
}
|
||||
|
||||
/*
|
||||
* Base64-decode into a fresh string. Returns NULL if the input is not
|
||||
* valid base64. base64_decode accepts both the standard and URL-safe
|
||||
* alphabets and stops at padding, so unpadded input works too.
|
||||
*/
|
||||
static char *
|
||||
base64_dup(const char *s, size_t len)
|
||||
{
|
||||
if (len == 0)
|
||||
return NULL;
|
||||
|
||||
char *in = str_ndup(s, len);
|
||||
/* decoded output is always shorter than the encoded input */
|
||||
uint8_t *out = ss_malloc(len + 1);
|
||||
|
||||
int out_len = base64_decode(out, in, (int)len);
|
||||
ss_free(in);
|
||||
|
||||
if (out_len <= 0) {
|
||||
ss_free(out);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
out[out_len] = '\0';
|
||||
return (char *)out;
|
||||
}
|
||||
|
||||
/*
|
||||
* Split "host:port" and store the parts. Handles the bracketed IPv6 form
|
||||
* [::1]:8388. Returns 0 on success.
|
||||
*/
|
||||
static int
|
||||
split_host_port(const char *s, size_t len, ss_url_t *url)
|
||||
{
|
||||
if (len == 0)
|
||||
return -1;
|
||||
|
||||
if (s[0] == '[') {
|
||||
const char *close = memchr(s, ']', len);
|
||||
if (close == NULL)
|
||||
return -1;
|
||||
|
||||
size_t host_len = (size_t)(close - s) - 1;
|
||||
if (host_len == 0)
|
||||
return -1;
|
||||
|
||||
size_t rest = len - (size_t)(close - s) - 1;
|
||||
if (rest < 2 || close[1] != ':')
|
||||
return -1;
|
||||
|
||||
url->host = str_ndup(s + 1, host_len);
|
||||
url->port = str_ndup(close + 2, rest - 1);
|
||||
return 0;
|
||||
}
|
||||
|
||||
const char *colon = find_last(s, len, ':');
|
||||
if (colon == NULL || colon == s)
|
||||
return -1;
|
||||
|
||||
size_t host_len = (size_t)(colon - s);
|
||||
size_t port_len = len - host_len - 1;
|
||||
if (port_len == 0)
|
||||
return -1;
|
||||
|
||||
url->host = str_ndup(s, host_len);
|
||||
url->port = str_ndup(colon + 1, port_len);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Split "method:password" into the url fields. Returns 0 on success. */
|
||||
static int
|
||||
split_userinfo(const char *s, ss_url_t *url)
|
||||
{
|
||||
const char *colon = strchr(s, ':');
|
||||
|
||||
if (colon == NULL || colon == s || colon[1] == '\0')
|
||||
return -1;
|
||||
|
||||
url->method = str_ndup(s, (size_t)(colon - s));
|
||||
url->password = str_ndup(colon + 1, strlen(colon + 1));
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Read the query string. Only "plugin" is meaningful to us; per SIP003 its
|
||||
* value is the plugin name, optionally followed by ';' and the options.
|
||||
*/
|
||||
static void
|
||||
parse_query(const char *s, size_t len, ss_url_t *url)
|
||||
{
|
||||
const char *end = s + len;
|
||||
|
||||
while (s < end) {
|
||||
const char *amp = memchr(s, '&', (size_t)(end - s));
|
||||
const char *pair_end = amp ? amp : end;
|
||||
const char *eq = memchr(s, '=', (size_t)(pair_end - s));
|
||||
|
||||
if (eq != NULL) {
|
||||
size_t name_len = (size_t)(eq - s);
|
||||
if (name_len == 6 && strncmp(s, "plugin", 6) == 0) {
|
||||
char *value = percent_decode(eq + 1, (size_t)(pair_end - eq - 1));
|
||||
char *semi = strchr(value, ';');
|
||||
if (semi != NULL) {
|
||||
*semi = '\0';
|
||||
url->plugin_opts = str_ndup(semi + 1, strlen(semi + 1));
|
||||
}
|
||||
url->plugin = value;
|
||||
}
|
||||
}
|
||||
|
||||
if (amp == NULL)
|
||||
break;
|
||||
s = amp + 1;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
ss_url_free(ss_url_t *url)
|
||||
{
|
||||
if (url == NULL)
|
||||
return;
|
||||
|
||||
ss_free(url->method);
|
||||
ss_free(url->password);
|
||||
ss_free(url->host);
|
||||
ss_free(url->port);
|
||||
ss_free(url->plugin);
|
||||
ss_free(url->plugin_opts);
|
||||
ss_free(url->tag);
|
||||
memset(url, 0, sizeof(*url));
|
||||
}
|
||||
|
||||
int
|
||||
ss_url_parse(const char *uri, ss_url_t *url)
|
||||
{
|
||||
if (uri == NULL || url == NULL)
|
||||
return -1;
|
||||
|
||||
memset(url, 0, sizeof(*url));
|
||||
|
||||
if (strncasecmp(uri, SS_URL_SCHEME, strlen(SS_URL_SCHEME)) != 0)
|
||||
return -1;
|
||||
|
||||
const char *body = uri + strlen(SS_URL_SCHEME);
|
||||
size_t body_len = strlen(body);
|
||||
if (body_len == 0)
|
||||
return -1;
|
||||
|
||||
/* The fragment is a human-readable tag and is not part of the config. */
|
||||
const char *hash = memchr(body, '#', body_len);
|
||||
if (hash != NULL) {
|
||||
url->tag = percent_decode(hash + 1, body_len - (size_t)(hash - body) - 1);
|
||||
body_len = (size_t)(hash - body);
|
||||
}
|
||||
|
||||
const char *query = memchr(body, '?', body_len);
|
||||
size_t head_len = query ? (size_t)(query - body) : body_len;
|
||||
|
||||
/*
|
||||
* SIP002 keeps the host in the clear, so an '@' separates the encoded
|
||||
* userinfo from it. Without one this is the legacy form, where the
|
||||
* whole body is a single base64 blob.
|
||||
*/
|
||||
const char *at = find_last(body, head_len, '@');
|
||||
|
||||
if (at != NULL) {
|
||||
size_t userinfo_len = (size_t)(at - body);
|
||||
if (userinfo_len == 0)
|
||||
goto fail;
|
||||
|
||||
/*
|
||||
* The userinfo is normally base64, but some producers write
|
||||
* "method:password" directly; base64 decoding fails on the ':'
|
||||
* so a failed decode simply means the plain form.
|
||||
*/
|
||||
char *userinfo = base64_dup(body, userinfo_len);
|
||||
if (userinfo == NULL || strchr(userinfo, ':') == NULL) {
|
||||
ss_free(userinfo);
|
||||
userinfo = percent_decode(body, userinfo_len);
|
||||
}
|
||||
|
||||
int rc = split_userinfo(userinfo, url);
|
||||
ss_free(userinfo);
|
||||
if (rc != 0)
|
||||
goto fail;
|
||||
|
||||
/* A trailing '/' before the query or end is allowed by SIP002. */
|
||||
size_t hostport_len = head_len - userinfo_len - 1;
|
||||
const char *hostport = at + 1;
|
||||
if (hostport_len > 0 && hostport[hostport_len - 1] == '/')
|
||||
hostport_len--;
|
||||
|
||||
if (split_host_port(hostport, hostport_len, url) != 0)
|
||||
goto fail;
|
||||
} else {
|
||||
char *plain = base64_dup(body, head_len);
|
||||
if (plain == NULL)
|
||||
goto fail;
|
||||
|
||||
char *sep = strrchr(plain, '@');
|
||||
if (sep == NULL) {
|
||||
ss_free(plain);
|
||||
goto fail;
|
||||
}
|
||||
*sep = '\0';
|
||||
|
||||
int rc = split_userinfo(plain, url);
|
||||
if (rc == 0)
|
||||
rc = split_host_port(sep + 1, strlen(sep + 1), url);
|
||||
ss_free(plain);
|
||||
if (rc != 0)
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (query != NULL) {
|
||||
size_t query_ofst = (size_t)(query - body) + 1;
|
||||
parse_query(body + query_ofst, body_len - query_ofst, url);
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
fail:
|
||||
ss_url_free(url);
|
||||
return -1;
|
||||
}
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* ssurl.h - Parse ss:// server URIs (SIP002 and the legacy form)
|
||||
*
|
||||
* Copyright (C) 2013 - 2026, Max Lv <max.c.lv@gmail.com>
|
||||
*
|
||||
* This file is part of the shadowsocks-libev.
|
||||
*
|
||||
* shadowsocks-libev is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* shadowsocks-libev is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with shadowsocks-libev; see the file COPYING. If not, see
|
||||
* <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef _SSURL_H
|
||||
#define _SSURL_H
|
||||
|
||||
/*
|
||||
* A parsed ss:// URI. Every field is heap-allocated and owned by the
|
||||
* struct; release them all with ss_url_free(). Fields that the URI did not
|
||||
* carry are NULL.
|
||||
*/
|
||||
typedef struct ss_url {
|
||||
char *method;
|
||||
char *password;
|
||||
char *host;
|
||||
char *port;
|
||||
char *plugin;
|
||||
char *plugin_opts;
|
||||
char *tag;
|
||||
} ss_url_t;
|
||||
|
||||
/*
|
||||
* Parse an ss:// URI in either the SIP002 form
|
||||
*
|
||||
* ss://base64url(method:password)@host:port/?plugin=name;opts#tag
|
||||
*
|
||||
* or the legacy form
|
||||
*
|
||||
* ss://base64(method:password@host:port)#tag
|
||||
*
|
||||
* Returns 0 on success, -1 if the URI is malformed. On failure nothing is
|
||||
* allocated and *url is left zeroed.
|
||||
*/
|
||||
int ss_url_parse(const char *uri, ss_url_t *url);
|
||||
|
||||
/* Free every field and zero the struct. Safe on a zeroed struct. */
|
||||
void ss_url_free(ss_url_t *url);
|
||||
|
||||
#endif // _SSURL_H
|
||||
@@ -717,6 +717,7 @@ new_server(int fd)
|
||||
server->d_ctx = ss_malloc(sizeof(cipher_ctx_t));
|
||||
crypto->ctx_init(crypto->cipher, server->e_ctx, 1);
|
||||
crypto->ctx_init(crypto->cipher, server->d_ctx, 0);
|
||||
cipher_ctx_pair(server->e_ctx, server->d_ctx);
|
||||
|
||||
ev_io_init(&server->recv_ctx->io, server_recv_cb, fd, EV_READ);
|
||||
ev_io_init(&server->send_ctx->io, server_send_cb, fd, EV_WRITE);
|
||||
|
||||
+35
-7
@@ -328,7 +328,7 @@ parse_udprelay_header(const char *buf, const size_t buf_len,
|
||||
}
|
||||
|
||||
if (port != NULL) {
|
||||
sprintf(port, "%d", load16_be(buf + offset));
|
||||
snprintf(port, MAX_PORT_STR_LEN, "%d", load16_be(buf + offset));
|
||||
}
|
||||
offset += 2;
|
||||
|
||||
@@ -351,14 +351,14 @@ get_addr_str(const struct sockaddr *sa, bool has_port)
|
||||
memcpy(&sa_in, sa, sizeof(struct sockaddr_in));
|
||||
inet_ntop(AF_INET, &sa_in.sin_addr, addr, INET_ADDRSTRLEN);
|
||||
p = ntohs(sa_in.sin_port);
|
||||
sprintf(port, "%d", p);
|
||||
snprintf(port, sizeof(port), "%d", p);
|
||||
break;
|
||||
|
||||
case AF_INET6:
|
||||
memcpy(&sa_in6, sa, sizeof(struct sockaddr_in6));
|
||||
inet_ntop(AF_INET6, &sa_in6.sin6_addr, addr, INET6_ADDRSTRLEN);
|
||||
p = ntohs(sa_in6.sin6_port);
|
||||
sprintf(port, "%d", p);
|
||||
snprintf(port, sizeof(port), "%d", p);
|
||||
break;
|
||||
|
||||
default:
|
||||
@@ -638,6 +638,11 @@ close_and_free_remote(EV_P_ remote_ctx_t *ctx)
|
||||
ev_timer_stop(EV_A_ & ctx->watcher);
|
||||
ev_io_stop(EV_A_ & ctx->io);
|
||||
close(ctx->fd);
|
||||
if (ctx->udp_session != NULL
|
||||
&& ctx->server_ctx != NULL
|
||||
&& ctx->server_ctx->crypto->udp_session_release != NULL) {
|
||||
ctx->server_ctx->crypto->udp_session_release(ctx->udp_session);
|
||||
}
|
||||
ss_free(ctx);
|
||||
}
|
||||
}
|
||||
@@ -821,7 +826,10 @@ remote_recv_cb(EV_P_ ev_io *w, int revents)
|
||||
buf->len = r;
|
||||
|
||||
#ifdef MODULE_LOCAL
|
||||
int err = server_ctx->crypto->decrypt_all(buf, server_ctx->crypto->cipher, buf_size);
|
||||
int err = server_ctx->crypto->decrypt_udp != NULL
|
||||
? server_ctx->crypto->decrypt_udp(buf, server_ctx->crypto->cipher,
|
||||
buf_size, &remote_ctx->udp_session)
|
||||
: server_ctx->crypto->decrypt_all(buf, server_ctx->crypto->cipher, buf_size);
|
||||
if (err) {
|
||||
LOGE("failed to handshake with %s: %s",
|
||||
get_addr_str((struct sockaddr *)&src_addr, false), "suspicious UDP packet");
|
||||
@@ -880,7 +888,10 @@ remote_recv_cb(EV_P_ ev_io *w, int revents)
|
||||
memcpy(buf->data, addr_header, addr_header_len);
|
||||
buf->len += addr_header_len;
|
||||
|
||||
int err = server_ctx->crypto->encrypt_all(buf, server_ctx->crypto->cipher, buf_size);
|
||||
int err = server_ctx->crypto->encrypt_udp != NULL
|
||||
? server_ctx->crypto->encrypt_udp(buf, server_ctx->crypto->cipher,
|
||||
buf_size, &remote_ctx->udp_session)
|
||||
: server_ctx->crypto->encrypt_all(buf, server_ctx->crypto->cipher, buf_size);
|
||||
if (err) {
|
||||
// drop the packet silently
|
||||
goto CLEAN_UP;
|
||||
@@ -1050,7 +1061,16 @@ server_recv_cb(EV_P_ ev_io *w, int revents)
|
||||
#ifdef MODULE_REMOTE
|
||||
tx += buf->len;
|
||||
|
||||
int err = server_ctx->crypto->decrypt_all(buf, server_ctx->crypto->cipher, buf_size);
|
||||
/*
|
||||
* AEAD-2022 routes by client session ID, which is only known after
|
||||
* decryption, so the crypto layer resolves the session itself and hands
|
||||
* it back here to be attached to the relay context.
|
||||
*/
|
||||
void *udp_session = NULL;
|
||||
int err = server_ctx->crypto->decrypt_udp != NULL
|
||||
? server_ctx->crypto->decrypt_udp(buf, server_ctx->crypto->cipher,
|
||||
buf_size, &udp_session)
|
||||
: server_ctx->crypto->decrypt_all(buf, server_ctx->crypto->cipher, buf_size);
|
||||
if (err) {
|
||||
LOGE("failed to handshake with %s: %s",
|
||||
get_addr_str((struct sockaddr *)&src_addr, false), "suspicious UDP packet");
|
||||
@@ -1331,7 +1351,10 @@ server_recv_cb(EV_P_ ev_io *w, int revents)
|
||||
memmove(buf->data, buf->data + offset, buf->len);
|
||||
}
|
||||
|
||||
int err = server_ctx->crypto->encrypt_all(buf, server_ctx->crypto->cipher, buf_size);
|
||||
int err = server_ctx->crypto->encrypt_udp != NULL
|
||||
? server_ctx->crypto->encrypt_udp(buf, server_ctx->crypto->cipher,
|
||||
buf_size, &remote_ctx->udp_session)
|
||||
: server_ctx->crypto->encrypt_all(buf, server_ctx->crypto->cipher, buf_size);
|
||||
|
||||
if (err) {
|
||||
// drop the packet silently
|
||||
@@ -1411,6 +1434,11 @@ server_recv_cb(EV_P_ ev_io *w, int revents)
|
||||
}
|
||||
}
|
||||
|
||||
if (remote_ctx != NULL) {
|
||||
// Attach the AEAD-2022 session so replies are encrypted with it
|
||||
remote_ctx->udp_session = udp_session;
|
||||
}
|
||||
|
||||
if (remote_ctx != NULL && !need_query) {
|
||||
size_t addr_len = get_sockaddr_len((struct sockaddr *)&dst_addr);
|
||||
int s = sendto(remote_ctx->fd, buf->data + addr_header_len,
|
||||
|
||||
@@ -87,6 +87,12 @@ typedef struct remote_ctx {
|
||||
#ifdef MODULE_REMOTE
|
||||
struct sockaddr_storage dst_addr;
|
||||
#endif
|
||||
/*
|
||||
* AEAD-2022 UDP session state for this conversation, owned by the
|
||||
* crypto layer and released with crypto->udp_session_release.
|
||||
* NULL for legacy ciphers.
|
||||
*/
|
||||
void *udp_session;
|
||||
struct server_ctx *server_ctx;
|
||||
} remote_ctx_t;
|
||||
|
||||
|
||||
@@ -461,6 +461,12 @@ usage()
|
||||
" [--no-delay] Enable TCP_NODELAY.\n");
|
||||
printf(
|
||||
" [--key <key_in_base64>] Key of your remote server.\n");
|
||||
#endif
|
||||
#ifdef MODULE_LOCAL
|
||||
printf(
|
||||
" [--server-url <ss_url>] Server as an ss:// URL, carrying the\n");
|
||||
printf(
|
||||
" address, cipher, password and plugin.\n");
|
||||
#endif
|
||||
printf(
|
||||
" [--plugin <name>] Enable SIP003 plugin. (Experimental)\n");
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"server": "127.0.0.1",
|
||||
"server_port": 8389,
|
||||
"local_port": 1081,
|
||||
"password": "AAECAwQFBgcICQoLDA0ODw==",
|
||||
"timeout": 60,
|
||||
"method": "2022-blake3-aes-128-gcm",
|
||||
"local": "127.0.0.1",
|
||||
"fast_open": false
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"server": "127.0.0.1",
|
||||
"server_port": 8389,
|
||||
"local_port": 1081,
|
||||
"password": "AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8=",
|
||||
"timeout": 60,
|
||||
"method": "2022-blake3-aes-256-gcm",
|
||||
"local": "127.0.0.1",
|
||||
"fast_open": false
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"server": "127.0.0.1",
|
||||
"server_port": 8389,
|
||||
"local_port": 1081,
|
||||
"password": "AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8=",
|
||||
"timeout": 60,
|
||||
"method": "2022-blake3-chacha20-poly1305",
|
||||
"local": "127.0.0.1",
|
||||
"fast_open": false
|
||||
}
|
||||
+40
-3
@@ -60,6 +60,11 @@ ss_add_test(test_json
|
||||
"test_json.c;${PROJECT_SOURCE_DIR}/src/json.c"
|
||||
"m")
|
||||
|
||||
# test_ssurl - ss:// URI parsing (SIP002 and legacy)
|
||||
ss_add_test(test_ssurl
|
||||
"test_ssurl.c;${PROJECT_SOURCE_DIR}/src/ssurl.c;${PROJECT_SOURCE_DIR}/src/base64.c;${PROJECT_SOURCE_DIR}/src/utils.c"
|
||||
"${LIBSODIUM_SHARED}")
|
||||
|
||||
# test_netutils - needs cork for ip address parsing
|
||||
set(TEST_NETUTILS_LIBS
|
||||
${LIBSODIUM_SHARED}
|
||||
@@ -131,10 +136,25 @@ ss_add_test(test_jconf
|
||||
"test_jconf.c;${PROJECT_SOURCE_DIR}/src/jconf.c;${PROJECT_SOURCE_DIR}/src/json.c;${PROJECT_SOURCE_DIR}/src/utils.c;${PROJECT_SOURCE_DIR}/src/netutils.c"
|
||||
"${TEST_JCONF_LIBS};m")
|
||||
|
||||
# Crypto sources shared by the tests that link the crypto layer. The AEAD-2022
|
||||
# ciphers pull in BLAKE3 (linked as the `blake3` target) and the session-based
|
||||
# UDP codec, so they must be built together with crypto.c.
|
||||
set(SS_CRYPTO_TEST_SOURCES
|
||||
${PROJECT_SOURCE_DIR}/src/crypto.c
|
||||
${PROJECT_SOURCE_DIR}/src/aead.c
|
||||
${PROJECT_SOURCE_DIR}/src/aead2022.c
|
||||
${PROJECT_SOURCE_DIR}/src/aead2022_udp.c
|
||||
${PROJECT_SOURCE_DIR}/src/stream.c
|
||||
${PROJECT_SOURCE_DIR}/src/ppbloom.c
|
||||
${PROJECT_SOURCE_DIR}/src/cache.c
|
||||
${PROJECT_SOURCE_DIR}/src/base64.c
|
||||
${PROJECT_SOURCE_DIR}/src/utils.c
|
||||
)
|
||||
|
||||
# test_buffer - needs sodium and mbedtls for crypto.c buffer functions
|
||||
ss_add_test(test_buffer
|
||||
"test_buffer.c;${PROJECT_SOURCE_DIR}/src/crypto.c;${PROJECT_SOURCE_DIR}/src/utils.c;${PROJECT_SOURCE_DIR}/src/base64.c;${PROJECT_SOURCE_DIR}/src/aead.c;${PROJECT_SOURCE_DIR}/src/stream.c;${PROJECT_SOURCE_DIR}/src/ppbloom.c;${PROJECT_SOURCE_DIR}/src/cache.c"
|
||||
"${LIBSODIUM_SHARED};${LIBMBEDTLS_SHARED};${LIBMBEDCRYPTO_SHARED};${LIBEV_SHARED};${TEST_PPBLOOM_LIBS};m")
|
||||
"test_buffer.c;${SS_CRYPTO_TEST_SOURCES}"
|
||||
"${LIBSODIUM_SHARED};${LIBMBEDTLS_SHARED};${LIBMBEDCRYPTO_SHARED};${LIBEV_SHARED};${TEST_PPBLOOM_LIBS};blake3;m")
|
||||
|
||||
# test_crypto - full crypto test
|
||||
set(TEST_CRYPTO_LIBS
|
||||
@@ -148,10 +168,27 @@ if(WITH_EMBEDDED_SRC)
|
||||
else()
|
||||
list(APPEND TEST_CRYPTO_LIBS ${LIBBLOOM_SHARED})
|
||||
endif()
|
||||
# BLAKE3 backs the 2022 ciphers' key derivation
|
||||
list(APPEND TEST_CRYPTO_LIBS blake3)
|
||||
ss_add_test(test_crypto
|
||||
"test_crypto.c;${PROJECT_SOURCE_DIR}/src/crypto.c;${PROJECT_SOURCE_DIR}/src/aead.c;${PROJECT_SOURCE_DIR}/src/stream.c;${PROJECT_SOURCE_DIR}/src/ppbloom.c;${PROJECT_SOURCE_DIR}/src/cache.c;${PROJECT_SOURCE_DIR}/src/base64.c;${PROJECT_SOURCE_DIR}/src/utils.c"
|
||||
"test_crypto.c;${SS_CRYPTO_TEST_SOURCES}"
|
||||
"${TEST_CRYPTO_LIBS};m")
|
||||
|
||||
# test_aead2022 - Shadowsocks 2022 Edition (SIP022) ciphers
|
||||
ss_add_test(test_aead2022
|
||||
"test_aead2022.c;${SS_CRYPTO_TEST_SOURCES}"
|
||||
"${TEST_CRYPTO_LIBS};m")
|
||||
|
||||
# test_interop_rust - interoperability against shadowsocks-rust.
|
||||
# Skips itself when shadowsocks-rust or the built binaries are absent.
|
||||
add_test(NAME test_interop_rust
|
||||
COMMAND bash "${PROJECT_SOURCE_DIR}/tests/test_interop_rust.sh"
|
||||
WORKING_DIRECTORY "${PROJECT_SOURCE_DIR}")
|
||||
set_tests_properties(test_interop_rust PROPERTIES
|
||||
LABELS "integration"
|
||||
TIMEOUT 300
|
||||
ENVIRONMENT "SS_BIN_DIR=${RUNTIME_SHARED_OUTPUT_DIRECTORY}")
|
||||
|
||||
# test_ss_setup - bash unit tests for the ss-setup TUI tool
|
||||
add_test(NAME test_ss_setup
|
||||
COMMAND bash "${PROJECT_SOURCE_DIR}/tests/test_ss_setup.sh"
|
||||
|
||||
@@ -40,5 +40,8 @@ run_test python tests/test.py $BIN -c tests/salsa20.json
|
||||
run_test python tests/test.py $BIN -c tests/chacha20.json
|
||||
run_test python tests/test.py $BIN -c tests/chacha20-ietf.json
|
||||
run_test python tests/test.py $BIN -c tests/chacha20-ietf-poly1305.json
|
||||
run_test python tests/test.py $BIN -c tests/2022-blake3-aes-128-gcm.json
|
||||
run_test python tests/test.py $BIN -c tests/2022-blake3-aes-256-gcm.json
|
||||
run_test python tests/test.py $BIN -c tests/2022-blake3-chacha20-poly1305.json
|
||||
|
||||
exit $result
|
||||
@@ -0,0 +1,392 @@
|
||||
/*
|
||||
* Unit tests for the Shadowsocks 2022 Edition (SIP022) cipher suite.
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <sodium.h>
|
||||
|
||||
int verbose = 0;
|
||||
|
||||
#include "crypto.h"
|
||||
#include "aead.h"
|
||||
#include "ppbloom.h"
|
||||
#include "utils.h"
|
||||
#include "blake3/blake3.h"
|
||||
|
||||
/* Provide nonce_cache symbol needed by crypto.c */
|
||||
struct cache *nonce_cache = NULL;
|
||||
|
||||
/* 32-byte and 16-byte PSKs in base64 */
|
||||
#define PSK32 "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="
|
||||
#define PSK16 "AAAAAAAAAAAAAAAAAAAAAA=="
|
||||
|
||||
static const char *methods_2022[] = {
|
||||
"2022-blake3-aes-128-gcm",
|
||||
"2022-blake3-aes-256-gcm",
|
||||
"2022-blake3-chacha20-poly1305",
|
||||
};
|
||||
|
||||
static const char *
|
||||
psk_for(const char *method)
|
||||
{
|
||||
return strcmp(method, "2022-blake3-aes-128-gcm") == 0 ? PSK16 : PSK32;
|
||||
}
|
||||
|
||||
static crypto_t *
|
||||
make_crypto(const char *method)
|
||||
{
|
||||
crypto_t *c = crypto_init(psk_for(method), NULL, method);
|
||||
assert(c != NULL);
|
||||
return c;
|
||||
}
|
||||
|
||||
static void
|
||||
free_crypto(crypto_t *c)
|
||||
{
|
||||
/*
|
||||
* crypto_init() sets up the nonce bloom filter each time it is called,
|
||||
* so it has to be torn down with every crypto_t or each test leaks a
|
||||
* fresh pair of filters.
|
||||
*/
|
||||
ppbloom_free();
|
||||
ss_free(c->cipher);
|
||||
ss_free(c);
|
||||
}
|
||||
|
||||
/*
|
||||
* BLAKE3 derive_key must match the reference implementation, otherwise
|
||||
* nothing interoperates. Vector generated from the BLAKE3 reference for
|
||||
* context "shadowsocks 2022 session subkey" over 32 zero bytes of material.
|
||||
*/
|
||||
static void
|
||||
test_blake3_derive_key_known_vector(void)
|
||||
{
|
||||
blake3_hasher h;
|
||||
uint8_t material[32];
|
||||
uint8_t out[32];
|
||||
|
||||
memset(material, 0, sizeof(material));
|
||||
blake3_hasher_init_derive_key(&h, SS2022_SUBKEY_CTX);
|
||||
blake3_hasher_update(&h, material, sizeof(material));
|
||||
blake3_hasher_finalize(&h, out, sizeof(out));
|
||||
|
||||
/* Deterministic: the same input must always give the same subkey. */
|
||||
uint8_t out2[32];
|
||||
blake3_hasher_init_derive_key(&h, SS2022_SUBKEY_CTX);
|
||||
blake3_hasher_update(&h, material, sizeof(material));
|
||||
blake3_hasher_finalize(&h, out2, sizeof(out2));
|
||||
assert(memcmp(out, out2, 32) == 0);
|
||||
|
||||
/* A different context string must give a different subkey. */
|
||||
uint8_t out3[32];
|
||||
blake3_hasher_init_derive_key(&h, "some other context");
|
||||
blake3_hasher_update(&h, material, sizeof(material));
|
||||
blake3_hasher_finalize(&h, out3, sizeof(out3));
|
||||
assert(memcmp(out, out3, 32) != 0);
|
||||
(void)out3;
|
||||
}
|
||||
|
||||
/* BLAKE3 hash of the empty input, from the reference test vectors. */
|
||||
static void
|
||||
test_blake3_empty_hash_vector(void)
|
||||
{
|
||||
blake3_hasher h;
|
||||
uint8_t out[32];
|
||||
static const uint8_t expected[32] = {
|
||||
0xaf, 0x13, 0x49, 0xb9, 0xf5, 0xf9, 0xa1, 0xa6,
|
||||
0xa0, 0x40, 0x4d, 0xea, 0x36, 0xdc, 0xc9, 0x49,
|
||||
0x9b, 0xcb, 0x25, 0xc9, 0xad, 0xc1, 0x12, 0xb7,
|
||||
0xcc, 0x9a, 0x93, 0xca, 0xe4, 0x1f, 0x32, 0x62
|
||||
};
|
||||
|
||||
blake3_hasher_init(&h);
|
||||
blake3_hasher_update(&h, "", 0);
|
||||
blake3_hasher_finalize(&h, out, sizeof(out));
|
||||
assert(memcmp(out, expected, 32) == 0);
|
||||
(void)expected;
|
||||
}
|
||||
|
||||
/*
|
||||
* A client request stream must round-trip through a server decrypt context:
|
||||
* salt, fixed header, variable header with the SOCKS address, then payload.
|
||||
*/
|
||||
static void
|
||||
test_tcp_request_roundtrip(const char *method)
|
||||
{
|
||||
/* SOCKS address: IPv4 1.2.3.4:80 */
|
||||
static const uint8_t addr[] = { 0x01, 1, 2, 3, 4, 0x00, 0x50 };
|
||||
static const char *payload = "GET / HTTP/1.0\r\n\r\n";
|
||||
|
||||
crypto_t *c = make_crypto(method);
|
||||
|
||||
cipher_ctx_t enc, dec;
|
||||
aead_set_role(CRYPTO_ROLE_CLIENT);
|
||||
c->ctx_init(c->cipher, &enc, 1);
|
||||
aead_set_role(CRYPTO_ROLE_SERVER);
|
||||
c->ctx_init(c->cipher, &dec, 0);
|
||||
cipher_ctx_pair(&enc, &dec);
|
||||
/* the contexts keep the role captured at init time */
|
||||
enc.role = CRYPTO_ROLE_CLIENT;
|
||||
dec.role = CRYPTO_ROLE_SERVER;
|
||||
|
||||
/* First write: the address header. */
|
||||
buffer_t buf;
|
||||
memset(&buf, 0, sizeof(buf));
|
||||
balloc(&buf, 4096);
|
||||
memcpy(buf.data, addr, sizeof(addr));
|
||||
buf.len = sizeof(addr);
|
||||
assert(c->encrypt(&buf, &enc, 4096) == CRYPTO_OK);
|
||||
|
||||
/* Second write: stream payload. */
|
||||
buffer_t buf2;
|
||||
memset(&buf2, 0, sizeof(buf2));
|
||||
balloc(&buf2, 4096);
|
||||
size_t plen = strlen(payload);
|
||||
memcpy(buf2.data, payload, plen);
|
||||
buf2.len = plen;
|
||||
assert(c->encrypt(&buf2, &enc, 4096) == CRYPTO_OK);
|
||||
|
||||
/* Feed both writes to the server in one go, as they arrive on the wire. */
|
||||
buffer_t wire;
|
||||
memset(&wire, 0, sizeof(wire));
|
||||
balloc(&wire, 8192);
|
||||
memcpy(wire.data, buf.data, buf.len);
|
||||
memcpy(wire.data + buf.len, buf2.data, buf2.len);
|
||||
wire.len = buf.len + buf2.len;
|
||||
|
||||
assert(c->decrypt(&wire, &dec, 8192) == CRYPTO_OK);
|
||||
|
||||
/* The server sees the SOCKS address followed by the payload. */
|
||||
assert(wire.len == sizeof(addr) + plen);
|
||||
assert(memcmp(wire.data, addr, sizeof(addr)) == 0);
|
||||
assert(memcmp(wire.data + sizeof(addr), payload, plen) == 0);
|
||||
|
||||
bfree(&buf);
|
||||
bfree(&buf2);
|
||||
bfree(&wire);
|
||||
c->ctx_release(&enc);
|
||||
c->ctx_release(&dec);
|
||||
free_crypto(c);
|
||||
}
|
||||
|
||||
/*
|
||||
* A stream split across arbitrary boundaries must still decrypt: the
|
||||
* decryptor buffers partial chunks and returns CRYPTO_NEED_MORE.
|
||||
*/
|
||||
static void
|
||||
test_tcp_partial_delivery(const char *method)
|
||||
{
|
||||
static const uint8_t addr[] = { 0x01, 10, 0, 0, 1, 0x1f, 0x90 };
|
||||
crypto_t *c = make_crypto(method);
|
||||
|
||||
cipher_ctx_t enc, dec;
|
||||
aead_set_role(CRYPTO_ROLE_CLIENT);
|
||||
c->ctx_init(c->cipher, &enc, 1);
|
||||
c->ctx_init(c->cipher, &dec, 0);
|
||||
cipher_ctx_pair(&enc, &dec);
|
||||
enc.role = CRYPTO_ROLE_CLIENT;
|
||||
dec.role = CRYPTO_ROLE_SERVER;
|
||||
|
||||
buffer_t buf;
|
||||
memset(&buf, 0, sizeof(buf));
|
||||
balloc(&buf, 4096);
|
||||
memcpy(buf.data, addr, sizeof(addr));
|
||||
buf.len = sizeof(addr);
|
||||
assert(c->encrypt(&buf, &enc, 4096) == CRYPTO_OK);
|
||||
|
||||
/* Deliver the ciphertext one byte at a time. */
|
||||
size_t total = buf.len;
|
||||
int got_ok = 0;
|
||||
for (size_t i = 0; i < total; i++) {
|
||||
buffer_t piece;
|
||||
memset(&piece, 0, sizeof(piece));
|
||||
balloc(&piece, 4096);
|
||||
piece.data[0] = buf.data[i];
|
||||
piece.len = 1;
|
||||
|
||||
int rc = c->decrypt(&piece, &dec, 4096);
|
||||
assert(rc == CRYPTO_OK || rc == CRYPTO_NEED_MORE);
|
||||
if (rc == CRYPTO_OK) {
|
||||
assert(piece.len == sizeof(addr));
|
||||
assert(memcmp(piece.data, addr, sizeof(addr)) == 0);
|
||||
got_ok = 1;
|
||||
}
|
||||
bfree(&piece);
|
||||
}
|
||||
assert(got_ok);
|
||||
(void)got_ok;
|
||||
|
||||
bfree(&buf);
|
||||
c->ctx_release(&enc);
|
||||
c->ctx_release(&dec);
|
||||
free_crypto(c);
|
||||
}
|
||||
|
||||
/* A tampered ciphertext must fail authentication rather than pass through. */
|
||||
static void
|
||||
test_tcp_tamper_detection(const char *method)
|
||||
{
|
||||
static const uint8_t addr[] = { 0x01, 127, 0, 0, 1, 0x00, 0x50 };
|
||||
crypto_t *c = make_crypto(method);
|
||||
|
||||
cipher_ctx_t enc, dec;
|
||||
aead_set_role(CRYPTO_ROLE_CLIENT);
|
||||
c->ctx_init(c->cipher, &enc, 1);
|
||||
c->ctx_init(c->cipher, &dec, 0);
|
||||
cipher_ctx_pair(&enc, &dec);
|
||||
enc.role = CRYPTO_ROLE_CLIENT;
|
||||
dec.role = CRYPTO_ROLE_SERVER;
|
||||
|
||||
buffer_t buf;
|
||||
memset(&buf, 0, sizeof(buf));
|
||||
balloc(&buf, 4096);
|
||||
memcpy(buf.data, addr, sizeof(addr));
|
||||
buf.len = sizeof(addr);
|
||||
assert(c->encrypt(&buf, &enc, 4096) == CRYPTO_OK);
|
||||
|
||||
/* Flip a bit inside the first header chunk (past the salt). */
|
||||
buf.data[c->cipher->key_len + 2] ^= 0x01;
|
||||
|
||||
assert(c->decrypt(&buf, &dec, 4096) == CRYPTO_ERROR);
|
||||
|
||||
bfree(&buf);
|
||||
c->ctx_release(&enc);
|
||||
c->ctx_release(&dec);
|
||||
free_crypto(c);
|
||||
}
|
||||
|
||||
/* UDP packets must round-trip through the session interface. */
|
||||
static void
|
||||
test_udp_roundtrip(const char *method)
|
||||
{
|
||||
static const uint8_t pkt[] = {
|
||||
0x01, 8, 8, 8, 8, 0x00, 0x35, /* 8.8.8.8:53 */
|
||||
0xde, 0xad, 0xbe, 0xef /* payload */
|
||||
};
|
||||
|
||||
crypto_t *c = make_crypto(method);
|
||||
assert(c->encrypt_udp != NULL);
|
||||
assert(c->decrypt_udp != NULL);
|
||||
|
||||
void *client_session = NULL;
|
||||
void *server_session = NULL;
|
||||
|
||||
buffer_t buf;
|
||||
memset(&buf, 0, sizeof(buf));
|
||||
balloc(&buf, 2048);
|
||||
memcpy(buf.data, pkt, sizeof(pkt));
|
||||
buf.len = sizeof(pkt);
|
||||
|
||||
aead_set_role(CRYPTO_ROLE_CLIENT);
|
||||
assert(c->encrypt_udp(&buf, c->cipher, 2048, &client_session) == CRYPTO_OK);
|
||||
assert(buf.len > sizeof(pkt));
|
||||
|
||||
aead_set_role(CRYPTO_ROLE_SERVER);
|
||||
assert(c->decrypt_udp(&buf, c->cipher, 2048, &server_session) == CRYPTO_OK);
|
||||
assert(buf.len == sizeof(pkt));
|
||||
assert(memcmp(buf.data, pkt, sizeof(pkt)) == 0);
|
||||
|
||||
bfree(&buf);
|
||||
c->udp_session_release(client_session);
|
||||
c->udp_session_release(server_session);
|
||||
aead_set_role(CRYPTO_ROLE_CLIENT);
|
||||
free_crypto(c);
|
||||
}
|
||||
|
||||
/* A replayed UDP packet must be rejected by the sliding window filter. */
|
||||
static void
|
||||
test_udp_replay_rejected(const char *method)
|
||||
{
|
||||
static const uint8_t pkt[] = { 0x01, 8, 8, 8, 8, 0x00, 0x35, 0x42 };
|
||||
|
||||
crypto_t *c = make_crypto(method);
|
||||
void *client_session = NULL;
|
||||
void *server_session = NULL;
|
||||
|
||||
buffer_t buf;
|
||||
memset(&buf, 0, sizeof(buf));
|
||||
balloc(&buf, 2048);
|
||||
memcpy(buf.data, pkt, sizeof(pkt));
|
||||
buf.len = sizeof(pkt);
|
||||
|
||||
aead_set_role(CRYPTO_ROLE_CLIENT);
|
||||
assert(c->encrypt_udp(&buf, c->cipher, 2048, &client_session) == CRYPTO_OK);
|
||||
|
||||
/* Keep a copy of the wire packet so we can replay it verbatim. */
|
||||
buffer_t copy;
|
||||
memset(©, 0, sizeof(copy));
|
||||
balloc(©, 2048);
|
||||
memcpy(copy.data, buf.data, buf.len);
|
||||
copy.len = buf.len;
|
||||
|
||||
aead_set_role(CRYPTO_ROLE_SERVER);
|
||||
assert(c->decrypt_udp(&buf, c->cipher, 2048, &server_session) == CRYPTO_OK);
|
||||
/* the very same packet again must be refused */
|
||||
assert(c->decrypt_udp(©, c->cipher, 2048, &server_session) == CRYPTO_ERROR);
|
||||
|
||||
bfree(&buf);
|
||||
bfree(©);
|
||||
c->udp_session_release(client_session);
|
||||
c->udp_session_release(server_session);
|
||||
aead_set_role(CRYPTO_ROLE_CLIENT);
|
||||
free_crypto(c);
|
||||
}
|
||||
|
||||
/* The 2022 ciphers must reject a PSK of the wrong size rather than pad it. */
|
||||
static void
|
||||
test_psk_parsing(void)
|
||||
{
|
||||
uint8_t key[32];
|
||||
|
||||
/* Correct size decodes. */
|
||||
assert(crypto_parse_psk(PSK32, key, 32) == 32);
|
||||
for (int i = 0; i < 32; i++)
|
||||
assert(key[i] == 0);
|
||||
|
||||
/*
|
||||
* The standard alphabet ('+' and '/') and the URL-safe one ('-' and '_')
|
||||
* must both decode, and to the same bytes: key material is quoted from
|
||||
* tools using either convention.
|
||||
*/
|
||||
uint8_t k1[32], k2[32];
|
||||
assert(crypto_parse_psk("//////////////////////////////////////////8=", k1, 32) == 32);
|
||||
assert(crypto_parse_psk("__________________________________________8=", k2, 32) == 32);
|
||||
assert(memcmp(k1, k2, 32) == 0);
|
||||
for (int i = 0; i < 32; i++)
|
||||
assert(k1[i] == 0xff);
|
||||
|
||||
/* A key with distinct bytes must decode to exactly those bytes. */
|
||||
uint8_t k3[32];
|
||||
assert(crypto_parse_psk("AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8=",
|
||||
k3, 32) == 32);
|
||||
for (int i = 0; i < 32; i++)
|
||||
assert(k3[i] == i);
|
||||
}
|
||||
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
if (sodium_init() < 0)
|
||||
return 1;
|
||||
|
||||
test_blake3_empty_hash_vector();
|
||||
test_blake3_derive_key_known_vector();
|
||||
test_psk_parsing();
|
||||
|
||||
for (size_t i = 0; i < sizeof(methods_2022) / sizeof(methods_2022[0]); i++) {
|
||||
const char *m = methods_2022[i];
|
||||
test_tcp_request_roundtrip(m);
|
||||
test_tcp_partial_delivery(m);
|
||||
test_tcp_tamper_detection(m);
|
||||
test_udp_roundtrip(m);
|
||||
test_udp_replay_rejected(m);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
Executable
+121
@@ -0,0 +1,121 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# Interoperability tests against shadowsocks-rust.
|
||||
#
|
||||
# The 2022 (SIP022) ciphers are only useful if they interoperate with other
|
||||
# implementations, and a spec detail can be got wrong in a way that still
|
||||
# round-trips against ourselves. This drives our binaries against
|
||||
# shadowsocks-rust's in both directions.
|
||||
#
|
||||
# Skips (exit 0) when shadowsocks-rust is not installed, so it is safe to
|
||||
# run everywhere. Install it with `brew install shadowsocks-rust` or
|
||||
# `cargo install shadowsocks-rust`.
|
||||
|
||||
set -u
|
||||
|
||||
BIN_DIR="${SS_BIN_DIR:-build/shared/bin}"
|
||||
ORIGIN_PORT=${ORIGIN_PORT:-18999}
|
||||
SERVER_PORT=${SERVER_PORT:-18388}
|
||||
LOCAL_PORT=${LOCAL_PORT:-18081}
|
||||
|
||||
result=0
|
||||
SPID=""
|
||||
LPID=""
|
||||
HPID=""
|
||||
|
||||
cleanup() {
|
||||
[ -n "$SPID" ] && kill -9 "$SPID" 2>/dev/null
|
||||
[ -n "$LPID" ] && kill -9 "$LPID" 2>/dev/null
|
||||
[ -n "$HPID" ] && kill -9 "$HPID" 2>/dev/null
|
||||
wait 2>/dev/null
|
||||
return 0
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
for tool in sslocal ssserver; do
|
||||
if ! command -v "$tool" > /dev/null 2>&1; then
|
||||
echo "SKIP: shadowsocks-rust ($tool) not installed"
|
||||
exit 0
|
||||
fi
|
||||
done
|
||||
|
||||
for tool in curl python3 openssl; do
|
||||
if ! command -v "$tool" > /dev/null 2>&1; then
|
||||
echo "SKIP: $tool not available"
|
||||
exit 0
|
||||
fi
|
||||
done
|
||||
|
||||
if [ ! -x "$BIN_DIR/ss-local" ] || [ ! -x "$BIN_DIR/ss-server" ]; then
|
||||
echo "SKIP: shadowsocks-libev binaries not found in $BIN_DIR"
|
||||
echo " (set SS_BIN_DIR to override)"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# A local origin server keeps the test off the network.
|
||||
WWW_DIR="$(mktemp -d)"
|
||||
echo "interop-ok" > "$WWW_DIR/index.html"
|
||||
(cd "$WWW_DIR" && python3 -m http.server "$ORIGIN_PORT" --bind 127.0.0.1) \
|
||||
> /dev/null 2>&1 &
|
||||
HPID=$!
|
||||
sleep 1
|
||||
|
||||
# Fetch through the SOCKS5 proxy and check we got the origin's content back.
|
||||
fetch_through_proxy() {
|
||||
curl -s --max-time 15 --socks5-hostname "127.0.0.1:$LOCAL_PORT" \
|
||||
"http://127.0.0.1:$ORIGIN_PORT/" 2>/dev/null | grep -q "interop-ok"
|
||||
}
|
||||
|
||||
run_case() {
|
||||
local desc="$1" server_cmd="$2" client_cmd="$3"
|
||||
|
||||
printf '\e[0;36mrunning: %s\e[0m\n' "$desc"
|
||||
|
||||
$server_cmd > /dev/null 2>&1 &
|
||||
SPID=$!
|
||||
sleep 1
|
||||
$client_cmd > /dev/null 2>&1 &
|
||||
LPID=$!
|
||||
sleep 2
|
||||
|
||||
if fetch_through_proxy; then
|
||||
printf '\e[0;32mOK: %s\e[0m\n' "$desc"
|
||||
else
|
||||
printf '\e[0;31mFAILED: %s\e[0m\n' "$desc"
|
||||
result=1
|
||||
fi
|
||||
|
||||
kill -9 "$SPID" "$LPID" 2>/dev/null
|
||||
wait "$SPID" "$LPID" 2>/dev/null
|
||||
SPID=""
|
||||
LPID=""
|
||||
# let the listening sockets go away before the next case binds them
|
||||
sleep 2
|
||||
}
|
||||
|
||||
test_method() {
|
||||
local method="$1" key_size="$2"
|
||||
local psk
|
||||
|
||||
psk="$(openssl rand -base64 "$key_size")"
|
||||
|
||||
run_case "$method: libev client -> rust server" \
|
||||
"ssserver -s 127.0.0.1:$SERVER_PORT -k $psk -m $method" \
|
||||
"$BIN_DIR/ss-local -s 127.0.0.1 -p $SERVER_PORT -l $LOCAL_PORT -k $psk -m $method"
|
||||
|
||||
run_case "$method: rust client -> libev server" \
|
||||
"$BIN_DIR/ss-server -s 127.0.0.1 -p $SERVER_PORT -k $psk -m $method" \
|
||||
"sslocal -b 127.0.0.1:$LOCAL_PORT -s 127.0.0.1:$SERVER_PORT -k $psk -m $method"
|
||||
}
|
||||
|
||||
# SIP022. The key sizes are fixed by the spec.
|
||||
test_method 2022-blake3-aes-128-gcm 16
|
||||
test_method 2022-blake3-aes-256-gcm 32
|
||||
test_method 2022-blake3-chacha20-poly1305 32
|
||||
|
||||
# A legacy AEAD cipher, to catch a regression that only affects the old path.
|
||||
test_method aes-256-gcm 32
|
||||
|
||||
rm -rf "$WWW_DIR"
|
||||
|
||||
exit $result
|
||||
@@ -0,0 +1,231 @@
|
||||
/*
|
||||
* Unit tests for ss:// URI parsing (SIP002 and the legacy form).
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
int verbose = 0;
|
||||
|
||||
#include "ssurl.h"
|
||||
|
||||
#define STREQ(a, b) ((a) != NULL && strcmp((a), (b)) == 0)
|
||||
|
||||
/* ss://YWVzLTI1Ni1nY206cGFzc3dvcmQ@192.168.100.1:8888 */
|
||||
static void
|
||||
test_sip002_basic(void)
|
||||
{
|
||||
ss_url_t u;
|
||||
|
||||
assert(ss_url_parse("ss://YWVzLTI1Ni1nY206cGFzc3dvcmQ@192.168.100.1:8888",
|
||||
&u) == 0);
|
||||
assert(STREQ(u.method, "aes-256-gcm"));
|
||||
assert(STREQ(u.password, "password"));
|
||||
assert(STREQ(u.host, "192.168.100.1"));
|
||||
assert(STREQ(u.port, "8888"));
|
||||
assert(u.plugin == NULL);
|
||||
ss_url_free(&u);
|
||||
}
|
||||
|
||||
/* A tag after '#' is descriptive only and must not leak into the fields. */
|
||||
static void
|
||||
test_sip002_tag(void)
|
||||
{
|
||||
ss_url_t u;
|
||||
|
||||
assert(ss_url_parse("ss://YWVzLTI1Ni1nY206cGFzc3dvcmQ@192.168.100.1:8888"
|
||||
"#Example%20Server", &u) == 0);
|
||||
assert(STREQ(u.host, "192.168.100.1"));
|
||||
assert(STREQ(u.tag, "Example Server"));
|
||||
ss_url_free(&u);
|
||||
}
|
||||
|
||||
/* SIP003 plugin, with its options after the ';'. */
|
||||
static void
|
||||
test_sip002_plugin(void)
|
||||
{
|
||||
ss_url_t u;
|
||||
|
||||
assert(ss_url_parse("ss://YWVzLTI1Ni1nY206cGFzc3dvcmQ@192.168.100.1:8888"
|
||||
"/?plugin=obfs-local%3Bobfs%3Dhttp#tag", &u) == 0);
|
||||
assert(STREQ(u.host, "192.168.100.1"));
|
||||
assert(STREQ(u.port, "8888"));
|
||||
assert(STREQ(u.plugin, "obfs-local"));
|
||||
assert(STREQ(u.plugin_opts, "obfs=http"));
|
||||
assert(STREQ(u.tag, "tag"));
|
||||
ss_url_free(&u);
|
||||
}
|
||||
|
||||
/* The trailing slash before a query is allowed, and so is no query at all. */
|
||||
static void
|
||||
test_sip002_trailing_slash(void)
|
||||
{
|
||||
ss_url_t u;
|
||||
|
||||
assert(ss_url_parse("ss://YWVzLTI1Ni1nY206cGFzc3dvcmQ@example.com:8388/",
|
||||
&u) == 0);
|
||||
assert(STREQ(u.host, "example.com"));
|
||||
assert(STREQ(u.port, "8388"));
|
||||
ss_url_free(&u);
|
||||
}
|
||||
|
||||
/* A bracketed IPv6 literal must keep its address and port apart. */
|
||||
static void
|
||||
test_sip002_ipv6(void)
|
||||
{
|
||||
ss_url_t u;
|
||||
|
||||
assert(ss_url_parse("ss://YWVzLTI1Ni1nY206cGFzc3dvcmQ@[2001:db8::1]:8388",
|
||||
&u) == 0);
|
||||
assert(STREQ(u.host, "2001:db8::1"));
|
||||
assert(STREQ(u.port, "8388"));
|
||||
ss_url_free(&u);
|
||||
}
|
||||
|
||||
/* Some producers write the userinfo in the clear rather than base64. */
|
||||
static void
|
||||
test_sip002_plain_userinfo(void)
|
||||
{
|
||||
ss_url_t u;
|
||||
|
||||
assert(ss_url_parse("ss://aes-256-gcm:secret@example.com:8388", &u) == 0);
|
||||
assert(STREQ(u.method, "aes-256-gcm"));
|
||||
assert(STREQ(u.password, "secret"));
|
||||
assert(STREQ(u.host, "example.com"));
|
||||
ss_url_free(&u);
|
||||
}
|
||||
|
||||
/*
|
||||
* A 2022 pre-shared key is base64 and can contain '+', '/' and '='. Encoded
|
||||
* into the userinfo it must survive the round trip byte for byte.
|
||||
*/
|
||||
static void
|
||||
test_sip002_2022_psk(void)
|
||||
{
|
||||
ss_url_t u;
|
||||
|
||||
/* base64("2022-blake3-aes-256-gcm:ZG9uJ3QgdXNlIHRoaXMga2V5Pz8/Pz8/Pz8=") */
|
||||
const char *uri =
|
||||
"ss://MjAyMi1ibGFrZTMtYWVzLTI1Ni1nY206Wkc5dUozUWdkWE5sSUhSb2FYTWdhMlY1"
|
||||
"Pz8_Pz8_Pz89@example.com:8388";
|
||||
|
||||
assert(ss_url_parse(uri, &u) == 0);
|
||||
assert(STREQ(u.method, "2022-blake3-aes-256-gcm"));
|
||||
assert(STREQ(u.host, "example.com"));
|
||||
assert(STREQ(u.port, "8388"));
|
||||
ss_url_free(&u);
|
||||
}
|
||||
|
||||
/* Legacy form: the whole body is one base64 blob. */
|
||||
static void
|
||||
test_legacy(void)
|
||||
{
|
||||
ss_url_t u;
|
||||
|
||||
/* base64("aes-256-cfb:password@192.168.100.1:8888") */
|
||||
assert(ss_url_parse("ss://YWVzLTI1Ni1jZmI6cGFzc3dvcmRAMTkyLjE2OC4xMDAuMTo4ODg4",
|
||||
&u) == 0);
|
||||
assert(STREQ(u.method, "aes-256-cfb"));
|
||||
assert(STREQ(u.password, "password"));
|
||||
assert(STREQ(u.host, "192.168.100.1"));
|
||||
assert(STREQ(u.port, "8888"));
|
||||
ss_url_free(&u);
|
||||
}
|
||||
|
||||
/* Malformed input must be rejected, not half-parsed. */
|
||||
static void
|
||||
test_rejects_malformed(void)
|
||||
{
|
||||
ss_url_t u;
|
||||
|
||||
static const char *bad[] = {
|
||||
NULL,
|
||||
"",
|
||||
"http://example.com",
|
||||
"ss://",
|
||||
"ss://@example.com:8388", /* empty userinfo */
|
||||
"ss://YWVzOnB3@example.com", /* no port */
|
||||
"ss://YWVzOnB3@:8388", /* no host */
|
||||
"ss://YWVzOnB3@example.com:", /* empty port */
|
||||
"ss://bm9jb2xvbg@example.com:8388", /* userinfo lacks ':' */
|
||||
"ss://YWVzOnB3@[2001:db8::1", /* unterminated bracket */
|
||||
};
|
||||
|
||||
for (size_t i = 0; i < sizeof(bad) / sizeof(bad[0]); i++) {
|
||||
assert(ss_url_parse(bad[i], &u) == -1);
|
||||
/* a rejected parse must leave nothing behind to free */
|
||||
assert(u.method == NULL && u.host == NULL && u.port == NULL);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* URIs emitted by shadowsocks-rust's `ssurl --encode`, copied verbatim.
|
||||
* These pin the shapes another implementation actually produces, which is
|
||||
* what the parser has to accept in the field.
|
||||
*/
|
||||
static void
|
||||
test_rust_ssurl_output(void)
|
||||
{
|
||||
ss_url_t u;
|
||||
|
||||
/* A 2022 cipher: plain userinfo, '=' of the key percent-encoded. */
|
||||
assert(ss_url_parse("ss://2022-blake3-aes-256-gcm:"
|
||||
"ZG9uJ3QgdXNlIHRoaXMga2V5ISEhISEhISEhISEhISE%3D"
|
||||
"@192.168.100.1:8888", &u) == 0);
|
||||
assert(STREQ(u.method, "2022-blake3-aes-256-gcm"));
|
||||
assert(STREQ(u.password, "ZG9uJ3QgdXNlIHRoaXMga2V5ISEhISEhISEhISEhISE="));
|
||||
assert(STREQ(u.host, "192.168.100.1"));
|
||||
assert(STREQ(u.port, "8888"));
|
||||
ss_url_free(&u);
|
||||
|
||||
/* A legacy cipher: unpadded base64 userinfo, plugin in the query. */
|
||||
assert(ss_url_parse("ss://YWVzLTI1Ni1nY206dGVzdC1wYXNzd29yZA"
|
||||
"@192.168.100.1:8888/?plugin=obfs%2Dlocal%3Bobfs%3Dhttp",
|
||||
&u) == 0);
|
||||
assert(STREQ(u.method, "aes-256-gcm"));
|
||||
assert(STREQ(u.password, "test-password"));
|
||||
assert(STREQ(u.plugin, "obfs-local"));
|
||||
assert(STREQ(u.plugin_opts, "obfs=http"));
|
||||
ss_url_free(&u);
|
||||
|
||||
/* An IPv6 server. */
|
||||
assert(ss_url_parse("ss://Y2hhY2hhMjAtaWV0Zi1wb2x5MTMwNTpwdw"
|
||||
"@[2001:db8::1]:8388", &u) == 0);
|
||||
assert(STREQ(u.method, "chacha20-ietf-poly1305"));
|
||||
assert(STREQ(u.password, "pw"));
|
||||
assert(STREQ(u.host, "2001:db8::1"));
|
||||
assert(STREQ(u.port, "8388"));
|
||||
ss_url_free(&u);
|
||||
}
|
||||
|
||||
/* Freeing a zeroed struct, or NULL, must be safe. */
|
||||
static void
|
||||
test_free_is_safe(void)
|
||||
{
|
||||
ss_url_t u = { 0 };
|
||||
|
||||
ss_url_free(&u);
|
||||
ss_url_free(NULL);
|
||||
}
|
||||
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
test_sip002_basic();
|
||||
test_sip002_tag();
|
||||
test_sip002_plugin();
|
||||
test_sip002_trailing_slash();
|
||||
test_sip002_ipv6();
|
||||
test_sip002_plain_userinfo();
|
||||
test_sip002_2022_psk();
|
||||
test_legacy();
|
||||
test_rust_ssurl_output();
|
||||
test_rejects_malformed();
|
||||
test_free_is_safe();
|
||||
return 0;
|
||||
}
|
||||
Reference in new issue
Block a user