/
testca.go
152 lines (138 loc) · 3.96 KB
/
testca.go
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
146
147
148
149
150
151
152
package testca
import (
"crypto/ecdsa"
"crypto/elliptic"
rand "crypto/rand"
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"fmt"
"math/big"
"time"
"github.com/PowerDNS/go-tlsconfig"
)
// Options are options for the test CA
type Options struct {
ExpiresAfter time.Duration // default: 1 hour
}
// New creates a testCA that can generate configurations for testing.
//
// Heavily inspired by:
// https://www.youtube.com/watch?v=VwPQKS9Njv0
// https://docs.google.com/presentation/d/16y-HTvL7ASzf9JspCBX0OVmhwUWVoLj9epzJfNMQRr8/edit
func New(opts Options) (*TestCA, error) {
if opts.ExpiresAfter == 0 {
opts.ExpiresAfter = time.Hour
}
caPriv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil {
return nil, fmt.Errorf("ca: generate: %v", err)
}
// Generate a self-signed certificate
caTmpl := &x509.Certificate{
Subject: pkix.Name{CommonName: "my-ca"},
SerialNumber: newSerialNum(),
BasicConstraintsValid: true,
IsCA: true,
NotBefore: time.Now(),
NotAfter: time.Now().Add(opts.ExpiresAfter),
KeyUsage: x509.KeyUsageKeyEncipherment |
x509.KeyUsageDigitalSignature |
x509.KeyUsageCertSign,
}
caCertDER, err := x509.CreateCertificate(rand.Reader, caTmpl, caTmpl, caPriv.Public(), caPriv)
if err != nil {
return nil, fmt.Errorf("ca: create: %v", err)
}
caPrivDER, err := x509.MarshalECPrivateKey(caPriv)
if err != nil {
return nil, fmt.Errorf("ca: marshal: %v", err)
}
// PEM encode the certificate and private key
caCertPEM := pemEncode(caCertDER, "CERTIFICATE")
caPrivPEM := pemEncode(caPrivDER, "EC PRIVATE KEY")
caCert, err := x509.ParseCertificate(caCertDER)
if err != nil {
return nil, err
}
ca := &TestCA{
Opts: opts,
CertPEM: caCertPEM,
PrivPEM: caPrivPEM,
CertDER: caCertDER,
PrivDER: caPrivDER,
Priv: caPriv,
Cert: caCert,
}
return ca, nil
}
type TestCA struct {
Opts Options
CertPEM []byte
PrivPEM []byte
CertDER []byte
PrivDER []byte
Priv *ecdsa.PrivateKey
Cert *x509.Certificate
}
func (ca *TestCA) createCert(name string, isClient bool) (certPEM, privPEM []byte, err error) {
// Generate a key pair and certificate template
priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil {
return nil, nil, err
}
tmpl := &x509.Certificate{
Subject: pkix.Name{CommonName: name},
SerialNumber: newSerialNum(),
NotBefore: time.Now(),
NotAfter: time.Now().Add(ca.Opts.ExpiresAfter),
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
}
if isClient {
tmpl.ExtKeyUsage = []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth}
} else {
tmpl.ExtKeyUsage = []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}
tmpl.DNSNames = []string{name}
}
// Sign the serving cert with the CA private key
certDER, err := x509.CreateCertificate(rand.Reader, tmpl, ca.Cert, priv.Public(), ca.Priv)
if err != nil {
return nil, nil, err
}
privDER, err := x509.MarshalECPrivateKey(priv)
if err != nil {
return nil, nil, err
}
certPEM = pemEncode(certDER, "CERTIFICATE")
privPEM = pemEncode(privDER, "EC PRIVATE KEY")
return certPEM, privPEM, nil
}
func (ca *TestCA) ServerConfig(name string) (tlsconfig.Config, error) {
certPEM, privPEM, err := ca.createCert(name, false)
if err != nil {
return tlsconfig.Config{}, err
}
return tlsconfig.Config{
CA: string(ca.CertPEM),
Cert: string(certPEM),
Key: string(privPEM),
RequireClientCert: true,
}, nil
}
func (ca *TestCA) ClientConfig(name string) (tlsconfig.Config, error) {
certPEM, privPEM, err := ca.createCert(name, true)
if err != nil {
return tlsconfig.Config{}, err
}
return tlsconfig.Config{
CA: string(ca.CertPEM),
Cert: string(certPEM),
Key: string(privPEM),
}, nil
}
func pemEncode(data []byte, typeName string) []byte {
return pem.EncodeToMemory(&pem.Block{Bytes: data, Type: typeName})
}
func newSerialNum() *big.Int {
return big.NewInt(time.Now().UnixNano())
}