From f0afe6fe3e1deb02c904cac1e1f4a131d62ff29e Mon Sep 17 00:00:00 2001 From: Paragon Initiative Enterprises Date: Wed, 30 Mar 2022 03:45:47 -0400 Subject: [PATCH] Add sodium_crypto_stream_xchacha20_xor_ic() There are many use-cases where a PHP user is currently using sodium_compat's implementation of this low-level XChaCha20 API. For example, multi-part message processing (in low-memory settings) for a ciphertext that was encrypted with XChaCha20-Poly1305 (rather than the secretstream API). Adding this function to ext/sodium offers better performance and lowers users' memory usage with the polyfill, and ensures that users coming from other languages that provide libsodium bindings have a more consistent experience with our bindings. This is a win-win. This patch follows the libsodium precedent of adding functions instead of optional parameters to existing functions. The parameter order is also consistent with the C API. https://doc.libsodium.org/advanced/stream_ciphers/xchacha20#usage --- ext/sodium/libsodium.c | 43 +++++++++++++++++++ ext/sodium/libsodium.stub.php | 2 + ext/sodium/libsodium_arginfo.h | 15 +++++++ ext/sodium/tests/crypto_stream_xchacha20.phpt | 18 ++++++++ 4 files changed, 78 insertions(+) diff --git a/ext/sodium/libsodium.c b/ext/sodium/libsodium.c index 53d7f116652de..54d687cd736c0 100644 --- a/ext/sodium/libsodium.c +++ b/ext/sodium/libsodium.c @@ -1578,6 +1578,49 @@ PHP_FUNCTION(sodium_crypto_stream_xchacha20_xor) RETURN_NEW_STR(ciphertext); } + +PHP_FUNCTION(sodium_crypto_stream_xchacha20_xor_ic) +{ + zend_string *ciphertext; + unsigned char *key; + unsigned char *msg; + unsigned char *nonce; + zend_long *ic; + + size_t ciphertext_len; + size_t key_len; + size_t msg_len; + size_t nonce_len; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "ssls", + &msg, &msg_len, + &nonce, &nonce_len, + &ic, + &key, &key_len) == FAILURE) { + sodium_remove_param_values_from_backtrace(EG(exception)); + RETURN_THROWS(); + } + if (nonce_len != crypto_stream_xchacha20_NONCEBYTES) { + zend_argument_error(sodium_exception_ce, 2, "must be SODIUM_CRYPTO_STREAM_XCHACHA20_NONCEBYTES bytes long"); + RETURN_THROWS(); + } + if (key_len != crypto_stream_xchacha20_KEYBYTES) { + zend_argument_error(sodium_exception_ce, 3, "must be SODIUM_CRYPTO_STREAM_XCHACHA20_KEYBYTES bytes long"); + RETURN_THROWS(); + } + ciphertext_len = msg_len; + ciphertext = zend_string_checked_alloc((size_t) ciphertext_len, 0); + if (crypto_stream_xchacha20_xor_ic((unsigned char *) ZSTR_VAL(ciphertext), msg, + (unsigned long long) msg_len, nonce, + (unsigned long long) ic, key) != 0) { + zend_string_free(ciphertext); + zend_throw_exception(sodium_exception_ce, "internal error", 0); + RETURN_THROWS(); + } + ZSTR_VAL(ciphertext)[ciphertext_len] = 0; + + RETURN_NEW_STR(ciphertext); +} #endif #ifdef crypto_pwhash_SALTBYTES diff --git a/ext/sodium/libsodium.stub.php b/ext/sodium/libsodium.stub.php index af859c58fe963..55d3e0a5161bc 100644 --- a/ext/sodium/libsodium.stub.php +++ b/ext/sodium/libsodium.stub.php @@ -205,6 +205,8 @@ function sodium_crypto_stream_xchacha20(int $length, string $nonce, string $key) function sodium_crypto_stream_xchacha20_keygen(): string {} function sodium_crypto_stream_xchacha20_xor(string $message, string $nonce, string $key): string {} + +function sodium_crypto_stream_xchacha20_xor_ic(string $message, string $nonce, int $counter, string $key): string {} #endif function sodium_add(string &$string1, string $string2): void {} diff --git a/ext/sodium/libsodium_arginfo.h b/ext/sodium/libsodium_arginfo.h index f0e7aed85e258..a45bd5f59ea03 100644 --- a/ext/sodium/libsodium_arginfo.h +++ b/ext/sodium/libsodium_arginfo.h @@ -445,6 +445,15 @@ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_sodium_crypto_stream_xchacha20_x ZEND_END_ARG_INFO() #endif +#if defined(crypto_stream_xchacha20_KEYBYTES) +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_sodium_crypto_stream_xchacha20_xor_ic, 0, 4, IS_STRING, 0) + ZEND_ARG_TYPE_INFO(0, message, IS_STRING, 0) + ZEND_ARG_TYPE_INFO(0, nonce, IS_STRING, 0) + ZEND_ARG_TYPE_INFO(0, counter, IS_LONG, 0) + ZEND_ARG_TYPE_INFO(0, key, IS_STRING, 0) +ZEND_END_ARG_INFO() +#endif + ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_sodium_add, 0, 2, IS_VOID, 0) ZEND_ARG_TYPE_INFO(1, string1, IS_STRING, 0) ZEND_ARG_TYPE_INFO(0, string2, IS_STRING, 0) @@ -662,6 +671,9 @@ ZEND_FUNCTION(sodium_crypto_stream_xchacha20_keygen); #if defined(crypto_stream_xchacha20_KEYBYTES) ZEND_FUNCTION(sodium_crypto_stream_xchacha20_xor); #endif +#if defined(crypto_stream_xchacha20_KEYBYTES) +ZEND_FUNCTION(sodium_crypto_stream_xchacha20_xor_ic); +#endif ZEND_FUNCTION(sodium_add); ZEND_FUNCTION(sodium_compare); ZEND_FUNCTION(sodium_increment); @@ -844,6 +856,9 @@ static const zend_function_entry ext_functions[] = { #endif #if defined(crypto_stream_xchacha20_KEYBYTES) ZEND_FE(sodium_crypto_stream_xchacha20_xor, arginfo_sodium_crypto_stream_xchacha20_xor) +#endif +#if defined(crypto_stream_xchacha20_KEYBYTES) + ZEND_FE(sodium_crypto_stream_xchacha20_xor_ic, arginfo_sodium_crypto_stream_xchacha20_xor_ic) #endif ZEND_FE(sodium_add, arginfo_sodium_add) ZEND_FE(sodium_compare, arginfo_sodium_compare) diff --git a/ext/sodium/tests/crypto_stream_xchacha20.phpt b/ext/sodium/tests/crypto_stream_xchacha20.phpt index 275e195a29281..466734853d705 100644 --- a/ext/sodium/tests/crypto_stream_xchacha20.phpt +++ b/ext/sodium/tests/crypto_stream_xchacha20.phpt @@ -34,6 +34,22 @@ $stream6 = sodium_crypto_stream_xchacha20_xor($stream5, $nonce, $key); var_dump($stream6 === $stream); +// New test (with Initial Counter feature): +$n2 = random_bytes(SODIUM_CRYPTO_STREAM_XCHACHA20_NONCEBYTES); +$left = str_repeat("\x01", 64); +$right = str_repeat("\xfe", 64); + +// All at once: +$stream7_unified = sodium_crypto_stream_xchacha20_xor($left . $right, $n2, $key); + +// Piecewise, with initial counter: +$stream7_left = sodium_crypto_stream_xchacha20_xor_ic($left, $n2, 0, $key); +$stream7_right = sodium_crypto_stream_xchacha20_xor_ic($right, $n2, 1, $key); +$stream7_concat = $stream7_left . $stream7_right; + +var_dump(strlen($stream7_concat)); +var_dump($stream7_unified === $stream7_concat); + try { sodium_crypto_stream_xchacha20(-1, $nonce, $key); } catch (SodiumException $ex) { @@ -71,6 +87,8 @@ bool(true) bool(true) bool(true) bool(true) +int(128) +bool(true) sodium_crypto_stream_xchacha20(): Argument #1 ($length) must be greater than 0 sodium_crypto_stream_xchacha20(): Argument #2 ($nonce) must be SODIUM_CRYPTO_STREAM_XCHACHA20_NONCEBYTES bytes long sodium_crypto_stream_xchacha20(): Argument #3 ($key) must be SODIUM_CRYPTO_STREAM_XCHACHA20_KEYBYTES bytes long