208 lines
5.6 KiB
Go
208 lines
5.6 KiB
Go
package protonmail
|
|
|
|
import (
|
|
"io"
|
|
"crypto"
|
|
"hash"
|
|
"time"
|
|
|
|
"golang.org/x/crypto/openpgp"
|
|
"golang.org/x/crypto/openpgp/packet"
|
|
)
|
|
|
|
// primaryIdentity returns the Identity marked as primary or the first identity
|
|
// if none are so marked.
|
|
func primaryIdentity(e *openpgp.Entity) *openpgp.Identity {
|
|
var firstIdentity *openpgp.Identity
|
|
for _, ident := range e.Identities {
|
|
if firstIdentity == nil {
|
|
firstIdentity = ident
|
|
}
|
|
if ident.SelfSignature.IsPrimaryId != nil && *ident.SelfSignature.IsPrimaryId {
|
|
return ident
|
|
}
|
|
}
|
|
return firstIdentity
|
|
}
|
|
|
|
// encryptionKey returns the best candidate Key for encrypting a message to the
|
|
// given Entity.
|
|
func encryptionKey(e *openpgp.Entity, now time.Time) (openpgp.Key, bool) {
|
|
candidateSubkey := -1
|
|
|
|
// Iterate the keys to find the newest key
|
|
var maxTime time.Time
|
|
for i, subkey := range e.Subkeys {
|
|
if subkey.Sig.FlagsValid &&
|
|
subkey.Sig.FlagEncryptCommunications &&
|
|
subkey.PublicKey.PubKeyAlgo.CanEncrypt() &&
|
|
!subkey.Sig.KeyExpired(now) &&
|
|
(maxTime.IsZero() || subkey.Sig.CreationTime.After(maxTime)) {
|
|
candidateSubkey = i
|
|
maxTime = subkey.Sig.CreationTime
|
|
}
|
|
}
|
|
|
|
if candidateSubkey != -1 {
|
|
subkey := e.Subkeys[candidateSubkey]
|
|
return openpgp.Key{e, subkey.PublicKey, subkey.PrivateKey, subkey.Sig}, true
|
|
}
|
|
|
|
// If we don't have any candidate subkeys for encryption and
|
|
// the primary key doesn't have any usage metadata then we
|
|
// assume that the primary key is ok. Or, if the primary key is
|
|
// marked as ok to encrypt to, then we can obviously use it.
|
|
i := primaryIdentity(e)
|
|
if !i.SelfSignature.FlagsValid || i.SelfSignature.FlagEncryptCommunications &&
|
|
e.PrimaryKey.PubKeyAlgo.CanEncrypt() &&
|
|
!i.SelfSignature.KeyExpired(now) {
|
|
return openpgp.Key{e, e.PrimaryKey, e.PrivateKey, i.SelfSignature}, true
|
|
}
|
|
|
|
// This Entity appears to be signing only.
|
|
return openpgp.Key{}, false
|
|
}
|
|
|
|
// signingKey return the best candidate Key for signing a message with this
|
|
// Entity.
|
|
func signingKey(e *openpgp.Entity, now time.Time) (openpgp.Key, bool) {
|
|
candidateSubkey := -1
|
|
|
|
for i, subkey := range e.Subkeys {
|
|
if subkey.Sig.FlagsValid &&
|
|
subkey.Sig.FlagSign &&
|
|
subkey.PublicKey.PubKeyAlgo.CanSign() &&
|
|
!subkey.Sig.KeyExpired(now) {
|
|
candidateSubkey = i
|
|
break
|
|
}
|
|
}
|
|
|
|
if candidateSubkey != -1 {
|
|
subkey := e.Subkeys[candidateSubkey]
|
|
return openpgp.Key{e, subkey.PublicKey, subkey.PrivateKey, subkey.Sig}, true
|
|
}
|
|
|
|
// If we have no candidate subkey then we assume that it's ok to sign
|
|
// with the primary key.
|
|
i := primaryIdentity(e)
|
|
if !i.SelfSignature.FlagsValid || i.SelfSignature.FlagSign &&
|
|
!i.SelfSignature.KeyExpired(now) {
|
|
return openpgp.Key{e, e.PrimaryKey, e.PrivateKey, i.SelfSignature}, true
|
|
}
|
|
|
|
return openpgp.Key{}, false
|
|
}
|
|
|
|
func generateUnencryptedKey(cipher packet.CipherFunction, config *packet.Config) (*packet.EncryptedKey, error) {
|
|
symKey := make([]byte, cipher.KeySize())
|
|
if _, err := io.ReadFull(config.Random(), symKey); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &packet.EncryptedKey{
|
|
CipherFunc: cipher,
|
|
Key: symKey,
|
|
}, nil
|
|
}
|
|
|
|
func symetricallyEncrypt(ciphertext io.Writer, symKey *packet.EncryptedKey, signer *packet.PrivateKey, hints *openpgp.FileHints, 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
|
|
}
|
|
}
|
|
|
|
if hints == nil {
|
|
hints = &openpgp.FileHints{}
|
|
}
|
|
|
|
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}
|
|
}
|
|
var epochSeconds uint32
|
|
if !hints.ModTime.IsZero() {
|
|
epochSeconds = uint32(hints.ModTime.Unix())
|
|
}
|
|
literalData, err := packet.SerializeLiteral(w, hints.IsBinary, hints.FileName, epochSeconds)
|
|
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
|
|
}
|