btrbk: add key derivation for encrypted raw targets using external backend

pull/204/head
Axel Burri 2017-06-30 14:35:20 +02:00
parent de7628ac7c
commit 422d52c063
4 changed files with 132 additions and 1 deletions

View File

@ -43,6 +43,7 @@ install-share:
install -pDm755 ssh_filter_btrbk.sh "$(DESTDIR)$(SCRIPTDIR)/ssh_filter_btrbk.sh" install -pDm755 ssh_filter_btrbk.sh "$(DESTDIR)$(SCRIPTDIR)/ssh_filter_btrbk.sh"
install -pDm755 contrib/cron/btrbk-mail "$(DESTDIR)$(SCRIPTDIR)/btrbk-mail" install -pDm755 contrib/cron/btrbk-mail "$(DESTDIR)$(SCRIPTDIR)/btrbk-mail"
install -pDm755 contrib/migration/raw_suffix2sidecar "$(DESTDIR)$(SCRIPTDIR)/raw_suffix2sidecar" install -pDm755 contrib/migration/raw_suffix2sidecar "$(DESTDIR)$(SCRIPTDIR)/raw_suffix2sidecar"
install -pDm755 contrib/crypt/kdf_pbkdf2.py "$(DESTDIR)$(SCRIPTDIR)/kdf_pbkdf2.py"
install-man: install-man:
@echo 'installing manpages...' @echo 'installing manpages...'

66
btrbk
View File

@ -114,6 +114,10 @@ my %config_options = (
openssl_iv_size => { default => undef, accept => [ "no", accept_numeric => 1 ] }, openssl_iv_size => { default => undef, accept => [ "no", accept_numeric => 1 ] },
openssl_keyfile => { default => undef, accept_file => { absolute => 1 } }, openssl_keyfile => { default => undef, accept_file => { absolute => 1 } },
kdf_backend => { default => undef, accept_file => { absolute => 1 } },
kdf_keysize => { default => "32", accept_numeric => 1 },
kdf_keygen => { default => "once", accept => [ "once", "each" ] },
group => { default => undef, accept_regexp => qr/^$group_match(\s*,\s*$group_match)*$/, split => qr/\s*,\s*/ }, group => { default => undef, accept_regexp => qr/^$group_match(\s*,\s*$group_match)*$/, split => qr/\s*,\s*/ },
backend => { default => "btrfs-progs", accept => [ "btrfs-progs", "btrfs-progs-btrbk", "btrfs-progs-sudo" ] }, backend => { default => "btrfs-progs", accept => [ "btrfs-progs", "btrfs-progs-btrbk", "btrfs-progs-sudo" ] },
@ -236,6 +240,7 @@ my %raw_info_sort = (
encrypt => 11, encrypt => 11,
cipher => 12, cipher => 12,
iv => 13, iv => 13,
# kdf_* (generated by kdf_backend)
); );
my %url_cache; # map URL to btr_tree node my %url_cache; # map URL to btr_tree node
@ -261,6 +266,8 @@ my @transaction_log;
my %config_override; my %config_override;
my @tm_now; # current localtime ( sec, min, hour, mday, mon, year, wday, yday, isdst ) my @tm_now; # current localtime ( sec, min, hour, mday, mon, year, wday, yday, isdst )
my %warn_once; my %warn_once;
my %kdf_vars;
my $kdf_session_key;
$SIG{__DIE__} = sub { $SIG{__DIE__} = sub {
@ -1433,9 +1440,63 @@ sub btrfs_send_to_file($$$;$$)
$raw_info{iv} = $iv; $raw_info{iv} = $iv;
} }
my $encrypt_key;
if($encrypt->{keyfile}) {
if($encrypt->{kdf_backend}) {
WARN "Both openssl_keyfile and kdf_backend are configured, ignoring kdf_backend!";
}
$encrypt_key = '$(cat ' . $encrypt->{keyfile} . ')';
}
elsif($encrypt->{kdf_backend}) {
if($encrypt->{kdf_keygen_each}) {
$kdf_session_key = undef;
%kdf_vars = ();
}
if($kdf_session_key) {
INFO "Reusing session key for: $vol_received->{PRINT}";
}
else {
# run kdf backend, set session key and vars
DEBUG "Generating session key for: $vol_received->{PRINT}";
my $kdf_backend_name = $encrypt->{kdf_backend};
$kdf_backend_name =~ s/^.*\///;
print STDOUT "\nGenerate session key for " . ($encrypt->{kdf_keygen_each} ? "\"$vol_received->{PRINT}\"" : "all raw backups") . ":\n";
my $kdf_values = run_cmd(cmd => [ $encrypt->{kdf_backend}, $encrypt->{kdf_keysize} ],
non_destructive => 1,
name => $kdf_backend_name
);
return undef unless(defined($kdf_values));
foreach(split("\n", $kdf_values)) {
chomp;
next if /^\s*$/; # ignore empty lines
if(/^KEY=([0-9a-fA-f]+)/) {
$kdf_session_key = $1;
}
elsif(/^([a-z_]+)=(.*)/) {
my $info_key = 'kdf_' . $1;
my $info_val = $2;
DEBUG "Adding raw_info from kdf_backend: $info_key=$info_val";
$kdf_vars{$info_key} = $info_val;
}
else {
ERROR "Ambiguous line from kdf_backend: $encrypt->{kdf_backend}";
return undef;
}
}
unless($kdf_session_key && (length($kdf_session_key) == ($encrypt->{kdf_keysize} * 2))) {
ERROR "Ambiguous key value from kdf_backend: $encrypt->{kdf_backend}";
return undef;
}
INFO "Generated session key for: $vol_received->{PRINT}";
}
$encrypt_key = $kdf_session_key;
%raw_info = ( %kdf_vars, %raw_info );
}
my @openssl_options = ( my @openssl_options = (
'-' . $encrypt->{ciphername}, '-' . $encrypt->{ciphername},
'-K $(cat ' . $encrypt->{keyfile} . ')', '-K', $encrypt_key,
); );
push @openssl_options, ('-iv', $iv) if($iv); push @openssl_options, ('-iv', $iv) if($iv);
@ -2852,6 +2913,9 @@ sub config_encrypt_hash($$)
iv_size => config_key($config, "openssl_iv_size"), iv_size => config_key($config, "openssl_iv_size"),
ciphername => config_key($config, "openssl_ciphername"), ciphername => config_key($config, "openssl_ciphername"),
keyfile => config_key($config, "openssl_keyfile"), keyfile => config_key($config, "openssl_keyfile"),
kdf_keygen_each => (config_key($config, "kdf_keygen") eq "each"),
kdf_backend => config_key($config, "kdf_backend"),
kdf_keysize => config_key($config, "kdf_keysize"),
}; };
} }

