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:
parent
82d49bf235
commit
e65ed17b41
34 changed files with 2573 additions and 1478 deletions
310
crypto/key.go
310
crypto/key.go
|
|
@ -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 {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue