aboutsummaryrefslogtreecommitdiff
path: root/scripts/rpki/cms.py
blob: 7b4916dccaa30be8bf692b107c4c9f15229289c2 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
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)