Vault has a secrets engine called Transit which handles cryptographic functions on data - this is commonly coined "cryptography as a service". One of the capabilities of Transit is signing data and producing a subsequent signature.
To quote a user on StackOverflow:
"Creating a digital signature for a message involves running the message through a hash function, creating a digest (a fixed-size representation) for the message. A mathematical operation is done on the digest using a secret value (a component of the private key) and a public value (a component of the public key). The result of this operation is the signature, and it is usually either attached to the message or otherwise delivered alongside it. Anyone can tell, just by having the signature and public key, if the message was signed by someone in possession of the private key."
The Transit secrets engine allows you to both sign data and verify signatures. However, it's perfectly acceptable to have a use case where you are signing data and then sending that data along with the signature to a remote host who doesn't have access to Vault to verify the signature. In this scenario, you may find that you need to verify your data's signature "offline". We will discuss one way of doing this.
This will be a hands on example where we sign data using Vault Transit secrets engine and then verify it "offline" using a ubiquitous command line application
First step is to get a Vault server up and running. For our example, we will do this using Docker.
docker run \ --cap-add=IPC_LOCK \ -e VAULT_DEV_LISTEN_ADDRESS='0.0.0.0:8200' \ -e VAULT_ADDR='http://0.0.0.0:8200' \ -e VAULT_DEV_ROOT_TOKEN_ID=root \ -e VAULT_LICENSE=$(echo $VAULT_LICENSE) \ -p 8200:8200 \ --name vault \ --detach \ hashicorp/vault-enterprise export VAULT_ADDR=http://localhost:8200 sleep 2 vault login root
Next we will prepare our data, which in this case is just simple text. Note that Transit requires that all data is signed and encoded with base64.
# Define our plaintext TEXT="abc123" # Encode our plaintext with base64 B64_ENCODED_TEXT=$(echo $TEXT | base64)
Next, enable the Transit secrets engine and create a key. Capture the resultant public key for use later on in the example.
# Enable the transit secrets engine 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')
Next, sign our base64 encoded data and then verify the signature using Vault. This is just to sanity check ourselves before moving on to verifying the signature using
# 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
Finally, time to verify the signature with
openssl. Write out the public key to a file. Write out the signature to a file. Note that the signature's metadata provided by Vault must be stripped off. Additionally, Vault encodes the signature with base64 encoding. You must decode it to it's normal state (which is binary) and then write that to a file for use with openssl. Finally, write our non-base64 encoded text to a file as well. Then use
openssl with the proper parameters to verify the signature.
# 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 # Verified OK