package service import ( "bytes" "context" "io" "io/ioutil" "os" "path" "testing" "github.com/keys-pub/keys" "github.com/keys-pub/keys/http" "github.com/stretchr/testify/require" "google.golang.org/grpc/status" ) func TestEncryptDecrypt(t *testing.T) { // SetLogger(NewLogger(DebugLevel)) // keys.SetLogger(NewLogger(DebugLevel)) // saltpack.SetLogger(NewLogger(DebugLevel)) env := newTestEnv(t) aliceService, aliceCloseFn := newTestService(t, env) defer aliceCloseFn() testAuthSetup(t, aliceService) testImportKey(t, aliceService, alice) bobService, bobCloseFn := newTestService(t, env) defer bobCloseFn() testAuthSetup(t, bobService) testImportKey(t, bobService, bob) testImportID(t, bobService, alice.ID()) testEncryptDecrypt(t, aliceService, bobService, alice.ID().String(), bob.ID().String(), DefaultEncrypt, false, alice.ID()) testEncryptDecrypt(t, aliceService, bobService, alice.ID().String(), bob.ID().String(), DefaultEncrypt, true, alice.ID()) testEncryptDecrypt(t, aliceService, bobService, alice.ID().String(), bob.ID().String(), SaltpackEncrypt, false, alice.ID()) testEncryptDecrypt(t, aliceService, bobService, alice.ID().String(), bob.ID().String(), SaltpackEncrypt, true, alice.ID()) testEncryptDecrypt(t, aliceService, bobService, alice.ID().String(), bob.ID().String(), SaltpackSigncrypt, false, alice.ID()) testEncryptDecrypt(t, aliceService, bobService, alice.ID().String(), bob.ID().String(), SaltpackSigncrypt, true, alice.ID()) testEncryptDecryptErrors(t, aliceService, bobService, DefaultEncrypt, false) testEncryptDecryptErrors(t, aliceService, bobService, DefaultEncrypt, true) testEncryptDecryptErrors(t, aliceService, bobService, SaltpackEncrypt, false) testEncryptDecryptErrors(t, aliceService, bobService, SaltpackEncrypt, true) testEncryptDecryptErrors(t, aliceService, bobService, SaltpackSigncrypt, false) testEncryptDecryptErrors(t, aliceService, bobService, SaltpackSigncrypt, true) } func testEncryptDecrypt(t *testing.T, aliceService *service, bobService *service, sender string, recipient string, mode EncryptMode, armored bool, expectedSigner keys.ID) { message := "Hey bob" // Encrypt encryptResp, err := aliceService.Encrypt(context.TODO(), &EncryptRequest{ Data: []byte(message), Recipients: []string{recipient}, Sender: sender, Options: &EncryptOptions{ Mode: mode, }, }) require.NoError(t, err) require.NotEmpty(t, encryptResp.Data) // Decrypt decryptResp, err := bobService.Decrypt(context.TODO(), &DecryptRequest{ Data: encryptResp.Data, }) require.NoError(t, err) require.Equal(t, message, string(decryptResp.Data)) require.Equal(t, expectedSigner.String(), decryptResp.Sender.ID) // Decrypt (alice) decryptResp, err = aliceService.Decrypt(context.TODO(), &DecryptRequest{ Data: encryptResp.Data, }) require.NoError(t, err) require.Equal(t, message, string(decryptResp.Data)) require.Equal(t, expectedSigner.String(), decryptResp.Sender.ID) } func testEncryptDecryptErrors(t *testing.T, aliceService *service, bobService *service, mode EncryptMode, armored bool) { message := "Hey bob" encryptResp, err := aliceService.Encrypt(context.TODO(), &EncryptRequest{ Data: []byte(message), Recipients: []string{bob.ID().String()}, Sender: alice.ID().String(), Options: &EncryptOptions{ Mode: mode, Armored: armored, NoSenderRecipient: true, }, }) require.NoError(t, err) require.NotEmpty(t, encryptResp.Data) // Alice try to decrypt her own message _, err = aliceService.Decrypt(context.TODO(), &DecryptRequest{ Data: encryptResp.Data, }) require.EqualError(t, err, "no decryption key found for message") _, err = aliceService.Encrypt(context.TODO(), &EncryptRequest{ Data: []byte(message), }) require.EqualError(t, err, "no recipients specified") // Decrypt garbage _, err = aliceService.Decrypt(context.TODO(), &DecryptRequest{ Data: []byte("????"), }) require.EqualError(t, err, "invalid data") // Decrypt empty _, err = aliceService.Decrypt(context.TODO(), &DecryptRequest{ Data: []byte{}, }) require.EqualError(t, err, "invalid data") } func TestEncryptAnonymous(t *testing.T) { env := newTestEnv(t) aliceService, aliceCloseFn := newTestService(t, env) defer aliceCloseFn() testAuthSetup(t, aliceService) testImportKey(t, aliceService, alice) bobService, bobCloseFn := newTestService(t, env) defer bobCloseFn() testAuthSetup(t, bobService) testImportKey(t, bobService, bob) message := "Hey bob" // Encrypt encryptResp, err := aliceService.Encrypt(context.TODO(), &EncryptRequest{ Data: []byte(message), Recipients: []string{bob.ID().String()}, Sender: "", Options: &EncryptOptions{ Mode: SaltpackEncrypt, }, }) require.NoError(t, err) require.NotEmpty(t, encryptResp.Data) // Decrypt decryptResp, err := bobService.Decrypt(context.TODO(), &DecryptRequest{ Data: encryptResp.Data, }) require.NoError(t, err) require.Equal(t, message, string(decryptResp.Data)) require.Nil(t, decryptResp.Sender) // Encrypt (signcrypt) encryptResp, err = aliceService.Encrypt(context.TODO(), &EncryptRequest{ Data: []byte(message), Recipients: []string{bob.ID().String()}, Sender: "", Options: &EncryptOptions{ Mode: SaltpackSigncrypt, }, }) require.NoError(t, err) // Decrypt decryptResp, err = bobService.Decrypt(context.TODO(), &DecryptRequest{ Data: encryptResp.Data, }) require.NoError(t, err) require.Equal(t, message, string(decryptResp.Data)) require.Nil(t, decryptResp.Sender) // Encrypt (no sign) encryptResp, err = aliceService.Encrypt(context.TODO(), &EncryptRequest{ Data: []byte(message), Recipients: []string{bob.ID().String()}, Sender: alice.ID().String(), Options: &EncryptOptions{ NoSign: true, }, }) require.NoError(t, err) // Decrypt decryptResp, err = bobService.Decrypt(context.TODO(), &DecryptRequest{ Data: encryptResp.Data, }) require.NoError(t, err) require.Equal(t, message, string(decryptResp.Data)) require.Nil(t, decryptResp.Sender) } func TestEncryptDecryptStream(t *testing.T) { // SetLogger(NewLogger(DebugLevel)) env := newTestEnv(t) aliceService, aliceCloseFn := newTestService(t, env) defer aliceCloseFn() testAuthSetup(t, aliceService) testImportKey(t, aliceService, alice) bobService, bobCloseFn := newTestService(t, env) defer bobCloseFn() testAuthSetup(t, bobService) testImportKey(t, bobService, bob) testImportID(t, bobService, alice.ID()) testEncryptDecryptStream(t, env, aliceService, bobService, bytes.Repeat([]byte{0x31}, 5), alice.ID().String(), []string{bob.ID().String()}, DefaultEncrypt, false, alice.ID()) testEncryptDecryptStream(t, env, aliceService, bobService, bytes.Repeat([]byte{0x31}, 5), alice.ID().String(), []string{bob.ID().String()}, SaltpackEncrypt, false, alice.ID()) testEncryptDecryptStream(t, env, aliceService, bobService, bytes.Repeat([]byte{0x31}, 5), alice.ID().String(), []string{bob.ID().String()}, SaltpackEncrypt, true, alice.ID()) testEncryptDecryptStream(t, env, aliceService, bobService, bytes.Repeat([]byte{0x31}, 5), alice.ID().String(), []string{bob.ID().String()}, SaltpackSigncrypt, false, alice.ID()) testEncryptDecryptStream(t, env, aliceService, bobService, bytes.Repeat([]byte{0x31}, 5), alice.ID().String(), []string{bob.ID().String()}, SaltpackSigncrypt, true, alice.ID()) testEncryptDecryptStream(t, env, aliceService, bobService, bytes.Repeat([]byte{0x31}, (1024*1024)+5), alice.ID().String(), []string{bob.ID().String()}, DefaultEncrypt, false, alice.ID()) testEncryptDecryptStream(t, env, aliceService, bobService, bytes.Repeat([]byte{0x31}, (1024*1024)+5), alice.ID().String(), []string{bob.ID().String()}, SaltpackEncrypt, false, alice.ID()) testEncryptDecryptStream(t, env, aliceService, bobService, bytes.Repeat([]byte{0x31}, (1024*1024)+5), alice.ID().String(), []string{bob.ID().String()}, SaltpackEncrypt, true, alice.ID()) testEncryptDecryptStream(t, env, aliceService, bobService, bytes.Repeat([]byte{0x31}, (1024*1024)+5), alice.ID().String(), []string{bob.ID().String()}, SaltpackSigncrypt, false, alice.ID()) testEncryptDecryptStream(t, env, aliceService, bobService, bytes.Repeat([]byte{0x31}, (1024*1024)+5), alice.ID().String(), []string{bob.ID().String()}, SaltpackSigncrypt, true, alice.ID()) // TODO: Test timeout if data stops streaming } func testEncryptDecryptStream(t *testing.T, env *testEnv, aliceService *service, bobService *service, plaintext []byte, sender string, recipients []string, mode EncryptMode, armored bool, expectedSender keys.ID) { encrypted, err := testEncryptStream(t, env, aliceService, plaintext, sender, recipients, mode, armored) require.NoError(t, err) out, outSigner, outMode, err := testDecryptStream(t, env, bobService, encrypted) require.NoError(t, err) require.Equal(t, plaintext, out) if mode == DefaultEncrypt { require.Equal(t, SaltpackSigncrypt, outMode) } else { require.Equal(t, mode, outMode) } require.NotNil(t, outSigner) require.Equal(t, expectedSender.String(), outSigner.ID) } func testEncryptStream(t *testing.T, env *testEnv, service *service, plaintext []byte, sender string, recipients []string, mode EncryptMode, armored bool) ([]byte, error) { // TODO: Assert out client, clientCloseFn := newTestRPCClient(t, service, env, "", nil) defer clientCloseFn() ctx, cancel := context.WithCancel(context.TODO()) defer cancel() streamClient, streamErr := client.RPCClient().EncryptStream(ctx) require.NoError(t, streamErr) chunkSize := 1024 * 1024 go func() { done := false err := streamClient.Send(&EncryptInput{ Recipients: recipients, Sender: sender, Options: &EncryptOptions{ Mode: mode, }, }) require.NoError(t, err) for chunk := 0; true; chunk++ { s, e := (chunk * chunkSize), ((chunk + 1) * chunkSize) if e > len(plaintext) { e = len(plaintext) done = true } logger.Debugf("(Test) Send chunk %d", len(plaintext[s:e])) err := streamClient.Send(&EncryptInput{ Data: plaintext[s:e], }) require.NoError(t, err) if done { logger.Debugf("(Test) Send done") break } } logger.Debugf("(Test) Close send") closeErr := streamClient.CloseSend() require.NoError(t, closeErr) }() var buf bytes.Buffer for { resp, recvErr := streamClient.Recv() if recvErr != nil { if recvErr == io.EOF { logger.Debugf("(Test) Recv EOF, done") break } require.NoError(t, recvErr) } logger.Infof("(Test) Recv %d", len(resp.Data)) _, writeErr := buf.Write(resp.Data) require.NoError(t, writeErr) } return buf.Bytes(), nil } func testDecryptStream(t *testing.T, env *testEnv, service *service, b []byte) ([]byte, *Key, EncryptMode, error) { // TODO: Assert out client, clientCloseFn := newTestRPCClient(t, service, env, "", nil) defer clientCloseFn() ctx, cancel := context.WithCancel(context.TODO()) defer cancel() chunkSize := 1024 * 1024 streamClient, err := client.RPCClient().DecryptStream(ctx) if err != nil { return nil, nil, DefaultEncrypt, err } go func() { done := false for chunk := 0; ; chunk++ { s, e := (chunk * chunkSize), ((chunk + 1) * chunkSize) if e > len(b) { e = len(b) done = true } err := streamClient.Send(&DecryptInput{ Data: b[s:e], }) require.NoError(t, err) if done { break } } closeErr := streamClient.CloseSend() require.NoError(t, closeErr) }() var bufOut bytes.Buffer var sender *Key var mode EncryptMode for { resp, recvErr := streamClient.Recv() if recvErr != nil { return nil, nil, DefaultEncrypt, recvErr } _, writeErr := bufOut.Write(resp.Data) require.NoError(t, writeErr) if resp.Sender != nil { sender = resp.Sender } mode = resp.Mode if len(resp.Data) == 0 { break } } return bufOut.Bytes(), sender, mode, nil } func TestDecryptStreamInvalid(t *testing.T) { env := newTestEnv(t) service, closeFn := newTestService(t, env) defer closeFn() testAuthSetup(t, service) testImportKey(t, service, alice) testImportKey(t, service, bob) _, _, _, err := testDecryptStream(t, env, service, []byte("???")) require.Error(t, err) st, ok := status.FromError(err) require.True(t, ok) require.Equal(t, "invalid data", st.Message()) } func TestEncryptDecryptByUser(t *testing.T) { env := newTestEnv(t) aliceService, aliceCloseFn := newTestService(t, env) defer aliceCloseFn() testAuthSetup(t, aliceService) testImportKey(t, aliceService, alice) testUserSetupGithub(t, env, aliceService, alice, "alice") bobService, bobCloseFn := newTestService(t, env) defer bobCloseFn() testAuthSetup(t, bobService) testImportKey(t, bobService, bob) testUserSetupGithub(t, env, bobService, bob, "bob") testPull(t, aliceService, bob.ID()) testPull(t, bobService, alice.ID()) testEncryptDecrypt(t, aliceService, bobService, "alice@github", "bob@github", SaltpackEncrypt, false, alice.ID()) testEncryptDecrypt(t, aliceService, bobService, "alice@github", "bob@github", SaltpackSigncrypt, false, alice.ID()) testEncryptDecryptStream(t, env, aliceService, bobService, bytes.Repeat([]byte{0x31}, (1024*1024)+5), "alice@github", []string{"bob@github"}, SaltpackEncrypt, false, alice.ID()) testEncryptDecryptStream(t, env, aliceService, bobService, bytes.Repeat([]byte{0x31}, (1024*1024)+5), "alice@github", []string{"bob@github"}, SaltpackSigncrypt, false, alice.ID()) } func TestEncryptDecryptFile(t *testing.T) { env := newTestEnv(t) aliceService, aliceCloseFn := newTestService(t, env) defer aliceCloseFn() testAuthSetup(t, aliceService) testImportKey(t, aliceService, alice) bobService, bobCloseFn := newTestService(t, env) defer bobCloseFn() testAuthSetup(t, bobService) testImportKey(t, bobService, bob) testImportID(t, bobService, alice.ID()) b := []byte("test message") inPath := keys.RandTempPath() encPath := inPath + ".enc" decPath := inPath + ".dec" defer os.Remove(inPath) defer os.Remove(encPath) defer os.Remove(decPath) writeErr := ioutil.WriteFile(inPath, b, 0644) require.NoError(t, writeErr) aliceClient, aliceClientCloseFn := newTestRPCClient(t, aliceService, env, "", nil) defer aliceClientCloseFn() options := &EncryptOptions{} err := encryptFile(aliceClient, inPath, encPath, []string{bob.ID().String()}, alice.ID().String(), options) require.NoError(t, err) // encrypted, err := ioutil.ReadFile(outPath) // require.NoError(t, err) // t.Logf("encrypted: %s", string(encrypted)) bobClient, bobClientCloseFn := newTestRPCClient(t, bobService, env, "", nil) defer bobClientCloseFn() dec, err := decryptFile(bobClient, encPath, decPath) require.NoError(t, err) require.NotNil(t, dec.Sender) require.Equal(t, alice.ID().String(), dec.Sender.ID) require.Equal(t, decPath, dec.Out) bout, err := ioutil.ReadFile(decPath) require.NoError(t, err) require.Equal(t, b, bout) os.Remove(decPath) // Decrypt .dat file datPath := inPath + ".dat" err = os.Rename(encPath, datPath) require.NoError(t, err) dec, err = decryptFile(bobClient, datPath, "") require.NoError(t, err) require.NotNil(t, dec.Sender) require.Equal(t, alice.ID().String(), dec.Sender.ID) ext := path.Ext(datPath) noExt := datPath[0 : len(datPath)-len(ext)] require.Equal(t, noExt+"-2.dat", dec.Out) } func TestEncryptVerifyFailed(t *testing.T) { // SetLogger(NewLogger(DebugLevel)) // keys.SetLogger(NewLogger(DebugLevel)) env := newTestEnv(t) aliceService, aliceCloseFn := newTestService(t, env) defer aliceCloseFn() testAuthSetup(t, aliceService) testImportKey(t, aliceService, alice) testUserSetupGithub(t, env, aliceService, alice, "alice") bobService, bobCloseFn := newTestService(t, env) defer bobCloseFn() testAuthSetup(t, bobService) testImportKey(t, bobService, bob) tub := testUserSetupGithub(t, env, bobService, bob, "bob") // Encrypt (not found) _, err := aliceService.Encrypt(context.TODO(), &EncryptRequest{ Data: []byte("hi"), Recipients: []string{"bob@github"}, Sender: "alice@github", }) require.EqualError(t, err, "bob@github not found") testPull(t, aliceService, bob.ID()) // Set 500 error for bob@github env.client.SetProxy(tub.URL, func(ctx context.Context, req *http.Request, headers []http.Header) http.ProxyResponse { return http.ProxyResponse{Err: http.Error{StatusCode: 500}} }) testPull(t, aliceService, bob.ID()) // Encrypt (bob, error) _, err = aliceService.Encrypt(context.TODO(), &EncryptRequest{ Data: []byte("hi"), Recipients: []string{"bob@github"}, Sender: "alice@github", }) require.EqualError(t, err, "user bob@github has failed status connection-fail") }