Reputation: 490
Let's say you use AWS and you want to use ID based authentication using Cognito.
Then aws
provides you with a public key you can verify the cognito payload with.
Let's also assume you don't want or cannot use any fancy libraries like jose since you are locked in a highly constrained environment.
The way to go is a BASH script that would make good old Brian Kernighan
proud.
You have to understand the encoding first.
Base64Url
needs to be translated to Base64.
This is achieved using padding characters =
If the number of characters is divisible by 4
you don't need padding.
This relates to the binary digit representation.
Once that is taken care of you can translate Base64
to binary
.
But how do I convert a JWKS/JWT
to a PEM
file this using BASH and BASH-programs only?
Upvotes: 3
Views: 3315
Reputation: 490
This is the solution I came up with following the officual documentation and sources listed below.
https://aws.amazon.com/premiumsupport/knowledge-center/decode-verify-cognito-json-token/
Please adapt input url or use json token directly and make sure you have jq
installed.
Descriptive help is given in the function as comments.
Tested successfully on Ubuntu 18.04 and AmazonLinux2 (CentOS) as of 2020-04-28
#!/usr/bin/env bash
set -e
# FUNCTIONS
decodeBase64UrlUInt() { #input:base64UrlUnsignedInteger
local binaryDigits paddedStr
case $(( ${#1} % 4 )) in
2) paddedStr="$1==" ;;
3) paddedStr="$1=" ;;
*) paddedStr="$1" ;;
esac
binaryDigits=$( \
echo -n "$paddedStr" \
| tr '_-' '/+' \
| openssl enc -d -a -A \
| xxd -b -g 0 \
| cut -d ' ' -f 2 \
| paste -s -d '' \
)
echo "ibase=2; obase=A; $binaryDigits" | bc
# openssl enc:encoding; -d=decrypt; -a=-base64; -A=singleLineBuffer
# xxd "make-hexdump": -b=bits; -g=groupsize
# cut -d=delimiter; -f=field
# paste -s=serial|singleFile; -d=delimiter
}
base64UrlToHex() { #input:base64UrlString
local hexStr paddedStr
case $(( ${#1} % 4 )) in
2) paddedStr="$1==" ;;
3) paddedStr="$1=" ;;
*) paddedStr="$1" ;;
esac
hexStr=$( \
echo -n "$paddedStr" \
| tr '_-' '/+' \
| base64 -d \
| xxd -p -u \
| tr -d '\n' \
)
echo "$hexStr"
# base64 -d=decode
# xxd -p=-plain=continuousHexDump; -u=upperCase
# tr -d=delete
}
asn1Conf() { #input:hexStrPlainUpperCase
local e="$1"
local n="$2"
echo "
asn1 = SEQUENCE:pubkeyinfo
[pubkeyinfo]
algorithm = SEQUENCE:rsa_alg
pubkey = BITWRAP,SEQUENCE:rsapubkey
[rsa_alg]
algorithm = OID:rsaEncryption
parameter = NULL
[rsapubkey]
n = INTEGER:0x$n
e = INTEGER:0x$e
" | sed '/^$/d ; s/^ *//g' \
| openssl asn1parse \
-genconf /dev/stdin \
-out /dev/stdout \
| openssl rsa \
-pubin \
-inform DER \
-outform PEM \
-in /dev/stdin \
-out /dev/
# sed /^$/d=removeEmptyLines; /^ */=removeLeadingSpaces
}
main() {
local e n hexArr
local jwksUrl="$1"
local jwkJson=$(curl -sSSL $jwksUrl)
local kidList=$(jq -r '.keys[].kid' <<< "$jwkJson")
for keyId in $kidList; do
n=$(jq -r ".keys[] | select(.kid == \"$keyId\") | .n" <<< "$jwkJson")
e=$(jq -r ".keys[] | select(.kid == \"$keyId\") | .e" <<< "$jwkJson")
echo -e "\n$keyId"
# decodeBase64UrlUInt "$e"
# decodeBase64UrlUInt "$n"
asn1Conf $(base64UrlToHex "$e") $(base64UrlToHex "$n")
done
}
# MAIN
main 'https://cognito-idp.{region}.amazonaws.com/{userPoolId}/.well-known/jwks.json'
exit 0
Special thanks to:
Yury Oparin https://www.yuryoparin.com/2014/05/base64url-in-bash.html
Cédric Deltheil https://github.com/Moodstocks/moodstocks-api-clients/blob/master/bash/base64url.sh
Alvis Tang https://gist.github.com/alvis/89007e96f7958f2686036d4276d28e47
Upvotes: 2
Reputation: 19625
Here are some options:
Either ignore base64 -d
complaining of truncated input:
<<<'SGVsbG8geW91Cg' base64 -d 2>/dev/null ||:
Or fix the base64 padding with Bash before decoding:
base64URL='SGVsbG8geW91Cg'
printf -v pad_space '%*s' $((${#base64URL}%4)) ''
padded_base64="$base64URL${pad_space// /=}"
<<<"$padded_base64" base64 -d
Upvotes: 0