#!/usr/bin/perl -w use strict; use IO::Socket; use FileHandle; use ModToolkit qw( $bignumlib str2n n2str rsa_encode rsa_decode brand blind mod_inverse npretty ); # settings (only location of blindsig_server) my $blindsig_server = "localhost:12345"; my $crlf = "\015\012"; # read response from the server # adapt for continuation lines (both \-ending and ###- lines), and # any lists returned after 3xx replies. # returns a list of three elements: # (last ### status code, reference to all headers (without code), reference # to all lines after the reply, if any). sub readsrv { my $fh = shift; my $ret; my @hdr; my @list; my $readlist; my $continuation; my $more; while ( <$fh> ) { s/\s+$//; print STDERR "< $_\n"; if ( $continuation ) { s/^\s+//; $$continuation .= $_; if ( $$continuation =~ s/\\$// ) { next; } else { undef $continuation; next if $more || $readlist; return ($ret, \@hdr); } } # strip any \-continuation char, and remember we did so. # (be explicit about operating on $_) $continuation = $_ =~ s/\\$//; if ( $readlist ) { # reading list after 3xx return code... if ( $_ eq "." ) { return ($ret, \@hdr, \@list); } push @list, $_; # set continuation to point to last elem. $continuation &&= \$list[-1]; } elsif ( ! /^(\d\d\d)([- ])(.*)$/ ) { die "Invalid line from server: $_\n"; } else { $ret = $1; push @hdr, $3; $more = $2 eq "-"; $continuation &&= \$hdr[-1]; } next if $more; if ( $ret >= 300 && $ret <= 399 ) { # list follows... $readlist = 1; next; } return($ret, \@hdr); } die "Unexpected EOF\n"; } # send command, args to server sub tosrv { my $fh = shift; my $cmd = shift; my $line = uc $cmd; $line .= " " . join(" ", @_) if @_; print STDERR "> $line\n"; print $fh $line, $crlf; } # extract a key from the banner sub extract_key { my $r = shift; my %k; for my $line ( @$r ) { if ( $line =~ /^([ne]): (\+?\d+$)/ ) { $k{$1} = $bignumlib->new($2); } } ($k{n}, $k{e}); } # extract a challenge from the banner sub extract_challenge { my $r = shift; my($c) = grep { defined && length } map { /^Challenge: (.*)$/ && $1 } @$r; $c; } # ask login & password sub ask_loginpw { print STDERR "Login: "; my $login = ; die "You don't want to login? Ok...\n" unless defined $login; chomp $login; # try to disable echo via Term::ReadKey my $havereadkey; eval { require Term::ReadKey; import Term::ReadKey; }; if ( $@ ) { print STDERR "(sorry, your password will show up on screen!)\n"; } else { $havereadkey = 1; } print STDERR "Password: "; ReadMode(2) if $havereadkey; my $passwd = ; if ( $havereadkey ) { ReadMode(0); print STDERR "\n"; } die "No password\n" unless defined $passwd; chomp $passwd; ($login, $passwd); } ################### # start of main program # connect to server my $fh = new IO::Socket::INET PeerAddr => $blindsig_server or die "cannot connect to $blindsig_server: $!\n"; $fh->autoflush(1); print STDERR "Connected to ", $fh->peerhost, ":", $fh->peerport, "\n"; # read banner and public key my($ret, $banner) = readsrv($fh); # must be "200 blindsigd" $ret && $ret eq "200" && $banner->[0] =~ /^blindsigd/ or die "$blindsig_server says $banner->[0]!\n"; # extract public key... my($n, $e) = extract_key($banner); $e or die "Cannot get banner.\n"; # extract challenge my $challenge = extract_challenge($banner); # determine server "fingerprint" my $fingerprint = unpack("%32L*", n2str($n)); printf STDERR "Server \"fingerprint\" is %08x\n", $fingerprint; # authenticate to server print STDERR "Please log in to obtain your anonymous ticket\n"; my($login, $pw) = ask_loginpw; my $response = "$challenge $login:$pw"; tosrv( $fh, npretty('IAM ', rsa_encode($n, $e, $response), $crlf) ); ($ret) = readsrv($fh); $ret && $ret eq "200" or die "Authentication failed!\n"; # make sure we can write to a a fresh result file my $result_base = "ballot"; my $result_file = "$result_base.txt"; if ( -f $result_file ) { die "Aargh! $result_file already exists!\n"; } # open result file now, as failure to open and write that file is bad++ open BALLOT, ">$result_file" or die "Cannot open $result_file: $!\n"; # test-write 4k characters print BALLOT " " x 4096 or die "Cannot write $result_file: $!\n"; # close (and flush) to make sure we have enough diskspace close BALLOT or die "Cannot close $result_file: $!\n"; # open (and truncate) file again. open BALLOT, ">$result_file" or die "Cannot re-open $result_file: $!\n"; # prepare to send blind signatures tosrv($fh, 'BLINDSIG'); my $blindr; ($ret, $blindr) = readsrv($fh); $ret && $ret eq "201" or die "Invalid response to BLINDSIG command\n"; # get number of signatures my($num) = $blindr->[0] =~ /(\d+)/; # get format my $format = $blindr->[-1]; # create blinded messages my @blinded; my $two = $bignumlib->new(2); my $sixtyfourbit = $two ** 64; $num--; for ( 0 .. $num ) { # 64-bit random number my $brand = brand($sixtyfourbit); # create message my $msg = "Ballot number: " . substr("0" x 16 . unpack("H*", n2str($brand)), -16, 16); # verify message $msg =~ /$format/ or die "Created an illegal message: $msg\n"; # blind message, including blinding factors my @bmsg = ( $msg, blind($n, $e, str2n($msg)) ); # store push @blinded, \@bmsg; # send blinded message to server tosrv( $fh, npretty('', $bmsg[1], $crlf) ); } # we should receive info from the server now ($ret, $blindr) = readsrv($fh); $ret && $ret eq "202" or die "Invalid response after sending blinded messages.\n"; # get message number that server will sign my($willsign) = $blindr->[1] =~ /(\d+)/; defined $willsign or die "Invalid response in unblinded message response.\n"; # server expects all other unblinding factors for my $i ( 0 .. $num ) { next if $i eq $willsign; # send unblinding factor to server tosrv( $fh, npretty('', $blinded[$i]->[3], $crlf) ); } # server should now give us the signature my $sigs; ($ret, $blindr, $sigs) = readsrv($fh); $ret && $ret eq "300" or die "Invalid response after giving unblinding factors.\n"; @$sigs == 1 && $sigs->[0] =~ /^\Q$willsign\E: (.*)$/ or die "Invalid signature.\n"; my $blindedsig = $bignumlib->new($1); # unblind signature my $msg = $blinded[$willsign][0]; my $realsig = ($blindedsig * mod_inverse($blinded[$willsign][2], $n)) % $n; # verify the signature my $msg2 = rsa_decode($n, $e, $realsig); $msg2 eq $msg or die "Waa! Invalid signature!\n"; # YES! print "We got a valid signature!\n"; my $sig = npretty('Sig: ', $realsig); print "$msg\n$sig\n"; tosrv($fh, 'QUIT'); print BALLOT "$msg\n$sig\n"; close BALLOT;