Files
self/app/ios/scripts/extract.py
2023-10-14 14:46:20 +02:00

216 lines
6.6 KiB
Python
Executable File

#!/usr/bin/env python
import os
import sys
import base64
import subprocess
import re
fingerprints = []
totalCerts = 0
certNr = 1
def main( filename ):
global certNr
# First, make sure that the version of openssl we are using supports the cms command
# the version supplied with OS X doesn't - you would need to get a different one in this case
# e.g. through Homebrew
out, err = execute( "openssl cms" )
if err.decode().find( "'cms' is an invalid command" ) != -1:
print( "The version of OpenSSL you are using doesn't support the CMS command" )
print( "You need to get a version that does (e.g. from Homebrew)" )
exit( 1 )
# remove old master list
if os.path.exists( "masterList.pem" ):
os.remove( "masterList.pem" )
# Identify Type of file - either LDIF or MasterList (CMS)
if filename.lower().endswith( ".ldif" ):
# Read and parse LDIF File
(cns,masterLists) = readAndExtractLDIFFile( filename )
elif filename.lower().endswith( ".ml" ):
masterLists = readInMasterListFile( filename )
print( f"Read in {len(masterLists)} masterlist files" )
print( f"Read in {cns} CNS" )
for index, ml in enumerate(masterLists):
certNr = 1
print( "-----------------------------------" )
print(f"Verifying and extracting MasterList {index} - {cns[index]}")
try:
extractCertsFromMasterlist( ml )
except Exception as e:
print( "Error extracting certs from masterlist")
print( "Skipping this masterlist - certs from this list will not be included.")
print( "====================================" )
print( f"Created MasterList.pem containing {totalCerts} certificates")
def readAndExtractLDIFFile( file ):
adding = False
certs = []
cns = []
cn = ""
cert = "" # Initialize cert here
with open(file, "r") as inf:
for line in inf:
if line.startswith( "cn: "):
cn = line[4:]
elif line.startswith( "pkdMasterListContent:: "):
cert = line[24:]
adding = True
elif not line.startswith(" ") and adding == True:
adding = False
cert = cert.rstrip() # Remove trailing whitespace
# Ensure cert has correct padding
missing_padding = len(cert) % 4
if missing_padding != 0:
cert += '='* (4 - missing_padding)
certs.append( cert )
cns.append( cn )
cert = ""
elif adding == True:
cert += line
if cert != "":
cert = cert.rstrip() # Remove trailing whitespace
# Ensure cert has correct padding
missing_padding = len(cert) % 4
if missing_padding != 0:
cert += '='* (4 - missing_padding)
certs.append( cert )
cns.append( cn )
print( f"Read {len(certs)} certs" )
# print(certs[1][:4])
# print(certs[0][:-3])
masterLists = []
for index, cert in enumerate(certs):
if index != 0:
continue
data = base64.b64decode(cert)
masterLists.append( data )
return (cns,masterLists)
def readInMasterListFile( file ):
with open(file, "rb") as inf:
data = inf.read()
return [data]
def extractCertsFromMasterlist( masterList ):
global totalCerts
# Run openssl cms to verify and extract the signed data
cmd = f"openssl cms -inform der -noverify -verify"
(signedData, err) = execute( cmd, masterList )
print(signedData)
err = err.decode("utf8").strip().replace("CMS ", "")
print(err)
if err != "Verification successful":
print( f"[{err.decode('utf8')}]" )
raise Exception( "Verification of Masterlist data failed" )
print( "MasterList Verification successful" )
certList = extractPEMCertificates( signedData )
print( "Removing duplicates")
uniqueCerts = [x for x in certList if uniqueHash(x)]
print( f"Removed {len(certList)-len(uniqueCerts)} duplicate certificates")
totalCerts += len(uniqueCerts)
# Append to masterList.pem
with open("masterList.pem", "ab") as f:
for c in uniqueCerts:
f.write(c)
def extractPEMCertificates( signedData ):
global certNr
print( "Extracting all certificates from payload" )
cmd = f"openssl asn1parse -inform der -i"
(data, err) = execute( cmd, signedData )
lines = data.decode("utf8").strip().split( "\n" )
valid = False
certs = []
certCount = len([i for i in lines if "d=2" in i])
for line in lines:
if re.search( r":d=1", line ):
valid = False
if re.search( r"d=1.*SET", line ):
valid = True
if re.search( r"d=2", line ) and valid:
# Parse line
match = re.search( r"^ *([0-9]*).*hl= *([0-9]*).*l= *([0-9]*).*", line)
if match:
print( f"Extracting cert {certNr} of {certCount}", end="\r" )
certNr += 1
offset = int(match.group(1))
header_len = int(match.group(2))
octet_len = int(match.group(3))
# Extract PEM certificate
data = signedData[offset : offset+header_len+octet_len]
(cert,err) = execute( f"openssl x509 -inform der -outform pem", data )
certs.append(cert)
else:
print( "Failed match")
print( f"\nExtracted {len(certs)} certs")
return certs
def uniqueHash( cert ):
(data,err) = execute( "openssl x509 -hash -fingerprint -inform PEM -noout", cert )
items = data.decode("utf8").split("\n")
hash = items[0].strip()
fingerprint = items[1].strip()
if fingerprint not in fingerprints:
fingerprints.append( fingerprint )
return True
#print( f"Found duplicate hash - {hash}")
return False
def writeToDisk(name, data):
with open( name, "wb" ) as f:
f.write(data)
def removeFromDisk(name):
try:
os.remove(name)
except:
pass
def execute(cmd, data = None, empty=False):
res = subprocess.Popen(cmd, shell=True, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
if data != None:
res.stdin.write(data)
res.stdin.close()
out = res.stdout.read()
err = res.stderr.read()
return (out, err)
if __name__ == "__main__":
if len(sys.argv) != 2:
print( "Invalid number of parameters: ")
print( "" )
print( "Usage - python extract.py [masterlist ml file|icao ldif file]")
print( "" )
exit(1)
main( sys.argv[1] )