225 lines
5.3 KiB
Go
225 lines
5.3 KiB
Go
package crypto
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/base64"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
|
|
"github.com/ProtonMail/gopenpgp/constants"
|
|
|
|
"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 (symmetricKey *SymmetricKey) GetCipherFunc() packet.CipherFunction {
|
|
cf, ok := symKeyAlgos[symmetricKey.Algo]
|
|
if ok {
|
|
return cf
|
|
}
|
|
|
|
panic("gopenpgp: unsupported cipher function: " + symmetricKey.Algo)
|
|
}
|
|
|
|
// GetBase64Key returns the session key as base64 encoded string.
|
|
func (symmetricKey *SymmetricKey) GetBase64Key() string {
|
|
return base64.StdEncoding.EncodeToString(symmetricKey.Key)
|
|
}
|
|
|
|
func NewSymmetricKeyFromToken(passphrase, algo string) *SymmetricKey {
|
|
return &SymmetricKey{
|
|
Key: []byte(passphrase),
|
|
Algo: algo,
|
|
}
|
|
}
|
|
|
|
func newSymmetricKeyFromEncrypted(ek *packet.EncryptedKey) (*SymmetricKey, error) {
|
|
var algo string
|
|
for k, v := range symKeyAlgos {
|
|
if v == ek.CipherFunc {
|
|
algo = k
|
|
break
|
|
}
|
|
}
|
|
if algo == "" {
|
|
return nil, fmt.Errorf("gopenpgp: unsupported cipher function: %v", ek.CipherFunc)
|
|
}
|
|
|
|
symmetricKey := &SymmetricKey{
|
|
Key: ek.Key,
|
|
Algo: algo,
|
|
}
|
|
|
|
return symmetricKey, nil
|
|
}
|
|
|
|
// Encrypt encrypts a PlainMessage to PGPMessage with a SymmetricKey
|
|
// * message : The plain data as a PlainMessage
|
|
// * output : The encrypted data as PGPMessage
|
|
func (symmetricKey *SymmetricKey) Encrypt(message *PlainMessage) (*PGPMessage, error) {
|
|
encrypted, err := symmetricEncrypt(message.GetBinary(), symmetricKey)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return NewPGPMessage(encrypted), nil
|
|
}
|
|
|
|
// Decrypt decrypts password protected pgp binary messages
|
|
// * encrypted: PGPMessage
|
|
// * output: PlainMessage
|
|
func (symmetricKey *SymmetricKey) Decrypt(message *PGPMessage) (*PlainMessage, error) {
|
|
decrypted, err := symmetricDecrypt(message.NewReader(), symmetricKey)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
binMessage := NewPlainMessage(decrypted)
|
|
return binMessage, nil
|
|
}
|
|
|
|
// NewSymmetricKeyFromKeyPacket decrypts the binary symmetrically encrypted
|
|
// session key packet and returns the session key.
|
|
func NewSymmetricKeyFromKeyPacket(keyPacket []byte, password string) (*SymmetricKey, error) {
|
|
keyReader := bytes.NewReader(keyPacket)
|
|
packets := packet.NewReader(keyReader)
|
|
|
|
var symKeys []*packet.SymmetricKeyEncrypted
|
|
for {
|
|
|
|
var p packet.Packet
|
|
var err error
|
|
if p, err = packets.Next(); err != nil {
|
|
break
|
|
}
|
|
|
|
switch p := p.(type) {
|
|
case *packet.SymmetricKeyEncrypted:
|
|
symKeys = append(symKeys, p)
|
|
}
|
|
}
|
|
|
|
pwdRaw := []byte(password)
|
|
// Try the symmetric passphrase first
|
|
if len(symKeys) != 0 && pwdRaw != nil {
|
|
for _, s := range symKeys {
|
|
key, cipherFunc, err := s.Decrypt(pwdRaw)
|
|
if err == nil {
|
|
return &SymmetricKey{
|
|
Key: key,
|
|
Algo: getAlgo(cipherFunc),
|
|
}, nil
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
return nil, errors.New("gopenpgp: password incorrect")
|
|
}
|
|
|
|
// EncryptToKeyPacket encrypts the session key with the password and
|
|
// returns a binary symmetrically encrypted session key packet.
|
|
func (symmetricKey *SymmetricKey) EncryptToKeyPacket(password string) ([]byte, error) {
|
|
outbuf := &bytes.Buffer{}
|
|
|
|
cf := symmetricKey.GetCipherFunc()
|
|
|
|
if len(password) <= 0 {
|
|
return nil, errors.New("gopenpgp: password can't be empty")
|
|
}
|
|
|
|
pwdRaw := []byte(password)
|
|
|
|
config := &packet.Config{
|
|
DefaultCipher: cf,
|
|
}
|
|
|
|
err := packet.SerializeSymmetricKeyEncryptedReuseKey(outbuf, symmetricKey.Key, pwdRaw, config)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return outbuf.Bytes(), nil
|
|
}
|
|
|
|
// ----- INTERNAL FUNCTIONS ------
|
|
|
|
func symmetricEncrypt(message []byte, sk *SymmetricKey) ([]byte, error) {
|
|
var outBuf bytes.Buffer
|
|
|
|
config := &packet.Config{
|
|
Time: pgp.getTimeGenerator(),
|
|
DefaultCipher: sk.GetCipherFunc(),
|
|
}
|
|
|
|
encryptWriter, err := openpgp.SymmetricallyEncrypt(&outBuf, sk.Key, nil, config)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
_, err = encryptWriter.Write(message)
|
|
encryptWriter.Close()
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return outBuf.Bytes(), nil
|
|
}
|
|
|
|
func symmetricDecrypt(encryptedIO io.Reader, sk *SymmetricKey) ([]byte, error) {
|
|
firstTimeCalled := true
|
|
var prompt = func(keys []openpgp.Key, symmetric bool) ([]byte, error) {
|
|
if firstTimeCalled {
|
|
firstTimeCalled = false
|
|
return sk.Key, nil
|
|
}
|
|
return nil, errors.New("gopenpgp: wrong password in symmetric decryption")
|
|
}
|
|
|
|
config := &packet.Config{
|
|
Time: pgp.getTimeGenerator(),
|
|
}
|
|
md, err := openpgp.ReadMessage(encryptedIO, nil, prompt, config)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
messageBuf := bytes.NewBuffer(nil)
|
|
_, err = io.Copy(messageBuf, md.UnverifiedBody)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return messageBuf.Bytes(), nil
|
|
}
|
|
|
|
func getAlgo(cipher packet.CipherFunction) string {
|
|
algo := constants.AES256
|
|
for k, v := range symKeyAlgos {
|
|
if v == cipher {
|
|
algo = k
|
|
break
|
|
}
|
|
}
|
|
|
|
return algo
|
|
}
|