60
contrib/crypt/kdf_pbkdf2.py Executable file
View File

@ -0,0 +1,60 @@
#!/usr/bin/env python3
#
# kdf_pbkdf2.py - (kdf_backend for btrbk)
#
# Copyright (c) 2017 Axel Burri
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
# ---------------------------------------------------------------------
# The official btrbk website is located at:
# http://digint.ch/btrbk/
#
# Author:
# Axel Burri <axel@tty0.ch>
# ---------------------------------------------------------------------
import sys
import os
import getpass
import hashlib
def passprompt():
pprompt = lambda: (getpass.getpass("Passphrase: "), getpass.getpass("Retype passphrase: "))
p1, p2 = pprompt()
while p1 != p2:
print("No match, please try again", file=sys.stderr)
p1, p2 = pprompt()
return p1
if len(sys.argv) <= 1:
print("Usage: {} <dklen>".format(sys.argv[0]), file=sys.stderr)
sys.exit(1)
hash_name = "sha256"
iterations = 300000
dklen = int(sys.argv[1])
salt = os.urandom(16)
password = passprompt().encode("utf-8")
dk = hashlib.pbkdf2_hmac(hash_name=hash_name, password=password, salt=salt, iterations=iterations, dklen=dklen)
salt_hex = "".join(["{:02x}".format(x) for x in salt])
dk_hex = "".join(["{:02x}".format(x) for x in dk])
print("KEY=" + dk_hex);
print("algoritm=pbkdf2_hmac");
print("hash_name=" + hash_name);
print("salt=" + salt_hex);
print("iterations=" + str(iterations));

View File

@ -456,6 +456,12 @@ openssl_ciphername <name> (defaults to "aes-256-cbc")
openssl_iv_size <size-in-bytes>|no (depends on selected cipher) openssl_iv_size <size-in-bytes>|no (depends on selected cipher)
.PP .PP
openssl_keyfile <file>|no openssl_keyfile <file>|no
.PP
kdf_backend <file>|no
.PP
kdf_keysize <size> (defaults to "32")
.PP
kdf_keygen once|each (defaults to "once")
.RE .RE
.PD .PD
.PP .PP