aboutsummaryrefslogtreecommitdiff
path: root/rpkid/rpki/cms.py
diff options
context:
space:
mode:
Diffstat (limited to 'rpkid/rpki/cms.py')
-rw-r--r--rpkid/rpki/cms.py120
1 files changed, 120 insertions, 0 deletions
diff --git a/rpkid/rpki/cms.py b/rpkid/rpki/cms.py
new file mode 100644
index 00000000..7b4916dc
--- /dev/null
+++ b/rpkid/rpki/cms.py
@@ -0,0 +1,120 @@
+# $Id$
+
+# Copyright (C) 2007--2008 American Registry for Internet Numbers ("ARIN")
+#
+# 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 ARIN DISCLAIMS ALL WARRANTIES WITH
+# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+# AND FITNESS. IN NO EVENT SHALL ARIN 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.
+
+"""CMS routines.
+
+These used to use the OpenSSL CLI too, which was slow. I've since
+added minimal PKCS #7 / CMS capability to POW, so we now use that
+instead. I should write a pretty DER_object wrapper around the POW
+code and include it in x509.py, but I haven't gotten to that yet.
+"""
+
+import os, rpki.x509, rpki.exceptions, lxml.etree, rpki.log, POW
+
+debug = 1
+
+# openssl smime -sign -nodetach -outform DER -signer biz-certs/Alice-EE.cer
+# -certfile biz-certs/Alice-CA.cer -inkey biz-certs/Alice-EE.key
+# -in THING -out THING.der
+
+def sign(plaintext, keypair, certs):
+ """Sign plaintext as CMS with specified key and bag of certificates.
+
+ We have to sort the certificates into the correct order before the
+ OpenSSL CLI tool will accept them. rpki.x509 handles that for us.
+ """
+
+ p7 = POW.PKCS7()
+ p7.sign(certs[0].get_POW(), keypair.get_POW(), [x.get_POW() for x in certs[1:]], plaintext)
+ cms = p7.derWrite()
+
+ if debug >= 2:
+ print
+ print "Signed CMS:"
+ dumpasn1(cms)
+
+ return cms
+
+# openssl smime -verify -inform DER -in THING.der -CAfile biz-certs/Alice-Root.cer
+
+def verify(cms, ta):
+ """Verify the signature of a chunk of CMS.
+
+ Returns the plaintext on success, otherwise raise an exception.
+ """
+
+ if debug >= 2:
+ print
+ print "Verifying CMS:"
+ dumpasn1(cms)
+
+ p7 = POW.derRead(POW.PKCS7_MESSAGE, cms)
+
+ store = POW.X509Store()
+ store.addTrust(ta.get_POW())
+
+ try:
+ return p7.verify(store)
+
+ except:
+ if debug >= 1:
+ print "CMS verification failed, dumping inputs:"
+ print
+ print "TA:"
+ dumpasn1(ta.get_DER())
+ print
+ print "CMS:"
+ dumpasn1(cms)
+ raise rpki.exceptions.CMSVerificationFailed, "CMS verification failed"
+
+# openssl smime -verify -noverify -inform DER -in THING.der
+
+def extract(cms):
+ """Extract the content of a signed CMS message WITHOUT verifying the
+ signature. Don't try this at home, kids.
+ """
+
+ return POW.derRead(POW.PKCS7_MESSAGE, cms).extract()
+
+def xml_verify(cms, ta):
+ """Composite routine to verify CMS-wrapped XML."""
+
+ val = lxml.etree.fromstring(verify(cms, ta))
+ return val
+
+def xml_sign(elt, key, certs, encoding = "us-ascii"):
+ """Composite routine to sign CMS-wrapped XML."""
+
+ val = sign(lxml.etree.tostring(elt, pretty_print = True, encoding = encoding, xml_declaration = True),
+ key, certs)
+ return val
+
+def dumpasn1(thing):
+ """Prettyprint an ASN.1 DER object using cryptlib dumpasn1 tool.
+ Use a temporary file rather than popen4() because dumpasn1 uses
+ seek() when decoding ASN.1 content nested in OCTET STRING values.
+ """
+
+ fn = "dumpasn1.tmp"
+ try:
+ f = open(fn, "w")
+ f.write(thing)
+ f.close()
+ f = os.popen("dumpasn1 2>&1 -a " + fn)
+ print "\n".join(x for x in f.read().splitlines() if x.startswith(" "))
+ f.close()
+ finally:
+ os.unlink(fn)