NAME
Fugu::Signer - one shape for the three modules that drive a signing command
SYNOPSIS
# A caller reads one type, and it then knows the other two.
my $signer = Fugu::Signify->new;
die $signer->error unless $signer->is_available;
# Each type takes the arguments that its unit names, such as
# the comment of a signify(1) key.
$signer->generate(
comment => 'example key',
public => '/etc/keys/example.pub',
secret => '/etc/keys/example.sec',
) or die $signer->error;
$signer->sign(
secret => '/etc/keys/example.sec',
file => '/var/www/htdocs/SHA256',
signature => '/var/www/htdocs/SHA256.sig',
) or die $signer->error;
my $key = $signer->verify(
keys => [ '/etc/keys/current.pub', '/etc/keys/next.pub' ],
file => '/var/www/htdocs/SHA256',
signature => '/var/www/htdocs/SHA256.sig',
) or die $signer->error;
# A subclass names its command and holds one command line for
# each verb.
package Fugu::Example;
our @ISA = ('Fugu::Signer');
sub _command_label ($) { return 'example' }
sub _command_defaults ($) { return ('example') }
sub _sign ( $self, %args )
{
$self->_run( [ '-s', $args{secret}, '-m', $args{file},
'-x', $args{signature} ],
"cannot sign $args{file}" ) or return;
return 1;
}
DESCRIPTION
Fugu::Signer is the parent class of Fugu::Signify over signify(1), of Fugu::OpenPGP over gpg(1), and of Fugu::X509 over openssl(1).
The parent holds what the three share: the constructor and the command resolution, the three verbs, the run through Fugu::Process, the key walk of a verification, and the failure convention. A subclass holds its file formats, its readers, and the arguments of each command line. A consumer that learns one type then knows the other two.
The three verbs are generate, sign and verify, and their argument names are public, secret, keys, file and signature. keys is a list of paths, and each other one is a path. A subclass adds an argument that its type needs, and its unit names it.
Every private key operation runs in the command. No method takes or answers the bytes of a secret half: a secret half enters as a path and leaves as a file. A secret half that passes through Perl sits in the heap of a long process, and a secret half that a command writes never does.
The private directory of a generator
generate refuses a path that exists, and it makes a key with no passphrase.
The command writes the secret half into a private directory beside its destination. The method sets the owner-only mode on the file there, and one rename then moves it into place. No wider access exists at any moment.
The directory sits beside the destination, because the rename that publishes the secret half must stay inside one filesystem. The method removes the directory on every exit, also after a failure of the command.
The key walk of a verification
verify takes keys, a list of public key paths in trust order: the current key first, and the next key second. The walk pins one key in one run, so it reads no keyring, no home and no agent of the user. It checks no chain. It answers the path of the key that verified.
When no key verifies, error names the file and then one reason for each key:
/var/www/htdocs/SHA256: no key verified the signature:
/etc/keys/current.pub: checked against wrong key;
/etc/keys/next.pub: checked against wrong key
Each subclass writes that one shape, so a caller tells a wrong key from an absent key file under each type.
When a run does not start, the walk stops at that key, and error holds that one reason. A failed fork, a failed chdir and an absent command give the same answer for every later key. Such a failure is no integrity failure.
An empty keys list is a programming error, and the method dies.
An input path
A command method takes paths. It refuses an input path that is not a plain file before it runs the command. A path of keys is the exception: a key that does not read is one reason of the walk.
public and secret of generate are outputs, and so is signature of sign.
An absent command
command_absent reports 1 only after a failure in which a method needed the command and it never ran. That failure is one of two: no command resolved, or the execve(2) failed. It reports 0 after every other failure.
An absent command is an install problem, and a failed signature is an integrity problem. The caller must tell them apart.
METHODS
new
new(command => $command, timeout => $seconds) builds a generator, a signer and a verifier. The method resolves the command once, through Fugu::Process->find_command, and it runs no process.
command names the command, as a name or as an absolute path. Without the argument, the module walks $ENV{PATH} over the search list of the subclass.
timeout is the bound of one run, in seconds, and the default is 30. A value that is no positive number is a programming error, and the method dies.
new must not die for an absent command. It sets error instead, and is_available then returns 0.
is_available
is_available() returns 1 when verify can run, and 0 when it cannot. The method runs no process, and it never dies. A subclass with a verifier in Perl returns 1 with no command.
command
command() returns the resolved command path, or undef. An operator who installed the wrong command needs this answer in a diagnostic.
error
error() returns the reason of the most recent failure, or undef after a success. Each public method clears the reason before it starts.
command_absent
command_absent() returns 1 after a failure in which no command resolved, or the execve(2) failed. It returns 0 after every other failure.
generate
generate(public => $path, secret => $path) makes a key pair with no passphrase. It returns 1, or undef with the reason in error.
sign
sign(secret => $path, file => $path, signature => $path) signs one file with a private half. It returns 1, or undef with the reason in error.
The command reads the private half from the path, and it writes the signature file itself. A second call over one signature path replaces the file, because a rotation signs one manifest again.
verify
verify(keys => \@paths, file => $path, signature => $path) verifies one file against the key set, in trust order. It returns the key path that verified the signature, or undef with the reason in error.
A SUBCLASS
A subclass inherits this class with our @ISA. It holds five hooks, it can override two more, and it can call the parts below. Each method of the parent reports through error, so a subclass writes no reason of its own into the object.
The hooks of a subclass
_command_label()- the name of the command in a diagnostic, such asgpg. The name of the module and the name of the command differ._command_defaults()- the default search list of the command, in the order of preference.newwalks$ENV{PATH}over it when the caller named no command._generate(%args)- run the generator of one type. The hook returns 1, orundefwith the reason inerror.secretnames a path inside the private directory, and the parent moves that file into place.publicnames its destination._sign(%args)- run the signer of one type oversecret,fileandsignature. The hook returns 1, orundefwith the reason inerror._verify($key, %args)- verifyfileagainstsignaturewith the one key, and pin that key in the run. The hook returnsundefwhen the key verified, or the reason that it did not. It resolves the command with_commanditself, because a subclass can verify with no command.
The overrides of a subclass
The parent holds a default for each method below, so a subclass takes it or overrides it.
_reason($result)- the reason of a run that reached the child and failed, and that did not time out. The default takes the first line of the diagnostic, or the exit code. A subclass that reads the diagnostic form of its command overrides the method._stop_helpers($dir)- stop each helper process that the command started under the temporary directory, before the tree goes. The default stops nothing. Fugu::OpenPGP overrides the method, because gpg(1) starts an agent.
The parts that a subclass calls
_begin()- start one call: clear the reason and each flag of a failure. Every public method of a subclass calls it first._set_error($reason)- the failure return of every method: the reason goes toerror, and the method answersundef._command()- the command of one call, orundefwith the reason inerror. It setscommand_absentfor the call, and it sets the flag that stops the key walk ofverify._run($args, $what, %options)- run one command of this object, and answer the result of the run, orundefwith the reason inerror.$argsholds every argument after the command, as a list, so no argument needs quoting.$whatnames the act that failed, and the reason starts with it. With no$whatthe reason stands alone, for the key walk.%optionsreachesFugu::Process->run:stdin,envandcwd._check_input(@paths)- answer 1 when each path is a plain file, orundefwith the reason inerror._read_bounded($path, $limit)- the bytes of a file under the limit, orundef. The bound reads the size on disk, before the content._wide($text)- true when the string holds a code point above 255. A reader tests this, because such a string is character data and not bytes._with_temp_dir($parent, $body)- make a private directory under$parent, run$bodyover it, and then remove the tree. An undef$parentnames the temporary directory of the system. The method answers what$bodyanswered, and it removes the tree on every exit.
RETURN VALUES
Every recoverable failure returns undef, and error holds the reason. No method answers the reason as a second return value. The module never logs, and the caller decides what to report.
A method dies for a programming error alone: a missing necessary argument, an empty keys list, and a timeout that is no positive number.
CAVEATS
The module holds no private key of its own. A caller names each key file, and it holds the access of each one.
One run of the command ends within timeout seconds, and the run takes an argument list and never a shell. A subclass whose command needs a wider bound sets timeout before it calls new of this class.
SEE ALSO
Fugu::OpenPGP, Fugu::Process, Fugu::Signify, Fugu::X509
AUTHORS
Dick Olsson <hi@senzilla.io>