Crypt-SecretBuffer
view release on metacpan or search on metacpan
lib/Crypt/SecretBuffer.pm view on Meta::CPAN
# take a guess at that. The caller can supply prompt_fh => \*STDOUT if they want to.
else {
delete @{$state}{qw( prompt char_mask )};
}
} else {
$state->{writing_to} ||= 'supplied_handle';
}
}
# If the user wants control over the keypresses, need to disable line-editing mode.
# ConsoleState obj with auto_restore restores the console state when it goes out of scope.
my $input_by_chars= defined $char_mask || defined $char_count
|| defined $char_class || defined $timeout
|| $utf8;
$state->{ttystate} ||= Crypt::SecretBuffer::ConsoleState->maybe_new(handle => $input_fh);
my $ttystate= $state->{ttystate};
if ($ttystate && $ttystate->echo) {
$ttystate->auto_restore(1);
$ttystate->echo(0);
}
if ($ttystate && $ttystate->line_input && $input_by_chars) {
$ttystate->auto_restore(1);
$ttystate->line_input(0);
}
# Write the initial prompt
if ($print_prompt) {
my $suffix= '';
if (defined $start_len && defined $char_mask) {
my $n= $self->len - $start_len;
if ($n && $utf8) {
$n= 0;
my $span= $self->span(pos => $start_len, encoding => 'UTF-8');
++$n while $span->len && eval { $span->ltrim(qr/./) };
}
$suffix= $char_mask x $n;
}
$prompt_fh->print($state->{prompt} . $suffix) && $prompt_fh->flush
or croak "Failed to write $state->{writing_to}: $!";
}
my $ret;
if ($input_by_chars) {
read_loop: while (1) {
if (defined $timeout) {
my $start_t= Time::HiRes::time();
if (Crypt::SecretBuffer::Exports::_wait_fh_readable($input_fh, $timeout)) {
# deduct from timeout
$timeout -= Time::HiRes::time() - $start_t;
$timeout= 0 if $timeout < 0;
} else {
# use EINTR to signal a timeout was reached, same as if we'd used alarm()
$!= Errno::EINTR();
$ret= undef;
last;
}
}
# Windows Console events can deliver UTF-16 characters which we can transcode to UTF-8
if ($utf8 && $ttystate && $^O eq 'MSWin32') {
$ret= eval { $ttystate->_append_console_char($self) };
unless (defined $ret) {
# Errors about invalid surrogate pairs probably need seen by the user
# because their unicode input is not reaching the buffer reliably.
if ($@ =~ /surrogate/) {
my $msg= "\nWarning: $@. Your console settings are probably wrong.\n";
$prompt_fh? $prompt_fh->print($msg) : warn $msg;
}
$ret= undef;
last;
}
next unless $ret; # false means no complete character yet, try again
} else {
$ret= $self->append_read($input_fh, 1)
or last; # EOF or system error
}
my $end_pos= $self->length - 1;
# Handle control characters
if ($self->index(qr/[\0-\x1F\x7F]/, $end_pos) == $end_pos) {
# If it is \r or \n, end. If char_count was requested, and we didn't
# end by that logic, then we don't have the requested char count, so
# return false.
if ($self->index(qr/[\r\n]/, $end_pos) == $end_pos) {
$self->length($end_pos); # remove CR or LF
last;
}
# handle backspace
elsif ($self->index(qr/[\b\x7F]/, $end_pos) == $end_pos) {
$self->length($end_pos); # remove backspace
if ($self->length > $start_len) {
$self->length($self->length-1); # remove previous char
# print a backspace + space + backspace to erase the final mask character
if (length $char_mask) {
$prompt_fh->print(
("\b" x length $char_mask)
.(" " x length $char_mask)
.("\b" x length $char_mask))
&& $prompt_fh->flush
or croak "Failed to write $state->{writing_to}: $!";
}
}
}
# just ignore any other control char
else {
$self->length($end_pos);
}
}
elsif ($utf8) {
# count chars, and also find out if there are invalid or incomplete characters
my $span= $self->span(pos => ($start_len || 0), encoding => 'UTF-8');
my $count= 0;
while ($span->len) {
my $ch= eval { $span->parse($char_class) };
unless ($ch) {
if (!defined $ch) {
# partial character? loop again until we get all of it.
$span->encoding(0);
my $ch_len= $span->starts_with(qr/[\xC0-\xDF]/)? 2
: $span->starts_with(qr/[\xE0-\xEF]/)? 3
: 4;
next read_loop if $span->len < $ch_len;
# else it's an invalid UTF-8 sequence. Hard to say what the right thing to
# do is here, but since passwords need to be an exact match, lets delete
# the invalid chars and emit a warning that hopefully the user can see.
lib/Crypt/SecretBuffer.pm view on Meta::CPAN
SecretBuffer tries not to expose the secret, so the default behavior of this function is to
return the string C<< "[REDACTED]" >> or whatever custom string you store in C<stringify_mask>.
If you set C<stringify_mask> to C<undef>, it exposes the secret. You can use C<local> to limit
the scope of this exposure.
=head2 unmask_to
@ret= $buf->unmask_to(sub{
# use $_[0]
});
Pass the secret value as an argument to a code-ref, and propagate the return value.
(C<wantarray> is propagated to the coderef).
If you want to prevent the secret from leaking into perl's heap, the coderef should be an XS
function, or strictly use C<< $_[0] >> without copying it to a 'my' variable.
See also: L</unmask_secrets_to>.
=head2 index
$ofs= $buf->index($str_or_charclass, $from_offset=0);
Like Perl's C<index> function, it scans the string from an optional offset and returns the
location the string was found, or -1 if it doesn't exist. This can also scan for a character
class provided in a C<< qr// >> expression, like the L</scan> function supports.
C<$from_offset> may be negative to count backward from the end of the buffer.
=head2 rindex
$ofs= $buf->index($str_or_charclass, $from_offset=-1);
Like L</index> but in reverse, where the default C<$from_offset> is -1 (end of buffer).
=head2 scan
($ofs, $len)= $buf->scan($s, $flags=0, $ofs=0, $len=undef);
This function scans through the buffer looking for the first match of a string
or a character class. The scan can optionally be limited to an offset and
length describing a substring of the buffer. The return value is the position
of the start of the match and number of I<bytes> matched (which can be greater
than one when matching a character class in UTF-8, or if C<MATCH_MULTI> flag is
requested). On failure, the return value is an empty list. (versions before
0.19 returned C<< $len == 0 >> and a seldom-useful C<$ofs>)
Unlike C<rindex>, a reverse scan match must fall entirely within the range of
C<< ($ofs .. $ofs+$len) >> rather than just starting before the ending offset.
Eventually, this function may be enhanced with full regex support, but for now
it is limited to one character class and optionally a '+' modifier as an alias
for flag C<MATCH_MULTI>. Until that enhancement occurs, your regex notation
for the character class must start with C<[> and end with C<]>, or be one of
the special classes C<< . \s \S \d \D \w \W >>.
($ofs, $len)= $buf->scan(qr/\w+/); # implies MATCH_MULTI
The C<$flags> may be a bitwise OR of the L</Match Flags> and one
L<Character Encoding|/Character Encodings>.
Note that C<$ofs> and C<$len> are still byte positions, and still suitable for
L</substr> on the buffer, which is different from Perl's substr on a unicode
string which works in terms of codepoint counts.
For a more convenient interface to this functionality, use L</span> to create a
L<Span object|Crypt::SecretBuffer::Span> and then call its methods.
=head2 memcmp
$cmp= $buf->memcmp($buf2);
Compare contents of the buffer byte-by-byte to another SecretBuffer (or Span, or plain scalar)
in the same manner as the C function C<memcmp>. Since version 0.019 this runs in constant time
of the shortest string. This may or may not be enough timing obfuscation to prevent
side-channel attacks in your application; better constant-time algorithms exist, and it is up
to you to decide whether you need one.
=head2 append_lenprefixed
$buf->append_lenprefixed(@byte_strings);
Append one or more strings (which can be a SecretBuffer, L<Span|Crypt::SecretBuffer::Span>, or
plain scalar of bytes) to the buffer, prefixing each with a variable-length encoding of the
number of bytes that follows.
The variable length encoding is base128 big-endian, the same as implemented in
C<< pack('w',...) >>. In other words,
for (@byte_strings) {
my $len= ref($_)? $_->length : length($_);
$buf->append(pack("w", $len))->append($_);
}
See L<Crypt::SecretBuffer::Span/parse_lenprefixed> for the decoding routine.
=head2 append_base128be
Append a variable-length integer encoded as base128 big-endian. (same as C<< pack('w') >>)
=head2 append_base128le
Append a variable-length integer encoded as base128 little-endian.
=head2 append_asn1_der_length
Append a variable-length integer encoded as the format used by ASN.1 DER for element lengths.
=head2 append_random
$byte_count= $buf->append_random($n_bytes);
$byte_count= $buf->append_random($n_bytes, NONBLOCK);
$byte_count= $buf->append_random($n_bytes, 'NONBLOCK');
Append N cryptographic-quality random bytes. On POSIX systems, this uses either the C library
C<getrandom> call with C<GRND_RANDOM>, or if that isn't available, it reads from C</dev/random>.
The C<NONBLOCK> flag can be used to avoid blocking on insufficient entropy. On Windows, this
uses C<CryptGenRandom> and the flag has no effect because it always returns the requested number
of bytes and never blocks.
=head2 append_console_line
$bool= $buf->append_console_line($input_fh);
$bool= $buf->append_console_line($input_fh, %options);
$bool= $buf->append_console_line(%options);
# Options:
# prompt => "Enter Password: " print prompt after disabling echo
# input_fh => $readable_handle handle for reading chars
# prompt_fh => $writeable_handle handle for writing prompt
# utf8 => $bool add only valid utf-8 sequences to the buffer
# char_mask => "*" show each char typed as '*'
# char_count => $n return success only at exactly N characters
# char_max => $n stop adding characters after $n added
# char_class => qr/[...]/ limit to members of character class
# timeout => $seconds abort collecting characters, return undef
# state => \%state track state across calls
This turns off TTY echo (if the handle is a Unix TTY or Windows Console) and reads and appends
characters until newline or EOF (and does not store the \r or \n characters).
It returns true if the read "completed" with a line terminator, or false on EOF, or
C<undef> on any OS error. Characters may be added to the buffer even when it returns false.
There may also be no characters added when it returns true, if the user just hits <enter>.
When possible, this reads directly from the OS to avoid buffering the secret in libc or Perl,
but reads from the buffer if you already have input data in one of those buffers, or if the
file handle is a virtual Perl handle not backed by the OS.
Options:
=over
=item prompt
This message is printed and flushed after disabling TTY echo. This helps prevent a race
condition where a scripted interaction could start typing a password in response to the prompt
before the echo was disabled. A defined value for this setting also echoes back a C<"\n">
to the user on completion.
=item input_fh
The file handle from which this function reads characters. The default is C<< /dev/tty >> on
Unix and C<CONIN$> on MSWin32. If this file handle is not writeable (such as C<< \*STDIN >>) and
you requested a prompt, you should probably also specify C<prompt_fh>.
=item prompt_fh
The file handle for writing the prompt and/or C<char_mask>. If C<$input_fh> defaulted to
C</dev/tty> this defaults to the same (or C<CONOUT$> on MSWin32). If C<$input_fh> is exactly
C<< \*STDIN >>, the default is to try opening C<< /dev/tty >> or C<CONOUT$> for writing.
Else the default for a terminal or socket is to write to C<$input_fh>, and if none of these
cases is true then the default is to suppress all prompting. These defaults should allow you
to pass C<< input_fh => \*STDIN >> for the behavior of simply allowing the password to be piped
into the command when it isn't a terminal without having to test those conditions yourself.
=item utf8
Read unicode characters into the buffer as UTF-8 byte sequences. If the input contains a
malformed or invalid character, croak. This option implies a default C<char_class> of
C<< qr/[\p{Print}]/ >>. (i.e. by default only printable characters can be added to the password)
=item char_mask
Display this static string every time the user types a key, for feedback. A common choice would
be C<'*'> or C<'* '>.
=item char_count
Change the completion condition to having added exactly C<$n> characters to the buffer.
The method returns true as soon as this count is reached. Pressing C<Enter> before the
required count is treated as an incomplete read and returns false, even though input was
successfully read.
Note that unicode is not supported yet, so this really means C<$n> bytes.
=item char_max
Stop appending characters when C<$n> have been added to the buffer, but don't return until the
user presses newline. This should only be used with C<char_mask> so that the user can see that
additional keys are not being accepted.
=item char_class
Restrict the permitted characters. This must be a Regexp-ref of a single character class.
Any character the user enters which is not in this class will be ignored and not added to the
buffer.
=item timeout
If a complete line has not been received within this number of seconds (may be fractional or
zero) return C<undef> and set C<$!> to C<EINTR>. This can be combined with C<state> to get the
rough equivalent of nonblocking behavior.
=item state
On an error, store the state of prompt collection into this hashref. Passing the same hashref
to the next call will resume the password prompt where it left off. If you destroyed the
printed state of the prompt on the terminal inbetween calls, set field
C<< $state->{re_prompt} = 1 >> to request re-printing the prompt. Beware that you need to
clear this hashref to trigger a restore of the console's 'echo' state flag.
=back
When using options C<char_mask>, C<char_count>, or C<char_class>, the TTY line-input mode is
disabled and the code processes each character as it is received, manually handling backspace
etc. The code does I<not> handle TTY geometry or unicode, and will display incorrectly if the
user's input reaches the edge of the terminal. This won't usually be a problem if you just
want some fancy handling of N-digit codes where you want to return as soon as they reach the
limit:
$buf->append_console_line(STDIN,
prompt => "PIN: [ ]\b\b\b\b\b\b\b\b\b\b\b\b\b",
char_mask => "* ",
char_count => 6,
char_class => qr/[0-9]/,
);
If this method doesn't have quite the behavior you were looking for, the read loop is perl
(not XS) and the cross-platform handling of console modes happens in
L<Crypt::SecretBuffer::ConsoleState>, so it should be reasonably easy to copy/paste and make
your own.
=head2 append_sysread
$byte_count= $buf->append_sysread($fh, $count);
This performs a low-level read from the file handle and appends the bytes to the buffer.
It must be a real file handle with an underlying file descriptor number (C<fileno>).
Like C<sysread>, on error it returns C<undef> and on success it returns the count added.
This ignores Perl I/O layers.
=head2 append_read
$byte_count= $buf->append_read($fh, $count);
This is a relaxed version of C<append_sysread> that when possible, reads directly from the OS
to avoid buffering the secret in libc or Perl, but reads from the Perl buffer if you already
have input data in one of those buffers, or if the file handle is a virtual Perl handle not
backed by the OS.
=head2 syswrite
$byte_count= $buf->syswrite($fh); # one syswrite attempt of whole buffer
$byte_count= $buf->syswrite($fh, $count); # prefix of buffer
$byte_count= $buf->syswrite($fh, $count, $offset); # substr of buffer
This performs a low-level write from the buffer into a file handle. It must be a real file
handle with an underlying file descriptor (C<fileno>). If the handle has pending bytes in its
IO buffer, those are flushed first. Like C<syswrite>, this returns C<undef> on an OS error,
and otherwise returns the number of bytes written. It only makes one write attempt, which may
be shorter than the requested C<$count>. This ignores Perl I/O layers.
=head2 write_async
$async_result= $buf->write_async($fh); # whole buffer
$async_result= $buf->write_async($fh, $count); # prefix of buffer
$async_result= $buf->write_async($fh, $count, $offset); # substr of buffer
($wrote, $errno)= $async_result->wait;
($wrote, $errno)= $async_result->wait($seconds);
Write data into a file handle, using a background thread if needed. Most likely, you will be
writing into a pipe, and your secret will be smaller than the OS pipe buffer, so this will
complete immediately without spawning a thread. It also immediately returns if there was a
fatal error attempting to write the handle. But if you have a large secret, or are writing into
a type of handle that can't buffer it, this function will duplicate your file handle and copy
the secret and pass them to a background thread to do the writing.
( run in 0.853 second using v1.01-cache-2.11-cpan-e1769b4cff6 )