#!/usr/bin/perl # se - safe edit use Getopt::Std; my $VER = '1.07'; my %opts = (c => 'builtin'); my @editors = qw(ew vi emacs pico); my ($enc, $dec, $uid, $editor); # Main initialize(); decrypt(); edit(); encrypt(); # Initialize all globals sub initialize() { # Check the command line usage() unless (getopts ('vu:e:cb', \%opts)); usage() if ($#ARGV != 0); # Check that gpg is there my $gpgfound = 0; for my $p (split (/:/, $ENV{PATH})) { if (-x "$p/gpg") { $gpgfound++; last; } } die ("Program gpg not found\n", "Se relies on 'gpg' to encrypt/decrypt files. Install and set up\n", "gpg, then retry.\n") unless ($gpgfound); # Define names of encrypted and decrypted files my $f = shift (@ARGV); if ($f =~ /\.gpg$/) { $enc = $f; $dec = $f; $dec =~ s/\.gpg$//; } else { $dec = $f; $enc = $f; $enc .= '.gpg'; } msg ("Plain text: $dec, encrypted: $enc\n"); die ("Both plain text $dec and encrypted $enc exist on disk!\n", "Choose which is the more recent one, and remove the other.\n", "Then retry.\n") if (-f $enc and -f $dec); # Define the gpg UID to use if ($opts{u}) { $uid = $opts{u}; } elsif ($ENV{SE_UID}) { $uid = $ENV{SE_UID}; } else { msg ("Getting a list of secret GPG keys\n"); open (my $if, "gpg --list-secret-keys |") or die ("Cannot start gpg --list-secret-keys\n", "Gpg doesn't seem to work well, or this 'se' doesn't know\n", "how to get a key listing from gpg.\n"); while (my $line = <$if>) { if ($line =~ /^uid/) { $uid = $line; chomp ($uid); $uid =~ s/.*\.*//; last; } } close ($if) or die ("Gpg keys listing failed\n", "Se cannot find a UID for encryption/decryption.\n", "Make sure that gpg has at least one secret key, or use\n", "option -u to force a key.\n"); } msg ("GPG UID: $uid\n"); # Find an editor we can use. if ($opts{e}) { $editor = $opts{e}; } elsif ($ENV{EDITOR}) { $editor = $ENV{EDITOR}; } elsif ($ENV{VISUAL}) { $editor = $ENV{VISUAL}; } else { my $found = 0; for my $e (@editors) { last if ($found); for my $p (split (/:/, $ENV{PATH})) { if (-x "$p/$e") { $editor = $e; $found++; last; } } } } msg ("Editor command: $editor\n"); } # Prepare a decrypted version sub decrypt() { return if (-f $dec); if (-f $enc) { msg ("Decrypting $enc\n"); xsystem ("gpg -u '$uid' $enc"); die ("Oops, $dec not present after decryption attempt!\n") unless (-f $dec); chmod (0600, $dec); xunlink ($enc); } } # Prepare an encrypted version sub encrypt() { return unless (-f $dec); xsystem ("gpg -e -r '$uid' $dec"); die ("Oops, $enc not present after encryption attempt!\n", "File $dec is now on disk in DECRYPTED state!\n") unless (-f $enc); xunlink ($dec); chmod (0600, $enc); } # Run the editor. sub edit() { msg ("Running: '$editor $dec'\n"); if (! $opts{b}) { xsystem ("$editor $dec"); } else { msg ("Running '$editor $dec' as a background process\n"); my $pid = fork(); if ($pid == 0) { # Child process exec ("$editor $dec"); die ("Failed to exec '$editor $dec'\n"); } elsif ($pid > 0) { # Parent msg ("$editor runs as pid $pid\n"); my $waitedpid = wait(); msg ("Child $pid terminated\n"); } } } # Show usage and die sub usage() { my $prog = $0; $prog =~ s/.*\///; die <<"ENDUSAGE"; This is se V$VER: SafeEdit, an editor/gpg wrapper Copyright (c) Karel Kubat / / http://www.kubat.nl 2000 ff. Distributed under GPL3. Use for your own pleasure and at your own risk. Usage: se [-flags] file[.gpg] Where: file[.gpg] is the file to edit, .gpg indicates encrypted state Flags: -c CLOBBER defines 'clobbering' of files to remove, options are none (unlinking only), or builtin (default, 3 pass overwrite, then unlink), or CMD (external command, e.g. 'srm') -e EDITOR defines the editor to use, default \$EDITOR or \$VISUAL or @editors -u UID defines the gpg UID to use, default \$SE_UID or first uid in 'gpg --list-secret-keys' output -v increases verbosity -b assumes editor is a background process, se will detach and wait for the editor to finish Run 'perldoc $prog' for extensive documentation. ENDUSAGE } # Verbose messaging (or not) sub msg { return unless ($opts{v}); print (@_); } # System command or stop sub xsystem ($) { my $cmd = shift; system ($cmd) and die ("Command $cmd failed\n"); } # Overwrite and unlink sub xunlink ($) { my $f = shift; if ($opts{c} eq 'builtin') { my $sz = (stat($f))[7] or die ("Cannot stat $f: $!\n"); msg ("Safe-unlinking $f ($sz bytes)\n"); open (my $fh, "+<$f") or die ("Cannot open $f for r/w: $!\n"); for (my $i = 0; $i < 3; $i++) { my $ch = (i & 1) ? chr(0) : chr(255); syswrite ($fh, $ch x $sz); seek ($fh, 0, 0); } close ($fh); unlink ($f) or die ("Cannot unlink $f: $!\n"); } elsif ($opts{c} eq 'none') { unlink ($f) or die ("Cannot unlink $f: $!\n"); } else { xsystem ("$opts{c} $f"); } } =pod =head1 se: Safe Editing wrapper I: SafeEdit, copyright (C) Karel Kubat / karel@kubat.nl. Distributed under GPL3 (Gnu Public License version 3); use for your own pleasure and at your own risk. =head2 Introduction I is a fairly simple wrapper around I and editors. It is intended to be invoked from the commandline, instead of calling your favorite editor. It supports one argument: a file to edit (and some flags). The actions are basically: =over 4 =item * If found, the stated file is decrypted using I. =item * An editor is invoked to edit the file. =item * Once the editor finishes, the file is encrypted. The plain-text version which exists on the file system (during editing) is 'clobbered' and removed. =back I is useful for e.g. storing your account names and passwords in a file, or for storing any other sensitive data. =head2 Command line invocation I supports the following command line: se [flags] FILE[.gpg] =over =item * I is the file to edit, optinally an extension I<.gpg> may be present (this extension is added by I when encrypting). I doesn't require the extension; it will check whether the file exists in encrypted or decrypted form anyway. =item * I<-b> is used to indicate that the editor command is a background process (e.g., nedit, emacs in X mode). se will detach and wait for the editor to finish before encrypting. =item * I<-c CLOBBER> specifies the method to 'clobber' plain text files. Possible values are: =over =item * I, the default: 3 passes of overwriting, followed by unlinking; =item * I, in which case the file is only unlinked; =item * Any other command, which is then executed. An example is I, a commonly used tool. =back =item * I<-e EDITOR> specifies the editor to use. The default is the program indicated by the environment variable $EDITOR, or $VISUAL. When both environment variables are not present, I tries I, I and I in that order. =item * I<-u UID> specifies the I UID to use for decrypting and encrypting. When not given, I inspects variable $SE_UID and uses that value. When there is no $SE_UID, I uses the first UID that is returned by the command I I<--list-secret-keys> =item * I<-v> increases verbosity: I's actions are shown. =back =cut =head2 Using se In order to use I, you will need I and an identity with a secret key. The identity is an e-mail address, e.g. I. Consult the I documentation for further information. Make sure that your secret key is protected using a pass phrase. Next, you can basically just run I I and put information into I. When you're done, a file I will be on the file system, which is the encrypted format of your input. The encryption is done using the identity of your secret key, in this example I. When you start I I again, I will be invoked to decrypt I before editing. Here you have to enter your pass phrase to unlock your secret key. Again you can edit the file, and again, when done, a new encrypted version will appear. I can of course be used for non-text files, e.g., Word documents. I doesn't know or care what type a file is, or what an editor actually is. E.g., if you have I (MS-Word under Crossover Office), then you can use I to secure Word documents, as in: se -b /opt/cxoffice/bin/winword myfile.doc which instructs I that 'winword' is a background process. This way, any 'editor' command can be used. Be sure to use the flag I<-b> when the external command detaches after startup (such as e.g. I does under MacOSX, or I, or I in X-windows mode), so that I can wait for the process to finish and then re-encrypt the file. =head2 Troubleshooting If your I keyset contains more than one secret keys, then I might get confused which key to use (it will always try only the first one). In that case, you must give I a hint: =over =item * Either use flag I<-u name@domain.org> in the invocation, or =item * Add I to your profile (and reload the profile), so that I may inspect this variable during the next invocation. =back When I starts, then it expects the stated file to be present in either plain text format, or in encrypted format (or not at all). However, when the file is present in both formats, e.g. simultaneously I and I, then I will abort with an error message. You then have to choose which file you want to keep, and remove the other. If you want to know what actions I takes, be sure to use the flag I<-v> for increased verbosity. =head2 Security-related Remarks The strength of the encryption will rely on two factors: I and your pass phrase. I doesn't know how good either are. So far, I appears unbreakable, so make sure that you use a good pass phrase. The built in 'clobber' mechanism, which overwrites plain text files, seems good enough for me. For increased security, get hold of an external tool such as I. I is not a replacement for encrypted file systems; it is intended to be used on a per-file basis. An encrypted home directory or partition will greatly increase the security of your system incase of e.g. hardware theft. When using the flag I<-w>, make sure that you hit ENTER and let I re-encrypt the processed file. =cut