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