public inbox for git-commits@fedoraproject.org
help / color / mirror / Atom feed
From: Filipe Rosset <rosset.filipe@gmail.com>
To: git-commits@fedoraproject.org
Subject: [rpms/python-twisted] rawhide: Fix test failures with pyOpenSSL 26.3
Date: Tue, 28 Jul 2026 23:09:15 GMT [thread overview]
Message-ID: <178528015546.1.16010398397640800199.rpms-python-twisted-c25ce59df943@fedoraproject.org> (raw)
A new commit has been pushed.
Repo : rpms/python-twisted
Branch : rawhide
Commit : c25ce59df94343ce45efb046f0d32c19882ddbe5
Author : Filipe Rosset <rosset.filipe@gmail.com>
Date : 2026-07-28T16:36:46-03:00
Stats : +351/-1 in 2 file(s)
URL : https://src.fedoraproject.org/rpms/python-twisted/c/c25ce59df94343ce45efb046f0d32c19882ddbe5?branch=rawhide
Log:
Fix test failures with pyOpenSSL 26.3
https://www.pyopenssl.org/en/latest/changelog.html#id1
Resolves: rhbz#2504592
---
diff --git a/0002-Replace-OpenSSL-crypto-X509Req-with-pyca-cryptography-CSR.patch b/0002-Replace-OpenSSL-crypto-X509Req-with-pyca-cryptography-CSR.patch
new file mode 100644
index 0000000..1d97341
--- /dev/null
+++ b/0002-Replace-OpenSSL-crypto-X509Req-with-pyca-cryptography-CSR.patch
@@ -0,0 +1,347 @@
+From 5b4601c9965ffc92d6aa952b8c05127d5ac37307 Mon Sep 17 00:00:00 2001
+From: Alex Gaynor <alex.gaynor@gmail.com>
+Date: Mon, 8 Jun 2026 11:53:05 -0400
+Subject: [PATCH] Replace OpenSSL.crypto.X509Req with pyca/cryptography CSR
+
+Replace usage of pyOpenSSL's X509Req with pyca/cryptography's
+CertificateSigningRequest in twisted.internet._sslverify and the
+test_ssl certificate generation helpers.
+---
+ src/twisted/internet/_sslverify.py | 92 ++++++++++++++++++++++++----
+ src/twisted/test/test_ssl.py | 34 +++++++---
+ src/twisted/test/test_sslverify.py | 79 ++++++++++++++++++++++++
+ 3 files changed, 185 insertions(+), 20 deletions(-)
+
+diff --git a/src/twisted/internet/_sslverify.py b/src/twisted/internet/_sslverify.py
+index c6dabf6c4c4..bd71a401636 100644
+--- a/src/twisted/internet/_sslverify.py
++++ b/src/twisted/internet/_sslverify.py
+@@ -20,6 +20,9 @@
+
+ import attr
+ from constantly import FlagConstant, Flags, NamedConstant, Names
++from cryptography import x509
++from cryptography.hazmat.primitives import hashes, serialization
++from cryptography.x509.oid import NameOID
+ from incremental import Version
+
+ from twisted.internet.abstract import isIPAddress, isIPv6Address
+@@ -169,6 +172,29 @@ def protocolNegotiationMechanisms() -> FlagConstant:
+ "emailAddress": "emailAddress",
+ }
+
++_x509NameOIDs = {
++ "commonName": NameOID.COMMON_NAME,
++ "organizationName": NameOID.ORGANIZATION_NAME,
++ "organizationalUnitName": NameOID.ORGANIZATIONAL_UNIT_NAME,
++ "localityName": NameOID.LOCALITY_NAME,
++ "stateOrProvinceName": NameOID.STATE_OR_PROVINCE_NAME,
++ "countryName": NameOID.COUNTRY_NAME,
++ "emailAddress": NameOID.EMAIL_ADDRESS,
++}
++
++# Reverse of _x509NameOIDs, for translating a parsed subject back into a
++# DistinguishedName.
++_x509OIDNames = {oid: name for name, oid in _x509NameOIDs.items()}
++
++_digestAlgorithms = {
++ "md5": hashes.MD5,
++ "sha1": hashes.SHA1,
++ "sha224": hashes.SHA224,
++ "sha256": hashes.SHA256,
++ "sha384": hashes.SHA384,
++ "sha512": hashes.SHA512,
++}
++
+
+ class DistinguishedName(dict[str, bytes]):
+ """
+@@ -490,19 +516,52 @@ class CertificateRequest(CertBase):
+
+ Certificate requests are given to certificate authorities to be signed and
+ returned resulting in an actual certificate.
++
++ @ivar original: The underlying CSR object.
+ """
+
++ original: x509.CertificateSigningRequest
++
+ @classmethod
+- def load(Class, requestData, requestFormat=crypto.FILETYPE_ASN1):
+- req = crypto.load_certificate_request(requestFormat, requestData)
++ def load(
++ cls, requestData: bytes, requestFormat: int = crypto.FILETYPE_ASN1
++ ) -> CertificateRequest:
++ if requestFormat == crypto.FILETYPE_ASN1:
++ req = x509.load_der_x509_csr(requestData)
++ elif requestFormat == crypto.FILETYPE_PEM:
++ req = x509.load_pem_x509_csr(requestData)
++ else:
++ raise ValueError(f"Unsupported format: {requestFormat!r}")
++ if not req.is_signature_valid:
++ subject = req.subject
++ raise VerifyError(
++ f"Can't verify that request for {subject!r} is self-signed."
++ )
++ return cls(req)
++
++ def _subjectToDistinguishedName(self) -> DistinguishedName:
++ """
++ Retrieve the subject of this certificate request.
++
++ @return: A copy of the subject of this certificate request.
++ @rtype: L{DistinguishedName}
++ """
+ dn = DistinguishedName()
+- dn._copyFrom(req.get_subject())
+- if not req.verify(req.get_pubkey()):
+- raise VerifyError(f"Can't verify that request for {dn!r} is self-signed.")
+- return Class(req)
++ for attribute in self.original.subject:
++ try:
++ name = _x509OIDNames[attribute.oid]
++ except KeyError:
++ raise ValueError(f"Unknown X509 name attribute: {attribute.oid!r}")
++ setattr(dn, name, attribute.value)
++ return dn
+
+- def dump(self, format=crypto.FILETYPE_ASN1):
+- return crypto.dump_certificate_request(format, self.original)
++ def dump(self, format: int = crypto.FILETYPE_ASN1) -> bytes:
++ if format == crypto.FILETYPE_ASN1:
++ return self.original.public_bytes(serialization.Encoding.DER)
++ elif format == crypto.FILETYPE_PEM:
++ return self.original.public_bytes(serialization.Encoding.PEM)
++ else:
++ raise ValueError(f"Unsupported format: {format!r}")
+
+
+ class PrivateCertificate(Certificate):
+@@ -714,10 +773,21 @@ def newCertificate(self, newCertData, format=crypto.FILETYPE_ASN1):
+ return PrivateCertificate.load(newCertData, self, format)
+
+ def requestObject(self, distinguishedName, digestAlgorithm="sha256"):
+- req = crypto.X509Req()
+- req.set_pubkey(self.original)
+- distinguishedName._copyInto(req.get_subject())
+- req.sign(self.original, digestAlgorithm)
++ req = (
++ x509.CertificateSigningRequestBuilder()
++ .subject_name(
++ x509.Name(
++ [
++ x509.NameAttribute(_x509NameOIDs[k], nativeString(v))
++ for k, v in distinguishedName.items()
++ ]
++ )
++ )
++ .sign(
++ self.original.to_cryptography_key(),
++ _digestAlgorithms[digestAlgorithm](),
++ )
++ )
+ return CertificateRequest(req)
+
+ def certificateRequest(
+@@ -750,7 +820,7 @@ def signCertificateRequest(
+ """
+ hlreq = CertificateRequest.load(requestData, requestFormat)
+
+- dn = hlreq.getSubject()
++ dn = hlreq._subjectToDistinguishedName()
+ vval = verifyDNCallback(dn)
+
+ def verified(value):
+@@ -787,8 +857,8 @@ def signRequestObject(
+ req = requestObject.original
+ cert = crypto.X509()
+ issuerDistinguishedName._copyInto(cert.get_issuer())
+- cert.set_subject(req.get_subject())
+- cert.set_pubkey(req.get_pubkey())
++ requestObject._subjectToDistinguishedName()._copyInto(cert.get_subject())
++ cert.set_pubkey(crypto.PKey.from_cryptography_key(req.public_key()))
+ cert.gmtime_adj_notBefore(0)
+ cert.gmtime_adj_notAfter(secondsToExpiry)
+ cert.set_serial_number(serialNumber)
+@@ -1656,10 +1726,10 @@ def contextSelectionCallback(connection: SSL.Connection) -> None:
+ return ctx
+
+
+-OpenSSLCertificateOptions.__getstate__ = deprecated( # type:ignore[method-assign]
++OpenSSLCertificateOptions.__getstate__ = deprecated( # type: ignore[method-assign]
+ Version("Twisted", 15, 0, 0), "a real persistence system"
+ )(OpenSSLCertificateOptions.__getstate__)
+-OpenSSLCertificateOptions.__setstate__ = deprecated( # type:ignore[method-assign]
++OpenSSLCertificateOptions.__setstate__ = deprecated( # type: ignore[method-assign]
+ Version("Twisted", 15, 0, 0), "a real persistence system"
+ )(OpenSSLCertificateOptions.__setstate__)
+
+diff --git a/src/twisted/test/test_ssl.py b/src/twisted/test/test_ssl.py
+index cb5d6925af7..8e2eb423949 100644
+--- a/src/twisted/test/test_ssl.py
++++ b/src/twisted/test/test_ssl.py
+@@ -21,6 +21,10 @@
+ try:
+ from OpenSSL import SSL, crypto
+
++ from cryptography import x509
++ from cryptography.hazmat.primitives import hashes, serialization
++ from cryptography.x509.oid import NameOID
++
+ from twisted.internet import ssl
+ from twisted.test.ssl_helpers import ClientTLSContext, certPath
+ except ImportError:
+@@ -164,21 +168,31 @@ def generateCertificateObjects(organization, organizationalUnit):
+ """
+ pkey = crypto.PKey()
+ pkey.generate_key(crypto.TYPE_RSA, 2048)
+- req = crypto.X509Req()
+- subject = req.get_subject()
+- subject.O = organization
+- subject.OU = organizationalUnit
+- req.set_pubkey(pkey)
+- req.sign(pkey, "md5")
++ req = (
++ x509.CertificateSigningRequestBuilder()
++ .subject_name(
++ x509.Name(
++ [
++ x509.NameAttribute(NameOID.ORGANIZATION_NAME, organization),
++ x509.NameAttribute(
++ NameOID.ORGANIZATIONAL_UNIT_NAME, organizationalUnit
++ ),
++ ]
++ )
++ )
++ .sign(pkey.to_cryptography_key(), hashes.SHA256())
++ )
+
+ # Here comes the actual certificate
+ cert = crypto.X509()
+ cert.set_serial_number(1)
+ cert.gmtime_adj_notBefore(0)
+ cert.gmtime_adj_notAfter(60) # Testing certificates need not be long lived
+- cert.set_issuer(req.get_subject())
+- cert.set_subject(req.get_subject())
+- cert.set_pubkey(req.get_pubkey())
++ subject = cert.get_subject()
++ subject.O = organization
++ subject.OU = organizationalUnit
++ cert.set_issuer(cert.get_subject())
++ cert.set_pubkey(pkey)
+ cert.sign(pkey, "md5")
+
+ return pkey, req, cert
+@@ -191,13 +205,13 @@ def generateCertificateFiles(basename, organization, organizationalUnit):
+ """
+ pkey, req, cert = generateCertificateObjects(organization, organizationalUnit)
+
+- for ext, obj, dumpFunc in [
+- ("key", pkey, crypto.dump_privatekey),
+- ("req", req, crypto.dump_certificate_request),
+- ("cert", cert, crypto.dump_certificate),
++ for ext, data in [
++ ("key", crypto.dump_privatekey(crypto.FILETYPE_PEM, pkey)),
++ ("req", req.public_bytes(serialization.Encoding.PEM)),
++ ("cert", crypto.dump_certificate(crypto.FILETYPE_PEM, cert)),
+ ]:
+ fName = os.extsep.join((basename, ext)).encode("utf-8")
+- FilePath(fName).setContent(dumpFunc(crypto.FILETYPE_PEM, obj))
++ FilePath(fName).setContent(data)
+
+
+ class ContextGeneratingMixin:
+diff --git a/src/twisted/test/test_sslverify.py b/src/twisted/test/test_sslverify.py
+index c50fb8b1055..282121bc023 100644
+--- a/src/twisted/test/test_sslverify.py
++++ b/src/twisted/test/test_sslverify.py
+@@ -51,6 +51,7 @@
+ from cryptography import x509
+ from cryptography.hazmat.backends import default_backend
+ from cryptography.hazmat.primitives import hashes
++ from cryptography.hazmat.primitives.asymmetric import ec
+ from cryptography.hazmat.primitives.asymmetric.rsa import (
+ RSAPrivateKey,
+ generate_private_key,
+@@ -3472,3 +3473,81 @@ def test_noTrailingNewlinePemCert(self):
+
+ certPEM = noTrailingNewlineKeyPemPath.getContent()
+ ssl.Certificate.loadPEM(certPEM)
++
++
++class CertificateRequestTests(SynchronousTestCase):
++ """
++ Tests for L{sslverify.CertificateRequest}.
++ """
++
++ if skipSSL:
++ skip = skipSSL
++
++ def _makeRequest(self):
++ """
++ Create a self-signed L{sslverify.CertificateRequest}.
++
++ @return: a fresh certificate request.
++ @rtype: L{sslverify.CertificateRequest}
++ """
++ dn = sslverify.DistinguishedName(commonName="example.twistedmatrix.com")
++ return sslverify.KeyPair.generate().requestObject(dn)
++
++ def test_pemRoundTrip(self):
++ """
++ A L{sslverify.CertificateRequest} dumped to PEM format and loaded back
++ again preserves its subject.
++ """
++ request = self._makeRequest()
++ pem = request.dump(FILETYPE_PEM)
++ self.assertIn(b"BEGIN CERTIFICATE REQUEST", pem)
++ loaded = sslverify.CertificateRequest.load(pem, FILETYPE_PEM)
++ self.assertEqual(
++ loaded._subjectToDistinguishedName(),
++ request._subjectToDistinguishedName(),
++ )
++
++ def test_loadUnsupportedFormat(self):
++ """
++ L{sslverify.CertificateRequest.load} raises L{ValueError} when given an
++ unrecognized format.
++ """
++ request = self._makeRequest()
++ with self.assertRaises(ValueError):
++ sslverify.CertificateRequest.load(request.dump(), object())
++
++ def test_loadUnverifiableSignature(self):
++ """
++ L{sslverify.CertificateRequest.load} raises L{sslverify.VerifyError}
++ when the request's self-signature does not verify.
++ """
++ data = bytearray(self._makeRequest().dump())
++ # Corrupt the trailing signature bytes so the self-signature no longer
++ # verifies while the structure still parses.
++ data[-1] ^= 0xFF
++ with self.assertRaises(sslverify.VerifyError):
++ sslverify.CertificateRequest.load(bytes(data))
++
++ def test_dumpUnsupportedFormat(self):
++ """
++ L{sslverify.CertificateRequest.dump} raises L{ValueError} when given an
++ unrecognized format.
++ """
++ with self.assertRaises(ValueError):
++ self._makeRequest().dump(object())
++
++ def test_subjectUnknownAttribute(self):
++ """
++ L{sslverify.CertificateRequest._subjectToDistinguishedName} raises
++ L{ValueError} when the subject contains a name attribute that does not
++ correspond to a known L{sslverify.DistinguishedName} field.
++ """
++ key = ec.generate_private_key(ec.SECP256R1(), backend=default_backend())
++ csr = (
++ x509.CertificateSigningRequestBuilder()
++ .subject_name(x509.Name([x509.NameAttribute(NameOID.GIVEN_NAME, "Alice")]))
++ .sign(key, hashes.SHA256())
++ )
++ request = sslverify.CertificateRequest(csr)
++ with self.assertRaises(ValueError):
++ request._subjectToDistinguishedName()
diff --git a/python-twisted.spec b/python-twisted.spec
index 8251500..aba9756 100644
--- a/python-twisted.spec
+++ b/python-twisted.spec
@@ -17,8 +17,11 @@ Source: %vcs/archive/%{srcname}-%{version}/%{srcname}-%{version}.tar.gz
# downstream-only disable tests that fail in the buildsystem or due to sha1
Patch: python-twisted-26.4.0-disable-tests.patch
# Fix Python 3.15 compatibility issues
-# https://github.com/twistWed/twisted/pull/12602
+# https://github.com/twisted/twisted/pull/12602
Patch: 0001-Fix-Python-3.15-compatibility-issues.patch
+# Replace OpenSSL.crypto.X509Req with pyca/cryptography CSR
+# https://github.com/twisted/twisted/pull/12661
+Patch: 0002-Replace-OpenSSL-crypto-X509Req-with-pyca-cryptography-CSR.patch
BuildArch: noarch
reply other threads:[~2026-07-28 23:09 UTC|newest]
Thread overview: [no followups] expand[flat|nested] mbox.gz Atom feed
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=178528015546.1.16010398397640800199.rpms-python-twisted-c25ce59df943@fedoraproject.org \
--to=rosset.filipe@gmail.com \
--cc=git-commits@fedoraproject.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox