all repos — postern

@ v0.1.0-alpha

Modern mail management

clone
git clone https:///postern.git

internal/dkim/keys.go (view raw)

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