Goal
Implement the first credential.
Plan
- Verify that tests still pass, and fix any problems.
- Write up my thoughts on implementing credentials.
- Plan implementation of a minimum set of commands to manage
credentials.
obnam credential new
to create a credentialobnam credential show
to show the client key in a credential
Notes
Verify tests pass
- Well, they don't. Since the last time, there's been a new Rust
version, with new
clippy
warnings. Fixed those, created a patch, waited for CI to run, made sure it was a success, merged, and pushed newmain
. Verified that CI still runs OK.
Thoughts on credential implementation
- A "credential" is what I call a chunk that contains an encrypted key for a client chunk. Each credential encrypts the client key in a different way.
- There are many encryption choices for credentials. The one's I'm
currently most interested in are:
- a user-provided passphrase
- an OpenPGP key
- a TPM chip
- The encryption method defines the type of credential.
- It will be possible to have many instances of a specific type of credential. For example, many different OpenPGP keys. This will allow a user to have one such key on their laptop, and another in some safe and secure location such as a bank safe deposit box.
- It will be possible, and hopefully easy, to add new credentials, and I hope others will contribute those.
The OpenPGP software key credential
- The first credential I will implement will be using OpenPGP software keys, using an implementation of the Stateless OpenPGP (SOP) interface. SOP is designed to make it easy to use OpenPGP from another program and that's what Obnam will do.
- I will specifically start by using software keys, which are just files on disk. Later I will want to add support for using OpenPGP cards, where the private key is stored on a physical device and can't be extracted. All operations using the private key will need to happen on the card.
- I will be using the
rsop
implementation of SOP, which I have previous experience with in mysopass
software.
Other credentials
- I want the passphrase credential primarily so that in an ultimate disaster scenario, where someone loses all physical belonging, they can still restore their backup if they remember that one passphrase. I am aware that user-provided passphrases tend to be relatively weak, and that this is a risk for the security of the backup. I will not require passphrase credential to exist, but I want to make it possible to use it for those who choose to do so.
- A TPM chip is part of most PC computers in recent years. It is conceptually similar to an OpenPGP card in that the private key only ever exists inside the chip. The big difference is that an OpenPGP card is usually a small USB device, where as a TPM chip is part of a specific computer. The USB device is easier to move to a new computer.
- I start with the OpenPGP key credential, because I currently lack the knowledge for implementing passphrase and TPM encryption well, and I don't want to take a detour to learn those before I start with credentials.
- Also, for my own use, I expect the OpenPGP credential to be the one I mostly use, so it has the highest priority for me.
Repository security
- A credential allows decryption of the client chunk, and thus access to the keys used for data chunks.
- I may want to allow different access to the backup repository based on which credential is used. For example, a TPM credential might allow access for making a new backup or restore a backup, but not access to delete old backups, or remove data chunk keys.
- However, I'm not yet ready to start seriously thinking about repository access control, so for now I won't worry about what credentials need to have for access control.
Representing a credential on disk
- A credential is a chunk, but can't be encrypted, because it needs to be inspected to that the client key can be decrypted: the credential encryption method needs to be known for this be possible.
- Thus a credential chunk will look something like this, as a Rust type:
enum Credential {
Sop(Key),
}
pub struct CredentialChunk {
metadata: Metadata,
credential: Credential,
}
- Here,
Key
is the AES-GCM-SIV key I already use in encrypting data chunks, andMetadata
is the same chunk metadata as I used for data chunks. (A client chunk is a data chunk, for the purposes of this discussion.) - The
CredentialChunk
will be encoded and decoded usingpostcard
, as normal chunks are. - The
Credential
enum encapsulates the client key, and the credential encryption method. Using an enum for this makes it easy to add more variants, and the Rust type system means the compiler will check that the code handles all possibilities, unless the code explicitly only handles some. - That reminds me, I expect all credential types to be implemented in
the
obnam
crate. I don't expect to support plug-ins for this. If users really want this, one could have aCredential::FooPlugin
variant, and the implementation of that variant can call an external programfoo
, whatever that is. But I'm not comfortable making this fully generic, at least not for now. Someone will have to convince me why that should happen, first. - Like with normal chunks, the actual encryption and decryption will not be part of the chunk type, but a separate "cipher engine" type. The credential cipher engine needs to be able to encrypt and decrypt all of the supported credential encryption methods.
pub struct CredentialCipherEngine { ... }
impl CredentialCipherEngine {
pub fn new(client_key: Key) -> Self { ... }
fn encrypt(&self, key: &Key) -> Result<Vec<u8>, CredentialError> {... }
fn decrypt(&self, ciphertext: &[u8]) -> Result<Vec<u8>, CredentialError> { ... }
}
- I don't fancy exposing a trait for implementing credential ciphers,
for now. It feels simpler to have the concrete
CredentialCIpherEngine
handle all of them. It needs to figure out the type of credential used in any case. - Started implementing credentials based on this idea, but ran into it being too big of a thing to think through at once. I need to break this into smaller units of work.
- I'll claim I planned to make a prototype today, in case anyone asks.
Plan of work
- Since I will be using SOP, I will need to be running
rsop
and that needs to be convenient and handle errors. I should start by making a "run command" abstraction for this. - Using that abstraction, make a module for running a SOP implementation. Ideally something I can use in other programs that use SOP.
- Using that SOP wrapper, implement a SOP credential, or more correctly an "OpenPGP software key" credential.
- Then build a credentials module that supports an OpenPGP software key credential as the only option, for now.
- Then add subcommands
obnam credential new
andshow
, with Subplot scenarios to verify they work. - Further work planned when I get that far.
Summary
- I got a good feeling for how to implement the first credential, but as usually bit off more than I could chew in one session. I've planned future work so that I can accomplish more the next few sessions.
Comments?
If you have feedback on this development session, please use the following fediverse thread: https://toot.liw.fi/@liw/114765792877723350.