1 #include "php_snuffleupagus.h"
2 
generate_key(unsigned char * key)3 void generate_key(unsigned char *key) {
4   PHP_SHA256_CTX ctx;
5   const char *user_agent = getenv("HTTP_USER_AGENT");
6   const zend_string *env_var_zend =
7       SNUFFLEUPAGUS_G(config).config_snuffleupagus->cookies_env_var;
8   const zend_string *encryption_key_zend =
9       SNUFFLEUPAGUS_G(config).config_snuffleupagus->encryption_key;
10   const char *env_var = (env_var_zend ? getenv(ZSTR_VAL(env_var_zend)) : NULL);
11   const char *encryption_key =
12       (encryption_key_zend ? ZSTR_VAL(encryption_key_zend) : NULL);
13 
14   assert(32 == crypto_secretbox_KEYBYTES);  // 32 is the size of a SHA256.
15   assert(encryption_key);                   // Encryption key can't be NULL
16 
17   PHP_SHA256Init(&ctx);
18 
19   if (user_agent) {
20     PHP_SHA256Update(&ctx, (unsigned char *)user_agent, strlen(user_agent));
21   }
22 
23   if (env_var) {
24     PHP_SHA256Update(&ctx, (unsigned char *)env_var, strlen(env_var));
25   } else {
26     sp_log_warn("cookie_encryption",
27                 "The environment variable '%s' "
28                 "is empty, cookies are weakly encrypted",
29                 ZSTR_VAL(env_var_zend));
30   }
31 
32   if (encryption_key) {
33     PHP_SHA256Update(&ctx, (const unsigned char *)encryption_key,
34                      strlen(encryption_key));
35   }
36 
37   PHP_SHA256Final((unsigned char *)key, &ctx);
38 }
39 
40 // This function return 0 upon success , non-zero otherwise
decrypt_zval(zval * pDest,bool simulation,zend_hash_key * hash_key)41 int decrypt_zval(zval *pDest, bool simulation, zend_hash_key *hash_key) {
42   unsigned char key[crypto_secretbox_KEYBYTES] = {0};
43   unsigned char *decrypted;
44   zend_string *debase64;
45   int ret = 0;
46 
47   debase64 = php_base64_decode((unsigned char *)(Z_STRVAL_P(pDest)),
48                                Z_STRLEN_P(pDest));
49 
50   if (ZSTR_LEN(debase64) < crypto_secretbox_NONCEBYTES) {
51     if (true == simulation) {
52       sp_log_simulation(
53           "cookie_encryption",
54           "Buffer underflow tentative detected in cookie encryption handling "
55           "for %s. Using the cookie 'as it' instead of decrypting it",
56           hash_key ? ZSTR_VAL(hash_key->key) : "the session");
57       return ZEND_HASH_APPLY_KEEP;
58     } else {
59       // LCOV_EXCL_START
60       sp_log_drop(
61           "cookie_encryption",
62           "Buffer underflow tentative detected in cookie encryption handling");
63       return ZEND_HASH_APPLY_REMOVE;
64       // LCOV_EXCL_STOP
65     }
66   }
67 
68   // LCOV_EXCL_START
69   if (ZSTR_LEN(debase64) + (size_t)crypto_secretbox_ZEROBYTES <
70       ZSTR_LEN(debase64)) {
71     if (true == simulation) {
72       sp_log_simulation(
73           "cookie_encryption",
74           "Integer overflow tentative detected in cookie encryption handling "
75           "for %s. Using the cookie 'as it' instead of decrypting it.",
76           hash_key ? ZSTR_VAL(hash_key->key) : "the session");
77       return ZEND_HASH_APPLY_KEEP;
78     } else {
79       sp_log_drop(
80           "cookie_encryption",
81           "Integer overflow tentative detected in cookie encryption handling.");
82       return ZEND_HASH_APPLY_REMOVE;
83     }
84   }
85   // LCOV_EXCL_STOP
86 
87   generate_key(key);
88 
89   decrypted = ecalloc(ZSTR_LEN(debase64) + crypto_secretbox_ZEROBYTES, 1);
90   char *backup = ecalloc(ZSTR_LEN(debase64), 1);
91   memcpy(backup, ZSTR_VAL(debase64), ZSTR_LEN(debase64));
92 
93   ret = crypto_secretbox_open(
94       decrypted,
95       (unsigned char *)(ZSTR_VAL(debase64) + crypto_secretbox_NONCEBYTES),
96       ZSTR_LEN(debase64) - crypto_secretbox_NONCEBYTES,
97       (unsigned char *)ZSTR_VAL(debase64), key);
98 
99   if (-1 == ret) {
100     if (true == simulation) {
101       sp_log_simulation(
102           "cookie_encryption",
103           "Something went wrong with the decryption of %s. Using the cookie "
104           "'as it' instead of decrypting it",
105           hash_key ? ZSTR_VAL(hash_key->key) : "the session");
106       memcpy(ZSTR_VAL(debase64), backup, ZSTR_LEN(debase64));
107       efree(backup);
108       return ZEND_HASH_APPLY_KEEP;
109     } else {
110       sp_log_warn("cookie_encryption",
111                   "Something went wrong with the decryption of %s",
112                   hash_key ? ZSTR_VAL(hash_key->key) : "the session");
113       efree(backup);
114       return ZEND_HASH_APPLY_REMOVE;
115     }
116   }
117   efree(backup);
118 
119   ZVAL_STRINGL(pDest, (char *)(decrypted + crypto_secretbox_ZEROBYTES),
120                ZSTR_LEN(debase64) - crypto_secretbox_NONCEBYTES - 1 -
121                    crypto_secretbox_ZEROBYTES);
122 
123   efree(decrypted);
124 
125   return ZEND_HASH_APPLY_KEEP;
126 }
127 
128 /*
129 ** This function will return the `data` of length `data_len` encrypted in the
130 ** form `base64(nonce | encrypted_data)` (with `|` being the concatenation
131 ** operation).
132 */
encrypt_zval(zend_string * data)133 zend_string *encrypt_zval(zend_string *data) {
134   const size_t encrypted_msg_len =
135       crypto_secretbox_ZEROBYTES + ZSTR_LEN(data) + 1;
136   // FIXME : We know that this len is too long
137   const size_t emsg_and_nonce_len =
138       encrypted_msg_len + crypto_secretbox_NONCEBYTES;
139 
140   unsigned char key[crypto_secretbox_KEYBYTES] = {0};
141   unsigned char nonce[crypto_secretbox_NONCEBYTES] = {0};
142   unsigned char *data_to_encrypt = ecalloc(encrypted_msg_len, 1);
143   unsigned char *encrypted_data = ecalloc(emsg_and_nonce_len, 1);
144 
145   generate_key(key);
146 
147   // Put random bytes in the nonce
148   php_random_bytes(nonce, sizeof(nonce), 0);
149 
150   /* tweetnacl's API requires the message to be padded with
151   crypto_secretbox_ZEROBYTES zeroes. */
152   memcpy(data_to_encrypt + crypto_secretbox_ZEROBYTES, ZSTR_VAL(data),
153          ZSTR_LEN(data));
154 
155   assert(sizeof(zend_long) <= crypto_secretbox_NONCEBYTES);
156 
157   memcpy(encrypted_data, nonce, crypto_secretbox_NONCEBYTES);
158 
159   crypto_secretbox(encrypted_data + crypto_secretbox_NONCEBYTES,
160                    data_to_encrypt, encrypted_msg_len, nonce, key);
161 
162   zend_string *z = php_base64_encode(encrypted_data, emsg_and_nonce_len);
163 
164   return z;
165 }
166