diff --git a/.github/workflows/cache_libs.yml b/.github/workflows/cache_libs.yml index 79b76f98..98ed1a4e 100644 --- a/.github/workflows/cache_libs.yml +++ b/.github/workflows/cache_libs.yml @@ -20,7 +20,7 @@ on: required: false type: string XMLSEC1_VERSION: - default: "1.3.12" + default: "1.3.13" required: false type: string ZLIB_VERSION: diff --git a/build_support/lib_xmlsec_dependency_builder.py b/build_support/lib_xmlsec_dependency_builder.py index b50513e1..db624c96 100644 --- a/build_support/lib_xmlsec_dependency_builder.py +++ b/build_support/lib_xmlsec_dependency_builder.py @@ -36,7 +36,7 @@ def triplet(self) -> str: class LibXmlsecDependencyBuilder: - WINDOWS_LIBS_DOWNLOAD_RELEASE_URL = 'https://github.com/mxamin/python-xmlsec-win-binaries/releases/download/2026.10.23/' + WINDOWS_LIBS_DOWNLOAD_RELEASE_URL = 'https://github.com/mxamin/python-xmlsec-win-binaries/releases/download/2026.10.28/' LIB_VERSION_ENV_VARS: ClassVar[dict[str, str]] = { 'libiconv_version': 'PYXMLSEC_LIBICONV_VERSION', 'libxml2_version': 'PYXMLSEC_LIBXML2_VERSION', @@ -50,7 +50,7 @@ class LibXmlsecDependencyBuilder: 'libxml2_version': '2.14.6', # Make sure it matches with lxml 'libxslt_version': '1.1.43', # 1.1.44+ requires libxml2 >= 2.15.1 'openssl_version': '3.6.5', - 'xmlsec1_version': '1.3.12', + 'xmlsec1_version': '1.3.13', 'zlib_version': '1.3.2', } WINDOWS_DEFAULT_LIB_VERSIONS: ClassVar[dict[str, str]] = { @@ -58,7 +58,7 @@ class LibXmlsecDependencyBuilder: 'libxml2_version': '2.11.9-8', # Make sure it matches with lxml 'libxslt_version': '1.1.45', 'openssl_version': '3.5.9', - 'xmlsec1_version': '1.3.12', + 'xmlsec1_version': '1.3.13', 'zlib_version': '1.3.2', } @@ -390,6 +390,8 @@ def _build_xmlsec1(self, env, prefix_arg, host_arg): './configure', prefix_arg, '--disable-shared', + '--with-pic', + '--disable-apps', '--disable-gost', '--enable-md5', '--enable-ripemd160', diff --git a/doc/source/modules/constants.rst b/doc/source/modules/constants.rst index 3df6b50f..23fafd9e 100644 --- a/doc/source/modules/constants.rst +++ b/doc/source/modules/constants.rst @@ -292,6 +292,31 @@ Transforms Transform could be used for operation. +.. data:: xmlsec.constants.TransformUriTypeNone + + No URI types are allowed. + +.. data:: xmlsec.constants.TransformUriTypeEmpty + + Empty URI (:xml:`URI=""`), i.e. the document containing the signature. + +.. data:: xmlsec.constants.TransformUriTypeSameDocument + + Same-document URI (:xml:`URI="#id"`). + +.. data:: xmlsec.constants.TransformUriTypeLocal + + Local file URI (:xml:`URI="file:///path"`). + +.. data:: xmlsec.constants.TransformUriTypeRemote + + Any other non-empty URI, e.g. :xml:`URI="http://..."`, :xml:`URI="cid:..."` + or a relative path such as :xml:`URI="other.xml"`. + +.. data:: xmlsec.constants.TransformUriTypeAny + + Any URI type. + .. data:: xmlsec.constants.TransformInclC14N The regular (inclusive) C14N without comments transform klass. diff --git a/src/constants.c b/src/constants.c index bd1fa5e0..e0e20207 100644 --- a/src/constants.c +++ b/src/constants.c @@ -282,6 +282,13 @@ int PyXmlSec_ConstantsModule_Init(PyObject* package) { if (PYXMLSEC_ADD_INT_CONSTANT(TransformUsageEncryptionMethod) < 0) goto ON_FAIL; if (PYXMLSEC_ADD_INT_CONSTANT(TransformUsageAny) < 0) goto ON_FAIL; + if (PYXMLSEC_ADD_INT_CONSTANT(TransformUriTypeNone) < 0) goto ON_FAIL; + if (PYXMLSEC_ADD_INT_CONSTANT(TransformUriTypeEmpty) < 0) goto ON_FAIL; + if (PYXMLSEC_ADD_INT_CONSTANT(TransformUriTypeSameDocument) < 0) goto ON_FAIL; + if (PYXMLSEC_ADD_INT_CONSTANT(TransformUriTypeLocal) < 0) goto ON_FAIL; + if (PYXMLSEC_ADD_INT_CONSTANT(TransformUriTypeRemote) < 0) goto ON_FAIL; + if (PYXMLSEC_ADD_INT_CONSTANT(TransformUriTypeAny) < 0) goto ON_FAIL; + #undef PYXMLSEC_ADD_INT_CONSTANT #define PYXMLSEC_DECLARE_NAMESPACE(var, name) \ diff --git a/src/ds.c b/src/ds.c index d0b4bdf9..7623fbcf 100644 --- a/src/ds.c +++ b/src/ds.c @@ -517,6 +517,40 @@ static PyObject* PyXmlSec_SignatureContextSetEnabledKeyData(PyObject* self, PyOb return NULL; } +static const char PyXmlSec_SignatureContextEnabledReferenceUris__doc__[] = \ + "Bitmask of :samp:`TransformUriType{XXX}` constants allowed in :xml:`` URI.\n\n" + ".. note:: since xmlsec1 1.3.13 only empty and same-document URIs are enabled by default. " + "Dereferencing local or remote URIs (e.g. through :func:`~xmlsec.register_callbacks`) requires " + "an explicit opt-in.\n"; +static PyObject* PyXmlSec_SignatureContextEnabledReferenceUrisGet(PyObject* self, void* closure) { + PyXmlSec_SignatureContext* ctx = (PyXmlSec_SignatureContext*)self; + return PyLong_FromUnsignedLong(ctx->handle->enabledReferenceUris); +} + +static int PyXmlSec_SignatureContextEnabledReferenceUrisSet(PyObject* self, PyObject* value, void* closure) { + PyXmlSec_SignatureContext* ctx = (PyXmlSec_SignatureContext*)self; + unsigned long uris; + + if (value == NULL) { + PyErr_SetString(PyExc_TypeError, "cannot delete enabled_reference_uris."); + return -1; + } + if (!PyLong_Check(value)) { + PyErr_SetString(PyExc_TypeError, "instance of *int* expected."); + return -1; + } + uris = PyLong_AsUnsignedLong(value); + if (PyErr_Occurred()) { + return -1; + } + if (uris > xmlSecTransformUriTypeAny) { + PyErr_SetString(PyExc_ValueError, "invalid uri type mask."); + return -1; + } + ctx->handle->enabledReferenceUris = (xmlSecTransformUriType)uris; + return 0; +} + static PyGetSetDef PyXmlSec_SignatureContextGetSet[] = { { "key", @@ -525,6 +559,13 @@ static PyGetSetDef PyXmlSec_SignatureContextGetSet[] = { (char*)PyXmlSec_SignatureContextKey__doc__, NULL }, + { + "enabled_reference_uris", + (getter)PyXmlSec_SignatureContextEnabledReferenceUrisGet, + (setter)PyXmlSec_SignatureContextEnabledReferenceUrisSet, + (char*)PyXmlSec_SignatureContextEnabledReferenceUris__doc__, + NULL + }, {NULL} /* Sentinel */ }; diff --git a/src/enc.c b/src/enc.c index 42195dd3..e85e4d0f 100644 --- a/src/enc.c +++ b/src/enc.c @@ -62,6 +62,17 @@ static int PyXmlSec_EncryptionContext__init__(PyObject* self, PyObject* args, Py ctx->handle->keyInfoReadCtx.flags = XMLSEC_KEYINFO_FLAGS_LAX_KEY_SEARCH; ctx->handle->keyInfoWriteCtx.flags = XMLSEC_KEYINFO_FLAGS_LAX_KEY_SEARCH; + // xmlsec 1.3.13 no longer copies these flags to the read context of the nested + // context used to encrypt , so the session key encryption key + // lookup becomes strict. Create the nested context upfront (it is reused by + // xmlsec) and enable lax key search on it as well. + if (xmlSecKeyInfoCtxCreateEncCtx(&(ctx->handle->keyInfoWriteCtx)) < 0) { + // ctx->manager is owned by ctx now and released on dealloc + PyXmlSec_SetLastError("failed to create the nested encryption context"); + return -1; + } + ctx->handle->keyInfoWriteCtx.encCtx->keyInfoReadCtx.flags = XMLSEC_KEYINFO_FLAGS_LAX_KEY_SEARCH; + return 0; ON_FAIL: PYXMLSEC_DEBUGF("%p: init enc context - failed", self); diff --git a/src/main.c b/src/main.c index c7dac2b5..b91365e0 100644 --- a/src/main.c +++ b/src/main.c @@ -14,6 +14,7 @@ #include #include +#include #include #include #include @@ -56,6 +57,12 @@ static void PyXmlSec_Free(int what) { } static int PyXmlSec_Init(void) { + // Already initialized (e.g. on module import). Since xmlsec1 1.3.13, + // calling xmlSecInit() again without a shutdown fails. + if (free_mode == _PYXMLSEC_FREE_ALL) { + return 0; + } + if (xmlSecInit() < 0) { PyXmlSec_SetLastError("cannot initialize xmlsec library."); PyXmlSec_Free(_PYXMLSEC_FREE_NONE); diff --git a/src/xmlsec/__init__.pyi b/src/xmlsec/__init__.pyi index 9cfc8cc6..6b7260ea 100644 --- a/src/xmlsec/__init__.pyi +++ b/src/xmlsec/__init__.pyi @@ -68,6 +68,7 @@ class KeysManager: class SignatureContext: key: Key | None + enabled_reference_uris: int def enable_reference_transform(self, transform: Transform) -> None: ... def enable_signature_transform(self, transform: Transform) -> None: ... def register_id(self, node: _Element, id_attr: str = ..., id_ns: str | None = ...) -> None: ... diff --git a/src/xmlsec/constants.pyi b/src/xmlsec/constants.pyi index a9254ddd..fda62320 100644 --- a/src/xmlsec/constants.pyi +++ b/src/xmlsec/constants.pyi @@ -129,6 +129,12 @@ TransformSha224: Final[__Transform] TransformSha256: Final[__Transform] TransformSha384: Final[__Transform] TransformSha512: Final[__Transform] +TransformUriTypeAny: Final[int] +TransformUriTypeEmpty: Final[int] +TransformUriTypeLocal: Final[int] +TransformUriTypeNone: Final[int] +TransformUriTypeRemote: Final[int] +TransformUriTypeSameDocument: Final[int] TransformUsageAny: Final[int] TransformUsageC14NMethod: Final[int] TransformUsageDSigTransform: Final[int] diff --git a/tests/test_ds.py b/tests/test_ds.py index dd0657d3..519c5f22 100644 --- a/tests/test_ds.py +++ b/tests/test_ds.py @@ -39,6 +39,23 @@ def test_set_key_bad_type(self): with self.assertRaisesRegex(TypeError, r'instance of \*xmlsec.Key\* expected.'): ctx.key = '' + def test_set_enabled_reference_uris(self): + ctx = xmlsec.SignatureContext() + uris = consts.TransformUriTypeEmpty | consts.TransformUriTypeSameDocument | consts.TransformUriTypeRemote + ctx.enabled_reference_uris = uris + self.assertEqual(ctx.enabled_reference_uris, uris) + + def test_set_enabled_reference_uris_bad_args(self): + ctx = xmlsec.SignatureContext() + with self.assertRaisesRegex(TypeError, r'instance of \*int\* expected.'): + ctx.enabled_reference_uris = '' + with self.assertRaises(OverflowError): + ctx.enabled_reference_uris = -1 + with self.assertRaisesRegex(ValueError, 'invalid uri type mask'): + ctx.enabled_reference_uris = consts.TransformUriTypeAny + 1 + with self.assertRaisesRegex(TypeError, 'cannot delete'): + del ctx.enabled_reference_uris + def test_set_invalid_key(self): ctx = xmlsec.SignatureContext(manager=xmlsec.KeysManager()) with self.assertRaisesRegex(TypeError, 'empty key.'): diff --git a/tests/test_main.py b/tests/test_main.py index 8f1501f2..a63a4871 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -48,6 +48,8 @@ def _sign_doc(self): xmlsec.template.add_reference(sign, consts.TransformSha1, uri='cid:123456') ctx = xmlsec.SignatureContext() + # xmlsec1 >= 1.3.13 only allows empty and same-document reference URIs by default + ctx.enabled_reference_uris = consts.TransformUriTypeAny ctx.key = xmlsec.Key.from_file(self.path('rsakey.pem'), format=consts.KeyDataFormatPem) ctx.sign(sign) return sign