Skip to content

Commit

Permalink
Rebuild structure to use Messages and Signature models
Browse files Browse the repository at this point in the history
  • Loading branch information
wussler committed May 17, 2019
1 parent 44b020d commit 56b36f5
Show file tree
Hide file tree
Showing 16 changed files with 812 additions and 694 deletions.
1 change: 1 addition & 0 deletions constants/armor.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ const (
ArmorHeaderVersion = "GopenPGP 0.0.1 (" + Version + ")"
ArmorHeaderComment = "https://gopenpgp.org"
PGPMessageHeader = "PGP MESSAGE"
PGPSignatureHeader = "PGP SIGNATURE"
PublicKeyHeader = "PGP PUBLIC KEY BLOCK"
PrivateKeyHeader = "PGP PRIVATE KEY BLOCK"
)
36 changes: 16 additions & 20 deletions crypto/attachment.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,7 @@ import (
"runtime"
"sync"

armorUtils "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"
)
Expand All @@ -20,7 +18,7 @@ type AttachmentProcessor struct {
w *io.WriteCloser
pipe *io.PipeWriter
done sync.WaitGroup
split *models.EncryptedSplit
split *PGPSplitMessage
garbageCollector int
err error
}
Expand All @@ -33,7 +31,7 @@ func (ap *AttachmentProcessor) Process(plainData []byte) {
}

