Skip to content

Commit 6135628

Browse files
Add initial support for crypto callbacks.
1 parent ec8ce54 commit 6135628

4 files changed

Lines changed: 302 additions & 1 deletion

File tree

scripts/build_ffi.py

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,10 @@ def make_flags(prefix, fips):
238238
# ML-DSA
239239
flags.append("--enable-dilithium")
240240

241+
# Crypto Callbacks
242+
flags.append("--enable-cryptocb")
243+
# flags.append("EXTRACPPFLAGS=-DDEBUG_CRYPTOCB")
244+
241245
# disabling other configs enabled by default
242246
flags.append("--disable-oldtls")
243247
flags.append("--disable-oldnames")
@@ -378,6 +382,7 @@ def get_features(local_wolfssl, features):
378382
features["ML_DSA"] = 1 if '#define HAVE_DILITHIUM' in defines else 0
379383
features["ML_KEM"] = 1 if '#define WOLFSSL_HAVE_MLKEM' in defines else 0
380384
features["HKDF"] = 1 if "#define HAVE_HKDF" in defines else 0
385+
features["CRYPTO_CB"] = 1 if "#define WOLF_CRYPTO_CB" in defines else 0
381386

382387
if '#define HAVE_FIPS' in defines:
383388
if not fips:
@@ -456,6 +461,7 @@ def build_ffi(local_wolfssl, features):
456461
#include <wolfssl/wolfcrypt/mlkem.h>
457462
#include <wolfssl/wolfcrypt/wc_mlkem.h>
458463
#include <wolfssl/wolfcrypt/dilithium.h>
464+
#include <wolfssl/wolfcrypt/cryptocb.h>
459465
"""
460466

461467
init_source_string = """
@@ -497,6 +503,7 @@ def build_ffi(local_wolfssl, features):
497503
int ML_KEM_ENABLED = """ + str(features["ML_KEM"]) + """;
498504
int ML_DSA_ENABLED = """ + str(features["ML_DSA"]) + """;
499505
int HKDF_ENABLED = """ + str(features["HKDF"]) + """;
506+
int CRYPTO_CB_ENABLED = """ + str(features["CRYPTO_CB"]) + """;
500507
"""
501508

502509
ffibuilder.set_source( "wolfcrypt._ffi", init_source_string,
@@ -537,13 +544,16 @@ def build_ffi(local_wolfssl, features):
537544
extern int ML_KEM_ENABLED;
538545
extern int ML_DSA_ENABLED;
539546
extern int HKDF_ENABLED;
547+
extern int CRYPTO_CB_ENABLED;
540548
541549
typedef unsigned char byte;
542550
typedef unsigned int word32;
543551
544552
typedef struct { ...; } WC_RNG;
545553
typedef struct { ...; } OS_Seed;
546554
555+
int wolfCrypt_Init(void);
556+
547557
int wc_InitRng(WC_RNG*);
548558
int wc_InitRngNonce(WC_RNG*, byte*, word32);
549559
int wc_InitRngNonce_ex(WC_RNG*, byte*, word32, void*, int);
@@ -1331,6 +1341,110 @@ def build_ffi(local_wolfssl, features):
13311341
int wc_MlDsaKey_GetSigLen(MlDsaKey* key, int* len);
13321342
"""
13331343

