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:
parent
136c0a5495
commit
54f45d0471
46 changed files with 2588 additions and 1770 deletions
455
crypto/key.go
455
crypto/key.go
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue