=head1 NAME

Fugu::Signify - verify a signify(1) signature, and read and write a SHA256 manifest

=head1 SYNOPSIS

    use Fugu::Signify;

    my $sig = Fugu::Signify->new(
        keys => [
            '/etc/signify/openbsd-78-base.pub',
            '/etc/signify/openbsd-79-base.pub',
        ],
    );

    die $sig->error unless $sig->is_available;

    my $key = $sig->verify('/var/cache/SHA256', '/var/cache/SHA256.sig')
        or die $sig->error;

    $key = $sig->verify_manifest(
        manifest => '/var/cache/SHA256',
        files    => { 'miniroot78.img' => $tmp_path },
    ) or die $sig->error;

=head1 DESCRIPTION

Fugu::Signify verifies a file against a signify(1) public key and a
signature file. It also verifies each named file of a signed SHA256
manifest against its digest. It reads and writes the manifest form
itself, so a producer and a checker share one implementation. The module runs signify(1) through
L<Fugu::Process>, with an argument list and never a shell. It computes
each manifest digest with core L<Digest::SHA>, and the digest streams
from a file handle, so a large file never enters memory whole.

The object holds a small key set, in trust order. A caller puts the
current key first and the next key second. The verification methods
return the key that matched, so a caller learns that a release moved
to the next key.

Every recoverable failure returns C<undef>, and C<error> holds the
reason. An absent signify(1) is a clean failure, and not a die. The
module never logs. The caller decides what to report.

=head2 new

C<new(keys =E<gt> \@paths, command =E<gt> $command)> builds a
verifier. The method resolves the command once, and it runs no
process.

C<keys> is an array reference of public key file paths. The argument
is necessary and must not be empty. The order is the trust order.
C<new> dies when C<keys> is absent, when it is not an array
reference, and when it is empty. Each one is a programming error.

C<command> names the signify command, as a name or as an absolute
path. Without the argument, the module walks C<$ENV{PATH}> over the
search list C<signify-openbsd>, then C<signify>.

C<new> must not die for an absent command. It sets C<error> instead,
and C<is_available> then returns 0.

=head2 is_available

C<is_available()> returns 1 when the object resolved an executable
command. It returns 0 otherwise. The method runs no process, and it
never dies.

=head2 command

C<command()> returns the resolved command, or C<undef>. An operator
who installed the wrong C<signify> needs this answer, and a caller
can put it in a diagnostic.

=head2 error

C<error()> returns the reason of the most recent failure. It returns
C<undef> after a success. Each verification method clears the reason
before it starts.

For a signature that no key verified, the string names the file. It
then names each key with the first line of its signify diagnostic:

    /var/cache/SHA256: no key verified the signature:
        /etc/signify/openbsd-78-base.pub: signature verification failed;
        /etc/signify/openbsd-79-base.pub: can't open /etc/signify/openbsd-79-base.pub

A caller thus tells a wrong key from an absent key file.

=head2 command_absent

C<command_absent()> returns 1 when the most recent failure means that
signify(1) never ran. It covers a command that the search list did
not resolve, and a command that failed to execve(2). It returns 0
otherwise.

An absent command is an install problem, and a failed signature is an
integrity problem. The two answers let a caller tell them apart.

=head2 verify

C<verify($file, $sigfile)> verifies one file against the key set, in
order. C<$sigfile> defaults to C<"$file.sig">, which is the default
of signify(1) itself.

The method returns the public key file that verified the signature.
It returns C<undef> on every failure. The method fails closed: an
absent command, an absent file, an absent signature file, and a
signature that no key verified are each a failure.

=head2 verify_manifest

C<verify_manifest(manifest =E<gt> $path, signature =E<gt> $path,
files =E<gt> \%map)> verifies a signed SHA256 manifest, and then
verifies the digest of each named file.

C<manifest> is the path of the signed SHA256 file. The argument is
necessary. C<signature> defaults to C<"$manifest.sig">. C<files> is a
hash reference: each key is a name in the manifest, and each value is
the local path to digest. C<files> is necessary and must not be
empty. The module must never choose which file to check, so an empty
C<files> is a programming error, and the method dies.

