1package userbase
2
3import (
4 "bytes"
5 "errors"
6 "fmt"
7 "io"
8
9 "filippo.io/age"
10)
11
12var (
13 ErrUndecryptable = errors.New("userbase: the export cannot be decrypted with this identity")
14 ErrNoRecipients = errors.New("userbase: an export needs at least one recipient")
15)
16
17func Seal(plaintext []byte, recipients ...*age.X25519Recipient) ([]byte, error) {
18 if len(recipients) == 0 {
19 return nil, ErrNoRecipients
20 }
21 rs := make([]age.Recipient, 0, len(recipients))
22 for _, r := range recipients {
23 if r == nil {
24 return nil, ErrNoRecipients
25 }
26 rs = append(rs, r)
27 }
28 var out bytes.Buffer
29 w, err := age.Encrypt(&out, rs...)
30 if err != nil {
31 return nil, fmt.Errorf("userbase: seal: %w", err)
32 }
33 if _, err := w.Write(plaintext); err != nil {
34 return nil, fmt.Errorf("userbase: seal: %w", err)
35 }
36 if err := w.Close(); err != nil {
37 return nil, fmt.Errorf("userbase: seal: %w", err)
38 }
39 return out.Bytes(), nil
40}
41
42func Open(ciphertext []byte, identity *age.X25519Identity) ([]byte, error) {
43 if identity == nil {
44 return nil, ErrUndecryptable
45 }
46 r, err := age.Decrypt(bytes.NewReader(ciphertext), identity)
47 var noMatch *age.NoIdentityMatchError
48 if errors.As(err, &noMatch) {
49 return nil, ErrUndecryptable
50 }
51 if err != nil {
52 return nil, fmt.Errorf("%w: %w", ErrUndecryptable, err)
53 }
54 plain, err := io.ReadAll(r)
55 if err != nil {
56 return nil, fmt.Errorf("%w: %w", ErrUndecryptable, err)
57 }
58 return plain, nil
59}