Skip to content

Commit 744a49e

Browse files
authored
Merge pull request #36 from LinuxJedi/rsa-padding
2 parents cdba0c7 + 013c1e6 commit 744a49e

5 files changed

Lines changed: 208 additions & 7 deletions

File tree

tests/test_aesgcmstream.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
# test_hashes.py
1+
# test_aesgcmstream.py
22
#
3-
# Copyright (C) 2006-2022 wolfSSL Inc.
3+
# Copyright (C) 2022 wolfSSL Inc.
44
#
55
# This file is part of wolfSSL. (formerly known as CyaSSL)
66
#

tests/test_ciphers.py

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@
3939
from wolfcrypt.ciphers import ChaCha
4040

4141
if _lib.RSA_ENABLED:
42-
from wolfcrypt.ciphers import (RsaPrivate, RsaPublic)
42+
from wolfcrypt.ciphers import (RsaPrivate, RsaPublic, HASH_TYPE_SHA256, MGF1SHA256, HASH_TYPE_SHA, MGF1SHA1)
4343

4444
if _lib.ECC_ENABLED:
4545
from wolfcrypt.ciphers import (EccPrivate, EccPublic)
@@ -382,6 +382,23 @@ def test_rsa_encrypt_decrypt(rsa_private, rsa_public):
382382
assert 1024 / 8 == len(ciphertext) == rsa_private.output_size
383383
assert plaintext == rsa_private.decrypt(ciphertext)
384384

385+
def test_rsa_encrypt_decrypt_pad_oaep(rsa_private, rsa_public):
386+
plaintext = t2b("Everyone gets Friday off.")
387+
388+
# normal usage, encrypt with public, decrypt with private
389+
ciphertext = rsa_public.encrypt_oaep(plaintext, HASH_TYPE_SHA, MGF1SHA1, "")
390+
391+
assert 1024 / 8 == len(ciphertext) == rsa_public.output_size
392+
assert plaintext == rsa_private.decrypt_oaep(ciphertext, HASH_TYPE_SHA, MGF1SHA1, "")
393+
394+
# private object holds both private and public info, so it can also encrypt
395+
# using the known public key.
396+
ciphertext = rsa_private.encrypt_oaep(plaintext, HASH_TYPE_SHA, MGF1SHA1, "")
397+
398+
assert 1024 / 8 == len(ciphertext) == rsa_private.output_size
399+
assert plaintext == rsa_private.decrypt_oaep(ciphertext, HASH_TYPE_SHA, MGF1SHA1, "")
400+
401+
385402
def test_rsa_pkcs8_encrypt_decrypt(rsa_private_pkcs8, rsa_public):
386403
plaintext = t2b("Everyone gets Friday off.")
387404

@@ -415,6 +432,23 @@ def test_rsa_sign_verify(rsa_private, rsa_public):
415432
assert 1024 / 8 == len(signature) == rsa_private.output_size
416433
assert plaintext == rsa_private.verify(signature)
417434

435+
if _lib.RSA_PSS_ENABLED:
436+
def test_rsa_pss_sign_verify(rsa_private, rsa_public):
437+
plaintext = t2b("Everyone gets Friday off yippee.")
438+
439+
# normal usage, sign with private, verify with public
440+
signature = rsa_private.sign_pss(plaintext, HASH_TYPE_SHA256, MGF1SHA256)
441+
442+
assert 1024 / 8 == len(signature) == rsa_private.output_size
443+
assert 0 == rsa_public.verify_pss(plaintext, signature, HASH_TYPE_SHA256, MGF1SHA256)
444+
445+
# private object holds both private and public info, so it can also verify
446+
# using the known public key.
447+
signature = rsa_private.sign_pss(plaintext, HASH_TYPE_SHA256, MGF1SHA256)
448+
449+
assert 1024 / 8 == len(signature) == rsa_private.output_size
450+
assert 0 == rsa_private.verify_pss(plaintext, signature, HASH_TYPE_SHA256, MGF1SHA256)
451+
418452
def test_rsa_sign_verify_pem(rsa_private_pem, rsa_public_pem):
419453
plaintext = t2b("Everyone gets Friday off.")
420454

wolfcrypt/_build_ffi.py

Lines changed: 49 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@ def generate_libwolfssl():
103103
ASN_ENABLED = 1
104104
WC_RNG_SEED_CB_ENABLED = 0
105105
AESGCM_STREAM = 1
106+
RSA_PSS_ENABLED = 1
106107

107108
# detect native features based on options.h defines
108109
if featureDetection:
@@ -128,6 +129,7 @@ def generate_libwolfssl():
128129
ASN_ENABLED = 0 if '#define NO_ASN' in optionsHeaderStr else 1
129130
WC_RNG_SEED_CB_ENABLED = 1 if '#define WC_RNG_SEED_CB' in optionsHeaderStr else 0
130131
AESGCM_STREAM = 1 if '#define WOLFSSL_AESGCM_STREAM' in optionsHeaderStr else 0
132+
RSA_PSS_ENABLED = 1 if '#define WC_RSA_PSS' in optionsHeaderStr else 0
131133

