Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using Google Cloud Key Management Service to sign JSON Web Tokens

Edit: I found the answer. Scroll to the bottom of this question.

I am working on a NodeJS authentication server and I would like to sign JSON Web Tokens (JWT) using google signatures.

I am using Google Cloud Key Management Service (KMS) and I created a key ring and an asymmetric signing key.

This is my code to get the signature:

signatureObject = await client.asymmetricSign({ name, digest })

signature = signatureObject["0"].signature

My Google signature object looks like this:

enter image description here

My question: How do I sign a JWT using the Google signature?

Or in other words, how do I concatenate the Google signature to the (header.payload) of the JWT?

The JWT should look something like this:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ. (GoogleSignature)

The Code I am using:

signing:

async function sign(message, name) {
  hashedMessage = crypto.createHash('sha256').update(message).digest('base64');
  digest = { 'sha256': hashedMessage }

  signatureObject = await client.asymmetricSign({ name, digest }).catch((err) => console.log(err))
  signature = signatureObject["0"].signature
  signJWT(signature)
}

Creating the JWT:

function signJWT(signature) {
  header = {
    alg: "RS256",
    typ: "JWT"
  }

  payload = {
    sub: "1234567890",
    name: "John Doe",
    iat: 1516239022
  }

  JWT = base64url(JSON.stringify(header)) + "." +
        base64url(JSON.stringify(payload)) + "." + 
        ???signature??? ; // what goes here?
}

Verifying:

async function validateSignature(message, signature) {
  // Get public key
  publicKeyObject = await client.getPublicKey({ name }).catch((err) => console.log(err))
  publicKey = publicKeyObject["0"].pem

  //Verify signature
  var verifier = crypto.createVerify('sha256');
  verifier.update(message)
  var ver = verifier.verify(publicKey, signature, 'base64')

  // Returns either true for a valid signature, or false for not valid.
  return ver
}

The Answer:

I can use the toString() method like so:

signatureString = signature.toString('base64');

AND then I can get the original signature octet stream by using

var buffer = Buffer.from(theString, 'base64');
like image 767
Jim van Lienden Avatar asked Oct 27 '22 21:10

Jim van Lienden


1 Answers

You did not post your code in your question, so I do not know how you are building the JWT for signing.

[EDIT 1/18/2019 after code added to question]

Your code is doing the signature backwards. You are creating a signature and trying to attach it to the JWT Headers + Payload. You want to instead take the JWT Headers + Payload and sign that data and then attach the signature to the JWT to create a Signed-JWT.

Psuedo code using your source code:

body_b64 = base64url(JSON.stringify(header)) + "." + base64url(JSON.stringify(payload))

signature = sign(body_b64, name);

jwt = body_b64 + '.' + base64url(signature)

Note: I am not sure what data format the signature is returned by signatureObject["0"].signature. You may have to convert this before converting to base64.

[END EDIT]

Example data:

JWT Header:

{
    alg: RS256
    kid: 0123456789abcdef62afcbbf01234567890abcdef
    typ: JWT
}

JWT Payload:

{
  "azp": "123456789012-gooddogsgotoheaven.apps.googleusercontent.com",
  "aud": "123456789012-gooddogsgotoheaven.apps.googleusercontent.com",
  "sub": "123456789012345678901",
  "scope": "https://www.googleapis.com/auth/cloud-platform",
  "exp": "1547806224",
  "expires_in": "3596",
  "email": "[email protected]",
  "email_verified": "true",
  "access_type": "offline"
}

Algorithm:

SHA256withRSA

To create a Signed JWT (JWS):

Step 1: Take the JWT Header and convert to Base-64. Let's call this hdr_b64.

Step 2: Take the JWT Payload and convert to Base-64. Let's call this payload_b64.

Step 3: Concatenate the encoded header and payload with a dot . in between: hdr_b64 + '.' + payload_b64`. Let's call this body_b64.

Step 4: Normally a JWS is signed with SHA256withRSA often called "RS256" using the Private Key:

signature = sign(body_b64, RS256, private_key)

Now convert the signature to Base-64. Let call this signature_b64.

To create the final JWS:

jws = body_b64 + '.' + signature_b64.

Recommendations:

Do you want to use KMS to create Signed JWTs? I would not recommend this. There is a cost accessing keys stored in KMS. Signed-JWTs are signed with the private key and verified with the public key. How are you going to publish the public key? What performance level do you need in accessing the private and public keys (how often will you be signing and verifying)?

When you create a service account in Google Cloud Platform, a keypair is created for you. This keypair has an ID with the public key available on the Internet and the private key is present in the Service Account Json credentials file. I would use a Service Account to create Signed-JWTs instead of a keypair in KMS.

Example code in Python to create and sign:

def create_signed_jwt(pkey, pkey_id, email, scope):
    '''
    Create a Signed JWT from a service account Json credentials file
    This Signed JWT will later be exchanged for an Access Token
   '''

    import jwt

    # Google Endpoint for creating OAuth 2.0 Access Tokens from Signed-JWT
    auth_url = "https://www.googleapis.com/oauth2/v4/token"

    issued = int(time.time())
    expires = issued + expires_in   # expires_in is in seconds

    # Note: this token expires and cannot be refreshed. The token must be recreated

    # JWT Headers
    headers = {
        "kid": pkey_id, # This is the service account private key ID
        "alg": "RS256",
        "typ": "JWT"    # Google uses SHA256withRSA
    }

    # JWT Payload
    payload = {
            "iss": email,           # Issuer claim
            "sub": email,           # Issuer claim
            "aud": auth_url,        # Audience claim
            "iat": issued,          # Issued At claim
            "exp": expires,         # Expire time
            "scope": scope          # Permissions
    }

    # Encode the headers and payload and sign creating a Signed JWT (JWS)
    sig = jwt.encode(payload, pkey, algorithm="RS256", headers=headers)

    return sig
like image 66
John Hanley Avatar answered Nov 01 '22 18:11

John Hanley