diff options
Diffstat (limited to 'lib/Config/ClawsMail/PasswordStore.pm')
-rw-r--r-- | lib/Config/ClawsMail/PasswordStore.pm | 62 |
1 files changed, 62 insertions, 0 deletions
diff --git a/lib/Config/ClawsMail/PasswordStore.pm b/lib/Config/ClawsMail/PasswordStore.pm new file mode 100644 index 0000000..3fbb68f --- /dev/null +++ b/lib/Config/ClawsMail/PasswordStore.pm @@ -0,0 +1,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; |