Skip to content

Commit f7f1f7f

Browse files
Add crypto_stream_xchacha20 to ext/sodium (#6868)
Paragon Initiative Enterprises is aware of PHP applications that use sodium_compat's ParagonIE\Sodium\Core\XChaCha20 class directly for stream encryption. Greater performance and security assurance is offered by exposing libsodium's crypto_stream_xchacha20 API to PHP users. It's acceptable to only include this change in PHP 8.1+; the offending applications are more than welcome to either install ext/sodium from PECL or upgrade to 8.1 when it comes out later this year. Ref: jedisct1/libsodium-php#211
1 parent 8bb8122 commit f7f1f7f

File tree

5 files changed

+216
-1
lines changed

5 files changed

+216
-1
lines changed

ext/sodium/libsodium.c

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,19 @@ static zend_class_entry *sodium_exception_ce;
3737
# define HAVE_AESGCM 1
3838
#endif
3939

40+
static zend_always_inline zend_string *zend_string_checked_alloc(size_t len, int persistent)
41+
{
42+
zend_string *zs;
43+
44+
if (ZEND_MM_ALIGNED_SIZE(_ZSTR_STRUCT_SIZE(len)) < len) {
45+
zend_error_noreturn(E_ERROR, "Memory allocation too large (%zu bytes)", len);
46+
}
47+
zs = zend_string_alloc(len, persistent);
48+
ZSTR_VAL(zs)[len] = 0;
49+
50+
return zs;
51+
}
52+
4053
#include "libsodium_arginfo.h"
4154

4255
#ifndef crypto_aead_chacha20poly1305_IETF_KEYBYTES
@@ -335,6 +348,13 @@ PHP_MINIT_FUNCTION(sodium)
335348
crypto_stream_NONCEBYTES, CONST_CS | CONST_PERSISTENT);
336349
REGISTER_LONG_CONSTANT("SODIUM_CRYPTO_STREAM_KEYBYTES",
337350
crypto_stream_KEYBYTES, CONST_CS | CONST_PERSISTENT);
351+
352+
#ifdef crypto_stream_xchacha20_KEYBYTES
353+
REGISTER_LONG_CONSTANT("SODIUM_CRYPTO_STREAM_XCHACHA20_NONCEBYTES",
354+
crypto_stream_xchacha20_NONCEBYTES, CONST_CS | CONST_PERSISTENT);
355+
REGISTER_LONG_CONSTANT("SODIUM_CRYPTO_STREAM_XCHACHA20_KEYBYTES",
356+
crypto_stream_xchacha20_KEYBYTES, CONST_CS | CONST_PERSISTENT);
357+
#endif
338358
#ifdef sodium_base64_VARIANT_ORIGINAL
339359
REGISTER_LONG_CONSTANT("SODIUM_BASE64_VARIANT_ORIGINAL",
340360
sodium_base64_VARIANT_ORIGINAL, CONST_CS | CONST_PERSISTENT);
@@ -1465,6 +1485,87 @@ PHP_FUNCTION(sodium_crypto_stream_xor)
14651485
RETURN_NEW_STR(ciphertext);
14661486
}
14671487

