general purpose certificate related helper functions Signed-off-by: Fabian Grünbichler <f.gruenbich...@proxmox.com> --- src/Makefile | 1 + src/PVE/Certificate.pm | 120 +++++++++++++++++++++++++++++++++++++++++++++++++ test/acme-test.pl | 8 ++-- 3 files changed, 126 insertions(+), 3 deletions(-) create mode 100644 src/PVE/Certificate.pm
diff --git a/src/Makefile b/src/Makefile index 0754666..17a794a 100644 --- a/src/Makefile +++ b/src/Makefile @@ -11,6 +11,7 @@ LIB_SOURCES = \ ACME/Challenge.pm \ ACME/StandAlone.pm \ AtomicFile.pm \ + Certificate.pm \ CLIHandler.pm \ CalendarEvent.pm \ CpuSet.pm \ diff --git a/src/PVE/Certificate.pm b/src/PVE/Certificate.pm new file mode 100644 index 0000000..b97d182 --- /dev/null +++ b/src/PVE/Certificate.pm @@ -0,0 +1,120 @@ +package PVE::Certificate; + +use strict; +use warnings; + +use Date::Parse; +use Net::SSLeay; + +use PVE::Tools; + +# see RFC 7468 +my $b64_char_re = qr![0-9A-Za-z\+/]!; +my $header_re = sub { + my ($label) = @_; + return qr!-----BEGIN\ $label-----(?:\s|\n)*!; +}; +my $footer_re = sub { + my ($label) = @_; + return qr!-----END\ $label-----(?:\s|\n)*!; +}; +my $pem_re = sub { + my ($label) = @_; + + my $header = $header_re->($label); + my $footer = $footer_re->($label); + + return qr{ + $header + (?:(?:$b64_char_re)+\s*\n)* + (?:$b64_char_re)*(?:=\s*\n=|={0,2})?\s*\n + $footer + }x; +}; + +my $strip_text = sub { + my ($label, $content) = @_; + + my $header = $header_re->($label); + $content =~ s/^.*?(?=$header)//s; + return $content; +}; + +sub check_pem { + my ($content, %opts) = @_; + + my $label = $opts{label} // 'CERTIFICATE'; + my $multiple = $opts{multiple}; + my $noerr = $opts{noerr}; + + $content = $strip_text->($label, $content); + + my $re = $pem_re->($label); + + $re = qr/($re\n+)*$re/ if $multiple; + + if ($content =~ /^$re$/) { + return $content; + } else { + return undef if $noerr; + die "not a valid PEM-formatted string.\n"; + } +} + +my $read_certificate = sub { + my ($cert_path) = @_; + + die "'$cert_path' does not exist!\n" if ! -e $cert_path; + + my $bio = Net::SSLeay::BIO_new_file($cert_path, 'r') + or die "unable to read '$cert_path' - $!\n"; + + my $cert = Net::SSLeay::PEM_read_bio_X509($bio); + if (!$cert) { + Net::SSLeay::BIO_free($bio); + die "unable to read certificate from '$cert_path'\n"; + } + + return $cert; +}; + +sub convert_asn1_to_epoch { + my ($asn1_time) = @_; + + my $iso_time = Net::SSLeay::P_ASN1_TIME_get_isotime($asn1_time); + return Date::Parse::str2time($iso_time); +} + +sub get_certificate_info { + my ($cert_path) = @_; + + my $cert = $read_certificate->($cert_path); + + my $info = {}; + $info->{fingerprint} = Net::SSLeay::X509_get_fingerprint($cert, 'sha256'); + $info->{subject} = Net::SSLeay::X509_NAME_oneline(Net::SSLeay::X509_get_subject_name($cert)); + $info->{issuer} = Net::SSLeay::X509_NAME_oneline(Net::SSLeay::X509_get_issuer_name($cert)); + $info->{notbefore} = convert_asn1_to_epoch(Net::SSLeay::X509_get_notBefore($cert)); + $info->{notafter} = convert_asn1_to_epoch(Net::SSLeay::X509_get_notAfter($cert)); + $info->{san} = [ Net::SSLeay::X509_get_subjectAltNames($cert) ]; + + Net::SSLeay::X509_free($cert); + + return $info; +}; + +# Checks whether certificate expires before $timestamp (UNIX epoch) +sub check_expiry { + my ($cert_path, $timestamp) = @_; + + $timestamp //= time(); + + my $cert = $read_certificate->($cert_path); + my $not_after = convert_asn1_to_epoch(Net::SSLeay::X509_get_notAfter($cert)); + + Net::SSLeay::X509_free($cert); + + return ($not_after < $timestamp) ? 1 : 0; +}; + +1; diff --git a/test/acme-test.pl b/test/acme-test.pl index d2d308a..729b711 100755 --- a/test/acme-test.pl +++ b/test/acme-test.pl @@ -37,6 +37,8 @@ my $tos_url = (defined($meta) && defined($meta->{termsOfService})) ? $meta->{ter print "ToS: ", $tos_url ? $tos_url : '-', "\n"; print "OK\n\n"; +$tos_url = undef; + if (!$acme->{location}) { print "Registering new account\n"; $acme->new_account($tos_url, contact => ['mailto:f...@bar.com']); @@ -113,10 +115,10 @@ print "OK\n\n"; print "getting certificate\n"; my $cert = $acme->get_certificate($order); -print Dumper($cert), "\n"; +PVE::Tools::file_set_contents('/tmp/cert.pem', $cert); -print "revoking certificate\n"; -print Dumper($acme->revoke_certificate($cert)), "\n"; +#print "revoking certificate\n"; +#print Dumper($acme->revoke_certificate($cert)), "\n"; print "deactivating authorizations\n"; for my $auth_url (@{$order->{authorizations}}) { -- 2.14.2 _______________________________________________ pve-devel mailing list pve-devel@pve.proxmox.com https://pve.proxmox.com/cgi-bin/mailman/listinfo/pve-devel