[pve-devel] [RFC common 4/4] add Certificate helper

Fabian Gr├╝nbichler f.gruenbichler at proxmox.com
Wed Apr 11 10:08:46 CEST 2018


general purpose certificate related helper functions

Signed-off-by: Fabian Gr├╝nbichler <f.gruenbichler at 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:foo at 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




More information about the pve-devel mailing list