github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/service/saltpack.go (about)

     1  // Copyright 2015 Keybase, Inc. All rights reserved. Use of
     2  // this source code is governed by the included BSD license.
     3  
     4  package service
     5  
     6  import (
     7  	"archive/zip"
     8  	"bufio"
     9  	"bytes"
    10  	"errors"
    11  	"fmt"
    12  	"io"
    13  	"os"
    14  	"path/filepath"
    15  	"strings"
    16  	"time"
    17  
    18  	"github.com/keybase/client/go/chat/attachments/progress"
    19  	"github.com/keybase/client/go/chat/types"
    20  	"github.com/keybase/client/go/engine"
    21  	"github.com/keybase/client/go/libkb"
    22  	keybase1 "github.com/keybase/client/go/protocol/keybase1"
    23  	"github.com/keybase/client/go/saltpackkeys"
    24  	"github.com/keybase/go-framed-msgpack-rpc/rpc"
    25  	"golang.org/x/net/context"
    26  )
    27  
    28  const saltpackExtension = ".saltpack"
    29  const encryptedSuffix = ".encrypted"
    30  const signedSuffix = ".signed"
    31  const txtExtension = ".txt"
    32  const zipExtension = ".zip"
    33  const encryptedExtension = encryptedSuffix + saltpackExtension
    34  const signedExtension = signedSuffix + saltpackExtension
    35  const decryptedExtension = ".decrypted"
    36  const verifiedExtension = ".verified"
    37  const encryptedDirSuffix = zipExtension + encryptedSuffix
    38  const signedDirSuffix = zipExtension + signedSuffix
    39  
    40  type SaltpackHandler struct {
    41  	*BaseHandler
    42  	libkb.Contextified
    43  }
    44  
    45  type RemoteSaltpackUI struct {
    46  	sessionID int
    47  	cli       keybase1.SaltpackUiClient
    48  }
    49  
    50  func NewRemoteSaltpackUI(sessionID int, c *rpc.Client) *RemoteSaltpackUI {
    51  	return &RemoteSaltpackUI{
    52  		sessionID: sessionID,
    53  		cli:       keybase1.SaltpackUiClient{Cli: c},
    54  	}
    55  }
    56  
    57  func (r *RemoteSaltpackUI) SaltpackPromptForDecrypt(ctx context.Context, arg keybase1.SaltpackPromptForDecryptArg, usedDelegateUI bool) (err error) {
    58  	arg.SessionID = r.sessionID
    59  	arg.UsedDelegateUI = usedDelegateUI
    60  	return r.cli.SaltpackPromptForDecrypt(ctx, arg)
    61  }
    62  
    63  func (r *RemoteSaltpackUI) SaltpackVerifySuccess(ctx context.Context, arg keybase1.SaltpackVerifySuccessArg) (err error) {
    64  	arg.SessionID = r.sessionID
    65  	return r.cli.SaltpackVerifySuccess(ctx, arg)
    66  }
    67  
    68  func (r *RemoteSaltpackUI) SaltpackVerifyBadSender(ctx context.Context, arg keybase1.SaltpackVerifyBadSenderArg) (err error) {
    69  	arg.SessionID = r.sessionID
    70  	return r.cli.SaltpackVerifyBadSender(ctx, arg)
    71  }
    72  
    73  func NewSaltpackHandler(xp rpc.Transporter, g *libkb.GlobalContext) *SaltpackHandler {
    74  	return &SaltpackHandler{
    75  		BaseHandler:  NewBaseHandler(g, xp),
    76  		Contextified: libkb.NewContextified(g),
    77  	}
    78  }
    79  
    80  func (h *SaltpackHandler) SaltpackDecrypt(ctx context.Context, arg keybase1.SaltpackDecryptArg) (info keybase1.SaltpackEncryptedMessageInfo, err error) {
    81  	ctx = libkb.WithLogTag(ctx, "SP")
    82  	cli := h.getStreamUICli()
    83  	src := libkb.NewRemoteStreamBuffered(arg.Source, cli, arg.SessionID)
    84  	snk := libkb.NewRemoteStreamBuffered(arg.Sink, cli, arg.SessionID)
    85  	earg := &engine.SaltpackDecryptArg{
    86  		Sink:   snk,
    87  		Source: src,
    88  		Opts:   arg.Opts,
    89  	}
    90  
    91  	uis := libkb.UIs{
    92  		IdentifyUI: h.NewRemoteIdentifyUI(arg.SessionID, h.G()),
    93  		SecretUI:   h.getSecretUI(arg.SessionID, h.G()),
    94  		SaltpackUI: h.getSaltpackUI(arg.SessionID),
    95  		SessionID:  arg.SessionID,
    96  	}
    97  	m := libkb.NewMetaContext(ctx, h.G()).WithUIs(uis)
    98  	resolver := saltpackkeys.NewKeyPseudonymResolver(m)
    99  	eng := engine.NewSaltpackDecrypt(earg, resolver)
   100  	err = engine.RunEngine2(m, eng)
   101  	info = eng.MessageInfo()
   102  	return info, err
   103  }
   104  
   105  func (h *SaltpackHandler) SaltpackEncrypt(ctx context.Context, arg keybase1.SaltpackEncryptArg) (keybase1.SaltpackEncryptResult, error) {
   106  	ctx = libkb.WithLogTag(ctx, "SP")
   107  	cli := h.getStreamUICli()
   108  	src := libkb.NewRemoteStreamBuffered(arg.Source, cli, arg.SessionID)
   109  	snk := libkb.NewRemoteStreamBuffered(arg.Sink, cli, arg.SessionID)
   110  	earg := &engine.SaltpackEncryptArg{
   111  		Opts:   arg.Opts,
   112  		Sink:   snk,
   113  		Source: src,
   114  	}
   115  
   116  	uis := libkb.UIs{
   117  		IdentifyUI: h.NewRemoteIdentifyUI(arg.SessionID, h.G()),
   118  		SecretUI:   h.getSecretUI(arg.SessionID, h.G()),
   119  		SessionID:  arg.SessionID,
   120  	}
   121  
   122  	keyfinderHook := saltpackkeys.NewRecipientKeyfinderEngineHook(arg.Opts.UseKBFSKeysOnlyForTesting)
   123  
   124  	eng := engine.NewSaltpackEncrypt(earg, keyfinderHook)
   125  	m := libkb.NewMetaContext(ctx, h.G()).WithUIs(uis)
   126  	if err := engine.RunEngine2(m, eng); err != nil {
   127  		return keybase1.SaltpackEncryptResult{}, err
   128  	}
   129  
   130  	return keybase1.SaltpackEncryptResult{
   131  		UsedUnresolvedSBS:      eng.UsedSBS,
   132  		UnresolvedSBSAssertion: eng.SBSAssertion,
   133  	}, nil
   134  }
   135  
   136  func (h *SaltpackHandler) SaltpackSign(ctx context.Context, arg keybase1.SaltpackSignArg) error {
   137  	ctx = libkb.WithLogTag(ctx, "SP")
   138  	cli := h.getStreamUICli()
   139  	src := libkb.NewRemoteStreamBuffered(arg.Source, cli, arg.SessionID)
   140  	snk := libkb.NewRemoteStreamBuffered(arg.Sink, cli, arg.SessionID)
   141  	earg := &engine.SaltpackSignArg{
   142  		Opts:   arg.Opts,
   143  		Sink:   snk,
   144  		Source: src,
   145  	}
   146  
   147  	uis := libkb.UIs{
   148  		IdentifyUI: h.NewRemoteIdentifyUI(arg.SessionID, h.G()),
   149  		SecretUI:   h.getSecretUI(arg.SessionID, h.G()),
   150  		SessionID:  arg.SessionID,
   151  	}
   152  	m := libkb.NewMetaContext(ctx, h.G()).WithUIs(uis)
   153  	eng := engine.NewSaltpackSign(h.G(), earg)
   154  	return engine.RunEngine2(m, eng)
   155  }
   156  
   157  func (h *SaltpackHandler) SaltpackVerify(ctx context.Context, arg keybase1.SaltpackVerifyArg) error {
   158  	ctx = libkb.WithLogTag(ctx, "SP")
   159  	cli := h.getStreamUICli()
   160  	src := libkb.NewRemoteStreamBuffered(arg.Source, cli, arg.SessionID)
   161  	snk := libkb.NewRemoteStreamBuffered(arg.Sink, cli, arg.SessionID)
   162  	earg := &engine.SaltpackVerifyArg{
   163  		Opts:   arg.Opts,
   164  		Sink:   snk,
   165  		Source: src,
   166  	}
   167  
   168  	uis := libkb.UIs{
   169  		IdentifyUI: h.NewRemoteIdentifyUI(arg.SessionID, h.G()),
   170  		SecretUI:   h.getSecretUI(arg.SessionID, h.G()),
   171  		SaltpackUI: h.getSaltpackUI(arg.SessionID),
   172  		SessionID:  arg.SessionID,
   173  	}
   174  	eng := engine.NewSaltpackVerify(h.G(), earg)
   175  	m := libkb.NewMetaContext(ctx, h.G()).WithUIs(uis)
   176  	return engine.RunEngine2(m, eng)
   177  }
   178  
   179  // frontend handlers:
   180  
   181  func (h *SaltpackHandler) SaltpackEncryptString(ctx context.Context, arg keybase1.SaltpackEncryptStringArg) (keybase1.SaltpackEncryptStringResult, error) {
   182  	ctx = libkb.WithLogTag(ctx, "SP")
   183  	eopts := h.encryptOptions(arg.Opts)
   184  	eopts.NoForcePoll = true
   185  	return h.encryptString(ctx, arg.SessionID, arg.Plaintext, eopts)
   186  }
   187  
   188  func (h *SaltpackHandler) SaltpackEncryptStringToTextFile(ctx context.Context, arg keybase1.SaltpackEncryptStringToTextFileArg) (r keybase1.SaltpackEncryptFileResult, err error) {
   189  	defer transformSaltpackError(&err)
   190  	ctx = libkb.WithLogTag(ctx, "SP")
   191  	eopts := h.encryptOptions(arg.Opts)
   192  	res, err := h.encryptString(ctx, arg.SessionID, arg.Plaintext, eopts)
   193  	if err != nil {
   194  		return keybase1.SaltpackEncryptFileResult{}, err
   195  	}
   196  	filename, err := h.writeStringToFile(ctx, res.Ciphertext, encryptedExtension)
   197  	if err != nil {
   198  		return keybase1.SaltpackEncryptFileResult{}, err
   199  	}
   200  	fres := keybase1.SaltpackEncryptFileResult{
   201  		UsedUnresolvedSBS:      res.UsedUnresolvedSBS,
   202  		UnresolvedSBSAssertion: res.UnresolvedSBSAssertion,
   203  		Filename:               filename,
   204  	}
   205  	return fres, nil
   206  }
   207  
   208  func (h *SaltpackHandler) encryptString(ctx context.Context, sessionID int, plaintext string, eopts keybase1.SaltpackEncryptOptions) (keybase1.SaltpackEncryptStringResult, error) {
   209  	sink := libkb.NewBufferCloser()
   210  	earg := &engine.SaltpackEncryptArg{
   211  		Opts:   eopts,
   212  		Sink:   sink,
   213  		Source: strings.NewReader(plaintext),
   214  	}
   215  
   216  	usedSBS, assertion, err := h.frontendEncrypt(ctx, sessionID, earg)
   217  	if err != nil {
   218  		return keybase1.SaltpackEncryptStringResult{}, err
   219  	}
   220  
   221  	return keybase1.SaltpackEncryptStringResult{
   222  		UsedUnresolvedSBS:      usedSBS,
   223  		UnresolvedSBSAssertion: assertion,
   224  		Ciphertext:             sink.String(),
   225  	}, nil
   226  }
   227  
   228  func (h *SaltpackHandler) SaltpackDecryptString(ctx context.Context, arg keybase1.SaltpackDecryptStringArg) (keybase1.SaltpackPlaintextResult, error) {
   229  	ctx = libkb.WithLogTag(ctx, "SP")
   230  	sink := libkb.NewBufferCloser()
   231  	earg := &engine.SaltpackDecryptArg{
   232  		Sink:   sink,
   233  		Source: strings.NewReader(arg.Ciphertext),
   234  	}
   235  
   236  	info, signed, err := h.frontendDecrypt(ctx, arg.SessionID, earg)
   237  	if err != nil {
   238  		return keybase1.SaltpackPlaintextResult{}, err
   239  	}
   240  	r := keybase1.SaltpackPlaintextResult{
   241  		Info:      info,
   242  		Plaintext: sink.String(),
   243  		Signed:    signed,
   244  	}
   245  	return r, nil
   246  }
   247  
   248  func (h *SaltpackHandler) SaltpackSignString(ctx context.Context, arg keybase1.SaltpackSignStringArg) (string, error) {
   249  	ctx = libkb.WithLogTag(ctx, "SP")
   250  	return h.signString(ctx, arg.SessionID, arg.Plaintext)
   251  }
   252  
   253  func (h *SaltpackHandler) SaltpackSignStringToTextFile(ctx context.Context, arg keybase1.SaltpackSignStringToTextFileArg) (s string, err error) {
   254  	defer transformSaltpackError(&err)
   255  	ctx = libkb.WithLogTag(ctx, "SP")
   256  	signed, err := h.signString(ctx, arg.SessionID, arg.Plaintext)
   257  	if err != nil {
   258  		return "", err
   259  	}
   260  
   261  	return h.writeStringToFile(ctx, signed, signedExtension)
   262  }
   263  
   264  func (h *SaltpackHandler) signString(ctx context.Context, sessionID int, plaintext string) (string, error) {
   265  	sink := libkb.NewBufferCloser()
   266  	earg := &engine.SaltpackSignArg{
   267  		Sink:   sink,
   268  		Source: io.NopCloser(bytes.NewBufferString(plaintext)),
   269  	}
   270  
   271  	if err := h.frontendSign(ctx, sessionID, earg); err != nil {
   272  		return "", err
   273  	}
   274  
   275  	return sink.String(), nil
   276  }
   277  
   278  func (h *SaltpackHandler) SaltpackVerifyString(ctx context.Context, arg keybase1.SaltpackVerifyStringArg) (keybase1.SaltpackVerifyResult, error) {
   279  	ctx = libkb.WithLogTag(ctx, "SP")
   280  	sink := libkb.NewBufferCloser()
   281  	earg := &engine.SaltpackVerifyArg{
   282  		Sink:   sink,
   283  		Source: strings.NewReader(arg.SignedMsg),
   284  	}
   285  
   286  	spui, err := h.frontendVerify(ctx, arg.SessionID, earg)
   287  	if err != nil {
   288  		return keybase1.SaltpackVerifyResult{}, err
   289  	}
   290  	res := keybase1.SaltpackVerifyResult{
   291  		Plaintext: sink.String(),
   292  		Verified:  spui.verified,
   293  	}
   294  	if spui.signingKID != nil {
   295  		res.SigningKID = *spui.signingKID
   296  	}
   297  	if spui.sender != nil {
   298  		res.Sender = *spui.sender
   299  	}
   300  	return res, nil
   301  }
   302  
   303  func (h *SaltpackHandler) SaltpackEncryptFile(ctx context.Context, arg keybase1.SaltpackEncryptFileArg) (r keybase1.SaltpackEncryptFileResult, err error) {
   304  	defer transformSaltpackError(&err)
   305  	ctx = libkb.WithLogTag(ctx, "SP")
   306  	dir, err := isDir(arg.Filename)
   307  	if err != nil {
   308  		return keybase1.SaltpackEncryptFileResult{}, err
   309  	}
   310  
   311  	if dir {
   312  		return h.saltpackEncryptDirectory(ctx, arg)
   313  	}
   314  	return h.saltpackEncryptFile(ctx, arg)
   315  }
   316  
   317  func (h *SaltpackHandler) saltpackEncryptFile(ctx context.Context, arg keybase1.SaltpackEncryptFileArg) (keybase1.SaltpackEncryptFileResult, error) {
   318  	sf, err := newSourceFile(h.G(), keybase1.SaltpackOperationType_ENCRYPT, arg.Filename)
   319  	if err != nil {
   320  		return keybase1.SaltpackEncryptFileResult{}, err
   321  	}
   322  	defer sf.Close()
   323  
   324  	outFilename, bw, err := boxFilename(arg.Filename, encryptedSuffix, arg.DestinationDir)
   325  	if err != nil {
   326  		return keybase1.SaltpackEncryptFileResult{}, err
   327  	}
   328  	defer bw.Close()
   329  
   330  	opts := h.encryptOptions(arg.Opts)
   331  	opts.Binary = true
   332  	opts.UseDeviceKeys = true
   333  
   334  	earg := &engine.SaltpackEncryptArg{
   335  		Opts:   opts,
   336  		Sink:   bw,
   337  		Source: sf,
   338  	}
   339  
   340  	usedSBS, assertion, err := h.frontendEncrypt(ctx, arg.SessionID, earg)
   341  	if err != nil {
   342  		h.G().Log.Debug("encrypt error, so removing output file")
   343  		if clErr := bw.Close(); clErr != nil {
   344  			transformSaltpackError(&clErr)
   345  			h.G().Log.Debug("error closing bw for output file: %s", clErr)
   346  		}
   347  		if rmErr := os.Remove(outFilename); rmErr != nil {
   348  			transformSaltpackError(&rmErr)
   349  			h.G().Log.Debug("error removing output file: %s", rmErr)
   350  		}
   351  		return keybase1.SaltpackEncryptFileResult{}, err
   352  	}
   353  
   354  	return keybase1.SaltpackEncryptFileResult{
   355  		UsedUnresolvedSBS:      usedSBS,
   356  		UnresolvedSBSAssertion: assertion,
   357  		Filename:               outFilename,
   358  	}, nil
   359  }
   360  
   361  func (h *SaltpackHandler) saltpackEncryptDirectory(ctx context.Context, arg keybase1.SaltpackEncryptFileArg) (keybase1.SaltpackEncryptFileResult, error) {
   362  	h.G().Log.Debug("encrypting directory")
   363  
   364  	if filepath.Clean(arg.Filename) == filepath.Clean(arg.DestinationDir) {
   365  		return keybase1.SaltpackEncryptFileResult{}, errors.New("source and destination directories cannot be the same")
   366  	}
   367  
   368  	h.G().NotifyRouter.HandleSaltpackOperationStart(ctx, keybase1.SaltpackOperationType_ENCRYPT, arg.Filename)
   369  	defer h.G().NotifyRouter.HandleSaltpackOperationDone(ctx, keybase1.SaltpackOperationType_ENCRYPT, arg.Filename)
   370  
   371  	progReporter := func(bytesComplete, bytesTotal int64) {
   372  		h.G().NotifyRouter.HandleSaltpackOperationProgress(ctx, keybase1.SaltpackOperationType_ENCRYPT, arg.Filename, bytesComplete, bytesTotal)
   373  	}
   374  	prog, err := newDirProgressWriter(progReporter, arg.Filename)
   375  	if err != nil {
   376  		return keybase1.SaltpackEncryptFileResult{}, err
   377  	}
   378  
   379  	pipeRead := zipDir(arg.Filename, prog)
   380  
   381  	outFilename, bw, err := boxFilename(arg.Filename, encryptedDirSuffix, arg.DestinationDir)
   382  	if err != nil {
   383  		return keybase1.SaltpackEncryptFileResult{}, err
   384  	}
   385  	defer bw.Close()
   386  
   387  	opts := h.encryptOptions(arg.Opts)
   388  	opts.Binary = true
   389  	opts.UseDeviceKeys = true
   390  
   391  	earg := &engine.SaltpackEncryptArg{
   392  		Opts:   opts,
   393  		Sink:   bw,
   394  		Source: pipeRead,
   395  	}
   396  
   397  	usedSBS, assertion, err := h.frontendEncrypt(ctx, arg.SessionID, earg)
   398  	if err != nil {
   399  		h.G().Log.Debug("encrypt error, so removing output file")
   400  		if clErr := bw.Close(); clErr != nil {
   401  			transformSaltpackError(&clErr)
   402  			h.G().Log.Debug("error closing bw for output file: %s", clErr)
   403  		}
   404  		if rmErr := os.Remove(outFilename); rmErr != nil {
   405  			transformSaltpackError(&rmErr)
   406  			h.G().Log.Debug("error removing output file: %s", rmErr)
   407  		}
   408  		return keybase1.SaltpackEncryptFileResult{}, err
   409  	}
   410  
   411  	return keybase1.SaltpackEncryptFileResult{
   412  		UsedUnresolvedSBS:      usedSBS,
   413  		UnresolvedSBSAssertion: assertion,
   414  		Filename:               outFilename,
   415  	}, nil
   416  }
   417  
   418  func (h *SaltpackHandler) SaltpackDecryptFile(ctx context.Context, arg keybase1.SaltpackDecryptFileArg) (r keybase1.SaltpackFileResult, err error) {
   419  	defer transformSaltpackError(&err)
   420  	ctx = libkb.WithLogTag(ctx, "SP")
   421  	sf, err := newSourceFile(h.G(), keybase1.SaltpackOperationType_DECRYPT, arg.EncryptedFilename)
   422  	if err != nil {
   423  		return keybase1.SaltpackFileResult{}, err
   424  	}
   425  	defer sf.Close()
   426  
   427  	outFilename, bw, err := unboxFilename(arg.EncryptedFilename, decryptedExtension, arg.DestinationDir)
   428  	if err != nil {
   429  		return keybase1.SaltpackFileResult{}, err
   430  	}
   431  	defer bw.Close()
   432  
   433  	earg := &engine.SaltpackDecryptArg{
   434  		Sink:   bw,
   435  		Source: sf,
   436  	}
   437  
   438  	info, signed, err := h.frontendDecrypt(ctx, arg.SessionID, earg)
   439  	if err != nil {
   440  		h.G().Log.Debug("decrypt error, so removing output file")
   441  		if clErr := bw.Close(); clErr != nil {
   442  			transformSaltpackError(&clErr)
   443  			h.G().Log.Debug("error closing bw for output file: %s", clErr)
   444  		}
   445  		if rmErr := os.Remove(outFilename); rmErr != nil {
   446  			transformSaltpackError(&rmErr)
   447  			h.G().Log.Debug("error removing output file: %s", rmErr)
   448  		}
   449  		return keybase1.SaltpackFileResult{}, err
   450  	}
   451  
   452  	r = keybase1.SaltpackFileResult{
   453  		Info:              info,
   454  		DecryptedFilename: outFilename,
   455  		Signed:            signed,
   456  	}
   457  	return r, nil
   458  }
   459  
   460  func (h *SaltpackHandler) SaltpackSignFile(ctx context.Context, arg keybase1.SaltpackSignFileArg) (s string, err error) {
   461  	defer transformSaltpackError(&err)
   462  	ctx = libkb.WithLogTag(ctx, "SP")
   463  	dir, err := isDir(arg.Filename)
   464  	if err != nil {
   465  		return "", err
   466  	}
   467  
   468  	if dir {
   469  		return h.saltpackSignDirectory(ctx, arg)
   470  	}
   471  	return h.saltpackSignFile(ctx, arg)
   472  }
   473  
   474  func (h *SaltpackHandler) saltpackSignFile(ctx context.Context, arg keybase1.SaltpackSignFileArg) (string, error) {
   475  	sf, err := newSourceFile(h.G(), keybase1.SaltpackOperationType_SIGN, arg.Filename)
   476  	if err != nil {
   477  		return "", err
   478  	}
   479  	defer sf.Close()
   480  
   481  	outFilename, bw, err := boxFilename(arg.Filename, signedSuffix, arg.DestinationDir)
   482  	if err != nil {
   483  		return "", err
   484  	}
   485  	defer bw.Close()
   486  
   487  	earg := &engine.SaltpackSignArg{
   488  		Sink:   bw,
   489  		Source: sf,
   490  		Opts: keybase1.SaltpackSignOptions{
   491  			Binary: true,
   492  		},
   493  	}
   494  
   495  	if err := h.frontendSign(ctx, arg.SessionID, earg); err != nil {
   496  		h.G().Log.Debug("sign error, so removing output file")
   497  		if clErr := bw.Close(); clErr != nil {
   498  			transformSaltpackError(&clErr)
   499  			h.G().Log.Debug("error closing bw for output file: %s", clErr)
   500  		}
   501  		if rmErr := os.Remove(outFilename); rmErr != nil {
   502  			transformSaltpackError(&rmErr)
   503  			h.G().Log.Debug("error removing output file: %s", rmErr)
   504  		}
   505  		return "", err
   506  	}
   507  
   508  	return outFilename, nil
   509  }
   510  
   511  func (h *SaltpackHandler) saltpackSignDirectory(ctx context.Context, arg keybase1.SaltpackSignFileArg) (string, error) {
   512  	h.G().Log.Debug("signing directory")
   513  
   514  	if filepath.Clean(arg.Filename) == filepath.Clean(arg.DestinationDir) {
   515  		return "", errors.New("source and destination directories cannot be the same")
   516  	}
   517  
   518  	h.G().NotifyRouter.HandleSaltpackOperationStart(ctx, keybase1.SaltpackOperationType_SIGN, arg.Filename)
   519  	defer h.G().NotifyRouter.HandleSaltpackOperationDone(ctx, keybase1.SaltpackOperationType_ENCRYPT, arg.Filename)
   520  
   521  	progReporter := func(bytesComplete, bytesTotal int64) {
   522  		h.G().NotifyRouter.HandleSaltpackOperationProgress(ctx, keybase1.SaltpackOperationType_SIGN, arg.Filename, bytesComplete, bytesTotal)
   523  	}
   524  
   525  	prog, err := newDirProgressWriter(progReporter, arg.Filename)
   526  	if err != nil {
   527  		return "", err
   528  	}
   529  
   530  	pipeRead := zipDir(arg.Filename, prog)
   531  
   532  	outFilename, bw, err := boxFilename(arg.Filename, signedDirSuffix, arg.DestinationDir)
   533  	if err != nil {
   534  		return "", err
   535  	}
   536  	defer bw.Close()
   537  
   538  	earg := &engine.SaltpackSignArg{
   539  		Sink:   bw,
   540  		Source: pipeRead,
   541  		Opts: keybase1.SaltpackSignOptions{
   542  			Binary: true,
   543  		},
   544  	}
   545  
   546  	if err := h.frontendSign(ctx, arg.SessionID, earg); err != nil {
   547  		h.G().Log.Debug("sign error, so removing output file")
   548  		if clErr := bw.Close(); clErr != nil {
   549  			transformSaltpackError(&clErr)
   550  			h.G().Log.Debug("error closing bw for output file: %s", clErr)
   551  		}
   552  		if rmErr := os.Remove(outFilename); rmErr != nil {
   553  			transformSaltpackError(&rmErr)
   554  			h.G().Log.Debug("error removing output file: %s", rmErr)
   555  		}
   556  		return "", err
   557  	}
   558  
   559  	return outFilename, nil
   560  }
   561  
   562  func (h *SaltpackHandler) SaltpackVerifyFile(ctx context.Context, arg keybase1.SaltpackVerifyFileArg) (r keybase1.SaltpackVerifyFileResult, err error) {
   563  	defer transformSaltpackError(&err)
   564  	ctx = libkb.WithLogTag(ctx, "SP")
   565  	sf, err := newSourceFile(h.G(), keybase1.SaltpackOperationType_VERIFY, arg.SignedFilename)
   566  	if err != nil {
   567  		return keybase1.SaltpackVerifyFileResult{}, err
   568  	}
   569  	defer sf.Close()
   570  
   571  	outFilename, bw, err := unboxFilename(arg.SignedFilename, verifiedExtension, arg.DestinationDir)
   572  	if err != nil {
   573  		return keybase1.SaltpackVerifyFileResult{}, err
   574  	}
   575  	defer bw.Close()
   576  
   577  	earg := &engine.SaltpackVerifyArg{
   578  		Sink:   bw,
   579  		Source: sf,
   580  	}
   581  
   582  	spui, err := h.frontendVerify(ctx, arg.SessionID, earg)
   583  	if err != nil {
   584  		h.G().Log.Debug("verify error, so removing ouput file")
   585  		if clErr := bw.Close(); clErr != nil {
   586  			transformSaltpackError(&clErr)
   587  			h.G().Log.Debug("error closing bw for output file: %s", clErr)
   588  		}
   589  		if rmErr := os.Remove(outFilename); rmErr != nil {
   590  			transformSaltpackError(&rmErr)
   591  			h.G().Log.Debug("error removing output file: %s", rmErr)
   592  		}
   593  		return keybase1.SaltpackVerifyFileResult{}, err
   594  	}
   595  	res := keybase1.SaltpackVerifyFileResult{
   596  		VerifiedFilename: outFilename,
   597  		Verified:         spui.verified,
   598  	}
   599  	if spui.signingKID != nil {
   600  		res.SigningKID = *spui.signingKID
   601  	}
   602  	if spui.sender != nil {
   603  		res.Sender = *spui.sender
   604  	}
   605  	return res, nil
   606  }
   607  
   608  func (h *SaltpackHandler) SaltpackSaveCiphertextToFile(ctx context.Context, arg keybase1.SaltpackSaveCiphertextToFileArg) (s string, err error) {
   609  	defer transformSaltpackError(&err)
   610  	ctx = libkb.WithLogTag(ctx, "SP")
   611  	return h.writeStringToFile(ctx, arg.Ciphertext, txtExtension+encryptedExtension)
   612  }
   613  
   614  func (h *SaltpackHandler) SaltpackSaveSignedMsgToFile(ctx context.Context, arg keybase1.SaltpackSaveSignedMsgToFileArg) (s string, err error) {
   615  	defer transformSaltpackError(&err)
   616  	ctx = libkb.WithLogTag(ctx, "SP")
   617  	return h.writeStringToFile(ctx, arg.SignedMsg, txtExtension+signedExtension)
   618  }
   619  
   620  func (h *SaltpackHandler) encryptOptions(opts keybase1.SaltpackFrontendEncryptOptions) keybase1.SaltpackEncryptOptions {
   621  	auth := keybase1.AuthenticityType_REPUDIABLE
   622  	if opts.Signed {
   623  		auth = keybase1.AuthenticityType_SIGNED
   624  	}
   625  	return keybase1.SaltpackEncryptOptions{
   626  		Recipients:       opts.Recipients,
   627  		AuthenticityType: auth,
   628  		NoSelfEncrypt:    !opts.IncludeSelf,
   629  		UseEntityKeys:    true,
   630  		UsePaperKeys:     true,
   631  	}
   632  }
   633  
   634  func (h *SaltpackHandler) frontendEncrypt(ctx context.Context, sessionID int, arg *engine.SaltpackEncryptArg) (bool, string, error) {
   635  	uis := libkb.UIs{
   636  		SecretUI:  &nopSecretUI{},
   637  		SessionID: sessionID,
   638  	}
   639  
   640  	keyfinderHook := saltpackkeys.NewRecipientKeyfinderEngineHook(false)
   641  
   642  	eng := engine.NewSaltpackEncrypt(arg, keyfinderHook)
   643  	m := libkb.NewMetaContext(ctx, h.G()).WithUIs(uis)
   644  
   645  	err := engine.RunEngine2(m, eng)
   646  	return eng.UsedSBS, eng.SBSAssertion, err
   647  }
   648  
   649  func (h *SaltpackHandler) frontendDecrypt(ctx context.Context, sessionID int, arg *engine.SaltpackDecryptArg) (keybase1.SaltpackEncryptedMessageInfo, bool, error) {
   650  	spui := &capSaltpackUI{}
   651  	uis := libkb.UIs{
   652  		IdentifyUI: h.NewRemoteIdentifyUI(sessionID, h.G()),
   653  		SecretUI:   &nopSecretUI{},
   654  		SaltpackUI: spui,
   655  		SessionID:  sessionID,
   656  	}
   657  	m := libkb.NewMetaContext(ctx, h.G()).WithUIs(uis)
   658  	resolver := saltpackkeys.NewKeyPseudonymResolver(m)
   659  	eng := engine.NewSaltpackDecrypt(arg, resolver)
   660  	if err := engine.RunEngine2(m, eng); err != nil {
   661  		return keybase1.SaltpackEncryptedMessageInfo{}, false, err
   662  	}
   663  	return eng.MessageInfo(), spui.verified, nil
   664  }
   665  
   666  func (h *SaltpackHandler) frontendVerify(ctx context.Context, sessionID int, arg *engine.SaltpackVerifyArg) (*capSaltpackUI, error) {
   667  	spui := &capSaltpackUI{}
   668  	uis := libkb.UIs{
   669  		IdentifyUI: h.NewRemoteIdentifyUI(sessionID, h.G()),
   670  		SecretUI:   &nopSecretUI{},
   671  		SaltpackUI: spui,
   672  		SessionID:  sessionID,
   673  	}
   674  	eng := engine.NewSaltpackVerify(h.G(), arg)
   675  	m := libkb.NewMetaContext(ctx, h.G()).WithUIs(uis)
   676  	if err := engine.RunEngine2(m, eng); err != nil {
   677  		return nil, err
   678  	}
   679  	return spui, nil
   680  }
   681  
   682  func (h *SaltpackHandler) frontendSign(ctx context.Context, sessionID int, arg *engine.SaltpackSignArg) error {
   683  	uis := libkb.UIs{
   684  		SecretUI:  &nopSecretUI{},
   685  		SessionID: sessionID,
   686  	}
   687  	m := libkb.NewMetaContext(ctx, h.G()).WithUIs(uis)
   688  	eng := engine.NewSaltpackSign(h.G(), arg)
   689  	return engine.RunEngine2(m, eng)
   690  }
   691  
   692  func boxFilename(inFilename, suffix, destinationDir string) (string, *libkb.BufferWriter, error) {
   693  	dir, file := filepath.Split(inFilename)
   694  	if destinationDir != "" {
   695  		dir = destinationDir
   696  	}
   697  	withExt := filepath.Join(dir, file+suffix+saltpackExtension)
   698  	f, err := os.Create(withExt)
   699  	if err != nil {
   700  		return "", nil, err
   701  	}
   702  	return withExt, libkb.NewBufferWriter(f), nil
   703  }
   704  
   705  // unboxFilename takes a filename and creates a new file where the result
   706  // of decrypt or verify can go.
   707  //
   708  // If inFilename ends in .encrypted.saltpack or .signed.saltpack, the result
   709  // filename will just have that stripped off.  If a file without that extension
   710  // already exists, it will look for a file with <name>` (n)`.<extension> for
   711  // n = 1..99 that doesn't exist.
   712  //
   713  // If inFilename doesn't have a saltpack suffix, it uses the suffix that is passed
   714  // in, so it would be <filename>.decrypted or <filename>.verified.
   715  //
   716  // If destinationDir is empty, it will use the directory of inFilename.
   717  func unboxFilename(inFilename, suffix, destinationDir string) (string, *libkb.BufferWriter, error) {
   718  	dir, file := filepath.Split(inFilename)
   719  	if destinationDir != "" {
   720  		dir = destinationDir
   721  	}
   722  	// default desired filename is the input filename plus the suffix.
   723  	desiredFilename := file + suffix
   724  
   725  	// if the input filename ends in .encrypted.saltpack or .signed.saltpack,
   726  	// strip that off and use that instead.
   727  	if strings.HasSuffix(file, encryptedExtension) {
   728  		desiredFilename = strings.TrimSuffix(file, encryptedExtension)
   729  	} else if strings.HasSuffix(file, signedExtension) {
   730  		desiredFilename = strings.TrimSuffix(file, signedExtension)
   731  	}
   732  
   733  	finalPath := filepath.Join(dir, desiredFilename)
   734  
   735  	var found bool
   736  	for i := 0; i < 100; i++ {
   737  		possible := finalPath
   738  		if i > 0 {
   739  			// after the first time through, add a (i) to the filename
   740  			// to try to find one that doesn't exist
   741  			ext := filepath.Ext(possible)
   742  			possible = fmt.Sprintf("%s (%d)%s", strings.TrimSuffix(possible, ext), i, ext)
   743  		}
   744  		exists, err := libkb.FileExists(possible)
   745  		if err != nil {
   746  			return "", nil, err
   747  		}
   748  		if !exists {
   749  			// found a filename that doesn't exist
   750  			found = true
   751  			finalPath = possible
   752  			break
   753  		}
   754  	}
   755  
   756  	if !found {
   757  		// after 100 attempts, no file found that wouldn't overwrite an existing
   758  		// file, so bail out.
   759  		return "", nil, errors.New("could not create output file without overwriting existing file")
   760  	}
   761  
   762  	// finalPath contains a filename that doesn't exist, so make the file
   763  	f, err := os.Create(finalPath)
   764  	if err != nil {
   765  		return "", nil, err
   766  	}
   767  	return finalPath, libkb.NewBufferWriter(f), nil
   768  }
   769  
   770  func (h *SaltpackHandler) writeStringToFile(ctx context.Context, contents, suffix string) (filename string, err error) {
   771  	dir := h.G().Env.GetDownloadsDir()
   772  	if err := os.MkdirAll(dir, libkb.PermDir); err != nil {
   773  		return "", err
   774  	}
   775  	tmpfile, err := os.CreateTemp(dir, "keybase_*"+suffix)
   776  	if err != nil {
   777  		return "", err
   778  	}
   779  	if _, err = tmpfile.Write([]byte(contents)); err != nil {
   780  		_ = tmpfile.Close()
   781  		return "", err
   782  	}
   783  	if err := tmpfile.Close(); err != nil {
   784  		return "", err
   785  	}
   786  
   787  	return tmpfile.Name(), nil
   788  }
   789  
   790  // nopSecretUI returns an error if it is ever called.
   791  // A lot of these saltpack engines say they require a secret UI.
   792  // They really don't, but it's dangerous to try to strip it out.
   793  type nopSecretUI struct{}
   794  
   795  func (n *nopSecretUI) GetPassphrase(pinentry keybase1.GUIEntryArg, terminal *keybase1.SecretEntryArg) (keybase1.GetPassphraseRes, error) {
   796  	return keybase1.GetPassphraseRes{}, errors.New("GetPassphrase called unexpectedly")
   797  }
   798  
   799  // capSaltpackUI captures the various sender info so the RPCs can just return that
   800  // directly to the caller instead of via a UI.
   801  type capSaltpackUI struct {
   802  	decryptArg *keybase1.SaltpackPromptForDecryptArg
   803  	signingKID *keybase1.KID
   804  	sender     *keybase1.SaltpackSender
   805  	verified   bool
   806  }
   807  
   808  func (c *capSaltpackUI) SaltpackPromptForDecrypt(ctx context.Context, arg keybase1.SaltpackPromptForDecryptArg, _ bool) error {
   809  	c.decryptArg = &arg
   810  	c.verified = arg.Signed
   811  	return nil
   812  }
   813  
   814  func (c *capSaltpackUI) SaltpackVerifySuccess(ctx context.Context, arg keybase1.SaltpackVerifySuccessArg) error {
   815  	c.signingKID = &arg.SigningKID
   816  	c.sender = &arg.Sender
   817  	c.verified = true
   818  	return nil
   819  }
   820  
   821  func (c *capSaltpackUI) SaltpackVerifyBadSender(ctx context.Context, arg keybase1.SaltpackVerifyBadSenderArg) error {
   822  	c.signingKID = &arg.SigningKID
   823  	c.sender = &arg.Sender
   824  	c.verified = false
   825  	return nil
   826  }
   827  
   828  type sourceFile struct {
   829  	filename string
   830  	op       keybase1.SaltpackOperationType
   831  	f        *os.File
   832  	r        io.Reader
   833  	prog     *progress.ProgressWriter
   834  	libkb.Contextified
   835  }
   836  
   837  func newSourceFile(g *libkb.GlobalContext, op keybase1.SaltpackOperationType, filename string) (*sourceFile, error) {
   838  	s, err := os.Stat(filename)
   839  	if err != nil {
   840  		return nil, err
   841  	}
   842  	f, err := os.Open(filename)
   843  	if err != nil {
   844  		return nil, err
   845  	}
   846  	sf := &sourceFile{
   847  		filename:     filename,
   848  		op:           op,
   849  		f:            f,
   850  		Contextified: libkb.NewContextified(g),
   851  	}
   852  	sf.G().NotifyRouter.HandleSaltpackOperationStart(context.Background(), sf.op, sf.filename)
   853  	sf.prog = progress.NewProgressWriterWithUpdateDuration(sf.reporter, s.Size(), 80*time.Millisecond)
   854  	sf.r = io.TeeReader(bufio.NewReader(f), sf.prog)
   855  	return sf, nil
   856  }
   857  
   858  func (sf *sourceFile) Read(p []byte) (n int, err error) {
   859  	return sf.r.Read(p)
   860  }
   861  
   862  func (sf *sourceFile) Close() error {
   863  	sf.G().NotifyRouter.HandleSaltpackOperationDone(context.Background(), sf.op, sf.filename)
   864  	sf.prog.Finish()
   865  	return sf.f.Close()
   866  }
   867  
   868  func (sf *sourceFile) reporter(bytesComplete, bytesTotal int64) {
   869  	sf.G().NotifyRouter.HandleSaltpackOperationProgress(context.Background(), sf.op, sf.filename, bytesComplete, bytesTotal)
   870  }
   871  
   872  func isDir(filename string) (bool, error) {
   873  	s, err := os.Stat(filename)
   874  	if err != nil {
   875  		return false, err
   876  	}
   877  	return s.IsDir(), nil
   878  }
   879  
   880  func dirTotalSize(filename string) (int64, error) {
   881  	var total int64
   882  	err := filepath.Walk(filename, func(path string, info os.FileInfo, inErr error) error {
   883  		if inErr != nil {
   884  			return inErr
   885  		}
   886  		if info.IsDir() {
   887  			return nil
   888  		}
   889  		total += info.Size()
   890  		return nil
   891  	})
   892  	return total, err
   893  }
   894  
   895  // zipDir will create a zip archive of everything in directory suitablie for streaming.
   896  // Read the data of the zip archive from the returned io.ReadCloser.
   897  func zipDir(directory string, prog *progress.ProgressWriter) io.ReadCloser {
   898  	// make a pipe so we can give saltpack engines a reader for the zip source
   899  	pipeRead, pipeWrite := io.Pipe()
   900  
   901  	// connect a zip archiver to the writer side of the pipe
   902  	w := zip.NewWriter(pipeWrite)
   903  
   904  	// given a directory like "a/b/c/d" or "/opt/blah/d", we want the files in the
   905  	// zip file to recreate just the final directory.  In other words, d/, d/1/, d/1/000.log
   906  	// and *not* a/b/c/d/, a/b/c/d/1/, a/b/c/d/1/000.log.
   907  	parent := filepath.Dir(directory)
   908  	stripParent := func(s string) string {
   909  		return strings.TrimPrefix(s, parent+string(filepath.Separator))
   910  	}
   911  
   912  	go func() {
   913  		err := filepath.Walk(directory, func(path string, info os.FileInfo, inErr error) (outErr error) {
   914  			defer transformSaltpackError(&outErr)
   915  			if inErr != nil {
   916  				return inErr
   917  			}
   918  
   919  			stripped := stripParent(path)
   920  
   921  			header, err := zip.FileInfoHeader(info)
   922  			if err != nil {
   923  				return err
   924  			}
   925  			header.Method = zip.Deflate
   926  
   927  			if info.IsDir() {
   928  				// make a directory by calling Create with a filename ending in a separator.
   929  				header.Name = stripped + string(filepath.Separator)
   930  				_, err := w.CreateHeader(header)
   931  				if err != nil {
   932  					return err
   933  				}
   934  				return nil
   935  			}
   936  
   937  			if (info.Mode() & os.ModeSymlink) != 0 {
   938  				// figure out the destination of the link
   939  				dest, err := os.Readlink(path)
   940  				if err != nil {
   941  					return err
   942  				}
   943  				header.Name = stripped
   944  				zw, err := w.CreateHeader(header)
   945  				if err != nil {
   946  					return err
   947  				}
   948  				// the body of the entry is the link destination
   949  				_, err = zw.Write([]byte(dest))
   950  				if err != nil {
   951  					return err
   952  				}
   953  				return nil
   954  			}
   955  
   956  			// make a regular file and copy all the data into it.
   957  			header.Name = stripped
   958  			zw, err := w.CreateHeader(header)
   959  			if err != nil {
   960  				return err
   961  			}
   962  			f, err := os.Open(path)
   963  			if err != nil {
   964  				return err
   965  			}
   966  			defer f.Close()
   967  			tr := io.TeeReader(bufio.NewReader(f), prog)
   968  			_, err = io.Copy(zw, tr)
   969  			if err != nil {
   970  				return err
   971  			}
   972  
   973  			return nil
   974  		})
   975  
   976  		// close everything
   977  		closeErr := w.Close()
   978  		if closeErr != nil && err == nil {
   979  			err = closeErr
   980  		}
   981  		_ = pipeWrite.CloseWithError(err)
   982  	}()
   983  
   984  	return pipeRead
   985  }
   986  
   987  func newDirProgressWriter(p types.ProgressReporter, dir string) (*progress.ProgressWriter, error) {
   988  	// for progress purposes, we need to know total size of everything in the directory
   989  	size, err := dirTotalSize(dir)
   990  	if err != nil {
   991  		return nil, err
   992  	}
   993  
   994  	return progress.NewProgressWriterWithUpdateDuration(p, size, 80*time.Millisecond), nil
   995  }
   996  
   997  func transformSaltpackError(err *error) {
   998  	if err == nil {
   999  		return
  1000  	}
  1001  	if *err == nil {
  1002  		return
  1003  	}
  1004  
  1005  	switch e := (*err).(type) {
  1006  	case *os.PathError:
  1007  		// this should remove the path from the error
  1008  		*err = e.Unwrap()
  1009  	default:
  1010  		// golangci-lint/gocritic, you really drive me crazy sometimes...
  1011  	}
  1012  }