Skip to content

Instantly share code, notes, and snippets.

@afresh1
Last active Dec 13, 2020
Embed
What would you like to do?
Helper to download OpenBSD dist files (mostly bsd.rd) and verify them with signify.
#!/usr/bin/perl -T
use v5.16;
use warnings;
delete $ENV{PATH};
delete $ENV{ENV};
use OpenBSD::Unveil;
use OpenBSD::Pledge;
pledge( qw< proc exec rpath unveil > );
die "Usage: $0 [[\$version/]\$arch/]fileXX.ext [...]\n" unless @ARGV;
my $base_url = 'cdn.openbsd.org';
if ( -e "/etc/installurl" ) {
open my $fh, '<', "/etc/installurl"
or die "Unable to open /etc/installurl: $!";
$base_url = readline $fh;
close $fh;
chomp $base_url;
}
{
$base_url =~ m{^
(?<scheme>\w+://)?
(?<host> [\w\.]+ (?: : (?<port>\d+) )? )
(?<path> .* )?
$}x;
$base_url = join '', (
( $+{scheme} || 'https://' ),
$+{host},
( $+{path} || '/pub/OpenBSD' )
);
$base_url =~ s{/$}{};
}
my %keys = do {
opendir my $dh, "/etc/signify" or die "Unable to opendir signify: $!";
map { /-(\d+)(\d)-/; "$1.$2" => "/etc/signify/$_" }
grep { /-base\.pub$/ }
readdir $dh;
};
($keys{snapshots}) = reverse sort values %keys;
# Seems safer to run this before unveiling things so we
# don't have access to sysctl while processing untrusted input
my ($default_version, $default_arch) = do {
qx{/sbin/sysctl -n kern.version} =~ m{^
OpenBSD \s+ ( \d+\.\d (?: - \w+ )? )
.* /sys/arch/([^/]+)/compile/
}xms;
};
$default_version = 'snapshots' if $default_version =~ /-/;
unveil( "/usr/bin/signify" => "x" );
unveil( "/usr/bin/ftp" => "x" );
pledge( qw< proc exec > );
my %get;
for my $path (@ARGV) {
my ($file, @extra) = reverse split /\//, $path;
my ($arch, $version);
while ( my $item = shift @extra ) {
if ( $item =~ /^(?: \d+\.\d | snapshots )$/x ) {
$version ||= $item;
}
else {
$arch ||= $item;
}
}
$arch ||= $default_arch;
$version ||= $default_version;
# untaint
($arch) = $arch =~ /^(\w+)$/;
($version) = $version =~ /^([.\w]+)$/;
my $url = join '/', $base_url, $version, $arch;
my $sig = $get{$url}{sig} ||= get_sha256_sig("$url/SHA256.sig");
my @files = grep { $_ =~ $file } @{ $sig->{files} };
die "Unable to find $file in SHA256.sig\n"
unless @files;
$get{$url}{key} = key_for($sig->{content}) || $keys{$version}
or die "No signify key for version: $version";;
push @{ $get{$url}{files} }, @files;
}
foreach my $url (sort keys %get) {
my $files = $get{$url}{files};
system "/usr/bin/ftp", map { "$url/$_" } @{ $files };
exit $? if $?;
signify( $get{$url}{key}, $get{$url}{sig}{content}, @{ $files } )
}
sub get_sha256_sig {
my $content = get_content(@_);
my @files = $content =~ /SHA256 \s+ \( ([^)]*) \) \s = /gxms;
return { content => $content, files => \@files };
}
sub get_content {
open my $fh, "-|", qw< /usr/bin/ftp -o- >, @_
or die "Unable to span ftp @_: $!";
my $output = do { local $/; readline $fh };
close $fh;
exit $? if $?;
return $output;
}
sub key_for {
my ($sig) = @_;
if ( $sig =~ /^untrusted comment: verify with (.*?)\n/ ) {
my $name = $1;
my ($key) = grep { /\Q$name\E$/ } values %keys;
return $key;
}
return undef;
}
sub signify {
my ($key, $sig, @files) = @_;
open my $fh, '|-', qw< /usr/bin/signify -C -x- -p >, $key, @files
or die "Unable to spawn signify: $!";
print $fh $sig;
close $fh;
exit $? if $?;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment