Skip to content

Commit cb2340c

Browse files
committed
Merge branch 'master' of github.com:mozilla/sops
2 parents bb248fb + 0442086 commit cb2340c

File tree

474 files changed

+152569
-79439
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

474 files changed

+152569
-79439
lines changed

CHANGELOG.rst

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,35 @@
11
Changelog
22
=========
33

4+
3.0.0
5+
-----
6+
7+
* Shamir secret sharing scheme support allows SOPS to require multiple master
8+
keys to access a data key and decrypt a file. See `sops groups -help` and the
9+
documentation in README.
10+
11+
* Keyservice to forward access to a local master key on a socket, similar to
12+
gpg-agent. See `sops keyservice --help` and the documentation in README.
13+
14+
* Encrypt comments by default
15+
16+
* Support for Google Compute Platform KMS
17+
18+
* Refactor of the store logic to separate the internal representation SOPS
19+
has of files from the external representation used in JSON and YAML files
20+
21+
* Reencoding of versions as string on sops 1.X files.
22+
**WARNING** this change breaks backward compatibility.
23+
SOPS shows an error message with instructions on how to solve
24+
this if it happens.
25+
26+
* Added command to reconfigure the keys used to encrypt/decrypt a file based on the .sops.yaml config file
27+
28+
* Retrieve missing PGP keys from gpg.mozilla.org
29+
30+
* Improved error messages for errors when decrypting files
31+
32+
433
2.0.0
534
-----
635

