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.