|
#! /usr/bin/env python |
|
|
|
# Script to manage additional trusted root certificate in the IOS simulator |
|
# |
|
# Allows to add/list/delete/export trusted root certificates to the IOS simulator |
|
# TrustStore.sqlite3 file. |
|
# |
|
# Additionally, root certificates added to a device can be listed and exported from |
|
# a device backup |
|
# |
|
# type ./iosCertTrustManager.py -h for help |
|
# |
|
# |
|
# This script contains code derived from Python-ASN1 to parse and re-encode |
|
# ASN1. The following notice is included: |
|
# |
|
# Python-ASN1 is free software that is made available under the MIT license. |
|
# Consult the file "LICENSE" that is |
|
# distributed together with this file for the exact licensing terms. |
|
# |
|
# Python-ASN1 is copyright (c) 2007-2008 by Geert Jansen <geert@boskant.nl>. |
|
# see https://github.com/geertj/python-asn1 |
|
|
|
import os |
|
import sys |
|
import argparse |
|
import sqlite3 |
|
import ssl |
|
import hashlib |
|
import subprocess |
|
import string |
|
import binascii |
|
import plistlib |
|
|
|
def query_yes_no(question, default="yes"): |
|
"""Ask a yes/no question via raw_input() and return their answer. |
|
|
|
"question" is a string that is presented to the user. |
|
"default" is the presumed answer if the user just hits <Enter>. |
|
It must be "yes" (the default), "no" or None (meaning |
|
an answer is required of the user). |
|
|
|
The "answer" return value is one of "yes" or "no". |
|
""" |
|
valid = {"yes":"yes", "y":"yes", "ye":"yes", |
|
"no":"no", "n":"no"} |
|
if default == None: |
|
prompt = " [y/n] " |
|
elif default == "yes": |
|
prompt = " [Y/n] " |
|
elif default == "no": |
|
prompt = " [y/N] " |
|
else: |
|
raise ValueError("invalid default answer: '%s'" % default) |
|
|
|
while 1: |
|
sys.stdout.write(question + prompt) |
|
choice = raw_input().lower() |
|
if default is not None and choice == '': |
|
return default |
|
elif choice in valid.keys(): |
|
return valid[choice] |
|
else: |
|
sys.stdout.write("Please respond with 'yes' or 'no' "\ |
|
"(or 'y' or 'n').\n") |
|
|
|
#---------------------------------------------------------------------- |
|
# A simple ASN1 decoder/encoder based on Python-ASN1 |
|
#---------------------------------------------------------------------- |
|
|
|
class ASN1: |
|
Sequence = 0x10 |
|
Set = 0x11 |
|
PrintableString = 0x13 |
|
|
|
TypeConstructed = 0x20 |
|
TypePrimitive = 0x00 |
|
|
|
ClassUniversal = 0x00 |
|
ClassApplication = 0x40 |
|
ClassContext = 0x80 |
|
ClassPrivate = 0xc0 |
|
|
|
class Error(Exception): |
|
"""ASN1 error""" |
|
|
|
|
|
class Encoder(object): |
|
"""A simple ASN.1 encoder. Uses DER encoding.""" |
|
|
|
def __init__(self): |
|
"""Constructor.""" |
|
self.m_stack = None |
|
|
|
def start(self): |
|
"""Start encoding.""" |
|
self.m_stack = [[]] |
|
|
|
def enter(self, nr, cls): |
|
"""Start a constructed data value.""" |
|
if self.m_stack is None: |
|
raise Error, 'Encoder not initialized. Call start() first.' |
|
self._emit_tag(nr, ASN1.TypeConstructed, cls) |
|
self.m_stack.append([]) |
|
|
|
def leave(self): |
|
"""Finish a constructed data value.""" |
|
if self.m_stack is None: |
|
raise Error, 'Encoder not initialized. Call start() first.' |
|
if len(self.m_stack) == 1: |
|
raise Error, 'Tag stack is empty.' |
|
value = ''.join(self.m_stack[-1]) |
|
del self.m_stack[-1] |
|
self._emit_length(len(value)) |
|
self._emit(value) |
|
|
|
def write(self, value, nr, typ, cls): |
|
"""Write a primitive data value.""" |
|
if self.m_stack is None: |
|
raise Error, 'Encoder not initialized. Call start() first.' |
|
self._emit_tag(nr, typ, cls) |
|
self._emit_length(len(value)) |
|
self._emit(value) |
|
|
|
def output(self): |
|
"""Return the encoded output.""" |
|
if self.m_stack is None: |
|
raise Error, 'Encoder not initialized. Call start() first.' |
|
if len(self.m_stack) != 1: |
|
raise Error, 'Stack is not empty.' |
|
output = ''.join(self.m_stack[0]) |
|
return output |
|
|
|
def _emit_tag(self, nr, typ, cls): |
|
"""Emit a tag.""" |
|
if nr < 31: |
|
self._emit_tag_short(nr, typ, cls) |
|
else: |
|
self._emit_tag_long(nr, typ, cls) |
|
|
|
def _emit_tag_short(self, nr, typ, cls): |
|
"""Emit a short (< 31 bytes) tag.""" |
|
assert nr < 31 |
|
self._emit(chr(nr | typ | cls)) |
|
|
|
def _emit_tag_long(self, nr, typ, cls): |
|
"""Emit a long (>= 31 bytes) tag.""" |
|
head = chr(typ | cls | 0x1f) |
|
self._emit(head) |
|
values = [] |
|
values.append((nr & 0x7f)) |
|
nr >>= 7 |
|
while nr: |
|
values.append((nr & 0x7f) | 0x80) |
|
nr >>= 7 |
|
values.reverse() |
|
values = map(chr, values) |
|
for val in values: |
|
self._emit(val) |
|
|
|
def _emit_length(self, length): |
|
"""Emit length octects.""" |
|
if length < 128: |
|
self._emit_length_short(length) |
|
else: |
|
self._emit_length_long(length) |
|
|
|
def _emit_length_short(self, length): |
|
"""Emit the short length form (< 128 octets).""" |
|
assert length < 128 |
|
self._emit(chr(length)) |
|
|
|
def _emit_length_long(self, length): |
|
"""Emit the long length form (>= 128 octets).""" |
|
values = [] |
|
while length: |
|
values.append(length & 0xff) |
|
length >>= 8 |
|
values.reverse() |
|
values = map(chr, values) |
|
# really for correctness as this should not happen anytime soon |
|
assert len(values) < 127 |
|
head = chr(0x80 | len(values)) |
|
self._emit(head) |
|
for val in values: |
|
self._emit(val) |
|
|
|
def _emit(self, s): |
|
"""Emit raw bytes.""" |
|
assert isinstance(s, str) |
|
self.m_stack[-1].append(s) |
|
|
|
|
|
class Decoder(object): |
|
"""A minimal ASN.1 decoder. Understands BER (and DER which is a subset).""" |
|
|
|
def __init__(self): |
|
"""Constructor.""" |
|
self.m_stack = None |
|
self.m_tag = None |
|
|
|
def start(self, data): |
|
"""Start processing `data'.""" |
|
if not isinstance(data, str): |
|
raise Error, 'Expecting string instance.' |
|
self.m_stack = [[0, data]] |
|
self.m_tag = None |
|
|
|
def peek(self): |
|
"""Return the value of the next tag without moving to the next |
|
TLV record.""" |
|
if self.m_stack is None: |
|
raise Error, 'No input selected. Call start() first.' |
|
if self._end_of_input(): |
|
return None |
|
if self.m_tag is None: |
|
self.m_tag = self._read_tag() |
|
return self.m_tag |
|
|
|
def read(self): |
|
"""Read a simple value and move to the next TLV record.""" |
|
if self.m_stack is None: |
|
raise Error, 'No input selected. Call start() first.' |
|
if self._end_of_input(): |
|
return None |
|
tag = self.peek() |
|
length = self._read_length() |
|
value = self._read_value(tag[0], length) |
|
self.m_tag = None |
|
return (tag, value) |
|
|
|
def eof(self): |
|
"""Return True if we are end of input.""" |
|
return self._end_of_input() |
|
|
|
def enter(self): |
|
"""Enter a constructed tag.""" |
|
if self.m_stack is None: |
|
raise Error, 'No input selected. Call start() first.' |
|
nr, typ, cls = self.peek() |
|
if typ != ASN1.TypeConstructed: |
|
raise Error, 'Cannot enter a non-constructed tag.' |
|
length = self._read_length() |
|
bytes = self._read_bytes(length) |
|
self.m_stack.append([0, bytes]) |
|
self.m_tag = None |
|
|
|
def leave(self): |
|
"""Leave the last entered constructed tag.""" |
|
if self.m_stack is None: |
|
raise Error, 'No input selected. Call start() first.' |
|
if len(self.m_stack) == 1: |
|
raise Error, 'Tag stack is empty.' |
|
del self.m_stack[-1] |
|
self.m_tag = None |
|
|
|
def _read_tag(self): |
|
"""Read a tag from the input.""" |
|
byte = self._read_byte() |
|
cls = byte & 0xc0 |
|
typ = byte & 0x20 |
|
nr = byte & 0x1f |
|
if nr == 0x1f: |
|
nr = 0 |
|
while True: |
|
byte = self._read_byte() |
|
nr = (nr << 7) | (byte & 0x7f) |
|
if not byte & 0x80: |
|
break |
|
return (nr, typ, cls) |
|
|
|
def _read_length(self): |
|
"""Read a length from the input.""" |
|
byte = self._read_byte() |
|
if byte & 0x80: |
|
count = byte & 0x7f |
|
if count == 0x7f: |
|
raise Error, 'ASN1 syntax error' |
|
bytes = self._read_bytes(count) |
|
bytes = [ ord(b) for b in bytes ] |
|
length = 0L |
|
for byte in bytes: |
|
length = (length << 8) | byte |
|
try: |
|
length = int(length) |
|
except OverflowError: |
|
pass |
|
else: |
|
length = byte |
|
return length |
|
|
|
def _read_value(self, nr, length): |
|
"""Read a value from the input.""" |
|
bytes = self._read_bytes(length) |
|
value = bytes |
|
return value |
|
|
|
def _read_byte(self): |
|
"""Return the next input byte, or raise an error on end-of-input.""" |
|
index, input = self.m_stack[-1] |
|
try: |
|
byte = ord(input[index]) |
|
except IndexError: |
|
raise Error, 'Premature end of input.' |
|
self.m_stack[-1][0] += 1 |
|
return byte |
|
|
|
def _read_bytes(self, count): |
|
"""Return the next `count' bytes of input. Raise error on |
|
end-of-input.""" |
|
index, input = self.m_stack[-1] |
|
bytes = input[index:index+count] |
|
if len(bytes) != count: |
|
raise Error, 'Premature end of input.' |
|
self.m_stack[-1][0] += count |
|
return bytes |
|
|
|
def _end_of_input(self): |
|
"""Return True if we are at the end of input.""" |
|
index, input = self.m_stack[-1] |
|
assert not index > len(input) |
|
return index == len(input) |
|
|
|
#---------------------------------------------------------------------- |
|
# Certificate class |
|
#---------------------------------------------------------------------- |
|
|
|
class Certificate: |
|
"""Represents a loaded certificate |
|
""" |
|
def __init__(self): |
|
self._init_data() |
|
|
|
def _init_data(self): |
|
self._fingerprint = None |
|
self._data = None |
|
self._subject = None |
|
self._filepath = None |
|
|
|
def load_PEMfile(self, certificate_path): |
|
"""Load a certificate from a file in PEM format |
|
""" |
|
self._init_data() |
|
self._filepath = certificate_path |
|
with open(self._filepath, "r") as inputFile: |
|
PEMdata = inputFile.read() |
|
# convert to binary (DER format) |
|
self._data = ssl.PEM_cert_to_DER_cert(PEMdata) |
|
|
|
def load_DERfile(self, certificate_path): |
|
"""Load a certificate from a file in PEM format |
|
""" |
|
self._init_data() |
|
self._filepath = certificate_path |
|
with open(self._filepath, "r") as inputFile: |
|
PEMdata = inputFile.read() |
|
# convert to binary (DER format) |
|
self._data = PEMdata |
|
|
|
def save_PEMfile(self, certificate_path): |
|
"""Save a certificate to a file in PEM format |
|
""" |
|
self._filepath = certificate_path |
|
# convert to text (PEM format) |
|
PEMdata = ssl.DER_cert_to_PEM_cert(self._data) |
|
with open(self._filepath, "w") as output_file: |
|
output_file.write(PEMdata) |
|
|
|
def load_data(self, data): |
|
self._init_data() |
|
self._data = data |
|
|
|
def get_data(self): |
|
return self._data |
|
|
|
def get_fingerprint(self): |
|
if self._fingerprint == None and self._data != None: |
|
sha1 = hashlib.sha1() |
|
sha1.update(self._data) |
|
self._fingerprint = sha1.digest() |
|
return self._fingerprint |
|
|
|
def get_subject(self): |
|
"""Get the certificate subject in human readable one line format |
|
""" |
|
if self._data != None: |
|
# use openssl to extract the subject text in single line format |
|
possl = subprocess.Popen(['openssl', 'x509', '-inform', 'DER', '-noout', '-subject', '-issuer', '-dates', '-nameopt', 'oneline'], |
|
shell=False, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=None) |
|
subjectText, error_text = possl.communicate(self.get_data()) |
|
return subjectText |
|
return None |
|
|
|
def get_subject_ASN1(self): |
|
"""Get the certificate subject in ASN1 encoded format as expected for the IOS trusted certificate keychain store |
|
""" |
|
if self._subject == None and self._data != None: |
|
self._subject = bytearray() |
|
decoder = Decoder() |
|
decoder.start(self._data) |
|
decoder.enter() |
|
decoder.enter() |
|
tag, value = decoder.read() # read version |
|
tag, value = decoder.read() # serial |
|
tag, value = decoder.read() |
|
tag, value = decoder.read() # issuer |
|
tag, value = decoder.read() # date |
|
decoder.enter() # enter in subject |
|
encoder = Encoder() |
|
encoder.start() |
|
self._process_subject(decoder, encoder) |
|
self._subject = encoder.output() |
|
return self._subject |
|
|
|
def _process_subject(self, input, output, indent=0): |
|
# trace = sys.stdout |
|
while not input.eof(): |
|
tag = input.peek() |
|
if tag[1] == ASN1.TypePrimitive: |
|
tag, value = input.read() |
|
if tag[0] == ASN1.PrintableString: |
|
value = string.upper(value) |
|
output.write(value, tag[0], tag[1], tag[2]) |
|
#trace.write(' ' * indent) |
|
#trace.write('[%s] %s (value %s)' % |
|
# (strclass(tag[2]), strid(tag[0]), repr(value))) |
|
#trace.write('\n') |
|
elif tag[1] == ASN1.TypeConstructed: |
|
#trace.write(' ' * indent) |
|
#trace.write('[%s] %s:\n' % (strclass(tag[2]), strid(tag[0]))) |
|
input.enter() |
|
output.enter(tag[0], tag[2]) |
|
self._process_subject(input, output, indent+2) |
|
output.leave() |
|
input.leave() |
|
|
|
#---------------------------------------------------------------------- |
|
# IOS TrustStore.sqlite3 handling |
|
#---------------------------------------------------------------------- |
|
|
|
class TrustStore: |
|
"""Represents the IOS trusted certificate store |
|
""" |
|
def __init__(self, path, title=None): |
|
self._path = path |
|
if title: |
|
self._title = title |
|
else: |
|
self._title = path |
|
self._tset = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"\ |
|
"<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n"\ |
|
"<plist version=\"1.0\">\n"\ |
|
"<array/>\n"\ |
|
"</plist>\n" |
|
#with open('cert_tset.plist', "rb") as inputFile: |
|
# self._tset = inputFile.read() |
|
|
|
|
|
def is_valid(self): |
|
conn = sqlite3.connect(self._path) |
|
c = conn.cursor() |
|
row = c.execute('SELECT count(*) FROM sqlite_master WHERE type=\'table\' AND name=\'tsettings\'').fetchone() |
|
conn.close() |
|
return (row[0] > 0) |
|
|
|
def _add_record(self, sha1, subj, tset, data): |
|
if not self.is_valid(): |
|
print " Invalid TrustStore.sqlite3" |
|
return |
|
conn = sqlite3.connect(self._path) |
|
c = conn.cursor() |
|
c.execute('SELECT COUNT(*) FROM tsettings WHERE subj=?', [sqlite3.Binary(subj)]) |
|
row = c.fetchone() |
|
if row[0] == 0: |
|
c.execute('INSERT INTO tsettings (sha1, subj, tset, data) VALUES (?, ?, ?, ?)', [sqlite3.Binary(sha1), sqlite3.Binary(subj), sqlite3.Binary(tset), sqlite3.Binary(data)]) |
|
print ' Certificate added' |
|
else: |
|
c.execute('UPDATE tsettings SET sha1=?, tset=?, data=? WHERE subj=?', [sqlite3.Binary(sha1), sqlite3.Binary(tset), sqlite3.Binary(data), sqlite3.Binary(subj)]) |
|
print ' Existing certificate replaced' |
|
conn.commit() |
|
conn.close() |
|
|
|
def _loadBlob(self, baseName, name): |
|
with open(baseName + '_' + name + '.bin', 'rb') as inputFile: |
|
return inputFile.read() |
|
|
|
def _saveBlob(self, baseName, name, data): |
|
with open(baseName + '_' + name + '.bin', 'wb') as outputFile: |
|
outputFile.write (data) |
|
|
|
|
|
def add_certificate(self, certificate): |
|
self._add_record(certificate.get_fingerprint(), certificate.get_subject_ASN1(), |
|
self._tset, certificate.get_data()) |
|
|
|
def export_certificates(self, base_filename): |
|
if not self.is_valid(): |
|
print " Invalid TrustStore.sqlite3" |
|
return |
|
conn = sqlite3.connect(self._path) |
|
c = conn.cursor() |
|
index = 1 |
|
print |
|
print self._title |
|
for row in c.execute('SELECT subj, data FROM tsettings'): |
|
cert = Certificate() |
|
cert.load_data(row[1]) |
|
if query_yes_no(" " + cert.get_subject() + " Export certificate", "no") == "yes": |
|
cert.save_PEMfile(base_filename + "_" + str(index) + ".crt") |
|
index = index + 1 |
|
conn.close() |
|
|
|
def export_certificates_data(self, base_filename): |
|
if not self.is_valid(): |
|
print " Invalid TrustStore.sqlite3" |
|
return |
|
conn = sqlite3.connect(self._path) |
|
c = conn.cursor() |
|
index = 1 |
|
for row in c.execute('SELECT sha1, subj, tset, data FROM tsettings'): |
|
cert = Certificate() |
|
cert.load_data(row[3]) |
|
if query_yes_no(" " + cert.get_subject() + " Export certificate", "no") == "yes": |
|
base_filename2 = base_filename + "_" + str(index) |
|
self._saveBlob(base_filename2, 'sha1', row[0]) |
|
self._saveBlob(base_filename2, 'subj', row[1]) |
|
self._saveBlob(base_filename2, 'tset', row[2]) |
|
self._saveBlob(base_filename2, 'data', row[3]) |
|
conn.close() |
|
|
|
def import_certificate_data(self, base_filename): |
|
certificateSha1 = self._loadBlob(base_filename, 'sha1') |
|
certificateSubject = self._loadBlob(base_filename, 'subj') |
|
certificateTSet = self._loadBlob(base_filename, 'tset') |
|
certificateData = self._loadBlob(base_filename, 'data') |
|
self._add_record(certificateSha1, certificateSubject, certificateTSet, certificateData) |
|
|
|
def list_certificates(self): |
|
#print |
|
#print self._title |
|
if not self.is_valid(): |
|
print " Invalid TrustStore.sqlite3" |
|
return |
|
conn = sqlite3.connect(self._path) |
|
c = conn.cursor() |
|
l = [] |
|
for row in c.execute('SELECT * FROM tsettings'): |
|
cert = Certificate() |
|
cert.load_data(row[3]) |
|
#print " ", cert.get_subject() |
|
l.append(cert.get_subject()) |
|
conn.close() |
|
return l |
|
|
|
def delete_certificates(self): |
|
if not self.is_valid(): |
|
print " Invalid TrustStore.sqlite3" |
|
return |
|
conn = sqlite3.connect(self._path) |
|
c = conn.cursor() |
|
print |
|
print self._title |
|
todelete = [] |
|
for row in c.execute('SELECT subj, data FROM tsettings'): |
|
cert = Certificate() |
|
cert.load_data(row[1]) |
|
if query_yes_no(" " + cert.get_subject() + " Delete certificate", "no") == "yes": |
|
todelete.append(row[0]) |
|
for item in todelete: |
|
c.execute('DELETE FROM tsettings WHERE subj=?', [item]) |
|
conn.commit() |
|
conn.close() |
|
|
|
def delete_cert_by_subject(self, _cert): |
|
if not self.is_valid(): |
|
print " Invalid TrustStore.sqlite3" |
|
return False |
|
conn = sqlite3.connect(self._path) |
|
c = conn.cursor() |
|
#print |
|
#print self._title |
|
todelete = [] |
|
for row in c.execute('SELECT subj, data FROM tsettings'): |
|
cert = Certificate() |
|
cert.load_data(row[1]) |
|
_subject = cert.get_subject() |
|
if _subject == _cert: |
|
if query_yes_no(_subject + " Delete certificate", "no") == "yes": |
|
todelete.append(row[0]) |
|
for item in todelete: |
|
c.execute('DELETE FROM tsettings WHERE subj=?', [item]) |
|
conn.commit() |
|
conn.close() |
|
return True |
|
|
|
#---------------------------------------------------------------------- |
|
# IOS Simulator access |
|
#---------------------------------------------------------------------- |
|
|
|
class IOSSimulator: |
|
"""Represents an instance of an IOS simulator folder |
|
""" |
|
simulatorDir = os.getenv('HOME') + "/Library/Developer/CoreSimulator/Devices/" |
|
trustStorePath = "/data/Library/Keychains/TrustStore.sqlite3" |
|
|
|
def __init__(self, subdir): |
|
self.plist = plistlib.readPlist(self.simulatorDir + subdir + "/device.plist") |
|
self.version = self.plist["runtime"].split(".")[-1].replace("iOS-", "").replace("-", ".") |
|
self.title = self.plist["name"] + " " + self.version |
|
self.truststore_file = self.simulatorDir + subdir + self.trustStorePath |
|
|
|
def is_valid(self): |
|
return os.path.isfile(self.truststore_file) |
|
|
|
def ios_simulators(): |
|
"""An iterator over the available IOS simulator versions |
|
""" |
|
for sdk_dir in os.listdir(IOSSimulator.simulatorDir): |
|
if not sdk_dir.startswith('.'): |
|
simulator = IOSSimulator(sdk_dir) |
|
if simulator.is_valid(): |
|
yield simulator |
|
|
|
#---------------------------------------------------------------------- |
|
# Device backup support |
|
#---------------------------------------------------------------------- |
|
|
|
class DeviceBackup: |
|
"""Represents an instance of an IOS simulator folder |
|
""" |
|
trustStore_filename = "61c8b15a0110ab17d1b7467c3a042eb1458426c6" |
|
|
|
def __init__(self, path): |
|
self._path = path |
|
self._isvalid = False |
|
info_plist = self._path + "/Info.plist" |
|
if os.path.isfile(info_plist): |
|
try: |
|
info = plistlib.readPlist(info_plist) |
|
self.device_name = info["Device Name"] |
|
self.title = "Backup of " + self.device_name + " - " + str(info["Last Backup Date"]) |
|
self._isvalid = True |
|
except: |
|
pass |
|
|
|
def is_valid(self): |
|
return self._isvalid |
|
|
|
def get_truststore_file(self): |
|
return self._path + "/" + DeviceBackup.trustStore_filename |
|
|
|
|
|
def device_backups(): |
|
"""An iterator over the available device backups |
|
""" |
|
base_backupdir = os.getenv('HOME') + "/Library/Application Support/MobileSync/Backup/" |
|
for backup_dir in os.listdir(base_backupdir): |
|
backup = DeviceBackup(base_backupdir + backup_dir) |
|
if backup.is_valid(): |
|
yield backup |
|
|
|
#---------------------------------------------------------------------- |
|
# Individual command implementation and main function |
|
#---------------------------------------------------------------------- |
|
|
|
class Program: |
|
def import_to_simulator(self, certificate_filepath, truststore_filepath=None): |
|
cert = Certificate() |
|
cert.load_PEMfile(certificate_filepath) |
|
print cert.get_subject() |
|
if truststore_filepath: |
|
if query_yes_no("Import certificate to " + truststore_filepath, "no") == "yes": |
|
tstore = TrustStore(truststore_filepath) |
|
tstore.add_certificate(cert) |
|
return |
|
for simulator in ios_simulators(): |
|
if query_yes_no("Import certificate to " + simulator.title, "no") == "yes": |
|
print "Importing to " + simulator.truststore_file |
|
tstore = TrustStore(simulator.truststore_file) |
|
tstore.add_certificate(cert) |
|
|
|
def addfromdump(self, dump_base_filename, truststore_filepath=None): |
|
if truststore_filepath: |
|
if query_yes_no("Import to " + truststore_filepath, "no") == "yes": |
|
tstore = TrustStore(truststore_filepath) |
|
tstore.import_certificate_data(dump_base_filename) |
|
return |
|
for simulator in ios_simulators(): |
|
if query_yes_no("Import to " + simulator.title, "no") == "yes": |
|
print "Importing to " + simulator.truststore_file |
|
tstore = TrustStore(simulator.truststore_file) |
|
tstore.import_certificate_data(dump_base_filename) |
|
|
|
def list_simulator_trustedcertificates(self, truststore_filepath=None): |
|
if truststore_filepath: |
|
tstore = TrustStore(truststore_filepath) |
|
tstore.list_certificates() |
|
return |
|
for simulator in ios_simulators(): |
|
tstore = TrustStore(simulator.truststore_file, simulator.title) |
|
tstore.list_certificates() |
|
|
|
def export_simulator_trustedcertificates(self, certificate_base_filename, mode_dump, truststore_filepath=None): |
|
if truststore_filepath: |
|
tstore = TrustStore(truststore_filepath) |
|
if mode_dump: |
|
tstore.export_certificates_data(certificate_base_filename) |
|
else: |
|
tstore.export_certificates(certificate_base_filename) |
|
return |
|
for simulator in ios_simulators(): |
|
tstore = TrustStore(simulator.truststore_file, simulator.title) |
|
if mode_dump: |
|
tstore.export_certificates_data(certificate_base_filename + "_" + simulator.version) |
|
else: |
|
tstore.export_certificates(certificate_base_filename + "_" + simulator.version) |
|
|
|
def delete_simulator_trustedcertificates(self, truststore_filepath=None): |
|
if truststore_filepath: |
|
tstore = TrustStore(truststore_filepath) |
|
tstore.delete_certificates() |
|
return |
|
for simulator in ios_simulators(): |
|
tstore = TrustStore(simulator.truststore_file, simulator.title) |
|
tstore.delete_certificates() |
|
|
|
def list_device_trustedcertificates(self): |
|
for backup in device_backups(): |
|
tstore = TrustStore(backup.get_truststore_file(), backup.title) |
|
tstore.list_certificates() |
|
|
|
def export_device_trustedcertificates(self, certificate_base_filename, mode_dump): |
|
for backup in device_backups(): |
|
tstore = TrustStore(backup.get_truststore_file(), backup.title) |
|
if mode_dump: |
|
tstore.export_certificates_data(certificate_base_filename + "_" + backup.device_name) |
|
else: |
|
tstore.export_certificates(certificate_base_filename + "_" + backup.device_name) |
|
|
|
def run(self): |
|
parser = argparse.ArgumentParser() |
|
group = parser.add_mutually_exclusive_group(required=True) |
|
group.add_argument("-l", "--list", help="list custom trusted certificates in IOS simulator", action="store_true") |
|
group.add_argument("-d", "--delete", help="delete custom trusted certificates in IOS simulator", action="store_true") |
|
group.add_argument("-a", "--add", help="specifies a certificate file in PEM format to import and add to the IOS simulator trusted list", dest='certificate_file') |
|
group.add_argument("-e", "--export", help="export custom trusted certificates from IOS simulator in PEM format. ", dest='export_base_filename') |
|
group.add_argument("--dump", help="dump custom trusted certificates records from IOS simulator. ", dest='dump_base_filename') |
|
group.add_argument("--addfromdump", help="add custom trusted certificates records to IOS simulator from dump file created with --dump. ", dest='adddump_base_filename') |
|
parser.add_argument("-t", "--truststore", help="specify the path of the IOS TrustStore.sqlite3 file to edit. The default is to select and prompt for each available version") |
|
parser.add_argument("-b", "--devicebackup", help="(experimental) select a device backup as the TrustStore.sqlite3 source for list or export", action="store_true") |
|
args = parser.parse_args() |
|
if args.truststore and not os.path.isfile(args.truststore): |
|
print "invalid file: ", args.truststore |
|
exit(1) |
|
if args.devicebackup: |
|
if args.list: |
|
self.list_device_trustedcertificates() |
|
elif args.export_base_filename: |
|
self.export_device_trustedcertificates(args.export_base_filename, False) |
|
elif args.dump_base_filename: |
|
self.export_device_trustedcertificates(args.dump_base_filename, True) |
|
else: |
|
print "option not supported" |
|
elif args.list: |
|
self.list_simulator_trustedcertificates(args.truststore) |
|
elif args.delete: |
|
self.delete_simulator_trustedcertificates(args.truststore) |
|
elif args.certificate_file: |
|
self.import_to_simulator(args.certificate_file, args.truststore) |
|
elif args.export_base_filename: |
|
self.export_simulator_trustedcertificates(args.export_base_filename, False, args.truststore) |
|
elif args.dump_base_filename: |
|
self.export_simulator_trustedcertificates(args.dump_base_filename, True, args.truststore) |
|
elif args.adddump_base_filename: |
|
self.addfromdump(args.adddump_base_filename, args.truststore) |
|
print |
|
|
|
if __name__ == "__main__": |
|
program = Program() |
|
program.run() |