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 *
27 # . Need to verify credentials
28 # . Need to add privileges (make PG and PL privs work together and add delegation per privelege instead of global)
29 # . Need to fix lifetime
30 # . Need to make sure delegation is fully supported
33 signature_template = \
35 <Signature xml:id="Sig_%s" xmlns="http://www.w3.org/2000/09/xmldsig#">
37 <CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/>
38 <SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/>
41 <Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature" />
43 <DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
44 <DigestValue></DigestValue>
64 # Credential is a tuple:
65 # (GIDCaller, GIDObject, LifeTime, Privileges, DelegateBit)
67 # These fields are encoded in one of two ways. The legacy style places
68 # it in the subjectAltName of an X509 certificate. The new credentials
69 # are placed in signed XML.
72 class Credential(object):
85 # Create a Credential object
87 # @param create If true, create a blank x509 certificate
88 # @param subject If subject!=None, create an x509 cert with the subject name
89 # @param string If string!=None, load the credential from the string
90 # @param filename If filename!=None, load the credential from the file
92 def __init__(self, create=False, subject=None, string=None, filename=None):
94 # Check if this is a legacy credential, translate it if so
95 if string or filename:
99 str = file(filename).read()
101 if str.strip().startswith("-----"):
102 self.translate_legacy(str)
105 # Let's not mess around with invalid credentials
109 # Translate a legacy credential into a new one
111 # @param String of the legacy credential
113 def translate_legacy(self, str):
114 legacy = CredentialLegacy(False,string=str)
115 self.gidCaller = legacy.get_gid_caller()
116 self.gidObject = legacy.get_gid_object()
117 self.lifeTime = legacy.get_lifetime()
118 self.privileges = legacy.get_privileges()
119 self.delegate = legacy.get_delegate()
122 # Need the issuer's private key and name
123 # @param key Keypair object containing the private key of the issuer
124 # @param gid GID of the issuing authority
126 def set_issuer_keys(self, privkey, gid):
127 self.issuer_privkey = privkey
128 self.issuer_gid = gid
130 def set_pubkey(self, pubkey):
131 self.issuer_pubkey = pubkey
133 def set_parent(self, cred):
137 # set the GID of the caller
139 # @param gid GID object of the caller
141 def set_gid_caller(self, gid):
143 # gid origin caller is the caller's gid by default
144 self.gidOriginCaller = gid
147 # get the GID of the object
149 def get_gid_caller(self):
150 if not self.gidCaller:
152 return self.gidCaller
155 # set the GID of the object
157 # @param gid GID object of the object
159 def set_gid_object(self, gid):
163 # get the GID of the object
165 def get_gid_object(self):
166 if not self.gidObject:
168 return self.gidObject
171 # set the lifetime of this credential
173 # @param lifetime lifetime of credential
175 def set_lifetime(self, lifeTime):
176 self.lifeTime = lifeTime
179 # get the lifetime of the credential
181 def get_lifetime(self):
182 if not self.lifeTime:
187 # set the delegate bit
189 # @param delegate boolean (True or False)
191 def set_delegate(self, delegate):
192 self.delegate = delegate
195 # get the delegate bit
197 def get_delegate(self):
198 if not self.delegate:
205 # @param privs either a comma-separated list of privileges of a RightList object
207 def set_privileges(self, privs):
208 if isinstance(privs, str):
209 self.privileges = RightList(string = privs)
211 self.privileges = privs
214 # return the privileges as a RightList object
216 def get_privileges(self):
217 if not self.privileges:
219 return self.privileges
222 # determine whether the credential allows a particular operation to be
225 # @param op_name string specifying name of operation ("lookup", "update", etc)
227 def can_perform(self, op_name):
228 rights = self.get_privileges()
231 return rights.can_perform(op_name)
233 def append_sub(self, doc, parent, element, text):
234 ele = doc.createElement(element)
235 ele.appendChild(doc.createTextNode(text))
236 parent.appendChild(ele)
239 # Encode the attributes of the credential into an XML string
240 # This should be done immediately before signing the credential.
244 # Get information from the parent credential
246 p_doc = xml.dom.minidom.parseString(self.parent)
247 p_signed_cred = p_doc.getElementsByTagName("signed-credential")[0]
248 p_cred = p_signed_cred.getElementsByTagName("credential")[0]
249 p_signatures = p_signed_cred.getElementsByTagName("signatures")[0]
250 p_sigs = p_signatures.getElementsByTagName("Signature")
252 # Create the XML document
254 signed_cred = doc.createElement("signed-credential")
255 doc.appendChild(signed_cred)
258 # Fill in the <credential> bit
260 cred = doc.createElement("credential")
261 cred.setAttribute("xml:id", refid)
262 signed_cred.appendChild(cred)
263 self.append_sub(doc, cred, "type", "privilege")
264 self.append_sub(doc, cred, "serial", "8")
265 self.append_sub(doc, cred, "owner_gid", self.gidCaller.save_to_string())
266 self.append_sub(doc, cred, "owner_urn", self.gidCaller.get_urn())
267 self.append_sub(doc, cred, "target_gid", self.gidObject.save_to_string())
268 self.append_sub(doc, cred, "target_urn", self.gidObject.get_urn())
269 self.append_sub(doc, cred, "uuid", "")
270 self.append_sub(doc, cred, "expires", str(self.lifeTime))
271 priveleges = doc.createElement("privileges")
272 cred.appendChild(priveleges)
275 rights = self.privileges.save_to_string().split(",")
277 priv = doc.createElement("privelege")
278 priv.append_sub(doc, priv, "name", right.strip())
279 priv.append_sub(doc, priv, "can_delegate", str(self.delegate))
280 priveleges.appendChild(priv)
282 # Add the parent credential if it exists
284 cred.appendChild(doc.createElement("parent").appendChild(p_cred))
287 signed_cred.appendChild(cred)
290 # Fill in the <signature> bit
291 signatures = doc.createElement("signatures")
293 sz_sig = signature_template % (refid,refid)
295 sdoc = xml.dom.minidom.parseString(sz_sig)
296 sig_ele = doc.importNode(sdoc.getElementsByTagName("Signature")[0], True)
297 signatures.appendChild(sig_ele)
300 # Add any parent signatures
303 signatures.appendChild(sig)
305 signed_cred.appendChild(signatures)
306 # Get the finished product
307 self.xml = doc.toxml()
308 #print doc.toprettyxml()
313 def save_to_string(self):
322 # Call out to xmlsec1 to sign it
323 XMLSEC = '/usr/bin/xmlsec1'
325 filename = "/tmp/cred_%d" % random.randint(0,999999999)
326 f = open(filename, "w")
329 signed = os.popen('/usr/bin/xmlsec1 --sign --node-id "%s" --privkey-pem %s,%s %s' \
330 % ('ref1', self.issuer_privkey, self.issuer_gid, filename)).read()
335 def getTextNode(self, element, subele):
336 sub = element.getElementsByTagName(subele)[0]
337 return sub.childNodes[0].nodeValue
340 # Retrieve the attributes of the credential from the XML.
341 # This is automatically caleld by the various get_* methods of
342 # this class and should not need to be called explicitly.
345 p_doc = xml.dom.minidom.parseString(self.xml)
346 p_signed_cred = p_doc.getElementsByTagName("signed-credential")[0]
347 p_cred = p_signed_cred.getElementsByTagName("credential")[0]
348 p_signatures = p_signed_cred.getElementsByTagName("signatures")[0]
349 p_sigs = p_signatures.getElementsByTagName("Signature")
351 self.lifeTime = self.getTextNode(p_cred, "expires")
352 self.gidCaller = GID(string=self.getTextNode(p_cred, "owner_gid"))
353 self.gidObject = GID(string=self.getTextNode(p_cred, "target_gid"))
354 privs = p_cred.getElementsByTagName("priveleges")[0]
357 for priv in privs.getElementsByTagName("privelege"):
358 sz_privs += self.getTextNode(priv, "name")
360 delegates.append(self.getTextNode(priv, "can_delegate"))
364 if "false" not in delegates:
367 # Make the rights list
368 sz_privs.rstrip(", ")
369 self.priveleges = RightList(string=sz_privs)
374 ## # Retrieve the attributes of the credential from the alt-subject-name field
375 ## # of the X509 certificate. This is automatically done by the various
376 ## # get_* methods of this class and should not need to be called explicitly.
379 ## data = self.get_data().lstrip('URI:http://')
382 ## dict = xmlrpclib.loads(data)[0][0]
386 ## self.lifeTime = dict.get("lifeTime", None)
387 ## self.delegate = dict.get("delegate", None)
389 ## privStr = dict.get("privileges", None)
391 ## self.privileges = RightList(string = privStr)
393 ## self.privileges = None
395 ## gidCallerStr = dict.get("gidCaller", None)
397 ## self.gidCaller = GID(string=gidCallerStr)
399 ## self.gidCaller = None
401 ## gidObjectStr = dict.get("gidObject", None)
403 ## self.gidObject = GID(string=gidObjectStr)
405 ## self.gidObject = None
409 # Verify for the initial credential:
410 # 1. That the signature is valid
411 # 2. That the xml signer's certificate matches the object's certificate
412 # 3. That the urns match those in the gids
414 # Verify for the delegated credentials:
415 # 1. That the signature is valid
418 # 3. That the object's certificate stays the s
419 # 2. That the GID of the
421 #def verify(self, trusted_certs = None):
426 # Verify that a chain of credentials is valid (see cert.py:verify). In
427 # addition to the checks for ordinary certificates, verification also
428 # ensures that the delegate bit was set by each parent in the chain. If
429 # a delegate bit was not set, then an exception is thrown.
431 # Each credential must be a subset of the rights of the parent.
433 def verify_chain(self, trusted_certs = None):
434 # do the normal certificate verification stuff
435 Certificate.verify_chain(self, trusted_certs)
438 # make sure the parent delegated rights to the child
439 if not self.parent.get_delegate():
440 raise MissingDelegateBit(self.parent.get_subject())
442 # make sure the rights given to the child are a subset of the
444 if not self.parent.get_privileges().is_superset(self.get_privileges()):
445 raise ChildRightsNotSubsetOfParent(self.get_subject()
446 + " " + self.parent.get_privileges().save_to_string()
447 + " " + self.get_privileges().save_to_string())
452 # Dump the contents of a credential to stdout in human-readable format
454 # @param dump_parents If true, also dump the parent certificates
456 def dump(self, dump_parents=False):
457 print "CREDENTIAL", self.get_subject()
459 print " privs:", self.get_privileges().save_to_string()
462 gidCaller = self.get_gid_caller()
464 gidCaller.dump(8, dump_parents)
467 gidObject = self.get_gid_object()
469 gidObject.dump(8, dump_parents)
471 print " delegate:", self.get_delegate()
473 if self.parent and dump_parents:
475 self.parent.dump(dump_parents)