6

I'm trying to set up a shell script to grab a file from a Box account. To do so, an auth token is required. The auth token must be generated automatically through the script, so no manual steps are required. This can be done by constructing and submitting a JWT Claim.

Box's documentation specifies that they accept only “RS256″, “RS384″, and “RS512″. I have been able to setup everything I need except for creating the assertion by using Box's documentation and this website:

http://willhaley.com/blog/generate-jwt-with-bash/

I've spent hours searching through websites to see if I can find any indication on how to generate the assertion with bash/shell scripting to no avail. The website I linked is the closest thing I found, but it uses HS256, which is not supported by Box.

My script is currently virtually identical to the first script on the website I linked earlier (I have a separate script I used to test actually grabbing the file with a manually generated auth token). Please help with the commands required to generate the assertion.

JKL
  • 63
  • 1
  • 4
  • *Pure* bash? Any reason not to, say, call into a Python interpreter that uses the [`python-jwt`](https://github.com/davedoesdev/python-jwt) library? – Charles Duffy Oct 10 '17 at 01:44
  • (Not that it couldn't be done -- openssl *does* expose RSA operations via the command line; the thing that makes writing an answer that does so tricky is the need to test it). – Charles Duffy Oct 10 '17 at 01:51
  • @CharlesDuffy Yes, pure bash. I have access to configure a single Jenkins job. I do not have access to the server or to configure Jenkins. All that's available is an "Execute Shell". – JKL Oct 10 '17 at 15:45
  • You realize that a shell started via "execute shell" can invoke `python -c 'python code here'`, right? Just because something is run *from* bash doesn't mean it can't invoke anything *but* bash. If your real constraint is "can't install any software", that's workable -- but please don't make it into something it's not. – Charles Duffy Oct 10 '17 at 15:55
  • @CharlesDuffy Yes, I realize this, which is why I stated that I do not have access to the server. I cannot install any software. – JKL Oct 10 '17 at 16:07
  • But you *can* use a Python interpreter that's already on the system. And while you can't install a new Python library on the system as a whole, you can create a virtualenv inside the working directory used by your Jenkins job and install a Python library into it. Which means we *can* use Python libraries, even ones not already installed on your server. – Charles Duffy Oct 10 '17 at 16:08
  • That said, if we *were* going for a native-bash solution -- does the version of `openssl` on your server provide a `pkeyutl` subcommand, or do we need to use the older `rsautl`? – Charles Duffy Oct 10 '17 at 16:09
  • Yes, it provides the pkeyutl subcommand. – JKL Oct 10 '17 at 16:10
  • Which version of bash? If 4.3 or later, we can replace the overhead of calling out to an external `date` command with `printf %(%s)T -1`. – Charles Duffy Oct 10 '17 at 16:18
  • The bash version is 4.1.2. – JKL Oct 10 '17 at 16:25
  • Related: https://stackoverflow.com/q/59002949/9636 – Heath Borders Jun 29 '20 at 20:57
  • Related: https://stackoverflow.com/a/58326090/9636 – Heath Borders Jun 29 '20 at 20:57

1 Answers1

25

Consider the following, which supports both HS256 and RS256:

#!/usr/bin/env bash

# Inspired by implementation by Will Haley at:
#   http://willhaley.com/blog/generate-jwt-with-bash/

set -o pipefail

# Shared content to use as template
header_template='{
    "typ": "JWT",
    "kid": "0001",
    "iss": "https://stackoverflow.com/questions/46657001/how-do-you-create-an-rs256-jwt-assertion-with-bash-shell-scripting"
}'

build_header() {
        jq -c \
                --arg iat_str "$(date +%s)" \
                --arg alg "${1:-HS256}" \
        '
        ($iat_str | tonumber) as $iat
        | .alg = $alg
        | .iat = $iat
        | .exp = ($iat + 1)
        ' <<<"$header_template" | tr -d '\n'
}

b64enc() { openssl enc -base64 -A | tr '+/' '-_' | tr -d '='; }
json() { jq -c . | LC_CTYPE=C tr -d '\n'; }
hs_sign() { openssl dgst -binary -sha"${1}" -hmac "$2"; }
rs_sign() { openssl dgst -binary -sha"${1}" -sign <(printf '%s\n' "$2"); }

sign() {
        local algo payload header sig secret=$3
        algo=${1:-RS256}; algo=${algo^^}
        header=$(build_header "$algo") || return
        payload=${2:-$test_payload}
        signed_content="$(json <<<"$header" | b64enc).$(json <<<"$payload" | b64enc)"
        case $algo in
                HS*) sig=$(printf %s "$signed_content" | hs_sign "${algo#HS}" "$secret" | b64enc) ;;
                RS*) sig=$(printf %s "$signed_content" | rs_sign "${algo#RS}" "$secret" | b64enc) ;;
                *) echo "Unknown algorithm" >&2; return 1 ;;
        esac
        printf '%s.%s\n' "${signed_content}" "${sig}"
}

