diff --git a/setup.cfg b/setup.cfg index 37df46f8..0a2ab086 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,2 +1,5 @@ -[pytest] -addopts = tests -s -rx +[bdist_rpm] +release = 1 +build-requires = pkgconfig xmlsec1-devel libxml2-devel xmlsec1-openssl-devel +group = Development/Libraries +requires = xmlsec1 xmlsec1-openssl \ No newline at end of file diff --git a/setup.py b/setup.py index 5dcf73d5..43c6074a 100644 --- a/setup.py +++ b/setup.py @@ -12,75 +12,45 @@ def lazy(function): @wraps(function) def wrapped(*args, **kwargs): - class LazyProxy(object): + class LazyProxy(Extension): + __arguments = dict() def __init__(self, function, args, kwargs): - self._function = function - self._args = args - self._kwargs = kwargs - self._result = None + self.__arguments["function"] = function + self.__arguments["args"] = args + self.__arguments["kwargs"] = kwargs + self.__arguments["result"] = None - def __getattribute__(self, name): - if name in ['_function', '_args', '_kwargs', '_result']: - return super(LazyProxy, self).__getattribute__(name) + def __getattr__(self, item): + if self.__arguments["result"] is None: + self.__arguments["result"] = self.__arguments["function"](*self.__arguments["args"], + **self.__arguments["kwargs"]) - if self._result is None: - self._result = self._function(*self._args, **self._kwargs) - - return object.__getattribute__(self._result, name) + return getattr(self.__arguments["result"], item) def __setattr__(self, name, value): - if name in ['_function', '_args', '_kwargs', '_result']: - super(LazyProxy, self).__setattr__(name, value) - return - - if self._result is None: - self._result = self._function(*self._args, **self._kwargs) + if self.__arguments["result"] is None: + self.__arguments["result"] = self.__arguments["function"](*self.__arguments["args"], + **self.__arguments["kwargs"]) - setattr(self._result, name, value) + setattr(self.__arguments["result"], name, value) return LazyProxy(function, args, kwargs) return wrapped -class Extension(Extension, object): - - lxml_extended = False - - @property - def include_dirs(self): - dirs = self.__dict__['include_dirs'] - if self.lxml_extended: - return dirs - - # Resolve lxml include directories. - import lxml - lxml_base = path.dirname(lxml.__file__) - lxml_include = path.join(lxml_base, 'includes') - - dirs.insert(0, lxml_include) - dirs.insert(0, lxml_base) - - self.lxml_extended = True - return dirs - - @include_dirs.setter - def include_dirs(self, dirs): - self.__dict__['include_dirs'] = dirs - - @lazy def make_extension(name, cython=True): from pkgconfig import parse # Declare the crypto implementation. - XMLSEC_CRYPTO = 'openssl' + xmlsec_crypto = 'openssl' # Process the `pkg-config` utility and discover include and library # directories. config = {} - for lib in ['libxml-2.0', 'xmlsec1-%s' % XMLSEC_CRYPTO]: + for lib in ['libxml-2.0', 'xmlsec1-%s' % xmlsec_crypto]: config.update(parse(lib)) config['extra_compile_args'] = ['-DXMLSEC_CRYPTO_OPENSSL=1'] @@ -93,6 +63,9 @@ def make_extension(name, cython=True): config['include_dirs'] = [] # Add the source directories for inclusion. + import lxml + config['include_dirs'].insert(0, path.dirname(lxml.__file__)) + config['include_dirs'].insert(0, path.join(path.dirname(lxml.__file__), 'includes')) config['include_dirs'].insert(0, 'src') # Resolve extension location from name. @@ -144,6 +117,7 @@ def make_extension(name, cython=True): make_extension('xmlsec.tree'), make_extension('xmlsec.key'), make_extension('xmlsec.ds'), + make_extension('xmlsec.enc'), make_extension('xmlsec.template'), ] ) diff --git a/src/xmlsec/__init__.py b/src/xmlsec/__init__.py index f5198254..106b1f00 100644 --- a/src/xmlsec/__init__.py +++ b/src/xmlsec/__init__.py @@ -6,6 +6,8 @@ from .utils import * from .key import * from .ds import * +from .enc import * +from .error import * from . import tree, template diff --git a/src/xmlsec/constants.pxd b/src/xmlsec/constants.pxd index 2250c569..d1634f6d 100644 --- a/src/xmlsec/constants.pxd +++ b/src/xmlsec/constants.pxd @@ -25,10 +25,40 @@ cdef extern from "xmlsec.h": # xmlsec/strings.h const_xmlChar* xmlSecNodeManifest const_xmlChar* xmlSecNodeSignatureProperties + # Encypted nodes + const_xmlChar* xmlSecNodeEncryptedData + const_xmlChar* xmlSecNodeEncryptedKey + const_xmlChar* xmlSecNodeEncryptionMethod + const_xmlChar* xmlSecNodeEncryptionProperties + const_xmlChar* xmlSecNodeEncryptionProperty + const_xmlChar* xmlSecNodeCipherData + const_xmlChar* xmlSecNodeCipherValue + const_xmlChar* xmlSecNodeCipherReference + const_xmlChar* xmlSecNodeReferenceList + const_xmlChar* xmlSecNodeDataReference + const_xmlChar* xmlSecNodeKeyReference + const_xmlChar* xmlSecNodeKeyInfo + + + # encryption types + const_xmlChar* xmlSecTypeEncContent + const_xmlChar* xmlSecTypeEncElement + + ctypedef unsigned int xmlSecTransformUsage + cdef enum: + xmlSecTransformUsageUnknown=0x0000 + xmlSecTransformUsageDSigTransform=0x0001 + xmlSecTransformUsageC14NMethod=0x0002 + xmlSecTransformUsageDigestMethod=0x0004 + xmlSecTransformUsageSignatureMethod=0x0008 + xmlSecTransformUsageEncryptionMethod=0x0010 + xmlSecTransformUsageAny=0xFFFF + # Transform ids cdef struct _xmlSecTransformKlass: const_xmlChar* name const_xmlChar* href + xmlSecTransformUsage usage ctypedef _xmlSecTransformKlass *xmlSecTransformId diff --git a/src/xmlsec/constants.pyx b/src/xmlsec/constants.pyx index 33109b60..88126fcd 100644 --- a/src/xmlsec/constants.pyx +++ b/src/xmlsec/constants.pyx @@ -1,13 +1,14 @@ # -*- coding: utf-8 -*- from __future__ import absolute_import, unicode_literals, division -from xmlsec.constants cimport * -from xmlsec.utils cimport * +from .constants cimport * +from .utils cimport * __all__ = [ 'Namespace', 'Node', - 'Transform' + 'Transform', + 'EncryptionType' ] @@ -24,6 +25,11 @@ class Namespace: SOAP12 = _u(xmlSecSoap12Ns) +class EncryptionType: + CONTENT = _u(xmlSecTypeEncContent) + ELEMENT = _u(xmlSecTypeEncElement) + + class Node: """Digital signature nodes.""" SIGNATURE = _u(xmlSecNodeSignature) @@ -36,6 +42,32 @@ class Node: OBJECT = _u(xmlSecNodeObject) MANIFEST = _u(xmlSecNodeManifest) SIGNATURE_PROPERTIES = _u(xmlSecNodeSignatureProperties) + ENCRYPTED_DATA = _u(xmlSecNodeEncryptedData) + ENCRYPTED_KEY = _u(xmlSecNodeEncryptedKey) + ENCRYPTION_METHOD = _u(xmlSecNodeEncryptionMethod) + ENCRYPTION_PROPERTIES = _u(xmlSecNodeEncryptionProperties) + ENCRYPTION_PROPERTY = _u(xmlSecNodeEncryptionProperty) + CIPHER_DATA = _u(xmlSecNodeCipherData) + CIPHER_VALUE = _u(xmlSecNodeCipherValue) + CIPHER_REFERENCE = _u(xmlSecNodeCipherReference) + REFERENCE_LIST = _u(xmlSecNodeReferenceList) + DATA_REFERENCE = _u(xmlSecNodeDataReference) + KEY_REFERENCE = _u(xmlSecNodeKeyReference) + KEY_INFO = _u(xmlSecNodeKeyInfo) + + +cdef class _Transform: + property name: + def __get__(self): + return _u(self.target.name) + + property href: + def __get__(self): + return _u(self.target.href) + + property usage: + def __get__(self): + return self.target.usage cdef _Transform _mkti(xmlSecTransformId target): diff --git a/src/xmlsec/ds.pxd b/src/xmlsec/ds.pxd index 59e486af..e8096677 100644 --- a/src/xmlsec/ds.pxd +++ b/src/xmlsec/ds.pxd @@ -1,11 +1,66 @@ from lxml.includes.tree cimport xmlNode from lxml.includes.tree cimport const_xmlChar, xmlNode, xmlID, xmlDoc, xmlAttr from lxml.includes.dtdvalid cimport xmlValidCtxt -from xmlsec.key cimport xmlSecKeyPtr +from .key cimport xmlSecKeyPtr, xmlSecKeyReq, xmlSecKeyReqPtr, xmlSecKeysMngrPtr +from .constants cimport xmlSecTransformId cdef extern from "xmlsec.h": # xmlsec/keys.h + ctypedef unsigned int xmlSecSize + ctypedef unsigned char xmlSecByte + ctypedef xmlSecByte const_xmlSecByte "const xmlSecByte" + + ctypedef void * xmlSecPtrList + ctypedef xmlSecPtrList * xmlSecPtrListPtr + ctypedef void * xmlSecPtr + + int xmlSecPtrListAdd(xmlSecPtrListPtr, xmlSecPtr) nogil + + int xmlSecPtrListEmpty(xmlSecPtrListPtr) nogil + + cdef struct _xmlSecBuffer: + xmlSecByte* data + size_t size + # size_t maxSize + # xmlSecAllocMode allocMode + + ctypedef _xmlSecBuffer *xmlSecBufferPtr + + # transforms and transform contexts (partial) + ctypedef enum xmlSecTransformStatus: + xmlSecTransformStatusNone = 0 + xmlSecTransformStatusWorking + xmlSecTransformStatusFinished + xmlSecTransformStatusOk + xmlSecTransformStatusFail + + ctypedef enum xmlSecTransformOperation: + xmlSecTransformOperationNone = 0 + xmlSecTransformOperationEncode + xmlSecTransformOperationDecode + xmlSecTransformOperationSign + xmlSecTransformOperationVerify + xmlSecTransformOperationEncrypt + xmlSecTransformOperationDecrypt + + cdef struct _xmlSecTransform: + xmlSecTransformOperation operation + xmlSecTransformStatus status + + ctypedef _xmlSecTransform* xmlSecTransformPtr + + cdef struct _xmlSecTransformCtx: + xmlSecBufferPtr result + xmlSecTransformStatus status + + ctypedef _xmlSecTransformCtx xmlSecTransformCtx + ctypedef _xmlSecTransformCtx* xmlSecTransformCtxPtr + + cdef struct xmlSecKeyInfoCtx: + xmlSecPtrList enabledKeyData + xmlSecKeyReq keyReq + ctypedef enum xmlSecDSigStatus: xmlSecDSigStatusUnknown = 0 xmlSecDSigStatusSucceeded = 1 @@ -15,9 +70,9 @@ cdef extern from "xmlsec.h": # xmlsec/keys.h # void* userData # unsigned int flags # unsigned int flags2 - # xmlSecKeyInfoCtx keyInfoReadCtx + xmlSecKeyInfoCtx keyInfoReadCtx # xmlSecKeyInfoCtx keyInfoWriteCtx - # xmlSecTransformCtx transformCtx + xmlSecTransformCtx transformCtx # xmlSecTransformUriType enabledReferenceUris # xmlSecPtrListPtr enabledReferenceTransforms # xmlSecTransformCtxPreExecuteCallback referencePreExecuteCallback @@ -25,10 +80,10 @@ cdef extern from "xmlsec.h": # xmlsec/keys.h # xmlSecTransformId defC14NMethodId # xmlSecTransformId defDigestMethodId xmlSecKeyPtr signKey - # xmlSecTransformOperation operation + xmlSecTransformOperation operation # xmlSecBufferPtr result xmlSecDSigStatus status - # xmlSecTransformPtr signMethod + xmlSecTransformPtr signMethod # xmlSecTransformPtr c14nMethod # xmlSecTransformPtr preSignMemBufMethod # xmlNode* signValueNode @@ -40,7 +95,6 @@ cdef extern from "xmlsec.h": # xmlsec/keys.h ctypedef _xmlSecDSigCtx* xmlSecDSigCtxPtr - ctypedef void* xmlSecKeysMngrPtr xmlSecDSigCtxPtr xmlSecDSigCtxCreate(xmlSecKeysMngrPtr) nogil @@ -50,13 +104,22 @@ cdef extern from "xmlsec.h": # xmlsec/keys.h int xmlSecDSigCtxVerify(xmlSecDSigCtxPtr, xmlNode*) nogil - # int xmlSecDSigCtxEnableReferenceTransform( - # xmlSecDSigCtxPtr, xmlSecTransformId) nogil + int xmlSecDSigCtxEnableReferenceTransform( + xmlSecDSigCtxPtr, xmlSecTransformId) nogil - # int xmlSecDSigCtxEnableSignatureTransform( - # xmlSecDSigCtxPtr, xmlSecTransformId) nogil + int xmlSecDSigCtxEnableSignatureTransform( + xmlSecDSigCtxPtr, xmlSecTransformId) nogil void xmlSecDSigCtxDestroy(xmlSecDSigCtxPtr) nogil - xmlID* xmlAddID( - xmlValidCtxt* ctx, xmlDoc* doc, const_xmlChar* value, xmlAttr* attr); + xmlID* xmlAddID(xmlValidCtxt* ctx, xmlDoc* doc, const_xmlChar* value, xmlAttr* attr) + + xmlSecTransformPtr xmlSecTransformCtxCreateAndAppend(xmlSecTransformCtxPtr, xmlSecTransformId) nogil + + int xmlSecTransformSetKey(xmlSecTransformPtr, xmlSecKeyPtr) nogil + + int xmlSecTransformSetKeyReq(xmlSecTransformPtr, xmlSecKeyReqPtr) nogil + + int xmlSecTransformVerify(xmlSecTransformPtr, const_xmlSecByte*, xmlSecSize, xmlSecTransformCtxPtr) nogil + + int xmlSecTransformCtxBinaryExecute(xmlSecTransformCtxPtr, const_xmlSecByte*, xmlSecSize) nogil diff --git a/src/xmlsec/ds.pyx b/src/xmlsec/ds.pyx index 33dadc08..2922c971 100644 --- a/src/xmlsec/ds.pyx +++ b/src/xmlsec/ds.pyx @@ -6,33 +6,32 @@ from lxml.includes.etreepublic cimport import_lxml__etree import_lxml__etree() from lxml.includes.etreepublic cimport _Element -from xmlsec.ds cimport * -from xmlsec.key cimport Key as _Key, xmlSecKeyDuplicate, xmlSecKeyDestroy -from copy import copy -from .key import Key -from xmlsec.utils cimport _b +from .ds cimport * +from .constants cimport _Transform, xmlSecTransformUsageSignatureMethod +from .key cimport Key as _Key, KeysManager as _KeysManager, _KeyData, \ + xmlSecKeyDuplicate, xmlSecKeyMatch, xmlSecKeyDestroy +from .error import * +from .utils cimport _b + __all__ = [ 'SignatureContext' ] - cdef class SignatureContext(object): """Digital signature context. """ cdef xmlSecDSigCtxPtr _handle - def __cinit__(self): # , KeysMngr manager=None): - # cdef xmlSecKeysMngrPtr _mngr - # _mngr = mngr.mngr if mngr is not None else NULL + def __cinit__(self, _KeysManager manager=None): + cdef xmlSecKeysMngrPtr _manager = manager._handle if manager is not None else NULL cdef xmlSecDSigCtxPtr handle - handle = xmlSecDSigCtxCreate(NULL) + handle = xmlSecDSigCtxCreate(_manager) if handle == NULL: - raise RuntimeError( - 'Failed to create the digital signature context.') + raise InternalError('Failed to create the digital signature context.', -1) # Store the constructed context handle. self._handle = handle @@ -43,14 +42,27 @@ cdef class SignatureContext(object): property key: def __set__(self, _Key key): - self._handle.signKey = key._handle + if self._handle.signKey != NULL: + xmlSecKeyDestroy(self._handle.signKey) + + self._handle.signKey = xmlSecKeyDuplicate(key._handle) + if self._handle.signKey == NULL: + raise InternalError("failed to duplicate key", -1) def __get__(self): - cdef _Key instance = Key.__new__(Key) + cdef _Key instance = _Key.__new__(_Key) instance._owner = False instance._handle = self._handle.signKey return instance + def register_id(self, _Element node not None): + cdef xmlAttr* attr + + attr = xmlHasProp(node._c_node, _b("ID")) + value = node.attrib.get("ID") + + xmlAddID(NULL, node._doc._c_doc, _b(value), attr) + def sign(self, _Element node not None): """Sign according to the signature template. """ @@ -59,15 +71,7 @@ cdef class SignatureContext(object): rv = xmlSecDSigCtxSign(self._handle, node._c_node) if rv != 0: - raise RuntimeError('sign failed with return value %r' % rv) - - def registerId(self, _Element node not None): - cdef xmlAttr* attr - - attr = xmlHasProp(node._c_node, _b("ID")) - value = node.attrib.get("ID") - - xmlAddID(NULL, node._doc._c_doc, _b(value), attr) + raise Error('sign failed with return value', rv) def verify(self, _Element node not None): """Verify according to the signature template. @@ -77,6 +81,113 @@ cdef class SignatureContext(object): rv = xmlSecDSigCtxVerify(self._handle, node._c_node) if rv != 0: - raise RuntimeError('verify failed with return value %r' % rv) + raise Error('verify failed with return value', rv) + + if self._handle.status != xmlSecDSigStatusSucceeded: + raise VerificationError('Signature verification failed', self._handle.status) + + def sign_binary(self, bytes data not None, _Transform algorithm not None): + """sign binary data *data* with *algorithm* and return the signature. + You must already have set the context's `signKey` (its value must + be compatible with *algorithm* and signature creation). + """ + + cdef xmlSecDSigCtxPtr context = self._handle + context.operation = xmlSecTransformOperationSign + self._binary(context, data, algorithm) + if context.transformCtx.status != xmlSecTransformStatusFinished: + raise Error("signing failed with transform status", context.transformCtx.status) + res = context.transformCtx.result + return (res.data)[:res.size] + + def verify_binary(self, bytes data not None, _Transform algorithm not None, bytes signature not None): + """Verify *signature* for *data* with *algorithm*. + You must already have set the context's `signKey` (its value must + be compatible with *algorithm* and signature verification). + """ + + cdef int rv + cdef xmlSecDSigCtxPtr context = self._handle + context.operation = xmlSecTransformOperationVerify + self._binary(context, data, algorithm) + rv = xmlSecTransformVerify(context.signMethod, + signature, + len(signature), + &context.transformCtx) + if rv != 0: + raise Error("Verification failed with return value", rv) + + if context.signMethod.status != xmlSecTransformStatusOk: + raise VerificationError("Signature verification failed", context.signMethod.status) + + cdef _binary(self, xmlSecDSigCtxPtr context, bytes data, _Transform algorithm): + """common helper used for `sign_binary` and `verify_binary`.""" + + cdef int rv + cdef const_xmlSecByte* c_data = data + cdef xmlSecSize c_size = len(data) + + if not (algorithm.target.usage & xmlSecTransformUsageSignatureMethod): + raise Error("improper signature algorithm") + + if context.signMethod != NULL: + raise Error("Signature context already used; it is designed for one use only") + + context.signMethod = xmlSecTransformCtxCreateAndAppend(&(context.transformCtx), algorithm.target) + + if context.signMethod == NULL: + raise Error("Could not create signature transform") + + context.signMethod.operation = context.operation + if context.signKey == NULL: + raise Error("signKey not yet set") + + xmlSecTransformSetKeyReq(context.signMethod, &(context.keyInfoReadCtx.keyReq)) + + rv = xmlSecKeyMatch(context.signKey, NULL, &(context.keyInfoReadCtx.keyReq)) + if rv != 1: + raise Error("inappropriate key type") + + rv = xmlSecTransformSetKey(context.signMethod, context.signKey) + if rv != 0: + raise Error("`xmlSecTransfromSetKey` failed", rv) + + context.transformCtx.result = NULL + context.transformCtx.status = xmlSecTransformStatusNone + + rv = xmlSecTransformCtxBinaryExecute(&(context.transformCtx), c_data, c_size) + + if rv != 0: + raise Error("transformation failed error value", rv) + + if context.transformCtx.status != xmlSecTransformStatusFinished: + raise Error("transformation failed with status", context.transformCtx.status) + + def enable_reference_transform(self, _Transform transform): + """enable use of *t* as reference transform. + Note: by default, all transforms are enabled. The first call of + `enableReferenceTransform` will switch to explicitely enabled transforms. + """ + + rv = xmlSecDSigCtxEnableReferenceTransform(self._handle, transform.target) + if rv < 0: + raise Error("enableReferenceTransform failed", rv) + + def enable_signature_transform(self, _Transform transform): + """enable use of *t* as signature transform. + Note: by default, all transforms are enabled. The first call of + `enableSignatureTransform` will switch to explicitely enabled transforms. + """ - return self._handle.status == xmlSecDSigStatusSucceeded + rv = xmlSecDSigCtxEnableSignatureTransform(self._handle, transform.target) + if rv < 0: + raise Error("enableSignatureTransform failed", rv) + + def set_enabled_key_data(self, keydata_list): + cdef _KeyData keydata + cdef xmlSecPtrListPtr enabled_list = &(self._handle.keyInfoReadCtx.enabledKeyData) + xmlSecPtrListEmpty(enabled_list) + for keydata in keydata_list: + rv = xmlSecPtrListAdd(enabled_list, keydata.target) + if rv < 0: + raise Error("setEnabledKeyData failed") diff --git a/src/xmlsec/enc.pxd b/src/xmlsec/enc.pxd new file mode 100644 index 00000000..2de629b1 --- /dev/null +++ b/src/xmlsec/enc.pxd @@ -0,0 +1,57 @@ +from lxml.includes.tree cimport xmlChar, const_xmlChar, xmlNode +from .key cimport xmlSecKeyPtr, xmlSecKeyReqPtr, xmlSecKeysMngrPtr +from .ds cimport const_xmlSecByte, xmlSecBufferPtr + + +cdef extern from "xmlsec.h": # xmlsec/keys.h + + unsigned int XMLSEC_ENC_RETURN_REPLACED_NODE + + cdef struct _xmlSecEncCtx: + # void * userData + unsigned int flags + # unsigned int flags2 + # xmlEncCtxMode mode + # xmlSecKeyInfoCtx keyInfoReadCtx + # xmlSecKeyInfoCtx keyInfoWriteCtx + # xmlSecTransformCtx transformCtx + # xmlSecTransformId defEncMethodId + xmlSecKeyPtr encKey + # xmlSecTransformOperation operation + xmlSecBufferPtr result + # int resultBase64Encoded + bint resultReplaced + # xmlSecTransformPtr encMethod + # xmlChar* id + # xmlChar* type + # xmlChar* mimeType + # xmlChar* encoding + # xmlChar* recipient + # xmlChar* carriedKeyName + # xmlNode* encDataNode + # xmlNode* encMethodNode + # xmlNode* keyInfoNode + # xmlNode* cipherValueNode + xmlNode* replacedNodeList + # void* reserved1 + + ctypedef _xmlSecEncCtx* xmlSecEncCtxPtr + + xmlSecEncCtxPtr xmlSecEncCtxCreate(xmlSecKeysMngrPtr) nogil + int xmlSecEncCtxInitialize(xmlSecEncCtxPtr encCtx, xmlSecKeysMngrPtr keysMngr) nogil + + void xmlSecEncCtxFinalize(xmlSecEncCtxPtr) nogil + void xmlSecEncCtxDestroy(xmlSecEncCtxPtr) nogil + + int xmlSecEncCtxBinaryEncrypt( + xmlSecEncCtxPtr, xmlNode*, const_xmlSecByte*, size_t) nogil + + int xmlSecEncCtxXmlEncrypt(xmlSecEncCtxPtr, xmlNode*, xmlNode*) nogil + + int xmlSecEncCtxUriEncrypt(xmlSecEncCtxPtr, xmlNode*, const_xmlChar*) nogil + + int xmlSecEncCtxDecrypt(xmlSecEncCtxPtr, xmlNode*) nogil + + void xmlSecErrorsSetCallback(void *callback) nogil + + int xmlSecKeyMatch(xmlSecKeyPtr, const_xmlChar*, xmlSecKeyReqPtr) diff --git a/src/xmlsec/enc.pyx b/src/xmlsec/enc.pyx new file mode 100644 index 00000000..bf970af5 --- /dev/null +++ b/src/xmlsec/enc.pyx @@ -0,0 +1,231 @@ +# -*- coding: utf-8 -*- +from __future__ import absolute_import, unicode_literals, division + +from lxml.includes.etreepublic cimport import_lxml__etree +import_lxml__etree() + +from lxml.includes.etreepublic cimport _Document, _Element, elementFactory +from lxml.includes.tree cimport xmlDocCopyNode, xmlFreeNode, xmlNode, xmlDoc, xmlDocGetRootElement + +from .enc cimport * +from .constants import EncryptionType +from .utils cimport * +from .key cimport Key as _Key, KeysManager as _KeysManager, _KeyData, \ + xmlSecKeyDuplicate, xmlSecKeyMatch, xmlSecKeyDestroy + +from .error import * +from copy import copy + + +__all__ = [ + 'EncryptionContext' +] + + +# we let `lxml` get rid of the subtree by wrapping *c_node* into a +# proxy and then releasing it again. +# Note: if referenced by `lxml`, nodes inside the subtree may lack +# necessary namespace daclarations. Hopefully, I can +# convince the `lxml` maintainers to provide a really safe +# `lxml_safe_unlink` function + +cdef inline int _lxml_safe_dealloc(_Document doc, xmlNode* c_node) except -1: + elementFactory(doc, c_node) + return 0 + + + +cdef class EncryptionContext: + """Encryption context.""" + + cdef xmlSecEncCtxPtr _handle + + def __cinit__(self, _KeysManager manager=None): + cdef xmlSecKeysMngrPtr _manager = manager._handle if manager is not None else NULL + cdef xmlSecEncCtxPtr handle = xmlSecEncCtxCreate(_manager) + if handle == NULL: + raise InternalError("failed to create encryption context") + + self._handle = handle + + def __dealloc__(self): + if self._handle != NULL: + xmlSecEncCtxDestroy(self._handle) + + property key: + def __set__(self, _Key key): + if self._handle.encKey != NULL: + xmlSecKeyDestroy(self._handle.encKey) + + self._handle.encKey = xmlSecKeyDuplicate(key._handle) + if self._handle.encKey == NULL: + raise InternalError("failed to duplicate key", -1) + + def __get__(self): + cdef _Key instance = _Key.__new__(_Key) + instance._owner = False + instance._handle = self._handle.encKey + return instance + + def encrypt_binary(self, _Element template not None, data): + """encrypt binary *data* according to `EncryptedData` template *template* + and return the resulting `EncryptedData` subtree. + Note: *template* is modified in place. + """ + cdef int rv + + # Data to bytes + cdef const_xmlSecByte* c_data = data + cdef size_t c_size = len(data) + + with nogil: + rv = xmlSecEncCtxBinaryEncrypt(self._handle, template._c_node, c_data, c_size) + if rv < 0: + raise Error("failed to encrypt binary", rv) + return template + + def encrypt_xml(self, _Element template not None, _Element node not None): + """encrpyt *node* using *template* and return the resulting `EncryptedData` element. + The `Type` attribute of *template* decides whether *node* itself is + encrypted (`http://www.w3.org/2001/04/xmlenc#Element`) + or its content (`http://www.w3.org/2001/04/xmlenc#Content`). + It must have one of these two + values (or an exception is raised). + The operation modifies the tree containing *node* in a way that + `lxml` references to or into this tree may see a surprising state. You + should no longer rely on them. Especially, you should use + `getroottree()` on the result to obtain the encrypted result tree. + """ + + cdef int rv + cdef xmlNode *n + cdef xmlNode *nn + cdef xmlSecEncCtxPtr context = self._handle + cdef xmlNode *c_node = template._c_node + + et = template.get("Type") + if et not in (EncryptionType.ELEMENT, EncryptionType.CONTENT): + raise Error("unsupported `Type` for `encryptXML` (must be `%s` or `%s`)" % (EncryptionType.ELEMENT, EncryptionType.CONTENT), et) + + # `xmlSecEncCtxEncrypt` expects *template* to belong to the document of *node* + # if this is not the case, we copy the `libxml2` subtree there. + + if template._doc._c_doc != node._doc._c_doc: + with nogil: + c_node = xmlDocCopyNode(c_node, node._doc._c_doc, 1) # recursive + if c_node == NULL: + raise MemoryError("could not copy template tree") + + # `xmlSecEncCtxXmlEncrypt` will replace the subtree rooted + # at `node._c_node` or its children by an extended subtree + # rooted at "c_node". + # We set `XMLSEC_ENC_RETURN_REPLACED_NODE` to prevent deallocation + # of the replaced node. This is important as `node` is still + # referencing it + + context.flags = XMLSEC_ENC_RETURN_REPLACED_NODE + + with nogil: + rv = xmlSecEncCtxXmlEncrypt(context, c_node, node._c_node) + + # release the replaced nodes in a way safe for `lxml` + + n = context.replacedNodeList + + while n != NULL: + nn = n.next + _lxml_safe_dealloc(node._doc, n) + n = nn + + context.replacedNodeList = NULL + if rv < 0: + if c_node._private == NULL: + # template tree was copied; free it again. + # Note: if the problem happened late (e.g. a `MemoryError`) + # `c_node' might already be part of the result tree. + # In this case, memory corruption may result. But, + # we have an inconsistent state anyway and the probability + # should be very low. + + with nogil: + xmlFreeNode(c_node) # free formerly copied template subtree + + raise Error("failed to encrypt xml", rv) + + # `c_node` contains the resulting `EncryptedData` element. + return elementFactory(node._doc, c_node) + + def encrypt_uri(self, _Element template not None, uri not None): + """encrypt binary data obtained from *uri* according to *template*.""" + + cdef int rv + cdef const_xmlChar* c_uri = _b(uri) + + with nogil: + rv = xmlSecEncCtxUriEncrypt(self._handle, template._c_node, c_uri) + + if rv < 0: + raise Error("failed to encrypt uri", rv) + return template + + def decrypt(self, _Element node not None): + """decrypt *node* (an `EncryptedData` element) and return the result. + The decryption may result in binary data or an XML subtree. + In the former case, the binary data is returned. In the latter case, + the input tree is modified and a reference to the decrypted + XML subtree is returned. + If the operation modifies the tree, + `lxml` references to or into this tree may see a surprising state. You + should no longer rely on them. Especially, you should use + `getroottree()` on the result to obtain the decrypted result tree. + """ + + cdef int rv + cdef xmlSecEncCtxPtr context = self._handle + cdef bint decrypt_content + cdef xmlNode *n + cdef xmlNode *nn + cdef xmlNode *c_root + cdef xmlSecBufferPtr result + + decrypt_content = node.get("Type") == EncryptionType.CONTENT + + # must provide sufficient context to find the decrypted node + parent = node.getparent() + + if parent is not None: + enc_index = parent.index(node) + + context.flags = XMLSEC_ENC_RETURN_REPLACED_NODE + + with nogil: + rv = xmlSecEncCtxDecrypt(context, node._c_node) + + # release the replaced nodes in a way safe for `lxml` + n = context.replacedNodeList + while n != NULL: + nn = n.next + _lxml_safe_dealloc(node._doc, n) + n = nn + + context.replacedNodeList = NULL + + if rv < 0: + raise Error("failed to decrypt", rv) + + if not context.resultReplaced: + # binary result + result = context.result + return (context.result.data)[:result.size] + # XML result + if parent is not None: + if decrypt_content: + return parent + else: + return parent[enc_index] + + # root has been replaced + c_root = xmlDocGetRootElement(node._doc._c_doc) + if c_root == NULL: + raise Error("decryption resulted in a non well formed document") + return elementFactory(node._doc, c_root) diff --git a/src/xmlsec/error.py b/src/xmlsec/error.py new file mode 100644 index 00000000..675d5173 --- /dev/null +++ b/src/xmlsec/error.py @@ -0,0 +1,15 @@ +# -*- coding: utf-8 -*- + +__all__ = ["Error", "VerificationError", "InternalError"] + + +class Error(Exception): + pass + + +class InternalError(Error): + pass + + +class VerificationError(Error): + pass diff --git a/src/xmlsec/key.pxd b/src/xmlsec/key.pxd index fedceb96..f90d670d 100644 --- a/src/xmlsec/key.pxd +++ b/src/xmlsec/key.pxd @@ -8,25 +8,23 @@ cdef extern from *: cdef extern from "xmlsec.h": # xmlsec/keys.h - # cdef struct _xmlSecKeyDataKlass: - # const_xmlChar* name - # const_xmlChar* href - - # ctypedef _xmlSecKeyDataKlass *xmlSecKeyDataId - - # xmlSecKeyDataId xmlSecKeyDataNameId - # xmlSecKeyDataId xmlSecKeyDataValueId - # xmlSecKeyDataId xmlSecKeyDataRetrievalMethodId - # xmlSecKeyDataId xmlSecKeyDataEncryptedKeyId - # xmlSecKeyDataId xmlSecKeyDataAesId - # xmlSecKeyDataId xmlSecKeyDataDesId - # xmlSecKeyDataId xmlSecKeyDataDsaId - # xmlSecKeyDataId xmlSecKeyDataHmacId - # xmlSecKeyDataId xmlSecKeyDataRsaId - # xmlSecKeyDataId xmlSecKeyDataX509Id - # xmlSecKeyDataId xmlSecKeyDataRawX509CertId - - ctypedef void* xmlSecKeyPtr + cdef struct _xmlSecKeyDataKlass: + const_xmlChar* name + const_xmlChar* href + + ctypedef _xmlSecKeyDataKlass *xmlSecKeyDataId + + xmlSecKeyDataId xmlSecKeyDataNameId + xmlSecKeyDataId xmlSecKeyDataValueId + xmlSecKeyDataId xmlSecKeyDataRetrievalMethodId + xmlSecKeyDataId xmlSecKeyDataEncryptedKeyId + xmlSecKeyDataId xmlSecKeyDataAesId + xmlSecKeyDataId xmlSecKeyDataDesId + xmlSecKeyDataId xmlSecKeyDataDsaId + xmlSecKeyDataId xmlSecKeyDataHmacId + xmlSecKeyDataId xmlSecKeyDataRsaId + xmlSecKeyDataId xmlSecKeyDataX509Id + xmlSecKeyDataId xmlSecKeyDataRawX509CertId ctypedef enum xmlSecKeyDataFormat: xmlSecKeyDataFormatUnknown = 0 @@ -39,18 +37,26 @@ cdef extern from "xmlsec.h": # xmlsec/keys.h xmlSecKeyDataFormatCertPem = 7 xmlSecKeyDataFormatCertDer = 8 - # ctypedef unsigned int xmlSecKeyDataType + ctypedef unsigned int xmlSecKeyDataType + + cdef enum: + xmlSecKeyDataTypeUnknown = 0x0000 + xmlSecKeyDataTypeNone = 0x0000 + xmlSecKeyDataTypePublic = 0x0001 + xmlSecKeyDataTypePrivate = 0x0002 + xmlSecKeyDataTypeSymmetric = 0x0004 + xmlSecKeyDataTypeSession = 0x0008 + xmlSecKeyDataTypePermanent = 0x0010 + xmlSecKeyDataTypeTrusted = 0x0100 + xmlSecKeyDataTypeAny = 0xFFFF + + ctypedef void* xmlSecKeyPtr + + cdef struct _xmlSecKeyReq: + pass - # cdef enum: - # xmlSecKeyDataTypeUnknown = 0x0000 - # xmlSecKeyDataTypeNone = 0x0000 - # xmlSecKeyDataTypePublic = 0x0001 - # xmlSecKeyDataTypePrivate = 0x0002 - # xmlSecKeyDataTypeSymmetric = 0x0004 - # xmlSecKeyDataTypeSession = 0x0008 - # xmlSecKeyDataTypePermanent = 0x0010 - # xmlSecKeyDataTypeTrusted = 0x0100 - # xmlSecKeyDataTypeAny = 0xFFFF + ctypedef _xmlSecKeyReq xmlSecKeyReq + ctypedef xmlSecKeyReq* xmlSecKeyReqPtr void xmlSecKeyDestroy(xmlSecKeyPtr) nogil @@ -63,7 +69,7 @@ cdef extern from "xmlsec.h": # xmlsec/keys.h const_unsigned_char*, int, xmlSecKeyDataFormat, const_char*, void*, void*) nogil - # xmlSecKeyPtr xmlSecKeyReadBinaryFile(xmlSecKeyDataId, const_char*) nogil + xmlSecKeyPtr xmlSecKeyReadBinaryFile(xmlSecKeyDataId, const_char*) nogil # xmlSecKeyPtr xmlSecKeyReadMemory( # xmlSecKeyDataId, const_unsigned_char*, size_t) nogil @@ -74,13 +80,40 @@ cdef extern from "xmlsec.h": # xmlsec/keys.h int xmlSecCryptoAppKeyCertLoadMemory( xmlSecKeyPtr, const_unsigned_char*, int, xmlSecKeyDataFormat) nogil - # xmlSecKeyPtr xmlSecKeyGenerate( - # xmlSecKeyDataId, size_t, xmlSecKeyDataType) nogil + xmlSecKeyPtr xmlSecKeyGenerate( + xmlSecKeyDataId, size_t, xmlSecKeyDataType) nogil int xmlSecKeySetName(xmlSecKeyPtr, const_xmlChar*) nogil const_xmlChar* xmlSecKeyGetName(xmlSecKeyPtr) nogil + int xmlSecKeyMatch(xmlSecKeyPtr, const_xmlChar *, xmlSecKeyReqPtr) nogil + + ctypedef void *xmlSecKeysMngrPtr + + xmlSecKeysMngrPtr xmlSecKeysMngrCreate() nogil + + void xmlSecKeysMngrDestroy(xmlSecKeysMngrPtr) nogil + + int xmlSecCryptoAppDefaultKeysMngrInit(xmlSecKeysMngrPtr) nogil + + int xmlSecCryptoAppDefaultKeysMngrAdoptKey(xmlSecKeysMngrPtr, xmlSecKeyPtr) nogil + + int xmlSecCryptoAppKeysMngrCertLoad( + xmlSecKeysMngrPtr, char * filename, xmlSecKeyDataFormat, xmlSecKeyDataType) nogil + + int xmlSecCryptoAppKeysMngrCertLoadMemory( + xmlSecKeysMngrPtr, const_unsigned_char *, size_t, xmlSecKeyDataFormat, xmlSecKeyDataType) nogil + + +cdef class _KeyData(object): + cdef xmlSecKeyDataId target + + cdef class Key(object): cdef xmlSecKeyPtr _handle cdef bint _owner + + +cdef class KeysManager(object): + cdef xmlSecKeysMngrPtr _handle diff --git a/src/xmlsec/key.pyx b/src/xmlsec/key.pyx index a083b3a6..ea5d4b78 100644 --- a/src/xmlsec/key.pyx +++ b/src/xmlsec/key.pyx @@ -1,12 +1,17 @@ # -*- coding: utf-8 -*- from __future__ import absolute_import, unicode_literals, division -from xmlsec.key cimport * -from xmlsec.utils cimport * +from .key cimport * +from .utils cimport * +from .error import * +from copy import copy __all__ = [ + 'KeyData', + 'KeyDataType', 'KeyFormat', - 'Key' + 'Key', + 'KeysManager' ] @@ -22,6 +27,49 @@ class KeyFormat: CERT_DER = xmlSecKeyDataFormatCertDer +cdef class _KeyData(object): + property name: + def __get__(self): + return _u(self.id.name) + + property href: + def __get__(self): + return _u(self.id.href) + + +cdef _KeyData _mkkdi(xmlSecKeyDataId target): + cdef _KeyData r = _KeyData.__new__(_KeyData) + r.target = target + return r + + +cdef class KeyData(object): + NAME = _mkkdi(xmlSecKeyDataNameId) + VALUE = _mkkdi(xmlSecKeyDataValueId) + RETRIEVALMETHOD = _mkkdi(xmlSecKeyDataRetrievalMethodId) + ENCRYPTEDKEY = _mkkdi(xmlSecKeyDataEncryptedKeyId) + AES = _mkkdi(xmlSecKeyDataAesId) + DES = _mkkdi(xmlSecKeyDataDesId) + DSA = _mkkdi(xmlSecKeyDataDsaId) + HMAC = _mkkdi(xmlSecKeyDataHmacId) + RSA = _mkkdi(xmlSecKeyDataRsaId) + X509 = _mkkdi(xmlSecKeyDataX509Id) + RAWX509CERT = _mkkdi(xmlSecKeyDataRawX509CertId) + + + +cdef class KeyDataType(object): + UNKNOWN = xmlSecKeyDataTypeUnknown + NONE = xmlSecKeyDataTypeNone + PUBLIC = xmlSecKeyDataTypePublic + PRIVATE = xmlSecKeyDataTypePrivate + SYMMETRIC = xmlSecKeyDataTypeSymmetric + SESSION = xmlSecKeyDataTypeSession + PERMANENT = xmlSecKeyDataTypePermanent + TRUSTED = xmlSecKeyDataTypeTrusted + ANY = xmlSecKeyDataTypeAny + + cdef class Key(object): def __dealloc__(self): @@ -32,13 +80,15 @@ cdef class Key(object): return self.__copy__() def __copy__(self): - cdef Key instance = Key.__new__(Key) + cdef Key instance = type(self)() instance._handle = xmlSecKeyDuplicate(self._handle) + if instance._handle == NULL: + raise InternalError("failed to duplicate key", -1) return instance @classmethod - def from_memory(cls, stream, xmlSecKeyDataFormat format, password=None): + def from_memory(cls, data, xmlSecKeyDataFormat format, password=None): """Load PKI key from memory. """ @@ -48,22 +98,22 @@ cdef class Key(object): cdef const_char* c_password = _b(password) cdef Key instance - # Read in the stream. - text = stream.read().encode('utf8') - c_size = len(text) - c_data = text + if isinstance(data, str): + data = data.encode('utf8') + c_size = len(data) + c_data = data with nogil: handle = xmlSecCryptoAppKeyLoadMemory( c_data, c_size, format, c_password, NULL, NULL) - if handle != NULL: - # Construct and return a new instance. - instance = Key.__new__(Key) - instance._handle = handle - return instance + if handle == NULL: + raise ValueError("failed to load key") - # Failed to load the key; return nothing. + # Construct and return a new instance. + instance = cls() + instance._handle = handle + return instance @classmethod def from_file(cls, filename, xmlSecKeyDataFormat format, password=None): @@ -79,30 +129,72 @@ cdef class Key(object): handle = xmlSecCryptoAppKeyLoad( c_filename, format, c_password, NULL, NULL) - if handle != NULL: - # Construct and return a new instance. - instance = Key.__new__(Key) - instance._handle = handle - return instance + if handle == NULL: + raise ValueError("failed to load key from '%s'" % filename) + + # Construct and return a new instance. + instance = cls() + instance._handle = handle + return instance + + @classmethod + def generate(cls, _KeyData data, size_t size, xmlSecKeyDataType type): + """Generate key of kind *data* with *size* and *type*. + """ + + cdef xmlSecKeyPtr handle + cdef xmlSecKeyDataId data_id = data.target + cdef Key instance - # Failed to load the key; return nothing. + with nogil: + handle = xmlSecKeyGenerate(data_id, size, type) + + if handle == NULL: + raise ValueError("failed to generate key") + + # Construct and return a new instance. + instance = cls() + instance._handle = handle + return instance + + @classmethod + def from_binary_file(cls, _KeyData data, filename): + """load (symmetric) key from file. + load key of kind *data* from *filename* + """ + cdef xmlSecKeyPtr handle + cdef const_char* c_filename = _b(filename) + cdef xmlSecKeyDataId data_id = data.target + cdef Key instance + + with nogil: + handle = xmlSecKeyReadBinaryFile(data_id, c_filename) - def load_cert_from_memory(self, stream, xmlSecKeyDataFormat format): + if handle == NULL: + raise ValueError("failed to load from '%s'" % filename) + + # Construct and return a new instance. + instance = cls() + instance._handle = handle + return instance + + + def load_cert_from_memory(self, data, xmlSecKeyDataFormat format): cdef int rv cdef size_t c_size cdef const_unsigned_char *c_data - text = stream.read().encode('utf8') - c_size = len(text) - c_data = text + if isinstance(data, str): + data = data.encode('utf8') + c_size = len(data) + c_data = data with nogil: rv = xmlSecCryptoAppKeyCertLoadMemory( self._handle, c_data, c_size, format) if rv != 0: - raise RuntimeError( - 'Failed to load the certificate from the I/O stream.') + raise ValueError('Failed to load the certificate from the I/O stream.') def load_cert_from_file(self, filename, xmlSecKeyDataFormat format): cdef int rv @@ -113,8 +205,7 @@ cdef class Key(object): rv = xmlSecCryptoAppKeyCertLoad(self._handle, c_filename, format) if rv != 0: - raise RuntimeError( - 'Failed to load the certificate from the file.') + raise ValueError('Failed to load the certificate from the file.') property name: def __get__(self): @@ -122,3 +213,69 @@ cdef class Key(object): def __set__(self, value): xmlSecKeySetName(self._handle, _b(value)) + + +cdef class KeysManager(object): + def __cinit__(self): + cdef int rv + cdef xmlSecKeysMngrPtr handle + + handle = xmlSecKeysMngrCreate() + if handle == NULL: + raise InternalError("failed to create keys manager", -1) + + rv = xmlSecCryptoAppDefaultKeysMngrInit(handle) + if rv < 0: + raise InternalError("failed to initialize keys manager", rv) + self._handle = handle + + def __dealloc__(self): + if self._handle != NULL: + xmlSecKeysMngrDestroy(self._handle) + + def add_key(self, Key key): + """add (a copy of) *key*.""" + + cdef int rv + cdef xmlSecKeyPtr key_handle = xmlSecKeyDuplicate(key._handle) + if key_handle == NULL: + raise InternalError("failed to copy key", -1) + + rv = xmlSecCryptoAppDefaultKeysMngrAdoptKey(self._handle, key_handle) + if rv < 0: + xmlSecKeyDestroy(key_handle) + raise Error("failed to add key", rv) + + def load_cert(self, filename, xmlSecKeyDataFormat format, xmlSecKeyDataType type): + """load certificate from *filename*. + *format* specifies the key data format. + *type* specifies the type and is an or of `KeyDataType*` constants. + """ + cdef int rv + cdef const_char* c_filename = _b(filename) + + with nogil: + rv = xmlSecCryptoAppKeysMngrCertLoad(self._handle, c_filename, format, type) + + if rv != 0: + raise Error("failed to load certificate from '%s'" % filename, rv) + + def load_cert_from_memory(self, data, xmlSecKeyDataFormat format, xmlSecKeyDataType type): + """load certificate from *data* (a sequence of bytes). + *format* specifies the key_data_format. + *type* specifies the type and is an or of `KeyDataType*` constants. + """ + cdef int rv + cdef size_t c_size + cdef const_unsigned_char *c_data + + if isinstance(data, str): + data = data.encode('utf8') + c_size = len(data) + c_data = data + + with nogil: + rv = xmlSecCryptoAppKeysMngrCertLoadMemory(self._handle, c_data, c_size, format, type) + + if rv != 0: + raise Error("failed to load certificate from memory", rv) diff --git a/src/xmlsec/meta.py b/src/xmlsec/meta.py index d2c8a8f5..ce17db64 100644 --- a/src/xmlsec/meta.py +++ b/src/xmlsec/meta.py @@ -1,2 +1,2 @@ -version = '0.2.0' +version = '0.2.1' description = 'Python bindings for the XML Security Library.' diff --git a/src/xmlsec/template.pxd b/src/xmlsec/template.pxd index 9e4ec50b..cdc3b183 100644 --- a/src/xmlsec/template.pxd +++ b/src/xmlsec/template.pxd @@ -1,12 +1,12 @@ from lxml.includes.tree cimport const_xmlChar, xmlNode, xmlDoc -from xmlsec.constants cimport xmlSecTransformId +from .constants cimport xmlSecTransformId cdef extern from "xmlsec.h": # xmlsec/templates.h - xmlNode* xmlSecTmplSignatureCreate( + xmlNode* xmlSecTmplSignatureCreateNsPref( xmlDoc* document, xmlSecTransformId c14n, xmlSecTransformId sign, - const_xmlChar* id) nogil + const_xmlChar* id, const_xmlChar* ns) nogil xmlNode* xmlSecTmplSignatureAddReference( xmlNode* node, xmlSecTransformId digest, @@ -23,3 +23,15 @@ cdef extern from "xmlsec.h": # xmlsec/templates.h xmlNode* xmlSecTmplKeyInfoAddKeyValue(xmlNode* node) nogil xmlNode* xmlSecTmplKeyInfoAddX509Data(xmlNode* node) nogil + + xmlNode* xmlSecTmplKeyInfoAddEncryptedKey( + xmlNode* keyInfoNode, xmlSecTransformId encMethodId, + const_xmlChar *id, const_xmlChar *type, const_xmlChar *recipient) nogil + + xmlNode* xmlSecTmplEncDataCreate( + xmlDoc* doc, xmlSecTransformId encMethodId, const_xmlChar *id, + const_xmlChar *type, const_xmlChar *mimeType, const_xmlChar *encoding) nogil + + xmlNode* xmlSecTmplEncDataEnsureKeyInfo(xmlNode* encNode, const_xmlChar *id) nogil + + xmlNode* xmlSecTmplEncDataEnsureCipherValue(xmlNode* encNode) nogil diff --git a/src/xmlsec/template.pyx b/src/xmlsec/template.pyx index a7744c98..81435e9e 100644 --- a/src/xmlsec/template.pyx +++ b/src/xmlsec/template.pyx @@ -5,31 +5,31 @@ from lxml.includes.etreepublic cimport import_lxml__etree import_lxml__etree() from lxml.includes.etreepublic cimport _Element, elementFactory -from lxml.includes.tree cimport const_xmlChar, xmlNode -from xmlsec.constants cimport _Transform -from xmlsec.utils cimport * -from xmlsec.template cimport * +from lxml.includes.tree cimport const_xmlChar, xmlNode, xmlStrdup +from .constants cimport _Transform +from .utils cimport * +from .template cimport * -def create( - _Element node not None, - _Transform c14n_method not None, - _Transform sign_method not None, - name=None): +def create(_Element node not None, + _Transform c14n_method not None, + _Transform sign_method not None, + name=None, + ns=None): cdef xmlNode* c_node cdef const_xmlChar* c_name = _b(name) + cdef const_xmlChar* c_ns = _b(ns) - c_node = xmlSecTmplSignatureCreate( - node._doc._c_doc, c14n_method.target, sign_method.target, c_name) + c_node = xmlSecTmplSignatureCreateNsPref( + node._doc._c_doc, c14n_method.target, sign_method.target, c_name, c_ns) return elementFactory(node._doc, c_node) -def add_reference( - _Element node not None, - _Transform digest_method not None, - id=None, uri=None, type=None): +def add_reference(_Element node not None, + _Transform digest_method not None, + id=None, uri=None, type=None): cdef xmlNode* c_node cdef const_xmlChar* c_id = _b(id) @@ -87,3 +87,66 @@ def add_x509_data(_Element node not None): c_node = xmlSecTmplKeyInfoAddX509Data(node._c_node) return elementFactory(node._doc, c_node) + + +def add_encrypted_key(_Element node not None, + _Transform method not None, + id=None, + type=None, + recipient=None): + """Adds node with given attributes to the node keyInfoNode. + """ + + cdef xmlNode* c_node + cdef const_xmlChar* c_id = _b(id) + cdef const_xmlChar* c_type = _b(type) + cdef const_xmlChar* c_recipient = _b(recipient) + + c_node = xmlSecTmplKeyInfoAddEncryptedKey(node._c_node, method.target, c_id, c_type, c_recipient) + return elementFactory(node._doc, c_node) + + +def encrypted_data_create(_Element node not None, + _Transform method not None, + id=None, + type=None, + mime_type=None, + encoding=None, + ns=None): + """ + Creates new <{ns}:EncryptedData /> node for encryption template. + """ + cdef xmlNode* c_node + cdef const_xmlChar* c_id = _b(id) + cdef const_xmlChar* c_type = _b(type) + cdef const_xmlChar* c_mtype = _b(mime_type) + cdef const_xmlChar* c_encoding = _b(encoding) + + c_node = xmlSecTmplEncDataCreate( + node._doc._c_doc, method.target, c_id, c_type, c_mtype, c_encoding) + + if ns is not None: + c_node.ns.prefix = xmlStrdup(_b(ns)) + return elementFactory(node._doc, c_node) + + +def encrypted_data_ensure_key_info(_Element node not None, id=None, ns=None): + """ + Adds <{ns}:KeyInfo/> to the node encNode. + """ + + cdef xmlNode* c_node + cdef const_xmlChar* c_id = _b(id) + + c_node = xmlSecTmplEncDataEnsureKeyInfo(node._c_node, c_id) + if ns is not None: + c_node.ns.prefix = xmlStrdup(_b(ns)) + + return elementFactory(node._doc, c_node) + + +def encrypted_data_ensure_cipher_value(_Element node not None): + cdef xmlNode* c_node + c_node = xmlSecTmplEncDataEnsureCipherValue(node._c_node) + + return elementFactory(node._doc, c_node) diff --git a/src/xmlsec/tree.pxd b/src/xmlsec/tree.pxd index 96688374..5ed86d6e 100644 --- a/src/xmlsec/tree.pxd +++ b/src/xmlsec/tree.pxd @@ -1,5 +1,4 @@ -from lxml.includes.tree cimport const_xmlChar, xmlNode, xmlID, xmlDoc, xmlAttr -from lxml.includes.dtdvalid cimport xmlValidCtxt +from lxml.includes.tree cimport const_xmlChar, xmlNode, xmlDoc cdef extern from "xmlsec.h": # xmlsec/xmltree.h @@ -50,8 +49,8 @@ cdef extern from "xmlsec.h": # xmlsec/xmltree.h # int xmlSecNodeEncodeAndSetContent(xmlNode* node, const_xmlChar* buffer) - # int xmlSecAddIDs(xmlDoc* document, xmlNode* node, const_xmlChar** ids) - + int xmlSecAddIDs(xmlDoc* document, xmlNode* node, const_xmlChar** ids) nogil + # int xmlSecGenerateAndAddID( # xmlNode* node, const_xmlChar* name, const_xmlChar* prefix) diff --git a/src/xmlsec/tree.pyx b/src/xmlsec/tree.pyx index 144a0487..2cf7557d 100644 --- a/src/xmlsec/tree.pyx +++ b/src/xmlsec/tree.pyx @@ -1,25 +1,26 @@ # -*- coding: utf-8 -*- from __future__ import absolute_import, unicode_literals, division +from libc cimport stdlib from lxml.includes.etreepublic cimport import_lxml__etree import_lxml__etree() from lxml.includes.etreepublic cimport _ElementTree, _Element, elementFactory -from lxml.includes.tree cimport const_xmlChar, xmlNode -from xmlsec.tree cimport * -from xmlsec.utils cimport * -from . import constants +from .tree cimport * +from .utils cimport * +from .constants import Namespace __all__ = [ 'find_child', 'find_parent', 'find_node', + 'add_ids' ] def find_child(_Element parent not None, name not None, - namespace=constants.Namespace.DS): + namespace=Namespace.DS): """ Searches a direct child of the parent node having given name and namespace href. @@ -31,7 +32,7 @@ def find_child(_Element parent not None, def find_parent(_Element node not None, name not None, - namespace=constants.Namespace.DS): + namespace=Namespace.DS): """ Searches the ancestors axis of the node for a node having given name and namespace href. @@ -43,7 +44,7 @@ def find_parent(_Element node not None, def find_node(_Element parent not None, name not None, - namespace=constants.Namespace.DS): + namespace=Namespace.DS): """ Searches all children of the parent node having given name and namespace href. @@ -51,3 +52,32 @@ def find_node(_Element parent not None, cdef xmlNode* c_node c_node = xmlSecFindNode(parent._c_node, _b(name), _b(namespace)) return elementFactory(parent._doc, c_node) if c_node else None + + + +def add_ids(_Element node, ids): + """register *ids* as ids used below *node*. + *ids* is a sequence of attribute names used as XML ids in the subtree + rooted at *node*. + A call to `addIds` may be necessary to make known which attributes + contain XML ids. This is the case, if a transform references + an id via `XPointer` or a self document uri and the id + inkey_data_formation is not available by other means (e.g. an associated + DTD or XML schema). + """ + + cdef const_xmlChar **lst + cdef int i, n + + n = len(ids) + lst = stdlib.malloc(sizeof(const_xmlChar*) * (n + 1)) + if lst == NULL: + raise MemoryError + try: + for i in range(n): + lst[i] = _b(ids[i]) + lst[n] = NULL + with nogil: + xmlSecAddIDs(node._doc._c_doc, node._c_node, lst) + finally: + stdlib.free(lst) diff --git a/src/xmlsec/utils.pxd b/src/xmlsec/utils.pxd index 5ac93b94..f5a2e878 100644 --- a/src/xmlsec/utils.pxd +++ b/src/xmlsec/utils.pxd @@ -21,6 +21,10 @@ cdef extern from "xmlsec.h": # xmlsec/xmlsec.h int xmlSecShutdown() nogil +cdef extern from "xmlsec.h": # xmlsec/errors.h + int xmlSecErrorsDefaultCallbackEnableOutput(int) nogil + + cdef extern from "xmlsec.h": # xmlsec/app.h int xmlSecCryptoInit() nogil int xmlSecCryptoShutdown() nogil diff --git a/src/xmlsec/utils.pyx b/src/xmlsec/utils.pyx index 54198a07..8287bad1 100644 --- a/src/xmlsec/utils.pyx +++ b/src/xmlsec/utils.pyx @@ -1,10 +1,12 @@ # -*- coding: utf-8 -*- from __future__ import absolute_import, unicode_literals, division -from xmlsec.utils cimport * + +from .utils cimport * __all__ = [ 'init', - 'shutdown' + 'shutdown', + 'enable_debug_trace' ] @@ -45,3 +47,7 @@ def shutdown(): r = xmlSecShutdown() return r == 0 + + +def enable_debug_trace(flag): + xmlSecErrorsDefaultCallbackEnableOutput(flag) diff --git a/tests/examples/base.py b/tests/examples/base.py index 4b2b42e7..6415dbb4 100644 --- a/tests/examples/base.py +++ b/tests/examples/base.py @@ -1,5 +1,4 @@ from os import path -import xmlsec from lxml import etree BASE_DIR = path.dirname(__file__) @@ -7,3 +6,16 @@ def parse_xml(name): return etree.parse(path.join(BASE_DIR, name)).getroot() + + +def compare(name, result): + # Parse the expected file. + xml = parse_xml(name) + + # Stringify the root, nodes of the two documents. + expected_text = etree.tostring(xml, pretty_print=False) + result_text = etree.tostring(result, pretty_print=False) + + # Compare the results. + assert expected_text == result_text + diff --git a/tests/examples/enc1-doc.xml b/tests/examples/enc1-doc.xml new file mode 100644 index 00000000..fd474859 --- /dev/null +++ b/tests/examples/enc1-doc.xml @@ -0,0 +1,7 @@ + + + +Hello, World! + \ No newline at end of file diff --git a/tests/examples/enc1-res.xml b/tests/examples/enc1-res.xml new file mode 100644 index 00000000..9499453b --- /dev/null +++ b/tests/examples/enc1-res.xml @@ -0,0 +1,22 @@ + + + + + + + + +UrTgE0UxQa8xevs4SyRA0rsibEz/ZFDjCBD+t4pKSdajB/cefYObZzqq2l41Q6R/ +tqYLht5hEBh26AHfjmQSJAL+eChXOt/EaOf63zzJedO90HGqIQyzOeOPURAl3Li8 +ivPyLVyocJDeVNeh7W+7kYwpFQ6PLuQxWsFFQXVoRAWbXHpZkSzVheR+5RpYJRTb +1UYXKxu8jg4NqbjucVMDIxUOzsVCDRyk8R8sQrM7D/H/N0y7DAY8oX/WZ45xLwUy +DY/U86tTpTn95NwHD10SLyrL6rpXdbEuoIQHhWLwV9uQxnJA/Pn1KZ+xXK/fePfP +26PBo/hUrN5pm5U8ycc4iw== + + + + +2pb5Mxd0f+AW56Cs3MfQ9HJkUVeliSi1hVCNCVHTKeMyC2VL6lPhQ9+L01aSeTSY + + + \ No newline at end of file diff --git a/tests/examples/enc2-res.xml b/tests/examples/enc2-res.xml new file mode 100644 index 00000000..6556248e --- /dev/null +++ b/tests/examples/enc2-res.xml @@ -0,0 +1,22 @@ + + + + + + + + +HJwrfL7kOIB0QaldMJdza1HitpLCjw+eoult1C6yExDXJ09zKaSQER+pUL9Vt5fm +d4Oitsf0CUNkjG1xWJdFsftqUIuvYGnkUNhT0vtqoYbdhJkCcB9cCwvTrww2+VTF +NIasTdechlSD1qQOR8uf6+S94Ae4PVSfWU+5YLTJFpMjR+OT7f6BSbYNv1By6Cko +G39WTSKTRcVDzcMxRepAGb59r508yKIJhwabCf3Opu+Ams7ia7BH4oa4ro9YSWwm +hAJ0CN4a6b5odcRbNvuHcwWSxpoysWKbOROQ0H4xC4nGZeL/AXlpSc8eNuNG+g6D +CTBwsOXCAEJYXPkTrnB3qQ== + + + + +4m5BRKEswOe8JISY7NrPGLBYv7Ay5pBV+nG6it51gz0= + + + \ No newline at end of file diff --git a/tests/examples/test_decrypt.py b/tests/examples/test_decrypt.py new file mode 100644 index 00000000..c5a51049 --- /dev/null +++ b/tests/examples/test_decrypt.py @@ -0,0 +1,37 @@ +from os import path +import xmlsec +from .base import parse_xml, BASE_DIR, compare + + +def test_decrypt1(): + manager = xmlsec.KeysManager() + filename = path.join(BASE_DIR, 'rsakey.pem') + key = xmlsec.Key.from_memory(open(filename, 'rb').read(), xmlsec.KeyFormat.PEM, None) + assert key is not None + manager.add_key(key) + + enc_ctx = xmlsec.EncryptionContext(manager) + + root = parse_xml("enc1-res.xml") + enc_data = xmlsec.tree.find_child(root, "EncryptedData", xmlsec.Namespace.ENC) + assert enc_data is not None + decrypted = enc_ctx.decrypt(enc_data) + assert decrypted.tag == "Data" + + compare("enc1-doc.xml", root) + + +def test_decrypt2(): + manager = xmlsec.KeysManager() + filename = path.join(BASE_DIR, 'rsakey.pem') + key = xmlsec.Key.from_memory(open(filename, 'rb').read(), xmlsec.KeyFormat.PEM, None) + assert key is not None + manager.add_key(key) + + enc_ctx = xmlsec.EncryptionContext(manager) + + root = parse_xml("enc2-res.xml") + enc_data = xmlsec.tree.find_child(root, xmlsec.constants.Node.ENCRYPTED_DATA, xmlsec.Namespace.ENC) + assert enc_data is not None + decrypted = enc_ctx.decrypt(enc_data) + assert decrypted.text == "\ntest\n" diff --git a/tests/examples/test_encrypt.py b/tests/examples/test_encrypt.py new file mode 100644 index 00000000..54b5d334 --- /dev/null +++ b/tests/examples/test_encrypt.py @@ -0,0 +1,80 @@ +from os import path +import xmlsec +from .base import parse_xml, BASE_DIR +from lxml import etree + + +def test_encrypt_xml(): + # Load the public cert + manager = xmlsec.KeysManager() + filename = path.join(BASE_DIR, 'rsacert.pem') + key = xmlsec.Key.from_memory(open(filename, 'rb').read(), xmlsec.KeyFormat.CERT_PEM, None) + assert key is not None + manager.add_key(key) + template = parse_xml('enc1-doc.xml') + assert template is not None + # Prepare for encryption + enc_data = xmlsec.template.encrypted_data_create( + template, xmlsec.Transform.AES128, type=xmlsec.EncryptionType.ELEMENT, ns="xenc") + + xmlsec.template.encrypted_data_ensure_cipher_value(enc_data) + key_info = xmlsec.template.encrypted_data_ensure_key_info(enc_data, ns="dsig") + enc_key = xmlsec.template.add_encrypted_key(key_info, xmlsec.Transform.RSA_OAEP) + xmlsec.template.encrypted_data_ensure_cipher_value(enc_key) + + data = template.find('./Data') + + assert data is not None + # Encrypt! + enc_ctx = xmlsec.EncryptionContext(manager) + enc_ctx.key = xmlsec.Key.generate(xmlsec.KeyData.AES, 128, xmlsec.KeyDataType.SESSION) + enc_data = enc_ctx.encrypt_xml(enc_data, data) + assert enc_data is not None + enc_method = xmlsec.tree.find_child(enc_data, xmlsec.Node.ENCRYPTION_METHOD, xmlsec.Namespace.ENC) + assert enc_method is not None + assert enc_method.get("Algorithm") == "http://www.w3.org/2001/04/xmlenc#aes128-cbc" + key_info = xmlsec.tree.find_child(enc_data, xmlsec.Node.KEY_INFO, xmlsec.Namespace.DS) + assert key_info is not None + enc_method = xmlsec.tree.find_node(key_info, xmlsec.Node.ENCRYPTION_METHOD, xmlsec.Namespace.ENC) + assert enc_method is not None + assert enc_method.get("Algorithm") == "http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p" + cipher_value = xmlsec.tree.find_node(key_info, xmlsec.Node.CIPHER_VALUE, xmlsec.Namespace.ENC) + assert cipher_value is not None + + +def test_encrypt_binary(): + # Load the public cert + manager = xmlsec.KeysManager() + filename = path.join(BASE_DIR, 'rsacert.pem') + key = xmlsec.Key.from_memory(open(filename, 'rb').read(), xmlsec.KeyFormat.CERT_PEM, None) + assert key is not None + manager.add_key(key) + template = etree.Element("root") + assert template is not None + # Prepare for encryption + enc_data = xmlsec.template.encrypted_data_create( + template, xmlsec.Transform.AES128, type=xmlsec.EncryptionType.CONTENT, ns="xenc", + mime_type="binary/octet-stream") + + xmlsec.template.encrypted_data_ensure_cipher_value(enc_data) + key_info = xmlsec.template.encrypted_data_ensure_key_info(enc_data, ns="dsig") + enc_key = xmlsec.template.add_encrypted_key(key_info, xmlsec.Transform.RSA_OAEP) + xmlsec.template.encrypted_data_ensure_cipher_value(enc_key) + + # Encrypt! + enc_ctx = xmlsec.EncryptionContext(manager) + enc_ctx.key = xmlsec.Key.generate(xmlsec.KeyData.AES, 128, xmlsec.KeyDataType.SESSION) + enc_data = enc_ctx.encrypt_binary(enc_data, b'test') + assert enc_data is not None + assert enc_data.tag == "{%s}%s" % (xmlsec.Namespace.ENC, xmlsec.Node.ENCRYPTED_DATA) + print(xmlsec.Node.ENCRYPTION_METHOD) + enc_method = xmlsec.tree.find_child(enc_data, xmlsec.Node.ENCRYPTION_METHOD, xmlsec.Namespace.ENC) + assert enc_method is not None + assert enc_method.get("Algorithm") == "http://www.w3.org/2001/04/xmlenc#aes128-cbc" + key_info = xmlsec.tree.find_child(enc_data, xmlsec.Node.KEY_INFO, xmlsec.Namespace.DS) + assert key_info is not None + enc_method = xmlsec.tree.find_node(key_info, xmlsec.Node.ENCRYPTION_METHOD, xmlsec.Namespace.ENC) + assert enc_method is not None + assert enc_method.get("Algorithm") == "http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p" + cipher_value = xmlsec.tree.find_node(key_info, xmlsec.Node.CIPHER_VALUE, xmlsec.Namespace.ENC) + assert cipher_value is not None diff --git a/tests/examples/test_sign.py b/tests/examples/test_sign.py index 7359b5ba..c3041b32 100644 --- a/tests/examples/test_sign.py +++ b/tests/examples/test_sign.py @@ -1,19 +1,6 @@ from os import path import xmlsec -from lxml import etree -from .base import parse_xml, BASE_DIR - - -def compare(name, result): - # Parse the expected file. - xml = parse_xml(name) - - # Stringify the root, nodes of the two documents. - expected_text = etree.tostring(xml) - result_text = etree.tostring(result) - - # Compare the results. - assert expected_text == result_text +from .base import parse_xml, compare, BASE_DIR def test_sign_template_pem(): @@ -48,6 +35,7 @@ def test_sign_template_pem(): assert ctx.key is not None assert ctx.key.name == path.basename(filename) + del key # Sign the template. ctx.sign(signature_node) @@ -168,3 +156,23 @@ def test_sign_generated_template_pem_with_x509(): # Assert the contents of the XML document against the expected result. compare('sign3-res.xml', template) + + +def test_sign_binary(): + ctx = xmlsec.SignatureContext() + filename = path.join(BASE_DIR, 'rsakey.pem') + key = xmlsec.Key.from_file(filename, xmlsec.KeyFormat.PEM) + assert key is not None + + key.name = path.basename(filename) + + # Set the key on the context. + ctx.key = key + + assert ctx.key is not None + assert ctx.key.name == path.basename(filename) + + data = b'\xa8f4dP\x82\x02\xd3\xf5.\x02\xc1\x03\xef\xc4\x86\xabC\xec\xb7>\x8e\x1f\xa3\xa3\xc5\xb9qc\xc2\x81\xb1-\xa4B\xdf\x03>\xba\xd1' + expected_sign = b"h\xcb\xb1\x82\xfa`e\x89x\xe5\xc5ir\xd6\xd1Q\x9a\x0b\xeaU_G\xcc'\xa4c\xa3>\x9b27\xbf^`\xa7p\xfb\x98\xcb\x81\xd2\xb1\x0c'\x9d\xe2\n\xec\xb2<\xcf@\x98=\xe0}O8}fy\xc2\xc4\xe9\xec\x87\xf6\xc1\xde\xfd\x96*o\xab\xae\x12\xc9{\xcc\x0e\x93y\x9a\x16\x80o\x92\xeb\x02^h|\xa0\x9b<\x99_\x97\xcb\xe27\xe9u\xc3\xfa_\xcct/sTb\xa0\t\xd3\x93'\xb4\xa4\x0ez\xcbL\x14D\xdb\xe3\x84\x886\xe9J[\xe7\xce\xc0\xb1\x99\x07\x17{\xc6:\xff\x1dt\xfd\xab^2\xf7\x9e\xa4\xccT\x8e~b\xdb\x9a\x04\x04\xbaM\xfa\xbd\xec)z\xbb\x89\xd7\xb2Q\xac\xaf\x13\xdcD\xcd\n6\x92\xfao\xb9\xd9\x96$\xce\xa6\xcf\xf8\xe4Bb60\xf5\xd2a\xb1o\x8c\x0f\x8bl\x88vh\xb5h\xfa\xfa\xb66\xedQ\x10\xc4\xef\xfa\x81\xf0\xc9.^\x98\x1ePQS\x9e\xafAy\x90\xe4\x95\x03V\xc2\xa0\x18\xa5d\xc2\x15*\xb6\xd7$\xc0\t2\xa1" + sign = ctx.sign_binary(data, xmlsec.Transform.RSA_SHA1) + assert sign == expected_sign diff --git a/tests/examples/test_verify.py b/tests/examples/test_verify.py index ae291605..4d37fd7d 100644 --- a/tests/examples/test_verify.py +++ b/tests/examples/test_verify.py @@ -1,7 +1,6 @@ from os import path from pytest import mark import xmlsec -from lxml import etree from .base import parse_xml, BASE_DIR @@ -38,4 +37,23 @@ def test_verify_with_pem_file(index): assert ctx.key.name == path.basename(filename) # Verify the signature. - assert ctx.verify(signature_node) + ctx.verify(signature_node) + + +def test_validate_binary_sign(): + ctx = xmlsec.SignatureContext() + filename = path.join(BASE_DIR, 'rsakey.pem') + key = xmlsec.Key.from_file(filename, xmlsec.KeyFormat.PEM) + assert key is not None + + key.name = path.basename(filename) + + # Set the key on the context. + ctx.key = key + + assert ctx.key is not None + assert ctx.key.name == path.basename(filename) + + data = b'\xa8f4dP\x82\x02\xd3\xf5.\x02\xc1\x03\xef\xc4\x86\xabC\xec\xb7>\x8e\x1f\xa3\xa3\xc5\xb9qc\xc2\x81\xb1-\xa4B\xdf\x03>\xba\xd1' + sign = b"h\xcb\xb1\x82\xfa`e\x89x\xe5\xc5ir\xd6\xd1Q\x9a\x0b\xeaU_G\xcc'\xa4c\xa3>\x9b27\xbf^`\xa7p\xfb\x98\xcb\x81\xd2\xb1\x0c'\x9d\xe2\n\xec\xb2<\xcf@\x98=\xe0}O8}fy\xc2\xc4\xe9\xec\x87\xf6\xc1\xde\xfd\x96*o\xab\xae\x12\xc9{\xcc\x0e\x93y\x9a\x16\x80o\x92\xeb\x02^h|\xa0\x9b<\x99_\x97\xcb\xe27\xe9u\xc3\xfa_\xcct/sTb\xa0\t\xd3\x93'\xb4\xa4\x0ez\xcbL\x14D\xdb\xe3\x84\x886\xe9J[\xe7\xce\xc0\xb1\x99\x07\x17{\xc6:\xff\x1dt\xfd\xab^2\xf7\x9e\xa4\xccT\x8e~b\xdb\x9a\x04\x04\xbaM\xfa\xbd\xec)z\xbb\x89\xd7\xb2Q\xac\xaf\x13\xdcD\xcd\n6\x92\xfao\xb9\xd9\x96$\xce\xa6\xcf\xf8\xe4Bb60\xf5\xd2a\xb1o\x8c\x0f\x8bl\x88vh\xb5h\xfa\xfa\xb66\xedQ\x10\xc4\xef\xfa\x81\xf0\xc9.^\x98\x1ePQS\x9e\xafAy\x90\xe4\x95\x03V\xc2\xa0\x18\xa5d\xc2\x15*\xb6\xd7$\xc0\t2\xa1" + ctx.verify_binary(data, xmlsec.Transform.RSA_SHA1, sign)