def add_enveloped_signature(t, c14n_method=config.default_c14n_alg, digest_alg=config.default_digest_alg, signature_alg=config.default_signature_alg, transforms=None, reference_uri='', pos=0): if transforms is None: transforms = (constants.TRANSFORM_ENVELOPED_SIGNATURE, constants.TRANSFORM_C14N_EXCLUSIVE_WITH_COMMENTS) tmpl = _enveloped_signature_template(c14n_method, digest_alg, transforms, reference_uri, signature_alg) if pos == -1: root_elt(t).append(tmpl) else: root_elt(t).insert(pos, tmpl)
def _c14n(t, exclusive, with_comments, inclusive_prefix_list=None, schema=None): """ Perform XML canonicalization (c14n) on an lxml.etree. :param t: XML as lxml.etree :param exclusive: boolean :param with_comments: boolean, keep comments or not :param inclusive_prefix_list: List of namespaces to include (?) :returns: XML as string (utf8) """ doc = t if root_elt(doc).getparent() is not None: xml_str = etree_to_string(doc) doc = parse_xml(xml_str, remove_whitespace=config.c14n_strip_ws, remove_comments=not with_comments, schema=schema) del xml_str buf = six.text_type( etree.tostring(doc, method='c14n', exclusive=exclusive, with_comments=with_comments, inclusive_ns_prefixes=inclusive_prefix_list), 'utf-8') #u = unescape_xml_entities(buf.decode("utf8", 'strict')).encode("utf8").strip() assert buf[0] == '<' assert buf[-1] == '>' #if u[0] != '<': # raise XMLSigException("C14N buffer doesn't start with '<'") #if u[-1] != '>': # raise XMLSigException("C14N buffer doesn't end with '>'") #return u return buf
def _verify(t, keyspec, sig_path=".//{%s}Signature" % NS['ds']): """ Verify the signature(s) in an XML document. Throws an XMLSigException on any non-matching signatures. :param t: XML as lxml.etree :param keyspec: X.509 cert filename, string with fingerprint or X.509 cert as string :returns: True if signature(s) validated, False if there were no signatures """ if config.debug_write_to_files: with open("/tmp/foo-sig.xml", "w") as fd: fd.write(etree.tostring(root_elt(t))) # Load and parse certificate, unless keyspec is a fingerprint. cert = _load_keyspec(keyspec) validated = [] for sig in t.findall(sig_path): sv = sig.findtext(".//{%s}SignatureValue" % NS['ds']) if sv is None: raise XMLSigException("No SignatureValue") this_f_public = None this_keysize = None if cert is None: # keyspec is fingerprint - look for matching certificate in XML this_cert = _load_keyspec(keyspec, signature_element=sig) if this_cert is None: raise XMLSigException( "Could not find certificate to validate signature") this_f_public = this_cert['f_public'] this_keysize = this_cert['keysize'] else: # Non-fingerprint keyspec, use pre-parsed values this_cert = cert this_f_public = cert['f_public'] this_keysize = cert['keysize'] logging.debug("key size: %d bits" % this_cert['keysize']) if this_cert is None: raise XMLSigException( "Could not find certificate to validate signature") si = sig.find(".//{%s}SignedInfo" % NS['ds']) cm_alg = _cm_alg(si) digest_alg = _sig_digest(si) validated_objects = _process_references(t, sig, sig_path) b_digest = _create_signature_digest(si, cm_alg, digest_alg) actual = _signed_value(b_digest, this_keysize, True, digest_alg) expected = this_f_public(b64d(sv)) if expected != actual: raise XMLSigException("Signature validation failed") validated.extend(validated_objects) return validated
def _process_references(t, sig, return_verified=True, sig_path=".//{%s}Signature" % NS['ds']): """ :returns: hash algorithm as string """ verified_objects = [] for ref in sig.findall(".//{%s}Reference" % NS['ds']): obj = None hash_alg = None uri = ref.get('URI', None) if uri is None or uri == '#' or uri == '': ct = _remove_child_comments(_implicit_same_document(t, sig)) obj = root_elt(ct) elif uri.startswith('#'): ct = copy.deepcopy(t) obj = _remove_child_comments(_get_by_id(ct, uri[1:])) else: raise XMLSigException("Unknown reference %s" % uri) if obj is None: raise XMLSigException("Unable to dereference Reference URI='%s'" % uri) if return_verified: verified_objects.append(copy.deepcopy(obj)) if config.debug_write_to_files: with open("/tmp/foo-pre-transform.xml", "w") as fd: fd.write(etree.tostring(obj)) for tr in ref.findall(".//{%s}Transform" % NS['ds']): logging.debug("transform: %s" % _alg(tr)) obj = _transform(_alg(tr), obj, tr=tr, sig_path=sig_path) if not isinstance(obj, bytes): if config.debug_write_to_files: with open("/tmp/foo-pre-serialize.xml", "w") as fd: fd.write(etree.tostring(obj)) obj = _transform(constants.TRANSFORM_C14N_INCLUSIVE, obj) if config.debug_write_to_files: with open("/tmp/foo-obj.xml", "w") as fd: fd.write(obj) dm = ref.find(".//{%s}DigestMethod" % NS['ds']) if dm is None: raise XMLSigException("Unable to find DigestMethod") hash_alg = (_alg(dm).split("#"))[1] logging.debug("using hash algorithm %s" % hash_alg) digest = _digest(obj, hash_alg) logging.debug("using digest %s (%s) for ref %s" % (digest, hash_alg, uri)) dv = ref.find(".//{%s}DigestValue" % NS['ds']) logging.debug(etree.tostring(dv)) dv.text = digest if return_verified: return verified_objects else: return None
def _verify(t, keyspec, sig_path=".//{%s}Signature" % NS['ds']): """ Verify the signature(s) in an XML document. Throws an XMLSigException on any non-matching signatures. :param t: XML as lxml.etree :param keyspec: X.509 cert filename, string with fingerprint or X.509 cert as string :returns: True if signature(s) validated, False if there were no signatures """ if config.debug_write_to_files: with open("/tmp/foo-sig.xml", "w") as fd: fd.write(etree.tostring(root_elt(t))) # Load and parse certificate, unless keyspec is a fingerprint. cert = _load_keyspec(keyspec) validated = [] for sig in t.findall(sig_path): sv = sig.findtext(".//{%s}SignatureValue" % NS['ds']) if sv is None: raise XMLSigException("No SignatureValue") this_f_public = None this_keysize = None if cert is None: # keyspec is fingerprint - look for matching certificate in XML this_cert = _load_keyspec(keyspec, signature_element=sig) if this_cert is None: raise XMLSigException("Could not find certificate to validate signature") this_f_public = this_cert['f_public'] this_keysize = this_cert['keysize'] else: # Non-fingerprint keyspec, use pre-parsed values this_cert = cert this_f_public = cert['f_public'] this_keysize = cert['keysize'] logging.debug("key size: %d bits" % this_cert['keysize']) if this_cert is None: raise XMLSigException("Could not find certificate to validate signature") si = sig.find(".//{%s}SignedInfo" % NS['ds']) cm_alg = _cm_alg(si) digest_alg = _sig_digest(si) validated_objects = _process_references(t, sig, sig_path) b_digest = _create_signature_digest(si, cm_alg, digest_alg) actual = _signed_value(b_digest, this_keysize, True, digest_alg) expected = this_f_public(b64d(sv)) if not compare_digest(expected, actual): raise XMLSigException("Signature validation failed") validated.extend(validated_objects) return validated
def _verify(t, keyspec, sig_path=".//{%s}Signature" % NS['ds'], drop_signature=False): """ Verify the signature(s) in an XML document. Throws an XMLSigException on any non-matching signatures. :param t: XML as lxml.etree :param keyspec: X.509 cert filename, string with fingerprint or X.509 cert as string :returns: True if signature(s) validated, False if there were no signatures """ if config.debug_write_to_files: with open("/tmp/foo-sig.xml", "w") as fd: fd.write(etree.tostring(root_elt(t))) validated = [] for sig in t.findall(sig_path): try: sv = sig.findtext(".//{%s}SignatureValue" % NS['ds']) if not sv: raise XMLSigException("No SignatureValue") logging.debug("SignatureValue: {!s}".format(sv)) this_cert = xmlsec.crypto.from_keyspec(keyspec, signature_element=sig) logging.debug("key size: {!s} bits".format(this_cert.keysize)) si = sig.find(".//{%s}SignedInfo" % NS['ds']) logging.debug("Found signedinfo {!s}".format(etree.tostring(si))) cm_alg = _cm_alg(si) sig_digest_alg = _sig_digest(si) refmap = _process_references(t, sig, sig_path=sig_path, drop_signature=drop_signature) for ref, obj in refmap.items(): b_digest = _create_signature_digest(si, cm_alg, sig_digest_alg) actual = _signed_value(b_digest, this_cert.keysize, True, sig_digest_alg) if not this_cert.verify(b64d(sv), actual): raise XMLSigException( "Failed to validate {!s} using ref digest {!s} and cm {!s}" .format(etree.tostring(sig), ref_digest_alg, cm_alg)) validated.append(obj) except XMLSigException, ex: logging.error(ex)
def _verify(t, keyspec, sig_path=".//{%s}Signature" % NS['ds'], drop_signature=False): """ Verify the signature(s) in an XML document. Throws an XMLSigException on any non-matching signatures. :param t: XML as lxml.etree :param keyspec: X.509 cert filename, string with fingerprint or X.509 cert as string :returns: True if signature(s) validated, False if there were no signatures """ if config.debug_write_to_files: with open("/tmp/foo-sig.xml", "w") as fd: fd.write(etree.tostring(root_elt(t))) validated = [] for sig in t.findall(sig_path): try: sv = sig.findtext(".//{%s}SignatureValue" % NS['ds']) if sv is None: raise XMLSigException("No SignatureValue") this_cert = xmlsec.crypto.from_keyspec(keyspec, signature_element=sig) logging.debug("key size: {!s} bits".format(this_cert.keysize)) si = sig.find(".//{%s}SignedInfo" % NS['ds']) cm_alg = _cm_alg(si) digest_alg = _sig_digest(si) validated_objects = _process_references(t, sig, sig_path=sig_path, drop_signature=drop_signature) b_digest = _create_signature_digest(si, cm_alg, digest_alg) actual = _signed_value(b_digest, this_cert.keysize, True, digest_alg) if not this_cert.verify(b64d(sv), actual): raise XMLSigException("Failed to validate {!s}".format(etree.tostring(sig))) validated.extend(validated_objects) except XMLSigException, ex: logging.error(ex)
def _process_references(t, sig, verify_mode=True, sig_path=".//{%s}Signature" % NS['ds'], drop_signature=False): """ :returns: hash algorithm as string """ verified_objects = {} for ref in sig.findall(".//{%s}Reference" % NS['ds']): obj = None hash_alg = None uri = ref.get('URI', None) if uri is None or uri == '#' or uri == '': ref_obj = _implicit_same_document(t, sig) if ref_obj is None: raise XMLSigException("Unable to find reference while processing implicit same document reference") ct = _remove_child_comments(ref_obj) obj = root_elt(ct) elif uri.startswith('#'): ct = copy.deepcopy(t) ref_obj = _get_by_id(ct, uri[1:]) if ref_obj is None: raise XMLSigException("Unable to find reference while processing '%s'" % uri) obj = _remove_child_comments(ref_obj) else: raise XMLSigException("Unknown reference %s" % uri) if obj is None: raise XMLSigException("Unable to dereference Reference URI='%s'" % uri) obj_copy = obj if verify_mode: obj_copy = copy.deepcopy(obj) if drop_signature: for sig in obj_copy.findall(sig_path): sig.getparent().remove(sig) if config.debug_write_to_files: with open("/tmp/foo-pre-transform.xml", "w") as fd: fd.write(etree_to_string(obj)) for tr in ref.findall(".//{%s}Transform" % NS['ds']): obj = _transform(_alg(tr), obj, tr=tr, sig_path=sig_path) nslist = _find_nslist(tr) if nslist is not None: r = root_elt(t) for nsprefix in nslist: if nsprefix in r.nsmap: obj_copy.nsmap[nsprefix] = r.nsmap[nsprefix] if not isinstance(obj, six.string_types): if config.debug_write_to_files: with open("/tmp/foo-pre-serialize.xml", "w") as fd: fd.write(etree_to_string(obj)) obj = _transform(constants.TRANSFORM_C14N_INCLUSIVE, obj) if config.debug_write_to_files: with open("/tmp/foo-obj.xml", "w") as fd: if six.PY2: obj = obj.encode('utf-8') fd.write(obj) hash_alg = _ref_digest(ref) log.debug("using hash algorithm %s" % hash_alg) digest = xmlsec.crypto._digest(obj, hash_alg) log.debug("computed %s digest %s for ref %s" % (hash_alg, digest, uri)) dv = ref.find(".//{%s}DigestValue" % NS['ds']) if verify_mode: log.debug("found %s digest %s for ref %s" % (hash_alg, dv.text, uri)) computed_digest_binary = b64d(digest) digest_binary = b64d(dv.text) if digest_binary == computed_digest_binary: # no point in verifying signature if the digest doesn't match verified_objects[ref] = obj_copy else: log.error("not returning ref %s - digest mismatch" % uri) else: # signing - lets store the digest log.debug("replacing digest in %s" % etree.tostring(dv)) dv.text = digest if verify_mode: return verified_objects else: return None
def _implicit_same_document(t, sig): if config.same_document_is_root: return root_elt(copy.deepcopy(t)) else: return copy.deepcopy(sig.getparent())
def sign(t, key_spec, cert_spec=None, reference_uri='', insert_index=0, sig_path=".//{%s}Signature" % NS['ds']): """ Sign an XML document. This means to 'complete' all Signature elements in the XML. :param t: XML as lxml.etree :param key_spec: private key reference, see xmlsec.crypto.from_keyspec() for syntax. :param cert_spec: None or public key reference (to add cert to document), see xmlsec.crypto.from_keyspec() for syntax. :param sig_path: An xpath expression identifying the Signature template element :param reference_uri: Envelope signature reference URI :param insert_index: Insertion point for the Signature element, Signature is inserted at beginning by default :returns: XML as lxml.etree (for convenience, 't' is modified in-place) """ private = xmlsec.crypto.from_keyspec(key_spec, private=True) public = None if cert_spec is not None: public = xmlsec.crypto.from_keyspec(cert_spec) if public is None: raise XMLSigException("Unable to load public key from '%s'" % cert_spec) if public.keysize and private.keysize: # XXX maybe one set and one not set should also raise exception? if public.keysize != private.keysize: raise XMLSigException( "Public and private key sizes do not match ({!s}, {!s})". format(public.keysize, private.keysize)) # This might be incorrect for PKCS#11 tokens if we have no public key logging.debug("Using {!s} bit key".format(private.keysize)) templates = filter(_is_template, t.findall(sig_path)) if not templates: tmpl = add_enveloped_signature(t, reference_uri=reference_uri, pos=insert_index) templates = [tmpl] assert templates, XMLSigException( "Failed to both find and add a signing template") if config.debug_write_to_files: with open("/tmp/sig-ref.xml", "w") as fd: fd.write(etree.tostring(root_elt(t))) for sig in templates: logging.debug("processing sig template: %s" % etree.tostring(sig)) si = sig.find(".//{%s}SignedInfo" % NS['ds']) assert si is not None cm_alg = _cm_alg(si) digest_alg = _sig_digest(si) _process_references(t, sig, return_verified=False, sig_path=sig_path) # XXX create signature reference duplicates/overlaps process references unless a c14 is part of transforms b_digest = _create_signature_digest(si, cm_alg, digest_alg) # sign hash digest and insert it into the XML tbs = _signed_value(b_digest, private.keysize, private.do_padding, digest_alg) signed = private.sign(tbs) signature = b64e(signed) logging.debug("SignatureValue: %s" % signature) sv = sig.find(".//{%s}SignatureValue" % NS['ds']) if sv is None: si.addnext(DS.SignatureValue(signature)) else: sv.text = signature for cert_src in (public, private): if cert_src is not None and cert_src.cert_pem: # Insert cert_data as b64-encoded X.509 certificate into XML document sv_elt = si.getnext() sv_elt.addnext( DS.KeyInfo( DS.X509Data( DS.X509Certificate(pem2b64(cert_src.cert_pem))))) break # add the first we find, no more return t
def _process_references(t, sig, return_verified=True, sig_path=".//{%s}Signature" % NS['ds'], drop_signature=False): """ :returns: hash algorithm as string """ verified_objects = {} for ref in sig.findall(".//{%s}Reference" % NS['ds']): obj = None hash_alg = None uri = ref.get('URI', None) if uri is None or uri == '#' or uri == '': ref_obj = _implicit_same_document(t, sig) if ref_obj is None: raise XMLSigException( "Unable to find reference while processing implicit same document reference" ) ct = _remove_child_comments(ref_obj) obj = root_elt(ct) elif uri.startswith('#'): ct = copy.deepcopy(t) ref_obj = _get_by_id(ct, uri[1:]) if ref_obj is None: raise XMLSigException( "Unable to find reference while processing '%s'" % uri) obj = _remove_child_comments(ref_obj) else: raise XMLSigException("Unknown reference %s" % uri) if obj is None: raise XMLSigException("Unable to dereference Reference URI='%s'" % uri) if return_verified: obj_copy = copy.deepcopy(obj) if drop_signature: for sig in obj_copy.findall(sig_path): sig.getparent().remove(sig) verified_objects[ref] = obj_copy if config.debug_write_to_files: with open("/tmp/foo-pre-transform.xml", "w") as fd: fd.write(etree.tostring(obj)) for tr in ref.findall(".//{%s}Transform" % NS['ds']): logging.debug("transform: %s" % _alg(tr)) obj = _transform(_alg(tr), obj, tr=tr, sig_path=sig_path) nslist = _find_nslist(tr) if nslist is not None: r = root_elt(t) for nsprefix in nslist: if nsprefix in r.nsmap: obj_copy.nsmap[nsprefix] = r.nsmap[nsprefix] if not isinstance(obj, basestring): if config.debug_write_to_files: with open("/tmp/foo-pre-serialize.xml", "w") as fd: fd.write(etree.tostring(obj)) obj = _transform(constants.TRANSFORM_C14N_INCLUSIVE, obj) if config.debug_write_to_files: with open("/tmp/foo-obj.xml", "w") as fd: fd.write(obj) hash_alg = _ref_digest(ref) logging.debug("using hash algorithm %s" % hash_alg) digest = _digest(obj, hash_alg) logging.debug("using digest %s (%s) for ref %s" % (digest, hash_alg, uri)) dv = ref.find(".//{%s}DigestValue" % NS['ds']) #logging.debug(etree.tostring(dv)) dv.text = digest if return_verified: return verified_objects else: return None
def _process_references(t, sig, verify_mode=True, sig_path=".//{%s}Signature" % NS['ds'], drop_signature=False): """ :returns: hash algorithm as string """ verified_objects = {} for ref in sig.findall(".//{%s}Reference" % NS['ds']): obj = None hash_alg = None uri = ref.get('URI', None) if uri is None or uri == '#' or uri == '': ref_obj = _implicit_same_document(t, sig) if ref_obj is None: raise XMLSigException( "Unable to find reference while processing implicit same document reference" ) ct = _remove_child_comments(ref_obj) obj = root_elt(ct) elif uri.startswith('#'): ct = copy.deepcopy(t) ref_obj = _get_by_id(ct, uri[1:]) if ref_obj is None: raise XMLSigException( "Unable to find reference while processing '%s'" % uri) obj = _remove_child_comments(ref_obj) else: raise XMLSigException("Unknown reference %s" % uri) if obj is None: raise XMLSigException("Unable to dereference Reference URI='%s'" % uri) obj_copy = obj if verify_mode: obj_copy = copy.deepcopy(obj) if drop_signature: for sig in obj_copy.findall(sig_path): sig.getparent().remove(sig) if config.debug_write_to_files: with open("/tmp/foo-pre-transform.xml", "w") as fd: fd.write(etree.tostring(obj)) for tr in ref.findall(".//{%s}Transform" % NS['ds']): obj = _transform(_alg(tr), obj, tr=tr, sig_path=sig_path) nslist = _find_nslist(tr) if nslist is not None: r = root_elt(t) for nsprefix in nslist: if nsprefix in r.nsmap: obj_copy.nsmap[nsprefix] = r.nsmap[nsprefix] if not isinstance(obj, basestring): if config.debug_write_to_files: with open("/tmp/foo-pre-serialize.xml", "w") as fd: fd.write(etree.tostring(obj)) obj = _transform(constants.TRANSFORM_C14N_INCLUSIVE, obj) if config.debug_write_to_files: with open("/tmp/foo-obj.xml", "w") as fd: fd.write(obj) hash_alg = _ref_digest(ref) logging.debug("using hash algorithm %s" % hash_alg) digest = xmlsec.crypto._digest(obj, hash_alg) logging.debug("computed %s digest %s for ref %s" % (hash_alg, digest, uri)) dv = ref.find(".//{%s}DigestValue" % NS['ds']) if verify_mode: logging.debug("found %s digest %s for ref %s" % (hash_alg, dv.text, uri)) computed_digest_binary = b64d(digest) digest_binary = b64d(dv.text) if digest_binary == computed_digest_binary: # no point in verifying signature if the digest doesn't match verified_objects[ref] = obj_copy else: logging.error("not returning ref %s - digest mismatch" % uri) else: # signing - lets store the digest logging.debug("replacing digest in %s" % etree.tostring(dv)) dv.text = digest if verify_mode: return verified_objects else: return None
def sign(t, key_spec, cert_spec=None, reference_uri='', insert_index=0, sig_path=".//{%s}Signature" % NS['ds']): """ Sign an XML document. This means to 'complete' all Signature elements in the XML. :param t: XML as lxml.etree :param key_spec: private key reference, see xmlsec.crypto.from_keyspec() for syntax. :param cert_spec: None or public key reference (to add cert to document), see xmlsec.crypto.from_keyspec() for syntax. :param sig_path: An xpath expression identifying the Signature template element :param reference_uri: Envelope signature reference URI :param insert_index: Insertion point for the Signature element, Signature is inserted at beginning by default :returns: XML as lxml.etree (for convenience, 't' is modified in-place) """ private = xmlsec.crypto.from_keyspec(key_spec, private=True) public = None if cert_spec is not None: public = xmlsec.crypto.from_keyspec(cert_spec) if public is None: raise XMLSigException("Unable to load public key from '%s'" % cert_spec) if public.keysize and private.keysize: # XXX maybe one set and one not set should also raise exception? if public.keysize != private.keysize: raise XMLSigException("Public and private key sizes do not match ({!s}, {!s})".format( public.keysize, private.keysize)) # This might be incorrect for PKCS#11 tokens if we have no public key log.debug("Using {!s} bit key".format(private.keysize)) sig_paths = t.findall(sig_path) templates = list(filter(_is_template, sig_paths)) if not templates: tmpl = add_enveloped_signature(t, reference_uri=reference_uri, pos=insert_index) templates = [tmpl] assert templates, XMLSigException("Failed to both find and add a signing template") if config.debug_write_to_files: with open("/tmp/sig-ref.xml", "w") as fd: fd.write(etree_to_string(root_elt(t))) for sig in templates: log.debug("processing sig template: %s" % etree.tostring(sig)) si = sig.find(".//{%s}SignedInfo" % NS['ds']) assert si is not None cm_alg = _cm_alg(si) sig_alg = _sig_alg(si) _process_references(t, sig, verify_mode=False, sig_path=sig_path) # XXX create signature reference duplicates/overlaps process references unless a c14 is part of transforms log.debug("transform %s on %s" % (cm_alg, etree.tostring(si))) sic = _transform(cm_alg, si) log.debug("SignedInfo C14N: %s" % sic) # sign hash digest and insert it into the XML if private.do_digest: digest = xmlsec.crypto._digest(sic, sig_alg) log.debug("SignedInfo digest: %s" % digest) b_digest = b64d(digest) tbs = _signed_value(b_digest, private.keysize, private.do_padding, sig_alg) else: tbs = sic signed = private.sign(tbs, sig_alg) signature = b64e(signed) if isinstance(signature, six.binary_type): signature = six.text_type(signature, 'utf-8') log.debug("SignatureValue: %s" % signature) sv = sig.find(".//{%s}SignatureValue" % NS['ds']) if sv is None: si.addnext(DS.SignatureValue(signature)) else: sv.text = signature for cert_src in (public, private): if cert_src is not None and cert_src.cert_pem: # Insert cert_data as b64-encoded X.509 certificate into XML document sv_elt = si.getnext() sv_elt.addnext(DS.KeyInfo(DS.X509Data(DS.X509Certificate(pem2b64(cert_src.cert_pem))))) break # add the first we find, no more return t
def _process_references(t, sig, return_verified=True, sig_path=".//{%s}Signature" % NS['ds']): """ :returns: hash algorithm as string """ verified_objects = [] for ref in sig.findall(".//{%s}Reference" % NS['ds']): obj = None hash_alg = None uri = ref.get('URI', None) if uri is None or uri == '#' or uri == '': ct = _remove_child_comments(_implicit_same_document(t, sig)) obj = root_elt(ct) elif uri.startswith('#'): ct = copy.deepcopy(t) obj = _remove_child_comments(_get_by_id(ct, uri[1:])) else: raise XMLSigException("Unknown reference %s" % uri) if obj is None: raise XMLSigException("Unable to dereference Reference URI='%s'" % uri) if return_verified: verified_objects.append(copy.deepcopy(obj)) if config.debug_write_to_files: with open("/tmp/foo-pre-transform.xml", "w") as fd: fd.write(etree.tostring(obj)) for tr in ref.findall(".//{%s}Transform" % NS['ds']): logging.debug("transform: %s" % _alg(tr)) obj = _transform(_alg(tr), obj, tr=tr, sig_path=sig_path) if not isinstance(obj, basestring): if config.debug_write_to_files: with open("/tmp/foo-pre-serialize.xml", "w") as fd: fd.write(etree.tostring(obj)) obj = _transform(constants.TRANSFORM_C14N_INCLUSIVE, obj) if config.debug_write_to_files: with open("/tmp/foo-obj.xml", "w") as fd: fd.write(obj) dm = ref.find(".//{%s}DigestMethod" % NS['ds']) if dm is None: raise XMLSigException("Unable to find DigestMethod") hash_alg = (_alg(dm).split("#"))[1] logging.debug("using hash algorithm %s" % hash_alg) digest = _digest(obj, hash_alg) logging.debug("using digest %s (%s) for ref %s" % (digest, hash_alg, uri)) dv = ref.find(".//{%s}DigestValue" % NS['ds']) logging.debug(etree.tostring(dv)) dv.text = digest if return_verified: return verified_objects else: return None
def sign(t, key_spec, cert_spec=None, reference_uri='', insert_index=0, sig_path=".//{%s}Signature" % NS['ds']): """ Sign an XML document. This means to 'complete' all Signature elements in the XML. :param t: XML as lxml.etree :param key_spec: private key reference, see _load_keyspec() for syntax. :param cert_spec: None or public key reference (to add cert to document), see _load_keyspec() for syntax. :param reference_uri: Envelope signature reference URI :param insert_index: Insertion point for the Signature element, Signature is inserted at beginning by default :returns: XML as lxml.etree (for convenience, 't' is modified in-place) """ do_padding = False # only in the case of our fallback keytype do we need to do pkcs1 padding here private = _load_keyspec(key_spec, private=True) if private is None: raise XMLSigException("Unable to load private key from '%s'" % key_spec) if private['source'] == 'file': do_padding = True # need to do p1 padding in this case public = None if cert_spec is not None: public = _load_keyspec(cert_spec) if public is None: raise XMLSigException("Unable to load public key from '%s'" % cert_spec) if public['keysize'] != private['keysize']: raise XMLSigException("Public and private key sizes do not match (%s, %s)" % (public['keysize'], private['keysize'])) # This might be incorrect for PKCS#11 tokens if we have no public key logging.debug("Using %s bit key" % (private['keysize'])) if t.find(sig_path) is None: add_enveloped_signature(t, reference_uri=reference_uri, pos=insert_index) if config.debug_write_to_files: with open("/tmp/sig-ref.xml", "w") as fd: fd.write(etree.tostring(root_elt(t))) for sig in t.findall(sig_path): logging.debug("processing sig template: %s" % etree.tostring(sig)) si = sig.find(".//{%s}SignedInfo" % NS['ds']) cm_alg = _cm_alg(si) digest_alg = _sig_digest(si) _process_references(t, sig, return_verified=False, sig_path=sig_path) # XXX create signature reference duplicates/overlaps process references unless a c14 is part of transforms b_digest = _create_signature_digest(si, cm_alg, digest_alg) # sign hash digest and insert it into the XML tbs = _signed_value(b_digest, private.get('keysize'), do_padding, digest_alg) signed = private['f_private'](tbs) signature = b64e(signed) logging.debug("SignatureValue: %s" % signature) sv = sig.find(".//{%s}SignatureValue" % NS['ds']) if sv is None: si.addnext(DS.SignatureValue(signature.decode('ascii'))) else: sv.text = signature if public is not None: # Insert cert_data as b64-encoded X.509 certificate into XML document sv_elt = si.getnext() sv_elt.addnext( DS.KeyInfo( DS.X509Data( DS.X509Certificate( pem2b64( public['data'] ).decode('ascii') ) ) ) ) return t
def sign(t, key_spec, cert_spec=None, reference_uri='', sig_path=".//{%s}Signature" % NS['ds']): """ Sign an XML document. This means to 'complete' all Signature elements in the XML. :param t: XML as lxml.etree :param key_spec: private key reference, see _load_keyspec() for syntax. :param cert_spec: None or public key reference (to add cert to document), see _load_keyspec() for syntax. :param reference_uri: Envelope signature reference URI :returns: XML as lxml.etree (for convenience, 't' is modified in-place) """ do_padding = False # only in the case of our fallback keytype do we need to do pkcs1 padding here private = _load_keyspec(key_spec, private=True) if private is None: raise XMLSigException("Unable to load private key from '%s'" % key_spec) if private['source'] == 'file': do_padding = True # need to do p1 padding in this case public = None if cert_spec is not None: public = _load_keyspec(cert_spec) if public is None: raise XMLSigException("Unable to load public key from '%s'" % cert_spec) if public['keysize'] != private['keysize']: raise XMLSigException( "Public and private key sizes do not match (%s, %s)" % (public['keysize'], private['keysize'])) # This might be incorrect for PKCS#11 tokens if we have no public key logging.debug("Using %s bit key" % (private['keysize'])) if t.find(sig_path) is None: add_enveloped_signature(t, reference_uri=reference_uri) if config.debug_write_to_files: with open("/tmp/sig-ref.xml", "w") as fd: fd.write(etree.tostring(root_elt(t))) for sig in t.findall(sig_path): logging.debug("processing sig template: %s" % etree.tostring(sig)) si = sig.find(".//{%s}SignedInfo" % NS['ds']) cm_alg = _cm_alg(si) digest_alg = _sig_digest(si) _process_references(t, sig, return_verified=False, sig_path=sig_path) # XXX create signature reference duplicates/overlaps process references unless a c14 is part of transforms b_digest = _create_signature_digest(si, cm_alg, digest_alg) # sign hash digest and insert it into the XML tbs = _signed_value(b_digest, private.get('keysize'), do_padding, digest_alg) signed = private['f_private'](tbs) signature = b64e(signed) logging.debug("SignatureValue: %s" % signature) sv = sig.find(".//{%s}SignatureValue" % NS['ds']) if sv is None: si.addnext(DS.SignatureValue(signature)) else: sv.text = signature if public is not None: # Insert cert_data as b64-encoded X.509 certificate into XML document sv_elt = si.getnext() sv_elt.addnext( DS.KeyInfo( DS.X509Data(DS.X509Certificate(pem2b64(public['data']))))) return t