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}