Skip to content

Commit 92fd623

Browse files
FileGo0xERR0R
andauthored
Self-signed certificate generation (#532)
* Added self-signed certificate functionality Co-authored-by: Dimitri Herzog <dimitri.herzog@gmail.com>
1 parent 0731ebe commit 92fd623

File tree

6 files changed

+149
-92
lines changed

6 files changed

+149
-92
lines changed

config/config.go

+2-16
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ import (
1717
"github.com/miekg/dns"
1818

1919
"github.com/hako/durafmt"
20-
"github.com/hashicorp/go-multierror"
2120

2221
"github.com/0xERR0R/blocky/log"
2322
"github.com/creasty/defaults"
@@ -609,30 +608,17 @@ func unmarshalConfig(data []byte, cfg *Config) error {
609608
return fmt.Errorf("wrong file structure: %w", err)
610609
}
611610

612-
err = validateConfig(cfg)
613-
if err != nil {
614-
return fmt.Errorf("unable to validate config: %w", err)
615-
}
611+
validateConfig(cfg)
616612

617613
return nil
618614
}
619615

620-
func validateConfig(cfg *Config) (err error) {
621-
if len(cfg.TLSPorts) != 0 && (cfg.CertFile == "" || cfg.KeyFile == "") {
622-
err = multierror.Append(err, errors.New("'certFile' and 'keyFile' parameters are mandatory for TLS"))
623-
}
624-
625-
if len(cfg.HTTPSPorts) != 0 && (cfg.CertFile == "" || cfg.KeyFile == "") {
626-
err = multierror.Append(err, errors.New("'certFile' and 'keyFile' parameters are mandatory for HTTPS"))
627-
}
628-
616+
func validateConfig(cfg *Config) {
629617
if cfg.DisableIPv6 {
630618
log.Log().Warnf("'disableIPv6' is deprecated. Please use 'filtering.queryTypes' with 'AAAA' instead.")
631619

632620
cfg.Filtering.QueryTypes.Insert(dns.Type(dns.TypeAAAA))
633621
}
634-
635-
return
636622
}
637623

638624
// GetConfig returns the current config

config/config_test.go

+1-62
Original file line numberDiff line numberDiff line change
@@ -161,78 +161,17 @@ bootstrapDns:
161161
})
162162
})
163163

164-
When("Validation fails", func() {
165-
It("should return error", func() {
166-
cfg := Config{}
167-
data :=
168-
`httpsPort: 443`
169-
err := unmarshalConfig([]byte(data), &cfg)
170-
Expect(err).Should(HaveOccurred())
171-
Expect(err.Error()).Should(ContainSubstring("'certFile' and 'keyFile' parameters are mandatory for HTTPS"))
172-
})
173-
})
174-
175-
When("TlsPort is defined", func() {
176-
It("certFile/keyFile must be set", func() {
177-
178-
By("certFile/keyFile not set", func() {
179-
c := &Config{
180-
TLSPorts: ListenConfig{"953"},
181-
}
182-
err := validateConfig(c)
183-
Expect(err).Should(HaveOccurred())
184-
Expect(err.Error()).Should(ContainSubstring("'certFile' and 'keyFile' parameters are mandatory for TLS"))
185-
})
186-
187-
By("certFile/keyFile set", func() {
188-
c := &Config{
189-
TLSPorts: ListenConfig{"953"},
190-
KeyFile: "key",
191-
CertFile: "cert",
192-
}
193-
err := validateConfig(c)
194-
Expect(err).Should(Succeed())
195-
196-
})
197-
})
198-
})
199-
200164
When("Deprecated parameter 'disableIPv6' is set", func() {
201165
It("should add 'AAAA' to filter.queryTypes", func() {
202166
c := &Config{
203167
DisableIPv6: true,
204168
}
205-
err := validateConfig(c)
206-
Expect(err).Should(Succeed())
169+
validateConfig(c)
207170
Expect(c.Filtering.QueryTypes).Should(HaveKey(QType(dns.TypeAAAA)))
208171
Expect(c.Filtering.QueryTypes.Contains(dns.Type(dns.TypeAAAA))).Should(BeTrue())
209172
})
210173
})
211174

