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}