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

@ -2,271 +2,326 @@ package crypto
import (
"bytes"
"encoding/base64"
"errors"
"fmt"
"io"
"io/ioutil"
"math"
"time"
"regexp"
"runtime"
"golang.org/x/crypto/openpgp"
"golang.org/x/crypto/openpgp/armor"
pgpErrors "golang.org/x/crypto/openpgp/errors"
"golang.org/x/crypto/openpgp/packet"
armorUtils "github.com/ProtonMail/gopenpgp/armor"
"github.com/ProtonMail/gopenpgp/armor"
"github.com/ProtonMail/gopenpgp/constants"
"github.com/ProtonMail/gopenpgp/internal"
"github.com/ProtonMail/gopenpgp/models"
"golang.org/x/crypto/openpgp/packet"
)
// DecryptMessageStringKey decrypts encrypted message use private key (string)
// encryptedText : string armored encrypted
// privateKey : armored private use to decrypt message
// passphrase : match with private key to decrypt message
func (pgp *GopenPGP) DecryptMessageStringKey(
encryptedText, privateKey, passphrase string,
) (string, error) {
privKeyRaw, err := armorUtils.Unarmor(privateKey)
if err != nil {
return "", err
}
privKeyReader := bytes.NewReader(privKeyRaw)
privKeyEntries, err := openpgp.ReadKeyRing(privKeyReader)
if err != nil {
return "", err
}
// ---- MODELS -----
return pgp.DecryptMessage(encryptedText, &KeyRing{entities: privKeyEntries}, passphrase)
// PlainMessage stores an unencrypted message.
type PlainMessage struct {
// The content of the message
Data []byte
// if the content is text or binary
TextType bool
}
// DecryptMessage decrypts encrypted string using keyring
// encryptedText : string armored encrypted
// privateKey : keyring with private key to decrypt message, could be multiple keys
// passphrase : match with private key to decrypt message
func (pgp *GopenPGP) DecryptMessage(encryptedText string, privateKey *KeyRing, passphrase string) (string, error) {
md, err := decryptCore(encryptedText, nil, privateKey, passphrase, pgp.getTimeGenerator())
if err != nil {
return "", err
}
decrypted := md.UnverifiedBody
b, err := ioutil.ReadAll(decrypted)
if err != nil {
return "", err
}
return string(b), nil
// Verification for a PlainMessage
type Verification struct {
// If the decoded message was correctly signed. See constants.SIGNATURE* for all values.
Verified int
}
func decryptCore(
encryptedText string, additionalEntries openpgp.EntityList,
privKey *KeyRing, passphrase string,
timeFunc func() time.Time,
) (*openpgp.MessageDetails, error) {
rawPwd := []byte(passphrase)
if err := privKey.Unlock(rawPwd); err != nil {
err = fmt.Errorf("gopenpgp: cannot decrypt passphrase: %v", err)
return nil, err
}
privKeyEntries := privKey.entities
if additionalEntries != nil {
privKeyEntries = append(privKeyEntries, additionalEntries...)
}
encryptedio, err := internal.Unarmor(encryptedText)
if err != nil {
return nil, err
}
config := &packet.Config{Time: timeFunc}
md, err := openpgp.ReadMessage(encryptedio.Body, privKeyEntries, nil, config)
return md, err
// PGPMessage stores a PGP-encrypted message.
type PGPMessage struct {
// The content of the message
Data []byte
}
// DecryptMessageVerify decrypts message and verify the signature
// encryptedText: string armored encrypted
// verifierKey []byte: unarmored verifier keys
// privateKeyRing []byte: unarmored private key to decrypt. could be multiple
// passphrase: match with private key to decrypt message
func (pgp *GopenPGP) DecryptMessageVerify(
encryptedText string, verifierKey, privateKeyRing *KeyRing,
passphrase string, verifyTime int64,
) (*models.DecryptSignedVerify, error) {
out := &models.DecryptSignedVerify{}
out.Verify = failed
// PGPSignature stores a PGP-encoded detached signature.
type PGPSignature struct {
// The content of the signature
Data []byte
}
var verifierEntries openpgp.EntityList
if len(verifierKey.entities) == 0 {
out.Verify = noVerifier
// PGPSplitMessage contains a separate session key packet and symmetrically
// encrypted data packet.
type PGPSplitMessage struct {
DataPacket []byte
KeyPacket []byte
}
// ---- GENERATORS -----
// NewPlainMessage generates a new binary PlainMessage ready for encryption,
// signature, or verification from the unencrypted binary data.
func NewPlainMessage(data []byte) *PlainMessage {
return &PlainMessage{
Data: data,
TextType: false,
}
}
md, err := decryptCore(
encryptedText,
verifierEntries,
privateKeyRing,
passphrase,
func() time.Time { return time.Unix(0, 0) }) // TODO: I doubt this time is correct
// NewPlainMessageFromString generates a new text PlainMessage,
// ready for encryption, signature, or verification from an unencrypted string.
func NewPlainMessageFromString(text string) *PlainMessage {
return &PlainMessage{
Data: []byte(text),
TextType: true,
}
}
// newVerification returns a new instance of *Verification with the specified value
func newVerification(value int) *Verification {
return &Verification{
Verified: value,
}
}
// NewPGPMessage generates a new PGPMessage from the unarmored binary data.
func NewPGPMessage(data []byte) *PGPMessage {
return &PGPMessage{
Data: data,
}
}
// NewPGPMessageFromArmored generates a new PGPMessage from an armored string ready for decryption.
func NewPGPMessageFromArmored(armored string) (*PGPMessage, error) {
encryptedIO, err := internal.Unarmor(armored)
if err != nil {
return nil, err
}
decrypted := md.UnverifiedBody
b, err := ioutil.ReadAll(decrypted)
message, err := ioutil.ReadAll(encryptedIO.Body)
if err != nil {
return nil, err
}
processSignatureExpiration(md, verifyTime)
return &PGPMessage{
Data: message,
}, nil
}
out.Plaintext = string(b)
if md.IsSigned {
if md.SignedBy != nil {
if len(verifierKey.entities) > 0 {
matches := verifierKey.entities.KeysById(md.SignedByKeyId)
if len(matches) > 0 {
if md.SignatureError == nil {
out.Verify = ok
} else {
out.Message = md.SignatureError.Error()
out.Verify = failed
}
// NewPGPSplitMessage generates a new PGPSplitMessage from the binary unarmored keypacket,
// datapacket, and encryption algorithm.
func NewPGPSplitMessage(keyPacket []byte, dataPacket []byte) *PGPSplitMessage {
return &PGPSplitMessage{
KeyPacket: keyPacket,
DataPacket: dataPacket,
}
}
// NewPGPSplitMessageFromArmored generates a new PGPSplitMessage by splitting an armored message into its
// session key packet and symmetrically encrypted data packet.
func NewPGPSplitMessageFromArmored(encrypted string) (*PGPSplitMessage, error) {
message, err := NewPGPMessageFromArmored(encrypted)
if err != nil {
return nil, err
}
return message.SeparateKeyAndData(len(encrypted), -1)
}
// NewPGPSignature generates a new PGPSignature from the unarmored binary data.
func NewPGPSignature(data []byte) *PGPSignature {
return &PGPSignature{
Data: data,
}
}
// NewPGPSignatureFromArmored generates a new PGPSignature from the armored string ready for verification.
func NewPGPSignatureFromArmored(armored string) (*PGPSignature, error) {
encryptedIO, err := internal.Unarmor(armored)
if err != nil {
return nil, err
}
signature, err := ioutil.ReadAll(encryptedIO.Body)
if err != nil {
return nil, err
}
return &PGPSignature{
Data: signature,
}, nil
}
// ---- MODEL METHODS -----
// GetBinary returns the binary content of the message as a []byte
func (msg *PlainMessage) GetBinary() []byte {
return msg.Data
}
// GetString returns the content of the message as a string
func (msg *PlainMessage) GetString() string {
return string(msg.Data)
}
// GetBase64 returns the base-64 encoded binary content of the message as a string
func (msg *PlainMessage) GetBase64() string {
return base64.StdEncoding.EncodeToString(msg.Data)
}
// GetVerification returns the verification status of a verification,
// to use after the KeyRing.Decrypt* or KeyRing.Verify* functions.
// The int value returned is to compare to constants.SIGNATURE*.
func (ver *Verification) GetVerification() int {
return ver.Verified
}
// IsValid returns true if the message is signed and the signature is valid.
// To use after the KeyRing.Decrypt* or KeyRing.Verify* functions.
func (ver *Verification) IsValid() bool {
return ver.Verified == constants.SIGNATURE_OK
}
// NewReader returns a New io.Reader for the bianry data of the message
func (msg *PlainMessage) NewReader() io.Reader {
return bytes.NewReader(msg.GetBinary())
}
// IsText returns whether the message is a text message
func (msg *PlainMessage) IsText() bool {
return msg.TextType
}
// IsBinary returns whether the message is a binary message
func (msg *PlainMessage) IsBinary() bool {
return !msg.TextType
}
// GetBinary returns the unarmored binary content of the message as a []byte
func (msg *PGPMessage) GetBinary() []byte {
return msg.Data
}
// NewReader returns a New io.Reader for the unarmored bianry data of the message
func (msg *PGPMessage) NewReader() io.Reader {
return bytes.NewReader(msg.GetBinary())
}
// GetArmored returns the armored message as a string
func (msg *PGPMessage) GetArmored() (string, error) {
return armor.ArmorWithType(msg.Data, constants.PGPMessageHeader)
}
// GetDataPacket returns the unarmored binary datapacket as a []byte
func (msg *PGPSplitMessage) GetDataPacket() []byte {
return msg.DataPacket
}
// GetKeyPacket returns the unarmored binary keypacket as a []byte
func (msg *PGPSplitMessage) GetKeyPacket() []byte {
return msg.KeyPacket
}
// SeparateKeyAndData returns the first keypacket and the (hopefully unique) dataPacket (not verified)
func (msg *PGPMessage) SeparateKeyAndData(estimatedLength, garbageCollector int) (outSplit *PGPSplitMessage, err error) {
// For info on each, see: https://golang.org/pkg/runtime/#MemStats
packets := packet.NewReader(bytes.NewReader(msg.Data))
outSplit = &PGPSplitMessage{}
gcCounter := 0
// Store encrypted key and symmetrically encrypted packet separately
var encryptedKey *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:
if encryptedKey != nil && encryptedKey.Key != nil {
break
}
encryptedKey = p
case *packet.SymmetricallyEncrypted:
// FIXME: add support for multiple keypackets
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 {
out.Verify = noVerifier
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)
}
} else {
out.Verify = noVerifier
}
} else {
out.Verify = notSigned
}
return out, nil
}
// processSignatureExpiration handles signature time verification manually, so we can add a margin to the
// creationTime check.
func processSignatureExpiration(md *openpgp.MessageDetails, verifyTime int64) {
if md.SignatureError == pgpErrors.ErrSignatureExpired {
if verifyTime > 0 {
created := md.Signature.CreationTime.Unix()
expires := int64(math.MaxInt64)
if md.Signature.SigLifetimeSecs != nil {
expires = int64(*md.Signature.SigLifetimeSecs) + created
}
if created-internal.CreationTimeOffset <= verifyTime && verifyTime <= expires {
md.SignatureError = nil
}
} else {
// verifyTime = 0: time check disabled, everything is okay
md.SignatureError = nil
outSplit.DataPacket = symEncryptedData
}
}
if decryptErr != nil {
return nil, fmt.Errorf("gopenpgp: cannot decrypt encrypted key packet: %v", decryptErr)
}
if encryptedKey == nil {
return nil, errors.New("gopenpgp: packets don't include an encrypted key packet")
}
var buf bytes.Buffer
if err := encryptedKey.Serialize(&buf); err != nil {
return nil, fmt.Errorf("gopenpgp: cannot serialize encrypted key: %v", err)
}
outSplit.KeyPacket = buf.Bytes()
return outSplit, nil
}
// EncryptMessageWithPassword encrypts a plain text to pgp message with a password
// plainText string: clear text
// output string: armored pgp message
func (pgp *GopenPGP) EncryptMessageWithPassword(plainText string, password string) (string, error) {
var outBuf bytes.Buffer
w, err := armor.Encode(&outBuf, constants.PGPMessageHeader, internal.ArmorHeaders)
if err != nil {
return "", err
}
config := &packet.Config{Time: pgp.getTimeGenerator()}
plaintext, err := openpgp.SymmetricallyEncrypt(w, []byte(password), nil, config)
if err != nil {
return "", err
}
message := []byte(plainText)
_, err = plaintext.Write(message)
if err != nil {
return "", err
}
err = plaintext.Close()
if err != nil {
return "", err
}
w.Close()
return outBuf.String(), nil
// GetBinary returns the unarmored binary content of the signature as a []byte
func (msg *PGPSignature) GetBinary() []byte {
return msg.Data
}
// EncryptMessage encrypts message with unarmored public key, if pass private key and passphrase will also sign
// the message
// publicKey : bytes unarmored public key
// plainText : the input
// privateKey : optional required when you want to sign
// passphrase : optional required when you pass the private key and this passphrase should decrypt the private key
// trim : bool true if need to trim new lines
func (pgp *GopenPGP) EncryptMessage(
plainText string, publicKey, privateKey *KeyRing,
passphrase string, trim bool,
) (string, error) {
if trim {
plainText = internal.TrimNewlines(plainText)
}
var outBuf bytes.Buffer
w, err := armor.Encode(&outBuf, constants.PGPMessageHeader, internal.ArmorHeaders)
if err != nil {
return "", err
}
var signEntity *openpgp.Entity
if len(passphrase) > 0 && len(privateKey.entities) > 0 {
var err error
signEntity, err = privateKey.GetSigningEntity(passphrase)
if err != nil {
return "", err
}
}
ew, err := EncryptCore(w, publicKey.entities, signEntity, "", false, pgp.getTimeGenerator())
if err != nil {
return "", err
}
_, err = ew.Write([]byte(plainText))
ew.Close()
w.Close()
return outBuf.String(), err
// GetArmored returns the armored signature as a string
func (msg *PGPSignature) GetArmored() (string, error) {
return armor.ArmorWithType(msg.Data, constants.PGPSignatureHeader)
}
// DecryptMessageWithPassword decrypts a pgp message with a password
// encrypted string : armored pgp message
// output string : clear text
func (pgp *GopenPGP) DecryptMessageWithPassword(encrypted string, password string) (string, error) {
encryptedio, err := internal.Unarmor(encrypted)
if err != nil {
return "", err
}
// ---- UTILS -----
firstTimeCalled := true
var prompt = func(keys []openpgp.Key, symmetric bool) ([]byte, error) {
if firstTimeCalled {
firstTimeCalled = false
return []byte(password), nil
}
return nil, errors.New("password incorrect")
}
config := &packet.Config{Time: pgp.getTimeGenerator()}
md, err := openpgp.ReadMessage(encryptedio.Body, nil, prompt, config)
if err != nil {
return "", err
}
messageBuf := bytes.NewBuffer(nil)
_, err = io.Copy(messageBuf, md.UnverifiedBody)
if err != nil {
return "", err
}
return messageBuf.String(), nil
// IsPGPMessage checks if data if has armored PGP message format.
func (pgp *GopenPGP) IsPGPMessage(data string) bool {
re := regexp.MustCompile("^-----BEGIN " + constants.PGPMessageHeader + "-----(?s:.+)-----END " +
constants.PGPMessageHeader + "-----")
return re.MatchString(data)
}