1 package rocsp_config
2
3 import (
4 "bytes"
5 "crypto/x509/pkix"
6 "encoding/asn1"
7 "errors"
8 "fmt"
9 "strings"
10
11 "github.com/jmhodges/clock"
12 "github.com/prometheus/client_golang/prometheus"
13 "github.com/redis/go-redis/v9"
14 "golang.org/x/crypto/ocsp"
15
16 "github.com/letsencrypt/boulder/cmd"
17 "github.com/letsencrypt/boulder/config"
18 "github.com/letsencrypt/boulder/issuance"
19 bredis "github.com/letsencrypt/boulder/redis"
20 "github.com/letsencrypt/boulder/rocsp"
21 )
22
23
24
25
26
27 type RedisConfig struct {
28
29 cmd.PasswordConfig
30
31 TLS cmd.TLSConfig
32
33 Username string `validate:"required"`
34
35
36
37 ShardAddrs map[string]string `validate:"min=1,dive,hostname_port"`
38
39 Timeout config.Duration `validate:"-"`
40
41
42 ReadOnly bool
43
44
45 RouteByLatency bool
46
47
48 RouteRandomly bool
49
50
51 PoolFIFO bool
52
53
54
55 MaxRetries int `validate:"min=0"`
56
57
58 MinRetryBackoff config.Duration `validate:"-"`
59
60
61 MaxRetryBackoff config.Duration `validate:"-"`
62
63
64
65 DialTimeout config.Duration `validate:"-"`
66
67
68
69 ReadTimeout config.Duration `validate:"-"`
70
71
72
73 WriteTimeout config.Duration `validate:"-"`
74
75
76
77
78
79
80 PoolSize int `validate:"min=0"`
81
82
83 MinIdleConns int `validate:"min=0"`
84
85
86 MaxConnAge config.Duration `validate:"-"`
87
88
89
90 PoolTimeout config.Duration `validate:"-"`
91
92
93
94 IdleTimeout config.Duration `validate:"-"`
95
96
97
98
99
100 IdleCheckFrequency config.Duration `validate:"-"`
101 }
102
103
104 func MakeClient(c *RedisConfig, clk clock.Clock, stats prometheus.Registerer) (*rocsp.RWClient, error) {
105 password, err := c.PasswordConfig.Pass()
106 if err != nil {
107 return nil, fmt.Errorf("loading password: %w", err)
108 }
109
110 tlsConfig, err := c.TLS.Load(stats)
111 if err != nil {
112 return nil, fmt.Errorf("loading TLS config: %w", err)
113 }
114
115 rdb := redis.NewRing(&redis.RingOptions{
116 Addrs: c.ShardAddrs,
117 Username: c.Username,
118 Password: password,
119 TLSConfig: tlsConfig,
120
121 MaxRetries: c.MaxRetries,
122 MinRetryBackoff: c.MinRetryBackoff.Duration,
123 MaxRetryBackoff: c.MaxRetryBackoff.Duration,
124 DialTimeout: c.DialTimeout.Duration,
125 ReadTimeout: c.ReadTimeout.Duration,
126 WriteTimeout: c.WriteTimeout.Duration,
127
128 PoolSize: c.PoolSize,
129 MinIdleConns: c.MinIdleConns,
130 ConnMaxLifetime: c.MaxConnAge.Duration,
131 PoolTimeout: c.PoolTimeout.Duration,
132 ConnMaxIdleTime: c.IdleTimeout.Duration,
133 })
134 return rocsp.NewWritingClient(rdb, c.Timeout.Duration, clk, stats), nil
135 }
136
137
138 func MakeReadClient(c *RedisConfig, clk clock.Clock, stats prometheus.Registerer) (*rocsp.ROClient, error) {
139 if len(c.ShardAddrs) == 0 {
140 return nil, errors.New("redis config's 'shardAddrs' field was empty")
141 }
142
143 password, err := c.PasswordConfig.Pass()
144 if err != nil {
145 return nil, fmt.Errorf("loading password: %w", err)
146 }
147
148 tlsConfig, err := c.TLS.Load(stats)
149 if err != nil {
150 return nil, fmt.Errorf("loading TLS config: %w", err)
151 }
152
153 rdb := redis.NewRing(&redis.RingOptions{
154 Addrs: c.ShardAddrs,
155 Username: c.Username,
156 Password: password,
157 TLSConfig: tlsConfig,
158
159 PoolFIFO: c.PoolFIFO,
160
161 MaxRetries: c.MaxRetries,
162 MinRetryBackoff: c.MinRetryBackoff.Duration,
163 MaxRetryBackoff: c.MaxRetryBackoff.Duration,
164 DialTimeout: c.DialTimeout.Duration,
165 ReadTimeout: c.ReadTimeout.Duration,
166
167 PoolSize: c.PoolSize,
168 MinIdleConns: c.MinIdleConns,
169 ConnMaxLifetime: c.MaxConnAge.Duration,
170 PoolTimeout: c.PoolTimeout.Duration,
171 ConnMaxIdleTime: c.IdleTimeout.Duration,
172 })
173 bredis.MustRegisterClientMetricsCollector(rdb, stats, rdb.Options().Addrs, rdb.Options().Username)
174 return rocsp.NewReadingClient(rdb, c.Timeout.Duration, clk, stats), nil
175 }
176
177
178
179 type ShortIDIssuer struct {
180 *issuance.Certificate
181 subject pkix.RDNSequence
182 shortID byte
183 }
184
185
186
187
188 func LoadIssuers(input map[string]int) ([]ShortIDIssuer, error) {
189 var issuers []ShortIDIssuer
190 for issuerFile, shortID := range input {
191 if shortID > 255 || shortID < 0 {
192 return nil, fmt.Errorf("invalid shortID %d (must be byte)", shortID)
193 }
194 cert, err := issuance.LoadCertificate(issuerFile)
195 if err != nil {
196 return nil, fmt.Errorf("reading issuer: %w", err)
197 }
198 var subject pkix.RDNSequence
199 _, err = asn1.Unmarshal(cert.Certificate.RawSubject, &subject)
200 if err != nil {
201 return nil, fmt.Errorf("parsing issuer.RawSubject: %w", err)
202 }
203 shortID := byte(shortID)
204 for _, issuer := range issuers {
205 if issuer.shortID == shortID {
206 return nil, fmt.Errorf("duplicate shortID '%d' in (for %q and %q) in config file", shortID, issuer.subject, subject)
207 }
208 if !issuer.IsCA {
209 return nil, fmt.Errorf("certificate for %q is not a CA certificate", subject)
210 }
211 }
212 issuers = append(issuers, ShortIDIssuer{
213 Certificate: cert,
214 subject: subject,
215 shortID: shortID,
216 })
217 }
218 return issuers, nil
219 }
220
221
222
223 func (si *ShortIDIssuer) ShortID() byte {
224 return si.shortID
225 }
226
227
228 func FindIssuerByID(longID int64, issuers []ShortIDIssuer) (*ShortIDIssuer, error) {
229 for _, iss := range issuers {
230 if iss.NameID() == issuance.IssuerNameID(longID) || iss.ID() == issuance.IssuerID(longID) {
231 return &iss, nil
232 }
233 }
234 return nil, fmt.Errorf("no issuer found for an ID in certificateStatus: %d", longID)
235 }
236
237
238 func FindIssuerByName(resp *ocsp.Response, issuers []ShortIDIssuer) (*ShortIDIssuer, error) {
239 var responder pkix.RDNSequence
240 _, err := asn1.Unmarshal(resp.RawResponderName, &responder)
241 if err != nil {
242 return nil, fmt.Errorf("parsing resp.RawResponderName: %w", err)
243 }
244 var responders strings.Builder
245 for _, issuer := range issuers {
246 fmt.Fprintf(&responders, "%s\n", issuer.subject)
247 if bytes.Equal(issuer.RawSubject, resp.RawResponderName) {
248 return &issuer, nil
249 }
250 }
251 return nil, fmt.Errorf("no issuer found matching OCSP response for %s. Available issuers:\n%s\n", responder, responders.String())
252 }
253
View as plain text