(( $# )) && sign "$@"

...usage as:

rsa_secret='
-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEAtHEDjwkBpsjhit+wXZMMj2AaRHyWSKatjzLtVEGdyXrbQGgQ
PjbfqPtqKsBPjcifHh8VAgrEtETbLN8pbE/XLRaB9P76hib6DATBn2JC6XG/NkAu
0b2F8WB6ZuJh3fbubSOZaORRIyRvfidV5Wjb7NbEDhuSxFLaq0ad2+rQHyBgMfQS
43OqhEa463WQt5F9NuWRTqweh5UotT6Mg9YgkvmBdA4IbJMEDWGFNecUzAGuESYq
wzJaaQ4S58ce7HxFDywM0nFXlNx1pxZwZOZfG7bddUD8FuwbBMx5c3Z3U8LAA+J/
50A/kxuZoa6sRTb7gXfBxy2riechlOTL+5ut3wIDAQABAoIBAD8bm5wGEV7MuR1B
+MPxbx4iBW3YiRMlwGPp8tlaDZ5u6onPG4c21+iY7du/4NL8zLHTOxy4uW02+9To
w+sOzXoGejM+jk4nCaL0cueUjURqNO77aaSPfW4bSRP8ry/bci4Xmkr2N25sCtZ7
WW5fyzM9NdqdSCqDs9jdXM6ShHGt4aG1w4Q38pfl2O2KUqgGYA8j8S7oEpcuApIj
sNH8o2PIFaFuRoUBq6WxSZBY7YdvKM0xlE0NKiDMAUIeTIRqtm8GPo7ot8dV6VHU
EglN7gaEve75XW0DAkK2lDDpGPlVHJwLgKGiSuW0qMh6lY+dKjsZ8wyz85DqTnyo
+42ZI6kCgYEA36X4c4a/tlh0A6i+EaA1CqmN8jh3nNMYgZvovTnIezCvO+RuJJEG
KQQjr8/z+E8FYobImrrZsuSL+UFs1trl/nSndWh22B7fQQbJBdHwhv39YWReS0tW
7t3LJJG3oQnR/ChlqyTToHfS0WcdtYQ0cnFWSx73Hg/S+cu0vHtcUrUCgYEAzosH
dXq1VGRgf3TIoI9s4xJt/SnH+VHtP4dvLKzY7NN4K76DIYdQIn1xQ1Y3705v/XG+
xTNAaoOaH6hBnRxwxcv6GmCpICJ2C21puxA63RqCslab5fc23wvMv/wwoEWPtXhf
3OOKZxszLR1vFqZaYTWzVmTxg+r5b2aNBB0MtsMCgYBAv+6Ek/ihNE6yWIJe3AE/
SwOboxmOP9eSfq8NSdNvRxMUxffVgl9ENLyYRB6gP1CRy+/8TCiHEIAt8/Es60c3
OlLZPRtbSuTcELjWhIecraBUOBjMt809bt1HgyCk8RDoblGxEQJsLQTON4p0aQg+
Me4H5bkp7O7p/z4ea6C5GQKBgQCewy+QliocHKwwTMyK3rSMNvZky2DzvI3pb2l9
pb95C3Qr691QQHrQiCwv3m5QfLKI1o4VdzfkqBQokWUeJ2ZoJEqzS+m00ch7MDc9
m1Qj8OTVwM1FD6oV+TQBvxCBofa9PzIw0JbqenX0D9P8TRLb9jNMDXu4Mz5Y6zMq
HkpPkQKBgQCgrcW7U0Q+081N25VfghCPRd/o+dtqP3udXgj0nIX6y3qeCJiPeamZ
plMNqdZScaK37wMouAIPD0u5w1OCnlepuUxU3h5y55Lzx3PnDlU1H+yfBsTi1KL/
sDjgs31j//w80krxJNj5/i2AeYzATmybPwyM/c/PVBv/hecreUbTlQ==
-----END RSA PRIVATE KEY-----
'

test_payload='{
    "Id": 1,
    "Name": "Hello, world!"
}'
sign rs256 "$test_payload" "$rsa_secret"
Charles Duffy
  • 280,126
  • 43
  • 390
  • 441
  • 1
    That did it! Thank you so much! – JKL Oct 10 '17 at 18:38
  • 1
    Hi, did you manage to have a valid signature with elliptic curve singatures (ES256) ? As far as I know the command `openssl dgst -binary -sha"${1}" -sign <(...)` should work for ec private key as well ? – bric3 Mar 27 '20 at 16:44