summaryrefslogtreecommitdiff
path: root/lib/Config/ClawsMail/PasswordStore.pm
diff options
context:
space:
mode:
Diffstat (limited to 'lib/Config/ClawsMail/PasswordStore.pm')
-rw-r--r--lib/Config/ClawsMail/PasswordStore.pm62
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;