1488+
#ifdef crypto_stream_xchacha20_KEYBYTES
1489+
PHP_FUNCTION(sodium_crypto_stream_xchacha20)
1490+
{
1491+
zend_string *ciphertext;
1492+
unsigned char *key;
1493+
unsigned char *nonce;
1494+
zend_long ciphertext_len;
1495+
size_t key_len;
1496+
size_t nonce_len;
1497+
1498+
if (zend_parse_parameters(ZEND_NUM_ARGS(), "lss",
1499+
&ciphertext_len,
1500+
&nonce, &nonce_len,
1501+
&key, &key_len) == FAILURE) {
1502+
sodium_remove_param_values_from_backtrace(EG(exception));
1503+
RETURN_THROWS();
1504+
}
1505+
if (ciphertext_len <= 0 || ciphertext_len >= SIZE_MAX) {
1506+
zend_argument_error(sodium_exception_ce, 1, "length must be greater than 0");
1507+
RETURN_THROWS();
1508+
}
1509+
if (nonce_len != crypto_stream_xchacha20_NONCEBYTES) {
1510+
zend_argument_error(sodium_exception_ce, 2, "nonce must be SODIUM_CRYPTO_STREAM_XCHACHA20_NONCEBYTES bytes long");
1511+
RETURN_THROWS();
1512+
}
1513+
if (key_len != crypto_stream_xchacha20_KEYBYTES) {
1514+
zend_argument_error(sodium_exception_ce, 3, "key must be SODIUM_CRYPTO_STREAM_XCHACHA20_KEYBYTES bytes long");
1515+
RETURN_THROWS();
1516+
}
1517+
ciphertext = zend_string_checked_alloc((size_t) ciphertext_len, 0);
1518+
if (crypto_stream_xchacha20((unsigned char *) ZSTR_VAL(ciphertext),
1519+
(unsigned long long) ciphertext_len, nonce, key) != 0) {
1520+
zend_string_free(ciphertext);
1521+
zend_throw_exception(sodium_exception_ce, "internal error", 0);
1522+
RETURN_THROWS();
1523+
}
1524+
ZSTR_VAL(ciphertext)[ciphertext_len] = 0;
1525+
1526+
RETURN_NEW_STR(ciphertext);
1527+
}
1528+
1529+
PHP_FUNCTION(sodium_crypto_stream_xchacha20_xor)
1530+
{
1531+
zend_string *ciphertext;
1532+
unsigned char *key;
1533+
unsigned char *msg;
1534+
unsigned char *nonce;
1535+
size_t ciphertext_len;
1536+
size_t key_len;
1537+
size_t msg_len;
1538+
size_t nonce_len;
1539+
1540+
if (zend_parse_parameters(ZEND_NUM_ARGS(), "sss",
1541+
&msg, &msg_len,
1542+
&nonce, &nonce_len,
1543+
&key, &key_len) == FAILURE) {
1544+
sodium_remove_param_values_from_backtrace(EG(exception));
1545+
RETURN_THROWS();
1546+
}
1547+
if (nonce_len != crypto_stream_xchacha20_NONCEBYTES) {
1548+
zend_argument_error(sodium_exception_ce, 2, "nonce must be SODIUM_CRYPTO_STREAM_XCHACHA20_NONCEBYTES bytes long");
1549+
RETURN_THROWS();
1550+
}
1551+
if (key_len != crypto_stream_xchacha20_KEYBYTES) {
1552+
zend_argument_error(sodium_exception_ce, 3, "key must be SODIUM_CRYPTO_STREAM_XCHACHA20_KEYBYTES bytes long");
1553+
RETURN_THROWS();
1554+
}
1555+
ciphertext_len = msg_len;
1556+
ciphertext = zend_string_checked_alloc((size_t) ciphertext_len, 0);
1557+
if (crypto_stream_xchacha20_xor((unsigned char *) ZSTR_VAL(ciphertext), msg,
1558+
(unsigned long long) msg_len, nonce, key) != 0) {
1559+
zend_string_free(ciphertext);
1560+
zend_throw_exception(sodium_exception_ce, "internal error", 0);
1561+
RETURN_THROWS();
1562+
}
1563+
ZSTR_VAL(ciphertext)[ciphertext_len] = 0;
1564+
1565+
RETURN_NEW_STR(ciphertext);
1566+
}
1567+
#endif
1568+
14681569
#ifdef crypto_pwhash_SALTBYTES
14691570
PHP_FUNCTION(sodium_crypto_pwhash)
14701571
{
@@ -2894,6 +2995,18 @@ PHP_FUNCTION(sodium_crypto_stream_keygen)
28942995
randombytes_buf(key, sizeof key);
28952996
RETURN_STRINGL((const char *) key, sizeof key);
28962997
}
2998+
#ifdef crypto_stream_xchacha20_KEYBYTES
2999+
PHP_FUNCTION(sodium_crypto_stream_xchacha20_keygen)
3000+
{
3001+
unsigned char key[crypto_stream_xchacha20_KEYBYTES];
3002+
3003+
if (zend_parse_parameters_none() == FAILURE) {
3004+
return;
3005+
}
3006+
randombytes_buf(key, sizeof key);
3007+
RETURN_STRINGL((const char *) key, sizeof key);
3008+
}
3009+
#endif
28973010

