github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/service/saltpack_test.go (about) 1 package service 2 3 import ( 4 "archive/zip" 5 6 "io" 7 "os" 8 "path/filepath" 9 "strings" 10 "testing" 11 12 "github.com/keybase/client/go/kbtest" 13 "github.com/keybase/client/go/libkb" 14 "github.com/keybase/client/go/protocol/keybase1" 15 "github.com/stretchr/testify/require" 16 "golang.org/x/net/context" 17 ) 18 19 func TestSaltpackFrontend(t *testing.T) { 20 tc := libkb.SetupTest(t, "sp", 0) 21 defer tc.Cleanup() 22 23 u1, err := kbtest.CreateAndSignupFakeUser("sp", tc.G) 24 require.NoError(t, err) 25 26 kr, err := tc.G.GetPerUserKeyring(context.Background()) 27 require.NoError(t, err) 28 err = kr.Sync(libkb.NewMetaContext(context.Background(), tc.G)) 29 require.NoError(t, err) 30 31 u2, err := kbtest.CreateAndSignupFakeUser("sp", tc.G) 32 require.NoError(t, err) 33 34 kr, err = tc.G.GetPerUserKeyring(context.Background()) 35 require.NoError(t, err) 36 err = kr.Sync(libkb.NewMetaContext(context.Background(), tc.G)) 37 require.NoError(t, err) 38 39 h := NewSaltpackHandler(nil, tc.G) 40 testEncryptDecryptString(tc, h, u1, u2) 41 testSignVerifyString(tc, h, u1, u2) 42 testEncryptDecryptFile(tc, h, u1, u2) 43 testSignVerifyFile(tc, h, u1, u2) 44 testSignToTextFile(tc, h, u1, u2) 45 testEncryptToTextFile(tc, h, u1, u2) 46 testDecryptBogusFile(tc, h, u1, u2) 47 testEncryptDecryptDirectory(tc, h, u1, u2) 48 testSignVerifyDirectory(tc, h, u1, u2) 49 } 50 51 func testEncryptDecryptString(tc libkb.TestContext, h *SaltpackHandler, u1, u2 *kbtest.FakeUser) { 52 ctx := context.Background() 53 encArg := keybase1.SaltpackEncryptStringArg{ 54 Plaintext: "Think of life as a banquet.", 55 Opts: keybase1.SaltpackFrontendEncryptOptions{ 56 Recipients: []string{u1.Username, u2.Username}, 57 Signed: true, 58 IncludeSelf: true, 59 }, 60 } 61 encRes, err := h.SaltpackEncryptString(ctx, encArg) 62 require.NoError(tc.T, err) 63 require.NotEqual(tc.T, encArg.Plaintext, encRes.Ciphertext) 64 require.False(tc.T, encRes.UsedUnresolvedSBS) 65 require.Empty(tc.T, encRes.UnresolvedSBSAssertion) 66 67 decArg := keybase1.SaltpackDecryptStringArg{Ciphertext: encRes.Ciphertext} 68 decRes, err := h.SaltpackDecryptString(ctx, decArg) 69 require.NoError(tc.T, err) 70 require.Equal(tc.T, decRes.Plaintext, encArg.Plaintext) 71 require.True(tc.T, decRes.Signed) 72 } 73 74 func testSignVerifyString(tc libkb.TestContext, h *SaltpackHandler, u1, u2 *kbtest.FakeUser) { 75 ctx := context.Background() 76 signArg := keybase1.SaltpackSignStringArg{Plaintext: "Begin with little things."} 77 signedMsg, err := h.SaltpackSignString(ctx, signArg) 78 require.NoError(tc.T, err) 79 require.NotEqual(tc.T, signArg.Plaintext, signedMsg) 80 81 verifyArg := keybase1.SaltpackVerifyStringArg{SignedMsg: signedMsg} 82 verifyRes, err := h.SaltpackVerifyString(ctx, verifyArg) 83 require.NoError(tc.T, err) 84 require.Equal(tc.T, verifyRes.Plaintext, signArg.Plaintext) 85 require.True(tc.T, verifyRes.Verified) 86 } 87 88 func testEncryptDecryptFile(tc libkb.TestContext, h *SaltpackHandler, u1, u2 *kbtest.FakeUser) { 89 ctx := context.Background() 90 encArg := keybase1.SaltpackEncryptFileArg{ 91 Filename: filepath.FromSlash("testdata/textfile"), 92 Opts: keybase1.SaltpackFrontendEncryptOptions{ 93 Recipients: []string{u1.Username, u2.Username}, 94 Signed: true, 95 IncludeSelf: true, 96 }, 97 } 98 encRes, err := h.SaltpackEncryptFile(ctx, encArg) 99 require.NoError(tc.T, err) 100 defer os.Remove(encRes.Filename) 101 require.NotEqual(tc.T, encRes.Filename, encArg.Filename) 102 require.True(tc.T, strings.HasSuffix(encRes.Filename, ".encrypted.saltpack")) 103 require.False(tc.T, encRes.UsedUnresolvedSBS) 104 require.Empty(tc.T, encRes.UnresolvedSBSAssertion) 105 106 decArg := keybase1.SaltpackDecryptFileArg{EncryptedFilename: encRes.Filename} 107 decRes, err := h.SaltpackDecryptFile(ctx, decArg) 108 require.NoError(tc.T, err) 109 defer os.Remove(decRes.DecryptedFilename) 110 require.Equal(tc.T, encArg.Filename+" (1)", decRes.DecryptedFilename) 111 require.True(tc.T, decRes.Signed) 112 113 filesEqual(tc, encArg.Filename, decRes.DecryptedFilename) 114 115 decArg.DestinationDir = os.TempDir() 116 decRes, err = h.SaltpackDecryptFile(ctx, decArg) 117 require.NoError(tc.T, err) 118 defer os.Remove(decRes.DecryptedFilename) 119 require.Equal(tc.T, filepath.Join(decArg.DestinationDir, filepath.Base(encArg.Filename)), decRes.DecryptedFilename) 120 require.True(tc.T, decRes.Signed) 121 filesEqual(tc, encArg.Filename, decRes.DecryptedFilename) 122 123 encArg.DestinationDir = os.TempDir() 124 encRes, err = h.SaltpackEncryptFile(ctx, encArg) 125 require.NoError(tc.T, err) 126 defer os.Remove(encRes.Filename) 127 require.NotEqual(tc.T, encRes.Filename, encArg.Filename) 128 require.True(tc.T, strings.HasSuffix(encRes.Filename, ".encrypted.saltpack")) 129 require.Equal(tc.T, filepath.Join(encArg.DestinationDir, filepath.Base(encArg.Filename)+".encrypted.saltpack"), encRes.Filename) 130 } 131 132 func testSignVerifyFile(tc libkb.TestContext, h *SaltpackHandler, u1, u2 *kbtest.FakeUser) { 133 ctx := context.Background() 134 signArg := keybase1.SaltpackSignFileArg{Filename: filepath.FromSlash("testdata/textfile")} 135 signedFile, err := h.SaltpackSignFile(ctx, signArg) 136 require.NoError(tc.T, err) 137 defer os.Remove(signedFile) 138 require.NotEqual(tc.T, signedFile, signArg.Filename) 139 require.True(tc.T, strings.HasSuffix(signedFile, ".signed.saltpack")) 140 141 verifyArg := keybase1.SaltpackVerifyFileArg{SignedFilename: signedFile} 142 verifyRes, err := h.SaltpackVerifyFile(ctx, verifyArg) 143 require.NoError(tc.T, err) 144 defer os.Remove(verifyRes.VerifiedFilename) 145 require.Equal(tc.T, signArg.Filename+" (1)", verifyRes.VerifiedFilename) 146 require.True(tc.T, verifyRes.Verified) 147 148 filesEqual(tc, signArg.Filename, verifyRes.VerifiedFilename) 149 } 150 151 func testSignToTextFile(tc libkb.TestContext, h *SaltpackHandler, u1, u2 *kbtest.FakeUser) { 152 ctx := context.Background() 153 signArg := keybase1.SaltpackSignStringToTextFileArg{Plaintext: "Begin with little things."} 154 signedFile, err := h.SaltpackSignStringToTextFile(ctx, signArg) 155 require.NoError(tc.T, err) 156 defer os.Remove(signedFile) 157 require.NotEmpty(tc.T, signedFile) 158 159 verifyArg := keybase1.SaltpackVerifyFileArg{SignedFilename: signedFile} 160 verifyRes, err := h.SaltpackVerifyFile(ctx, verifyArg) 161 require.NoError(tc.T, err) 162 defer os.Remove(verifyRes.VerifiedFilename) 163 require.True(tc.T, verifyRes.Verified) 164 fdata, err := os.ReadFile(verifyRes.VerifiedFilename) 165 require.NoError(tc.T, err) 166 require.Equal(tc.T, []byte(signArg.Plaintext), fdata) 167 } 168 169 func testEncryptToTextFile(tc libkb.TestContext, h *SaltpackHandler, u1, u2 *kbtest.FakeUser) { 170 ctx := context.Background() 171 encArg := keybase1.SaltpackEncryptStringToTextFileArg{ 172 Plaintext: "Think of life as a banquet.", 173 Opts: keybase1.SaltpackFrontendEncryptOptions{ 174 Recipients: []string{u1.Username, u2.Username}, 175 Signed: true, 176 IncludeSelf: true, 177 }, 178 } 179 encRes, err := h.SaltpackEncryptStringToTextFile(ctx, encArg) 180 require.NoError(tc.T, err) 181 defer os.Remove(encRes.Filename) 182 require.NotEmpty(tc.T, encRes.Filename) 183 require.False(tc.T, encRes.UsedUnresolvedSBS) 184 require.Empty(tc.T, encRes.UnresolvedSBSAssertion) 185 186 decArg := keybase1.SaltpackDecryptFileArg{EncryptedFilename: encRes.Filename} 187 decRes, err := h.SaltpackDecryptFile(ctx, decArg) 188 require.NoError(tc.T, err) 189 defer os.Remove(decRes.DecryptedFilename) 190 require.True(tc.T, decRes.Signed) 191 fdata, err := os.ReadFile(decRes.DecryptedFilename) 192 require.NoError(tc.T, err) 193 require.Equal(tc.T, []byte(encArg.Plaintext), fdata) 194 } 195 196 func testDecryptBogusFile(tc libkb.TestContext, h *SaltpackHandler, u1, u2 *kbtest.FakeUser) { 197 ctx := context.Background() 198 testFile := "testdata/textfile" 199 decFilename := testFile + ".decrypted" 200 201 // this file is not encrypted 202 decArg := keybase1.SaltpackDecryptFileArg{EncryptedFilename: filepath.FromSlash(testFile)} 203 _, err := h.SaltpackDecryptFile(ctx, decArg) 204 require.Error(tc.T, err) 205 if exists, _ := libkb.FileExists(filepath.FromSlash(decFilename)); exists { 206 os.Remove(filepath.FromSlash(decFilename)) 207 tc.T.Errorf("%s exists, it should be deleted if there is an error", decFilename) 208 } 209 } 210 211 func testEncryptDecryptDirectory(tc libkb.TestContext, h *SaltpackHandler, u1, u2 *kbtest.FakeUser) { 212 ctx := context.Background() 213 encArg := keybase1.SaltpackEncryptFileArg{ 214 Filename: filepath.FromSlash("testdata/archive"), 215 Opts: keybase1.SaltpackFrontendEncryptOptions{ 216 Recipients: []string{u1.Username, u2.Username}, 217 Signed: true, 218 IncludeSelf: true, 219 }, 220 } 221 encRes, err := h.SaltpackEncryptFile(ctx, encArg) 222 require.NoError(tc.T, err) 223 defer os.Remove(encRes.Filename) 224 require.NotEqual(tc.T, encRes.Filename, encArg.Filename) 225 require.True(tc.T, strings.HasSuffix(encRes.Filename, ".zip.encrypted.saltpack")) 226 require.False(tc.T, encRes.UsedUnresolvedSBS) 227 require.Empty(tc.T, encRes.UnresolvedSBSAssertion) 228 229 decArg := keybase1.SaltpackDecryptFileArg{EncryptedFilename: encRes.Filename} 230 decRes, err := h.SaltpackDecryptFile(ctx, decArg) 231 require.NoError(tc.T, err) 232 defer os.Remove(decRes.DecryptedFilename) 233 require.Equal(tc.T, encArg.Filename+".zip", decRes.DecryptedFilename) 234 require.True(tc.T, decRes.Signed) 235 236 checkZipArchive(tc, decRes.DecryptedFilename) 237 } 238 239 func testSignVerifyDirectory(tc libkb.TestContext, h *SaltpackHandler, u1, u2 *kbtest.FakeUser) { 240 ctx := context.Background() 241 signArg := keybase1.SaltpackSignFileArg{Filename: filepath.FromSlash("testdata/archive")} 242 signedFile, err := h.SaltpackSignFile(ctx, signArg) 243 require.NoError(tc.T, err) 244 defer os.Remove(signedFile) 245 require.NotEqual(tc.T, signedFile, signArg.Filename) 246 require.True(tc.T, strings.HasSuffix(signedFile, ".zip.signed.saltpack")) 247 248 verifyArg := keybase1.SaltpackVerifyFileArg{SignedFilename: signedFile} 249 verifyRes, err := h.SaltpackVerifyFile(ctx, verifyArg) 250 require.NoError(tc.T, err) 251 defer os.Remove(verifyRes.VerifiedFilename) 252 require.Equal(tc.T, signArg.Filename+".zip", verifyRes.VerifiedFilename) 253 require.True(tc.T, verifyRes.Verified) 254 255 checkZipArchive(tc, verifyRes.VerifiedFilename) 256 } 257 258 func filesEqual(tc libkb.TestContext, a, b string) { 259 adata, err := os.ReadFile(a) 260 require.NoError(tc.T, err) 261 bdata, err := os.ReadFile(b) 262 require.NoError(tc.T, err) 263 require.Equal(tc.T, adata, bdata) 264 } 265 266 func checkZipArchive(tc libkb.TestContext, filename string) { 267 r, err := zip.OpenReader(filename) 268 require.NoError(tc.T, err) 269 defer r.Close() 270 // some platforms make `@tmp` entries for the directories, so there 271 // can be 11 272 if len(r.File) != 9 && len(r.File) != 11 { 273 tc.T.Errorf("number of files in zip archive: %d, expected 9 or 11", len(r.File)) 274 } 275 for _, f := range r.File { 276 switch filepath.ToSlash(f.Name) { 277 case "archive/", "archive/1/", "archive/2/": // skip the directory entries 278 case "archive/a.txt": 279 checkZipFileEqual(tc, f) 280 case "archive/b.txt": 281 checkZipFileEqual(tc, f) 282 case "archive/c.txt": 283 checkZipFileEqual(tc, f) 284 case "archive/1/000.log": 285 checkZipFileEqual(tc, f) 286 case "archive/1/001.log": 287 checkZipFileEqual(tc, f) 288 case "archive/2/000.log": 289 checkZipFileEqual(tc, f) 290 default: 291 tc.T.Logf("unknown file in zip: %s", f.Name) 292 } 293 } 294 } 295 296 func checkZipFileEqual(tc libkb.TestContext, f *zip.File) { 297 localName := filepath.Join("testdata", f.Name) 298 localData, err := os.ReadFile(localName) 299 require.NoError(tc.T, err) 300 fz, err := f.Open() 301 require.NoError(tc.T, err) 302 defer fz.Close() 303 zipData, err := io.ReadAll(fz) 304 require.NoError(tc.T, err) 305 require.Equal(tc.T, localData, zipData) 306 }