internal/dkim/dkim.go (view raw)
1package dkim
2
3import (
4 "bytes"
5 "context"
6 "database/sql"
7 "errors"
8 "fmt"
9 "strings"
10
11 "postern/internal/db"
12 "postern/internal/model"
13 "postern/internal/persistence"
14)
15
16type Config struct {
17 DB *db.DB
18 Persistence *persistence.Persistence
19}
20
21type DKIM struct {
22 db *db.DB
23 persistence *persistence.Persistence
24}
25
26func New(config *Config) *DKIM {
27 return &DKIM{
28 db: config.DB,
29 persistence: config.Persistence,
30 }
31}
32
33// Add generates a new RSA-2048 private key for the given selector/domain, stores it
34// as a blob, inserts the corresponding row in the database and returns the expected
35// DNS TXT record that should be published before enabling the selector.
36func (d *DKIM) Add(ctx context.Context, selector, domain string) (string, error) {
37 if selector == "" {
38 return "", fmt.Errorf("invalid selector")
39 }
40 if strings.Contains(selector, ".") {
41 return "", fmt.Errorf("selector must not contain '.'")
42 }
43 if domain == "" {
44 return "", fmt.Errorf("invalid domain")
45 }
46
47 privKey, keyType := genRSAPrivateKey()
48 encodedPrivKey := encodePrivateKey(privKey)
49
50 blobName, _, err := d.persistence.WriteBlob(bytes.NewReader(encodedPrivKey))
51 if err != nil {
52 return "", err
53 }
54
55 err = d.db.InsertDKIMForDomain(ctx, model.DKIM{
56 Domain: domain,
57 Selector: selector,
58 BlobAddress: blobName,
59 KeyType: keyType,
60 Enabled: false,
61 })
62 if err != nil {
63 return "", err
64 }
65
66 return pubKeyRecord(privKey.Public()), nil
67}
68
69// List returns all configured DKIM selectors.
70func (d *DKIM) List(ctx context.Context) ([]model.DKIM, error) {
71 return d.db.ListDKIM(ctx)
72}
73
74// PublicDNSRecord returns the expected DNS TXT record for the given selector/domain
75// as it should be published under selector._domainkey.domain.
76func (d *DKIM) PublicDNSRecord(ctx context.Context, selector, domain string) (string, error) {
77 privKey, _, err := d.retrievePrivateKey(ctx, selector, domain)
78 if err != nil {
79 return "", err
80 }
81 return pubKeyRecord(privKey.Public()), nil
82}
83
84// SetEnabled enables or disables the given selector/domain selector.
85func (d *DKIM) SetEnabled(ctx context.Context, selector, domain string, enabled bool) error {
86 return d.db.SetDKIMEnabled(ctx, selector, domain, enabled)
87}
88
89// Remove deletes the private key and the database entry.
90func (d *DKIM) Remove(ctx context.Context, selector, domain string) error {
91 dkimRow, err := d.db.GetDKIMForSelectorDomain(ctx, selector, domain)
92 if err != nil {
93 if errors.Is(err, sql.ErrNoRows) {
94 return errors.New("DKIM entry not found")
95 }
96 return err
97 }
98
99 tx, err := d.db.GetWriteTx(ctx)
100 if err != nil {
101 return err
102 }
103
104 err = db.RemoveDKIM(ctx, tx, selector, domain)
105 if err != nil {
106 _ = tx.Rollback()
107 return err
108 }
109
110 err = d.persistence.RemoveBlob(dkimRow.BlobAddress)
111 if err != nil {
112 _ = tx.Rollback()
113 return err
114 }
115
116 return tx.Commit()
117}