diff options
Diffstat (limited to 'scripts/rcynic-lta')
-rwxr-xr-x | scripts/rcynic-lta | 1055 |
1 files changed, 0 insertions, 1055 deletions
diff --git a/scripts/rcynic-lta b/scripts/rcynic-lta deleted file mode 100755 index 4c55db92..00000000 --- a/scripts/rcynic-lta +++ /dev/null @@ -1,1055 +0,0 @@ -#!/usr/local/bin/python - -# $Id$ - -# Copyright (C) 2013 Dragon Research Labs ("DRL") -# -# Permission to use, copy, modify, and distribute this software for any -# purpose with or without fee is hereby granted, provided that the above -# copyright notice and this permission notice appear in all copies. -# -# THE SOFTWARE IS PROVIDED "AS IS" AND DRL DISCLAIMS ALL WARRANTIES WITH -# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY -# AND FITNESS. IN NO EVENT SHALL DRL BE LIABLE FOR ANY SPECIAL, DIRECT, -# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM -# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE -# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR -# PERFORMANCE OF THIS SOFTWARE. - -######################################################################## -# -# DANGER WILL ROBINSON -# -# This is a PROTOTYPE of a local trust anchor mechanism. At the -# moment, it DOES NOT WORK by any sane standard of measurement. It -# produces output, but there is no particular reason to believe said -# output is useful, and fairly good reason to believe that it is not. -# -# With luck, this may eventually mutate into something useful. For -# now, just leave it alone unless you really know what you are doing, -# in which case, on your head be it. -# -# YOU HAVE BEEN WARNED -# -######################################################################## - -import os -import sys -import yaml -import glob -import time -import shutil -import base64 -import socket -import sqlite3 -import weakref -import rpki.POW -import rpki.x509 -import rpki.sundial -import rpki.resource_set - -# Teach SQLite3 about our data types. - -sqlite3.register_adapter(rpki.POW.IPAddress, - lambda x: buffer("_" + x.toBytes())) - -sqlite3.register_converter("RangeVal", - lambda s: long(s) if s.isdigit() else rpki.POW.IPAddress.fromBytes(s[1:])) - -sqlite3.register_adapter(rpki.x509.X501DN, str) - - -class main(object): - - tal_directory = None - constraints = None - rcynic_input = None - rcynic_output = None - tals = None - keyfile = None - - ltakey = None - ltacer = None - - ltauri = "rsync://localhost/lta" - ltasia = ltauri + "/" - ltaaia = ltauri + ".cer" - ltamft = ltauri + "/lta.mft" - ltacrl = ltauri + "/lta.crl" - - cer_delta = rpki.sundial.timedelta(days = 7) - crl_delta = rpki.sundial.timedelta(hours = 1) - - all_mentioned_resources = rpki.resource_set.resource_bag() - - - def __init__(self): - print "Parsing YAML" - self.parse_yaml() - print - print "Parsing TALs" - self.parse_tals() - print - print "Creating DB" - self.rpdb = RPDB(self.db_name) - print - print "Creating CA" - self.create_ca() - print - print "Loading DB" - self.rpdb.load(self.rcynic_input) - print - print "Processing adds and drops" - self.process_add_drop() - print - print "Processing deletions" - self.process_constraint_deletions() - print - print "Re-parenting TAs" - self.re_parent_tas() - print - print "Generating CRL and manifest" - self.generate_crl_and_manifest() - print - print "Committing final changes to DB" - self.rpdb.commit() - print - print "Dumping para-objects" - self.rpdb.dump_paras(self.rcynic_output) - print - print "Closing DB" - self.rpdb.close() - - - def create_ca(self): - self.serial = Serial() - self.ltakey = rpki.x509.RSA.generate(quiet = True) - cer = OutgoingX509.self_certify( - cn = "%s LTA Root Certificate" % socket.getfqdn(), - keypair = self.ltakey, - subject_key = self.ltakey.get_RSApublic(), - serial = self.serial(), - sia = (self.ltasia, self.ltamft, None), - notAfter = rpki.sundial.now() + self.cer_delta, - resources = rpki.resource_set.resource_bag.from_str("0-4294967295,0.0.0.0/0,::/0")) - subject_id = self.rpdb.find_keyname(cer.getSubject(), cer.get_SKI()) - self.rpdb.cur.execute("INSERT INTO outgoing (der, fn2, subject, issuer, uri, key) " - "VALUES (?, 'cer', ?, ?, ?, ?)", - (buffer(cer.get_DER()), subject_id, subject_id, self.ltaaia, - buffer(self.ltakey.get_DER()))) - self.ltacer = self.rpdb.find_outgoing_by_id(self.rpdb.cur.lastrowid) - - - def parse_yaml(self, fn = "rcynic-lta.yaml"): - y = yaml.safe_load(open(fn, "r")) - self.db_name = y["db-name"] - self.tal_directory = y["tal-directory"] - self.rcynic_input = y["rcynic-input"] - self.rcynic_output = y["rcynic-output"] - self.keyfile = y["keyfile"] - self.constraints = [Constraint(yc) for yc in y["constraints"]] - - - def parse_tals(self): - self.tals = {} - for fn in glob.iglob(os.path.join(self.tal_directory, "*.tal")): - with open(fn, "r") as f: - uri = f.readline().strip() - key = rpki.POW.Asymmetric.derReadPublic(base64.b64decode(f.read())) - self.tals[uri] = key - - - @staticmethod - def show_candidates(constraint, candidates): - print - print "Constraint:", repr(constraint) - print "Resources: ", constraint.mentioned_resources - for i, candidate in enumerate(candidates): - print " Candidate #%d id %d depth %d name %s uri %s" % ( - i, candidate.rowid, - candidate.depth, - candidate.subject_name, - candidate.uri) - if constraint.mentioned_resources <= candidate.resources: - print " Matched" - #print " Constraint resources:", constraint.mentioned_resources - #print " Candidate resources: ", candidate.resources - break - else: - print " No match" - - - def process_add_drop(self): - # - # We probably need to create the output root before running this, - # otherwise there's a chance that an "add" constraint will yield - # no viable candidate parent. Not likely to happen with current - # test setup where several of our roots claim 0/0. - # - for constraint in self.constraints: - candidates = self.rpdb.find_by_resource_bag(constraint.mentioned_resources) - candidates.sort(reverse = True, key = lambda candidate: candidate.depth) - #self.show_candidates(constraint, candidates) - constraint.drop(candidates) - constraint.add(candidates) - - - def process_constraint_deletions(self): - for obj in self.rpdb.find_by_resource_bag(self.all_mentioned_resources): - self.add_para(obj, obj.resources - self.all_mentioned_resources) - - - def re_parent_tas(self): - for uri, key in self.tals.iteritems(): - for ta in self.rpdb.find_by_ski_or_uri(key.calculateSKI(), uri): - if ta.para_obj is None: - self.add_para(ta, ta.resources - self.all_mentioned_resources) - - - def add_para(self, obj, resources): - return self.rpdb.add_para( - obj = obj, - resources = resources, - serial = self.serial, - ltacer = self.ltacer, - ltasia = self.ltasia, - ltaaia = self.ltaaia, - ltamft = self.ltamft, - ltacrl = self.ltacrl, - ltakey = self.ltakey) - - - def generate_crl_and_manifest(self): - thisUpdate = rpki.sundial.now() - nextUpdate = thisUpdate + self.crl_delta - serial = self.serial() - issuer = self.ltacer.getSubject() - aki = buffer(self.ltacer.get_SKI()) - - crl = OutgoingCRL.generate( - keypair = self.ltakey, - issuer = self.ltacer, - serial = serial, - thisUpdate = thisUpdate, - nextUpdate = nextUpdate, - revokedCertificates = ()) - - issuer_id = self.rpdb.find_keyname(issuer, aki) - - self.rpdb.cur.execute("INSERT INTO outgoing (der, fn2, subject, issuer, uri) " - "VALUES (?, 'crl', NULL, ?, ?)", - (buffer(crl.get_DER()), issuer_id, self.ltacrl)) - crl = self.rpdb.find_outgoing_by_id(self.rpdb.cur.lastrowid) - - key = rpki.x509.RSA.generate(quiet = True) - - cer = self.ltacer.issue( - keypair = self.ltakey, - subject_key = key.get_RSApublic(), - serial = serial, - sia = (None, None, self.ltamft), - aia = self.ltaaia, - crldp = self.ltacrl, - resources = rpki.resource_set.resource_bag.from_inheritance(), - notAfter = self.ltacer.getNotAfter(), - is_ca = False) - - # Temporary kludge, need more general solution but that requires - # more refactoring than I feel like doing this late in the day. - # - names_and_objs = [(uri, OutgoingObject.create(fn2 = fn2, der = der, uri = uri, - rpdb = None, rowid = None, - subject_id = None, issuer_id = None)) - for fn2, der, uri in - self.rpdb.cur.execute("SELECT fn2, der, uri FROM outgoing WHERE issuer = ?", - (self.ltacer.rowid,))] - - mft = OutgoingSignedManifest.build( - serial = serial, - thisUpdate = thisUpdate, - nextUpdate = nextUpdate, - names_and_objs = names_and_objs, - keypair = key, - certs = cer) - - subject_id = self.rpdb.find_keyname(cer.getSubject(), cer.get_SKI()) - - self.rpdb.cur.execute("INSERT INTO outgoing (der, fn2, subject, issuer, uri, key) " - "VALUES (?, 'mft', ?, ?, ?, ?)", - (buffer(mft.get_DER()), subject_id, issuer_id, self.ltamft, buffer(key.get_DER()))) - - - @staticmethod - def parse_xki(s): - """ - Parse text form of an SKI or AKI. We accept two encodings: - colon-delimited hexadecimal, and URL-safe Base64. The former is - what OpenSSL prints in its text representation of SKI and AKI - extensions; the latter is the g(SKI) value that some RPKI CA engines - (including rpkid) use when constructing filenames. - - In either case, we check that the decoded result contains the right - number of octets to be a SHA-1 hash. - """ - - if ":" in s: - b = "".join(chr(int(c, 16)) for c in s.split(":")) - else: - b = base64.urlsafe_b64decode(s + ("=" * (4 - len(s) % 4))) - if len(b) != 20: - raise RuntimeError("Bad length for SHA1 xKI value: %r" % s) - return b - - - -class Serial(object): - - def __init__(self): - self.value = long(time.time()) << 32 - - def __call__(self): - self.value += 1 - return self.value - - -class ConstrainedObject(object): - # I keep expecting the classes derived from this to have some common - # methods, but so far it hasn't happened. Clean up eventually if not. - pass - -class ConstrainedROA(ConstrainedObject): - - def __init__(self, constraint, y): - self.constraint = constraint - self.asn = long(y["asn"]) if y is not None else None - self.maxlen = long(y["maxlen"]) if y is not None and "maxlen" in y else None - - def drop(self, candidates): - for candidate in candidates: - if isinstance(candidate, IncomingROA) and \ - self.constraint.mentioned_resources == candidate.resources and \ - (self.asn is None or self.asn == candidate.get_POW().getASID()): - print "Dropping ROA %r" % candidate - candidate.disposition = "delete" - - def add(self, candidates): - assert self.asn is not None - for candidate in candidates: - if isinstance(candidate, IncomingX509) and self.constraint.mentioned_resources <= candidate.resources: - print "Should add ROA %s %s\nunder candidate %s (depth %s resources %s)" % ( - self.asn, self.constraint.prefixes, candidate.subject_name, candidate.depth, candidate.resources) - break - -class ConstrainedGBR(ConstrainedObject): - - def __init__(self, constraint, y): - self.constraint = constraint - self.vcard = y - - def drop(self, candidates): - for candidate in candidates: - if isinstance(candidate, IncomingX509) and self.constraint.mentioned_resources == candidate.resources: - print "Dropping GBRs directly under %r" % candidate - for gbr in candidate.find_children("gbr"): - print "Dropping GBR %r" % gbr - gbr.disposition = "delete" - - def add(self, candidates): - assert self.vcard is not None - for candidate in candidates: - if isinstance(candidate, IncomingX509) and self.constraint.mentioned_resources <= candidate.resources: - print "Should add GBR\n%s\nunder candidate %s (depth %s resources %s)" % ( - "\n".join((" " * 4) + line for line in self.vcard.splitlines()), - candidate.subject_name, candidate.depth, candidate.resources) - break - -class ConstrainedRTR(ConstrainedObject): - - def __init__(self, constraint, y): - self.constraint = constraint - self.key = y["key"] if y is not None else None - self.subject = y["subject"] if y is not None else None - - def add(self, candidates): - raise NotImplementedError - - def drop(self, candidates): - for candidate in candidates: - if isinstance(candidate, IncomingX509) and not candidate.is_ca and \ - self.constraint.mentioned_resources == candidate.resources and \ - (self.subject is None or candidate.getSubject() == self.subject): - print "Dropping RTR certificate %r" % candidate - candidate.disposition = "delete" - -class Constraint(object): - - dispatch = dict(roa = ConstrainedROA, - gbr = ConstrainedGBR, - rtr = ConstrainedRTR) - - def __init__(self, y): - self.y = y # Mostly for debugging. I think. - self.prefixes = rpki.resource_set.resource_bag.from_str(str(y.get("prefix", ""))) - self.asns = rpki.resource_set.resource_bag.from_str(str(y.get("asn", ""))) - self.init_drops(y.get("drop", ())) - self.init_adds( y.get("add", ())) - - def init_drops(self, drops): - if drops == "all": - self.drops = tuple(d(self, None) for d in self.dispatch.itervalues()) - else: - dd = [] - for d in (drops if isinstance(drops, (list, tuple)) else [drops]): - if isinstance(d, str): - dd.append(self.dispatch[d[:-1]](self, None)) - elif isinstance(d, dict) and len(d) == 1: - dd.append(self.dispatch[d.keys()[0]](self, d.values()[0])) - else: - raise ValueError("Unexpected drop clause " + repr(drops)) - self.drops = tuple(dd) - - def init_adds(self, adds): - if not all(isinstance(a, dict) and len(a) == 1 for a in adds): - raise ValueError("Expected list of single-entry mappings, got " + repr(adds)) - self.adds = tuple(self.dispatch[a.keys()[0]](self, a.values()[0]) for a in adds) - - def drop(self, candidates): - for d in self.drops: - d.drop(candidates) - - def add(self, candidates): - for a in self.adds: - a.add(candidates) - - def __repr__(self): - return "<%s:%s %r>" % (self.__class__.__module__, self.__class__.__name__, self.y) - - @property - def mentioned_resources(self): - return self.prefixes | self.asns - - -class BaseObject(object): - """ - Mixin to add some SQL-related methods to classes derived from - rpki.x509.DER_object. - """ - - _rpdb = None - _rowid = None - _fn2 = None - _fn2map = None - _uri = None - _subject_id = None - _issuer_id = None - - @property - def rowid(self): - return self._rowid - - @property - def para_resources(self): - return self.resources if self.para_obj is None else self.para_obj.resources - - @property - def fn2(self): - return self._fn2 - - @property - def uri(self): - return self._uri - - @classmethod - def setfn2map(cls, **map): - cls._fn2map = map - for k, v in map.iteritems(): - v._fn2 = k - - @classmethod - def create(cls, rpdb, rowid, fn2, der, uri, subject_id, issuer_id): - self = cls._fn2map[fn2]() - if der is not None: - self.set(DER = der) - self._rpdb = rpdb - self._rowid = rowid - self._uri = uri - self._subject_id = subject_id - self._issuer_id = issuer_id - return self - - @property - def subject_id(self): - return self._subject_id - - @property - def subject_name(self): - return self._rpdb.find_keyname_by_id(self._subject_id)[0] - - @property - def issuer_id(self): - return self._issuer_id - - @property - def issuer_name(self): - return self._rpdb.find_keyname_by_id(self._subject_id)[0] - - -class IncomingObject(BaseObject): - - _depth = None - _is_ca = False - _disposition = None - - @property - def para_obj(self): - if getattr(self, "_para_id", None) is None: - self._rpdb.cur.execute("SELECT replacement FROM incoming WHERE id = ?", (self.rowid,)) - self._para_id = self._rpdb.cur.fetchone()[0] - return self._rpdb.find_outgoing_by_id(self._para_id) - - @para_obj.setter - def para_obj(self, value): - if value is None: - self._rpdb.cur.execute("DELETE FROM outgoing WHERE id IN (SELECT replacement FROM incoming WHERE id = ?)", - (self.rowid,)) - try: - del self._para_id - except AttributeError: - pass - else: - assert isinstance(value.rowid, int) - self._rpdb.cur.execute("UPDATE incoming SET replacement = ? WHERE id = ?", (value.rowid, self.rowid)) - self._para_id = value.rowid - - @property - def disposition(self): - if self._disposition is None: - self._disposition = self._rpdb.cur.execute("SELECT disposition FROM incoming " - "WHERE id = ?", (self.rowid,)).fetchone()[0] - return self._disposition - - @disposition.setter - def disposition(self, value): - self._rpdb.cur.execute("UPDATE incoming SET disposition = ? WHERE id = ?", (value, self.rowid)) - self._disposition = value - - @classmethod - def fromFile(cls, fn): - return cls._fn2map[os.path.splitext(fn)[1][1:]](DER_file = fn) - - @classmethod - def create(cls, rpdb, rowid, fn2, der, uri, subject_id, issuer_id, depth = None, is_ca = False): - assert der is not None - self = super(IncomingObject, cls).create(rpdb, rowid, fn2, der, uri, subject_id, issuer_id) - self._depth = depth - self._is_ca = is_ca - return self - - @property - def depth(self): - return self._depth - - @property - def is_ca(self): - return self._is_ca - - @property - def issuer(self): - if self._issuer_id is None or self._issuer_id == self._subject_id: - return None - return self._rpdb.find_incoming_by_id(self._issuer_id) - - -class OutgoingObject(BaseObject): - - @property - def orig_obj(self): - if getattr(self, "_orig_id", None) is None: - self._rpdb.cur.execute("SELECT id FROM incoming WHERE replacement = ?", (self.rowid,)) - r = self._rpdb.cur.fetchone() - self._orig_id = None if r is None else r[0] - return self._rpdb.find_incoming_by_id(self._orig_id) - - -class BaseX509(rpki.x509.X509): - - @property - def resources(self): - r = self.get_3779resources() - r.valid_until = None - return r - - def find_children(self, fn2 = None): - return self._rpdb._find_results(fn2, "WHERE issuer = ?", [self.subject_id]) - - -class BaseCRL(rpki.x509.CRL): - - @property - def resources(self): - return None - - -class CommonCMS(object): - - @property - def resources(self): - r = rpki.x509.X509(POW = self.get_POW().certs()[0]).get_3779resources() - r.valid_until = None - return r - - -class BaseSignedManifest (rpki.x509.SignedManifest, CommonCMS): pass -class BaseROA (rpki.x509.ROA, CommonCMS): pass -class BaseGhostbuster (rpki.x509.Ghostbuster, CommonCMS): pass - -class IncomingX509 (BaseX509, IncomingObject): pass -class IncomingCRL (BaseCRL, IncomingObject): pass -class IncomingSignedManifest (BaseSignedManifest, IncomingObject): pass -class IncomingROA (BaseROA, IncomingObject): pass -class IncomingGhostbuster (BaseGhostbuster, IncomingObject): pass - -class OutgoingX509 (BaseX509, OutgoingObject): pass -class OutgoingCRL (BaseCRL, OutgoingObject): pass -class OutgoingSignedManifest (BaseSignedManifest, OutgoingObject): pass -class OutgoingROA (BaseROA, OutgoingObject): pass -class OutgoingGhostbuster (BaseGhostbuster, OutgoingObject): pass - -IncomingObject.setfn2map(cer = IncomingX509, - crl = IncomingCRL, - mft = IncomingSignedManifest, - roa = IncomingROA, - gbr = IncomingGhostbuster) - -OutgoingObject.setfn2map(cer = OutgoingX509, - crl = OutgoingCRL, - mft = OutgoingSignedManifest, - roa = OutgoingROA, - gbr = OutgoingGhostbuster) - - -class RPDB(object): - """ - Relying party database. - """ - - def __init__(self, db_name): - - try: - os.unlink(db_name) - except: - pass - - self.db = sqlite3.connect(db_name, detect_types = sqlite3.PARSE_DECLTYPES) - self.db.text_factory = str - self.cur = self.db.cursor() - - self.incoming_cache = weakref.WeakValueDictionary() - self.outgoing_cache = weakref.WeakValueDictionary() - - self.cur.executescript(''' - PRAGMA foreign_keys = on; - - CREATE TABLE keyname ( - id INTEGER PRIMARY KEY NOT NULL, - name TEXT NOT NULL, - keyid BLOB NOT NULL, - UNIQUE (name, keyid)); - - CREATE TABLE incoming ( - id INTEGER PRIMARY KEY NOT NULL, - der BLOB NOT NULL, - fn2 TEXT NOT NULL - CHECK (fn2 IN ('cer', 'crl', 'mft', 'roa', 'gbr')), - uri TEXT NOT NULL, - depth INTEGER, - is_ca BOOLEAN NOT NULL DEFAULT 0, - disposition TEXT NOT NULL - DEFAULT 'keep' - CHECK (disposition IN ('keep', 'delete', 'replace')), - subject INTEGER - REFERENCES keyname(id) - ON DELETE RESTRICT - ON UPDATE RESTRICT, - issuer INTEGER NOT NULL - REFERENCES keyname(id) - ON DELETE RESTRICT - ON UPDATE RESTRICT, - replacement INTEGER - REFERENCES outgoing(id) - ON DELETE SET NULL - ON UPDATE SET NULL, - UNIQUE (der), - UNIQUE (subject, issuer), - CHECK ((subject IS NULL) == (fn2 == 'crl'))); - - CREATE TABLE outgoing ( - id INTEGER PRIMARY KEY NOT NULL, - der BLOB, - key BLOB, - fn2 TEXT NOT NULL - CHECK (fn2 IN ('cer', 'crl', 'mft', 'roa', 'gbr')), - uri TEXT NOT NULL, - subject INTEGER - REFERENCES keyname(id) - ON DELETE RESTRICT - ON UPDATE RESTRICT, - issuer INTEGER NOT NULL - REFERENCES keyname(id) - ON DELETE RESTRICT - ON UPDATE RESTRICT, - UNIQUE (subject, issuer), - CHECK ((key IS NULL) == (fn2 == 'crl')), - CHECK ((subject IS NULL) == (fn2 == 'crl'))); - - CREATE TABLE range ( - id INTEGER NOT NULL - REFERENCES incoming(id) - ON DELETE CASCADE - ON UPDATE CASCADE, - min RangeVal NOT NULL, - max RangeVal NOT NULL, - UNIQUE (id, min, max)); - - ''') - - - def load(self, rcynic_input, spinner = 100): - - start = rpki.sundial.now() - nobj = 0 - - for root, dirs, files in os.walk(rcynic_input): - for fn in files: - fn = os.path.join(root, fn) - - try: - obj = IncomingObject.fromFile(fn) - except: - if spinner: - sys.stderr.write("\r") - sys.stderr.write("Couldn't read %s, skipping\n" % fn) - continue - - if spinner and nobj % spinner == 0: - sys.stderr.write("\r%s %d %s..." % ("|\\-/"[(nobj/spinner) & 3], nobj, rpki.sundial.now() - start)) - - nobj += 1 - - if obj.fn2 == "crl": - ski = None - aki = buffer(obj.get_AKI()) - cer = None - bag = None - issuer = obj.getIssuer() - subject = None - is_ca = False - - else: - if obj.fn2 == "cer": - cer = obj - else: - cer = rpki.x509.X509(POW = obj.get_POW().certs()[0]) - issuer = cer.getIssuer() - subject = cer.getSubject() - ski = buffer(cer.get_SKI()) - aki = cer.get_AKI() - if aki is None: - assert subject == issuer - aki = ski - else: - aki = buffer(aki) - bag = cer.get_3779resources() - is_ca = cer.is_CA() - - der = buffer(obj.get_DER()) - uri = "rsync://" + fn[len(rcynic_input) + 1:] - - self.cur.execute("SELECT id FROM incoming WHERE der = ?", (der,)) - r = self.cur.fetchone() - - if r is not None: - rowid = r[0] - - else: - subject_id = None if ski is None else self.find_keyname(subject, ski) - issuer_id = self.find_keyname(issuer, aki) - - self.cur.execute("INSERT INTO incoming (der, fn2, subject, issuer, uri, is_ca) " - "VALUES (?, ?, ?, ?, ?, ?)", - (der, obj.fn2, subject_id, issuer_id, uri, is_ca)) - rowid = self.cur.lastrowid - - if bag is not None: - for rset in (bag.asn, bag.v4, bag.v6): - if rset is not None: - self.cur.executemany("REPLACE INTO range (id, min, max) VALUES (?, ?, ?)", - ((rowid, i.min, i.max) for i in rset)) - - if spinner: - sys.stderr.write("\r= %d objects in %s.\n" % (nobj, rpki.sundial.now() - start)) - - self.cur.execute("UPDATE incoming SET depth = 0 WHERE subject = issuer") - - for depth in xrange(1, 500): - - self.cur.execute("SELECT COUNT(*) FROM incoming WHERE depth IS NULL") - if self.cur.fetchone()[0] == 0: - break - - if spinner: - sys.stderr.write("\rSetting depth %d..." % depth) - - self.cur.execute(""" - UPDATE incoming SET depth = ? - WHERE depth IS NULL - AND issuer IN (SELECT subject FROM incoming WHERE depth = ?) - """, - (depth, depth - 1)) - - else: - if spinner: - sys.stderr.write("\rSetting depth %d is absurd, giving up, " % depth) - - if spinner: - sys.stderr.write("\nCommitting...") - - self.db.commit() - - if spinner: - sys.stderr.write("done.\n") - - - def add_para(self, obj, resources, serial, ltacer, ltasia, ltaaia, ltamft, ltacrl, ltakey): - - assert isinstance(obj, IncomingX509) - - if obj.para_obj is not None: - resources &= obj.para_obj.resources - - obj.para_obj = None - - if not resources: - return - - pow = obj.get_POW() - - x = rpki.POW.X509() - - x.setVersion( pow.getVersion()) - x.setSubject( pow.getSubject()) - x.setNotBefore( pow.getNotBefore()) - x.setNotAfter( pow.getNotAfter()) - x.setPublicKey( pow.getPublicKey()) - x.setSKI( pow.getSKI()) - x.setBasicConstraints( pow.getBasicConstraints()) - x.setKeyUsage( pow.getKeyUsage()) - x.setCertificatePolicies( pow.getCertificatePolicies()) - x.setSIA( *pow.getSIA()) - - x.setIssuer( ltacer.get_POW().getIssuer()) - x.setAKI( ltacer.get_POW().getSKI()) - x.setAIA( (ltaaia,)) - x.setCRLDP( (ltacrl,)) - - x.setSerial( serial()) - x.setRFC3779( - asn = ((r.min, r.max) for r in resources.asn), - ipv4 = ((r.min, r.max) for r in resources.v4), - ipv6 = ((r.min, r.max) for r in resources.v6)) - - x.sign(ltakey.get_POW(), rpki.POW.SHA256_DIGEST) - cer = OutgoingX509(POW = x) - - ski = buffer(cer.get_SKI()) - aki = buffer(cer.get_AKI()) - bag = cer.get_3779resources() - issuer = cer.getIssuer() - subject = cer.getSubject() - der = buffer(cer.get_DER()) - uri = ltasia + cer.gSKI() + ".cer" - - # This will want to change when we start generating replacement keys for everything. - # This should really be a keypair, not just a public key, same comment. - # - key = buffer(pow.getPublicKey().derWritePublic()) - - subject_id = self.find_keyname(subject, ski) - issuer_id = self.find_keyname(issuer, aki) - - self.cur.execute("INSERT INTO outgoing (der, fn2, subject, issuer, uri, key) " - "VALUES (?, 'cer', ?, ?, ?, ?)", - (der, subject_id, issuer_id, uri, key)) - rowid = self.cur.lastrowid - self.cur.execute("UPDATE incoming SET replacement = ? WHERE id = ?", - (rowid, obj.rowid)) - - # Fix up _orig_id and _para_id here? Maybe later. - - #self.db.commit() - - - def dump_paras(self, rcynic_output): - shutil.rmtree(rcynic_output, ignore_errors = True) - rsync = "rsync://" - for der, uri in self.cur.execute("SELECT der, uri FROM outgoing"): - assert uri.startswith(rsync) - fn = os.path.join(rcynic_output, uri[len(rsync):]) - dn = os.path.dirname(fn) - if not os.path.exists(dn): - os.makedirs(dn) - with open(fn, "wb") as f: - #print ">> Writing", f.name - f.write(der) - - - def find_keyname(self, name, keyid): - keys = (name, buffer(keyid)) - self.cur.execute("SELECT id FROM keyname WHERE name = ? AND keyid = ?", keys) - result = self.cur.fetchone() - if result is None: - self.cur.execute("INSERT INTO keyname (name, keyid) VALUES (?, ?)", keys) - result = self.cur.lastrowid - else: - result = result[0] - return result - - - def find_keyname_by_id(self, rowid): - self.cur.execute("SELECT name, keyid FROM keyname WHERE id = ?", (rowid,)) - result = self.cur.fetchone() - return (None, None) if result is None else result - - - def find_incoming_by_id(self, rowid): - if rowid is None: - return None - if rowid in self.incoming_cache: - return self.incoming_cache[rowid] - r = self._find_results(None, "WHERE id = ?", [rowid]) - assert len(r) < 2 - return r[0] if r else None - - - def find_outgoing_by_id(self, rowid): - if rowid is None: - return None - if rowid in self.outgoing_cache: - return self.outgoing_cache[rowid] - self.cur.execute("SELECT fn2, der, key, uri, subject, issuer FROM outgoing WHERE id = ?", (rowid,)) - r = self.cur.fetchone() - if r is None: - return None - fn2, der, key, uri, subject_id, issuer_id = r - obj = OutgoingObject.create(rpdb = self, rowid = rowid, fn2 = fn2, der = der, uri = uri, - subject_id = subject_id, issuer_id = issuer_id) - self.outgoing_cache[rowid] = obj - return obj - - - def find_by_ski_or_uri(self, ski, uri): - if not ski and not uri: - return [] - j = "" - w = [] - a = [] - if ski: - j = "JOIN keyname ON incoming.subject = keyname.id" - w.append("keyname.keyid = ?") - a.append(buffer(ski)) - if uri: - w.append("incoming.uri = ?") - a.append(uri) - return self._find_results(None, "%s WHERE %s" % (j, " AND ".join(w)), a) - - - # It's easiest to understand overlap conditions by understanding - # non-overlap then inverting and and applying De Morgan's law. - # Ranges A and B do not overlap if: A.min > B.max or B.min > A.max; - # therefore A and B do overlap if: A.min <= B.max and B.min <= A.max. - - def find_by_range(self, range_min, range_max = None, fn2 = None): - if range_max is None: - range_max = range_min - if isinstance(range_min, (str, unicode)): - range_min = long(range_min) if range_min.isdigit() else rpki.POW.IPAddress(range_min) - if isinstance(range_max, (str, unicode)): - range_max = long(range_max) if range_max.isdigit() else rpki.POW.IPAddress(range_max) - assert isinstance(range_min, (int, long, rpki.POW.IPAddress)) - assert isinstance(range_max, (int, long, rpki.POW.IPAddress)) - return self._find_results(fn2, - "JOIN range ON incoming.id = range.id " - "WHERE ? <= range.max AND ? >= range.min", - [range_min, range_max]) - - - def find_by_resource_bag(self, bag, fn2 = None): - assert bag.asn or bag.v4 or bag.v6 - qset = [] - aset = [] - for rset in (bag.asn, bag.v4, bag.v6): - if rset: - for r in rset: - qset.append("(? <= max AND ? >= min)") - aset.append(r.min) - aset.append(r.max) - return self._find_results( - fn2, - """ - JOIN range ON incoming.id = range.id - WHERE - """ + (" OR ".join(qset)), - aset) - - - def _find_results(self, fn2, query, args = None): - if args is None: - args = [] - if fn2 is not None: - query += " AND fn2 = ?" - args.append(fn2) - results = [] - for rowid, fn2, der, uri, subject_id, issuer_id, depth, is_ca in self.cur.execute( - ''' - SELECT DISTINCT - incoming.id, incoming.fn2, - incoming.der, incoming.uri, - incoming.subject, incoming.issuer, - incoming.depth, incoming.is_ca - FROM incoming - ''' + query, args): - if rowid in self.incoming_cache: - obj = self.incoming_cache[rowid] - assert obj.rowid == rowid - else: - obj = IncomingObject.create(rpdb = self, rowid = rowid, fn2 = fn2, der = der, uri = uri, - subject_id = subject_id, issuer_id = issuer_id, depth = depth, - is_ca = is_ca) - self.incoming_cache[rowid] = obj - results.append(obj) - return results - - - def commit(self): - self.db.commit() - - - def close(self): - self.commit() - self.cur.close() - self.db.close() - -if __name__ == "__main__": - #profile = None - profile = "rcynic-lta.prof" - if profile: - import cProfile - prof = cProfile.Profile() - try: - prof.runcall(main) - finally: - prof.dump_stats(profile) - sys.stderr.write("Dumped profile data to %s\n" % profile) - else: - main() - |