protonmail: sign message bodies, fixes #22

This commit is contained in:
emersion 2018-01-11 00:01:49 +01:00
parent ab599a27fa
commit 0ac8dda458
No known key found for this signature in database
GPG Key ID: 0FDE7BE0E88F5E48
2 changed files with 122 additions and 13 deletions

View File

@ -2,6 +2,8 @@ package protonmail
import ( import (
"io" "io"
"crypto"
"hash"
"time" "time"
"golang.org/x/crypto/openpgp" "golang.org/x/crypto/openpgp"
@ -103,3 +105,96 @@ func generateUnencryptedKey(cipher packet.CipherFunction, config *packet.Config)
Key: symKey, Key: symKey,
}, nil }, nil
} }
func symetricallyEncrypt(ciphertext io.Writer, symKey *packet.EncryptedKey, signer *packet.PrivateKey, config *packet.Config) (plaintext io.WriteCloser, err error) {
// From https://github.com/golang/crypto/blob/master/openpgp/write.go#L172
encryptedData, err := packet.SerializeSymmetricallyEncrypted(ciphertext, symKey.CipherFunc, symKey.Key, config)
if err != nil {
return nil, err
}
hash := crypto.SHA256
if signer != nil {
ops := &packet.OnePassSignature{
SigType: packet.SigTypeBinary,
Hash: hash,
PubKeyAlgo: signer.PubKeyAlgo,
KeyId: signer.KeyId,
IsLast: true,
}
if err := ops.Serialize(encryptedData); err != nil {
return nil, err
}
}
w := encryptedData
if signer != nil {
// If we need to write a signature packet after the literal
// data then we need to stop literalData from closing
// encryptedData.
w = noOpCloser{encryptedData}
}
literalData, err := packet.SerializeLiteral(w, false, "", 0)
if err != nil {
return nil, err
}
if signer != nil {
return signatureWriter{encryptedData, literalData, hash, hash.New(), signer, config}, nil
}
return literalData, nil
}
// signatureWriter hashes the contents of a message while passing it along to
// literalData. When closed, it closes literalData, writes a signature packet
// to encryptedData and then also closes encryptedData.
type signatureWriter struct {
encryptedData io.WriteCloser
literalData io.WriteCloser
hashType crypto.Hash
h hash.Hash
signer *packet.PrivateKey
config *packet.Config
}
func (s signatureWriter) Write(data []byte) (int, error) {
s.h.Write(data)
return s.literalData.Write(data)
}
func (s signatureWriter) Close() error {
sig := &packet.Signature{
SigType: packet.SigTypeBinary,
PubKeyAlgo: s.signer.PubKeyAlgo,
Hash: s.hashType,
CreationTime: s.config.Now(),
IssuerKeyId: &s.signer.KeyId,
}
if err := sig.Sign(s.h, s.signer, s.config); err != nil {
return err
}
if err := s.literalData.Close(); err != nil {
return err
}
if err := sig.Serialize(s.encryptedData); err != nil {
return err
}
return s.encryptedData.Close()
}
// noOpCloser is like an ioutil.NopCloser, but for an io.Writer.
type noOpCloser struct {
w io.Writer
}
func (c noOpCloser) Write(data []byte) (n int, err error) {
return c.w.Write(data)
}
func (c noOpCloser) Close() error {
return nil
}

View File

@ -209,7 +209,7 @@ const (
MessagePackageMIME = 32 MessagePackageMIME = 32
) )
// From https://github.com/ProtonMail/Angular/blob/v3/src/app/composer/controllers/composeMessage.js#L656 // From https://github.com/ProtonMail/WebClient/blob/public/src/app/composer/services/encryptMessage.js
type MessagePackage struct { type MessagePackage struct {
Type MessagePackageType Type MessagePackageType
@ -229,7 +229,7 @@ type MessagePackageSet struct {
Type MessagePackageType // OR of each Type Type MessagePackageType // OR of each Type
Addresses map[string]*MessagePackage Addresses map[string]*MessagePackage
MIMEType string MIMEType string
Body string // Body data packet Body string // Encrypted body data packet
// Only if cleartext is sent // Only if cleartext is sent
BodyKey string BodyKey string
@ -237,6 +237,7 @@ type MessagePackageSet struct {
bodyKey *packet.EncryptedKey bodyKey *packet.EncryptedKey
attachmentKeys map[string]*packet.EncryptedKey attachmentKeys map[string]*packet.EncryptedKey
signature int
} }
func NewMessagePackageSet(attachmentKeys map[string]*packet.EncryptedKey) *MessagePackageSet { func NewMessagePackageSet(attachmentKeys map[string]*packet.EncryptedKey) *MessagePackageSet {
@ -284,31 +285,43 @@ func (set *MessagePackageSet) Encrypt(mimeType string, signed *openpgp.Entity) (
} }
set.bodyKey = key set.bodyKey = key
var encoded bytes.Buffer var signer *packet.PrivateKey
ciphertext := base64.NewEncoder(base64.StdEncoding, &encoded) if signed != nil {
signKey, ok := signingKey(signed, config.Now())
encryptedData, err := packet.SerializeSymmetricallyEncrypted(ciphertext, set.bodyKey.CipherFunc, set.bodyKey.Key, config) if !ok {
if err != nil { return nil, errors.New("no valid signing keys")
return nil, err }
signer = signKey.PrivateKey
if signer == nil {
return nil, errors.New("no private key in signing key")
}
if signer.Encrypted {
return nil, errors.New("signing key must be decrypted")
}
set.signature = 1
} }
// TODO: sign, see https://github.com/golang/crypto/blob/master/openpgp/write.go#L287 encoded := new(bytes.Buffer)
ciphertext := base64.NewEncoder(base64.StdEncoding, encoded)
literalData, err := packet.SerializeLiteral(encryptedData, false, "", 0) cleartext, err := symetricallyEncrypt(ciphertext, key, signer, config)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return &outgoingMessageWriter{ return &outgoingMessageWriter{
cleartext: literalData, cleartext: cleartext,
ciphertext: ciphertext, ciphertext: ciphertext,
encoded: &encoded, encoded: encoded,
set: set, set: set,
}, nil }, nil
} }
func (set *MessagePackageSet) AddCleartext(addr string) error { func (set *MessagePackageSet) AddCleartext(addr string) error {
set.Addresses[addr] = &MessagePackage{Type: MessagePackageCleartext} set.Addresses[addr] = &MessagePackage{
Type: MessagePackageCleartext,
Signature: set.signature,
}
set.Type |= MessagePackageCleartext set.Type |= MessagePackageCleartext
if set.BodyKey == "" || set.AttachmentKeys == nil { if set.BodyKey == "" || set.AttachmentKeys == nil {
@ -364,6 +377,7 @@ func (set *MessagePackageSet) AddInternal(addr string, pub *openpgp.Entity) erro
Type: MessagePackageInternal, Type: MessagePackageInternal,
BodyKeyPacket: bodyKey, BodyKeyPacket: bodyKey,
AttachmentKeyPackets: attachmentKeys, AttachmentKeyPackets: attachmentKeys,
Signature: set.signature,
} }
return nil return nil
} }