Skip to content

Commit 19d390f

Browse files
alexclaude
andcommitted
Add aws-lc compatibility to tests and CI
Make the test suite pass when built against aws-lc by handling behavioral differences: error message format (UPPER_CASE reasons, OPENSSL_internal function name), unsupported features (Ed25519/Ed448 via legacy PEM, renegotiation, DTLS cookies, certain EC curves, ASN1 time offsets), different state strings, and constants that are 0 (MODE_RELEASE_BUFFERS, OP_NO_COMPRESSION). Also replace blowfish cipher references with aes-256-cbc and make OP_COOKIE_EXCHANGE conditional with a try/except. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 7b29beb commit 19d390f

File tree

6 files changed

+139
-29
lines changed

6 files changed

+139
-29
lines changed

.github/workflows/ci.yml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ jobs:
4545
- {VERSION: "3.9", NOXSESSION: "tests-wheel"}
4646
# Random order
4747
- {VERSION: "3.9", NOXSESSION: "tests-random-order"}
48+
# aws-lc
49+
- {VERSION: "3.14", NOXSESSION: "tests-cryptography-main", OPENSSL: {TYPE: "aws-lc", VERSION: "v1.67.0"}}
4850
# Meta
4951
- {VERSION: "3.9", NOXSESSION: "check-manifest"}
5052
- {VERSION: "3.11", NOXSESSION: "lint"}
@@ -56,6 +58,16 @@ jobs:
5658
uses: actions/setup-python@v6.2.0
5759
with:
5860
python-version: ${{ matrix.PYTHON.VERSION }}
61+
- name: Build custom OpenSSL
62+
if: matrix.PYTHON.OPENSSL
63+
run: |
64+
cargo install bindgen-cli
65+
66+
git clone --depth 1 --branch ${{ matrix.PYTHON.OPENSSL.VERSION }} https://github.com/aws/aws-lc.git
67+
cmake -GNinja -B aws-lc/build -S aws-lc -DCMAKE_INSTALL_PREFIX="${HOME}/ossl" -DBUILD_TESTING=OFF
68+
ninja -C aws-lc/build install
69+
rm -rf aws-lc/
70+
echo "OPENSSL_DIR=${HOME}/ossl" >> $GITHUB_ENV
5971
- run: python -m pip install nox
6072
- run: nox
6173
env:

src/OpenSSL/SSL.py

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,6 @@
6464
"OPENSSL_VERSION_NUMBER",
6565
"OP_ALL",
6666
"OP_CIPHER_SERVER_PREFERENCE",
67-
"OP_COOKIE_EXCHANGE",
6867
"OP_DONT_INSERT_EMPTY_FRAGMENTS",
6968
"OP_EPHEMERAL_RSA",
7069
"OP_MICROSOFT_BIG_SSLV3_BUFFER",
@@ -221,7 +220,11 @@
221220
OP_NO_COMPRESSION: int = _lib.SSL_OP_NO_COMPRESSION
222221

223222
OP_NO_QUERY_MTU: int = _lib.SSL_OP_NO_QUERY_MTU
224-
OP_COOKIE_EXCHANGE: int = _lib.SSL_OP_COOKIE_EXCHANGE
223+
try:
224+
OP_COOKIE_EXCHANGE: int | None = _lib.SSL_OP_COOKIE_EXCHANGE
225+
__all__.append("OP_COOKIE_EXCHANGE")
226+
except AttributeError:
227+
OP_COOKIE_EXCHANGE = None
225228
OP_NO_TICKET: int = _lib.SSL_OP_NO_TICKET
226229

227230
try:
@@ -820,6 +823,11 @@ def explode(*args, **kwargs): # type: ignore[no-untyped-def]
820823
"Getting group name is not supported by the linked OpenSSL version",
821824
)
822825

826+
_requires_ssl_cookie = _make_requires(
827+
getattr(_lib, "Cryptography_HAS_SSL_COOKIE", 0),
828+
"DTLS cookie support is not available",
829+
)
830+
823831

