Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
5203cb0
Disarm ChaCha when set_iv() rejects the nonce length (F-10069)
julek-wolfssl Sep 24, 2026
9979253
Validate ECC public keys with wc_ecc_check_key on import (F-8277)
julek-wolfssl Sep 24, 2026
a184041
Size ECC private key DER buffer with wc_EccKeyDerSize (F-10070)
julek-wolfssl Sep 24, 2026
1e27639
Detect AES-CTR support before declaring wc_AesCtrEncrypt (F-11250)
julek-wolfssl Sep 24, 2026
30d22ab
Declare AES-CBC functions only when wolfSSL provides them (F-12224)
julek-wolfssl Sep 24, 2026
77a2c28
Gate streaming AES-GCM decryption on wolfSSL decrypt support (F-12225)
julek-wolfssl Sep 24, 2026
7c2eef3
Declare SHA-3 functions only for sizes wolfSSL provides (F-12226)
julek-wolfssl Sep 24, 2026
4e6f1b8
Detect HKDF only when wolfSSL also provides HMAC (F-13020)
julek-wolfssl Sep 24, 2026
27fc298
Declare wc_PBKDF2 only when wolfSSL builds PBKDF2 with HMAC (F-12232)
julek-wolfssl Sep 24, 2026
d0a00a0
Detect PEM-to-DER and DER-to-PEM support separately (F-12233)
julek-wolfssl Sep 24, 2026
1b73449
Declare wc_GetPkcs8TraditionalOffset only with PKCS#8 support (F-12234)
julek-wolfssl Sep 24, 2026
0d96ac5
Declare RNG functions only when wolfSSL builds its RNG (F-13021)
julek-wolfssl Sep 24, 2026
3fc2fd1
Declare RSA operations only when wolfSSL builds them (F-12227)
julek-wolfssl Sep 24, 2026
2d5ddbb
Declare ECC operations only when wolfSSL builds them (F-12228)
julek-wolfssl Sep 24, 2026
45141b9
Validate ECC private keys with wc_ecc_check_key on import
julek-wolfssl Sep 28, 2026
0c45c88
Declare Ed25519 operations only when wolfSSL builds them (F-12229)
julek-wolfssl Sep 24, 2026
91eda40
Declare Ed448 operations only when wolfSSL builds them (F-12230)
julek-wolfssl Sep 24, 2026
6536a3b
Declare ML-KEM operations only when wolfSSL builds them (F-12231)
julek-wolfssl Sep 24, 2026
10bd367
Declare ML-DSA operations only when wolfSSL builds them (F-10071)
julek-wolfssl Sep 24, 2026
3acbdd4
Detect ML-DSA no-context macros on the last line (F-13023)
julek-wolfssl Sep 28, 2026
e3b0c99
Detect feature macros regardless of indentation or value (F-8281)
julek-wolfssl Sep 28, 2026
b9947aa
Check the digest size in ECDSA sign() and verify() (F-8279)
julek-wolfssl Sep 24, 2026
ee6ef4d
Check the digest size in ECDSA sign_raw() and verify_raw() (F-8280)
julek-wolfssl Sep 24, 2026
b23d8ef
Build bundled wolfSSL with -fPIC on every Linux platform
julek-wolfssl Sep 25, 2026
0edda71
Run CI on arm64 Linux too
julek-wolfssl Sep 25, 2026
3be92c2
Address review: create RSA RNG on first use
julek-wolfssl Sep 29, 2026
08dc337
Fix CI: restore patched cffi lib flags without monkeypatch
julek-wolfssl Nov 2, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion .github/workflows/python-app.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,12 @@ permissions:
jobs:
build:

runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
os: [ ubuntu-latest, ubuntu-24.04-arm ]

runs-on: ${{ matrix.os }}

steps:
- uses: actions/checkout@v7
Expand Down
761 changes: 568 additions & 193 deletions scripts/build_ffi.py

Large diffs are not rendered by default.

68 changes: 68 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
# conftest.py
#
# Copyright (C) 2006-2026 wolfSSL Inc.
#
# This file is part of wolfSSL.
#
# wolfSSL is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
#
# wolfSSL is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA

"""Shared pytest fixtures."""

from __future__ import annotations

import contextlib
from typing import TYPE_CHECKING, Any

import pytest

from wolfcrypt._ffi import lib as _lib

if TYPE_CHECKING:
from collections.abc import Iterator


