summaryrefslogtreecommitdiff
path: root/lib/Config/ClawsMail/PasswordStore.pm
blob: 3fbb68f69274c1bddd25127019d95ad76f0ba4c9 (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
package Config::ClawsMail::PasswordStore; 
use v5.26;
use Moo;
# VERSION 
use experimental 'signatures';
use Types::Standard qw(HashRef);
use Crypt::Misc qw(decode_b64);
use Crypt::KeyDerivation qw(pbkdf2);
use Crypt::Cipher::AES;
use Crypt::Mode::CBC;
use namespace::clean;
 
# ABSTRACT: decrypt Claws-Mail password store 
 
sub PASSCRYPT_KEY() { 'passkey0' }
 
has raw_data => (
    is => 'ro',
    required => 1,
    isa => HashRef,
);
 
has master_password => ( is => 'ro' );
 
has master_salt_bs64 => ( is => 'ro'required => 1 );
has master_salt => ( is => 'lazy' );
sub _build_master_salt($self) { decode_b64($self->master_salt_bs64) }
 
sub decrypt($self,$input) {
    return $input unless $input && $input =~ m{\A \{ ([a-z0-9-]+),(\d+) \} (.+) \z}smxi;
    my ($algo,$rounds,$ciphertext) = ($1,$2,$3);
 
    die 'unknown algo' unless $algo eq 'AES-256-CBC';
 
    my $key = pbkdf2(
        $self->master_password || PASSCRYPT_KEY(),
        $self->master_salt,
        $rounds'SHA1', 32, # 32 bytes = 256 bits,for AES-256 
    );
 
    my $cipher = Crypt::Mode::CBC->new('AES', 5); # 5 = zero padding 
 
    $ciphertext = decode_b64($ciphertext);
 
    # claws sets up 16 random bytes as IV?? 
    my $iv = '0123456789abcdef';
 
    my $cleartext = $cipher->decrypt($ciphertext$key$iv);
 
    # the first 16 bytes are generated from the IV, we don't care 
    # about them 
    $cleartext = substr($cleartext,16);
    $cleartext =~ s/\0+\z//;
 
    return $cleartext;
}
 
sub password_for($self,$section,$key) {
    return $self->decrypt($self->raw_data->{$section}{$key});
}
 
1;