added the ability to encrypt fwknop client plaintext data with openssl
authorMichael Rash <mbr@cipherdyne.org>
Mon, 6 Feb 2012 20:12:31 +0000 (15:12 -0500)
committerMichael Rash <mbr@cipherdyne.org>
Mon, 6 Feb 2012 20:12:31 +0000 (15:12 -0500)
extras/spa-entropy/spa-entropy.pl

index 0f375cd..24962c7 100755 (executable)
@@ -1,10 +1,33 @@
 #!/usr/bin/perl -w
+#
+# Author: Michael Rash <mbr@cipherdyne.org>
+#
 
 use MIME::Base64;
 use IPC::Open2;
 use Data::Password::Entropy;
+use Getopt::Long 'GetOptions';
 use strict;
 
+my $use_ent = 1;
+my $use_pw_entropy = 0;
+my $base64_decode = 1;
+my $no_base64_decode = 0;
+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 = 'ecb';
+my $spa_key_file = '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' => 0,
@@ -15,61 +38,50 @@ my %min_max_entropy = (
         'pos' => 0,
     }
 );
+
 my @encrypted_data = ();
+my @plaintext_data = ();
 my @cross_pkt_data = ();
 
-while (<STDIN>) {
-    next unless $_ =~ /\S/;
-    chomp;
-
-    push @encrypted_data, $_;
-    next;
-
-    my $base64_str = $_;
-    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;
-    }
-    push @encrypted_data, decode_base64($base64_str);
-}
-
-### calculate minimum length
-my $min_len = 0;
-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;
-        }
-    }
-}
-
-my $l_ctr = 0;
-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++;
-    }
-    $l_ctr++;
-}
-
-open F, "> entropy.dat" or die $!;
+Getopt::Long::Configure('no_ignore_case');
+die "[*] See '$0 -h' for usage information" unless (GetOptions(
+    'file-to-measure=s' => \$file_to_measure,
+    'no-base64-decode'  => \$no_base64_decode,
+    'count=i'           => \$packets,
+    'prefix=s'          => \$prefix,
+    'run-fwknop-client' => \$run_fwknop_client,
+    'enc-mode=s'        => \$enc_mode,
+    'lib-dir=s'         => \$lib_dir,
+    'Client-path=s'     => \$fwknop_client_path,
+    'use-pw-entropy'    => \$use_pw_entropy,
+    'use-openssl'       => \$use_openssl,
+    'openssl-salt=s'    => \$openssl_salt,
+    'openssl-mode=s'    => \$openssl_mode,
+    'help'              => \$help,
+));
+&usage() if $help;
+
+$base64_decode = 0 if $no_base64_decode;
+$use_ent = 0 if $use_pw_entropy;
+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\n";
+    print F "$pos $entropy   ### " . &hex_dump($str) . "\n";
 
     if ($min_max_entropy{'min'}{'val'} == 0
             and $min_max_entropy{'max'}{'val'} == 0) {
@@ -93,56 +105,209 @@ close F;
 
 my $min = $min_max_entropy{'min'}{'val'};
 my $max = $min_max_entropy{'max'}{'val'};
-print "Min entropy: $min at position: $min_max_entropy{'min'}{'pos'}\n";
-print "Max entropy: $max at position: $min_max_entropy{'max'}{'pos'}\n";
 
-open F, "> entropy.gnu" or die $!;
-print F <<_GNUPLOT_;
+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 = $_;
+                ### 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;
+                }
+                push @encrypted_data, decode_base64($base64_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 -M $enc_mode " .
+        "-B $file_to_measure -b -v --test 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 $!;
+    print F <<_GNUPLOT_;
 set title "entropy measurement"
 set terminal gif nocrop enhanced
-set output "entropy.gif"
+set output "$prefix.gif"
 set grid
-plot 'entropy.dat' using 1:2 with lines title 'min: $min, max: $max'
+plot '$prefix.dat' using 1:2 with lines title 'min: $min, max: $max'
 _GNUPLOT_
-close F;
+    close F;
 
-system "gnuplot entropy.gnu";
+    print "[+] Creating $prefix.gif gnuplot graph...\n\n";
+    system "gnuplot $prefix.gnu";
 
-exit 0;
+    return;
+}
 
 sub get_entropy() {
     my $data = shift;
 
-    #return password_entropy($data);
-
     my $entropy = '';
 
-    ### Entropy = 5.637677 bits per byte.
-#    system "echo -n $data | ent | grep Entropy > tmp.ent";
-#    open ENT, "< tmp.ent" or die $!;
-#    my $line = <ENT>;
-#    if ($line =~ /\s=\s(\d\S+)/) {
-#        $entropy = $1;
-#    }
-#    close ENT;
-#    return $entropy;
-
-    my $pid = open2(\*CHLD_OUT, \*CHLD_IN, 'ent');
-
-    print CHLD_IN $data;
-    close CHLD_IN;
-
-    while (<CHLD_OUT>) {
-        if (/Entropy\s=\s(\d\S+)/) {
-            $entropy = $1;
-            last;
+    if ($use_ent) {
+        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;
+        close CHLD_OUT;
 
-    waitpid( $pid, 0 );
-    my $child_exit_status = $? >> 8;
+        waitpid( $pid, 0 );
+        my $child_exit_status = $? >> 8;
+
+    } else {
+        $entropy = password_entropy($data);
+    }
 
     return $entropy;
 }
@@ -172,38 +337,40 @@ sub hex_dump() {
 
     my @chars = split //, $data;
     my $ctr = 0;
-    my $ascii_str = '';
+
+    my $hex_part   = '';
+    my $ascii_part = '';
+
     for my $char (@chars) {
-        if ($ctr % 16 == 0) {
-            print STDOUT " $ascii_str\n" if $ascii_str;
-            printf STDOUT "        0x%.4x:  ", $ctr;
-            $ascii_str = '';
-        }
-        printf STDOUT "%.2x", ord($char);
 
-        if ((($ctr+1) % 2 == 0) and ($ctr % 16 != 0)) {
-            print STDOUT ' ';
-        }
+        $hex_part .= sprintf "%.2x", ord($char);
 
         if ($char =~ /[^\x20-\x7e]/) {
-            $ascii_str .= '.';
+            $ascii_part .= '.';
         } else {
-            $ascii_str .= $char;
+            $ascii_part .= $char;
         }
         $ctr++;
     }
-    if ($ascii_str) {
-        my $remainder = 1;
-        if ($ctr % 16 != 0) {
-            $remainder = 16 - $ctr % 16;
-            if ($remainder % 2 == 0) {
-                $remainder = 2*$remainder + int($remainder/2) + 1;
-            } else {
-                $remainder = 2*$remainder + int($remainder/2) + 2;
-            }
-        }
-        print STDOUT ' 'x$remainder, $ascii_str;
+    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;
     }
-    print STDOUT "\n";
-    return;
+    if ($data =~ /=[^=]/) {
+        return 0;
+    }
+    return 1;
+}
+
+sub usage() {
+    print "$0 [options]\n";
+    exit 0;
 }