Skip to content

Commit

Permalink
service: Encrypt add recipient, default signcrypt (#56)
Browse files Browse the repository at this point in the history
  • Loading branch information
gabriel authored Sep 3, 2020
1 parent a9e2b46 commit 7b86d8a
Show file tree
Hide file tree
Showing 11 changed files with 1,081 additions and 710 deletions.
2 changes: 1 addition & 1 deletion scripts/test/encrypt.sh
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ diff "$infile" "$infile.forig"
echo "- decrypt (file, unrecognized ext)"
mv "$infile.enc" "$infile.dat"
$keycmd decrypt -in "$infile.dat"
diff "$infile.dat.dec" "$infile"
diff "$infile-2.dat" "$infile"

echo "- encrypt/decrypt (signcrypt, piped)"
echo "testing" > $infile
Expand Down
43 changes: 23 additions & 20 deletions service/cmd_encrypt.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,25 +20,32 @@ func encryptCommands(client *Client) []cli.Command {
Usage: "Encrypt",
Flags: []cli.Flag{
cli.StringSliceFlag{Name: "recipient, r", Usage: "recipients"},
cli.StringFlag{Name: "sender, signer, s", Usage: "signer (or anonymous if not specified)"},
cli.StringFlag{Name: "sender, signer, s", Usage: "signer (or anonymous)"},
cli.BoolFlag{Name: "armor, a", Usage: "armored"},
cli.StringFlag{Name: "in, i", Usage: "file to read"},
cli.StringFlag{Name: "out, o", Usage: "file to write, defaults to <in>.enc"},
cli.StringFlag{Name: "mode, m", Usage: "encryption mode: encrypt (default) or signcrypt"},
cli.StringFlag{Name: "mode, m", Usage: "encryption mode: signcrypt, encrypt or default (signcrypt if signing, encrypt otherwise)"},
cli.BoolFlag{Hidden: true, Name: "no-signer-recipient", Usage: "don't add signer to recipients"},
},
Action: func(c *cli.Context) error {
if c.String("in") == "" && c.String("out") != "" {
return errors.Errorf("-out option is unsupported without -in")
}

if c.String("in") != "" {
return encryptFileForCLI(c, client)
}

mode, err := encryptModeFromString(c.String("mode"))
if err != nil {
return err
}
options := &EncryptOptions{
Mode: mode,
Armored: c.Bool("armor"),
NoSenderRecipient: c.Bool("no-sender-recipient"),
}

if c.String("in") != "" {
return encryptFileForCLI(c, client, options)
}

reader := bufio.NewReader(os.Stdin)
writer := os.Stdout

Expand All @@ -50,8 +57,7 @@ func encryptCommands(client *Client) []cli.Command {
if err := encryptClient.Send(&EncryptInput{
Recipients: c.StringSlice("recipient"),
Sender: c.String("sender"),
Mode: mode,
Armored: c.Bool("armor"),
Options: options,
}); err != nil {
return err
}
Expand Down Expand Up @@ -175,15 +181,11 @@ func encryptCommands(client *Client) []cli.Command {
}
}

func encryptFileForCLI(c *cli.Context, client *Client) error {
mode, err := encryptModeFromString(c.String("mode"))
if err != nil {
return err
}
return encryptFile(client, c.StringSlice("recipient"), c.String("sender"), mode, c.Bool("armor"), c.String("in"), c.String("out"))
func encryptFileForCLI(c *cli.Context, client *Client, options *EncryptOptions) error {
return encryptFile(client, c.String("in"), c.String("out"), c.StringSlice("recipient"), c.String("sender"), options)
}

func encryptFile(client *Client, recipients []string, sender string, mode EncryptMode, armored bool, in string, out string) error {
func encryptFile(client *Client, in string, out string, recipients []string, sender string, options *EncryptOptions) error {
in, err := filepath.Abs(in)
if err != nil {
return err
Expand All @@ -201,12 +203,11 @@ func encryptFile(client *Client, recipients []string, sender string, mode Encryp
}

if err := encryptClient.Send(&EncryptFileInput{
Recipients: recipients,
Sender: sender,
Mode: mode,
Armored: armored,
In: in,
Out: out,
Recipients: recipients,
Sender: sender,
Options: options,
}); err != nil {
return err
}
Expand Down Expand Up @@ -272,7 +273,9 @@ func decryptFile(client *Client, in string, out string) (*DecryptFileOutput, err

func encryptModeFromString(s string) (EncryptMode, error) {
switch s {
case "", "encrypt":
case "", "default":
return DefaultEncrypt, nil
case "encrypt":
return SaltpackEncrypt, nil
case "signcrypt":
return SaltpackSigncrypt, nil
Expand Down
70 changes: 44 additions & 26 deletions service/encrypt.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,40 +19,64 @@ type encrypt struct {
armored bool
}

func (s *service) newEncrypt(ctx context.Context, recipients []string, sender string, mode EncryptMode, armored bool) (*encrypt, error) {
if len(recipients) == 0 {
return nil, errors.Errorf("no recipients specified")
func (s *service) newEncrypt(ctx context.Context, recipients []string, sender string, options *EncryptOptions) (*encrypt, error) {
if options == nil {
options = &EncryptOptions{}
}

var skid keys.ID
if sender != "" {
s, err := s.lookup(ctx, sender, &LookupOpts{Verify: true})
if err != nil {
return nil, err
}
skid = s
}

recs, err := s.lookupAll(ctx, recipients, &LookupOpts{Verify: true})
if err != nil {
return nil, err
}

if mode == DefaultEncrypt {
mode = SaltpackEncrypt
// Add sender as a recipient (unless options.NoSenderRecipient).
recsSet := keys.NewIDSet(recs...)
if !options.NoSenderRecipient && skid != "" {
recsSet.Add(skid)
}

var kid keys.ID
if sender != "" {
s, err := s.lookup(ctx, sender, &LookupOpts{Verify: true})
if err != nil {
return nil, err
if recsSet.Size() == 0 {
return nil, errors.Errorf("no recipients specified")
}

if skid != "" && options.NoSign && options.NoSenderRecipient {
return nil, errors.Errorf("sender specified without signing or adding as a recipient")
}

if options.NoSign {
skid = ""
}

// For default mode, if signing, use signcrypt, otherwise use encrypt.
mode := options.Mode
if mode == DefaultEncrypt {
if skid != "" {
mode = SaltpackSigncrypt
} else {
mode = SaltpackEncrypt
}
kid = s
}

return &encrypt{
recipients: recs,
sender: kid,
recipients: recsSet.IDs(),
sender: skid,
mode: mode,
armored: armored,
armored: options.Armored,
}, nil
}

// Encrypt (RPC) data.
func (s *service) Encrypt(ctx context.Context, req *EncryptRequest) (*EncryptResponse, error) {
enc, err := s.newEncrypt(ctx, req.Recipients, req.Sender, req.Mode, req.Armored)
enc, err := s.newEncrypt(ctx, req.Recipients, req.Sender, req.Options)
if err != nil {
return nil, err
}
Expand All @@ -64,22 +88,16 @@ func (s *service) Encrypt(ctx context.Context, req *EncryptRequest) (*EncryptRes
if err != nil {
return nil, err
}
out, err = saltpack.Encrypt(req.Data, req.Armored, sbk, enc.recipients...)
out, err = saltpack.Encrypt(req.Data, enc.armored, sbk, enc.recipients...)
if err != nil {
return nil, err
}
case SaltpackSigncrypt:
if enc.sender == "" {
return nil, errors.Errorf("no sender specified: sender is required for signcrypt mode")
}
sk, err := s.vault.EdX25519Key(enc.sender)
if err != nil {
return nil, err
}
if sk == nil {
return nil, keys.NewErrNotFound(enc.sender.String())
}
out, err = saltpack.Signcrypt(req.Data, req.Armored, sk, enc.recipients...)
out, err = saltpack.Signcrypt(req.Data, enc.armored, sk, enc.recipients...)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -192,7 +210,7 @@ func (s *service) EncryptFile(srv Keys_EncryptFileServer) error {
out = in + ".enc"
}

enc, err := s.newEncrypt(srv.Context(), req.Recipients, req.Sender, req.Mode, req.Armored)
enc, err := s.newEncrypt(srv.Context(), req.Recipients, req.Sender, req.Options)
if err != nil {
return err
}
Expand Down Expand Up @@ -239,7 +257,7 @@ func (s *service) EncryptStream(srv Keys_EncryptStreamServer) error {
return errors.Errorf("stream already initialized")
}

enc, err := s.newEncrypt(ctx, req.Recipients, req.Sender, req.Mode, req.Armored)
enc, err := s.newEncrypt(ctx, req.Recipients, req.Sender, req.Options)
if err != nil {
return err
}
Expand All @@ -252,7 +270,7 @@ func (s *service) EncryptStream(srv Keys_EncryptStreamServer) error {

} else {
// Make sure request only sends data after init
if len(req.Recipients) != 0 || req.Sender != "" {
if len(req.Recipients) != 0 || req.Sender != "" || req.Options != nil {
return errors.Errorf("after stream is initalized, only data should be sent")
}
}
Expand Down
76 changes: 56 additions & 20 deletions service/encrypt_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,11 @@ func testEncryptDecrypt(t *testing.T, aliceService *service, bobService *service
// Encrypt
encryptResp, err := aliceService.Encrypt(context.TODO(), &EncryptRequest{
Data: []byte(message),
Recipients: []string{recipient},
Sender: sender,
Recipients: []string{recipient, sender},
Mode: mode,
Options: &EncryptOptions{
Mode: mode,
},
})
require.NoError(t, err)
require.NotEmpty(t, encryptResp.Data)
Expand All @@ -83,24 +85,25 @@ func testEncryptDecryptErrors(t *testing.T, aliceService *service, bobService *s

encryptResp, err := aliceService.Encrypt(context.TODO(), &EncryptRequest{
Data: []byte(message),
Sender: alice.ID().String(),
Recipients: []string{bob.ID().String()},
Mode: mode,
Armored: armored,
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
// TODO: Include alice by default?
_, 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),
Sender: alice.ID().String(),
Data: []byte(message),
})
require.EqualError(t, err, "no recipients specified")

Expand Down Expand Up @@ -135,8 +138,11 @@ func TestEncryptAnonymous(t *testing.T) {
// Encrypt
encryptResp, err := aliceService.Encrypt(context.TODO(), &EncryptRequest{
Data: []byte(message),
Sender: "",
Recipients: []string{bob.ID().String()},
Sender: "",
Options: &EncryptOptions{
Mode: SaltpackEncrypt,
},
})
require.NoError(t, err)
require.NotEmpty(t, encryptResp.Data)
Expand All @@ -147,15 +153,44 @@ func TestEncryptAnonymous(t *testing.T) {
})
require.NoError(t, err)
require.Equal(t, message, string(decryptResp.Data))
require.Nil(t, decryptResp.Sender)

// Encrypt
_, err = aliceService.Encrypt(context.TODO(), &EncryptRequest{
// 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()},
Mode: SaltpackSigncrypt,
Sender: alice.ID().String(),
Options: &EncryptOptions{
NoSign: true,
},
})
require.NoError(t, err)

// Decrypt
decryptResp, err = bobService.Decrypt(context.TODO(), &DecryptRequest{
Data: encryptResp.Data,
})
require.EqualError(t, err, "no sender specified: sender is required for signcrypt mode")
require.NoError(t, err)
require.Equal(t, message, string(decryptResp.Data))
require.Nil(t, decryptResp.Sender)
}

func TestEncryptDecryptStream(t *testing.T) {
Expand Down Expand Up @@ -198,7 +233,7 @@ func testEncryptDecryptStream(t *testing.T, env *testEnv,
require.NoError(t, err)
require.Equal(t, plaintext, out)
if mode == DefaultEncrypt {
require.Equal(t, SaltpackEncrypt, outMode)
require.Equal(t, SaltpackSigncrypt, outMode)
} else {
require.Equal(t, mode, outMode)
}
Expand All @@ -223,7 +258,9 @@ func testEncryptStream(t *testing.T, env *testEnv, service *service, plaintext [
err := streamClient.Send(&EncryptInput{
Recipients: recipients,
Sender: sender,
Mode: mode,
Options: &EncryptOptions{
Mode: mode,
},
})
require.NoError(t, err)
for chunk := 0; true; chunk++ {
Expand Down Expand Up @@ -392,7 +429,8 @@ func TestEncryptDecryptFile(t *testing.T) {
aliceClient, aliceClientCloseFn := newTestRPCClient(t, aliceService, env, "", nil)
defer aliceClientCloseFn()

err := encryptFile(aliceClient, []string{bob.ID().String()}, alice.ID().String(), SaltpackEncrypt, false, inPath, encPath)
options := &EncryptOptions{}
err := encryptFile(aliceClient, inPath, encPath, []string{bob.ID().String()}, alice.ID().String(), options)
require.NoError(t, err)

// encrypted, err := ioutil.ReadFile(outPath)
Expand Down Expand Up @@ -448,9 +486,8 @@ func TestEncryptUnverified(t *testing.T) {
// Encrypt (not found)
_, err := aliceService.Encrypt(context.TODO(), &EncryptRequest{
Data: []byte("hi"),
Sender: "alice@github",
Recipients: []string{"bob@github"},
Mode: SaltpackEncrypt,
Sender: "alice@github",
})
require.EqualError(t, err, "not found bob@github")

Expand All @@ -464,9 +501,8 @@ func TestEncryptUnverified(t *testing.T) {
// Encrypt (bob, error)
_, err = aliceService.Encrypt(context.TODO(), &EncryptRequest{
Data: []byte("hi"),
Sender: "alice@github",
Recipients: []string{"bob@github"},
Mode: SaltpackEncrypt,
Sender: "alice@github",
})
require.EqualError(t, err, "user bob@github has failed status connection-fail")
}
Loading

0 comments on commit 7b86d8a

Please sign in to comment.