package Config::ClawsMail::PasswordStore;
use v5.26;
use Moo;
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;
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,
);
my $cipher = Crypt::Mode::CBC->new('AES', 5);
$ciphertext = decode_b64($ciphertext);
my $iv = '0123456789abcdef';
my $cleartext = $cipher->decrypt($ciphertext, $key, $iv);
$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;