diff --git a/Makefile b/Makefile index 6f0ba17..1ddd030 100644 --- a/Makefile +++ b/Makefile @@ -43,6 +43,7 @@ install-share: 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/migration/raw_suffix2sidecar "$(DESTDIR)$(SCRIPTDIR)/raw_suffix2sidecar" + install -pDm755 contrib/crypt/kdf_pbkdf2.py "$(DESTDIR)$(SCRIPTDIR)/kdf_pbkdf2.py" install-man: @echo 'installing manpages...' diff --git a/btrbk b/btrbk index ba91849..33f8988 100755 --- a/btrbk +++ b/btrbk @@ -114,6 +114,10 @@ my %config_options = ( openssl_iv_size => { default => undef, accept => [ "no", accept_numeric => 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*/ }, backend => { default => "btrfs-progs", accept => [ "btrfs-progs", "btrfs-progs-btrbk", "btrfs-progs-sudo" ] }, @@ -236,6 +240,7 @@ my %raw_info_sort = ( encrypt => 11, cipher => 12, iv => 13, + # kdf_* (generated by kdf_backend) ); my %url_cache; # map URL to btr_tree node @@ -261,6 +266,8 @@ my @transaction_log; my %config_override; my @tm_now; # current localtime ( sec, min, hour, mday, mon, year, wday, yday, isdst ) my %warn_once; +my %kdf_vars; +my $kdf_session_key; $SIG{__DIE__} = sub { @@ -1433,9 +1440,63 @@ sub btrfs_send_to_file($$$;$$) $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 = ( '-' . $encrypt->{ciphername}, - '-K $(cat ' . $encrypt->{keyfile} . ')', + '-K', $encrypt_key, ); push @openssl_options, ('-iv', $iv) if($iv); @@ -2852,6 +2913,9 @@ sub config_encrypt_hash($$) iv_size => config_key($config, "openssl_iv_size"), ciphername => config_key($config, "openssl_ciphername"), 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"), }; } diff --git a/contrib/crypt/kdf_pbkdf2.py b/contrib/crypt/kdf_pbkdf2.py new file mode 100755 index 0000000..1f58d86 --- /dev/null +++ b/contrib/crypt/kdf_pbkdf2.py @@ -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 . +# +# --------------------------------------------------------------------- +# The official btrbk website is located at: +# http://digint.ch/btrbk/ +# +# Author: +# Axel Burri +# --------------------------------------------------------------------- + +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: {} ".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)); diff --git a/doc/btrbk.conf.5 b/doc/btrbk.conf.5 index 10414b4..daeb7c2 100644 --- a/doc/btrbk.conf.5 +++ b/doc/btrbk.conf.5 @@ -456,6 +456,12 @@ openssl_ciphername (defaults to "aes-256-cbc") openssl_iv_size |no (depends on selected cipher) .PP openssl_keyfile |no +.PP +kdf_backend |no +.PP +kdf_keysize (defaults to "32") +.PP +kdf_keygen once|each (defaults to "once") .RE .PD .PP