diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..8ac6b8c --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,6 @@ +version: 2 +updates: + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "monthly" diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml deleted file mode 100644 index 9e990bd..0000000 --- a/.github/workflows/linux.yml +++ /dev/null @@ -1,63 +0,0 @@ -name: linux - -on: - push: - branches: - - '*' - tags-ignore: - - '*' - pull_request: - -jobs: - - ubuntu: - env: - PERL_USE_UNSAFE_INC: 0 - AUTHOR_TESTING: 1 - AUTOMATED_TESTING: 1 - RELEASE_TESTING: 1 - - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v2 - - name: perl -V - run: perl -V - - name: uses install-with-cpm - uses: perl-actions/install-with-cpm@stable - with: - cpanfile: "cpanfile" - - name: Makefile.PL - run: perl -I$(pwd) Makefile.PL - - name: make test - run: make test - - perl: - env: - PERL_USE_UNSAFE_INC: 0 - AUTHOR_TESTING: 1 - AUTOMATED_TESTING: 1 - RELEASE_TESTING: 1 - - runs-on: ubuntu-latest - needs: [ubuntu] - - strategy: - fail-fast: false - matrix: - perl-version: [latest, 5.34, 5.32, "5.30", 5.28, 5.26, 5.24, 5.22, "5.20", 5.18, 5.16, 5.14, 5.12, "5.10", 5.8 ] - - container: - image: perl:${{ matrix.perl-version }} - - steps: - - uses: actions/checkout@v2 - - run: perl -V - - name: Deps for testing - run: | - cpanm --notest Crypt::OpenSSL::Random Crypt::OpenSSL::Guess Test::CPAN::Meta Perl::MinimumVersion Test::Pod::Coverage Test::Pod Test::MinimumVersion Crypt::OpenSSL::Bignum || /bin/true - cpanm --notest Crypt::OpenSSL::Random Crypt::OpenSSL::Guess Test::CPAN::Meta Perl::MinimumVersion Test::Pod::Coverage Test::Pod Test::MinimumVersion Crypt::OpenSSL::Bignum - # not available < 5.12 - cpanm --notest Test::Kwalitee ||: - - run: perl Makefile.PL - - run: make test diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml deleted file mode 100644 index 061a12d..0000000 --- a/.github/workflows/macos.yml +++ /dev/null @@ -1,35 +0,0 @@ -name: macos - -on: - push: - branches: - - '*' - tags-ignore: - - '*' - pull_request: - -jobs: - perl: - env: - PERL_USE_UNSAFE_INC: 0 - AUTHOR_TESTING: 1 - AUTOMATED_TESTING: 1 - RELEASE_TESTING: 1 - - runs-on: macOS-latest - - strategy: - fail-fast: false - matrix: - perl-version: [latest] - - steps: - - uses: actions/checkout@v1 - - name: perl -V - run: perl -V - - name: Deps for testing - run: cpan -T Crypt::OpenSSL::Random Crypt::OpenSSL::Guess - - name: Makefile.PL - run: perl Makefile.PL - - name: make test - run: make test diff --git a/.github/workflows/testsuite.yml b/.github/workflows/testsuite.yml new file mode 100644 index 0000000..8433835 --- /dev/null +++ b/.github/workflows/testsuite.yml @@ -0,0 +1,175 @@ +name: linux + +on: + push: + branches: + - '*' + tags-ignore: + - '*' + pull_request: + types: [ opened, synchronize, reopened, edited, ready_for_review ] + +jobs: + +# +# A quick and cheap test first before running other jobs +# + + ubuntu: + env: + PERL_USE_UNSAFE_INC: 0 + AUTHOR_TESTING: 1 + AUTOMATED_TESTING: 1 + RELEASE_TESTING: 1 + + runs-on: ubuntu-latest + + strategy: + fail-fast: false + + steps: + - uses: actions/checkout@v6 + - run: openssl version + - run: perl -V + - name: uses install-with-cpm + uses: perl-actions/install-with-cpm@v1 + with: + cpanfile: "cpanfile" + - name: Makefile.PL + run: perl -I$(pwd) Makefile.PL + - run: make && ( make test || prove -wbvm t/*.t ) + + openssl-matrix: + env: + PERL_USE_UNSAFE_INC: 0 + AUTHOR_TESTING: 1 + AUTOMATED_TESTING: 1 + RELEASE_TESTING: 1 + + runs-on: ubuntu-latest + needs: [ubuntu] + name: "OpenSSL ${{ matrix.os-version }}" + + strategy: + fail-fast: false + matrix: + os-version: + # - debian:buster # OpenSSL 1.1.1 + - debian:bullseye # OpenSSL 1.1.1 + - debian:bookworm # OpenSSL 3.0.x + - debian:trixie # OpenSSL 3.4.x (Debian 13) + - almalinux:9 # OpenSSL with new crypto policies (RHEL-compatible) + + container: ${{ matrix.os-version }} + steps: + - uses: actions/checkout@v6 + - name: Install dependencies using apt-get + if: ${{ startsWith(matrix.os-version, 'debian:') }} + run: | + apt-get update + apt-get install -y openssl perl make gcc libssl-dev sudo curl + - name: Install dependencies using yum + if: ${{ matrix.os-version == 'almalinux:9' }} + run: | + yum install --skip-broken -y openssl perl make gcc openssl-devel sudo curl + - run: openssl version + - run: perl -V + - name: uses install-with-cpm + uses: perl-actions/install-with-cpm@v1 + with: + cpanfile: "cpanfile" + - name: Makefile.PL + run: perl -I$(pwd) Makefile.PL + - run: make && prove -wbvm t/*.t + +# +# List of Perl Versions available +# + + perl-versions: + runs-on: ubuntu-latest + needs: [openssl-matrix] + name: List Perl versions + outputs: + perl-versions: ${{ steps.action.outputs.perl-versions }} + steps: + - id: action + uses: perl-actions/perl-versions@v1 + with: + since-perl: v5.10 + with-devel: true + +# +# The Perl matrix on linux +# + + perl: + env: + PERL_USE_UNSAFE_INC: 0 + AUTHOR_TESTING: 1 + AUTOMATED_TESTING: 1 + RELEASE_TESTING: 1 + + runs-on: ubuntu-latest + needs: [openssl-matrix,perl-versions] + name: "Perl ${{ matrix.perl-version }}" + + strategy: + fail-fast: false + matrix: + perl-version: ${{ fromJson (needs.perl-versions.outputs.perl-versions) }} + + container: perldocker/perl-tester:${{ matrix.perl-version }} + + steps: + - uses: actions/checkout@v6 + - run: openssl version + - run: perl -V + - name: Deps for testing + run: | + cpanm --notest Crypt::OpenSSL::Random Crypt::OpenSSL::Guess Test::CPAN::Meta Perl::MinimumVersion Test::Pod::Coverage Test::Pod Test::MinimumVersion Crypt::OpenSSL::Bignum ||: + cpanm --notest Crypt::OpenSSL::Random Crypt::OpenSSL::Guess Test::CPAN::Meta Perl::MinimumVersion Test::Pod::Coverage Test::Pod Test::MinimumVersion Crypt::OpenSSL::Bignum + # not available < 5.12 + cpanm --notest Test::Kwalitee ||: + - run: perl Makefile.PL + - run: make && ( make test || prove -wbvm t/*.t ) + +# +# Windows +# + + windows: + env: + PERL_USE_UNSAFE_INC: 0 + AUTHOR_TESTING: 0 + AUTOMATED_TESTING: 1 + RELEASE_TESTING: 0 + + needs: [openssl-matrix, perl-versions] + runs-on: windows-latest + + strategy: + fail-fast: false + matrix: + perl-version: [latest] + + steps: + - uses: actions/checkout@v6 + - name: Set up Perl + run: | + # skip installing perl if it is already installed. + if (!(Test-Path "C:\strawberry\perl\bin")) { + choco install strawberryperl + } + echo @" + C:\strawberry\c\bin + C:\strawberry\perl\site\bin + C:\strawberry\perl\bin + "@ | + Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append + - run: openssl version + - run: perl -V + - name: Deps for testing + run: cpanm --notest Crypt::OpenSSL::Random Crypt::OpenSSL::Guess + - run: perl Makefile.PL + - run: make && ( make test || prove -wbvm t/*.t ) diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml deleted file mode 100644 index a0eb14a..0000000 --- a/.github/workflows/windows.yml +++ /dev/null @@ -1,47 +0,0 @@ -name: windows - -on: - push: - branches: - - '*' - tags-ignore: - - '*' - pull_request: - -jobs: - perl: - env: - PERL_USE_UNSAFE_INC: 0 - AUTHOR_TESTING: 0 - AUTOMATED_TESTING: 1 - RELEASE_TESTING: 0 - - runs-on: windows-latest - - strategy: - fail-fast: false - matrix: - perl-version: [latest] - - steps: - - uses: actions/checkout@v2 - - name: Set up Perl - run: | - # skip installing perl if it is already installed. - if (!(Test-Path "C:\strawberry\perl\bin")) { - choco install strawberryperl - } - echo @" - C:\strawberry\c\bin - C:\strawberry\perl\site\bin - C:\strawberry\perl\bin - "@ | - Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append - - name: perl -V - run: perl -V - - name: Deps for testing - run: cpanm --notest Crypt::OpenSSL::Random Crypt::OpenSSL::Guess - - name: Makefile.PL - run: perl Makefile.PL - - name: make test - run: make test diff --git a/.gitignore b/.gitignore index 433618c..b7a4352 100644 --- a/.gitignore +++ b/.gitignore @@ -15,7 +15,5 @@ *.o *.gcov *.gcno -*.gcov *.gcda -*.gcno *.ERR diff --git a/Changes b/Changes index 25f4c31..73b3acb 100644 --- a/Changes +++ b/Changes @@ -1,5 +1,38 @@ Revision history for Perl extension Crypt::OpenSSL::RSA. +0.37 Oct 29 2025 + - Fix libressl bitwise logic error in RSA.xs + +0.36 Oct 29 2025 + - Fix old openssl on strawberry does not include whrlpool.h + - libressl message digest functions md cannot be NULL + - Don't support whirlpool in libressl + - Add support for use_pkcs1_pss_padding with fatal error if RSA-PSS is used for encryption operations + +0.35 May 7 2025 + - Disable PKCS#1 v1.5 padding. It's not practical to mitigate marvin attacks so we will instead disable this and require alternatives to address the issue. + - Resolves #42 - CVE-2024-2467. + +0.34 May 5 2025 + - Production release. + +0.34_03 May 4 2025 + - Fix bug in rsa_crypt. Need to pass NULL + +0.34_02 May 4 2025 + - t/rsa.t needs to tolerate sha1 being disabled on rhel. + +0.34_01 May 3 2025 + - docs - plaintext = decrypt(cyphertext) + - #44 - Fix issue when libz is not linked on AIX + - #50 - Correct openssl version may not be found + - #52 - Out of memory on openssl 1.1.1w hpux + - #47 - Update FSF address and LGPL name in LICENSE + - #55 - stop using AutoLoader + - #48 - Whirlpool is missing the header + - Move github repo to cpan-authors + - Fully support openSSL 3.x API + 0.33 July 7 2022 - Update for windows github CI - Remove duplicit 'LICENSE' key diff --git a/LICENSE b/LICENSE index d4b3b03..af0d572 100644 --- a/LICENSE +++ b/LICENSE @@ -11,9 +11,10 @@ b) the "Artistic License" The General Public License (GPL) Version 2, June 1991 -Copyright (C) 1989, 1991 Free Software Foundation, Inc. 675 Mass Ave, -Cambridge, MA 02139, USA. Everyone is permitted to copy and distribute -verbatim copies of this license document, but changing it is not allowed. +Copyright (C) 1989, 1991 Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +Everyone is permitted to copy and distribute verbatim copies +of this license document, but changing it is not allowed. Preamble @@ -23,7 +24,7 @@ guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is -covered by the GNU Library General Public License instead.) You can apply it to +covered by the GNU Lesser General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our diff --git a/Makefile.PL b/Makefile.PL index 3b72fdd..7fb2978 100644 --- a/Makefile.PL +++ b/Makefile.PL @@ -1,13 +1,21 @@ use strict; use warnings; +use Config; use 5.006; use ExtUtils::MakeMaker 6.48; -use Crypt::OpenSSL::Guess qw(openssl_inc_paths openssl_lib_paths); +use Crypt::OpenSSL::Guess qw(openssl_inc_paths openssl_lib_paths openssl_version); +my ($major, $minor, $patch) = openssl_version(); +print "OpenSSL version: $major.$minor $patch", "\n"; # See lib/ExtUtils/MakeMaker.pm for details of how to influence # the contents of the Makefile that is written. +my $libs = ' -lssl -lcrypto'; +if ( $Config{osname} eq 'aix' ) { + $libs = $libs . ' -lz'; +} + WriteMakefile( 'NAME' => 'Crypt::OpenSSL::RSA', 'AUTHOR' => 'Ian Robertson ', @@ -22,7 +30,8 @@ WriteMakefile( 'Test::More' => 0, }, 'OBJECT' => 'RSA.o', - 'LIBS' => [openssl_lib_paths() . ' -lssl -lcrypto'], + 'LIBS' => [openssl_lib_paths() . $libs], + 'LDDLFLAGS' => openssl_lib_paths() . ' ' . $Config{lddlflags}, 'DEFINE' => '-DPERL5 -DOPENSSL_NO_KRB5', # perl-5.8/gcc-3.2 needs -DPERL5, and redhat9 likes -DOPENSSL_NO_KRB5 @@ -38,13 +47,13 @@ WriteMakefile( 'Crypt::OpenSSL::Guess' => '0.11', }, build_requires => { - 'Test' => 0, # For testing + 'Test::More' => 0, }, resources => { - 'license' => 'http://dev.perl.org/licenses/', - 'homepage' => 'http://github.com/toddr/Crypt-OpenSSL-RSA', - 'bugtracker' => 'https://github.com/toddr/Crypt-OpenSSL-RSA/issues', - 'repository' => 'http://github.com/toddr/Crypt-OpenSSL-RSA', + 'license' => 'https://dev.perl.org/licenses/', + 'homepage' => 'https://github.com/cpan-authors/Crypt-OpenSSL-RSA', + 'bugtracker' => 'https://github.com/cpan-authors/Crypt-OpenSSL-RSA/issues', + 'repository' => 'https://github.com/cpan-authors/Crypt-OpenSSL-RSA', } } ); diff --git a/README b/README index 4fe1a46..a6587a3 100644 --- a/README +++ b/README @@ -13,7 +13,7 @@ SYNOPSIS $ciphertext = $rsa->encrypt($plaintext); $rsa_priv = Crypt::OpenSSL::RSA->new_private_key($key_string); - $plaintext = $rsa->encrypt($ciphertext); + $plaintext = $rsa->decrypt($ciphertext); $rsa = Crypt::OpenSSL::RSA->generate_key(1024); # or $rsa = Crypt::OpenSSL::RSA->generate_key(1024, $prime); @@ -28,6 +28,15 @@ SYNOPSIS $signature = $rsa_priv->sign($plaintext); print "Signed correctly\n" if ($rsa->verify($plaintext, $signature)); +SECURITY + Version 0.35 makes the use of PKCS#1 v1.5 padding a fatal error. It is + very difficult to implement PKCS#1 v1.5 padding securely. If you are + still using RSA in in general, you should be looking at alternative + encryption algorithms. Version 0.36 implements RSA-PSS padding (PKCS#1 + v2.1) and makes setting an invalid padding a fatal error. Note, + PKCS1_OAEP can only be used for encryption and PKCS1_PSS can only be + used for signing. + DESCRIPTION "Crypt::OpenSSL::RSA" provides the ability to RSA encrypt strings which are somewhat shorter than the block size of a key. It also allows for @@ -48,6 +57,10 @@ Class Methods The padding is set to PKCS1_OAEP, but can be changed with the "use_xxx_padding" methods. + Note, PKCS1_OAEP can only be used for encryption. You must + specifically call use_pkcs1_pss_padding (or use_pkcs1_pss_padding) + prior to signing operations. + new_private_key Create a new "Crypt::OpenSSL::RSA" object by loading a private key in from an string containing the Base64/DER encoding of the PKCS1 @@ -140,20 +153,50 @@ Instance Methods verify Check the signature on a text. +Padding Methods + Versions prior to 0.35 allowed using pkcs1 padding for both encryption + and signature operations but has been disabled for security reasons. + + While use_no_padding can be used for encryption or signature operations + use_pkcs1_pss_padding is used for signature operations and + use_pkcs1_oaep_padding is used for encryption operations. + + Version 0.38 sets the appropriate padding for each operation unless + use_no_padding is called before either operation. + + Note: while "pkcs1-pss" is the effective replacement for "pkcs1" your + use case may require some additional steps. JSON Web Tokens (JWT) for + instance require the algorithm to be changed from "RS256" for "pkcs1" + (SHA1256) to "PS256" for "pkcs1-pss" (SHA-256 and MGF1 with SHA-256) + use_no_padding Use raw RSA encryption. This mode should only be used to implement cryptographically sound padding modes in the application code. Encrypting user data directly with RSA is insecure. use_pkcs1_padding - Use PKCS #1 v1.5 padding. This currently is the most widely used - mode of padding. + PKCS #1 v1.5 padding has been disabled as it is nearly impossible to + use this padding method in a secure manner. It is known to be + vulnerable to timing based side channel attacks. use_pkcs1_padding() + results in a fatal error. + + Marvin Attack + use_pkcs1_oaep_padding Use "EME-OAEP" padding as defined in PKCS #1 v2.0 with SHA-1, MGF1 and an empty encoding parameter. This mode of padding is recommended for all new applications. It is the default mode used by - "Crypt::OpenSSL::RSA". + "Crypt::OpenSSL::RSA" but is only valid for encryption/decryption. + + use_pkcs1_pss_padding + Use RSA-PSS padding as defined in PKCS#1 v2.1. In general, RSA-PSS + should be used as a replacement for RSA-PKCS#1 v1.5. The module + specifies the message digest being requested and the appropriate mgf1 + setting and salt length for the digest. + + Note: RSA-PSS cannot be used for encryption/decryption and results in + a fatal error. Call use_pkcs1_oaep_padding for encryption operations. use_sslv23_padding Use "PKCS #1 v1.5" padding with an SSL-specific modification that @@ -161,6 +204,7 @@ Instance Methods Not available since OpenSSL 3. +Hash/Digest Methods use_md5_hash Use the RFC 1321 MD5 hashing algorithm by Ron Rivest when signing and verifying messages. diff --git a/README.md b/README.md index d0f37a8..9d90808 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ Crypt::OpenSSL::RSA - RSA encoding and decoding, using the openSSL libraries $ciphertext = $rsa->encrypt($plaintext); $rsa_priv = Crypt::OpenSSL::RSA->new_private_key($key_string); - $plaintext = $rsa->encrypt($ciphertext); + $plaintext = $rsa->decrypt($ciphertext); $rsa = Crypt::OpenSSL::RSA->generate_key(1024); # or $rsa = Crypt::OpenSSL::RSA->generate_key(1024, $prime); @@ -31,6 +31,15 @@ Crypt::OpenSSL::RSA - RSA encoding and decoding, using the openSSL libraries $signature = $rsa_priv->sign($plaintext); print "Signed correctly\n" if ($rsa->verify($plaintext, $signature)); +# SECURITY + +Version 0.35 makes the use of PKCS#1 v1.5 padding a fatal error. It is +very difficult to implement PKCS#1 v1.5 padding securely. If you are still +using RSA in in general, you should be looking at alternative encryption +algorithms. Version 0.36 implements RSA-PSS padding (PKCS#1 v2.1) and makes +setting an invalid padding a fatal error. Note, PKCS1\_OAEP can only be used +for encryption and PKCS1\_PSS can only be used for signing. + # DESCRIPTION `Crypt::OpenSSL::RSA` provides the ability to RSA encrypt strings which are @@ -54,6 +63,10 @@ this (never documented) behavior is no longer the case. The padding is set to PKCS1\_OAEP, but can be changed with the `use_xxx_padding` methods. + Note, PKCS1\_OAEP can only be used for encryption. You must specifically + call use\_pkcs1\_pss\_padding (or use\_pkcs1\_pss\_padding) prior to signing + operations. + - new\_private\_key Create a new `Crypt::OpenSSL::RSA` object by loading a private key in @@ -165,6 +178,23 @@ this (never documented) behavior is no longer the case. Check the signature on a text. +# Padding Methods + +Versions prior to 0.35 allowed using pkcs1 padding for both encryption +and signature operations but has been disabled for security reasons. + +While **use\_no\_padding** can be used for encryption or signature operations +**use\_pkcs1\_pss\_padding** is used for signature operations and +**use\_pkcs1\_oaep\_padding** is used for encryption operations. + +Version 0.38 sets the appropriate padding for each operation unless +**use\_no\_padding** is called before either operation. + +**Note:** while "pkcs1-pss" is the effective replacement for "pkcs1" your +use case may require some additional steps. JSON Web Tokens (JWT) for +instance require the algorithm to be changed from "RS256" for "pkcs1" +(SHA1256) to "PS256" for "pkcs1-pss" (SHA-256 and MGF1 with SHA-256) + - use\_no\_padding Use raw RSA encryption. This mode should only be used to implement @@ -173,15 +203,28 @@ this (never documented) behavior is no longer the case. - use\_pkcs1\_padding - Use PKCS #1 v1.5 padding. This currently is the most widely used mode - of padding. + PKCS #1 v1.5 padding has been disabled as it is nearly impossible to use this + padding method in a secure manner. It is known to be vulnerable to timing + based side channel attacks. use\_pkcs1\_padding() results in a fatal error. + + [Marvin Attack](https://github.com/tomato42/marvin-toolkit/blob/master/README.md) - use\_pkcs1\_oaep\_padding Use `EME-OAEP` padding as defined in PKCS #1 v2.0 with SHA-1, MGF1 and an empty encoding parameter. This mode of padding is recommended for all new applications. It is the default mode used by - `Crypt::OpenSSL::RSA`. + `Crypt::OpenSSL::RSA` but is only valid for encryption/decryption. + +- use\_pkcs1\_pss\_padding + + Use `RSA-PSS` padding as defined in PKCS#1 v2.1. In general, RSA-PSS + should be used as a replacement for RSA-PKCS#1 v1.5. The module specifies + the message digest being requested and the appropriate mgf1 setting and + salt length for the digest. + + **Note**: RSA-PSS cannot be used for encryption/decryption and results in a + fatal error. Call `use_pkcs1_oaep_padding` for encryption operations. - use\_sslv23\_padding @@ -190,6 +233,8 @@ this (never documented) behavior is no longer the case. Not available since OpenSSL 3. +# Hash/Digest Methods + - use\_md5\_hash Use the RFC 1321 MD5 hashing algorithm by Ron Rivest when signing and diff --git a/RSA.pm b/RSA.pm index 1fefbdf..42a296e 100644 --- a/RSA.pm +++ b/RSA.pm @@ -5,10 +5,7 @@ use warnings; use Carp; # Removing carp will break the XS code. -our $VERSION = '0.33'; - -our $AUTOLOAD; -use AutoLoader 'AUTOLOAD'; +our $VERSION = '0.37'; use XSLoader; XSLoader::load 'Crypt::OpenSSL::RSA', $VERSION; @@ -20,6 +17,34 @@ BEGIN { }; } ## no critic qw(RequireCheckingReturnValueOfEval); +sub new_public_key { + my ( $proto, $p_key_string ) = @_; + if ( $p_key_string =~ /^-----BEGIN RSA PUBLIC KEY-----/ ) { + return $proto->_new_public_key_pkcs1($p_key_string); + } + elsif ( $p_key_string =~ /^-----BEGIN PUBLIC KEY-----/ ) { + return $proto->_new_public_key_x509($p_key_string); + } + else { + croak "unrecognized key format"; + } +} + +sub new_key_from_parameters { + my ( $proto, $n, $e, $d, $p, $q ) = @_; + return $proto->_new_key_from_parameters( map { $_ ? $_->pointer_copy() : 0 } $n, $e, $d, $p, $q ); +} + +sub import_random_seed { + until ( _random_status() ) { + _random_seed( Crypt::OpenSSL::Random::random_bytes(20) ); + } +} + +sub get_key_parameters { + return map { $_ ? Crypt::OpenSSL::Bignum->bless_pointer($_) : undef } shift->_get_key_parameters(); +} + 1; __END__ @@ -55,6 +80,15 @@ Crypt::OpenSSL::RSA - RSA encoding and decoding, using the openSSL libraries $signature = $rsa_priv->sign($plaintext); print "Signed correctly\n" if ($rsa->verify($plaintext, $signature)); +=head1 SECURITY + +Version 0.35 makes the use of PKCS#1 v1.5 padding a fatal error. It is +very difficult to implement PKCS#1 v1.5 padding securely. If you are still +using RSA in in general, you should be looking at alternative encryption +algorithms. Version 0.36 implements RSA-PSS padding (PKCS#1 v2.1) and makes +setting an invalid padding a fatal error. Note, PKCS1_OAEP can only be used +for encryption and PKCS1_PSS can only be used for signing. + =head1 DESCRIPTION C provides the ability to RSA encrypt strings which are @@ -80,19 +114,9 @@ C<-----BEGIN...-----> and C<-----END...-----> lines. The padding is set to PKCS1_OAEP, but can be changed with the C methods. -=cut -sub new_public_key { - my ( $proto, $p_key_string ) = @_; - if ( $p_key_string =~ /^-----BEGIN RSA PUBLIC KEY-----/ ) { - return $proto->_new_public_key_pkcs1($p_key_string); - } - elsif ( $p_key_string =~ /^-----BEGIN PUBLIC KEY-----/ ) { - return $proto->_new_public_key_x509($p_key_string); - } - else { - croak "unrecognized key format"; - } -} +Note, PKCS1_OAEP can only be used for encryption. You must specifically +call use_pkcs1_pss_padding (or use_pkcs1_pss_padding) prior to signing +operations. =item new_private_key @@ -130,25 +154,12 @@ provided and d is undef, d is computed. Note that while p and q are not necessary for a private key, their presence will speed up computation. -=cut -sub new_key_from_parameters { - my ( $proto, $n, $e, $d, $p, $q ) = @_; - return $proto->_new_key_from_parameters( map { $_ ? $_->pointer_copy() : 0 } $n, $e, $d, $p, $q ); -} - =item import_random_seed Import a random seed from L, since the OpenSSL libraries won't allow sharing of random structures across perl XS modules. -=cut -sub import_random_seed { - until ( _random_status() ) { - _random_seed( Crypt::OpenSSL::Random::random_bytes(20) ); - } -} - =back =head1 Instance Methods @@ -230,6 +241,27 @@ Sign a string using the secret (portion of the) key. Check the signature on a text. +=back + +=head1 Padding Methods + +Versions prior to 0.35 allowed using pkcs1 padding for both encryption +and signature operations but has been disabled for security reasons. + +While B can be used for encryption or signature operations +B is used for signature operations and +B is used for encryption operations. + +Version 0.38 sets the appropriate padding for each operation unless +B is called before either operation. + +B while C is the effective replacement for your +use case may require some additional steps. JSON Web Tokens (JWT) for +instance require the algorithm to be changed from "RS256" for "pkcs1" +(SHA1256) to "PS256" for "pkcs1-pss" (SHA-256 and MGF1 with SHA-256) + +=over + =item use_no_padding Use raw RSA encryption. This mode should only be used to implement @@ -238,15 +270,28 @@ Encrypting user data directly with RSA is insecure. =item use_pkcs1_padding -Use PKCS #1 v1.5 padding. This currently is the most widely used mode -of padding. +PKCS #1 v1.5 padding has been disabled as it is nearly impossible to use this +padding method in a secure manner. It is known to be vulnerable to timing +based side channel attacks. use_pkcs1_padding() results in a fatal error. + +L =item use_pkcs1_oaep_padding Use C padding as defined in PKCS #1 v2.0 with SHA-1, MGF1 and an empty encoding parameter. This mode of padding is recommended for all new applications. It is the default mode used by -C. +C but is only valid for encryption/decryption. + +=item use_pkcs1_pss_padding + +Use C padding as defined in PKCS#1 v2.1. In general, RSA-PSS +should be used as a replacement for RSA-PKCS#1 v1.5. The module specifies +the message digest being requested and the appropriate mgf1 setting and +salt length for the digest. + +B: RSA-PSS cannot be used for encryption/decryption and results in a +fatal error. Call C for encryption operations. =item use_sslv23_padding @@ -255,6 +300,12 @@ denotes that the server is SSL3 capable. Not available since OpenSSL 3. +=back + +=head1 Hash/Digest Methods + +=over + =item use_md5_hash Use the RFC 1321 MD5 hashing algorithm by Ron Rivest when signing and @@ -325,11 +376,6 @@ C module must be installed for this to work. Return true if this is a private key, and false if it is private only. -=cut -sub get_key_parameters { - return map { $_ ? Crypt::OpenSSL::Bignum->bless_pointer($_) : undef } shift->_get_key_parameters(); -} - =back =head1 BUGS diff --git a/RSA.xs b/RSA.xs index 5f5cfae..3c4a084 100644 --- a/RSA.xs +++ b/RSA.xs @@ -10,14 +10,52 @@ #include #include #include +#if OPENSSL_VERSION_NUMBER >= 0x10000000 && OPENSSL_VERSION_NUMBER < 0x30000000 +#ifndef LIBRESSL_VERSION_NUMBER +#ifndef OPENSSL_NO_WHIRLPOOL +#include +#endif +#endif +#endif #include #include #include #include +#if OPENSSL_VERSION_NUMBER >= 0x30000000L +#include +#include +#include +#endif + +#if OPENSSL_VERSION_NUMBER >= 0x30000000L +#define UNSIGNED_CHAR unsigned char +#define SIZE_T_INT size_t +#define SIZE_T_UNSIGNED_INT size_t +#define EVP_PKEY EVP_PKEY +#define EVP_PKEY_free(p) EVP_PKEY_free(p) +#define EVP_PKEY_get_size(p) EVP_PKEY_get_size(p) +#define PEM_read_bio_PrivateKey PEM_read_bio_PrivateKey +#define PEM_read_bio_RSAPublicKey PEM_read_bio_PUBKEY +#define PEM_read_bio_RSA_PUBKEY PEM_read_bio_PUBKEY +#define PEM_write_bio_PUBKEY(o,p) PEM_write_bio_PUBKEY(o,p); +#define PEM_write_bio_PrivateKey_traditional(m, n, o, p, q, r, s) PEM_write_bio_PrivateKey_traditional(m, n, o, p, q, r, s) +#else +#define UNSIGNED_CHAR char +#define SIZE_T_INT int +#define SIZE_T_UNSIGNED_INT unsigned int +#define EVP_PKEY RSA +#define EVP_PKEY_free(p) RSA_free(p) +#define EVP_PKEY_get_size(p) RSA_size(p) +#define PEM_read_bio_PrivateKey PEM_read_bio_RSAPrivateKey +#define PEM_read_bio_RSAPublicKey PEM_read_bio_RSAPublicKey +#define PEM_read_bio_RSA_PUBKEY PEM_read_bio_RSA_PUBKEY +#define PEM_write_bio_PUBKEY(o,p) PEM_write_bio_RSA_PUBKEY(o,p) +#define PEM_write_bio_PrivateKey_traditional(m, n, o, p, q, r, s) PEM_write_bio_RSAPrivateKey(m, n , o, p, q, r, s) +#endif typedef struct { - RSA* rsa; + EVP_PKEY* rsa; int padding; int hashMode; } rsaData; @@ -52,22 +90,37 @@ void croakSsl(char* p_file, int p_line) char _is_private(rsaData* p_rsa) { - const BIGNUM *d; + char ret = 0; #if OLD_CRUFTY_SSL_VERSION + const BIGNUM* d; d = p_rsa->rsa->d; + ret = (d != NULL); #else +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + BIGNUM* d = NULL; + EVP_PKEY_get_bn_param(p_rsa->rsa, OSSL_PKEY_PARAM_RSA_D, &d); + ret = (d != NULL); + BN_clear_free(d); +#else + const BIGNUM* d = NULL; RSA_get0_key(p_rsa->rsa, NULL, NULL, &d); + ret = (d != NULL); +#endif #endif - return(d != NULL); + return ret; } -SV* make_rsa_obj(SV* p_proto, RSA* p_rsa) +SV* make_rsa_obj(SV* p_proto, EVP_PKEY* p_rsa) { rsaData* rsa; CHECK_NEW(rsa, 1, rsaData); rsa->rsa = p_rsa; +#ifdef SHA512_DIGEST_LENGTH + rsa->hashMode = NID_sha256; +#else rsa->hashMode = NID_sha1; +#endif rsa->padding = RSA_PKCS1_OAEP_PADDING; return sv_bless( newRV_noinc(newSViv((IV) rsa)), @@ -111,42 +164,88 @@ int get_digest_length(int hash_method) break; } } +#if OPENSSL_VERSION_NUMBER >= 0x30000000L -unsigned char* get_message_digest(SV* text_SV, int hash_method) +EVP_MD *get_md_bynid(int hash_method) +{ + switch(hash_method) + { + case NID_md5: + return EVP_MD_fetch(NULL, "md5", NULL); + break; + case NID_sha1: + return EVP_MD_fetch(NULL, "sha1", NULL); + break; +#ifdef SHA512_DIGEST_LENGTH + case NID_sha224: + return EVP_MD_fetch(NULL, "sha224", NULL); + break; + case NID_sha256: + return EVP_MD_fetch(NULL, "sha256", NULL); + break; + case NID_sha384: + return EVP_MD_fetch(NULL, "sha384", NULL); + break; + case NID_sha512: + return EVP_MD_fetch(NULL, "sha512", NULL); + break; +#endif + case NID_ripemd160: + return EVP_MD_fetch(NULL, "ripemd160", NULL); + break; +#ifdef WHIRLPOOL_DIGEST_LENGTH + case NID_whirlpool: + return EVP_MD_fetch(NULL, "whirlpool", NULL); + break; +#endif + default: + croak("Unknown digest hash mode %u", hash_method); + break; + } +} +#endif +unsigned char* get_message_digest(SV* text_SV, int hash_method, unsigned char* md) { STRLEN text_length; unsigned char* text; - text = (unsigned char*) SvPV(text_SV, text_length); switch(hash_method) { case NID_md5: - return MD5(text, text_length, NULL); +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + return EVP_Q_digest(NULL, "MD5", NULL, text, text_length, md, NULL) ? md : NULL; +#else + return MD5(text, text_length, md); +#endif break; case NID_sha1: - return SHA1(text, text_length, NULL); + return SHA1(text, text_length, md); break; #ifdef SHA512_DIGEST_LENGTH case NID_sha224: - return SHA224(text, text_length, NULL); + return SHA224(text, text_length, md); break; case NID_sha256: - return SHA256(text, text_length, NULL); + return SHA256(text, text_length, md); break; case NID_sha384: - return SHA384(text, text_length, NULL); + return SHA384(text, text_length, md); break; case NID_sha512: - return SHA512(text, text_length, NULL); + return SHA512(text, text_length, md); break; #endif case NID_ripemd160: - return RIPEMD160(text, text_length, NULL); +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + return EVP_Q_digest(NULL, "RIPEMD160", NULL, text, text_length, md, NULL) ? md : NULL; +#else + return RIPEMD160(text, text_length, md); +#endif break; #ifdef WHIRLPOOL_DIGEST_LENGTH case NID_whirlpool: - return WHIRLPOOL(text, text_length, NULL); + return WHIRLPOOL(text, text_length, md); break; #endif default: @@ -165,26 +264,28 @@ SV* cor_bn2sv(const BIGNUM* p_bn) SV* extractBioString(BIO* p_stringBio) { SV* sv; - BUF_MEM* bptr; + char* datap; + long datasize = 0; CHECK_OPEN_SSL(BIO_flush(p_stringBio) == 1); - BIO_get_mem_ptr(p_stringBio, &bptr); - sv = newSVpv(bptr->data, bptr->length); + + datasize = BIO_get_mem_data(p_stringBio, &datap); + sv = newSVpv(datap, datasize); CHECK_OPEN_SSL(BIO_set_close(p_stringBio, BIO_CLOSE) == 1); BIO_free(p_stringBio); return sv; } -RSA* _load_rsa_key(SV* p_keyStringSv, - RSA*(*p_loader)(BIO*, RSA**, pem_password_cb*, void*), +EVP_PKEY* _load_rsa_key(SV* p_keyStringSv, + EVP_PKEY*(*p_loader)(BIO *, EVP_PKEY**, pem_password_cb*, void*), SV* p_passphaseSv) { STRLEN keyStringLength; char* keyString; - char* passphase = NULL; + UNSIGNED_CHAR *passphase = NULL; - RSA* rsa; + EVP_PKEY* rsa; BIO* stringBIO; keyString = SvPV(p_keyStringSv, keyStringLength); @@ -203,35 +304,68 @@ RSA* _load_rsa_key(SV* p_keyStringSv, CHECK_OPEN_SSL(rsa); return rsa; } +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + +SV* rsa_crypt(rsaData* p_rsa, SV* p_from, + int (*p_crypt)(EVP_PKEY_CTX*, unsigned char*, size_t*, const unsigned char*, size_t), + int (*init_crypt)(EVP_PKEY_CTX*), int public) +#else SV* rsa_crypt(rsaData* p_rsa, SV* p_from, int (*p_crypt)(int, const unsigned char*, unsigned char*, RSA*, int)) +#endif { STRLEN from_length; - int to_length; + SIZE_T_INT to_length; int size; unsigned char* from; - char* to; + UNSIGNED_CHAR *to; SV* sv; from = (unsigned char*) SvPV(p_from, from_length); - size = RSA_size(p_rsa->rsa); - CHECK_NEW(to, size, char); + size = EVP_PKEY_get_size(p_rsa->rsa); + CHECK_NEW(to, size, UNSIGNED_CHAR); +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + + if(p_rsa->padding == RSA_PKCS1_PSS_PADDING) + croak("PKCS#1 v2.1 RSA-PSS cannot be used for encryption operations call \"use_pkcs1_oaep_padding\" instead."); + EVP_PKEY_CTX *ctx; + + OSSL_LIB_CTX *ossllibctx = OSSL_LIB_CTX_new(); + if (public) { + ctx = EVP_PKEY_CTX_new_from_pkey(ossllibctx, (EVP_PKEY* )p_rsa->rsa, NULL); + } else { + ctx = EVP_PKEY_CTX_new((EVP_PKEY* )p_rsa->rsa, NULL); + } + + CHECK_OPEN_SSL(ctx); + + CHECK_OPEN_SSL(init_crypt(ctx) == 1); + int crypt_pad = p_rsa->padding; + if (p_rsa->padding != RSA_NO_PADDING) { + crypt_pad = RSA_PKCS1_OAEP_PADDING; + } + CHECK_OPEN_SSL(EVP_PKEY_CTX_set_rsa_padding(ctx, crypt_pad) > 0); + CHECK_OPEN_SSL(p_crypt(ctx, NULL, &to_length, from, from_length) == 1); + CHECK_OPEN_SSL(p_crypt(ctx, to, &to_length, from, from_length) == 1); + + EVP_PKEY_CTX_free(ctx); + OSSL_LIB_CTX_free(ossllibctx); +#else to_length = p_crypt( from_length, from, (unsigned char*) to, p_rsa->rsa, p_rsa->padding); - +#endif if (to_length < 0) { Safefree(to); CHECK_OPEN_SSL(0); } - sv = newSVpv(to, to_length); + sv = newSVpv((char* ) to, to_length); Safefree(to); return sv; } - MODULE = Crypt::OpenSSL::RSA PACKAGE = Crypt::OpenSSL::RSA PROTOTYPES: DISABLE @@ -252,7 +386,7 @@ new_private_key(proto, key_string_SV, passphase_SV=&PL_sv_undef) SV* passphase_SV; CODE: RETVAL = make_rsa_obj( - proto, _load_rsa_key(key_string_SV, PEM_read_bio_RSAPrivateKey, passphase_SV)); + proto, _load_rsa_key(key_string_SV, PEM_read_bio_PrivateKey, passphase_SV)); OUTPUT: RETVAL @@ -280,7 +414,7 @@ void DESTROY(p_rsa) rsaData* p_rsa; CODE: - RSA_free(p_rsa->rsa); + EVP_PKEY_free(p_rsa->rsa); Safefree(p_rsa); SV* @@ -313,8 +447,8 @@ get_private_key_string(p_rsa, passphase_SV=&PL_sv_undef, cipher_name_SV=&PL_sv_u } CHECK_OPEN_SSL(stringBIO = BIO_new(BIO_s_mem())); - PEM_write_bio_RSAPrivateKey( - stringBIO, p_rsa->rsa, enc, passphase, passphaseLength, NULL, NULL); + PEM_write_bio_PrivateKey_traditional( + stringBIO, p_rsa->rsa, enc, (unsigned char* ) passphase, passphaseLength, NULL, NULL); RETVAL = extractBioString(stringBIO); OUTPUT: @@ -327,7 +461,19 @@ get_public_key_string(p_rsa) BIO* stringBIO; CODE: CHECK_OPEN_SSL(stringBIO = BIO_new(BIO_s_mem())); +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + OSSL_ENCODER_CTX *ctx = NULL; + + ctx = OSSL_ENCODER_CTX_new_for_pkey(p_rsa->rsa, OSSL_KEYMGMT_SELECT_PUBLIC_KEY, + "PEM", "PKCS1", NULL); + CHECK_OPEN_SSL(ctx != NULL && OSSL_ENCODER_CTX_get_num_encoders(ctx)); + + CHECK_OPEN_SSL(OSSL_ENCODER_to_bio(ctx, stringBIO) == 1); + + OSSL_ENCODER_CTX_free(ctx); +#else PEM_write_bio_RSAPublicKey(stringBIO, p_rsa->rsa); +#endif RETVAL = extractBioString(stringBIO); OUTPUT: @@ -340,7 +486,7 @@ get_public_key_x509_string(p_rsa) BIO* stringBIO; CODE: CHECK_OPEN_SSL(stringBIO = BIO_new(BIO_s_mem())); - PEM_write_bio_RSA_PUBKEY(stringBIO, p_rsa->rsa); + PEM_write_bio_PUBKEY(stringBIO, p_rsa->rsa); RETVAL = extractBioString(stringBIO); OUTPUT: @@ -352,20 +498,38 @@ generate_key(proto, bitsSV, exponent = 65537) SV* bitsSV; unsigned long exponent; PREINIT: - RSA* rsa; + EVP_PKEY* rsa = NULL; +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + EVP_PKEY_CTX *ctx; +#endif CODE: -#if OPENSSL_VERSION_NUMBER >= 0x00908000L BIGNUM *e; - int rc; e = BN_new(); BN_set_word(e, exponent); +#if OPENSSL_VERSION_NUMBER < 0x00908000L + rsa = RSA_generate_key(SvIV(bitsSV), exponent, NULL, NULL); + CHECK_OPEN_SSL(rsa != NULL); +#endif +#if OPENSSL_VERSION_NUMBER >= 0x00908000L && OPENSSL_VERSION_NUMBER < 0x30000000L rsa = RSA_new(); - rc = RSA_generate_key_ex(rsa, SvIV(bitsSV), e, NULL); + if (!RSA_generate_key_ex(rsa, SvIV(bitsSV), e, NULL)) + croak("Unable to generate a key"); BN_free(e); + CHECK_OPEN_SSL(rsa != NULL); +#endif +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + ctx = EVP_PKEY_CTX_new_from_name(NULL, "RSA", NULL); + + CHECK_OPEN_SSL(ctx); + CHECK_OPEN_SSL(EVP_PKEY_keygen_init(ctx) == 1); + CHECK_OPEN_SSL(EVP_PKEY_CTX_set_rsa_keygen_bits(ctx, SvIV(bitsSV)) > 0); + CHECK_OPEN_SSL(EVP_PKEY_CTX_set1_rsa_keygen_pubexp(ctx, e) >0); + CHECK_OPEN_SSL(EVP_PKEY_generate(ctx, &rsa) == 1); + CHECK_OPEN_SSL(rsa != NULL); + e = NULL; - CHECK_OPEN_SSL(rc != -1); -#else - rsa = RSA_generate_key(SvIV(bitsSV), exponent, NULL, NULL); + BN_free(e); + EVP_PKEY_CTX_free(ctx); #endif CHECK_OPEN_SSL(rsa); RETVAL = make_rsa_obj(proto, rsa); @@ -382,8 +546,8 @@ _new_key_from_parameters(proto, n, e, d, p, q) BIGNUM* p; BIGNUM* q; PREINIT: - RSA* rsa; - BN_CTX* ctx; + EVP_PKEY* rsa = NULL; + BN_CTX* ctx = NULL; BIGNUM* p_minus_1 = NULL; BIGNUM* q_minus_1 = NULL; BIGNUM* dmp1 = NULL; @@ -396,10 +560,23 @@ _new_key_from_parameters(proto, n, e, d, p, q) { croak("At least a modulus and public key must be provided"); } +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + OSSL_PARAM *params = NULL; + EVP_PKEY_CTX *pctx = EVP_PKEY_CTX_new_from_name(NULL, "RSA", NULL); + CHECK_OPEN_SSL(pctx != NULL); + CHECK_OPEN_SSL(EVP_PKEY_fromdata_init(pctx) > 0); + OSSL_PARAM_BLD *params_build = OSSL_PARAM_BLD_new(); + CHECK_OPEN_SSL(params_build) +#else CHECK_OPEN_SSL(rsa = RSA_new()); +#endif #if OLD_CRUFTY_SSL_VERSION rsa->n = n; rsa->e = e; +#endif +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + CHECK_OPEN_SSL(OSSL_PARAM_BLD_push_BN(params_build, OSSL_PKEY_PARAM_RSA_N, n)); + CHECK_OPEN_SSL(OSSL_PARAM_BLD_push_BN(params_build, OSSL_PKEY_PARAM_RSA_E, e)); #endif if (p || q) { @@ -418,8 +595,11 @@ _new_key_from_parameters(proto, n, e, d, p, q) #if OLD_CRUFTY_SSL_VERSION rsa->p = p; rsa->q = q; +#else +#if OPENSSL_VERSION_NUMBER >= 0x30000000L #else THROW(RSA_set0_factors(rsa, p, q)); +#endif #endif THROW(p_minus_1 = BN_new()); THROW(BN_sub(p_minus_1, p, BN_value_one())); @@ -433,8 +613,14 @@ _new_key_from_parameters(proto, n, e, d, p, q) } #if OLD_CRUFTY_SSL_VERSION rsa->d = d; +#else +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + THROW(OSSL_PARAM_BLD_push_BN(params_build, OSSL_PKEY_PARAM_RSA_D, d)); + THROW(OSSL_PARAM_BLD_push_BN(params_build, OSSL_PKEY_PARAM_RSA_FACTOR1, p)); + THROW(OSSL_PARAM_BLD_push_BN(params_build, OSSL_PKEY_PARAM_RSA_FACTOR2, q)); #else THROW(RSA_set0_key(rsa, n, e, d)); +#endif #endif THROW(dmp1 = BN_new()); THROW(BN_mod(dmp1, d, p_minus_1, ctx)); @@ -446,13 +632,59 @@ _new_key_from_parameters(proto, n, e, d, p, q) rsa->dmp1 = dmp1; rsa->dmq1 = dmq1; rsa->iqmp = iqmp; +#else +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + THROW(OSSL_PARAM_BLD_push_BN(params_build, OSSL_PKEY_PARAM_RSA_EXPONENT1, dmp1)); + THROW(OSSL_PARAM_BLD_push_BN(params_build, OSSL_PKEY_PARAM_RSA_EXPONENT2, dmq1)); + THROW(OSSL_PARAM_BLD_push_BN(params_build, OSSL_PKEY_PARAM_RSA_COEFFICIENT1, iqmp)); + + params = OSSL_PARAM_BLD_to_param(params_build); + THROW(params != NULL); + + int status = EVP_PKEY_fromdata(pctx, &rsa, EVP_PKEY_KEYPAIR, params); + THROW( status > 0 && rsa != NULL ); + EVP_PKEY_CTX* test_ctx = EVP_PKEY_CTX_new_from_pkey(NULL, rsa, NULL); + THROW(EVP_PKEY_check(test_ctx) == 1); #else THROW(RSA_set0_crt_params(rsa, dmp1, dmq1, iqmp)); +#endif #endif dmp1 = dmq1 = iqmp = NULL; +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + OSSL_PARAM_BLD_free(params_build); + OSSL_PARAM_free(params); +#else THROW(RSA_check_key(rsa) == 1); - err: +#endif + } + else + { +#if OLD_CRUFTY_SSL_VERSION + rsa->d = d; +#else +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + if(d != NULL) + THROW(OSSL_PARAM_BLD_push_BN(params_build, OSSL_PKEY_PARAM_RSA_D, d)); + params = OSSL_PARAM_BLD_to_param(params_build); + THROW(params != NULL); + + int status = EVP_PKEY_fromdata(pctx, &rsa, EVP_PKEY_KEYPAIR, params); + THROW( status > 0 && rsa != NULL ); +#else + CHECK_OPEN_SSL(RSA_set0_key(rsa, n, e, d)); +#endif +#endif + } + + RETVAL = make_rsa_obj(proto, rsa); + if(RETVAL) + goto end; + + err: + //if (p) BN_clear_free(p); if (p_minus_1) BN_clear_free(p_minus_1); + //if (q) BN_clear_free(q); + //if (d) BN_clear_free(d); if (q_minus_1) BN_clear_free(q_minus_1); if (dmp1) BN_clear_free(dmp1); if (dmq1) BN_clear_free(dmq1); @@ -460,20 +692,12 @@ _new_key_from_parameters(proto, n, e, d, p, q) if (ctx) BN_CTX_free(ctx); if (error) { - RSA_free(rsa); + EVP_PKEY_free(rsa); CHECK_OPEN_SSL(0); } } - else - { -#if OLD_CRUFTY_SSL_VERSION - rsa->d = d; -#else - CHECK_OPEN_SSL(RSA_set0_key(rsa, n, e, d)); -#endif - } - RETVAL = make_rsa_obj(proto, rsa); -} + end: + OUTPUT: RETVAL @@ -481,6 +705,16 @@ void _get_key_parameters(p_rsa) rsaData* p_rsa; PREINIT: +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + BIGNUM* n = NULL; + BIGNUM* e = NULL; + BIGNUM* d = NULL; + BIGNUM* p = NULL; + BIGNUM* q = NULL; + BIGNUM* dmp1 = NULL; + BIGNUM* dmq1 = NULL; + BIGNUM* iqmp = NULL; +#else const BIGNUM* n; const BIGNUM* e; const BIGNUM* d; @@ -489,9 +723,10 @@ PREINIT: const BIGNUM* dmp1; const BIGNUM* dmq1; const BIGNUM* iqmp; +#endif PPCODE: { - RSA* rsa; + EVP_PKEY* rsa; rsa = p_rsa->rsa; #if OLD_CRUFTY_SSL_VERSION n = rsa->n; @@ -502,10 +737,21 @@ PPCODE: dmp1 = rsa->dmp1; dmq1 = rsa->dmq1; iqmp = rsa->iqmp; +#else +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + EVP_PKEY_get_bn_param(rsa, OSSL_PKEY_PARAM_RSA_N, &n); + EVP_PKEY_get_bn_param(rsa, OSSL_PKEY_PARAM_RSA_E, &e); + EVP_PKEY_get_bn_param(rsa, OSSL_PKEY_PARAM_RSA_D, &d); + EVP_PKEY_get_bn_param(rsa, OSSL_PKEY_PARAM_RSA_FACTOR1, &p); + EVP_PKEY_get_bn_param(rsa, OSSL_PKEY_PARAM_RSA_FACTOR2, &q); + EVP_PKEY_get_bn_param(rsa, OSSL_PKEY_PARAM_RSA_EXPONENT1, &dmp1); + EVP_PKEY_get_bn_param(rsa, OSSL_PKEY_PARAM_RSA_EXPONENT2, &dmq1); + EVP_PKEY_get_bn_param(rsa, OSSL_PKEY_PARAM_RSA_COEFFICIENT1, &iqmp); #else RSA_get0_key(rsa, &n, &e, &d); RSA_get0_factors(rsa, &p, &q); RSA_get0_crt_params(rsa, &dmp1, &dmq1, &iqmp); +#endif #endif XPUSHs(cor_bn2sv(n)); XPUSHs(cor_bn2sv(e)); @@ -522,7 +768,11 @@ encrypt(p_rsa, p_plaintext) rsaData* p_rsa; SV* p_plaintext; CODE: +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + RETVAL = rsa_crypt(p_rsa, p_plaintext, EVP_PKEY_encrypt, EVP_PKEY_encrypt_init, 1 /* public */); +#else RETVAL = rsa_crypt(p_rsa, p_plaintext, RSA_public_encrypt); +#endif OUTPUT: RETVAL @@ -535,7 +785,11 @@ decrypt(p_rsa, p_ciphertext) { croak("Public keys cannot decrypt"); } +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + RETVAL = rsa_crypt(p_rsa, p_ciphertext, EVP_PKEY_decrypt, EVP_PKEY_decrypt_init, 0 /* private */); +#else RETVAL = rsa_crypt(p_rsa, p_ciphertext, RSA_private_decrypt); +#endif OUTPUT: RETVAL @@ -548,7 +802,11 @@ private_encrypt(p_rsa, p_plaintext) { croak("Public keys cannot private_encrypt"); } +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + RETVAL = rsa_crypt(p_rsa, p_plaintext, EVP_PKEY_sign, EVP_PKEY_sign_init, 0 /* private */); +#else RETVAL = rsa_crypt(p_rsa, p_plaintext, RSA_private_encrypt); +#endif OUTPUT: RETVAL @@ -557,7 +815,11 @@ public_decrypt(p_rsa, p_ciphertext) rsaData* p_rsa; SV* p_ciphertext; CODE: +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + RETVAL = rsa_crypt(p_rsa, p_ciphertext, EVP_PKEY_verify_recover, EVP_PKEY_verify_recover_init, 1 /*public */); +#else RETVAL = rsa_crypt(p_rsa, p_ciphertext, RSA_public_decrypt); +#endif OUTPUT: RETVAL @@ -565,7 +827,7 @@ int size(p_rsa) rsaData* p_rsa; CODE: - RETVAL = RSA_size(p_rsa->rsa); + RETVAL = EVP_PKEY_get_size(p_rsa->rsa); OUTPUT: RETVAL @@ -577,7 +839,14 @@ check_key(p_rsa) { croak("Public keys cannot be checked"); } +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + EVP_PKEY_CTX *pctx = EVP_PKEY_CTX_new_from_pkey(NULL, p_rsa->rsa, NULL); + CHECK_OPEN_SSL(pctx); + RETVAL = EVP_PKEY_private_check(pctx); + EVP_PKEY_CTX_free(pctx); +#else RETVAL = RSA_check_key(p_rsa->rsa); +#endif OUTPUT: RETVAL @@ -672,7 +941,7 @@ void use_pkcs1_padding(p_rsa) rsaData* p_rsa; CODE: - p_rsa->padding = RSA_PKCS1_PADDING; + croak("PKCS#1 1.5 is disabled as it is known to be vulnerable to marvin attacks."); void use_pkcs1_oaep_padding(p_rsa) @@ -680,6 +949,12 @@ use_pkcs1_oaep_padding(p_rsa) CODE: p_rsa->padding = RSA_PKCS1_OAEP_PADDING; +void +use_pkcs1_pss_padding(p_rsa) + rsaData* p_rsa; + CODE: + p_rsa->padding = RSA_PKCS1_PSS_PADDING; + #if OPENSSL_VERSION_NUMBER < 0x30000000L void @@ -697,26 +972,60 @@ sign(p_rsa, text_SV) rsaData* p_rsa; SV* text_SV; PREINIT: - char* signature; + UNSIGNED_CHAR *signature; unsigned char* digest; - unsigned int signature_length; + unsigned char digest_buf[EVP_MAX_MD_SIZE]; + SIZE_T_UNSIGNED_INT signature_length; CODE: { if (!_is_private(p_rsa)) { croak("Public keys cannot sign messages"); } + CHECK_NEW(signature, EVP_PKEY_get_size(p_rsa->rsa), UNSIGNED_CHAR); + + CHECK_OPEN_SSL(digest = get_message_digest(text_SV, p_rsa->hashMode, digest_buf)); +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + EVP_PKEY_CTX *ctx; + ctx = EVP_PKEY_CTX_new(p_rsa->rsa, NULL /* no engine */); + CHECK_OPEN_SSL(ctx); + CHECK_OPEN_SSL(EVP_PKEY_sign_init(ctx)); + /* FIXME: Issue setting padding in some cases */ + int sign_pad = p_rsa->padding; + if (p_rsa->padding != RSA_NO_PADDING) { + sign_pad = RSA_PKCS1_PSS_PADDING; + } + CHECK_OPEN_SSL(EVP_PKEY_CTX_set_rsa_padding(ctx, sign_pad) > 0); + + EVP_MD* md = get_md_bynid(p_rsa->hashMode); + CHECK_OPEN_SSL(md != NULL); + + int md_status; + CHECK_OPEN_SSL((md_status = EVP_PKEY_CTX_set_signature_md(ctx, md)) > 0); + if (p_rsa->padding == RSA_PKCS1_PSS_PADDING) { + CHECK_OPEN_SSL((md_status = EVP_PKEY_CTX_set_rsa_mgf1_md(ctx, md)) > 0); + CHECK_OPEN_SSL(EVP_PKEY_CTX_set_rsa_pss_saltlen(ctx, RSA_PSS_SALTLEN_DIGEST) > 0); + } + CHECK_OPEN_SSL(EVP_PKEY_sign(ctx, NULL, &signature_length, digest, get_digest_length(p_rsa->hashMode)) == 1); - CHECK_NEW(signature, RSA_size(p_rsa->rsa), char); + //signature = OPENSSL_malloc(signature_length); + Newx(signature, signature_length, char); - CHECK_OPEN_SSL(digest = get_message_digest(text_SV, p_rsa->hashMode)); + CHECK_OPEN_SSL(signature); + + CHECK_OPEN_SSL(EVP_PKEY_sign(ctx, signature, &signature_length, digest, get_digest_length(p_rsa->hashMode)) == 1); + CHECK_OPEN_SSL(signature); + EVP_MD_free(md); + EVP_PKEY_CTX_free(ctx); +#else CHECK_OPEN_SSL(RSA_sign(p_rsa->hashMode, digest, get_digest_length(p_rsa->hashMode), (unsigned char*) signature, &signature_length, p_rsa->rsa)); - RETVAL = newSVpvn(signature, signature_length); +#endif + RETVAL = newSVpvn((const char* )signature, signature_length); Safefree(signature); } OUTPUT: @@ -733,21 +1042,46 @@ PPCODE: { unsigned char* sig; unsigned char* digest; + unsigned char digest_buf[EVP_MAX_MD_SIZE]; STRLEN sig_length; sig = (unsigned char*) SvPV(sig_SV, sig_length); - if (RSA_size(p_rsa->rsa) < sig_length) + if (EVP_PKEY_get_size(p_rsa->rsa) < sig_length) { croak("Signature longer than key"); } - CHECK_OPEN_SSL(digest = get_message_digest(text_SV, p_rsa->hashMode)); + CHECK_OPEN_SSL(digest = get_message_digest(text_SV, p_rsa->hashMode, digest_buf)); +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + EVP_PKEY_CTX *ctx; + ctx = EVP_PKEY_CTX_new(p_rsa->rsa, NULL /* no engine */); + CHECK_OPEN_SSL(ctx); + CHECK_OPEN_SSL(EVP_PKEY_verify_init(ctx) == 1); + /* FIXME: Issue setting padding in some cases */ + int verify_pad = p_rsa->padding; + if (p_rsa->padding != RSA_NO_PADDING) { + verify_pad = RSA_PKCS1_PSS_PADDING; + } + CHECK_OPEN_SSL(EVP_PKEY_CTX_set_rsa_padding(ctx, verify_pad) > 0); + EVP_MD* md = get_md_bynid(p_rsa->hashMode); + CHECK_OPEN_SSL(md != NULL); + + int md_status; + CHECK_OPEN_SSL((md_status = EVP_PKEY_CTX_set_signature_md(ctx, md)) > 0); + if (p_rsa->padding == RSA_PKCS1_PSS_PADDING) { + CHECK_OPEN_SSL((md_status = EVP_PKEY_CTX_set_rsa_mgf1_md(ctx, md)) > 0); + CHECK_OPEN_SSL(EVP_PKEY_CTX_set_rsa_pss_saltlen(ctx, RSA_PSS_SALTLEN_DIGEST) > 0); + } + + switch (EVP_PKEY_verify(ctx, sig, sig_length, digest, get_digest_length(p_rsa->hashMode))) +#else switch(RSA_verify(p_rsa->hashMode, digest, get_digest_length(p_rsa->hashMode), sig, sig_length, p_rsa->rsa)) +#endif { case 0: ERR_clear_error(); @@ -760,6 +1094,10 @@ PPCODE: CHECK_OPEN_SSL(0); break; } +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + EVP_MD_free(md); + EVP_PKEY_CTX_free(ctx); +#endif } int diff --git a/t/bignum.t b/t/bignum.t index 77691db..68776d9 100644 --- a/t/bignum.t +++ b/t/bignum.t @@ -48,7 +48,7 @@ sub check_key_parameters # runs 8 tests $rsa->use_no_padding(); - my $plaintext = pack( 'C*', 100, 100, 100, 12 ); + my $plaintext = pack( 'C*', 100, 100, 100, 12 ); my $ciphertext = Crypt::OpenSSL::Bignum->new_from_bin($plaintext)->mod_exp( $e, $n, $ctx )->to_bin(); check_key_parameters( $rsa, $n, $e, $d, $p, $q, $dmp1, $dmq1, $iqmp ); diff --git a/t/format.t b/t/format.t index 214302f..ec3cbda 100644 --- a/t/format.t +++ b/t/format.t @@ -79,12 +79,12 @@ ok( $PUBLIC_KEY_PKCS1_STRING eq $public_key->get_public_key_string() ); ok( $PUBLIC_KEY_X509_STRING eq $public_key->get_public_key_x509_string() ); my $passphase = '123456'; -ok( $private_key = Crypt::OpenSSL::RSA->new_private_key($ENCRYPT_PRIVATE_KEY_STRING, $passphase) ); +ok( $private_key = Crypt::OpenSSL::RSA->new_private_key( $ENCRYPT_PRIVATE_KEY_STRING, $passphase ) ); ok( $DECRYPT_PRIVATE_KEY_STRING eq $private_key->get_private_key_string() ); -ok( $private_key = Crypt::OpenSSL::RSA->new_private_key($DECRYPT_PRIVATE_KEY_STRING) ); -ok( $private_key2 = Crypt::OpenSSL::RSA->new_private_key($private_key->get_private_key_string($passphase), $passphase) ); +ok( $private_key = Crypt::OpenSSL::RSA->new_private_key($DECRYPT_PRIVATE_KEY_STRING) ); +ok( $private_key2 = Crypt::OpenSSL::RSA->new_private_key( $private_key->get_private_key_string($passphase), $passphase ) ); ok( $DECRYPT_PRIVATE_KEY_STRING eq $private_key2->get_private_key_string() ); -ok( $private_key2 = Crypt::OpenSSL::RSA->new_private_key($private_key->get_private_key_string($passphase, 'des3'), $passphase) ); +ok( $private_key2 = Crypt::OpenSSL::RSA->new_private_key( $private_key->get_private_key_string( $passphase, 'des3' ), $passphase ) ); +ok( $DECRYPT_PRIVATE_KEY_STRING eq $private_key2->get_private_key_string() ); +ok( $private_key2 = Crypt::OpenSSL::RSA->new_private_key( $private_key->get_private_key_string( $passphase, 'aes-128-cbc' ), $passphase ) ); ok( $DECRYPT_PRIVATE_KEY_STRING eq $private_key2->get_private_key_string() ); -ok( $private_key2 = Crypt::OpenSSL::RSA->new_private_key($private_key->get_private_key_string($passphase, 'aes-128-cbc'), $passphase) ); -ok( $DECRYPT_PRIVATE_KEY_STRING eq $private_key2->get_private_key_string() ); \ No newline at end of file diff --git a/t/padding.t b/t/padding.t new file mode 100644 index 0000000..092f50c --- /dev/null +++ b/t/padding.t @@ -0,0 +1,160 @@ +use strict; +use Test::More; + +use Crypt::OpenSSL::Random; +use Crypt::OpenSSL::RSA; +use Crypt::OpenSSL::Guess qw(openssl_version); + +my ($major, $minor, $patch) = openssl_version; + +BEGIN { + plan tests => 87 + ( UNIVERSAL::can( "Crypt::OpenSSL::RSA", "use_sha512_hash" ) ? 4 * 5 : 0 ); +} + +sub _Test_Encrypt_And_Decrypt { + my ( $p_plaintext_length, $p_rsa, $p_check_private_encrypt, $padding, $hash ) = @_; + + my ( $ciphertext, $decoded_text ); + my $plaintext = pack( + "C${p_plaintext_length}", + ( + 1, 255, 0, 128, 4, # Make sure these characters work + map { int( rand 256 ) } ( 1 .. $p_plaintext_length - 5 ) + ) + ); + ok( $ciphertext = $p_rsa->encrypt($plaintext), "Padding method $padding is valid for encrypting with $hash" ); + ok( $decoded_text = $p_rsa->decrypt($ciphertext), "Padding method $padding is valid for decrypting with $hash" ); + ok( $decoded_text eq $plaintext ); + + if ($p_check_private_encrypt) { + ok( $ciphertext = $p_rsa->private_encrypt($plaintext), "Padding method $padding is valid for private_encrypt with $hash" ); + ok( $decoded_text = $p_rsa->public_decrypt($ciphertext), "Padding method $padding is valid for private_decrypt with $hash" ); + ok( $decoded_text eq $plaintext ); + } +} + +sub _Test_Sign_And_Verify { + my ( $p_plaintext_length, $rsa, $rsa_pub, $padding, $hash ) = @_; + + my $plaintext = pack( + "C${p_plaintext_length}", + ( + 1, 255, 0, 128, 4, # Make sure these characters work + map { int( rand 256 ) } ( 1 .. $p_plaintext_length - 5 ) + ) + ); + + my $sig = eval { $rsa->sign($plaintext) }; + + SKIP: { + skip "OpenSSL error: illegal or unsupported padding mode - $hash", 6 if $@ =~ /illegal or unsupported padding mode/i; + skip "OpenSSL error: invalid digest - $hash", 6 if $@ =~ /invalid digest/i; + ok(!$@, "Padding method $padding is valid for signing with $hash"); + ok( $rsa_pub->verify( $plaintext, $sig ), "Padding method $padding is valid for verifying with $hash"); + + my $false_sig = unpack "H*", $sig; + $false_sig =~ tr/[a-f]/[0a-d]/; + ok( !$rsa_pub->verify( $plaintext, pack( "H*", $false_sig )), "rsa_pub: False signature does not verify"); + ok( !$rsa->verify( $plaintext, pack( "H*", $false_sig )), "rsa: False signature does not verify"); + + my $sig_of_other = $rsa->sign("different"); + ok( !$rsa_pub->verify( $plaintext, $sig_of_other ), "rsa_pub: plaintext does not match signature" ); + ok( !$rsa->verify( $plaintext, $sig_of_other ), "rsa: plaintext does not match signature"); + } +} + +Crypt::OpenSSL::Random::random_seed("OpenSSL needs at least 32 bytes."); +Crypt::OpenSSL::RSA->import_random_seed(); + +my $rsa = Crypt::OpenSSL::RSA->generate_key(2048); +ok( $rsa->size() * 8 == 2048); +ok( $rsa->check_key() ); + +my $private_key_string = $rsa->get_private_key_string(); +my $public_key_string = $rsa->get_public_key_string(); + +ok( $private_key_string and $public_key_string ); + +my $plaintext = "The quick brown fox jumped over the lazy dog"; +my $rsa_priv = Crypt::OpenSSL::RSA->new_private_key($private_key_string); +ok( $plaintext eq $rsa_priv->decrypt( $rsa_priv->encrypt($plaintext) ) ); + +my $rsa_pub = Crypt::OpenSSL::RSA->new_public_key($public_key_string); + +my @unsupported_paddings = qw/pkcs1 sslv23/; + +$plaintext .= $plaintext x 5; +# pkcs1 sslv23 are unsupported methods +foreach my $pad (@unsupported_paddings) { + my $method = "use_${pad}_padding"; + SKIP: { + skip "OpenSSL version less than 3.0 supports sslv23", 1 + if $major lt '3.0' && $pad eq 'sslv23'; + eval { + $rsa->$method; + }; + ok($@, "Padding method $pad unsupported"); + } +} + +my @supported_paddings = qw/no pkcs1_pss pkcs1_oaep/; +# no pkcs1_pss pkcs1_oaep are supported methods +foreach my $pad (@supported_paddings) { + my $method = "use_${pad}_padding"; + eval { + $rsa->$method; + }; + ok(!$@, "Padding method $pad supported"); +} + +my @hashes = qw/md5 sha1 sha224 sha256 sha384 sha512 ripemd160/; # whirlpool/; + +my %padding_methods = ( + 'no' => {'sign' => 1, 'encrypt' => 1, 'pad' => 0}, + 'pkcs1_pss' => {'sign' => 1, 'encrypt' => 0, 'pad' => 1}, + 'pkcs1_oaep' => {'sign' => 0, 'encrypt' => 1, 'pad' => 42}, + 'pkcs1' => {'sign' => 0, 'encrypt' => 0, 'pad' => 11}, + #'sslv23' => {'sign' => 0, 'encrypt' => 0, 'pad' => 11}, + ); + + +foreach my $padding (keys %padding_methods) { + diag $padding; + foreach my $hash (@hashes) { + next if $hash ne 'sha256' && $padding eq 'x931'; + my $props = $padding_methods{$padding}; + my $sign = $props->{sign}; + my $encrypt = $props->{encrypt}; + my $pad = $props->{pad}; + + my $hash_mth = "use_${hash}_hash"; + $rsa->$hash_mth; + $rsa_pub->$hash_mth; + my $method = "use_${padding}_padding"; + if ($sign || $encrypt ) { + $rsa->$method; + $rsa_pub->$method; + } + # Valid signing methods + if ($sign && $pad) { + _Test_Sign_And_Verify( $rsa->size() - $pad, $rsa, $rsa_pub, $padding, $hash ); + } + + # Invalid signing methods + if ((!$sign) && $pad) { + eval { + $rsa->$method; + $rsa->sign($plaintext); + }; + ok(defined $@, "Padding $padding is invalid for signing"); + } + + # Valid encryption methods with padding + if ($encrypt) { + _Test_Encrypt_And_Decrypt( $rsa->size() - $pad, $rsa, 0, $padding, $hash ); + } + + } +} + +# Try diff --git a/t/rsa.t b/t/rsa.t index d3e7f0b..83942f9 100644 --- a/t/rsa.t +++ b/t/rsa.t @@ -3,8 +3,11 @@ use Test::More; use Crypt::OpenSSL::Random; use Crypt::OpenSSL::RSA; +use Crypt::OpenSSL::Guess qw(openssl_version); -BEGIN { plan tests => 43 + ( UNIVERSAL::can( "Crypt::OpenSSL::RSA", "use_sha512_hash" ) ? 4 * 5 : 0 ) } +BEGIN { + plan tests => 67 + ( UNIVERSAL::can( "Crypt::OpenSSL::RSA", "use_sha512_hash" ) ? 4 * 5 : 0 ) + ( UNIVERSAL::can( "Crypt::OpenSSL::RSA", "use_whirlpool_hash" ) ? 1 * 5 : 0 ); +} sub _Test_Encrypt_And_Decrypt { my ( $p_plaintext_length, $p_rsa, $p_check_private_encrypt ) = @_; @@ -29,19 +32,23 @@ sub _Test_Encrypt_And_Decrypt { } sub _Test_Sign_And_Verify { - my ( $plaintext, $rsa, $rsa_pub ) = @_; - - my $sig = $rsa->sign($plaintext); - ok( $rsa_pub->verify( $plaintext, $sig ) ); - - my $false_sig = unpack "H*", $sig; - $false_sig =~ tr/[a-f]/[0a-d]/; - ok( !$rsa_pub->verify( $plaintext, pack( "H*", $false_sig ) ) ); - ok( !$rsa->verify( $plaintext, pack( "H*", $false_sig ) ) ); - - my $sig_of_other = $rsa->sign("different"); - ok( !$rsa_pub->verify( $plaintext, $sig_of_other ) ); - ok( !$rsa->verify( $plaintext, $sig_of_other ) ); + my ( $plaintext, $rsa, $rsa_pub, $hash ) = @_; + + my $sig = eval { $rsa->sign($plaintext) }; + SKIP: { + skip "OpenSSL error: illegal or unsupported padding mode - $hash", 5 if $@ =~ /illegal or unsupported padding mode/i; + skip "OpenSSL error: invalid digest - $hash", 5 if $@ =~ /invalid digest/i; + ok( $rsa_pub->verify( $plaintext, $sig ), "rsa_pub verify $hash"); + + my $false_sig = unpack "H*", $sig; + $false_sig =~ tr/[a-f]/[0a-d]/; + ok( !$rsa_pub->verify( $plaintext, pack( "H*", $false_sig ) ), "rsa_pub do not verify invalid $hash" ); + ok( !$rsa->verify( $plaintext, pack( "H*", $false_sig ) ), "rsa do not verify invalid $hash" ); + + my $sig_of_other = $rsa->sign("different"); + ok( !$rsa_pub->verify( $plaintext, $sig_of_other ), "rsa_pub do not verify unmatching message" ); + ok( !$rsa->verify( $plaintext, $sig_of_other ), "rsa do not verify unmatching message"); + } } sub _check_for_croak { @@ -63,16 +70,13 @@ Crypt::OpenSSL::RSA->import_random_seed(); ok( Crypt::OpenSSL::RSA->generate_key(512)->size() * 8 == 512 ); -my $rsa = Crypt::OpenSSL::RSA->generate_key(1024); -ok( $rsa->size() * 8 == 1024 ); +my $rsa = Crypt::OpenSSL::RSA->generate_key(2048); +ok( $rsa->size() * 8 == 2048 ); ok( $rsa->check_key() ); $rsa->use_no_padding(); _Test_Encrypt_And_Decrypt( $rsa->size(), $rsa, 1 ); -$rsa->use_pkcs1_padding(); -_Test_Encrypt_And_Decrypt( $rsa->size() - 11, $rsa, 1 ); - $rsa->use_pkcs1_oaep_padding(); # private_encrypt does not work with pkcs1_oaep_padding @@ -118,41 +122,56 @@ _check_for_croak( $plaintext .= $plaintext x 5; -# check signature algorithms -$rsa->use_md5_hash(); -$rsa_pub->use_md5_hash(); -_Test_Sign_And_Verify( $plaintext, $rsa, $rsa_pub ); +my @paddings = qw/pkcs1_oaep pkcs1_pss/; +foreach my $padding (@paddings) { + my $p = "use_${padding}_padding"; + + $rsa->$p; + $rsa_pub->$p; + # check signature algorithms + $rsa->use_md5_hash(); + $rsa_pub->use_md5_hash(); + _Test_Sign_And_Verify( $plaintext, $rsa, $rsa_pub, "md5 with $padding padding" ); -$rsa->use_sha1_hash(); -$rsa_pub->use_sha1_hash(); -_Test_Sign_And_Verify( $plaintext, $rsa, $rsa_pub ); + $rsa->use_sha1_hash(); + $rsa_pub->use_sha1_hash(); + _Test_Sign_And_Verify( $plaintext, $rsa, $rsa_pub, "sha1 with $padding padding" ); -if ( UNIVERSAL::can( "Crypt::OpenSSL::RSA", "use_sha512_hash" ) ) { + if ( UNIVERSAL::can( "Crypt::OpenSSL::RSA", "use_sha512_hash" ) ) { $rsa->use_sha224_hash(); $rsa_pub->use_sha224_hash(); - _Test_Sign_And_Verify( $plaintext, $rsa, $rsa_pub ); + _Test_Sign_And_Verify( $plaintext, $rsa, $rsa_pub, "sha224 with $padding padding" ); $rsa->use_sha256_hash(); $rsa_pub->use_sha256_hash(); - _Test_Sign_And_Verify( $plaintext, $rsa, $rsa_pub ); + _Test_Sign_And_Verify( $plaintext, $rsa, $rsa_pub, "sha256 with $padding padding" ); $rsa->use_sha384_hash(); $rsa_pub->use_sha384_hash(); - _Test_Sign_And_Verify( $plaintext, $rsa, $rsa_pub ); + _Test_Sign_And_Verify( $plaintext, $rsa, $rsa_pub, "sha384 with $padding padding" ); $rsa->use_sha512_hash(); $rsa_pub->use_sha512_hash(); - _Test_Sign_And_Verify( $plaintext, $rsa, $rsa_pub ); + _Test_Sign_And_Verify( $plaintext, $rsa, $rsa_pub, "sha512 with $padding padding" ); + } } -$rsa->use_ripemd160_hash(); -$rsa_pub->use_ripemd160_hash(); -_Test_Sign_And_Verify( $plaintext, $rsa, $rsa_pub ); +my ( $major, $minor, $patch ) = openssl_version(); + +SKIP: { + skip "ripemd160 in legacy provider between 3.02 and 3.07", 5 + if $major eq '3.0' + && ( $minor ge '2' && $minor le '6' ); + + $rsa->use_ripemd160_hash(); + $rsa_pub->use_ripemd160_hash(); + _Test_Sign_And_Verify( $plaintext, $rsa, $rsa_pub, "ripemd160" ); +} -if (UNIVERSAL::can("Crypt::OpenSSL::RSA", "use_whirlpool_hash")) { +if ( UNIVERSAL::can( "Crypt::OpenSSL::RSA", "use_whirlpool_hash" ) ) { $rsa->use_whirlpool_hash(); $rsa_pub->use_whirlpool_hash(); - _Test_Sign_And_Verify($plaintext, $rsa, $rsa_pub); + _Test_Sign_And_Verify( $plaintext, $rsa, $rsa_pub, "whirlpool" ); } # check subclassing