version 1.4, 2004/05/27 10:03:58
|
version 1.24, 2018/12/14 02:05:38
|
Line 23
|
Line 23
|
# |
# |
# http://www.lon-capa.org/ |
# http://www.lon-capa.org/ |
# |
# |
|
package lonssl; |
# lonssl.pm |
# lonssl.pm |
# This file contains common functions used by lond and lonc when |
# This file contains common functions used by lond and lonc when |
# negotiating the exchange of the session encryption key via an |
# negotiating the exchange of the session encryption key via an |
Line 33
|
Line 33
|
|
|
use strict; |
use strict; |
|
|
# CPAN modules: |
# CPAN/Standard modules: |
|
|
use IO::Socket::INET; |
use IO::Socket::INET; |
use IO::Socket::SSL; |
use IO::Socket::SSL; |
|
use Net::SSLeay; |
|
|
|
use Fcntl; |
|
use POSIX; |
|
|
# Loncapa modules: |
# Loncapa modules: |
|
|
Line 44 use LONCAPA::Configuration;
|
Line 48 use LONCAPA::Configuration;
|
|
|
# Global storage: |
# Global storage: |
|
|
my $perlvar; # When configRead is true this refers to |
my $perlvar; # this refers to the apache perlsetvar |
# the apache perlsetvar variable hash. |
# variable hash. |
|
|
my $pathsep = "/"; # We're on unix after all. |
my $pathsep = "/"; # We're on unix after all. |
|
|
|
my $DEBUG = 0; # Set to non zero to enable debug output. |
|
|
|
|
# Initialization code: |
# Initialization code: |
|
|
$perlvar = LONCAPA::Configuration::read_conf('loncapa.conf'); |
$perlvar = LONCAPA::Configuration::read_conf('loncapa.conf'); |
|
|
|
|
|
my $lasterror=""; |
|
|
|
|
|
|
|
sub LastError { |
|
return $lasterror; |
|
} |
|
|
|
sub Debug { |
|
my $msg = shift; |
|
if ($DEBUG) { |
|
print STDERR $msg; |
|
} |
|
} |
|
|
|
#------------------------------------------------------------------------- |
|
# Name SetFdBlocking - |
|
# Turn blocking mode on on the file handle. This is required for |
|
# SSL key negotiation. |
|
# |
|
# Parameters: |
|
# Handle - Reference to the handle to modify. |
|
# Returns: |
|
# prior flag settings. |
|
# |
|
sub SetFdBlocking { |
|
Debug("SetFdBlocking called \n"); |
|
my $Handle = shift; |
|
|
|
|
|
|
|
my $flags = fcntl($Handle, F_GETFL, 0); |
|
if(!$flags) { |
|
Debug("SetBLocking fcntl get faild $!\n"); |
|
} |
|
my $newflags = $flags & (~ O_NONBLOCK); # Turn off O_NONBLOCK... |
|
if(!fcntl($Handle, F_SETFL, $newflags)) { |
|
Debug("Can't set non block mode $!\n"); |
|
} |
|
return $flags; |
|
} |
|
|
#-------------------------------------------------------------------------- |
#-------------------------------------------------------------------------- |
# |
# |
Line 65 $perlvar = LONCAPA::Configuration::read_
|
Line 112 $perlvar = LONCAPA::Configuration::read_
|
# Socket IO::Socket::INET Original ordinary socket. |
# Socket IO::Socket::INET Original ordinary socket. |
# CACert string Full path name to the certificate |
# CACert string Full path name to the certificate |
# authority certificate file. |
# authority certificate file. |
# MyCert string Full path name to the certificate |
# MyCert string Full path name to the certificate |
# issued to this host. |
# issued to this host. |
# KeyFile string Full pathname to the host's private |
# KeyFile string Full pathname to the host's private |
# key file for the certificate. |
# key file for the certificate. |
|
# peer string lonid of remote LON-CAPA server |
|
# peerdef string default lonHostID of remote server |
|
# CRLFile Full path name to the certificate |
|
# revocation list file for the cluster |
|
# to which server belongs (optional) |
|
# serverversion LON-CAPA version running on remote |
|
# server. |
|
|
# Returns |
# Returns |
# - Reference to an SSL socket on success |
# - Reference to an SSL socket on success |
# - undef on failure. Reason for failure can be interrogated from |
# - undef on failure. Reason for failure can be interrogated from |
# IO::Socket::SSL |
# IO::Socket::SSL |
|
# Side effects: socket is left in blocking mode!! |
|
# |
|
|
sub PromoteClientSocket { |
sub PromoteClientSocket { |
my $PlaintextSocket = shift; |
my ($PlaintextSocket, |
my $CACert = shift; |
$CACert, |
my $MyCert = shift; |
$MyCert, |
my $KeyFile = shift; |
$KeyFile, |
|
$peer, |
|
$peerdef, |
|
$CRLFile, |
|
$serverversion) = @_; |
|
|
|
Debug("Client promotion using key: $KeyFile, Cert: $MyCert, CA: $CACert, CRL: $CRLFile, Remote Host: $peer, RemoteDefHost: $peerdef, RemoteLCVersion: $serverversion\n"); |
|
|
# To create the ssl socket we need to duplicate the existing |
# To create the ssl socket we need to duplicate the existing |
# socket. Otherwise closing the ssl socket will close the plaintext socket |
# socket. Otherwise closing the ssl socket will close the plaintext socket |
# too: |
# too. We also must flip into blocking mode for the duration of the |
|
# ssl negotiation phase.. the caller will have to flip to non block if |
open (DUPLICATE, "+>$PlaintextSocket"); |
# that's what they want |
|
|
my $client = IO::Socket::SSL->new_from_fd(fileno(DUPLICATE), |
my $oldflags = SetFdBlocking($PlaintextSocket); |
SSL_user_cert => 1, |
my $dupfno = fcntl($PlaintextSocket, F_DUPFD, 0); |
SSL_key_file => $KeyFile, |
Debug("Client promotion got dup = $dupfno\n"); |
SSL_cert_file => $MyCert, |
|
SSL_ca_fie => $$CACert); |
# Starting with IO::Socket::SSL rev. 1.79, carp warns that a verify |
|
# mode of SSL_VERIFY_NONE should be explicitly set for client, if |
|
# verification is not to be used, and SSL_verify_mode is not set. |
|
# Starting with rev. 1.95, the default became SSL_VERIFY_PEER which |
|
# prevents an SSL connection to lond unless SSL_verifycn_name is set |
|
# to the lonHostID of the remote host, (and the remote certificate has |
|
# the remote lonHostID as CN, and has been signed by the LON-CAPA CA. |
|
# Set SSL_verify_mode to Net::SSLeay::VERIFY_PEER() instead of to |
|
# SSL_VERIFY_PEER for compatibility with IO::Socket::SSL rev. 1.01 |
|
# used by CentOS/RHEL/Scientific Linux 5). |
|
|
|
my $verify_cn = $peerdef; |
|
if ($verify_cn eq '') { |
|
$verify_cn = $peer; |
|
} |
|
|
|
my %sslargs = (SSL_use_cert => 1, |
|
SSL_key_file => $KeyFile, |
|
SSL_cert_file => $MyCert, |
|
SSL_ca_file => $CACert); |
|
my ($major,$minor) = split(/\./,$serverversion); |
|
if (($major < 2) || ($major == 2 && $minor < 12)) { |
|
$sslargs{SSL_verify_mode} = Net::SSLeay::VERIFY_NONE(); |
|
} else { |
|
$sslargs{SSL_verifycn_scheme} = 'http', |
|
$sslargs{SSL_verifycn_name} = $verify_cn, |
|
$sslargs{SSL_verify_mode} = Net::SSLeay::VERIFY_PEER(); |
|
if (($CRLFile ne '') && (-e $CRLFile)) { |
|
$sslargs{SSL_check_crl} = 1; |
|
$sslargs{SSL_crl_file} = $CRLFile; |
|
} |
|
} |
|
# Uncomment next two $IO::Socket::SSL::DEBUG lines, for debugging |
|
# $IO::Socket::SSL::DEBUG = 0; # Set to integer >0 and <4 |
|
# # to write debugging to lonc_errors |
|
my $client = IO::Socket::SSL->new_from_fd($dupfno,%sslargs); |
|
# $IO::Socket::SSL::DEBUG = 0; # Do not change |
|
if(!$client) { |
|
if ($IO::Socket::SSL::SSL_ERROR == -1) { |
|
$lasterror = -1; |
|
} |
|
return undef; |
|
} |
return $client; # Undef if the client negotiation fails. |
return $client; # Undef if the client negotiation fails. |
} |
} |
|
|
#---------------------------------------------------------------------- |
#---------------------------------------------------------------------- |
# Name PromoteServerSocket |
# Name PromoteServerSocket |
# Description Given an ordinary IO::Socket::INET Creates an SSL socket |
# Description Given an ordinary IO::Socket::INET Creates an SSL socket |
# for a server that is connected to the same client.l |
# for a server that is connected to the same client. |
# Parameters Name Type Description |
# Parameters Name Type Description |
# Socket IO::Socket::INET Original ordinary socket. |
# Socket IO::Socket::INET Original ordinary socket. |
# CACert string Full path name to the certificate |
# CACert string Full path name to the certificate |
Line 107 sub PromoteClientSocket {
|
Line 211 sub PromoteClientSocket {
|
# issued to this host. |
# issued to this host. |
# KeyFile string Full pathname to the host's private |
# KeyFile string Full pathname to the host's private |
# key file for the certificate. |
# key file for the certificate. |
|
# peer string lonHostID of remote LON-CAPA client |
|
# CRLFile Full path name to the certificate |
|
# revocation list file for the cluster |
|
# to which server belongs (optional) |
|
# clientversion LON-CAPA version running on remote |
|
# client |
# Returns |
# Returns |
# - Reference to an SSL socket on success |
# - Reference to an SSL socket on success |
# - undef on failure. Reason for failure can be interrogated from |
# - undef on failure. Reason for failure can be interrogated from |
# IO::Socket::SSL |
# IO::Socket::SSL |
|
# Side Effects: |
|
# Socket is left in blocking mode!!! |
|
# |
sub PromoteServerSocket { |
sub PromoteServerSocket { |
my $PlaintextSocket = shift; |
my ($PlaintextSocket, |
my $CACert = shift; |
$CACert, |
my $MyCert = shift; |
$MyCert, |
my $KeyFile = shift; |
$KeyFile, |
|
$peer, |
|
$CRLFile, |
|
$clientversion) = @_; |
|
|
# To create the ssl socket we need to duplicate the existing |
# To create the ssl socket we need to duplicate the existing |
# socket. Otherwise closing the ssl socket will close the plaintext socket |
# socket. Otherwise closing the ssl socket will close the plaintext socket |
# too: |
# too: |
|
|
open (DUPLICATE, "+>$PlaintextSocket"); |
Debug("Server promotion: Key = $KeyFile, Cert $MyCert CA $CACert\n"); |
|
|
my $client = IO::Socket::SSL->new_from_fd(fileno(DUPLICATE), |
my $oldflags = SetFdBlocking($PlaintextSocket); |
SSL_server => 1, # Server role. |
my $dupfno = fcntl($PlaintextSocket, F_DUPFD, 0); |
SSL_user_cert => 1, |
if (!$dupfno) { |
SSL_key_file => $KeyFile, |
Debug("dup failed: $!\n"); |
SSL_cert_file => $MyCert, |
} |
SSL_ca_fie => $$CACert); |
Debug(" Fileno = $dupfno\n"); |
|
my %sslargs = (SSL_server => 1, # Server role. |
|
SSL_use_cert => 1, |
|
SSL_key_file => $KeyFile, |
|
SSL_cert_file => $MyCert, |
|
SSL_ca_file => $CACert); |
|
my ($major,$minor) = split(/\./,$clientversion); |
|
if (($major < 2) || ($major == 2 && $minor < 12)) { |
|
$sslargs{SSL_verify_mode} = Net::SSLeay::VERIFY_NONE(); |
|
} else { |
|
$sslargs{SSL_verifycn_scheme} = 'http'; |
|
$sslargs{SSL_verifycn_name} = $peer; |
|
$sslargs{SSL_verify_mode} = Net::SSLeay::VERIFY_PEER(); |
|
if (($CRLFile ne '') && (-e $CRLFile)) { |
|
$sslargs{SSL_check_crl} = 1; |
|
$sslargs{SSL_crl_file} = $CRLFile; |
|
} |
|
} |
|
# Uncomment next two $IO::Socket::SSL::DEBUG lines, for debugging |
|
# $IO::Socket::SSL::DEBUG = 0; # Set to integer >0 and <4 |
|
# # to write debugging to lond_errors |
|
my $client = IO::Socket::SSL->new_from_fd($dupfno,%sslargs); |
|
# $IO::Socket::SSL::DEBUG = 0; # Do not change |
|
if(!$client) { |
|
if ($IO::Socket::SSL::SSL_ERROR == -1) { |
|
$lasterror = -1; |
|
} |
|
return undef; |
|
} |
return $client; |
return $client; |
} |
} |
|
|
Line 163 sub Close {
|
Line 306 sub Close {
|
# the certificate authority. The second element of the list is the name |
# the certificate authority. The second element of the list is the name |
# of the owner of the certificate. |
# of the owner of the certificate. |
sub GetPeerCertificate { |
sub GetPeerCertificate { |
my $SSLSocket = shift; |
my $SSLSocket = shift; |
|
|
my $CertOwner = $SSLSocket->peer_certificate("owner"); |
my $CertOwner = $SSLSocket->peer_certificate("owner"); |
my $CertCA = $SSLSocket->peer_certificate("authority"); |
my $CertCA = $SSLSocket->peer_certificate("authority"); |
|
|
return \($CertCA, $CertOwner); |
return ($CertCA, $CertOwner); |
} |
} |
#---------------------------------------------------------------------------- |
#---------------------------------------------------------------------------- |
# |
# |
Line 182 sub GetPeerCertificate {
|
Line 325 sub GetPeerCertificate {
|
# |
# |
sub CertificateFile { |
sub CertificateFile { |
|
|
# I need some perl variables from the configuration file for this: |
# I need some perl variables from the configuration file for this: |
|
|
my $CertificateDir = $perlvar->{lonCertificateDirectory}; |
my $CertificateDir = $perlvar->{lonCertificateDirectory}; |
my $CaFilename = $perlvar->{lonnetCertificateAuthority}; |
my $CaFilename = $perlvar->{lonnetCertificateAuthority}; |
my $CertFilename = $perlvar->{lonnetCertificate}; |
my $CertFilename = $perlvar->{lonnetCertificate}; |
|
|
# Ensure the existence of these variables: |
# Ensure the existence of these variables: |
|
|
if((!$CertificateDir) || (!$CaFilename) || (!$CertFilename)) { |
if((!$CertificateDir) || (!$CaFilename) || (!$CertFilename)) { |
return undef; |
$lasterror = "Missing info: dir: $CertificateDir CA: $CaFilename " |
} |
."Cert: $CertFilename"; |
|
return undef; |
# Build the actual filenames and check for their existence and |
} |
# readability. |
|
|
# Build the actual filenames and check for their existence and |
my $CaFilename = $CertificateDir.$pathsep.$CaFilename; |
# readability. |
my $CertFilename = $CertificateDir.$pathsep.$CertFilename; |
|
|
$CaFilename = $CertificateDir.$pathsep.$CaFilename; |
if((! -r $CaFilename) || (! -r $CertFilename)) { |
$CertFilename = $CertificateDir.$pathsep.$CertFilename; |
return undef; |
|
} |
if((! -r $CaFilename) || (! -r $CertFilename)) { |
|
$lasterror = "CA file $CaFilename or Cert File: $CertFilename " |
# Everything works fine!! |
."not readable"; |
|
return undef; |
return \($CaFilename, $CertFilename); |
} |
|
|
|
# Everything works fine!! |
|
|
|
return ($CaFilename, $CertFilename); |
|
|
} |
} |
#------------------------------------------------------------------------ |
#------------------------------------------------------------------------ |
Line 220 sub CertificateFile {
|
Line 367 sub CertificateFile {
|
# |
# |
sub KeyFile { |
sub KeyFile { |
|
|
# I need some perl variables from the configuration file for this: |
# I need some perl variables from the configuration file for this: |
|
|
|
my $CertificateDir = $perlvar->{lonCertificateDirectory}; |
|
my $KeyFilename = $perlvar->{lonnetPrivateKey}; |
|
|
|
# Ensure the variables exist: |
|
|
|
if((!$CertificateDir) || (!$KeyFilename)) { |
|
$lasterror = "Missing parameter dir: $CertificateDir " |
|
."key: $KeyFilename"; |
|
return undef; |
|
} |
|
|
|
# Build the actual filename and ensure that it not only exists but |
|
# is also readable: |
|
|
|
$KeyFilename = $CertificateDir.$pathsep.$KeyFilename; |
|
if(! (-r $KeyFilename)) { |
|
$lasterror = "Unreadable key file $KeyFilename"; |
|
return undef; |
|
} |
|
|
|
return $KeyFilename; |
|
} |
|
|
|
sub CRLFile { |
|
|
|
# I need some perl variables from the configuration file for this: |
|
|
my $CertificateDir = $perlvar->{lonCertificateDirectory}; |
my $CertificateDir = $perlvar->{lonCertificateDirectory}; |
my $KeyFilename = $perlvar->{lonnetPrivateKey}; |
my $CRLFilename = $perlvar->{lonnetCertRevocationList}; |
|
|
# Ensure the variables exist: |
# Ensure the variables exist: |
|
|
if((!$CertificateDir) || (!$KeyFilename)) { |
if((!$CertificateDir) || (!$CRLFilename)) { |
return undef; |
$lasterror = "Missing parameter dir: $CertificateDir " |
} |
."CRL file: $CRLFilename"; |
|
return undef; |
|
} |
|
|
# Build the actual filename and ensure that it not only exists but |
# Build the actual filename and ensure that it not only exists but |
# is also readable: |
# is also readable: |
|
|
my $KeyFilename = $CertificateDir.$pathsep.$KeyFilename; |
$CRLFilename = $CertificateDir.$pathsep.$CRLFilename; |
if(! (-r $KeyFilename)) { |
if(! (-r $CRLFilename)) { |
return undef; |
$lasterror = "Unreadable key file $CRLFilename"; |
} |
return undef; |
|
} |
|
|
|
return $CRLFilename; |
|
} |
|
|
|
sub BadCertDir { |
|
my $SocketDir = $perlvar->{lonSockDir}; |
|
if (-d "$SocketDir/nosslverify/") { |
|
return "$SocketDir/nosslverify" |
|
} |
|
} |
|
|
|
sub has_badcert_file { |
|
my ($client) = @_; |
|
my $SocketDir = $perlvar->{lonSockDir}; |
|
if (-e "$SocketDir/nosslverify/$client") { |
|
return 1; |
|
} |
|
return; |
|
} |
|
|
|
sub Read_Connect_Config { |
|
my ($secureconf,$perlvarref,$crlcheckedref) = @_; |
|
return unless (ref($secureconf) eq 'HASH'); |
|
|
|
unless (ref($perlvarref) eq 'HASH') { |
|
$perlvarref = $perlvar; |
|
} |
|
|
|
# Clear hash of clients in lond for which Certificate Revocation List checked |
|
if (ref($crlcheckedref) eq 'HASH') { |
|
foreach my $key (keys(%{$crlcheckedref})) { |
|
delete($crlcheckedref->{$key}); |
|
} |
|
} |
|
# Clean out the old table first. |
|
foreach my $key (keys(%{$secureconf})) { |
|
delete($secureconf->{$key}); |
|
} |
|
|
|
my $result; |
|
my $tablename = $perlvarref->{'lonTabDir'}."/connectionrules.tab"; |
|
if (open(my $fh,'<',$tablename)) { |
|
while (my $line = <$fh>) { |
|
chomp($line); |
|
my ($name,$value) = split(/=/,$line); |
|
if ($value =~ /^(?:no|yes|req)$/) { |
|
if ($name =~ /^conn(to|from)_(dom|intdom|other)$/) { |
|
$secureconf->{'conn'.$1}{$2} = $value; |
|
} |
|
} |
|
} |
|
close($fh); |
|
return 'ok'; |
|
} |
|
return; |
|
} |
|
|
return $KeyFilename; |
sub Read_Host_Types { |
|
my ($hosttypes,$perlvarref) = @_; |
|
return unless (ref($hosttypes) eq 'HASH'); |
|
|
|
unless (ref($perlvarref) eq 'HASH') { |
|
$perlvarref = $perlvar; |
|
} |
|
|
|
# Clean out the old table first. |
|
foreach my $key (keys(%{$hosttypes})) { |
|
delete($hosttypes->{$key}); |
|
} |
|
|
|
my $result; |
|
my $tablename = $perlvarref->{'lonTabDir'}."/hosttypes.tab"; |
|
if (open(my $fh,'<',$tablename)) { |
|
while (my $line = <$fh>) { |
|
chomp($line); |
|
my ($name,$value) = split(/:/,$line); |
|
if (($name ne '') && ($value =~ /^(dom|intdom|other)$/)) { |
|
$hosttypes->{$name} = $value; |
|
} |
|
} |
|
close($fh); |
|
return 'ok'; |
|
} |
|
return; |
} |
} |
|
|
1; |
1; |