824832
class Session:
825833
"""
@@ -1910,6 +1918,7 @@ def set_ocsp_client_callback(
19101918
self._set_ocsp_callback(helper, data)
19111919

19121920
@_require_not_used
1921+
@_requires_ssl_cookie
19131922
def set_cookie_generate_callback(
19141923
self, callback: _CookieGenerateCallback
19151924
) -> None:
@@ -1920,6 +1929,7 @@ def set_cookie_generate_callback(
19201929
)
19211930

19221931
@_require_not_used
1932+
@_requires_ssl_cookie
19231933
def set_cookie_verify_callback(
19241934
self, callback: _CookieVerifyCallback
19251935
) -> None:

tests/conftest.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@
66

77
import pytest
88

9+
from OpenSSL.SSL import OPENSSL_VERSION, SSLeay_version
10+
11+
is_awslc = b"AWS-LC" in SSLeay_version(OPENSSL_VERSION)
12+
913

1014
def pytest_report_header(config: pytest.Config) -> str:
1115
import cryptography

tests/test_crypto.py

Lines changed: 50 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@
5858
load_publickey,
5959
)
6060

61+
from . import conftest
6162
from .util import (
6263
NON_ASCII,
6364
)
@@ -71,7 +72,7 @@ def utcnow() -> datetime:
7172
return datetime.now(timezone.utc).replace(tzinfo=None)
7273

7374

74-
GOOD_CIPHER = "blowfish"
75+
GOOD_CIPHER = "aes-256-cbc"
7576
BAD_CIPHER = "zippers"
7677

7778
GOOD_DIGEST = "SHA256"
@@ -1023,8 +1024,22 @@ class TestPKey:
10231024
[
10241025
(dsa_private_key_pem, dsa.DSAPrivateKey),
10251026
(ec_private_key_pem, ec.EllipticCurvePrivateKey),
1026-
(ed25519_private_key_pem, ed25519.Ed25519PrivateKey),
1027-
(ed448_private_key_pem, ed448.Ed448PrivateKey),
1027+
pytest.param(
1028+
ed25519_private_key_pem,
1029+
ed25519.Ed25519PrivateKey,
1030+
marks=pytest.mark.skipif(
1031+
conftest.is_awslc,
1032+
reason="aws-lc doesn't support Ed25519 via PEM",
1033+
),
1034+
),
1035+
pytest.param(
1036+
ed448_private_key_pem,
1037+
ed448.Ed448PrivateKey,
1038+
marks=pytest.mark.skipif(
1039+
conftest.is_awslc,
1040+
reason="aws-lc doesn't support Ed448 via PEM",
1041+
),
1042+
),
10281043
(rsa_private_key_pem, rsa.RSAPrivateKey),
10291044
],
10301045
)
@@ -1077,8 +1092,22 @@ def test_convert_roundtrip_cryptography_private_key(
10771092
[
10781093
(dsa_public_key_pem, dsa.DSAPublicKey),
10791094
(ec_public_key_pem, ec.EllipticCurvePublicKey),
1080-
(ed25519_public_key_pem, ed25519.Ed25519PublicKey),
1081-
(ed448_public_key_pem, ed448.Ed448PublicKey),
1095+
pytest.param(
1096+
ed25519_public_key_pem,
1097+
ed25519.Ed25519PublicKey,
1098+
marks=pytest.mark.skipif(
1099+
conftest.is_awslc,
1100+
reason="aws-lc doesn't support Ed25519 via PEM",
1101+
),
1102+
),
1103+
pytest.param(
1104+
ed448_public_key_pem,
1105+
ed448.Ed448PublicKey,
1106+
marks=pytest.mark.skipif(
1107+
conftest.is_awslc,
1108+
reason="aws-lc doesn't support Ed448 via PEM",
1109+
),
1110+
),
10821111
(rsa_public_key_pem, rsa.RSAPublicKey),
10831112
],
10841113
)
@@ -1822,6 +1851,8 @@ def test_sign(self) -> None:
18221851
key = PKey()
18231852
key.generate_key(TYPE_RSA, 2048)
18241853
cert.set_pubkey(key)
1854+
cert.gmtime_adj_notBefore(0)
1855+
cert.gmtime_adj_notAfter(24 * 60 * 60)
18251856
cert.sign(key, GOOD_DIGEST)
18261857

18271858
def test_construction(self) -> None:
@@ -1890,20 +1921,21 @@ def _setBoundTest(
18901921
set(certificate, when)
18911922
assert get(certificate) == when
18921923

1893-
# A plus two hours and thirty minutes offset
1894-
when = b"20040203040506+0530"
1895-
set(certificate, when)
1896-
assert get(certificate) == when
1897-
1898-
# A minus one hour fifteen minutes offset
1899-
when = b"20040203040506-0115"
1900-
set(certificate, when)
1901-
assert (
1902-
get(
1903-
certificate,
1924+
if not conftest.is_awslc:
1925+
# A plus two hours and thirty minutes offset
1926+
when = b"20040203040506+0530"
1927+
set(certificate, when)
1928+
assert get(certificate) == when
1929+
1930+
# A minus one hour fifteen minutes offset
1931+
when = b"20040203040506-0115"
1932+
set(certificate, when)
1933+
assert (
1934+
get(
1935+
certificate,
1936+
)
1937+
== when
19041938
)
1905-
== when
1906-
)
19071939

19081940
# An invalid string results in a ValueError
19091941
with pytest.raises(ValueError):

tests/test_ssl.py

Lines changed: 60 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,6 @@
6767
)
6868
from OpenSSL.SSL import (
6969
DTLS_METHOD,
70-
MODE_RELEASE_BUFFERS,
7170
NO_OVERLAPPING_PROTOCOLS,
7271
OP_COOKIE_EXCHANGE,
7372
OP_NO_COMPRESSION,
@@ -132,6 +131,7 @@
132131
_NoOverlappingProtocols,
133132
)
134133

134+
from . import conftest
135135
from .test_crypto import (
136136
client_cert_pem,
137137
client_key_pem,
@@ -643,6 +643,12 @@ def test_set_cipher_list_no_cipher_match(self, context: Context) -> None:
643643
"",
644644
"no cipher match",
645645
),
646+
# aws-lc
647+
(
648+
"SSL routines",
649+
"OPENSSL_internal",
650+
"NO_CIPHER_MATCH",
651+
),
646652
]
647653

648654
def test_load_client_ca(self, context: Context, ca_file: bytes) -> None:
@@ -700,6 +706,12 @@ def test_set_session_id_fail(self, context: Context) -> None:
700706
"",
701707
"ssl session id context too long",
702708
),
709+
# aws-lc
710+
(
711+
"SSL routines",
712+
"OPENSSL_internal",
713+
"SSL_SESSION_ID_CONTEXT_TOO_LONG",
714+
),
703715
]
704716

705717
def test_set_session_id_unicode(self, context: Context) -> None:
@@ -922,7 +934,8 @@ def test_set_mode(self) -> None:
922934
newly set mode.
923935
"""
924936
context = Context(SSLv23_METHOD)
925-
assert MODE_RELEASE_BUFFERS & context.set_mode(MODE_RELEASE_BUFFERS)
937+
mode = _lib.SSL_MODE_ENABLE_PARTIAL_WRITE
938+
assert mode & context.set_mode(mode)
926939

