diff --git a/tests/test_aesgcmstream.py b/tests/test_aesgcmstream.py index 492bf54..30a14ab 100644 --- a/tests/test_aesgcmstream.py +++ b/tests/test_aesgcmstream.py @@ -1,6 +1,6 @@ -# test_hashes.py +# test_aesgcmstream.py # -# Copyright (C) 2006-2022 wolfSSL Inc. +# Copyright (C) 2022 wolfSSL Inc. # # This file is part of wolfSSL. (formerly known as CyaSSL) # diff --git a/tests/test_ciphers.py b/tests/test_ciphers.py index 3951b73..7eea349 100644 --- a/tests/test_ciphers.py +++ b/tests/test_ciphers.py @@ -39,7 +39,7 @@ if _lib.CHACHA_ENABLED: from wolfcrypt.ciphers import ChaCha if _lib.RSA_ENABLED: - from wolfcrypt.ciphers import (RsaPrivate, RsaPublic) + from wolfcrypt.ciphers import (RsaPrivate, RsaPublic, HASH_TYPE_SHA256, MGF1SHA256, HASH_TYPE_SHA, MGF1SHA1) if _lib.ECC_ENABLED: from wolfcrypt.ciphers import (EccPrivate, EccPublic) @@ -382,6 +382,23 @@ if _lib.RSA_ENABLED: assert 1024 / 8 == len(ciphertext) == rsa_private.output_size assert plaintext == rsa_private.decrypt(ciphertext) + def test_rsa_encrypt_decrypt_pad_oaep(rsa_private, rsa_public): + plaintext = t2b("Everyone gets Friday off.") + + # normal usage, encrypt with public, decrypt with private + ciphertext = rsa_public.encrypt_oaep(plaintext, HASH_TYPE_SHA, MGF1SHA1, "") + + assert 1024 / 8 == len(ciphertext) == rsa_public.output_size + assert plaintext == rsa_private.decrypt_oaep(ciphertext, HASH_TYPE_SHA, MGF1SHA1, "") + + # private object holds both private and public info, so it can also encrypt + # using the known public key. + ciphertext = rsa_private.encrypt_oaep(plaintext, HASH_TYPE_SHA, MGF1SHA1, "") + + assert 1024 / 8 == len(ciphertext) == rsa_private.output_size + assert plaintext == rsa_private.decrypt_oaep(ciphertext, HASH_TYPE_SHA, MGF1SHA1, "") + + def test_rsa_pkcs8_encrypt_decrypt(rsa_private_pkcs8, rsa_public): plaintext = t2b("Everyone gets Friday off.") @@ -415,6 +432,22 @@ if _lib.RSA_ENABLED: assert 1024 / 8 == len(signature) == rsa_private.output_size assert plaintext == rsa_private.verify(signature) + def test_rsa_pss_sign_verify(rsa_private, rsa_public): + plaintext = t2b("Everyone gets Friday off yippee.") + + # normal usage, sign with private, verify with public + signature = rsa_private.sign_pss(plaintext, HASH_TYPE_SHA256, MGF1SHA256) + + assert 1024 / 8 == len(signature) == rsa_private.output_size + assert 0 == rsa_public.verify_pss(plaintext, signature, HASH_TYPE_SHA256, MGF1SHA256) + + # private object holds both private and public info, so it can also verify + # using the known public key. + signature = rsa_private.sign_pss(plaintext, HASH_TYPE_SHA256, MGF1SHA256) + + assert 1024 / 8 == len(signature) == rsa_private.output_size + assert 0 == rsa_private.verify_pss(plaintext, signature, HASH_TYPE_SHA256, MGF1SHA256) + def test_rsa_sign_verify_pem(rsa_private_pem, rsa_public_pem): plaintext = t2b("Everyone gets Friday off.") diff --git a/wolfcrypt/_build_ffi.py b/wolfcrypt/_build_ffi.py index 89900f8..7b7c690 100644 --- a/wolfcrypt/_build_ffi.py +++ b/wolfcrypt/_build_ffi.py @@ -381,12 +381,22 @@ if RSA_ENABLED: RsaKey* key); int wc_RsaPublicEncrypt(const byte*, word32, byte*, word32, RsaKey*, WC_RNG*); - + int wc_RsaPublicEncrypt_ex(const byte* in, word32 inLen, byte* out, + word32 outLen, RsaKey* key, WC_RNG* rng, int type, + enum wc_HashType hash, int mgf, byte* label, word32 labelSz); + int wc_RsaPrivateDecrypt_ex(const byte* in, word32 inLen, + byte* out, word32 outLen, RsaKey* key, int type, + enum wc_HashType hash, int mgf, byte* label, word32 labelSz); + int wc_RsaPSS_Sign(const byte* in, word32 inLen, byte* out, word32 outLen, + enum wc_HashType hash, int mgf, RsaKey* key, WC_RNG* rng); + int wc_RsaPSS_Verify(byte* in, word32 inLen, byte* out, word32 outLen, + enum wc_HashType hash, int mgf, RsaKey* key); + int wc_RsaPSS_CheckPadding(const byte* in, word32 inSz, byte* sig, + word32 sigSz, enum wc_HashType hashType); int wc_RsaSSL_Sign(const byte*, word32, byte*, word32, RsaKey*, WC_RNG*); int wc_RsaSSL_Verify(const byte*, word32, byte*, word32, RsaKey*); """ - if RSA_BLINDING_ENABLED: _cdef += """ int wc_RsaSetRNG(RsaKey* key, WC_RNG* rng); diff --git a/wolfcrypt/_build_wolfssl.py b/wolfcrypt/_build_wolfssl.py index ea4387a..c8a8689 100644 --- a/wolfcrypt/_build_wolfssl.py +++ b/wolfcrypt/_build_wolfssl.py @@ -153,6 +153,7 @@ def make_flags(prefix): flags.append("-DWOLFSSL_SHA224=no") flags.append("-DWOLFSSL_POLY1305=no") flags.append("-DWOLFSSL_RSA=yes") + flags.append("-DWOLFSSL_RSA_PSS=yes") flags.append("-DWOLFSSL_ECC=yes") flags.append("-DWOLFSSL_ED25519=yes") flags.append("-DWOLFSSL_ED448=yes") @@ -205,6 +206,7 @@ def make_flags(prefix): # asymmetric ciphers flags.append("--enable-rsa") + flags.append("--enable-rsapss") flags.append("--enable-ecc") flags.append("--enable-ed25519") flags.append("--enable-ed448") diff --git a/wolfcrypt/ciphers.py b/wolfcrypt/ciphers.py index 89f0815..b1ee5ce 100644 --- a/wolfcrypt/ciphers.py +++ b/wolfcrypt/ciphers.py @@ -82,6 +82,39 @@ ECC_BRAINPOOLP320R1 = 25 ECC_BRAINPOOLP384R1 = 26 ECC_BRAINPOOLP512R1 = 27 +RSA_PKCSV15_PAD = 0 +RSA_OAEP_PAD = 1 +RSA_PSS_PAD = 2 +RSA_NO_PSA = 3 + +MGF1NONE = 0 +MGF1SHA1 = 26 +MGF1SHA224 = 4 +MGF1SHA256 = 1 +MGF1SHA384 = 2 +MGF1SHA512 = 3 + +BLOCK_TYPE_1 = 1 +BLOCK_TYPE_2 = 2 + +HASH_TYPE_NONE = 0 +HASH_TYPE_MD2 = 1 +HASH_TYPE_MD4 = 2 +HASH_TYPE_MD5 = 3 +HASH_TYPE_SHA = 4 +HASH_TYPE_SHA224 = 5 +HASH_TYPE_SHA256 = 6 +HASH_TYPE_SHA384 = 7 +HASH_TYPE_SHA512 = 8 +HASH_TYPE_MD5_SHA = 9 +HASH_TYPE_SHA3_224 = 10 +HASH_TYPE_SHA3_256 = 11 +HASH_TYPE_SHA3_384 = 12 +HASH_TYPE_SHA3_512 = 13 +HASH_TYPE_BLAKE2B = 14 +HASH_TYPE_BLAKE2S = 15 + + class _Cipher(object): """ @@ -473,6 +506,23 @@ if _lib.RSA_ENABLED: return _ffi.buffer(ciphertext)[:] + def encrypt_oaep(self, plaintext, hash_type, mgf, label): + plaintext = t2b(plaintext) + label = t2b(label) + ciphertext = _ffi.new("byte[%d]" % self.output_size) + + ret = _lib.wc_RsaPublicEncrypt_ex(plaintext, len(plaintext), + ciphertext, self.output_size, + self.native_object, + self._random.native_object, + RSA_OAEP_PAD, hash_type, mgf, + label, len(label)) + + if ret != self.output_size: # pragma: no cover + raise WolfCryptError("Encryption error (%d)" % ret) + + return _ffi.buffer(ciphertext)[:] + def verify(self, signature): """ Verifies **signature**, using the public key data in the @@ -494,6 +544,32 @@ if _lib.RSA_ENABLED: return _ffi.buffer(plaintext, ret)[:] + def verify_pss(self, plaintext, signature, hash_type, mgf): + """ + Verifies **signature**, using the public key data in the + object. The signature's length must be equal to: + + **self.output_size** + + Returns a string containing the plaintext. + """ + plaintext = t2b(plaintext) + signature = t2b(signature) + verify = _ffi.new("byte[%d]" % self.output_size) + + ret = _lib.wc_RsaPSS_Verify(signature, len(signature), + verify, self.output_size, + hash_type, mgf, + self.native_object) + + if ret < 0: # pragma: no cover + raise WolfCryptError("Verify error (%d)" % ret) + ret = _lib.wc_RsaPSS_CheckPadding(plaintext, len(plaintext), + verify, ret, hash_type) + + return ret + + class RsaPrivate(RsaPublic): if _lib.KEYGEN_ENABLED: @@ -597,6 +673,28 @@ if _lib.RSA_ENABLED: return _ffi.buffer(plaintext, ret)[:] + def decrypt_oaep(self, ciphertext, hash_type, mgf, label): + """ + Decrypts **ciphertext**, using the private key data in the + object. The ciphertext's length must be equal to: + + **self.output_size** + + Returns a string containing the plaintext. + """ + ciphertext = t2b(ciphertext) + label = t2b(label) + plaintext = _ffi.new("byte[%d]" % self.output_size) + ret = _lib.wc_RsaPrivateDecrypt_ex(ciphertext, len(ciphertext), + plaintext, self.output_size, + self.native_object, RSA_OAEP_PAD, + hash_type, mgf, label, len(label)) + + if ret < 0: # pragma: no cover + raise WolfCryptError("Decryption error (%d)" % ret) + + return _ffi.buffer(plaintext, ret)[:] + def sign(self, plaintext): """ Signs **plaintext**, using the private key data in the object. @@ -619,6 +717,29 @@ if _lib.RSA_ENABLED: return _ffi.buffer(signature, self.output_size)[:] + def sign_pss(self, plaintext, hash_type, mgf): + """ + Signs **plaintext**, using the private key data in the object. + The plaintext's length must not be greater than: + + **self.output_size - self.RSA_MIN_PAD_SIZE** + + Returns a string containing the signature. + """ + plaintext = t2b(plaintext) + signature = _ffi.new("byte[%d]" % self.output_size) + + ret = _lib.wc_RsaPSS_Sign(plaintext, len(plaintext), + signature, self.output_size, + hash_type, mgf, + self.native_object, + self._random.native_object) + + if ret != self.output_size: # pragma: no cover + raise WolfCryptError("Signature error (%d)" % ret) + + return _ffi.buffer(signature, self.output_size)[:] + if _lib.ECC_ENABLED: class _Ecc(object): # pylint: disable=too-few-public-methods