Skip to content

Commit 5c6de2b

Browse files
authored
Add support for libmemcached encryption (#381)
This feature is fully supported in libmemcached 1.0.18 and higher, but has a notable bug in the original implementation in libmemcached versions 1.0.6 - 1.0.17 that the encryption key, once set, cannot be changed otherwise all further get/set operations fail.
1 parent e622992 commit 5c6de2b

6 files changed

+177
-0
lines changed

Diff for: config.m4

+19
Original file line numberDiff line numberDiff line change
@@ -317,10 +317,29 @@ if test "$PHP_MEMCACHED" != "no"; then
317317
CFLAGS="$ORIG_CFLAGS"
318318
LIBS="$ORIG_LIBS"
319319

320+
CFLAGS="$CFLAGS $PHP_LIBMEMCACHED_INCLUDES"
321+
LIBS="$LIBS $PHP_LIBMEMCACHED_LIBS"
322+
320323
if test "$ac_cv_have_memcached_exist" = "yes"; then
321324
AC_DEFINE(HAVE_MEMCACHED_EXIST, [1], [Whether memcached_exist is defined])
322325
fi
323326

327+
AC_CACHE_CHECK([whether memcached_set_encoding_key is defined], ac_cv_have_memcached_set_encoding_key, [
328+
AC_TRY_LINK(
329+
[ #include <libmemcached/memcached.h> ],
330+
[ memcached_set_encoding_key (NULL, NULL, 0); ],
331+
[ ac_cv_have_memcached_set_encoding_key="yes" ],
332+
[ ac_cv_have_memcached_set_encoding_key="no" ]
333+
)
334+
])
335+
336+
CFLAGS="$ORIG_CFLAGS"
337+
LIBS="$ORIG_LIBS"
338+
339+
if test "$ac_cv_have_memcached_set_encoding_key" = "yes"; then
340+
AC_DEFINE(HAVE_MEMCACHED_SET_ENCODING_KEY, [1], [Whether memcached_set_encoding_key is defined])
341+
fi
342+
324343
PHP_MEMCACHED_FILES="php_memcached.c php_libmemcached_compat.c g_fmt.c"
325344

326345
if test "$PHP_SYSTEM_FASTLZ" != "no"; then

Diff for: memcached-api.php

+4
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,8 @@ class Memcached {
9696

9797
const HAVE_MSGPACK;
9898

99+
const HAVE_ENCODING;
100+
99101
/**
100102
* Feature support
101103
*/
@@ -363,6 +365,8 @@ public function isPristine( ) {}
363365

364366
public function setSaslAuthData( $username, $password ) {}
365367

368+
public function setEncodingKey( $key ) {}
369+
366370
}
367371

368372
class MemcachedException extends Exception {

Diff for: package.xml

+2
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,8 @@ Fixes
159159
<file role='test' name='testdata.res'/>
160160
<file role='test' name='config.inc'/>
161161
<file role='test' name='sasl_basic.phpt'/>
162+
<file role='test' name='set_encoding_key.phpt'/>
163+
<file role='test' name='set_encoding_key2.phpt'/>
162164
<file role='test' name='getserverbykey.phpt'/>
163165
<file role='test' name='gh_155.phpt'/>
164166
<file role='test' name='get_flags.phpt'/>

Diff for: php_memcached.c

+56
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,7 @@ typedef struct {
142142

143143
zend_bool is_persistent;
144144
zend_bool compression_enabled;
145+
zend_bool encoding_enabled;
145146

146147
zend_long serializer;
147148
zend_long compression_type;
@@ -1227,6 +1228,7 @@ static PHP_METHOD(Memcached, __construct)
12271228
memc_user_data->serializer = MEMC_G(serializer_type);
12281229
memc_user_data->compression_type = MEMC_G(compression_type);
12291230
memc_user_data->compression_enabled = 1;
1231+
memc_user_data->encoding_enabled = 0;
12301232
memc_user_data->store_retry_count = MEMC_G(store_retry_count);
12311233
memc_user_data->set_udf_flags = -1;
12321234
memc_user_data->is_persistent = is_persistent;
@@ -3265,6 +3267,42 @@ static PHP_METHOD(Memcached, setSaslAuthData)
32653267
/* }}} */
32663268
#endif /* HAVE_MEMCACHED_SASL */
32673269

3270+
#ifdef HAVE_MEMCACHED_SET_ENCODING_KEY
3271+
/* {{{ Memcached::setEncodingKey(string key)
3272+
Sets AES encryption key (libmemcached 1.0.6 and higher) */
3273+
static PHP_METHOD(Memcached, setEncodingKey)
3274+
{
3275+
MEMC_METHOD_INIT_VARS;
3276+
memcached_return status;
3277+
zend_string *key;
3278+
3279+
/* "S" */
3280+
ZEND_PARSE_PARAMETERS_START(1, 1)
3281+
Z_PARAM_STR(key)
3282+
ZEND_PARSE_PARAMETERS_END();
3283+
3284+
MEMC_METHOD_FETCH_OBJECT;
3285+
3286+
// libmemcached < 1.0.18 cannot handle a change of encoding key. Warn about this and return false.
3287+
#if defined(LIBMEMCACHED_VERSION_HEX) && LIBMEMCACHED_VERSION_HEX < 0x01000018
3288+
if (memc_user_data->encoding_enabled) {
3289+
php_error_docref(NULL, E_WARNING, "libmemcached versions less than 1.0.18 cannot change encoding key");
3290+
RETURN_FALSE;
3291+
}
3292+
#endif
3293+
3294+
status = memcached_set_encoding_key(intern->memc, ZSTR_VAL(key), ZSTR_LEN(key));
3295+
3296+
if (s_memc_status_handle_result_code(intern, status) == FAILURE) {
3297+
RETURN_FALSE;
3298+
}
3299+
3300+
memc_user_data->encoding_enabled = 1;
3301+
RETURN_TRUE;
3302+
}
3303+
/* }}} */
3304+
#endif /* HAVE_MEMCACHED_SET_ENCODING_KEY */
3305+
32683306
/* {{{ Memcached::getResultCode()
32693307
Returns the result code from the last operation */
32703308
static PHP_METHOD(Memcached, getResultCode)
@@ -4034,6 +4072,12 @@ ZEND_BEGIN_ARG_INFO(arginfo_setSaslAuthData, 0)
40344072
ZEND_END_ARG_INFO()
40354073
#endif
40364074

4075+
#ifdef HAVE_MEMCACHED_SET_ENCODING_KEY
4076+
ZEND_BEGIN_ARG_INFO(arginfo_setEncodingKey, 0)
4077+
ZEND_ARG_INFO(0, key)
4078+
ZEND_END_ARG_INFO()
4079+
#endif
4080+
40374081
ZEND_BEGIN_ARG_INFO(arginfo_setOption, 0)
40384082
ZEND_ARG_INFO(0, option)
40394083
ZEND_ARG_INFO(0, value)
@@ -4133,6 +4177,9 @@ static zend_function_entry memcached_class_methods[] = {
41334177
MEMC_ME(setBucket, arginfo_setBucket)
41344178
#ifdef HAVE_MEMCACHED_SASL
41354179
MEMC_ME(setSaslAuthData, arginfo_setSaslAuthData)
4180+
#endif
4181+
#ifdef HAVE_MEMCACHED_SET_ENCODING_KEY
4182+
MEMC_ME(setEncodingKey, arginfo_setEncodingKey)
41364183
#endif
41374184
MEMC_ME(isPersistent, arginfo_isPersistent)
41384185
MEMC_ME(isPristine, arginfo_isPristine)
@@ -4282,6 +4329,15 @@ static void php_memc_register_constants(INIT_FUNC_ARGS)
42824329
REGISTER_MEMC_CLASS_CONST_BOOL(HAVE_MSGPACK, 0);
42834330
#endif
42844331

4332+
/*
4333+
* Indicate whether set_encoding_key is available
4334+
*/
4335+
#ifdef HAVE_MEMCACHED_SET_ENCODING_KEY
4336+
REGISTER_MEMC_CLASS_CONST_BOOL(HAVE_ENCODING, 1);
4337+
#else
4338+
REGISTER_MEMC_CLASS_CONST_BOOL(HAVE_ENCODING, 0);
4339+
#endif
4340+
42854341
#ifdef HAVE_MEMCACHED_SESSION
42864342
REGISTER_MEMC_CLASS_CONST_BOOL(HAVE_SESSION, 1);
42874343
#else

Diff for: tests/set_encoding_key.phpt

+59
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
--TEST--
2+
Test libmemcached encryption
3+
--SKIPIF--
4+
<?php
5+
include dirname (__FILE__) . '/config.inc';
6+
if (!extension_loaded("memcached")) die ("skip");
7+
if (!Memcached::HAVE_ENCODING) die ("skip no set_encoding_key support enabled");
8+
if (Memcached::LIBMEMCACHED_VERSION_HEX < 0x01000018) die ("skip test for libmemcached 1.0.18 and higher");
9+
?>
10+
--FILE--
11+
<?php
12+
include dirname (__FILE__) . '/config.inc';
13+
$m = memc_get_instance ();
14+
$key = uniqid ('encoding_test_');
15+
16+
var_dump ($m->setEncodingKey("Hello"));
17+
var_dump ($m->set ($key, 'set using encoding'));
18+
var_dump ($m->get ($key));
19+
20+
echo "OK" . PHP_EOL;
21+
22+
# Change the encryption key. The old value will be inaccessible.
23+
var_dump ($m->setEncodingKey("World"));
24+
var_dump ($m->get ($key));
25+
26+
echo "OK" . PHP_EOL;
27+
28+
# Restore the original key to retrieve old values again.
29+
var_dump ($m->setEncodingKey("Hello"));
30+
var_dump ($m->get ($key));
31+
32+
echo "OK" . PHP_EOL;
33+
34+
# With a new encoding key we can still write new values,
35+
# this works as expected with libmemcached 1.0.18 and higher.
36+
var_dump ($m->setEncodingKey("World"));
37+
var_dump ($m->get ($key));
38+
var_dump ($m->set ($key, 'set using encoding'));
39+
var_dump ($m->get ($key));
40+
41+
echo "OK" . PHP_EOL;
42+
43+
?>
44+
--EXPECT--
45+
bool(true)
46+
bool(true)
47+
string(18) "set using encoding"
48+
OK
49+
bool(true)
50+
bool(false)
51+
OK
52+
bool(true)
53+
string(18) "set using encoding"
54+
OK
55+
bool(true)
56+
bool(false)
57+
bool(true)
58+
string(18) "set using encoding"
59+
OK

Diff for: tests/set_encoding_key2.phpt

+37
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
--TEST--
2+
Test libmemcached encryption
3+
--SKIPIF--
4+
<?php
5+
include dirname (__FILE__) . '/config.inc';
6+
if (!extension_loaded("memcached")) die ("skip");
7+
if (!Memcached::HAVE_ENCODING) die ("skip no set_encoding_key support enabled");
8+
if (Memcached::LIBMEMCACHED_VERSION_HEX >= 0x01000018) die ("skip test for libmemcached lower than 1.0.18");
9+
?>
10+
--FILE--
11+
<?php
12+
include dirname (__FILE__) . '/config.inc';
13+
$m = memc_get_instance ();
14+
$key = uniqid ('encoding_test_');
15+
16+
var_dump ($m->setEncodingKey("Hello"));
17+
var_dump ($m->set ($key, 'set using encoding'));
18+
var_dump ($m->get ($key));
19+
20+
echo "OK" . PHP_EOL;
21+
22+
# libmemcached < 1.0.18 goes into a bad state when the encoding key is changed,
23+
# so php-memcached warns and returns false when trying to change the key.
24+
var_dump ($m->setEncodingKey("World"));
25+
26+
echo "OK" . PHP_EOL;
27+
28+
?>
29+
--EXPECTF--
30+
bool(true)
31+
bool(true)
32+
string(18) "set using encoding"
33+
OK
34+
35+
Warning: Memcached::setEncodingKey(): libmemcached versions less than 1.0.18 cannot change encoding key in %s on line %d
36+
bool(false)
37+
OK

0 commit comments

Comments
 (0)