-#----------------------------------------------------------------------\r
-# Copyright (c) 2014 Raytheon BBN Technologies\r
-#\r
-# Permission is hereby granted, free of charge, to any person obtaining\r
-# a copy of this software and/or hardware specification (the "Work") to\r
-# deal in the Work without restriction, including without limitation the\r
-# rights to use, copy, modify, merge, publish, distribute, sublicense,\r
-# and/or sell copies of the Work, and to permit persons to whom the Work\r
-# is furnished to do so, subject to the following conditions:\r
-#\r
-# The above copyright notice and this permission notice shall be\r
-# included in all copies or substantial portions of the Work.\r
-#\r
-# THE WORK IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS\r
-# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\r
-# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\r
-# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\r
-# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,\r
-# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r
-# OUT OF OR IN CONNECTION WITH THE WORK OR THE USE OR OTHER DEALINGS\r
-# IN THE WORK.\r
-#----------------------------------------------------------------------\r
-\r
-from sfa.trust.credential import Credential, append_sub\r
-from sfa.util.sfalogging import logger\r
-\r
-from StringIO import StringIO\r
-from xml.dom.minidom import Document, parseString\r
-\r
-HAVELXML = False\r
-try:\r
- from lxml import etree\r
- HAVELXML = True\r
-except:\r
- pass\r
-\r
-# This module defines a subtype of sfa.trust,credential.Credential\r
-# called an ABACCredential. An ABAC credential is a signed statement\r
-# asserting a role representing the relationship between a subject and target\r
-# or between a subject and a class of targets (all those satisfying a role).\r
-#\r
-# An ABAC credential is like a normal SFA credential in that it has\r
-# a validated signature block and is checked for expiration. \r
-# It does not, however, have 'privileges'. Rather it contains a 'head' and\r
-# list of 'tails' of elements, each of which represents a principal and\r
-# role.\r
-\r
-# A special case of an ABAC credential is a speaks_for credential. Such\r
-# a credential is simply an ABAC credential in form, but has a single \r
-# tail and fixed role 'speaks_for'. In ABAC notation, it asserts\r
-# AGENT.speaks_for(AGENT)<-CLIENT, or "AGENT asserts that CLIENT may speak\r
-# for AGENT". The AGENT in this case is the head and the CLIENT is the\r
-# tail and 'speaks_for_AGENT' is the role on the head. These speaks-for\r
-# Credentials are used to allow a tool to 'speak as' itself but be recognized\r
-# as speaking for an individual and be authorized to the rights of that\r
-# individual and not to the rights of the tool itself.\r
-\r
-# For more detail on the semantics and syntax and expected usage patterns\r
-# of ABAC credentials, see http://groups.geni.net/geni/wiki/TIEDABACCredential.\r
-\r
-\r
-# An ABAC element contains a principal (keyid and optional mnemonic)\r
-# and optional role and linking_role element\r
-class ABACElement:\r
- def __init__(self, principal_keyid, principal_mnemonic=None, \\r
- role=None, linking_role=None):\r
- self._principal_keyid = principal_keyid\r
- self._principal_mnemonic = principal_mnemonic\r
- self._role = role\r
- self._linking_role = linking_role\r
-\r
- def get_principal_keyid(self): return self._principal_keyid\r
- def get_principal_mnemonic(self): return self._principal_mnemonic\r
- def get_role(self): return self._role\r
- def get_linking_role(self): return self._linking_role\r
-\r
- def __str__(self):\r
- ret = self._principal_keyid\r
- if self._principal_mnemonic:\r
- ret = "%s (%s)" % (self._principal_mnemonic, self._principal_keyid)\r
- if self._linking_role:\r
- ret += ".%s" % self._linking_role\r
- if self._role:\r
- ret += ".%s" % self._role\r
- return ret\r
-\r
-# Subclass of Credential for handling ABAC credentials\r
-# They have a different cred_type (geni_abac vs. geni_sfa)\r
-# and they have a head and tail and role (as opposed to privileges)\r
-class ABACCredential(Credential):\r
-\r
- ABAC_CREDENTIAL_TYPE = 'geni_abac'\r
-\r
- def __init__(self, create=False, subject=None, \r
- string=None, filename=None):\r
- self.head = None # An ABACElemenet\r
- self.tails = [] # List of ABACElements\r
- super(ABACCredential, self).__init__(create=create, \r
- subject=subject, \r
- string=string, \r
- filename=filename)\r
- self.cred_type = ABACCredential.ABAC_CREDENTIAL_TYPE\r
-\r
- def get_head(self) : \r
- if not self.head: \r
- self.decode()\r
- return self.head\r
-\r
- def get_tails(self) : \r
- if len(self.tails) == 0:\r
- self.decode()\r
- return self.tails\r
-\r
- def decode(self):\r
- super(ABACCredential, self).decode()\r
- # Pull out the ABAC-specific info\r
- doc = parseString(self.xml)\r
- rt0s = doc.getElementsByTagName('rt0')\r
- if len(rt0s) != 1:\r
- raise CredentialNotVerifiable("ABAC credential had no rt0 element")\r
- rt0_root = rt0s[0]\r
- heads = self._get_abac_elements(rt0_root, 'head')\r
- if len(heads) != 1:\r
- raise CredentialNotVerifiable("ABAC credential should have exactly 1 head element, had %d" % len(heads))\r
-\r
- self.head = heads[0]\r
- self.tails = self._get_abac_elements(rt0_root, 'tail')\r
-\r
- def _get_abac_elements(self, root, label):\r
- abac_elements = []\r
- elements = root.getElementsByTagName(label)\r
- for elt in elements:\r
- keyids = elt.getElementsByTagName('keyid')\r
- if len(keyids) != 1:\r
- raise CredentialNotVerifiable("ABAC credential element '%s' should have exactly 1 keyid, had %d." % (label, len(keyids)))\r
- keyid_elt = keyids[0]\r
- keyid = keyid_elt.childNodes[0].nodeValue.strip()\r
-\r
- mnemonic = None\r
- mnemonic_elts = elt.getElementsByTagName('mnemonic')\r
- if len(mnemonic_elts) > 0:\r
- mnemonic = mnemonic_elts[0].childNodes[0].nodeValue.strip()\r
-\r
- role = None\r
- role_elts = elt.getElementsByTagName('role')\r
- if len(role_elts) > 0:\r
- role = role_elts[0].childNodes[0].nodeValue.strip()\r
-\r
- linking_role = None\r
- linking_role_elts = elt.getElementsByTagName('linking_role')\r
- if len(linking_role_elts) > 0:\r
- linking_role = linking_role_elts[0].childNodes[0].nodeValue.strip()\r
-\r
- abac_element = ABACElement(keyid, mnemonic, role, linking_role)\r
- abac_elements.append(abac_element)\r
-\r
- return abac_elements\r
-\r
- def dump_string(self, dump_parents=False, show_xml=False):\r
- result = "ABAC Credential\n"\r
- filename=self.get_filename()\r
- if filename: result += "Filename %s\n"%filename\r
- if self.expiration:\r
- result += "\texpiration: %s \n" % self.expiration.isoformat()\r
-\r
- result += "\tHead: %s\n" % self.get_head() \r
- for tail in self.get_tails():\r
- result += "\tTail: %s\n" % tail\r
- if self.get_signature():\r
- result += " gidIssuer:\n"\r
- result += self.get_signature().get_issuer_gid().dump_string(8, dump_parents)\r
- if show_xml and HAVELXML:\r
- try:\r
- tree = etree.parse(StringIO(self.xml))\r
- aside = etree.tostring(tree, pretty_print=True)\r
- result += "\nXML:\n\n"\r
- result += aside\r
- result += "\nEnd XML\n"\r
- except:\r
- import traceback\r
- print "exc. Credential.dump_string / XML"\r
- traceback.print_exc()\r
- return result\r
-\r
- # sounds like this should be __repr__ instead ??\r
- # Produce the ABAC assertion. Something like [ABAC cred: Me.role<-You] or similar\r
- def get_summary_tostring(self):\r
- result = "[ABAC cred: " + str(self.get_head())\r
- for tail in self.get_tails():\r
- result += "<-%s" % str(tail)\r
- result += "]"\r
- return result\r
-\r
- def createABACElement(self, doc, tagName, abacObj):\r
- kid = abacObj.get_principal_keyid()\r
- mnem = abacObj.get_principal_mnemonic() # may be None\r
- role = abacObj.get_role() # may be None\r
- link = abacObj.get_linking_role() # may be None\r
- ele = doc.createElement(tagName)\r
- prin = doc.createElement('ABACprincipal')\r
- ele.appendChild(prin)\r
- append_sub(doc, prin, "keyid", kid)\r
- if mnem:\r
- append_sub(doc, prin, "mnemonic", mnem)\r
- if role:\r
- append_sub(doc, ele, "role", role)\r
- if link:\r
- append_sub(doc, ele, "linking_role", link)\r
- return ele\r
-\r
- ##\r
- # Encode the attributes of the credential into an XML string\r
- # This should be done immediately before signing the credential.\r
- # WARNING:\r
- # In general, a signed credential obtained externally should\r
- # not be changed else the signature is no longer valid. So, once\r
- # you have loaded an existing signed credential, do not call encode() or sign() on it.\r
-\r
- def encode(self):\r
- # Create the XML document\r
- doc = Document()\r
- signed_cred = doc.createElement("signed-credential")\r
-\r
-# Declare namespaces\r
-# Note that credential/policy.xsd are really the PG schemas\r
-# in a PL namespace.\r
-# Note that delegation of credentials between the 2 only really works\r
-# cause those schemas are identical.\r
-# Also note these PG schemas talk about PG tickets and CM policies.\r
- signed_cred.setAttribute("xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance")\r
- signed_cred.setAttribute("xsi:noNamespaceSchemaLocation", "http://www.geni.net/resources/credential/2/credential.xsd")\r
- signed_cred.setAttribute("xsi:schemaLocation", "http://www.planet-lab.org/resources/sfa/ext/policy/1 http://www.planet-lab.org/resources/sfa/ext/policy/1/policy.xsd")\r
-\r
-# PG says for those last 2:\r
-# signed_cred.setAttribute("xsi:noNamespaceSchemaLocation", "http://www.protogeni.net/resources/credential/credential.xsd")\r
-# signed_cred.setAttribute("xsi:schemaLocation", "http://www.protogeni.net/resources/credential/ext/policy/1 http://www.protogeni.net/resources/credential/ext/policy/1/policy.xsd")\r
-\r
- doc.appendChild(signed_cred)\r
-\r
- # Fill in the <credential> bit\r
- cred = doc.createElement("credential")\r
- cred.setAttribute("xml:id", self.get_refid())\r
- signed_cred.appendChild(cred)\r
- append_sub(doc, cred, "type", "abac")\r
-\r
- # Stub fields\r
- append_sub(doc, cred, "serial", "8")\r
- append_sub(doc, cred, "owner_gid", '')\r
- append_sub(doc, cred, "owner_urn", '')\r
- append_sub(doc, cred, "target_gid", '')\r
- append_sub(doc, cred, "target_urn", '')\r
- append_sub(doc, cred, "uuid", "")\r
-\r
- if not self.expiration:\r
- self.set_expiration(datetime.datetime.utcnow() + datetime.timedelta(seconds=DEFAULT_CREDENTIAL_LIFETIME))\r
- self.expiration = self.expiration.replace(microsecond=0)\r
- if self.expiration.tzinfo is not None and self.expiration.tzinfo.utcoffset(self.expiration) is not None:\r
- # TZ aware. Make sure it is UTC\r
- self.expiration = self.expiration.astimezone(tz.tzutc())\r
- append_sub(doc, cred, "expires", self.expiration.strftime('%Y-%m-%dT%H:%M:%SZ')) # RFC3339\r
-\r
- abac = doc.createElement("abac")\r
- rt0 = doc.createElement("rt0")\r
- abac.appendChild(rt0)\r
- cred.appendChild(abac)\r
- append_sub(doc, rt0, "version", "1.1")\r
- head = self.createABACElement(doc, "head", self.get_head())\r
- rt0.appendChild(head)\r
- for tail in self.get_tails():\r
- tailEle = self.createABACElement(doc, "tail", tail)\r
- rt0.appendChild(tailEle)\r
-\r
- # Create the <signatures> tag\r
- signatures = doc.createElement("signatures")\r
- signed_cred.appendChild(signatures)\r
-\r
- # Get the finished product\r
- self.xml = doc.toxml("utf-8")\r
+#----------------------------------------------------------------------
+# Copyright (c) 2014 Raytheon BBN Technologies
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and/or hardware specification (the "Work") to
+# deal in the Work without restriction, including without limitation the
+# rights to use, copy, modify, merge, publish, distribute, sublicense,
+# and/or sell copies of the Work, and to permit persons to whom the Work
+# is furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Work.
+#
+# THE WORK IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE WORK OR THE USE OR OTHER DEALINGS
+# IN THE WORK.
+#----------------------------------------------------------------------
+
+from __future__ import print_function
+
+from sfa.trust.credential import Credential, append_sub, DEFAULT_CREDENTIAL_LIFETIME
+from sfa.util.sfalogging import logger
+from sfa.util.sfatime import SFATIME_FORMAT
+
+from xml.dom.minidom import Document, parseString
+
+from sfa.util.py23 import StringIO
+
+HAVELXML = False
+try:
+ from lxml import etree
+ HAVELXML = True
+except:
+ pass
+
+# This module defines a subtype of sfa.trust,credential.Credential
+# called an ABACCredential. An ABAC credential is a signed statement
+# asserting a role representing the relationship between a subject and target
+# or between a subject and a class of targets (all those satisfying a role).
+#
+# An ABAC credential is like a normal SFA credential in that it has
+# a validated signature block and is checked for expiration.
+# It does not, however, have 'privileges'. Rather it contains a 'head' and
+# list of 'tails' of elements, each of which represents a principal and
+# role.
+
+# A special case of an ABAC credential is a speaks_for credential. Such
+# a credential is simply an ABAC credential in form, but has a single
+# tail and fixed role 'speaks_for'. In ABAC notation, it asserts
+# AGENT.speaks_for(AGENT)<-CLIENT, or "AGENT asserts that CLIENT may speak
+# for AGENT". The AGENT in this case is the head and the CLIENT is the
+# tail and 'speaks_for_AGENT' is the role on the head. These speaks-for
+# Credentials are used to allow a tool to 'speak as' itself but be recognized
+# as speaking for an individual and be authorized to the rights of that
+# individual and not to the rights of the tool itself.
+
+# For more detail on the semantics and syntax and expected usage patterns
+# of ABAC credentials, see http://groups.geni.net/geni/wiki/TIEDABACCredential.
+
+
+# An ABAC element contains a principal (keyid and optional mnemonic)
+# and optional role and linking_role element
+class ABACElement:
+ def __init__(self, principal_keyid, principal_mnemonic=None, \
+ role=None, linking_role=None):
+ self._principal_keyid = principal_keyid
+ self._principal_mnemonic = principal_mnemonic
+ self._role = role
+ self._linking_role = linking_role
+
+ def get_principal_keyid(self): return self._principal_keyid
+ def get_principal_mnemonic(self): return self._principal_mnemonic
+ def get_role(self): return self._role
+ def get_linking_role(self): return self._linking_role
+
+ def __str__(self):
+ ret = self._principal_keyid
+ if self._principal_mnemonic:
+ ret = "%s (%s)" % (self._principal_mnemonic, self._principal_keyid)
+ if self._linking_role:
+ ret += ".%s" % self._linking_role
+ if self._role:
+ ret += ".%s" % self._role
+ return ret
+
+# Subclass of Credential for handling ABAC credentials
+# They have a different cred_type (geni_abac vs. geni_sfa)
+# and they have a head and tail and role (as opposed to privileges)
+class ABACCredential(Credential):
+
+ ABAC_CREDENTIAL_TYPE = 'geni_abac'
+
+ def __init__(self, create=False, subject=None,
+ string=None, filename=None):
+ self.head = None # An ABACElemenet
+ self.tails = [] # List of ABACElements
+ super(ABACCredential, self).__init__(create=create,
+ subject=subject,
+ string=string,
+ filename=filename)
+ self.cred_type = ABACCredential.ABAC_CREDENTIAL_TYPE
+
+ def get_head(self) :
+ if not self.head:
+ self.decode()
+ return self.head
+
+ def get_tails(self) :
+ if len(self.tails) == 0:
+ self.decode()
+ return self.tails
+
+ def decode(self):
+ super(ABACCredential, self).decode()
+ # Pull out the ABAC-specific info
+ doc = parseString(self.xml)
+ rt0s = doc.getElementsByTagName('rt0')
+ if len(rt0s) != 1:
+ raise CredentialNotVerifiable("ABAC credential had no rt0 element")
+ rt0_root = rt0s[0]
+ heads = self._get_abac_elements(rt0_root, 'head')
+ if len(heads) != 1:
+ raise CredentialNotVerifiable("ABAC credential should have exactly 1 head element, had %d" % len(heads))
+
+ self.head = heads[0]
+ self.tails = self._get_abac_elements(rt0_root, 'tail')
+
+ def _get_abac_elements(self, root, label):
+ abac_elements = []
+ elements = root.getElementsByTagName(label)
+ for elt in elements:
+ keyids = elt.getElementsByTagName('keyid')
+ if len(keyids) != 1:
+ raise CredentialNotVerifiable("ABAC credential element '%s' should have exactly 1 keyid, had %d." % (label, len(keyids)))
+ keyid_elt = keyids[0]
+ keyid = keyid_elt.childNodes[0].nodeValue.strip()
+
+ mnemonic = None
+ mnemonic_elts = elt.getElementsByTagName('mnemonic')
+ if len(mnemonic_elts) > 0:
+ mnemonic = mnemonic_elts[0].childNodes[0].nodeValue.strip()
+
+ role = None
+ role_elts = elt.getElementsByTagName('role')
+ if len(role_elts) > 0:
+ role = role_elts[0].childNodes[0].nodeValue.strip()
+
+ linking_role = None
+ linking_role_elts = elt.getElementsByTagName('linking_role')
+ if len(linking_role_elts) > 0:
+ linking_role = linking_role_elts[0].childNodes[0].nodeValue.strip()
+
+ abac_element = ABACElement(keyid, mnemonic, role, linking_role)
+ abac_elements.append(abac_element)
+
+ return abac_elements
+
+ def dump_string(self, dump_parents=False, show_xml=False):
+ result = "ABAC Credential\n"
+ filename=self.get_filename()
+ if filename: result += "Filename %s\n"%filename
+ if self.expiration:
+ result += "\texpiration: %s \n" % self.expiration.strftime(SFATIME_FORMAT)
+
+ result += "\tHead: %s\n" % self.get_head()
+ for tail in self.get_tails():
+ result += "\tTail: %s\n" % tail
+ if self.get_signature():
+ result += " gidIssuer:\n"
+ result += self.get_signature().get_issuer_gid().dump_string(8, dump_parents)
+ if show_xml and HAVELXML:
+ try:
+ tree = etree.parse(StringIO(self.xml))
+ aside = etree.tostring(tree, pretty_print=True)
+ result += "\nXML:\n\n"
+ result += aside
+ result += "\nEnd XML\n"
+ except:
+ import traceback
+ print("exc. Credential.dump_string / XML")
+ traceback.print_exc()
+ return result
+
+ # sounds like this should be __repr__ instead ??
+ # Produce the ABAC assertion. Something like [ABAC cred: Me.role<-You] or similar
+ def pretty_cred(self):
+ result = "[ABAC cred: " + str(self.get_head())
+ for tail in self.get_tails():
+ result += "<-%s" % str(tail)
+ result += "]"
+ return result
+
+ def createABACElement(self, doc, tagName, abacObj):
+ kid = abacObj.get_principal_keyid()
+ mnem = abacObj.get_principal_mnemonic() # may be None
+ role = abacObj.get_role() # may be None
+ link = abacObj.get_linking_role() # may be None
+ ele = doc.createElement(tagName)
+ prin = doc.createElement('ABACprincipal')
+ ele.appendChild(prin)
+ append_sub(doc, prin, "keyid", kid)
+ if mnem:
+ append_sub(doc, prin, "mnemonic", mnem)
+ if role:
+ append_sub(doc, ele, "role", role)
+ if link:
+ append_sub(doc, ele, "linking_role", link)
+ return ele
+
+ ##
+ # Encode the attributes of the credential into an XML string
+ # This should be done immediately before signing the credential.
+ # WARNING:
+ # In general, a signed credential obtained externally should
+ # not be changed else the signature is no longer valid. So, once
+ # you have loaded an existing signed credential, do not call encode() or sign() on it.
+
+ def encode(self):
+ # Create the XML document
+ doc = Document()
+ signed_cred = doc.createElement("signed-credential")
+
+# Declare namespaces
+# Note that credential/policy.xsd are really the PG schemas
+# in a PL namespace.
+# Note that delegation of credentials between the 2 only really works
+# cause those schemas are identical.
+# Also note these PG schemas talk about PG tickets and CM policies.
+ signed_cred.setAttribute("xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance")
+ signed_cred.setAttribute("xsi:noNamespaceSchemaLocation", "http://www.geni.net/resources/credential/2/credential.xsd")
+ signed_cred.setAttribute("xsi:schemaLocation", "http://www.planet-lab.org/resources/sfa/ext/policy/1 http://www.planet-lab.org/resources/sfa/ext/policy/1/policy.xsd")
+
+# PG says for those last 2:
+# signed_cred.setAttribute("xsi:noNamespaceSchemaLocation", "http://www.protogeni.net/resources/credential/credential.xsd")
+# signed_cred.setAttribute("xsi:schemaLocation", "http://www.protogeni.net/resources/credential/ext/policy/1 http://www.protogeni.net/resources/credential/ext/policy/1/policy.xsd")
+
+ doc.appendChild(signed_cred)
+
+ # Fill in the <credential> bit
+ cred = doc.createElement("credential")
+ cred.setAttribute("xml:id", self.get_refid())
+ signed_cred.appendChild(cred)
+ append_sub(doc, cred, "type", "abac")
+
+ # Stub fields
+ append_sub(doc, cred, "serial", "8")
+ append_sub(doc, cred, "owner_gid", '')
+ append_sub(doc, cred, "owner_urn", '')
+ append_sub(doc, cred, "target_gid", '')
+ append_sub(doc, cred, "target_urn", '')
+ append_sub(doc, cred, "uuid", "")
+
+ if not self.expiration:
+ self.set_expiration(datetime.datetime.utcnow() + datetime.timedelta(seconds=DEFAULT_CREDENTIAL_LIFETIME))
+ self.expiration = self.expiration.replace(microsecond=0)
+ if self.expiration.tzinfo is not None and self.expiration.tzinfo.utcoffset(self.expiration) is not None:
+ # TZ aware. Make sure it is UTC
+ self.expiration = self.expiration.astimezone(tz.tzutc())
+ append_sub(doc, cred, "expires", self.expiration.strftime(SFATIME_FORMAT)) # RFC3339
+
+ abac = doc.createElement("abac")
+ rt0 = doc.createElement("rt0")
+ abac.appendChild(rt0)
+ cred.appendChild(abac)
+ append_sub(doc, rt0, "version", "1.1")
+ head = self.createABACElement(doc, "head", self.get_head())
+ rt0.appendChild(head)
+ for tail in self.get_tails():
+ tailEle = self.createABACElement(doc, "tail", tail)
+ rt0.appendChild(tailEle)
+
+ # Create the <signatures> tag
+ signatures = doc.createElement("signatures")
+ signed_cred.appendChild(signatures)
+
+ # Get the finished product
+ self.xml = doc.toxml("utf-8")