Openpgp security update (V2) (#31)

* Change keyring unlock functionalities

* Add keyring#Lock, keyring#CheckIntegrity, tests

* Update helpers, fix bugs

* Update go.mod with ProtonMail/crypto commit

* Change key management system

* Clear keys from memory + tests

* Create SessionKey with direct encryption for datapackets. Move symmetrickey to password.

* Fix upstream dependencies

* Update module to V2, documentation

* Add linter

* Add v2 folder to .gitignore

* Minor changes to KeyID getters

* Remove old changelog

* Improve docs, remove compilation script
This commit is contained in:
wussler 2019-12-27 19:35:43 +01:00 committed by GitHub
parent 136c0a5495
commit 54f45d0471
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
46 changed files with 2588 additions and 1770 deletions

View file

@ -4,54 +4,353 @@ import (
"bytes"
"crypto"
"encoding/hex"
"errors"
"fmt"
"io"
"math/big"
"strconv"
"strings"
"github.com/ProtonMail/gopenpgp/armor"
"github.com/ProtonMail/gopenpgp/constants"
"github.com/ProtonMail/gopenpgp/v2/armor"
"github.com/ProtonMail/gopenpgp/v2/constants"
"github.com/pkg/errors"
"golang.org/x/crypto/openpgp"
xarmor "golang.org/x/crypto/openpgp/armor"
"golang.org/x/crypto/openpgp/packet"
)
// IsKeyExpired checks whether the given (unarmored, binary) key is expired.
func IsKeyExpired(publicKey []byte) (bool, error) {
now := getNow()
pubKeyReader := bytes.NewReader(publicKey)
pubKeyEntries, err := openpgp.ReadKeyRing(pubKeyReader)
// Key contains a single private or public key
type Key struct {
// PGP entities in this keyring.
entity *openpgp.Entity
}
// --- Create Key object
// NewKeyFromArmoredReader reads an armored data into a key.
func NewKeyFromArmoredReader(r io.Reader) (key *Key, err error) {
key = &Key{}
err = key.readFrom(r, true)
if err != nil {
return true, err
return nil, err
}
for _, e := range pubKeyEntries {
if _, ok := e.EncryptionKey(now); ok {
return key, nil
}
// NewKeyFromReader reads an binary data into Key
func NewKeyFromReader(r io.Reader) (key *Key, err error) {
key = &Key{}
err = key.readFrom(r, false)
if err != nil {
return nil, err
}
return key, nil
}
// NewKey creates a new key from the first key in the unarmored binary data
func NewKey(binKeys []byte) (key *Key, err error) {
return NewKeyFromReader(bytes.NewReader(binKeys))
}
// NewKeyFromArmored creates a new key from the first key in an armored
func NewKeyFromArmored(armored string) (key *Key, err error) {
return NewKeyFromArmoredReader(strings.NewReader(armored))
}
// GenerateRSAKeyWithPrimes generates a RSA key using the given primes.
func GenerateRSAKeyWithPrimes(
name, email string,
bits int,
primeone, primetwo, primethree, primefour []byte,
) (*Key, error) {
return generateKey(name, email, "rsa", bits, primeone, primetwo, primethree, primefour)
}
// GenerateKey generates a key of the given keyType ("rsa" or "x25519").
// If keyType is "rsa", bits is the RSA bitsize of the key.
// If keyType is "x25519" bits is unused.
func GenerateKey(name, email string, keyType string, bits int) (*Key, error) {
return generateKey(name, email, keyType, bits, nil, nil, nil, nil)
}
// --- Operate on key
// Copy creates a deep copy of the key.
func (key *Key) Copy() (*Key, error) {
serialized, err := key.Serialize()
if err != nil {
return nil, err
}
return NewKey(serialized)
}
// Lock locks a copy of the key.
func (key *Key) Lock(passphrase []byte) (*Key, error) {
unlocked, err := key.IsUnlocked()
if err != nil {
return nil, err
}
if !unlocked {
return nil, errors.New("gopenpgp: key is not unlocked")
}
lockedKey, err := key.Copy()
if err != nil {
return nil, err
}
err = lockedKey.entity.PrivateKey.Encrypt(passphrase)
if err != nil {
return nil, errors.Wrap(err, "gopenpgp: error in locking key")
}
for _, sub := range lockedKey.entity.Subkeys {
if sub.PrivateKey != nil {
if err := sub.PrivateKey.Encrypt(passphrase); err != nil {
return nil, errors.Wrap(err, "gopenpgp: error in locking sub key")
}
}
}
locked, err := lockedKey.IsLocked()
if err != nil {
return nil, err
}
if !locked {
return nil, errors.New("gopenpgp: unable to lock key")
}
return lockedKey, nil
}
// Unlock unlocks a copy of the key
func (key *Key) Unlock(passphrase []byte) (*Key, error) {
isLocked, err := key.IsLocked()
if err != nil {
return nil, err
}
if !isLocked {
return nil, errors.New("gopenpgp: key is not locked")
}
unlockedKey, err := key.Copy()
if err != nil {
return nil, err
}
err = unlockedKey.entity.PrivateKey.Decrypt(passphrase)
if err != nil {
return nil, errors.Wrap(err, "gopenpgp: error in unlocking key")
}
for _, sub := range unlockedKey.entity.Subkeys {
if sub.PrivateKey != nil {
if err := sub.PrivateKey.Decrypt(passphrase); err != nil {
return nil, errors.Wrap(err, "gopenpgp: error in unlocking sub key")
}
}
}
isUnlocked, err := unlockedKey.IsUnlocked()
if err != nil {
return nil, err
}
if !isUnlocked {
return nil, errors.New("gopenpgp: unable to unlock key")
}
return unlockedKey, nil
}
// --- Export key
func (key *Key) Serialize() ([]byte, error) {
var buffer bytes.Buffer
var err error
if key.entity.PrivateKey == nil {
err = key.entity.Serialize(&buffer)
} else {
err = key.entity.SerializePrivateNoSign(&buffer, nil)
}
return buffer.Bytes(), err
}
func (key *Key) Armor() (string, error) {
serialized, err := key.Serialize()
if err != nil {
return "", err
}
return armor.ArmorWithType(serialized, constants.PrivateKeyHeader)
}
// GetArmoredPublicKey returns the armored public keys from this keyring.
func (key *Key) GetArmoredPublicKey() (s string, err error) {
var outBuf bytes.Buffer
aw, err := xarmor.Encode(&outBuf, openpgp.PublicKeyType, nil)
if err != nil {
return "", err
}
if err = key.entity.Serialize(aw); err != nil {
_ = aw.Close()
return "", err
}
err = aw.Close()
return outBuf.String(), err
}
// GetPublicKey returns the unarmored public keys from this keyring.
func (key *Key) GetPublicKey() (b []byte, err error) {
var outBuf bytes.Buffer
if err = key.entity.Serialize(&outBuf); err != nil {
return nil, err
}
return outBuf.Bytes(), nil
}
// --- Key object properties
// IsExpired checks whether the key is expired.
func (key *Key) IsExpired() bool {
_, ok := key.entity.EncryptionKey(getNow())
return !ok
}
// IsPrivate returns true if the key is private
func (key *Key) IsPrivate() bool {
return key.entity.PrivateKey != nil
}
// IsLocked checks if a private key is locked
func (key *Key) IsLocked() (bool, error) {
if key.entity.PrivateKey == nil {
return true, errors.New("gopenpgp: a public key cannot be locked")
}
for _, sub := range key.entity.Subkeys {
if sub.PrivateKey != nil && !sub.PrivateKey.Encrypted {
return false, nil
}
}
return true, errors.New("keys expired")
return key.entity.PrivateKey.Encrypted, nil
}
// IsArmoredKeyExpired checks whether the given armored key is expired.
func IsArmoredKeyExpired(publicKey string) (bool, error) {
rawPubKey, err := armor.Unarmor(publicKey)
if err != nil {
return false, err
// IsUnlocked checks if a private key is unlocked
func (key *Key) IsUnlocked() (bool, error) {
if key.entity.PrivateKey == nil {
return true, errors.New("gopenpgp: a public key cannot be unlocked")
}
return IsKeyExpired(rawPubKey)
for _, sub := range key.entity.Subkeys {
if sub.PrivateKey != nil && sub.PrivateKey.Encrypted {
return false, nil
}
}
return !key.entity.PrivateKey.Encrypted, nil
}
// Check verifies if the public keys match the private key parameters by signing and verifying
func (key *Key) Check() (bool, error) {
var err error
testSign := bytes.Repeat([]byte{0x01}, 64)
testReader := bytes.NewReader(testSign)
if !key.IsPrivate() {
return false, errors.New("gopenpgp: can check only private key")
}
var signBuf bytes.Buffer
if err = openpgp.DetachSign(&signBuf, key.entity, testReader, nil); err != nil {
return false, errors.New("gopenpgp: unable to sign with key")
}
testReader = bytes.NewReader(testSign)
signer, err := openpgp.CheckDetachedSignature(openpgp.EntityList{key.entity}, testReader, &signBuf, nil)
if signer == nil || err != nil {
return false, nil
}
return true, nil
}
// PrintFingerprints is a debug helper function that prints the key and subkey fingerprints.
func (key *Key) PrintFingerprints() {
for _, subKey := range key.entity.Subkeys {
if !subKey.Sig.FlagsValid || subKey.Sig.FlagEncryptStorage || subKey.Sig.FlagEncryptCommunications {
fmt.Println("SubKey:" + hex.EncodeToString(subKey.PublicKey.Fingerprint[:]))
}
}
fmt.Println("PrimaryKey:" + hex.EncodeToString(key.entity.PrimaryKey.Fingerprint[:]))
}
// GetHexKeyID returns the key ID, hex encoded as a string
func (key *Key) GetHexKeyID() string {
return strconv.FormatUint(key.GetKeyID(), 16)
}
// GetKeyID returns the key ID, encoded as 8-byte int
func (key *Key) GetKeyID() uint64 {
return key.entity.PrimaryKey.KeyId
}
// GetFingerprint gets the fingerprint from the key
func (key *Key) GetFingerprint() string {
return hex.EncodeToString(key.entity.PrimaryKey.Fingerprint[:])
}
// --- Internal methods
// readFrom reads unarmored and armored keys from r and adds them to the keyring.
func (key *Key) readFrom(r io.Reader, armored bool) error {
var err error
var entities openpgp.EntityList
if armored {
entities, err = openpgp.ReadArmoredKeyRing(r)
} else {
entities, err = openpgp.ReadKeyRing(r)
}
if err != nil {
return err
}
if len(entities) > 1 {
return errors.New("gopenpgp: the key contains too many entities")
}
if len(entities) == 0 {
return errors.New("gopenpgp: the key does not contain any entity")
}
key.entity = entities[0]
return nil
}
func generateKey(
name, email, passphrase, keyType string,
name, email string,
keyType string,
bits int,
prime1, prime2, prime3, prime4 []byte,
) (string, error) {
if len(email) <= 0 {
return "", errors.New("invalid email format")
) (*Key, error) {
if len(email) == 0 {
return nil, errors.New("gopenpgp: invalid email format")
}
if len(name) <= 0 {
return "", errors.New("invalid name format")
if len(name) == 0 {
return nil, errors.New("gopenpgp: invalid name format")
}
comments := ""
@ -84,114 +383,16 @@ func generateKey(
newEntity, err := openpgp.NewEntity(name, comments, email, cfg)
if err != nil {
return "", err
return nil, err
}
if err := newEntity.SelfSign(nil); err != nil {
return "", err
return nil, err
}
rawPwd := []byte(passphrase)
if newEntity.PrivateKey != nil && !newEntity.PrivateKey.Encrypted {
if err := newEntity.PrivateKey.Encrypt(rawPwd); err != nil {
return "", err
}
if newEntity.PrivateKey == nil {
return nil, errors.New("gopenpgp: error in generating private key")
}
for _, sub := range newEntity.Subkeys {
if sub.PrivateKey != nil && !sub.PrivateKey.Encrypted {
if err := sub.PrivateKey.Encrypt(rawPwd); err != nil {
return "", err
}
}
}
w := bytes.NewBuffer(nil)
if err := newEntity.SerializePrivateNoSign(w, nil); err != nil {
return "", err
}
serialized := w.Bytes()
return armor.ArmorWithType(serialized, constants.PrivateKeyHeader)
}
// GenerateRSAKeyWithPrimes generates a RSA key using the given primes.
func GenerateRSAKeyWithPrimes(
name, email, passphrase string,
bits int,
primeone, primetwo, primethree, primefour []byte,
) (string, error) {
return generateKey(name, email, passphrase, "rsa", bits, primeone, primetwo, primethree, primefour)
}
// GenerateKey generates a key of the given keyType ("rsa" or "x25519").
// If keyType is "rsa", bits is the RSA bitsize of the key.
// If keyType is "x25519" bits is unused.
func GenerateKey(name, email, passphrase, keyType string, bits int) (string, error) {
return generateKey(name, email, passphrase, keyType, bits, nil, nil, nil, nil)
}
// UpdatePrivateKeyPassphrase decrypts the given armored privateKey with oldPassphrase,
// re-encrypts it with newPassphrase, and returns the new armored key.
func UpdatePrivateKeyPassphrase(
privateKey string, oldPassphrase string, newPassphrase string,
) (string, error) {
privKey := strings.NewReader(privateKey)
privKeyEntries, err := openpgp.ReadArmoredKeyRing(privKey)
if err != nil {
return "", err
}
oldrawPwd := []byte(oldPassphrase)
newRawPwd := []byte(newPassphrase)
w := bytes.NewBuffer(nil)
for _, e := range privKeyEntries {
if e.PrivateKey != nil && e.PrivateKey.Encrypted {
if err := e.PrivateKey.Decrypt(oldrawPwd); err != nil {
return "", err
}
}
if e.PrivateKey != nil && !e.PrivateKey.Encrypted {
if err := e.PrivateKey.Encrypt(newRawPwd); err != nil {
return "", err
}
}
for _, sub := range e.Subkeys {
if sub.PrivateKey != nil && sub.PrivateKey.Encrypted {
if err := sub.PrivateKey.Decrypt(oldrawPwd); err != nil {
return "", err
}
}
if sub.PrivateKey != nil && !sub.PrivateKey.Encrypted {
if err := sub.PrivateKey.Encrypt(newRawPwd); err != nil {
return "", err
}
}
}
if err := e.SerializePrivateNoSign(w, nil); err != nil {
return "", err
}
}
serialized := w.Bytes()
return armor.ArmorWithType(serialized, constants.PrivateKeyHeader)
}
// PrintFingerprints is a debug helper function that prints the key and subkey fingerprints.
func PrintFingerprints(pubKey string) (string, error) {
pubKeyReader := strings.NewReader(pubKey)
entries, err := openpgp.ReadArmoredKeyRing(pubKeyReader)
if err != nil {
return "", err
}
for _, e := range entries {
for _, subKey := range e.Subkeys {
if !subKey.Sig.FlagsValid || subKey.Sig.FlagEncryptStorage || subKey.Sig.FlagEncryptCommunications {
fmt.Println("SubKey:" + hex.EncodeToString(subKey.PublicKey.Fingerprint[:]))
}
}
fmt.Println("PrimaryKey:" + hex.EncodeToString(e.PrimaryKey.Fingerprint[:]))
}
return "", nil
return &Key{newEntity}, nil
}