How can a service authorize itself with another service to make requests?
For example, if Service B wants to integrate with Service A.
Token based
Service A can share a consumer id (token id) with Service B; whenever Service B wants to make a request, it can use this consumer id, and service A can identify who is making the request using consumer id.
This issue with the above approach, if traffic is exposed then shared token can be used anyone and can impersonate as service B.
Request signing
Service B will create private/public key pair and share the public key to Service A. (You should keep the private key in a secure location) Service A will generate a UUID (consumer id) and keep a mapping with this id and the shared public key of Service B.
Service A will share this UUID (consumer id) with Service B. Service B now will send a message containing consumer id signed with a private key and send it as a header (auth_signature) including plain consumer id. Service A, while processing request, checks consumer id and retrieves associated public key, and verify the signature, if the signature is valid; allow the request to process further.
The only concern with the above is that if someone could snoop the traffic, they can use this header value and make a request on behalf of Service B. For this purpose, there should be a dynamic factor that needs to be involved in signature: timestamp. Now Service B will sign the message (consumer id and current timestamp) and send plain and signed value to Service A. Service A can verify the timestamp header and see if this is within the agreed limit (5 minutes) and then verify the message with the associated public key. In this way, service A can ensure that service B is making the request.
Below is how you can generate 2048 RSA key pair.
openssl genrsa -des3 -out my_rsa_key_pair 2048
Get the private key
openssl pkcs8 -topk8 -inform PEM -in my_rsa_key_pair -outform PEM -out private_key.pem -nocrypt
Above should be stored somewhere secure. Get the public key
openssl rsa -in my_rsa_key_pair -outform PEM -pubout -out public_key.pem
Below is how you can sign and verify through command line.
openssl rsautl -sign -inkey private_key.pem -in /tmp/test | base64 > /tmp/test_priv
To verify, you can use like below
cat /tmp/test_priv | base64 -d | openssl rsautl -verify -pubin -inkey public_key.pem
In python, you can do like this; you need a python package like pycryptodome to sign and verify.
Below python program uses the private key and signs consumer id, key version, and time.
#!/usr/bin/env python
import sys
import time
from Crypto.PublicKey import RSA
from Crypto.Signature import PKCS1_v1_5
from Crypto.Hash import SHA256
from base64 import b64encode
def printError(msg=""):
if msg != "":
print("Error: " + msg)
print("Usage: ")
print(sys.argv[0] + " [private key file] [consumer ID] [Key version] time")
print("")
print("Example: ")
print(sys.argv[0] + " private_key.pem 44444444-23f9-3333-2222-111111111111 1 1644449622132")
print("")
print("")
sys.exit()
def sign_data(private_key, data):
key = open(private_key, "r").read()
rsakey = RSA.importKey(key)
signer = PKCS1_v1_5.new(rsakey)
digest = SHA256.new()
digest.update(data.encode("utf-8"))
sign = signer.sign(digest)
return b64encode(sign)
def main(argv):
ep_time = None
try:
privateKey = str(sys.argv[1])
consumedId = str(sys.argv[2])
keyVersion = str(sys.argv[3])
if len(sys.argv) == 5:
ep_time = str(sys.argv[4])
except Exception as e:
print(e)
printError()
if ep_time:
epoch_time = ep_time
else:
epoch_time = str(int(time.time()) * 1000)
data = consumedId + "\n" + epoch_time + "\n" + keyVersion + "\n"
print(data)
print("Timestamp:", epoch_time)
print("Signature:", sign_data(privateKey, data).decode())
if __name__ == "__main__":
main(sys.argv)
Running above code
python gen_auth.py private_key.pem 3bf354e2-b1b7-421a-a9ee-6dc8d6894db6 1
3bf354e2-b1b7-421a-a9ee-6dc8d6894db6
1647894496000
1
Timestamp: 1647894496000
Signature: ee2t1EqU+/YYshoiTf6CH4hlhiTsCP/QA4/La7ky0oVbnVSPuPvHC+BoYXofXd9DRZMZyzXmlzYleHATDp0LxMnDDE6gHfLeyySMcA3ZbTnSOLOOZaOgXqiCbp9Yjg2lP6IEOf/11uKKr9IbQQN66f5KWFIm/94zRQu97eicwCwZMt1bVJCWZtoC5Oo3EpluqXcdDcoXYhBsIPfubw0NuPw8VEm5FESRWIbJgKOiuhAv0ygWA2hHFfROilMQNCrr7rye1W4uqB0gdRdmZiDNkoxhfT1aldfO3rf+aXDrS0VCgNnc2QSp1zOyYLs1gthoYTcNTJqGDqBDalhISOeTLw==
Below is how you can verify the signature.
import sys
from Crypto.PublicKey import RSA
from Crypto.Signature import PKCS1_v1_5
from Crypto.Hash import SHA256
from base64 import b64decode
def printError(msg=""):
if msg != "":
print("Error: " + msg)
print("Usage: ")
print(sys.argv[0] + " [public key file] [consumer ID] [Key version] time [auth]")
print("")
print("Example: ")
print(
sys.argv[0]
+ " public_key.pem 44444444-23f9-3333-2222-111111111111 1 1644449622132 4444444423f93333222211111111111111644449622132"
)
print("")
print("")
sys.exit()
def verify_data(pub_key, data, auth):
key = open(pub_key, "r").read()
rsakey = RSA.importKey(key)
signer = PKCS1_v1_5.new(rsakey)
digest = SHA256.new()
digest.update(data.encode("utf-8"))
return signer.verify(digest, b64decode(auth))
def main(argv):
try:
pub_key = str(sys.argv[1])
consumedId = str(sys.argv[2])
keyVersion = str(sys.argv[3])
ep_time = str(sys.argv[4])
auth = str(sys.argv[5])
data = consumedId + "\n" + ep_time + "\n" + keyVersion + "\n"
print(data)
res = verify_data(pub_key, data, auth)
if res:
print("Auth signature is valid")
else:
print("Auth signature is NOT valid")
except Exception as e:
print(e)
printError()
if __name__ == "__main__":
main(sys.argv)
Running above code
python validate_gen.py public_key.pem 3bf354e2-b1b7-421a-a9ee-6dc8d6894db6 1 1647894496000 ee2t1EqU+/YYshoiTf6CH4hlhiTsCP/QA4/La7ky0oVbnVSPuPvHC+BoYXofXd9DRZMZyzXmlzYleHATDp0LxMnDDE6gHfLeyySMcA3ZbTnSOLOOZaOgXqiCbp9Yjg2lP6IEOf/11uKKr9IbQQN66f5KWFIm/94zRQu97eicwCwZMt1bVJCWZtoC5Oo3EpluqXcdDcoXYhBsIPfubw0NuPw8VEm5FESRWIbJgKOiuhAv0ygWA2hHFfROilMQNCrr7rye1W4uqB0gdRdmZiDNkoxhfT1aldfO3rf+aXDrS0VCgNnc2QSp1zOyYLs1gthoYTcNTJqGDqBDalhISOeTLw==
3bf354e2-b1b7-421a-a9ee-6dc8d6894db6
1647894496000
1
Auth signature is valid
I hope the above helps.
– RC
Comments