2 # Implements SFA Credentials
4 # Credentials are layered on top of certificates, and are essentially a
5 # certificate that stores a tuple of parameters.
16 import xml.dom.minidom
17 from xml.dom.minidom import Document
18 from sfa.trust.credential_legacy import CredentialLegacy
19 from sfa.trust.certificate import Certificate
20 from sfa.trust.rights import *
21 from sfa.trust.gid import *
22 from sfa.util.faults import *
23 from sfa.util.sfalogging import *
26 signed_cred_template = \
28 <?xml version="1.0" encoding="UTF-8" standalone="no"?>
38 credential_template = \
40 <credential xml:id="%s">
41 <type>privilege</type>
43 <owner_gid>%s</owner_gid>
44 <owner_urn>%s</owner_urn>
45 <target_gid>%s</target_gid>
46 <target_urn>%s</target_urn>
56 signature_template = \
58 <Signature xml:id="Sig_%s" xmlns="http://www.w3.org/2000/09/xmldsig#">
60 <CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/>
61 <SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/>
64 <Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature" />
66 <DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
67 <DigestValue></DigestValue>
87 # Credential is a tuple:
88 # (GIDCaller, GIDObject, LifeTime, Privileges, DelegateBit)
90 # These fields are encoded in one of two ways. The legacy style places
91 # it in the subjectAltName of an X509 certificate. The new credentials
92 # are placed in signed XML.
95 class Credential(Certificate):
108 # Create a Credential object
110 # @param create If true, create a blank x509 certificate
111 # @param subject If subject!=None, create an x509 cert with the subject name
112 # @param string If string!=None, load the credential from the string
113 # @param filename If filename!=None, load the credential from the file
115 def __init__(self, create=False, subject=None, string=None, filename=None):
117 # Check if this is a legacy credential, translate it if so
118 if string or filename:
122 str = file(filename).read()
124 if str.strip().startswith("-----"):
125 self.translate_legacy(str)
129 # Translate a legacy credential into a new one
131 # @param String of the legacy credential
133 def translate_legacy(self, str):
134 legacy = CredentialLegacy(create, subject, string, filename)
135 self.gidCaller = legacy.get_gid_caller()
136 self.gidObject = legacy.get_gid_object()
137 self.lifeTime = legacy.get_lifetime()
138 self.privileges = legacy.get_privileges()
139 self.delegate = legacy.get_delegate()
143 # Need the issuer's private key and name
144 # @param key Keypair object containing the private key of the issuer
145 # @param gid GID of the issuing authority
147 def set_issuer_keys(self, privkey, gid):
148 self.issuer_key = privkey
149 self.issuer_gid = gid
151 def set_pubkey(self, pubkey):
152 self.issuer_pubkey = pubkey
154 def set_parent(self, cred):
158 # set the GID of the caller
160 # @param gid GID object of the caller
162 def set_gid_caller(self, gid):
164 # gid origin caller is the caller's gid by default
165 self.gidOriginCaller = gid
168 # get the GID of the object
170 def get_gid_caller(self):
171 if not self.gidCaller:
173 return self.gidCaller
176 # set the GID of the object
178 # @param gid GID object of the object
180 def set_gid_object(self, gid):
184 # get the GID of the object
186 def get_gid_object(self):
187 if not self.gidObject:
189 return self.gidObject
192 # set the lifetime of this credential
194 # @param lifetime lifetime of credential
196 def set_lifetime(self, lifeTime):
197 self.lifeTime = lifeTime
200 # get the lifetime of the credential
202 def get_lifetime(self):
203 if not self.lifeTime:
208 # set the delegate bit
210 # @param delegate boolean (True or False)
212 def set_delegate(self, delegate):
213 self.delegate = delegate
216 # get the delegate bit
218 def get_delegate(self):
219 if not self.delegate:
226 # @param privs either a comma-separated list of privileges of a RightList object
228 def set_privileges(self, privs):
229 if isinstance(privs, str):
230 self.privileges = RightList(string = privs)
232 self.privileges = privs
235 # return the privileges as a RightList object
237 def get_privileges(self):
238 if not self.privileges:
240 return self.privileges
243 # determine whether the credential allows a particular operation to be
246 # @param op_name string specifying name of operation ("lookup", "update", etc)
248 def can_perform(self, op_name):
249 rights = self.get_privileges()
252 return rights.can_perform(op_name)
254 def append_sub(self, doc, parent, element, text):
255 parent.appendChild(doc.createElement(element).appendChild(doc.createTextNode(text)))
258 # Encode the attributes of the credential into an XML string
259 # This should be done immediately before signing the credential.
263 # Get information from the parent credential
265 p_doc = xml.dom.minidom.parseString(self.parent)
266 p_signed_cred = p_doc.getElementsByTagName("signed-credential")[0]
267 p_cred = p_signed_cred.getElementsByTagName("credential")[0]
268 p_signatures = p_signed_cred.getElementsByTagName("signatures")[0]
269 p_sigs = p_signatures.getElementsByTagName("Signature")
271 # Create the XML document
273 signed_cred = doc.createElement("signed-credential")
274 doc.appendChild(signed_cred)
277 # Fill in the <credential> bit
278 cred = doc.createElement("credential")
279 cred.setAttribute("xml:id", refid)
280 signed_cred.appendChild(cred)
281 self.append_sub(doc, cred, "type", "privilege")
282 self.append_sub(doc, cred, "serial", "8")
283 self.append_sub(doc, cred, "owner_gid", cur_cred.gidCaller.save_to_string())
284 self.append_sub(doc, cred, "owner_urn", cur_cred.gidCaller.get_urn())
285 self.append_sub(doc, cred, "target_gid", cur_cred.gidObject.save_to_string())
286 self.append_sub(doc, cred, "target_urn", cur_cred.gidObject.get_urn())
287 self.append_sub(doc, cred, "uuid", "")
288 self.append_sub(doc, cred, "expires", self.lifeTime)
289 priveleges = doc.createElement("privileges")
290 cred.appendChild(priveleges)
292 # Add the parent credential if it exists
294 cred.appendChild(doc.createElement("parent").appendChild(p_cred))
297 # Fill out any priveleges here
301 signed_cred.appendChild(cred)
304 # Fill in the <signature> bit
305 signatures = doc.createElement("signatures")
307 sz_sig = signature_template % (refid,refid)
309 sdoc = xml.dom.minidom.parseString(sz_sig)
310 sig_ele = doc.importNode(sdoc.getElemenetsByTagName("Signature")[0], True)
311 signatures.appendChild(sig_ele)
314 # Add any parent signatures
317 signatures.appendChild(sig)
319 # Get the finished product
320 self.xml = doc.toxml()
323 ## # Call out to xmlsec1 to sign it
324 ## XMLSEC = '/usr/bin/xmlsec1'
326 ## filename = "/tmp/cred_%d" % random.random()
327 ## f = open(filename, "w")
330 ## signed = os.popen('/usr/bin/xmlsec1 --sign --node-id "%s" --privkey-pem %s,%s %s'
331 ## % ('ref1', self.issuer_privkey, self.issuer_cert, filename)).readlines()
334 ## self.encoded = signed
337 # Retrieve the attributes of the credential from the alt-subject-name field
338 # of the X509 certificate. This is automatically done by the various
339 # get_* methods of this class and should not need to be called explicitly.
342 data = self.get_data().lstrip('URI:http://')
345 dict = xmlrpclib.loads(data)[0][0]
349 self.lifeTime = dict.get("lifeTime", None)
350 self.delegate = dict.get("delegate", None)
352 privStr = dict.get("privileges", None)
354 self.privileges = RightList(string = privStr)
356 self.privileges = None
358 gidCallerStr = dict.get("gidCaller", None)
360 self.gidCaller = GID(string=gidCallerStr)
362 self.gidCaller = None
364 gidObjectStr = dict.get("gidObject", None)
366 self.gidObject = GID(string=gidObjectStr)
368 self.gidObject = None
371 # Verify that a chain of credentials is valid (see cert.py:verify). In
372 # addition to the checks for ordinary certificates, verification also
373 # ensures that the delegate bit was set by each parent in the chain. If
374 # a delegate bit was not set, then an exception is thrown.
376 # Each credential must be a subset of the rights of the parent.
378 def verify_chain(self, trusted_certs = None):
379 # do the normal certificate verification stuff
380 Certificate.verify_chain(self, trusted_certs)
383 # make sure the parent delegated rights to the child
384 if not self.parent.get_delegate():
385 raise MissingDelegateBit(self.parent.get_subject())
387 # make sure the rights given to the child are a subset of the
389 if not self.parent.get_privileges().is_superset(self.get_privileges()):
390 raise ChildRightsNotSubsetOfParent(self.get_subject()
391 + " " + self.parent.get_privileges().save_to_string()
392 + " " + self.get_privileges().save_to_string())
397 # Dump the contents of a credential to stdout in human-readable format
399 # @param dump_parents If true, also dump the parent certificates
401 def dump(self, dump_parents=False):
402 print "CREDENTIAL", self.get_subject()
404 print " privs:", self.get_privileges().save_to_string()
407 gidCaller = self.get_gid_caller()
409 gidCaller.dump(8, dump_parents)
412 gidObject = self.get_gid_object()
414 gidObject.dump(8, dump_parents)
416 print " delegate:", self.get_delegate()
418 if self.parent and dump_parents:
420 self.parent.dump(dump_parents)