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 dbRecord, err := d.db.GetDKIMForSelectorDomain(ctx, selector, domain)
78 if err != nil {
79 return "", err
80 }
81 privKey, err := d.readPrivateKey(dbRecord.BlobAddress)
82 if err != nil {
83 return "", err
84 }
85 return pubKeyRecord(privKey.Public()), nil
86}
87
88// SetEnabled enables or disables the given selector/domain selector.
89func (d *DKIM) SetEnabled(ctx context.Context, selector, domain string, enabled bool) error {
90 return d.db.SetDKIMEnabled(ctx, selector, domain, enabled)
91}
92
93// Remove deletes the private key and the database entry.
94func (d *DKIM) Remove(ctx context.Context, selector, domain string) error {
95 dkimRow, err := d.db.GetDKIMForSelectorDomain(ctx, selector, domain)
96 if err != nil {
97 if errors.Is(err, sql.ErrNoRows) {
98 return errors.New("DKIM entry not found")
99 }
100 return err
101 }
102
103 tx, err := d.db.GetWriteTx(ctx)
104 if err != nil {
105 return err
106 }
107
108 err = db.RemoveDKIM(ctx, tx, selector, domain)
109 if err != nil {
110 _ = tx.Rollback()
111 return err
112 }
113
114 err = d.persistence.RemoveBlob(dkimRow.BlobAddress)
115 if err != nil {
116 _ = tx.Rollback()
117 return err
118 }
119
120 return tx.Commit()
121}