WIP: Add compression to API (#91)
* Add compression to API * Add docs * Use defaults for a simpler interface * Update x/crypto * Fix ecdsa key types for lib update
This commit is contained in:
parent
9503b68f0c
commit
371d429001
13 changed files with 177 additions and 31 deletions
11
CHANGELOG.md
11
CHANGELOG.md
|
|
@ -5,6 +5,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [Unreleased]
|
||||
### Security
|
||||
- Updated underlying crypto library
|
||||
|
||||
### Added
|
||||
- Key Armoring with custom headers
|
||||
```go
|
||||
|
|
@ -138,6 +141,13 @@ NewPlainMessageFromFile(data []byte, filename string, modTime int) *PlainMessage
|
|||
(msg *PlainMessage) GetModTime() uint32
|
||||
```
|
||||
|
||||
- `EncryptWithCompression` to encrypt specifying a compression for asymmetric and session keys
|
||||
```go
|
||||
(keyRing *KeyRing) EncryptWithCompression(message *PlainMessage, privateKey *KeyRing) (*PGPMessage, error)
|
||||
|
||||
(sk *SessionKey) EncryptWithCompression(message *PlainMessage) ([]byte, error)
|
||||
```
|
||||
|
||||
### Changed
|
||||
- Improved key and message armoring testing
|
||||
- `EncryptSessionKey` now creates encrypted key packets for each valid encryption key in the provided keyring.
|
||||
|
|
@ -148,6 +158,7 @@ NewPlainMessageFromFile(data []byte, filename string, modTime int) *PlainMessage
|
|||
- The `PlainMessage` struct now contains the fields `Filename` (string) and `Time` (uint32)
|
||||
- All the Decrypt* functions return the filename, type, and time specified in the encrypted message
|
||||
- Improved error wrapping and management
|
||||
- CI has been moved from travis to Actions, with automated artifacts build
|
||||
|
||||
### Fixed
|
||||
- Public key armoring headers
|
||||
|
|
|
|||
|
|
@ -16,3 +16,6 @@ const (
|
|||
SIGNATURE_NO_VERIFIER int = 2
|
||||
SIGNATURE_FAILED int = 3
|
||||
)
|
||||
|
||||
const DefaultCompression = 2 // ZLIB
|
||||
const DefaultCompressionLevel = 6 // Corresponds to default -1 for ZLIB
|
||||
|
|
|
|||
|
|
@ -63,8 +63,8 @@ func assertRSACleared(t *testing.T, rsaPriv *rsa.PrivateKey) {
|
|||
}
|
||||
}
|
||||
|
||||
func assertEdDSACleared(t *testing.T, priv ed25519.PrivateKey) {
|
||||
assertMemCleared(t, priv)
|
||||
func assertEdDSACleared(t *testing.T, priv *ed25519.PrivateKey) {
|
||||
assertMemCleared(t, *priv)
|
||||
}
|
||||
|
||||
func assertECDHCleared(t *testing.T, priv *ecdh.PrivateKey) {
|
||||
|
|
|
|||
|
|
@ -328,10 +328,10 @@ func (key *Key) Check() (bool, error) {
|
|||
func (key *Key) PrintFingerprints() {
|
||||
for _, subKey := range key.entity.Subkeys {
|
||||
if !subKey.Sig.FlagsValid || subKey.Sig.FlagEncryptStorage || subKey.Sig.FlagEncryptCommunications {
|
||||
fmt.Println("SubKey:" + hex.EncodeToString(subKey.PublicKey.Fingerprint[:]))
|
||||
fmt.Println("SubKey:" + hex.EncodeToString(subKey.PublicKey.Fingerprint))
|
||||
}
|
||||
}
|
||||
fmt.Println("PrimaryKey:" + hex.EncodeToString(key.entity.PrimaryKey.Fingerprint[:]))
|
||||
fmt.Println("PrimaryKey:" + hex.EncodeToString(key.entity.PrimaryKey.Fingerprint))
|
||||
}
|
||||
|
||||
// GetHexKeyID returns the key ID, hex encoded as a string.
|
||||
|
|
@ -346,7 +346,7 @@ func (key *Key) GetKeyID() uint64 {
|
|||
|
||||
// GetFingerprint gets the fingerprint from the key.
|
||||
func (key *Key) GetFingerprint() string {
|
||||
return hex.EncodeToString(key.entity.PrimaryKey.Fingerprint[:])
|
||||
return hex.EncodeToString(key.entity.PrimaryKey.Fingerprint)
|
||||
}
|
||||
|
||||
// GetSHA256Fingerprints computes the SHA256 fingerprints of the key and subkeys.
|
||||
|
|
|
|||
|
|
@ -57,7 +57,7 @@ func clearPrivateKey(privateKey interface{}) error {
|
|||
return clearElGamalPrivateKey(priv)
|
||||
case *ecdsa.PrivateKey:
|
||||
return clearECDSAPrivateKey(priv)
|
||||
case ed25519.PrivateKey:
|
||||
case *ed25519.PrivateKey:
|
||||
return clearEdDSAPrivateKey(priv)
|
||||
case *ecdh.PrivateKey:
|
||||
return clearECDHPrivateKey(priv)
|
||||
|
|
@ -115,8 +115,8 @@ func clearECDSAPrivateKey(priv *ecdsa.PrivateKey) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func clearEdDSAPrivateKey(priv ed25519.PrivateKey) error {
|
||||
clearMem(priv)
|
||||
func clearEdDSAPrivateKey(priv *ed25519.PrivateKey) error {
|
||||
clearMem(*priv)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -257,17 +257,21 @@ func TestFailCheckIntegrity(t *testing.T) {
|
|||
|
||||
k1.entity.PrivateKey.PrivateKey = k2.entity.PrivateKey.PrivateKey // Swap private keys
|
||||
|
||||
k3, err := k1.Copy()
|
||||
isVerified, err := k1.Check()
|
||||
if err != nil {
|
||||
t.Fatal("Expected no error while locking keyring kr3, got:", err)
|
||||
}
|
||||
|
||||
isVerified, err := k3.Check()
|
||||
if err != nil {
|
||||
t.Fatal("Expected no error while checking correct passphrase, got:", err)
|
||||
t.Fatal("Expected no error while checking key, got:", err)
|
||||
}
|
||||
|
||||
assert.Exactly(t, false, isVerified)
|
||||
|
||||
serialized, err := k1.Serialize()
|
||||
if err != nil {
|
||||
t.Fatal("Expected no error while serializing keyring kr3, got:", err)
|
||||
}
|
||||
|
||||
_, err = NewKey(serialized)
|
||||
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func TestGetPublicKey(t *testing.T) {
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import (
|
|||
"io"
|
||||
"io/ioutil"
|
||||
|
||||
"github.com/ProtonMail/gopenpgp/v2/constants"
|
||||
"github.com/pkg/errors"
|
||||
"golang.org/x/crypto/openpgp"
|
||||
"golang.org/x/crypto/openpgp/packet"
|
||||
|
|
@ -16,7 +17,28 @@ import (
|
|||
// * message : The plaintext input as a PlainMessage.
|
||||
// * privateKey : (optional) an unlocked private keyring to include signature in the message.
|
||||
func (keyRing *KeyRing) Encrypt(message *PlainMessage, privateKey *KeyRing) (*PGPMessage, error) {
|
||||
encrypted, err := asymmetricEncrypt(message, keyRing, privateKey)
|
||||
config := &packet.Config{DefaultCipher: packet.CipherAES256, Time: getTimeGenerator()}
|
||||
encrypted, err := asymmetricEncrypt(message, keyRing, privateKey, config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return NewPGPMessage(encrypted), nil
|
||||
}
|
||||
|
||||
// EncryptWithCompression encrypts with compression support a PlainMessage to PGPMessage using public/private keys.
|
||||
// * message : The plain data as a PlainMessage.
|
||||
// * privateKey : (optional) an unlocked private keyring to include signature in the message.
|
||||
// * output : The encrypted data as PGPMessage.
|
||||
func (keyRing *KeyRing) EncryptWithCompression(message *PlainMessage, privateKey *KeyRing) (*PGPMessage, error) {
|
||||
config := &packet.Config{
|
||||
DefaultCipher: packet.CipherAES256,
|
||||
Time: getTimeGenerator(),
|
||||
DefaultCompressionAlgo: constants.DefaultCompression,
|
||||
CompressionConfig: &packet.CompressionConfig{Level: constants.DefaultCompressionLevel},
|
||||
}
|
||||
|
||||
encrypted, err := asymmetricEncrypt(message, keyRing, privateKey, config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -68,7 +90,11 @@ func (keyRing *KeyRing) VerifyDetached(message *PlainMessage, signature *PGPSign
|
|||
// ------ INTERNAL FUNCTIONS -------
|
||||
|
||||
// Core for encryption+signature functions.
|
||||
func asymmetricEncrypt(plainMessage *PlainMessage, publicKey, privateKey *KeyRing) ([]byte, error) {
|
||||
func asymmetricEncrypt(
|
||||
plainMessage *PlainMessage,
|
||||
publicKey, privateKey *KeyRing,
|
||||
config *packet.Config,
|
||||
) ([]byte, error) {
|
||||
var outBuf bytes.Buffer
|
||||
var encryptWriter io.WriteCloser
|
||||
var signEntity *openpgp.Entity
|
||||
|
|
@ -82,8 +108,6 @@ func asymmetricEncrypt(plainMessage *PlainMessage, publicKey, privateKey *KeyRin
|
|||
}
|
||||
}
|
||||
|
||||
config := &packet.Config{DefaultCipher: packet.CipherAES256, Time: getTimeGenerator()}
|
||||
|
||||
hints := &openpgp.FileHints{
|
||||
IsBinary: plainMessage.IsBinary(),
|
||||
FileName: plainMessage.Filename,
|
||||
|
|
|
|||
|
|
@ -157,7 +157,7 @@ func TestClearPrivateKey(t *testing.T) {
|
|||
|
||||
keys := keyRingCopy.GetKeys()
|
||||
assertRSACleared(t, keys[0].entity.PrivateKey.PrivateKey.(*rsa.PrivateKey))
|
||||
assertEdDSACleared(t, keys[1].entity.PrivateKey.PrivateKey.(ed25519.PrivateKey))
|
||||
assertEdDSACleared(t, keys[1].entity.PrivateKey.PrivateKey.(*ed25519.PrivateKey))
|
||||
assertRSACleared(t, keys[2].entity.PrivateKey.PrivateKey.(*rsa.PrivateKey))
|
||||
}
|
||||
|
||||
|
|
@ -175,7 +175,7 @@ func TestClearPrivateWithSubkeys(t *testing.T) {
|
|||
assertRSACleared(t, keys[0].entity.PrivateKey.PrivateKey.(*rsa.PrivateKey))
|
||||
assertRSACleared(t, keys[0].entity.Subkeys[0].PrivateKey.PrivateKey.(*rsa.PrivateKey))
|
||||
|
||||
assertEdDSACleared(t, keys[1].entity.PrivateKey.PrivateKey.(ed25519.PrivateKey))
|
||||
assertEdDSACleared(t, keys[1].entity.PrivateKey.PrivateKey.(*ed25519.PrivateKey))
|
||||
assertECDHCleared(t, keys[1].entity.Subkeys[0].PrivateKey.PrivateKey.(*ecdh.PrivateKey))
|
||||
|
||||
assertRSACleared(t, keys[2].entity.PrivateKey.PrivateKey.(*rsa.PrivateKey))
|
||||
|
|
|
|||
|
|
@ -87,6 +87,54 @@ func TestTextMixedMessageDecryptionWithPassword(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestTextMessageEncryption(t *testing.T) {
|
||||
var message = NewPlainMessageFromString(
|
||||
"The secret code is... 1, 2, 3, 4, 5. I repeat: the secret code is... 1, 2, 3, 4, 5",
|
||||
)
|
||||
|
||||
ciphertext, err := keyRingTestPublic.Encrypt(message, nil)
|
||||
if err != nil {
|
||||
t.Fatal("Expected no error when encrypting, got:", err)
|
||||
}
|
||||
|
||||
split, err := ciphertext.SeparateKeyAndData(1024, 0)
|
||||
if err != nil {
|
||||
t.Fatal("Expected no error when splitting, got:", err)
|
||||
}
|
||||
|
||||
assert.Len(t, split.GetBinaryDataPacket(), 133) // Assert uncompressed encrypted body length
|
||||
|
||||
decrypted, err := keyRingTestPrivate.Decrypt(ciphertext, nil, 0)
|
||||
if err != nil {
|
||||
t.Fatal("Expected no error when decrypting, got:", err)
|
||||
}
|
||||
assert.Exactly(t, message.GetString(), decrypted.GetString())
|
||||
}
|
||||
|
||||
func TestTextMessageEncryptionWithCompression(t *testing.T) {
|
||||
var message = NewPlainMessageFromString(
|
||||
"The secret code is... 1, 2, 3, 4, 5. I repeat: the secret code is... 1, 2, 3, 4, 5",
|
||||
)
|
||||
|
||||
ciphertext, err := keyRingTestPublic.EncryptWithCompression(message, nil)
|
||||
if err != nil {
|
||||
t.Fatal("Expected no error when encrypting, got:", err)
|
||||
}
|
||||
|
||||
split, err := ciphertext.SeparateKeyAndData(1024, 0)
|
||||
if err != nil {
|
||||
t.Fatal("Expected no error when splitting, got:", err)
|
||||
}
|
||||
|
||||
assert.Len(t, split.GetBinaryDataPacket(), 117) // Assert uncompressed encrypted body length
|
||||
|
||||
decrypted, err := keyRingTestPrivate.Decrypt(ciphertext, nil, 0)
|
||||
if err != nil {
|
||||
t.Fatal("Expected no error when decrypting, got:", err)
|
||||
}
|
||||
assert.Exactly(t, message.GetString(), decrypted.GetString())
|
||||
}
|
||||
|
||||
func TestTextMessageEncryptionWithSignature(t *testing.T) {
|
||||
var message = NewPlainMessageFromString("plain text")
|
||||
|
||||
ciphertext, err := keyRingTestPublic.Encrypt(message, keyRingTestPrivate)
|
||||
|
|
|
|||
|
|
@ -114,9 +114,6 @@ func newSessionKeyFromEncrypted(ek *packet.EncryptedKey) (*SessionKey, error) {
|
|||
// * message : The plain data as a PlainMessage.
|
||||
// * output : The encrypted data as PGPMessage.
|
||||
func (sk *SessionKey) Encrypt(message *PlainMessage) ([]byte, error) {
|
||||
var encBuf bytes.Buffer
|
||||
var encryptWriter io.WriteCloser
|
||||
|
||||
dc, err := sk.GetCipherFunc()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "gopenpgp: unable to encrypt with session key")
|
||||
|
|
@ -127,7 +124,33 @@ func (sk *SessionKey) Encrypt(message *PlainMessage) ([]byte, error) {
|
|||
DefaultCipher: dc,
|
||||
}
|
||||
|
||||
encryptWriter, err = packet.SerializeSymmetricallyEncrypted(&encBuf, config.Cipher(), sk.Key, config)
|
||||
return encryptWithSessionKey(message, sk, config)
|
||||
}
|
||||
|
||||
// EncryptWithCompression encrypts with compression support a PlainMessage to PGPMessage with a SessionKey.
|
||||
// * message : The plain data as a PlainMessage.
|
||||
// * output : The encrypted data as PGPMessage.
|
||||
func (sk *SessionKey) EncryptWithCompression(message *PlainMessage) ([]byte, error) {
|
||||
dc, err := sk.GetCipherFunc()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "gopenpgp: unable to encrypt with session key")
|
||||
}
|
||||
|
||||
config := &packet.Config{
|
||||
Time: getTimeGenerator(),
|
||||
DefaultCipher: dc,
|
||||
DefaultCompressionAlgo: constants.DefaultCompression,
|
||||
CompressionConfig: &packet.CompressionConfig{Level: constants.DefaultCompressionLevel},
|
||||
}
|
||||
|
||||
return encryptWithSessionKey(message, sk, config)
|
||||
}
|
||||
|
||||
func encryptWithSessionKey(message *PlainMessage, sk *SessionKey, config *packet.Config) ([]byte, error) {
|
||||
var encBuf bytes.Buffer
|
||||
var encryptWriter io.WriteCloser
|
||||
|
||||
encryptWriter, err := packet.SerializeSymmetricallyEncrypted(&encBuf, config.Cipher(), sk.Key, config)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "gopenpgp: unable to encrypt")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -101,13 +101,18 @@ func TestSymmetricKeyPacketWrongSize(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestDataPacketEncryption(t *testing.T) {
|
||||
var message = NewPlainMessageFromString("The secret code is... 1, 2, 3, 4, 5")
|
||||
var message = NewPlainMessageFromString(
|
||||
"The secret code is... 1, 2, 3, 4, 5. I repeat: the secret code is... 1, 2, 3, 4, 5",
|
||||
)
|
||||
|
||||
// Encrypt data with session key
|
||||
dataPacket, err := testSessionKey.Encrypt(message)
|
||||
if err != nil {
|
||||
t.Fatal("Expected no error when encrypting, got:", err)
|
||||
}
|
||||
|
||||
assert.Len(t, dataPacket, 133) // Assert uncompressed encrypted body length
|
||||
|
||||
// Decrypt data with wrong session key
|
||||
wrongKey := SessionKey{
|
||||
Key: []byte("wrong pass"),
|
||||
|
|
@ -184,3 +189,24 @@ func TestSessionKeyClear(t *testing.T) {
|
|||
testSessionKey.Clear()
|
||||
assertMemCleared(t, testSessionKey.Key)
|
||||
}
|
||||
|
||||
func TestDataPacketEncryptionWithCompression(t *testing.T) {
|
||||
var message = NewPlainMessageFromString(
|
||||
"The secret code is... 1, 2, 3, 4, 5. I repeat: the secret code is... 1, 2, 3, 4, 5",
|
||||
)
|
||||
|
||||
// Encrypt data with session key
|
||||
dataPacket, err := testSessionKey.EncryptWithCompression(message)
|
||||
if err != nil {
|
||||
t.Fatal("Expected no error when encrypting, got:", err)
|
||||
}
|
||||
|
||||
assert.Len(t, dataPacket, 117) // Assert compressed encrypted body length
|
||||
|
||||
// Decrypt data with the good session key
|
||||
decrypted, err := testSessionKey.Decrypt(dataPacket)
|
||||
if err != nil {
|
||||
t.Fatal("Expected no error when decrypting, got:", err)
|
||||
}
|
||||
assert.Exactly(t, message.GetString(), decrypted.GetString())
|
||||
}
|
||||
|
|
|
|||
2
go.mod
2
go.mod
|
|
@ -10,6 +10,6 @@ require (
|
|||
golang.org/x/mobile v0.0.0-20200801112145-973feb4309de
|
||||
)
|
||||
|
||||
replace golang.org/x/crypto => github.com/ProtonMail/crypto v0.0.0-20200416114516-1fa7f403fb9c
|
||||
replace golang.org/x/crypto => github.com/ProtonMail/crypto v0.0.0-20201104134830-fcb5d97d611a
|
||||
|
||||
replace golang.org/x/mobile => github.com/zhj4478/mobile v0.0.0-20201014085805-7a2d68bf792f
|
||||
|
|
|
|||
15
go.sum
15
go.sum
|
|
@ -1,6 +1,14 @@
|
|||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||
github.com/ProtonMail/crypto v0.0.0-20200416114516-1fa7f403fb9c h1:DAvlgde2Stu18slmjwikiMPs/CKPV35wSvmJS34z0FU=
|
||||
github.com/ProtonMail/crypto v0.0.0-20200416114516-1fa7f403fb9c/go.mod h1:Pxr7w4gA2ikI4sWyYwEffm+oew1WAJHzG1SiDpQMkrI=
|
||||
github.com/ProtonMail/crypto v0.0.0-20201016191319-576ad9c42ffa h1:RnTRazSOTTBw0S2D0dK6pbC0uEYtHFdO3YQo8lI6coc=
|
||||
github.com/ProtonMail/crypto v0.0.0-20201016191319-576ad9c42ffa/go.mod h1:Pxr7w4gA2ikI4sWyYwEffm+oew1WAJHzG1SiDpQMkrI=
|
||||
github.com/ProtonMail/crypto v0.0.0-20201019194525-d010555f6cb5 h1:VgYclRLmViKcELmBf27PajBM/XFSlRLf+fUacMC5ahM=
|
||||
github.com/ProtonMail/crypto v0.0.0-20201019194525-d010555f6cb5/go.mod h1:Pxr7w4gA2ikI4sWyYwEffm+oew1WAJHzG1SiDpQMkrI=
|
||||
github.com/ProtonMail/crypto v0.0.0-20201020131021-8e70b29752f8 h1:L3xFiZBIG8YCbF+DaQyNJ6QwmVhLsd4XQtH20AtZGIE=
|
||||
github.com/ProtonMail/crypto v0.0.0-20201020131021-8e70b29752f8/go.mod h1:Pxr7w4gA2ikI4sWyYwEffm+oew1WAJHzG1SiDpQMkrI=
|
||||
github.com/ProtonMail/crypto v0.0.0-20201022141144-3fe6b6992c0f h1:CrqdTsoF7teMqQok+iHUx3yjYJfkpDuU7y/nIxRJ2rY=
|
||||
github.com/ProtonMail/crypto v0.0.0-20201022141144-3fe6b6992c0f/go.mod h1:Pxr7w4gA2ikI4sWyYwEffm+oew1WAJHzG1SiDpQMkrI=
|
||||
github.com/ProtonMail/crypto v0.0.0-20201104134830-fcb5d97d611a h1:1zaMXAQgYCo4ca10i6CKcKbd+M3phLp5VFP+f+VrbSI=
|
||||
github.com/ProtonMail/crypto v0.0.0-20201104134830-fcb5d97d611a/go.mod h1:Pxr7w4gA2ikI4sWyYwEffm+oew1WAJHzG1SiDpQMkrI=
|
||||
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/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
|
|
@ -19,7 +27,6 @@ github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
|
|||
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=
|
||||
github.com/zhj4478/mobile v0.0.0-20201014085805-7a2d68bf792f h1:3NX1KS08WQ2sF4EYpqlpWlBDpxpcaIkhywFAKQM1iYQ=
|
||||
github.com/zhj4478/mobile v0.0.0-20201014085805-7a2d68bf792f/go.mod h1:skQtrUTUwhdJvXM/2KKJzY8pDgNr9I/FOMqDVRPBUS4=
|
||||
golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56/go.mod h1:JhuoJpWY28nO4Vef9tZUw9qufEGTyX1+7lmHxV5q5G4=
|
||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||
|
|
@ -41,10 +48,10 @@ 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-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=
|
||||
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.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue