diff options
-rw-r--r-- | rpkid/rpki/exceptions.py | 5 | ||||
-rw-r--r-- | rpkid/rpki/http.py | 2 | ||||
-rw-r--r-- | rpkid/rpki/irdbd.py | 8 | ||||
-rw-r--r-- | rpkid/rpki/left_right.py | 31 | ||||
-rw-r--r-- | rpkid/rpki/pubd.py | 8 | ||||
-rw-r--r-- | rpkid/rpki/publication.py | 1 | ||||
-rw-r--r-- | rpkid/rpki/rootd.py | 5 | ||||
-rw-r--r-- | rpkid/rpki/rpkid.py | 16 | ||||
-rw-r--r-- | rpkid/rpki/up_down.py | 2 | ||||
-rw-r--r-- | rpkid/rpki/x509.py | 21 | ||||
-rw-r--r-- | rpkid/tests/smoketest.py | 23 | ||||
-rw-r--r-- | rpkid/tests/testpoke.py | 4 |
12 files changed, 100 insertions, 26 deletions
diff --git a/rpkid/rpki/exceptions.py b/rpkid/rpki/exceptions.py index 21410380..68ea3bf6 100644 --- a/rpkid/rpki/exceptions.py +++ b/rpkid/rpki/exceptions.py @@ -346,3 +346,8 @@ class ResourceOverlap(RPKI_Exception): """ Overlapping resources in resource_set. """ + +class CMSReplay(RPKI_Exception): + """ + Possible CMS replay attack detected. + """ diff --git a/rpkid/rpki/http.py b/rpkid/rpki/http.py index a0055ac9..244a9305 100644 --- a/rpkid/rpki/http.py +++ b/rpkid/rpki/http.py @@ -1033,6 +1033,7 @@ class caller(object): self.server_ta = server_ta self.server_cert = server_cert self.url = url + self.cms_timestamp = None if debug is not None: self.debug = debug @@ -1044,6 +1045,7 @@ class caller(object): """ r_cms = self.proto.cms_msg(DER = r_der) r_msg = r_cms.unwrap((self.server_ta, self.server_cert)) + self.cms_timestamp = r_cms.check_replay(self.cms_timestamp) if self.debug: print "<!-- Reply -->" print r_cms.pretty_print_content() diff --git a/rpkid/rpki/irdbd.py b/rpkid/rpki/irdbd.py index 166b160a..ae250085 100644 --- a/rpkid/rpki/irdbd.py +++ b/rpkid/rpki/irdbd.py @@ -104,7 +104,9 @@ class main(object): serverCA = rpki.irdb.ServerCA.objects.get() rpkid = serverCA.ee_certificates.get(purpose = "rpkid") try: - q_msg = rpki.left_right.cms_msg(DER = query).unwrap((serverCA.certificate, rpkid.certificate)) + q_cms = rpki.left_right.cms_msg(DER = query) + q_msg = q_cms.unwrap((serverCA.certificate, rpkid.certificate)) + self.cms_timestamp = q_cms.check_replay(self.cms_timestamp) if not isinstance(q_msg, rpki.left_right.msg) or not q_msg.is_query(): raise rpki.exceptions.BadQuery("Unexpected %r PDU" % q_msg) for q_pdu in q_msg: @@ -143,7 +145,7 @@ class main(object): cfg_file = None foreground = False - + opts, argv = getopt.getopt(sys.argv[1:], "c:dfh?", ["config=", "debug", "foreground", "help"]) for o, a in opts: if o in ("-h", "--help", "-?"): @@ -230,6 +232,8 @@ class main(object): u.query == "" and \ u.fragment == "" + self.cms_timestamp = None + rpki.http.server( host = u.hostname or "localhost", port = u.port or 443, diff --git a/rpkid/rpki/left_right.py b/rpkid/rpki/left_right.py index 17d665c9..4c8c6cd0 100644 --- a/rpkid/rpki/left_right.py +++ b/rpkid/rpki/left_right.py @@ -791,6 +791,7 @@ class repository_elt(data_elt): bpki_cert = None bpki_glue = None + last_cms_timestamp = None @property def parents(self): @@ -840,7 +841,9 @@ class repository_elt(data_elt): def done(r_der): try: - r_msg = rpki.publication.cms_msg(DER = r_der).unwrap(bpki_ta_path) + r_cms = rpki.publication.cms_msg(DER = r_der) + r_msg = r_cms.unwrap(bpki_ta_path) + r_cms.check_replay_sql(self) for r_pdu in r_msg: handler = handlers.get(r_pdu.tag, self.default_pubd_handler) if handler: @@ -887,6 +890,7 @@ class parent_elt(data_elt): bpki_cms_cert = None bpki_cms_glue = None + last_cms_timestamp = None @property def repository(self): @@ -1066,11 +1070,13 @@ class parent_elt(data_elt): def unwrap(r_der): try: - r_msg = rpki.up_down.cms_msg(DER = r_der).unwrap((self.gctx.bpki_ta, - self.self.bpki_cert, - self.self.bpki_glue, - self.bpki_cms_cert, - self.bpki_cms_glue)) + r_cms = rpki.up_down.cms_msg(DER = r_der) + r_msg = r_cms.unwrap((self.gctx.bpki_ta, + self.self.bpki_cert, + self.self.bpki_glue, + self.bpki_cms_cert, + self.bpki_cms_glue)) + r_cms.check_replay_sql(self) r_msg.payload.check_response() except (SystemExit, rpki.async.ExitNow): raise @@ -1105,6 +1111,7 @@ class child_elt(data_elt): bpki_cert = None bpki_glue = None + last_cms_timestamp = None def fetch_child_certs(self, ca_detail = None, ski = None, unique = False): """ @@ -1178,11 +1185,13 @@ class child_elt(data_elt): bsc = self.bsc if bsc is None: raise rpki.exceptions.BSCNotFound, "Could not find BSC %s" % self.bsc_id - q_msg = rpki.up_down.cms_msg(DER = query).unwrap((self.gctx.bpki_ta, - self.self.bpki_cert, - self.self.bpki_glue, - self.bpki_cert, - self.bpki_glue)) + q_cms = rpki.up_down.cms_msg(DER = query) + q_msg = q_cms.unwrap((self.gctx.bpki_ta, + self.self.bpki_cert, + self.self.bpki_glue, + self.bpki_cert, + self.bpki_glue)) + q_cms.check_replay_sql(self) q_msg.payload.gctx = self.gctx if enforce_strict_up_down_xml_sender and q_msg.sender != str(self.child_id): raise rpki.exceptions.BadSender, "Unexpected XML sender %s" % q_msg.sender diff --git a/rpkid/rpki/pubd.py b/rpkid/rpki/pubd.py index 555a4d6e..0bf811db 100644 --- a/rpkid/rpki/pubd.py +++ b/rpkid/rpki/pubd.py @@ -68,6 +68,7 @@ class main(object): self.cfg_file = None self.profile = False self.foreground = False + self.irbe_cms_timestamp = None opts, argv = getopt.getopt(sys.argv[1:], "c:dfhp:?", ["config=", "debug", "foreground", "help", "profile="]) @@ -136,7 +137,12 @@ class main(object): self.sql.sweep() cb(reply) - q_msg = rpki.publication.cms_msg(DER = query).unwrap(certs) + q_cms = rpki.publication.cms_msg(DER = query) + q_msg = q_cms.unwrap(certs) + if client is None: + self.irbe_cms_timestamp = q_cms.check_replay(self.irbe_cms_timestamp) + else: + q_cms.check_replay_sql(client) q_msg.serve_top_level(self, client, done) def control_handler(self, query, path, cb): diff --git a/rpkid/rpki/publication.py b/rpkid/rpki/publication.py index 920f925d..7cdb3167 100644 --- a/rpkid/rpki/publication.py +++ b/rpkid/rpki/publication.py @@ -127,6 +127,7 @@ class client_elt(control_elt): base_uri = None bpki_cert = None bpki_glue = None + last_cms_timestamp = None def serve_fetch_one_maybe(self): """ diff --git a/rpkid/rpki/rootd.py b/rpkid/rpki/rootd.py index 26b5db11..75257a80 100644 --- a/rpkid/rpki/rootd.py +++ b/rpkid/rpki/rootd.py @@ -264,7 +264,9 @@ class main(object): def up_down_handler(self, query, path, cb): try: - q_msg = cms_msg(DER = query).unwrap((self.bpki_ta, self.child_bpki_cert)) + q_cms = cms_msg(DER = query) + q_msg = q_cms.unwrap((self.bpki_ta, self.child_bpki_cert)) + self.cms_timestamp = q_cms.check_replay(self.cms_timestamp) except (rpki.async.ExitNow, SystemExit): raise except Exception, e: @@ -323,6 +325,7 @@ class main(object): self.crl_number = None self.revoked = [] self.foreground = False + self.cms_timestamp = None os.environ["TZ"] = "UTC" time.tzset() diff --git a/rpkid/rpki/rpkid.py b/rpkid/rpki/rpkid.py index 0fbf4093..051e7c17 100644 --- a/rpkid/rpki/rpkid.py +++ b/rpkid/rpki/rpkid.py @@ -71,6 +71,8 @@ class main(object): self.cfg_file = None self.profile = None self.foreground = False + self.irdbd_cms_timestamp = None + self.irbe_cms_timestamp = None opts, argv = getopt.getopt(sys.argv[1:], "c:dfhp:?", ["config=", "debug", "foreground", "help", "profile="]) @@ -182,12 +184,16 @@ class main(object): def unwrap(r_der): r_cms = rpki.left_right.cms_msg(DER = r_der) r_msg = r_cms.unwrap((self.bpki_ta, self.irdb_cert)) + self.irdbd_cms_timestamp = r_cms.check_replay(self.irdbd_cms_timestamp) if not r_msg.is_reply() or not all(type(r_pdu) in q_types for r_pdu in r_msg): - raise rpki.exceptions.BadIRDBReply, "Unexpected response to IRDB query: %s" % r_cms.pretty_print_content() + raise rpki.exceptions.BadIRDBReply( + "Unexpected response to IRDB query: %s" % r_cms.pretty_print_content()) if expected_pdu_count is not None and len(r_msg) != expected_pdu_count: assert isinstance(expected_pdu_count, (int, long)) - raise rpki.exceptions.BadIRDBReply, "Expected exactly %d PDU%s from IRDB: %s" % ( - expected_pdu_count, "" if expected_pdu_count == 1 else "s", r_cms.pretty_print_content()) + raise rpki.exceptions.BadIRDBReply( + "Expected exactly %d PDU%s from IRDB: %s" % ( + expected_pdu_count, "" if expected_pdu_count == 1 else "s", + r_cms.pretty_print_content())) callback(r_msg) rpki.http.client( @@ -259,7 +265,9 @@ class main(object): try: self.sql.ping() - q_msg = rpki.left_right.cms_msg(DER = query).unwrap((self.bpki_ta, self.irbe_cert)) + q_cms = rpki.left_right.cms_msg(DER = query) + q_msg = q_cms.unwrap((self.bpki_ta, self.irbe_cert)) + self.irbe_cms_timestamp = q_cms.check_replay(self.irbe_cms_timestamp) if not q_msg.is_query(): raise rpki.exceptions.BadQuery, "Message type is not query" q_msg.serve_top_level(self, done) diff --git a/rpkid/rpki/up_down.py b/rpkid/rpki/up_down.py index 4fd06edd..1562e8e8 100644 --- a/rpkid/rpki/up_down.py +++ b/rpkid/rpki/up_down.py @@ -252,7 +252,7 @@ class list_pdu(base_elt): r_msg.payload = list_response_pdu() if irdb_resources.valid_until < rpki.sundial.now(): - rpki.log.debug("Child %s's resources expired %s" % child.child_handle, irdb_resources.valid_until) + rpki.log.debug("Child %s's resources expired %s" % (child.child_handle, irdb_resources.valid_until)) else: for parent in child.parents: for ca in parent.cas: diff --git a/rpkid/rpki/x509.py b/rpkid/rpki/x509.py index 569e017e..42b52f1d 100644 --- a/rpkid/rpki/x509.py +++ b/rpkid/rpki/x509.py @@ -1550,6 +1550,27 @@ class XML_CMS_object(CMS_object): else: return self.saxify(self.get_content()) + def check_replay(self, timestamp): + """ + Check CMS signing-time in this object against a recorded + timestamp. Raises an exception if the recorded timestamp is more + recent, otherwise returns the new timestamp. + """ + new_timestamp = self.get_signingTime() + if timestamp is not None and timestamp > new_timestamp: + raise rpki.exceptions.CMSReplay( + "CMS replay: last message %s, this message %s" % (timestamp, new_timestamp)) + return new_timestamp + + def check_replay_sql(self, obj): + """ + Like .check_replay() but gets recorded timestamp from + "last_cms_timestamp" field of an SQL object and stores the new + timestamp back in that same field. + """ + obj.last_cms_timestamp = self.check_replay(obj.last_cms_timestamp) + obj.sql_mark_dirty() + ## @var saxify # SAX handler hook. Subclasses can set this to a SAX handler, in # which case .unwrap() will call it and return the result. diff --git a/rpkid/tests/smoketest.py b/rpkid/tests/smoketest.py index 6d5da7ea..bb97108b 100644 --- a/rpkid/tests/smoketest.py +++ b/rpkid/tests/smoketest.py @@ -146,6 +146,8 @@ pubd_irbe_key = None pubd_irbe_cert = None pubd_pubd_cert = None +pubd_last_cms_time = None + class CantRekeyYAMLLeaf(Exception): """ Can't rekey YAML leaf. @@ -468,6 +470,7 @@ class allocation(object): rpki_port = None crl_interval = None regen_margin = None + last_cms_time = None def __init__(self, yaml, db, parent = None): """ @@ -821,6 +824,7 @@ class allocation(object): rpki.log.info("Callback from rpkid %s" % self.name) r_cms = rpki.left_right.cms_msg(DER = r_der) r_msg = r_cms.unwrap((self.rpkid_ta, self.rpkid_cert)) + self.last_cms_time = r_cms.check_replay(self.last_cms_time) rpki.log.debug(r_cms.pretty_print_content()) assert r_msg.is_reply for r_pdu in r_msg: @@ -1221,8 +1225,10 @@ def call_pubd(pdus, cb): rpki.log.debug(q_cms.pretty_print_content()) def call_pubd_cb(r_der): + global pubd_last_cms_time r_cms = rpki.publication.cms_msg(DER = r_der) r_msg = r_cms.unwrap((pubd_ta, pubd_pubd_cert)) + pubd_last_cms_time = r_cms.check_replay(pubd_last_cms_time) rpki.log.debug(r_cms.pretty_print_content()) assert r_msg.is_reply for r_pdu in r_msg: @@ -1278,25 +1284,25 @@ def mangle_sql(filename): return " ".join(words).strip(";").split(";") bpki_cert_fmt_1 = '''\ -[ req ] +[req] distinguished_name = req_dn x509_extensions = req_x509_ext prompt = no default_md = sha256 -[ req_dn ] +[req_dn] CN = Test Certificate %(name)s %(kind)s -[ req_x509_ext ] +[req_x509_ext] basicConstraints = critical,CA:%(ca)s subjectKeyIdentifier = hash authorityKeyIdentifier = keyid:always -[ ca ] +[ca] default_ca = ca_default -[ ca_default ] +[ca_default] certificate = %(name)s-%(kind)s.cer serial = %(name)s-%(kind)s.srl @@ -1379,7 +1385,8 @@ bpki-ta = %(my_name)s-TA.cer rpkid-cert = %(my_name)s-RPKI.cer irdbd-cert = %(my_name)s-IRDB.cer irdbd-key = %(my_name)s-IRDB.key -http-url = http://localhost:%(irdb_port)d/ +http-url = http://localhost:%(irdb_port)d/ +enable_tracebacks = yes [irbe_cli] @@ -1388,6 +1395,7 @@ rpkid-cert = %(my_name)s-RPKI.cer rpkid-irbe-cert = %(my_name)s-IRBE.cer rpkid-irbe-key = %(my_name)s-IRBE.key rpkid-url = http://localhost:%(rpki_port)d/left-right +enable_tracebacks = yes [rpkid] @@ -1409,6 +1417,7 @@ server-host = localhost server-port = %(rpki_port)d use-internal-cron = false +enable_tracebacks = yes ''' rootd_fmt_1 = '''\ @@ -1440,6 +1449,7 @@ rpki-class-name = Wombat rpki-subject-cert = Wombat.cer include-bpki-crl = yes +enable_tracebacks = yes [req] default_bits = 2048 @@ -1523,6 +1533,7 @@ irbe-cert = %(pubd_name)s-IRBE.cer server-host = localhost server-port = %(pubd_port)d publication-base = %(pubd_dir)s +enable_tracebacks = yes ''' main() diff --git a/rpkid/tests/testpoke.py b/rpkid/tests/testpoke.py index 8851a821..1f7713a1 100644 --- a/rpkid/tests/testpoke.py +++ b/rpkid/tests/testpoke.py @@ -112,8 +112,10 @@ def query_up_down(q_pdu): q_der = rpki.up_down.cms_msg().wrap(q_msg, cms_key, cms_certs, cms_crl) def done(r_der): + global last_cms_timestamp r_cms = rpki.up_down.cms_msg(DER = r_der) r_msg = r_cms.unwrap([cms_ta] + cms_ca_certs) + last_cms_timestamp = r_cms.check_replay(last_cms_timestamp) print r_cms.pretty_print_content() try: r_msg.payload.check_response() @@ -161,6 +163,8 @@ cms_crl = get_PEM("cms-crl", rpki.x509.CRL) cms_certs = get_PEM_chain("cms-cert-chain", cms_cert) cms_ca_certs = get_PEM_chain("cms-ca-certs") +last_cms_timestamp = None + try: dispatch[yaml_req["type"]]() rpki.async.event_loop() |