Commits

Toby Inkster  committed bd3992f

s/Web::Id/Web::ID/gg; minor documentation improvements

  • Participants
  • Parent commits cfceaa0

Comments (0)

Files changed (33)

File devel.plack-experiments/auth-webid/simple.psgi

 use Data::Dumper;
 
 my $cache = Cache::MemoryCache->new({
-	namespace          => 'WebId',
+	namespace          => 'WebID',
 	default_expires_in => 600,
 });
 
 
 builder
 {
-	enable "Auth::WebId",
+	enable "Auth::WebID",
 		cache            => $cache,
 		no_object_please => 1;
 	$app;

File examples/certificate-generation.pl

-use Web::Id::Certificate::Generator;
+use Web::ID::Certificate::Generator;
 
-Web::Id::Certificate->generate(
+Web::ID::Certificate->generate(
 	passphrase        => 'test1234',
 	subject_alt_names => [
-		Web::Id::SAN::URI->new(value => 'http://example.com/id/alice'),
-		Web::Id::SAN::URI->new(value => 'http://example.net/id/alice'),
+		Web::ID::SAN::URI->new(value => 'http://example.com/id/alice'),
+		Web::ID::SAN::URI->new(value => 'http://example.net/id/alice'),
 		],
 	cert_output       => \(my $output),
 	rdf_output        => \(my $model),

File examples/certificate-parsing.pl

 use lib "lib";
 use Data::Dumper;
 #use Moose ();
-use Web::Id::Certificate;
-use Web::Id::SAN;
-use Web::Id::SAN::Email;
-use Web::Id::SAN::URI;
+use Web::ID::Certificate;
+use Web::ID::SAN;
+use Web::ID::SAN::Email;
+use Web::ID::SAN::URI;
 
-my $cert = Web::Id::Certificate->new( pem => <<PEM );
+my $cert = Web::ID::Certificate->new( pem => <<PEM );
 -----BEGIN CERTIFICATE-----
 MIIDwjCCAyugAwIBAgIBADANBgkqhkiG9w0BAQUFADCBiDELMAkGA1UEBhMCR0Ix
 FDASBgNVBAgTC0Vhc3QgU3Vzc2V4MQ4wDAYDVQQHEwVMZXdlczEVMBMGA1UEChMM

File examples/fingerpoint-test.pl

 use 5.010;
-use Web::Id::SAN::Email;
+use Web::ID::SAN::Email;
 use RDF::Trine;
 
-my $san = Web::Id::SAN::Email->new(
+my $san = Web::ID::SAN::Email->new(
 	type     => 'rfc822Address',
 	value    => 'somebody@fingerpoint.tobyinkster.co.uk',
 	);

File examples/web-id-validation.pl

 use lib "lib";
 use Data::Dumper;
 #use Moose ();
-use Web::Id;
+use Web::ID;
 
-my $id = Web::Id->new( certificate => <<PEM );
+my $id = Web::ID->new( certificate => <<PEM );
 -----BEGIN CERTIFICATE-----
 MIIDwjCCAyugAwIBAgIBADANBgkqhkiG9w0BAQUFADCBiDELMAkGA1UEBhMCR0Ix
 FDASBgNVBAgTC0Vhc3QgU3Vzc2V4MQ4wDAYDVQQHEwVMZXdlczEVMBMGA1UEChMM

File lib/Plack/Middleware/Auth/WebId.pm

-package Plack::Middleware::Auth::WebId;
+package Plack::Middleware::Auth::WebID;
 
 use strict;
 use base qw(Plack::Middleware);
 	$self->certificate_env_key('SSL_CLIENT_CERT')
 		unless defined $self->certificate_env_key;
 	
-	$self->webid_class('Web::Id')
+	$self->webid_class('Web::ID')
 		unless defined $self->webid_class;
 	
 	$self->on_unauth($default_unauth)
 		unless defined $self->on_unauth;
 	
-	load_class('Web::Id');
+	load_class('Web::ID');
 }
 
 sub call
 	# I know what you're thinking... what's the point in caching these
 	# objects, if we're already constructed it above?!
 	#
-	# Well, much of the heavy work for Web::Id is done in lazy builders.
+	# Well, much of the heavy work for Web::ID is done in lazy builders.
 	# If we return a cached copy of the object, then we avoid running
 	# those builders again.
 	#
 
 =head1 NAME
 
-Plack::Middleware::Auth::WebId - authentication middleware for WebId
+Plack::Middleware::Auth::WebID - authentication middleware for WebID
 
 =head1 SYNOPSIS
 
   
   builder
   {
-    enable "Auth::WebId",
+    enable "Auth::WebID",
         cache     => $cache,
         on_unauth => \&unauthenticated;
     $app;
 
 =head1 DESCRIPTION
 
-Plack::Middleware::Auth::WebId is a WebId handler for Plack.
+Plack::Middleware::Auth::WebID is a WebID handler for Plack.
 
 If authentication is successful, then the handler sets C<< $env->{WEBID} >>
-to the user's WebId URI, and sets C<< $env->{WEBID_OBJECT} >> to a
-L<Web::Id> object.
+to the user's WebID URI, and sets C<< $env->{WEBID_OBJECT} >> to a
+L<Web::ID> object.
 
 =begin private
 
 
 =item cache
 
-This may be set to an object that will act as a cache for Web::Id
+This may be set to an object that will act as a cache for Web::ID
 objects. 
 
-Plack::Middleware::Auth::WebId does not care what package you use for
+Plack::Middleware::Auth::WebID does not care what package you use for
 your caching needs. L<CHI>, L<Cache::Cache> and L<Cache> should all
 work. In fact, any package that provides a similar one-argument C<get>
 and a two-argument C<set> ought to work. Which should you use? Well
 reauthentication (which is computationally expensive) happens for
 every request. Use of a cache with an expiration time of around 15
 minutes should significantly speed up the responsiveness of a
-WebId-secured site. (For forking servers you probably want a cache
+WebID-secured site. (For forking servers you probably want a cache
 that is shared between processes, such as a memcached cache.)
 
 =item on_unauth
 
 =item webid_class
 
-Name of an alternative class to use for WebId authentication instead
-of L<Web::Id>. Note that any such class would need to provide a compatible
+Name of an alternative class to use for WebID authentication instead
+of L<Web::ID>. Note that any such class would need to provide a compatible
 C<new> constructor.
 
 =item certificate_env_key
 
-The key within C<< $env >> where Plack::Middleware::Auth::WebId can find
+The key within C<< $env >> where Plack::Middleware::Auth::WebID can find
 a PEM-encoded client SSL certificate.
 
 Apache keeps this information in C<< $env->{'SSL_CLIENT_CERT'} >>, so
 
 =head1 SERVER SUPPORT
 
-WebId is an authentication system based on the Semantic Web and HTTPS.
+WebID is an authentication system based on the Semantic Web and HTTPS.
 It relies on client certificates (but not on certification authorities;
 self-signed certificates are OK).
 
 then you might need to be creative to find a way to forward this
 information to your backend web server.
 
-=item * The client browser needs to have a WebId-compatible certificate installed.
+=item * The client browser needs to have a WebID-compatible certificate installed.
 
 Nuff said.
 
 =head1 BUGS
 
 Please report any bugs to
-L<http://rt.cpan.org/Dist/Display.html?Queue=Web-Id>.
+L<http://rt.cpan.org/Dist/Display.html?Queue=Web-ID>.
 
 =head1 SEE ALSO
 
-L<Plack>, L<Web::Id>.
+L<Plack>, L<Web::ID>, L<Web::ID::FAQ>.
 
+General WebID information:
 L<http://webid.info/>,
 L<http://www.w3.org/wiki/WebID>,
-L<http://www.w3.org/2005/Incubator/webid/spec/>.
+L<http://www.w3.org/2005/Incubator/webid/spec/>,
+L<http://lists.foaf-project.org/mailman/listinfo/foaf-protocols>.
+
+Apache mod_ssl:
+L<Plack::Middleware::Apache2::ModSSL>,
+L<Apache2::ModSSL>,
+L<http://httpd.apache.org/docs/2.0/mod/mod_ssl.html>.
 
 =head1 AUTHOR
 

File lib/Web/ID.pm

+package Web::ID;
+
+use 5.010;
+use utf8;
+
+BEGIN {
+	$Web::ID::AUTHORITY = 'cpan:TOBYINK';
+	$Web::ID::VERSION   = '0.001';
+}
+
+use Any::Moose 'X::Types::Moose' => [':all'];
+use Web::ID::Types ':all';
+use Web::ID::Certificate;
+use Web::ID::Util qw(:default uniq);
+
+use Any::Moose;
+use namespace::clean -except => 'meta';
+
+has certificate => (
+	is          => read_only,
+	isa         => Certificate,
+	required    => true,
+	coerce      => true,
+	);
+
+has uri => (
+	is          => read_only,
+	isa         => Uri,
+	lazy_build  => true,
+	coerce      => true,
+	);
+
+has profile => (
+	is          => read_only,
+	isa         => Model,
+	lazy_build  => true,
+	);
+
+has valid => (
+	is          => read_only,
+	isa         => Bool,
+	lazy_build  => true,
+	);
+
+has first_valid_san => (
+	is          => read_only,
+	isa         => San | Undef,
+	lazy_build  => true,
+	);
+
+sub _build_valid
+{
+	my ($self) = @_;
+	return false unless $self->certificate->timely;
+	return true if defined $self->first_valid_san;
+	return false;
+}
+
+sub _build_uri
+{
+	my ($self) = @_;
+	$self->first_valid_san->uri_object;
+}
+
+sub _build_profile
+{
+	my ($self) = @_;
+	$self->first_valid_san->model;
+}
+
+sub _build_first_valid_san
+{
+	my ($self) = @_;
+	my $cert   = $self->certificate;
+	my @sans   = @{ $cert->subject_alt_names };
+	
+	foreach my $san (@sans)
+	{
+		foreach my $key ( $san->associated_keys )
+		{
+			return $san if $key->rsa_equal($cert);
+		}
+	}
+	
+	return undef;
+}
+
+sub node
+{
+	my ($self) = @_;
+	RDF::Trine::Node::Resource->new($self->uri.'');
+}
+
+sub get
+{
+	my $self = shift;
+	my @pred = map {
+		if (blessed $_ and $_->isa('RDF::Trine::Node'))   {   $_ }
+		else                                              { u $_ }
+	} @_;
+	
+	my @results = uniq
+		map { $_->is_resource ? $_->uri : $_->literal_value }
+		grep { $_->is_literal or $_->is_resource }
+		$self->profile->objects_for_predicate_list($self->node, @pred);
+	
+	wantarray ? @results : $results[0];
+}
+
+__PACKAGE__
+__END__
+
+=head1 NAME
+
+Web::ID - implementation of WebID (a.k.a. FOAF+SSL)
+
+=head1 SYNOPSIS
+
+ my $webid = Web::ID->new(certificate => $pem_encoded_x509);
+ if ($webid->valid)
+ {
+   say "Authenticated as: ", $webid->uri;
+ }
+
+=head1 DESCRIPTION
+
+=head2 Constructor
+
+=over
+
+=item C<< new >>
+
+Standard Moose-style constructor. (This class uses L<Any::Moose>.)
+
+=back
+
+=head2 Attributes
+
+=over
+
+=item C<< certificate >>
+
+A L<Web::ID::Certificate> object representing and x509 certificate,
+though a PEM-encoded string will be coerced.
+
+This is usually the only attribute you want to pass to the constructor.
+Allow the others to be built automatically.
+
+=item C<< first_valid_san >>
+
+Probably fairly uninteresting. This is the first subjectAltName value
+found in the certificate that could be successfully authenticated
+using Web::ID. An L<Web::ID::SAN> object.
+
+=item C<< uri >>
+
+The URI associated with the first valid SAN. A L<URI> object.
+
+This is a URI you can use to identify the person, organisation or
+robotic poodle holding the certificate.
+
+=item C<< profile >>
+
+Data about the certificate holder. An L<RDF::Trine::Model> object.
+Their FOAF file (probably).
+
+=item C<< valid >>
+
+Boolean.
+
+=back
+
+=head2 Methods
+
+=over
+
+=item C<< node >>
+
+Returns the same as C<uri>, but as an L<RDF::Trine::Node> object.
+
+=item C<< get(@predicates) >>
+
+Queries the C<profile> for triples of the form:
+
+  $self->node $predicate $x .
+
+And returns literal and URI values for $x, as strings.
+
+C<< $predicate >> should be an L<RDF::Trine::Node>, or a string. If a
+string, it will be expanded using L<RDF::Trine::NamespaceMap>, so you 
+can do stuff like:
+
+  my $name   = $webid->get('foaf:name', 'rdfs:label');
+  my @mboxes = $webid->get('foaf:mbox');
+
+=back
+
+=head1 BUGS
+
+Please report any bugs to
+L<http://rt.cpan.org/Dist/Display.html?Queue=Web-ID>.
+
+=head1 SEE ALSO
+
+L<Web::ID::FAQ>.
+
+L<Web::ID::Certificate>,
+L<Plack::Middleware::Auth::WebID>.
+
+L<RDF::ACL> provides an access control system that complements WebID.
+
+L<CGI::Auth::FOAF_SSL> is the spiritual ancestor of this module though
+they share very little code, and have quite different APIs.
+
+General WebID information:
+L<http://webid.info/>,
+L<http://www.w3.org/wiki/WebID>,
+L<http://www.w3.org/2005/Incubator/webid/spec/>,
+L<http://lists.foaf-project.org/mailman/listinfo/foaf-protocols>.
+
+Mailing list for general Perl RDF/SemWeb discussion and support:
+L<http://www.perlrdf.org/>.
+
+=head1 AUTHOR
+
+Toby Inkster E<lt>tobyink@cpan.orgE<gt>.
+
+=head1 THANKS
+
+Thanks to Kjetil Kjernsmo (cpan:KJETILK) for persuading me to port my old
+CGI-specific implementaton of this to Plack.
+
+Thanks to Henry Story, Melvin Carvalho, Bruno Harbulot, Ian Jacobi and
+many others for developing WebID from a poorly thought out idea to a 
+clever, yet simple and practical authentication protocol.
+
+Thanks to Gregory Williams (cpan:GWILLIAMS), Tatsuhiko Miyagawa
+(cpan:MIYAGAWA) and the Moose team for providing really good platforms
+(RDF::Trine, Plack and Any::Moose respectively) to build this on.
+
+=head1 COPYRIGHT AND LICENCE
+
+This software is copyright (c) 2012 by Toby Inkster.
+
+This is free software; you can redistribute it and/or modify it under
+the same terms as the Perl 5 programming language system itself.
+
+=head1 DISCLAIMER OF WARRANTIES
+
+THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED
+WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
+MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
+

File lib/Web/ID/Certificate.pm

+package Web::ID::Certificate;
+
+use 5.010;
+use utf8;
+
+BEGIN {
+	$Web::ID::Certificate::AUTHORITY = 'cpan:TOBYINK';
+	$Web::ID::Certificate::VERSION   = '0.001';
+}
+
+use Crypt::X509 0.50 ();  # why the hell does this export anything?!
+use DateTime 0;
+use Any::Moose 'X::Types::Moose' => [':all'];
+use Digest::SHA1 qw(sha1_hex);
+use MIME::Base64 0 qw(decode_base64);
+use Web::ID::Types qw(:all);
+use Web::ID::SAN;
+use Web::ID::SAN::Email;
+use Web::ID::SAN::URI;
+use Web::ID::Util qw(:default part);
+
+# Partly sorts a list of Web::ID::SAN objects,
+# prioritising URIs and Email addresses.
+#
+# Note: placing this deliberately before namespace::clean.
+#
+sub _sort_san
+{
+	map  { ref($_) eq 'ARRAY' ? (@$_) : () }
+	part {
+		if ($_->isa('Web::ID::SAN::URI'))       { 0 }
+		elsif ($_->isa('Web::ID::SAN::Email'))  { 1 }
+		else                                    { 2 }
+	}
+	@_;
+}
+
+use Any::Moose;
+use namespace::clean -except => 'meta';
+
+has pem => (
+	is          => read_only,
+	isa         => Str,
+	required    => true,
+	coerce      => false,
+	);
+
+has _der => (
+	is          => read_only,
+	isa         => Str,
+	required    => true,
+	lazy_build  => true,
+	);
+
+has _x509 => (
+	is          => read_only,
+	isa         => 'Crypt::X509',
+	lazy_build  => true,
+	);
+
+has public_key => (
+	is          => read_only,
+	isa         => Rsakey,
+	lazy_build  => true,
+	handles     => [qw(modulus exponent)],
+	);
+
+has subject_alt_names => (
+	is          => read_only,
+	isa         => ArrayRef,
+	lazy_build  => true,
+	);
+
+has $_ => (
+	is          => read_only,
+	isa         => Datetime,
+	lazy_build  => true,
+	coerce      => true,
+	)
+	for qw( not_before not_after );
+
+has san_factory => (
+	is          => read_only,
+	isa         => CodeRef,
+	lazy_build  => true,
+	);
+
+has fingerprint => (
+	is          => read_only,
+	isa         => Str,
+	lazy_build  => true,
+	);
+
+sub _build_fingerprint
+{
+	lc sha1_hex( shift->_der );
+}
+
+sub _build__der
+{
+	my @lines = split /\n/, shift->pem;
+	decode_base64(join "\n", grep { !/--(BEGIN|END) CERTIFICATE--/ } @lines);
+}
+
+sub _build__x509
+{
+	return Crypt::X509->new(cert => shift->_der);
+}
+
+sub _build_public_key
+{
+	my ($self) = @_;
+	Web::ID::RSAKey->new($self->_x509->pubkey_components);
+}
+
+sub _build_subject_alt_names
+{
+	my ($self) = @_;
+	my $factory = $self->san_factory;
+
+	[_sort_san(
+		map {
+			my ($type, $value) = split /=/, $_, 2;
+			$factory->(type => $type, value => $value);
+		}
+		@{ $self->_x509->SubjectAltName }
+	)];
+}
+
+sub _build_not_before
+{
+	my ($self) = @_;
+	return $self->_x509->not_before;
+}
+
+sub _build_not_after
+{
+	my ($self) = @_;
+	return $self->_x509->not_after;
+}
+
+my $default_san_factory = sub
+{
+	my (%args) = @_;
+	my $class = {
+			uniformResourceIdentifier  => 'Web::ID::SAN::URI',
+			rfc822Name                 => 'Web::ID::SAN::Email',
+		}->{ $args{type} }
+		// 'Web::ID::SAN';
+	$class->new(%args);
+};
+
+sub _build_san_factory
+{
+	return $default_san_factory;
+}
+
+sub timely
+{
+	my ($self, $now) = @_;
+	$now //= DateTime->now;
+	
+	return if $now > $self->not_after;
+	return if $now < $self->not_before;
+	
+	return $self;
+}
+
+__PACKAGE__
+__END__
+
+=head1 NAME
+
+Web::ID::Certificate - an x509 certificate
+
+=head1 SYNOPSIS
+
+ my $cert = Web::ID::Certificate->new(pem => $pem_encoded_x509);
+ foreach (@{ $cert->subject_alt_names })
+ {
+   say "SAN: ", $_->type, " = ", $_->value;
+ }
+
+=head1 DESCRIPTION
+
+=head2 Constructor
+
+=over
+
+=item C<< new >>
+
+Standard Moose-style constructor. (This class uses L<Any::Moose>.)
+
+=back
+
+=head2 Attributes
+
+=over
+
+=item C<< pem >>
+
+A PEM-encoded string for the certificate.
+
+This is usually the only attribute you want to pass to the constructor.
+Allow the others to be built automatically.
+
+=item C<< public_key >>
+
+A L<Web::ID::RSAKey> object.
+
+=item C<< fingerprint >>
+
+A string identifier for the certificate. It is the lower-cased
+hexadecimal SHA1 hash of the DER-encoded certificate.
+
+This is not used in WebID authentication, but may be used as an
+identifier for the certificate if you need to keep it in a cache.
+
+=item C<< not_before >>
+
+L<DateTime> object indicating when the certificate started (or will
+start) to be valid.
+
+=item C<< not_after >>
+
+L<DateTime> object indicating when the certificate will cease (or
+has ceased) to be valid.
+
+=item C<< subject_alt_names >>
+
+An arrayref containing a list of subject alt names (L<Web::ID::SAN>
+objects) associated with the certificate. These are sorted in the order
+they'll be tried for WebID authentication. 
+
+=item C<< san_factory >>
+
+A coderef used for building L<Web::ID::SAN> objects. It's very unlikely
+you need to play with this - the default is probably OK. But changing this
+is "supported" (in so much as any of this is supported).
+
+The coderef is passed a hash (not hashref) along the lines of:
+
+ (
+   type  => 'uniformResourceIdentifier',
+   value => 'http://example.com/id/alice',
+ )
+
+=back
+
+=head2 Methods
+
+=over
+
+=item C<< timely >>
+
+Checks C<not_before> and C<not_after> against the current system time to
+indicate whether the certifixate is temporally valid. Returns a boolean.
+
+You can optionally pass it a L<DateTime> object to use instead of the
+current system time.
+
+=item C<< exponent >>
+
+Delegated to the C<public_key> attribute.
+
+=item C<< modulus >>
+
+Delegated to the C<public_key> attribute.
+
+=back
+
+=head1 BUGS
+
+Please report any bugs to
+L<http://rt.cpan.org/Dist/Display.html?Queue=Web-ID>.
+
+=head1 SEE ALSO
+
+L<Web::ID>,
+L<Web::ID::SAN>,
+L<Web::ID::RSAKey>.
+
+L<Web::ID::Certificate::Generator> - augments this class to add the
+ability to generate new WebID certificates.
+
+L<Crypt::X509> provides a pure Perl X.509 certificate parser, and is
+used internally by this module.
+
+=head1 AUTHOR
+
+Toby Inkster E<lt>tobyink@cpan.orgE<gt>.
+
+=head1 COPYRIGHT AND LICENCE
+
+This software is copyright (c) 2012 by Toby Inkster.
+
+This is free software; you can redistribute it and/or modify it under
+the same terms as the Perl 5 programming language system itself.
+
+=head1 DISCLAIMER OF WARRANTIES
+
+THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED
+WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
+MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
+

File lib/Web/ID/Certificate/Generator.pm

+package Web::ID::Certificate::Generator;
+
+use 5.010;
+use utf8;
+
+BEGIN {
+	$Web::ID::Certificate::Generator::AUTHORITY = 'cpan:TOBYINK';
+	$Web::ID::Certificate::Generator::VERSION   = '0.001';
+}
+
+use Any::Moose
+	'X::Types::Moose' => [':all'],
+	'Util' => ['apply_all_roles'];
+use File::Temp qw();
+use Path::Class qw();
+use RDF::Trine qw(statement blank iri literal);
+use Web::ID::Certificate;
+use Web::ID::Types ':all';
+use Web::ID::Util;
+
+use Any::Moose 'Role';
+use namespace::clean -except => 'meta';
+
+sub import
+{
+	apply_all_roles('Web::ID::Certificate', __PACKAGE__);
+}
+
+sub _openssl_path
+{
+	Path::Class::File->new(
+		$^O eq 'Win32'
+			? 'c:\\openssl\\bin\\openssl.exe'
+			: '/usr/bin/openssl'
+	)
+}
+
+sub generate
+{
+	my ($class, %options) = @_;
+	
+	my $openssl    = (delete $options{openssl_path}) // $class->_openssl_path;
+	my $passphrase = (delete $options{passphrase})
+		or confess "need to provide passphrase option";
+	my $key_size   = (delete $options{key_size}) // 1024;
+	my $sans       = (delete $options{subject_alt_names})
+		or confess "need to provide subject_alt_names option";
+	my $not_after  = (delete $options{not_after});
+	my $dest       = (delete $options{cert_output})
+		or confess "need to provide cert_output option";
+	my $rdf_sink   = (delete $options{rdf_output})
+		or confess "need to provide rdf_output option";
+	
+	my %subject = (
+		C    => delete $options{subject_country},
+		ST   => delete $options{subject_region},
+		L    => delete $options{subject_locality},
+		O    => delete $options{subject_org},
+		CN   => delete $options{subject_cn},
+		);
+	
+	confess "need to provide subject_cn option" unless $subject{CN};
+	
+	confess "unsupported options: ".(join q(, ), sort keys %options) if %options;
+	
+	my $days = $not_after
+		? $not_after->delta_days( DateTime->now )->days
+		: 365;
+	
+	my $tempdir = Path::Class::Dir->new( File::Temp->newdir );
+	$tempdir->mkpath;
+	
+	open my $config, '>', $tempdir->file('openssl.cnf');
+	say $config $_ for
+		q([req]),
+		q(default_bits = 1024),
+		q(default_keyfile = privkey.pem),
+		q(distinguished_name = req_distinguished_name),
+		q(x509_extensions = v3_ca),
+		q(prompt = no),
+		q(),
+		q([v3_ca]);
+	
+	say $config
+		q(subjectAltName = ) .
+		join q(,),
+		map {
+			my $value = $_->value;
+			my $type = {
+				rfc822Name                => 'email',
+				uniformResourceIdentifier => 'URI',
+			}->{ $_->type };
+			$type ? (join q(:), $type, $value) : ();
+		} @$sans;
+	
+	say $config $_ for
+		q(),
+		q([req_distinguished_name]);
+	
+	foreach (qw(C ST L O CN))
+	{
+		next unless (defined $subject{$_} and length $subject{$_});
+		say $config "$_ = ", $subject{$_};
+	}
+	
+	close $config;
+	
+	system(
+		$openssl,
+		"req",
+		"-newkey"  => "rsa:".$key_size,
+		"-x509",
+		"-days"    => $days,
+		"-config"  => $tempdir->file('openssl.cnf'),
+		"-out"     => $tempdir->file('cert.pem'),
+		"-keyout"  => $tempdir->file('privkey.pem'),
+		"-passout" => "pass:".$passphrase,
+		);
+	
+	system(
+		$openssl,
+		"pkcs12",
+		"-export",
+		"-in"      => $tempdir->file('cert.pem'),
+		"-inkey"   => $tempdir->file('privkey.pem'),
+		"-out"     => $tempdir->file('cert.p12'),
+		"-name"    => sprintf('%s <%s>', ($subject{CN}//'Unnamed'), $sans->[0]->value), 
+		"-passin"  => "pass:".$passphrase,
+		"-passout" => "pass:".$passphrase,
+		);
+	
+	if (ref $dest eq 'SCALAR')
+	{
+		$$dest = $tempdir->file('cert.p12')->slurp;
+	}
+	elsif (ref $dest =~ m/^IO/)
+	{
+		my $p12 = $tempdir->file('cert.p12')->slurp;
+		print $dest $p12;
+	}
+	else
+	{
+		my $p12 = $tempdir->file('cert.p12')->slurp;
+		my $fh  = Path::Class::File->new($dest)->openw;
+		print $fh $p12;
+	}
+	
+	my ($on_triple, $on_done) = (sub {}, sub {});
+	if (ref $rdf_sink eq 'SCALAR')
+	{
+		$$rdf_sink = RDF::Trine::Model->new;
+		$on_triple = sub { $$rdf_sink->add_statement(statement(@_)) };
+	}
+	elsif (blessed($rdf_sink) and $rdf_sink->isa('RDF::Trine::Model'))
+	{
+		$on_triple = sub { $rdf_sink->add_statement(statement(@_)) };
+	}
+	else
+	{
+		my $model = RDF::Trine::Model->new;
+		my $fh    = Path::Class::File->new($rdf_sink)->openw;
+		$on_triple = sub { $model->add_statement(statement(@_)) };
+		$on_done   = sub { RDF::Trine::Serializer->new('RDFXML')->serialize_model_to_file($fh, $model) };
+	}
+	
+	my $pem  = $tempdir->file('cert.pem')->slurp;
+	my $cert = $class->new(pem => $pem);
+	
+	my $hex = sub {
+		(my $h = shift->as_hex) =~ s/^0x//;
+		$h;
+	};
+	
+	my $k = blank();
+	$on_triple->($k, u('rdf:type'), u('cert:RSAPublicKey'));
+	$on_triple->($k, u('cert:modulus'), literal($cert->modulus->$hex, undef, uu('xsd:hexBinary')));
+	$on_triple->($k, u('cert:exponent'), literal($cert->exponent->bstr, undef, uu('xsd:integer')));
+	foreach my $san (@$sans)
+	{
+		next unless $san->type eq 'uniformResourceIdentifier';
+		$on_triple->(iri($san->value), u('cert:key'), $k);
+	}
+	$on_done->();
+	
+	$tempdir->rmtree;
+	
+	return $cert;
+}
+
+__PACKAGE__
+__END__
+
+=head1 NAME
+
+Web::ID::Certificate::Generator - role for Web::ID::Certificate
+
+=head1 SYNOPSIS
+
+ use Web::ID::Certificate::Generator;
+ 
+ my %options = (
+   cert_output       => '/home/alice/webid.p12',
+   passphrase        => 's3cr3t s0urc3',
+   rdf_output        => '/home/alice/public_html/foaf.rdf',
+   subject_alt_names => [
+     Web::ID::SAN::URI->new(
+       value => 'http://example.com/~alice/foaf.rdf#me',
+     ),
+     Web::ID::SAN::Email->new(
+       value => 'alice@example.com',
+     ),
+   ],
+   subject_name      => 'Alice Jones',
+   subject_locality  => 'Lewes',
+   subject_region    => 'East Sussex',
+   subject_country   => 'GB',   # ISO 3166-1 alpha-2 code
+ );
+ 
+ my $cert = Web::ID::Certificate->generate(%options);
+
+=head1 DESCRIPTION
+
+This is a role that may be applied to L<Web::ID::Certificate>. It is not
+consumed by Web::ID::Certificate by default as I was trying to avoid
+tainting the class with the horror that's found in this role.
+
+The C<import> routine of this package applies the role to
+Web::ID::Certificate, so it is sufficient to do:
+
+ use Web::ID::Certificate::Generator;
+
+You don't need to muck around with C<apply_all_roles> yourself.
+
+=head2 Constructor
+
+=over
+
+=item C<< generator(%options) >>
+
+Generates a brand new WebID-enabled certificate.
+
+=back
+
+=head2 Options
+
+The following options can be passed to C<generator>
+
+=over
+
+=item * C<cert_output>
+
+A passphrase-protected PKCS12 certificate file is generated as part of
+the certificate generation process. The PKCS12 file is what you'd
+typically import into a browser.
+
+You can pass a scalar reference, in which case the PKCS12 data will be
+written to that scalar; or a file handle or string file name.
+
+This is a required option.
+
+=item * C<passphrase>
+
+The password for the PKCS12 file.
+
+This is a required option.
+
+=item * C<rdf_output>
+
+RDF data is also generated as part of the certificate generation
+process.
+
+Again a file handle or string file name can be passed, or an
+L<RDF::Trine::Model>.
+
+This is a required option.
+
+=item * C<subject_alt_names>
+
+List of L<Web::ID::SAN> objects to generate the certificate's
+subjectAltNames field. You want at least one L<Web::ID::SAN::URI>
+in there.
+
+This is a required option.
+
+=item * C<subject_name>
+
+The name of the person who will hold the certificate. (e.g. "Alice
+Smith".)
+
+This is a required option.
+
+=item * C<subject_org>
+
+The certificate holder's organisation.
+
+Not required.
+
+=item * C<subject_locality>
+
+The locality (e.g. city) of the certificate holder's address.
+
+Not required.
+
+=item * C<subject_region>
+
+The region (e.g. state or county) of the certificate holder's address.
+
+Not required.
+
+=item * C<subject_country>
+
+Two letter ISO code for the country of the certificate holder's address.
+
+Not required.
+
+=item * C<openssl_path>
+
+The path to the OpenSSL binary. Yes that's right, this role calls the
+OpenSSL binary via C<system> calls. Defaults to "/usr/bin/openssl" (or
+"c:\openssl\bin\openssl.exe" on Windows).
+
+=item * C<key_size>
+
+Key size in bits. Defaults to 1024. Bigger keys are more secure. Keys
+bigger than 2048 bits will take a ridiculously long time to generate.
+Keys less than 512 bits are pretty poor.
+
+=item * C<not_after>
+
+Date when the certificate should expire, as a L<DateTime> object.
+Defaults to 365 days.
+
+=back
+
+=head1 BUGS AND LIMITATIONS
+
+Generating the private key results in shedloads of nasty crud being spewed
+out on STDERR.
+
+Please report any bugs to
+L<http://rt.cpan.org/Dist/Display.html?Queue=Web-ID>.
+
+=head1 SEE ALSO
+
+L<Web::ID>,
+L<Web::ID::Certificate>.
+
+=head1 AUTHOR
+
+Toby Inkster E<lt>tobyink@cpan.orgE<gt>.
+
+=head1 COPYRIGHT AND LICENCE
+
+This software is copyright (c) 2012 by Toby Inkster.
+
+This is free software; you can redistribute it and/or modify it under
+the same terms as the Perl 5 programming language system itself.
+
+=head1 DISCLAIMER OF WARRANTIES
+
+THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED
+WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
+MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
+

File lib/Web/ID/RSAKey.pm

+package Web::ID::RSAKey;
+
+use 5.010;
+use utf8;
+
+BEGIN {
+	$Web::ID::RSAKey::AUTHORITY = 'cpan:TOBYINK';
+	$Web::ID::RSAKey::VERSION   = '0.001';
+}
+
+use Any::Moose 'X::Types::Moose' => [':all'];
+use Web::ID::Types ':all';
+use Web::ID::Util;
+
+use Any::Moose;
+use namespace::clean -except => 'meta';
+
+for (qw( exponent modulus ))
+{
+	has $_ => (
+		is          => read_only,
+		isa         => Bigint,
+		required    => true,
+		coerce      => true,
+		);
+}
+
+sub rsa_equal
+{
+	my ($self, $other) = @_;
+	
+	foreach (qw(exponent modulus))
+	{
+		my $m1 = $self->can($_)  or return;
+		my $m2 = $other->can($_) or return;
+		return unless $self->$m1 == $other->$m2;
+	}
+	
+	return true;
+}
+
+__PACKAGE__
+__END__
+
+=head1 NAME
+
+Web::ID::RSAKey - an RSA key
+
+=head1 DESCRIPTION
+
+=head2 Constructor
+
+=over
+
+=item C<< new >>
+
+Standard Moose-style constructor. (This class uses L<Any::Moose>.)
+
+=back
+
+=head2 Attributes
+
+=over
+
+=item C<< exponent >>
+
+The exponent as a Math::BigInt object.
+
+=item C<< modulus >>
+
+The modulus as a Math::BigInt object.
+
+=back
+
+=head2 Methods
+
+=over
+
+=item C<< rsa_equal($that) >>
+
+Returns true iff this key is the same as that key.
+
+=back
+
+=head1 BUGS
+
+Please report any bugs to
+L<http://rt.cpan.org/Dist/Display.html?Queue=Web-ID>.
+
+=head1 SEE ALSO
+
+L<Web::ID>, L<Web::ID::Certificate>.
+
+=head1 AUTHOR
+
+Toby Inkster E<lt>tobyink@cpan.orgE<gt>.
+
+=head1 COPYRIGHT AND LICENCE
+
+This software is copyright (c) 2012 by Toby Inkster.
+
+This is free software; you can redistribute it and/or modify it under
+the same terms as the Perl 5 programming language system itself.
+
+=head1 DISCLAIMER OF WARRANTIES
+
+THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED
+WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
+MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
+

File lib/Web/ID/SAN.pm

+package Web::ID::SAN;
+
+use 5.010;
+use utf8;
+
+BEGIN {
+	$Web::ID::SAN::AUTHORITY = 'cpan:TOBYINK';
+	$Web::ID::SAN::VERSION   = '0.001';
+}
+
+use Any::Moose 'X::Types::Moose' => [':all'];
+use Web::ID::Types ':all';
+use RDF::Query 2.900;
+use URI 0;
+use URI::Escape 0 qw/uri_escape/;
+use Web::ID::RSAKey;
+use Web::ID::Util;
+
+use Any::Moose;
+use namespace::clean -except => 'meta';
+
+has $_ => (
+	is          => read_only,
+	isa         => Str,
+	required    => true,
+	coerce      => false,
+	)
+	for qw(type value);
+
+has model => (
+	is          => read_only,
+	isa         => Model,
+	lazy_build  => true,
+	);
+
+has key_factory => (
+	is          => read_only,
+	isa         => CodeRef,
+	lazy_build  => true,
+	);
+
+sub _build_model
+{
+	return RDF::Trine::Model->new;
+}
+
+my $default_key_factory = sub
+{
+	my (%args) = @_;
+	return unless $args{exponent};
+	return unless $args{modulus};
+	Web::ID::RSAKey->new(%args);
+};
+
+sub _build_key_factory
+{
+	return $default_key_factory;
+}
+
+sub uri_object
+{
+	my ($self) = @_;
+	return URI->new(sprintf 'urn:x-subject-alt-name:%s:%s', map {uri_escape $_} $self->type, $self->value);
+}
+
+sub to_string
+{
+	my ($self) = @_;
+	sprintf('%s=%s', $self->type, $self->value);
+}
+
+sub associated_keys
+{
+	return;
+}
+
+__PACKAGE__
+__END__
+
+=head1 NAME
+
+Web::ID::SAN - represents a single name from a certificate's subjectAltName field
+
+=head1 DESCRIPTION
+
+=head2 Constructor
+
+=over
+
+=item C<< new >>
+
+Standard Moose-style constructor. (This class uses L<Any::Moose>.)
+
+=back
+
+=head2 Attributes
+
+=over
+
+=item C<< type >>
+
+Something like 'uniformResourceIdentifier' or 'rfc822Name'. A string.
+
+=item C<< value >>
+
+The name itself. A string.
+
+=item C<< model >>
+
+An RDF::Trine::Model representing data about the subject identified by
+this name.
+
+To be useful, the C<model> needs to be buildable automatically given
+C<type> and C<value>.
+
+=item C<< key_factory >>
+
+This is similar to the C<san_factory> found in L<Web::ID::Certificate>.
+It's a coderef used to construct L<Web::ID::RSAKey> objects.
+
+=back
+
+=head2 Methods
+
+=over
+
+=item C<< uri_object >>
+
+Forces the name to take the form of a URI identifying the subject. It's
+not always an especially interesting URI.
+
+=item C<< to_string >>
+
+A printable form of the name. Not always very pretty.
+
+=item C<< associated_keys >>
+
+Finds RSA keys associated with this name in C<model>, and returns them as
+a list of L<Web::ID::RSAKey> objects.
+
+=back
+
+=head1 BUGS
+
+Please report any bugs to
+L<http://rt.cpan.org/Dist/Display.html?Queue=Web-ID>.
+
+=head1 SEE ALSO
+
+L<Web::ID>,
+L<Web::ID::Certificate>,
+L<Web::ID::SAN::Email>,
+L<Web::ID::SAN::URI>.
+
+=head1 AUTHOR
+
+Toby Inkster E<lt>tobyink@cpan.orgE<gt>.
+
+=head1 COPYRIGHT AND LICENCE
+
+This software is copyright (c) 2012 by Toby Inkster.
+
+This is free software; you can redistribute it and/or modify it under
+the same terms as the Perl 5 programming language system itself.
+
+=head1 DISCLAIMER OF WARRANTIES
+
+THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED
+WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
+MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
+

File lib/Web/ID/SAN/Email.pm

+package Web::ID::SAN::Email;
+
+use 5.010;
+use utf8;
+
+our $WWW_Finger = 0;
+
+BEGIN {
+	$Web::ID::SAN::Email::AUTHORITY = 'cpan:TOBYINK';
+	$Web::ID::SAN::Email::VERSION   = '0.001';
+	
+	eval {
+		require WWW::Finger;
+		WWW::Finger->VERSION('0.100');
+		$WWW_Finger++;
+	}
+}
+
+use Any::Moose 'X::Types::Moose' => [':all'];
+use Web::ID::Types ':all';
+use Web::ID::Util;
+
+use Any::Moose;
+use namespace::clean -except => 'meta';
+extends 'Web::ID::SAN';
+
+has '+type' => (default => 'rfc822Name');
+
+has finger => (
+	is          => read_only,
+	isa         => Finger | Undef,
+	lazy        => true,
+	builder     => '_build_finger',
+	);
+
+sub _build_finger
+{
+	my ($self) = @_;
+	return WWW::Finger->new($self->value);
+}
+
+around _build_model => sub
+{
+	my ($orig, $self) = @_;
+	
+	if (my $finger = $self->finger)
+	{
+		if ($finger->endpoint)
+		{
+			my $store = RDF::Trine::Store::SPARQL->new($finger->endpoint);
+			return RDF::Trine::Model->new($store);
+		}
+		return $finger->graph;
+	}
+	
+	$self->$orig();
+};
+
+around associated_keys => sub
+{
+	my ($orig, $self) = @_;
+	my @keys = $self->$orig;
+	
+	my $results = $self->query->execute( $self->model );
+	RESULT: while (my $result = $results->next)
+	{
+		my $modulus = make_bigint_from_node(
+			$result->{modulus},
+			fallback      => $result->{hexModulus},
+			fallback_type =>'hex',
+			);
+		my $exponent = make_bigint_from_node(
+			$result->{exponent},
+			fallback      => $result->{decExponent},
+			fallback_type =>'dec',
+			);
+				
+		my $key = $self->key_factory->(
+			modulus  => $modulus,
+			exponent => $exponent,
+			);
+		push @keys, $key if $key;
+	}
+	
+	return @keys;
+};
+
+sub query
+{
+	my ($self) = @_;
+	my $email = 'mailto:' . $self->value;
+	return RDF::Query->new( sprintf(<<'SPARQL', (($email)x4)) );
+PREFIX cert: <http://www.w3.org/ns/auth/cert#>
+PREFIX rsa: <http://www.w3.org/ns/auth/rsa#>
+PREFIX foaf: <http://xmlns.com/foaf/0.1/>
+SELECT
+	?webid
+	?modulus
+	?exponent
+	?decExponent
+	?hexModulus
+WHERE
+{
+	{
+		?webid foaf:mbox <%s> .
+		?key
+			cert:identity ?webid ;
+			rsa:modulus ?modulus ;
+			rsa:public_exponent ?exponent .
+	}
+	UNION
+	{
+		?webid
+			foaf:mbox <%s> ;
+			cert:key ?key .
+		?key
+			rsa:modulus ?modulus ;
+			rsa:public_exponent ?exponent .
+	}
+	UNION
+	{
+		?webid foaf:mbox <%s> .
+		?key
+			cert:identity ?webid ;
+			cert:modulus ?modulus ;
+			cert:exponent ?exponent .
+	}
+	UNION
+	{
+		?webid
+			foaf:mbox <%s> ;
+			cert:key ?key .
+		?key
+			cert:modulus ?modulus ;
+			cert:exponent ?exponent .
+	}
+	OPTIONAL { ?modulus cert:hex ?hexModulus . }
+	OPTIONAL { ?exponent cert:decimal ?decExponent . }
+}
+SPARQL
+}
+
+__PACKAGE__
+__END__
+
+=head1 NAME
+
+Web::ID::SAN::Email - represents subjectAltNames that are e-mail addresses
+
+=head1 DESCRIPTION
+
+This module uses L<WWW::Finger> (if installed) to attempt to locate some
+RDF data about the holder of the given e-mail address. It is probably not
+especially interoperable with other WebID implementations.
+
+=head1 BUGS
+
+Please report any bugs to
+L<http://rt.cpan.org/Dist/Display.html?Queue=Web-ID>.
+
+=head1 SEE ALSO
+
+L<Web::ID>,
+L<Web::ID::SAN>.
+
+=head1 AUTHOR
+
+Toby Inkster E<lt>tobyink@cpan.orgE<gt>.
+
+=head1 COPYRIGHT AND LICENCE
+
+This software is copyright (c) 2012 by Toby Inkster.
+
+This is free software; you can redistribute it and/or modify it under
+the same terms as the Perl 5 programming language system itself.
+
+=head1 DISCLAIMER OF WARRANTIES
+
+THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED
+WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
+MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
+

File lib/Web/ID/SAN/URI.pm

+package Web::ID::SAN::URI;
+
+use 5.010;
+use utf8;
+
+BEGIN {
+	$Web::ID::SAN::URI::AUTHORITY = 'cpan:TOBYINK';
+	$Web::ID::SAN::URI::VERSION   = '0.001';
+}
+
+use Any::Moose 'X::Types::Moose' => [':all'];
+use Web::ID::Types ':all';
+use Web::ID::Util;
+
+use Any::Moose;
+use namespace::clean -except => 'meta';
+extends 'Web::ID::SAN';
+
+has '+type' => (default => 'uniformResourceIdentifier');
+
+override uri_object => sub
+{
+	my ($self) = @_;
+	return URI->new($self->value);
+};
+
+around _build_model => sub
+{
+	my ($orig, $self) = @_;
+	my $model = $self->$orig;
+	return get_trine_model($self->value => $model);
+};
+
+around associated_keys => sub
+{
+	my ($orig, $self) = @_;
+	my @keys = $self->$orig;
+	
+	my $results = $self->query->execute( $self->model );
+	RESULT: while (my $result = $results->next)
+	{
+		# trim any whitespace around modulus
+		# (HACK for MyProfile WebIDs)
+		# Should probably be in ::Util.
+		$result->{modulus}->[0] =~ s/(^\s+)|(\s+$)//g;
+		
+		my $modulus = make_bigint_from_node(
+			$result->{modulus},
+			fallback      => $result->{hexModulus},
+			fallback_type =>'hex',
+			);
+		my $exponent = make_bigint_from_node(
+			$result->{exponent},
+			fallback      => $result->{decExponent},
+			fallback_type =>'dec',
+			);
+		
+		my $key = $self->key_factory->(
+			modulus  => $modulus,
+			exponent => $exponent,
+			);
+		push @keys, $key if $key;
+	}
+	
+	return @keys;
+};
+
+sub query
+{
+	my ($self) = @_;
+	return RDF::Query->new( sprintf(<<'SPARQL', (($self->uri_object)x4)) );
+PREFIX cert: <http://www.w3.org/ns/auth/cert#>
+PREFIX rsa: <http://www.w3.org/ns/auth/rsa#>
+SELECT
+	?modulus
+	?exponent
+	?decExponent
+	?hexModulus
+WHERE
+{
+	{
+		?key
+			cert:identity <%s> ;
+			rsa:modulus ?modulus ;
+			rsa:public_exponent ?exponent .
+	}
+	UNION
+	{
+		<%s> cert:key ?key .
+		?key
+			rsa:modulus ?modulus ;
+			rsa:public_exponent ?exponent .
+	}
+	UNION
+	{
+		?key
+			cert:identity <%s> ;
+			cert:modulus ?modulus ;
+			cert:exponent ?exponent .
+	}
+	UNION
+	{
+		<%s> cert:key ?key .
+		?key
+			cert:modulus ?modulus ;
+			cert:exponent ?exponent .
+	}
+	OPTIONAL { ?modulus cert:hex ?hexModulus . }
+	OPTIONAL { ?exponent cert:decimal ?decExponent . }
+}
+SPARQL
+}
+
+__PACKAGE__
+__END__
+
+=head1 NAME
+
+Web::ID::SAN::URI - represents subjectAltNames that are URIs
+
+=head1 DESCRIPTION
+
+subjectAltNames such as these are the foundation of the whole WebID idea.
+
+=head1 BUGS
+
+Please report any bugs to
+L<http://rt.cpan.org/Dist/Display.html?Queue=Web-ID>.
+
+=head1 SEE ALSO
+
+L<Web::ID>,
+L<Web::ID::SAN>.
+
+=head1 AUTHOR
+
+Toby Inkster E<lt>tobyink@cpan.orgE<gt>.
+
+=head1 COPYRIGHT AND LICENCE
+
+This software is copyright (c) 2012 by Toby Inkster.
+
+This is free software; you can redistribute it and/or modify it under
+the same terms as the Perl 5 programming language system itself.
+
+=head1 DISCLAIMER OF WARRANTIES
+
+THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED
+WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
+MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
+

File lib/Web/ID/Types.pm

+package Web::ID::Types;
+
+use 5.010;
+use strict;
+use utf8;
+
+use DateTime;
+use Math::BigInt;
+use RDF::Trine;
+use URI;
+
+use Any::Moose
+	'X::Types' => [
+		-declare => [qw[ Bigint Certificate Datetime Finger Model Rsakey San Uri ]],
+	],
+	'X::Types::Moose' => [
+		':all',
+	];
+
+class_type Bigint, { class => 'Math::BigInt' };
+coerce Bigint,
+	from Str, via { Math::BigInt->new($_) };
+		
+class_type Certificate, { class => 'Web::ID::Certificate' };
+coerce Certificate,
+	from HashRef, via  { Web::ID::Certificate->new(%$_) },
+	from Str,     via  { Web::ID::Certificate->new(pem => $_) };
+
+class_type Datetime,	{ class => 'DateTime' };
+coerce Datetime,
+	from Num, via { DateTime->from_epoch(epoch => $_) };
+
+class_type Finger, { class => 'WWW::Finger' };
+coerce Finger,
+	from Str, via { WWW::Finger->new($_) if UNIVERSAL::can('WWW::Finger', 'new') };
+
+class_type Model, { class => 'RDF::Trine::Model' };
+
+class_type Rsakey, { class => 'Web::ID::RSAKey' };
+coerce Rsakey,
+	from HashRef, via  { Web::ID::RSAKey->new(%$_) };
+
+class_type San, { class => 'Web::ID::SAN' };
+
+class_type Uri, { class => 'URI' };
+coerce Uri,
+	from Str, via { URI->new($_) };
+
+
+__PACKAGE__
+__END__
+
+=head1 NAME
+
+Web::ID::Types - type library for Web::ID and friends
+
+=head1 DESCRIPTION
+
+This module uses L<Any::Moose> and is capable of providing either a
+L<MooseX::Types> type library, or a L<MouseX::Types> type library.
+
+=head2 Types
+
+=over
+
+=item * C<Bigint>
+
+=item * C<Certificate>
+
+=item * C<Datetime>
+
+=item * C<Finger>
+
+=item * C<Model>
+
+=item * C<Rsakey>
+
+=item * C<San>
+
+=item * C<Uri>
+
+=back
+
+=head1 BUGS
+
+Please report any bugs to
+L<http://rt.cpan.org/Dist/Display.html?Queue=Web-ID>.
+
+=head1 SEE ALSO
+
+L<Web::ID>.
+
+=head1 AUTHOR
+
+Toby Inkster E<lt>tobyink@cpan.orgE<gt>.
+
+=head1 COPYRIGHT AND LICENCE
+
+This software is copyright (c) 2012 by Toby Inkster.
+
+This is free software; you can redistribute it and/or modify it under
+the same terms as the Perl 5 programming language system itself.
+
+=head1 DISCLAIMER OF WARRANTIES
+
+THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED
+WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
+MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
+

File lib/Web/ID/Util.pm

+package Web::ID::Util;
+
+use 5.010;
+use strict;
+use utf8;
+
+BEGIN {
+	$Web::ID::Util::AUTHORITY = 'cpan:TOBYINK';
+	$Web::ID::Util::VERSION   = '0.001';
+}
+
+use Carp qw/confess/;
+use Math::BigInt 0 try => 'GMP';
+use RDF::Trine::NamespaceMap;
+use List::MoreUtils qw(:all !true !false);
+
+our (@EXPORT, @EXPORT_OK);
+BEGIN {
+	@EXPORT    = qw(make_bigint_from_node get_trine_model u uu
+	                true false read_only read_write);
+	@EXPORT_OK = (@EXPORT, grep {!/^(true|false)$/} @List::MoreUtils::EXPORT_OK);
+}
+
+use Sub::Exporter -setup => {
+	exports => \@EXPORT_OK,
+	groups  => {
+		default  => \@EXPORT,
+		all      => \@EXPORT_OK,
+	},
+};
+
+use constant {
+	read_only  => 'ro',
+	read_write => 'rw',
+};
+
+use constant {
+	true  => !!1, 
+	false => !!0,
+};
+
+sub u (;$)
+{
+	state $namespaces //= RDF::Trine::NamespaceMap->new({
+		rdf	=> 'http://www.w3.org/1999/02/22-rdf-syntax-ns#',
+		rdfs	=> 'http://www.w3.org/2000/01/rdf-schema#',
+		owl	=> 'http://www.w3.org/2002/07/owl#',
+		xsd	=> 'http://www.w3.org/2001/XMLSchema#',
+		foaf	=> 'http://xmlns.com/foaf/0.1/',
+		cert	=> 'http://www.w3.org/ns/auth/cert#',
+		rsa	=> 'http://www.w3.org/ns/auth/rsa#',
+	});
+	
+	if (@_)
+	{
+		my $rv = $namespaces->uri(@_)
+			or confess "couldn't expand term $_[0]";
+		return $rv;
+	}
+	
+	return $namespaces;
+}
+
+sub uu ($)
+{
+	return u(shift)->uri;
+}
+
+sub get_trine_model
+{
+	my ($uri, $model) = @_;
+	
+	$model //= RDF::Trine::Model->new;
+	eval {
+		RDF::Trine::Parser->parse_url_into_model($uri, $model);
+	};
+	
+	return $model;
+}
+
+sub make_bigint_from_node
+{
+	my ($node, %opts) = @_;
+	
+	state $test_hex = [
+		uu('cert:hex'),
+		uu('xsd:hexBinary'),
+	];
+	
+	state $test_unsigned = [
+		uu('cert:decimal'),
+		uu('cert:int'),
+		uu('xsd:unsignedLong'),
+		uu('xsd:unsignedInt'),
+		uu('xsd:unsignedShort'),
+		uu('xsd:unsignedByte'),
+		uu('xsd:positiveInteger'),
+		uu('xsd:nonNegitiveInteger'),
+	];
+	
+	state $test_signed = [
+		uu('xsd:integer'),
+		uu('xsd:negitiveInteger'),
+		uu('xsd:nonPositiveInteger'),
+		uu('xsd:long'),
+		uu('xsd:short'),
+		uu('xsd:int'),
+		uu('xsd:byte'),
+	];
+	
+	state $test_decimal = uu('xsd:decimal');
+	
+	if ($node->is_literal)
+	{
+		given ($node->literal_datatype)
+		{
+			when ($_ ~~ $test_hex)
+			{
+				( my $hex = $node->literal_value ) =~ s/[^0-9A-F]//ig;
+				return Math::BigInt->from_hex("0x$hex");
+			}
+			
+			when ($_ ~~ $test_unsigned)
+			{
+				( my $dec = $node->literal_value ) =~ s/[^0-9]//ig;
+				return Math::BigInt->new("$dec");
+			}
+			
+			when ($_ ~~ $test_signed)
+			{
+				( my $dec = $node->literal_value ) =~ s/[^0-9-]//ig;
+				return Math::BigInt->new("$dec");
+			}
+			
+			when ($_ ~~ $test_decimal)
+			{
+				my ($dec, $frac) = split /\./, $node->literal_value, 2;
+				warn "Ignoring fractional part of xsd:decimal number."
+					if defined $frac;
+				
+				$dec =~ s/[^0-9-]//ig;
+				return Math::BigInt->new("$dec");
+			}
+			
+			when ($_ ~~ undef)
+			{
+				$opts{'fallback'} = $node;
+			}
+		}
+	}
+	
+	if (defined( my $node = $opts{'fallback'} )
+	and $opts{'fallback'}->is_literal)
+	{
+		if ($opts{'fallback_type'} eq 'hex')
+		{
+			(my $hex = $node->literal_value) =~ s/[^0-9A-F]//ig;
+			return Math::BigInt->from_hex("0x$hex");
+		}
+		else # dec
+		{
+			my ($dec, $frac) = split /\./, $node->literal_value, 2;
+			warn "Ignoring fractional part of xsd:decimal number."
+				if defined $frac;
+				
+			$dec =~ s/[^0-9]//ig;
+			return Math::BigInt->new("$dec");
+		}
+	}
+	
+	return;
+}
+
+
+__PACKAGE__
+__END__
+
+=head1 NAME
+
+Web::ID::Util - utility functions used in Web-ID
+
+=head1 DESCRIPTION
+
+These are utility functions which I found useful building Web-ID.
+Many of them may also be useful creating the kind of apps that
+Web-ID is used to authenticate for.
+
+Here is a very brief summary. By default, they're B<all> exported
+to your namespace. (This modulue uses L<Sub::Exporter> so you get
+pretty good control over what gets exported.)
+
+=over
+
+=item C<true> - constant for true
+
+=item C<false> - constant for false
+
+=item C<read_only> - constant for string 'ro' (nice for Moose/Mouse)
+
+=item C<read_write> - constant for string 'rw' (nice for Moose/Mouse)
+
+=item C<< get_trine_model($url) >> - fetches a URL and parses RDF into
+an L<RDF::Trine::Model>
+
+=item C<< u($curie) >> - expands a CURIE, returning an
+L<RDF::Trine::Node::Resource>
+
+=item C<< uu($curie) >> - as per C<< u($curie) >>, but returns string
+
+=item C<< u() >> - called with no CURIE, returns the
+L<RDF::Trine::NamespaceMap> used to map CURIEs to URIs
+
+=item C<< make_bigint_from_node($node, %options) >> - makes a L<Math::BigInt>
+object from a numeric L<RDF::Trine::Node::Literal>. Supports most datatypes
+you'd care about, including hexadecimally ones. 
+
+Supported options are C<fallback> which provides a fallback node which will
+be used when C<< $node >> is non-literal; and C<fallback_type> either 'dec'
+or 'hex' which is used when parsing the fallback node, or if C<< $node >>
+is a plain literal. (The actual datatype of the fallback node is ignored for
+hysterical raisins.)
+