212-
When("HttpsPort is defined", func() {
213-
It("certFile/keyFile must be set", func() {
214-
215-
By("certFile/keyFile not set", func() {
216-
c := &Config{
217-
HTTPSPorts: ListenConfig{"443"},
218-
}
219-
err := validateConfig(c)
220-
Expect(err).Should(HaveOccurred())
221-
Expect(err.Error()).Should(ContainSubstring("'certFile' and 'keyFile' parameters are mandatory for HTTPS"))
222-
})
223-
224-
By("certFile/keyFile set", func() {
225-
c := &Config{
226-
TLSPorts: ListenConfig{"443"},
227-
KeyFile: "key",
228-
CertFile: "cert",
229-
}
230-
err := validateConfig(c)
231-
Expect(err).Should(Succeed())
232-
})
233-
})
234-
})
235-
236175
When("config directory does not exist", func() {
237176
It("should return error", func() {
238177
err := os.Chdir("../..")

docs/config.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -185,7 +185,7 @@ port: 53
185185
# optional: HTTPS listener port(s) and bind ip address(es), default empty = no http listener. If > 0, will be used for prometheus metrics, pprof, REST API, DoH... Example: 443, :443, 127.0.0.1:443
186186
httpPort: 4000
187187
#httpsPort: 443
188-
# mandatory, if https port > 0: path to cert and key file for SSL encryption
188+
# if https port > 0: path to cert and key file for SSL encryption. if not set, self-signed certificate will be generated
189189
#certFile: server.crt
190190
#keyFile: server.key
191191
# optional: use this DNS server to resolve blacklist urls and upstream DNS servers. Useful if no DNS resolver is configured and blocky needs to resolve a host name. Format net:IP:port, net must be udp or tcp

docs/configuration.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@ configuration properties as [JSON](config.yml).
1717
| tlsPort | [IP]:port[,[IP]:port]* | no | | Port(s) and optional bind ip address(es) to serve DoT DNS endpoint (DNS-over-TLS). If you wish to specify a specific IP, you can do so such as `192.168.0.1:853`. Example: `83`, `:853`, `127.0.0.1:853,[::1]:853` |
1818
| httpPort | [IP]:port[,[IP]:port]* | no | | Port(s) and optional bind ip address(es) to serve HTTP used for prometheus metrics, pprof, REST API, DoH... If you wish to specify a specific IP, you can do so such as `192.168.0.1:4000`. Example: `4000`, `:4000`, `127.0.0.1:4000,[::1]:4000` |
1919
| httpsPort | [IP]:port[,[IP]:port]* | no | | Port(s) and optional bind ip address(es) to serve HTTPS used for prometheus metrics, pprof, REST API, DoH... If you wish to specify a specific IP, you can do so such as `192.168.0.1:443`. Example: `443`, `:443`, `127.0.0.1:443,[::1]:443` |
20-
| certFile | path | yes, if httpsPort > 0 | | Path to cert and key file for SSL encryption (DoH and DoT) |
21-
| keyFile | path | yes, if httpsPort > 0 | | Path to cert and key file for SSL encryption (DoH and DoT)
20+
| certFile | path | no | | Path to cert and key file for SSL encryption (DoH and DoT); if empty, self-signed certificate is generated |
21+
| keyFile | path | no | | Path to cert and key file for SSL encryption (DoH and DoT); if empty, self-signed certificate is generated |
2222
| logLevel | enum (debug, info, warn, error) | no | info | Log level |
2323
| logFormat | enum (text, json) | no | text | Log format (text or json). |
2424
| logTimestamp | bool | no | true | Log time stamps (true or false). |

server/server.go

+120-11
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,15 @@
11
package server
22

33
import (
4+
"bytes"
5+
"crypto/rand"
6+
"crypto/rsa"
47
"crypto/tls"
8+
"crypto/x509"
9+
"encoding/pem"
510
"fmt"
11+
"math/big"
12+
mrand "math/rand"
613
"net"
714
"net/http"
815
"runtime"
@@ -27,6 +34,9 @@ import (
2734

2835
const (
2936
maxUDPBufferSize = 65535
37+
caExpiryYears = 10
38+
certExpiryYears = 5
39+
certRSAsize = 4096
3040
)
3141

3242
// Server controls the endpoints for DNS and HTTP
@@ -37,6 +47,7 @@ type Server struct {
3747
queryResolver resolver.Resolver
3848
cfg *config.Config
3949
httpMux *chi.Mux
50+
cert tls.Certificate
4051
}
4152

4253
func logger() *logrus.Entry {
@@ -67,10 +78,27 @@ func getServerAddress(addr string) string {
6778
type NewServerFunc func(address string) (*dns.Server, error)
6879

6980
// NewServer creates new server instance with passed config
81+
// nolint:funlen
7082
func NewServer(cfg *config.Config) (server *Server, err error) {
7183
log.ConfigureLogger(cfg.LogLevel, cfg.LogFormat, cfg.LogTimestamp)
7284

73-
dnsServers, err := createServers(cfg)
85+
var cert tls.Certificate
86+
87+
if cfg.CertFile == "" && cfg.KeyFile == "" {
88+
cert, err = createSelfSignedCert()
89+
if err != nil {
90+
return nil, fmt.Errorf("unable to generate self-signed certificate: %w", err)
91+
}
92+
93+
log.Log().Info("using self-signed certificate")
94+
} else {
95+
cert, err = tls.LoadX509KeyPair(cfg.CertFile, cfg.KeyFile)
96+
if err != nil {
97+
return nil, fmt.Errorf("can't load certificate files: %w", err)
98+
}
99+
}
100+
101+
dnsServers, err := createServers(cfg, cert)
74102
if err != nil {
75103
return nil, fmt.Errorf("server creation failed: %w", err)
76104
}
@@ -110,6 +138,7 @@ func NewServer(cfg *config.Config) (server *Server, err error) {
110138
httpListeners: httpListeners,
111139
httpsListeners: httpsListeners,
112140
httpMux: router,
141+
cert: cert,
113142
}
114143

115144
server.printConfiguration()
@@ -122,7 +151,7 @@ func NewServer(cfg *config.Config) (server *Server, err error) {
122151
return server, err
123152
}
124153

125-
func createServers(cfg *config.Config) ([]*dns.Server, error) {
154+
func createServers(cfg *config.Config, cert tls.Certificate) ([]*dns.Server, error) {
126155
var dnsServers []*dns.Server
127156

128157
var err *multierror.Error
@@ -144,7 +173,7 @@ func createServers(cfg *config.Config) ([]*dns.Server, error) {
144173
addServers(createUDPServer, cfg.DNSPorts),
145174
addServers(createTCPServer, cfg.DNSPorts),
146175
addServers(func(address string) (*dns.Server, error) {
147-
return createTLSServer(address, cfg.CertFile, cfg.KeyFile)
176+
return createTLSServer(address, cert)
148177
}, cfg.TLSPorts))
149178

150179
return dnsServers, err.ErrorOrNil()
@@ -191,17 +220,12 @@ func registerResolverAPIEndpoints(router chi.Router, res resolver.Resolver) {
191220
}
192221
}
193222

194-
func createTLSServer(address string, certFile string, keyFile string) (*dns.Server, error) {
195-
cer, err := tls.LoadX509KeyPair(certFile, keyFile)
196-
if err != nil {
197-
return nil, fmt.Errorf("can't load certificate files: %w", err)
198-
}
199-
223+
func createTLSServer(address string, cert tls.Certificate) (*dns.Server, error) {
200224
return &dns.Server{
201225
Addr: address,
202226
Net: "tcp-tls",
203227
TLSConfig: &tls.Config{
204-
Certificates: []tls.Certificate{cer},
228+
Certificates: []tls.Certificate{cert},
205229
MinVersion: tls.VersionTLS12,
206230
CipherSuites: tlsCipherSuites(),
207231
},
@@ -235,6 +259,90 @@ func createUDPServer(address string) (*dns.Server, error) {
235259
}, nil
236260
}
237261

262+
// nolint:funlen
263+
func createSelfSignedCert() (tls.Certificate, error) {
264+
// Create CA
265+
ca := &x509.Certificate{
266+
SerialNumber: big.NewInt(int64(mrand.Intn(certRSAsize))), //nolint:gosec
267+
NotBefore: time.Now(),
268+
NotAfter: time.Now().AddDate(caExpiryYears, 0, 0),
269+
IsCA: true,
270+
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageAny},
271+
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
272+
BasicConstraintsValid: true,
273+
}
274+
275+
caPrivKey, err := rsa.GenerateKey(rand.Reader, certRSAsize)
276+
if err != nil {
277+
return tls.Certificate{}, err
278+
}
279+
280+
caBytes, err := x509.CreateCertificate(rand.Reader, ca, ca, &caPrivKey.PublicKey, caPrivKey)
281+
if err != nil {
282+
return tls.Certificate{}, err
283+
}
284+
285+
caPEM := new(bytes.Buffer)
286+
if err = pem.Encode(caPEM, &pem.Block{
287+
Type: "CERTIFICATE",
288+
Bytes: caBytes,
289+
}); err != nil {
290+
return tls.Certificate{}, err
291+
}
292+
293+
caPrivKeyPEM := new(bytes.Buffer)
294+
if err = pem.Encode(caPrivKeyPEM, &pem.Block{
295+
Type: "RSA PRIVATE KEY",
296+
Bytes: x509.MarshalPKCS1PrivateKey(caPrivKey),
297+
}); err != nil {
298+
return tls.Certificate{}, err
299+
}
300+
301+
// Create certificate
302+
cert := &x509.Certificate{
303+
SerialNumber: big.NewInt(int64(mrand.Intn(certRSAsize))), //nolint:gosec
304+
DNSNames: []string{"*"},
305+
NotBefore: time.Now(),
306+
NotAfter: time.Now().AddDate(certExpiryYears, 0, 0),
307+
SubjectKeyId: []byte{1, 2, 3, 4, 6},
308+
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth},
309+
KeyUsage: x509.KeyUsageDigitalSignature,
310+
}
311+
312+
certPrivKey, err := rsa.GenerateKey(rand.Reader, certRSAsize)
313+
if err != nil {
314+
return tls.Certificate{}, err
315+
}
316+
317+
certBytes, err := x509.CreateCertificate(rand.Reader, cert, ca, &certPrivKey.PublicKey, caPrivKey)
318+
if err != nil {
319+
return tls.Certificate{}, err
320+
}
321+
322+
certPEM := new(bytes.Buffer)
323+
if err = pem.Encode(certPEM, &pem.Block{
324+
Type: "CERTIFICATE",
325+
Bytes: certBytes,
326+
}); err != nil {
327+
return tls.Certificate{}, err
328+
}
329+
330+
certPrivKeyPEM := new(bytes.Buffer)
331+
if err = pem.Encode(certPrivKeyPEM, &pem.Block{
332+
Type: "RSA PRIVATE KEY",
333+
Bytes: x509.MarshalPKCS1PrivateKey(certPrivKey),
334+
}); err != nil {
335+
return tls.Certificate{}, err
336+
}
337+
338+
keyPair, err := tls.X509KeyPair(certPEM.Bytes(), certPrivKeyPEM.Bytes())
339+
if err != nil {
340+
return tls.Certificate{}, err
341+
}
342+
343+
return keyPair, nil
344+
}
345+
238346
func createQueryResolver(
239347
cfg *config.Config,
240348
bootstrap *resolver.Bootstrap,
@@ -366,10 +474,11 @@ func (s *Server) Start(errCh chan<- error) {
366474
TLSConfig: &tls.Config{
367475
MinVersion: tls.VersionTLS12,
368476
CipherSuites: tlsCipherSuites(),
477+
Certificates: []tls.Certificate{s.cert},
369478
},
370479
}
371480

372-
if err := server.ServeTLS(listener, s.cfg.CertFile, s.cfg.KeyFile); err != nil {
481+
if err := server.ServeTLS(listener, "", ""); err != nil {
373482
errCh <- fmt.Errorf("start https listener failed: %w", err)
374483
}
375484
}()

server/server_test.go

+23
Original file line numberDiff line numberDiff line change
@@ -641,6 +641,29 @@ var _ = Describe("Running DNS server", func() {
641641
})
642642
})
643643

644+
Describe("self-signed certificate creation", func() {
645+
var (
646+
cfg config.Config
647+
cErr error
648+
)
649+
BeforeEach(func() {
650+
cErr = defaults.Set(&cfg)
651+
652+
Expect(cErr).Should(Succeed())
653+
654+
cfg.Upstream.ExternalResolvers = map[string][]config.Upstream{
655+
"default": {config.Upstream{Net: config.NetProtocolTcpUdp, Host: "4.4.4.4", Port: 53}}}
656+
})
657+
658+
It("should create self-signed certificate if key/cert files are not provided", func() {
659+
cfg.KeyFile = ""
660+
cfg.CertFile = ""
661+
662+
sut, err := NewServer(&cfg)
663+
Expect(err).Should(Succeed())
664+
Expect(sut.cert.Certificate).ShouldNot(BeNil())
665+
})
666+
})
644667
})
645668

646669
func requestServer(request *dns.Msg) *dns.Msg {

0 commit comments

Comments
 (0)