/
githubmirror
/
client
Обзор
Документация
Войти
/
githubmirror
/
client
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
master
go/engine/saltpack_decrypt_test.go
587 строк
18 KB
zoom-ua
modernize error handling with errors.As/Is (#29350)
25 июн 2026, 17:41
Не верифицирован
25 июн 2026, 17:41
e09e254
Код
Авторство
О чём код?
// Copyright 2015 Keybase, Inc. All rights reserved. Use of // this source code is governed by the included BSD license. package engine import ( "context" "errors" "fmt" "strings" "testing" "github.com/keybase/client/go/libkb" keybase1 "github.com/keybase/client/go/protocol/keybase1" "github.com/keybase/client/go/saltpackkeystest" "github.com/keybase/saltpack" "github.com/stretchr/testify/require" ) type fakeSaltpackUI struct{} func (s fakeSaltpackUI) SaltpackPromptForDecrypt(_ context.Context, arg keybase1.SaltpackPromptForDecryptArg, usedDelegateUI bool) (err error) { return nil } func (s fakeSaltpackUI) SaltpackVerifySuccess(_ context.Context, arg keybase1.SaltpackVerifySuccessArg) error { return nil } type FakeBadSenderError struct { senderType keybase1.SaltpackSenderType } func (e *FakeBadSenderError) Error() string { return fmt.Sprintf("fakeSaltpackUI bad sender error: %s", e.senderType.String()) } func (s fakeSaltpackUI) SaltpackVerifyBadSender(_ context.Context, arg keybase1.SaltpackVerifyBadSenderArg) error { return &FakeBadSenderError{arg.Sender.SenderType} } func initPerUserKeyringInTestContext(t *testing.T, tc libkb.TestContext) { kr, err := tc.G.GetPerUserKeyring(context.Background()) require.NoError(t, err) err = kr.Sync(libkb.NewMetaContext(context.Background(), tc.G)) require.NoError(t, err) } func TestSaltpackDecrypt(t *testing.T) { tc := SetupEngineTest(t, "SaltpackDecrypt") defer tc.Cleanup() fu := CreateAndSignupFakeUser(tc, "naclp") initPerUserKeyringInTestContext(t, tc) // encrypt a message msg := "10 days in Japan" sink := libkb.NewBufferCloser() uis := libkb.UIs{ IdentifyUI: &FakeIdentifyUI{}, SecretUI: fu.NewSecretUI(), LogUI: tc.G.UI.GetLogUI(), SaltpackUI: &fakeSaltpackUI{}, } // Should encrypt for self, too. arg := &SaltpackEncryptArg{ Opts: keybase1.SaltpackEncryptOptions{ UseEntityKeys: true, }, Source: strings.NewReader(msg), Sink: sink, } enc := NewSaltpackEncrypt(arg, NewSaltpackUserKeyfinderAsInterface) m := NewMetaContextForTest(tc).WithUIs(uis) if err := RunEngine2(m, enc); err != nil { t.Fatal(err) } out := sink.String() t.Logf("encrypted data: %s", out) // decrypt it decoded := libkb.NewBufferCloser() decarg := &SaltpackDecryptArg{ Source: strings.NewReader(out), Sink: decoded, } dec := NewSaltpackDecrypt(decarg, saltpackkeystest.NewMockPseudonymResolver(t)) if err := RunEngine2(m, dec); err != nil { t.Fatal(err) } decmsg := decoded.String() if decmsg != msg { t.Errorf("decoded: %s, expected: %s", decmsg, msg) } pgpMsg := `-----BEGIN PGP MESSAGE----- Version: GnuPG v1 hQEMA5gKPw0B/gTfAQf+JacZcP+4d1cdmRV5qlrDUhK3qm5dtzAh8KE3z6OMSOmE fUAdMZweHZMkWA5C1OZbvZ6SKaFLFHjmiD0DWlcdiXsvgPH9RpTHOSrxdjRlBuwK JBz5OrDM/OStIam6jKcxBcrI43JkWOG64AOwJ4Rx3OjAnzbKJKeUCAaopbXc2M5O iyTPzEsexRFjSfPGRk9cQD5zfar3Qjk2cRWElgABiQczWtfNAQ3NyQLzmRU6mw+i ZLoViAwQm2BMYa2i6MYOJCQtxHLwZCtAbRXTGFZ2nP0gVVX50KIeL/rnzrQ4I05M CljEVk3BBSQBl3jqecfT2Ooh+rwgf3VSQ684HIEt5dI/Aama8l7S3ypwVyt8gWhN HTngZWUk8Tjn6Q8zrnnoB92G1G+rZHAiChgBFQCaYDBsWa0Pia6Vm+10OAIulGGj =pNG+ -----END PGP MESSAGE----- ` decoded = libkb.NewBufferCloser() decarg = &SaltpackDecryptArg{ Source: strings.NewReader(pgpMsg), Sink: decoded, } dec = NewSaltpackDecrypt(decarg, saltpackkeystest.NewMockPseudonymResolver(t)) err := RunEngine2(m, dec) if wse, ok := err.(libkb.WrongCryptoFormatError); !ok { t.Fatalf("Wanted a WrongCryptoFormat error, but got %T (%v)", err, err) } else if wse.Wanted != libkb.CryptoMessageFormatSaltpack || wse.Received != libkb.CryptoMessageFormatPGP || wse.Operation != "decrypt" { t.Fatalf("Bad error: %v", wse) } } type testDecryptSaltpackUI struct { fakeSaltpackUI f func(arg keybase1.SaltpackPromptForDecryptArg) error } func (t *testDecryptSaltpackUI) SaltpackPromptForDecrypt(_ context.Context, arg keybase1.SaltpackPromptForDecryptArg, usedDelegateUI bool) (err error) { if t.f == nil { return nil } return t.f(arg) } func TestSaltpackDecryptBrokenTrack(t *testing.T) { tc := SetupEngineTest(t, "SaltpackDecrypt") defer tc.Cleanup() sigVersion := libkb.GetDefaultSigVersion(tc.G) // create a user to track the proofUser trackUser := CreateAndSignupFakeUser(tc, "naclp") Logout(tc) // create a user with a rooter proof proofUser := CreateAndSignupFakeUser(tc, "naclp") ui, _, err := proveRooter(tc.G, proofUser, sigVersion) if err != nil { t.Fatal(err) } spui := testDecryptSaltpackUI{} // encrypt a message msg := "10 days in Japan" sink := libkb.NewBufferCloser() uis := libkb.UIs{ IdentifyUI: &FakeIdentifyUI{}, SecretUI: proofUser.NewSecretUI(), LogUI: tc.G.UI.GetLogUI(), SaltpackUI: &spui, } arg := &SaltpackEncryptArg{ Source: strings.NewReader(msg), Sink: sink, Opts: keybase1.SaltpackEncryptOptions{ NoSelfEncrypt: true, UseEntityKeys: true, Recipients: []string{ trackUser.Username, }, }, } enc := NewSaltpackEncrypt(arg, NewSaltpackUserKeyfinderAsInterface) m := NewMetaContextForTest(tc).WithUIs(uis) if err := RunEngine2(m, enc); err != nil { t.Fatal(err) } out := sink.String() // Also output a anonymous-sender message arg.Opts.AuthenticityType = keybase1.AuthenticityType_ANONYMOUS sink = libkb.NewBufferCloser() arg.Source = strings.NewReader(msg) arg.Sink = sink enc = NewSaltpackEncrypt(arg, NewSaltpackUserKeyfinderAsInterface) if err := RunEngine2(m, enc); err != nil { t.Fatal(err) } outHidden := sink.String() Logout(tc) // Now login as the track user and track the proofUser trackUser.LoginOrBust(tc) rbl := sb{ social: true, id: proofUser.Username + "@rooter", proofState: keybase1.ProofState_OK, } outcome := keybase1.IdentifyOutcome{ NumProofSuccesses: 1, TrackStatus: keybase1.TrackStatus_NEW_OK, } err = checkTrack(tc, trackUser, proofUser.Username, []sb{rbl}, &outcome, sigVersion) if err != nil { t.Fatal(err) } // decrypt it decoded := libkb.NewBufferCloser() decarg := &SaltpackDecryptArg{ Source: strings.NewReader(out), Sink: decoded, } initPerUserKeyringInTestContext(t, tc) dec := NewSaltpackDecrypt(decarg, saltpackkeystest.NewMockPseudonymResolver(t)) spui.f = func(arg keybase1.SaltpackPromptForDecryptArg) error { if arg.Sender.SenderType != keybase1.SaltpackSenderType_TRACKING_OK { t.Fatalf("Bad sender type: %v", arg.Sender.SenderType) } return nil } if err := RunEngine2(m, dec); err != nil { t.Fatal(err) } decmsg := decoded.String() // Should work! if decmsg != msg { t.Fatalf("decoded: %s, expected: %s", decmsg, msg) } // now decrypt the hidden-self message decoded = libkb.NewBufferCloser() decarg = &SaltpackDecryptArg{ Source: strings.NewReader(outHidden), Sink: decoded, } initPerUserKeyringInTestContext(t, tc) dec = NewSaltpackDecrypt(decarg, saltpackkeystest.NewMockPseudonymResolver(t)) spui.f = func(arg keybase1.SaltpackPromptForDecryptArg) error { if arg.Sender.SenderType != keybase1.SaltpackSenderType_ANONYMOUS { t.Fatalf("Bad sender type: %v", arg.Sender.SenderType) } return nil } if err := RunEngine2(m, dec); err != nil { t.Fatal(err) } decmsg = decoded.String() // Should work! if decmsg != msg { t.Fatalf("decoded: %s, expected: %s", decmsg, msg) } // remove the rooter proof to break the tracking statement Logout(tc) proofUser.LoginOrBust(tc) if err := proveRooterRemove(tc.G, ui.postID); err != nil { t.Fatal(err) } Logout(tc) // Decrypt the message and fail, since our tracking statement is now // broken. trackUser.LoginOrBust(tc) decoded = libkb.NewBufferCloser() decarg = &SaltpackDecryptArg{ Source: strings.NewReader(out), Sink: decoded, Opts: keybase1.SaltpackDecryptOptions{ ForceRemoteCheck: true, }, } initPerUserKeyringInTestContext(t, tc) dec = NewSaltpackDecrypt(decarg, saltpackkeystest.NewMockPseudonymResolver(t)) errTrackingBroke := errors.New("tracking broke") spui.f = func(arg keybase1.SaltpackPromptForDecryptArg) error { if arg.Sender.SenderType != keybase1.SaltpackSenderType_TRACKING_BROKE { t.Fatalf("Bad sender type: %v", arg.Sender.SenderType) } return errTrackingBroke } err = RunEngine2(m, dec) if decErr, ok := err.(libkb.DecryptionError); ok && !errors.Is(decErr.Cause.Err, errTrackingBroke) { t.Fatalf("Expected an error %v; but got %v", errTrackingBroke, err) } } // The error info that this test is looking for is only available in legacy // saltpack encryption (i.e. repudiable) mode messages (originally called // encryption-only mode). Modern repudiable messages always have all-anonymous-recipients, // and signcryption messages have opaque receivers. func TestSaltpackNoEncryptionForDevice(t *testing.T) { // userX has one device (device X) and a paper key. userZ will encrypt a message for userX, // then userX will add a new device (Y) which should not be able to decrypt the message // (as we are not using PUKs) but will receive an helpful error message which tells them // which other devices to use to decrypt. // device X (provisioner) context: tcX := SetupEngineTest(t, "kex2provision") defer tcX.Cleanup() // device Z is the encryptor's device tcZ := SetupEngineTest(t, "encryptor") defer tcZ.Cleanup() // provisioner needs to be logged in userX := CreateAndSignupFakeUserPaper(tcX, "naclp") encryptor := CreateAndSignupFakeUser(tcZ, "naclp") spui := testDecryptSaltpackUI{} // encrypt a message with encryption / tcZ msg := "10 days in Japan" sink := libkb.NewBufferCloser() uis := libkb.UIs{ IdentifyUI: &FakeIdentifyUI{}, SecretUI: encryptor.NewSecretUI(), LogUI: tcZ.G.UI.GetLogUI(), SaltpackUI: &spui, } arg := &SaltpackEncryptArg{ Source: strings.NewReader(msg), Sink: sink, Opts: keybase1.SaltpackEncryptOptions{ Recipients: []string{ userX.Username, }, UseDeviceKeys: true, UsePaperKeys: true, AuthenticityType: keybase1.AuthenticityType_REPUDIABLE, }, } enc := NewSaltpackEncrypt(arg, NewSaltpackUserKeyfinderAsInterface) m := NewMetaContextForTest(tcZ).WithUIs(uis) // The error messages in this test only work for visible recipients, which // aren't the default anymore. enc.visibleRecipientsForTesting = true if err := RunEngine2(m, enc); err != nil { t.Fatal(err) } out := sink.String() // decrypt it with userX / tcX decoded := libkb.NewBufferCloser() decarg := &SaltpackDecryptArg{ Source: strings.NewReader(out), Sink: decoded, } dec := NewSaltpackDecrypt(decarg, saltpackkeystest.NewMockPseudonymResolver(t)) spui.f = func(arg keybase1.SaltpackPromptForDecryptArg) error { if arg.Sender.SenderType != keybase1.SaltpackSenderType_NOT_TRACKED { t.Fatalf("Bad sender type: %v", arg.Sender.SenderType) } return nil } uis = libkb.UIs{ IdentifyUI: &FakeIdentifyUI{}, SecretUI: userX.NewSecretUI(), LogUI: tcX.G.UI.GetLogUI(), SaltpackUI: &spui, } m = NewMetaContextForTest(tcX).WithUIs(uis) if err := RunEngine2(m, dec); err != nil { t.Fatal(err) } decmsg := decoded.String() // Should work! if decmsg != msg { t.Fatalf("decoded: %s, expected: %s", decmsg, msg) } // Now make a new device for userX tcY, Cleanup := provisionNewDeviceKex(&tcX, userX) defer Cleanup() if err := AssertProvisioned(*tcY); err != nil { t.Fatal(err) } // Now try and fail to decrypt with device Y (via tcY) decoded = libkb.NewBufferCloser() decarg = &SaltpackDecryptArg{ Source: strings.NewReader(out), Sink: decoded, } dec = NewSaltpackDecrypt(decarg, saltpackkeystest.NewMockPseudonymResolver(t)) spui.f = func(arg keybase1.SaltpackPromptForDecryptArg) error { t.Fatal("should not be prompted for decryption") return nil } uis = libkb.UIs{ IdentifyUI: &FakeIdentifyUI{}, SecretUI: userX.NewSecretUI(), LogUI: tcY.G.UI.GetLogUI(), SaltpackUI: &spui, } m = NewMetaContextForTest(*tcY).WithUIs(uis) if err := RunEngine2(m, dec); err == nil { t.Fatal("Should have seen a decryption error") } // Make sure we get the right helpful debug message back me := dec.MessageInfo() if len(me.Devices) != 2 { t.Fatalf("expected 2 valid devices; got %d", len(me.Devices)) } backup := 0 desktops := 0 for _, d := range me.Devices { switch d.Type { case keybase1.DeviceTypeV2_PAPER: backup++ case keybase1.DeviceTypeV2_DESKTOP: desktops++ if !userX.EncryptionKey.GetKID().Equal(d.EncryptKey) { t.Fatal("got wrong encryption key for good possibilities") } default: t.Fatalf("wrong kind of device: %s\n", d.Type) } } if backup != 1 { t.Fatal("Only wanted 1 backup key") } if desktops != 1 { t.Fatal("only wanted 1 desktop key") } } func TestSaltpackDecryptWithPaperKey(t *testing.T) { tc := SetupEngineTest(t, "SaltpackDecrypt") defer tc.Cleanup() // We don't log in as a test user here. This flow should work even if // you're totally logged out. msg := `BEGIN KEYBASE SALTPACK ENCRYPTED MESSAGE. kiPgBwdlv6bV9N8 dSkCbjKrku4NADt gV8qC1k8zRA0Bi0 6KvGQoMyf99b2id uGZ3EDTqb5nZVPT vhMiB49BOHavdzN mySkmzwlSWDsuQA z9RIPfrIX9IJCfi yqlaD1HOqK1lilP tDrign5LrAB8zLz 4NwPFBwpQJWW8sO N9Jk6yzf6QvdPav GN9SqL6YX7XEbJc PLrDD7LCj7fHObD O3pTQSLjuUKqAqa 3LDiQEVEDUZzYLy TvKyMJ2U8gCuhcU SeqDClUNAPKqEEM MRgyTcw0LSwK4A5 YyZhDM065PA5SHb 6ZFPGYnv81HOibR FHHv5lYEqPqPAZa ETIXLblnxI61F2q 6cH3w60bbxFuQB2 fwLZQSUS4ZzyVSw UN3OKZMr79vqr6S ap3vMMqGiWm3blG ptjZEmFXI5dQqZG w9AO0Djmy2fWnCB Z42e7BZteGaRhz8 zVmNLdOvtWiJkRF FUo2KvUgBsk9ecJ 3iZUOhYbdqja2Xx osdOqu6OUS9V4XC H9vRylZJShvVg2X NLaeeHZ6AHdxkxO NgrG1NqHeIubq8p 0VaDq1iKk78Qj27 4q26yqnt5E9sgnN xJ850oP5DeKWrN3 yaif8ouprlETzY3 CLmDsAN5vVCgVga gx1q3YEjKUmJqD2 EsY5KBKogE1YjvQ eVaoqX5qiKtS6o0 oGE70tbveveK0kV SErmRsOSFBieaCq JzW75TXRCHpLvVB 1ZB8Wih6cyvw1yx pK5RJNfPOF6lzKm i28FT9EoCw7uvsB kBG2EfA9YRkhXKh RoqAGrkdX3ziGy8 j5eOK91eyIcl7f7 SfUFLzETW5ULZfm 7Z9BIeOJogk7a1B 7IUJQiYpLyG3xAF p3nmeSIalwfIzhV opNxUB7ltUOn3PX t9abJAZkUodMURG zXw0dKHQKtWXce6 y8jHbaU0zLwxvhO W3bxHNGoQ10t7Gq hSPu7SYLYyD926w 8nv5FqiUtTf7eJq Ay1c2FAYMPkB4ay 6lB0wxtpNCGt8MO RtrC1Da3aj7rLTL fFNz2kxb78hT2Tu QNiHyBL. END KEYBASE SALTPACK ENCRYPTED MESSAGE.` paperkey := "fitness weasel session truly among connect explain found measure smile ask ball shoulder" // decrypt it decoded := libkb.NewBufferCloser() decarg := &SaltpackDecryptArg{ Source: strings.NewReader(msg), Sink: decoded, Opts: keybase1.SaltpackDecryptOptions{ UsePaperKey: true, }, } dec := NewSaltpackDecrypt(decarg, saltpackkeystest.NewMockPseudonymResolver(t)) uis := libkb.UIs{ IdentifyUI: &FakeIdentifyUI{}, // Here's where the paper key goes in! SecretUI: &libkb.TestSecretUI{Passphrase: paperkey}, LogUI: tc.G.UI.GetLogUI(), SaltpackUI: &fakeSaltpackUI{}, } m := NewMetaContextForTest(tc).WithUIs(uis) if err := RunEngine2(m, dec); err != nil { t.Fatal(err) } decmsg := decoded.String() expected := "message for paper key" if decmsg != expected { t.Errorf("decoded: %s, expected: %s", decmsg, expected) } } func TestSaltpackDecryptErrors(t *testing.T) { tc := SetupEngineTest(t, "SaltpackDecrypt") defer tc.Cleanup() fu := CreateAndSignupFakeUser(tc, "naclp") initPerUserKeyringInTestContext(t, tc) // encrypt a message msg := "10 days in Japan" sink := libkb.NewBufferCloser() uis := libkb.UIs{ IdentifyUI: &FakeIdentifyUI{}, SecretUI: fu.NewSecretUI(), LogUI: tc.G.UI.GetLogUI(), SaltpackUI: &fakeSaltpackUI{}, } // Should encrypt for self, too. arg := &SaltpackEncryptArg{ Opts: keybase1.SaltpackEncryptOptions{ UseEntityKeys: true, }, Source: strings.NewReader(msg), Sink: sink, } enc := NewSaltpackEncrypt(arg, NewSaltpackUserKeyfinderAsInterface) m := NewMetaContextForTest(tc).WithUIs(uis) if err := RunEngine2(m, enc); err != nil { t.Fatal(err) } out := sink.String() t.Logf("encrypted data: %s", out) // assert that decryption works decoded := libkb.NewBufferCloser() decarg := &SaltpackDecryptArg{ Source: strings.NewReader(out), Sink: decoded, } dec := NewSaltpackDecrypt(decarg, saltpackkeystest.NewMockPseudonymResolver(t)) if err := RunEngine2(m, dec); err != nil { t.Fatal(err) } decmsg := decoded.String() if decmsg != msg { t.Errorf("decoded: %s, expected: %s", decmsg, msg) } // no suitable key noKeyOut := ` BEGIN KEYBASE SALTPACK ENCRYPTED MESSAGE. keDIDMQWYvVR58B FTfTeDQNI4kF4aV DVTLefltGS2t8xf vO9CL7B6UQvpJEM WofNn31JYOAziF6 5vLTstSAXwLrJQ0 K6KJtSb38V1EsJJ sJMdU57C9s3dYEy Fk9Rw1bK2dTnua5 b822rmZdhdw7VzS RwR1shIDHPXgFGC 5HhonSburbeywdE lSriPuVhSAXX9yR kwgCa3jRY3y863C pwxv8lB10IjmgSx Xb5D61t8WCZ86N0 pADtHnYeyXwezjW fSEE57xOLE8naAz RJLPlTEhoqllLQh iK1yzp6bGeJyAlI ctWQKKORiIyIsNS 1oFT8dj7SUtWhxy nAGjUIDfkIBzBB3 SbPgWxhBKnXbW0R LCMxKOGhgpolubZ sdlVLZmP0F. END KEYBASE SALTPACK ENCRYPTED MESSAGE. ` decoded = libkb.NewBufferCloser() decarg = &SaltpackDecryptArg{ Source: strings.NewReader(noKeyOut), Sink: decoded, } dec = NewSaltpackDecrypt(decarg, saltpackkeystest.NewMockPseudonymResolver(t)) err := RunEngine2(m, dec) require.Error(t, err) require.IsType(t, libkb.DecryptionError{}, err) if err, ok := err.(libkb.DecryptionError); ok { require.IsType(t, libkb.NoDecryptionKeyError{}, err.Cause.Err) require.Equal(t, libkb.SCDecryptionKeyNotFound, err.Cause.StatusCode) } // wrong type wrongTypeOut := `BEGIN KEYBASE SALTPACK SIGNED MESSAGE. kXR7VktZdyH7rvq v5weRa0zkUpZbPl nShyKLCCGSyBcvL sg7Gi9ySjlkxHPS RUM4Vm3DUCrEkxO bkbzrs0zuASSJbt aRoYF7ojm5zUxFX QasNzKboA7khwAa qIlCC0iwTFB3NBq fmDjvyKYfyQACcV 4BG5Mij3RfCYqAw gLtvUzU0UmfhJoP JPbj2eztW. END KEYBASE SALTPACK SIGNED MESSAGE.` decoded = libkb.NewBufferCloser() decarg = &SaltpackDecryptArg{ Source: strings.NewReader(wrongTypeOut), Sink: decoded, } dec = NewSaltpackDecrypt(decarg, saltpackkeystest.NewMockPseudonymResolver(t)) err = RunEngine2(m, dec) require.Error(t, err) require.IsType(t, libkb.DecryptionError{}, err) if err, ok := err.(libkb.DecryptionError); ok { require.IsType(t, saltpack.ErrWrongMessageType{}, err.Cause.Err) require.Equal(t, libkb.SCWrongCryptoMsgType, err.Cause.StatusCode) } }