handout Fall'22

- added Jansson examples to jwt_demo_hs256
- added cookie property tests
This commit is contained in:
Godmar Back 2022-11-17 01:10:53 -05:00
parent be04f7d339
commit 06153db5c1
5 changed files with 53 additions and 135 deletions

View File

@ -19,7 +19,7 @@ git clone https://github.com/akheron/jansson.git
git clone https://git@github.com/benmcollins/libjwt.git
(cd libjwt;
git checkout v1.13.1;
git checkout v1.14.0;
autoreconf -fi;
env PKG_CONFIG_PATH=../deps/lib/pkgconfig:${PKG_CONFIG_PATH} ./configure --prefix=${BASE}/deps;
make -j 40 install

View File

@ -2,6 +2,10 @@
* Quick demo of how to use libjwt using a HS256.
*
* @author gback, CS 3214, Spring 2018, updated Spring 2021
* Added Jansson demo Fall'22
*
* I included the necessary free() operations here which
* are needed in a long-running server.
*/
#include <jwt.h>
#include <stdlib.h>
@ -9,6 +13,7 @@
#include <string.h>
#include <time.h>
#include <errno.h>
#include <jansson.h>
static const char * NEVER_EMBED_A_SECRET_IN_CODE = "supa secret";
@ -56,6 +61,7 @@ main()
if (encoded == NULL)
die("jwt_encode_str", ENOMEM);
jwt_free(mytoken);
printf("encoded as %s\nTry entering this at jwt.io\n", encoded);
jwt_t *ymtoken;
@ -65,9 +71,32 @@ main()
if (rc)
die("jwt_decode", rc);
free(encoded);
char *grants = jwt_get_grants_json(ymtoken, NULL); // NULL means all
if (grants == NULL)
die("jwt_get_grants_json", ENOMEM);
jwt_free(ymtoken);
printf("redecoded: %s\n", grants);
// an example of how to use Jansson
json_error_t error;
json_t *jgrants = json_loadb(grants, strlen(grants), 0, &error);
if (jgrants == NULL)
die("json_loadb", EINVAL);
free (grants);
json_int_t exp, iat;
const char *sub;
rc = json_unpack(jgrants, "{s:I, s:I, s:s}",
"exp", &exp, "iat", &iat, "sub", &sub);
if (rc == -1)
die("json_unpack", EINVAL);
printf ("exp: %lld\n", exp);
printf ("iat: %lld\n", iat);
printf ("sub: %s\n", sub);
json_decref(jgrants);
}

View File

@ -62,6 +62,7 @@ usage(char * av0)
" -p port port number to bind to\n"
" -R rootdir root directory from which to serve files\n"
" -e seconds expiration time for tokens in seconds\n"
" -a enable HTML5 fallback\n"
" -h display this help\n"
, av0);
exit(EXIT_FAILURE);

View File

@ -4,7 +4,7 @@ PORT=10000
# to test against a working implementation (and see the intended responses)
# change this variable, e.g.
# use URL=http://theta.cs.vt.edu:3000/
#URL=http://hazelnut.rlogin:12345
URL=http://localhost:${PORT}
# the file in which curl stores cookies across runs
@ -34,8 +34,17 @@ curl -v \
curl -v \
${URL}/private/secret.txt
# this should succeed since credentials are included
# this should succeed since credentials are included (via the cookie jar)
curl -v \
-b ${COOKIEJAR} \
${URL}/private/secret.txt
# now log out
curl -v -X POST \
-c ${COOKIEJAR} \
${URL}/api/logout
# this should fail since the cookie should have been removed from the cookie jar
curl -v \
-b ${COOKIEJAR} \
${URL}/private/secret.txt

View File

