#include "php_snuffleupagus.h" void generate_key(unsigned char *key) { PHP_SHA256_CTX ctx; const char *user_agent = getenv("HTTP_USER_AGENT"); const zend_string *env_var_zend = SPCFG(cookies_env_var); const zend_string *encryption_key_zend = SPCFG(encryption_key); const char *env_var = (env_var_zend ? getenv(ZSTR_VAL(env_var_zend)) : NULL); const char *encryption_key = (encryption_key_zend ? ZSTR_VAL(encryption_key_zend) : NULL); assert(32 == crypto_secretbox_KEYBYTES); // 32 is the size of a SHA256. assert(encryption_key); // Encryption key can't be NULL PHP_SHA256Init(&ctx); if (user_agent) { PHP_SHA256Update(&ctx, (const unsigned char *)user_agent, strlen(user_agent)); } if (env_var) { PHP_SHA256Update(&ctx, (const unsigned char *)env_var, strlen(env_var)); } else { sp_log_warn("cookie_encryption", "The environment variable '%s' " "is empty, cookies are weakly encrypted", ZSTR_VAL(env_var_zend)); } if (encryption_key) { PHP_SHA256Update(&ctx, (const unsigned char *)encryption_key, strlen(encryption_key)); } PHP_SHA256Final((unsigned char *)key, &ctx); } // This function return 0 upon success , non-zero otherwise int decrypt_zval(zval *pDest, bool simulation, zend_hash_key *hash_key) { unsigned char key[crypto_secretbox_KEYBYTES] = {0}; unsigned char *decrypted = NULL, *backup = NULL; int ret = 0; zend_string *debase64 = php_base64_decode((unsigned char *)(Z_STRVAL_P(pDest)), Z_STRLEN_P(pDest)); if (ZSTR_LEN(debase64) < crypto_secretbox_NONCEBYTES) { if (true == simulation) { sp_log_simulation( "cookie_encryption", "Buffer underflow tentative detected in cookie encryption handling " "for %s. Using the cookie 'as is' instead of decrypting it", hash_key ? ZSTR_VAL(hash_key->key) : "the session"); ret = ZEND_HASH_APPLY_KEEP; goto out; } else { // LCOV_EXCL_START sp_log_drop( "cookie_encryption", "Buffer underflow (tentative) detected in cookie encryption handling"); ret = ZEND_HASH_APPLY_REMOVE; goto out; // LCOV_EXCL_STOP } } // LCOV_EXCL_START if (ZSTR_LEN(debase64) + (size_t)crypto_secretbox_ZEROBYTES < ZSTR_LEN(debase64)) { if (true == simulation) { sp_log_simulation( "cookie_encryption", "Integer overflow (tentative) detected in cookie encryption handling " "for %s. Using the cookie 'as it' instead of decrypting it.", hash_key ? ZSTR_VAL(hash_key->key) : "the session"); ret = ZEND_HASH_APPLY_KEEP; goto out; } else { sp_log_drop( "cookie_encryption", "Integer overflow (tentative) detected in cookie encryption handling."); ret = ZEND_HASH_APPLY_REMOVE; goto out; } } // LCOV_EXCL_STOP generate_key(key); decrypted = ecalloc(ZSTR_LEN(debase64) + crypto_secretbox_ZEROBYTES, 1); backup = ecalloc(ZSTR_LEN(debase64), 1); memcpy(backup, ZSTR_VAL(debase64), ZSTR_LEN(debase64)); ret = crypto_secretbox_open( decrypted, (unsigned char *)(ZSTR_VAL(debase64) + crypto_secretbox_NONCEBYTES), ZSTR_LEN(debase64) - crypto_secretbox_NONCEBYTES, (unsigned char *)ZSTR_VAL(debase64), key); if (-1 == ret) { if (true == simulation) { sp_log_simulation( "cookie_encryption", "Something went wrong with the decryption of %s. Using the cookie " "'as is' instead of decrypting it", hash_key ? ZSTR_VAL(hash_key->key) : "the session"); memcpy(ZSTR_VAL(debase64), backup, ZSTR_LEN(debase64)); ret = ZEND_HASH_APPLY_KEEP; goto out; } else { sp_log_warn("cookie_encryption", "Something went wrong with the decryption of %s", hash_key ? ZSTR_VAL(hash_key->key) : "the session"); ret = ZEND_HASH_APPLY_REMOVE; goto out; } } ZVAL_STRINGL(pDest, (char *)(decrypted + crypto_secretbox_ZEROBYTES), ZSTR_LEN(debase64) - crypto_secretbox_NONCEBYTES - 1 - crypto_secretbox_ZEROBYTES); ret = ZEND_HASH_APPLY_KEEP; out: zend_string_efree(debase64); efree(decrypted); efree(backup); return ret; } /* ** This function will return the `data` of length `data_len` encrypted in the ** form `base64(nonce | encrypted_data)` (with `|` being the concatenation ** operation). */ zend_string *encrypt_zval(zend_string *data) { const size_t encrypted_msg_len = crypto_secretbox_ZEROBYTES + ZSTR_LEN(data) + 1; // FIXME : We know that this len is too long const size_t emsg_and_nonce_len = encrypted_msg_len + crypto_secretbox_NONCEBYTES; unsigned char key[crypto_secretbox_KEYBYTES] = {0}; unsigned char nonce[crypto_secretbox_NONCEBYTES] = {0}; unsigned char *data_to_encrypt = ecalloc(encrypted_msg_len, 1); unsigned char *encrypted_data = ecalloc(emsg_and_nonce_len, 1); generate_key(key); // Put random bytes in the nonce php_random_bytes(nonce, sizeof(nonce), 0); /* tweetnacl's API requires the message to be padded with crypto_secretbox_ZEROBYTES zeroes. */ memcpy(data_to_encrypt + crypto_secretbox_ZEROBYTES, ZSTR_VAL(data), ZSTR_LEN(data)); assert(sizeof(zend_long) <= crypto_secretbox_NONCEBYTES); memcpy(encrypted_data, nonce, crypto_secretbox_NONCEBYTES); int err = crypto_secretbox(encrypted_data + crypto_secretbox_NONCEBYTES, data_to_encrypt, encrypted_msg_len, nonce, key); zend_string *z = NULL; if (err) { sp_log_err("cookie_encryption", "something went wrong during encryption"); z = zend_string_init("", 21, 0); } else { z = php_base64_encode(encrypted_data, emsg_and_nonce_len); } efree(data_to_encrypt); efree(encrypted_data); return z; }