Script 'mail_helper' called by obssrc
Hello community,
here is the log from the commit of package python-M2Crypto for openSUSE:Factory
checked in at 2026-10-06 19:13:23
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-M2Crypto (Old)
and /work/SRC/openSUSE:Factory/.python-M2Crypto.new.3698736 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-M2Crypto"
Tue Oct 6 19:13:23 2026 rev:61 rq:1382336 version:0.50.0
Changes:
--------
--- /work/SRC/openSUSE:Factory/python-M2Crypto/python-M2Crypto.changes
2026-08-21 16:50:47.023543728 +0200
+++
/work/SRC/openSUSE:Factory/.python-M2Crypto.new.3698736/python-M2Crypto.changes
2026-10-06 19:13:27.634058516 +0200
@@ -1,0 +2,16 @@
+Sun Oct 4 15:00:00 UTC 2026 - Dirk Müller <[email protected]>
+
+- update to 0.50.0:
+ * switch to Codefloe
+ * isolate OpenSSL providers in private contexts
+ * disable legacy ENGINE bindings with OpenSSL 3 deprecations
+ * manage BIO ownership when attaching to SSL connections
+ * repair Windows CI pipeline and improve setup.py robustness
+ * record security-related behavior changes
+ * emit DeprecationWarning when RC4 cipher is instantiated
+ * disable TLSv1.0 and TLSv1.1 by default
+ * prevent out-of-bounds read in BN conversions
+ * preserve errno from gettimeofday failures
+ * restore timed read retries
+
+-------------------------------------------------------------------
Old:
----
m2crypto-0.49.0.tar.gz
New:
----
m2crypto-0.50.0.tar.gz
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Other differences:
------------------
++++++ python-M2Crypto.spec ++++++
--- /var/tmp/diff_new_pack.51N6JP/_old 2026-10-06 19:13:28.420091401 +0200
+++ /var/tmp/diff_new_pack.51N6JP/_new 2026-10-06 19:13:28.421091443 +0200
@@ -21,7 +21,7 @@
%define skip_python312 1
%{?sle15allpythons}
Name: python-M2Crypto
-Version: 0.49.0
+Version: 0.50.0
Release: 0
Summary: Crypto and SSL toolkit for Python
License: BSD-2-Clause
++++++ m2crypto-0.49.0.tar.gz -> m2crypto-0.50.0.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/m2crypto-0.49.0/CHANGES new/m2crypto-0.50.0/CHANGES
--- old/m2crypto-0.49.0/CHANGES 2026-08-16 00:26:35.166126700 +0200
+++ new/m2crypto-0.50.0/CHANGES 2026-09-09 22:27:01.087097200 +0200
@@ -1,3 +1,18 @@
+0.50.0 - 2026-09-09
+-------------------
+
+- switch to Codefloe
+- isolate OpenSSL providers in private contexts
+- disable legacy ENGINE bindings with OpenSSL 3 deprecations
+- manage BIO ownership when attaching to SSL connections
+- repair Windows CI pipeline and improve setup.py robustness
+- record security-related behavior changes
+- emit DeprecationWarning when RC4 cipher is instantiated
+- disable TLSv1.0 and TLSv1.1 by default
+- prevent out-of-bounds read in BN conversions
+- preserve errno from gettimeofday failures
+- restore timed read retries
+
0.49.0 - 2026-08-15
-------------------
@@ -6,6 +21,9 @@
release. Also, it resolves compatiblility with new Fedora OpenSSL 4.0
and Swig 4.5.
+- prevent out-of-bounds reads in BIGNUM hexadecimal and decimal conversions
+- disable TLS 1.0 and TLS 1.1 by default
+- deprecate RC4 and warn when it is instantiated
- defer unavailable ENGINE constant lookup
- fixes problematic behaviour of pkcs11-provider
- support TLS 1.0 contexts with modern OpenSSL
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/m2crypto-0.49.0/PKG-INFO new/m2crypto-0.50.0/PKG-INFO
--- old/m2crypto-0.49.0/PKG-INFO 2026-08-16 00:26:58.389001100 +0200
+++ new/m2crypto-0.50.0/PKG-INFO 2026-09-09 22:27:40.382026200 +0200
@@ -1,6 +1,6 @@
Metadata-Version: 2.4
Name: M2Crypto
-Version: 0.49.0
+Version: 0.50.0
Summary: A Python crypto and SSL toolkit
Author: Ng Pheng Siong
Author-email: [email protected]
@@ -8,10 +8,10 @@
Maintainer-email: [email protected]
License: BSD-2-Clause
Project-URL: home_page, https://sr.ht/~mcepl/m2crypto/
-Project-URL: source, https://codeberg.org/mcepl/m2crypto/
+Project-URL: source, https://codefloe.com/mcepl/m2crypto/
Project-URL: documentation, https://m2crypto.readthedocs.io/en/latest/
Project-URL: report, https://lists.sr.ht/~mcepl/m2crypto
-Project-URL: changelog,
https://codeberg.org/mcepl/m2crypto/src/branch/master/CHANGES
+Project-URL: changelog,
https://codefloe.com/mcepl/m2crypto/src/branch/master/CHANGES
Keywords: cryptography,openssl
Classifier: Development Status :: 5 - Production/Stable
Classifier: Intended Audience :: Developers
@@ -41,7 +41,7 @@
<https://todo.sr.ht/~mcepl/m2crypto> (R/O, please\
report issues via the email list).\
**maintainer**: "Matěj Cepl"\
-**web-Site**: <https://codeberg.org/mcepl/m2crypto/>
+**web-Site**: <https://codefloe.com/mcepl/m2crypto/>
# M2Crypto
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/m2crypto-0.49.0/README.md
new/m2crypto-0.50.0/README.md
--- old/m2crypto-0.49.0/README.md 2026-08-16 00:26:35.166126700 +0200
+++ new/m2crypto-0.50.0/README.md 2026-09-09 22:27:01.088097300 +0200
@@ -5,7 +5,7 @@
<https://todo.sr.ht/~mcepl/m2crypto> (R/O, please\
report issues via the email list).\
**maintainer**: "Matěj Cepl"\
-**web-Site**: <https://codeberg.org/mcepl/m2crypto/>
+**web-Site**: <https://codefloe.com/mcepl/m2crypto/>
# M2Crypto
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/m2crypto-0.49.0/setup.cfg
new/m2crypto-0.50.0/setup.cfg
--- old/m2crypto-0.49.0/setup.cfg 2026-08-16 00:26:58.390001000 +0200
+++ new/m2crypto-0.50.0/setup.cfg 2026-09-09 22:27:40.383026100 +0200
@@ -31,10 +31,10 @@
Programming Language :: Python :: 3.12
project_urls =
home_page = https://sr.ht/~mcepl/m2crypto/
- source = https://codeberg.org/mcepl/m2crypto/
+ source = https://codefloe.com/mcepl/m2crypto/
documentation = https://m2crypto.readthedocs.io/en/latest/
report = https://lists.sr.ht/~mcepl/m2crypto
- changelog =
https://codeberg.org/mcepl/m2crypto/src/branch/master/CHANGES
+ changelog =
https://codefloe.com/mcepl/m2crypto/src/branch/master/CHANGES
[options]
zip_safe = False
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/m2crypto-0.49.0/setup.py new/m2crypto-0.50.0/setup.py
--- old/m2crypto-0.49.0/setup.py 2026-08-16 00:26:35.173126700 +0200
+++ new/m2crypto-0.50.0/setup.py 2026-09-09 22:27:01.108097300 +0200
@@ -115,7 +115,7 @@
else:
crypt_lib = ctypes.cdll.LoadLibrary("libssl.so")
log.info(f"crypt_lib = {crypt_lib}")
- except (AttributeError, FileNotFoundError):
+ except Exception:
ver = None
file = os.path.join(ossldir, "include", "openssl", "opensslv.h")
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/m2crypto-0.49.0/src/M2Crypto/BIO.py
new/m2crypto-0.50.0/src/M2Crypto/BIO.py
--- old/m2crypto-0.49.0/src/M2Crypto/BIO.py 2026-08-16 00:26:35.174126600
+0200
+++ new/m2crypto-0.50.0/src/M2Crypto/BIO.py 2026-09-09 22:27:01.109097200
+0200
@@ -31,6 +31,11 @@
_close_cb: Optional[Callable] = None,
) -> None:
self.bio = bio
+ # Whether this Python wrapper owns the native BIO pointer. A value of
+ # 1 makes __del__() call m2.bio_free() for self.bio; a value of 0
+ # means that another object, usually OpenSSL after a BIO is passed to
+ # SSL_set_bio(), owns the pointer. In the latter case the wrapper must
+ # stay alive if its pointer is still in use, but must not free it.
self._pyfree = _pyfree
self._close_cb = _close_cb
self.closed = 0
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/m2crypto-0.49.0/src/M2Crypto/Provider.py
new/m2crypto-0.50.0/src/M2Crypto/Provider.py
--- old/m2crypto-0.49.0/src/M2Crypto/Provider.py 2026-08-16
00:26:35.174126600 +0200
+++ new/m2crypto-0.50.0/src/M2Crypto/Provider.py 2026-09-09
22:27:01.110097200 +0200
@@ -1,11 +1,14 @@
-"""
-M2Crypto wrapper for OpenSSL PROVIDER API.
+"""M2Crypto wrapper for the OpenSSL provider API.
+
+Each provider uses a private OpenSSL library context so its implementations do
+not affect algorithm selection in the process-wide default context.
"""
import re
from typing import Optional
from M2Crypto import EVP, X509, m2
+from M2Crypto import types as C
class ProviderError(ValueError):
@@ -27,29 +30,64 @@
:param _id: A string identifying the OpenSSL provider to load.
:raises ProviderError: If the provider fails to load.
"""
+ self._context = m2.provider_context_new()
+ self._supporting_providers = []
+ self._ptr = None
+ if self._context is None:
+ raise ProviderError("Failed to create an OpenSSL library context")
+
try:
- self._ptr = m2.provider_load(_id)
- except ProviderError:
+ # Explicitly loading any provider disables OpenSSL's automatic
+ # loading of the default provider in this library context. Load
+ # the providers needed for encoders, digests, and other support
+ # operations alongside the requested provider.
+ for name in ("default", "base"):
+ if name != _id:
+ supporting_provider = m2.provider_load(self._context, name)
+ if supporting_provider is None:
+ raise ProviderError(f"Failed to load OpenSSL provider
'{name}'")
+ self._supporting_providers.append(supporting_provider)
+
+ self._ptr = m2.provider_load(self._context, _id)
+ if self._ptr is None:
+ raise ProviderError(f"Failed to load OpenSSL provider '{_id}'")
+ except Exception:
+ self._close()
raise
- if self._ptr is None:
- raise ProviderError(f"Failed to load OpenSSL provider '{_id}'")
-
def __del__(self) -> None:
- """
- Clean up the provider by unloading it from memory.
-
- Automatically called when the Provider object is garbage collected.
- Ensures that the underlying OpenSSL provider is properly unloaded
- to free system resources.
- """
+ """Unload the providers and free their private library context."""
try:
- if getattr(self, "_ptr", None) is not None:
- m2.provider_unload(self._ptr) # type: ignore[arg-type]
+ self._close()
except Exception:
# Destructors must not raise.
pass
+ def _close(self) -> None:
+ provider = getattr(self, "_ptr", None)
+ if provider is not None:
+ m2.provider_unload(provider)
+ self._ptr = None
+
+ supporting_providers = getattr(self, "_supporting_providers", [])
+ while supporting_providers:
+ m2.provider_unload(supporting_providers.pop())
+
+ context = getattr(self, "_context", None)
+ if context is not None:
+ m2.provider_context_free(context)
+ self._context = None
+
+ def _context_ptr(self) -> C.OSSL_LIB_CTX:
+ if self._context is None:
+ raise ProviderError("Provider is closed")
+ return self._context
+
+ def _provider_ptr(self) -> C.OSSL_PROVIDER:
+ if self._ptr is None:
+ raise ProviderError("Provider is closed")
+ return self._ptr
+
def load_key(self, uri: str) -> EVP.PKey:
"""
Load a private or public key from a provider using the given URI.
@@ -70,11 +108,13 @@
"Key URI should indicate " + "'type=private' or 'type=public'"
)
- cptr = m2.provider_load_key(uri)
+ cptr = m2.provider_load_key(self._context_ptr(), uri)
if not cptr:
raise ProviderError("Key or card not found")
- return EVP.PKey(cptr, _pyfree=1)
+ pkey = EVP.PKey(cptr, _pyfree=1)
+ setattr(pkey, "_provider_owner", self)
+ return pkey
def load_certificate(self, uri: str) -> X509.X509:
"""
@@ -88,11 +128,13 @@
if not isinstance(uri, str):
raise ProviderError(f"Wrong type {type(uri)} != str for uri")
- cptr = m2.provider_load_certificate(uri)
+ cptr = m2.provider_load_certificate(self._context_ptr(), uri)
if not cptr:
raise ProviderError("Certificate or card not found")
- return X509.X509(cptr, _pyfree=1)
+ certificate = X509.X509(cptr, _pyfree=1)
+ setattr(certificate, "_provider_owner", self)
+ return certificate
def generate_rsa_key_pair(
self, bits: int = 2048, exponent: int = 65537
@@ -110,11 +152,15 @@
if not isinstance(exponent, int) or exponent < 3:
raise ProviderError(f"Invalid exponent value: {exponent}")
- cptr = m2.provider_generate_rsa_key_pair(bits, exponent, self._ptr) #
type: ignore[arg-type]
+ cptr = m2.provider_generate_rsa_key_pair(
+ bits, exponent, self._context_ptr(), self._provider_ptr()
+ )
if not cptr:
raise ProviderError("Failed to generate RSA key pair")
- return EVP.PKey(cptr, _pyfree=1)
+ pkey = EVP.PKey(cptr, _pyfree=1)
+ setattr(pkey, "_provider_owner", self)
+ return pkey
def generate_ec_key_pair(self, curve_name: str = "prime256v1") -> EVP.PKey:
"""
@@ -128,11 +174,15 @@
if not isinstance(curve_name, str) or not curve_name:
raise ProviderError(f"Invalid curve_name: {curve_name}")
- cptr = m2.provider_generate_ec_key_pair(curve_name, self._ptr) #
type: ignore[arg-type]
+ cptr = m2.provider_generate_ec_key_pair(
+ curve_name, self._context_ptr(), self._provider_ptr()
+ )
if not cptr:
raise ProviderError("Failed to generate EC key pair")
- return EVP.PKey(cptr, _pyfree=1)
+ pkey = EVP.PKey(cptr, _pyfree=1)
+ setattr(pkey, "_provider_owner", self)
+ return pkey
def destroy_key(self, uri: str, user_pin: Optional[str] = None) -> None:
"""
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/m2crypto-0.49.0/src/M2Crypto/RC4.py
new/m2crypto-0.50.0/src/M2Crypto/RC4.py
--- old/m2crypto-0.49.0/src/M2Crypto/RC4.py 2026-08-16 00:26:35.174126600
+0200
+++ new/m2crypto-0.50.0/src/M2Crypto/RC4.py 2026-09-09 22:27:01.110097200
+0200
@@ -1,15 +1,34 @@
"""M2Crypto wrapper for OpenSSL RC4 API.
-Copyright (c) 1999-2003 Ng Pheng Siong. All rights reserved."""
+WARNING: RC4 is cryptographically broken (RFC 7465 prohibits its use
+in TLS). This module is kept only for interoperability with legacy
+data formats and will be removed in a future release. New code
+should use AES (e.g. EVP.Cipher with aes_256_gcm) instead.
+
+Copyright (c) 1999-2003 Ng Pheng Siong. All rights reserved.
+"""
+
+import warnings
from typing import Optional
from M2Crypto import m2, types as C
class RC4(object):
- """Object interface to the stream cipher RC4."""
+ """Object interface to the stream cipher RC4.
+
+ .. deprecated:: 0.49
+ RC4 is insecure (RFC 7465); use AES instead. Instantiating
+ this class emits a :class:`DeprecationWarning`.
+ """
def __init__(self, key: Optional[bytes] = None) -> None:
+ warnings.warn(
+ "M2Crypto.RC4 is deprecated and will be removed in a future "
+ "release; RC4 is insecure (RFC 7465). Use AES instead.",
+ DeprecationWarning,
+ stacklevel=2,
+ )
self.cipher = m2.rc4_new()
if key:
m2.rc4_set_key(self.cipher, key)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/m2crypto-0.49.0/src/M2Crypto/SSL/Connection.py
new/m2crypto-0.50.0/src/M2Crypto/SSL/Connection.py
--- old/m2crypto-0.49.0/src/M2Crypto/SSL/Connection.py 2026-08-16
00:26:35.174126600 +0200
+++ new/m2crypto-0.50.0/src/M2Crypto/SSL/Connection.py 2026-09-09
22:27:01.111097000 +0200
@@ -62,6 +62,11 @@
self._bio_freed = False
self.ctx = ctx
self.ssl: C.SSL = m2.ssl_new(self.ctx.ctx)
+ # SSL_set_bio() takes ownership of the native BIOs. Keep the Python
+ # wrappers alive while SSL owns their pointers, but prevent their
+ # destructors from freeing those pointers a second time.
+ self._readbio: Optional[BIO.BIO] = None
+ self._writebio: Optional[BIO.BIO] = None
if sock is not None:
self.socket = sock
else:
@@ -115,6 +120,8 @@
self.close()
if self.ssl_close_flag == self.m2_bio_noclose and getattr(self, "ssl",
None):
self.m2_ssl_free(self.ssl)
+ self._readbio = None
+ self._writebio = None
def close(self, freeBio: Optional[bool] = True) -> None:
"""
@@ -189,7 +196,13 @@
:param readbio: BIO for reading
:param writebio: BIO for writing.
"""
+ # SSL_set_bio() takes ownership of both native BIOs. The wrappers
+ # must remain alive, but must not free the pointers independently.
+ readbio._pyfree = 0
+ writebio._pyfree = 0
m2.ssl_set_bio(self.ssl, readbio._ptr(), writebio._ptr())
+ self._readbio = readbio
+ self._writebio = writebio
def set_client_CA_list_from_file(self, cafile: str) -> None:
"""Set the acceptable client CA list.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/m2crypto-0.49.0/src/M2Crypto/SSL/Context.py
new/m2crypto-0.50.0/src/M2Crypto/SSL/Context.py
--- old/m2crypto-0.49.0/src/M2Crypto/SSL/Context.py 2026-08-16
00:26:35.174126600 +0200
+++ new/m2crypto-0.50.0/src/M2Crypto/SSL/Context.py 2026-09-09
22:27:01.111097000 +0200
@@ -67,7 +67,17 @@
ctxmap()[id(self.ctx)] = self
m2.ssl_ctx_set_cache_size(self.ctx, 128)
if weak_crypto is None and protocol in ("sslv23", "tls"):
- self.set_options(m2.SSL_OP_ALL | m2.SSL_OP_NO_SSLv2 |
m2.SSL_OP_NO_SSLv3)
+ # SSLv2, SSLv3, TLS 1.0 and TLS 1.1 are all considered
+ # insecure and are disabled by default. Callers that need
+ # to interoperate with a legacy peer must pass weak_crypto=1
+ # when constructing the context.
+ self.set_options(
+ m2.SSL_OP_ALL
+ | m2.SSL_OP_NO_SSLv2
+ | m2.SSL_OP_NO_SSLv3
+ | m2.SSL_OP_NO_TLSv1
+ | m2.SSL_OP_NO_TLSv1_1
+ )
def __del__(self) -> None:
if getattr(self, "ctx", None):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/m2crypto-0.49.0/src/M2Crypto/__init__.py
new/m2crypto-0.50.0/src/M2Crypto/__init__.py
--- old/m2crypto-0.49.0/src/M2Crypto/__init__.py 2026-08-16
00:26:35.175126800 +0200
+++ new/m2crypto-0.50.0/src/M2Crypto/__init__.py 2026-09-09
22:27:01.112097300 +0200
@@ -18,7 +18,7 @@
from typing import Any, Callable, Optional, Tuple, cast
-__version__: str = "0.49.0"
+__version__: str = "0.50.0"
version: str = __version__
VersionCtor: Optional[Callable[[str], Any]]
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/m2crypto-0.49.0/src/M2Crypto/_m2crypto.pyi
new/m2crypto-0.50.0/src/M2Crypto/_m2crypto.pyi
--- old/m2crypto-0.49.0/src/M2Crypto/_m2crypto.pyi 2026-08-16
00:26:35.175126800 +0200
+++ new/m2crypto-0.50.0/src/M2Crypto/_m2crypto.pyi 2026-09-09
22:27:01.112097300 +0200
@@ -62,6 +62,9 @@
SSL_OP_NO_SSLv2: int
SSL_OP_NO_SSLv3: int
SSL_OP_NO_TLSv1: int
+SSL_OP_NO_TLSv1_1: int
+SSL_OP_NO_TLSv1_2: int
+SSL_OP_NO_TLSv1_3: int
# SSL state flags
SSL_ST_CONNECT: int
@@ -334,9 +337,9 @@
def mpi_to_bn(s: bytes) -> C.BIGNUM: ...
def bn_to_bin(bn: C.BIGNUM) -> bytes: ...
def bin_to_bn(s: bytes) -> C.BIGNUM: ...
-def bn_to_hex(bn: C.BIGNUM) -> str: ...
-def hex_to_bn(s: str) -> C.BIGNUM: ...
-def dec_to_bn(s: str) -> C.BIGNUM: ...
+def bn_to_hex(bn: C.BIGNUM) -> bytes: ...
+def hex_to_bn(s: Union[bytes, bytearray, memoryview]) -> C.BIGNUM: ...
+def dec_to_bn(s: Union[bytes, bytearray, memoryview]) -> C.BIGNUM: ...
def bn_rand(bits: int, top: int, bottom: int) -> int: ...
def bn_rand_range(range_bn: int) -> int: ...
@@ -940,14 +943,21 @@
# --- Provider ---
def provider_init_error(provider_err: Type[BaseException]) -> None: ...
-def provider_load(name: str) -> Optional[C.OSSL_PROVIDER]: ...
-def provider_load_key(uri: str) -> Optional[C.EVP_PKEY]: ...
-def provider_load_certificate(uri: str) -> Optional[C.X509]: ...
+def provider_context_new() -> Optional[C.OSSL_LIB_CTX]: ...
+def provider_context_free(libctx: C.OSSL_LIB_CTX) -> None: ...
+def provider_load(libctx: C.OSSL_LIB_CTX, name: str) ->
Optional[C.OSSL_PROVIDER]: ...
+def provider_load_key(libctx: C.OSSL_LIB_CTX, uri: str) ->
Optional[C.EVP_PKEY]: ...
+def provider_load_certificate(libctx: C.OSSL_LIB_CTX, uri: str) ->
Optional[C.X509]: ...
def provider_unload(provider: C.OSSL_PROVIDER) -> None: ...
def provider_generate_rsa_key_pair(
- bits: int, exponent: int, provider: Optional[C.OSSL_PROVIDER]
+ bits: int,
+ exponent: int,
+ libctx: C.OSSL_LIB_CTX,
+ provider: Optional[C.OSSL_PROVIDER],
) -> Optional[C.EVP_PKEY]: ...
def provider_generate_ec_key_pair(
- curve_name: str, provider: Optional[C.OSSL_PROVIDER]
+ curve_name: str,
+ libctx: C.OSSL_LIB_CTX,
+ provider: Optional[C.OSSL_PROVIDER],
) -> Optional[C.EVP_PKEY]: ...
def provider_destroy_key(uri: str, user_pin: Optional[str]) -> int: ...
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/m2crypto-0.49.0/src/M2Crypto/types.py
new/m2crypto-0.50.0/src/M2Crypto/types.py
--- old/m2crypto-0.49.0/src/M2Crypto/types.py 2026-08-16 00:26:35.175126800
+0200
+++ new/m2crypto-0.50.0/src/M2Crypto/types.py 2026-09-09 22:27:01.113097200
+0200
@@ -31,6 +31,7 @@
EVP_PKEY_CTX = NewType("EVP_PKEY_CTX", object)
EVP_PKEY = NewType("EVP_PKEY", object)
HMAC_CTX = NewType("HMAC_CTX", object)
+OSSL_LIB_CTX = NewType("OSSL_LIB_CTX", object)
OSSL_PROVIDER = NewType("OSSL_PROVIDER", object)
PKCS7 = NewType("PKCS7", object)
RC4_KEY = NewType("RC4_KEY", object)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/m2crypto-0.49.0/src/M2Crypto.egg-info/PKG-INFO
new/m2crypto-0.50.0/src/M2Crypto.egg-info/PKG-INFO
--- old/m2crypto-0.49.0/src/M2Crypto.egg-info/PKG-INFO 2026-08-16
00:26:58.315001500 +0200
+++ new/m2crypto-0.50.0/src/M2Crypto.egg-info/PKG-INFO 2026-09-09
22:27:40.168025700 +0200
@@ -1,6 +1,6 @@
Metadata-Version: 2.4
Name: M2Crypto
-Version: 0.49.0
+Version: 0.50.0
Summary: A Python crypto and SSL toolkit
Author: Ng Pheng Siong
Author-email: [email protected]
@@ -8,10 +8,10 @@
Maintainer-email: [email protected]
License: BSD-2-Clause
Project-URL: home_page, https://sr.ht/~mcepl/m2crypto/
-Project-URL: source, https://codeberg.org/mcepl/m2crypto/
+Project-URL: source, https://codefloe.com/mcepl/m2crypto/
Project-URL: documentation, https://m2crypto.readthedocs.io/en/latest/
Project-URL: report, https://lists.sr.ht/~mcepl/m2crypto
-Project-URL: changelog,
https://codeberg.org/mcepl/m2crypto/src/branch/master/CHANGES
+Project-URL: changelog,
https://codefloe.com/mcepl/m2crypto/src/branch/master/CHANGES
Keywords: cryptography,openssl
Classifier: Development Status :: 5 - Production/Stable
Classifier: Intended Audience :: Developers
@@ -41,7 +41,7 @@
<https://todo.sr.ht/~mcepl/m2crypto> (R/O, please\
report issues via the email list).\
**maintainer**: "Matěj Cepl"\
-**web-Site**: <https://codeberg.org/mcepl/m2crypto/>
+**web-Site**: <https://codefloe.com/mcepl/m2crypto/>
# M2Crypto
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/m2crypto-0.49.0/src/SWIG/_engine.i
new/m2crypto-0.50.0/src/SWIG/_engine.i
--- old/m2crypto-0.49.0/src/SWIG/_engine.i 2026-08-16 00:26:35.176126700
+0200
+++ new/m2crypto-0.50.0/src/SWIG/_engine.i 2026-09-09 22:27:01.114097000
+0200
@@ -9,17 +9,23 @@
*/
%{
#include <openssl/opensslconf.h>
+#include <openssl/opensslv.h>
#ifndef OPENSSL_NO_ENGINE
+#if !defined(OPENSSL_NO_DEPRECATED_3_0) && OPENSSL_VERSION_NUMBER < 0x30500000L
#include <openssl/engine.h>
#endif
+#endif
#include <openssl/ui.h>
#include <stdio.h>
#include <stdbool.h>
%}
%include <openssl/opensslconf.h>
+%include <openssl/opensslv.h>
-#if defined(OPENSSL_NO_ENGINE)
+#if defined(OPENSSL_NO_ENGINE) \
+ || defined(OPENSSL_NO_DEPRECATED_3_0) \
+ || OPENSSL_VERSION_NUMBER >= 0x30500000L
%constant bool is_engine_available = false;
#else
%constant bool is_engine_available = true;
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/m2crypto-0.49.0/src/SWIG/_lib.i
new/m2crypto-0.50.0/src/SWIG/_lib.i
--- old/m2crypto-0.49.0/src/SWIG/_lib.i 2026-08-16 00:26:35.176126700 +0200
+++ new/m2crypto-0.50.0/src/SWIG/_lib.i 2026-09-09 22:27:01.114097000 +0200
@@ -566,25 +566,43 @@
BIGNUM *hex_to_bn(PyObject *value) {
Py_buffer vbuf;
BIGNUM *bn;
+ char *tmp;
+ /* m2_PyObject_GetBufferInt also bounds vbuf.len <= INT_MAX, which
+ * matches what OpenSSL's BN_hex2bn can safely consume and keeps
+ * the +1 below from overflowing on a 32-bit size_t. */
+ if (m2_PyObject_GetBufferInt(value, &vbuf, PyBUF_SIMPLE) == -1)
+ return NULL;
- if (m2_PyObject_GetBuffer(value, &vbuf, PyBUF_SIMPLE) == -1)
+ if ((bn = BN_new()) == NULL) {
+ PyErr_SetString(PyExc_MemoryError, "hex_to_bn: BN_new");
+ m2_PyBuffer_Release(value, &vbuf);
return NULL;
+ }
- if ((bn=BN_new())==NULL) {
- PyErr_SetString(PyExc_MemoryError, "hex_to_bn");
+ /* BN_hex2bn requires a NUL-terminated C string. Python bytes
+ * (and bytearray slices, memoryview, etc.) are not guaranteed
+ * to be NUL-terminated, so copy into a temporary buffer that
+ * we control. Embedded NULs will still truncate parsing -- that
+ * matches BN_hex2bn's documented behaviour. */
+ tmp = PyMem_Malloc((size_t)vbuf.len + 1);
+ if (tmp == NULL) {
+ PyErr_SetString(PyExc_MemoryError, "hex_to_bn: tmp buffer");
+ BN_free(bn);
m2_PyBuffer_Release(value, &vbuf);
return NULL;
}
+ memcpy(tmp, vbuf.buf, (size_t)vbuf.len);
+ tmp[vbuf.len] = '\0';
- /* BN_hex2bn expects a null-terminated string/buffer */
- /* We pass vbuf.buf assuming input is correctly formatted (e.g. bytes from
hex string) */
- if (BN_hex2bn(&bn, (const char *)vbuf.buf) <= 0) {
+ if (BN_hex2bn(&bn, tmp) <= 0) {
m2_PyErr_Msg(PyExc_RuntimeError);
BN_free(bn);
+ PyMem_Free(tmp);
m2_PyBuffer_Release(value, &vbuf);
return NULL;
}
+ PyMem_Free(tmp);
m2_PyBuffer_Release(value, &vbuf);
return bn;
}
@@ -592,23 +610,37 @@
BIGNUM *dec_to_bn(PyObject *value) {
Py_buffer vbuf;
BIGNUM *bn;
+ char *tmp;
- if (m2_PyObject_GetBuffer(value, &vbuf, PyBUF_SIMPLE) == -1)
+ if (m2_PyObject_GetBufferInt(value, &vbuf, PyBUF_SIMPLE) == -1)
return NULL;
- if ((bn=BN_new())==NULL) {
- PyErr_SetString(PyExc_MemoryError, "dec_to_bn");
- m2_PyBuffer_Release(value, &vbuf);
- return NULL;
+ if ((bn = BN_new()) == NULL) {
+ PyErr_SetString(PyExc_MemoryError, "dec_to_bn: BN_new");
+ m2_PyBuffer_Release(value, &vbuf);
+ return NULL;
}
- /* BN_dec2bn expects a null-terminated string/buffer */
- if ((BN_dec2bn(&bn, (const char *)vbuf.buf) <= 0)) {
- m2_PyErr_Msg(PyExc_RuntimeError);
- BN_free(bn);
- m2_PyBuffer_Release(value, &vbuf);
- return NULL;
+ /* See hex_to_bn() above for the rationale behind the NUL-terminated
+ * temporary copy. */
+ tmp = PyMem_Malloc((size_t)vbuf.len + 1);
+ if (tmp == NULL) {
+ PyErr_SetString(PyExc_MemoryError, "dec_to_bn: tmp buffer");
+ BN_free(bn);
+ m2_PyBuffer_Release(value, &vbuf);
+ return NULL;
+ }
+ memcpy(tmp, vbuf.buf, (size_t)vbuf.len);
+ tmp[vbuf.len] = '\0';
+
+ if (BN_dec2bn(&bn, tmp) <= 0) {
+ m2_PyErr_Msg(PyExc_RuntimeError);
+ BN_free(bn);
+ PyMem_Free(tmp);
+ m2_PyBuffer_Release(value, &vbuf);
+ return NULL;
}
+ PyMem_Free(tmp);
m2_PyBuffer_Release(value, &vbuf);
return bn;
}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/m2crypto-0.49.0/src/SWIG/_provider.i
new/m2crypto-0.50.0/src/SWIG/_provider.i
--- old/m2crypto-0.49.0/src/SWIG/_provider.i 2026-08-16 00:26:35.176126700
+0200
+++ new/m2crypto-0.50.0/src/SWIG/_provider.i 2026-09-09 22:27:01.115097300
+0200
@@ -17,6 +17,7 @@
#include <openssl/provider.h>
#include <openssl/params.h>
#else
+typedef void OSSL_LIB_CTX;
typedef void OSSL_PROVIDER;
#endif
%}
@@ -63,21 +64,32 @@
}
}
-EVP_PKEY *provider_load_key(const char *uri)
+EVP_PKEY *provider_load_key(OSSL_LIB_CTX *libctx, const char *uri)
{
+#if OPENSSL_VERSION_NUMBER < 0x30000000L
+ (void)libctx;
+ (void)uri;
+ PyErr_SetString(provider_exc(), "OpenSSL provider API requires OpenSSL >=
3.0");
+ return NULL;
+#else
OSSL_STORE_CTX *store;
OSSL_STORE_INFO *info;
EVP_PKEY *key = NULL;
int want_private = 0;
int want_public = 0;
+ if (!libctx) {
+ PyErr_SetString(provider_exc(), "Invalid NULL library context");
+ return NULL;
+ }
+
if (!uri) {
raise_ossl_error(_provider_err, "Invalid NULL uri");
return NULL;
}
ERR_clear_error();
- store = OSSL_STORE_open(uri, NULL, NULL, NULL, NULL);
+ store = OSSL_STORE_open_ex(uri, libctx, NULL, NULL, NULL, NULL, NULL,
NULL);
if (store == NULL) {
raise_ossl_error(_provider_err, "Failed to open store: %s", uri);
return NULL;
@@ -180,21 +192,33 @@
OSSL_STORE_close(store);
return key;
+#endif
}
-X509 *provider_load_certificate(const char *uri)
+X509 *provider_load_certificate(OSSL_LIB_CTX *libctx, const char *uri)
{
+#if OPENSSL_VERSION_NUMBER < 0x30000000L
+ (void)libctx;
+ (void)uri;
+ PyErr_SetString(provider_exc(), "OpenSSL provider API requires OpenSSL >=
3.0");
+ return NULL;
+#else
OSSL_STORE_CTX *store;
OSSL_STORE_INFO *info;
X509 *cert = NULL;
+ if (!libctx) {
+ PyErr_SetString(provider_exc(), "Invalid NULL library context");
+ return NULL;
+ }
+
if (!uri) {
raise_ossl_error(_provider_err, "Invalid NULL uri");
return NULL;
}
ERR_clear_error();
- store = OSSL_STORE_open(uri, NULL, NULL, NULL, NULL);
+ store = OSSL_STORE_open_ex(uri, libctx, NULL, NULL, NULL, NULL, NULL,
NULL);
if (store == NULL) {
raise_ossl_error(_provider_err, "Failed to open store: %s", uri);
return NULL;
@@ -228,24 +252,54 @@
OSSL_STORE_close(store);
return cert;
+#endif
+}
+
+OSSL_LIB_CTX *provider_context_new(void)
+{
+#if OPENSSL_VERSION_NUMBER < 0x30000000L
+ PyErr_SetString(provider_exc(), "OpenSSL provider API requires OpenSSL >=
3.0");
+ return NULL;
+#else
+ OSSL_LIB_CTX *libctx = OSSL_LIB_CTX_new();
+ if (libctx == NULL) {
+ raise_ossl_error(provider_exc(), "Failed to create OpenSSL library
context");
+ }
+ return libctx;
+#endif
+}
+
+void provider_context_free(OSSL_LIB_CTX *libctx)
+{
+#if OPENSSL_VERSION_NUMBER < 0x30000000L
+ (void)libctx;
+#else
+ OSSL_LIB_CTX_free(libctx);
+#endif
}
-OSSL_PROVIDER *provider_load(const char *name)
+OSSL_PROVIDER *provider_load(OSSL_LIB_CTX *libctx, const char *name)
{
#if OPENSSL_VERSION_NUMBER < 0x30000000L
+ (void)libctx;
(void)name;
PyErr_SetString(provider_exc(), "OpenSSL provider API requires OpenSSL >=
3.0");
return NULL;
#else
OSSL_PROVIDER *provider = NULL;
+ if (!libctx) {
+ PyErr_SetString(provider_exc(), "Invalid NULL library context");
+ return NULL;
+ }
+
ERR_clear_error();
/* Load providers */
#if OPENSSL_VERSION_NUMBER >= 0x30500000L // OpenSSL 3.5.0
- provider = OSSL_PROVIDER_load_ex(NULL, name, NULL);
+ provider = OSSL_PROVIDER_load_ex(libctx, name, NULL);
#else
/* Use the older function for OpenSSL < 3.5 */
- provider = OSSL_PROVIDER_load(NULL, name);
+ provider = OSSL_PROVIDER_load(libctx, name);
#endif
if (!provider) {
raise_ossl_error(provider_exc(), "Failed to load provider '%s'", name);
@@ -266,11 +320,12 @@
#endif
}
-EVP_PKEY *provider_generate_rsa_key_pair(int bits, int exponent, OSSL_PROVIDER
*provider)
+EVP_PKEY *provider_generate_rsa_key_pair(int bits, int exponent, OSSL_LIB_CTX
*libctx, OSSL_PROVIDER *provider)
{
#if OPENSSL_VERSION_NUMBER < 0x30000000L
(void)bits;
(void)exponent;
+ (void)libctx;
(void)provider;
PyErr_SetString(provider_exc(), "Key generation requires OpenSSL >= 3.0");
return NULL;
@@ -283,6 +338,11 @@
char propq[128];
const char *pname = NULL;
+ if (libctx == NULL) {
+ PyErr_SetString(provider_exc(), "Invalid NULL library context");
+ return NULL;
+ }
+
if (provider == NULL) {
PyErr_SetString(provider_exc(), "Invalid NULL provider");
return NULL;
@@ -299,7 +359,7 @@
ERR_clear_error();
/* Create a context for RSA key generation from the requested provider. */
- ctx = EVP_PKEY_CTX_new_from_name(NULL, "RSA", propq);
+ ctx = EVP_PKEY_CTX_new_from_name(libctx, "RSA", propq);
if (ctx == NULL) {
raise_ossl_error(provider_exc(), "Failed to create RSA key generation
context");
return NULL;
@@ -355,10 +415,11 @@
#endif
}
-EVP_PKEY *provider_generate_ec_key_pair(const char *curve_name, OSSL_PROVIDER
*provider)
+EVP_PKEY *provider_generate_ec_key_pair(const char *curve_name, OSSL_LIB_CTX
*libctx, OSSL_PROVIDER *provider)
{
#if OPENSSL_VERSION_NUMBER < 0x30000000L
(void)curve_name;
+ (void)libctx;
(void)provider;
PyErr_SetString(provider_exc(), "Key generation requires OpenSSL >= 3.0");
return NULL;
@@ -372,6 +433,11 @@
ERR_clear_error();
+ if (libctx == NULL) {
+ PyErr_SetString(provider_exc(), "Invalid NULL library context");
+ return NULL;
+ }
+
if (!curve_name) {
raise_ossl_error(provider_exc(), "Invalid NULL curve_name");
return NULL;
@@ -391,7 +457,7 @@
snprintf(propq, sizeof(propq), "provider=%s", pname);
/* Create a context for EC key generation from the requested provider. */
- ctx = EVP_PKEY_CTX_new_from_name(NULL, "EC", propq);
+ ctx = EVP_PKEY_CTX_new_from_name(libctx, "EC", propq);
if (ctx == NULL) {
raise_ossl_error(provider_exc(), "Failed to create EC key generation
context");
return NULL;
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/m2crypto-0.49.0/src/SWIG/_ssl.i
new/m2crypto-0.50.0/src/SWIG/_ssl.i
--- old/m2crypto-0.49.0/src/SWIG/_ssl.i 2026-08-16 00:26:35.176126700 +0200
+++ new/m2crypto-0.50.0/src/SWIG/_ssl.i 2026-09-09 22:27:01.115097300 +0200
@@ -246,6 +246,9 @@
%constant int SSL_OP_NO_SSLv2 = 0x01000000L;
%constant int SSL_OP_NO_SSLv3 = 0x02000000L;
%constant int SSL_OP_NO_TLSv1 = 0x04000000L;
+%constant int SSL_OP_NO_TLSv1_1 = 0x10000000L;
+%constant int SSL_OP_NO_TLSv1_2 = 0x08000000L;
+%constant int SSL_OP_NO_TLSv1_3 = 0x20000000L;
%constant int SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS = 0x00000800L;
%constant int SSL_MODE_ENABLE_PARTIAL_WRITE = SSL_MODE_ENABLE_PARTIAL_WRITE;
@@ -555,7 +558,7 @@
again:
if (gettimeofday(&now, NULL) != 0) {
// Should not happen, but handle defensively
- PyErr_SetString(PyExc_OSError, "gettimeofday failed");
+ PyErr_SetFromErrno(PyExc_OSError);
return -1;
}
@@ -664,7 +667,7 @@
// Get start time only if a timeout is specified
if (has_timeout) {
if (gettimeofday(&tv_start, NULL) != 0) {
- PyErr_SetString(PyExc_OSError, "gettimeofday failed");
+ PyErr_SetFromErrno(PyExc_OSError);
return NULL;
}
}
@@ -790,6 +793,11 @@
int r;
struct timeval tv;
+ if (timeout > 0 && gettimeofday(&tv, NULL) != 0) {
+ PyErr_SetFromErrno(PyExc_OSError);
+ return NULL;
+ }
+
if (!(buf = PyMem_Malloc(num))) {
PyErr_SetString(PyExc_MemoryError, "ssl_read");
return NULL;
@@ -825,14 +833,13 @@
goto cleanup;
}
- // NOTE: ssl_sleep_with_timeout must now check and enforce
the timeout
- // This function should return 0 on success/retry, and -1
on timeout or error
- if (ssl_sleep_with_timeout(ssl, &tv, timeout, ssl_err) ==
0) {
+ // ssl_sleep_with_timeout returns 0 when the socket is
+ // ready and -1 after setting an exception.
+ if (ssl_sleep_with_timeout(ssl, &tv, timeout, ssl_err) !=
0) {
obj = NULL;
goto cleanup;
}
- // If it returns 0 (success/retry), loop continues.
- break;
+ continue;
/* Some non-recoverable, fatal I/O error occurred. If this
* error occurs then no further I/O operations should be
* performed on the connection and SSL_shutdown() must not
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/m2crypto-0.49.0/tests/test_bio_ssl.py
new/m2crypto-0.50.0/tests/test_bio_ssl.py
--- old/m2crypto-0.49.0/tests/test_bio_ssl.py 2026-08-16 00:26:35.178126600
+0200
+++ new/m2crypto-0.50.0/tests/test_bio_ssl.py 2026-09-09 22:27:01.120097200
+0200
@@ -34,13 +34,13 @@
try:
ctx = SSL.Context()
ctx.load_cert_chain("tests/server.pem")
- conn = SSL.Connection(ctx)
+ self.conn = SSL.Connection(ctx)
sslbio = BIO.SSLBio()
readbio = BIO.MemoryBuffer()
writebio = BIO.MemoryBuffer()
- sslbio.set_ssl(conn)
- conn.set_bio(readbio, writebio)
- conn.set_connect_state()
+ sslbio.set_ssl(self.conn)
+ self.conn.set_bio(readbio, writebio)
+ self.conn.set_connect_state()
sock.settimeout(_HANDSHAKE_TIMEOUT)
sock.connect((self.host, self.port))
@@ -121,12 +121,12 @@
def test_do_handshake_succeed(self): # XXX leaks 196/26586 bytes
ctx = SSL.Context()
ctx.load_cert_chain("tests/server.pem")
- conn = SSL.Connection(ctx)
- self.sslbio.set_ssl(conn)
+ self.conn = SSL.Connection(ctx)
+ self.sslbio.set_ssl(self.conn)
readbio = BIO.MemoryBuffer()
writebio = BIO.MemoryBuffer()
- conn.set_bio(readbio, writebio)
- conn.set_accept_state()
+ self.conn.set_bio(readbio, writebio)
+ self.conn.set_accept_state()
handshake_complete = False
srv_port = allocate_srv_port()
sock = socket.socket()
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/m2crypto-0.49.0/tests/test_bn.py
new/m2crypto-0.50.0/tests/test_bn.py
--- old/m2crypto-0.49.0/tests/test_bn.py 2026-08-16 00:26:35.178126600
+0200
+++ new/m2crypto-0.50.0/tests/test_bn.py 2026-09-09 22:27:01.120097200
+0200
@@ -9,7 +9,7 @@
import re
import warnings
-from M2Crypto import BN, Rand
+from M2Crypto import BN, Rand, m2
from tests import unittest
loops = 16
@@ -71,8 +71,60 @@
assert m.match(r)
+class BNConvTestCase(unittest.TestCase):
+ """Regression tests for m2.hex_to_bn / m2.dec_to_bn.
+
+ These wrap OpenSSL's BN_hex2bn / BN_dec2bn, which require a
+ NUL-terminated C string. The Python wrappers must copy the
+ incoming buffer into a NUL-terminated scratch area; passing the
+ raw buffer pointer is an out-of-bounds read on hostile input.
+ """
+
+ def test_hex_to_bn_roundtrip(self):
+ bn = m2.hex_to_bn(b"deadbeef")
+ # bn_to_hex returns bytes (the .pyi annotation is incorrect)
+ self.assertEqual(m2.bn_to_hex(bn).upper(), b"DEADBEEF")
+
+ def test_dec_to_bn_roundtrip(self):
+ bn_dec = m2.dec_to_bn(b"3735928559") # 0xDEADBEEF
+ bn_hex = m2.hex_to_bn(b"deadbeef")
+ self.assertEqual(m2.bn_to_bin(bn_dec), m2.bn_to_bin(bn_hex))
+
+ def test_hex_to_bn_non_nul_terminated(self):
+ """The bytearray slice has no trailing NUL: must not OOB-read.
+
+ Before the fix, BN_hex2bn would read past the end of the
+ buffer until it found a NUL or a non-hex byte. After the fix,
+ the wrapper copies into its own NUL-terminated scratch buffer,
+ so the result must depend only on the slice contents.
+ """
+ big = bytearray(b"deadbeef" + b"ZZZZZZZZ")
+ view = memoryview(big)[:8]
+ bn = m2.hex_to_bn(view)
+ self.assertEqual(m2.bn_to_hex(bn).upper(), b"DEADBEEF")
+
+ def test_dec_to_bn_non_nul_terminated(self):
+ big = bytearray(b"12345" + b"9999")
+ view = memoryview(big)[:5]
+ bn = m2.dec_to_bn(view)
+ self.assertEqual(int(m2.bn_to_hex(bn), 16), 12345)
+
+ def test_hex_to_bn_rejects_str(self):
+ # Note: the underlying TypeError is wrapped in SystemError by
+ # SWIG's BIGNUM* output typemap (pre-existing behaviour).
+ with self.assertRaises((TypeError, SystemError)):
+ m2.hex_to_bn("deadbeef")
+
+ def test_dec_to_bn_rejects_str(self):
+ with self.assertRaises((TypeError, SystemError)):
+ m2.dec_to_bn("12345")
+
+
def suite():
- return unittest.TestLoader().loadTestsFromTestCase(BNTestCase)
+ t_suite = unittest.TestSuite()
+ t_suite.addTest(unittest.TestLoader().loadTestsFromTestCase(BNTestCase))
+
t_suite.addTest(unittest.TestLoader().loadTestsFromTestCase(BNConvTestCase))
+ return t_suite
if __name__ == "__main__":
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/m2crypto-0.49.0/tests/test_provider.py
new/m2crypto-0.50.0/tests/test_provider.py
--- old/m2crypto-0.49.0/tests/test_provider.py 2026-08-16 00:26:35.179126700
+0200
+++ new/m2crypto-0.50.0/tests/test_provider.py 2026-09-09 22:27:01.121097000
+0200
@@ -1,6 +1,7 @@
#!/usr/bin/env python3
"""Test file for M2Crypto.Provider."""
+import gc
import hashlib
import os
import platform
@@ -70,6 +71,17 @@
openssl_module_pkcs11 = cls.getenv("M2CRYPTO_OPENSSL_MODULE_PKCS11")
pkcs11_module_path = cls.getenv("M2CRYPTO_PKCS11_MODULE_PATH")
+ cls.original_environment = {
+ name: os.environ.get(name)
+ for name in (
+ "OPENSSL_CONF",
+ "OPENSSL_MODULES",
+ "SOFTHSM2_CONF",
+ "PKCS11_MODULE_PATH",
+ "PKCS11_PIN",
+ )
+ }
+
# Use mkdtemp + explicit cleanup for Python 3.6+ compatibility.
cls.tempdir = tempfile.mkdtemp(prefix="m2crypto-provider-")
openssl_conf = os.path.join(cls.tempdir, "openssl.conf")
@@ -268,9 +280,24 @@
@classmethod
def tearDownClass(cls):
+ # Release provider-backed objects before unloading the provider and
+ # removing the module, token, and configuration files they use.
+ cls.privkey = None
+ cls.pubkey = None
+ cls.cert = None
+ cls.provider = None
+ gc.collect()
+
+ for name, value in cls.original_environment.items():
+ if value is None:
+ os.environ.pop(name, None)
+ else:
+ os.environ[name] = value
+
# Best-effort cleanup; tests may create extra files under tempdir.
if getattr(cls, "tempdir", None):
shutil.rmtree(cls.tempdir, ignore_errors=True)
+ cls.tempdir = None
def test_public_key_der_comparison(self):
"""
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/m2crypto-0.49.0/tests/test_rc4.py
new/m2crypto-0.50.0/tests/test_rc4.py
--- old/m2crypto-0.49.0/tests/test_rc4.py 2026-08-16 00:26:35.179126700
+0200
+++ new/m2crypto-0.50.0/tests/test_rc4.py 2026-09-09 22:27:01.121097000
+0200
@@ -4,6 +4,8 @@
Copyright (c) 2009 Heikki Toivonen. All rights reserved."""
+import warnings
+
from M2Crypto import RC4, Rand
from binascii import hexlify
@@ -30,20 +32,34 @@
),
)
- rc4 = RC4.RC4()
- for key, plaintext, ciphertext in vectors:
- rc4.set_key(key)
- self.assertEqual(hexlify(rc4.update(plaintext)).upper(),
ciphertext)
+ with warnings.catch_warnings():
+ warnings.simplefilter("ignore", DeprecationWarning)
+ rc4 = RC4.RC4()
+ for key, plaintext, ciphertext in vectors:
+ rc4.set_key(key)
+ self.assertEqual(hexlify(rc4.update(plaintext)).upper(),
ciphertext)
- self.assertEqual(rc4.final(), "")
+ self.assertEqual(rc4.final(), "")
@unittest.skipIf(fips_mode, "Can't be run in FIPS mode")
def test_bad(self):
if fips_mode:
return
- rc4 = RC4.RC4(b"foo")
+ with warnings.catch_warnings():
+ warnings.simplefilter("ignore", DeprecationWarning)
+ rc4 = RC4.RC4(b"foo")
self.assertNotEqual(hexlify(rc4.update(b"bar")).upper(), b"45678")
+ @unittest.skipIf(fips_mode, "Can't be run in FIPS mode")
+ def test_deprecation_warning(self):
+ """RC4() must emit DeprecationWarning per RFC 7465."""
+ with warnings.catch_warnings(record=True) as caught:
+ warnings.simplefilter("always", DeprecationWarning)
+ RC4.RC4(b"key")
+ deprecations = [w for w in caught if issubclass(w.category,
DeprecationWarning)]
+ self.assertEqual(len(deprecations), 1)
+ self.assertIn("RC4", str(deprecations[0].message))
+
def suite():
return unittest.TestLoader().loadTestsFromTestCase(RC4TestCase)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/m2crypto-0.49.0/tests/test_ssl.py
new/m2crypto-0.50.0/tests/test_ssl.py
--- old/m2crypto-0.49.0/tests/test_ssl.py 2026-08-16 00:26:35.179126700
+0200
+++ new/m2crypto-0.50.0/tests/test_ssl.py 2026-09-09 22:27:01.122097300
+0200
@@ -1211,12 +1211,13 @@
class ChunkedHTTPSServer(threading.Thread):
"""Simple HTTPS server that sends a chunked response."""
- def __init__(self, host, port, certfile, keyfile):
+ def __init__(self, host, port, certfile, keyfile, response_delay=0):
super().__init__(daemon=True)
self.host = host
self.port = port
self.certfile = certfile
self.keyfile = keyfile
+ self.response_delay = response_delay
self.ready = threading.Event()
self.stop_event = threading.Event()
@@ -1224,6 +1225,7 @@
self.server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR,
1)
self.server_socket.bind((self.host, self.port))
+ self.port = self.server_socket.getsockname()[1]
self.server_socket.listen(1)
self.server_socket.settimeout(0.5)
@@ -1247,6 +1249,9 @@
# Read the request
request = ssl_conn.recv(4096)
+ if self.response_delay:
+ time.sleep(self.response_delay)
+
# Send properly formatted chunked response
response = (
b"HTTP/1.1 200 OK\r\n"
@@ -1279,6 +1284,39 @@
pass
+class SSLReadTimeoutTestCase(unittest.TestCase):
+ """Test SSL reads which must wait for the socket to become ready."""
+
+ def setUp(self):
+ self.server = ChunkedHTTPSServer(
+ srv_bind_host,
+ 0,
+ certfile="tests/server.pem",
+ keyfile="tests/server_key.pem",
+ response_delay=0.2,
+ )
+ self.server.start()
+ if not self.server.ready.wait(timeout=5):
+ self.fail("HTTPS server failed to start")
+
+ def tearDown(self):
+ self.server.stop()
+ self.server.join(timeout=2)
+
+ def test_read_retries_after_socket_becomes_ready(self):
+ ctx = SSL.Context()
+ conn = SSL.Connection(ctx)
+ try:
+ conn.connect((srv_host, self.server.port))
+ conn.write(b"GET / HTTP/1.0\r\n\r\n")
+ conn.settimeout(2.0)
+
+ self.assertEqual(conn.read(1), b"H")
+ finally:
+ conn.close()
+ ctx.close()
+
+
class Urllib2TEChunkedSSLClientTestCase(BaseSSLClientTestCase):
"""Test a response with "Transfer-Encoding: chunked"."""
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/m2crypto-0.49.0/tests/test_ssl_offline.py
new/m2crypto-0.50.0/tests/test_ssl_offline.py
--- old/m2crypto-0.49.0/tests/test_ssl_offline.py 2026-08-16
00:26:35.179126700 +0200
+++ new/m2crypto-0.50.0/tests/test_ssl_offline.py 2026-09-09
22:27:01.122097300 +0200
@@ -7,7 +7,7 @@
import doctest
-from M2Crypto import Rand, SSL, X509
+from M2Crypto import Rand, SSL, X509, m2
from tests import unittest
from tests.test_ssl import srv_host
@@ -56,6 +56,36 @@
store = ctx.get_cert_store()
self.assertIsInstance(store, X509.X509_Store)
+ def test_default_options_disable_legacy_protocols(self):
+ """Default Context() must disable SSLv2, SSLv3, TLS 1.0 and TLS 1.1.
+
+ Regression test for the security-audit finding that TLS 1.0 / 1.1
+ were enabled by default.
+ """
+ # set_options(0) returns the currently-set bitmask without
+ # changing it (OR-ing in zero is a no-op).
+ ctx = SSL.Context()
+ opts = ctx.set_options(0)
+ for flag_name in (
+ "SSL_OP_NO_SSLv2",
+ "SSL_OP_NO_SSLv3",
+ "SSL_OP_NO_TLSv1",
+ "SSL_OP_NO_TLSv1_1",
+ ):
+ flag = getattr(m2, flag_name)
+ self.assertTrue(
+ opts & flag,
+ "Default Context() must have %s set (mask=0x%x)" % (flag_name,
opts),
+ )
+
+ def test_weak_crypto_keeps_legacy_protocols(self):
+ """Context(weak_crypto=1) preserves the legacy escape hatch."""
+ ctx = SSL.Context(weak_crypto=1)
+ opts = ctx.set_options(0)
+ # weak_crypto=1 must not gratuitously set the NO_TLSv1* bits.
+ self.assertFalse(opts & m2.SSL_OP_NO_TLSv1)
+ self.assertFalse(opts & m2.SSL_OP_NO_TLSv1_1)
+
def suite():
t_suite = unittest.TestSuite()