Goal

Last time I ran out of time implementing an OpenPGP credential, because I had to implement a bunch of scaffolding for executing programs in a convenient manner, and a SOP implementation in particular.

  • Add tooling for running a SOP implementation to encrypt and decrypt data.

Plan

  • Add a wrapper around std::process::Command to make it easy to run a command and inspect the result easier.
  • Implement a type for convenient execution of a SOP implementation.

Notes

A command runner

  • The Rust standard library has the std::process::Command module for executing a program. It works well.
  • I find the least convenient part of using Command is to analyze the result and to deal with the various failure modes. It's not just "program ended with a non-zero exit code", there's so many other ways in which this can fail.
  • I have previously written a little Rust module to show how to handle errors from Command. I will steal that to add a module to the obnam crate. I'll try to make the module as self-contained as possible so I can make use of it other projects. Some day I may find a crate where I can put it so that I don't copy it from project to project. But right now that is premature.
  • Copied the module and made some improvements. At some point this will need a proper test suite, but I'll worry about that later. It'll probably be tricky.
  • The module, src/runner.rsx in the source tree, gets a src::process::Command that has already been set up, runs that, and inspects the results. The big difference with Command is that it treats a non-zero exit code as an error.

A SOP abstraction

  • The SOP specification is an IETF draft.
  • To use SOP, one needs OpenPGP keys and certificates in files. From those keys, one can extract certificates. It seems convenient to only require the user to provide a key file and have Obnam extract the certificate.
  • The basic operations we care about are:
    • sop extract-cert < KEY > CERT
    • sop encrypt CERT < PLAINTEXT > CIPHERTEXT
    • sop decrypt KEY < CIPHERTEXT > PLAINTEXT
  • I will define a type Sop:
pub struct OpenPgpKey {...}
pub struct OpenPgpCert {...}
pub struct Sop {...}
impl Sop {
    pub fn new(command: &OsStr) -> Self {...}
    pub fn extract_cert(&self, key: &OpenPgpKey) -> Result<OpenPgpCert, SopError> {...}
    pub fn encrypt(&self, certs: &[OpenPgpKey]) -> Result<Vec<u8>, SopError> {...}
    pub fn decrypt(&self, key: &OpenPgpKey) -> Result<Vec<u8>, SopError> {...}
}
  • The types for OpenPGP keys and certificates are there for type safety.
  • Implemented certificate extraction.
  • Implemented encryption.
  • Tried to implement decryption, but having trouble getting it to work. sqop says "Invalid data type". But it works when I run it manually.
  • Oh! It works if I feed the encrypted file to decrypt, not the plaintext file. That should not have been difficult.
  • Added verification scenarios for obnam sop extract-cert, encrypt, and decrypt.

Tidy up

  • Tidied up the history of today's branch.
  • Created a patch for the Radicle repository.
  • Waited for CI run to succeed. It did.
  • Merged patch branch to main.
  • Pushed main to Radicle.

Summary

Today was mostly about creating building blocks to make OpenPGP credentials easier to implement. I got that done.

As an extra bonus twist, added an OpenPGP key to the Obnam subplot, to see how many people complain that I distribute a private key in Git.

Comments?

If you have feedback on this development session, please use the following fediverse thread: https://toot.liw.fi/@liw/114805474268611738.