commit
8d00f5f19b
|
|
@ -304,13 +304,14 @@ def generate_libwolfssl(fips):
|
|||
|
||||
def get_features(local_wolfssl, features):
|
||||
fips = False
|
||||
fips_file = None
|
||||
|
||||
if sys.platform == "win32":
|
||||
if local_wolfssl and sys.platform == "win32":
|
||||
# On Windows, we assume the local_wolfssl path is to a wolfSSL source
|
||||
# directory where the library has been built.
|
||||
fips_file = os.path.join(local_wolfssl, "wolfssl", "wolfcrypt",
|
||||
"fips.h")
|
||||
else:
|
||||
elif local_wolfssl:
|
||||
# On non-Windows platforms, first assume local_wolfssl is an
|
||||
# installation directory with an include subdirectory.
|
||||
fips_file = os.path.join(local_wolfssl, "include", "wolfssl",
|
||||
|
|
@ -320,7 +321,7 @@ def get_features(local_wolfssl, features):
|
|||
fips_file = os.path.join(local_wolfssl, "wolfssl", "wolfcrypt",
|
||||
"fips.h")
|
||||
|
||||
if os.path.exists(fips_file):
|
||||
if fips_file and os.path.exists(fips_file):
|
||||
with open(fips_file, "r") as f:
|
||||
contents = f.read()
|
||||
if not contents.isspace():
|
||||
|
|
@ -495,6 +496,7 @@ def build_ffi(local_wolfssl, features):
|
|||
int ML_KEM_ENABLED = """ + str(features["ML_KEM"]) + """;
|
||||
int ML_DSA_ENABLED = """ + str(features["ML_DSA"]) + """;
|
||||
int HKDF_ENABLED = """ + str(features["HKDF"]) + """;
|
||||
int ERROR_STRINGS_ENABLED = """ + str(features["ERROR_STRINGS"]) + """;
|
||||
"""
|
||||
|
||||
ffibuilder.set_source( "wolfcrypt._ffi", init_source_string,
|
||||
|
|
@ -534,6 +536,7 @@ def build_ffi(local_wolfssl, features):
|
|||
extern int ML_KEM_ENABLED;
|
||||
extern int ML_DSA_ENABLED;
|
||||
extern int HKDF_ENABLED;
|
||||
extern int ERROR_STRINGS_ENABLED;
|
||||
|
||||
typedef unsigned char byte;
|
||||
typedef unsigned int word32;
|
||||
|
|
@ -559,6 +562,7 @@ def build_ffi(local_wolfssl, features):
|
|||
typedef struct { ...; } mp_int;
|
||||
|
||||
int mp_init (mp_int * a);
|
||||
void mp_clear (mp_int * a);
|
||||
int mp_to_unsigned_bin (mp_int * a, unsigned char *b);
|
||||
int mp_to_unsigned_bin_len (mp_int * a, unsigned char *b, int c);
|
||||
int mp_read_unsigned_bin (mp_int * a, const unsigned char *b, int c);
|
||||
|
|
@ -570,6 +574,8 @@ def build_ffi(local_wolfssl, features):
|
|||
int wc_InitSha(wc_Sha*);
|
||||
int wc_ShaUpdate(wc_Sha*, const byte*, word32);
|
||||
int wc_ShaFinal(wc_Sha*, byte*);
|
||||
void wc_ShaFree(wc_Sha*);
|
||||
int wc_ShaCopy(wc_Sha*, wc_Sha*);
|
||||
"""
|
||||
|
||||
if features["SHA256"]:
|
||||
|
|
@ -578,6 +584,8 @@ def build_ffi(local_wolfssl, features):
|
|||
int wc_InitSha256(wc_Sha256*);
|
||||
int wc_Sha256Update(wc_Sha256*, const byte*, word32);
|
||||
int wc_Sha256Final(wc_Sha256*, byte*);
|
||||
void wc_Sha256Free(wc_Sha256*);
|
||||
int wc_Sha256Copy(wc_Sha256*, wc_Sha256*);
|
||||
"""
|
||||
|
||||
if features["SHA384"]:
|
||||
|
|
@ -586,6 +594,8 @@ def build_ffi(local_wolfssl, features):
|
|||
int wc_InitSha384(wc_Sha384*);
|
||||
int wc_Sha384Update(wc_Sha384*, const byte*, word32);
|
||||
int wc_Sha384Final(wc_Sha384*, byte*);
|
||||
void wc_Sha384Free(wc_Sha384*);
|
||||
int wc_Sha384Copy(wc_Sha384*, wc_Sha384*);
|
||||
"""
|
||||
|
||||
if features["SHA512"]:
|
||||
|
|
@ -595,6 +605,8 @@ def build_ffi(local_wolfssl, features):
|
|||
int wc_InitSha512(wc_Sha512*);
|
||||
int wc_Sha512Update(wc_Sha512*, const byte*, word32);
|
||||
int wc_Sha512Final(wc_Sha512*, byte*);
|
||||
void wc_Sha512Free(wc_Sha512*);
|
||||
int wc_Sha512Copy(wc_Sha512*, wc_Sha512*);
|
||||
"""
|
||||
if features["SHA3"]:
|
||||
cdef += """
|
||||
|
|
@ -611,6 +623,14 @@ def build_ffi(local_wolfssl, features):
|
|||
int wc_Sha3_256_Final(wc_Sha3*, byte*);
|
||||
int wc_Sha3_384_Final(wc_Sha3*, byte*);
|
||||
int wc_Sha3_512_Final(wc_Sha3*, byte*);
|
||||
void wc_Sha3_224_Free(wc_Sha3*);
|
||||
void wc_Sha3_256_Free(wc_Sha3*);
|
||||
void wc_Sha3_384_Free(wc_Sha3*);
|
||||
void wc_Sha3_512_Free(wc_Sha3*);
|
||||
int wc_Sha3_224_Copy(wc_Sha3*, wc_Sha3*);
|
||||
int wc_Sha3_256_Copy(wc_Sha3*, wc_Sha3*);
|
||||
int wc_Sha3_384_Copy(wc_Sha3*, wc_Sha3*);
|
||||
int wc_Sha3_512_Copy(wc_Sha3*, wc_Sha3*);
|
||||
"""
|
||||
|
||||
if features["DES3"]:
|
||||
|
|
@ -650,6 +670,7 @@ def build_ffi(local_wolfssl, features):
|
|||
word32 sz, const byte* authIn, word32 authInSz);
|
||||
int wc_AesGcmDecryptFinal(Aes* aes, const byte* authTag,
|
||||
word32 authTagSz);
|
||||
void wc_AesFree(Aes* aes);
|
||||
"""
|
||||
|
||||
if features["AES"] and features["AES_SIV"]:
|
||||
|
|
@ -706,6 +727,7 @@ def build_ffi(local_wolfssl, features):
|
|||
int wc_HmacSetKey(Hmac*, int, const byte*, word32);
|
||||
int wc_HmacUpdate(Hmac*, const byte*, word32);
|
||||
int wc_HmacFinal(Hmac*, byte*);
|
||||
void wc_HmacFree(Hmac*);
|
||||
"""
|
||||
|
||||
if features["RSA"]:
|
||||
|
|
@ -962,6 +984,7 @@ def build_ffi(local_wolfssl, features):
|
|||
int wc_PemToDer(const unsigned char* buff, long longSz, int type,
|
||||
DerBuffer** pDer, void* heap, EncryptedInfo* info,
|
||||
int* keyFormat);
|
||||
void wc_FreeDer(DerBuffer** pDer);
|
||||
int wc_DerToPemEx(const byte* der, word32 derSz, byte* output, word32 outSz,
|
||||
byte *cipher_info, int type);
|
||||
"""
|
||||
|
|
@ -989,6 +1012,11 @@ def build_ffi(local_wolfssl, features):
|
|||
int wolfCrypt_GetPrivateKeyReadEnable_fips(enum wc_KeyType);
|
||||
"""
|
||||
|
||||
if features["ERROR_STRINGS"]:
|
||||
cdef += """
|
||||
const char* wc_GetErrorString(int error);
|
||||
"""
|
||||
|
||||
if features["ML_KEM"] or features["ML_DSA"]:
|
||||
cdef += """
|
||||
static const int INVALID_DEVID;
|
||||
|
|
@ -1093,17 +1121,17 @@ def main(ffibuilder):
|
|||
e = "Local wolfssl installation path {} doesn't exist.".format(local_wolfssl)
|
||||
raise FileNotFoundError(e)
|
||||
|
||||
get_features(local_wolfssl, features)
|
||||
|
||||
if features["RSA_BLINDING"] and features["FIPS"]:
|
||||
# These settings can't coexist. See settings.h.
|
||||
features["RSA_BLINDING"] = 0
|
||||
|
||||
if not local_wolfssl:
|
||||
print("Building wolfSSL...")
|
||||
if not get_libwolfssl():
|
||||
generate_libwolfssl(features["FIPS"])
|
||||
|
||||
get_features(local_wolfssl, features)
|
||||
|
||||
if features["RSA_BLINDING"] and features["FIPS"]:
|
||||
# These settings can't coexist. See settings.h.
|
||||
features["RSA_BLINDING"] = 0
|
||||
|
||||
build_ffi(local_wolfssl, features)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -122,3 +122,35 @@ if _lib.AESGCM_STREAM_ENABLED:
|
|||
gcmdec.decrypt(buf)
|
||||
with pytest.raises(WolfCryptError):
|
||||
gcmdec.final(authTag)
|
||||
|
||||
def test_invalid_tag_bytes():
|
||||
key = "fedcba9876543210"
|
||||
iv = "0123456789abcdef"
|
||||
# Out of range
|
||||
with pytest.raises(ValueError, match="tag_bytes must be one of"):
|
||||
AesGcmStream(key, iv, tag_bytes=0)
|
||||
with pytest.raises(ValueError, match="tag_bytes must be one of"):
|
||||
AesGcmStream(key, iv, tag_bytes=3)
|
||||
with pytest.raises(ValueError, match="tag_bytes must be one of"):
|
||||
AesGcmStream(key, iv, tag_bytes=17)
|
||||
# Non-NIST sizes within 4-16 range
|
||||
for bad in (5, 6, 7, 9, 10, 11):
|
||||
with pytest.raises(ValueError, match="tag_bytes must be one of"):
|
||||
AesGcmStream(key, iv, tag_bytes=bad)
|
||||
# Valid NIST sizes: verify the resulting tag has the requested length.
|
||||
for good in (4, 8, 12, 13, 14, 15, 16):
|
||||
gcm = AesGcmStream(key, iv, tag_bytes=good)
|
||||
gcm.encrypt("hello world")
|
||||
tag = gcm.final()
|
||||
assert len(tag) == good
|
||||
|
||||
def test_repeated_construction_destruction():
|
||||
import gc
|
||||
key = "fedcba9876543210"
|
||||
iv = "0123456789abcdef"
|
||||
for _ in range(1000):
|
||||
gcm = AesGcmStream(key, iv)
|
||||
gcm.encrypt("hello world")
|
||||
gcm.final()
|
||||
del gcm
|
||||
gc.collect()
|
||||
|
|
|
|||
|
|
@ -872,3 +872,47 @@ def test_aessiv_decrypt_kat_openssl():
|
|||
TEST_VECTOR_CIPHERTEXT_OPENSSL
|
||||
)
|
||||
assert plaintext == TEST_VECTOR_PLAINTEXT_OPENSSL
|
||||
|
||||
|
||||
if _lib.DES3_ENABLED:
|
||||
def test_des3_rejects_mode_ctr():
|
||||
key = b"\x01\x23\x45\x67\x89\xab\xcd\xef" * 3
|
||||
iv = b"\xfe\xdc\xba\x98\x76\x54\x32\x10"
|
||||
with pytest.raises(ValueError, match="Des3 only supports MODE_CBC"):
|
||||
Des3.new(key, MODE_CTR, iv)
|
||||
|
||||
def test_des3_rejects_mode_ecb():
|
||||
key = b"\x01\x23\x45\x67\x89\xab\xcd\xef" * 3
|
||||
iv = b"\xfe\xdc\xba\x98\x76\x54\x32\x10"
|
||||
with pytest.raises(ValueError, match="Des3 only supports MODE_CBC"):
|
||||
Des3.new(key, MODE_ECB, iv)
|
||||
|
||||
|
||||
if _lib.CHACHA_ENABLED:
|
||||
def test_chacha_non_block_aligned():
|
||||
key = b"\x00" * 32
|
||||
chacha = ChaCha(key)
|
||||
chacha.set_iv(b"\x00" * 12)
|
||||
plaintext = b"This is 25 bytes of text!"
|
||||
assert len(plaintext) == 25
|
||||
ciphertext = chacha.encrypt(plaintext)
|
||||
assert len(ciphertext) == 25
|
||||
chacha2 = ChaCha(key)
|
||||
chacha2.set_iv(b"\x00" * 12)
|
||||
assert chacha2.decrypt(ciphertext) == plaintext
|
||||
|
||||
def test_chacha_invalid_key_length():
|
||||
with pytest.raises(ValueError, match="key must be"):
|
||||
ChaCha(b"\x00" * 20)
|
||||
|
||||
|
||||
if _lib.RSA_ENABLED:
|
||||
def test_encrypt_oaep_requires_hash_type(vectors):
|
||||
rsa = RsaPublic(vectors[RsaPublic].key)
|
||||
with pytest.raises(WolfCryptError, match="Hash type not set"):
|
||||
rsa.encrypt_oaep(b"plaintext")
|
||||
|
||||
def test_decrypt_oaep_requires_hash_type(vectors):
|
||||
rsa = RsaPrivate(vectors[RsaPrivate].key)
|
||||
with pytest.raises(WolfCryptError, match="Hash type not set"):
|
||||
rsa.decrypt_oaep(b"\x00" * rsa.output_size)
|
||||
|
|
|
|||
|
|
@ -184,3 +184,27 @@ def test_hash(hash_cls, vectors):
|
|||
copy.update("wolfcrypt")
|
||||
|
||||
assert hash_obj.hexdigest() == copy.hexdigest() == digest
|
||||
|
||||
|
||||
def test_hash_repeated_construction_destruction(hash_cls, vectors):
|
||||
import gc
|
||||
digest = vectors[hash_cls].digest
|
||||
for _ in range(1000):
|
||||
h = hash_new(hash_cls, "wolfcrypt")
|
||||
assert h.hexdigest() == digest
|
||||
del h
|
||||
gc.collect()
|
||||
|
||||
|
||||
def test_hash_copy_destroy_lifecycle(hash_cls, vectors):
|
||||
import gc
|
||||
digest = vectors[hash_cls].digest
|
||||
for _ in range(100):
|
||||
h = hash_new(hash_cls, "wolfcrypt")
|
||||
c = h.copy()
|
||||
# Destroy original first, then verify copy still produces correct digest.
|
||||
del h
|
||||
gc.collect()
|
||||
assert c.hexdigest() == digest
|
||||
del c
|
||||
gc.collect()
|
||||
|
|
|
|||
|
|
@ -20,6 +20,8 @@
|
|||
|
||||
# pylint: disable=no-member,no-name-in-module
|
||||
|
||||
import hmac as _hmac
|
||||
|
||||
from wolfcrypt._ffi import ffi as _ffi
|
||||
from wolfcrypt._ffi import lib as _lib
|
||||
from wolfcrypt.exceptions import WolfCryptError
|
||||
|
|
@ -42,7 +44,11 @@ if _lib.ASN_ENABLED:
|
|||
err = "Error converting from PEM to DER. ({})".format(ret)
|
||||
raise WolfCryptError(err)
|
||||
|
||||
return _ffi.buffer(der[0][0].buffer, der[0][0].length)[:]
|
||||
try:
|
||||
result = _ffi.buffer(der[0][0].buffer, der[0][0].length)[:]
|
||||
finally:
|
||||
_lib.wc_FreeDer(der)
|
||||
return result
|
||||
|
||||
def der_to_pem(der, pem_type):
|
||||
pem_length = _lib.wc_DerToPemEx(der, len(der), _ffi.NULL, 0, _ffi.NULL,
|
||||
|
|
@ -61,13 +67,13 @@ if _lib.ASN_ENABLED:
|
|||
return _ffi.buffer(pem, pem_length)[:]
|
||||
|
||||
def hash_oid_from_class(hash_cls):
|
||||
if hash_cls == Sha:
|
||||
if _lib.SHA_ENABLED and hash_cls == Sha:
|
||||
return _lib.SHAh
|
||||
elif hash_cls == Sha256:
|
||||
elif _lib.SHA256_ENABLED and hash_cls == Sha256:
|
||||
return _lib.SHA256h
|
||||
elif hash_cls == Sha384:
|
||||
elif _lib.SHA384_ENABLED and hash_cls == Sha384:
|
||||
return _lib.SHA384h
|
||||
elif hash_cls == Sha512:
|
||||
elif _lib.SHA512_ENABLED and hash_cls == Sha512:
|
||||
return _lib.SHA512h
|
||||
else:
|
||||
err = "Unknown hash class {}.".format(hash_cls.__name__)
|
||||
|
|
@ -95,4 +101,4 @@ if _lib.ASN_ENABLED:
|
|||
def check_signature(signature, data, hash_cls, pub_key):
|
||||
computed_signature = make_signature(data, hash_cls)
|
||||
decrypted_signature = pub_key.verify(signature)
|
||||
return computed_signature == decrypted_signature
|
||||
return _hmac.compare_digest(computed_signature, decrypted_signature)
|
||||
|
|
|
|||
|
|
@ -129,6 +129,10 @@ class _Cipher:
|
|||
|
||||
self.mode = mode
|
||||
|
||||
key = t2b(key)
|
||||
if IV is not None:
|
||||
IV = t2b(IV)
|
||||
|
||||
if self.key_size:
|
||||
if self.key_size != len(key):
|
||||
raise ValueError("key must be %d in length, not %d" %
|
||||
|
|
@ -147,10 +151,10 @@ class _Cipher:
|
|||
self._native_object = _ffi.new(self._native_type)
|
||||
self._enc = None
|
||||
self._dec = None
|
||||
self._key = t2b(key)
|
||||
self._key = key
|
||||
|
||||
if IV:
|
||||
self._IV = t2b(IV)
|
||||
self._IV = IV
|
||||
else: # pragma: no cover
|
||||
self._IV = _ffi.new("byte[%d]" % self.block_size)
|
||||
|
||||
|
|
@ -183,7 +187,7 @@ class _Cipher:
|
|||
raise ValueError(
|
||||
"empty string not allowed")
|
||||
|
||||
if len(string) % self.block_size and not self.mode == MODE_CTR and "ChaCha" not in self._native_type:
|
||||
if len(string) % self.block_size and "ChaCha" not in self._native_type and self.mode != MODE_CTR:
|
||||
raise ValueError(
|
||||
"string must be a multiple of %d in length" % self.block_size)
|
||||
|
||||
|
|
@ -191,6 +195,7 @@ class _Cipher:
|
|||
self._enc = _ffi.new(self._native_type)
|
||||
ret = self._set_key(_ENCRYPTION)
|
||||
if ret < 0: # pragma: no cover
|
||||
self._enc = None
|
||||
raise WolfCryptError("Invalid key error (%d)" % ret)
|
||||
|
||||
result = _ffi.new("byte[%d]" % len(string))
|
||||
|
|
@ -215,7 +220,7 @@ class _Cipher:
|
|||
if not string:
|
||||
raise ValueError("empty string not allowed")
|
||||
|
||||
if len(string) % self.block_size and self.mode != MODE_CTR and "ChaCha" not in self._native_type:
|
||||
if len(string) % self.block_size and "ChaCha" not in self._native_type and self.mode != MODE_CTR:
|
||||
raise ValueError(
|
||||
"string must be a multiple of %d in length" % self.block_size)
|
||||
|
||||
|
|
@ -223,6 +228,7 @@ class _Cipher:
|
|||
self._dec = _ffi.new(self._native_type)
|
||||
ret = self._set_key(_DECRYPTION)
|
||||
if ret < 0: # pragma: no cover
|
||||
self._dec = None
|
||||
raise WolfCryptError("Invalid key error (%d)" % ret)
|
||||
|
||||
result = _ffi.new("byte[%d]" % len(string))
|
||||
|
|
@ -390,9 +396,8 @@ if _lib.AESGCM_STREAM_ENABLED:
|
|||
block_size = 16
|
||||
_key_sizes = [16, 24, 32]
|
||||
_native_type = "Aes *"
|
||||
_aad = bytes()
|
||||
_tag_bytes = 16
|
||||
_mode = None
|
||||
# making sure _lib.wc_AesFree outlives Aes instances
|
||||
_delete = _lib.wc_AesFree
|
||||
|
||||
def __init__(self, key, IV, tag_bytes=16):
|
||||
"""
|
||||
|
|
@ -400,16 +405,32 @@ if _lib.AESGCM_STREAM_ENABLED:
|
|||
"""
|
||||
key = t2b(key)
|
||||
IV = t2b(IV)
|
||||
# NIST SP 800-38D valid GCM tag lengths: 16, 15, 14, 13, 12, 8, 4 bytes.
|
||||
if tag_bytes not in (4, 8, 12, 13, 14, 15, 16):
|
||||
raise ValueError(
|
||||
"tag_bytes must be one of 4, 8, 12, 13, 14, 15, or 16")
|
||||
# Per-instance state: AAD, tag length, and current mode (enc/dec).
|
||||
self._aad = bytes()
|
||||
self._tag_bytes = tag_bytes
|
||||
self._mode = None
|
||||
if len(key) not in self._key_sizes:
|
||||
raise ValueError("key must be %s in length, not %d" %
|
||||
(self._key_sizes, len(key)))
|
||||
self._init_done = False
|
||||
self._native_object = _ffi.new(self._native_type)
|
||||
_lib.wc_AesInit(self._native_object, _ffi.NULL, -2)
|
||||
ret = _lib.wc_AesInit(self._native_object, _ffi.NULL, -2)
|
||||
if ret < 0:
|
||||
raise WolfCryptError("AES init error (%d)" % ret)
|
||||
self._init_done = True
|
||||
ret = _lib.wc_AesGcmInit(self._native_object, key, len(key), IV, len(IV))
|
||||
if ret < 0:
|
||||
raise WolfCryptError("Init error (%d)" % ret)
|
||||
|
||||
def __del__(self):
|
||||
if getattr(self, '_init_done', False):
|
||||
self._delete(self._native_object)
|
||||
self._init_done = False
|
||||
|
||||
def set_aad(self, data):
|
||||
"""
|
||||
Set the additional authentication data for the stream
|
||||
|
|
@ -432,11 +453,11 @@ if _lib.AESGCM_STREAM_ENABLED:
|
|||
aad = self._aad
|
||||
elif self._mode == _DECRYPTION:
|
||||
raise WolfCryptError("Class instance already in use for decryption")
|
||||
self._buf = _ffi.new("byte[%d]" % (len(data)))
|
||||
ret = _lib.wc_AesGcmEncryptUpdate(self._native_object, self._buf, data, len(data), aad, len(aad))
|
||||
buf = _ffi.new("byte[%d]" % (len(data)))
|
||||
ret = _lib.wc_AesGcmEncryptUpdate(self._native_object, buf, data, len(data), aad, len(aad))
|
||||
if ret < 0:
|
||||
raise WolfCryptError("Decryption error (%d)" % ret)
|
||||
return bytes(self._buf)
|
||||
raise WolfCryptError("Encryption error (%d)" % ret)
|
||||
return bytes(buf)
|
||||
|
||||
def decrypt(self, data):
|
||||
"""
|
||||
|
|
@ -448,12 +469,12 @@ if _lib.AESGCM_STREAM_ENABLED:
|
|||
self._mode = _DECRYPTION
|
||||
aad = self._aad
|
||||
elif self._mode == _ENCRYPTION:
|
||||
raise WolfCryptError("Class instance already in use for decryption")
|
||||
self._buf = _ffi.new("byte[%d]" % (len(data)))
|
||||
ret = _lib.wc_AesGcmDecryptUpdate(self._native_object, self._buf, data, len(data), aad, len(aad))
|
||||
raise WolfCryptError("Class instance already in use for encryption")
|
||||
buf = _ffi.new("byte[%d]" % (len(data)))
|
||||
ret = _lib.wc_AesGcmDecryptUpdate(self._native_object, buf, data, len(data), aad, len(aad))
|
||||
if ret < 0:
|
||||
raise WolfCryptError("Decryption error (%d)" % ret)
|
||||
return bytes(self._buf)
|
||||
return bytes(buf)
|
||||
|
||||
def final(self, authTag=None):
|
||||
"""
|
||||
|
|
@ -488,20 +509,23 @@ if _lib.CHACHA_ENABLED:
|
|||
key_size = None # 16, 24, 32
|
||||
_key_sizes = [16, 32]
|
||||
_native_type = "ChaCha *"
|
||||
_IV_nonce = []
|
||||
_IV_nonce = b""
|
||||
_IV_counter = 0
|
||||
|
||||
def __init__(self, key="", size=32):
|
||||
def __init__(self, key="", size=32): # pylint: disable=unused-argument
|
||||
# size is kept for backwards compatibility; key length is now
|
||||
# derived from the actual key and validated against _key_sizes.
|
||||
self._native_object = _ffi.new(self._native_type)
|
||||
self._enc = None
|
||||
self._dec = None
|
||||
self._key = None
|
||||
if len(key) > 0:
|
||||
if size not in self._key_sizes:
|
||||
raise ValueError("Invalid key size %d" % size)
|
||||
self._key = t2b(key)
|
||||
self.key_size = size
|
||||
self._IV_nonce = []
|
||||
if len(self._key) not in self._key_sizes:
|
||||
raise ValueError("key must be %s in length, not %d" %
|
||||
(self._key_sizes, len(self._key)))
|
||||
self.key_size = len(self._key)
|
||||
self._IV_nonce = b""
|
||||
self._IV_counter = 0
|
||||
|
||||
def _set_key(self, direction):
|
||||
|
|
@ -510,13 +534,13 @@ if _lib.CHACHA_ENABLED:
|
|||
if self._enc:
|
||||
ret = _lib.wc_Chacha_SetKey(self._enc, self._key, len(self._key))
|
||||
if ret == 0:
|
||||
_lib.wc_Chacha_SetIV(self._enc, self._IV_nonce, self._IV_counter)
|
||||
ret = _lib.wc_Chacha_SetIV(self._enc, self._IV_nonce, self._IV_counter)
|
||||
if ret != 0:
|
||||
return ret
|
||||
if self._dec:
|
||||
ret = _lib.wc_Chacha_SetKey(self._dec, self._key, len(self._key))
|
||||
if ret == 0:
|
||||
_lib.wc_Chacha_SetIV(self._dec, self._IV_nonce, self._IV_counter)
|
||||
ret = _lib.wc_Chacha_SetIV(self._dec, self._IV_nonce, self._IV_counter)
|
||||
if ret != 0:
|
||||
return ret
|
||||
return 0
|
||||
|
|
@ -537,7 +561,9 @@ if _lib.CHACHA_ENABLED:
|
|||
raise ValueError("nonce must be %d bytes, got %d" %
|
||||
(self._NONCE_SIZE, len(self._IV_nonce)))
|
||||
self._IV_counter = counter
|
||||
self._set_key(0)
|
||||
ret = self._set_key(0)
|
||||
if ret < 0:
|
||||
raise WolfCryptError("ChaCha set_iv error (%d)" % ret)
|
||||
|
||||
if _lib.CHACHA20_POLY1305_ENABLED:
|
||||
class ChaCha20Poly1305:
|
||||
|
|
@ -627,6 +653,14 @@ if _lib.DES3_ENABLED:
|
|||
key_size = 24
|
||||
_native_type = "Des3 *"
|
||||
|
||||
def __init__(self, key, mode, IV=None):
|
||||
# Intentionally stricter than _Cipher.__init__, which accepts both
|
||||
# CBC and CTR. wolfCrypt has no 3DES-CTR implementation, so reject
|
||||
# MODE_CTR here with a clearer error before delegating.
|
||||
if mode != MODE_CBC:
|
||||
raise ValueError("Des3 only supports MODE_CBC")
|
||||
super().__init__(key, mode, IV)
|
||||
|
||||
def _set_key(self, direction):
|
||||
if direction == _ENCRYPTION:
|
||||
return _lib.wc_Des3_SetKey(self._enc, self._key,
|
||||
|
|
@ -741,6 +775,8 @@ if _lib.RSA_ENABLED:
|
|||
return _ffi.buffer(ciphertext)[:]
|
||||
|
||||
def encrypt_oaep(self, plaintext, label=""):
|
||||
if not self._hash_type:
|
||||
raise WolfCryptError("Hash type not set. Cannot use OAEP padding without a hash type.")
|
||||
plaintext = t2b(plaintext)
|
||||
label = t2b(label)
|
||||
ciphertext = _ffi.new("byte[%d]" % self.output_size)
|
||||
|
|
@ -824,10 +860,12 @@ if _lib.RSA_ENABLED:
|
|||
class RsaPrivate(RsaPublic):
|
||||
if _lib.KEYGEN_ENABLED:
|
||||
@classmethod
|
||||
def make_key(cls, size, rng=Random(), hash_type=None):
|
||||
def make_key(cls, size, rng=None, hash_type=None):
|
||||
"""
|
||||
Generates a new key pair of desired length **size**.
|
||||
"""
|
||||
if rng is None:
|
||||
rng = Random()
|
||||
rsa = cls(hash_type=hash_type)
|
||||
|
||||
ret = _lib.wc_MakeRsaKey(rsa.native_object, size, 65537,
|
||||
|
|
@ -840,6 +878,9 @@ if _lib.RSA_ENABLED:
|
|||
if rsa.output_size <= 0: # pragma: no cover
|
||||
raise WolfCryptError("Invalid key size error (%d)" % ret)
|
||||
|
||||
# Retain RNG reference defensively.
|
||||
rsa._rng = rng
|
||||
|
||||
return rsa
|
||||
|
||||
def __init__(self, key=None, hash_type=None): # pylint: disable=super-init-not-called
|
||||
|
|
@ -930,6 +971,8 @@ if _lib.RSA_ENABLED:
|
|||
|
||||
Returns a string containing the plaintext.
|
||||
"""
|
||||
if not self._hash_type:
|
||||
raise WolfCryptError("Hash type not set. Cannot use OAEP padding without a hash type.")
|
||||
ciphertext = t2b(ciphertext)
|
||||
label = t2b(label)
|
||||
plaintext = _ffi.new("byte[%d]" % self.output_size)
|
||||
|
|
@ -1160,33 +1203,39 @@ if _lib.ECC_ENABLED:
|
|||
raise WolfCryptError("wolfCrypt error (%d)" % ret)
|
||||
ret = _lib.mp_init(mpS)
|
||||
if ret != 0: # pragma: no cover
|
||||
_lib.mp_clear(mpR)
|
||||
raise WolfCryptError("wolfCrypt error (%d)" % ret)
|
||||
|
||||
ret = _lib.mp_read_unsigned_bin(mpR, R, len(R))
|
||||
if ret != 0: # pragma: no cover
|
||||
raise WolfCryptError("wolfCrypt error (%d)" % ret)
|
||||
try:
|
||||
ret = _lib.mp_read_unsigned_bin(mpR, R, len(R))
|
||||
if ret != 0: # pragma: no cover
|
||||
raise WolfCryptError("wolfCrypt error (%d)" % ret)
|
||||
|
||||
ret = _lib.mp_read_unsigned_bin(mpS, S, len(S))
|
||||
if ret != 0: # pragma: no cover
|
||||
raise WolfCryptError("wolfCrypt error (%d)" % ret)
|
||||
ret = _lib.mp_read_unsigned_bin(mpS, S, len(S))
|
||||
if ret != 0: # pragma: no cover
|
||||
raise WolfCryptError("wolfCrypt error (%d)" % ret)
|
||||
|
||||
ret = _lib.wc_ecc_verify_hash_ex(mpR, mpS,
|
||||
data, len(data),
|
||||
status, self.native_object)
|
||||
|
||||
ret = _lib.wc_ecc_verify_hash_ex(mpR, mpS,
|
||||
data, len(data),
|
||||
status, self.native_object)
|
||||
if ret < 0:
|
||||
raise WolfCryptError("Verify error (%d)" % ret)
|
||||
|
||||
if ret < 0:
|
||||
raise WolfCryptError("Verify error (%d)" % ret)
|
||||
|
||||
return status[0] == 1
|
||||
return status[0] == 1
|
||||
finally:
|
||||
_lib.mp_clear(mpR)
|
||||
_lib.mp_clear(mpS)
|
||||
|
||||
|
||||
class EccPrivate(EccPublic):
|
||||
@classmethod
|
||||
def make_key(cls, size, rng=Random()):
|
||||
def make_key(cls, size, rng=None):
|
||||
"""
|
||||
Generates a new key pair of desired length **size**.
|
||||
"""
|
||||
if rng is None:
|
||||
rng = Random()
|
||||
ecc = cls()
|
||||
|
||||
ret = _lib.wc_ecc_make_key(rng.native_object, size,
|
||||
|
|
@ -1200,6 +1249,11 @@ if _lib.ECC_ENABLED:
|
|||
if ret < 0:
|
||||
raise WolfCryptError("Error setting ECC RNG (%d)" % ret)
|
||||
|
||||
# Retain the RNG so it outlives the ECC key. Even outside the
|
||||
# timing-resistance path, wolfSSL internals may retain a pointer
|
||||
# to the RNG; keeping the reference avoids any UAF risk.
|
||||
ecc._rng = rng
|
||||
|
||||
return ecc
|
||||
|
||||
def decode_key(self, key):
|
||||
|
|
@ -1289,12 +1343,14 @@ if _lib.ECC_ENABLED:
|
|||
|
||||
return _ffi.buffer(shared_secret, secret_size[0])[:]
|
||||
|
||||
def sign(self, plaintext, rng=Random()):
|
||||
def sign(self, plaintext, rng=None):
|
||||
"""
|
||||
Signs **plaintext**, using the private key data in the object.
|
||||
|
||||
Returns the signature.
|
||||
"""
|
||||
if rng is None:
|
||||
rng = Random()
|
||||
plaintext = t2b(plaintext)
|
||||
signature = _ffi.new("byte[%d]" % self.max_signature_size)
|
||||
|
||||
|
|
@ -1312,12 +1368,14 @@ if _lib.ECC_ENABLED:
|
|||
return _ffi.buffer(signature, signature_size[0])[:]
|
||||
|
||||
if _lib.MPAPI_ENABLED:
|
||||
def sign_raw(self, plaintext, rng=Random()):
|
||||
def sign_raw(self, plaintext, rng=None):
|
||||
"""
|
||||
Signs **plaintext**, using the private key data in the object.
|
||||
|
||||
Returns the signature in its two raw components r, s
|
||||
"""
|
||||
if rng is None:
|
||||
rng = Random()
|
||||
plaintext = t2b(plaintext)
|
||||
R = _ffi.new("mp_int[1]")
|
||||
S = _ffi.new("mp_int[1]")
|
||||
|
|
@ -1330,25 +1388,30 @@ if _lib.ECC_ENABLED:
|
|||
raise WolfCryptError("wolfCrypt error (%d)" % ret)
|
||||
ret = _lib.mp_init(S)
|
||||
if ret != 0: # pragma: no cover
|
||||
_lib.mp_clear(R)
|
||||
raise WolfCryptError("wolfCrypt error (%d)" % ret)
|
||||
|
||||
ret = _lib.wc_ecc_sign_hash_ex(plaintext, len(plaintext),
|
||||
rng.native_object,
|
||||
self.native_object,
|
||||
R, S)
|
||||
if ret != 0: # pragma: no cover
|
||||
raise WolfCryptError("Signature error (%d)" % ret)
|
||||
try:
|
||||
ret = _lib.wc_ecc_sign_hash_ex(plaintext, len(plaintext),
|
||||
rng.native_object,
|
||||
self.native_object,
|
||||
R, S)
|
||||
if ret != 0: # pragma: no cover
|
||||
raise WolfCryptError("Signature error (%d)" % ret)
|
||||
|
||||
ret = _lib.mp_to_unsigned_bin_len(R, R_bin, self.size)
|
||||
if ret != 0: # pragma: no cover
|
||||
raise WolfCryptError("wolfCrypt error (%d)" % ret)
|
||||
ret = _lib.mp_to_unsigned_bin_len(R, R_bin, self.size)
|
||||
if ret != 0: # pragma: no cover
|
||||
raise WolfCryptError("wolfCrypt error (%d)" % ret)
|
||||
|
||||
ret = _lib.mp_to_unsigned_bin_len(S, S_bin, self.size)
|
||||
if ret != 0: # pragma: no cover
|
||||
raise WolfCryptError("wolfCrypt error (%d)" % ret)
|
||||
ret = _lib.mp_to_unsigned_bin_len(S, S_bin, self.size)
|
||||
if ret != 0: # pragma: no cover
|
||||
raise WolfCryptError("wolfCrypt error (%d)" % ret)
|
||||
|
||||
return _ffi.buffer(R_bin, self.size)[:], _ffi.buffer(S_bin,
|
||||
self.size)[:]
|
||||
return _ffi.buffer(R_bin, self.size)[:], _ffi.buffer(S_bin,
|
||||
self.size)[:]
|
||||
finally:
|
||||
_lib.mp_clear(R)
|
||||
_lib.mp_clear(S)
|
||||
|
||||
|
||||
if _lib.ED25519_ENABLED:
|
||||
|
|
@ -1449,10 +1512,12 @@ if _lib.ED25519_ENABLED:
|
|||
self.decode_key(key,pub)
|
||||
|
||||
@classmethod
|
||||
def make_key(cls, size, rng=Random()):
|
||||
def make_key(cls, size, rng=None):
|
||||
"""
|
||||
Generates a new key pair of desired length **size**.
|
||||
"""
|
||||
if rng is None:
|
||||
rng = Random()
|
||||
ed25519 = cls()
|
||||
|
||||
ret = _lib.wc_ed25519_make_key(rng.native_object, size,
|
||||
|
|
@ -1460,6 +1525,10 @@ if _lib.ED25519_ENABLED:
|
|||
if ret < 0:
|
||||
raise WolfCryptError("Key generation error (%d)" % ret)
|
||||
|
||||
# Retain RNG reference defensively; wolfSSL may retain a pointer
|
||||
# internally on some builds.
|
||||
ed25519._rng = rng
|
||||
|
||||
return ed25519
|
||||
|
||||
def decode_key(self, key, pub = None):
|
||||
|
|
@ -1490,6 +1559,8 @@ if _lib.ED25519_ENABLED:
|
|||
raise WolfCryptError("Public key generate error (%d)" % ret)
|
||||
ret = _lib.wc_ed25519_import_public(pubkey, self.size,
|
||||
self.native_object)
|
||||
if ret < 0:
|
||||
raise WolfCryptError("Public key import error (%d)" % ret)
|
||||
|
||||
if self.size <= 0: # pragma: no cover
|
||||
raise WolfCryptError("Key decode error (%d)" % self.size)
|
||||
|
|
@ -1505,20 +1576,22 @@ if _lib.ED25519_ENABLED:
|
|||
"""
|
||||
key = _ffi.new("byte[%d]" % (self.size * 4))
|
||||
pubkey = _ffi.new("byte[%d]" % (self.size * 4))
|
||||
size = _ffi.new("word32[1]")
|
||||
priv_size = _ffi.new("word32[1]")
|
||||
pub_size = _ffi.new("word32[1]")
|
||||
|
||||
size[0] = _lib.wc_ed25519_priv_size(self.native_object)
|
||||
priv_size[0] = _lib.wc_ed25519_priv_size(self.native_object)
|
||||
pub_size[0] = _lib.wc_ed25519_pub_size(self.native_object)
|
||||
|
||||
ret = _lib.wc_ed25519_export_private_only(self.native_object,
|
||||
key, size)
|
||||
key, priv_size)
|
||||
if ret != 0: # pragma: no cover
|
||||
raise WolfCryptError("Private key encode error (%d)" % ret)
|
||||
ret = _lib.wc_ed25519_export_public(self.native_object, pubkey,
|
||||
size)
|
||||
pub_size)
|
||||
if ret != 0: # pragma: no cover
|
||||
raise WolfCryptError("Public key encode error (%d)" % ret)
|
||||
|
||||
return _ffi.buffer(key, size[0])[:], _ffi.buffer(pubkey, size[0])[:]
|
||||
return _ffi.buffer(key, priv_size[0])[:], _ffi.buffer(pubkey, pub_size[0])[:]
|
||||
|
||||
def sign(self, plaintext):
|
||||
"""
|
||||
|
|
@ -1645,10 +1718,12 @@ if _lib.ED448_ENABLED:
|
|||
self.decode_key(key,pub)
|
||||
|
||||
@classmethod
|
||||
def make_key(cls, size, rng=Random()):
|
||||
def make_key(cls, size, rng=None):
|
||||
"""
|
||||
Generates a new key pair of desired length **size**.
|
||||
"""
|
||||
if rng is None:
|
||||
rng = Random()
|
||||
ed448 = cls()
|
||||
|
||||
ret = _lib.wc_ed448_make_key(rng.native_object, size,
|
||||
|
|
@ -1656,6 +1731,10 @@ if _lib.ED448_ENABLED:
|
|||
if ret < 0:
|
||||
raise WolfCryptError("Key generation error (%d)" % ret)
|
||||
|
||||
# Retain RNG reference defensively; wolfSSL may retain a pointer
|
||||
# internally on some builds.
|
||||
ed448._rng = rng
|
||||
|
||||
return ed448
|
||||
|
||||
def decode_key(self, key, pub = None):
|
||||
|
|
@ -1686,6 +1765,8 @@ if _lib.ED448_ENABLED:
|
|||
raise WolfCryptError("Public key generate error (%d)" % ret)
|
||||
ret = _lib.wc_ed448_import_public(pubkey, self.size,
|
||||
self.native_object)
|
||||
if ret < 0:
|
||||
raise WolfCryptError("Public key import error (%d)" % ret)
|
||||
|
||||
if self.size <= 0: # pragma: no cover
|
||||
raise WolfCryptError("Key decode error (%d)" % self.size)
|
||||
|
|
@ -1701,20 +1782,22 @@ if _lib.ED448_ENABLED:
|
|||
"""
|
||||
key = _ffi.new("byte[%d]" % (self.size * 4))
|
||||
pubkey = _ffi.new("byte[%d]" % (self.size * 4))
|
||||
size = _ffi.new("word32[1]")
|
||||
priv_size = _ffi.new("word32[1]")
|
||||
pub_size = _ffi.new("word32[1]")
|
||||
|
||||
size[0] = _lib.wc_ed448_priv_size(self.native_object)
|
||||
priv_size[0] = _lib.wc_ed448_priv_size(self.native_object)
|
||||
pub_size[0] = _lib.wc_ed448_pub_size(self.native_object)
|
||||
|
||||
ret = _lib.wc_ed448_export_private_only(self.native_object,
|
||||
key, size)
|
||||
key, priv_size)
|
||||
if ret != 0: # pragma: no cover
|
||||
raise WolfCryptError("Private key encode error (%d)" % ret)
|
||||
ret = _lib.wc_ed448_export_public(self.native_object, pubkey,
|
||||
size)
|
||||
pub_size)
|
||||
if ret != 0: # pragma: no cover
|
||||
raise WolfCryptError("Public key encode error (%d)" % ret)
|
||||
|
||||
return _ffi.buffer(key, size[0])[:], _ffi.buffer(pubkey, size[0])[:]
|
||||
return _ffi.buffer(key, priv_size[0])[:], _ffi.buffer(pubkey, pub_size[0])[:]
|
||||
|
||||
def sign(self, plaintext, ctx=None):
|
||||
"""
|
||||
|
|
@ -1862,13 +1945,15 @@ if _lib.ML_KEM_ENABLED:
|
|||
if ret < 0: # pragma: no cover
|
||||
raise WolfCryptError("wc_KyberKey_DecodePublicKey() error (%d)" % ret)
|
||||
|
||||
def encapsulate(self, rng=Random()):
|
||||
def encapsulate(self, rng=None):
|
||||
"""
|
||||
:param rng: random number generator for an encupsulation
|
||||
:type rng: Random
|
||||
:return: tuple of a shared secret (first element) and the cipher text (second element)
|
||||
:rtype: tuple[bytes, bytes]
|
||||
"""
|
||||
if rng is None:
|
||||
rng = Random()
|
||||
ct_size = self.ct_size
|
||||
ss_size = self.ss_size
|
||||
ct = _ffi.new(f"unsigned char[{ct_size}]")
|
||||
|
|
@ -1906,7 +1991,7 @@ if _lib.ML_KEM_ENABLED:
|
|||
|
||||
class MlKemPrivate(_MlKemBase):
|
||||
@classmethod
|
||||
def make_key(cls, mlkem_type, rng=Random()):
|
||||
def make_key(cls, mlkem_type, rng=None):
|
||||
"""
|
||||
:param mlkem_type: ML-KEM type
|
||||
:type mlkem_type: MlKemType
|
||||
|
|
@ -1915,12 +2000,17 @@ if _lib.ML_KEM_ENABLED:
|
|||
:return: `MlKemPrivate` object
|
||||
:rtype: MlKemPrivate
|
||||
"""
|
||||
if rng is None:
|
||||
rng = Random()
|
||||
mlkem_priv = cls(mlkem_type)
|
||||
ret = _lib.wc_KyberKey_MakeKey(mlkem_priv.native_object, rng.native_object)
|
||||
|
||||
if ret < 0: # pragma: no cover
|
||||
raise WolfCryptError("wc_KyberKey_MakeKey() error (%d)" % ret)
|
||||
|
||||
# Retain RNG reference defensively.
|
||||
mlkem_priv._rng = rng
|
||||
|
||||
return mlkem_priv
|
||||
|
||||
@classmethod
|
||||
|
|
@ -2021,7 +2111,6 @@ if _lib.ML_KEM_ENABLED:
|
|||
)
|
||||
|
||||
if ret < 0: # pragma: no cover
|
||||
self.native_object = None
|
||||
raise WolfCryptError("wc_KyberKey_Decapsulate() error (%d)" % ret)
|
||||
|
||||
return _ffi.buffer(ss, ss_size)[:]
|
||||
|
|
@ -2170,7 +2259,7 @@ if _lib.ML_DSA_ENABLED:
|
|||
class MlDsaPrivate(_MlDsaBase):
|
||||
|
||||
@classmethod
|
||||
def make_key(cls, mldsa_type, rng=Random()):
|
||||
def make_key(cls, mldsa_type, rng=None):
|
||||
"""
|
||||
:param mldsa_type: ML-DSA type
|
||||
:type mldsa_type: MlDsaType
|
||||
|
|
@ -2179,6 +2268,8 @@ if _lib.ML_DSA_ENABLED:
|
|||
:return: `MlDsaPrivate` object
|
||||
:rtype: MlDsaPrivate
|
||||
"""
|
||||
if rng is None:
|
||||
rng = Random()
|
||||
mldsa_priv = cls(mldsa_type)
|
||||
ret = _lib.wc_dilithium_make_key(
|
||||
mldsa_priv.native_object, rng.native_object
|
||||
|
|
@ -2187,6 +2278,9 @@ if _lib.ML_DSA_ENABLED:
|
|||
if ret < 0: # pragma: no cover
|
||||
raise WolfCryptError("wc_dilithium_make_key() error (%d)" % ret)
|
||||
|
||||
# Retain RNG reference defensively.
|
||||
mldsa_priv._rng = rng
|
||||
|
||||
return mldsa_priv
|
||||
|
||||
@classmethod
|
||||
|
|
@ -2239,9 +2333,7 @@ if _lib.ML_DSA_ENABLED:
|
|||
if ret < 0: # pragma: no cover
|
||||
raise WolfCryptError("wc_MlDsaKey_GetPrivLen() error (%d)" % ret)
|
||||
|
||||
key_pair_size = size[0]
|
||||
|
||||
return key_pair_size - self.pub_key_size
|
||||
return size[0] - self.pub_key_size
|
||||
|
||||
def encode_pub_key(self):
|
||||
"""
|
||||
|
|
@ -2293,7 +2385,7 @@ if _lib.ML_DSA_ENABLED:
|
|||
if pub_key is not None:
|
||||
self._decode_pub_key(pub_key)
|
||||
|
||||
def sign(self, message, rng=Random(), ctx=None):
|
||||
def sign(self, message, rng=None, ctx=None):
|
||||
"""
|
||||
:param message: message to be signed
|
||||
:type message: bytes or str
|
||||
|
|
@ -2304,6 +2396,8 @@ if _lib.ML_DSA_ENABLED:
|
|||
:return: signature
|
||||
:rtype: bytes
|
||||
"""
|
||||
if rng is None:
|
||||
rng = Random()
|
||||
msg_bytestype = t2b(message)
|
||||
in_size = self.sig_size
|
||||
signature = _ffi.new(f"byte[{in_size}]")
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ class _Hash:
|
|||
"""
|
||||
def __init__(self, string=None):
|
||||
self._native_object = _ffi.new(self._native_type)
|
||||
self._shallow_copy = False
|
||||
ret = self._init()
|
||||
if ret < 0: # pragma: no cover
|
||||
raise WolfCryptError("Hash init error (%d)" % ret)
|
||||
|
|
@ -56,11 +57,32 @@ class _Hash:
|
|||
Returns a separate copy of this hashing object. An update
|
||||
to this copy won't affect the original object.
|
||||
"""
|
||||
copy = self.new("")
|
||||
# Bypass __init__ to avoid calling _init() on a state that _copy
|
||||
# immediately overwrites (which would leak internal resources in
|
||||
# async/HW-accelerated builds). Mark as shallow up front so __del__
|
||||
# skips the free if we bail out before the copy completes.
|
||||
copy = type(self).__new__(type(self))
|
||||
copy._shallow_copy = True # pylint: disable=protected-access
|
||||
copy._native_object = _ffi.new(self._native_type) # pylint: disable=protected-access
|
||||
|
||||
_ffi.memmove(copy._native_object, # pylint: disable=protected-access
|
||||
self._native_object,
|
||||
self._native_size)
|
||||
copy_fn = getattr(self, '_copy', None)
|
||||
if copy_fn:
|
||||
ret = copy_fn(self._native_object,
|
||||
copy._native_object) # pylint: disable=protected-access
|
||||
if ret < 0: # pragma: no cover
|
||||
# Free any partial allocation before raising; __del__ would
|
||||
# skip it because _shallow_copy is still True.
|
||||
delete = getattr(self, '_delete', None)
|
||||
if delete:
|
||||
delete(copy._native_object) # pylint: disable=protected-access
|
||||
raise WolfCryptError("Hash copy error (%d)" % ret)
|
||||
copy._shallow_copy = False # pylint: disable=protected-access
|
||||
else:
|
||||
_ffi.memmove(copy._native_object, # pylint: disable=protected-access
|
||||
self._native_object,
|
||||
self._native_size)
|
||||
# Keep _shallow_copy = True: memmove shares internal state with
|
||||
# self, so __del__ must not free it separately.
|
||||
|
||||
return copy
|
||||
|
||||
|
|
@ -87,12 +109,31 @@ class _Hash:
|
|||
|
||||
if self._native_object:
|
||||
obj = _ffi.new(self._native_type)
|
||||
# _copy and _delete are class attributes on Sha/Sha256/etc, but
|
||||
# are set as instance attributes on Sha3 (because the SHA3 variant
|
||||
# is selected by digest_size at __init__ time). getattr handles
|
||||
# both cases.
|
||||
copy_fn = getattr(self, '_copy', None)
|
||||
|
||||
_ffi.memmove(obj, self._native_object, self._native_size)
|
||||
try:
|
||||
if copy_fn:
|
||||
ret = copy_fn(self._native_object, obj)
|
||||
if ret < 0: # pragma: no cover
|
||||
raise WolfCryptError("Hash copy error (%d)" % ret)
|
||||
else:
|
||||
_ffi.memmove(obj, self._native_object, self._native_size)
|
||||
|
||||
ret = self._final(obj, result)
|
||||
if ret < 0: # pragma: no cover
|
||||
raise WolfCryptError("Hash finalize error (%d)" % ret)
|
||||
ret = self._final(obj, result)
|
||||
if ret < 0: # pragma: no cover
|
||||
raise WolfCryptError("Hash finalize error (%d)" % ret)
|
||||
finally:
|
||||
# Only free when we did a deep copy; memmove'd temps share
|
||||
# internal resources with self and must not be separately freed.
|
||||
# Runs even on failed copy to clean up any partial allocation.
|
||||
if copy_fn:
|
||||
delete = getattr(self, '_delete', None)
|
||||
if delete:
|
||||
delete(obj)
|
||||
|
||||
return _ffi.buffer(result, self.digest_size)[:]
|
||||
|
||||
|
|
@ -116,6 +157,12 @@ if _lib.SHA_ENABLED:
|
|||
digest_size = 20
|
||||
_native_type = "wc_Sha *"
|
||||
_native_size = _ffi.sizeof("wc_Sha")
|
||||
_delete = _lib.wc_ShaFree
|
||||
_copy = _lib.wc_ShaCopy
|
||||
|
||||
def __del__(self):
|
||||
if hasattr(self, '_native_object') and not getattr(self, '_shallow_copy', False):
|
||||
self._delete(self._native_object)
|
||||
|
||||
def _init(self):
|
||||
return _lib.wc_InitSha(self._native_object)
|
||||
|
|
@ -138,6 +185,12 @@ if _lib.SHA256_ENABLED:
|
|||
digest_size = 32
|
||||
_native_type = "wc_Sha256 *"
|
||||
_native_size = _ffi.sizeof("wc_Sha256")
|
||||
_delete = _lib.wc_Sha256Free
|
||||
_copy = _lib.wc_Sha256Copy
|
||||
|
||||
def __del__(self):
|
||||
if hasattr(self, '_native_object') and not getattr(self, '_shallow_copy', False):
|
||||
self._delete(self._native_object)
|
||||
|
||||
def _init(self):
|
||||
return _lib.wc_InitSha256(self._native_object)
|
||||
|
|
@ -160,6 +213,12 @@ if _lib.SHA384_ENABLED:
|
|||
digest_size = 48
|
||||
_native_type = "wc_Sha384 *"
|
||||
_native_size = _ffi.sizeof("wc_Sha384")
|
||||
_delete = _lib.wc_Sha384Free
|
||||
_copy = _lib.wc_Sha384Copy
|
||||
|
||||
def __del__(self):
|
||||
if hasattr(self, '_native_object') and not getattr(self, '_shallow_copy', False):
|
||||
self._delete(self._native_object)
|
||||
|
||||
def _init(self):
|
||||
return _lib.wc_InitSha384(self._native_object)
|
||||
|
|
@ -182,6 +241,12 @@ if _lib.SHA512_ENABLED:
|
|||
digest_size = 64
|
||||
_native_type = "wc_Sha512 *"
|
||||
_native_size = _ffi.sizeof("wc_Sha512")
|
||||
_delete = _lib.wc_Sha512Free
|
||||
_copy = _lib.wc_Sha512Copy
|
||||
|
||||
def __del__(self):
|
||||
if hasattr(self, '_native_object') and not getattr(self, '_shallow_copy', False):
|
||||
self._delete(self._native_object)
|
||||
|
||||
def _init(self):
|
||||
return _lib.wc_InitSha512(self._native_object)
|
||||
|
|
@ -209,9 +274,35 @@ if _lib.SHA3_ENABLED:
|
|||
SHA3_384_DIGEST_SIZE = 48
|
||||
SHA3_512_DIGEST_SIZE = 64
|
||||
|
||||
_SHA3_FREE = {
|
||||
28: _lib.wc_Sha3_224_Free,
|
||||
32: _lib.wc_Sha3_256_Free,
|
||||
48: _lib.wc_Sha3_384_Free,
|
||||
64: _lib.wc_Sha3_512_Free,
|
||||
}
|
||||
|
||||
_SHA3_COPY = {
|
||||
28: _lib.wc_Sha3_224_Copy,
|
||||
32: _lib.wc_Sha3_256_Copy,
|
||||
48: _lib.wc_Sha3_384_Copy,
|
||||
64: _lib.wc_Sha3_512_Copy,
|
||||
}
|
||||
|
||||
def __del__(self):
|
||||
# Unlike the SHA-1/2 classes, Sha3's _delete is set per-instance
|
||||
# from a size->function dict and is None for invalid sizes, so
|
||||
# we need the extra truthiness check.
|
||||
if (hasattr(self, '_native_object')
|
||||
and not getattr(self, '_shallow_copy', False)
|
||||
and getattr(self, '_delete', None)):
|
||||
self._delete(self._native_object)
|
||||
|
||||
def __init__(self, string=None, size=SHA3_384_DIGEST_SIZE): # pylint: disable=W0231
|
||||
self._native_object = _ffi.new(self._native_type)
|
||||
self._shallow_copy = False
|
||||
self.digest_size = size
|
||||
self._delete = self._SHA3_FREE.get(size)
|
||||
self._copy = self._SHA3_COPY.get(size)
|
||||
ret = self._init()
|
||||
if ret < 0: # pragma: no cover
|
||||
raise WolfCryptError("Sha3 init error (%d)" % ret)
|
||||
|
|
@ -223,8 +314,27 @@ if _lib.SHA3_ENABLED:
|
|||
return cls(string, size)
|
||||
|
||||
def copy(self):
|
||||
c = Sha3(size=self.digest_size)
|
||||
_ffi.memmove(c._native_object, self._native_object, self._native_size)
|
||||
# Bypass __init__ to avoid calling _init() on a state that _copy
|
||||
# immediately overwrites (which would leak internal resources in
|
||||
# async/HW-accelerated builds). Mark as shallow up front so
|
||||
# __del__ skips the free if we bail out before the copy completes.
|
||||
c = type(self).__new__(type(self))
|
||||
c._shallow_copy = True
|
||||
c._native_object = _ffi.new(self._native_type)
|
||||
c.digest_size = self.digest_size
|
||||
c._delete = self._delete
|
||||
c._copy = self._copy
|
||||
if self._copy:
|
||||
ret = self._copy(self._native_object, c._native_object)
|
||||
if ret < 0: # pragma: no cover
|
||||
# Free any partial allocation before raising.
|
||||
if self._delete:
|
||||
self._delete(c._native_object)
|
||||
raise WolfCryptError("Hash copy error (%d)" % ret)
|
||||
c._shallow_copy = False
|
||||
else:
|
||||
_ffi.memmove(c._native_object, self._native_object, self._native_size)
|
||||
# Keep _shallow_copy = True: memmove shares state with self.
|
||||
return c
|
||||
|
||||
def _init(self):
|
||||
|
|
@ -281,15 +391,29 @@ if _lib.HMAC_ENABLED:
|
|||
"""
|
||||
A **PEP 247: Cryptographic Hash Functions** compliant
|
||||
**Keyed Hash Function Interface**.
|
||||
|
||||
Note: wolfSSL does not provide a `wc_HmacCopy` equivalent, so
|
||||
`copy()` falls back to a byte-level memmove. In default builds the
|
||||
Hmac struct is self-contained and this is safe. In async or
|
||||
hardware-accelerated builds where the struct contains internal
|
||||
pointers, the copy shares those pointers with the original; the
|
||||
copy must not outlive the original or be used after the original
|
||||
is freed.
|
||||
"""
|
||||
digest_size = None
|
||||
_native_type = "Hmac *"
|
||||
_native_size = _ffi.sizeof("Hmac")
|
||||
_delete = _lib.wc_HmacFree
|
||||
|
||||
def __del__(self):
|
||||
if hasattr(self, '_native_object') and not getattr(self, '_shallow_copy', False):
|
||||
self._delete(self._native_object)
|
||||
|
||||
def __init__(self, key, string=None): # pylint: disable=W0231
|
||||
key = t2b(key)
|
||||
|
||||
self._native_object = _ffi.new(self._native_type)
|
||||
self._shallow_copy = False
|
||||
ret = self._init(self._type, key)
|
||||
if ret < 0: # pragma: no cover
|
||||
raise WolfCryptError("Hmac init error (%d)" % ret)
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ if _lib.HKDF_ENABLED:
|
|||
Perform HKDF Extract-and-Expand in one call (wraps wc_HKDF).
|
||||
|
||||
Parameters:
|
||||
- hash_cls: hash class, see `wolfcrypt.hashes`.
|
||||
- hash_cls: HMAC class, e.g. HmacSha256, see `wolfcrypt.hashes`.
|
||||
- in_key: input key material (IKM) as bytes or str.
|
||||
- salt: optional salt value (bytes or str). If None, treated as empty.
|
||||
- info: optional context/application info (bytes or str). If None,
|
||||
|
|
@ -79,7 +79,7 @@ if _lib.HKDF_ENABLED:
|
|||
Wraps wc_HKDF_Extract.
|
||||
|
||||
Parameters:
|
||||
- hash_cls: hash class, see `wolfcrypt.hashes`.
|
||||
- hash_cls: HMAC class, e.g. HmacSha256, see `wolfcrypt.hashes`.
|
||||
- salt: bytes/str (can be None -> treated as empty).
|
||||
- in_key: input key material (IKM) as bytes/str.
|
||||
|
||||
|
|
@ -106,7 +106,7 @@ if _lib.HKDF_ENABLED:
|
|||
Wraps wc_HKDF_Expand.
|
||||
|
||||
Parameters:
|
||||
- hash_cls: hash class, see `wolfcrypt.hashes`.
|
||||
- hash_cls: HMAC class, e.g. HmacSha256, see `wolfcrypt.hashes`.
|
||||
- prk: pseudorandom key (output from HKDF-Extract) as bytes/str.
|
||||
- info: optional context/application info (bytes/str). If None, treated as empty.
|
||||
- out_len: length of output keying material in bytes.
|
||||
|
|
|
|||
Loading…
Reference in New Issue