Crypt-OpenSSL-RSA-0.41/0000755000175000017500000000000015172615445012621 5ustar timtimCrypt-OpenSSL-RSA-0.41/CONTRIBUTING.md0000644000175000017500000000173315165307422015051 0ustar timtim# Contributing to Crypt::OpenSSL::RSA Thank you for your interest in contributing! ## Getting Started ```bash # Install build dependencies cpanm --notest Crypt::OpenSSL::Guess Crypt::OpenSSL::Random # Build and test perl Makefile.PL && make && make test ``` ## Reporting Bugs Please open an issue at https://github.com/cpan-authors/Crypt-OpenSSL-RSA/issues with: - Your Perl version (`perl -v`) - Your OpenSSL version (`openssl version`) - A minimal reproducing script ## Submitting Changes 1. Fork the repository 2. Create a feature branch 3. Write tests for your changes 4. Run the full test suite (`make test`) 5. Submit a pull request ## Code Style - Follow existing conventions in the codebase - XS changes must compile cleanly on OpenSSL 1.0.x, 1.1.x, 3.x, and LibreSSL - Use preprocessor conditionals to handle version differences (see `RSA.xs`) ## Security Issues For security vulnerabilities, please see [SECURITY.md](SECURITY.md) instead of opening a public issue. Crypt-OpenSSL-RSA-0.41/t/0000755000175000017500000000000015172615445013064 5ustar timtimCrypt-OpenSSL-RSA-0.41/t/z_perl_minimum_version.t0000644000175000017500000000132014606636707020045 0ustar timtim# -*- perl -*- # Test that our declared minimum Perl version matches our syntax use strict; BEGIN { $| = 1; $^W = 1; } my @MODULES = ( 'Perl::MinimumVersion 1.20', 'Test::MinimumVersion 0.008', ); # Don't run tests during end-user installs use Test::More; unless ( -d '.git' || $ENV{IS_MAINTAINER} ) { plan( skip_all => "Author tests not required for installation" ); } # Load the testing modules foreach my $MODULE (@MODULES) { eval "use $MODULE"; if ($@) { plan( skip_all => "$MODULE not available for testing" ); die "Failed to load required release-testing module $MODULE" if -d '.git' || $ENV{IS_MAINTAINER}; } } all_minimum_version_ok("5.006"); 1; Crypt-OpenSSL-RSA-0.41/t/pss_auto_promote.t0000644000175000017500000000455715165307422016661 0ustar timtimuse strict; use Test::More; use Crypt::OpenSSL::Random; use Crypt::OpenSSL::RSA; use Crypt::OpenSSL::Guess qw(openssl_version); my ($major) = openssl_version; if ($major lt '3.0') { plan skip_all => 'PSS auto-promote only applies to OpenSSL 3.x'; } plan tests => 6; 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); my $pub_key_str = $rsa->get_public_key_string(); my $rsa_pub = Crypt::OpenSSL::RSA->new_public_key($pub_key_str); my $plaintext = "The quick brown fox jumped over the lazy dog"; # Test 1-2: OAEP auto-promotes to PSS for signing — basic round-trip # When OAEP is set, sign() auto-promotes to PSS internally. # The bug: mgf1_md and saltlen are NOT configured for the auto-promoted case # because the check uses p_rsa->padding (still OAEP) instead of sign_pad (PSS). $rsa->use_pkcs1_oaep_padding; $rsa->use_sha256_hash; $rsa_pub->use_pkcs1_oaep_padding; $rsa_pub->use_sha256_hash; my $sig_oaep = $rsa->sign($plaintext); ok(defined $sig_oaep, "sign with OAEP padding succeeds (auto-promotes to PSS)"); ok($rsa_pub->verify($plaintext, $sig_oaep), "verify with OAEP padding matches (both use same auto-promoted params)"); # Test 3-4: Cross-verification — sign with OAEP, verify with explicit PSS # This is the real test. If mgf1/saltlen setup is skipped for auto-promoted PSS, # the signature uses OpenSSL defaults (MGF1=SHA-1, saltlen=max). # Explicit PSS sets MGF1=SHA-256, saltlen=digest_length. # With the bug, these differ — cross-verification fails. $rsa->use_pkcs1_oaep_padding; $rsa->use_sha256_hash; my $sig_from_oaep = $rsa->sign($plaintext); ok(defined $sig_from_oaep, "sign with OAEP+SHA256 produces signature"); $rsa_pub->use_pkcs1_pss_padding; $rsa_pub->use_sha256_hash; ok($rsa_pub->verify($plaintext, $sig_from_oaep), "OAEP-signed message verifies with explicit PSS (mgf1/saltlen must match)"); # Test 5-6: Reverse — sign with explicit PSS, verify with OAEP (auto-promoted) $rsa->use_pkcs1_pss_padding; $rsa->use_sha256_hash; my $sig_from_pss = $rsa->sign($plaintext); ok(defined $sig_from_pss, "sign with explicit PSS+SHA256 produces signature"); $rsa_pub->use_pkcs1_oaep_padding; $rsa_pub->use_sha256_hash; ok($rsa_pub->verify($plaintext, $sig_from_pss), "PSS-signed message verifies with OAEP padding (auto-promoted PSS must match)"); Crypt-OpenSSL-RSA-0.41/t/format.t0000644000175000017500000003234115172443224014536 0ustar timtimuse strict; use Test::More; use File::Temp qw(tempfile); use Crypt::OpenSSL::RSA; use Crypt::OpenSSL::Guess qw(openssl_version); my ($major, $minor, $patch) = openssl_version(); BEGIN { plan tests => 56 } my $PRIVATE_KEY_STRING = <new_private_key($PRIVATE_KEY_STRING), "load private key from string" ); is( $private_key->get_private_key_string(), $PRIVATE_KEY_STRING, "private key round-trips" ); is( $private_key->get_public_key_string(), $PUBLIC_KEY_PKCS1_STRING, "PKCS1 public key matches expected" ); is( $private_key->get_public_key_x509_string(), $PUBLIC_KEY_X509_STRING, "X509 public key matches expected" ); ok( $public_key = Crypt::OpenSSL::RSA->new_public_key($PUBLIC_KEY_PKCS1_STRING), "load PKCS1 public key" ); is( $public_key->get_public_key_string(), $PUBLIC_KEY_PKCS1_STRING, "PKCS1 public key round-trips" ); is( $public_key->get_public_key_x509_string(), $PUBLIC_KEY_X509_STRING, "PKCS1 key exports to X509 correctly" ); ok( $public_key = Crypt::OpenSSL::RSA->new_public_key($PUBLIC_KEY_X509_STRING), "load X509 public key" ); is( $public_key->get_public_key_string(), $PUBLIC_KEY_PKCS1_STRING, "X509 key exports to PKCS1 correctly" ); is( $public_key->get_public_key_x509_string(), $PUBLIC_KEY_X509_STRING, "X509 public key round-trips" ); # get_public_key_pkcs1_string is an alias for get_public_key_string is( $private_key->get_public_key_pkcs1_string(), $PUBLIC_KEY_PKCS1_STRING, "get_public_key_pkcs1_string returns PKCS1 from private key" ); is( $public_key->get_public_key_pkcs1_string(), $PUBLIC_KEY_PKCS1_STRING, "get_public_key_pkcs1_string returns PKCS1 from public key" ); is( $private_key->get_public_key_pkcs1_string(), $private_key->get_public_key_string(), "pkcs1 alias matches get_public_key_string on private key" ); ok( $public_key = Crypt::OpenSSL::RSA->new_public_key($private_key->get_public_key_pkcs1_string()), "new_public_key accepts output of get_public_key_pkcs1_string" ); my $passphrase = '123456'; ok( $private_key = Crypt::OpenSSL::RSA->new_private_key( $ENCRYPT_PRIVATE_KEY_STRING, $passphrase ), "load encrypted private key" ); is( $private_key->get_private_key_string(), $DECRYPT_PRIVATE_KEY_STRING, "encrypted key decrypts to expected private key" ); ok( $private_key = Crypt::OpenSSL::RSA->new_private_key($DECRYPT_PRIVATE_KEY_STRING), "load decrypted private key" ); ok( $private_key2 = Crypt::OpenSSL::RSA->new_private_key( $private_key->get_private_key_string($passphrase), $passphrase ), "re-encrypt and reload with passphrase" ); is( $private_key2->get_private_key_string(), $DECRYPT_PRIVATE_KEY_STRING, "re-encrypted key round-trips" ); ok( $private_key2 = Crypt::OpenSSL::RSA->new_private_key( $private_key->get_private_key_string( $passphrase, 'des3' ), $passphrase ), "encrypt with des3 and reload" ); is( $private_key2->get_private_key_string(), $DECRYPT_PRIVATE_KEY_STRING, "des3-encrypted key round-trips" ); ok( $private_key2 = Crypt::OpenSSL::RSA->new_private_key( $private_key->get_private_key_string( $passphrase, 'aes-128-cbc' ), $passphrase ), "encrypt with aes-128-cbc and reload" ); is( $private_key2->get_private_key_string(), $DECRYPT_PRIVATE_KEY_STRING, "aes-128-cbc-encrypted key round-trips" ); # --- Additional cipher algorithms --- ok( $private_key2 = Crypt::OpenSSL::RSA->new_private_key( $private_key->get_private_key_string( $passphrase, 'aes-256-cbc' ), $passphrase ), "encrypt with aes-256-cbc and reload" ); is( $private_key2->get_private_key_string(), $DECRYPT_PRIVATE_KEY_STRING, "aes-256-cbc-encrypted key round-trips" ); ok( $private_key2 = Crypt::OpenSSL::RSA->new_private_key( $private_key->get_private_key_string( $passphrase, 'aes-192-cbc' ), $passphrase ), "encrypt with aes-192-cbc and reload" ); is( $private_key2->get_private_key_string(), $DECRYPT_PRIVATE_KEY_STRING, "aes-192-cbc-encrypted key round-trips" ); # --- Passphrase with special characters --- my $special_pass = q{p@ss!w0rd#$%^&*()}; ok( $private_key2 = Crypt::OpenSSL::RSA->new_private_key( $private_key->get_private_key_string($special_pass), $special_pass ), "passphrase with special characters round-trips" ); is( $private_key2->get_private_key_string(), $DECRYPT_PRIVATE_KEY_STRING, "special-char passphrase key decrypts correctly" ); # --- Error: cipher specified without passphrase --- eval { $private_key->get_private_key_string(undef, 'des3') }; like($@, qr/Passphrase is required for cipher/, "get_private_key_string croaks when cipher given without passphrase"); # --- Error: unsupported cipher name --- eval { $private_key->get_private_key_string($passphrase, 'bogus-cipher-xyz') }; like($@, qr/Unsupported cipher/, "get_private_key_string croaks on unsupported cipher"); # --- Error: export private key from public-only key --- my $pub_only = Crypt::OpenSSL::RSA->new_public_key($PUBLIC_KEY_PKCS1_STRING); eval { $pub_only->get_private_key_string() }; like($@, qr/Public keys cannot export private key strings/, "get_private_key_string croaks on public-only key"); # --- Error: wrong passphrase on re-import --- my $encrypted_pem = $private_key->get_private_key_string($passphrase, 'aes-128-cbc'); eval { Crypt::OpenSSL::RSA->new_private_key($encrypted_pem, 'wrong_passphrase') }; ok($@, "new_private_key croaks on wrong passphrase"); # --- Error: garbage / truncated private key input --- eval { Crypt::OpenSSL::RSA->new_private_key("not a valid PEM key\n") }; ok( $@, "new_private_key croaks on garbage input" ); eval { Crypt::OpenSSL::RSA->new_private_key("-----BEGIN RSA PRIVATE KEY-----\ngarbage\n-----END RSA PRIVATE KEY-----\n") }; ok( $@, "new_private_key croaks on truncated PEM" ); # --- Public key format detection --- eval { Crypt::OpenSSL::RSA->new_public_key("-----BEGIN CERTIFICATE-----\nfoo\n-----END CERTIFICATE-----\n") }; like($@, qr/unrecognized key format/, "new_public_key croaks on certificate PEM header"); eval { Crypt::OpenSSL::RSA->new_public_key("not a PEM key at all") }; like($@, qr/unrecognized key format/, "new_public_key croaks on non-PEM input"); # --- PKCS#8 private key export --- { my $rsa = Crypt::OpenSSL::RSA->new_private_key($DECRYPT_PRIVATE_KEY_STRING); my $pkcs8_pem = $rsa->get_private_key_pkcs8_string(); like($pkcs8_pem, qr/^-----BEGIN PRIVATE KEY-----/m, "PKCS#8 output has correct header"); like($pkcs8_pem, qr/-----END PRIVATE KEY-----\s*$/m, "PKCS#8 output has correct footer"); unlike($pkcs8_pem, qr/BEGIN RSA PRIVATE KEY/, "PKCS#8 output is not PKCS#1 format"); # encrypted PKCS#8 export my $pass = 'test_pkcs8_pass'; my $enc_pem = $rsa->get_private_key_pkcs8_string($pass, 'aes-128-cbc'); like($enc_pem, qr/^-----BEGIN ENCRYPTED PRIVATE KEY-----/m, "encrypted PKCS#8 has correct header"); # Round-trip tests require new_private_key to read PKCS#8. On pre-3.x # PEM_read_bio_PrivateKey is macro'd to PEM_read_bio_RSAPrivateKey which # only reads PKCS#1, so these must be skipped. SKIP: { skip "new_private_key cannot read PKCS#8 on OpenSSL < 3.x", 3 if $major < 3; my $reimported = Crypt::OpenSSL::RSA->new_private_key($pkcs8_pem); is($reimported->get_private_key_string(), $DECRYPT_PRIVATE_KEY_STRING, "PKCS#8 round-trip: re-import then export as PKCS#1 matches original"); is($reimported->get_private_key_pkcs8_string(), $pkcs8_pem, "PKCS#8 round-trip: re-export as PKCS#8 matches"); my $dec_rsa = Crypt::OpenSSL::RSA->new_private_key($enc_pem, $pass); is($dec_rsa->get_private_key_string(), $DECRYPT_PRIVATE_KEY_STRING, "encrypted PKCS#8 round-trip decrypts to original key"); } # error: cipher without passphrase eval { $rsa->get_private_key_pkcs8_string(undef, 'des3') }; like($@, qr/Passphrase is required for cipher/, "get_private_key_pkcs8_string croaks when cipher given without passphrase"); # error: unsupported cipher eval { $rsa->get_private_key_pkcs8_string($pass, 'bogus-cipher-xyz') }; like($@, qr/Unsupported cipher/, "get_private_key_pkcs8_string croaks on unsupported cipher"); } # --- X509 public key from private key matches PKCS1 --- my $priv_for_x509 = Crypt::OpenSSL::RSA->new_private_key($PRIVATE_KEY_STRING); ok( $public_key = Crypt::OpenSSL::RSA->new_public_key($priv_for_x509->get_public_key_x509_string()), "load X509 public key from private key" ); is( $public_key->get_public_key_string(), $PUBLIC_KEY_PKCS1_STRING, "X509 from private key matches PKCS1" ); # --- Non-RSA key rejection --- # On OpenSSL 3.x, the generic PEM loaders accept any key type. # Verify we reject non-RSA keys with a clear error. SKIP: { my $ec_pem = `openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:prime256v1 2>/dev/null`; skip "EC key generation not available", 4 unless ($? >> 8) == 0 && $ec_pem =~ /-----BEGIN PRIVATE KEY-----/; eval { Crypt::OpenSSL::RSA->new_private_key($ec_pem) }; ok($@, "new_private_key rejects EC private key"); like($@, qr/not an RSA key|expecting an rsa key|ASN1/i, "EC private key error message mentions RSA"); my ($tmpfh, $tmpfile) = tempfile(UNLINK => 1); print $tmpfh $ec_pem; close $tmpfh; my $ec_pub = `openssl pkey -in $tmpfile -pubout 2>/dev/null`; skip "EC public key export failed", 2 unless ($? >> 8) == 0 && $ec_pub =~ /-----BEGIN PUBLIC KEY-----/; eval { Crypt::OpenSSL::RSA->new_public_key($ec_pub) }; ok($@, "new_public_key rejects EC public key"); like($@, qr/not an RSA key|unrecognized key format|ASN1/i, "EC public key gives appropriate error"); } # --- RSA-PSS key rejection --- # EVP_PKEY_get_base_id() returns EVP_PKEY_RSA_PSS for RSA-PSS keys, # which is distinct from EVP_PKEY_RSA. This module only supports # traditional RSA, so RSA-PSS keys should also be rejected. SKIP: { my $rsa_pss_pem = `openssl genpkey -algorithm RSA-PSS -pkeyopt rsa_keygen_bits:2048 2>/dev/null`; skip "RSA-PSS key generation not available", 4 unless ($? >> 8) == 0 && $rsa_pss_pem =~ /-----BEGIN PRIVATE KEY-----/; # On pre-3.x OpenSSL, RSA-PSS keys are loaded via RSA-specific PEM # readers which accept them (they are structurally RSA). The # EVP_PKEY_get_base_id() rejection only exists on OpenSSL 3.x+. eval { Crypt::OpenSSL::RSA->new_private_key($rsa_pss_pem) }; skip "RSA-PSS rejection not supported on this OpenSSL version (pre-3.x)", 4 unless $@; ok(1, "new_private_key rejects RSA-PSS private key"); like($@, qr/not an RSA key|expecting an rsa key|ASN1/i, "RSA-PSS private key error message mentions RSA"); my ($tmpfh, $tmpfile) = tempfile(UNLINK => 1); print $tmpfh $rsa_pss_pem; close $tmpfh; my $rsa_pss_pub = `openssl pkey -in $tmpfile -pubout 2>/dev/null`; skip "RSA-PSS public key export failed", 2 unless ($? >> 8) == 0 && $rsa_pss_pub =~ /-----BEGIN PUBLIC KEY-----/; eval { Crypt::OpenSSL::RSA->new_public_key($rsa_pss_pub) }; ok($@, "new_public_key rejects RSA-PSS public key"); like($@, qr/not an RSA key|unrecognized key format|ASN1/i, "RSA-PSS public key gives appropriate error"); } Crypt-OpenSSL-RSA-0.41/t/z_pod-coverage.t0000644000175000017500000000074414606636707016167 0ustar timtim# -*- perl -*- use strict; use warnings; use Test::More; BEGIN { plan skip_all => 'done_testing requires Test::More 0.88' if Test::More->VERSION < 0.88; } plan skip_all => 'This test is only run for the module author' unless -d '.git' || $ENV{IS_MAINTAINER}; eval "use Test::Pod::Coverage 1.04"; plan skip_all => "Test::Pod::Coverage 1.04 required for testing POD coverage" if $@; for ( all_modules() ) { pod_coverage_ok($_) unless /Filter::decrypt/; } done_testing; Crypt-OpenSSL-RSA-0.41/t/der.t0000644000175000017500000001477115172443224014027 0ustar timtimuse strict; use warnings; use Test::More; use MIME::Base64; use Crypt::OpenSSL::RSA; use File::Temp qw(tempfile); BEGIN { plan tests => 30 } # --- Generate a key pair for testing --- my $rsa = Crypt::OpenSSL::RSA->generate_key(2048); # --- Extract PEM public keys --- my $pkcs1_pem = $rsa->get_public_key_string(); # PKCS#1 (BEGIN RSA PUBLIC KEY) my $x509_pem = $rsa->get_public_key_x509_string(); # X.509 (BEGIN PUBLIC KEY) # --- Convert PEM to DER by stripping headers and base64-decoding --- sub pem_to_der { my ($pem) = @_; $pem =~ s/-----BEGIN [^-]+-----//; $pem =~ s/-----END [^-]+-----//; $pem =~ s/\s+//g; return decode_base64($pem); } my $pkcs1_der = pem_to_der($pkcs1_pem); my $x509_der = pem_to_der($x509_pem); # Sanity check: DER data starts with ASN.1 SEQUENCE tag is( ord(substr($pkcs1_der, 0, 1)), 0x30, "PKCS#1 DER starts with SEQUENCE tag" ); is( ord(substr($x509_der, 0, 1)), 0x30, "X.509 DER starts with SEQUENCE tag" ); # --- Load DER keys via new_public_key --- my ($pub_from_x509_der, $pub_from_pkcs1_der); ok( $pub_from_x509_der = Crypt::OpenSSL::RSA->new_public_key($x509_der), "new_public_key loads X.509 DER key" ); ok( $pub_from_pkcs1_der = Crypt::OpenSSL::RSA->new_public_key($pkcs1_der), "new_public_key loads PKCS#1 DER key" ); # --- Verify round-trip: DER-loaded keys produce the same PEM output --- is( $pub_from_x509_der->get_public_key_x509_string(), $x509_pem, "X.509 DER key exports to same X.509 PEM" ); is( $pub_from_x509_der->get_public_key_string(), $pkcs1_pem, "X.509 DER key exports to same PKCS#1 PEM" ); is( $pub_from_pkcs1_der->get_public_key_x509_string(), $x509_pem, "PKCS#1 DER key exports to same X.509 PEM" ); is( $pub_from_pkcs1_der->get_public_key_string(), $pkcs1_pem, "PKCS#1 DER key exports to same PKCS#1 PEM" ); # --- Verify DER-loaded keys can actually verify signatures --- $rsa->use_sha256_hash(); my $plaintext = "Hello, DER world!"; my $sig = $rsa->sign($plaintext); $pub_from_x509_der->use_sha256_hash(); ok( $pub_from_x509_der->verify($plaintext, $sig), "X.509 DER-loaded key verifies signature" ); $pub_from_pkcs1_der->use_sha256_hash(); ok( $pub_from_pkcs1_der->verify($plaintext, $sig), "PKCS#1 DER-loaded key verifies signature" ); # --- Private key DER support --- my $priv_pem = $rsa->get_private_key_string(); my $priv_der = pem_to_der($priv_pem); is( ord(substr($priv_der, 0, 1)), 0x30, "Private key DER starts with SEQUENCE tag" ); my $priv_from_der; ok( $priv_from_der = Crypt::OpenSSL::RSA->new_private_key($priv_der), "new_private_key loads DER-encoded private key" ); ok( $priv_from_der->is_private(), "DER-loaded private key is recognized as private" ); is( $priv_from_der->get_public_key_x509_string(), $x509_pem, "DER-loaded private key exports same public key" ); # Verify DER-loaded private key can sign and original public key can verify $priv_from_der->use_sha256_hash(); my $sig2 = $priv_from_der->sign($plaintext); ok( $pub_from_x509_der->verify($plaintext, $sig2), "signature from DER-loaded private key verifies" ); # Error: DER-like data for private key eval { Crypt::OpenSSL::RSA->new_private_key("\x30\x00") }; ok( $@, "new_private_key croaks on truncated DER data" ); # Error: bogus binary data for private key eval { Crypt::OpenSSL::RSA->new_private_key("\x01\x02\x03\x04") }; like( $@, qr/unrecognized key format/, "new_private_key gives helpful error on random binary data" ); # PEM private keys still work through the wrapper my $priv_from_pem; ok( $priv_from_pem = Crypt::OpenSSL::RSA->new_private_key($priv_pem), "new_private_key still loads PEM-encoded private key" ); # --- Error cases --- # DER-like data that isn't a valid key (no RSA OID, so falls through to PKCS#1 path) eval { Crypt::OpenSSL::RSA->new_public_key("\x30\x00") }; ok( $@, "new_public_key croaks on truncated DER data" ); # Completely bogus binary data (not starting with 0x30) eval { Crypt::OpenSSL::RSA->new_public_key("\x01\x02\x03\x04") }; like( $@, qr/unrecognized key format/, "new_public_key gives helpful error on random binary data" ); # Empty string eval { Crypt::OpenSSL::RSA->new_public_key("") }; like( $@, qr/unrecognized key format/, "new_public_key gives helpful error on empty string" ); # --- Encrypted PKCS#8 DER private key with passphrase --- my $passphrase = 'test_der_pass'; my $enc_pkcs8_pem = $rsa->get_private_key_pkcs8_string($passphrase, 'aes-128-cbc'); my $enc_pkcs8_der = pem_to_der($enc_pkcs8_pem); is( ord(substr($enc_pkcs8_der, 0, 1)), 0x30, "Encrypted PKCS#8 DER starts with SEQUENCE tag" ); my $priv_from_enc_der; ok( $priv_from_enc_der = Crypt::OpenSSL::RSA->new_private_key($enc_pkcs8_der, $passphrase), "new_private_key loads encrypted PKCS#8 DER with passphrase" ); ok( $priv_from_enc_der->is_private(), "Encrypted PKCS#8 DER-loaded key is private" ); is( $priv_from_enc_der->get_public_key_x509_string(), $x509_pem, "Encrypted PKCS#8 DER key exports same public key as original" ); $priv_from_enc_der->use_sha256_hash(); my $sig3 = $priv_from_enc_der->sign($plaintext); ok( $pub_from_x509_der->verify($plaintext, $sig3), "Signature from encrypted PKCS#8 DER-loaded key verifies" ); eval { Crypt::OpenSSL::RSA->new_private_key($enc_pkcs8_der, 'wrong_pass') }; ok( $@, "new_private_key croaks on wrong passphrase for encrypted PKCS#8 DER" ); # PEM header for wrong type eval { Crypt::OpenSSL::RSA->new_public_key("-----BEGIN CERTIFICATE-----\nfoo\n-----END CERTIFICATE-----\n") }; like( $@, qr/unrecognized key format/, "new_public_key gives helpful error on certificate PEM" ); # --- Non-RSA DER key rejection --- # On OpenSSL 3.x, d2i_PUBKEY_bio() accepts any key type. # _new_public_key_x509_der must reject non-RSA keys. SKIP: { my $ec_pem = `openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:prime256v1 2>/dev/null`; skip "EC key generation not available", 2 unless ($? >> 8) == 0 && $ec_pem =~ /-----BEGIN PRIVATE KEY-----/; my ($tmpfh, $tmpfile) = tempfile(UNLINK => 1); print $tmpfh $ec_pem; close $tmpfh; my $ec_pub_pem = `openssl pkey -in $tmpfile -pubout -outform PEM 2>/dev/null`; skip "EC public key export failed", 2 unless ($? >> 8) == 0 && $ec_pub_pem =~ /-----BEGIN PUBLIC KEY-----/; my $ec_pub_der = pem_to_der($ec_pub_pem); eval { Crypt::OpenSSL::RSA->_new_public_key_x509_der($ec_pub_der) }; ok($@, "_new_public_key_x509_der rejects EC DER key"); like($@, qr/not an RSA key|ASN1|expecting an rsa key/i, "_new_public_key_x509_der gives appropriate error for non-RSA DER key"); } Crypt-OpenSSL-RSA-0.41/t/pkcs1_sign.t0000644000175000017500000000707015172613713015312 0ustar timtimuse strict; use Test::More; use Crypt::OpenSSL::Random; use Crypt::OpenSSL::RSA; use Crypt::OpenSSL::Guess qw(openssl_version find_openssl_prefix find_openssl_exec); my ($major, $minor, $patch) = openssl_version(); my $is_libressl = (`"@{[find_openssl_exec(find_openssl_prefix())]}" version` =~ /LibreSSL/); # Regression tests for PKCS#1 v1.5 signing (RSASSA-PKCS1-v1_5). # Issue #146: PKCS#1 v1.5 was disabled entirely in v0.35 to mitigate # the Marvin attack, but the Marvin attack only affects decryption. # PKCS#1 v1.5 signatures are secure and required by protocols like # ACME/Let's Encrypt (RS256) and many JOSE/JWS implementations. plan tests => 10; 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); my $pub_pem = $rsa->get_public_key_x509_string(); my $rsa_pub = Crypt::OpenSSL::RSA->new_public_key($pub_pem); # --- use_pkcs1_padding() must not croak --- eval { $rsa->use_pkcs1_padding() }; ok(!$@, "use_pkcs1_padding() does not croak"); # --- SHA-256 sign/verify (RS256 — used by ACME/Let's Encrypt) --- { $rsa->use_pkcs1_padding(); $rsa->use_sha256_hash(); $rsa_pub->use_pkcs1_padding(); $rsa_pub->use_sha256_hash(); my $msg = '{"protected":"...","payload":"..."}'; my $sig = $rsa->sign($msg); ok(defined $sig && length($sig) > 0, "PKCS#1 v1.5 + SHA-256 sign produces signature"); ok($rsa_pub->verify($msg, $sig), "PKCS#1 v1.5 + SHA-256 signature verifies"); ok(!$rsa_pub->verify("tampered", $sig), "PKCS#1 v1.5 + SHA-256 rejects tampered message"); } # --- SHA-1 sign/verify (RS1 — legacy but still used) --- SKIP: { $rsa->use_pkcs1_padding(); $rsa->use_sha1_hash(); my $sig = eval { $rsa->sign("sha1 test") }; skip "SHA-1 signing not available on this system", 2 if $@; ok(defined $sig, "PKCS#1 v1.5 + SHA-1 sign produces signature"); $rsa_pub->use_pkcs1_padding(); $rsa_pub->use_sha1_hash(); ok($rsa_pub->verify("sha1 test", $sig), "PKCS#1 v1.5 + SHA-1 signature verifies"); } # --- Cross-padding: sign with PKCS1, verify with PSS must fail --- # On pre-3.x and LibreSSL, RSA_verify ignores the padding mode setting SKIP: { skip "cross-padding test requires OpenSSL 3.x (not LibreSSL)", 1 if $major < 3 || $is_libressl; $rsa->use_pkcs1_padding(); $rsa->use_sha256_hash(); my $sig = $rsa->sign("cross-padding test"); $rsa_pub->use_pkcs1_pss_padding(); $rsa_pub->use_sha256_hash(); my $result = eval { $rsa_pub->verify("cross-padding test", $sig) }; ok(!$result, "PKCS1 signature does not verify with PSS padding"); } # --- Encryption with PKCS1 must still croak (Marvin protection) --- { $rsa->use_pkcs1_padding(); eval { $rsa->encrypt("test") }; like($@, qr/Marvin|vulnerable/i, "PKCS#1 v1.5 encryption still blocked (Marvin)"); } # --- Reload key from PEM and verify signature --- { $rsa->use_pkcs1_padding(); $rsa->use_sha256_hash(); my $sig = $rsa->sign("persistence test"); my $priv_pem = $rsa->get_private_key_string(); my $rsa2 = Crypt::OpenSSL::RSA->new_private_key($priv_pem); $rsa2->use_pkcs1_padding(); $rsa2->use_sha256_hash(); ok($rsa2->verify("persistence test", $sig), "signature verifies after key round-trip through PEM"); my $rsa_pub2 = Crypt::OpenSSL::RSA->new_public_key($pub_pem); $rsa_pub2->use_pkcs1_padding(); $rsa_pub2->use_sha256_hash(); ok($rsa_pub2->verify("persistence test", $sig), "signature verifies with fresh public key object"); } Crypt-OpenSSL-RSA-0.41/t/z_pod.t0000644000175000017500000000037414606636707014375 0ustar timtim# -*- perl -*- use Test::More; plan skip_all => 'This test is only run for the module author' unless -d '.git' || $ENV{IS_MAINTAINER}; eval "use Test::Pod 1.00"; plan skip_all => "Test::Pod 1.00 required for testing POD" if $@; all_pod_files_ok(); Crypt-OpenSSL-RSA-0.41/t/crypto.t0000644000175000017500000001153115165307422014565 0ustar timtimuse strict; use Test::More; use Crypt::OpenSSL::Random; use Crypt::OpenSSL::RSA; # Tests for encrypt/decrypt error paths, boundary conditions, and edge cases. # These cover gaps not addressed by rsa.t or padding.t. plan tests => 20; 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); my $rsa2 = Crypt::OpenSSL::RSA->generate_key(2048); my $key_size = $rsa->size(); # 256 bytes for 2048-bit key # --- OAEP boundary tests --- $rsa->use_pkcs1_oaep_padding(); my $oaep_max = $key_size - 42; # SHA-1 OAEP overhead # Max-length plaintext that fits OAEP { my $max_data = "x" x $oaep_max; my $ct = eval { $rsa->encrypt($max_data) }; ok(!$@, "OAEP encrypt at max plaintext length ($oaep_max bytes) succeeds") or diag $@; SKIP: { skip "encryption failed", 1 if $@; is($rsa->decrypt($ct), $max_data, "OAEP max-length plaintext round-trips correctly"); } } # One byte over max should fail { my $too_long = "x" x ($oaep_max + 1); eval { $rsa->encrypt($too_long) }; ok($@, "OAEP encrypt with plaintext one byte over max croaks"); } # --- No-padding boundary tests --- $rsa->use_no_padding(); # Too-short data for no-padding (requires exactly key_size bytes) { eval { $rsa->encrypt("x" x ($key_size - 1)) }; ok($@, "no-padding encrypt with data shorter than key size croaks"); } # Too-long data for no-padding { eval { $rsa->encrypt("x" x ($key_size + 1)) }; ok($@, "no-padding encrypt with data longer than key size croaks"); } # --- Decrypt error cases --- $rsa->use_pkcs1_oaep_padding(); # Decrypt garbage data { my $garbage = "G" x $key_size; eval { $rsa->decrypt($garbage) }; ok($@, "decrypt of garbage data croaks"); } # Decrypt truncated ciphertext { my $ct = $rsa->encrypt("test data"); my $truncated = substr($ct, 0, length($ct) - 10); eval { $rsa->decrypt($truncated) }; ok($@, "decrypt of truncated ciphertext croaks"); } # Decrypt with wrong private key { $rsa2->use_pkcs1_oaep_padding(); my $ct = $rsa->encrypt("wrong key test"); eval { $rsa2->decrypt($ct) }; ok($@, "decrypt with wrong private key croaks"); } # Decrypt bit-flipped ciphertext { my $ct = $rsa->encrypt("bit flip test"); my $flipped = $ct; substr($flipped, length($flipped) / 2, 1) ^= "\x01"; eval { $rsa->decrypt($flipped) }; ok($@, "decrypt of bit-flipped ciphertext croaks"); } # --- Empty string --- # Note: empty string OAEP encrypt succeeds on all versions, but decrypt # behavior varies (some OpenSSL versions return trailing garbage for # zero-length plaintext). We only test that encrypt doesn't crash. { my $ct = eval { $rsa->encrypt("") }; ok(!$@, "OAEP encrypt of empty string succeeds") or diag $@; } # --- Binary data with embedded NULs --- { my $binary = "\x00\x01\x00\xFF\x00" . ("\x00" x 50) . "\xFE"; my $ct = $rsa->encrypt($binary); is($rsa->decrypt($ct), $binary, "binary data with embedded NUL bytes round-trips correctly"); } # --- PSS padding cannot be used for encryption --- { $rsa->use_pkcs1_pss_padding(); eval { $rsa->encrypt("test") }; ok($@, "PSS padding cannot be used for encryption"); } # --- Plaintext length pre-validation error messages --- # OAEP: clear message with byte counts { $rsa->use_pkcs1_oaep_padding(); my $too_long = "x" x ($oaep_max + 1); eval { $rsa->encrypt($too_long) }; like($@, qr/plaintext too long for key size with OAEP padding/, "OAEP oversized plaintext gives clear error message"); like($@, qr/\Q$oaep_max bytes max\E/, "OAEP error includes max byte count"); my $got = $oaep_max + 1; like($@, qr/got $got/, "OAEP error includes actual byte count"); } # PKCS#1 v1.5 via private_encrypt: clear message { $rsa->use_pkcs1_padding(); my $pkcs1_max = $key_size - 11; my $too_long = "x" x ($pkcs1_max + 1); eval { $rsa->private_encrypt($too_long) }; like($@, qr/plaintext too long for key size with PKCS#1 v1\.5 padding/, "PKCS#1 v1.5 oversized plaintext gives clear error message"); like($@, qr/\Q$pkcs1_max bytes max\E/, "PKCS#1 v1.5 error includes max byte count"); } # no-padding: clear message { $rsa->use_no_padding(); eval { $rsa->encrypt("x" x ($key_size + 1)) }; like($@, qr/plaintext too long for key size with no padding/, "no-padding oversized plaintext gives clear error message"); } # Decrypt still works (no false positive from validation) { $rsa->use_pkcs1_oaep_padding(); my $ct = $rsa->encrypt("validation bypass test"); my $pt = eval { $rsa->decrypt($ct) }; ok(!$@, "decrypt not affected by plaintext length validation") or diag $@; is($pt, "validation bypass test", "decrypt returns correct plaintext after validation change"); } Crypt-OpenSSL-RSA-0.41/t/fakelib/0000755000175000017500000000000015172615445014461 5ustar timtimCrypt-OpenSSL-RSA-0.41/t/fakelib/Crypt/0000755000175000017500000000000015172615445015562 5ustar timtimCrypt-OpenSSL-RSA-0.41/t/fakelib/Crypt/OpenSSL/0000755000175000017500000000000015172615445017045 5ustar timtimCrypt-OpenSSL-RSA-0.41/t/fakelib/Crypt/OpenSSL/Bignum.pm0000644000175000017500000000007314606636707020631 0ustar timtimpackage Crypt::OpenSSL::Bignum; 0; # make require fail Crypt-OpenSSL-RSA-0.41/t/error_queue.t0000644000175000017500000000231615165307422015603 0ustar timtimuse strict; use warnings; use Test::More; use Crypt::OpenSSL::RSA; plan tests => 4; # Test that eval-caught OpenSSL failures don't pollute subsequent error messages. # Bug: croakSsl() used ERR_get_error() once (oldest error) instead of draining # the queue to the last (most recent/descriptive) error. my $rsa = Crypt::OpenSSL::RSA->generate_key(2048); # Trigger a decrypt failure inside eval eval { $rsa->decrypt("not valid ciphertext that is too short") }; my $first_error = $@; ok($first_error, "decrypt failure with short input caught in eval"); # Trigger a different decrypt failure eval { $rsa->decrypt("x" x 256) }; my $second_error = $@; ok($second_error, "decrypt failure with full-length garbage caught in eval"); # The second error should be a real decryption error, not stale from the first like($second_error, qr/OpenSSL error: \S/, "second error has a meaningful OpenSSL message"); # Trigger yet another failure after two eval-caught ones — error queue should be clean eval { $rsa->encrypt("A" x 500) }; my $third_error = $@; like($third_error, qr/too large|data greater|asym cipher failure|plaintext too long/i, "third error reports actual problem (data too large), not stale from earlier failures"); Crypt-OpenSSL-RSA-0.41/t/openssl_der.t0000644000175000017500000001464515165307422015573 0ustar timtimuse strict; use warnings; use Test::More; use MIME::Base64 qw/decode_base64/; use File::Temp qw/ tempfile /; use Crypt::OpenSSL::RSA; use Crypt::OpenSSL::Bignum; BEGIN { unless ($ENV{AUTHOR_TESTING}) { print qq{1..0 # SKIP these tests are for testing by the author\n}; exit } } my ($rsa_fh, $rsa_file) = tempfile(UNLINK => 1); # Create a new RSA key `openssl genrsa -out $rsa_file 2048 > /dev/null 2>&1`; # Get the output as text that includes the private key PEM my $priv_output = `openssl rsa -inform PEM -in $rsa_file -text 2>&1`; # X.509 SubjectPublicKeyInfo format (BEGIN PUBLIC KEY) my $pub_x509_output = `openssl rsa -inform PEM -in $rsa_file -pubout -text 2>&1`; # PKCS#1 RSAPublicKey format (BEGIN RSA PUBLIC KEY) my $pub_pkcs1_output = `openssl rsa -inform PEM -in $rsa_file -RSAPublicKey_out -text 2>&1`; # Basic grab multi-line data between # two tags from openssl -text output sub get_parameter { my $text = shift; my $start = shift; my $end = shift; # Fieldname may end in ':' $text =~ /$start:*\s*(.*?)\s*$end:*/s; my $parameter = $1; return undef unless defined $parameter; # Remove ':' and white space including newlines $parameter =~ s/[:\s]//g; # The exponent data we want is the hex data # with no 0x prefix if($parameter =~ /\((.*?)\)/ ) { $parameter = $1; $parameter =~ s/0x//g; } return $parameter; } # Extract the base64 PEM body between header/footer lines sub extract_pem_body { my ($text, $header_re, $footer_re) = @_; if ($text =~ /($header_re)\s*(.*?)\s*($footer_re)/s) { my $body = $2; $body =~ s/\s//g; return $body; } return undef; } # Compare a bignum to hex data sub compare_bignum_to_hex { my $bn1 = shift; my $hex = shift; my $bn2 = Crypt::OpenSSL::Bignum->new_from_hex($hex); isa_ok($bn2, 'Crypt::OpenSSL::Bignum'); return $bn2->cmp($bn1); } #################### # Check private key #################### diag("Check private key"); # Extract the values from the openssl private key output my $priv_n = get_parameter($priv_output, 'modulus', 'publicExponent'); my $priv_e = get_parameter($priv_output, 'publicExponent', 'privateExponent'); my $priv_d = get_parameter($priv_output, 'privateExponent', 'prime1'); my $priv_p = get_parameter($priv_output, 'prime1', 'prime2'); my $priv_q = get_parameter($priv_output, 'prime2', 'exponent1'); my $priv_dmp1 = get_parameter($priv_output, 'exponent1', 'exponent2'); my $priv_dmq1 = get_parameter($priv_output, 'exponent2', 'coefficient'); my $priv_iqmp = get_parameter($priv_output, 'coefficient', '-----BEGIN .*PRIVATE KEY-----'); my $priv_pem = extract_pem_body($priv_output, '-----BEGIN .*PRIVATE KEY-----', '-----END .*PRIVATE KEY-----'); # Load the private key from the DER (base64 decoded PEM) my $rsa = Crypt::OpenSSL::RSA->new_private_key(decode_base64($priv_pem)); # Get the private key parameters my ($n, $e, $d, $p, $q, $dmp1, $dmq1, $iqmp) = $rsa->get_key_parameters(); # Check each private key parameter to the expected values ok(compare_bignum_to_hex($n, $priv_n) == 0, "Imported DER n parameter matches expected"); ok(compare_bignum_to_hex($e, $priv_e) == 0, "Imported DER e parameter matches expected"); ok(compare_bignum_to_hex($d, $priv_d) == 0, "Imported DER d parameter matches expected"); ok(compare_bignum_to_hex($p, $priv_p) == 0, "Imported DER p parameter matches expected"); ok(compare_bignum_to_hex($q, $priv_q) == 0, "Imported DER q parameter matches expected"); ok(compare_bignum_to_hex($dmp1, $priv_dmp1) == 0, "Imported DER dmp1 parameter matches expected"); ok(compare_bignum_to_hex($dmq1, $priv_dmq1) == 0, "Imported DER dmq1 parameter matches expected"); ok(compare_bignum_to_hex($iqmp, $priv_iqmp) == 0, "Imported DER iqmp parameter matches expected"); ################################### # Check X.509 SubjectPublicKeyInfo ################################### diag("Check X.509 public key (from -pubout)"); # Extract PEM body — -pubout produces X.509 SubjectPublicKeyInfo (BEGIN PUBLIC KEY) my $pub_x509_pem = extract_pem_body($pub_x509_output, '-----BEGIN PUBLIC KEY-----', '-----END PUBLIC KEY-----'); # Load the public key from the DER (base64 decoded PEM) my $pub_x509_rsa = Crypt::OpenSSL::RSA->new_public_key(decode_base64($pub_x509_pem)); # Get the key parameters my ($px_n, $px_e, $px_d, $px_p, $px_q, $px_dmp1, $px_dmq1, $px_iqmp) = $pub_x509_rsa->get_key_parameters(); # n and e should match the private key's values (same key) ok(compare_bignum_to_hex($px_n, $priv_n) == 0, "X.509 public DER n matches private key n"); ok(compare_bignum_to_hex($px_e, $priv_e) == 0, "X.509 public DER e matches private key e"); ok(!$px_d, "X.509 public DER d parameter undef as expected"); ok(!$px_p, "X.509 public DER p parameter undef as expected"); ok(!$px_q, "X.509 public DER q parameter undef as expected"); ok(!$px_dmp1, "X.509 public DER dmp1 parameter undef as expected"); ok(!$px_dmq1, "X.509 public DER dmq1 parameter undef as expected"); ok(!$px_iqmp, "X.509 public DER iqmp parameter undef as expected"); ############################# # Check PKCS#1 RSAPublicKey ############################# diag("Check PKCS#1 public key (from -RSAPublicKey_out)"); # Extract PEM body — -RSAPublicKey_out produces PKCS#1 (BEGIN RSA PUBLIC KEY) my $pub_pkcs1_pem = extract_pem_body($pub_pkcs1_output, '-----BEGIN RSA PUBLIC KEY-----', '-----END RSA PUBLIC KEY-----'); SKIP: { skip "openssl does not support -RSAPublicKey_out", 8 unless defined $pub_pkcs1_pem && length($pub_pkcs1_pem) > 0; # Load the public key from the DER (base64 decoded PEM) my $pub_pkcs1_rsa = Crypt::OpenSSL::RSA->new_public_key(decode_base64($pub_pkcs1_pem)); # Get the key parameters my ($pn, $pe, $pd, $pp, $pq, $pdmp1, $pdmq1, $piqmp) = $pub_pkcs1_rsa->get_key_parameters(); # n and e should match the private key's values (same key) ok(compare_bignum_to_hex($pn, $priv_n) == 0, "PKCS#1 public DER n matches private key n"); ok(compare_bignum_to_hex($pe, $priv_e) == 0, "PKCS#1 public DER e matches private key e"); ok(!$pd, "PKCS#1 public DER d parameter undef as expected"); ok(!$pp, "PKCS#1 public DER p parameter undef as expected"); ok(!$pq, "PKCS#1 public DER q parameter undef as expected"); ok(!$pdmp1, "PKCS#1 public DER dmp1 parameter undef as expected"); ok(!$pdmq1, "PKCS#1 public DER dmq1 parameter undef as expected"); ok(!$piqmp, "PKCS#1 public DER iqmp parameter undef as expected"); } done_testing(); Crypt-OpenSSL-RSA-0.41/t/key_lifecycle.t0000644000175000017500000002176015165307422016061 0ustar timtimuse strict; use Test::More; use Crypt::OpenSSL::Random; use Crypt::OpenSSL::RSA; Crypt::OpenSSL::Random::random_seed("OpenSSL needs at least 32 bytes."); Crypt::OpenSSL::RSA->import_random_seed(); my $HAS_BIGNUM = eval { require Crypt::OpenSSL::Bignum; 1 } ? 1 : 0; # skip() reports skipped tests which count toward total, so plan must # always include them regardless of whether Bignum is available. plan tests => 9 + 14 + 20 + 2 + 4; # --- Cross-key operations --- # Sign with key1, verify with key2 — should return false, not croak. my $rsa1 = Crypt::OpenSSL::RSA->generate_key(2048); my $rsa2 = Crypt::OpenSSL::RSA->generate_key(2048); $rsa1->use_pkcs1_pss_padding(); $rsa2->use_pkcs1_pss_padding(); my $plaintext = "The quick brown fox jumps over the lazy dog"; my $sig1 = $rsa1->sign($plaintext); ok( defined $sig1 && length($sig1) > 0, "sign with key1 produces signature" ); my $cross_verify = eval { $rsa2->verify($plaintext, $sig1) }; ok( !$@, "cross-key verify does not croak" ); ok( !$cross_verify, "cross-key verify returns false (key1 sig, key2 verify)" ); # Sign with key2, verify with key1 my $sig2 = $rsa2->sign($plaintext); $cross_verify = eval { $rsa1->verify($plaintext, $sig2) }; ok( !$@, "reverse cross-key verify does not croak" ); ok( !$cross_verify, "reverse cross-key verify returns false" ); # Same key should verify its own signature ok( $rsa1->verify($plaintext, $sig1), "key1 verifies its own signature" ); ok( $rsa2->verify($plaintext, $sig2), "key2 verifies its own signature" ); # --- Key export → import → sign/verify round-trip --- my $priv_pem = $rsa1->get_private_key_string(); my $pub_pem = $rsa1->get_public_key_string(); my $rsa1_copy = Crypt::OpenSSL::RSA->new_private_key($priv_pem); my $rsa1_pub = Crypt::OpenSSL::RSA->new_public_key($pub_pem); $rsa1_copy->use_pkcs1_pss_padding(); $rsa1_pub->use_pkcs1_pss_padding(); my $sig_copy = $rsa1_copy->sign($plaintext); ok( $rsa1_pub->verify($plaintext, $sig_copy), "exported private key signs, exported public key verifies" ); ok( $rsa1->verify($plaintext, $sig_copy), "original key verifies signature from exported key" ); # --- Key parameter round-trip with real-sized keys --- # Requires Crypt::OpenSSL::Bignum SKIP: { skip "Crypt::OpenSSL::Bignum required for parameter tests", 14 unless $HAS_BIGNUM; # Extract parameters from a 2048-bit key my ($n, $e, $d, $p, $q, $dmp1, $dmq1, $iqmp) = $rsa1->get_key_parameters(); ok( defined $n && defined $e, "get_key_parameters returns n and e" ); ok( defined $d, "get_key_parameters returns d for private key" ); ok( defined $p && defined $q, "get_key_parameters returns p and q" ); ok( defined $dmp1 && defined $dmq1 && defined $iqmp, "get_key_parameters returns CRT params" ); # Reconstruct from all parameters my $rsa_from_params = Crypt::OpenSSL::RSA->new_key_from_parameters($n, $e, $d, $p, $q); ok( $rsa_from_params, "new_key_from_parameters succeeds with full params" ); is( $rsa_from_params->size(), $rsa1->size(), "reconstructed key has same size as original" ); ok( $rsa_from_params->check_key(), "reconstructed key passes check_key()" ); # Sign with reconstructed, verify with original $rsa_from_params->use_pkcs1_pss_padding(); my $sig_params = $rsa_from_params->sign($plaintext); ok( $rsa1->verify($plaintext, $sig_params), "original key verifies signature from reconstructed key" ); ok( $rsa_from_params->verify($plaintext, $sig1), "reconstructed key verifies signature from original key" ); # Encrypt with reconstructed, decrypt with original $rsa_from_params->use_pkcs1_oaep_padding(); $rsa1->use_pkcs1_oaep_padding(); my $ct = $rsa_from_params->encrypt("secret message"); my $pt = $rsa1->decrypt($ct); is( $pt, "secret message", "original key decrypts ciphertext from reconstructed key" ); # Public-only key from parameters my $rsa_pub_params = Crypt::OpenSSL::RSA->new_key_from_parameters($n, $e); ok( $rsa_pub_params, "new_key_from_parameters with n,e only succeeds" ); ok( !$rsa_pub_params->is_private(), "key from n,e only is not private" ); $rsa_pub_params->use_pkcs1_pss_padding(); eval { $rsa_pub_params->sign("hello") }; like( $@, qr/Public keys cannot sign/, "public-only key from params cannot sign" ); # Restore PSS for verification $rsa1->use_pkcs1_pss_padding(); ok( $rsa_pub_params->verify($plaintext, $sig1), "public-only key from params verifies original signature" ); } # --- Parameter derivation paths --- # Tests for deriving missing p or q from n, and constructing keys # from n/e/d without CRT params. SKIP: { skip "Crypt::OpenSSL::Bignum required for derivation tests", 20 unless $HAS_BIGNUM; my ($n, $e, $d, $p, $q) = $rsa1->get_key_parameters(); # --- Derive q from n and p (pass p, omit q) --- my $rsa_derive_q = eval { Crypt::OpenSSL::RSA->new_key_from_parameters($n, $e, $d, $p); }; ok( !$@, "new_key_from_parameters(n,e,d,p) does not croak" ) or diag "Error: $@"; ok( $rsa_derive_q, "derive-q key constructed" ); ok( $rsa_derive_q->is_private(), "derive-q key is private" ); ok( $rsa_derive_q->check_key(), "derive-q key passes check_key()" ); # Verify the derived key can sign and the original can verify $rsa_derive_q->use_pkcs1_pss_padding(); my $sig_dq = $rsa_derive_q->sign($plaintext); ok( $rsa1->verify($plaintext, $sig_dq), "original verifies signature from derive-q key" ); # --- Derive p from n and q (pass q as 5th arg, omit p) --- my $rsa_derive_p = eval { Crypt::OpenSSL::RSA->new_key_from_parameters($n, $e, $d, undef, $q); }; ok( !$@, "new_key_from_parameters(n,e,d,undef,q) does not croak" ) or diag "Error: $@"; ok( $rsa_derive_p, "derive-p key constructed" ); ok( $rsa_derive_p->is_private(), "derive-p key is private" ); ok( $rsa_derive_p->check_key(), "derive-p key passes check_key()" ); # Verify the derived key can sign and the original can verify $rsa_derive_p->use_pkcs1_pss_padding(); my $sig_dp = $rsa_derive_p->sign($plaintext); ok( $rsa1->verify($plaintext, $sig_dp), "original verifies signature from derive-p key" ); # --- Private key from n, e, d only (no p, q — the "else" branch) --- my $rsa_ned = eval { Crypt::OpenSSL::RSA->new_key_from_parameters($n, $e, $d); }; ok( !$@, "new_key_from_parameters(n,e,d) does not croak" ) or diag "Error: $@"; ok( $rsa_ned, "n/e/d-only key constructed" ); ok( $rsa_ned->is_private(), "n/e/d-only key is private" ); is( $rsa_ned->size(), $rsa1->size(), "n/e/d-only key has correct size" ); # n/e/d key can encrypt/decrypt $rsa_ned->use_pkcs1_oaep_padding(); $rsa1->use_pkcs1_oaep_padding(); my $ct = $rsa_ned->encrypt("round-trip test"); my $pt = $rsa1->decrypt($ct); is( $pt, "round-trip test", "original decrypts ciphertext from n/e/d-only key" ); # Cross-derived key interop: derive-q encrypts, derive-p decrypts $rsa_derive_q->use_pkcs1_oaep_padding(); $rsa_derive_p->use_pkcs1_oaep_padding(); $ct = $rsa_derive_q->encrypt("cross-derive test"); $pt = $rsa_derive_p->decrypt($ct); is( $pt, "cross-derive test", "derive-p decrypts ciphertext from derive-q key" ); # Derive-p signs, derive-q verifies $rsa_derive_p->use_pkcs1_pss_padding(); $rsa_derive_q->use_pkcs1_pss_padding(); my $sig_cross = $rsa_derive_p->sign("interop"); ok( $rsa_derive_q->verify("interop", $sig_cross), "derive-q verifies signature from derive-p key" ); # Derive d from p and q (pass p, q but omit d) my $rsa_derive_d = eval { Crypt::OpenSSL::RSA->new_key_from_parameters($n, $e, undef, $p, $q); }; ok( !$@, "new_key_from_parameters(n,e,undef,p,q) does not croak" ) or diag "Error: $@"; ok( $rsa_derive_d, "derive-d key constructed" ); ok( $rsa_derive_d->check_key(), "derive-d key passes check_key()" ); } # --- Error cases (no Bignum needed) --- eval { Crypt::OpenSSL::RSA->_new_key_from_parameters(0, 0, 0, 0, 0) }; like( $@, qr/modulus and public key must be provided/, "croak when both n and e are NULL" ); eval { Crypt::OpenSSL::RSA->_new_key_from_parameters(0, 0, 0, 0, 0) }; like( $@, qr/modulus and public key must be provided/, "croak when n=0 and e=0 (missing required params)" ); # --- Invalid exponent rejection (prevents RSA_generate_key_ex hang on OpenSSL 1.1.x) --- eval { Crypt::OpenSSL::RSA->generate_key(2048, 2) }; like( $@, qr/RSA exponent must be odd and >= 3/, "generate_key croaks on even exponent (2)" ); eval { Crypt::OpenSSL::RSA->generate_key(2048, 1) }; like( $@, qr/RSA exponent must be odd and >= 3/, "generate_key croaks on exponent 1" ); eval { Crypt::OpenSSL::RSA->generate_key(2048, 0) }; like( $@, qr/RSA exponent must be odd and >= 3/, "generate_key croaks on exponent 0" ); eval { Crypt::OpenSSL::RSA->generate_key(2048, 100) }; like( $@, qr/RSA exponent must be odd and >= 3/, "generate_key croaks on even exponent (100)" ); Crypt-OpenSSL-RSA-0.41/t/sign_verify.t0000644000175000017500000000470615165307422015577 0ustar timtimuse strict; use Test::More; use Crypt::OpenSSL::Random; use Crypt::OpenSSL::RSA; # Tests for sign/verify edge cases not covered by rsa.t or padding.t: # cross-hash verification, empty messages, and malformed signatures. plan tests => 7; 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); my $rsa_pub = Crypt::OpenSSL::RSA->new_public_key($rsa->get_public_key_string()); # --- Cross-hash verification --- # Sign with one hash, verify with another — should always fail. # sha256 sign, sha1 verify { $rsa->use_sha256_hash(); $rsa->use_pkcs1_pss_padding(); my $msg = "cross-hash test message"; my $sig = $rsa->sign($msg); # SHA1 verify may croak on systems with legacy digest restrictions (e.g. almalinux:9) $rsa_pub->use_sha1_hash(); $rsa_pub->use_pkcs1_pss_padding(); my $result = eval { $rsa_pub->verify($msg, $sig) }; ok(!$result, "sha256 signature does not verify with sha1 hash"); } # sha1 sign, sha256 verify — SHA1 signing may be disabled on FIPS-like systems SKIP: { $rsa->use_sha1_hash(); $rsa->use_pkcs1_pss_padding(); my $msg = "reverse cross-hash test"; my $sig = eval { $rsa->sign($msg) }; skip "SHA1 signing not available: $@", 1 if $@; $rsa_pub->use_sha256_hash(); $rsa_pub->use_pkcs1_pss_padding(); my $result = eval { $rsa_pub->verify($msg, $sig) }; ok(!$result, "sha1 signature does not verify with sha256 hash"); } # --- Empty message --- { $rsa->use_sha256_hash(); $rsa->use_pkcs1_pss_padding(); $rsa_pub->use_sha256_hash(); $rsa_pub->use_pkcs1_pss_padding(); my $sig = $rsa->sign(""); ok(defined $sig && length($sig) > 0, "signing empty message produces a non-empty signature"); ok($rsa_pub->verify("", $sig), "empty message signature verifies correctly"); ok(!$rsa_pub->verify("not empty", $sig), "empty message signature does not verify different message"); } # --- Malformed signatures --- { $rsa_pub->use_sha256_hash(); $rsa_pub->use_pkcs1_pss_padding(); # Empty string as signature my $result = eval { $rsa_pub->verify("test", "") }; ok(!$result || $@, "verify with empty signature returns false or croaks"); # Single-byte signature $result = eval { $rsa_pub->verify("test", "\x00") }; ok(!$result || $@, "verify with 1-byte signature returns false or croaks"); } Crypt-OpenSSL-RSA-0.41/t/error.t0000644000175000017500000001304115165307422014374 0ustar timtimuse strict; use Test::More; use Crypt::OpenSSL::Random; use Crypt::OpenSSL::RSA; 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); my $rsa_pub = Crypt::OpenSSL::RSA->new_public_key($rsa->get_public_key_string()); # --- Malformed key loading --- eval { Crypt::OpenSSL::RSA->new_private_key("not a key at all") }; ok($@, "new_private_key croaks on garbage input"); eval { Crypt::OpenSSL::RSA->new_private_key("") }; ok($@, "new_private_key croaks on empty string"); eval { Crypt::OpenSSL::RSA->new_private_key(undef) }; ok($@, "new_private_key croaks on undef"); eval { Crypt::OpenSSL::RSA->new_private_key( "-----BEGIN RSA PRIVATE KEY-----\ngarbage\n-----END RSA PRIVATE KEY-----\n" ); }; ok($@, "new_private_key croaks on corrupted PEM body"); eval { Crypt::OpenSSL::RSA->_new_public_key_pkcs1("not a key") }; ok($@, "_new_public_key_pkcs1 croaks on garbage input"); eval { Crypt::OpenSSL::RSA->_new_public_key_x509("not a key") }; ok($@, "_new_public_key_x509 croaks on garbage input"); # --- Unrecognized public key format (Perl-level croak) --- eval { Crypt::OpenSSL::RSA->new_public_key("-----BEGIN CERTIFICATE-----\nfoo\n-----END CERTIFICATE-----\n") }; like($@, qr/unrecognized key format/, "new_public_key croaks on unrecognized PEM header"); eval { Crypt::OpenSSL::RSA->new_public_key("just plain text") }; like($@, qr/unrecognized key format/, "new_public_key croaks on plain text"); # --- Wrong passphrase on encrypted key --- my $encrypted_pem = $rsa->get_private_key_string("correct_passphrase", "aes-128-cbc"); eval { Crypt::OpenSSL::RSA->new_private_key($encrypted_pem, "wrong_passphrase") }; ok($@, "new_private_key croaks on wrong passphrase"); # Note: testing with no passphrase or empty passphrase is intentionally # omitted — OpenSSL may prompt on the terminal, hanging non-interactive runs. # --- Public key cannot perform private operations --- eval { $rsa_pub->sign("hello") }; like($@, qr/Public keys cannot sign/i, "public key cannot sign"); eval { $rsa_pub->decrypt("hello") }; like($@, qr/Public keys cannot decrypt/i, "public key cannot decrypt"); eval { $rsa_pub->private_encrypt("hello") }; like($@, qr/Public keys cannot private_encrypt/i, "public key cannot private_encrypt"); eval { $rsa_pub->check_key() }; like($@, qr/Public keys cannot be checked/i, "public key cannot check_key"); # --- Corrupted ciphertext --- $rsa->use_pkcs1_oaep_padding(); my $ciphertext = $rsa->encrypt("test message"); # Flip bits in ciphertext my $corrupted = $ciphertext; substr($corrupted, 10, 1) ^= "\xff"; eval { $rsa->decrypt($corrupted) }; ok($@, "decrypt croaks on corrupted ciphertext"); # Wrong-length ciphertext eval { $rsa->decrypt("too short") }; ok($@, "decrypt croaks on wrong-length ciphertext"); eval { $rsa->decrypt("") }; ok($@, "decrypt croaks on empty ciphertext"); # --- Plaintext too large for padding mode --- $rsa->use_pkcs1_oaep_padding(); my $max_oaep = $rsa->size() - 42; my $too_large = "x" x ($max_oaep + 1); eval { $rsa->encrypt($too_large) }; ok($@, "encrypt croaks when plaintext exceeds OAEP max size"); # Exact max should work my $exact_max = "x" x $max_oaep; my $ct = eval { $rsa->encrypt($exact_max) }; ok(!$@, "encrypt succeeds at exact OAEP max size"); is(eval { $rsa->decrypt($ct) }, $exact_max, "round-trip at OAEP max size"); # --- Cross-key signature verification --- my $rsa2 = Crypt::OpenSSL::RSA->generate_key(2048); $rsa->use_pkcs1_pss_padding(); $rsa2->use_pkcs1_pss_padding(); my $sig = $rsa->sign("message to sign"); ok(!eval { $rsa2->verify("message to sign", $sig) }, "signature from key1 does not verify with key2"); my $rsa2_pub = Crypt::OpenSSL::RSA->new_public_key($rsa2->get_public_key_string()); $rsa2_pub->use_pkcs1_pss_padding(); ok(!eval { $rsa2_pub->verify("message to sign", $sig) }, "signature from key1 does not verify with key2 public"); # --- Empty message signing --- my $empty_sig = eval { $rsa->sign("") }; ok(!$@, "sign succeeds on empty message"); ok($rsa->verify("", $empty_sig), "verify succeeds on empty message signature"); ok(!$rsa->verify("not empty", $empty_sig), "empty message signature does not verify different message"); # --- Truncated signature --- my $full_sig = $rsa->sign("test data"); my $truncated_sig = substr($full_sig, 0, length($full_sig) - 1); ok(!eval { $rsa->verify("test data", $truncated_sig) }, "truncated signature does not verify"); my $extended_sig = $full_sig . "\x00"; ok(!eval { $rsa->verify("test data", $extended_sig) }, "extended signature does not verify"); # --- Key size boundary --- my $small_rsa = eval { Crypt::OpenSSL::RSA->generate_key(512) }; SKIP: { skip "OpenSSL 3.x rejects 512-bit keys at default security level", 2 if $@; ok($small_rsa, "512-bit key generation succeeds"); is($small_rsa->size() * 8, 512, "512-bit key has correct size"); } # --- generate_key with custom exponent --- my $rsa_e3 = eval { Crypt::OpenSSL::RSA->generate_key(2048, 3) }; SKIP: { skip "OpenSSL rejected exponent 3", 2 if $@; ok($rsa_e3, "generate_key with exponent 3 succeeds"); ok($rsa_e3->check_key(), "key with exponent 3 passes check_key"); } my $rsa_e17 = eval { Crypt::OpenSSL::RSA->generate_key(2048, 17) }; SKIP: { skip "OpenSSL rejected exponent 17", 2 if $@; ok($rsa_e17, "generate_key with exponent 17 succeeds"); ok($rsa_e17->check_key(), "key with exponent 17 passes check_key"); } # Even exponent should fail eval { Crypt::OpenSSL::RSA->generate_key(2048, 2) }; ok($@, "generate_key croaks on even exponent"); done_testing; Crypt-OpenSSL-RSA-0.41/t/z_kwalitee.t0000644000175000017500000000101514606636707015411 0ustar timtim# -*- perl -*- use strict; use warnings; use Test::More; use Config; plan skip_all => 'This test is only run for the module author' unless -d '.git' || $ENV{IS_MAINTAINER}; plan skip_all => 'Test::Kwalitee fails with clang -faddress-sanitizer' if $Config{ccflags} =~ /-faddress-sanitizer/; use File::Copy 'cp'; cp( 'MYMETA.yml', 'META.yml' ) if -e 'MYMETA.yml' and !-e 'META.yml'; eval { require Test::Kwalitee; Test::Kwalitee->import; }; plan skip_all => "Test::Kwalitee needed for testing kwalitee" if $@; Crypt-OpenSSL-RSA-0.41/t/padding.t0000644000175000017500000001476015172613713014663 0ustar timtimuse strict; use Test::More; use Crypt::OpenSSL::Random; use Crypt::OpenSSL::RSA; use Crypt::OpenSSL::Guess qw(openssl_version find_openssl_prefix find_openssl_exec); my ($major, $minor, $patch) = openssl_version; my $is_libressl = (`"@{[find_openssl_exec(find_openssl_prefix())]}" version` =~ /LibreSSL/); BEGIN { plan tests => 124 + ( 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" ); is( $decoded_text, $plaintext, "decrypted text matches plaintext with $padding/$hash" ); 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" ); is( $decoded_text, $plaintext, "public_decrypt(private_encrypt(plaintext)) round-trips with $padding/$hash" ); } } 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|no digest set/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); is( $rsa->size() * 8, 2048, "2048-bit key has correct size" ); ok( $rsa->check_key(), "2048-bit key passes 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, "key strings are non-empty" ); my $plaintext = "The quick brown fox jumped over the lazy dog"; my $rsa_priv = Crypt::OpenSSL::RSA->new_private_key($private_key_string); is( $rsa_priv->decrypt( $rsa_priv->encrypt($plaintext) ), $plaintext, "private key round-trips encrypt/decrypt" ); my $rsa_pub = Crypt::OpenSSL::RSA->new_public_key($public_key_string); $plaintext .= $plaintext x 5; # sslv23 is unsupported on OpenSSL 3.x but LibreSSL still supports it SKIP: { skip "sslv23 is available on OpenSSL < 3.0 and LibreSSL", 2 if $major lt '3.0' || $is_libressl; eval { $rsa->use_sslv23_padding; }; ok($@, "use_sslv23_padding croaks on OpenSSL 3.x"); like($@, qr/SSLv23 padding was removed/, "error message explains deprecation"); } # pkcs1 is supported (for signatures, not encryption) eval { $rsa->use_pkcs1_padding; }; ok(!$@, "Padding method pkcs1 supported"); my @supported_paddings = qw/no pkcs1 pkcs1_pss pkcs1_oaep/; # no pkcs1 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' => 1, 'encrypt' => 0, 'pad' => 11}, # pad value only affects plaintext length; sign() hashes input so value is arbitrary (must be non-zero) #'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) { SKIP: { # OAEP only affects encryption; signing uses its own padding # and does not croak when OAEP is set skip "Signing with $padding padding does not croak", 1 if $encrypt; eval { $rsa->$method; $rsa->sign($plaintext); }; ok($@, "Padding $padding is invalid for signing with $hash"); } } # Valid encryption methods with padding if ($encrypt) { _Test_Encrypt_And_Decrypt( $rsa->size() - $pad, $rsa, 0, $padding, $hash ); } } } # Try Crypt-OpenSSL-RSA-0.41/t/private_encrypt.t0000644000175000017500000000713215172443224016464 0ustar timtimuse strict; use warnings; use Test::More; use Crypt::OpenSSL::Random; use Crypt::OpenSSL::RSA; plan tests => 14; 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); my $key_size = $rsa->size(); # --- NO_PADDING: private_encrypt / public_decrypt roundtrip --- $rsa->use_no_padding(); my $data = "\0" x ($key_size - 11) . "Hello World"; my $enc = $rsa->private_encrypt($data); ok(defined $enc, "private_encrypt with no_padding succeeds"); my $dec = $rsa->public_decrypt($enc); is($dec, $data, "public_decrypt(private_encrypt(data)) round-trips with no_padding"); # --- OAEP: should croak for private_encrypt --- $rsa->use_pkcs1_oaep_padding(); eval { $rsa->private_encrypt("test") }; like($@, qr/OAEP padding is not supported for private_encrypt/, "private_encrypt with OAEP croaks with clear message"); # --- OAEP: should croak for public_decrypt --- eval { $rsa->public_decrypt("test" x 64) }; like($@, qr/OAEP padding is not supported for private_encrypt\/public_decrypt/, "public_decrypt with OAEP croaks with clear message"); # --- PSS: should croak for private_encrypt --- $rsa->use_pkcs1_pss_padding(); eval { $rsa->private_encrypt("test") }; like($@, qr/PSS padding with private_encrypt\/public_decrypt is not supported/, "private_encrypt with PSS croaks with clear message"); # --- PSS: should croak for public_decrypt --- eval { $rsa->public_decrypt("test" x 64) }; like($@, qr/PSS padding with private_encrypt\/public_decrypt is not supported/, "public_decrypt with PSS croaks with clear message"); # --- Error ordering: padding error must come before length error --- { my $rsa_fresh = Crypt::OpenSSL::RSA->generate_key(2048); my $large_data = "x" x 250; # exceeds OAEP limit (256 - 42 = 214) eval { $rsa_fresh->private_encrypt($large_data) }; like($@, qr/OAEP padding is not supported for private_encrypt/, "private_encrypt with default OAEP + oversized data gives padding error, not length error"); $rsa_fresh->use_pkcs1_pss_padding(); eval { $rsa_fresh->private_encrypt($large_data) }; like($@, qr/PSS padding with private_encrypt\/public_decrypt is not supported/, "private_encrypt with PSS + oversized data gives padding error, not length error"); $rsa_fresh->use_pkcs1_oaep_padding(); my $exact_data = "y" x 215; # just over OAEP limit eval { $rsa_fresh->private_encrypt($exact_data) }; like($@, qr/OAEP padding is not supported for private_encrypt/, "private_encrypt with OAEP + barely-over-limit data gives padding error, not length error"); $rsa_fresh->use_pkcs1_padding(); eval { $rsa_fresh->private_encrypt("test") }; ok(!$@, "private_encrypt with PKCS#1 v1.5 padding works"); } # --- Encryption operations still work correctly --- $rsa->use_pkcs1_oaep_padding(); my $plaintext = "Hello World"; my $ciphertext = $rsa->encrypt($plaintext); ok(defined $ciphertext, "encrypt with OAEP still works"); is($rsa->decrypt($ciphertext), $plaintext, "decrypt with OAEP round-trips"); # --- PSS still croaks for encrypt --- $rsa->use_pkcs1_pss_padding(); eval { $rsa->encrypt($plaintext) }; like($@, qr/RSA-PSS cannot be used for encryption/, "encrypt with PSS still croaks"); # --- Public key cannot private_encrypt --- my $pub_key_string = $rsa->get_public_key_string(); my $rsa_pub = Crypt::OpenSSL::RSA->new_public_key($pub_key_string); $rsa_pub->use_no_padding(); eval { $rsa_pub->private_encrypt("\0" x $key_size) }; like($@, qr/Public keys cannot private_encrypt/, "public key private_encrypt croaks"); Crypt-OpenSSL-RSA-0.41/t/rsa.t0000644000175000017500000001530515172443224014034 0ustar timtimuse strict; use Test::More; use Crypt::OpenSSL::Random; use Crypt::OpenSSL::RSA; use Crypt::OpenSSL::Guess qw(openssl_version); BEGIN { plan tests => 78 + ( UNIVERSAL::can( "Crypt::OpenSSL::RSA", "use_sha512_hash" ) ? 8 * 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 ) = @_; 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), "encrypt succeeds" ); ok( $decoded_text = $p_rsa->decrypt($ciphertext), "decrypt succeeds" ); is( $decoded_text, $plaintext, "decrypted text matches plaintext" ); if ($p_check_private_encrypt) { ok( $ciphertext = $p_rsa->private_encrypt($plaintext), "private_encrypt succeeds" ); ok( $decoded_text = $p_rsa->public_decrypt($ciphertext), "public_decrypt succeeds" ); is( $decoded_text, $plaintext, "public_decrypt(private_encrypt(plaintext)) round-trips" ); } } sub _Test_Sign_And_Verify { 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|no digest set/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 { my ( $code, $expected ) = @_; eval { &$code() }; like( $@, qr/$expected/, "croak matches: $expected" ); } # On platforms without a /dev/random, we need to manually seed. In # real life, the following would stink, but for testing purposes, it # suffices to seed with any old thing, even if it is not actually # random. We'll at least emulate seeding from Crypt::OpenSSL::Random, # which is what we would have to do in "real life", since the private # data used by the OpenSSL random library apparently does not span # across perl XS modules. Crypt::OpenSSL::Random::random_seed("OpenSSL needs at least 32 bytes."); Crypt::OpenSSL::RSA->import_random_seed(); is( Crypt::OpenSSL::RSA->generate_key(512)->size() * 8, 512, "512-bit key has correct size" ); my $rsa = Crypt::OpenSSL::RSA->generate_key(2048); is( $rsa->size() * 8, 2048, "2048-bit key has correct size" ); ok( $rsa->check_key(), "2048-bit key passes check_key()" ); $rsa->use_no_padding(); _Test_Encrypt_And_Decrypt( $rsa->size(), $rsa, 1 ); $rsa->use_pkcs1_oaep_padding(); # private_encrypt does not work with pkcs1_oaep_padding _Test_Encrypt_And_Decrypt( $rsa->size() - 42, $rsa, 0 ); #FIXME - use_sslv23_padding seems to fail on decryption. openssl bug? 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, "get_private_key_string and get_public_key_string return data" ); my $plaintext = "The quick brown fox jumped over the lazy dog"; my $rsa_priv = Crypt::OpenSSL::RSA->new_private_key($private_key_string); is( $rsa_priv->decrypt( $rsa_priv->encrypt($plaintext) ), $plaintext, "private key round-trips encrypt/decrypt" ); my $rsa_pub = Crypt::OpenSSL::RSA->new_public_key($public_key_string); $rsa->use_pkcs1_oaep_padding(); is( $rsa->decrypt( $rsa_pub->encrypt($plaintext) ), $plaintext, "pub encrypt + priv decrypt round-trips" ); ok( $rsa_priv->is_private(), "private key reports is_private() true" ); ok( !$rsa_pub->is_private(), "public key reports is_private() false" ); _check_for_croak( sub { $rsa_pub->decrypt( $rsa_pub->encrypt($plaintext) ); }, "Public keys cannot decrypt" ); _check_for_croak( sub { $rsa_pub->check_key(); }, "Public keys cannot be checked" ); _check_for_croak( sub { $rsa_pub->private_encrypt("foo"); }, "Public keys cannot private_encrypt" ); _check_for_croak( sub { $rsa_pub->sign("foo"); }, "Public keys cannot sign messages" ); $plaintext .= $plaintext x 5; my @paddings = qw/pkcs1 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, "sha1 with $padding padding" ); 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, "sha224 with $padding padding" ); $rsa->use_sha256_hash(); $rsa_pub->use_sha256_hash(); _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, "sha384 with $padding padding" ); $rsa->use_sha512_hash(); $rsa_pub->use_sha512_hash(); _Test_Sign_And_Verify( $plaintext, $rsa, $rsa_pub, "sha512 with $padding padding" ); } } 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" ) ) { $rsa->use_whirlpool_hash(); $rsa_pub->use_whirlpool_hash(); _Test_Sign_And_Verify( $plaintext, $rsa, $rsa_pub, "whirlpool" ); } # PKCS#1 v1.5 encryption must croak (Marvin attack) $rsa->use_pkcs1_padding(); _check_for_croak( sub { $rsa->encrypt("test") }, "Marvin attack" ); # check subclassing eval { Crypt::OpenSSL::RSA::Subpackage->generate_key(512); }; ok( !$@, "subclass generate_key() succeeds" ); package Crypt::OpenSSL::RSA::Subpackage; use base qw(Crypt::OpenSSL::RSA); Crypt-OpenSSL-RSA-0.41/t/keygen.t0000644000175000017500000001376715172443224014543 0ustar timtimuse strict; use Test::More; use Crypt::OpenSSL::Random; use Crypt::OpenSSL::RSA; # Tests for generate_key() with non-default exponents and edge cases. # The default exponent (65537) is well-tested elsewhere; this file # exercises the explicit exponent parameter and verifies generated keys # are fully functional. Crypt::OpenSSL::Random::random_seed("OpenSSL needs at least 32 bytes."); Crypt::OpenSSL::RSA->import_random_seed(); my $HAS_BIGNUM = eval { require Crypt::OpenSSL::Bignum; 1 } ? 1 : 0; # Use 2048-bit keys throughout — AlmaLinux 9 and other FIPS-like systems # enforce a minimum RSA key size of 2048 bits. my $BITS = 2048; my $BYTES = $BITS / 8; plan tests => 29; # --- Default exponent (65537) explicitly passed --- { my $rsa = Crypt::OpenSSL::RSA->generate_key($BITS, 65537); ok($rsa, "generate_key with explicit default exponent 65537"); is($rsa->size(), $BYTES, "key size is $BYTES bytes ($BITS bits)"); ok($rsa->is_private(), "generated key is private"); ok($rsa->check_key(), "key passes check_key"); SKIP: { skip "Crypt::OpenSSL::Bignum not available", 1 unless $HAS_BIGNUM; my (undef, $e) = $rsa->get_key_parameters(); is($e->to_decimal(), "65537", "exponent is 65537"); } } # --- Small valid exponent: 3 --- { my $rsa = eval { Crypt::OpenSSL::RSA->generate_key($BITS, 3) }; SKIP: { skip "OpenSSL rejected exponent 3: $@", 5 if $@; ok($rsa, "generate_key with exponent 3"); ok($rsa->check_key(), "e=3 key passes check_key"); # Verify e=3 key can sign/verify $rsa->use_sha256_hash(); $rsa->use_pkcs1_pss_padding(); my $sig = $rsa->sign("test message"); ok($sig, "e=3 key can sign"); ok($rsa->verify("test message", $sig), "e=3 key signature verifies"); SKIP: { skip "Crypt::OpenSSL::Bignum not available", 1 unless $HAS_BIGNUM; my (undef, $e) = $rsa->get_key_parameters(); is($e->to_decimal(), "3", "exponent is 3"); } } } # --- Valid exponent: 17 --- { my $rsa = eval { Crypt::OpenSSL::RSA->generate_key($BITS, 17) }; SKIP: { skip "OpenSSL rejected exponent 17: $@", 4 if $@; ok($rsa, "generate_key with exponent 17"); ok($rsa->check_key(), "e=17 key passes check_key"); # Verify encrypt/decrypt works $rsa->use_pkcs1_oaep_padding(); my $ct = $rsa->encrypt("hello"); my $pt = $rsa->decrypt($ct); is($pt, "hello", "e=17 key encrypt/decrypt round-trip"); SKIP: { skip "Crypt::OpenSSL::Bignum not available", 1 unless $HAS_BIGNUM; my (undef, $e) = $rsa->get_key_parameters(); is($e->to_decimal(), "17", "exponent is 17"); } } } # --- Valid exponent: 257 --- { my $rsa = eval { Crypt::OpenSSL::RSA->generate_key($BITS, 257) }; SKIP: { skip "OpenSSL rejected exponent 257: $@", 2 if $@; ok($rsa, "generate_key with exponent 257"); ok($rsa->check_key(), "e=257 key passes check_key"); } } # --- Invalid exponent: even number (2) --- # OpenSSL 1.1.x may loop forever on invalid exponents instead of # rejecting them, so we use alarm() to avoid hanging CI. { my $rsa = eval { local $SIG{ALRM} = sub { die "timeout\n" }; alarm(10); my $r = Crypt::OpenSSL::RSA->generate_key($BITS, 2); alarm(0); $r; }; alarm(0); ok(!$rsa || $@, "exponent 2 (even) is rejected or times out"); } # --- Invalid exponent: 1 --- { my $rsa = eval { local $SIG{ALRM} = sub { die "timeout\n" }; alarm(10); my $r = Crypt::OpenSSL::RSA->generate_key($BITS, 1); alarm(0); $r; }; alarm(0); ok(!$rsa || $@, "exponent 1 is rejected or times out"); } # --- Key reuse after error --- # Generate a key, trigger an eval-caught error, then use the key again. # Validates the key object isn't corrupted by a caught failure. { my $rsa = Crypt::OpenSSL::RSA->generate_key($BITS); $rsa->use_pkcs1_oaep_padding(); # Trigger an error: plaintext too long for OAEP my $too_long = "x" x ($rsa->size()); eval { $rsa->encrypt($too_long) }; ok($@, "encrypt with oversized plaintext fails as expected"); # Key should still work for a valid operation my $ct = eval { $rsa->encrypt("works after error") }; ok(!$@, "key is reusable after caught encrypt error") or diag $@; my $pt = eval { $rsa->decrypt($ct) }; is($pt, "works after error", "decrypt succeeds after prior error"); } # --- Hash mode switching --- # Verify that changing hash modes on a key object works correctly. { my $rsa = Crypt::OpenSSL::RSA->generate_key($BITS); $rsa->use_pkcs1_pss_padding(); my $msg = "hash switching test"; $rsa->use_sha256_hash(); my $sig256 = $rsa->sign($msg); ok($rsa->verify($msg, $sig256), "SHA256 sign/verify after mode set"); # SHA1 signing may be disabled on FIPS-like systems (e.g. almalinux:9) SKIP: { $rsa->use_sha1_hash(); my $sig1 = eval { $rsa->sign($msg) }; skip "SHA1 signing not available: $@", 2 if $@; ok($rsa->verify($msg, $sig1), "SHA1 sign/verify after switching from SHA256"); # SHA256 signature should NOT verify under SHA1 my $result = eval { $rsa->verify($msg, $sig256) }; ok(!$result, "SHA256 signature fails under SHA1 mode"); } } # --- Key size validation --- { eval { Crypt::OpenSSL::RSA->generate_key(-1) }; like($@, qr/at least 512 bits/, "generate_key croaks on negative key size"); eval { Crypt::OpenSSL::RSA->generate_key(0) }; like($@, qr/at least 512 bits/, "generate_key croaks on zero key size"); eval { Crypt::OpenSSL::RSA->generate_key(256) }; like($@, qr/at least 512 bits/, "generate_key croaks on 256-bit key size"); eval { Crypt::OpenSSL::RSA->generate_key(511) }; like($@, qr/at least 512 bits/, "generate_key croaks on 511-bit key size"); my $rsa = eval { Crypt::OpenSSL::RSA->generate_key(512) }; ok($rsa && !$@, "generate_key accepts 512-bit key size (minimum)"); } Crypt-OpenSSL-RSA-0.41/t/private_crypt.t0000644000175000017500000001255315165307422016145 0ustar timtimuse strict; use Test::More; use Crypt::OpenSSL::Random; use Crypt::OpenSSL::RSA; # Tests for private_encrypt() and public_decrypt() which use different # OpenSSL code paths from encrypt()/decrypt(): # pre-3.x: RSA_private_encrypt / RSA_public_decrypt # 3.x: EVP_PKEY_sign / EVP_PKEY_verify_recover plan tests => 16; 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); my $rsa2 = Crypt::OpenSSL::RSA->generate_key(2048); my $key_size = $rsa->size(); my $rsa_pub = Crypt::OpenSSL::RSA->new_public_key($rsa->get_public_key_string()); # --- PKCS1 padding round-trip --- { $rsa->use_pkcs1_padding(); $rsa_pub->use_pkcs1_padding(); my $plaintext = "private_encrypt with PKCS1 padding"; my $ct = $rsa->private_encrypt($plaintext); ok(defined $ct && length($ct) > 0, "private_encrypt with PKCS1 padding produces output"); my $pt = $rsa_pub->public_decrypt($ct); is($pt, $plaintext, "public_decrypt recovers plaintext with PKCS1 padding"); } # --- No-padding round-trip --- { $rsa->use_no_padding(); $rsa_pub->use_no_padding(); # no_padding requires exactly key_size bytes my $plaintext = "\x00" x ($key_size - 1) . "\x42"; my $ct = $rsa->private_encrypt($plaintext); ok(defined $ct && length($ct) == $key_size, "private_encrypt with no padding produces key-sized output"); my $pt = $rsa_pub->public_decrypt($ct); is($pt, $plaintext, "public_decrypt recovers plaintext with no padding"); } # --- Binary data with embedded NUL bytes --- { $rsa->use_pkcs1_padding(); $rsa_pub->use_pkcs1_padding(); # PKCS1 overhead is 11 bytes, so max plaintext = key_size - 11 my $max_len = $key_size - 11; my $binary = "\x00\xFF\x00\x80\x00" . ("\xAB" x ($max_len - 6)) . "\x00"; my $ct = $rsa->private_encrypt($binary); my $pt = $rsa_pub->public_decrypt($ct); is($pt, $binary, "binary data with embedded NULs round-trips through private_encrypt/public_decrypt"); } # --- Max-length plaintext with PKCS1 --- { $rsa->use_pkcs1_padding(); $rsa_pub->use_pkcs1_padding(); my $max_len = $key_size - 11; # PKCS1 overhead my $plaintext = "X" x $max_len; my $ct = eval { $rsa->private_encrypt($plaintext) }; ok(!$@, "private_encrypt at max PKCS1 plaintext length ($max_len bytes) succeeds") or diag $@; SKIP: { skip "private_encrypt failed", 1 if $@; is($rsa_pub->public_decrypt($ct), $plaintext, "max-length PKCS1 plaintext round-trips correctly"); } } # --- Plaintext too long for PKCS1 --- { $rsa->use_pkcs1_padding(); my $too_long = "X" x ($key_size - 10); eval { $rsa->private_encrypt($too_long) }; ok($@, "private_encrypt with plaintext too long for PKCS1 croaks"); } # --- Cross-key: private_encrypt with key1, public_decrypt with key2 --- { $rsa->use_pkcs1_padding(); $rsa2->use_pkcs1_padding(); my $ct = $rsa->private_encrypt("cross key test"); eval { $rsa2->public_decrypt($ct) }; ok($@, "public_decrypt with wrong key croaks"); } # --- public_decrypt of garbage data --- { $rsa_pub->use_pkcs1_padding(); eval { $rsa_pub->public_decrypt("G" x $key_size) }; ok($@, "public_decrypt of garbage data croaks"); } # --- public_decrypt of truncated ciphertext --- { $rsa->use_pkcs1_padding(); $rsa_pub->use_pkcs1_padding(); my $ct = $rsa->private_encrypt("truncation test"); my $truncated = substr($ct, 0, length($ct) - 10); eval { $rsa_pub->public_decrypt($truncated) }; ok($@, "public_decrypt of truncated ciphertext croaks"); } # --- PSS padding rejected for private_encrypt --- { $rsa->use_pkcs1_pss_padding(); eval { $rsa->private_encrypt("pss test") }; ok($@, "PSS padding cannot be used with private_encrypt"); } # --- OAEP padding rejected for private_encrypt --- # OAEP is an encryption scheme, invalid for sign-type operations. { $rsa->use_pkcs1_oaep_padding(); eval { $rsa->private_encrypt("oaep test") }; ok($@, "OAEP padding cannot be used with private_encrypt"); } # --- Public key cannot private_encrypt --- { $rsa_pub->use_pkcs1_padding(); eval { $rsa_pub->private_encrypt("should fail") }; like($@, qr/Public keys cannot private_encrypt/, "public key cannot call private_encrypt"); } # --- Verify interop: private_encrypt produces data that sign() does not --- # private_encrypt operates on raw data; sign() hashes first. The outputs # for the same input should differ. { $rsa->use_pkcs1_padding(); my $msg = "interop test message"; my $ct = $rsa->private_encrypt($msg); $rsa->use_sha256_hash(); $rsa->use_pkcs1_padding(); my $sig = $rsa->sign($msg); isnt($ct, $sig, "private_encrypt and sign produce different outputs for same message"); } # --- Empty string with PKCS1 padding --- # Note: empty-string round-trip behavior varies across OpenSSL versions. # On 3.x, EVP_PKEY_verify_recover of a zero-length payload may return # a "provider signature failure" error. We only test that private_encrypt # succeeds; public_decrypt may legitimately fail on some versions. { $rsa->use_pkcs1_padding(); $rsa_pub->use_pkcs1_padding(); my $ct = eval { $rsa->private_encrypt("") }; ok(!$@, "private_encrypt of empty string with PKCS1 succeeds") or diag $@; } Crypt-OpenSSL-RSA-0.41/t/get_key_parameters.t0000644000175000017500000000326315165307422017122 0ustar timtimuse strict; use warnings; use Test::More; use Crypt::OpenSSL::Random; use Crypt::OpenSSL::RSA; Crypt::OpenSSL::Random::random_seed("OpenSSL needs at least 32 bytes."); Crypt::OpenSSL::RSA->import_random_seed(); plan tests => 10; my $rsa_priv = Crypt::OpenSSL::RSA->generate_key(2048); my $pub_pem = $rsa_priv->get_public_key_string(); my $rsa_pub = Crypt::OpenSSL::RSA->new_public_key($pub_pem); # --- Private key: all 8 parameters must be defined --- my @priv_params = eval { $rsa_priv->get_key_parameters() }; ok( !$@, "get_key_parameters on private key does not croak" ) or diag "Error: $@"; is( scalar @priv_params, 8, "get_key_parameters returns 8 values" ); my ($n, $e, $d, $p, $q, $dmp1, $dmq1, $iqmp) = @priv_params; ok( defined $n && defined $e, "private key: n and e are defined (mandatory public components)" ); ok( defined $d, "private key: d is defined (private exponent)" ); ok( defined $p && defined $q, "private key: p and q are defined (prime factors)" ); ok( defined $dmp1 && defined $dmq1 && defined $iqmp, "private key: CRT parameters are defined" ); # --- Public key: n and e defined, private components are undef --- my @pub_params = eval { $rsa_pub->get_key_parameters() }; ok( !$@, "get_key_parameters on public key does not croak" ) or diag "Error: $@"; is( scalar @pub_params, 8, "get_key_parameters returns 8 values for public key" ); my ($pn, $pe, $pd, $pp, $pq, $pdmp1, $pdmq1, $piqmp) = @pub_params; ok( defined $pn && defined $pe, "public key: n and e are defined" ); ok( !defined $pd && !defined $pp && !defined $pq && !defined $pdmp1 && !defined $pdmq1 && !defined $piqmp, "public key: private components (d, p, q, CRT) are undef" ); Crypt-OpenSSL-RSA-0.41/t/bignum.t0000644000175000017500000000775015165307422014536 0ustar timtimuse strict; use Test::More; use Crypt::OpenSSL::RSA; $INC{'Crypt/OpenSSL/Bignum.pm'} ? plan( tests => 64 ) : plan( skip_all => "Crypt::OpenSSL::Bignum required for bignum tests" ); my @PARAM_NAMES = qw(n e d p q dmp1 dmq1 iqmp); sub check_datum { my ( $p_expected, $p_actual, $name ) = @_; if ( defined($p_expected) ) { ok( $p_actual && $p_expected->equals($p_actual), $name ); } else { is( $p_actual, undef, $name ); } } sub check_key_parameters # runs 8 tests { my ( $p_rsa, $label, $n, $e, $d, $p, $q, $dmp1, $dmq1, $iqmp ) = @_; my ( $rn, $re, $rd, $rp, $rq, $rdmp1, $rdmq1, $riqmp ) = $p_rsa->get_key_parameters(); my @expected = ( $n, $e, $d, $p, $q, $dmp1, $dmq1, $iqmp ); my @actual = ( $rn, $re, $rd, $rp, $rq, $rdmp1, $rdmq1, $riqmp ); for my $i ( 0 .. 7 ) { check_datum( $expected[$i], $actual[$i], "$label: $PARAM_NAMES[$i] matches expected" ); } } { my $ctx = Crypt::OpenSSL::Bignum::CTX->new(); my $one = Crypt::OpenSSL::Bignum->one(); my $p = Crypt::OpenSSL::Bignum->new_from_word(65521); my $q = Crypt::OpenSSL::Bignum->new_from_word(65537); my $e = Crypt::OpenSSL::Bignum->new_from_word(11); my $d = $e->mod_inverse( $p->sub($one)->mul( $q->sub($one), $ctx ), $ctx ); my $n = $p->mul( $q, $ctx ); my $dmp1 = $d->mod( $p->sub($one), $ctx ); my $dmq1 = $d->mod( $q->sub($one), $ctx ); my $iqmp = $q->mod_inverse( $p, $ctx ); my $rsa = Crypt::OpenSSL::RSA->new_key_from_parameters( $n, $e, $d, $p, $q ); ok( $rsa, "new_key_from_parameters(n,e,d,p,q) returns an object" ); $rsa->use_no_padding(); 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, "full key", $n, $e, $d, $p, $q, $dmp1, $dmq1, $iqmp ); is( $rsa->encrypt($plaintext), $ciphertext, "encrypt produces expected ciphertext" ); is( $rsa->decrypt($ciphertext), $plaintext, "decrypt recovers original plaintext" ); my $rsa_pub = Crypt::OpenSSL::RSA->new_public_key( $rsa->get_public_key_string() ); $rsa_pub->use_no_padding(); is( $rsa->private_encrypt($ciphertext), $plaintext, "private_encrypt produces expected plaintext" ); is( $rsa_pub->public_decrypt($plaintext), $ciphertext, "public_decrypt produces expected ciphertext" ); my @pub_parameters = $rsa_pub->get_key_parameters(); is( scalar(@pub_parameters), 8, "public key returns 8 parameters" ); check_key_parameters( $rsa_pub, "public key", $n, $e ); $rsa = Crypt::OpenSSL::RSA->new_key_from_parameters( $n, $e, $d, $p ); check_key_parameters( $rsa, "from (n,e,d,p)", $n, $e, $d, $p, $q, $dmp1, $dmq1, $iqmp ); $rsa = Crypt::OpenSSL::RSA->new_key_from_parameters( $n, $e, $d, undef, $q ); check_key_parameters( $rsa, "from (n,e,d,undef,q)", $n, $e, $d, $p, $q, $dmp1, $dmq1, $iqmp ); $rsa = Crypt::OpenSSL::RSA->new_key_from_parameters( $n, $e ); check_key_parameters( $rsa, "from (n,e)", $n, $e ); $rsa = Crypt::OpenSSL::RSA->new_key_from_parameters( $n, $e, $d ); check_key_parameters( $rsa, "from (n,e,d)", $n, $e, $d ); $rsa = Crypt::OpenSSL::RSA->new_key_from_parameters( $n, $e, undef, $p ); check_key_parameters( $rsa, "from (n,e,undef,p)", $n, $e, $d, $p, $q, $dmp1, $dmq1, $iqmp ); eval { Crypt::OpenSSL::RSA->new_key_from_parameters( $n->sub( Crypt::OpenSSL::Bignum->one() ), $e, $d, undef, $q ); }; like( $@, qr/OpenSSL error: (?:p not prime|d e not congruent to 1)/, "bad n with q triggers key validation error" ); #try again, to make sure the error queue was properly flushed eval { Crypt::OpenSSL::RSA->new_key_from_parameters( $n->sub( Crypt::OpenSSL::Bignum->one() ), $e, $d, undef, $q ); }; like( $@, qr/OpenSSL error: (?:p not prime|d e not congruent to 1)/, "error queue flushed: repeat triggers same error" ); } Crypt-OpenSSL-RSA-0.41/t/z_meta.t0000644000175000017500000000130314606636707014532 0ustar timtim# -*- perl -*- # Test that our META.yml file matches the current specification. use strict; BEGIN { $| = 1; $^W = 1; } my $MODULE = 'Test::CPAN::Meta 0.12'; # Don't run tests for installs use Test::More; use Config; plan skip_all => 'This test is only run for the module author' unless -d '.git' || $ENV{IS_MAINTAINER}; plan skip_all => 'Test::CPAN::Meta fails with clang -faddress-sanitizer' if $Config{ccflags} =~ /-faddress-sanitizer/; # Load the testing module eval "use $MODULE;"; if ($@) { plan( skip_all => "$MODULE not available for testing" ); die "Failed to load required release-testing module $MODULE 0.12" if -d '.git' || $ENV{IS_MAINTAINER}; } meta_yaml_ok(); Crypt-OpenSSL-RSA-0.41/t/check_param.t0000644000175000017500000000437015165307422015505 0ustar timtimuse strict; use Test::More; use Crypt::OpenSSL::Random; use Crypt::OpenSSL::RSA; Crypt::OpenSSL::Random::random_seed("OpenSSL needs at least 32 bytes."); Crypt::OpenSSL::RSA->import_random_seed(); my $HAS_BIGNUM = $INC{'Crypt/OpenSSL/Bignum.pm'} ? 1 : 0; $HAS_BIGNUM ? plan( tests => 9 ) : plan( skip_all => "Crypt::OpenSSL::Bignum required for check_param tests" ); my $rsa = Crypt::OpenSSL::RSA->generate_key(2048); my ( $n, $e, $d, $p, $q ) = $rsa->get_key_parameters(); # 1. check=>1 with valid full params — should succeed { my $key = eval { Crypt::OpenSSL::RSA->new_key_from_parameters( $n, $e, $d, $p, $q, check => 1 ); }; ok( !$@, "check=>1 with valid params does not croak" ) or diag("Error: $@"); ok( $key && $key->is_private(), "check=>1 returns valid private key" ); } # 2. check=>1 with public-only key — should succeed (check skipped) { my $key = eval { Crypt::OpenSSL::RSA->new_key_from_parameters( $n, $e, undef, undef, undef, check => 1 ); }; ok( !$@, "check=>1 with public-only key does not croak" ) or diag("Error: $@"); ok( $key && !$key->is_private(), "check=>1 returns public key without validation" ); } # 3. check=>1 with bad d — should croak { my $bad_d = Crypt::OpenSSL::Bignum->new_from_word(12345); eval { Crypt::OpenSSL::RSA->new_key_from_parameters( $n, $e, $bad_d, $p, $q, check => 1 ); }; ok( $@, "check=>1 with bad d croaks" ); like( $@, qr/(?:check failed|OpenSSL error)/i, "error message mentions check failure or OpenSSL error" ); } # 4. Without check option — no extra validation at Perl level { my $key = eval { Crypt::OpenSSL::RSA->new_key_from_parameters( $n, $e, $d, $p, $q ); }; ok( !$@, "without check option, valid params succeed as before" ); } # 5. check_key() returns exactly 1, not just truthy # OpenSSL's RSA_check_key/EVP_PKEY_private_check can return -1 on error, # which is truthy in Perl. The XS code must normalize to 0/1. { cmp_ok( $rsa->check_key(), '==', 1, "check_key returns exactly 1 for valid key (not raw OpenSSL int)" ); ok( ref(\($rsa->check_key())) ne 'GLOB', "check_key returns a plain scalar" ); } Crypt-OpenSSL-RSA-0.41/RSA.pm0000644000175000017500000004071115172615427013607 0ustar timtimpackage Crypt::OpenSSL::RSA; use strict; use warnings; use Carp; # Removing carp will break the XS code. use Crypt::OpenSSL::Bignum; our $VERSION = '0.41'; use XSLoader; XSLoader::load 'Crypt::OpenSSL::RSA', $VERSION; BEGIN { eval { local $SIG{__DIE__}; # prevent outer handlers from being called require Crypt::OpenSSL::Bignum; }; } ## no critic qw(RequireCheckingReturnValueOfEval); sub new_public_key { my ( $proto, $p_key_string ) = @_; croak "unrecognized key format: expected PEM-encoded key (starting with '-----BEGIN') " . "or DER-encoded key (binary ASN.1 data)" unless defined $p_key_string && length($p_key_string) > 0; 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); } elsif ( $p_key_string =~ /^-----/ ) { croak "unrecognized key format: PEM header not recognized as RSA public key. " . "Expected '-----BEGIN RSA PUBLIC KEY-----' (PKCS#1) or " . "'-----BEGIN PUBLIC KEY-----' (X.509)"; } elsif ( substr($p_key_string, 0, 1) eq "\x30" ) { # ASN.1 SEQUENCE tag detected — likely DER-encoded key. # Search for the RSA OID (1.2.840.113549.1.1.1) in raw binary to distinguish # X.509 SubjectPublicKeyInfo from PKCS#1 RSAPublicKey. if (index($p_key_string, "\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x01") >= 0) { # RSA encryption OID found — X.509 SubjectPublicKeyInfo return $proto->_new_public_key_x509_der($p_key_string); } else { # No OID — assume PKCS#1 RSAPublicKey, let OpenSSL reject invalid data return $proto->_new_public_key_pkcs1_der($p_key_string); } } else { croak "unrecognized key format: expected PEM-encoded key (starting with '-----BEGIN') " . "or DER-encoded key (binary ASN.1 data)"; } } sub new_private_key { my ( $proto, $p_key_string, @rest ) = @_; croak "unrecognized key format: expected PEM-encoded key (starting with '-----BEGIN') " . "or DER-encoded key (binary ASN.1 data)" unless defined $p_key_string && length($p_key_string) > 0; if ( $p_key_string =~ /^-----/ ) { return $proto->_new_private_key_pem($p_key_string, @rest); } elsif ( substr($p_key_string, 0, 1) eq "\x30" ) { # ASN.1 SEQUENCE tag detected — likely DER-encoded private key. return $proto->_new_private_key_der($p_key_string, @rest); } else { croak "unrecognized key format: expected PEM-encoded key (starting with '-----BEGIN') " . "or DER-encoded key (binary ASN.1 data)"; } } sub new_key_from_parameters { my ( $proto, $n, $e, $d, $p, $q, %opts ) = @_; my $rsa = $proto->_new_key_from_parameters( map { $_ ? $_->pointer_copy() : 0 } $n, $e, $d, $p, $q ); if ( $opts{check} && $rsa && $rsa->is_private() ) { $rsa->check_key() or croak("RSA key check failed: inconsistent key parameters"); } return $rsa; } 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(); } *get_public_key_pkcs1_string = \&get_public_key_string; unless ( defined &use_sslv23_padding ) { *use_sslv23_padding = sub { croak( "use_sslv23_padding is not available: " . "SSLv23 padding was removed in OpenSSL 3.x. " . "Use use_pkcs1_oaep_padding() for encryption " . "or use_pkcs1_pss_padding() for signatures instead." ); }; } 1; __END__ =for markdown [![testsuite](https://github.com/cpan-authors/Crypt-OpenSSL-RSA/actions/workflows/testsuite.yml/badge.svg)](https://github.com/cpan-authors/Crypt-OpenSSL-RSA/actions/workflows/testsuite.yml) =head1 NAME Crypt::OpenSSL::RSA - RSA encoding and decoding, using the openSSL libraries =head1 SYNOPSIS use Crypt::OpenSSL::Random; use Crypt::OpenSSL::RSA; # not necessary if we have /dev/random: Crypt::OpenSSL::Random::random_seed($good_entropy); Crypt::OpenSSL::RSA->import_random_seed(); $rsa_pub = Crypt::OpenSSL::RSA->new_public_key($key_string); $ciphertext = $rsa->encrypt($plaintext); $rsa_priv = Crypt::OpenSSL::RSA->new_private_key($key_string); $plaintext = $rsa->decrypt($ciphertext); $rsa = Crypt::OpenSSL::RSA->generate_key(1024); # or $rsa = Crypt::OpenSSL::RSA->generate_key(1024, $prime); print "private key is:\n", $rsa->get_private_key_string(); print "public key (in PKCS1 format) is:\n", $rsa->get_public_key_string(); print "public key (in X509 format) is:\n", $rsa->get_public_key_x509_string(); $rsa_priv->use_md5_hash(); # insecure. use_sha256_hash or use_sha1_hash are the default $signature = $rsa_priv->sign($plaintext); print "Signed correctly\n" if ($rsa->verify($plaintext, $signature)); =head1 SECURITY Version 0.35 disabled PKCS#1 v1.5 padding entirely to mitigate the Marvin attack. However, the Marvin attack only affects PKCS#1 v1.5 B (padding oracle), not B. Version 0.38 re-enables C for use with C and C, while keeping it disabled for C and C. PKCS1_OAEP should be used for encryption and either PKCS1_PSS or PKCS1 can be used for signing. =head1 DESCRIPTION C provides the ability to RSA encrypt strings which are somewhat shorter than the block size of a key. It also allows for decryption, signatures and signature verification. I: Many of the methods in this package can croak, so use C, or Error.pm's try/catch mechanism to capture errors. Also, while some methods from earlier versions of this package return true on success, this (never documented) behavior is no longer the case. =head1 Class Methods =over =item new_public_key Create a new C object by loading a public key in from a string containing either PEM or DER encoding of the PKCS#1 or X.509 representation of the key. For PEM keys, the string should include the C<-----BEGIN...-----> and C<-----END...-----> lines. Both C (PKCS#1) and C (X.509/SubjectPublicKeyInfo) formats are supported. DER-encoded keys (raw binary ASN.1) are also accepted and the format (PKCS#1 vs X.509) is auto-detected. The padding is set to PKCS1_OAEP, but can be changed with the C methods. Note, PKCS1_OAEP can only be used for encryption. Call C or C prior to signing operations. =item new_private_key Create a new C object by loading a private key in from a string containing either PEM or DER encoding of the key. For PEM keys, the string should include the C<-----BEGIN...-----> and C<-----END...-----> lines. The padding is set to PKCS1_OAEP, but can be changed with C. DER-encoded keys (raw binary ASN.1) are also accepted. An optional parameter can be passed for passphrase-protected private keys: =over =item passphrase The passphrase which protects the private key. For PEM keys, this decrypts traditional encrypted PEM (C header) and encrypted PKCS#8 PEM (C). For DER keys, this decrypts encrypted PKCS#8 DER (C ASN.1 structure). =back =item generate_key Create a new C object by constructing a private/public key pair. The first (mandatory) argument is the key size, while the second optional argument specifies the public exponent (the default public exponent is 65537). The padding is set to C, but can be changed with use_xxx_padding methods. =item new_key_from_parameters Given L objects for n, e, and optionally d, p, and q, where p and q are the prime factors of n, e is the public exponent and d is the private exponent, create a new Crypt::OpenSSL::RSA object using these values. If p and q are 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. An optional C 1> parameter can be passed after the key components to validate the key immediately after construction: my $rsa = Crypt::OpenSSL::RSA->new_key_from_parameters( $n, $e, $d, $p, $q, check => 1 ); When enabled, C is called on the resulting key. If the key parameters are inconsistent (e.g. wrong CRT values, mismatched n/e/d/p/q), the constructor will croak instead of returning an object that fails at first use. The check is only performed on private keys; public-only keys (n and e only) are returned without validation. =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. =back =head1 Instance Methods =over =item DESTROY Clean up after ourselves. In particular, erase and free the memory occupied by the RSA key structure. =item get_public_key_string Return the Base64/DER-encoded PKCS1 representation of the public key. This string has header and footer lines: -----BEGIN RSA PUBLIC KEY------ -----END RSA PUBLIC KEY------ =item get_public_key_pkcs1_string Alias for C. Returns the same PKCS#1 C PEM format (C). Provided for naming symmetry with the import method C (which auto-detects PKCS#1 vs X.509) and with C. =item get_public_key_x509_string Return the Base64/DER-encoded representation of the "subject public key", suitable for use in X509 certificates. This string has header and footer lines: -----BEGIN PUBLIC KEY------ -----END PUBLIC KEY------ and is the format that is produced by running C. =item get_private_key_string Return the Base64/DER-encoded PKCS1 representation of the private key. This string has header and footer lines: -----BEGIN RSA PRIVATE KEY------ -----END RSA PRIVATE KEY------ 2 optional parameters can be passed for passphrase protected private key string: =over =item passphrase The passphrase which protects the private key. =item cipher name The cipher algorithm used to protect the private key. Default to 'des3'. =back =item get_private_key_pkcs8_string Return the Base64/DER-encoded PKCS#8 representation of the private key. This string has header and footer lines: -----BEGIN PRIVATE KEY----- -----END PRIVATE KEY----- This is the format produced by C, and is the private-key counterpart of C. Accepts the same optional passphrase and cipher-name parameters as C. =item encrypt Encrypt a binary "string" using the public (portion of the) key. =item decrypt Decrypt a binary "string". Croaks if the key is public only. =item private_encrypt Encrypt a binary "string" using the private key. Croaks if the key is public only. On OpenSSL 3.x, only C and C are supported; OAEP and PSS will croak. =item public_decrypt Decrypt a binary "string" using the public (portion of the) key. On OpenSSL 3.x, only C and C are supported; OAEP and PSS will croak. =item sign Sign a string using the secret (portion of the) key. =item verify Check the signature on a text. =back =head1 Padding Methods B can be used for signature operations (C and C). PKCS#1 v1.5 encryption is disabled due to the Marvin attack. B is the recommended replacement for signatures. B is used for encryption operations. On OpenSSL 3.x, the appropriate padding is set for each operation unless B or B is called before the operation. =over =item 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. =item use_pkcs1_padding Use C padding for B. PKCS#1 v1.5 signatures (RSASSA-PKCS1-v1.5) are secure and widely required by protocols such as JWT RS256, ACME (RFC 8555), and SAML. B: PKCS#1 v1.5 B is disabled because it is vulnerable to the L (a timing side-channel on decryption padding validation). Calling C or C with this padding will croak. Use C for encryption. =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 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 Use C padding with an SSL-specific modification that denotes that the server is SSL3 capable. B Calling this method will croak with a descriptive error message suggesting alternatives. Use C for encryption or C for signatures. =back =head1 Hash/Digest Methods =over =item use_md5_hash Use the RFC 1321 MD5 hashing algorithm by Ron Rivest when signing and verifying messages. Note that this is considered B. =item use_sha1_hash Use the RFC 3174 Secure Hashing Algorithm (FIPS 180-1) when signing and verifying messages. This is the default, when use_sha256_hash is not available. =item use_sha224_hash, use_sha256_hash, use_sha384_hash, use_sha512_hash These FIPS 180-2 hash algorithms, for use when signing and verifying messages, are only available with newer openssl versions (>= 0.9.8). use_sha256_hash is the default hash mode when available. =item use_ripemd160_hash Dobbertin, Bosselaers and Preneel's RIPEMD hashing algorithm when signing and verifying messages. =item use_whirlpool_hash Vincent Rijmen und Paulo S. L. M. Barreto ISO/IEC 10118-3:2004 WHIRLPOOL hashing algorithm when signing and verifying messages. =item size Returns the size, in bytes, of the key. All encrypted text will be of this size, and depending on the padding mode used, the length of the text to be encrypted should be: =over =item pkcs1_oaep_padding at most 42 bytes less than this size. =item pkcs1_padding or sslv23_padding at most 11 bytes less than this size. =item no_padding exactly this size. =back =item check_key This function validates the RSA key, returning a true value if the key is valid, and a false value otherwise. Croaks if the key is public only. =item get_key_parameters Return C objects representing the values of C, C, C, C

