3
0
mirror of https://github.com/ergochat/ergo.git synced 2025-04-23 16:27:56 +02:00
ergo/irc/email/dkim.go
Shivaram Lingamneni 68cee9e2cd
use emersion/go-msgauth for DKIM (#2242)
Fixes #1041 (support ed25519-sha256 for DKIM)
2025-04-07 00:24:08 -04:00

103 lines
2.3 KiB
Go

// Copyright (c) 2020 Shivaram Lingamneni
// released under the MIT license
package email
import (
"bytes"
"crypto"
"crypto/ed25519"
"crypto/rsa"
"crypto/x509"
"encoding/pem"
"errors"
"fmt"
"os"
dkim "github.com/emersion/go-msgauth/dkim"
)
var (
ErrMissingFields = errors.New("DKIM config is missing fields")
)
type DKIMConfig struct {
Domain string
Selector string
KeyFile string `yaml:"key-file"`
privKey crypto.Signer
}
func (dkim *DKIMConfig) Enabled() bool {
return dkim.Domain != ""
}
func (dkim *DKIMConfig) Postprocess() (err error) {
if !dkim.Enabled() {
return nil
}
if dkim.Selector == "" || dkim.KeyFile == "" {
return ErrMissingFields
}
keyBytes, err := os.ReadFile(dkim.KeyFile)
if err != nil {
return fmt.Errorf("Could not read DKIM key file: %w", err)
}
dkim.privKey, err = parseDKIMPrivKey(keyBytes)
if err != nil {
return fmt.Errorf("Could not parse DKIM key file: %w", err)
}
return nil
}
func parseDKIMPrivKey(input []byte) (crypto.Signer, error) {
if len(input) == 0 {
return nil, errors.New("DKIM private key is empty")
}
// raw ed25519 private key format
if len(input) == ed25519.PrivateKeySize {
return ed25519.PrivateKey(input), nil
}
d, _ := pem.Decode(input)
if d == nil {
return nil, errors.New("Invalid PEM data for DKIM private key")
}
if rsaKey, err := x509.ParsePKCS1PrivateKey(d.Bytes); err == nil {
return rsaKey, nil
}
if k, err := x509.ParsePKCS8PrivateKey(d.Bytes); err == nil {
switch key := k.(type) {
case *rsa.PrivateKey:
return key, nil
case ed25519.PrivateKey:
return key, nil
default:
return nil, fmt.Errorf("Unacceptable type for DKIM private key: %T", k)
}
}
return nil, errors.New("No acceptable format for DKIM private key")
}
func DKIMSign(message []byte, dkimConfig DKIMConfig) (result []byte, err error) {
options := dkim.SignOptions{
Domain: dkimConfig.Domain,
Selector: dkimConfig.Selector,
Signer: dkimConfig.privKey,
HeaderCanonicalization: dkim.CanonicalizationRelaxed,
BodyCanonicalization: dkim.CanonicalizationRelaxed,
}
input := bytes.NewBuffer(message)
output := bytes.NewBuffer(make([]byte, 0, len(message)+1024))
err = dkim.Sign(output, input, &options)
return output.Bytes(), err
}