aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRob Austein <sra@hactrn.net>2013-08-23 20:40:05 +0000
committerRob Austein <sra@hactrn.net>2013-08-23 20:40:05 +0000
commitef66b606d993faa8ab56e979fd59bf18ee820ca6 (patch)
treed2f9d845663f326d84695ab8c0930bd6717f0970
parent2c9857c2c5ffa7686748b0e37a85635a8b13e290 (diff)
Rip out PEM_converter class, as we no longer need to support
interoperation between three separate ASN.1 packages and our updated rpki.POW PEM functions use the OpenSSL libraries to handle corner cases that PEM_converter did not, like PKCS8. See #603. Fix log-rate-limiting in Auto_update DER objects to be time-based rather than counter based; reorder checks so that daemons will have some chance of recovering when the user does something strange with required .cer or .key files then repairs the error. See #602. svn path=/trunk/; revision=5462
-rw-r--r--rpkid/rpki/x509.py152
1 files changed, 80 insertions, 72 deletions
diff --git a/rpkid/rpki/x509.py b/rpkid/rpki/x509.py
index a536cc97..3bd0a3cd 100644
--- a/rpkid/rpki/x509.py
+++ b/rpkid/rpki/x509.py
@@ -69,43 +69,13 @@ def base64_with_linebreaks(der):
n = len(b)
return "\n" + "\n".join(b[i : min(i + 64, n)] for i in xrange(0, n, 64)) + "\n"
-class PEM_converter(object):
+def looks_like_PEM(text):
"""
- Convert between DER and PEM encodings for various kinds of ASN.1 data.
+ Guess whether text looks like a PEM encoding.
"""
- def __init__(self, kind): # "CERTIFICATE", "RSA PRIVATE KEY", ...
- """
- Initialize PEM_converter.
- """
- self.b = "-----BEGIN %s-----" % kind
- self.e = "-----END %s-----" % kind
-
- def looks_like_PEM(self, text):
- """
- Guess whether text looks like a PEM encoding.
- """
- b = text.find(self.b)
- return b >= 0 and text.find(self.e) > b + len(self.b)
-
- def to_DER(self, pem):
- """
- Convert from PEM to DER.
- """
- lines = [line.strip() for line in pem.splitlines(0)]
- while lines and lines.pop(0) != self.b:
- pass
- while lines and lines.pop(-1) != self.e:
- pass
- if not lines:
- raise rpki.exceptions.EmptyPEM("Could not find PEM in:\n%s" % pem)
- return base64.b64decode("".join(lines))
-
- def to_PEM(self, der):
- """
- Convert from DER to PEM.
- """
- return self.b + base64_with_linebreaks(der) + self.e + "\n"
+ i = text.find("-----BEGIN ")
+ return i >= 0 and text.find("\n-----END ", i) > i
def first_rsync_uri(xia):
"""
@@ -207,17 +177,28 @@ class DER_object(object):
Virtual class to hold a generic DER object.
"""
- ## Formats supported in this object
- formats = ("DER",)
+ ## @var formats
+ # Formats supported in this object. This is kind of redundant now
+ # that we're down to a single ASN.1 package and everything supports
+ # the same DER and POW formats, it's mostly historical baggage from
+ # the days when we had three different ASN.1 encoders, each with its
+ # own low-level Python object format. Clean up, some day.
+ formats = ("DER", "POW")
- ## PEM converter for this object
- pem_converter = None
+ ## @var POW_class
+ # Class of underlying POW object. Concrete subclasses must supply this.
+ POW_class = None
- ## Other attributes that self.clear() should whack
+ ## Other attributes that self.clear() should whack.
other_clear = ()
## @var DER
- ## DER value of this object
+ # DER value of this object
+ DER = None
+
+ ## @var failure_threshold
+ # Rate-limiting interval between whines about Auto_update objects.
+ failure_threshold = rpki.sundial.timedelta(minutes = 5)
def empty(self):
"""
@@ -233,7 +214,7 @@ class DER_object(object):
setattr(self, a, None)
self.filename = None
self.timestamp = None
- self.failures = 0
+ self.lastfail = None
def __init__(self, **kw):
"""
@@ -261,7 +242,7 @@ class DER_object(object):
return
if name == "PEM":
self.clear()
- self.DER = self.pem_converter.to_DER(kw[name])
+ self._set_PEM(kw[name])
return
if name == "Base64":
self.clear()
@@ -275,10 +256,11 @@ class DER_object(object):
f = open(kw[name], "rb")
value = f.read()
f.close()
- if name == "PEM_file" or (name == "Auto_file" and self.pem_converter.looks_like_PEM(value)):
- value = self.pem_converter.to_DER(value)
self.clear()
- self.DER = value
+ if name == "PEM_file" or (name == "Auto_file" and looks_like_PEM(value)):
+ self._set_PEM(value)
+ else:
+ self.DER = value
return
raise rpki.exceptions.DERObjectConversionError("Can't honor conversion request %r" % (kw,))
@@ -296,25 +278,35 @@ class DER_object(object):
f = open(filename, "rb")
value = f.read()
f.close()
- if self.pem_converter.looks_like_PEM(value):
- value = self.pem_converter.to_DER(value)
self.clear()
- self.DER = value
+ if looks_like_PEM(value):
+ self._set_PEM(value)
+ else:
+ self.DER = value
self.filename = filename
self.timestamp = timestamp
except (IOError, OSError), e:
- self.failures += 1
- if self.failures % 100 == 1:
+ now = rpki.sundial.now()
+ if self.lastfail is None or now > self.lastfail + self.failure_threshold:
rpki.log.warn("Could not auto_update %r (failures %d): %s" % (self, self.failures, e))
+ self.lastfail = now
else:
- self.failures = 0
+ self.failures = None
def check(self):
"""
Perform basic checks on a DER object.
"""
- assert not self.empty()
self.check_auto_update()
+ assert not self.empty()
+
+ def _set_PEM(self, pem):
+ """
+ Set the POW value of this object based on a PEM input value.
+ Subclasses may need to override this.
+ """
+ assert self.empty()
+ self.POW = self.POW_class.pemRead(pem)
def get_DER(self):
"""
@@ -337,7 +329,7 @@ class DER_object(object):
"""
Get the PEM representation of this object.
"""
- return self.pem_converter.to_PEM(self.get_DER())
+ return self.get_POW().pemWrite()
def __cmp__(self, other):
"""
@@ -541,9 +533,8 @@ class X509(DER_object):
have to care about this implementation nightmare.
"""
- formats = ("DER", "POW")
- pem_converter = PEM_converter("CERTIFICATE")
-
+ POW_class = rpki.POW.X509
+
def get_DER(self):
"""
Get the DER value of this certificate.
@@ -838,8 +829,7 @@ class PKCS10(DER_object):
Class to hold a PKCS #10 request.
"""
- formats = ("DER", "POW")
- pem_converter = PEM_converter("CERTIFICATE REQUEST")
+ POW_class = rpki.POW.PKCS10
## @var expected_ca_keyUsage
# KeyUsage extension flags expected for CA requests.
@@ -1030,10 +1020,9 @@ class RSA(DER_object):
"""
Class to hold an RSA key pair.
"""
-
- formats = ("DER", "POW")
- pem_converter = PEM_converter("RSA PRIVATE KEY")
+ POW_class = rpki.POW.Asymmetric
+
def get_DER(self):
"""
Get the DER value of this keypair.
@@ -1055,6 +1044,19 @@ class RSA(DER_object):
self.POW = rpki.POW.Asymmetric.derReadPrivate(self.get_DER())
return self.POW
+ def get_PEM(self):
+ """
+ Get the PEM representation of this keypair.
+ """
+ return self.get_POW().pemWritePrivate()
+
+ def _set_PEM(self, pem):
+ """
+ Set the POW value of this keypair from a PEM string.
+ """
+ assert self.empty()
+ self.POW = self.POW_class.pemReadPrivate(pem)
+
@classmethod
def generate(cls, keylength = 2048, quiet = False):
"""
@@ -1089,10 +1091,9 @@ class RSApublic(DER_object):
"""
Class to hold an RSA public key.
"""
-
- formats = ("DER", "POW")
- pem_converter = PEM_converter("RSA PUBLIC KEY")
+ POW_class = rpki.POW.Asymmetric
+
def get_DER(self):
"""
Get the DER value of this public key.
@@ -1114,6 +1115,19 @@ class RSApublic(DER_object):
self.POW = rpki.POW.Asymmetric.derReadPublic(self.get_DER())
return self.POW
+ def get_PEM(self):
+ """
+ Get the PEM representation of this public key.
+ """
+ return self.get_POW().pemWritePublic()
+
+ def _set_PEM(self, pem):
+ """
+ Set the POW value of this public key from a PEM string.
+ """
+ assert self.empty()
+ self.POW = self.POW_class.pemReadPublic(pem)
+
def get_SKI(self):
"""
Calculate the SKI of this public key.
@@ -1135,9 +1149,7 @@ class CMS_object(DER_object):
Abstract class to hold a CMS object.
"""
- formats = ("DER", "POW")
econtent_oid = POWify_OID("id-data")
- pem_converter = PEM_converter("CMS")
POW_class = rpki.POW.CMS
## @var dump_on_verify_failure
@@ -1473,7 +1485,6 @@ class SignedManifest(DER_CMS_object):
Class to hold a signed manifest.
"""
- pem_converter = PEM_converter("RPKI MANIFEST")
econtent_oid = POWify_OID("id-ct-rpkiManifest")
POW_class = rpki.POW.Manifest
@@ -1519,7 +1530,6 @@ class ROA(DER_CMS_object):
Class to hold a signed ROA.
"""
- pem_converter = PEM_converter("ROUTE ORIGIN ATTESTATION")
econtent_oid = POWify_OID("id-ct-routeOriginAttestation")
POW_class = rpki.POW.ROA
@@ -1741,7 +1751,6 @@ class Ghostbuster(Wrapped_CMS_object):
managed by the back-end.
"""
- pem_converter = PEM_converter("GHOSTBUSTERS RECORD")
econtent_oid = POWify_OID("id-ct-rpkiGhostbusters")
def encode(self):
@@ -1773,10 +1782,9 @@ class CRL(DER_object):
"""
Class to hold a Certificate Revocation List.
"""
-
- formats = ("DER", "POW")
- pem_converter = PEM_converter("X509 CRL")
+ POW_class = rpki.POW.CRL
+
def get_DER(self):
"""
Get the DER value of this CRL.