[LON-CAPA-cvs] cvs: doc /loncapafiles updatequery.piml loncom Lond.pm lond loncom/configuration SSL.pm loncom/lonnet/perl lonnet.pm
raeburn
raeburn at source.lon-capa.org
Sat Aug 18 18:08:05 EDT 2018
raeburn Sat Aug 18 22:08:05 2018 EDT
Modified files:
/loncom Lond.pm lond
/loncom/configuration SSL.pm
/loncom/lonnet/perl lonnet.pm
/doc/loncapafiles updatequery.piml
Log:
- LON-CAPA SSL certificate verification
- Detect revoked or expired certs, or certs with incorrect Common Name.
- If host cert or hostname cert are not OK, check if a valid CSR exists,
and if so, report status of that.
- perl-Crypt-PKCS10 needs to be added to LONCAPA-prerequisites.
-------------- next part --------------
Index: loncom/Lond.pm
diff -u loncom/Lond.pm:1.11 loncom/Lond.pm:1.12
--- loncom/Lond.pm:1.11 Thu Aug 9 14:04:30 2018
+++ loncom/Lond.pm Sat Aug 18 22:07:48 2018
@@ -1,6 +1,6 @@
# The LearningOnline Network
#
-# $Id: Lond.pm,v 1.11 2018/08/09 14:04:30 raeburn Exp $
+# $Id: Lond.pm,v 1.12 2018/08/18 22:07:48 raeburn Exp $
#
# Copyright Michigan State University Board of Trustees
#
@@ -38,7 +38,7 @@
use Apache::lonnet;
use GDBM_File;
use Crypt::OpenSSL::X509;
-
+use Crypt::PKCS10;
sub dump_with_regexp {
my ( $tail, $clientversion ) = @_;
@@ -813,17 +813,20 @@
}
sub server_certs {
- my ($perlvar) = @_;
+ my ($perlvar,$lonhost,$hostname) = @_;
my %pemfiles = (
key => 'lonnetPrivateKey',
host => 'lonnetCertificate',
hostname => 'lonnetHostnameCertificate',
ca => 'lonnetCertificateAuthority',
);
- my (%md5hash,%info);
+ my (%md5hash,%expected_cn,%expired,%revoked,%wrongcn,%info,$crlfile);
if (ref($perlvar) eq 'HASH') {
+ $expected_cn{'host'} = $lonhost;
+ $expected_cn{'hostname'} = 'internal-'.$hostname;
my $certsdir = $perlvar->{'lonCertificateDirectory'};
if (-d $certsdir) {
+ $crlfile = $certsdir.'/'.$perlvar->{'lonnetCertRevocationList'};
foreach my $key (keys(%pemfiles)) {
if ($perlvar->{$pemfiles{$key}}) {
my $file = $certsdir.'/'.$perlvar->{$pemfiles{$key}};
@@ -838,6 +841,7 @@
if (open(PIPE,"openssl rsa -noout -modulus -in $file | openssl md5 |")) {
$md5hash{$key} = <PIPE>;
close(PIPE);
+ chomp($md5hash{$key});
}
} else {
if ($key eq 'ca') {
@@ -856,6 +860,7 @@
if (open(PIPE,"openssl x509 -noout -modulus -in $file | openssl md5 |")) {
$md5hash{$key} = <PIPE>;
close(PIPE);
+ chomp($md5hash{$key});
}
}
my $x509 = Crypt::OpenSSL::X509->new_from_file($file);
@@ -871,6 +876,54 @@
$info{$key}{'alg'} = $x509->sig_alg_name();
$info{$key}{'size'} = $x509->bit_length();
$info{$key}{'email'} = $x509->email();
+ $info{$key}{'serial'} = $x509->serial();
+ if ($x509->checkend(0)) {
+ $expired{$key} = 1;
+ }
+ if (($key eq 'host') || ($key eq 'hostname')) {
+ if ($info{$key}{'cn'} ne $expected_cn{$key}) {
+ $wrongcn{$key} = 1;
+ }
+ if ((-e $crlfile) && ($info{$key}{'serial'} =~ /^\w+$/)) {
+ my $serial = $info{$key}{'serial'};
+ if (open(PIPE,"openssl crl -inform PEM -text -in $crlfile | grep $serial |")) {
+ my $result = <PIPE>;
+ close(PIPE);
+ chomp($result);
+ if ($result ne '') {
+ $revoked{$key} = 1;
+ }
+ }
+ }
+ }
+ }
+ }
+ if (($key eq 'host') || ($key eq 'hostname')) {
+ my $csrfile = $file;
+ $csrfile =~ s/\.pem$/.csr/;
+ if (-e $csrfile) {
+ if (open(PIPE,"openssl req -noout -modulus -in $csrfile |openssl md5 |")) {
+ my $csrhash = <PIPE>;
+ close(PIPE);
+ chomp($csrhash);
+ if ((!-e $file) || ($csrhash ne $md5hash{$key}) || ($expired{$key}) ||
+ ($wrongcn{$key}) || ($revoked{$key})) {
+ Crypt::PKCS10->setAPIversion(1);
+ my $decoded = Crypt::PKCS10->new( $csrfile,(PEMonly => 1, readFile => 1));
+ if (ref($decoded)) {
+ if ($decoded->commonName() eq $expected_cn{$key}) {
+ $info{$key.'-csr'}{'cn'} = $decoded->commonName();
+ $info{$key.'-csr'}{'alg'} = $decoded->pkAlgorithm();
+ $info{$key.'-csr'}{'email'} = $decoded->emailAddress();
+ my $params = $decoded->subjectPublicKeyParams();
+ if (ref($params) eq 'HASH') {
+ $info{$key.'-csr'}{'size'} = $params->{keylen};
+ }
+ $md5hash{$key.'-csr'} = $csrhash;
+ }
+ }
+ }
+ }
}
}
}
@@ -880,13 +933,30 @@
foreach my $key ('host','hostname') {
if ($md5hash{$key}) {
if ($md5hash{$key} eq $md5hash{'key'}) {
- $info{$key}{'status'} = 'ok';
+ if ($revoked{$key}) {
+ $info{$key}{'status'} = 'revoked';
+ } elsif ($expired{$key}) {
+ $info{$key}{'status'} = 'expired';
+ } elsif ($wrongcn{$key}) {
+ $info{$key}{'status'} = 'wrongcn';
+ } else {
+ $info{$key}{'status'} = 'ok';
+ }
} elsif ($info{'key'}{'status'} =~ /ok/) {
$info{$key}{'status'} = 'otherkey';
} else {
$info{$key}{'status'} = 'nokey';
}
}
+ if ($md5hash{$key.'-csr'}) {
+ if ($md5hash{$key.'-csr'} eq $md5hash{'key'}) {
+ $info{$key.'-csr'}{'status'} = 'ok';
+ } elsif ($info{'key'}{'status'} =~ /ok/) {
+ $info{$key.'-csr'}{'status'} = 'otherkey';
+ } else {
+ $info{$key.'-csr'}{'status'} = 'nokey';
+ }
+ }
}
my $result;
foreach my $key (keys(%info)) {
Index: loncom/lond
diff -u loncom/lond:1.547 loncom/lond:1.548
--- loncom/lond:1.547 Thu Aug 9 14:07:40 2018
+++ loncom/lond Sat Aug 18 22:07:48 2018
@@ -2,7 +2,7 @@
# The LearningOnline Network
# lond "LON Daemon" Server (port "LOND" 5663)
#
-# $Id: lond,v 1.547 2018/08/09 14:07:40 raeburn Exp $
+# $Id: lond,v 1.548 2018/08/18 22:07:48 raeburn Exp $
#
# Copyright Michigan State University Board of Trustees
#
@@ -65,7 +65,7 @@
my $status='';
my $lastlog='';
-my $VERSION='$Revision: 1.547 $'; #' stupid emacs
+my $VERSION='$Revision: 1.548 $'; #' stupid emacs
my $remoteVERSION;
my $currenthostid="default";
my $currentdomainid;
@@ -2109,8 +2109,8 @@
sub server_certs_handler {
my ($cmd,$tail,$client) = @_;
my $userinput = "$cmd:$tail";
- my $result;
- my $result = &LONCAPA::Lond::server_certs(\%perlvar);
+ my $hostname = &Apache::lonnet::hostname($perlvar{'lonHostID'});
+ my $result = &LONCAPA::Lond::server_certs(\%perlvar,$perlvar{'lonHostID'},$hostname);
&Reply($client,\$result,$userinput);
return;
}
Index: loncom/configuration/SSL.pm
diff -u loncom/configuration/SSL.pm:1.6 loncom/configuration/SSL.pm:1.7
--- loncom/configuration/SSL.pm:1.6 Fri May 26 03:43:43 2017
+++ loncom/configuration/SSL.pm Sat Aug 18 22:07:53 2018
@@ -1,7 +1,7 @@
# The LearningOnline Network with CAPA
# Checksum installed LON-CAPA modules and some configuration files
#
-# $Id: SSL.pm,v 1.6 2017/05/26 03:43:43 raeburn Exp $
+# $Id: SSL.pm,v 1.7 2018/08/18 22:07:53 raeburn Exp $
#
# The LearningOnline Network with CAPA
#
@@ -31,7 +31,7 @@
package LONCAPA::SSL;
use strict;
use lib '/home/httpd/lib/perl/';
-use Apache::lonlocal();
+use Apache::lonlocal;
use Apache::lonnet();
use Apache::loncommon();
use Apache::lonhtmlcommon();
@@ -48,7 +48,7 @@
'avai' => 'Available',
'yes' => 'Yes',
'no' => 'No',
- 'cn' => 'Common Name',
+ 'cn' => 'Common Name (CN)',
'start' => 'Valid From',
'end' => 'Valid To',
'alg' => 'Signature Algorithm',
@@ -62,15 +62,19 @@
'expired' => 'Expired',
'future' => 'Future validity',
'nokey' => 'No key',
- 'otherkey' => 'No matching key',
+ 'otherkey' => 'No matching key',
+ 'revoked' => 'Revoked by CA',
+ 'wrongcn' => 'Incorrect CN',
);
my @files = qw(key host hostname ca);
my @fields = qw(status cn start end alg size email);
foreach my $server (sort(keys(%{$servers}))) {
- my ($result,$hashref) = &Apache::lonnet::get_servercerts_info($server,$context);
+ my $hostname = $servers->{$server};
+ my ($result,$hashref) = &Apache::lonnet::get_servercerts_info($server,
+ $hostname,
+ $context);
if ($result eq 'ok' && ref($hashref) eq 'HASH') {
if ($target eq 'web') {
- my $hostname = &Apache::lonnet::hostname($server);
$message .= "<fieldset><legend>$hostname ($server)</legend>".
&Apache::loncommon::start_data_table().
&Apache::loncommon::start_data_table_header_row()."\n";
@@ -81,6 +85,7 @@
} else {
$message .= $server.':';
}
+ my %csr;
foreach my $file (@files) {
if ($target eq 'web') {
$message .= &Apache::loncommon::start_data_table_row()."\n".
@@ -146,9 +151,19 @@
} elsif ($target eq 'web') {
$display = &Apache::lonhtmlcommon::confirm_success($display);
}
- } elsif (($display eq 'nokey') || ($display eq 'otherkey')) {
+ } elsif (($display eq 'nokey') || ($display eq 'otherkey') ||
+ ($display eq 'revoked') || ($display eq 'expired') ||
+ ($display eq 'wrongcn')) {
if ($target eq 'web') {
- $display = $lt{$display};
+ $display = $lt{$display};
+ }
+ if (ref($hashref->{$file.'-csr'}) eq 'HASH') {
+ if ($hashref->{$file.'-csr'}->{$item} eq 'ok') {
+ if ($target eq 'web') {
+ $display .= '<br />'.&mt('(New request awaiting signature)');
+ }
+ $csr{$file} = 1;
+ }
}
}
} elsif ($item eq 'start') {
@@ -180,9 +195,23 @@
} else {
$message .= 'no,';
}
+ if ((($file eq 'host') || ($file eq 'hostname')) &&
+ (ref($hashref->{$file.'-csr'}) eq 'HASH')) {
+ if ($hashref->{$file.'-csr'}->{'status'} eq 'ok') {
+ if ($target eq 'web') {
+ my $colspan = scalar(@fields);
+ $message .= '<td colspan="'.$colspan.'">'.
+ &mt('Request for [_1] awaiting signature',
+ $lt{$file}).'</td>';
+ }
+ $csr{$file} = 1;
+ }
+ }
foreach my $item (@fields) {
if ($target eq 'web') {
- $message .= '<td> </td>';
+ unless ($csr{$file}) {
+ $message .= '<td> </td>';
+ }
} else {
$message .= ',';
}
@@ -198,6 +227,18 @@
if ($target eq 'web') {
$message .= &Apache::loncommon::end_data_table().'</fieldset>';
} else {
+ if (keys(%csr)) {
+ foreach my $file (keys(%csr)) {
+ if (ref($hashref->{$file.'-csr'}) eq 'HASH') {
+ $message .= $file.'-csr=yes,';
+ foreach my $item (@fields) {
+ $message .= $hashref->{$file.'-csr'}->{$item}.',';
+ }
+ $message =~ s/,$//;
+ $message .= '&';
+ }
+ }
+ }
$message =~ s/\&$//;
}
$message .= "\n";
Index: loncom/lonnet/perl/lonnet.pm
diff -u loncom/lonnet/perl/lonnet.pm:1.1382 loncom/lonnet/perl/lonnet.pm:1.1383
--- loncom/lonnet/perl/lonnet.pm:1.1382 Tue Aug 14 18:29:33 2018
+++ loncom/lonnet/perl/lonnet.pm Sat Aug 18 22:07:59 2018
@@ -1,7 +1,7 @@
# The LearningOnline Network
# TCP networking package
#
-# $Id: lonnet.pm,v 1.1382 2018/08/14 18:29:33 raeburn Exp $
+# $Id: lonnet.pm,v 1.1383 2018/08/18 22:07:59 raeburn Exp $
#
# Copyright Michigan State University Board of Trustees
#
@@ -230,7 +230,12 @@
}
sub get_servercerts_info {
- my ($lonhost,$context) = @_;
+ my ($lonhost,$hostname,$context) = @_;
+ return if ($lonhost eq '');
+ if ($hostname eq '') {
+ $hostname = &hostname($lonhost);
+ }
+ return if ($hostname eq '');
my ($rep,$uselocal);
if (grep { $_ eq $lonhost } ¤t_machine_ids()) {
$uselocal = 1;
@@ -250,16 +255,11 @@
}
}
if ($uselocal) {
- $rep = LONCAPA::Lond::server_certs(\%perlvar);
+ $rep = LONCAPA::Lond::server_certs(\%perlvar,$lonhost,$hostname);
} else {
$rep=&reply('servercerts',$lonhost);
}
my ($result,%returnhash);
- if (defined($lonhost)) {
- if (!defined(&hostname($lonhost))) {
- return;
- }
- }
if (($rep=~/^(refused|rejected|error)/) || ($rep eq 'con_lost') ||
($rep eq 'unknown_cmd')) {
$result = $rep;
Index: doc/loncapafiles/updatequery.piml
diff -u doc/loncapafiles/updatequery.piml:1.89 doc/loncapafiles/updatequery.piml:1.90
--- doc/loncapafiles/updatequery.piml:1.89 Tue Jun 19 12:26:32 2018
+++ doc/loncapafiles/updatequery.piml Sat Aug 18 22:08:05 2018
@@ -1,6 +1,6 @@
<!-- updatequery.piml -->
-<!-- $Id: updatequery.piml,v 1.89 2018/06/19 12:26:32 raeburn Exp $ -->
+<!-- $Id: updatequery.piml,v 1.90 2018/08/18 22:08:05 raeburn Exp $ -->
<!--
@@ -537,8 +537,8 @@
}
sub get_cert_status {
- my ($lonHostID,$perlvarstatic) = @_;
- my $currcerts = &LONCAPA::SSL::print_certstatus({$lonHostID => 1,},'text','cgi');
+ my ($lonHostID,$hostname,$perlvarstatic) = @_;
+ my $currcerts = &LONCAPA::SSL::print_certstatus({$lonHostID => $hostname,},'text','cgi');
my ($lonkeystatus,$lonhostcertstatus,$lonhostnamecertstatus,%sslstatus);
my $output = '';
if ($currcerts eq "$lonHostID:error") {
@@ -553,13 +553,18 @@
} else {
my %sslnames = &get_sslnames();
my %ssldesc = &get_ssldesc();
+ my %csr;
my ($lonhost,$info) = split(/\:/,$currcerts,2);
if ($lonhost eq $lonHostID) {
my @items = split(/\&/,$info);
foreach my $item (@items) {
my ($key,$value) = split(/=/,$item,2);
+ if ($key =~ /^(host(?:|name))\-csr$/) {
+ $csr{$1} = $value;
+ }
my @data = split(/,/,$value);
if (grep(/^\Q$key\E$/,keys(%sslnames))) {
+ my ($checkcsr,$comparecsr);
if (lc($data[0]) eq 'yes') {
$output .= "$ssldesc{$key} ".$perlvarstatic->{$sslnames{$key}}." available with status = $data[1]\n";
if ($key eq 'key') {
@@ -587,6 +592,9 @@
$lonhostnamecertstatus = "status: created with missing key";
}
}
+ if ($setstatus) {
+ $comparecsr = 1;
+ }
}
unless ($setstatus) {
if ($data[1] eq 'expired') {
@@ -606,18 +614,25 @@
} else {
$sslstatus{$key} = 0;
$output .= "$ssldesc{$key} ".$perlvarstatic->{$sslnames{$key}}." not available\n";
- if (($key eq 'host') || ($key eq 'hostname')) {
- my $csr = $perlvarstatic->{$sslnames{$key}};
- $csr =~s /\.pem$/.csr/;
- my $csrstatus;
- if (-e $perlvarstatic->{'lonCertificateDirectory'}."/$csr") {
- open(PIPE,"openssl req -text -noout -verify -in ".$perlvarstatic->{'lonCertificateDirectory'}."/$csr 2>&1 |");
- while(<PIPE>) {
- chomp();
- $csrstatus = $_;
- last;
- }
- close(PIPE);
+ if ($key eq 'key') {
+ $lonkeystatus = 'still needed';
+ } elsif (($key eq 'host') || ($key eq 'hostname')) {
+ $checkcsr = 1;
+ }
+ }
+ if (($checkcsr) || ($comparecsr)) {
+ my $csrfile = $perlvarstatic->{$sslnames{$key}};
+ $csrfile =~s /\.pem$/.csr/;
+ my $csrstatus;
+ if (-e $perlvarstatic->{'lonCertificateDirectory'}."/$csrfile") {
+ open(PIPE,"openssl req -text -noout -verify -in ".$perlvarstatic->{'lonCertificateDirectory'}."/$csrfile 2>&1 |");
+ while(<PIPE>) {
+ chomp();
+ $csrstatus = $_;
+ last;
+ }
+ close(PIPE);
+ if ((($comparecsr) && ($csr{$key})) || ($checkcsr)) {
$output .= "Certificate signing request for $ssldesc{$key} available with status = $csrstatus\n\n";
if ($key eq 'host') {
$lonhostcertstatus = 'awaiting signature';
@@ -625,16 +640,14 @@
$lonhostnamecertstatus = 'awaiting signature';
}
$sslstatus{$key} = 3;
+ }
+ } elsif ($checkcsr) {
+ $output .= "No certificate signing request available for $ssldesc{$key}\n\n";
+ if ($key eq 'host') {
+ $lonhostcertstatus = 'still needed';
} else {
- $output .= "No certificate signing request available for $ssldesc{$key}\n\n";
- if ($key eq 'host') {
- $lonhostcertstatus = 'still needed';
- } else {
- $lonhostnamecertstatus = 'still needed';
- }
+ $lonhostnamecertstatus = 'still needed';
}
- } elsif ($key eq 'key') {
- $lonkeystatus = 'still needed';
}
}
}
@@ -1601,7 +1614,7 @@
print "\nRetrieving status information for SSL key and certificates ...\n\n";
my ($certinfo,$lonkeystatus,$lonhostcertstatus,$lonhostnamecertstatus,$sslref) =
- &get_cert_status($perlvar{'lonHostID'},$perlvarstatic);
+ &get_cert_status($perlvar{'lonHostID'},$desiredhostname,$perlvarstatic);
print $certinfo;
my %sslstatus;
if (ref($sslref) eq 'HASH') {
@@ -1918,7 +1931,7 @@
&make_key($certsdir,$privkey,$sslkeypass);
print "\nRetrieving status information for SSL key and certificates ...\n\n";
($certinfo,$lonkeystatus,$lonhostcertstatus,$lonhostnamecertstatus,$sslref) =
- &get_cert_status($perlvar{'lonHostID'},$perlvarstatic);
+ &get_cert_status($perlvar{'lonHostID'},$desiredhostname,$perlvarstatic);
if (ref($sslref) eq 'HASH') {
%sslstatus = %{$sslref};
}
@@ -1957,7 +1970,7 @@
&mail_csr('host',$lonCluster,$perlvar{'lonHostID'},$desiredhostname,$certsdir,$connectcsr,$replicatecsr,$perlvarstatic);
print "\nRetrieving status information for SSL key and certificates ...\n\n";
($certinfo,$lonkeystatus,$lonhostcertstatus,$lonhostnamecertstatus,$sslref) =
- &get_cert_status($perlvar{'lonHostID'},$perlvarstatic);
+ &get_cert_status($perlvar{'lonHostID'},$desiredhostname,$perlvarstatic);
if (ref($sslref) eq 'HASH') {
%sslstatus = %{$sslref};
}
@@ -2002,7 +2015,7 @@
&mail_csr('host',$lonCluster,$perlvar{'lonHostID'},$desiredhostname,$certsdir,$connectcsr,$replicatecsr,$perlvarstatic);
print "\nRetrieving status information for SSL key and certificates ...\n\n";
($certinfo,$lonkeystatus,$lonhostcertstatus,$lonhostnamecertstatus,$sslref) =
- &get_cert_status($perlvar{'lonHostID'},$perlvarstatic);
+ &get_cert_status($perlvar{'lonHostID'},$desiredhostname,$perlvarstatic);
if (ref($sslref) eq 'HASH') {
%sslstatus = %{$sslref};
}
@@ -2039,7 +2052,7 @@
&mail_csr('hostname',$lonCluster,$perlvar{'lonHostID'},$desiredhostname,$certsdir,$connectcsr,$replicatecsr,$perlvarstatic);
print "\nRetrieving status information for SSL key and certificates ...\n\n";
($certinfo,$lonkeystatus,$lonhostcertstatus,$lonhostnamecertstatus,$sslref) =
- &get_cert_status($perlvar{'lonHostID'},$perlvarstatic);
+ &get_cert_status($perlvar{'lonHostID'},$desiredhostname,$perlvarstatic);
if (ref($sslref) eq 'HASH') {
%sslstatus = %{$sslref};
}
@@ -2084,7 +2097,7 @@
&mail_csr('hostname',$lonCluster,$perlvar{'lonHostID'},$desiredhostname,$certsdir,$connectcsr,$replicatecsr,$perlvarstatic);
print "\nRetrieving status information for SSL key and certificates ...\n\n";
($certinfo,$lonkeystatus,$lonhostcertstatus,$lonhostnamecertstatus,$sslref) =
- &get_cert_status($perlvar{'lonHostID'},$perlvarstatic);
+ &get_cert_status($perlvar{'lonHostID'},$desiredhostname,$perlvarstatic);
if (ref($sslref) eq 'HASH') {
%sslstatus = %{$sslref};
}
More information about the LON-CAPA-cvs
mailing list