summaryrefslogtreecommitdiff
path: root/src/sp_cookie_encryption.c
diff options
context:
space:
mode:
authorSebastien Blot2017-09-20 10:11:01 +0200
committerSebastien Blot2017-09-20 10:11:01 +0200
commit868f96c759b6650d88ff9f4fbc5c048302134248 (patch)
treec0de0af318bf77a8959164ef11aeeeb2b7bab294 /src/sp_cookie_encryption.c
Initial import
Diffstat (limited to 'src/sp_cookie_encryption.c')
-rw-r--r--src/sp_cookie_encryption.c216
1 files changed, 216 insertions, 0 deletions
diff --git a/src/sp_cookie_encryption.c b/src/sp_cookie_encryption.c
new file mode 100644
index 0000000..5248486
--- /dev/null
+++ b/src/sp_cookie_encryption.c
@@ -0,0 +1,216 @@
1#include "php_snuffleupagus.h"
2
3#include "ext/standard/url.h"
4
5ZEND_DECLARE_MODULE_GLOBALS(snuffleupagus)
6
7static unsigned int nonce_d = 0;
8
9static inline void generate_key(unsigned char *key) {
10 PHP_SHA256_CTX ctx;
11 const char *user_agent = sp_getenv("HTTP_USER_AGENT");
12 const char *remote_addr = sp_getenv("REMOTE_ADDR");
13 const char *encryption_key =
14 SNUFFLEUPAGUS_G(config).config_snuffleupagus->encryption_key;
15
16 /* 32 is the size of a SHA256. */
17 assert(32 == crypto_secretbox_KEYBYTES);
18
19 PHP_SHA256Init(&ctx);
20
21 if (user_agent) {
22 PHP_SHA256Update(&ctx, (unsigned char *)user_agent, strlen(user_agent));
23 }
24
25 if (remote_addr) {
26 char out[128];
27 apply_mask_on_ip(out, remote_addr);
28 PHP_SHA256Update(&ctx, (unsigned char*)out, sizeof(out));
29 }
30
31 if (encryption_key) {
32 PHP_SHA256Update(&ctx, (const unsigned char *)encryption_key,
33 strlen(encryption_key));
34 }
35
36 PHP_SHA256Final((unsigned char *)key, &ctx);
37}
38
39int decrypt_cookie(zval *pDest, int num_args, va_list args,
40 zend_hash_key *hash_key) {
41 unsigned char key[crypto_secretbox_KEYBYTES] = {0};
42 size_t value_len;
43 zend_string *debase64;
44 unsigned char *decrypted;
45 int ret = 0;
46
47 /* If the cookie isn't in the conf, it shouldn't be encrypted. */
48 if (0 ==
49 zend_hash_exists(SNUFFLEUPAGUS_G(config).config_cookie_encryption->names,
50 hash_key->key)) {
51 return ZEND_HASH_APPLY_KEEP;
52 }
53
54 generate_key(key);
55
56 value_len = php_url_decode(Z_STRVAL_P(pDest), Z_STRLEN_P(pDest));
57
58 if (value_len == 0) {
59 return ZEND_HASH_APPLY_KEEP;
60 }
61
62 debase64 = php_base64_decode((unsigned char *)(Z_STRVAL_P(pDest)), value_len);
63
64 if (value_len <
65 crypto_secretbox_NONCEBYTES + crypto_secretbox_ZEROBYTES) {
66 sp_log_msg("cookie_encryption", LOG_DROP,
67 "Buffer underflow tentative detected in cookie encryption handling.");
68 return ZEND_HASH_APPLY_REMOVE;
69 }
70
71 decrypted = pecalloc(value_len, 1, 0);
72
73 ret = crypto_secretbox_open(
74 decrypted,
75 (unsigned char *)(ZSTR_VAL(debase64) + crypto_secretbox_NONCEBYTES),
76 ZSTR_LEN(debase64) - crypto_secretbox_NONCEBYTES,
77 (unsigned char *)ZSTR_VAL(debase64), key);
78
79 if (ret == -1) {
80 sp_log_msg("cookie_encryption", LOG_DROP,
81 "Something went wrong with the decryption of %s.",
82 ZSTR_VAL(hash_key->key));
83 return ZEND_HASH_APPLY_REMOVE;
84 }
85
86 ZVAL_STRINGL(pDest, (char *)(decrypted + crypto_secretbox_ZEROBYTES),
87 ZSTR_LEN(debase64) - crypto_secretbox_NONCEBYTES - 1 -
88 crypto_secretbox_ZEROBYTES);
89
90 return ZEND_HASH_APPLY_KEEP;
91}
92
93/**
94 This function will return the `data` of length `data_len` encrypted in the
95 form
96 base64(nonce | encrypted_data) (with `|` being the concatenation
97 operation).
98
99 The `nonce` is time-based.
100*/
101static zend_string *encrypt_data(char *data, unsigned long long data_len) {
102 const size_t encrypted_msg_len = crypto_secretbox_ZEROBYTES + data_len + 1;
103 const size_t emsg_and_nonce_len = encrypted_msg_len + crypto_secretbox_NONCEBYTES;
104
105 unsigned char key[crypto_secretbox_KEYBYTES] = {0};
106 unsigned char nonce[crypto_secretbox_NONCEBYTES] = {0};
107 unsigned char *data_to_encrypt = pecalloc(encrypted_msg_len, 1, 0);
108 unsigned char *encrypted_data = pecalloc(emsg_and_nonce_len, 1, 1);
109
110 generate_key(key);
111
112 /* tweetnacl's API requires the message to be padded with
113 crypto_secretbox_ZEROBYTES zeroes. */
114 memcpy(data_to_encrypt + crypto_secretbox_ZEROBYTES, data, data_len);
115
116 assert(sizeof(size_t) <= crypto_secretbox_NONCEBYTES);
117
118 nonce_d++;
119 sscanf((char*)nonce, "%ud", &nonce_d);
120
121 memcpy(encrypted_data, nonce, crypto_secretbox_NONCEBYTES);
122 crypto_secretbox(encrypted_data + crypto_secretbox_NONCEBYTES,
123 data_to_encrypt, encrypted_msg_len, nonce, key);
124
125 zend_string *z = php_base64_encode(encrypted_data, emsg_and_nonce_len);
126 sp_log_debug("cookie_encryption", "Cookie value:%s:", z->val);
127 return z;
128}
129
130PHP_FUNCTION(sp_setcookie) {
131 zval params[7] = { 0 };
132 zend_string *name = NULL, *value = NULL, *path = NULL, *domain = NULL;
133 zend_long expires = 0;
134 zend_bool secure = 0, httponly = 0;
135 zval ret_val;
136 zval func_name;
137
138 ZEND_PARSE_PARAMETERS_START(1, 7)
139 Z_PARAM_STR(name)
140 Z_PARAM_OPTIONAL
141 Z_PARAM_STR(value)
142 Z_PARAM_LONG(expires)
143 Z_PARAM_STR(path)
144 Z_PARAM_STR(domain)
145 Z_PARAM_BOOL(secure)
146 Z_PARAM_BOOL(httponly)
147 ZEND_PARSE_PARAMETERS_END();
148
149 /* If the request was issued over HTTPS, the cookie should be "secure" */
150 if (SNUFFLEUPAGUS_G(config).config_auto_cookie_secure) {
151 const zval server_vars = PG(http_globals)[TRACK_VARS_SERVER];
152 if (Z_TYPE(server_vars) == IS_ARRAY) {
153 const zval *is_https =
154 zend_hash_str_find(Z_ARRVAL(server_vars), "HTTPS", strlen("HTTPS"));
155 if (NULL != is_https) {
156 secure = 1;
157 }
158 }
159 }
160
161 /* If the cookie's value is encrypted, it won't be usable by
162 * javascript anyway.
163 */
164 if (zend_hash_exists(SNUFFLEUPAGUS_G(config).config_cookie_encryption->names,
165 name) > 0) {
166 httponly = 1;
167 }
168
169 /* Shall we encrypt the cookie's value? */
170 if (zend_hash_exists(SNUFFLEUPAGUS_G(config).config_cookie_encryption->names,
171 name) > 0 && value) {
172 zend_string *encrypted_data = encrypt_data(value->val, value->len);
173 ZVAL_STR_COPY(&params[1], encrypted_data);
174 zend_string_release(encrypted_data);
175 } else if (value) {
176 ZVAL_STR_COPY(&params[1], value);
177 }
178
179 ZVAL_STRING(&func_name, "setcookie");
180 ZVAL_STR_COPY(&params[0], name);
181 ZVAL_LONG(&params[2], expires);
182 if (path) {
183 ZVAL_STR_COPY(&params[3], path);
184 }
185 if (domain) {
186 ZVAL_STR_COPY(&params[4], domain);
187 }
188 if (secure) {
189 ZVAL_LONG(&params[5], secure);
190 }
191 if (httponly) {
192 ZVAL_LONG(&params[6], httponly);
193 }
194
195 /* This is the _fun_ part: because PHP is utterly idiotic and nonsensical,
196 the `call_user_function` macro will __discard__ (yes) its first argument
197 (the hashtable), effectively calling functions from `CG(function_table)`.
198 This is why were replacing our hook with the original function, calling
199 the function, and then re-hooking it. */
200 void (*handler)(INTERNAL_FUNCTION_PARAMETERS);
201 handler = zend_hash_str_find_ptr(SNUFFLEUPAGUS_G(sp_internal_functions_hook), "setcookie",
202 strlen("setcookie"));
203 zend_internal_function *func = zend_hash_str_find_ptr(
204 CG(function_table), "setcookie", strlen("setcookie"));
205 func->handler = handler;
206
207 call_user_function(CG(function_table), NULL, &func_name, &ret_val, 7, params);
208
209 func->handler = PHP_FN(sp_setcookie);
210}
211
212int hook_cookies() {
213 HOOK_FUNCTION("setcookie", sp_internal_functions_hook, PHP_FN(sp_setcookie), false);
214
215 return SUCCESS;
216}