def utcparse(input): """ Translate a string into a time using dateutil.parser.parse but make sure it's in UTC time and strip the timezone, so that it's compatible with normal datetime.datetime objects. For safety this can also handle inputs that are either timestamps, or datetimes """ # prepare the input for the checks below by # casting strings ('1327098335') to ints if isinstance(input, StringTypes): try: input = int(input) except ValueError: pass if isinstance (input, datetime.datetime): logger.warn ("argument to utcparse already a datetime - doing nothing") return input elif isinstance (input, StringTypes): t = dateutil.parser.parse(input) if t.utcoffset() is not None: t = t.utcoffset() + t.replace(tzinfo=None) return t elif isinstance (input, (int,float,long)): return datetime.datetime.fromtimestamp(input) else: logger.error("Unexpected type in utcparse [%s]"%type(input))
def verify_chain(self, trusted_certs = None): # Verify a chain of certificates. Each certificate must be signed by # the public key contained in it's parent. The chain is recursed # until a certificate is found that is signed by a trusted root. # verify expiration time if self.cert.has_expired(): logger.debug("verify_chain: NO, Certificate %s has expired" % self.get_printable_subject()) raise CertExpired(self.get_printable_subject(), "client cert") # if this cert is signed by a trusted_cert, then we are set for trusted_cert in trusted_certs: if self.is_signed_by_cert(trusted_cert): # verify expiration of trusted_cert ? if not trusted_cert.cert.has_expired(): logger.debug("verify_chain: YES. Cert %s signed by trusted cert %s"%( self.get_printable_subject(), trusted_cert.get_printable_subject())) return trusted_cert else: logger.debug("verify_chain: NO. Cert %s is signed by trusted_cert %s, but that signer is expired..."%( self.get_printable_subject(),trusted_cert.get_printable_subject())) raise CertExpired(self.get_printable_subject()," signer trusted_cert %s"%trusted_cert.get_printable_subject()) # if there is no parent, then no way to verify the chain if not self.parent: logger.debug("verify_chain: NO. %s has no parent and issuer %s is not in %d trusted roots"%(self.get_printable_subject(), self.get_issuer(), len(trusted_certs))) raise CertMissingParent(self.get_printable_subject() + ": Issuer %s is not one of the %d trusted roots, and cert has no parent." % (self.get_issuer(), len(trusted_certs))) # if it wasn't signed by the parent... if not self.is_signed_by_cert(self.parent): logger.debug("verify_chain: NO. %s is not signed by parent %s, but by %s"%\ (self.get_printable_subject(), self.parent.get_printable_subject(), self.get_issuer())) raise CertNotSignedByParent("%s: Parent %s, issuer %s"\ % (self.get_printable_subject(), self.parent.get_printable_subject(), self.get_issuer())) # Confirm that the parent is a CA. Only CAs can be trusted as # signers. # Note that trusted roots are not parents, so don't need to be # CAs. # Ugly - cert objects aren't parsed so we need to read the # extension and hope there are no other basicConstraints if not self.parent.isCA and not (self.parent.get_extension('basicConstraints') == 'CA:TRUE'): logger.warn("verify_chain: cert %s's parent %s is not a CA" % \ (self.get_printable_subject(), self.parent.get_printable_subject())) raise CertNotSignedByParent("%s: Parent %s not a CA" % (self.get_printable_subject(), self.parent.get_printable_subject())) # if the parent isn't verified... logger.debug("verify_chain: .. %s, -> verifying parent %s"%\ (self.get_printable_subject(),self.parent.get_printable_subject())) self.parent.verify_chain(trusted_certs) return
def sign(self): if not self.issuer_privkey: logger.warn("Cannot sign credential (no private key)") return if not self.issuer_gid: logger.warn("Cannot sign credential (no issuer gid)") return doc = parseString(self.get_xml()) sigs = doc.getElementsByTagName("signatures")[0] # Create the signature template to be signed signature = Signature() signature.set_refid(self.get_refid()) sdoc = parseString(signature.get_xml()) sig_ele = doc.importNode(sdoc.getElementsByTagName("Signature")[0], True) sigs.appendChild(sig_ele) self.xml = doc.toxml() # Split the issuer GID into multiple certificates if it's a chain chain = GID(filename=self.issuer_gid) gid_files = [] while chain: gid_files.append(chain.save_to_random_tmp_file(False)) if chain.get_parent(): chain = chain.get_parent() else: chain = None # Call out to xmlsec1 to sign it ref = 'Sig_%s' % self.get_refid() filename = self.save_to_random_tmp_file() signed = os.popen('%s --sign --node-id "%s" --privkey-pem %s,%s %s' \ % (self.xmlsec_path, ref, self.issuer_privkey, ",".join(gid_files), filename)).read() os.remove(filename) for gid_file in gid_files: os.remove(gid_file) self.xml = signed # This is no longer a legacy credential if self.legacy: self.legacy = None # Update signatures self.decode()
def sign(self): if not self.issuer_privkey: logger.warn("Cannot sign credential (no private key)") return if not self.issuer_gid: logger.warn("Cannot sign credential (no issuer gid)") return doc = parseString(self.get_xml()) sigs = doc.getElementsByTagName("signatures")[0] # Create the signature template to be signed signature = Signature() signature.set_refid(self.get_refid()) sdoc = parseString(signature.get_xml()) sig_ele = doc.importNode( sdoc.getElementsByTagName("Signature")[0], True) sigs.appendChild(sig_ele) self.xml = doc.toxml() # Split the issuer GID into multiple certificates if it's a chain chain = GID(filename=self.issuer_gid) gid_files = [] while chain: gid_files.append(chain.save_to_random_tmp_file(False)) if chain.get_parent(): chain = chain.get_parent() else: chain = None # Call out to xmlsec1 to sign it ref = 'Sig_%s' % self.get_refid() filename = self.save_to_random_tmp_file() signed = os.popen('%s --sign --node-id "%s" --privkey-pem %s,%s %s' \ % (self.xmlsec_path, ref, self.issuer_privkey, ",".join(gid_files), filename)).read() os.remove(filename) for gid_file in gid_files: os.remove(gid_file) self.xml = signed # This is no longer a legacy credential if self.legacy: self.legacy = None # Update signatures self.decode()
def __init__(self, create=False, subject=None, string=None, filename=None): self.gidCaller = None self.gidObject = None self.expiration = None self.privileges = None self.issuer_privkey = None self.issuer_gid = None self.issuer_pubkey = None self.parent = None self.signature = None self.xml = None self.refid = None self.legacy = None # Check if this is a legacy credential, translate it if so if string or filename: if string: str = string elif filename: str = file(filename).read() if str.strip().startswith("-----"): self.legacy = CredentialLegacy(False, string=str) self.translate_legacy(str) else: self.xml = str self.decode() # Find an xmlsec1 path self.xmlsec_path = '' paths = [ '/usr/bin', '/usr/local/bin', '/bin', '/opt/bin', '/opt/local/bin' ] for path in paths: if os.path.isfile(path + '/' + 'xmlsec1'): self.xmlsec_path = path + '/' + 'xmlsec1' break if not self.xmlsec_path: logger.warn( "Could not locate binary for xmlsec1 - SFA will be unable to sign stuff !!" )
def __init__(self, create=False, subject=None, string=None, filename=None): self.gidCaller = None self.gidObject = None self.expiration = None self.privileges = None self.issuer_privkey = None self.issuer_gid = None self.issuer_pubkey = None self.parent = None self.signature = None self.xml = None self.refid = None self.legacy = None # Check if this is a legacy credential, translate it if so if string or filename: if string: str = string elif filename: str = file(filename).read() if str.strip().startswith("-----"): self.legacy = CredentialLegacy(False,string=str) self.translate_legacy(str) else: self.xml = str self.decode() # Find an xmlsec1 path self.xmlsec_path = '' paths = ['/usr/bin','/usr/local/bin','/bin','/opt/bin','/opt/local/bin'] for path in paths: if os.path.isfile(path + '/' + 'xmlsec1'): self.xmlsec_path = path + '/' + 'xmlsec1' break if not self.xmlsec_path: logger.warn("Could not locate binary for xmlsec1 - SFA will be unable to sign stuff !!")
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.planet-lab.org/resources/sfa/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", "privilege") append_sub(doc, cred, "serial", "8") append_sub(doc, cred, "owner_gid", self.gidCaller.save_to_string()) append_sub(doc, cred, "owner_urn", self.gidCaller.get_urn()) append_sub(doc, cred, "target_gid", self.gidObject.save_to_string()) append_sub(doc, cred, "target_urn", self.gidObject.get_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) append_sub(doc, cred, "expires", self.expiration.isoformat()) privileges = doc.createElement("privileges") cred.appendChild(privileges) if self.privileges: rights = self.get_privileges() for right in rights.rights: priv = doc.createElement("privilege") append_sub(doc, priv, "name", right.kind) append_sub(doc, priv, "can_delegate", str(right.delegate).lower()) privileges.appendChild(priv) # Add the parent credential if it exists if self.parent: sdoc = parseString(self.parent.get_xml()) # If the root node is a signed-credential (it should be), then # get all its attributes and attach those to our signed_cred # node. # Specifically, PG and PLadd attributes for namespaces (which is reasonable), # and we need to include those again here or else their signature # no longer matches on the credential. # We expect three of these, but here we copy them all: # signed_cred.setAttribute("xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance") # and from PG (PL is equivalent, as shown above): # 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") # HOWEVER! # PL now also declares these, with different URLs, so # the code notices those attributes already existed with # different values, and complains. # This happens regularly on delegation now that PG and # PL both declare the namespace with different URLs. # If the content ever differs this is a problem, # but for now it works - different URLs (values in the attributes) # but the same actual schema, so using the PG schema # on delegated-to-PL credentials works fine. # Note: you could also not copy attributes # which already exist. It appears that both PG and PL # will actually validate a slicecred with a parent # signed using PG namespaces and a child signed with PL # namespaces over the whole thing. But I don't know # if that is a bug in xmlsec1, an accident since # the contents of the schemas are the same, # or something else, but it seems odd. And this works. parentRoot = sdoc.documentElement if parentRoot.tagName == "signed-credential" and parentRoot.hasAttributes( ): for attrIx in range(0, parentRoot.attributes.length): attr = parentRoot.attributes.item(attrIx) # returns the old attribute of same name that was # on the credential # Below throws InUse exception if we forgot to clone the attribute first oldAttr = signed_cred.setAttributeNode( attr.cloneNode(True)) if oldAttr and oldAttr.value != attr.value: msg = "Delegating cred from owner %s to %s over %s:\n - Replaced attribute %s value '%s' with '%s'" % ( self.parent.gidCaller.get_urn(), self.gidCaller.get_urn(), self.gidObject.get_urn(), oldAttr.name, oldAttr.value, attr.value) logger.warn(msg) #raise CredentialNotVerifiable("Can't encode new valid delegated credential: %s" % msg) p_cred = doc.importNode( sdoc.getElementsByTagName("credential")[0], True) p = doc.createElement("parent") p.appendChild(p_cred) cred.appendChild(p) # done handling parent credential # Create the <signatures> tag signatures = doc.createElement("signatures") signed_cred.appendChild(signatures) # Add any parent signatures if self.parent: for cur_cred in self.get_credential_list()[1:]: sdoc = parseString(cur_cred.get_signature().get_xml()) ele = doc.importNode( sdoc.getElementsByTagName("Signature")[0], True) signatures.appendChild(ele) # Get the finished product self.xml = doc.toxml()
def verify_chain(self, trusted_certs=None): # Verify a chain of certificates. Each certificate must be signed by # the public key contained in it's parent. The chain is recursed # until a certificate is found that is signed by a trusted root. # verify expiration time if self.cert.has_expired(): logger.debug("verify_chain: NO, Certificate %s has expired" % self.get_printable_subject()) raise CertExpired(self.get_printable_subject(), "client cert") # if this cert is signed by a trusted_cert, then we are set for trusted_cert in trusted_certs: if self.is_signed_by_cert(trusted_cert): # verify expiration of trusted_cert ? if not trusted_cert.cert.has_expired(): logger.debug( "verify_chain: YES. Cert %s signed by trusted cert %s" % (self.get_printable_subject(), trusted_cert.get_printable_subject())) return trusted_cert else: logger.debug( "verify_chain: NO. Cert %s is signed by trusted_cert %s, but that signer is expired..." % (self.get_printable_subject(), trusted_cert.get_printable_subject())) raise CertExpired( self.get_printable_subject(), " signer trusted_cert %s" % trusted_cert.get_printable_subject()) # if there is no parent, then no way to verify the chain if not self.parent: logger.debug( "verify_chain: NO. %s has no parent and issuer %s is not in %d trusted roots" % (self.get_printable_subject(), self.get_issuer(), len(trusted_certs))) raise CertMissingParent( self.get_printable_subject() + ": Issuer %s is not one of the %d trusted roots, and cert has no parent." % (self.get_issuer(), len(trusted_certs))) # if it wasn't signed by the parent... if not self.is_signed_by_cert(self.parent): logger.debug("verify_chain: NO. %s is not signed by parent %s, but by %s"%\ (self.get_printable_subject(), self.parent.get_printable_subject(), self.get_issuer())) raise CertNotSignedByParent("%s: Parent %s, issuer %s"\ % (self.get_printable_subject(), self.parent.get_printable_subject(), self.get_issuer())) # Confirm that the parent is a CA. Only CAs can be trusted as # signers. # Note that trusted roots are not parents, so don't need to be # CAs. # Ugly - cert objects aren't parsed so we need to read the # extension and hope there are no other basicConstraints if not self.parent.isCA and not ( self.parent.get_extension('basicConstraints') == 'CA:TRUE'): logger.warn("verify_chain: cert %s's parent %s is not a CA" % \ (self.get_printable_subject(), self.parent.get_printable_subject())) raise CertNotSignedByParent("%s: Parent %s not a CA" % (self.get_printable_subject(), self.parent.get_printable_subject())) # if the parent isn't verified... logger.debug("verify_chain: .. %s, -> verifying parent %s"%\ (self.get_printable_subject(),self.parent.get_printable_subject())) self.parent.verify_chain(trusted_certs) return
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.planet-lab.org/resources/sfa/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", "privilege") append_sub(doc, cred, "serial", "8") append_sub(doc, cred, "owner_gid", self.gidCaller.save_to_string()) append_sub(doc, cred, "owner_urn", self.gidCaller.get_urn()) append_sub(doc, cred, "target_gid", self.gidObject.save_to_string()) append_sub(doc, cred, "target_urn", self.gidObject.get_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) append_sub(doc, cred, "expires", self.expiration.isoformat()) privileges = doc.createElement("privileges") cred.appendChild(privileges) if self.privileges: rights = self.get_privileges() for right in rights.rights: priv = doc.createElement("privilege") append_sub(doc, priv, "name", right.kind) append_sub(doc, priv, "can_delegate", str(right.delegate).lower()) privileges.appendChild(priv) # Add the parent credential if it exists if self.parent: sdoc = parseString(self.parent.get_xml()) # If the root node is a signed-credential (it should be), then # get all its attributes and attach those to our signed_cred # node. # Specifically, PG and PLadd attributes for namespaces (which is reasonable), # and we need to include those again here or else their signature # no longer matches on the credential. # We expect three of these, but here we copy them all: # signed_cred.setAttribute("xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance") # and from PG (PL is equivalent, as shown above): # 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") # HOWEVER! # PL now also declares these, with different URLs, so # the code notices those attributes already existed with # different values, and complains. # This happens regularly on delegation now that PG and # PL both declare the namespace with different URLs. # If the content ever differs this is a problem, # but for now it works - different URLs (values in the attributes) # but the same actual schema, so using the PG schema # on delegated-to-PL credentials works fine. # Note: you could also not copy attributes # which already exist. It appears that both PG and PL # will actually validate a slicecred with a parent # signed using PG namespaces and a child signed with PL # namespaces over the whole thing. But I don't know # if that is a bug in xmlsec1, an accident since # the contents of the schemas are the same, # or something else, but it seems odd. And this works. parentRoot = sdoc.documentElement if parentRoot.tagName == "signed-credential" and parentRoot.hasAttributes(): for attrIx in range(0, parentRoot.attributes.length): attr = parentRoot.attributes.item(attrIx) # returns the old attribute of same name that was # on the credential # Below throws InUse exception if we forgot to clone the attribute first oldAttr = signed_cred.setAttributeNode(attr.cloneNode(True)) if oldAttr and oldAttr.value != attr.value: msg = "Delegating cred from owner %s to %s over %s:\n - Replaced attribute %s value '%s' with '%s'" % (self.parent.gidCaller.get_urn(), self.gidCaller.get_urn(), self.gidObject.get_urn(), oldAttr.name, oldAttr.value, attr.value) logger.warn(msg) #raise CredentialNotVerifiable("Can't encode new valid delegated credential: %s" % msg) p_cred = doc.importNode(sdoc.getElementsByTagName("credential")[0], True) p = doc.createElement("parent") p.appendChild(p_cred) cred.appendChild(p) # done handling parent credential # Create the <signatures> tag signatures = doc.createElement("signatures") signed_cred.appendChild(signatures) # Add any parent signatures if self.parent: for cur_cred in self.get_credential_list()[1:]: sdoc = parseString(cur_cred.get_signature().get_xml()) ele = doc.importNode(sdoc.getElementsByTagName("Signature")[0], True) signatures.appendChild(ele) # Get the finished product self.xml = doc.toxml()