28983011
PHP_FUNCTION(sodium_crypto_kdf_derive_from_key)
28993012
{

ext/sodium/libsodium.stub.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,14 @@ function sodium_crypto_stream_keygen(): string {}
160160

161161
function sodium_crypto_stream_xor(string $message, string $nonce, string $key): string {}
162162

163+
#if defined(crypto_stream_xchacha20_KEYBYTES)
164+
function sodium_crypto_stream_xchacha20(int $length, string $nonce, string $key) : string {}
165+
166+
function sodium_crypto_stream_xchacha20_keygen() : string {}
167+
168+
function sodium_crypto_stream_xchacha20_xor(string $message, string $nonce, string $key) : string {}
169+
#endif
170+
163171
function sodium_add(string &$string1, string $string2): void {}
164172

165173
function sodium_compare(string $string1, string $string2): int {}

ext/sodium/libsodium_arginfo.h

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/* This is a generated file, edit the .stub.php file instead.
2-
* Stub hash: 457c4c5a0243f815d859bdc9728709b4a8dc84d7 */
2+
* Stub hash: 55ce0e93db5fac4311ba90693668a92001167573 */
33

44
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_sodium_crypto_aead_aes256gcm_is_available, 0, 0, _IS_BOOL, 0)
55
ZEND_END_ARG_INFO()
@@ -348,6 +348,27 @@ ZEND_END_ARG_INFO()
348348

349349
#define arginfo_sodium_crypto_stream_xor arginfo_sodium_crypto_secretbox
350350

