HTTP-PublicKeyPins
view release on metacpan or search on metacpan
lib/HTTP/PublicKeyPins.pm view on Meta::CPAN
package HTTP::PublicKeyPins;
use 5.006;
use strict;
use warnings;
use Crypt::OpenSSL::X509();
use Crypt::OpenSSL::RSA();
use Crypt::PKCS10();
use Convert::ASN1();
use Digest();
use MIME::Base64();
use English qw( -no_match_vars );
use FileHandle();
use Exporter();
use Carp();
*import = \&Exporter::import;
our @EXPORT_OK = qw(
pin_sha256
);
our %EXPORT_TAGS = ( 'all' => \@EXPORT_OK, );
sub _CERTIFICATE_HEADER_SIZE { return 40; }
sub _MAX_PUBLIC_KEY_SIZE { return 65_536; }
our $VERSION = '0.16';
sub pin_sha256 {
my ($path) = @_;
my $handle = FileHandle->new($path)
or Carp::croak("Failed to open $path for reading:$EXTENDED_OS_ERROR");
binmode $handle;
read $handle, my $file_header, _CERTIFICATE_HEADER_SIZE()
or Carp::croak("Failed to read from $path:$EXTENDED_OS_ERROR");
my $pem_encoded_public_key_string;
if ( $file_header =~
/^[-]{5}BEGIN[ ](?:X[.]?509[ ]|TRUSTED[ ])?CERTIFICATE[-]{5}/smx )
{
$pem_encoded_public_key_string =
_process_pem_x509_certificate( $handle, $file_header, $path );
}
elsif ( $file_header =~
/^[-]{5}BEGIN[ ](?:RSA[ ])?(PUBLIC|PRIVATE)[ ]KEY[-]{5}/smx )
{
my ($type) = ($1);
if ( $type eq 'PRIVATE' ) {
$pem_encoded_public_key_string =
_process_pem_private_key( $handle, $file_header, $path );
}
else {
$pem_encoded_public_key_string =
_process_pem_public_key( $handle, $file_header, $path );
}
}
elsif ( $file_header =~
/^[-]{5}BEGIN[ ](?:NEW[ ])?CERTIFICATE[ ]REQUEST[-]{5}/smx )
{
$pem_encoded_public_key_string =
_process_pem_pkcs10_certificate_request( $handle, $file_header,
$path );
}
else {
$pem_encoded_public_key_string =
_check_for_der_encoded_x509_certificate( $handle, $file_header,
$path )
|| _check_for_der_encoded_private_key( $handle, $file_header, $path )
|| _check_for_der_pkcs10_certificate_request( $handle, $file_header,
$path )
|| _check_for_der_encoded_public_key( $handle, $file_header, $path );
if ( !defined $pem_encoded_public_key_string ) {
Carp::croak("$path is not an X.509 Certificate");
}
}
$pem_encoded_public_key_string =~
s/^[-]{5}BEGIN[ ]PUBLIC[ ]KEY[-]{5}\r?\n//smx;
$pem_encoded_public_key_string =~
s/^[-]{5}END[ ]PUBLIC[ ]KEY[-]{5}\r?\n//smx;
my $der_encoded_public_key_string =
MIME::Base64::decode($pem_encoded_public_key_string);
my $digest = Digest->new('SHA-256');
$digest->add($der_encoded_public_key_string);
my $base64 = MIME::Base64::encode_base64( $digest->digest() );
chomp $base64;
return $base64;
}
sub _process_pem_x509_certificate {
my ( $handle, $file_header, $path ) = @_;
my $pem_encoded_public_key_string;
if ( $file_header =~ /^[-]{5}BEGIN[ ]CERTIFICATE[-]{5}/smx ) {
my $x509 = Crypt::OpenSSL::X509->new_from_file($path);
$pem_encoded_public_key_string =
_get_pem_encoded_public_key_string($x509);
}
else {
seek $handle, 0, Fcntl::SEEK_SET()
or Carp::croak("Failed to seek to start of $path:$EXTENDED_OS_ERROR");
defined read $handle, my $pem_encoded_certificate_string,
_MAX_PUBLIC_KEY_SIZE()
or Carp::croak("Failed to read from $path:$EXTENDED_OS_ERROR");
$pem_encoded_certificate_string =~
s/^([-]{5}BEGIN[ ])(?:X[.]?509|TRUSTED)[ ](CERTIFICATE[-]{5})/$1$2/smx;
$pem_encoded_certificate_string =~
s/^([-]{5}END[ ])(?:X[.]?509|TRUSTED)[ ](CERTIFICATE[-]{5})/$1$2/smx;
my $x509 = Crypt::OpenSSL::X509->new_from_string(
$pem_encoded_certificate_string);
$pem_encoded_public_key_string =
_get_pem_encoded_public_key_string($x509);
}
return $pem_encoded_public_key_string;
}
sub _process_pem_pkcs10_certificate_request {
my ( $handle, $file_header, $path ) = @_;
seek $handle, 0, Fcntl::SEEK_SET()
or Carp::croak("Failed to seek to start of $path:$EXTENDED_OS_ERROR");
defined read $handle, my $pkcs10_certificate_string, _MAX_PUBLIC_KEY_SIZE()
or Carp::croak("Failed to read from $path:$EXTENDED_OS_ERROR");
Crypt::PKCS10->setAPIversion(1);
my $req = Crypt::PKCS10->new($pkcs10_certificate_string)
or Carp::croak( 'Failed to initialise Crypt::PKCS10 library:'
. Crypt::PKCS10->error() );
my $pem_encoded_public_key_string = $req->subjectPublicKey(1);
return $pem_encoded_public_key_string;
}
lib/HTTP/PublicKeyPins.pm view on Meta::CPAN
} or do {
return;
};
return $pem_encoded_public_key_string;
}
sub _check_for_der_encoded_x509_certificate {
my ( $handle, $file_header, $path ) = @_;
my $pem_encoded_public_key_string;
eval {
my $temp_handle = FileHandle->new( $path, Fcntl::O_RDONLY() )
or
Carp::croak("Failed to open '$path' for reading:$EXTENDED_OS_ERROR");
binmode $temp_handle;
my $string;
while ( my $line = <$temp_handle> ) {
$string .= $line;
}
close $temp_handle
or Carp::croak("Failed to close '$path':$EXTENDED_OS_ERROR");
my $x509 = Crypt::OpenSSL::X509->new_from_string( $string,
Crypt::OpenSSL::X509::FORMAT_ASN1() );
$pem_encoded_public_key_string =
_get_pem_encoded_public_key_string($x509);
} or do {
return;
};
return $pem_encoded_public_key_string;
}
sub _check_for_der_encoded_public_key {
my ( $handle, $file_header, $path ) = @_;
my $pem_encoded_public_key_string;
seek $handle, 0, Fcntl::SEEK_SET()
or Carp::croak("Failed to seek to start of $path:$EXTENDED_OS_ERROR");
defined read $handle, my $der_encoded_public_key_string,
_MAX_PUBLIC_KEY_SIZE()
or Carp::croak("Failed to read from $path:$EXTENDED_OS_ERROR");
my $asn = Convert::ASN1->new( encoding => 'DER' );
$asn->prepare(
<<"__SUBJECT_PUBLIC_KEY_INFO__") or Carp::croak( 'Failed to prepare SubjectPublicKeyInfo in ASN1:' . $asn->error() );
SEQUENCE {
algorithm SEQUENCE { algorithm OBJECT IDENTIFIER, parameters ANY OPTIONAL },
subjectPublicKey BIT STRING
}
__SUBJECT_PUBLIC_KEY_INFO__
eval {
my $pub_key = $asn->decode($der_encoded_public_key_string)
or Carp::croak(
'Failed to decode SubjectPublicKeyInfo in ASN1:' . $asn->error() );
$pem_encoded_public_key_string =
"-----BEGIN PUBLIC KEY-----\n"
. MIME::Base64::encode_base64($der_encoded_public_key_string)
. "-----END PUBLIC KEY-----\n";
} or do {
return;
};
return $pem_encoded_public_key_string;
}
sub _check_for_der_encoded_private_key {
my ( $handle, $file_header, $path ) = @_;
my $pem_encoded_public_key_string;
seek $handle, 0, Fcntl::SEEK_SET()
or Carp::croak("Failed to seek to start of $path:$EXTENDED_OS_ERROR");
defined read $handle, my $der_encoded_private_key_string,
_MAX_PUBLIC_KEY_SIZE()
or Carp::croak("Failed to read from $path:$EXTENDED_OS_ERROR");
my $pem_encoded_private_key_string =
"-----BEGIN RSA PRIVATE KEY-----\n"
. MIME::Base64::encode_base64($der_encoded_private_key_string)
. "-----END RSA PRIVATE KEY-----\n";
eval {
my $privkey =
Crypt::OpenSSL::RSA->new_private_key($pem_encoded_private_key_string);
$pem_encoded_public_key_string = $privkey->get_public_key_x509_string();
} or do {
return;
};
return $pem_encoded_public_key_string;
}
sub _process_pem_private_key {
my ( $handle, $file_header, $path ) = @_;
my $pem_encoded_public_key_string;
seek $handle, 0, Fcntl::SEEK_SET()
or Carp::croak("Failed to seek to start of $path:$EXTENDED_OS_ERROR");
defined read $handle, my $rsa_private_key_string, _MAX_PUBLIC_KEY_SIZE()
or Carp::croak("Failed to read from $path:$EXTENDED_OS_ERROR");
my $privkey = Crypt::OpenSSL::RSA->new_private_key($rsa_private_key_string);
$pem_encoded_public_key_string = $privkey->get_public_key_x509_string();
return $pem_encoded_public_key_string;
}
sub _process_pem_public_key {
my ( $handle, $file_header, $path ) = @_;
my $pem_encoded_public_key_string;
if ( $file_header =~ /^[-]{5}BEGIN[ ]RSA[ ]PUBLIC[ ]KEY[-]{5}/smx ) {
seek $handle, 0, Fcntl::SEEK_SET()
or Carp::croak("Failed to seek to start of $path:$EXTENDED_OS_ERROR");
defined read $handle, my $pem_encoded_rsa_public_key_string,
_MAX_PUBLIC_KEY_SIZE()
or Carp::croak("Failed to read from $path:$EXTENDED_OS_ERROR");
my $pubkey = Crypt::OpenSSL::RSA->new_public_key(
$pem_encoded_rsa_public_key_string);
$pem_encoded_public_key_string = $pubkey->get_public_key_x509_string();
}
else {
seek $handle, 0, Fcntl::SEEK_SET()
or Carp::croak("Failed to seek to start of $path:$EXTENDED_OS_ERROR");
defined read $handle, $pem_encoded_public_key_string,
_MAX_PUBLIC_KEY_SIZE()
or Carp::croak("Failed to read from $path:$EXTENDED_OS_ERROR");
}
return $pem_encoded_public_key_string;
}
sub _get_pem_encoded_public_key_string {
my ($x509) = @_;
my $pem_encoded_public_key_string;
if ( $x509->key_alg_name() eq 'rsaEncryption' ) {
my $pubkey = Crypt::OpenSSL::RSA->new_public_key( $x509->pubkey() );
$pem_encoded_public_key_string = $pubkey->get_public_key_x509_string();
}
else {
$pem_encoded_public_key_string = $x509->pubkey();
}
return $pem_encoded_public_key_string;
}
1; # End of HTTP::PublicKeyPins
__END__
=head1 NAME
HTTP::PublicKeyPins - Generate RFC 7469 HTTP Public Key Pin (HPKP) header values
=head1 VERSION
Version 0.16
=head1 SYNOPSIS
Make it more difficult for the bad guys to Man-In-The-Middle your users TLS sessions
use HTTP::Headers();
use HTTP::PublicKeyPins qw( pin_sha256 );
...
my $h = HTTP::Headers->new();
( run in 0.452 second using v1.01-cache-2.11-cpan-13bb782fe5a )