180
pkg/crypters/gnupg/gnupg.go
Normal file
180
pkg/crypters/gnupg/gnupg.go
Normal file
@@ -0,0 +1,180 @@
|
||||
package gnupg
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"syscall"
|
||||
|
||||
"github.com/StackExchange/blackbox/v2/pkg/bblog"
|
||||
"github.com/StackExchange/blackbox/v2/pkg/bbutil"
|
||||
"github.com/StackExchange/blackbox/v2/pkg/crypters"
|
||||
)
|
||||
|
||||
var pluginName = "GnuPG"
|
||||
|
||||
func init() {
|
||||
crypters.Register(pluginName, 100, registerNew)
|
||||
}
|
||||
|
||||
// CrypterHandle is the handle
|
||||
type CrypterHandle struct {
|
||||
GPGCmd string // "gpg2" or "gpg"
|
||||
logErr *log.Logger
|
||||
logDebug *log.Logger
|
||||
}
|
||||
|
||||
func registerNew(debug bool) (crypters.Crypter, error) {
|
||||
|
||||
crypt := &CrypterHandle{
|
||||
logErr: bblog.GetErr(),
|
||||
logDebug: bblog.GetDebug(debug),
|
||||
}
|
||||
|
||||
// Which binary to use?
|
||||
path, err := exec.LookPath("gpg2")
|
||||
if err != nil {
|
||||
path, err = exec.LookPath("gpg")
|
||||
if err != nil {
|
||||
path = "gpg2"
|
||||
}
|
||||
}
|
||||
crypt.GPGCmd = path
|
||||
|
||||
return crypt, nil
|
||||
}
|
||||
|
||||
// Name returns my name.
|
||||
func (crypt CrypterHandle) Name() string {
|
||||
return pluginName
|
||||
}
|
||||
|
||||
// Decrypt name+".gpg", possibly overwriting name.
|
||||
func (crypt CrypterHandle) Decrypt(filename string, umask int, overwrite bool) error {
|
||||
|
||||
a := []string{
|
||||
"--use-agent",
|
||||
"-q",
|
||||
"--decrypt",
|
||||
"-o", filename,
|
||||
}
|
||||
if overwrite {
|
||||
a = append(a, "--yes")
|
||||
}
|
||||
a = append(a, filename+".gpg")
|
||||
|
||||
oldumask := syscall.Umask(umask)
|
||||
err := bbutil.RunBash(crypt.GPGCmd, a...)
|
||||
syscall.Umask(oldumask)
|
||||
return err
|
||||
}
|
||||
|
||||
// Cat returns the plaintext or, if it is missing, the decrypted cyphertext.
|
||||
func (crypt CrypterHandle) Cat(filename string) ([]byte, error) {
|
||||
|
||||
a := []string{
|
||||
"--use-agent",
|
||||
"-q",
|
||||
"--decrypt",
|
||||
}
|
||||
|
||||
// TODO(tlim): This assumes the entire gpg file fits in memory. If
|
||||
// this becomes a problem, re-implement this using exec Cmd.StdinPipe()
|
||||
// and feed the input in chunks.
|
||||
in, err := ioutil.ReadFile(filename + ".gpg")
|
||||
if err != nil {
|
||||
|
||||
if os.IsNotExist(err) {
|
||||
// Encrypted file doesn't exit? Return the plaintext.
|
||||
return ioutil.ReadFile(filename)
|
||||
}
|
||||
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return bbutil.RunBashInputOutput(in, crypt.GPGCmd, a...)
|
||||
}
|
||||
|
||||
// Encrypt name, overwriting name+".gpg"
|
||||
func (crypt CrypterHandle) Encrypt(filename string, umask int, receivers []string) (string, error) {
|
||||
var err error
|
||||
|
||||
crypt.logDebug.Printf("Encrypt(%q, %d, %q)", filename, umask, receivers)
|
||||
encrypted := filename + ".gpg"
|
||||
a := []string{
|
||||
"--use-agent",
|
||||
"--yes",
|
||||
"--trust-model=always",
|
||||
"--encrypt",
|
||||
"-o", encrypted,
|
||||
}
|
||||
for _, f := range receivers {
|
||||
a = append(a, "-r", f)
|
||||
}
|
||||
a = append(a, "--encrypt")
|
||||
a = append(a, filename)
|
||||
//err = bbutil.RunBash("ls", "-la")
|
||||
|
||||
oldumask := syscall.Umask(umask)
|
||||
crypt.logDebug.Printf("Args = %q", a)
|
||||
err = bbutil.RunBash(crypt.GPGCmd, a...)
|
||||
syscall.Umask(oldumask)
|
||||
|
||||
return encrypted, err
|
||||
}
|
||||
|
||||
// AddNewKey extracts keyname from sourcedir's GnuPG chain to destdir keychain.
|
||||
// It returns a list of files that may have changed.
|
||||
func (crypt CrypterHandle) AddNewKey(keyname, repobasedir, sourcedir, destdir string) ([]string, error) {
|
||||
|
||||
// $GPG --homedir="$2" --export -a "$KEYNAME" >"$pubkeyfile"
|
||||
args := []string{
|
||||
"--export",
|
||||
"-a",
|
||||
}
|
||||
if sourcedir != "" {
|
||||
args = append(args, "--homedir", sourcedir)
|
||||
}
|
||||
args = append(args, keyname)
|
||||
crypt.logDebug.Printf("ADDNEWKEY: Extracting key=%v: gpg, %v\n", keyname, args)
|
||||
pubkey, err := bbutil.RunBashOutput("gpg", args...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(pubkey) == 0 {
|
||||
return nil, fmt.Errorf("Nothing found when %q exported from %q", keyname, sourcedir)
|
||||
}
|
||||
|
||||
// $GPG --no-permission-warning --homedir="$KEYRINGDIR" --import "$pubkeyfile"
|
||||
args = []string{
|
||||
"--no-permission-warning",
|
||||
"--homedir", destdir,
|
||||
"--import",
|
||||
}
|
||||
crypt.logDebug.Printf("ADDNEWKEY: Importing: gpg %v\n", args)
|
||||
// fmt.Printf("DEBUG: crypter ADD %q", args)
|
||||
err = bbutil.RunBashInput(pubkey, "gpg", args...)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("AddNewKey failed: %w", err)
|
||||
}
|
||||
|
||||
// Suggest: ${pubring_path} trustdb.gpg blackbox-admins.txt
|
||||
var changed []string
|
||||
|
||||
// Prefix each file with the relative path to it.
|
||||
prefix, err := filepath.Rel(repobasedir, destdir)
|
||||
if err != nil {
|
||||
//fmt.Printf("FAIL (%v) (%v) (%v)\n", repobasedir, destdir, err)
|
||||
prefix = destdir
|
||||
}
|
||||
for _, file := range []string{"pubring.gpg", "pubring.kbx", "trustdb.gpg"} {
|
||||
path := filepath.Join(destdir, file)
|
||||
if bbutil.FileExistsOrProblem(path) {
|
||||
changed = append(changed, filepath.Join(prefix, file))
|
||||
}
|
||||
}
|
||||
return changed, nil
|
||||
}
|
||||
107
pkg/crypters/gnupg/keychain.go
Normal file
107
pkg/crypters/gnupg/keychain.go
Normal file
@@ -0,0 +1,107 @@
|
||||
package gnupg
|
||||
|
||||
/*
|
||||
|
||||
# How does Blackbox manage key rings?
|
||||
|
||||
Blackbox uses the user's .gnupg directory for most actions, such as decrypting data.
|
||||
Decrypting requires the user's private key, which is stored by the user in their
|
||||
home directory (and up to them to store safely).
|
||||
Black box does not store the user's private key in the repo.
|
||||
|
||||
When encrypting data, black needs the public key of all the admins, not just the users.
|
||||
To assure that the user's `.gnupg` has all these public keys, prior to
|
||||
encrypting data the public keys are imported from .blackbox, which stores
|
||||
a keychain that stores the public (not private!) keys of all the admins.
|
||||
|
||||
FYI: v1 does this import before decrypting, because I didn't know any better.
|
||||
|
||||
# Binary compatibility:
|
||||
|
||||
When writing v1, we didn't realize that the pubkey.gpg file is a binary format
|
||||
that is not intended to be portable. In fact, it is intentionally not portable.
|
||||
This means that all admins must use the exact same version of GnuPG
|
||||
or the files (pubring.gpg or pubring.kbx) may get corrupted.
|
||||
|
||||
In v2, we store the public keys in the portable ascii format
|
||||
in a file called `.blackbox/public-keys-db.asc`.
|
||||
It will also update the binary files if they exist.
|
||||
If `.blackbox/public-keys-db.asc` doesn't exist, it will be created.
|
||||
|
||||
Eventually we will stop updating the binary files.
|
||||
|
||||
# Importing public keys to the user
|
||||
|
||||
How to import the public keys to the user's GPG system:
|
||||
|
||||
If pubkeyring-ascii.txt exists:
|
||||
gpg --import pubkeyring-ascii.asc
|
||||
Else if pubring.kbx
|
||||
gpg --import pubring.kbx
|
||||
Else if pubring.gpg
|
||||
gpg --import pubring.gpg
|
||||
|
||||
This is what v1 does:
|
||||
#if gpg2 is installed next to gpg like on ubuntu 16
|
||||
if [[ "$GPG" != "gpg2" ]]; then
|
||||
$GPG --export --no-default-keyring --keyring "$(get_pubring_path)" >"$keyringasc"
|
||||
$GPG --import "$keyringasc" 2>&1 | egrep -v 'not changed$' >&2
|
||||
Else
|
||||
$GPG --keyring "$(get_pubring_path)" --export | $GPG --import
|
||||
fi
|
||||
|
||||
# How to add a key to the keyring?
|
||||
|
||||
Old, binary format:
|
||||
# Get the key they want to add:
|
||||
FOO is a user-specified directory, otherwise $HOME/.gnupg:
|
||||
$GPG --homedir="FOO" --export -a "$KEYNAME" >TEMPFILE
|
||||
# Import into the binary files:
|
||||
KEYRINGDIR is .blackbox
|
||||
$GPG --no-permission-warning --homedir="$KEYRINGDIR" --import TEMPFILE
|
||||
# Git add any of these files if they exist:
|
||||
pubring.gpg pubring.kbx trustdb.gpg blackbox-admins.txt
|
||||
# Tell the user to git commit them.
|
||||
|
||||
New, ascii format:
|
||||
# Get the key to be added. Write to a TEMPFILE
|
||||
FOO is a user-specified directory, otherwise $HOME/.gnupg:
|
||||
$GPG --homedir="FOO" --export -a "$KEYNAME" >TEMPFILE
|
||||
# Make a tempdir called TEMPDIR
|
||||
# Import the pubkeyring-ascii.txt to TEMPDIR's keyring. (Skip if file not found)
|
||||
# Import the temp1 data to TEMPDIR
|
||||
# Export the TEMPDIR to create a new .blackbox/pubkeyring-ascii.txt
|
||||
PATH_TO_BINARY is the path to .blackbox/pubring.gpg; if that's not found then pubring.kbx
|
||||
$GPG --keyring PATH_TO_BINARY --export -a --output .blackbox/pubkeyring-ascii.txt
|
||||
# Git add .blackbox/pubkeyring-ascii.txt and .blackbox/blackbox-admins.txt
|
||||
# Tell the user to git commit them.
|
||||
# Delete TEMPDIR
|
||||
|
||||
# How to remove a key from the keyring?
|
||||
|
||||
Old, binary format:
|
||||
# Remove key from the binary file
|
||||
$GPG --no-permission-warning --homedir="$KEYRINGDIR" --batch --yes --delete-key "$KEYNAME" || true
|
||||
# Git add any of these files if they exist:
|
||||
pubring.gpg pubring.kbx trustdb.gpg blackbox-admins.txt
|
||||
# Tell the user to git commit them.
|
||||
|
||||
New, ascii format:
|
||||
# Make a tempdir called TEMPDIR
|
||||
# Import the pubkeyring-ascii.txt to TEMPDIR's keyring. (Skip if file not found)
|
||||
# Remove key from the ring file
|
||||
$GPG --no-permission-warning --homedir="$KEYRINGDIR" --batch --yes --delete-key "$KEYNAME" || true
|
||||
# Export the TEMPDIR to create a new .blackbox/pubkeyring-ascii.txt
|
||||
PATH_TO_BINARY is the path to .blackbox/pubring.gpg; if that's not found then pubring.kbx
|
||||
$GPG --keyring PATH_TO_BINARY --export -a --output .blackbox/pubkeyring-ascii.txt
|
||||
# Git add .blackbox/pubkeyring-ascii.txt and .blackbox/blackbox-admins.txt
|
||||
# Update the .blackbox copy of pubring.gpg, pubring.kbx, or trustdb.gpg (if they exist)
|
||||
# with copies from TEMPDIR (if they exist). Git add any files that are updated.
|
||||
# Tell the user to git commit them.
|
||||
# Delete TEMPDIR
|
||||
|
||||
*/
|
||||
|
||||
//func prepareUserKeychain() error {
|
||||
// return nil
|
||||
//}
|
||||
Reference in New Issue
Block a user