1344+
if features["CRYPTO_CB"]:
1345+
cdef += """
1346+
static const int WC_ALGO_TYPE_NONE;
1347+
static const int WC_ALGO_TYPE_HASH;
1348+
static const int WC_ALGO_TYPE_CIPHER;
1349+
static const int WC_ALGO_TYPE_PK;
1350+
static const int WC_ALGO_TYPE_RNG;
1351+
static const int WC_ALGO_TYPE_SEED;
1352+
static const int WC_ALGO_TYPE_HMAC;
1353+
static const int WC_ALGO_TYPE_CMAC;
1354+
static const int WC_ALGO_TYPE_CERT;
1355+
static const int WC_ALGO_TYPE_KDF;
1356+
static const int WC_ALGO_TYPE_COPY;
1357+
static const int WC_ALGO_TYPE_FREE;
1358+
static const int WC_ALGO_TYPE_MAX;
1359+
1360+
static const int WC_HASH_TYPE_SHA; /* SHA-1 (not old SHA-0) */
1361+
static const int WC_HASH_TYPE_SHA256;
1362+
static const int WC_HASH_TYPE_SHA384;
1363+
static const int WC_HASH_TYPE_SHA512;
1364+
static const int WC_HASH_TYPE_SHA3_256;
1365+
static const int WC_HASH_TYPE_SHA3_384;
1366+
static const int WC_HASH_TYPE_SHA3_512;
1367+
"""
1368+
1369+
1370+
cdef += """
1371+
typedef struct {
1372+
int algo_type; /* enum wc_AlgoType */
1373+
union {
1374+
"""
1375+
1376+
"""
1377+
struct {
1378+
int type; /* enum wc_CipherType */
1379+
int enc;
1380+
union {
1381+
//wc_CryptoCb_AesAuthEnc aesgcm_enc;
1382+
//wc_CryptoCb_AesAuthDec aesgcm_dec;
1383+
//wc_CryptoCb_AesAuthEnc aesccm_enc;
1384+
//wc_CryptoCb_AesAuthDec aesccm_dec;
1385+
struct {
1386+
Aes* aes;
1387+
byte* out;
1388+
const byte* in;
1389+
word32 sz;
1390+
} aescbc;
1391+
//struct {
1392+
// Aes* aes;
1393+
// byte* out;
1394+
// const byte* in;
1395+
// word32 sz;
1396+
//} aesctr;
1397+
//struct {
1398+
// Aes* aes;
1399+
// byte* out;
1400+
// const byte* in;
1401+
// word32 sz;
1402+
//} aesecb;
1403+
//struct {
1404+
// Des3* des;
1405+
// byte* out;
1406+
// const byte* in;
1407+
// word32 sz;
1408+
//} des3;
1409+
//void* ctx;
1410+
};
1411+
} cipher;
1412+
"""
1413+
cdef += """
1414+
struct {
1415+
int type; /* enum wc_HashType */
1416+
const byte* data;
1417+
word32 data_size;
1418+
byte* digest;
1419+
union {
1420+
wc_Sha* sha1;
1421+
// wc_Sha224* sha224;
1422+
wc_Sha256* sha256;
1423+
wc_Sha384* sha384;
1424+
wc_Sha512* sha512;
1425+
wc_Sha3* sha3;
1426+
void* ctx;
1427+
} u;
1428+
} hash;
1429+
"""
1430+
cdef += """
1431+
struct {
1432+
WC_RNG* rng;
1433+
byte* out;
1434+
word32 sz;
1435+
} rng;
1436+
};
1437+
...;
1438+
} wc_CryptoInfo;
1439+
1440+
typedef int (*CryptoDevCallbackFunc)(int devId, wc_CryptoInfo* info, void* ctx);
1441+
extern "Python" int py_wc_crypto_callback(int devId, wc_CryptoInfo* info, void* ctx);
1442+
int wc_CryptoCb_RegisterDevice(int devId, CryptoDevCallbackFunc cb, void* ctx);
1443+
void wc_CryptoCb_UnRegisterDevice(int devId);
1444+
int wc_CryptoCb_DefaultDevID();
1445+
// void wc_CryptoCb_InfoString(wc_CryptoInfo* info);
1446+
"""
1447+
13341448
ffibuilder.cdef(cdef)
13351449

13361450
def main(ffibuilder):
@@ -1365,6 +1479,7 @@ def main(ffibuilder):
13651479
"ML_KEM": 1,
13661480
"ML_DSA": 1,
13671481
"HKDF": 1,
1482+
"CRYPTO_CB": 1,
13681483
}
13691484

13701485
# Ed448 requires SHAKE256, which isn't part of the Windows build, yet.

