#!/usr/bin/perl # # Quick hack to make SHA256 files and create gpg detached signatures # for all packages and SHA256 files. use warnings; use File::Find; use Digest::MD5; use Digest::SHA; use POSIX qw(setsid dup STDIN_FILENO); umask(022); die "usage: $0 start_dir\n" unless @ARGV; $| = 1; # unbuffered stdout # Read passphrase early--we don't know if we need to use it yet # XXX - should verify passphrase is correct (try signing temp file?) system("stty -echo"); print "Enter passphrase: "; chomp(my $pass = ); system("stty echo"); print "\n"; # Also used by wanted() my @signfiles; my %filelist; foreach my $startdir (@ARGV) { undef @signfiles; undef %filelist; print "Checking signatures for $startdir"; find(\&wanted, $startdir); print "\n"; # Sign any files without a signature (detached or embedded) foreach (@signfiles) { my $pid = open(CHILD, "|-"); die "unable to fork\n" unless defined($pid); if ($pid == 0) { # Child if (/\.rpm$/) { # Disassociate from tty so we can pipe the passphrase setsid(); exec("rpm", "--addsign", $_); } elsif (/\.deb$/) { # dup stdin to a different fd for gpg, debsigs is using stdin. my $fd = dup(STDIN_FILENO) || die "dup: $!\n"; exec("debsigs", "--sign=origin", "--gpgopts=--batch,--no-tty,--pinentry-mode=loopback,--passphrase-fd=$fd", $_); } else { exec("gpg", "--yes", "--batch", "--no-tty", "--pinentry-mode=loopback", "--passphrase-fd=0", "-b", $_); } exit 1; } # Parent: write passphrase print CHILD $pass; close(CHILD); } print "Computing md5 and sha256 digests for $startdir\n"; foreach my $file (keys %filelist) { write_md5($file, @{$filelist{$file}}); write_sha($file, @{$filelist{$file}}); } } $pass =~ s/./x/g; # probably not effective exit(0); # XXX - return an error if warranted sub wanted { my $dir = $_; if (-d && opendir(DIR, $dir)) { my @files = (); while (defined(my $file = readdir(DIR))) { next unless $file =~ /\.(gz|deb|rpm|pkg)$/; push(@files, $file); } if (int(@files)) { print "."; $filelist{$File::Find::name} = \@files; foreach (@files) { # Only need to sign if no valid existing signature my $file = "$dir/$_"; my $sign_it = 1; if (/\.rpm$/) { my $output = `rpm --checksig $file`; if ($output =~ / gpg .*OK$/) { $sign_it = 0; } } elsif (/\.deb$/) { # Old way: just check for presence of signature #my @out = `debsigs --list --gpgopts=--batch,--no-tty,--pinentry-mode=loopback $file`; #$sign_it = 0 if grep(": signed by ", @out); # Note: requires local changes to debsigs system("debsigs --verify --gpgopts=--batch,--no-tty,--pinentry-mode=loopback $file >/dev/null 2>&1"); $sign_it = $? >> 8; } elsif (-f "$file.sig") { system("gpg --quiet --batch --no-tty --verify $file.sig $file >/dev/null 2>&1"); $sign_it = $? >> 8; } push(@signfiles, $File::Find::name . "/$_") if $sign_it; } } closedir(DIR); } } sub write_md5 { my ($dir, @files) = @_; # Calculate MD5 digest of all the files in the dir my $md5 = Digest::MD5->new; my %digests; foreach my $file (@files) { if (open(FILE, '<', "$dir/$file")) { $md5->addfile(*FILE); $digests{$file} = $md5->hexdigest; close(FILE); $md5->reset; } else { warn "$dir/$file: $!\n"; } } if (scalar(keys %digests)) { my $modified = 1; if (open(MD5, '<', "$dir/MD5")) { my %old_digests; while () { chomp; my ($digest, $file) = split; $old_digests{$file} = $digest; } close(MD5); # Check existing digest file for modifications if (scalar(keys %old_digests) == scalar(keys %digests)) { $modified = 0; foreach my $file (sort keys %digests) { if ($old_digests{$file} ne $digests{$file}) { $modified = 1; last; } } } } if ($modified) { if (open(MD5, '>', "$dir/MD5")) { # Digest file missing or changed, write a new one foreach my $file (sort keys %digests) { print MD5 "$digests{$file} $file\n"; } close(MD5); } else { warn "$dir/MD5: $!\n"; } open(GPG, "|gpg --yes --batch --no-tty --pinentry-mode=loopback --passphrase-fd=0 -b $dir/MD5"); print GPG $pass; close(GPG); } } } sub write_sha { my ($dir, @files) = @_; # Calculate SHA256 digest of all the files in the dir my $sha = Digest::SHA->new(256); my %digests; foreach my $file (@files) { if (open(FILE, '<', "$dir/$file")) { $sha->addfile(*FILE); $digests{$file} = $sha->hexdigest; close(FILE); $sha->reset; } else { warn "$dir/$file: $!\n"; } } if (scalar(keys %digests)) { my $modified = 1; if (open(SHA256, '<', "$dir/SHA256")) { my %old_digests; while () { chomp; my ($digest, $file) = split; $old_digests{$file} = $digest; } close(SHA256); # Check existing digest file for modifications if (scalar(keys %old_digests) == scalar(keys %digests)) { $modified = 0; foreach my $file (sort keys %digests) { if ($old_digests{$file} ne $digests{$file}) { $modified = 1; last; } } } } if ($modified) { if (open(SHA256, '>', "$dir/SHA256")) { # Digest file missing or changed, write a new one foreach my $file (sort keys %digests) { print SHA256 "$digests{$file} $file\n"; } close(SHA256); } else { warn "$dir/SHA256: $!\n"; } open(GPG, "|gpg --yes --batch --pinentry-mode=loopback --passphrase-fd=0 -b $dir/SHA256"); print GPG $pass; close(GPG); } } }