File Coverage

blib/lib/OpenSocialX/Shindig/Crypter.pm
Criterion Covered Total %
statement 63 65 96.9
branch 9 18 50.0
condition 3 9 33.3
subroutine 11 11 100.0
pod 6 6 100.0
total 92 109 84.4


line stmt bran cond sub pod time code
1             package OpenSocialX::Shindig::Crypter;
2             our $VERSION = '0.03';
3              
4             # ABSTRACT: OpenSocial Shindig Crypter
5              
6 2     2   59132 use URI::Escape qw/uri_escape uri_unescape/;
  2         1783  
  2         156  
7 2     2   2077 use MIME::Base64 qw/decode_base64 encode_base64/;
  2         15004  
  2         188  
8 2     2   2317 use Crypt::CBC;
  2         12624  
  2         76  
9 2     2   50640 use Digest::SHA;
  2         11459  
  2         8509  
10              
11             # Key used for time stamp (in seconds) of data
12             my $TIMESTAMP_KEY = 't';
13              
14             # allow three minutes for clock skew
15             my $CLOCK_SKEW_ALLOWANCE = 180;
16              
17             sub new {
18 1     1 1 18 my $class = shift;
19              
20 1 50 33     9 my $cfg = defined $_[0] && ref( $_[0] ) eq 'HASH' ? shift : {@_};
21              
22             # validate
23 1 50       5 $cfg->{cipher} or die 'cipher key is required';
24 1 50       6 $cfg->{hmac} or die 'hmac key is required';
25 1 50       3 $cfg->{iv} or die 'iv key is required';
26              
27 1 50       5 ( length( $cfg->{cipher} ) == 16 ) or die 'cipher key must be 16 chars';
28 1 50       4 ( length( $cfg->{iv} ) == 16 ) or die 'iv key must be 16 chars';
29              
30 1         4 return bless $cfg, $class;
31             }
32              
33             sub wrap {
34 2     2 1 13 my ( $self, $in ) = @_;
35              
36 2         7 my $encoded = _serializeAndTimestamp($in);
37 2         36 my $cipher = Crypt::CBC->new(
38             {
39             'key' => $self->{cipher},
40             'cipher' => 'Rijndael',
41             'iv' => $self->{iv},
42             'literal_key' => 1,
43             'padding' => 'null',
44             'header' => 'none',
45             keysize => 128 / 8,
46             }
47             );
48 2         2951 my $cipherText = $cipher->encrypt($encoded);
49 2         360 my $hmac = Digest::SHA::hmac_sha1( $cipherText, $self->{hmac} );
50 2         20 my $b64 = encode_base64( $cipherText . $hmac );
51 2         23 return $b64;
52             }
53              
54             sub _serializeAndTimestamp {
55 2     2   4 my ($in) = @_;
56              
57 2         2 my $encoded;
58 2         8 foreach my $key ( keys %$in ) {
59 9         249 $encoded .= uri_escape($key) . "=" . uri_escape( $in->{$key} ) . "&";
60             }
61 2         42 $encoded .= $TIMESTAMP_KEY . "=" . time();
62 2         6 return $encoded;
63             }
64              
65             sub unwrap {
66 2     2 1 2001084 my ( $self, $in, $max_age ) = @_;
67              
68 2         19 my $bin = decode_base64($in);
69 2         10 my $cipherText = substr( $bin, 0, -20 );
70 2         6 my $hmac = substr( $bin, length($cipherText) );
71              
72             # verify
73 2         39 my $v_hmac = Digest::SHA::hmac_sha1( $cipherText, $self->{hmac} );
74 2 50       15 if ( $v_hmac ne $hmac ) {
75 0         0 die 'HMAC verification failure';
76             }
77 2         55 my $cipher = Crypt::CBC->new(
78             {
79             'key' => $self->{cipher},
80             'cipher' => 'Rijndael',
81             'iv' => $self->{iv},
82             'literal_key' => 1,
83             'padding' => 'null',
84             'header' => 'none',
85             keysize => 128 / 8,
86             }
87             );
88 2         348 my $plain = $cipher->decrypt($cipherText);
89 2         386 my $out = $self->deserialize($plain);
90              
91 2         9 $self->checkTimestamp( $out, $max_age );
92              
93 2         25 return $out;
94             }
95              
96             sub deserialize {
97 2     2 1 4 my ( $self, $plain ) = @_;
98              
99 2         4 my $h;
100 2         25 my @items = split( /[\&\=]/, $plain );
101 2         6 my $i;
102 2         10 for ( $i = 0 ; $i < scalar(@items) ; ) {
103 11         37 my $key = uri_unescape( $items[ $i++ ] );
104 11         121 my $value = uri_unescape( $items[ $i++ ] );
105 11         122 $h->{$key} = $value;
106             }
107 2         9 return $h;
108             }
109              
110             sub checkTimestamp {
111 2     2 1 44 my ( $self, $out, $max_age ) = @_;
112              
113 2         13 my $minTime = $out->{$TIMESTAMP_KEY} - $CLOCK_SKEW_ALLOWANCE;
114 2         6 my $maxTime = $out->{$TIMESTAMP_KEY} + $max_age + $CLOCK_SKEW_ALLOWANCE;
115 2         4 my $now = time();
116 2 50 33     84 if ( !( $minTime < $now && $now < $maxTime ) ) {
117 0         0 die "Security token expired";
118             }
119             }
120              
121             my $OWNER_KEY = "o";
122             my $APP_KEY = "a";
123             my $VIEWER_KEY = "v";
124             my $DOMAIN_KEY = "d";
125             my $APPURL_KEY = "u";
126             my $MODULE_KEY = "m";
127              
128             sub create_token {
129 1     1 1 9202 my $self = shift;
130              
131 1 50 33     14 my $data = defined $_[0] && ref( $_[0] ) eq 'HASH' ? shift : {@_};
132 1         10 my $token_data = {
133             $OWNER_KEY => $data->{owner},
134             $APP_KEY => $data->{app},
135             $VIEWER_KEY => $data->{viewer},
136             $DOMAIN_KEY => $data->{domain},
137             $APPURL_KEY => $data->{app_url},
138             $MODULE_KEY => $data->{module_id},
139             };
140 1         6 my $token = $self->wrap($token_data);
141 1         4 return uri_escape($token);
142             }
143              
144             1;
145             __END__