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 }