Merge branch 'linter' into 'master'

Add Linter

See merge request ProtonMail/go-pm-crypto!13
This commit is contained in:
Daniel Huigens 2019-05-14 14:42:38 +00:00
commit e4814f20a6
16 changed files with 302 additions and 164 deletions

View file

@ -22,6 +22,7 @@ test-all:
- go test -coverprofile cover.out ./...
- mkdir reports
- go tool cover -html=cover.out -o reports/cover.html
- go run vendor/github.com/golangci/golangci-lint/cmd/golangci-lint/main.go run crypto/... --enable-all -D govet -D interfacer -D gofmt -D prealloc -D gochecknoglobals -D goimports -D gosec -D gocritic -D gochecknoinits
tags:
- coverage
artifacts:

11
constants/cipher.go Normal file
View file

@ -0,0 +1,11 @@
package constants
// Definitions for cipher suites
const (
ThreeDES = "3des"
TripleDES = "tripledes"
CAST5 = "cast5"
AES128 = "aes128"
AES192 = "aes192"
AES256 = "aes256"
)

View file

@ -9,12 +9,13 @@ import (
"sync"
armorUtils "github.com/ProtonMail/go-pm-crypto/armor"
"github.com/ProtonMail/go-pm-crypto/constants"
"github.com/ProtonMail/go-pm-crypto/models"
"golang.org/x/crypto/openpgp"
"golang.org/x/crypto/openpgp/packet"
)
// EncryptedSplit when encrypt attachment
// AttachmentProcessor to encrypt an attachment
type AttachmentProcessor struct {
w *io.WriteCloser
pipe *io.PipeWriter
@ -26,7 +27,9 @@ type AttachmentProcessor struct {
// Process allows the attachment processor to write the encrypted attachment
func (ap *AttachmentProcessor) Process(plainData []byte) {
(*ap.w).Write(plainData)
if _, err := (*ap.w).Write(plainData); err != nil {
panic(err)
}
}
// Finish attachment process
@ -43,11 +46,12 @@ func (ap *AttachmentProcessor) Finish() (*models.EncryptedSplit, error) {
return ap.split, nil
}
// Encrypts attachment. Takes input data and key data in binary form
func (pm *PmCrypto) encryptAttachment(estimatedSize int, fileName string, publicKey *KeyRing, garbageCollector int) (*AttachmentProcessor, error) {
// encryptAttachment takes input data from file
func (pm *PmCrypto) encryptAttachment(
estimatedSize int, fileName string, publicKey *KeyRing, garbageCollector int,
) (*AttachmentProcessor, error) {
attachmentProc := &AttachmentProcessor{}
// you can also add these one at
// a time if you need to
// you can also add these one at a time if you need to
attachmentProc.done.Add(1)
attachmentProc.garbageCollector = garbageCollector
@ -68,7 +72,7 @@ func (pm *PmCrypto) encryptAttachment(estimatedSize int, fileName string, public
if attachmentProc.err != nil {
attachmentProc.err = splitError
}
split.Algo = "aes256"
split.Algo = constants.AES256
attachmentProc.split = split
}()
@ -84,8 +88,11 @@ func (pm *PmCrypto) encryptAttachment(estimatedSize int, fileName string, public
return attachmentProc, nil
}
// EncryptAttachment encrypts attachment. Takes input data and key data in binary form
func (pm *PmCrypto) EncryptAttachment(plainData []byte, fileName string, publicKey *KeyRing) (*models.EncryptedSplit, error) {
// EncryptAttachment encrypts attachment. Takes input data and key data in
// binary form
func (pm *PmCrypto) EncryptAttachment(
plainData []byte, fileName string, publicKey *KeyRing,
) (*models.EncryptedSplit, error) {
ap, err := pm.encryptAttachment(len(plainData), fileName, publicKey, -1)
if err != nil {
return nil, err
@ -96,12 +103,12 @@ func (pm *PmCrypto) EncryptAttachment(plainData []byte, fileName string, publicK
return nil, err
}
return split, nil
}
// EncryptAttachmentLowMemory ...
func (pm *PmCrypto) EncryptAttachmentLowMemory(estimatedSize int, fileName string, publicKey *KeyRing) (*AttachmentProcessor, error) {
// Garbage collect every megabyte
// EncryptAttachmentLowMemory with garbage collected every megabyte
func (pm *PmCrypto) EncryptAttachmentLowMemory(
estimatedSize int, fileName string, publicKey *KeyRing,
) (*AttachmentProcessor, error) {
return pm.encryptAttachment(estimatedSize, fileName, publicKey, 1<<20)
}
@ -119,9 +126,12 @@ func SplitArmor(encrypted string) (*models.EncryptedSplit, error) {
return SeparateKeyAndData(nil, encryptedReader, len(encrypted), -1)
}
// Decrypt attachment. Takes input data and key data in binary form. privateKeys can contains more keys. passphrase is used to unlock keys
func (pm *PmCrypto) DecryptAttachment(keyPacket []byte, dataPacket []byte, kr *KeyRing, passphrase string) ([]byte, error) {
// DecryptAttachment takes input data and key data in binary form. The
// privateKeys can contains more keys. The passphrase is used to unlock keys
func (pm *PmCrypto) DecryptAttachment(
keyPacket, dataPacket []byte,
kr *KeyRing, passphrase string,
) ([]byte, error) {
privKeyEntries := kr.entities
if err := kr.Unlock([]byte(passphrase)); err != nil {

View file

@ -1,14 +1,16 @@
package crypto
import (
"github.com/stretchr/testify/assert"
"encoding/base64"
"io/ioutil"
"strings"
"testing"
"github.com/stretchr/testify/assert"
)
// const testAttachmentEncrypted = `0ksB0fHC6Duezx/0TqpK/82HSl8+qCY0c2BCuyrSFoj6Dubd93T3//32jVYa624NYvfvxX+UxFKYKJxG09gFsU1IVc87cWvUgmUmgjU=`
// const testAttachmentEncrypted =
// `0ksB0fHC6Duezx/0TqpK/82HSl8+qCY0c2BCuyrSFoj6Dubd93T3//32jVYa624NYvfvxX+UxFKYKJxG09gFsU1IVc87cWvUgmUmgjU=`
func TestAttachmentGetKey(t *testing.T) {
testKeyPackets, err := ioutil.ReadFile("testdata/attachment_keypacket")
@ -22,7 +24,11 @@ func TestAttachmentGetKey(t *testing.T) {
t.Fatal("Expected no error while decoding base64 KeyPacket, got:", err)
}
split, err := SeparateKeyAndData(testPrivateKeyRing, strings.NewReader(string(testKeyPacketsDecoded)), len(testKeyPacketsDecoded), -1)
split, err := SeparateKeyAndData(
testPrivateKeyRing,
strings.NewReader(string(testKeyPacketsDecoded)),
len(testKeyPacketsDecoded),
-1)
if err != nil {
t.Fatal("Expected no error while decrypting attachment key, got:", err)
}

View file

@ -33,15 +33,16 @@ type SymmetricKey struct {
const SymmetricallyEncryptedTag = 210
var symKeyAlgos = map[string]packet.CipherFunction{
"3des": packet.Cipher3DES,
"tripledes": packet.Cipher3DES,
"cast5": packet.CipherCAST5,
"aes128": packet.CipherAES128,
"aes192": packet.CipherAES192,
"aes256": packet.CipherAES256,
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 function corresponding to an algorithm used in this SymmetricKey
// GetCipherFunc returns function corresponding to an algorithm used in
// this SymmetricKey
func (sk *SymmetricKey) GetCipherFunc() packet.CipherFunction {
cf, ok := symKeyAlgos[sk.Algo]
if ok {
@ -108,7 +109,10 @@ func DecryptAttKey(kr *KeyRing, keyPacket string) (key *SymmetricKey, err error)
}
// SeparateKeyAndData from packets in a pgp session
func SeparateKeyAndData(kr *KeyRing, r io.Reader, estimatedLength int, garbageCollector int) (outSplit *models.EncryptedSplit, err error) {
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{}
@ -254,21 +258,20 @@ func SetKey(kr *KeyRing, symKey *SymmetricKey) (packets string, err error) {
}
if pub == nil {
err = fmt.Errorf("pm-crypto: cannot set key: no public key available")
return
return "", err
}
if err = packet.SerializeEncryptedKey(w, pub, cf, symKey.Key, nil); err != nil {
err = fmt.Errorf("pm-crypto: cannot set key: %v", err)
return
return "", err
}
if err = w.Close(); err != nil {
err = fmt.Errorf("pm-crypto: cannot set key: %v", err)
return
return "", err
}
packets = b.String()
return
return b.String(), nil
}
// IsKeyExpiredBin checks if the given key is expired. Input in binary format
@ -339,9 +342,11 @@ func (pm *PmCrypto) IsKeyExpired(publicKey string) (bool, error) {
return pm.IsKeyExpiredBin(rawPubKey)
}
func (pm *PmCrypto) generateKey(userName string, domain string, passphrase string, keyType string, bits int,
prime1 []byte, prime2 []byte, prime3 []byte, prime4 []byte) (string, error) {
func (pm *PmCrypto) generateKey(
userName, domain, passphrase, keyType string,
bits int,
prime1, prime2, prime3, prime4 []byte,
) (string, error) {
if len(userName) <= 0 {
return "", errors.New("invalid user name format")
}
@ -421,12 +426,15 @@ func (pm *PmCrypto) GenerateRSAKeyWithPrimes(
}
// GenerateKey and generate primes
func (pm *PmCrypto) GenerateKey(userName string, domain string, passphrase string, keyType string, bits int) (string, error) {
func (pm *PmCrypto) GenerateKey(userName, domain, passphrase, keyType string, bits int) (string, error) {
return pm.generateKey(userName, domain, passphrase, keyType, bits, nil, nil, nil, nil)
}
// UpdatePrivateKeyPassphrase decrypts the given private key with oldPhrase and re-encrypts with the newPassphrase
func (pm *PmCrypto) UpdatePrivateKeyPassphrase(privateKey string, oldPassphrase string, newPassphrase string) (string, error) {
// UpdatePrivateKeyPassphrase decrypts the given private key with oldPhrase and
// re-encrypts with the newPassphrase
func (pm *PmCrypto) UpdatePrivateKeyPassphrase(
privateKey string, oldPassphrase string, newPassphrase string,
) (string, error) {
privKey := strings.NewReader(privateKey)
privKeyEntries, err := openpgp.ReadArmoredKeyRing(privKey)
if err != nil {

View file

@ -1,11 +1,13 @@
package crypto
import (
"github.com/stretchr/testify/assert"
"encoding/base64"
"regexp"
"strings"
"testing"
"github.com/ProtonMail/go-pm-crypto/constants"
"github.com/stretchr/testify/assert"
)
const name = "richard.stallman"
@ -38,7 +40,7 @@ func TestGenerateKeys(t *testing.T) {
}
func TestGenerateKeyRings(t *testing.T) {
rsaPrivateKeyRing, err = ReadArmoredKeyRing(strings.NewReader(rsaKey));
rsaPrivateKeyRing, err = ReadArmoredKeyRing(strings.NewReader(rsaKey))
if err != nil {
t.Fatal("Cannot read RSA key:", err)
}
@ -48,7 +50,7 @@ func TestGenerateKeyRings(t *testing.T) {
t.Fatal("Cannot extract RSA public key:", err)
}
rsaPublicKeyRing, err = ReadArmoredKeyRing(strings.NewReader(rsaPublicKey));
rsaPublicKeyRing, err = ReadArmoredKeyRing(strings.NewReader(rsaPublicKey))
if err != nil {
t.Fatal("Cannot read RSA public key:", err)
}
@ -58,7 +60,7 @@ func TestGenerateKeyRings(t *testing.T) {
t.Fatal("Cannot decrypt RSA key:", err)
}
ecPrivateKeyRing, err = ReadArmoredKeyRing(strings.NewReader(ecKey));
ecPrivateKeyRing, err = ReadArmoredKeyRing(strings.NewReader(ecKey))
if err != nil {
t.Fatal("Cannot read EC key:", err)
}
@ -68,7 +70,7 @@ func TestGenerateKeyRings(t *testing.T) {
t.Fatal("Cannot extract EC public key:", err)
}
ecPublicKeyRing, err = ReadArmoredKeyRing(strings.NewReader(ecPublicKey));
ecPublicKeyRing, err = ReadArmoredKeyRing(strings.NewReader(ecPublicKey))
if err != nil {
t.Fatal("Cannot read EC public key:", err)
}
@ -83,7 +85,7 @@ func TestEncryptDecryptKeys(t *testing.T) {
var pass, _ = base64.StdEncoding.DecodeString("H2CAwzpdexjxXucVYMERDiAc/td8aGPrr6ZhfMnZlLI=")
var testSymmetricKey = &SymmetricKey{
Key: pass,
Algo: "aes256",
Algo: constants.AES256,
}
packet, err := SetKey(rsaPublicKeyRing, testSymmetricKey)
@ -122,8 +124,9 @@ func TestUpdatePrivateKeysPassphrase(t *testing.T) {
passphrase = newPassphrase
}
func ExampleCheckKeys() {
pmCrypto.CheckKey(readTestFile("keyring_publicKey"))
// ExampleCheckKey to track changes in test data
func ExampleCheckKey() {
_, _ = pmCrypto.CheckKey(readTestFile("keyring_publicKey"))
// Output:
// SubKey:37e4bcf09b36e34012d10c0247dc67b5cb8267f6
// PrimaryKey:6e8ba229b0cccaf6962f97953eb6259edf21df24

View file

@ -115,22 +115,28 @@ func (kr *KeyRing) GetEntities() openpgp.EntityList {
}
// GetSigningEntity returns first private signing entity from keyring
func (kr *KeyRing) GetSigningEntity(passphrase string) *openpgp.Entity {
func (kr *KeyRing) GetSigningEntity(passphrase string) (*openpgp.Entity, error) {
var signEntity *openpgp.Entity
for _, e := range kr.entities {
// Entity.PrivateKey must be a signing key
if e.PrivateKey != nil {
if e.PrivateKey.Encrypted {
e.PrivateKey.Decrypt([]byte(passphrase))
}
if !e.PrivateKey.Encrypted {
if err := e.PrivateKey.Decrypt([]byte(passphrase)); err != nil {
continue
}
signEntity = e
break
}
}
}
return signEntity
if signEntity == nil {
err := errors.New("pmcrypto: cannot sign message, unable to unlock signer key")
return signEntity, err
}
return signEntity, nil
}
// Encrypt encrypts data to this keyring's owner. If sign is not nil, it also
@ -161,11 +167,19 @@ func (kr *KeyRing) Encrypt(w io.Writer, sign *KeyRing, filename string, canonica
}
}
return EncryptCore(w, encryptEntities, signEntity, filename, canonicalizeText, func() time.Time { return GetPmCrypto().GetTime() })
return EncryptCore(
w,
encryptEntities,
signEntity,
filename,
canonicalizeText,
func() time.Time { return GetPmCrypto().GetTime() })
}
// EncryptCore is common encryption method for desktop and mobile clients
func EncryptCore(w io.Writer, encryptEntities []*openpgp.Entity, signEntity *openpgp.Entity, filename string, canonicalizeText bool, timeGenerator func() time.Time) (io.WriteCloser, error) {
func EncryptCore(w io.Writer, encryptEntities []*openpgp.Entity, signEntity *openpgp.Entity, filename string,
canonicalizeText bool, timeGenerator func() time.Time) (io.WriteCloser, error) {
config := &packet.Config{DefaultCipher: packet.CipherAES256, Time: timeGenerator}
hints := &openpgp.FileHints{
@ -235,7 +249,9 @@ func (kr *KeyRing) EncryptString(s string, sign *KeyRing) (encrypted string, err
}
// EncryptSymmetric data using generated symmetric key encrypted with this KeyRing
func (kr *KeyRing) EncryptSymmetric(textToEncrypt string, canonicalizeText bool) (outSplit *models.EncryptedSplit, err error) {
func (kr *KeyRing) EncryptSymmetric(textToEncrypt string, canonicalizeText bool) (outSplit *models.EncryptedSplit,
err error) {
var encryptedWriter io.WriteCloser
buffer := &bytes.Buffer{}
@ -277,7 +293,9 @@ func (kr *KeyRing) DecryptString(encrypted string) (SignedString, error) {
// If error is errors.ErrSignatureExpired (from golang.org/x/crypto/openpgp/errors),
// contents are still provided if library clients wish to process this message further
func (kr *KeyRing) DecryptStringIfNeeded(data string) (decrypted string, err error) {
if re := regexp.MustCompile("^-----BEGIN " + constants.PGPMessageHeader + "-----(?s:.+)-----END " + constants.PGPMessageHeader + "-----"); re.MatchString(data) {
if re := regexp.MustCompile("^-----BEGIN " + constants.PGPMessageHeader + "-----(?s:.+)-----END " +
constants.PGPMessageHeader + "-----"); re.MatchString(data) {
var signed SignedString
signed, err = kr.DecryptString(data)
decrypted = signed.String
@ -328,11 +346,7 @@ func (kr *KeyRing) DetachedSign(w io.Writer, toSign io.Reader, canonicalizeText
err = openpgp.DetachSign(w, signEntity, toSign, config)
}
}
if err != nil {
return
}
return
return err
}
// VerifyString may return errors.ErrSignatureExpired (defined in
@ -377,7 +391,9 @@ func (kr *KeyRing) Unlock(passphrase []byte) error {
// Entity.Subkeys can be used for encryption
for _, subKey := range e.Subkeys {
if subKey.PrivateKey != nil && (!subKey.Sig.FlagsValid || subKey.Sig.FlagEncryptStorage || subKey.Sig.FlagEncryptCommunications) {
if subKey.PrivateKey != nil && (!subKey.Sig.FlagsValid || subKey.Sig.FlagEncryptStorage ||
subKey.Sig.FlagEncryptCommunications) {
keys = append(keys, subKey.PrivateKey)
}
}
@ -516,10 +532,8 @@ func (kr *KeyRing) CheckPassphrase(passphrase string) bool {
n++
}
}
if n == 0 {
return false
}
return true
return n != 0
}
// readFrom reads unarmored and armored keys from r and adds them to the keyring.
@ -536,18 +550,28 @@ func (kr *KeyRing) readFrom(r io.Reader, armored bool) error {
switch entity.PrivateKey.PrivateKey.(type) {
// TODO: type mismatch after crypto lib update, fix this:
case *rsa.PrivateKey:
entity.PrimaryKey = packet.NewRSAPublicKey(time.Now(), entity.PrivateKey.PrivateKey.(*rsa.PrivateKey).Public().(*xrsa.PublicKey))
entity.PrimaryKey = packet.NewRSAPublicKey(
time.Now(),
entity.PrivateKey.PrivateKey.(*rsa.PrivateKey).Public().(*xrsa.PublicKey))
case *ecdsa.PrivateKey:
entity.PrimaryKey = packet.NewECDSAPublicKey(time.Now(), entity.PrivateKey.PrivateKey.(*ecdsa.PrivateKey).Public().(*ecdsa.PublicKey))
entity.PrimaryKey = packet.NewECDSAPublicKey(
time.Now(),
entity.PrivateKey.PrivateKey.(*ecdsa.PrivateKey).Public().(*ecdsa.PublicKey))
}
}
for _, subkey := range entity.Subkeys {
if subkey.PrivateKey != nil {
switch subkey.PrivateKey.PrivateKey.(type) {
case *rsa.PrivateKey:
subkey.PublicKey = packet.NewRSAPublicKey(time.Now(), subkey.PrivateKey.PrivateKey.(*rsa.PrivateKey).Public().(*xrsa.PublicKey))
subkey.PublicKey = packet.NewRSAPublicKey(
time.Now(),
subkey.PrivateKey.PrivateKey.(*rsa.PrivateKey).Public().(*xrsa.PublicKey))
case *ecdsa.PrivateKey:
subkey.PublicKey = packet.NewECDSAPublicKey(time.Now(), subkey.PrivateKey.PrivateKey.(*ecdsa.PrivateKey).Public().(*ecdsa.PublicKey))
subkey.PublicKey = packet.NewECDSAPublicKey(
time.Now(),
subkey.PrivateKey.PrivateKey.(*ecdsa.PrivateKey).Public().(*ecdsa.PublicKey))
}
}
}
@ -607,10 +631,13 @@ func (kr *KeyRing) UnmarshalJSON(b []byte) (err error) {
if i == 0 {
kr.FirstKeyID = ko.ID
}
kr.readFrom(ko.PrivateKeyReader(), true)
err = kr.readFrom(ko.PrivateKeyReader(), true)
if err != nil {
return err
}
}
return
return nil
}
// Identities returns the list of identities associated with this key ring.
@ -656,7 +683,7 @@ func ReadKeyRing(r io.Reader) (kr *KeyRing, err error) {
func FilterExpiredKeys(contactKeys []*KeyRing) (filteredKeys []*KeyRing, err error) {
now := time.Now()
hasExpiredEntity := false
filteredKeys = make([]*KeyRing, 0, 0)
filteredKeys = make([]*KeyRing, 0)
for _, contactKeyRing := range contactKeys {
keyRingHasUnexpiredEntity := false
@ -688,5 +715,5 @@ func FilterExpiredKeys(contactKeys []*KeyRing) (filteredKeys []*KeyRing, err err
return filteredKeys, errors.New("all contacts keys are expired")
}
return
return filteredKeys, nil
}

View file

@ -1,26 +1,29 @@
package crypto
import (
"github.com/stretchr/testify/assert"
"encoding/base64"
"golang.org/x/crypto/openpgp/armor"
"io/ioutil"
"strings"
"testing"
"golang.org/x/crypto/openpgp/armor"
"github.com/ProtonMail/go-pm-crypto/constants"
"github.com/stretchr/testify/assert"
)
var decodedSymmetricKey, _ = base64.StdEncoding.DecodeString("ExXmnSiQ2QCey20YLH6qlLhkY3xnIBC1AwlIXwK/HvY=");
var decodedSymmetricKey, _ = base64.StdEncoding.DecodeString("ExXmnSiQ2QCey20YLH6qlLhkY3xnIBC1AwlIXwK/HvY=")
var testSymmetricKey = &SymmetricKey{
Key: decodedSymmetricKey,
Algo: "aes256",
Algo: constants.AES256,
}
// Corresponding key in testdata/keyring_privateKey
const testMailboxPassword = "apple"
// Corresponding key in testdata/keyring_privateKeyLegacy
const testMailboxPasswordLegacy = "123"
// const testMailboxPasswordLegacy = "123"
const testToken = "d79ca194a22810a5363eeddfdef7dfbc327c6229"
@ -29,22 +32,25 @@ var (
testPublicKeyRing *KeyRing
)
var testIdentity = &Identity{
Name: "UserID",
Email: "",
}
// var testIdentity = &Identity{
// Name: "UserID",
// Email: "",
// }
func init() {
var err error
if testPrivateKeyRing, err = ReadArmoredKeyRing(strings.NewReader(readTestFile("keyring_privateKey"))); err != nil {
testPrivateKeyRing, err = ReadArmoredKeyRing(strings.NewReader(readTestFile("keyring_privateKey")))
if err != nil {
panic(err)
}
if testPublicKeyRing, err = ReadArmoredKeyRing(strings.NewReader(readTestFile("keyring_publicKey"))); err != nil {
testPublicKeyRing, err = ReadArmoredKeyRing(strings.NewReader(readTestFile("keyring_publicKey")))
if err != nil {
panic(err)
}
if err := testPrivateKeyRing.Unlock([]byte(testMailboxPassword)); err != nil {
err = testPrivateKeyRing.Unlock([]byte(testMailboxPassword))
if err != nil {
panic(err)
}
}

View file

@ -2,7 +2,6 @@ package crypto
import (
"bytes"
"errors"
"fmt"
"io"
"io/ioutil"
@ -20,11 +19,13 @@ import (
"github.com/ProtonMail/go-pm-crypto/models"
)
// DecryptMessageStringKey decrypts encrypted message use private key (string )
// 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 (pm *PmCrypto) DecryptMessageStringKey(encryptedText string, privateKey string, passphrase string) (string, error) {
func (pm *PmCrypto) DecryptMessageStringKey(
encryptedText, privateKey, passphrase string,
) (string, error) {
privKeyRaw, err := armorUtils.Unarmor(privateKey)
if err != nil {
return "", err
@ -58,7 +59,11 @@ func (pm *PmCrypto) DecryptMessage(encryptedText string, privateKey *KeyRing, pa
return string(b), nil
}
func decryptCore(encryptedText string, additionalEntries openpgp.EntityList, privKey *KeyRing, passphrase string, timeFunc func() time.Time) (*openpgp.MessageDetails, error) {
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("pm-crypto: cannot decrypt passphrase: %v", err)
@ -66,14 +71,9 @@ func decryptCore(encryptedText string, additionalEntries openpgp.EntityList, pri
}
privKeyEntries := privKey.entities
for _, entity := range privKey.entities {
privKeyEntries = append(privKeyEntries, entity)
}
if additionalEntries != nil {
for _, e := range additionalEntries {
privKeyEntries = append(privKeyEntries, e)
}
privKeyEntries = append(privKeyEntries, additionalEntries...)
}
encryptedio, err := internal.Unarmor(encryptedText)
@ -92,7 +92,10 @@ func decryptCore(encryptedText string, additionalEntries openpgp.EntityList, pri
// verifierKey []byte: unarmored verifier keys
// privateKeyRing []byte: unarmored private key to decrypt. could be multiple
// passphrase: match with private key to decrypt message
func (pm *PmCrypto) DecryptMessageVerify(encryptedText string, verifierKey *KeyRing, privateKeyRing *KeyRing, passphrase string, verifyTime int64) (*models.DecryptSignedVerify, error) {
func (pm *PmCrypto) DecryptMessageVerify(
encryptedText string, verifierKey, privateKeyRing *KeyRing,
passphrase string, verifyTime int64,
) (*models.DecryptSignedVerify, error) {
out := &models.DecryptSignedVerify{}
out.Verify = failed
@ -101,7 +104,16 @@ func (pm *PmCrypto) DecryptMessageVerify(encryptedText string, verifierKey *KeyR
out.Verify = noVerifier
}
md, err := decryptCore(encryptedText, verifierEntries, privateKeyRing, passphrase, func() time.Time { return time.Unix(0, 0) }) // TODO: I doubt this time is correct
md, err := decryptCore(
encryptedText,
verifierEntries,
privateKeyRing,
passphrase,
func() time.Time { return time.Unix(0, 0) }) // TODO: I doubt this time is correct
if err != nil {
return nil, err
}
decrypted := md.UnverifiedBody
b, err := ioutil.ReadAll(decrypted)
@ -136,7 +148,8 @@ func (pm *PmCrypto) DecryptMessageVerify(encryptedText string, verifierKey *KeyR
return out, nil
}
// processSignatureExpiration handles signature time verification manually, so we can add a margin to the creationTime check.
// 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 {
@ -159,7 +172,6 @@ func processSignatureExpiration(md *openpgp.MessageDetails, verifyTime int64) {
// plainText string: clear text
// output string: armored pgp message
func (pm *PmCrypto) EncryptMessageWithPassword(plainText string, password string) (string, error) {
var outBuf bytes.Buffer
w, err := armor.Encode(&outBuf, constants.PGPMessageHeader, internal.ArmorHeaders)
if err != nil {
@ -185,14 +197,17 @@ func (pm *PmCrypto) EncryptMessageWithPassword(plainText string, password string
return outBuf.String(), nil
}
// EncryptMessage encrypts message with unarmored public key, if pass private key and passphrase will also sign the message
// 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 (pm *PmCrypto) EncryptMessage(plainText string, publicKey *KeyRing, privateKey *KeyRing, passphrase string, trim bool) (string, error) {
func (pm *PmCrypto) EncryptMessage(
plainText string, publicKey, privateKey *KeyRing,
passphrase string, trim bool,
) (string, error) {
if trim {
plainText = internal.TrimNewlines(plainText)
}
@ -205,20 +220,22 @@ func (pm *PmCrypto) EncryptMessage(plainText string, publicKey *KeyRing, private
var signEntity *openpgp.Entity
if len(passphrase) > 0 && len(privateKey.entities) > 0 {
signEntity := privateKey.GetSigningEntity(passphrase)
if signEntity == nil {
return "", errors.New("cannot sign message, signer key is not unlocked")
var err error
signEntity, err = privateKey.GetSigningEntity(passphrase)
if err != nil {
return "", err
}
}
ew, err := EncryptCore(w, publicKey.entities, signEntity, "", false, pm.getTimeGenerator())
if err != nil {
return "", err
}
_, _ = ew.Write([]byte(plainText))
_, err = ew.Write([]byte(plainText))
ew.Close()
w.Close()
return outBuf.String(), nil
return outBuf.String(), err
}
// DecryptMessageWithPassword decrypts a pgp message with a password

View file

@ -13,7 +13,9 @@ import (
"golang.org/x/crypto/openpgp/packet"
)
func (pm PmCrypto) parseMIME(mimeBody string, verifierKey *KeyRing) (*pmmime.BodyCollector, int, []string, []string, error) {
func (pm PmCrypto) parseMIME(
mimeBody string, verifierKey *KeyRing,
) (*pmmime.BodyCollector, int, []string, []string, error) {
mm, err := mail.ReadMessage(strings.NewReader(mimeBody))
if err != nil {
return nil, 0, nil, nil, err
@ -22,6 +24,9 @@ func (pm PmCrypto) parseMIME(mimeBody string, verifierKey *KeyRing) (*pmmime.Bod
h := textproto.MIMEHeader(mm.Header)
mmBodyData, err := ioutil.ReadAll(mm.Body)
if err != nil {
return nil, 0, nil, nil, err
}
printAccepter := pmmime.NewMIMEPrinter()
bodyCollector := pmmime.NewBodyCollector(printAccepter)
@ -42,10 +47,10 @@ func (pm PmCrypto) parseMIME(mimeBody string, verifierKey *KeyRing) (*pmmime.Bod
atts := attachmentsCollector.GetAttachments()
attHeaders := attachmentsCollector.GetAttHeaders()
return body, verified, atts, attHeaders, nil
return body, verified, atts, attHeaders, err
}
// MIMECallbacks defines a call back interface
// MIMECallbacks defines a call back methods to process MIME message
type MIMECallbacks interface {
OnBody(body string, mimetype string)
OnAttachment(headers string, data []byte)
@ -56,8 +61,10 @@ type MIMECallbacks interface {
}
// DecryptMIMEMessage decrypts a MIME message
func (pm *PmCrypto) DecryptMIMEMessage(encryptedText string, verifierKey *KeyRing, privateKeyRing *KeyRing,
passphrase string, callbacks MIMECallbacks, verifyTime int64) {
func (pm *PmCrypto) DecryptMIMEMessage(
encryptedText string, verifierKey, privateKeyRing *KeyRing,
passphrase string, callbacks MIMECallbacks, verifyTime int64,
) {
decsignverify, err := pm.DecryptMessageVerify(encryptedText, verifierKey, privateKeyRing, passphrase, verifyTime)
if err != nil {
callbacks.OnError(err)

View file

@ -7,6 +7,8 @@ import (
"io"
"github.com/ProtonMail/go-pm-crypto/armor"
"github.com/ProtonMail/go-pm-crypto/constants"
"golang.org/x/crypto/openpgp"
"golang.org/x/crypto/openpgp/packet"
)
@ -33,8 +35,10 @@ func (pm *PmCrypto) RandomTokenWith(size int) ([]byte, error) {
}
// GetSessionFromKeyPacket gets session key no encoding in and out
func (pm *PmCrypto) GetSessionFromKeyPacket(keyPackage []byte, privateKey *KeyRing, passphrase string) (*SymmetricKey, error) {
func (pm *PmCrypto) GetSessionFromKeyPacket(
keyPackage []byte, privateKey *KeyRing, passphrase string,
) (*SymmetricKey,
error) {
keyReader := bytes.NewReader(keyPackage)
packets := packet.NewReader(keyReader)
@ -68,7 +72,7 @@ func (pm *PmCrypto) GetSessionFromKeyPacket(keyPackage []byte, privateKey *KeyRi
return getSessionSplit(ek)
}
// KeyPacketWithPublicKey
// KeyPacketWithPublicKey returns binary packet from symmetric key and armored public key
func (pm *PmCrypto) KeyPacketWithPublicKey(sessionSplit *SymmetricKey, publicKey string) ([]byte, error) {
pubkeyRaw, err := armor.Unarmor(publicKey)
if err != nil {
@ -77,10 +81,13 @@ func (pm *PmCrypto) KeyPacketWithPublicKey(sessionSplit *SymmetricKey, publicKey
return pm.KeyPacketWithPublicKeyBin(sessionSplit, pubkeyRaw)
}
// KeyPacketWithPublicKeyBin
// KeyPacketWithPublicKeyBin returns binary packet from symmetric key and binary public key
func (pm *PmCrypto) KeyPacketWithPublicKeyBin(sessionSplit *SymmetricKey, publicKey []byte) ([]byte, error) {
publicKeyReader := bytes.NewReader(publicKey)
pubKeyEntries, err := openpgp.ReadKeyRing(publicKeyReader)
if err != nil {
return nil, err
}
outbuf := &bytes.Buffer{}
@ -117,14 +124,13 @@ func (pm *PmCrypto) KeyPacketWithPublicKeyBin(sessionSplit *SymmetricKey, public
if err = packet.SerializeEncryptedKey(outbuf, pub, cf, sessionSplit.Key, nil); err != nil {
err = fmt.Errorf("pm-crypto: cannot set key: %v", err)
return nil, errors.New("cannot set key: key ring is empty")
return nil, err
}
return outbuf.Bytes(), nil
}
// GetSessionFromSymmetricPacket
// GetSessionFromSymmetricPacket extracts symmentric key from binary packet
func (pm *PmCrypto) GetSessionFromSymmetricPacket(keyPackage []byte, password string) (*SymmetricKey, error) {
keyReader := bytes.NewReader(keyPackage)
packets := packet.NewReader(keyReader)
@ -161,7 +167,7 @@ func (pm *PmCrypto) GetSessionFromSymmetricPacket(keyPackage []byte, password st
return nil, errors.New("password incorrect")
}
// SymmetricKeyPacketWithPassword
// SymmetricKeyPacketWithPassword return binary packet from symmetric key and password
func (pm *PmCrypto) SymmetricKeyPacketWithPassword(sessionSplit *SymmetricKey, password string) ([]byte, error) {
outbuf := &bytes.Buffer{}
@ -188,7 +194,7 @@ func getSessionSplit(ek *packet.EncryptedKey) (*SymmetricKey, error) {
if ek == nil {
return nil, errors.New("can't decrypt key packet")
}
algo := "aes256"
algo := constants.AES256
for k, v := range symKeyAlgos {
if v == ek.CipherFunc {
algo = k
@ -207,7 +213,7 @@ func getSessionSplit(ek *packet.EncryptedKey) (*SymmetricKey, error) {
}
func getAlgo(cipher packet.CipherFunction) string {
algo := "aes256"
algo := constants.AES256
for k, v := range symKeyAlgos {
if v == cipher {
algo = k

View file

@ -3,27 +3,29 @@ package crypto
import (
"bytes"
"errors"
"io"
"strings"
"time"
"github.com/ProtonMail/go-pm-crypto/internal"
"golang.org/x/crypto/openpgp"
errors2 "golang.org/x/crypto/openpgp/errors"
errorsPGP "golang.org/x/crypto/openpgp/errors"
"golang.org/x/crypto/openpgp/packet"
"io"
)
// SignTextDetached signs detached text type
func (pm *PmCrypto) SignTextDetached(plainText string, privateKey *KeyRing, passphrase string, trim bool) (string, error) {
func (pm *PmCrypto) SignTextDetached(
plainText string, privateKey *KeyRing, passphrase string, trim bool,
) (string, error) {
//sign with 0x01 text
if trim {
plainText = internal.TrimNewlines(plainText)
}
signEntity := privateKey.GetSigningEntity(passphrase)
if signEntity == nil {
return "", errors.New("cannot sign message, signer key is not unlocked")
signEntity, err := privateKey.GetSigningEntity(passphrase)
if err != nil {
return "", err
}
config := &packet.Config{DefaultCipher: packet.CipherAES256, Time: pm.getTimeGenerator()}
@ -42,10 +44,9 @@ func (pm *PmCrypto) SignTextDetached(plainText string, privateKey *KeyRing, pass
// SignBinDetached Signs detached bin data using string key
func (pm *PmCrypto) SignBinDetached(plainData []byte, privateKey *KeyRing, passphrase string) (string, error) {
//sign with 0x00
signEntity := privateKey.GetSigningEntity(passphrase)
if signEntity == nil {
return "", errors.New("cannot sign message, singer key is not unlocked")
signEntity, err := privateKey.GetSigningEntity(passphrase)
if err != nil {
return "", err
}
config := &packet.Config{DefaultCipher: packet.CipherAES256, Time: pm.getTimeGenerator()}
@ -61,15 +62,21 @@ func (pm *PmCrypto) SignBinDetached(plainData []byte, privateKey *KeyRing, passp
return outBuf.String(), nil
}
// VerifyTextDetachedSig verifies detached text - check if signature is valid using a given publicKey in binary format
func (pm *PmCrypto) VerifyTextDetachedSig(signature string, plainText string, publicKey *KeyRing, verifyTime int64) (bool, error) {
// VerifyTextDetachedSig verifies detached text
// - check if signature is valid using a given publicKey in binary format
func (pm *PmCrypto) VerifyTextDetachedSig(
signature string, plainText string, publicKey *KeyRing, verifyTime int64,
) (bool, error) {
plainText = internal.TrimNewlines(plainText)
origText := bytes.NewReader(bytes.NewBufferString(plainText).Bytes())
return verifySignature(publicKey.entities, origText, signature, verifyTime)
}
func verifySignature(pubKeyEntries openpgp.EntityList, origText *bytes.Reader, signature string, verifyTime int64) (bool, error) {
func verifySignature(
pubKeyEntries openpgp.EntityList, origText *bytes.Reader,
signature string, verifyTime int64,
) (bool, error) {
config := &packet.Config{}
if verifyTime == 0 {
config.Time = func() time.Time {
@ -84,24 +91,26 @@ func verifySignature(pubKeyEntries openpgp.EntityList, origText *bytes.Reader, s
signer, err := openpgp.CheckArmoredDetachedSignature(pubKeyEntries, origText, signatureReader, config)
if err == errors2.ErrSignatureExpired && signer != nil {
if verifyTime > 0 {
if err == errorsPGP.ErrSignatureExpired && signer != nil {
if verifyTime > 0 { // if verifyTime = 0: time check disabled, everything is okay
// Maybe the creation time offset pushed it over the edge
// Retry with the actual verification time
config.Time = func() time.Time {
return time.Unix(verifyTime, 0)
}
signatureReader.Seek(0, io.SeekStart)
_, err = signatureReader.Seek(0, io.SeekStart)
if err != nil {
return false, err
}
signer, err = openpgp.CheckArmoredDetachedSignature(pubKeyEntries, origText, signatureReader, config)
} else {
// verifyTime = 0: time check disabled, everything is okay
err = nil
if err != nil {
return false, err
}
}
}
if err != nil {
return false, err
}
if signer == nil {
return false, errors.New("signer is empty")
}
@ -112,8 +121,11 @@ func verifySignature(pubKeyEntries openpgp.EntityList, origText *bytes.Reader, s
return true, nil
}
// VerifyBinDetachedSig verifies detached text in binary format - check if signature is valid using a given publicKey in binary format
func (pm *PmCrypto) VerifyBinDetachedSig(signature string, plainData []byte, publicKey *KeyRing, verifyTime int64) (bool, error) {
// VerifyBinDetachedSig verifies detached text in binary format
// - check if signature is valid using a given publicKey in binary format
func (pm *PmCrypto) VerifyBinDetachedSig(
signature string, plainData []byte, publicKey *KeyRing, verifyTime int64,
) (bool, error) {
origText := bytes.NewReader(plainData)
return verifySignature(publicKey.entities, origText, signature, verifyTime)

View file

@ -22,7 +22,9 @@ type SignatureCollector struct {
verified int
}
func newSignatureCollector(targetAcceptor pmmime.VisitAcceptor, keyring openpgp.KeyRing, config *packet.Config) *SignatureCollector {
func newSignatureCollector(
targetAcceptor pmmime.VisitAcceptor, keyring openpgp.KeyRing, config *packet.Config,
) *SignatureCollector {
return &SignatureCollector{
target: targetAcceptor,
config: config,
@ -30,11 +32,14 @@ func newSignatureCollector(targetAcceptor pmmime.VisitAcceptor, keyring openpgp.
}
}
// Accept
func (sc *SignatureCollector) Accept(part io.Reader, header textproto.MIMEHeader, hasPlainSibling bool, isFirst, isLast bool) (err error) {
// Accept collects the signature
func (sc *SignatureCollector) Accept(
part io.Reader, header textproto.MIMEHeader,
hasPlainSibling, isFirst, isLast bool,
) (err error) {
parentMediaType, params, _ := mime.ParseMediaType(header.Get("Content-Type"))
if parentMediaType == "multipart/signed" {
newPart, rawBody := pmmime.GetRawMimePart(part, "--" + params["boundary"])
newPart, rawBody := pmmime.GetRawMimePart(part, "--"+params["boundary"])
var multiparts []io.Reader
var multipartHeaders []textproto.MIMEHeader
if multiparts, multipartHeaders, err = pmmime.GetMultipartParts(newPart, params); err == nil {
@ -48,7 +53,11 @@ func (sc *SignatureCollector) Accept(part io.Reader, header textproto.MIMEHeader
if len(multiparts) != 2 {
sc.verified = notSigned
// Invalid multipart/signed format just pass along
ioutil.ReadAll(rawBody)
_, err = ioutil.ReadAll(rawBody)
if err != nil {
return err
}
for i, p := range multiparts {
if err = sc.target.Accept(p, multipartHeaders[i], hasPlainChild, true, true); err != nil {
return
@ -60,11 +69,18 @@ func (sc *SignatureCollector) Accept(part io.Reader, header textproto.MIMEHeader
// actual multipart/signed format
err = sc.target.Accept(multiparts[0], multipartHeaders[0], hasPlainChild, true, true)
if err != nil {
return
return err
}
partData, _ := ioutil.ReadAll(multiparts[1])
decodedPart := pmmime.DecodeContentEncoding(bytes.NewReader(partData), multipartHeaders[1].Get("Content-Transfer-Encoding"))
partData, err := ioutil.ReadAll(multiparts[1])
if err != nil {
return err
}
decodedPart := pmmime.DecodeContentEncoding(
bytes.NewReader(partData),
multipartHeaders[1].Get("Content-Transfer-Encoding"))
buffer, err := ioutil.ReadAll(decodedPart)
if err != nil {
return err
@ -91,11 +107,15 @@ func (sc *SignatureCollector) Accept(part io.Reader, header textproto.MIMEHeader
}
return
}
sc.target.Accept(part, header, hasPlainSibling, isFirst, isLast)
err = sc.target.Accept(part, header, hasPlainSibling, isFirst, isLast)
if err != nil {
return err
}
return nil
}
// GetSignature
// GetSignature collected by Accept
func (sc SignatureCollector) GetSignature() string {
return sc.signature
}

View file

@ -7,7 +7,8 @@ import (
"golang.org/x/crypto/scrypt"
)
// EncryptWithoutIntegrity encrypts data with AES-CTR. Note: this encryption mode is not secure when stored/sent on an untrusted medium.
// EncryptWithoutIntegrity encrypts data with AES-CTR. Note: this encryption
// mode is not secure when stored/sent on an untrusted medium.
func EncryptWithoutIntegrity(key, input, iv []byte) (output []byte, err error) {
var block cipher.Block
if block, err = aes.NewCipher(key); err != nil {
@ -25,7 +26,8 @@ func DecryptWithoutIntegrity(key, input, iv []byte) ([]byte, error) {
return EncryptWithoutIntegrity(key, input, iv)
}
// DeriveKey derives a key from a password using scrypt. N should be set to the highest power of 2 you can derive within 100 milliseconds.
// DeriveKey derives a key from a password using scrypt. N should be set to the
// highest power of 2 you can derive within 100 milliseconds.
func DeriveKey(password string, salt []byte, N int) ([]byte, error) {
return scrypt.Key([]byte(password), salt, N, 8, 1, 32)
}

View file

@ -6,7 +6,7 @@ import (
var pmCrypto = PmCrypto{}
// GetPmCrypto
// GetPmCrypto return global PmCrypto
func GetPmCrypto() *PmCrypto {
return &pmCrypto
}
@ -29,8 +29,8 @@ func (pm *PmCrypto) GetTime() time.Time {
func (pm *PmCrypto) getNow() time.Time {
if pm.latestServerTime > 0 && !pm.latestClientTime.IsZero() {
// Sub is monotonic, it uses a monotonic clock in this case instead of the wall clock
extrapolate := int64(pm.latestClientTime.Sub(time.Now()).Seconds())
// Until is monotonic, it uses a monotonic clock in this case instead of the wall clock
extrapolate := int64(time.Until(pm.latestClientTime).Seconds())
return time.Unix(pm.latestServerTime+extrapolate, 0)
}

2
glide.lock generated
View file

@ -58,3 +58,5 @@ testImports:
version: 34c6fa2dc70986bccbbffcc6130f6920a924b075
subpackages:
- assert
- name: github.com/golangci/golangci-lint
version: 901cf25e20f86b7e9dc6f73eaba5afbd0cbdc257