github.com/keybase/client/go@v0.0.0-20241007131713-f10651d043c8/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  }