, C, C, C, and C<1/q mod p>, where C

and C are the prime factors of C, C is the public exponent and C is the private exponent. Some of these values may return as C; only C and C will be defined for a public key. The C module must be installed for this to work. =item is_private Return true if this is a private key, and false if it is public only. =back =head1 AUTHOR Ian Robertson, C. For support, please email C. =head1 ACKNOWLEDGEMENTS =head1 LICENSE Copyright (c) 2001-2011 Ian Robertson. Crypt::OpenSSL::RSA is free software; you may redistribute it and/or modify it under the same terms as Perl itself. =head1 SEE ALSO L, L, L, L, L, L, L, L, L =cut Crypt-OpenSSL-RSA-0.41/hints/0000755000175000017500000000000015172615445013746 5ustar timtimCrypt-OpenSSL-RSA-0.41/hints/MSWin32.pl0000644000175000017500000000066214606636707015456 0ustar timtimuse Config; use Crypt::OpenSSL::Guess 0.11 qw(openssl_lib_paths); if (my $libs = `pkg-config --libs libssl libcrypto 2>nul`) { # strawberry perl has pkg-config $self->{LIBS} = [openssl_lib_paths() . " $libs"]; } else { $self->{LIBS} = [openssl_lib_paths() . '-lssleay32 -llibeay32'] if $Config{cc} =~ /cl/; # msvc with ActivePerl $self->{LIBS} = [openssl_lib_paths() . '-lssl32 -leay32'] if $Config{gccversion}; # gcc } Crypt-OpenSSL-RSA-0.41/MANIFEST0000644000175000017500000000125615172615445013756 0ustar timtimAI_POLICY.md Changes CONTRIBUTING.md hints/MSWin32.pl LICENSE Makefile.PL MANIFEST MANIFEST.SKIP README.md RSA.pm RSA.xs SECURITY.md t/bignum.t t/check_param.t t/crypto.t t/der.t t/error.t t/error_queue.t t/fakelib/Crypt/OpenSSL/Bignum.pm t/format.t t/get_key_parameters.t t/key_lifecycle.t t/keygen.t t/openssl_der.t t/padding.t t/pkcs1_sign.t t/private_crypt.t t/private_encrypt.t t/pss_auto_promote.t t/rsa.t t/sign_verify.t t/z_kwalitee.t t/z_meta.t t/z_perl_minimum_version.t t/z_pod-coverage.t t/z_pod.t typemap META.yml Module YAML meta-data (added by MakeMaker) META.json Module JSON meta-data (added by MakeMaker) Crypt-OpenSSL-RSA-0.41/Makefile.PL0000644000175000017500000000441515172443224014571 0ustar timtimuse strict; use warnings; use Config; use 5.006; use ExtUtils::MakeMaker 6.64; 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 ', 'VERSION_FROM' => 'RSA.pm', # finds $VERSION 'DISTNAME' => 'Crypt-OpenSSL-RSA', 'ABSTRACT_FROM' => 'RSA.pm', 'MIN_PERL_VERSION' => 5.006, 'PL_FILES' => {}, 'LICENSE' => 'perl', 'CONFIGURE_REQUIRES' => { "Crypt::OpenSSL::Guess" => 0.11, }, 'PREREQ_PM' => { 'Crypt::OpenSSL::Bignum' => 0, 'Crypt::OpenSSL::Random' => 0, }, 'TEST_REQUIRES' => { 'Test::More' => 0, }, 'OBJECT' => 'RSA.o', 'LIBS' => [openssl_lib_paths() . $libs], 'LDDLFLAGS' => openssl_lib_paths() . ' ' . $Config{lddlflags}, 'INC' => openssl_inc_paths(), # e.g., '-I/usr/include/other' 'dist' => { COMPRESS => 'gzip -9f', SUFFIX => 'gz', }, 'clean' => { FILES => 'Crypt-OpenSSL-RSA-*' }, 'META_MERGE' => { 'meta-spec' => { version => 2 }, provides => { 'Crypt::OpenSSL::RSA' => { file => 'RSA.pm', version => MM->parse_version('RSA.pm'), }, }, configure_requires => { 'Crypt::OpenSSL::Guess' => '0.11', }, resources => { 'license' => ['https://dev.perl.org/licenses/'], 'homepage' => 'https://github.com/cpan-authors/Crypt-OpenSSL-RSA', 'bugtracker' => { 'web' => 'https://github.com/cpan-authors/Crypt-OpenSSL-RSA/issues', }, 'repository' => { 'type' => 'git', 'url' => 'https://github.com/cpan-authors/Crypt-OpenSSL-RSA.git', 'web' => 'https://github.com/cpan-authors/Crypt-OpenSSL-RSA', }, } } ); Crypt-OpenSSL-RSA-0.41/MANIFEST.SKIP0000644000175000017500000000012415165307422014507 0ustar timtim^\.perltidyrc$ ^cpanfile$ ^Makefile$ ^\.git ^MYMETA\.* ^CLAUDE\.md$ ^MANIFEST\.bak$ Crypt-OpenSSL-RSA-0.41/typemap0000644000175000017500000000036014606636707014227 0ustar timtimTYPEMAP BIGNUM* T_PTR rsaData* O_OBJECT INPUT O_OBJECT if (!(SvROK($arg) && sv_derived_from($arg, PACKAGE_NAME))) { croak(\"argument is not a ${type} object\"); } $var = (${type}) SvIV(SvRV($arg)); OUTPUT Crypt-OpenSSL-RSA-0.41/META.yml0000664000175000017500000000174115172615445014077 0ustar timtim--- abstract: 'RSA encoding and decoding, using the openSSL libraries' author: - 'Ian Robertson ' build_requires: ExtUtils::MakeMaker: '0' Test::More: '0' configure_requires: Crypt::OpenSSL::Guess: '0.11' dynamic_config: 1 generated_by: 'ExtUtils::MakeMaker version 7.78, CPAN::Meta::Converter version 2.150013' license: perl meta-spec: url: http://module-build.sourceforge.net/META-spec-v1.4.html version: '1.4' name: Crypt-OpenSSL-RSA no_index: directory: - t - inc provides: Crypt::OpenSSL::RSA: file: RSA.pm version: '0.41' requires: Crypt::OpenSSL::Bignum: '0' Crypt::OpenSSL::Random: '0' perl: '5.006' resources: bugtracker: https://github.com/cpan-authors/Crypt-OpenSSL-RSA/issues homepage: https://github.com/cpan-authors/Crypt-OpenSSL-RSA license: https://dev.perl.org/licenses/ repository: https://github.com/cpan-authors/Crypt-OpenSSL-RSA.git version: '0.41' x_serialization_backend: 'CPAN::Meta::YAML version 0.020' Crypt-OpenSSL-RSA-0.41/RSA.xs0000644000175000017500000012631715172443224013626 0ustar timtim#include "EXTERN.h" #include "perl.h" #include "XSUB.h" #include #include #include #include #include #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 #include #endif /* Pre-3.x helper for PKCS#8 export: wraps RSA* in a real EVP_PKEY and writes PKCS#8 PEM. Defined BEFORE the EVP_PKEY->RSA compatibility macros so that EVP_PKEY, EVP_PKEY_new, EVP_PKEY_free, and PEM_write_bio_PrivateKey resolve to their real OpenSSL symbols. */ #if OPENSSL_VERSION_NUMBER < 0x30000000L static int _write_pkcs8_pem(BIO* bio, RSA* rsa, const EVP_CIPHER* enc, unsigned char* pass, int passlen) { EVP_PKEY* pkey = EVP_PKEY_new(); int ok; if (!pkey) return 0; if (!EVP_PKEY_set1_RSA(pkey, rsa)) { EVP_PKEY_free(pkey); return 0; } ok = PEM_write_bio_PrivateKey(bio, pkey, enc, pass, passlen, NULL, NULL); EVP_PKEY_free(pkey); return ok; } #endif /* Pre-3.x helper for loading encrypted PKCS#8 DER private keys. Placed BEFORE the EVP_PKEY->RSA compatibility macros so that EVP_PKEY, EVP_PKEY_free, and EVP_PKEY_get1_RSA resolve to their real OpenSSL symbols. */ #if OPENSSL_VERSION_NUMBER < 0x30000000L static RSA* _load_pkcs8_der_key(BIO* bio, const char* passphrase) { EVP_PKEY* pkey; RSA* rsa; pkey = d2i_PKCS8PrivateKey_bio(bio, NULL, NULL, (void*)passphrase); if (!pkey) return NULL; rsa = EVP_PKEY_get1_RSA(pkey); EVP_PKEY_free(pkey); return rsa; } #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 { EVP_PKEY* rsa; int padding; int hashMode; int is_private_key; /* cached once at construction; avoids per-call BIGNUM alloc on 3.x */ } rsaData; /* Key names for the rsa hash structure */ #define KEY_KEY "_Key" #define PADDING_KEY "_Padding" #define HASH_KEY "_Hash_Mode" #define PACKAGE_NAME "Crypt::OpenSSL::RSA" #ifdef LIBRESSL_VERSION_NUMBER #define OLD_CRUFTY_SSL_VERSION (OPENSSL_VERSION_NUMBER < 0x10100000L || LIBRESSL_VERSION_NUMBER < 0x03050000fL) #else #define OLD_CRUFTY_SSL_VERSION (OPENSSL_VERSION_NUMBER < 0x10100000L) #endif void croakSsl(char* p_file, int p_line) { const char* errorReason; unsigned long last_err = 0; unsigned long err; /* Drain the error queue and use the last (most recent) error, which is typically the most descriptive. This also prevents stale errors from a previous eval-caught failure from leaking into the next croak message. */ while ((err = ERR_get_error()) != 0) { last_err = err; } errorReason = ERR_reason_error_string(last_err); croak("%s:%d: OpenSSL error: %s", p_file, p_line, errorReason ? errorReason : "(unknown error)"); } #define CHECK_OPEN_SSL(p_result) if (!(p_result)) croakSsl(__FILE__, __LINE__); #define CHECK_OPEN_SSL_BIO(p_result, bio) \ if (!(p_result)) { BIO_free(bio); croakSsl(__FILE__, __LINE__); } #define PACKAGE_CROAK(p_message) croak("%s", (p_message)) #define CHECK_NEW(p_var, p_size, p_type) \ if (New(0, p_var, p_size, p_type) == NULL) \ { PACKAGE_CROAK("unable to alloc buffer"); } #define THROW(p_result) if (!(p_result)) { error = 1; goto err; } char _is_private(rsaData* p_rsa) { return p_rsa->is_private_key; } static int _detect_private_key(EVP_PKEY* p_rsa) { #if OLD_CRUFTY_SSL_VERSION return (p_rsa->d != NULL); #else #if OPENSSL_VERSION_NUMBER >= 0x30000000L BIGNUM* d = NULL; EVP_PKEY_get_bn_param(p_rsa, OSSL_PKEY_PARAM_RSA_D, &d); if (d) { BN_clear_free(d); return 1; } return 0; #else const BIGNUM* d = NULL; RSA_get0_key(p_rsa, NULL, NULL, &d); return (d != NULL); #endif #endif } 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; rsa->is_private_key = _detect_private_key(p_rsa); return sv_bless( newRV_noinc(newSViv((IV) rsa)), (SvROK(p_proto) ? SvSTASH(SvRV(p_proto)) : gv_stashsv(p_proto, 1))); } int get_digest_length(int hash_method) { switch(hash_method) { case NID_md5: return MD5_DIGEST_LENGTH; break; case NID_sha1: return SHA_DIGEST_LENGTH; break; #ifdef SHA512_DIGEST_LENGTH case NID_sha224: return SHA224_DIGEST_LENGTH; break; case NID_sha256: return SHA256_DIGEST_LENGTH; break; case NID_sha384: return SHA384_DIGEST_LENGTH; break; case NID_sha512: return SHA512_DIGEST_LENGTH; break; #endif case NID_ripemd160: return RIPEMD160_DIGEST_LENGTH; break; #ifdef WHIRLPOOL_DIGEST_LENGTH case NID_whirlpool: return WHIRLPOOL_DIGEST_LENGTH; break; #endif default: croak("Unknown digest hash mode %u", hash_method); break; } } #if OPENSSL_VERSION_NUMBER >= 0x30000000L 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; } } /* Configure PSS/PKCS1 padding, signature digest, and MGF1 on an already-initialised * EVP_PKEY_CTX. On success returns 1 and sets *md_out to a freshly-fetched EVP_MD * that the caller must free with EVP_MD_free(). Returns 0 on any OpenSSL error. */ static int setup_pss_sign_ctx(EVP_PKEY_CTX *ctx, int padding, int hash_nid, EVP_MD **md_out) { int effective_pad = padding; EVP_MD *md = NULL; if (padding != RSA_NO_PADDING && padding != RSA_PKCS1_PADDING) effective_pad = RSA_PKCS1_PSS_PADDING; if (EVP_PKEY_CTX_set_rsa_padding(ctx, effective_pad) <= 0) return 0; md = get_md_bynid(hash_nid); if (!md) return 0; if (EVP_PKEY_CTX_set_signature_md(ctx, md) <= 0) { EVP_MD_free(md); return 0; } if (effective_pad == RSA_PKCS1_PSS_PADDING) { if (EVP_PKEY_CTX_set_rsa_mgf1_md(ctx, md) <= 0 || EVP_PKEY_CTX_set_rsa_pss_saltlen(ctx, RSA_PSS_SALTLEN_DIGEST) <= 0) { EVP_MD_free(md); return 0; } } *md_out = md; return 1; } #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); #if OPENSSL_VERSION_NUMBER >= 0x30000000L /* Delegate NID→name lookup to get_md_bynid() — single source of truth. */ { EVP_MD *md_obj = get_md_bynid(hash_method); /* croak()s on unknown NID */ unsigned int result_len; int ok = EVP_Digest(text, text_length, md, &result_len, md_obj, NULL); EVP_MD_free(md_obj); return ok ? md : NULL; } #else switch(hash_method) { case NID_md5: return MD5(text, text_length, md); break; case NID_sha1: return SHA1(text, text_length, md); break; #ifdef SHA512_DIGEST_LENGTH case NID_sha224: return SHA224(text, text_length, md); break; case NID_sha256: return SHA256(text, text_length, md); break; case NID_sha384: return SHA384(text, text_length, md); break; case NID_sha512: return SHA512(text, text_length, md); break; #endif case NID_ripemd160: return RIPEMD160(text, text_length, md); break; #ifdef WHIRLPOOL_DIGEST_LENGTH case NID_whirlpool: return WHIRLPOOL(text, text_length, md); break; #endif default: croak("Unknown digest hash mode %u", hash_method); break; } #endif } SV* cor_bn2sv(const BIGNUM* p_bn) { return p_bn != NULL ? sv_2mortal(newSViv((IV) BN_dup(p_bn))) : &PL_sv_undef; } SV* extractBioString(BIO* p_stringBio) { SV* sv; char* datap; long datasize; int error = 0; THROW(BIO_flush(p_stringBio) == 1); datasize = BIO_get_mem_data(p_stringBio, &datap); THROW(datasize > 0); sv = newSVpv(datap, datasize); BIO_set_close(p_stringBio, BIO_CLOSE); BIO_free(p_stringBio); return sv; err: BIO_free(p_stringBio); CHECK_OPEN_SSL(0); return NULL; /* unreachable, CHECK_OPEN_SSL croaks */ } EVP_PKEY* _load_rsa_key(SV* p_keyStringSv, EVP_PKEY*(*p_loader)(BIO *, EVP_PKEY**, pem_password_cb*, void*), SV* p_passphraseSv) { STRLEN keyStringLength; char* keyString; UNSIGNED_CHAR *passphrase = NULL; EVP_PKEY* rsa; BIO* stringBIO; keyString = SvPV(p_keyStringSv, keyStringLength); if (SvPOK(p_passphraseSv)) { passphrase = (UNSIGNED_CHAR *)SvPV_nolen(p_passphraseSv); } CHECK_OPEN_SSL(stringBIO = BIO_new_mem_buf(keyString, keyStringLength)); rsa = p_loader(stringBIO, NULL, NULL, passphrase); CHECK_OPEN_SSL(BIO_set_close(stringBIO, BIO_CLOSE) == 1); BIO_free(stringBIO); CHECK_OPEN_SSL(rsa); #if OPENSSL_VERSION_NUMBER >= 0x30000000L /* On 3.x, PEM_read_bio_PrivateKey/PEM_read_bio_PUBKEY accept any key type (EC, DSA, etc.). Pre-3.x used RSA-specific loaders that would reject non-RSA keys at parse time. Validate here to preserve that behavior and give a clear error instead of confusing failures later. Also rejects RSA-PSS keys (EVP_PKEY_RSA_PSS) — this module only supports traditional RSA (EVP_PKEY_RSA). */ if (EVP_PKEY_get_base_id(rsa) != EVP_PKEY_RSA) { EVP_PKEY_free(rsa); croak("The key loaded is not an RSA key"); } #endif return rsa; } static void check_max_message_length(rsaData* p_rsa, STRLEN from_length) { int size; int max_len = -1; const char *pad_name = NULL; size = EVP_PKEY_get_size(p_rsa->rsa); if (p_rsa->padding == RSA_PKCS1_OAEP_PADDING) { max_len = size - 42; /* 2 * SHA1_DIGEST_LENGTH + 2 */ pad_name = "OAEP"; } else if (p_rsa->padding == RSA_PKCS1_PADDING) { max_len = size - 11; /* PKCS#1 v1.5 overhead */ pad_name = "PKCS#1 v1.5"; } else if (p_rsa->padding == RSA_NO_PADDING) { max_len = size; pad_name = "no"; } if (max_len >= 0 && from_length > (STRLEN) max_len) { croak("plaintext too long for key size with %s padding" " (%d bytes max, got %d)", pad_name, max_len, (int)from_length); } } #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 is_encrypt) #else SV* rsa_crypt(rsaData* p_rsa, SV* p_from, int (*p_crypt)(int, const unsigned char*, unsigned char*, RSA*, int), int is_encrypt) #endif { STRLEN from_length; SIZE_T_INT to_length; unsigned char* from; UNSIGNED_CHAR *to = NULL; SV* sv; #if OPENSSL_VERSION_NUMBER < 0x30000000L int size; #endif from = (unsigned char*) SvPV(p_from, from_length); if(is_encrypt && p_rsa->padding == RSA_PKCS1_PADDING) { croak("PKCS#1 v1.5 padding for encryption is vulnerable to the Marvin attack. " "Use use_pkcs1_oaep_padding() for encryption, or use_pkcs1_padding() with sign()/verify()."); } if(is_encrypt && 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."); } #if OPENSSL_VERSION_NUMBER >= 0x30000000L EVP_PKEY_CTX *ctx = NULL; int error = 0; if (is_encrypt) { /* Encryption path: OAEP is the only safe padding for encrypt/decrypt. */ if (p_rsa->padding != RSA_NO_PADDING && p_rsa->padding != RSA_PKCS1_OAEP_PADDING) { croak("Only OAEP padding or no padding is supported for encrypt/decrypt. " "Call \"use_pkcs1_oaep_padding()\" or \"use_no_padding()\" first."); } } else { /* Sign/verify_recover path (private_encrypt / public_decrypt): these are low-level RSA operations that respect the user's padding choice. OAEP and PSS are not valid here. */ if (p_rsa->padding == RSA_PKCS1_OAEP_PADDING) { croak("OAEP padding is not supported for private_encrypt/public_decrypt. " "Call use_no_padding() or use_pkcs1_padding() first."); } if (p_rsa->padding == RSA_PKCS1_PSS_PADDING) { croak("PSS padding with private_encrypt/public_decrypt is not supported. " "Use sign()/verify() for PSS signatures."); } } ctx = EVP_PKEY_CTX_new_from_pkey(NULL, (EVP_PKEY* )p_rsa->rsa, NULL); THROW(ctx); THROW(init_crypt(ctx) == 1); THROW(EVP_PKEY_CTX_set_rsa_padding(ctx, p_rsa->padding) > 0); THROW(p_crypt(ctx, NULL, &to_length, from, from_length) == 1); Newx(to, to_length, UNSIGNED_CHAR); THROW(to); THROW(p_crypt(ctx, to, &to_length, from, from_length) == 1); EVP_PKEY_CTX_free(ctx); goto crypt_done; err: if (ctx) EVP_PKEY_CTX_free(ctx); Safefree(to); CHECK_OPEN_SSL(0); crypt_done: #else size = EVP_PKEY_get_size(p_rsa->rsa); CHECK_NEW(to, size, UNSIGNED_CHAR); 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((char* ) to, to_length); Safefree(to); return sv; } MODULE = Crypt::OpenSSL::RSA PACKAGE = Crypt::OpenSSL::RSA PROTOTYPES: DISABLE BOOT: #if OPENSSL_VERSION_NUMBER < 0x10100000L # might introduce memory leak without calling EVP_cleanup() on exit # see https://wiki.openssl.org/index.php/Library_Initialization ERR_load_crypto_strings(); OpenSSL_add_all_algorithms(); #else # NOOP #endif SV* _new_private_key_pem(proto, key_string_SV, passphrase_SV=&PL_sv_undef) SV* proto; SV* key_string_SV; SV* passphrase_SV; CODE: RETVAL = make_rsa_obj( proto, _load_rsa_key(key_string_SV, PEM_read_bio_PrivateKey, passphrase_SV)); OUTPUT: RETVAL SV* _new_public_key_pkcs1(proto, key_string_SV) SV* proto; SV* key_string_SV; CODE: RETVAL = make_rsa_obj( proto, _load_rsa_key(key_string_SV, PEM_read_bio_RSAPublicKey, &PL_sv_undef)); OUTPUT: RETVAL SV* _new_public_key_x509(proto, key_string_SV) SV* proto; SV* key_string_SV; CODE: RETVAL = make_rsa_obj( proto, _load_rsa_key(key_string_SV, PEM_read_bio_RSA_PUBKEY, &PL_sv_undef)); OUTPUT: RETVAL SV* _new_public_key_x509_der(proto, key_string_SV) SV* proto; SV* key_string_SV; PREINIT: STRLEN keyStringLength; char* keyString; EVP_PKEY* pkey; BIO* bio; CODE: keyString = SvPV(key_string_SV, keyStringLength); CHECK_OPEN_SSL(bio = BIO_new_mem_buf(keyString, keyStringLength)); #if OPENSSL_VERSION_NUMBER >= 0x30000000L pkey = d2i_PUBKEY_bio(bio, NULL); #else pkey = d2i_RSA_PUBKEY_bio(bio, NULL); #endif BIO_free(bio); CHECK_OPEN_SSL(pkey); #if OPENSSL_VERSION_NUMBER >= 0x30000000L if (EVP_PKEY_get_base_id(pkey) != EVP_PKEY_RSA) { EVP_PKEY_free(pkey); croak("The key loaded is not an RSA key"); } #endif RETVAL = make_rsa_obj(proto, pkey); OUTPUT: RETVAL SV* _new_public_key_pkcs1_der(proto, key_string_SV) SV* proto; SV* key_string_SV; PREINIT: STRLEN keyStringLength; char* keyString; EVP_PKEY* pkey; BIO* bio; #if OPENSSL_VERSION_NUMBER >= 0x30000000L OSSL_DECODER_CTX* dctx; #endif CODE: keyString = SvPV(key_string_SV, keyStringLength); CHECK_OPEN_SSL(bio = BIO_new_mem_buf(keyString, keyStringLength)); #if OPENSSL_VERSION_NUMBER >= 0x30000000L pkey = NULL; dctx = OSSL_DECODER_CTX_new_for_pkey(&pkey, "DER", "type-specific", "RSA", OSSL_KEYMGMT_SELECT_PUBLIC_KEY, NULL, NULL); if (!dctx) { BIO_free(bio); croakSsl(__FILE__, __LINE__); } if (!OSSL_DECODER_from_bio(dctx, bio)) { OSSL_DECODER_CTX_free(dctx); BIO_free(bio); croakSsl(__FILE__, __LINE__); } OSSL_DECODER_CTX_free(dctx); #else pkey = d2i_RSAPublicKey_bio(bio, NULL); #endif BIO_free(bio); CHECK_OPEN_SSL(pkey); RETVAL = make_rsa_obj(proto, pkey); OUTPUT: RETVAL SV* _new_private_key_der(proto, key_string_SV, passphrase_SV=&PL_sv_undef) SV* proto; SV* key_string_SV; SV* passphrase_SV; PREINIT: STRLEN keyStringLength; char* keyString; EVP_PKEY* pkey; BIO* bio; #if OPENSSL_VERSION_NUMBER >= 0x30000000L OSSL_DECODER_CTX* dctx; #endif CODE: keyString = SvPV(key_string_SV, keyStringLength); CHECK_OPEN_SSL(bio = BIO_new_mem_buf(keyString, keyStringLength)); #if OPENSSL_VERSION_NUMBER >= 0x30000000L pkey = NULL; dctx = OSSL_DECODER_CTX_new_for_pkey(&pkey, "DER", NULL, "RSA", OSSL_KEYMGMT_SELECT_ALL, NULL, NULL); if (!dctx) { BIO_free(bio); croakSsl(__FILE__, __LINE__); } if (SvPOK(passphrase_SV)) { STRLEN passlen; unsigned char* pass = (unsigned char*)SvPV(passphrase_SV, passlen); if (!OSSL_DECODER_CTX_set_passphrase(dctx, pass, passlen)) { OSSL_DECODER_CTX_free(dctx); BIO_free(bio); croakSsl(__FILE__, __LINE__); } } if (!OSSL_DECODER_from_bio(dctx, bio)) { OSSL_DECODER_CTX_free(dctx); BIO_free(bio); croakSsl(__FILE__, __LINE__); } OSSL_DECODER_CTX_free(dctx); #else if (SvPOK(passphrase_SV)) { char* passphrase = SvPV_nolen(passphrase_SV); pkey = _load_pkcs8_der_key(bio, passphrase); } else { pkey = d2i_RSAPrivateKey_bio(bio, NULL); } #endif BIO_free(bio); CHECK_OPEN_SSL(pkey); RETVAL = make_rsa_obj(proto, pkey); OUTPUT: RETVAL void DESTROY(p_rsa) rsaData* p_rsa; CODE: EVP_PKEY_free(p_rsa->rsa); Safefree(p_rsa); SV* get_private_key_string(p_rsa, passphrase_SV=&PL_sv_undef, cipher_name_SV=&PL_sv_undef) rsaData* p_rsa; SV* passphrase_SV; SV* cipher_name_SV; PREINIT: BIO* stringBIO; char* passphrase = NULL; STRLEN passphraseLength = 0; char* cipher_name; const EVP_CIPHER* enc = NULL; CODE: if (!_is_private(p_rsa)) { croak("Public keys cannot export private key strings"); } if (SvPOK(cipher_name_SV) && !SvPOK(passphrase_SV)) { croak("Passphrase is required for cipher"); } if (SvPOK(passphrase_SV)) { passphrase = SvPV(passphrase_SV, passphraseLength); if (SvPOK(cipher_name_SV)) { cipher_name = SvPV_nolen(cipher_name_SV); } else { cipher_name = "des3"; } enc = EVP_get_cipherbyname(cipher_name); if (enc == NULL) { croak("Unsupported cipher: %s", cipher_name); } } CHECK_OPEN_SSL(stringBIO = BIO_new(BIO_s_mem())); CHECK_OPEN_SSL_BIO(PEM_write_bio_PrivateKey_traditional( stringBIO, p_rsa->rsa, enc, (unsigned char* ) passphrase, passphraseLength, NULL, NULL), stringBIO); RETVAL = extractBioString(stringBIO); OUTPUT: RETVAL SV* get_private_key_pkcs8_string(p_rsa, passphrase_SV=&PL_sv_undef, cipher_name_SV=&PL_sv_undef) rsaData* p_rsa; SV* passphrase_SV; SV* cipher_name_SV; PREINIT: BIO* stringBIO; char* passphrase = NULL; STRLEN passphraseLength = 0; char* cipher_name; const EVP_CIPHER* enc = NULL; CODE: if (SvPOK(cipher_name_SV) && !SvPOK(passphrase_SV)) { croak("Passphrase is required for cipher"); } if (SvPOK(passphrase_SV)) { passphrase = SvPV(passphrase_SV, passphraseLength); if (SvPOK(cipher_name_SV)) { cipher_name = SvPV_nolen(cipher_name_SV); } else { cipher_name = "des3"; } enc = EVP_get_cipherbyname(cipher_name); if (enc == NULL) { croak("Unsupported cipher: %s", cipher_name); } } CHECK_OPEN_SSL(stringBIO = BIO_new(BIO_s_mem())); #if OPENSSL_VERSION_NUMBER >= 0x30000000L CHECK_OPEN_SSL_BIO(PEM_write_bio_PrivateKey( stringBIO, p_rsa->rsa, enc, (unsigned char*) passphrase, passphraseLength, NULL, NULL), stringBIO); #else CHECK_OPEN_SSL_BIO(_write_pkcs8_pem( stringBIO, p_rsa->rsa, enc, (unsigned char*) passphrase, passphraseLength), stringBIO); #endif RETVAL = extractBioString(stringBIO); OUTPUT: RETVAL SV* get_public_key_string(p_rsa) rsaData* p_rsa; PREINIT: BIO* stringBIO; #if OPENSSL_VERSION_NUMBER >= 0x30000000L OSSL_ENCODER_CTX *ctx = NULL; int error = 0; #endif CODE: CHECK_OPEN_SSL(stringBIO = BIO_new(BIO_s_mem())); #if OPENSSL_VERSION_NUMBER >= 0x30000000L ctx = OSSL_ENCODER_CTX_new_for_pkey(p_rsa->rsa, OSSL_KEYMGMT_SELECT_PUBLIC_KEY, "PEM", "PKCS1", NULL); THROW(ctx != NULL && OSSL_ENCODER_CTX_get_num_encoders(ctx)); THROW(OSSL_ENCODER_to_bio(ctx, stringBIO) == 1); OSSL_ENCODER_CTX_free(ctx); ctx = NULL; goto pubkey_done; err: if (ctx) { OSSL_ENCODER_CTX_free(ctx); ctx = NULL; } BIO_free(stringBIO); CHECK_OPEN_SSL(0); pubkey_done: #else CHECK_OPEN_SSL_BIO(PEM_write_bio_RSAPublicKey(stringBIO, p_rsa->rsa), stringBIO); #endif RETVAL = extractBioString(stringBIO); OUTPUT: RETVAL SV* get_public_key_x509_string(p_rsa) rsaData* p_rsa; PREINIT: BIO* stringBIO; CODE: CHECK_OPEN_SSL(stringBIO = BIO_new(BIO_s_mem())); CHECK_OPEN_SSL_BIO(PEM_write_bio_PUBKEY(stringBIO, p_rsa->rsa), stringBIO); RETVAL = extractBioString(stringBIO); OUTPUT: RETVAL SV* generate_key(proto, bitsSV, exponent = 65537) SV* proto; SV* bitsSV; unsigned long exponent; PREINIT: EVP_PKEY* rsa = NULL; BIGNUM *e = NULL; #if OPENSSL_VERSION_NUMBER >= 0x30000000L EVP_PKEY_CTX *ctx = NULL; int error = 0; #endif CODE: if (SvIV(bitsSV) < 512) croak("RSA key size must be at least 512 bits (got %"IVdf")", SvIV(bitsSV)); if (exponent < 3 || (exponent % 2) == 0) croak("RSA exponent must be odd and >= 3 (got %lu)", exponent); e = BN_new(); BN_set_word(e, exponent); #if OPENSSL_VERSION_NUMBER < 0x00908000L rsa = RSA_generate_key(SvIV(bitsSV), exponent, NULL, NULL); BN_free(e); CHECK_OPEN_SSL(rsa != NULL); #endif #if OPENSSL_VERSION_NUMBER >= 0x00908000L && OPENSSL_VERSION_NUMBER < 0x30000000L rsa = RSA_new(); if (!RSA_generate_key_ex(rsa, SvIV(bitsSV), e, NULL)) { BN_free(e); RSA_free(rsa); croak("Unable to generate a key"); } BN_free(e); #endif #if OPENSSL_VERSION_NUMBER >= 0x30000000L ctx = EVP_PKEY_CTX_new_from_name(NULL, "RSA", NULL); THROW(ctx); THROW(EVP_PKEY_keygen_init(ctx) == 1); THROW(EVP_PKEY_CTX_set_rsa_keygen_bits(ctx, SvIV(bitsSV)) > 0); THROW(EVP_PKEY_CTX_set1_rsa_keygen_pubexp(ctx, e) > 0); THROW(EVP_PKEY_generate(ctx, &rsa) == 1); err: BN_free(e); e = NULL; EVP_PKEY_CTX_free(ctx); ctx = NULL; if (error) croakSsl(__FILE__, __LINE__); #endif CHECK_OPEN_SSL(rsa); RETVAL = make_rsa_obj(proto, rsa); OUTPUT: RETVAL SV* _new_key_from_parameters(proto, n, e, d, p, q) SV* proto; BIGNUM* n; BIGNUM* e; BIGNUM* d; BIGNUM* p; BIGNUM* q; PREINIT: EVP_PKEY* rsa = NULL; BN_CTX* ctx = NULL; BIGNUM* p_minus_1 = NULL; BIGNUM* q_minus_1 = NULL; BIGNUM* dmp1 = NULL; BIGNUM* dmq1 = NULL; BIGNUM* iqmp = NULL; int error = 0; #if OPENSSL_VERSION_NUMBER >= 0x30000000L OSSL_PARAM *params = NULL; EVP_PKEY_CTX *pctx = NULL; OSSL_PARAM_BLD *params_build = NULL; #endif CODE: { if (!(n && e)) { croak("At least a modulus and public key must be provided"); } #if OPENSSL_VERSION_NUMBER >= 0x30000000L pctx = EVP_PKEY_CTX_new_from_name(NULL, "RSA", NULL); THROW(pctx != NULL); THROW(EVP_PKEY_fromdata_init(pctx) > 0); params_build = OSSL_PARAM_BLD_new(); THROW(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 THROW(OSSL_PARAM_BLD_push_BN(params_build, OSSL_PKEY_PARAM_RSA_N, n)); THROW(OSSL_PARAM_BLD_push_BN(params_build, OSSL_PKEY_PARAM_RSA_E, e)); #endif if (p || q) { error = 0; THROW(ctx = BN_CTX_new()); if (!p) { THROW(p = BN_new()); THROW(BN_div(p, NULL, n, q, ctx)); } else if (!q) { THROW(q = BN_new()); THROW(BN_div(q, NULL, n, p, ctx)); } #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())); THROW(q_minus_1 = BN_new()); THROW(BN_sub(q_minus_1, q, BN_value_one())); if (!d) { THROW(d = BN_new()); THROW(BN_mul(d, p_minus_1, q_minus_1, ctx)); THROW(BN_mod_inverse(d, e, d, ctx)); } #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)); THROW(dmq1 = BN_new()); THROW(BN_mod(dmq1, d, q_minus_1, ctx)); THROW(iqmp = BN_new()); THROW(BN_mod_inverse(iqmp, q, p, ctx)); #if OLD_CRUFTY_SSL_VERSION 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); int check_ok = (test_ctx != NULL && EVP_PKEY_check(test_ctx) == 1); EVP_PKEY_CTX_free(test_ctx); THROW(check_ok); #else THROW(RSA_set0_crt_params(rsa, dmp1, dmq1, iqmp)); #endif #endif #if OPENSSL_VERSION_NUMBER >= 0x30000000L /* OSSL_PARAM_BLD_push_BN() copies the value, so the original BIGNUMs (from pointer_copy or BN_new) must be freed here. On pre-3.x, RSA_set0_key/RSA_set0_factors took ownership. */ BN_clear_free(n); BN_clear_free(e); BN_clear_free(d); BN_clear_free(p); BN_clear_free(q); BN_clear_free(dmp1); BN_clear_free(dmq1); BN_clear_free(iqmp); n = e = d = p = q = NULL; #endif dmp1 = dmq1 = iqmp = NULL; BN_CTX_free(ctx); ctx = NULL; BN_clear_free(p_minus_1); p_minus_1 = NULL; BN_clear_free(q_minus_1); q_minus_1 = NULL; #if OPENSSL_VERSION_NUMBER >= 0x30000000L OSSL_PARAM_BLD_free(params_build); params_build = NULL; OSSL_PARAM_free(params); params = NULL; EVP_PKEY_CTX_free(pctx); pctx = NULL; #else THROW(RSA_check_key(rsa) == 1); #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); OSSL_PARAM_BLD_free(params_build); OSSL_PARAM_free(params); params_build = NULL; params = NULL; THROW( status > 0 && rsa != NULL ); EVP_PKEY_CTX_free(pctx); pctx = NULL; BN_clear_free(n); BN_clear_free(e); BN_clear_free(d); n = e = d = NULL; #else CHECK_OPEN_SSL(RSA_set0_key(rsa, n, e, d)); #endif #endif } THROW(RETVAL = make_rsa_obj(proto, rsa)); goto end; err: #if OPENSSL_VERSION_NUMBER >= 0x30000000L /* On 3.x, push_BN copies, so originals are always ours to free. On pre-3.x, RSA_set0_key/set0_factors may have taken ownership, so these are intentionally skipped (risk of double-free). */ BN_clear_free(n); BN_clear_free(e); BN_clear_free(d); BN_clear_free(p); BN_clear_free(q); #endif if (p_minus_1) BN_clear_free(p_minus_1); if (q_minus_1) BN_clear_free(q_minus_1); if (dmp1) BN_clear_free(dmp1); if (dmq1) BN_clear_free(dmq1); if (iqmp) BN_clear_free(iqmp); if (ctx) BN_CTX_free(ctx); #if OPENSSL_VERSION_NUMBER >= 0x30000000L if (pctx) { EVP_PKEY_CTX_free(pctx); pctx = NULL; } if (params_build) { OSSL_PARAM_BLD_free(params_build); params_build = NULL; } if (params) { OSSL_PARAM_free(params); params = NULL; } #endif if (error) { EVP_PKEY_free(rsa); CHECK_OPEN_SSL(0); } } end: OUTPUT: RETVAL 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; const BIGNUM* p; const BIGNUM* q; const BIGNUM* dmp1; const BIGNUM* dmq1; const BIGNUM* iqmp; #endif PPCODE: { EVP_PKEY* rsa; rsa = p_rsa->rsa; #if OLD_CRUFTY_SSL_VERSION n = rsa->n; e = rsa->e; d = rsa->d; p = rsa->p; q = rsa->q; dmp1 = rsa->dmp1; dmq1 = rsa->dmq1; iqmp = rsa->iqmp; #else #if OPENSSL_VERSION_NUMBER >= 0x30000000L /* n and e are mandatory for every RSA key — croak on failure. */ if (!EVP_PKEY_get_bn_param(rsa, OSSL_PKEY_PARAM_RSA_N, &n)) croakSsl(__FILE__, __LINE__); if (!EVP_PKEY_get_bn_param(rsa, OSSL_PKEY_PARAM_RSA_E, &e)) { BN_free(n); croakSsl(__FILE__, __LINE__); } /* Private components are absent for public keys — EVP_PKEY_get_bn_param() returns 0 and may push errors onto the queue, but the pointer stays NULL so cor_bn2sv() will return undef. This matches the pre-3.x behaviour where RSA_get0_key/factors/crt_params simply set NULL for missing fields. */ 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); /* Failed calls (e.g. private params on a public key) push errors onto the OpenSSL error queue. Drain them so they don't leak into the next croakSsl() call from an unrelated operation. */ ERR_clear_error(); #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)); XPUSHs(cor_bn2sv(d)); XPUSHs(cor_bn2sv(p)); XPUSHs(cor_bn2sv(q)); XPUSHs(cor_bn2sv(dmp1)); XPUSHs(cor_bn2sv(dmq1)); XPUSHs(cor_bn2sv(iqmp)); #if OPENSSL_VERSION_NUMBER >= 0x30000000L /* EVP_PKEY_get_bn_param() allocates new BIGNUMs (unlike the pre-3.x getters which return internal pointers). cor_bn2sv() duplicates them via BN_dup(), so we must free the originals here. */ BN_free(n); BN_free(e); BN_clear_free(d); BN_clear_free(p); BN_clear_free(q); BN_clear_free(dmp1); BN_clear_free(dmq1); BN_clear_free(iqmp); #endif } SV* encrypt(p_rsa, p_plaintext) rsaData* p_rsa; SV* p_plaintext; CODE: check_max_message_length(p_rsa, sv_len(p_plaintext)); #if OPENSSL_VERSION_NUMBER >= 0x30000000L RETVAL = rsa_crypt(p_rsa, p_plaintext, EVP_PKEY_encrypt, EVP_PKEY_encrypt_init, 1 /* is_encrypt */); #else RETVAL = rsa_crypt(p_rsa, p_plaintext, RSA_public_encrypt, 1 /* is_encrypt */); #endif OUTPUT: RETVAL SV* decrypt(p_rsa, p_ciphertext) rsaData* p_rsa; SV* p_ciphertext; CODE: if (!_is_private(p_rsa)) { croak("Public keys cannot decrypt"); } #if OPENSSL_VERSION_NUMBER >= 0x30000000L RETVAL = rsa_crypt(p_rsa, p_ciphertext, EVP_PKEY_decrypt, EVP_PKEY_decrypt_init, 1 /* is_encrypt */); #else RETVAL = rsa_crypt(p_rsa, p_ciphertext, RSA_private_decrypt, 1 /* is_encrypt */); #endif OUTPUT: RETVAL SV* private_encrypt(p_rsa, p_plaintext) rsaData* p_rsa; SV* p_plaintext; CODE: if (!_is_private(p_rsa)) { croak("Public keys cannot private_encrypt"); } if (p_rsa->padding == RSA_PKCS1_OAEP_PADDING) { croak("OAEP padding is not supported for private_encrypt/public_decrypt. " "Call use_no_padding() or use_pkcs1_padding() first."); } if (p_rsa->padding == RSA_PKCS1_PSS_PADDING) { croak("PSS padding with private_encrypt/public_decrypt is not supported. " "Use sign()/verify() for PSS signatures."); } check_max_message_length(p_rsa, sv_len(p_plaintext)); #if OPENSSL_VERSION_NUMBER >= 0x30000000L RETVAL = rsa_crypt(p_rsa, p_plaintext, EVP_PKEY_sign, EVP_PKEY_sign_init, 0 /* is_encrypt */); #else RETVAL = rsa_crypt(p_rsa, p_plaintext, RSA_private_encrypt, 0 /* is_encrypt */); #endif OUTPUT: RETVAL SV* public_decrypt(p_rsa, p_ciphertext) rsaData* p_rsa; SV* p_ciphertext; CODE: if (p_rsa->padding == RSA_PKCS1_OAEP_PADDING) { croak("OAEP padding is not supported for private_encrypt/public_decrypt. " "Call use_no_padding() or use_pkcs1_padding() first."); } if (p_rsa->padding == RSA_PKCS1_PSS_PADDING) { croak("PSS padding with private_encrypt/public_decrypt is not supported. " "Use sign()/verify() for PSS signatures."); } #if OPENSSL_VERSION_NUMBER >= 0x30000000L RETVAL = rsa_crypt(p_rsa, p_ciphertext, EVP_PKEY_verify_recover, EVP_PKEY_verify_recover_init, 0 /* is_encrypt */); #else RETVAL = rsa_crypt(p_rsa, p_ciphertext, RSA_public_decrypt, 0 /* is_encrypt */); #endif OUTPUT: RETVAL int size(p_rsa) rsaData* p_rsa; CODE: RETVAL = EVP_PKEY_get_size(p_rsa->rsa); OUTPUT: RETVAL int check_key(p_rsa) rsaData* p_rsa; CODE: if (!_is_private(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) == 1); EVP_PKEY_CTX_free(pctx); #else RETVAL = (RSA_check_key(p_rsa->rsa) == 1); #endif OUTPUT: RETVAL # Seed the PRNG with user-provided bytes; returns true if the # seeding was sufficient. int _random_seed(random_bytes_SV) SV* random_bytes_SV; PREINIT: STRLEN random_bytes_length; char* random_bytes; CODE: random_bytes = SvPV(random_bytes_SV, random_bytes_length); RAND_seed(random_bytes, random_bytes_length); RETVAL = RAND_status(); OUTPUT: RETVAL # Returns true if the PRNG has enough seed data int _random_status() CODE: RETVAL = RAND_status(); OUTPUT: RETVAL void use_md5_hash(p_rsa) rsaData* p_rsa; CODE: p_rsa->hashMode = NID_md5; void use_sha1_hash(p_rsa) rsaData* p_rsa; CODE: p_rsa->hashMode = NID_sha1; #ifdef SHA512_DIGEST_LENGTH void use_sha224_hash(p_rsa) rsaData* p_rsa; CODE: p_rsa->hashMode = NID_sha224; void use_sha256_hash(p_rsa) rsaData* p_rsa; CODE: p_rsa->hashMode = NID_sha256; void use_sha384_hash(p_rsa) rsaData* p_rsa; CODE: p_rsa->hashMode = NID_sha384; void use_sha512_hash(p_rsa) rsaData* p_rsa; CODE: p_rsa->hashMode = NID_sha512; #endif void use_ripemd160_hash(p_rsa) rsaData* p_rsa; CODE: p_rsa->hashMode = NID_ripemd160; #ifdef WHIRLPOOL_DIGEST_LENGTH void use_whirlpool_hash(p_rsa) rsaData* p_rsa; CODE: p_rsa->hashMode = NID_whirlpool; #endif void use_no_padding(p_rsa) rsaData* p_rsa; CODE: p_rsa->padding = RSA_NO_PADDING; void use_pkcs1_padding(p_rsa) rsaData* p_rsa; CODE: p_rsa->padding = RSA_PKCS1_PADDING; void use_pkcs1_oaep_padding(p_rsa) rsaData* 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 use_sslv23_padding(p_rsa) rsaData* p_rsa; CODE: p_rsa->padding = RSA_SSLV23_PADDING; #endif # Sign text. Returns the signature. SV* sign(p_rsa, text_SV) rsaData* p_rsa; SV* text_SV; PREINIT: UNSIGNED_CHAR *signature = NULL; unsigned char* digest; SIZE_T_UNSIGNED_INT signature_length; #if OPENSSL_VERSION_NUMBER >= 0x30000000L EVP_PKEY_CTX *ctx = NULL; EVP_MD *md = NULL; int error = 0; #endif CODE: { if (!_is_private(p_rsa)) { croak("Public keys cannot sign messages"); } unsigned char digest_buf[EVP_MAX_MD_SIZE]; CHECK_OPEN_SSL(digest = get_message_digest(text_SV, p_rsa->hashMode, digest_buf)); #if OPENSSL_VERSION_NUMBER >= 0x30000000L ctx = EVP_PKEY_CTX_new(p_rsa->rsa, NULL /* no engine */); THROW(ctx); THROW(EVP_PKEY_sign_init(ctx)); THROW(setup_pss_sign_ctx(ctx, p_rsa->padding, p_rsa->hashMode, &md)); THROW(EVP_PKEY_sign(ctx, NULL, &signature_length, digest, get_digest_length(p_rsa->hashMode)) == 1); Newx(signature, signature_length, UNSIGNED_CHAR); THROW(signature); THROW(EVP_PKEY_sign(ctx, signature, &signature_length, digest, get_digest_length(p_rsa->hashMode)) == 1); EVP_MD_free(md); EVP_PKEY_CTX_free(ctx); goto sign_done; err: Safefree(signature); if (md) EVP_MD_free(md); if (ctx) EVP_PKEY_CTX_free(ctx); CHECK_OPEN_SSL(0); sign_done: #else CHECK_NEW(signature, EVP_PKEY_get_size(p_rsa->rsa), UNSIGNED_CHAR); if (!RSA_sign(p_rsa->hashMode, digest, get_digest_length(p_rsa->hashMode), (unsigned char*) signature, &signature_length, p_rsa->rsa)) { Safefree(signature); croakSsl(__FILE__, __LINE__); } #endif RETVAL = newSVpvn((const char* )signature, signature_length); Safefree(signature); } OUTPUT: RETVAL # Verify signature. Returns true if correct, false otherwise. void verify(p_rsa, text_SV, sig_SV) rsaData* p_rsa; SV* text_SV; SV* sig_SV; PREINIT: int verify_result; #if OPENSSL_VERSION_NUMBER >= 0x30000000L int error = 0; EVP_PKEY_CTX *ctx = NULL; EVP_MD *md = NULL; #endif PPCODE: { unsigned char* sig; unsigned char* digest; STRLEN sig_length; sig = (unsigned char*) SvPV(sig_SV, sig_length); if (EVP_PKEY_get_size(p_rsa->rsa) < sig_length) { croak("Signature longer than key"); } unsigned char digest_buf[EVP_MAX_MD_SIZE]; CHECK_OPEN_SSL(digest = get_message_digest(text_SV, p_rsa->hashMode, digest_buf)); #if OPENSSL_VERSION_NUMBER >= 0x30000000L ctx = EVP_PKEY_CTX_new(p_rsa->rsa, NULL /* no engine */); THROW(ctx); THROW(EVP_PKEY_verify_init(ctx) == 1); THROW(setup_pss_sign_ctx(ctx, p_rsa->padding, p_rsa->hashMode, &md)); verify_result = EVP_PKEY_verify(ctx, sig, sig_length, digest, get_digest_length(p_rsa->hashMode)); EVP_MD_free(md); EVP_PKEY_CTX_free(ctx); goto verify_switch; err: if (md) EVP_MD_free(md); if (ctx) EVP_PKEY_CTX_free(ctx); CHECK_OPEN_SSL(0); verify_switch: ; #else verify_result = RSA_verify(p_rsa->hashMode, digest, get_digest_length(p_rsa->hashMode), sig, sig_length, p_rsa->rsa); #endif switch(verify_result) { case 0: ERR_clear_error(); XSRETURN_NO; break; case 1: XSRETURN_YES; break; default: CHECK_OPEN_SSL(0); break; } } int is_private(p_rsa) rsaData* p_rsa; CODE: RETVAL = _is_private(p_rsa); OUTPUT: RETVAL Crypt-OpenSSL-RSA-0.41/AI_POLICY.md0000644000175000017500000001303615165307422014511 0ustar timtim# AI Policy > **TL;DR** — AI tools assist our workflow at every stage. Humans remain in control of every decision, every review, and every release. --- ## Overview This document describes how artificial intelligence tools are used in the maintenance and development of this project. It is intended to be transparent with our contributors, users, and the broader open-source community about the role AI plays — and, equally importantly, the role it does **not** play. We believe in honest, clear communication about AI-assisted workflows. This policy will be updated as our practices evolve. --- ## Our Guiding Principle **AI assists. Humans decide.** The maintainers who have been stewarding this project for years remain fully responsible for every line of code that ships. AI tools extend our capacity to review, research, and improve — they do not replace human judgment, expertise, or accountability. --- ## How AI Is Used in This Project ### 1. Code and Issue Analysis AI tools help us process and understand incoming issues, pull requests, and code changes at scale. This includes: - Summarising issue reports and identifying patterns across similar bugs - Analysing code diffs for potential problems, regressions, or style inconsistencies - Surfacing relevant context from the codebase, documentation, and prior discussions - Flagging potential security concerns for human review This analysis is **always** used as input to human decision-making, never as a substitute for it. ### 2. Draft Pull Requests AI may generate draft pull requests as a starting point for a fix, a refactor, or an improvement. These drafts: - Are clearly labelled as AI-generated when created - Represent a first pass only — they are never considered complete or correct without human review - May be substantially reworked, rejected, or replaced entirely by maintainers Think of these drafts the way you would think of a junior contributor's first attempt: useful raw material that still needs experienced eyes. ### 3. Human Review of Every Pull Request **Every pull request — whether AI-drafted or human-authored — is reviewed by a human maintainer before it can be merged.** During review, maintainers actively use AI as a tool to assist their own thinking: - Asking AI to explain or justify specific implementation choices - Challenging AI-generated code and requesting alternative approaches - Using AI to research edge cases, relevant standards, or upstream behaviour - Requesting targeted rewrites of individual sections based on review feedback The maintainer's judgment always takes precedence. AI answers are treated as input to be verified, not conclusions to be accepted. ### 4. Test Coverage and Defect Detection AI helps us improve the quality and completeness of our test suite by: - Suggesting test cases for edge conditions and failure modes - Identifying gaps in existing test coverage - Proposing tests that target known classes of defects or security issues - Helping reproduce and characterise reported bugs All suggested tests are reviewed and validated by maintainers before being committed. ### 5. Security Review AI tools assist in identifying potential security issues, including: - Common vulnerability patterns (injection, insecure defaults, deprecated APIs, etc.) - Dependencies with known CVEs - Code paths that may warrant closer scrutiny Security findings from AI are **always** verified by a human maintainer. We do not act on AI-flagged security issues without independent assessment. --- ## What AI Does Not Do To be explicit about the limits of AI involvement in this project: | ❌ AI does not… | ✅ A human maintainer does… | |---|---| | Approve or merge pull requests | Review and decide on every PR | | Make architectural decisions | Own all design and direction choices | | Triage and close issues autonomously | Assess and respond to all issues | | Publish releases | Tag, build, and release manually | | Represent the project publicly | Communicate on behalf of the project | --- ## Releases Releases are performed manually by the same long-standing maintainers as always. The release process — including changelog review, version tagging, and publication — uses standard Perl ecosystem tooling (e.g. ExtUtils::MakeMaker, Dist::Zilla, Module::Build) but involves no AI-driven automation. Every release is initiated, supervised, and published by a human maintainer. AI may assist in drafting changelogs or release notes, but these are always reviewed and edited before publication. --- ## Attribution and Transparency Where AI has played a material role in generating code or content within a pull request, we aim to note this in the PR description (e.g. via a `Generated-By` or `AI-Assisted` label or note). We do not consider AI the author of any contribution — the maintainer who reviewed and approved the work takes responsibility for it. --- ## Why We Do This Open-source software is built on trust. Our users and downstream dependants trust us to ship correct, secure, and well-considered code. AI tools help us do that work better — but they do not change who is responsible for the outcome. We use AI because it makes our maintainers more effective, not because it replaces them. --- ## Questions and Feedback If you have questions about our use of AI, or concerns about a specific pull request or change, please open an issue or start a discussion. We are committed to being open about our process. --- *Last updated: 2026-03-23* *This policy is maintained by the project maintainers and subject to revision as AI tooling and community norms evolve.* Crypt-OpenSSL-RSA-0.41/SECURITY.md0000644000175000017500000000167315165307422014414 0ustar timtim# Security Policy ## Reporting a Vulnerability If you discover a security vulnerability in Crypt::OpenSSL::RSA, please report it responsibly. **Preferred:** Use [GitHub's private vulnerability reporting](https://github.com/cpan-authors/Crypt-OpenSSL-RSA/security/advisories/new) to submit a report directly on GitHub. **Alternative:** Email Todd Rinaldo Please include: - A description of the vulnerability - Steps to reproduce the issue - Any relevant version or platform details We will acknowledge receipt within 48 hours and aim to provide an initial assessment within one week. ## Supported Versions Security fixes are applied to the latest release. Users are encouraged to keep their installation up to date. ## Scope This module is a Perl XS wrapper around OpenSSL's RSA implementation. Vulnerabilities in OpenSSL itself should be reported to the [OpenSSL security team](https://www.openssl.org/policies/secpolicy.html). Crypt-OpenSSL-RSA-0.41/LICENSE0000644000175000017500000004740715164606110013630 0ustar timtim Terms of Perl itself a) the GNU General Public License as published by the Free Software Foundation; either version 1, or (at your option) any later version, or b) the "Artistic License" ---------------------------------------------------------------------------- The General Public License (GPL) Version 2, June 1991 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 The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to 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 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 General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS ---------------------------------------------------------------------------- The Artistic License Preamble The intent of this document is to state the conditions under which a Package may be copied, such that the Copyright Holder maintains some semblance of artistic control over the development of the package, while giving the users of the package the right to use and distribute the Package in a more-or-less customary fashion, plus the right to make reasonable modifications. Definitions: - "Package" refers to the collection of files distributed by the Copyright Holder, and derivatives of that collection of files created through textual modification. - "Standard Version" refers to such a Package if it has not been modified, or has been modified in accordance with the wishes of the Copyright Holder. - "Copyright Holder" is whoever is named in the copyright or copyrights for the package. - "You" is you, if you're thinking about copying or distributing this Package. - "Reasonable copying fee" is whatever you can justify on the basis of media cost, duplication charges, time of people involved, and so on. (You will not be required to justify it to the Copyright Holder, but only to the computing community at large as a market that must bear the fee.) - "Freely Available" means that no fee is charged for the item itself, though there may be fees involved in handling the item. It also means that recipients of the item may redistribute it under the same conditions they received it. 1. You may make and give away verbatim copies of the source form of the Standard Version of this Package without restriction, provided that you duplicate all of the original copyright notices and associated disclaimers. 2. You may apply bug fixes, portability fixes and other modifications derived from the Public Domain or from the Copyright Holder. A Package modified in such a way shall still be considered the Standard Version. 3. You may otherwise modify your copy of this Package in any way, provided that you insert a prominent notice in each changed file stating how and when you changed that file, and provided that you do at least ONE of the following: a) place your modifications in the Public Domain or otherwise make them Freely Available, such as by posting said modifications to Usenet or an equivalent medium, or placing the modifications on a major archive site such as ftp.uu.net, or by allowing the Copyright Holder to include your modifications in the Standard Version of the Package. b) use the modified Package only within your corporation or organization. c) rename any non-standard executables so the names do not conflict with standard executables, which must also be provided, and provide a separate manual page for each non-standard executable that clearly documents how it differs from the Standard Version. d) make other distribution arrangements with the Copyright Holder. 4. You may distribute the programs of this Package in object code or executable form, provided that you do at least ONE of the following: a) distribute a Standard Version of the executables and library files, together with instructions (in the manual page or equivalent) on where to get the Standard Version. b) accompany the distribution with the machine-readable source of the Package with your modifications. c) accompany any non-standard executables with their corresponding Standard Version executables, giving the non-standard executables non-standard names, and clearly documenting the differences in manual pages (or equivalent), together with instructions on where to get the Standard Version. d) make other distribution arrangements with the Copyright Holder. 5. You may charge a reasonable copying fee for any distribution of this Package. You may charge any fee you choose for support of this Package. You may not charge a fee for this Package itself. However, you may distribute this Package in aggregate with other (possibly commercial) programs as part of a larger (possibly commercial) software distribution provided that you do not advertise this Package as a product of your own. 6. The scripts and library files supplied as input to or produced as output from the programs of this Package do not automatically fall under the copyright of this Package, but belong to whomever generated them, and may be sold commercially, and may be aggregated with this Package. 7. C or perl subroutines supplied by you and linked into this Package shall not be considered part of this Package. 8. The name of the Copyright Holder may not be used to endorse or promote products derived from this software without specific prior written permission. 9. THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. The End Crypt-OpenSSL-RSA-0.41/Changes0000644000175000017500000004367415172615427014132 0ustar timtimRevision history for Perl extension Crypt::OpenSSL::RSA. 0.41 Apr 24 2026 [Bug Fixes] - PR #181: Skip OpenSSL 3.x-specific tests on LibreSSL. LibreSSL reports version >= 3.0 via Crypt::OpenSSL::Guess's openssl_version() but internally uses the pre-3.x code path (OPENSSL_VERSION_NUMBER < 0x30000000L), causing two CPAN Testers failures on OpenBSD: t/padding.t (use_sslv23_padding is still a valid XS function on LibreSSL because RSA_SSLV23_PADDING exists) and t/pkcs1_sign.t (RSA_verify on pre-3.x/LibreSSL ignores the padding mode, so cross-padding verification succeeds). LibreSSL is now detected by parsing `openssl version` output for the "LibreSSL" string, using find_openssl_exec(find_openssl_prefix()) from Crypt::OpenSSL::Guess to locate the correct binary. The earlier approach of detecting LibreSSL via an undefined patch level was not reliable. 0.39 Apr 23 2026 [Bug Fixes] - PR #171 GH #170: Fix macOS compile warnings. The OLD_CRUFTY_SSL_VERSION macro used defined() inside a #define (undefined behavior when expanded in #if directives); split into #ifdef/#else branches. Also cast SvPV_nolen() result to UNSIGNED_CHAR* to silence the pointer-sign mismatch in _load_rsa_key(). - PR #173: Reject non-RSA keys (EC, DSA, etc.) in _new_public_key_x509_der() on OpenSSL 3.x. d2i_PUBKEY_bio() accepts any key type, unlike pre-3.x d2i_RSA_PUBKEY_bio(); without validation a non-RSA DER key would be stored in the rsaData struct and produce confusing failures later. - PR #177: Check padding compatibility before message length in private_encrypt() and public_decrypt(). Previously, calling these with the default OAEP padding (or PSS) produced a misleading "plaintext too long" error that hid the real issue (OAEP/PSS are fundamentally incompatible with private_encrypt/public_decrypt). The clear "OAEP/PSS padding is not supported" error is now emitted regardless of data size, and the rejection extends to pre-3.x OpenSSL (previously only checked on 3.x inside rsa_crypt()). - PR #178: Validate key size in generate_key() before calling OpenSSL. Reject negative, zero, and sub-512-bit key sizes with a clear croak instead of letting OpenSSL produce cryptic errors or hang. - PR #179 GH #174: Restore the lost configure_requires prereq on Crypt::OpenSSL::Guess in Makefile.PL. - PR #179 GH #175: Fix failing test 'Padding method pkcs1_pss is valid for signing with ripemd160'. [Improvements] - PR #180: Add optional passphrase argument to new_private_key_der(), enabling decryption of encrypted PKCS#8 DER (EncryptedPrivateKeyInfo) private keys. On OpenSSL 3.x the passphrase is passed to the existing OSSL_DECODER_CTX; on pre-3.x a d2i_PKCS8PrivateKey_bio() helper is used. Previously only PEM-encoded keys supported a passphrase. [Maintenance] - PR #172: Fix 'passphase' -> 'passphrase' typo throughout the codebase (RSA.xs internal names, RSA.pm POD for get_private_key_string, and the test variable in t/format.t). The typo dates to the original 0.33 passphrase support. No functional change -- all renames are internal. 0.38 Apr 23 2026 [Bug Fixes] - PR #103 GH #61: Re-enable PKCS#1 v1.5 padding for sign()/verify(). It was incorrectly disabled in 0.35; the Marvin attack only affects decryption, not signatures. - PR #168: Fix croak message to reference use_pkcs1_oaep_padding() (not use_pkcs1_padding()) when non-OAEP padding is used for encrypt/decrypt. - PR #165: Fix OAEP overhead calculation that was hardcoded for SHA-1; correct overhead is now computed per the configured hash algorithm. - PR #141: Reject non-RSA keys (EC, DSA, RSA-PSS) loaded via _load_rsa_key() on OpenSSL 3.x with a clear error instead of a confusing failure later. - PR #118: Fix private_encrypt() and public_decrypt() broken on OpenSSL 3.x with any padding except NO_PADDING; rsa_crypt() now distinguishes encrypt vs. sign paths. - PR #142: Free signature buffer on RSA_sign() failure on pre-3.x. - PR #164 GH #152: Drain OpenSSL error queue after _get_key_parameters() on OpenSSL 3.x so a failed optional-param lookup does not pollute the error queue for subsequent operations. - PR #161 GH #152: Cache is_private_key flag in rsaData struct to avoid a per-call BIGNUM heap allocation on OpenSSL 3.x. - PR #159 GH #155: Check return values of EVP_PKEY_get_bn_param() in _get_key_parameters(); a failed mandatory param (n or e) now croaks instead of silently returning undef. - PR #160 GH #156: Use THROW macro for make_rsa_obj() result in _new_key_from_parameters() to prevent resource leak on a NULL return. - PR #158 GH #154: Extract setup_pss_sign_ctx() helper to deduplicate PSS context setup in sign() and verify(); the two paths could previously diverge silently. - PR #157 GH #153: Eliminate duplicate NID-to-name table in get_message_digest(); fixes whirlpool on OpenSSL 3.x where the old low-level WHIRLPOOL() API path was being used instead of EVP_MD_fetch(). - PR #145: Fix BIO resource leak in extractBioString() error paths. - PR #143: Validate that a private key is present before attempting export in get_private_key_string(). - PR #140: NULL out BIGNUMs after freeing them in _new_key_from_parameters() to prevent a double-free when make_rsa_obj() fails after they are freed. - PR #137: Use BN_clear_free() (instead of BN_free()) for private key BIGNUMs in _get_key_parameters() to scrub sensitive material. - PR #136: Remove static buffer in get_message_digest() that caused thread-safety problems under Perl ithreads. - PR #134: Add Perl-level stub for use_sslv23_padding() on OpenSSL 3.x where the underlying RSA_SSLV23_PADDING constant was removed. - PR #133: Fix PSS MGF1 setup to inspect the correct padding fields (sign_pad/verify_pad) instead of p_rsa->padding, preventing wrong MGF1 hash on auto-promoted PSS operations. - PR #120: Check PEM_write_bio_* return values in key export functions so failures are reported rather than silently ignored. - PR #119: Migrate SHA* digest calls to EVP_Q_digest() on OpenSSL 3.x, replacing deprecated low-level SHA*() functions. - PR #109: Drain the full OpenSSL error queue in croakSsl() and report the last (most specific) error rather than the oldest one. - PR #104: Guard croakSsl() against a NULL error string from ERR_reason_error_string() to prevent a NULL-deref croak. - PR #76: Do not include whrlpool.h when whirlpool support is disabled. - Memory leak fixes across OpenSSL 3.x code paths (PR #75, PR #77, PR #78, PR #79, PR #80, PR #81, PR #83, PR #87, PR #90, PR #99, PR #101, PR #108, PR #112, PR #114, PR #127, PR #128, PR #129, PR #131): plugged leaks in generate_key(), sign(), verify(), rsa_crypt(), check_key(), get_public_key_string(), _new_key_from_parameters(), and _get_key_parameters() across success and error paths. [Improvements] - PR #169: Make Crypt::OpenSSL::Bignum a hard runtime requirement (moved from recommended to required in Makefile.PL and added hard import in RSA.pm); it was already required in practice for get_key_parameters(). - PR #126: new_public_key() now accepts DER-encoded public keys in addition to PEM; format is detected automatically via ASN.1 OID inspection. - PR #124: Add get_private_key_pkcs8_string() to export private keys in PKCS#8 PEM format. - PR #110: Add get_public_key_pkcs1_string() as an alias for get_public_key_string() for API symmetry with the X.509/PKCS#1 naming. - PR #111: Add optional check=>1 parameter to new_key_from_parameters() to validate the constructed key via check_key() before returning it. - PR #135: Add plaintext length pre-validation in rsa_crypt() with a descriptive croak before attempting the OpenSSL operation. - PR #151: Reject invalid (even-numbered) RSA exponents before passing them to OpenSSL, preventing a potential hang during key generation. [Maintenance] - PR #163: Add CONTRIBUTING.md and SECURITY.md to satisfy CPANTS experimental kwalitee metrics. - PR #144: Clean up Makefile.PL metadata: remove dead -DPERL5 and -DOPENSSL_NO_KRB5 defines; derive version dynamically from RSA.pm. - PR #130: Add test coverage for generate_key() with custom public exponents and exponent validation. - PR #121: Add test coverage for private_encrypt() and public_decrypt(). - PR #148: Add PKCS#1 v1.5 signing regression tests (PR #148). - PR #95: Add error-path and edge-case test coverage (t/error.t). - PR #115, PR #116: Add encrypt/decrypt and sign/verify edge-case tests. - PR #85, PR #86, PR #88, PR #91: Improve test assertions — replace bare ok() calls with is()/like() and add descriptive test names throughout. - PR #84: Add macOS CI job covering both system LibreSSL and Homebrew OpenSSL 3.x. - PR #123: Add Valgrind memory-leak detection CI job on Debian bookworm. - PR #73: Fix META URLs, remove duplicate .gitignore entries, fix build_requires; add Debian trixie (OpenSSL 3.4.x) to CI matrix. - PR #72: Bump actions/checkout from v4 to v6. - PR #82: Bump perl-actions/perl-versions from 1 to 2. - PR #70: Add Dependabot for automatic GitHub Actions version updates. - PR #69: Remove Debian buster from CI matrix (EOL). 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 - Remove EUMM Remove version check - #31 by removing reference to RSA_SSLV23_PADDING (removed from OpenSSL starting from v3.0.0) - support passphase protected private key load - fix 'unsupported encryption' error on old library versions - Clarify croak message for missing passphrase on older cyphers - More structs opaqued in LibreSSL 3.5 - Use a macro for dealing with older SSL lacking macros - more CI fixups. Drop testing for 5.10 and 5.8. Something is broken upstream. 0.32 Wed Sep 8 2021 - Prefix internal bn2sv function so it doesn't collide with Net::SSLeay - Ensure that verify() leaves openssl error stack clean on failure - Fixed broken SEE ALSO links. - prevent outer $SIG{__DIE__} handler from being called during optional require. - omit done_testing since it does not work for older perl versions 0.31 Mon Sep 24 2018 - Remove default of SHA256 for RSA keys. This has caused significant problems with downstream modules and it has always been possible to do $key->use_sha256_hash() 0.30 Tue May 1 2018 - Working windows library detection - Actively testing on appveyor for windows now. - work correctly on LibreSSL 0.29_03 Mon Apr 16 2018 - Add whirlpool hash support. - Crypt::OpenSSL::Random is now required at comnpile-time. - Use the new interface to RSA_generate_key if available - Add library paths to LIBS from Crypt::OpenSSL::Guess 0.29_02 Sun Apr 15 2018 - Add missing require of Config::OpenSSL::Guess 0.29_01 Fri Apr 13 2018 - Adapt to OpenSSL 1.1.0 (dur-randir) - Move issue tracker to github. - Modernization as in Crypt::OpenSSL::Random. - better MSWin32 hints, fixes MSVC libraries, - more meta tests, - prefer hash mode NID_sha256 over NID_sha1 for sign 0.28 Thu Aug 25 2011 - Moritz Onken (PERLER) - RT 56454 - Win32 compatibility patch (kmx@cpan.org) 0.27 Wed Jun 29 2011 - Todd Rinaldo (TODDR) - RT 65947 - Fix RSA.pm break with perl 5.14+ 0.26 Sun Nov 22 2009 11:01:13 - Change subclassing test to generate a 512 bit key in order to work around an odd issue seen on some 64-bit redhat systems. (CPAN bug 45498) 0.25 Sun May 20 2007 12:56:11 - Add a LICENSE file. - Fix a bug (reported by many) in rsa.t - we were incorrectly counting the number of tests in situations where use_sha512_hash was not available. 0.24 Mon Nov 13 2006 08:21:14 - Fix a bug reported by Mark Martinec where encrypt could segfault if called with insufficient data; it now informatively croaks instead. - Fix a bug reported by Mark Martinec where check_key would segfault instead of croaking when called on a public key. - Fix decrypt and private_encrypt to croak instead of segfault when called on a public key. - Add an is_private method. - Silence a few compiler warnings about ignoring return values from certain BIO_* methods. 0.23 Wed Apr 12 2006 00:06:10 - Provide 32 bytes of seeding in tests, up from 19. - Stop relying on implicit includes, which disappeared in the 0.98 release of OpenSSL. - Apply patch from Jim Radford to add support for SHA{224,256,384,512} 0.22 Mon Nov 15 2005 21:13:20 - Add public_decrypt, private_encrypt methods, contributed by Paul G. Weiss - Some changes to help builds on Redhat9 - Remove deprecated methods: * the no-arg new constructor - use new_from_public_key, new_from_private_key or Crypt::OpenSSL::RSA->generate_key instead * load_public_key - use new_from_public_key * load_private_key - use new_from_private_key * generate_key as an instance method - use it as a class constructor method instead. * set_padding_mode - use use_no_padding, use_pkcs1_padding, use_pkcs1_oaep_padding, or use_sslv23_padding instead. * get_padding_mode - Eliminate all(most all) memory leaks. - fix email address - Stop returning true from methods just to indicate success. - Change default public exponent from 65535 to 65537 0.21 Sun Feb 15 2004 21:13:45 - Include t/format.t in the MANIFEST file, so that it is actually included in the distribution. 0.20 Sun Feb 15 2004 15:21:40 - Finally add support for the public key format produced by "openssl rsa -pubout". - Add comment in readme about locating kerberos files on redhat systems 0.19 Sun Apr 27 2003 18:33:48 - Revert back to old declaration style so that we no longer break under perl 5.005 (spotted by Rob Brown ). - Add some needed use statements in legacy.t and rsa.t (patch submitted by Rob Brown). - Fix typo in docs spotted by Daniel Drown - Update copyright dates. 0.18 Sun Feb 23 2003 20:44:35 - Add two new methods, new_key_from_parameters and get_key_parameters, which, working with Crypt::OpenSSL::Bignum, allow working directly with the paramaters of an rsa key. 0.17 Mon Jan 06 2003 22:43:31 - Workaround for gcc 3.2 compile problems: "/usr/include/openssl/des.h:193: parse error before '&' token" (Patch by Rob Brown ) - Deprecate no-arg constructor, load_*_key methods and the instance method generate_key; switch to three constructors: new_public_key, new_private_key and generate_key (as a class method) - Deprecate set_padding_mode method; replace with use_xxx_padding. - move tests into t directory, use Test as a framework 0.16 Tue Jun 11 22:01:45 - Fix bug reported by Rob McMillin which prevented subclassing. 0.15 Fri Jun 07 09:13:12 - Fix two bugs reported by Gordon Lack : use IV, not I32, for pointers, and cast the right-hand, not left-hand, value when doing an assignment from an SV to an HV 0.14 Sun May 19 12:35:21 - Fix bug reported by Charles Jardine : use Safefree, not free, to release memory allocated by New 0.13 Thu Mar 21 00:10:30 - Incorporating patch from Matthias Bauer , which provides signing and verification, as well as uses OpenSSL's internal error reporting system. This patch also fixes a bug with the RSA_NO_PADDING_MODE. Thanks, Matthias! - Deprecate set_padding_mode in favor of use_xxx_padding. - Rather than returning true on success, false on failure, just croak when there are problems. - Plug memory leaks. - Fix my email address (it's cpan.org, not cpan.com) 0.12 Thu Sep 06 22:44:17 - Fixing bug with Crypt::OpenSSL::Random interoperability - Implementing patch from Thomas Linden fixing a keysize bug - Fixing email address in docs. 0.11 Tue Apr 10 22:45:31 - Fixing bug in test.pl. 0.10 Mon Apr 09 18:25:41 - Moving random routines into Crypt::OpenSSL::Random - Use New instead of malloc 0.09 Mon Apr 02 12:27:10 - Typo fix, and always exercise test random_seed in testing. 0.08 Sun Apr 01 23:04:31 - Changing method names to match convention 0.07 Thu Mar 08 3:31:41 2001 - Allow seeding of the PRNG 0.06 Thu Mar 08 12:40:04 2001 - Adding a readme file. 0.05 Mon Feb 26 10:50:43 2001 - Removing signing and verification, due to bizarre bugs 0.04 Fri Feb 23 10:41:33 2001 - Removing Base64 functionality and dependence 0.01 Wed Feb 14 11:21:42 2001 - original version; created by h2xs 1.19 Crypt-OpenSSL-RSA-0.41/README.md0000644000175000017500000003255415172443224014103 0ustar timtim[![testsuite](https://github.com/cpan-authors/Crypt-OpenSSL-RSA/actions/workflows/testsuite.yml/badge.svg)](https://github.com/cpan-authors/Crypt-OpenSSL-RSA/actions/workflows/testsuite.yml) # NAME Crypt::OpenSSL::RSA - RSA encoding and decoding, using the openSSL libraries # SYNOPSIS use Crypt::OpenSSL::Random; use Crypt::OpenSSL::RSA; # not necessary if we have /dev/random: Crypt::OpenSSL::Random::random_seed($good_entropy); Crypt::OpenSSL::RSA->import_random_seed(); $rsa_pub = Crypt::OpenSSL::RSA->new_public_key($key_string); $ciphertext = $rsa->encrypt($plaintext); $rsa_priv = Crypt::OpenSSL::RSA->new_private_key($key_string); $plaintext = $rsa->decrypt($ciphertext); $rsa = Crypt::OpenSSL::RSA->generate_key(1024); # or $rsa = Crypt::OpenSSL::RSA->generate_key(1024, $prime); print "private key is:\n", $rsa->get_private_key_string(); print "public key (in PKCS1 format) is:\n", $rsa->get_public_key_string(); print "public key (in X509 format) is:\n", $rsa->get_public_key_x509_string(); $rsa_priv->use_md5_hash(); # insecure. use_sha256_hash or use_sha1_hash are the default $signature = $rsa_priv->sign($plaintext); print "Signed correctly\n" if ($rsa->verify($plaintext, $signature)); # SECURITY Version 0.35 disabled PKCS#1 v1.5 padding entirely to mitigate the Marvin attack. However, the Marvin attack only affects PKCS#1 v1.5 **decryption** (padding oracle), not **signatures**. Version 0.38 re-enables `use_pkcs1_padding()` for use with `sign()` and `verify()`, while keeping it disabled for `encrypt()` and `decrypt()`. PKCS1\_OAEP should be used for encryption and either PKCS1\_PSS or PKCS1 can 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 decryption, signatures and signature verification. _NOTE_: Many of the methods in this package can croak, so use `eval`, or Error.pm's try/catch mechanism to capture errors. Also, while some methods from earlier versions of this package return true on success, this (never documented) behavior is no longer the case. # Class Methods - new\_public\_key Create a new `Crypt::OpenSSL::RSA` object by loading a public key in from a string containing either PEM or DER encoding of the PKCS#1 or X.509 representation of the key. For PEM keys, the string should include the `-----BEGIN...-----` and `-----END...-----` lines. Both `BEGIN RSA PUBLIC KEY` (PKCS#1) and `BEGIN PUBLIC KEY` (X.509/SubjectPublicKeyInfo) formats are supported. DER-encoded keys (raw binary ASN.1) are also accepted and the format (PKCS#1 vs X.509) is auto-detected. 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. Call `use_pkcs1_pss_padding` or `use_pkcs1_padding` prior to signing operations. - new\_private\_key Create a new `Crypt::OpenSSL::RSA` object by loading a private key in from a string containing either PEM or DER encoding of the key. For PEM keys, the string should include the `-----BEGIN...-----` and `-----END...-----` lines. The padding is set to PKCS1\_OAEP, but can be changed with `use_xxx_padding`. DER-encoded keys (raw binary ASN.1) are also accepted. An optional parameter can be passed for passphrase-protected private keys: - passphrase The passphrase which protects the private key. For PEM keys, this decrypts traditional encrypted PEM (`DEK-Info` header) and encrypted PKCS#8 PEM (`BEGIN ENCRYPTED PRIVATE KEY`). For DER keys, this decrypts encrypted PKCS#8 DER (`EncryptedPrivateKeyInfo` ASN.1 structure). - generate\_key Create a new `Crypt::OpenSSL::RSA` object by constructing a private/public key pair. The first (mandatory) argument is the key size, while the second optional argument specifies the public exponent (the default public exponent is 65537). The padding is set to `PKCS1_OAEP`, but can be changed with use\_xxx\_padding methods. - new\_key\_from\_parameters Given [Crypt::OpenSSL::Bignum](https://metacpan.org/pod/Crypt%3A%3AOpenSSL%3A%3ABignum) objects for n, e, and optionally d, p, and q, where p and q are the prime factors of n, e is the public exponent and d is the private exponent, create a new Crypt::OpenSSL::RSA object using these values. If p and q are 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. An optional `check => 1` parameter can be passed after the key components to validate the key immediately after construction: my $rsa = Crypt::OpenSSL::RSA->new_key_from_parameters( $n, $e, $d, $p, $q, check => 1 ); When enabled, `check_key()` is called on the resulting key. If the key parameters are inconsistent (e.g. wrong CRT values, mismatched n/e/d/p/q), the constructor will croak instead of returning an object that fails at first use. The check is only performed on private keys; public-only keys (n and e only) are returned without validation. - import\_random\_seed Import a random seed from [Crypt::OpenSSL::Random](https://metacpan.org/pod/Crypt%3A%3AOpenSSL%3A%3ARandom), since the OpenSSL libraries won't allow sharing of random structures across perl XS modules. # Instance Methods - DESTROY Clean up after ourselves. In particular, erase and free the memory occupied by the RSA key structure. - get\_public\_key\_string Return the Base64/DER-encoded PKCS1 representation of the public key. This string has header and footer lines: -----BEGIN RSA PUBLIC KEY------ -----END RSA PUBLIC KEY------ - get\_public\_key\_pkcs1\_string Alias for `get_public_key_string`. Returns the same PKCS#1 `RSAPublicKey` PEM format (`BEGIN RSA PUBLIC KEY`). Provided for naming symmetry with the import method `new_public_key` (which auto-detects PKCS#1 vs X.509) and with `get_public_key_x509_string`. - get\_public\_key\_x509\_string Return the Base64/DER-encoded representation of the "subject public key", suitable for use in X509 certificates. This string has header and footer lines: -----BEGIN PUBLIC KEY------ -----END PUBLIC KEY------ and is the format that is produced by running `openssl rsa -pubout`. - get\_private\_key\_string Return the Base64/DER-encoded PKCS1 representation of the private key. This string has header and footer lines: -----BEGIN RSA PRIVATE KEY------ -----END RSA PRIVATE KEY------ 2 optional parameters can be passed for passphrase protected private key string: - passphrase The passphrase which protects the private key. - cipher name The cipher algorithm used to protect the private key. Default to 'des3'. - get\_private\_key\_pkcs8\_string Return the Base64/DER-encoded PKCS#8 representation of the private key. This string has header and footer lines: -----BEGIN PRIVATE KEY----- -----END PRIVATE KEY----- This is the format produced by `openssl pkey -outform PEM`, and is the private-key counterpart of `get_public_key_x509_string`. Accepts the same optional passphrase and cipher-name parameters as `get_private_key_string`. - encrypt Encrypt a binary "string" using the public (portion of the) key. - decrypt Decrypt a binary "string". Croaks if the key is public only. - private\_encrypt Encrypt a binary "string" using the private key. Croaks if the key is public only. On OpenSSL 3.x, only `use_no_padding` and `use_pkcs1_padding` are supported; OAEP and PSS will croak. - public\_decrypt Decrypt a binary "string" using the public (portion of the) key. On OpenSSL 3.x, only `use_no_padding` and `use_pkcs1_padding` are supported; OAEP and PSS will croak. - sign Sign a string using the secret (portion of the) key. - verify Check the signature on a text. # Padding Methods **use\_pkcs1\_padding** can be used for signature operations (`sign()` and `verify()`). PKCS#1 v1.5 encryption is disabled due to the Marvin attack. **use\_pkcs1\_pss\_padding** is the recommended replacement for signatures. **use\_pkcs1\_oaep\_padding** is used for encryption operations. On OpenSSL 3.x, the appropriate padding is set for each operation unless **use\_no\_padding** or **use\_pkcs1\_padding** is called before the operation. - 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 for **signature operations only**. PKCS#1 v1.5 signatures (RSASSA-PKCS1-v1.5) are secure and widely required by protocols such as JWT RS256, ACME (RFC 8555), and SAML. **Note**: PKCS#1 v1.5 **encryption** is disabled because it is vulnerable to the [Marvin Attack](https://github.com/tomato42/marvin-toolkit/blob/master/README.md) (a timing side-channel on decryption padding validation). Calling `encrypt()` or `decrypt()` with this padding will croak. Use `use_pkcs1_oaep_padding()` for encryption. - 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` 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 denotes that the server is SSL3 capable. **Not available on OpenSSL 3.x or later.** Calling this method will croak with a descriptive error message suggesting alternatives. Use `use_pkcs1_oaep_padding()` for encryption or `use_pkcs1_pss_padding()` for signatures. # Hash/Digest Methods - use\_md5\_hash Use the RFC 1321 MD5 hashing algorithm by Ron Rivest when signing and verifying messages. Note that this is considered **insecure**. - use\_sha1\_hash Use the RFC 3174 Secure Hashing Algorithm (FIPS 180-1) when signing and verifying messages. This is the default, when use\_sha256\_hash is not available. - use\_sha224\_hash, use\_sha256\_hash, use\_sha384\_hash, use\_sha512\_hash These FIPS 180-2 hash algorithms, for use when signing and verifying messages, are only available with newer openssl versions (>= 0.9.8). use\_sha256\_hash is the default hash mode when available. - use\_ripemd160\_hash Dobbertin, Bosselaers and Preneel's RIPEMD hashing algorithm when signing and verifying messages. - use\_whirlpool\_hash Vincent Rijmen und Paulo S. L. M. Barreto ISO/IEC 10118-3:2004 WHIRLPOOL hashing algorithm when signing and verifying messages. - size Returns the size, in bytes, of the key. All encrypted text will be of this size, and depending on the padding mode used, the length of the text to be encrypted should be: - pkcs1\_oaep\_padding at most 42 bytes less than this size. - pkcs1\_padding or sslv23\_padding at most 11 bytes less than this size. - no\_padding exactly this size. - check\_key This function validates the RSA key, returning a true value if the key is valid, and a false value otherwise. Croaks if the key is public only. - get\_key\_parameters Return `Crypt::OpenSSL::Bignum` objects representing the values of `n`, `e`, `d`, `p`, `q`, `d mod (p-1)`, `d mod (q-1)`, and `1/q mod p`, where `p` and `q` are the prime factors of `n`, `e` is the public exponent and `d` is the private exponent. Some of these values may return as `undef`; only `n` and `e` will be defined for a public key. The `Crypt::OpenSSL::Bignum` module must be installed for this to work. - is\_private Return true if this is a private key, and false if it is public only. # AUTHOR Ian Robertson, `iroberts@cpan.org`. For support, please email `perl-openssl-users@lists.sourceforge.net`. # ACKNOWLEDGEMENTS # LICENSE Copyright (c) 2001-2011 Ian Robertson. Crypt::OpenSSL::RSA is free software; you may redistribute it and/or modify it under the same terms as Perl itself. # SEE ALSO [perl(1)](http://man.he.net/man1/perl), [Crypt::OpenSSL::Random](https://metacpan.org/pod/Crypt%3A%3AOpenSSL%3A%3ARandom), [Crypt::OpenSSL::Bignum](https://metacpan.org/pod/Crypt%3A%3AOpenSSL%3A%3ABignum), [rsa(3)](http://man.he.net/man3/rsa), [RSA\_new(3)](http://man.he.net/?topic=RSA_new§ion=3), [RSA\_public\_encrypt(3)](http://man.he.net/?topic=RSA_public_encrypt§ion=3), [RSA\_size(3)](http://man.he.net/?topic=RSA_size§ion=3), [RSA\_generate\_key(3)](http://man.he.net/?topic=RSA_generate_key§ion=3), [RSA\_check\_key(3)](http://man.he.net/?topic=RSA_check_key§ion=3) Crypt-OpenSSL-RSA-0.41/META.json0000664000175000017500000000331415172615445014245 0ustar timtim{ "abstract" : "RSA encoding and decoding, using the openSSL libraries", "author" : [ "Ian Robertson " ], "dynamic_config" : 1, "generated_by" : "ExtUtils::MakeMaker version 7.78, CPAN::Meta::Converter version 2.150013", "license" : [ "perl_5" ], "meta-spec" : { "url" : "http://search.cpan.org/perldoc?CPAN::Meta::Spec", "version" : 2 }, "name" : "Crypt-OpenSSL-RSA", "no_index" : { "directory" : [ "t", "inc" ] }, "prereqs" : { "build" : { "requires" : { "ExtUtils::MakeMaker" : "0" } }, "configure" : { "requires" : { "Crypt::OpenSSL::Guess" : "0.11" } }, "runtime" : { "requires" : { "Crypt::OpenSSL::Bignum" : "0", "Crypt::OpenSSL::Random" : "0", "perl" : "5.006" } }, "test" : { "requires" : { "Test::More" : "0" } } }, "provides" : { "Crypt::OpenSSL::RSA" : { "file" : "RSA.pm", "version" : "0.41" } }, "release_status" : "stable", "resources" : { "bugtracker" : { "web" : "https://github.com/cpan-authors/Crypt-OpenSSL-RSA/issues" }, "homepage" : "https://github.com/cpan-authors/Crypt-OpenSSL-RSA", "license" : [ "https://dev.perl.org/licenses/" ], "repository" : { "type" : "git", "url" : "https://github.com/cpan-authors/Crypt-OpenSSL-RSA.git", "web" : "https://github.com/cpan-authors/Crypt-OpenSSL-RSA" } }, "version" : "0.41", "x_serialization_backend" : "JSON::PP version 4.16" }