mirror of
https://github.com/aljazceru/cyphernode.git
synced 2025-12-17 20:55:04 +01:00
Fixed JWT and added tests for the gatekeeper
This commit is contained in:
@@ -3,15 +3,29 @@
|
||||
#
|
||||
# This is not designed to serve thousands of API key!
|
||||
#
|
||||
# 401 = authentication error
|
||||
# 403 = authorization error
|
||||
#
|
||||
# header = {"alg":"HS256","typ":"JWT"}
|
||||
# header64 = base64(header) = eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9Cg==
|
||||
# header64 = unpad(base64url(header)) = eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
|
||||
#
|
||||
# payload = {"id":"001","exp":1538528077}
|
||||
# payload64 = base64(payload) = eyJpZCI6IjAwMSIsImV4cCI6MTUzODUyODA3N30K
|
||||
# payload64 = unpad(base64url(payload)) = eyJpZCI6IjAwMSIsImV4cCI6MTUzODUyODA3N30K
|
||||
#
|
||||
# signature = hmacsha256(header64.payload64, key)
|
||||
# signature = unpad(base64url(hmacsha256(header64.payload64, key)))
|
||||
#
|
||||
# token = header64.payload64.signature = eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9Cg==.eyJpZCI6IjAwMSIsImV4cCI6MTUzODUyODA3N30K.signature
|
||||
# token = header64.payload64.signature = eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjAwMSIsImV4cCI6MTUzODUyODA3N30K.signature
|
||||
#
|
||||
#
|
||||
# Previous implementation of gatekeeper had a bug in the generation/validation of the JWT token:
|
||||
# - The header and payload were in base64 instead of unpadded base64url
|
||||
# - the signature was in HEX instead of unpadded base64url.
|
||||
#
|
||||
# Ref.: Appendix C of RFC 7515, "JSON Web Signature (JWS)"
|
||||
# https://www.rfc-editor.org/rfc/rfc7515.txt
|
||||
#
|
||||
# To stay backward-compatible, we'll validate the right way first and if the
|
||||
# signature is not valid, we'll validate the old-broken way.
|
||||
#
|
||||
|
||||
. ./trace.sh
|
||||
@@ -27,11 +41,47 @@ verify_sign() {
|
||||
trace "[verify_sign] payload64=${payload64}"
|
||||
trace "[verify_sign] signature=${signature}"
|
||||
|
||||
local payload=$(echo -n "${payload64}" | base64 -d)
|
||||
local exp=$(echo "${payload}" | jq ".exp")
|
||||
local padding
|
||||
case $((${#payload64}%4)) in
|
||||
2) padding='=='
|
||||
;;
|
||||
3) padding='='
|
||||
;;
|
||||
esac
|
||||
# When broken-legacy used, padding is always empty because we were using base64
|
||||
# which is padded
|
||||
trace "[verify_sign] padding=${padding}"
|
||||
local payload
|
||||
local legacy
|
||||
# When broken-legacy used, this will fail if + and / found in payload because
|
||||
# it's base64 instead of base64url.
|
||||
payload=$(echo -n "${payload64}${padding}" | basenc --base64url -d)
|
||||
if [ "$?" -ne "0" ]; then
|
||||
# We got a legacy broken JWT with + and / in it
|
||||
trace "[verify_sign] We got a legacy broken JWT"
|
||||
legacy=1
|
||||
fi
|
||||
|
||||
# Let's get the base64 broken legacy payload in case we have to validate it below...
|
||||
# If base64 -d fails, it means we got a correct JWT-formed payload.
|
||||
local legacypayload
|
||||
legacypayload=$(echo -n "${payload64}" | base64 -d)
|
||||
if [ "$?" -ne "0" ]; then
|
||||
# We got a fixed unpadded base64url, no need to try old-broken validation
|
||||
trace "[verify_sign] We got a fixed unpadded base64url, no need to try old-broken validation"
|
||||
legacy=0
|
||||
fi
|
||||
|
||||
local exp
|
||||
if [ "${legacy}" -eq "1" ]; then
|
||||
exp=$(echo "${legacypayload}" | jq ".exp")
|
||||
else
|
||||
exp=$(echo "${payload}" | jq ".exp")
|
||||
fi
|
||||
local current=$(date +"%s")
|
||||
|
||||
trace "[verify_sign] payload=${payload}"
|
||||
trace "[verify_sign] legacypayload=${legacypayload}"
|
||||
trace "[verify_sign] exp=${exp}"
|
||||
trace "[verify_sign] current=${current}"
|
||||
|
||||
@@ -57,9 +107,23 @@ verify_sign() {
|
||||
local msg="${header64}.${payload64}"
|
||||
trace "[verify_sign] msg=${msg}"
|
||||
|
||||
local comp_sign=$(echo -n "${msg}" | openssl dgst -hmac "${key}" -sha256 -r | cut -sd ' ' -f1)
|
||||
local comp_sign
|
||||
if [ "${legacy}" -eq "1" ]; then
|
||||
comp_sign=$(echo -n "${msg}" | openssl dgst -hmac "${key}" -sha256 -r | cut -sd ' ' -f1)
|
||||
else
|
||||
comp_sign=$(echo -n "${msg}" | openssl dgst -hmac "${key}" -sha256 -r -binary | basenc --base64url | tr -d '=')
|
||||
fi
|
||||
trace "[verify_sign] comp_sign=${comp_sign}"
|
||||
|
||||
if [ "${comp_sign}" != "${signature}" ] && [ -z "${legacy}" ]; then
|
||||
# Invalid sig and legacy empty, we don't know if legacy or not...
|
||||
# So we'll try legacy validation...
|
||||
trace "[verify_sign] Invalid signature, let's try legacy..."
|
||||
|
||||
comp_sign=$(echo -n "${msg}" | openssl dgst -hmac "${key}" -sha256 -r | cut -sd ' ' -f1)
|
||||
trace "[verify_sign] comp_sign=${comp_sign}"
|
||||
fi
|
||||
|
||||
if [ "${comp_sign}" = "${signature}" ]; then
|
||||
trace "[verify_sign] Valid signature!"
|
||||
|
||||
@@ -71,14 +135,14 @@ verify_sign() {
|
||||
return
|
||||
fi
|
||||
trace "[verify_sign] Invalid group!"
|
||||
return 1
|
||||
return 3
|
||||
fi
|
||||
trace "[verify_sign] Invalid signature!"
|
||||
return 1
|
||||
fi
|
||||
|
||||
trace "[verify_sign] Expired!"
|
||||
return 1
|
||||
return 3
|
||||
}
|
||||
|
||||
verify_group() {
|
||||
@@ -95,7 +159,7 @@ verify_group() {
|
||||
local actiontoinspect=$(echo "$action" | tr -d '_-')
|
||||
case $actiontoinspect in (*[![:alnum:]]*|"")
|
||||
trace "[verify_group] Potential code injection, exiting"
|
||||
return 1
|
||||
return 3
|
||||
esac
|
||||
|
||||
local needed_group
|
||||
@@ -124,9 +188,10 @@ verify_group() {
|
||||
fi
|
||||
|
||||
trace "[verify_group] Access NOT granted"
|
||||
return 1
|
||||
return 3
|
||||
}
|
||||
|
||||
returncode=0
|
||||
|
||||
# $HTTP_AUTHORIZATION = Bearer <token>
|
||||
# Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjAwMyIsImV4cCI6MTU0MjE0OTMyNH0=.b811067cf79c7009a0a38f110a6e3bf82cc4310aa6afae75b9d915b9febf13f7
|
||||
@@ -139,8 +204,15 @@ if [ "-${HTTP_AUTHORIZATION%% *}" = "-Bearer" ]; then
|
||||
if [ -n "$token" ]; then
|
||||
trace "[auth.sh] Valid format for authorization header"
|
||||
verify_sign "${token}"
|
||||
[ "$?" -eq "0" ] && return
|
||||
returncode=$?
|
||||
trace "[auth.sh] returncode=${returncode}"
|
||||
[ "$returncode" -eq "0" ] && return
|
||||
fi
|
||||
fi
|
||||
|
||||
echo -en "Status: 403 Forbidden\r\n\r\n"
|
||||
if [ "${returncode}" -eq "1" ]; then
|
||||
echo -en "Status: 401 Unauthorized\r\n\r\n"
|
||||
else
|
||||
echo -en "Status: 403 Forbidden\r\n\r\n"
|
||||
fi
|
||||
|
||||
|
||||
Reference in New Issue
Block a user