class LibPatch:
"""Override wolfcrypt._ffi.lib attributes and restore them on undo().

pytest's monkeypatch restores values from Lib.__dict__, which holds cffi
internals that cannot be assigned back to a Lib.
"""

def __init__(self) -> None:
self._saved: list[tuple[str, Any]] = []

def setattr(self, name: str, value: Any) -> None:
self._saved.append((name, getattr(_lib, name)))
setattr(_lib, name, value)

def undo(self) -> None:
while self._saved:
name, value = self._saved.pop()
setattr(_lib, name, value)

@contextlib.contextmanager
def context(self) -> Iterator[LibPatch]:
patch = LibPatch()
try:
yield patch
finally:
patch.undo()


@pytest.fixture
def lib_patch() -> Iterator[LibPatch]:
patch = LibPatch()
yield patch
patch.undo()
23 changes: 23 additions & 0 deletions tests/test_aesgcmstream.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@
from binascii import hexlify as b2h
from wolfcrypt.ciphers import AesGcmStream

def skip_without_decrypt():
if not _lib.AESGCM_STREAM_DECRYPT_ENABLED:
pytest.skip("AES-GCM streaming decryption is not compiled in")

def test_encrypt():
"""Known answer encrypt-decrypt test with default authentication tag size of 16 bytes"""
key = "fedcba9876543210"
Expand All @@ -42,6 +46,7 @@ def test_encrypt():
assert authTag is not None
assert b2h(authTag) == bytes('ac8fcee96dc6ef8e5236da19b6197d2e', 'utf-8')
assert b2h(buf) == bytes('5ba7d42e1bf01d7998e932', "utf-8")
skip_without_decrypt()
gcmdec = AesGcmStream(key, iv)
bufdec = gcmdec.decrypt(buf)
gcmdec.final(authTag)
Expand All @@ -58,6 +63,7 @@ def test_encrypt_short_tag():
assert authTag is not None
assert b2h(authTag) == bytes('ac8fcee96dc6ef8e5236da19', 'utf-8')
assert b2h(buf) == bytes('5ba7d42e1bf01d7998e932', "utf-8")
skip_without_decrypt()
gcmdec = AesGcmStream(key, iv, 12)
bufdec = gcmdec.decrypt(buf)
gcmdec.final(authTag)
Expand All @@ -73,6 +79,7 @@ def test_multipart():
assert authTag is not None
assert b2h(authTag) == bytes('ac8fcee96dc6ef8e5236da19b6197d2e', 'utf-8')
assert b2h(buf) == bytes('5ba7d42e1bf01d7998e932', "utf-8")
skip_without_decrypt()
gcmdec = AesGcmStream(key, iv)
bufdec = gcmdec.decrypt(buf[:5])
bufdec += gcmdec.decrypt(buf[5:])
Expand All @@ -91,6 +98,7 @@ def test_encrypt_aad():
print(b2h(authTag))
assert b2h(authTag) == bytes('8f85338aa0b13f48f8b17482dbb8acca', 'utf-8')
assert b2h(buf) == bytes('5ba7d42e1bf01d7998e932', "utf-8")
skip_without_decrypt()
gcmdec = AesGcmStream(key, iv)
gcmdec.set_aad(aad)
bufdec = gcmdec.decrypt(buf)
Expand All @@ -109,6 +117,7 @@ def test_multipart_aad():
assert authTag is not None
assert b2h(authTag) == bytes('8f85338aa0b13f48f8b17482dbb8acca', 'utf-8')
assert b2h(buf) == bytes('5ba7d42e1bf01d7998e932', "utf-8")
skip_without_decrypt()
gcmdec = AesGcmStream(key, iv)
gcmdec.set_aad(aad)
bufdec = gcmdec.decrypt(buf[:5])
Expand All @@ -117,6 +126,7 @@ def test_multipart_aad():
assert bufdec == t2b("hello world")

def test_encrypt_aad_bad():
skip_without_decrypt()
key = "fedcba9876543210"
iv = "0123456789abcdef"
aad = "aad data"
Expand Down Expand Up @@ -163,6 +173,7 @@ def test_invalid_tag_bytes():
assert len(tag) == good

def test_decrypt_rejects_wrong_tag_length():
skip_without_decrypt()
key = "fedcba9876543210"
iv = "0123456789abcdef"
gcm = AesGcmStream(key, iv, tag_bytes=16)
Expand Down Expand Up @@ -200,3 +211,15 @@ def test_repeated_construction_destruction():
gcm.final()
del gcm
gc.collect()

