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;
$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 });
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:
@$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);
}
=for :list
* C<YKCHALRESP> - Path to the L<ykchalresp(1)> program
+* C<YKCHALRESP_FLAGS> - Extra arguments to the B<ykchalresp> program
* C<YKINFO> - Path to the L<ykinfo(1)> program
+* C<YKINFO_FLAGS> - Extra arguments to the B<ykinfo> program
-C<YubiKey> searches for these programs in the same way perl typically searches for executables (using the
+B<YubiKey> searches for these programs in the same way perl typically searches for executables (using the
C<PATH> 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.
-#!/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 = <STDIN>;
+
+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;
-#!/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;
+}
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);
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';
{
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';