Merge branch 'master' into crypto_update
authorMichael Rash <mbr@cipherdyne.org>
Tue, 10 Jul 2012 12:23:16 +0000 (08:23 -0400)
committerMichael Rash <mbr@cipherdyne.org>
Tue, 10 Jul 2012 12:23:16 +0000 (08:23 -0400)
26 files changed:
client/cmd_opts.h
client/config_init.c
client/fwknop.c
client/fwknop_common.h
configure.ac
doc/fwknop.man.asciidoc
doc/fwknopd.man.asciidoc
extras/spa-entropy/spa-entropy.pl [new file with mode: 0755]
lib/cipher_funcs.c
lib/cipher_funcs.h
lib/fko.h
lib/fko_context.h
lib/fko_encryption.c
lib/fko_funcs.c
lib/fko_state.h
lib/rijndael.h
server/access.c
server/fwknopd_common.h
server/incoming_spa.c
test/conf/cfb_mode_access.conf [new file with mode: 0644]
test/conf/ctr_mode_access.conf [new file with mode: 0644]
test/conf/ecb_mode_access.conf [new file with mode: 0644]
test/conf/invalid_source_access.conf [new file with mode: 0644]
test/conf/ofb_mode_access.conf [new file with mode: 0644]
test/test-coverage/iptables/zero_called_functions [new file with mode: 0644]
test/test-fwknop.pl

index cd042c1..ab2f8f4 100644 (file)
@@ -35,6 +35,7 @@
 */
 enum {
     FKO_DIGEST_NAME     = 0x100,
+    ENCRYPTION_MODE,
     NAT_LOCAL,
     NAT_PORT,
     NAT_RAND_PORT,
@@ -55,7 +56,7 @@ enum {
 
 /* Our getopt_long options string.
 */
-#define GETOPTS_OPTION_STRING "a:A:bB:C:D:f:gG:hH:lm:n:N:p:P:Q:rRsS:Tu:U:vV"
+#define GETOPTS_OPTION_STRING "a:A:bB:C:D:f:gG:hH:lm:M:n:N:p:P:Q:rRsS:Tu:U:vV"
 
 /* Our program command-line options...
 */
@@ -69,6 +70,7 @@ static struct option cmd_opts[] =
     {"server-cmd",          1, NULL, 'C'},
     {"digest-type",         1, NULL, FKO_DIGEST_NAME},
     {"destination",         1, NULL, 'D'},
+    {"encryption-mode",     1, NULL, ENCRYPTION_MODE},
     {"fw-timeout",          1, NULL, 'f'},
     {"gpg-encryption",      0, NULL, 'g'},
     {"gpg-recipient-key",   1, NULL, GPG_RECIP_KEY },
index 19ec13e..2632abd 100644 (file)
@@ -35,7 +35,7 @@
 
 /* Convert a digest_type string to its integer value.
 */
-static int
+static short
 digest_strtoint(const char *dt_str)
 {
     if(strcasecmp(dt_str, "md5") == 0)
@@ -52,7 +52,28 @@ digest_strtoint(const char *dt_str)
         return(-1);
 }
 
-/* Convert a protocol string to its intger value.
+/* Convert an encryption_mode string to its integer value.
+*/
+static int
+enc_mode_strtoint(const char *enc_mode_str)
+{
+    if(strcasecmp(enc_mode_str, "cbc") == 0)
+        return(FKO_ENC_MODE_CBC);
+    else if(strcasecmp(enc_mode_str, "ecb") == 0)
+        return(FKO_ENC_MODE_ECB);
+    else if(strcasecmp(enc_mode_str, "cfb") == 0)
+        return(FKO_ENC_MODE_CFB);
+    else if(strcasecmp(enc_mode_str, "pcbc") == 0)
+        return(-1); /* not supported yet */
+    else if(strcasecmp(enc_mode_str, "ofb") == 0)
+        return(FKO_ENC_MODE_OFB);
+    else if(strcasecmp(enc_mode_str, "ctr") == 0)
+        return(FKO_ENC_MODE_CTR);
+    else
+        return(-1);
+}
+
+/* Convert a protocol string to its integer value.
 */
 static int
 proto_strtoint(const char *pr_str)
@@ -290,6 +311,15 @@ parse_rc_param(fko_cli_options_t *options, const char *var, char * val)
         else
             options->time_offset_plus = parse_time_offset(val);
     }
+    /* symmetric encryption mode */
+    else if(CONF_VAR_IS(var, "ENCRYPTION_MODE"))
+    {
+        tmpint = enc_mode_strtoint(val);
+        if(tmpint < 0)
+            return(-1);
+        else
+            options->encryption_mode = tmpint;
+    }
     /* Use GPG ? */
     else if(CONF_VAR_IS(var, "USE_GPG"))
     {
@@ -724,7 +754,19 @@ config_init(fko_cli_options_t *options, int argc, char **argv)
             case FKO_DIGEST_NAME:
                 if((options->digest_type = digest_strtoint(optarg)) < 0)
                 {
-                    fprintf(stderr, "* Invalid digest type: %s\n", optarg);
+                    fprintf(stderr,
+                        "* Invalid digest type: %s, use {md5,sha1,sha256,sha384,sha512}\n",
+                    optarg);
+                    exit(EXIT_FAILURE);
+                }
+                break;
+            case 'M':
+            case ENCRYPTION_MODE:
+                if((options->encryption_mode = enc_mode_strtoint(optarg)) < 0)
+                {
+                    fprintf(stderr,
+                        "* Invalid encryption mode: %s, use {cbc,ecb}\n",
+                    optarg);
                     exit(EXIT_FAILURE);
                 }
                 break;
index 687f5a0..5273448 100644 (file)
@@ -252,6 +252,23 @@ main(int argc, char **argv)
                 return(EXIT_FAILURE);
             }
         }
+
+        res = fko_set_spa_encryption_mode(ctx, FKO_ENC_MODE_ASYMMETRIC);
+        if(res != FKO_SUCCESS)
+        {
+            errmsg("fko_set_spa_encryption_mode", res);
+            return(EXIT_FAILURE);
+        }
+    }
+
+    if(options.encryption_mode && !options.use_gpg)
+    {
+        res = fko_set_spa_encryption_mode(ctx, options.encryption_mode);
+        if(res != FKO_SUCCESS)
+        {
+            errmsg("fko_set_spa_encryption_mode", res);
+            return(EXIT_FAILURE);
+        }
     }
 
     /* Set Digest type.
@@ -328,13 +345,20 @@ main(int argc, char **argv)
          * an empty context, populate it with the encrypted data, set our
          * options, then decode it.
         */
-        res = fko_new_with_data(&ctx2, spa_data, NULL);
+        res = fko_new_with_data(&ctx2, spa_data, NULL, ctx->encryption_mode);
         if(res != FKO_SUCCESS)
         {
             errmsg("fko_new_with_data", res);
             return(EXIT_FAILURE);
         }
 
+        res = fko_set_spa_encryption_mode(ctx2, ctx->encryption_mode);
+        if(res != FKO_SUCCESS)
+        {
+            errmsg("fko_set_spa_encryption_mode", res);
+            return(EXIT_FAILURE);
+        }
+
         /* See if we are using gpg and if we need to set the GPG home dir.
         */
         if(options.use_gpg)
@@ -770,6 +794,7 @@ display_ctx(fko_ctx_t ctx)
     time_t      timestamp       = 0;
     short       msg_type        = -1;
     short       digest_type     = -1;
+    int         encryption_mode = -1;
     int         client_timeout  = -1;
 
     /* Should be checking return values, but this is temp code. --DSS
@@ -784,6 +809,7 @@ display_ctx(fko_ctx_t ctx)
     fko_get_spa_server_auth(ctx, &server_auth);
     fko_get_spa_client_timeout(ctx, &client_timeout);
     fko_get_spa_digest_type(ctx, &digest_type);
+    fko_get_spa_encryption_mode(ctx, &encryption_mode);
     fko_get_encoded_data(ctx, &enc_data);
     fko_get_spa_digest(ctx, &spa_digest);
     fko_get_spa_data(ctx, &spa_data);
@@ -798,9 +824,14 @@ display_ctx(fko_ctx_t ctx)
     printf("     Nat Access: %s\n", nat_access == NULL ? "<NULL>" : nat_access);
     printf("    Server Auth: %s\n", server_auth == NULL ? "<NULL>" : server_auth);
     printf(" Client Timeout: %u\n", client_timeout);
-    printf("    Digest Type: %u\n", digest_type);
+    printf("    Digest Type: %d\n", digest_type);
+    printf("Encryption Mode: %d\n", encryption_mode);
     printf("\n   Encoded Data: %s\n", enc_data == NULL ? "<NULL>" : enc_data);
-    printf("\nSPA Data Digest: %s\n", spa_digest == NULL ? "<NULL>" : spa_digest);
+    printf("SPA Data Digest: %s\n", spa_digest == NULL ? "<NULL>" : spa_digest);
+
+    if (enc_data != NULL && spa_digest != NULL)
+        printf("      Plaintext: %s:%s\n", enc_data, spa_digest);
+
     printf("\nFinal Packed/Encrypted/Encoded Data:\n\n%s\n\n", spa_data);
 }
 
index 5d7e8ef..4e494aa 100644 (file)
@@ -116,7 +116,8 @@ typedef struct fko_cli_options
     unsigned int spa_dst_port;
     unsigned int spa_src_port; /* only used with --source-port */
 
-    unsigned int digest_type;
+    short digest_type;
+    int encryption_mode;
 
     /* Various command-line flags */
     unsigned char   verbose; /* --verbose mode */
index 79f8613..55a6c0e 100644 (file)
@@ -120,6 +120,20 @@ AC_PROG_LN_S
 AC_PROG_MAKE_SET
 AC_PROG_LIBTOOL
 