927940
def test_set_timeout_wrong_args(self) -> None:
928941
"""
@@ -969,7 +982,7 @@ def _write_encrypted_pem(self, passphrase: bytes, tmpfile: bytes) -> bytes:
969982
"""
970983
key = PKey()
971984
key.generate_key(TYPE_RSA, 1024)
972-
pem = dump_privatekey(FILETYPE_PEM, key, "blowfish", passphrase)
985+
pem = dump_privatekey(FILETYPE_PEM, key, "aes-256-cbc", passphrase)
973986
with open(tmpfile, "w") as fObj:
974987
fObj.write(pem.decode("ascii"))
975988
return tmpfile
@@ -1163,7 +1176,7 @@ def test_set_proto_version(self) -> None:
11631176
client_context = Context(TLS_METHOD)
11641177
client_context.set_max_proto_version(low_version)
11651178

1166-
with pytest.raises(Error, match="unsupported protocol"):
1179+
with pytest.raises(Error, match=r"(?i)unsupported.protocol"):
11671180
self._handshake_test(server_context, client_context)
11681181

11691182
client_context = Context(TLS_METHOD)
@@ -1632,7 +1645,9 @@ def test_set_verify_default_callback(self, mode: int) -> None:
16321645
if mode == SSL.VERIFY_PEER:
16331646
with pytest.raises(Exception) as exc:
16341647
self._handshake_test(serverContext, clientContext)
1635-
assert "certificate verify failed" in str(exc.value)
1648+
assert "certificate verify failed" in str(
1649+
exc.value
1650+
) or "CERTIFICATE_VERIFY_FAILED" in str(exc.value)
16361651
else:
16371652
self._handshake_test(serverContext, clientContext)
16381653

@@ -1861,9 +1876,27 @@ def test_set_tmp_ecdh(self) -> None:
18611876
with pytest.deprecated_call():
18621877
context.set_tmp_ecdh(curve)
18631878

