I have some problem with SSLeay in Perl. And I've no experience of Perl to speak of so I need your help!
I'm trying to communicate whit Apple Push Notification Service (APNS). That's kind of easy, but I can't use files to store the certificate and the RSA private key. This code should be run from a database.
This works:
Net::SSLeay::CTX_use_RSAPrivateKey_file( $ctx, 'key.pem', &Net::SSLeay::FILETYPE_PEM );
die_if_ssl_error("private key");
Net::SSLeay::CTX_use_certificate_file( $ctx, 'cert.pem', &Net::SSLeay::FILETYPE_PEM );
die_if_ssl_error("certificate");
But as I said earlier, I cannot use files. So I tried this:
$private_key = '----BEGIN RSA PRIVATE [...]';
my $rsa_private = Crypt::OpenSSL::RSA->new_private_key($private_key);
Net::SSLeay::CTX_use_RSAPrivateKey( $ctx, $rsa_private );
die_if_ssl_error("private key");
Net::SSLeay::CTX_use_certificate_file( $ctx, 'cert.pem', &Net::SSLeay::FILETYPE_PEM );
die_if_ssl_error("certificate");
But now I get Segmentation fault at row 7. If I exit at row 6, no error occurs.
What am I doing wrong at this step?
Next step is to get the certificate from a string as well. The documentation for SSLeay says the second parameter of CTX_use_certificate must be a x509 object. So I try to create one:
my $private_key = '----BEGIN RSA PRIVATE [...]';
my $certificate = '----BEGIN CERTIFICATE [...]';
my $rsa_private = Crypt::OpenSSL::RSA->new_private_key($private_key);
Net::SSLeay::CTX_use_RSAPrivateKey( $ctx, $rsa_private );
die_if_ssl_error("private key");
my $x509 = Crypt::OpenSSL::X509->new_from_string($certificate);
Net::SSLeay::CTX_use_certificate( $ctx, $x509 );
die_if_ssl_error("certificate");
But that gives me:
certificate 9530: 1 - error:140BF10C:SSL routines:SSL_SET_CERT:x509 lib
9530: certificate
Do you have any tip or idea how to solve this problem?
this should work if Net::SSLeay version is equal or higher then 1.45
my $pkey = '----BEGIN PRIVATE KEY ... (PEM formated)';
my $cert = '----BEGIN CERTIFICATE ... (PEM formated)';
my $bio_key = Net::SSLeay::BIO_new(Net::SSLeay::BIO_s_mem()) or die;
Net::SSLeay::BIO_write($bio_key, $pkey) or die "no key";
my $evp_pkey = Net::SSLeay::PEM_read_bio_PrivateKey($bio_key,
sub { RETURN PASSWORD IF ANY NESSESARY}) or die "no evp_pkey structure";
Net::SSLeay::CTX_use_PrivateKey($ctx, $evp_pkey);
Net::SSLeay::BIO_free($bio_key);
my $bio_cert = Net::SSLeay::BIO_new(Net::SSLeay::BIO_s_mem()) or die;
Net::SSLeay::BIO_write($bio_cert, $cert) or die "no cert";
my $x509 = Net::SSLeay::PEM_read_bio_X509($bio_cert) or die "no x509 structure";
Net::SSLeay::CTX_use_certificate($ctx, $x509) or die ;
Net::SSLeay::BIO_free($bio_cert);
Related
I was creating a mock server for OAuth2 and I was stuck on during the generation of a JWK token, more specifically, I failed to understand how to convert the e and n parameters
I have this code:
use strict;
use v5.26.0;
use Data::Dumper; # or DDP if have i t installed!
use Crypt::JWT qw(encode_jwt decode_jwt);
use Crypt::OpenSSL::RSA;
use Crypt::OpenSSL::Bignum; # needs to be installed in order to run $rsa_pub->get_key_parameters();
use MIME::Base64;
# don't worry, this is just a test/mock server with 'random' generated RSA pair
my $rsa_pvt = Crypt::OpenSSL::RSA->new_private_key(
'-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEA0LdLaW9i0NUVwV1+ZSnwwYebLliFLCEQjzsklGww0fYa0VWM
kEl/WD1mam/idqqAadkgMKzZY4JWx824rRpHOosTJbOMHKa1cRA933yv8Ywzahmx
ih9Jx/QjK2VHSlxTRq2cKN/E9S2VN64PBlBc2LsoAzM9M4r9X90KJFi3+VazjDZ+
9iWA5UCqrWcGrEy21ZVLF27Dm03STIewtV9goBn/7Gd5sMfDfnvMLKSK5ZRbGigt
fkjS91qZUiHjU+WXJrMS2mTr/W9fmAJ7R9jQ09wpJVqP+UOnFjL/0mesAQ5H4FTt
RoXhYiKF3zqckRkAwRuP3sv2nCGA8MoiZR5rFwIDAQABAoIBAFpoSz4shX04D+hm
ey2O8T6jYtC8f1MSL34bfEjeZHdOR2eNywllDMhIMGjCdjI4wM8YwhzTgobcGoMJ
1YkF7Pyq6WxXTcXLYKTNCEAaXowe0taOspzF2MvIMMPHZw4K1/exlAcQhtw9Fnm7
574waUdoKnjYZRZCimZP9OixlV9nKrD36lytd1HewwcD+nm0whMq4Ud5W9AyGwwM
xJR9++srSiqi6wpj5p1cP73UgpdOn293foATV3NBjTBjndk/q1IoO8ylv+rn2Pwi
iFYGHUIrLsBBBYYPclEggP09M/r4vLlVhMpvf+0UBOodRLk5QuGsPlk3ch/jfSpf
f1JNLYECgYEA6lf4yjim+U3LumPKYzhM0zsQf5Qvp0pGb/7XWw3WdWc3hcnJevMp
uL1k6Qyg3186K+loJqdrsDqOQhZO2BBA8TMcsgiiEk2pTl5InSIf/aedNzuClX0D
gxjgOTMsp+9G6YafcvzG+5gCuFM0ONqyyC+mOl55ICSF0HPgN7TaUhECgYEA5AEI
LoxwPs4fXmXmpNg/2JD8upwl8GdEiDs4yaY2kE+5ocvtaNn6qi3kO5cDIAmkUqFE
WwjFMD2sROROtpivrlb3+GJbrjbmY27KrEFd5RkNRCArZV+sUNt/s7RrvNOVvJ9z
e82xYrNBXhpFhnaI4oLSN9ZWjx+mroX2M65iwqcCgYEAzzForqLYPqQh9HI2hvNI
OZqHQ8VpPKfXDz5qef8KFlNkK831bdeAk+4gQk0AD37Kl/iONV7hP7cGADhpDW+R
e7CNNnubkENJ5hhGa2e4kTSZNDRRiIo3iLl9xhUQ7ooIUIDOiYQlCl2kSgSGr53t
ZEF83y6YOWsRRPSu0ZH9VYECgYA4A/zjmsM02uUgBv78Ptioty4wFo7HmkdfBNW1
zO0Y1U1w7637FZqc1rt83GP7KgNB/bbSerwfVveM0V55Q9fdiCZR0rBdg8VkZmLK
oSCVWWtF8nVW6YNnNhYQq2HQuVbPSYlQwD81VX7YxLGSEGse4y8MYs9PSGJl/Cl5
lv1SfwKBgFE8zTWFAnMOBczF0VCl0hpUaIknlTwvL6iGf5Vl4M4XBKE8KEkjE+gE
H7lxZ8XxxVHrvm++BZZk5uSmIWGUZlJuRNLQDiRxLqNOAaG12YGyg/eTJOzkvzfL
U8UQV8ROVyZR1Rw8ya9ksAmlDjy55VOXHT9pvHd/FjQMcBeh9lE8
-----END RSA PRIVATE KEY-----'
);
my $rsa_pub = Crypt::OpenSSL::RSA->new_public_key(
'-----BEGIN RSA PUBLIC KEY-----
MIIBCgKCAQEA0LdLaW9i0NUVwV1+ZSnwwYebLliFLCEQjzsklGww0fYa0VWMkEl/
WD1mam/idqqAadkgMKzZY4JWx824rRpHOosTJbOMHKa1cRA933yv8Ywzahmxih9J
x/QjK2VHSlxTRq2cKN/E9S2VN64PBlBc2LsoAzM9M4r9X90KJFi3+VazjDZ+9iWA
5UCqrWcGrEy21ZVLF27Dm03STIewtV9goBn/7Gd5sMfDfnvMLKSK5ZRbGigtfkjS
91qZUiHjU+WXJrMS2mTr/W9fmAJ7R9jQ09wpJVqP+UOnFjL/0mesAQ5H4FTtRoXh
YiKF3zqckRkAwRuP3sv2nCGA8MoiZR5rFwIDAQAB
-----END RSA PUBLIC KEY-----'
);
my ($n, $e, #o) = $rsa_pub->get_key_parameters();
# I think something is wrong in this conversion
$n = encode_base64($n->to_bin(), '');
$e = encode_base64($e->to_bin(), '');
my $kids = {
"keys" => [
{
"kty" => "RSA",
"kid" => "rsa1",
"alg" => "RS256",
"use" => "sig",
"n" => $n,
"e" => $e
}
]
};
say "Keys parameters";
say Dumper($kids);
my $encoded = encode_jwt(
payload => {a => 'b'},
alg => 'RS256',
key => $rsa_pvt,
extra_headers => {'kid' => 'rsa1'},
);
say "Generated JWT: $encoded";
my $decoded_with_pub = decode_jwt(
token => $encoded,
key => $rsa_pub,
);
say "Decoded with pub certificate";
say Dumper($decoded_with_pub);
say "Trying to decode with certificate from parameters";
# do not returns,
# Use of uninitialized value in unpack at ~/perl5/lib/perl5/x86_64-linux-gnu-thread-multi/Crypt/PK/RSA.pm line 102.
my $decoded_with_kid = decode_jwt(
token => $encoded,
kid_keys => $kids,
);
The Crypt::JWT is working when I test with the production keys https://sso.staging.acesso.gov.br/jwk
The e parameter AQAB (same recommended exponent of 65537) is matching, so I don't know how else I should convert the n from BigInt into Base64 any different than the way I did
SEO:
Use of uninitialized value in unpack at ~/perl5/lib/perl5/x86_64-linux-gnu-thread-multi/Crypt/PK/RSA.pm line 102.
I am trying to seal/open a file. Encryption fails and the following error is generated.
error:0E06D06C:configuration file routines:NCONF_get_string:no value
Here's a code sample that can reproduce the issue
// Generate key pair and keep them safe...
$key = openssl_pkey_new([
'private_key_bits' => 4096,
'private_key_type' => OPENSSL_KEYTYPE_EC,
'curve_name' => 'prime256v1',
]);
$privKey = null;
openssl_pkey_export($key, $privKey);
$pubKeyDetails = openssl_pkey_get_details($key);
$pubKey = $pubKeyDetails['key'];
openssl_free_key($key);
// Load the pubkey to encrypt
$key = openssl_pkey_get_public($pubKey);
$data = file_get_contents('of-some-pretty-large-file');
// ----- This here fails -----
if (openssl_seal($data, $sealed, $eKeys, [$key]) === false) {
echo "Encryption failed\n";
echo openssl_error_string() . "\n";
exit;
}
openssl_free_key($key);
$key = openssl_pkey_get_private($privKey);
if (openssl_open($sealed, $decryptedData, $eKeys[0], $key)) {
echo ($decryptedData === $data ? "Matched\n" : "Trash\n");
}
openssl_free_key($key);
The error message
error:0E06D06C:configuration file routines:NCONF_get_string:no value
is not caused by openssl_seal, but by openssl_pkey_new. This does not affect the functionality, i.e. the key is generated successfully, see here and here. This also applies to the posted code, which generates a private EC key in SEC1 format and a public key in X.509 format.
The PHP method openssl_seal is based on the OpenSSL functions EVP_SealInit, EVP_SealUpdate and EVP_SealFinal, here. In the corresponding OpenSSL documentation the following is described in the Notes-section:
The public key must be RSA because it is the only OpenSSL public key algorithm that supports key transport.
This means that openssl_seal only works with RSA or an RSA key. If the following is used in the posted code:
$key = openssl_pkey_new([
'private_key_bits' => 4096,
'private_key_type' => OPENSSL_KEYTYPE_RSA
]);
decryption and encryption work as expected.
I'm trying to generate a token for an Http request call based on a private key generated using OpenSSL RSA (2048) and then a public key, which is generated by the group that has the API we're calling. One side of the token string is encrypted using the public key the other with the private key. The private key encryption works with the code below but calling the Crypt::OpenSSL::RSA->new_public_key(public key string) fails with the "Error : unrecognized key format" issue. I'm not sure what's wrong with the key but given that the other one works (and was supposedly generated using same OpenSSL unix tool) it seems odd.
Any help would be greatly appreciated.
Thanks,
Dave
use strict;
use Time::HiRes qw( gettimeofday );
use Crypt::OpenSSL::RSA;
use Crypt::OpenSSL::X509;
use Crypt::OpenSSL::Random;
use MIME::Base64;
use DateTime;
my $sysTime=undef;
my $sTime=undef;
my $sAPIToken;
my $EDMPrivateKey=qq~
-----BEGIN PRIVATE KEY-----
<private key base 64>
-----END PRIVATE KEY-----
~;
my $DMPublicKey=qq~
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsftmdt4aAZ7B1F7wVjIz0RSO6w1bIX/4
<rest of public key>
-----END PUBLIC KEY-----
~;
sub setTime() {
my $ms;
my $ms2;
my $microSec;
my $tz;
($sysTime,$microSec) = gettimeofday();
# $sysTime=gettimeofday();
$ms=sprintf("%.*s",3,$microSec);
$ms2=$microSec-($ms*1000);
my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst)=localtime($sysTime);
$sTime=sprintf("%04d-%02d-%02dT%02d:%02d:%02d.%03d-%04d",
$year+1900,$mon+1,$mday,$hour,$min,$sec,$ms,$tz);
$sysTime=$sysTime*1000+$ms;
}
sub setTimeNew() {
my $ms;
my $ms2;
my $microSec;
($sysTime,$microSec) = gettimeofday();
my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst)=localtime($sysTime);
$ms=sprintf("%.*s",3,$microSec);
$ms2=$microSec-($ms*1000);
my $dt=DateTime->new(
year => $year+1900,
month => $mon+1,
day => $yday,
hour => $hour,
minute => $min,
second => $sec,
nanosecond => $microSec*1000
);
$sysTime=$sysTime*1000+$ms;
$sTime=$dt->strftime("%C%y-%m-%dT%H:%M:%S.%3N%z");
}
sub main() {
my $sTokenPt1;
my $sTokenPt2;
my $sPubStr;
my $rsaPublic = Crypt::OpenSSL::RSA->new_public_key($DMPublicKey);
$rsaPublic->use_sha1_hash();
setTimeNew();
$sTokenPt1 = encode_base64($rsaPrivate->encrypt($sysTime));
$sTokenPt2 = encode_base64($rsaPublic->encrypt($sTime));
printf "%d\n%s\n",$sysTime,$sTime;
$sAPIToken = sprintf("%s,%s",$sTokenPt1,$sTokenPt2);
printf "%d\n%s\nTTOKEN=%s\n",$sysTime,$sTime,$sAPIToken;
}
eval {
main();
};
if ($#) {
printf "Error : %s\n",$#;
}
I need to open an SFTP connection in Perl and I need to use a dsa key file but I can't actually store the file on the hard disk for security reasons. I am trying to use Net::SFTP.
my $sftp = Net::SFTP->new(
$host, user=>"$userid",
ssh_args => {
identity_files => [ $pathToInMemoryKeyFile ]
}
);
I think I know how to get a string represented as an in memory file handle but I don't know how to get the path of that file handle such that I can pass it in as one of the ssh_args. Does anybody have any suggestions?
Thanks!
I've looked through the various options of doing SFTP (Net::SFTP hasn't been updated since 2005, Net::SFTP::Foreign is more up to date) and they all do key authentication via a file.
Net::SFTP is backed by Net::SSH::Perl which is a pure Perl SSH implementation. You can do some patching to make it do what you want. I'm going to sketch it out for you.
Patch or put a wrapper around Net::SSH::Perl::Auth::PublicKey->authenticate to look for a new configuration key. Let's call it identity_keys.
sub authenticate {
my $auth = shift;
my $ssh = $auth->{ssh};
my $sent = 0;
if (my $agent = $auth->mgr->agent) {
do {
$sent = $auth->_auth_agent;
} until $sent || $agent->num_left <= 0;
}
return $sent if $sent;
##### This is the new bit which tries any keys passed in. ######
my $ik = $ssh->config->get('identity_keys') || [];
for my $key (#$ik) {
return 1 if $auth->_auth_key($key);
}
my $if = $ssh->config->get('identity_files') || [];
my $idx = $auth->{_identity_idx} || 0;
for my $f (#$if[$idx..$#$if]) {
$auth->{_identity_idx}++;
return 1 if $auth->_auth_identity($f);
}
}
auth_key would be a copy of _auth_identity but calling Net::SSH::Perl::Key->read_private_key which would be the guts of Net::SSH::Perl::Key->read_private_pem minus opening and reading the key from a file. read_private_pem would then be gutted to use read_private_key.
Alternatively, use an ssh-agent. It holds the decrypted private key in memory, so you can immediately wipe it from the disk.
I have a private certificate that I use to authenticate with an external webserver. I want to automate downloading the webpage, but it requires my private key passphrase whenever it makes a request. Is there a way to provide this key through the script somehow (either in an environment variable or otherwise)? One small caveat: this needs to run on a Windows machine, so I cannot use the Expect module.
Here is the code I have so far:
use strict;
use warnings;
use LWP::UserAgent;
my $output_doc = $ARGV[0];
my $url = $ARGV[1];
$ENV{HTTPS_PROXY} = 'http://1.2.3.4:3128';
$ENV{PERL_NET_HTTPS_SSL_SOCKET_CLASS} = "Net::SSL";
$ENV{PERL_LWP_SSL_VERIFY_HOSTNAME} = 0;
$ENV{HTTPS_DEBUG} = 1;
$ENV{HTTPS_CERT_FILE} = 'test_cert/cert.pem';
$ENV{HTTPS_KEY_FILE} = 'test_cert/private.key';
my $ua = LWP::UserAgent->new;
my $req = HTTP::Request->new(GET => "$url");
my $response = $ua->request($req);
open OUT, ">$output_doc";
print OUT $response->as_string;
close OUT;
So far it works properly. It pulls down the webpage I provide in the second argument, but for each page I have to type my PEM passphrase. Thanks for any suggestions!
Remove the passphrase from the key.
openssl rsa -in private.key -out private-nopass.key
I am afraid you cannot supply passphrase for LWP::UserAgent, just remove it from certificate key.
You may try the following:
...
my $ua = LWP::UserAgent->new;
my $req = HTTP::Request->new(GET => "$url");
$IO::Socket::SSL::GLOBAL_CONTEXT_ARGS->{SSL_passwd_cb} = sub {
# get passphrase
return $passphrase
};
my $response = $ua->request($req);
...
Optionally you may consider to use:
# Client PKCS12 cert support
$ENV{HTTPS_PKCS12_FILE} = 'certs/pkcs12.pkcs12';
$ENV{HTTPS_PKCS12_PASSWORD} = 'PKCS12_PASSWORD';
I ended up creating the key in the PKCS12 format.
# Client PKCS12 cert support
$ENV{HTTPS_PKCS12_FILE} = 'certs/pkcs12.pkcs12';
$ENV{HTTPS_PKCS12_PASSWORD} = 'PKCS12_PASSWORD';
This worked without a problem.