# Introduction

`sopass` manages passwords on the command line using the [stateless
OpenPGP interface
(SOP)](https://datatracker.ietf.org/doc/draft-dkg-openpgp-stateless-cli/)
for encryption. It is a re-interpretation of the concept championed by
`pass`, but using SOP. It does not try to be compatible with `pass`,
only to fit in the same ecological niche.

[`pass`](https://www.passwordstore.org/), also known as
"passwordstore" and described as "the standard unix password manager"
is a command line password manager that uses GnuPG for encryption.
`sopass` prefers SOP to avoid a lock-in to a specific implementation.

## Example

This section gives a taste of using `sopass`. We first initialize the
password database. This creates an empty database as
`~/.local/state/sopass/passwords.sopass`, and also copies the key file
to that directory so that later invocations of `sopass` find it.

~~~sh
$ sopass init --name mine --key my-openpgpg.key
~~~

We can now add a value and show it.

~~~sh
$ echo my secret password | sopass value add --text my/password
$ sopass value show my/password
my secret password
$
~~~

We can list all passwords in the database.

~~~sh
$ sopass value list
my/password
$
~~~

Finally, we can remove a value.

~~~sh
$ sopass value remove my/password
$ sopass value list
$
~~~

# Software architecture

`sopass` is a command line tool that runs a SOP implementation as a
sub-process, but does not otherwise interact with other software. It
stores key/value pairs in a **password database**, as well as a set of
certificates. The database is encrypted with OpenPGP using an implementation
of the SOP interface. The clear text data is never stored on
persistently. The secret OpenPGP key is stored next to the encrypted
database or on an OpenPGP card. The encryption is done using all the
certificates stored in the store itself. Whenever data is accessed,
the database is decrypted using the key. Whenever data is modified, the
database is re-encrypted using the stored certificates.

Some justifications for the architecture:

* "command line tool"
  - this is inherent in the purpose of the tool
* "SOP as a sub-process"
  - we want to avoid locking in the user of `sopass` to a specific
    implementation of cryptography, so we use the SOP interface to
    access the implementation we use
* "does not interact with other software"
  - there is no daemon, background process, online service, or other
    subsystem that `sopass` uses, because we want to keep it simple to
    deploy: only the `sopass` binary, the database file and the OpenPGP
    key are needed
* "OpenPGP"
  - OpenPGP is criticized and debated, but works well enough, and the
    SOP interface makes it easy to use
  - we don't expect to change away from OpenPGP or SOP
* "key stored next to database"
  - this is simplicity for user: the statelessness of SOP means the
    key location needs to be specified explicitly, and it isn't
    implicit the way GnuPG does; this is a big part of why SOP is
    nicer to use from other programs than GnuPG is
  - this is now optional: `sopass` can also an OpenPGP card for the
    secret key
* "certificates stored in the database"
  - the database has a list of certificates and the list can be managed
    with `sopass cert`
  - the list is boot strapped when the database is initialized: the
    initialization uses a key and a certificate is extracted from that
* "never stored persistently"
  - this reduces the likelihood of accidentally leaking secrets in
    clear text
  - `sopass` exchanges data with the SOP implementation via Unix pipes

Some compromises made at this stage development:

* keys without passphrases
  - it's simpler to deal with such keys, as it avoids needing
    machinery to ask the user for a passphrase
  - this will change, as it's obviously an unacceptable compromise
  - we also aim to support keys backed by hardware modules such as
    trusted platform or OpenPGP cards
* database is only encrypted
  - this is for simplicity
  - we will change this so that the database is also always signed so
* single encryption key
  - for simplicity
  - we will change this so that the database will store all the
    certificates that it should be encrypted for, similar to the
    `.gpg-id` file in the `pass` store
* no Git support
  - we will add support to automatically commit a modified database to
    Git, if the database directory is version controlled with Git; this
    is similar to what `pass` does
  - we will also make it easy to manage the Git repository via
    `sopass` to mimic `pass` more
* no configuration file
  - this is temporary, for simplicity
  - we will add a configuration file to specify things like database and
    key location

# Acceptance criteria

This chapter documents explicit acceptance criteria for `sopass`, and
how we verify that the implementation meets them. The verification is
done using "scenarios", and the [Subplot](https://subplot.tech/)
software turns those into executable code. Running the code verifies
the implementation.

## Data files

### Configuration file

This uses the directory `my.sopass` as the database, `rsop` for the SOP
implem3entation. The configuration file gets install so that the `sopass`
invoked by scenarios use it by default.

~~~{#config.yaml .file .yaml}
database: my.sopass
sop: rsop
sop_decrypt: rsop
~~~

### Pre-generated keys

This is a pre-generated key. We want to avoid generating a new key for
each test run, for speed.

~~~{#my.key .file}
-----BEGIN PGP PRIVATE KEY BLOCK-----

xVgEZ2+sZBYJKwYBBAHaRw8BAQdAesm4pX4NYlVa3XUImN1VoGYqlV5wrc+0ChK2
nHQJbagAAQCNaRb0BIm+FvnSehQ+eiGlYt7XkHQEpstH0h6IaMIrsg+PzQpsaXdA
bGl3LmZpwo8EEBYIADcCGQEFAmdvrGQCGwMICwkIBwoNDAsFFQoJCAsCFgIBJxYh
BD3Ix4Uvg4E82hngYARnFaLt6wu0AAoJEARnFaLt6wu03aABANDExR2u4LmA1Ibb
DtTyQnxieLRvVeucpgIWIyR6N6i8AP0Uh6M/yIPJKf9+TPXzyX/pW2hPqFgX4Eqt
XzFP/OrBCcddBGdvrGQSCisGAQQBl1UBBQEBB0DU+xruTz4AOT0hsLSu6Ji5Onkq
xCKtzdW7upJKzInEWAMBCAcAAP9m/VjK+jQOvMF8aBBthpE/nU44qkHwTlORSTFl
anrreBBKwngEGBYIACAFAmdvrGQCGwwWIQQ9yMeFL4OBPNoZ4GAEZxWi7esLtAAK
CRAEZxWi7esLtL42AP9maHm227K9/V68GOLXLgykAwlwqhx7KKbAcyRJPOhq5gEA
z28qv1cXrs0ctv3VAqsMd/OTV+ODDFvwOVvNdDZuYQ0=
=AYJk
-----END PGP PRIVATE KEY BLOCK-----
~~~

This is a second pre-generated key.

~~~{#other.key .file}
-----BEGIN PGP PRIVATE KEY BLOCK-----
Comment: BC2B ADD9 3C89 D080 8E98  5A1F 8962 5382 6F49 4D3A

xVgEZ3gPORYJKwYBBAHaRw8BAQdARrDdx/wzj8P9F40LJWYpT3wdy9yLvf1U069q
vc6M4M4AAQCwrh2W3cRSGCCciDWgMo210pLeq3Os/faaFXaPslu+pRHNwsALBB8W
CgB9BYJneA85AwsJBwkQiWJTgm9JTTpHFAAAAAAAHgAgc2FsdEBub3RhdGlvbnMu
c2VxdW9pYS1wZ3Aub3JnCkala0J2+OcEzzc7NfBJGv0HsiACp/2nWQe54bOlwZUD
FQoIApsBAh4BFiEEvCut2TyJ0ICOmFofiWJTgm9JTToAABnwAP9rn+AciFLaN/Up
leuz6jVhRfYG33IGj13n35rvs4qm+gEA3uHf9y5T9emmEv95MhhazGlp3xCugki/
i9WkEhTEvALHWARneA85FgkrBgEEAdpHDwEBB0CWv7+ruWlq8GTIo2QG/ycWJMz7
mFBQS9Uibyv7RiB4CAAA/igjj1gIsn4lP5TfzK4aQhx9arUI5b5qYFjgUVCAO3EG
Dk3CwL8EGBYKATEFgmd4DzkJEIliU4JvSU06RxQAAAAAAB4AIHNhbHRAbm90YXRp
b25zLnNlcXVvaWEtcGdwLm9yZ5Z1FkiL1r/pJIlRW3bogHpL5xt010SBHQWC6eR+
jx3xApsCvqAEGRYKAG8Fgmd4DzkJEA5YzpG5RD+ARxQAAAAAAB4AIHNhbHRAbm90
YXRpb25zLnNlcXVvaWEtcGdwLm9yZ+CQJbbTcTmFrGiLf4ts77eIQ9rYMljuvzwY
p/iEYJzgFiEEaZkggLtCLYWSMdahDljOkblEP4AAAPeXAP9C3R7Hn0jqm+xsC2Ym
Pv+H4d3eRtfndDCQTR2p3bJwkAD+IRMUXNS/Hg2zoVY4Y6LJMz6sdzi4dgzoUo+q
I1tkQAAWIQS8K63ZPInQgI6YWh+JYlOCb0lNOgAAJz8A/jIRrHZZ6lt9LecBzc+Y
7sG75m1XlvY/b8FEhE2ac9bqAQCyK4FVI1tmYmQ2Ji+wMwyRQ6iK8Brd85GSIcFI
FVfmBMddBGd4DzkSCisGAQQBl1UBBQEBB0Cr2zlOc4zZQiYg8gIQTBZX4xAJKTin
0JFL8ttuUcLNDAMBCAcAAP9qOp+iWiZMDuekZ8jdQC802NVZZXIe9JNK0YMp+Wc7
kBBswsAABBgWCgByBYJneA85CRCJYlOCb0lNOkcUAAAAAAAeACBzYWx0QG5vdGF0
aW9ucy5zZXF1b2lhLXBncC5vcmf6TAclvwInoVzGCDeyXLkdwBQ4zRdcr+KAXnIW
P1CwuAKbDBYhBLwrrdk8idCAjphaH4liU4JvSU06AAAE0wD/eCxv/xXYqgju+noA
a/VAyUkJsRGHkLPyK8YX1r565G8BAOpeUkkWeAyOJLVXyZ650xJkoEXvLz3+XGft
fU5QwskF
=lebt
-----END PGP PRIVATE KEY BLOCK-----
~~~

The certificate extracted from `other.key`:

~~~{#other.cert .file}
-----BEGIN PGP PUBLIC KEY BLOCK-----
Comment: BC2B ADD9 3C89 D080 8E98  5A1F 8962 5382 6F49 4D3A

xjMEZ3gPORYJKwYBBAHaRw8BAQdARrDdx/wzj8P9F40LJWYpT3wdy9yLvf1U069q
vc6M4M7CwAsEHxYKAH0Fgmd4DzkDCwkHCRCJYlOCb0lNOkcUAAAAAAAeACBzYWx0
QG5vdGF0aW9ucy5zZXF1b2lhLXBncC5vcmcKRqVrQnb45wTPNzs18Eka/QeyIAKn
/adZB7nhs6XBlQMVCggCmwECHgEWIQS8K63ZPInQgI6YWh+JYlOCb0lNOgAAGfAA
/2uf4ByIUto39SmV67PqNWFF9gbfcgaPXeffmu+ziqb6AQDe4d/3LlP16aYS/3ky
GFrMaWnfEK6CSL+L1aQSFMS8As4zBGd4DzkWCSsGAQQB2kcPAQEHQJa/v6u5aWrw
ZMijZAb/JxYkzPuYUFBL1SJvK/tGIHgIwsC/BBgWCgExBYJneA85CRCJYlOCb0lN
OkcUAAAAAAAeACBzYWx0QG5vdGF0aW9ucy5zZXF1b2lhLXBncC5vcmeWdRZIi9a/
6SSJUVt26IB6S+cbdNdEgR0Fgunkfo8d8QKbAr6gBBkWCgBvBYJneA85CRAOWM6R
uUQ/gEcUAAAAAAAeACBzYWx0QG5vdGF0aW9ucy5zZXF1b2lhLXBncC5vcmfgkCW2
03E5haxoi3+LbO+3iEPa2DJY7r88GKf4hGCc4BYhBGmZIIC7Qi2FkjHWoQ5YzpG5
RD+AAAD3lwD/Qt0ex59I6pvsbAtmJj7/h+Hd3kbX53QwkE0dqd2ycJAA/iETFFzU
vx4Ns6FWOGOiyTM+rHc4uHYM6FKPqiNbZEAAFiEEvCut2TyJ0ICOmFofiWJTgm9J
TToAACc/AP4yEax2WepbfS3nAc3PmO7Bu+ZtV5b2P2/BRIRNmnPW6gEAsiuBVSNb
ZmJkNiYvsDMMkUOoivAa3fORkiHBSBVX5gTOOARneA85EgorBgEEAZdVAQUBAQdA
q9s5TnOM2UImIPICEEwWV+MQCSk4p9CRS/LbblHCzQwDAQgHwsAABBgWCgByBYJn
eA85CRCJYlOCb0lNOkcUAAAAAAAeACBzYWx0QG5vdGF0aW9ucy5zZXF1b2lhLXBn
cC5vcmf6TAclvwInoVzGCDeyXLkdwBQ4zRdcr+KAXnIWP1CwuAKbDBYhBLwrrdk8
idCAjphaH4liU4JvSU06AAAE0wD/eCxv/xXYqgju+noAa/VAyUkJsRGHkLPyK8YX
1r565G8BAOpeUkkWeAyOJLVXyZ650xJkoEXvLz3+XGftfU5QwskF
=Gwpf
-----END PGP PUBLIC KEY BLOCK-----
~~~

## Reports its version

_Want:_ The `sopass` program reports its version when requested, and
it's in the format used my [semantic versioning](https://semver.org/).

_Why:_ This is partly a smoke test: if this doesn't work, we can't
expect anything else to work either. But it's also useful in
situations when someone else is using `sopass` and we want to know
what version they have.

Common current Unix practice is to have a `--version` option, so we
support that, but we also support a `version` subcommand, as we have
an interface based on subcommands.

~~~scenario
given an installed sopass
given file .config/sopass/sopass.yml from config.yaml
when I run sopass --version
then stdout matches regex ^sopass \d+\.\d+\.\d+$
when I run sopass version
then stdout matches regex ^sopass \d+\.\d+\.\d+$
~~~

## Reports a default configuration

_Want:_ The `sopass` program reports its default configuration when
requested.

_Why:_ This is useful so that users can see what the configuration is,
even if they haven't set on themselves.

~~~scenario
given an installed sopass
when I run sopass config
then stdout contains ""database": "/"
then stdout contains ""sop": ""
then stdout contains ""key_file": ""
then file .config/sopass/sopass.yml does not exist
~~~

## Loads default configuration file

_Want:_ The `sopass` program loads its default configuration when it
exists.

_Why:_ This is useful so that users don't need to always specify which
configuration file to use.

~~~scenario
given an installed sopass
given file .config/sopass/sopass.yml from default.yaml
when I run env HOME=. sopass config
then stdout contains ""database": "/over/the/rainbow""
~~~

~~~{#default.yaml .file .yaml}
database: /over/the/rainbow
~~~

## Loads specified configuration file

_Want:_ The `sopass` program loads a configuration file requested by user.

_Why:_ This is useful so that user can use a non-default configuration
file.

~~~scenario
given an installed sopass
when I try to run sopass --config custom.yaml config
then command fails
then stderr contains "custom.yaml"
given file custom.yaml
when I run sopass --config custom.yaml config
then stdout contains ""sop": "soppy""
~~~

~~~{#custom.yaml .file .yaml}
sop: soppy
~~~


## Rejects unknown field in configuration file

_Want:_ The `sopass` program refuses to load a configuration file that
has an unknown field.

_Why:_ This helps prevent typos in configuration files.

~~~scenario
given an installed sopass
given file unknown.yaml
when I try to run sopass --config unknown.yaml config
then command fails
then stderr contains "unknown"
~~~

~~~{#unknown.yaml .file .yaml}
unknown: true
~~~


## Initializes the password database

_Want:_ The program initializes the password database.

_Why:_ This is fundamental to how we want the software to be used.

~~~scenario
given an installed sopass
given file .config/sopass/sopass.yml from config.yaml
given file my.key
then directory xyzzy does not exist
when I run sopass init --name primary --key my.key
then file my.sopass/values.sopass exists
~~~

## Manages values

_Want:_ The user can add and remove a value and list all values.

_Why:_ This is fundamental for the purpose of the software.

~~~scenario
given an installed sopass
given file .config/sopass/sopass.yml from config.yaml
given file my.key

when I run sopass init --name primary --key my.key
when I run sopass value list
then stdout is exactly ""

given file value.dat
when I run sopass value add foo --file value.dat
when I run sopass value list
then stdout is exactly "foo\n"
when I run sopass value show foo
then stdout is exactly "bar\n"
when I run sopass value remove foo
then stdout is exactly ""

given file add-stdin.sh
when I run sh add-stdin.sh
when I run sopass value list
then stdout is exactly "foo\n"
when I run sopass value show foo
then stdout is exactly "bar\n"
when I run sopass value remove foo
then stdout is exactly ""
~~~

~~~{#value.dat .file add-newline=no}
bar
~~~

~~~{#add-stdin.sh .file .sh}
#!/bin/sh
set -x
cat value.dat
ls -l value.dat
sopass value add foo --stdin < value.dat
~~~

## Showing value that does not exist fails

_What:_ Trying to show a value that does not exist in the database fails.

_Why:_ If the command doesn't fail, the user may think the value is
the empty string.

~~~scenario
given an installed sopass
given file .config/sopass/sopass.yml from config.yaml
given file my.key

when I run sopass init --name primary --key my.key
when I try to run sopass value show foo
then command fails
then stderr contains "foo"
then stdout is exactly ""
~~~

## Renames values

_Want:_ The user can rename a value.

_Why:_ This is very handy.

~~~scenario
given an installed sopass
given file .config/sopass/sopass.yml from config.yaml
given file my.key

when I run sopass init --name primary --key my.key

given file value.dat
when I run sopass value add foo --file value.dat
when I run sopass value add foobar --file value.dat

when I try to run sopass value rename ghost yo
then command fails
then stderr contains "ghost"

when I try to run sopass value rename foo foobar
then command fails
then stderr contains "foobar"

when I run sopass value rename foo yo
when I run sopass value list
then stdout is exactly "foobar\nyo\n"
~~~

## Manages certificates

_Want:_ The password database contains certificates for which to encrypt.

_Why:_ This allows the database to be shared between devices without
sharing the encryption key.

~~~scenario
given an installed sopass
given file .config/sopass/sopass.yml from config.yaml
given file my.key
given file other.key
given file other.cert

when I run sopass init --name primary --key my.key
when I run sopass cert list
then stdout is exactly "primary\n"

when I run sopass cert add --name secondary --cert other.cert
when I run sopass cert list
then stdout contains "primary"
then stdout contains "secondary"

when I run mv other.key my.sopass/default.key
when I run rm my.key
when I run sopass cert list
then stdout contains "primary"
then stdout contains "secondary"

when I run sopass cert remove primary
when I run sopass cert list
then stdout doesn't contain "primary"
then stdout contains "secondary"

when I try to run sopass cert remove secondary
then command fails
then stderr contains "secondary"
when I run sopass cert list
then stdout contains "secondary"
~~~

## Exports database to file

_Want:_ The user can export all contents from database to a JSON file.

_Why:_ This allows moving data to another application or another,
incompatible version of `sopass` itself.

~~~scenario
given an installed sopass
given file .config/sopass/sopass.yml from config.yaml
given file my.key

when I run sopass init --name primary --key my.key
given file value.dat
when I run sopass value add foo --file value.dat

when I run sopass export database.json
when I run cat database.json
then file database.json contains ""certs":"
then file database.json contains ""primary":"
then file database.json contains ""kv":"
then file database.json contains ""foo":"
~~~

## Imports from a file

_Want:_ The user can import an exported database into another database.

_Why:_ This allows moving data to another application or another,
incompatible version of `sopass` itself.

~~~scenario
given an installed sopass
given file .config/sopass/sopass.yml from config.yaml

given file my.key
given file value.dat
given file yo.dat

when I run sopass init --name primary --key my.key
when I run sopass value add foo --file value.dat
when I run sopass value add bar --file value.dat
when I run sopass value add yo --file value.dat
when I run sopass export database.json

when I run sopass --database new init --name primary --key my.key
when I run sopass --database new value add foo --file yo.dat
when I run sopass --database new value add bar --file yo.dat
when I run sopass --database new import database.json

when I run sopass --database new value list
then stdout contains "foo"
then stdout contains "bar"
then stdout contains "yo"

when I run sopass --database new value show foo
then stdout is exactly "bar\n"

when I run sopass --database new value show bar
then stdout is exactly "bar\n"

when I run sopass --database new value show yo
then stdout is exactly "bar\n"
~~~

~~~{#yo.dat .file add-newline=no}
yo
~~~

## Generates a password

_Want:_ The user can have the program generate a random password

_Why:_ This is a common need.

Note that we do not verify that the generated passwords are actually secure.
We rely on the [`passwords` crate](https://crates.io/crates/passwords) to do
this correctly.

~~~scenario
given an installed sopass
given file .config/sopass/sopass.yml from config.yaml
given file my.key
when I run sopass init --name primary --key my.key
when I run sopass value generate foo
when I run sopass value list
then stdout is exactly "foo\n"
when I run sopass value show foo
then stdout isn't exactly ""
~~~
