John Hanley
John Hanley

Reputation: 81444

Google Service Account P12 Credentials - What is the certificate for?

My question is what is the certificate for in Google P12 credentials?

I wrote the following program in Python that takes a Google Service Account Credentials file in P12 (PFX) format. This program extracts the Private Key and Certificate.

The Private Key is used to sign a JWT when creating an Google Access Token (see this article on creating Access Tokens from P12 credentials). I can find no use for the certificate.

Note: This code also work with normal SSL PFX certificate bundles. The CAs are put into a separate file.

'''
Convert a Google P12 (PFX) service account into private key and certificate.
Convert an SSL Certifcate (PFX) into private key, certificate and CAs.
'''

import os
import OpenSSL.crypto

def write_CAs(filename, p12):
    ''' Write the Certificate Authorities, if any, to filename '''

    ca = p12.get_ca_certificates()

    if ca is None:
        return

    if os.path.exists(filename):
        os.remove(filename)

    print('Creating Certificate CA File:', filename)

    with open(filename, 'wb') as f:
        for cert in ca:
            f.write(OpenSSL.crypto.dump_certificate(OpenSSL.crypto.FILETYPE_PEM, cert))

def pfx_to_pem(pfx_path, pfx_password, pkey_path, pem_path, pem_ca_path):
    '''
    Decrypt the P12 (PFX) file and create a private key file and certificate file.

    Input:
        pfx_path    INPUT: This is the Google P12 file or SSL PFX certificate file
        pfx_password    INPUT: Password used to protect P12 (PFX)
        pkey_path   INPUT: File name to write the Private Key to
        pem_path    INPUT: File name to write the Certificate to
        pem_ca_path INPUT: File name to write the Certificate Authorities to
    '''

    print('Opening:', pfx_path)
    with open(pfx_path, 'rb') as f_pfx:
        pfx = f_pfx.read()

    print('Loading P12 (PFX) contents:')
    p12 = OpenSSL.crypto.load_pkcs12(pfx, pfx_password)

    print('Creating Private Key File:', pkey_path)
    with open(pkey_path, 'wb') as f:
        # Write Private Key
        f.write(OpenSSL.crypto.dump_privatekey(OpenSSL.crypto.FILETYPE_PEM, p12.get_privatekey()))

    print('Creating Certificate File:', pem_path)
    with open(pem_path, 'wb') as f:
        # Write Certificate
        f.write(OpenSSL.crypto.dump_certificate(OpenSSL.crypto.FILETYPE_PEM, p12.get_certificate()))

    # Google P12 does not have certifiate authorities but SSL PFX certificates do
    write_CAs(pem_ca_path, p12)

# Start here

pfx_to_pem(
    'compute-engine.p12',   # Google Service Account P12 file
    'notasecret',       # P12 file password
    'compute-engine.key',   # Filename to write private key
    'compute-engine.pem',   # Filename to write certificate
    'compute-engine_ca.pem')# Filename to write CAs if present

Upvotes: 0

Views: 3855

Answers (1)

John Hanley
John Hanley

Reputation: 81444

For Google Service Account credentials, the certificate is used to verify the Signed JWT.

The Signed JWT is used to request an Access Token from Google's OAuth 2.0 servers.

Service Account credentials in P12 format include the certificate as a PKCS#12 bundle. Service Account credentials in Json format have the certificate available on Google's website.

This following code is an example of using Google P12 credentials to create a Signed JWT and then verify it and display the contents.

'''
This program verifies a Signed JWT created by Google Service Account P12 credentials
First a JWT is signed with the P12 Private Key.
The certificate is extracted from the P12 file and used to verify the signature
'''

import json
import time
import base64
import jwt
import OpenSSL.crypto

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

# Set how long this token will be valid in seconds
expires_in = 3600   # Expires in 1 hour

#scopes = "https://www.googleapis.com/auth/cloud-platform"
scopes = "https://www.googleapis.com/auth/devstorage.read_only"

# Details on the Google Service Account. The email must match the Google Console.
sa_filename = 'compute-engine.p12'
sa_password = 'notasecret'
sa_email = '[email protected]'

# You can control what is verified in the JWT. For example to allow expired JWTs
# set 'verify_exp' to False
options = {
    'verify_signature': True,
    'verify_exp': True,
    'verify_nbf': True,
    'verify_iat': True,
    'verify_aud': True,
    'require_exp': False,
    'require_iat': False,
    'require_nbf': False
}

aud = 'https://www.googleapis.com/oauth2/v4/token'

def load_private_key(p12_path, p12_password):
    ''' Read the private key and return as base64 encoded '''

    # print('Opening:', p12_path)
    with open(p12_path, 'rb') as f:
        data = f.read()

    # print('Loading P12 (PFX) contents:')
    p12 = OpenSSL.crypto.load_pkcs12(data, p12_password)

    # Dump the Private Key in PKCS#1 PEM format
    pkey = OpenSSL.crypto.dump_privatekey(
            OpenSSL.crypto.FILETYPE_PEM,
            p12.get_privatekey())

    # return the private key
    return pkey

def load_public_key(p12_path, p12_password):
    ''' Read the public key and return as base64 encoded '''

    # print('Opening:', p12_path)
    with open(p12_path, 'rb') as f:
        p12_data = f.read()

    # print('Loading P12 (PFX) contents:')
    p12 = OpenSSL.crypto.load_pkcs12(p12_data, p12_password)

    public_key = OpenSSL.crypto.dump_publickey(
                    OpenSSL.crypto.FILETYPE_PEM,
                    p12.get_certificate().get_pubkey())

    # print(public_key)

    return public_key

def create_signed_jwt(p12_path, p12_password, p12_email, scope):
    ''' Create an AccessToken from a service account p12 credentials file '''

    pkey = load_private_key(p12_path, p12_password)

    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
    additional_headers = {
            "alg": "RS256",
            "typ": "JWT"    # Google uses SHA256withRSA
    }

    # JWT Payload
    payload = {
        "iss": p12_email,   # Issuer claim
        "sub": p12_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=additional_headers)

    # print(sig)

    return sig

def pad(data):
    """ pad base64 string """

    missing_padding = len(data) % 4
    data += '=' * (4 - missing_padding)
    return data

def print_jwt(signed_jwt):
    """ Print a JWT Header and Payload """

    s = signed_jwt.decode('utf-8').split('.')

    print('Header:')
    h = base64.urlsafe_b64decode(pad(s[0])).decode('utf-8')
    print(json.dumps(json.loads(h), indent=4))

    print('Payload:')
    p = base64.urlsafe_b64decode(pad(s[1])).decode('utf-8')
    print(json.dumps(json.loads(p), indent=4))

def verify_signed_jwt(signed_jwt):
    '''
    This function takes a Signed JWT and verifies it using a Google P12 service account.
    '''

    # Get the Public Key
    public_key = load_public_key(sa_filename, sa_password)

    # Verify the Signed JWT
    r = jwt.decode(signed_jwt, public_key, algorithms=["RS256"], audience=aud, options=options)

    print('Decoded JWT:')
    print(json.dumps(r, indent=4))

if __name__ == '__main__':
    s_jwt = create_signed_jwt(sa_filename, sa_password, sa_email, scopes)

    print_jwt(s_jwt)

    verify_signed_jwt(s_jwt)

Upvotes: 2

Related Questions