package dkim import ( "bytes" "context" "database/sql" "errors" "fmt" "strings" "postern/internal/db" "postern/internal/model" "postern/internal/persistence" ) type Config struct { DB *db.DB Persistence *persistence.Persistence } type DKIM struct { db *db.DB persistence *persistence.Persistence } func New(config *Config) *DKIM { return &DKIM{ db: config.DB, persistence: config.Persistence, } } // Add generates a new RSA-2048 private key for the given selector/domain, stores it // as a blob, inserts the corresponding row in the database and returns the expected // DNS TXT record that should be published before enabling the selector. func (d *DKIM) Add(ctx context.Context, selector, domain string) (string, error) { if selector == "" { return "", fmt.Errorf("invalid selector") } if strings.Contains(selector, ".") { return "", fmt.Errorf("selector must not contain '.'") } if domain == "" { return "", fmt.Errorf("invalid domain") } privKey, keyType := genRSAPrivateKey() encodedPrivKey := encodePrivateKey(privKey) blobName, _, err := d.persistence.WriteBlob(bytes.NewReader(encodedPrivKey)) if err != nil { return "", err } err = d.db.InsertDKIMForDomain(ctx, model.DKIM{ Domain: domain, Selector: selector, BlobAddress: blobName, KeyType: keyType, Enabled: false, }) if err != nil { return "", err } return pubKeyRecord(privKey.Public()), nil } // List returns all configured DKIM selectors. func (d *DKIM) List(ctx context.Context) ([]model.DKIM, error) { return d.db.ListDKIM(ctx) } // PublicDNSRecord returns the expected DNS TXT record for the given selector/domain // as it should be published under selector._domainkey.domain. func (d *DKIM) PublicDNSRecord(ctx context.Context, selector, domain string) (string, error) { dbRecord, err := d.db.GetDKIMForSelectorDomain(ctx, selector, domain) if err != nil { return "", err } privKey, err := d.readPrivateKey(dbRecord.BlobAddress) if err != nil { return "", err } return pubKeyRecord(privKey.Public()), nil } // SetEnabled enables or disables the given selector/domain selector. func (d *DKIM) SetEnabled(ctx context.Context, selector, domain string, enabled bool) error { return d.db.SetDKIMEnabled(ctx, selector, domain, enabled) } // Remove deletes the private key and the database entry. func (d *DKIM) Remove(ctx context.Context, selector, domain string) error { dkimRow, err := d.db.GetDKIMForSelectorDomain(ctx, selector, domain) if err != nil { if errors.Is(err, sql.ErrNoRows) { return errors.New("DKIM entry not found") } return err } tx, err := d.db.GetWriteTx(ctx) if err != nil { return err } err = db.RemoveDKIM(ctx, tx, selector, domain) if err != nil { _ = tx.Rollback() return err } err = d.persistence.RemoveBlob(dkimRow.BlobAddress) if err != nil { _ = tx.Rollback() return err } return tx.Commit() }