README
¶
Experimental Crypto Backend for gopass
This package contains an experimental crypto backend for gopass. The goal is to provide an implementation that is feature complete compared to the GPG backend but doesn't require any external binaries, especially no GPG. Of course this would break compatibility to existing GPG deployments and users of different pass implementations, but especially for closed teams with no existing GPG deployment this should make little to no difference.
Motivation
While GPG is believed to be very secure and it supports a wide range of applications and devices, it's not really user friendly. Even passioned crypto experts don't enjoy working with GPG and for newcomers it's a major hurdle. For the gopass developers it's about the most time consuming task to provide support and implement workaround for GPG issues. This doesn't mean that GPG is bad, but security is hard and complex and GPG adds a lot of flexibility on top of that so the result is complex and complicated.
WARNING
We are no crypto experts. While this code uses professional implementations of well known and rather easy to use crypto primitives there is still a lot of room for making mistakes. This code so far has received no kind of security audit. Please don't use it for anything critical unless you have reviewed and verified it yourself and are willing to take any risk.
Status
Working, needs more of testing.
Design
The design tries to follow current industry best-practices wherever possible.
- Hybrid encryption
- Symmetric encryption for payload (secrets)
- Using XSalsa20, Poly1305 (NaCL Secretbox) AEAD
- Random session key
- Asymmetric encryption per recipient
- (optional) Unencrypted Metadata
- Disk format uses protocol buffers version 3 encoding
- Keystore
- Unencrypted public keys / metadata
- Private Keys encrypted with XSalsa20, Poly1305 (NaCL Secretbox)
- Using Argon2 KDF
- Key ID similar to GnuPG, using the low 64 bits of a SHA-3 / SHAKE-256 hash
- Disk format uses protocol buffers version 3 encoding
- Agent
- (optional) Listens on Unix Socket
- Invokes pinentry if necessary, caches passphrase
Attack Vectors
- Information Disclosure
- Header
- Session key is encrypted and authenticated using NaCl Box
- Metadata is unencrypted, but unused right now
- Body
- Plaintext is encrypted and authenticated using Chacha20Poly1305 AEAD
- Header
Testing Notes
# Create two different homedirs
mkdir -p /tmp/gp1 /tmp/gp2
# Create a shared remote
mkdir -p /tmp/gpgit
cd /tmp/gpgit && git init --bare
# Generate two keypairs
GOPASS_HOMEDIR=/tmp/gp1 gopass xc generate
GOPASS_HOMEDIR=/tmp/gp2 gopass xc generate
# Get the key IDs (the init wizard doesn't support XC, yet)
GOPASS_HOMEDIR=/tmp/gp1 gopass xc list-private-keys
GOPASS_HOMEDIR=/tmp/gp2 gopass xc list-private-keys
# Init first password store
GOPASS_HOMEDIR=/tmp/gp1 gopass init --crypto=xc --sync=gitcli <key-id-1>
# add git remote
GOPASS_HOMEDIR=/tmp/gp1 ./gopass git remote add --remote origin --url /tmp/gpgit
# push to git remote (will produce a warning which can be ignored)
GOPASS_HOMEDIR=/tmp/gp1 ./gopass git push
# clone second store
GOPASS_HOMEDIR=/tmp/gp2 ./gopass clone --crypto=xc --sync=gitcli /tmp/gpgit
# Generate some secrets
GOPASS_HOMEDIR=/tmp/gp1 gopass generate foo/bar 24
# Sync stores
GOPASS_HOMEDIR=/tmp/gp1 gopass sync
GOPASS_HOMEDIR=/tmp/gp2 gopass sync
# Try to decrypt
GOPASS_HOMEDIR=/tmp/gp2 gopass show foo/bar # should fail
# Export recipient
GOPASS_HOMEDIR=/tmp/gp2 gopass xc export --id <key-id-2> --file /tmp/pub
# Import recipient
GOPASS_HOMEDIR=/tmp/gp1 gopass xc import --file /tmp/pub
# Add recipient
GOPASS_HOMEDIR=/tmp/gp1 gopass recipients add <key-id-2>
# Sync
GOPASS_HOMEDIR=/tmp/gp2 gopass sync
# Display secret
GOPASS_HOMEDIR=/tmp/gp2 gopass show foo/bar
Documentation
¶
Index ¶
- Constants
- type XC
- func (x *XC) CreatePrivateKey(ctx context.Context) error
- func (x *XC) CreatePrivateKeyBatch(ctx context.Context, name, email, passphrase string) error
- func (x *XC) Decrypt(ctx context.Context, buf []byte) ([]byte, error)
- func (x *XC) DecryptStream(ctx context.Context, ciphertext io.Reader, plaintext io.Writer) error
- func (x *XC) EmailFromKey(ctx context.Context, id string) string
- func (x *XC) Encrypt(ctx context.Context, plaintext []byte, recipients []string) ([]byte, error)
- func (x *XC) EncryptStream(ctx context.Context, plaintext io.Reader, recipients []string, ...) error
- func (x *XC) ExportPrivateKey(ctx context.Context, id string) ([]byte, error)
- func (x *XC) ExportPublicKey(ctx context.Context, id string) ([]byte, error)
- func (x *XC) Ext() string
- func (x *XC) FindPrivateKeys(ctx context.Context, search ...string) ([]string, error)
- func (x *XC) FindPublicKeys(ctx context.Context, search ...string) ([]string, error)
- func (x *XC) Fingerprint(ctx context.Context, id string) string
- func (x *XC) FormatKey(ctx context.Context, id string) string
- func (x *XC) IDFile() string
- func (x *XC) ImportPrivateKey(ctx context.Context, buf []byte) error
- func (x *XC) ImportPublicKey(ctx context.Context, buf []byte) error
- func (x *XC) Initialized(ctx context.Context) error
- func (x *XC) ListPrivateKeyIDs(ctx context.Context) ([]string, error)
- func (x *XC) ListPublicKeyIDs(ctx context.Context) ([]string, error)
- func (x *XC) Name() string
- func (x *XC) NameFromKey(ctx context.Context, id string) string
- func (x *XC) ReadNamesFromKey(ctx context.Context, buf []byte) ([]string, error)
- func (x *XC) RecipientIDs(ctx context.Context, ciphertext []byte) ([]string, error)
- func (x *XC) RemoveKey(id string) error
- func (x *XC) Version(ctx context.Context) semver.Version
Constants ¶
const ( // Ext is the extension used by this backend Ext = "xc" // IDFile is the recipients list used by this backend IDFile = ".xc-ids" )
const (
// OnDiskVersion is the version of our on-disk format
OnDiskVersion = 1
)
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type XC ¶
type XC struct {
// contains filtered or unexported fields
}
XC is an experimental crypto backend
func (*XC) CreatePrivateKey ¶
CreatePrivateKey is not implemented
func (*XC) CreatePrivateKeyBatch ¶
CreatePrivateKeyBatch creates a new keypair
func (*XC) DecryptStream ¶
DecryptStream decrypts an stream encrypted with EncryptStream
func (*XC) EmailFromKey ¶
EmailFromKey extracts the email from a key
func (*XC) Encrypt ¶
Encrypt encrypts the given plaintext for all the given recipients and returns the ciphertext
func (*XC) EncryptStream ¶
func (x *XC) EncryptStream(ctx context.Context, plaintext io.Reader, recipients []string, ciphertext io.Writer) error
EncryptStream encrypts the plaintext using a slightly modified on disk-format suitable for streaming
func (*XC) ExportPrivateKey ¶
ExportPrivateKey exports a given private key
func (*XC) ExportPublicKey ¶
ExportPublicKey exports a given public key
func (*XC) FindPrivateKeys ¶
FindPrivateKeys finds all matching private keys
func (*XC) FindPublicKeys ¶
FindPublicKeys finds all matching public keys
func (*XC) Fingerprint ¶
Fingerprint returns the full-length native fingerprint
func (*XC) ImportPrivateKey ¶
ImportPrivateKey imports a given private key into the keyring
func (*XC) ImportPublicKey ¶
ImportPublicKey imports a given public key into the keyring
func (*XC) Initialized ¶
Initialized returns an error if this backend is not properly initialized
func (*XC) ListPrivateKeyIDs ¶
ListPrivateKeyIDs lists all private key IDs
func (*XC) ListPublicKeyIDs ¶
ListPublicKeyIDs lists all public key IDs
func (*XC) NameFromKey ¶
NameFromKey extracts the name from a key
func (*XC) ReadNamesFromKey ¶
ReadNamesFromKey unmarshals the given public key and returns the identities name
func (*XC) RecipientIDs ¶
RecipientIDs reads the header of the given file and extracts the recipients IDs