Add new attachment processor that uses pre-allocated buffer (#120)
This commit is contained in:
parent
b5823b9dee
commit
973856d299
6 changed files with 424 additions and 2 deletions
|
|
@ -6,6 +6,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
|
||||
## [Unreleased]
|
||||
|
||||
### Added
|
||||
- `ManualAttachmentProcessor`: a new kind of attachment processor where the caller has to first allocate a buffer large enough for the whole data packet to be written to. It can be created with
|
||||
```processor, err := keyRing.NewManualAttachmentProcessor(estimatedSize, filename, dataBuffer)```.
|
||||
|
||||
### Changed
|
||||
- Updated the x/mobile fork and the build script to work with golang 1.16
|
||||
|
||||
|
|
|
|||
188
crypto/attachment_manual.go
Normal file
188
crypto/attachment_manual.go
Normal file
|
|
@ -0,0 +1,188 @@
|
|||
package crypto
|
||||
|
||||
import (
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"runtime"
|
||||
"runtime/debug"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/ProtonMail/go-crypto/openpgp"
|
||||
"github.com/ProtonMail/go-crypto/openpgp/packet"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// ManualAttachmentProcessor keeps track of the progress of encrypting an attachment
|
||||
// (optimized for encrypting large files).
|
||||
// With this processor, the caller has to first allocate
|
||||
// a buffer large enough to hold the whole data packet.
|
||||
type ManualAttachmentProcessor struct {
|
||||
keyPacket []byte
|
||||
dataLength int
|
||||
plaintextWriter io.WriteCloser
|
||||
ciphertextWriter *io.PipeWriter
|
||||
err error
|
||||
done sync.WaitGroup
|
||||
}
|
||||
|
||||
// GetKeyPacket returns the key packet for the attachment.
|
||||
// This should be called only after Finish() has been called.
|
||||
func (ap *ManualAttachmentProcessor) GetKeyPacket() []byte {
|
||||
return ap.keyPacket
|
||||
}
|
||||
|
||||
// GetDataLength returns the number of bytes in the DataPacket.
|
||||
// This should be called only after Finish() has been called.
|
||||
func (ap *ManualAttachmentProcessor) GetDataLength() int {
|
||||
return ap.dataLength
|
||||
}
|
||||
|
||||
// Process writes attachment data to be encrypted.
|
||||
func (ap *ManualAttachmentProcessor) Process(plainData []byte) error {
|
||||
defer runtime.GC()
|
||||
_, err := ap.plaintextWriter.Write(plainData)
|
||||
return errors.Wrap(err, "gopenpgp: couldn't write attachment data")
|
||||
}
|
||||
|
||||
// Finish tells the processor to finalize encryption.
|
||||
func (ap *ManualAttachmentProcessor) Finish() error {
|
||||
defer runtime.GC()
|
||||
if ap.err != nil {
|
||||
return ap.err
|
||||
}
|
||||
if err := ap.plaintextWriter.Close(); err != nil {
|
||||
return errors.Wrap(err, "gopengpp: unable to close the plaintext writer")
|
||||
}
|
||||
if err := ap.ciphertextWriter.Close(); err != nil {
|
||||
return errors.Wrap(err, "gopengpp: unable to close the dataPacket writer")
|
||||
}
|
||||
ap.done.Wait()
|
||||
if ap.err != nil {
|
||||
return ap.err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// NewManualAttachmentProcessor creates an AttachmentProcessor which can be used
|
||||
// to encrypt a file. It takes an estimatedSize and filename as hints about the
|
||||
// file and a buffer to hold the DataPacket.
|
||||
// It is optimized for low-memory environments and collects garbage every megabyte.
|
||||
// The buffer for the data packet must be manually allocated by the caller.
|
||||
// Make sure that the dataBuffer is large enough to hold the whole data packet
|
||||
// otherwise Finish() will return an error.
|
||||
func (keyRing *KeyRing) NewManualAttachmentProcessor(
|
||||
estimatedSize int, filename string, dataBuffer []byte,
|
||||
) (*ManualAttachmentProcessor, error) {
|
||||
if len(dataBuffer) == 0 {
|
||||
return nil, errors.New("gopenpgp: can't give a nil or empty buffer to process the attachement")
|
||||
}
|
||||
|
||||
// forces the gc to be called often
|
||||
debug.SetGCPercent(10)
|
||||
|
||||
attachmentProc := &ManualAttachmentProcessor{}
|
||||
|
||||
// hints for the encrypted file
|
||||
isBinary := true
|
||||
modTime := GetUnixTime()
|
||||
hints := &openpgp.FileHints{
|
||||
FileName: filename,
|
||||
IsBinary: isBinary,
|
||||
ModTime: time.Unix(modTime, 0),
|
||||
}
|
||||
|
||||
// encryption config
|
||||
config := &packet.Config{
|
||||
DefaultCipher: packet.CipherAES256,
|
||||
Time: getTimeGenerator(),
|
||||
}
|
||||
|
||||
// goroutine that reads the key packet
|
||||
// to be later returned to the caller via GetKeyPacket()
|
||||
keyReader, keyWriter := io.Pipe()
|
||||
attachmentProc.done.Add(1)
|
||||
go func() {
|
||||
defer attachmentProc.done.Done()
|
||||
keyPacket, err := ioutil.ReadAll(keyReader)
|
||||
if err != nil {
|
||||
attachmentProc.err = err
|
||||
} else {
|
||||
attachmentProc.keyPacket = clone(keyPacket)
|
||||
}
|
||||
}()
|
||||
|
||||
// goroutine that reads the data packet into the provided buffer
|
||||
dataReader, dataWriter := io.Pipe()
|
||||
attachmentProc.done.Add(1)
|
||||
go func() {
|
||||
defer attachmentProc.done.Done()
|
||||
totalRead, err := readAll(dataBuffer, dataReader)
|
||||
if err != nil {
|
||||
attachmentProc.err = err
|
||||
} else {
|
||||
attachmentProc.dataLength = totalRead
|
||||
}
|
||||
}()
|
||||
|
||||
// We generate the encrypting writer
|
||||
var ew io.WriteCloser
|
||||
var encryptErr error
|
||||
ew, encryptErr = openpgp.EncryptSplit(keyWriter, dataWriter, keyRing.entities, nil, hints, config)
|
||||
if encryptErr != nil {
|
||||
return nil, errors.Wrap(encryptErr, "gopengpp: unable to encrypt attachment")
|
||||
}
|
||||
|
||||
attachmentProc.plaintextWriter = ew
|
||||
attachmentProc.ciphertextWriter = dataWriter
|
||||
|
||||
// The key packet should have been already written, so we can close
|
||||
if err := keyWriter.Close(); err != nil {
|
||||
return nil, errors.Wrap(err, "gopenpgp: couldn't close the keyPacket writer")
|
||||
}
|
||||
|
||||
// Check if the goroutines encountered errors
|
||||
if attachmentProc.err != nil {
|
||||
return nil, attachmentProc.err
|
||||
}
|
||||
return attachmentProc, nil
|
||||
}
|
||||
|
||||
// readAll works a bit like io.ReadAll
|
||||
// but we can choose the buffer to write to
|
||||
// and we don't grow the slice in case of overflow.
|
||||
func readAll(buffer []byte, reader io.Reader) (int, error) {
|
||||
bufferLen := len(buffer)
|
||||
totalRead := 0
|
||||
offset := 0
|
||||
overflow := false
|
||||
reset := false
|
||||
for {
|
||||
// We read into the buffer
|
||||
n, err := reader.Read(buffer[offset:])
|
||||
totalRead += n
|
||||
offset += n
|
||||
if !overflow && reset && n != 0 {
|
||||
// In case we've started overwriting the beginning of the buffer
|
||||
// We will return an error at Finish()
|
||||
overflow = true
|
||||
}
|
||||
if err != nil {
|
||||
if errors.Is(err, io.EOF) {
|
||||
break
|
||||
}
|
||||
return 0, errors.Wrap(err, "gopenpgp: couldn't read data from the encrypted reader")
|
||||
}
|
||||
if offset == bufferLen {
|
||||
// Here we've reached the end of the buffer
|
||||
// But we need to keep reading to not block the Process()
|
||||
// So we reset the buffer
|
||||
reset = true
|
||||
offset = 0
|
||||
}
|
||||
}
|
||||
if overflow {
|
||||
return 0, errors.New("gopenpgp: read more bytes that was allocated in the buffer")
|
||||
}
|
||||
return totalRead, nil
|
||||
}
|
||||
176
crypto/attachment_manual_test.go
Normal file
176
crypto/attachment_manual_test.go
Normal file
|
|
@ -0,0 +1,176 @@
|
|||
package crypto
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"testing"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func TestManualAttachmentProcessor(t *testing.T) {
|
||||
pgp.latestServerTime = 1615394034
|
||||
defer func() { pgp.latestServerTime = testTime }()
|
||||
passphrase := []byte("wUMuF/lkDPYWH/0ZqqY8kJKw7YJg6kS")
|
||||
pk, err := NewKeyFromArmored(readTestFile("att_key", false))
|
||||
if err != nil {
|
||||
t.Error("Expected no error while unarmoring private key, got:" + err.Error())
|
||||
}
|
||||
|
||||
uk, err := pk.Unlock(passphrase)
|
||||
if err != nil {
|
||||
t.Error("Expected no error while unlocking private key, got:" + err.Error())
|
||||
}
|
||||
|
||||
defer uk.ClearPrivateParams()
|
||||
|
||||
ukr, err := NewKeyRing(uk)
|
||||
if err != nil {
|
||||
t.Error("Expected no error while building private keyring, got:" + err.Error())
|
||||
}
|
||||
|
||||
inputPlaintext := readTestFile("att_cleartext", false)
|
||||
plaintextBytes := []byte(inputPlaintext)
|
||||
plaintextReader := bytes.NewReader(plaintextBytes)
|
||||
bufferLen := 2 * len(plaintextBytes)
|
||||
dataPacket := make([]byte, bufferLen)
|
||||
ap, err := ukr.NewManualAttachmentProcessor(
|
||||
len(plaintextBytes),
|
||||
"test.txt",
|
||||
dataPacket,
|
||||
)
|
||||
if err != nil {
|
||||
t.Error("Expected no error while building the attachment processor, got:" + err.Error())
|
||||
}
|
||||
chunkSize := 1 << 10
|
||||
inputBytes := make([]byte, chunkSize)
|
||||
var readAllPlaintext = false
|
||||
for !readAllPlaintext {
|
||||
nBytesRead, err := plaintextReader.Read(inputBytes)
|
||||
if errors.Is(err, io.EOF) {
|
||||
readAllPlaintext = true
|
||||
} else if err != nil {
|
||||
t.Error("Expected no error while reading plain data, got:" + err.Error())
|
||||
}
|
||||
err = ap.Process(inputBytes[:nBytesRead])
|
||||
if err != nil {
|
||||
t.Error("Expected no error while writing plain data, got:" + err.Error())
|
||||
}
|
||||
}
|
||||
err = ap.Finish()
|
||||
if err != nil {
|
||||
t.Error("Expected no error while calling finish, got:" + err.Error())
|
||||
}
|
||||
dataLength := ap.GetDataLength()
|
||||
keyPacket := ap.GetKeyPacket()
|
||||
if keyPacket == nil {
|
||||
t.Error("The key packet was nil")
|
||||
}
|
||||
if len(keyPacket) == 0 {
|
||||
t.Error("The key packet was empty")
|
||||
}
|
||||
t.Logf("buffer size : %d total written : %d", bufferLen, dataLength)
|
||||
if dataLength > bufferLen {
|
||||
t.Errorf("Wrote more than was allocated, buffer size : %d total written : %d", bufferLen, dataLength)
|
||||
}
|
||||
|
||||
pgpMsg := NewPGPSplitMessage(keyPacket, dataPacket[:dataLength]).GetPGPMessage()
|
||||
plainMsg, err := ukr.Decrypt(pgpMsg, nil, 0)
|
||||
if err != nil {
|
||||
t.Error("Expected no error while decrypting, got:" + err.Error())
|
||||
}
|
||||
outputPlaintext := string(plainMsg.Data)
|
||||
if outputPlaintext != inputPlaintext {
|
||||
t.Errorf("Expectedplaintext to be %s got %s", inputPlaintext, outputPlaintext)
|
||||
}
|
||||
}
|
||||
|
||||
func TestManualAttachmentProcessorNotEnoughBuffer(t *testing.T) {
|
||||
pgp.latestServerTime = 1615394034
|
||||
defer func() { pgp.latestServerTime = testTime }()
|
||||
passphrase := []byte("wUMuF/lkDPYWH/0ZqqY8kJKw7YJg6kS")
|
||||
pk, err := NewKeyFromArmored(readTestFile("att_key", false))
|
||||
if err != nil {
|
||||
t.Error("Expected no error while unarmoring private key, got:" + err.Error())
|
||||
}
|
||||
|
||||
uk, err := pk.Unlock(passphrase)
|
||||
if err != nil {
|
||||
t.Error("Expected no error while unlocking private key, got:" + err.Error())
|
||||
}
|
||||
|
||||
defer uk.ClearPrivateParams()
|
||||
|
||||
ukr, err := NewKeyRing(uk)
|
||||
if err != nil {
|
||||
t.Error("Expected no error while building private keyring, got:" + err.Error())
|
||||
}
|
||||
|
||||
inputPlaintext := readTestFile("att_cleartext", false)
|
||||
plaintextBytes := []byte(inputPlaintext)
|
||||
plaintextReader := bytes.NewReader(plaintextBytes)
|
||||
bufferLen := len(plaintextBytes) / 2
|
||||
dataPacket := make([]byte, bufferLen)
|
||||
ap, err := ukr.NewManualAttachmentProcessor(
|
||||
len(plaintextBytes),
|
||||
"test.txt",
|
||||
dataPacket,
|
||||
)
|
||||
if err != nil {
|
||||
t.Error("Expected no error while building the attachment processor, got:" + err.Error())
|
||||
}
|
||||
chunkSize := 1 << 10
|
||||
inputBytes := make([]byte, chunkSize)
|
||||
var readAllPlaintext = false
|
||||
for !readAllPlaintext {
|
||||
nBytesRead, err := plaintextReader.Read(inputBytes)
|
||||
if errors.Is(err, io.EOF) {
|
||||
readAllPlaintext = true
|
||||
} else if err != nil {
|
||||
t.Error("Expected no error while reading plain data, got:" + err.Error())
|
||||
}
|
||||
err = ap.Process(inputBytes[:nBytesRead])
|
||||
if err != nil {
|
||||
t.Error("Expected no error while writing plain data, got:" + err.Error())
|
||||
}
|
||||
}
|
||||
err = ap.Finish()
|
||||
if err == nil {
|
||||
t.Error("Expected an error while calling finish, got nil")
|
||||
}
|
||||
}
|
||||
|
||||
func TestManualAttachmentProcessorEmptyBuffer(t *testing.T) {
|
||||
pgp.latestServerTime = 1615394034
|
||||
defer func() { pgp.latestServerTime = testTime }()
|
||||
passphrase := []byte("wUMuF/lkDPYWH/0ZqqY8kJKw7YJg6kS")
|
||||
pk, err := NewKeyFromArmored(readTestFile("att_key", false))
|
||||
if err != nil {
|
||||
t.Error("Expected no error while unarmoring private key, got:" + err.Error())
|
||||
}
|
||||
|
||||
uk, err := pk.Unlock(passphrase)
|
||||
if err != nil {
|
||||
t.Error("Expected no error while unlocking private key, got:" + err.Error())
|
||||
}
|
||||
|
||||
defer uk.ClearPrivateParams()
|
||||
|
||||
ukr, err := NewKeyRing(uk)
|
||||
if err != nil {
|
||||
t.Error("Expected no error while building private keyring, got:" + err.Error())
|
||||
}
|
||||
|
||||
inputPlaintext := readTestFile("att_cleartext", false)
|
||||
plaintextBytes := []byte(inputPlaintext)
|
||||
bufferLen := 0
|
||||
dataPacket := make([]byte, bufferLen)
|
||||
_, err = ukr.NewManualAttachmentProcessor(
|
||||
len(plaintextBytes),
|
||||
"test.txt",
|
||||
dataPacket,
|
||||
)
|
||||
if err == nil {
|
||||
t.Error("Expected an error while building the attachment processor with an empty buffer got nil")
|
||||
}
|
||||
}
|
||||
19
crypto/testdata/att_cleartext
vendored
Normal file
19
crypto/testdata/att_cleartext
vendored
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus eget imperdiet justo. Phasellus a tellus a lorem tempus rhoncus quis iaculis ante. Nullam et arcu cursus, vulputate quam sagittis, rutrum arcu. Nam egestas mauris a volutpat commodo. Vivamus eu ullamcorper turpis. Etiam eget odio tempor eros tempor bibendum id at mi. Vestibulum ipsum ipsum, auctor eget eros ut, feugiat sollicitudin libero. Aenean id ipsum urna. Donec sed est interdum, eleifend dolor et, viverra dui. Fusce felis enim, interdum a rhoncus eget, pellentesque sit amet erat. Mauris consequat mauris enim, sit amet blandit massa elementum at. Donec quis lorem lectus.
|
||||
|
||||
Nulla ultricies risus tortor, et auctor magna mattis id. Nullam vel pretium nisl. Aenean hendrerit purus sed interdum porta. Quisque eu porttitor felis. Aliquam tempor mi lorem, vitae placerat elit congue ut. Sed et orci massa. Nunc suscipit auctor diam et dapibus. Nullam vehicula accumsan libero facilisis dignissim.
|
||||
|
||||
Vestibulum pellentesque cursus tortor, ut porta eros eleifend quis. Vestibulum nec consequat elit. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Vestibulum a gravida sapien. In vel vulputate dui. Nam mattis, dui a finibus cursus, lectus enim aliquam ante, ut finibus tortor mauris vel nunc. Nam vehicula nulla nec tortor faucibus mollis. Nunc eget purus non mauris placerat euismod. Mauris ut mi cursus ante efficitur posuere in sit amet orci.
|
||||
|
||||
Maecenas placerat neque vel congue feugiat. Quisque laoreet viverra posuere. Pellentesque eget mauris dictum, condimentum augue mattis, lacinia est. Duis faucibus ligula orci. Pellentesque hendrerit dolor dolor, nec tempus magna eleifend et. Aliquam eleifend sodales tortor, sit amet rhoncus tellus varius vel. Mauris fermentum faucibus velit. Morbi porta lectus eu consequat elementum. Aenean quis porta sem. Nam vulputate vitae ligula vitae scelerisque. Pellentesque rutrum in tellus nec accumsan. Vestibulum imperdiet dictum ipsum at congue. Phasellus vel metus sagittis, posuere nulla eget, congue urna.
|
||||
|
||||
Mauris lorem neque, ornare eget efficitur sit amet, aliquam eu neque. Suspendisse sed lectus eleifend, vestibulum orci quis, efficitur ex. Praesent id vulputate dui. Pellentesque sagittis felis sit amet nulla elementum sodales. Phasellus id commodo ante. Curabitur ac ante lorem. Phasellus ante arcu, tristique vitae ipsum quis, placerat pretium augue. Nunc egestas in sem finibus dapibus. Vestibulum luctus hendrerit odio a tempor. In hac habitasse platea dictumst. Phasellus varius facilisis pharetra. Donec eleifend elit blandit, gravida erat vitae, convallis risus.
|
||||
|
||||
Nulla aliquam convallis velit vel fringilla. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Aenean pellentesque, eros a porta rhoncus, lectus tellus aliquet lectus, vitae pharetra metus mauris et risus. Vestibulum facilisis ipsum non augue vehicula convallis. Phasellus eu sodales elit. Proin auctor, est ac vestibulum faucibus, odio arcu viverra ante, sed accumsan massa eros ac quam. Curabitur sit amet quam lectus. Aenean interdum turpis eget neque aliquet, blandit placerat urna accumsan.
|
||||
|
||||
Nullam vitae consectetur diam, sit amet laoreet leo. Quisque pretium consectetur tellus et pretium. Praesent ut commodo tellus. Fusce finibus dolor vel ex pretium, ut eleifend elit molestie. Donec molestie gravida metus eu luctus. Interdum et malesuada fames ac ante ipsum primis in faucibus. Fusce massa ipsum, sodales ut ullamcorper vel, ultricies et tellus. Nunc pellentesque eu sem quis tincidunt. Nulla justo urna, elementum a libero ut, tempor bibendum nisl.
|
||||
|
||||
Vestibulum aliquet varius mauris eu pellentesque. Proin elit dui, auctor ac nulla id, tincidunt vulputate turpis. Pellentesque rutrum efficitur dolor, venenatis sollicitudin dolor scelerisque at. Etiam eu velit eu massa dignissim suscipit. In non nibh metus. Nam mi arcu, malesuada iaculis augue nec, imperdiet fermentum lacus. Etiam malesuada risus justo, suscipit volutpat metus sodales eget. Sed sit amet lacus interdum, vehicula tortor eget, convallis turpis. Mauris in aliquam metus, at scelerisque quam. Suspendisse facilisis suscipit mi at mollis.
|
||||
|
||||
Vestibulum eget purus ut lacus venenatis dictum et id ipsum. Integer in lorem non turpis elementum tempus ut a sem. Curabitur aliquam volutpat fringilla. Nulla sed facilisis sapien, at consequat odio. Pellentesque vehicula tincidunt lacus eget malesuada. Donec porttitor quam nec lacus commodo pellentesque. Cras ut accumsan tellus, ut rhoncus mi.
|
||||
|
||||
Nullam eget rhoncus ligula. Suspendisse porttitor ac quam sit amet gravida. Cras et porttitor nisi, eget accumsan augue. Proin turpis nibh, hendrerit vitae placerat at, mattis in mi. Sed eu mi nulla. Aenean sit amet condimentum ipsum, nec aliquet felis. Ut a dolor iaculis, commodo tortor ut, viverra ligula. Mauris sed ullamcorper ligula. Ut ornare egestas est, sed pulvinar ipsum lacinia quis. Praesent nec erat vestibulum, interdum ipsum at, pellentesque ipsum. Sed finibus, sapien at hendrerit luctus, metus leo ultrices erat, vitae rutrum augue lacus ut dolor. Mauris elementum nec leo interdum gravida. Cras id enim placerat, fermentum nisi eget, vestibulum sapien. Quisque aliquam, mauris ut tempor facilisis, lacus nisl maximus sapien, eu ultrices ex dolor in risus. Mauris facilisis est scelerisque, interdum erat vel, lobortis nunc.
|
||||
4
go.mod
4
go.mod
|
|
@ -3,9 +3,9 @@ module github.com/ProtonMail/gopenpgp/v2
|
|||
go 1.15
|
||||
|
||||
require (
|
||||
github.com/ProtonMail/go-crypto v0.0.0-20210317131446-1ce7a431ff79
|
||||
github.com/ProtonMail/go-crypto v0.0.0-20210329181949-3900d675f39b
|
||||
github.com/ProtonMail/go-mime v0.0.0-20190923161245-9b5a4261663a
|
||||
github.com/pkg/errors v0.8.1
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/stretchr/testify v1.4.0
|
||||
golang.org/x/mobile v0.0.0-20200801112145-973feb4309de
|
||||
)
|
||||
|
|
|
|||
35
go.sum
35
go.sum
|
|
@ -1,7 +1,15 @@
|
|||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802 h1:1BDTz0u9nC3//pOCMdNH+CiXJVYJh5UQNCOBG7jbELc=
|
||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||
github.com/ProtonMail/go-crypto v0.0.0-20201208171014-cdb7591792e2 h1:pQkjJELHayW59jp7r4G5Dlmnicr5McejDfwsjcwI1SU=
|
||||
github.com/ProtonMail/go-crypto v0.0.0-20201208171014-cdb7591792e2/go.mod h1:HTM9X7e9oLwn7RiqLG0UVwVRJenLs3wN+tQ0NPAfwMQ=
|
||||
github.com/ProtonMail/go-crypto v0.0.0-20210315112718-27cd8a6cfae2 h1:6AqBi/qGQ87mrfpYRahPUtRXLDw+gCE9hcSSqrg56R0=
|
||||
github.com/ProtonMail/go-crypto v0.0.0-20210315112718-27cd8a6cfae2/go.mod h1:HTM9X7e9oLwn7RiqLG0UVwVRJenLs3wN+tQ0NPAfwMQ=
|
||||
github.com/ProtonMail/go-crypto v0.0.0-20210317131446-1ce7a431ff79 h1:Y6Zid4OqXGXRaaowOfYf2+kNE0BYmnqACXnix/VtXQg=
|
||||
github.com/ProtonMail/go-crypto v0.0.0-20210317131446-1ce7a431ff79/go.mod h1:HTM9X7e9oLwn7RiqLG0UVwVRJenLs3wN+tQ0NPAfwMQ=
|
||||
github.com/ProtonMail/go-crypto v0.0.0-20210326100218-fe57d110ff3d h1:RLRwJxyk20dxDOp9rHId/SPyB6N2YkctnsyFhbIv5lg=
|
||||
github.com/ProtonMail/go-crypto v0.0.0-20210326100218-fe57d110ff3d/go.mod h1:HTM9X7e9oLwn7RiqLG0UVwVRJenLs3wN+tQ0NPAfwMQ=
|
||||
github.com/ProtonMail/go-crypto v0.0.0-20210329181949-3900d675f39b h1:E0jcApeWTn0zEUOANmwLg2k3IfTIyX4ffz2l95AEIBk=
|
||||
github.com/ProtonMail/go-crypto v0.0.0-20210329181949-3900d675f39b/go.mod h1:HTM9X7e9oLwn7RiqLG0UVwVRJenLs3wN+tQ0NPAfwMQ=
|
||||
github.com/ProtonMail/go-mime v0.0.0-20190923161245-9b5a4261663a h1:W6RrgN/sTxg1msqzFFb+G80MFmpjMw61IU+slm+wln4=
|
||||
github.com/ProtonMail/go-mime v0.0.0-20190923161245-9b5a4261663a/go.mod h1:NYt+V3/4rEeDuaev/zw1zCq8uqVEuPHzDPo3OZrlGJ4=
|
||||
github.com/ProtonMail/go-mobile v0.0.0-20201014085805-7a2d68bf792f h1:u2i2ZBaZNzyJlIpyZWRJAoYMYqKObfJxvja3lr2olWw=
|
||||
|
|
@ -15,18 +23,41 @@ github.com/hashicorp/go-version v1.2.1 h1:zEfKbn2+PDgroKdiOzqiE8rsmLqU2uwi5PB5pB
|
|||
github.com/hashicorp/go-version v1.2.1/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.3 h1:CE8S1cTafDpPvMhIxNJKvHsGVBgn1xWYf1NbHQhywc8=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/mattn/go-colorable v0.0.0-20170327083344-ded68f7a9561 h1:isR/L+BIZ+rqODWYR/f526ygrBMGKZYFhaaFRDGvuZ8=
|
||||
github.com/mattn/go-colorable v0.0.0-20170327083344-ded68f7a9561/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
||||
github.com/mattn/go-isatty v0.0.3 h1:ns/ykhmWi7G9O+8a448SecJU3nSMBXJfqQkl0upE1jI=
|
||||
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
||||
github.com/peterh/liner v0.0.0-20170317030525-88609521dc4b h1:8uaXtUkxiy+T/zdLWuxa/PG4so0TPZDZfafFNNSaptE=
|
||||
github.com/peterh/liner v0.0.0-20170317030525-88609521dc4b/go.mod h1:xIteQHvHuaLYG9IFj6mSxM0fCKrs34IrEQUhOYuGPHc=
|
||||
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/profile v0.0.0-20170413231811-06b906832ed0/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNueLj0oo=
|
||||
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
|
||||
github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4=
|
||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||
github.com/sirupsen/logrus v1.6.0 h1:UBcNElsrwanuuMsnGSlYmtmgbb23qDR5dG+6X6Oo89I=
|
||||
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
|
||||
github.com/spf13/cobra v0.0.0-20170417170307-b6cb39589372 h1:eRfW1vRS4th8IX2iQeyqQ8cOUNOySvAYJ0IUvTXGoYA=
|
||||
github.com/spf13/cobra v0.0.0-20170417170307-b6cb39589372/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
|
||||
github.com/spf13/pflag v0.0.0-20170417173400-9e4c21054fa1 h1:7bozMfSdo41n2NOc0GsVTTVUiA+Ncaj6pXNpm4UHKys=
|
||||
github.com/spf13/pflag v0.0.0-20170417173400-9e4c21054fa1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A=
|
||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
go.starlark.net v0.0.0-20200821142938-949cc6f4b097 h1:YiRMXXgG+Pg26t1fjq+iAjaauKWMC9cmGFrtOEuwDDg=
|
||||
go.starlark.net v0.0.0-20200821142938-949cc6f4b097/go.mod h1:f0znQkUKRrkk36XxWbGjMqQM8wGv/xHBVE2qc3B5oFU=
|
||||
golang.org/x/arch v0.0.0-20190927153633-4e8777c89be4 h1:QlVATYS7JBoZMVaf+cNjb90WD/beKVHnIxFKT4QaHVI=
|
||||
golang.org/x/arch v0.0.0-20190927153633-4e8777c89be4/go.mod h1:flIaEI6LNU6xOCD5PaJvn9wGP0agmIOqjrtsKGRguv4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550 h1:ObdrDkeb4kJdCP557AjRjq69pTHfNouLtWZG7j9rPN8=
|
||||
|
|
@ -60,6 +91,7 @@ golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
|||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191127201027-ecd32218bd7f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20200117012304-6edc0a871e69 h1:yBHHx+XZqXJBm6Exke3N7V9gnlsyXxoCPEb1yVenjfk=
|
||||
golang.org/x/tools v0.0.0-20200117012304-6edc0a871e69/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
|
|
@ -67,5 +99,8 @@ golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898 h1:/atklqdjdhuosWIl6AIbO
|
|||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
qgithub.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue