diff options
Diffstat (limited to 'scripts')
-rwxr-xr-x | scripts/rcynic-lta | 441 |
1 files changed, 225 insertions, 216 deletions
diff --git a/scripts/rcynic-lta b/scripts/rcynic-lta index d88bbf8a..fc7290a6 100755 --- a/scripts/rcynic-lta +++ b/scripts/rcynic-lta @@ -48,106 +48,238 @@ import rpki.x509 import rpki.sundial import rpki.resource_set -# Lots of icky global variables, clean this up later. +# Teach SQLite3 about our data types. -tal_directory = None -constraints = None -rcynic_input = None -rcynic_output = None -tals = None -keyfile = None +sqlite3.register_adapter(rpki.POW.IPAddress, + lambda x: buffer("_" + x.toBytes())) -serial = long(time.time()) << 32 +sqlite3.register_converter("RangeVal", + lambda s: long(s) if s.isdigit() else rpki.POW.IPAddress.fromBytes(s[1:])) -ltakey = None -ltacer = None +sqlite3.register_adapter(rpki.x509.X501DN, str) -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) +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() + print + print "Creating CA" + self.create_ca() + print + print "Loading DB" + self.rpdb.load(self.rcynic_input) + print + print "Compute resources we need to prune from input forest" + self.compute_all_mentioned_resources() + 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() + if os.path.exists(self.keyfile): + self.ltakey = rpki.x509.RSA(Auto_file = self.keyfile) + else: + self.ltakey = rpki.x509.RSA.generate(quiet = True) + with os.fdopen(os.open(keyfile, os.O_WRONLY | os.O_CREAT | os.O_TRUNC, 0400), "w") as f: + f.write(self.ltakey.get_PEM()) + cer = X509.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) " + "VALUES (?, 'cer', ?, ?, ?)", + (buffer(cer.get_DER()), subject_id, subject_id, self.ltaaia)) + 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.tal_directory = y["tal-directory"] + self.rcynic_input = y["rcynic-input"] + self.rcynic_output = y["rcynic-output"] + self.keyfile = y["keyfile"] + self.constraints = [Constraint(yy) for yy 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 + + + def compute_all_mentioned_resources(self): + for constraint in self.constraints: + self.all_mentioned_resources |= constraint.mentioned_resources + + + 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) -all_mentioned_resources = rpki.resource_set.resource_bag() -# Teach SQLite3 about our data types. + 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 = CRL.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. + # + self.rpdb.cur.execute("SELECT fn2, der, uri FROM outgoing WHERE issuer = ?", (self.ltacer.rowid,)) + names_and_objs = [(uri, self.rpdb.fn2map[fn2](DER = der)) for fn2, der, uri in self.rpdb.cur.fetchall()] + + mft = rpki.x509.SignedManifest.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) " + "VALUES (?, 'mft', ?, ?, ?)", + (buffer(mft.get_DER()), subject_id, issuer_id, self.ltamft)) + + + @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 -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 Serial(object): + def __init__(self): + self.value = long(time.time()) << 32 + + def __call__(self): + self.value += 1 + return self.value -def main(): - print "Parsing YAML" - parse_yaml() - print - print "Parsing TALs" - parse_tals() - print - print "Creating DB" - rpdb = RPDB() - print - print "Creating CA" - create_ca(rpdb) - print - print "Loading DB" - rpdb.load() - - print - print "Compute resources we need to prune from input forest" - compute_all_mentioned_resources() - - print - print "Processing deletions" - process_constraint_deletions(rpdb) - - print - print "Re-parenting TAs" - re_parent_tas(rpdb) - print - print "Generating CRL and manifest" - generate_crl_and_manifest(rpdb) - print - print "Committing final changes to DB" - rpdb.commit() - print - print "Dumping para-objects" - rpdb.dump_paras() - print - print "Closing DB" - rpdb.close() - - -def create_ca(rpdb): - global serial - global ltakey - global ltacer - if os.path.exists(keyfile): - ltakey = rpki.x509.RSA(Auto_file = keyfile) - else: - ltakey = rpki.x509.RSA.generate(quiet = True) - with os.fdopen(os.open(keyfile, os.O_WRONLY | os.O_CREAT | os.O_TRUNC, 0400), "w") as f: - f.write(ltakey.get_PEM()) - cer = X509.self_certify( - cn = "%s LTA Root Certificate" % socket.getfqdn(), - keypair = ltakey, - subject_key = ltakey.get_RSApublic(), - serial = serial, - sia = (ltasia, ltamft, None), - notAfter = rpki.sundial.now() + cer_delta, - resources = rpki.resource_set.resource_bag.from_str("0-4294967295,0.0.0.0/0,::/0")) - subject_id = rpdb.find_keyname(cer.getSubject(), cer.get_SKI()) - rpdb.cur.execute("INSERT INTO outgoing (der, fn2, subject, issuer, uri) " - "VALUES (?, 'cer', ?, ?, ?)", - (buffer(cer.get_DER()), subject_id, subject_id, ltaaia)) - ltacer = rpdb.find_outgoing_by_id(rpdb.cur.lastrowid) class Constraint(object): @@ -176,104 +308,6 @@ class Constraint(object): return self.prefixes | self.asns -def parse_yaml(fn = "rcynic-lta.yaml"): - global tal_directory - global constraints - global rcynic_input - global rcynic_output - global keyfile - y = yaml.safe_load(open(fn, "r")) - tal_directory = y["tal-directory"] - rcynic_input = y["rcynic-input"] - rcynic_output = y["rcynic-output"] - keyfile = y["keyfile"] - constraints = [Constraint(yy) for yy in y["constraints"]] - - -def parse_tals(): - global tals - tals = {} - for fn in glob.iglob(os.path.join(tal_directory, "*.tal")): - with open(fn, "r") as f: - uri = f.readline().strip() - key = rpki.POW.Asymmetric.derReadPublic(base64.b64decode(f.read())) - tals[uri] = key - - -def compute_all_mentioned_resources(): - global all_mentioned_resources - for constraint in constraints: - all_mentioned_resources |= constraint.mentioned_resources - - -def process_constraint_deletions(rpdb): - for obj in rpdb.find_by_resource_bag(all_mentioned_resources): - rpdb.add_para(obj, obj.resources - all_mentioned_resources) - - -def re_parent_tas(rpdb): - for uri, key in tals.iteritems(): - for ta in rpdb.find_by_ski_or_uri(key.calculateSKI(), uri): - if ta.para_obj is None: - rpdb.add_para(ta, ta.resources - all_mentioned_resources) - - -def generate_crl_and_manifest(rpdb): - thisUpdate = rpki.sundial.now() - nextUpdate = thisUpdate + crl_delta - serial = long(time.time()) - issuer = ltacer.getSubject() - aki = buffer(ltacer.get_SKI()) - - crl = CRL.generate( - keypair = ltakey, - issuer = ltacer, - serial = serial, - thisUpdate = thisUpdate, - nextUpdate = nextUpdate, - revokedCertificates = ()) - - issuer_id = rpdb.find_keyname(issuer, aki) - - rpdb.cur.execute("INSERT INTO outgoing (der, fn2, subject, issuer, uri) " - "VALUES (?, 'crl', NULL, ?, ?)", - (buffer(crl.get_DER()), issuer_id, ltacrl)) - crl = rpdb.find_outgoing_by_id(rpdb.cur.lastrowid) - - key = rpki.x509.RSA.generate(quiet = True) - - cer = ltacer.issue( - keypair = ltakey, - subject_key = key.get_RSApublic(), - serial = serial, - sia = (None, None, ltamft), - aia = ltaaia, - crldp = ltacrl, - resources = rpki.resource_set.resource_bag.from_inheritance(), - notAfter = 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. - # - rpdb.cur.execute("SELECT fn2, der, uri FROM outgoing WHERE issuer = ?", (ltacer.rowid,)) - names_and_objs = [(uri, rpdb.fn2map[fn2](DER = der)) for fn2, der, uri in rpdb.cur.fetchall()] - - mft = rpki.x509.SignedManifest.build( - serial = serial, - thisUpdate = thisUpdate, - nextUpdate = nextUpdate, - names_and_objs = names_and_objs, - keypair = key, - certs = cer) - - subject_id = rpdb.find_keyname(cer.getSubject(), cer.get_SKI()) - - rpdb.cur.execute("INSERT INTO outgoing (der, fn2, subject, issuer, uri) " - "VALUES (?, 'mft', ?, ?, ?)", - (buffer(mft.get_DER()), subject_id, issuer_id, ltamft)) - - class DER_object_mixin(object): """ Mixin to add some SQL-related methods to classes derived from @@ -453,7 +487,7 @@ class RPDB(object): ''') - def load(self, spinner = 100): + def load(self, rcynic_input, spinner = 100): start = rpki.sundial.now() nobj = 0 @@ -557,7 +591,7 @@ class RPDB(object): sys.stderr.write("done.\n") - def add_para(self, obj, resources): + def add_para(self, obj, resources, serial, ltacer, ltasia, ltaaia, ltamft, ltacrl, ltakey): # At least some of the following is probably wrong at this point. # Under the new scheme we're going to need to generate signed @@ -590,9 +624,6 @@ class RPDB(object): if not resources: return - global serial - serial += 1 - pow = obj.get_POW() x = rpki.POW.X509() @@ -613,7 +644,7 @@ class RPDB(object): x.setAIA( (ltaaia,)) x.setCRLDP( (ltacrl,)) - x.setSerial( serial) + 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), @@ -645,7 +676,7 @@ class RPDB(object): #self.db.commit() - def dump_paras(self): + def dump_paras(self, rcynic_output): shutil.rmtree(rcynic_output, ignore_errors = True) rsync = "rsync://" self.cur.execute("SELECT der, uri FROM outgoing") @@ -656,7 +687,7 @@ class RPDB(object): if not os.path.exists(dn): os.makedirs(dn) with open(fn, "wb") as f: - print ">> Writing", f.name + #print ">> Writing", f.name f.write(der) @@ -806,28 +837,6 @@ class RPDB(object): self.cur.close() self.db.close() - -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 - - if __name__ == "__main__": #profile = None profile = "rcynic-lta.prof" |