cmd/sops/common/common.go

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -32,11 +32,11 @@ type DecryptTreeOpts struct {
3232
func DecryptTree(opts DecryptTreeOpts) (dataKey []byte, err error) {
3333
dataKey, err = opts.Tree.Metadata.GetDataKeyWithKeyServices(opts.KeyServices)
3434
if err != nil {
35-
return nil, cli.NewExitError(err.Error(), codes.CouldNotRetrieveKey)
35+
return nil, NewExitError(err, codes.CouldNotRetrieveKey)
3636
}
3737
computedMac, err := opts.Tree.Decrypt(dataKey, opts.Cipher)
3838
if err != nil {
39-
return nil, cli.NewExitError(fmt.Sprintf("Error decrypting tree: %s", err), codes.ErrorDecryptingTree)
39+
return nil, NewExitError(fmt.Sprintf("Error decrypting tree: %s", err), codes.ErrorDecryptingTree)
4040
}
4141
fileMac, err := opts.Cipher.Decrypt(opts.Tree.Metadata.MessageAuthenticationCode, dataKey, opts.Tree.Metadata.LastModified.Format(time.RFC3339))
4242
if !opts.IgnoreMac {
@@ -45,7 +45,7 @@ func DecryptTree(opts DecryptTreeOpts) (dataKey []byte, err error) {
4545
if fileMac == "" {
4646
fileMac = "no MAC"
4747
}
48-
return nil, cli.NewExitError(fmt.Sprintf("MAC mismatch. File has %s, computed %s", fileMac, computedMac), codes.MacMismatch)
48+
return nil, NewExitError(fmt.Sprintf("MAC mismatch. File has %s, computed %s", fileMac, computedMac), codes.MacMismatch)
4949
}
5050
}
5151
return dataKey, nil
@@ -65,12 +65,12 @@ type EncryptTreeOpts struct {
6565
func EncryptTree(opts EncryptTreeOpts) error {
6666
unencryptedMac, err := opts.Tree.Encrypt(opts.DataKey, opts.Cipher)
6767
if err != nil {
68-
return cli.NewExitError(fmt.Sprintf("Error encrypting tree: %s", err), codes.ErrorEncryptingTree)
68+
return NewExitError(fmt.Sprintf("Error encrypting tree: %s", err), codes.ErrorEncryptingTree)
6969
}
7070
opts.Tree.Metadata.LastModified = time.Now().UTC()
7171
opts.Tree.Metadata.MessageAuthenticationCode, err = opts.Cipher.Encrypt(unencryptedMac, opts.DataKey, opts.Tree.Metadata.LastModified.Format(time.RFC3339))
7272
if err != nil {
73-
return cli.NewExitError(fmt.Sprintf("Could not encrypt MAC: %s", err), codes.ErrorEncryptingMac)
73+
return NewExitError(fmt.Sprintf("Could not encrypt MAC: %s", err), codes.ErrorEncryptingMac)
7474
}
7575
return nil
7676
}
@@ -79,15 +79,15 @@ func EncryptTree(opts EncryptTreeOpts) error {
7979
func LoadEncryptedFile(inputStore sops.Store, inputPath string) (*sops.Tree, error) {
8080
fileBytes, err := ioutil.ReadFile(inputPath)
8181
if err != nil {
82-
return nil, cli.NewExitError(fmt.Sprintf("Error reading file: %s", err), codes.CouldNotReadInputFile)
82+
return nil, NewExitError(fmt.Sprintf("Error reading file: %s", err), codes.CouldNotReadInputFile)
8383
}
8484
metadata, err := inputStore.UnmarshalMetadata(fileBytes)
8585
if err != nil {
86-
return nil, cli.NewExitError(fmt.Sprintf("Error loading file metadata: %s", err), codes.CouldNotReadInputFile)
86+
return nil, NewExitError(fmt.Sprintf("Error loading file metadata: %s", err), codes.CouldNotReadInputFile)
8787
}
8888
branch, err := inputStore.Unmarshal(fileBytes)
8989
if err != nil {
90-
return nil, cli.NewExitError(fmt.Sprintf("Error loading file: %s", err), codes.CouldNotReadInputFile)
90+
return nil, NewExitError(fmt.Sprintf("Error loading file: %s", err), codes.CouldNotReadInputFile)
9191
}
9292
tree := sops.Tree{
9393
Branch: branch,
@@ -96,6 +96,13 @@ func LoadEncryptedFile(inputStore sops.Store, inputPath string) (*sops.Tree, err
9696
return &tree, nil
9797
}
9898

99+
func NewExitError(i interface{}, exitCode int) *cli.ExitError {
100+
if userErr, ok := i.(sops.UserError); ok {
101+
return NewExitError(userErr.UserError(), exitCode)
102+
}
103+
return cli.NewExitError(i, exitCode)
104+
}
105+
99106
func IsYAMLFile(path string) bool {
100107
return strings.HasSuffix(path, ".yaml") || strings.HasSuffix(path, ".yml")
101108
}

cmd/sops/decrypt.go

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import (
77
"go.mozilla.org/sops/cmd/sops/codes"
88
"go.mozilla.org/sops/cmd/sops/common"
99
"go.mozilla.org/sops/keyservice"
10-
"gopkg.in/urfave/cli.v1"
1110
)
1211

1312
type decryptOpts struct {
@@ -41,7 +40,7 @@ func decrypt(opts decryptOpts) (decryptedFile []byte, err error) {
4140
}
4241
decryptedFile, err = opts.OutputStore.Marshal(tree.Branch)
4342
if err != nil {
44-
return nil, cli.NewExitError(fmt.Sprintf("Error dumping file: %s", err), codes.ErrorDumpingTree)
43+
return nil, common.NewExitError(fmt.Sprintf("Error dumping file: %s", err), codes.ErrorDumpingTree)
4544
}
4645
return decryptedFile, err
4746
}
@@ -55,13 +54,13 @@ func extract(tree *sops.Tree, path []interface{}, outputStore sops.Store) (outpu
5554
tree.Branch = newBranch
5655
decrypted, err := outputStore.Marshal(tree.Branch)
5756
if err != nil {
58-
return nil, cli.NewExitError(fmt.Sprintf("Error dumping file: %s", err), codes.ErrorDumpingTree)
57+
return nil, common.NewExitError(fmt.Sprintf("Error dumping file: %s", err), codes.ErrorDumpingTree)
5958
}
6059
return decrypted, err
6160
}
6261
bytes, err := outputStore.MarshalValue(v)
6362
if err != nil {
64-
return nil, cli.NewExitError(fmt.Sprintf("Error dumping tree: %s", err), codes.ErrorDumpingTree)
63+
return nil, common.NewExitError(fmt.Sprintf("Error dumping tree: %s", err), codes.ErrorDumpingTree)
6564
}
6665
return bytes, nil
6766
}

cmd/sops/edit.go

Lines changed: 13 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ import (
2121
"go.mozilla.org/sops/cmd/sops/common"
2222
"go.mozilla.org/sops/keyservice"
2323
"go.mozilla.org/sops/stores/json"
24-
"gopkg.in/urfave/cli.v1"
2524
)
2625

2726
type editOpts struct {
@@ -91,7 +90,7 @@ func editExample(opts editExampleOpts) ([]byte, error) {
9190
var tree sops.Tree
9291
branch, err := opts.InputStore.Unmarshal(fileBytes)
9392
if err != nil {
94-
return nil, cli.NewExitError(fmt.Sprintf("Error unmarshalling file: %s", err), codes.CouldNotReadInputFile)
93+
return nil, common.NewExitError(fmt.Sprintf("Error unmarshalling file: %s", err), codes.CouldNotReadInputFile)
9594
}
9695
tree.Branch = branch
9796
tree.Metadata = sops.Metadata{
@@ -104,7 +103,7 @@ func editExample(opts editExampleOpts) ([]byte, error) {
104103
// Generate a data key
105104
dataKey, errs := tree.GenerateDataKeyWithKeyServices(opts.KeyServices)
106105
if len(errs) > 0 {
107-
return nil, cli.NewExitError(fmt.Sprintf("Error encrypting the data key with one or more master keys: %s", errs), codes.CouldNotRetrieveKey)
106+
return nil, common.NewExitError(fmt.Sprintf("Error encrypting the data key with one or more master keys: %s", errs), codes.CouldNotRetrieveKey)
108107
}
109108

110109
return editTree(opts.editOpts, &tree, dataKey)
@@ -131,12 +130,12 @@ func editTree(opts editOpts, tree *sops.Tree, dataKey []byte) ([]byte, error) {
131130
// Create temporary file for editing
132131
tmpdir, err := ioutil.TempDir("", "")
133132
if err != nil {
134-
return nil, cli.NewExitError(fmt.Sprintf("Could not create temporary directory: %s", err), codes.CouldNotWriteOutputFile)
133+
return nil, common.NewExitError(fmt.Sprintf("Could not create temporary directory: %s", err), codes.CouldNotWriteOutputFile)
135134
}
136135
defer os.RemoveAll(tmpdir)
137136
tmpfile, err := os.Create(path.Join(tmpdir, path.Base(opts.InputPath)))
138137
if err != nil {
139-
return nil, cli.NewExitError(fmt.Sprintf("Could not create temporary file: %s", err), codes.CouldNotWriteOutputFile)
138+
return nil, common.NewExitError(fmt.Sprintf("Could not create temporary file: %s", err), codes.CouldNotWriteOutputFile)
140139
}
141140

142141
// Write to temporary file
@@ -147,17 +146,17 @@ func editTree(opts editOpts, tree *sops.Tree, dataKey []byte) ([]byte, error) {
147146
out, err = opts.OutputStore.Marshal(tree.Branch)
148147
}
149148
if err != nil {
150-
return nil, cli.NewExitError(fmt.Sprintf("Could not marshal tree: %s", err), codes.ErrorDumpingTree)
149+
return nil, common.NewExitError(fmt.Sprintf("Could not marshal tree: %s", err), codes.ErrorDumpingTree)
151150
}
152151
_, err = tmpfile.Write(out)
153152
if err != nil {
154-
return nil, cli.NewExitError(fmt.Sprintf("Could not write output file: %s", err), codes.CouldNotWriteOutputFile)
153+
return nil, common.NewExitError(fmt.Sprintf("Could not write output file: %s", err), codes.CouldNotWriteOutputFile)
155154
}
156155

157156
// Compute file hash to detect if the file has been edited
158157
origHash, err := hashFile(tmpfile.Name())
159158
if err != nil {
160-
return nil, cli.NewExitError(fmt.Sprintf("Could not hash file: %s", err), codes.CouldNotReadInputFile)
159+
return nil, common.NewExitError(fmt.Sprintf("Could not hash file: %s", err), codes.CouldNotReadInputFile)
161160
}
162161

163162
// Let the user edit the file
@@ -179,7 +178,7 @@ func editTree(opts editOpts, tree *sops.Tree, dataKey []byte) ([]byte, error) {
179178
// Output the file
180179
encryptedFile, err := opts.OutputStore.MarshalWithMetadata(tree.Branch, tree.Metadata)
181180
if err != nil {
182-
return nil, cli.NewExitError(fmt.Sprintf("Could not marshal tree: %s", err), codes.ErrorDumpingTree)
181+
return nil, common.NewExitError(fmt.Sprintf("Could not marshal tree: %s", err), codes.ErrorDumpingTree)
183182
}
184183
return encryptedFile, nil
185184
}
@@ -188,18 +187,18 @@ func runEditorUntilOk(opts runEditorUntilOkOpts) error {
188187
for {
189188
err := runEditor(opts.TmpFile.Name())
190189
if err != nil {
191-
return cli.NewExitError(fmt.Sprintf("Could not run editor: %s", err), codes.NoEditorFound)
190+
return common.NewExitError(fmt.Sprintf("Could not run editor: %s", err), codes.NoEditorFound)
192191
}
193192
newHash, err := hashFile(opts.TmpFile.Name())
194193
if err != nil {
195-
return cli.NewExitError(fmt.Sprintf("Could not hash file: %s", err), codes.CouldNotReadInputFile)
194+
return common.NewExitError(fmt.Sprintf("Could not hash file: %s", err), codes.CouldNotReadInputFile)
196195
}
197196
if bytes.Equal(newHash, opts.OriginalHash) {
198-
return cli.NewExitError("File has not changed, exiting.", codes.FileHasNotBeenModified)
197+
return common.NewExitError("File has not changed, exiting.", codes.FileHasNotBeenModified)
199198
}
200199
edited, err := ioutil.ReadFile(opts.TmpFile.Name())
201200
if err != nil {
202-
return cli.NewExitError(fmt.Sprintf("Could not read edited file: %s", err), codes.CouldNotReadInputFile)
201+
return common.NewExitError(fmt.Sprintf("Could not read edited file: %s", err), codes.CouldNotReadInputFile)
203202
}
204203
newBranch, err := opts.InputStore.Unmarshal(edited)
205204
if err != nil {
@@ -228,7 +227,7 @@ func runEditorUntilOk(opts runEditorUntilOkOpts) error {
228227
opts.Tree.Branch = newBranch
229228
needVersionUpdated, err := AIsNewerThanB(version, opts.Tree.Metadata.Version)
230229
if err != nil {
231-
return cli.NewExitError(fmt.Sprintf("Failed to compare document version %q with program version %q: %v", opts.Tree.Metadata.Version, version, err), codes.FailedToCompareVersions)
230+
return common.NewExitError(fmt.Sprintf("Failed to compare document version %q with program version %q: %v", opts.Tree.Metadata.Version, version, err), codes.FailedToCompareVersions)
232231
}
233232
if needVersionUpdated {
234233
opts.Tree.Metadata.Version = version

cmd/sops/encrypt.go

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ import (
99
"go.mozilla.org/sops/cmd/sops/codes"
1010
"go.mozilla.org/sops/cmd/sops/common"
1111
"go.mozilla.org/sops/keyservice"
12-
"gopkg.in/urfave/cli.v1"
1312
)
1413

1514
type encryptOpts struct {
@@ -27,12 +26,12 @@ func encrypt(opts encryptOpts) (encryptedFile []byte, err error) {
2726
// Load the file
2827
fileBytes, err := ioutil.ReadFile(opts.InputPath)
2928
if err != nil {
30-
return nil, cli.NewExitError(fmt.Sprintf("Error reading file: %s", err), codes.CouldNotReadInputFile)
29+
return nil, common.NewExitError(fmt.Sprintf("Error reading file: %s", err), codes.CouldNotReadInputFile)
3130
}
3231
var tree sops.Tree
3332
branch, err := opts.InputStore.Unmarshal(fileBytes)
3433
if err != nil {
35-
return nil, cli.NewExitError(fmt.Sprintf("Error unmarshalling file: %s", err), codes.CouldNotReadInputFile)
34+
return nil, common.NewExitError(fmt.Sprintf("Error unmarshalling file: %s", err), codes.CouldNotReadInputFile)
3635
}
3736
tree.Branch = branch
3837
tree.Metadata = sops.Metadata{
@@ -58,7 +57,7 @@ func encrypt(opts encryptOpts) (encryptedFile []byte, err error) {
5857

5958
encryptedFile, err = opts.OutputStore.MarshalWithMetadata(tree.Branch, tree.Metadata)
6059
if err != nil {
61-
return nil, cli.NewExitError(fmt.Sprintf("Could not marshal tree: %s", err), codes.ErrorDumpingTree)
60+
return nil, common.NewExitError(fmt.Sprintf("Could not marshal tree: %s", err), codes.ErrorDumpingTree)
6261
}
6362
return
6463
}

cmd/sops/main.go

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -226,10 +226,10 @@ func main() {
226226
Action: func(c *cli.Context) error {
227227
configPath, err := config.FindConfigFile(".")
228228
if err != nil {
229-
return cli.NewExitError(err, codes.ErrorGeneric)
229+
return common.NewExitError(err, codes.ErrorGeneric)
230230
}
231231
if c.NArg() < 1 {
232-
return cli.NewExitError("Error: no file specified", codes.NoFileSpecified)
232+
return common.NewExitError("Error: no file specified", codes.NoFileSpecified)
233233
}
234234
err = updatekeys.UpdateKeys(updatekeys.Opts{
235235
InputPath: c.Args()[0],
@@ -241,7 +241,7 @@ func main() {
241241
if cliErr, ok := err.(*cli.ExitError); ok && cliErr != nil {
242242
return cliErr
243243
} else if err != nil {
244-
return cli.NewExitError(err, codes.ErrorGeneric)
244+
return common.NewExitError(err, codes.ErrorGeneric)
245245
}
246246
return nil
247247
},
@@ -348,15 +348,15 @@ func main() {
348348

349349
app.Action = func(c *cli.Context) error {
350350
if c.NArg() < 1 {
351-
return cli.NewExitError("Error: no file specified", codes.NoFileSpecified)
351+
return common.NewExitError("Error: no file specified", codes.NoFileSpecified)
352352
}
353353
fileName := c.Args()[0]
354354
if _, err := os.Stat(fileName); os.IsNotExist(err) {
355355
if c.String("add-kms") != "" || c.String("add-pgp") != "" || c.String("add-gcp-kms") != "" || c.String("rm-kms") != "" || c.String("rm-pgp") != "" || c.String("rm-gcp-kms") != "" {
356-
return cli.NewExitError("Error: cannot add or remove keys on non-existent files, use `--kms` and `--pgp` instead.", 49)
356+
return common.NewExitError("Error: cannot add or remove keys on non-existent files, use `--kms` and `--pgp` instead.", 49)
357357
}
358358
if c.Bool("encrypt") || c.Bool("decrypt") || c.Bool("rotate") {
359-
return cli.NewExitError("Error: cannot operate on non-existent file", codes.NoFileSpecified)
359+
return common.NewExitError("Error: cannot operate on non-existent file", codes.NoFileSpecified)
360360
}
361361
}
362362

@@ -393,7 +393,7 @@ func main() {
393393
var extract []interface{}
394394
extract, err = parseTreePath(c.String("extract"))
395395
if err != nil {
396-
return cli.NewExitError(fmt.Errorf("error parsing --extract path: %s", err), codes.InvalidTreePathFormat)
396+
return common.NewExitError(fmt.Errorf("error parsing --extract path: %s", err), codes.InvalidTreePathFormat)
397397
}
398398
output, err = decrypt(decryptOpts{
399399
OutputStore: outputStore,
@@ -503,7 +503,7 @@ func main() {
503503
if c.Bool("in-place") || isEditMode || c.String("set") != "" {
504504
file, err := os.Create(fileName)
505505
if err != nil {
506-
return cli.NewExitError(fmt.Sprintf("Could not open in-place file for writing: %s", err), codes.CouldNotWriteOutputFile)
506+
return common.NewExitError(fmt.Sprintf("Could not open in-place file for writing: %s", err), codes.CouldNotWriteOutputFile)
507507
}
508508
defer file.Close()
509509
_, err = file.Write(output)
@@ -617,7 +617,7 @@ func keyGroups(c *cli.Context, file string) ([]sops.KeyGroup, error) {
617617
var cloudKmsKeys []keys.MasterKey
618618
kmsEncryptionContext := kms.ParseKMSContext(c.String("encryption-context"))
619619
if c.String("encryption-context") != "" && kmsEncryptionContext == nil {
620-
return nil, cli.NewExitError("Invalid KMS encryption context format", codes.ErrorInvalidKMSEncryptionContextFormat)
620+
return nil, common.NewExitError("Invalid KMS encryption context format", codes.ErrorInvalidKMSEncryptionContextFormat)
621621
}
622622
if c.String("kms") != "" {
623623
for _, k := range kms.MasterKeysFromArnString(c.String("kms"), kmsEncryptionContext) {
@@ -685,7 +685,7 @@ func jsonValueToTreeInsertableValue(jsonValue string) (interface{}, error) {
685685
var valueToInsert interface{}
686686
err := encodingjson.Unmarshal([]byte(jsonValue), &valueToInsert)
687687
if err != nil {
688-
return nil, cli.NewExitError("Value for --set is not valid JSON", codes.ErrorInvalidSetFormat)
688+
return nil, common.NewExitError("Value for --set is not valid JSON", codes.ErrorInvalidSetFormat)
689689
}
690690
// Check if decoding it as json we find a single value
691691
// and not a map or slice, in which case we can't marshal
@@ -695,7 +695,7 @@ func jsonValueToTreeInsertableValue(jsonValue string) (interface{}, error) {
695695
var err error
696696
valueToInsert, err = (&json.Store{}).Unmarshal([]byte(jsonValue))
697697
if err != nil {
698-
return nil, cli.NewExitError("Invalid --set value format", codes.ErrorInvalidSetFormat)
698+
return nil, common.NewExitError("Invalid --set value format", codes.ErrorInvalidSetFormat)
699699
}
700700
}
701701
return valueToInsert, nil
@@ -706,15 +706,15 @@ func extractSetArguments(set string) (path []interface{}, valueToInsert interfac
706706
// Since python-dict-index has to end with ], we split at "] " to get the two parts
707707
pathValuePair := strings.SplitAfterN(set, "] ", 2)
708708
if len(pathValuePair) < 2 {
709-
return nil, nil, cli.NewExitError("Invalid --set format", codes.ErrorInvalidSetFormat)
709+
return nil, nil, common.NewExitError("Invalid --set format", codes.ErrorInvalidSetFormat)
710710
}
711711
fullPath := strings.TrimRight(pathValuePair[0], " ")
712712
jsonValue := pathValuePair[1]
713713
valueToInsert, err = jsonValueToTreeInsertableValue(jsonValue)
714714

715715
path, err = parseTreePath(fullPath)
716716
if err != nil {
717-
return nil, nil, cli.NewExitError("Invalid --set format", codes.ErrorInvalidSetFormat)
717+
return nil, nil, common.NewExitError("Invalid --set format", codes.ErrorInvalidSetFormat)
718718
}
719719
return path, valueToInsert, nil
720720
}

cmd/sops/rotate.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import (
88
"go.mozilla.org/sops/cmd/sops/common"
99
"go.mozilla.org/sops/keys"
1010
"go.mozilla.org/sops/keyservice"
11-
cli "gopkg.in/urfave/cli.v1"
1211
)
1312

1413
type rotateOpts struct {
@@ -68,7 +67,7 @@ func rotate(opts rotateOpts) ([]byte, error) {
6867

6968
encryptedFile, err := opts.OutputStore.MarshalWithMetadata(tree.Branch, tree.Metadata)
7069
if err != nil {
71-
return nil, cli.NewExitError(fmt.Sprintf("Could not marshal tree: %s", err), codes.ErrorDumpingTree)
70+
return nil, common.NewExitError(fmt.Sprintf("Could not marshal tree: %s", err), codes.ErrorDumpingTree)
7271
}
7372
return encryptedFile, nil
7473
}

0 commit comments

Comments
 (0)