summaryrefslogtreecommitdiff
path: root/lib/DeWeave/Crypto.pm
blob: dc657b6651c2a1e6235d77c9e2715024e8185bda (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
package DeWeave::Crypto; 
use Moose;
use namespace::autoclean;
use MooseX::Types::Moose qw(HashRef Str);
use MooseX::Types::Structured qw(Tuple);
use JSON::Any;
use Try::Tiny;
use Digest::SHA ();
use MIME::Base32 'RFC';
use Crypt::CBC;
use MIME::Base64 ();
use Data::Dump 'pp';
 
has storage => (
    isa => 'DeWeave::Storage',
    required => 1,
    is => 'ro',
);
 
has sync_key => (
    isa => Str,
    required => 1,
    is => 'ro',
);
 
sub _byte_sync_key {
    my ($self) = @_;
 
    my $key = $self->sync_key;
    $key =~ y{89}{LO};
    $key =~ s{-}{}g;
    my $byte_key = MIME::Base32::decode(uc($key));
    return substr($byte_key,0,16);
}
 
has _hmac_input => (
    isa => Str,
    default => 'Sync-AES_256_CBC-HMAC256',
    is => 'ro',
);
 
has _encryption_key => (
    isa => Str,
    lazy_build => 1,
    is => 'ro',
);
 
sub _build__encryption_key {
    my ($self) = @_;
 
    my $secret = $self->_hmac_input
        $self->storage->username . "\x01";
 
    return Digest::SHA::hmac_sha256($secret$self->_byte_sync_key);
}
 
has _hmac_key => (
    isa => Str,
    lazy_build => 1,
    is => 'ro',
);
 
sub _build__hmac_key {
    my ($self) = @_;
 
    my $secret = $self->_encryption_key . $self->_hmac_input
        $self->storage->username . "\x02";
    return Digest::SHA::hmac_sha256($secret$self->_byte_sync_key);
}
 
has _keys => (
    isa => HashRef[Tuple[Str,Str]],
    init_arg => undef,
    lazy_build => 1,
    is => 'ro',
    traits => ['Hash'],
    handles => {
        _has_collection_keys => 'exists',
        _get_collection_keys => 'get',
    },
);
 
sub _build__keys {
    my ($self) = @_;
 
    my $j = JSON::Any->new;
 
    my $keys_raw = $self->storage->get_item('storage/crypto/keys');
 
    my $keys_struct = $j->decode($keys_raw);
 
    my $payload = $j->decode($keys_struct->{payload});
 
    my $struct = $j->decode($self->decrypt({
        %$payload,
        key => $self->_encryption_key,
    }));
 
    my $keys = {
        default => $struct->{default},
        %{$struct->{collections}},
    };
 
    return $keys;
}
 
sub keys_for_collection {
    my ($self,$collection) = @_;
 
    my $key;
    if ($self->_has_collection_keys($collection)) {
        $key = $self->_get_collection_keys($collection);
    }
    else {
        $key = $self->_get_collection_keys('default');
    }
    return [ map { MIME::Base64::decode($_) } @$key ];
}
 
sub decrypt {
    my ($self,$args) = @_;
 
    my $iv = MIME::Base64::decode($args->{IV});
    my $hmac = $args->{hmac};
    my $ct = MIME::Base64::decode($args->{ciphertext});
    my $key = $args->{key} || $self->keys_for_collection('default')->[0];
 
    my $cipher = Crypt::CBC->new(
        -key => $key,
        -cipher => 'Crypt::OpenSSL::AES',
        -iv => $iv,
        -header => 'none',
        -literal_key => 1,
    );
 
    my $pt = $cipher->decrypt($ct);
    # TODO verify 
    return $pt;
}
 
1;