132134
if '#define HAVE_FIPS' in optionsHeaderStr:
133135
FIPS_ENABLED = 1
@@ -205,6 +207,7 @@ def generate_libwolfssl():
205207
int ASN_ENABLED = """ + str(ASN_ENABLED) + """;
206208
int WC_RNG_SEED_CB_ENABLED = """ + str(WC_RNG_SEED_CB_ENABLED) + """;
207209
int AESGCM_STREAM = """ + str(AESGCM_STREAM) + """;
210+
int RSA_PSS_ENABLED = """ + str(RSA_PSS_ENABLED) + """;
208211
""",
209212
include_dirs=[wolfssl_inc_path()],
210213
library_dirs=[wolfssl_lib_path()],
@@ -235,6 +238,7 @@ def generate_libwolfssl():
235238
extern int ASN_ENABLED;
236239
extern int WC_RNG_SEED_CB_ENABLED;
237240
extern int AESGCM_STREAM;
241+
extern int RSA_PSS_ENABLED;
238242
239243
typedef unsigned char byte;
240244
typedef unsigned int word32;
@@ -368,6 +372,34 @@ def generate_libwolfssl():
368372

369373
if RSA_ENABLED:
370374
_cdef += """
375+
static const int WC_RSA_PKCSV15_PAD;
376+
static const int WC_RSA_OAEP_PAD;
377+
static const int WC_RSA_PSS_PAD;
378+
static const int WC_RSA_NO_PAD;
379+
380+
static const int WC_MGF1NONE;
381+
static const int WC_MGF1SHA1;
382+
static const int WC_MGF1SHA224;
383+
static const int WC_MGF1SHA256;
384+
static const int WC_MGF1SHA384;
385+
static const int WC_MGF1SHA512;
386+
387+
static const int WC_HASH_TYPE_NONE;
388+
static const int WC_HASH_TYPE_MD2;
389+
static const int WC_HASH_TYPE_MD4;
390+
static const int WC_HASH_TYPE_MD5;
391+
static const int WC_HASH_TYPE_SHA;
392+
static const int WC_HASH_TYPE_SHA224;
393+
static const int WC_HASH_TYPE_SHA256;
394+
static const int WC_HASH_TYPE_SHA384;
395+
static const int WC_HASH_TYPE_SHA512;
396+
static const int WC_HASH_TYPE_MD5_SHA;
397+
static const int WC_HASH_TYPE_SHA3_224;
398+
static const int WC_HASH_TYPE_SHA3_256;
399+
static const int WC_HASH_TYPE_SHA3_384;
400+
static const int WC_HASH_TYPE_SHA3_512;
401+
static const int WC_HASH_TYPE_BLAKE2B;
402+
static const int WC_HASH_TYPE_BLAKE2S;
371403
typedef struct {...; } RsaKey;
372404
373405
int wc_InitRsaKey(RsaKey* key, void*);
@@ -381,11 +413,25 @@ def generate_libwolfssl():
381413
RsaKey* key);
382414
int wc_RsaPublicEncrypt(const byte*, word32, byte*, word32,
383415
RsaKey*, WC_RNG*);
384-
385-
int wc_RsaSSL_Sign(const byte*, word32, byte*, word32, RsaKey*, WC_RNG*);
386-
int wc_RsaSSL_Verify(const byte*, word32, byte*, word32, RsaKey*);
416+
int wc_RsaPublicEncrypt_ex(const byte* in, word32 inLen, byte* out,
417+
word32 outLen, RsaKey* key, WC_RNG* rng, int type,
418+
enum wc_HashType hash, int mgf, byte* label, word32 labelSz);
419+
int wc_RsaPrivateDecrypt_ex(const byte* in, word32 inLen,
420+
byte* out, word32 outLen, RsaKey* key, int type,
421+
enum wc_HashType hash, int mgf, byte* label, word32 labelSz);
387422
"""
388423

424+
if RSA_PSS_ENABLED:
425+
_cdef += """
426+
int wc_RsaPSS_Sign(const byte* in, word32 inLen, byte* out, word32 outLen,
427+
enum wc_HashType hash, int mgf, RsaKey* key, WC_RNG* rng);
428+
int wc_RsaPSS_Verify(byte* in, word32 inLen, byte* out, word32 outLen,
429+
enum wc_HashType hash, int mgf, RsaKey* key);
430+
int wc_RsaPSS_CheckPadding(const byte* in, word32 inSz, byte* sig,
431+
word32 sigSz, enum wc_HashType hashType);
432+
int wc_RsaSSL_Sign(const byte*, word32, byte*, word32, RsaKey*, WC_RNG*);
433+
int wc_RsaSSL_Verify(const byte*, word32, byte*, word32, RsaKey*);
434+
"""
389435

390436
if RSA_BLINDING_ENABLED:
391437
_cdef += """

