-
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathconfig.go
More file actions
145 lines (123 loc) · 3.89 KB
/
config.go
File metadata and controls
145 lines (123 loc) · 3.89 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
// Copyright (c) 2022, Roel Schut. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package easytls
import (
"crypto/tls"
"crypto/x509"
"strconv"
"github.com/go-pogo/errors"
)
type Target uint8
const (
TargetServer Target = iota + 1
TargetClient
ErrAddCACertToPool errors.Msg = "failed to add CA certificate to pool"
ErrNotMarkedAsCA errors.Msg = "certificate is not marked as a CA"
ErrMissingCertSign errors.Msg = "certificate is missing the cert sign flag"
)
type InvalidTarget struct {
Target Target
}
func (e InvalidTarget) Error() string {
return "invalid target " + strconv.FormatUint(uint64(e.Target), 10)
}
var _ Option = (*Config)(nil)
// Config is a general config struct which can be used to configure or create
// a [tls.Config] for clients or servers.
type Config struct {
// CACertFile is the path to the root certificate authority (CA) file. It
// is used to verify both client and server certificates are signed by the
// same CA.
CACertFile CertificateFile `env:""`
// CertFile is the path to the certificate file.
CertFile string `env:""`
// KeyFile is the path to the private key file.
KeyFile string `env:""`
// VerifyClient enables mutual TLS authentication.
VerifyClient bool `env:""`
// InsecureSkipVerify disabled all certificate verification and should only
// be used for testing. See [tls.Config.InsecureSkipVerify] for additional
// information.
InsecureSkipVerify bool `env:""`
}
// Client creates a [tls.Config] for client connections. It is based on
// [DefaultTLSConfig], with [Config] applied to it.
func (tc Config) Client() (*tls.Config, error) {
conf := DefaultTLSConfig()
return conf, tc.ApplyTo(conf, TargetClient)
}
// Server creates a [tls.Config] for server connections. It is based on
// [DefaultTLSConfig], with [Config] applied to it.
func (tc Config) Server() (*tls.Config, error) {
conf := DefaultTLSConfig()
return conf, tc.ApplyTo(conf, TargetServer)
}
// ApplyTo applies the [Config] fields' values to the provided [tls.Config] for
// the specified [Target].
func (tc Config) ApplyTo(conf *tls.Config, target Target) error {
if conf == nil {
return nil
}
conf.InsecureSkipVerify = tc.InsecureSkipVerify
if tc.CACertFile != "" {
cert, err := tc.CACertFile.LoadX509Certificate()
if err != nil {
return errors.Wrap(err, ErrAddCACertToPool)
}
if valid, err := ValidateCA(cert); err != nil {
return errors.Wrap(err, ErrAddCACertToPool)
} else if valid {
pool, err := getCertPool(conf, target)
if err != nil {
return err
}
pool.AddCert(cert)
}
}
if tc.VerifyClient {
if conf.InsecureSkipVerify {
conf.ClientAuth = tls.RequireAnyClientCert
} else {
conf.ClientAuth = tls.RequireAndVerifyClientCert
}
}
kp := KeyPair{CertFile: tc.CertFile, KeyFile: tc.KeyFile}
if err := kp.ApplyTo(conf, target); err != nil {
return err
}
return nil
}
// ValidateCA checks if the provided [x509.Certificate] can be used as CA
// certificate. It return true when the certificate is valid, otherwise false.
// Any reasons why the certificate is invalid will be returned as an error, or
// nil when no non-nil certificate is provided.
func ValidateCA(cert *x509.Certificate) (bool, error) {
if cert == nil {
return false, nil
}
var err error
if !cert.IsCA {
err = errors.New(ErrNotMarkedAsCA)
}
if cert.KeyUsage == 0 || cert.KeyUsage&x509.KeyUsageCertSign == 0 {
err = errors.Append(err, errors.New(ErrMissingCertSign))
}
return err == nil, err
}
func getCertPool(conf *tls.Config, target Target) (*x509.CertPool, error) {
switch target {
case TargetServer:
if conf.ClientCAs == nil {
conf.ClientCAs = x509.NewCertPool()
}
return conf.ClientCAs, nil
case TargetClient:
if conf.RootCAs == nil {
conf.RootCAs = x509.NewCertPool()
}
return conf.RootCAs, nil
default:
return nil, errors.WithStack(&InvalidTarget{Target: target})
}
}