+dnl Decide whether or not to build binaries with profiling coverage support
+dnl
+want_profile_coverage=no
+AC_ARG_ENABLE([profile-coverage],
+  [AS_HELP_STRING([--enable-profile-coverage],
+    [Build fwknop binaries with profile coverage support @<:@default is to disable@:>@])],
+  [want_profile_coverage=$enableval],
+  [])
+
+if test "x$want_profile_coverage" = "xyes"; then
+    CFLAGS="-g -O0"
+    FKO_CHECK_COMPILER_ARG([-fprofile-arcs -ftest-coverage -fno-inline])
+fi
+
 dnl Decide whether or not to enable all warnings with -Wall
 dnl
 use_wall=yes
index f110d20..7d8ff1f 100644 (file)
@@ -1,5 +1,5 @@
 :man source: Fwknop Client
-:man manual: Fwknop Client 
+:man manual: Fwknop Client
 
 FWKNOP(8)
 =========
@@ -38,7 +38,7 @@ sequence on the server to break port knocking authentication.
 SPA packets can easily be spoofed as well (this is a good thing in this
 context), and this makes it possible to make it appear as though, say,
 www.yahoo.com is trying to authenticate to a target system but in reality
-the actual connection will come from a seemingly unrelated IP.  
+the actual connection will come from a seemingly unrelated IP.
 
 Authorization packets are either encrypted with the 'Rijndael' block cipher
 or via 'GnuPG' and associated asymmetric ciphers.  If the symmetric encryption
@@ -161,10 +161,10 @@ SPA OPTIONS
 *-a, --allow-ip*='<IP-address>'::
     Specify IP address that should be permitted through the destination
     *fwknopd* server firewall (this IP is encrypted within the SPA packet
-    itself).  This is useful to prevent a MTIM attack where a SPA packet
+    itself).  This is useful to prevent a MITM attack where a SPA packet
     can be intercepted en-route and sent from a different IP than the
     original.  Hence, if the *fwknopd* server trusts the source address
-    on the  SPA  packet IP header then the attacker gains access. 
+    on the  SPA  packet IP header then the attacker gains access.
     The *-a* option puts the source address within the encrypted SPA
     packet, and so thwarts this attack.  The *-a* option is also
     useful to specify the IP that will be granted access when the
@@ -197,6 +197,13 @@ SPA OPTIONS
     Specify the message digest algorithm to use in the SPA data.  Choices
     are: *MD5*, *SHA1*, *SHA256* (the default), *SHA384*, and *SHA512*.
 
+*-M, --encryption-mode*='<mode>'::
+    Specify the encryption mode when AES is used for encrypting SPA packets.
+    The default is CBC mode, but others can be chosen such as CFB or OFB
+    as long as this is also specified in the 'access.conf' file on the
+    server side via the ENCRYPTION_MODE variable.  In general, it is
+    recommended to not use this argument and just use the default.
+
 *-N, --nat-access*='<internalIP:forwardPort>'::
     The *fwknopd* server offers the ability to provide SPA access through
     an iptables firewall to an internal service by interfacing with the
@@ -205,7 +212,7 @@ SPA OPTIONS
     client can request that the server port forward an external port to an
     internal IP, i.e. ``+--NAT-access 192.168.10.2,55000+''.  In this case,
     access will be granted to 192.168.10.2 via port 55000 to whatever
-    service is requested via the *--access* argument (usually tcp/22). 
+    service is requested via the *--access* argument (usually tcp/22).
     Hence, after sending such an SPA packet, one would then do
     ``ssh -p 55000 user@host'' and the connection would be forwarded on
     through to the internal 192.168.10.2 system automatically.  Note that
@@ -404,6 +411,10 @@ description and its matching command-line option(s):
     Set a value to apply to the timestamp in the SPA packet.  This can
     be either a positive or negative value ('--time-offset-plus/minus').
 
+*ENCRYPTION_MODE*::
+    Specify the encryption mode when AES is used.  This variable is a synonym
+    for the '--encryption-mode' command line argument.
+
 *USE_GPG*::
     Set to 'Y' to specify the use of GPG for encryption ('--gpg-encryption').
 
@@ -552,7 +563,7 @@ More information on Single Packet Authorization can be found in the paper
 
 AUTHORS
 -------
-Damien Stuart <dstuart@dstuart.org>, 
+Damien Stuart <dstuart@dstuart.org>,
 Michael Rash <mbr@cipherdyne.org>
 
 CONTRIBUTORS
index a4b8666..d39c920 100644 (file)
@@ -344,6 +344,11 @@ directive starts a new stanza.
     ``FW_ACCESS_TIMEOUT'' is not set then the default timeout of 30 seconds
     will automatically be set.
 
+*ENCRYPTION_MODE*: '<mode>'::
+    Specify the encryption mode when AES is used.  The default is CBC mode,
+    but other modes can be selected such as OFB and CFB.  In general, it is
+    recommended to not use this variable and leave it as the default.
+
 *ENABLE_CMD_EXEC*: '<Y/N>'::
     This instructs *fwknopd* to accept complete commands that are contained
     within an authorization packet.  Any such command will be executed on