wolfcrypt/_build_wolfssl.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,7 @@ def make_flags(prefix):
153153
flags.append("-DWOLFSSL_SHA224=no")
154154
flags.append("-DWOLFSSL_POLY1305=no")
155155
flags.append("-DWOLFSSL_RSA=yes")
156+
flags.append("-DWOLFSSL_RSA_PSS=yes")
156157
flags.append("-DWOLFSSL_ECC=yes")
157158
flags.append("-DWOLFSSL_ED25519=yes")
158159
flags.append("-DWOLFSSL_ED448=yes")
@@ -165,7 +166,7 @@ def make_flags(prefix):
165166
flags.append("-DWOLFSSL_EXTENDED_MASTER=no")
166167
flags.append("-DWOLFSSL_ERROR_STRINGS=no")
167168
# Part of hack for missing CMake option
168-
flags.append("-DCMAKE_C_FLAGS=\"/DWOLFSSL_KEY_GEN=1 /DWOLFCRYPT_ONLY=1 /DWOLFSSL_AESGCM_STREAM=1\"")
169+
flags.append("-DCMAKE_C_FLAGS=\"/DWOLFSSL_KEY_GEN=1 /DWOLFCRYPT_ONLY=1 /DWOLFSSL_AESGCM_STREAM=1 /DWOLFSSL_AES_COUNTER=1\"")
169170

170171
return " ".join(flags)
171172
else:
@@ -205,6 +206,7 @@ def make_flags(prefix):
205206

206207
# asymmetric ciphers
207208
flags.append("--enable-rsa")
209+
flags.append("--enable-rsapss")
208210
flags.append("--enable-ecc")
209211
flags.append("--enable-ed25519")
210212
flags.append("--enable-ed448")
@@ -238,6 +240,8 @@ def cmake_hack():
238240
contents.insert(29, "#define WOLFCRYPT_ONLY\n")
239241
contents.insert(30, "#undef WOLFSSL_AESGCM_STREAM\n")
240242
contents.insert(31, "#define WOLFSSL_AESGCM_STREAM\n")
243+
contents.insert(32, "#undef WOLFSSL_AES_COUNTER\n")
244+
contents.insert(33, "#define WOLFSSL_AES_COUNTER\n")
241245

242246
with open(options_file, "w") as f:
243247
contents = "".join(contents)

wolfcrypt/ciphers.py

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,32 @@
8282
ECC_BRAINPOOLP384R1 = 26
8383
ECC_BRAINPOOLP512R1 = 27
8484

85+
if _lib.RSA_ENABLED:
86+
MGF1NONE = _lib.WC_MGF1NONE
87+
MGF1SHA1 = _lib.WC_MGF1SHA1
88+
MGF1SHA224 = _lib.WC_MGF1SHA224
89+
MGF1SHA256 = _lib.WC_MGF1SHA256
90+
MGF1SHA384 = _lib.WC_MGF1SHA384
91+
MGF1SHA512 = _lib.WC_MGF1SHA512
92+
93+
HASH_TYPE_NONE = _lib.WC_HASH_TYPE_NONE
94+
HASH_TYPE_MD2 = _lib.WC_HASH_TYPE_MD2
95+
HASH_TYPE_MD4 = _lib.WC_HASH_TYPE_MD4
96+
HASH_TYPE_MD5 = _lib.WC_HASH_TYPE_MD5
97+
HASH_TYPE_SHA = _lib.WC_HASH_TYPE_SHA
98+
HASH_TYPE_SHA224 = _lib.WC_HASH_TYPE_SHA224
99+
HASH_TYPE_SHA256 = _lib.WC_HASH_TYPE_SHA256
100+
HASH_TYPE_SHA384 = _lib.WC_HASH_TYPE_SHA384
101+
HASH_TYPE_SHA512 = _lib.WC_HASH_TYPE_SHA512
102+
HASH_TYPE_MD5_SHA = _lib.WC_HASH_TYPE_MD5_SHA
103+
HASH_TYPE_SHA3_224 = _lib.WC_HASH_TYPE_SHA3_224
104+
HASH_TYPE_SHA3_256 = _lib.WC_HASH_TYPE_SHA3_256
105+
HASH_TYPE_SHA3_384 = _lib.WC_HASH_TYPE_SHA3_384
106+
HASH_TYPE_SHA3_512 = _lib.WC_HASH_TYPE_SHA3_512
107+
HASH_TYPE_BLAKE2B = _lib.WC_HASH_TYPE_BLAKE2B
108+
HASH_TYPE_BLAKE2S = _lib.WC_HASH_TYPE_BLAKE2S
109+
110+
85111

