75f5c2db254c0167a0e396254460de09b775d203trawick#!/usr/bin/env python
75f5c2db254c0167a0e396254460de09b775d203trawick#
75f5c2db254c0167a0e396254460de09b775d203trawick# Licensed to the Apache Software Foundation (ASF) under one or more
75f5c2db254c0167a0e396254460de09b775d203trawick# contributor license agreements. See the NOTICE file distributed with
75f5c2db254c0167a0e396254460de09b775d203trawick# this work for additional information regarding copyright ownership.
75f5c2db254c0167a0e396254460de09b775d203trawick# The ASF licenses this file to You under the Apache License, Version 2.0
75f5c2db254c0167a0e396254460de09b775d203trawick# (the "License"); you may not use this file except in compliance with
75f5c2db254c0167a0e396254460de09b775d203trawick# the License. You may obtain a copy of the License at
75f5c2db254c0167a0e396254460de09b775d203trawick#
75f5c2db254c0167a0e396254460de09b775d203trawick# http://www.apache.org/licenses/LICENSE-2.0
75f5c2db254c0167a0e396254460de09b775d203trawick#
75f5c2db254c0167a0e396254460de09b775d203trawick# Unless required by applicable law or agreed to in writing, software
75f5c2db254c0167a0e396254460de09b775d203trawick# distributed under the License is distributed on an "AS IS" BASIS,
75f5c2db254c0167a0e396254460de09b775d203trawick# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
75f5c2db254c0167a0e396254460de09b775d203trawick# See the License for the specific language governing permissions and
75f5c2db254c0167a0e396254460de09b775d203trawick# limitations under the License.
75f5c2db254c0167a0e396254460de09b775d203trawick
75f5c2db254c0167a0e396254460de09b775d203trawickimport binascii
75f5c2db254c0167a0e396254460de09b775d203trawickimport os
75f5c2db254c0167a0e396254460de09b775d203trawickimport sqlite3
75f5c2db254c0167a0e396254460de09b775d203trawickimport ssl
75f5c2db254c0167a0e396254460de09b775d203trawickimport struct
75f5c2db254c0167a0e396254460de09b775d203trawickimport sys
75f5c2db254c0167a0e396254460de09b775d203trawickimport tempfile
75f5c2db254c0167a0e396254460de09b775d203trawick
75f5c2db254c0167a0e396254460de09b775d203trawickfrom contextlib import closing
75f5c2db254c0167a0e396254460de09b775d203trawick
75f5c2db254c0167a0e396254460de09b775d203trawickSERVER_START = 1
75f5c2db254c0167a0e396254460de09b775d203trawickKEY_START = 2
75f5c2db254c0167a0e396254460de09b775d203trawickCERT_START = 3
75f5c2db254c0167a0e396254460de09b775d203trawickSCT_START = 4
75f5c2db254c0167a0e396254460de09b775d203trawick
75f5c2db254c0167a0e396254460de09b775d203trawick
75f5c2db254c0167a0e396254460de09b775d203trawickdef usage():
75f5c2db254c0167a0e396254460de09b775d203trawick print >> sys.stderr, ('Usage: %s /path/to/audit/files ' +
75f5c2db254c0167a0e396254460de09b775d203trawick '[/path/to/log-config-db]') % sys.argv[0]
75f5c2db254c0167a0e396254460de09b775d203trawick sys.exit(1)
75f5c2db254c0167a0e396254460de09b775d203trawick
75f5c2db254c0167a0e396254460de09b775d203trawick
75f5c2db254c0167a0e396254460de09b775d203trawickdef audit(fn, tmp, already_checked, cur):
75f5c2db254c0167a0e396254460de09b775d203trawick print 'Auditing %s...' % fn
75f5c2db254c0167a0e396254460de09b775d203trawick
75f5c2db254c0167a0e396254460de09b775d203trawick # First, parse the audit file into a series of related
75f5c2db254c0167a0e396254460de09b775d203trawick #
75f5c2db254c0167a0e396254460de09b775d203trawick # 1. PEM file with certificate chain
75f5c2db254c0167a0e396254460de09b775d203trawick # 2. Individual SCT files
75f5c2db254c0167a0e396254460de09b775d203trawick #
75f5c2db254c0167a0e396254460de09b775d203trawick # Next, for each SCT, invoke verify_single_proof to verify.
75f5c2db254c0167a0e396254460de09b775d203trawick log_bytes = open(fn, 'rb').read()
75f5c2db254c0167a0e396254460de09b775d203trawick offset = 0
75f5c2db254c0167a0e396254460de09b775d203trawick while offset < len(log_bytes):
75f5c2db254c0167a0e396254460de09b775d203trawick print 'Got package from server...'
75f5c2db254c0167a0e396254460de09b775d203trawick val = struct.unpack_from('>H', log_bytes, offset)
75f5c2db254c0167a0e396254460de09b775d203trawick assert val[0] == SERVER_START
75f5c2db254c0167a0e396254460de09b775d203trawick offset += 2
75f5c2db254c0167a0e396254460de09b775d203trawick
75f5c2db254c0167a0e396254460de09b775d203trawick assert struct.unpack_from('>H', log_bytes, offset)[0] == KEY_START
75f5c2db254c0167a0e396254460de09b775d203trawick offset += 2
75f5c2db254c0167a0e396254460de09b775d203trawick
75f5c2db254c0167a0e396254460de09b775d203trawick key_size = struct.unpack_from('>H', log_bytes, offset)[0]
75f5c2db254c0167a0e396254460de09b775d203trawick assert key_size > 0
75f5c2db254c0167a0e396254460de09b775d203trawick offset += 2
75f5c2db254c0167a0e396254460de09b775d203trawick
75f5c2db254c0167a0e396254460de09b775d203trawick key = log_bytes[offset:offset + key_size]
75f5c2db254c0167a0e396254460de09b775d203trawick offset += key_size
75f5c2db254c0167a0e396254460de09b775d203trawick
75f5c2db254c0167a0e396254460de09b775d203trawick # at least one certificate
75f5c2db254c0167a0e396254460de09b775d203trawick assert struct.unpack_from('>H', log_bytes, offset)[0] == CERT_START
75f5c2db254c0167a0e396254460de09b775d203trawick
75f5c2db254c0167a0e396254460de09b775d203trawick # for each certificate:
75f5c2db254c0167a0e396254460de09b775d203trawick leaf = None
75f5c2db254c0167a0e396254460de09b775d203trawick while struct.unpack_from('>H', log_bytes, offset)[0] == CERT_START:
75f5c2db254c0167a0e396254460de09b775d203trawick offset += 2
75f5c2db254c0167a0e396254460de09b775d203trawick val = struct.unpack_from('BBB', log_bytes, offset)
75f5c2db254c0167a0e396254460de09b775d203trawick offset += 3
75f5c2db254c0167a0e396254460de09b775d203trawick der_size = (val[0] << 16) | (val[1] << 8) | (val[2] << 0)
75f5c2db254c0167a0e396254460de09b775d203trawick print ' Certificate size:', hex(der_size)
75f5c2db254c0167a0e396254460de09b775d203trawick if not leaf:
75f5c2db254c0167a0e396254460de09b775d203trawick leaf = (offset, der_size)
75f5c2db254c0167a0e396254460de09b775d203trawick offset += der_size
75f5c2db254c0167a0e396254460de09b775d203trawick
75f5c2db254c0167a0e396254460de09b775d203trawick pem = ssl.DER_cert_to_PEM_cert(log_bytes[leaf[0]:leaf[0] + leaf[1]])
75f5c2db254c0167a0e396254460de09b775d203trawick
75f5c2db254c0167a0e396254460de09b775d203trawick tmp_leaf_pem = tempfile.mkstemp(text=True)
75f5c2db254c0167a0e396254460de09b775d203trawick with closing(os.fdopen(tmp_leaf_pem[0], 'w')) as f:
75f5c2db254c0167a0e396254460de09b775d203trawick f.write(pem)
75f5c2db254c0167a0e396254460de09b775d203trawick
75f5c2db254c0167a0e396254460de09b775d203trawick # at least one SCT
75f5c2db254c0167a0e396254460de09b775d203trawick assert struct.unpack_from('>H', log_bytes, offset)[0] == SCT_START
75f5c2db254c0167a0e396254460de09b775d203trawick
75f5c2db254c0167a0e396254460de09b775d203trawick # for each SCT:
75f5c2db254c0167a0e396254460de09b775d203trawick while offset < len(log_bytes) and \
75f5c2db254c0167a0e396254460de09b775d203trawick struct.unpack_from('>H', log_bytes, offset)[0] == SCT_START:
75f5c2db254c0167a0e396254460de09b775d203trawick offset += 2
75f5c2db254c0167a0e396254460de09b775d203trawick len_offset = offset
75f5c2db254c0167a0e396254460de09b775d203trawick sct_size = struct.unpack_from('>H', log_bytes, len_offset)[0]
75f5c2db254c0167a0e396254460de09b775d203trawick offset += 2
75f5c2db254c0167a0e396254460de09b775d203trawick print ' SCT size:', hex(sct_size)
75f5c2db254c0167a0e396254460de09b775d203trawick log_id = log_bytes[offset + 1:offset + 1 + 32]
75f5c2db254c0167a0e396254460de09b775d203trawick log_id_hex = binascii.hexlify(log_id).upper()
75f5c2db254c0167a0e396254460de09b775d203trawick print ' Log id: %s' % log_id_hex
75f5c2db254c0167a0e396254460de09b775d203trawick timestamp_ms = struct.unpack_from('>Q', log_bytes, offset + 33)[0]
75f5c2db254c0167a0e396254460de09b775d203trawick print ' Timestamp: %s' % timestamp_ms
75f5c2db254c0167a0e396254460de09b775d203trawick
75f5c2db254c0167a0e396254460de09b775d203trawick # If we ever need the full SCT: sct = (offset, sct_size)
75f5c2db254c0167a0e396254460de09b775d203trawick offset += sct_size
75f5c2db254c0167a0e396254460de09b775d203trawick
75f5c2db254c0167a0e396254460de09b775d203trawick if key in already_checked:
75f5c2db254c0167a0e396254460de09b775d203trawick print ' (SCTs already checked)'
75f5c2db254c0167a0e396254460de09b775d203trawick continue
75f5c2db254c0167a0e396254460de09b775d203trawick
75f5c2db254c0167a0e396254460de09b775d203trawick already_checked[key] = True
75f5c2db254c0167a0e396254460de09b775d203trawick
75f5c2db254c0167a0e396254460de09b775d203trawick log_url_arg = ''
75f5c2db254c0167a0e396254460de09b775d203trawick if cur:
75f5c2db254c0167a0e396254460de09b775d203trawick stmt = 'SELECT * FROM loginfo WHERE log_id = ?'
75f5c2db254c0167a0e396254460de09b775d203trawick cur.execute(stmt, [log_id_hex])
75f5c2db254c0167a0e396254460de09b775d203trawick recs = list(cur.fetchall())
75f5c2db254c0167a0e396254460de09b775d203trawick if len(recs) > 0 and recs[0][6] is not None:
75f5c2db254c0167a0e396254460de09b775d203trawick log_url = recs[0][6]
75f5c2db254c0167a0e396254460de09b775d203trawick
75f5c2db254c0167a0e396254460de09b775d203trawick # verify_single_proof doesn't accept <scheme>://
75f5c2db254c0167a0e396254460de09b775d203trawick if '://' in log_url:
75f5c2db254c0167a0e396254460de09b775d203trawick log_url = log_url.split('://')[1]
75f5c2db254c0167a0e396254460de09b775d203trawick log_url_arg = '--log_url %s' % log_url
75f5c2db254c0167a0e396254460de09b775d203trawick
75f5c2db254c0167a0e396254460de09b775d203trawick print ' Log URL: ' + log_url
75f5c2db254c0167a0e396254460de09b775d203trawick
75f5c2db254c0167a0e396254460de09b775d203trawick cmd = 'verify_single_proof.py --cert %s --timestamp %s %s' % \
75f5c2db254c0167a0e396254460de09b775d203trawick (tmp_leaf_pem[1], timestamp_ms, log_url_arg)
75f5c2db254c0167a0e396254460de09b775d203trawick print '>%s<' % cmd
75f5c2db254c0167a0e396254460de09b775d203trawick os.system(cmd)
75f5c2db254c0167a0e396254460de09b775d203trawick
75f5c2db254c0167a0e396254460de09b775d203trawick os.unlink(tmp_leaf_pem[1])
75f5c2db254c0167a0e396254460de09b775d203trawick
75f5c2db254c0167a0e396254460de09b775d203trawick
75f5c2db254c0167a0e396254460de09b775d203trawickdef main():
75f5c2db254c0167a0e396254460de09b775d203trawick if len(sys.argv) != 2 and len(sys.argv) != 3:
75f5c2db254c0167a0e396254460de09b775d203trawick usage()
75f5c2db254c0167a0e396254460de09b775d203trawick
75f5c2db254c0167a0e396254460de09b775d203trawick top = sys.argv[1]
75f5c2db254c0167a0e396254460de09b775d203trawick tmp = '/tmp'
75f5c2db254c0167a0e396254460de09b775d203trawick
75f5c2db254c0167a0e396254460de09b775d203trawick if len(sys.argv) == 3:
75f5c2db254c0167a0e396254460de09b775d203trawick cxn = sqlite3.connect(sys.argv[2])
75f5c2db254c0167a0e396254460de09b775d203trawick cur = cxn.cursor()
75f5c2db254c0167a0e396254460de09b775d203trawick else:
75f5c2db254c0167a0e396254460de09b775d203trawick cur = None
75f5c2db254c0167a0e396254460de09b775d203trawick
75f5c2db254c0167a0e396254460de09b775d203trawick # could serialize this between runs to further limit duplicate checking
75f5c2db254c0167a0e396254460de09b775d203trawick already_checked = dict()
75f5c2db254c0167a0e396254460de09b775d203trawick
75f5c2db254c0167a0e396254460de09b775d203trawick for dirpath, dnames, fnames in os.walk(top):
75f5c2db254c0167a0e396254460de09b775d203trawick fnames = [fn for fn in fnames if fn[-4:] == '.out']
75f5c2db254c0167a0e396254460de09b775d203trawick for fn in fnames:
75f5c2db254c0167a0e396254460de09b775d203trawick audit(os.path.join(dirpath, fn), tmp, already_checked, cur)
75f5c2db254c0167a0e396254460de09b775d203trawick
75f5c2db254c0167a0e396254460de09b775d203trawick
75f5c2db254c0167a0e396254460de09b775d203trawickif __name__ == "__main__":
75f5c2db254c0167a0e396254460de09b775d203trawick main()