1879+
awslc_unsupported_curves = {
1880+
"BRAINPOOLP256R1",
1881+
"BRAINPOOLP384R1",
1882+
"BRAINPOOLP512R1",
1883+
"SECP192R1",
1884+
"SECT163K1",
1885+
"SECT163R2",
1886+
"SECT233K1",
1887+
"SECT233R1",
1888+
"SECT283K1",
1889+
"SECT283R1",
1890+
"SECT409K1",
1891+
"SECT409R1",
1892+
"SECT571K1",
1893+
"SECT571R1",
1894+
}
18641895
for name in dir(ec.EllipticCurveOID):
18651896
if name.startswith("_"):
18661897
continue
1898+
if conftest.is_awslc and name in awslc_unsupported_curves:
1899+
continue
18671900
oid = getattr(ec.EllipticCurveOID, name)
18681901
cryptography_curve = ec.get_curve_for_oid(oid)
18691902
context.set_tmp_ecdh(cryptography_curve())
@@ -2699,10 +2732,12 @@ def test_state_string(self) -> None:
26992732
assert tls_server.get_state_string() in [
27002733
b"before/accept initialization",
27012734
b"before SSL initialization",
2735+
b"TLS server start_accept",
27022736
]
27032737
assert tls_client.get_state_string() in [
27042738
b"before/connect initialization",
27052739
b"before SSL initialization",
2740+
b"TLS client start_connect",
27062741
]
27072742

27082743
def test_app_data(self) -> None:
@@ -3179,9 +3214,10 @@ def _perform_moving_buffer_test(
31793214
return False # Retry succeeded
31803215
except SSL.Error as e:
31813216
reason = get_ssl_error_reason(e)
3182-
assert reason == "bad write retry", (
3183-
f"Retry failed with unexpected SSL error: {e!r}({reason})."
3184-
)
3217+
assert reason in (
3218+
"bad write retry",
3219+
"BAD_WRITE_RETRY",
3220+
), f"Retry failed with unexpected SSL error: {e!r}({reason})."
31853221
return True # Bad write retry
31863222

31873223
def _shutdown_connections(
@@ -3895,6 +3931,10 @@ def test_total_renegotiations(self) -> None:
38953931
connection = Connection(Context(SSLv23_METHOD), None)
38963932
assert connection.total_renegotiations() == 0
38973933

3934+
@pytest.mark.skipif(
3935+
conftest.is_awslc,
3936+
reason="aws-lc doesn't support renegotiation",
3937+
)
38983938
def test_renegotiate(self) -> None:
38993939
"""
39003940
Go through a complete renegotiation cycle.
@@ -3988,6 +4028,10 @@ def test_op_no_ticket(self) -> None:
39884028
"OP_NO_COMPRESSION unavailable - OpenSSL version may be too old"
39894029
),
39904030
)
4031+
@pytest.mark.skipif(
4032+
conftest.is_awslc,
4033+
reason="aws-lc defines OP_NO_COMPRESSION as 0",
4034+
)
39914035
def test_op_no_compression(self) -> None:
39924036
"""
39934037
The value of `OpenSSL.SSL.OP_NO_COMPRESSION` is 0x20000, the
@@ -5050,9 +5094,17 @@ def pump() -> None:
50505094
c.set_ciphertext_mtu(500)
50515095
assert 0 < c.get_cleartext_mtu() < 500
50525096

5097+
@pytest.mark.skipif(
5098+
OP_COOKIE_EXCHANGE is None,
5099+
reason="DTLS cookie exchange not supported",
5100+
)
50535101
def test_it_works_at_all(self) -> None:
50545102
self._test_handshake_and_data(srtp_profile=None)
50555103

5104+
@pytest.mark.skipif(
5105+
OP_COOKIE_EXCHANGE is None,
5106+
reason="DTLS cookie exchange not supported",
5107+
)
50565108
def test_it_works_with_srtp(self) -> None:
50575109
self._test_handshake_and_data(srtp_profile=b"SRTP_AES128_CM_SHA1_80")
50585110

tests/test_util.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,4 @@ def test_exception_from_error_queue_nonexistent_reason(self) -> None:
1616
lib.ERR_put_error(lib.ERR_LIB_EVP, 0, 1112, b"", 10)
1717
with pytest.raises(ValueError) as exc:
1818
exception_from_error_queue(ValueError)
19-
assert exc.value.args[0][0][2] == ""
19+
assert exc.value.args[0][0][2] in ("", "unknown error")

0 commit comments

Comments
 (0)