@ -61,27 +61,6 @@ def get_socket_connection(hostname, port):
else:
return sock
def run_connection_check_loadavg(http_conn, hostname):
"""
Run a check of the connection for validity, using a well-formed
request for /loadavg and checking it after receiving it.
"""
# GET request for the object /loadavg
http_conn.request("GET", "/loadavg", headers={"Host": hostname})
# Get the server's response
server_response = http_conn.getresponse()
# Check the response status code
assert server_response.status == OK, "Server failed to respond"
# Check the data included in the server's response
assert check_loadavg_response(server_response.read().decode('utf-8')), \
"loadavg check failed"
def run_connection_check_empty_login(http_conn, hostname):
"""
Run a check of the connection for validity, using a well-formed
@ -107,7 +86,7 @@ def run_404_check(http_conn, obj, hostname):
requesting a non-existent URL object.
"""
# GET request for the object /loadavg
# GET request for obj
http_conn.request("GET", obj, headers={"Host": hostname})
# Get the server's response
@ -119,27 +98,6 @@ def run_404_check(http_conn, obj, hostname):
server_response.read()
def run_query_check(http_conn, request, req_object, callback, hostname):
"""
Checks that the server properly processes the query string passed to it.
"""
http_conn.request("GET", request, headers={"Host": hostname})
server_response = http_conn.getresponse()
assert server_response.status == OK, "Server failed to respond"
if callback is None:
if req_object == "loadavg":
assert check_loadavg_response(server_response.read().decode('utf-8')), \
"loadavg check failed"
else:
assert check_meminfo_response(server_response.read().decode('utf-8')), \
"meminfo check failed"
else:
assert check_callback_response(server_response.read().decode('utf-8'),
callback, req_object), "callback check failed"
def run_method_check(http_conn, method, hostname):
"""
Check that the unsupported method supplied has either a NOT IMPLEMENTED
@ -165,92 +123,6 @@ def print_response(response):
for line in lines:
print(line.strip())
def check_loadavg_response(response):
"""Check that the response to a loadavg request generated the correctly
formatted output. Returns true if it executes properly, throws an
AssertionError if it does not execute properly or another error if json
is unable to decode the response."""
try:
data = json.loads(response.strip())
except ValueError as msg:
raise AssertionError("Invalid JSON object. Received: " + response)
assert len(data) == 3, "Improper number of data items returned"
assert 'total_threads' in data, "total_threads element missing"
assert 'loadavg' in data, "loadavg element missing"
assert 'running_threads' in data, "running_threads element missing"
assert len(data['loadavg']) == 3, 'Improper number of data items in \
loadavg'
return True
def check_meminfo_response(response):
"""Check that the response to a meminfo request generated the correctly
formatted output. Returns true if it executes properly, throws an
AssertionError if it does not execute properly or another error if json
is unable to decode the response."""
try:
data = json.loads(response.strip())
except ValueError as msg:
raise AssertionError("Invalid JSON object. Received: " + response)
for line in open("/proc/meminfo"):
entry = re.split(":?\s+", line)
assert entry[0] in data, entry[0] + " key is missing"
try:
int(data[entry[0]])
except (TypeError, ValueError):
raise AssertionError("a non-integer was passed to meminfo")
return True
def check_callback_response(response, callback, req_obj):
"""Check that the response to a req_obj request with callback function
callback generated the correctly formatted output. Returns true if it
executes properly, throws an AssertionError if it does not execute properly
or another error if json is unable to decode the response."""
callback.replace(' ', '')
response.replace(' ', '')
assert response[0:len(callback) + 1] == callback + "(", 'callback incorrect, was: ' + response[0:len(
callback) + 1] + ' , expected: ' + callback + '('
assert response[len(response) - 1] == ")", 'missing close parenthesis'
if req_obj == "meminfo":
check_meminfo_response(response[len(callback) + 1:len(response) - 1])
elif req_obj == "loadavg":
check_loadavg_response(response[len(callback) + 1:len(response) - 1])
else:
return False
return True
def check_meminfo_change(response1, response2, key, delta, safety_margin=0):
"""Check that a specific value in meminfo has changed by at least some amount.
Used by allocanon and freeanon tests. Returns true if it executes properly,
throws an AssertionError if it does not execute properly or another error
if json is unable to decode the response."""
check_meminfo_response(response1)
check_meminfo_response(response2)
data1 = json.loads(response1.strip())
data2 = json.loads(response2.strip())
if delta >= 0:
return float(data2[key]) - float(data1[key]) > delta * (1 - safety_margin)
else:
return float(data2[key]) - float(data1[key]) < delta * (1 - safety_margin)
def check_empty_login_respnse(response):
return response.strip() == "{}"
@ -385,7 +257,7 @@ class Single_Conn_Protocol_Case(Doc_Print_Test_Case):
pass
sock.send(encode("\r\n"))
# If there is a HTTP response, it should be a valid /loadavg
# If there is a HTTP response, it should be a valid /login
# response.
data = ""
@ -1313,13 +1185,13 @@ class Single_Conn_Good_Case(Doc_Print_Test_Case):
print("The server has crashed. Please investigate.")
def test_login_get(self):
""" Test Name: test_loadavg_no_callback\n\
""" Test Name: test_login_get\n\
Number Connections: One \n\
Procedure: Simple GET request:\n\
GET /api/login HTTP/1.1
"""
# GET request for the object /loadavg
# GET request for the object /api/login
self.http_connection.request("GET", "/api/login")
# Get the server's response
@ -2005,6 +1877,7 @@ class Authentication(Doc_Print_Test_Case):
response = self.sessions[i].post('http://%s:%s/api/login' % (self.hostname, self.port),
json={'username': self.username, 'password': self.password},
timeout=2)
except requests.exceptions.RequestException:
raise AssertionError("The server did not respond within 2s")
@ -2016,6 +1889,12 @@ class Authentication(Doc_Print_Test_Case):
for cookie in self.sessions[i].cookies:
try:
self.assertEquals(cookie.path, "/", "Cookie path should be /")
self.assertTrue("HttpOnly" in cookie._rest, "Cookie is not http only.")
maxage = cookie.expires - time.mktime(datetime.now().timetuple())
if abs(maxage - int(auth_token_expiry)) > 1:
raise AssertionError(f"Cookie's Max-Age is {maxage} should be {auth_token_expiry}")
encoded_data = cookie.value.split('.')[1]
# Try to decode the payload