From 78e3abb0d85fb39a953686f64818af0129075a33 Mon Sep 17 00:00:00 2001 From: Aron Wussler Date: Tue, 14 May 2019 14:42:38 +0000 Subject: [PATCH] go vet and lint MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Naming * If this is not some OpenPGP standard I follow rule that `DES` should be upper case as it is abreviation and `Triple` should be camel-case as it is normal word hence `TripleDES` * rename `errors2` -> `errorsPGP` * long lines * https://github.com/golang/go/wiki/CodeReviewComments#line-length * I bit improved long lines based on my folding * reuse type in definition if possible i.e. `a string, b string, c string` -> `a,b,c string` * `if long_statetent(); err!=nil {` -> `long_statement;↵ if err!=nil {` * spaces around operators (e.g. `a + b` -> `a+b`) * removing empty lines on start and end of scope * comments * on all exported functions * start with function name * import: * order in alphabet * separate native, golang.org/x/ and our libs --- .gitlab-ci.yml | 1 + constants/cipher.go | 11 +++++ crypto/attachment.go | 42 +++++++++++------- crypto/attachment_test.go | 12 ++++-- crypto/key.go | 46 ++++++++++++-------- crypto/key_test.go | 19 ++++---- crypto/keyring.go | 81 +++++++++++++++++++++++------------ crypto/keyring_test.go | 30 +++++++------ crypto/message.go | 65 +++++++++++++++++----------- crypto/mime.go | 17 +++++--- crypto/session.go | 26 ++++++----- crypto/sign_detached.go | 62 ++++++++++++++++----------- crypto/signature_collector.go | 40 ++++++++++++----- crypto/subtle.go | 6 ++- crypto/time.go | 6 +-- glide.lock | 2 + 16 files changed, 302 insertions(+), 164 deletions(-) create mode 100644 constants/cipher.go diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 5eed47a..562e6e5 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -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: diff --git a/constants/cipher.go b/constants/cipher.go new file mode 100644 index 0000000..cc91aab --- /dev/null +++ b/constants/cipher.go @@ -0,0 +1,11 @@ +package constants + +// Definitions for cipher suites +const ( + ThreeDES = "3des" + TripleDES = "tripledes" + CAST5 = "cast5" + AES128 = "aes128" + AES192 = "aes192" + AES256 = "aes256" +) diff --git a/crypto/attachment.go b/crypto/attachment.go index 8f1da17..8354422 100644 --- a/crypto/attachment.go +++ b/crypto/attachment.go @@ -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 { diff --git a/crypto/attachment_test.go b/crypto/attachment_test.go index 599b054..70bd0bb 100644 --- a/crypto/attachment_test.go +++ b/crypto/attachment_test.go @@ -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) } diff --git a/crypto/key.go b/crypto/key.go index dca4d9a..d068404 100644 --- a/crypto/key.go +++ b/crypto/key.go @@ -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 { diff --git a/crypto/key_test.go b/crypto/key_test.go index c655bda..8e608dc 100644 --- a/crypto/key_test.go +++ b/crypto/key_test.go @@ -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 diff --git a/crypto/keyring.go b/crypto/keyring.go index a191321..ae35ac5 100644 --- a/crypto/keyring.go +++ b/crypto/keyring.go @@ -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 } diff --git a/crypto/keyring_test.go b/crypto/keyring_test.go index e3f6191..de44b1b 100644 --- a/crypto/keyring_test.go +++ b/crypto/keyring_test.go @@ -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) } } diff --git a/crypto/message.go b/crypto/message.go index 5e02dc2..0db61ee 100644 --- a/crypto/message.go +++ b/crypto/message.go @@ -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 diff --git a/crypto/mime.go b/crypto/mime.go index 19da35d..d8a4963 100644 --- a/crypto/mime.go +++ b/crypto/mime.go @@ -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) diff --git a/crypto/session.go b/crypto/session.go index 2203e41..85e4f24 100644 --- a/crypto/session.go +++ b/crypto/session.go @@ -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 diff --git a/crypto/sign_detached.go b/crypto/sign_detached.go index 5b07d34..34f4d45 100644 --- a/crypto/sign_detached.go +++ b/crypto/sign_detached.go @@ -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) diff --git a/crypto/signature_collector.go b/crypto/signature_collector.go index 03f5d3f..b81675c 100644 --- a/crypto/signature_collector.go +++ b/crypto/signature_collector.go @@ -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 } diff --git a/crypto/subtle.go b/crypto/subtle.go index 9a3f078..f1a004f 100644 --- a/crypto/subtle.go +++ b/crypto/subtle.go @@ -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) } diff --git a/crypto/time.go b/crypto/time.go index 9ea3a5a..717ef48 100644 --- a/crypto/time.go +++ b/crypto/time.go @@ -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) } diff --git a/glide.lock b/glide.lock index fdd991a..cc5bdbf 100644 --- a/glide.lock +++ b/glide.lock @@ -58,3 +58,5 @@ testImports: version: 34c6fa2dc70986bccbbffcc6130f6920a924b075 subpackages: - assert + - name: github.com/golangci/golangci-lint + version: 901cf25e20f86b7e9dc6f73eaba5afbd0cbdc257