Source

p5-rdf-wot-validation / testing / trust.pl

Full commit
#!/usr/bin/perl

# Example - the GPG signature should fail, but the MD5SUM passes.
use Data::Dumper;
my $rdf = 'file:///Users/tai/trust/test.rdf';
my $v   = RDF::WOT::Verification->new($rdf);
print Dumper($v);


package RDF::WOT::Verification;

use strict;
use Carp;
use DateTime;
use Digest::MD5;
use Digest::SHA1;
use IPC::Open3;
use LWP::Simple;
use LWP::UserAgent;
use RDF::Redland;
use URI;

sub new
{
	my $klass = shift;
	my $uri   = shift;
	my $data  = shift;
	my $type  = shift || 'application/rdf+xml';
	
	unless ($data)
	{
		my $ua = LWP::UserAgent->new;
		$ua->timeout(10);
		$ua->env_proxy;
		my $response = $ua->get($uri);
		if ($response->is_success)
		{
			$data = $response->content;
			$type = $response->header('Content-Type');
			
			$type = 'application/rdf+xml'
				if $type =~ /text\/plain/ || !$type;
		}
		else
		{
			carp("Could not fetch data from $uri.\n");
		}
	}
	
	my $storage = RDF::Redland::Storage->new("hashes", "test", "new='yes',hash-type='memory'");
	my $model   = RDF::Redland::Model->new($storage, "");
	my $parser  = RDF::Redland::Parser->new(undef, $type);
	$parser->parse_string_into_model($data, RDF::Redland::URI->new($uri), $model);
	
	my $self  = {};
	
	$self->{'uri'}   = $uri;
	$self->{'data'}  = $data;
	$self->{'model'} = $model;
	
	bless $self, $klass;
	
	$self->check_all;
	
	return $self;
}

sub check_all
{
	my $self = shift;
	
	my $query = sprintf("SELECT ?a WHERE {<%s> <%s> ?a . }",
		$self->{'uri'},
		'http://xmlns.com/wot/0.1/assurance');
	my $qh = RDF::Redland::Query->new($query, undef, undef, 'sparql');
	my $qr = $qh->execute($self->{'model'});
	
	while(!$qr->finished)
	{
		my $assurance = $qr->binding_value_by_name('a');
		my $assurance_uri = $assurance->uri->as_string;
		$self->check_uri($assurance_uri);
		$qr->next_result;
	}
	
	return $self;
}

sub check_uri
{
	my $self   = shift;
	my $siguri = shift;
	my ($sig, $type);

	my $ua = LWP::UserAgent->new;
	$ua->timeout(10);
	$ua->env_proxy;
	my $response = $ua->get($siguri);
	if ($response->is_success)
	{
		$sig  = $response->content;
		$type = $response->header('Content-Type');
	}
	else
	{
		return $self->check_sig_unknown($siguri, $sig, 'HTTP Failure');
	}

	if ($sig =~ /-----BEGIN PGP SIGNATURE-----/)
	{
		return $self->check_sig_pgp($siguri, $sig);
	}
	elsif ($siguri =~ /(\/MD5SUMS?|\.MD5)$/)
	{
		return $self->check_sig_hash($siguri, $sig, 'MD5')
	}
	elsif ($siguri =~ /(\/SHA1SUMS?|\.SHA1?)$/)
	{
		return $self->check_sig_hash($siguri, $sig, 'SHA1')
	}
	else
	{
		return $self->check_sig_unknown($siguri, $sig, 'Unrecognised Data');
	}
}

sub check_sig_unknown
{
	my $self   = shift;
	my $siguri = shift;
	my $sig    = shift;
	my $reason = shift;
	
	$self->{'signatures'}->{$siguri} = {
			'signature_data' => $sig ,
			'signature_uri'  => $siguri ,
			'signature_type' => 'unknown',
			'status'         => 0 ,
			'reason'         => $reason ,
		};
}

