summaryrefslogtreecommitdiffstats
path: root/python/eme/gen-eme-voucher.py
diff options
context:
space:
mode:
Diffstat (limited to 'python/eme/gen-eme-voucher.py')
-rw-r--r--python/eme/gen-eme-voucher.py633
1 files changed, 633 insertions, 0 deletions
diff --git a/python/eme/gen-eme-voucher.py b/python/eme/gen-eme-voucher.py
new file mode 100644
index 000000000..299bc7146
--- /dev/null
+++ b/python/eme/gen-eme-voucher.py
@@ -0,0 +1,633 @@
+#!/usr/bin/env python2.7
+#
+# Copyright 2014 Adobe Systems Incorporated. All Rights Reserved.
+#
+# Adobe permits you to use, modify, and distribute this file in accordance
+# with the terms of the Mozilla Public License, v 2.0 accompanying it. If
+# a copy of the MPL was not distributed with this file, You can obtain one
+# at http://mozilla.org/MPL/2.0/.
+#
+# Creates an Adobe Access signed voucher for x32/x64 windows executables
+# Notes: This is currently python2.7 due to mozilla build system requirements
+
+from __future__ import print_function
+
+import argparse, bitstring, pprint, hashlib, os, subprocess, sys, tempfile, macholib, macholib.MachO
+from pyasn1.codec.der import encoder as der_encoder
+from pyasn1.type import univ, namedtype, namedval, constraint
+
+
+# Defined in WinNT.h from the Windows SDK
+IMAGE_SCN_MEM_EXECUTE = 0x20000000
+IMAGE_REL_BASED_HIGHLOW = 3
+IMAGE_REL_BASED_DIR64 = 10
+
+
+# CodeSectionDigest ::= SEQUENCE {
+# offset INTEGER -- section's file offset in the signed binary
+# digestAlgorithm OBJECT IDENTIFIER -- algorithm identifier for the hash value below. For now only supports SHA256.
+# digestValue OCTET STRING -- hash value of the TEXT segment.
+# }
+class CodeSectionDigest(univ.Sequence):
+ componentType = namedtype.NamedTypes(
+ namedtype.NamedType('offset', univ.Integer()),
+ namedtype.NamedType('digestAlgorithm', univ.ObjectIdentifier()),
+ namedtype.NamedType('digest', univ.OctetString()))
+
+
+# CodeSegmentDigest ::= SEQUENCE {
+# offset INTEGER -- TEXT segment's file offset in the signed binary
+# codeSectionDigests SET OF CodeSectionDigests
+# }
+
+class SetOfCodeSectionDigest(univ.SetOf):
+ componentType = CodeSectionDigest()
+
+
+class CodeSegmentDigest(univ.Sequence):
+ componentType = namedtype.NamedTypes(
+ namedtype.NamedType('offset', univ.Integer()),
+ namedtype.NamedType('codeSectionDigests', SetOfCodeSectionDigest()))
+
+
+# ArchitectureDigest ::= SEQUENCE {
+# cpuType ENUMERATED CpuType
+# cpuSubType ENUMERATED CpuSubType
+# CodeSegmentDigests SET OF CodeSegmentDigests
+# }
+class SetOfCodeSegmentDigest(univ.SetOf):
+ componentType = CodeSegmentDigest()
+
+
+class CPUType(univ.Enumerated):
+ namedValues = namedval.NamedValues(
+ ('IMAGE_FILE_MACHINE_I386', 0x14c),
+ ('IMAGE_FILE_MACHINE_AMD64',0x8664 ),
+ ('MACHO_CPU_TYPE_I386',0x7 ),
+ ('MACHO_CPU_TYPE_X86_64',0x1000007 ),
+ )
+ subtypeSpec = univ.Enumerated.subtypeSpec + \
+ constraint.SingleValueConstraint(0x14c, 0x8664, 0x7, 0x1000007)
+
+
+class CPUSubType(univ.Enumerated):
+ namedValues = namedval.NamedValues(
+ ('IMAGE_UNUSED', 0x0),
+ ('CPU_SUBTYPE_X86_ALL', 0x3),
+ ('CPU_SUBTYPE_X86_64_ALL', 0x80000003)
+ )
+ subtypeSpec = univ.Enumerated.subtypeSpec + \
+ constraint.SingleValueConstraint(0, 0x3, 0x80000003)
+
+
+class ArchitectureDigest(univ.Sequence):
+ componentType = namedtype.NamedTypes(
+ namedtype.NamedType('cpuType', CPUType()),
+ namedtype.NamedType('cpuSubType', CPUSubType()),
+ namedtype.NamedType('CodeSegmentDigests', SetOfCodeSegmentDigest())
+ )
+
+
+# ApplicationDigest ::= SEQUENCE {
+# version INTEGER
+# digests SET OF ArchitectureDigest
+# }
+class SetOfArchitectureDigest(univ.SetOf):
+ componentType = ArchitectureDigest()
+
+
+class ApplicationDigest(univ.Sequence):
+ componentType = namedtype.NamedTypes(
+ namedtype.NamedType('version', univ.Integer()),
+ namedtype.NamedType('digests', SetOfArchitectureDigest())
+ )
+
+
+def meets_requirements(items, requirements):
+ for r in requirements:
+ for n, v in r.items():
+ if n not in items or items[n] != v: return False
+ return True
+
+
+# return total number of bytes read from items_in excluding leaves
+# TODO: research replacing this with the python built-in struct module
+def parse_items(stream, items_in, items_out):
+ bits_read = 0
+ total_bits_read = 0
+
+ for item in items_in:
+ name = item[0]
+ t = item[1]
+ bits = 1 if ":" not in t else int(t[t.index(":") + 1:])
+
+ if ":" in t and t.find("bytes") >= 0:
+ bits = bits * 8
+
+ if len(item) == 2:
+ items_out[name] = stream.read(t)
+ bits_read += bits
+ total_bits_read += bits
+ elif len(item) == 3 or len(item) == 4:
+ requirements = list(filter(lambda x: isinstance(x, dict), item[2]))
+ sub_items = list(filter(lambda x: isinstance(x, tuple), item[2]))
+
+ if not meets_requirements(items_out, requirements): continue
+
+ # has sub-items based on length
+ items_out[name] = stream.read(t)
+ bits_read += bits
+ total_bits_read += bits
+
+ if len(item) == 4:
+ bit_length = items_out[name] * 8
+
+ if bit_length > 0:
+ sub_read, sub_total_read = parse_items(stream, sub_items, items_out)
+ bit_length -= sub_read
+ total_bits_read += sub_total_read
+
+ if bit_length > 0:
+ items_out[item[3]] = stream.read('bits:' + str(bit_length))
+ bits_read += bit_length
+ total_bits_read += bit_length
+ else:
+ raise Exception("unrecognized item" + pprint.pformat(item))
+
+ return bits_read, total_bits_read
+
+
+# macho stuff
+# Constant for the magic field of the mach_header (32-bit architectures)
+MH_MAGIC =0xfeedface # the mach magic number
+MH_CIGAM =0xcefaedfe # NXSwapInt(MH_MAGIC)
+
+MH_MAGIC_64 =0xfeedfacf # the the 64-bit mach magic number
+MH_CIGAM_64 =0xcffaedfe # NXSwapInt(MH_MAGIC_64)
+
+FAT_CIGAM = 0xbebafeca
+FAT_MAGIC = 0xcafebabe
+
+LC_SEGMENT = 0x1
+LC_SEGMENT_64 = 0x19 # 64-bit segment of this file to be
+
+
+
+# TODO: perhaps switch to pefile module when it officially supports python3
+class SectionHeader:
+ def __init__(self, stream):
+ items = [
+ ('Name', 'bytes:8'),
+ ('VirtualSize', 'uintle:32'),
+ ('VirtualAddress', 'uintle:32'),
+ ('SizeOfRawData', 'uintle:32'),
+ ('PointerToRawData', 'uintle:32'),
+ ('PointerToRelocations', 'uintle:32'),
+ ('PointerToLineNumber', 'uintle:32'),
+ ('NumberOfRelocations', 'uintle:16'),
+ ('NumberOfLineNumbers', 'uintle:16'),
+ ('Characteristics', 'uintle:32')
+ ]
+ self.items = dict()
+ self.relocs = dict()
+
+ _, self.bits_read = parse_items(stream, items, self.items)
+
+ self.sectionName = self.items['Name'].decode('utf-8')
+ self.offset = self.items['PointerToRawData']
+
+COFF_DATA_DIRECTORY_TYPES = [
+ "Export Table",
+ "Import Table",
+ "Resource Table",
+ "Exception Table",
+ "Certificate Tble",
+ "Base Relocation Table",
+ "Debug",
+ "Architecture",
+ "Global Ptr",
+ "TLS Table",
+ "Load Config Table",
+ "Bound Import",
+ "IAT",
+ "Delay Import Descriptor",
+ "CLR Runtime Header",
+ "Reserved",
+]
+
+
+def chained_safe_get(obj, names, default=None):
+ if obj is None: return default
+
+ for n in names:
+ if n in obj:
+ obj = obj[n]
+ else:
+ return default
+
+ return obj
+
+
+class OptionalHeader:
+ def __init__(self, stream, size):
+ self.items = {}
+ items = []
+
+ if size:
+ items += [
+ ('Magic', 'uintle:16'),
+ ('MajorLinkerVersion', 'uintle:8'),
+ ('MinorLinkerVersion', 'uintle:8'),
+ ('SizeOfCode', 'uintle:32'),
+ ('SizeOfInitializedData', 'uintle:32'),
+ ('SizeOfUninitializedData', 'uintle:32'),
+ ('AddressOfEntryPoint', 'uintle:32'),
+ ('BaseOfCode', 'uintle:32'),
+ ]
+
+ _, self.bits_read = parse_items(stream, items, self.items)
+
+ items = []
+ if self.items['Magic'] == 0x10b: # PE32
+ items += [('BaseOfData', 'uintle:32')]
+
+ address_size = 'uintle:64' if self.items['Magic'] == 0x20b else 'uintle:32'
+
+ items += [
+ ('ImageBase', address_size),
+ ('SectionAlignment', 'uintle:32'),
+ ('FileAlignment', 'uintle:32'),
+ ('MajorOperatingSystemVersion', 'uintle:16'),
+ ('MinorOperatingSystemVersion', 'uintle:16'),
+ ('MajorImageVersion', 'uintle:16'),
+ ('MinorImageVersion', 'uintle:16'),
+ ('MajorSubsystemVersion', 'uintle:16'),
+ ('MinorSubsystemVersion', 'uintle:16'),
+ ('Win32VersionValue', 'uintle:32'),
+ ('SizeOfImage', 'uintle:32'),
+ ('SizeOfHeaders', 'uintle:32'),
+ ('CheckSum', 'uintle:32'),
+ ('Subsystem', 'uintle:16'),
+ ('DllCharacteristics', 'uintle:16'),
+ ('SizeOfStackReserve', address_size),
+ ('SizeOfStackCommit', address_size),
+ ('SizeOfHeapReserve', address_size),
+ ('SizeOfHeapCommit', address_size),
+ ('LoaderFlags', 'uintle:32'),
+ ('NumberOfRvaAndSizes', 'uintle:32'),
+ ]
+
+ if size > 28:
+ _, bits_read = parse_items(stream, items, self.items)
+ self.bits_read += bits_read
+
+ if 'NumberOfRvaAndSizes' in self.items:
+ index = 0
+ self.items['Data Directories'] = dict()
+ while self.bits_read / 8 < size:
+ d = self.items['Data Directories'][COFF_DATA_DIRECTORY_TYPES[index]] = dict()
+
+ _, bits_read = parse_items(stream, [('VirtualAddress', 'uintle:32'), ('Size', 'uintle:32')], d)
+ self.bits_read += bits_read
+ index += 1
+
+
+class COFFFileHeader:
+ def __init__(self, stream):
+ self.items = {}
+ self.section_headers = []
+
+ items = [
+ ('Machine', 'uintle:16'),
+ ('NumberOfSections', 'uintle:16'),
+ ('TimeDateStamp', 'uintle:32'),
+ ('PointerToSymbolTable', 'uintle:32'),
+ ('NumberOfSymbols', 'uintle:32'),
+ ('SizeOfOptionalHeader', 'uintle:16'),
+ ('Characteristics', 'uintle:16')
+ ]
+ _, self.bits_read = parse_items(stream, items, self.items)
+
+ self.OptionalHeader = OptionalHeader(stream, self.items['SizeOfOptionalHeader'])
+ self.bits_read += self.OptionalHeader.bits_read
+
+ # start reading section headers
+ num_sections = self.items['NumberOfSections']
+
+ while num_sections > 0 :
+ section_header = SectionHeader(stream)
+ self.bits_read += section_header.bits_read
+ self.section_headers.append(section_header)
+ num_sections -= 1
+
+ self.section_headers.sort(key=lambda header: header.offset)
+
+ # Read Relocations
+ self.process_relocs(stream)
+
+ def process_relocs(self, stream):
+ reloc_table = chained_safe_get(self.OptionalHeader.items, ['Data Directories', 'Base Relocation Table'])
+ if reloc_table is None: return
+
+ orig_pos = stream.bitpos
+ _, stream.bytepos = self.get_rva_section(reloc_table['VirtualAddress'])
+ end_pos = stream.bitpos + reloc_table['Size'] * 8
+
+ while stream.bitpos < end_pos:
+ page_rva = stream.read('uintle:32')
+ block_size = stream.read('uintle:32')
+
+ for i in range(0, int((block_size - 8) / 2)):
+ data = stream.read('uintle:16')
+ typ = data >> 12
+ offset = data & 0xFFF
+
+ if offset == 0 and i > 0: continue
+
+ assert(typ == IMAGE_REL_BASED_HIGHLOW or typ == IMAGE_REL_BASED_DIR64)
+
+ cur_pos = stream.bitpos
+ sh, value_bytepos = self.get_rva_section(page_rva + offset)
+ stream.bytepos = value_bytepos
+ value = stream.read('uintle:32' if typ == IMAGE_REL_BASED_HIGHLOW else 'uintle:64')
+
+ # remove BaseAddress
+ value -= self.OptionalHeader.items['ImageBase']
+
+ bit_size = (4 if typ == IMAGE_REL_BASED_HIGHLOW else 8) * 8
+ stream.overwrite(bitstring.BitArray(uint=value, length=bit_size), pos=value_bytepos * 8)
+ stream.pos = cur_pos
+
+ stream.bitpos = orig_pos
+
+ def get_rva_section(self, rva):
+ for sh in self.section_headers:
+ if rva < sh.items['VirtualAddress'] or rva >= sh.items['VirtualAddress'] + sh.items['VirtualSize']:
+ continue
+
+ file_pointer = rva - sh.items['VirtualAddress'] + sh.items['PointerToRawData']
+ return sh, file_pointer
+
+ raise Exception('Could not match RVA to section')
+
+
+def create_temp_file(suffix=""):
+ fd, path = tempfile.mkstemp(suffix=suffix)
+ os.close(fd)
+ return path
+
+
+class ExpandPath(argparse.Action):
+ def __call__(self, parser, namespace, values, option_string=None):
+ setattr(namespace, self.dest, os.path.abspath(os.path.expanduser(values)))
+
+
+# this does a naming trick since windows doesn't allow multiple usernames for the same server
+def get_password(service_name, user_name):
+ try:
+ import keyring
+
+ # windows doesn't allow multiple usernames for the same server, argh
+ if sys.platform == "win32":
+ password = keyring.get_password(service_name + "-" + user_name, user_name)
+ else:
+ password = keyring.get_password(service_name, user_name)
+
+ return password
+ except:
+ # This allows for manual testing where you do not wish to cache the password on the system
+ print("Missing keyring module...getting password manually")
+
+ return None
+
+
+def openssl_cmd(app_args, args, password_in, password_out):
+ password = get_password(app_args.password_service, app_args.password_user) if (password_in or password_out) else None
+ env = None
+ args = [app_args.openssl_path] + args
+
+ if password is not None:
+ env = os.environ.copy()
+ env["COFF_PW"] = password
+
+ if password_in: args += ["-passin", "env:COFF_PW"]
+ if password_out: args += ["-passout", "env:COFF_PW", "-password", "env:COFF_PW"]
+
+ subprocess.check_call(args, env=env)
+
+
+def processMachoBinary(filename):
+
+ outDict = dict()
+ outDict['result'] = False
+
+ setOfArchDigests = SetOfArchitectureDigest()
+ archDigestIdx = 0
+
+ parsedMacho = macholib.MachO.MachO(filename)
+
+ for header in parsedMacho.headers :
+ arch_digest = ArchitectureDigest()
+ lc_segment = LC_SEGMENT
+
+ arch_digest.setComponentByName('cpuType', CPUType(header.header.cputype))
+ arch_digest.setComponentByName('cpuSubType', CPUSubType(header.header.cpusubtype))
+
+ if header.header.cputype == 0x1000007:
+ lc_segment = LC_SEGMENT_64
+
+
+
+ segment_commands = list(filter(lambda x: x[0].cmd == lc_segment, header.commands))
+ text_segment_commands = list(filter(lambda x: x[1].segname.decode("utf-8").startswith("__TEXT"), segment_commands))
+
+
+ code_segment_digests = SetOfCodeSegmentDigest()
+ code_segment_idx = 0
+
+ for text_command in text_segment_commands:
+
+ codeSegmentDigest = CodeSegmentDigest()
+ codeSegmentDigest.setComponentByName('offset', text_command[1].fileoff)
+
+ sectionDigestIdx = 0
+ set_of_digest = SetOfCodeSectionDigest()
+ for section in text_command[2]:
+ digester = hashlib.sha256()
+ digester.update(section.section_data)
+ digest = digester.digest()
+
+ code_section_digest = CodeSectionDigest()
+ code_section_digest.setComponentByName('offset', section.offset)
+ code_section_digest.setComponentByName('digestAlgorithm', univ.ObjectIdentifier('2.16.840.1.101.3.4.2.1'))
+ code_section_digest.setComponentByName('digest', univ.OctetString(digest))
+
+ set_of_digest.setComponentByPosition(sectionDigestIdx, code_section_digest)
+ sectionDigestIdx += 1
+
+
+ codeSegmentDigest.setComponentByName('codeSectionDigests', set_of_digest)
+
+ code_segment_digests.setComponentByPosition(code_segment_idx, codeSegmentDigest)
+
+ code_segment_idx += 1
+
+ arch_digest.setComponentByName('CodeSegmentDigests', code_segment_digests)
+ setOfArchDigests.setComponentByPosition(archDigestIdx, arch_digest)
+ archDigestIdx += 1
+
+ outDict['result'] = True
+
+ if outDict['result']:
+ appDigest = ApplicationDigest()
+ appDigest.setComponentByName('version', 1)
+ appDigest.setComponentByName('digests', setOfArchDigests)
+ outDict['digest'] = appDigest
+
+
+ return outDict
+
+
+
+def processCOFFBinary(stream):
+
+ outDict = dict()
+ outDict['result'] = False
+
+ # find the COFF header.
+ # skip forward past the MSDOS stub header to 0x3c.
+ stream.bytepos = 0x3c
+
+ # read 4 bytes, this is the file offset of the PE signature.
+ pe_sig_offset = stream.read('uintle:32')
+ stream.bytepos = pe_sig_offset
+
+ # read 4 bytes, make sure it's a PE signature.
+ signature = stream.read('uintle:32')
+ if signature != 0x00004550:
+ return outDict
+
+ # after signature is the actual COFF file header.
+ coff_header = COFFFileHeader(stream)
+
+ arch_digest = ArchitectureDigest()
+ if coff_header.items['Machine'] == 0x14c:
+ arch_digest.setComponentByName('cpuType', CPUType('IMAGE_FILE_MACHINE_I386'))
+ elif coff_header.items['Machine'] == 0x8664:
+ arch_digest.setComponentByName('cpuType', CPUType('IMAGE_FILE_MACHINE_AMD64'))
+
+ arch_digest.setComponentByName('cpuSubType', CPUSubType('IMAGE_UNUSED'))
+
+ text_section_headers = list(filter(lambda x: (x.items['Characteristics'] & IMAGE_SCN_MEM_EXECUTE) == IMAGE_SCN_MEM_EXECUTE, coff_header.section_headers))
+
+ code_segment_digests = SetOfCodeSegmentDigest()
+ code_segment_idx = 0
+ for code_sect_header in text_section_headers:
+ stream.bytepos = code_sect_header.offset
+ code_sect_bytes = stream.read('bytes:' + str(code_sect_header.items['VirtualSize']))
+
+ digester = hashlib.sha256()
+ digester.update(code_sect_bytes)
+ digest = digester.digest()
+
+ # with open('segment_' + str(code_sect_header.offset) + ".bin", 'wb') as f:
+ # f.write(code_sect_bytes)
+
+ code_section_digest = CodeSectionDigest()
+ code_section_digest.setComponentByName('offset', code_sect_header.offset)
+ code_section_digest.setComponentByName('digestAlgorithm', univ.ObjectIdentifier('2.16.840.1.101.3.4.2.1'))
+ code_section_digest.setComponentByName('digest', univ.OctetString(digest))
+
+ set_of_digest = SetOfCodeSectionDigest()
+ set_of_digest.setComponentByPosition(0, code_section_digest)
+
+ codeSegmentDigest = CodeSegmentDigest()
+ codeSegmentDigest.setComponentByName('offset', code_sect_header.offset)
+ codeSegmentDigest.setComponentByName('codeSectionDigests', set_of_digest)
+
+ code_segment_digests.setComponentByPosition(code_segment_idx, codeSegmentDigest)
+ code_segment_idx += 1
+
+ arch_digest.setComponentByName('CodeSegmentDigests', code_segment_digests)
+
+ setOfArchDigests = SetOfArchitectureDigest()
+ setOfArchDigests.setComponentByPosition(0, arch_digest)
+
+ appDigest = ApplicationDigest()
+
+ appDigest.setComponentByName('version', 1)
+ appDigest.setComponentByName('digests', setOfArchDigests)
+
+ outDict['result'] = True
+ outDict['digest'] = appDigest
+
+ return outDict
+
+def main():
+ parser = argparse.ArgumentParser(description='PE/COFF Signer')
+ parser.add_argument('-input', action=ExpandPath, required=True, help="File to parse.")
+ parser.add_argument('-output', action=ExpandPath, required=True, help="File to write to.")
+ parser.add_argument('-openssl_path', action=ExpandPath, help="Path to OpenSSL to create signed voucher")
+ parser.add_argument('-signer_pfx', action=ExpandPath, help="Path to certificate to use to sign voucher. Must contain full certificate chain.")
+ parser.add_argument('-password_service', help="Name of Keyring/Wallet service/host")
+ parser.add_argument('-password_user', help="Name of Keyring/Wallet user name")
+ parser.add_argument('-verbose', action='store_true', help="Verbose output.")
+ app_args = parser.parse_args()
+
+ # to simplify relocation handling we use a mutable BitStream so we can remove
+ # the BaseAddress from each relocation
+ stream = bitstring.BitStream(filename=app_args.input)
+
+
+ dict = processCOFFBinary(stream)
+
+ if dict['result'] == False:
+ dict = processMachoBinary(app_args.input)
+
+
+
+ if dict['result'] == False:
+ raise Exception("Invalid File")
+
+ binaryDigest = der_encoder.encode(dict['digest'])
+
+ with open(app_args.output, 'wb') as f:
+ f.write(binaryDigest)
+
+ # sign with openssl if specified
+ if app_args.openssl_path is not None:
+ assert app_args.signer_pfx is not None
+
+ out_base, out_ext = os.path.splitext(app_args.output)
+ signed_path = out_base + ".signed" + out_ext
+
+ # http://stackoverflow.com/questions/12507277/how-to-fix-unable-to-write-random-state-in-openssl
+ temp_files = []
+ if sys.platform == "win32" and "RANDFILE" not in os.environ:
+ temp_file = create_temp_file()
+ temp_files += [temp_file]
+ os.environ["RANDFILE"] = temp_file
+
+ try:
+ # create PEM from PFX
+ pfx_pem_path = create_temp_file(".pem")
+ temp_files += [pfx_pem_path]
+ print("Extracting PEM from PFX to:" + pfx_pem_path)
+ openssl_cmd(app_args, ["pkcs12", "-in", app_args.signer_pfx, "-out", pfx_pem_path], True, True)
+
+ # extract CA certs
+ pfx_cert_path = create_temp_file(".cert")
+ temp_files += [pfx_cert_path]
+ print("Extracting cert from PFX to:" + pfx_cert_path)
+ openssl_cmd(app_args, ["pkcs12", "-in", app_args.signer_pfx, "-cacerts", "-nokeys", "-out", pfx_cert_path], True, False)
+
+ # we embed the public keychain for client validation
+ openssl_cmd(app_args, ["cms", "-sign", "-nodetach", "-md", "sha256", "-binary", "-in", app_args.output, "-outform", "der", "-out", signed_path, "-signer", pfx_pem_path, "-certfile", pfx_cert_path], True, False)
+ finally:
+ for t in temp_files:
+ if "RANDFILE" in os.environ and t == os.environ["RANDFILE"]:
+ del os.environ["RANDFILE"]
+ os.unlink(t)
+
+if __name__ == '__main__':
+ main()