diff --git a/extras/spa-entropy/spa-entropy.pl b/extras/spa-entropy/spa-entropy.pl
new file mode 100755 (executable)
index 0000000..a5ebd7f
--- /dev/null
@@ -0,0 +1,404 @@
+#!/usr/bin/perl -w
+#
+# File: spa-entropy.pl
+#
+# Purpose: To measure cross-packet SPA entropy on a byte by byte slice basis
+#          and produce gunplot graphs.  This is useful to measure SPA packet
+#          randomness after encryption.
+#
+# Author: Michael Rash <mbr@cipherdyne.org>
+#
+# License: GPL v2
+#
+
+use MIME::Base64;
+use IPC::Open2;
+use Getopt::Long 'GetOptions';
+use strict;
+
+my $use_ent = 1;
+my $base64_decode = 1;
+my $packets = 0;
+my $prefix = 'entropy';
+my $file_to_measure = '';
+my $run_fwknop_client = 0;
+my $min_len = 0;
+my $lib_dir = '../../lib/.libs';
+my $fwknop_client_path = '../../client/.libs/fwknop';
+my $enc_mode = 'cbc';
+my $enable_fwknop_client_gpg = 0;
+my $spa_key_file = '../../test/local_spa.key';
+my $help = 0;
+
+my $use_openssl = 0;
+my $openssl_salt = '0000000000000000';
+my $openssl_mode = 'aes-256-cbc';
+
+my %min_max_entropy = (
+    'min' => {
+        'val' => -1,
+        'pos' => 0,
+    },
+    'max' => {
+        'val' => -1,
+        'pos' => 0,
+    }
+);
+
+my @encrypted_data = ();
+my @plaintext_data = ();
+my @cross_pkt_data = ();
+
+Getopt::Long::Configure('no_ignore_case');
+die "[*] See '$0 -h' for usage information" unless (GetOptions(
+    'file-to-measure=s' => \$file_to_measure,
+    'base64-decode'     => \$base64_decode,
+    'count=i'           => \$packets,
+    'prefix=s'          => \$prefix,
+    'run-fwknop-client' => \$run_fwknop_client,
+    'enc-mode=s'        => \$enc_mode,
+    'gpg'               => \$enable_fwknop_client_gpg,
+    'lib-dir=s'         => \$lib_dir,
+    'Client-path=s'     => \$fwknop_client_path,
+    'use-openssl'       => \$use_openssl,
+    'openssl-salt=s'    => \$openssl_salt,
+    'openssl-mode=s'    => \$openssl_mode,
+    'help'              => \$help,
+));
+&usage() if $help;
+
+die "[*] Must execute --run-fwknop-client in --use-openssl mode"
+    if $use_openssl and not $run_fwknop_client;
+
+&run_fwknop_client() if $run_fwknop_client;
+
+&read_data();
+
+&get_min_len();
+
+&build_data_slices();
+
+open F, "> $prefix.dat" or die $!;
+my $pos = 0;
+for my $str (@cross_pkt_data) {
+
+    my $entropy = &get_entropy($str);
+
+#    print F "$pos $entropy\n";
+    print F "$pos $entropy   ### " . &hex_dump($str) . "\n";
+
+    if ($min_max_entropy{'min'}{'val'} == -1
+            and $min_max_entropy{'max'}{'val'} == -1) {
+        $min_max_entropy{'min'}{'val'} = $entropy;
+        $min_max_entropy{'min'}{'pos'} = $pos;
+        $min_max_entropy{'max'}{'val'} = $entropy;
+        $min_max_entropy{'max'}{'pos'} = $pos;
+    } else {
+        if ($entropy < $min_max_entropy{'min'}{'val'}) {
+            $min_max_entropy{'min'}{'val'} = $entropy;
+            $min_max_entropy{'min'}{'pos'} = $pos;
+        }
+        if ($entropy > $min_max_entropy{'max'}{'val'}) {
+            $min_max_entropy{'max'}{'val'} = $entropy;
+            $min_max_entropy{'max'}{'pos'} = $pos;
+        }
+    }
+    $pos++;
+}
+close F;
+
+my $min = sprintf "%.2f", $min_max_entropy{'min'}{'val'};
+my $max = sprintf "%.2f", $min_max_entropy{'max'}{'val'};
+
+print "[+] Min entropy: $min at byte: $min_max_entropy{'min'}{'pos'}\n";
+print "[+] Max entropy: $max at byte: $min_max_entropy{'max'}{'pos'}\n";
+
+&run_gnuplot();
+
+exit 0;
+
+sub read_data() {
+
+    if ($use_openssl) {
+
+        ### we've already gotten plaintext information from the fwknop client,
+        ### so encrypt this data with openssl and use it to re-write the
+        ### $file_to_measure
+        unlink $file_to_measure if -e $file_to_measure;
+
+        my @openssl_encrypted_data = ();
+
+        ### encrypt the plaintext and use it to re-write the -f file
+        for my $line (@plaintext_data) {
+
+            my $ptext_file = 'ptext.tmp';
+            my $enc_file   = 'ptext.enc';
+
+            open F, "> $ptext_file" or die $!;
+            print F $line;
+            close F;
+
+            unlink $enc_file if -e $enc_file;
+
+            system "openssl enc -$openssl_mode -a -S $openssl_salt " .
+                "-in ptext.tmp -out ptext.enc -k fwknoptest000000";
+
+            my $base64_enc_data = '';
+            open F, "< $enc_file" or die $!;
+            while (<F>) {
+                chomp;
+                $base64_enc_data .= $_;
+            }
+            close F;
+
+            push @openssl_encrypted_data, $base64_enc_data;
+
+        }
+
+        open F, "> $file_to_measure" or die $!;
+        for my $line (@openssl_encrypted_data) {
+            print F $line, "\n";
+        }
+        close F;
+    }
+
+    my $fh = *STDIN;
+    if ($file_to_measure) {
+        open IN, "< $file_to_measure" or die "[*] Could not open $file_to_measure: $!";
+        $fh = *IN;
+    }
+
+    my $l_ctr = 0;
+    while (<$fh>) {
+        next unless $_ =~ /\S/;
+        chomp;
+
+        if ($base64_decode) {
+            if (&is_base64($_)) {
+                my $base64_str = $_;
+
+                if ($enable_fwknop_client_gpg) {
+                    unless ($base64_str =~ /^hQ/) {
+                        $base64_str = 'hQ' . $base64_str;
+                    }
+                } else {
+                    ### base64-encoded "Salted__" prefix
+                    unless ($base64_str =~ /^U2FsdGVkX1/) {
+                        $base64_str = 'U2FsdGVkX1' . $base64_str;
+                    }
+                }
+
+                my ($equals_rv, $equals_padding) = &base64_equals_padding($base64_str);
+                if ($equals_padding) {
+                    $base64_str .= $equals_padding;
+                }
+                my $str = decode_base64($base64_str);
+
+                if ($enable_fwknop_client_gpg) {
+                    $str =~ s/^\x85\x02//;
+                } else {
+                    $str =~ s/^Salted__//;
+                }
+                push @encrypted_data, $str;
+            } else {
+                push @encrypted_data, $_;
+            }
+        } else {
+            push @encrypted_data, $_;
+        }
+
+        $l_ctr++;
+        if ($packets > 0) {
+            last if $l_ctr == $packets;
+        }
+    }
+
+    ### hex dump encrypted data
+    open HEX, "> hex_dump.data" or die $!;
+    for my $line (@encrypted_data) {
+        print HEX &hex_dump($line), "\n";
+    }
+    close HEX;
+
+    print "[+] Read in $l_ctr SPA packets...\n";
+    return;
+}
+
+sub run_fwknop_client() {
+    die "[*] Must set packets file with -f <file>" unless $file_to_measure;
+    die "[*] Must set packet count with -c <count>" unless $packets;
+
+    if (-e $file_to_measure) {
+        unlink $file_to_measure or die $!;
+    }
+
+    my $cmd = "LD_LIBRARY_PATH=$lib_dir $fwknop_client_path -A tcp/22 " .
+        "-a 127.0.0.2 -D 127.0.0.1 --get-key $spa_key_file " .
+        "-B $file_to_measure -b -v --test";
+
+    if ($enable_fwknop_client_gpg) {
+        $cmd .= ' --gpg-recipient-key 361BBAD4 --gpg-signer-key 6A3FAD56 ' .
+            '--gpg-home-dir ../../test/conf/client-gpg';
+    } else {
+        $cmd .= " -M $enc_mode";
+    }
+    $cmd .= " 2> /dev/null";
+
+    print "[+] Running fwknop client via the following command:\n\n$cmd\n\n";
+
+    for (my $i=0; $i < $packets; $i++) {
+        open C, "$cmd |" or die $!;
+        while (<C>) {
+            if (/Plaintext\:\s+(\S+)/) {
+                push @plaintext_data, $1;
+                last;
+            }
+        }
+        close C;
+    }
+
+    return;
+}
+
+sub get_min_len() {
+
+    ### calculate minimum length
+    for my $line (@encrypted_data) {
+        chomp $line;
+        next unless $line =~ /\S/;
+        my $len = length($line);
+        if ($min_len == 0) {
+            $min_len = $len;
+        } else {
+            if ($len < $min_len) {
+                $min_len = $len;
+            }
+        }
+    }
+    return;
+}
+
+sub build_data_slices() {
+    for my $line (@encrypted_data) {
+        my @chars = split //, $line;
+        my $c_ctr = 0;
+        for my $char (@chars) {
+            $cross_pkt_data[$c_ctr] .= $char;
+            last if $c_ctr == $min_len;
+            $c_ctr++;
+        }
+    }
+    return;
+}
+
+sub run_gnuplot() {
+    open F, "> $prefix.gnu" or die $!;
+
+    my $enc_str = $enc_mode;
+    $enc_str = 'gpg' if $enable_fwknop_client_gpg;
+
+    my $yrange = '[0:9]';
+    print F <<_GNUPLOT_;
+set title "SPA slice entropy (encryption mode: $enc_str)"
+set terminal gif nocrop enhanced
+set output "$prefix.gif"
+set grid
+set yrange $yrange
+plot '$prefix.dat' using 1:2 with lines title 'min: $min \\@ byte: $min_max_entropy{'min'}{'pos'}, max: $max \\@ byte: $min_max_entropy{'max'}{'pos'}'
+_GNUPLOT_
+    close F;
+
+    print "[+] Creating $prefix.gif gnuplot graph...\n\n";
+    system "gnuplot $prefix.gnu";
+
+    return;
+}
+
+sub get_entropy() {
+    my $data = shift;
+
+    my $entropy = '';
+
+    my $pid = open2(\*CHLD_OUT, \*CHLD_IN, 'ent');
+
+    print CHLD_IN $data;
+    close CHLD_IN;
+
+    while (<CHLD_OUT>) {
+        ### Entropy = 5.637677 bits per byte.
+        if (/Entropy\s=\s(\d\S+)/) {
+            $entropy = $1;
+            last;
+        }
+    }
+
+    close CHLD_OUT;
+
+    waitpid $pid, 0;
+    my $child_exit_status = $? >> 8;
+
+    return $entropy;
+}
+
+sub base64_equals_padding() {
+    my $msg = shift;
+    my $padding = '';
+
+    return 1, $padding if $msg =~ /=$/;
+
+    my $remainder = 4 - length($msg) % 4;
+
+    if ($remainder == 3) {
+        ### not possible for valid base64 data - should only have
+        ### pad with one or two '=' chars
+        return 0, $padding;
+    }
+
+    unless ($remainder == 4) {
+        $padding .= '='x$remainder;
+    }
+    return 1, $padding;
+}
+
+sub hex_dump() {
+    my $data = shift;
+
+    my @chars = split //, $data;
+    my $ctr = 0;
+
+    my $hex_part   = '';
+    my $ascii_part = '';
+
+    for my $char (@chars) {
+
+        $hex_part .= sprintf "%.2x", ord($char);
+
+        if ($char =~ /[^\x20-\x7e]/) {
+            $ascii_part .= '.';
+        } else {
+            $ascii_part .= $char;
+        }
+        $ctr++;
+    }
+    return "$hex_part $ascii_part";
+#    return "$ascii_part";
+}
+
+sub is_base64() {
+    my $data = shift;
+
+    ### check to make sure the packet data only contains base64 encoded
+    ### characters per RFC 3548:   0-9, A-Z, a-z, +, /, =
+    if ($data =~ /[^\x30-\x39\x41-\x5a\x61-\x7a\x2b\x2f\x3d]/) {
+        return 0;
+    }
+    if ($data =~ /=[^=]/) {
+        return 0;
+    }
+    return 1;
+}
+
+sub usage() {
+    print "$0 [options]\n";
+    exit 0;
+}
index 8fee690..0a0ce3b 100644 (file)
@@ -156,7 +156,7 @@ rij_salt_and_iv(RIJNDAEL_context *ctx, const char *pass, const unsigned char *da
     /* Now generate the key and initialization vector.
      * (again it is the perl Crypt::CBC way, with a touch of
      * fwknop).
-    */ 
+    */
     memcpy(tmp_buf+16, pw_buf, 16);
     memcpy(tmp_buf+32, ctx->salt, 8);
 
@@ -181,12 +181,14 @@ rij_salt_and_iv(RIJNDAEL_context *ctx, const char *pass, const unsigned char *da
 /* Initialization entry point.
 */
 static void
-rijndael_init(RIJNDAEL_context *ctx, const char *pass, const unsigned char *data)
+rijndael_init(RIJNDAEL_context *ctx, const char *pass,
+    const unsigned char *data, int encryption_mode)
 {
 
-    /* Use ECB mode to be compatible with the Crypt::CBC perl module.
+    /* The default (set in fko.h) is ECB mode to be compatible with the
+     * Crypt::CBC perl module.
     */
-    ctx->mode = MODE_ECB;
+    ctx->mode = encryption_mode;
 
     /* Generate the salt and initialization vector.
     */
@@ -201,53 +203,32 @@ rijndael_init(RIJNDAEL_context *ctx, const char *pass, const unsigned char *data
  * module would.
 */
 size_t
-rij_encrypt(unsigned char *in, size_t in_len, const char *pass, unsigned char *out)
+rij_encrypt(unsigned char *in, size_t in_len,
+    const char *pass, unsigned char *out, int encryption_mode)
 {
     RIJNDAEL_context    ctx;
-    unsigned char       plaintext[RIJNDAEL_BLOCKSIZE];
-    unsigned char       mixtext[RIJNDAEL_BLOCKSIZE];
-    unsigned char       ciphertext[RIJNDAEL_BLOCKSIZE];
     int                 i, pad_val;
-
     unsigned char      *ondx = out;
 
-    rijndael_init(&ctx, pass, NULL);
+    rijndael_init(&ctx, pass, NULL, encryption_mode);
 
-    /* Prepend the salt...
+    /* Prepend the salt to the ciphertext...
     */
     memcpy(ondx, "Salted__", 8);
     ondx+=8;
     memcpy(ondx, ctx.salt, 8);
     ondx+=8;
 
-    /* Now iterate of the input data and encrypt in 16-byte chunks.
+    /* Add padding to the original plaintext to ensure that it is a
+     * multiple of the Rijndael block size
     */
-    while(in_len)
-    {
-        for(i=0; i<sizeof(plaintext); i++)
-        {
-            if(in_len < 1)
-                break;
-
-            plaintext[i] = *in++;
-            in_len--;
-        }
-
-        pad_val = sizeof(plaintext) - i;
-
-        for(; i < sizeof(plaintext); i++)
-            plaintext[i] = pad_val;
+    pad_val = RIJNDAEL_BLOCKSIZE - (in_len % RIJNDAEL_BLOCKSIZE);
+    for (i = in_len; i < in_len+pad_val; i++)
+        in[i] = pad_val;
 
-        for(i=0; i<RIJNDAEL_BLOCKSIZE; i++)
-            mixtext[i] = plaintext[i] ^ ctx.iv[i];
+    block_encrypt(&ctx, in, in_len+pad_val, ondx, ctx.iv);
 
-        block_encrypt(&ctx, mixtext, RIJNDAEL_BLOCKSIZE, ciphertext, ctx.iv);
-
-        memcpy(ctx.iv, ciphertext, RIJNDAEL_BLOCKSIZE);
-
-        for(i=0; i<sizeof(ciphertext); i++)
-            *ondx++ = ciphertext[i];
-    }
+    ondx += in_len+pad_val;
 
     return(ondx - out);
 }
@@ -255,44 +236,25 @@ rij_encrypt(unsigned char *in, size_t in_len, const char *pass, unsigned char *o
 /* Decrypt the given data.
 */
 size_t
-rij_decrypt(unsigned char *in, size_t in_len, const char *pass, unsigned char *out)
+rij_decrypt(unsigned char *in, size_t in_len,
+    const char *pass, unsigned char *out, int encryption_mode)
 {
     RIJNDAEL_context    ctx;
-    unsigned char       plaintext[RIJNDAEL_BLOCKSIZE];
-    unsigned char       mixtext[RIJNDAEL_BLOCKSIZE];
-    unsigned char       ciphertext[RIJNDAEL_BLOCKSIZE];
     int                 i, pad_val, pad_err = 0;
     unsigned char      *pad_s;
     unsigned char      *ondx = out;
 
-    rijndael_init(&ctx, pass, in);
+    rijndael_init(&ctx, pass, in, encryption_mode);
 
-    /* Remove the salt from the input.
+    /* Remove the first block since it contains the salt (it was consumed
+     * by the rijndael_init() function above).
     */
-    in_len -= 16;
-    memmove(in, in+16, in_len);
+    in_len -= RIJNDAEL_BLOCKSIZE;
+    memmove(in, in+RIJNDAEL_BLOCKSIZE, in_len);
 
-    while(in_len)
-    {
-        for(i=0; i<sizeof(ciphertext); i++)
-        {
-            if(in_len < 1)
-                break;
-
-            ciphertext[i] = *in++;
-            in_len--;
-        }
+    block_decrypt(&ctx, in, in_len, out, ctx.iv);
 
-        block_decrypt(&ctx, ciphertext, RIJNDAEL_BLOCKSIZE, mixtext, ctx.iv);
-
-        for(i=0; i<sizeof(ciphertext); i++)
-            plaintext[i] = mixtext[i] ^ ctx.iv[i];
-
-        memcpy(ctx.iv, ciphertext, RIJNDAEL_BLOCKSIZE);
-
-        for(i=0; i<sizeof(plaintext); i++)
-            *ondx++ = plaintext[i];
-    }
+    ondx += in_len;
 
     /* Find and remove padding.
     */
index 07edace..a311d10 100644 (file)
 */
 #define PREDICT_ENCSIZE(x) (1+(x>>4)+(x&0xf?1:0))<<4
 
-size_t rij_encrypt(unsigned char *in, size_t len, const char *key, unsigned char *out);
-size_t rij_decrypt(unsigned char *in, size_t len, const char *key, unsigned char *out);
+size_t rij_encrypt(unsigned char *in, size_t len,
+    const char *key, unsigned char *out, int encryption_mode);
+size_t rij_decrypt(unsigned char *in, size_t len,
+    const char *key, unsigned char *out, int encryption_mode);
 
 #endif /* CIPHER_FUNCS_H */
 
index 1f5af83..9ef8ae4 100644 (file)
--- a/lib/fko.h
+++ b/lib/fko.h
@@ -33,6 +33,8 @@
 
 #include <time.h>
 
+#include "rijndael.h"   /* For encryption modes */
+
 #ifdef __cplusplus
 extern "C" {
 #endif
@@ -91,6 +93,20 @@ typedef enum {
     FKO_LAST_ENCRYPTION_TYPE /* Always leave this as the last one */
 } fko_encryption_type_t;
 
+/* Symmetric encryption modes derived from rijndael.h
+*/
+typedef enum {
+    FKO_ENC_MODE_UNKNOWN = 0,
+    FKO_ENC_MODE_ECB  = MODE_ECB,
+    FKO_ENC_MODE_CBC  = MODE_CBC,
+    FKO_ENC_MODE_CFB  = MODE_CFB,
+    FKO_ENC_MODE_PCBC = MODE_PCBC,
+    FKO_ENC_MODE_OFB  = MODE_OFB,
+    FKO_ENC_MODE_CTR  = MODE_CTR,
+    FKO_ENC_MODE_ASYMMETRIC,  /* placeholder when GPG is used */
+    FKO_LAST_ENC_MODE /* Always leave this as the last one */
+} fko_encryption_mode_t;
+
 /* FKO ERROR_CODES
  *
  * Note: If you change this list in any way, please be sure to make the
@@ -160,6 +176,7 @@ typedef enum {
 #define FKO_DEFAULT_MSG_TYPE    FKO_ACCESS_MSG
 #define FKO_DEFAULT_DIGEST      FKO_DIGEST_SHA256
 #define FKO_DEFAULT_ENCRYPTION  FKO_ENCRYPTION_RIJNDAEL
+#define FKO_DEFAULT_ENC_MODE    MODE_CBC
 
 /* The context holds the global state and config options, as
  * well as some intermediate results during processing. This
@@ -189,7 +206,8 @@ enum {
 /* General api calls
 */
 DLL_API int fko_new(fko_ctx_t *ctx);
-DLL_API int fko_new_with_data(fko_ctx_t *ctx, const char *enc_msg, const char *dec_key);
+DLL_API int fko_new_with_data(fko_ctx_t *ctx, const char *enc_msg, const char *dec_key,
+    int encryption_mode);
 DLL_API void fko_destroy(fko_ctx_t ctx);
 DLL_API int fko_spa_data_final(fko_ctx_t ctx, const char *enc_key);
 
@@ -209,6 +227,7 @@ DLL_API int fko_set_spa_digest(fko_ctx_t ctx);
 DLL_API int fko_set_raw_spa_digest_type(fko_ctx_t ctx, const short raw_digest_type);
 DLL_API int fko_set_raw_spa_digest(fko_ctx_t ctx);
 DLL_API int fko_set_spa_encryption_type(fko_ctx_t ctx, const short encrypt_type);
+DLL_API int fko_set_spa_encryption_mode(fko_ctx_t ctx, const int encrypt_mode);
 DLL_API int fko_set_spa_data(fko_ctx_t ctx, const char *enc_msg);
 
 /* Data processing and misc utility functions
@@ -239,6 +258,7 @@ DLL_API int fko_get_raw_spa_digest_type(fko_ctx_t ctx, short *raw_spa_digest_typ
 DLL_API int fko_get_spa_digest(fko_ctx_t ctx, char **spa_digest);
 DLL_API int fko_get_raw_spa_digest(fko_ctx_t ctx, char **raw_spa_digest);
 DLL_API int fko_get_spa_encryption_type(fko_ctx_t ctx, short *spa_enc_type);
+DLL_API int fko_get_spa_encryption_mode(fko_ctx_t ctx, int *spa_enc_mode);
 DLL_API int fko_get_spa_data(fko_ctx_t ctx, char **spa_data);
 
 DLL_API int fko_get_version(fko_ctx_t ctx, char **version);
index a57a2dc..52a1c6a 100644 (file)
@@ -63,6 +63,7 @@ struct fko_context {
     /* FKO SPA user-settable message encoding types */
     short  digest_type;
     short  encryption_type;
+    int    encryption_mode;
 
     /* Computed or predefined data */
     char           *version;
index 4b81a41..af43a87 100644 (file)
 static int
 _rijndael_encrypt(fko_ctx_t ctx, const char *enc_key)
 {
-    char           *plain;
-    char           *b64cipher;
-    unsigned char  *cipher;
+    char           *plaintext;
+    char           *b64ciphertext;
+    unsigned char  *ciphertext;
     int             cipher_len;
 
     /* Make a bucket big enough to hold the enc msg + digest (plaintext)
      * and populate it appropriately.
     */
-    plain = malloc(strlen(ctx->encoded_msg) + strlen(ctx->digest) + 2);
-    if(plain == NULL)
+    plaintext = calloc(1, strlen(ctx->encoded_msg)
+                    + strlen(ctx->digest) + RIJNDAEL_BLOCKSIZE + 2);
+
+    if(plaintext == NULL)
         return(FKO_ERROR_MEMORY_ALLOCATION);
 
-    sprintf(plain, "%s:%s", ctx->encoded_msg, ctx->digest);
+    sprintf(plaintext, "%s:%s", ctx->encoded_msg, ctx->digest);
 
     /* Make a bucket for the encrypted version and populate it.
     */
-    cipher = malloc(strlen(plain) + 32); /* Plus padding for salt and Block */
-    if(cipher == NULL)
+    ciphertext = calloc(1, strlen(plaintext) + 32); /* Plus padding for salt and Block */
+    if(ciphertext == NULL)
         return(FKO_ERROR_MEMORY_ALLOCATION);
 
     cipher_len = rij_encrypt(
-        (unsigned char*)plain, strlen(plain), (char*)enc_key, cipher
+        (unsigned char*)plaintext, strlen(plaintext), (char*)enc_key, ciphertext,
+        ctx->encryption_mode
     );
 
     /* Now make a bucket for the base64-encoded version and populate it.
     */
-    b64cipher = malloc(((cipher_len / 3) * 4) + 8);
-    if(b64cipher == NULL)
+    b64ciphertext = malloc(((cipher_len / 3) * 4) + 8);
+    if(b64ciphertext == NULL)
         return(FKO_ERROR_MEMORY_ALLOCATION);
 
-    b64_encode(cipher, b64cipher, cipher_len);
-    strip_b64_eq(b64cipher);
+    b64_encode(ciphertext, b64ciphertext, cipher_len);
+    strip_b64_eq(b64ciphertext);
 
-    ctx->encrypted_msg = strdup(b64cipher);
+    ctx->encrypted_msg = strdup(b64ciphertext);
 
     /* Clean-up
     */
-    free(plain);
-    free(cipher);
-    free(b64cipher);
+    free(plaintext);
+    free(ciphertext);
+    free(b64ciphertext);
 
     if(ctx->encrypted_msg == NULL)
         return(FKO_ERROR_MEMORY_ALLOCATION);
@@ -95,7 +98,7 @@ _rijndael_encrypt(fko_ctx_t ctx, const char *enc_key)
 /* Decode, decrypt, and parse SPA data into the context.
 */
 static int
-_rijndael_decrypt(fko_ctx_t ctx, const char *dec_key)
+_rijndael_decrypt(fko_ctx_t ctx, const char *dec_key, int encryption_mode)
 {
     char           *tbuf;
     unsigned char  *ndx;
@@ -136,6 +139,15 @@ _rijndael_decrypt(fko_ctx_t ctx, const char *dec_key)
 
     cipher_len = b64_decode(ctx->encrypted_msg, cipher);
 
+    /* Since we're using AES, make sure the incoming data is a multiple of
+     * the blocksize
+    */
+    if((cipher_len % RIJNDAEL_BLOCKSIZE) != 0)
+    {
+        free(cipher);
+        return(FKO_ERROR_INVALID_DATA);
+    }
+
     /* Create a bucket for the plaintext data and decrypt the message
      * data into it.
     */
@@ -143,7 +155,8 @@ _rijndael_decrypt(fko_ctx_t ctx, const char *dec_key)
     if(ctx->encoded_msg == NULL)
         return(FKO_ERROR_MEMORY_ALLOCATION);
 
-    pt_len = rij_decrypt(cipher, cipher_len, dec_key, (unsigned char*)ctx->encoded_msg);
+    pt_len = rij_decrypt(cipher, cipher_len, dec_key,
+                (unsigned char*)ctx->encoded_msg, encryption_mode);
 
     /* Done with cipher...
     */
@@ -351,6 +364,41 @@ fko_get_spa_encryption_type(fko_ctx_t ctx, short *enc_type)
     return(FKO_SUCCESS);
 }
 
+/* Set the SPA encryption mode.
+*/
+int
+fko_set_spa_encryption_mode(fko_ctx_t ctx, const int encrypt_mode)
+{
+    /* Must be initialized
+    */
+    if(!CTX_INITIALIZED(ctx))
+        return(FKO_ERROR_CTX_NOT_INITIALIZED);
+
+    if(encrypt_mode < 0 || encrypt_mode >= FKO_LAST_ENC_MODE)
+        return(FKO_ERROR_INVALID_DATA);
+
+    ctx->encryption_mode = encrypt_mode;
+
+    ctx->state |= FKO_ENCRYPT_MODE_MODIFIED;
+
+    return(FKO_SUCCESS);
+}
+
+/* Return the SPA encryption mode.
+*/
+int
+fko_get_spa_encryption_mode(fko_ctx_t ctx, int *enc_mode)
+{
+    /* Must be initialized
+    */
+    if(!CTX_INITIALIZED(ctx))
+        return(FKO_ERROR_CTX_NOT_INITIALIZED);
+
+    *enc_mode = ctx->encryption_mode;
+
+    return(FKO_SUCCESS);
+}
+
 /* Encrypt the encoded SPA data.
 */
 int
@@ -425,7 +473,7 @@ fko_decrypt_spa_data(fko_ctx_t ctx, const char *dec_key)
     else if(enc_type == FKO_ENCRYPTION_RIJNDAEL)
     {
         ctx->encryption_type = FKO_ENCRYPTION_RIJNDAEL;
-        res = _rijndael_decrypt(ctx, dec_key);
+        res = _rijndael_decrypt(ctx, dec_key, ctx->encryption_mode);
     }
     else
         return(FKO_ERROR_INVALID_DATA);
index 5ea4f64..91c01a7 100644 (file)
@@ -133,6 +133,18 @@ fko_new(fko_ctx_t *r_ctx)
         return res;
     }
 
+    /* Default Encryption Mode (Rijndael in EBC mode for backwards
+     * compatibility - it recommended to change this to CBC mode)
+    */
+    ctx->initval = FKO_CTX_INITIALIZED;
+    res = fko_set_spa_encryption_mode(ctx, FKO_DEFAULT_ENC_MODE);
+    ctx->initval = 0;
+    if(res != FKO_SUCCESS)
+    {
+        fko_destroy(ctx);
+        return res;
+    }
+
 #if HAVE_LIBGPGME
     /* Set gpg signature verify on.
     */
@@ -156,7 +168,8 @@ fko_new(fko_ctx_t *r_ctx)
  * and parsing the provided data into the context data.
 */
 int
-fko_new_with_data(fko_ctx_t *r_ctx, const char *enc_msg, const char *dec_key)
+fko_new_with_data(fko_ctx_t *r_ctx, const char *enc_msg,
+    const char *dec_key, int encryption_mode)
 {
     fko_ctx_t   ctx;
     int         res = FKO_SUCCESS; /* Are we optimistic or what? */
@@ -174,13 +187,23 @@ fko_new_with_data(fko_ctx_t *r_ctx, const char *enc_msg, const char *dec_key)
         return(FKO_ERROR_MEMORY_ALLOCATION);
     }
 
+    /* Default Encryption Mode (Rijndael in CBC mode)
+    */
+    ctx->initval = FKO_CTX_INITIALIZED;
+    res = fko_set_spa_encryption_mode(ctx, encryption_mode);
+    ctx->initval = 0;
+    if(res != FKO_SUCCESS)
+    {
+        fko_destroy(ctx);
+        return res;
+    }
+
     /* Consider it initialized here.
     */
     ctx->initval = FKO_CTX_INITIALIZED;
     FKO_SET_CTX_INITIALIZED(ctx);
 
-    /* If a decryption password is provided, go ahead and decrypt and
-     * decode.
+    /* If a decryption key is provided, go ahead and decrypt and decode.
     */
     if(dec_key != NULL)
     {
index 8c37f0a..8349e5f 100644 (file)
@@ -49,7 +49,8 @@ typedef enum {
     FKO_DIGEST_TYPE_MODIFIED    = 1 << 12,
     FKO_ENCRYPT_TYPE_MODIFIED   = 1 << 13,
     STATE_RESERVED_14           = 1 << 14,
-    FKO_BACKWARD_COMPATIBLE     = 1 << 15
+    FKO_BACKWARD_COMPATIBLE     = 1 << 15,
+    FKO_ENCRYPT_MODE_MODIFIED   = 1 << 16,
 } fko_state_flags_t;
 
 /* This is used in conjunction with the ctx->initial value as a means to
index 197c089..9dc8a2e 100644 (file)
@@ -40,7 +40,7 @@
 #include "fko_common.h"
 
 /* Other block sizes and key lengths are possible, but in the context of
- * the ssh protocols, 256 bits is the default. 
+ * the ssh protocols, 256 bits is the default.
  */
 #define RIJNDAEL_BLOCKSIZE 16
 #define RIJNDAEL_KEYSIZE   32
@@ -74,7 +74,7 @@ typedef struct {
  * bits).  If a value other than these three is specified, the key will be
  * truncated to the closest value less than the key size specified, e.g.
  * specifying 7 will use only the first 6 bytes of the key given.  DO NOT
- * PASS A VALUE LESS THAN 16 TO KEYSIZE! 
+ * PASS A VALUE LESS THAN 16 TO KEYSIZE!
  */
 void
 rijndael_setup(RIJNDAEL_context *ctx, size_t keysize, const uint8_t *key);
@@ -103,7 +103,7 @@ rijndael_encrypt(RIJNDAEL_context *context,
  *
  * Before this function can be used, rijndael_setup() must be used in order
  * to set up the key schedule required for the decryption algorithm.
- * 
+ *
  * This function always decrypts 16 bytes of ciphertext to 16 bytes of
  * plaintext.  The memory areas of the plaintext and the ciphertext can
  * overlap.
index 889d2f7..14eaa73 100644 (file)
@@ -132,6 +132,27 @@ add_acc_expire_time_epoch(fko_srv_options_t *opts, time_t *access_expire_time, c
     return;
 }
 
+/* Convert an encryption_mode string to its integer value.
+*/
+static int
+enc_mode_strtoint(const char *enc_mode_str)
+{
+    if(strcasecmp(enc_mode_str, "cbc") == 0)
+        return(FKO_ENC_MODE_CBC);
+    else if(strcasecmp(enc_mode_str, "ecb") == 0)
+        return(FKO_ENC_MODE_ECB);
+    else if(strcasecmp(enc_mode_str, "cfb") == 0)
+        return(FKO_ENC_MODE_CFB);
+    else if(strcasecmp(enc_mode_str, "pcbc") == 0)
+        return(-1);  /* not supported yet */
+    else if(strcasecmp(enc_mode_str, "ofb") == 0)
+        return(FKO_ENC_MODE_OFB);
+    else if(strcasecmp(enc_mode_str, "ctr") == 0)
+        return(FKO_ENC_MODE_CTR);
+    else
+        return(-1);
+}
+
 #if FIREWALL_IPTABLES
 static void
 add_acc_force_nat(fko_srv_options_t *opts, acc_stanza_t *curr_acc, const char *val)
@@ -166,7 +187,7 @@ add_acc_force_nat(fko_srv_options_t *opts, acc_stanza_t *curr_acc, const char *v
  * comparisons of incoming source IPs against this mask.
 */
 static void
-add_source_mask(acc_stanza_t *acc, const char *ip)
+add_source_mask(fko_srv_options_t *opts, acc_stanza_t *acc, const char *ip)
 {
     char                *ndx;
     char                ip_str[MAX_IPV4_STR_LEN] = {0};
@@ -181,7 +202,7 @@ add_source_mask(acc_stanza_t *acc, const char *ip)
         log_msg(LOG_ERR,
             "Fatal memory allocation error adding stanza source_list entry"
         );
-        exit(EXIT_FAILURE);
+        clean_exit(opts, NO_FW_CLEANUP, EXIT_FAILURE);
     }
 
     /* If this is not the first entry, we walk our pointer to the
@@ -228,13 +249,9 @@ add_source_mask(acc_stanza_t *acc, const char *ip)
         if(inet_aton(ip_str, &in) == 0)
         {
             log_msg(LOG_ERR,
-                "Error parsing IP to int for: %s", ip_str
+                "Fatal error parsing IP to int for: %s", ip_str
             );
-
-            free(new_sle);
-            new_sle = NULL;
-
-            return;
+            clean_exit(opts, NO_FW_CLEANUP, EXIT_FAILURE);
         }
 
         /* Store our mask converted from CIDR to a 32-bit value.
@@ -251,7 +268,7 @@ add_source_mask(acc_stanza_t *acc, const char *ip)
 /* Expand the access SOURCE string to a list of masks.
 */
 void
-expand_acc_source(acc_stanza_t *acc)
+expand_acc_source(fko_srv_options_t *opts, acc_stanza_t *acc)
 {
     char           *ndx, *start;
     char            buf[32];
@@ -268,7 +285,7 @@ expand_acc_source(acc_stanza_t *acc)
                 start++;
 
             strlcpy(buf, start, (ndx-start)+1);
-            add_source_mask(acc, buf);
+            add_source_mask(opts, acc, buf);
             start = ndx+1;
         }
     }
@@ -279,7 +296,7 @@ expand_acc_source(acc_stanza_t *acc)
         start++;
 
     strlcpy(buf, start, (ndx-start)+1);
-    add_source_mask(acc, buf);
+    add_source_mask(opts, acc, buf);
 }
 
 static int
@@ -596,7 +613,7 @@ expand_acc_ent_lists(fko_srv_options_t *opts)
     {
         /* Expand the source string to 32-bit integer masks foreach entry.
         */
-        expand_acc_source(acc);
+        expand_acc_source(opts, acc);
 
         /* Now expand the open_ports string.
         */
@@ -708,8 +725,14 @@ set_acc_defaults(fko_srv_options_t *opts)
 
         /* set default gpg keyring path if necessary
         */
-        if(acc->gpg_decrypt_pw != NULL && acc->gpg_home_dir == NULL)
-            add_acc_string(&(acc->gpg_home_dir), opts->config[CONF_GPG_HOME_DIR]);
+        if(acc->gpg_decrypt_pw != NULL)
+        {
+            if(acc->gpg_home_dir == NULL)
+                add_acc_string(&(acc->gpg_home_dir), opts->config[CONF_GPG_HOME_DIR]);
+        }
+
+        if (acc->encryption_mode == FKO_ENC_MODE_UNKNOWN)
+            acc->encryption_mode = FKO_DEFAULT_ENC_MODE;
 
         acc = acc->next;
     }
@@ -869,6 +892,16 @@ parse_access_file(fko_srv_options_t *opts)
         {
             add_acc_int(&(curr_acc->fw_access_timeout), val);
         }
+        else if(CONF_VAR_IS(var, "ENCRYPTION_MODE"))
+        {
+            if((curr_acc->encryption_mode = enc_mode_strtoint(val)) < 0)
+            {
+                fprintf(stderr,
+                    "[*] Unrecognized ENCRYPTION_MODE '%s', use {cbc,ecb}\n",
+                    val);
+                clean_exit(opts, NO_FW_CLEANUP, EXIT_FAILURE);
+            }
+        }
         else if(CONF_VAR_IS(var, "ENABLE_CMD_EXEC"))
         {
             add_acc_bool(&(curr_acc->enable_cmd_exec), val);
@@ -1005,7 +1038,6 @@ parse_access_file(fko_srv_options_t *opts)
     expand_acc_ent_lists(opts);
 
     /* Make sure default values are set where needed.
-     * a default value.
     */
     set_acc_defaults(opts);
 
index 4fe7ec4..3661227 100644 (file)
@@ -282,6 +282,7 @@ typedef struct acc_stanza
     acc_string_list_t   *gpg_remote_id_list;
     time_t              access_expire_time;
     int                 expired;
+    int                 encryption_mode;
     unsigned char       force_nat;
     char                *force_nat_ip;
     char                *force_nat_proto;
index 2c33192..254990e 100644 (file)
@@ -345,7 +345,8 @@ incoming_spa(fko_srv_options_t *opts)
         if(enc_type == FKO_ENCRYPTION_RIJNDAEL)
         {
             if(acc->key != NULL)
-                res = fko_new_with_data(&ctx, (char *)spa_pkt->packet_data, acc->key);
+                res = fko_new_with_data(&ctx,
+                    (char *)spa_pkt->packet_data, acc->key, acc->encryption_mode);
             else
             {
                 log_msg(LOG_ERR,
@@ -359,11 +360,12 @@ incoming_spa(fko_srv_options_t *opts)
         else if(enc_type == FKO_ENCRYPTION_GPG)
         {
             /* For GPG we create the new context without decrypting on the fly
-             * so we can set some  GPG parameters first.
+             * so we can set some GPG parameters first.
             */
             if(acc->gpg_decrypt_pw != NULL)
             {
-                res = fko_new_with_data(&ctx, (char *)spa_pkt->packet_data, NULL);
+                res = fko_new_with_data(&ctx, (char *)spa_pkt->packet_data, NULL,
+                        acc->encryption_mode);
                 if(res != FKO_SUCCESS)
                 {
                     log_msg(LOG_WARNING,
diff --git a/test/conf/cfb_mode_access.conf b/test/conf/cfb_mode_access.conf
new file mode 100644 (file)
index 0000000..9230298
--- /dev/null
@@ -0,0 +1,4 @@
+SOURCE: ANY;
+KEY: fwknoptest;
+FW_ACCESS_TIMEOUT:  3;
+ENCRYPTION_MODE: CFB;
diff --git a/test/conf/ctr_mode_access.conf b/test/conf/ctr_mode_access.conf
new file mode 100644 (file)
index 0000000..ba4e393
--- /dev/null
@@ -0,0 +1,4 @@
+SOURCE: ANY;
+KEY: fwknoptest;
+FW_ACCESS_TIMEOUT:  3;
+ENCRYPTION_MODE: CTR;
diff --git a/test/conf/ecb_mode_access.conf b/test/conf/ecb_mode_access.conf
new file mode 100644 (file)
index 0000000..a7b62ed
--- /dev/null
@@ -0,0 +1,4 @@
+SOURCE: ANY;
+KEY: fwknoptest;
+FW_ACCESS_TIMEOUT:  3;
+ENCRYPTION_MODE: ECB;
diff --git a/test/conf/invalid_source_access.conf b/test/conf/invalid_source_access.conf
new file mode 100644 (file)
index 0000000..10a72b5
--- /dev/null
@@ -0,0 +1,7 @@
+SOURCE :ANY;
+KEY: fwknoptest;
+FW_ACCESS_TIMEOUT:  3;
+
+SOURCE: ANY;
+KEY: fwknoptest2;
+FW_ACCESS_TIMEOUT:  3;
diff --git a/test/conf/ofb_mode_access.conf b/test/conf/ofb_mode_access.conf
new file mode 100644 (file)
index 0000000..2ebc238
--- /dev/null
@@ -0,0 +1,4 @@
+SOURCE: ANY;
+KEY: fwknoptest;
+FW_ACCESS_TIMEOUT:  3;
+ENCRYPTION_MODE: OFB;
diff --git a/test/test-coverage/iptables/zero_called_functions b/test/test-coverage/iptables/zero_called_functions
new file mode 100644 (file)
index 0000000..12b8077
--- /dev/null
@@ -0,0 +1,79 @@
+[+] TEST: [profile coverage] gcov profile coverage
+CMD: grep "called 0 returned" ../client/*.gcov
+../client/config_init.c.gcov:function digest_strtoint called 0 returned 0% blocks executed 0%
+../client/config_init.c.gcov:function proto_strtoint called 0 returned 0% blocks executed 0%
+../client/config_init.c.gcov:function create_fwknoprc called 0 returned 0% blocks executed 0%
+../client/config_init.c.gcov:function parse_rc_param called 0 returned 0% blocks executed 0%
+../client/fwknop.c.gcov:function show_last_command called 0 returned 0% blocks executed 0%
+../client/fwknop.c.gcov:function run_last_args called 0 returned 0% blocks executed 0%
+../client/getpasswd.c.gcov:function getpasswd called 0 returned 0% blocks executed 0%
+../client/http_resolve_host.c.gcov:function parse_url called 0 returned 0% blocks executed 0%
+../client/http_resolve_host.c.gcov:function resolve_ip_http called 0 returned 0% blocks executed 0%
+../client/spa_comm.c.gcov:function chksum called 0 returned 0% blocks executed 0%
+../client/spa_comm.c.gcov:function send_spa_packet_tcp_raw called 0 returned 0% blocks executed 0%
+../client/spa_comm.c.gcov:function send_spa_packet_icmp called 0 returned 0% blocks executed 0%
+../client/spa_comm.c.gcov:function send_spa_packet_http called 0 returned 0% blocks executed 0%
+../client/spa_comm.c.gcov:function write_spa_packet_data called 0 returned 0% blocks executed 0%
+../client/utils.c.gcov:function hex_dump called 0 returned 0% blocks executed 0%
+CMD: grep "called 0 returned" ../server/*.gcov
+../server/access.c.gcov:function acc_check_gpg_remote_id called 0 returned 0% blocks executed 0%
+../server/config_init.c.gcov:function config_entry_index called 0 returned 0% blocks executed 0%
+../server/extcmd.c.gcov:function run_extcmd_as called 0 returned 0% blocks executed 0%
+../server/fwknopd.c.gcov:function make_dir_path called 0 returned 0% blocks executed 0%
+../server/fwknopd.c.gcov:function daemonize_process called 0 returned 0% blocks executed 0%
+../server/log_msg.c.gcov:function set_log_facility called 0 returned 0% blocks executed 0%
+../server/replay_cache.c.gcov:function rotate_digest_cache_file called 0 returned 0% blocks executed 0%
+../server/tcp_server.c.gcov:function run_tcp_server called 0 returned 0% blocks executed 0%
+../server/utils.c.gcov:function hex_dump called 0 returned 0% blocks executed 0%
+CMD: grep "called 0 returned" ../lib/.libs/*.gcov
+../lib/.libs/digest.c.gcov:function digest_to_hex called 0 returned 0% blocks executed 0%
+../lib/.libs/digest.c.gcov:function md5_hex called 0 returned 0% blocks executed 0%
+../lib/.libs/digest.c.gcov:function md5_base64 called 0 returned 0% blocks executed 0%
+../lib/.libs/digest.c.gcov:function sha1 called 0 returned 0% blocks executed 0%
+../lib/.libs/digest.c.gcov:function sha1_hex called 0 returned 0% blocks executed 0%
+../lib/.libs/digest.c.gcov:function sha1_base64 called 0 returned 0% blocks executed 0%
+../lib/.libs/digest.c.gcov:function sha256_hex called 0 returned 0% blocks executed 0%
+../lib/.libs/digest.c.gcov:function sha384 called 0 returned 0% blocks executed 0%
+../lib/.libs/digest.c.gcov:function sha384_hex called 0 returned 0% blocks executed 0%
+../lib/.libs/digest.c.gcov:function sha384_base64 called 0 returned 0% blocks executed 0%
+../lib/.libs/digest.c.gcov:function sha512 called 0 returned 0% blocks executed 0%
+../lib/.libs/digest.c.gcov:function sha512_hex called 0 returned 0% blocks executed 0%
+../lib/.libs/digest.c.gcov:function sha512_base64 called 0 returned 0% blocks executed 0%
+../lib/.libs/fko_encryption.c.gcov:function fko_get_spa_encryption_type called 0 returned 0% blocks executed 0%
+../lib/.libs/fko_encryption.c.gcov:function fko_set_gpg_exe called 0 returned 0% blocks executed 0%
+../lib/.libs/fko_encryption.c.gcov:function fko_get_gpg_exe called 0 returned 0% blocks executed 0%
+../lib/.libs/fko_encryption.c.gcov:function fko_get_gpg_recipient called 0 returned 0% blocks executed 0%
+../lib/.libs/fko_encryption.c.gcov:function fko_get_gpg_signer called 0 returned 0% blocks executed 0%
+../lib/.libs/fko_encryption.c.gcov:function fko_get_gpg_home_dir called 0 returned 0% blocks executed 0%
+../lib/.libs/fko_encryption.c.gcov:function fko_get_gpg_signature_verify called 0 returned 0% blocks executed 0%
+../lib/.libs/fko_encryption.c.gcov:function fko_get_gpg_ignore_verify_error called 0 returned 0% blocks executed 0%
+../lib/.libs/fko_encryption.c.gcov:function fko_get_gpg_signature_fpr called 0 returned 0% blocks executed 0%
+../lib/.libs/fko_encryption.c.gcov:function fko_get_gpg_signature_id called 0 returned 0% blocks executed 0%
+../lib/.libs/fko_encryption.c.gcov:function fko_get_gpg_signature_summary called 0 returned 0% blocks executed 0%
+../lib/.libs/fko_encryption.c.gcov:function fko_get_gpg_signature_status called 0 returned 0% blocks executed 0%
+../lib/.libs/fko_encryption.c.gcov:function fko_gpg_signature_id_match called 0 returned 0% blocks executed 0%
+../lib/.libs/fko_encryption.c.gcov:function fko_gpg_signature_fpr_match called 0 returned 0% blocks executed 0%
+../lib/.libs/fko_funcs.c.gcov:function fko_set_spa_data called 0 returned 0% blocks executed 0%
+../lib/.libs/fko_message.c.gcov:function validate_cmd_msg called 0 returned 0% blocks executed 0%
+../lib/.libs/fko_server_auth.c.gcov:function fko_set_spa_server_auth called 0 returned 0% blocks executed 0%
+../lib/.libs/gpgme.h.gcov:function gpgme_err_code called 0 returned 0% blocks executed 0%
+../lib/.libs/gpgme_funcs.c.gcov:function process_sigs called 0 returned 0% blocks executed 0%
+../lib/.libs/sha1.c.gcov:function sha1_transform called 0 returned 0% blocks executed 0%
+../lib/.libs/sha1.c.gcov:function sha1_init called 0 returned 0% blocks executed 0%
+../lib/.libs/sha1.c.gcov:function sha1_update called 0 returned 0% blocks executed 0%
+../lib/.libs/sha1.c.gcov:function sha1_transform_and_copy called 0 returned 0% blocks executed 0%
+../lib/.libs/sha1.c.gcov:function sha1_final called 0 returned 0% blocks executed 0%
+../lib/.libs/sha2.c.gcov:function SHA256_End called 0 returned 0% blocks executed 0%
+../lib/.libs/sha2.c.gcov:function SHA256_Data called 0 returned 0% blocks executed 0%
+../lib/.libs/sha2.c.gcov:function SHA512_Init called 0 returned 0% blocks executed 0%
+../lib/.libs/sha2.c.gcov:function SHA512_Transform called 0 returned 0% blocks executed 0%
+../lib/.libs/sha2.c.gcov:function SHA512_Update called 0 returned 0% blocks executed 0%
+../lib/.libs/sha2.c.gcov:function SHA512_Last called 0 returned 0% blocks executed 0%
+../lib/.libs/sha2.c.gcov:function SHA512_Final called 0 returned 0% blocks executed 0%
+../lib/.libs/sha2.c.gcov:function SHA512_End called 0 returned 0% blocks executed 0%
+../lib/.libs/sha2.c.gcov:function SHA512_Data called 0 returned 0% blocks executed 0%
+../lib/.libs/sha2.c.gcov:function SHA384_Init called 0 returned 0% blocks executed 0%
+../lib/.libs/sha2.c.gcov:function SHA384_Update called 0 returned 0% blocks executed 0%
+../lib/.libs/sha2.c.gcov:function SHA384_Final called 0 returned 0% blocks executed 0%
+../lib/.libs/sha2.c.gcov:function SHA384_End called 0 returned 0% blocks executed 0%
+../lib/.libs/sha2.c.gcov:function SHA384_Data called 0 returned 0% blocks executed 0%
index 198f12e..389a268 100755 (executable)
@@ -4,6 +4,7 @@ use File::Copy;
 use File::Path;
 use IO::Socket;
 use Data::Dumper;
+use Cwd;
 use Getopt::Long 'GetOptions';
 use strict;
 
@@ -22,10 +23,15 @@ my $gpg_client_home_dir = "$conf_dir/client-gpg";
 my $nat_conf            = "$conf_dir/nat_fwknopd.conf";
 my $default_conf        = "$conf_dir/default_fwknopd.conf";
 my $default_access_conf = "$conf_dir/default_access.conf";
+my $ecb_mode_access_conf = "$conf_dir/ecb_mode_access.conf";
+my $ctr_mode_access_conf = "$conf_dir/ctr_mode_access.conf";
+my $cfb_mode_access_conf = "$conf_dir/cfb_mode_access.conf";
+my $ofb_mode_access_conf = "$conf_dir/ofb_mode_access.conf";
 my $expired_access_conf = "$conf_dir/expired_stanza_access.conf";
 my $future_expired_access_conf = "$conf_dir/future_expired_stanza_access.conf";
 my $expired_epoch_access_conf = "$conf_dir/expired_epoch_stanza_access.conf";
 my $invalid_expire_access_conf = "$conf_dir/invalid_expire_access.conf";
+my $invalid_source_access_conf = "$conf_dir/invalid_source_access.conf";
 my $force_nat_access_conf = "$conf_dir/force_nat_access.conf";
 my $dual_key_usage_access_conf = "$conf_dir/dual_key_usage_access.conf";
 my $gpg_access_conf     = "$conf_dir/gpg_access.conf";
@@ -86,7 +92,9 @@ my $valgrind_str = '';
 my $saved_last_results = 0;
 my $diff_mode = 0;
 my $enable_recompilation_warnings_check = 0;
+my $enable_profile_coverage_check = 0;
 my $sudo_path = '';
+my $gcov_path = '';
 my $platform = '';
 my $help = 0;
 my $YES = 1;
@@ -116,6 +124,7 @@ exit 1 unless GetOptions(
     'test-exclude=s'    => \$test_exclude,
     'exclude=s'         => \$test_exclude,  ### synonym
     'enable-recompile-check' => \$enable_recompilation_warnings_check,
+    'enable-profile-coverage-check' => \$enable_profile_coverage_check,
     'List-mode'         => \$list_mode,
     'enable-valgrind'   => \$use_valgrind,
     'valgrind-path=s'   => \$valgrindCmd,
@@ -646,6 +655,20 @@ my @tests = (
     {
         'category' => 'Rijndael SPA',
         'subcategory' => 'client+server',
+        'detail'   => 'invalid SOURCE (tcp/22 ssh)',
+        'err_msg'  => 'SPA packet accepted',
+        'function' => \&spa_cycle,
+        'cmdline'  => $default_client_args,
+        'fwknopd_cmdline'  => "LD_LIBRARY_PATH=$lib_dir $valgrind_str " .
+            "$fwknopdCmd -c $default_conf -a $invalid_source_access_conf " .
+            "-d $default_digest_file -p $default_pid_file $intf_str",
+        'server_positive_output_matches' => [qr/Fatal\serror\sparsing\sIP\sto\sint/],
+        'fw_rule_created' => $REQUIRE_NO_NEW_RULE,
+        'fatal'    => $NO
+    },
+    {
+        'category' => 'Rijndael SPA',
+        'subcategory' => 'client+server',
         'detail'   => 'expired stanza (tcp/22 ssh)',
         'err_msg'  => 'SPA packet accepted',
         'function' => \&spa_cycle,
@@ -949,6 +972,81 @@ my @tests = (
         'server_conf' => $nat_conf,
         'fatal'    => $NO
     },
+    {
+        'category' => 'Rijndael SPA',
+        'subcategory' => 'client+server',
+        'detail'   => 'ECB mode (tcp/22 ssh)',
+        'err_msg'  => 'could not complete SPA cycle',
+        'function' => \&spa_cycle,
+        'cmdline'  => "$default_client_args -M ecb",
+        'fwknopd_cmdline'  => "LD_LIBRARY_PATH=$lib_dir $valgrind_str " .
+            "$fwknopdCmd -c $default_conf -a $ecb_mode_access_conf " .
+            "-d $default_digest_file -p $default_pid_file $intf_str",
+        'server_negative_output_matches' => [qr/Decryption\sfailed/i],
+        'fw_rule_created' => $NEW_RULE_REQUIRED,
+        'fw_rule_removed' => $NEW_RULE_REMOVED,
+        'fatal'    => $NO
+    },
+    {
+        'category' => 'Rijndael SPA',
+        'subcategory' => 'client+server',
+        'detail'   => 'CFB mode (tcp/22 ssh)',
+        'err_msg'  => 'could not complete SPA cycle',
+        'function' => \&spa_cycle,
+        'cmdline'  => "$default_client_args -M cfb",
+        'fwknopd_cmdline'  => "LD_LIBRARY_PATH=$lib_dir $valgrind_str " .
+            "$fwknopdCmd -c $default_conf -a $cfb_mode_access_conf " .
+            "-d $default_digest_file -p $default_pid_file $intf_str",
+        'server_negative_output_matches' => [qr/Decryption\sfailed/i],
+        'fw_rule_created' => $NEW_RULE_REQUIRED,
+        'fw_rule_removed' => $NEW_RULE_REMOVED,
+        'fatal'    => $NO
+    },
+    {
+        'category' => 'Rijndael SPA',
+        'subcategory' => 'client+server',
+        'detail'   => 'CTR mode (tcp/22 ssh)',
+        'err_msg'  => 'could not complete SPA cycle',
+        'function' => \&spa_cycle,
+        'cmdline'  => "$default_client_args -M ctr",
+        'fwknopd_cmdline'  => "LD_LIBRARY_PATH=$lib_dir $valgrind_str " .
+            "$fwknopdCmd -c $default_conf -a $ctr_mode_access_conf " .
+            "-d $default_digest_file -p $default_pid_file $intf_str",
+        'server_negative_output_matches' => [qr/Decryption\sfailed/i],
+        'fw_rule_created' => $NEW_RULE_REQUIRED,
+        'fw_rule_removed' => $NEW_RULE_REMOVED,
+        'fatal'    => $NO
+    },
+    {
+        'category' => 'Rijndael SPA',
+        'subcategory' => 'client+server',
+        'detail'   => 'OFB mode (tcp/22 ssh)',
+        'err_msg'  => 'could not complete SPA cycle',
+        'function' => \&spa_cycle,
+        'cmdline'  => "$default_client_args -M ofb",
+        'fwknopd_cmdline'  => "LD_LIBRARY_PATH=$lib_dir $valgrind_str " .
+            "$fwknopdCmd -c $default_conf -a $ofb_mode_access_conf " .
+            "-d $default_digest_file -p $default_pid_file $intf_str",
+        'server_negative_output_matches' => [qr/Decryption\sfailed/i],
+        'fw_rule_created' => $NEW_RULE_REQUIRED,
+        'fw_rule_removed' => $NEW_RULE_REMOVED,
+        'fatal'    => $NO
+    },
+
+    {
+        'category' => 'Rijndael SPA',
+        'subcategory' => 'client+server',
+        'detail'   => 'mode mismatch (tcp/22 ssh)',
+        'err_msg'  => 'server accepted mismatch enc mode',
+        'function' => \&spa_cycle,
+        'cmdline'  => "$default_client_args -M ecb",
+        'fwknopd_cmdline'  => "LD_LIBRARY_PATH=$lib_dir $valgrind_str " .
+            "$fwknopdCmd -c $default_conf -a $default_access_conf " .
+            "-d $default_digest_file -p $default_pid_file $intf_str",
+        'server_positive_output_matches' => [qr/Decryption\sfailed/i],
+        'fw_rule_created' => $REQUIRE_NO_NEW_RULE,
+        'fatal'    => $NO
+    },
 
     {
         'category' => 'Rijndael SPA',
@@ -1254,6 +1352,14 @@ my @tests = (
         'function' => \&digest_cache_structure,
         'fatal'    => $NO
     },
+
+    {
+        'category' => 'profile coverage',
+        'detail'   => 'gcov profile coverage',
+        'err_msg'  => 'profile coverage failed',
+        'function' => \&profile_coverage,
+        'fatal'    => $NO
+    },
 );
 
 if ($use_valgrind) {
@@ -1490,7 +1596,8 @@ sub compile_warnings() {
 
     ### look for compilation warnings - something like:
     ###     warning: ‘test’ is used uninitialized in this function
-    return 0 if &file_find_regex([qr/\swarning:\s/, qr/gcc\:.*\sunused/], $current_test_file);
+    return 0 if &file_find_regex([qr/\swarning:\s/, qr/gcc\:.*\sunused/],
+        $current_test_file);
 
     ### the new binaries should exist
     unless (-e $fwknopCmd and -x $fwknopCmd) {
@@ -1505,6 +1612,32 @@ sub compile_warnings() {
     return 1;
 }
 
+sub profile_coverage() {
+
+    ### check for any *.gcno files - if they don't exist, then fwknop was
+    ### not compiled with profile support
+    unless (glob('../client/*.gcno') and glob('../server/*.gcno')) {
+        &write_test_file("[-] ../client/*.gcno and " .
+            "../server/*.gcno files do not exist.\n", $current_test_file);
+        return 0;
+    }
+
+    my $curr_dir = getcwd() or die $!;
+
+    ### gcov -b ../client/*.gcno
+    for my $dir ('../client', '../server', '../lib/.libs') {
+        next unless -d $dir;
+        chdir $dir or die $!;
+        system "$gcov_path -b -u *.gcno > /dev/null 2>&1";
+        chdir $curr_dir or die $!;
+
+        &run_cmd(qq|grep "called 0 returned" $dir/*.gcov|,
+                $cmd_out_tmp, $current_test_file);
+    }
+
+    return 1;
+}
+
 sub binary_exists() {
     my $test_hr = shift;
     return 0 unless $test_hr->{'binary'};
@@ -2540,6 +2673,10 @@ sub init() {
         push @tests_to_exclude, 'recompilation';
     }
 
+    unless ($enable_profile_coverage_check) {
+        push @tests_to_exclude, 'profile coverage';
+    }
+
     $sudo_path = &find_command('sudo');
 
     unless ((&find_command('cc') or &find_command('gcc')) and &find_command('make')) {
@@ -2547,6 +2684,19 @@ sub init() {
         push @tests_to_exclude, 'recompilation';
     }
 
+    $gcov_path = &find_command('gcov');
+
+    if ($gcov_path) {
+        if ($enable_profile_coverage_check) {
+            for my $extension ('*.gcov', '*.gcda') {
+                ### remove profile output from any previous run
+                system qq{find .. -name $extension | xargs rm 2> /dev/null};
+            }
+        }
+    } else {
+        push @tests_to_exclude, 'profile coverage';
+    }
+
     open UNAME, "uname |" or die "[*] Could not execute uname: $!";
     while (<UNAME>) {
         if (/linux/i) {