#!/usr/bin/env python # # Copyright (c) 2015-2021 by cisco Systems, Inc. # All rights reserved. #--------------------------------------------------------------------------- # This script is used to verify three tier certificate # chain and verify file signature using openssl. import sys import os import argparse import commands import urllib import hashlib import shutil import subprocess import tempfile #constants PROG_NAME = "cisco_x509_verify_release.py" #menu constants MENU_MAIN_DESC = "Image signing application. This will verify the certificate chain"\ " and image signature using openssl commands." CISCO_X509_VERIFY_REL_VERSION = "PY2.1.6" #color constants FAIL = '\033[91m' #red WARNING = '\033[93m' #orange HEADER = '\033[95m' #pink OKGREEN = '\033[92m' OKBLUE = '\033[94m' OKCYAN = '\033[96m' #using for debugs OKWHITE = '\033[97m' ENDC = '\033[0m' BOLD = '\033[1m' UNDERLINE = '\033[4m' #globals rel_root_cert_file = "crcam2.cer" rel_root_cert_url = "http://www.cisco.com/security/pki/certs/"+rel_root_cert_file rel_root_cert_sha256 = "cd85167b3935e27bcc3b0f5fa24c8457882d0bb994f88269a7f72829d957eae9" rel_subca_cert_file = "innerspace.cer" rel_subca_cert_url = "http://www.cisco.com/security/pki/certs/"+rel_subca_cert_file rel_subca_cert_sha256 = "f31e6b39dae6996fdf2045a61be8bd3688a86dfd06c46ce71af4af239f411c56" cont_rel_root_cert_file = "crrca.cer" cont_rel_root_cert_url = "http://www.cisco.com/security/pki/certs/"+cont_rel_root_cert_file cont_rel_root_cert_sha256 = "1997ba40d0d6fafb672c67d5200d0c0846b540d1e431634c2a8923508b2974c0" xrcont_rel_subca_cert_file = "xrcrrsca.cer" xrcont_rel_subca_cert_url = "http://www.cisco.com/security/pki/certs/"+xrcont_rel_subca_cert_file xrcont_rel_subca_cert_sha256 = "c86f7c94650ba37ac0f0a31fe132f375447d90a5319f455298b5620142a1da3e" vir_rel_root_cert_file = "vuefirca.cer" vir_rel_root_cert_url = "http://www.cisco.com/security/pki/certs/"+vir_rel_root_cert_file vir_rel_root_cert_sha256 = "67501b730fe840f05cba7a84cfd26e0f11311e5f9b16ce60004fd97d3f55de4c" vir_rel_subca_cert_file = "vuefiscav2.cer" vir_rel_subca_cert_url = "http://www.cisco.com/security/pki/certs/"+vir_rel_subca_cert_file vir_rel_subca_cert_sha256 = "4f5d920ed0f53651f68789534dc190fe964dd174ff251381b207a129b82d84c0" local_dir = None tempdir = None """ LOG() function prints string in requested color on terminal """ def LOG(color, string): if(sys.platform.find("linux") == -1): if(color == FAIL): print "Error Log: " + string else: print string else: if(color == FAIL): print color + "Error log: " + string + ENDC else: print color + string + ENDC """ execute_command(cmd) function executes the shell command and returns status and output. """ def execute_command(cmd): p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=False) out,_ = p.communicate() status = p.returncode return status, out """ cleanup() function deletes any newly created temp files. """ def cleanup(args, ld, td): if os.path.exists("ee_pubkey.pem"): os.remove("ee_pubkey.pem") #if script found rootca/subca cert in cwd, then dont delete the file. #otherwise, it's a copy or download which needs to be cleaned up. if(ld != None and os.path.abspath(ld) == os.getcwd()): #LOG(OKCYAN, "certs came from cwd") if(td != None): #LOG(OKCYAN, "tempdir previously set: "+td) if(os.path.exists(td)): LOG(OKGREEN, "Restoring local_dir certs and removing "+td) shutil.copyfile(td+"/"+l_rel_root_cert_file, ld+"/"+l_rel_root_cert_file) shutil.copyfile(td+"/"+l_rel_subca_cert_file, ld+"/"+l_rel_subca_cert_file) shutil.rmtree(td) else: td = td LOG(WARNING, "tempdir "+td+" does not exist!") return if(root_cert != None and os.path.exists(root_cert)): os.remove(root_cert) if(subca_cert != None and os.path.exists(subca_cert)): os.remove(subca_cert) """ url_exists() function checks if provided url link is valid or not """ def url_exists(url): if(urllib.urlopen(url).code >= 400): return False else: return True """ verify_cert_sha256() function computes the sha256sum of certificate and compares with the expected value. """ def verify_cert_sha256(cert_name, expected_sha256): #LOG(OKCYAN, "Computing sha256sum for "+cert_name) cert_sha256 = hashlib.sha256(open(cert_name, 'rb').read()).hexdigest() if(cert_sha256 == expected_sha256): #LOG(OKCYAN, "Computed sha256sum of "+cert_name+" = "+cert_sha256) #LOG(OKCYAN, "Expected sha256sum of "+cert_name+" = "+expected_sha256) return True else: LOG(FAIL, "Computed sha256sum of "+cert_name+" = "+cert_sha256) LOG(FAIL, "Expected sha256sum of "+cert_name+" = "+expected_sha256) return False """ convert_cert_to_pem() function converts DER formatted cert to PEM format. """ def convert_cert_to_pem(cert_name): with open(cert_name, 'r') as f: first_line = f.readline() if(first_line.find("-----BEGIN CERTIFICATE-----") == -1): cmd = ['openssl', 'x509', '-inform', 'der', '-in', cert_name, '-out', cert_name] status, out = execute_command(cmd) if(out.find("error") != -1): LOG(FAIL, "Failed to convert "+cert_name+"from DER to PEM.") LOG(FAIL, out) return False return True """ download_cert() function downloads a certificate from provided url link if its a valid url. """ def download_cert(cert_url): cert_name = "N/A" if(url_exists(cert_url)): cert_name = cert_url.split('/')[-1] urllib.urlretrieve(cert_url, cert_name) else: LOG(FAIL, "Download certificate failed.") return cert_name """ copy_local_cert() function copies cert_file from 'local_dir' """ def copy_local_cert(cert_file, ld): cert_name = "N/A" cert_full_path = ld + "/" + cert_file if(os.path.isfile(cert_full_path)): cert_name = cert_file if(os.path.abspath(ld) != os.getcwd()): shutil.copyfile(cert_full_path, cert_name) else: LOG(FAIL, "Copy local certificate failed.") return cert_name """ verify_3tier_cert_chain() function verifies the 3 tier cert chain """ def verify_3tier_cert_chain(ee_cert): #verify root and subca certificate cmd = ['openssl', 'verify', '-CAfile', root_cert, subca_cert] status, out = execute_command(cmd) if(out.find("error") != -1 or status != 0): LOG(FAIL, "Verification of root and subca certificate failed.") LOG(FAIL, out) return -1 #verify end-entity certificate chain cmd = ['openssl', 'verify', '-CAfile', root_cert, '-untrusted', subca_cert, ee_cert] status, out = execute_command(cmd) if(out.find("error") != -1 or status != 0): LOG(FAIL, "Failed to verify root, subca and end-entity certificate chain.") LOG(FAIL, out) return -1 else: LOG(OKGREEN, "Successfully verified root, subca and end-entity certificate chain.") return status """ fetch_pubkey_from_cert() function retrieves public key from x509 PEM certificate. """ def fetch_pubkey_from_cert(cert_name): cmd = ['openssl', 'x509', '-pubkey', '-noout', '-in', cert_name] f = open("ee_pubkey.pem", "w") p = subprocess.Popen(cmd, stdout=f, stderr=subprocess.PIPE, shell=False) _,err = p.communicate() status = p.returncode f.close() if(status != 0): LOG(FAIL, "Failed to fetch a public key from x509 PEM certificate") LOG(FAIL, err) else: LOG(OKGREEN, "Successfully fetched a public key from "+cert_name+".") return status """ verify_dgst_signature() function verifies the signature of an image. """ def verify_dgst_signature(args): if(args.sha256): sha_version = "-sha256" else: sha_version = "-sha512" cmd = ['openssl', 'dgst', sha_version, '-verify', 'ee_pubkey.pem', '-signature', args.signature, args.image_name] status, out = execute_command(cmd) if(status != 0): LOG(FAIL, "Failed to verify dgst signature of "+args.image_name+".") LOG(FAIL, out) return status """ verify_smime_signature() function verifies the openssl smime signature of an image. """ def verify_smime_signature(args): cmd = ['openssl', 'smime', '-verify', '-binary', '-in', args.signature, '-inform', args.sig_type, '-content', args.image_name, '-noverify', '-nointern', '-certfile', args.ee_cert, '-out', '/dev/null'] status, out = execute_command(cmd) if(status != 0): LOG(FAIL, "Failed to verify smime signature of "+args.image_name+".") LOG(FAIL, out) return status """ verify_signature() function verifies the image signature using either smime or dgst openssl command. """ def verify_signature(args): if(args.verify_type == "smime"): status = verify_smime_signature(args) else: status = fetch_pubkey_from_cert(args.ee_cert) if(status != 0): return 1 status = verify_dgst_signature(args) return status """ command_handler() is a handler function """ def command_handler(args): global root_cert global subca_cert global l_rel_root_cert_file global l_rel_subca_cert_file local_dir = None tempdir = None root_cert = None subca_cert = None #validate and download root certificate l_rel_root_cert_file = "" l_rel_root_cert_url = "" l_rel_root_cert_sha256 = "" l_rel_subca_cert_file = "" l_rel_subca_cert_url = "" l_rel_subca_cert_sha256 = "" if (args.virtual): l_rel_root_cert_file = vir_rel_root_cert_file l_rel_root_cert_url = vir_rel_root_cert_url l_rel_root_cert_sha256 = vir_rel_root_cert_sha256 l_rel_subca_cert_file = vir_rel_subca_cert_file l_rel_subca_cert_url = vir_rel_subca_cert_url l_rel_subca_cert_sha256 = vir_rel_subca_cert_sha256 elif (args.container): l_rel_root_cert_file = cont_rel_root_cert_file l_rel_root_cert_url = cont_rel_root_cert_url l_rel_root_cert_sha256 = cont_rel_root_cert_sha256 if (args.container == 'xr'): l_rel_subca_cert_file = xrcont_rel_subca_cert_file l_rel_subca_cert_url = xrcont_rel_subca_cert_url l_rel_subca_cert_sha256 = xrcont_rel_subca_cert_sha256 else : l_rel_root_cert_file = rel_root_cert_file l_rel_root_cert_url = rel_root_cert_url l_rel_root_cert_sha256 = rel_root_cert_sha256 l_rel_subca_cert_file = rel_subca_cert_file l_rel_subca_cert_url = rel_subca_cert_url l_rel_subca_cert_sha256 = rel_subca_cert_sha256 #setup local dir if specified if(args.cert_dir != None): local_dir = args.cert_dir #LOG(OKCYAN, "local_dir set to '"+args.cert_dir+"'") if(os.path.abspath(local_dir) == os.getcwd()): tempdir = tempfile.mkdtemp(); shutil.copyfile(l_rel_root_cert_file, tempdir+"/"+l_rel_root_cert_file) shutil.copyfile(l_rel_subca_cert_file, tempdir+"/"+l_rel_subca_cert_file) LOG(OKGREEN, "Caching working directory certs to tempdir "+tempdir) # Download rootCA certificate or use local cache if(local_dir != None): LOG(OKGREEN, "Retrieving local CA certificate") root_cert = copy_local_cert(l_rel_root_cert_file, local_dir) else: LOG(OKGREEN, "Retrieving CA certificate from "+l_rel_root_cert_url+" ...") root_cert = download_cert(l_rel_root_cert_url) if(root_cert != "N/A"): if(verify_cert_sha256(root_cert, l_rel_root_cert_sha256)): convert_cert_to_pem(root_cert) LOG(OKGREEN, "Successfully retrieved and verified "+root_cert+".") else: cleanup(args, local_dir, tempdir) return 1 else: cleanup(args, local_dir, tempdir) return 1 # Download SubCA certificate or use local cache if(local_dir != None): LOG(OKGREEN, "Retrieving local SubCA certificate") subca_cert = copy_local_cert(l_rel_subca_cert_file, local_dir) else: LOG(OKGREEN, "Retrieving SubCA certificate from "+l_rel_subca_cert_url+" ...") subca_cert = download_cert(l_rel_subca_cert_url) if(subca_cert != "N/A"): if(verify_cert_sha256(subca_cert, l_rel_subca_cert_sha256)): convert_cert_to_pem(subca_cert) LOG(OKGREEN, "Successfully retrieved and verified "+subca_cert+".") else: cleanup(args, local_dir, tempdir) return 1 else: cleanup(args, local_dir, tempdir) return 1 #verify 3 tier certificate chain status = verify_3tier_cert_chain(args.ee_cert) if(status != 0): cleanup(args, local_dir, tempdir) return status #verify signature status = verify_signature(args) if(status == 0): LOG(OKGREEN, "Successfully verified the signature of "+args.image_name+" using "+args.ee_cert) cleanup(args, local_dir, tempdir) return status """ verify_parser_options() is used to verify input arguments. It returns error if any required argument is missing. """ def verify_parser_options(args): if(args.image_name != None): if(not os.path.exists(args.image_name)): LOG(FAIL, "'"+args.image_name+"' does not exist") return 1 if(args.signature != None): if(not os.path.exists(args.signature)): LOG(FAIL, "'"+args.signature+"' does not exist") return 1 if(args.ee_cert != None): if(not os.path.exists(args.ee_cert)): LOG(FAIL, "'"+args.ee_cert+"' does not exist") return 1 if(args.cert_dir != None): #LOG(OKCYAN, "'"+args.cert_dir+"' parameter detected") if(not os.path.exists(args.cert_dir)): LOG(FAIL, "'"+args.cert_dir+"' does not exist") return 1 return 0 """ arg_parser() is used to setup command line options. """ def arg_parser(): #setup main parser pmain = argparse.ArgumentParser(prog=PROG_NAME, description=MENU_MAIN_DESC) #version argument pmain.add_argument("-V", "--version", action='version', version='%(prog)s (version ' + CISCO_X509_VERIFY_REL_VERSION + ')') #certificate argument pmain.add_argument("-e", "--ee_cert", metavar = "", dest = "ee_cert", required = True, help = "Local path to End-entity certificate in PEM format") #signature file argument pmain.add_argument("-s", "--signature", metavar = "", dest = "signature", required = True, help = "Filename containing image signature") #input image argument pmain.add_argument("-i", "--image_name", metavar = "", dest = "image_name", required = True, help = "Image name") #cert_dir argument pmain.add_argument("-c", "--cert_dir", metavar = "", dest = "cert_dir", required = False, help = "directory to find local certs") #virtual cert chain pmain.add_argument("-root_type", "--virtual", dest = "virtual", required = False, help = "Using virtual cert chain ") #container cert chain pmain.add_argument("--container", choices = ['xr'], dest = "container", required = False, help = "Using container cert chain (ignored if virtual set)") #openssl verify type argument pmain.add_argument("-v", "--verify_type", choices = ['dgst', 'smime'], default = 'dgst', dest = "verify_type", required = False, help = "Verify type: dgst|smime") #openssl verify type argument for SMIME only pmain.add_argument("--sig_type", choices = ['DER', 'PEM'], default = 'PEM', dest = "sig_type", required = False, help = "signature encoding (optional, used only for smime, dflt is PEM)") #hashing algorithm argument group_input = pmain.add_mutually_exclusive_group(required=False) group_input.add_argument("-sha256", action = "store_true", dest = "sha256", help = "Using sha256 hashing algorithm (required only for 'dgst')") group_input.add_argument("-sha512", action = "store_true", dest = "sha512", help = "Using sha512 hashing algorithm (required only for 'dgst')") pmain.set_defaults(func=command_handler) return pmain """ main function for STO image signing script to verify cert chain and bulk hash signatures. """ def main(): #setup console menu parsers pmain = arg_parser() #parse args args = pmain.parse_args() #manually verify the input arguments if(verify_parser_options(args) != 0): return 1 #invoke appropriate handler function status = args.func(args) return status """ Starting point """ if __name__ == "__main__": sys.exit(main())