351+
#if defined(crypto_stream_xchacha20_KEYBYTES)
352+
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_sodium_crypto_stream_xchacha20, 0, 3, IS_STRING, 0)
353+
ZEND_ARG_TYPE_INFO(0, length, IS_LONG, 0)
354+
ZEND_ARG_TYPE_INFO(0, nonce, IS_STRING, 0)
355+
ZEND_ARG_TYPE_INFO(0, key, IS_STRING, 0)
356+
ZEND_END_ARG_INFO()
357+
#endif
358+
359+
#if defined(crypto_stream_xchacha20_KEYBYTES)
360+
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_sodium_crypto_stream_xchacha20_keygen, 0, 0, IS_STRING, 0)
361+
ZEND_END_ARG_INFO()
362+
#endif
363+
364+
#if defined(crypto_stream_xchacha20_KEYBYTES)
365+
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_sodium_crypto_stream_xchacha20_xor, 0, 3, IS_STRING, 0)
366+
ZEND_ARG_TYPE_INFO(0, message, IS_STRING, 0)
367+
ZEND_ARG_TYPE_INFO(0, nonce, IS_STRING, 0)
368+
ZEND_ARG_TYPE_INFO(0, key, IS_STRING, 0)
369+
ZEND_END_ARG_INFO()
370+
#endif
371+
351372
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_sodium_add, 0, 2, IS_VOID, 0)
352373
ZEND_ARG_TYPE_INFO(1, string1, IS_STRING, 0)
353374
ZEND_ARG_TYPE_INFO(0, string2, IS_STRING, 0)
@@ -514,6 +535,15 @@ ZEND_FUNCTION(sodium_crypto_sign_verify_detached);
514535
ZEND_FUNCTION(sodium_crypto_stream);
515536
ZEND_FUNCTION(sodium_crypto_stream_keygen);
516537
ZEND_FUNCTION(sodium_crypto_stream_xor);
538+
#if defined(crypto_stream_xchacha20_KEYBYTES)
539+
ZEND_FUNCTION(sodium_crypto_stream_xchacha20);
540+
#endif
541+
#if defined(crypto_stream_xchacha20_KEYBYTES)
542+
ZEND_FUNCTION(sodium_crypto_stream_xchacha20_keygen);
543+
#endif
544+
#if defined(crypto_stream_xchacha20_KEYBYTES)
545+
ZEND_FUNCTION(sodium_crypto_stream_xchacha20_xor);
546+
#endif
517547
ZEND_FUNCTION(sodium_add);
518548
ZEND_FUNCTION(sodium_compare);
519549
ZEND_FUNCTION(sodium_increment);
@@ -643,6 +673,15 @@ static const zend_function_entry ext_functions[] = {
643673
ZEND_FE(sodium_crypto_stream, arginfo_sodium_crypto_stream)
644674
ZEND_FE(sodium_crypto_stream_keygen, arginfo_sodium_crypto_stream_keygen)
645675
ZEND_FE(sodium_crypto_stream_xor, arginfo_sodium_crypto_stream_xor)
676+
#if defined(crypto_stream_xchacha20_KEYBYTES)
677+
ZEND_FE(sodium_crypto_stream_xchacha20, arginfo_sodium_crypto_stream_xchacha20)
678+
#endif
679+
#if defined(crypto_stream_xchacha20_KEYBYTES)
680+
ZEND_FE(sodium_crypto_stream_xchacha20_keygen, arginfo_sodium_crypto_stream_xchacha20_keygen)
681+
#endif
682+
#if defined(crypto_stream_xchacha20_KEYBYTES)
683+
ZEND_FE(sodium_crypto_stream_xchacha20_xor, arginfo_sodium_crypto_stream_xchacha20_xor)
684+
#endif
646685
ZEND_FE(sodium_add, arginfo_sodium_add)
647686
ZEND_FE(sodium_compare, arginfo_sodium_compare)
648687
ZEND_FE(sodium_increment, arginfo_sodium_increment)

ext/sodium/php_libsodium.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,9 @@ PHP_FUNCTION(sodium_crypto_sign_verify_detached);
112112
PHP_FUNCTION(sodium_crypto_stream);
113113
PHP_FUNCTION(sodium_crypto_stream_keygen);
114114
PHP_FUNCTION(sodium_crypto_stream_xor);
115+
PHP_FUNCTION(sodium_crypto_stream_xchacha20);
116+
PHP_FUNCTION(sodium_crypto_stream_xchacha20_keygen);
117+
PHP_FUNCTION(sodium_crypto_stream_xchacha20_xor);
115118
PHP_FUNCTION(sodium_hex2bin);
116119
PHP_FUNCTION(sodium_increment);
117120
PHP_FUNCTION(sodium_memcmp);
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
--TEST--
2+
Check for libsodium stream
3+
--SKIPIF--
4+
<?php if (!extension_loaded("sodium") || !defined('CRYPTO_STREAM_XCHACHA20_KEYBYTES')) print "skip"; ?>
5+
--FILE--
6+
<?php
7+
$nonce = random_bytes(SODIUM_CRYPTO_STREAM_XCHACHA20_NONCEBYTES);
8+
$key = sodium_crypto_stream_xchacha20_keygen();
9+
10+
$len = 100;
11+
$stream = sodium_crypto_stream_xchacha20($len, $nonce, $key);
12+
var_dump(strlen($stream));
13+
14+
$stream2 = sodium_crypto_stream_xchacha20($len, $nonce, $key);
15+
16+
$nonce = random_bytes(SODIUM_CRYPTO_STREAM_XCHACHA20_NONCEBYTES);
17+
$stream3 = sodium_crypto_stream_xchacha20($len, $nonce, $key);
18+
19+
$key = sodium_crypto_stream_keygen();
20+
$stream4 = sodium_crypto_stream_xchacha20($len, $nonce, $key);
21+
22+
var_dump($stream === $stream2);
23+
var_dump($stream !== $stream3);
24+
var_dump($stream !== $stream4);
25+
var_dump($stream2 !== $stream3);
26+
var_dump($stream2 !== $stream4);
27+
var_dump($stream3 !== $stream4);
28+
29+
$stream5 = sodium_crypto_stream_xchacha20_xor($stream, $nonce, $key);
30+
var_dump($stream5 !== $stream);
31+
$stream6 = sodium_crypto_stream_xchacha20_xor($stream5, $nonce, $key);
32+
33+
var_dump($stream6 === $stream);
34+
35+
try {
36+
sodium_crypto_stream_xchacha20($len, substr($nonce, 1), $key);
37+
} catch (SodiumException $ex) {
38+
var_dump(true);
39+
}
40+
41+
?>
42+
--EXPECT--
43+
int(100)
44+
bool(true)
45+
bool(true)
46+
bool(true)
47+
bool(true)
48+
bool(true)
49+
bool(true)
50+
bool(true)
51+
bool(true)
52+
bool(true)

0 commit comments

Comments
 (0)