package service
import (
"bufio"
"context"
"io"
"os"
"github.com/keys-pub/keys"
"github.com/keys-pub/keys/saltpack"
"github.com/pkg/errors"
"google.golang.org/grpc"
)
func (s *service) findSender(ctx context.Context, kid keys.ID) (*Key, error) {
if kid == "" {
logger.Infof("No decrypt sender")
return nil, nil
}
convert, err := s.convertIfX25519ID(kid)
if err != nil {
return nil, err
}
kid = convert
return s.key(ctx, kid)
}
func (s *service) Decrypt(ctx context.Context, req *DecryptRequest) (*DecryptResponse, error) {
out, key, enc, err := saltpack.Open(req.Data, s.vault)
if err != nil {
if err.Error() == "failed to read header bytes" {
return nil, errors.Errorf("invalid data")
}
return nil, err
}
mode, err := modeFromEncoding(enc)
if err != nil {
return nil, err
}
var sender *Key
if key != nil {
s, err := s.findSender(ctx, key.ID())
if err != nil {
return nil, err
}
sender = s
}
return &DecryptResponse{
Data: out,
Sender: sender,
Mode: mode,
}, nil
}
func (s *service) DecryptFile(srv RPC_DecryptFileServer) error {
req, err := srv.Recv()
if err != nil {
return err
}
if req.In == "" {
return errors.Errorf("in not specified")
}
out, err := resolveOutPath(req.Out, req.In, ".enc")
if err != nil {
return err
}
sender, mode, err := s.decryptWriteInOut(srv.Context(), req.In, out)
if err != nil {
return errors.Wrapf(err, "failed to decrypt")
}
if err := srv.Send(&DecryptFileOutput{
Sender: sender,
Out: out,
Mode: mode,
}); err != nil {
return err
}
return nil
}
type DecryptStreamClient interface {
Send(*DecryptInput) error
Recv() (*DecryptOutput, error)
grpc.ClientStream
}
func NewDecryptStreamClient(ctx context.Context, cl RPCClient) (DecryptStreamClient, error) {
return cl.DecryptStream(ctx)
}
func (s *service) DecryptStream(srv RPC_DecryptStreamServer) error {
return s.decryptStream(srv)
}
type decryptStreamServer interface {
Send(*DecryptOutput) error
Recv() (*DecryptInput, error)
grpc.ServerStream
}
func (s *service) decryptStream(srv decryptStreamServer) error {
recvFn := func() ([]byte, error) {
req, recvErr := srv.Recv()
if recvErr != nil {
return nil, recvErr
}
return req.Data, nil
}
reader := newStreamReader(srv.Context(), recvFn)
out, key, enc, err := saltpack.NewReader(reader, s.vault)
if err != nil {
return err
}
mode, err := modeFromEncoding(enc)
if err != nil {
return err
}
var sender *Key
if key != nil {
s, err := s.findSender(srv.Context(), key.ID())
if err != nil {
return err
}
sender = s
}
sendFn := func(b []byte, sender *Key) error {
resp := DecryptOutput{
Data: b,
Sender: sender,
Mode: mode,
}
return srv.Send(&resp)
}
return s.readFromStream(srv.Context(), out, sender, sendFn)
}
func modeFromEncoding(enc saltpack.Encoding) (EncryptMode, error) {
switch enc {
case saltpack.EncryptEncoding:
return SaltpackEncrypt, nil
case saltpack.SigncryptEncoding:
return SaltpackSigncrypt, nil
default:
return DefaultEncrypt, errors.Errorf("invalid encoding %q", enc)
}
}
func (s *service) decryptWriteInOut(ctx context.Context, in string, out string) (*Key, EncryptMode, error) {
inFile, err := os.Open(in) if err != nil {
return nil, DefaultEncrypt, errors.Wrapf(err, "failed to open %s", in)
}
defer func() {
_ = inFile.Close()
}()
reader := bufio.NewReader(inFile)
decReader, key, enc, err := saltpack.NewReader(reader, s.vault)
if err != nil {
return nil, DefaultEncrypt, err
}
mode, err := modeFromEncoding(enc)
if err != nil {
return nil, DefaultEncrypt, err
}
if err := writeFile(out, decReader); err != nil {
return nil, DefaultEncrypt, err
}
if err := inFile.Close(); err != nil {
return nil, DefaultEncrypt, err
}
var sender *Key
if key != nil {
s, err := s.findSender(ctx, key.ID())
if err != nil {
return nil, DefaultEncrypt, err
}
sender = s
}
return sender, mode, nil
}
func writeFile(out string, reader io.Reader) error {
outTmp := out + ".tmp"
outFile, err := os.Create(outTmp)
if err != nil {
return err
}
defer func() {
_ = outFile.Close()
_ = os.Remove(outTmp)
}()
writer := bufio.NewWriter(outFile)
if _, err := writer.ReadFrom(reader); err != nil {
return err
}
if err := writer.Flush(); err != nil {
return err
}
if err := outFile.Close(); err != nil {
return err
}
if err := os.Rename(outTmp, out); err != nil {
return err
}
return nil
}