sub check_sig_pgp
{
	my $self   = shift;
	my $siguri = shift;
	my $sig    = shift;
	my $status = 0;
	
	my $fdata = '/tmp/RDF-WOT-Verification.data';
	my $fsig  = '/tmp/RDF-WOT-Verification.sig';
	
	open TMPDATA, ">$fdata";
	print TMPDATA $self->{'data'};
	close TMPDATA;
	open TMPSIG, ">$fsig";
	print TMPSIG $sig;
	close TMPSIG;

	my ($wtr, $rdr, $err);
	my $pid = open3($wtr, $rdr, $err,
		'/usr/local/bin/gpg', '--verify',
		'--no-tty', '--status-fd', '2', '--command-fd',  0,
		$fsig, $fdata);
	my $gpg = {};
	while ($_ = (<$err> || <$rdr>))
	{
		chomp;

		if (/^\[GNUPG:\] SIG_ID (\S+) (\S+) (\S+)$/)
		{
			$gpg->{'timestamp'} = $3;
			$gpg->{'datetime'}  = DateTime->from_epoch( epoch => $gpg->{'timestamp'} );
		}
		elsif (/^\[GNUPG:\] (GOOD|BAD)SIG (\S+) (.*)$/)
		{
			$gpg->{'signature_status'} = $1;
			$gpg->{'signature_key'}    = $2;
			$gpg->{'signer'}           = $3;
			
			$status = ($gpg->{'signature_status'} eq 'GOOD') ? 1 : -1;
		}
		elsif (/^\[GNUPG:\] VALIDSIG (\S+)/)
		{
			$gpg->{'signature_key_fingerprint'} = $1;
		}
		elsif (/^\[GNUPG:\] TRUST_(\S+)/)
		{
			$gpg->{'signature_key_trust'} = $1;
		}		
	}
	waitpid($pid, 0);
	unlink $fdata, $fsig;

	$self->{'signatures'}->{$siguri} = {
			'signature_data' => $sig ,
			'signature_uri'  => $siguri ,
			'signature_type' => 'gpg',
			'status'         => $status ,
			'gpg'            => $gpg ,
		};
}

sub check_sig_hash
{
	my $self   = shift;
	my $siguri = shift;
	my $sig    = shift;
	my $hash   = shift || 'MD5';
	
	my $rdfuri = URI->new($self->{'uri'});
	my $asserted_hash;
	
	my @lines = split /\r?\n/, $sig;
	LINELOOP: foreach (@lines)
	{
		my ($sum, $uri) = split /\s+/;
		
		my $absolute;
		if ($uri =~ /:/)
			{ $absolute = URI->new($uri); }
		else
		 	{ $absolute = URI->new_abs($uri, $siguri); }

		if ($absolute->eq($rdfuri))
		{
			$asserted_hash = lc($sum);
			last LINELOOP;
		}
	}
	
	if (! $asserted_hash)
	{
		return $self->check_sig_unknown($siguri, $sig, 'No relevent data found.');
	}
	
	my $real_hash;
	if ($hash eq 'MD5')
		{ $real_hash = lc( Digest::MD5::md5_hex($self->{'data'}) ); }
	elsif ($hash eq 'SHA1')
		{ $real_hash = lc( Digest::SHA1::sha1_hex($self->{'data'}) ); }
	
	my $status = ($real_hash eq $asserted_hash) ? 1 : -1;
	
	$self->{'signatures'}->{$siguri} = {
			'signature_data' => $sig ,
			'signature_uri'  => $siguri ,
			'signature_type' => lc($hash),
			'status'         => $status ,
			'hash'           => $real_hash ,
		};
}

sub signatures
{
	my $self   = shift;
	my $siguri = shift;
	return keys %{ $self->{'signatures'} };
}

sub signature_status
{
	my $self   = shift;
	my $siguri = shift;
	return $self->{'signatures'}->{$siguri}->{'status'};
}

sub signature_type
{
	my $self   = shift;
	my $siguri = shift;
	return $self->{'signatures'}->{$siguri}->{'signature_type'};
}

1;