tests/test_cryptocb.py

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
# test_cryptocb.py
2+
#
3+
# Copyright (C) 2026 wolfSSL Inc.
4+
#
5+
# This file is part of wolfSSL. (formerly known as CyaSSL)
6+
#
7+
# wolfSSL is free software; you can redistribute it and/or modify
8+
# it under the terms of the GNU General Public License as published by
9+
# the Free Software Foundation; either version 2 of the License, or
10+
# (at your option) any later version.
11+
#
12+
# wolfSSL is distributed in the hope that it will be useful,
13+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
14+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15+
# GNU General Public License for more details.
16+
#
17+
# You should have received a copy of the GNU General Public License
18+
# along with this program; if not, write to the Free Software
19+
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
20+
21+
import pytest
22+
23+
from wolfcrypt._ffi import lib as _lib
24+
from wolfcrypt.random import Random
25+
26+
27+
if not _lib.CRYPTO_CB_ENABLED:
28+
pytest.skip("Crypto Callbacks not supported", allow_module_level=True)
29+
30+
from wolfcrypt.cryptocb import CryptoCallback
31+
32+
33+
def test_default_device_id():
34+
print(f"Default device ID = {CryptoCallback.default_device_id()}")
35+
36+
class RngCryptoCallback(CryptoCallback):
37+
def rng_callback(self, _device_id: int, _rng, size: int) -> bytes:
38+
# Generate fake random data for testing purposes.
39+
return bytes(range(1, 1 + size))
40+
41+
42+
def test_rng_callback():
43+
with RngCryptoCallback(10):
44+
rng = Random(device_id=10)
45+
46+
random = rng.byte()
47+
assert random == b"\01"
48+
49+
random = rng.bytes(1)
50+
assert random == b"\01"
51+
52+
random = rng.bytes(3)
53+
assert random == b"\01\02\03"

wolfcrypt/__init__.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333
__all__ = [
3434
"__title__", "__summary__", "__uri__", "__version__",
3535
"__author__", "__email__", "__license__", "__copyright__",
36-
"ciphers", "hashes", "random", "pwdbased"
36+
"ciphers", "hashes", "random", "pwdbased", "cryptocb"
3737
]
3838

3939
import os
@@ -46,8 +46,19 @@
4646
if top_level_py not in ["setup.py", "build_ffi.py"]:
4747
from wolfcrypt._ffi import ffi as _ffi
4848
from wolfcrypt._ffi import lib as _lib
49+
from wolfcrypt.cryptocb import CryptoCallback
4950
from wolfcrypt.exceptions import WolfCryptError
5051

52+
_lib.wolfCrypt_Init()
53+
54+
if _lib.CRYPTO_CB_ENABLED:
55+
@_ffi.def_extern()
56+
def py_wc_crypto_callback(device_id: int, info: _ffi.CData, ctx: _ffi.CData) -> int:
57+
if ctx == _ffi.NULL:
58+
return _lib.CRYPTOCB_UNAVAILABLE
59+
crypto_cb: CryptoCallback = _ffi.from_handle(ctx)
60+
return crypto_cb.callback(device_id, info)
61+
5162
if hasattr(_lib, 'WC_RNG_SEED_CB_ENABLED'):
5263
if _lib.WC_RNG_SEED_CB_ENABLED:
5364
ret = _lib.wc_SetSeed_Cb(_ffi.addressof(_lib, "wc_GenerateSeed"))

