sq: Rename --signer-key
Soon we will add keystore support to sq and there will be two ways to address keys: keys in files, and keys on the keystore. There are actually even more ways to address keys that we may want to support: reading a key from an environment variable, which is useful for things like docker, and reading a key from a file descriptor. (The latter is less useful in Unix-like environments where /dev/fd/N can usually be used, but is, I think, needed on Windows.)
There are (at least) two ways we can handle this: a single argument taking a type and a value, or multiple arguments each taking a single value. The single argument taking a type and value is the approach that SOP takes. Using this approach, one could do: --sign-with @ENV:FOO to pass a key via the environment variable FOO. SOP also has a default type, files. If sq were to adopt this approach, it should probably also have a default, which is probably keystore identifiers, but could also be files. The alternative approach is to have an argument for each access mechanism, e.g., --signer-file, --signer-keystore, --signer-env, --signer-fd, etc. I have two primary concerns: safety, and usability.
Type Confusion
The single argument approach's use of a default type introduces the possibility of type confusion, as the SOP draft notes. In short, if the default type is files, and a file called @FD:0 is passed to the program then the program would read from stdin and not from the file. Whoops. This increases the cognitive burden of the user a bit. But the worse part is that an attacker can potentially take advantage of this if input is not sufficiently sanitized. This problem disappears when using multiple arguments as the type is always explicit.
Tab Completion Usability
Let's consider how tab completion works in both approaches. When completing on the value of --signer, the single argument approach will probably complete the possible types and possible values of the default type. That is, the types and values will be mixed:
# Single argument approach (perhaps)
$ sq ... --signer <TAB>
Display all 345 possibilities? (y or n)
@ENV:
@FD:
@KEYSTORE:
file1
file2
...
In the multiple argument approach, we have to do a bit more work, but the results are more readable. If we complete on --signer, we first get the possible types (at least on bash). And when we complete on the value of a concrete argument like --signer-file, we get the possible values for that type:
# Multiple argument approach
$ sq ... --signer<TAB>
--signer-file=
--signer-keystore=
--signer-env=
--signer-fd=
$ sq ... --signer-file <TAB>
file1
file2
...
I think the latter improves discoverability of the different access mechanisms, because they are not hidden in a sea of values. And, I think the two-step approach increases usability.
Tab Completion Implementation Complexity
Most shells include tab completion facilities. Bash can even be taught that an argument takes a file, or an environment variable. (I suspect that zsh and fish are even more powerful.) Unfortunately, I don't think it has first class mechanisms to deal with SOP's type:value system. At least in bash, I think we'd have to shell out (using complete's -F option) and manually list the prefixes and the possible values (which means reimplementing filename and environment name completion).
Using multiple arguments, supporting tab completion can largely be done natively. In fact, clap can be leveraged here. The only exception is for completing keys on the key store.
Manual Pages
When using the single option approach, manual pages may be a bit less redundant: there is only a single option that needs to be explained, and the different access mechanisms can be factored out into a dedicated section. This particularly helps reduce repetition when a subcommand has multiple options take a type and a value, like sq encrypt --signer KEY --recipient CERT.
On the other hand, I don't think some repetition is that bad. We do it a lot in the sequoia-openpgp documentation, and I find having all the information is one place is easier to use. And, in the case of sq's manual pages, it will actually be automatically generated.
Summary
The following table summarizes my points. An x means "better", e.g., the multiple argument's way of dealing with type confusion is better.
| Single | Multiple | |
|---|---|---|
| Type confusion | x | |
| Tab completion usability | x | |
| Tab completion implementation complexity | x | |
| Documentation | x? |
I think on the whole, the multiple argument approach is better and what I plan to implement, but I'd like to hear what others think.
Concretely, I plan to rename --signer-key (and --recipient-key, ...) to --signer-file and add the --signer-keystore option.