Commits

Toby Inkster committed 1426761

PLaying with x.509 signing of graphs.

Comments (0)

Files changed (3)

+#!/usr/bin/perl
+
+use common::sense;
+use Cwd 'abs_path';
+use File::Slurp 'read_file';
+use Getopt::Long;
+use RDF::Trine 0.112;
+
+my ($hash, $hex, $verify, $sign, $debug, $base, $fmt) = ('sha1');
+
+GetOptions(
+	"hash|H=s"   => \$hash ,
+	"hex|X"      => \$hex ,
+	"verify|v=s" => \$verify ,
+	"sign|s=s"   => \$sign ,
+	"debug"      => \$debug ,
+	"base|B=s"   => \$base ,
+	"format|F=s" => \$fmt ,
+	"help|h"     => \&helps ,
+	);
+
+my $input  = shift @ARGV || die &usage;
+my $key    = shift @ARGV || die &usage;
+
+$base = 'file://' . abs_path($input)
+	unless $base;
+
+die "Hashes are md2,md4,md5,sha1,sha224,sha256,sha384,sha512,mdc2,ripemd160\n"
+	unless $hash =~ /^(md2|md4|md5|sha1|sha224|sha256|sha384|sha512|mdc2|ripemd160)$/;
+
+if ($sign && $verify)
+{
+	die "Cannot sign and verify at the same time.\n";
+}
+
+my $data   = read_file($input);
+
+my $parser;
+$parser = RDF::Trine::Parser::RDFXML->new
+	if $fmt =~ /xml/i && !defined $parser;
+$parser = RDF::Trine::Parser::Turtle->new
+	if $fmt =~ /(triple|turtle|n3|nt)/i && !defined $parser;
+$parser = RDF::Trine::Parser::RDFJSON->new
+	if $fmt =~ /(json)/i && !defined $parser;
+$parser = RDF::Trine::Parser::RDFXML->new
+	if $data =~ /<rdf:RDF/ && !defined $parser;
+$parser = RDF::Trine::Parser::Turtle->new
+	if $data =~ /^(#|_:|<http:|\[\]\s)/ && !defined $parser;
+$parser = RDF::Trine::Parser::RDFJSON->new
+	if $data =~ /^[\s\r\n]*\{/ && !defined $parser;
+$parser = RDF::Trine::Parser::RDFXML->new
+	unless $parser;
+
+if ($debug && !$fmt)
+{
+	print STDERR "DEBUG: Guessed RDF parser - ".ref($parser)."\n";
+}
+
+my $model = RDF::Trine::Model->new( RDF::Trine::Store->temporary_store );
+$parser->parse_into_model($base, $data, $model);
+
+my $canon_ser = RDF::Trine::Serializer::NTriples::Canonical->new(onfail=>'truncate');
+my $tempfile  = $input . '.CANONICAL';
+open TEMPFILE, ">$tempfile";
+$canon_ser->serialize_model_to_file(*TEMPFILE, $model);
+close TEMPFILE;
+
+$hex = '-hex -c' if defined $hex && length $hex;
+my $cmd;
+
+if ($verify)
+{
+	$cmd = "openssl dgst $hex -$hash -verify '$key' -signature '$verify' '$tempfile'";
+}
+else
+{
+	$sign = "-out '$sign'" if defined $sign && length $sign;
+	$cmd = "openssl dgst $hex -$hash -sign '$key' $sign '$tempfile'";
+}
+
+print STDERR "DEBUG: Running `$cmd`\n" if $debug;
+my $output = `$cmd`;
+
+print $output;
+
+if ($debug)
+{
+	print STDERR "DEBUG: Leaving temporary file $tempfile in place.\n";
+}
+else
+{
+	unlink $tempfile;
+}
+
+exit 1 if $verify && $output ne 'Verified OK';
+exit;
+
+sub usage
+{
+	return "usage: rdf-sig [options] xml_file.rdf privatekey.pem\n" 
+		. "   or: rdf-sig [options] --verify=signature.sig xml_file.rdf publickey.pem\n";
+}
+
+sub helps
+{
+	print &usage . "\n";
+	print <<HELP;
+Options:
+
+  --base=URI         Use URI as base for relative URIs.
+  --debug            Switch debugging on.
+  --format=FMT       RDF serialisation. By default, guess.
+  --hash=HASH,       Use HASH hash instead of SHA1.
+  --help             Show this help.
+  --hex              Use hexadecimal encoding.
+  --sign=SIGFILE     Output to SIGFILE instead of STDOUT.
+  --verify=SIGFILE   Don't sign, but verify.
+
+HELP
+	exit 11;
+}
+
+package RDF::Trine::Serializer::NTriples::Canonical;
+
+use 5.008001;
+use strict;
+
+use Carp;
+use RDF::Trine;
+
+our @ISA = qw();
+
+our $VERSION = '0.01';
+
+sub new
+{
+	my $class = shift;
+	my %opts;
+	
+	while (@_)
+	{
+		my $field = lc shift;
+		my $value = shift;
+		$opts{$field} = $value;
+	}
+	
+	return bless \%opts, $class;
+}
+
+sub serialize_model_to_file
+{
+	my $self  = shift;
+	my $file  = shift;
+	my $model = shift;
+	
+	my $string = $self->serialize_model_to_string($model);
+	print {$file} $string;
+}
+
+sub serialize_model_to_string
+{
+	my $self  = shift;
+	my $model = shift;
+	
+	my $blankNodes = {};
+	my @statements;
+	
+	my $stream = $model->as_stream;
+	while (my $ST = $stream->next)
+	{
+		push @statements, { 'trine' => $ST };
+		
+		if ($ST->subject->is_blank)
+		{
+			$blankNodes->{ $ST->subject->blank_identifier }->{'trine'} = $ST->subject;
+		}
+		
+		if ($ST->object->is_blank)
+		{
+			$blankNodes->{ $ST->object->blank_identifier }->{'trine'} = $ST->object;
+		}
+	}
+	
+	my %lexCounts;
+	
+	foreach my $st (@statements)
+	{
+		# Really need to canonicalise typed literals as per XSD.
+		
+		$st->{'lex'} = sprintf('%s %s %s',
+			($st->{'trine'}->subject->is_blank ? '~' : $st->{'trine'}->subject->sse),
+			$st->{'trine'}->predicate->sse,
+			($st->{'trine'}->object->is_blank ? '~' : $st->{'trine'}->object->sse)
+			);
+		$lexCounts{ $st->{'lex'} }++;
+	}
+
+	my $blankNodeCount   = scalar keys %$blankNodes;
+	my $blankNodeLength  = length "$blankNodeCount";
+	my $blankNodePattern = '_:g%0'.$blankNodeLength.'d';
+	my $hardNodePattern  = '_:h%0'.$blankNodeLength.'d';
+	
+	@statements = sort { $a->{'lex'} cmp $b->{'lex'} } @statements;
+	
+	my $genSymCounter = 1;
+	
+	foreach my $st (@statements)
+	{
+		next unless $lexCounts{ $st->{'lex'} } == 1;
+		
+		if ($st->{'trine'}->object->is_blank)
+		{
+			unless (defined $blankNodes->{ $st->{'trine'}->object->blank_identifier }->{'lex'})
+			{
+				$blankNodes->{ $st->{'trine'}->object->blank_identifier }->{'lex'} =
+					sprintf($blankNodePattern, $genSymCounter);
+				$genSymCounter++;
+			}
+			my $b = $blankNodes->{ $st->{'trine'}->object->blank_identifier }->{'lex'};
+			$st->{'lex'} =~ s/\~$/$b/;
+		}
+		
+		if ($st->{'trine'}->subject->is_blank)
+		{
+			unless (defined $blankNodes->{ $st->{'trine'}->subject->blank_identifier }->{'lex'})
+			{
+				$blankNodes->{ $st->{'trine'}->subject->blank_identifier }->{'lex'} =
+					sprintf($blankNodePattern, $genSymCounter);
+				$genSymCounter++;
+			}
+			my $b = $blankNodes->{ $st->{'trine'}->subject->blank_identifier }->{'lex'};
+			$st->{'lex'} =~ s/^\~/$b/;
+		}
+	}
+	
+	foreach my $st (@statements)
+	{
+		if ($st->{'lex'} =~ /\~$/)
+		{
+			if (defined $blankNodes->{ $st->{'trine'}->object->blank_identifier }->{'lex'})
+			{
+				my $b = $blankNodes->{ $st->{'trine'}->object->blank_identifier }->{'lex'};
+				$st->{'lex'} =~ s/\~$/$b/;
+			}
+		}
+		
+		if ($st->{'lex'} =~ /^\~/)
+		{
+			if (defined $blankNodes->{ $st->{'trine'}->subject->blank_identifier }->{'lex'})
+			{
+				my $b = $blankNodes->{ $st->{'trine'}->subject->blank_identifier }->{'lex'};
+				$st->{'lex'} =~ s/^\~/$b/;
+			}
+		}
+	}
+	
+	@statements = sort { $a->{'lex'} cmp $b->{'lex'} } @statements;
+	
+	my @canonicalStatements;
+	my @otherStatements;
+	foreach my $st (@statements)
+	{
+		if ($st->{'lex'} =~ /(^\~)|(\~$)/)
+		{
+			if (lc $self->{'onfail'} eq 'die')
+			{
+				croak "Model could not be canonicalised";
+			}
+			elsif (lc $self->{'onfail'} eq 'truncate')
+			{
+				next;
+			}
+			
+			if ($st->{'lex'} =~ /\~$/)
+			{
+				unless (defined $blankNodes->{ $st->{'trine'}->object->blank_identifier }->{'lex'})
+				{
+					$blankNodes->{ $st->{'trine'}->object->blank_identifier }->{'lex'} =
+						sprintf($hardNodePattern, $genSymCounter);
+					$genSymCounter++;
+				}
+				my $b = $blankNodes->{ $st->{'trine'}->object->blank_identifier }->{'lex'};
+				$st->{'lex'} =~ s/\~$/$b/;
+			}
+
+			if ($st->{'lex'} =~ /^\~/)
+			{
+				unless (defined $blankNodes->{ $st->{'trine'}->subject->blank_identifier }->{'lex'})
+				{
+					$blankNodes->{ $st->{'trine'}->subject->blank_identifier }->{'lex'} =
+						sprintf($hardNodePattern, $genSymCounter);
+					$genSymCounter++;
+				}
+				my $b = $blankNodes->{ $st->{'trine'}->subject->blank_identifier }->{'lex'};
+				$st->{'lex'} =~ s/^\~/$b/;
+			}
+
+			push @otherStatements, $st;
+		}
+		else
+		{
+			push @canonicalStatements, $st;
+		}
+	}
+	
+	my $rv = '';
+	foreach my $st (@canonicalStatements)
+	{
+		$rv .= $st->{'lex'} . " .\r\n";
+	}
+
+	$rv .= "\r\n"
+		if lc $self->{'onfail'} eq 'space';
+	
+	foreach my $st (@otherStatements)
+	{
+		$rv .= $st->{'lex'} . " .\r\n";
+	}
+
+	return $rv;
+}
+
+1;

testing/test-x509.rdf

+<rdf:RDF
+	xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+	xmlns:wot="http://xmlns.com/wot/0.1/"
+	xmlns="http://xmlns.com/foaf/0.1/">
+
+	<Document rdf:about="">
+		<wot:assurance rdf:resource="test-x509.sig" />
+	</Document>
+
+</rdf:RDF>

testing/test-x509.sig

+d�����튾/�,Gu�o.��sz?J