wolfcrypt/cryptocb.py

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
# cryptocb.py
2+
#
3+
# Copyright (C) 2026 wolfSSL Inc.
4+
#
5+
# This file is part of wolfSSL. (formerly known as CyaSSL)
6+
#
7+
# wolfSSL is free software; you can redistribute it and/or modify
8+
# it under the terms of the GNU General Public License as published by
9+
# the Free Software Foundation; either version 2 of the License, or
10+
# (at your option) any later version.
11+
#
12+
# wolfSSL is distributed in the hope that it will be useful,
13+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
14+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15+
# GNU General Public License for more details.
16+
#
17+
# You should have received a copy of the GNU General Public License
18+
# along with this program; if not, write to the Free Software
19+
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
20+
21+
# pylint: disable=no-member,no-name-in-module
22+
23+
from __future__ import annotations
24+
25+
from typing import Final
26+
from typing_extensions import Self
27+
28+
from wolfcrypt._ffi import ffi as _ffi
29+
from wolfcrypt._ffi import lib as _lib
30+
31+
from wolfcrypt.exceptions import WolfCryptError
32+
33+
ALGO_TYPE_NAME: Final = {
34+
_lib.WC_ALGO_TYPE_HASH: "hash",
35+
_lib.WC_ALGO_TYPE_CIPHER: "cipher",
36+
_lib.WC_ALGO_TYPE_RNG: "rng",
37+
_lib.WC_ALGO_TYPE_SEED: "seed",
38+
}
39+
40+
HASH_TYPE_NAME: Final = {
41+
_lib.WC_HASH_TYPE_SHA: "SHA1",
42+
_lib.WC_HASH_TYPE_SHA256: "SHA256",
43+
_lib.WC_HASH_TYPE_SHA384: "SHA384",
44+
_lib.WC_HASH_TYPE_SHA512: "SHA512",
45+
_lib.WC_HASH_TYPE_SHA3_256: "SHA3_256",
46+
_lib.WC_HASH_TYPE_SHA3_384: "SHA3_384",
47+
_lib.WC_HASH_TYPE_SHA3_512: "SHA3_512",
48+
}
49+
50+
DIGEST_SIZE: Final = {
51+
_lib.WC_HASH_TYPE_SHA: 20,
52+
_lib.WC_HASH_TYPE_SHA256: 32,
53+
_lib.WC_HASH_TYPE_SHA384: 48,
54+
_lib.WC_HASH_TYPE_SHA512: 64,
55+
_lib.WC_HASH_TYPE_SHA3_256: 32,
56+
_lib.WC_HASH_TYPE_SHA3_384: 48,
57+
_lib.WC_HASH_TYPE_SHA3_512: 64,
58+
}
59+
60+
61+
if _lib.CRYPTO_CB_ENABLED:
62+
class CryptoCallback:
63+
def __init__(self, device_id: int):
64+
self.device_id = device_id
65+
self.ctx = _ffi.new_handle(self)
66+
ret = _lib.wc_CryptoCb_RegisterDevice(device_id, _lib.py_wc_crypto_callback, self.ctx)
67+
if ret < 0: # pragma: no cover
68+
raise WolfCryptError(f"CryptoCb device registration error ({ret})")
69+
70+
def __enter__(self) -> Self:
71+
return self
72+
73+
def __exit__(self, exc_type, exc_value, traceback) -> None:
74+
self._unregister()
75+
76+
def __del__(self) -> None:
77+
self._unregister()
78+
79+
def callback(self, device_id: int, info: _ffi.CData) -> int:
80+
print(f"{device_id=} algo = {ALGO_TYPE_NAME[info.algo_type]}")
81+
# _lib.wc_CryptoCb_InfoString(info)
82+
try:
83+
if info.algo_type == _lib.WC_ALGO_TYPE_HASH:
84+
print(f"hash = {HASH_TYPE_NAME[info.hash.type]}")
85+
print(f"{info.hash.data=} {info.hash.data_size=} {info.hash.digest=} {info.hash.u.sha256=}")
86+
if info.hash.digest == _ffi.NULL:
87+
self.hash_update_callback(device_id, info.hash.type, bytes(_ffi.buffer(info.hash.data, info.hash.data_size)))
88+
else:
89+
digest = self.hash_finalize_callback(device_id, info.hash.type)
90+
_ffi.buffer(info.hash.digest, DIGEST_SIZE[info.hash.type])[:] = digest
91+
return 0
92+
if info.algo_type == _lib.WC_ALGO_TYPE_CIPHER:
93+
self.cipher_callback(device_id)
94+
return 0
95+
if info.algo_type == _lib.WC_ALGO_TYPE_RNG:
96+
out = self.rng_callback(device_id, info.rng.rng, info.rng.sz)
97+
_ffi.buffer(info.rng.out, info.rng.sz)[:] = out
98+
return 0
99+
return _lib.CRYPTOCB_UNAVAILABLE
100+
except NotImplementedError:
101+
return _lib.CRYPTOCB_UNAVAILABLE
102+
103+
def rng_callback(self, device_id: int, rng, size: int) -> bytes:
104+
raise NotImplementedError
105+
106+
def hash_update_callback(self, device_id: int, hash_type: int, data: bytes) -> None:
107+
print("hash_update_callback")
108+
raise NotImplementedError
109+
110+
def hash_finalize_callback(self, device_id: int, hash_type: int) -> bytes:
111+
print("hash_finalize_callback")
112+
raise NotImplementedError
113+
114+
def cipher_callback(self, device_id: int) -> None:
115+
raise NotImplementedError
116+
117+
def _unregister(self) -> None:
118+
_lib.wc_CryptoCb_UnRegisterDevice(self.device_id)
119+
120+
@classmethod
121+
def default_device_id(cls) -> int:
122+
return _lib.wc_CryptoCb_DefaultDevID()

0 commit comments

Comments
 (0)