Hashicorp Vault RSASSA-PSS Prehashed cannot be verified with OpenSSL

Viewed 234

I am trying to use Hashicorp Vault to sign a file with RSASSA-PSS-4096. The file is too big for sending it to the server directly, so I want to prehash it locally and then send the digest via POST request to the Vault transit engine.

While the Vault signature verification works, the OpenSSL verification fails. Please see my drafted script:

# Calculate SHA256 hash and convert to base64
sha256sum_base64=$(openssl dgst -sha256 -binary $1 | base64)

# Sign Hash Value with Vault
json_response=$(curl -s \
    --header "X-Vault-Token: $(cat token)" \
    --request POST \
    --data-binary '{"input": "'"$sha256sum_base64"'", "prehashed": true, "signature_algorithm": "pss", "hash_algorithm": "sha2-256"}' \
    http://127.0.0.1:8200/v1/transit/sign/rsa_4096)

# Extract base64 signature from the json response.
signature_base64=$(echo $json_response | python3 -c "import sys, json; print(json.load(sys.stdin)['data']['signature'])" | cut -d ":" -f 3)

# Convert signature from base64 to binary and write to file
sigfile=$1__signature.bin
echo $signature_base64 | openssl base64 -d -A -in - -out $sigfile

# Check whether signature is valid via OpenSSL
echo "OpenSSL --> " $(openssl dgst -sha256 -sigopt rsa_padding_mode:pss -sigopt rsa_pss_saltlen:32 -verify rsa_4096_pub.pem -signature $sigfile $1)

# Check whether signature is valid via Vault
signature_vaultformat="vault:v1:$signature_base64"
verify_response=$(curl -s \
    --header "X-Vault-Token: $(cat token)" \
    --request POST \
    --data-binary '{"input": "'"$sha256sum_base64"'", "signature": "'"$signature_vaultformat"'", "prehashed": true, "signature_algorithm": "pss", "hash_algorithm": "sha2-256"}' \
    http://127.0.0.1:8200/v1/transit/verify/rsa_4096) 
echo "Vault Verify --> " $(echo $verify_response | python3 -c "import sys, json; print(json.load(sys.stdin)['data']['valid'])")

What could be the problem here? I played with rsa_pss_saltlen parameters (e.g. -1) without success. Is there another OpenSSL parameter I am missing? Do I need to consider something for EMSA-PSS?

1 Answers

Here is a proof-of-concept where you can sign a piece of text using the Transit secrets engine and then verify the signature using openssl rather than using the Transit secrets engine again.

# Define our plaintext
TEXT="abc123"

# Encode our plaintext with base64
B64_ENCODED_TEXT=$(echo $TEXT | base64)

# Reset the transit secrets engine
vault secrets disable transit
vault secrets enable transit

# Create a key called 'test' using 'rsa-2048'
vault write -f transit/keys/test \
    type='rsa-2048'

# Export the public key from the transit secret engine key named 'test'
PUBLIC_KEY=$(vault read -format=json transit/keys/test | \
    jq -r '.data.keys."1".public_key')

# Sign our base64 encoded text using our transit key named 'test' and
#  capture the signature
SIGNATURE=$(vault write -format=json transit/sign/test/sha2-256 \
    input="$B64_ENCODED_TEXT" \
    signature_algorithm="pss" | \
    jq -r '.data.signature')

# Demonstrate that we can use transit to verify our signature
printf "\nVerifying signature using Vault Transit...\n"
vault write transit/verify/test/sha2-256 \
    signature_algorithm="pss" \
    input=$B64_ENCODED_TEXT \
    signature=$SIGNATURE

# Write out public key to a file
echo $PUBLIC_KEY > publickey.pem

# Remove the metadata from the Vault supplied signature and decode the
#  signature using base64, writing the raw signature to a file
echo $SIGNATURE | cut -d':' -f3 | base64 -d > sig

# Write the non-encoded plaintext to a file
echo "$TEXT" > mytext

# Use openssl to verify the signature using the base64 decoded raw signature
#  along with the public key and the non-encoded plaintext
printf "\nVerifying signature using openssl...\n"
openssl dgst \
    -sha256 \
    -verify publickey.pem \
    -signature sig \
    -sigopt rsa_padding_mode:pss \
    mytext

Some important notes below:

  • Note that ALL data that is signed by Vault Transit secret engine must first be base64 encoded.
  • When using openssl to verify a signature, you must make sure that you are using the correct signature algorithm.
  • When Vault provides a signature, it's in the following format: vault:v1:8SDd3WHDOjf7mq69... where vault denotes that it was signed by Vault, v1 denotes the version of the key and the final part is the actual signature that is encoded using base64. The openssl utility requires that the signature is binary and not base64. In order to verify this signature with openssl, you must remove the first 2 parts of the Vault provided signature. You must then decode the base64 encoded signature and use the resultant binary signature when verifying with openssl.
  • When verifying with openssl you can not use use the base64 encoded version of the text, you must use the non-base64 encoded plaintext.
Related