The method verifies the manifest signature first. No file is digested
before the manifest verifies. It then refuses a manifest above
C<MAX_MANIFEST_SIZE>, which is 1 MiB. A name that the manifest does
not hold, a local file that does not open, and a digest mismatch are
each a failure. A mismatch names the file, the expected digest and
the computed digest.

The method returns the public key file that verified the manifest, or
C<undef> on every failure.

The manifest name and the local path can differ. A caller therefore
verifies a file before it moves the file into place:

    my $key = $sig->verify_manifest(
        manifest => "$cache/SHA256",
        files    => { 'miniroot78.img' => $tmp_path },
    );

=head2 parse_manifest

C<parse_manifest($bytes)> is the public form of the parser that
C<verify_manifest> uses. It returns a hash reference of manifest key to
lower-case digest.

Two callers read a manifest without a signature at that moment. A
rotation writes a manifest, and it must read the file that it wrote. A
site check compares a manifest against the files beside it, and a site
build cannot sign. A private parser would make each one write the line
form again.

The method verifies nothing. A caller that needs the signature calls
C<verify_manifest>, which verifies the signature before it digests one
file.

An undef input, an empty manifest, a line that the method cannot
parse, a digest that is not 64 hexadecimal characters, and a duplicate
key are each a failure.

=head2 write_manifest

C<write_manifest(\%digests)> returns the text of a C<SHA256> manifest.
Each line holds C<SHA256 (key) = digest>.

The keys sort in ascending order, so two runs of a rotation write one
byte sequence. A diff of two manifests then shows the change only. The
method lowercases each digest.

The key of a line is a file name, a file path, or a download URL,
whichever the producer writes. The method therefore rejects only a key
that another reader cannot carry.

C<parse_manifest> takes the text up to the last parenthesis. It
therefore reads a key with a parenthesis back without a change. A
stricter reader does not.

A parenthesis ends the key in a reader that stops at the first one.
Whitespace breaks a reader that splits a line on space. A manifest
travels to sha256(1) and to C<scripts/deps>, so the writer holds a
key to the strict form.

The whitespace test names the ASCII whitespace alone. A file name that
holds a byte above 127 therefore passes.

Each of these is a failure: an empty hash, an empty key, a key with a
parenthesis, a key with whitespace, and a digest that is not 64
hexadecimal characters.

The method dies when the argument is not a hash reference.

Neither method needs signify(1). An object whose command did not
resolve still parses and still writes.

C<new> still needs a non-empty C<keys>, because the verification
methods need a key set. A caller that only parses or writes names any
path, and it never reads C<error> from the construction:

    my $manifest = Fugu::Signify->new(keys => ['/nonexistent']);
    my $text     = $manifest->write_manifest(\%digests);

=head1 RETURN VALUES

C<verify()> and C<verify_manifest()> return the public key file that
verified the signature, or C<undef>. C<parse_manifest()> returns a
hash reference, and C<write_manifest()> returns text; each one returns
C<undef> on a failure. C<error()> returns the reason of the most
recent failure, or C<undef>.

=head1 CAVEATS

The module cannot sign, and it takes no secret key path. A signature
is a human act, without exception.

The command name differs by platform. OpenBSD base holds C<signify>.
The Debian and Ubuntu package C<signify-openbsd> installs the command
as C<signify-openbsd>. The Homebrew package C<signify-osx> installs
the command as C<signify>.

A caller under taint mode must pass C<command> as an absolute path.
C<$ENV{PATH}> is tainted, so a path from the search list cannot reach
execve(2) under C<perl -T>.

A caller under pledge(2) needs two promises: C<rpath> because the
module reads files, and C<proc exec> because the module runs a
command. The pledge belongs to the program, not to a library method.

=head1 SEE ALSO

signify(1), sha256(1), L<Digest::SHA>, L<Fugu::File>,
L<Fugu::KeyDir>, L<Fugu::Process>

=head1 AUTHORS

Dick Olsson E<lt>hi@senzilla.ioE<gt>

=cut
