1/* 2Package pgp contains an implementation of the go.mozilla.org/sops/v3.MasterKey interface that encrypts and decrypts the 3data key by first trying with the golang.org/x/crypto/openpgp package and if that fails, by calling the "gpg" binary. 4*/ 5package pgp //import "go.mozilla.org/sops/v3/pgp" 6 7import ( 8 "bytes" 9 "encoding/hex" 10 "fmt" 11 "io/ioutil" 12 "net/http" 13 "os" 14 "os/user" 15 "path" 16 "strings" 17 "time" 18 19 "os/exec" 20 21 "github.com/howeyc/gopass" 22 "github.com/sirupsen/logrus" 23 gpgagent "go.mozilla.org/gopgagent" 24 "go.mozilla.org/sops/v3/logging" 25 "golang.org/x/crypto/openpgp" 26 "golang.org/x/crypto/openpgp/armor" 27) 28 29var log *logrus.Logger 30 31func init() { 32 log = logging.NewLogger("PGP") 33} 34 35// MasterKey is a PGP key used to securely store sops' data key by encrypting it and decrypting it 36type MasterKey struct { 37 Fingerprint string 38 EncryptedKey string 39 CreationDate time.Time 40} 41 42// EncryptedDataKey returns the encrypted data key this master key holds 43func (key *MasterKey) EncryptedDataKey() []byte { 44 return []byte(key.EncryptedKey) 45} 46 47// SetEncryptedDataKey sets the encrypted data key for this master key 48func (key *MasterKey) SetEncryptedDataKey(enc []byte) { 49 key.EncryptedKey = string(enc) 50} 51 52func gpgBinary() string { 53 binary := "gpg" 54 if envBinary := os.Getenv("SOPS_GPG_EXEC"); envBinary != "" { 55 binary = envBinary 56 } 57 return binary 58} 59 60func (key *MasterKey) encryptWithGPGBinary(dataKey []byte) error { 61 fingerprint := key.Fingerprint 62 if offset := len(fingerprint) - 16; offset > 0 { 63 fingerprint = fingerprint[offset:] 64 } 65 args := []string{ 66 "--no-default-recipient", 67 "--yes", 68 "--encrypt", 69 "-a", 70 "-r", 71 key.Fingerprint, 72 "--trusted-key", 73 fingerprint, 74 "--no-encrypt-to", 75 } 76 cmd := exec.Command(gpgBinary(), args...) 77 var stdout, stderr bytes.Buffer 78 cmd.Stdin = bytes.NewReader(dataKey) 79 cmd.Stdout = &stdout 80 cmd.Stderr = &stderr 81 err := cmd.Run() 82 if err != nil { 83 return err 84 } 85 key.EncryptedKey = stdout.String() 86 return nil 87} 88 89func getKeyFromKeyServer(fingerprint string) (openpgp.Entity, error) { 90 log.Warn("Deprecation Warning: GPG key fetching from a keyserver witihin sops will be removed in a future version of sops. See https://github.com/mozilla/sops/issues/727 for more information.") 91 92 url := fmt.Sprintf("https://keys.openpgp.org/vks/v1/by-fingerprint/%s", fingerprint) 93 resp, err := http.Get(url) 94 if err != nil { 95 return openpgp.Entity{}, fmt.Errorf("error getting key from keyserver: %s", err) 96 } 97 defer resp.Body.Close() 98 if resp.StatusCode != 200 { 99 return openpgp.Entity{}, fmt.Errorf("keyserver returned non-200 status code %s", resp.Status) 100 } 101 ents, err := openpgp.ReadArmoredKeyRing(resp.Body) 102 if err != nil { 103 return openpgp.Entity{}, fmt.Errorf("could not read entities: %s", err) 104 } 105 return *ents[0], nil 106} 107 108func (key *MasterKey) getPubKey() (openpgp.Entity, error) { 109 ring, err := key.pubRing() 110 if err == nil { 111 fingerprints := key.fingerprintMap(ring) 112 entity, ok := fingerprints[key.Fingerprint] 113 if ok { 114 return entity, nil 115 } 116 } 117 entity, err := getKeyFromKeyServer(key.Fingerprint) 118 if err != nil { 119 return openpgp.Entity{}, 120 fmt.Errorf("key with fingerprint %s is not available "+ 121 "in keyring and could not be retrieved from keyserver", key.Fingerprint) 122 } 123 return entity, nil 124} 125 126func (key *MasterKey) encryptWithCryptoOpenPGP(dataKey []byte) error { 127 entity, err := key.getPubKey() 128 if err != nil { 129 return err 130 } 131 encbuf := new(bytes.Buffer) 132 armorbuf, err := armor.Encode(encbuf, "PGP MESSAGE", nil) 133 if err != nil { 134 return err 135 } 136 plaintextbuf, err := openpgp.Encrypt(armorbuf, []*openpgp.Entity{&entity}, nil, &openpgp.FileHints{IsBinary: true}, nil) 137 if err != nil { 138 return err 139 } 140 _, err = plaintextbuf.Write(dataKey) 141 if err != nil { 142 return err 143 } 144 err = plaintextbuf.Close() 145 if err != nil { 146 return err 147 } 148 err = armorbuf.Close() 149 if err != nil { 150 return err 151 } 152 bytes, err := ioutil.ReadAll(encbuf) 153 if err != nil { 154 return err 155 } 156 key.EncryptedKey = string(bytes) 157 return nil 158} 159 160// Encrypt encrypts the data key with the PGP key with the same fingerprint as the MasterKey. It looks for PGP public keys in $PGPHOME/pubring.gpg. 161func (key *MasterKey) Encrypt(dataKey []byte) error { 162 openpgpErr := key.encryptWithCryptoOpenPGP(dataKey) 163 if openpgpErr == nil { 164 log.WithField("fingerprint", key.Fingerprint).Info("Encryption succeeded") 165 return nil 166 } 167 binaryErr := key.encryptWithGPGBinary(dataKey) 168 if binaryErr == nil { 169 log.WithField("fingerprint", key.Fingerprint).Info("Encryption succeeded") 170 return nil 171 } 172 log.WithField("fingerprint", key.Fingerprint).Info("Encryption failed") 173 return fmt.Errorf( 174 `could not encrypt data key with PGP key: golang.org/x/crypto/openpgp error: %v; GPG binary error: %v`, 175 openpgpErr, binaryErr) 176} 177 178// EncryptIfNeeded encrypts the data key with PGP only if it's needed, that is, if it hasn't been encrypted already 179func (key *MasterKey) EncryptIfNeeded(dataKey []byte) error { 180 if key.EncryptedKey == "" { 181 return key.Encrypt(dataKey) 182 } 183 return nil 184} 185 186func (key *MasterKey) decryptWithGPGBinary() ([]byte, error) { 187 args := []string{ 188 "--use-agent", 189 "-d", 190 } 191 cmd := exec.Command(gpgBinary(), args...) 192 var stdout, stderr bytes.Buffer 193 cmd.Stdin = strings.NewReader(key.EncryptedKey) 194 cmd.Stdout = &stdout 195 cmd.Stderr = &stderr 196 err := cmd.Run() 197 if err != nil { 198 return nil, err 199 } 200 return stdout.Bytes(), nil 201} 202 203func (key *MasterKey) decryptWithCryptoOpenpgp() ([]byte, error) { 204 ring, err := key.secRing() 205 if err != nil { 206 return nil, fmt.Errorf("Could not load secring: %s", err) 207 } 208 block, err := armor.Decode(strings.NewReader(key.EncryptedKey)) 209 if err != nil { 210 return nil, fmt.Errorf("Armor decoding failed: %s", err) 211 } 212 md, err := openpgp.ReadMessage(block.Body, ring, key.passphrasePrompt(), nil) 213 if err != nil { 214 return nil, fmt.Errorf("Reading PGP message failed: %s", err) 215 } 216 if b, err := ioutil.ReadAll(md.UnverifiedBody); err == nil { 217 return b, nil 218 } 219 return nil, fmt.Errorf("The key could not be decrypted with any of the PGP entries") 220} 221 222// Decrypt uses PGP to obtain the data key from the EncryptedKey store in the MasterKey and returns it 223func (key *MasterKey) Decrypt() ([]byte, error) { 224 dataKey, openpgpErr := key.decryptWithCryptoOpenpgp() 225 if openpgpErr == nil { 226 log.WithField("fingerprint", key.Fingerprint).Info("Decryption succeeded") 227 return dataKey, nil 228 } 229 dataKey, binaryErr := key.decryptWithGPGBinary() 230 if binaryErr == nil { 231 log.WithField("fingerprint", key.Fingerprint).Info("Decryption succeeded") 232 return dataKey, nil 233 } 234 log.WithField("fingerprint", key.Fingerprint).Info("Decryption failed") 235 return nil, fmt.Errorf( 236 `could not decrypt data key with PGP key: golang.org/x/crypto/openpgp error: %v; GPG binary error: %v`, 237 openpgpErr, binaryErr) 238} 239 240// NeedsRotation returns whether the data key needs to be rotated or not 241func (key *MasterKey) NeedsRotation() bool { 242 return time.Since(key.CreationDate).Hours() > 24*30*6 243} 244 245// ToString returns the string representation of the key, i.e. its fingerprint 246func (key *MasterKey) ToString() string { 247 return key.Fingerprint 248} 249 250func (key *MasterKey) gpgHome() string { 251 dir := os.Getenv("GNUPGHOME") 252 if dir == "" { 253 usr, err := user.Current() 254 if err != nil { 255 return path.Join(os.Getenv("HOME"), "/.gnupg") 256 } 257 return path.Join(usr.HomeDir, ".gnupg") 258 } 259 return dir 260} 261 262// NewMasterKeyFromFingerprint takes a PGP fingerprint and returns a new MasterKey with that fingerprint 263func NewMasterKeyFromFingerprint(fingerprint string) *MasterKey { 264 return &MasterKey{ 265 Fingerprint: strings.Replace(fingerprint, " ", "", -1), 266 CreationDate: time.Now().UTC(), 267 } 268} 269 270// MasterKeysFromFingerprintString takes a comma separated list of PGP fingerprints and returns a slice of new MasterKeys with those fingerprints 271func MasterKeysFromFingerprintString(fingerprint string) []*MasterKey { 272 var keys []*MasterKey 273 if fingerprint == "" { 274 return keys 275 } 276 for _, s := range strings.Split(fingerprint, ",") { 277 keys = append(keys, NewMasterKeyFromFingerprint(s)) 278 } 279 return keys 280} 281 282func (key *MasterKey) loadRing(path string) (openpgp.EntityList, error) { 283 f, err := os.Open(path) 284 if err != nil { 285 return openpgp.EntityList{}, err 286 } 287 defer f.Close() 288 keyring, err := openpgp.ReadKeyRing(f) 289 if err != nil { 290 return keyring, err 291 } 292 return keyring, nil 293} 294 295func (key *MasterKey) secRing() (openpgp.EntityList, error) { 296 return key.loadRing(key.gpgHome() + "/secring.gpg") 297} 298 299func (key *MasterKey) pubRing() (openpgp.EntityList, error) { 300 return key.loadRing(key.gpgHome() + "/pubring.gpg") 301} 302 303func (key *MasterKey) fingerprintMap(ring openpgp.EntityList) map[string]openpgp.Entity { 304 fps := make(map[string]openpgp.Entity) 305 for _, entity := range ring { 306 fp := strings.ToUpper(hex.EncodeToString(entity.PrimaryKey.Fingerprint[:])) 307 if entity != nil { 308 fps[fp] = *entity 309 } 310 } 311 return fps 312} 313 314func (key *MasterKey) passphrasePrompt() func(keys []openpgp.Key, symmetric bool) ([]byte, error) { 315 callCounter := 0 316 maxCalls := 3 317 return func(keys []openpgp.Key, symmetric bool) ([]byte, error) { 318 if callCounter >= maxCalls { 319 return nil, fmt.Errorf("function passphrasePrompt called too many times") 320 } 321 callCounter++ 322 323 conn, err := gpgagent.NewConn() 324 if err == gpgagent.ErrNoAgent { 325 log.Infof("gpg-agent not found, continuing with manual passphrase " + 326 "input...") 327 fmt.Print("Enter PGP key passphrase: ") 328 pass, err := gopass.GetPasswd() 329 if err != nil { 330 return nil, err 331 } 332 for _, k := range keys { 333 k.PrivateKey.Decrypt(pass) 334 } 335 return pass, err 336 } 337 if err != nil { 338 return nil, fmt.Errorf("Could not establish connection with gpg-agent: %s", err) 339 } 340 defer conn.Close() 341 for _, k := range keys { 342 req := gpgagent.PassphraseRequest{ 343 CacheKey: k.PublicKey.KeyIdShortString(), 344 Prompt: "Passphrase", 345 Desc: fmt.Sprintf("Unlock key %s to decrypt sops's key", k.PublicKey.KeyIdShortString()), 346 } 347 pass, err := conn.GetPassphrase(&req) 348 if err != nil { 349 return nil, fmt.Errorf("gpg-agent passphrase request errored: %s", err) 350 } 351 k.PrivateKey.Decrypt([]byte(pass)) 352 return []byte(pass), nil 353 } 354 return nil, fmt.Errorf("No key to unlock") 355 } 356} 357 358// ToMap converts the MasterKey into a map for serialization purposes 359func (key MasterKey) ToMap() map[string]interface{} { 360 out := make(map[string]interface{}) 361 out["fp"] = key.Fingerprint 362 out["created_at"] = key.CreationDate.UTC().Format(time.RFC3339) 363 out["enc"] = key.EncryptedKey 364 return out 365} 366