aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authordakkar <dakkar@thenautilus.net>2014-12-20 14:27:55 +0000
committerdakkar <dakkar@thenautilus.net>2014-12-20 14:27:55 +0000
commita237abd8e01e2783b9bb9d6eb3c3c26ef8832f92 (patch)
treeed88c9fed05152c9ce2ee77e3bf42dc600a1d24b
downloadnet-hawk-a237abd8e01e2783b9bb9d6eb3c3c26ef8832f92.tar.gz
net-hawk-a237abd8e01e2783b9bb9d6eb3c3c26ef8832f92.tar.bz2
net-hawk-a237abd8e01e2783b9bb9d6eb3c3c26ef8832f92.zip
first tests passing
-rw-r--r--.gitignore14
-rw-r--r--lib/Net/Hawk/Crypto.pm91
-rw-r--r--lib/Net/Hawk/Utils.pm14
-rw-r--r--t/tests/Net/Hawk/Crypto.t73
4 files changed, 192 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..263b16e
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,14 @@
+blib
+pm_to_blib
+*.sw?
+Makefile
+Makefile.old
+MANIFEST.bak
+*.tar.gz
+/inc/
+/META.*
+/MYMETA.*
+.prove
+*~
+/.build/
+/Net-Hawk-*/
diff --git a/lib/Net/Hawk/Crypto.pm b/lib/Net/Hawk/Crypto.pm
new file mode 100644
index 0000000..a730ca6
--- /dev/null
+++ b/lib/Net/Hawk/Crypto.pm
@@ -0,0 +1,91 @@
+package Net::Hawk::Crypto;
+use strict;
+use warnings;
+use 5.010;
+use Moo;
+use Types::Standard 1.000003 qw(Str Int Object Dict Optional Undef Any HasMethods slurpy);
+use Types::URI qw(Uri);
+use Type::Params qw(compile);
+use Try::Tiny;
+use Net::Hawk::Utils;
+use Digest;
+
+sub header_version() { 1 }
+
+sub generate_normalized_string {
+ state $argcheck = compile(Object,Str,Dict[
+ resource => Uri,
+ ts => Int,
+ nonce => Str,
+ method => Optional[Str],
+ host => Str,
+ port => Int,
+ hash => Optional[Str],
+ ext => Optional[Str],
+ app => Optional[Str],
+ dlg => Optional[Str],
+ slurpy Any,
+ ]);
+ my ($self,$type,$options) = $argcheck->(@_);
+
+ my $normalized = sprintf(
+ "hawk.%d.%s\n%d\n%s\n%s\n%s\n%s\n%d\n%s\n%s\n",
+ header_version(), $type,
+ $options->{ts},
+ $options->{nonce},
+ uc($options->{method} // ''),
+ $options->{resource}->path_query,
+ lc($options->{host}),
+ $options->{port},
+ $options->{hash} // '',
+ ($options->{ext} // '') =~ s{\\}{\\\\}gr =~ s{\n}{\\n}gr,
+ );
+
+ if ($options->{app}) {
+ $normalized .= sprintf(
+ "%s\n%s\n",
+ $options->{app},
+ $options->{dlg} // '',
+ );
+ }
+
+ return $normalized;
+}
+
+sub calculate_payload_hash {
+ state $argcheck = compile(Object,Str|Undef,Str,Str);
+ my ($self,$payload,$algorithm,$content_type) = $argcheck->(@_);
+
+ my $hash = $self->initialize_payload_hash($algorithm,$content_type);
+ $hash->add($payload//'');
+ return $self->finalize_payload_hash($hash);
+}
+
+sub initialize_payload_hash {
+ state $argcheck = compile(Object,Str,Str);
+ my ($self,$algorithm,$content_type) = $argcheck->(@_);
+
+ my $digest = try {
+ Digest->new($algorithm);
+ }
+ catch {
+ $algorithm =~ s{(?<=[a-z])(?=[0-9])}{-};
+ Digest->new(uc($algorithm));
+ };
+
+ $digest->add(sprintf("hawk.%d.payload\n",header_version()));
+ $digest->add(Net::Hawk::Utils::parse_content_type($content_type),"\n");
+ return $digest;
+}
+
+sub finalize_payload_hash {
+ state $argcheck = compile(Object,HasMethods[qw(add b64digest)]);
+ my ($self,$digest) = $argcheck->(@_);
+
+ $digest->add("\n");
+ my $ret = $digest->b64digest();
+ $ret .= '=' while length($ret) % 4;
+ return $ret;
+}
+
+1;
diff --git a/lib/Net/Hawk/Utils.pm b/lib/Net/Hawk/Utils.pm
new file mode 100644
index 0000000..527021d
--- /dev/null
+++ b/lib/Net/Hawk/Utils.pm
@@ -0,0 +1,14 @@
+package Net::Hawk::Utils;
+use strict;
+use warnings;
+use 5.010;
+
+sub parse_content_type {
+ my ($header) = @_;
+ return '' unless defined $header;
+
+ my ($ret) = $header =~ m{^\s*(\S+?)\s*(;|$)};
+ return lc($ret);
+}
+
+1;
diff --git a/t/tests/Net/Hawk/Crypto.t b/t/tests/Net/Hawk/Crypto.t
new file mode 100644
index 0000000..eb1a52f
--- /dev/null
+++ b/t/tests/Net/Hawk/Crypto.t
@@ -0,0 +1,73 @@
+#!perl
+use strict;
+use warnings;
+use Test::More;
+use Net::Hawk::Crypto;
+
+my $c = Net::Hawk::Crypto->new();
+
+my %credentials = (
+ id => 'dh37fgj492je',
+ key => 'werxhqb98rpaxn39848xrunpaw3489ruxnpa98w4rxn',
+ algorithm => 'sha256',
+);
+my %options = (
+ credentials => \%credentials,
+ timestamp => 1353832234,
+ nonce => 'j4h3g2',
+ ext => 'some-app-ext-data'
+);
+
+subtest GET => sub {
+ my $string = $c->generate_normalized_string(
+ header => {
+ credentials => \%credentials,
+ ts => $options{timestamp},
+ nonce => $options{nonce},
+ method => 'GET',
+ resource => '/resource?a=1&b=2',
+ host => 'example.com',
+ port => 8000,
+ ext => $options{ext},
+ }
+ );
+
+ is(
+ $string,
+ "hawk.1.header\n1353832234\nj4h3g2\nGET\n/resource?a=1&b=2\nexample.com\n8000\n\nsome-app-ext-data\n",
+ 'normalized string generated ok',
+ );
+};
+
+subtest POST => sub {
+ my $payload = 'Thank you for flying Hawk';
+ my $content_type = 'text/plain';
+
+ my $payload_hash = $c->calculate_payload_hash(
+ $payload,
+ $credentials{algorithm},
+ $content_type,
+ );
+
+ my $string = $c->generate_normalized_string(
+ header => {
+ credentials => \%credentials,
+ ts => $options{timestamp},
+ nonce => $options{nonce},
+ method => 'POST',
+ resource => '/resource?a=1&b=2',
+ host => 'example.com',
+ port => 8000,
+ hash => $payload_hash,
+ ext => $options{ext},
+ }
+ );
+
+ is(
+ $string,
+ "hawk.1.header\n1353832234\nj4h3g2\nPOST\n/resource?a=1&b=2\nexample.com\n8000\nYi9LfIIFRtBEPt74PVmbTF/xVAwPn7ub15ePICfgnuY=\nsome-app-ext-data\n",
+ 'normalized string generated ok',
+ );
+};
+
+done_testing;