#!/usr/bin/perl -w
use Capture::Tiny qw(capture);
use File::Copy;
use File::Glob ':bsd_glob';
use File::Temp qw(tempdir);
use Getopt::Long;
use IO::Socket qw(AF_INET SOCK_STREAM SHUT_RD);
use strict;

my ($stdout, $stderr, $exited);
my (%conf, $dir, $cert_file, @pkts, $valid_cert);

# Defaults
%conf = (debug => 0,
	 attest_port => 3333,
	 attest_ip => '10.0.3.250');
GetOptions(\%conf, 'debug', 'file=s', 'attest_port=i', 'attest_ip=s');

$dir = tempdir(CLEANUP => 1);
$cert_file = join('/', $dir, 'cert.pgp');
move($conf{file}, $cert_file);
chdir($dir);

# Certificates always have to be split, checked and joined, as they
# will always bear a signature (even if it's a self-sig).
($stdout, $stderr, $exited) = capture {system qw(sq packet split), $cert_file};
die "Could not split packets from $cert_file" unless $exited == 0;

@pkts = valid_pkts_from_cert();
$valid_cert = join_pkts(@pkts);
print $valid_cert;
exit 0;

# Filter the packets in the certificate, returning only the (indexes
# of) those we consider valid.
sub valid_pkts_from_cert {
    # "sq packet dump" tells us what is the nature of each of the
    # packets. We then assemble the validated certificate with:
    # - All Public-Key or Public-Subkeys packets
    # - All Signature packets where the Issuer Fingerprint is the same of any
    #   of the cert's Public-Key's Fingerprint.
    #
    #   *To check:* Should we also allow for signing with public-subkey
    #   fingerprints?
    # - All Signature packets that are part of the list of attestations
    # - Packets that are _not_ signature packets are also passed without
    #   further validation.
    my ($cert_data, $pkt_idx, @pkts, $fpr, @attestations);

    ($stdout, $stderr, $exited) = capture {system qw(sq packet dump), $cert_file};
    die "Could not dump packets in $cert_file" unless $exited == 0;
    $cert_data = $stdout;
    @attestations = parse_attest_list();
    debug("Parsed attestations:", @attestations);

    $pkt_idx = 0;
    for my $pkt (split(/\n(?=\S)/, $cert_data)) {
	$pkt =~ /^(.+) Packet/;
	my $pkt_type = $1;
	debug("Packet $pkt_idx: $pkt_type");
	# Find this key's fingerprint: If @pkts is empty, this is the
	# first packet we review, and its fingerprint is the master
	# key fingerprint.
	if (scalar @pkts == 0 and $pkt_type eq 'Public-Key' and
	    $pkt =~ /\n\s+Fingerprint: ([\dABCDEF]{40})\n/) {
	    $fpr = $1;
	    debug('Fingerprint:', $fpr);
	}

	if ($pkt_type eq 'Signature') {
	    if ($pkt =~ /\n\s+Issuer Fingerprint: ([\dABCDEF]{40})\n/) {
		my $cert_fpr = $1;
		if (defined($fpr) and $cert_fpr eq $fpr) {
		    # Self-sig
		    push(@pkts, $pkt_idx);
		} elsif (scalar(@attestations) > 0 and
			 grep {$_ eq $cert_fpr} @attestations) {
		    debug("Packet $pkt_idx properly attested 😃");
		    # Attested signature
		    push(@pkts, $pkt_idx);
		} else {
		    # Discard this packet
		    debug("Discarding non-attested $pkt_type packet $pkt_idx");
		}
	    }
	} else {
	    push(@pkts, $pkt_idx);
	}
	$pkt_idx += 1;
    }
    debug(scalar(@pkts), " valid packets: ", join(', ', @pkts));
    return @pkts;
}

# Once we know which packets we are to keep from the certificate we
# are validating, join it into a new certificate.
#
# Returns the resulting ASCII-armored certificate.
sub join_pkts {
    my (@pkts, @cmd);
    @pkts = @_;
    @cmd = ('sq', 'packet', 'join');
    for my $pkt (@pkts) {
	my $file = bsd_glob("${cert_file}-${pkt}--*");
	debug("File for $pkt: $file");
	push(@cmd, $file);
    }
    ($stdout, $stderr, $exited) = capture { system(@cmd) };
    die 'Could not join packets ', join(', ', @pkts) unless $exited == 0;
    return $stdout;
}

# Returns the list of attested certifications this cert has.
sub parse_attest_list {
    my (@lines, @atts, $att_hlp, $att_res, $num, $fh, $cert);

    # To send the certificate to be checked to the server, we read it
    # from the file
    open ($fh, '<', $cert_file);
    $cert = join("\n", <$fh>);
    close($fh);

    # Create the client socket to the predefined server
    $att_hlp = IO::Socket->new(
	Domain => AF_INET,
	Type => SOCK_STREAM,
	proto => 'tcp',
	PeerPort => $conf{attest_port},
	PeerHost => $conf{attest_ip}
	);
    if (! $att_hlp) {
	warn "Could not connect to attestation helper: #IO::Socket::errstr";
	return undef;
    }

    # Send the certificate ($cert) and receive the results ($att_res)
    $att_hlp->send($cert);
    $att_hlp->shutdown(SHUT_RD);
    $att_hlp->recv($att_res, 1024);
    $att_hlp->close;
 
    # Split the results, looking for the attested key signatures
    @lines = split(/\n/, $att_res);
    if (scalar(@lines) >= 1 and $lines[0] =~ /^(\d+) certification/) {
	$num = $1;
    } else {$num = 0;}

    # No attestations found.
    return if (!defined($num) or $num eq 0);
    for my $lin (@lines) {
	$lin =~ /^\s+([\dABCDEF]{40})/ and push @atts, $1;
    }

    return @atts;
}

sub debug {
    return unless $conf{debug};
    print STDERR @_,"\n";
}