// Finish closes the attachment and returns the encrypted data
func (ap *AttachmentProcessor) Finish() (*models.EncryptedSplit, error) {
func (ap *AttachmentProcessor) Finish() (*PGPSplitMessage, error) {
if ap.err != nil {
return nil, ap.err
}
Expand All @@ -48,7 +46,7 @@ func (ap *AttachmentProcessor) Finish() (*models.EncryptedSplit, error) {

// newAttachmentProcessor creates an AttachmentProcessor which can be used to encrypt
// a file. It takes an estimatedSize and fileName as hints about the file.
func (publicKey *KeyRing) newAttachmentProcessor(
func (keyRing *KeyRing) newAttachmentProcessor(
estimatedSize int, fileName string, garbageCollector int,
) (*AttachmentProcessor, error) {
attachmentProc := &AttachmentProcessor{}
Expand All @@ -69,7 +67,9 @@ func (publicKey *KeyRing) newAttachmentProcessor(

go func() {
defer attachmentProc.done.Done()
split, splitError := SeparateKeyAndData(nil, reader, estimatedSize, garbageCollector)
ciphertext, _ := ioutil.ReadAll(reader)
message := NewPGPMessage(ciphertext)
split, splitError := message.SeparateKeyAndData(estimatedSize, garbageCollector)
if attachmentProc.err != nil {
attachmentProc.err = splitError
}
Expand All @@ -79,7 +79,7 @@ func (publicKey *KeyRing) newAttachmentProcessor(

var ew io.WriteCloser
var encryptErr error
ew, encryptErr = openpgp.Encrypt(writer, publicKey.entities, nil, hints, config)
ew, encryptErr = openpgp.Encrypt(writer, keyRing.entities, nil, hints, config)
if encryptErr != nil {
return nil, encryptErr
}
Expand All @@ -90,8 +90,8 @@ func (publicKey *KeyRing) newAttachmentProcessor(
}

// EncryptAttachment encrypts a file. fileName
func (publicKey *KeyRing) EncryptAttachment(plainData []byte, fileName string) (*models.EncryptedSplit, error) {
ap, err := publicKey.newAttachmentProcessor(len(plainData), fileName, -1)
func (keyRing *KeyRing) EncryptAttachment(plainData []byte, fileName string) (*PGPSplitMessage, error) {
ap, err := keyRing.newAttachmentProcessor(len(plainData), fileName, -1)
if err != nil {
return nil, err
}
Expand All @@ -107,31 +107,27 @@ func (publicKey *KeyRing) EncryptAttachment(plainData []byte, fileName string) (
// to encrypt a file. It takes an estimatedSize and fileName as hints about the
// file. It is optimized for low-memory environments and collects garbage every
// megabyte.
func (publicKey *KeyRing) NewLowMemoryAttachmentProcessor(
func (keyRing *KeyRing) NewLowMemoryAttachmentProcessor(
estimatedSize int, fileName string,
) (*AttachmentProcessor, error) {
return publicKey.newAttachmentProcessor(estimatedSize, fileName, 1<<20)
return keyRing.newAttachmentProcessor(estimatedSize, fileName, 1<<20)
}

// SplitArmor is a helper method which splits an armored message into its
// session key packet and symmetrically encrypted data packet.
func SplitArmor(encrypted string) (*models.EncryptedSplit, error) {
var err error

encryptedRaw, err := armorUtils.Unarmor(encrypted)
func SplitArmor(encrypted string) (*PGPSplitMessage, error) {
message, err := NewPGPMessageFromArmored(encrypted)
if err != nil {
return nil, err
}

encryptedReader := bytes.NewReader(encryptedRaw)

return SeparateKeyAndData(nil, encryptedReader, len(encrypted), -1)
return message.SeparateKeyAndData(len(encrypted), -1)
}

// DecryptAttachment takes a session key packet and symmetrically encrypted data
// packet. privateKeys is a KeyRing that can contain multiple keys.
func (kr *KeyRing) DecryptAttachment(keyPacket, dataPacket []byte) ([]byte, error) {
privKeyEntries := kr.entities
func (keyRing *KeyRing) DecryptAttachment(keyPacket, dataPacket []byte) ([]byte, error) {
privKeyEntries := keyRing.entities

keyReader := bytes.NewReader(keyPacket)
dataReader := bytes.NewReader(dataPacket)
Expand Down
16 changes: 5 additions & 11 deletions crypto/attachment_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package crypto

import (
"encoding/base64"
"strings"
"testing"

"github.com/stretchr/testify/assert"
Expand All @@ -13,21 +12,16 @@ import (

func TestAttachmentGetKey(t *testing.T) {
testKeyPacketsDecoded, err := base64.StdEncoding.DecodeString(readTestFile("attachment_keypacket", false))

if err != nil {
t.Fatal("Expected no error while decoding base64 KeyPacket, got:", err)
}

split, err := SeparateKeyAndData(
testPrivateKeyRing,
strings.NewReader(string(testKeyPacketsDecoded)),
len(testKeyPacketsDecoded),
-1)
simmetricKey, err := testPrivateKeyRing.DecryptSessionKey(testKeyPacketsDecoded)
if err != nil {
t.Fatal("Expected no error while decrypting attachment key, got:", err)
t.Fatal("Expected no error while decrypting KeyPacket, got:", err)
}

assert.Exactly(t, testSymmetricKey.Key, split.KeyPacket)
assert.Exactly(t, testSymmetricKey, simmetricKey)
}

func TestAttachmentSetKey(t *testing.T) {
Expand All @@ -36,12 +30,12 @@ func TestAttachmentSetKey(t *testing.T) {
t.Fatal("Expected no error while encrypting attachment key, got:", err)
}

split, err := SeparateKeyAndData(testPrivateKeyRing, strings.NewReader(string(keyPackets)), len(keyPackets), -1)
simmetricKey, err := testPrivateKeyRing.DecryptSessionKey(keyPackets)
if err != nil {
t.Fatal("Expected no error while decrypting attachment key, got:", err)
}

assert.Exactly(t, testSymmetricKey.Key, split.KeyPacket)
assert.Exactly(t, testSymmetricKey, simmetricKey)
}

func TestAttachnentEncryptDecrypt(t *testing.T) {
Expand Down
171 changes: 0 additions & 171 deletions crypto/key.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,191 +3,20 @@ 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,
}
}

// 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
}

// IsBinKeyExpired checks whether the given (unarmored, binary) key is expired.
func (pgp *GopenPGP) IsBinKeyExpired(publicKey []byte) (bool, error) {
now := pgp.getNow()
Expand Down
Loading

0 comments on commit 56b36f5

Please sign in to comment.