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.

Service D impersonating as 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.

Sign and verify requests

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