At some point I decided to block all CAs (Certificate Authorities) on my Android phone of which I did not know which page I needed them for. As expected I broke most of my apps and needed a way to identify which CAs I had to enable to get a minimal set. As most modern apps only report a “connection error” when encountering invalid certificates, I needed an external device&app independent approach.
Approach
- Connect phone to a wireless access point
- Start creating PCAP trace (wireshark, tshark, tcpdump)
- Use phone for a while
- Parse trace with script
- Identify failed handshakes
Script
The python script will parse through a PCAP file using pyshark. The display filter (ssl.handshake.certificates or ssl.alert_message.desc == 46)
is used to filter the trace down to certificate exchanges and Certificate Unknown
error messages.
#ssl_error_extract.py
#(c) bbutterly 2017
import operator
import pyshark
cap = pyshark.FileCapture('sniff.pcap', keep_packets=False, display_filter="(ssl.handshake.certificate or ssl.alert_message.desc == 46)")
c_pkg = 0
print_fine_certs_too = False
cons = []
class con:
rem_ip = ""
rem_port = 0
chain = ""
error = False
def __init__(self,rem_ip,rem_port):
self.rem_ip = rem_ip
self.rem_port = rem_port
def cmp(self,con):
return (self.rem_ip == con.rem_ip and self.rem_port == con.rem_port)
def out(self):
return "=== " + self.rem_ip + ":" + str(self.rem_port) + " ===\n" + ("Certificate Error\n" if self.error else "") + str(self.chain) + "\n"
def exists(tcon):
for con in cons:
if (con.cmp(tcon)):
return con
return False
for pkg in cap:
if 'ssl' in pkg:
if "Alert" in str(pkg['SSL']):
tcon = con(pkg['IP'].get_field('dst'), pkg['TCP'].get_field('dstport'))
ocon = exists(tcon)
if ocon:
ocon.error = True
else:
tcon = con(pkg['IP'].get_field('src'), pkg['TCP'].get_field('srcport'))
if not exists(tcon):
chain = ""
for line in str(pkg['ssl']).split("\n"):
if ("Certificate: " in line):
chain+=str(line.split('(')[1]) + "\n"
tcon.chain = chain
cons.append(tcon)
out_file = open("output.txt",'w')
clean = ""
for con in cons:
if con.error:
print con.out() + "\n\n"
out_file.write(con.out()+"\n\n")
else:
clean+=con.out() + "\n\n"
if print_fine_certs_too:
print clean
out_file.write(clean)
out_file.close()
Results
The results look as follows:
=== 104.87.205.97:443 ===
Certificate Error
id-at-commonName=*.urbanairship.com,id-at-organizationalUnitName=IT,id-at-organizationName=URBAN AIRSHIP,id-at-localityName=Portland,id-at-stateOrProvinceName=OR,id-at-count
id-at-commonName=Verizon Akamai SureServer CA G14-SHA2,id-at-organizationalUnitName=Cybertrust,id-at-organizationName=Verizon Enterprise Solutions,id-at-localityName=Amsterd
id-at-commonName=Baltimore CyberTrust Root,id-at-organizationalUnitName=CyberTrust,id-at-organizationName=Baltimore,id-at-countryName=IE)
=== 52.218.65.137:443 ===
Certificate Error
id-at-commonName=*.s3.amazonaws.com,id-at-organizationName=Amazon.com Inc.,id-at-localityName=Seattle,id-at-stateOrProvinceName=Washington,id-at-countryName=US)
id-at-commonName=DigiCert Baltimore CA-2 G2,id-at-organizationalUnitName=www.digicert.com,id-at-organizationName=DigiCert Inc,id-at-countryName=US)
=== 52.218.20.81:443 ===
Certificate Error
id-at-commonName=*.s3.amazonaws.com,id-at-organizationName=Amazon.com Inc.,id-at-localityName=Seattle,id-at-stateOrProvinceName=Washington,id-at-countryName=US)
id-at-commonName=DigiCert Baltimore CA-2 G2,id-at-organizationalUnitName=www.digicert.com,id-at-organizationName=DigiCert Inc,id-at-countryName=US)
=== 54.231.131.106:443 ===
Certificate Error
id-at-commonName=*.s3.amazonaws.com,id-at-organizationName=Amazon.com Inc.,id-at-localityName=Seattle,id-at-stateOrProvinceName=Washington,id-at-countryName=US)
id-at-commonName=DigiCert Baltimore CA-2 G2,id-at-organizationalUnitName=www.digicert.com,id-at-organizationName=DigiCert Inc,id-at-countryName=US)
Each entry contains the remote IP address, the destination port and the certificate chain as it was extracted from the key exchange. It is possible that chain is not complete. In such a situation further manual research will be necessary.