-
-
Save timmc/d2814d7da19521dda1883dd3cc046217 to your computer and use it in GitHub Desktop.
| #!/bin/bash | |
| echo "DO NOT USE -- incorrect signature format, see comments on gist." | |
| exit 1 | |
| # Create and sign a JWT token with ES256 given the path to an ECDSA | |
| # private key and a JSON payload. | |
| # $0 path/to/keypair.der '{"JSON": "payload"}' | |
| # Example keypair creation: | |
| # openssl ecparam -name prime256v1 -genkey -noout -outform DER > example-keypair.der | |
| # A few tips for generating the payload: | |
| # - Pipe raw strings through `jq --raw-input .` to encode them as | |
| # JSON strings. https://stedolan.github.io/jq/ | |
| # - GNU date is great for generating the iat, nbf, and exp time | |
| # fields: `date --date="15 minutes" +"%s"` | |
| set -eu -o pipefail | |
| keypair_path="$1" | |
| payload="$2" | |
| function base64_urlsafe { | |
| # Implement own URL-safe Base64 based on standard version. Delete | |
| # padding, undo wrapping, and swap out chars 62 and 63. Not all | |
| # versions of `base64` have the `--wrap=0` that GNU coreutils has. | |
| base64 | tr -d '\r\n=' | tr '+/' '-_' | |
| } | |
| header_enc="$(echo -n '{"typ":"JWT","alg":"ES256"}' | base64_urlsafe)" | |
| payload_enc="$(echo -n "$payload" | base64_urlsafe)" | |
| message="$header_enc.$payload_enc" | |
| # If you're on a Mac, you might have a really old version of openssl | |
| # that doesn't support ECDSA signing this way. | |
| sig="$(echo -n "$message" | openssl dgst -sha256 -sign "$keypair_path" -keyform DER | base64_urlsafe)" | |
| echo -n "$message.$sig" |
@bric3, @timmc, I think this is wrong. See this Stack Overflow for details, but effectively openssl is outputting a specific format of signature that isn't actually a valid JWS signature (not even a base64url decoded one). It's wrapping the two integers (R and S) in a DER format that can be read by the ASN1 module within openssl. This SO article goes into detail about that encoding/wrapper.
Hmm, thanks! That's unfortunate.
Luckily, I only ever used this script to generate values for a test suite, or something similar. We did have trouble with it not working on everyone's computer, so I wonder if there was a difference in output for certain openssl versions.
I'll put a giant disclaimer at the top. :-)
I posted my take on the procedure here.
I think you can fix this by making the following switch:
# Current signature construction
sig="$(echo -n "$message" | openssl dgst -sha256 -sign "$keypair_path" -keyform DER | base64_urlsafe)"
# Replacement
sig ="$(echo -n "$message" | openssl dgst -sha256 -sign "$keypair_path" -keyform DER | openssl asn1parse -inform DER | perl -n -e'/INTEGER :([0-9A-Z]*)$/ && print $1' | xxd -p -r | base64_urlsafe)"I've just added a couple extra processing steps:
- To let openssl parse the signature
- A regex to extract the R and S integers from that output
- xxd to parse the strings as hex (that's how asn1parse outputs them) and converts them to binary before passing to your base64_urlsafe alg.
I combined all the above with results from multiple stack overflow topics into the following, hopefully it helped someone :)
kid="3UT......."
iss="69a6de78-....."
keyPath="somepath/AuthKey.p8"
iat=$(date +"%s")
exp=$(date --date="15 minutes" +"%s")
header=$(echo -n '{"alg": "ES256", "kid": "'$kid'", "typ": "JWT"}' | openssl base64 -e -A | tr '+/' '-_' | tr -d '=')
payload=$(echo -n '{"iss": "'$iss'", "iat": '$iat', "exp": '$exp', "aud": "appstoreconnect-v1"}' | openssl base64 -e -A | tr '+/' '-_' | tr -d '=')
sig=$(echo -n "$header.$payload" | openssl dgst -sha256 -sign "$keyPath" -keyform PEM | openssl asn1parse -inform DER | perl -n -e'/INTEGER :([0-9A-Z]*)$/ && print $1' | xxd -p -r | openssl base64 -e -A | tr '+/' '-_' | tr -d '=')
jwt=$(echo -n "$header.$payload.$sig")
curl -X GET \
-H "Authorization: Bearer $jwt" \
-H "Content-Type: application/json" \
https://api.appstoreconnect.apple.com/v1/apps
For reference I was able to do what I wanted using python