Refactor api (#6)

* Refactor library, remove duplicates

* Rebuild structure to use Messages and Signature models

* Use PGPSplitMessage

* Remove signature model

* Various fixes

* Add helpers with tests

* Fixes, add some docs, add tests

* Add attachment helpers

* Add helpers Symmetric encryption

* Edit docs + examples

* Rename kr to keyRing

* Various fixes for documentation

* Edit JSON handling functions, add decrypt keyring via token

* Add proposal changes doc

* Fix CI

* Drop *Message functions, join CleartextMessage and BinaryMessage

* Change canonicalization and trimming only to text signatures

* Add cleartextsignature, detach signature from message model, move helpers

* Documentation, remove optional parameters

* Move verification to separate model

* Don't return message in VerifyDetached

* Update table of contents in readme

* Appease golint

* Run go fmt

* Rename Encrypt/DecryptMessageWithPassword to ..WithToken

These functions shouldn't be used with user-provided passwords,
as they don't do any key-stretching.

* Change key generation usernames
This commit is contained in:
wussler 2019-06-03 17:00:01 +02:00 committed by GitHub
parent 82d49bf235
commit e65ed17b41
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
34 changed files with 2573 additions and 1478 deletions

View file

@ -3,278 +3,22 @@ package crypto
import (
"bytes"
"crypto"
"encoding/base64"
"encoding/hex"
"errors"
"fmt"
"io"
"math/big"
"runtime"
"strings"
"time"
"github.com/ProtonMail/gopenpgp/armor"
"github.com/ProtonMail/gopenpgp/constants"
"github.com/ProtonMail/gopenpgp/models"
"golang.org/x/crypto/openpgp"
"golang.org/x/crypto/openpgp/packet"
)
// SymmetricKey stores a decrypted session key.
type SymmetricKey struct {
// The decrypted binary session key.
Key []byte
// The symmetric encryption algorithm used with this key.
Algo string
}
var symKeyAlgos = map[string]packet.CipherFunction{
constants.ThreeDES: packet.Cipher3DES,
constants.TripleDES: packet.Cipher3DES,
constants.CAST5: packet.CipherCAST5,
constants.AES128: packet.CipherAES128,
constants.AES192: packet.CipherAES192,
constants.AES256: packet.CipherAES256,
}
// GetCipherFunc returns the cipher function corresponding to the algorithm used
// with this SymmetricKey.
func (sk *SymmetricKey) GetCipherFunc() packet.CipherFunction {
cf, ok := symKeyAlgos[sk.Algo]
if ok {
return cf
}
panic("gopenpgp: unsupported cipher function: " + sk.Algo)
}
// GetBase64Key returns the session key as base64 encoded string.
func (sk *SymmetricKey) GetBase64Key() string {
return base64.StdEncoding.EncodeToString(sk.Key)
}
func newSymmetricKey(ek *packet.EncryptedKey) *SymmetricKey {
var algo string
for k, v := range symKeyAlgos {
if v == ek.CipherFunc {
algo = k
break
}
}
if algo == "" {
panic(fmt.Sprintf("gopenpgp: unsupported cipher function: %v", ek.CipherFunc))
}
return &SymmetricKey{
Key: ek.Key, //base64.StdEncoding.EncodeToString(ek.Key),
Algo: algo,
}
}
// DecryptAttKey decrypts a public-key encrypted session key and returns the
// decrypted symmetric session key.
func DecryptAttKey(kr *KeyRing, keyPacket string) (key *SymmetricKey, err error) {
r := base64.NewDecoder(base64.StdEncoding, strings.NewReader(keyPacket))
packets := packet.NewReader(r)
var p packet.Packet
if p, err = packets.Next(); err != nil {
return
}
ek := p.(*packet.EncryptedKey)
var decryptErr error
for _, key := range kr.entities.DecryptionKeys() {
priv := key.PrivateKey
if priv.Encrypted {
continue
}
if decryptErr = ek.Decrypt(priv, nil); decryptErr == nil {
break
}
}
if decryptErr != nil {
err = fmt.Errorf("gopenpgp: cannot decrypt encrypted key packet: %v", decryptErr)
return
}
key = newSymmetricKey(ek)
return
}
// SeparateKeyAndData reads a binary PGP message from r and splits it into its
// session key packet and symmetrically encrypted data packet.
func SeparateKeyAndData(
kr *KeyRing, r io.Reader,
estimatedLength, garbageCollector int,
) (outSplit *models.EncryptedSplit, err error) {
// For info on each, see: https://golang.org/pkg/runtime/#MemStats
packets := packet.NewReader(r)
outSplit = &models.EncryptedSplit{}
gcCounter := 0
// Store encrypted key and symmetrically encrypted packet separately
var ek *packet.EncryptedKey
var decryptErr error
for {
var p packet.Packet
if p, err = packets.Next(); err == io.EOF {
err = nil
break
}
switch p := p.(type) {
case *packet.EncryptedKey:
// We got an encrypted key. Try to decrypt it with each available key
if ek != nil && ek.Key != nil {
break
}
ek = p
if kr != nil {
for _, key := range kr.entities.DecryptionKeys() {
priv := key.PrivateKey
if priv.Encrypted {
continue
}
if decryptErr = ek.Decrypt(priv, nil); decryptErr == nil {
break
}
}
}
case *packet.SymmetricallyEncrypted:
// The code below is optimized to not
var b bytes.Buffer
// 2^16 is an estimation of the size difference between input and output, the size difference is most probably
// 16 bytes at a maximum though.
// We need to avoid triggering a grow from the system as this will allocate too much memory causing problems
// in low-memory environments
b.Grow(1<<16 + estimatedLength)
// empty encoded length + start byte
b.Write(make([]byte, 6))
b.WriteByte(byte(1))
actualLength := 1
block := make([]byte, 128)
for {
n, err := p.Contents.Read(block)
if err == io.EOF {
break
}
b.Write(block[:n])
actualLength += n
gcCounter += n
if gcCounter > garbageCollector && garbageCollector > 0 {
runtime.GC()
gcCounter = 0
}
}
// quick encoding
symEncryptedData := b.Bytes()
if actualLength < 192 {
symEncryptedData[4] = byte(210)
symEncryptedData[5] = byte(actualLength)
symEncryptedData = symEncryptedData[4:]
} else if actualLength < 8384 {
actualLength = actualLength - 192
symEncryptedData[3] = byte(210)
symEncryptedData[4] = 192 + byte(actualLength>>8)
symEncryptedData[5] = byte(actualLength)
symEncryptedData = symEncryptedData[3:]
} else {
symEncryptedData[0] = byte(210)
symEncryptedData[1] = byte(255)
symEncryptedData[2] = byte(actualLength >> 24)
symEncryptedData[3] = byte(actualLength >> 16)
symEncryptedData[4] = byte(actualLength >> 8)
symEncryptedData[5] = byte(actualLength)
}
outSplit.DataPacket = symEncryptedData
}
}
if decryptErr != nil {
err = fmt.Errorf("gopenpgp: cannot decrypt encrypted key packet: %v", decryptErr)
return nil, err
}
if ek == nil {
err = errors.New("gopenpgp: packets don't include an encrypted key packet")
return nil, err
}
if kr == nil {
var buf bytes.Buffer
if err := ek.Serialize(&buf); err != nil {
err = fmt.Errorf("gopenpgp: cannot serialize encrypted key: %v", err)
return nil, err
}
outSplit.KeyPacket = buf.Bytes()
} else {
key := newSymmetricKey(ek)
outSplit.KeyPacket = key.Key
outSplit.Algo = key.Algo
}
return outSplit, nil
}
// EncryptKey encrypts the provided key.
func (kr *KeyRing) EncryptKey(symKey *SymmetricKey) (packets string, err error) {
b := &bytes.Buffer{}
w := base64.NewEncoder(base64.StdEncoding, b)
cf := symKey.GetCipherFunc()
if len(kr.entities) == 0 {
err = fmt.Errorf("gopenpgp: cannot set key: key ring is empty")
return
}
var pub *packet.PublicKey
for _, e := range kr.entities {
for _, subKey := range e.Subkeys {
if !subKey.Sig.FlagsValid || subKey.Sig.FlagEncryptStorage || subKey.Sig.FlagEncryptCommunications {
pub = subKey.PublicKey
break
}
}
if pub == nil && len(e.Identities) > 0 {
var i *openpgp.Identity
for _, i = range e.Identities {
break
}
if i.SelfSignature.FlagsValid || i.SelfSignature.FlagEncryptStorage || i.SelfSignature.FlagEncryptCommunications {
pub = e.PrimaryKey
}
}
if pub != nil {
break
}
}
if pub == nil {
err = fmt.Errorf("gopenpgp: cannot set key: no public key available")
return "", err
}
if err = packet.SerializeEncryptedKey(w, pub, cf, symKey.Key, nil); err != nil {
err = fmt.Errorf("gopenpgp: cannot set key: %v", err)
return "", err
}
if err = w.Close(); err != nil {
err = fmt.Errorf("gopenpgp: cannot set key: %v", err)
return "", err
}
return b.String(), nil
}
// IsKeyExpiredBin checks whether the given (unarmored, binary) key is expired.
func (pgp *GopenPGP) IsKeyExpiredBin(publicKey []byte) (bool, error) {
// IsKeyExpired checks whether the given (unarmored, binary) key is expired.
func (pgp *GopenPGP) IsKeyExpired(publicKey []byte) (bool, error) {
now := pgp.getNow()
pubKeyReader := bytes.NewReader(publicKey)
pubKeyEntries, err := openpgp.ReadKeyRing(pubKeyReader)
@ -325,34 +69,26 @@ func (pgp *GopenPGP) IsKeyExpiredBin(publicKey []byte) (bool, error) {
return true, errors.New("keys expired")
}
const (
ok = 0
notSigned = 1
noVerifier = 2
failed = 3
)
// IsKeyExpired checks whether the given armored key is expired.
func (pgp *GopenPGP) IsKeyExpired(publicKey string) (bool, error) {
// IsArmoredKeyExpired checks whether the given armored key is expired.
func (pgp *GopenPGP) IsArmoredKeyExpired(publicKey string) (bool, error) {
rawPubKey, err := armor.Unarmor(publicKey)
if err != nil {
return false, err
}
return pgp.IsKeyExpiredBin(rawPubKey)
return pgp.IsKeyExpired(rawPubKey)
}
func (pgp *GopenPGP) generateKey(
userName, domain, passphrase, keyType string,
name, email, passphrase, keyType string,
bits int,
prime1, prime2, prime3, prime4 []byte,
) (string, error) {
if len(userName) <= 0 {
return "", errors.New("invalid user name format")
if len(email) <= 0 {
return "", errors.New("invalid email format")
}
var email = userName
if len(domain) > 0 {
email = email + "@" + domain
if len(name) <= 0 {
return "", errors.New("invalid name format")
}
comments := ""
@ -383,7 +119,7 @@ func (pgp *GopenPGP) generateKey(
cfg.RSAPrimes = bigPrimes[:]
}
newEntity, err := openpgp.NewEntity(email, comments, email, cfg)
newEntity, err := openpgp.NewEntity(name, comments, email, cfg)
if err != nil {
return "", err
}
@ -417,23 +153,22 @@ func (pgp *GopenPGP) generateKey(
// GenerateRSAKeyWithPrimes generates a RSA key using the given primes.
func (pgp *GopenPGP) GenerateRSAKeyWithPrimes(
userName, domain, passphrase string,
name, email, passphrase string,
bits int,
primeone, primetwo, primethree, primefour []byte,
) (string, error) {
return pgp.generateKey(userName, domain, passphrase, "rsa", bits, primeone, primetwo, primethree, primefour)
return pgp.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 (pgp *GopenPGP) GenerateKey(userName, domain, passphrase, keyType string, bits int) (string, error) {
return pgp.generateKey(userName, domain, passphrase, keyType, bits, nil, nil, nil, nil)
// 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 (pgp *GopenPGP) GenerateKey(name, email, passphrase, keyType string, bits int) (string, error) {
return pgp.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.
// UpdatePrivateKeyPassphrase decrypts the given armored privateKey with oldPassphrase,
// re-encrypts it with newPassphrase, and returns the new armored key.
func (pgp *GopenPGP) UpdatePrivateKeyPassphrase(
privateKey string, oldPassphrase string, newPassphrase string,
) (string, error) {
@ -479,9 +214,8 @@ func (pgp *GopenPGP) UpdatePrivateKeyPassphrase(
return armor.ArmorWithType(serialized, constants.PrivateKeyHeader)
}
// CheckKey is a debug helper function that prints the key and subkey
// fingerprints.
func (pgp *GopenPGP) CheckKey(pubKey string) (string, error) {
// PrintFingerprints is a debug helper function that prints the key and subkey fingerprints.
func (pgp *GopenPGP) PrintFingerprints(pubKey string) (string, error) {
pubKeyReader := strings.NewReader(pubKey)
entries, err := openpgp.ReadArmoredKeyRing(pubKeyReader)
if err != nil {