Implement blackbox in Golang (#250)

* Initial release
This commit is contained in:
Tom Limoncelli
2020-07-24 14:21:33 -04:00
committed by GitHub
parent e049c02655
commit 1c77c87555
86 changed files with 6074 additions and 22 deletions

180
pkg/crypters/gnupg/gnupg.go Normal file
View 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
}

View 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
//}