86112
class _Cipher(object):
87113
"""
@@ -473,6 +499,23 @@ def encrypt(self, plaintext):
473499

474500
return _ffi.buffer(ciphertext)[:]
475501

502+
def encrypt_oaep(self, plaintext, hash_type, mgf, label):
503+
plaintext = t2b(plaintext)
504+
label = t2b(label)
505+
ciphertext = _ffi.new("byte[%d]" % self.output_size)
506+
507+
ret = _lib.wc_RsaPublicEncrypt_ex(plaintext, len(plaintext),
508+
ciphertext, self.output_size,
509+
self.native_object,
510+
self._random.native_object,
511+
_lib.WC_RSA_OAEP_PAD, hash_type,
512+
mgf, label, len(label))
513+
514+
if ret != self.output_size: # pragma: no cover
515+
raise WolfCryptError("Encryption error (%d)" % ret)
516+
517+
return _ffi.buffer(ciphertext)[:]
518+
476519
def verify(self, signature):
477520
"""
478521
Verifies **signature**, using the public key data in the
@@ -494,6 +537,33 @@ def verify(self, signature):
494537

495538
return _ffi.buffer(plaintext, ret)[:]
496539

540+
if _lib.RSA_PSS_ENABLED:
541+
def verify_pss(self, plaintext, signature, hash_type, mgf):
542+
"""
543+
Verifies **signature**, using the public key data in the
544+
object. The signature's length must be equal to:
545+
546+
**self.output_size**
547+
548+
Returns a string containing the plaintext.
549+
"""
550+
plaintext = t2b(plaintext)
551+
signature = t2b(signature)
552+
verify = _ffi.new("byte[%d]" % self.output_size)
553+
554+
ret = _lib.wc_RsaPSS_Verify(signature, len(signature),
555+
verify, self.output_size,
556+
hash_type, mgf,
557+
self.native_object)
558+
559+
if ret < 0: # pragma: no cover
560+
raise WolfCryptError("Verify error (%d)" % ret)
561+
ret = _lib.wc_RsaPSS_CheckPadding(plaintext, len(plaintext),
562+
verify, ret, hash_type)
563+
564+
return ret
565+
566+
497567

498568
class RsaPrivate(RsaPublic):
499569
if _lib.KEYGEN_ENABLED:
@@ -597,6 +667,29 @@ def decrypt(self, ciphertext):
597667

598668
return _ffi.buffer(plaintext, ret)[:]
599669

670+
def decrypt_oaep(self, ciphertext, hash_type, mgf, label):
671+
"""
672+
Decrypts **ciphertext**, using the private key data in the
673+
object. The ciphertext's length must be equal to:
674+
675+
**self.output_size**
676+
677+
Returns a string containing the plaintext.
678+
"""
679+
ciphertext = t2b(ciphertext)
680+
label = t2b(label)
681+
plaintext = _ffi.new("byte[%d]" % self.output_size)
682+
ret = _lib.wc_RsaPrivateDecrypt_ex(ciphertext, len(ciphertext),
683+
plaintext, self.output_size,
684+
self.native_object,
685+
_lib.WC_RSA_OAEP_PAD, hash_type,
686+
mgf, label, len(label))
687+
688+
if ret < 0: # pragma: no cover
689+
raise WolfCryptError("Decryption error (%d)" % ret)
690+
691+
return _ffi.buffer(plaintext, ret)[:]
692+
600693
def sign(self, plaintext):
601694
"""
602695
Signs **plaintext**, using the private key data in the object.
@@ -619,6 +712,30 @@ def sign(self, plaintext):
619712

620713
return _ffi.buffer(signature, self.output_size)[:]
621714

715+
if _lib.RSA_PSS_ENABLED:
716+
def sign_pss(self, plaintext, hash_type, mgf):
717+
"""
718+
Signs **plaintext**, using the private key data in the object.
719+
The plaintext's length must not be greater than:
720+
721+
**self.output_size - self.RSA_MIN_PAD_SIZE**
722+
723+
Returns a string containing the signature.
724+
"""
725+
plaintext = t2b(plaintext)
726+
signature = _ffi.new("byte[%d]" % self.output_size)
727+
728+
ret = _lib.wc_RsaPSS_Sign(plaintext, len(plaintext),
729+
signature, self.output_size,
730+
hash_type, mgf,
731+
self.native_object,
732+
self._random.native_object)
733+
734+
if ret != self.output_size: # pragma: no cover
735+
raise WolfCryptError("Signature error (%d)" % ret)
736+
737+
return _ffi.buffer(signature, self.output_size)[:]
738+
622739

623740
if _lib.ECC_ENABLED:
624741
class _Ecc(object): # pylint: disable=too-few-public-methods

0 commit comments

Comments
 (0)