all repos — postern @ main

Modern mail management

internal/dkim/keys.go (view raw)

  1package dkim
  2
  3import (
  4	"crypto"
  5	"crypto/ed25519"
  6	"crypto/rand"
  7	"crypto/rsa"
  8	"crypto/x509"
  9	"encoding/base64"
 10	"encoding/pem"
 11	"fmt"
 12	"log"
 13	"strings"
 14
 15	"postern/internal/model"
 16)
 17
 18type privateKey interface {
 19	Public() crypto.PublicKey
 20}
 21
 22func genRSAPrivateKey() (privateKey, model.DKIMAlgorithmType) {
 23	nBits := 2048
 24	log.Printf("Generating a %v-bit RSA key", nBits)
 25	privKey, err := rsa.GenerateKey(rand.Reader, nBits)
 26
 27	if err != nil {
 28		log.Fatalf("Failed to generate key: %v", err)
 29	}
 30	return privKey, model.DKIMAlgorithmRSA2048
 31}
 32
 33func genEd25519PrivateKey() (privateKey, model.DKIMAlgorithmType) {
 34	_, privKey, err := ed25519.GenerateKey(rand.Reader)
 35	if err != nil {
 36		log.Fatalf("Failed to generate key: %v", err)
 37	}
 38	return privKey, model.DKIMAlgorithmEd25519
 39}
 40
 41func encodePrivateKey(privateKey privateKey) []byte {
 42	privBytes, err := x509.MarshalPKCS8PrivateKey(privateKey)
 43	if err != nil {
 44		log.Fatalf("Failed to marshal private key: %v", err)
 45	}
 46
 47	privBlock := pem.Block{
 48		Type:  "PRIVATE KEY",
 49		Bytes: privBytes,
 50	}
 51
 52	encoded := pem.EncodeToMemory(&privBlock)
 53	if encoded == nil {
 54		log.Fatalf("Failed to write key PEM block: %v", err)
 55	}
 56	return encoded
 57}
 58
 59func decodePrivateKey(encoded string) (privateKey, error) {
 60	// Decode PEM block from string
 61	block, _ := pem.Decode([]byte(encoded))
 62	if block == nil {
 63		return nil, fmt.Errorf("failed to decode PEM block: no valid PEM data found")
 64	}
 65
 66	// Validate PEM type
 67	if block.Type != "PRIVATE KEY" {
 68		return nil, fmt.Errorf("invalid PEM type: expected 'PRIVATE KEY', got '%s'", block.Type)
 69	}
 70
 71	// Parse PKCS#8 private key
 72	key, err := x509.ParsePKCS8PrivateKey(block.Bytes)
 73	if err != nil {
 74		return nil, fmt.Errorf("failed to parse private key: %w", err)
 75	}
 76
 77	switch k := key.(type) {
 78	case *rsa.PrivateKey:
 79		return k, nil
 80	case ed25519.PrivateKey:
 81		return k, nil
 82	default:
 83		return nil, fmt.Errorf("unsupported private key type: %T", k)
 84	}
 85}
 86
 87func pubKeyRecord(pubKey crypto.PublicKey) string {
 88	var pubBytes []byte
 89	var keyType string
 90	switch p := pubKey.(type) {
 91	case *rsa.PublicKey:
 92		keyType = "rsa"
 93		// RFC 6376 is inconsistent about whether RSA public keys should
 94		// be formatted as RSAPublicKey or SubjectPublicKeyInfo.
 95		// Erratum 3017 (https://www.rfc-editor.org/errata/eid3017)
 96		// proposes allowing both.  We use SubjectPublicKeyInfo for
 97		// consistency with other implementations including opendkim,
 98		// Gmail, and Fastmail.
 99		var err error
100		pubBytes, err = x509.MarshalPKIXPublicKey(p)
101		if err != nil {
102			log.Fatalf("Failed to marshal public key: %v", err)
103		}
104	case ed25519.PublicKey:
105		keyType = "ed25519"
106		pubBytes = p
107	default:
108		panic("unreachable")
109	}
110
111	params := []string{
112		"v=DKIM1",
113		"k=" + keyType,
114		"p=" + base64.StdEncoding.EncodeToString(pubBytes),
115	}
116	return strings.Join(params, "; ")
117}