aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rwxr-xr-xcli/icevault51
-rw-r--r--cli/icevault.147
2 files changed, 89 insertions, 9 deletions
diff --git a/cli/icevault b/cli/icevault
index c93a608..67e06ec 100755
--- a/cli/icevault
+++ b/cli/icevault
@@ -35,7 +35,7 @@ delete @ENV{qw/IFS CDPATH ENV BASH_ENV/};
my $LANGINFO = I18N::Langinfo::langinfo(I18N::Langinfo::CODESET());
my $LOCALE = Encode::find_encoding $LANGINFO;
-my (%CONFIG, @GPG);
+my (%CONFIG, @GIT, @GPG);
my $SOCKET;
@@ -146,6 +146,12 @@ sub loadConfig() {
error "C<%s> must contain %%s, %%h and %%i placeholders", 'template'
unless $CONFIG{template} =~ /%s/ and $CONFIG{template} =~ /%h/ and $CONFIG{template} =~ /%i/;
+ $CONFIG{'git-dir'} //= '.git';
+ $CONFIG{'git-dir'} =~ s#\A~/#$ENV{HOME}#;
+ $CONFIG{'git-dir'} = "$CONFIG{store}/$CONFIG{'git-dir'}" unless $CONFIG{'git-dir'} =~ /\A\//;
+ $CONFIG{'git-dir'} =~ /\A(\/\p{Print}+)\z/ or error "Insecure C<%s>", $CONFIG{'git-dir'};
+ $CONFIG{'git-dir'} = $1; # untaint $CONFIG{'git-dir'}
+
$CONFIG{socket} //= 'socket';
$CONFIG{socket} =~ s#\A~/#$ENV{HOME}#;
@@ -169,6 +175,7 @@ sub loadConfig() {
error "Insecure C<%s>", $CONFIG{gpg} unless $CONFIG{gpg} =~ /\A([-_\@a-zA-Z0-9\.%\/= ]+)\z/;
$CONFIG{gpg} = $1;
+ @GIT = ('git', '--work-tree='.$CONFIG{store}, '--git-dir='.$CONFIG{'git-dir'});
@GPG = split / /, $CONFIG{gpg};
}
@@ -453,8 +460,6 @@ sub saveIdentityFile($$) {
unlink $outfh->filename or error "Can't unlink C<%s>: %s", $outfh->filename, $!;
error "Can't move C<%s> to C<%s>: %s", $outfh->filename, $filename, $r;
}
-
- # TODO: git add $filename; git commit
}
# Get the visible form list from the server, and croak if it's empty.
@@ -580,6 +585,25 @@ sub sha256_file($) {
return $sha256->digest;
}
+# Add the given @filenames to the index and if there are any staged
+# changes for these files, commit them with the given message.
+sub commit($@) {
+ my $msg = shift;
+ my @filenames = @_;
+
+ return unless -d $CONFIG{'git-dir'} and @filenames;
+ mysystem @GIT, 'add', @filenames;
+
+ # check if there are any staged changes on @filenames
+ system {$GIT[0]} @GIT, 'diff', '--quiet', '--staged', '--', @filenames;
+ return unless $?; # exit value 0: nothing staged
+ error "C<%s> exited with value %d", $GIT[0], ($? >> 8) if $? and $? != -1 and $? != 256;
+
+ $msg =~ /\A(\p{Print}*)\z/ or error "Insecure C<%s>", $msg;
+ $msg = $1; # untaint $msg
+ mysystem @GIT, 'commit', '-m', $msg, '--', @filenames;
+}
+
#######################################################################
@@ -594,6 +618,7 @@ my @USAGE = (
clip => "scheme://hostname/identity",
dump => "[-p, --show-passwords] scheme://hostname/identity",
edit => "scheme://hostname/identity",
+ git => "GIT-COMMAND [GIT-ARG ...]",
insert => "[-f, --force] [-s, --socket=PATH] [identity]",
ls => "[-0, --zero] [scheme://[hostname/[identity]]]",
);
@@ -771,6 +796,7 @@ elsif ($COMMAND eq 'insert') {
undef @{$form->{fields}}[@dontsave] if @dontsave; # remove the field we don't want to save
myprintf "Saving identity C<%s>", "$uri/$id";
saveIdentityFile $form, $filename;
+ commit "Add new identity $uri/$id", $filename;
}
elsif ($COMMAND eq 'fill') {
@@ -921,6 +947,7 @@ elsif ($COMMAND eq 'fill') {
if ($changed) {
my $r = promptYN "Save changes?", 0;
saveIdentityFile ($myform, $filename) if $r;
+ commit "Save imported changes for $id", $filename;
}
}
@@ -982,6 +1009,7 @@ elsif ($COMMAND eq 'edit') {
} elsif (defined $fh2) {
myprintf "Saving user changes for identity C<%s>", $id;
saveIdentityFile($fh2, $filename); # use the FH we opened before unlinking
+ commit "Save manual (using $EDITOR) changes for $id", $filename;
} else {
print "Aborting\n";
exit 1;
@@ -1039,6 +1067,23 @@ elsif ($COMMAND eq 'ls') {
print $LOCALE->encode($_), $delim foreach sort keys %matches;
}
+elsif ($COMMAND eq 'git') {
+ getopts();
+ usage() unless @ARGV;
+
+ unless (-d $CONFIG{store}) {
+ require 'File/Path.pm';
+ File::Path::make_path($CONFIG{store});
+ myprintf "Created directory C<%s>", $CONFIG{store};
+ }
+
+ for (my $i = 0; $i <= $#ARGV; $i++) {
+ $ARGV[$i] =~ /\A(\p{print}*)\z/ or die;
+ $ARGV[$i] = $1;
+ }
+ exec {$GIT[0]} @GIT, @ARGV;
+}
+
else {
print STDERR "Usage: $NAME [COMMAND] [OPTION ...] [ARG ...]\n";
error "Unknown command C<%s>. Try C<%s> for more information.", $COMMAND, "$NAME --help";
diff --git a/cli/icevault.1 b/cli/icevault.1
index 0768b68..906cc8d 100644
--- a/cli/icevault.1
+++ b/cli/icevault.1
@@ -22,12 +22,14 @@ browser and the \fBicevault\fR client.
Each form is stored in a separate file, encrypted separately with
\fIgpg\fR(1); cleartext are never stored on disk. Form history can be kept
-track of by adding the encrypted files to a VCS as binary blobs. File
-paths are of the form ".../\fIscheme\fR/\fIhostname\fR/\fIidentity\fR"
-where \fIidentity\fR is an arbitrary user-chosen value (allowing
-multiple identities for a given site); since the URI of the active tab
-can be retrieved from the socket and since the URI of a stored form can
-be recovered from its file path, phishing attacks are easily detected.
+track of by versioning the encrypted files to a Git repository as binary
+blobs. (Modification of the stored forms are then automatically
+committed to said repository.) File paths are of the form
+".../\fIscheme\fR/\fIhostname\fR/\fIidentity\fR" where \fIidentity\fR is
+an arbitrary user-chosen value (allowing multiple identities for a given
+site); since the URI of the active tab can be retrieved from the socket
+and since the URI of a stored form can be recovered from its file path,
+phishing attacks are easily detected.
Like Firefox's builtin password manager, IceVault has some heuristics to
detect signup and password changing pages. In these cases, and if the
@@ -77,6 +79,33 @@ digest of its content differs. Note that formatting and comments may
not be preserved by subsequent updates of the \fIidentity\fR file.
.TP
+.B git\fR \fIGIT-COMMAND\fR [\fIGIT-ARG\fR...]
+Pass \fIGIT-COMMAND\fR [\fIGIT-ARG\fR...] as arguments to \fIgit\fR(1)
+using the configuration value for \fIstore\fR and that for \fIgit-dir\fR
+as the Git working tree and Git repository, respectively.
+\fIstore\fR is automatically created if it is not an existing directory.
+
+It is recommended to initialize the repository as follows:
+
+ \fBicevault git\fR init
+ echo '*.gpg diff=gpg' >"${XDG_DATA_HOME:-$HOME/.local/share}/icevault/.gitattributes"
+ \fBicevault git\fR add .gitattributes
+ \fBicevault git\fR commit \-m 'Add Git attributes for .gpg binary files.'
+ \fBicevault git\fR config diff.gpg.binary true
+ \fBicevault git\fR config diff.gpg.textconv 'gpg2 \-o \- \-\-decrypt'
+
+The textconv config option enable on-the-fly decryption prior to Git
+operations such as \fIdiff\fR or \fIgrep\fR, see \fIgitattributes\fR(5).
+For instance, grep'ing through the cleartext becomes trivial:
+
+ \fBicevault git\fR grep \-\-textconv \fIpattern\fR
+
+Signing each commit can be achieved as follows, see \fIgit-config\fR(1):
+
+ \fBicevault git\fR config commit.gpgsign true
+ \fBicevault git\fR config user.signingkey 0x39278DA8109E6244
+
+.TP
.B insert\fR [\fB-f\fR, \fB--force\fR] [\fB-s\fR, \fB--socket=\fR\fIPATH\fR] [\fIidentity\fR]
Create a new \fIscheme\fR://\fIhostname\fR/\fIidentity\fR URI available
for further commands.
@@ -122,6 +151,12 @@ Empty lines and comments (starting with a "#" characters are ignored).
Valid options are:
.TP
+.I git-dir
+Path to the Git directory. Can be an absolute path or a path relative
+to the working directory (specified with \fIstore\fR).
+(Default: ".git")
+
+.TP
.I gpg
The \fIgpg\fR(1) command to use. Note that users of GnuPG 1.4.x will
probably want to add the \fB--use-agent\fR option. (Default: "gpg".)