SSLCertificateKeyFile /etc/httpd/server-ecdsa.key
Header always set X-Content-Type-Options nosniff
- Header always set Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'"
+ Header always set Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data:"
Header always set Referrer-Policy strict-origin
Header always set X-Frame-Options sameorigin
RewriteRule .* - [F]
Header always set X-Content-Type-Options nosniff
- Header always set Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'"
+ Header always set Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src: 'self' data:"
Header always set Referrer-Policy strict-origin
Header always set X-Frame-Options sameorigin
--- /dev/null
+#!/usr/bin/perl
+############################################################################
+# #
+# This file is part of the IPFire Firewall. #
+# #
+# IPFire is free software; you can redistribute it and/or modify #
+# it under the terms of the GNU General Public License as published by #
+# the Free Software Foundation; either version 2 of the License, or #
+# (at your option) any later version. #
+# #
+# IPFire is distributed in the hope that it will be useful, #
+# but WITHOUT ANY WARRANTY; without even the implied warranty of #
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
+# GNU General Public License for more details. #
+# #
+# You should have received a copy of the GNU General Public License #
+# along with IPFire; if not, write to the Free Software #
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA #
+# #
+# Copyright (C) 2022 IPFire Team <info@ipfire.org>. #
+# #
+############################################################################
+
+use strict;
+use warnings;
+
+use MIME::Base64;
+
+require '/var/ipfire/general-functions.pl';
+
+my $cn;
+my $prefix;
+my $password;
+my $otp;
+my @valid_otps;
+
+#&General::log("otp-verify DEBUG: ENV:common_name: $ENV{'common_name'}");
+
+# line 1: <COMMON NAME>
+# line 2: <CREDENTIALS> e.g.: SCRV1:cGFzc3dvcmQ=:ODg2MTM2
+while(<>) {
+ #&General::log("otp-verify DEBUG: line: $_");
+ if ($_ =~ /^(?!SCRV[[:digit:]]).+/) {
+ chomp;
+ $cn = $_;
+ #$cn =~ s/\s*$//g;
+ }
+ if ($_ =~ /^SCRV[[:digit:]]:.+/) {
+ ($prefix, $password, $otp) = split /:/;
+ $password = decode_base64($password);
+ $otp = decode_base64($otp);
+ }
+}
+
+if ($cn == "") {
+ #&General::log("otp-verify DEBUG: no credentials provided by client, setting CN from ENV.");
+ $cn = $ENV{'common_name'};
+}
+
+#&General::log("otp-verify DEBUG: CN: \"$cn\"\n");
+#&General::log("otp-verify DEBUG: PW: \"$password\"\n");
+#&General::log("otp-verify DEBUG: OTP: \"$otp\"\n");
+#&General::log("otp-verify DEBUG: ----\n");
+
+my %confighash = ();
+if (-f "${General::swroot}/ovpn/ovpnconfig") {
+ &General::readhasharray("${General::swroot}/ovpn/ovpnconfig", \%confighash);
+ foreach my $key (keys %confighash){
+ if ($cn eq $confighash{$key}[2]) {
+ # Exit successfully for non-roadwarrior connections.
+ exit 0 unless ($confighash{$key}[3] eq "host");
+
+ # Exit successfully for disabled otp connections.
+ exit 0 unless (defined $confighash{$key}[43] and $confighash{$key}[43] eq "on");
+
+ # Exit with failure if required otp config is missing.
+ exit 1 if (not defined $confighash{$key}[42]);
+ exit 1 if (not defined $confighash{$key}[44]);
+
+ #&General::log("otp-verify DEBUG: connection key: $key\n");
+ #&General::log("otp-verify DEBUG: connection type: $confighash{$key}[3]\n");
+ #&General::log("otp-verify DEBUG: CN: $confighash{$key}[2]\n");
+ #&General::log("otp-verify DEBUG: otp Type: $confighash{$key}[42]\n");
+ #&General::log("otp-verify DEBUG: otp State: $confighash{$key}[43]\n");
+ #&General::log("otp-verify DEBUG: otp Secret: $confighash{$key}[44]\n");
+
+ # Get valid OTPs.
+ my @valid_otps = &General::system_output("/usr/bin/oathtool", "--totp", "-w", "3", "$confighash{$key}[44]");
+ foreach (@valid_otps) {
+ # Exit successfully if OTP is correct.
+ exit 0 if ($otp == $_)
+ }
+
+ # Exit with failure if no matching OTP was found.
+ exit 1;
+ }
+ }
+} else {
+ # Return an error if ovpnconfig could not be found.
+ exit 1;
+}
+
+# Exit successfully if no auth-user-pass data received.
+exit 0;
+
+# vim: ts=3 sts=3 sw=3 et nu list
require "${General::swroot}/location-functions.pl";
# enable only the following on debugging purpose
-#use warnings;
-#use CGI::Carp 'fatalsToBrowser';
+use warnings;
+use CGI::Carp 'fatalsToBrowser';
#workaround to suppress a warning when a variable is used only once
my @dummy = ( ${Header::colourgreen}, ${Header::colourblue} );
undef (@dummy);
}
print CONF "tls-verify /usr/lib/openvpn/verify\n";
print CONF "crl-verify /var/ipfire/ovpn/crls/cacrl.pem\n";
+ print CONF "auth-user-pass-verify \"/usr/lib/openvpn/otp-verify\" via-file\n";
+ print CONF "auth-user-pass-optional\n";
+ print CONF "reneg-sec 86400\n";
print CONF "user nobody\n";
print CONF "group nobody\n";
print CONF "persist-key\n";
if ($vpnsettings{FRAGMENT} ne '' && $vpnsettings{DPROTOCOL} ne 'tcp' ) {
print CLIENTCONF "fragment $vpnsettings{'FRAGMENT'}\r\n";
}
+ if ($confighash{$cgiparams{'KEY'}}[43] eq 'on') {
+ print CLIENTCONF "auth-nocache\r\n";
+ print CLIENTCONF "auth-user-pass credentials\r\n";
+ print CLIENTCONF "static-challenge \"One Time Password (OTP): \" 1\r\n";
+
+ open(CLIENTCREDS, ">$tempdir/credentials") or die "Unable to open tempfile: $!";
+ print CLIENTCREDS "user\r\n";
+ print CLIENTCREDS "password";
+ close(CLIENTCREDS);
+ $zip->addFile( "$tempdir/credentials", "credentials") or die "Can't add file credentials\n";
+ }
if ($include_certs) {
print CLIENTCONF "\r\n";
exit(0);
}
+###
+### Display OTP QRCode
+###
+} elsif ($cgiparams{'ACTION'} eq $Lang::tr{'show otp qrcode'}) {
+ &General::readhasharray("${General::swroot}/ovpn/ovpnconfig", \%confighash);
+
+ use MIME::Base32;
+ use MIME::Base64;
+ use Imager::QRCode;
+ my $qrcode = Imager::QRCode->new(
+ size => 6,
+ margin => 0,
+ version => 0,
+ level => 'M',
+ mode => '8-bit',
+ casesensitive => 1,
+ lightcolor => Imager::Color->new(255, 255, 255),
+ darkcolor => Imager::Color->new(0, 0, 0),
+ );
+ my $cn = $confighash{$cgiparams{'KEY'}}[2];
+ my $secret = encode_base32($confighash{$cgiparams{'KEY'}}[44]);
+ my $issuer = "$mainsettings{'HOSTNAME'}.$mainsettings{'DOMAINNAME'}";
+ my $qrcodeimg = $qrcode->plot("otpauth://totp/$cn?secret=$secret&issuer=$issuer");
+ my $qrcodeimgdata;
+ $qrcodeimg->write(data => \$qrcodeimgdata, type=> 'png')
+ or die $qrcodeimg->errstr;
+ $qrcodeimgdata = encode_base64($qrcodeimgdata, '');
+
+ &Header::showhttpheaders();
+ &Header::openpage($Lang::tr{'ovpn'}, 1, '');
+ &Header::openbigbox('100%', 'LEFT', '', '');
+ &Header::openbox('100%', 'LEFT', "$Lang::tr{'otp qrcode'}:");
+ print <<END;
+$Lang::tr{'secret'}: $secret</br></br>
+<img alt="$Lang::tr{'otp qrcode'}" src="data:image/png;base64,$qrcodeimgdata">
+END
+ &Header::closebox();
+ print "<div align='center'><a href='/cgi-bin/ovpnmain.cgi'>$Lang::tr{'back'}</a></div>";
+ &Header::closebigbox();
+ &Header::closepage();
+ exit(0);
+
###
### Display Diffie-Hellman key
###
$cgiparams{'DAUTH'} = $confighash{$cgiparams{'KEY'}}[39];
$cgiparams{'DCIPHER'} = $confighash{$cgiparams{'KEY'}}[40];
$cgiparams{'TLSAUTH'} = $confighash{$cgiparams{'KEY'}}[41];
+ $cgiparams{'OTP_STATE'} = $confighash{$cgiparams{'KEY'}}[43];
} elsif ($cgiparams{'ACTION'} eq $Lang::tr{'save'}) {
$cgiparams{'REMARK'} = &Header::cleanhtml($cgiparams{'REMARK'});
$confighash{$key}[41] = "no-pass";
}
+ $confighash{$key}[42] = 'HOTP/T30/6';
+ $confighash{$key}[43] = $cgiparams{'OTP_STATE'};
+ if (($confighash{$key}[43] == 'on') && ($confighash{$key}[44] == '')) {
+ my @otp_secret = &General::system_output("/usr/bin/openssl", "rand", "-hex", "20");
+ $confighash{$key}[44] = $otp_secret[0];
+ } elsif ($confighash{$key}[43] == '') {
+ $confighash{$key}[44] = '';
+ }
+
&General::writehasharray("${General::swroot}/ovpn/ovpnconfig", \%confighash);
if ($cgiparams{'CHECK1'} ){
print"</td></tr></table><br><br>";
my $name=$cgiparams{'CHECK1'};
$checked{'RG'}{$cgiparams{'RG'}} = 'CHECKED';
+ $checked{'OTP_STATE'}{$cgiparams{'OTP_STATE'}} = 'CHECKED';
if (! -z "${General::swroot}/ovpn/ccd.conf"){
print"<table border='0' width='100%' cellspacing='1' cellpadding='0'><tr><td width='1%'></td><td width='30%' class='boldbase' align='center'><b>$Lang::tr{'ccd name'}</td><td width='15%' class='boldbase' align='center'><b>$Lang::tr{'network'}</td><td class='boldbase' align='center' width='18%'><b>$Lang::tr{'ccd clientip'}</td></tr>";
print <<END;
<table border='0' width='100%'>
+ <tr><td width='20%'>$Lang::tr{'enable otp'}:</td><td colspan='3'><input type='checkbox' name='OTP_STATE' $checked{'OTP_STATE'}{'on'} /></td></tr>
<tr><td width='20%'>Redirect Gateway:</td><td colspan='3'><input type='checkbox' name='RG' $checked{'RG'}{'on'} /></td></tr>
<tr><td colspan='4'><b><br>$Lang::tr{'ccd routes'}</b></td></tr>
<tr><td colspan='4'> </td></tr>
<th width='15%' class='boldbase' align='center'><b>$Lang::tr{'type'}</b></th>
<th width='20%' class='boldbase' align='center'><b>$Lang::tr{'remark'}</b></th>
<th width='10%' class='boldbase' align='center'><b>$Lang::tr{'status'}</b></th>
- <th width='5%' class='boldbase' colspan='7' align='center'><b>$Lang::tr{'action'}</b></th>
+ <th width='5%' class='boldbase' colspan='8' align='center'><b>$Lang::tr{'action'}</b></th>
</tr>
END
}
<th width='15%' class='boldbase' align='center'><b>$Lang::tr{'type'}</b></th>
<th width='20%' class='boldbase' align='center'><b>$Lang::tr{'remark'}</b></th>
<th width='10%' class='boldbase' align='center'><b>$Lang::tr{'status'}</b></th>
- <th width='5%' class='boldbase' colspan='7' align='center'><b>$Lang::tr{'action'}</b></th>
+ <th width='5%' class='boldbase' colspan='8' align='center'><b>$Lang::tr{'action'}</b></th>
</tr>
END
}
; } else {
print "<td> </td>";
}
+
+ if ($confighash{$key}[43] eq 'on') {
+ print <<END;
+<form method='post' name='frm${key}o'><td align='center' $col>
+<input type='image' name='$Lang::tr{'show otp qrcode'}' src='/images/qr-code.png' alt='$Lang::tr{'show otp qrcode'}' title='$Lang::tr{'show otp qrcode'}' border='0' />
+<input type='hidden' name='ACTION' value='$Lang::tr{'show otp qrcode'}' />
+<input type='hidden' name='KEY' value='$key' />
+</td></form>
+END
+; } else {
+ print "<td $col> </td>";
+ }
+
if ($confighash{$key}[4] eq 'cert' && -f "${General::swroot}/ovpn/certs/$confighash{$key}[1].p12") {
print <<END;
<form method='post' name='frm${key}c'><td align='center' $col>
<td class='base'>$Lang::tr{'download certificate'}</td>
<td> <img src='/images/openvpn.png' alt='?RELOAD'/></td>
<td class='base'>$Lang::tr{'dl client arch'}</td>
+ <td> <img src='/images/qr-code.png' alt='$Lang::tr{'show otp qrcode'}'/></td>
+ <td class='base'>$Lang::tr{'show otp qrcode'}</td>
</tr>
</table><br>
END
--- /dev/null
+<?xml version="1.0" encoding="iso-8859-1"?>\r
+<!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->\r
+<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"\r
+ viewBox="0 0 512 512" style="enable-background:new 0 0 512 512;" xml:space="preserve">\r
+<path d="M0,0v233.739h233.739V0H0z M200.348,200.348H33.391V33.391h166.957V200.348z"/>\r
+<rect x="66.783" y="66.783" width="100.174" height="100.174"/>\r
+<path d="M278.261,0v233.739H512V0H278.261z M478.609,200.348H311.652V33.391h166.957V200.348z"/>\r
+<rect x="345.043" y="66.783" width="100.174" height="100.174"/>\r
+<path d="M0,278.261V512h233.739V278.261H0z M200.348,478.609H33.391V311.652h166.957V478.609z"/>\r
+<rect x="66.783" y="345.043" width="100.174" height="100.174"/>\r
+<polygon points="278.261,278.261 278.261,512 345.043,512 345.043,478.609 311.652,478.609 311.652,411.826 345.043,411.826 \r
+ 345.043,378.435 311.652,378.435 311.652,311.652 345.043,311.652 345.043,278.261 "/>\r
+<rect x="478.609" y="278.261" width="33.391" height="33.391"/>\r
+<polygon points="478.609,478.609 445.217,478.609 445.217,512 512,512 512,356.174 478.609,356.174 "/>\r
+<rect x="378.435" y="278.261" width="66.783" height="33.391"/>\r
+<polygon points="445.217,411.826 411.826,411.826 411.826,378.435 445.217,378.435 445.217,345.043 378.435,345.043 \r
+ 378.435,445.217 445.217,445.217 "/>\r
+<rect x="378.435" y="478.609" width="33.391" height="33.391"/>\r
+<g>\r
+</g>\r
+<g>\r
+</g>\r
+<g>\r
+</g>\r
+<g>\r
+</g>\r
+<g>\r
+</g>\r
+<g>\r
+</g>\r
+<g>\r
+</g>\r
+<g>\r
+</g>\r
+<g>\r
+</g>\r
+<g>\r
+</g>\r
+<g>\r
+</g>\r
+<g>\r
+</g>\r
+<g>\r
+</g>\r
+<g>\r
+</g>\r
+<g>\r
+</g>\r
+</svg>\r
'empty profile' => 'Unbenannt',
'enable ignore filter' => '"Ignorieren"-Filter ein',
'enable javascript' => 'Javascript aktivieren',
+'enable otp' => 'Aktiviere OTP',
'enable smt' => 'Simultaneous Multi-Threading (SMT) einschalten',
'enable wildcards' => 'Wildcards erlauben:',
'enabled' => 'Aktiviert:',
'other login script' => 'Anderes Anmeldeskript',
'otherip' => 'Andere IP',
'otherport' => 'Anderer Port',
+'otp qrcode' => 'OTP QRCode',
'our donors' => 'Unsere Unterstützer',
'out' => 'Aus',
'outgoing' => 'ausgehend',
'secondary ntp server' => 'Sekundärer NTP-Server',
'secondary wins server address' => 'Sekundärer WINS-Server',
'seconds' => 'Sek.',
+'secret' => 'Geheimnis',
'section' => 'Abschnitt',
'secure shell server' => 'Secure Shell Server',
'security' => 'Sicherheit',
'show last x lines' => 'die letzten x Zeilen anzeigen',
'show root certificate' => 'Root-Zertifikat anzeigen',
'show share options' => 'Anzeige der Freigabeeinstellungen',
+'show otp qrcode' => 'Zeige OTP QRCode',
'shuffle' => 'Zufall',
'shutdown' => 'Herunterfahren',
'shutdown ask' => 'Herunterfahren?',
'empty' => 'This field may be left blank',
'empty profile' => 'empty',
'enable' => 'Enable',
+'enable otp' => 'Enable OTP',
'enable ignore filter' => 'Enable ignore filter',
'enable javascript' => 'Enable javascript',
'enable smt' => 'Enable Simultaneous Multi-Threading (SMT)',
'other login script' => 'Other login script',
'otherip' => 'other IP',
'otherport' => 'other Port',
+'otp qrcode' => 'OTP QRCode',
'our donors' => 'Our donors',
'out' => 'Out',
'outgoing' => 'outgoing',
'secondary ntp server' => 'Secondary NTP server',
'secondary wins server address' => 'Secondary WINS server address',
'seconds' => 'Secs',
+'secret' => 'Secret',
'section' => 'Section',
'secure shell server' => 'Secure Shell Server',
'security' => 'Security',
'show host certificate' => 'Show host certificate',
'show last x lines' => 'Show last x lines',
'show lines' => 'Show lines',
+'show otp qrcode' => 'Show OTP QRCode',
'show root certificate' => 'Show root certificate',
'show share options' => 'Show shares options',
'show tls-auth key' => 'Show tls-auth key',
mv -v /var/ipfire/ovpn/verify /usr/lib/openvpn/verify
chown root:root /usr/lib/openvpn/verify
chmod 755 /usr/lib/openvpn/verify
+ mv -v /var/ipfire/ovpn/otp-verify /usr/lib/openvpn/otp-verify
+ chown root:root /usr/lib/openvpn/otp-verify
+ chmod 755 /usr/lib/openvpn/otp-verify
# Add crl updater
mv -v /var/ipfire/ovpn/openvpn-crl-updater /etc/fcron.daily
chown root:root /etc/fcron.daily/openvpn-crl-updater