aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--rpkid/rpki/exceptions.py5
-rw-r--r--rpkid/rpki/http.py2
-rw-r--r--rpkid/rpki/irdbd.py8
-rw-r--r--rpkid/rpki/left_right.py31
-rw-r--r--rpkid/rpki/pubd.py8
-rw-r--r--rpkid/rpki/publication.py1
-rw-r--r--rpkid/rpki/rootd.py5
-rw-r--r--rpkid/rpki/rpkid.py16
-rw-r--r--rpkid/rpki/up_down.py2
-rw-r--r--rpkid/rpki/x509.py21
-rw-r--r--rpkid/tests/smoketest.py23
-rw-r--r--rpkid/tests/testpoke.py4
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()