From: Charles McGarvey Date: Wed, 13 Apr 2022 22:43:52 +0000 (-0600) Subject: Fix YubiKey unit test portability issues X-Git-Tag: v0.800~35 X-Git-Url: https://git.dogcows.com/gitweb?p=chaz%2Fp5-File-KDBX;a=commitdiff_plain;h=0d19b6cdca02d43bb5c6bdf7b2617ae5e54f2953 Fix YubiKey unit test portability issues --- diff --git a/lib/File/KDBX/Key/YubiKey.pm b/lib/File/KDBX/Key/YubiKey.pm index 7a7e238..e86b6e7 100644 --- a/lib/File/KDBX/Key/YubiKey.pm +++ b/lib/File/KDBX/Key/YubiKey.pm @@ -8,6 +8,7 @@ use File::KDBX::Constants qw(:yubikey); use File::KDBX::Error; use File::KDBX::Util qw(pad_pkcs7); use IPC::Open3; +use Ref::Util qw(is_arrayref); use Scope::Guard; use Symbol qw(gensym); use namespace::clean; @@ -38,7 +39,7 @@ sub challenge { $hook->($self, $challenge); } - my @cmd = ($self->ykchalresp, "-n$device", "-$slot", qw{-H -i-}, $timeout == 0 ? '-N' : ()); + my @cmd = ($self->_program('ykchalresp'), "-n$device", "-$slot", qw{-H -i-}, $timeout == 0 ? '-N' : ()); my ($pid, $child_in, $child_out, $child_err) = _run_ykpers(@cmd); push @cleanup, Scope::Guard->new(sub { kill $pid if defined $pid }); @@ -310,7 +311,7 @@ sub _get_yubikey_info { my $self = shift; my $device = shift; - my @cmd = ($self->ykinfo, "-n$device", qw{-a}); + my @cmd = ($self->_program('ykinfo'), "-n$device", qw{-a}); my $try = 0; TRY: @@ -361,12 +362,22 @@ sub _set_yubikey_info { @$self{keys %info} = values %info; } +sub _program { + my $self = shift; + my $name = shift; + my @cmd = $self->$name // $name; + my $name_uc = uc($name); + my $flags = $ENV{"${name_uc}_FLAGS"}; + push @cmd, split(/\h+/, $flags) if $flags; + return @cmd; +} + sub _run_ykpers { my ($child_err, $child_in, $child_out) = (gensym); my $pid = eval { open3($child_in, $child_out, $child_err, @_) }; if (my $err = $@) { throw "Failed to run $_[0] - Make sure you have the YubiKey Personalization Tool (CLI) package installed.\n", - error => $err; + error => $err; } return ($pid, $child_in, $child_out, $child_err); } @@ -436,9 +447,11 @@ See L for more information. =for :list * C - Path to the L program +* C - Extra arguments to the B program * C - Path to the L program +* C - Extra arguments to the B program -C searches for these programs in the same way perl typically searches for executables (using the +B searches for these programs in the same way perl typically searches for executables (using the C environment variable on many platforms). If the programs aren't installed normally, or if you want to override the default programs, these environment variables can be used. diff --git a/t/files/bin/ykchalresp b/t/files/bin/ykchalresp index 7cac1f5..c94a3d5 100755 --- a/t/files/bin/ykchalresp +++ b/t/files/bin/ykchalresp @@ -1,76 +1,55 @@ -#!/bin/sh +#!/usr/bin/env perl # This is a fake ykchalresp program that provides canned responses, for testing. -device= -slot= -blocking=1 -hmac= -in= +use warnings; +use strict; -while getopts 12HNn:i: arg -do - case "$arg" in - n) - device="$OPTARG" - ;; - 1) - slot=1 - ;; - 2) - slot=2 - ;; - H) - hmac=1 - ;; - N) - blocking=0 - ;; - i) - in="$OPTARG" - ;; - esac -done +use Getopt::Std; -if [ -z "$hmac" ] -then - echo 'HMAC-SHA1 not requested' >&2 - exit 3 -fi +my %opts; +getopts('12HNn:i:', \%opts); -if [ "$in" != '-' ] -then - echo "Unexpected input file: $in" >&2 - exit 3 -fi +my ($device, $hmac, $nonblocking, $in) = @opts{qw(n H N i)}; -read challenge +if (!$hmac) { + print STDERR "HMAC-SHA1 not requested\n"; + exit 3; +} +elsif (!defined($in) || $in ne '-') { + $in //= '(none)'; + print STDERR "Unexpected input file: $in\n"; + exit 3; +} + +my $challenge = ; + +my $mock = $ENV{YKCHALRESP_MOCK} || ''; +if ($mock eq 'block') { + if ($nonblocking) { + print STDERR "Yubikey core error: operation would block\n"; + exit 1; + } + sleep 2; + succeed(); +} +elsif ($mock eq 'error') { + my $resp = $ENV{YKCHALRESP_ERROR} || 'not yet implemented'; + print STDERR "Yubikey core error: $resp\n"; + exit 1; +} +elsif ($mock eq 'usberror') { + print STDERR "USB error: something happened\n"; + exit 1; +} +else { # OK + succeed(); +} -succeed() { - echo "${YKCHALRESP_RESPONSE:-f000000000000000000000000000000000000000}" - exit 0 +sub succeed { + my $resp = $ENV{YKCHALRESP_RESPONSE} || 'f000000000000000000000000000000000000000'; + print "$resp\n"; + exit 0; } -case "$YKCHALRESP_MOCK" in - block) - if [ "$blocking" -eq 0 ] - then - echo "Yubikey core error: operation would block" >&2 - exit 1 - fi - sleep 2 - succeed - ;; - error) - echo "Yubikey core error: ${YKCHALRESP_ERROR:-not yet implemented}" >&2 - exit 1 - ;; - usberror) - echo "USB error: something happened" >&2 - exit 1 - ;; - *) # OK - succeed - ;; -esac -exit 2 +exit 2; diff --git a/t/files/bin/ykinfo b/t/files/bin/ykinfo index 8a93cc3..a8cc021 100755 --- a/t/files/bin/ykinfo +++ b/t/files/bin/ykinfo @@ -1,43 +1,37 @@ -#!/bin/sh +#!/usr/bin/env perl # This is a fake ykinfo program that provides canned responses, for testing. -device= -all= +use warnings; +use strict; -while getopts an: arg -do - case "$arg" in - n) - device="$OPTARG" - ;; - a) - all=1 - ;; - esac -done +use Getopt::Std; -case "$device" in - 0) - printf 'serial: 123 +our ($opt_a, $opt_n); +getopts('an:'); + +my $device = $opt_n // -1; + +if ($device == 0) { + print q{serial: 123 version: 2.0.0 touch_level: 0 vendor_id: 1050 product_id: 113 -' - exit 0 - ;; - 1) - printf 'serial: 456 +}; + exit 0; +} +elsif ($device == 1) { + print q{serial: 456 version: 3.0.1 touch_level: 10 vendor_id: 1050 product_id: 401 -' - exit 0 - ;; - *) - echo "Yubikey core error: no yubikey present" >&2 - exit 1 -esac +}; + exit 0; +} +else { + print STDERR "Yubikey core error: no yubikey present\n"; + exit 1; +} diff --git a/t/yubikey.t b/t/yubikey.t index 1ec1ed4..61ca28c 100644 --- a/t/yubikey.t +++ b/t/yubikey.t @@ -6,12 +6,12 @@ use strict; use lib 't/lib'; use TestCommon; +use Config; +use File::KDBX::Key::YubiKey; use Test::More; -BEGIN { use_ok 'File::KDBX::Key::YubiKey' } - -local $ENV{YKCHALRESP} = testfile(qw{bin ykchalresp}); -local $ENV{YKINFO} = testfile(qw{bin ykinfo}); +@ENV{qw(YKCHALRESP YKCHALRESP_FLAGS)} = ($Config{perlpath}, testfile(qw{bin ykchalresp})); +@ENV{qw(YKINFO YKINFO_FLAGS)} = ($Config{perlpath}, testfile(qw{bin ykinfo})); { my ($pre, $post); @@ -20,8 +20,7 @@ local $ENV{YKINFO} = testfile(qw{bin ykinfo}); post_challenge => sub { ++$post }, ); my $resp; - is exception { $resp = $key->challenge('foo') }, undef, - 'Do not throw during non-blocking response'; + is exception { $resp = $key->challenge('foo') }, undef, 'Do not throw during non-blocking response'; is $resp, "\xf0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0", 'Get a non-blocking challenge response'; is length($resp), 20, 'Response is the proper length'; is $pre, 1, 'The pre-challenge callback is called'; @@ -77,6 +76,7 @@ local $ENV{YKINFO} = testfile(qw{bin ykinfo}); { local $ENV{YKCHALRESP} = testfile(qw{bin nonexistent}); + local $ENV{YKCHALRESP_FLAGS} = undef; my $key = File::KDBX::Key::YubiKey->new; like exception { $key->challenge('foo') }, qr/failed to run|failed to receive challenge response/i, 'Throw if the program failed to run';