def test_decrypt_rejected_when_not_compiled_in(lib_patch):
"""F-12225: streaming decryption needs AES-GCM decryption in the linked wolfSSL."""
lib_patch.setattr("AESGCM_STREAM_DECRYPT_ENABLED", 0)
key = "fedcba9876543210"
iv = "0123456789abcdef"
gcm = AesGcmStream(key, iv)
with pytest.raises(NotImplementedError, match="AES-GCM streaming decryption"):
gcm.decrypt(bytes.fromhex("5ba7d42e1bf01d7998e932"))
# The rejected call leaves the object usable for encryption.
assert gcm.encrypt("hello world") == bytes.fromhex("5ba7d42e1bf01d7998e932")
assert gcm.final() == bytes.fromhex("ac8fcee96dc6ef8e5236da19b6197d2e")
41 changes: 35 additions & 6 deletions tests/test_asn.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,19 @@
# ty: ignore[possibly-missing-import]

from collections import namedtuple
import importlib.util
import pytest
import os
from wolfcrypt import asn
from wolfcrypt._ffi import lib as _lib
from wolfcrypt.utils import h2b

if _lib.ASN_ENABLED:
from wolfcrypt.asn import pem_to_der, der_to_pem, make_signature, check_signature
from wolfcrypt.asn import make_signature, check_signature
if _lib.PEM_TO_DER_ENABLED:
from wolfcrypt.asn import pem_to_der
if _lib.DER_TO_PEM_ENABLED:
from wolfcrypt.asn import der_to_pem
if _lib.SHA256_ENABLED:
from wolfcrypt.hashes import Sha256
if _lib.RSA_ENABLED:
Expand Down Expand Up @@ -77,7 +83,9 @@ def signature_vectors():
# Signature computed with:
# echo -n "wolfcrypt is the best crypto around" | \
# openssl dgst -hex -sha256 -sign tests/certs/server-key.pem
if _lib.ASN_ENABLED and _lib.SHA256_ENABLED and _lib.RSA_ENABLED:
# RSA key objects always create a Random.
if (_lib.PEM_TO_DER_ENABLED and _lib.SHA256_ENABLED and _lib.RSA_ENABLED and _lib.RNG_ENABLED
and _lib.RSA_SIGN_ENABLED and _lib.RSA_VERIFY_ENABLED):
vectors.append(TestVector(
data="wolfcrypt is the best crypto around",
signature=h2b("1d65f21df8fdc9f3c2351792840423481c6b0f2332105abd9248"
Expand All @@ -99,14 +107,35 @@ def signature_vectors():

def test_pem_der_conversion(pem_der_conversion_vectors):
for vector in pem_der_conversion_vectors:
computed_der = pem_to_der(vector.pem, vector.type)
assert computed_der == vector.der
if _lib.PEM_TO_DER_ENABLED:
computed_der = pem_to_der(vector.pem, vector.type)
assert computed_der == vector.der

computed_pem = der_to_pem(vector.der, vector.type)
assert computed_pem == vector.pem
if _lib.DER_TO_PEM_ENABLED:
computed_pem = der_to_pem(vector.der, vector.type)
assert computed_pem == vector.pem

def test_signature(signature_vectors):
for vector in signature_vectors:
assert make_signature(vector.data, vector.hash_cls, vector.priv_key) == vector.signature
assert check_signature(vector.signature, vector.data, vector.hash_cls,
vector.pub_key)

def test_pem_der_helpers_defined_only_when_enabled(lib_patch):
"""F-12233: each conversion direction needs its own wolfSSL function."""
helpers = {"PEM_TO_DER_ENABLED": "pem_to_der", "DER_TO_PEM_ENABLED": "der_to_pem"}
for flag, name in helpers.items():
assert hasattr(asn, name) == bool(getattr(_lib, flag)), name

# Load fresh copies of the module as if one direction were not compiled in.
for disabled, disabled_name in helpers.items():
with lib_patch.context() as m:
m.setattr(disabled, 0)
spec = importlib.util.find_spec("wolfcrypt.asn")
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
assert not hasattr(module, disabled_name), disabled
for flag, name in helpers.items():
if flag != disabled:
assert hasattr(module, name) == bool(getattr(_lib, flag)), (disabled, name)
assert hasattr(module, "make_signature") == bool(_lib.ASN_ENABLED), disabled
Loading
Loading