Why is my key not found when sending a JWT assertion to Azure AD? - bash

I'm trying to write a bash script to get access tokens from Microsoft for my app registered in AD. I can't seem to get past this error though:
"error":"invalid_client","error_description":"AADSTS700027: The certificate with identifier used to sign the client assertion is not registered on application. [Reason - The key was not found., Thumbprint of key used by client: '356134 ...
The full gist can be found here: https://gist.github.com/smaring/3a3a6779a809beecc39624aada6e2b88
Here are some of the juicy bits ...
$ openssl pkcs12 -in <your-app>.pfx -out <your-app>.pem
$ openssl x509 -outform der -in <your-app>.pem -out ${PUBLIC_CERT_FILE}
$ openssl rsa -in <your-app>.pem -out ${PRIVATE_KEY_FILE}
x5t="$(sha1sum ${PUBLIC_CERT_FILE} | awk '{print $1;}' | openssl base64 | sed s/\+/-/g |sed 's/\//_/g' | sed -E s/=+$// )"
read -r -d '' HEADER <<EOF
{
"alg": "RS256",
"typ": "JWT",
"x5t": "${x5t}"
}
EOF
HEADER_NO_WHITESPACE=$(echo "${HEADER}" | sed ':a; N; s/[[:space:]]//g; ta')
BASE64_ENCODED_HEADER=$(echo ${HEADER_NO_WHITESPACE} | openssl base64 | sed s/\+/-/g |sed 's/\//_/g' | sed -E s/=+$// )
read -r -d '' PAYLOAD <<EOF
{
"aud": "https: //login.microsoftonline.com/${TENANT_ID}/oauth2/v2.0/token",
"exp": ${exp},
"iss": "${CLIENT_ID}",
"jti": "${jti}",
"nbf": ${nbf},
"sub": "${CLIENT_ID}"
}
EOF
PAYLOAD_NO_WHITESPACE=$(echo "${PAYLOAD}" | sed ':a; N; s/[[:space:]]//g; ta')
BASE64_ENCODED_PAYLOAD=$(echo ${PAYLOAD_NO_WHITESPACE} | openssl base64 | sed s/\+/-/g |sed 's/\//_/g' | sed -E s/=+$// )
SIGNATURE=$( echo -n "${BASE64_ENCODED_HEADER}.${BASE64_ENCODED_PAYLOAD}" | \
openssl dgst -sha256 -binary -sign <(cat ${PRIVATE_KEY_FILE}) | \
openssl base64 | sed s/\+/-/g |sed 's/\//_/g' | sed -E s/=+$// )
CLIENT_ASSERTION="${BASE64_ENCODED_HEADER}.${BASE64_ENCODED_PAYLOAD}.${SIGNATURE}"
curl -s -X POST \
--header \"Content-Type: application/x-www-form-urlencoded\" \
-d \"\
scope=https%3A%2F%2Fgraph.microsoft.com%2F.default&\
grant_type=client_credentials&\
client_assertion_type=urn%3Aietf%3Aparams%3Aoauth%3Aclient-assertion-type%3Ajwt-bearer&\
client_id=${CLIENT_ID}&\
client_assertion=${CLIENT_ASSERTION}\" \
https://login.microsoftonline.com/${TENANT_ID}/oauth2/v2.0/token"

After getting a Java version of this working I figured out that I was not generating the thumbprint properly. The fix was this:
thumbprint=$(openssl x509 -in ${publicCertFile} -fingerprint -noout | awk '{split($0,a,"="); print a[2]}' )
thumbprintHex=$(echo -e "\\x${thumbprint}" | sed 's/:/\\x/g' )
thumbprintHexString=$(echo -e "${thumbprint}" | sed 's/://g' )
x5t=$(echo -ne "${thumbprintHex}" | openssl base64 | sed s/\+/-/g | sed 's/\//_/g' | sed -E s/=+$// )
I have updated the gist to the fully working bash script.
Construct a JWT assertion in bash using client-credential with cert to get an access token from Microsoft Azure Directory --> https://gist.github.com/smaring/3a3a6779a809beecc39624aada6e2b88

Related

copy var partial bytes in shell

The following contents in my shell file :
#!/usr/bin/env bash
api_params_with_timestamp="somethingXXXX"
SECRET_KEY="XXXXXXXXXXXXXX"
signature=$(echo -n "$api_params_with_timestamp" \
| openssl dgst -sha256 -hmac "$SECRET_KEY")
echo "signature"=$signature
execute this shell I got
signature=(stdin)= 3eee8d204c83381dddffgggg
I just need "3eee8d204c83381dddffgggg" part , what can I do
so that (stdin)= part would be skip ?!
Edit:
signature=$(echo -n "$api_params_with_timestamp" \
| openssl dgst -sha256 -hmac "$SECRET_KEY" | cut -c10-1000)
not perfect but works !
The openssl-dgst page doesn't provide a direct option to drop the "(stdin)= " portion of the output, but that can be done trivially with cut, grep or sed. For example:
with cut
signature=$(echo -n "$api_params_with_timestamp" \
| openssl dgst -sha256 -hmac "$SECRET_KEY" \
| cut -d ' ' -f2)
with grep
signature=$(echo -n "$api_params_with_timestamp" \
| openssl dgst -sha256 -hmac "$SECRET_KEY" \
| grep -E -o '[^ ]+$')
with sed
signature=$(echo -n "$api_params_with_timestamp" \
| openssl dgst -sha256 -hmac "$SECRET_KEY" \
| sed 's/^.*[[:space:]]//')
Note: you can also remove the line separator characters "\" and use the pipe for that purpose instead, e.g.
signature=$(echo -n "$api_params_with_timestamp" |
openssl dgst -sha256 -hmac "$SECRET_KEY" |
sed 's/^.*[[:space:]]//')
Either way is fine -- up to you.
Example Use/Output
$ api_params_with_timestamp="somethingXXXX"
> SECRET_KEY="XXXXXXXXXXXXXX"
> signature=$(echo -n "$api_params_with_timestamp" |
> openssl dgst -sha256 -hmac "$SECRET_KEY" |
> sed 's/^.*[[:space:]]//')
> echo $signature
46c86ea27df85b8ab9a8c565a344cadd1230f32ff1d773ba0fa5aa1076116d0b

How to generate an aws4 signed request in bash?

I'm trying to connect to the Scaleway S3 api in bash (it's a subset of aws s3 api). The signature model is aws4_request, and is documented. In the code below I'm uploading a file. I've reviewed every line and although I'm no bash guru, I'm pretty confident I have all the right parts to compute a correct signature, but I still get 403 error as a response. The request itself seems fine, as far as I can see in a wireshark capture. Can anyone find any issue here?
#!/bin/bash
set -e
echo "creds $SCW_ACCESS_KEY/$SCW_SECRET_KEY"
BUCKET="vni-backups"
REGION="fr-par"
HOST="$BUCKET.s3.$REGION.scw.cloud"
filepath="../import.yml"
file_sha256=$(sha256sum -b $filepath | head -c64)
fulldate=$(date +"%Y%m%dT%H%M%SZ" --utc)
shortdate=$(date +"%Y%m%d" --utc)
# Assemble canonical url
canonicalRequest="PUT
/import.yml
content-type:text/plain
host:$HOST
x-amz-content-sha256:$file_sha256
x-amz-date:$fulldate
content-type;host;x-amz-content-sha256;x-amz-date
$file_sha256"
canonReqSha=$(echo -n "$canonicalRequest" | openssl dgst -sha256 | awk '{print $2}')
echo $canonicalRequest
echo "requestHash: $canonReqSha"
echo "
---------------------------------------
"
stringToSign="AWS4-HMAC-SHA256
$fulldate
$shortdate/fr-par/s3/aws4_request
$canonReqSha"
echo $stringToSign
dateKey=$(echo -n "$shortdate" | openssl dgst -sha256 -binary -hmac "AWS4$SCW_SECRET_KEY")
regionKey=$(echo -n "fr-par" | openssl dgst -sha256 -binary -hmac "$dateKey")
serviceKey=$(echo -n "s3" | openssl dgst -sha256 -binary -hmac "$regionKey")
signingKey=$(echo -n "aws4_request" | openssl dgst -sha256 -binary -hmac "$serviceKey")
signature=$(echo -n "$stringToSign" | openssl dgst -sha256 -hmac "$signingKey" | awk '{print $2}')
echo "signature: $signature"
echo "
---------------------------------------
"
# Make request
curl -X PUT \
-H "Content-Type: text/plain" \
-H "x-amz-content-sha256: $file_sha256" \
-H "x-amz-date: $fulldate" \
-H "Authorization: AWS4-HMAC-SHA256" \
-H "Credential: $SCW_ACCESS_KEY/${shortdate}/$REGION/s3/aws4_request" \
-H "SignedHeaders: content-type;host;x-amz-content-sha256;x-amz-date" \
-H "Signature: $signature" \
--data-binary #../import.yml \
"http://$BUCKET.s3.$REGION.scw.cloud/import.yml"
The request is done in clear http just because it's simpler to capture it with network analysis tool (e.g. wireshark) than https.

Why do i have an empty file after sending stdout to it

Using bash i am executing following commands to filter information from a certificate
openssl s_client -connect google.com:443 < /dev/null > cert.pem
openssl x509 -in cert.pem -noout -subject > commonName
tr "," "\n" < commonName | sed -nr '/CN/p' | tr -d ' /t' > commonName
The last command leaves the file "commonName" empty, i wonder why this is.
If i instead append the file ">>" the desired filtered output is shown but unfiltered content remains.
Leaves file empty
tr "," "\n" < commonName | sed -nr '/CN/p' | tr -d ' /t' > commonName
Works but with undesired content
tr "," "\n" < commonName | sed -nr '/CN/p' | tr -d ' /t' >> commonName
Edit,
Might add that sending to a file with a new name works as intended. Changing "commonName" to "test" for example.
Thanks in advance!
/R
You can't edit the file with other commands and sed in only one pipe (the way you do it). You need a temporary file:
openssl s_client -connect google.com:443 < /dev/null > cert.pem
openssl x509 -in cert.pem -noout -subject > commonName
tr "," "\n" < commonName | sed -nr '/CN/p' | tr -d ' /t' > /tmp/temp
mv /tmp/temp commonName
And a better way to achieve the whole script, without temporary file:
openssl s_client -connect google.com:443 < /dev/null > cert.pem
openssl x509 -in cert.pem -noout -subject |
tr "," "\n" |
grep -o 'CN .*' > commonName

Uploading to Amazon S3 via curl route

I am trying to set up a file upload REST API via Spring Boot.
I currently have a list/GET method curl http://localhost:8080/api/aws/s3/list which returns a list of objects that currently exist in the bucket.
For upload, I have been trying:
curl -F "data=#test.txt" http://localhost:8080/api/aws/s3/upload -i
Which produces:
HTTP/1.1 100 Continue
HTTP/1.1 200 OK
Date: Sun, 25 Jun 2017 23:28:36 GMT
X-Application-Context: application
Content-Type: application/json;charset=utf-8
Transfer-Encoding: chunked
But when I look at the bucket, it hasn't been updated with the new file.
Would this be a permissions issue on AWS? Only my account has read and write access. The user and group I created have admin privileges. I didn't add a Bucket Policy. This is my CORS configuration:
<?xml version="1.0" encoding="UTF-8"?>
<CORSConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
<CORSRule>
<AllowedOrigin>*</AllowedOrigin>
<AllowedMethod>GET</AllowedMethod>
<AllowedMethod>POST</AllowedMethod>
<AllowedMethod>DELETE</AllowedMethod>
<MaxAgeSeconds>3000</MaxAgeSeconds>
<AllowedHeader>Authorization</AllowedHeader>
</CORSRule>
</CORSConfiguration>
Here is the upload section of my S3 Controller in spring:
#RequestMapping(value = "/upload", method = RequestMethod.POST)
public List<PutObjectResult> upload(#RequestParam("file") MultipartFile[] multipartFiles) {
return s3Wrapper.upload(multipartFiles);
}
You should specify your using bucket name and some parameters for S3. And I guess it's PUT, not POST. There are several command line samples in the internet.
file=/path/to/file/to/upload.tar.gz
bucket=your-bucket
resource="/${bucket}/${file}"
contentType="application/x-compressed-tar"
dateValue=`date -R`
stringToSign="PUT\n\n${contentType}\n${dateValue}\n${resource}"
s3Key=xxxxxxxxxxxxxxxxxxxx
s3Secret=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
signature=`echo -en ${stringToSign} | openssl sha1 -hmac ${s3Secret} -binary | base64`
curl -X PUT -T "${file}" \
-H "Host: ${bucket}.s3.amazonaws.com" \
-H "Date: ${dateValue}" \
-H "Content-Type: ${contentType}" \
-H "Authorization: AWS ${s3Key}:${signature}" \
https://${bucket}.s3.amazonaws.com/${file}
Uploading to S3 in 18 lines of Shell (used to upload builds for http://soltrader.net)
Uploading to S3 in Bash
#run this code on ec2 linux with s3 write role
TOKEN=`curl -s -X PUT "http://169.254.169.254/latest/api/token" -H "X-aws-ec2-metadata-token-ttl-seconds: 21600"`
xamztoken=`curl -s -H "X-aws-ec2-metadata-token: $TOKEN" http://169.254.169.254/latest/meta-data/iam/security-credentials/ec2files3 | jq -r ".Token"`
yyyymmdd=`date +%Y%m%d`
s3Bucket="testfiles3bc"
bucketLocation="us-east-1"
s3SecretKey=`curl -s -H "X-aws-ec2-metadata-token: $TOKEN" http://169.254.169.254/latest/meta-data/iam/security-credentials/ec2files3 | jq -r '.SecretAccessKey'`
s3AccessKey=`curl -s -H "X-aws-ec2-metadata-token: $TOKEN" http://169.254.169.254/latest/meta-data/iam/security-credentials/ec2files3 | jq -r ".AccessKeyId"`
endpoint="testfiles3bc.s3.amazonaws.com"
fileName="image.gif"
contentLength=`cat ${fileName} | wc -c`
contentHash=`openssl sha -sha256 -hex ${fileName} | sed 's/.* //'`
contentType=`file -b --mime-type $fileName`
b64=`openssl md5 -binary "$fileName" | openssl base64`
acl="private"
date=`date -u +%Y%m%dT%H%M%SZ`
expdate_s="2022-12-30T12:00:00.000Z"
region="us-east-1"
p=$(cat <<POLICY | openssl base64 | tr -d \\n
{ "expiration": "${expdate_s}T12:00:00.000Z",
"conditions": [
{"acl": "$acl" },
{"bucket": "$s3Bucket" },
["starts-with", "\$key", ""],
["starts-with", "$contentType", "image/"],
{"x-amz-date": "$date" },
{"content-md5": "$b64" },
{"x-amz-credential": "${s3AccessKey}/${yyyymmdd}/${region}/s3/aws4_request" },
{"x-amz-security-token": "${xamztoken}" },
{"x-amz-algorithm": "AWS4-HMAC-SHA256" }
]
}
POLICY
)
stringToSign=$p
echo "----------------- canonicalRequest --------------------"
echo -e ${canonicalRequest}
echo "----------------- stringToSign --------------------"
echo -e ${stringToSign}
echo "-------------------------------------------------------"
# calculate the signing key
DateKey=`echo -n "${yyyymmdd}" | openssl sha -sha256 -hex -hmac "AWS4${s3SecretKey}" | sed 's/.* //'`
DateRegionKey=`echo -n "${bucketLocation}" | openssl sha -sha256 -hex -mac HMAC -macopt hexkey:${DateKey} | sed 's/.* //'`
DateRegionServiceKey=`echo -n "s3" | openssl sha -sha256 -hex -mac HMAC -macopt hexkey:${DateRegionKey} | sed 's/.* //'`
SigningKey=`echo -n "aws4_request" | openssl sha -sha256 -hex -mac HMAC -macopt hexkey:${DateRegionServiceKey} | sed 's/.* //'`
# then, once more a HMAC for the signature
signature=`echo -en ${p} | openssl sha -sha256 -hex -mac HMAC -macopt hexkey:${SigningKey} | sed 's/.* //'`
key_and_sig_args="-F X-Amz-Credential=${s3AccessKey}/${yyyymmdd}/${region}/s3/aws4_request -F X-Amz-Algorithm=AWS4-HMAC-SHA256 -F X-Amz-Signature=$signature -F X-Amz-Date=${date}"
curl -v \
-F key=$fileName \
-F acl=$acl \
$key_and_sig_args \
-F "content-md5= ${b64}" \
-F "Policy=$p" \
-F "X-Amz-Security-Token= ${xamztoken}" \
-F "file=#$fileName" \
https://${s3Bucket}.s3.amazonaws.com/
It was because the parameter I set in my controller was file but in my curl route I kept using data

How to verify ECDSA and RSA certificates

I am going to run acme-tiny on a central webserver in order to get the certificates for my two Nginx reverse proxies issued. The newly created certificates are published over https and available to the reverse proxies via download.
I want the Nginx servers to check the newly created certificates before replacing the old certificates with the new ones. For this purpose, I wrote the following bash script, which is run on each Nginx server. I would like to know whether I missed something or whether you have ideas for improvement. Or is there a better way to realise this?
#!/bin/bash
set -e
# Commands for deriving the public keys:
# openssl ec -in ecdsa.key -pubout > ecdsa_public_key.pem
# openssl rsa -in rsa.key -pubout > rsa_public_key.pem
curl -O https://example.org/ecdsa.pem
curl -O https://example.org/intermediate.pem
curl -O https://example.org/rsa.pem
# Are the certificates not expired?
# Have they (ecdsa.pem, rsa.pem) been recently issued (validity >= 80 days)?
openssl x509 -checkend 6912000 -noout -in intermediate.pem
openssl x509 -checkend 6912000 -noout -in ecdsa.pem
openssl x509 -checkend 6912000 -noout -in rsa.pem
# Do the private keys and certificates belong together?
openssl x509 -in ecdsa.pem -pubkey | \
sed -n '/-----BEGIN PUBLIC KEY-----/,/-----END PUBLIC KEY-----/p' | \
cmp - ecdsa_public_key.pem
openssl x509 -in rsa.pem -pubkey | \
sed -n '/-----BEGIN PUBLIC KEY-----/,/-----END PUBLIC KEY-----/p' | \
cmp - rsa_public_key.pem
# Is the certificate chain valid?
openssl verify -CAfile /etc/ssl/certs/ca-certificates.crt intermediate.pem
openssl verify -CAfile intermediate.pem ecdsa.pem
openssl verify -CAfile intermediate.pem rsa.pem
# Are the certificates issued for the correct domain names?
(openssl x509 -noout -subject -in ecdsa.pem | awk -F'CN=' '{print $2}' | \
awk -F'/' '{print $1}'; \
openssl x509 -noout -text -in ecdsa.pem | grep "^[[:space:]]*DNS:" | \
xargs | tr ' ' '\n' | grep ^DNS | sed 's/^DNS://' | sed 's/,$//')| \
sort | uniq | cmp - domains.txt
(openssl x509 -noout -subject -in rsa.pem | awk -F'CN=' '{print $2}' | \
awk -F'/' '{print $1}'; \
openssl x509 -noout -text -in rsa.pem | grep "^[[:space:]]*DNS:" | \
xargs | tr ' ' '\n' | grep ^DNS | sed 's/^DNS://' | sed 's/,$//')| \
sort | uniq | cmp - domains.txt
UPDATE 1:
As I have stated I want to check every aspect of the certificate not just the validity of the certificate chain. It currently checks:
the chains validity,
whether private key and certificate match,
whether the cert has been recently issued making replacement of the older cert necessary
whether the cert has been issued for the desired domain names
Therefore, it isn't a duplicate to the linked questions. And, I would like to know whether I have forgotten some check or whether there is space for improvement. Below is an update to the bash script. Beware I have not tested it thoroughly, yet.
#!/bin/bash
set -e
# Execute the script:
# ( cd /path_to_workdir && \
# su - james -c "./check_cert.sh https://www.example.org intermediate.pem ecdsa.pem ecdsa_pubkey.pem /etc/ssl/certs/ca-certificates.crt domains.txt pass.txt" && \
# su - james -c "./check_cert.sh https://www.example.org intermediate.pem rsa.pem rsa_pubkey.pem /etc/ssl/certs/ca-certificates.crt domains.txt pass.txt" && \
# cat ecdsa.pem intermediate.pem > /etc/nginx/ssl/ecdsa_bundle.pem ) && \
# cat rsa.pem intermediate.pem > /etc/nginx/ssl/rsa_bundle.pem ) && \
# /etc/init.d/nginx reload
# Download URL, e.g. https://example.org
URL=$1
# Intermediate certificate of certificate chain
INTERMEDIATE=$2
# Issued certificate, e.g. ecdsa.pem
CERT=$3
# Public key derived from private key via:
# openssl ec -in ecdsa.key -pubout > ecdsa_pubkey.pem
# openssl rsa -in rsa.key -pubout > rsa_pubkey.pem
PUBKEY=$4
# ca-certificates file, e.g. /etc/ssl/certs/ca-certificates.crt
CACERTS=$5
# Domains that the cert should cover
DOMAINS=$6
# password-file.txt possible content:
# machine example.org login james password H3Llo
PASS=$7
# Download files if newer than local.
if [[ $(curl -s -O --netrc-file "$PASS" -w "%{http_code}" -z "$CERT" "$URL/$CERT") -eq 304 ]]; then
echo "No new certificate issued. Nothing to do!"
exit 1
fi
curl -s -O --netrc-file "$PASS" -z "$INTERMEDIATE" "$URL/$INTERMEDIATE"
# Is the certificate chain valid?
openssl verify -CAfile "$CACERTS" -untrusted "$INTERMEDIATE" "$CERT"
# Do the private keys and certificates belong together?
openssl x509 -in "$CERT" -pubkey | \
sed -n '/-----BEGIN PUBLIC KEY-----/,/-----END PUBLIC KEY-----/p' | \
cmp - "${PUBKEY}"
# Has the Let's Encrypt certificate been recently issued (validity >= 80 days)?
openssl x509 -checkend 6912000 -noout -in "$CERT"
# Are the certificates issued for the correct domain names?
(openssl x509 -noout -subject -in "$CERT" | awk -F'CN=' '{print $2}' | \
awk -F'/' '{print $1}'; \
openssl x509 -noout -text -in "$CERT" | grep "^[[:space:]]*DNS:" | \
xargs | tr ' ' '\n' | grep ^DNS | sed 's/^DNS://' | sed 's/,$//')| \
sort | uniq | cmp - "$DOMAINS"
The code might be run via cronjob:
0 1 * * * /usr/local/bin/update_cert.sh >/dev/null 2>&1
The content of update_cert.sh might be:
#!/bin/bash
( cd /path_to_workdir && \
su - james -c "./check_cert.sh https://www.example.org intermediate.pem ecdsa.pem ecdsa_pubkey.pem /etc/ssl/certs/ca-certificates.crt domains.txt pass.txt" && \
su - james -c "./check_cert.sh https://www.example.org intermediate.pem rsa.pem rsa_pubkey.pem /etc/ssl/certs/ca-certificates.crt domains.txt pass.txt" && \
cat ecdsa.pem intermediate.pem > /etc/nginx/ssl/ecdsa_bundle.pem ) && \
cat rsa.pem intermediate.pem > /etc/nginx/ssl/rsa_bundle.pem ) && \
/etc/init.d/nginx reload

Resources