From: Josh Karlin Date: Wed, 7 Apr 2010 18:37:11 +0000 (+0000) Subject: about to overhaul signatures and signed-creds X-Git-Tag: geni-apiv1-totrunk~78 X-Git-Url: http://git.onelab.eu/?a=commitdiff_plain;h=dbac5373f763370b39b2b60d3722998b1cfed96d;p=sfa.git about to overhaul signatures and signed-creds --- diff --git a/sfa/managers/registry_manager_pl.py b/sfa/managers/registry_manager_pl.py index 68690dd5..d2ed29bf 100644 --- a/sfa/managers/registry_manager_pl.py +++ b/sfa/managers/registry_manager_pl.py @@ -60,7 +60,8 @@ def get_credential(api, xrn, type, is_self=False): new_cred.set_privileges(rights) new_cred.set_delegate(True) auth_kind = "authority,ma,sa" - new_cred.set_parent(api.auth.hierarchy.get_auth_cred(auth_hrn, kind=auth_kind)) + # Parent not necessary, verify with certs + #new_cred.set_parent(api.auth.hierarchy.get_auth_cred(auth_hrn, kind=auth_kind)) new_cred.encode() new_cred.sign() diff --git a/sfa/server/modpythonapi/AuthenticatedApi.py b/sfa/server/modpythonapi/AuthenticatedApi.py index c909346b..f87d4bb3 100755 --- a/sfa/server/modpythonapi/AuthenticatedApi.py +++ b/sfa/server/modpythonapi/AuthenticatedApi.py @@ -18,6 +18,7 @@ class AuthenticatedApi(BaseApi): BaseApi.__init__(self, encoding) if trustedRootsDir: self.trusted_cert_list = TrustedRootList(trustedRootsDir).get_list() + self.trusted_cert_file_list = TrustedRootList(trustedRootsDir).get_file_list() else: self.trusted_cert_list = None @@ -40,7 +41,7 @@ class AuthenticatedApi(BaseApi): def validateCred(self, cred): if self.trusted_cert_list: - cred.verify_chain(self.trusted_cert_list) + cred.verify(self.trusted_cert_file_list) caller_gid = cred.get_gid_caller() object_gid = cred.get_gid_object() if caller_gid: diff --git a/sfa/trust/auth.py b/sfa/trust/auth.py index 3bcc0971..51b7edc1 100644 --- a/sfa/trust/auth.py +++ b/sfa/trust/auth.py @@ -28,6 +28,7 @@ class Auth: if not config: self.config = Config() self.trusted_cert_list = TrustedRootList(self.config.get_trustedroots_dir()).get_list() + self.trusted_cert_file_list = TrustedRootList(self.config.get_trustedroots_dir()).get_file_list() def check(self, cred, operation): @@ -56,7 +57,7 @@ class Auth: raise InsufficientRights(operation) if self.trusted_cert_list: - self.client_cred.verify_chain(self.trusted_cert_list) + self.client_cred.verify(self.trusted_cert_file_list) if self.client_gid: self.client_gid.verify_chain(self.trusted_cert_list) if self.object_gid: @@ -98,7 +99,7 @@ class Auth: def validateCred(self, cred): if self.trusted_cert_list: - cred.verify_chain(self.trusted_cert_list) + cred.verify(self.trusted_cert_file_list) caller_gid = cred.get_gid_caller() object_gid = cred.get_gid_object() if caller_gid: diff --git a/sfa/trust/credential.py b/sfa/trust/credential.py index 608acfa4..e6245dbf 100644 --- a/sfa/trust/credential.py +++ b/sfa/trust/credential.py @@ -32,6 +32,7 @@ from sfa.util.sfalogging import logger # . remove verify_chain # . make delegation per privilege instead of global # . make privs match between PG and PL +# . what about tickets? do they need to be redone to be like credentials? # . Need to test signature_template = \ @@ -62,6 +63,12 @@ signature_template = \ + +#class Signature(object): + +#class Signed_Credential(object): + + ## # Credential is a tuple: # (GIDCaller, GIDObject, Expiration (in UTC time), Privileges, DelegateBit) @@ -153,7 +160,6 @@ class Credential(object): if not cred.xml: cred.encode() - logger.info("Setting parent for %s to %s" % (self.gidCaller.get_urn(), cred.gidCaller.get_urn())) doc = parseString(cred.xml) signed = doc.getElementsByTagName("signed-credential")[0] cred = signed.getElementsByTagName("credential")[0] @@ -338,7 +344,7 @@ class Credential(object): # Get the finished product self.xml = doc.toxml() - #print doc.toprettyxml() + #print doc.toxml() #self.sign() @@ -378,25 +384,15 @@ class Credential(object): return [] refs = [] - - next_xml = self.parent_xml - - logger.info("instance--") - while next_xml: - doc = parseString(next_xml) - cred = doc.getElementsByTagName("credential")[0] - refid = cred.getAttribute("xml:id") - logger.info("Found refid %s" % refid) - assert(refid not in refs) - refs.append(refid) - - parent = doc.getElementsByTagName("parent") - if len(parent) > 0: - next_xml = parent[0].getElementsByTagName("credential")[0].toxml() + next_cred = Credential(string=self.parent_xml) + while next_cred: + refs.append(next_cred.get_refid()) + if next_cred.parent_xml: + next_cred = Credential(string=next_cred.parent_xml) else: - next_xml = None - + next_cred = None + # Find a unique refid for this credential rid = self.get_refid() while rid in refs: @@ -454,13 +450,21 @@ class Credential(object): def decode(self): doc = parseString(self.xml) - signed_cred = doc.getElementsByTagName("signed-credential")[0] - cred = signed_cred.getElementsByTagName("credential")[0] - signatures = signed_cred.getElementsByTagName("signatures")[0] - sigs = signatures.getElementsByTagName("Signature") + sigs = None + signed_cred = doc.getElementsByTagName("signed-credential") + + # Is this a signed-cred or just a cred? + if len(signed_cred) > 0: + cred = signed_cred[0].getElementsByTagName("credential")[0] + signatures = signed_cred[0].getElementsByTagName("signatures") + if len(signatures) > 0: + sigs = signatures[0].getElementsByTagName("Signature") + else: + cred = doc.getElementsByTagName("credential")[0] + - self.max_refid = int(cred.getAttribute("xml:id")[3:]) + self.set_refid(cred.getAttribute("xml:id")) sz_expires = self.getTextNode(cred, "expires") if sz_expires != '': self.expiration = datetime.datetime.strptime(sz_expires, '%Y-%m-%dT%H:%M:%S') @@ -496,30 +500,101 @@ class Credential(object): self.signatures.append(sig.toxml()) ## - # For a simple credential (no delegation) verify.. - # . That the signature is valid for the credential's xml:id - # . That the signer's pub key matches the pub key of the target (object) - # . That the target/owner urns match those in the gids + # Verify that: + # . All of the signatures are valid and that the issuers trace back + # to trusted roots (performed by xmlsec1) + # . That the issuer of the credential is the authority in the target's urn + # . In the case of a delegated credential, this must be true of the root + # + # -- For Delegates (credentials with parents) + # . The privileges must be a subset of the parent credentials + # . The privileges must have "can_delegate" set for each delegated privilege + # . The target gid must be the same between child and parents + # . The expiry time on the child must be no later than the parent + # . The signer of the child must be the owner of the parent + # + # -- Verify does *NOT* + # . ensure that an xmlrpc client's gid matches a credential gid, that + # must be done elsewhere # # @param trusted_certs: The certificates of trusted CA certificates def verify(self, trusted_certs): + logger.info("verifying cert") if not self.xml: self.decode() - # Verify the sigatures + # Verify the signatures filename = self.save_to_random_tmp_file() cert_args = " ".join(['--trusted-pem %s' % x for x in trusted_certs]) - ref = "Sig_%s" % self.get_refid() - verified = os.popen('/usr/bin/xmlsec1 --verify --node-id "%s" %s %s 2>&1' \ - % (ref, cert_args, filename)).read() + + refs = [] + refs.append("Sig_%s" % self.get_refid()) - if not verified.strip().startswith("OK"): - raise CredentialNotVerifiable("xmlsec1 error: " + verified) + parentRefs = self.updateRefID() + for ref in parentRefs: + refs.append("Sig_%s" % ref) + + for ref in refs: + logger.info('/usr/bin/xmlsec1 --verify --node-id "%s" %s %s 2>&1' \ + % (ref, cert_args, filename)) + verified = os.popen('/usr/bin/xmlsec1 --verify --node-id "%s" %s %s 2>&1' \ + % (ref, cert_args, filename)).read() + if not verified.strip().startswith("OK"): + raise CredentialNotVerifiable("xmlsec1 error: " + verified) os.remove(filename) + # Verify the parents (delegation) + #if self.parent_xml: + # self.verify_parent(Credential(string=self.parent_xml)) + + # Make sure the issuer is the target's authority + self.verify_issuer() + + + + ## + # Make sure the issuer of this credential is the target's authority + def verify_issuer(self): + target_authority = get_authority(self.get_gid_object().get_hrn()) + + # Find the root credential's refid + cur_cred = self + root_refid = None + while cur_cred: + if cur_cred.parent_xml: + cur_cred = Credential(string=cur_cred.parent_xml) + else: + root_refid = "Sig_%s" % cur_cred.get_refid() + cur_cred = None + + # Find the signature for the root credential + root_issuer = None + for sig in self.signatures: + doc = parseString(sig) + esig = doc.getElementsByTagName("Signature")[0] + ref = esig.getAttribute("xml:id") + if ref.lower() == root_refid.lower(): + # Found the right signature, look for the issuer + keyinfo = esig.getElementsByTagName("X509Data")[0] + root_issuer = self.getTextNode(keyinfo, "X509SubjectName") + root_issuer = root_issuer.strip('CN=') + + # Ensure that the signer of the root credential is the target_authority + root_issuer = hrn_to_urn(root_issuer, 'authority') + target_authority = hrn_to_urn(target_authority, 'authority') + + if root_issuer != target_authority: + raise CredentialNotVerifiable("issuer (%s) != authority of target (%s)" \ + % (root_issuer, target_authority)) + + + def verify_parent(self, parent_cred): + if parent_cred.parent_xml: + parent_cred.verify_parent(Credential(string=parent_cred.parent_xml)) + ## # Verify that a chain of credentials is valid (see cert.py:verify). In # addition to the checks for ordinary certificates, verification also diff --git a/sfa/trust/hierarchy.py b/sfa/trust/hierarchy.py index c4b9054c..609a48a9 100644 --- a/sfa/trust/hierarchy.py +++ b/sfa/trust/hierarchy.py @@ -314,6 +314,8 @@ class Hierarchy: # we need the parent's private key in order to sign this GID parent_auth_info = self.get_auth_info(parent_hrn) cred.set_issuer_keys(parent_auth_info.get_privkey_filename(), parent_auth_info.get_gid_filename()) + + cred.set_parent(self.get_auth_cred(parent_hrn, kind)) cred.encode() diff --git a/sfa/trust/trustedroot.py b/sfa/trust/trustedroot.py index 90dfe462..2d4b89ef 100644 --- a/sfa/trust/trustedroot.py +++ b/sfa/trust/trustedroot.py @@ -34,3 +34,13 @@ class TrustedRootList: return gid_list + def get_file_list(self): + gid_file_list = [] + + file_list = os.listdir(self.basedir) + for gid_file in file_list: + fn = os.path.join(self.basedir, gid_file) + if os.path.isfile(fn): + gid_file_list.append(fn) + + return gid_file_list